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