diff options
5 files changed, 429 insertions, 19 deletions
diff --git a/core/java/android/os/VibrationAttributes.java b/core/java/android/os/VibrationAttributes.java index 2093077dbfdf..217f1785bcd6 100644 --- a/core/java/android/os/VibrationAttributes.java +++ b/core/java/android/os/VibrationAttributes.java @@ -268,7 +268,7 @@ public final class VibrationAttributes implements Parcelable { } /** @hide */ - public String usageToString(int usage) { + public static String usageToString(int usage) { switch (usage) { case USAGE_UNKNOWN: return "UNKNOWN"; diff --git a/services/core/java/com/android/server/VibratorManagerService.java b/services/core/java/com/android/server/VibratorManagerService.java index 1738c971afa2..6a816af68959 100644 --- a/services/core/java/com/android/server/VibratorManagerService.java +++ b/services/core/java/com/android/server/VibratorManagerService.java @@ -45,10 +45,12 @@ import android.os.Vibrator; import android.os.VibratorInfo; import android.util.Slog; import android.util.SparseArray; +import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.IBatteryStats; +import com.android.internal.util.DumpUtils; import com.android.server.vibrator.InputDeviceDelegate; import com.android.server.vibrator.Vibration; import com.android.server.vibrator.VibrationScaler; @@ -61,7 +63,9 @@ import libcore.util.NativeAllocationRegistry; import java.io.FileDescriptor; import java.io.PrintWriter; import java.lang.ref.WeakReference; +import java.util.ArrayList; import java.util.Arrays; +import java.util.LinkedList; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; @@ -106,6 +110,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { private final Handler mHandler; private final AppOpsManager mAppOps; private final NativeWrapper mNativeWrapper; + private final VibratorManagerRecords mVibratorManagerRecords; private final int[] mVibratorIds; private final SparseArray<VibratorController> mVibrators; private final VibrationCallbacks mVibrationCallbacks = new VibrationCallbacks(); @@ -135,6 +140,10 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { mNativeWrapper = injector.getNativeWrapper(); mNativeWrapper.init(); + int dumpLimit = mContext.getResources().getInteger( + com.android.internal.R.integer.config_previousVibrationsDumpLimit); + mVibratorManagerRecords = new VibratorManagerRecords(dumpLimit); + mBatteryStatsService = IBatteryStats.Stub.asInterface(ServiceManager.getService( BatteryStats.SERVICE_NAME)); @@ -340,7 +349,8 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { mCurrentVibration.cancel(); } if (mCurrentExternalVibration != null) { - // TODO(b/167946816): end vibration and add to list to be dumped for debug + mCurrentExternalVibration.end(Vibration.Status.CANCELLED); + mVibratorManagerRecords.record(mCurrentExternalVibration); mCurrentExternalVibration.externalVibration.mute(); mCurrentExternalVibration = null; // TODO(b/167946816): set external control to false @@ -355,6 +365,29 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { } @Override + protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return; + + final long ident = Binder.clearCallingIdentity(); + + boolean isDumpProto = false; + for (String arg : args) { + if (arg.equals("--proto")) { + isDumpProto = true; + } + } + try { + if (isDumpProto) { + mVibratorManagerRecords.dumpProto(fd); + } else { + mVibratorManagerRecords.dumpText(pw); + } + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + @Override public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err, String[] args, ShellCallback cb, ResultReceiver resultReceiver) { new VibratorManagerShellCommand(this).exec(this, in, out, err, args, cb, resultReceiver); @@ -451,7 +484,8 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { @GuardedBy("mLock") private void endVibrationLocked(Vibration vib, Vibration.Status status) { - // TODO(b/167946816): end vibration and add to list to be dumped for debug + vib.end(status); + mVibratorManagerRecords.record(vib); } @GuardedBy("mLock") @@ -936,8 +970,158 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { } } + /** Keep records of vibrations played and provide debug information for this service. */ + private final class VibratorManagerRecords { + @GuardedBy("mLock") + private final SparseArray<LinkedList<Vibration.DebugInfo>> mPreviousVibrations = + new SparseArray<>(); + @GuardedBy("mLock") + private final LinkedList<Vibration.DebugInfo> mPreviousExternalVibrations = + new LinkedList<>(); + private final int mPreviousVibrationsLimit; + + private VibratorManagerRecords(int limit) { + mPreviousVibrationsLimit = limit; + } + + @GuardedBy("mLock") + void record(Vibration vib) { + int usage = vib.attrs.getUsage(); + if (!mPreviousVibrations.contains(usage)) { + mPreviousVibrations.put(usage, new LinkedList<>()); + } + record(mPreviousVibrations.get(usage), vib.getDebugInfo()); + } + + @GuardedBy("mLock") + void record(ExternalVibrationHolder vib) { + record(mPreviousExternalVibrations, vib.getDebugInfo()); + } + + @GuardedBy("mLock") + void record(LinkedList<Vibration.DebugInfo> records, Vibration.DebugInfo info) { + if (records.size() > mPreviousVibrationsLimit) { + records.removeFirst(); + } + records.addLast(info); + } + + void dumpText(PrintWriter pw) { + pw.println("Vibrator Manager Service:"); + synchronized (mLock) { + pw.println(" mVibratorControllers:"); + for (int i = 0; i < mVibrators.size(); i++) { + pw.println(" " + mVibrators.valueAt(i)); + } + pw.println(); + pw.println(" mCurrentVibration:"); + pw.println(" " + mCurrentVibration == null + ? null : mCurrentVibration.getVibration().getDebugInfo()); + pw.println(" mNextVibration:"); + pw.println(" " + mNextVibration == null + ? null : mNextVibration.getVibration().getDebugInfo()); + pw.println(" mCurrentExternalVibration:"); + pw.println(" " + mCurrentExternalVibration == null + ? null : mCurrentExternalVibration.getDebugInfo()); + pw.println(); + pw.println(" mVibrationSettings=" + mVibrationSettings); + 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); + } + } + + pw.println(" Previous external vibrations:"); + for (Vibration.DebugInfo info : mPreviousExternalVibrations) { + pw.println(" " + info); + } + } + } + + synchronized void dumpProto(FileDescriptor fd) { + final ProtoOutputStream proto = new ProtoOutputStream(fd); + + synchronized (mLock) { + mVibrationSettings.dumpProto(proto); + if (mCurrentVibration != null) { + mCurrentVibration.getVibration().getDebugInfo().dumpProto(proto, + VibratorServiceDumpProto.CURRENT_VIBRATION); + } + if (mCurrentExternalVibration != null) { + mCurrentExternalVibration.getDebugInfo().dumpProto(proto, + VibratorServiceDumpProto.CURRENT_EXTERNAL_VIBRATION); + } + + boolean isVibrating = false; + boolean isUnderExternalControl = false; + for (int i = 0; i < mVibrators.size(); i++) { + isVibrating |= mVibrators.valueAt(i).isVibrating(); + isUnderExternalControl |= mVibrators.valueAt(i).isUnderExternalControl(); + } + proto.write(VibratorServiceDumpProto.IS_VIBRATING, isVibrating); + proto.write(VibratorServiceDumpProto.VIBRATOR_UNDER_EXTERNAL_CONTROL, + isUnderExternalControl); + + for (Vibration.DebugInfo info : mPreviousVibrations.get( + VibrationAttributes.USAGE_RINGTONE)) { + info.dumpProto(proto, VibratorServiceDumpProto.PREVIOUS_RING_VIBRATIONS); + } + + for (Vibration.DebugInfo info : mPreviousVibrations.get( + VibrationAttributes.USAGE_NOTIFICATION)) { + info.dumpProto(proto, + VibratorServiceDumpProto.PREVIOUS_NOTIFICATION_VIBRATIONS); + } + + for (Vibration.DebugInfo info : mPreviousVibrations.get( + VibrationAttributes.USAGE_ALARM)) { + info.dumpProto(proto, VibratorServiceDumpProto.PREVIOUS_ALARM_VIBRATIONS); + } + + for (Vibration.DebugInfo info : mPreviousVibrations.get( + VibrationAttributes.USAGE_UNKNOWN)) { + info.dumpProto(proto, VibratorServiceDumpProto.PREVIOUS_VIBRATIONS); + } + + for (Vibration.DebugInfo info : mPreviousExternalVibrations) { + info.dumpProto(proto, VibratorServiceDumpProto.PREVIOUS_EXTERNAL_VIBRATIONS); + } + } + proto.flush(); + } + } + /** Provide limited functionality from {@link VibratorManagerService} as shell commands. */ private final class VibratorManagerShellCommand extends ShellCommand { + public static final String SHELL_PACKAGE_NAME = "com.android.shell"; + + private final class CommonOptions { + public boolean force = false; + public String description = "Shell command"; + + CommonOptions() { + String nextArg; + while ((nextArg = peekNextArg()) != null) { + switch (nextArg) { + case "-f": + getNextArgRequired(); // consume the -f argument; + force = true; + break; + case "-d": + getNextArgRequired(); // consume the -d argument; + description = getNextArgRequired(); + break; + default: + // Not a common option, finish reading. + return; + } + } + } + } private final IBinder mToken; @@ -947,10 +1131,27 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { @Override public int onCommand(String cmd) { - if ("list".equals(cmd)) { - return runListVibrators(); + Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "onCommand " + cmd); + try { + if ("list".equals(cmd)) { + return runListVibrators(); + } + if ("synced".equals(cmd)) { + return runMono(); + } + if ("combined".equals(cmd)) { + return runStereo(); + } + if ("sequential".equals(cmd)) { + return runSequential(); + } + if ("cancel".equals(cmd)) { + return runCancel(); + } + return handleDefaultCommands(cmd); + } finally { + Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR); } - return handleDefaultCommands(cmd); } private int runListVibrators() { @@ -967,6 +1168,157 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { } } + private int runMono() { + CommonOptions commonOptions = new CommonOptions(); + VibrationEffect effect = nextEffect(); + if (effect == null) { + return 0; + } + + CombinedVibrationEffect combinedEffect = CombinedVibrationEffect.createSynced(effect); + VibrationAttributes attrs = createVibrationAttributes(commonOptions); + vibrate(Binder.getCallingUid(), SHELL_PACKAGE_NAME, combinedEffect, attrs, + commonOptions.description, mToken); + return 0; + } + + private int runStereo() { + CommonOptions commonOptions = new CommonOptions(); + CombinedVibrationEffect.SyncedCombination combination = + CombinedVibrationEffect.startSynced(); + while ("-v".equals(getNextOption())) { + int vibratorId = Integer.parseInt(getNextArgRequired()); + VibrationEffect effect = nextEffect(); + if (effect != null) { + combination.addVibrator(vibratorId, effect); + } + } + VibrationAttributes attrs = createVibrationAttributes(commonOptions); + vibrate(Binder.getCallingUid(), SHELL_PACKAGE_NAME, combination.combine(), attrs, + commonOptions.description, mToken); + return 0; + } + + private int runSequential() { + CommonOptions commonOptions = new CommonOptions(); + + CombinedVibrationEffect.SequentialCombination combination = + CombinedVibrationEffect.startSequential(); + while ("-v".equals(getNextOption())) { + int vibratorId = Integer.parseInt(getNextArgRequired()); + int delay = 0; + if ("-w".equals(getNextOption())) { + delay = Integer.parseInt(getNextArgRequired()); + } + VibrationEffect effect = nextEffect(); + if (effect != null) { + combination.addNext(vibratorId, effect, delay); + } + } + VibrationAttributes attrs = createVibrationAttributes(commonOptions); + vibrate(Binder.getCallingUid(), SHELL_PACKAGE_NAME, combination.combine(), attrs, + commonOptions.description, mToken); + return 0; + } + + private int runCancel() { + cancelVibrate(mToken); + return 0; + } + + @Nullable + private VibrationEffect nextEffect() { + String effectType = getNextArgRequired(); + if ("oneshot".equals(effectType)) { + return nextOneShot(); + } + if ("waveform".equals(effectType)) { + return nextWaveform(); + } + if ("prebaked".equals(effectType)) { + return nextPrebaked(); + } + if ("composed".equals(effectType)) { + return nextComposed(); + } + return null; + } + + private VibrationEffect nextOneShot() { + boolean hasAmplitude = "-a".equals(getNextOption()); + long duration = Long.parseLong(getNextArgRequired()); + int amplitude = hasAmplitude ? Integer.parseInt(getNextArgRequired()) + : VibrationEffect.DEFAULT_AMPLITUDE; + return VibrationEffect.createOneShot(duration, amplitude); + } + + private VibrationEffect nextWaveform() { + boolean hasAmplitudes = false; + int repeat = -1; + + String nextOption = getNextOption(); + while (nextOption != null) { + if ("-a".equals(nextOption)) { + hasAmplitudes = true; + } else if ("-r".equals(nextOption)) { + repeat = Integer.parseInt(getNextArgRequired()); + } + nextOption = getNextOption(); + } + List<Long> durations = new ArrayList<>(); + List<Integer> amplitudes = new ArrayList<>(); + + String nextArg; + while ((nextArg = peekNextArg()) != null && !"-v".equals(nextArg)) { + durations.add(Long.parseLong(getNextArgRequired())); + if (hasAmplitudes) { + amplitudes.add(Integer.parseInt(getNextArgRequired())); + } + } + + long[] durationArray = durations.stream().mapToLong(Long::longValue).toArray(); + if (!hasAmplitudes) { + return VibrationEffect.createWaveform(durationArray, repeat); + } + + int[] amplitudeArray = amplitudes.stream().mapToInt(Integer::intValue).toArray(); + return VibrationEffect.createWaveform(durationArray, amplitudeArray, repeat); + } + + private VibrationEffect nextPrebaked() { + boolean shouldFallback = "-b".equals(getNextOption()); + int effectId = Integer.parseInt(getNextArgRequired()); + return VibrationEffect.get(effectId, shouldFallback); + } + + private VibrationEffect nextComposed() { + VibrationEffect.Composition composition = VibrationEffect.startComposition(); + String nextArg; + while ((nextArg = peekNextArg()) != null) { + int delay = 0; + if ("-w".equals(nextArg)) { + getNextArgRequired(); // consume the -w option + delay = Integer.parseInt(getNextArgRequired()); + } else if ("-v".equals(nextArg)) { + // Starting next vibrator, this composed effect if finished. + break; + } + int primitiveId = Integer.parseInt(getNextArgRequired()); + composition.addPrimitive(primitiveId, /* scale= */ 1f, delay); + } + return composition.compose(); + } + + private VibrationAttributes createVibrationAttributes(CommonOptions commonOptions) { + final int flags = + commonOptions.force ? VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY : 0; + return new VibrationAttributes.Builder() + .setFlags(flags, VibrationAttributes.FLAG_ALL_SUPPORTED) + // Used to apply Settings.System.HAPTIC_FEEDBACK_INTENSITY to scale effects. + .setUsage(VibrationAttributes.USAGE_TOUCH) + .build(); + } + @Override public void onHelp() { try (PrintWriter pw = getOutPrintWriter();) { @@ -977,6 +1329,49 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { pw.println(" list"); pw.println(" Prints the id of device vibrators. This does not include any "); pw.println(" connected input device."); + pw.println(" synced [options] <effect>"); + pw.println(" Vibrates effect on all vibrators in sync."); + pw.println(" combined [options] (-v <vibrator-id> <effect>)..."); + pw.println(" Vibrates different effects on each vibrator in sync."); + pw.println(" sequential [options] (-v <vibrator-id> [-w <delay>] <effect>)..."); + pw.println(" Vibrates different effects on each vibrator in sequence."); + pw.println(" cancel"); + pw.println(" Cancels any active vibration"); + pw.println(""); + pw.println("Effect commands:"); + pw.println(" oneshot [-a] <duration> [<amplitude>]"); + pw.println(" Vibrates for duration milliseconds; ignored when device is on "); + pw.println(" DND (Do Not Disturb) mode; touch feedback strength user setting "); + pw.println(" will be used to scale amplitude."); + pw.println(" If -a is provided, the command accepts a second argument for "); + pw.println(" amplitude, in a scale of 1-255."); + pw.println(" waveform [-r <index>] [-a] (<duration> [<amplitude>])..."); + pw.println(" Vibrates for durations and amplitudes in list; ignored when "); + pw.println(" device is on DND (Do Not Disturb) mode; touch feedback strength "); + pw.println(" user setting will be used to scale amplitude."); + pw.println(" If -r is provided, the waveform loops back to the specified"); + pw.println(" index (e.g. 0 loops from the beginning)"); + pw.println(" If -a is provided, the command accepts duration-amplitude pairs;"); + pw.println(" otherwise, it accepts durations only and alternates off/on"); + pw.println(" Duration is in milliseconds; amplitude is a scale of 1-255."); + pw.println(" prebaked [-b] <effect-id>"); + pw.println(" Vibrates with prebaked effect; ignored when device is on DND "); + pw.println(" (Do Not Disturb) mode; touch feedback strength user setting "); + pw.println(" will be used to scale amplitude."); + pw.println(" If -b is provided, the prebaked fallback effect will be played if"); + pw.println(" the device doesn't support the given effect-id."); + pw.println(" composed [-w <delay>] <primitive-id>..."); + pw.println(" Vibrates with a composed effect; ignored when device is on DND "); + pw.println(" (Do Not Disturb) mode; touch feedback strength user setting "); + pw.println(" will be used to scale primitive intensities."); + pw.println(" If -w is provided, the next primitive will be played after the "); + pw.println(" specified wait time in milliseconds."); + pw.println(""); + pw.println("Common Options:"); + pw.println(" -f"); + pw.println(" Force. Ignore Do Not Disturb setting."); + pw.println(" -d <description>"); + pw.println(" Add description to the vibration."); pw.println(""); } } diff --git a/services/core/java/com/android/server/VibratorService.java b/services/core/java/com/android/server/VibratorService.java index 97e313e13f6d..026eb630d59a 100644 --- a/services/core/java/com/android/server/VibratorService.java +++ b/services/core/java/com/android/server/VibratorService.java @@ -772,18 +772,7 @@ public class VibratorService extends IVibratorService.Stub { proto.write(VibratorServiceDumpProto.IS_VIBRATING, mVibratorController.isVibrating()); proto.write(VibratorServiceDumpProto.VIBRATOR_UNDER_EXTERNAL_CONTROL, mVibratorController.isUnderExternalControl()); - proto.write(VibratorServiceDumpProto.HAPTIC_FEEDBACK_INTENSITY, - mVibrationSettings.getCurrentIntensity(VibrationAttributes.USAGE_TOUCH)); - proto.write(VibratorServiceDumpProto.HAPTIC_FEEDBACK_DEFAULT_INTENSITY, - mVibrationSettings.getDefaultIntensity(VibrationAttributes.USAGE_TOUCH)); - proto.write(VibratorServiceDumpProto.NOTIFICATION_INTENSITY, - mVibrationSettings.getCurrentIntensity(VibrationAttributes.USAGE_NOTIFICATION)); - proto.write(VibratorServiceDumpProto.NOTIFICATION_DEFAULT_INTENSITY, - mVibrationSettings.getDefaultIntensity(VibrationAttributes.USAGE_NOTIFICATION)); - proto.write(VibratorServiceDumpProto.RING_INTENSITY, - mVibrationSettings.getCurrentIntensity(VibrationAttributes.USAGE_RINGTONE)); - proto.write(VibratorServiceDumpProto.RING_DEFAULT_INTENSITY, - mVibrationSettings.getDefaultIntensity(VibrationAttributes.USAGE_RINGTONE)); + mVibrationSettings.dumpProto(proto); for (Vibration.DebugInfo info : mPreviousRingVibrations) { info.dumpProto(proto, VibratorServiceDumpProto.PREVIOUS_RING_VIBRATIONS); diff --git a/services/core/java/com/android/server/vibrator/VibrationSettings.java b/services/core/java/com/android/server/vibrator/VibrationSettings.java index 536375f1311d..8910bdfa1c05 100644 --- a/services/core/java/com/android/server/vibrator/VibrationSettings.java +++ b/services/core/java/com/android/server/vibrator/VibrationSettings.java @@ -34,10 +34,12 @@ import android.os.VibrationEffect; import android.os.Vibrator; import android.provider.Settings; import android.util.SparseArray; +import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.server.LocalServices; +import com.android.server.VibratorServiceDumpProto; import java.util.ArrayList; import java.util.List; @@ -340,6 +342,24 @@ public final class VibrationSettings { + '}'; } + /** Write current settings into given {@link ProtoOutputStream}. */ + public void dumpProto(ProtoOutputStream proto) { + synchronized (mLock) { + proto.write(VibratorServiceDumpProto.HAPTIC_FEEDBACK_INTENSITY, + mHapticFeedbackIntensity); + proto.write(VibratorServiceDumpProto.HAPTIC_FEEDBACK_DEFAULT_INTENSITY, + mVibrator.getDefaultHapticFeedbackIntensity()); + proto.write(VibratorServiceDumpProto.NOTIFICATION_INTENSITY, + mNotificationIntensity); + proto.write(VibratorServiceDumpProto.NOTIFICATION_DEFAULT_INTENSITY, + mVibrator.getDefaultNotificationVibrationIntensity()); + proto.write(VibratorServiceDumpProto.RING_INTENSITY, + mRingIntensity); + proto.write(VibratorServiceDumpProto.RING_DEFAULT_INTENSITY, + mVibrator.getDefaultRingVibrationIntensity()); + } + } + private void notifyListeners() { List<OnVibratorSettingsChanged> currentListeners; synchronized (mLock) { diff --git a/services/core/java/com/android/server/vibrator/VibrationThread.java b/services/core/java/com/android/server/vibrator/VibrationThread.java index 6f391f3bbc92..4f2fc86df06c 100644 --- a/services/core/java/com/android/server/vibrator/VibrationThread.java +++ b/services/core/java/com/android/server/vibrator/VibrationThread.java @@ -116,6 +116,9 @@ public final class VibrationThread extends Thread implements IBinder.DeathRecipi @Override public void binderDied() { + if (DEBUG) { + Slog.d(TAG, "Binder died, cancelling vibration..."); + } cancel(); } @@ -146,6 +149,9 @@ public final class VibrationThread extends Thread implements IBinder.DeathRecipi /** Notify current vibration that a step has completed on given vibrator. */ public void vibratorComplete(int vibratorId) { synchronized (mLock) { + if (DEBUG) { + Slog.d(TAG, "Vibration complete reported by vibrator " + vibratorId); + } if (mCurrentVibrateStep != null) { mCurrentVibrateStep.vibratorComplete(vibratorId); } @@ -467,7 +473,7 @@ public final class VibrationThread extends Thread implements IBinder.DeathRecipi noteVibratorOff(); } if (DEBUG) { - Slog.d(TAG, "SingleVibrateStep step done."); + Slog.d(TAG, "SingleVibrateStep done."); } Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR); } |