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                         // The focused view not shown, we failed.
    410                         if (!isShown(host)) {
    411                             break;
    412                         }
    413                         // If the host has a provider ask this provider to search for the
    414                         // focus instead fetching all provider nodes to do the search here.
    415                         AccessibilityNodeProvider provider = host.getAccessibilityNodeProvider();
    416                         if (provider != null) {
    417                             if (mViewRootImpl.mAccessibilityFocusedVirtualView != null) {
    418                                 focused = AccessibilityNodeInfo.obtain(
    419                                         mViewRootImpl.mAccessibilityFocusedVirtualView);
    420                             }
    421                         } else if (virtualDescendantId == View.NO_ID) {
    422                             focused = host.createAccessibilityNodeInfo();
    423                         }
    424                     } break;
    425                     case AccessibilityNodeInfo.FOCUS_INPUT: {
    426                         View target = root.findFocus();
    427                         if (target == null || !isShown(target)) {
    428                             break;
    429                         }
    430                         AccessibilityNodeProvider provider = target.getAccessibilityNodeProvider();
    431                         if (provider != null) {
    432                             focused = provider.findFocus(focusType);
    433                         }
    434                         if (focused == null) {
    435                             focused = target.createAccessibilityNodeInfo();
    436                         }
    437                     } break;
    438                     default:
    439                         throw new IllegalArgumentException("Unknown focus type: " + focusType);
    440                 }
    441             }
    442         } finally {
    443             try {
    444                 mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
    445                 applyAppScaleAndMagnificationSpecIfNeeded(focused, spec);
    446                 if (spec != null) {
    447                     spec.recycle();
    448                 }
    449                 callback.setFindAccessibilityNodeInfoResult(focused, interactionId);
    450             } catch (RemoteException re) {
    451                 /* ignore - the other side will time out */
    452             }
    453         }
    454     }
    455 
    456     public void focusSearchClientThread(long accessibilityNodeId, int direction, int interactionId,
    457             IAccessibilityInteractionConnectionCallback callback, int flags, int interogatingPid,
    458             long interrogatingTid, MagnificationSpec spec) {
    459         Message message = mHandler.obtainMessage();
    460         message.what = PrivateHandler.MSG_FOCUS_SEARCH;
    461         message.arg1 = flags;
    462         message.arg2 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
    463 
    464         SomeArgs args = SomeArgs.obtain();
    465         args.argi2 = direction;
    466         args.argi3 = interactionId;
    467         args.arg1 = callback;
    468         args.arg2 = spec;
    469 
    470         message.obj = args;
    471 
    472         // If the interrogation is performed by the same thread as the main UI
    473         // thread in this process, set the message as a static reference so
    474         // after this call completes the same thread but in the interrogating
    475         // client can handle the message to generate the result.
    476         if (interogatingPid == mMyProcessId && interrogatingTid == mMyLooperThreadId) {
    477             AccessibilityInteractionClient.getInstanceForThread(
    478                     interrogatingTid).setSameThreadMessage(message);
    479         } else {
    480             mHandler.sendMessage(message);
    481         }
    482     }
    483 
    484     private void focusSearchUiThread(Message message) {
    485         final int flags = message.arg1;
    486         final int accessibilityViewId = message.arg2;
    487 
    488         SomeArgs args = (SomeArgs) message.obj;
    489         final int direction = args.argi2;
    490         final int interactionId = args.argi3;
    491         final IAccessibilityInteractionConnectionCallback callback =
    492             (IAccessibilityInteractionConnectionCallback) args.arg1;
    493         final MagnificationSpec spec = (MagnificationSpec) args.arg2;
    494 
    495         args.recycle();
    496 
    497         AccessibilityNodeInfo next = null;
    498         try {
    499             if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
    500                 return;
    501             }
    502             mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
    503             View root = null;
    504             if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED) {
    505                 root = findViewByAccessibilityId(accessibilityViewId);
    506             } else {
    507                 root = mViewRootImpl.mView;
    508             }
    509             if (root != null && isShown(root)) {
    510                 View nextView = root.focusSearch(direction);
    511                 if (nextView != null) {
    512                     next = nextView.createAccessibilityNodeInfo();
    513                 }
    514             }
    515         } finally {
    516             try {
    517                 mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
    518                 applyAppScaleAndMagnificationSpecIfNeeded(next, spec);
    519                 if (spec != null) {
    520                     spec.recycle();
    521                 }
    522                 callback.setFindAccessibilityNodeInfoResult(next, interactionId);
    523             } catch (RemoteException re) {
    524                 /* ignore - the other side will time out */
    525             }
    526         }
    527     }
    528 
    529     public void performAccessibilityActionClientThread(long accessibilityNodeId, int action,
    530             Bundle arguments, int interactionId,
    531             IAccessibilityInteractionConnectionCallback callback, int flags, int interogatingPid,
    532             long interrogatingTid) {
    533         Message message = mHandler.obtainMessage();
    534         message.what = PrivateHandler.MSG_PERFORM_ACCESSIBILITY_ACTION;
    535         message.arg1 = flags;
    536         message.arg2 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
    537 
    538         SomeArgs args = SomeArgs.obtain();
    539         args.argi1 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId);
    540         args.argi2 = action;
    541         args.argi3 = interactionId;
    542         args.arg1 = callback;
    543         args.arg2 = arguments;
    544 
    545         message.obj = args;
    546 
    547         // If the interrogation is performed by the same thread as the main UI
    548         // thread in this process, set the message as a static reference so
    549         // after this call completes the same thread but in the interrogating
    550         // client can handle the message to generate the result.
    551         if (interogatingPid == mMyProcessId && interrogatingTid == mMyLooperThreadId) {
    552             AccessibilityInteractionClient.getInstanceForThread(
    553                     interrogatingTid).setSameThreadMessage(message);
    554         } else {
    555             mHandler.sendMessage(message);
    556         }
    557     }
    558 
    559     private void perfromAccessibilityActionUiThread(Message message) {
    560         final int flags = message.arg1;
    561         final int accessibilityViewId = message.arg2;
    562 
    563         SomeArgs args = (SomeArgs) message.obj;
    564         final int virtualDescendantId = args.argi1;
    565         final int action = args.argi2;
    566         final int interactionId = args.argi3;
    567         final IAccessibilityInteractionConnectionCallback callback =
    568             (IAccessibilityInteractionConnectionCallback) args.arg1;
    569         Bundle arguments = (Bundle) args.arg2;
    570 
    571         args.recycle();
    572 
    573         boolean succeeded = false;
    574         try {
    575             if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
    576                 return;
    577             }
    578             mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
    579             View target = null;
    580             if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED) {
    581                 target = findViewByAccessibilityId(accessibilityViewId);
    582             } else {
    583                 target = mViewRootImpl.mView;
    584             }
    585             if (target != null && isShown(target)) {
    586                 AccessibilityNodeProvider provider = target.getAccessibilityNodeProvider();
    587                 if (provider != null) {
    588                     succeeded = provider.performAction(virtualDescendantId, action,
    589                             arguments);
    590                 } else if (virtualDescendantId == View.NO_ID) {
    591                     succeeded = target.performAccessibilityAction(action, arguments);
    592                 }
    593             }
    594         } finally {
    595             try {
    596                 mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
    597                 callback.setPerformAccessibilityActionResult(succeeded, interactionId);
    598             } catch (RemoteException re) {
    599                 /* ignore - the other side will time out */
    600             }
    601         }
    602     }
    603 
    604     private View findViewByAccessibilityId(int accessibilityId) {
    605         View root = mViewRootImpl.mView;
    606         if (root == null) {
    607             return null;
    608         }
    609         View foundView = root.findViewByAccessibilityId(accessibilityId);
    610         if (foundView != null && !isShown(foundView)) {
    611             return null;
    612         }
    613         return foundView;
    614     }
    615 
    616     private void applyAppScaleAndMagnificationSpecIfNeeded(List<AccessibilityNodeInfo> infos,
    617             MagnificationSpec spec) {
    618         if (infos == null) {
    619             return;
    620         }
    621         final float applicationScale = mViewRootImpl.mAttachInfo.mApplicationScale;
    622         if (shouldApplyAppScaleAndMagnificationSpec(applicationScale, spec)) {
    623             final int infoCount = infos.size();
    624             for (int i = 0; i < infoCount; i++) {
    625                 AccessibilityNodeInfo info = infos.get(i);
    626                 applyAppScaleAndMagnificationSpecIfNeeded(info, spec);
    627             }
    628         }
    629     }
    630 
    631     private void applyAppScaleAndMagnificationSpecIfNeeded(AccessibilityNodeInfo info,
    632             MagnificationSpec spec) {
    633         if (info == null) {
    634             return;
    635         }
    636 
    637         final float applicationScale = mViewRootImpl.mAttachInfo.mApplicationScale;
    638         if (!shouldApplyAppScaleAndMagnificationSpec(applicationScale, spec)) {
    639             return;
    640         }
    641 
    642         Rect boundsInParent = mTempRect;
    643         Rect boundsInScreen = mTempRect1;
    644 
    645         info.getBoundsInParent(boundsInParent);
    646         info.getBoundsInScreen(boundsInScreen);
    647         if (applicationScale != 1.0f) {
    648             boundsInParent.scale(applicationScale);
    649             boundsInScreen.scale(applicationScale);
    650         }
    651         if (spec != null) {
    652             boundsInParent.scale(spec.scale);
    653             // boundsInParent must not be offset.
    654             boundsInScreen.scale(spec.scale);
    655             boundsInScreen.offset((int) spec.offsetX, (int) spec.offsetY);
    656         }
    657         info.setBoundsInParent(boundsInParent);
    658         info.setBoundsInScreen(boundsInScreen);
    659 
    660         if (spec != null) {
    661             AttachInfo attachInfo = mViewRootImpl.mAttachInfo;
    662             if (attachInfo.mDisplay == null) {
    663                 return;
    664             }
    665 
    666             final float scale = attachInfo.mApplicationScale * spec.scale;
    667 
    668             Rect visibleWinFrame = mTempRect1;
    669             visibleWinFrame.left = (int) (attachInfo.mWindowLeft * scale + spec.offsetX);
    670             visibleWinFrame.top = (int) (attachInfo.mWindowTop * scale + spec.offsetY);
    671             visibleWinFrame.right = (int) (visibleWinFrame.left + mViewRootImpl.mWidth * scale);
    672             visibleWinFrame.bottom = (int) (visibleWinFrame.top + mViewRootImpl.mHeight * scale);
    673 
    674             attachInfo.mDisplay.getRealSize(mTempPoint);
    675             final int displayWidth = mTempPoint.x;
    676             final int displayHeight = mTempPoint.y;
    677 
    678             Rect visibleDisplayFrame = mTempRect2;
    679             visibleDisplayFrame.set(0, 0, displayWidth, displayHeight);
    680 
    681             visibleWinFrame.intersect(visibleDisplayFrame);
    682 
    683             if (!visibleWinFrame.intersects(boundsInScreen.left, boundsInScreen.top,
    684                     boundsInScreen.right, boundsInScreen.bottom)) {
    685                 info.setVisibleToUser(false);
    686             }
    687         }
    688     }
    689 
    690     private boolean shouldApplyAppScaleAndMagnificationSpec(float appScale,
    691             MagnificationSpec spec) {
    692         return (appScale != 1.0f || (spec != null && !spec.isNop()));
    693     }
    694 
    695     /**
    696      * This class encapsulates a prefetching strategy for the accessibility APIs for
    697      * querying window content. It is responsible to prefetch a batch of
    698      * AccessibilityNodeInfos in addition to the one for a requested node.
    699      */
    700     private class AccessibilityNodePrefetcher {
    701 
    702         private static final int MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE = 50;
    703 
    704         private final ArrayList<View> mTempViewList = new ArrayList<View>();
    705 
    706         public void prefetchAccessibilityNodeInfos(View view, int virtualViewId, int fetchFlags,
    707                 List<AccessibilityNodeInfo> outInfos) {
    708             AccessibilityNodeProvider provider = view.getAccessibilityNodeProvider();
    709             if (provider == null) {
    710                 AccessibilityNodeInfo root = view.createAccessibilityNodeInfo();
    711                 if (root != null) {
    712                     outInfos.add(root);
    713                     if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) != 0) {
    714                         prefetchPredecessorsOfRealNode(view, outInfos);
    715                     }
    716                     if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS) != 0) {
    717                         prefetchSiblingsOfRealNode(view, outInfos);
    718                     }
    719                     if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS) != 0) {
    720                         prefetchDescendantsOfRealNode(view, outInfos);
    721                     }
    722                 }
    723             } else {
    724                 AccessibilityNodeInfo root = provider.createAccessibilityNodeInfo(virtualViewId);
    725                 if (root != null) {
    726                     outInfos.add(root);
    727                     if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) != 0) {
    728                         prefetchPredecessorsOfVirtualNode(root, view, provider, outInfos);
    729                     }
    730                     if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS) != 0) {
    731                         prefetchSiblingsOfVirtualNode(root, view, provider, outInfos);
    732                     }
    733                     if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS) != 0) {
    734                         prefetchDescendantsOfVirtualNode(root, provider, outInfos);
    735                     }
    736                 }
    737             }
    738         }
    739 
    740         private void prefetchPredecessorsOfRealNode(View view,
    741                 List<AccessibilityNodeInfo> outInfos) {
    742             ViewParent parent = view.getParentForAccessibility();
    743             while (parent instanceof View
    744                     && outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
    745                 View parentView = (View) parent;
    746                 AccessibilityNodeInfo info = parentView.createAccessibilityNodeInfo();
    747                 if (info != null) {
    748                     outInfos.add(info);
    749                 }
    750                 parent = parent.getParentForAccessibility();
    751             }
    752         }
    753 
    754         private void prefetchSiblingsOfRealNode(View current,
    755                 List<AccessibilityNodeInfo> outInfos) {
    756             ViewParent parent = current.getParentForAccessibility();
    757             if (parent instanceof ViewGroup) {
    758                 ViewGroup parentGroup = (ViewGroup) parent;
    759                 ArrayList<View> children = mTempViewList;
    760                 children.clear();
    761                 try {
    762                     parentGroup.addChildrenForAccessibility(children);
    763                     final int childCount = children.size();
    764                     for (int i = 0; i < childCount; i++) {
    765                         if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
    766                             return;
    767                         }
    768                         View child = children.get(i);
    769                         if (child.getAccessibilityViewId() != current.getAccessibilityViewId()
    770                                 &&  isShown(child)) {
    771                             AccessibilityNodeInfo info = null;
    772                             AccessibilityNodeProvider provider =
    773                                 child.getAccessibilityNodeProvider();
    774                             if (provider == null) {
    775                                 info = child.createAccessibilityNodeInfo();
    776                             } else {
    777                                 info = provider.createAccessibilityNodeInfo(
    778                                         AccessibilityNodeInfo.UNDEFINED);
    779                             }
    780                             if (info != null) {
    781                                 outInfos.add(info);
    782                             }
    783                         }
    784                     }
    785                 } finally {
    786                     children.clear();
    787                 }
    788             }
    789         }
    790 
    791         private void prefetchDescendantsOfRealNode(View root,
    792                 List<AccessibilityNodeInfo> outInfos) {
    793             if (!(root instanceof ViewGroup)) {
    794                 return;
    795             }
    796             HashMap<View, AccessibilityNodeInfo> addedChildren =
    797                 new HashMap<View, AccessibilityNodeInfo>();
    798             ArrayList<View> children = mTempViewList;
    799             children.clear();
    800             try {
    801                 root.addChildrenForAccessibility(children);
    802                 final int childCount = children.size();
    803                 for (int i = 0; i < childCount; i++) {
    804                     if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
    805                         return;
    806                     }
    807                     View child = children.get(i);
    808                     if (isShown(child)) {
    809                         AccessibilityNodeProvider provider = child.getAccessibilityNodeProvider();
    810                         if (provider == null) {
    811                             AccessibilityNodeInfo info = child.createAccessibilityNodeInfo();
    812                             if (info != null) {
    813                                 outInfos.add(info);
    814                                 addedChildren.put(child, null);
    815                             }
    816                         } else {
    817                             AccessibilityNodeInfo info = provider.createAccessibilityNodeInfo(
    818                                    AccessibilityNodeInfo.UNDEFINED);
    819                             if (info != null) {
    820                                 outInfos.add(info);
    821                                 addedChildren.put(child, info);
    822                             }
    823                         }
    824                     }
    825                 }
    826             } finally {
    827                 children.clear();
    828             }
    829             if (outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
    830                 for (Map.Entry<View, AccessibilityNodeInfo> entry : addedChildren.entrySet()) {
    831                     View addedChild = entry.getKey();
    832                     AccessibilityNodeInfo virtualRoot = entry.getValue();
    833                     if (virtualRoot == null) {
    834                         prefetchDescendantsOfRealNode(addedChild, outInfos);
    835                     } else {
    836                         AccessibilityNodeProvider provider =
    837                             addedChild.getAccessibilityNodeProvider();
    838                         prefetchDescendantsOfVirtualNode(virtualRoot, provider, outInfos);
    839                     }
    840                 }
    841             }
    842         }
    843 
    844         private void prefetchPredecessorsOfVirtualNode(AccessibilityNodeInfo root,
    845                 View providerHost, AccessibilityNodeProvider provider,
    846                 List<AccessibilityNodeInfo> outInfos) {
    847             long parentNodeId = root.getParentNodeId();
    848             int accessibilityViewId = AccessibilityNodeInfo.getAccessibilityViewId(parentNodeId);
    849             while (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED) {
    850                 if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
    851                     return;
    852                 }
    853                 final int virtualDescendantId =
    854                     AccessibilityNodeInfo.getVirtualDescendantId(parentNodeId);
    855                 if (virtualDescendantId != AccessibilityNodeInfo.UNDEFINED
    856                         || accessibilityViewId == providerHost.getAccessibilityViewId()) {
    857                     AccessibilityNodeInfo parent = provider.createAccessibilityNodeInfo(
    858                             virtualDescendantId);
    859                     if (parent != null) {
    860                         outInfos.add(parent);
    861                     }
    862                     parentNodeId = parent.getParentNodeId();
    863                     accessibilityViewId = AccessibilityNodeInfo.getAccessibilityViewId(
    864                             parentNodeId);
    865                 } else {
    866                     prefetchPredecessorsOfRealNode(providerHost, outInfos);
    867                     return;
    868                 }
    869             }
    870         }
    871 
    872         private void prefetchSiblingsOfVirtualNode(AccessibilityNodeInfo current, View providerHost,
    873                 AccessibilityNodeProvider provider, List<AccessibilityNodeInfo> outInfos) {
    874             final long parentNodeId = current.getParentNodeId();
    875             final int parentAccessibilityViewId =
    876                 AccessibilityNodeInfo.getAccessibilityViewId(parentNodeId);
    877             final int parentVirtualDescendantId =
    878                 AccessibilityNodeInfo.getVirtualDescendantId(parentNodeId);
    879             if (parentVirtualDescendantId != AccessibilityNodeInfo.UNDEFINED
    880                     || parentAccessibilityViewId == providerHost.getAccessibilityViewId()) {
    881                 AccessibilityNodeInfo parent =
    882                     provider.createAccessibilityNodeInfo(parentVirtualDescendantId);
    883                 if (parent != null) {
    884                     SparseLongArray childNodeIds = parent.getChildNodeIds();
    885                     final int childCount = childNodeIds.size();
    886                     for (int i = 0; i < childCount; i++) {
    887                         if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
    888                             return;
    889                         }
    890                         final long childNodeId = childNodeIds.get(i);
    891                         if (childNodeId != current.getSourceNodeId()) {
    892                             final int childVirtualDescendantId =
    893                                 AccessibilityNodeInfo.getVirtualDescendantId(childNodeId);
    894                             AccessibilityNodeInfo child = provider.createAccessibilityNodeInfo(
    895                                     childVirtualDescendantId);
    896                             if (child != null) {
    897                                 outInfos.add(child);
    898                             }
    899                         }
    900                     }
    901                 }
    902             } else {
    903                 prefetchSiblingsOfRealNode(providerHost, outInfos);
    904             }
    905         }
    906 
    907         private void prefetchDescendantsOfVirtualNode(AccessibilityNodeInfo root,
    908                 AccessibilityNodeProvider provider, List<AccessibilityNodeInfo> outInfos) {
    909             SparseLongArray childNodeIds = root.getChildNodeIds();
    910             final int initialOutInfosSize = outInfos.size();
    911             final int childCount = childNodeIds.size();
    912             for (int i = 0; i < childCount; i++) {
    913                 if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
    914                     return;
    915                 }
    916                 final long childNodeId = childNodeIds.get(i);
    917                 AccessibilityNodeInfo child = provider.createAccessibilityNodeInfo(
    918                         AccessibilityNodeInfo.getVirtualDescendantId(childNodeId));
    919                 if (child != null) {
    920                     outInfos.add(child);
    921                 }
    922             }
    923             if (outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
    924                 final int addedChildCount = outInfos.size() - initialOutInfosSize;
    925                 for (int i = 0; i < addedChildCount; i++) {
    926                     AccessibilityNodeInfo child = outInfos.get(initialOutInfosSize + i);
    927                     prefetchDescendantsOfVirtualNode(child, provider, outInfos);
    928                 }
    929             }
    930         }
    931     }
    932 
    933     private class PrivateHandler extends Handler {
    934         private final static int MSG_PERFORM_ACCESSIBILITY_ACTION = 1;
    935         private final static int MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID = 2;
    936         private final static int MSG_FIND_ACCESSIBLITY_NODE_INFOS_BY_VIEW_ID = 3;
    937         private final static int MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT = 4;
    938         private final static int MSG_FIND_FOCUS = 5;
    939         private final static int MSG_FOCUS_SEARCH = 6;
    940 
    941         public PrivateHandler(Looper looper) {
    942             super(looper);
    943         }
    944 
    945         @Override
    946         public String getMessageName(Message message) {
    947             final int type = message.what;
    948             switch (type) {
    949                 case MSG_PERFORM_ACCESSIBILITY_ACTION:
    950                     return "MSG_PERFORM_ACCESSIBILITY_ACTION";
    951                 case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID:
    952                     return "MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID";
    953                 case MSG_FIND_ACCESSIBLITY_NODE_INFOS_BY_VIEW_ID:
    954                     return "MSG_FIND_ACCESSIBLITY_NODE_INFOS_BY_VIEW_ID";
    955                 case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT:
    956                     return "MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT";
    957                 case MSG_FIND_FOCUS:
    958                     return "MSG_FIND_FOCUS";
    959                 case MSG_FOCUS_SEARCH:
    960                     return "MSG_FOCUS_SEARCH";
    961                 default:
    962                     throw new IllegalArgumentException("Unknown message type: " + type);
    963             }
    964         }
    965 
    966         @Override
    967         public void handleMessage(Message message) {
    968             final int type = message.what;
    969             switch (type) {
    970                 case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID: {
    971                     findAccessibilityNodeInfoByAccessibilityIdUiThread(message);
    972                 } break;
    973                 case MSG_PERFORM_ACCESSIBILITY_ACTION: {
    974                     perfromAccessibilityActionUiThread(message);
    975                 } break;
    976                 case MSG_FIND_ACCESSIBLITY_NODE_INFOS_BY_VIEW_ID: {
    977                     findAccessibilityNodeInfosByViewIdUiThread(message);
    978                 } break;
    979                 case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT: {
    980                     findAccessibilityNodeInfosByTextUiThread(message);
    981                 } break;
    982                 case MSG_FIND_FOCUS: {
    983                     findFocusUiThread(message);
    984                 } break;
    985                 case MSG_FOCUS_SEARCH: {
    986                     focusSearchUiThread(message);
    987                 } break;
    988                 default:
    989                     throw new IllegalArgumentException("Unknown message type: " + type);
    990             }
    991         }
    992     }
    993 
    994     private final class AddNodeInfosForViewId implements Predicate<View> {
    995         private int mViewId = View.NO_ID;
    996         private List<AccessibilityNodeInfo> mInfos;
    997 
    998         public void init(int viewId, List<AccessibilityNodeInfo> infos) {
    999             mViewId = viewId;
   1000             mInfos = infos;
   1001         }
   1002 
   1003         public void reset() {
   1004             mViewId = View.NO_ID;
   1005             mInfos = null;
   1006         }
   1007 
   1008         @Override
   1009         public boolean apply(View view) {
   1010             if (view.getId() == mViewId && isShown(view)) {
   1011                 mInfos.add(view.createAccessibilityNodeInfo());
   1012             }
   1013             return false;
   1014         }
   1015     }
   1016 }
   1017