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

Commit 8d9147e3 authored by Ahmad Khalil's avatar Ahmad Khalil Committed by Automerger Merge Worker
Browse files

Merge "Fix vibration prioritisation for external and "next" vibrations" into udc-dev am: a054b3e1

parents 85e4186c a054b3e1
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -155,7 +155,7 @@ final class HalVibration extends Vibration {
        return mStatus != Status.RUNNING;
    }

    /** Return true is effect is a repeating vibration. */
    @Override
    public boolean isRepeating() {
        return mEffect.getDuration() == Long.MAX_VALUE;
    }
+4 −1
Original line number Diff line number Diff line
@@ -37,7 +37,7 @@ import java.util.concurrent.atomic.AtomicInteger;
/**
 * The base class for all vibrations.
 */
class Vibration {
abstract class Vibration {
    private static final SimpleDateFormat DEBUG_DATE_FORMAT =
            new SimpleDateFormat("MM-dd HH:mm:ss.SSS");
    // Used to generate globally unique vibration ids.
@@ -97,6 +97,9 @@ class Vibration {
        this.callerInfo = callerInfo;
    }

    /** Return true if vibration is a repeating vibration. */
    abstract boolean isRepeating();

    /**
     * Holds lightweight immutable info on the process that triggered the vibration. This data
     * could potentially be kept in memory for a long time for bugreport dumpsys operations.
+140 −71
Original line number Diff line number Diff line
@@ -419,31 +419,28 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
                if (DEBUG) {
                    Slog.d(TAG, "Starting vibrate for vibration " + vib.id);
                }
                Vibration.CallerInfo ignoredByCallerInfo = null;
                Vibration.Status status;

                // Check if user settings or DnD is set to ignore this vibration.
                status = shouldIgnoreVibrationLocked(vib.callerInfo);

                // Check if something has external control, assume it's more important.
                if ((status == null) && (mCurrentExternalVibration != null)) {
                    status = Vibration.Status.IGNORED_FOR_EXTERNAL;
                    ignoredByCallerInfo = mCurrentExternalVibration.callerInfo;
                }
                Vibration.EndInfo vibrationEndInfo = shouldIgnoreVibrationLocked(vib.callerInfo);

                // Check if ongoing vibration is more important than this vibration.
                if (status == null) {
                    status = shouldIgnoreVibrationForOngoingLocked(vib);
                    if (status != null) {
                        ignoredByCallerInfo = mCurrentVibration.getVibration().callerInfo;
                    }
                if (vibrationEndInfo == null) {
                    vibrationEndInfo = shouldIgnoreVibrationForOngoingLocked(vib);
                }

                // If not ignored so far then try to start this vibration.
                if (status == null) {
                if (vibrationEndInfo == null) {
                    final long ident = Binder.clearCallingIdentity();
                    try {
                        if (mCurrentVibration != null) {
                        if (mCurrentExternalVibration != null) {
                            mCurrentExternalVibration.mute();
                            vib.stats.reportInterruptedAnotherVibration(
                                    mCurrentExternalVibration.callerInfo);
                            endExternalVibrateLocked(
                                    new Vibration.EndInfo(Vibration.Status.CANCELLED_SUPERSEDED,
                                            vib.callerInfo),
                                    /* continueExternalControl= */ false);
                        } else if (mCurrentVibration != null) {
                            if (mCurrentVibration.getVibration().canPipelineWith(vib)) {
                                // Don't cancel the current vibration if it's pipeline-able.
                                // Note that if there is a pending next vibration that can't be
@@ -461,17 +458,15 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
                                        /* immediate= */ false);
                            }
                        }
                        status = startVibrationLocked(vib);
                        vibrationEndInfo = startVibrationLocked(vib);
                    } finally {
                        Binder.restoreCallingIdentity(ident);
                    }
                }

                // Ignored or failed to start the vibration, end it and report metrics right away.
                if (status != Vibration.Status.RUNNING) {
                    endVibrationLocked(vib,
                            new Vibration.EndInfo(status, ignoredByCallerInfo),
                            /* shouldWriteStats= */ true);
                if (vibrationEndInfo != null) {
                    endVibrationLocked(vib, vibrationEndInfo, /* shouldWriteStats= */ true);
                }
                return vib;
            }
