diff options
| author | 2023-07-26 18:09:39 +0100 | |
|---|---|---|
| committer | 2023-07-31 10:28:03 +0100 | |
| commit | 7b41ed78d140db2c7feb0230dec30941a23ebddc (patch) | |
| tree | eb06fd3369e8eda7495821392ec02cd1e3e8dffe | |
| parent | 19dae7cd516fade02b202ad1c25cd797bf0662ea (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
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. */ |