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