Merge "Introduce SystemVibratorManager" into sc-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
ba4fa60b35
@@ -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;
|
||||
|
||||
@@ -10441,6 +10441,7 @@ package android.content {
|
||||
field public static final String USAGE_STATS_SERVICE = "usagestats";
|
||||
field public static final String USB_SERVICE = "usb";
|
||||
field public static final String USER_SERVICE = "user";
|
||||
field public static final String VIBRATOR_MANAGER_SERVICE = "vibrator_manager";
|
||||
field public static final String VIBRATOR_SERVICE = "vibrator";
|
||||
field public static final String VPN_MANAGEMENT_SERVICE = "vpn_management";
|
||||
field public static final String WALLPAPER_SERVICE = "wallpaper";
|
||||
@@ -31629,6 +31630,7 @@ package android.os {
|
||||
method @NonNull public int[] areEffectsSupported(@NonNull int...);
|
||||
method @NonNull public boolean[] arePrimitivesSupported(@NonNull int...);
|
||||
method @RequiresPermission(android.Manifest.permission.VIBRATE) public abstract void cancel();
|
||||
method public int getId();
|
||||
method public abstract boolean hasAmplitudeControl();
|
||||
method public abstract boolean hasVibrator();
|
||||
method @Deprecated @RequiresPermission(android.Manifest.permission.VIBRATE) public void vibrate(long);
|
||||
@@ -31643,10 +31645,12 @@ package android.os {
|
||||
}
|
||||
|
||||
public abstract class VibratorManager {
|
||||
method @RequiresPermission(android.Manifest.permission.VIBRATE) public abstract void cancel();
|
||||
method @NonNull public abstract android.os.Vibrator getDefaultVibrator();
|
||||
method @NonNull public abstract android.os.Vibrator getVibrator(int);
|
||||
method @NonNull public abstract int[] getVibratorIds();
|
||||
method public abstract void vibrate(@NonNull android.os.CombinedVibrationEffect);
|
||||
method @RequiresPermission(android.Manifest.permission.VIBRATE) public final void vibrate(@NonNull android.os.CombinedVibrationEffect);
|
||||
method @RequiresPermission(android.Manifest.permission.VIBRATE) public final void vibrate(@NonNull android.os.CombinedVibrationEffect, @Nullable android.os.VibrationAttributes);
|
||||
}
|
||||
|
||||
public class WorkSource implements android.os.Parcelable {
|
||||
|
||||
@@ -1371,6 +1371,32 @@ package android.os {
|
||||
field public static final int RESOURCES_SDK_INT;
|
||||
}
|
||||
|
||||
public abstract class CombinedVibrationEffect implements android.os.Parcelable {
|
||||
method public abstract long getDuration();
|
||||
}
|
||||
|
||||
public static final class CombinedVibrationEffect.Mono extends android.os.CombinedVibrationEffect {
|
||||
method public long getDuration();
|
||||
method @NonNull public android.os.VibrationEffect getEffect();
|
||||
method public void writeToParcel(@NonNull android.os.Parcel, int);
|
||||
field @NonNull public static final android.os.Parcelable.Creator<android.os.CombinedVibrationEffect.Mono> CREATOR;
|
||||
}
|
||||
|
||||
public static final class CombinedVibrationEffect.Sequential extends android.os.CombinedVibrationEffect {
|
||||
method @NonNull public java.util.List<java.lang.Integer> getDelays();
|
||||
method public long getDuration();
|
||||
method @NonNull public java.util.List<android.os.CombinedVibrationEffect> getEffects();
|
||||
method public void writeToParcel(@NonNull android.os.Parcel, int);
|
||||
field @NonNull public static final android.os.Parcelable.Creator<android.os.CombinedVibrationEffect.Sequential> CREATOR;
|
||||
}
|
||||
|
||||
public static final class CombinedVibrationEffect.Stereo extends android.os.CombinedVibrationEffect {
|
||||
method public long getDuration();
|
||||
method @NonNull public android.util.SparseArray<android.os.VibrationEffect> getEffects();
|
||||
method public void writeToParcel(@NonNull android.os.Parcel, int);
|
||||
field @NonNull public static final android.os.Parcelable.Creator<android.os.CombinedVibrationEffect.Stereo> CREATOR;
|
||||
}
|
||||
|
||||
public class DeviceIdleManager {
|
||||
method @NonNull public String[] getSystemPowerWhitelist();
|
||||
method @NonNull public String[] getSystemPowerWhitelistExceptIdle();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1291,7 +1291,6 @@ public final class SystemServer implements Dumpable {
|
||||
t.traceBegin("startOtherServices");
|
||||
|
||||
final Context context = mSystemContext;
|
||||
VibratorService vibrator = null;
|
||||
DynamicSystemService dynamicSystem = null;
|
||||
IStorageManager storageManager = null;
|
||||
NetworkManagementService networkManagement = null;
|
||||
@@ -1417,11 +1416,6 @@ public final class SystemServer implements Dumpable {
|
||||
mSystemServiceManager.startService(VibratorManagerService.Lifecycle.class);
|
||||
t.traceEnd();
|
||||
|
||||
t.traceBegin("StartVibratorService");
|
||||
vibrator = new VibratorService(context);
|
||||
ServiceManager.addService("vibrator", vibrator);
|
||||
t.traceEnd();
|
||||
|
||||
t.traceBegin("StartDynamicSystemService");
|
||||
dynamicSystem = new DynamicSystemService(context);
|
||||
ServiceManager.addService("dynamic_system", dynamicSystem);
|
||||
@@ -2490,14 +2484,6 @@ public final class SystemServer implements Dumpable {
|
||||
|
||||
// It is now time to start up the app processes...
|
||||
|
||||
t.traceBegin("MakeVibratorServiceReady");
|
||||
try {
|
||||
vibrator.systemReady();
|
||||
} catch (Throwable e) {
|
||||
reportWtf("making Vibrator Service ready", e);
|
||||
}
|
||||
t.traceEnd();
|
||||
|
||||
t.traceBegin("MakeLockSettingsServiceReady");
|
||||
if (lockSettings != null) {
|
||||
try {
|
||||
|
||||
@@ -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