diff options
| author | 2024-11-15 12:16:38 +0000 | |
|---|---|---|
| committer | 2024-11-15 12:16:38 +0000 | |
| commit | 08091da75cf3ad077dc1923cdf1da75f1f59f596 (patch) | |
| tree | 66525c0423f8f50aa18147a3068b8aba17f1c7d0 | |
| parent | 55fc3d50bef51fdb4850816ce30964ab71c81a94 (diff) | |
| parent | 1cbf97ad6d6a80691b5939c0e3330a36111b2ee4 (diff) | |
Merge "Implement VendorVibrationSession.vibrate" into main
3 files changed, 512 insertions, 61 deletions
diff --git a/services/core/java/com/android/server/vibrator/VendorVibrationSession.java b/services/core/java/com/android/server/vibrator/VendorVibrationSession.java index 07478e360d27..9e75cf2fc3f3 100644 --- a/services/core/java/com/android/server/vibrator/VendorVibrationSession.java +++ b/services/core/java/com/android/server/vibrator/VendorVibrationSession.java @@ -37,8 +37,11 @@ import android.util.Slog; import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; +import java.util.ArrayList; import java.util.Arrays; +import java.util.List; import java.util.Locale; import java.util.NoSuchElementException; @@ -60,6 +63,9 @@ final class VendorVibrationSession extends IVibrationSession.Stub * used for another vibration. */ 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(); @@ -71,7 +77,9 @@ final class VendorVibrationSession extends IVibrationSession.Stub private final IVibrationSessionCallback mCallback; private final CallerInfo mCallerInfo; private final VibratorManagerHooks mManagerHooks; + private final DeviceAdapter mDeviceAdapter; private final Handler mHandler; + private final List<DebugInfo> mVibrations = new ArrayList<>(); @GuardedBy("mLock") private Status mStatus = Status.RUNNING; @@ -83,24 +91,28 @@ final class VendorVibrationSession extends IVibrationSession.Stub private long mEndUptime; @GuardedBy("mLock") private long mEndTime; // for debugging + @GuardedBy("mLock") + private VibrationStepConductor mConductor; VendorVibrationSession(@NonNull CallerInfo callerInfo, @NonNull Handler handler, - @NonNull VibratorManagerHooks managerHooks, @NonNull int[] vibratorIds, + @NonNull VibratorManagerHooks managerHooks, @NonNull DeviceAdapter deviceAdapter, @NonNull IVibrationSessionCallback callback) { mCreateUptime = SystemClock.uptimeMillis(); mCreateTime = System.currentTimeMillis(); - mVibratorIds = vibratorIds; + mVibratorIds = deviceAdapter.getAvailableVibratorIds(); mHandler = handler; mCallback = callback; mCallerInfo = callerInfo; mManagerHooks = managerHooks; + mDeviceAdapter = deviceAdapter; CancellationSignal.fromTransport(mCancellationSignal).setOnCancelListener(this); } @Override public void vibrate(CombinedVibration vibration, String reason) { - // TODO(b/345414356): implement vibration support - throw new UnsupportedOperationException("Vendor session vibrations not yet implemented"); + CallerInfo vibrationCallerInfo = new CallerInfo(mCallerInfo.attrs, mCallerInfo.uid, + mCallerInfo.deviceId, mCallerInfo.opPkg, reason); + mManagerHooks.vibrate(mSessionId, vibrationCallerInfo, vibration); } @Override @@ -146,7 +158,7 @@ final class VendorVibrationSession extends IVibrationSession.Stub public DebugInfo getDebugInfo() { synchronized (mLock) { return new DebugInfoImpl(mStatus, mCallerInfo, mCreateUptime, mCreateTime, mStartTime, - mEndUptime, mEndTime); + mEndUptime, mEndTime, mVibrations); } } @@ -200,12 +212,12 @@ final class VendorVibrationSession extends IVibrationSession.Stub @Override 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 public void notifySyncedVibratorsCallback(long vibrationId) { - // TODO(b/345414356): implement vibration support + // Ignore it, the session vibration playback doesn't depend on HAL timings } @Override @@ -214,8 +226,9 @@ final class VendorVibrationSession extends IVibrationSession.Stub // If end was not requested then the HAL has cancelled the session. maybeSetEndRequestLocked(Status.CANCELLED_BY_UNKNOWN_REASON); maybeSetStatusToRequestedLocked(); + clearVibrationConductor(); } - mManagerHooks.onSessionReleased(mSessionId); + mHandler.post(() -> mManagerHooks.onSessionReleased(mSessionId)); } @Override @@ -228,7 +241,8 @@ final class VendorVibrationSession extends IVibrationSession.Stub /* includeDate= */ true)) + ", status: " + mStatus.name().toLowerCase(Locale.ROOT) + ", callerInfo: " + mCallerInfo - + ", vibratorIds: " + Arrays.toString(mVibratorIds); + + ", vibratorIds: " + Arrays.toString(mVibratorIds) + + ", vibrations: " + mVibrations; } } @@ -254,6 +268,13 @@ final class VendorVibrationSession extends IVibrationSession.Stub return mVibratorIds; } + @VisibleForTesting + public List<DebugInfo> getVibrations() { + synchronized (mLock) { + return new ArrayList<>(mVibrations); + } + } + public ICancellationSignal getCancellationSignal() { return mCancellationSignal; } @@ -278,7 +299,39 @@ final class VendorVibrationSession extends IVibrationSession.Stub } if (isAlreadyEnded) { // 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; } } @@ -296,7 +349,7 @@ final class VendorVibrationSession extends IVibrationSession.Stub } } if (shouldTriggerSessionHook) { - mManagerHooks.endSession(mSessionId, shouldAbort); + mHandler.post(() -> mManagerHooks.endSession(mSessionId, shouldAbort)); } } @@ -309,6 +362,11 @@ final class VendorVibrationSession extends IVibrationSession.Stub mEndStatusRequest = status; mEndTime = System.currentTimeMillis(); 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()) { // Only trigger "finishing" callback if session started. // Run client callback in separate thread. @@ -377,6 +435,7 @@ final class VendorVibrationSession extends IVibrationSession.Stub static final class DebugInfoImpl implements VibrationSession.DebugInfo { private final Status mStatus; private final CallerInfo mCallerInfo; + private final List<DebugInfo> mVibrations; private final long mCreateUptime; private final long mCreateTime; @@ -385,7 +444,7 @@ final class VendorVibrationSession extends IVibrationSession.Stub private final long mDurationMs; 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; mCallerInfo = callerInfo; mCreateUptime = createUptime; @@ -393,6 +452,7 @@ final class VendorVibrationSession extends IVibrationSession.Stub mStartTime = startTime; mEndTime = endTime; mDurationMs = endUptime > 0 ? endUptime - createUptime : -1; + mVibrations = vibrations == null ? new ArrayList<>() : new ArrayList<>(vibrations); } @Override @@ -418,6 +478,9 @@ final class VendorVibrationSession extends IVibrationSession.Stub @Override public void logMetrics(VibratorFrameworkStatsLogger statsLogger) { + for (DebugInfo vibration : mVibrations) { + vibration.logMetrics(statsLogger); + } } @Override @@ -448,6 +511,14 @@ final class VendorVibrationSession extends IVibrationSession.Stub pw.println("endTime = " + (mEndTime == 0 ? null : formatTime(mEndTime, /*includeDate=*/ true))); pw.println("callerInfo = " + mCallerInfo); + + pw.println("vibrations:"); + pw.increaseIndent(); + for (DebugInfo vibration : mVibrations) { + vibration.dump(pw); + } + pw.decreaseIndent(); + pw.decreaseIndent(); } @@ -477,6 +548,12 @@ final class VendorVibrationSession extends IVibrationSession.Stub " | %s (uid=%d, deviceId=%d) | reason: %s", mCallerInfo.opPkg, mCallerInfo.uid, mCallerInfo.deviceId, mCallerInfo.reason); pw.println(timingsStr + paramStr + audioUsageStr + callerStr); + + pw.increaseIndent(); + for (DebugInfo vibration : mVibrations) { + vibration.dumpCompact(pw); + } + pw.decreaseIndent(); } @Override @@ -487,7 +564,8 @@ final class VendorVibrationSession extends IVibrationSession.Stub /* includeDate= */ true)) + ", durationMs: " + mDurationMs + ", status: " + mStatus.name().toLowerCase(Locale.ROOT) - + ", callerInfo: " + mCallerInfo; + + ", callerInfo: " + mCallerInfo + + ", vibrations: " + mVibrations; } } } diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java index 1030df692543..cc163db4dc36 100644 --- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java +++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java @@ -106,7 +106,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { private static final String EXTERNAL_VIBRATOR_SERVICE = "external_vibrator_service"; private static final String VIBRATOR_CONTROL_SERVICE = "android.frameworks.vibrator.IVibratorControlService/default"; - private static final boolean DEBUG = true; + private static final boolean DEBUG = false; private static final VibrationAttributes DEFAULT_ATTRIBUTES = new VibrationAttributes.Builder().build(); private static final int ATTRIBUTES_ALL_BYPASS_FLAGS = @@ -610,6 +610,11 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { logAndRecordVibrationAttempt(effect, callerInfo, Status.IGNORED_ERROR_TOKEN); return null; } + enforceUpdateAppOpsStatsPermission(uid); + if (!isEffectValid(effect)) { + logAndRecordVibrationAttempt(effect, callerInfo, Status.IGNORED_UNSUPPORTED); + return null; + } if (effect.hasVendorEffects()) { if (!Flags.vendorVibrationEffects()) { Slog.e(TAG, "vibrate; vendor effects feature disabled"); @@ -622,11 +627,6 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { return null; } } - enforceUpdateAppOpsStatsPermission(uid); - if (!isEffectValid(effect)) { - logAndRecordVibrationAttempt(effect, callerInfo, Status.IGNORED_UNSUPPORTED); - return null; - } // Create Vibration.Stats as close to the received request as possible, for tracking. SingleVibrationSession session = new SingleVibrationSession(token, callerInfo, effect); HalVibration vib = session.getVibration(); @@ -658,6 +658,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { // If not ignored so far then try to start this vibration. if (ignoreStatus == null) { + // TODO(b/378492007): Investigate if we can move this around AppOpsManager calls final long ident = Binder.clearCallingIdentity(); try { if (mCurrentSession != null) { @@ -703,6 +704,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { if (DEBUG) { Slog.d(TAG, "Canceling vibration"); } + // TODO(b/378492007): Investigate if we can move this around AppOpsManager calls final long ident = Binder.clearCallingIdentity(); try { // TODO(b/370948466): investigate why token not checked on external vibrations. @@ -762,8 +764,18 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { vibratorIds = new int[0]; } enforceUpdateAppOpsStatsPermission(uid); + + // Create session with adapter that only uses the session vibrators. + SparseArray<VibratorController> sessionVibrators = new SparseArray<>(vibratorIds.length); + for (int vibratorId : vibratorIds) { + VibratorController controller = mVibrators.get(vibratorId); + if (controller != null) { + sessionVibrators.put(vibratorId, controller); + } + } + DeviceAdapter deviceAdapter = new DeviceAdapter(mVibrationSettings, sessionVibrators); VendorVibrationSession session = new VendorVibrationSession(callerInfo, mHandler, - mVendorVibrationSessionCallbacks, vibratorIds, callback); + mVendorVibrationSessionCallbacks, deviceAdapter, callback); if (attrs.isFlagSet(VibrationAttributes.FLAG_INVALIDATE_SETTINGS_CACHE)) { // Force update of user settings before checking if this vibration effect should @@ -787,12 +799,15 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { ignoreStatus = Status.IGNORED_UNSUPPORTED; } - // Check if any vibrator ID was requested. - if (ignoreStatus == null && vibratorIds.length == 0) { - if (DEBUG) { - Slog.d(TAG, "Empty vibrator ids to start session, ignoring request"); + // Check if vibrator IDs requested are available. + if (ignoreStatus == null) { + if (vibratorIds.length == 0 + || vibratorIds.length != deviceAdapter.getAvailableVibratorIds().length) { + Slog.e(TAG, "Bad vibrator ids to start session, ignoring request." + + " requested=" + Arrays.toString(vibratorIds) + + " available=" + Arrays.toString(mVibratorIds)); + ignoreStatus = Status.IGNORED_UNSUPPORTED; } - ignoreStatus = Status.IGNORED_UNSUPPORTED; } // Check if user settings or DnD is set to ignore this session. @@ -810,6 +825,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { } if (ignoreStatus == null) { + // TODO(b/378492007): Investigate if we can move this around AppOpsManager calls final long ident = Binder.clearCallingIdentity(); try { // If not ignored so far then stop ongoing sessions before starting this one. @@ -839,22 +855,40 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { private Status startVendorSessionLocked(VendorVibrationSession session) { Trace.traceBegin(TRACE_TAG_VIBRATOR, "startSessionLocked"); try { + long sessionId = session.getSessionId(); + if (DEBUG) { + Slog.d(TAG, "Starting session " + sessionId + " in HAL"); + } if (session.isEnded()) { // Session already ended, possibly cancelled by app cancellation signal. return session.getStatus(); } - if (!session.linkToDeath()) { - return Status.IGNORED_ERROR_TOKEN; - } - if (!mNativeWrapper.startSession(session.getSessionId(), session.getVibratorIds())) { - Slog.e(TAG, "Error starting session " + session.getSessionId() - + " on vibrators " + Arrays.toString(session.getVibratorIds())); - session.unlinkToDeath(); - return Status.IGNORED_UNSUPPORTED; + int mode = startAppOpModeLocked(session.getCallerInfo()); + switch (mode) { + case AppOpsManager.MODE_ALLOWED: + Trace.asyncTraceBegin(TRACE_TAG_VIBRATOR, "vibration", 0); + // Make sure mCurrentVibration is set while triggering the HAL. + mCurrentSession = session; + if (!session.linkToDeath()) { + mCurrentSession = null; + return Status.IGNORED_ERROR_TOKEN; + } + if (!mNativeWrapper.startSession(sessionId, session.getVibratorIds())) { + Slog.e(TAG, "Error starting session " + sessionId + " on vibrators " + + Arrays.toString(session.getVibratorIds())); + session.unlinkToDeath(); + mCurrentSession = null; + return Status.IGNORED_UNSUPPORTED; + } + session.notifyStart(); + return null; + case AppOpsManager.MODE_ERRORED: + Slog.w(TAG, "Start AppOpsManager operation errored for uid " + + session.getCallerInfo().uid); + return Status.IGNORED_ERROR_APP_OPS; + default: + return Status.IGNORED_APP_OPS; } - session.notifyStart(); - mCurrentSession = session; - return null; } finally { Trace.traceEnd(TRACE_TAG_VIBRATOR); } @@ -1045,6 +1079,9 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { @GuardedBy("mLock") @Nullable private Status startVibrationOnThreadLocked(SingleVibrationSession session) { + if (DEBUG) { + Slog.d(TAG, "Starting vibration " + session.getVibration().id + " on thread"); + } VibrationStepConductor conductor = createVibrationStepConductor(session.getVibration()); session.setVibrationConductor(conductor); int mode = startAppOpModeLocked(session.getCallerInfo()); @@ -1080,12 +1117,18 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { mNextSession = null; Status errorStatus = startVibrationOnThreadLocked(session); if (errorStatus != null) { + if (DEBUG) { + Slog.d(TAG, "Error starting next vibration " + session.getVibration().id); + } endSessionLocked(session, errorStatus); } } else if (mNextSession instanceof VendorVibrationSession session) { mNextSession = null; Status errorStatus = startVendorSessionLocked(session); if (errorStatus != null) { + if (DEBUG) { + Slog.d(TAG, "Error starting next session " + session.getSessionId()); + } endSessionLocked(session, errorStatus); } } // External vibrations cannot be started asynchronously. @@ -1103,6 +1146,16 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { } private VibrationStepConductor createVibrationStepConductor(HalVibration vib) { + return createVibrationStepConductor(vib, mDeviceAdapter, /* isInSession= */ false); + } + + private VibrationStepConductor createSessionVibrationStepConductor(HalVibration vib, + DeviceAdapter deviceAdapter) { + return createVibrationStepConductor(vib, deviceAdapter, /* isInSession= */ true); + } + + private VibrationStepConductor createVibrationStepConductor(HalVibration vib, + DeviceAdapter deviceAdapter, boolean isInSession) { CompletableFuture<Void> requestVibrationParamsFuture = null; if (Flags.adaptiveHapticsEnabled() @@ -1114,8 +1167,8 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { mVibrationSettings.getRequestVibrationParamsTimeoutMs()); } - return new VibrationStepConductor(vib, /* isInSession= */ false, mVibrationSettings, - mDeviceAdapter, mVibrationScaler, mFrameworkStatsLogger, + return new VibrationStepConductor(vib, isInSession, mVibrationSettings, + deviceAdapter, mVibrationScaler, mFrameworkStatsLogger, requestVibrationParamsFuture, mVibrationThreadCallbacks); } @@ -1136,18 +1189,15 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { private void logAndRecordVibrationAttempt(@Nullable CombinedVibration effect, CallerInfo callerInfo, Status status) { - logAndRecordVibration( - new Vibration.DebugInfoImpl(status, callerInfo, - VibrationStats.StatsInfo.findVibrationType(effect), new VibrationStats(), - effect, /* originalEffect= */ null, VibrationScaler.SCALE_NONE, - VibrationScaler.ADAPTIVE_SCALE_NONE)); + logAndRecordVibration(createVibrationAttemptDebugInfo(effect, callerInfo, status)); } private void logAndRecordSessionAttempt(CallerInfo callerInfo, Status status) { logAndRecordVibration( new VendorVibrationSession.DebugInfoImpl(status, callerInfo, SystemClock.uptimeMillis(), System.currentTimeMillis(), - /* startTime= */ 0, /* endUptime= */ 0, /* endTime= */ 0)); + /* startTime= */ 0, /* endUptime= */ 0, /* endTime= */ 0, + /* vibrations= */ null)); } private void logAndRecordVibration(DebugInfo info) { @@ -1156,6 +1206,14 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { mVibratorManagerRecords.record(info); } + private DebugInfo createVibrationAttemptDebugInfo(@Nullable CombinedVibration effect, + CallerInfo callerInfo, Status status) { + return new Vibration.DebugInfoImpl(status, callerInfo, + VibrationStats.StatsInfo.findVibrationType(effect), new VibrationStats(), + effect, /* originalEffect= */ null, VibrationScaler.SCALE_NONE, + VibrationScaler.ADAPTIVE_SCALE_NONE); + } + private void logVibrationStatus(int uid, VibrationAttributes attrs, Status status) { switch (status) { case IGNORED_BACKGROUND: @@ -1766,25 +1824,35 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { Trace.traceBegin(TRACE_TAG_VIBRATOR, "onVibrationThreadReleased"); try { synchronized (mLock) { - if (!(mCurrentSession instanceof SingleVibrationSession session)) { + if (mCurrentSession instanceof SingleVibrationSession session) { + if (Build.IS_DEBUGGABLE && (session.getVibration().id != vibrationId)) { + Slog.wtf(TAG, TextUtils.formatSimple( + "VibrationId mismatch on vibration thread release." + + " expected=%d, released=%d", + session.getVibration().id, vibrationId)); + } + finishAppOpModeLocked(mCurrentSession.getCallerInfo()); + clearCurrentSessionLocked(); + Trace.asyncTraceEnd(Trace.TRACE_TAG_VIBRATOR, "vibration", 0); + // Start next vibration if it's waiting for the thread. + maybeStartNextSessionLocked(); + } else if (mCurrentSession instanceof VendorVibrationSession session) { + VibrationStepConductor conductor = session.clearVibrationConductor(); if (Build.IS_DEBUGGABLE) { - Slog.wtf(TAG, "VibrationSession invalid on vibration thread release." - + " currentSession=" + mCurrentSession); + if (conductor == null) { + Slog.wtf(TAG, "Vendor session without ongoing vibration on" + + " thread release. currentSession=" + mCurrentSession); + } else if (conductor.getVibration().id != vibrationId) { + Slog.wtf(TAG, TextUtils.formatSimple( + "VibrationId mismatch on vibration thread release." + + " expected=%d, released=%d", + conductor.getVibration().id, vibrationId)); + } } - // Only single vibration sessions are ended by thread being released. Abort. - return; + } else if (Build.IS_DEBUGGABLE) { + Slog.wtf(TAG, "VibrationSession invalid on vibration thread release." + + " currentSession=" + mCurrentSession); } - if (Build.IS_DEBUGGABLE && (session.getVibration().id != vibrationId)) { - Slog.wtf(TAG, TextUtils.formatSimple( - "VibrationId mismatch on vibration thread release." - + " expected=%d, released=%d", - session.getVibration().id, vibrationId)); - } - finishAppOpModeLocked(mCurrentSession.getCallerInfo()); - clearCurrentSessionLocked(); - Trace.asyncTraceEnd(Trace.TRACE_TAG_VIBRATOR, "vibration", 0); - // Start next vibration if it's waiting for the thread. - maybeStartNextSessionLocked(); } } finally { Trace.traceEnd(TRACE_TAG_VIBRATOR); @@ -1839,6 +1907,86 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { implements VendorVibrationSession.VibratorManagerHooks { @Override + public void vibrate(long sessionId, CallerInfo callerInfo, CombinedVibration effect) { + if (DEBUG) { + Slog.d(TAG, "Vibration session " + sessionId + " vibration requested"); + } + Trace.traceBegin(TRACE_TAG_VIBRATOR, "sessionVibrate"); + try { + synchronized (mLock) { + if (!(mCurrentSession instanceof VendorVibrationSession session)) { + if (Build.IS_DEBUGGABLE) { + Slog.wtf(TAG, "VibrationSession invalid on session vibrate." + + " currentSession=" + mCurrentSession); + } + // Only vendor vibration sessions can handle this call. Abort. + return; + } + if (session.getSessionId() != sessionId) { + if (Build.IS_DEBUGGABLE) { + Slog.wtf(TAG, TextUtils.formatSimple( + "SessionId mismatch on vendor vibration session vibrate." + + " expected=%d, released=%d", + session.getSessionId(), sessionId)); + } + // Only the ongoing vendor vibration sessions can handle this call. Abort. + return; + } + if (session.wasEndRequested()) { + if (DEBUG) { + Slog.d(TAG, "session vibrate; session is ending, vibration ignored"); + } + session.notifyVibrationAttempt(createVibrationAttemptDebugInfo(effect, + callerInfo, Status.IGNORED_ERROR_SCHEDULING)); + return; + } + if (!isEffectValid(effect)) { + session.notifyVibrationAttempt(createVibrationAttemptDebugInfo(effect, + callerInfo, Status.IGNORED_UNSUPPORTED)); + return; + } + if (effect.getDuration() == Long.MAX_VALUE) { + // Repeating effects cannot be played by the service in a session. + session.notifyVibrationAttempt(createVibrationAttemptDebugInfo(effect, + callerInfo, Status.IGNORED_UNSUPPORTED)); + return; + } + // Create Vibration.Stats as close to the request as possible, for tracking. + HalVibration vib = new HalVibration(callerInfo, effect); + vib.fillFallbacks(mVibrationSettings::getFallbackEffect); + + if (callerInfo.attrs.isFlagSet( + VibrationAttributes.FLAG_INVALIDATE_SETTINGS_CACHE)) { + // Force update of user settings before checking if this vibration effect + // should be ignored or scaled. + mVibrationSettings.update(); + } + + if (DEBUG) { + Slog.d(TAG, "Starting vibrate for vibration " + vib.id + + " in session " + sessionId); + } + + VibrationStepConductor conductor = + createSessionVibrationStepConductor(vib, session.getDeviceAdapter()); + if (session.maybeSetVibrationConductor(conductor)) { + if (!mVibrationThread.runVibrationOnVibrationThread(conductor)) { + // Shouldn't happen. The method call already logs. + vib.end(new Vibration.EndInfo(Status.IGNORED_ERROR_SCHEDULING)); + session.clearVibrationConductor(); // Rejected by thread, clear it. + } + } else { + // Cannot set vibration in session, log failed attempt. + session.notifyVibrationAttempt(createVibrationAttemptDebugInfo(effect, + callerInfo, Status.IGNORED_ERROR_SCHEDULING)); + } + } + } finally { + Trace.traceEnd(TRACE_TAG_VIBRATOR); + } + } + + @Override public void endSession(long sessionId, boolean shouldAbort) { if (DEBUG) { Slog.d(TAG, "Vibration session " + sessionId @@ -1874,6 +2022,12 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { + " expected=%d, released=%d", session.getSessionId(), sessionId)); } + // Make sure all controllers in session are reset after session ended. + // This will update the vibrator state to isVibrating = false for listeners. + for (int vibratorId : session.getVibratorIds()) { + mVibrators.get(vibratorId).off(); + } + finishAppOpModeLocked(mCurrentSession.getCallerInfo()); clearCurrentSessionLocked(); // Start next vibration if it's waiting for the HAL session to be over. maybeStartNextSessionLocked(); diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java index 5f76d6815cb8..ec83e990a70d 100644 --- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java +++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java @@ -2865,7 +2865,7 @@ public class VibratorManagerServiceTest { mTestLooper.dispatchAll(); 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()).onFinishing(); verify(callback) @@ -2889,6 +2889,7 @@ public class VibratorManagerServiceTest { verify(callback).onStarted(captor.capture()); captor.getValue().finishSession(); + mTestLooper.dispatchAll(); // Session not ended until HAL callback. assertThat(session.getStatus()).isEqualTo(Status.RUNNING); @@ -3139,6 +3140,224 @@ public class VibratorManagerServiceTest { } @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 public void frameworkStats_externalVibration_reportsAllMetrics() throws Exception { mockVibrators(1); mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL); |