Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 7e396362 authored by Simon Bowden's avatar Simon Bowden
Browse files

Changes the vibrator_manager shell commands to block until the end of

the vibration by default, and adds a -B option to give the old behaviour
of just initiating the vibration.

This has the side effect of cancelling the vibration if the adb command
is cancelled, which is useful default behavior for repeating vibrations.

The change also introduces slightly more robust cancellation of
mNextVibration, which may previously have missed some dropped vibrations
- these are now recorded with a new status of IGNORED_SUPERSEDED.

Change-Id: Ibf0eb72a71a6793370317f4e14210f5899d5f5c9
Test: manual adb commands
Bug: 127483176
parent 6c9a015d
Loading
Loading
Loading
Loading
+11 −0
Original line number Diff line number Diff line
@@ -33,6 +33,7 @@ import android.util.proto.ProtoOutputStream;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.function.Function;

/** Represents a vibration request to the vibrator service. */
@@ -58,6 +59,7 @@ final class Vibration {
        IGNORED_FOR_ONGOING,
        IGNORED_FOR_POWER,
        IGNORED_FOR_SETTINGS,
        IGNORED_SUPERSEDED,
    }

    /** Start time in CLOCK_BOOTTIME base. */
@@ -90,6 +92,9 @@ final class Vibration {
    private long mEndTimeDebug;
    private Status mStatus;

    /** A {@link CountDownLatch} to enable waiting for completion. */
    private final CountDownLatch mCompletionLatch = new CountDownLatch(1);

    Vibration(IBinder token, int id, CombinedVibration effect,
            VibrationAttributes attrs, int uid, String opPkg, String reason) {
        this.token = token;
@@ -118,6 +123,12 @@ final class Vibration {
        }
        mStatus = status;
        mEndTimeDebug = System.currentTimeMillis();
        mCompletionLatch.countDown();
    }

    /** Waits indefinitely until another thread calls {@link #end(Status)} on this vibration. */
    public void waitForEnd() throws InterruptedException {
        mCompletionLatch.await();
    }

    /**
+73 −25
Original line number Diff line number Diff line
@@ -144,9 +144,12 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
                    // them.  However it may happen that the system is currently playing
                    // haptic feedback as part of the transition.  So we don't cancel
                    // system vibrations.
                    if (mNextVibration != null
                            && !isSystemHapticFeedback(mNextVibration.getVibration())) {
                        clearNextVibrationLocked(Vibration.Status.CANCELLED);
                    }
                    if (mCurrentVibration != null
                            && !isSystemHapticFeedback(mCurrentVibration.getVibration())) {
                        mNextVibration = null;
                        mCurrentVibration.cancel();
                    }
                }
@@ -336,17 +339,27 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
    @Override // Binder call
    public void vibrate(int uid, String opPkg, @NonNull CombinedVibration effect,
            @Nullable VibrationAttributes attrs, String reason, IBinder token) {
        vibrateInternal(uid, opPkg, effect, attrs, reason, token);
    }

    /**
     * An internal-only version of vibrate that allows the caller access to the {@link Vibration}.
     * The Vibration is only returned if it is ongoing after this method returns.
     */
    @Nullable
    private Vibration vibrateInternal(int uid, String opPkg, @NonNull CombinedVibration effect,
            @Nullable VibrationAttributes attrs, String reason, IBinder token) {
        Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "vibrate, reason = " + reason);
        try {
            mContext.enforceCallingOrSelfPermission(android.Manifest.permission.VIBRATE, "vibrate");

            if (token == null) {
                Slog.e(TAG, "token must not be null");
                return;
                return null;
            }
            enforceUpdateAppOpsStatsPermission(uid);
            if (!isEffectValid(effect)) {
                return;
                return null;
            }
            attrs = fixupVibrationAttributes(attrs);
            Vibration vib = new Vibration(token, mNextVibrationId.getAndIncrement(), effect, attrs,
@@ -357,13 +370,13 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
                Vibration.Status ignoreStatus = shouldIgnoreVibrationLocked(vib);
                if (ignoreStatus != null) {
                    endVibrationLocked(vib, ignoreStatus);
                    return;
                    return vib;
                }

                ignoreStatus = shouldIgnoreVibrationForCurrentLocked(vib);
                if (ignoreStatus != null) {
                    endVibrationLocked(vib, ignoreStatus);
                    return;
                    return vib;
                }

                final long ident = Binder.clearCallingIdentity();
@@ -375,6 +388,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
                    if (status != Vibration.Status.RUNNING) {
                        endVibrationLocked(vib, status);
                    }
                    return vib;
                } finally {
                    Binder.restoreCallingIdentity(ident);
                }
@@ -401,7 +415,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
                    if (mNextVibration != null
                            && shouldCancelVibration(mNextVibration.getVibration(),
                            usageFilter, token)) {
                        mNextVibration = null;
                        clearNextVibrationLocked(Vibration.Status.CANCELLED);
                    }
                    if (mCurrentVibration != null
                            && shouldCancelVibration(mCurrentVibration.getVibration(),
@@ -453,7 +467,8 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
    @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);
        new VibratorManagerShellCommand(cb.getShellCallbackBinder())
                .exec(this, in, out, err, args, cb, resultReceiver);
    }

    @VisibleForTesting
@@ -521,7 +536,9 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
            if (mCurrentVibration == null) {
                return startVibrationThreadLocked(vibThread);
            }

            // If there's already a vibration queued (waiting for the previous one to finish
            // cancelling), end it cleanly and replace it with the new one.
            clearNextVibrationLocked(Vibration.Status.IGNORED_SUPERSEDED);
            mNextVibration = vibThread;
            return Vibration.Status.RUNNING;
        } finally {
@@ -1300,6 +1317,14 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
        }
    }

    /** Clears mNextVibration if set, ending it cleanly */
    private void clearNextVibrationLocked(Vibration.Status endStatus) {
        if (mNextVibration != null) {
            endVibrationLocked(mNextVibration.getVibration(), endStatus);
            mNextVibration = null;
        }
    }

    /** Implementation of {@link IExternalVibratorService} to be triggered on external control. */
    @VisibleForTesting
    final class ExternalVibratorService extends IExternalVibratorService.Stub {
@@ -1347,7 +1372,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
                    // If we're not under external control right now, then cancel any normal
                    // vibration that may be playing and ready the vibrator for external control.
                    if (mCurrentVibration != null) {
                        mNextVibration = null;
                        clearNextVibrationLocked(Vibration.Status.IGNORED_FOR_EXTERNAL);
                        mCurrentVibration.cancelImmediately();
                        cancelingVibration = mCurrentVibration;
                    }
@@ -1454,6 +1479,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
        private final class CommonOptions {
            public boolean force = false;
            public String description = "Shell command";
            public boolean background = false;

            CommonOptions() {
                String nextArg;
@@ -1463,6 +1489,10 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
                            getNextArgRequired(); // consume "-f"
                            force = true;
                            break;
                        case "-B":
                            getNextArgRequired(); // consume "-B"
                            background = true;
                            break;
                        case "-d":
                            getNextArgRequired(); // consume "-d"
                            description = getNextArgRequired();
@@ -1475,10 +1505,10 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
            }
        }

        private final IBinder mToken;
        private final IBinder mShellCallbacksToken;

        private VibratorManagerShellCommand(IBinder token) {
            mToken = token;
        private VibratorManagerShellCommand(IBinder shellCallbacksToken) {
            mShellCallbacksToken = shellCallbacksToken;
        }

        @Override
@@ -1520,12 +1550,28 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
            }
        }

        private int runMono() {
            CommonOptions commonOptions = new CommonOptions();
            CombinedVibration effect = CombinedVibration.createParallel(nextEffect());
        /**
         * Runs a CombinedVibration using the configured common options and attributes.
         */
        private void runVibrate(CommonOptions commonOptions, CombinedVibration combined) {
            VibrationAttributes attrs = createVibrationAttributes(commonOptions);
            vibrate(Binder.getCallingUid(), SHELL_PACKAGE_NAME, effect, attrs,
                    commonOptions.description, mToken);
            // If running in the background, bind to death of the server binder rather than the
            // client, and the cancel command likewise uses the server binder reference to
            // only cancel background vibrations.
            IBinder deathBinder = commonOptions.background ? VibratorManagerService.this
                    : mShellCallbacksToken;
            Vibration vib = vibrateInternal(Binder.getCallingUid(), SHELL_PACKAGE_NAME, combined,
                    attrs, commonOptions.description, deathBinder);
            if (vib != null && !commonOptions.background) {
                try {
                    vib.waitForEnd();
                } catch (InterruptedException e) {
                }
            }
        }

        private int runMono() {
            runVibrate(new CommonOptions(), CombinedVibration.createParallel(nextEffect()));
            return 0;
        }

@@ -1537,9 +1583,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
                int vibratorId = Integer.parseInt(getNextArgRequired());
                combination.addVibrator(vibratorId, nextEffect());
            }
            VibrationAttributes attrs = createVibrationAttributes(commonOptions);
            vibrate(Binder.getCallingUid(), SHELL_PACKAGE_NAME, combination.combine(), attrs,
                    commonOptions.description, mToken);
            runVibrate(commonOptions, combination.combine());
            return 0;
        }

