diff options
6 files changed, 201 insertions, 2 deletions
diff --git a/core/api/current.txt b/core/api/current.txt index 2a958933713c..8ecf38abf85d 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -52687,12 +52687,14 @@ package android.view.accessibility { method public void addAudioDescriptionRequestedChangeListener(@NonNull java.util.concurrent.Executor, @NonNull android.view.accessibility.AccessibilityManager.AudioDescriptionRequestedChangeListener); method public boolean addTouchExplorationStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener); method public void addTouchExplorationStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener, @Nullable android.os.Handler); + method public void addUiContrastChangeListener(@NonNull java.util.concurrent.Executor, @NonNull android.view.accessibility.AccessibilityManager.UiContrastChangeListener); method @ColorInt public int getAccessibilityFocusColor(); method public int getAccessibilityFocusStrokeWidth(); method @Deprecated public java.util.List<android.content.pm.ServiceInfo> getAccessibilityServiceList(); method public java.util.List<android.accessibilityservice.AccessibilityServiceInfo> getEnabledAccessibilityServiceList(int); method public java.util.List<android.accessibilityservice.AccessibilityServiceInfo> getInstalledAccessibilityServiceList(); method public int getRecommendedTimeoutMillis(int, int); + method @FloatRange(from=-1.0F, to=1.0f) public float getUiContrast(); method public void interrupt(); method public static boolean isAccessibilityButtonSupported(); method public boolean isAudioDescriptionRequested(); @@ -52704,6 +52706,7 @@ package android.view.accessibility { method public boolean removeAccessibilityStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener); method public boolean removeAudioDescriptionRequestedChangeListener(@NonNull android.view.accessibility.AccessibilityManager.AudioDescriptionRequestedChangeListener); method public boolean removeTouchExplorationStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener); + method public void removeUiContrastChangeListener(@NonNull android.view.accessibility.AccessibilityManager.UiContrastChangeListener); method public void sendAccessibilityEvent(android.view.accessibility.AccessibilityEvent); field public static final int FLAG_CONTENT_CONTROLS = 4; // 0x4 field public static final int FLAG_CONTENT_ICONS = 1; // 0x1 @@ -52726,6 +52729,10 @@ package android.view.accessibility { method public void onTouchExplorationStateChanged(boolean); } + public static interface AccessibilityManager.UiContrastChangeListener { + method public void onUiContrastChanged(@FloatRange(from=-1.0F, to=1.0f) float); + } + public class AccessibilityNodeInfo implements android.os.Parcelable { ctor public AccessibilityNodeInfo(); ctor public AccessibilityNodeInfo(@NonNull android.view.View); diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java index 423c560d5c57..9abbba923a66 100644 --- a/core/java/android/view/accessibility/AccessibilityManager.java +++ b/core/java/android/view/accessibility/AccessibilityManager.java @@ -25,6 +25,7 @@ import android.accessibilityservice.AccessibilityServiceInfo.FeedbackType; import android.accessibilityservice.AccessibilityShortcutInfo; import android.annotation.CallbackExecutor; import android.annotation.ColorInt; +import android.annotation.FloatRange; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -75,6 +76,7 @@ import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Objects; import java.util.concurrent.Executor; /** @@ -138,6 +140,21 @@ public final class AccessibilityManager { public static final int AUTOCLICK_DELAY_DEFAULT = 600; /** + * The contrast is defined as a float in [-1, 1], with a default value of 0. + * @hide + */ + public static final float CONTRAST_MIN_VALUE = -1f; + + /** @hide */ + public static final float CONTRAST_MAX_VALUE = 1f; + + /** @hide */ + public static final float CONTRAST_DEFAULT_VALUE = 0f; + + /** @hide */ + public static final float CONTRAST_NOT_SET = Float.MIN_VALUE; + + /** * Activity action: Launch UI to manage which accessibility service or feature is assigned * to the navigation bar Accessibility button. * <p> @@ -246,6 +263,8 @@ public final class AccessibilityManager { @UnsupportedAppUsage(trackingBug = 123768939L) boolean mIsHighTextContrastEnabled; + private float mUiContrast; + boolean mIsAudioDescriptionByDefaultRequested; // accessibility tracing state @@ -270,6 +289,9 @@ public final class AccessibilityManager { private final ArrayMap<HighTextContrastChangeListener, Handler> mHighTextContrastStateChangeListeners = new ArrayMap<>(); + private final ArrayMap<UiContrastChangeListener, Executor> + mUiContrastChangeListeners = new ArrayMap<>(); + private final ArrayMap<AccessibilityServicesStateChangeListener, Executor> mServicesStateChangeListeners = new ArrayMap<>(); @@ -336,7 +358,7 @@ public final class AccessibilityManager { * * @param manager The manager that is calling back */ - void onAccessibilityServicesStateChanged(@NonNull AccessibilityManager manager); + void onAccessibilityServicesStateChanged(@NonNull AccessibilityManager manager); } /** @@ -358,6 +380,21 @@ public final class AccessibilityManager { } /** + * Listener for the UI contrast. To listen for changes to + * the UI contrast on the device, implement this interface and + * register it with the system by calling {@link #addUiContrastChangeListener}. + */ + public interface UiContrastChangeListener { + + /** + * Called when the color contrast enabled state changes. + * + * @param uiContrast The color contrast as in {@link #getUiContrast} + */ + void onUiContrastChanged(@FloatRange(from = -1.0f, to = 1.0f) float uiContrast); + } + + /** * Listener for the audio description by default state. To listen for * changes to the audio description by default state on the device, * implement this interface and register it with the system by calling @@ -471,6 +508,16 @@ public final class AccessibilityManager { updateFocusAppearanceLocked(strokeWidth, color); } } + + @Override + public void setUiContrast(float contrast) { + synchronized (mLock) { + // if value changed in the settings, update the cached value and notify listeners + if (Math.abs(mUiContrast - contrast) < 1e-10) return; + mUiContrast = contrast; + } + mHandler.obtainMessage(MyCallback.MSG_NOTIFY_CONTRAST_CHANGED).sendToTarget(); + } }; /** @@ -641,7 +688,7 @@ public final class AccessibilityManager { /** * Returns if the high text contrast in the system is enabled. * <p> - * <strong>Note:</strong> You need to query this only if you application is + * <strong>Note:</strong> You need to query this only if your application is * doing its own rendering and does not rely on the platform rendering pipeline. * </p> * @@ -661,6 +708,24 @@ public final class AccessibilityManager { } /** + * Returns the color contrast for the user. + * <p> + * <strong>Note:</strong> You need to query this only if your application is + * doing its own rendering and does not rely on the platform rendering pipeline. + * </p> + * @return The color contrast, float in [-1, 1] where + * 0 corresponds to the default contrast + * -1 corresponds to the minimum contrast that the user can set + * 1 corresponds to the maximum contrast that the user can set + */ + @FloatRange(from = -1.0f, to = 1.0f) + public float getUiContrast() { + synchronized (mLock) { + return mUiContrast; + } + } + + /** * Sends an {@link AccessibilityEvent}. * * @param event The event to send. @@ -1240,6 +1305,35 @@ public final class AccessibilityManager { } /** + * Registers a {@link UiContrastChangeListener} for the current user. + * + * @param executor The executor on which the listener should be called back. + * @param listener The listener. + */ + public void addUiContrastChangeListener( + @NonNull @CallbackExecutor Executor executor, + @NonNull UiContrastChangeListener listener) { + Objects.requireNonNull(executor); + Objects.requireNonNull(listener); + synchronized (mLock) { + mUiContrastChangeListeners.put(listener, executor); + } + } + + /** + * Unregisters a {@link UiContrastChangeListener} for the current user. + * If the listener was not registered, does nothing and returns. + * + * @param listener The listener to unregister. + */ + public void removeUiContrastChangeListener(@NonNull UiContrastChangeListener listener) { + Objects.requireNonNull(listener); + synchronized (mLock) { + mUiContrastChangeListeners.remove(listener); + } + } + + /** * Registers a {@link AudioDescriptionRequestedChangeListener} * for changes in the audio description by default state of the system. * The value could be read via {@link #isAudioDescriptionRequested}. @@ -2004,6 +2098,7 @@ public final class AccessibilityManager { mRelevantEventTypes = IntPair.second(userStateAndRelevantEvents); updateUiTimeout(service.getRecommendedTimeoutMillis()); updateFocusAppearanceLocked(service.getFocusStrokeWidth(), service.getFocusColor()); + mUiContrast = service.getUiContrast(); mService = service; } catch (RemoteException re) { Log.e(LOG_TAG, "AccessibilityManagerService is dead", re); @@ -2082,6 +2177,22 @@ public final class AccessibilityManager { } /** + * Notifies the registered {@link UiContrastChangeListener}s if the value changed. + */ + private void notifyUiContrastChanged() { + final ArrayMap<UiContrastChangeListener, Executor> listeners; + synchronized (mLock) { + listeners = new ArrayMap<>(mUiContrastChangeListeners); + } + + listeners.entrySet().forEach(entry -> { + UiContrastChangeListener listener = entry.getKey(); + Executor executor = entry.getValue(); + executor.execute(() -> listener.onUiContrastChanged(mUiContrast)); + }); + } + + /** * Notifies the registered {@link AudioDescriptionStateChangeListener}s. */ private void notifyAudioDescriptionbyDefaultStateChanged() { @@ -2171,6 +2282,7 @@ public final class AccessibilityManager { private final class MyCallback implements Handler.Callback { public static final int MSG_SET_STATE = 1; + public static final int MSG_NOTIFY_CONTRAST_CHANGED = 2; @Override public boolean handleMessage(Message message) { @@ -2182,6 +2294,9 @@ public final class AccessibilityManager { setStateLocked(state); } } break; + case MSG_NOTIFY_CONTRAST_CHANGED: { + notifyUiContrastChanged(); + } } return true; } diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl index 364c7c8e1fb9..c2d899a50d4e 100644 --- a/core/java/android/view/accessibility/IAccessibilityManager.aidl +++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl @@ -118,4 +118,6 @@ interface IAccessibilityManager { // Used by UiAutomation for tests on the InputFilter void injectInputEventToInputFilter(in InputEvent event); + + float getUiContrast(); } diff --git a/core/java/android/view/accessibility/IAccessibilityManagerClient.aidl b/core/java/android/view/accessibility/IAccessibilityManagerClient.aidl index 041399ccb8ec..931f862e581b 100644 --- a/core/java/android/view/accessibility/IAccessibilityManagerClient.aidl +++ b/core/java/android/view/accessibility/IAccessibilityManagerClient.aidl @@ -31,4 +31,6 @@ oneway interface IAccessibilityManagerClient { void setRelevantEventTypes(int eventTypes); void setFocusAppearance(int strokeWidth, int color); + + void setUiContrast(float contrast); } diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index 3145139cc6bb..5c2f6fc1d6cf 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -26,8 +26,11 @@ import static android.accessibilityservice.AccessibilityTrace.FLAGS_USER_BROADCA import static android.accessibilityservice.AccessibilityTrace.FLAGS_WINDOW_MAGNIFICATION_CONNECTION; import static android.accessibilityservice.AccessibilityTrace.FLAGS_WINDOW_MANAGER_INTERNAL; import static android.provider.Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED; +import static android.provider.Settings.Secure.CONTRAST_LEVEL; import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_BUTTON; import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY; +import static android.view.accessibility.AccessibilityManager.CONTRAST_DEFAULT_VALUE; +import static android.view.accessibility.AccessibilityManager.CONTRAST_NOT_SET; import static android.view.accessibility.AccessibilityManager.ShortcutType; import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_COMPONENT_NAME; @@ -1899,6 +1902,16 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub return false; } + private boolean readUiContrastLocked(AccessibilityUserState userState) { + float contrast = Settings.Secure.getFloatForUser(mContext.getContentResolver(), + CONTRAST_LEVEL, CONTRAST_DEFAULT_VALUE, userState.mUserId); + if (Math.abs(userState.getUiContrastLocked() - contrast) >= 1e-10) { + userState.setUiContrastLocked(contrast); + return true; + } + return false; + } + /** * Performs {@link AccessibilityService}s delayed notification. The delay is configurable * and denotes the period after the last event before notifying the service. @@ -2565,6 +2578,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub somethingChanged |= readMagnificationModeForDefaultDisplayLocked(userState); somethingChanged |= readMagnificationCapabilitiesLocked(userState); somethingChanged |= readMagnificationFollowTypingLocked(userState); + somethingChanged |= readUiContrastLocked(userState); return somethingChanged; } @@ -3706,6 +3720,19 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub return mProxyManager.unregisterProxy(displayId); } + @Override public float getUiContrast() { + if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) { + mTraceManager.logTrace(LOG_TAG + ".getUiContrast", FLAGS_ACCESSIBILITY_MANAGER); + } + synchronized (mLock) { + AccessibilityUserState userState = getCurrentUserStateLocked(); + float contrast = userState.getUiContrastLocked(); + if (contrast != CONTRAST_NOT_SET) return contrast; + readUiContrastLocked(userState); + return userState.getUiContrastLocked(); + } + } + @Override public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) { if (!DumpUtils.checkDumpPermission(mContext, LOG_TAG, pw)) return; @@ -4153,6 +4180,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub private final Uri mMagnificationFollowTypingUri = Settings.Secure.getUriFor( Settings.Secure.ACCESSIBILITY_MAGNIFICATION_FOLLOW_TYPING_ENABLED); + private final Uri mUiContrastUri = Settings.Secure.getUriFor( + CONTRAST_LEVEL); + public AccessibilityContentObserver(Handler handler) { super(handler); } @@ -4193,6 +4223,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub mMagnificationCapabilityUri, false, this, UserHandle.USER_ALL); contentResolver.registerContentObserver( mMagnificationFollowTypingUri, false, this, UserHandle.USER_ALL); + contentResolver.registerContentObserver( + mUiContrastUri, false, this, UserHandle.USER_ALL); } @Override @@ -4262,6 +4294,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } } else if (mMagnificationFollowTypingUri.equals(uri)) { readMagnificationFollowTypingLocked(userState); + } else if (mUiContrastUri.equals(uri)) { + if (readUiContrastLocked(userState)) { + updateUiContrastLocked(userState); + } } } } @@ -4551,7 +4587,22 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub userState.getFocusColorLocked()); })); }); + } + private void updateUiContrastLocked(AccessibilityUserState userState) { + if (userState.mUserId != mCurrentUserId) { + return; + } + if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_SERVICE_CLIENT)) { + mTraceManager.logTrace(LOG_TAG + ".updateUiContrastLocked", + FLAGS_ACCESSIBILITY_SERVICE_CLIENT, "userState=" + userState); + } + float contrast = userState.getUiContrastLocked(); + mMainHandler.post(() -> { + broadcastToClients(userState, ignoreRemoteException(client -> { + client.mCallback.setUiContrast(contrast); + })); + }); } public AccessibilityTraceManager getTraceManager() { diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java index 0db169fd76c3..43730fce0cb7 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java @@ -26,6 +26,8 @@ import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_NONE; import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_BUTTON; import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY; +import static android.view.accessibility.AccessibilityManager.CONTRAST_DEFAULT_VALUE; +import static android.view.accessibility.AccessibilityManager.CONTRAST_NOT_SET; import static android.view.accessibility.AccessibilityManager.ShortcutType; import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME; @@ -143,6 +145,8 @@ class AccessibilityUserState { private final int mFocusStrokeWidthDefaultValue; // The default value of the focus color. private final int mFocusColorDefaultValue; + /** The color contrast in [-1, 1] */ + private float mUiContrast = CONTRAST_DEFAULT_VALUE; private Context mContext; @@ -217,6 +221,7 @@ class AccessibilityUserState { mFocusStrokeWidth = mFocusStrokeWidthDefaultValue; mFocusColor = mFocusColorDefaultValue; mMagnificationFollowTypingEnabled = true; + mUiContrast = CONTRAST_NOT_SET; } void addServiceLocked(AccessibilityServiceConnection serviceConnection) { @@ -983,6 +988,7 @@ class AccessibilityUserState { return mFocusColor; } + /** * Sets the stroke width and color of the focus rectangle. * @@ -1008,4 +1014,20 @@ class AccessibilityUserState { } return false; } + + /** + * Get the color contrast + * @return color contrast in [-1, 1] + */ + public float getUiContrastLocked() { + return mUiContrast; + } + + /** + * Set the color contrast + * @param contrast the new color contrast in [-1, 1] + */ + public void setUiContrastLocked(float contrast) { + mUiContrast = contrast; + } } |