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