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