AccessibilityInteractionController.java revision fd8d9c4c0a3930c9b2585cd54ea4288fbb15cceb
1/*
2 * Copyright (C) 2012 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.view;
18
19import static android.view.accessibility.AccessibilityNodeInfo.INCLUDE_NOT_IMPORTANT_VIEWS;
20
21import android.graphics.Rect;
22import android.os.Bundle;
23import android.os.Handler;
24import android.os.Looper;
25import android.os.Message;
26import android.os.Process;
27import android.os.RemoteException;
28import android.util.Pool;
29import android.util.Poolable;
30import android.util.PoolableManager;
31import android.util.Pools;
32import android.util.SparseLongArray;
33import android.view.accessibility.AccessibilityInteractionClient;
34import android.view.accessibility.AccessibilityNodeInfo;
35import android.view.accessibility.AccessibilityNodeProvider;
36import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
37
38import java.util.ArrayList;
39import java.util.HashMap;
40import java.util.List;
41import java.util.Map;
42
43/**
44 * Class for managing accessibility interactions initiated from the system
45 * and targeting the view hierarchy. A *ClientThread method is to be
46 * called from the interaction connection ViewAncestor gives the system to
47 * talk to it and a corresponding *UiThread method that is executed on the
48 * UI thread.
49 */
50final class AccessibilityInteractionController {
51    private static final int POOL_SIZE = 5;
52
53    private ArrayList<AccessibilityNodeInfo> mTempAccessibilityNodeInfoList =
54        new ArrayList<AccessibilityNodeInfo>();
55
56    private final Handler mHandler;
57
58    private final ViewRootImpl mViewRootImpl;
59
60    private final AccessibilityNodePrefetcher mPrefetcher;
61
62    private final long mMyLooperThreadId;
63
64    private final int mMyProcessId;
65
66    private final ArrayList<View> mTempArrayList = new ArrayList<View>();
67
68    private final Rect mTempRect = new Rect();
69
70    public AccessibilityInteractionController(ViewRootImpl viewRootImpl) {
71        Looper looper =  viewRootImpl.mHandler.getLooper();
72        mMyLooperThreadId = looper.getThread().getId();
73        mMyProcessId = Process.myPid();
74        mHandler = new PrivateHandler(looper);
75        mViewRootImpl = viewRootImpl;
76        mPrefetcher = new AccessibilityNodePrefetcher();
77    }
78
79    // Reusable poolable arguments for interacting with the view hierarchy
80    // to fit more arguments than Message and to avoid sharing objects between
81    // two messages since several threads can send messages concurrently.
82    private final Pool<SomeArgs> mPool = Pools.synchronizedPool(Pools.finitePool(
83            new PoolableManager<SomeArgs>() {
84                public SomeArgs newInstance() {
85                    return new SomeArgs();
86                }
87
88                public void onAcquired(SomeArgs info) {
89                    /* do nothing */
90                }
91
92                public void onReleased(SomeArgs info) {
93                    info.clear();
94                }
95            }, POOL_SIZE)
96    );
97
98    private class SomeArgs implements Poolable<SomeArgs> {
99        private SomeArgs mNext;
100        private boolean mIsPooled;
101
102        public Object arg1;
103        public Object arg2;
104        public int argi1;
105        public int argi2;
106        public int argi3;
107
108        public SomeArgs getNextPoolable() {
109            return mNext;
110        }
111
112        public boolean isPooled() {
113            return mIsPooled;
114        }
115
116        public void setNextPoolable(SomeArgs args) {
117            mNext = args;
118        }
119
120        public void setPooled(boolean isPooled) {
121            mIsPooled = isPooled;
122        }
123
124        private void clear() {
125            arg1 = null;
126            arg2 = null;
127            argi1 = 0;
128            argi2 = 0;
129            argi3 = 0;
130        }
131    }
132
133    private boolean isShown(View view) {
134        // The first two checks are made also made by isShown() which
135        // however traverses the tree up to the parent to catch that.
136        // Therefore, we do some fail fast check to minimize the up
137        // tree traversal.
138        return (view.mAttachInfo != null
139                && view.mAttachInfo.mWindowVisibility == View.VISIBLE
140                && view.isShown());
141    }
142
143    public void findAccessibilityNodeInfoByAccessibilityIdClientThread(
144            long accessibilityNodeId, int windowLeft, int windowTop, int interactionId,
145            IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid,
146            long interrogatingTid) {
147        Message message = mHandler.obtainMessage();
148        message.what = PrivateHandler.MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID;
149        message.arg1 = flags;
150
151        SomeArgs args = mPool.acquire();
152        args.argi1 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
153        args.argi2 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId);
154        args.argi3 = interactionId;
155        args.arg1 = callback;
156
157        SomeArgs moreArgs = mPool.acquire();
158        moreArgs.argi1 = windowLeft;
159        moreArgs.argi2 = windowTop;
160        args.arg2 = moreArgs;
161
162        message.obj = args;
163
164        // If the interrogation is performed by the same thread as the main UI
165        // thread in this process, set the message as a static reference so
166        // after this call completes the same thread but in the interrogating
167        // client can handle the message to generate the result.
168        if (interrogatingPid == mMyProcessId && interrogatingTid == mMyLooperThreadId) {
169            AccessibilityInteractionClient.getInstanceForThread(
170                    interrogatingTid).setSameThreadMessage(message);
171        } else {
172            mHandler.sendMessage(message);
173        }
174    }
175
176    private void findAccessibilityNodeInfoByAccessibilityIdUiThread(Message message) {
177        final int flags = message.arg1;
178
179        SomeArgs args = (SomeArgs) message.obj;
180        final int accessibilityViewId = args.argi1;
181        final int virtualDescendantId = args.argi2;
182        final int interactionId = args.argi3;
183        final IAccessibilityInteractionConnectionCallback callback =
184            (IAccessibilityInteractionConnectionCallback) args.arg1;
185
186        SomeArgs moreArgs = (SomeArgs) args.arg2;
187        mViewRootImpl.mAttachInfo.mActualWindowLeft = moreArgs.argi1;
188        mViewRootImpl.mAttachInfo.mActualWindowTop = moreArgs.argi2;
189
190        mPool.release(moreArgs);
191        mPool.release(args);
192
193        List<AccessibilityNodeInfo> infos = mTempAccessibilityNodeInfoList;
194        infos.clear();
195        try {
196            if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
197                return;
198            }
199            mViewRootImpl.mAttachInfo.mIncludeNotImportantViews =
200                (flags & INCLUDE_NOT_IMPORTANT_VIEWS) != 0;
201            View root = null;
202            if (accessibilityViewId == AccessibilityNodeInfo.UNDEFINED) {
203                root = mViewRootImpl.mView;
204            } else {
205                root = findViewByAccessibilityId(accessibilityViewId);
206            }
207            if (root != null && isShown(root)) {
208                mPrefetcher.prefetchAccessibilityNodeInfos(root, virtualDescendantId, flags, infos);
209            }
210        } finally {
211            try {
212                mViewRootImpl.mAttachInfo.mIncludeNotImportantViews = false;
213                applyApplicationScaleIfNeeded(infos);
214                callback.setFindAccessibilityNodeInfosResult(infos, interactionId);
215                infos.clear();
216            } catch (RemoteException re) {
217                /* ignore - the other side will time out */
218            }
219        }
220    }
221
222    public void findAccessibilityNodeInfoByViewIdClientThread(long accessibilityNodeId,
223            int viewId, int windowLeft, int windowTop, int interactionId,
224            IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid,
225            long interrogatingTid) {
226        Message message = mHandler.obtainMessage();
227        message.what = PrivateHandler.MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID;
228        message.arg1 = flags;
229        message.arg2 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
230
231        SomeArgs args = mPool.acquire();
232        args.argi1 = viewId;
233        args.argi2 = interactionId;
234        args.arg1 = callback;
235
236        SomeArgs moreArgs = mPool.acquire();
237        moreArgs.argi1 = windowLeft;
238        moreArgs.argi2 = windowTop;
239        args.arg2 = moreArgs;
240
241        message.obj = args;
242
243        // If the interrogation is performed by the same thread as the main UI
244        // thread in this process, set the message as a static reference so
245        // after this call completes the same thread but in the interrogating
246        // client can handle the message to generate the result.
247        if (interrogatingPid == mMyProcessId && interrogatingTid == mMyLooperThreadId) {
248            AccessibilityInteractionClient.getInstanceForThread(
249                    interrogatingTid).setSameThreadMessage(message);
250        } else {
251            mHandler.sendMessage(message);
252        }
253    }
254
255    private void findAccessibilityNodeInfoByViewIdUiThread(Message message) {
256        final int flags = message.arg1;
257        final int accessibilityViewId = message.arg2;
258
259        SomeArgs args = (SomeArgs) message.obj;
260        final int viewId = args.argi1;
261        final int interactionId = args.argi2;
262        final IAccessibilityInteractionConnectionCallback callback =
263            (IAccessibilityInteractionConnectionCallback) args.arg1;
264
265        SomeArgs moreArgs = (SomeArgs) args.arg2;
266        mViewRootImpl.mAttachInfo.mActualWindowLeft = moreArgs.argi1;
267        mViewRootImpl.mAttachInfo.mActualWindowTop = moreArgs.argi2;
268
269        mPool.release(moreArgs);
270        mPool.release(args);
271
272        AccessibilityNodeInfo info = null;
273        try {
274            if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
275                return;
276            }
277            mViewRootImpl.mAttachInfo.mIncludeNotImportantViews =
278                (flags & INCLUDE_NOT_IMPORTANT_VIEWS) != 0;
279            View root = null;
280            if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED) {
281                root = findViewByAccessibilityId(accessibilityViewId);
282            } else {
283                root = mViewRootImpl.mView;
284            }
285            if (root != null) {
286                View target = root.findViewById(viewId);
287                if (target != null && isShown(target)) {
288                    info = target.createAccessibilityNodeInfo();
289                }
290            }
291        } finally {
292            try {
293                mViewRootImpl.mAttachInfo.mIncludeNotImportantViews = false;
294                applyApplicationScaleIfNeeded(info);
295                callback.setFindAccessibilityNodeInfoResult(info, interactionId);
296            } catch (RemoteException re) {
297                /* ignore - the other side will time out */
298            }
299        }
300    }
301
302    public void findAccessibilityNodeInfosByTextClientThread(long accessibilityNodeId,
303            String text, int windowLeft, int windowTop, int interactionId,
304            IAccessibilityInteractionConnectionCallback callback, int flags,
305            int interrogatingPid, long interrogatingTid) {
306        Message message = mHandler.obtainMessage();
307        message.what = PrivateHandler.MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT;
308        message.arg1 = flags;
309
310        SomeArgs args = mPool.acquire();
311        args.arg1 = text;
312        args.argi1 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
313        args.argi2 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId);
314        args.argi3 = interactionId;
315
316        SomeArgs moreArgs = mPool.acquire();
317        moreArgs.arg1 = callback;
318        moreArgs.argi1 = windowLeft;
319        moreArgs.argi2 = windowTop;
320        args.arg2 = moreArgs;
321
322        message.obj = args;
323
324        // If the interrogation is performed by the same thread as the main UI
325        // thread in this process, set the message as a static reference so
326        // after this call completes the same thread but in the interrogating
327        // client can handle the message to generate the result.
328        if (interrogatingPid == mMyProcessId && interrogatingTid == mMyLooperThreadId) {
329            AccessibilityInteractionClient.getInstanceForThread(
330                    interrogatingTid).setSameThreadMessage(message);
331        } else {
332            mHandler.sendMessage(message);
333        }
334    }
335
336    private void findAccessibilityNodeInfosByTextUiThread(Message message) {
337        final int flags = message.arg1;
338
339        SomeArgs args = (SomeArgs) message.obj;
340        final String text = (String) args.arg1;
341        final int accessibilityViewId = args.argi1;
342        final int virtualDescendantId = args.argi2;
343        final int interactionId = args.argi3;
344
345        SomeArgs moreArgs = (SomeArgs) args.arg2;
346        final IAccessibilityInteractionConnectionCallback callback =
347            (IAccessibilityInteractionConnectionCallback) moreArgs.arg1;
348        mViewRootImpl.mAttachInfo.mActualWindowLeft = moreArgs.argi1;
349        mViewRootImpl.mAttachInfo.mActualWindowTop = moreArgs.argi2;
350
351        mPool.release(moreArgs);
352        mPool.release(args);
353
354        List<AccessibilityNodeInfo> infos = null;
355        try {
356            if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
357                return;
358            }
359            mViewRootImpl.mAttachInfo.mIncludeNotImportantViews =
360                (flags & INCLUDE_NOT_IMPORTANT_VIEWS) != 0;
361            View root = null;
362            if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED) {
363                root = findViewByAccessibilityId(accessibilityViewId);
364            } else {
365                root = mViewRootImpl.mView;
366            }
367            if (root != null && isShown(root)) {
368                AccessibilityNodeProvider provider = root.getAccessibilityNodeProvider();
369                if (provider != null) {
370                    infos = provider.findAccessibilityNodeInfosByText(text,
371                            virtualDescendantId);
372                } else if (virtualDescendantId == AccessibilityNodeInfo.UNDEFINED) {
373                    ArrayList<View> foundViews = mTempArrayList;
374                    foundViews.clear();
375                    root.findViewsWithText(foundViews, text, View.FIND_VIEWS_WITH_TEXT
376                            | View.FIND_VIEWS_WITH_CONTENT_DESCRIPTION
377                            | View.FIND_VIEWS_WITH_ACCESSIBILITY_NODE_PROVIDERS);
378                    if (!foundViews.isEmpty()) {
379                        infos = mTempAccessibilityNodeInfoList;
380                        infos.clear();
381                        final int viewCount = foundViews.size();
382                        for (int i = 0; i < viewCount; i++) {
383                            View foundView = foundViews.get(i);
384                            if (isShown(foundView)) {
385                                provider = foundView.getAccessibilityNodeProvider();
386                                if (provider != null) {
387                                    List<AccessibilityNodeInfo> infosFromProvider =
388                                        provider.findAccessibilityNodeInfosByText(text,
389                                                AccessibilityNodeInfo.UNDEFINED);
390                                    if (infosFromProvider != null) {
391                                        infos.addAll(infosFromProvider);
392                                    }
393                                } else  {
394                                    infos.add(foundView.createAccessibilityNodeInfo());
395                                }
396                            }
397                        }
398                    }
399                }
400            }
401        } finally {
402            try {
403                mViewRootImpl.mAttachInfo.mIncludeNotImportantViews = false;
404                applyApplicationScaleIfNeeded(infos);
405                callback.setFindAccessibilityNodeInfosResult(infos, interactionId);
406            } catch (RemoteException re) {
407                /* ignore - the other side will time out */
408            }
409        }
410    }
411
412    public void findFocusClientThread(long accessibilityNodeId, int focusType, int windowLeft,
413            int windowTop, int interactionId, IAccessibilityInteractionConnectionCallback callback,
414            int flags, int interogatingPid, long interrogatingTid) {
415        Message message = mHandler.obtainMessage();
416        message.what = PrivateHandler.MSG_FIND_FOCUS;
417        message.arg1 = flags;
418        message.arg2 = focusType;
419
420        SomeArgs args = mPool.acquire();
421        args.argi1 = interactionId;
422        args.argi2 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
423        args.argi3 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId);
424        args.arg1 = callback;
425
426        SomeArgs moreArgs = mPool.acquire();
427        moreArgs.argi1 = windowLeft;
428        moreArgs.argi2 = windowTop;
429        args.arg2 = moreArgs;
430
431        message.obj = args;
432
433        // If the interrogation is performed by the same thread as the main UI
434        // thread in this process, set the message as a static reference so
435        // after this call completes the same thread but in the interrogating
436        // client can handle the message to generate the result.
437        if (interogatingPid == mMyProcessId && interrogatingTid == mMyLooperThreadId) {
438            AccessibilityInteractionClient.getInstanceForThread(
439                    interrogatingTid).setSameThreadMessage(message);
440        } else {
441            mHandler.sendMessage(message);
442        }
443    }
444
445    private void findFocusUiThread(Message message) {
446        final int flags = message.arg1;
447        final int focusType = message.arg2;
448
449        SomeArgs args = (SomeArgs) message.obj;
450        final int interactionId = args.argi1;
451        final int accessibilityViewId = args.argi2;
452        final int virtualDescendantId = args.argi3;
453        final IAccessibilityInteractionConnectionCallback callback =
454            (IAccessibilityInteractionConnectionCallback) args.arg1;
455
456        SomeArgs moreArgs = (SomeArgs) args.arg2;
457        mViewRootImpl.mAttachInfo.mActualWindowLeft = moreArgs.argi1;
458        mViewRootImpl.mAttachInfo.mActualWindowTop = moreArgs.argi2;
459
460        mPool.release(moreArgs);
461        mPool.release(args);
462
463        AccessibilityNodeInfo focused = null;
464        try {
465            if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
466                return;
467            }
468            mViewRootImpl.mAttachInfo.mIncludeNotImportantViews =
469                (flags & INCLUDE_NOT_IMPORTANT_VIEWS) != 0;
470            View root = null;
471            if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED) {
472                root = findViewByAccessibilityId(accessibilityViewId);
473            } else {
474                root = mViewRootImpl.mView;
475            }
476            if (root != null && isShown(root)) {
477                switch (focusType) {
478                    case AccessibilityNodeInfo.FOCUS_ACCESSIBILITY: {
479                        View host = mViewRootImpl.mAccessibilityFocusedHost;
480                        // If there is no accessibility focus host or it is not a descendant
481                        // of the root from which to start the search, then the search failed.
482                        if (host == null || !ViewRootImpl.isViewDescendantOf(host, root)) {
483                            break;
484                        }
485                        // If the host has a provider ask this provider to search for the
486                        // focus instead fetching all provider nodes to do the search here.
487                        AccessibilityNodeProvider provider = host.getAccessibilityNodeProvider();
488                        if (provider != null) {
489                            if (mViewRootImpl.mAccessibilityFocusedVirtualView != null) {
490                                focused = AccessibilityNodeInfo.obtain(
491                                        mViewRootImpl.mAccessibilityFocusedVirtualView);
492                            }
493                        } else if (virtualDescendantId == View.NO_ID) {
494                            focused = host.createAccessibilityNodeInfo();
495                        }
496                    } break;
497                    case AccessibilityNodeInfo.FOCUS_INPUT: {
498                        // Input focus cannot go to virtual views.
499                        View target = root.findFocus();
500                        if (target != null && isShown(target)) {
501                            focused = target.createAccessibilityNodeInfo();
502                        }
503                    } break;
504                    default:
505                        throw new IllegalArgumentException("Unknown focus type: " + focusType);
506                }
507            }
508        } finally {
509            try {
510                mViewRootImpl.mAttachInfo.mIncludeNotImportantViews = false;
511                applyApplicationScaleIfNeeded(focused);
512                callback.setFindAccessibilityNodeInfoResult(focused, interactionId);
513            } catch (RemoteException re) {
514                /* ignore - the other side will time out */
515            }
516        }
517    }
518
519    public void focusSearchClientThread(long accessibilityNodeId, int direction, int windowLeft,
520            int windowTop, int interactionId, IAccessibilityInteractionConnectionCallback callback,
521            int flags, int interogatingPid, long interrogatingTid) {
522        Message message = mHandler.obtainMessage();
523        message.what = PrivateHandler.MSG_FOCUS_SEARCH;
524        message.arg1 = flags;
525        message.arg2 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
526
527        SomeArgs args = mPool.acquire();
528        args.argi1 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId);
529        args.argi2 = direction;
530        args.argi3 = interactionId;
531        args.arg1 = callback;
532
533        SomeArgs moreArgs = mPool.acquire();
534        moreArgs.argi1 = windowLeft;
535        moreArgs.argi2 = windowTop;
536        args.arg2 = moreArgs;
537
538        message.obj = args;
539
540        // If the interrogation is performed by the same thread as the main UI
541        // thread in this process, set the message as a static reference so
542        // after this call completes the same thread but in the interrogating
543        // client can handle the message to generate the result.
544        if (interogatingPid == mMyProcessId && interrogatingTid == mMyLooperThreadId) {
545            AccessibilityInteractionClient.getInstanceForThread(
546                    interrogatingTid).setSameThreadMessage(message);
547        } else {
548            mHandler.sendMessage(message);
549        }
550    }
551
552    private void focusSearchUiThread(Message message) {
553        final int flags = message.arg1;
554        final int accessibilityViewId = message.arg2;
555
556        SomeArgs args = (SomeArgs) message.obj;
557        final int virtualDescendantId = args.argi1;
558        final int direction = args.argi2;
559        final int interactionId = args.argi3;
560        final IAccessibilityInteractionConnectionCallback callback =
561            (IAccessibilityInteractionConnectionCallback) args.arg1;
562
563        SomeArgs moreArgs = (SomeArgs) args.arg2;
564        mViewRootImpl.mAttachInfo.mActualWindowLeft = moreArgs.argi1;
565        mViewRootImpl.mAttachInfo.mActualWindowTop = moreArgs.argi2;
566
567        mPool.release(moreArgs);
568        mPool.release(args);
569
570        AccessibilityNodeInfo next = null;
571        try {
572            if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
573                return;
574            }
575            mViewRootImpl.mAttachInfo.mIncludeNotImportantViews =
576                (flags & INCLUDE_NOT_IMPORTANT_VIEWS) != 0;
577            View root = null;
578            if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED) {
579                root = findViewByAccessibilityId(accessibilityViewId);
580            } else {
581                root = mViewRootImpl.mView;
582            }
583            if (root != null && isShown(root)) {
584                View nextView = root.focusSearch(direction);
585                if (nextView != null) {
586                    next = nextView.createAccessibilityNodeInfo();
587                }
588            }
589        } finally {
590            try {
591                mViewRootImpl.mAttachInfo.mIncludeNotImportantViews = false;
592                applyApplicationScaleIfNeeded(next);
593                callback.setFindAccessibilityNodeInfoResult(next, interactionId);
594            } catch (RemoteException re) {
595                /* ignore - the other side will time out */
596            }
597        }
598    }
599
600    public void performAccessibilityActionClientThread(long accessibilityNodeId, int action,
601            Bundle arguments, int interactionId,
602            IAccessibilityInteractionConnectionCallback callback, int flags, int interogatingPid,
603            long interrogatingTid) {
604        Message message = mHandler.obtainMessage();
605        message.what = PrivateHandler.MSG_PERFORM_ACCESSIBILITY_ACTION;
606        message.arg1 = flags;
607        message.arg2 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
608
609        SomeArgs args = mPool.acquire();
610        args.argi1 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId);
611        args.argi2 = action;
612        args.argi3 = interactionId;
613        args.arg1 = callback;
614        args.arg2 = arguments;
615
616        message.obj = args;
617
618        // If the interrogation is performed by the same thread as the main UI
619        // thread in this process, set the message as a static reference so
620        // after this call completes the same thread but in the interrogating
621        // client can handle the message to generate the result.
622        if (interogatingPid == mMyProcessId && interrogatingTid == mMyLooperThreadId) {
623            AccessibilityInteractionClient.getInstanceForThread(
624                    interrogatingTid).setSameThreadMessage(message);
625        } else {
626            mHandler.sendMessage(message);
627        }
628    }
629
630    private void perfromAccessibilityActionUiThread(Message message) {
631        final int flags = message.arg1;
632        final int accessibilityViewId = message.arg2;
633
634        SomeArgs args = (SomeArgs) message.obj;
635        final int virtualDescendantId = args.argi1;
636        final int action = args.argi2;
637        final int interactionId = args.argi3;
638        final IAccessibilityInteractionConnectionCallback callback =
639            (IAccessibilityInteractionConnectionCallback) args.arg1;
640        Bundle arguments = (Bundle) args.arg2;
641
642        mPool.release(args);
643
644        boolean succeeded = false;
645        try {
646            if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
647                return;
648            }
649            mViewRootImpl.mAttachInfo.mIncludeNotImportantViews =
650                (flags & INCLUDE_NOT_IMPORTANT_VIEWS) != 0;
651            View target = null;
652            if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED) {
653                target = findViewByAccessibilityId(accessibilityViewId);
654            } else {
655                target = mViewRootImpl.mView;
656            }
657            if (target != null && isShown(target)) {
658                AccessibilityNodeProvider provider = target.getAccessibilityNodeProvider();
659                if (provider != null) {
660                    succeeded = provider.performAction(virtualDescendantId, action,
661                            arguments);
662                } else if (virtualDescendantId == View.NO_ID) {
663                    succeeded = target.performAccessibilityAction(action, arguments);
664                }
665            }
666        } finally {
667            try {
668                mViewRootImpl.mAttachInfo.mIncludeNotImportantViews = false;
669                callback.setPerformAccessibilityActionResult(succeeded, interactionId);
670            } catch (RemoteException re) {
671                /* ignore - the other side will time out */
672            }
673        }
674    }
675
676    private View findViewByAccessibilityId(int accessibilityId) {
677        View root = mViewRootImpl.mView;
678        if (root == null) {
679            return null;
680        }
681        View foundView = root.findViewByAccessibilityId(accessibilityId);
682        if (foundView != null && !isShown(foundView)) {
683            return null;
684        }
685        return foundView;
686    }
687
688    private void applyApplicationScaleIfNeeded(List<AccessibilityNodeInfo> infos) {
689        if (infos == null) {
690            return;
691        }
692        final float applicationScale = mViewRootImpl.mAttachInfo.mApplicationScale;
693        if (applicationScale != 1.0f) {
694            final int infoCount = infos.size();
695            for (int i = 0; i < infoCount; i++) {
696                AccessibilityNodeInfo info = infos.get(i);
697                applyApplicationScaleIfNeeded(info);
698            }
699        }
700    }
701
702    private void applyApplicationScaleIfNeeded(AccessibilityNodeInfo info) {
703        if (info == null) {
704            return;
705        }
706        final float applicationScale = mViewRootImpl.mAttachInfo.mApplicationScale;
707        if (applicationScale != 1.0f) {
708            Rect bounds = mTempRect;
709
710            info.getBoundsInParent(bounds);
711            bounds.scale(applicationScale);
712            info.setBoundsInParent(bounds);
713
714            info.getBoundsInScreen(bounds);
715            bounds.scale(applicationScale);
716            info.setBoundsInScreen(bounds);
717        }
718    }
719
720
721    /**
722     * This class encapsulates a prefetching strategy for the accessibility APIs for
723     * querying window content. It is responsible to prefetch a batch of
724     * AccessibilityNodeInfos in addition to the one for a requested node.
725     */
726    private class AccessibilityNodePrefetcher {
727
728        private static final int MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE = 50;
729
730        private final ArrayList<View> mTempViewList = new ArrayList<View>();
731
732        public void prefetchAccessibilityNodeInfos(View view, int virtualViewId, int prefetchFlags,
733                List<AccessibilityNodeInfo> outInfos) {
734            AccessibilityNodeProvider provider = view.getAccessibilityNodeProvider();
735            if (provider == null) {
736                AccessibilityNodeInfo root = view.createAccessibilityNodeInfo();
737                if (root != null) {
738                    outInfos.add(root);
739                    if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) != 0) {
740                        prefetchPredecessorsOfRealNode(view, outInfos);
741                    }
742                    if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS) != 0) {
743                        prefetchSiblingsOfRealNode(view, outInfos);
744                    }
745                    if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS) != 0) {
746                        prefetchDescendantsOfRealNode(view, outInfos);
747                    }
748                }
749            } else {
750                AccessibilityNodeInfo root = provider.createAccessibilityNodeInfo(virtualViewId);
751                if (root != null) {
752                    outInfos.add(root);
753                    if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) != 0) {
754                        prefetchPredecessorsOfVirtualNode(root, view, provider, outInfos);
755                    }
756                    if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS) != 0) {
757                        prefetchSiblingsOfVirtualNode(root, view, provider, outInfos);
758                    }
759                    if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS) != 0) {
760                        prefetchDescendantsOfVirtualNode(root, provider, outInfos);
761                    }
762                }
763            }
764        }
765
766        private void prefetchPredecessorsOfRealNode(View view,
767                List<AccessibilityNodeInfo> outInfos) {
768            ViewParent parent = view.getParentForAccessibility();
769            while (parent instanceof View
770                    && outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
771                View parentView = (View) parent;
772                AccessibilityNodeInfo info = parentView.createAccessibilityNodeInfo();
773                if (info != null) {
774                    outInfos.add(info);
775                }
776                parent = parent.getParentForAccessibility();
777            }
778        }
779
780        private void prefetchSiblingsOfRealNode(View current,
781                List<AccessibilityNodeInfo> outInfos) {
782            ViewParent parent = current.getParentForAccessibility();
783            if (parent instanceof ViewGroup) {
784                ViewGroup parentGroup = (ViewGroup) parent;
785                ArrayList<View> children = mTempViewList;
786                children.clear();
787                try {
788                    parentGroup.addChildrenForAccessibility(children);
789                    final int childCount = children.size();
790                    for (int i = 0; i < childCount; i++) {
791                        if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
792                            return;
793                        }
794                        View child = children.get(i);
795                        if (child.getAccessibilityViewId() != current.getAccessibilityViewId()
796                                &&  isShown(child)) {
797                            AccessibilityNodeInfo info = null;
798                            AccessibilityNodeProvider provider =
799                                child.getAccessibilityNodeProvider();
800                            if (provider == null) {
801                                info = child.createAccessibilityNodeInfo();
802                            } else {
803                                info = provider.createAccessibilityNodeInfo(
804                                        AccessibilityNodeInfo.UNDEFINED);
805                            }
806                            if (info != null) {
807                                outInfos.add(info);
808                            }
809                        }
810                    }
811                } finally {
812                    children.clear();
813                }
814            }
815        }
816
817        private void prefetchDescendantsOfRealNode(View root,
818                List<AccessibilityNodeInfo> outInfos) {
819            if (!(root instanceof ViewGroup)) {
820                return;
821            }
822            HashMap<View, AccessibilityNodeInfo> addedChildren =
823                new HashMap<View, AccessibilityNodeInfo>();
824            ArrayList<View> children = mTempViewList;
825            children.clear();
826            try {
827                root.addChildrenForAccessibility(children);
828                final int childCount = children.size();
829                for (int i = 0; i < childCount; i++) {
830                    if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
831                        return;
832                    }
833                    View child = children.get(i);
834                    if (isShown(child)) {
835                        AccessibilityNodeProvider provider = child.getAccessibilityNodeProvider();
836                        if (provider == null) {
837                            AccessibilityNodeInfo info = child.createAccessibilityNodeInfo();
838                            if (info != null) {
839                                outInfos.add(info);
840                                addedChildren.put(child, null);
841                            }
842                        } else {
843                            AccessibilityNodeInfo info = provider.createAccessibilityNodeInfo(
844                                   AccessibilityNodeInfo.UNDEFINED);
845                            if (info != null) {
846                                outInfos.add(info);
847                                addedChildren.put(child, info);
848                            }
849                        }
850                    }
851                }
852            } finally {
853                children.clear();
854            }
855            if (outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
856                for (Map.Entry<View, AccessibilityNodeInfo> entry : addedChildren.entrySet()) {
857                    View addedChild = entry.getKey();
858                    AccessibilityNodeInfo virtualRoot = entry.getValue();
859                    if (virtualRoot == null) {
860                        prefetchDescendantsOfRealNode(addedChild, outInfos);
861                    } else {
862                        AccessibilityNodeProvider provider =
863                            addedChild.getAccessibilityNodeProvider();
864                        prefetchDescendantsOfVirtualNode(virtualRoot, provider, outInfos);
865                    }
866                }
867            }
868        }
869
870        private void prefetchPredecessorsOfVirtualNode(AccessibilityNodeInfo root,
871                View providerHost, AccessibilityNodeProvider provider,
872                List<AccessibilityNodeInfo> outInfos) {
873            long parentNodeId = root.getParentNodeId();
874            int accessibilityViewId = AccessibilityNodeInfo.getAccessibilityViewId(parentNodeId);
875            while (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED) {
876                if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
877                    return;
878                }
879                final int virtualDescendantId =
880                    AccessibilityNodeInfo.getVirtualDescendantId(parentNodeId);
881                if (virtualDescendantId != AccessibilityNodeInfo.UNDEFINED
882                        || accessibilityViewId == providerHost.getAccessibilityViewId()) {
883                    AccessibilityNodeInfo parent = provider.createAccessibilityNodeInfo(
884                            virtualDescendantId);
885                    if (parent != null) {
886                        outInfos.add(parent);
887                    }
888                    parentNodeId = parent.getParentNodeId();
889                    accessibilityViewId = AccessibilityNodeInfo.getAccessibilityViewId(
890                            parentNodeId);
891                } else {
892                    prefetchPredecessorsOfRealNode(providerHost, outInfos);
893                    return;
894                }
895            }
896        }
897
898        private void prefetchSiblingsOfVirtualNode(AccessibilityNodeInfo current, View providerHost,
899                AccessibilityNodeProvider provider, List<AccessibilityNodeInfo> outInfos) {
900            final long parentNodeId = current.getParentNodeId();
901            final int parentAccessibilityViewId =
902                AccessibilityNodeInfo.getAccessibilityViewId(parentNodeId);
903            final int parentVirtualDescendantId =
904                AccessibilityNodeInfo.getVirtualDescendantId(parentNodeId);
905            if (parentVirtualDescendantId != AccessibilityNodeInfo.UNDEFINED
906                    || parentAccessibilityViewId == providerHost.getAccessibilityViewId()) {
907                AccessibilityNodeInfo parent =
908                    provider.createAccessibilityNodeInfo(parentVirtualDescendantId);
909                if (parent != null) {
910                    SparseLongArray childNodeIds = parent.getChildNodeIds();
911                    final int childCount = childNodeIds.size();
912                    for (int i = 0; i < childCount; i++) {
913                        if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
914                            return;
915                        }
916                        final long childNodeId = childNodeIds.get(i);
917                        if (childNodeId != current.getSourceNodeId()) {
918                            final int childVirtualDescendantId =
919                                AccessibilityNodeInfo.getVirtualDescendantId(childNodeId);
920                            AccessibilityNodeInfo child = provider.createAccessibilityNodeInfo(
921                                    childVirtualDescendantId);
922                            if (child != null) {
923                                outInfos.add(child);
924                            }
925                        }
926                    }
927                }
928            } else {
929                prefetchSiblingsOfRealNode(providerHost, outInfos);
930            }
931        }
932
933        private void prefetchDescendantsOfVirtualNode(AccessibilityNodeInfo root,
934                AccessibilityNodeProvider provider, List<AccessibilityNodeInfo> outInfos) {
935            SparseLongArray childNodeIds = root.getChildNodeIds();
936            final int initialOutInfosSize = outInfos.size();
937            final int childCount = childNodeIds.size();
938            for (int i = 0; i < childCount; i++) {
939                if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
940                    return;
941                }
942                final long childNodeId = childNodeIds.get(i);
943                AccessibilityNodeInfo child = provider.createAccessibilityNodeInfo(
944                        AccessibilityNodeInfo.getVirtualDescendantId(childNodeId));
945                if (child != null) {
946                    outInfos.add(child);
947                }
948            }
949            if (outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
950                final int addedChildCount = outInfos.size() - initialOutInfosSize;
951                for (int i = 0; i < addedChildCount; i++) {
952                    AccessibilityNodeInfo child = outInfos.get(initialOutInfosSize + i);
953                    prefetchDescendantsOfVirtualNode(child, provider, outInfos);
954                }
955            }
956        }
957    }
958
959    private class PrivateHandler extends Handler {
960        private final static int MSG_PERFORM_ACCESSIBILITY_ACTION = 1;
961        private final static int MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID = 2;
962        private final static int MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID = 3;
963        private final static int MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT = 4;
964        private final static int MSG_FIND_FOCUS = 5;
965        private final static int MSG_FOCUS_SEARCH = 6;
966
967        public PrivateHandler(Looper looper) {
968            super(looper);
969        }
970
971        @Override
972        public String getMessageName(Message message) {
973            final int type = message.what;
974            switch (type) {
975                case MSG_PERFORM_ACCESSIBILITY_ACTION:
976                    return "MSG_PERFORM_ACCESSIBILITY_ACTION";
977                case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID:
978                    return "MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID";
979                case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID:
980                    return "MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID";
981                case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT:
982                    return "MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT";
983                case MSG_FIND_FOCUS:
984                    return "MSG_FIND_FOCUS";
985                case MSG_FOCUS_SEARCH:
986                    return "MSG_FOCUS_SEARCH";
987                default:
988                    throw new IllegalArgumentException("Unknown message type: " + type);
989            }
990        }
991
992        @Override
993        public void handleMessage(Message message) {
994            final int type = message.what;
995            switch (type) {
996                case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID: {
997                    findAccessibilityNodeInfoByAccessibilityIdUiThread(message);
998                } break;
999                case MSG_PERFORM_ACCESSIBILITY_ACTION: {
1000                    perfromAccessibilityActionUiThread(message);
1001                } break;
1002                case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID: {
1003                    findAccessibilityNodeInfoByViewIdUiThread(message);
1004                } break;
1005                case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT: {
1006                    findAccessibilityNodeInfosByTextUiThread(message);
1007                } break;
1008                case MSG_FIND_FOCUS: {
1009                    findFocusUiThread(message);
1010                } break;
1011                case MSG_FOCUS_SEARCH: {
1012                    focusSearchUiThread(message);
1013                } break;
1014                default:
1015                    throw new IllegalArgumentException("Unknown message type: " + type);
1016            }
1017        }
1018    }
1019}
1020