AccessibilityButtonController.java revision a338b51e0232daadbee7fe9dd861cd78e9e6d9a9
1/* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package android.accessibilityservice; 18 19import android.annotation.NonNull; 20import android.annotation.Nullable; 21import android.os.Handler; 22import android.os.RemoteException; 23import android.util.ArrayMap; 24import android.util.Slog; 25 26import com.android.internal.util.Preconditions; 27 28/** 29 * Controller for the accessibility button within the system's navigation area 30 * <p> 31 * This class may be used to query the accessibility button's state and register 32 * callbacks for interactions with and state changes to the accessibility button when 33 * {@link AccessibilityServiceInfo#FLAG_REQUEST_ACCESSIBILITY_BUTTON} is set. 34 * </p> 35 * <p> 36 * <strong>Note:</strong> This class and 37 * {@link AccessibilityServiceInfo#FLAG_REQUEST_ACCESSIBILITY_BUTTON} should not be used as 38 * the sole means for offering functionality to users via an {@link AccessibilityService}. 39 * Some device implementations may choose not to provide a software-rendered system 40 * navigation area, making this affordance permanently unavailable. 41 * </p> 42 * <p> 43 * <strong>Note:</strong> On device implementations where the accessibility button is 44 * supported, it may not be available at all times, such as when a foreground application uses 45 * {@link android.view.View#SYSTEM_UI_FLAG_HIDE_NAVIGATION}. A user may also choose to assign 46 * this button to another accessibility service or feature. In each of these cases, a 47 * registered {@link AccessibilityButtonCallback}'s 48 * {@link AccessibilityButtonCallback#onAvailabilityChanged(AccessibilityButtonController, boolean)} 49 * method will be invoked to provide notifications of changes in the accessibility button's 50 * availability to the registering service. 51 * </p> 52 */ 53public final class AccessibilityButtonController { 54 private static final String LOG_TAG = "A11yButtonController"; 55 56 private final IAccessibilityServiceConnection mServiceConnection; 57 private final Object mLock; 58 private ArrayMap<AccessibilityButtonCallback, Handler> mCallbacks; 59 60 AccessibilityButtonController(@NonNull IAccessibilityServiceConnection serviceConnection) { 61 mServiceConnection = serviceConnection; 62 mLock = new Object(); 63 } 64 65 /** 66 * Retrieves whether the accessibility button in the system's navigation area is 67 * available to the calling service. 68 * <p> 69 * <strong>Note:</strong> If the service is not yet connected (e.g. 70 * {@link AccessibilityService#onServiceConnected()} has not yet been called) or the 71 * service has been disconnected, this method will have no effect and return {@code false}. 72 * </p> 73 * 74 * @return {@code true} if the accessibility button in the system's navigation area is 75 * available to the calling service, {@code false} otherwise 76 */ 77 public boolean isAccessibilityButtonAvailable() { 78 try { 79 return mServiceConnection.isAccessibilityButtonAvailable(); 80 } catch (RemoteException re) { 81 Slog.w(LOG_TAG, "Failed to get accessibility button availability.", re); 82 re.rethrowFromSystemServer(); 83 return false; 84 } 85 } 86 87 /** 88 * Registers the provided {@link AccessibilityButtonCallback} for interaction and state 89 * changes callbacks related to the accessibility button. 90 * 91 * @param callback the callback to add, must be non-null 92 */ 93 public void registerAccessibilityButtonCallback(@NonNull AccessibilityButtonCallback callback) { 94 registerAccessibilityButtonCallback(callback, new Handler()); 95 } 96 97 /** 98 * Registers the provided {@link AccessibilityButtonCallback} for interaction and state 99 * change callbacks related to the accessibility button. The callback will occur on the 100 * specified {@link Handler}'s thread, or on the services's main thread if the handler is 101 * {@code null}. 102 * 103 * @param callback the callback to add, must be non-null 104 * @param handler the handler on which the callback should execute, must be non-null 105 */ 106 public void registerAccessibilityButtonCallback(@NonNull AccessibilityButtonCallback callback, 107 @NonNull Handler handler) { 108 Preconditions.checkNotNull(callback); 109 Preconditions.checkNotNull(handler); 110 synchronized (mLock) { 111 if (mCallbacks == null) { 112 mCallbacks = new ArrayMap<>(); 113 } 114 115 mCallbacks.put(callback, handler); 116 } 117 } 118 119 /** 120 * Unregisters the provided {@link AccessibilityButtonCallback} for interaction and state 121 * change callbacks related to the accessibility button. 122 * 123 * @param callback the callback to remove, must be non-null 124 */ 125 public void unregisterAccessibilityButtonCallback( 126 @NonNull AccessibilityButtonCallback callback) { 127 Preconditions.checkNotNull(callback); 128 synchronized (mLock) { 129 if (mCallbacks == null) { 130 return; 131 } 132 133 final int keyIndex = mCallbacks.indexOfKey(callback); 134 final boolean hasKey = keyIndex >= 0; 135 if (hasKey) { 136 mCallbacks.removeAt(keyIndex); 137 } 138 } 139 } 140 141 /** 142 * Dispatches the accessibility button click to any registered callbacks. This should 143 * be called on the service's main thread. 144 */ 145 void dispatchAccessibilityButtonClicked() { 146 final ArrayMap<AccessibilityButtonCallback, Handler> entries; 147 synchronized (mLock) { 148 if (mCallbacks == null || mCallbacks.isEmpty()) { 149 Slog.w(LOG_TAG, "Received accessibility button click with no callbacks!"); 150 return; 151 } 152 153 // Callbacks may remove themselves. Perform a shallow copy to avoid concurrent 154 // modification. 155 entries = new ArrayMap<>(mCallbacks); 156 } 157 158 for (int i = 0, count = entries.size(); i < count; i++) { 159 final AccessibilityButtonCallback callback = entries.keyAt(i); 160 final Handler handler = entries.valueAt(i); 161 handler.post(() -> callback.onClicked(this)); 162 } 163 } 164 165 /** 166 * Dispatches the accessibility button availability changes to any registered callbacks. 167 * This should be called on the service's main thread. 168 */ 169 void dispatchAccessibilityButtonAvailabilityChanged(boolean available) { 170 final ArrayMap<AccessibilityButtonCallback, Handler> entries; 171 synchronized (mLock) { 172 if (mCallbacks == null || mCallbacks.isEmpty()) { 173 Slog.w(LOG_TAG, 174 "Received accessibility button availability change with no callbacks!"); 175 return; 176 } 177 178 // Callbacks may remove themselves. Perform a shallow copy to avoid concurrent 179 // modification. 180 entries = new ArrayMap<>(mCallbacks); 181 } 182 183 for (int i = 0, count = entries.size(); i < count; i++) { 184 final AccessibilityButtonCallback callback = entries.keyAt(i); 185 final Handler handler = entries.valueAt(i); 186 handler.post(() -> callback.onAvailabilityChanged(this, available)); 187 } 188 } 189 190 /** 191 * Callback for interaction with and changes to state of the accessibility button 192 * within the system's navigation area. 193 */ 194 public static abstract class AccessibilityButtonCallback { 195 196 /** 197 * Called when the accessibility button in the system's navigation area is clicked. 198 * 199 * @param controller the controller used to register for this callback 200 */ 201 public void onClicked(AccessibilityButtonController controller) {} 202 203 /** 204 * Called when the availability of the accessibility button in the system's 205 * navigation area has changed. The accessibility button may become unavailable 206 * because the device shopped showing the button, the button was assigned to another 207 * service, or for other reasons. 208 * 209 * @param controller the controller used to register for this callback 210 * @param available {@code true} if the accessibility button is available to this 211 * service, {@code false} otherwise 212 */ 213 public void onAvailabilityChanged(AccessibilityButtonController controller, 214 boolean available) { 215 } 216 } 217} 218