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