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
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -73,6 +73,11 @@ final class InputDeviceVibrator extends Vibrator {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getId() {
|
||||
return mVibratorId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasVibrator() {
|
||||
return true;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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++) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -17,19 +17,18 @@
|
||||
package android.os;
|
||||
|
||||
import android.annotation.CallbackExecutor;
|
||||
import android.annotation.IntDef;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.compat.annotation.UnsupportedAppUsage;
|
||||
import android.content.Context;
|
||||
import android.media.AudioAttributes;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.Log;
|
||||
import android.util.SparseArray;
|
||||
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
@@ -41,142 +40,46 @@ import java.util.concurrent.Executor;
|
||||
public class SystemVibrator extends Vibrator {
|
||||
private static final String TAG = "Vibrator";
|
||||
|
||||
private static final int VIBRATOR_PRESENT_UNKNOWN = 0;
|
||||
private static final int VIBRATOR_PRESENT_YES = 1;
|
||||
private static final int VIBRATOR_PRESENT_NO = 2;
|
||||
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@IntDef({VIBRATOR_PRESENT_UNKNOWN, VIBRATOR_PRESENT_YES, VIBRATOR_PRESENT_NO})
|
||||
private @interface VibratorPresent {}
|
||||
|
||||
private final IVibratorService mService;
|
||||
private final IVibratorManagerService mManagerService;
|
||||
private final Object mLock = new Object();
|
||||
private final Binder mToken = new Binder();
|
||||
private final VibratorManager mVibratorManager;
|
||||
private final Context mContext;
|
||||
@GuardedBy("mLock")
|
||||
private VibratorInfo mVibratorInfo;
|
||||
@GuardedBy("mLock")
|
||||
@VibratorPresent
|
||||
private int mVibratorPresent;
|
||||
|
||||
@GuardedBy("mDelegates")
|
||||
private final ArrayMap<OnVibratorStateChangedListener,
|
||||
OnVibratorStateChangedListenerDelegate> mDelegates = new ArrayMap<>();
|
||||
@GuardedBy("mBrokenListeners")
|
||||
private final ArrayList<AllVibratorsStateListener> mBrokenListeners = new ArrayList<>();
|
||||
|
||||
@UnsupportedAppUsage
|
||||
public SystemVibrator() {
|
||||
mContext = null;
|
||||
mService = IVibratorService.Stub.asInterface(ServiceManager.getService("vibrator"));
|
||||
mManagerService = IVibratorManagerService.Stub.asInterface(
|
||||
ServiceManager.getService("vibrator_manager"));
|
||||
}
|
||||
@GuardedBy("mRegisteredListeners")
|
||||
private final ArrayMap<OnVibratorStateChangedListener, AllVibratorsStateListener>
|
||||
mRegisteredListeners = new ArrayMap<>();
|
||||
|
||||
@UnsupportedAppUsage
|
||||
public SystemVibrator(Context context) {
|
||||
super(context);
|
||||
mContext = context;
|
||||
mService = IVibratorService.Stub.asInterface(ServiceManager.getService("vibrator"));
|
||||
mManagerService = IVibratorManagerService.Stub.asInterface(
|
||||
ServiceManager.getService("vibrator_manager"));
|
||||
mVibratorManager = mContext.getSystemService(VibratorManager.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasVibrator() {
|
||||
try {
|
||||
synchronized (mLock) {
|
||||
if (mVibratorPresent == VIBRATOR_PRESENT_UNKNOWN && mService != null) {
|
||||
mVibratorPresent =
|
||||
mService.hasVibrator() ? VIBRATOR_PRESENT_YES : VIBRATOR_PRESENT_NO;
|
||||
}
|
||||
return mVibratorPresent == VIBRATOR_PRESENT_YES;
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
Log.w(TAG, "Failed to query vibrator presence", e);
|
||||
if (mVibratorManager == null) {
|
||||
Log.w(TAG, "Failed to check if vibrator exists; no vibrator manager.");
|
||||
return false;
|
||||
}
|
||||
return mVibratorManager.getVibratorIds().length > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the vibrator is vibrating.
|
||||
*
|
||||
* @return True if the hardware is vibrating, otherwise false.
|
||||
*/
|
||||
@Override
|
||||
public boolean isVibrating() {
|
||||
if (mService == null) {
|
||||
Log.w(TAG, "Failed to vibrate; no vibrator service.");
|
||||
if (mVibratorManager == null) {
|
||||
Log.w(TAG, "Failed to vibrate; no vibrator manager.");
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
return mService.isVibrating();
|
||||
} catch (RemoteException e) {
|
||||
e.rethrowFromSystemServer();
|
||||
for (int vibratorId : mVibratorManager.getVibratorIds()) {
|
||||
if (mVibratorManager.getVibrator(vibratorId).isVibrating()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private class OnVibratorStateChangedListenerDelegate extends
|
||||
IVibratorStateListener.Stub {
|
||||
private final Executor mExecutor;
|
||||
private final OnVibratorStateChangedListener mListener;
|
||||
|
||||
OnVibratorStateChangedListenerDelegate(@NonNull OnVibratorStateChangedListener listener,
|
||||
@NonNull Executor executor) {
|
||||
mExecutor = executor;
|
||||
mListener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVibrating(boolean isVibrating) {
|
||||
mExecutor.execute(() -> mListener.onVibratorStateChanged(isVibrating));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a listener for vibrator state change. If the listener was previously added and not
|
||||
* removed, this call will be ignored.
|
||||
*
|
||||
* @param listener Listener to be added.
|
||||
* @param executor The {@link Executor} on which the listener's callbacks will be executed on.
|
||||
*/
|
||||
@Override
|
||||
public void addVibratorStateListener(
|
||||
@NonNull @CallbackExecutor Executor executor,
|
||||
@NonNull OnVibratorStateChangedListener listener) {
|
||||
Objects.requireNonNull(listener);
|
||||
Objects.requireNonNull(executor);
|
||||
if (mService == null) {
|
||||
Log.w(TAG, "Failed to add vibrate state listener; no vibrator service.");
|
||||
return;
|
||||
}
|
||||
|
||||
synchronized (mDelegates) {
|
||||
// If listener is already registered, reject and return.
|
||||
if (mDelegates.containsKey(listener)) {
|
||||
Log.w(TAG, "Listener already registered.");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
final OnVibratorStateChangedListenerDelegate delegate =
|
||||
new OnVibratorStateChangedListenerDelegate(listener, executor);
|
||||
if (!mService.registerVibratorStateListener(delegate)) {
|
||||
Log.w(TAG, "Failed to register vibrate state listener");
|
||||
return;
|
||||
}
|
||||
mDelegates.put(listener, delegate);
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a listener for vibrator state changes. Callbacks will be executed on the main thread.
|
||||
* If the listener was previously added and not removed, this call will be ignored.
|
||||
*
|
||||
* @param listener listener to be added
|
||||
*/
|
||||
@Override
|
||||
public void addVibratorStateListener(@NonNull OnVibratorStateChangedListener listener) {
|
||||
Objects.requireNonNull(listener);
|
||||
@@ -187,88 +90,128 @@ public class SystemVibrator extends Vibrator {
|
||||
addVibratorStateListener(mContext.getMainExecutor(), listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the listener for vibrator state changes. If the listener was not previously
|
||||
* registered, this call will do nothing.
|
||||
*
|
||||
* @param listener Listener to be removed.
|
||||
*/
|
||||
@Override
|
||||
public void removeVibratorStateListener(@NonNull OnVibratorStateChangedListener listener) {
|
||||
public void addVibratorStateListener(
|
||||
@NonNull @CallbackExecutor Executor executor,
|
||||
@NonNull OnVibratorStateChangedListener listener) {
|
||||
Objects.requireNonNull(listener);
|
||||
if (mService == null) {
|
||||
Log.w(TAG, "Failed to remove vibrate state listener; no vibrator service.");
|
||||
Objects.requireNonNull(executor);
|
||||
if (mVibratorManager == null) {
|
||||
Log.w(TAG, "Failed to add vibrate state listener; no vibrator manager.");
|
||||
return;
|
||||
}
|
||||
synchronized (mDelegates) {
|
||||
// Check if the listener is registered, otherwise will return.
|
||||
if (mDelegates.containsKey(listener)) {
|
||||
final OnVibratorStateChangedListenerDelegate delegate = mDelegates.get(listener);
|
||||
try {
|
||||
if (!mService.unregisterVibratorStateListener(delegate)) {
|
||||
Log.w(TAG, "Failed to unregister vibrate state listener");
|
||||
return;
|
||||
}
|
||||
mDelegates.remove(listener);
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowFromSystemServer();
|
||||
AllVibratorsStateListener delegate = null;
|
||||
try {
|
||||
synchronized (mRegisteredListeners) {
|
||||
// If listener is already registered, reject and return.
|
||||
if (mRegisteredListeners.containsKey(listener)) {
|
||||
Log.w(TAG, "Listener already registered.");
|
||||
return;
|
||||
}
|
||||
delegate = new AllVibratorsStateListener(executor, listener);
|
||||
delegate.register(mVibratorManager);
|
||||
mRegisteredListeners.put(listener, delegate);
|
||||
delegate = null;
|
||||
}
|
||||
} finally {
|
||||
if (delegate != null && delegate.hasRegisteredListeners()) {
|
||||
// The delegate listener was left in a partial state with listeners registered to
|
||||
// some but not all vibrators. Keep track of this to try to unregister them later.
|
||||
synchronized (mBrokenListeners) {
|
||||
mBrokenListeners.add(delegate);
|
||||
}
|
||||
}
|
||||
tryUnregisterBrokenListeners();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeVibratorStateListener(@NonNull OnVibratorStateChangedListener listener) {
|
||||
Objects.requireNonNull(listener);
|
||||
if (mVibratorManager == null) {
|
||||
Log.w(TAG, "Failed to remove vibrate state listener; no vibrator manager.");
|
||||
return;
|
||||
}
|
||||
synchronized (mRegisteredListeners) {
|
||||
if (mRegisteredListeners.containsKey(listener)) {
|
||||
AllVibratorsStateListener delegate = mRegisteredListeners.get(listener);
|
||||
delegate.unregister(mVibratorManager);
|
||||
mRegisteredListeners.remove(listener);
|
||||
}
|
||||
}
|
||||
tryUnregisterBrokenListeners();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasAmplitudeControl() {
|
||||
if (mService == null) {
|
||||
Log.w(TAG, "Failed to check amplitude control; no vibrator service.");
|
||||
if (mVibratorManager == null) {
|
||||
Log.w(TAG, "Failed to check vibrator has amplitude control; no vibrator manager.");
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
return mService.hasAmplitudeControl();
|
||||
} catch (RemoteException e) {
|
||||
int[] vibratorIds = mVibratorManager.getVibratorIds();
|
||||
if (vibratorIds.length == 0) {
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
for (int vibratorId : vibratorIds) {
|
||||
if (!mVibratorManager.getVibrator(vibratorId).hasAmplitudeControl()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setAlwaysOnEffect(int uid, String opPkg, int alwaysOnId, VibrationEffect effect,
|
||||
AudioAttributes attributes) {
|
||||
if (mManagerService == null) {
|
||||
Log.w(TAG, "Failed to set always-on effect; no vibrator service.");
|
||||
if (mVibratorManager == null) {
|
||||
Log.w(TAG, "Failed to set always-on effect; no vibrator manager.");
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
VibrationAttributes atr = new VibrationAttributes.Builder(attributes, effect).build();
|
||||
CombinedVibrationEffect combinedEffect = CombinedVibrationEffect.createSynced(effect);
|
||||
return mManagerService.setAlwaysOnEffect(uid, opPkg, alwaysOnId, combinedEffect, atr);
|
||||
} catch (RemoteException e) {
|
||||
Log.w(TAG, "Failed to set always-on effect.", e);
|
||||
}
|
||||
return false;
|
||||
VibrationAttributes attr = new VibrationAttributes.Builder(attributes, effect).build();
|
||||
CombinedVibrationEffect combinedEffect = CombinedVibrationEffect.createSynced(effect);
|
||||
return mVibratorManager.setAlwaysOnEffect(uid, opPkg, alwaysOnId, combinedEffect, attr);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void vibrate(int uid, String opPkg, @NonNull VibrationEffect effect,
|
||||
String reason, @NonNull VibrationAttributes attributes) {
|
||||
if (mService == null) {
|
||||
Log.w(TAG, "Failed to vibrate; no vibrator service.");
|
||||
if (mVibratorManager == null) {
|
||||
Log.w(TAG, "Failed to vibrate; no vibrator manager.");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
mService.vibrate(uid, opPkg, effect, attributes, reason, mToken);
|
||||
} catch (RemoteException e) {
|
||||
Log.w(TAG, "Failed to vibrate.", e);
|
||||
}
|
||||
CombinedVibrationEffect combinedEffect = CombinedVibrationEffect.createSynced(effect);
|
||||
mVibratorManager.vibrate(uid, opPkg, combinedEffect, reason, attributes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] areEffectsSupported(@VibrationEffect.EffectType int... effectIds) {
|
||||
VibratorInfo vibratorInfo = getVibratorInfo();
|
||||
int[] supported = new int[effectIds.length];
|
||||
for (int i = 0; i < effectIds.length; i++) {
|
||||
supported[i] = vibratorInfo == null
|
||||
? Vibrator.VIBRATION_EFFECT_SUPPORT_UNKNOWN
|
||||
: vibratorInfo.isEffectSupported(effectIds[i]);
|
||||
if (mVibratorManager == null) {
|
||||
Log.w(TAG, "Failed to check supported effects; no vibrator manager.");
|
||||
Arrays.fill(supported, Vibrator.VIBRATION_EFFECT_SUPPORT_NO);
|
||||
return supported;
|
||||
}
|
||||
int[] vibratorIds = mVibratorManager.getVibratorIds();
|
||||
if (vibratorIds.length == 0) {
|
||||
Arrays.fill(supported, Vibrator.VIBRATION_EFFECT_SUPPORT_NO);
|
||||
return supported;
|
||||
}
|
||||
int[][] vibratorSupportMap = new int[vibratorIds.length][effectIds.length];
|
||||
for (int i = 0; i < vibratorIds.length; i++) {
|
||||
vibratorSupportMap[i] = mVibratorManager.getVibrator(
|
||||
vibratorIds[i]).areEffectsSupported(effectIds);
|
||||
}
|
||||
Arrays.fill(supported, Vibrator.VIBRATION_EFFECT_SUPPORT_YES);
|
||||
for (int effectIdx = 0; effectIdx < effectIds.length; effectIdx++) {
|
||||
for (int vibratorIdx = 0; vibratorIdx < vibratorIds.length; vibratorIdx++) {
|
||||
int effectSupported = vibratorSupportMap[vibratorIdx][effectIdx];
|
||||
if (effectSupported == Vibrator.VIBRATION_EFFECT_SUPPORT_NO) {
|
||||
supported[effectIdx] = Vibrator.VIBRATION_EFFECT_SUPPORT_NO;
|
||||
break;
|
||||
} else if (effectSupported == Vibrator.VIBRATION_EFFECT_SUPPORT_UNKNOWN) {
|
||||
supported[effectIdx] = Vibrator.VIBRATION_EFFECT_SUPPORT_UNKNOWN;
|
||||
}
|
||||
}
|
||||
}
|
||||
return supported;
|
||||
}
|
||||
@@ -276,42 +219,169 @@ public class SystemVibrator extends Vibrator {
|
||||
@Override
|
||||
public boolean[] arePrimitivesSupported(
|
||||
@NonNull @VibrationEffect.Composition.Primitive int... primitiveIds) {
|
||||
VibratorInfo vibratorInfo = getVibratorInfo();
|
||||
boolean[] supported = new boolean[primitiveIds.length];
|
||||
for (int i = 0; i < primitiveIds.length; i++) {
|
||||
supported[i] = vibratorInfo == null
|
||||
? false : vibratorInfo.isPrimitiveSupported(primitiveIds[i]);
|
||||
if (mVibratorManager == null) {
|
||||
Log.w(TAG, "Failed to check supported primitives; no vibrator manager.");
|
||||
Arrays.fill(supported, false);
|
||||
return supported;
|
||||
}
|
||||
int[] vibratorIds = mVibratorManager.getVibratorIds();
|
||||
if (vibratorIds.length == 0) {
|
||||
Arrays.fill(supported, false);
|
||||
return supported;
|
||||
}
|
||||
boolean[][] vibratorSupportMap = new boolean[vibratorIds.length][primitiveIds.length];
|
||||
for (int i = 0; i < vibratorIds.length; i++) {
|
||||
vibratorSupportMap[i] = mVibratorManager.getVibrator(
|
||||
vibratorIds[i]).arePrimitivesSupported(primitiveIds);
|
||||
}
|
||||
Arrays.fill(supported, true);
|
||||
for (int primitiveIdx = 0; primitiveIdx < primitiveIds.length; primitiveIdx++) {
|
||||
for (int vibratorIdx = 0; vibratorIdx < vibratorIds.length; vibratorIdx++) {
|
||||
if (!vibratorSupportMap[vibratorIdx][primitiveIdx]) {
|
||||
supported[primitiveIdx] = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return supported;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel() {
|
||||
if (mService == null) {
|
||||
if (mVibratorManager == null) {
|
||||
Log.w(TAG, "Failed to cancel vibrate; no vibrator manager.");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
mService.cancelVibrate(mToken);
|
||||
} catch (RemoteException e) {
|
||||
Log.w(TAG, "Failed to cancel vibration.", e);
|
||||
mVibratorManager.cancel();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to unregister individual {@link android.os.Vibrator.OnVibratorStateChangedListener}
|
||||
* that were left registered to vibrators after failures to register them to all vibrators.
|
||||
*
|
||||
* <p>This might happen if {@link AllVibratorsStateListener} fails to register to any vibrator
|
||||
* and also fails to unregister any previously registered single listeners to other vibrators.
|
||||
*
|
||||
* <p>This method never throws {@link RuntimeException} if it fails to unregister again, it will
|
||||
* fail silently and attempt to unregister the same broken listener later.
|
||||
*/
|
||||
private void tryUnregisterBrokenListeners() {
|
||||
synchronized (mBrokenListeners) {
|
||||
try {
|
||||
for (int i = mBrokenListeners.size(); --i >= 0; ) {
|
||||
mBrokenListeners.get(i).unregister(mVibratorManager);
|
||||
mBrokenListeners.remove(i);
|
||||
}
|
||||
} catch (RuntimeException e) {
|
||||
Log.w(TAG, "Failed to unregister broken listener", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private VibratorInfo getVibratorInfo() {
|
||||
try {
|
||||
/** Listener for a single vibrator state change. */
|
||||
private static class SingleVibratorStateListener implements OnVibratorStateChangedListener {
|
||||
private final AllVibratorsStateListener mAllVibratorsListener;
|
||||
private final int mVibratorIdx;
|
||||
|
||||
SingleVibratorStateListener(AllVibratorsStateListener listener, int vibratorIdx) {
|
||||
mAllVibratorsListener = listener;
|
||||
mVibratorIdx = vibratorIdx;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVibratorStateChanged(boolean isVibrating) {
|
||||
mAllVibratorsListener.onVibrating(mVibratorIdx, isVibrating);
|
||||
}
|
||||
}
|
||||
|
||||
/** Listener for all vibrators state change. */
|
||||
private static class AllVibratorsStateListener {
|
||||
private final Object mLock = new Object();
|
||||
private final Executor mExecutor;
|
||||
private final OnVibratorStateChangedListener mDelegate;
|
||||
|
||||
@GuardedBy("mLock")
|
||||
private final SparseArray<SingleVibratorStateListener> mVibratorListeners =
|
||||
new SparseArray<>();
|
||||
|
||||
@GuardedBy("mLock")
|
||||
private int mInitializedMask;
|
||||
@GuardedBy("mLock")
|
||||
private int mVibratingMask;
|
||||
|
||||
AllVibratorsStateListener(@NonNull Executor executor,
|
||||
@NonNull OnVibratorStateChangedListener listener) {
|
||||
mExecutor = executor;
|
||||
mDelegate = listener;
|
||||
}
|
||||
|
||||
boolean hasRegisteredListeners() {
|
||||
synchronized (mLock) {
|
||||
if (mVibratorInfo != null) {
|
||||
return mVibratorInfo;
|
||||
}
|
||||
if (mService == null) {
|
||||
return null;
|
||||
}
|
||||
return mVibratorInfo = mService.getVibratorInfo();
|
||||
return mVibratorListeners.size() > 0;
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
Log.w(TAG, "Failed to query vibrator info");
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
|
||||
void register(VibratorManager vibratorManager) {
|
||||
int[] vibratorIds = vibratorManager.getVibratorIds();
|
||||
synchronized (mLock) {
|
||||
for (int i = 0; i < vibratorIds.length; i++) {
|
||||
int vibratorId = vibratorIds[i];
|
||||
SingleVibratorStateListener listener = new SingleVibratorStateListener(this, i);
|
||||
try {
|
||||
vibratorManager.getVibrator(vibratorId).addVibratorStateListener(mExecutor,
|
||||
listener);
|
||||
mVibratorListeners.put(vibratorId, listener);
|
||||
} catch (RuntimeException e) {
|
||||
try {
|
||||
unregister(vibratorManager);
|
||||
} catch (RuntimeException e1) {
|
||||
Log.w(TAG,
|
||||
"Failed to unregister listener while recovering from a failed "
|
||||
+ "register call", e1);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void unregister(VibratorManager vibratorManager) {
|
||||
synchronized (mLock) {
|
||||
for (int i = mVibratorListeners.size(); --i >= 0; ) {
|
||||
int vibratorId = mVibratorListeners.keyAt(i);
|
||||
SingleVibratorStateListener listener = mVibratorListeners.valueAt(i);
|
||||
vibratorManager.getVibrator(vibratorId).removeVibratorStateListener(listener);
|
||||
mVibratorListeners.removeAt(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void onVibrating(int vibratorIdx, boolean vibrating) {
|
||||
mExecutor.execute(() -> {
|
||||
boolean anyVibrating;
|
||||
synchronized (mLock) {
|
||||
int allInitializedMask = 1 << mVibratorListeners.size() - 1;
|
||||
int vibratorMask = 1 << vibratorIdx;
|
||||
if ((mInitializedMask & vibratorMask) == 0) {
|
||||
// First state report for this vibrator, set vibrating initial value.
|
||||
mInitializedMask |= vibratorMask;
|
||||
mVibratingMask |= vibrating ? vibratorMask : 0;
|
||||
} else {
|
||||
// Flip vibrating value, if changed.
|
||||
boolean prevVibrating = (mVibratingMask & vibratorMask) != 0;
|
||||
if (prevVibrating != vibrating) {
|
||||
mVibratingMask ^= vibratorMask;
|
||||
}
|
||||
}
|
||||
if (mInitializedMask != allInitializedMask) {
|
||||
// Wait for all vibrators initial state to be reported before delegating.
|
||||
return;
|
||||
}
|
||||
anyVibrating = mVibratingMask != 0;
|
||||
}
|
||||
mDelegate.onVibratorStateChanged(anyVibrating);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
360
core/java/android/os/SystemVibratorManager.java
Normal file
360
core/java/android/os/SystemVibratorManager.java
Normal file
@@ -0,0 +1,360 @@
|
||||
/*
|
||||
* Copyright (C) 2021 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.os;
|
||||
|
||||
import android.annotation.CallbackExecutor;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.content.Context;
|
||||
import android.media.AudioAttributes;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.Log;
|
||||
import android.util.SparseArray;
|
||||
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
/**
|
||||
* VibratorManager implementation that controls the system vibrators.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public class SystemVibratorManager extends VibratorManager {
|
||||
private static final String TAG = "VibratorManager";
|
||||
|
||||
private final IVibratorManagerService mService;
|
||||
private final Context mContext;
|
||||
private final Binder mToken = new Binder();
|
||||
private final Object mLock = new Object();
|
||||
@GuardedBy("mLock")
|
||||
private int[] mVibratorIds;
|
||||
@GuardedBy("mLock")
|
||||
private final SparseArray<Vibrator> mVibrators = new SparseArray<>();
|
||||
|
||||
@GuardedBy("mLock")
|
||||
private final ArrayMap<Vibrator.OnVibratorStateChangedListener,
|
||||
OnVibratorStateChangedListenerDelegate> mListeners = new ArrayMap<>();
|
||||
|
||||
/**
|
||||
* @hide to prevent subclassing from outside of the framework
|
||||
*/
|
||||
public SystemVibratorManager(Context context) {
|
||||
super(context);
|
||||
mContext = context;
|
||||
mService = IVibratorManagerService.Stub.asInterface(
|
||||
ServiceManager.getService(Context.VIBRATOR_MANAGER_SERVICE));
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public int[] getVibratorIds() {
|
||||
synchronized (mLock) {
|
||||
if (mVibratorIds != null) {
|
||||
return mVibratorIds;
|
||||
}
|
||||
try {
|
||||
if (mService == null) {
|
||||
Log.w(TAG, "Failed to retrieve vibrator ids; no vibrator manager service.");
|
||||
} else {
|
||||
return mVibratorIds = mService.getVibratorIds();
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
e.rethrowFromSystemServer();
|
||||
}
|
||||
return new int[0];
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Vibrator getVibrator(int vibratorId) {
|
||||
synchronized (mLock) {
|
||||
Vibrator vibrator = mVibrators.get(vibratorId);
|
||||
if (vibrator != null) {
|
||||
return vibrator;
|
||||
}
|
||||
VibratorInfo info = null;
|
||||
try {
|
||||
if (mService == null) {
|
||||
Log.w(TAG, "Failed to retrieve vibrator; no vibrator manager service.");
|
||||
} else {
|
||||
info = mService.getVibratorInfo(vibratorId);
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
e.rethrowFromSystemServer();
|
||||
}
|
||||
if (info != null) {
|
||||
vibrator = new SingleVibrator(info);
|
||||
mVibrators.put(vibratorId, vibrator);
|
||||
} else {
|
||||
vibrator = NullVibrator.getInstance();
|
||||
}
|
||||
return vibrator;
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Vibrator getDefaultVibrator() {
|
||||
return mContext.getSystemService(Vibrator.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setAlwaysOnEffect(int uid, String opPkg, int alwaysOnId,
|
||||
@Nullable CombinedVibrationEffect effect, @Nullable VibrationAttributes attributes) {
|
||||
if (mService == null) {
|
||||
Log.w(TAG, "Failed to set always-on effect; no vibrator manager service.");
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
return mService.setAlwaysOnEffect(uid, opPkg, alwaysOnId, effect, attributes);
|
||||
} catch (RemoteException e) {
|
||||
Log.w(TAG, "Failed to set always-on effect.", e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void vibrate(int uid, String opPkg, @NonNull CombinedVibrationEffect effect,
|
||||
String reason, @Nullable VibrationAttributes attributes) {
|
||||
if (mService == null) {
|
||||
Log.w(TAG, "Failed to vibrate; no vibrator manager service.");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
mService.vibrate(uid, opPkg, effect, attributes, reason, mToken);
|
||||
} catch (RemoteException e) {
|
||||
Log.w(TAG, "Failed to vibrate.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel() {
|
||||
if (mService == null) {
|
||||
Log.w(TAG, "Failed to cancel vibration; no vibrator manager service.");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
mService.cancelVibrate(mToken);
|
||||
} catch (RemoteException e) {
|
||||
Log.w(TAG, "Failed to cancel vibration.", e);
|
||||
}
|
||||
}
|
||||
|
||||
/** Listener for vibrations on a single vibrator. */
|
||||
private static class OnVibratorStateChangedListenerDelegate extends
|
||||
IVibratorStateListener.Stub {
|
||||
private final Executor mExecutor;
|
||||
private final Vibrator.OnVibratorStateChangedListener mListener;
|
||||
|
||||
OnVibratorStateChangedListenerDelegate(
|
||||
@NonNull Vibrator.OnVibratorStateChangedListener listener,
|
||||
@NonNull Executor executor) {
|
||||
mExecutor = executor;
|
||||
mListener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVibrating(boolean isVibrating) {
|
||||
mExecutor.execute(() -> mListener.onVibratorStateChanged(isVibrating));
|
||||
}
|
||||
}
|
||||
|
||||
/** Controls vibrations on a single vibrator. */
|
||||
private final class SingleVibrator extends Vibrator {
|
||||
private final VibratorInfo mVibratorInfo;
|
||||
|
||||
SingleVibrator(@NonNull VibratorInfo vibratorInfo) {
|
||||
mVibratorInfo = vibratorInfo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getId() {
|
||||
return mVibratorInfo.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasVibrator() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasAmplitudeControl() {
|
||||
return mVibratorInfo.hasAmplitudeControl();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public int[] areEffectsSupported(@NonNull int... effectIds) {
|
||||
int[] supported = new int[effectIds.length];
|
||||
for (int i = 0; i < effectIds.length; i++) {
|
||||
supported[i] = mVibratorInfo.isEffectSupported(effectIds[i]);
|
||||
}
|
||||
return supported;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean[] arePrimitivesSupported(
|
||||
@NonNull @VibrationEffect.Composition.Primitive int... primitiveIds) {
|
||||
boolean[] supported = new boolean[primitiveIds.length];
|
||||
for (int i = 0; i < primitiveIds.length; i++) {
|
||||
supported[i] = mVibratorInfo.isPrimitiveSupported(primitiveIds[i]);
|
||||
}
|
||||
return supported;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setAlwaysOnEffect(int uid, String opPkg, int alwaysOnId,
|
||||
@Nullable VibrationEffect effect, @Nullable AudioAttributes attributes) {
|
||||
if (mService == null) {
|
||||
Log.w(TAG, "Failed to set always-on effect on vibrator " + mVibratorInfo.getId()
|
||||
+ "; no vibrator manager service.");
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
VibrationAttributes attr = new VibrationAttributes.Builder(
|
||||
attributes, effect).build();
|
||||
CombinedVibrationEffect combined = CombinedVibrationEffect.startSynced()
|
||||
.addVibrator(mVibratorInfo.getId(), effect)
|
||||
.combine();
|
||||
return mService.setAlwaysOnEffect(uid, opPkg, alwaysOnId, combined, attr);
|
||||
} catch (RemoteException e) {
|
||||
Log.w(TAG, "Failed to set always-on effect on vibrator " + mVibratorInfo.getId());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void vibrate(int uid, String opPkg, @NonNull VibrationEffect vibe, String reason,
|
||||
@NonNull VibrationAttributes attributes) {
|
||||
if (mService == null) {
|
||||
Log.w(TAG, "Failed to vibrate on vibrator " + mVibratorInfo.getId()
|
||||
+ "; no vibrator manager service.");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
CombinedVibrationEffect combined = CombinedVibrationEffect.startSynced()
|
||||
.addVibrator(mVibratorInfo.getId(), vibe)
|
||||
.combine();
|
||||
mService.vibrate(uid, opPkg, combined, attributes, reason, mToken);
|
||||
} catch (RemoteException e) {
|
||||
Log.w(TAG, "Failed to vibrate.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel() {
|
||||
if (mService == null) {
|
||||
Log.w(TAG, "Failed to cancel vibration on vibrator " + mVibratorInfo.getId()
|
||||
+ "; no vibrator manager service.");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
mService.cancelVibrate(mToken);
|
||||
} catch (RemoteException e) {
|
||||
Log.w(TAG, "Failed to cancel vibration on vibrator " + mVibratorInfo.getId(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVibrating() {
|
||||
if (mService == null) {
|
||||
Log.w(TAG, "Failed to check status of vibrator " + mVibratorInfo.getId()
|
||||
+ "; no vibrator service.");
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
return mService.isVibrating(mVibratorInfo.getId());
|
||||
} catch (RemoteException e) {
|
||||
e.rethrowFromSystemServer();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addVibratorStateListener(@NonNull OnVibratorStateChangedListener listener) {
|
||||
Objects.requireNonNull(listener);
|
||||
if (mContext == null) {
|
||||
Log.w(TAG, "Failed to add vibrate state listener; no vibrator context.");
|
||||
return;
|
||||
}
|
||||
addVibratorStateListener(mContext.getMainExecutor(), listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addVibratorStateListener(
|
||||
@NonNull @CallbackExecutor Executor executor,
|
||||
@NonNull OnVibratorStateChangedListener listener) {
|
||||
Objects.requireNonNull(listener);
|
||||
Objects.requireNonNull(executor);
|
||||
if (mService == null) {
|
||||
Log.w(TAG,
|
||||
"Failed to add vibrate state listener to vibrator " + mVibratorInfo.getId()
|
||||
+ "; no vibrator service.");
|
||||
return;
|
||||
}
|
||||
synchronized (mLock) {
|
||||
// If listener is already registered, reject and return.
|
||||
if (mListeners.containsKey(listener)) {
|
||||
Log.w(TAG, "Listener already registered.");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
OnVibratorStateChangedListenerDelegate delegate =
|
||||
new OnVibratorStateChangedListenerDelegate(listener, executor);
|
||||
if (!mService.registerVibratorStateListener(mVibratorInfo.getId(), delegate)) {
|
||||
Log.w(TAG, "Failed to add vibrate state listener to vibrator "
|
||||
+ mVibratorInfo.getId());
|
||||
return;
|
||||
}
|
||||
mListeners.put(listener, delegate);
|
||||
} catch (RemoteException e) {
|
||||
e.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeVibratorStateListener(@NonNull OnVibratorStateChangedListener listener) {
|
||||
Objects.requireNonNull(listener);
|
||||
if (mService == null) {
|
||||
Log.w(TAG, "Failed to remove vibrate state listener from vibrator "
|
||||
+ mVibratorInfo.getId() + "; no vibrator service.");
|
||||
return;
|
||||
}
|
||||
synchronized (mLock) {
|
||||
// Check if the listener is registered, otherwise will return.
|
||||
if (mListeners.containsKey(listener)) {
|
||||
OnVibratorStateChangedListenerDelegate delegate = mListeners.get(listener);
|
||||
try {
|
||||
if (!mService.unregisterVibratorStateListener(mVibratorInfo.getId(),
|
||||
delegate)) {
|
||||
Log.w(TAG, "Failed to remove vibrate state listener from vibrator "
|
||||
+ mVibratorInfo.getId());
|
||||
return;
|
||||
}
|
||||
mListeners.remove(listener);
|
||||
} catch (RemoteException e) {
|
||||
e.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -183,6 +183,15 @@ public abstract class Vibrator {
|
||||
return mDefaultRingVibrationIntensity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the ID of this vibrator.
|
||||
*
|
||||
* @return The id of the vibrator controlled by this service.
|
||||
*/
|
||||
public int getId() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the hardware has a vibrator.
|
||||
*
|
||||
|
||||
@@ -17,45 +17,123 @@
|
||||
package android.os;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.RequiresPermission;
|
||||
import android.annotation.SystemService;
|
||||
import android.app.ActivityThread;
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* VibratorManager provides access to multiple vibrators, as well as the ability to run them in
|
||||
* a synchronized fashion.
|
||||
* Class that provides access to all vibrators from the device, as well as the ability to run them
|
||||
* in a synchronized fashion.
|
||||
* <p>
|
||||
* If your process exits, any vibration you started will stop.
|
||||
* </p>
|
||||
*/
|
||||
@SystemService(Context.VIBRATOR_MANAGER_SERVICE)
|
||||
public abstract class VibratorManager {
|
||||
/** @hide */
|
||||
protected static final String TAG = "VibratorManager";
|
||||
private static final String TAG = "VibratorManager";
|
||||
|
||||
private final String mPackageName;
|
||||
|
||||
/**
|
||||
* {@hide}
|
||||
* @hide to prevent subclassing from outside of the framework
|
||||
*/
|
||||
public VibratorManager() {
|
||||
mPackageName = ActivityThread.currentPackageName();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method lists all available actuator ids, returning a possible empty list.
|
||||
* If the device has only a single actuator, this should return a single entry with a
|
||||
* default id.
|
||||
* @hide to prevent subclassing from outside of the framework
|
||||
*/
|
||||
protected VibratorManager(Context context) {
|
||||
mPackageName = context.getOpPackageName();
|
||||
}
|
||||
|
||||
/**
|
||||
* List all available vibrator ids, returning a possible empty list.
|
||||
*
|
||||
* @return An array containing the ids of the vibrators available on the device.
|
||||
*/
|
||||
@NonNull
|
||||
public abstract int[] getVibratorIds();
|
||||
|
||||
/**
|
||||
* Returns a Vibrator service for given id.
|
||||
* This allows users to perform a vibration effect on a single actuator.
|
||||
*/
|
||||
* Retrieve a single vibrator by id.
|
||||
*
|
||||
* @param vibratorId The id of the vibrator to be retrieved.
|
||||
* @return The vibrator with given {@code vibratorId}, never null.
|
||||
*/
|
||||
@NonNull
|
||||
public abstract Vibrator getVibrator(int vibratorId);
|
||||
|
||||
/**
|
||||
* Returns the system default Vibrator service.
|
||||
*/
|
||||
* Returns the system default Vibrator service.
|
||||
*/
|
||||
@NonNull
|
||||
public abstract Vibrator getDefaultVibrator();
|
||||
|
||||
/**
|
||||
* Vibrates all actuators by passing each VibrationEffect within CombinedVibrationEffect
|
||||
* to the respective actuator, in sync.
|
||||
* Configure an always-on haptics effect.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public abstract void vibrate(@NonNull CombinedVibrationEffect effect);
|
||||
@RequiresPermission(android.Manifest.permission.VIBRATE_ALWAYS_ON)
|
||||
public boolean setAlwaysOnEffect(int uid, String opPkg, int alwaysOnId,
|
||||
@Nullable CombinedVibrationEffect effect, @Nullable VibrationAttributes attributes) {
|
||||
Log.w(TAG, "Always-on effects aren't supported");
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Vibrate with a given combination of effects.
|
||||
*
|
||||
* <p>
|
||||
* Pass in a {@link CombinedVibrationEffect} representing a combination of {@link
|
||||
* VibrationEffect} to be played on one or more vibrators.
|
||||
* </p>
|
||||
*
|
||||
* @param effect an array of longs of times for which to turn the vibrator on or off.
|
||||
*/
|
||||
@RequiresPermission(android.Manifest.permission.VIBRATE)
|
||||
public final void vibrate(@NonNull CombinedVibrationEffect effect) {
|
||||
vibrate(effect, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Vibrate with a given combination of effects.
|
||||
*
|
||||
* <p>
|
||||
* Pass in a {@link CombinedVibrationEffect} representing a combination of {@link
|
||||
* VibrationEffect} to be played on one or more vibrators.
|
||||
* </p>
|
||||
*
|
||||
* @param effect an array of longs of times for which to turn the vibrator on or off.
|
||||
* @param attributes {@link VibrationAttributes} corresponding to the vibration. For example,
|
||||
* specify {@link VibrationAttributes#USAGE_ALARM} for alarm vibrations or
|
||||
* {@link VibrationAttributes#USAGE_RINGTONE} for vibrations associated with
|
||||
* incoming calls.
|
||||
*/
|
||||
@RequiresPermission(android.Manifest.permission.VIBRATE)
|
||||
public final void vibrate(@NonNull CombinedVibrationEffect effect,
|
||||
@Nullable VibrationAttributes attributes) {
|
||||
vibrate(Process.myUid(), mPackageName, effect, null, attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Like {@link #vibrate(CombinedVibrationEffect, VibrationAttributes)}, but allows the
|
||||
* caller to specify the vibration is owned by someone else and set reason for vibration.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@RequiresPermission(android.Manifest.permission.VIBRATE)
|
||||
public abstract void vibrate(int uid, String opPkg, @NonNull CombinedVibrationEffect effect,
|
||||
String reason, @Nullable VibrationAttributes attributes);
|
||||
|
||||
/**
|
||||
* Turn all the vibrators off.
|
||||
*/
|
||||
@RequiresPermission(android.Manifest.permission.VIBRATE)
|
||||
public abstract void cancel();
|
||||
}
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
package android.os;
|
||||
|
||||
import static junit.framework.Assert.assertEquals;
|
||||
import static junit.framework.Assert.assertFalse;
|
||||
import static junit.framework.Assert.assertTrue;
|
||||
|
||||
import static org.testng.Assert.assertThrows;
|
||||
|
||||
@@ -31,7 +33,6 @@ import java.util.Arrays;
|
||||
@Presubmit
|
||||
@RunWith(JUnit4.class)
|
||||
public class CombinedVibrationEffectTest {
|
||||
|
||||
private static final VibrationEffect VALID_EFFECT = VibrationEffect.createOneShot(10, 255);
|
||||
private static final VibrationEffect INVALID_EFFECT = new VibrationEffect.OneShot(-1, -1);
|
||||
|
||||
@@ -171,6 +172,37 @@ public class CombinedVibrationEffectTest {
|
||||
.getDuration());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHasVibratorMono_returnsTrueForAnyVibrator() {
|
||||
CombinedVibrationEffect effect = CombinedVibrationEffect.createSynced(
|
||||
VibrationEffect.get(VibrationEffect.EFFECT_CLICK));
|
||||
assertTrue(effect.hasVibrator(0));
|
||||
assertTrue(effect.hasVibrator(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHasVibratorStereo_returnsOnlyTheIdsSet() {
|
||||
CombinedVibrationEffect effect = CombinedVibrationEffect.startSynced()
|
||||
.addVibrator(1, VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
|
||||
.combine();
|
||||
assertFalse(effect.hasVibrator(0));
|
||||
assertTrue(effect.hasVibrator(1));
|
||||
assertFalse(effect.hasVibrator(2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHasVibratorSequential_returnsNestedVibrators() {
|
||||
CombinedVibrationEffect effect = CombinedVibrationEffect.startSequential()
|
||||
.addNext(1, VibrationEffect.get(VibrationEffect.EFFECT_CLICK))
|
||||
.addNext(CombinedVibrationEffect.startSynced()
|
||||
.addVibrator(2, VibrationEffect.get(VibrationEffect.EFFECT_TICK))
|
||||
.combine())
|
||||
.combine();
|
||||
assertFalse(effect.hasVibrator(0));
|
||||
assertTrue(effect.hasVibrator(1));
|
||||
assertTrue(effect.hasVibrator(2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSerializationMono() {
|
||||
CombinedVibrationEffect original = CombinedVibrationEffect.createSynced(VALID_EFFECT);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -18,9 +18,14 @@ package com.android.server;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.app.ActivityManager;
|
||||
import android.app.AppOpsManager;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.PackageManagerInternal;
|
||||
import android.hardware.vibrator.IVibrator;
|
||||
import android.os.BatteryStats;
|
||||
import android.os.Binder;
|
||||
@@ -74,6 +79,7 @@ import java.util.function.Function;
|
||||
/** System implementation of {@link IVibratorManagerService}. */
|
||||
public class VibratorManagerService extends IVibratorManagerService.Stub {
|
||||
private static final String TAG = "VibratorManagerService";
|
||||
private static final String EXTERNAL_VIBRATOR_SERVICE = "external_vibrator_service";
|
||||
private static final boolean DEBUG = false;
|
||||
private static final VibrationAttributes DEFAULT_ATTRIBUTES =
|
||||
new VibrationAttributes.Builder().build();
|
||||
@@ -89,7 +95,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
|
||||
@Override
|
||||
public void onStart() {
|
||||
mService = new VibratorManagerService(getContext(), new Injector());
|
||||
publishBinderService("vibrator_manager", mService);
|
||||
publishBinderService(Context.VIBRATOR_MANAGER_SERVICE, mService);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -105,6 +111,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
|
||||
|
||||
private final Object mLock = new Object();
|
||||
private final Context mContext;
|
||||
private final String mSystemUiPackage;
|
||||
private final PowerManager.WakeLock mWakeLock;
|
||||
private final IBatteryStats mBatteryStatsService;
|
||||
private final Handler mHandler;
|
||||
@@ -128,6 +135,26 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
|
||||
private VibrationScaler mVibrationScaler;
|
||||
private InputDeviceDelegate mInputDeviceDelegate;
|
||||
|
||||
private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
|
||||
synchronized (mLock) {
|
||||
// When the system is entering a non-interactive state, we want
|
||||
// to cancel vibrations in case a misbehaving app has abandoned
|
||||
// them. However it may happen that the system is currently playing
|
||||
// haptic feedback as part of the transition. So we don't cancel
|
||||
// system vibrations.
|
||||
if (mCurrentVibration != null
|
||||
&& !isSystemHapticFeedback(mCurrentVibration.getVibration())) {
|
||||
mNextVibration = null;
|
||||
mCurrentVibration.cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static native long nativeInit(OnSyncedVibrationCompleteListener listener);
|
||||
|
||||
static native long nativeGetFinalizer();
|
||||
@@ -155,6 +182,9 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
|
||||
com.android.internal.R.integer.config_previousVibrationsDumpLimit);
|
||||
mVibratorManagerRecords = new VibratorManagerRecords(dumpLimit);
|
||||
|
||||
mSystemUiPackage = LocalServices.getService(PackageManagerInternal.class)
|
||||
.getSystemUiServiceComponent().getPackageName();
|
||||
|
||||
mBatteryStatsService = IBatteryStats.Stub.asInterface(ServiceManager.getService(
|
||||
BatteryStats.SERVICE_NAME));
|
||||
|
||||
@@ -184,6 +214,12 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
|
||||
for (int i = 0; i < mVibrators.size(); i++) {
|
||||
mVibrators.valueAt(i).off();
|
||||
}
|
||||
|
||||
IntentFilter filter = new IntentFilter();
|
||||
filter.addAction(Intent.ACTION_SCREEN_OFF);
|
||||
context.registerReceiver(mIntentReceiver, filter);
|
||||
|
||||
injector.addService(EXTERNAL_VIBRATOR_SERVICE, new ExternalVibratorService());
|
||||
}
|
||||
|
||||
/** Finish initialization at boot phase {@link SystemService#PHASE_SYSTEM_SERVICES_READY}. */
|
||||
@@ -371,7 +407,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
|
||||
mVibratorManagerRecords.record(mCurrentExternalVibration);
|
||||
mCurrentExternalVibration.externalVibration.mute();
|
||||
mCurrentExternalVibration = null;
|
||||
// TODO(b/167946816): set external control to false
|
||||
setExternalControl(false);
|
||||
}
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(ident);
|
||||
@@ -432,6 +468,12 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
|
||||
}
|
||||
}
|
||||
|
||||
private void setExternalControl(boolean externalControl) {
|
||||
for (int i = 0; i < mVibrators.size(); i++) {
|
||||
mVibrators.valueAt(i).setExternalControl(externalControl);
|
||||
}
|
||||
}
|
||||
|
||||
@GuardedBy("mLock")
|
||||
private void updateAlwaysOnLocked(AlwaysOnVibration vib) {
|
||||
for (int i = 0; i < vib.effects.size(); i++) {
|
||||
@@ -506,6 +548,12 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
|
||||
mVibratorManagerRecords.record(vib);
|
||||
}
|
||||
|
||||
@GuardedBy("mLock")
|
||||
private void endVibrationLocked(ExternalVibrationHolder vib, Vibration.Status status) {
|
||||
vib.end(status);
|
||||
mVibratorManagerRecords.record(vib);
|
||||
}
|
||||
|
||||
@GuardedBy("mLock")
|
||||
private void reportFinishedVibrationLocked(Vibration.Status status) {
|
||||
Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "reportFinishVibrationLocked");
|
||||
@@ -827,6 +875,13 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
|
||||
== PackageManager.PERMISSION_GRANTED;
|
||||
}
|
||||
|
||||
private boolean isSystemHapticFeedback(Vibration vib) {
|
||||
if (vib.attrs.getUsage() != VibrationAttributes.USAGE_TOUCH) {
|
||||
return false;
|
||||
}
|
||||
return vib.uid == Process.SYSTEM_UID || vib.uid == 0 || mSystemUiPackage.equals(vib.opPkg);
|
||||
}
|
||||
|
||||
@GuardedBy("mLock")
|
||||
private void onAllVibratorsLocked(Consumer<VibratorController> consumer) {
|
||||
for (int i = 0; i < mVibrators.size(); i++) {
|
||||
@@ -859,6 +914,10 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
|
||||
VibratorController.OnVibrationCompleteListener listener) {
|
||||
return new VibratorController(vibratorId, listener);
|
||||
}
|
||||
|
||||
void addService(String name, IBinder service) {
|
||||
ServiceManager.addService(name, service);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1088,14 +1147,14 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
|
||||
}
|
||||
pw.println();
|
||||
pw.println(" mCurrentVibration:");
|
||||
pw.println(" " + mCurrentVibration == null
|
||||
? null : mCurrentVibration.getVibration().getDebugInfo());
|
||||
pw.println(" " + (mCurrentVibration == null
|
||||
? null : mCurrentVibration.getVibration().getDebugInfo()));
|
||||
pw.println(" mNextVibration:");
|
||||
pw.println(" " + mNextVibration == null
|
||||
? null : mNextVibration.getVibration().getDebugInfo());
|
||||
pw.println(" " + (mNextVibration == null
|
||||
? null : mNextVibration.getVibration().getDebugInfo()));
|
||||
pw.println(" mCurrentExternalVibration:");
|
||||
pw.println(" " + mCurrentExternalVibration == null
|
||||
? null : mCurrentExternalVibration.getDebugInfo());
|
||||
pw.println(" " + (mCurrentExternalVibration == null
|
||||
? null : mCurrentExternalVibration.getDebugInfo()));
|
||||
pw.println();
|
||||
pw.println(" mVibrationSettings=" + mVibrationSettings);
|
||||
for (int i = 0; i < mPreviousVibrations.size(); i++) {
|
||||
@@ -1168,6 +1227,145 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
|
||||
}
|
||||
}
|
||||
|
||||
/** Implementation of {@link IExternalVibratorService} to be triggered on external control. */
|
||||
private final class ExternalVibratorService extends IExternalVibratorService.Stub {
|
||||
ExternalVibrationDeathRecipient mCurrentExternalDeathRecipient;
|
||||
|
||||
@Override
|
||||
public int onExternalVibrationStart(ExternalVibration vib) {
|
||||
if (!hasExternalControlCapability()) {
|
||||
return IExternalVibratorService.SCALE_MUTE;
|
||||
}
|
||||
if (ActivityManager.checkComponentPermission(android.Manifest.permission.VIBRATE,
|
||||
vib.getUid(), -1 /*owningUid*/, true /*exported*/)
|
||||
!= PackageManager.PERMISSION_GRANTED) {
|
||||
Slog.w(TAG, "pkg=" + vib.getPackage() + ", uid=" + vib.getUid()
|
||||
+ " tried to play externally controlled vibration"
|
||||
+ " without VIBRATE permission, ignoring.");
|
||||
return IExternalVibratorService.SCALE_MUTE;
|
||||
}
|
||||
|
||||
int mode = checkAppOpModeLocked(vib.getUid(), vib.getPackage(),
|
||||
vib.getVibrationAttributes());
|
||||
if (mode != AppOpsManager.MODE_ALLOWED) {
|
||||
ExternalVibrationHolder vibHolder = new ExternalVibrationHolder(vib);
|
||||
vibHolder.scale = IExternalVibratorService.SCALE_MUTE;
|
||||
if (mode == AppOpsManager.MODE_ERRORED) {
|
||||
Slog.w(TAG, "Would be an error: external vibrate from uid " + vib.getUid());
|
||||
endVibrationLocked(vibHolder, Vibration.Status.IGNORED_ERROR_APP_OPS);
|
||||
} else {
|
||||
endVibrationLocked(vibHolder, Vibration.Status.IGNORED_APP_OPS);
|
||||
}
|
||||
return vibHolder.scale;
|
||||
}
|
||||
|
||||
VibrationThread cancelingVibration = null;
|
||||
int scale;
|
||||
synchronized (mLock) {
|
||||
if (mCurrentExternalVibration != null
|
||||
&& mCurrentExternalVibration.externalVibration.equals(vib)) {
|
||||
// We are already playing this external vibration, so we can return the same
|
||||
// scale calculated in the previous call to this method.
|
||||
return mCurrentExternalVibration.scale;
|
||||
}
|
||||
if (mCurrentExternalVibration == null) {
|
||||
// If we're not under external control right now, then cancel any normal
|
||||
// vibration that may be playing and ready the vibrator for external control.
|
||||
if (mCurrentVibration != null) {
|
||||
mNextVibration = null;
|
||||
mCurrentVibration.cancel();
|
||||
cancelingVibration = mCurrentVibration;
|
||||
}
|
||||
} else {
|
||||
endVibrationLocked(mCurrentExternalVibration, Vibration.Status.CANCELLED);
|
||||
}
|
||||
// At this point we either have an externally controlled vibration playing, or
|
||||
// no vibration playing. Since the interface defines that only one externally
|
||||
// controlled vibration can play at a time, by returning something other than
|
||||
// SCALE_MUTE from this function we can be assured that if we are currently
|
||||
// playing vibration, it will be muted in favor of the new vibration.
|
||||
//
|
||||
// Note that this doesn't support multiple concurrent external controls, as we
|
||||
// would need to mute the old one still if it came from a different controller.
|
||||
mCurrentExternalVibration = new ExternalVibrationHolder(vib);
|
||||
mCurrentExternalDeathRecipient = new ExternalVibrationDeathRecipient();
|
||||
vib.linkToDeath(mCurrentExternalDeathRecipient);
|
||||
mCurrentExternalVibration.scale = mVibrationScaler.getExternalVibrationScale(
|
||||
vib.getVibrationAttributes().getUsage());
|
||||
scale = mCurrentExternalVibration.scale;
|
||||
}
|
||||
|
||||
if (cancelingVibration != null) {
|
||||
try {
|
||||
cancelingVibration.join();
|
||||
} catch (InterruptedException e) {
|
||||
Slog.w("Interrupted while waiting for vibration to finish before starting "
|
||||
+ "external control", e);
|
||||
}
|
||||
}
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "Vibrator going under external control.");
|
||||
}
|
||||
setExternalControl(true);
|
||||
if (DEBUG) {
|
||||
Slog.e(TAG, "Playing external vibration: " + vib);
|
||||
}
|
||||
return scale;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onExternalVibrationStop(ExternalVibration vib) {
|
||||
synchronized (mLock) {
|
||||
if (mCurrentExternalVibration != null
|
||||
&& mCurrentExternalVibration.externalVibration.equals(vib)) {
|
||||
if (DEBUG) {
|
||||
Slog.e(TAG, "Stopping external vibration" + vib);
|
||||
}
|
||||
stopExternalVibrateLocked(Vibration.Status.FINISHED);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void stopExternalVibrateLocked(Vibration.Status status) {
|
||||
Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "stopExternalVibrateLocked");
|
||||
try {
|
||||
if (mCurrentExternalVibration == null) {
|
||||
return;
|
||||
}
|
||||
endVibrationLocked(mCurrentExternalVibration, status);
|
||||
mCurrentExternalVibration.externalVibration.unlinkToDeath(
|
||||
mCurrentExternalDeathRecipient);
|
||||
mCurrentExternalDeathRecipient = null;
|
||||
mCurrentExternalVibration = null;
|
||||
setExternalControl(false);
|
||||
} finally {
|
||||
Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasExternalControlCapability() {
|
||||
for (int i = 0; i < mVibrators.size(); i++) {
|
||||
if (mVibrators.valueAt(i).hasCapability(IVibrator.CAP_EXTERNAL_CONTROL)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private class ExternalVibrationDeathRecipient implements IBinder.DeathRecipient {
|
||||
public void binderDied() {
|
||||
synchronized (mLock) {
|
||||
if (mCurrentExternalVibration != null) {
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "External vibration finished because binder died");
|
||||
}
|
||||
stopExternalVibrateLocked(Vibration.Status.CANCELLED);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Provide limited functionality from {@link VibratorManagerService} as shell commands. */
|
||||
private final class VibratorManagerShellCommand extends ShellCommand {
|
||||
public static final String SHELL_PACKAGE_NAME = "com.android.shell";
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
Reference in New Issue
Block a user