1 /* 2 * Copyright (C) 2022 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 17 package android.view.accessibility; 18 19 import android.accessibilityservice.AccessibilityGestureEvent; 20 import android.accessibilityservice.AccessibilityService; 21 import android.accessibilityservice.AccessibilityServiceInfo; 22 import android.accessibilityservice.IAccessibilityServiceClient; 23 import android.accessibilityservice.IAccessibilityServiceConnection; 24 import android.accessibilityservice.MagnificationConfig; 25 import android.annotation.ColorInt; 26 import android.annotation.NonNull; 27 import android.annotation.Nullable; 28 import android.annotation.SystemApi; 29 import android.content.ComponentName; 30 import android.content.Context; 31 import android.content.pm.ResolveInfo; 32 import android.graphics.Region; 33 import android.os.IBinder; 34 import android.os.RemoteException; 35 import android.util.Log; 36 import android.view.KeyEvent; 37 import android.view.MotionEvent; 38 import android.view.inputmethod.EditorInfo; 39 40 import com.android.internal.inputmethod.IAccessibilityInputMethodSessionCallback; 41 import com.android.internal.inputmethod.RemoteAccessibilityInputConnection; 42 43 import java.util.Collections; 44 import java.util.List; 45 import java.util.concurrent.Executor; 46 47 /** 48 * Allows a privileged app - an app with MANAGE_ACCESSIBILITY permission and SystemAPI access - to 49 * interact with the windows in the display that this proxy represents. Proxying the default display 50 * or a display that is not tracked by accessibility, such as private displays, will throw an 51 * exception. Only the real user has access to global clients like SystemUI. 52 * 53 * <p> 54 * To register and unregister a proxy, use 55 * {@link AccessibilityManager#registerDisplayProxy(AccessibilityDisplayProxy)} 56 * and {@link AccessibilityManager#unregisterDisplayProxy(AccessibilityDisplayProxy)}. If the app 57 * that has registered the proxy dies, the system will remove the proxy. 58 * 59 * <p> 60 * Avoid using the app's main thread. Proxy methods such as {@link #getWindows} and node methods 61 * like {@link AccessibilityNodeInfo#getChild(int)} will happen frequently. Node methods may also 62 * wait on the displayed app's UI thread to obtain accurate screen data. 63 * 64 * <p> 65 * To get a list of {@link AccessibilityServiceInfo}s that have populated {@link ComponentName}s and 66 * {@link ResolveInfo}s, retrieve the list using {@link #getInstalledAndEnabledServices()} after 67 * {@link #onProxyConnected()} has been called. 68 * 69 * @hide 70 */ 71 @SystemApi 72 public abstract class AccessibilityDisplayProxy { 73 private static final String LOG_TAG = "AccessibilityDisplayProxy"; 74 private static final int INVALID_CONNECTION_ID = -1; 75 76 private List<AccessibilityServiceInfo> mInstalledAndEnabledServices; 77 private Executor mExecutor; 78 private int mConnectionId = INVALID_CONNECTION_ID; 79 private int mDisplayId; 80 IAccessibilityServiceClient mServiceClient; 81 82 /** 83 * Constructs an AccessibilityDisplayProxy instance. 84 * @param displayId the id of the display to proxy. 85 * @param executor the executor used to execute proxy callbacks. 86 * @param installedAndEnabledServices the list of infos representing the installed and 87 * enabled accessibility services. 88 */ AccessibilityDisplayProxy(int displayId, @NonNull Executor executor, @NonNull List<AccessibilityServiceInfo> installedAndEnabledServices)89 public AccessibilityDisplayProxy(int displayId, @NonNull Executor executor, 90 @NonNull List<AccessibilityServiceInfo> installedAndEnabledServices) { 91 mDisplayId = displayId; 92 mExecutor = executor; 93 // Typically, the context is the Service context of an accessibility service. 94 // Context is used for ResolveInfo check, which a proxy won't have, IME input 95 // (FLAG_INPUT_METHOD_EDITOR), which the proxy doesn't need, and tracing 96 // A11yInteractionClient methods. 97 // TODO(254097475): Enable tracing, potentially without exposing Context. 98 mServiceClient = new IAccessibilityServiceClientImpl(null, mExecutor); 99 mInstalledAndEnabledServices = installedAndEnabledServices; 100 } 101 102 /** 103 * Returns the id of the display being proxy-ed. 104 */ getDisplayId()105 public int getDisplayId() { 106 return mDisplayId; 107 } 108 109 /** 110 * Handles {@link android.view.accessibility.AccessibilityEvent}s. 111 * <p> 112 * AccessibilityEvents represent changes to the UI, or what parts of the node tree have changed. 113 * AccessibilityDisplayProxy should use these to query new UI and send appropriate feedback 114 * to their users. 115 * <p> 116 * For example, a {@link AccessibilityEvent#TYPE_WINDOWS_CHANGED} indicates a change in windows, 117 * so a proxy may query {@link #getWindows} to obtain updated UI and potentially inform of a new 118 * window title. Or a proxy may emit an earcon on a 119 * {@link AccessibilityEvent#TYPE_VIEW_SCROLLED} event. 120 */ onAccessibilityEvent(@onNull AccessibilityEvent event)121 public void onAccessibilityEvent(@NonNull AccessibilityEvent event) { 122 // Default no-op 123 } 124 125 /** 126 * Handles a successful system connection after 127 * {@link AccessibilityManager#registerDisplayProxy(AccessibilityDisplayProxy)} is called. 128 * 129 * <p> 130 * At this point, querying for UI is available and {@link AccessibilityEvent}s will begin being 131 * sent. An AccessibilityDisplayProxy may instantiate core infrastructure components here. 132 */ onProxyConnected()133 public void onProxyConnected() { 134 // Default no-op 135 } 136 137 /** 138 * Handles a request to interrupt the accessibility feedback. 139 * <p> 140 * AccessibilityDisplayProxy should interrupt the accessibility activity occurring on its 141 * display. For example, a screen reader may interrupt speech. 142 * 143 * @see AccessibilityManager#interrupt() 144 * @see AccessibilityService#onInterrupt() 145 */ interrupt()146 public void interrupt() { 147 // Default no-op 148 } 149 150 /** 151 * Gets the node with focus, in this display. 152 * 153 * <p>For {@link AccessibilityNodeInfo#FOCUS_INPUT}, this returns the input-focused node in the 154 * proxy display if this display can receive unspecified input events (input that does not 155 * specify a target display.) 156 * 157 * <p>For {@link AccessibilityNodeInfo#FOCUS_ACCESSIBILITY}, this returns the 158 * accessibility-focused node in the proxy display if the display has accessibility focus. 159 * @param focusType The focus to find. One of {@link AccessibilityNodeInfo#FOCUS_INPUT} or 160 * {@link AccessibilityNodeInfo#FOCUS_ACCESSIBILITY}. 161 * @return The node info of the focused view or null. 162 163 */ 164 @Nullable findFocus(int focusType)165 public AccessibilityNodeInfo findFocus(int focusType) { 166 // TODO(264423198): Support querying the focused node of the proxy's display even if it is 167 // not the top-focused display and can't receive untargeted input events. 168 // TODO(254545943): Separate accessibility focus between proxy and phone state. 169 return AccessibilityInteractionClient.getInstance().findFocus(mConnectionId, 170 AccessibilityWindowInfo.ANY_WINDOW_ID, AccessibilityNodeInfo.ROOT_NODE_ID, 171 focusType); 172 } 173 174 /** 175 * Gets the windows of the tracked display. 176 * 177 * @see AccessibilityService#getWindows() 178 */ 179 @NonNull getWindows()180 public List<AccessibilityWindowInfo> getWindows() { 181 return AccessibilityInteractionClient.getInstance().getWindowsOnDisplay(mConnectionId, 182 mDisplayId); 183 } 184 185 /** 186 * Sets the list of {@link AccessibilityServiceInfo}s describing the services interested in the 187 * {@link AccessibilityDisplayProxy}'s display. 188 * 189 * <p>These represent accessibility features and services that are installed and running. These 190 * should not include {@link AccessibilityService}s installed on the phone. 191 * 192 * @param installedAndEnabledServices the list of installed and running accessibility services. 193 */ setInstalledAndEnabledServices( @onNull List<AccessibilityServiceInfo> installedAndEnabledServices)194 public void setInstalledAndEnabledServices( 195 @NonNull List<AccessibilityServiceInfo> installedAndEnabledServices) { 196 mInstalledAndEnabledServices = installedAndEnabledServices; 197 sendServiceInfos(); 198 } 199 200 /** 201 * Sets the {@link AccessibilityServiceInfo} for this service if the latter is 202 * properly set and there is an {@link IAccessibilityServiceConnection} to the 203 * AccessibilityManagerService. 204 */ sendServiceInfos()205 private void sendServiceInfos() { 206 IAccessibilityServiceConnection connection = 207 AccessibilityInteractionClient.getInstance().getConnection(mConnectionId); 208 if (mInstalledAndEnabledServices != null && mInstalledAndEnabledServices.size() > 0 209 && connection != null) { 210 try { 211 connection.setInstalledAndEnabledServices(mInstalledAndEnabledServices); 212 AccessibilityInteractionClient.getInstance().clearCache(mConnectionId); 213 } catch (RemoteException re) { 214 Log.w(LOG_TAG, "Error while setting AccessibilityServiceInfos", re); 215 re.rethrowFromSystemServer(); 216 } 217 } 218 mInstalledAndEnabledServices = null; 219 } 220 221 /** 222 * Gets the list of {@link AccessibilityServiceInfo}s describing the services interested in the 223 * {@link AccessibilityDisplayProxy}'s display. 224 * 225 * @return The {@link AccessibilityServiceInfo}s of interested services. 226 * @see AccessibilityServiceInfo 227 */ 228 @NonNull getInstalledAndEnabledServices()229 public final List<AccessibilityServiceInfo> getInstalledAndEnabledServices() { 230 IAccessibilityServiceConnection connection = 231 AccessibilityInteractionClient.getInstance().getConnection(mConnectionId); 232 if (connection != null) { 233 try { 234 return connection.getInstalledAndEnabledServices(); 235 } catch (RemoteException re) { 236 Log.w(LOG_TAG, "Error while getting AccessibilityServiceInfo", re); 237 re.rethrowFromSystemServer(); 238 } 239 } 240 return Collections.emptyList(); 241 } 242 243 /** 244 * Sets the strokeWidth and color of the accessibility focus rectangle. 245 * 246 * @param strokeWidth The stroke width of the rectangle in pixels. 247 * Setting this value to zero results in no focus rectangle being drawn. 248 * @param color The color of the rectangle. 249 */ setAccessibilityFocusAppearance(int strokeWidth, @ColorInt int color)250 public void setAccessibilityFocusAppearance(int strokeWidth, @ColorInt int color) { 251 IAccessibilityServiceConnection connection = 252 AccessibilityInteractionClient.getInstance().getConnection(mConnectionId); 253 if (connection != null) { 254 try { 255 connection.setFocusAppearance(strokeWidth, color); 256 } catch (RemoteException re) { 257 Log.w(LOG_TAG, "Error while setting the strokeWidth and color of the " 258 + "accessibility focus rectangle", re); 259 re.rethrowFromSystemServer(); 260 } 261 } 262 } 263 264 /** 265 * An IAccessibilityServiceClient that handles interrupts, accessibility events, and system 266 * connection. 267 */ 268 private class IAccessibilityServiceClientImpl extends 269 AccessibilityService.IAccessibilityServiceClientWrapper { 270 IAccessibilityServiceClientImpl(Context context, Executor executor)271 IAccessibilityServiceClientImpl(Context context, Executor executor) { 272 super(context, executor, new AccessibilityService.Callbacks() { 273 @Override 274 public void onAccessibilityEvent(AccessibilityEvent event) { 275 AccessibilityDisplayProxy.this.onAccessibilityEvent(event); 276 } 277 278 @Override 279 public void onInterrupt() { 280 AccessibilityDisplayProxy.this.interrupt(); 281 } 282 283 @Override 284 public void onServiceConnected() { 285 AccessibilityDisplayProxy.this.sendServiceInfos(); 286 AccessibilityDisplayProxy.this.onProxyConnected(); 287 } 288 289 @Override 290 public void init(int connectionId, IBinder windowToken) { 291 mConnectionId = connectionId; 292 } 293 294 @Override 295 public boolean onGesture(AccessibilityGestureEvent gestureInfo) { 296 return false; 297 } 298 299 @Override 300 public boolean onKeyEvent(KeyEvent event) { 301 return false; 302 } 303 304 @Override 305 public void onMagnificationChanged(int displayId, @NonNull Region region, 306 MagnificationConfig config) { 307 } 308 309 @Override 310 public void onMotionEvent(MotionEvent event) { 311 } 312 313 @Override 314 public void onTouchStateChanged(int displayId, int state) { 315 } 316 317 @Override 318 public void onSoftKeyboardShowModeChanged(int showMode) { 319 } 320 321 @Override 322 public void onPerformGestureResult(int sequence, boolean completedSuccessfully) { 323 } 324 325 @Override 326 public void onFingerprintCapturingGesturesChanged(boolean active) { 327 } 328 329 @Override 330 public void onFingerprintGesture(int gesture) { 331 } 332 333 @Override 334 public void onAccessibilityButtonClicked(int displayId) { 335 } 336 337 @Override 338 public void onAccessibilityButtonAvailabilityChanged(boolean available) { 339 } 340 341 @Override 342 public void onSystemActionsChanged() { 343 } 344 345 @Override 346 public void createImeSession(IAccessibilityInputMethodSessionCallback callback) { 347 } 348 349 @Override 350 public void startInput(@Nullable RemoteAccessibilityInputConnection inputConnection, 351 @NonNull EditorInfo editorInfo, boolean restarting) { 352 } 353 }); 354 } 355 } 356 } 357