Merge "Make VibrationThread long-lived." into tm-dev
This commit is contained in:
@@ -49,6 +49,8 @@ final class Vibration {
|
||||
FORWARDED_TO_INPUT_DEVICES,
|
||||
CANCELLED,
|
||||
IGNORED_ERROR_APP_OPS,
|
||||
IGNORED_ERROR_CANCELLING,
|
||||
IGNORED_ERROR_SCHEDULING,
|
||||
IGNORED_ERROR_TOKEN,
|
||||
IGNORED,
|
||||
IGNORED_APP_OPS,
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package com.android.server.vibrator;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.os.IBinder;
|
||||
import android.os.PowerManager;
|
||||
import android.os.Process;
|
||||
@@ -23,9 +25,12 @@ import android.os.RemoteException;
|
||||
import android.os.Trace;
|
||||
import android.os.WorkSource;
|
||||
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.Objects;
|
||||
|
||||
/** Plays a {@link Vibration} in dedicated thread. */
|
||||
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
|
||||
* 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 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;
|
||||
// Variable only set and read in main thread.
|
||||
/**
|
||||
* The conductor that is intended to be active. Null value means that a new conductor can
|
||||
* 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;
|
||||
|
||||
VibrationThread(Vibration vib, VibrationSettings vibrationSettings,
|
||||
DeviceVibrationEffectAdapter effectAdapter,
|
||||
SparseArray<VibratorController> availableVibrators, PowerManager.WakeLock wakeLock,
|
||||
VibratorManagerHooks vibratorManagerHooks) {
|
||||
mVibratorManagerHooks = vibratorManagerHooks;
|
||||
VibrationThread(PowerManager.WakeLock wakeLock, VibratorManagerHooks vibratorManagerHooks) {
|
||||
mWakeLock = wakeLock;
|
||||
mStepConductor = new VibrationStepConductor(vib, vibrationSettings, effectAdapter,
|
||||
availableVibrators, vibratorManagerHooks);
|
||||
}
|
||||
|
||||
Vibration getVibration() {
|
||||
return mStepConductor.getVibration();
|
||||
mVibratorManagerHooks = vibratorManagerHooks;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -108,32 +123,138 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient {
|
||||
if (DEBUG) {
|
||||
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
|
||||
public void run() {
|
||||
// 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);
|
||||
Process.setThreadPriority(Process.THREAD_PRIORITY_URGENT_DISPLAY);
|
||||
while (true) {
|
||||
// mExecutingConductor is only modified in this loop.
|
||||
mExecutingConductor = Objects.requireNonNull(waitForVibrationRequest());
|
||||
|
||||
mCalledVibrationCompleteCallback = false;
|
||||
runCurrentVibrationWithWakeLock();
|
||||
if (!mExecutingConductor.isFinished()) {
|
||||
Slog.wtf(TAG, "VibrationThread terminated with unfinished vibration");
|
||||
}
|
||||
} finally {
|
||||
mVibratorManagerHooks.onVibrationThreadReleased();
|
||||
synchronized (mLock) {
|
||||
// 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. */
|
||||
private void runWithWakeLock() {
|
||||
WorkSource workSource = new WorkSource(mStepConductor.getVibration().uid);
|
||||
private void runCurrentVibrationWithWakeLock() {
|
||||
WorkSource workSource = new WorkSource(mExecutingConductor.getVibration().uid);
|
||||
mWakeLock.setWorkSource(workSource);
|
||||
mWakeLock.acquire();
|
||||
try {
|
||||
runWithWakeLockAndDeathLink();
|
||||
try {
|
||||
runCurrentVibrationWithWakeLockAndDeathLink();
|
||||
} finally {
|
||||
clientVibrationCompleteIfNotAlready(Vibration.Status.FINISHED_UNEXPECTED);
|
||||
}
|
||||
} finally {
|
||||
mWakeLock.release();
|
||||
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.
|
||||
* Called from within runWithWakeLock.
|
||||
*/
|
||||
private void runWithWakeLockAndDeathLink() {
|
||||
IBinder vibrationBinderToken = mStepConductor.getVibration().token;
|
||||
private void runCurrentVibrationWithWakeLockAndDeathLink() {
|
||||
IBinder vibrationBinderToken = mExecutingConductor.getVibration().token;
|
||||
try {
|
||||
vibrationBinderToken.linkToDeath(this, 0);
|
||||
} 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
|
||||
// convenience of handling error conditions - an error after the client is complete won't
|
||||
// affect the status.
|
||||
@@ -193,16 +294,16 @@ final class VibrationThread extends Thread implements IBinder.DeathRecipient {
|
||||
if (!mCalledVibrationCompleteCallback) {
|
||||
mCalledVibrationCompleteCallback = true;
|
||||
mVibratorManagerHooks.onVibrationCompleted(
|
||||
mStepConductor.getVibration().id, completedStatus);
|
||||
mExecutingConductor.getVibration().id, completedStatus);
|
||||
}
|
||||
}
|
||||
|
||||
private void playVibration() {
|
||||
Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "playVibration");
|
||||
try {
|
||||
mStepConductor.prepareToStart();
|
||||
while (!mStepConductor.isFinished()) {
|
||||
boolean readyToRun = mStepConductor.waitUntilNextStepIsDue();
|
||||
mExecutingConductor.prepareToStart();
|
||||
while (!mExecutingConductor.isFinished()) {
|
||||
boolean readyToRun = mExecutingConductor.waitUntilNextStepIsDue();
|
||||
// If we waited, don't run the next step, but instead re-evaluate status.
|
||||
if (readyToRun) {
|
||||
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
|
||||
// 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.
|
||||
if (status != Vibration.Status.RUNNING && !mCalledVibrationCompleteCallback) {
|
||||
// First time vibration stopped running, start clean-up tasks and notify
|
||||
|
||||
@@ -31,6 +31,7 @@ import android.content.pm.PackageManager;
|
||||
import android.hardware.vibrator.IVibrator;
|
||||
import android.os.BatteryStats;
|
||||
import android.os.Binder;
|
||||
import android.os.Build;
|
||||
import android.os.CombinedVibration;
|
||||
import android.os.ExternalVibration;
|
||||
import android.os.Handler;
|
||||
@@ -52,6 +53,7 @@ import android.os.VibrationEffect;
|
||||
import android.os.VibratorInfo;
|
||||
import android.os.vibrator.PrebakedSegment;
|
||||
import android.os.vibrator.VibrationEffectSegment;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Slog;
|
||||
import android.util.SparseArray;
|
||||
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}. */
|
||||
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. */
|
||||
public static class Lifecycle extends SystemService {
|
||||
private VibratorManagerService mService;
|
||||
@@ -121,6 +129,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
|
||||
private final PowerManager.WakeLock mWakeLock;
|
||||
private final IBatteryStats mBatteryStatsService;
|
||||
private final Handler mHandler;
|
||||
private final VibrationThread mVibrationThread;
|
||||
private final AppOpsManager mAppOps;
|
||||
private final NativeWrapper mNativeWrapper;
|
||||
private final VibratorManagerRecords mVibratorManagerRecords;
|
||||
@@ -132,9 +141,9 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
|
||||
@GuardedBy("mLock")
|
||||
private final SparseArray<AlwaysOnVibration> mAlwaysOnEffects = new SparseArray<>();
|
||||
@GuardedBy("mLock")
|
||||
private VibrationThread mCurrentVibration;
|
||||
private VibrationStepConductor mCurrentVibration;
|
||||
@GuardedBy("mLock")
|
||||
private VibrationThread mNextVibration;
|
||||
private VibrationStepConductor mNextVibration;
|
||||
@GuardedBy("mLock")
|
||||
private ExternalVibrationHolder mCurrentExternalVibration;
|
||||
@GuardedBy("mLock")
|
||||
@@ -156,7 +165,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
|
||||
clearNextVibrationLocked(Vibration.Status.CANCELLED);
|
||||
}
|
||||
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);
|
||||
mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "*vibrator*");
|
||||
mWakeLock.setReferenceCounted(true);
|
||||
mVibrationThread = new VibrationThread(mWakeLock, mVibrationThreadCallbacks);
|
||||
mVibrationThread.start();
|
||||
|
||||
// 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
|
||||
@@ -409,7 +420,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
|
||||
final long ident = Binder.clearCallingIdentity();
|
||||
try {
|
||||
if (mCurrentVibration != null) {
|
||||
mCurrentVibration.cancel();
|
||||
mCurrentVibration.notifyCancelled(/* immediate= */ false);
|
||||
}
|
||||
Vibration.Status status = startVibrationLocked(vib);
|
||||
if (status != Vibration.Status.RUNNING) {
|
||||
@@ -447,7 +458,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
|
||||
if (mCurrentVibration != null
|
||||
&& shouldCancelVibration(mCurrentVibration.getVibration(),
|
||||
usageFilter, token)) {
|
||||
mCurrentVibration.cancel();
|
||||
mCurrentVibration.notifyCancelled(/* immediate= */false);
|
||||
}
|
||||
if (mCurrentExternalVibration != null
|
||||
&& shouldCancelVibration(
|
||||
@@ -584,7 +595,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
|
||||
Slog.d(TAG, "Canceling vibration because settings changed: "
|
||||
+ (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;
|
||||
}
|
||||
|
||||
VibrationThread vibThread = new VibrationThread(vib, mVibrationSettings,
|
||||
mDeviceVibrationEffectAdapter, mVibrators, mWakeLock,
|
||||
mVibrationThreadCallbacks);
|
||||
|
||||
VibrationStepConductor conductor = new VibrationStepConductor(vib, mVibrationSettings,
|
||||
mDeviceVibrationEffectAdapter, mVibrators, mVibrationThreadCallbacks);
|
||||
if (mCurrentVibration == null) {
|
||||
return startVibrationThreadLocked(vibThread);
|
||||
return startVibrationOnThreadLocked(conductor);
|
||||
}
|
||||
// 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.
|
||||
clearNextVibrationLocked(Vibration.Status.IGNORED_SUPERSEDED);
|
||||
mNextVibration = vibThread;
|
||||
mNextVibration = conductor;
|
||||
return Vibration.Status.RUNNING;
|
||||
} finally {
|
||||
Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
|
||||
@@ -644,16 +653,20 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
|
||||
}
|
||||
|
||||
@GuardedBy("mLock")
|
||||
private Vibration.Status startVibrationThreadLocked(VibrationThread vibThread) {
|
||||
private Vibration.Status startVibrationOnThreadLocked(VibrationStepConductor conductor) {
|
||||
Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "startVibrationThreadLocked");
|
||||
try {
|
||||
Vibration vib = vibThread.getVibration();
|
||||
Vibration vib = conductor.getVibration();
|
||||
int mode = startAppOpModeLocked(vib.uid, vib.opPkg, vib.attrs);
|
||||
switch (mode) {
|
||||
case AppOpsManager.MODE_ALLOWED:
|
||||
Trace.asyncTraceBegin(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
|
||||
mCurrentVibration = vibThread;
|
||||
mCurrentVibration.start();
|
||||
mCurrentVibration = conductor;
|
||||
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;
|
||||
case AppOpsManager.MODE_ERRORED:
|
||||
Slog.w(TAG, "Start AppOpsManager operation errored for uid " + vib.uid);
|
||||
@@ -741,7 +754,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
|
||||
if (DEBUG) {
|
||||
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
|
||||
+ " complete, notifying thread");
|
||||
}
|
||||
mCurrentVibration.vibratorComplete(vibratorId);
|
||||
mCurrentVibration.notifyVibratorComplete(vibratorId);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1064,11 +1077,11 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
|
||||
}
|
||||
|
||||
@GuardedBy("mLock")
|
||||
private boolean shouldCancelOnScreenOffLocked(@Nullable VibrationThread vibrationThread) {
|
||||
if (vibrationThread == null) {
|
||||
private boolean shouldCancelOnScreenOffLocked(@Nullable VibrationStepConductor conductor) {
|
||||
if (conductor == null) {
|
||||
return false;
|
||||
}
|
||||
Vibration vib = vibrationThread.getVibration();
|
||||
Vibration vib = conductor.getVibration();
|
||||
return mVibrationSettings.shouldCancelVibrationOnScreenOff(
|
||||
vib.uid, vib.opPkg, vib.attrs.getUsage());
|
||||
}
|
||||
@@ -1185,21 +1198,27 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVibrationThreadReleased() {
|
||||
public void onVibrationThreadReleased(long vibrationId) {
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "Vibrators released after finished vibration");
|
||||
Slog.d(TAG, "VibrationThread released after finished vibration");
|
||||
}
|
||||
synchronized (mLock) {
|
||||
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;
|
||||
if (mNextVibration != null) {
|
||||
VibrationThread vibThread = mNextVibration;
|
||||
VibrationStepConductor nextConductor = mNextVibration;
|
||||
mNextVibration = null;
|
||||
Vibration.Status status = startVibrationThreadLocked(vibThread);
|
||||
Vibration.Status status = startVibrationOnThreadLocked(nextConductor);
|
||||
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;
|
||||
VibrationThread cancelingVibration = null;
|
||||
boolean waitForCompletion = false;
|
||||
int scale;
|
||||
synchronized (mLock) {
|
||||
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.
|
||||
if (mCurrentVibration != null) {
|
||||
clearNextVibrationLocked(Vibration.Status.IGNORED_FOR_EXTERNAL);
|
||||
mCurrentVibration.cancelImmediately();
|
||||
cancelingVibration = mCurrentVibration;
|
||||
mCurrentVibration.notifyCancelled(/* immediate= */ true);
|
||||
waitForCompletion = true;
|
||||
}
|
||||
} else {
|
||||
// At this point we have an externally controlled vibration playing already.
|
||||
@@ -1497,12 +1516,13 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
|
||||
scale = mCurrentExternalVibration.scale;
|
||||
}
|
||||
|
||||
if (cancelingVibration != null) {
|
||||
try {
|
||||
cancelingVibration.join();
|
||||
} catch (InterruptedException e) {
|
||||
Slog.w("Interrupted while waiting for vibration to finish before starting "
|
||||
+ "external control", e);
|
||||
if (waitForCompletion) {
|
||||
if (!mVibrationThread.waitForThreadIdle(VIBRATION_CANCEL_WAIT_MILLIS)) {
|
||||
Slog.e(TAG, "Timed out waiting for vibration to cancel");
|
||||
synchronized (mLock) {
|
||||
stopExternalVibrateLocked(Vibration.Status.IGNORED_ERROR_CANCELLING);
|
||||
}
|
||||
return IExternalVibratorService.SCALE_MUTE;
|
||||
}
|
||||
}
|
||||
if (cancelingExternalVibration == null) {
|
||||
|
||||
@@ -34,6 +34,7 @@ import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
/**
|
||||
* Provides {@link VibratorController} with controlled vibrator hardware capabilities and
|
||||
@@ -43,8 +44,8 @@ final class FakeVibratorControllerProvider {
|
||||
private static final int EFFECT_DURATION = 20;
|
||||
|
||||
private final Map<Long, PrebakedSegment> mEnabledAlwaysOnEffects = new HashMap<>();
|
||||
private final List<VibrationEffectSegment> mEffectSegments = new ArrayList<>();
|
||||
private final List<Integer> mBraking = new ArrayList<>();
|
||||
private final Map<Long, List<VibrationEffectSegment>> mEffectSegments = new TreeMap<>();
|
||||
private final Map<Long, List<Integer>> mBraking = new HashMap<>();
|
||||
private final List<Float> mAmplitudes = new ArrayList<>();
|
||||
private final List<Boolean> mExternalControlStates = new ArrayList<>();
|
||||
private final Handler mHandler;
|
||||
@@ -67,6 +68,14 @@ final class FakeVibratorControllerProvider {
|
||||
private float mQFactor = Float.NaN;
|
||||
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 {
|
||||
public int vibratorId;
|
||||
public OnVibrationCompleteListener listener;
|
||||
@@ -86,7 +95,7 @@ final class FakeVibratorControllerProvider {
|
||||
|
||||
@Override
|
||||
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));
|
||||
applyLatency();
|
||||
scheduleListener(milliseconds, vibrationId);
|
||||
@@ -110,7 +119,8 @@ final class FakeVibratorControllerProvider {
|
||||
|| Arrays.binarySearch(mSupportedEffects, (int) effect) < 0) {
|
||||
return 0;
|
||||
}
|
||||
mEffectSegments.add(new PrebakedSegment((int) effect, false, (int) strength));
|
||||
recordEffectSegment(vibrationId,
|
||||
new PrebakedSegment((int) effect, false, (int) strength));
|
||||
applyLatency();
|
||||
scheduleListener(EFFECT_DURATION, vibrationId);
|
||||
return EFFECT_DURATION;
|
||||
@@ -121,7 +131,7 @@ final class FakeVibratorControllerProvider {
|
||||
long duration = 0;
|
||||
for (PrimitiveSegment primitive : effects) {
|
||||
duration += EFFECT_DURATION + primitive.getDelay();
|
||||
mEffectSegments.add(primitive);
|
||||
recordEffectSegment(vibrationId, primitive);
|
||||
}
|
||||
applyLatency();
|
||||
scheduleListener(duration, vibrationId);
|
||||
@@ -133,9 +143,9 @@ final class FakeVibratorControllerProvider {
|
||||
long duration = 0;
|
||||
for (RampSegment primitive : primitives) {
|
||||
duration += primitive.getDuration();
|
||||
mEffectSegments.add(primitive);
|
||||
recordEffectSegment(vibrationId, primitive);
|
||||
}
|
||||
mBraking.add(braking);
|
||||
recordBraking(vibrationId, braking);
|
||||
applyLatency();
|
||||
scheduleListener(duration, vibrationId);
|
||||
return duration;
|
||||
@@ -304,15 +314,35 @@ final class FakeVibratorControllerProvider {
|
||||
}
|
||||
|
||||
/** Return the braking values passed to the compose PWLE method. */
|
||||
public List<Integer> getBraking() {
|
||||
return mBraking;
|
||||
public List<Integer> getBraking(long vibrationId) {
|
||||
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. */
|
||||
public List<VibrationEffectSegment> getEffectSegments() {
|
||||
return new ArrayList<>(mEffectSegments);
|
||||
public List<VibrationEffectSegment> getEffectSegments(long vibrationId) {
|
||||
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. */
|
||||
public List<Boolean> getExternalControlStates() {
|
||||
return mExternalControlStates;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -571,27 +571,27 @@ public class VibratorManagerServiceTest {
|
||||
VibratorManagerService service = createSystemReadyService();
|
||||
vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), RINGTONE_ATTRS);
|
||||
// Wait before checking it never played.
|
||||
assertFalse(waitUntil(s -> !fakeVibrator.getEffectSegments().isEmpty(),
|
||||
assertFalse(waitUntil(s -> !fakeVibrator.getAllEffectSegments().isEmpty(),
|
||||
service, /* timeout= */ 50));
|
||||
|
||||
setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 0);
|
||||
setUserSetting(Settings.System.APPLY_RAMPING_RINGER, 1);
|
||||
service = createSystemReadyService();
|
||||
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));
|
||||
|
||||
setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 1);
|
||||
setUserSetting(Settings.System.APPLY_RAMPING_RINGER, 0);
|
||||
service = createSystemReadyService();
|
||||
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));
|
||||
|
||||
assertEquals(
|
||||
Arrays.asList(expectedPrebaked(VibrationEffect.EFFECT_HEAVY_CLICK),
|
||||
expectedPrebaked(VibrationEffect.EFFECT_DOUBLE_CLICK)),
|
||||
mVibratorProviders.get(1).getEffectSegments());
|
||||
mVibratorProviders.get(1).getAllEffectSegments());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -604,25 +604,25 @@ public class VibratorManagerServiceTest {
|
||||
mRegisteredPowerModeListener.onLowPowerModeChanged(LOW_POWER_STATE);
|
||||
vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_TICK), HAPTIC_FEEDBACK_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));
|
||||
|
||||
mRegisteredPowerModeListener.onLowPowerModeChanged(NORMAL_POWER_STATE);
|
||||
vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_HEAVY_CLICK),
|
||||
/* attrs= */ null);
|
||||
assertTrue(waitUntil(s -> fakeVibrator.getEffectSegments().size() == 2,
|
||||
assertTrue(waitUntil(s -> fakeVibrator.getAllEffectSegments().size() == 2,
|
||||
service, TEST_TIMEOUT_MILLIS));
|
||||
|
||||
vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK),
|
||||
NOTIFICATION_ATTRS);
|
||||
assertTrue(waitUntil(s -> fakeVibrator.getEffectSegments().size() == 3,
|
||||
assertTrue(waitUntil(s -> fakeVibrator.getAllEffectSegments().size() == 3,
|
||||
service, TEST_TIMEOUT_MILLIS));
|
||||
|
||||
assertEquals(
|
||||
Arrays.asList(expectedPrebaked(VibrationEffect.EFFECT_CLICK),
|
||||
expectedPrebaked(VibrationEffect.EFFECT_HEAVY_CLICK),
|
||||
expectedPrebaked(VibrationEffect.EFFECT_DOUBLE_CLICK)),
|
||||
mVibratorProviders.get(1).getEffectSegments());
|
||||
mVibratorProviders.get(1).getAllEffectSegments());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -738,14 +738,14 @@ public class VibratorManagerServiceTest {
|
||||
VibrationAttributes.USAGE_UNKNOWN).build());
|
||||
|
||||
// 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));
|
||||
|
||||
vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK),
|
||||
HAPTIC_FEEDBACK_ATTRS);
|
||||
|
||||
// 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));
|
||||
|
||||
// The time estimate is recorded when the vibration starts, repeating vibrations
|
||||
@@ -767,14 +767,14 @@ public class VibratorManagerServiceTest {
|
||||
VibrationAttributes.USAGE_ALARM).build());
|
||||
|
||||
// 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));
|
||||
|
||||
vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK),
|
||||
HAPTIC_FEEDBACK_ATTRS);
|
||||
|
||||
// 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));
|
||||
}
|
||||
|
||||
@@ -798,7 +798,7 @@ public class VibratorManagerServiceTest {
|
||||
verify(mIInputManagerMock).vibrateCombined(eq(1), eq(effect), any());
|
||||
|
||||
// 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));
|
||||
}
|
||||
|
||||
@@ -863,8 +863,8 @@ public class VibratorManagerServiceTest {
|
||||
verify(mNativeWrapperMock).triggerSynced(anyLong());
|
||||
PrimitiveSegment expected = new PrimitiveSegment(
|
||||
VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 100);
|
||||
assertEquals(Arrays.asList(expected), mVibratorProviders.get(1).getEffectSegments());
|
||||
assertEquals(Arrays.asList(expected), mVibratorProviders.get(2).getEffectSegments());
|
||||
assertEquals(Arrays.asList(expected), mVibratorProviders.get(1).getAllEffectSegments());
|
||||
assertEquals(Arrays.asList(expected), mVibratorProviders.get(2).getAllEffectSegments());
|
||||
|
||||
// VibrationThread needs some time to react to native callbacks and stop the vibrator.
|
||||
assertTrue(waitUntil(s -> !s.isVibrating(1), service, TEST_TIMEOUT_MILLIS));
|
||||
@@ -891,7 +891,7 @@ public class VibratorManagerServiceTest {
|
||||
.compose())
|
||||
.combine();
|
||||
vibrate(service, effect, ALARM_ATTRS);
|
||||
assertTrue(waitUntil(s -> !fakeVibrator1.getEffectSegments().isEmpty(), service,
|
||||
assertTrue(waitUntil(s -> !fakeVibrator1.getAllEffectSegments().isEmpty(), service,
|
||||
TEST_TIMEOUT_MILLIS));
|
||||
|
||||
verify(mNativeWrapperMock).prepareSynced(eq(new int[]{1, 2}));
|
||||
@@ -915,7 +915,7 @@ public class VibratorManagerServiceTest {
|
||||
.addVibrator(2, VibrationEffect.createOneShot(10, 100))
|
||||
.combine();
|
||||
vibrate(service, effect, ALARM_ATTRS);
|
||||
assertTrue(waitUntil(s -> !fakeVibrator1.getEffectSegments().isEmpty(), service,
|
||||
assertTrue(waitUntil(s -> !fakeVibrator1.getAllEffectSegments().isEmpty(), service,
|
||||
TEST_TIMEOUT_MILLIS));
|
||||
|
||||
verify(mNativeWrapperMock, never()).prepareSynced(any());
|
||||
@@ -935,8 +935,8 @@ public class VibratorManagerServiceTest {
|
||||
.addVibrator(2, VibrationEffect.createOneShot(10, 100))
|
||||
.combine();
|
||||
vibrate(service, effect, ALARM_ATTRS);
|
||||
assertTrue(waitUntil(s -> !mVibratorProviders.get(1).getEffectSegments().isEmpty(), service,
|
||||
TEST_TIMEOUT_MILLIS));
|
||||
assertTrue(waitUntil(s -> !mVibratorProviders.get(1).getAllEffectSegments().isEmpty(),
|
||||
service, TEST_TIMEOUT_MILLIS));
|
||||
|
||||
verify(mNativeWrapperMock).prepareSynced(eq(new int[]{1, 2}));
|
||||
verify(mNativeWrapperMock, never()).triggerSynced(anyLong());
|
||||
@@ -956,8 +956,8 @@ public class VibratorManagerServiceTest {
|
||||
.addVibrator(2, VibrationEffect.createOneShot(10, 100))
|
||||
.combine();
|
||||
vibrate(service, effect, ALARM_ATTRS);
|
||||
assertTrue(waitUntil(s -> !mVibratorProviders.get(1).getEffectSegments().isEmpty(), service,
|
||||
TEST_TIMEOUT_MILLIS));
|
||||
assertTrue(waitUntil(s -> !mVibratorProviders.get(1).getAllEffectSegments().isEmpty(),
|
||||
service, TEST_TIMEOUT_MILLIS));
|
||||
|
||||
verify(mNativeWrapperMock).prepareSynced(eq(new int[]{1, 2}));
|
||||
verify(mNativeWrapperMock).triggerSynced(anyLong());
|
||||
@@ -995,26 +995,26 @@ public class VibratorManagerServiceTest {
|
||||
vibrate(service, CombinedVibration.startSequential()
|
||||
.addNext(1, VibrationEffect.createOneShot(100, 125))
|
||||
.combine(), NOTIFICATION_ATTRS);
|
||||
assertTrue(waitUntil(s -> fakeVibrator.getEffectSegments().size() == 1,
|
||||
assertTrue(waitUntil(s -> fakeVibrator.getAllEffectSegments().size() == 1,
|
||||
service, TEST_TIMEOUT_MILLIS));
|
||||
|
||||
vibrate(service, VibrationEffect.startComposition()
|
||||
.addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f)
|
||||
.compose(), HAPTIC_FEEDBACK_ATTRS);
|
||||
assertTrue(waitUntil(s -> fakeVibrator.getEffectSegments().size() == 2,
|
||||
assertTrue(waitUntil(s -> fakeVibrator.getAllEffectSegments().size() == 2,
|
||||
service, TEST_TIMEOUT_MILLIS));
|
||||
|
||||
vibrate(service, VibrationEffect.startComposition()
|
||||
.addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f)
|
||||
.compose(), ALARM_ATTRS);
|
||||
assertTrue(waitUntil(s -> fakeVibrator.getEffectSegments().size() == 3,
|
||||
assertTrue(waitUntil(s -> fakeVibrator.getAllEffectSegments().size() == 3,
|
||||
service, TEST_TIMEOUT_MILLIS));
|
||||
|
||||
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));
|
||||
|
||||
assertEquals(3, fakeVibrator.getEffectSegments().size());
|
||||
assertEquals(3, fakeVibrator.getAllEffectSegments().size());
|
||||
|
||||
// Notification vibrations will be scaled with SCALE_HIGH or none if default is 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.
|
||||
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.
|
||||
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.
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user