Merge "Introduce SystemVibratorManager" into sc-dev

This commit is contained in:
TreeHugger Robot
2021-02-11 13:12:18 +00:00
committed by Android (Google) Code Review
24 changed files with 1105 additions and 2309 deletions

View File

@@ -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;

View File

@@ -10441,6 +10441,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";
@@ -31629,6 +31630,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);
@@ -31643,10 +31645,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 {

View File

@@ -1371,6 +1371,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();

View File

@@ -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

View File

@@ -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

View File

@@ -73,6 +73,11 @@ final class InputDeviceVibrator extends Vibrator {
}
}
@Override
public int getId() {
return mVibratorId;
}
@Override
public boolean hasVibrator() {
return true;

View File

@@ -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);
}
}

View File

@@ -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++) {

View File

@@ -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);
}

View File

@@ -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

View File

@@ -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,142 +40,46 @@ 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));
}
}
/**
* 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.");
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");
return;
}
mDelegates.put(listener, delegate);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
}
/**
* 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);
@@ -187,88 +90,128 @@ public class SystemVibrator extends Vibrator {
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) {
public void addVibratorStateListener(
@NonNull @CallbackExecutor Executor executor,
@NonNull OnVibratorStateChangedListener listener) {
Objects.requireNonNull(listener);
if (mService == null) {
Log.w(TAG, "Failed to remove vibrate state listener; no vibrator service.");
Objects.requireNonNull(executor);
if (mVibratorManager == null) {
Log.w(TAG, "Failed to add 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();
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;
}
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();
}
}
@Override
public void removeVibratorStateListener(@NonNull OnVibratorStateChangedListener listener) {
Objects.requireNonNull(listener);
if (mVibratorManager == null) {
Log.w(TAG, "Failed to remove vibrate state listener; no vibrator manager.");
return;
}
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) {
if (mVibratorInfo != null) {
return mVibratorInfo;
}
if (mService == null) {
return null;
}
return mVibratorInfo = mService.getVibratorInfo();
return mVibratorListeners.size() > 0;
}
} catch (RemoteException e) {
Log.w(TAG, "Failed to query vibrator info");
throw e.rethrowFromSystemServer();
}
void register(VibratorManager vibratorManager) {
int[] vibratorIds = vibratorManager.getVibratorIds();
synchronized (mLock) {
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;
}
}
}
}
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);
}
}
}
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);
});
}
}
}

View File

@@ -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();
}
}
}
}
}
}

View File

@@ -183,6 +183,15 @@ public abstract class Vibrator {
return mDefaultRingVibrationIntensity;
}
/**
* 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.
*

View File

@@ -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();
}
/**
* 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.
* @hide to prevent subclassing from outside of the framework
*/
protected VibratorManager(Context context) {
mPackageName = context.getOpPackageName();
}
/**
* 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
*/
public abstract void vibrate(@NonNull CombinedVibrationEffect effect);
@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.
*/
@RequiresPermission(android.Manifest.permission.VIBRATE)
public abstract void cancel();
}

View File

@@ -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);
@@ -171,6 +172,37 @@ public class CombinedVibrationEffectTest {
.getDuration());
}
@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);

View File

@@ -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

View File

@@ -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++) {
@@ -506,6 +548,12 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
mVibratorManagerRecords.record(vib);
}
@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");
@@ -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";

File diff suppressed because it is too large Load Diff

View File

@@ -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.

View File

@@ -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;

View File

@@ -1291,7 +1291,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;
@@ -1417,11 +1416,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);
@@ -2490,14 +2484,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 {

View File

@@ -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;

View File

@@ -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;
}
}

View File

@@ -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 {