summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--api/current.txt15
-rw-r--r--api/system-current.txt15
-rw-r--r--api/test-current.txt15
-rw-r--r--core/java/android/accessibilityservice/AccessibilityButtonController.java223
-rw-r--r--core/java/android/accessibilityservice/AccessibilityService.java89
-rw-r--r--core/java/android/accessibilityservice/AccessibilityServiceInfo.java16
-rw-r--r--core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl4
-rw-r--r--core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl2
-rw-r--r--core/java/android/app/UiAutomation.java10
-rw-r--r--core/java/android/view/accessibility/AccessibilityManager.java43
-rw-r--r--core/java/android/view/accessibility/IAccessibilityManager.aidl4
-rw-r--r--core/res/res/values/attrs.xml13
-rw-r--r--packages/SystemUI/res/drawable-hdpi/ic_sysbar_accessibility_button.pngbin0 -> 232 bytes
-rw-r--r--packages/SystemUI/res/drawable-hdpi/ic_sysbar_accessibility_button_dark.pngbin0 -> 234 bytes
-rw-r--r--packages/SystemUI/res/drawable-mdpi/ic_sysbar_accessibility_button.pngbin0 -> 166 bytes
-rw-r--r--packages/SystemUI/res/drawable-mdpi/ic_sysbar_accessibility_button_dark.pngbin0 -> 165 bytes
-rw-r--r--packages/SystemUI/res/drawable-xhdpi/ic_sysbar_accessibility_button.pngbin0 -> 282 bytes
-rw-r--r--packages/SystemUI/res/drawable-xhdpi/ic_sysbar_accessibility_button_dark.pngbin0 -> 283 bytes
-rw-r--r--packages/SystemUI/res/drawable-xxhdpi/ic_sysbar_accessibility_button.pngbin0 -> 383 bytes
-rw-r--r--packages/SystemUI/res/drawable-xxhdpi/ic_sysbar_accessibility_button_dark.pngbin0 -> 384 bytes
-rw-r--r--packages/SystemUI/res/drawable-xxxhdpi/ic_sysbar_accessibility_button.pngbin0 -> 498 bytes
-rw-r--r--packages/SystemUI/res/drawable-xxxhdpi/ic_sysbar_accessibility_button_dark.pngbin0 -> 504 bytes
-rw-r--r--packages/SystemUI/res/layout/menu_ime.xml9
-rw-r--r--packages/SystemUI/res/values/strings.xml2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java40
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java34
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java179
-rw-r--r--services/core/java/com/android/server/policy/BarController.java34
-rw-r--r--services/core/java/com/android/server/policy/PhoneWindowManager.java10
29 files changed, 710 insertions, 47 deletions
diff --git a/api/current.txt b/api/current.txt
index e315958badeb..18ece9148fc7 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -2688,11 +2688,25 @@ package android {
package android.accessibilityservice {
+ public final class AccessibilityButtonController {
+ method public boolean isAccessibilityButtonAvailable();
+ method public void registerAccessibilityButtonCallback(android.accessibilityservice.AccessibilityButtonController.AccessibilityButtonCallback);
+ method public void registerAccessibilityButtonCallback(android.accessibilityservice.AccessibilityButtonController.AccessibilityButtonCallback, android.os.Handler);
+ method public void unregisterAccessibilityButtonCallback(android.accessibilityservice.AccessibilityButtonController.AccessibilityButtonCallback);
+ }
+
+ public static abstract class AccessibilityButtonController.AccessibilityButtonCallback {
+ ctor public AccessibilityButtonController.AccessibilityButtonCallback();
+ method public void onAvailabilityChanged(android.accessibilityservice.AccessibilityButtonController, boolean);
+ method public void onClicked(android.accessibilityservice.AccessibilityButtonController);
+ }
+
public abstract class AccessibilityService extends android.app.Service {
ctor public AccessibilityService();
method public final void disableSelf();
method public final boolean dispatchGesture(android.accessibilityservice.GestureDescription, android.accessibilityservice.AccessibilityService.GestureResultCallback, android.os.Handler);
method public android.view.accessibility.AccessibilityNodeInfo findFocus(int);
+ method public final android.accessibilityservice.AccessibilityButtonController getAccessibilityButtonController();
method public final android.accessibilityservice.FingerprintGestureController getFingerprintGestureController();
method public final android.accessibilityservice.AccessibilityService.MagnificationController getMagnificationController();
method public android.view.accessibility.AccessibilityNodeInfo getRootInActiveWindow();
@@ -2805,6 +2819,7 @@ package android.accessibilityservice {
field public static final int FLAG_ENABLE_ACCESSIBILITY_VOLUME = 128; // 0x80
field public static final int FLAG_INCLUDE_NOT_IMPORTANT_VIEWS = 2; // 0x2
field public static final int FLAG_REPORT_VIEW_IDS = 16; // 0x10
+ field public static final int FLAG_REQUEST_ACCESSIBILITY_BUTTON = 256; // 0x100
field public static final int FLAG_REQUEST_ENHANCED_WEB_ACCESSIBILITY = 8; // 0x8
field public static final int FLAG_REQUEST_FILTER_KEY_EVENTS = 32; // 0x20
field public static final int FLAG_REQUEST_TOUCH_EXPLORATION_MODE = 4; // 0x4
diff --git a/api/system-current.txt b/api/system-current.txt
index ae145dc9b037..2b9f931374b0 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -2807,11 +2807,25 @@ package android {
package android.accessibilityservice {
+ public final class AccessibilityButtonController {
+ method public boolean isAccessibilityButtonAvailable();
+ method public void registerAccessibilityButtonCallback(android.accessibilityservice.AccessibilityButtonController.AccessibilityButtonCallback);
+ method public void registerAccessibilityButtonCallback(android.accessibilityservice.AccessibilityButtonController.AccessibilityButtonCallback, android.os.Handler);
+ method public void unregisterAccessibilityButtonCallback(android.accessibilityservice.AccessibilityButtonController.AccessibilityButtonCallback);
+ }
+
+ public static abstract class AccessibilityButtonController.AccessibilityButtonCallback {
+ ctor public AccessibilityButtonController.AccessibilityButtonCallback();
+ method public void onAvailabilityChanged(android.accessibilityservice.AccessibilityButtonController, boolean);
+ method public void onClicked(android.accessibilityservice.AccessibilityButtonController);
+ }
+
public abstract class AccessibilityService extends android.app.Service {
ctor public AccessibilityService();
method public final void disableSelf();
method public final boolean dispatchGesture(android.accessibilityservice.GestureDescription, android.accessibilityservice.AccessibilityService.GestureResultCallback, android.os.Handler);
method public android.view.accessibility.AccessibilityNodeInfo findFocus(int);
+ method public final android.accessibilityservice.AccessibilityButtonController getAccessibilityButtonController();
method public final android.accessibilityservice.FingerprintGestureController getFingerprintGestureController();
method public final android.accessibilityservice.AccessibilityService.MagnificationController getMagnificationController();
method public android.view.accessibility.AccessibilityNodeInfo getRootInActiveWindow();
@@ -2924,6 +2938,7 @@ package android.accessibilityservice {
field public static final int FLAG_ENABLE_ACCESSIBILITY_VOLUME = 128; // 0x80
field public static final int FLAG_INCLUDE_NOT_IMPORTANT_VIEWS = 2; // 0x2
field public static final int FLAG_REPORT_VIEW_IDS = 16; // 0x10
+ field public static final int FLAG_REQUEST_ACCESSIBILITY_BUTTON = 256; // 0x100
field public static final int FLAG_REQUEST_ENHANCED_WEB_ACCESSIBILITY = 8; // 0x8
field public static final int FLAG_REQUEST_FILTER_KEY_EVENTS = 32; // 0x20
field public static final int FLAG_REQUEST_TOUCH_EXPLORATION_MODE = 4; // 0x4
diff --git a/api/test-current.txt b/api/test-current.txt
index 1e3b6db92185..f19d959d777d 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -2688,11 +2688,25 @@ package android {
package android.accessibilityservice {
+ public final class AccessibilityButtonController {
+ method public boolean isAccessibilityButtonAvailable();
+ method public void registerAccessibilityButtonCallback(android.accessibilityservice.AccessibilityButtonController.AccessibilityButtonCallback);
+ method public void registerAccessibilityButtonCallback(android.accessibilityservice.AccessibilityButtonController.AccessibilityButtonCallback, android.os.Handler);
+ method public void unregisterAccessibilityButtonCallback(android.accessibilityservice.AccessibilityButtonController.AccessibilityButtonCallback);
+ }
+
+ public static abstract class AccessibilityButtonController.AccessibilityButtonCallback {
+ ctor public AccessibilityButtonController.AccessibilityButtonCallback();
+ method public void onAvailabilityChanged(android.accessibilityservice.AccessibilityButtonController, boolean);
+ method public void onClicked(android.accessibilityservice.AccessibilityButtonController);
+ }
+
public abstract class AccessibilityService extends android.app.Service {
ctor public AccessibilityService();
method public final void disableSelf();
method public final boolean dispatchGesture(android.accessibilityservice.GestureDescription, android.accessibilityservice.AccessibilityService.GestureResultCallback, android.os.Handler);
method public android.view.accessibility.AccessibilityNodeInfo findFocus(int);
+ method public final android.accessibilityservice.AccessibilityButtonController getAccessibilityButtonController();
method public final android.accessibilityservice.FingerprintGestureController getFingerprintGestureController();
method public final android.accessibilityservice.AccessibilityService.MagnificationController getMagnificationController();
method public android.view.accessibility.AccessibilityNodeInfo getRootInActiveWindow();
@@ -2805,6 +2819,7 @@ package android.accessibilityservice {
field public static final int FLAG_ENABLE_ACCESSIBILITY_VOLUME = 128; // 0x80
field public static final int FLAG_INCLUDE_NOT_IMPORTANT_VIEWS = 2; // 0x2
field public static final int FLAG_REPORT_VIEW_IDS = 16; // 0x10
+ field public static final int FLAG_REQUEST_ACCESSIBILITY_BUTTON = 256; // 0x100
field public static final int FLAG_REQUEST_ENHANCED_WEB_ACCESSIBILITY = 8; // 0x8
field public static final int FLAG_REQUEST_FILTER_KEY_EVENTS = 32; // 0x20
field public static final int FLAG_REQUEST_TOUCH_EXPLORATION_MODE = 4; // 0x4
diff --git a/core/java/android/accessibilityservice/AccessibilityButtonController.java b/core/java/android/accessibilityservice/AccessibilityButtonController.java
new file mode 100644
index 000000000000..c3a5daba4cfc
--- /dev/null
+++ b/core/java/android/accessibilityservice/AccessibilityButtonController.java
@@ -0,0 +1,223 @@
+/*
+ * Copyright (C) 2017 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.accessibilityservice;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.util.ArrayMap;
+import android.util.Slog;
+
+/**
+ * Controller for the accessibility button within the system's navigation area
+ * <p>
+ * This class may be used to query the accessibility button's state and register
+ * callbacks for interactions with and state changes to the accessibility button when
+ * {@link AccessibilityServiceInfo#FLAG_REQUEST_ACCESSIBILITY_BUTTON} is set.
+ * </p>
+ * <p>
+ * <strong>Note:</strong> This class and
+ * {@link AccessibilityServiceInfo#FLAG_REQUEST_ACCESSIBILITY_BUTTON} should not be used as
+ * the sole means for offering functionality to users via an {@link AccessibilityService}.
+ * Some device implementations may choose not to provide a software-rendered system
+ * navigation area, making this affordance permanently unavailable.
+ * </p>
+ * <p>
+ * <strong>Note:</strong> On device implementations where the accessibility button is
+ * supported, it may not be available at all times, such as when a foreground application uses
+ * {@link android.view.View#SYSTEM_UI_FLAG_HIDE_NAVIGATION}. A user may also choose to assign
+ * this button to another accessibility service or feature. In each of these cases, a
+ * registered {@link AccessibilityButtonCallback}'s
+ * {@link AccessibilityButtonCallback#onAvailabilityChanged(AccessibilityButtonController, boolean)}
+ * method will be invoked to provide notifications of changes in the accessibility button's
+ * availability to the registering service.
+ * </p>
+ */
+public final class AccessibilityButtonController {
+ private static final String LOG_TAG = "A11yButtonController";
+
+ private final IAccessibilityServiceConnection mServiceConnection;
+ private final Object mLock;
+ private ArrayMap<AccessibilityButtonCallback, Handler> mCallbacks;
+
+ AccessibilityButtonController(@NonNull IAccessibilityServiceConnection serviceConnection) {
+ mServiceConnection = serviceConnection;
+ mLock = new Object();
+ }
+
+ /**
+ * Retrieves whether the accessibility button in the system's navigation area is
+ * available to the calling service.
+ * <p>
+ * <strong>Note:</strong> If the service is not yet connected (e.g.
+ * {@link AccessibilityService#onServiceConnected()} has not yet been called) or the
+ * service has been disconnected, this method will have no effect and return {@code false}.
+ * </p>
+ *
+ * @return {@code true} if the accessibility button in the system's navigation area is
+ * available to the calling service, {@code false} otherwise
+ */
+ public boolean isAccessibilityButtonAvailable() {
+ try {
+ return mServiceConnection.isAccessibilityButtonAvailable();
+ } catch (RemoteException re) {
+ Slog.w(LOG_TAG, "Failed to get accessibility button availability.", re);
+ re.rethrowFromSystemServer();
+ return false;
+ }
+ }
+
+ /**
+ * Registers the provided {@link AccessibilityButtonCallback} for interaction and state
+ * changes callbacks related to the accessibility button.
+ *
+ * @param callback the callback to add, must be non-null
+ */
+ public void registerAccessibilityButtonCallback(@NonNull AccessibilityButtonCallback callback) {
+ registerAccessibilityButtonCallback(callback, null);
+ }
+
+ /**
+ * Registers the provided {@link AccessibilityButtonCallback} for interaction and state
+ * change callbacks related to the accessibility button. The callback will occur on the
+ * specified {@link Handler}'s thread, or on the services's main thread if the handler is
+ * {@code null}.
+ *
+ * @param callback the callback to add, must be non-null
+ * @param handler the handler on which to callback should execute, or {@code null} to
+ * execute on the service's main thread
+ */
+ public void registerAccessibilityButtonCallback(@NonNull AccessibilityButtonCallback callback,
+ @Nullable Handler handler) {
+ synchronized (mLock) {
+ if (mCallbacks == null) {
+ mCallbacks = new ArrayMap<>();
+ }
+
+ mCallbacks.put(callback, handler);
+ }
+ }
+
+ /**
+ * Unregisters the provided {@link AccessibilityButtonCallback} for interaction and state
+ * change callbacks related to the accessibility button.
+ *
+ * @param callback the callback to remove, must be non-null
+ */
+ public void unregisterAccessibilityButtonCallback(
+ @NonNull AccessibilityButtonCallback callback) {
+ synchronized (mLock) {
+ if (mCallbacks == null) {
+ return;
+ }
+
+ final int keyIndex = mCallbacks.indexOfKey(callback);
+ final boolean hasKey = keyIndex >= 0;
+ if (hasKey) {
+ mCallbacks.removeAt(keyIndex);
+ }
+ }
+ }
+
+ /**
+ * Dispatches the accessibility button click to any registered callbacks. This should
+ * be called on the service's main thread.
+ */
+ void dispatchAccessibilityButtonClicked() {
+ final ArrayMap<AccessibilityButtonCallback, Handler> entries;
+ synchronized (mLock) {
+ if (mCallbacks == null || mCallbacks.isEmpty()) {
+ Slog.w(LOG_TAG, "Received accessibility button click with no callbacks!");
+ return;
+ }
+
+ // Callbacks may remove themselves. Perform a shallow copy to avoid concurrent
+ // modification.
+ entries = new ArrayMap<>(mCallbacks);
+ }
+
+ for (int i = 0, count = entries.size(); i < count; i++) {
+ final AccessibilityButtonCallback callback = entries.keyAt(i);
+ final Handler handler = entries.valueAt(i);
+ if (handler != null) {
+ handler.post(() -> callback.onClicked(this));
+ } else {
+ // We're already on the main thread, just run the callback.
+ callback.onClicked(this);
+ }
+ }
+ }
+
+ /**
+ * Dispatches the accessibility button availability changes to any registered callbacks.
+ * This should be called on the service's main thread.
+ */
+ void dispatchAccessibilityButtonAvailabilityChanged(boolean available) {
+ final ArrayMap<AccessibilityButtonCallback, Handler> entries;
+ synchronized (mLock) {
+ if (mCallbacks == null || mCallbacks.isEmpty()) {
+ Slog.w(LOG_TAG,
+ "Received accessibility button availability change with no callbacks!");
+ return;
+ }
+
+ // Callbacks may remove themselves. Perform a shallow copy to avoid concurrent
+ // modification.
+ entries = new ArrayMap<>(mCallbacks);
+ }
+
+ for (int i = 0, count = entries.size(); i < count; i++) {
+ final AccessibilityButtonCallback callback = entries.keyAt(i);
+ final Handler handler = entries.valueAt(i);
+ if (handler != null) {
+ handler.post(() -> callback.onAvailabilityChanged(this, available));
+ } else {
+ // We're already on the main thread, just run the callback.
+ callback.onAvailabilityChanged(this, available);
+ }
+ }
+ }
+
+ /**
+ * Callback for interaction with and changes to state of the accessibility button
+ * within the system's navigation area.
+ */
+ public static abstract class AccessibilityButtonCallback {
+
+ /**
+ * Called when the accessibility button in the system's navigation area is clicked.
+ *
+ * @param controller the controller used to register for this callback
+ */
+ public void onClicked(AccessibilityButtonController controller) {}
+
+ /**
+ * Called when the availability of the accessibility button in the system's
+ * navigation area has changed. The accessibility button may become unavailable
+ * because the device shopped showing the button, the button was assigned to another
+ * service, or for other reasons.
+ *
+ * @param controller the controller used to register for this callback
+ * @param available {@code true} if the accessibility button is available to this
+ * service, {@code false} otherwise
+ */
+ public void onAvailabilityChanged(AccessibilityButtonController controller,
+ boolean available) {
+ }
+ }
+}
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index a036b6a6dfab..9e486d544195 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -378,6 +378,8 @@ public abstract class AccessibilityService extends Service {
void onPerformGestureResult(int sequence, boolean completedSuccessfully);
void onFingerprintCapturingGesturesChanged(boolean active);
void onFingerprintGesture(int gesture);
+ void onAccessibilityButtonClicked();
+ void onAccessibilityButtonAvailabilityChanged(boolean available);
}
/**
@@ -400,6 +402,7 @@ public abstract class AccessibilityService extends Service {
private MagnificationController mMagnificationController;
private SoftKeyboardController mSoftKeyboardController;
+ private AccessibilityButtonController mAccessibilityButtonController;
private int mGestureStatusCallbackSequence;
@@ -431,6 +434,9 @@ public abstract class AccessibilityService extends Service {
if (mMagnificationController != null) {
mMagnificationController.onServiceConnected();
}
+ if (mSoftKeyboardController != null) {
+ mSoftKeyboardController.onServiceConnected();
+ }
// The client gets to handle service connection last, after we've set
// up any state upon which their code may rely.
@@ -809,12 +815,10 @@ public abstract class AccessibilityService extends Service {
}
/**
- * Removes all instances of the specified change listener from the list
- * of magnification change listeners.
+ * Removes the specified change listener from the list of magnification change listeners.
*
* @param listener the listener to remove, must be non-null
- * @return {@code true} if at least one instance of the listener was
- * removed
+ * @return {@code true} if the listener was removed, {@code false} otherwise
*/
public boolean removeListener(@NonNull OnMagnificationChangedListener listener) {
if (mListeners == null) {
@@ -1203,11 +1207,11 @@ public abstract class AccessibilityService extends Service {
}
/**
- * Removes all instances of the specified change listener from the list of magnification
- * change listeners.
+ * Removes the specified change listener from the list of keyboard show mode change
+ * listeners.
*
* @param listener the listener to remove, must be non-null
- * @return {@code true} if at least one instance of the listener was removed
+ * @return {@code true} if the listener was removed, {@code false} otherwise
*/
public boolean removeOnShowModeChangedListener(@NonNull OnShowModeChangedListener listener) {
if (mListeners == null) {
@@ -1252,7 +1256,7 @@ public abstract class AccessibilityService extends Service {
final ArrayMap<OnShowModeChangedListener, Handler> entries;
synchronized (mLock) {
if (mListeners == null || mListeners.isEmpty()) {
- Slog.d(LOG_TAG, "Received soft keyboard show mode changed callback"
+ Slog.w(LOG_TAG, "Received soft keyboard show mode changed callback"
+ " with no listeners registered!");
setSoftKeyboardCallbackEnabled(false);
return;
@@ -1308,9 +1312,9 @@ public abstract class AccessibilityService extends Service {
* The lastto this method will be honored, regardless of any previous calls (including those
* made by other AccessibilityServices).
* <p>
- * <strong>Note:</strong> If the service is not yet conected (e.g.
+ * <strong>Note:</strong> If the service is not yet connected (e.g.
* {@link AccessibilityService#onServiceConnected()} has not yet been called) or the
- * service has been disconnected, this method will hav no effect and return {@code false}.
+ * service has been disconnected, this method will have no effect and return {@code false}.
*
* @param showMode the new show mode for the soft keyboard
* @return {@code true} on success
@@ -1349,6 +1353,39 @@ public abstract class AccessibilityService extends Service {
}
/**
+ * Returns the controller for the accessibility button within the system's navigation area.
+ * This instance may be used to query the accessibility button's state and register listeners
+ * for interactions with and state changes for the accessibility button when
+ * {@link AccessibilityServiceInfo#FLAG_REQUEST_ACCESSIBILITY_BUTTON} is set.
+ * <p>
+ * <strong>Note:</strong> Not all devices are capable of displaying the accessibility button
+ * within a navigation area, and as such, use of this class should be considered only as an
+ * optional feature or shortcut on supported device implementations.
+ * </p>
+ *
+ * @return the accessibility button controller for this {@link AccessibilityService}
+ */
+ @NonNull
+ public final AccessibilityButtonController getAccessibilityButtonController() {
+ synchronized (mLock) {
+ if (mAccessibilityButtonController == null) {
+ mAccessibilityButtonController = new AccessibilityButtonController(
+ AccessibilityInteractionClient.getInstance().getConnection(mConnectionId));
+ }
+ return mAccessibilityButtonController;
+ }
+ }
+
+ private void onAccessibilityButtonClicked() {
+ getAccessibilityButtonController().dispatchAccessibilityButtonClicked();
+ }
+
+ private void onAccessibilityButtonAvailabilityChanged(boolean available) {
+ getAccessibilityButtonController().dispatchAccessibilityButtonAvailabilityChanged(
+ available);
+ }
+
+ /**
* Performs a global action. Such an action can be performed
* at any moment regardless of the current application or user
* location in that application. For example going back, going
@@ -1543,6 +1580,16 @@ public abstract class AccessibilityService extends Service {
public void onFingerprintGesture(int gesture) {
AccessibilityService.this.onFingerprintGesture(gesture);
}
+
+ @Override
+ public void onAccessibilityButtonClicked() {
+ AccessibilityService.this.onAccessibilityButtonClicked();
+ }
+
+ @Override
+ public void onAccessibilityButtonAvailabilityChanged(boolean available) {
+ AccessibilityService.this.onAccessibilityButtonAvailabilityChanged(available);
+ }
});
}
@@ -1565,6 +1612,8 @@ public abstract class AccessibilityService extends Service {
private static final int DO_GESTURE_COMPLETE = 9;
private static final int DO_ON_FINGERPRINT_ACTIVE_CHANGED = 10;
private static final int DO_ON_FINGERPRINT_GESTURE = 11;
+ private static final int DO_ACCESSIBILITY_BUTTON_CLICKED = 12;
+ private static final int DO_ACCESSIBILITY_BUTTON_AVAILABILITY_CHANGED = 13;
private final HandlerCaller mCaller;
@@ -1645,6 +1694,17 @@ public abstract class AccessibilityService extends Service {
mCaller.sendMessage(mCaller.obtainMessageI(DO_ON_FINGERPRINT_GESTURE, gesture));
}
+ public void onAccessibilityButtonClicked() {
+ final Message message = mCaller.obtainMessage(DO_ACCESSIBILITY_BUTTON_CLICKED);
+ mCaller.sendMessage(message);
+ }
+
+ public void onAccessibilityButtonAvailabilityChanged(boolean available) {
+ final Message message = mCaller.obtainMessageI(
+ DO_ACCESSIBILITY_BUTTON_AVAILABILITY_CHANGED, (available ? 1 : 0));
+ mCaller.sendMessage(message);
+ }
+
@Override
public void executeMessage(Message message) {
switch (message.what) {
@@ -1750,6 +1810,15 @@ public abstract class AccessibilityService extends Service {
mCallback.onFingerprintGesture(message.arg1);
} return;
+ case (DO_ACCESSIBILITY_BUTTON_CLICKED): {
+ mCallback.onAccessibilityButtonClicked();
+ } return;
+
+ case (DO_ACCESSIBILITY_BUTTON_AVAILABILITY_CHANGED): {
+ final boolean available = (message.arg1 != 0);
+ mCallback.onAccessibilityButtonAvailabilityChanged(available);
+ } return;
+
default :
Log.w(LOG_TAG, "Unknown message type " + message.what);
}
diff --git a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
index 18e57cb5694c..e135ffdf9b13 100644
--- a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
+++ b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
@@ -306,6 +306,12 @@ public class AccessibilityServiceInfo implements Parcelable {
*/
public static final int FLAG_ENABLE_ACCESSIBILITY_VOLUME = 0x00000080;
+ /**
+ * This flag indicates to the system that the accessibility service requests that an
+ * accessibility button be shown within the system's navigation area, if available.
+ */
+ public static final int FLAG_REQUEST_ACCESSIBILITY_BUTTON = 0x00000100;
+
/**
* This flag requests that all fingerprint gestures be sent to the accessibility service.
* It is handled in {@link FingerprintGestureController}
@@ -396,6 +402,8 @@ public class AccessibilityServiceInfo implements Parcelable {
* @see #FLAG_REQUEST_FILTER_KEY_EVENTS
* @see #FLAG_REPORT_VIEW_IDS
* @see #FLAG_RETRIEVE_INTERACTIVE_WINDOWS
+ * @see #FLAG_ENABLE_ACCESSIBILITY_VOLUME
+ * @see #FLAG_REQUEST_ACCESSIBILITY_BUTTON
*/
public int flags;
@@ -631,7 +639,7 @@ public class AccessibilityServiceInfo implements Parcelable {
* @see #CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT
* @see #CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION
* @see #CAPABILITY_CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY
- * @see #CAPABILITY_FILTER_KEY_EVENTS
+ * @see #CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS
* @see #CAPABILITY_CAN_CONTROL_MAGNIFICATION
* @see #CAPABILITY_CAN_PERFORM_GESTURES
*/
@@ -648,7 +656,7 @@ public class AccessibilityServiceInfo implements Parcelable {
* @see #CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT
* @see #CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION
* @see #CAPABILITY_CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY
- * @see #CAPABILITY_FILTER_KEY_EVENTS
+ * @see #CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS
* @see #CAPABILITY_CAN_CONTROL_MAGNIFICATION
* @see #CAPABILITY_CAN_PERFORM_GESTURES
*
@@ -936,6 +944,8 @@ public class AccessibilityServiceInfo implements Parcelable {
return "FLAG_RETRIEVE_INTERACTIVE_WINDOWS";
case FLAG_ENABLE_ACCESSIBILITY_VOLUME:
return "FLAG_ENABLE_ACCESSIBILITY_VOLUME";
+ case FLAG_REQUEST_ACCESSIBILITY_BUTTON:
+ return "FLAG_REQUEST_ACCESSIBILITY_BUTTON";
case FLAG_CAPTURE_FINGERPRINT_GESTURES:
return "FLAG_CAPTURE_FINGERPRINT_GESTURES";
default:
@@ -960,7 +970,7 @@ public class AccessibilityServiceInfo implements Parcelable {
case CAPABILITY_CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY:
return "CAPABILITY_CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY";
case CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS:
- return "CAPABILITY_CAN_FILTER_KEY_EVENTS";
+ return "CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS";
case CAPABILITY_CAN_CONTROL_MAGNIFICATION:
return "CAPABILITY_CAN_CONTROL_MAGNIFICATION";
case CAPABILITY_CAN_PERFORM_GESTURES:
diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl
index 3f778ad0e148..4e96b8f11628 100644
--- a/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl
+++ b/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl
@@ -50,4 +50,8 @@ import android.view.KeyEvent;
void onFingerprintCapturingGesturesChanged(boolean capturing);
void onFingerprintGesture(int gesture);
+
+ void onAccessibilityButtonClicked();
+
+ void onAccessibilityButtonAvailabilityChanged(boolean available);
}
diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
index 5499bd562901..5bd372208f6f 100644
--- a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
+++ b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
@@ -88,6 +88,8 @@ interface IAccessibilityServiceConnection {
void setSoftKeyboardCallbackEnabled(boolean enabled);
+ boolean isAccessibilityButtonAvailable();
+
void sendGesture(int sequence, in ParceledListSlice gestureSteps);
boolean isFingerprintGestureDetectionAvailable();
diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java
index 6d1d1a331b61..1d6f42ecdb0b 100644
--- a/core/java/android/app/UiAutomation.java
+++ b/core/java/android/app/UiAutomation.java
@@ -1118,6 +1118,16 @@ public final class UiAutomation {
public void onFingerprintGesture(int gesture) {
/* do nothing */
}
+
+ @Override
+ public void onAccessibilityButtonClicked() {
+ /* do nothing */
+ }
+
+ @Override
+ public void onAccessibilityButtonAvailabilityChanged(boolean available) {
+ /* do nothing */
+ }
});
}
}
diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java
index 1ef0d1799c2c..45302b6cbdce 100644
--- a/core/java/android/view/accessibility/AccessibilityManager.java
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -779,6 +779,49 @@ public final class AccessibilityManager {
}
}
+ /**
+ * Notifies that the accessibility button in the system's navigation area has been clicked
+ *
+ * @hide
+ */
+ public void notifyAccessibilityButtonClicked() {
+ final IAccessibilityManager service;
+ synchronized (mLock) {
+ service = getServiceLocked();
+ if (service == null) {
+ return;
+ }
+ }
+ try {
+ service.notifyAccessibilityButtonClicked();
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error while dispatching accessibility button click", re);
+ }
+ }
+
+ /**
+ * Notifies that the availability of the accessibility button in the system's navigation area
+ * has changed.
+ *
+ * @param available {@code true} if the accessibility button is available within the system
+ * navigation area, {@code false} otherwise
+ * @hide
+ */
+ public void notifyAccessibilityButtonAvailabilityChanged(boolean available) {
+ final IAccessibilityManager service;
+ synchronized (mLock) {
+ service = getServiceLocked();
+ if (service == null) {
+ return;
+ }
+ }
+ try {
+ service.notifyAccessibilityButtonAvailabilityChanged(available);
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error while dispatching accessibility button availability change", re);
+ }
+ }
+
private IAccessibilityManager getServiceLocked() {
if (mService == null) {
tryConnectToServiceLocked(null);
diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl
index 136bbbe39c75..8fde47a30b2b 100644
--- a/core/java/android/view/accessibility/IAccessibilityManager.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl
@@ -59,6 +59,10 @@ interface IAccessibilityManager {
IBinder getWindowToken(int windowId, int userId);
+ void notifyAccessibilityButtonClicked();
+
+ void notifyAccessibilityButtonAvailabilityChanged(boolean available);
+
// Requires WRITE_SECURE_SETTINGS
void performAccessibilityShortcut();
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index c548219b5206..7f49f056d3bf 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -3392,6 +3392,8 @@
<flag name="flagRetrieveInteractiveWindows" value="0x00000040" />
<!-- Has flag {@link android.accessibilityservice.AccessibilityServiceInfo#FLAG_ENABLE_ACCESSIBILITY_VOLUME} -->
<flag name="flagEnableAccessibilityVolume" value="0x00000080" />
+ <!-- Has flag {@link android.accessibilityservice.AccessibilityServiceInfo#FLAG_REQUEST_ACCESSIBILITY_BUTTON} -->
+ <flag name="flagRequestAccessibilityButton" value="0x00000100" />
<!-- Has flag {@link android.accessibilityservice.AccessibilityServiceInfo#FLAG_CAPTURE_FINGERPRINT_GESTURES} -->
<flag name="flagCaptureFingerprintGestures" value="0x00000200" />
</attr>
@@ -3429,18 +3431,9 @@
<attr name="canRequestFilterKeyEvents" format="boolean" />
<!-- Attribute whether the accessibility service wants to be able to control
display magnification.
- <p>
- Required to allow setting the {@link android.accessibilityservice
- #AccessibilityServiceInfo#FLAG_CAN_CONTROL_MAGNIFICATION} flag.
- </p>
-->
<attr name="canControlMagnification" format="boolean" />
- <!-- Attribute whether the accessibility service wants to be able to perform gestures.
- <p>
- Required to allow setting the {@link android.accessibilityservice
- #AccessibilityServiceInfo#FLAG_CAN_PERFORM_GESTURES} flag.
- </p>
- -->
+ <!-- Attribute whether the accessibility service wants to be able to perform gestures. -->
<attr name="canPerformGestures" format="boolean" />
<!-- Attribute whether the accessibility service wants to be able to capture gestures from
the fingerprint sensor.
diff --git a/packages/SystemUI/res/drawable-hdpi/ic_sysbar_accessibility_button.png b/packages/SystemUI/res/drawable-hdpi/ic_sysbar_accessibility_button.png
new file mode 100644
index 000000000000..0615668b6465
--- /dev/null
+++ b/packages/SystemUI/res/drawable-hdpi/ic_sysbar_accessibility_button.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/ic_sysbar_accessibility_button_dark.png b/packages/SystemUI/res/drawable-hdpi/ic_sysbar_accessibility_button_dark.png
new file mode 100644
index 000000000000..839e5edeead3
--- /dev/null
+++ b/packages/SystemUI/res/drawable-hdpi/ic_sysbar_accessibility_button_dark.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/ic_sysbar_accessibility_button.png b/packages/SystemUI/res/drawable-mdpi/ic_sysbar_accessibility_button.png
new file mode 100644
index 000000000000..1480c865e2dd
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/ic_sysbar_accessibility_button.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/ic_sysbar_accessibility_button_dark.png b/packages/SystemUI/res/drawable-mdpi/ic_sysbar_accessibility_button_dark.png
new file mode 100644
index 000000000000..66e11fb52b30
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/ic_sysbar_accessibility_button_dark.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xhdpi/ic_sysbar_accessibility_button.png b/packages/SystemUI/res/drawable-xhdpi/ic_sysbar_accessibility_button.png
new file mode 100644
index 000000000000..d2fe0c32cad3
--- /dev/null
+++ b/packages/SystemUI/res/drawable-xhdpi/ic_sysbar_accessibility_button.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xhdpi/ic_sysbar_accessibility_button_dark.png b/packages/SystemUI/res/drawable-xhdpi/ic_sysbar_accessibility_button_dark.png
new file mode 100644
index 000000000000..5923269a882b
--- /dev/null
+++ b/packages/SystemUI/res/drawable-xhdpi/ic_sysbar_accessibility_button_dark.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xxhdpi/ic_sysbar_accessibility_button.png b/packages/SystemUI/res/drawable-xxhdpi/ic_sysbar_accessibility_button.png
new file mode 100644
index 000000000000..d0196e5f8ea3
--- /dev/null
+++ b/packages/SystemUI/res/drawable-xxhdpi/ic_sysbar_accessibility_button.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xxhdpi/ic_sysbar_accessibility_button_dark.png b/packages/SystemUI/res/drawable-xxhdpi/ic_sysbar_accessibility_button_dark.png
new file mode 100644
index 000000000000..d3a2b393e685
--- /dev/null
+++ b/packages/SystemUI/res/drawable-xxhdpi/ic_sysbar_accessibility_button_dark.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xxxhdpi/ic_sysbar_accessibility_button.png b/packages/SystemUI/res/drawable-xxxhdpi/ic_sysbar_accessibility_button.png
new file mode 100644
index 000000000000..726643ca1bb1
--- /dev/null
+++ b/packages/SystemUI/res/drawable-xxxhdpi/ic_sysbar_accessibility_button.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xxxhdpi/ic_sysbar_accessibility_button_dark.png b/packages/SystemUI/res/drawable-xxxhdpi/ic_sysbar_accessibility_button_dark.png
new file mode 100644
index 000000000000..31078f6aa42c
--- /dev/null
+++ b/packages/SystemUI/res/drawable-xxxhdpi/ic_sysbar_accessibility_button_dark.png
Binary files differ
diff --git a/packages/SystemUI/res/layout/menu_ime.xml b/packages/SystemUI/res/layout/menu_ime.xml
index 5e85ba06beea..6bd79a4b4ae4 100644
--- a/packages/SystemUI/res/layout/menu_ime.xml
+++ b/packages/SystemUI/res/layout/menu_ime.xml
@@ -39,4 +39,13 @@
android:contentDescription="@string/accessibility_ime_switch_button"
android:scaleType="centerInside"
/>
+ <com.android.systemui.statusbar.policy.KeyButtonView
+ android:id="@+id/accessibility_button"
+ android:layout_width="@dimen/navigation_extra_key_width"
+ android:layout_height="match_parent"
+ android:layout_marginEnd="2dp"
+ android:visibility="invisible"
+ android:contentDescription="@string/accessibility_accessibility_button"
+ android:scaleType="centerInside"
+ />
</FrameLayout>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 363b3e20449e..2cc2621f7c5c 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -207,6 +207,8 @@
<string name="accessibility_home">Home</string>
<!-- Content description of the menu button for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
<string name="accessibility_menu">Menu</string>
+ <!-- Content description of the accessibility button in the navigation bar (not shown on the screen). [CHAR LIMIT=NONE] -->
+ <string name="accessibility_accessibility_button">Accessibility</string>
<!-- Content description of the recents button for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
<string name="accessibility_recent">Overview</string>
<!-- Content description of the search button for accessibility. [CHAR LIMIT=NONE] -->
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
index 62b536e85f85..808cd2108829 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
@@ -23,6 +23,7 @@ import static com.android.systemui.statusbar.phone.BarTransitions.MODE_SEMI_TRAN
import static com.android.systemui.statusbar.phone.StatusBar.DEBUG_WINDOW_STATE;
import static com.android.systemui.statusbar.phone.StatusBar.dumpBarTransitions;
+import android.accessibilityservice.AccessibilityServiceInfo;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityManagerNative;
@@ -79,6 +80,7 @@ import com.android.systemui.statusbar.stack.StackStateAnimator;
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.util.List;
import java.util.Locale;
/**
@@ -101,7 +103,7 @@ public class NavigationBarFragment extends Fragment implements Callbacks {
private int mNavigationIconHints = 0;
private int mNavigationBarMode;
- protected AccessibilityManager mAccessibilityManager;
+ private AccessibilityManager mAccessibilityManager;
private int mDisabledFlags1;
private StatusBar mStatusBar;
@@ -132,6 +134,8 @@ public class NavigationBarFragment extends Fragment implements Callbacks {
mDivider = SysUiServiceProvider.getComponent(getContext(), Divider.class);
mWindowManager = getContext().getSystemService(WindowManager.class);
mAccessibilityManager = getContext().getSystemService(AccessibilityManager.class);
+ mAccessibilityManager.addAccessibilityServicesStateChangeListener(
+ this::updateAccessibilityServicesState);
if (savedInstanceState != null) {
mDisabledFlags1 = savedInstanceState.getInt(EXTRA_DISABLE_STATE, 0);
}
@@ -149,6 +153,8 @@ public class NavigationBarFragment extends Fragment implements Callbacks {
public void onDestroy() {
super.onDestroy();
mCommandQueue.removeCallbacks(this);
+ mAccessibilityManager.removeAccessibilityServicesStateChangeListener(
+ this::updateAccessibilityServicesState);
try {
WindowManagerGlobal.getWindowManagerService()
.removeRotationWatcher(mRotationWatcher);
@@ -402,6 +408,10 @@ public class NavigationBarFragment extends Fragment implements Callbacks {
ButtonDispatcher homeButton = mNavigationBarView.getHomeButton();
homeButton.setOnTouchListener(this::onHomeTouch);
homeButton.setOnLongClickListener(this::onHomeLongClick);
+
+ ButtonDispatcher accessibilityButton = mNavigationBarView.getAccessibilityButton();
+ accessibilityButton.setOnClickListener(this::onAccessibilityClick);
+ accessibilityButton.setOnLongClickListener(this::onAccessibilityLongClick);
}
private boolean onHomeTouch(View v, MotionEvent event) {
@@ -553,6 +563,34 @@ public class NavigationBarFragment extends Fragment implements Callbacks {
MetricsEvent.ACTION_WINDOW_UNDOCK_LONGPRESS);
}
+ private void onAccessibilityClick(View v) {
+ mAccessibilityManager.notifyAccessibilityButtonClicked();
+ }
+
+ private boolean onAccessibilityLongClick(View v) {
+ // TODO(b/34720082): Target service selection via long click
+ android.widget.Toast.makeText(getContext(), "Service selection coming soon...",
+ android.widget.Toast.LENGTH_LONG).show();
+ return true;
+ }
+
+ private void updateAccessibilityServicesState() {
+ final List<AccessibilityServiceInfo> services =
+ mAccessibilityManager.getEnabledAccessibilityServiceList(
+ AccessibilityServiceInfo.FEEDBACK_ALL_MASK);
+ int requestingServices = 0;
+ for (int i = services.size() - 1; i >= 0; --i) {
+ AccessibilityServiceInfo info = services.get(i);
+ if ((info.flags & AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0) {
+ requestingServices++;
+ }
+ }
+
+ final boolean showAccessibilityButton = requestingServices >= 1;
+ final boolean targetSelection = requestingServices >= 2;
+ mNavigationBarView.setAccessibilityButtonState(showAccessibilityButton, targetSelection);
+ }
+
// ----- Methods that StatusBar talks to (should be minimized) -----
public void setLightBarController(LightBarController lightBarController) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
index dced747968aa..5d13289ef9cc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -78,6 +78,8 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav
private int mCurrentRotation = -1;
boolean mShowMenu;
+ boolean mShowAccessibilityButton;
+ boolean mLongClickableAccessibilityButton;
int mDisabledFlags = 0;
int mNavigationIconHints = 0;
@@ -89,6 +91,7 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav
private KeyButtonDrawable mDockedIcon;
private KeyButtonDrawable mImeIcon;
private KeyButtonDrawable mMenuIcon;
+ private KeyButtonDrawable mAccessibilityIcon;
private GestureHelper mGestureHelper;
private DeadZone mDeadZone;
@@ -202,6 +205,9 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav
mVertical = false;
mShowMenu = false;
+ mShowAccessibilityButton = false;
+ mLongClickableAccessibilityButton = false;
+
mConfiguration = new Configuration();
mConfiguration.updateFrom(context.getResources().getConfiguration());
updateIcons(context, Configuration.EMPTY, mConfiguration);
@@ -213,6 +219,8 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav
mButtonDispatchers.put(R.id.recent_apps, new ButtonDispatcher(R.id.recent_apps));
mButtonDispatchers.put(R.id.menu, new ButtonDispatcher(R.id.menu));
mButtonDispatchers.put(R.id.ime_switcher, new ButtonDispatcher(R.id.ime_switcher));
+ mButtonDispatchers.put(R.id.accessibility_button,
+ new ButtonDispatcher(R.id.accessibility_button));
}
public BarTransitions getBarTransitions() {
@@ -287,6 +295,10 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav
return mButtonDispatchers.get(R.id.ime_switcher);
}
+ public ButtonDispatcher getAccessibilityButton() {
+ return mButtonDispatchers.get(R.id.accessibility_button);
+ }
+
public SparseArray<ButtonDispatcher> getButtonDispatchers() {
return mButtonDispatchers;
}
@@ -320,6 +332,8 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav
mRecentIcon = getDrawable(ctx,
R.drawable.ic_sysbar_recent, R.drawable.ic_sysbar_recent_dark);
mMenuIcon = getDrawable(ctx, R.drawable.ic_sysbar_menu, R.drawable.ic_sysbar_menu_dark);
+ mAccessibilityIcon = getDrawable(ctx, R.drawable.ic_sysbar_accessibility_button,
+ R.drawable.ic_sysbar_accessibility_button_dark);
Context darkContext = new ContextThemeWrapper(ctx, R.style.DualToneDarkTheme);
Context lightContext = new ContextThemeWrapper(ctx, R.style.DualToneLightTheme);
@@ -411,6 +425,9 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav
setMenuVisibility(mShowMenu, true);
getMenuButton().setImageDrawable(mMenuIcon);
+ setAccessibilityButtonState(mShowAccessibilityButton, mLongClickableAccessibilityButton);
+ getAccessibilityButton().setImageDrawable(mAccessibilityIcon);
+
setDisabledFlags(mDisabledFlags, true);
mBarTransitions.reapplyDarkIntensity();
@@ -517,13 +534,25 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav
mShowMenu = show;
- // Only show Menu if IME switcher not shown.
- final boolean shouldShow = mShowMenu &&
+ // Only show Menu if IME switcher and Accessibility button not shown.
+ final boolean shouldShow = mShowMenu && !mShowAccessibilityButton &&
((mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_IME_SHOWN) == 0);
getMenuButton().setVisibility(shouldShow ? View.VISIBLE : View.INVISIBLE);
}
+ public void setAccessibilityButtonState(final boolean visible, final boolean longClickable) {
+ mShowAccessibilityButton = visible;
+ mLongClickableAccessibilityButton = longClickable;
+ if (visible) {
+ // Accessibility button overrides Menu button.
+ setMenuVisibility(false, true);
+ }
+
+ getAccessibilityButton().setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
+ getAccessibilityButton().setLongClickable(longClickable);
+ }
+
@Override
public void onFinishInflate() {
mNavigationInflaterView = (NavigationBarInflaterView) findViewById(
@@ -814,6 +843,7 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav
dumpButton(pw, "home", getHomeButton());
dumpButton(pw, "rcnt", getRecentsButton());
dumpButton(pw, "menu", getMenuButton());
+ dumpButton(pw, "a11y", getAccessibilityButton());
pw.println(" }");
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index a861522c0576..987e42665908 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -445,7 +445,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
}
return userState.getClientState();
} else {
- userState.mClients.register(client);
+ userState.mUserClients.register(client);
// If this client is not for the current user we do not
// return a state since it is not for the foreground user.
// We will send the state to the client on a user switch.
@@ -813,6 +813,42 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
}
}
+ /**
+ * Invoked remotely over AIDL by SysUi when the accessibility button within the system's
+ * navigation area has been clicked.
+ */
+ @Override
+ public void notifyAccessibilityButtonClicked() {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.STATUS_BAR)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Caller does not hold permission "
+ + android.Manifest.permission.STATUS_BAR);
+ }
+ synchronized (mLock) {
+ notifyAccessibilityButtonClickedLocked();
+ }
+ }
+
+ /**
+ * Invoked remotely over AIDL by SysUi when the availability of the accessibility
+ * button within the system's navigation area has changed.
+ *
+ * @param available {@code true} if the accessibility button is available to the
+ * user, {@code false} otherwise
+ */
+ @Override
+ public void notifyAccessibilityButtonAvailabilityChanged(boolean available) {
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.STATUS_BAR)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Caller does not hold permission "
+ + android.Manifest.permission.STATUS_BAR);
+ }
+ synchronized (mLock) {
+ notifyAccessibilityButtonAvailabilityChangedLocked(available);
+ }
+ }
+
+
boolean onGesture(int gestureId) {
synchronized (mLock) {
boolean handled = notifyGestureLocked(gestureId, false);
@@ -927,7 +963,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
oldUserState.onSwitchToAnotherUser();
// Disable the local managers for the old user.
- if (oldUserState.mClients.getRegisteredCallbackCount() > 0) {
+ if (oldUserState.mUserClients.getRegisteredCallbackCount() > 0) {
mMainHandler.obtainMessage(MainHandler.MSG_SEND_CLEARED_STATE_TO_CLIENTS_FOR_USER,
oldUserState.mUserId, 0).sendToTarget();
}
@@ -1044,6 +1080,28 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
}
}
+ private void notifyAccessibilityButtonClickedLocked() {
+ final UserState state = getCurrentUserStateLocked();
+ for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
+ final Service service = state.mBoundServices.get(i);
+ // TODO(b/34720082): Only notify a single user-defined service
+ if (service.mRequestAccessibilityButton) {
+ service.notifyAccessibilityButtonClickedLocked();
+ }
+ }
+ }
+
+ private void notifyAccessibilityButtonAvailabilityChangedLocked(boolean available) {
+ final UserState state = getCurrentUserStateLocked();
+ state.mIsAccessibilityButtonAvailable = available;
+ for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
+ final Service service = state.mBoundServices.get(i);
+ if (service.mRequestAccessibilityButton) {
+ service.notifyAccessibilityButtonAvailabilityChangedLocked(available);
+ }
+ }
+ }
+
/**
* Removes an AccessibilityInteractionConnection.
*
@@ -1178,7 +1236,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
service.onAdded();
userState.mBoundServices.add(service);
userState.mComponentNameToServiceMap.put(service.mComponentName, service);
- scheduleNotifyClientsOfServicesStateChange();
+ scheduleNotifyClientsOfServicesStateChange(userState);
}
} catch (RemoteException re) {
/* do nothing */
@@ -1200,7 +1258,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
Service boundService = userState.mBoundServices.get(i);
userState.mComponentNameToServiceMap.put(boundService.mComponentName, boundService);
}
- scheduleNotifyClientsOfServicesStateChange();
+ scheduleNotifyClientsOfServicesStateChange(userState);
}
/**
@@ -1362,16 +1420,16 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
final int clientState = userState.getClientState();
if (userState.mLastSentClientState != clientState
&& (mGlobalClients.getRegisteredCallbackCount() > 0
- || userState.mClients.getRegisteredCallbackCount() > 0)) {
+ || userState.mUserClients.getRegisteredCallbackCount() > 0)) {
userState.mLastSentClientState = clientState;
mMainHandler.obtainMessage(MainHandler.MSG_SEND_STATE_TO_CLIENTS,
clientState, userState.mUserId).sendToTarget();
}
}
- private void scheduleNotifyClientsOfServicesStateChange() {
- mMainHandler.obtainMessage(MainHandler.MSG_SEND_SERVICES_STATE_CHANGED_TO_CLIENTS)
- .sendToTarget();
+ private void scheduleNotifyClientsOfServicesStateChange(UserState userState) {
+ mMainHandler.obtainMessage(MainHandler.MSG_SEND_SERVICES_STATE_CHANGED_TO_CLIENTS,
+ userState.mUserId).sendToTarget();
}
private void scheduleUpdateInputFilter(UserState userState) {
@@ -2225,12 +2283,12 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
final int clientState = msg.arg1;
final int userId = msg.arg2;
sendStateToClients(clientState, mGlobalClients);
- sendStateToClientsForUser(clientState, userId);
+ sendStateToClients(clientState, getUserClientsForId(userId));
} break;
case MSG_SEND_CLEARED_STATE_TO_CLIENTS_FOR_USER: {
final int userId = msg.arg1;
- sendStateToClientsForUser(0, userId);
+ sendStateToClients(0, getUserClientsForId(userId));
} break;
case MSG_ANNOUNCE_NEW_USER_IF_NEEDED: {
@@ -2257,7 +2315,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
} break;
case MSG_SEND_SERVICES_STATE_CHANGED_TO_CLIENTS: {
- notifyClientsOfServicesStateChange();
+ final int userId = msg.arg1;
+ notifyClientsOfServicesStateChange(mGlobalClients);
+ notifyClientsOfServicesStateChange(getUserClientsForId(userId));
} break;
case MSG_UPDATE_FINGERPRINT: {
@@ -2282,12 +2342,12 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
}
}
- private void sendStateToClientsForUser(int clientState, int userId) {
+ private RemoteCallbackList<IAccessibilityManagerClient> getUserClientsForId(int userId) {
final UserState userState;
synchronized (mLock) {
userState = getUserStateLocked(userId);
}
- sendStateToClients(clientState, userState.mClients);
+ return userState.mUserClients;
}
private void sendStateToClients(int clientState,
@@ -2307,11 +2367,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
}
}
- private void notifyClientsOfServicesStateChange() {
- RemoteCallbackList<IAccessibilityManagerClient> clients;
- synchronized (mLock) {
- clients = getCurrentUserStateLocked().mClients;
- }
+ private void notifyClientsOfServicesStateChange(
+ RemoteCallbackList<IAccessibilityManagerClient> clients) {
try {
final int userClientCount = clients.beginBroadcast();
for (int i = 0; i < userClientCount; i++) {
@@ -2426,6 +2483,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
boolean mCaptureFingerprintGestures;
+ boolean mRequestAccessibilityButton;
+
int mFetchFlags;
long mNotificationTimeout;
@@ -2581,6 +2640,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
& AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS) != 0;
mCaptureFingerprintGestures = (info.flags
& AccessibilityServiceInfo.FLAG_CAPTURE_FINGERPRINT_GESTURES) != 0;
+ mRequestAccessibilityButton = (info.flags
+ & AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0;
}
/**
@@ -2694,7 +2755,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
}
UserState userState = getUserStateLocked(mUserId);
onUserStateChangedLocked(userState);
- scheduleNotifyClientsOfServicesStateChange();
+ scheduleNotifyClientsOfServicesStateChange(userState);
}
} finally {
Binder.restoreCallingIdentity(identity);
@@ -3332,6 +3393,19 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
}
@Override
+ public boolean isAccessibilityButtonAvailable() {
+ final UserState userState;
+ synchronized (mLock) {
+ if (!isCalledForCurrentUserLocked()) {
+ return false;
+ }
+ userState = getCurrentUserStateLocked();
+ }
+
+ return mRequestAccessibilityButton && userState.mIsAccessibilityButtonAvailable;
+ }
+
+ @Override
public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
mSecurityPolicy.enforceCallingPermission(Manifest.permission.DUMP, FUNCTION_DUMP);
synchronized (mLock) {
@@ -3544,6 +3618,14 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
mInvocationHandler.notifySoftKeyboardShowModeChangedLocked(showState);
}
+ public void notifyAccessibilityButtonClickedLocked() {
+ mInvocationHandler.notifyAccessibilityButtonClickedLocked();
+ }
+
+ public void notifyAccessibilityButtonAvailabilityChangedLocked(boolean available) {
+ mInvocationHandler.notifyAccessibilityButtonAvailabilityChangedLocked(available);
+ }
+
/**
* Called by the invocation handler to notify the service that the
* state of magnification has changed.
@@ -3582,6 +3664,36 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
}
}
+ private void notifyAccessibilityButtonClickedInternal() {
+ final IAccessibilityServiceClient listener;
+ synchronized (mLock) {
+ listener = mServiceInterface;
+ }
+ if (listener != null) {
+ try {
+ listener.onAccessibilityButtonClicked();
+ } catch (RemoteException re) {
+ Slog.e(LOG_TAG, "Error sending accessibility button click to " + mService, re);
+ }
+ }
+ }
+
+ private void notifyAccessibilityButtonAvailabilityChangedInternal(boolean available) {
+ final IAccessibilityServiceClient listener;
+ synchronized (mLock) {
+ listener = mServiceInterface;
+ }
+ if (listener != null) {
+ try {
+ listener.onAccessibilityButtonAvailabilityChanged(available);
+ } catch (RemoteException re) {
+ Slog.e(LOG_TAG,
+ "Error sending accessibility button availability change to " + mService,
+ re);
+ }
+ }
+ }
+
private void notifyGestureInternal(int gestureId) {
final IAccessibilityServiceClient listener;
synchronized (mLock) {
@@ -3723,6 +3835,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
private static final int MSG_ON_MAGNIFICATION_CHANGED = 5;
private static final int MSG_ON_SOFT_KEYBOARD_STATE_CHANGED = 6;
+ private static final int MSG_ON_ACCESSIBILITY_BUTTON_CLICKED = 7;
+ private static final int MSG_ON_ACCESSIBILITY_BUTTON_AVAILABILITY_CHANGED = 8;
private boolean mIsMagnificationCallbackEnabled = false;
private boolean mIsSoftKeyboardCallbackEnabled = false;
@@ -3758,6 +3872,15 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
notifySoftKeyboardShowModeChangedInternal(showState);
} break;
+ case MSG_ON_ACCESSIBILITY_BUTTON_CLICKED: {
+ notifyAccessibilityButtonClickedInternal();
+ } break;
+
+ case MSG_ON_ACCESSIBILITY_BUTTON_AVAILABILITY_CHANGED: {
+ final boolean available = (message.arg1 != 0);
+ notifyAccessibilityButtonAvailabilityChangedInternal(available);
+ } break;
+
default: {
throw new IllegalArgumentException("Unknown message: " + type);
}
@@ -3797,6 +3920,17 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
public void setSoftKeyboardCallbackEnabled(boolean enabled) {
mIsSoftKeyboardCallbackEnabled = enabled;
}
+
+ public void notifyAccessibilityButtonClickedLocked() {
+ final Message msg = obtainMessage(MSG_ON_ACCESSIBILITY_BUTTON_CLICKED);
+ msg.sendToTarget();
+ }
+
+ public void notifyAccessibilityButtonAvailabilityChangedLocked(boolean available) {
+ final Message msg = obtainMessage(MSG_ON_ACCESSIBILITY_BUTTON_AVAILABILITY_CHANGED,
+ (available ? 1 : 0), 0);
+ msg.sendToTarget();
+ }
}
}
@@ -4467,7 +4601,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
// Non-transient state.
- public final RemoteCallbackList<IAccessibilityManagerClient> mClients =
+ public final RemoteCallbackList<IAccessibilityManagerClient> mUserClients =
new RemoteCallbackList<>();
public final SparseArray<AccessibilityConnectionWrapper> mInteractionConnections =
@@ -4501,6 +4635,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
public int mSoftKeyboardShowMode = 0;
+ public boolean mIsAccessibilityButtonAvailable;
+
public boolean mIsTouchExplorationEnabled;
public boolean mIsTextHighContrastEnabled;
public boolean mIsEnhancedWebAccessibilityEnabled;
@@ -4575,6 +4711,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
mIsDisplayMagnificationEnabled = false;
mIsAutoclickEnabled = false;
mSoftKeyboardShowMode = 0;
+
+ // Clear state tracked from system UI
+ mIsAccessibilityButtonAvailable = false;
}
public void destroyUiAutomationService() {
diff --git a/services/core/java/com/android/server/policy/BarController.java b/services/core/java/com/android/server/policy/BarController.java
index 6edb4d2d2ac1..7a2808146f62 100644
--- a/services/core/java/com/android/server/policy/BarController.java
+++ b/services/core/java/com/android/server/policy/BarController.java
@@ -18,6 +18,7 @@ package com.android.server.policy;
import android.app.StatusBarManager;
import android.os.Handler;
+import android.os.Message;
import android.os.SystemClock;
import android.util.Slog;
import android.view.View;
@@ -43,6 +44,8 @@ public class BarController {
private static final int TRANSLUCENT_ANIMATION_DELAY_MS = 1000;
+ private static final int MSG_NAV_BAR_VISIBILITY_CHANGED = 1;
+
protected final String mTag;
private final int mTransientFlag;
private final int mUnhideFlag;
@@ -63,6 +66,8 @@ public class BarController {
private boolean mSetUnHideFlagWhenNextTransparent;
private boolean mNoAnimationOnNextShow;
+ private OnBarVisibilityChangedListener mVisibilityChangeListener;
+
public BarController(String tag, int transientFlag, int unhideFlag, int translucentFlag,
int statusBarManagerId, int translucentWmFlag, int transparentFlag) {
mTag = "BarController." + tag;
@@ -72,7 +77,7 @@ public class BarController {
mStatusBarManagerId = statusBarManagerId;
mTranslucentWmFlag = translucentWmFlag;
mTransparentFlag = transparentFlag;
- mHandler = new Handler();
+ mHandler = new BarHandler();
}
public void setWindow(WindowState win) {
@@ -153,9 +158,18 @@ public class BarController {
mNoAnimationOnNextShow = false;
final int state = computeStateLw(wasVis, wasAnim, mWin, change);
final boolean stateChanged = updateStateLw(state);
+
+ if (change && (mVisibilityChangeListener != null)) {
+ mHandler.obtainMessage(MSG_NAV_BAR_VISIBILITY_CHANGED, show ? 1 : 0, 0).sendToTarget();
+ }
+
return change || stateChanged;
}
+ void setOnBarVisibilityChangedListener(OnBarVisibilityChangedListener listener) {
+ mVisibilityChangeListener = listener;
+ }
+
protected boolean skipAnimation() {
return false;
}
@@ -300,4 +314,22 @@ public class BarController {
pw.println(transientBarStateToString(mTransientBarState));
}
}
+
+ private class BarHandler extends Handler {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_NAV_BAR_VISIBILITY_CHANGED:
+ final boolean visible = msg.arg1 != 0;
+ if (mVisibilityChangeListener != null) {
+ mVisibilityChangeListener.onBarVisibilityChanged(visible);
+ }
+ break;
+ }
+ }
+ }
+
+ interface OnBarVisibilityChangedListener {
+ void onBarVisibilityChanged(boolean visible);
+ }
}
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 180f6c9ae963..c795676eddcf 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -990,6 +990,14 @@ public class PhoneWindowManager implements WindowManagerPolicy {
WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION,
View.NAVIGATION_BAR_TRANSPARENT);
+ private final BarController.OnBarVisibilityChangedListener mNavBarVisibilityListener =
+ new BarController.OnBarVisibilityChangedListener() {
+ @Override
+ public void onBarVisibilityChanged(boolean visible) {
+ mAccessibilityManager.notifyAccessibilityButtonAvailabilityChanged(visible);
+ }
+ };
+
private ImmersiveModeConfirmation mImmersiveModeConfirmation;
private SystemGesturesPointerEventListener mSystemGestures;
@@ -2900,6 +2908,8 @@ public class PhoneWindowManager implements WindowManagerPolicy {
}
mNavigationBar = win;
mNavigationBarController.setWindow(win);
+ mNavigationBarController.setOnBarVisibilityChangedListener(
+ mNavBarVisibilityListener);
if (DEBUG_LAYOUT) Slog.i(TAG, "NAVIGATION BAR: " + mNavigationBar);
break;
case TYPE_NAVIGATION_BAR_PANEL: