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