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,
CANCELLED,
IGNORED_ERROR_APP_OPS,
IGNORED_ERROR_CANCELLING,
IGNORED_ERROR_SCHEDULING,
IGNORED_ERROR_TOKEN,
IGNORED,
IGNORED_APP_OPS,

View File

@@ -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

View File

@@ -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) {

View File

@@ -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;

View File

@@ -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.
}