Merge "Restructure the top level of VibrationThread to more clearly guarantee execution of link/unlink pairs, and vibration completion callbacks." into sc-v2-dev
diff --git a/services/core/java/com/android/server/vibrator/Vibration.java b/services/core/java/com/android/server/vibrator/Vibration.java
index e447a23..245f453 100644
--- a/services/core/java/com/android/server/vibrator/Vibration.java
+++ b/services/core/java/com/android/server/vibrator/Vibration.java
@@ -44,9 +44,11 @@
enum Status {
RUNNING,
FINISHED,
+ FINISHED_UNEXPECTED, // Didn't terminate in the usual way.
FORWARDED_TO_INPUT_DEVICES,
CANCELLED,
IGNORED_ERROR_APP_OPS,
+ IGNORED_ERROR_TOKEN,
IGNORED,
IGNORED_APP_OPS,
IGNORED_BACKGROUND,
diff --git a/services/core/java/com/android/server/vibrator/VibrationThread.java b/services/core/java/com/android/server/vibrator/VibrationThread.java
index 630bf0a..25321c1 100644
--- a/services/core/java/com/android/server/vibrator/VibrationThread.java
+++ b/services/core/java/com/android/server/vibrator/VibrationThread.java
@@ -47,6 +47,7 @@
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
+import java.util.NoSuchElementException;
import java.util.PriorityQueue;
import java.util.Queue;
@@ -110,6 +111,8 @@
private volatile boolean mStop;
private volatile boolean mForceStop;
+ // Variable only set and read in main thread.
+ private boolean mCalledVibrationCompleteCallback = false;
VibrationThread(Vibration vib, VibrationSettings vibrationSettings,
DeviceVibrationEffectAdapter effectAdapter,
@@ -150,18 +153,53 @@
@Override
public void run() {
- Process.setThreadPriority(Process.THREAD_PRIORITY_URGENT_DISPLAY);
+ // Structured to guarantee the vibrators completed and released callbacks at the end of
+ // thread execution. Both of these callbacks are exclusively called from this thread.
+ try {
+ try {
+ Process.setThreadPriority(Process.THREAD_PRIORITY_URGENT_DISPLAY);
+ runWithWakeLock();
+ } finally {
+ clientVibrationCompleteIfNotAlready(Vibration.Status.FINISHED_UNEXPECTED);
+ }
+ } finally {
+ mCallbacks.onVibratorsReleased();
+ }
+ }
+
+ /** Runs the VibrationThread ensuring that the wake lock is acquired and released. */
+ private void runWithWakeLock() {
mWakeLock.setWorkSource(mWorkSource);
mWakeLock.acquire();
try {
+ runWithWakeLockAndDeathLink();
+ } finally {
+ mWakeLock.release();
+ }
+ }
+
+ /**
+ * Runs the VibrationThread with the binder death link, handling link/unlink failures.
+ * Called from within runWithWakeLock.
+ */
+ private void runWithWakeLockAndDeathLink() {
+ try {
mVibration.token.linkToDeath(this, 0);
- playVibration();
- mCallbacks.onVibratorsReleased();
} catch (RemoteException e) {
Slog.e(TAG, "Error linking vibration to token death", e);
+ clientVibrationCompleteIfNotAlready(Vibration.Status.IGNORED_ERROR_TOKEN);
+ return;
+ }
+ // Ensure that the unlink always occurs now.
+ try {
+ // This is the actual execution of the vibration.
+ playVibration();
} finally {
- mVibration.token.unlinkToDeath(this, 0);
- mWakeLock.release();
+ try {
+ mVibration.token.unlinkToDeath(this, 0);
+ } catch (NoSuchElementException e) {
+ Slog.wtf(TAG, "Failed to unlink token", e);
+ }
}
}
@@ -219,6 +257,16 @@
}
}
+ // Indicate that the vibration is complete. This can be called multiple times only for
+ // convenience of handling error conditions - an error after the client is complete won't
+ // affect the status.
+ private void clientVibrationCompleteIfNotAlready(Vibration.Status completedStatus) {
+ if (!mCalledVibrationCompleteCallback) {
+ mCalledVibrationCompleteCallback = true;
+ mCallbacks.onVibrationCompleted(mVibration.id, completedStatus);
+ }
+ }
+
private void playVibration() {
Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "playVibration");
try {
@@ -226,7 +274,6 @@
final int sequentialEffectSize = sequentialEffect.getEffects().size();
mStepQueue.offer(new StartVibrateStep(sequentialEffect));
- Vibration.Status status = null;
while (!mStepQueue.isEmpty()) {
long waitTime;
synchronized (mLock) {
@@ -242,13 +289,12 @@
if (waitTime <= 0) {
mStepQueue.consumeNext();
}
- Vibration.Status currentStatus = mStop ? Vibration.Status.CANCELLED
+ Vibration.Status status = mStop ? Vibration.Status.CANCELLED
: mStepQueue.calculateVibrationStatus(sequentialEffectSize);
- if (status == null && currentStatus != Vibration.Status.RUNNING) {
+ if (status != Vibration.Status.RUNNING && !mCalledVibrationCompleteCallback) {
// First time vibration stopped running, start clean-up tasks and notify
// callback immediately.
- status = currentStatus;
- mCallbacks.onVibrationCompleted(mVibration.id, status);
+ clientVibrationCompleteIfNotAlready(status);
if (status == Vibration.Status.CANCELLED) {
mStepQueue.cancel();
}
@@ -256,19 +302,10 @@
if (mForceStop) {
// Cancel every step and stop playing them right away, even clean-up steps.
mStepQueue.cancelImmediately();
+ clientVibrationCompleteIfNotAlready(Vibration.Status.CANCELLED);
break;
}
}
-
- if (status == null) {
- status = mStepQueue.calculateVibrationStatus(sequentialEffectSize);
- if (status == Vibration.Status.RUNNING) {
- Slog.w(TAG, "Something went wrong, step queue completed but vibration status"
- + " is still RUNNING for vibration " + mVibration.id);
- status = Vibration.Status.FINISHED;
- }
- mCallbacks.onVibrationCompleted(mVibration.id, status);
- }
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
}