diff options
24 files changed, 1096 insertions, 2300 deletions
diff --git a/config/hiddenapi-unsupported.txt b/config/hiddenapi-unsupported.txt index 90a526bcfaf7..48aa8b2c2e30 100644 --- a/config/hiddenapi-unsupported.txt +++ b/config/hiddenapi-unsupported.txt @@ -204,7 +204,6 @@ Landroid/os/IRemoteCallback$Stub;-><init>()V Landroid/os/IUpdateEngine$Stub;-><init>()V Landroid/os/IUserManager$Stub$Proxy;-><init>(Landroid/os/IBinder;)V Landroid/os/IUserManager$Stub;->asInterface(Landroid/os/IBinder;)Landroid/os/IUserManager; -Landroid/os/IVibratorService$Stub;->asInterface(Landroid/os/IBinder;)Landroid/os/IVibratorService; Landroid/os/storage/IObbActionListener$Stub;-><init>()V Landroid/os/storage/IStorageManager$Stub$Proxy;-><init>(Landroid/os/IBinder;)V Landroid/os/storage/IStorageManager$Stub;->asInterface(Landroid/os/IBinder;)Landroid/os/storage/IStorageManager; diff --git a/core/api/current.txt b/core/api/current.txt index 838dd55e34d5..604a62e5a379 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -10424,6 +10424,7 @@ package android.content { field public static final String USAGE_STATS_SERVICE = "usagestats"; field public static final String USB_SERVICE = "usb"; field public static final String USER_SERVICE = "user"; + field public static final String VIBRATOR_MANAGER_SERVICE = "vibrator_manager"; field public static final String VIBRATOR_SERVICE = "vibrator"; field public static final String VPN_MANAGEMENT_SERVICE = "vpn_management"; field public static final String WALLPAPER_SERVICE = "wallpaper"; @@ -31608,6 +31609,7 @@ package android.os { method @NonNull public int[] areEffectsSupported(@NonNull int...); method @NonNull public boolean[] arePrimitivesSupported(@NonNull int...); method @RequiresPermission(android.Manifest.permission.VIBRATE) public abstract void cancel(); + method public int getId(); method public abstract boolean hasAmplitudeControl(); method public abstract boolean hasVibrator(); method @Deprecated @RequiresPermission(android.Manifest.permission.VIBRATE) public void vibrate(long); @@ -31622,10 +31624,12 @@ package android.os { } public abstract class VibratorManager { + method @RequiresPermission(android.Manifest.permission.VIBRATE) public abstract void cancel(); method @NonNull public abstract android.os.Vibrator getDefaultVibrator(); method @NonNull public abstract android.os.Vibrator getVibrator(int); method @NonNull public abstract int[] getVibratorIds(); - method public abstract void vibrate(@NonNull android.os.CombinedVibrationEffect); + method @RequiresPermission(android.Manifest.permission.VIBRATE) public final void vibrate(@NonNull android.os.CombinedVibrationEffect); + method @RequiresPermission(android.Manifest.permission.VIBRATE) public final void vibrate(@NonNull android.os.CombinedVibrationEffect, @Nullable android.os.VibrationAttributes); } public class WorkSource implements android.os.Parcelable { diff --git a/core/api/test-current.txt b/core/api/test-current.txt index e39b2b856661..ce846f84fcc7 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -1369,6 +1369,32 @@ package android.os { field public static final int RESOURCES_SDK_INT; } + public abstract class CombinedVibrationEffect implements android.os.Parcelable { + method public abstract long getDuration(); + } + + public static final class CombinedVibrationEffect.Mono extends android.os.CombinedVibrationEffect { + method public long getDuration(); + method @NonNull public android.os.VibrationEffect getEffect(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.os.CombinedVibrationEffect.Mono> CREATOR; + } + + public static final class CombinedVibrationEffect.Sequential extends android.os.CombinedVibrationEffect { + method @NonNull public java.util.List<java.lang.Integer> getDelays(); + method public long getDuration(); + method @NonNull public java.util.List<android.os.CombinedVibrationEffect> getEffects(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.os.CombinedVibrationEffect.Sequential> CREATOR; + } + + public static final class CombinedVibrationEffect.Stereo extends android.os.CombinedVibrationEffect { + method public long getDuration(); + method @NonNull public android.util.SparseArray<android.os.VibrationEffect> getEffects(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.os.CombinedVibrationEffect.Stereo> CREATOR; + } + public class DeviceIdleManager { method @NonNull public String[] getSystemPowerWhitelist(); method @NonNull public String[] getSystemPowerWhitelistExceptIdle(); diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index 7404e53bd8b3..d5e95708a805 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -167,9 +167,11 @@ import android.os.StatsFrameworkInitializer; import android.os.SystemConfigManager; import android.os.SystemUpdateManager; import android.os.SystemVibrator; +import android.os.SystemVibratorManager; import android.os.UserHandle; import android.os.UserManager; import android.os.Vibrator; +import android.os.VibratorManager; import android.os.health.SystemHealthManager; import android.os.image.DynamicSystemManager; import android.os.image.IDynamicSystemService; @@ -699,6 +701,13 @@ public final class SystemServiceRegistry { } }); + registerService(Context.VIBRATOR_MANAGER_SERVICE, VibratorManager.class, + new CachedServiceFetcher<VibratorManager>() { + @Override + public VibratorManager createService(ContextImpl ctx) { + return new SystemVibratorManager(ctx); + }}); + registerService(Context.VIBRATOR_SERVICE, Vibrator.class, new CachedServiceFetcher<Vibrator>() { @Override diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 2a402b204cb7..025d777f2053 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -3479,6 +3479,7 @@ public abstract class Context { STORAGE_STATS_SERVICE, WALLPAPER_SERVICE, TIME_ZONE_RULES_MANAGER_SERVICE, + VIBRATOR_MANAGER_SERVICE, VIBRATOR_SERVICE, //@hide: STATUS_BAR_SERVICE, CONNECTIVITY_SERVICE, @@ -3625,9 +3626,11 @@ public abstract class Context { * (e.g., GPS) updates. * <dt> {@link #SEARCH_SERVICE} ("search") * <dd> A {@link android.app.SearchManager} for handling search. + * <dt> {@link #VIBRATOR_MANAGER_SERVICE} ("vibrator_manager") + * <dd> A {@link android.os.VibratorManager} for accessing the device vibrators, interacting + * with individual ones and playing synchronized effects on multiple vibrators. * <dt> {@link #VIBRATOR_SERVICE} ("vibrator") - * <dd> A {@link android.os.Vibrator} for interacting with the vibrator - * hardware. + * <dd> A {@link android.os.Vibrator} for interacting with the vibrator hardware. * <dt> {@link #CONNECTIVITY_SERVICE} ("connectivity") * <dd> A {@link android.net.ConnectivityManager ConnectivityManager} for * handling management of network connections. @@ -3707,6 +3710,8 @@ public abstract class Context { * @see android.hardware.SensorManager * @see #STORAGE_SERVICE * @see android.os.storage.StorageManager + * @see #VIBRATOR_MANAGER_SERVICE + * @see android.os.VibratorManager * @see #VIBRATOR_SERVICE * @see android.os.Vibrator * @see #CONNECTIVITY_SERVICE @@ -4034,8 +4039,19 @@ public abstract class Context { public static final String WALLPAPER_SERVICE = "wallpaper"; /** - * Use with {@link #getSystemService(String)} to retrieve a {@link - * android.os.Vibrator} for interacting with the vibration hardware. + * Use with {@link #getSystemService(String)} to retrieve a {@link android.os.VibratorManager} + * for accessing the device vibrators, interacting with individual ones and playing synchronized + * effects on multiple vibrators. + * + * @see #getSystemService(String) + * @see android.os.VibratorManager + */ + @SuppressLint("ServiceName") + public static final String VIBRATOR_MANAGER_SERVICE = "vibrator_manager"; + + /** + * Use with {@link #getSystemService(String)} to retrieve a {@link android.os.Vibrator} for + * interacting with the vibration hardware. * * @see #getSystemService(String) * @see android.os.Vibrator diff --git a/core/java/android/hardware/input/InputDeviceVibrator.java b/core/java/android/hardware/input/InputDeviceVibrator.java index f4d8a65d54c6..a4817ae27fa5 100644 --- a/core/java/android/hardware/input/InputDeviceVibrator.java +++ b/core/java/android/hardware/input/InputDeviceVibrator.java @@ -74,6 +74,11 @@ final class InputDeviceVibrator extends Vibrator { } @Override + public int getId() { + return mVibratorId; + } + + @Override public boolean hasVibrator() { return true; } diff --git a/core/java/android/hardware/input/InputDeviceVibratorManager.java b/core/java/android/hardware/input/InputDeviceVibratorManager.java index a381b02ab2a6..d843407c289d 100644 --- a/core/java/android/hardware/input/InputDeviceVibratorManager.java +++ b/core/java/android/hardware/input/InputDeviceVibratorManager.java @@ -16,9 +16,12 @@ package android.hardware.input; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.os.Binder; import android.os.CombinedVibrationEffect; import android.os.NullVibrator; +import android.os.VibrationAttributes; import android.os.Vibrator; import android.os.VibratorManager; import android.util.SparseArray; @@ -86,6 +89,7 @@ public class InputDeviceVibratorManager extends VibratorManager } } + @NonNull @Override public int[] getVibratorIds() { synchronized (mVibrators) { @@ -97,6 +101,7 @@ public class InputDeviceVibratorManager extends VibratorManager } } + @NonNull @Override public Vibrator getVibrator(int vibratorId) { synchronized (mVibrators) { @@ -107,6 +112,7 @@ public class InputDeviceVibratorManager extends VibratorManager return NullVibrator.getInstance(); } + @NonNull @Override public Vibrator getDefaultVibrator() { // Returns vibrator ID 0 @@ -119,7 +125,13 @@ public class InputDeviceVibratorManager extends VibratorManager } @Override - public void vibrate(CombinedVibrationEffect effect) { + public void vibrate(int uid, String opPkg, @NonNull CombinedVibrationEffect effect, + String reason, @Nullable VibrationAttributes attributes) { mInputManager.vibrate(mDeviceId, effect, mToken); } + + @Override + public void cancel() { + mInputManager.cancelVibrate(mDeviceId, mToken); + } } diff --git a/core/java/android/os/CombinedVibrationEffect.java b/core/java/android/os/CombinedVibrationEffect.java index cb4e9cba0977..c8e682c86ea7 100644 --- a/core/java/android/os/CombinedVibrationEffect.java +++ b/core/java/android/os/CombinedVibrationEffect.java @@ -17,6 +17,7 @@ package android.os; import android.annotation.NonNull; +import android.annotation.TestApi; import android.util.SparseArray; import com.android.internal.util.Preconditions; @@ -86,7 +87,20 @@ public abstract class CombinedVibrationEffect implements Parcelable { return 0; } - /** @hide */ + /** + * Gets the estimated duration of the combined vibration in milliseconds. + * + * <p>For synced combinations this means the maximum duration of any individual {@link + * VibrationEffect}. For sequential combinations, this is a sum of each step and delays. + * + * <p>For combinations of effects without a defined end (e.g. a Waveform with a non-negative + * repeat index), this returns Long.MAX_VALUE. For effects with an unknown duration (e.g. + * Prebaked effects where the length is device and potentially run-time dependent), this returns + * -1. + * + * @hide + */ + @TestApi public abstract long getDuration(); /** @hide */ @@ -256,6 +270,7 @@ public abstract class CombinedVibrationEffect implements Parcelable { * * @hide */ + @TestApi public static final class Mono extends CombinedVibrationEffect { private final VibrationEffect mEffect; @@ -267,6 +282,7 @@ public abstract class CombinedVibrationEffect implements Parcelable { mEffect = effect; } + @NonNull public VibrationEffect getEffect() { return mEffect; } @@ -282,6 +298,7 @@ public abstract class CombinedVibrationEffect implements Parcelable { mEffect.validate(); } + /** @hide */ @Override public boolean hasVibrator(int vibratorId) { return true; @@ -307,7 +324,12 @@ public abstract class CombinedVibrationEffect implements Parcelable { } @Override - public void writeToParcel(Parcel out, int flags) { + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel out, int flags) { out.writeInt(PARCEL_TOKEN_MONO); mEffect.writeToParcel(out, flags); } @@ -335,6 +357,7 @@ public abstract class CombinedVibrationEffect implements Parcelable { * * @hide */ + @TestApi public static final class Stereo extends CombinedVibrationEffect { /** Mapping vibrator ids to effects. */ @@ -357,6 +380,7 @@ public abstract class CombinedVibrationEffect implements Parcelable { } /** Effects to be performed in sync, where each key represents the vibrator id. */ + @NonNull public SparseArray<VibrationEffect> getEffects() { return mEffects; } @@ -394,6 +418,7 @@ public abstract class CombinedVibrationEffect implements Parcelable { } } + /** @hide */ @Override public boolean hasVibrator(int vibratorId) { return mEffects.indexOfKey(vibratorId) >= 0; @@ -418,7 +443,7 @@ public abstract class CombinedVibrationEffect implements Parcelable { @Override public int hashCode() { - return Objects.hash(mEffects); + return mEffects.contentHashCode(); } @Override @@ -427,7 +452,12 @@ public abstract class CombinedVibrationEffect implements Parcelable { } @Override - public void writeToParcel(Parcel out, int flags) { + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel out, int flags) { out.writeInt(PARCEL_TOKEN_STEREO); out.writeInt(mEffects.size()); for (int i = 0; i < mEffects.size(); i++) { @@ -459,6 +489,7 @@ public abstract class CombinedVibrationEffect implements Parcelable { * * @hide */ + @TestApi public static final class Sequential extends CombinedVibrationEffect { private final List<CombinedVibrationEffect> mEffects; private final List<Integer> mDelays; @@ -480,11 +511,13 @@ public abstract class CombinedVibrationEffect implements Parcelable { } /** Effects to be performed in sequence. */ + @NonNull public List<CombinedVibrationEffect> getEffects() { return mEffects; } /** Delay to be applied before each effect in {@link #getEffects()}. */ + @NonNull public List<Integer> getDelays() { return mDelays; } @@ -542,6 +575,7 @@ public abstract class CombinedVibrationEffect implements Parcelable { } } + /** @hide */ @Override public boolean hasVibrator(int vibratorId) { final int effectCount = mEffects.size(); @@ -564,7 +598,7 @@ public abstract class CombinedVibrationEffect implements Parcelable { @Override public int hashCode() { - return Objects.hash(mEffects); + return Objects.hash(mEffects, mDelays); } @Override @@ -573,7 +607,12 @@ public abstract class CombinedVibrationEffect implements Parcelable { } @Override - public void writeToParcel(Parcel out, int flags) { + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel out, int flags) { out.writeInt(PARCEL_TOKEN_SEQUENTIAL); out.writeInt(mEffects.size()); for (int i = 0; i < mEffects.size(); i++) { diff --git a/core/java/android/os/IVibratorService.aidl b/core/java/android/os/IVibratorService.aidl deleted file mode 100644 index 1cd48dcf797b..000000000000 --- a/core/java/android/os/IVibratorService.aidl +++ /dev/null @@ -1,37 +0,0 @@ -/** - * Copyright (c) 2007, 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 android.os; - -import android.os.VibrationEffect; -import android.os.VibrationAttributes; -import android.os.VibratorInfo; -import android.os.IVibratorStateListener; - -/** {@hide} */ -interface IVibratorService -{ - boolean hasVibrator(); - boolean isVibrating(); - VibratorInfo getVibratorInfo(); - boolean registerVibratorStateListener(in IVibratorStateListener listener); - boolean unregisterVibratorStateListener(in IVibratorStateListener listener); - boolean hasAmplitudeControl(); - void vibrate(int uid, String opPkg, in VibrationEffect effect, - in VibrationAttributes attributes, String reason, IBinder token); - void cancelVibrate(IBinder token); -} - diff --git a/core/java/android/os/OWNERS b/core/java/android/os/OWNERS index 2559a33c1ab2..dac1edea7d3e 100644 --- a/core/java/android/os/OWNERS +++ b/core/java/android/os/OWNERS @@ -4,7 +4,6 @@ per-file ExternalVibration.java = michaelwr@google.com per-file IExternalVibrationController.aidl = michaelwr@google.com per-file IExternalVibratorService.aidl = michaelwr@google.com per-file IVibratorManagerService.aidl = michaelwr@google.com -per-file IVibratorService.aidl = michaelwr@google.com per-file NullVibrator.java = michaelwr@google.com per-file SystemVibrator.java = michaelwr@google.com per-file VibrationEffect.aidl = michaelwr@google.com diff --git a/core/java/android/os/SystemVibrator.java b/core/java/android/os/SystemVibrator.java index 30afe38be397..b42a495ece56 100644 --- a/core/java/android/os/SystemVibrator.java +++ b/core/java/android/os/SystemVibrator.java @@ -17,19 +17,18 @@ package android.os; import android.annotation.CallbackExecutor; -import android.annotation.IntDef; import android.annotation.NonNull; -import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.media.AudioAttributes; import android.util.ArrayMap; import android.util.Log; +import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.Arrays; import java.util.Objects; import java.util.concurrent.Executor; @@ -41,234 +40,178 @@ import java.util.concurrent.Executor; public class SystemVibrator extends Vibrator { private static final String TAG = "Vibrator"; - private static final int VIBRATOR_PRESENT_UNKNOWN = 0; - private static final int VIBRATOR_PRESENT_YES = 1; - private static final int VIBRATOR_PRESENT_NO = 2; - - @Retention(RetentionPolicy.SOURCE) - @IntDef({VIBRATOR_PRESENT_UNKNOWN, VIBRATOR_PRESENT_YES, VIBRATOR_PRESENT_NO}) - private @interface VibratorPresent {} - - private final IVibratorService mService; - private final IVibratorManagerService mManagerService; - private final Object mLock = new Object(); - private final Binder mToken = new Binder(); + private final VibratorManager mVibratorManager; private final Context mContext; - @GuardedBy("mLock") - private VibratorInfo mVibratorInfo; - @GuardedBy("mLock") - @VibratorPresent - private int mVibratorPresent; - @GuardedBy("mDelegates") - private final ArrayMap<OnVibratorStateChangedListener, - OnVibratorStateChangedListenerDelegate> mDelegates = new ArrayMap<>(); + @GuardedBy("mBrokenListeners") + private final ArrayList<AllVibratorsStateListener> mBrokenListeners = new ArrayList<>(); - @UnsupportedAppUsage - public SystemVibrator() { - mContext = null; - mService = IVibratorService.Stub.asInterface(ServiceManager.getService("vibrator")); - mManagerService = IVibratorManagerService.Stub.asInterface( - ServiceManager.getService("vibrator_manager")); - } + @GuardedBy("mRegisteredListeners") + private final ArrayMap<OnVibratorStateChangedListener, AllVibratorsStateListener> + mRegisteredListeners = new ArrayMap<>(); @UnsupportedAppUsage public SystemVibrator(Context context) { super(context); mContext = context; - mService = IVibratorService.Stub.asInterface(ServiceManager.getService("vibrator")); - mManagerService = IVibratorManagerService.Stub.asInterface( - ServiceManager.getService("vibrator_manager")); + mVibratorManager = mContext.getSystemService(VibratorManager.class); } @Override public boolean hasVibrator() { - try { - synchronized (mLock) { - if (mVibratorPresent == VIBRATOR_PRESENT_UNKNOWN && mService != null) { - mVibratorPresent = - mService.hasVibrator() ? VIBRATOR_PRESENT_YES : VIBRATOR_PRESENT_NO; - } - return mVibratorPresent == VIBRATOR_PRESENT_YES; - } - } catch (RemoteException e) { - Log.w(TAG, "Failed to query vibrator presence", e); + if (mVibratorManager == null) { + Log.w(TAG, "Failed to check if vibrator exists; no vibrator manager."); return false; } + return mVibratorManager.getVibratorIds().length > 0; } - /** - * Check whether the vibrator is vibrating. - * - * @return True if the hardware is vibrating, otherwise false. - */ @Override public boolean isVibrating() { - if (mService == null) { - Log.w(TAG, "Failed to vibrate; no vibrator service."); + if (mVibratorManager == null) { + Log.w(TAG, "Failed to vibrate; no vibrator manager."); return false; } - try { - return mService.isVibrating(); - } catch (RemoteException e) { - e.rethrowFromSystemServer(); + for (int vibratorId : mVibratorManager.getVibratorIds()) { + if (mVibratorManager.getVibrator(vibratorId).isVibrating()) { + return true; + } } return false; } - private class OnVibratorStateChangedListenerDelegate extends - IVibratorStateListener.Stub { - private final Executor mExecutor; - private final OnVibratorStateChangedListener mListener; - - OnVibratorStateChangedListenerDelegate(@NonNull OnVibratorStateChangedListener listener, - @NonNull Executor executor) { - mExecutor = executor; - mListener = listener; - } - - @Override - public void onVibrating(boolean isVibrating) { - mExecutor.execute(() -> mListener.onVibratorStateChanged(isVibrating)); + @Override + public void addVibratorStateListener(@NonNull OnVibratorStateChangedListener listener) { + Objects.requireNonNull(listener); + if (mContext == null) { + Log.w(TAG, "Failed to add vibrate state listener; no vibrator context."); + return; } + addVibratorStateListener(mContext.getMainExecutor(), listener); } - /** - * Adds a listener for vibrator state change. If the listener was previously added and not - * removed, this call will be ignored. - * - * @param listener Listener to be added. - * @param executor The {@link Executor} on which the listener's callbacks will be executed on. - */ @Override public void addVibratorStateListener( @NonNull @CallbackExecutor Executor executor, @NonNull OnVibratorStateChangedListener listener) { Objects.requireNonNull(listener); Objects.requireNonNull(executor); - if (mService == null) { - Log.w(TAG, "Failed to add vibrate state listener; no vibrator service."); + if (mVibratorManager == null) { + Log.w(TAG, "Failed to add vibrate state listener; no vibrator manager."); return; } - - synchronized (mDelegates) { - // If listener is already registered, reject and return. - if (mDelegates.containsKey(listener)) { - Log.w(TAG, "Listener already registered."); - return; - } - try { - final OnVibratorStateChangedListenerDelegate delegate = - new OnVibratorStateChangedListenerDelegate(listener, executor); - if (!mService.registerVibratorStateListener(delegate)) { - Log.w(TAG, "Failed to register vibrate state listener"); + AllVibratorsStateListener delegate = null; + try { + synchronized (mRegisteredListeners) { + // If listener is already registered, reject and return. + if (mRegisteredListeners.containsKey(listener)) { + Log.w(TAG, "Listener already registered."); return; } - mDelegates.put(listener, delegate); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); + delegate = new AllVibratorsStateListener(executor, listener); + delegate.register(mVibratorManager); + mRegisteredListeners.put(listener, delegate); + delegate = null; } + } finally { + if (delegate != null && delegate.hasRegisteredListeners()) { + // The delegate listener was left in a partial state with listeners registered to + // some but not all vibrators. Keep track of this to try to unregister them later. + synchronized (mBrokenListeners) { + mBrokenListeners.add(delegate); + } + } + tryUnregisterBrokenListeners(); } } - /** - * Adds a listener for vibrator state changes. Callbacks will be executed on the main thread. - * If the listener was previously added and not removed, this call will be ignored. - * - * @param listener listener to be added - */ - @Override - public void addVibratorStateListener(@NonNull OnVibratorStateChangedListener listener) { - Objects.requireNonNull(listener); - if (mContext == null) { - Log.w(TAG, "Failed to add vibrate state listener; no vibrator context."); - return; - } - addVibratorStateListener(mContext.getMainExecutor(), listener); - } - - /** - * Removes the listener for vibrator state changes. If the listener was not previously - * registered, this call will do nothing. - * - * @param listener Listener to be removed. - */ @Override public void removeVibratorStateListener(@NonNull OnVibratorStateChangedListener listener) { Objects.requireNonNull(listener); - if (mService == null) { - Log.w(TAG, "Failed to remove vibrate state listener; no vibrator service."); + if (mVibratorManager == null) { + Log.w(TAG, "Failed to remove vibrate state listener; no vibrator manager."); return; } - synchronized (mDelegates) { - // Check if the listener is registered, otherwise will return. - if (mDelegates.containsKey(listener)) { - final OnVibratorStateChangedListenerDelegate delegate = mDelegates.get(listener); - try { - if (!mService.unregisterVibratorStateListener(delegate)) { - Log.w(TAG, "Failed to unregister vibrate state listener"); - return; - } - mDelegates.remove(listener); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + synchronized (mRegisteredListeners) { + if (mRegisteredListeners.containsKey(listener)) { + AllVibratorsStateListener delegate = mRegisteredListeners.get(listener); + delegate.unregister(mVibratorManager); + mRegisteredListeners.remove(listener); } } + tryUnregisterBrokenListeners(); } @Override public boolean hasAmplitudeControl() { - if (mService == null) { - Log.w(TAG, "Failed to check amplitude control; no vibrator service."); + if (mVibratorManager == null) { + Log.w(TAG, "Failed to check vibrator has amplitude control; no vibrator manager."); return false; } - try { - return mService.hasAmplitudeControl(); - } catch (RemoteException e) { + int[] vibratorIds = mVibratorManager.getVibratorIds(); + if (vibratorIds.length == 0) { + return false; } - return false; + for (int vibratorId : vibratorIds) { + if (!mVibratorManager.getVibrator(vibratorId).hasAmplitudeControl()) { + return false; + } + } + return true; } @Override public boolean setAlwaysOnEffect(int uid, String opPkg, int alwaysOnId, VibrationEffect effect, AudioAttributes attributes) { - if (mManagerService == null) { - Log.w(TAG, "Failed to set always-on effect; no vibrator service."); + if (mVibratorManager == null) { + Log.w(TAG, "Failed to set always-on effect; no vibrator manager."); return false; } - try { - VibrationAttributes atr = new VibrationAttributes.Builder(attributes, effect).build(); - CombinedVibrationEffect combinedEffect = CombinedVibrationEffect.createSynced(effect); - return mManagerService.setAlwaysOnEffect(uid, opPkg, alwaysOnId, combinedEffect, atr); - } catch (RemoteException e) { - Log.w(TAG, "Failed to set always-on effect.", e); - } - return false; + VibrationAttributes attr = new VibrationAttributes.Builder(attributes, effect).build(); + CombinedVibrationEffect combinedEffect = CombinedVibrationEffect.createSynced(effect); + return mVibratorManager.setAlwaysOnEffect(uid, opPkg, alwaysOnId, combinedEffect, attr); } @Override public void vibrate(int uid, String opPkg, @NonNull VibrationEffect effect, String reason, @NonNull VibrationAttributes attributes) { - if (mService == null) { - Log.w(TAG, "Failed to vibrate; no vibrator service."); + if (mVibratorManager == null) { + Log.w(TAG, "Failed to vibrate; no vibrator manager."); return; } - try { - mService.vibrate(uid, opPkg, effect, attributes, reason, mToken); - } catch (RemoteException e) { - Log.w(TAG, "Failed to vibrate.", e); - } + CombinedVibrationEffect combinedEffect = CombinedVibrationEffect.createSynced(effect); + mVibratorManager.vibrate(uid, opPkg, combinedEffect, reason, attributes); } @Override public int[] areEffectsSupported(@VibrationEffect.EffectType int... effectIds) { - VibratorInfo vibratorInfo = getVibratorInfo(); int[] supported = new int[effectIds.length]; - for (int i = 0; i < effectIds.length; i++) { - supported[i] = vibratorInfo == null - ? Vibrator.VIBRATION_EFFECT_SUPPORT_UNKNOWN - : vibratorInfo.isEffectSupported(effectIds[i]); + if (mVibratorManager == null) { + Log.w(TAG, "Failed to check supported effects; no vibrator manager."); + Arrays.fill(supported, Vibrator.VIBRATION_EFFECT_SUPPORT_NO); + return supported; + } + int[] vibratorIds = mVibratorManager.getVibratorIds(); + if (vibratorIds.length == 0) { + Arrays.fill(supported, Vibrator.VIBRATION_EFFECT_SUPPORT_NO); + return supported; + } + int[][] vibratorSupportMap = new int[vibratorIds.length][effectIds.length]; + for (int i = 0; i < vibratorIds.length; i++) { + vibratorSupportMap[i] = mVibratorManager.getVibrator( + vibratorIds[i]).areEffectsSupported(effectIds); + } + Arrays.fill(supported, Vibrator.VIBRATION_EFFECT_SUPPORT_YES); + for (int effectIdx = 0; effectIdx < effectIds.length; effectIdx++) { + for (int vibratorIdx = 0; vibratorIdx < vibratorIds.length; vibratorIdx++) { + int effectSupported = vibratorSupportMap[vibratorIdx][effectIdx]; + if (effectSupported == Vibrator.VIBRATION_EFFECT_SUPPORT_NO) { + supported[effectIdx] = Vibrator.VIBRATION_EFFECT_SUPPORT_NO; + break; + } else if (effectSupported == Vibrator.VIBRATION_EFFECT_SUPPORT_UNKNOWN) { + supported[effectIdx] = Vibrator.VIBRATION_EFFECT_SUPPORT_UNKNOWN; + } + } } return supported; } @@ -276,42 +219,169 @@ public class SystemVibrator extends Vibrator { @Override public boolean[] arePrimitivesSupported( @NonNull @VibrationEffect.Composition.Primitive int... primitiveIds) { - VibratorInfo vibratorInfo = getVibratorInfo(); boolean[] supported = new boolean[primitiveIds.length]; - for (int i = 0; i < primitiveIds.length; i++) { - supported[i] = vibratorInfo == null - ? false : vibratorInfo.isPrimitiveSupported(primitiveIds[i]); + if (mVibratorManager == null) { + Log.w(TAG, "Failed to check supported primitives; no vibrator manager."); + Arrays.fill(supported, false); + return supported; + } + int[] vibratorIds = mVibratorManager.getVibratorIds(); + if (vibratorIds.length == 0) { + Arrays.fill(supported, false); + return supported; + } + boolean[][] vibratorSupportMap = new boolean[vibratorIds.length][primitiveIds.length]; + for (int i = 0; i < vibratorIds.length; i++) { + vibratorSupportMap[i] = mVibratorManager.getVibrator( + vibratorIds[i]).arePrimitivesSupported(primitiveIds); + } + Arrays.fill(supported, true); + for (int primitiveIdx = 0; primitiveIdx < primitiveIds.length; primitiveIdx++) { + for (int vibratorIdx = 0; vibratorIdx < vibratorIds.length; vibratorIdx++) { + if (!vibratorSupportMap[vibratorIdx][primitiveIdx]) { + supported[primitiveIdx] = false; + break; + } + } } return supported; } @Override public void cancel() { - if (mService == null) { + if (mVibratorManager == null) { + Log.w(TAG, "Failed to cancel vibrate; no vibrator manager."); return; } - try { - mService.cancelVibrate(mToken); - } catch (RemoteException e) { - Log.w(TAG, "Failed to cancel vibration.", e); + mVibratorManager.cancel(); + } + + /** + * Tries to unregister individual {@link android.os.Vibrator.OnVibratorStateChangedListener} + * that were left registered to vibrators after failures to register them to all vibrators. + * + * <p>This might happen if {@link AllVibratorsStateListener} fails to register to any vibrator + * and also fails to unregister any previously registered single listeners to other vibrators. + * + * <p>This method never throws {@link RuntimeException} if it fails to unregister again, it will + * fail silently and attempt to unregister the same broken listener later. + */ + private void tryUnregisterBrokenListeners() { + synchronized (mBrokenListeners) { + try { + for (int i = mBrokenListeners.size(); --i >= 0; ) { + mBrokenListeners.get(i).unregister(mVibratorManager); + mBrokenListeners.remove(i); + } + } catch (RuntimeException e) { + Log.w(TAG, "Failed to unregister broken listener", e); + } } } - @Nullable - private VibratorInfo getVibratorInfo() { - try { + /** Listener for a single vibrator state change. */ + private static class SingleVibratorStateListener implements OnVibratorStateChangedListener { + private final AllVibratorsStateListener mAllVibratorsListener; + private final int mVibratorIdx; + + SingleVibratorStateListener(AllVibratorsStateListener listener, int vibratorIdx) { + mAllVibratorsListener = listener; + mVibratorIdx = vibratorIdx; + } + + @Override + public void onVibratorStateChanged(boolean isVibrating) { + mAllVibratorsListener.onVibrating(mVibratorIdx, isVibrating); + } + } + + /** Listener for all vibrators state change. */ + private static class AllVibratorsStateListener { + private final Object mLock = new Object(); + private final Executor mExecutor; + private final OnVibratorStateChangedListener mDelegate; + + @GuardedBy("mLock") + private final SparseArray<SingleVibratorStateListener> mVibratorListeners = + new SparseArray<>(); + + @GuardedBy("mLock") + private int mInitializedMask; + @GuardedBy("mLock") + private int mVibratingMask; + + AllVibratorsStateListener(@NonNull Executor executor, + @NonNull OnVibratorStateChangedListener listener) { + mExecutor = executor; + mDelegate = listener; + } + + boolean hasRegisteredListeners() { + synchronized (mLock) { + return mVibratorListeners.size() > 0; + } + } + + void register(VibratorManager vibratorManager) { + int[] vibratorIds = vibratorManager.getVibratorIds(); synchronized (mLock) { - if (mVibratorInfo != null) { - return mVibratorInfo; + for (int i = 0; i < vibratorIds.length; i++) { + int vibratorId = vibratorIds[i]; + SingleVibratorStateListener listener = new SingleVibratorStateListener(this, i); + try { + vibratorManager.getVibrator(vibratorId).addVibratorStateListener(mExecutor, + listener); + mVibratorListeners.put(vibratorId, listener); + } catch (RuntimeException e) { + try { + unregister(vibratorManager); + } catch (RuntimeException e1) { + Log.w(TAG, + "Failed to unregister listener while recovering from a failed " + + "register call", e1); + } + throw e; + } } - if (mService == null) { - return null; + } + } + + void unregister(VibratorManager vibratorManager) { + synchronized (mLock) { + for (int i = mVibratorListeners.size(); --i >= 0; ) { + int vibratorId = mVibratorListeners.keyAt(i); + SingleVibratorStateListener listener = mVibratorListeners.valueAt(i); + vibratorManager.getVibrator(vibratorId).removeVibratorStateListener(listener); + mVibratorListeners.removeAt(i); } - return mVibratorInfo = mService.getVibratorInfo(); } - } catch (RemoteException e) { - Log.w(TAG, "Failed to query vibrator info"); - throw e.rethrowFromSystemServer(); + } + + void onVibrating(int vibratorIdx, boolean vibrating) { + mExecutor.execute(() -> { + boolean anyVibrating; + synchronized (mLock) { + int allInitializedMask = 1 << mVibratorListeners.size() - 1; + int vibratorMask = 1 << vibratorIdx; + if ((mInitializedMask & vibratorMask) == 0) { + // First state report for this vibrator, set vibrating initial value. + mInitializedMask |= vibratorMask; + mVibratingMask |= vibrating ? vibratorMask : 0; + } else { + // Flip vibrating value, if changed. + boolean prevVibrating = (mVibratingMask & vibratorMask) != 0; + if (prevVibrating != vibrating) { + mVibratingMask ^= vibratorMask; + } + } + if (mInitializedMask != allInitializedMask) { + // Wait for all vibrators initial state to be reported before delegating. + return; + } + anyVibrating = mVibratingMask != 0; + } + mDelegate.onVibratorStateChanged(anyVibrating); + }); } } } diff --git a/core/java/android/os/SystemVibratorManager.java b/core/java/android/os/SystemVibratorManager.java new file mode 100644 index 000000000000..b528eb157e36 --- /dev/null +++ b/core/java/android/os/SystemVibratorManager.java @@ -0,0 +1,360 @@ +/* + * Copyright (C) 2021 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 android.os; + +import android.annotation.CallbackExecutor; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.media.AudioAttributes; +import android.util.ArrayMap; +import android.util.Log; +import android.util.SparseArray; + +import com.android.internal.annotations.GuardedBy; + +import java.util.Objects; +import java.util.concurrent.Executor; + +/** + * VibratorManager implementation that controls the system vibrators. + * + * @hide + */ +public class SystemVibratorManager extends VibratorManager { + private static final String TAG = "VibratorManager"; + + private final IVibratorManagerService mService; + private final Context mContext; + private final Binder mToken = new Binder(); + private final Object mLock = new Object(); + @GuardedBy("mLock") + private int[] mVibratorIds; + @GuardedBy("mLock") + private final SparseArray<Vibrator> mVibrators = new SparseArray<>(); + + @GuardedBy("mLock") + private final ArrayMap<Vibrator.OnVibratorStateChangedListener, + OnVibratorStateChangedListenerDelegate> mListeners = new ArrayMap<>(); + + /** + * @hide to prevent subclassing from outside of the framework + */ + public SystemVibratorManager(Context context) { + super(context); + mContext = context; + mService = IVibratorManagerService.Stub.asInterface( + ServiceManager.getService(Context.VIBRATOR_MANAGER_SERVICE)); + } + + @NonNull + @Override + public int[] getVibratorIds() { + synchronized (mLock) { + if (mVibratorIds != null) { + return mVibratorIds; + } + try { + if (mService == null) { + Log.w(TAG, "Failed to retrieve vibrator ids; no vibrator manager service."); + } else { + return mVibratorIds = mService.getVibratorIds(); + } + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + return new int[0]; + } + } + + @NonNull + @Override + public Vibrator getVibrator(int vibratorId) { + synchronized (mLock) { + Vibrator vibrator = mVibrators.get(vibratorId); + if (vibrator != null) { + return vibrator; + } + VibratorInfo info = null; + try { + if (mService == null) { + Log.w(TAG, "Failed to retrieve vibrator; no vibrator manager service."); + } else { + info = mService.getVibratorInfo(vibratorId); + } + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + if (info != null) { + vibrator = new SingleVibrator(info); + mVibrators.put(vibratorId, vibrator); + } else { + vibrator = NullVibrator.getInstance(); + } + return vibrator; + } + } + + @NonNull + @Override + public Vibrator getDefaultVibrator() { + return mContext.getSystemService(Vibrator.class); + } + + @Override + public boolean setAlwaysOnEffect(int uid, String opPkg, int alwaysOnId, + @Nullable CombinedVibrationEffect effect, @Nullable VibrationAttributes attributes) { + if (mService == null) { + Log.w(TAG, "Failed to set always-on effect; no vibrator manager service."); + return false; + } + try { + return mService.setAlwaysOnEffect(uid, opPkg, alwaysOnId, effect, attributes); + } catch (RemoteException e) { + Log.w(TAG, "Failed to set always-on effect.", e); + } + return false; + } + + @Override + public void vibrate(int uid, String opPkg, @NonNull CombinedVibrationEffect effect, + String reason, @Nullable VibrationAttributes attributes) { + if (mService == null) { + Log.w(TAG, "Failed to vibrate; no vibrator manager service."); + return; + } + try { + mService.vibrate(uid, opPkg, effect, attributes, reason, mToken); + } catch (RemoteException e) { + Log.w(TAG, "Failed to vibrate.", e); + } + } + + @Override + public void cancel() { + if (mService == null) { + Log.w(TAG, "Failed to cancel vibration; no vibrator manager service."); + return; + } + try { + mService.cancelVibrate(mToken); + } catch (RemoteException e) { + Log.w(TAG, "Failed to cancel vibration.", e); + } + } + + /** Listener for vibrations on a single vibrator. */ + private static class OnVibratorStateChangedListenerDelegate extends + IVibratorStateListener.Stub { + private final Executor mExecutor; + private final Vibrator.OnVibratorStateChangedListener mListener; + + OnVibratorStateChangedListenerDelegate( + @NonNull Vibrator.OnVibratorStateChangedListener listener, + @NonNull Executor executor) { + mExecutor = executor; + mListener = listener; + } + + @Override + public void onVibrating(boolean isVibrating) { + mExecutor.execute(() -> mListener.onVibratorStateChanged(isVibrating)); + } + } + + /** Controls vibrations on a single vibrator. */ + private final class SingleVibrator extends Vibrator { + private final VibratorInfo mVibratorInfo; + + SingleVibrator(@NonNull VibratorInfo vibratorInfo) { + mVibratorInfo = vibratorInfo; + } + + @Override + public int getId() { + return mVibratorInfo.getId(); + } + + @Override + public boolean hasVibrator() { + return true; + } + + @Override + public boolean hasAmplitudeControl() { + return mVibratorInfo.hasAmplitudeControl(); + } + + @NonNull + @Override + public int[] areEffectsSupported(@NonNull int... effectIds) { + int[] supported = new int[effectIds.length]; + for (int i = 0; i < effectIds.length; i++) { + supported[i] = mVibratorInfo.isEffectSupported(effectIds[i]); + } + return supported; + } + + @Override + public boolean[] arePrimitivesSupported( + @NonNull @VibrationEffect.Composition.Primitive int... primitiveIds) { + boolean[] supported = new boolean[primitiveIds.length]; + for (int i = 0; i < primitiveIds.length; i++) { + supported[i] = mVibratorInfo.isPrimitiveSupported(primitiveIds[i]); + } + return supported; + } + + @Override + public boolean setAlwaysOnEffect(int uid, String opPkg, int alwaysOnId, + @Nullable VibrationEffect effect, @Nullable AudioAttributes attributes) { + if (mService == null) { + Log.w(TAG, "Failed to set always-on effect on vibrator " + mVibratorInfo.getId() + + "; no vibrator manager service."); + return false; + } + try { + VibrationAttributes attr = new VibrationAttributes.Builder( + attributes, effect).build(); + CombinedVibrationEffect combined = CombinedVibrationEffect.startSynced() + .addVibrator(mVibratorInfo.getId(), effect) + .combine(); + return mService.setAlwaysOnEffect(uid, opPkg, alwaysOnId, combined, attr); + } catch (RemoteException e) { + Log.w(TAG, "Failed to set always-on effect on vibrator " + mVibratorInfo.getId()); + } + return false; + } + + @Override + public void vibrate(int uid, String opPkg, @NonNull VibrationEffect vibe, String reason, + @NonNull VibrationAttributes attributes) { + if (mService == null) { + Log.w(TAG, "Failed to vibrate on vibrator " + mVibratorInfo.getId() + + "; no vibrator manager service."); + return; + } + try { + CombinedVibrationEffect combined = CombinedVibrationEffect.startSynced() + .addVibrator(mVibratorInfo.getId(), vibe) + .combine(); + mService.vibrate(uid, opPkg, combined, attributes, reason, mToken); + } catch (RemoteException e) { + Log.w(TAG, "Failed to vibrate.", e); + } + } + + @Override + public void cancel() { + if (mService == null) { + Log.w(TAG, "Failed to cancel vibration on vibrator " + mVibratorInfo.getId() + + "; no vibrator manager service."); + return; + } + try { + mService.cancelVibrate(mToken); + } catch (RemoteException e) { + Log.w(TAG, "Failed to cancel vibration on vibrator " + mVibratorInfo.getId(), e); + } + } + + @Override + public boolean isVibrating() { + if (mService == null) { + Log.w(TAG, "Failed to check status of vibrator " + mVibratorInfo.getId() + + "; no vibrator service."); + return false; + } + try { + return mService.isVibrating(mVibratorInfo.getId()); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + return false; + } + + @Override + public void addVibratorStateListener(@NonNull OnVibratorStateChangedListener listener) { + Objects.requireNonNull(listener); + if (mContext == null) { + Log.w(TAG, "Failed to add vibrate state listener; no vibrator context."); + return; + } + addVibratorStateListener(mContext.getMainExecutor(), listener); + } + + @Override + public void addVibratorStateListener( + @NonNull @CallbackExecutor Executor executor, + @NonNull OnVibratorStateChangedListener listener) { + Objects.requireNonNull(listener); + Objects.requireNonNull(executor); + if (mService == null) { + Log.w(TAG, + "Failed to add vibrate state listener to vibrator " + mVibratorInfo.getId() + + "; no vibrator service."); + return; + } + synchronized (mLock) { + // If listener is already registered, reject and return. + if (mListeners.containsKey(listener)) { + Log.w(TAG, "Listener already registered."); + return; + } + try { + OnVibratorStateChangedListenerDelegate delegate = + new OnVibratorStateChangedListenerDelegate(listener, executor); + if (!mService.registerVibratorStateListener(mVibratorInfo.getId(), delegate)) { + Log.w(TAG, "Failed to add vibrate state listener to vibrator " + + mVibratorInfo.getId()); + return; + } + mListeners.put(listener, delegate); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + } + + @Override + public void removeVibratorStateListener(@NonNull OnVibratorStateChangedListener listener) { + Objects.requireNonNull(listener); + if (mService == null) { + Log.w(TAG, "Failed to remove vibrate state listener from vibrator " + + mVibratorInfo.getId() + "; no vibrator service."); + return; + } + synchronized (mLock) { + // Check if the listener is registered, otherwise will return. + if (mListeners.containsKey(listener)) { + OnVibratorStateChangedListenerDelegate delegate = mListeners.get(listener); + try { + if (!mService.unregisterVibratorStateListener(mVibratorInfo.getId(), + delegate)) { + Log.w(TAG, "Failed to remove vibrate state listener from vibrator " + + mVibratorInfo.getId()); + return; + } + mListeners.remove(listener); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + } + } + } +} diff --git a/core/java/android/os/Vibrator.java b/core/java/android/os/Vibrator.java index 7d85d13094a1..d6fa733927fb 100644 --- a/core/java/android/os/Vibrator.java +++ b/core/java/android/os/Vibrator.java @@ -184,6 +184,15 @@ public abstract class Vibrator { } /** + * Return the ID of this vibrator. + * + * @return The id of the vibrator controlled by this service. + */ + public int getId() { + return -1; + } + + /** * Check whether the hardware has a vibrator. * * @return True if the hardware has a vibrator, else false. diff --git a/core/java/android/os/VibratorManager.java b/core/java/android/os/VibratorManager.java index 1d5a58745279..5dd38b6cbd86 100644 --- a/core/java/android/os/VibratorManager.java +++ b/core/java/android/os/VibratorManager.java @@ -17,45 +17,123 @@ package android.os; import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.RequiresPermission; +import android.annotation.SystemService; +import android.app.ActivityThread; +import android.content.Context; +import android.util.Log; /** - * VibratorManager provides access to multiple vibrators, as well as the ability to run them in - * a synchronized fashion. + * Class that provides access to all vibrators from the device, as well as the ability to run them + * in a synchronized fashion. + * <p> + * If your process exits, any vibration you started will stop. + * </p> */ +@SystemService(Context.VIBRATOR_MANAGER_SERVICE) public abstract class VibratorManager { - /** @hide */ - protected static final String TAG = "VibratorManager"; + private static final String TAG = "VibratorManager"; + + private final String mPackageName; /** - * {@hide} + * @hide to prevent subclassing from outside of the framework */ public VibratorManager() { + mPackageName = ActivityThread.currentPackageName(); + } + + /** + * @hide to prevent subclassing from outside of the framework + */ + protected VibratorManager(Context context) { + mPackageName = context.getOpPackageName(); } /** - * This method lists all available actuator ids, returning a possible empty list. - * If the device has only a single actuator, this should return a single entry with a - * default id. + * List all available vibrator ids, returning a possible empty list. + * + * @return An array containing the ids of the vibrators available on the device. */ @NonNull public abstract int[] getVibratorIds(); /** - * Returns a Vibrator service for given id. - * This allows users to perform a vibration effect on a single actuator. - */ + * Retrieve a single vibrator by id. + * + * @param vibratorId The id of the vibrator to be retrieved. + * @return The vibrator with given {@code vibratorId}, never null. + */ @NonNull public abstract Vibrator getVibrator(int vibratorId); /** - * Returns the system default Vibrator service. - */ + * Returns the system default Vibrator service. + */ @NonNull public abstract Vibrator getDefaultVibrator(); /** - * Vibrates all actuators by passing each VibrationEffect within CombinedVibrationEffect - * to the respective actuator, in sync. + * Configure an always-on haptics effect. + * + * @hide + */ + @RequiresPermission(android.Manifest.permission.VIBRATE_ALWAYS_ON) + public boolean setAlwaysOnEffect(int uid, String opPkg, int alwaysOnId, + @Nullable CombinedVibrationEffect effect, @Nullable VibrationAttributes attributes) { + Log.w(TAG, "Always-on effects aren't supported"); + return false; + } + + /** + * Vibrate with a given combination of effects. + * + * <p> + * Pass in a {@link CombinedVibrationEffect} representing a combination of {@link + * VibrationEffect} to be played on one or more vibrators. + * </p> + * + * @param effect an array of longs of times for which to turn the vibrator on or off. + */ + @RequiresPermission(android.Manifest.permission.VIBRATE) + public final void vibrate(@NonNull CombinedVibrationEffect effect) { + vibrate(effect, null); + } + + /** + * Vibrate with a given combination of effects. + * + * <p> + * Pass in a {@link CombinedVibrationEffect} representing a combination of {@link + * VibrationEffect} to be played on one or more vibrators. + * </p> + * + * @param effect an array of longs of times for which to turn the vibrator on or off. + * @param attributes {@link VibrationAttributes} corresponding to the vibration. For example, + * specify {@link VibrationAttributes#USAGE_ALARM} for alarm vibrations or + * {@link VibrationAttributes#USAGE_RINGTONE} for vibrations associated with + * incoming calls. + */ + @RequiresPermission(android.Manifest.permission.VIBRATE) + public final void vibrate(@NonNull CombinedVibrationEffect effect, + @Nullable VibrationAttributes attributes) { + vibrate(Process.myUid(), mPackageName, effect, null, attributes); + } + + /** + * Like {@link #vibrate(CombinedVibrationEffect, VibrationAttributes)}, but allows the + * caller to specify the vibration is owned by someone else and set reason for vibration. + * + * @hide + */ + @RequiresPermission(android.Manifest.permission.VIBRATE) + public abstract void vibrate(int uid, String opPkg, @NonNull CombinedVibrationEffect effect, + String reason, @Nullable VibrationAttributes attributes); + + /** + * Turn all the vibrators off. */ - public abstract void vibrate(@NonNull CombinedVibrationEffect effect); + @RequiresPermission(android.Manifest.permission.VIBRATE) + public abstract void cancel(); } diff --git a/core/tests/coretests/src/android/os/CombinedVibrationEffectTest.java b/core/tests/coretests/src/android/os/CombinedVibrationEffectTest.java index 564103efef65..11239db9f404 100644 --- a/core/tests/coretests/src/android/os/CombinedVibrationEffectTest.java +++ b/core/tests/coretests/src/android/os/CombinedVibrationEffectTest.java @@ -17,6 +17,8 @@ package android.os; import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertTrue; import static org.testng.Assert.assertThrows; @@ -31,7 +33,6 @@ import java.util.Arrays; @Presubmit @RunWith(JUnit4.class) public class CombinedVibrationEffectTest { - private static final VibrationEffect VALID_EFFECT = VibrationEffect.createOneShot(10, 255); private static final VibrationEffect INVALID_EFFECT = new VibrationEffect.OneShot(-1, -1); @@ -172,6 +173,37 @@ public class CombinedVibrationEffectTest { } @Test + public void testHasVibratorMono_returnsTrueForAnyVibrator() { + CombinedVibrationEffect effect = CombinedVibrationEffect.createSynced( + VibrationEffect.get(VibrationEffect.EFFECT_CLICK)); + assertTrue(effect.hasVibrator(0)); + assertTrue(effect.hasVibrator(1)); + } + + @Test + public void testHasVibratorStereo_returnsOnlyTheIdsSet() { + CombinedVibrationEffect effect = CombinedVibrationEffect.startSynced() + .addVibrator(1, VibrationEffect.get(VibrationEffect.EFFECT_CLICK)) + .combine(); + assertFalse(effect.hasVibrator(0)); + assertTrue(effect.hasVibrator(1)); + assertFalse(effect.hasVibrator(2)); + } + + @Test + public void testHasVibratorSequential_returnsNestedVibrators() { + CombinedVibrationEffect effect = CombinedVibrationEffect.startSequential() + .addNext(1, VibrationEffect.get(VibrationEffect.EFFECT_CLICK)) + .addNext(CombinedVibrationEffect.startSynced() + .addVibrator(2, VibrationEffect.get(VibrationEffect.EFFECT_TICK)) + .combine()) + .combine(); + assertFalse(effect.hasVibrator(0)); + assertTrue(effect.hasVibrator(1)); + assertTrue(effect.hasVibrator(2)); + } + + @Test public void testSerializationMono() { CombinedVibrationEffect original = CombinedVibrationEffect.createSynced(VALID_EFFECT); diff --git a/services/core/java/com/android/server/OWNERS b/services/core/java/com/android/server/OWNERS index a6cfae492db9..c6a8660d8797 100644 --- a/services/core/java/com/android/server/OWNERS +++ b/services/core/java/com/android/server/OWNERS @@ -2,7 +2,7 @@ per-file ConnectivityService.java,ConnectivityServiceInitializer.java,NetworkManagementService.java,NsdService.java = file:/services/core/java/com/android/server/net/OWNERS # Vibrator / Threads -per-file VibratorManagerService.java, VibratorService.java, DisplayThread.java = michaelwr@google.com, ogunwale@google.com +per-file VibratorManagerService.java, DisplayThread.java = michaelwr@google.com, ogunwale@google.com # Zram writeback per-file ZramWriteback.java = minchan@google.com, rajekumar@google.com diff --git a/services/core/java/com/android/server/VibratorManagerService.java b/services/core/java/com/android/server/VibratorManagerService.java index e7e5d67ff9f4..d264f8570cf2 100644 --- a/services/core/java/com/android/server/VibratorManagerService.java +++ b/services/core/java/com/android/server/VibratorManagerService.java @@ -18,9 +18,14 @@ package com.android.server; import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.ActivityManager; import android.app.AppOpsManager; +import android.content.BroadcastReceiver; import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; import android.content.pm.PackageManager; +import android.content.pm.PackageManagerInternal; import android.hardware.vibrator.IVibrator; import android.os.BatteryStats; import android.os.Binder; @@ -74,6 +79,7 @@ import java.util.function.Function; /** System implementation of {@link IVibratorManagerService}. */ public class VibratorManagerService extends IVibratorManagerService.Stub { private static final String TAG = "VibratorManagerService"; + private static final String EXTERNAL_VIBRATOR_SERVICE = "external_vibrator_service"; private static final boolean DEBUG = false; private static final VibrationAttributes DEFAULT_ATTRIBUTES = new VibrationAttributes.Builder().build(); @@ -89,7 +95,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { @Override public void onStart() { mService = new VibratorManagerService(getContext(), new Injector()); - publishBinderService("vibrator_manager", mService); + publishBinderService(Context.VIBRATOR_MANAGER_SERVICE, mService); } @Override @@ -105,6 +111,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { private final Object mLock = new Object(); private final Context mContext; + private final String mSystemUiPackage; private final PowerManager.WakeLock mWakeLock; private final IBatteryStats mBatteryStatsService; private final Handler mHandler; @@ -128,6 +135,26 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { private VibrationScaler mVibrationScaler; private InputDeviceDelegate mInputDeviceDelegate; + private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) { + synchronized (mLock) { + // When the system is entering a non-interactive state, we want + // to cancel vibrations in case a misbehaving app has abandoned + // 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 (mCurrentVibration != null + && !isSystemHapticFeedback(mCurrentVibration.getVibration())) { + mNextVibration = null; + mCurrentVibration.cancel(); + } + } + } + } + }; + static native long nativeInit(OnSyncedVibrationCompleteListener listener); static native long nativeGetFinalizer(); @@ -155,6 +182,9 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { com.android.internal.R.integer.config_previousVibrationsDumpLimit); mVibratorManagerRecords = new VibratorManagerRecords(dumpLimit); + mSystemUiPackage = LocalServices.getService(PackageManagerInternal.class) + .getSystemUiServiceComponent().getPackageName(); + mBatteryStatsService = IBatteryStats.Stub.asInterface(ServiceManager.getService( BatteryStats.SERVICE_NAME)); @@ -184,6 +214,12 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { for (int i = 0; i < mVibrators.size(); i++) { mVibrators.valueAt(i).off(); } + + IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_SCREEN_OFF); + context.registerReceiver(mIntentReceiver, filter); + + injector.addService(EXTERNAL_VIBRATOR_SERVICE, new ExternalVibratorService()); } /** Finish initialization at boot phase {@link SystemService#PHASE_SYSTEM_SERVICES_READY}. */ @@ -371,7 +407,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { mVibratorManagerRecords.record(mCurrentExternalVibration); mCurrentExternalVibration.externalVibration.mute(); mCurrentExternalVibration = null; - // TODO(b/167946816): set external control to false + setExternalControl(false); } } finally { Binder.restoreCallingIdentity(ident); @@ -432,6 +468,12 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { } } + private void setExternalControl(boolean externalControl) { + for (int i = 0; i < mVibrators.size(); i++) { + mVibrators.valueAt(i).setExternalControl(externalControl); + } + } + @GuardedBy("mLock") private void updateAlwaysOnLocked(AlwaysOnVibration vib) { for (int i = 0; i < vib.effects.size(); i++) { @@ -507,6 +549,12 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { } @GuardedBy("mLock") + private void endVibrationLocked(ExternalVibrationHolder vib, Vibration.Status status) { + vib.end(status); + mVibratorManagerRecords.record(vib); + } + + @GuardedBy("mLock") private void reportFinishedVibrationLocked(Vibration.Status status) { Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "reportFinishVibrationLocked"); Trace.asyncTraceEnd(Trace.TRACE_TAG_VIBRATOR, "vibration", 0); @@ -827,6 +875,13 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { == PackageManager.PERMISSION_GRANTED; } + private boolean isSystemHapticFeedback(Vibration vib) { + if (vib.attrs.getUsage() != VibrationAttributes.USAGE_TOUCH) { + return false; + } + return vib.uid == Process.SYSTEM_UID || vib.uid == 0 || mSystemUiPackage.equals(vib.opPkg); + } + @GuardedBy("mLock") private void onAllVibratorsLocked(Consumer<VibratorController> consumer) { for (int i = 0; i < mVibrators.size(); i++) { @@ -859,6 +914,10 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { VibratorController.OnVibrationCompleteListener listener) { return new VibratorController(vibratorId, listener); } + + void addService(String name, IBinder service) { + ServiceManager.addService(name, service); + } } /** @@ -1088,14 +1147,14 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { } pw.println(); pw.println(" mCurrentVibration:"); - pw.println(" " + mCurrentVibration == null - ? null : mCurrentVibration.getVibration().getDebugInfo()); + pw.println(" " + (mCurrentVibration == null + ? null : mCurrentVibration.getVibration().getDebugInfo())); pw.println(" mNextVibration:"); - pw.println(" " + mNextVibration == null - ? null : mNextVibration.getVibration().getDebugInfo()); + pw.println(" " + (mNextVibration == null + ? null : mNextVibration.getVibration().getDebugInfo())); pw.println(" mCurrentExternalVibration:"); - pw.println(" " + mCurrentExternalVibration == null - ? null : mCurrentExternalVibration.getDebugInfo()); + pw.println(" " + (mCurrentExternalVibration == null + ? null : mCurrentExternalVibration.getDebugInfo())); pw.println(); pw.println(" mVibrationSettings=" + mVibrationSettings); for (int i = 0; i < mPreviousVibrations.size(); i++) { @@ -1168,6 +1227,145 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { } } + /** Implementation of {@link IExternalVibratorService} to be triggered on external control. */ + private final class ExternalVibratorService extends IExternalVibratorService.Stub { + ExternalVibrationDeathRecipient mCurrentExternalDeathRecipient; + + @Override + public int onExternalVibrationStart(ExternalVibration vib) { + if (!hasExternalControlCapability()) { + return IExternalVibratorService.SCALE_MUTE; + } + if (ActivityManager.checkComponentPermission(android.Manifest.permission.VIBRATE, + vib.getUid(), -1 /*owningUid*/, true /*exported*/) + != PackageManager.PERMISSION_GRANTED) { + Slog.w(TAG, "pkg=" + vib.getPackage() + ", uid=" + vib.getUid() + + " tried to play externally controlled vibration" + + " without VIBRATE permission, ignoring."); + return IExternalVibratorService.SCALE_MUTE; + } + + int mode = checkAppOpModeLocked(vib.getUid(), vib.getPackage(), + vib.getVibrationAttributes()); + if (mode != AppOpsManager.MODE_ALLOWED) { + ExternalVibrationHolder vibHolder = new ExternalVibrationHolder(vib); + vibHolder.scale = IExternalVibratorService.SCALE_MUTE; + if (mode == AppOpsManager.MODE_ERRORED) { + Slog.w(TAG, "Would be an error: external vibrate from uid " + vib.getUid()); + endVibrationLocked(vibHolder, Vibration.Status.IGNORED_ERROR_APP_OPS); + } else { + endVibrationLocked(vibHolder, Vibration.Status.IGNORED_APP_OPS); + } + return vibHolder.scale; + } + + VibrationThread cancelingVibration = null; + int scale; + synchronized (mLock) { + if (mCurrentExternalVibration != null + && mCurrentExternalVibration.externalVibration.equals(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 (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. + if (mCurrentVibration != null) { + mNextVibration = null; + mCurrentVibration.cancel(); + cancelingVibration = mCurrentVibration; + } + } else { + endVibrationLocked(mCurrentExternalVibration, Vibration.Status.CANCELLED); + } + // At this point we either have an externally controlled vibration playing, or + // no vibration playing. Since the interface defines that only one externally + // controlled vibration can play at a time, by returning something other than + // SCALE_MUTE from this function we can be assured that if we are currently + // playing vibration, it will be muted in favor of the new vibration. + // + // Note that this doesn't support multiple concurrent external controls, as we + // would need to mute the old one still if it came from a different controller. + mCurrentExternalVibration = new ExternalVibrationHolder(vib); + mCurrentExternalDeathRecipient = new ExternalVibrationDeathRecipient(); + vib.linkToDeath(mCurrentExternalDeathRecipient); + mCurrentExternalVibration.scale = mVibrationScaler.getExternalVibrationScale( + vib.getVibrationAttributes().getUsage()); + scale = mCurrentExternalVibration.scale; + } + + if (cancelingVibration != null) { + try { + cancelingVibration.join(); + } catch (InterruptedException e) { + Slog.w("Interrupted while waiting for vibration to finish before starting " + + "external control", e); + } + } + if (DEBUG) { + Slog.d(TAG, "Vibrator going under external control."); + } + setExternalControl(true); + if (DEBUG) { + Slog.e(TAG, "Playing external vibration: " + vib); + } + return scale; + } + + @Override + public void onExternalVibrationStop(ExternalVibration vib) { + synchronized (mLock) { + if (mCurrentExternalVibration != null + && mCurrentExternalVibration.externalVibration.equals(vib)) { + if (DEBUG) { + Slog.e(TAG, "Stopping external vibration" + vib); + } + stopExternalVibrateLocked(Vibration.Status.FINISHED); + } + } + } + + private void stopExternalVibrateLocked(Vibration.Status status) { + Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "stopExternalVibrateLocked"); + try { + if (mCurrentExternalVibration == null) { + return; + } + endVibrationLocked(mCurrentExternalVibration, status); + mCurrentExternalVibration.externalVibration.unlinkToDeath( + mCurrentExternalDeathRecipient); + mCurrentExternalDeathRecipient = null; + mCurrentExternalVibration = null; + setExternalControl(false); + } finally { + Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR); + } + } + + private boolean hasExternalControlCapability() { + for (int i = 0; i < mVibrators.size(); i++) { + if (mVibrators.valueAt(i).hasCapability(IVibrator.CAP_EXTERNAL_CONTROL)) { + return true; + } + } + return false; + } + + private class ExternalVibrationDeathRecipient implements IBinder.DeathRecipient { + public void binderDied() { + synchronized (mLock) { + if (mCurrentExternalVibration != null) { + if (DEBUG) { + Slog.d(TAG, "External vibration finished because binder died"); + } + stopExternalVibrateLocked(Vibration.Status.CANCELLED); + } + } + } + } + } + /** Provide limited functionality from {@link VibratorManagerService} as shell commands. */ private final class VibratorManagerShellCommand extends ShellCommand { public static final String SHELL_PACKAGE_NAME = "com.android.shell"; diff --git a/services/core/java/com/android/server/VibratorService.java b/services/core/java/com/android/server/VibratorService.java deleted file mode 100644 index 2ac365d6d11b..000000000000 --- a/services/core/java/com/android/server/VibratorService.java +++ /dev/null @@ -1,1243 +0,0 @@ -/* - * Copyright (C) 2008 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; - -import android.annotation.Nullable; -import android.app.ActivityManager; -import android.app.AppOpsManager; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.pm.PackageManager; -import android.content.pm.PackageManagerInternal; -import android.hardware.vibrator.IVibrator; -import android.os.BatteryStats; -import android.os.Binder; -import android.os.CombinedVibrationEffect; -import android.os.ExternalVibration; -import android.os.Handler; -import android.os.IBinder; -import android.os.IExternalVibratorService; -import android.os.IVibratorService; -import android.os.IVibratorStateListener; -import android.os.Looper; -import android.os.PowerManager; -import android.os.Process; -import android.os.ResultReceiver; -import android.os.ServiceManager; -import android.os.ShellCallback; -import android.os.ShellCommand; -import android.os.Trace; -import android.os.VibrationAttributes; -import android.os.VibrationEffect; -import android.os.Vibrator; -import android.os.VibratorInfo; -import android.util.Slog; -import android.util.proto.ProtoOutputStream; - -import com.android.internal.annotations.GuardedBy; -import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.app.IBatteryStats; -import com.android.internal.util.DumpUtils; -import com.android.server.vibrator.InputDeviceDelegate; -import com.android.server.vibrator.Vibration; -import com.android.server.vibrator.VibrationScaler; -import com.android.server.vibrator.VibrationSettings; -import com.android.server.vibrator.VibrationThread; -import com.android.server.vibrator.VibratorController; -import com.android.server.vibrator.VibratorController.OnVibrationCompleteListener; - -import java.io.FileDescriptor; -import java.io.PrintWriter; -import java.lang.ref.WeakReference; -import java.util.ArrayList; -import java.util.LinkedList; -import java.util.concurrent.atomic.AtomicInteger; - -/** System implementation of {@link IVibratorService}. */ -public class VibratorService extends IVibratorService.Stub { - private static final String TAG = "VibratorService"; - private static final boolean DEBUG = false; - private static final String EXTERNAL_VIBRATOR_SERVICE = "external_vibrator_service"; - - // Default vibration attributes. Used when vibration is requested without attributes - private static final VibrationAttributes DEFAULT_ATTRIBUTES = - new VibrationAttributes.Builder().build(); - - // Used to generate globally unique vibration ids. - private final AtomicInteger mNextVibrationId = new AtomicInteger(1); // 0 = no callback - - private final LinkedList<Vibration.DebugInfo> mPreviousRingVibrations; - private final LinkedList<Vibration.DebugInfo> mPreviousNotificationVibrations; - private final LinkedList<Vibration.DebugInfo> mPreviousAlarmVibrations; - private final LinkedList<Vibration.DebugInfo> mPreviousExternalVibrations; - private final LinkedList<Vibration.DebugInfo> mPreviousVibrations; - private final int mPreviousVibrationsLimit; - private final Handler mH; - private final Object mLock = new Object(); - private final VibratorController mVibratorController; - private final VibrationCallbacks mVibrationCallbacks = new VibrationCallbacks(); - - private final Context mContext; - private final PowerManager.WakeLock mWakeLock; - private final AppOpsManager mAppOps; - private final IBatteryStats mBatteryStatsService; - private final String mSystemUiPackage; - private VibrationSettings mVibrationSettings; - private VibrationScaler mVibrationScaler; - private InputDeviceDelegate mInputDeviceDelegate; - - @GuardedBy("mLock") - private VibrationThread mThread; - @GuardedBy("mLock") - private VibrationThread mNextVibrationThread; - - @GuardedBy("mLock") - private Vibration mCurrentVibration; - private int mCurVibUid = -1; - private ExternalVibrationHolder mCurrentExternalVibration; - - /** - * Implementation of {@link VibrationThread.VibrationCallbacks} that reports finished - * vibrations. - */ - private final class VibrationCallbacks implements VibrationThread.VibrationCallbacks { - - @Override - public boolean prepareSyncedVibration(long requiredCapabilities, int[] vibratorIds) { - return false; - } - - @Override - public boolean triggerSyncedVibration(long vibrationId) { - return false; - } - - @Override - public void cancelSyncedVibration() { - } - - @Override - public void onVibrationEnded(long vibrationId, Vibration.Status status) { - if (DEBUG) { - Slog.d(TAG, "Vibration thread finished with status " + status); - } - synchronized (mLock) { - if (mCurrentVibration != null && mCurrentVibration.id == vibrationId) { - mThread = null; - reportFinishVibrationLocked(status); - if (mNextVibrationThread != null) { - startVibrationThreadLocked(mNextVibrationThread); - mNextVibrationThread = null; - } - } - } - } - } - - /** - * Implementation of {@link OnVibrationCompleteListener} with a weak reference to this service. - */ - private static final class VibrationCompleteListener implements OnVibrationCompleteListener { - private WeakReference<VibratorService> mServiceRef; - - VibrationCompleteListener(VibratorService service) { - mServiceRef = new WeakReference<>(service); - } - - @Override - public void onComplete(int vibratorId, long vibrationId) { - VibratorService service = mServiceRef.get(); - if (service != null) { - service.onVibrationComplete(vibratorId, vibrationId); - } - } - } - - /** Holder for a {@link ExternalVibration}. */ - private final class ExternalVibrationHolder { - - public final ExternalVibration externalVibration; - public int scale; - - private final long mStartTimeDebug; - private long mEndTimeDebug; - private Vibration.Status mStatus; - - private ExternalVibrationHolder(ExternalVibration externalVibration) { - this.externalVibration = externalVibration; - this.scale = IExternalVibratorService.SCALE_NONE; - mStartTimeDebug = System.currentTimeMillis(); - mStatus = Vibration.Status.RUNNING; - } - - public void end(Vibration.Status status) { - if (mStatus != Vibration.Status.RUNNING) { - // Vibration already ended, keep first ending status set and ignore this one. - return; - } - mStatus = status; - mEndTimeDebug = System.currentTimeMillis(); - } - - public Vibration.DebugInfo getDebugInfo() { - return new Vibration.DebugInfo( - mStartTimeDebug, mEndTimeDebug, /* effect= */ null, /* originalEffect= */ null, - scale, externalVibration.getVibrationAttributes(), - externalVibration.getUid(), externalVibration.getPackage(), - /* reason= */ null, mStatus); - } - } - - VibratorService(Context context) { - this(context, new Injector()); - } - - @VisibleForTesting - VibratorService(Context context, Injector injector) { - mH = injector.createHandler(Looper.myLooper()); - mVibratorController = injector.createVibratorController( - new VibrationCompleteListener(this)); - - // Reset the hardware to a default state, in case this is a runtime - // restart instead of a fresh boot. - mVibratorController.off(); - - mContext = context; - PowerManager pm = context.getSystemService(PowerManager.class); - mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "*vibrator*"); - mWakeLock.setReferenceCounted(true); - - mAppOps = mContext.getSystemService(AppOpsManager.class); - mBatteryStatsService = IBatteryStats.Stub.asInterface(ServiceManager.getService( - BatteryStats.SERVICE_NAME)); - mSystemUiPackage = LocalServices.getService(PackageManagerInternal.class) - .getSystemUiServiceComponent().getPackageName(); - - mPreviousVibrationsLimit = mContext.getResources().getInteger( - com.android.internal.R.integer.config_previousVibrationsDumpLimit); - - mPreviousRingVibrations = new LinkedList<>(); - mPreviousNotificationVibrations = new LinkedList<>(); - mPreviousAlarmVibrations = new LinkedList<>(); - mPreviousVibrations = new LinkedList<>(); - mPreviousExternalVibrations = new LinkedList<>(); - - IntentFilter filter = new IntentFilter(); - filter.addAction(Intent.ACTION_SCREEN_OFF); - context.registerReceiver(mIntentReceiver, filter); - - injector.addService(EXTERNAL_VIBRATOR_SERVICE, new ExternalVibratorService()); - } - - public void systemReady() { - Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "VibratorService#systemReady"); - try { - mVibrationSettings = new VibrationSettings(mContext, mH); - mVibrationScaler = new VibrationScaler(mContext, mVibrationSettings); - mInputDeviceDelegate = new InputDeviceDelegate(mContext, mH); - - mVibrationSettings.addListener(this::updateVibrators); - - mContext.registerReceiver(new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - mVibrationSettings.updateSettings(); - } - }, new IntentFilter(Intent.ACTION_USER_SWITCHED), null, mH); - - updateVibrators(); - } finally { - Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR); - } - } - - /** Callback for when vibration is complete, to be called by native. */ - @VisibleForTesting - public void onVibrationComplete(int vibratorId, long vibrationId) { - synchronized (mLock) { - if (mCurrentVibration != null && mCurrentVibration.id == vibrationId - && mThread != null) { - if (DEBUG) { - Slog.d(TAG, "Vibration onComplete callback, notifying VibrationThread"); - } - // Let the thread playing the vibration handle the callback, since it might be - // expecting the vibrator to turn off multiple times during a single vibration. - mThread.vibratorComplete(vibratorId); - } - } - } - - @Override // Binder call - public boolean hasVibrator() { - // For now, we choose to ignore the presence of input devices that have vibrators - // when reporting whether the device has a vibrator. Applications often use this - // information to decide whether to enable certain features so they expect the - // result of hasVibrator() to be constant. For now, just report whether - // the device has a built-in vibrator. - return mVibratorController.isAvailable(); - } - - @Override // Binder call - public boolean isVibrating() { - if (!hasPermission(android.Manifest.permission.ACCESS_VIBRATOR_STATE)) { - throw new SecurityException("Requires ACCESS_VIBRATOR_STATE permission"); - } - return mVibratorController.isVibrating(); - } - - @Override // Binder call - public VibratorInfo getVibratorInfo() { - return mVibratorController.getVibratorInfo(); - } - - @Override // Binder call - public boolean registerVibratorStateListener(IVibratorStateListener listener) { - if (!hasPermission(android.Manifest.permission.ACCESS_VIBRATOR_STATE)) { - throw new SecurityException("Requires ACCESS_VIBRATOR_STATE permission"); - } - return mVibratorController.registerVibratorStateListener(listener); - } - - @Override // Binder call - @GuardedBy("mLock") - public boolean unregisterVibratorStateListener(IVibratorStateListener listener) { - if (!hasPermission(android.Manifest.permission.ACCESS_VIBRATOR_STATE)) { - throw new SecurityException("Requires ACCESS_VIBRATOR_STATE permission"); - } - return mVibratorController.unregisterVibratorStateListener(listener); - } - - @Override // Binder call - public boolean hasAmplitudeControl() { - // Input device vibrators always support amplitude controls. - return mInputDeviceDelegate.isAvailable() - || mVibratorController.hasCapability(IVibrator.CAP_AMPLITUDE_CONTROL); - } - - private void verifyIncomingUid(int uid) { - if (uid == Binder.getCallingUid()) { - return; - } - if (Binder.getCallingPid() == Process.myPid()) { - return; - } - mContext.enforcePermission(android.Manifest.permission.UPDATE_APP_OPS_STATS, - Binder.getCallingPid(), Binder.getCallingUid(), null); - } - - /** - * Validate the incoming VibrationEffect. - * - * We can't throw exceptions here since we might be called from some system_server component, - * which would bring the whole system down. - * - * @return whether the VibrationEffect is valid - */ - private static boolean verifyVibrationEffect(VibrationEffect effect) { - if (effect == null) { - // Effect must not be null. - Slog.wtf(TAG, "effect must not be null"); - return false; - } - try { - effect.validate(); - } catch (Exception e) { - Slog.wtf(TAG, "Encountered issue when verifying VibrationEffect.", e); - return false; - } - return true; - } - - private VibrationEffect fixupVibrationEffect(VibrationEffect effect) { - if (effect instanceof VibrationEffect.Prebaked - && ((VibrationEffect.Prebaked) effect).shouldFallback()) { - VibrationEffect.Prebaked prebaked = (VibrationEffect.Prebaked) effect; - VibrationEffect fallback = mVibrationSettings.getFallbackEffect(prebaked.getId()); - return new VibrationEffect.Prebaked(prebaked.getId(), prebaked.getEffectStrength(), - fallback); - } - return effect; - } - - private VibrationAttributes fixupVibrationAttributes(VibrationAttributes attrs) { - if (attrs == null) { - attrs = DEFAULT_ATTRIBUTES; - } - if (shouldBypassDnd(attrs)) { - if (!(hasPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) - || hasPermission(android.Manifest.permission.MODIFY_PHONE_STATE) - || hasPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING))) { - final int flags = attrs.getFlags() - & ~VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY; - attrs = new VibrationAttributes.Builder(attrs) - .setFlags(flags, attrs.getFlags()).build(); - } - } - - return attrs; - } - - @Override // Binder call - public void vibrate(int uid, String opPkg, VibrationEffect effect, - @Nullable VibrationAttributes attrs, String reason, IBinder token) { - Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "vibrate, reason = " + reason); - try { - if (!hasPermission(android.Manifest.permission.VIBRATE)) { - throw new SecurityException("Requires VIBRATE permission"); - } - if (token == null) { - Slog.e(TAG, "token must not be null"); - return; - } - verifyIncomingUid(uid); - if (!verifyVibrationEffect(effect)) { - return; - } - effect = fixupVibrationEffect(effect); - attrs = fixupVibrationAttributes(attrs); - Vibration vib = new Vibration(token, mNextVibrationId.getAndIncrement(), - CombinedVibrationEffect.createSynced(effect), attrs, uid, opPkg, reason); - - // If our current vibration is longer than the new vibration and is the same amplitude, - // then just let the current one finish. - synchronized (mLock) { - VibrationEffect currentEffect = - mCurrentVibration == null ? null : getEffect(mCurrentVibration); - if (effect instanceof VibrationEffect.OneShot - && currentEffect instanceof VibrationEffect.OneShot) { - VibrationEffect.OneShot newOneShot = (VibrationEffect.OneShot) effect; - VibrationEffect.OneShot currentOneShot = - (VibrationEffect.OneShot) currentEffect; - if (currentOneShot.getDuration() > newOneShot.getDuration() - && newOneShot.getAmplitude() == currentOneShot.getAmplitude()) { - if (DEBUG) { - Slog.d(TAG, - "Ignoring incoming vibration in favor of current vibration"); - } - endVibrationLocked(vib, Vibration.Status.IGNORED_FOR_ONGOING); - return; - } - } - - - // If something has external control of the vibrator, assume that it's more - // important for now. - if (mCurrentExternalVibration != null) { - if (DEBUG) { - Slog.d(TAG, "Ignoring incoming vibration for current external vibration"); - } - endVibrationLocked(vib, Vibration.Status.IGNORED_FOR_EXTERNAL); - return; - } - - // If the current vibration is repeating and the incoming one is non-repeating, - // then ignore the non-repeating vibration. This is so that we don't cancel - // vibrations that are meant to grab the attention of the user, like ringtones and - // alarms, in favor of one-shot vibrations that are likely quite short. - if (!isRepeatingVibration(effect) - && mCurrentVibration != null - && isRepeatingVibration(currentEffect)) { - if (DEBUG) { - Slog.d(TAG, "Ignoring incoming vibration in favor of alarm vibration"); - } - endVibrationLocked(vib, Vibration.Status.IGNORED_FOR_ALARM); - return; - } - - if (!mVibrationSettings.shouldVibrateForUid(uid, vib.attrs.getUsage())) { - Slog.e(TAG, "Ignoring incoming vibration as process with" - + " uid= " + uid + " is background," - + " attrs= " + vib.attrs); - endVibrationLocked(vib, Vibration.Status.IGNORED_BACKGROUND); - return; - } - final long ident = Binder.clearCallingIdentity(); - try { - doCancelVibrateLocked(Vibration.Status.CANCELLED); - startVibrationLocked(vib); - boolean isNextVibration = mNextVibrationThread != null - && vib.equals(mNextVibrationThread.getVibration()); - - if (!vib.hasEnded() && !vib.equals(mCurrentVibration) && !isNextVibration) { - // Vibration was unexpectedly ignored: add to list for debugging - endVibrationLocked(vib, Vibration.Status.IGNORED); - } - } finally { - Binder.restoreCallingIdentity(ident); - } - } - } finally { - Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR); - } - } - - private boolean hasPermission(String permission) { - return mContext.checkCallingOrSelfPermission(permission) - == PackageManager.PERMISSION_GRANTED; - } - - private static boolean isRepeatingVibration(VibrationEffect effect) { - return effect.getDuration() == Long.MAX_VALUE; - } - - private static <T extends VibrationEffect> T getEffect(Vibration vib) { - return (T) ((CombinedVibrationEffect.Mono) vib.getEffect()).getEffect(); - } - - private void endVibrationLocked(Vibration vib, Vibration.Status status) { - final LinkedList<Vibration.DebugInfo> previousVibrations; - switch (vib.attrs.getUsage()) { - case VibrationAttributes.USAGE_NOTIFICATION: - previousVibrations = mPreviousNotificationVibrations; - break; - case VibrationAttributes.USAGE_RINGTONE: - previousVibrations = mPreviousRingVibrations; - break; - case VibrationAttributes.USAGE_ALARM: - previousVibrations = mPreviousAlarmVibrations; - break; - default: - previousVibrations = mPreviousVibrations; - } - if (previousVibrations.size() > mPreviousVibrationsLimit) { - previousVibrations.removeFirst(); - } - vib.end(status); - previousVibrations.addLast(vib.getDebugInfo()); - } - - private void endVibrationLocked(ExternalVibrationHolder vib, Vibration.Status status) { - if (mPreviousExternalVibrations.size() > mPreviousVibrationsLimit) { - mPreviousExternalVibrations.removeFirst(); - } - vib.end(status); - mPreviousExternalVibrations.addLast(vib.getDebugInfo()); - } - - @Override // Binder call - public void cancelVibrate(IBinder token) { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.VIBRATE, - "cancelVibrate"); - - synchronized (mLock) { - if (mCurrentVibration != null && mCurrentVibration.token == token) { - if (DEBUG) { - Slog.d(TAG, "Canceling vibration."); - } - final long ident = Binder.clearCallingIdentity(); - try { - mNextVibrationThread = null; - doCancelVibrateLocked(Vibration.Status.CANCELLED); - } finally { - Binder.restoreCallingIdentity(ident); - } - } - } - } - - @GuardedBy("mLock") - private void doCancelVibrateLocked(Vibration.Status status) { - Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "doCancelVibrateLocked"); - try { - if (mThread != null) { - mThread.cancel(); - } - mInputDeviceDelegate.cancelVibrateIfAvailable(); - if (mCurrentExternalVibration != null) { - endVibrationLocked(mCurrentExternalVibration, status); - mCurrentExternalVibration.externalVibration.mute(); - mCurrentExternalVibration = null; - mVibratorController.setExternalControl(false); - } - } finally { - Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR); - } - } - - @GuardedBy("mLock") - private void startVibrationLocked(final Vibration vib) { - Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "startVibrationLocked"); - try { - if (!shouldVibrate(vib)) { - return; - } - applyVibrationIntensityScalingLocked(vib); - startVibrationInnerLocked(vib); - } finally { - Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR); - } - } - - @GuardedBy("mLock") - private void startVibrationInnerLocked(Vibration vib) { - Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "startVibrationInnerLocked"); - try { - boolean inputDevicesAvailable = mInputDeviceDelegate.vibrateIfAvailable( - vib.uid, vib.opPkg, vib.getEffect(), vib.reason, vib.attrs); - if (inputDevicesAvailable) { - endVibrationLocked(vib, Vibration.Status.FORWARDED_TO_INPUT_DEVICES); - } else if (mThread == null) { - startVibrationThreadLocked(new VibrationThread(vib, mVibratorController, mWakeLock, - mBatteryStatsService, mVibrationCallbacks)); - } else { - mNextVibrationThread = new VibrationThread(vib, mVibratorController, mWakeLock, - mBatteryStatsService, mVibrationCallbacks); - } - } finally { - Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR); - } - } - - @GuardedBy("mLock") - private void startVibrationThreadLocked(VibrationThread thread) { - Trace.asyncTraceBegin(Trace.TRACE_TAG_VIBRATOR, "vibration", 0); - mCurrentVibration = thread.getVibration(); - mThread = thread; - mThread.start(); - } - - /** Scale the vibration effect by the intensity as appropriate based its intent. */ - private void applyVibrationIntensityScalingLocked(Vibration vib) { - vib.updateEffect(mVibrationScaler.scale(vib.getEffect(), vib.attrs.getUsage())); - } - - private static boolean shouldBypassDnd(VibrationAttributes attrs) { - return attrs.isFlagSet(VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY); - } - - private int getAppOpMode(int uid, String packageName, VibrationAttributes attrs) { - int mode = mAppOps.checkAudioOpNoThrow(AppOpsManager.OP_VIBRATE, - attrs.getAudioUsage(), uid, packageName); - if (mode == AppOpsManager.MODE_ALLOWED) { - mode = mAppOps.startOpNoThrow(AppOpsManager.OP_VIBRATE, uid, packageName); - } - - if (mode == AppOpsManager.MODE_IGNORED && shouldBypassDnd(attrs)) { - // If we're just ignoring the vibration op then this is set by DND and we should ignore - // if we're asked to bypass. AppOps won't be able to record this operation, so make - // sure we at least note it in the logs for debugging. - Slog.d(TAG, "Bypassing DND for vibrate from uid " + uid); - mode = AppOpsManager.MODE_ALLOWED; - } - return mode; - } - - private boolean shouldVibrate(Vibration vib) { - if (!mVibrationSettings.shouldVibrateForPowerMode(vib.attrs.getUsage())) { - endVibrationLocked(vib, Vibration.Status.IGNORED_FOR_POWER); - return false; - } - - int intensity = mVibrationSettings.getCurrentIntensity(vib.attrs.getUsage()); - if (intensity == Vibrator.VIBRATION_INTENSITY_OFF) { - endVibrationLocked(vib, Vibration.Status.IGNORED_FOR_SETTINGS); - return false; - } - - if (!mVibrationSettings.shouldVibrateForRingerMode(vib.attrs.getUsage())) { - if (DEBUG) { - Slog.e(TAG, "Vibrate ignored, not vibrating for ringtones"); - } - endVibrationLocked(vib, Vibration.Status.IGNORED_RINGTONE); - return false; - } - - final int mode = getAppOpMode(vib.uid, vib.opPkg, vib.attrs); - if (mode != AppOpsManager.MODE_ALLOWED) { - 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. - Slog.w(TAG, "Would be an error: vibrate from uid " + vib.uid); - endVibrationLocked(vib, Vibration.Status.IGNORED_ERROR_APP_OPS); - } else { - endVibrationLocked(vib, Vibration.Status.IGNORED_APP_OPS); - } - return false; - } - - return true; - } - - @GuardedBy("mLock") - private void reportFinishVibrationLocked(Vibration.Status status) { - Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "reportFinishVibrationLocked"); - try { - if (mCurrentVibration != null) { - endVibrationLocked(mCurrentVibration, status); - mAppOps.finishOp(AppOpsManager.OP_VIBRATE, mCurrentVibration.uid, - mCurrentVibration.opPkg); - - Trace.asyncTraceEnd(Trace.TRACE_TAG_VIBRATOR, "vibration", 0); - mCurrentVibration = null; - } - } finally { - Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR); - } - } - - @VisibleForTesting - void updateVibrators() { - synchronized (mLock) { - boolean inputDevicesChanged = mInputDeviceDelegate.updateInputDeviceVibrators( - mVibrationSettings.shouldVibrateInputDevices()); - - if (mCurrentVibration == null) { - return; - } - - if (inputDevicesChanged || !mVibrationSettings.shouldVibrateForPowerMode( - mCurrentVibration.attrs.getUsage())) { - // If the state changes out from under us then just reset. - doCancelVibrateLocked(Vibration.Status.CANCELLED); - } - } - } - - private boolean isSystemHapticFeedback(Vibration vib) { - if (vib.attrs.getUsage() != VibrationAttributes.USAGE_TOUCH) { - return false; - } - return vib.uid == Process.SYSTEM_UID || vib.uid == 0 || mSystemUiPackage.equals(vib.opPkg); - } - - private void dumpInternal(PrintWriter pw) { - pw.println("Vibrator Service:"); - synchronized (mLock) { - pw.print(" mCurrentVibration="); - if (mCurrentVibration != null) { - pw.println(mCurrentVibration.getDebugInfo().toString()); - } else { - pw.println("null"); - } - pw.print(" mCurrentExternalVibration="); - if (mCurrentExternalVibration != null) { - pw.println(mCurrentExternalVibration.getDebugInfo().toString()); - } else { - pw.println("null"); - } - pw.println(" mVibratorController=" + mVibratorController); - pw.println(" mVibrationSettings=" + mVibrationSettings); - pw.println(); - pw.println(" Previous ring vibrations:"); - for (Vibration.DebugInfo info : mPreviousRingVibrations) { - pw.print(" "); - pw.println(info.toString()); - } - - pw.println(" Previous notification vibrations:"); - for (Vibration.DebugInfo info : mPreviousNotificationVibrations) { - pw.println(" " + info); - } - - pw.println(" Previous alarm vibrations:"); - for (Vibration.DebugInfo info : mPreviousAlarmVibrations) { - pw.println(" " + info); - } - - pw.println(" Previous vibrations:"); - for (Vibration.DebugInfo info : mPreviousVibrations) { - pw.println(" " + info); - } - - pw.println(" Previous external vibrations:"); - for (Vibration.DebugInfo info : mPreviousExternalVibrations) { - pw.println(" " + info); - } - } - } - - private void dumpProto(FileDescriptor fd) { - final ProtoOutputStream proto = new ProtoOutputStream(fd); - - synchronized (mLock) { - if (mCurrentVibration != null) { - mCurrentVibration.getDebugInfo().dumpProto(proto, - VibratorServiceDumpProto.CURRENT_VIBRATION); - } - if (mCurrentExternalVibration != null) { - mCurrentExternalVibration.getDebugInfo().dumpProto(proto, - VibratorServiceDumpProto.CURRENT_EXTERNAL_VIBRATION); - } - proto.write(VibratorServiceDumpProto.IS_VIBRATING, mVibratorController.isVibrating()); - proto.write(VibratorServiceDumpProto.VIBRATOR_UNDER_EXTERNAL_CONTROL, - mVibratorController.isUnderExternalControl()); - mVibrationSettings.dumpProto(proto); - - for (Vibration.DebugInfo info : mPreviousRingVibrations) { - info.dumpProto(proto, VibratorServiceDumpProto.PREVIOUS_RING_VIBRATIONS); - } - - for (Vibration.DebugInfo info : mPreviousNotificationVibrations) { - info.dumpProto(proto, VibratorServiceDumpProto.PREVIOUS_NOTIFICATION_VIBRATIONS); - } - - for (Vibration.DebugInfo info : mPreviousAlarmVibrations) { - info.dumpProto(proto, VibratorServiceDumpProto.PREVIOUS_ALARM_VIBRATIONS); - } - - for (Vibration.DebugInfo info : mPreviousVibrations) { - info.dumpProto(proto, VibratorServiceDumpProto.PREVIOUS_VIBRATIONS); - } - - for (Vibration.DebugInfo info : mPreviousExternalVibrations) { - info.dumpProto(proto, VibratorServiceDumpProto.PREVIOUS_EXTERNAL_VIBRATIONS); - } - } - proto.flush(); - } - - /** Point of injection for test dependencies */ - @VisibleForTesting - static class Injector { - - VibratorController createVibratorController(OnVibrationCompleteListener listener) { - return new VibratorController(/* vibratorId= */ -1, listener); - } - - Handler createHandler(Looper looper) { - return new Handler(looper); - } - - void addService(String name, IBinder service) { - ServiceManager.addService(name, service); - } - } - - BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) { - synchronized (mLock) { - // When the system is entering a non-interactive state, we want - // to cancel vibrations in case a misbehaving app has abandoned - // 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 (mCurrentVibration != null && !isSystemHapticFeedback(mCurrentVibration)) { - mNextVibrationThread = null; - doCancelVibrateLocked(Vibration.Status.CANCELLED); - } - } - } - } - }; - - @Override - protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return; - - final long ident = Binder.clearCallingIdentity(); - - boolean isDumpProto = false; - for (String arg : args) { - if (arg.equals("--proto")) { - isDumpProto = true; - } - } - try { - if (isDumpProto) { - dumpProto(fd); - } else { - dumpInternal(pw); - } - } finally { - Binder.restoreCallingIdentity(ident); - } - } - - @Override - public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err, - String[] args, ShellCallback callback, ResultReceiver resultReceiver) { - new VibratorShellCommand(this).exec(this, in, out, err, args, callback, resultReceiver); - } - - final class ExternalVibratorService extends IExternalVibratorService.Stub { - ExternalVibrationDeathRecipient mCurrentExternalDeathRecipient; - - @Override - public int onExternalVibrationStart(ExternalVibration vib) { - if (!mVibratorController.hasCapability(IVibrator.CAP_EXTERNAL_CONTROL)) { - return IExternalVibratorService.SCALE_MUTE; - } - if (ActivityManager.checkComponentPermission(android.Manifest.permission.VIBRATE, - vib.getUid(), -1 /*owningUid*/, true /*exported*/) - != PackageManager.PERMISSION_GRANTED) { - Slog.w(TAG, "pkg=" + vib.getPackage() + ", uid=" + vib.getUid() - + " tried to play externally controlled vibration" - + " without VIBRATE permission, ignoring."); - return IExternalVibratorService.SCALE_MUTE; - } - - int mode = getAppOpMode(vib.getUid(), vib.getPackage(), vib.getVibrationAttributes()); - if (mode != AppOpsManager.MODE_ALLOWED) { - ExternalVibrationHolder vibHolder = new ExternalVibrationHolder(vib); - vibHolder.scale = SCALE_MUTE; - if (mode == AppOpsManager.MODE_ERRORED) { - Slog.w(TAG, "Would be an error: external vibrate from uid " + vib.getUid()); - endVibrationLocked(vibHolder, Vibration.Status.IGNORED_ERROR_APP_OPS); - } else { - endVibrationLocked(vibHolder, Vibration.Status.IGNORED_APP_OPS); - } - return IExternalVibratorService.SCALE_MUTE; - } - - VibrationThread cancelingVibration = null; - int scale; - synchronized (mLock) { - if (mCurrentExternalVibration != null - && mCurrentExternalVibration.externalVibration.equals(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 (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. - mNextVibrationThread = null; - doCancelVibrateLocked(Vibration.Status.CANCELLED); - cancelingVibration = mThread; - } else { - endVibrationLocked(mCurrentExternalVibration, Vibration.Status.CANCELLED); - } - // At this point we either have an externally controlled vibration playing, or - // no vibration playing. Since the interface defines that only one externally - // controlled vibration can play at a time, by returning something other than - // SCALE_MUTE from this function we can be assured that if we are currently - // playing vibration, it will be muted in favor of the new vibration. - // - // Note that this doesn't support multiple concurrent external controls, as we - // would need to mute the old one still if it came from a different controller. - mCurrentExternalVibration = new ExternalVibrationHolder(vib); - mCurrentExternalDeathRecipient = new ExternalVibrationDeathRecipient(); - vib.linkToDeath(mCurrentExternalDeathRecipient); - mCurrentExternalVibration.scale = mVibrationScaler.getExternalVibrationScale( - vib.getVibrationAttributes().getUsage()); - scale = mCurrentExternalVibration.scale; - } - if (cancelingVibration != null) { - try { - cancelingVibration.join(); - } catch (InterruptedException e) { - Slog.w("Interrupted while waiting current vibration to be cancelled before " - + "starting external vibration", e); - } - } - if (DEBUG) { - Slog.d(TAG, "Vibrator going under external control."); - } - mVibratorController.setExternalControl(true); - if (DEBUG) { - Slog.e(TAG, "Playing external vibration: " + vib); - } - return scale; - } - - @Override - public void onExternalVibrationStop(ExternalVibration vib) { - synchronized (mLock) { - if (mCurrentExternalVibration != null - && mCurrentExternalVibration.externalVibration.equals(vib)) { - if (DEBUG) { - Slog.e(TAG, "Stopping external vibration" + vib); - } - doCancelExternalVibrateLocked(Vibration.Status.FINISHED); - } - } - } - - private void doCancelExternalVibrateLocked(Vibration.Status status) { - Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "doCancelExternalVibrateLocked"); - try { - if (mCurrentExternalVibration == null) { - return; - } - endVibrationLocked(mCurrentExternalVibration, status); - mCurrentExternalVibration.externalVibration.unlinkToDeath( - mCurrentExternalDeathRecipient); - mCurrentExternalDeathRecipient = null; - mCurrentExternalVibration = null; - mVibratorController.setExternalControl(false); - } finally { - Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR); - } - } - - private class ExternalVibrationDeathRecipient implements IBinder.DeathRecipient { - public void binderDied() { - synchronized (mLock) { - if (mCurrentExternalVibration != null) { - if (DEBUG) { - Slog.d(TAG, "External vibration finished because binder died"); - } - doCancelExternalVibrateLocked(Vibration.Status.CANCELLED); - } - } - } - } - } - - private final class VibratorShellCommand extends ShellCommand { - - private final IBinder mToken; - - private final class CommonOptions { - public boolean force = false; - public void check(String opt) { - switch (opt) { - case "-f": - force = true; - break; - } - } - } - - private VibratorShellCommand(IBinder token) { - mToken = token; - } - - @Override - public int onCommand(String cmd) { - if ("vibrate".equals(cmd)) { - return runVibrate(); - } else if ("waveform".equals(cmd)) { - return runWaveform(); - } else if ("prebaked".equals(cmd)) { - return runPrebaked(); - } else if ("capabilities".equals(cmd)) { - return runCapabilities(); - } else if ("cancel".equals(cmd)) { - cancelVibrate(mToken); - return 0; - } - return handleDefaultCommands(cmd); - } - - private boolean checkDoNotDisturb(CommonOptions opts) { - if (mVibrationSettings.isInZenMode() && !opts.force) { - try (PrintWriter pw = getOutPrintWriter();) { - pw.print("Ignoring because device is on DND mode "); - return true; - } - } - return false; - } - - private int runVibrate() { - Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "runVibrate"); - try { - CommonOptions commonOptions = new CommonOptions(); - - String opt; - while ((opt = getNextOption()) != null) { - commonOptions.check(opt); - } - - if (checkDoNotDisturb(commonOptions)) { - return 0; - } - - final long duration = Long.parseLong(getNextArgRequired()); - String description = getNextArg(); - if (description == null) { - description = "Shell command"; - } - - VibrationEffect effect = - VibrationEffect.createOneShot(duration, VibrationEffect.DEFAULT_AMPLITUDE); - VibrationAttributes attrs = createVibrationAttributes(commonOptions); - vibrate(Binder.getCallingUid(), description, effect, attrs, "Shell Command", - mToken); - return 0; - } finally { - Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR); - } - } - - private int runWaveform() { - Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "runWaveform"); - try { - String description = "Shell command"; - int repeat = -1; - ArrayList<Integer> amplitudesList = null; - CommonOptions commonOptions = new CommonOptions(); - - String opt; - while ((opt = getNextOption()) != null) { - switch (opt) { - case "-d": - description = getNextArgRequired(); - break; - case "-r": - repeat = Integer.parseInt(getNextArgRequired()); - break; - case "-a": - if (amplitudesList == null) { - amplitudesList = new ArrayList<Integer>(); - } - break; - default: - commonOptions.check(opt); - break; - } - } - - if (checkDoNotDisturb(commonOptions)) { - return 0; - } - - ArrayList<Long> timingsList = new ArrayList<Long>(); - - String arg; - while ((arg = getNextArg()) != null) { - if (amplitudesList != null && amplitudesList.size() < timingsList.size()) { - amplitudesList.add(Integer.parseInt(arg)); - } else { - timingsList.add(Long.parseLong(arg)); - } - } - - VibrationEffect effect; - long[] timings = timingsList.stream().mapToLong(Long::longValue).toArray(); - if (amplitudesList == null) { - effect = VibrationEffect.createWaveform(timings, repeat); - } else { - int[] amplitudes = - amplitudesList.stream().mapToInt(Integer::intValue).toArray(); - effect = VibrationEffect.createWaveform(timings, amplitudes, repeat); - } - VibrationAttributes attrs = createVibrationAttributes(commonOptions); - vibrate(Binder.getCallingUid(), description, effect, attrs, "Shell Command", - mToken); - return 0; - } finally { - Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR); - } - } - - private int runPrebaked() { - Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "runPrebaked"); - try { - CommonOptions commonOptions = new CommonOptions(); - boolean shouldFallback = false; - - String opt; - while ((opt = getNextOption()) != null) { - if ("-b".equals(opt)) { - shouldFallback = true; - } else { - commonOptions.check(opt); - } - } - - if (checkDoNotDisturb(commonOptions)) { - return 0; - } - - final int id = Integer.parseInt(getNextArgRequired()); - - String description = getNextArg(); - if (description == null) { - description = "Shell command"; - } - - VibrationEffect effect = VibrationEffect.get(id, shouldFallback); - VibrationAttributes attrs = createVibrationAttributes(commonOptions); - vibrate(Binder.getCallingUid(), description, effect, attrs, "Shell Command", - mToken); - return 0; - } finally { - Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR); - } - } - - private int runCapabilities() { - Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "runCapabilities"); - try (PrintWriter pw = getOutPrintWriter();) { - pw.println("Vibrator capabilities:"); - if (mVibratorController.hasCapability(IVibrator.CAP_ALWAYS_ON_CONTROL)) { - pw.println(" Always on effects"); - } - if (mVibratorController.hasCapability(IVibrator.CAP_COMPOSE_EFFECTS)) { - pw.println(" Compose effects"); - } - if (mVibratorController.hasCapability(IVibrator.CAP_AMPLITUDE_CONTROL)) { - pw.println(" Amplitude control"); - } - if (mVibratorController.hasCapability(IVibrator.CAP_EXTERNAL_CONTROL)) { - pw.println(" External control"); - } - if (mVibratorController.hasCapability(IVibrator.CAP_EXTERNAL_AMPLITUDE_CONTROL)) { - pw.println(" External amplitude control"); - } - pw.println(""); - return 0; - } finally { - Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR); - } - } - - private VibrationAttributes createVibrationAttributes(CommonOptions commonOptions) { - final int flags = commonOptions.force - ? VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY - : 0; - return new VibrationAttributes.Builder() - .setFlags(flags, VibrationAttributes.FLAG_ALL_SUPPORTED) - // Used to apply Settings.System.HAPTIC_FEEDBACK_INTENSITY to scale effects. - .setUsage(VibrationAttributes.USAGE_TOUCH) - .build(); - } - - @Override - public void onHelp() { - try (PrintWriter pw = getOutPrintWriter();) { - pw.println("Vibrator commands:"); - pw.println(" help"); - pw.println(" Prints this help text."); - pw.println(""); - pw.println(" vibrate duration [description]"); - pw.println(" Vibrates for duration milliseconds; ignored when device is on "); - pw.println(" DND (Do Not Disturb) mode; touch feedback strength user setting "); - pw.println(" will be used to scale amplitude."); - pw.println(" waveform [-d description] [-r index] [-a] duration [amplitude] ..."); - pw.println(" Vibrates for durations and amplitudes in list; ignored when "); - pw.println(" device is on DND (Do Not Disturb) mode; touch feedback strength "); - pw.println(" user setting will be used to scale amplitude."); - pw.println(" If -r is provided, the waveform loops back to the specified"); - pw.println(" index (e.g. 0 loops from the beginning)"); - pw.println(" If -a is provided, the command accepts duration-amplitude pairs;"); - pw.println(" otherwise, it accepts durations only and alternates off/on"); - pw.println(" Duration is in milliseconds; amplitude is a scale of 1-255."); - pw.println(" prebaked [-b] effect-id [description]"); - pw.println(" Vibrates with prebaked effect; ignored when device is on DND "); - pw.println(" (Do Not Disturb) mode; touch feedback strength user setting "); - pw.println(" will be used to scale amplitude."); - pw.println(" If -b is provided, the prebaked fallback effect will be played if"); - pw.println(" the device doesn't support the given effect-id."); - pw.println(" capabilities"); - pw.println(" Prints capabilities of this device."); - pw.println(" cancel"); - pw.println(" Cancels any active vibration"); - pw.println("Common Options:"); - pw.println(" -f - Force. Ignore Do Not Disturb setting."); - pw.println(""); - } - } - } -} diff --git a/services/core/java/com/android/server/vibrator/VibrationThread.java b/services/core/java/com/android/server/vibrator/VibrationThread.java index bee66637fb2f..04dac7c2b198 100644 --- a/services/core/java/com/android/server/vibrator/VibrationThread.java +++ b/services/core/java/com/android/server/vibrator/VibrationThread.java @@ -92,13 +92,6 @@ public final class VibrationThread extends Thread implements IBinder.DeathRecipi @GuardedBy("mLock") private boolean mForceStop; - // TODO(b/159207608): Remove this constructor once VibratorService is removed - public VibrationThread(Vibration vib, VibratorController vibrator, - PowerManager.WakeLock wakeLock, IBatteryStats batteryStatsService, - VibrationCallbacks callbacks) { - this(vib, toSparseArray(vibrator), wakeLock, batteryStatsService, callbacks); - } - public VibrationThread(Vibration vib, SparseArray<VibratorController> availableVibrators, PowerManager.WakeLock wakeLock, IBatteryStats batteryStatsService, VibrationCallbacks callbacks) { @@ -286,12 +279,6 @@ public final class VibrationThread extends Thread implements IBinder.DeathRecipi return filteredEffects; } - private static SparseArray<VibratorController> toSparseArray(VibratorController controller) { - SparseArray<VibratorController> array = new SparseArray<>(1); - array.put(controller.getVibratorInfo().getId(), controller); - return array; - } - /** * Get the duration the vibrator will be on for given {@code waveform}, starting at {@code * startIndex} until the next time it's vibrating amplitude is zero. diff --git a/services/core/jni/com_android_server_vibrator_VibratorController.cpp b/services/core/jni/com_android_server_vibrator_VibratorController.cpp index 4e47984fa75c..a6029cd28029 100644 --- a/services/core/jni/com_android_server_vibrator_VibratorController.cpp +++ b/services/core/jni/com_android_server_vibrator_VibratorController.cpp @@ -73,10 +73,6 @@ static_assert(static_cast<uint8_t>(V1_3::Effect::TEXTURE_TICK) == static_cast<uint8_t>(aidl::Effect::TEXTURE_TICK)); static std::shared_ptr<vibrator::HalController> findVibrator(int32_t vibratorId) { - // TODO(b/167946816): remove this once VibratorService is removed. - if (vibratorId < 0) { - return std::move(std::make_unique<vibrator::HalController>()); - } vibrator::ManagerHalController* manager = android_server_VibratorManagerService_getManager(); if (manager == nullptr) { return nullptr; diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 98c3b99124ee..4eabfb7f0b9b 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -1292,7 +1292,6 @@ public final class SystemServer implements Dumpable { t.traceBegin("startOtherServices"); final Context context = mSystemContext; - VibratorService vibrator = null; DynamicSystemService dynamicSystem = null; IStorageManager storageManager = null; NetworkManagementService networkManagement = null; @@ -1418,11 +1417,6 @@ public final class SystemServer implements Dumpable { mSystemServiceManager.startService(VibratorManagerService.Lifecycle.class); t.traceEnd(); - t.traceBegin("StartVibratorService"); - vibrator = new VibratorService(context); - ServiceManager.addService("vibrator", vibrator); - t.traceEnd(); - t.traceBegin("StartDynamicSystemService"); dynamicSystem = new DynamicSystemService(context); ServiceManager.addService("dynamic_system", dynamicSystem); @@ -2491,14 +2485,6 @@ public final class SystemServer implements Dumpable { // It is now time to start up the app processes... - t.traceBegin("MakeVibratorServiceReady"); - try { - vibrator.systemReady(); - } catch (Throwable e) { - reportWtf("making Vibrator Service ready", e); - } - t.traceEnd(); - t.traceBegin("MakeLockSettingsServiceReady"); if (lockSettings != null) { try { diff --git a/services/tests/servicestests/src/com/android/server/VibratorManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/VibratorManagerServiceTest.java index f0d7006633a2..da3d1d6187fc 100644 --- a/services/tests/servicestests/src/com/android/server/VibratorManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/VibratorManagerServiceTest.java @@ -198,6 +198,10 @@ public class VibratorManagerServiceTest { return mVibratorProviders.get(vibratorId) .newVibratorController(vibratorId, listener); } + + @Override + void addService(String name, IBinder service) { + } }); service.systemReady(); return service; diff --git a/services/tests/servicestests/src/com/android/server/VibratorServiceTest.java b/services/tests/servicestests/src/com/android/server/VibratorServiceTest.java deleted file mode 100644 index 633957a8b13a..000000000000 --- a/services/tests/servicestests/src/com/android/server/VibratorServiceTest.java +++ /dev/null @@ -1,757 +0,0 @@ -/* - * Copyright (C) 2020 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; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.atLeastOnce; -import static org.mockito.Mockito.clearInvocations; -import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.inOrder; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.app.AppOpsManager; -import android.content.ComponentName; -import android.content.ContentResolver; -import android.content.Context; -import android.content.ContextWrapper; -import android.content.pm.PackageManagerInternal; -import android.hardware.input.IInputManager; -import android.hardware.input.InputManager; -import android.hardware.vibrator.IVibrator; -import android.media.AudioAttributes; -import android.media.AudioManager; -import android.os.Handler; -import android.os.IBinder; -import android.os.IVibratorStateListener; -import android.os.Looper; -import android.os.PowerManagerInternal; -import android.os.PowerSaveState; -import android.os.Process; -import android.os.SystemClock; -import android.os.UserHandle; -import android.os.VibrationAttributes; -import android.os.VibrationEffect; -import android.os.Vibrator; -import android.os.VibratorInfo; -import android.os.test.TestLooper; -import android.platform.test.annotations.Presubmit; -import android.provider.Settings; -import android.view.InputDevice; - -import androidx.test.InstrumentationRegistry; - -import com.android.internal.util.test.FakeSettingsProvider; -import com.android.internal.util.test.FakeSettingsProviderRule; -import com.android.server.vibrator.FakeVibrator; -import com.android.server.vibrator.FakeVibratorControllerProvider; -import com.android.server.vibrator.VibratorController; - -import org.junit.After; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.mockito.InOrder; -import org.mockito.Mock; -import org.mockito.junit.MockitoJUnit; -import org.mockito.junit.MockitoRule; - -import java.util.Arrays; -import java.util.List; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; -import java.util.function.Predicate; -import java.util.stream.Collectors; - -/** - * Tests for {@link VibratorService}. - * - * Build/Install/Run: - * atest FrameworksServicesTests:VibratorServiceTest - */ -@Presubmit -public class VibratorServiceTest { - - private static final int TEST_TIMEOUT_MILLIS = 1_000; - private static final int UID = Process.ROOT_UID; - private static final int VIBRATOR_ID = 1; - private static final String PACKAGE_NAME = "package"; - private static final PowerSaveState NORMAL_POWER_STATE = new PowerSaveState.Builder().build(); - private static final PowerSaveState LOW_POWER_STATE = new PowerSaveState.Builder() - .setBatterySaverEnabled(true).build(); - private static final VibrationAttributes ALARM_ATTRS = - new VibrationAttributes.Builder().setUsage(VibrationAttributes.USAGE_ALARM).build(); - private static final VibrationAttributes HAPTIC_FEEDBACK_ATTRS = - new VibrationAttributes.Builder().setUsage( - VibrationAttributes.USAGE_TOUCH).build(); - private static final VibrationAttributes NOTIFICATION_ATTRS = - new VibrationAttributes.Builder().setUsage( - VibrationAttributes.USAGE_NOTIFICATION).build(); - private static final VibrationAttributes RINGTONE_ATTRS = - new VibrationAttributes.Builder().setUsage( - VibrationAttributes.USAGE_RINGTONE).build(); - - @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule(); - @Rule public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule(); - - @Mock private PackageManagerInternal mPackageManagerInternalMock; - @Mock private PowerManagerInternal mPowerManagerInternalMock; - @Mock private AppOpsManager mAppOpsManagerMock; - @Mock private IVibratorStateListener mVibratorStateListenerMock; - @Mock private IInputManager mIInputManagerMock; - @Mock private IBinder mVibratorStateListenerBinderMock; - - private TestLooper mTestLooper; - private ContextWrapper mContextSpy; - private PowerManagerInternal.LowPowerModeListener mRegisteredPowerModeListener; - private FakeVibrator mFakeVibrator; - private FakeVibratorControllerProvider mVibratorProvider; - - @Before - public void setUp() throws Exception { - mTestLooper = new TestLooper(); - mFakeVibrator = new FakeVibrator(); - mVibratorProvider = new FakeVibratorControllerProvider(mTestLooper.getLooper()); - mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getContext())); - InputManager inputManager = InputManager.resetInstance(mIInputManagerMock); - - ContentResolver contentResolver = mSettingsProviderRule.mockContentResolver(mContextSpy); - when(mContextSpy.getContentResolver()).thenReturn(contentResolver); - when(mContextSpy.getSystemService(eq(Context.VIBRATOR_SERVICE))).thenReturn(mFakeVibrator); - when(mContextSpy.getSystemService(eq(Context.INPUT_SERVICE))).thenReturn(inputManager); - when(mContextSpy.getSystemService(Context.APP_OPS_SERVICE)).thenReturn(mAppOpsManagerMock); - when(mVibratorStateListenerMock.asBinder()).thenReturn(mVibratorStateListenerBinderMock); - when(mPackageManagerInternalMock.getSystemUiServiceComponent()) - .thenReturn(new ComponentName("", "")); - doAnswer(invocation -> { - mRegisteredPowerModeListener = invocation.getArgument(0); - return null; - }).when(mPowerManagerInternalMock).registerLowPowerModeObserver(any()); - when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[0]); - - setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 1); - setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, - Vibrator.VIBRATION_INTENSITY_MEDIUM); - setUserSetting(Settings.System.RING_VIBRATION_INTENSITY, - Vibrator.VIBRATION_INTENSITY_MEDIUM); - setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, - Vibrator.VIBRATION_INTENSITY_MEDIUM); - - addLocalServiceMock(PackageManagerInternal.class, mPackageManagerInternalMock); - addLocalServiceMock(PowerManagerInternal.class, mPowerManagerInternalMock); - } - - @After - public void tearDown() throws Exception { - InputManager.clearInstance(); - LocalServices.removeServiceForTest(PackageManagerInternal.class); - LocalServices.removeServiceForTest(PowerManagerInternal.class); - } - - private VibratorService createService() { - VibratorService service = new VibratorService(mContextSpy, - new VibratorService.Injector() { - @Override - VibratorController createVibratorController( - VibratorController.OnVibrationCompleteListener listener) { - return mVibratorProvider.newVibratorController(VIBRATOR_ID, listener); - } - - @Override - Handler createHandler(Looper looper) { - return new Handler(mTestLooper.getLooper()); - } - - @Override - void addService(String name, IBinder service) { - // ignore - } - }); - service.systemReady(); - return service; - } - - @Test - public void createService_initializesNativeService() { - createService(); - assertTrue(mVibratorProvider.isInitialized()); - } - - @Test - public void hasVibrator_withVibratorHalPresent_returnsTrue() { - assertTrue(createService().hasVibrator()); - } - - @Test - public void hasVibrator_withNoVibratorHalPresent_returnsFalse() { - mVibratorProvider.disableVibrators(); - assertFalse(createService().hasVibrator()); - } - - @Test - public void hasAmplitudeControl_withAmplitudeControlSupport_returnsTrue() { - mVibratorProvider.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL); - assertTrue(createService().hasAmplitudeControl()); - } - - @Test - public void hasAmplitudeControl_withNoAmplitudeControlSupport_returnsFalse() { - assertFalse(createService().hasAmplitudeControl()); - } - - @Test - public void hasAmplitudeControl_withInputDevices_returnsTrue() throws Exception { - when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1}); - when(mIInputManagerMock.getInputDevice(1)).thenReturn(createInputDeviceWithVibrator(1)); - mVibratorProvider.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL); - setUserSetting(Settings.System.VIBRATE_INPUT_DEVICES, 1); - assertTrue(createService().hasAmplitudeControl()); - } - - @Test - public void getVibratorInfo_returnsSameInfoFromNative() { - mVibratorProvider.setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS, - IVibrator.CAP_AMPLITUDE_CONTROL); - mVibratorProvider.setSupportedEffects(VibrationEffect.EFFECT_CLICK); - mVibratorProvider.setSupportedPrimitives(VibrationEffect.Composition.PRIMITIVE_CLICK); - - VibratorInfo info = createService().getVibratorInfo(); - assertTrue(info.hasAmplitudeControl()); - assertEquals(Vibrator.VIBRATION_EFFECT_SUPPORT_YES, - info.isEffectSupported(VibrationEffect.EFFECT_CLICK)); - assertEquals(Vibrator.VIBRATION_EFFECT_SUPPORT_NO, - info.isEffectSupported(VibrationEffect.EFFECT_TICK)); - assertTrue(info.isPrimitiveSupported(VibrationEffect.Composition.PRIMITIVE_CLICK)); - assertFalse(info.isPrimitiveSupported(VibrationEffect.Composition.PRIMITIVE_TICK)); - } - - @Test - public void vibrate_withRingtone_usesRingtoneSettings() throws Exception { - setRingerMode(AudioManager.RINGER_MODE_NORMAL); - setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 0); - setGlobalSetting(Settings.Global.APPLY_RAMPING_RINGER, 0); - vibrate(createService(), VibrationEffect.createOneShot(1, 1), RINGTONE_ATTRS); - - setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 0); - setGlobalSetting(Settings.Global.APPLY_RAMPING_RINGER, 1); - vibrateAndWait(createService(), VibrationEffect.createOneShot(10, 10), RINGTONE_ATTRS); - - setUserSetting(Settings.System.VIBRATE_WHEN_RINGING, 1); - setGlobalSetting(Settings.Global.APPLY_RAMPING_RINGER, 0); - vibrateAndWait(createService(), VibrationEffect.createOneShot(100, 100), RINGTONE_ATTRS); - - List<VibrationEffect> effects = mVibratorProvider.getEffects(); - assertEquals(2, effects.size()); - assertEquals(10, effects.get(0).getDuration()); - assertEquals(100, effects.get(1).getDuration()); - } - - @Test - public void vibrate_withPowerModeChange_usesLowPowerModeState() throws Exception { - VibratorService service = createService(); - mRegisteredPowerModeListener.onLowPowerModeChanged(LOW_POWER_STATE); - vibrate(service, VibrationEffect.createOneShot(1, 1), HAPTIC_FEEDBACK_ATTRS); - vibrateAndWait(service, VibrationEffect.createOneShot(2, 2), RINGTONE_ATTRS); - - mRegisteredPowerModeListener.onLowPowerModeChanged(NORMAL_POWER_STATE); - vibrateAndWait(service, VibrationEffect.createOneShot(3, 3), /* attributes= */ null); - vibrateAndWait(service, VibrationEffect.createOneShot(4, 4), NOTIFICATION_ATTRS); - - List<VibrationEffect> effects = mVibratorProvider.getEffects(); - assertEquals(3, effects.size()); - assertEquals(2, effects.get(0).getDuration()); - assertEquals(3, effects.get(1).getDuration()); - assertEquals(4, effects.get(2).getDuration()); - } - - @Test - public void vibrate_withAudioAttributes_usesOriginalAudioUsageInAppOpsManager() { - VibratorService service = createService(); - - VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK); - AudioAttributes audioAttributes = new AudioAttributes.Builder() - .setUsage(AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY).build(); - VibrationAttributes vibrationAttributes = new VibrationAttributes.Builder( - audioAttributes, effect).build(); - - vibrate(service, effect, vibrationAttributes); - - verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE), - eq(AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY), anyInt(), anyString()); - } - - @Test - public void vibrate_withVibrationAttributes_usesCorrespondingAudioUsageInAppOpsManager() { - VibratorService service = createService(); - - vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), ALARM_ATTRS); - vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_TICK), NOTIFICATION_ATTRS); - vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), RINGTONE_ATTRS); - vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_TICK), HAPTIC_FEEDBACK_ATTRS); - vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), - new VibrationAttributes.Builder().setUsage( - VibrationAttributes.USAGE_COMMUNICATION_REQUEST).build()); - vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_TICK), - new VibrationAttributes.Builder().setUsage( - VibrationAttributes.USAGE_UNKNOWN).build()); - - InOrder inOrderVerifier = inOrder(mAppOpsManagerMock); - inOrderVerifier.verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE), - eq(AudioAttributes.USAGE_ALARM), anyInt(), anyString()); - inOrderVerifier.verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE), - eq(AudioAttributes.USAGE_NOTIFICATION), anyInt(), anyString()); - inOrderVerifier.verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE), - eq(AudioAttributes.USAGE_NOTIFICATION_RINGTONE), anyInt(), anyString()); - inOrderVerifier.verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE), - eq(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION), anyInt(), anyString()); - inOrderVerifier.verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE), - eq(AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_REQUEST), - anyInt(), anyString()); - inOrderVerifier.verify(mAppOpsManagerMock).checkAudioOpNoThrow(eq(AppOpsManager.OP_VIBRATE), - eq(AudioAttributes.USAGE_UNKNOWN), anyInt(), anyString()); - } - - @Test - public void vibrate_withOneShotAndInputDevices_vibratesInputDevices() throws Exception { - when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1}); - when(mIInputManagerMock.getInputDevice(1)).thenReturn(createInputDeviceWithVibrator(1)); - setUserSetting(Settings.System.VIBRATE_INPUT_DEVICES, 1); - VibratorService service = createService(); - - VibrationEffect effect = VibrationEffect.createOneShot(100, 128); - vibrate(service, effect, ALARM_ATTRS); - verify(mIInputManagerMock).vibrate(eq(1), eq(effect), any()); - - // VibrationThread will start this vibration async, so wait before checking it never played. - assertFalse(waitUntil(s -> !mVibratorProvider.getEffects().isEmpty(), service, - /* timeout= */ 20)); - } - - @Test - public void vibrate_withOneShotAndAmplitudeControl_turnsVibratorOnAndSetsAmplitude() - throws Exception { - mVibratorProvider.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL); - VibratorService service = createService(); - - vibrateAndWait(service, VibrationEffect.createOneShot(100, 128), ALARM_ATTRS); - - List<VibrationEffect> effects = mVibratorProvider.getEffects(); - assertEquals(1, effects.size()); - assertEquals(100, effects.get(0).getDuration()); - assertEquals(Arrays.asList(128), mVibratorProvider.getAmplitudes()); - } - - @Test - public void vibrate_withOneShotAndNoAmplitudeControl_turnsVibratorOnAndIgnoresAmplitude() - throws Exception { - VibratorService service = createService(); - clearInvocations(); - - vibrateAndWait(service, VibrationEffect.createOneShot(100, 128), ALARM_ATTRS); - - List<VibrationEffect> effects = mVibratorProvider.getEffects(); - assertEquals(1, effects.size()); - assertEquals(100, effects.get(0).getDuration()); - assertTrue(mVibratorProvider.getAmplitudes().isEmpty()); - } - - @Test - public void vibrate_withPrebaked_performsEffect() throws Exception { - mVibratorProvider.setSupportedEffects(VibrationEffect.EFFECT_CLICK); - VibratorService service = createService(); - - VibrationEffect effect = VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK); - vibrateAndWait(service, effect, ALARM_ATTRS); - - VibrationEffect.Prebaked expectedEffect = new VibrationEffect.Prebaked( - VibrationEffect.EFFECT_CLICK, false, VibrationEffect.EFFECT_STRENGTH_STRONG); - assertEquals(Arrays.asList(expectedEffect), mVibratorProvider.getEffects()); - } - - @Test - public void vibrate_withPrebakedAndInputDevices_vibratesFallbackWaveformOnInputDevices() - throws Exception { - when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1}); - when(mIInputManagerMock.getInputDevice(1)).thenReturn(createInputDeviceWithVibrator(1)); - setUserSetting(Settings.System.VIBRATE_INPUT_DEVICES, 1); - VibratorService service = createService(); - - vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), ALARM_ATTRS); - verify(mIInputManagerMock).vibrate(eq(1), any(), any()); - - // VibrationThread will start this vibration async, so wait before checking it never played. - assertFalse(waitUntil(s -> !mVibratorProvider.getEffects().isEmpty(), service, - /* timeout= */ 20)); - } - - @Test - public void vibrate_enteringLowPowerMode_cancelVibration() throws Exception { - VibratorService service = createService(); - - mRegisteredPowerModeListener.onLowPowerModeChanged(NORMAL_POWER_STATE); - vibrate(service, VibrationEffect.createOneShot(1000, 100), HAPTIC_FEEDBACK_ATTRS); - assertTrue(waitUntil(s -> s.isVibrating(), service, TEST_TIMEOUT_MILLIS)); - - mRegisteredPowerModeListener.onLowPowerModeChanged(LOW_POWER_STATE); - assertTrue(waitUntil(s -> !s.isVibrating(), service, TEST_TIMEOUT_MILLIS)); - } - - @Test - public void vibrate_enteringLowPowerModeAndRingtone_doNotCancelVibration() throws Exception { - VibratorService service = createService(); - - mRegisteredPowerModeListener.onLowPowerModeChanged(NORMAL_POWER_STATE); - vibrate(service, VibrationEffect.createOneShot(1000, 100), RINGTONE_ATTRS); - assertTrue(waitUntil(s -> s.isVibrating(), service, TEST_TIMEOUT_MILLIS)); - - mRegisteredPowerModeListener.onLowPowerModeChanged(LOW_POWER_STATE); - // Settings callback is async, so wait before checking it never got cancelled. - assertFalse(waitUntil(s -> !s.isVibrating(), service, /* timeout= */ 20)); - } - - @Test - public void vibrate_withSettingsChanged_doNotCancelVibration() throws Exception { - VibratorService service = createService(); - vibrate(service, VibrationEffect.createOneShot(1000, 100), HAPTIC_FEEDBACK_ATTRS); - assertTrue(waitUntil(s -> s.isVibrating(), service, TEST_TIMEOUT_MILLIS)); - - setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, - Vibrator.VIBRATION_INTENSITY_MEDIUM); - - // FakeSettingsProvider don't support testing triggering ContentObserver yet. - service.updateVibrators(); - - // Settings callback is async, so wait before checking it never got cancelled. - assertFalse(waitUntil(s -> !s.isVibrating(), service, /* timeout= */ 20)); - } - - @Test - public void vibrate_withComposed_performsEffect() throws Exception { - mVibratorProvider.setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS); - VibratorService service = createService(); - - VibrationEffect effect = VibrationEffect.startComposition() - .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 0.5f, 10) - .compose(); - vibrateAndWait(service, effect, ALARM_ATTRS); - assertEquals(Arrays.asList(effect), mVibratorProvider.getEffects()); - } - - @Test - public void vibrate_withComposedAndInputDevices_vibratesInputDevices() throws Exception { - when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1, 2}); - when(mIInputManagerMock.getInputDevice(1)).thenReturn(createInputDeviceWithVibrator(1)); - when(mIInputManagerMock.getInputDevice(2)).thenReturn(createInputDeviceWithVibrator(2)); - setUserSetting(Settings.System.VIBRATE_INPUT_DEVICES, 1); - VibratorService service = createService(); - - VibrationEffect effect = VibrationEffect.startComposition() - .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 0.5f, 10) - .compose(); - vibrate(service, effect, ALARM_ATTRS); - InOrder inOrderVerifier = inOrder(mIInputManagerMock); - inOrderVerifier.verify(mIInputManagerMock).vibrate(eq(1), eq(effect), any()); - inOrderVerifier.verify(mIInputManagerMock).vibrate(eq(2), eq(effect), any()); - - // VibrationThread will start this vibration async, so wait before checking it never played. - assertFalse(waitUntil(s -> !mVibratorProvider.getEffects().isEmpty(), service, - /* timeout= */ 20)); - } - - @Test - public void vibrate_withWaveform_controlsVibratorAmplitudeDuringTotalVibrationTime() - throws Exception { - mVibratorProvider.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL); - VibratorService service = createService(); - - VibrationEffect effect = VibrationEffect.createWaveform( - new long[]{10, 10, 10}, new int[]{100, 200, 50}, -1); - vibrateAndWait(service, effect, ALARM_ATTRS); - - assertEquals(Arrays.asList(100, 200, 50), mVibratorProvider.getAmplitudes()); - assertEquals( - Arrays.asList(VibrationEffect.createOneShot(30, VibrationEffect.DEFAULT_AMPLITUDE)), - mVibratorProvider.getEffects()); - } - - @Test - public void vibrate_withWaveformAndInputDevices_vibratesInputDevices() throws Exception { - when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{1}); - when(mIInputManagerMock.getInputDevice(1)).thenReturn(createInputDeviceWithVibrator(1)); - setUserSetting(Settings.System.VIBRATE_INPUT_DEVICES, 1); - VibratorService service = createService(); - - VibrationEffect effect = VibrationEffect.createWaveform( - new long[]{10, 10, 10}, new int[]{100, 200, 50}, -1); - vibrate(service, effect, ALARM_ATTRS); - verify(mIInputManagerMock).vibrate(eq(1), eq(effect), any()); - - // VibrationThread will start this vibration async, so wait before checking it never played. - assertFalse(waitUntil(s -> !mVibratorProvider.getEffects().isEmpty(), service, - /* timeout= */ 20)); - } - - @Test - public void vibrate_withNativeCallbackTriggered_finishesVibration() throws Exception { - mVibratorProvider.setSupportedEffects(VibrationEffect.EFFECT_CLICK); - VibratorService service = createService(); - - vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), ALARM_ATTRS); - assertTrue(waitUntil(s -> s.isVibrating(), service, TEST_TIMEOUT_MILLIS)); - - // Trigger callbacks from controller. - mTestLooper.moveTimeForward(50); - mTestLooper.dispatchAll(); - assertTrue(waitUntil(s -> !s.isVibrating(), service, TEST_TIMEOUT_MILLIS)); - } - - @Test - public void cancelVibrate_withDeviceVibrating_callsOff() throws Exception { - VibratorService service = createService(); - - vibrate(service, VibrationEffect.createOneShot(100, 100), ALARM_ATTRS); - assertTrue(waitUntil(s -> s.isVibrating(), service, TEST_TIMEOUT_MILLIS)); - - service.cancelVibrate(service); - assertTrue(waitUntil(s -> !s.isVibrating(), service, TEST_TIMEOUT_MILLIS)); - } - - @Test - public void registerVibratorStateListener_callbacksAreTriggered() throws Exception { - VibratorService service = createService(); - service.registerVibratorStateListener(mVibratorStateListenerMock); - - vibrateAndWait(service, VibrationEffect.createOneShot(100, 100), ALARM_ATTRS); - - InOrder inOrderVerifier = inOrder(mVibratorStateListenerMock); - // First notification done when listener is registered. - inOrderVerifier.verify(mVibratorStateListenerMock).onVibrating(eq(false)); - inOrderVerifier.verify(mVibratorStateListenerMock).onVibrating(eq(true)); - inOrderVerifier.verify(mVibratorStateListenerMock).onVibrating(eq(false)); - inOrderVerifier.verifyNoMoreInteractions(); - } - - @Test - public void unregisterVibratorStateListener_callbackNotTriggeredAfter() throws Exception { - VibratorService service = createService(); - - service.registerVibratorStateListener(mVibratorStateListenerMock); - - vibrate(service, VibrationEffect.createOneShot(30, 100), ALARM_ATTRS); - assertTrue(waitUntil(s -> s.isVibrating(), service, TEST_TIMEOUT_MILLIS)); - - service.unregisterVibratorStateListener(mVibratorStateListenerMock); - // Trigger callbacks from controller. - mTestLooper.moveTimeForward(50); - mTestLooper.dispatchAll(); - assertTrue(waitUntil(s -> !s.isVibrating(), service, TEST_TIMEOUT_MILLIS)); - - InOrder inOrderVerifier = inOrder(mVibratorStateListenerMock); - // First notification done when listener is registered. - inOrderVerifier.verify(mVibratorStateListenerMock).onVibrating(eq(false)); - inOrderVerifier.verify(mVibratorStateListenerMock).onVibrating(eq(true)); - inOrderVerifier.verify(mVibratorStateListenerMock, atLeastOnce()).asBinder(); // unregister - inOrderVerifier.verifyNoMoreInteractions(); - } - - @Test - public void scale_withPrebaked_userIntensitySettingAsEffectStrength() throws Exception { - // Alarm vibration is always VIBRATION_INTENSITY_HIGH. - setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, - Vibrator.VIBRATION_INTENSITY_MEDIUM); - setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, - Vibrator.VIBRATION_INTENSITY_LOW); - setUserSetting(Settings.System.RING_VIBRATION_INTENSITY, - Vibrator.VIBRATION_INTENSITY_OFF); - mVibratorProvider.setSupportedEffects( - VibrationEffect.EFFECT_CLICK, - VibrationEffect.EFFECT_TICK, - VibrationEffect.EFFECT_DOUBLE_CLICK, - VibrationEffect.EFFECT_HEAVY_CLICK); - VibratorService service = createService(); - - vibrateAndWait(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), ALARM_ATTRS); - vibrateAndWait(service, VibrationEffect.get(VibrationEffect.EFFECT_TICK), - NOTIFICATION_ATTRS); - vibrateAndWait(service, VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK), - HAPTIC_FEEDBACK_ATTRS); - vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_HEAVY_CLICK), RINGTONE_ATTRS); - - List<Integer> playedStrengths = mVibratorProvider.getEffects().stream() - .map(VibrationEffect.Prebaked.class::cast) - .map(VibrationEffect.Prebaked::getEffectStrength) - .collect(Collectors.toList()); - assertEquals(Arrays.asList( - VibrationEffect.EFFECT_STRENGTH_STRONG, - VibrationEffect.EFFECT_STRENGTH_MEDIUM, - VibrationEffect.EFFECT_STRENGTH_LIGHT), - playedStrengths); - } - - @Test - public void scale_withOneShotAndWaveform_usesScaleLevelOnAmplitude() throws Exception { - mFakeVibrator.setDefaultNotificationVibrationIntensity(Vibrator.VIBRATION_INTENSITY_LOW); - setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, - Vibrator.VIBRATION_INTENSITY_HIGH); - setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, - Vibrator.VIBRATION_INTENSITY_LOW); - setUserSetting(Settings.System.RING_VIBRATION_INTENSITY, - Vibrator.VIBRATION_INTENSITY_OFF); - - mVibratorProvider.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL); - VibratorService service = createService(); - - vibrateAndWait(service, VibrationEffect.createOneShot(20, 100), ALARM_ATTRS); - vibrateAndWait(service, VibrationEffect.createOneShot(20, 100), NOTIFICATION_ATTRS); - vibrateAndWait(service, - VibrationEffect.createWaveform(new long[]{10}, new int[]{100}, -1), - HAPTIC_FEEDBACK_ATTRS); - vibrate(service, VibrationEffect.createOneShot(20, 255), RINGTONE_ATTRS); - - List<Integer> amplitudes = mVibratorProvider.getAmplitudes(); - assertEquals(3, amplitudes.size()); - // Alarm vibration is never scaled. - assertEquals(100, amplitudes.get(0).intValue()); - // Notification vibrations will be scaled with SCALE_VERY_HIGH. - assertTrue(amplitudes.get(1) > 150); - // Haptic feedback vibrations will be scaled with SCALE_LOW. - assertTrue(amplitudes.get(2) < 100 && amplitudes.get(2) > 50); - } - - @Test - public void scale_withComposed_usesScaleLevelOnPrimitiveScaleValues() throws Exception { - mFakeVibrator.setDefaultNotificationVibrationIntensity(Vibrator.VIBRATION_INTENSITY_LOW); - setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, - Vibrator.VIBRATION_INTENSITY_HIGH); - setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, - Vibrator.VIBRATION_INTENSITY_LOW); - setUserSetting(Settings.System.RING_VIBRATION_INTENSITY, - Vibrator.VIBRATION_INTENSITY_OFF); - - mVibratorProvider.setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS); - VibratorService service = createService(); - - VibrationEffect effect = VibrationEffect.startComposition() - .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 1f) - .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f) - .compose(); - - vibrateAndWait(service, effect, ALARM_ATTRS); - vibrateAndWait(service, effect, NOTIFICATION_ATTRS); - vibrateAndWait(service, effect, HAPTIC_FEEDBACK_ATTRS); - vibrate(service, effect, RINGTONE_ATTRS); - - List<VibrationEffect.Composition.PrimitiveEffect> primitives = - mVibratorProvider.getEffects().stream() - .map(VibrationEffect.Composed.class::cast) - .map(VibrationEffect.Composed::getPrimitiveEffects) - .flatMap(List::stream) - .collect(Collectors.toList()); - - // Ringtone vibration is off, so only the other 3 are propagated to native. - assertEquals(6, primitives.size()); - - // Alarm vibration is never scaled. - assertEquals(1f, primitives.get(0).scale, /* delta= */ 1e-2); - assertEquals(0.5f, primitives.get(1).scale, /* delta= */ 1e-2); - - // Notification vibrations will be scaled with SCALE_VERY_HIGH. - assertEquals(1f, primitives.get(2).scale, /* delta= */ 1e-2); - assertTrue(0.7 < primitives.get(3).scale); - - // Haptic feedback vibrations will be scaled with SCALE_LOW. - assertTrue(0.5 < primitives.get(4).scale); - assertTrue(0.5 > primitives.get(5).scale); - } - - private void vibrate(VibratorService service, VibrationEffect effect, - VibrationAttributes attrs) { - service.vibrate(UID, PACKAGE_NAME, effect, attrs, "some reason", service); - } - - private void vibrateAndWait(VibratorService service, VibrationEffect effect, - VibrationAttributes attrs) throws Exception { - CountDownLatch startedCount = new CountDownLatch(1); - CountDownLatch finishedCount = new CountDownLatch(1); - service.registerVibratorStateListener(new IVibratorStateListener() { - @Override - public void onVibrating(boolean vibrating) { - if (vibrating) { - startedCount.countDown(); - } else if (startedCount.getCount() == 0) { - finishedCount.countDown(); - } - } - - @Override - public IBinder asBinder() { - return mock(IBinder.class); - } - }); - - mTestLooper.startAutoDispatch(); - service.vibrate(UID, PACKAGE_NAME, effect, attrs, "some reason", service); - assertTrue(startedCount.await(1, TimeUnit.SECONDS)); - assertTrue(finishedCount.await(1, TimeUnit.SECONDS)); - mTestLooper.stopAutoDispatchAndIgnoreExceptions(); - } - - private InputDevice createInputDeviceWithVibrator(int id) { - return new InputDevice(id, 0, 0, "name", 0, 0, "description", false, 0, 0, - null, /* hasVibrator= */ true, false, false, false /* hasSensor */, - false /* hasBattery */); - } - - private static <T> void addLocalServiceMock(Class<T> clazz, T mock) { - LocalServices.removeServiceForTest(clazz); - LocalServices.addService(clazz, mock); - } - - private void setRingerMode(int ringerMode) { - AudioManager audioManager = mContextSpy.getSystemService(AudioManager.class); - audioManager.setRingerModeInternal(ringerMode); - assertEquals(ringerMode, audioManager.getRingerModeInternal()); - } - - private void setUserSetting(String settingName, int value) { - Settings.System.putIntForUser( - mContextSpy.getContentResolver(), settingName, value, UserHandle.USER_CURRENT); - } - - private void setGlobalSetting(String settingName, int value) { - Settings.Global.putInt(mContextSpy.getContentResolver(), settingName, value); - } - - private boolean waitUntil(Predicate<VibratorService> predicate, - VibratorService service, long timeout) throws InterruptedException { - long timeoutTimestamp = SystemClock.uptimeMillis() + timeout; - boolean predicateResult = false; - while (!predicateResult && SystemClock.uptimeMillis() < timeoutTimestamp) { - Thread.sleep(10); - predicateResult = predicate.test(service); - } - return predicateResult; - } -} diff --git a/tests/permission/src/com/android/framework/permission/tests/VibratorServicePermissionTest.java b/tests/permission/src/com/android/framework/permission/tests/VibratorManagerServicePermissionTest.java index d1d6a26790fd..0f920b36cffd 100644 --- a/tests/permission/src/com/android/framework/permission/tests/VibratorServicePermissionTest.java +++ b/tests/permission/src/com/android/framework/permission/tests/VibratorManagerServicePermissionTest.java @@ -16,8 +16,11 @@ package com.android.framework.permission.tests; +import android.content.Context; import android.os.Binder; -import android.os.IVibratorService; +import android.os.CombinedVibrationEffect; +import android.os.IBinder; +import android.os.IVibratorManagerService; import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; @@ -32,27 +35,28 @@ import junit.framework.TestCase; * Verify that Hardware apis cannot be called without required permissions. */ @SmallTest -public class VibratorServicePermissionTest extends TestCase { +public class VibratorManagerServicePermissionTest extends TestCase { - private IVibratorService mVibratorService; + private IVibratorManagerService mVibratorService; @Override protected void setUp() throws Exception { - mVibratorService = IVibratorService.Stub.asInterface( - ServiceManager.getService("vibrator")); + mVibratorService = IVibratorManagerService.Stub.asInterface( + ServiceManager.getService(Context.VIBRATOR_MANAGER_SERVICE)); } /** - * Test that calling {@link android.os.IVibratorService#vibrate(long)} requires permissions. + * Test that calling {@link android.os.IVibratorManagerService#vibrate(int, String, + * CombinedVibrationEffect, VibrationAttributes, String, IBinder)} requires permissions. * <p>Tests permission: - * {@link android.Manifest.permission#VIBRATE} - * @throws RemoteException + * {@link android.Manifest.permission#VIBRATE} */ public void testVibrate() throws RemoteException { try { - final VibrationEffect effect = - VibrationEffect.createOneShot(100, VibrationEffect.DEFAULT_AMPLITUDE); - final VibrationAttributes attrs = new VibrationAttributes.Builder() + CombinedVibrationEffect effect = + CombinedVibrationEffect.createSynced( + VibrationEffect.createOneShot(100, VibrationEffect.DEFAULT_AMPLITUDE)); + VibrationAttributes attrs = new VibrationAttributes.Builder() .setUsage(VibrationAttributes.USAGE_ALARM) .build(); mVibratorService.vibrate(Process.myUid(), null, effect, attrs, @@ -64,10 +68,10 @@ public class VibratorServicePermissionTest extends TestCase { } /** - * Test that calling {@link android.os.IVibratorService#cancelVibrate()} requires permissions. + * Test that calling {@link android.os.IVibratorManagerService#cancelVibrate(IBinder)} requires + * permissions. * <p>Tests permission: - * {@link android.Manifest.permission#VIBRATE} - * @throws RemoteException + * {@link android.Manifest.permission#VIBRATE} */ public void testCancelVibrate() throws RemoteException { try { |