summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Lais Andrade <lsandrade@google.com> 2023-07-26 18:09:39 +0100
committer Lais Andrade <lsandrade@google.com> 2023-07-31 10:28:03 +0100
commit7b41ed78d140db2c7feb0230dec30941a23ebddc (patch)
treeeb06fd3369e8eda7495821392ec02cd1e3e8dffe
parent19dae7cd516fade02b202ad1c25cd797bf0662ea (diff)
Improve VibratorManagerService dumpsys
Sometimes it can be hard to debug vibrations using a bugreport when the user interacts with the keyboard during the bugreport generation. This can create multiple touch vibrations that push all other touch vibrations out of the buffer. This change keeps the existing vibration records, now renamed to "recent vibrations", and adds a new field "aggregated vibration history" which aggregates vibrations by uid, attributes and effect when they are requested in close succession (less than 1s). This change also introduces other improvements to dumpsys: - Print VibrationSettings, VibrationConfig and VibratorController using multiple lines and indentation - External vibration breakdown by usage - Simplify each vibration text, printing a few lines for each vibration and simplifying the VibrationEffect string. Fix: 260309860 Test: adb shell dumpsys vibrator_manager Change-Id: I3d065a6264716185675f6a83ba61dcae71e8e38f
-rw-r--r--core/java/android/os/CombinedVibration.java38
-rw-r--r--core/java/android/os/VibrationAttributes.java9
-rw-r--r--core/java/android/os/VibrationEffect.java44
-rw-r--r--core/java/android/os/VibratorInfo.java24
-rw-r--r--core/java/android/os/vibrator/PrebakedSegment.java9
-rw-r--r--core/java/android/os/vibrator/PrimitiveSegment.java7
-rw-r--r--core/java/android/os/vibrator/RampSegment.java11
-rw-r--r--core/java/android/os/vibrator/StepSegment.java7
-rw-r--r--core/java/android/os/vibrator/VibrationConfig.java17
-rw-r--r--core/java/android/os/vibrator/VibrationEffectSegment.java7
-rw-r--r--core/res/res/values/config.xml8
-rw-r--r--core/res/res/values/symbols.xml4
-rw-r--r--services/core/java/com/android/server/vibrator/Vibration.java80
-rw-r--r--services/core/java/com/android/server/vibrator/VibrationSettings.java28
-rw-r--r--services/core/java/com/android/server/vibrator/VibratorController.java15
-rw-r--r--services/core/java/com/android/server/vibrator/VibratorManagerService.java256
16 files changed, 469 insertions, 95 deletions
diff --git a/core/java/android/os/CombinedVibration.java b/core/java/android/os/CombinedVibration.java
index db1a7411de29..f32a1f831a07 100644
--- a/core/java/android/os/CombinedVibration.java
+++ b/core/java/android/os/CombinedVibration.java
@@ -24,7 +24,9 @@ import com.android.internal.util.Preconditions;
import java.util.ArrayList;
import java.util.List;
+import java.util.Locale;
import java.util.Objects;
+import java.util.StringJoiner;
/**
* A CombinedVibration describes a combination of haptic effects to be performed by one or more
@@ -151,6 +153,13 @@ public abstract class CombinedVibration implements Parcelable {
public abstract boolean hasVibrator(int vibratorId);
/**
+ * Returns a compact version of the {@link #toString()} result for debugging purposes.
+ *
+ * @hide
+ */
+ public abstract String toDebugString();
+
+ /**
* Adapts a {@link VibrationEffect} to a specific device vibrator using the ID.
*
* <p>This can be used for adapting effects to the capabilities of the specific device vibrator
@@ -437,6 +446,13 @@ public abstract class CombinedVibration implements Parcelable {
return "Mono{mEffect=" + mEffect + '}';
}
+ /** @hide */
+ @Override
+ public String toDebugString() {
+ // Simplify vibration string, use the single effect to represent it.
+ return mEffect.toDebugString();
+ }
+
@Override
public int describeContents() {
return 0;
@@ -619,6 +635,17 @@ public abstract class CombinedVibration implements Parcelable {
return "Stereo{mEffects=" + mEffects + '}';
}
+ /** @hide */
+ @Override
+ public String toDebugString() {
+ StringJoiner sj = new StringJoiner(",", "Stereo{", "}");
+ for (int i = 0; i < mEffects.size(); i++) {
+ sj.add(String.format(Locale.ROOT, "vibrator(id=%d): %s",
+ mEffects.keyAt(i), mEffects.valueAt(i).toDebugString()));
+ }
+ return sj.toString();
+ }
+
@Override
public int describeContents() {
return 0;
@@ -833,6 +860,17 @@ public abstract class CombinedVibration implements Parcelable {
return "Sequential{mEffects=" + mEffects + ", mDelays=" + mDelays + '}';
}
+ /** @hide */
+ @Override
+ public String toDebugString() {
+ StringJoiner sj = new StringJoiner(",", "Sequential{", "}");
+ for (int i = 0; i < mEffects.size(); i++) {
+ sj.add(String.format(Locale.ROOT, "delayMs=%d, effect=%s",
+ mDelays.get(i), mEffects.get(i).toDebugString()));
+ }
+ return sj.toString();
+ }
+
@Override
public int describeContents() {
return 0;
diff --git a/core/java/android/os/VibrationAttributes.java b/core/java/android/os/VibrationAttributes.java
index b2d62ebad2a6..98f9dffaae13 100644
--- a/core/java/android/os/VibrationAttributes.java
+++ b/core/java/android/os/VibrationAttributes.java
@@ -336,10 +336,11 @@ public final class VibrationAttributes implements Parcelable {
@Override
public String toString() {
- return "VibrationAttributes:"
- + " Usage=" + usageToString()
- + " Audio Usage= " + AudioAttributes.usageToString(mOriginalAudioUsage)
- + " Flags=" + mFlags;
+ return "VibrationAttributes{"
+ + "mUsage=" + usageToString()
+ + ", mAudioUsage= " + AudioAttributes.usageToString(mOriginalAudioUsage)
+ + ", mFlags=" + mFlags
+ + '}';
}
/** @hide */
diff --git a/core/java/android/os/VibrationEffect.java b/core/java/android/os/VibrationEffect.java
index 0461b2eb413b..b1899177d3c5 100644
--- a/core/java/android/os/VibrationEffect.java
+++ b/core/java/android/os/VibrationEffect.java
@@ -44,7 +44,9 @@ import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import java.util.Locale;
import java.util.Objects;
+import java.util.StringJoiner;
/**
* A VibrationEffect describes a haptic effect to be performed by a {@link Vibrator}.
@@ -629,6 +631,13 @@ public abstract class VibrationEffect implements Parcelable {
return MathUtils.constrain(a * fx, 0f, 1f);
}
+ /**
+ * Returns a compact version of the {@link #toString()} result for debugging purposes.
+ *
+ * @hide
+ */
+ public abstract String toDebugString();
+
/** @hide */
public static String effectIdToString(int effectId) {
switch (effectId) {
@@ -925,6 +934,23 @@ public abstract class VibrationEffect implements Parcelable {
+ "}";
}
+ /** @hide */
+ @Override
+ public String toDebugString() {
+ if (mSegments.size() == 1 && mRepeatIndex < 0) {
+ // Simplify effect string, use the single segment to represent it.
+ return mSegments.get(0).toDebugString();
+ }
+ StringJoiner sj = new StringJoiner(",", "[", "]");
+ for (int i = 0; i < mSegments.size(); i++) {
+ sj.add(mSegments.get(i).toDebugString());
+ }
+ if (mRepeatIndex >= 0) {
+ return String.format(Locale.ROOT, "%s, repeat=%d", sj, mRepeatIndex);
+ }
+ return sj.toString();
+ }
+
@Override
public int describeContents() {
return 0;
@@ -1250,23 +1276,23 @@ public abstract class VibrationEffect implements Parcelable {
public static String primitiveToString(@PrimitiveType int id) {
switch (id) {
case PRIMITIVE_NOOP:
- return "PRIMITIVE_NOOP";
+ return "NOOP";
case PRIMITIVE_CLICK:
- return "PRIMITIVE_CLICK";
+ return "CLICK";
case PRIMITIVE_THUD:
- return "PRIMITIVE_THUD";
+ return "THUD";
case PRIMITIVE_SPIN:
- return "PRIMITIVE_SPIN";
+ return "SPIN";
case PRIMITIVE_QUICK_RISE:
- return "PRIMITIVE_QUICK_RISE";
+ return "QUICK_RISE";
case PRIMITIVE_SLOW_RISE:
- return "PRIMITIVE_SLOW_RISE";
+ return "SLOW_RISE";
case PRIMITIVE_QUICK_FALL:
- return "PRIMITIVE_QUICK_FALL";
+ return "QUICK_FALL";
case PRIMITIVE_TICK:
- return "PRIMITIVE_TICK";
+ return "TICK";
case PRIMITIVE_LOW_TICK:
- return "PRIMITIVE_LOW_TICK";
+ return "LOW_TICK";
default:
return Integer.toString(id);
}
diff --git a/core/java/android/os/VibratorInfo.java b/core/java/android/os/VibratorInfo.java
index 71ec0967b327..02e685699398 100644
--- a/core/java/android/os/VibratorInfo.java
+++ b/core/java/android/os/VibratorInfo.java
@@ -20,6 +20,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.hardware.vibrator.Braking;
import android.hardware.vibrator.IVibrator;
+import android.util.IndentingPrintWriter;
import android.util.MathUtils;
import android.util.Range;
import android.util.SparseBooleanArray;
@@ -207,6 +208,25 @@ public class VibratorInfo implements Parcelable {
+ '}';
}
+ /** @hide */
+ public void dump(IndentingPrintWriter pw) {
+ pw.println("VibratorInfo:");
+ pw.increaseIndent();
+ pw.println("id = " + mId);
+ pw.println("capabilities = " + Arrays.toString(getCapabilitiesNames()));
+ pw.println("capabilitiesFlags = " + Long.toBinaryString(mCapabilities));
+ pw.println("supportedEffects = " + Arrays.toString(getSupportedEffectsNames()));
+ pw.println("supportedPrimitives = " + Arrays.toString(getSupportedPrimitivesNames()));
+ pw.println("supportedBraking = " + Arrays.toString(getSupportedBrakingNames()));
+ pw.println("primitiveDelayMax = " + mPrimitiveDelayMax);
+ pw.println("compositionSizeMax = " + mCompositionSizeMax);
+ pw.println("pwlePrimitiveDurationMax = " + mPwlePrimitiveDurationMax);
+ pw.println("pwleSizeMax = " + mPwleSizeMax);
+ pw.println("q-factor = " + mQFactor);
+ pw.println("frequencyProfile = " + mFrequencyProfile);
+ pw.decreaseIndent();
+ }
+
/** Return the id of this vibrator. */
public int getId() {
return mId;
@@ -370,7 +390,7 @@ public class VibratorInfo implements Parcelable {
* Gets the resonant frequency of the vibrator.
*
* @return the resonant frequency of the vibrator, or {@link Float#NaN NaN} if it's unknown or
- * this vibrator is a composite of multiple physical devices.
+ * this vibrator is a composite of multiple physical devices.
*/
public float getResonantFrequencyHz() {
return mFrequencyProfile.mResonantFrequencyHz;
@@ -380,7 +400,7 @@ public class VibratorInfo implements Parcelable {
* Gets the <a href="https://en.wikipedia.org/wiki/Q_factor">Q factor</a> of the vibrator.
*
* @return the Q factor of the vibrator, or {@link Float#NaN NaN} if it's unknown or
- * this vibrator is a composite of multiple physical devices.
+ * this vibrator is a composite of multiple physical devices.
*/
public float getQFactor() {
return mQFactor;
diff --git a/core/java/android/os/vibrator/PrebakedSegment.java b/core/java/android/os/vibrator/PrebakedSegment.java
index da2ee6c75609..d9a6165a9258 100644
--- a/core/java/android/os/vibrator/PrebakedSegment.java
+++ b/core/java/android/os/vibrator/PrebakedSegment.java
@@ -209,6 +209,15 @@ public final class PrebakedSegment extends VibrationEffectSegment {
+ "}";
}
+ /** @hide */
+ @Override
+ public String toDebugString() {
+ return String.format("Prebaked=%s(%s, %s fallback)",
+ VibrationEffect.effectIdToString(mEffectId),
+ VibrationEffect.effectStrengthToString(mEffectStrength),
+ mFallback ? "with" : "no");
+ }
+
@Override
public int describeContents() {
return 0;
diff --git a/core/java/android/os/vibrator/PrimitiveSegment.java b/core/java/android/os/vibrator/PrimitiveSegment.java
index e1fa97b34bd1..96a2f2a8d42e 100644
--- a/core/java/android/os/vibrator/PrimitiveSegment.java
+++ b/core/java/android/os/vibrator/PrimitiveSegment.java
@@ -147,6 +147,13 @@ public final class PrimitiveSegment extends VibrationEffectSegment {
+ '}';
}
+ /** @hide */
+ @Override
+ public String toDebugString() {
+ return String.format("Primitive=%s(scale=%.2f, delay=%dms)",
+ VibrationEffect.Composition.primitiveToString(mPrimitiveId), mScale, mDelay);
+ }
+
@Override
public boolean equals(@Nullable Object o) {
if (this == o) return true;
diff --git a/core/java/android/os/vibrator/RampSegment.java b/core/java/android/os/vibrator/RampSegment.java
index 034962a5f91b..21a453acd071 100644
--- a/core/java/android/os/vibrator/RampSegment.java
+++ b/core/java/android/os/vibrator/RampSegment.java
@@ -185,6 +185,17 @@ public final class RampSegment extends VibrationEffectSegment {
+ "}";
}
+ /** @hide */
+ @Override
+ public String toDebugString() {
+ return String.format("Ramp=%dms(amplitude=%.2f%s to %.2f%s)",
+ mDuration,
+ mStartAmplitude,
+ Float.compare(mStartFrequencyHz, 0) == 0 ? "" : " @ " + mStartFrequencyHz + "Hz",
+ mEndAmplitude,
+ Float.compare(mEndFrequencyHz, 0) == 0 ? "" : " @ " + mEndFrequencyHz + "Hz");
+ }
+
@Override
public int describeContents() {
return 0;
diff --git a/core/java/android/os/vibrator/StepSegment.java b/core/java/android/os/vibrator/StepSegment.java
index 817187e1cebb..5be45384018b 100644
--- a/core/java/android/os/vibrator/StepSegment.java
+++ b/core/java/android/os/vibrator/StepSegment.java
@@ -168,6 +168,13 @@ public final class StepSegment extends VibrationEffectSegment {
+ "}";
}
+ /** @hide */
+ @Override
+ public String toDebugString() {
+ return String.format("Step=%dms(amplitude=%.2f%s)", mDuration, mAmplitude,
+ Float.compare(mFrequencyHz, 0) == 0 ? "" : " @ " + mFrequencyHz + "Hz");
+ }
+
@Override
public int describeContents() {
return 0;
diff --git a/core/java/android/os/vibrator/VibrationConfig.java b/core/java/android/os/vibrator/VibrationConfig.java
index 4790d81eeb6b..bde334a6edc5 100644
--- a/core/java/android/os/vibrator/VibrationConfig.java
+++ b/core/java/android/os/vibrator/VibrationConfig.java
@@ -32,6 +32,9 @@ import android.content.res.Resources;
import android.os.VibrationAttributes;
import android.os.Vibrator;
import android.os.Vibrator.VibrationIntensity;
+import android.util.IndentingPrintWriter;
+
+import java.io.PrintWriter;
/**
* List of device-specific internal vibration configuration loaded from platform config.xml.
@@ -191,4 +194,18 @@ public class VibrationConfig {
+ ", mDefaultRingIntensity=" + mDefaultRingVibrationIntensity
+ "}";
}
+
+ /**
+ * Write current settings into given {@link PrintWriter}, skipping the default settings.
+ *
+ * @hide
+ */
+ public void dumpWithoutDefaultSettings(IndentingPrintWriter pw) {
+ pw.println("VibrationConfig:");
+ pw.increaseIndent();
+ pw.println("hapticChannelMaxAmplitude = " + mHapticChannelMaxVibrationAmplitude);
+ pw.println("rampStepDurationMs = " + mRampStepDurationMs);
+ pw.println("rampDownDurationMs = " + mRampDownDurationMs);
+ pw.decreaseIndent();
+ }
}
diff --git a/core/java/android/os/vibrator/VibrationEffectSegment.java b/core/java/android/os/vibrator/VibrationEffectSegment.java
index 75a055fa5273..a5949e13583b 100644
--- a/core/java/android/os/vibrator/VibrationEffectSegment.java
+++ b/core/java/android/os/vibrator/VibrationEffectSegment.java
@@ -123,6 +123,13 @@ public abstract class VibrationEffectSegment implements Parcelable {
public abstract <T extends VibrationEffectSegment> T applyEffectStrength(int effectStrength);
/**
+ * Returns a compact version of the {@link #toString()} result for debugging purposes.
+ *
+ * @hide
+ */
+ public abstract String toDebugString();
+
+ /**
* Checks the given frequency argument is valid to represent a vibration effect frequency in
* hertz, i.e. a finite non-negative value.
*
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index f63f8b3c7af8..b082b538626f 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3936,8 +3936,14 @@
<!-- Flag indicating device support for EAP SIM, AKA, AKA' -->
<bool name="config_eap_sim_based_auth_supported">true</bool>
+ <!-- How long history of recent vibrations should be kept for the dumpsys. -->
+ <integer name="config_recentVibrationsDumpSizeLimit">20</integer>
+
<!-- How long history of previous vibrations should be kept for the dumpsys. -->
- <integer name="config_previousVibrationsDumpLimit">50</integer>
+ <integer name="config_previousVibrationsDumpSizeLimit">50</integer>
+
+ <!-- How close vibration request should be when they're aggregated for dumpsys, in ms. -->
+ <integer name="config_previousVibrationsDumpAggregationTimeMillisLimit">1000</integer>
<!-- The default vibration strength, must be between 1 and 255 inclusive. -->
<integer name="config_defaultVibrationAmplitude">255</integer>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 8e52633c8324..580d76937954 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2038,7 +2038,9 @@
<java-symbol type="integer" name="config_notificationsBatteryNearlyFullLevel" />
<java-symbol type="integer" name="config_notificationServiceArchiveSize" />
<java-symbol type="dimen" name="config_rotaryEncoderAxisScrollTickInterval" />
- <java-symbol type="integer" name="config_previousVibrationsDumpLimit" />
+ <java-symbol type="integer" name="config_recentVibrationsDumpSizeLimit" />
+ <java-symbol type="integer" name="config_previousVibrationsDumpSizeLimit" />
+ <java-symbol type="integer" name="config_previousVibrationsDumpAggregationTimeMillisLimit" />
<java-symbol type="integer" name="config_defaultVibrationAmplitude" />
<java-symbol type="dimen" name="config_hapticChannelMaxVibrationAmplitude" />
<java-symbol type="integer" name="config_vibrationWaveformRampStepDuration" />
diff --git a/services/core/java/com/android/server/vibrator/Vibration.java b/services/core/java/com/android/server/vibrator/Vibration.java
index 4f7f13e42790..fed6e7ee4686 100644
--- a/services/core/java/com/android/server/vibrator/Vibration.java
+++ b/services/core/java/com/android/server/vibrator/Vibration.java
@@ -18,6 +18,7 @@ package com.android.server.vibrator;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.media.AudioAttributes;
import android.os.CombinedVibration;
import android.os.IBinder;
import android.os.VibrationAttributes;
@@ -27,8 +28,10 @@ import android.os.vibrator.PrimitiveSegment;
import android.os.vibrator.RampSegment;
import android.os.vibrator.StepSegment;
import android.os.vibrator.VibrationEffectSegment;
+import android.util.IndentingPrintWriter;
import android.util.proto.ProtoOutputStream;
+import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
@@ -39,7 +42,9 @@ import java.util.concurrent.atomic.AtomicInteger;
* The base class for all vibrations.
*/
abstract class Vibration {
- private static final SimpleDateFormat DEBUG_DATE_FORMAT =
+ private static final SimpleDateFormat DEBUG_TIME_FORMAT =
+ new SimpleDateFormat("HH:mm:ss.SSS");
+ private static final SimpleDateFormat DEBUG_DATE_TIME_FORMAT =
new SimpleDateFormat("MM-dd HH:mm:ss.SSS");
// Used to generate globally unique vibration ids.
private static final AtomicInteger sNextVibrationId = new AtomicInteger(1); // 0 = no callback
@@ -146,10 +151,10 @@ abstract class Vibration {
@Override
public String toString() {
return "CallerInfo{"
- + " attrs=" + attrs
- + ", uid=" + uid
- + ", displayId=" + displayId
+ + " uid=" + uid
+ ", opPkg=" + opPkg
+ + ", displayId=" + displayId
+ + ", attrs=" + attrs
+ ", reason=" + reason
+ '}';
}
@@ -203,14 +208,17 @@ abstract class Vibration {
* potentially expensive or resource-linked objects, such as {@link IBinder}.
*/
static final class DebugInfo {
- private final long mCreateTime;
+ final long mCreateTime;
+ final CallerInfo mCallerInfo;
+ @Nullable
+ final CombinedVibration mPlayedEffect;
+
private final long mStartTime;
private final long mEndTime;
private final long mDurationMs;
- @Nullable private final CombinedVibration mOriginalEffect;
- @Nullable private final CombinedVibration mPlayedEffect;
+ @Nullable
+ private final CombinedVibration mOriginalEffect;
private final float mScale;
- private final CallerInfo mCallerInfo;
private final Status mStatus;
DebugInfo(Status status, VibrationStats stats, @Nullable CombinedVibration playedEffect,
@@ -230,10 +238,10 @@ abstract class Vibration {
@Override
public String toString() {
- return "createTime: " + DEBUG_DATE_FORMAT.format(new Date(mCreateTime))
- + ", startTime: " + DEBUG_DATE_FORMAT.format(new Date(mStartTime))
+ return "createTime: " + DEBUG_DATE_TIME_FORMAT.format(new Date(mCreateTime))
+ + ", startTime: " + DEBUG_DATE_TIME_FORMAT.format(new Date(mStartTime))
+ ", endTime: "
- + (mEndTime == 0 ? null : DEBUG_DATE_FORMAT.format(new Date(mEndTime)))
+ + (mEndTime == 0 ? null : DEBUG_DATE_TIME_FORMAT.format(new Date(mEndTime)))
+ ", durationMs: " + mDurationMs
+ ", status: " + mStatus.name().toLowerCase(Locale.ROOT)
+ ", playedEffect: " + mPlayedEffect
@@ -242,8 +250,56 @@ abstract class Vibration {
+ ", callerInfo: " + mCallerInfo;
}
+ /**
+ * Write this info in a compact way into given {@link PrintWriter}.
+ *
+ * <p>This is used by dumpsys to log multiple vibration records in single lines that are
+ * easy to skim through by the sorted created time.
+ */
+ void dumpCompact(IndentingPrintWriter pw) {
+ boolean isExternalVibration = mPlayedEffect == null;
+ String timingsStr = String.format(Locale.ROOT,
+ "%s | %8s | %20s | duration: %5dms | start: %12s | end: %10s",
+ DEBUG_DATE_TIME_FORMAT.format(new Date(mCreateTime)),
+ isExternalVibration ? "external" : "effect",
+ mStatus.name().toLowerCase(Locale.ROOT),
+ mDurationMs,
+ mStartTime == 0 ? "" : DEBUG_TIME_FORMAT.format(new Date(mStartTime)),
+ mEndTime == 0 ? "" : DEBUG_TIME_FORMAT.format(new Date(mEndTime)));
+ String callerInfoStr = String.format(Locale.ROOT,
+ " | %s (uid=%d, displayId=%d) | usage: %s (audio=%s) | flags: %s | reason: %s",
+ mCallerInfo.opPkg, mCallerInfo.uid, mCallerInfo.displayId,
+ mCallerInfo.attrs.usageToString(),
+ AudioAttributes.usageToString(mCallerInfo.attrs.getAudioUsage()),
+ Long.toBinaryString(mCallerInfo.attrs.getFlags()),
+ mCallerInfo.reason);
+ String effectStr = String.format(Locale.ROOT,
+ " | played: %s | original: %s | scale: %.2f",
+ mPlayedEffect == null ? null : mPlayedEffect.toDebugString(),
+ mOriginalEffect == null ? null : mOriginalEffect.toDebugString(),
+ mScale);
+ pw.println(timingsStr + callerInfoStr + effectStr);
+ }
+
+ /** Write this info into given {@link PrintWriter}. */
+ void dump(IndentingPrintWriter pw) {
+ pw.println("Vibration:");
+ pw.increaseIndent();
+ pw.println("status = " + mStatus.name().toLowerCase(Locale.ROOT));
+ pw.println("durationMs = " + mDurationMs);
+ pw.println("createTime = " + DEBUG_DATE_TIME_FORMAT.format(new Date(mCreateTime)));
+ pw.println("startTime = " + DEBUG_DATE_TIME_FORMAT.format(new Date(mStartTime)));
+ pw.println("endTime = "
+ + (mEndTime == 0 ? null : DEBUG_DATE_TIME_FORMAT.format(new Date(mEndTime))));
+ pw.println("playedEffect = " + mPlayedEffect);
+ pw.println("originalEffect = " + mOriginalEffect);
+ pw.println("scale = " + String.format(Locale.ROOT, "%.2f", mScale));
+ pw.println("callerInfo = " + mCallerInfo);
+ pw.decreaseIndent();
+ }
+
/** Write this info into given {@code fieldId} on {@link ProtoOutputStream}. */
- public void dumpProto(ProtoOutputStream proto, long fieldId) {
+ void dump(ProtoOutputStream proto, long fieldId) {
final long token = proto.start(fieldId);
proto.write(VibrationProto.START_TIME, mStartTime);
proto.write(VibrationProto.END_TIME, mEndTime);
diff --git a/services/core/java/com/android/server/vibrator/VibrationSettings.java b/services/core/java/com/android/server/vibrator/VibrationSettings.java
index dbd6bf461e85..db8a9ae553cd 100644
--- a/services/core/java/com/android/server/vibrator/VibrationSettings.java
+++ b/services/core/java/com/android/server/vibrator/VibrationSettings.java
@@ -54,6 +54,7 @@ import android.os.Vibrator;
import android.os.Vibrator.VibrationIntensity;
import android.os.vibrator.VibrationConfig;
import android.provider.Settings;
+import android.util.IndentingPrintWriter;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseIntArray;
@@ -65,6 +66,7 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.server.LocalServices;
import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
+import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
@@ -601,8 +603,32 @@ final class VibrationSettings {
}
}
+ /** Write current settings into given {@link PrintWriter}. */
+ void dump(IndentingPrintWriter pw) {
+ pw.println("VibrationSettings:");
+ pw.increaseIndent();
+ pw.println("vibrateOn = " + mVibrateOn);
+ pw.println("vibrateInputDevices = " + mVibrateInputDevices);
+ pw.println("batterySaverMode = " + mBatterySaverMode);
+ pw.println("VibrationIntensities:");
+
+ pw.increaseIndent();
+ for (int i = 0; i < mCurrentVibrationIntensities.size(); i++) {
+ int usage = mCurrentVibrationIntensities.keyAt(i);
+ int intensity = mCurrentVibrationIntensities.valueAt(i);
+ pw.println(VibrationAttributes.usageToString(usage) + " = "
+ + intensityToString(intensity)
+ + ", default: " + intensityToString(getDefaultIntensity(usage)));
+ }
+ pw.decreaseIndent();
+
+ mVibrationConfig.dumpWithoutDefaultSettings(pw);
+ pw.println("processStateCache = " + mUidObserver.mProcStatesCache);
+ pw.decreaseIndent();
+ }
+
/** Write current settings into given {@link ProtoOutputStream}. */
- public void dumpProto(ProtoOutputStream proto) {
+ void dump(ProtoOutputStream proto) {
synchronized (mLock) {
proto.write(VibratorManagerServiceDumpProto.VIBRATE_ON, mVibrateOn);
proto.write(VibratorManagerServiceDumpProto.LOW_POWER_MODE, mBatterySaverMode);
diff --git a/services/core/java/com/android/server/vibrator/VibratorController.java b/services/core/java/com/android/server/vibrator/VibratorController.java
index 47b3e1a2294c..f5d4d1e3926b 100644
--- a/services/core/java/com/android/server/vibrator/VibratorController.java
+++ b/services/core/java/com/android/server/vibrator/VibratorController.java
@@ -17,6 +17,7 @@
package com.android.server.vibrator;
import android.annotation.Nullable;
+import android.annotation.Nullable;
import android.hardware.vibrator.IVibrator;
import android.os.Binder;
import android.os.IVibratorStateListener;
@@ -26,6 +27,7 @@ import android.os.VibratorInfo;
import android.os.vibrator.PrebakedSegment;
import android.os.vibrator.PrimitiveSegment;
import android.os.vibrator.RampSegment;
+import android.util.IndentingPrintWriter;
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
@@ -351,6 +353,19 @@ final class VibratorController {
+ '}';
}
+ void dump(IndentingPrintWriter pw) {
+ pw.println("VibratorController:");
+ pw.increaseIndent();
+ pw.println("isVibrating = " + mIsVibrating);
+ pw.println("isUnderExternalControl = " + mIsUnderExternalControl);
+ pw.println("currentAmplitude = " + mCurrentAmplitude);
+ pw.println("vibratorInfoLoadSuccessful = " + mVibratorInfoLoadSuccessful);
+ pw.println("vibratorStateListenerCount = "
+ + mVibratorStateListeners.getRegisteredCallbackCount());
+ mVibratorInfo.dump(pw);
+ pw.decreaseIndent();
+ }
+
@GuardedBy("mLock")
private void notifyListenerOnVibrating(boolean isVibrating) {
if (mIsVibrating != isVibrating) {
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index 6fdb1dbf2a83..270f7f0c1980 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -56,6 +56,7 @@ import android.os.vibrator.PrebakedSegment;
import android.os.vibrator.VibrationEffectSegment;
import android.os.vibrator.persistence.VibrationXmlParser;
import android.text.TextUtils;
+import android.util.IndentingPrintWriter;
import android.util.Slog;
import android.util.SparseArray;
import android.util.proto.ProtoOutputStream;
@@ -80,6 +81,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
+import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.Function;
@@ -204,9 +206,15 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
mNativeWrapper = injector.getNativeWrapper();
mNativeWrapper.init(listener);
- int dumpLimit = mContext.getResources().getInteger(
- com.android.internal.R.integer.config_previousVibrationsDumpLimit);
- mVibratorManagerRecords = new VibratorManagerRecords(dumpLimit);
+ int recentDumpSizeLimit = mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_recentVibrationsDumpSizeLimit);
+ int dumpSizeLimit = mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_previousVibrationsDumpSizeLimit);
+ int dumpAggregationTimeLimit = mContext.getResources().getInteger(
+ com.android.internal.R.integer
+ .config_previousVibrationsDumpAggregationTimeMillisLimit);
+ mVibratorManagerRecords = new VibratorManagerRecords(
+ recentDumpSizeLimit, dumpSizeLimit, dumpAggregationTimeLimit);
mBatteryStatsService = injector.getBatteryStatsService();
mFrameworkStatsLogger = injector.getFrameworkStatsLogger(mHandler);
@@ -544,49 +552,74 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
}
}
- private void dumpText(PrintWriter pw) {
+ private void dumpText(PrintWriter w) {
if (DEBUG) {
Slog.d(TAG, "Dumping vibrator manager service to text...");
}
+ IndentingPrintWriter pw = new IndentingPrintWriter(w, /* singleIndent= */ " ");
synchronized (mLock) {
pw.println("Vibrator Manager Service:");
- pw.println(" mVibrationSettings:");
- pw.println(" " + mVibrationSettings);
+ pw.increaseIndent();
+
+ mVibrationSettings.dump(pw);
pw.println();
- pw.println(" mVibratorControllers:");
+
+ pw.println("VibratorControllers:");
+ pw.increaseIndent();
for (int i = 0; i < mVibrators.size(); i++) {
- pw.println(" " + mVibrators.valueAt(i));
+ mVibrators.valueAt(i).dump(pw);
}
+ pw.decreaseIndent();
pw.println();
- pw.println(" mCurrentVibration:");
- pw.println(" " + (mCurrentVibration == null
- ? null : mCurrentVibration.getVibration().getDebugInfo()));
- pw.println();
- pw.println(" mNextVibration:");
- pw.println(" " + (mNextVibration == null
- ? null : mNextVibration.getVibration().getDebugInfo()));
+
+ pw.println("CurrentVibration:");
+ pw.increaseIndent();
+ if (mCurrentVibration != null) {
+ mCurrentVibration.getVibration().getDebugInfo().dump(pw);
+ } else {
+ pw.println("null");
+ }
+ pw.decreaseIndent();
pw.println();
- pw.println(" mCurrentExternalVibration:");
- pw.println(" " + (mCurrentExternalVibration == null
- ? null : mCurrentExternalVibration.getDebugInfo()));
+
+ pw.println("NextVibration:");
+ pw.increaseIndent();
+ if (mNextVibration != null) {
+ mNextVibration.getVibration().getDebugInfo().dump(pw);
+ } else {
+ pw.println("null");
+ }
+ pw.decreaseIndent();
pw.println();
+
+ pw.println("CurrentExternalVibration:");
+ pw.increaseIndent();
+ if (mCurrentExternalVibration != null) {
+ mCurrentExternalVibration.getDebugInfo().dump(pw);
+ } else {
+ pw.println("null");
+ }
+ pw.decreaseIndent();
}
- mVibratorManagerRecords.dumpText(pw);
+
+ pw.println();
+ pw.println();
+ mVibratorManagerRecords.dump(pw);
}
- synchronized void dumpProto(FileDescriptor fd) {
+ private void dumpProto(FileDescriptor fd) {
final ProtoOutputStream proto = new ProtoOutputStream(fd);
if (DEBUG) {
Slog.d(TAG, "Dumping vibrator manager service to proto...");
}
synchronized (mLock) {
- mVibrationSettings.dumpProto(proto);
+ mVibrationSettings.dump(proto);
if (mCurrentVibration != null) {
- mCurrentVibration.getVibration().getDebugInfo().dumpProto(proto,
+ mCurrentVibration.getVibration().getDebugInfo().dump(proto,
VibratorManagerServiceDumpProto.CURRENT_VIBRATION);
}
if (mCurrentExternalVibration != null) {
- mCurrentExternalVibration.getDebugInfo().dumpProto(proto,
+ mCurrentExternalVibration.getDebugInfo().dump(proto,
VibratorManagerServiceDumpProto.CURRENT_EXTERNAL_VIBRATION);
}
@@ -601,7 +634,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
proto.write(VibratorManagerServiceDumpProto.VIBRATOR_UNDER_EXTERNAL_CONTROL,
isUnderExternalControl);
}
- mVibratorManagerRecords.dumpProto(proto);
+ mVibratorManagerRecords.dump(proto);
proto.flush();
}
@@ -1581,64 +1614,105 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
/** Keep records of vibrations played and provide debug information for this service. */
private static final class VibratorManagerRecords {
- private final SparseArray<LinkedList<Vibration.DebugInfo>> mPreviousVibrations =
- new SparseArray<>();
- private final LinkedList<Vibration.DebugInfo> mPreviousExternalVibrations =
- new LinkedList<>();
- private final int mPreviousVibrationsLimit;
+ private final VibrationRecords mAggregatedVibrationHistory;
+ private final VibrationRecords mRecentVibrations;
- VibratorManagerRecords(int limit) {
- mPreviousVibrationsLimit = limit;
+ VibratorManagerRecords(int recentVibrationSizeLimit, int aggregationSizeLimit,
+ int aggregationTimeLimit) {
+ mAggregatedVibrationHistory =
+ new VibrationRecords(aggregationSizeLimit, aggregationTimeLimit);
+ mRecentVibrations = new VibrationRecords(
+ recentVibrationSizeLimit, /* aggregationTimeLimit= */ 0);
}
synchronized void record(HalVibration vib) {
- int usage = vib.callerInfo.attrs.getUsage();
- if (!mPreviousVibrations.contains(usage)) {
- mPreviousVibrations.put(usage, new LinkedList<>());
- }
- record(mPreviousVibrations.get(usage), vib.getDebugInfo());
+ record(vib.getDebugInfo());
}
synchronized void record(ExternalVibrationHolder vib) {
- record(mPreviousExternalVibrations, vib.getDebugInfo());
+ record(vib.getDebugInfo());
}
- synchronized void record(LinkedList<Vibration.DebugInfo> records,
- Vibration.DebugInfo info) {
- if (records.size() > mPreviousVibrationsLimit) {
- records.removeFirst();
+ private synchronized void record(Vibration.DebugInfo info) {
+ AggregatedVibrationRecord removedRecord = mRecentVibrations.record(info);
+ if (removedRecord != null) {
+ mAggregatedVibrationHistory.record(removedRecord.mLatestVibration);
}
- records.addLast(info);
}
- synchronized void dumpText(PrintWriter pw) {
- for (int i = 0; i < mPreviousVibrations.size(); i++) {
- pw.println();
- pw.print(" Previous vibrations for usage ");
- pw.print(VibrationAttributes.usageToString(mPreviousVibrations.keyAt(i)));
- pw.println(":");
- for (Vibration.DebugInfo info : mPreviousVibrations.valueAt(i)) {
- pw.println(" " + info);
+ synchronized void dump(IndentingPrintWriter pw) {
+ pw.println("Recent vibrations:");
+ pw.increaseIndent();
+ mRecentVibrations.dump(pw);
+ pw.decreaseIndent();
+ pw.println();
+ pw.println();
+
+ pw.println("Aggregated vibration history:");
+ pw.increaseIndent();
+ mAggregatedVibrationHistory.dump(pw);
+ pw.decreaseIndent();
+ }
+
+ synchronized void dump(ProtoOutputStream proto) {
+ mRecentVibrations.dump(proto);
+ }
+ }
+
+ /** Keep records of vibrations played and provide debug information for this service. */
+ private static final class VibrationRecords {
+ private final SparseArray<LinkedList<AggregatedVibrationRecord>> mVibrations =
+ new SparseArray<>();
+ private final int mSizeLimit;
+ private final int mAggregationTimeLimit;
+
+ VibrationRecords(int sizeLimit, int aggregationTimeLimit) {
+ mSizeLimit = sizeLimit;
+ mAggregationTimeLimit = aggregationTimeLimit;
+ }
+
+ synchronized AggregatedVibrationRecord record(Vibration.DebugInfo info) {
+ int usage = info.mCallerInfo.attrs.getUsage();
+ if (!mVibrations.contains(usage)) {
+ mVibrations.put(usage, new LinkedList<>());
+ }
+ LinkedList<AggregatedVibrationRecord> records = mVibrations.get(usage);
+ if (mAggregationTimeLimit > 0 && !records.isEmpty()) {
+ AggregatedVibrationRecord lastRecord = records.getLast();
+ if (lastRecord.mayAggregate(info, mAggregationTimeLimit)) {
+ lastRecord.record(info);
+ return null;
}
}
+ AggregatedVibrationRecord removedRecord = null;
+ if (records.size() > mSizeLimit) {
+ removedRecord = records.removeFirst();
+ }
+ records.addLast(new AggregatedVibrationRecord(info));
+ return removedRecord;
+ }
- pw.println();
- pw.println(" Previous external vibrations:");
- for (Vibration.DebugInfo info : mPreviousExternalVibrations) {
- pw.println(" " + info);
+ synchronized void dump(IndentingPrintWriter pw) {
+ for (int i = 0; i < mVibrations.size(); i++) {
+ pw.println(VibrationAttributes.usageToString(mVibrations.keyAt(i)) + ":");
+ pw.increaseIndent();
+ for (AggregatedVibrationRecord info : mVibrations.valueAt(i)) {
+ info.dump(pw);
+ }
+ pw.decreaseIndent();
+ pw.println();
}
}
- synchronized void dumpProto(ProtoOutputStream proto) {
- for (int i = 0; i < mPreviousVibrations.size(); i++) {
+ synchronized void dump(ProtoOutputStream proto) {
+ for (int i = 0; i < mVibrations.size(); i++) {
long fieldId;
- switch (mPreviousVibrations.keyAt(i)) {
+ switch (mVibrations.keyAt(i)) {
case VibrationAttributes.USAGE_RINGTONE:
fieldId = VibratorManagerServiceDumpProto.PREVIOUS_RING_VIBRATIONS;
break;
case VibrationAttributes.USAGE_NOTIFICATION:
- fieldId = VibratorManagerServiceDumpProto
- .PREVIOUS_NOTIFICATION_VIBRATIONS;
+ fieldId = VibratorManagerServiceDumpProto.PREVIOUS_NOTIFICATION_VIBRATIONS;
break;
case VibrationAttributes.USAGE_ALARM:
fieldId = VibratorManagerServiceDumpProto.PREVIOUS_ALARM_VIBRATIONS;
@@ -1646,18 +1720,70 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
default:
fieldId = VibratorManagerServiceDumpProto.PREVIOUS_VIBRATIONS;
}
- for (Vibration.DebugInfo info : mPreviousVibrations.valueAt(i)) {
- info.dumpProto(proto, fieldId);
+ for (AggregatedVibrationRecord info : mVibrations.valueAt(i)) {
+ if (info.mLatestVibration.mPlayedEffect == null) {
+ // External vibrations are reported separately in the dump proto
+ info.dump(proto,
+ VibratorManagerServiceDumpProto.PREVIOUS_EXTERNAL_VIBRATIONS);
+ } else {
+ info.dump(proto, fieldId);
+ }
}
}
+ }
- for (Vibration.DebugInfo info : mPreviousExternalVibrations) {
- info.dumpProto(proto,
- VibratorManagerServiceDumpProto.PREVIOUS_EXTERNAL_VIBRATIONS);
+ synchronized void dumpOnSingleField(ProtoOutputStream proto, long fieldId) {
+ for (int i = 0; i < mVibrations.size(); i++) {
+ for (AggregatedVibrationRecord info : mVibrations.valueAt(i)) {
+ info.dump(proto, fieldId);
+ }
}
}
}
+ /**
+ * Record that keeps the last {@link Vibration.DebugInfo} played, aggregating close vibrations
+ * from the same uid that have the same {@link VibrationAttributes} and {@link VibrationEffect}.
+ */
+ private static final class AggregatedVibrationRecord {
+ private final Vibration.DebugInfo mFirstVibration;
+ private Vibration.DebugInfo mLatestVibration;
+ private int mVibrationCount;
+
+ AggregatedVibrationRecord(Vibration.DebugInfo info) {
+ mLatestVibration = mFirstVibration = info;
+ mVibrationCount = 1;
+ }
+
+ synchronized boolean mayAggregate(Vibration.DebugInfo info, long timeLimit) {
+ return Objects.equals(mLatestVibration.mCallerInfo.uid, info.mCallerInfo.uid)
+ && Objects.equals(mLatestVibration.mCallerInfo.attrs, info.mCallerInfo.attrs)
+ && Objects.equals(mLatestVibration.mPlayedEffect, info.mPlayedEffect)
+ && Math.abs(mLatestVibration.mCreateTime - info.mCreateTime) < timeLimit;
+ }
+
+ synchronized void record(Vibration.DebugInfo vib) {
+ mLatestVibration = vib;
+ mVibrationCount++;
+ }
+
+ synchronized void dump(IndentingPrintWriter pw) {
+ mFirstVibration.dumpCompact(pw);
+ if (mVibrationCount == 1) {
+ return;
+ }
+ if (mVibrationCount > 2) {
+ pw.println(
+ "-> Skipping " + (mVibrationCount - 2) + " aggregated vibrations, latest:");
+ }
+ mLatestVibration.dumpCompact(pw);
+ }
+
+ synchronized void dump(ProtoOutputStream proto, long fieldId) {
+ mLatestVibration.dump(proto, fieldId);
+ }
+ }
+
/** Clears mNextVibration if set, ending it cleanly */
@GuardedBy("mLock")
private void clearNextVibrationLocked(Vibration.EndInfo vibrationEndInfo) {
@@ -1676,7 +1802,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
/**
* Ends the external vibration, and clears related service state.
*
- * @param vibrationEndInfo the status and related info to end the associated Vibration with
+ * @param vibrationEndInfo the status and related info to end the associated Vibration
* @param continueExternalControl indicates whether external control will continue. If not, the
* HAL will have external control turned off.
*/