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

Commit 7a316326 authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Introduce VibrationSession to VibratorManagerService" into main

parents 6f8e1d3b 790f3563
Loading
Loading
Loading
Loading
+146 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.server.vibrator;

import android.annotation.Nullable;
import android.content.Context;
import android.os.ExternalVibration;
import android.os.ExternalVibrationScale;
import android.os.IBinder;
import android.os.VibrationAttributes;
import android.os.vibrator.Flags;

import com.android.internal.util.FrameworkStatsLog;

/**
 * A vibration session holding a single {@link ExternalVibration} request.
 */
final class ExternalVibrationSession extends Vibration
        implements VibrationSession, IBinder.DeathRecipient {

    private final ExternalVibration mExternalVibration;
    private final ExternalVibrationScale mScale = new ExternalVibrationScale();

    @Nullable
    private Runnable mBinderDeathCallback;

    ExternalVibrationSession(ExternalVibration externalVibration) {
        super(externalVibration.getToken(), new CallerInfo(
                externalVibration.getVibrationAttributes(), externalVibration.getUid(),
                // TODO(b/249785241): Find a way to link ExternalVibration to a VirtualDevice
                // instead of using DEVICE_ID_INVALID here and relying on the UID checks.
                Context.DEVICE_ID_INVALID, externalVibration.getPackage(), null));
        mExternalVibration = externalVibration;
    }

    public ExternalVibrationScale getScale() {
        return mScale;
    }

    @Override
    public CallerInfo getCallerInfo() {
        return callerInfo;
    }

    @Override
    public VibrationSession.DebugInfo getDebugInfo() {
        return new Vibration.DebugInfoImpl(getStatus(), stats, /* playedEffect= */ null,
                /* originalEffect= */ null, mScale.scaleLevel, mScale.adaptiveHapticsScale,
                callerInfo);
    }

    @Override
    public VibrationStats.StatsInfo getStatsInfo(long completionUptimeMillis) {
        return new VibrationStats.StatsInfo(
                mExternalVibration.getUid(),
                FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__EXTERNAL,
                mExternalVibration.getVibrationAttributes().getUsage(), getStatus(), stats,
                completionUptimeMillis);
    }

    @Override
    public 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 = mExternalVibration.getVibrationAttributes().getUsage();
        return usage == VibrationAttributes.USAGE_RINGTONE
                || usage == VibrationAttributes.USAGE_ALARM;
    }

    @Override
    public void linkToDeath(Runnable callback) {
        synchronized (this) {
            mBinderDeathCallback = callback;
        }
        mExternalVibration.linkToDeath(this);
    }

    @Override
    public void unlinkToDeath() {
        mExternalVibration.unlinkToDeath(this);
        synchronized (this) {
            mBinderDeathCallback = null;
        }
    }

    @Override
    public void binderDied() {
        synchronized (this) {
            if (mBinderDeathCallback != null) {
                mBinderDeathCallback.run();
            }
        }
    }

    @Override
    void end(EndInfo endInfo) {
        super.end(endInfo);
        if (stats.hasStarted()) {
            // External vibration doesn't have feedback from total time the vibrator was playing
            // with non-zero amplitude, so we use the duration between start and end times of
            // the vibration as the time the vibrator was ON, since the haptic channels are
            // open for this duration and can receive vibration waveform data.
            stats.reportVibratorOn(stats.getEndUptimeMillis() - stats.getStartUptimeMillis());
        }
    }

    @Override
    public void notifyEnded() {
        // Notify external client that this vibration should stop sending data to the vibrator.
        mExternalVibration.mute();
    }

    boolean isHoldingSameVibration(ExternalVibration vib) {
        return mExternalVibration.equals(vib);
    }

    void muteScale() {
        mScale.scaleLevel = ExternalVibrationScale.ScaleLevel.SCALE_MUTE;
        if (Flags.hapticsScaleV2Enabled()) {
            mScale.scaleFactor = 0;
        }
    }

    void scale(VibrationScaler scaler, int usage) {
        mScale.scaleLevel = scaler.getScaleLevel(usage);
        if (Flags.hapticsScaleV2Enabled()) {
            mScale.scaleFactor = scaler.getScaleFactor(usage);
        }
        mScale.adaptiveHapticsScale = scaler.getAdaptiveHapticsScale(usage);
        stats.reportAdaptiveScale(mScale.adaptiveHapticsScale);
    }
}
+9 −29
Original line number Diff line number Diff line
@@ -51,37 +51,22 @@ final class HalVibration extends Vibration {
    @NonNull
    private volatile CombinedVibration mEffectToPlay;

    /** Vibration status. */
    private Vibration.Status mStatus;

    /** Reported scale values applied to the vibration effects. */
    private int mScaleLevel;
    private float mAdaptiveScale;

    HalVibration(@NonNull IBinder token, @NonNull CombinedVibration effect,
            @NonNull CallerInfo callerInfo) {
            @NonNull VibrationSession.CallerInfo callerInfo) {
        super(token, callerInfo);
        mOriginalEffect = effect;
        mEffectToPlay = effect;
        mStatus = Vibration.Status.RUNNING;
        mScaleLevel = VibrationScaler.SCALE_NONE;
        mAdaptiveScale = VibrationScaler.ADAPTIVE_SCALE_NONE;
    }

    /**
     * Set the {@link Status} of this vibration and reports the current system time as this
     * vibration end time, for debugging purposes.
     *
     * <p>This method will only accept given value if the current status is {@link
     * Status#RUNNING}.
     */
    public void end(EndInfo info) {
        if (hasEnded()) {
            // Vibration already ended, keep first ending status set and ignore this one.
            return;
        }
        mStatus = info.status;
        stats.reportEnded(info.endedBy);
    @Override
    public void end(EndInfo endInfo) {
        super.end(endInfo);
        mCompletionLatch.countDown();
    }

@@ -144,11 +129,6 @@ final class HalVibration extends Vibration {
        // No need to update fallback effects, they are already configured per device.
    }

    /** Return true is current status is different from {@link Status#RUNNING}. */
    public boolean hasEnded() {
        return mStatus != Status.RUNNING;
    }

    @Override
    public boolean isRepeating() {
        return mOriginalEffect.getDuration() == Long.MAX_VALUE;
@@ -159,16 +139,16 @@ final class HalVibration extends Vibration {
        return mEffectToPlay;
    }

    /** Return {@link Vibration.DebugInfo} with read-only debug information about this vibration. */
    public Vibration.DebugInfo getDebugInfo() {
    @Override
    public VibrationSession.DebugInfo getDebugInfo() {
        // Clear the original effect if it's the same as the effect that was played, for simplicity
        CombinedVibration originalEffect =
                Objects.equals(mOriginalEffect, mEffectToPlay) ? null : mOriginalEffect;
        return new Vibration.DebugInfo(mStatus, stats, mEffectToPlay, originalEffect,
        return new Vibration.DebugInfoImpl(getStatus(), stats, mEffectToPlay, originalEffect,
                mScaleLevel, mAdaptiveScale, callerInfo);
    }

    /** Return {@link VibrationStats.StatsInfo} with read-only metrics about this vibration. */
    @Override
    public VibrationStats.StatsInfo getStatsInfo(long completionUptimeMillis) {
        int vibrationType = mEffectToPlay.hasVendorEffects()
                ? FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__VENDOR
@@ -176,7 +156,7 @@ final class HalVibration extends Vibration {
                        ? FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__REPEATED
                        : FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__SINGLE;
        return new VibrationStats.StatsInfo(
                callerInfo.uid, vibrationType, callerInfo.attrs.getUsage(), mStatus,
                callerInfo.uid, vibrationType, callerInfo.attrs.getUsage(), getStatus(),
                stats, completionUptimeMillis);
    }

+2 −1
Original line number Diff line number Diff line
@@ -26,6 +26,7 @@ import android.util.SparseArray;
import android.view.InputDevice;

import com.android.internal.annotations.GuardedBy;
import com.android.server.vibrator.VibrationSession.CallerInfo;

/** Delegates vibrations to all connected {@link InputDevice} with one or more vibrators. */
final class InputDeviceDelegate implements InputManager.InputDeviceListener {
@@ -93,7 +94,7 @@ final class InputDeviceDelegate implements InputManager.InputDeviceListener {
     *
     * @return {@link #isAvailable()}
     */
    public boolean vibrateIfAvailable(Vibration.CallerInfo callerInfo, CombinedVibration effect) {
    public boolean vibrateIfAvailable(CallerInfo callerInfo, CombinedVibration effect) {
        synchronized (mLock) {
            for (int i = 0; i < mInputDeviceVibrators.size(); i++) {
                mInputDeviceVibrators.valueAt(i).vibrate(callerInfo.uid, callerInfo.opPkg, effect,
+70 −115
Original line number Diff line number Diff line
@@ -31,7 +31,6 @@ import android.os.vibrator.VibrationEffectSegment;
import android.util.IndentingPrintWriter;
import android.util.proto.ProtoOutputStream;

import java.io.PrintWriter;
import java.time.Instant;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
@@ -52,131 +51,69 @@ abstract class Vibration {
    private static final AtomicInteger sNextVibrationId = new AtomicInteger(1); // 0 = no callback

    public final long id;
    public final CallerInfo callerInfo;
    public final VibrationSession.CallerInfo callerInfo;
    public final VibrationStats stats = new VibrationStats();
    public final IBinder callerToken;

    /** Vibration status with reference to values from vibratormanagerservice.proto for logging. */
    enum Status {
        UNKNOWN(VibrationProto.UNKNOWN),
        RUNNING(VibrationProto.RUNNING),
        FINISHED(VibrationProto.FINISHED),
        FINISHED_UNEXPECTED(VibrationProto.FINISHED_UNEXPECTED),
        FORWARDED_TO_INPUT_DEVICES(VibrationProto.FORWARDED_TO_INPUT_DEVICES),
        CANCELLED_BINDER_DIED(VibrationProto.CANCELLED_BINDER_DIED),
        CANCELLED_BY_SCREEN_OFF(VibrationProto.CANCELLED_BY_SCREEN_OFF),
        CANCELLED_BY_SETTINGS_UPDATE(VibrationProto.CANCELLED_BY_SETTINGS_UPDATE),
        CANCELLED_BY_USER(VibrationProto.CANCELLED_BY_USER),
        CANCELLED_BY_FOREGROUND_USER(VibrationProto.CANCELLED_BY_FOREGROUND_USER),
        CANCELLED_BY_UNKNOWN_REASON(VibrationProto.CANCELLED_BY_UNKNOWN_REASON),
        CANCELLED_SUPERSEDED(VibrationProto.CANCELLED_SUPERSEDED),
        CANCELLED_BY_APP_OPS(VibrationProto.CANCELLED_BY_APP_OPS),
        IGNORED_ERROR_APP_OPS(VibrationProto.IGNORED_ERROR_APP_OPS),
        IGNORED_ERROR_CANCELLING(VibrationProto.IGNORED_ERROR_CANCELLING),
        IGNORED_ERROR_SCHEDULING(VibrationProto.IGNORED_ERROR_SCHEDULING),
        IGNORED_ERROR_TOKEN(VibrationProto.IGNORED_ERROR_TOKEN),
        IGNORED_APP_OPS(VibrationProto.IGNORED_APP_OPS),
        IGNORED_BACKGROUND(VibrationProto.IGNORED_BACKGROUND),
        IGNORED_MISSING_PERMISSION(VibrationProto.IGNORED_MISSING_PERMISSION),
        IGNORED_UNSUPPORTED(VibrationProto.IGNORED_UNSUPPORTED),
        IGNORED_FOR_EXTERNAL(VibrationProto.IGNORED_FOR_EXTERNAL),
        IGNORED_FOR_HIGHER_IMPORTANCE(VibrationProto.IGNORED_FOR_HIGHER_IMPORTANCE),
        IGNORED_FOR_ONGOING(VibrationProto.IGNORED_FOR_ONGOING),
        IGNORED_FOR_POWER(VibrationProto.IGNORED_FOR_POWER),
        IGNORED_FOR_RINGER_MODE(VibrationProto.IGNORED_FOR_RINGER_MODE),
        IGNORED_FOR_SETTINGS(VibrationProto.IGNORED_FOR_SETTINGS),
        IGNORED_SUPERSEDED(VibrationProto.IGNORED_SUPERSEDED),
        IGNORED_FROM_VIRTUAL_DEVICE(VibrationProto.IGNORED_FROM_VIRTUAL_DEVICE),
        IGNORED_ON_WIRELESS_CHARGER(VibrationProto.IGNORED_ON_WIRELESS_CHARGER);

        private final int mProtoEnumValue;

        Status(int value) {
            mProtoEnumValue = value;
        }

        public int getProtoEnumValue() {
            return mProtoEnumValue;
        }
    }

    Vibration(@NonNull IBinder token, @NonNull CallerInfo callerInfo) {
    private VibrationSession.Status mStatus;

    Vibration(@NonNull IBinder token, @NonNull VibrationSession.CallerInfo callerInfo) {
        Objects.requireNonNull(token);
        Objects.requireNonNull(callerInfo);
        mStatus = VibrationSession.Status.RUNNING;
        this.id = sNextVibrationId.getAndIncrement();
        this.callerToken = token;
        this.callerInfo = callerInfo;
    }

    /** Return true if vibration is a repeating vibration. */
    abstract boolean isRepeating();
    VibrationSession.Status getStatus() {
        return mStatus;
    }

    /** Return true is current status is different from {@link VibrationSession.Status#RUNNING}. */
    boolean hasEnded() {
        return mStatus != VibrationSession.Status.RUNNING;
    }

    /**
     * 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.
     * Set the {@link VibrationSession} of this vibration and reports the current system time as
     * this vibration end time, for debugging purposes.
     *
     * Since CallerInfo can be kept in memory for a long time, it shouldn't hold any references to
     * potentially expensive or resource-linked objects, such as {@link IBinder}.
     * <p>This method will only accept given value if the current status is {@link
     * VibrationSession.Status#RUNNING}.
     */
    static final class CallerInfo {
        public final VibrationAttributes attrs;
        public final int uid;
        public final int deviceId;
        public final String opPkg;
        public final String reason;

        CallerInfo(@NonNull VibrationAttributes attrs, int uid, int deviceId, String opPkg,
                String reason) {
            Objects.requireNonNull(attrs);
            this.attrs = attrs;
            this.uid = uid;
            this.deviceId = deviceId;
            this.opPkg = opPkg;
            this.reason = reason;
    void end(Vibration.EndInfo endInfo) {
        if (hasEnded()) {
            // Vibration already ended, keep first ending status set and ignore this one.
            return;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (!(o instanceof CallerInfo)) return false;
            CallerInfo that = (CallerInfo) o;
            return Objects.equals(attrs, that.attrs)
                    && uid == that.uid
                    && deviceId == that.deviceId
                    && Objects.equals(opPkg, that.opPkg)
                    && Objects.equals(reason, that.reason);
        mStatus = endInfo.status;
        stats.reportEnded(endInfo.endedBy);
    }

        @Override
        public int hashCode() {
            return Objects.hash(attrs, uid, deviceId, opPkg, reason);
        }
    /** Return true if vibration is a repeating vibration. */
    abstract boolean isRepeating();

        @Override
        public String toString() {
            return "CallerInfo{"
                    + " uid=" + uid
                    + ", opPkg=" + opPkg
                    + ", deviceId=" + deviceId
                    + ", attrs=" + attrs
                    + ", reason=" + reason
                    + '}';
        }
    }
    /** Return {@link VibrationSession.DebugInfo} with read-only debug data about this vibration. */
    abstract VibrationSession.DebugInfo getDebugInfo();

    /** Return {@link VibrationStats.StatsInfo} with read-only metrics about this vibration. */
    abstract VibrationStats.StatsInfo getStatsInfo(long completionUptimeMillis);

    /** Immutable info passed as a signal to end a vibration. */
    static final class EndInfo {
        /** The {@link Status} to be set to the vibration when it ends with this info. */
        /** The vibration status to be set when it ends with this info. */
        @NonNull
        public final Status status;
        public final VibrationSession.Status status;
        /** Info about the process that ended the vibration. */
        public final CallerInfo endedBy;
        public final VibrationSession.CallerInfo endedBy;

        EndInfo(@NonNull Vibration.Status status) {
        EndInfo(@NonNull VibrationSession.Status status) {
            this(status, null);
        }

        EndInfo(@NonNull Vibration.Status status, @Nullable CallerInfo endedBy) {
        EndInfo(@NonNull VibrationSession.Status status,
                @Nullable VibrationSession.CallerInfo endedBy) {
            this.status = status;
            this.endedBy = endedBy;
        }
@@ -211,10 +148,10 @@ abstract class Vibration {
     * Since DebugInfo can be kept in memory for a long time, it shouldn't hold any references to
     * potentially expensive or resource-linked objects, such as {@link IBinder}.
     */
    static final class DebugInfo {
        final Status mStatus;
    static final class DebugInfoImpl implements VibrationSession.DebugInfo {
        final VibrationSession.Status mStatus;
        final long mCreateTime;
        final CallerInfo mCallerInfo;
        final VibrationSession.CallerInfo mCallerInfo;
        @Nullable
        final CombinedVibration mPlayedEffect;

@@ -226,9 +163,10 @@ abstract class Vibration {
        private final int mScaleLevel;
        private final float mAdaptiveScale;

        DebugInfo(Status status, VibrationStats stats, @Nullable CombinedVibration playedEffect,
        DebugInfoImpl(VibrationSession.Status status, VibrationStats stats,
                @Nullable CombinedVibration playedEffect,
                @Nullable CombinedVibration originalEffect, int scaleLevel,
                float adaptiveScale, @NonNull CallerInfo callerInfo) {
                float adaptiveScale, @NonNull VibrationSession.CallerInfo callerInfo) {
            Objects.requireNonNull(callerInfo);
            mCreateTime = stats.getCreateTimeDebug();
            mStartTime = stats.getStartTimeDebug();
@@ -242,6 +180,27 @@ abstract class Vibration {
            mStatus = status;
        }

        @Override
        public VibrationSession.Status getStatus() {
            return mStatus;
        }

        @Override
        public long getCreateUptimeMillis() {
            return mCreateTime;
        }

        @Override
        public VibrationSession.CallerInfo getCallerInfo() {
            return mCallerInfo;
        }

        @Nullable
        @Override
        public Object getDumpAggregationKey() {
            return mPlayedEffect;
        }

        @Override
        public String toString() {
            return "createTime: " + formatTime(mCreateTime, /*includeDate=*/ true)
@@ -257,17 +216,13 @@ abstract class Vibration {
                    + ", callerInfo: " + mCallerInfo;
        }

        void logMetrics(VibratorFrameworkStatsLogger statsLogger) {
        @Override
        public void logMetrics(VibratorFrameworkStatsLogger statsLogger) {
            statsLogger.logVibrationAdaptiveHapticScale(mCallerInfo.uid, mAdaptiveScale);
        }

        /**
         * 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) {
        @Override
        public void dumpCompact(IndentingPrintWriter pw) {
            boolean isExternalVibration = mPlayedEffect == null;
            String timingsStr = String.format(Locale.ROOT,
                    "%s | %8s | %20s | duration: %5dms | start: %12s | end: %12s",
@@ -299,8 +254,8 @@ abstract class Vibration {
            pw.println(timingsStr + paramStr + audioUsageStr + callerStr + effectStr);
        }

        /** Write this info into given {@link PrintWriter}. */
        void dump(IndentingPrintWriter pw) {
        @Override
        public void dump(IndentingPrintWriter pw) {
            pw.println("Vibration:");
            pw.increaseIndent();
            pw.println("status = " + mStatus.name().toLowerCase(Locale.ROOT));
@@ -317,8 +272,8 @@ abstract class Vibration {
            pw.decreaseIndent();
        }

        /** Write this info into given {@code fieldId} on {@link ProtoOutputStream}. */
        void dump(ProtoOutputStream proto, long fieldId) {
        @Override
        public void dump(ProtoOutputStream proto, long fieldId) {
            final long token = proto.start(fieldId);
            proto.write(VibrationProto.START_TIME, mStartTime);
            proto.write(VibrationProto.END_TIME, mEndTime);
+208 −0

File added.

Preview size limit exceeded, changes collapsed.

Loading