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