Loading services/core/java/com/android/server/vibrator/VendorVibrationSession.java +30 −11 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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 Loading Loading @@ -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); } } Loading @@ -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 Loading Loading @@ -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 Loading @@ -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(); } Loading Loading @@ -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. Loading @@ -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) { Loading Loading @@ -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); } Loading Loading @@ -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); } Loading services/core/java/com/android/server/vibrator/Vibration.java +6 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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 Loading services/core/java/com/android/server/vibrator/VibratorFrameworkStatsLogger.java +98 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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); } } services/core/java/com/android/server/vibrator/VibratorManagerService.java +1 −1 Original line number Diff line number Diff line Loading @@ -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) { Loading services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java +55 −0 Original line number Diff line number Diff line Loading @@ -2796,6 +2796,12 @@ public class VibratorManagerServiceTest { stopAutoDispatcherAndDispatchAll(); 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()); Loading @@ -2817,6 +2823,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)); Loading @@ -2838,6 +2850,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 Loading @@ -2860,6 +2878,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, times(2)) .onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_UNSUPPORTED)); Loading @@ -2886,6 +2910,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) Loading Loading @@ -2923,6 +2953,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 Loading @@ -2949,6 +2985,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 Loading Loading @@ -3038,6 +3080,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 Loading Loading @@ -3340,6 +3387,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 Loading Loading @@ -3391,6 +3442,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 Loading Loading
services/core/java/com/android/server/vibrator/VendorVibrationSession.java +30 −11 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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 Loading Loading @@ -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); } } Loading @@ -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 Loading Loading @@ -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 Loading @@ -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(); } Loading Loading @@ -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. Loading @@ -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) { Loading Loading @@ -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); } Loading Loading @@ -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); } Loading
services/core/java/com/android/server/vibrator/Vibration.java +6 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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 Loading
services/core/java/com/android/server/vibrator/VibratorFrameworkStatsLogger.java +98 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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); } }
services/core/java/com/android/server/vibrator/VibratorManagerService.java +1 −1 Original line number Diff line number Diff line Loading @@ -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) { Loading
services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java +55 −0 Original line number Diff line number Diff line Loading @@ -2796,6 +2796,12 @@ public class VibratorManagerServiceTest { stopAutoDispatcherAndDispatchAll(); 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()); Loading @@ -2817,6 +2823,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)); Loading @@ -2838,6 +2850,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 Loading @@ -2860,6 +2878,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, times(2)) .onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_UNSUPPORTED)); Loading @@ -2886,6 +2910,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) Loading Loading @@ -2923,6 +2953,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 Loading @@ -2949,6 +2985,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 Loading Loading @@ -3038,6 +3080,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 Loading Loading @@ -3340,6 +3387,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 Loading Loading @@ -3391,6 +3442,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 Loading