diff --git a/api/system-current.txt b/api/system-current.txt index 51cab1344f471..aacd453a272c1 100755 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -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); diff --git a/api/test-current.txt b/api/test-current.txt index e352cb65156ea..6a61398bd1f33 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -2605,6 +2605,17 @@ package android.os { field @NonNull public static final android.os.Parcelable.Creator 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(); diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java index 83f01a5dca35a..a7b65165b5eee 100644 --- a/core/java/android/hardware/input/InputManager.java +++ b/core/java/android/hardware/input/InputManager.java @@ -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; diff --git a/core/java/android/os/IVibratorService.aidl b/core/java/android/os/IVibratorService.aidl index e201e43b5586d..84013e7ebc886 100644 --- a/core/java/android/os/IVibratorService.aidl +++ b/core/java/android/os/IVibratorService.aidl @@ -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); diff --git a/core/java/android/os/IVibratorStateListener.aidl b/core/java/android/os/IVibratorStateListener.aidl new file mode 100644 index 0000000000000..5ff18a351865b --- /dev/null +++ b/core/java/android/os/IVibratorStateListener.aidl @@ -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); +} \ No newline at end of file diff --git a/core/java/android/os/NullVibrator.java b/core/java/android/os/NullVibrator.java index 1d0f9d3157c0a..6d8ab6d70cdf3 100644 --- a/core/java/android/os/NullVibrator.java +++ b/core/java/android/os/NullVibrator.java @@ -38,6 +38,11 @@ public class NullVibrator extends Vibrator { return false; } + @Override + public boolean isVibrating() { + return false; + } + @Override public boolean hasAmplitudeControl() { return false; diff --git a/core/java/android/os/SystemVibrator.java b/core/java/android/os/SystemVibrator.java index faf4a36ff5774..da20c7f2ae70e 100644 --- a/core/java/android/os/SystemVibrator.java +++ b/core/java/android/os/SystemVibrator.java @@ -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 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) { diff --git a/core/java/android/os/Vibrator.java b/core/java/android/os/Vibrator.java index f055c60e6a417..d4da7a84d2a1f 100644 --- a/core/java/android/os/Vibrator.java +++ b/core/java/android/os/Vibrator.java @@ -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) { + } } diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 71a42e454f303..e961b74fd0e9a 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -1912,6 +1912,15 @@ + + + Allows the app to control the vibrator. + + Allows the app to access the vibrator state. directly call phone numbers diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index f83fb3f312e92..403e0d1c85ed7 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -378,6 +378,8 @@ applications that come with the platform + + diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index 5946f2158ac8b..772f6e43c0ab3 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -139,6 +139,7 @@ + diff --git a/services/core/java/com/android/server/VibratorService.java b/services/core/java/com/android/server/VibratorService.java index f7d7d6c5d15fd..cdde67d8fc3a1 100644 --- a/services/core/java/com/android/server/VibratorService.java +++ b/services/core/java/com/android/server/VibratorService.java @@ -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 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);