summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Treehugger Robot <android-test-infra-autosubmit@system.gserviceaccount.com> 2024-11-15 12:16:38 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2024-11-15 12:16:38 +0000
commit08091da75cf3ad077dc1923cdf1da75f1f59f596 (patch)
tree66525c0423f8f50aa18147a3068b8aba17f1c7d0
parent55fc3d50bef51fdb4850816ce30964ab71c81a94 (diff)
parent1cbf97ad6d6a80691b5939c0e3330a36111b2ee4 (diff)
Merge "Implement VendorVibrationSession.vibrate" into main
-rw-r--r--services/core/java/com/android/server/vibrator/VendorVibrationSession.java104
-rw-r--r--services/core/java/com/android/server/vibrator/VibratorManagerService.java248
-rw-r--r--services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java221
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);