@@ -1551,14 +1595,15 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
                int vibratorId = Integer.parseInt(getNextArgRequired());
                combination.addNext(vibratorId, nextEffect());
            }
            VibrationAttributes attrs = createVibrationAttributes(commonOptions);
            vibrate(Binder.getCallingUid(), SHELL_PACKAGE_NAME, combination.combine(), attrs,
                    commonOptions.description, mToken);
            runVibrate(commonOptions, combination.combine());
            return 0;
        }

        private int runCancel() {
            cancelVibrate(VibrationAttributes.USAGE_FILTER_MATCH_ALL, mToken);
            // Cancel is only needed if the vibration was run in the background, otherwise it's
            // terminated by the shell command ending. In these cases, the token was that of the
            // service rather than the client.
            cancelVibrate(VibrationAttributes.USAGE_FILTER_MATCH_ALL, VibratorManagerService.this);
            return 0;
        }

@@ -1777,6 +1822,9 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
                pw.println("Common Options:");
                pw.println("  -f");
                pw.println("    Force. Ignore Do Not Disturb setting.");
                pw.println("  -B");
                pw.println("    Run in the background; without this option the shell cmd will");
                pw.println("    block until the vibration has completed.");
                pw.println("  -d <description>");
                pw.println("    Add description to the vibration.");
                pw.println("");