Home | History | Annotate | Download | only in view
      1 /*
      2  * Copyright (C) 2012 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.view;
     18 
     19 import static android.view.accessibility.AccessibilityNodeInfo.ACTION_ARGUMENT_ACCESSIBLE_CLICKABLE_SPAN;
     20 import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_REQUESTED_KEY;
     21 import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY;
     22 
     23 import android.graphics.Point;
     24 import android.graphics.Rect;
     25 import android.graphics.RectF;
     26 import android.graphics.Region;
     27 import android.os.Binder;
     28 import android.os.Bundle;
     29 import android.os.Handler;
     30 import android.os.Looper;
     31 import android.os.Message;
     32 import android.os.Parcelable;
     33 import android.os.Process;
     34 import android.os.RemoteException;
     35 import android.text.style.AccessibilityClickableSpan;
     36 import android.text.style.ClickableSpan;
     37 import android.util.LongSparseArray;
     38 import android.util.Slog;
     39 import android.view.View.AttachInfo;
     40 import android.view.accessibility.AccessibilityInteractionClient;
     41 import android.view.accessibility.AccessibilityManager;
     42 import android.view.accessibility.AccessibilityNodeInfo;
     43 import android.view.accessibility.AccessibilityNodeProvider;
     44 import android.view.accessibility.AccessibilityRequestPreparer;
     45 import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
     46 
     47 import com.android.internal.R;
     48 import com.android.internal.annotations.GuardedBy;
     49 import com.android.internal.os.SomeArgs;
     50 
     51 import java.util.ArrayList;
     52 import java.util.HashMap;
     53 import java.util.HashSet;
     54 import java.util.LinkedList;
     55 import java.util.List;
     56 import java.util.Map;
     57 import java.util.Queue;
     58 import java.util.function.Predicate;
     59 
     60 /**
     61  * Class for managing accessibility interactions initiated from the system
     62  * and targeting the view hierarchy. A *ClientThread method is to be
     63  * called from the interaction connection ViewAncestor gives the system to
     64  * talk to it and a corresponding *UiThread method that is executed on the
     65  * UI thread.
     66  */
     67 final class AccessibilityInteractionController {
     68 
     69     private static final String LOG_TAG = "AccessibilityInteractionController";
     70 
     71     // Debugging flag
     72     private static final boolean ENFORCE_NODE_TREE_CONSISTENT = false;
     73 
     74     // Constants for readability
     75     private static final boolean IGNORE_REQUEST_PREPARERS = true;
     76     private static final boolean CONSIDER_REQUEST_PREPARERS = false;
     77 
     78     // If an app holds off accessibility for longer than this, the hold-off is canceled to prevent
     79     // accessibility from hanging
     80     private static final long REQUEST_PREPARER_TIMEOUT_MS = 500;
     81 
     82     private final ArrayList<AccessibilityNodeInfo> mTempAccessibilityNodeInfoList =
     83         new ArrayList<AccessibilityNodeInfo>();
     84 
     85     private final Object mLock = new Object();
     86 
     87     private final Handler mHandler;
     88 
     89     private final ViewRootImpl mViewRootImpl;
     90 
     91     private final AccessibilityNodePrefetcher mPrefetcher;
     92 
     93     private final long mMyLooperThreadId;
     94 
     95     private final int mMyProcessId;
     96 
     97     private final AccessibilityManager mA11yManager;
     98 
     99     private final ArrayList<View> mTempArrayList = new ArrayList<View>();
    100 
    101     private final Point mTempPoint = new Point();
    102     private final Rect mTempRect = new Rect();
    103     private final Rect mTempRect1 = new Rect();
    104     private final Rect mTempRect2 = new Rect();
    105 
    106     private AddNodeInfosForViewId mAddNodeInfosForViewId;
    107 
    108     @GuardedBy("mLock")
    109     private int mNumActiveRequestPreparers;
    110     @GuardedBy("mLock")
    111     private List<MessageHolder> mMessagesWaitingForRequestPreparer;
    112     @GuardedBy("mLock")
    113     private int mActiveRequestPreparerId;
    114 
    115     public AccessibilityInteractionController(ViewRootImpl viewRootImpl) {
    116         Looper looper =  viewRootImpl.mHandler.getLooper();
    117         mMyLooperThreadId = looper.getThread().getId();
    118         mMyProcessId = Process.myPid();
    119         mHandler = new PrivateHandler(looper);
    120         mViewRootImpl = viewRootImpl;
    121         mPrefetcher = new AccessibilityNodePrefetcher();
    122         mA11yManager = mViewRootImpl.mContext.getSystemService(AccessibilityManager.class);
    123     }
    124 
    125     private void scheduleMessage(Message message, int interrogatingPid, long interrogatingTid,
    126             boolean ignoreRequestPreparers) {
    127         if (ignoreRequestPreparers
    128                 || !holdOffMessageIfNeeded(message, interrogatingPid, interrogatingTid)) {
    129             // If the interrogation is performed by the same thread as the main UI
    130             // thread in this process, set the message as a static reference so
    131             // after this call completes the same thread but in the interrogating
    132             // client can handle the message to generate the result.
    133             if (interrogatingPid == mMyProcessId && interrogatingTid == mMyLooperThreadId) {
    134                 AccessibilityInteractionClient.getInstanceForThread(
    135                         interrogatingTid).setSameThreadMessage(message);
    136             } else {
    137                 mHandler.sendMessage(message);
    138             }
    139         }
    140     }
    141 
    142     private boolean isShown(View view) {
    143         // The first two checks are made also made by isShown() which
    144         // however traverses the tree up to the parent to catch that.
    145         // Therefore, we do some fail fast check to minimize the up
    146         // tree traversal.
    147         return (view.mAttachInfo != null
    148                 && view.mAttachInfo.mWindowVisibility == View.VISIBLE
    149                 && view.isShown());
    150     }
    151 
    152     public void findAccessibilityNodeInfoByAccessibilityIdClientThread(
    153             long accessibilityNodeId, Region interactiveRegion, int interactionId,
    154             IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid,
    155             long interrogatingTid, MagnificationSpec spec, Bundle arguments) {
    156         final Message message = mHandler.obtainMessage();
    157         message.what = PrivateHandler.MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID;
    158         message.arg1 = flags;
    159 
    160         final SomeArgs args = SomeArgs.obtain();
    161         args.argi1 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
    162         args.argi2 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId);
    163         args.argi3 = interactionId;
    164         args.arg1 = callback;
    165         args.arg2 = spec;
    166         args.arg3 = interactiveRegion;
    167         args.arg4 = arguments;
    168         message.obj = args;
    169 
    170         scheduleMessage(message, interrogatingPid, interrogatingTid, CONSIDER_REQUEST_PREPARERS);
    171     }
    172 
    173     /**
    174      * Check if this message needs to be held off while the app prepares to meet either this
    175      * request, or a request ahead of it.
    176      *
    177      * @param originalMessage The message to be processed
    178      * @param callingPid The calling process id
    179      * @param callingTid The calling thread id
    180      *
    181      * @return {@code true} if the message is held off and will be processed later, {@code false} if
    182      *         the message should be posted.
    183      */
    184     private boolean holdOffMessageIfNeeded(
    185             Message originalMessage, int callingPid, long callingTid) {
    186         synchronized (mLock) {
    187             // If a request is already pending, queue this request for when it's finished
    188             if (mNumActiveRequestPreparers != 0) {
    189                 queueMessageToHandleOncePrepared(originalMessage, callingPid, callingTid);
    190                 return true;
    191             }
    192 
    193             // Currently the only message that can hold things off is findByA11yId with extra data.
    194             if (originalMessage.what
    195                     != PrivateHandler.MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID) {
    196                 return false;
    197             }
    198             SomeArgs originalMessageArgs = (SomeArgs) originalMessage.obj;
    199             Bundle requestArguments = (Bundle) originalMessageArgs.arg4;
    200             if (requestArguments == null) {
    201                 return false;
    202             }
    203 
    204             // If nothing it registered for this view, nothing to do
    205             int accessibilityViewId = originalMessageArgs.argi1;
    206             final List<AccessibilityRequestPreparer> preparers =
    207                     mA11yManager.getRequestPreparersForAccessibilityId(accessibilityViewId);
    208             if (preparers == null) {
    209                 return false;
    210             }
    211 
    212             // If the bundle doesn't request the extra data, nothing to do
    213             final String extraDataKey = requestArguments.getString(EXTRA_DATA_REQUESTED_KEY);
    214             if (extraDataKey == null) {
    215                 return false;
    216             }
    217 
    218             // Send the request to the AccessibilityRequestPreparers on the UI thread
    219             mNumActiveRequestPreparers = preparers.size();
    220             for (int i = 0; i < preparers.size(); i++) {
    221                 final Message requestPreparerMessage = mHandler.obtainMessage(
    222                         PrivateHandler.MSG_PREPARE_FOR_EXTRA_DATA_REQUEST);
    223                 final SomeArgs requestPreparerArgs = SomeArgs.obtain();
    224                 // virtualDescendentId
    225                 requestPreparerArgs.argi1 =
    226                         (originalMessageArgs.argi2 == AccessibilityNodeInfo.UNDEFINED_ITEM_ID)
    227                         ? AccessibilityNodeProvider.HOST_VIEW_ID : originalMessageArgs.argi2;
    228                 requestPreparerArgs.arg1 = preparers.get(i);
    229                 requestPreparerArgs.arg2 = extraDataKey;
    230                 requestPreparerArgs.arg3 = requestArguments;
    231                 Message preparationFinishedMessage = mHandler.obtainMessage(
    232                         PrivateHandler.MSG_APP_PREPARATION_FINISHED);
    233                 preparationFinishedMessage.arg1 = ++mActiveRequestPreparerId;
    234                 requestPreparerArgs.arg4 = preparationFinishedMessage;
    235 
    236                 requestPreparerMessage.obj = requestPreparerArgs;
    237                 scheduleMessage(requestPreparerMessage, callingPid, callingTid,
    238                         IGNORE_REQUEST_PREPARERS);
    239                 mHandler.obtainMessage(PrivateHandler.MSG_APP_PREPARATION_TIMEOUT);
    240                 mHandler.sendEmptyMessageDelayed(PrivateHandler.MSG_APP_PREPARATION_TIMEOUT,
    241                         REQUEST_PREPARER_TIMEOUT_MS);
    242             }
    243 
    244             // Set the initial request aside
    245             queueMessageToHandleOncePrepared(originalMessage, callingPid, callingTid);
    246             return true;
    247         }
    248     }
    249 
    250     private void prepareForExtraDataRequestUiThread(Message message) {
    251         SomeArgs args = (SomeArgs) message.obj;
    252         final int virtualDescendantId = args.argi1;
    253         final AccessibilityRequestPreparer preparer = (AccessibilityRequestPreparer) args.arg1;
    254         final String extraDataKey = (String) args.arg2;
    255         final Bundle requestArguments = (Bundle) args.arg3;
    256         final Message preparationFinishedMessage = (Message) args.arg4;
    257 
    258         preparer.onPrepareExtraData(virtualDescendantId, extraDataKey,
    259                 requestArguments, preparationFinishedMessage);
    260     }
    261 
    262     private void queueMessageToHandleOncePrepared(Message message, int interrogatingPid,
    263             long interrogatingTid) {
    264         if (mMessagesWaitingForRequestPreparer == null) {
    265             mMessagesWaitingForRequestPreparer = new ArrayList<>(1);
    266         }
    267         MessageHolder messageHolder =
    268                 new MessageHolder(message, interrogatingPid, interrogatingTid);
    269         mMessagesWaitingForRequestPreparer.add(messageHolder);
    270     }
    271 
    272     private void requestPreparerDoneUiThread(Message message) {
    273         synchronized (mLock) {
    274             if (message.arg1 != mActiveRequestPreparerId) {
    275                 Slog.e(LOG_TAG, "Surprising AccessibilityRequestPreparer callback (likely late)");
    276                 return;
    277             }
    278             mNumActiveRequestPreparers--;
    279             if (mNumActiveRequestPreparers <= 0) {
    280                 mHandler.removeMessages(PrivateHandler.MSG_APP_PREPARATION_TIMEOUT);
    281                 scheduleAllMessagesWaitingForRequestPreparerLocked();
    282             }
    283         }
    284     }
    285 
    286     private void requestPreparerTimeoutUiThread() {
    287         synchronized (mLock) {
    288             Slog.e(LOG_TAG, "AccessibilityRequestPreparer timed out");
    289             scheduleAllMessagesWaitingForRequestPreparerLocked();
    290         }
    291     }
    292 
    293     @GuardedBy("mLock")
    294     private void scheduleAllMessagesWaitingForRequestPreparerLocked() {
    295         int numMessages = mMessagesWaitingForRequestPreparer.size();
    296         for (int i = 0; i < numMessages; i++) {
    297             MessageHolder request = mMessagesWaitingForRequestPreparer.get(i);
    298             scheduleMessage(request.mMessage, request.mInterrogatingPid,
    299                     request.mInterrogatingTid,
    300                     (i == 0) /* the app is ready for the first request */);
    301         }
    302         mMessagesWaitingForRequestPreparer.clear();
    303         mNumActiveRequestPreparers = 0; // Just to be safe - should be unnecessary
    304         mActiveRequestPreparerId = -1;
    305     }
    306 
    307     private void findAccessibilityNodeInfoByAccessibilityIdUiThread(Message message) {
    308         final int flags = message.arg1;
    309 
    310         SomeArgs args = (SomeArgs) message.obj;
    311         final int accessibilityViewId = args.argi1;
    312         final int virtualDescendantId = args.argi2;
    313         final int interactionId = args.argi3;
    314         final IAccessibilityInteractionConnectionCallback callback =
    315             (IAccessibilityInteractionConnectionCallback) args.arg1;
    316         final MagnificationSpec spec = (MagnificationSpec) args.arg2;
    317         final Region interactiveRegion = (Region) args.arg3;
    318         final Bundle arguments = (Bundle) args.arg4;
    319 
    320         args.recycle();
    321 
    322         List<AccessibilityNodeInfo> infos = mTempAccessibilityNodeInfoList;
    323         infos.clear();
    324         try {
    325             if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
    326                 return;
    327             }
    328             mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
    329             View root = null;
    330             if (accessibilityViewId == AccessibilityNodeInfo.ROOT_ITEM_ID) {
    331                 root = mViewRootImpl.mView;
    332             } else {
    333                 root = findViewByAccessibilityId(accessibilityViewId);
    334             }
    335             if (root != null && isShown(root)) {
    336                 mPrefetcher.prefetchAccessibilityNodeInfos(
    337                         root, virtualDescendantId, flags, infos, arguments);
    338             }
    339         } finally {
    340             updateInfosForViewportAndReturnFindNodeResult(
    341                     infos, callback, interactionId, spec, interactiveRegion);
    342         }
    343     }
    344 
    345     public void findAccessibilityNodeInfosByViewIdClientThread(long accessibilityNodeId,
    346             String viewId, Region interactiveRegion, int interactionId,
    347             IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid,
    348             long interrogatingTid, MagnificationSpec spec) {
    349         Message message = mHandler.obtainMessage();
    350         message.what = PrivateHandler.MSG_FIND_ACCESSIBILITY_NODE_INFOS_BY_VIEW_ID;
    351         message.arg1 = flags;
    352         message.arg2 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
    353 
    354         SomeArgs args = SomeArgs.obtain();
    355         args.argi1 = interactionId;
    356         args.arg1 = callback;
    357         args.arg2 = spec;
    358         args.arg3 = viewId;
    359         args.arg4 = interactiveRegion;
    360         message.obj = args;
    361 
    362         scheduleMessage(message, interrogatingPid, interrogatingTid, CONSIDER_REQUEST_PREPARERS);
    363     }
    364 
    365     private void findAccessibilityNodeInfosByViewIdUiThread(Message message) {
    366         final int flags = message.arg1;
    367         final int accessibilityViewId = message.arg2;
    368 
    369         SomeArgs args = (SomeArgs) message.obj;
    370         final int interactionId = args.argi1;
    371         final IAccessibilityInteractionConnectionCallback callback =
    372             (IAccessibilityInteractionConnectionCallback) args.arg1;
    373         final MagnificationSpec spec = (MagnificationSpec) args.arg2;
    374         final String viewId = (String) args.arg3;
    375         final Region interactiveRegion = (Region) args.arg4;
    376         args.recycle();
    377 
    378         final List<AccessibilityNodeInfo> infos = mTempAccessibilityNodeInfoList;
    379         infos.clear();
    380         try {
    381             if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
    382                 return;
    383             }
    384             mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
    385             View root = null;
    386             if (accessibilityViewId != AccessibilityNodeInfo.ROOT_ITEM_ID) {
    387                 root = findViewByAccessibilityId(accessibilityViewId);
    388             } else {
    389                 root = mViewRootImpl.mView;
    390             }
    391             if (root != null) {
    392                 final int resolvedViewId = root.getContext().getResources()
    393                         .getIdentifier(viewId, null, null);
    394                 if (resolvedViewId <= 0) {
    395                     return;
    396                 }
    397                 if (mAddNodeInfosForViewId == null) {
    398                     mAddNodeInfosForViewId = new AddNodeInfosForViewId();
    399                 }
    400                 mAddNodeInfosForViewId.init(resolvedViewId, infos);
    401                 root.findViewByPredicate(mAddNodeInfosForViewId);
    402                 mAddNodeInfosForViewId.reset();
    403             }
    404         } finally {
    405             updateInfosForViewportAndReturnFindNodeResult(
    406                     infos, callback, interactionId, spec, interactiveRegion);
    407         }
    408     }
    409 
    410     public void findAccessibilityNodeInfosByTextClientThread(long accessibilityNodeId,
    411             String text, Region interactiveRegion, int interactionId,
    412             IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid,
    413             long interrogatingTid, MagnificationSpec spec) {
    414         Message message = mHandler.obtainMessage();
    415         message.what = PrivateHandler.MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_TEXT;
    416         message.arg1 = flags;
    417 
    418         SomeArgs args = SomeArgs.obtain();
    419         args.arg1 = text;
    420         args.arg2 = callback;
    421         args.arg3 = spec;
    422         args.argi1 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
    423         args.argi2 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId);
    424         args.argi3 = interactionId;
    425         args.arg4 = interactiveRegion;
    426         message.obj = args;
    427 
    428         scheduleMessage(message, interrogatingPid, interrogatingTid, CONSIDER_REQUEST_PREPARERS);
    429     }
    430 
    431     private void findAccessibilityNodeInfosByTextUiThread(Message message) {
    432         final int flags = message.arg1;
    433 
    434         SomeArgs args = (SomeArgs) message.obj;
    435         final String text = (String) args.arg1;
    436         final IAccessibilityInteractionConnectionCallback callback =
    437             (IAccessibilityInteractionConnectionCallback) args.arg2;
    438         final MagnificationSpec spec = (MagnificationSpec) args.arg3;
    439         final int accessibilityViewId = args.argi1;
    440         final int virtualDescendantId = args.argi2;
    441         final int interactionId = args.argi3;
    442         final Region interactiveRegion = (Region) args.arg4;
    443         args.recycle();
    444 
    445         List<AccessibilityNodeInfo> infos = null;
    446         try {
    447             if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
    448                 return;
    449             }
    450             mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
    451             View root = null;
    452             if (accessibilityViewId != AccessibilityNodeInfo.ROOT_ITEM_ID) {
    453                 root = findViewByAccessibilityId(accessibilityViewId);
    454             } else {
    455                 root = mViewRootImpl.mView;
    456             }
    457             if (root != null && isShown(root)) {
    458                 AccessibilityNodeProvider provider = root.getAccessibilityNodeProvider();
    459                 if (provider != null) {
    460                     infos = provider.findAccessibilityNodeInfosByText(text,
    461                             virtualDescendantId);
    462                 } else if (virtualDescendantId == AccessibilityNodeProvider.HOST_VIEW_ID) {
    463                     ArrayList<View> foundViews = mTempArrayList;
    464                     foundViews.clear();
    465                     root.findViewsWithText(foundViews, text, View.FIND_VIEWS_WITH_TEXT
    466                             | View.FIND_VIEWS_WITH_CONTENT_DESCRIPTION
    467                             | View.FIND_VIEWS_WITH_ACCESSIBILITY_NODE_PROVIDERS);
    468                     if (!foundViews.isEmpty()) {
    469                         infos = mTempAccessibilityNodeInfoList;
    470                         infos.clear();
    471                         final int viewCount = foundViews.size();
    472                         for (int i = 0; i < viewCount; i++) {
    473                             View foundView = foundViews.get(i);
    474                             if (isShown(foundView)) {
    475                                 provider = foundView.getAccessibilityNodeProvider();
    476                                 if (provider != null) {
    477                                     List<AccessibilityNodeInfo> infosFromProvider =
    478                                         provider.findAccessibilityNodeInfosByText(text,
    479                                                 AccessibilityNodeProvider.HOST_VIEW_ID);
    480                                     if (infosFromProvider != null) {
    481                                         infos.addAll(infosFromProvider);
    482                                     }
    483                                 } else  {
    484                                     infos.add(foundView.createAccessibilityNodeInfo());
    485                                 }
    486                             }
    487                         }
    488                     }
    489                 }
    490             }
    491         } finally {
    492             updateInfosForViewportAndReturnFindNodeResult(
    493                     infos, callback, interactionId, spec, interactiveRegion);
    494         }
    495     }
    496 
    497     public void findFocusClientThread(long accessibilityNodeId, int focusType,
    498             Region interactiveRegion, int interactionId,
    499             IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid,
    500             long interrogatingTid, MagnificationSpec spec) {
    501         Message message = mHandler.obtainMessage();
    502         message.what = PrivateHandler.MSG_FIND_FOCUS;
    503         message.arg1 = flags;
    504         message.arg2 = focusType;
    505 
    506         SomeArgs args = SomeArgs.obtain();
    507         args.argi1 = interactionId;
    508         args.argi2 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
    509         args.argi3 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId);
    510         args.arg1 = callback;
    511         args.arg2 = spec;
    512         args.arg3 = interactiveRegion;
    513 
    514         message.obj = args;
    515 
    516         scheduleMessage(message, interrogatingPid, interrogatingTid, CONSIDER_REQUEST_PREPARERS);
    517     }
    518 
    519     private void findFocusUiThread(Message message) {
    520         final int flags = message.arg1;
    521         final int focusType = message.arg2;
    522 
    523         SomeArgs args = (SomeArgs) message.obj;
    524         final int interactionId = args.argi1;
    525         final int accessibilityViewId = args.argi2;
    526         final int virtualDescendantId = args.argi3;
    527         final IAccessibilityInteractionConnectionCallback callback =
    528             (IAccessibilityInteractionConnectionCallback) args.arg1;
    529         final MagnificationSpec spec = (MagnificationSpec) args.arg2;
    530         final Region interactiveRegion = (Region) args.arg3;
    531         args.recycle();
    532 
    533         AccessibilityNodeInfo focused = null;
    534         try {
    535             if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
    536                 return;
    537             }
    538             mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
    539             View root = null;
    540             if (accessibilityViewId != AccessibilityNodeInfo.ROOT_ITEM_ID) {
    541                 root = findViewByAccessibilityId(accessibilityViewId);
    542             } else {
    543                 root = mViewRootImpl.mView;
    544             }
    545             if (root != null && isShown(root)) {
    546                 switch (focusType) {
    547                     case AccessibilityNodeInfo.FOCUS_ACCESSIBILITY: {
    548                         View host = mViewRootImpl.mAccessibilityFocusedHost;
    549                         // If there is no accessibility focus host or it is not a descendant
    550                         // of the root from which to start the search, then the search failed.
    551                         if (host == null || !ViewRootImpl.isViewDescendantOf(host, root)) {
    552                             break;
    553                         }
    554                         // The focused view not shown, we failed.
    555                         if (!isShown(host)) {
    556                             break;
    557                         }
    558                         // If the host has a provider ask this provider to search for the
    559                         // focus instead fetching all provider nodes to do the search here.
    560                         AccessibilityNodeProvider provider = host.getAccessibilityNodeProvider();
    561                         if (provider != null) {
    562                             if (mViewRootImpl.mAccessibilityFocusedVirtualView != null) {
    563                                 focused = AccessibilityNodeInfo.obtain(
    564                                         mViewRootImpl.mAccessibilityFocusedVirtualView);
    565                             }
    566                         } else if (virtualDescendantId == AccessibilityNodeProvider.HOST_VIEW_ID) {
    567                             focused = host.createAccessibilityNodeInfo();
    568                         }
    569                     } break;
    570                     case AccessibilityNodeInfo.FOCUS_INPUT: {
    571                         View target = root.findFocus();
    572                         if (target == null || !isShown(target)) {
    573                             break;
    574                         }
    575                         AccessibilityNodeProvider provider = target.getAccessibilityNodeProvider();
    576                         if (provider != null) {
    577                             focused = provider.findFocus(focusType);
    578                         }
    579                         if (focused == null) {
    580                             focused = target.createAccessibilityNodeInfo();
    581                         }
    582                     } break;
    583                     default:
    584                         throw new IllegalArgumentException("Unknown focus type: " + focusType);
    585                 }
    586             }
    587         } finally {
    588             updateInfoForViewportAndReturnFindNodeResult(
    589                     focused, callback, interactionId, spec, interactiveRegion);
    590         }
    591     }
    592 
    593     public void focusSearchClientThread(long accessibilityNodeId, int direction,
    594             Region interactiveRegion, int interactionId,
    595             IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid,
    596             long interrogatingTid, MagnificationSpec spec) {
    597         Message message = mHandler.obtainMessage();
    598         message.what = PrivateHandler.MSG_FOCUS_SEARCH;
    599         message.arg1 = flags;
    600         message.arg2 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
    601 
    602         SomeArgs args = SomeArgs.obtain();
    603         args.argi2 = direction;
    604         args.argi3 = interactionId;
    605         args.arg1 = callback;
    606         args.arg2 = spec;
    607         args.arg3 = interactiveRegion;
    608 
    609         message.obj = args;
    610 
    611         scheduleMessage(message, interrogatingPid, interrogatingTid, CONSIDER_REQUEST_PREPARERS);
    612     }
    613 
    614     private void focusSearchUiThread(Message message) {
    615         final int flags = message.arg1;
    616         final int accessibilityViewId = message.arg2;
    617 
    618         SomeArgs args = (SomeArgs) message.obj;
    619         final int direction = args.argi2;
    620         final int interactionId = args.argi3;
    621         final IAccessibilityInteractionConnectionCallback callback =
    622             (IAccessibilityInteractionConnectionCallback) args.arg1;
    623         final MagnificationSpec spec = (MagnificationSpec) args.arg2;
    624         final Region interactiveRegion = (Region) args.arg3;
    625 
    626         args.recycle();
    627 
    628         AccessibilityNodeInfo next = null;
    629         try {
    630             if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
    631                 return;
    632             }
    633             mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
    634             View root = null;
    635             if (accessibilityViewId != AccessibilityNodeInfo.ROOT_ITEM_ID) {
    636                 root = findViewByAccessibilityId(accessibilityViewId);
    637             } else {
    638                 root = mViewRootImpl.mView;
    639             }
    640             if (root != null && isShown(root)) {
    641                 View nextView = root.focusSearch(direction);
    642                 if (nextView != null) {
    643                     next = nextView.createAccessibilityNodeInfo();
    644                 }
    645             }
    646         } finally {
    647             updateInfoForViewportAndReturnFindNodeResult(
    648                     next, callback, interactionId, spec, interactiveRegion);
    649         }
    650     }
    651 
    652     public void performAccessibilityActionClientThread(long accessibilityNodeId, int action,
    653             Bundle arguments, int interactionId,
    654             IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid,
    655             long interrogatingTid) {
    656         Message message = mHandler.obtainMessage();
    657         message.what = PrivateHandler.MSG_PERFORM_ACCESSIBILITY_ACTION;
    658         message.arg1 = flags;
    659         message.arg2 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
    660 
    661         SomeArgs args = SomeArgs.obtain();
    662         args.argi1 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId);
    663         args.argi2 = action;
    664         args.argi3 = interactionId;
    665         args.arg1 = callback;
    666         args.arg2 = arguments;
    667 
    668         message.obj = args;
    669 
    670         scheduleMessage(message, interrogatingPid, interrogatingTid, CONSIDER_REQUEST_PREPARERS);
    671     }
    672 
    673     private void performAccessibilityActionUiThread(Message message) {
    674         final int flags = message.arg1;
    675         final int accessibilityViewId = message.arg2;
    676 
    677         SomeArgs args = (SomeArgs) message.obj;
    678         final int virtualDescendantId = args.argi1;
    679         final int action = args.argi2;
    680         final int interactionId = args.argi3;
    681         final IAccessibilityInteractionConnectionCallback callback =
    682             (IAccessibilityInteractionConnectionCallback) args.arg1;
    683         Bundle arguments = (Bundle) args.arg2;
    684 
    685         args.recycle();
    686 
    687         boolean succeeded = false;
    688         try {
    689             if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null ||
    690                     mViewRootImpl.mStopped || mViewRootImpl.mPausedForTransition) {
    691                 return;
    692             }
    693             mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
    694             View target = null;
    695             if (accessibilityViewId != AccessibilityNodeInfo.ROOT_ITEM_ID) {
    696                 target = findViewByAccessibilityId(accessibilityViewId);
    697             } else {
    698                 target = mViewRootImpl.mView;
    699             }
    700             if (target != null && isShown(target)) {
    701                 if (action == R.id.accessibilityActionClickOnClickableSpan) {
    702                     // Handle this hidden action separately
    703                     succeeded = handleClickableSpanActionUiThread(
    704                             target, virtualDescendantId, arguments);
    705                 } else {
    706                     AccessibilityNodeProvider provider = target.getAccessibilityNodeProvider();
    707                     if (provider != null) {
    708                         succeeded = provider.performAction(virtualDescendantId, action,
    709                                 arguments);
    710                     } else if (virtualDescendantId == AccessibilityNodeProvider.HOST_VIEW_ID) {
    711                         succeeded = target.performAccessibilityAction(action, arguments);
    712                     }
    713                 }
    714             }
    715         } finally {
    716             try {
    717                 mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
    718                 callback.setPerformAccessibilityActionResult(succeeded, interactionId);
    719             } catch (RemoteException re) {
    720                 /* ignore - the other side will time out */
    721             }
    722         }
    723     }
    724 
    725     private View findViewByAccessibilityId(int accessibilityId) {
    726         View root = mViewRootImpl.mView;
    727         if (root == null) {
    728             return null;
    729         }
    730         View foundView = root.findViewByAccessibilityId(accessibilityId);
    731         if (foundView != null && !isShown(foundView)) {
    732             return null;
    733         }
    734         return foundView;
    735     }
    736 
    737     private void applyAppScaleAndMagnificationSpecIfNeeded(List<AccessibilityNodeInfo> infos,
    738             MagnificationSpec spec) {
    739         if (infos == null) {
    740             return;
    741         }
    742         final float applicationScale = mViewRootImpl.mAttachInfo.mApplicationScale;
    743         if (shouldApplyAppScaleAndMagnificationSpec(applicationScale, spec)) {
    744             final int infoCount = infos.size();
    745             for (int i = 0; i < infoCount; i++) {
    746                 AccessibilityNodeInfo info = infos.get(i);
    747                 applyAppScaleAndMagnificationSpecIfNeeded(info, spec);
    748             }
    749         }
    750     }
    751 
    752     private void adjustIsVisibleToUserIfNeeded(List<AccessibilityNodeInfo> infos,
    753             Region interactiveRegion) {
    754         if (interactiveRegion == null || infos == null) {
    755             return;
    756         }
    757         final int infoCount = infos.size();
    758         for (int i = 0; i < infoCount; i++) {
    759             AccessibilityNodeInfo info = infos.get(i);
    760             adjustIsVisibleToUserIfNeeded(info, interactiveRegion);
    761         }
    762     }
    763 
    764     private void adjustIsVisibleToUserIfNeeded(AccessibilityNodeInfo info,
    765             Region interactiveRegion) {
    766         if (interactiveRegion == null || info == null) {
    767             return;
    768         }
    769         Rect boundsInScreen = mTempRect;
    770         info.getBoundsInScreen(boundsInScreen);
    771         if (interactiveRegion.quickReject(boundsInScreen)) {
    772             info.setVisibleToUser(false);
    773         }
    774     }
    775 
    776     private void applyAppScaleAndMagnificationSpecIfNeeded(AccessibilityNodeInfo info,
    777             MagnificationSpec spec) {
    778         if (info == null) {
    779             return;
    780         }
    781 
    782         final float applicationScale = mViewRootImpl.mAttachInfo.mApplicationScale;
    783         if (!shouldApplyAppScaleAndMagnificationSpec(applicationScale, spec)) {
    784             return;
    785         }
    786 
    787         Rect boundsInParent = mTempRect;
    788         Rect boundsInScreen = mTempRect1;
    789 
    790         info.getBoundsInParent(boundsInParent);
    791         info.getBoundsInScreen(boundsInScreen);
    792         if (applicationScale != 1.0f) {
    793             boundsInParent.scale(applicationScale);
    794             boundsInScreen.scale(applicationScale);
    795         }
    796         if (spec != null) {
    797             boundsInParent.scale(spec.scale);
    798             // boundsInParent must not be offset.
    799             boundsInScreen.scale(spec.scale);
    800             boundsInScreen.offset((int) spec.offsetX, (int) spec.offsetY);
    801         }
    802         info.setBoundsInParent(boundsInParent);
    803         info.setBoundsInScreen(boundsInScreen);
    804 
    805         // Scale text locations if they are present
    806         if (info.hasExtras()) {
    807             Bundle extras = info.getExtras();
    808             Parcelable[] textLocations =
    809                     extras.getParcelableArray(EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY);
    810             if (textLocations != null) {
    811                 for (int i = 0; i < textLocations.length; i++) {
    812                     // Unchecked cast - an app that puts other objects in this bundle with this
    813                     // key will crash.
    814                     RectF textLocation = ((RectF) textLocations[i]);
    815                     textLocation.scale(applicationScale);
    816                     if (spec != null) {
    817                         textLocation.scale(spec.scale);
    818                         textLocation.offset(spec.offsetX, spec.offsetY);
    819                     }
    820                 }
    821             }
    822         }
    823 
    824         if (spec != null) {
    825             AttachInfo attachInfo = mViewRootImpl.mAttachInfo;
    826             if (attachInfo.mDisplay == null) {
    827                 return;
    828             }
    829 
    830             final float scale = attachInfo.mApplicationScale * spec.scale;
    831 
    832             Rect visibleWinFrame = mTempRect1;
    833             visibleWinFrame.left = (int) (attachInfo.mWindowLeft * scale + spec.offsetX);
    834             visibleWinFrame.top = (int) (attachInfo.mWindowTop * scale + spec.offsetY);
    835             visibleWinFrame.right = (int) (visibleWinFrame.left + mViewRootImpl.mWidth * scale);
    836             visibleWinFrame.bottom = (int) (visibleWinFrame.top + mViewRootImpl.mHeight * scale);
    837 
    838             attachInfo.mDisplay.getRealSize(mTempPoint);
    839             final int displayWidth = mTempPoint.x;
    840             final int displayHeight = mTempPoint.y;
    841 
    842             Rect visibleDisplayFrame = mTempRect2;
    843             visibleDisplayFrame.set(0, 0, displayWidth, displayHeight);
    844 
    845             if (!visibleWinFrame.intersect(visibleDisplayFrame)) {
    846                 // If there's no intersection with display, set visibleWinFrame empty.
    847                 visibleDisplayFrame.setEmpty();
    848             }
    849 
    850             if (!visibleWinFrame.intersects(boundsInScreen.left, boundsInScreen.top,
    851                     boundsInScreen.right, boundsInScreen.bottom)) {
    852                 info.setVisibleToUser(false);
    853             }
    854         }
    855     }
    856 
    857     private boolean shouldApplyAppScaleAndMagnificationSpec(float appScale,
    858             MagnificationSpec spec) {
    859         return (appScale != 1.0f || (spec != null && !spec.isNop()));
    860     }
    861 
    862     private void updateInfosForViewportAndReturnFindNodeResult(List<AccessibilityNodeInfo> infos,
    863             IAccessibilityInteractionConnectionCallback callback, int interactionId,
    864             MagnificationSpec spec, Region interactiveRegion) {
    865         try {
    866             mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
    867             applyAppScaleAndMagnificationSpecIfNeeded(infos, spec);
    868             adjustIsVisibleToUserIfNeeded(infos, interactiveRegion);
    869             callback.setFindAccessibilityNodeInfosResult(infos, interactionId);
    870             if (infos != null) {
    871                 infos.clear();
    872             }
    873         } catch (RemoteException re) {
    874             /* ignore - the other side will time out */
    875         } finally {
    876             recycleMagnificationSpecAndRegionIfNeeded(spec, interactiveRegion);
    877         }
    878     }
    879 
    880     private void updateInfoForViewportAndReturnFindNodeResult(AccessibilityNodeInfo info,
    881             IAccessibilityInteractionConnectionCallback callback, int interactionId,
    882             MagnificationSpec spec, Region interactiveRegion) {
    883         try {
    884             mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
    885             applyAppScaleAndMagnificationSpecIfNeeded(info, spec);
    886             adjustIsVisibleToUserIfNeeded(info, interactiveRegion);
    887             callback.setFindAccessibilityNodeInfoResult(info, interactionId);
    888         } catch (RemoteException re) {
    889                 /* ignore - the other side will time out */
    890         } finally {
    891             recycleMagnificationSpecAndRegionIfNeeded(spec, interactiveRegion);
    892         }
    893     }
    894 
    895     private void recycleMagnificationSpecAndRegionIfNeeded(MagnificationSpec spec, Region region) {
    896         if (android.os.Process.myPid() != Binder.getCallingPid()) {
    897             // Specs are cached in the system process and obtained from a pool when read from
    898             // a parcel, so only recycle the spec if called from another process.
    899             if (spec != null) {
    900                 spec.recycle();
    901             }
    902         } else {
    903             // Regions are obtained in the system process and instantiated when read from
    904             // a parcel, so only recycle the region if caled from the same process.
    905             if (region != null) {
    906                 region.recycle();
    907             }
    908         }
    909     }
    910 
    911     private boolean handleClickableSpanActionUiThread(
    912             View view, int virtualDescendantId, Bundle arguments) {
    913         Parcelable span = arguments.getParcelable(ACTION_ARGUMENT_ACCESSIBLE_CLICKABLE_SPAN);
    914         if (!(span instanceof AccessibilityClickableSpan)) {
    915             return false;
    916         }
    917 
    918         // Find the original ClickableSpan if it's still on the screen
    919         AccessibilityNodeInfo infoWithSpan = null;
    920         AccessibilityNodeProvider provider = view.getAccessibilityNodeProvider();
    921         if (provider != null) {
    922             infoWithSpan = provider.createAccessibilityNodeInfo(virtualDescendantId);
    923         } else if (virtualDescendantId == AccessibilityNodeProvider.HOST_VIEW_ID) {
    924             infoWithSpan = view.createAccessibilityNodeInfo();
    925         }
    926         if (infoWithSpan == null) {
    927             return false;
    928         }
    929 
    930         // Click on the corresponding span
    931         ClickableSpan clickableSpan = ((AccessibilityClickableSpan) span).findClickableSpan(
    932                 infoWithSpan.getOriginalText());
    933         if (clickableSpan != null) {
    934             clickableSpan.onClick(view);
    935             return true;
    936         }
    937         return false;
    938     }
    939 
    940     /**
    941      * This class encapsulates a prefetching strategy for the accessibility APIs for
    942      * querying window content. It is responsible to prefetch a batch of
    943      * AccessibilityNodeInfos in addition to the one for a requested node.
    944      */
    945     private class AccessibilityNodePrefetcher {
    946 
    947         private static final int MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE = 50;
    948 
    949         private final ArrayList<View> mTempViewList = new ArrayList<View>();
    950 
    951         public void prefetchAccessibilityNodeInfos(View view, int virtualViewId, int fetchFlags,
    952                 List<AccessibilityNodeInfo> outInfos, Bundle arguments) {
    953             AccessibilityNodeProvider provider = view.getAccessibilityNodeProvider();
    954             // Determine if we'll be populating extra data
    955             final String extraDataRequested = (arguments == null) ? null
    956                     : arguments.getString(EXTRA_DATA_REQUESTED_KEY);
    957             if (provider == null) {
    958                 AccessibilityNodeInfo root = view.createAccessibilityNodeInfo();
    959                 if (root != null) {
    960                     if (extraDataRequested != null) {
    961                         view.addExtraDataToAccessibilityNodeInfo(
    962                                 root, extraDataRequested, arguments);
    963                     }
    964                     outInfos.add(root);
    965                     if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) != 0) {
    966                         prefetchPredecessorsOfRealNode(view, outInfos);
    967                     }
    968                     if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS) != 0) {
    969                         prefetchSiblingsOfRealNode(view, outInfos);
    970                     }
    971                     if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS) != 0) {
    972                         prefetchDescendantsOfRealNode(view, outInfos);
    973                     }
    974                 }
    975             } else {
    976                 final AccessibilityNodeInfo root =
    977                         provider.createAccessibilityNodeInfo(virtualViewId);
    978                 if (root != null) {
    979                     if (extraDataRequested != null) {
    980                         provider.addExtraDataToAccessibilityNodeInfo(
    981                                 virtualViewId, root, extraDataRequested, arguments);
    982                     }
    983                     outInfos.add(root);
    984                     if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) != 0) {
    985                         prefetchPredecessorsOfVirtualNode(root, view, provider, outInfos);
    986                     }
    987                     if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS) != 0) {
    988                         prefetchSiblingsOfVirtualNode(root, view, provider, outInfos);
    989                     }
    990                     if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS) != 0) {
    991                         prefetchDescendantsOfVirtualNode(root, provider, outInfos);
    992                     }
    993                 }
    994             }
    995             if (ENFORCE_NODE_TREE_CONSISTENT) {
    996                 enforceNodeTreeConsistent(outInfos);
    997             }
    998         }
    999 
   1000         private void enforceNodeTreeConsistent(List<AccessibilityNodeInfo> nodes) {
   1001             LongSparseArray<AccessibilityNodeInfo> nodeMap =
   1002                     new LongSparseArray<AccessibilityNodeInfo>();
   1003             final int nodeCount = nodes.size();
   1004             for (int i = 0; i < nodeCount; i++) {
   1005                 AccessibilityNodeInfo node = nodes.get(i);
   1006                 nodeMap.put(node.getSourceNodeId(), node);
   1007             }
   1008 
   1009             // If the nodes are a tree it does not matter from
   1010             // which node we start to search for the root.
   1011             AccessibilityNodeInfo root = nodeMap.valueAt(0);
   1012             AccessibilityNodeInfo parent = root;
   1013             while (parent != null) {
   1014                 root = parent;
   1015                 parent = nodeMap.get(parent.getParentNodeId());
   1016             }
   1017 
   1018             // Traverse the tree and do some checks.
   1019             AccessibilityNodeInfo accessFocus = null;
   1020             AccessibilityNodeInfo inputFocus = null;
   1021             HashSet<AccessibilityNodeInfo> seen = new HashSet<AccessibilityNodeInfo>();
   1022             Queue<AccessibilityNodeInfo> fringe = new LinkedList<AccessibilityNodeInfo>();
   1023             fringe.add(root);
   1024 
   1025             while (!fringe.isEmpty()) {
   1026                 AccessibilityNodeInfo current = fringe.poll();
   1027 
   1028                 // Check for duplicates
   1029                 if (!seen.add(current)) {
   1030                     throw new IllegalStateException("Duplicate node: "
   1031                             + current + " in window:"
   1032                             + mViewRootImpl.mAttachInfo.mAccessibilityWindowId);
   1033                 }
   1034 
   1035                 // Check for one accessibility focus.
   1036                 if (current.isAccessibilityFocused()) {
   1037                     if (accessFocus != null) {
   1038                         throw new IllegalStateException("Duplicate accessibility focus:"
   1039                                 + current
   1040                                 + " in window:" + mViewRootImpl.mAttachInfo.mAccessibilityWindowId);
   1041                     } else {
   1042                         accessFocus = current;
   1043                     }
   1044                 }
   1045 
   1046                 // Check for one input focus.
   1047                 if (current.isFocused()) {
   1048                     if (inputFocus != null) {
   1049                         throw new IllegalStateException("Duplicate input focus: "
   1050                             + current + " in window:"
   1051                             + mViewRootImpl.mAttachInfo.mAccessibilityWindowId);
   1052                     } else {
   1053                         inputFocus = current;
   1054                     }
   1055                 }
   1056 
   1057                 final int childCount = current.getChildCount();
   1058                 for (int j = 0; j < childCount; j++) {
   1059                     final long childId = current.getChildId(j);
   1060                     final AccessibilityNodeInfo child = nodeMap.get(childId);
   1061                     if (child != null) {
   1062                         fringe.add(child);
   1063                     }
   1064                 }
   1065             }
   1066 
   1067             // Check for disconnected nodes.
   1068             for (int j = nodeMap.size() - 1; j >= 0; j--) {
   1069                 AccessibilityNodeInfo info = nodeMap.valueAt(j);
   1070                 if (!seen.contains(info)) {
   1071                     throw new IllegalStateException("Disconnected node: " + info);
   1072                 }
   1073             }
   1074         }
   1075 
   1076         private void prefetchPredecessorsOfRealNode(View view,
   1077                 List<AccessibilityNodeInfo> outInfos) {
   1078             ViewParent parent = view.getParentForAccessibility();
   1079             while (parent instanceof View
   1080                     && outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
   1081                 View parentView = (View) parent;
   1082                 AccessibilityNodeInfo info = parentView.createAccessibilityNodeInfo();
   1083                 if (info != null) {
   1084                     outInfos.add(info);
   1085                 }
   1086                 parent = parent.getParentForAccessibility();
   1087             }
   1088         }
   1089 
   1090         private void prefetchSiblingsOfRealNode(View current,
   1091                 List<AccessibilityNodeInfo> outInfos) {
   1092             ViewParent parent = current.getParentForAccessibility();
   1093             if (parent instanceof ViewGroup) {
   1094                 ViewGroup parentGroup = (ViewGroup) parent;
   1095                 ArrayList<View> children = mTempViewList;
   1096                 children.clear();
   1097                 try {
   1098                     parentGroup.addChildrenForAccessibility(children);
   1099                     final int childCount = children.size();
   1100                     for (int i = 0; i < childCount; i++) {
   1101                         if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
   1102                             return;
   1103                         }
   1104                         View child = children.get(i);
   1105                         if (child.getAccessibilityViewId() != current.getAccessibilityViewId()
   1106                                 &&  isShown(child)) {
   1107                             AccessibilityNodeInfo info = null;
   1108                             AccessibilityNodeProvider provider =
   1109                                 child.getAccessibilityNodeProvider();
   1110                             if (provider == null) {
   1111                                 info = child.createAccessibilityNodeInfo();
   1112                             } else {
   1113                                 info = provider.createAccessibilityNodeInfo(
   1114                                         AccessibilityNodeProvider.HOST_VIEW_ID);
   1115                             }
   1116                             if (info != null) {
   1117                                 outInfos.add(info);
   1118                             }
   1119                         }
   1120                     }
   1121                 } finally {
   1122                     children.clear();
   1123                 }
   1124             }
   1125         }
   1126 
   1127         private void prefetchDescendantsOfRealNode(View root,
   1128                 List<AccessibilityNodeInfo> outInfos) {
   1129             if (!(root instanceof ViewGroup)) {
   1130                 return;
   1131             }
   1132             HashMap<View, AccessibilityNodeInfo> addedChildren =
   1133                 new HashMap<View, AccessibilityNodeInfo>();
   1134             ArrayList<View> children = mTempViewList;
   1135             children.clear();
   1136             try {
   1137                 root.addChildrenForAccessibility(children);
   1138                 final int childCount = children.size();
   1139                 for (int i = 0; i < childCount; i++) {
   1140                     if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
   1141                         return;
   1142                     }
   1143                     View child = children.get(i);
   1144                     if (isShown(child)) {
   1145                         AccessibilityNodeProvider provider = child.getAccessibilityNodeProvider();
   1146                         if (provider == null) {
   1147                             AccessibilityNodeInfo info = child.createAccessibilityNodeInfo();
   1148                             if (info != null) {
   1149                                 outInfos.add(info);
   1150                                 addedChildren.put(child, null);
   1151                             }
   1152                         } else {
   1153                             AccessibilityNodeInfo info = provider.createAccessibilityNodeInfo(
   1154                                    AccessibilityNodeProvider.HOST_VIEW_ID);
   1155                             if (info != null) {
   1156                                 outInfos.add(info);
   1157                                 addedChildren.put(child, info);
   1158                             }
   1159                         }
   1160                     }
   1161                 }
   1162             } finally {
   1163                 children.clear();
   1164             }
   1165             if (outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
   1166                 for (Map.Entry<View, AccessibilityNodeInfo> entry : addedChildren.entrySet()) {
   1167                     View addedChild = entry.getKey();
   1168                     AccessibilityNodeInfo virtualRoot = entry.getValue();
   1169                     if (virtualRoot == null) {
   1170                         prefetchDescendantsOfRealNode(addedChild, outInfos);
   1171                     } else {
   1172                         AccessibilityNodeProvider provider =
   1173                             addedChild.getAccessibilityNodeProvider();
   1174                         prefetchDescendantsOfVirtualNode(virtualRoot, provider, outInfos);
   1175                     }
   1176                 }
   1177             }
   1178         }
   1179 
   1180         private void prefetchPredecessorsOfVirtualNode(AccessibilityNodeInfo root,
   1181                 View providerHost, AccessibilityNodeProvider provider,
   1182                 List<AccessibilityNodeInfo> outInfos) {
   1183             final int initialResultSize = outInfos.size();
   1184             long parentNodeId = root.getParentNodeId();
   1185             int accessibilityViewId = AccessibilityNodeInfo.getAccessibilityViewId(parentNodeId);
   1186             while (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) {
   1187                 if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
   1188                     return;
   1189                 }
   1190                 final int virtualDescendantId =
   1191                     AccessibilityNodeInfo.getVirtualDescendantId(parentNodeId);
   1192                 if (virtualDescendantId != AccessibilityNodeProvider.HOST_VIEW_ID
   1193                         || accessibilityViewId == providerHost.getAccessibilityViewId()) {
   1194                     final AccessibilityNodeInfo parent;
   1195                     parent = provider.createAccessibilityNodeInfo(virtualDescendantId);
   1196                     if (parent == null) {
   1197                         // Going up the parent relation we found a null predecessor,
   1198                         // so remove these disconnected nodes form the result.
   1199                         final int currentResultSize = outInfos.size();
   1200                         for (int i = currentResultSize - 1; i >= initialResultSize; i--) {
   1201                             outInfos.remove(i);
   1202                         }
   1203                         // Couldn't obtain the parent, which means we have a
   1204                         // disconnected sub-tree. Abort prefetch immediately.
   1205                         return;
   1206                     }
   1207                     outInfos.add(parent);
   1208                     parentNodeId = parent.getParentNodeId();
   1209                     accessibilityViewId = AccessibilityNodeInfo.getAccessibilityViewId(
   1210                             parentNodeId);
   1211                 } else {
   1212                     prefetchPredecessorsOfRealNode(providerHost, outInfos);
   1213                     return;
   1214                 }
   1215             }
   1216         }
   1217 
   1218         private void prefetchSiblingsOfVirtualNode(AccessibilityNodeInfo current, View providerHost,
   1219                 AccessibilityNodeProvider provider, List<AccessibilityNodeInfo> outInfos) {
   1220             final long parentNodeId = current.getParentNodeId();
   1221             final int parentAccessibilityViewId =
   1222                 AccessibilityNodeInfo.getAccessibilityViewId(parentNodeId);
   1223             final int parentVirtualDescendantId =
   1224                 AccessibilityNodeInfo.getVirtualDescendantId(parentNodeId);
   1225             if (parentVirtualDescendantId != AccessibilityNodeProvider.HOST_VIEW_ID
   1226                     || parentAccessibilityViewId == providerHost.getAccessibilityViewId()) {
   1227                 final AccessibilityNodeInfo parent =
   1228                         provider.createAccessibilityNodeInfo(parentVirtualDescendantId);
   1229                 if (parent != null) {
   1230                     final int childCount = parent.getChildCount();
   1231                     for (int i = 0; i < childCount; i++) {
   1232                         if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
   1233                             return;
   1234                         }
   1235                         final long childNodeId = parent.getChildId(i);
   1236                         if (childNodeId != current.getSourceNodeId()) {
   1237                             final int childVirtualDescendantId =
   1238                                 AccessibilityNodeInfo.getVirtualDescendantId(childNodeId);
   1239                             AccessibilityNodeInfo child = provider.createAccessibilityNodeInfo(
   1240                                     childVirtualDescendantId);
   1241                             if (child != null) {
   1242                                 outInfos.add(child);
   1243                             }
   1244                         }
   1245                     }
   1246                 }
   1247             } else {
   1248                 prefetchSiblingsOfRealNode(providerHost, outInfos);
   1249             }
   1250         }
   1251 
   1252         private void prefetchDescendantsOfVirtualNode(AccessibilityNodeInfo root,
   1253                 AccessibilityNodeProvider provider, List<AccessibilityNodeInfo> outInfos) {
   1254             final int initialOutInfosSize = outInfos.size();
   1255             final int childCount = root.getChildCount();
   1256             for (int i = 0; i < childCount; i++) {
   1257                 if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
   1258                     return;
   1259                 }
   1260                 final long childNodeId = root.getChildId(i);
   1261                 AccessibilityNodeInfo child = provider.createAccessibilityNodeInfo(
   1262                         AccessibilityNodeInfo.getVirtualDescendantId(childNodeId));
   1263                 if (child != null) {
   1264                     outInfos.add(child);
   1265                 }
   1266             }
   1267             if (outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
   1268                 final int addedChildCount = outInfos.size() - initialOutInfosSize;
   1269                 for (int i = 0; i < addedChildCount; i++) {
   1270                     AccessibilityNodeInfo child = outInfos.get(initialOutInfosSize + i);
   1271                     prefetchDescendantsOfVirtualNode(child, provider, outInfos);
   1272                 }
   1273             }
   1274         }
   1275     }
   1276 
   1277     private class PrivateHandler extends Handler {
   1278         private static final int MSG_PERFORM_ACCESSIBILITY_ACTION = 1;
   1279         private static final int MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID = 2;
   1280         private static final int MSG_FIND_ACCESSIBILITY_NODE_INFOS_BY_VIEW_ID = 3;
   1281         private static final int MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_TEXT = 4;
   1282         private static final int MSG_FIND_FOCUS = 5;
   1283         private static final int MSG_FOCUS_SEARCH = 6;
   1284         private static final int MSG_PREPARE_FOR_EXTRA_DATA_REQUEST = 7;
   1285         private static final int MSG_APP_PREPARATION_FINISHED = 8;
   1286         private static final int MSG_APP_PREPARATION_TIMEOUT = 9;
   1287 
   1288         public PrivateHandler(Looper looper) {
   1289             super(looper);
   1290         }
   1291 
   1292         @Override
   1293         public String getMessageName(Message message) {
   1294             final int type = message.what;
   1295             switch (type) {
   1296                 case MSG_PERFORM_ACCESSIBILITY_ACTION:
   1297                     return "MSG_PERFORM_ACCESSIBILITY_ACTION";
   1298                 case MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID:
   1299                     return "MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID";
   1300                 case MSG_FIND_ACCESSIBILITY_NODE_INFOS_BY_VIEW_ID:
   1301                     return "MSG_FIND_ACCESSIBILITY_NODE_INFOS_BY_VIEW_ID";
   1302                 case MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_TEXT:
   1303                     return "MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_TEXT";
   1304                 case MSG_FIND_FOCUS:
   1305                     return "MSG_FIND_FOCUS";
   1306                 case MSG_FOCUS_SEARCH:
   1307                     return "MSG_FOCUS_SEARCH";
   1308                 case MSG_PREPARE_FOR_EXTRA_DATA_REQUEST:
   1309                     return "MSG_PREPARE_FOR_EXTRA_DATA_REQUEST";
   1310                 case MSG_APP_PREPARATION_FINISHED:
   1311                     return "MSG_APP_PREPARATION_FINISHED";
   1312                 case MSG_APP_PREPARATION_TIMEOUT:
   1313                     return "MSG_APP_PREPARATION_TIMEOUT";
   1314                 default:
   1315                     throw new IllegalArgumentException("Unknown message type: " + type);
   1316             }
   1317         }
   1318 
   1319         @Override
   1320         public void handleMessage(Message message) {
   1321             final int type = message.what;
   1322             switch (type) {
   1323                 case MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID: {
   1324                     findAccessibilityNodeInfoByAccessibilityIdUiThread(message);
   1325                 } break;
   1326                 case MSG_PERFORM_ACCESSIBILITY_ACTION: {
   1327                     performAccessibilityActionUiThread(message);
   1328                 } break;
   1329                 case MSG_FIND_ACCESSIBILITY_NODE_INFOS_BY_VIEW_ID: {
   1330                     findAccessibilityNodeInfosByViewIdUiThread(message);
   1331                 } break;
   1332                 case MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_TEXT: {
   1333                     findAccessibilityNodeInfosByTextUiThread(message);
   1334                 } break;
   1335                 case MSG_FIND_FOCUS: {
   1336                     findFocusUiThread(message);
   1337                 } break;
   1338                 case MSG_FOCUS_SEARCH: {
   1339                     focusSearchUiThread(message);
   1340                 } break;
   1341                 case MSG_PREPARE_FOR_EXTRA_DATA_REQUEST: {
   1342                     prepareForExtraDataRequestUiThread(message);
   1343                 } break;
   1344                 case MSG_APP_PREPARATION_FINISHED: {
   1345                     requestPreparerDoneUiThread(message);
   1346                 } break;
   1347                 case MSG_APP_PREPARATION_TIMEOUT: {
   1348                     requestPreparerTimeoutUiThread();
   1349                 } break;
   1350                 default:
   1351                     throw new IllegalArgumentException("Unknown message type: " + type);
   1352             }
   1353         }
   1354     }
   1355 
   1356     private final class AddNodeInfosForViewId implements Predicate<View> {
   1357         private int mViewId = View.NO_ID;
   1358         private List<AccessibilityNodeInfo> mInfos;
   1359 
   1360         public void init(int viewId, List<AccessibilityNodeInfo> infos) {
   1361             mViewId = viewId;
   1362             mInfos = infos;
   1363         }
   1364 
   1365         public void reset() {
   1366             mViewId = View.NO_ID;
   1367             mInfos = null;
   1368         }
   1369 
   1370         @Override
   1371         public boolean test(View view) {
   1372             if (view.getId() == mViewId && isShown(view)) {
   1373                 mInfos.add(view.createAccessibilityNodeInfo());
   1374             }
   1375             return false;
   1376         }
   1377     }
   1378 
   1379     private static final class MessageHolder {
   1380         final Message mMessage;
   1381         final int mInterrogatingPid;
   1382         final long mInterrogatingTid;
   1383 
   1384         MessageHolder(Message message, int interrogatingPid, long interrogatingTid) {
   1385             mMessage = message;
   1386             mInterrogatingPid = interrogatingPid;
   1387             mInterrogatingTid = interrogatingTid;
   1388         }
   1389     }
   1390 }
   1391