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

Commit 8c986285 authored by Lais Andrade's avatar Lais Andrade
Browse files

Introduce VendorVibrationSession

Introduce Vibrator APIs to start vendor vibration sessions.

Vendor sessions will play vibration requests without resetting the
vibrator hardware state, allowing the vendor to customize the behaviour
between vibrations.

Vibration sessions can be ended by the client app or by the vibrator
service, allowing higher priority vibrations (e.g. ringtone, alarms) to
take control of the vibrator and play as usual.

Bug: 345414356
Test: FrameworksVibratorServicesTests
Flag: android.os.vibrator.vendor_vibration_effects
Change-Id: Id749cc240201bf398526c2416d5767a364b86cbf
parent 0b497fae
Loading
Loading
Loading
Loading
+26 −0
Original line number Diff line number Diff line
@@ -388,6 +388,7 @@ package android {
    field public static final String START_CROSS_PROFILE_ACTIVITIES = "android.permission.START_CROSS_PROFILE_ACTIVITIES";
    field public static final String START_REVIEW_PERMISSION_DECISIONS = "android.permission.START_REVIEW_PERMISSION_DECISIONS";
    field public static final String START_TASKS_FROM_RECENTS = "android.permission.START_TASKS_FROM_RECENTS";
    field @FlaggedApi("android.os.vibrator.vendor_vibration_effects") public static final String START_VIBRATION_SESSIONS = "android.permission.START_VIBRATION_SESSIONS";
    field public static final String STATUS_BAR_SERVICE = "android.permission.STATUS_BAR_SERVICE";
    field public static final String STOP_APP_SWITCHES = "android.permission.STOP_APP_SWITCHES";
    field public static final String SUBSTITUTE_NOTIFICATION_APP_NAME = "android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME";
@@ -11642,8 +11643,11 @@ package android.os {
  public abstract class Vibrator {
    method @RequiresPermission(android.Manifest.permission.ACCESS_VIBRATOR_STATE) public void addVibratorStateListener(@NonNull android.os.Vibrator.OnVibratorStateChangedListener);
    method @RequiresPermission(android.Manifest.permission.ACCESS_VIBRATOR_STATE) public void addVibratorStateListener(@NonNull java.util.concurrent.Executor, @NonNull android.os.Vibrator.OnVibratorStateChangedListener);
    method @FlaggedApi("android.os.vibrator.vendor_vibration_effects") public boolean areVendorEffectsSupported();
    method @FlaggedApi("android.os.vibrator.vendor_vibration_effects") public boolean areVendorSessionsSupported();
    method @RequiresPermission(android.Manifest.permission.ACCESS_VIBRATOR_STATE) public boolean isVibrating();
    method @RequiresPermission(android.Manifest.permission.ACCESS_VIBRATOR_STATE) public void removeVibratorStateListener(@NonNull android.os.Vibrator.OnVibratorStateChangedListener);
    method @FlaggedApi("android.os.vibrator.vendor_vibration_effects") @RequiresPermission(allOf={android.Manifest.permission.VIBRATE, android.Manifest.permission.VIBRATE_VENDOR_EFFECTS, android.Manifest.permission.START_VIBRATION_SESSIONS}) public void startVendorSession(@NonNull android.os.VibrationAttributes, @Nullable String, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.vibrator.VendorVibrationSession.Callback);
  }
  public static interface Vibrator.OnVibratorStateChangedListener {
@@ -11795,6 +11799,28 @@ package android.os.storage {
}
package android.os.vibrator {
  @FlaggedApi("android.os.vibrator.vendor_vibration_effects") public final class VendorVibrationSession implements java.lang.AutoCloseable {
    method public void cancel();
    method public void close();
    method @RequiresPermission(android.Manifest.permission.VIBRATE) public void vibrate(@NonNull android.os.VibrationEffect, @Nullable String);
    field public static final int STATUS_CANCELED = 4; // 0x4
    field public static final int STATUS_IGNORED = 2; // 0x2
    field public static final int STATUS_SUCCESS = 1; // 0x1
    field public static final int STATUS_UNKNOWN = 0; // 0x0
    field public static final int STATUS_UNKNOWN_ERROR = 5; // 0x5
    field public static final int STATUS_UNSUPPORTED = 3; // 0x3
  }
  public static interface VendorVibrationSession.Callback {
    method public void onFinished(int);
    method public void onFinishing();
    method public void onStarted(@NonNull android.os.vibrator.VendorVibrationSession);
  }
}
package android.os.vibrator.persistence {
  @FlaggedApi("android.os.vibrator.vibration_xml_apis") public final class ParsedVibration {
+9 −0
Original line number Diff line number Diff line
@@ -17,13 +17,17 @@
package android.os;

import android.os.CombinedVibration;
import android.os.ICancellationSignal;
import android.os.IVibratorStateListener;
import android.os.VibrationAttributes;
import android.os.VibratorInfo;
import android.os.vibrator.IVibrationSession;
import android.os.vibrator.IVibrationSessionCallback;

/** {@hide} */
interface IVibratorManagerService {
    int[] getVibratorIds();
    int getCapabilities();
    VibratorInfo getVibratorInfo(int vibratorId);
    @EnforcePermission("ACCESS_VIBRATOR_STATE")
    boolean isVibrating(int vibratorId);
@@ -50,4 +54,9 @@ interface IVibratorManagerService {
    oneway void performHapticFeedbackForInputDevice(int uid, int deviceId, String opPkg,
            int constant, int inputDeviceId, int inputSource, String reason, int flags,
            int privFlags);

    @EnforcePermission(allOf={"VIBRATE", "VIBRATE_VENDOR_EFFECTS", "START_VIBRATION_SESSIONS"})
    ICancellationSignal startVendorVibrationSession(int uid, int deviceId, String opPkg,
            in int[] vibratorIds, in VibrationAttributes attributes, String reason,
            in IVibrationSessionCallback callback);
}
+62 −8
Original line number Diff line number Diff line
@@ -18,8 +18,11 @@ package android.os;

import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.hardware.vibrator.IVibratorManager;
import android.os.vibrator.VendorVibrationSession;
import android.os.vibrator.VibratorInfoFactory;
import android.util.ArrayMap;
import android.util.Log;
@@ -53,6 +56,7 @@ public class SystemVibrator extends Vibrator {
    private final Object mLock = new Object();
    @GuardedBy("mLock")
    private VibratorInfo mVibratorInfo;
    private int[] mVibratorIds;

    @UnsupportedAppUsage
    public SystemVibrator(Context context) {
@@ -71,7 +75,11 @@ public class SystemVibrator extends Vibrator {
                Log.w(TAG, "Failed to retrieve vibrator info; no vibrator manager.");
                return VibratorInfo.EMPTY_VIBRATOR_INFO;
            }
            int[] vibratorIds = mVibratorManager.getVibratorIds();
            int[] vibratorIds = getVibratorIds();
            if (vibratorIds == null) {
                Log.w(TAG, "Failed to retrieve vibrator info; error retrieving vibrator ids.");
                return VibratorInfo.EMPTY_VIBRATOR_INFO;
            }
            if (vibratorIds.length == 0) {
                // It is known that the device has no vibrator, so cache and return info that
                // reflects the lack of support for effects/primitives.
@@ -95,20 +103,22 @@ public class SystemVibrator extends Vibrator {

    @Override
    public boolean hasVibrator() {
        if (mVibratorManager == null) {
        int[] vibratorIds = getVibratorIds();
        if (vibratorIds == null) {
            Log.w(TAG, "Failed to check if vibrator exists; no vibrator manager.");
            return false;
        }
        return mVibratorManager.getVibratorIds().length > 0;
        return vibratorIds.length > 0;
    }

    @Override
    public boolean isVibrating() {
        if (mVibratorManager == null) {
        int[] vibratorIds = getVibratorIds();
        if (vibratorIds == null) {
            Log.w(TAG, "Failed to vibrate; no vibrator manager.");
            return false;
        }
        for (int vibratorId : mVibratorManager.getVibratorIds()) {
        for (int vibratorId : vibratorIds) {
            if (mVibratorManager.getVibrator(vibratorId).isVibrating()) {
                return true;
            }
@@ -136,6 +146,11 @@ public class SystemVibrator extends Vibrator {
            Log.w(TAG, "Failed to add vibrate state listener; no vibrator manager.");
            return;
        }
        int[] vibratorIds = getVibratorIds();
        if (vibratorIds == null) {
            Log.w(TAG, "Failed to add vibrate state listener; error retrieving vibrator ids.");
            return;
        }
        MultiVibratorStateListener delegate = null;
        try {
            synchronized (mRegisteredListeners) {
@@ -145,7 +160,7 @@ public class SystemVibrator extends Vibrator {
                    return;
                }
                delegate = new MultiVibratorStateListener(executor, listener);
                delegate.register(mVibratorManager);
                delegate.register(mVibratorManager, vibratorIds);
                mRegisteredListeners.put(listener, delegate);
                delegate = null;
            }
@@ -183,6 +198,11 @@ public class SystemVibrator extends Vibrator {
        return getInfo().hasAmplitudeControl();
    }

    @Override
    public boolean areVendorSessionsSupported() {
        return mVibratorManager.hasCapabilities(IVibratorManager.CAP_START_SESSIONS);
    }

    @Override
    public boolean setAlwaysOnEffect(int uid, String opPkg, int alwaysOnId, VibrationEffect effect,
            VibrationAttributes attrs) {
@@ -243,6 +263,41 @@ public class SystemVibrator extends Vibrator {
        mVibratorManager.cancel(usageFilter);
    }

    @Override
    public void startVendorSession(@NonNull VibrationAttributes attrs, @Nullable String reason,
            @Nullable CancellationSignal cancellationSignal, @NonNull Executor executor,
            @NonNull VendorVibrationSession.Callback callback) {
        if (mVibratorManager == null) {
            Log.w(TAG, "Failed to start vibration session; no vibrator manager.");
            executor.execute(
                    () -> callback.onFinished(VendorVibrationSession.STATUS_UNKNOWN_ERROR));
            return;
        }
        int[] vibratorIds = getVibratorIds();
        if (vibratorIds == null) {
            Log.w(TAG, "Failed to start vibration session; error retrieving vibrator ids.");
            executor.execute(
                    () -> callback.onFinished(VendorVibrationSession.STATUS_UNKNOWN_ERROR));
            return;
        }
        mVibratorManager.startVendorSession(vibratorIds, attrs, reason, cancellationSignal,
                executor, callback);
    }

    @Nullable
    private int[] getVibratorIds() {
        synchronized (mLock) {
            if (mVibratorIds != null) {
                return mVibratorIds;
            }
            if (mVibratorManager == null) {
                Log.w(TAG, "Failed to retrieve vibrator ids; no vibrator manager.");
                return null;
            }
            return mVibratorIds = mVibratorManager.getVibratorIds();
        }
    }

    /**
     * Tries to unregister individual {@link android.os.Vibrator.OnVibratorStateChangedListener}
     * that were left registered to vibrators after failures to register them to all vibrators.
@@ -319,8 +374,7 @@ public class SystemVibrator extends Vibrator {
        }

        /** Registers a listener to all individual vibrators in {@link VibratorManager}. */
        public void register(VibratorManager vibratorManager) {
            int[] vibratorIds = vibratorManager.getVibratorIds();
        public void register(VibratorManager vibratorManager, @NonNull int[] vibratorIds) {
            synchronized (mLock) {
                for (int i = 0; i < vibratorIds.length; i++) {
                    int vibratorId = vibratorIds[i];
+102 −1
Original line number Diff line number Diff line
@@ -22,6 +22,10 @@ import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.hardware.vibrator.IVibratorManager;
import android.os.vibrator.IVibrationSession;
import android.os.vibrator.IVibrationSessionCallback;
import android.os.vibrator.VendorVibrationSession;
import android.util.ArrayMap;
import android.util.Log;
import android.util.SparseArray;
@@ -47,6 +51,8 @@ public class SystemVibratorManager extends VibratorManager {
    @GuardedBy("mLock")
    private int[] mVibratorIds;
    @GuardedBy("mLock")
    private int mCapabilities;
    @GuardedBy("mLock")
    private final SparseArray<Vibrator> mVibrators = new SparseArray<>();

    @GuardedBy("mLock")
@@ -84,6 +90,11 @@ public class SystemVibratorManager extends VibratorManager {
        }
    }

    @Override
    public boolean hasCapabilities(int capabilities) {
        return (getCapabilities() & capabilities) == capabilities;
    }

    @NonNull
    @Override
    public Vibrator getVibrator(int vibratorId) {
@@ -197,6 +208,50 @@ public class SystemVibratorManager extends VibratorManager {
        cancelVibration(usageFilter);
    }

    @Override
    public void startVendorSession(@NonNull int[] vibratorIds, @NonNull VibrationAttributes attrs,
            @Nullable String reason, @Nullable CancellationSignal cancellationSignal,
            @NonNull Executor executor, @NonNull VendorVibrationSession.Callback callback) {
        Objects.requireNonNull(vibratorIds);
        VendorVibrationSessionCallbackDelegate callbackDelegate =
                new VendorVibrationSessionCallbackDelegate(executor, callback);
        if (mService == null) {
            Log.w(TAG, "Failed to start vibration session; no vibrator manager service.");
            callbackDelegate.onFinished(VendorVibrationSession.STATUS_UNKNOWN_ERROR);
            return;
        }
        try {
            ICancellationSignal remoteCancellationSignal = mService.startVendorVibrationSession(
                    mUid, mContext.getDeviceId(), mPackageName, vibratorIds, attrs, reason,
                    callbackDelegate);
            if (cancellationSignal != null) {
                cancellationSignal.setRemote(remoteCancellationSignal);
            }
        } catch (RemoteException e) {
            Log.w(TAG, "Failed to start vibration session.", e);
            callbackDelegate.onFinished(VendorVibrationSession.STATUS_UNKNOWN_ERROR);
        }
    }

    private int getCapabilities() {
        synchronized (mLock) {
            if (mCapabilities != 0) {
                return mCapabilities;
            }
            try {
                if (mService == null) {
                    Log.w(TAG, "Failed to retrieve vibrator manager capabilities;"
                            + " no vibrator manager service.");
                } else {
                    return mCapabilities = mService.getCapabilities();
                }
            } catch (RemoteException e) {
                e.rethrowFromSystemServer();
            }
            return 0;
        }
    }

    private void cancelVibration(int usageFilter) {
        if (mService == null) {
            Log.w(TAG, "Failed to cancel vibration; no vibrator manager service.");
@@ -228,12 +283,45 @@ public class SystemVibratorManager extends VibratorManager {
        }
    }

    /** Callback for vendor vibration sessions. */
    private static class VendorVibrationSessionCallbackDelegate extends
            IVibrationSessionCallback.Stub {
        private final Executor mExecutor;
        private final VendorVibrationSession.Callback mCallback;

        VendorVibrationSessionCallbackDelegate(
                @NonNull Executor executor,
                @NonNull VendorVibrationSession.Callback callback) {
            Objects.requireNonNull(executor);
            Objects.requireNonNull(callback);
            mExecutor = executor;
            mCallback = callback;
        }

        @Override
        public void onStarted(IVibrationSession session) {
            mExecutor.execute(() -> mCallback.onStarted(new VendorVibrationSession(session)));
        }

        @Override
        public void onFinishing() {
            mExecutor.execute(() -> mCallback.onFinishing());
        }

        @Override
        public void onFinished(int status) {
            mExecutor.execute(() -> mCallback.onFinished(status));
        }
    }

    /** Controls vibrations on a single vibrator. */
    private final class SingleVibrator extends Vibrator {
        private final VibratorInfo mVibratorInfo;
        private final int[] mVibratorId;

        SingleVibrator(@NonNull VibratorInfo vibratorInfo) {
            mVibratorInfo = vibratorInfo;
            mVibratorId = new int[]{mVibratorInfo.getId()};
        }

        @Override
@@ -251,6 +339,11 @@ public class SystemVibratorManager extends VibratorManager {
            return mVibratorInfo.hasAmplitudeControl();
        }

        @Override
        public boolean areVendorSessionsSupported() {
            return SystemVibratorManager.this.hasCapabilities(IVibratorManager.CAP_START_SESSIONS);
        }

        @Override
        public boolean setAlwaysOnEffect(int uid, String opPkg, int alwaysOnId,
                @Nullable VibrationEffect effect, @Nullable VibrationAttributes attrs) {
@@ -369,5 +462,13 @@ public class SystemVibratorManager extends VibratorManager {
                }
            }
        }

        @Override
        public void startVendorSession(@NonNull VibrationAttributes attrs, String reason,
                @Nullable CancellationSignal cancellationSignal, @NonNull Executor executor,
                @NonNull VendorVibrationSession.Callback callback) {
            SystemVibratorManager.this.startVendorSession(mVibratorId, attrs, reason,
                    cancellationSignal, executor, callback);
        }
    }
}
+69 −0
Original line number Diff line number Diff line
@@ -33,6 +33,7 @@ import android.content.res.Resources;
import android.hardware.vibrator.IVibrator;
import android.media.AudioAttributes;
import android.os.vibrator.Flags;
import android.os.vibrator.VendorVibrationSession;
import android.os.vibrator.VibrationConfig;
import android.os.vibrator.VibratorFrequencyProfile;
import android.os.vibrator.VibratorFrequencyProfileLegacy;
@@ -246,6 +247,34 @@ public abstract class Vibrator {
        return getInfo().areVibrationFeaturesSupported(effect);
    }

    /**
     * Check whether the vibrator has support for vendor-specific effects.
     *
     * <p>Vendor vibration effects can be created via {@link VibrationEffect#createVendorEffect}.
     *
     * @return True if the hardware can play vendor-specific vibration effects, false otherwise.
     * @hide
     */
    @SystemApi
    @FlaggedApi(Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
    public boolean areVendorEffectsSupported() {
        return getInfo().hasCapability(IVibrator.CAP_PERFORM_VENDOR_EFFECTS);
    }

    /**
     * Check whether the vibrator has support for vendor-specific vibration sessions.
     *
     * <p>Vendor vibration sessions can be started via {@link #startVendorSession}.
     *
     * @return True if the hardware can play vendor-specific vibration sessions, false otherwise.
     * @hide
     */
    @SystemApi
    @FlaggedApi(Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
    public boolean areVendorSessionsSupported() {
        return false;
    }

    /**
     * Check whether the vibrator can be controlled by an external service with the
     * {@link IExternalVibratorService}.
@@ -922,4 +951,44 @@ public abstract class Vibrator {
    @RequiresPermission(android.Manifest.permission.ACCESS_VIBRATOR_STATE)
    public void removeVibratorStateListener(@NonNull OnVibratorStateChangedListener listener) {
    }

    /**
     * Starts a vibration session in this vibrator.
     *
     * <p>The session will start asynchronously once the vibrator control can be acquired. Once it's
     * started the {@link VendorVibrationSession} will be provided to the callback. This session
     * should be used to play vibrations until the session is ended or canceled.
     *
     * <p>The vendor app will have exclusive control over the vibrator during this session. This
     * control can be revoked by the vibrator service, which will be notified to the same session
     * callback with the {@link VendorVibrationSession#STATUS_CANCELED}.
     *
     * <p>The {@link VibrationAttributes} will be used to decide the priority of the vendor
     * vibrations that will be performed in this session. All vibrations within this session will
     * apply the same attributes.
     *
     * @param attrs    The {@link VibrationAttributes} corresponding to the vibrations that will be
     *                 performed in the session. This will be used to decide the priority of this
     *                 session against other system vibrations.
     * @param reason   The description for this session, used for debugging purposes.
     * @param cancellationSignal A signal to cancel the session before it starts.
     * @param executor The executor for the session callbacks.
     * @param callback The {@link VendorVibrationSession.Callback} for the started session.
     *
     * @see VendorVibrationSession
     * @hide
     */
    @SystemApi
    @FlaggedApi(Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
    @RequiresPermission(allOf = {
            android.Manifest.permission.VIBRATE,
            android.Manifest.permission.VIBRATE_VENDOR_EFFECTS,
            android.Manifest.permission.START_VIBRATION_SESSIONS,
    })
    public void startVendorSession(@NonNull VibrationAttributes attrs, @Nullable String reason,
            @Nullable CancellationSignal cancellationSignal, @NonNull Executor executor,
            @NonNull VendorVibrationSession.Callback callback) {
        Log.w(TAG, "startVendorSession is not supported");
        executor.execute(() -> callback.onFinished(VendorVibrationSession.STATUS_UNSUPPORTED));
    }
}
Loading