diff options
5 files changed, 190 insertions, 12 deletions
diff --git a/services/core/java/com/android/server/vibrator/VendorVibrationSession.java b/services/core/java/com/android/server/vibrator/VendorVibrationSession.java index 9e75cf2fc3f3..15c3099511f9 100644 --- a/services/core/java/com/android/server/vibrator/VendorVibrationSession.java +++ b/services/core/java/com/android/server/vibrator/VendorVibrationSession.java @@ -86,6 +86,8 @@ final class VendorVibrationSession extends IVibrationSession.Stub @GuardedBy("mLock") private Status mEndStatusRequest; @GuardedBy("mLock") + private boolean mEndedByVendor; + @GuardedBy("mLock") private long mStartTime; // for debugging @GuardedBy("mLock") private long mEndUptime; @@ -119,14 +121,15 @@ final class VendorVibrationSession extends IVibrationSession.Stub public void finishSession() { // Do not abort session in HAL, wait for ongoing vibration requests to complete. // This might take a while to end the session, but it can be aborted by cancelSession. - requestEndSession(Status.FINISHED, /* shouldAbort= */ false); + requestEndSession(Status.FINISHED, /* shouldAbort= */ false, /* isVendorRequest= */ true); } @Override public void cancelSession() { // Always abort session in HAL while cancelling it. // This might be triggered after finishSession was already called. - requestEndSession(Status.CANCELLED_BY_USER, /* shouldAbort= */ true); + requestEndSession(Status.CANCELLED_BY_USER, /* shouldAbort= */ true, + /* isVendorRequest= */ true); } @Override @@ -158,7 +161,7 @@ final class VendorVibrationSession extends IVibrationSession.Stub public DebugInfo getDebugInfo() { synchronized (mLock) { return new DebugInfoImpl(mStatus, mCallerInfo, mCreateUptime, mCreateTime, mStartTime, - mEndUptime, mEndTime, mVibrations); + mEndUptime, mEndTime, mEndedByVendor, mVibrations); } } @@ -172,13 +175,15 @@ final class VendorVibrationSession extends IVibrationSession.Stub @Override public void onCancel() { Slog.d(TAG, "Cancellation signal received, cancelling vibration session..."); - requestEnd(Status.CANCELLED_BY_USER, /* endedBy= */ null, /* immediate= */ false); + requestEndSession(Status.CANCELLED_BY_USER, /* shouldAbort= */ true, + /* isVendorRequest= */ true); } @Override public void binderDied() { Slog.d(TAG, "Binder died, cancelling vibration session..."); - requestEnd(Status.CANCELLED_BINDER_DIED, /* endedBy= */ null, /* immediate= */ false); + requestEndSession(Status.CANCELLED_BINDER_DIED, /* shouldAbort= */ true, + /* isVendorRequest= */ false); } @Override @@ -207,7 +212,7 @@ final class VendorVibrationSession extends IVibrationSession.Stub // All requests to end a session should abort it to stop ongoing vibrations, even if // immediate flag is false. Only the #finishSession API will not abort and wait for // session vibrations to complete, which might take a long time. - requestEndSession(status, /* shouldAbort= */ true); + requestEndSession(status, /* shouldAbort= */ true, /* isVendorRequest= */ false); } @Override @@ -224,7 +229,8 @@ final class VendorVibrationSession extends IVibrationSession.Stub public void notifySessionCallback() { synchronized (mLock) { // If end was not requested then the HAL has cancelled the session. - maybeSetEndRequestLocked(Status.CANCELLED_BY_UNKNOWN_REASON); + maybeSetEndRequestLocked(Status.CANCELLED_BY_UNKNOWN_REASON, + /* isVendorRequest= */ false); maybeSetStatusToRequestedLocked(); clearVibrationConductor(); } @@ -335,10 +341,10 @@ final class VendorVibrationSession extends IVibrationSession.Stub } } - private void requestEndSession(Status status, boolean shouldAbort) { + private void requestEndSession(Status status, boolean shouldAbort, boolean isVendorRequest) { boolean shouldTriggerSessionHook = false; synchronized (mLock) { - maybeSetEndRequestLocked(status); + maybeSetEndRequestLocked(status, isVendorRequest); if (isStarted()) { // Always trigger session hook after it has started, in case new request aborts an // already finishing session. Wait for HAL callback before actually ending here. @@ -354,12 +360,13 @@ final class VendorVibrationSession extends IVibrationSession.Stub } @GuardedBy("mLock") - private void maybeSetEndRequestLocked(Status status) { + private void maybeSetEndRequestLocked(Status status, boolean isVendorRequest) { if (mEndStatusRequest != null) { // End already requested, keep first requested status and time. return; } mEndStatusRequest = status; + mEndedByVendor = isVendorRequest; mEndTime = System.currentTimeMillis(); mEndUptime = SystemClock.uptimeMillis(); if (mConductor != null) { @@ -442,15 +449,18 @@ final class VendorVibrationSession extends IVibrationSession.Stub private final long mStartTime; private final long mEndTime; private final long mDurationMs; + private final boolean mEndedByVendor; DebugInfoImpl(Status status, CallerInfo callerInfo, long createUptime, long createTime, - long startTime, long endUptime, long endTime, List<DebugInfo> vibrations) { + long startTime, long endUptime, long endTime, boolean endedByVendor, + List<DebugInfo> vibrations) { mStatus = status; mCallerInfo = callerInfo; mCreateUptime = createUptime; mCreateTime = createTime; mStartTime = startTime; mEndTime = endTime; + mEndedByVendor = endedByVendor; mDurationMs = endUptime > 0 ? endUptime - createUptime : -1; mVibrations = vibrations == null ? new ArrayList<>() : new ArrayList<>(vibrations); } @@ -478,6 +488,15 @@ final class VendorVibrationSession extends IVibrationSession.Stub @Override public void logMetrics(VibratorFrameworkStatsLogger statsLogger) { + if (mStartTime > 0) { + // Only log sessions that have started. + statsLogger.logVibrationVendorSessionStarted(mCallerInfo.uid); + statsLogger.logVibrationVendorSessionVibrations(mCallerInfo.uid, + mVibrations.size()); + if (!mEndedByVendor) { + statsLogger.logVibrationVendorSessionInterrupted(mCallerInfo.uid); + } + } for (DebugInfo vibration : mVibrations) { vibration.logMetrics(statsLogger); } diff --git a/services/core/java/com/android/server/vibrator/Vibration.java b/services/core/java/com/android/server/vibrator/Vibration.java index 27f92b2080e6..2bf44981e6d5 100644 --- a/services/core/java/com/android/server/vibrator/Vibration.java +++ b/services/core/java/com/android/server/vibrator/Vibration.java @@ -25,6 +25,7 @@ import android.os.CombinedVibration; import android.os.IBinder; import android.os.VibrationAttributes; import android.os.VibrationEffect; +import android.os.vibrator.Flags; import android.os.vibrator.PrebakedSegment; import android.os.vibrator.PrimitiveSegment; import android.os.vibrator.RampSegment; @@ -211,6 +212,11 @@ abstract class Vibration { public void logMetrics(VibratorFrameworkStatsLogger statsLogger) { statsLogger.logVibrationAdaptiveHapticScale(mCallerInfo.uid, mAdaptiveScale); statsLogger.writeVibrationReportedAsync(mStatsInfo); + if (Flags.vendorVibrationEffects()) { + // Log effect as it was originally requested. + statsLogger.logVibrationCountAndSizeIfVendorEffect(mCallerInfo.uid, + mOriginalEffect != null ? mOriginalEffect : mPlayedEffect); + } } @Override diff --git a/services/core/java/com/android/server/vibrator/VibratorFrameworkStatsLogger.java b/services/core/java/com/android/server/vibrator/VibratorFrameworkStatsLogger.java index e9c38940601c..08da43d8f0e0 100644 --- a/services/core/java/com/android/server/vibrator/VibratorFrameworkStatsLogger.java +++ b/services/core/java/com/android/server/vibrator/VibratorFrameworkStatsLogger.java @@ -16,8 +16,12 @@ package com.android.server.vibrator; +import android.annotation.Nullable; +import android.os.CombinedVibration; import android.os.Handler; +import android.os.Parcel; import android.os.SystemClock; +import android.os.VibrationEffect; import android.util.Slog; import android.view.HapticFeedbackConstants; @@ -58,6 +62,16 @@ public class VibratorFrameworkStatsLogger { "vibrator.value_vibration_adaptive_haptic_scale", new Histogram.UniformOptions(20, 0, 2)); + // Sizes in [1KB, ~4.5MB) defined by scaled buckets. + private static final Histogram sVibrationVendorEffectSizeHistogram = new Histogram( + "vibrator.value_vibration_vendor_effect_size", + new Histogram.ScaledRangeOptions(25, 0, 1, 1.4f)); + + // Session vibration count in [0, ~840) defined by scaled buckets. + private static final Histogram sVibrationVendorSessionVibrationsHistogram = new Histogram( + "vibrator.value_vibration_vendor_session_vibrations", + new Histogram.ScaledRangeOptions(20, 0, 1, 1.4f)); + private final Object mLock = new Object(); private final Handler mHandler; private final long mVibrationReportedLogIntervalMillis; @@ -201,4 +215,88 @@ public class VibratorFrameworkStatsLogger { Counter.logIncrementWithUid("vibrator.value_perform_haptic_feedback_keyboard", uid); } } + + /** Logs when a vendor vibration session successfully started. */ + public void logVibrationVendorSessionStarted(int uid) { + Counter.logIncrementWithUid("vibrator.value_vibration_vendor_session_started", uid); + } + + /** + * Logs when a vendor vibration session is interrupted by the platform. + * + * <p>A vendor session is interrupted if it has successfully started and its end was not + * requested by the vendor. This could be the vibrator service interrupting an ongoing session, + * the vibrator HAL triggering the session completed callback early. + */ + public void logVibrationVendorSessionInterrupted(int uid) { + Counter.logIncrementWithUid("vibrator.value_vibration_vendor_session_interrupted", uid); + } + + /** Logs the number of vibrations requested for a single vendor vibration session. */ + public void logVibrationVendorSessionVibrations(int uid, int vibrationCount) { + sVibrationVendorSessionVibrationsHistogram.logSampleWithUid(uid, vibrationCount); + } + + /** + * Logs if given vibration contains at least one {@link VibrationEffect.VendorEffect}. + * + * <p>Each {@link VibrationEffect.VendorEffect} will also log the parcel data size for the + * {@link VibrationEffect.VendorEffect#getVendorData()} it holds. + */ + public void logVibrationCountAndSizeIfVendorEffect(int uid, + @Nullable CombinedVibration vibration) { + if (vibration == null) { + return; + } + boolean hasVendorEffects = logVibrationSizeOfVendorEffects(uid, vibration); + if (hasVendorEffects) { + // Increment CombinedVibration with one or more vendor effects only once. + Counter.logIncrementWithUid("vibrator.value_vibration_vendor_effect_requests", uid); + } + } + + private static boolean logVibrationSizeOfVendorEffects(int uid, CombinedVibration vibration) { + if (vibration instanceof CombinedVibration.Mono mono) { + if (mono.getEffect() instanceof VibrationEffect.VendorEffect effect) { + logVibrationVendorEffectSize(uid, effect); + return true; + } + return false; + } + if (vibration instanceof CombinedVibration.Stereo stereo) { + boolean hasVendorEffects = false; + for (int i = 0; i < stereo.getEffects().size(); i++) { + if (stereo.getEffects().valueAt(i) instanceof VibrationEffect.VendorEffect effect) { + logVibrationVendorEffectSize(uid, effect); + hasVendorEffects = true; + } + } + return hasVendorEffects; + } + if (vibration instanceof CombinedVibration.Sequential sequential) { + boolean hasVendorEffects = false; + for (int i = 0; i < sequential.getEffects().size(); i++) { + hasVendorEffects |= logVibrationSizeOfVendorEffects(uid, + sequential.getEffects().get(i)); + } + return hasVendorEffects; + } + // Unknown combined vibration, skip metrics. + return false; + } + + private static void logVibrationVendorEffectSize(int uid, VibrationEffect.VendorEffect effect) { + int dataSize; + Parcel vendorData = Parcel.obtain(); + try { + // Measure data size as it'll be sent to the HAL via binder, not the serialization size. + // PersistableBundle creates an XML representation for the data in writeToStream, so it + // might be larger than the actual data that is transferred between processes. + effect.getVendorData().writeToParcel(vendorData, /* flags= */ 0); + dataSize = vendorData.dataSize(); + } finally { + vendorData.recycle(); + } + sVibrationVendorEffectSizeHistogram.logSampleWithUid(uid, dataSize); + } } diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java index cc163db4dc36..ae726c15ed79 100644 --- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java +++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java @@ -1197,7 +1197,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { new VendorVibrationSession.DebugInfoImpl(status, callerInfo, SystemClock.uptimeMillis(), System.currentTimeMillis(), /* startTime= */ 0, /* endUptime= */ 0, /* endTime= */ 0, - /* vibrations= */ null)); + /* endedByVendor= */ false, /* vibrations= */ null)); } private void logAndRecordVibration(DebugInfo info) { 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 ec83e990a70d..15ad5884aa18 100644 --- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java +++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java @@ -2783,6 +2783,12 @@ public class VibratorManagerServiceTest { mTestLooper.dispatchAll(); verify(mNativeWrapperMock, never()).startSession(anyLong(), any(int[].class)); + verify(mVibratorFrameworkStatsLoggerMock, never()) + .logVibrationVendorSessionStarted(anyInt()); + verify(mVibratorFrameworkStatsLoggerMock, never()) + .logVibrationVendorSessionVibrations(anyInt(), anyInt()); + verify(mVibratorFrameworkStatsLoggerMock, never()) + .logVibrationVendorSessionInterrupted(anyInt()); verify(callback, never()).onStarted(any(IVibrationSession.class)); verify(callback, never()).onFinishing(); verify(callback, never()).onFinished(anyInt()); @@ -2802,6 +2808,12 @@ public class VibratorManagerServiceTest { assertThat(session.getStatus()).isEqualTo(Status.IGNORED_UNSUPPORTED); verify(mNativeWrapperMock, never()).startSession(anyLong(), any(int[].class)); + verify(mVibratorFrameworkStatsLoggerMock, never()) + .logVibrationVendorSessionStarted(anyInt()); + verify(mVibratorFrameworkStatsLoggerMock, never()) + .logVibrationVendorSessionVibrations(anyInt(), anyInt()); + verify(mVibratorFrameworkStatsLoggerMock, never()) + .logVibrationVendorSessionInterrupted(anyInt()); verify(callback, never()).onFinishing(); verify(callback) .onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_UNSUPPORTED)); @@ -2821,6 +2833,12 @@ public class VibratorManagerServiceTest { assertThat(session).isNull(); verify(mNativeWrapperMock, never()).startSession(anyLong(), any(int[].class)); + verify(mVibratorFrameworkStatsLoggerMock, never()) + .logVibrationVendorSessionStarted(anyInt()); + verify(mVibratorFrameworkStatsLoggerMock, never()) + .logVibrationVendorSessionVibrations(anyInt(), anyInt()); + verify(mVibratorFrameworkStatsLoggerMock, never()) + .logVibrationVendorSessionInterrupted(anyInt()); } @Test @@ -2842,6 +2860,12 @@ public class VibratorManagerServiceTest { mTestLooper.dispatchAll(); verify(mNativeWrapperMock, never()).startSession(anyLong(), any(int[].class)); + verify(mVibratorFrameworkStatsLoggerMock, never()) + .logVibrationVendorSessionStarted(anyInt()); + verify(mVibratorFrameworkStatsLoggerMock, never()) + .logVibrationVendorSessionVibrations(anyInt(), anyInt()); + verify(mVibratorFrameworkStatsLoggerMock, never()) + .logVibrationVendorSessionInterrupted(anyInt()); verify(callback, never()).onFinishing(); verify(callback, times(2)) .onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_UNSUPPORTED)); @@ -2866,6 +2890,12 @@ public class VibratorManagerServiceTest { assertThat(session.getStatus()).isEqualTo(Status.IGNORED_UNSUPPORTED); verify(mNativeWrapperMock, never()).startSession(anyLong(), any(int[].class)); + verify(mVibratorFrameworkStatsLoggerMock, never()) + .logVibrationVendorSessionStarted(anyInt()); + verify(mVibratorFrameworkStatsLoggerMock, never()) + .logVibrationVendorSessionVibrations(anyInt(), anyInt()); + verify(mVibratorFrameworkStatsLoggerMock, never()) + .logVibrationVendorSessionInterrupted(anyInt()); verify(callback, never()).onStarted(any(IVibrationSession.class)); verify(callback, never()).onFinishing(); verify(callback) @@ -2901,6 +2931,12 @@ public class VibratorManagerServiceTest { assertThat(session.getStatus()).isEqualTo(Status.FINISHED); verify(callback).onFinishing(); verify(callback).onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_SUCCESS)); + + verify(mVibratorFrameworkStatsLoggerMock).logVibrationVendorSessionStarted(eq(UID)); + verify(mVibratorFrameworkStatsLoggerMock) + .logVibrationVendorSessionVibrations(eq(UID), eq(0)); + verify(mVibratorFrameworkStatsLoggerMock, never()) + .logVibrationVendorSessionInterrupted(anyInt()); } @Test @@ -2925,6 +2961,12 @@ public class VibratorManagerServiceTest { assertThat(session.getStatus()).isEqualTo(Status.CANCELLED_BY_USER); verify(callback).onFinishing(); verify(callback).onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_CANCELED)); + + verify(mVibratorFrameworkStatsLoggerMock).logVibrationVendorSessionStarted(eq(UID)); + verify(mVibratorFrameworkStatsLoggerMock) + .logVibrationVendorSessionVibrations(eq(UID), eq(0)); + verify(mVibratorFrameworkStatsLoggerMock, never()) + .logVibrationVendorSessionInterrupted(anyInt()); } @Test @@ -3011,6 +3053,11 @@ public class VibratorManagerServiceTest { assertThat(session.getStatus()).isEqualTo(Status.CANCELLED_BY_UNKNOWN_REASON); verify(callback).onFinishing(); verify(callback).onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_CANCELED)); + + verify(mVibratorFrameworkStatsLoggerMock).logVibrationVendorSessionStarted(eq(UID)); + verify(mVibratorFrameworkStatsLoggerMock) + .logVibrationVendorSessionVibrations(eq(UID), eq(0)); + verify(mVibratorFrameworkStatsLoggerMock).logVibrationVendorSessionInterrupted(anyInt()); } @Test @@ -3305,6 +3352,10 @@ public class VibratorManagerServiceTest { assertThat(service.isVibrating(2)).isFalse(); verify(callback).onFinishing(); verify(callback).onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_SUCCESS)); + + verify(mVibratorFrameworkStatsLoggerMock).logVibrationVendorSessionStarted(eq(UID)); + verify(mVibratorFrameworkStatsLoggerMock) + .logVibrationVendorSessionVibrations(eq(UID), eq(1)); } @Test @@ -3355,6 +3406,10 @@ public class VibratorManagerServiceTest { assertThat(service.isVibrating(2)).isFalse(); verify(callback).onFinishing(); verify(callback).onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_SUCCESS)); + + verify(mVibratorFrameworkStatsLoggerMock).logVibrationVendorSessionStarted(eq(UID)); + verify(mVibratorFrameworkStatsLoggerMock) + .logVibrationVendorSessionVibrations(eq(UID), eq(2)); } @Test |