Merge "Make VibrationThread long-lived." into tm-dev

This commit is contained in:
Simon Bowden
2022-03-02 17:12:05 +00:00
committed by Android (Google) Code Review
6 changed files with 612 additions and 349 deletions

View File

@@ -49,6 +49,8 @@ final class Vibration {
FORWARDED_TO_INPUT_DEVICES, FORWARDED_TO_INPUT_DEVICES,
CANCELLED, CANCELLED,
IGNORED_ERROR_APP_OPS, IGNORED_ERROR_APP_OPS,
IGNORED_ERROR_CANCELLING,
IGNORED_ERROR_SCHEDULING,
IGNORED_ERROR_TOKEN, IGNORED_ERROR_TOKEN,
IGNORED, IGNORED,
IGNORED_APP_OPS, IGNORED_APP_OPS,

View File

@@ -16,6 +16,8 @@
package com.android.server.vibrator; package com.android.server.vibrator;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.IBinder; import android.os.IBinder;
import android.os.PowerManager; import android.os.PowerManager;
import android.os.Process; import android.os.Process;
@@ -23,9 +25,12 @@ import android.os.RemoteException;
import android.os.Trace; import android.os.Trace;
import android.os.WorkSource; import android.os.WorkSource;
import android.util.Slog; import android.util.Slog;
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import java.util.NoSuchElementException; import java.util.NoSuchElementException;
import java.util.Objects;
/** Plays a {@link Vibration} in dedicated thread. */ /** Plays a {@link Vibration} in dedicated thread. */
final class VibrationThread extends Thread implements IBinder.DeathRecipient { final class VibrationThread extends Thread implements IBinder.DeathRecipient {
@@ -76,31 +81,41 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient {
* Tells the manager that the VibrationThread is finished with the previous vibration and * Tells the manager that the VibrationThread is finished with the previous vibration and
* all of its cleanup tasks, and the vibrators can now be used for another vibration. * all of its cleanup tasks, and the vibrators can now be used for another vibration.
*/ */
void onVibrationThreadReleased(); void onVibrationThreadReleased(long vibrationId);
} }
private final PowerManager.WakeLock mWakeLock; private final PowerManager.WakeLock mWakeLock;
private final VibrationThread.VibratorManagerHooks mVibratorManagerHooks; private final VibrationThread.VibratorManagerHooks mVibratorManagerHooks;
private final VibrationStepConductor mStepConductor; // mLock is used here to communicate that the thread's work status has changed. The
// VibrationThread is expected to wait until work arrives, and other threads may wait until
// work has finished. Therefore, any changes to the conductor must be followed by a notifyAll
// so that threads check if their desired state is achieved.
private final Object mLock = new Object();
private volatile boolean mStop; /**
private volatile boolean mForceStop; * The conductor that is intended to be active. Null value means that a new conductor can
// Variable only set and read in main thread. * be set to run. Note that this field is only reset to null when mExecutingConductor has
* completed, so the two fields should be in sync.
*/
@GuardedBy("mLock")
@Nullable
private VibrationStepConductor mRequestedActiveConductor;
/**
* The conductor being executed by this thread, should only be accessed within this thread's
* execution. i.e. not thread-safe. {@link #mRequestedActiveConductor} is for cross-thread
* signalling.
*/
@Nullable
private VibrationStepConductor mExecutingConductor;
// Variable only set and read in main thread, no need to lock.
private boolean mCalledVibrationCompleteCallback = false; private boolean mCalledVibrationCompleteCallback = false;
VibrationThread(Vibration vib, VibrationSettings vibrationSettings, VibrationThread(PowerManager.WakeLock wakeLock, VibratorManagerHooks vibratorManagerHooks) {
DeviceVibrationEffectAdapter effectAdapter,
SparseArray<VibratorController> availableVibrators, PowerManager.WakeLock wakeLock,
VibratorManagerHooks vibratorManagerHooks) {
mVibratorManagerHooks = vibratorManagerHooks;
mWakeLock = wakeLock; mWakeLock = wakeLock;
mStepConductor = new VibrationStepConductor(vib, vibrationSettings, effectAdapter, mVibratorManagerHooks = vibratorManagerHooks;
availableVibrators, vibratorManagerHooks);
}
Vibration getVibration() {
return mStepConductor.getVibration();
} }
@Override @Override
@@ -108,32 +123,138 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient {
if (DEBUG) { if (DEBUG) {
Slog.d(TAG, "Binder died, cancelling vibration..."); Slog.d(TAG, "Binder died, cancelling vibration...");
} }
mStepConductor.notifyCancelled(/* immediate= */ false); // The binder death link only exists while the conductor is set.
// TODO: move the death linking to be associated with the conductor directly, this
// awkwardness will go away.
VibrationStepConductor conductor;
synchronized (mLock) {
conductor = mRequestedActiveConductor;
}
if (conductor != null) {
conductor.notifyCancelled(/* immediate= */ false);
}
}
/**
* Sets/activates the current vibration. Must only be called after receiving
* onVibratorsReleased from the previous vibration.
*
* @return false if VibrationThread couldn't accept it, which shouldn't happen unless called
* before the release callback.
*/
boolean runVibrationOnVibrationThread(VibrationStepConductor conductor) {
synchronized (mLock) {
if (mRequestedActiveConductor != null) {
Slog.wtf(TAG, "Attempt to start vibration when one already running");
return false;
}
mRequestedActiveConductor = conductor;
mLock.notifyAll();
}
return true;
} }
@Override @Override
public void run() { public void run() {
// Structured to guarantee the vibrators completed and released callbacks at the end of Process.setThreadPriority(Process.THREAD_PRIORITY_URGENT_DISPLAY);
// thread execution. Both of these callbacks are exclusively called from this thread. while (true) {
try { // mExecutingConductor is only modified in this loop.
try { mExecutingConductor = Objects.requireNonNull(waitForVibrationRequest());
Process.setThreadPriority(Process.THREAD_PRIORITY_URGENT_DISPLAY);
runWithWakeLock(); mCalledVibrationCompleteCallback = false;
} finally { runCurrentVibrationWithWakeLock();
clientVibrationCompleteIfNotAlready(Vibration.Status.FINISHED_UNEXPECTED); if (!mExecutingConductor.isFinished()) {
Slog.wtf(TAG, "VibrationThread terminated with unfinished vibration");
} }
} finally { synchronized (mLock) {
mVibratorManagerHooks.onVibrationThreadReleased(); // Allow another vibration to be requested.
mRequestedActiveConductor = null;
}
// The callback is run without holding the lock, as it may initiate another vibration.
// It's safe to notify even if mVibratorConductor has been re-written, as the "wait"
// methods all verify their waited state before returning. In reality though, if the
// manager is waiting for the thread to finish, then there is no pending vibration
// for this thread.
// No point doing this in finally, as if there's an exception, this thread will die
// and be unusable anyway.
mVibratorManagerHooks.onVibrationThreadReleased(mExecutingConductor.getVibration().id);
synchronized (mLock) {
mLock.notifyAll();
}
mExecutingConductor = null;
}
}
/**
* Waits until the VibrationThread has finished processing, timing out after the given
* number of milliseconds. In general, external locking will manage the ordering of this
* with calls to {@link #runVibrationOnVibrationThread}.
*
* @return true if the vibration completed, or false if waiting timed out.
*/
public boolean waitForThreadIdle(long maxWaitMillis) {
long now = System.currentTimeMillis();
long deadline = now + maxWaitMillis;
synchronized (mLock) {
while (true) {
if (mRequestedActiveConductor == null) {
return true; // Done
}
if (now >= deadline) { // Note that thread.wait(0) waits indefinitely.
return false; // Timed out.
}
try {
mLock.wait(deadline - now);
} catch (InterruptedException e) {
Slog.w(TAG, "VibrationThread interrupted waiting to stop, continuing");
}
now = System.currentTimeMillis();
}
}
}
/** Waits for a signal indicating a vibration is ready to run, then returns its conductor. */
@NonNull
private VibrationStepConductor waitForVibrationRequest() {
while (true) {
synchronized (mLock) {
if (mRequestedActiveConductor != null) {
return mRequestedActiveConductor;
}
try {
mLock.wait();
} catch (InterruptedException e) {
Slog.w(TAG, "VibrationThread interrupted waiting to start, continuing");
}
}
}
}
/**
* Only for testing: this method relies on the requested-active conductor, rather than
* the executing conductor that's not intended for other threads.
*
* @return true if the vibration that's currently desired to be active has the given id.
*/
@VisibleForTesting
boolean isRunningVibrationId(long id) {
synchronized (mLock) {
return (mRequestedActiveConductor != null
&& mRequestedActiveConductor.getVibration().id == id);
} }
} }
/** Runs the VibrationThread ensuring that the wake lock is acquired and released. */ /** Runs the VibrationThread ensuring that the wake lock is acquired and released. */
private void runWithWakeLock() { private void runCurrentVibrationWithWakeLock() {
WorkSource workSource = new WorkSource(mStepConductor.getVibration().uid); WorkSource workSource = new WorkSource(mExecutingConductor.getVibration().uid);
mWakeLock.setWorkSource(workSource); mWakeLock.setWorkSource(workSource);
mWakeLock.acquire(); mWakeLock.acquire();
try { try {
runWithWakeLockAndDeathLink(); try {
runCurrentVibrationWithWakeLockAndDeathLink();
} finally {
clientVibrationCompleteIfNotAlready(Vibration.Status.FINISHED_UNEXPECTED);
}
} finally { } finally {
mWakeLock.release(); mWakeLock.release();
mWakeLock.setWorkSource(null); mWakeLock.setWorkSource(null);
@@ -144,8 +265,8 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient {
* Runs the VibrationThread with the binder death link, handling link/unlink failures. * Runs the VibrationThread with the binder death link, handling link/unlink failures.
* Called from within runWithWakeLock. * Called from within runWithWakeLock.
*/ */
private void runWithWakeLockAndDeathLink() { private void runCurrentVibrationWithWakeLockAndDeathLink() {
IBinder vibrationBinderToken = mStepConductor.getVibration().token; IBinder vibrationBinderToken = mExecutingConductor.getVibration().token;
try { try {
vibrationBinderToken.linkToDeath(this, 0); vibrationBinderToken.linkToDeath(this, 0);
} catch (RemoteException e) { } catch (RemoteException e) {
@@ -166,26 +287,6 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient {
} }
} }
/** Cancel current vibration and ramp down the vibrators gracefully. */
public void cancel() {
mStepConductor.notifyCancelled(/* immediate= */ false);
}
/** Cancel current vibration and shuts off the vibrators immediately. */
public void cancelImmediately() {
mStepConductor.notifyCancelled(/* immediate= */ true);
}
/** Notify current vibration that a synced step has completed. */
public void syncedVibrationComplete() {
mStepConductor.notifySyncedVibrationComplete();
}
/** Notify current vibration that a step has completed on given vibrator. */
public void vibratorComplete(int vibratorId) {
mStepConductor.notifyVibratorComplete(vibratorId);
}
// Indicate that the vibration is complete. This can be called multiple times only for // 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 // convenience of handling error conditions - an error after the client is complete won't
// affect the status. // affect the status.
@@ -193,16 +294,16 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient {
if (!mCalledVibrationCompleteCallback) { if (!mCalledVibrationCompleteCallback) {
mCalledVibrationCompleteCallback = true; mCalledVibrationCompleteCallback = true;
mVibratorManagerHooks.onVibrationCompleted( mVibratorManagerHooks.onVibrationCompleted(
mStepConductor.getVibration().id, completedStatus); mExecutingConductor.getVibration().id, completedStatus);
} }
} }
private void playVibration() { private void playVibration() {
Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "playVibration"); Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "playVibration");
try { try {
mStepConductor.prepareToStart(); mExecutingConductor.prepareToStart();
while (!mStepConductor.isFinished()) { while (!mExecutingConductor.isFinished()) {
boolean readyToRun = mStepConductor.waitUntilNextStepIsDue(); boolean readyToRun = mExecutingConductor.waitUntilNextStepIsDue();
// If we waited, don't run the next step, but instead re-evaluate status. // If we waited, don't run the next step, but instead re-evaluate status.
if (readyToRun) { if (readyToRun) {
if (DEBUG) { if (DEBUG) {
@@ -210,10 +311,10 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient {
} }
// Run the step without holding the main lock, to avoid HAL interactions from // Run the step without holding the main lock, to avoid HAL interactions from
// blocking the thread. // blocking the thread.
mStepConductor.runNextStep(); mExecutingConductor.runNextStep();
} }
Vibration.Status status = mStepConductor.calculateVibrationStatus(); Vibration.Status status = mExecutingConductor.calculateVibrationStatus();
// This block can only run once due to mCalledVibrationCompleteCallback. // This block can only run once due to mCalledVibrationCompleteCallback.
if (status != Vibration.Status.RUNNING && !mCalledVibrationCompleteCallback) { if (status != Vibration.Status.RUNNING && !mCalledVibrationCompleteCallback) {
// First time vibration stopped running, start clean-up tasks and notify // First time vibration stopped running, start clean-up tasks and notify

View File

@@ -31,6 +31,7 @@ import android.content.pm.PackageManager;
import android.hardware.vibrator.IVibrator; import android.hardware.vibrator.IVibrator;
import android.os.BatteryStats; import android.os.BatteryStats;
import android.os.Binder; import android.os.Binder;
import android.os.Build;
import android.os.CombinedVibration; import android.os.CombinedVibration;
import android.os.ExternalVibration; import android.os.ExternalVibration;
import android.os.Handler; import android.os.Handler;
@@ -52,6 +53,7 @@ import android.os.VibrationEffect;
import android.os.VibratorInfo; import android.os.VibratorInfo;
import android.os.vibrator.PrebakedSegment; import android.os.vibrator.PrebakedSegment;
import android.os.vibrator.VibrationEffectSegment; import android.os.vibrator.VibrationEffectSegment;
import android.text.TextUtils;
import android.util.Slog; import android.util.Slog;
import android.util.SparseArray; import android.util.SparseArray;
import android.util.proto.ProtoOutputStream; import android.util.proto.ProtoOutputStream;
@@ -91,6 +93,12 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
/** Fixed large duration used to note repeating vibrations to {@link IBatteryStats}. */ /** Fixed large duration used to note repeating vibrations to {@link IBatteryStats}. */
private static final long BATTERY_STATS_REPEATING_VIBRATION_DURATION = 5_000; private static final long BATTERY_STATS_REPEATING_VIBRATION_DURATION = 5_000;
/**
* Maximum millis to wait for a vibration thread cancellation to "clean up" and finish, when
* blocking for an external vibration. In practice, this should be plenty.
*/
private static final long VIBRATION_CANCEL_WAIT_MILLIS = 5000;
/** Lifecycle responsible for initializing this class at the right system server phases. */ /** Lifecycle responsible for initializing this class at the right system server phases. */
public static class Lifecycle extends SystemService { public static class Lifecycle extends SystemService {
private VibratorManagerService mService; private VibratorManagerService mService;
@@ -121,6 +129,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
private final PowerManager.WakeLock mWakeLock; private final PowerManager.WakeLock mWakeLock;
private final IBatteryStats mBatteryStatsService; private final IBatteryStats mBatteryStatsService;
private final Handler mHandler; private final Handler mHandler;
private final VibrationThread mVibrationThread;
private final AppOpsManager mAppOps; private final AppOpsManager mAppOps;
private final NativeWrapper mNativeWrapper; private final NativeWrapper mNativeWrapper;
private final VibratorManagerRecords mVibratorManagerRecords; private final VibratorManagerRecords mVibratorManagerRecords;
@@ -132,9 +141,9 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
@GuardedBy("mLock") @GuardedBy("mLock")
private final SparseArray<AlwaysOnVibration> mAlwaysOnEffects = new SparseArray<>(); private final SparseArray<AlwaysOnVibration> mAlwaysOnEffects = new SparseArray<>();
@GuardedBy("mLock") @GuardedBy("mLock")
private VibrationThread mCurrentVibration; private VibrationStepConductor mCurrentVibration;
@GuardedBy("mLock") @GuardedBy("mLock")
private VibrationThread mNextVibration; private VibrationStepConductor mNextVibration;
@GuardedBy("mLock") @GuardedBy("mLock")
private ExternalVibrationHolder mCurrentExternalVibration; private ExternalVibrationHolder mCurrentExternalVibration;
@GuardedBy("mLock") @GuardedBy("mLock")
@@ -156,7 +165,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
clearNextVibrationLocked(Vibration.Status.CANCELLED); clearNextVibrationLocked(Vibration.Status.CANCELLED);
} }
if (shouldCancelOnScreenOffLocked(mCurrentVibration)) { if (shouldCancelOnScreenOffLocked(mCurrentVibration)) {
mCurrentVibration.cancel(); mCurrentVibration.notifyCancelled(/* immediate= */ false);
} }
} }
} }
@@ -202,6 +211,8 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
PowerManager pm = context.getSystemService(PowerManager.class); PowerManager pm = context.getSystemService(PowerManager.class);
mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "*vibrator*"); mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "*vibrator*");
mWakeLock.setReferenceCounted(true); mWakeLock.setReferenceCounted(true);
mVibrationThread = new VibrationThread(mWakeLock, mVibrationThreadCallbacks);
mVibrationThread.start();
// Load vibrator hardware info. The vibrator ids and manager capabilities are loaded only // Load vibrator hardware info. The vibrator ids and manager capabilities are loaded only
// once and assumed unchanged for the lifecycle of this service. Each individual vibrator // once and assumed unchanged for the lifecycle of this service. Each individual vibrator
@@ -409,7 +420,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
final long ident = Binder.clearCallingIdentity(); final long ident = Binder.clearCallingIdentity();
try { try {
if (mCurrentVibration != null) { if (mCurrentVibration != null) {
mCurrentVibration.cancel(); mCurrentVibration.notifyCancelled(/* immediate= */ false);
} }
Vibration.Status status = startVibrationLocked(vib); Vibration.Status status = startVibrationLocked(vib);
if (status != Vibration.Status.RUNNING) { if (status != Vibration.Status.RUNNING) {
@@ -447,7 +458,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
if (mCurrentVibration != null if (mCurrentVibration != null
&& shouldCancelVibration(mCurrentVibration.getVibration(), && shouldCancelVibration(mCurrentVibration.getVibration(),
usageFilter, token)) { usageFilter, token)) {
mCurrentVibration.cancel(); mCurrentVibration.notifyCancelled(/* immediate= */false);
} }
if (mCurrentExternalVibration != null if (mCurrentExternalVibration != null
&& shouldCancelVibration( && shouldCancelVibration(
@@ -584,7 +595,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
Slog.d(TAG, "Canceling vibration because settings changed: " Slog.d(TAG, "Canceling vibration because settings changed: "
+ (inputDevicesChanged ? "input devices changed" : ignoreStatus)); + (inputDevicesChanged ? "input devices changed" : ignoreStatus));
} }
mCurrentVibration.cancel(); mCurrentVibration.notifyCancelled(/* immediate= */ false);
} }
} }
} }
@@ -626,17 +637,15 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
return Vibration.Status.FORWARDED_TO_INPUT_DEVICES; return Vibration.Status.FORWARDED_TO_INPUT_DEVICES;
} }
VibrationThread vibThread = new VibrationThread(vib, mVibrationSettings, VibrationStepConductor conductor = new VibrationStepConductor(vib, mVibrationSettings,
mDeviceVibrationEffectAdapter, mVibrators, mWakeLock, mDeviceVibrationEffectAdapter, mVibrators, mVibrationThreadCallbacks);
mVibrationThreadCallbacks);
if (mCurrentVibration == null) { if (mCurrentVibration == null) {
return startVibrationThreadLocked(vibThread); return startVibrationOnThreadLocked(conductor);
} }
// If there's already a vibration queued (waiting for the previous one to finish // If there's already a vibration queued (waiting for the previous one to finish
// cancelling), end it cleanly and replace it with the new one. // cancelling), end it cleanly and replace it with the new one.
clearNextVibrationLocked(Vibration.Status.IGNORED_SUPERSEDED); clearNextVibrationLocked(Vibration.Status.IGNORED_SUPERSEDED);
mNextVibration = vibThread; mNextVibration = conductor;
return Vibration.Status.RUNNING; return Vibration.Status.RUNNING;
} finally { } finally {
Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR); Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
@@ -644,16 +653,20 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
} }
@GuardedBy("mLock") @GuardedBy("mLock")
private Vibration.Status startVibrationThreadLocked(VibrationThread vibThread) { private Vibration.Status startVibrationOnThreadLocked(VibrationStepConductor conductor) {
Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "startVibrationThreadLocked"); Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "startVibrationThreadLocked");
try { try {
Vibration vib = vibThread.getVibration(); Vibration vib = conductor.getVibration();
int mode = startAppOpModeLocked(vib.uid, vib.opPkg, vib.attrs); int mode = startAppOpModeLocked(vib.uid, vib.opPkg, vib.attrs);
switch (mode) { switch (mode) {
case AppOpsManager.MODE_ALLOWED: case AppOpsManager.MODE_ALLOWED:
Trace.asyncTraceBegin(Trace.TRACE_TAG_VIBRATOR, "vibration", 0); Trace.asyncTraceBegin(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
mCurrentVibration = vibThread; mCurrentVibration = conductor;
mCurrentVibration.start(); if (!mVibrationThread.runVibrationOnVibrationThread(mCurrentVibration)) {
// Shouldn't happen. The method call already logs a wtf.
mCurrentVibration = null; // Aborted.
return Vibration.Status.IGNORED_ERROR_SCHEDULING;
}
return Vibration.Status.RUNNING; return Vibration.Status.RUNNING;
case AppOpsManager.MODE_ERRORED: case AppOpsManager.MODE_ERRORED:
Slog.w(TAG, "Start AppOpsManager operation errored for uid " + vib.uid); Slog.w(TAG, "Start AppOpsManager operation errored for uid " + vib.uid);
@@ -741,7 +754,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
if (DEBUG) { if (DEBUG) {
Slog.d(TAG, "Synced vibration " + vibrationId + " complete, notifying thread"); Slog.d(TAG, "Synced vibration " + vibrationId + " complete, notifying thread");
} }
mCurrentVibration.syncedVibrationComplete(); mCurrentVibration.notifySyncedVibrationComplete();
} }
} }
} }
@@ -753,7 +766,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
Slog.d(TAG, "Vibration " + vibrationId + " on vibrator " + vibratorId Slog.d(TAG, "Vibration " + vibrationId + " on vibrator " + vibratorId
+ " complete, notifying thread"); + " complete, notifying thread");
} }
mCurrentVibration.vibratorComplete(vibratorId); mCurrentVibration.notifyVibratorComplete(vibratorId);
} }
} }
} }
@@ -1064,11 +1077,11 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
} }
@GuardedBy("mLock") @GuardedBy("mLock")
private boolean shouldCancelOnScreenOffLocked(@Nullable VibrationThread vibrationThread) { private boolean shouldCancelOnScreenOffLocked(@Nullable VibrationStepConductor conductor) {
if (vibrationThread == null) { if (conductor == null) {
return false; return false;
} }
Vibration vib = vibrationThread.getVibration(); Vibration vib = conductor.getVibration();
return mVibrationSettings.shouldCancelVibrationOnScreenOff( return mVibrationSettings.shouldCancelVibrationOnScreenOff(
vib.uid, vib.opPkg, vib.attrs.getUsage()); vib.uid, vib.opPkg, vib.attrs.getUsage());
} }
@@ -1185,21 +1198,27 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
} }
@Override @Override
public void onVibrationThreadReleased() { public void onVibrationThreadReleased(long vibrationId) {
if (DEBUG) { if (DEBUG) {
Slog.d(TAG, "Vibrators released after finished vibration"); Slog.d(TAG, "VibrationThread released after finished vibration");
} }
synchronized (mLock) { synchronized (mLock) {
if (DEBUG) { if (DEBUG) {
Slog.d(TAG, "Processing vibrators released callback"); Slog.d(TAG, "Processing VibrationThread released callback");
}
if (Build.IS_DEBUGGABLE && mCurrentVibration != null
&& mCurrentVibration.getVibration().id != vibrationId) {
Slog.wtf(TAG, TextUtils.formatSimple(
"VibrationId mismatch on release. expected=%d, released=%d",
mCurrentVibration.getVibration().id, vibrationId));
} }
mCurrentVibration = null; mCurrentVibration = null;
if (mNextVibration != null) { if (mNextVibration != null) {
VibrationThread vibThread = mNextVibration; VibrationStepConductor nextConductor = mNextVibration;
mNextVibration = null; mNextVibration = null;
Vibration.Status status = startVibrationThreadLocked(vibThread); Vibration.Status status = startVibrationOnThreadLocked(nextConductor);
if (status != Vibration.Status.RUNNING) { if (status != Vibration.Status.RUNNING) {
endVibrationLocked(vibThread.getVibration(), status); endVibrationLocked(nextConductor.getVibration(), status);
} }
} }
} }
@@ -1451,7 +1470,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
} }
ExternalVibrationHolder cancelingExternalVibration = null; ExternalVibrationHolder cancelingExternalVibration = null;
VibrationThread cancelingVibration = null; boolean waitForCompletion = false;
int scale; int scale;
synchronized (mLock) { synchronized (mLock) {
Vibration.Status ignoreStatus = shouldIgnoreVibrationLocked( Vibration.Status ignoreStatus = shouldIgnoreVibrationLocked(
@@ -1473,8 +1492,8 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
// vibration that may be playing and ready the vibrator for external control. // vibration that may be playing and ready the vibrator for external control.
if (mCurrentVibration != null) { if (mCurrentVibration != null) {
clearNextVibrationLocked(Vibration.Status.IGNORED_FOR_EXTERNAL); clearNextVibrationLocked(Vibration.Status.IGNORED_FOR_EXTERNAL);
mCurrentVibration.cancelImmediately(); mCurrentVibration.notifyCancelled(/* immediate= */ true);
cancelingVibration = mCurrentVibration; waitForCompletion = true;
} }
} else { } else {
// At this point we have an externally controlled vibration playing already. // At this point we have an externally controlled vibration playing already.
@@ -1497,12 +1516,13 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
scale = mCurrentExternalVibration.scale; scale = mCurrentExternalVibration.scale;
} }
if (cancelingVibration != null) { if (waitForCompletion) {
try { if (!mVibrationThread.waitForThreadIdle(VIBRATION_CANCEL_WAIT_MILLIS)) {
cancelingVibration.join(); Slog.e(TAG, "Timed out waiting for vibration to cancel");
} catch (InterruptedException e) { synchronized (mLock) {
Slog.w("Interrupted while waiting for vibration to finish before starting " stopExternalVibrateLocked(Vibration.Status.IGNORED_ERROR_CANCELLING);
+ "external control", e); }
return IExternalVibratorService.SCALE_MUTE;
} }
} }
if (cancelingExternalVibration == null) { if (cancelingExternalVibration == null) {

View File

@@ -34,6 +34,7 @@ import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.TreeMap;
/** /**
* Provides {@link VibratorController} with controlled vibrator hardware capabilities and * Provides {@link VibratorController} with controlled vibrator hardware capabilities and
@@ -43,8 +44,8 @@ final class FakeVibratorControllerProvider {
private static final int EFFECT_DURATION = 20; private static final int EFFECT_DURATION = 20;
private final Map<Long, PrebakedSegment> mEnabledAlwaysOnEffects = new HashMap<>(); private final Map<Long, PrebakedSegment> mEnabledAlwaysOnEffects = new HashMap<>();
private final List<VibrationEffectSegment> mEffectSegments = new ArrayList<>(); private final Map<Long, List<VibrationEffectSegment>> mEffectSegments = new TreeMap<>();
private final List<Integer> mBraking = new ArrayList<>(); private final Map<Long, List<Integer>> mBraking = new HashMap<>();
private final List<Float> mAmplitudes = new ArrayList<>(); private final List<Float> mAmplitudes = new ArrayList<>();
private final List<Boolean> mExternalControlStates = new ArrayList<>(); private final List<Boolean> mExternalControlStates = new ArrayList<>();
private final Handler mHandler; private final Handler mHandler;
@@ -67,6 +68,14 @@ final class FakeVibratorControllerProvider {
private float mQFactor = Float.NaN; private float mQFactor = Float.NaN;
private float[] mMaxAmplitudes; private float[] mMaxAmplitudes;
void recordEffectSegment(long vibrationId, VibrationEffectSegment segment) {
mEffectSegments.computeIfAbsent(vibrationId, k -> new ArrayList<>()).add(segment);
}
void recordBraking(long vibrationId, int braking) {
mBraking.computeIfAbsent(vibrationId, k -> new ArrayList<>()).add(braking);
}
private final class FakeNativeWrapper extends VibratorController.NativeWrapper { private final class FakeNativeWrapper extends VibratorController.NativeWrapper {
public int vibratorId; public int vibratorId;
public OnVibrationCompleteListener listener; public OnVibrationCompleteListener listener;
@@ -86,7 +95,7 @@ final class FakeVibratorControllerProvider {
@Override @Override
public long on(long milliseconds, long vibrationId) { public long on(long milliseconds, long vibrationId) {
mEffectSegments.add(new StepSegment(VibrationEffect.DEFAULT_AMPLITUDE, recordEffectSegment(vibrationId, new StepSegment(VibrationEffect.DEFAULT_AMPLITUDE,
/* frequencyHz= */ 0, (int) milliseconds)); /* frequencyHz= */ 0, (int) milliseconds));
applyLatency(); applyLatency();
scheduleListener(milliseconds, vibrationId); scheduleListener(milliseconds, vibrationId);
@@ -110,7 +119,8 @@ final class FakeVibratorControllerProvider {
|| Arrays.binarySearch(mSupportedEffects, (int) effect) < 0) { || Arrays.binarySearch(mSupportedEffects, (int) effect) < 0) {
return 0; return 0;
} }
mEffectSegments.add(new PrebakedSegment((int) effect, false, (int) strength)); recordEffectSegment(vibrationId,
new PrebakedSegment((int) effect, false, (int) strength));
applyLatency(); applyLatency();
scheduleListener(EFFECT_DURATION, vibrationId); scheduleListener(EFFECT_DURATION, vibrationId);
return EFFECT_DURATION; return EFFECT_DURATION;
@@ -121,7 +131,7 @@ final class FakeVibratorControllerProvider {
long duration = 0; long duration = 0;
for (PrimitiveSegment primitive : effects) { for (PrimitiveSegment primitive : effects) {
duration += EFFECT_DURATION + primitive.getDelay(); duration += EFFECT_DURATION + primitive.getDelay();
mEffectSegments.add(primitive); recordEffectSegment(vibrationId, primitive);
} }
applyLatency(); applyLatency();
scheduleListener(duration, vibrationId); scheduleListener(duration, vibrationId);
@@ -133,9 +143,9 @@ final class FakeVibratorControllerProvider {
long duration = 0; long duration = 0;
for (RampSegment primitive : primitives) { for (RampSegment primitive : primitives) {
duration += primitive.getDuration(); duration += primitive.getDuration();
mEffectSegments.add(primitive); recordEffectSegment(vibrationId, primitive);
} }
mBraking.add(braking); recordBraking(vibrationId, braking);
applyLatency(); applyLatency();
scheduleListener(duration, vibrationId); scheduleListener(duration, vibrationId);
return duration; return duration;
@@ -304,15 +314,35 @@ final class FakeVibratorControllerProvider {
} }
/** Return the braking values passed to the compose PWLE method. */ /** Return the braking values passed to the compose PWLE method. */
public List<Integer> getBraking() { public List<Integer> getBraking(long vibrationId) {
return mBraking; if (mBraking.containsKey(vibrationId)) {
return new ArrayList<>(mBraking.get(vibrationId));
} else {
return new ArrayList<>();
}
} }
/** Return list of {@link VibrationEffectSegment} played by this controller, in order. */ /** Return list of {@link VibrationEffectSegment} played by this controller, in order. */
public List<VibrationEffectSegment> getEffectSegments() { public List<VibrationEffectSegment> getEffectSegments(long vibrationId) {
return new ArrayList<>(mEffectSegments); if (mEffectSegments.containsKey(vibrationId)) {
return new ArrayList<>(mEffectSegments.get(vibrationId));
} else {
return new ArrayList<>();
}
} }
/**
* Returns a list of all vibrations' effect segments, for external-use where vibration IDs
* aren't exposed.
*/
public List<VibrationEffectSegment> getAllEffectSegments() {
// Returns segments in order of vibrationId, which increases over time. TreeMap gives order.
ArrayList<VibrationEffectSegment> result = new ArrayList<>();
for (List<VibrationEffectSegment> subList : mEffectSegments.values()) {
result.addAll(subList);
}
return result;
}
/** Return list of states set for external control to the fake vibrator hardware. */ /** Return list of states set for external control to the fake vibrator hardware. */
public List<Boolean> getExternalControlStates() { public List<Boolean> getExternalControlStates() {
return mExternalControlStates; return mExternalControlStates;

View File

@@ -571,27 +571,27 @@ public class VibratorManagerServiceTest {
VibratorManagerService service = createSystemReadyService(); VibratorManagerService service = createSystemReadyService();
vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), RINGTONE_ATTRS); vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), RINGTONE_ATTRS);
// Wait before checking it never played. // Wait before checking it never played.
assertFalse(waitUntil(s -> !fakeVibrator.getEffectSegments().isEmpty(), assertFalse(waitUntil(s -> !fakeVibrator.getAllEffectSegments().isEmpty(),
service, /* timeout= */ 50)); service, /* timeout= */ 50));
setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 0); setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 0);
setUserSetting(Settings.System.APPLY_RAMPING_RINGER, 1); setUserSetting(Settings.System.APPLY_RAMPING_RINGER, 1);
service = createSystemReadyService(); service = createSystemReadyService();
vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_HEAVY_CLICK), RINGTONE_ATTRS); vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_HEAVY_CLICK), RINGTONE_ATTRS);
assertTrue(waitUntil(s -> fakeVibrator.getEffectSegments().size() == 1, assertTrue(waitUntil(s -> fakeVibrator.getAllEffectSegments().size() == 1,
service, TEST_TIMEOUT_MILLIS)); service, TEST_TIMEOUT_MILLIS));
setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 1); setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 1);
setUserSetting(Settings.System.APPLY_RAMPING_RINGER, 0); setUserSetting(Settings.System.APPLY_RAMPING_RINGER, 0);
service = createSystemReadyService(); service = createSystemReadyService();
vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK), RINGTONE_ATTRS); vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK), RINGTONE_ATTRS);
assertTrue(waitUntil(s -> fakeVibrator.getEffectSegments().size() == 2, assertTrue(waitUntil(s -> fakeVibrator.getAllEffectSegments().size() == 2,
service, TEST_TIMEOUT_MILLIS)); service, TEST_TIMEOUT_MILLIS));
assertEquals( assertEquals(
Arrays.asList(expectedPrebaked(VibrationEffect.EFFECT_HEAVY_CLICK), Arrays.asList(expectedPrebaked(VibrationEffect.EFFECT_HEAVY_CLICK),
expectedPrebaked(VibrationEffect.EFFECT_DOUBLE_CLICK)), expectedPrebaked(VibrationEffect.EFFECT_DOUBLE_CLICK)),
mVibratorProviders.get(1).getEffectSegments()); mVibratorProviders.get(1).getAllEffectSegments());
} }
@Test @Test
@@ -604,25 +604,25 @@ public class VibratorManagerServiceTest {
mRegisteredPowerModeListener.onLowPowerModeChanged(LOW_POWER_STATE); mRegisteredPowerModeListener.onLowPowerModeChanged(LOW_POWER_STATE);
vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_TICK), HAPTIC_FEEDBACK_ATTRS); vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_TICK), HAPTIC_FEEDBACK_ATTRS);
vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), RINGTONE_ATTRS); vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), RINGTONE_ATTRS);
assertTrue(waitUntil(s -> fakeVibrator.getEffectSegments().size() == 1, assertTrue(waitUntil(s -> fakeVibrator.getAllEffectSegments().size() == 1,
service, TEST_TIMEOUT_MILLIS)); service, TEST_TIMEOUT_MILLIS));
mRegisteredPowerModeListener.onLowPowerModeChanged(NORMAL_POWER_STATE); mRegisteredPowerModeListener.onLowPowerModeChanged(NORMAL_POWER_STATE);
vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_HEAVY_CLICK), vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_HEAVY_CLICK),
/* attrs= */ null); /* attrs= */ null);
assertTrue(waitUntil(s -> fakeVibrator.getEffectSegments().size() == 2, assertTrue(waitUntil(s -> fakeVibrator.getAllEffectSegments().size() == 2,
service, TEST_TIMEOUT_MILLIS)); service, TEST_TIMEOUT_MILLIS));
vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK), vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK),
NOTIFICATION_ATTRS); NOTIFICATION_ATTRS);
assertTrue(waitUntil(s -> fakeVibrator.getEffectSegments().size() == 3, assertTrue(waitUntil(s -> fakeVibrator.getAllEffectSegments().size() == 3,
service, TEST_TIMEOUT_MILLIS)); service, TEST_TIMEOUT_MILLIS));
assertEquals( assertEquals(
Arrays.asList(expectedPrebaked(VibrationEffect.EFFECT_CLICK), Arrays.asList(expectedPrebaked(VibrationEffect.EFFECT_CLICK),
expectedPrebaked(VibrationEffect.EFFECT_HEAVY_CLICK), expectedPrebaked(VibrationEffect.EFFECT_HEAVY_CLICK),
expectedPrebaked(VibrationEffect.EFFECT_DOUBLE_CLICK)), expectedPrebaked(VibrationEffect.EFFECT_DOUBLE_CLICK)),
mVibratorProviders.get(1).getEffectSegments()); mVibratorProviders.get(1).getAllEffectSegments());
} }
@Test @Test
@@ -738,14 +738,14 @@ public class VibratorManagerServiceTest {
VibrationAttributes.USAGE_UNKNOWN).build()); VibrationAttributes.USAGE_UNKNOWN).build());
// VibrationThread will start this vibration async, so wait before checking it started. // VibrationThread will start this vibration async, so wait before checking it started.
assertTrue(waitUntil(s -> !mVibratorProviders.get(1).getEffectSegments().isEmpty(), assertTrue(waitUntil(s -> !mVibratorProviders.get(1).getAllEffectSegments().isEmpty(),
service, TEST_TIMEOUT_MILLIS)); service, TEST_TIMEOUT_MILLIS));
vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK),
HAPTIC_FEEDBACK_ATTRS); HAPTIC_FEEDBACK_ATTRS);
// Wait before checking it never played a second effect. // Wait before checking it never played a second effect.
assertFalse(waitUntil(s -> mVibratorProviders.get(1).getEffectSegments().size() > 1, assertFalse(waitUntil(s -> mVibratorProviders.get(1).getAllEffectSegments().size() > 1,
service, /* timeout= */ 50)); service, /* timeout= */ 50));
// The time estimate is recorded when the vibration starts, repeating vibrations // The time estimate is recorded when the vibration starts, repeating vibrations
@@ -767,14 +767,14 @@ public class VibratorManagerServiceTest {
VibrationAttributes.USAGE_ALARM).build()); VibrationAttributes.USAGE_ALARM).build());
// VibrationThread will start this vibration async, so wait before checking it started. // VibrationThread will start this vibration async, so wait before checking it started.
assertTrue(waitUntil(s -> !mVibratorProviders.get(1).getEffectSegments().isEmpty(), assertTrue(waitUntil(s -> !mVibratorProviders.get(1).getAllEffectSegments().isEmpty(),
service, TEST_TIMEOUT_MILLIS)); service, TEST_TIMEOUT_MILLIS));
vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK),
HAPTIC_FEEDBACK_ATTRS); HAPTIC_FEEDBACK_ATTRS);
// Wait before checking it never played a second effect. // Wait before checking it never played a second effect.
assertFalse(waitUntil(s -> mVibratorProviders.get(1).getEffectSegments().size() > 1, assertFalse(waitUntil(s -> mVibratorProviders.get(1).getAllEffectSegments().size() > 1,
service, /* timeout= */ 50)); service, /* timeout= */ 50));
} }
@@ -798,7 +798,7 @@ public class VibratorManagerServiceTest {
verify(mIInputManagerMock).vibrateCombined(eq(1), eq(effect), any()); verify(mIInputManagerMock).vibrateCombined(eq(1), eq(effect), any());
// VibrationThread will start this vibration async, so wait before checking it never played. // VibrationThread will start this vibration async, so wait before checking it never played.
assertFalse(waitUntil(s -> !mVibratorProviders.get(1).getEffectSegments().isEmpty(), assertFalse(waitUntil(s -> !mVibratorProviders.get(1).getAllEffectSegments().isEmpty(),
service, /* timeout= */ 50)); service, /* timeout= */ 50));
} }
@@ -863,8 +863,8 @@ public class VibratorManagerServiceTest {
verify(mNativeWrapperMock).triggerSynced(anyLong()); verify(mNativeWrapperMock).triggerSynced(anyLong());
PrimitiveSegment expected = new PrimitiveSegment( PrimitiveSegment expected = new PrimitiveSegment(
VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 100); VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 100);
assertEquals(Arrays.asList(expected), mVibratorProviders.get(1).getEffectSegments()); assertEquals(Arrays.asList(expected), mVibratorProviders.get(1).getAllEffectSegments());
assertEquals(Arrays.asList(expected), mVibratorProviders.get(2).getEffectSegments()); assertEquals(Arrays.asList(expected), mVibratorProviders.get(2).getAllEffectSegments());
// VibrationThread needs some time to react to native callbacks and stop the vibrator. // VibrationThread needs some time to react to native callbacks and stop the vibrator.
assertTrue(waitUntil(s -> !s.isVibrating(1), service, TEST_TIMEOUT_MILLIS)); assertTrue(waitUntil(s -> !s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
@@ -891,7 +891,7 @@ public class VibratorManagerServiceTest {
.compose()) .compose())
.combine(); .combine();
vibrate(service, effect, ALARM_ATTRS); vibrate(service, effect, ALARM_ATTRS);
assertTrue(waitUntil(s -> !fakeVibrator1.getEffectSegments().isEmpty(), service, assertTrue(waitUntil(s -> !fakeVibrator1.getAllEffectSegments().isEmpty(), service,
TEST_TIMEOUT_MILLIS)); TEST_TIMEOUT_MILLIS));
verify(mNativeWrapperMock).prepareSynced(eq(new int[]{1, 2})); verify(mNativeWrapperMock).prepareSynced(eq(new int[]{1, 2}));
@@ -915,7 +915,7 @@ public class VibratorManagerServiceTest {
.addVibrator(2, VibrationEffect.createOneShot(10, 100)) .addVibrator(2, VibrationEffect.createOneShot(10, 100))
.combine(); .combine();
vibrate(service, effect, ALARM_ATTRS); vibrate(service, effect, ALARM_ATTRS);
assertTrue(waitUntil(s -> !fakeVibrator1.getEffectSegments().isEmpty(), service, assertTrue(waitUntil(s -> !fakeVibrator1.getAllEffectSegments().isEmpty(), service,
TEST_TIMEOUT_MILLIS)); TEST_TIMEOUT_MILLIS));
verify(mNativeWrapperMock, never()).prepareSynced(any()); verify(mNativeWrapperMock, never()).prepareSynced(any());
@@ -935,8 +935,8 @@ public class VibratorManagerServiceTest {
.addVibrator(2, VibrationEffect.createOneShot(10, 100)) .addVibrator(2, VibrationEffect.createOneShot(10, 100))
.combine(); .combine();
vibrate(service, effect, ALARM_ATTRS); vibrate(service, effect, ALARM_ATTRS);
assertTrue(waitUntil(s -> !mVibratorProviders.get(1).getEffectSegments().isEmpty(), service, assertTrue(waitUntil(s -> !mVibratorProviders.get(1).getAllEffectSegments().isEmpty(),
TEST_TIMEOUT_MILLIS)); service, TEST_TIMEOUT_MILLIS));
verify(mNativeWrapperMock).prepareSynced(eq(new int[]{1, 2})); verify(mNativeWrapperMock).prepareSynced(eq(new int[]{1, 2}));
verify(mNativeWrapperMock, never()).triggerSynced(anyLong()); verify(mNativeWrapperMock, never()).triggerSynced(anyLong());
@@ -956,8 +956,8 @@ public class VibratorManagerServiceTest {
.addVibrator(2, VibrationEffect.createOneShot(10, 100)) .addVibrator(2, VibrationEffect.createOneShot(10, 100))
.combine(); .combine();
vibrate(service, effect, ALARM_ATTRS); vibrate(service, effect, ALARM_ATTRS);
assertTrue(waitUntil(s -> !mVibratorProviders.get(1).getEffectSegments().isEmpty(), service, assertTrue(waitUntil(s -> !mVibratorProviders.get(1).getAllEffectSegments().isEmpty(),
TEST_TIMEOUT_MILLIS)); service, TEST_TIMEOUT_MILLIS));
verify(mNativeWrapperMock).prepareSynced(eq(new int[]{1, 2})); verify(mNativeWrapperMock).prepareSynced(eq(new int[]{1, 2}));
verify(mNativeWrapperMock).triggerSynced(anyLong()); verify(mNativeWrapperMock).triggerSynced(anyLong());
@@ -995,26 +995,26 @@ public class VibratorManagerServiceTest {
vibrate(service, CombinedVibration.startSequential() vibrate(service, CombinedVibration.startSequential()
.addNext(1, VibrationEffect.createOneShot(100, 125)) .addNext(1, VibrationEffect.createOneShot(100, 125))
.combine(), NOTIFICATION_ATTRS); .combine(), NOTIFICATION_ATTRS);
assertTrue(waitUntil(s -> fakeVibrator.getEffectSegments().size() == 1, assertTrue(waitUntil(s -> fakeVibrator.getAllEffectSegments().size() == 1,
service, TEST_TIMEOUT_MILLIS)); service, TEST_TIMEOUT_MILLIS));
vibrate(service, VibrationEffect.startComposition() vibrate(service, VibrationEffect.startComposition()
.addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f) .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f)
.compose(), HAPTIC_FEEDBACK_ATTRS); .compose(), HAPTIC_FEEDBACK_ATTRS);
assertTrue(waitUntil(s -> fakeVibrator.getEffectSegments().size() == 2, assertTrue(waitUntil(s -> fakeVibrator.getAllEffectSegments().size() == 2,
service, TEST_TIMEOUT_MILLIS)); service, TEST_TIMEOUT_MILLIS));
vibrate(service, VibrationEffect.startComposition() vibrate(service, VibrationEffect.startComposition()
.addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f) .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f)
.compose(), ALARM_ATTRS); .compose(), ALARM_ATTRS);
assertTrue(waitUntil(s -> fakeVibrator.getEffectSegments().size() == 3, assertTrue(waitUntil(s -> fakeVibrator.getAllEffectSegments().size() == 3,
service, TEST_TIMEOUT_MILLIS)); service, TEST_TIMEOUT_MILLIS));
vibrate(service, VibrationEffect.createOneShot(100, 125), RINGTONE_ATTRS); vibrate(service, VibrationEffect.createOneShot(100, 125), RINGTONE_ATTRS);
assertFalse(waitUntil(s -> fakeVibrator.getEffectSegments().size() > 3, assertFalse(waitUntil(s -> fakeVibrator.getAllEffectSegments().size() > 3,
service, TEST_TIMEOUT_MILLIS)); service, TEST_TIMEOUT_MILLIS));
assertEquals(3, fakeVibrator.getEffectSegments().size()); assertEquals(3, fakeVibrator.getAllEffectSegments().size());
// Notification vibrations will be scaled with SCALE_HIGH or none if default is high. // Notification vibrations will be scaled with SCALE_HIGH or none if default is high.
assertEquals(defaultNotificationIntensity < Vibrator.VIBRATION_INTENSITY_HIGH, assertEquals(defaultNotificationIntensity < Vibrator.VIBRATION_INTENSITY_HIGH,
@@ -1022,11 +1022,11 @@ public class VibratorManagerServiceTest {
// Haptic feedback vibrations will be scaled with SCALE_LOW or none if default is low. // Haptic feedback vibrations will be scaled with SCALE_LOW or none if default is low.
assertEquals(defaultTouchIntensity > Vibrator.VIBRATION_INTENSITY_LOW, assertEquals(defaultTouchIntensity > Vibrator.VIBRATION_INTENSITY_LOW,
0.5 > ((PrimitiveSegment) fakeVibrator.getEffectSegments().get(1)).getScale()); 0.5 > ((PrimitiveSegment) fakeVibrator.getAllEffectSegments().get(1)).getScale());
// Alarm vibration will be scaled with SCALE_NONE. // Alarm vibration will be scaled with SCALE_NONE.
assertEquals(1f, assertEquals(1f,
((PrimitiveSegment) fakeVibrator.getEffectSegments().get(2)).getScale(), 1e-5); ((PrimitiveSegment) fakeVibrator.getAllEffectSegments().get(2)).getScale(), 1e-5);
// Ring vibrations have intensity OFF and are not played. // Ring vibrations have intensity OFF and are not played.
} }