Access to Device Vibrator State

Add API to access vibrator state and vibrator state listener.

Bug: 135949568
Change-Id: I96636b432d581cea03a9fc9fecba4c08045f5006
This commit is contained in:
Chris Ye
2020-01-28 22:54:23 -08:00
parent 245a76e405
commit 473c84ff4c
13 changed files with 399 additions and 2 deletions

View File

@@ -18,6 +18,7 @@ package android {
field public static final String ACCESS_SHORTCUTS = "android.permission.ACCESS_SHORTCUTS";
field public static final String ACCESS_SURFACE_FLINGER = "android.permission.ACCESS_SURFACE_FLINGER";
field public static final String ACCESS_TV_TUNER = "android.permission.ACCESS_TV_TUNER";
field public static final String ACCESS_VIBRATOR_STATE = "android.permission.ACCESS_VIBRATOR_STATE";
field public static final String ACTIVITY_EMBEDDING = "android.permission.ACTIVITY_EMBEDDING";
field public static final String ADJUST_RUNTIME_PERMISSIONS_POLICY = "android.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY";
field public static final String ALLOCATE_AGGRESSIVE = "android.permission.ALLOCATE_AGGRESSIVE";
@@ -9092,6 +9093,17 @@ package android.os {
@IntDef(flag=true, prefix={"RESTRICTION_"}, value={android.os.UserManager.RESTRICTION_NOT_SET, android.os.UserManager.RESTRICTION_SOURCE_SYSTEM, android.os.UserManager.RESTRICTION_SOURCE_DEVICE_OWNER, android.os.UserManager.RESTRICTION_SOURCE_PROFILE_OWNER}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface UserManager.UserRestrictionSource {
}
public abstract class Vibrator {
method @RequiresPermission(android.Manifest.permission.ACCESS_VIBRATOR_STATE) public void addVibratorStateListener(@NonNull android.os.Vibrator.OnVibratorStateChangedListener);
method @RequiresPermission(android.Manifest.permission.ACCESS_VIBRATOR_STATE) public void addVibratorStateListener(@NonNull java.util.concurrent.Executor, @NonNull android.os.Vibrator.OnVibratorStateChangedListener);
method @RequiresPermission(android.Manifest.permission.ACCESS_VIBRATOR_STATE) public boolean isVibrating();
method @RequiresPermission(android.Manifest.permission.ACCESS_VIBRATOR_STATE) public void removeVibratorStateListener(@NonNull android.os.Vibrator.OnVibratorStateChangedListener);
}
public static interface Vibrator.OnVibratorStateChangedListener {
method public void onVibratorStateChanged(boolean);
}
public class WorkSource implements android.os.Parcelable {
ctor public WorkSource(int);
ctor public WorkSource(int, @NonNull String);

View File

@@ -2605,6 +2605,17 @@ package android.os {
field @NonNull public static final android.os.Parcelable.Creator<android.os.VibrationEffect.Waveform> CREATOR;
}
public abstract class Vibrator {
method @RequiresPermission("android.permission.ACCESS_VIBRATOR_STATE") public void addVibratorStateListener(@NonNull android.os.Vibrator.OnVibratorStateChangedListener);
method @RequiresPermission("android.permission.ACCESS_VIBRATOR_STATE") public void addVibratorStateListener(@NonNull java.util.concurrent.Executor, @NonNull android.os.Vibrator.OnVibratorStateChangedListener);
method @RequiresPermission("android.permission.ACCESS_VIBRATOR_STATE") public boolean isVibrating();
method @RequiresPermission("android.permission.ACCESS_VIBRATOR_STATE") public void removeVibratorStateListener(@NonNull android.os.Vibrator.OnVibratorStateChangedListener);
}
public static interface Vibrator.OnVibratorStateChangedListener {
method public void onVibratorStateChanged(boolean);
}
public class VintfObject {
method public static String[] getHalNamesAndVersions();
method public static String getSepolicyVersion();

View File

@@ -16,6 +16,7 @@
package android.hardware.input;
import android.annotation.CallbackExecutor;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.SdkConstant;
@@ -51,6 +52,7 @@ import com.android.internal.os.SomeArgs;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.concurrent.Executor;
import java.util.List;
/**
@@ -1235,6 +1237,32 @@ public final class InputManager {
return true;
}
@Override
public boolean isVibrating() {
throw new UnsupportedOperationException(
"isVibrating not supported in InputDeviceVibrator");
}
@Override
public void addVibratorStateListener(@NonNull OnVibratorStateChangedListener listener) {
throw new UnsupportedOperationException(
"addVibratorStateListener not supported in InputDeviceVibrator");
}
@Override
public void addVibratorStateListener(
@NonNull @CallbackExecutor Executor executor,
@NonNull OnVibratorStateChangedListener listener) {
throw new UnsupportedOperationException(
"addVibratorStateListener not supported in InputDeviceVibrator");
}
@Override
public void removeVibratorStateListener(@NonNull OnVibratorStateChangedListener listener) {
throw new UnsupportedOperationException(
"removeVibratorStateListener not supported in InputDeviceVibrator");
}
@Override
public boolean hasAmplitudeControl() {
return false;

View File

@@ -18,11 +18,15 @@ package android.os;
import android.os.VibrationEffect;
import android.os.VibrationAttributes;
import android.os.IVibratorStateListener;
/** {@hide} */
interface IVibratorService
{
boolean hasVibrator();
boolean isVibrating();
boolean registerVibratorStateListener(in IVibratorStateListener listener);
boolean unregisterVibratorStateListener(in IVibratorStateListener listener);
boolean hasAmplitudeControl();
boolean[] areEffectsSupported(in int[] effectIds);
boolean[] arePrimitivesSupported(in int[] primitiveIds);

View File

@@ -0,0 +1,29 @@
/*
** Copyright 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 android.os;
/**
* Listener for vibrator state.
* {@hide}
*/
oneway interface IVibratorStateListener {
/**
* Called when a vibrator start/stop vibrating.
* @param state the vibrator state.
*/
void onVibrating(in boolean vibrating);
}

View File

@@ -38,6 +38,11 @@ public class NullVibrator extends Vibrator {
return false;
}
@Override
public boolean isVibrating() {
return false;
}
@Override
public boolean hasAmplitudeControl() {
return false;

View File

@@ -16,11 +16,17 @@
package android.os;
import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.media.AudioAttributes;
import android.os.IVibratorStateListener;
import android.util.ArrayMap;
import android.util.Log;
import com.android.internal.annotations.GuardedBy;
import java.util.concurrent.Executor;
import java.util.Objects;
/**
* Vibrator implementation that controls the main system vibrator.
@@ -32,15 +38,22 @@ public class SystemVibrator extends Vibrator {
private final IVibratorService mService;
private final Binder mToken = new Binder();
private final Context mContext;
@GuardedBy("mDelegates")
private final ArrayMap<OnVibratorStateChangedListener,
OnVibratorStateChangedListenerDelegate> mDelegates = new ArrayMap<>();
@UnsupportedAppUsage
public SystemVibrator() {
mContext = null;
mService = IVibratorService.Stub.asInterface(ServiceManager.getService("vibrator"));
}
@UnsupportedAppUsage
public SystemVibrator(Context context) {
super(context);
mContext = context;
mService = IVibratorService.Stub.asInterface(ServiceManager.getService("vibrator"));
}
@@ -57,6 +70,126 @@ public class SystemVibrator extends Vibrator {
return false;
}
/**
* 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.");
return false;
}
try {
return mService.isVibrating();
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
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);
if (mContext == null) {
Log.w(TAG, "Failed to add vibrate state listener; no vibrator context.");
return;
}
addVibratorStateListener(mContext.getMainExecutor(), listener);
}
/**
* Removes the listener for vibrator state changes. If the listener was not previously
* registered, this call will do nothing.
*
* @param listener Listener to be removed.
*/
@Override
public void removeVibratorStateListener(@NonNull OnVibratorStateChangedListener listener) {
Objects.requireNonNull(listener);
if (mService == null) {
Log.w(TAG, "Failed to remove vibrate state listener; no vibrator service.");
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();
}
}
}
}
@Override
public boolean hasAmplitudeControl() {
if (mService == null) {

View File

@@ -16,11 +16,14 @@
package android.os;
import android.annotation.CallbackExecutor;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.annotation.TestApi;
import android.app.ActivityThread;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
@@ -29,6 +32,7 @@ import android.util.Log;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.concurrent.Executor;
/**
* Class that operates the vibrator on the device.
@@ -395,4 +399,78 @@ public abstract class Vibrator {
*/
@RequiresPermission(android.Manifest.permission.VIBRATE)
public abstract void cancel();
/**
* Check whether the vibrator is vibrating.
*
* @return True if the hardware is vibrating, otherwise false.
* @hide
*/
@SystemApi
@TestApi
@RequiresPermission(android.Manifest.permission.ACCESS_VIBRATOR_STATE)
public boolean isVibrating() {
return false;
}
/**
* Listener for when the vibrator state has changed.
*
* @see #addVibratorStateListener
* @see #removeVibratorStateListener
* @hide
*/
@SystemApi
@TestApi
public interface OnVibratorStateChangedListener {
/**
* Called when the vibrator state has changed.
*
* @param isVibrating If true, the vibrator has started vibrating. If false,
* it's stopped vibrating.
*/
void onVibratorStateChanged(boolean isVibrating);
}
/**
* 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
* @hide
*/
@SystemApi
@TestApi
@RequiresPermission(android.Manifest.permission.ACCESS_VIBRATOR_STATE)
public void addVibratorStateListener(@NonNull OnVibratorStateChangedListener listener) {
}
/**
* Adds a listener for vibrator state change. If the listener was previously added and not
* removed, this call will be ignored.
*
* @param listener listener to be added
* @param executor executor of listener
* @hide
*/
@SystemApi
@TestApi
@RequiresPermission(android.Manifest.permission.ACCESS_VIBRATOR_STATE)
public void addVibratorStateListener(
@NonNull @CallbackExecutor Executor executor,
@NonNull OnVibratorStateChangedListener 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
* @hide
*/
@SystemApi
@TestApi
@RequiresPermission(android.Manifest.permission.ACCESS_VIBRATOR_STATE)
public void removeVibratorStateListener(@NonNull OnVibratorStateChangedListener listener) {
}
}

View File

@@ -1912,6 +1912,15 @@
<permission android:name="android.permission.VIBRATE_ALWAYS_ON"
android:protectionLevel="signature" />
<!-- @SystemApi Allows access to the vibrator state.
<p>Protection level: signature
@hide
-->
<permission android:name="android.permission.ACCESS_VIBRATOR_STATE"
android:label="@string/permdesc_vibrator_state"
android:description="@string/permdesc_vibrator_state"
android:protectionLevel="signature|privileged" />
<!-- Allows using PowerManager WakeLocks to keep processor from sleeping or screen
from dimming.
<p>Protection level: normal

View File

@@ -1156,6 +1156,8 @@
<string name="permlab_vibrate">control vibration</string>
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permdesc_vibrate">Allows the app to control the vibrator.</string>
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permdesc_vibrator_state">Allows the app to access the vibrator state.</string>
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permlab_callPhone">directly call phone numbers</string>

View File

@@ -378,6 +378,8 @@ applications that come with the platform
<!-- Permission required for ShortcutManagerUsageTest CTS test. -->
<permission name="android.permission.ACCESS_SHORTCUTS"/>
<permission name="android.permission.REBOOT"/>
<!-- Permission required for access VIBRATOR_STATE. -->
<permission name="android.permission.ACCESS_VIBRATOR_STATE"/>
</privapp-permissions>
<privapp-permissions package="com.android.statementservice">

View File

@@ -139,6 +139,7 @@
<uses-permission android:name="android.permission.GET_APP_OPS_STATS" />
<uses-permission android:name="android.permission.MANAGE_APP_OPS_MODES" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.ACCESS_VIBRATOR_STATE" />
<uses-permission android:name="android.permission.MANAGE_ACTIVITY_STACKS" />
<uses-permission android:name="android.permission.START_TASKS_FROM_RECENTS" />
<uses-permission android:name="android.permission.START_ACTIVITIES_FROM_BACKGROUND" />

View File

@@ -42,11 +42,13 @@ import android.os.Handler;
import android.os.IBinder;
import android.os.IExternalVibratorService;
import android.os.IVibratorService;
import android.os.IVibratorStateListener;
import android.os.PowerManager;
import android.os.PowerManager.ServiceType;
import android.os.PowerManagerInternal;
import android.os.PowerSaveState;
import android.os.Process;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ServiceManager;
@@ -166,7 +168,11 @@ public class VibratorService extends IVibratorService.Stub
private ExternalVibration mCurrentExternalVibration;
private boolean mVibratorUnderExternalControl;
private boolean mLowPowerMode;
@GuardedBy("mLock")
private boolean mIsVibrating;
@GuardedBy("mLock")
private final RemoteCallbackList<IVibratorStateListener> mVibratorStateListeners =
new RemoteCallbackList<>();
private int mHapticFeedbackIntensity;
private int mNotificationIntensity;
private int mRingIntensity;
@@ -521,6 +527,75 @@ public class VibratorService extends IVibratorService.Stub
return doVibratorExists();
}
@Override // Binder call
public boolean isVibrating() {
if (!hasPermission(android.Manifest.permission.ACCESS_VIBRATOR_STATE)) {
throw new SecurityException("Requires ACCESS_VIBRATOR_STATE permission");
}
synchronized (mLock) {
return mIsVibrating;
}
}
@GuardedBy("mLock")
private void notifyStateListenerLocked(IVibratorStateListener listener) {
try {
listener.onVibrating(mIsVibrating);
} catch (RemoteException | RuntimeException e) {
Slog.e(TAG, "Vibrator callback failed to call", e);
}
}
@GuardedBy("mLock")
private void notifyStateListenersLocked() {
final int length = mVibratorStateListeners.beginBroadcast();
try {
for (int i = 0; i < length; i++) {
final IVibratorStateListener listener =
mVibratorStateListeners.getBroadcastItem(i);
notifyStateListenerLocked(listener);
}
} finally {
mVibratorStateListeners.finishBroadcast();
}
}
@Override // Binder call
public boolean registerVibratorStateListener(IVibratorStateListener listener) {
if (!hasPermission(android.Manifest.permission.ACCESS_VIBRATOR_STATE)) {
throw new SecurityException("Requires ACCESS_VIBRATOR_STATE permission");
}
synchronized (mLock) {
final long token = Binder.clearCallingIdentity();
try {
if (!mVibratorStateListeners.register(listener)) {
return false;
}
// Notify its callback after new client registered.
notifyStateListenerLocked(listener);
return true;
} finally {
Binder.restoreCallingIdentity(token);
}
}
}
@Override // Binder call
@GuardedBy("mLock")
public boolean unregisterVibratorStateListener(IVibratorStateListener listener) {
if (!hasPermission(android.Manifest.permission.ACCESS_VIBRATOR_STATE)) {
throw new SecurityException("Requires ACCESS_VIBRATOR_STATE permission");
}
synchronized (mLock) {
final long token = Binder.clearCallingIdentity();
try {
return mVibratorStateListeners.unregister(listener);
} finally {
Binder.restoreCallingIdentity(token);
}
}
}
@Override // Binder call
public boolean hasAmplitudeControl() {
synchronized (mInputDeviceVibrators) {
@@ -1373,7 +1448,10 @@ public class VibratorService extends IVibratorService.Stub
FrameworkStatsLog.write_non_chained(FrameworkStatsLog.VIBRATOR_STATE_CHANGED, uid, null,
FrameworkStatsLog.VIBRATOR_STATE_CHANGED__STATE__ON, millis);
mCurVibUid = uid;
mIsVibrating = true;
if (!mIsVibrating) {
mIsVibrating = true;
notifyStateListenersLocked();
}
} catch (RemoteException e) {
}
}
@@ -1387,7 +1465,10 @@ public class VibratorService extends IVibratorService.Stub
} catch (RemoteException e) { }
mCurVibUid = -1;
}
mIsVibrating = false;
if (mIsVibrating) {
mIsVibrating = false;
notifyStateListenersLocked();
}
}
private void setVibratorUnderExternalControl(boolean externalControl) {
@@ -1414,6 +1495,8 @@ public class VibratorService extends IVibratorService.Stub
pw.print(" mCurrentExternalVibration=" + mCurrentExternalVibration);
pw.println(" mVibratorUnderExternalControl=" + mVibratorUnderExternalControl);
pw.println(" mIsVibrating=" + mIsVibrating);
pw.println(" mVibratorStateListeners Count=" +
mVibratorStateListeners.getRegisteredCallbackCount());
pw.println(" mLowPowerMode=" + mLowPowerMode);
pw.println(" mHapticFeedbackIntensity=" + mHapticFeedbackIntensity);
pw.println(" mNotificationIntensity=" + mNotificationIntensity);