@@ -633,12 +628,13 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
            }

            HalVibration vib = mCurrentVibration.getVibration();
            Vibration.Status ignoreStatus = shouldIgnoreVibrationLocked(vib.callerInfo);
            Vibration.EndInfo vibrationEndInfo = shouldIgnoreVibrationLocked(vib.callerInfo);

            if (inputDevicesChanged || (ignoreStatus != null)) {
            if (inputDevicesChanged || (vibrationEndInfo != null)) {
                if (DEBUG) {
                    Slog.d(TAG, "Canceling vibration because settings changed: "
                            + (inputDevicesChanged ? "input devices changed" : ignoreStatus));
                            + (inputDevicesChanged ? "input devices changed"
                            : vibrationEndInfo.status));
                }
                mCurrentVibration.notifyCancelled(
                        new Vibration.EndInfo(Vibration.Status.CANCELLED_BY_SETTINGS_UPDATE),
@@ -662,8 +658,8 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
            if (vibrator == null) {
                continue;
            }
            Vibration.Status ignoreStatus = shouldIgnoreVibrationLocked(vib.callerInfo);
            if (ignoreStatus == null) {
            Vibration.EndInfo vibrationEndInfo = shouldIgnoreVibrationLocked(vib.callerInfo);
            if (vibrationEndInfo == null) {
                effect = mVibrationScaler.scale(effect, vib.callerInfo.attrs.getUsage());
            } else {
                // Vibration should not run, use null effect to remove registered effect.
@@ -674,7 +670,8 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
    }

    @GuardedBy("mLock")
    private Vibration.Status startVibrationLocked(HalVibration vib) {
    @Nullable
    private Vibration.EndInfo startVibrationLocked(HalVibration vib) {
        Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "startVibrationLocked");
        try {
            vib.updateEffects(
@@ -682,7 +679,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
            boolean inputDevicesAvailable = mInputDeviceDelegate.vibrateIfAvailable(
                    vib.callerInfo, vib.getEffect());
            if (inputDevicesAvailable) {
                return Vibration.Status.FORWARDED_TO_INPUT_DEVICES;
                return new Vibration.EndInfo(Vibration.Status.FORWARDED_TO_INPUT_DEVICES);
            }

            VibrationStepConductor conductor = new VibrationStepConductor(vib, mVibrationSettings,
@@ -697,14 +694,15 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
            clearNextVibrationLocked(
                    new Vibration.EndInfo(Vibration.Status.IGNORED_SUPERSEDED, vib.callerInfo));
            mNextVibration = conductor;
            return Vibration.Status.RUNNING;
            return null;
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
        }
    }

    @GuardedBy("mLock")
    private Vibration.Status startVibrationOnThreadLocked(VibrationStepConductor conductor) {
    @Nullable
    private Vibration.EndInfo startVibrationOnThreadLocked(VibrationStepConductor conductor) {
        Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "startVibrationThreadLocked");
        try {
            HalVibration vib = conductor.getVibration();
@@ -717,15 +715,15 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
                    if (!mVibrationThread.runVibrationOnVibrationThread(mCurrentVibration)) {
                        // Shouldn't happen. The method call already logs a wtf.
                        mCurrentVibration = null;  // Aborted.
                        return Vibration.Status.IGNORED_ERROR_SCHEDULING;
                        return new Vibration.EndInfo(Vibration.Status.IGNORED_ERROR_SCHEDULING);
                    }
                    return Vibration.Status.RUNNING;
                    return null;
                case AppOpsManager.MODE_ERRORED:
                    Slog.w(TAG, "Start AppOpsManager operation errored for uid "
                            + vib.callerInfo.uid);
                    return Vibration.Status.IGNORED_ERROR_APP_OPS;
                    return new Vibration.EndInfo(Vibration.Status.IGNORED_ERROR_APP_OPS);
                default:
                    return Vibration.Status.IGNORED_APP_OPS;
                    return new Vibration.EndInfo(Vibration.Status.IGNORED_APP_OPS);
            }
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
@@ -849,39 +847,91 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
    /**
     * Check if given vibration should be ignored by this service because of the ongoing vibration.
     *
     * @return One of Vibration.Status.IGNORED_* values if the vibration should be ignored, null
     * otherwise.
     * @return a Vibration.EndInfo if the vibration should be ignored, null otherwise.
     */
    @GuardedBy("mLock")
    @Nullable
    private Vibration.Status shouldIgnoreVibrationForOngoingLocked(HalVibration vib) {
        if (mCurrentVibration == null || vib.isRepeating()) {
            // Incoming repeating vibrations always take precedence over ongoing vibrations.
            return null;
    private Vibration.EndInfo shouldIgnoreVibrationForOngoingLocked(Vibration vib) {
        if (mCurrentExternalVibration != null) {
            return shouldIgnoreVibrationForOngoing(vib, mCurrentExternalVibration);
        }

        if (mNextVibration != null) {
            Vibration.EndInfo vibrationEndInfo = shouldIgnoreVibrationForOngoing(vib,
                    mNextVibration.getVibration());
            if (vibrationEndInfo != null) {
                // Next vibration has higher importance than the new one, so the new vibration
                // should be ignored.
                return vibrationEndInfo;
            }
        }

        if (mCurrentVibration != null) {
            HalVibration currentVibration = mCurrentVibration.getVibration();
            if (currentVibration.hasEnded() || mCurrentVibration.wasNotifiedToCancel()) {
            // Current vibration has ended or is cancelling, should not block incoming vibrations.
                // Current vibration has ended or is cancelling, should not block incoming
                // vibrations.
                return null;
            }

        int currentUsage = currentVibration.callerInfo.attrs.getUsage();
        int newUsage = vib.callerInfo.attrs.getUsage();
        if (getVibrationImportance(currentUsage) > getVibrationImportance(newUsage)) {
            // Current vibration has higher importance than this one and should not be cancelled.
            return Vibration.Status.IGNORED_FOR_HIGHER_IMPORTANCE;
            return shouldIgnoreVibrationForOngoing(vib, currentVibration);
        }

        if (currentVibration.isRepeating()) {
            // Current vibration is repeating, assume it's more important.
            return Vibration.Status.IGNORED_FOR_ONGOING;
        return null;
    }

    /**
     * Checks if the ongoing vibration has higher importance than the new one. If they have similar
     * importance, then {@link Vibration#isRepeating()} is used as a tiebreaker.
     *
     * @return a Vibration.EndInfo if the vibration should be ignored, null otherwise.
     */
    @Nullable
    private static Vibration.EndInfo shouldIgnoreVibrationForOngoing(
            @NonNull Vibration newVibration, @NonNull Vibration ongoingVibration) {

        int newVibrationImportance = getVibrationImportance(newVibration);
        int ongoingVibrationImportance = getVibrationImportance(ongoingVibration);

        if (newVibrationImportance > ongoingVibrationImportance) {
            // New vibration has higher importance and should not be ignored.
            return null;
        }

    private static int getVibrationImportance(@VibrationAttributes.Usage int usage) {
        if (ongoingVibrationImportance > newVibrationImportance) {
            // Existing vibration has higher importance and should not be cancelled.
            return new Vibration.EndInfo(Vibration.Status.IGNORED_FOR_HIGHER_IMPORTANCE,
                    ongoingVibration.callerInfo);
        }

        // Same importance, use repeating as a tiebreaker.
        if (ongoingVibration.isRepeating() && !newVibration.isRepeating()) {
            // Ongoing vibration is repeating and new one is not, give priority to ongoing
            return new Vibration.EndInfo(Vibration.Status.IGNORED_FOR_ONGOING,
                    ongoingVibration.callerInfo);
        }
        // New vibration is repeating or this is a complete tie between them,
        // give priority to new vibration.
        return null;
    }

    /**
     * Gets the vibration importance based on usage. In the case where usage is unknown, it maps
     * repeating vibrations to ringtones and non-repeating vibrations to touches.
     *
     * @return a numeric representation for the vibration importance, larger values represent a
     * higher importance
     */
    private static int getVibrationImportance(Vibration vibration) {
        int usage = vibration.callerInfo.attrs.getUsage();
        if (usage == VibrationAttributes.USAGE_UNKNOWN) {
            if (vibration.isRepeating()) {
                usage = VibrationAttributes.USAGE_RINGTONE;
            } else {
                usage = VibrationAttributes.USAGE_TOUCH;
            }
        }

        switch (usage) {
            case VibrationAttributes.USAGE_RINGTONE:
                return 5;
@@ -897,7 +947,6 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
                return 1;
            case VibrationAttributes.USAGE_MEDIA:
            case VibrationAttributes.USAGE_TOUCH:
            case VibrationAttributes.USAGE_UNKNOWN:
            default:
                return 0;
        }
@@ -906,15 +955,14 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
    /**
     * Check if given vibration should be ignored by this service.
     *
     * @return One of Vibration.Status.IGNORED_* values if the vibration should be ignored, null
     * otherwise.
     * @return a Vibration.EndInfo if the vibration should be ignored, null otherwise.
     */
    @GuardedBy("mLock")
    @Nullable
    private Vibration.Status shouldIgnoreVibrationLocked(Vibration.CallerInfo callerInfo) {
    private Vibration.EndInfo shouldIgnoreVibrationLocked(Vibration.CallerInfo callerInfo) {
        Vibration.Status statusFromSettings = mVibrationSettings.shouldIgnoreVibration(callerInfo);
        if (statusFromSettings != null) {
            return statusFromSettings;
            return new Vibration.EndInfo(statusFromSettings);
        }

        int mode = checkAppOpModeLocked(callerInfo);
@@ -922,9 +970,9 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
            if (mode == AppOpsManager.MODE_ERRORED) {
                // We might be getting calls from within system_server, so we don't actually
                // want to throw a SecurityException here.
                return Vibration.Status.IGNORED_ERROR_APP_OPS;
                return new Vibration.EndInfo(Vibration.Status.IGNORED_ERROR_APP_OPS);
            } else {
                return Vibration.Status.IGNORED_APP_OPS;
                return new Vibration.EndInfo(Vibration.Status.IGNORED_APP_OPS);
            }
        }

@@ -1327,11 +1375,12 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
                if (mNextVibration != null) {
                    VibrationStepConductor nextConductor = mNextVibration;
                    mNextVibration = null;
                    Vibration.Status status = startVibrationOnThreadLocked(nextConductor);
                    if (status != Vibration.Status.RUNNING) {
                    Vibration.EndInfo vibrationEndInfo = startVibrationOnThreadLocked(
                            nextConductor);
                    if (vibrationEndInfo != null) {
                        // Failed to start the vibration, end it and report metrics right away.
                        endVibrationLocked(nextConductor.getVibration(),
                                new Vibration.EndInfo(status), /* shouldWriteStats= */ true);
                                vibrationEndInfo, /* shouldWriteStats= */ true);
                    }
                }
            }
@@ -1471,6 +1520,16 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
                    externalVibration.getVibrationAttributes().getUsage(), mStatus, stats,
                    completionUptimeMillis);
        }

        @Override
        boolean isRepeating() {
            // We don't currently know if the external vibration is repeating, so we just use a
            // heuristic based on the usage. Ideally this would be propagated in the
            // ExternalVibration.
            int usage = externalVibration.getVibrationAttributes().getUsage();
            return usage == VibrationAttributes.USAGE_RINGTONE
                    || usage == VibrationAttributes.USAGE_ALARM;
        }
    }

    /** Wrapper around the static-native methods of {@link VibratorManagerService} for tests. */
@@ -1672,19 +1731,29 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
            synchronized (mLock) {
                // TODO(b/243604888): propagating displayID from IExternalVibration instead of
                // using INVALID_DISPLAY for all external vibrations.
                Vibration.Status ignoreStatus = shouldIgnoreVibrationLocked(vibHolder.callerInfo);
                if (ignoreStatus != null) {
                    vibHolder.scale = IExternalVibratorService.SCALE_MUTE;
                    // Failed to start the vibration, end it and report metrics right away.
                    endVibrationAndWriteStatsLocked(vibHolder, new Vibration.EndInfo(ignoreStatus));
                    return vibHolder.scale;
                }
                if (mCurrentExternalVibration != null
                Vibration.EndInfo vibrationEndInfo = shouldIgnoreVibrationLocked(
                        vibHolder.callerInfo);

                if (vibrationEndInfo == null
                        && mCurrentExternalVibration != null
                        && mCurrentExternalVibration.isHoldingSameVibration(vib)) {
                    // We are already playing this external vibration, so we can return the same
                    // scale calculated in the previous call to this method.
                    return mCurrentExternalVibration.scale;
                }

                if (vibrationEndInfo == null) {
                    // Check if ongoing vibration is more important than this vibration.
                    vibrationEndInfo = shouldIgnoreVibrationForOngoingLocked(vibHolder);
                }

                if (vibrationEndInfo != null) {
                    vibHolder.scale = IExternalVibratorService.SCALE_MUTE;
                    // Failed to start the vibration, end it and report metrics right away.
                    endVibrationAndWriteStatsLocked(vibHolder, vibrationEndInfo);
                    return vibHolder.scale;
                }

                if (mCurrentExternalVibration == null) {
                    // 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.
+213 −12

File changed.

Preview size limit exceeded, changes collapsed.