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