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

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

Merge "Introduce SingleVibrationSession" into main

parents ed687e00 56297dfb
Loading
Loading
Loading
Loading
+33 −13
Original line number Diff line number Diff line
@@ -55,25 +55,27 @@ final class ExternalVibrationSession extends Vibration
        return mScale;
    }

    @Override
    public long getCreateUptimeMillis() {
        return stats.getCreateUptimeMillis();
    }

    @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);
    public IBinder getCallerToken() {
        return mExternalVibration.getToken();
    }

    @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);
    public VibrationSession.DebugInfo getDebugInfo() {
        return new Vibration.DebugInfoImpl(getStatus(), callerInfo,
                FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__EXTERNAL, stats,
                /* playedEffect= */ null, /* originalEffect= */ null, mScale.scaleLevel,
                mScale.adaptiveHapticsScale);
    }

    @Override
@@ -85,6 +87,12 @@ final class ExternalVibrationSession extends Vibration
                || usage == VibrationAttributes.USAGE_ALARM;
    }

    @Override
    public boolean wasEndRequested() {
        // End request is immediate, so just check if vibration has already ended.
        return hasEnded();
    }

    @Override
    public boolean linkToDeath(Runnable callback) {
        synchronized (mLock) {
@@ -104,10 +112,12 @@ final class ExternalVibrationSession extends Vibration

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

@@ -131,6 +141,16 @@ final class ExternalVibrationSession extends Vibration
        end(new EndInfo(status, endedBy));
    }

    @Override
    public void notifyVibratorCallback(int vibratorId, long vibrationId) {
        // ignored, external control does not expect callbacks from the vibrator
    }

    @Override
    public void notifySyncedVibratorsCallback(long vibrationId) {
        // ignored, external control does not expect callbacks from the vibrator manager
    }

    boolean isHoldingSameVibration(ExternalVibration vib) {
        return mExternalVibration.equals(vib);
    }
+50 −31
Original line number Diff line number Diff line
@@ -19,15 +19,16 @@ package com.android.server.vibrator;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.CombinedVibration;
import android.os.IBinder;
import android.os.VibrationAttributes;
import android.os.VibrationEffect;
import android.os.vibrator.PrebakedSegment;
import android.os.vibrator.VibrationEffectSegment;
import android.util.SparseArray;

import com.android.internal.util.FrameworkStatsLog;

import java.util.List;
import java.util.Objects;
import java.util.concurrent.CountDownLatch;
import java.util.function.IntFunction;

/**
 * Represents a vibration defined by a {@link CombinedVibration} that will be performed by
@@ -36,7 +37,6 @@ import java.util.concurrent.CountDownLatch;
final class HalVibration extends Vibration {

    public final SparseArray<VibrationEffect> mFallbacks = new SparseArray<>();
    public final IBinder callerToken;

    /** A {@link CountDownLatch} to enable waiting for completion. */
    private final CountDownLatch mCompletionLatch = new CountDownLatch(1);
@@ -56,10 +56,9 @@ final class HalVibration extends Vibration {
    private int mScaleLevel;
    private float mAdaptiveScale;

    HalVibration(@NonNull IBinder callerToken, @NonNull CombinedVibration effect,
            @NonNull VibrationSession.CallerInfo callerInfo) {
    HalVibration(@NonNull VibrationSession.CallerInfo callerInfo,
            @NonNull CombinedVibration effect) {
        super(callerInfo);
        this.callerToken = callerToken;
        mOriginalEffect = effect;
        mEffectToPlay = effect;
        mScaleLevel = VibrationScaler.SCALE_NONE;
@@ -87,11 +86,11 @@ final class HalVibration extends Vibration {
    }

    /**
     * Add a fallback {@link VibrationEffect} to be played when given effect id is not supported,
     * which might be necessary for replacement in realtime.
     * Add a fallback {@link VibrationEffect} to be played for each predefined effect id, which
     * might be necessary for replacement in realtime.
     */
    public void addFallback(int effectId, VibrationEffect effect) {
        mFallbacks.put(effectId, effect);
    public void fillFallbacks(IntFunction<VibrationEffect> fallbackProvider) {
        fillFallbacksForEffect(mEffectToPlay, fallbackProvider);
    }

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

    @Override
    public boolean isRepeating() {
        return mOriginalEffect.getDuration() == Long.MAX_VALUE;
    }

    /** Return the effect that should be played by this vibration. */
    public CombinedVibration getEffectToPlay() {
        return mEffectToPlay;
@@ -146,20 +140,9 @@ final class HalVibration extends Vibration {
        // 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.DebugInfoImpl(getStatus(), stats, mEffectToPlay, originalEffect,
                mScaleLevel, mAdaptiveScale, callerInfo);
    }

    @Override
    public VibrationStats.StatsInfo getStatsInfo(long completionUptimeMillis) {
        int vibrationType = mEffectToPlay.hasVendorEffects()
                ? FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__VENDOR
                : isRepeating()
                        ? FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__REPEATED
                        : FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__SINGLE;
        return new VibrationStats.StatsInfo(
                callerInfo.uid, vibrationType, callerInfo.attrs.getUsage(), getStatus(),
                stats, completionUptimeMillis);
        return new Vibration.DebugInfoImpl(getStatus(), callerInfo,
                VibrationStats.StatsInfo.findVibrationType(mEffectToPlay), stats, mEffectToPlay,
                originalEffect, mScaleLevel, mAdaptiveScale);
    }

    /**
@@ -174,6 +157,42 @@ final class HalVibration extends Vibration {
        return callerInfo.uid == vib.callerInfo.uid && callerInfo.attrs.isFlagSet(
                VibrationAttributes.FLAG_PIPELINED_EFFECT)
                && vib.callerInfo.attrs.isFlagSet(VibrationAttributes.FLAG_PIPELINED_EFFECT)
                && !isRepeating();
                && (mOriginalEffect.getDuration() != Long.MAX_VALUE);
    }

    private void fillFallbacksForEffect(CombinedVibration effect,
            IntFunction<VibrationEffect> fallbackProvider) {
        if (effect instanceof CombinedVibration.Mono) {
            fillFallbacksForEffect(((CombinedVibration.Mono) effect).getEffect(), fallbackProvider);
        } else if (effect instanceof CombinedVibration.Stereo) {
            SparseArray<VibrationEffect> effects =
                    ((CombinedVibration.Stereo) effect).getEffects();
            for (int i = 0; i < effects.size(); i++) {
                fillFallbacksForEffect(effects.valueAt(i), fallbackProvider);
            }
        } else if (effect instanceof CombinedVibration.Sequential) {
            List<CombinedVibration> effects =
                    ((CombinedVibration.Sequential) effect).getEffects();
            for (int i = 0; i < effects.size(); i++) {
                fillFallbacksForEffect(effects.get(i), fallbackProvider);
            }
        }
    }

    private void fillFallbacksForEffect(VibrationEffect effect,
            IntFunction<VibrationEffect> fallbackProvider) {
        if (!(effect instanceof VibrationEffect.Composed composed)) {
            return;
        }
        int segmentCount = composed.getSegments().size();
        for (int i = 0; i < segmentCount; i++) {
            VibrationEffectSegment segment = composed.getSegments().get(i);
            if ((segment instanceof PrebakedSegment prebaked) && prebaked.shouldFallback()) {
                VibrationEffect fallback = fallbackProvider.apply(prebaked.getEffectId());
                if (fallback != null) {
                    mFallbacks.put(prebaked.getEffectId(), fallback);
                }
            }
        }
    }
}
+173 −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.NonNull;
import android.annotation.Nullable;
import android.os.CombinedVibration;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Slog;

import com.android.internal.annotations.GuardedBy;

import java.util.NoSuchElementException;

/**
 * A vibration session holding a single {@link CombinedVibration} request, performed by a
 * {@link VibrationStepConductor}.
 */
final class SingleVibrationSession implements VibrationSession, IBinder.DeathRecipient {
    private static final String TAG = "SingleVibrationSession";

    private final Object mLock = new Object();
    private final IBinder mCallerToken;
    private final HalVibration mVibration;

    @GuardedBy("mLock")
    private VibrationStepConductor mConductor;

    @GuardedBy("mLock")
    @Nullable
    private Runnable mBinderDeathCallback;

    SingleVibrationSession(@NonNull IBinder callerToken, @NonNull CallerInfo callerInfo,
            @NonNull CombinedVibration vibration) {
        mCallerToken = callerToken;
        mVibration = new HalVibration(callerInfo, vibration);
    }

    public void setVibrationConductor(@Nullable VibrationStepConductor conductor) {
        synchronized (mLock) {
            mConductor = conductor;
        }
    }

    public HalVibration getVibration() {
        return mVibration;
    }

    @Override
    public long getCreateUptimeMillis() {
        return mVibration.stats.getCreateUptimeMillis();
    }

    @Override
    public boolean isRepeating() {
        return mVibration.getEffectToPlay().getDuration() == Long.MAX_VALUE;
    }

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

    @Override
    public IBinder getCallerToken() {
        return mCallerToken;
    }

    @Override
    public DebugInfo getDebugInfo() {
        return mVibration.getDebugInfo();
    }

    @Override
    public boolean wasEndRequested() {
        if (mVibration.hasEnded()) {
            return true;
        }
        synchronized (mLock) {
            return mConductor != null && mConductor.wasNotifiedToCancel();
        }
    }

    @Override
    public void binderDied() {
        Slog.d(TAG, "Binder died, cancelling vibration...");
        requestEnd(Status.CANCELLED_BINDER_DIED, /* endedBy= */ null, /* immediate= */ false);
        Runnable callback;
        synchronized (mLock) {
            callback = mBinderDeathCallback;
        }
        if (callback != null) {
            callback.run();
        }
    }

    @Override
    public boolean linkToDeath(@Nullable Runnable callback) {
        synchronized (mLock) {
            mBinderDeathCallback = callback;
        }
        try {
            mCallerToken.linkToDeath(this, 0);
        } catch (RemoteException e) {
            Slog.e(TAG, "Error linking vibration to token death", e);
            return false;
        }
        return true;
    }

    @Override
    public void unlinkToDeath() {
        try {
            mCallerToken.unlinkToDeath(this, 0);
        } catch (NoSuchElementException e) {
            Slog.wtf(TAG, "Failed to unlink vibration to token death", e);
        }
        synchronized (mLock) {
            mBinderDeathCallback = null;
        }
    }

    @Override
    public void requestEnd(@NonNull Status status, @Nullable CallerInfo endedBy,
            boolean immediate) {
        synchronized (mLock) {
            if (mConductor != null) {
                mConductor.notifyCancelled(new Vibration.EndInfo(status, endedBy), immediate);
            } else {
                mVibration.end(new Vibration.EndInfo(status, endedBy));
            }
        }
    }

    @Override
    public void notifyVibratorCallback(int vibratorId, long vibrationId) {
        if (vibrationId != mVibration.id) {
            return;
        }
        synchronized (mLock) {
            if (mConductor != null) {
                mConductor.notifyVibratorComplete(vibratorId);
            }
        }
    }

    @Override
    public void notifySyncedVibratorsCallback(long vibrationId) {
        if (vibrationId != mVibration.id) {
            return;
        }
        synchronized (mLock) {
            if (mConductor != null) {
                mConductor.notifySyncedVibrationComplete();
            }
        }
    }
}
+25 −24
Original line number Diff line number Diff line
@@ -88,15 +88,9 @@ abstract class Vibration {
        stats.reportEnded(endInfo.endedBy);
    }

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

    /** 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 vibration status to be set when it ends with this info. */
@@ -146,35 +140,41 @@ abstract class Vibration {
     * potentially expensive or resource-linked objects, such as {@link IBinder}.
     */
    static final class DebugInfoImpl implements VibrationSession.DebugInfo {
        final VibrationSession.Status mStatus;
        final long mCreateTime;
        final VibrationSession.CallerInfo mCallerInfo;
        private final VibrationSession.Status mStatus;
        private final VibrationStats.StatsInfo mStatsInfo;
        private final VibrationSession.CallerInfo mCallerInfo;
        @Nullable
        final CombinedVibration mPlayedEffect;

        private final long mStartTime;
        private final long mEndTime;
        private final long mDurationMs;
        private final CombinedVibration mPlayedEffect;
        @Nullable
        private final CombinedVibration mOriginalEffect;
        private final int mScaleLevel;
        private final float mAdaptiveScale;

        DebugInfoImpl(VibrationSession.Status status, VibrationStats stats,
                @Nullable CombinedVibration playedEffect,
                @Nullable CombinedVibration originalEffect, int scaleLevel,
                float adaptiveScale, @NonNull VibrationSession.CallerInfo callerInfo) {
        private final long mCreateUptime;
        private final long mCreateTime;
        private final long mStartTime;
        private final long mEndTime;
        private final long mDurationMs;

        DebugInfoImpl(VibrationSession.Status status,
                @NonNull VibrationSession.CallerInfo callerInfo, int vibrationType,
                VibrationStats stats, @Nullable CombinedVibration playedEffect,
                @Nullable CombinedVibration originalEffect, int scaleLevel, float adaptiveScale) {
            Objects.requireNonNull(callerInfo);
            mCreateTime = stats.getCreateTimeDebug();
            mStartTime = stats.getStartTimeDebug();
            mEndTime = stats.getEndTimeDebug();
            mDurationMs = stats.getDurationDebug();
            mCallerInfo = callerInfo;
            mStatsInfo = stats.toStatsInfo(callerInfo.uid, vibrationType,
                    callerInfo.attrs.getUsage(), status);
            mPlayedEffect = playedEffect;
            mOriginalEffect = originalEffect;
            mScaleLevel = scaleLevel;
            mAdaptiveScale = adaptiveScale;
            mCallerInfo = callerInfo;
            mStatus = status;

            mCreateUptime = stats.getCreateUptimeMillis();
            mCreateTime = stats.getCreateTimeDebug();
            mStartTime = stats.getStartTimeDebug();
            mEndTime = stats.getEndTimeDebug();
            mDurationMs = stats.getDurationDebug();
        }

        @Override
@@ -184,7 +184,7 @@ abstract class Vibration {

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

        @Override
@@ -216,6 +216,7 @@ abstract class Vibration {
        @Override
        public void logMetrics(VibratorFrameworkStatsLogger statsLogger) {
            statsLogger.logVibrationAdaptiveHapticScale(mCallerInfo.uid, mAdaptiveScale);
            statsLogger.writeVibrationReportedAsync(mStatsInfo);
        }

        @Override
+41 −0
Original line number Diff line number Diff line
@@ -39,9 +39,18 @@ import java.util.Objects;
 */
interface VibrationSession {

    /** Returns the session creation time from {@link android.os.SystemClock#uptimeMillis()}. */
    long getCreateUptimeMillis();

    /** Return true if vibration session plays a repeating vibration. */
    boolean isRepeating();

    /** Returns data about the client app that triggered this vibration session. */
    CallerInfo getCallerInfo();

    /** Returns the binder token from the client app attached to this vibration session. */
    IBinder getCallerToken();

    /** Returns debug data for logging and metric reports. */
    DebugInfo getDebugInfo();

@@ -58,6 +67,19 @@ interface VibrationSession {
    /** Removes link to the app process death. */
    void unlinkToDeath();

    /** Returns true if this session was requested to end by {@link #requestEnd}. */
    boolean wasEndRequested();

    /**
     * Request the end of this session, which might be acted upon asynchronously.
     *
     * <p>This is the same as {@link #requestEnd(Status, CallerInfo, boolean)}, with no
     * {@link CallerInfo} and with {@code immediate} flag set to false.
     */
    default void requestEnd(@NonNull Status status) {
        requestEnd(status, /* endedBy= */ null, /* immediate= */ false);
    }

    /**
     * Notify the session end was requested, which might be acted upon asynchronously.
     *
@@ -70,6 +92,25 @@ interface VibrationSession {
     */
    void requestEnd(@NonNull Status status, @Nullable CallerInfo endedBy, boolean immediate);

    /**
     * Notify a vibrator has completed the last command during the playback of given vibration.
     *
     * <p>This will be called by the vibrator hardware callback indicating the last vibrate call is
     * complete (e.g. on(), perform(), compose()). This does not mean the vibration is complete,
     * since its playback might have one or more interactions with the vibrator hardware.
     */
    void notifyVibratorCallback(int vibratorId, long vibrationId);

    /**
     * Notify all synced vibrators have completed the last synchronized command during the playback
     * of given vibration.
     *
     * <p>This will be called by the vibrator manager hardware callback indicating the last
     * synchronized vibrate call is complete. This does not mean the vibration is complete, since
     * its playback might have one or more interactions with the vibrator hardware.
     */
    void notifySyncedVibratorsCallback(long vibrationId);

    /**
     * Session status with reference to values from vibratormanagerservice.proto for logging.
     */
Loading