AccessibilityService.java revision 214fb68767502f5fede643a062c1dc5975d75b27
1/*
2 * Copyright (C) 2009 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.app.Service;
22import android.content.Context;
23import android.content.Intent;
24import android.graphics.Region;
25import android.os.Handler;
26import android.os.IBinder;
27import android.os.Looper;
28import android.os.Message;
29import android.os.RemoteException;
30import android.util.ArrayMap;
31import android.util.Log;
32import android.util.Slog;
33import android.view.KeyEvent;
34import android.view.WindowManager;
35import android.view.WindowManagerImpl;
36import android.view.accessibility.AccessibilityEvent;
37import android.view.accessibility.AccessibilityInteractionClient;
38import android.view.accessibility.AccessibilityNodeInfo;
39import android.view.accessibility.AccessibilityWindowInfo;
40
41import com.android.internal.os.HandlerCaller;
42import com.android.internal.os.SomeArgs;
43
44import java.util.ArrayList;
45import java.util.List;
46import java.util.Map.Entry;
47import java.util.Set;
48
49/**
50 * An accessibility service runs in the background and receives callbacks by the system
51 * when {@link AccessibilityEvent}s are fired. Such events denote some state transition
52 * in the user interface, for example, the focus has changed, a button has been clicked,
53 * etc. Such a service can optionally request the capability for querying the content
54 * of the active window. Development of an accessibility service requires extending this
55 * class and implementing its abstract methods.
56 *
57 * <div class="special reference">
58 * <h3>Developer Guides</h3>
59 * <p>For more information about creating AccessibilityServices, read the
60 * <a href="{@docRoot}guide/topics/ui/accessibility/index.html">Accessibility</a>
61 * developer guide.</p>
62 * </div>
63 *
64 * <h3>Lifecycle</h3>
65 * <p>
66 * The lifecycle of an accessibility service is managed exclusively by the system and
67 * follows the established service life cycle. Additionally, starting or stopping an
68 * accessibility service is triggered exclusively by an explicit user action through
69 * enabling or disabling it in the device settings. After the system binds to a service it
70 * calls {@link AccessibilityService#onServiceConnected()}. This method can be
71 * overriden by clients that want to perform post binding setup.
72 * </p>
73 * <h3>Declaration</h3>
74 * <p>
75 * An accessibility is declared as any other service in an AndroidManifest.xml but it
76 * must also specify that it handles the "android.accessibilityservice.AccessibilityService"
77 * {@link android.content.Intent}. Failure to declare this intent will cause the system to
78 * ignore the accessibility service. Additionally an accessibility service must request the
79 * {@link android.Manifest.permission#BIND_ACCESSIBILITY_SERVICE} permission to ensure
80 * that only the system
81 * can bind to it. Failure to declare this intent will cause the system to ignore the
82 * accessibility service. Following is an example declaration:
83 * </p>
84 * <pre> &lt;service android:name=".MyAccessibilityService"
85 *         android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"&gt;
86 *     &lt;intent-filter&gt;
87 *         &lt;action android:name="android.accessibilityservice.AccessibilityService" /&gt;
88 *     &lt;/intent-filter&gt;
89 *     . . .
90 * &lt;/service&gt;</pre>
91 * <h3>Configuration</h3>
92 * <p>
93 * An accessibility service can be configured to receive specific types of accessibility events,
94 * listen only to specific packages, get events from each type only once in a given time frame,
95 * retrieve window content, specify a settings activity, etc.
96 * </p>
97 * <p>
98 * There are two approaches for configuring an accessibility service:
99 * </p>
100 * <ul>
101 * <li>
102 * Providing a {@link #SERVICE_META_DATA meta-data} entry in the manifest when declaring
103 * the service. A service declaration with a meta-data tag is presented below:
104 * <pre> &lt;service android:name=".MyAccessibilityService"&gt;
105 *     &lt;intent-filter&gt;
106 *         &lt;action android:name="android.accessibilityservice.AccessibilityService" /&gt;
107 *     &lt;/intent-filter&gt;
108 *     &lt;meta-data android:name="android.accessibilityservice" android:resource="@xml/accessibilityservice" /&gt;
109 * &lt;/service&gt;</pre>
110 * <p class="note">
111 * <strong>Note:</strong> This approach enables setting all properties.
112 * </p>
113 * <p>
114 * For more details refer to {@link #SERVICE_META_DATA} and
115 * <code>&lt;{@link android.R.styleable#AccessibilityService accessibility-service}&gt;</code>.
116 * </p>
117 * </li>
118 * <li>
119 * Calling {@link AccessibilityService#setServiceInfo(AccessibilityServiceInfo)}. Note
120 * that this method can be called any time to dynamically change the service configuration.
121 * <p class="note">
122 * <strong>Note:</strong> This approach enables setting only dynamically configurable properties:
123 * {@link AccessibilityServiceInfo#eventTypes},
124 * {@link AccessibilityServiceInfo#feedbackType},
125 * {@link AccessibilityServiceInfo#flags},
126 * {@link AccessibilityServiceInfo#notificationTimeout},
127 * {@link AccessibilityServiceInfo#packageNames}
128 * </p>
129 * <p>
130 * For more details refer to {@link AccessibilityServiceInfo}.
131 * </p>
132 * </li>
133 * </ul>
134 * <h3>Retrieving window content</h3>
135 * <p>
136 * A service can specify in its declaration that it can retrieve the active window
137 * content which is represented as a tree of {@link AccessibilityNodeInfo}. Note that
138 * declaring this capability requires that the service declares its configuration via
139 * an XML resource referenced by {@link #SERVICE_META_DATA}.
140 * </p>
141 * <p>
142 * For security purposes an accessibility service can retrieve only the content of the
143 * currently active window. The currently active window is defined as the window from
144 * which was fired the last event of the following types:
145 * {@link AccessibilityEvent#TYPE_WINDOW_STATE_CHANGED},
146 * {@link AccessibilityEvent#TYPE_VIEW_HOVER_ENTER},
147 * {@link AccessibilityEvent#TYPE_VIEW_HOVER_EXIT},
148 * In other words, the last window that was shown or the last window that the user has touched
149 * during touch exploration.
150 * </p>
151 * <p>
152 * The entry point for retrieving window content is through calling
153 * {@link AccessibilityEvent#getSource() AccessibilityEvent.getSource()} of the last received
154 * event of the above types or a previous event from the same window
155 * (see {@link AccessibilityEvent#getWindowId() AccessibilityEvent.getWindowId()}). Invoking
156 * this method will return an {@link AccessibilityNodeInfo} that can be used to traverse the
157 * window content which represented as a tree of such objects.
158 * </p>
159 * <p class="note">
160 * <strong>Note</strong> An accessibility service may have requested to be notified for
161 * a subset of the event types, thus be unaware that the active window has changed. Therefore
162 * accessibility service that would like to retrieve window content should:
163 * <ul>
164 * <li>
165 * Register for all event types with no notification timeout and keep track for the active
166 * window by calling {@link AccessibilityEvent#getWindowId()} of the last received event and
167 * compare this with the {@link AccessibilityNodeInfo#getWindowId()} before calling retrieval
168 * methods on the latter.
169 * </li>
170 * <li>
171 * Prepare that a retrieval method on {@link AccessibilityNodeInfo} may fail since the
172 * active window has changed and the service did not get the accessibility event yet. Note
173 * that it is possible to have a retrieval method failing even adopting the strategy
174 * specified in the previous bullet because the accessibility event dispatch is asynchronous
175 * and crosses process boundaries.
176 * </li>
177 * </ul>
178 * </p>
179 * <h3>Notification strategy</h3>
180 * <p>
181 * For each feedback type only one accessibility service is notified. Services are notified
182 * in the order of registration. Hence, if two services are registered for the same
183 * feedback type in the same package the first one wins. It is possible however, to
184 * register a service as the default one for a given feedback type. In such a case this
185 * service is invoked if no other service was interested in the event. In other words, default
186 * services do not compete with other services and are notified last regardless of the
187 * registration order. This enables "generic" accessibility services that work reasonably
188 * well with most applications to coexist with "polished" ones that are targeted for
189 * specific applications.
190 * </p>
191 * <p class="note">
192 * <strong>Note:</strong> The event notification timeout is useful to avoid propagating
193 * events to the client too frequently since this is accomplished via an expensive
194 * interprocess call. One can think of the timeout as a criteria to determine when
195 * event generation has settled down.</p>
196 * <h3>Event types</h3>
197 * <ul>
198 * <li>{@link AccessibilityEvent#TYPE_VIEW_CLICKED}</li>
199 * <li>{@link AccessibilityEvent#TYPE_VIEW_LONG_CLICKED}</li>
200 * <li>{@link AccessibilityEvent#TYPE_VIEW_FOCUSED}</li>
201 * <li>{@link AccessibilityEvent#TYPE_VIEW_SELECTED}</li>
202 * <li>{@link AccessibilityEvent#TYPE_VIEW_TEXT_CHANGED}</li>
203 * <li>{@link AccessibilityEvent#TYPE_WINDOW_STATE_CHANGED}</li>
204 * <li>{@link AccessibilityEvent#TYPE_NOTIFICATION_STATE_CHANGED}</li>
205 * <li>{@link AccessibilityEvent#TYPE_TOUCH_EXPLORATION_GESTURE_START}</li>
206 * <li>{@link AccessibilityEvent#TYPE_TOUCH_EXPLORATION_GESTURE_END}</li>
207 * <li>{@link AccessibilityEvent#TYPE_VIEW_HOVER_ENTER}</li>
208 * <li>{@link AccessibilityEvent#TYPE_VIEW_HOVER_EXIT}</li>
209 * <li>{@link AccessibilityEvent#TYPE_VIEW_SCROLLED}</li>
210 * <li>{@link AccessibilityEvent#TYPE_VIEW_TEXT_SELECTION_CHANGED}</li>
211 * <li>{@link AccessibilityEvent#TYPE_WINDOW_CONTENT_CHANGED}</li>
212 * <li>{@link AccessibilityEvent#TYPE_ANNOUNCEMENT}</li>
213 * <li>{@link AccessibilityEvent#TYPE_GESTURE_DETECTION_START}</li>
214 * <li>{@link AccessibilityEvent#TYPE_GESTURE_DETECTION_END}</li>
215 * <li>{@link AccessibilityEvent#TYPE_TOUCH_INTERACTION_START}</li>
216 * <li>{@link AccessibilityEvent#TYPE_TOUCH_INTERACTION_END}</li>
217 * <li>{@link AccessibilityEvent#TYPE_VIEW_ACCESSIBILITY_FOCUSED}</li>
218 * <li>{@link AccessibilityEvent#TYPE_WINDOWS_CHANGED}</li>
219 * <li>{@link AccessibilityEvent#TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED}</li>
220 * </ul>
221 * <h3>Feedback types</h3>
222 * <ul>
223 * <li>{@link AccessibilityServiceInfo#FEEDBACK_AUDIBLE}</li>
224 * <li>{@link AccessibilityServiceInfo#FEEDBACK_HAPTIC}</li>
225 * <li>{@link AccessibilityServiceInfo#FEEDBACK_AUDIBLE}</li>
226 * <li>{@link AccessibilityServiceInfo#FEEDBACK_VISUAL}</li>
227 * <li>{@link AccessibilityServiceInfo#FEEDBACK_GENERIC}</li>
228 * <li>{@link AccessibilityServiceInfo#FEEDBACK_BRAILLE}</li>
229 * </ul>
230 * @see AccessibilityEvent
231 * @see AccessibilityServiceInfo
232 * @see android.view.accessibility.AccessibilityManager
233 */
234public abstract class AccessibilityService extends Service {
235
236    /**
237     * The user has performed a swipe up gesture on the touch screen.
238     */
239    public static final int GESTURE_SWIPE_UP = 1;
240
241    /**
242     * The user has performed a swipe down gesture on the touch screen.
243     */
244    public static final int GESTURE_SWIPE_DOWN = 2;
245
246    /**
247     * The user has performed a swipe left gesture on the touch screen.
248     */
249    public static final int GESTURE_SWIPE_LEFT = 3;
250
251    /**
252     * The user has performed a swipe right gesture on the touch screen.
253     */
254    public static final int GESTURE_SWIPE_RIGHT = 4;
255
256    /**
257     * The user has performed a swipe left and right gesture on the touch screen.
258     */
259    public static final int GESTURE_SWIPE_LEFT_AND_RIGHT = 5;
260
261    /**
262     * The user has performed a swipe right and left gesture on the touch screen.
263     */
264    public static final int GESTURE_SWIPE_RIGHT_AND_LEFT = 6;
265
266    /**
267     * The user has performed a swipe up and down gesture on the touch screen.
268     */
269    public static final int GESTURE_SWIPE_UP_AND_DOWN = 7;
270
271    /**
272     * The user has performed a swipe down and up gesture on the touch screen.
273     */
274    public static final int GESTURE_SWIPE_DOWN_AND_UP = 8;
275
276    /**
277     * The user has performed a left and up gesture on the touch screen.
278     */
279    public static final int GESTURE_SWIPE_LEFT_AND_UP = 9;
280
281    /**
282     * The user has performed a left and down gesture on the touch screen.
283     */
284    public static final int GESTURE_SWIPE_LEFT_AND_DOWN = 10;
285
286    /**
287     * The user has performed a right and up gesture on the touch screen.
288     */
289    public static final int GESTURE_SWIPE_RIGHT_AND_UP = 11;
290
291    /**
292     * The user has performed a right and down gesture on the touch screen.
293     */
294    public static final int GESTURE_SWIPE_RIGHT_AND_DOWN = 12;
295
296    /**
297     * The user has performed an up and left gesture on the touch screen.
298     */
299    public static final int GESTURE_SWIPE_UP_AND_LEFT = 13;
300
301    /**
302     * The user has performed an up and right gesture on the touch screen.
303     */
304    public static final int GESTURE_SWIPE_UP_AND_RIGHT = 14;
305
306    /**
307     * The user has performed an down and left gesture on the touch screen.
308     */
309    public static final int GESTURE_SWIPE_DOWN_AND_LEFT = 15;
310
311    /**
312     * The user has performed an down and right gesture on the touch screen.
313     */
314    public static final int GESTURE_SWIPE_DOWN_AND_RIGHT = 16;
315
316    /**
317     * The {@link Intent} that must be declared as handled by the service.
318     */
319    public static final String SERVICE_INTERFACE =
320        "android.accessibilityservice.AccessibilityService";
321
322    /**
323     * Name under which an AccessibilityService component publishes information
324     * about itself. This meta-data must reference an XML resource containing an
325     * <code>&lt;{@link android.R.styleable#AccessibilityService accessibility-service}&gt;</code>
326     * tag. This is a a sample XML file configuring an accessibility service:
327     * <pre> &lt;accessibility-service
328     *     android:accessibilityEventTypes="typeViewClicked|typeViewFocused"
329     *     android:packageNames="foo.bar, foo.baz"
330     *     android:accessibilityFeedbackType="feedbackSpoken"
331     *     android:notificationTimeout="100"
332     *     android:accessibilityFlags="flagDefault"
333     *     android:settingsActivity="foo.bar.TestBackActivity"
334     *     android:canRetrieveWindowContent="true"
335     *     android:canRequestTouchExplorationMode="true"
336     *     android:canRequestEnhancedWebAccessibility="true"
337     *     . . .
338     * /&gt;</pre>
339     */
340    public static final String SERVICE_META_DATA = "android.accessibilityservice";
341
342    /**
343     * Action to go back.
344     */
345    public static final int GLOBAL_ACTION_BACK = 1;
346
347    /**
348     * Action to go home.
349     */
350    public static final int GLOBAL_ACTION_HOME = 2;
351
352    /**
353     * Action to open the recent apps.
354     */
355    public static final int GLOBAL_ACTION_RECENTS = 3;
356
357    /**
358     * Action to open the notifications.
359     */
360    public static final int GLOBAL_ACTION_NOTIFICATIONS = 4;
361
362    /**
363     * Action to open the quick settings.
364     */
365    public static final int GLOBAL_ACTION_QUICK_SETTINGS = 5;
366
367    /**
368     * Action to open the power long-press dialog.
369     */
370    public static final int GLOBAL_ACTION_POWER_DIALOG = 6;
371
372    private static final String LOG_TAG = "AccessibilityService";
373
374    /**
375     * @hide
376     */
377    public interface Callbacks {
378        public void onAccessibilityEvent(AccessibilityEvent event);
379        public void onInterrupt();
380        public void onServiceConnected();
381        public void init(int connectionId, IBinder windowToken);
382        public boolean onGesture(int gestureId);
383        public boolean onKeyEvent(KeyEvent event);
384        public void onMagnificationChanged(@NonNull Region region,
385                float scale, float centerX, float centerY);
386    }
387
388    private int mConnectionId;
389
390    private AccessibilityServiceInfo mInfo;
391
392    private IBinder mWindowToken;
393
394    private WindowManager mWindowManager;
395
396    private MagnificationController mMagnificationController;
397
398    /**
399     * Callback for {@link android.view.accessibility.AccessibilityEvent}s.
400     *
401     * @param event An event.
402     */
403    public abstract void onAccessibilityEvent(AccessibilityEvent event);
404
405    /**
406     * Callback for interrupting the accessibility feedback.
407     */
408    public abstract void onInterrupt();
409
410    /**
411     * Dispatches service connection to internal components first, then the
412     * client code.
413     */
414    private void dispatchServiceConnected() {
415        if (mMagnificationController != null) {
416            mMagnificationController.onServiceConnected();
417        }
418
419        // The client gets to handle service connection last, after we've set
420        // up any state upon which their code may rely.
421        onServiceConnected();
422    }
423
424    /**
425     * This method is a part of the {@link AccessibilityService} lifecycle and is
426     * called after the system has successfully bound to the service. If is
427     * convenient to use this method for setting the {@link AccessibilityServiceInfo}.
428     *
429     * @see AccessibilityServiceInfo
430     * @see #setServiceInfo(AccessibilityServiceInfo)
431     */
432    protected void onServiceConnected() {
433
434    }
435
436    /**
437     * Called by the system when the user performs a specific gesture on the
438     * touch screen.
439     *
440     * <strong>Note:</strong> To receive gestures an accessibility service must
441     * request that the device is in touch exploration mode by setting the
442     * {@link android.accessibilityservice.AccessibilityServiceInfo#FLAG_REQUEST_TOUCH_EXPLORATION_MODE}
443     * flag.
444     *
445     * @param gestureId The unique id of the performed gesture.
446     *
447     * @return Whether the gesture was handled.
448     *
449     * @see #GESTURE_SWIPE_UP
450     * @see #GESTURE_SWIPE_UP_AND_LEFT
451     * @see #GESTURE_SWIPE_UP_AND_DOWN
452     * @see #GESTURE_SWIPE_UP_AND_RIGHT
453     * @see #GESTURE_SWIPE_DOWN
454     * @see #GESTURE_SWIPE_DOWN_AND_LEFT
455     * @see #GESTURE_SWIPE_DOWN_AND_UP
456     * @see #GESTURE_SWIPE_DOWN_AND_RIGHT
457     * @see #GESTURE_SWIPE_LEFT
458     * @see #GESTURE_SWIPE_LEFT_AND_UP
459     * @see #GESTURE_SWIPE_LEFT_AND_RIGHT
460     * @see #GESTURE_SWIPE_LEFT_AND_DOWN
461     * @see #GESTURE_SWIPE_RIGHT
462     * @see #GESTURE_SWIPE_RIGHT_AND_UP
463     * @see #GESTURE_SWIPE_RIGHT_AND_LEFT
464     * @see #GESTURE_SWIPE_RIGHT_AND_DOWN
465     */
466    protected boolean onGesture(int gestureId) {
467        return false;
468    }
469
470    /**
471     * Callback that allows an accessibility service to observe the key events
472     * before they are passed to the rest of the system. This means that the events
473     * are first delivered here before they are passed to the device policy, the
474     * input method, or applications.
475     * <p>
476     * <strong>Note:</strong> It is important that key events are handled in such
477     * a way that the event stream that would be passed to the rest of the system
478     * is well-formed. For example, handling the down event but not the up event
479     * and vice versa would generate an inconsistent event stream.
480     * </p>
481     * <p>
482     * <strong>Note:</strong> The key events delivered in this method are copies
483     * and modifying them will have no effect on the events that will be passed
484     * to the system. This method is intended to perform purely filtering
485     * functionality.
486     * <p>
487     *
488     * @param event The event to be processed.
489     * @return If true then the event will be consumed and not delivered to
490     *         applications, otherwise it will be delivered as usual.
491     */
492    protected boolean onKeyEvent(KeyEvent event) {
493        return false;
494    }
495
496    /**
497     * Gets the windows on the screen. This method returns only the windows
498     * that a sighted user can interact with, as opposed to all windows.
499     * For example, if there is a modal dialog shown and the user cannot touch
500     * anything behind it, then only the modal window will be reported
501     * (assuming it is the top one). For convenience the returned windows
502     * are ordered in a descending layer order, which is the windows that
503     * are higher in the Z-order are reported first. Since the user can always
504     * interact with the window that has input focus by typing, the focused
505     * window is always returned (even if covered by a modal window).
506     * <p>
507     * <strong>Note:</strong> In order to access the windows your service has
508     * to declare the capability to retrieve window content by setting the
509     * {@link android.R.styleable#AccessibilityService_canRetrieveWindowContent}
510     * property in its meta-data. For details refer to {@link #SERVICE_META_DATA}.
511     * Also the service has to opt-in to retrieve the interactive windows by
512     * setting the {@link AccessibilityServiceInfo#FLAG_RETRIEVE_INTERACTIVE_WINDOWS}
513     * flag.
514     * </p>
515     *
516     * @return The windows if there are windows and the service is can retrieve
517     *         them, otherwise an empty list.
518     */
519    public List<AccessibilityWindowInfo> getWindows() {
520        return AccessibilityInteractionClient.getInstance().getWindows(mConnectionId);
521    }
522
523    /**
524     * Gets the root node in the currently active window if this service
525     * can retrieve window content. The active window is the one that the user
526     * is currently touching or the window with input focus, if the user is not
527     * touching any window.
528     * <p>
529     * <strong>Note:</strong> In order to access the root node your service has
530     * to declare the capability to retrieve window content by setting the
531     * {@link android.R.styleable#AccessibilityService_canRetrieveWindowContent}
532     * property in its meta-data. For details refer to {@link #SERVICE_META_DATA}.
533     * </p>
534     *
535     * @return The root node if this service can retrieve window content.
536     */
537    public AccessibilityNodeInfo getRootInActiveWindow() {
538        return AccessibilityInteractionClient.getInstance().getRootInActiveWindow(mConnectionId);
539    }
540
541    /**
542     * Returns the magnification controller, which may be used to query and
543     * modify the state of display magnification.
544     * <p>
545     * <strong>Note:</strong> In order to control magnification, your service
546     * must declare the capability by setting the
547     * {@link android.R.styleable#AccessibilityService_canControlMagnification}
548     * property in its meta-data. For more information, see
549     * {@link #SERVICE_META_DATA}.
550     *
551     * @return the magnification controller
552     */
553    @NonNull
554    public final MagnificationController getMagnificationController() {
555        if (mMagnificationController == null) {
556            mMagnificationController = new MagnificationController(this);
557        }
558        return mMagnificationController;
559    }
560
561    private void onMagnificationChanged(@NonNull Region region, float scale,
562            float centerX, float centerY) {
563        if (mMagnificationController != null) {
564            mMagnificationController.dispatchMagnificationChanged(
565                    region, scale, centerX, centerY);
566        }
567    }
568
569    /**
570     * Used to control and query the state of display magnification.
571     */
572    public static final class MagnificationController {
573        private final AccessibilityService mService;
574
575        /**
576         * Map of listeners to their handlers. Lazily created when adding the
577         * first magnification listener.
578         */
579        private ArrayMap<OnMagnificationChangedListener, Handler> mListeners;
580
581        MagnificationController(@NonNull AccessibilityService service) {
582            mService = service;
583        }
584
585        /**
586         * Called when the service is connected.
587         */
588        void onServiceConnected() {
589            if (mListeners != null && !mListeners.isEmpty()) {
590                setMagnificationCallbackEnabled(true);
591            }
592        }
593
594        /**
595         * Adds the specified change listener to the list of magnification
596         * change listeners. The callback will occur on the service's main
597         * thread.
598         *
599         * @param listener the listener to add, must be non-{@code null}
600         */
601        public void addListener(@NonNull OnMagnificationChangedListener listener) {
602            addListener(listener, null);
603        }
604
605        /**
606         * Adds the specified change listener to the list of magnification
607         * change listeners. The callback will occur on the specified
608         * {@link Handler}'s thread, or on the service's main thread if the
609         * handler is {@code null}.
610         *
611         * @param listener the listener to add, must be non-null
612         * @param handler the handler on which the callback should execute, or
613         *        {@code null} to execute on the service's main thread
614         */
615        public void addListener(@NonNull OnMagnificationChangedListener listener,
616                @Nullable Handler handler) {
617            if (mListeners == null) {
618                mListeners = new ArrayMap<>();
619            }
620
621            final boolean shouldEnableCallback = mListeners.isEmpty();
622            mListeners.put(listener, handler);
623
624            if (shouldEnableCallback) {
625                // This may fail if the service is not connected yet, but if we
626                // still have listeners when it connects then we can try again.
627                setMagnificationCallbackEnabled(true);
628            }
629        }
630
631        /**
632         * Removes all instances of the specified change listener from the list
633         * of magnification change listeners.
634         *
635         * @param listener the listener to remove, must be non-null
636         * @return {@code true} if at least one instance of the listener was
637         *         removed
638         */
639        public boolean removeListener(@NonNull OnMagnificationChangedListener listener) {
640            if (mListeners == null) {
641                return false;
642            }
643
644            final int keyIndex = mListeners.indexOfKey(listener);
645            final boolean hasKey = keyIndex >= 0;
646            if (hasKey) {
647                mListeners.removeAt(keyIndex);
648            }
649
650            if (hasKey && mListeners.isEmpty()) {
651                // We just removed the last listener, so we don't need
652                // callbacks from the service anymore.
653                setMagnificationCallbackEnabled(false);
654            }
655
656            return hasKey;
657        }
658
659        private void setMagnificationCallbackEnabled(boolean enabled) {
660            final IAccessibilityServiceConnection connection =
661                    AccessibilityInteractionClient.getInstance().getConnection(
662                            mService.mConnectionId);
663            if (connection != null) {
664                try {
665                    connection.setMagnificationCallbackEnabled(enabled);
666                } catch (RemoteException re) {
667                    throw new RuntimeException(re);
668                }
669            }
670        }
671
672        /**
673         * Dispatches magnification changes to any registered listeners. This
674         * should be called on the service's main thread.
675         */
676        void dispatchMagnificationChanged(final @NonNull Region region, final float scale,
677                final float centerX, final float centerY) {
678            if (mListeners == null || mListeners.isEmpty()) {
679                Slog.d(LOG_TAG, "Received magnification changed "
680                        + "callback with no listeners registered!");
681                setMagnificationCallbackEnabled(false);
682                return;
683            }
684
685            // Listeners may remove themselves. Perform a shallow copy to avoid
686            // concurrent modification.
687            final ArrayMap<OnMagnificationChangedListener, Handler> entries =
688                    new ArrayMap<>(mListeners);
689
690            for (int i = 0, count = entries.size(); i < count; i++) {
691                final OnMagnificationChangedListener listener = entries.keyAt(i);
692                final Handler handler = entries.valueAt(i);
693                if (handler != null) {
694                    handler.post(new Runnable() {
695                        @Override
696                        public void run() {
697                            listener.onMagnificationChanged(MagnificationController.this,
698                                    region, scale, centerX, centerY);
699                        }
700                    });
701                } else {
702                    // We're already on the main thread, just run the listener.
703                    listener.onMagnificationChanged(this, region, scale, centerX, centerY);
704                }
705            }
706        }
707
708        /**
709         * Returns the current magnification scale.
710         * <p>
711         * <strong>Note:</strong> If the service is not yet connected (e.g.
712         * {@link AccessibilityService#onServiceConnected()} has not yet been
713         * called) or the service has been disconnected, this method will
714         * return a default value of {@code 1.0f}.
715         *
716         * @return the current magnification scale
717         */
718        public float getScale() {
719            final IAccessibilityServiceConnection connection =
720                    AccessibilityInteractionClient.getInstance().getConnection(
721                            mService.mConnectionId);
722            if (connection != null) {
723                try {
724                    return connection.getMagnificationScale();
725                } catch (RemoteException re) {
726                    Log.w(LOG_TAG, "Failed to obtain scale", re);
727                }
728            }
729            return 1.0f;
730        }
731
732        /**
733         * Returns the unscaled screen-relative X coordinate of the focal
734         * center of the magnified region. This is the point around which
735         * zooming occurs and is guaranteed to lie within the magnified
736         * region.
737         * <p>
738         * <strong>Note:</strong> If the service is not yet connected (e.g.
739         * {@link AccessibilityService#onServiceConnected()} has not yet been
740         * called) or the service has been disconnected, this method will
741         * return a default value of {@code 0.0f}.
742         *
743         * @return the unscaled screen-relative X coordinate of the center of
744         *         the magnified region
745         */
746        public float getCenterX() {
747            final IAccessibilityServiceConnection connection =
748                    AccessibilityInteractionClient.getInstance().getConnection(
749                            mService.mConnectionId);
750            if (connection != null) {
751                try {
752                    return connection.getMagnificationCenterX();
753                } catch (RemoteException re) {
754                    Log.w(LOG_TAG, "Failed to obtain center X", re);
755                }
756            }
757            return 0.0f;
758        }
759
760        /**
761         * Returns the unscaled screen-relative Y coordinate of the focal
762         * center of the magnified region. This is the point around which
763         * zooming occurs and is guaranteed to lie within the magnified
764         * region.
765         * <p>
766         * <strong>Note:</strong> If the service is not yet connected (e.g.
767         * {@link AccessibilityService#onServiceConnected()} has not yet been
768         * called) or the service has been disconnected, this method will
769         * return a default value of {@code 0.0f}.
770         *
771         * @return the unscaled screen-relative Y coordinate of the center of
772         *         the magnified region
773         */
774        public float getCenterY() {
775            final IAccessibilityServiceConnection connection =
776                    AccessibilityInteractionClient.getInstance().getConnection(
777                            mService.mConnectionId);
778            if (connection != null) {
779                try {
780                    return connection.getMagnificationCenterY();
781                } catch (RemoteException re) {
782                    Log.w(LOG_TAG, "Failed to obtain center Y", re);
783                }
784            }
785            return 0.0f;
786        }
787
788        /**
789         * Returns the region of the screen currently being magnified. If
790         * magnification is not enabled, the returned region will be empty.
791         * <p>
792         * <strong>Note:</strong> If the service is not yet connected (e.g.
793         * {@link AccessibilityService#onServiceConnected()} has not yet been
794         * called) or the service has been disconnected, this method will
795         * return an empty region.
796         *
797         * @return the screen-relative bounds of the magnified region
798         */
799        @NonNull
800        public Region getMagnifiedRegion() {
801            final IAccessibilityServiceConnection connection =
802                    AccessibilityInteractionClient.getInstance().getConnection(
803                            mService.mConnectionId);
804            if (connection != null) {
805                try {
806                    return connection.getMagnifiedRegion();
807                } catch (RemoteException re) {
808                    Log.w(LOG_TAG, "Failed to obtain magnified region", re);
809                }
810            }
811            return Region.obtain();
812        }
813
814        /**
815         * Resets magnification scale and center to their default (e.g. no
816         * magnification) values.
817         * <p>
818         * <strong>Note:</strong> If the service is not yet connected (e.g.
819         * {@link AccessibilityService#onServiceConnected()} has not yet been
820         * called) or the service has been disconnected, this method will have
821         * no effect and return {@code false}.
822         *
823         * @param animate {@code true} to animate from the current scale and
824         *                center or {@code false} to reset the scale and center
825         *                immediately
826         * @return {@code true} on success, {@code false} on failure
827         */
828        public boolean reset(boolean animate) {
829            final IAccessibilityServiceConnection connection =
830                    AccessibilityInteractionClient.getInstance().getConnection(
831                            mService.mConnectionId);
832            if (connection != null) {
833                try {
834                    return connection.resetMagnification(animate);
835                } catch (RemoteException re) {
836                    Log.w(LOG_TAG, "Failed to reset", re);
837                }
838            }
839            return false;
840        }
841
842        /**
843         * Sets the magnification scale.
844         * <p>
845         * <strong>Note:</strong> If the service is not yet connected (e.g.
846         * {@link AccessibilityService#onServiceConnected()} has not yet been
847         * called) or the service has been disconnected, this method will have
848         * no effect and return {@code false}.
849         *
850         * @param scale the magnification scale to set, must be >= 1 and <= 5
851         * @param animate {@code true} to animate from the current scale or
852         *                {@code false} to set the scale immediately
853         * @return {@code true} on success, {@code false} on failure
854         */
855        public boolean setScale(float scale, boolean animate) {
856            final IAccessibilityServiceConnection connection =
857                    AccessibilityInteractionClient.getInstance().getConnection(
858                            mService.mConnectionId);
859            if (connection != null) {
860                try {
861                    return connection.setMagnificationScaleAndCenter(
862                            scale, Float.NaN, Float.NaN, animate);
863                } catch (RemoteException re) {
864                    Log.w(LOG_TAG, "Failed to set scale", re);
865                }
866            }
867            return false;
868        }
869
870        /**
871         * Sets the center of the magnified viewport.
872         * <p>
873         * <strong>Note:</strong> If the service is not yet connected (e.g.
874         * {@link AccessibilityService#onServiceConnected()} has not yet been
875         * called) or the service has been disconnected, this method will have
876         * no effect and return {@code false}.
877         *
878         * @param centerX the unscaled screen-relative X coordinate on which to
879         *                center the viewport
880         * @param centerY the unscaled screen-relative Y coordinate on which to
881         *                center the viewport
882         * @param animate {@code true} to animate from the current viewport
883         *                center or {@code false} to set the center immediately
884         * @return {@code true} on success, {@code false} on failure
885         */
886        public boolean setCenter(float centerX, float centerY, boolean animate) {
887            final IAccessibilityServiceConnection connection =
888                    AccessibilityInteractionClient.getInstance().getConnection(
889                            mService.mConnectionId);
890            if (connection != null) {
891                try {
892                    return connection.setMagnificationScaleAndCenter(
893                            Float.NaN, centerX, centerY, animate);
894                } catch (RemoteException re) {
895                    Log.w(LOG_TAG, "Failed to set center", re);
896                }
897            }
898            return false;
899        }
900
901        /**
902         * Listener for changes in the state of magnification.
903         */
904        public interface OnMagnificationChangedListener {
905            /**
906             * Called when the magnified region, scale, or center changes.
907             *
908             * @param controller the magnification controller
909             * @param region the new magnified region, may be empty if
910             *               magnification is not enabled (e.g. scale is 1)
911             * @param scale the new scale
912             * @param centerX the new X coordinate around which magnification is focused
913             * @param centerY the new Y coordinate around which magnification is focused
914             */
915            void onMagnificationChanged(@NonNull MagnificationController controller,
916                    @NonNull Region region, float scale, float centerX, float centerY);
917        }
918    }
919
920    /**
921     * Performs a global action. Such an action can be performed
922     * at any moment regardless of the current application or user
923     * location in that application. For example going back, going
924     * home, opening recents, etc.
925     *
926     * @param action The action to perform.
927     * @return Whether the action was successfully performed.
928     *
929     * @see #GLOBAL_ACTION_BACK
930     * @see #GLOBAL_ACTION_HOME
931     * @see #GLOBAL_ACTION_NOTIFICATIONS
932     * @see #GLOBAL_ACTION_RECENTS
933     */
934    public final boolean performGlobalAction(int action) {
935        IAccessibilityServiceConnection connection =
936            AccessibilityInteractionClient.getInstance().getConnection(mConnectionId);
937        if (connection != null) {
938            try {
939                return connection.performGlobalAction(action);
940            } catch (RemoteException re) {
941                Log.w(LOG_TAG, "Error while calling performGlobalAction", re);
942            }
943        }
944        return false;
945    }
946
947    /**
948     * Find the view that has the specified focus type. The search is performed
949     * across all windows.
950     * <p>
951     * <strong>Note:</strong> In order to access the windows your service has
952     * to declare the capability to retrieve window content by setting the
953     * {@link android.R.styleable#AccessibilityService_canRetrieveWindowContent}
954     * property in its meta-data. For details refer to {@link #SERVICE_META_DATA}.
955     * Also the service has to opt-in to retrieve the interactive windows by
956     * setting the {@link AccessibilityServiceInfo#FLAG_RETRIEVE_INTERACTIVE_WINDOWS}
957     * flag.Otherwise, the search will be performed only in the active window.
958     * </p>
959     *
960     * @param focus The focus to find. One of {@link AccessibilityNodeInfo#FOCUS_INPUT} or
961     *         {@link AccessibilityNodeInfo#FOCUS_ACCESSIBILITY}.
962     * @return The node info of the focused view or null.
963     *
964     * @see AccessibilityNodeInfo#FOCUS_INPUT
965     * @see AccessibilityNodeInfo#FOCUS_ACCESSIBILITY
966     */
967    public AccessibilityNodeInfo findFocus(int focus) {
968        return AccessibilityInteractionClient.getInstance().findFocus(mConnectionId,
969                AccessibilityNodeInfo.ANY_WINDOW_ID, AccessibilityNodeInfo.ROOT_NODE_ID, focus);
970    }
971
972    /**
973     * Gets the an {@link AccessibilityServiceInfo} describing this
974     * {@link AccessibilityService}. This method is useful if one wants
975     * to change some of the dynamically configurable properties at
976     * runtime.
977     *
978     * @return The accessibility service info.
979     *
980     * @see AccessibilityServiceInfo
981     */
982    public final AccessibilityServiceInfo getServiceInfo() {
983        IAccessibilityServiceConnection connection =
984            AccessibilityInteractionClient.getInstance().getConnection(mConnectionId);
985        if (connection != null) {
986            try {
987                return connection.getServiceInfo();
988            } catch (RemoteException re) {
989                Log.w(LOG_TAG, "Error while getting AccessibilityServiceInfo", re);
990            }
991        }
992        return null;
993    }
994
995    /**
996     * Sets the {@link AccessibilityServiceInfo} that describes this service.
997     * <p>
998     * Note: You can call this method any time but the info will be picked up after
999     *       the system has bound to this service and when this method is called thereafter.
1000     *
1001     * @param info The info.
1002     */
1003    public final void setServiceInfo(AccessibilityServiceInfo info) {
1004        mInfo = info;
1005        sendServiceInfo();
1006    }
1007
1008    /**
1009     * Sets the {@link AccessibilityServiceInfo} for this service if the latter is
1010     * properly set and there is an {@link IAccessibilityServiceConnection} to the
1011     * AccessibilityManagerService.
1012     */
1013    private void sendServiceInfo() {
1014        IAccessibilityServiceConnection connection =
1015            AccessibilityInteractionClient.getInstance().getConnection(mConnectionId);
1016        if (mInfo != null && connection != null) {
1017            try {
1018                connection.setServiceInfo(mInfo);
1019                mInfo = null;
1020                AccessibilityInteractionClient.getInstance().clearCache();
1021            } catch (RemoteException re) {
1022                Log.w(LOG_TAG, "Error while setting AccessibilityServiceInfo", re);
1023            }
1024        }
1025    }
1026
1027    @Override
1028    public Object getSystemService(@ServiceName @NonNull String name) {
1029        if (getBaseContext() == null) {
1030            throw new IllegalStateException(
1031                    "System services not available to Activities before onCreate()");
1032        }
1033
1034        // Guarantee that we always return the same window manager instance.
1035        if (WINDOW_SERVICE.equals(name)) {
1036            if (mWindowManager == null) {
1037                mWindowManager = (WindowManager) getBaseContext().getSystemService(name);
1038            }
1039            return mWindowManager;
1040        }
1041        return super.getSystemService(name);
1042    }
1043
1044    /**
1045     * Implement to return the implementation of the internal accessibility
1046     * service interface.
1047     */
1048    @Override
1049    public final IBinder onBind(Intent intent) {
1050        return new IAccessibilityServiceClientWrapper(this, getMainLooper(), new Callbacks() {
1051            @Override
1052            public void onServiceConnected() {
1053                AccessibilityService.this.dispatchServiceConnected();
1054            }
1055
1056            @Override
1057            public void onInterrupt() {
1058                AccessibilityService.this.onInterrupt();
1059            }
1060
1061            @Override
1062            public void onAccessibilityEvent(AccessibilityEvent event) {
1063                AccessibilityService.this.onAccessibilityEvent(event);
1064            }
1065
1066            @Override
1067            public void init(int connectionId, IBinder windowToken) {
1068                mConnectionId = connectionId;
1069                mWindowToken = windowToken;
1070
1071                // The client may have already obtained the window manager, so
1072                // update the default token on whatever manager we gave them.
1073                final WindowManagerImpl wm = (WindowManagerImpl) getSystemService(WINDOW_SERVICE);
1074                wm.setDefaultToken(windowToken);
1075            }
1076
1077            @Override
1078            public boolean onGesture(int gestureId) {
1079                return AccessibilityService.this.onGesture(gestureId);
1080            }
1081
1082            @Override
1083            public boolean onKeyEvent(KeyEvent event) {
1084                return AccessibilityService.this.onKeyEvent(event);
1085            }
1086
1087            @Override
1088            public void onMagnificationChanged(@NonNull Region region,
1089                    float scale, float centerX, float centerY) {
1090                AccessibilityService.this.onMagnificationChanged(region, scale, centerX, centerY);
1091            }
1092        });
1093    }
1094
1095    /**
1096     * Implements the internal {@link IAccessibilityServiceClient} interface to convert
1097     * incoming calls to it back to calls on an {@link AccessibilityService}.
1098     *
1099     * @hide
1100     */
1101    public static class IAccessibilityServiceClientWrapper extends IAccessibilityServiceClient.Stub
1102            implements HandlerCaller.Callback {
1103        private static final int DO_INIT = 1;
1104        private static final int DO_ON_INTERRUPT = 2;
1105        private static final int DO_ON_ACCESSIBILITY_EVENT = 3;
1106        private static final int DO_ON_GESTURE = 4;
1107        private static final int DO_CLEAR_ACCESSIBILITY_CACHE = 5;
1108        private static final int DO_ON_KEY_EVENT = 6;
1109        private static final int DO_ON_MAGNIFICATION_CHANGED = 7;
1110
1111        private final HandlerCaller mCaller;
1112
1113        private final Callbacks mCallback;
1114
1115        private int mConnectionId;
1116
1117        public IAccessibilityServiceClientWrapper(Context context, Looper looper,
1118                Callbacks callback) {
1119            mCallback = callback;
1120            mCaller = new HandlerCaller(context, looper, this, true /*asyncHandler*/);
1121        }
1122
1123        public void init(IAccessibilityServiceConnection connection, int connectionId,
1124                IBinder windowToken) {
1125            Message message = mCaller.obtainMessageIOO(DO_INIT, connectionId,
1126                    connection, windowToken);
1127            mCaller.sendMessage(message);
1128        }
1129
1130        public void onInterrupt() {
1131            Message message = mCaller.obtainMessage(DO_ON_INTERRUPT);
1132            mCaller.sendMessage(message);
1133        }
1134
1135        public void onAccessibilityEvent(AccessibilityEvent event) {
1136            Message message = mCaller.obtainMessageO(DO_ON_ACCESSIBILITY_EVENT, event);
1137            mCaller.sendMessage(message);
1138        }
1139
1140        public void onGesture(int gestureId) {
1141            Message message = mCaller.obtainMessageI(DO_ON_GESTURE, gestureId);
1142            mCaller.sendMessage(message);
1143        }
1144
1145        public void clearAccessibilityCache() {
1146            Message message = mCaller.obtainMessage(DO_CLEAR_ACCESSIBILITY_CACHE);
1147            mCaller.sendMessage(message);
1148        }
1149
1150        @Override
1151        public void onKeyEvent(KeyEvent event, int sequence) {
1152            Message message = mCaller.obtainMessageIO(DO_ON_KEY_EVENT, sequence, event);
1153            mCaller.sendMessage(message);
1154        }
1155
1156        public void onMagnificationChanged(@NonNull Region region,
1157                float scale, float centerX, float centerY) {
1158            final SomeArgs args = SomeArgs.obtain();
1159            args.arg1 = region;
1160            args.arg2 = scale;
1161            args.arg3 = centerX;
1162            args.arg4 = centerY;
1163
1164            final Message message = mCaller.obtainMessageO(DO_ON_MAGNIFICATION_CHANGED, args);
1165            mCaller.sendMessage(message);
1166        }
1167
1168        @Override
1169        public void executeMessage(Message message) {
1170            switch (message.what) {
1171                case DO_ON_ACCESSIBILITY_EVENT: {
1172                    AccessibilityEvent event = (AccessibilityEvent) message.obj;
1173                    if (event != null) {
1174                        AccessibilityInteractionClient.getInstance().onAccessibilityEvent(event);
1175                        mCallback.onAccessibilityEvent(event);
1176                        // Make sure the event is recycled.
1177                        try {
1178                            event.recycle();
1179                        } catch (IllegalStateException ise) {
1180                            /* ignore - best effort */
1181                        }
1182                    }
1183                } return;
1184
1185                case DO_ON_INTERRUPT: {
1186                    mCallback.onInterrupt();
1187                } return;
1188
1189                case DO_INIT: {
1190                    mConnectionId = message.arg1;
1191                    SomeArgs args = (SomeArgs) message.obj;
1192                    IAccessibilityServiceConnection connection =
1193                            (IAccessibilityServiceConnection) args.arg1;
1194                    IBinder windowToken = (IBinder) args.arg2;
1195                    args.recycle();
1196                    if (connection != null) {
1197                        AccessibilityInteractionClient.getInstance().addConnection(mConnectionId,
1198                                connection);
1199                        mCallback.init(mConnectionId, windowToken);
1200                        mCallback.onServiceConnected();
1201                    } else {
1202                        AccessibilityInteractionClient.getInstance().removeConnection(
1203                                mConnectionId);
1204                        mConnectionId = AccessibilityInteractionClient.NO_ID;
1205                        AccessibilityInteractionClient.getInstance().clearCache();
1206                        mCallback.init(AccessibilityInteractionClient.NO_ID, null);
1207                    }
1208                } return;
1209
1210                case DO_ON_GESTURE: {
1211                    final int gestureId = message.arg1;
1212                    mCallback.onGesture(gestureId);
1213                } return;
1214
1215                case DO_CLEAR_ACCESSIBILITY_CACHE: {
1216                    AccessibilityInteractionClient.getInstance().clearCache();
1217                } return;
1218
1219                case DO_ON_KEY_EVENT: {
1220                    KeyEvent event = (KeyEvent) message.obj;
1221                    try {
1222                        IAccessibilityServiceConnection connection = AccessibilityInteractionClient
1223                                .getInstance().getConnection(mConnectionId);
1224                        if (connection != null) {
1225                            final boolean result = mCallback.onKeyEvent(event);
1226                            final int sequence = message.arg1;
1227                            try {
1228                                connection.setOnKeyEventResult(result, sequence);
1229                            } catch (RemoteException re) {
1230                                /* ignore */
1231                            }
1232                        }
1233                    } finally {
1234                        // Make sure the event is recycled.
1235                        try {
1236                            event.recycle();
1237                        } catch (IllegalStateException ise) {
1238                            /* ignore - best effort */
1239                        }
1240                    }
1241                } return;
1242
1243                case DO_ON_MAGNIFICATION_CHANGED: {
1244                    final SomeArgs args = (SomeArgs) message.obj;
1245                    final Region region = (Region) args.arg1;
1246                    final float scale = (float) args.arg2;
1247                    final float centerX = (float) args.arg3;
1248                    final float centerY = (float) args.arg4;
1249                    mCallback.onMagnificationChanged(region, scale, centerX, centerY);
1250                } return;
1251
1252                default :
1253                    Log.w(LOG_TAG, "Unknown message type " + message.what);
1254            }
1255        }
1256    }
1257}
1258