summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--services/core/java/com/android/server/vibrator/VendorVibrationSession.java41
-rw-r--r--services/core/java/com/android/server/vibrator/Vibration.java6
-rw-r--r--services/core/java/com/android/server/vibrator/VibratorFrameworkStatsLogger.java98
-rw-r--r--services/core/java/com/android/server/vibrator/VibratorManagerService.java2
-rw-r--r--services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java55
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