Loading services/core/java/com/android/server/vibrator/VendorVibrationSession.java +91 −13 Original line number Original line Diff line number Diff line Loading @@ -37,8 +37,11 @@ import android.util.Slog; import android.util.proto.ProtoOutputStream; import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import java.util.ArrayList; import java.util.Arrays; import java.util.Arrays; import java.util.List; import java.util.Locale; import java.util.Locale; import java.util.NoSuchElementException; import java.util.NoSuchElementException; Loading @@ -60,6 +63,9 @@ final class VendorVibrationSession extends IVibrationSession.Stub * used for another vibration. * used for another vibration. */ */ void onSessionReleased(long sessionId); void onSessionReleased(long sessionId); /** Request the manager to trigger a vibration within this session. */ void vibrate(long sessionId, CallerInfo callerInfo, CombinedVibration vibration); } } private final Object mLock = new Object(); private final Object mLock = new Object(); Loading @@ -71,7 +77,9 @@ final class VendorVibrationSession extends IVibrationSession.Stub private final IVibrationSessionCallback mCallback; private final IVibrationSessionCallback mCallback; private final CallerInfo mCallerInfo; private final CallerInfo mCallerInfo; private final VibratorManagerHooks mManagerHooks; private final VibratorManagerHooks mManagerHooks; private final DeviceAdapter mDeviceAdapter; private final Handler mHandler; private final Handler mHandler; private final List<DebugInfo> mVibrations = new ArrayList<>(); @GuardedBy("mLock") @GuardedBy("mLock") private Status mStatus = Status.RUNNING; private Status mStatus = Status.RUNNING; Loading @@ -83,24 +91,28 @@ final class VendorVibrationSession extends IVibrationSession.Stub private long mEndUptime; private long mEndUptime; @GuardedBy("mLock") @GuardedBy("mLock") private long mEndTime; // for debugging private long mEndTime; // for debugging @GuardedBy("mLock") private VibrationStepConductor mConductor; VendorVibrationSession(@NonNull CallerInfo callerInfo, @NonNull Handler handler, VendorVibrationSession(@NonNull CallerInfo callerInfo, @NonNull Handler handler, @NonNull VibratorManagerHooks managerHooks, @NonNull int[] vibratorIds, @NonNull VibratorManagerHooks managerHooks, @NonNull DeviceAdapter deviceAdapter, @NonNull IVibrationSessionCallback callback) { @NonNull IVibrationSessionCallback callback) { mCreateUptime = SystemClock.uptimeMillis(); mCreateUptime = SystemClock.uptimeMillis(); mCreateTime = System.currentTimeMillis(); mCreateTime = System.currentTimeMillis(); mVibratorIds = vibratorIds; mVibratorIds = deviceAdapter.getAvailableVibratorIds(); mHandler = handler; mHandler = handler; mCallback = callback; mCallback = callback; mCallerInfo = callerInfo; mCallerInfo = callerInfo; mManagerHooks = managerHooks; mManagerHooks = managerHooks; mDeviceAdapter = deviceAdapter; CancellationSignal.fromTransport(mCancellationSignal).setOnCancelListener(this); CancellationSignal.fromTransport(mCancellationSignal).setOnCancelListener(this); } } @Override @Override public void vibrate(CombinedVibration vibration, String reason) { public void vibrate(CombinedVibration vibration, String reason) { // TODO(b/345414356): implement vibration support CallerInfo vibrationCallerInfo = new CallerInfo(mCallerInfo.attrs, mCallerInfo.uid, throw new UnsupportedOperationException("Vendor session vibrations not yet implemented"); mCallerInfo.deviceId, mCallerInfo.opPkg, reason); mManagerHooks.vibrate(mSessionId, vibrationCallerInfo, vibration); } } @Override @Override Loading Loading @@ -146,7 +158,7 @@ final class VendorVibrationSession extends IVibrationSession.Stub public DebugInfo getDebugInfo() { public DebugInfo getDebugInfo() { synchronized (mLock) { synchronized (mLock) { return new DebugInfoImpl(mStatus, mCallerInfo, mCreateUptime, mCreateTime, mStartTime, return new DebugInfoImpl(mStatus, mCallerInfo, mCreateUptime, mCreateTime, mStartTime, mEndUptime, mEndTime); mEndUptime, mEndTime, mVibrations); } } } } Loading Loading @@ -200,12 +212,12 @@ final class VendorVibrationSession extends IVibrationSession.Stub @Override @Override public void notifyVibratorCallback(int vibratorId, long vibrationId) { public void notifyVibratorCallback(int vibratorId, long vibrationId) { // TODO(b/345414356): implement vibration support // Ignore it, the session vibration playback doesn't depend on HAL timings } } @Override @Override public void notifySyncedVibratorsCallback(long vibrationId) { public void notifySyncedVibratorsCallback(long vibrationId) { // TODO(b/345414356): implement vibration support // Ignore it, the session vibration playback doesn't depend on HAL timings } } @Override @Override Loading @@ -214,8 +226,9 @@ final class VendorVibrationSession extends IVibrationSession.Stub // If end was not requested then the HAL has cancelled the session. // If end was not requested then the HAL has cancelled the session. maybeSetEndRequestLocked(Status.CANCELLED_BY_UNKNOWN_REASON); maybeSetEndRequestLocked(Status.CANCELLED_BY_UNKNOWN_REASON); maybeSetStatusToRequestedLocked(); maybeSetStatusToRequestedLocked(); clearVibrationConductor(); } } mManagerHooks.onSessionReleased(mSessionId); mHandler.post(() -> mManagerHooks.onSessionReleased(mSessionId)); } } @Override @Override Loading @@ -228,7 +241,8 @@ final class VendorVibrationSession extends IVibrationSession.Stub /* includeDate= */ true)) /* includeDate= */ true)) + ", status: " + mStatus.name().toLowerCase(Locale.ROOT) + ", status: " + mStatus.name().toLowerCase(Locale.ROOT) + ", callerInfo: " + mCallerInfo + ", callerInfo: " + mCallerInfo + ", vibratorIds: " + Arrays.toString(mVibratorIds); + ", vibratorIds: " + Arrays.toString(mVibratorIds) + ", vibrations: " + mVibrations; } } } } Loading @@ -254,6 +268,13 @@ final class VendorVibrationSession extends IVibrationSession.Stub return mVibratorIds; return mVibratorIds; } } @VisibleForTesting public List<DebugInfo> getVibrations() { synchronized (mLock) { return new ArrayList<>(mVibrations); } } public ICancellationSignal getCancellationSignal() { public ICancellationSignal getCancellationSignal() { return mCancellationSignal; return mCancellationSignal; } } Loading @@ -278,7 +299,39 @@ final class VendorVibrationSession extends IVibrationSession.Stub } } if (isAlreadyEnded) { if (isAlreadyEnded) { // Session already ended, make sure we end it in the HAL. // Session already ended, make sure we end it in the HAL. mManagerHooks.endSession(mSessionId, /* shouldAbort= */ true); mHandler.post(() -> mManagerHooks.endSession(mSessionId, /* shouldAbort= */ true)); } } public void notifyVibrationAttempt(DebugInfo vibrationDebugInfo) { mVibrations.add(vibrationDebugInfo); } @Nullable public VibrationStepConductor clearVibrationConductor() { synchronized (mLock) { VibrationStepConductor conductor = mConductor; if (conductor != null) { mVibrations.add(conductor.getVibration().getDebugInfo()); } mConductor = null; return conductor; } } public DeviceAdapter getDeviceAdapter() { return mDeviceAdapter; } public boolean maybeSetVibrationConductor(VibrationStepConductor conductor) { synchronized (mLock) { if (mConductor != null) { Slog.d(TAG, "Vibration session still dispatching previous vibration," + " new vibration ignored"); return false; } mConductor = conductor; return true; } } } } Loading @@ -296,7 +349,7 @@ final class VendorVibrationSession extends IVibrationSession.Stub } } } } if (shouldTriggerSessionHook) { if (shouldTriggerSessionHook) { mManagerHooks.endSession(mSessionId, shouldAbort); mHandler.post(() -> mManagerHooks.endSession(mSessionId, shouldAbort)); } } } } Loading @@ -309,6 +362,11 @@ final class VendorVibrationSession extends IVibrationSession.Stub mEndStatusRequest = status; mEndStatusRequest = status; mEndTime = System.currentTimeMillis(); mEndTime = System.currentTimeMillis(); mEndUptime = SystemClock.uptimeMillis(); mEndUptime = SystemClock.uptimeMillis(); if (mConductor != null) { // Vibration is being dispatched when session end was requested, cancel it. mConductor.notifyCancelled(new Vibration.EndInfo(status), /* immediate= */ status != Status.FINISHED); } if (isStarted()) { if (isStarted()) { // Only trigger "finishing" callback if session started. // Only trigger "finishing" callback if session started. // Run client callback in separate thread. // Run client callback in separate thread. Loading Loading @@ -377,6 +435,7 @@ final class VendorVibrationSession extends IVibrationSession.Stub static final class DebugInfoImpl implements VibrationSession.DebugInfo { static final class DebugInfoImpl implements VibrationSession.DebugInfo { private final Status mStatus; private final Status mStatus; private final CallerInfo mCallerInfo; private final CallerInfo mCallerInfo; private final List<DebugInfo> mVibrations; private final long mCreateUptime; private final long mCreateUptime; private final long mCreateTime; private final long mCreateTime; Loading @@ -385,7 +444,7 @@ final class VendorVibrationSession extends IVibrationSession.Stub private final long mDurationMs; private final long mDurationMs; DebugInfoImpl(Status status, CallerInfo callerInfo, long createUptime, long createTime, DebugInfoImpl(Status status, CallerInfo callerInfo, long createUptime, long createTime, long startTime, long endUptime, long endTime) { long startTime, long endUptime, long endTime, List<DebugInfo> vibrations) { mStatus = status; mStatus = status; mCallerInfo = callerInfo; mCallerInfo = callerInfo; mCreateUptime = createUptime; mCreateUptime = createUptime; Loading @@ -393,6 +452,7 @@ final class VendorVibrationSession extends IVibrationSession.Stub mStartTime = startTime; mStartTime = startTime; mEndTime = endTime; mEndTime = endTime; mDurationMs = endUptime > 0 ? endUptime - createUptime : -1; mDurationMs = endUptime > 0 ? endUptime - createUptime : -1; mVibrations = vibrations == null ? new ArrayList<>() : new ArrayList<>(vibrations); } } @Override @Override Loading @@ -418,6 +478,9 @@ final class VendorVibrationSession extends IVibrationSession.Stub @Override @Override public void logMetrics(VibratorFrameworkStatsLogger statsLogger) { public void logMetrics(VibratorFrameworkStatsLogger statsLogger) { for (DebugInfo vibration : mVibrations) { vibration.logMetrics(statsLogger); } } } @Override @Override Loading Loading @@ -448,6 +511,14 @@ final class VendorVibrationSession extends IVibrationSession.Stub pw.println("endTime = " + (mEndTime == 0 ? null pw.println("endTime = " + (mEndTime == 0 ? null : formatTime(mEndTime, /*includeDate=*/ true))); : formatTime(mEndTime, /*includeDate=*/ true))); pw.println("callerInfo = " + mCallerInfo); pw.println("callerInfo = " + mCallerInfo); pw.println("vibrations:"); pw.increaseIndent(); for (DebugInfo vibration : mVibrations) { vibration.dump(pw); } pw.decreaseIndent(); pw.decreaseIndent(); pw.decreaseIndent(); } } Loading Loading @@ -477,6 +548,12 @@ final class VendorVibrationSession extends IVibrationSession.Stub " | %s (uid=%d, deviceId=%d) | reason: %s", " | %s (uid=%d, deviceId=%d) | reason: %s", mCallerInfo.opPkg, mCallerInfo.uid, mCallerInfo.deviceId, mCallerInfo.reason); mCallerInfo.opPkg, mCallerInfo.uid, mCallerInfo.deviceId, mCallerInfo.reason); pw.println(timingsStr + paramStr + audioUsageStr + callerStr); pw.println(timingsStr + paramStr + audioUsageStr + callerStr); pw.increaseIndent(); for (DebugInfo vibration : mVibrations) { vibration.dumpCompact(pw); } pw.decreaseIndent(); } } @Override @Override Loading @@ -487,7 +564,8 @@ final class VendorVibrationSession extends IVibrationSession.Stub /* includeDate= */ true)) /* includeDate= */ true)) + ", durationMs: " + mDurationMs + ", durationMs: " + mDurationMs + ", status: " + mStatus.name().toLowerCase(Locale.ROOT) + ", status: " + mStatus.name().toLowerCase(Locale.ROOT) + ", callerInfo: " + mCallerInfo; + ", callerInfo: " + mCallerInfo + ", vibrations: " + mVibrations; } } } } } } services/core/java/com/android/server/vibrator/VibratorManagerService.java +201 −47 File changed.Preview size limit exceeded, changes collapsed. Show changes services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java +220 −1 Original line number Original line Diff line number Diff line Loading @@ -2865,7 +2865,7 @@ public class VibratorManagerServiceTest { mTestLooper.dispatchAll(); mTestLooper.dispatchAll(); assertThat(session.getStatus()).isEqualTo(Status.IGNORED_UNSUPPORTED); assertThat(session.getStatus()).isEqualTo(Status.IGNORED_UNSUPPORTED); verify(mNativeWrapperMock).startSession(eq(session.getSessionId()), eq(new int[] {1, 3})); verify(mNativeWrapperMock, never()).startSession(anyLong(), any(int[].class)); verify(callback, never()).onStarted(any(IVibrationSession.class)); verify(callback, never()).onStarted(any(IVibrationSession.class)); verify(callback, never()).onFinishing(); verify(callback, never()).onFinishing(); verify(callback) verify(callback) Loading @@ -2889,6 +2889,7 @@ public class VibratorManagerServiceTest { verify(callback).onStarted(captor.capture()); verify(callback).onStarted(captor.capture()); captor.getValue().finishSession(); captor.getValue().finishSession(); mTestLooper.dispatchAll(); // Session not ended until HAL callback. // Session not ended until HAL callback. assertThat(session.getStatus()).isEqualTo(Status.RUNNING); assertThat(session.getStatus()).isEqualTo(Status.RUNNING); Loading Loading @@ -3138,6 +3139,224 @@ public class VibratorManagerServiceTest { verify(callback).onStarted(any(IVibrationSession.class)); verify(callback).onStarted(any(IVibrationSession.class)); } } @Test @EnableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS) public void vibrateInSession_afterCancel_vibrationIgnored() throws Exception { mockCapabilities(IVibratorManager.CAP_START_SESSIONS); mockVibrators(1, 2); FakeVibratorControllerProvider fakeVibrator1 = mVibratorProviders.get(1); fakeVibrator1.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL); VibratorManagerService service = createSystemReadyService(); int sessionFinishDelayMs = 200; IVibrationSessionCallback callback = mockSessionCallbacks(sessionFinishDelayMs); VendorVibrationSession session = startSession(service, RINGTONE_ATTRS, callback, 1, 2); mTestLooper.dispatchAll(); verify(mNativeWrapperMock).startSession(eq(session.getSessionId()), eq(new int[] {1, 2})); ArgumentCaptor<IVibrationSession> captor = ArgumentCaptor.forClass(IVibrationSession.class); verify(callback).onStarted(captor.capture()); IVibrationSession startedSession = captor.getValue(); startedSession.cancelSession(); startedSession.vibrate( CombinedVibration.createParallel(VibrationEffect.createOneShot(10, 255)), "reason"); // VibrationThread will never start this vibration. assertFalse(waitUntil(s -> !fakeVibrator1.getAmplitudes().isEmpty(), service, TEST_TIMEOUT_MILLIS)); // Dispatch HAL callbacks. mTestLooper.moveTimeForward(sessionFinishDelayMs); mTestLooper.dispatchAll(); assertThat(session.getStatus()).isEqualTo(Status.CANCELLED_BY_USER); verify(callback).onFinishing(); verify(callback).onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_CANCELED)); } @Test @EnableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS) public void vibrateInSession_afterFinish_vibrationIgnored() throws Exception { mockCapabilities(IVibratorManager.CAP_START_SESSIONS); mockVibrators(1, 2); FakeVibratorControllerProvider fakeVibrator1 = mVibratorProviders.get(1); fakeVibrator1.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL); VibratorManagerService service = createSystemReadyService(); int sessionFinishDelayMs = 200; IVibrationSessionCallback callback = mockSessionCallbacks(sessionFinishDelayMs); VendorVibrationSession session = startSession(service, RINGTONE_ATTRS, callback, 1, 2); mTestLooper.dispatchAll(); verify(mNativeWrapperMock).startSession(eq(session.getSessionId()), eq(new int[] {1, 2})); ArgumentCaptor<IVibrationSession> captor = ArgumentCaptor.forClass(IVibrationSession.class); verify(callback).onStarted(captor.capture()); IVibrationSession startedSession = captor.getValue(); startedSession.finishSession(); mTestLooper.dispatchAll(); startedSession.vibrate( CombinedVibration.createParallel(VibrationEffect.createOneShot(10, 255)), "reason"); // Session not ended until HAL callback. assertThat(session.getStatus()).isEqualTo(Status.RUNNING); // VibrationThread will never start this vibration. assertFalse(waitUntil(s -> !fakeVibrator1.getAmplitudes().isEmpty(), service, TEST_TIMEOUT_MILLIS)); // Dispatch HAL callbacks. mTestLooper.moveTimeForward(sessionFinishDelayMs); mTestLooper.dispatchAll(); assertThat(session.getStatus()).isEqualTo(Status.FINISHED); verify(callback).onFinishing(); verify(callback).onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_SUCCESS)); } @Test @EnableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS) public void vibrateInSession_repeatingVibration_vibrationIgnored() throws Exception { mockCapabilities(IVibratorManager.CAP_START_SESSIONS); mockVibrators(1, 2); FakeVibratorControllerProvider fakeVibrator1 = mVibratorProviders.get(1); fakeVibrator1.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL); VibratorManagerService service = createSystemReadyService(); int sessionFinishDelayMs = 200; IVibrationSessionCallback callback = mockSessionCallbacks(sessionFinishDelayMs); VendorVibrationSession session = startSession(service, RINGTONE_ATTRS, callback, 1, 2); mTestLooper.dispatchAll(); verify(mNativeWrapperMock).startSession(eq(session.getSessionId()), eq(new int[] {1, 2})); ArgumentCaptor<IVibrationSession> captor = ArgumentCaptor.forClass(IVibrationSession.class); verify(callback).onStarted(captor.capture()); IVibrationSession startedSession = captor.getValue(); startedSession.vibrate( CombinedVibration.createParallel( VibrationEffect.createWaveform(new long[]{ 10, 10, 10, 10}, 0)), "reason"); // VibrationThread will never start this vibration. assertFalse(waitUntil(s -> !fakeVibrator1.getAmplitudes().isEmpty(), service, TEST_TIMEOUT_MILLIS)); startedSession.finishSession(); mTestLooper.dispatchAll(); // Dispatch HAL callbacks. mTestLooper.moveTimeForward(sessionFinishDelayMs); mTestLooper.dispatchAll(); assertThat(session.getStatus()).isEqualTo(Status.FINISHED); assertThat(service.isVibrating(1)).isFalse(); assertThat(service.isVibrating(2)).isFalse(); verify(callback).onFinishing(); verify(callback).onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_SUCCESS)); } @Test @EnableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS) public void vibrateInSession_singleVibration_playsAllVibrateCommands() throws Exception { mockCapabilities(IVibratorManager.CAP_START_SESSIONS); mockVibrators(1, 2); FakeVibratorControllerProvider fakeVibrator1 = mVibratorProviders.get(1); fakeVibrator1.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL); FakeVibratorControllerProvider fakeVibrator2 = mVibratorProviders.get(1); fakeVibrator2.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL); VibratorManagerService service = createSystemReadyService(); int sessionFinishDelayMs = 200; IVibrationSessionCallback callback = mockSessionCallbacks(sessionFinishDelayMs); VendorVibrationSession session = startSession(service, RINGTONE_ATTRS, callback, 1, 2); mTestLooper.dispatchAll(); verify(mNativeWrapperMock).startSession(eq(session.getSessionId()), eq(new int[] {1, 2})); ArgumentCaptor<IVibrationSession> captor = ArgumentCaptor.forClass(IVibrationSession.class); verify(callback).onStarted(captor.capture()); IVibrationSession startedSession = captor.getValue(); startedSession.vibrate( CombinedVibration.createParallel( VibrationEffect.createWaveform(new long[]{ 10, 10, 10, 10}, -1)), "reason"); // VibrationThread will start this vibration async, so wait until vibration is triggered. // Vibrators will receive 2 requests for the waveform playback assertTrue(waitUntil(s -> fakeVibrator1.getAmplitudes().size() == 2, service, TEST_TIMEOUT_MILLIS)); assertTrue(waitUntil(s -> fakeVibrator2.getAmplitudes().size() == 2, service, TEST_TIMEOUT_MILLIS)); startedSession.finishSession(); mTestLooper.dispatchAll(); // Dispatch HAL callbacks. mTestLooper.moveTimeForward(sessionFinishDelayMs); mTestLooper.dispatchAll(); assertThat(session.getStatus()).isEqualTo(Status.FINISHED); assertThat(service.isVibrating(1)).isFalse(); assertThat(service.isVibrating(2)).isFalse(); verify(callback).onFinishing(); verify(callback).onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_SUCCESS)); } @Test @EnableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS) public void vibrateInSession_multipleVibrations_playsAllVibrations() throws Exception { mockCapabilities(IVibratorManager.CAP_START_SESSIONS); mockVibrators(1, 2); FakeVibratorControllerProvider fakeVibrator1 = mVibratorProviders.get(1); fakeVibrator1.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL); VibratorManagerService service = createSystemReadyService(); int sessionFinishDelayMs = 200; IVibrationSessionCallback callback = mockSessionCallbacks(sessionFinishDelayMs); VendorVibrationSession session = startSession(service, RINGTONE_ATTRS, callback, 1, 2); mTestLooper.dispatchAll(); verify(mNativeWrapperMock).startSession(eq(session.getSessionId()), eq(new int[] {1, 2})); ArgumentCaptor<IVibrationSession> captor = ArgumentCaptor.forClass(IVibrationSession.class); verify(callback).onStarted(captor.capture()); IVibrationSession startedSession = captor.getValue(); startedSession.vibrate( CombinedVibration.createParallel(VibrationEffect.createOneShot(10, 255)), "reason"); // VibrationThread will start this vibration async, so wait until vibration is completed. assertTrue(waitUntil(s -> fakeVibrator1.getAmplitudes().size() == 1, service, TEST_TIMEOUT_MILLIS)); assertTrue(waitUntil(s -> !session.getVibrations().isEmpty(), service, TEST_TIMEOUT_MILLIS)); startedSession.vibrate( CombinedVibration.createParallel(VibrationEffect.createOneShot(20, 255)), "reason"); assertTrue(waitUntil(s -> fakeVibrator1.getAmplitudes().size() == 2, service, TEST_TIMEOUT_MILLIS)); startedSession.finishSession(); mTestLooper.dispatchAll(); // Dispatch HAL callbacks. mTestLooper.moveTimeForward(sessionFinishDelayMs); mTestLooper.dispatchAll(); assertThat(session.getStatus()).isEqualTo(Status.FINISHED); assertThat(service.isVibrating(1)).isFalse(); assertThat(service.isVibrating(2)).isFalse(); verify(callback).onFinishing(); verify(callback).onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_SUCCESS)); } @Test @Test public void frameworkStats_externalVibration_reportsAllMetrics() throws Exception { public void frameworkStats_externalVibration_reportsAllMetrics() throws Exception { mockVibrators(1); mockVibrators(1); Loading Loading
services/core/java/com/android/server/vibrator/VendorVibrationSession.java +91 −13 Original line number Original line Diff line number Diff line Loading @@ -37,8 +37,11 @@ import android.util.Slog; import android.util.proto.ProtoOutputStream; import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import java.util.ArrayList; import java.util.Arrays; import java.util.Arrays; import java.util.List; import java.util.Locale; import java.util.Locale; import java.util.NoSuchElementException; import java.util.NoSuchElementException; Loading @@ -60,6 +63,9 @@ final class VendorVibrationSession extends IVibrationSession.Stub * used for another vibration. * used for another vibration. */ */ void onSessionReleased(long sessionId); void onSessionReleased(long sessionId); /** Request the manager to trigger a vibration within this session. */ void vibrate(long sessionId, CallerInfo callerInfo, CombinedVibration vibration); } } private final Object mLock = new Object(); private final Object mLock = new Object(); Loading @@ -71,7 +77,9 @@ final class VendorVibrationSession extends IVibrationSession.Stub private final IVibrationSessionCallback mCallback; private final IVibrationSessionCallback mCallback; private final CallerInfo mCallerInfo; private final CallerInfo mCallerInfo; private final VibratorManagerHooks mManagerHooks; private final VibratorManagerHooks mManagerHooks; private final DeviceAdapter mDeviceAdapter; private final Handler mHandler; private final Handler mHandler; private final List<DebugInfo> mVibrations = new ArrayList<>(); @GuardedBy("mLock") @GuardedBy("mLock") private Status mStatus = Status.RUNNING; private Status mStatus = Status.RUNNING; Loading @@ -83,24 +91,28 @@ final class VendorVibrationSession extends IVibrationSession.Stub private long mEndUptime; private long mEndUptime; @GuardedBy("mLock") @GuardedBy("mLock") private long mEndTime; // for debugging private long mEndTime; // for debugging @GuardedBy("mLock") private VibrationStepConductor mConductor; VendorVibrationSession(@NonNull CallerInfo callerInfo, @NonNull Handler handler, VendorVibrationSession(@NonNull CallerInfo callerInfo, @NonNull Handler handler, @NonNull VibratorManagerHooks managerHooks, @NonNull int[] vibratorIds, @NonNull VibratorManagerHooks managerHooks, @NonNull DeviceAdapter deviceAdapter, @NonNull IVibrationSessionCallback callback) { @NonNull IVibrationSessionCallback callback) { mCreateUptime = SystemClock.uptimeMillis(); mCreateUptime = SystemClock.uptimeMillis(); mCreateTime = System.currentTimeMillis(); mCreateTime = System.currentTimeMillis(); mVibratorIds = vibratorIds; mVibratorIds = deviceAdapter.getAvailableVibratorIds(); mHandler = handler; mHandler = handler; mCallback = callback; mCallback = callback; mCallerInfo = callerInfo; mCallerInfo = callerInfo; mManagerHooks = managerHooks; mManagerHooks = managerHooks; mDeviceAdapter = deviceAdapter; CancellationSignal.fromTransport(mCancellationSignal).setOnCancelListener(this); CancellationSignal.fromTransport(mCancellationSignal).setOnCancelListener(this); } } @Override @Override public void vibrate(CombinedVibration vibration, String reason) { public void vibrate(CombinedVibration vibration, String reason) { // TODO(b/345414356): implement vibration support CallerInfo vibrationCallerInfo = new CallerInfo(mCallerInfo.attrs, mCallerInfo.uid, throw new UnsupportedOperationException("Vendor session vibrations not yet implemented"); mCallerInfo.deviceId, mCallerInfo.opPkg, reason); mManagerHooks.vibrate(mSessionId, vibrationCallerInfo, vibration); } } @Override @Override Loading Loading @@ -146,7 +158,7 @@ final class VendorVibrationSession extends IVibrationSession.Stub public DebugInfo getDebugInfo() { public DebugInfo getDebugInfo() { synchronized (mLock) { synchronized (mLock) { return new DebugInfoImpl(mStatus, mCallerInfo, mCreateUptime, mCreateTime, mStartTime, return new DebugInfoImpl(mStatus, mCallerInfo, mCreateUptime, mCreateTime, mStartTime, mEndUptime, mEndTime); mEndUptime, mEndTime, mVibrations); } } } } Loading Loading @@ -200,12 +212,12 @@ final class VendorVibrationSession extends IVibrationSession.Stub @Override @Override public void notifyVibratorCallback(int vibratorId, long vibrationId) { public void notifyVibratorCallback(int vibratorId, long vibrationId) { // TODO(b/345414356): implement vibration support // Ignore it, the session vibration playback doesn't depend on HAL timings } } @Override @Override public void notifySyncedVibratorsCallback(long vibrationId) { public void notifySyncedVibratorsCallback(long vibrationId) { // TODO(b/345414356): implement vibration support // Ignore it, the session vibration playback doesn't depend on HAL timings } } @Override @Override Loading @@ -214,8 +226,9 @@ final class VendorVibrationSession extends IVibrationSession.Stub // If end was not requested then the HAL has cancelled the session. // If end was not requested then the HAL has cancelled the session. maybeSetEndRequestLocked(Status.CANCELLED_BY_UNKNOWN_REASON); maybeSetEndRequestLocked(Status.CANCELLED_BY_UNKNOWN_REASON); maybeSetStatusToRequestedLocked(); maybeSetStatusToRequestedLocked(); clearVibrationConductor(); } } mManagerHooks.onSessionReleased(mSessionId); mHandler.post(() -> mManagerHooks.onSessionReleased(mSessionId)); } } @Override @Override Loading @@ -228,7 +241,8 @@ final class VendorVibrationSession extends IVibrationSession.Stub /* includeDate= */ true)) /* includeDate= */ true)) + ", status: " + mStatus.name().toLowerCase(Locale.ROOT) + ", status: " + mStatus.name().toLowerCase(Locale.ROOT) + ", callerInfo: " + mCallerInfo + ", callerInfo: " + mCallerInfo + ", vibratorIds: " + Arrays.toString(mVibratorIds); + ", vibratorIds: " + Arrays.toString(mVibratorIds) + ", vibrations: " + mVibrations; } } } } Loading @@ -254,6 +268,13 @@ final class VendorVibrationSession extends IVibrationSession.Stub return mVibratorIds; return mVibratorIds; } } @VisibleForTesting public List<DebugInfo> getVibrations() { synchronized (mLock) { return new ArrayList<>(mVibrations); } } public ICancellationSignal getCancellationSignal() { public ICancellationSignal getCancellationSignal() { return mCancellationSignal; return mCancellationSignal; } } Loading @@ -278,7 +299,39 @@ final class VendorVibrationSession extends IVibrationSession.Stub } } if (isAlreadyEnded) { if (isAlreadyEnded) { // Session already ended, make sure we end it in the HAL. // Session already ended, make sure we end it in the HAL. mManagerHooks.endSession(mSessionId, /* shouldAbort= */ true); mHandler.post(() -> mManagerHooks.endSession(mSessionId, /* shouldAbort= */ true)); } } public void notifyVibrationAttempt(DebugInfo vibrationDebugInfo) { mVibrations.add(vibrationDebugInfo); } @Nullable public VibrationStepConductor clearVibrationConductor() { synchronized (mLock) { VibrationStepConductor conductor = mConductor; if (conductor != null) { mVibrations.add(conductor.getVibration().getDebugInfo()); } mConductor = null; return conductor; } } public DeviceAdapter getDeviceAdapter() { return mDeviceAdapter; } public boolean maybeSetVibrationConductor(VibrationStepConductor conductor) { synchronized (mLock) { if (mConductor != null) { Slog.d(TAG, "Vibration session still dispatching previous vibration," + " new vibration ignored"); return false; } mConductor = conductor; return true; } } } } Loading @@ -296,7 +349,7 @@ final class VendorVibrationSession extends IVibrationSession.Stub } } } } if (shouldTriggerSessionHook) { if (shouldTriggerSessionHook) { mManagerHooks.endSession(mSessionId, shouldAbort); mHandler.post(() -> mManagerHooks.endSession(mSessionId, shouldAbort)); } } } } Loading @@ -309,6 +362,11 @@ final class VendorVibrationSession extends IVibrationSession.Stub mEndStatusRequest = status; mEndStatusRequest = status; mEndTime = System.currentTimeMillis(); mEndTime = System.currentTimeMillis(); mEndUptime = SystemClock.uptimeMillis(); mEndUptime = SystemClock.uptimeMillis(); if (mConductor != null) { // Vibration is being dispatched when session end was requested, cancel it. mConductor.notifyCancelled(new Vibration.EndInfo(status), /* immediate= */ status != Status.FINISHED); } if (isStarted()) { if (isStarted()) { // Only trigger "finishing" callback if session started. // Only trigger "finishing" callback if session started. // Run client callback in separate thread. // Run client callback in separate thread. Loading Loading @@ -377,6 +435,7 @@ final class VendorVibrationSession extends IVibrationSession.Stub static final class DebugInfoImpl implements VibrationSession.DebugInfo { static final class DebugInfoImpl implements VibrationSession.DebugInfo { private final Status mStatus; private final Status mStatus; private final CallerInfo mCallerInfo; private final CallerInfo mCallerInfo; private final List<DebugInfo> mVibrations; private final long mCreateUptime; private final long mCreateUptime; private final long mCreateTime; private final long mCreateTime; Loading @@ -385,7 +444,7 @@ final class VendorVibrationSession extends IVibrationSession.Stub private final long mDurationMs; private final long mDurationMs; DebugInfoImpl(Status status, CallerInfo callerInfo, long createUptime, long createTime, DebugInfoImpl(Status status, CallerInfo callerInfo, long createUptime, long createTime, long startTime, long endUptime, long endTime) { long startTime, long endUptime, long endTime, List<DebugInfo> vibrations) { mStatus = status; mStatus = status; mCallerInfo = callerInfo; mCallerInfo = callerInfo; mCreateUptime = createUptime; mCreateUptime = createUptime; Loading @@ -393,6 +452,7 @@ final class VendorVibrationSession extends IVibrationSession.Stub mStartTime = startTime; mStartTime = startTime; mEndTime = endTime; mEndTime = endTime; mDurationMs = endUptime > 0 ? endUptime - createUptime : -1; mDurationMs = endUptime > 0 ? endUptime - createUptime : -1; mVibrations = vibrations == null ? new ArrayList<>() : new ArrayList<>(vibrations); } } @Override @Override Loading @@ -418,6 +478,9 @@ final class VendorVibrationSession extends IVibrationSession.Stub @Override @Override public void logMetrics(VibratorFrameworkStatsLogger statsLogger) { public void logMetrics(VibratorFrameworkStatsLogger statsLogger) { for (DebugInfo vibration : mVibrations) { vibration.logMetrics(statsLogger); } } } @Override @Override Loading Loading @@ -448,6 +511,14 @@ final class VendorVibrationSession extends IVibrationSession.Stub pw.println("endTime = " + (mEndTime == 0 ? null pw.println("endTime = " + (mEndTime == 0 ? null : formatTime(mEndTime, /*includeDate=*/ true))); : formatTime(mEndTime, /*includeDate=*/ true))); pw.println("callerInfo = " + mCallerInfo); pw.println("callerInfo = " + mCallerInfo); pw.println("vibrations:"); pw.increaseIndent(); for (DebugInfo vibration : mVibrations) { vibration.dump(pw); } pw.decreaseIndent(); pw.decreaseIndent(); pw.decreaseIndent(); } } Loading Loading @@ -477,6 +548,12 @@ final class VendorVibrationSession extends IVibrationSession.Stub " | %s (uid=%d, deviceId=%d) | reason: %s", " | %s (uid=%d, deviceId=%d) | reason: %s", mCallerInfo.opPkg, mCallerInfo.uid, mCallerInfo.deviceId, mCallerInfo.reason); mCallerInfo.opPkg, mCallerInfo.uid, mCallerInfo.deviceId, mCallerInfo.reason); pw.println(timingsStr + paramStr + audioUsageStr + callerStr); pw.println(timingsStr + paramStr + audioUsageStr + callerStr); pw.increaseIndent(); for (DebugInfo vibration : mVibrations) { vibration.dumpCompact(pw); } pw.decreaseIndent(); } } @Override @Override Loading @@ -487,7 +564,8 @@ final class VendorVibrationSession extends IVibrationSession.Stub /* includeDate= */ true)) /* includeDate= */ true)) + ", durationMs: " + mDurationMs + ", durationMs: " + mDurationMs + ", status: " + mStatus.name().toLowerCase(Locale.ROOT) + ", status: " + mStatus.name().toLowerCase(Locale.ROOT) + ", callerInfo: " + mCallerInfo; + ", callerInfo: " + mCallerInfo + ", vibrations: " + mVibrations; } } } } } }
services/core/java/com/android/server/vibrator/VibratorManagerService.java +201 −47 File changed.Preview size limit exceeded, changes collapsed. Show changes
services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java +220 −1 Original line number Original line Diff line number Diff line Loading @@ -2865,7 +2865,7 @@ public class VibratorManagerServiceTest { mTestLooper.dispatchAll(); mTestLooper.dispatchAll(); assertThat(session.getStatus()).isEqualTo(Status.IGNORED_UNSUPPORTED); assertThat(session.getStatus()).isEqualTo(Status.IGNORED_UNSUPPORTED); verify(mNativeWrapperMock).startSession(eq(session.getSessionId()), eq(new int[] {1, 3})); verify(mNativeWrapperMock, never()).startSession(anyLong(), any(int[].class)); verify(callback, never()).onStarted(any(IVibrationSession.class)); verify(callback, never()).onStarted(any(IVibrationSession.class)); verify(callback, never()).onFinishing(); verify(callback, never()).onFinishing(); verify(callback) verify(callback) Loading @@ -2889,6 +2889,7 @@ public class VibratorManagerServiceTest { verify(callback).onStarted(captor.capture()); verify(callback).onStarted(captor.capture()); captor.getValue().finishSession(); captor.getValue().finishSession(); mTestLooper.dispatchAll(); // Session not ended until HAL callback. // Session not ended until HAL callback. assertThat(session.getStatus()).isEqualTo(Status.RUNNING); assertThat(session.getStatus()).isEqualTo(Status.RUNNING); Loading Loading @@ -3138,6 +3139,224 @@ public class VibratorManagerServiceTest { verify(callback).onStarted(any(IVibrationSession.class)); verify(callback).onStarted(any(IVibrationSession.class)); } } @Test @EnableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS) public void vibrateInSession_afterCancel_vibrationIgnored() throws Exception { mockCapabilities(IVibratorManager.CAP_START_SESSIONS); mockVibrators(1, 2); FakeVibratorControllerProvider fakeVibrator1 = mVibratorProviders.get(1); fakeVibrator1.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL); VibratorManagerService service = createSystemReadyService(); int sessionFinishDelayMs = 200; IVibrationSessionCallback callback = mockSessionCallbacks(sessionFinishDelayMs); VendorVibrationSession session = startSession(service, RINGTONE_ATTRS, callback, 1, 2); mTestLooper.dispatchAll(); verify(mNativeWrapperMock).startSession(eq(session.getSessionId()), eq(new int[] {1, 2})); ArgumentCaptor<IVibrationSession> captor = ArgumentCaptor.forClass(IVibrationSession.class); verify(callback).onStarted(captor.capture()); IVibrationSession startedSession = captor.getValue(); startedSession.cancelSession(); startedSession.vibrate( CombinedVibration.createParallel(VibrationEffect.createOneShot(10, 255)), "reason"); // VibrationThread will never start this vibration. assertFalse(waitUntil(s -> !fakeVibrator1.getAmplitudes().isEmpty(), service, TEST_TIMEOUT_MILLIS)); // Dispatch HAL callbacks. mTestLooper.moveTimeForward(sessionFinishDelayMs); mTestLooper.dispatchAll(); assertThat(session.getStatus()).isEqualTo(Status.CANCELLED_BY_USER); verify(callback).onFinishing(); verify(callback).onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_CANCELED)); } @Test @EnableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS) public void vibrateInSession_afterFinish_vibrationIgnored() throws Exception { mockCapabilities(IVibratorManager.CAP_START_SESSIONS); mockVibrators(1, 2); FakeVibratorControllerProvider fakeVibrator1 = mVibratorProviders.get(1); fakeVibrator1.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL); VibratorManagerService service = createSystemReadyService(); int sessionFinishDelayMs = 200; IVibrationSessionCallback callback = mockSessionCallbacks(sessionFinishDelayMs); VendorVibrationSession session = startSession(service, RINGTONE_ATTRS, callback, 1, 2); mTestLooper.dispatchAll(); verify(mNativeWrapperMock).startSession(eq(session.getSessionId()), eq(new int[] {1, 2})); ArgumentCaptor<IVibrationSession> captor = ArgumentCaptor.forClass(IVibrationSession.class); verify(callback).onStarted(captor.capture()); IVibrationSession startedSession = captor.getValue(); startedSession.finishSession(); mTestLooper.dispatchAll(); startedSession.vibrate( CombinedVibration.createParallel(VibrationEffect.createOneShot(10, 255)), "reason"); // Session not ended until HAL callback. assertThat(session.getStatus()).isEqualTo(Status.RUNNING); // VibrationThread will never start this vibration. assertFalse(waitUntil(s -> !fakeVibrator1.getAmplitudes().isEmpty(), service, TEST_TIMEOUT_MILLIS)); // Dispatch HAL callbacks. mTestLooper.moveTimeForward(sessionFinishDelayMs); mTestLooper.dispatchAll(); assertThat(session.getStatus()).isEqualTo(Status.FINISHED); verify(callback).onFinishing(); verify(callback).onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_SUCCESS)); } @Test @EnableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS) public void vibrateInSession_repeatingVibration_vibrationIgnored() throws Exception { mockCapabilities(IVibratorManager.CAP_START_SESSIONS); mockVibrators(1, 2); FakeVibratorControllerProvider fakeVibrator1 = mVibratorProviders.get(1); fakeVibrator1.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL); VibratorManagerService service = createSystemReadyService(); int sessionFinishDelayMs = 200; IVibrationSessionCallback callback = mockSessionCallbacks(sessionFinishDelayMs); VendorVibrationSession session = startSession(service, RINGTONE_ATTRS, callback, 1, 2); mTestLooper.dispatchAll(); verify(mNativeWrapperMock).startSession(eq(session.getSessionId()), eq(new int[] {1, 2})); ArgumentCaptor<IVibrationSession> captor = ArgumentCaptor.forClass(IVibrationSession.class); verify(callback).onStarted(captor.capture()); IVibrationSession startedSession = captor.getValue(); startedSession.vibrate( CombinedVibration.createParallel( VibrationEffect.createWaveform(new long[]{ 10, 10, 10, 10}, 0)), "reason"); // VibrationThread will never start this vibration. assertFalse(waitUntil(s -> !fakeVibrator1.getAmplitudes().isEmpty(), service, TEST_TIMEOUT_MILLIS)); startedSession.finishSession(); mTestLooper.dispatchAll(); // Dispatch HAL callbacks. mTestLooper.moveTimeForward(sessionFinishDelayMs); mTestLooper.dispatchAll(); assertThat(session.getStatus()).isEqualTo(Status.FINISHED); assertThat(service.isVibrating(1)).isFalse(); assertThat(service.isVibrating(2)).isFalse(); verify(callback).onFinishing(); verify(callback).onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_SUCCESS)); } @Test @EnableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS) public void vibrateInSession_singleVibration_playsAllVibrateCommands() throws Exception { mockCapabilities(IVibratorManager.CAP_START_SESSIONS); mockVibrators(1, 2); FakeVibratorControllerProvider fakeVibrator1 = mVibratorProviders.get(1); fakeVibrator1.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL); FakeVibratorControllerProvider fakeVibrator2 = mVibratorProviders.get(1); fakeVibrator2.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL); VibratorManagerService service = createSystemReadyService(); int sessionFinishDelayMs = 200; IVibrationSessionCallback callback = mockSessionCallbacks(sessionFinishDelayMs); VendorVibrationSession session = startSession(service, RINGTONE_ATTRS, callback, 1, 2); mTestLooper.dispatchAll(); verify(mNativeWrapperMock).startSession(eq(session.getSessionId()), eq(new int[] {1, 2})); ArgumentCaptor<IVibrationSession> captor = ArgumentCaptor.forClass(IVibrationSession.class); verify(callback).onStarted(captor.capture()); IVibrationSession startedSession = captor.getValue(); startedSession.vibrate( CombinedVibration.createParallel( VibrationEffect.createWaveform(new long[]{ 10, 10, 10, 10}, -1)), "reason"); // VibrationThread will start this vibration async, so wait until vibration is triggered. // Vibrators will receive 2 requests for the waveform playback assertTrue(waitUntil(s -> fakeVibrator1.getAmplitudes().size() == 2, service, TEST_TIMEOUT_MILLIS)); assertTrue(waitUntil(s -> fakeVibrator2.getAmplitudes().size() == 2, service, TEST_TIMEOUT_MILLIS)); startedSession.finishSession(); mTestLooper.dispatchAll(); // Dispatch HAL callbacks. mTestLooper.moveTimeForward(sessionFinishDelayMs); mTestLooper.dispatchAll(); assertThat(session.getStatus()).isEqualTo(Status.FINISHED); assertThat(service.isVibrating(1)).isFalse(); assertThat(service.isVibrating(2)).isFalse(); verify(callback).onFinishing(); verify(callback).onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_SUCCESS)); } @Test @EnableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS) public void vibrateInSession_multipleVibrations_playsAllVibrations() throws Exception { mockCapabilities(IVibratorManager.CAP_START_SESSIONS); mockVibrators(1, 2); FakeVibratorControllerProvider fakeVibrator1 = mVibratorProviders.get(1); fakeVibrator1.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL); VibratorManagerService service = createSystemReadyService(); int sessionFinishDelayMs = 200; IVibrationSessionCallback callback = mockSessionCallbacks(sessionFinishDelayMs); VendorVibrationSession session = startSession(service, RINGTONE_ATTRS, callback, 1, 2); mTestLooper.dispatchAll(); verify(mNativeWrapperMock).startSession(eq(session.getSessionId()), eq(new int[] {1, 2})); ArgumentCaptor<IVibrationSession> captor = ArgumentCaptor.forClass(IVibrationSession.class); verify(callback).onStarted(captor.capture()); IVibrationSession startedSession = captor.getValue(); startedSession.vibrate( CombinedVibration.createParallel(VibrationEffect.createOneShot(10, 255)), "reason"); // VibrationThread will start this vibration async, so wait until vibration is completed. assertTrue(waitUntil(s -> fakeVibrator1.getAmplitudes().size() == 1, service, TEST_TIMEOUT_MILLIS)); assertTrue(waitUntil(s -> !session.getVibrations().isEmpty(), service, TEST_TIMEOUT_MILLIS)); startedSession.vibrate( CombinedVibration.createParallel(VibrationEffect.createOneShot(20, 255)), "reason"); assertTrue(waitUntil(s -> fakeVibrator1.getAmplitudes().size() == 2, service, TEST_TIMEOUT_MILLIS)); startedSession.finishSession(); mTestLooper.dispatchAll(); // Dispatch HAL callbacks. mTestLooper.moveTimeForward(sessionFinishDelayMs); mTestLooper.dispatchAll(); assertThat(session.getStatus()).isEqualTo(Status.FINISHED); assertThat(service.isVibrating(1)).isFalse(); assertThat(service.isVibrating(2)).isFalse(); verify(callback).onFinishing(); verify(callback).onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_SUCCESS)); } @Test @Test public void frameworkStats_externalVibration_reportsAllMetrics() throws Exception { public void frameworkStats_externalVibration_reportsAllMetrics() throws Exception { mockVibrators(1); mockVibrators(1); Loading