blob: 6d4ac0ed53275d3da99ac9d45991816fc8bfb41b [file] [log] [blame]
Svetoslav Ganov42138042012-03-20 11:51:39 -07001/*
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
17package android.view;
18
Jackal Guoecd09bb2019-03-20 17:43:19 +080019import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
Phil Weaver193520e2016-12-13 09:39:06 -080020import static android.view.accessibility.AccessibilityNodeInfo.ACTION_ARGUMENT_ACCESSIBLE_CLICKABLE_SPAN;
Phil Weaver08cccc12017-02-28 09:55:31 -080021import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_REQUESTED_KEY;
Phil Weaverc2e28932016-12-08 12:29:25 -080022import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY;
Phil Weaver193520e2016-12-13 09:39:06 -080023
Jackal Guo957deab2020-02-04 14:34:40 +080024import android.graphics.Matrix;
Svetoslav Ganov152e9bb2012-10-12 20:15:29 -070025import android.graphics.Point;
Svetoslav Ganovfd8d9c42012-07-09 18:16:55 -070026import android.graphics.Rect;
Phil Weaverc2e28932016-12-08 12:29:25 -080027import android.graphics.RectF;
Svetoslav9ae9ed22014-09-02 16:36:35 -070028import android.graphics.Region;
Nirmal Patel386a8242015-06-02 18:11:32 -070029import android.os.Binder;
Svetoslav Ganovaa780c12012-04-19 23:01:39 -070030import android.os.Bundle;
Svetoslav Ganov42138042012-03-20 11:51:39 -070031import android.os.Handler;
32import android.os.Looper;
33import android.os.Message;
Phil Weaver193520e2016-12-13 09:39:06 -080034import android.os.Parcelable;
Svetoslav Ganov42138042012-03-20 11:51:39 -070035import android.os.Process;
36import android.os.RemoteException;
Jackal Guoac2b62e2018-08-22 18:28:46 +080037import android.os.SystemClock;
Phil Weaver193520e2016-12-13 09:39:06 -080038import android.text.style.AccessibilityClickableSpan;
39import android.text.style.ClickableSpan;
Svetoslav8e3feb12014-02-24 13:46:47 -080040import android.util.LongSparseArray;
Phil Weaver08cccc12017-02-28 09:55:31 -080041import android.util.Slog;
Svetoslav Ganov42138042012-03-20 11:51:39 -070042import android.view.accessibility.AccessibilityInteractionClient;
Phil Weaver08cccc12017-02-28 09:55:31 -080043import android.view.accessibility.AccessibilityManager;
Qasid Ahmad Sadiq834787a2019-01-18 00:01:54 -080044import android.view.accessibility.AccessibilityNodeIdManager;
Svetoslav Ganov42138042012-03-20 11:51:39 -070045import android.view.accessibility.AccessibilityNodeInfo;
Rhed Jao23813d92018-12-18 21:30:52 +080046import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
Svetoslav Ganov42138042012-03-20 11:51:39 -070047import android.view.accessibility.AccessibilityNodeProvider;
Phil Weaver08cccc12017-02-28 09:55:31 -080048import android.view.accessibility.AccessibilityRequestPreparer;
Svetoslav Ganov42138042012-03-20 11:51:39 -070049import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
50
Phil Weaver193520e2016-12-13 09:39:06 -080051import com.android.internal.R;
Phil Weaver08cccc12017-02-28 09:55:31 -080052import com.android.internal.annotations.GuardedBy;
Rhed Jao23813d92018-12-18 21:30:52 +080053import com.android.internal.annotations.VisibleForTesting;
Svetoslav Ganov758143e2012-08-06 16:40:27 -070054import com.android.internal.os.SomeArgs;
55
Svetoslav Ganov42138042012-03-20 11:51:39 -070056import java.util.ArrayList;
57import java.util.HashMap;
Svetoslav8e3feb12014-02-24 13:46:47 -080058import java.util.HashSet;
59import java.util.LinkedList;
Svetoslav Ganov42138042012-03-20 11:51:39 -070060import java.util.List;
61import java.util.Map;
Svetoslav8e3feb12014-02-24 13:46:47 -080062import java.util.Queue;
Paul Duffinca4964c2017-02-07 15:04:10 +000063import java.util.function.Predicate;
Svetoslav Ganov42138042012-03-20 11:51:39 -070064
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.
Rhed Jao23813d92018-12-18 21:30:52 +080071 *
72 * @hide
Svetoslav Ganov42138042012-03-20 11:51:39 -070073 */
Rhed Jao23813d92018-12-18 21:30:52 +080074@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
75public final class AccessibilityInteractionController {
Svetoslav Ganov42138042012-03-20 11:51:39 -070076
Phil Weaver08cccc12017-02-28 09:55:31 -080077 private static final String LOG_TAG = "AccessibilityInteractionController";
78
79 // Debugging flag
Svetoslavaaa11422014-03-28 13:31:13 -070080 private static final boolean ENFORCE_NODE_TREE_CONSISTENT = false;
Svetoslav8e3feb12014-02-24 13:46:47 -080081
Phil Weaver08cccc12017-02-28 09:55:31 -080082 // 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
Svetoslav Ganov80943d82013-01-02 10:25:37 -080090 private final ArrayList<AccessibilityNodeInfo> mTempAccessibilityNodeInfoList =
Svetoslav Ganov42138042012-03-20 11:51:39 -070091 new ArrayList<AccessibilityNodeInfo>();
92
Phil Weaver08cccc12017-02-28 09:55:31 -080093 private final Object mLock = new Object();
94
Rhed Jao23813d92018-12-18 21:30:52 +080095 private final PrivateHandler mHandler;
Svetoslav Ganov42138042012-03-20 11:51:39 -070096
97 private final ViewRootImpl mViewRootImpl;
98
99 private final AccessibilityNodePrefetcher mPrefetcher;
100
Svetoslav Ganov749e7962012-04-19 17:13:46 -0700101 private final long mMyLooperThreadId;
102
103 private final int mMyProcessId;
104
Phil Weaver08cccc12017-02-28 09:55:31 -0800105 private final AccessibilityManager mA11yManager;
106
Svetoslav Ganov30ac6452012-06-01 09:10:25 -0700107 private final ArrayList<View> mTempArrayList = new ArrayList<View>();
108
Svetoslav Ganov152e9bb2012-10-12 20:15:29 -0700109 private final Point mTempPoint = new Point();
Svetoslav Ganovfd8d9c42012-07-09 18:16:55 -0700110 private final Rect mTempRect = new Rect();
Svetoslav Ganov152e9bb2012-10-12 20:15:29 -0700111 private final Rect mTempRect1 = new Rect();
112 private final Rect mTempRect2 = new Rect();
Jackal Guo957deab2020-02-04 14:34:40 +0800113 private final RectF mTempRectF = new RectF();
Svetoslav Ganovfd8d9c42012-07-09 18:16:55 -0700114
Svetoslav Ganov80943d82013-01-02 10:25:37 -0800115 private AddNodeInfosForViewId mAddNodeInfosForViewId;
116
Phil Weaver08cccc12017-02-28 09:55:31 -0800117 @GuardedBy("mLock")
118 private int mNumActiveRequestPreparers;
119 @GuardedBy("mLock")
120 private List<MessageHolder> mMessagesWaitingForRequestPreparer;
121 @GuardedBy("mLock")
122 private int mActiveRequestPreparerId;
123
Svetoslav Ganov42138042012-03-20 11:51:39 -0700124 public AccessibilityInteractionController(ViewRootImpl viewRootImpl) {
Svetoslav Ganov749e7962012-04-19 17:13:46 -0700125 Looper looper = viewRootImpl.mHandler.getLooper();
126 mMyLooperThreadId = looper.getThread().getId();
127 mMyProcessId = Process.myPid();
Svetoslav Ganov005b83b2012-04-16 18:17:17 -0700128 mHandler = new PrivateHandler(looper);
Svetoslav Ganov42138042012-03-20 11:51:39 -0700129 mViewRootImpl = viewRootImpl;
130 mPrefetcher = new AccessibilityNodePrefetcher();
Phil Weaver08cccc12017-02-28 09:55:31 -0800131 mA11yManager = mViewRootImpl.mContext.getSystemService(AccessibilityManager.class);
Svetoslav Ganov42138042012-03-20 11:51:39 -0700132 }
133
Phil Weaver08cccc12017-02-28 09:55:31 -0800134 private void scheduleMessage(Message message, int interrogatingPid, long interrogatingTid,
135 boolean ignoreRequestPreparers) {
136 if (ignoreRequestPreparers
137 || !holdOffMessageIfNeeded(message, interrogatingPid, interrogatingTid)) {
138 // If the interrogation is performed by the same thread as the main UI
139 // thread in this process, set the message as a static reference so
140 // after this call completes the same thread but in the interrogating
141 // client can handle the message to generate the result.
Rhed Jao23813d92018-12-18 21:30:52 +0800142 if (interrogatingPid == mMyProcessId && interrogatingTid == mMyLooperThreadId
143 && mHandler.hasAccessibilityCallback(message)) {
Phil Weaver08cccc12017-02-28 09:55:31 -0800144 AccessibilityInteractionClient.getInstanceForThread(
145 interrogatingTid).setSameThreadMessage(message);
146 } else {
Rhed Jao23813d92018-12-18 21:30:52 +0800147 // For messages without callback of interrogating client, just handle the
148 // message immediately if this is UI thread.
149 if (!mHandler.hasAccessibilityCallback(message)
150 && Thread.currentThread().getId() == mMyLooperThreadId) {
151 mHandler.handleMessage(message);
152 } else {
153 mHandler.sendMessage(message);
154 }
Phil Weaver08cccc12017-02-28 09:55:31 -0800155 }
Phil Weaverc2e28932016-12-08 12:29:25 -0800156 }
157 }
158
Svetoslav Ganov0a1bb6d2012-05-07 11:54:39 -0700159 private boolean isShown(View view) {
Qasid Ahmad Sadiq834787a2019-01-18 00:01:54 -0800160 return (view != null) && (view.getWindowVisibility() == View.VISIBLE && view.isShown());
Svetoslav Ganov0a1bb6d2012-05-07 11:54:39 -0700161 }
162
Svetoslav Ganov42138042012-03-20 11:51:39 -0700163 public void findAccessibilityNodeInfoByAccessibilityIdClientThread(
Svetoslav9ae9ed22014-09-02 16:36:35 -0700164 long accessibilityNodeId, Region interactiveRegion, int interactionId,
Svetoslav Ganov42138042012-03-20 11:51:39 -0700165 IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid,
Phil Weaverc2e28932016-12-08 12:29:25 -0800166 long interrogatingTid, MagnificationSpec spec, Bundle arguments) {
Phil Weaver08cccc12017-02-28 09:55:31 -0800167 final Message message = mHandler.obtainMessage();
Svet Ganov7498efd2014-09-03 21:33:00 -0700168 message.what = PrivateHandler.MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID;
Svetoslav Ganov42138042012-03-20 11:51:39 -0700169 message.arg1 = flags;
Svetoslav Ganov86783472012-06-06 21:12:20 -0700170
Phil Weaver08cccc12017-02-28 09:55:31 -0800171 final SomeArgs args = SomeArgs.obtain();
Svetoslav Ganov42138042012-03-20 11:51:39 -0700172 args.argi1 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
173 args.argi2 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId);
174 args.argi3 = interactionId;
175 args.arg1 = callback;
Svetoslav Ganov152e9bb2012-10-12 20:15:29 -0700176 args.arg2 = spec;
Svetoslav9ae9ed22014-09-02 16:36:35 -0700177 args.arg3 = interactiveRegion;
Phil Weaverc2e28932016-12-08 12:29:25 -0800178 args.arg4 = arguments;
Svetoslav Ganov42138042012-03-20 11:51:39 -0700179 message.obj = args;
Svetoslav Ganov86783472012-06-06 21:12:20 -0700180
Phil Weaver08cccc12017-02-28 09:55:31 -0800181 scheduleMessage(message, interrogatingPid, interrogatingTid, CONSIDER_REQUEST_PREPARERS);
182 }
183
184 /**
185 * Check if this message needs to be held off while the app prepares to meet either this
186 * request, or a request ahead of it.
187 *
188 * @param originalMessage The message to be processed
189 * @param callingPid The calling process id
190 * @param callingTid The calling thread id
191 *
192 * @return {@code true} if the message is held off and will be processed later, {@code false} if
193 * the message should be posted.
194 */
195 private boolean holdOffMessageIfNeeded(
196 Message originalMessage, int callingPid, long callingTid) {
197 synchronized (mLock) {
198 // If a request is already pending, queue this request for when it's finished
199 if (mNumActiveRequestPreparers != 0) {
200 queueMessageToHandleOncePrepared(originalMessage, callingPid, callingTid);
201 return true;
202 }
203
204 // Currently the only message that can hold things off is findByA11yId with extra data.
205 if (originalMessage.what
206 != PrivateHandler.MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID) {
207 return false;
208 }
209 SomeArgs originalMessageArgs = (SomeArgs) originalMessage.obj;
210 Bundle requestArguments = (Bundle) originalMessageArgs.arg4;
211 if (requestArguments == null) {
212 return false;
213 }
214
215 // If nothing it registered for this view, nothing to do
216 int accessibilityViewId = originalMessageArgs.argi1;
217 final List<AccessibilityRequestPreparer> preparers =
218 mA11yManager.getRequestPreparersForAccessibilityId(accessibilityViewId);
219 if (preparers == null) {
220 return false;
221 }
222
223 // If the bundle doesn't request the extra data, nothing to do
224 final String extraDataKey = requestArguments.getString(EXTRA_DATA_REQUESTED_KEY);
225 if (extraDataKey == null) {
226 return false;
227 }
228
229 // Send the request to the AccessibilityRequestPreparers on the UI thread
230 mNumActiveRequestPreparers = preparers.size();
231 for (int i = 0; i < preparers.size(); i++) {
232 final Message requestPreparerMessage = mHandler.obtainMessage(
233 PrivateHandler.MSG_PREPARE_FOR_EXTRA_DATA_REQUEST);
234 final SomeArgs requestPreparerArgs = SomeArgs.obtain();
235 // virtualDescendentId
236 requestPreparerArgs.argi1 =
237 (originalMessageArgs.argi2 == AccessibilityNodeInfo.UNDEFINED_ITEM_ID)
238 ? AccessibilityNodeProvider.HOST_VIEW_ID : originalMessageArgs.argi2;
239 requestPreparerArgs.arg1 = preparers.get(i);
240 requestPreparerArgs.arg2 = extraDataKey;
241 requestPreparerArgs.arg3 = requestArguments;
242 Message preparationFinishedMessage = mHandler.obtainMessage(
243 PrivateHandler.MSG_APP_PREPARATION_FINISHED);
244 preparationFinishedMessage.arg1 = ++mActiveRequestPreparerId;
245 requestPreparerArgs.arg4 = preparationFinishedMessage;
246
247 requestPreparerMessage.obj = requestPreparerArgs;
248 scheduleMessage(requestPreparerMessage, callingPid, callingTid,
249 IGNORE_REQUEST_PREPARERS);
250 mHandler.obtainMessage(PrivateHandler.MSG_APP_PREPARATION_TIMEOUT);
251 mHandler.sendEmptyMessageDelayed(PrivateHandler.MSG_APP_PREPARATION_TIMEOUT,
252 REQUEST_PREPARER_TIMEOUT_MS);
253 }
254
255 // Set the initial request aside
256 queueMessageToHandleOncePrepared(originalMessage, callingPid, callingTid);
257 return true;
258 }
259 }
260
261 private void prepareForExtraDataRequestUiThread(Message message) {
262 SomeArgs args = (SomeArgs) message.obj;
263 final int virtualDescendantId = args.argi1;
264 final AccessibilityRequestPreparer preparer = (AccessibilityRequestPreparer) args.arg1;
265 final String extraDataKey = (String) args.arg2;
266 final Bundle requestArguments = (Bundle) args.arg3;
267 final Message preparationFinishedMessage = (Message) args.arg4;
268
269 preparer.onPrepareExtraData(virtualDescendantId, extraDataKey,
270 requestArguments, preparationFinishedMessage);
271 }
272
273 private void queueMessageToHandleOncePrepared(Message message, int interrogatingPid,
274 long interrogatingTid) {
275 if (mMessagesWaitingForRequestPreparer == null) {
276 mMessagesWaitingForRequestPreparer = new ArrayList<>(1);
277 }
278 MessageHolder messageHolder =
279 new MessageHolder(message, interrogatingPid, interrogatingTid);
280 mMessagesWaitingForRequestPreparer.add(messageHolder);
281 }
282
283 private void requestPreparerDoneUiThread(Message message) {
284 synchronized (mLock) {
285 if (message.arg1 != mActiveRequestPreparerId) {
286 Slog.e(LOG_TAG, "Surprising AccessibilityRequestPreparer callback (likely late)");
287 return;
288 }
289 mNumActiveRequestPreparers--;
290 if (mNumActiveRequestPreparers <= 0) {
291 mHandler.removeMessages(PrivateHandler.MSG_APP_PREPARATION_TIMEOUT);
292 scheduleAllMessagesWaitingForRequestPreparerLocked();
293 }
294 }
295 }
296
297 private void requestPreparerTimeoutUiThread() {
298 synchronized (mLock) {
299 Slog.e(LOG_TAG, "AccessibilityRequestPreparer timed out");
300 scheduleAllMessagesWaitingForRequestPreparerLocked();
301 }
302 }
303
304 @GuardedBy("mLock")
305 private void scheduleAllMessagesWaitingForRequestPreparerLocked() {
306 int numMessages = mMessagesWaitingForRequestPreparer.size();
307 for (int i = 0; i < numMessages; i++) {
308 MessageHolder request = mMessagesWaitingForRequestPreparer.get(i);
309 scheduleMessage(request.mMessage, request.mInterrogatingPid,
310 request.mInterrogatingTid,
311 (i == 0) /* the app is ready for the first request */);
312 }
313 mMessagesWaitingForRequestPreparer.clear();
314 mNumActiveRequestPreparers = 0; // Just to be safe - should be unnecessary
315 mActiveRequestPreparerId = -1;
Svetoslav Ganov42138042012-03-20 11:51:39 -0700316 }
317
318 private void findAccessibilityNodeInfoByAccessibilityIdUiThread(Message message) {
319 final int flags = message.arg1;
Svetoslav Ganov86783472012-06-06 21:12:20 -0700320
Svetoslav Ganov42138042012-03-20 11:51:39 -0700321 SomeArgs args = (SomeArgs) message.obj;
322 final int accessibilityViewId = args.argi1;
323 final int virtualDescendantId = args.argi2;
324 final int interactionId = args.argi3;
325 final IAccessibilityInteractionConnectionCallback callback =
326 (IAccessibilityInteractionConnectionCallback) args.arg1;
Svetoslav Ganov152e9bb2012-10-12 20:15:29 -0700327 final MagnificationSpec spec = (MagnificationSpec) args.arg2;
Svetoslav9ae9ed22014-09-02 16:36:35 -0700328 final Region interactiveRegion = (Region) args.arg3;
Phil Weaverc2e28932016-12-08 12:29:25 -0800329 final Bundle arguments = (Bundle) args.arg4;
Svetoslav Ganov86783472012-06-06 21:12:20 -0700330
Svetoslav Ganov758143e2012-08-06 16:40:27 -0700331 args.recycle();
Svetoslav Ganov86783472012-06-06 21:12:20 -0700332
Svetoslav Ganov42138042012-03-20 11:51:39 -0700333 List<AccessibilityNodeInfo> infos = mTempAccessibilityNodeInfoList;
334 infos.clear();
335 try {
336 if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
337 return;
338 }
Svetoslav Ganov80943d82013-01-02 10:25:37 -0800339 mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
Qasid Ahmad Sadiq834787a2019-01-18 00:01:54 -0800340 final View root = findViewByAccessibilityId(accessibilityViewId);
Rhed Jao4b87f312019-02-13 17:45:02 +0800341 if (root != null && isShown(root)) {
Phil Weaverc2e28932016-12-08 12:29:25 -0800342 mPrefetcher.prefetchAccessibilityNodeInfos(
343 root, virtualDescendantId, flags, infos, arguments);
Svetoslav Ganov42138042012-03-20 11:51:39 -0700344 }
345 } finally {
Phil Weaverc2e28932016-12-08 12:29:25 -0800346 updateInfosForViewportAndReturnFindNodeResult(
347 infos, callback, interactionId, spec, interactiveRegion);
Svetoslav Ganov42138042012-03-20 11:51:39 -0700348 }
349 }
350
Svetoslav Ganov80943d82013-01-02 10:25:37 -0800351 public void findAccessibilityNodeInfosByViewIdClientThread(long accessibilityNodeId,
Svetoslav9ae9ed22014-09-02 16:36:35 -0700352 String viewId, Region interactiveRegion, int interactionId,
353 IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid,
354 long interrogatingTid, MagnificationSpec spec) {
Svetoslav Ganov42138042012-03-20 11:51:39 -0700355 Message message = mHandler.obtainMessage();
Svet Ganov7498efd2014-09-03 21:33:00 -0700356 message.what = PrivateHandler.MSG_FIND_ACCESSIBILITY_NODE_INFOS_BY_VIEW_ID;
Svetoslav Ganov42138042012-03-20 11:51:39 -0700357 message.arg1 = flags;
358 message.arg2 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
Svetoslav Ganov86783472012-06-06 21:12:20 -0700359
Svetoslav Ganov758143e2012-08-06 16:40:27 -0700360 SomeArgs args = SomeArgs.obtain();
Svetoslav Ganov80943d82013-01-02 10:25:37 -0800361 args.argi1 = interactionId;
Svetoslav Ganov42138042012-03-20 11:51:39 -0700362 args.arg1 = callback;
Svetoslav Ganov152e9bb2012-10-12 20:15:29 -0700363 args.arg2 = spec;
Svetoslav Ganov80943d82013-01-02 10:25:37 -0800364 args.arg3 = viewId;
Svetoslav9ae9ed22014-09-02 16:36:35 -0700365 args.arg4 = interactiveRegion;
Svetoslav Ganov42138042012-03-20 11:51:39 -0700366 message.obj = args;
Svetoslav Ganov86783472012-06-06 21:12:20 -0700367
Phil Weaver08cccc12017-02-28 09:55:31 -0800368 scheduleMessage(message, interrogatingPid, interrogatingTid, CONSIDER_REQUEST_PREPARERS);
Svetoslav Ganov42138042012-03-20 11:51:39 -0700369 }
370
Svetoslav Ganov80943d82013-01-02 10:25:37 -0800371 private void findAccessibilityNodeInfosByViewIdUiThread(Message message) {
Svetoslav Ganov42138042012-03-20 11:51:39 -0700372 final int flags = message.arg1;
373 final int accessibilityViewId = message.arg2;
Svetoslav Ganov86783472012-06-06 21:12:20 -0700374
Svetoslav Ganov42138042012-03-20 11:51:39 -0700375 SomeArgs args = (SomeArgs) message.obj;
Svetoslav Ganov80943d82013-01-02 10:25:37 -0800376 final int interactionId = args.argi1;
Svetoslav Ganov42138042012-03-20 11:51:39 -0700377 final IAccessibilityInteractionConnectionCallback callback =
378 (IAccessibilityInteractionConnectionCallback) args.arg1;
Svetoslav Ganov152e9bb2012-10-12 20:15:29 -0700379 final MagnificationSpec spec = (MagnificationSpec) args.arg2;
Svetoslav Ganov80943d82013-01-02 10:25:37 -0800380 final String viewId = (String) args.arg3;
Svetoslav9ae9ed22014-09-02 16:36:35 -0700381 final Region interactiveRegion = (Region) args.arg4;
Svetoslav Ganov758143e2012-08-06 16:40:27 -0700382 args.recycle();
Svetoslav Ganov86783472012-06-06 21:12:20 -0700383
Svetoslav Ganov80943d82013-01-02 10:25:37 -0800384 final List<AccessibilityNodeInfo> infos = mTempAccessibilityNodeInfoList;
385 infos.clear();
Svetoslav Ganov42138042012-03-20 11:51:39 -0700386 try {
387 if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
388 return;
389 }
Svetoslav Ganov80943d82013-01-02 10:25:37 -0800390 mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
Qasid Ahmad Sadiq834787a2019-01-18 00:01:54 -0800391 final View root = findViewByAccessibilityId(accessibilityViewId);
Svetoslav Ganov42138042012-03-20 11:51:39 -0700392 if (root != null) {
Svetoslava33243e2013-02-11 15:43:46 -0800393 final int resolvedViewId = root.getContext().getResources()
394 .getIdentifier(viewId, null, null);
Svetoslav Ganov80943d82013-01-02 10:25:37 -0800395 if (resolvedViewId <= 0) {
396 return;
397 }
398 if (mAddNodeInfosForViewId == null) {
399 mAddNodeInfosForViewId = new AddNodeInfosForViewId();
400 }
401 mAddNodeInfosForViewId.init(resolvedViewId, infos);
402 root.findViewByPredicate(mAddNodeInfosForViewId);
403 mAddNodeInfosForViewId.reset();
Svetoslav Ganov42138042012-03-20 11:51:39 -0700404 }
405 } finally {
Phil Weaverc2e28932016-12-08 12:29:25 -0800406 updateInfosForViewportAndReturnFindNodeResult(
407 infos, callback, interactionId, spec, interactiveRegion);
Svetoslav Ganov42138042012-03-20 11:51:39 -0700408 }
409 }
410
411 public void findAccessibilityNodeInfosByTextClientThread(long accessibilityNodeId,
Svetoslav9ae9ed22014-09-02 16:36:35 -0700412 String text, Region interactiveRegion, int interactionId,
413 IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid,
414 long interrogatingTid, MagnificationSpec spec) {
Svetoslav Ganov42138042012-03-20 11:51:39 -0700415 Message message = mHandler.obtainMessage();
Svet Ganov7498efd2014-09-03 21:33:00 -0700416 message.what = PrivateHandler.MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_TEXT;
Svetoslav Ganov42138042012-03-20 11:51:39 -0700417 message.arg1 = flags;
Svetoslav Ganov86783472012-06-06 21:12:20 -0700418
Svetoslav Ganov758143e2012-08-06 16:40:27 -0700419 SomeArgs args = SomeArgs.obtain();
Svetoslav Ganov42138042012-03-20 11:51:39 -0700420 args.arg1 = text;
Svetoslav Ganovc9c9a482012-07-16 08:46:07 -0700421 args.arg2 = callback;
Svetoslav Ganov152e9bb2012-10-12 20:15:29 -0700422 args.arg3 = spec;
Svetoslav Ganov42138042012-03-20 11:51:39 -0700423 args.argi1 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
424 args.argi2 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId);
425 args.argi3 = interactionId;
Svetoslav9ae9ed22014-09-02 16:36:35 -0700426 args.arg4 = interactiveRegion;
Svetoslav Ganov42138042012-03-20 11:51:39 -0700427 message.obj = args;
Svetoslav Ganov86783472012-06-06 21:12:20 -0700428
Phil Weaver08cccc12017-02-28 09:55:31 -0800429 scheduleMessage(message, interrogatingPid, interrogatingTid, CONSIDER_REQUEST_PREPARERS);
Svetoslav Ganov42138042012-03-20 11:51:39 -0700430 }
431
432 private void findAccessibilityNodeInfosByTextUiThread(Message message) {
433 final int flags = message.arg1;
Svetoslav Ganov86783472012-06-06 21:12:20 -0700434
Svetoslav Ganov42138042012-03-20 11:51:39 -0700435 SomeArgs args = (SomeArgs) message.obj;
436 final String text = (String) args.arg1;
Svetoslav Ganovc9c9a482012-07-16 08:46:07 -0700437 final IAccessibilityInteractionConnectionCallback callback =
438 (IAccessibilityInteractionConnectionCallback) args.arg2;
Svetoslav Ganov152e9bb2012-10-12 20:15:29 -0700439 final MagnificationSpec spec = (MagnificationSpec) args.arg3;
Svetoslav Ganov42138042012-03-20 11:51:39 -0700440 final int accessibilityViewId = args.argi1;
441 final int virtualDescendantId = args.argi2;
442 final int interactionId = args.argi3;
Svetoslav9ae9ed22014-09-02 16:36:35 -0700443 final Region interactiveRegion = (Region) args.arg4;
Svetoslav Ganov758143e2012-08-06 16:40:27 -0700444 args.recycle();
Svetoslav Ganov86783472012-06-06 21:12:20 -0700445
Svetoslav Ganov42138042012-03-20 11:51:39 -0700446 List<AccessibilityNodeInfo> infos = null;
447 try {
448 if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
449 return;
450 }
Svetoslav Ganov80943d82013-01-02 10:25:37 -0800451 mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
Qasid Ahmad Sadiq834787a2019-01-18 00:01:54 -0800452 final View root = findViewByAccessibilityId(accessibilityViewId);
Rhed Jao4b87f312019-02-13 17:45:02 +0800453 if (root != null && isShown(root)) {
Svetoslav Ganov42138042012-03-20 11:51:39 -0700454 AccessibilityNodeProvider provider = root.getAccessibilityNodeProvider();
455 if (provider != null) {
Phil Weaverf00cd142017-03-03 13:44:00 -0800456 infos = provider.findAccessibilityNodeInfosByText(text,
457 virtualDescendantId);
458 } else if (virtualDescendantId == AccessibilityNodeProvider.HOST_VIEW_ID) {
Svetoslav Ganov30ac6452012-06-01 09:10:25 -0700459 ArrayList<View> foundViews = mTempArrayList;
Svetoslav Ganov42138042012-03-20 11:51:39 -0700460 foundViews.clear();
461 root.findViewsWithText(foundViews, text, View.FIND_VIEWS_WITH_TEXT
462 | View.FIND_VIEWS_WITH_CONTENT_DESCRIPTION
463 | View.FIND_VIEWS_WITH_ACCESSIBILITY_NODE_PROVIDERS);
464 if (!foundViews.isEmpty()) {
465 infos = mTempAccessibilityNodeInfoList;
466 infos.clear();
467 final int viewCount = foundViews.size();
468 for (int i = 0; i < viewCount; i++) {
469 View foundView = foundViews.get(i);
Svetoslav Ganov0a1bb6d2012-05-07 11:54:39 -0700470 if (isShown(foundView)) {
Svetoslav Ganov42138042012-03-20 11:51:39 -0700471 provider = foundView.getAccessibilityNodeProvider();
472 if (provider != null) {
473 List<AccessibilityNodeInfo> infosFromProvider =
474 provider.findAccessibilityNodeInfosByText(text,
Svetoslav8e3feb12014-02-24 13:46:47 -0800475 AccessibilityNodeProvider.HOST_VIEW_ID);
Svetoslav Ganov42138042012-03-20 11:51:39 -0700476 if (infosFromProvider != null) {
477 infos.addAll(infosFromProvider);
478 }
479 } else {
480 infos.add(foundView.createAccessibilityNodeInfo());
481 }
482 }
483 }
484 }
485 }
486 }
487 } finally {
Phil Weaverc2e28932016-12-08 12:29:25 -0800488 updateInfosForViewportAndReturnFindNodeResult(
489 infos, callback, interactionId, spec, interactiveRegion);
Svetoslav Ganov42138042012-03-20 11:51:39 -0700490 }
491 }
492
Svetoslav9ae9ed22014-09-02 16:36:35 -0700493 public void findFocusClientThread(long accessibilityNodeId, int focusType,
494 Region interactiveRegion, int interactionId,
Phil Weaverc2e28932016-12-08 12:29:25 -0800495 IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid,
Svetoslav Ganov152e9bb2012-10-12 20:15:29 -0700496 long interrogatingTid, MagnificationSpec spec) {
Svetoslav Ganov42138042012-03-20 11:51:39 -0700497 Message message = mHandler.obtainMessage();
498 message.what = PrivateHandler.MSG_FIND_FOCUS;
499 message.arg1 = flags;
500 message.arg2 = focusType;
Svetoslav Ganov86783472012-06-06 21:12:20 -0700501
Svetoslav Ganov758143e2012-08-06 16:40:27 -0700502 SomeArgs args = SomeArgs.obtain();
Svetoslav Ganov42138042012-03-20 11:51:39 -0700503 args.argi1 = interactionId;
504 args.argi2 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
505 args.argi3 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId);
506 args.arg1 = callback;
Svetoslav Ganov152e9bb2012-10-12 20:15:29 -0700507 args.arg2 = spec;
Svetoslav9ae9ed22014-09-02 16:36:35 -0700508 args.arg3 = interactiveRegion;
Svetoslav Ganov86783472012-06-06 21:12:20 -0700509
Svetoslav Ganov42138042012-03-20 11:51:39 -0700510 message.obj = args;
Svetoslav Ganov86783472012-06-06 21:12:20 -0700511
Phil Weaver08cccc12017-02-28 09:55:31 -0800512 scheduleMessage(message, interrogatingPid, interrogatingTid, CONSIDER_REQUEST_PREPARERS);
Svetoslav Ganov42138042012-03-20 11:51:39 -0700513 }
514
515 private void findFocusUiThread(Message message) {
516 final int flags = message.arg1;
517 final int focusType = message.arg2;
Svetoslav Ganov86783472012-06-06 21:12:20 -0700518
Svetoslav Ganov42138042012-03-20 11:51:39 -0700519 SomeArgs args = (SomeArgs) message.obj;
520 final int interactionId = args.argi1;
521 final int accessibilityViewId = args.argi2;
522 final int virtualDescendantId = args.argi3;
523 final IAccessibilityInteractionConnectionCallback callback =
524 (IAccessibilityInteractionConnectionCallback) args.arg1;
Svetoslav Ganov152e9bb2012-10-12 20:15:29 -0700525 final MagnificationSpec spec = (MagnificationSpec) args.arg2;
Svetoslav9ae9ed22014-09-02 16:36:35 -0700526 final Region interactiveRegion = (Region) args.arg3;
Svetoslav Ganov758143e2012-08-06 16:40:27 -0700527 args.recycle();
Svetoslav Ganov86783472012-06-06 21:12:20 -0700528
Svetoslav Ganov42138042012-03-20 11:51:39 -0700529 AccessibilityNodeInfo focused = null;
530 try {
531 if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
532 return;
533 }
Svetoslav Ganov80943d82013-01-02 10:25:37 -0800534 mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
Qasid Ahmad Sadiq834787a2019-01-18 00:01:54 -0800535 final View root = findViewByAccessibilityId(accessibilityViewId);
Rhed Jao4b87f312019-02-13 17:45:02 +0800536 if (root != null && isShown(root)) {
Svetoslav Ganov42138042012-03-20 11:51:39 -0700537 switch (focusType) {
538 case AccessibilityNodeInfo.FOCUS_ACCESSIBILITY: {
539 View host = mViewRootImpl.mAccessibilityFocusedHost;
540 // If there is no accessibility focus host or it is not a descendant
541 // of the root from which to start the search, then the search failed.
542 if (host == null || !ViewRootImpl.isViewDescendantOf(host, root)) {
543 break;
544 }
Svetoslav6254f482013-06-04 17:22:14 -0700545 // The focused view not shown, we failed.
546 if (!isShown(host)) {
547 break;
548 }
Svetoslav Ganov42138042012-03-20 11:51:39 -0700549 // If the host has a provider ask this provider to search for the
550 // focus instead fetching all provider nodes to do the search here.
551 AccessibilityNodeProvider provider = host.getAccessibilityNodeProvider();
552 if (provider != null) {
Svetoslav Ganov45a02e02012-06-17 15:07:29 -0700553 if (mViewRootImpl.mAccessibilityFocusedVirtualView != null) {
554 focused = AccessibilityNodeInfo.obtain(
555 mViewRootImpl.mAccessibilityFocusedVirtualView);
556 }
Phil Weaverf00cd142017-03-03 13:44:00 -0800557 } else if (virtualDescendantId == AccessibilityNodeProvider.HOST_VIEW_ID) {
Svetoslav Ganov42138042012-03-20 11:51:39 -0700558 focused = host.createAccessibilityNodeInfo();
559 }
560 } break;
561 case AccessibilityNodeInfo.FOCUS_INPUT: {
Svetoslav Ganov42138042012-03-20 11:51:39 -0700562 View target = root.findFocus();
Qasid Ahmad Sadiq834787a2019-01-18 00:01:54 -0800563 if (!isShown(target)) {
Alan Viverette2e1e0812013-09-30 13:45:55 -0700564 break;
565 }
566 AccessibilityNodeProvider provider = target.getAccessibilityNodeProvider();
567 if (provider != null) {
568 focused = provider.findFocus(focusType);
569 }
570 if (focused == null) {
Svetoslav Ganov42138042012-03-20 11:51:39 -0700571 focused = target.createAccessibilityNodeInfo();
572 }
573 } break;
574 default:
575 throw new IllegalArgumentException("Unknown focus type: " + focusType);
576 }
577 }
578 } finally {
Phil Weaverc2e28932016-12-08 12:29:25 -0800579 updateInfoForViewportAndReturnFindNodeResult(
580 focused, callback, interactionId, spec, interactiveRegion);
Svetoslav Ganov42138042012-03-20 11:51:39 -0700581 }
582 }
583
Svetoslav9ae9ed22014-09-02 16:36:35 -0700584 public void focusSearchClientThread(long accessibilityNodeId, int direction,
585 Region interactiveRegion, int interactionId,
Phil Weaverc2e28932016-12-08 12:29:25 -0800586 IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid,
Svetoslav Ganov152e9bb2012-10-12 20:15:29 -0700587 long interrogatingTid, MagnificationSpec spec) {
Svetoslav Ganov42138042012-03-20 11:51:39 -0700588 Message message = mHandler.obtainMessage();
589 message.what = PrivateHandler.MSG_FOCUS_SEARCH;
590 message.arg1 = flags;
591 message.arg2 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
Svetoslav Ganov86783472012-06-06 21:12:20 -0700592
Svetoslav Ganov758143e2012-08-06 16:40:27 -0700593 SomeArgs args = SomeArgs.obtain();
Svetoslav Ganov42138042012-03-20 11:51:39 -0700594 args.argi2 = direction;
595 args.argi3 = interactionId;
596 args.arg1 = callback;
Svetoslav Ganov152e9bb2012-10-12 20:15:29 -0700597 args.arg2 = spec;
Svetoslav9ae9ed22014-09-02 16:36:35 -0700598 args.arg3 = interactiveRegion;
Svetoslav Ganov86783472012-06-06 21:12:20 -0700599
Svetoslav Ganov42138042012-03-20 11:51:39 -0700600 message.obj = args;
Svetoslav Ganov86783472012-06-06 21:12:20 -0700601
Phil Weaver08cccc12017-02-28 09:55:31 -0800602 scheduleMessage(message, interrogatingPid, interrogatingTid, CONSIDER_REQUEST_PREPARERS);
Svetoslav Ganov42138042012-03-20 11:51:39 -0700603 }
604
605 private void focusSearchUiThread(Message message) {
606 final int flags = message.arg1;
607 final int accessibilityViewId = message.arg2;
Svetoslav Ganov86783472012-06-06 21:12:20 -0700608
Svetoslav Ganov42138042012-03-20 11:51:39 -0700609 SomeArgs args = (SomeArgs) message.obj;
Svetoslav Ganov42138042012-03-20 11:51:39 -0700610 final int direction = args.argi2;
611 final int interactionId = args.argi3;
612 final IAccessibilityInteractionConnectionCallback callback =
613 (IAccessibilityInteractionConnectionCallback) args.arg1;
Svetoslav Ganov152e9bb2012-10-12 20:15:29 -0700614 final MagnificationSpec spec = (MagnificationSpec) args.arg2;
Svetoslav9ae9ed22014-09-02 16:36:35 -0700615 final Region interactiveRegion = (Region) args.arg3;
Svetoslav Ganov86783472012-06-06 21:12:20 -0700616
Svetoslav Ganov758143e2012-08-06 16:40:27 -0700617 args.recycle();
Svetoslav Ganov86783472012-06-06 21:12:20 -0700618
Svetoslav Ganov42138042012-03-20 11:51:39 -0700619 AccessibilityNodeInfo next = null;
620 try {
621 if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
622 return;
623 }
Svetoslav Ganov80943d82013-01-02 10:25:37 -0800624 mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
Qasid Ahmad Sadiq834787a2019-01-18 00:01:54 -0800625 final View root = findViewByAccessibilityId(accessibilityViewId);
Rhed Jao4b87f312019-02-13 17:45:02 +0800626 if (root != null && isShown(root)) {
Svetoslav Ganov27e2da72012-07-02 18:12:00 -0700627 View nextView = root.focusSearch(direction);
628 if (nextView != null) {
629 next = nextView.createAccessibilityNodeInfo();
Svetoslav Ganov42138042012-03-20 11:51:39 -0700630 }
631 }
632 } finally {
Phil Weaverc2e28932016-12-08 12:29:25 -0800633 updateInfoForViewportAndReturnFindNodeResult(
634 next, callback, interactionId, spec, interactiveRegion);
Svetoslav Ganov42138042012-03-20 11:51:39 -0700635 }
636 }
637
638 public void performAccessibilityActionClientThread(long accessibilityNodeId, int action,
Svetoslav Ganovaa780c12012-04-19 23:01:39 -0700639 Bundle arguments, int interactionId,
Phil Weaverc2e28932016-12-08 12:29:25 -0800640 IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid,
Svetoslav Ganovaa780c12012-04-19 23:01:39 -0700641 long interrogatingTid) {
Svetoslav Ganov42138042012-03-20 11:51:39 -0700642 Message message = mHandler.obtainMessage();
643 message.what = PrivateHandler.MSG_PERFORM_ACCESSIBILITY_ACTION;
644 message.arg1 = flags;
645 message.arg2 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
Svetoslav Ganov86783472012-06-06 21:12:20 -0700646
Svetoslav Ganov758143e2012-08-06 16:40:27 -0700647 SomeArgs args = SomeArgs.obtain();
Svetoslav Ganov42138042012-03-20 11:51:39 -0700648 args.argi1 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId);
649 args.argi2 = action;
650 args.argi3 = interactionId;
651 args.arg1 = callback;
Svetoslav Ganovaa780c12012-04-19 23:01:39 -0700652 args.arg2 = arguments;
Svetoslav Ganov86783472012-06-06 21:12:20 -0700653
Svetoslav Ganov42138042012-03-20 11:51:39 -0700654 message.obj = args;
Svetoslav Ganov86783472012-06-06 21:12:20 -0700655
Phil Weaver08cccc12017-02-28 09:55:31 -0800656 scheduleMessage(message, interrogatingPid, interrogatingTid, CONSIDER_REQUEST_PREPARERS);
Svetoslav Ganov42138042012-03-20 11:51:39 -0700657 }
658
George Mount41725de2015-04-09 08:23:05 -0700659 private void performAccessibilityActionUiThread(Message message) {
Svetoslav Ganov42138042012-03-20 11:51:39 -0700660 final int flags = message.arg1;
661 final int accessibilityViewId = message.arg2;
Svetoslav Ganov86783472012-06-06 21:12:20 -0700662
Svetoslav Ganov42138042012-03-20 11:51:39 -0700663 SomeArgs args = (SomeArgs) message.obj;
664 final int virtualDescendantId = args.argi1;
665 final int action = args.argi2;
666 final int interactionId = args.argi3;
667 final IAccessibilityInteractionConnectionCallback callback =
668 (IAccessibilityInteractionConnectionCallback) args.arg1;
Svetoslav Ganovaa780c12012-04-19 23:01:39 -0700669 Bundle arguments = (Bundle) args.arg2;
Svetoslav Ganov86783472012-06-06 21:12:20 -0700670
Svetoslav Ganov758143e2012-08-06 16:40:27 -0700671 args.recycle();
Svetoslav Ganov86783472012-06-06 21:12:20 -0700672
Svetoslav Ganov42138042012-03-20 11:51:39 -0700673 boolean succeeded = false;
674 try {
George Mount41725de2015-04-09 08:23:05 -0700675 if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null ||
676 mViewRootImpl.mStopped || mViewRootImpl.mPausedForTransition) {
Svetoslav Ganov42138042012-03-20 11:51:39 -0700677 return;
678 }
Svetoslav Ganov80943d82013-01-02 10:25:37 -0800679 mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
Qasid Ahmad Sadiq834787a2019-01-18 00:01:54 -0800680 final View target = findViewByAccessibilityId(accessibilityViewId);
Rhed Jao4b87f312019-02-13 17:45:02 +0800681 if (target != null && isShown(target)) {
Phil Weaver193520e2016-12-13 09:39:06 -0800682 if (action == R.id.accessibilityActionClickOnClickableSpan) {
683 // Handle this hidden action separately
684 succeeded = handleClickableSpanActionUiThread(
685 target, virtualDescendantId, arguments);
686 } else {
687 AccessibilityNodeProvider provider = target.getAccessibilityNodeProvider();
688 if (provider != null) {
Phil Weaverf00cd142017-03-03 13:44:00 -0800689 succeeded = provider.performAction(virtualDescendantId, action,
690 arguments);
691 } else if (virtualDescendantId == AccessibilityNodeProvider.HOST_VIEW_ID) {
Phil Weaver193520e2016-12-13 09:39:06 -0800692 succeeded = target.performAccessibilityAction(action, arguments);
Svetoslav8e3feb12014-02-24 13:46:47 -0800693 }
Svetoslav Ganov42138042012-03-20 11:51:39 -0700694 }
695 }
696 } finally {
697 try {
Svetoslav Ganov80943d82013-01-02 10:25:37 -0800698 mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
Svetoslav Ganov42138042012-03-20 11:51:39 -0700699 callback.setPerformAccessibilityActionResult(succeeded, interactionId);
700 } catch (RemoteException re) {
701 /* ignore - the other side will time out */
702 }
703 }
704 }
705
Rhed Jao23813d92018-12-18 21:30:52 +0800706 /**
707 * Finds the accessibility focused node in the root, and clears the accessibility focus.
708 */
709 public void clearAccessibilityFocusClientThread() {
710 final Message message = mHandler.obtainMessage();
711 message.what = PrivateHandler.MSG_CLEAR_ACCESSIBILITY_FOCUS;
712
713 // Don't care about pid and tid because there's no interrogating client for this message.
714 scheduleMessage(message, 0, 0, CONSIDER_REQUEST_PREPARERS);
715 }
716
717 private void clearAccessibilityFocusUiThread() {
718 if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
719 return;
720 }
721 try {
722 mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags =
723 AccessibilityNodeInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS;
724 final View root = mViewRootImpl.mView;
725 if (root != null && isShown(root)) {
726 final View host = mViewRootImpl.mAccessibilityFocusedHost;
727 // If there is no accessibility focus host or it is not a descendant
728 // of the root from which to start the search, then the search failed.
729 if (host == null || !ViewRootImpl.isViewDescendantOf(host, root)) {
730 return;
731 }
732 final AccessibilityNodeProvider provider = host.getAccessibilityNodeProvider();
733 final AccessibilityNodeInfo focusNode =
734 mViewRootImpl.mAccessibilityFocusedVirtualView;
735 if (provider != null && focusNode != null) {
736 final int virtualNodeId = AccessibilityNodeInfo.getVirtualDescendantId(
737 focusNode.getSourceNodeId());
738 provider.performAction(virtualNodeId,
739 AccessibilityAction.ACTION_CLEAR_ACCESSIBILITY_FOCUS.getId(),
740 null);
741 } else {
742 host.performAccessibilityAction(
743 AccessibilityAction.ACTION_CLEAR_ACCESSIBILITY_FOCUS.getId(),
744 null);
745 }
746 }
747 } finally {
748 mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
749 }
750 }
751
Jackal Guo8bcc0a92019-04-16 15:26:00 +0800752 /**
753 * Notify outside touch event to the target window.
754 */
755 public void notifyOutsideTouchClientThread() {
756 final Message message = mHandler.obtainMessage();
757 message.what = PrivateHandler.MSG_NOTIFY_OUTSIDE_TOUCH;
758
759 // Don't care about pid and tid because there's no interrogating client for this message.
760 scheduleMessage(message, 0, 0, CONSIDER_REQUEST_PREPARERS);
761 }
762
763 private void notifyOutsideTouchUiThread() {
764 if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null
765 || mViewRootImpl.mStopped || mViewRootImpl.mPausedForTransition) {
766 return;
767 }
768 final View root = mViewRootImpl.mView;
769 if (root != null && isShown(root)) {
770 // trigger ACTION_OUTSIDE to notify windows
771 final long now = SystemClock.uptimeMillis();
772 final MotionEvent event = MotionEvent.obtain(now, now, MotionEvent.ACTION_OUTSIDE,
773 0, 0, 0);
774 event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
775 mViewRootImpl.dispatchInputEvent(event);
776 }
777 }
778
Svetoslav Ganov42138042012-03-20 11:51:39 -0700779 private View findViewByAccessibilityId(int accessibilityId) {
Qasid Ahmad Sadiq834787a2019-01-18 00:01:54 -0800780 if (accessibilityId == AccessibilityNodeInfo.ROOT_ITEM_ID) {
781 return mViewRootImpl.mView;
782 } else {
Rhed Jao4b87f312019-02-13 17:45:02 +0800783 return AccessibilityNodeIdManager.getInstance().findView(accessibilityId);
Svetoslav Ganov42138042012-03-20 11:51:39 -0700784 }
Svetoslav Ganov42138042012-03-20 11:51:39 -0700785 }
786
Svetoslav Ganov152e9bb2012-10-12 20:15:29 -0700787 private void applyAppScaleAndMagnificationSpecIfNeeded(List<AccessibilityNodeInfo> infos,
788 MagnificationSpec spec) {
Svetoslav Ganovfd8d9c42012-07-09 18:16:55 -0700789 if (infos == null) {
790 return;
791 }
792 final float applicationScale = mViewRootImpl.mAttachInfo.mApplicationScale;
Svetoslav Ganov152e9bb2012-10-12 20:15:29 -0700793 if (shouldApplyAppScaleAndMagnificationSpec(applicationScale, spec)) {
Svetoslav Ganovfd8d9c42012-07-09 18:16:55 -0700794 final int infoCount = infos.size();
795 for (int i = 0; i < infoCount; i++) {
796 AccessibilityNodeInfo info = infos.get(i);
Svetoslav Ganov152e9bb2012-10-12 20:15:29 -0700797 applyAppScaleAndMagnificationSpecIfNeeded(info, spec);
Svetoslav Ganovfd8d9c42012-07-09 18:16:55 -0700798 }
799 }
800 }
801
Svetoslav9ae9ed22014-09-02 16:36:35 -0700802 private void adjustIsVisibleToUserIfNeeded(List<AccessibilityNodeInfo> infos,
803 Region interactiveRegion) {
804 if (interactiveRegion == null || infos == null) {
805 return;
806 }
807 final int infoCount = infos.size();
808 for (int i = 0; i < infoCount; i++) {
809 AccessibilityNodeInfo info = infos.get(i);
810 adjustIsVisibleToUserIfNeeded(info, interactiveRegion);
811 }
812 }
813
814 private void adjustIsVisibleToUserIfNeeded(AccessibilityNodeInfo info,
815 Region interactiveRegion) {
816 if (interactiveRegion == null || info == null) {
817 return;
818 }
819 Rect boundsInScreen = mTempRect;
820 info.getBoundsInScreen(boundsInScreen);
Jackal Guoecd09bb2019-03-20 17:43:19 +0800821 if (interactiveRegion.quickReject(boundsInScreen) && !shouldBypassAdjustIsVisible()) {
Svetoslav9ae9ed22014-09-02 16:36:35 -0700822 info.setVisibleToUser(false);
823 }
824 }
825
Jackal Guoecd09bb2019-03-20 17:43:19 +0800826 private boolean shouldBypassAdjustIsVisible() {
827 final int windowType = mViewRootImpl.mOrigWindowType;
828 if (windowType == TYPE_INPUT_METHOD) {
829 return true;
830 }
831 return false;
832 }
833
Jackal Guoc43a0a62019-04-23 09:15:14 +0800834 private void adjustBoundsInScreenIfNeeded(List<AccessibilityNodeInfo> infos) {
835 if (infos == null || shouldBypassAdjustBoundsInScreen()) {
836 return;
837 }
838 final int infoCount = infos.size();
839 for (int i = 0; i < infoCount; i++) {
840 final AccessibilityNodeInfo info = infos.get(i);
841 adjustBoundsInScreenIfNeeded(info);
842 }
843 }
844
845 private void adjustBoundsInScreenIfNeeded(AccessibilityNodeInfo info) {
846 if (info == null || shouldBypassAdjustBoundsInScreen()) {
847 return;
848 }
849 final Rect boundsInScreen = mTempRect;
850 info.getBoundsInScreen(boundsInScreen);
851 boundsInScreen.offset(mViewRootImpl.mAttachInfo.mLocationInParentDisplay.x,
852 mViewRootImpl.mAttachInfo.mLocationInParentDisplay.y);
853 info.setBoundsInScreen(boundsInScreen);
854 }
855
856 private boolean shouldBypassAdjustBoundsInScreen() {
857 return mViewRootImpl.mAttachInfo.mLocationInParentDisplay.equals(0, 0);
858 }
859
Jackal Guo957deab2020-02-04 14:34:40 +0800860 private void applyScreenMatrixIfNeeded(List<AccessibilityNodeInfo> infos) {
861 if (infos == null || shouldBypassApplyScreenMatrix()) {
862 return;
863 }
864 final int infoCount = infos.size();
865 for (int i = 0; i < infoCount; i++) {
866 final AccessibilityNodeInfo info = infos.get(i);
867 applyScreenMatrixIfNeeded(info);
868 }
869 }
870
871 private void applyScreenMatrixIfNeeded(AccessibilityNodeInfo info) {
872 if (info == null || shouldBypassApplyScreenMatrix()) {
873 return;
874 }
875 final Rect boundsInScreen = mTempRect;
876 final RectF transformedBounds = mTempRectF;
877 final Matrix screenMatrix = mViewRootImpl.mAttachInfo.mScreenMatrixInEmbeddedHierarchy;
878
879 info.getBoundsInScreen(boundsInScreen);
880 transformedBounds.set(boundsInScreen);
881 screenMatrix.mapRect(transformedBounds);
882 boundsInScreen.set((int) transformedBounds.left, (int) transformedBounds.top,
883 (int) transformedBounds.right, (int) transformedBounds.bottom);
884 info.setBoundsInScreen(boundsInScreen);
885 }
886
887 private boolean shouldBypassApplyScreenMatrix() {
888 final Matrix screenMatrix = mViewRootImpl.mAttachInfo.mScreenMatrixInEmbeddedHierarchy;
889 return screenMatrix == null || screenMatrix.isIdentity();
890 }
891
Jackal Guoa459b192019-12-23 16:46:27 +0800892 private void associateLeashedParentIfNeeded(List<AccessibilityNodeInfo> infos) {
893 if (infos == null || shouldBypassAssociateLeashedParent()) {
894 return;
895 }
896 final int infoCount = infos.size();
897 for (int i = 0; i < infoCount; i++) {
898 final AccessibilityNodeInfo info = infos.get(i);
899 associateLeashedParentIfNeeded(info);
900 }
901 }
902
903 private void associateLeashedParentIfNeeded(AccessibilityNodeInfo info) {
904 if (info == null || shouldBypassAssociateLeashedParent()) {
905 return;
906 }
907 // The node id of root node in embedded maybe not be ROOT_NODE_ID so we compare the id
908 // with root view.
909 if (mViewRootImpl.mView.getAccessibilityViewId()
910 != AccessibilityNodeInfo.getAccessibilityViewId(info.getSourceNodeId())) {
911 return;
912 }
913 info.setLeashedParent(mViewRootImpl.mAttachInfo.mLeashedParentToken,
914 mViewRootImpl.mAttachInfo.mLeashedParentAccessibilityViewId);
915 }
916
917 private boolean shouldBypassAssociateLeashedParent() {
918 return (mViewRootImpl.mAttachInfo.mLeashedParentToken == null
919 && mViewRootImpl.mAttachInfo.mLeashedParentAccessibilityViewId == View.NO_ID);
920 }
921
Svetoslav Ganov152e9bb2012-10-12 20:15:29 -0700922 private void applyAppScaleAndMagnificationSpecIfNeeded(AccessibilityNodeInfo info,
923 MagnificationSpec spec) {
Svetoslav Ganovfd8d9c42012-07-09 18:16:55 -0700924 if (info == null) {
925 return;
926 }
Svetoslav Ganov152e9bb2012-10-12 20:15:29 -0700927
Svetoslav Ganovfd8d9c42012-07-09 18:16:55 -0700928 final float applicationScale = mViewRootImpl.mAttachInfo.mApplicationScale;
Svetoslav Ganov152e9bb2012-10-12 20:15:29 -0700929 if (!shouldApplyAppScaleAndMagnificationSpec(applicationScale, spec)) {
930 return;
931 }
932
933 Rect boundsInParent = mTempRect;
934 Rect boundsInScreen = mTempRect1;
935
936 info.getBoundsInParent(boundsInParent);
937 info.getBoundsInScreen(boundsInScreen);
Svetoslav Ganovfd8d9c42012-07-09 18:16:55 -0700938 if (applicationScale != 1.0f) {
Svetoslav Ganov152e9bb2012-10-12 20:15:29 -0700939 boundsInParent.scale(applicationScale);
940 boundsInScreen.scale(applicationScale);
941 }
942 if (spec != null) {
943 boundsInParent.scale(spec.scale);
944 // boundsInParent must not be offset.
945 boundsInScreen.scale(spec.scale);
946 boundsInScreen.offset((int) spec.offsetX, (int) spec.offsetY);
947 }
948 info.setBoundsInParent(boundsInParent);
949 info.setBoundsInScreen(boundsInScreen);
Svetoslav Ganovfd8d9c42012-07-09 18:16:55 -0700950
Phil Weaverc2e28932016-12-08 12:29:25 -0800951 // Scale text locations if they are present
952 if (info.hasExtras()) {
953 Bundle extras = info.getExtras();
954 Parcelable[] textLocations =
955 extras.getParcelableArray(EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY);
956 if (textLocations != null) {
957 for (int i = 0; i < textLocations.length; i++) {
958 // Unchecked cast - an app that puts other objects in this bundle with this
959 // key will crash.
960 RectF textLocation = ((RectF) textLocations[i]);
961 textLocation.scale(applicationScale);
962 if (spec != null) {
963 textLocation.scale(spec.scale);
964 textLocation.offset(spec.offsetX, spec.offsetY);
965 }
966 }
967 }
968 }
Svetoslav Ganovfd8d9c42012-07-09 18:16:55 -0700969 }
970
Svetoslav Ganov152e9bb2012-10-12 20:15:29 -0700971 private boolean shouldApplyAppScaleAndMagnificationSpec(float appScale,
972 MagnificationSpec spec) {
973 return (appScale != 1.0f || (spec != null && !spec.isNop()));
974 }
Svetoslav Ganovfd8d9c42012-07-09 18:16:55 -0700975
Phil Weaverc2e28932016-12-08 12:29:25 -0800976 private void updateInfosForViewportAndReturnFindNodeResult(List<AccessibilityNodeInfo> infos,
977 IAccessibilityInteractionConnectionCallback callback, int interactionId,
978 MagnificationSpec spec, Region interactiveRegion) {
979 try {
980 mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
Jackal Guoa459b192019-12-23 16:46:27 +0800981 associateLeashedParentIfNeeded(infos);
Jackal Guo957deab2020-02-04 14:34:40 +0800982 applyScreenMatrixIfNeeded(infos);
Jackal Guoc43a0a62019-04-23 09:15:14 +0800983 adjustBoundsInScreenIfNeeded(infos);
Rhed Jao882145e2019-07-04 19:56:18 +0800984 // To avoid applyAppScaleAndMagnificationSpecIfNeeded changing the bounds of node,
985 // then impact the visibility result, we need to adjust visibility before apply scale.
Phil Weaverc2e28932016-12-08 12:29:25 -0800986 adjustIsVisibleToUserIfNeeded(infos, interactiveRegion);
Rhed Jao882145e2019-07-04 19:56:18 +0800987 applyAppScaleAndMagnificationSpecIfNeeded(infos, spec);
Phil Weaverc2e28932016-12-08 12:29:25 -0800988 callback.setFindAccessibilityNodeInfosResult(infos, interactionId);
Phil Weaverf00cd142017-03-03 13:44:00 -0800989 if (infos != null) {
990 infos.clear();
991 }
Phil Weaverc2e28932016-12-08 12:29:25 -0800992 } catch (RemoteException re) {
993 /* ignore - the other side will time out */
994 } finally {
995 recycleMagnificationSpecAndRegionIfNeeded(spec, interactiveRegion);
996 }
997 }
998
999 private void updateInfoForViewportAndReturnFindNodeResult(AccessibilityNodeInfo info,
1000 IAccessibilityInteractionConnectionCallback callback, int interactionId,
1001 MagnificationSpec spec, Region interactiveRegion) {
1002 try {
1003 mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
Jackal Guoa459b192019-12-23 16:46:27 +08001004 associateLeashedParentIfNeeded(info);
Jackal Guo957deab2020-02-04 14:34:40 +08001005 applyScreenMatrixIfNeeded(info);
Jackal Guoc43a0a62019-04-23 09:15:14 +08001006 adjustBoundsInScreenIfNeeded(info);
Rhed Jao882145e2019-07-04 19:56:18 +08001007 // To avoid applyAppScaleAndMagnificationSpecIfNeeded changing the bounds of node,
1008 // then impact the visibility result, we need to adjust visibility before apply scale.
Phil Weaverc2e28932016-12-08 12:29:25 -08001009 adjustIsVisibleToUserIfNeeded(info, interactiveRegion);
Rhed Jao882145e2019-07-04 19:56:18 +08001010 applyAppScaleAndMagnificationSpecIfNeeded(info, spec);
Phil Weaverc2e28932016-12-08 12:29:25 -08001011 callback.setFindAccessibilityNodeInfoResult(info, interactionId);
1012 } catch (RemoteException re) {
1013 /* ignore - the other side will time out */
1014 } finally {
1015 recycleMagnificationSpecAndRegionIfNeeded(spec, interactiveRegion);
1016 }
1017 }
1018
1019 private void recycleMagnificationSpecAndRegionIfNeeded(MagnificationSpec spec, Region region) {
1020 if (android.os.Process.myPid() != Binder.getCallingPid()) {
1021 // Specs are cached in the system process and obtained from a pool when read from
1022 // a parcel, so only recycle the spec if called from another process.
1023 if (spec != null) {
1024 spec.recycle();
1025 }
1026 } else {
1027 // Regions are obtained in the system process and instantiated when read from
1028 // a parcel, so only recycle the region if caled from the same process.
1029 if (region != null) {
1030 region.recycle();
1031 }
1032 }
1033 }
1034
Phil Weaver193520e2016-12-13 09:39:06 -08001035 private boolean handleClickableSpanActionUiThread(
1036 View view, int virtualDescendantId, Bundle arguments) {
1037 Parcelable span = arguments.getParcelable(ACTION_ARGUMENT_ACCESSIBLE_CLICKABLE_SPAN);
1038 if (!(span instanceof AccessibilityClickableSpan)) {
1039 return false;
1040 }
1041
1042 // Find the original ClickableSpan if it's still on the screen
1043 AccessibilityNodeInfo infoWithSpan = null;
1044 AccessibilityNodeProvider provider = view.getAccessibilityNodeProvider();
1045 if (provider != null) {
Phil Weaverf00cd142017-03-03 13:44:00 -08001046 infoWithSpan = provider.createAccessibilityNodeInfo(virtualDescendantId);
1047 } else if (virtualDescendantId == AccessibilityNodeProvider.HOST_VIEW_ID) {
Phil Weaver193520e2016-12-13 09:39:06 -08001048 infoWithSpan = view.createAccessibilityNodeInfo();
1049 }
1050 if (infoWithSpan == null) {
1051 return false;
1052 }
1053
1054 // Click on the corresponding span
1055 ClickableSpan clickableSpan = ((AccessibilityClickableSpan) span).findClickableSpan(
1056 infoWithSpan.getOriginalText());
1057 if (clickableSpan != null) {
1058 clickableSpan.onClick(view);
1059 return true;
1060 }
1061 return false;
1062 }
1063
Svetoslav Ganov42138042012-03-20 11:51:39 -07001064 /**
Svetoslav Ganov42138042012-03-20 11:51:39 -07001065 * This class encapsulates a prefetching strategy for the accessibility APIs for
1066 * querying window content. It is responsible to prefetch a batch of
1067 * AccessibilityNodeInfos in addition to the one for a requested node.
1068 */
1069 private class AccessibilityNodePrefetcher {
1070
1071 private static final int MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE = 50;
1072
Svetoslav Ganov4528b4e2012-05-15 18:24:10 -07001073 private final ArrayList<View> mTempViewList = new ArrayList<View>();
1074
Svetoslav Ganov80943d82013-01-02 10:25:37 -08001075 public void prefetchAccessibilityNodeInfos(View view, int virtualViewId, int fetchFlags,
Phil Weaverc2e28932016-12-08 12:29:25 -08001076 List<AccessibilityNodeInfo> outInfos, Bundle arguments) {
Svetoslav Ganov42138042012-03-20 11:51:39 -07001077 AccessibilityNodeProvider provider = view.getAccessibilityNodeProvider();
Phil Weaverc2e28932016-12-08 12:29:25 -08001078 // Determine if we'll be populating extra data
1079 final String extraDataRequested = (arguments == null) ? null
Phil Weaver08cccc12017-02-28 09:55:31 -08001080 : arguments.getString(EXTRA_DATA_REQUESTED_KEY);
Svetoslav Ganov42138042012-03-20 11:51:39 -07001081 if (provider == null) {
1082 AccessibilityNodeInfo root = view.createAccessibilityNodeInfo();
1083 if (root != null) {
Phil Weaverc2e28932016-12-08 12:29:25 -08001084 if (extraDataRequested != null) {
1085 view.addExtraDataToAccessibilityNodeInfo(
1086 root, extraDataRequested, arguments);
1087 }
Svetoslav Ganov42138042012-03-20 11:51:39 -07001088 outInfos.add(root);
Svetoslav Ganov80943d82013-01-02 10:25:37 -08001089 if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) != 0) {
Svetoslav Ganov42138042012-03-20 11:51:39 -07001090 prefetchPredecessorsOfRealNode(view, outInfos);
1091 }
Svetoslav Ganov80943d82013-01-02 10:25:37 -08001092 if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS) != 0) {
Svetoslav Ganov42138042012-03-20 11:51:39 -07001093 prefetchSiblingsOfRealNode(view, outInfos);
1094 }
Svetoslav Ganov80943d82013-01-02 10:25:37 -08001095 if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS) != 0) {
Svetoslav Ganov42138042012-03-20 11:51:39 -07001096 prefetchDescendantsOfRealNode(view, outInfos);
1097 }
1098 }
1099 } else {
Phil Weaverf00cd142017-03-03 13:44:00 -08001100 final AccessibilityNodeInfo root =
1101 provider.createAccessibilityNodeInfo(virtualViewId);
Svetoslav Ganov42138042012-03-20 11:51:39 -07001102 if (root != null) {
Phil Weaverc2e28932016-12-08 12:29:25 -08001103 if (extraDataRequested != null) {
1104 provider.addExtraDataToAccessibilityNodeInfo(
Phil Weaverf00cd142017-03-03 13:44:00 -08001105 virtualViewId, root, extraDataRequested, arguments);
Phil Weaverc2e28932016-12-08 12:29:25 -08001106 }
Svetoslav Ganov42138042012-03-20 11:51:39 -07001107 outInfos.add(root);
Svetoslav Ganov80943d82013-01-02 10:25:37 -08001108 if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) != 0) {
Svetoslav Ganov42138042012-03-20 11:51:39 -07001109 prefetchPredecessorsOfVirtualNode(root, view, provider, outInfos);
1110 }
Svetoslav Ganov80943d82013-01-02 10:25:37 -08001111 if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS) != 0) {
Svetoslav Ganov42138042012-03-20 11:51:39 -07001112 prefetchSiblingsOfVirtualNode(root, view, provider, outInfos);
1113 }
Svetoslav Ganov80943d82013-01-02 10:25:37 -08001114 if ((fetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS) != 0) {
Svetoslav Ganov42138042012-03-20 11:51:39 -07001115 prefetchDescendantsOfVirtualNode(root, provider, outInfos);
1116 }
1117 }
1118 }
Svetoslav8e3feb12014-02-24 13:46:47 -08001119 if (ENFORCE_NODE_TREE_CONSISTENT) {
1120 enforceNodeTreeConsistent(outInfos);
1121 }
1122 }
1123
1124 private void enforceNodeTreeConsistent(List<AccessibilityNodeInfo> nodes) {
1125 LongSparseArray<AccessibilityNodeInfo> nodeMap =
1126 new LongSparseArray<AccessibilityNodeInfo>();
1127 final int nodeCount = nodes.size();
1128 for (int i = 0; i < nodeCount; i++) {
1129 AccessibilityNodeInfo node = nodes.get(i);
1130 nodeMap.put(node.getSourceNodeId(), node);
1131 }
1132
1133 // If the nodes are a tree it does not matter from
1134 // which node we start to search for the root.
1135 AccessibilityNodeInfo root = nodeMap.valueAt(0);
1136 AccessibilityNodeInfo parent = root;
1137 while (parent != null) {
1138 root = parent;
1139 parent = nodeMap.get(parent.getParentNodeId());
1140 }
1141
1142 // Traverse the tree and do some checks.
1143 AccessibilityNodeInfo accessFocus = null;
1144 AccessibilityNodeInfo inputFocus = null;
1145 HashSet<AccessibilityNodeInfo> seen = new HashSet<AccessibilityNodeInfo>();
1146 Queue<AccessibilityNodeInfo> fringe = new LinkedList<AccessibilityNodeInfo>();
1147 fringe.add(root);
1148
1149 while (!fringe.isEmpty()) {
1150 AccessibilityNodeInfo current = fringe.poll();
1151
1152 // Check for duplicates
1153 if (!seen.add(current)) {
1154 throw new IllegalStateException("Duplicate node: "
1155 + current + " in window:"
1156 + mViewRootImpl.mAttachInfo.mAccessibilityWindowId);
1157 }
1158
1159 // Check for one accessibility focus.
1160 if (current.isAccessibilityFocused()) {
1161 if (accessFocus != null) {
1162 throw new IllegalStateException("Duplicate accessibility focus:"
1163 + current
1164 + " in window:" + mViewRootImpl.mAttachInfo.mAccessibilityWindowId);
1165 } else {
1166 accessFocus = current;
1167 }
1168 }
1169
1170 // Check for one input focus.
1171 if (current.isFocused()) {
1172 if (inputFocus != null) {
1173 throw new IllegalStateException("Duplicate input focus: "
1174 + current + " in window:"
1175 + mViewRootImpl.mAttachInfo.mAccessibilityWindowId);
1176 } else {
1177 inputFocus = current;
1178 }
1179 }
1180
1181 final int childCount = current.getChildCount();
1182 for (int j = 0; j < childCount; j++) {
1183 final long childId = current.getChildId(j);
1184 final AccessibilityNodeInfo child = nodeMap.get(childId);
1185 if (child != null) {
1186 fringe.add(child);
1187 }
1188 }
1189 }
1190
1191 // Check for disconnected nodes.
1192 for (int j = nodeMap.size() - 1; j >= 0; j--) {
1193 AccessibilityNodeInfo info = nodeMap.valueAt(j);
1194 if (!seen.contains(info)) {
1195 throw new IllegalStateException("Disconnected node: " + info);
1196 }
1197 }
Svetoslav Ganov42138042012-03-20 11:51:39 -07001198 }
1199
1200 private void prefetchPredecessorsOfRealNode(View view,
1201 List<AccessibilityNodeInfo> outInfos) {
1202 ViewParent parent = view.getParentForAccessibility();
1203 while (parent instanceof View
1204 && outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
1205 View parentView = (View) parent;
Svetoslav Ganov42138042012-03-20 11:51:39 -07001206 AccessibilityNodeInfo info = parentView.createAccessibilityNodeInfo();
1207 if (info != null) {
1208 outInfos.add(info);
1209 }
1210 parent = parent.getParentForAccessibility();
1211 }
1212 }
1213
1214 private void prefetchSiblingsOfRealNode(View current,
1215 List<AccessibilityNodeInfo> outInfos) {
1216 ViewParent parent = current.getParentForAccessibility();
1217 if (parent instanceof ViewGroup) {
1218 ViewGroup parentGroup = (ViewGroup) parent;
Svetoslav Ganov4528b4e2012-05-15 18:24:10 -07001219 ArrayList<View> children = mTempViewList;
1220 children.clear();
Svetoslav Ganov76f287e2012-04-23 11:02:36 -07001221 try {
Svetoslav Ganov4528b4e2012-05-15 18:24:10 -07001222 parentGroup.addChildrenForAccessibility(children);
1223 final int childCount = children.size();
Svetoslav Ganov76f287e2012-04-23 11:02:36 -07001224 for (int i = 0; i < childCount; i++) {
1225 if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
1226 return;
Svetoslav Ganov42138042012-03-20 11:51:39 -07001227 }
Svetoslav Ganov4528b4e2012-05-15 18:24:10 -07001228 View child = children.get(i);
Svetoslav Ganov76f287e2012-04-23 11:02:36 -07001229 if (child.getAccessibilityViewId() != current.getAccessibilityViewId()
Qasid Ahmad Sadiq834787a2019-01-18 00:01:54 -08001230 && isShown(child)) {
Svetoslav Ganov76f287e2012-04-23 11:02:36 -07001231 AccessibilityNodeInfo info = null;
Svetoslav Ganov4528b4e2012-05-15 18:24:10 -07001232 AccessibilityNodeProvider provider =
1233 child.getAccessibilityNodeProvider();
Svetoslav Ganov76f287e2012-04-23 11:02:36 -07001234 if (provider == null) {
1235 info = child.createAccessibilityNodeInfo();
1236 } else {
1237 info = provider.createAccessibilityNodeInfo(
Svetoslav8e3feb12014-02-24 13:46:47 -08001238 AccessibilityNodeProvider.HOST_VIEW_ID);
Svetoslav Ganov76f287e2012-04-23 11:02:36 -07001239 }
1240 if (info != null) {
1241 outInfos.add(info);
1242 }
Svetoslav Ganov42138042012-03-20 11:51:39 -07001243 }
1244 }
Svetoslav Ganov76f287e2012-04-23 11:02:36 -07001245 } finally {
Svetoslav Ganov4528b4e2012-05-15 18:24:10 -07001246 children.clear();
Svetoslav Ganov42138042012-03-20 11:51:39 -07001247 }
Svetoslav Ganov42138042012-03-20 11:51:39 -07001248 }
1249 }
1250
1251 private void prefetchDescendantsOfRealNode(View root,
1252 List<AccessibilityNodeInfo> outInfos) {
1253 if (!(root instanceof ViewGroup)) {
1254 return;
1255 }
Svetoslav Ganov42138042012-03-20 11:51:39 -07001256 HashMap<View, AccessibilityNodeInfo> addedChildren =
1257 new HashMap<View, AccessibilityNodeInfo>();
Svetoslav Ganov4528b4e2012-05-15 18:24:10 -07001258 ArrayList<View> children = mTempViewList;
1259 children.clear();
Svetoslav Ganov76f287e2012-04-23 11:02:36 -07001260 try {
Svetoslav Ganov4528b4e2012-05-15 18:24:10 -07001261 root.addChildrenForAccessibility(children);
1262 final int childCount = children.size();
Svetoslav Ganov76f287e2012-04-23 11:02:36 -07001263 for (int i = 0; i < childCount; i++) {
1264 if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
1265 return;
1266 }
Svetoslav Ganov4528b4e2012-05-15 18:24:10 -07001267 View child = children.get(i);
Svetoslav Ganov0a1bb6d2012-05-07 11:54:39 -07001268 if (isShown(child)) {
Svetoslav Ganov76f287e2012-04-23 11:02:36 -07001269 AccessibilityNodeProvider provider = child.getAccessibilityNodeProvider();
1270 if (provider == null) {
1271 AccessibilityNodeInfo info = child.createAccessibilityNodeInfo();
1272 if (info != null) {
1273 outInfos.add(info);
1274 addedChildren.put(child, null);
1275 }
1276 } else {
1277 AccessibilityNodeInfo info = provider.createAccessibilityNodeInfo(
Svetoslav8e3feb12014-02-24 13:46:47 -08001278 AccessibilityNodeProvider.HOST_VIEW_ID);
Svetoslav Ganov76f287e2012-04-23 11:02:36 -07001279 if (info != null) {
1280 outInfos.add(info);
1281 addedChildren.put(child, info);
1282 }
Svetoslav Ganov42138042012-03-20 11:51:39 -07001283 }
1284 }
1285 }
Svetoslav Ganov76f287e2012-04-23 11:02:36 -07001286 } finally {
Svetoslav Ganov4528b4e2012-05-15 18:24:10 -07001287 children.clear();
Svetoslav Ganov42138042012-03-20 11:51:39 -07001288 }
Svetoslav Ganov42138042012-03-20 11:51:39 -07001289 if (outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
1290 for (Map.Entry<View, AccessibilityNodeInfo> entry : addedChildren.entrySet()) {
1291 View addedChild = entry.getKey();
1292 AccessibilityNodeInfo virtualRoot = entry.getValue();
1293 if (virtualRoot == null) {
1294 prefetchDescendantsOfRealNode(addedChild, outInfos);
1295 } else {
1296 AccessibilityNodeProvider provider =
1297 addedChild.getAccessibilityNodeProvider();
1298 prefetchDescendantsOfVirtualNode(virtualRoot, provider, outInfos);
1299 }
1300 }
1301 }
1302 }
1303
1304 private void prefetchPredecessorsOfVirtualNode(AccessibilityNodeInfo root,
1305 View providerHost, AccessibilityNodeProvider provider,
1306 List<AccessibilityNodeInfo> outInfos) {
Svet Ganov75e58162016-02-19 16:29:24 -08001307 final int initialResultSize = outInfos.size();
Svetoslav Ganov42138042012-03-20 11:51:39 -07001308 long parentNodeId = root.getParentNodeId();
1309 int accessibilityViewId = AccessibilityNodeInfo.getAccessibilityViewId(parentNodeId);
Svetoslav8e3feb12014-02-24 13:46:47 -08001310 while (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) {
Svetoslav Ganov42138042012-03-20 11:51:39 -07001311 if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
1312 return;
1313 }
1314 final int virtualDescendantId =
1315 AccessibilityNodeInfo.getVirtualDescendantId(parentNodeId);
Phil Weaverf00cd142017-03-03 13:44:00 -08001316 if (virtualDescendantId != AccessibilityNodeProvider.HOST_VIEW_ID
Svetoslav Ganov42138042012-03-20 11:51:39 -07001317 || accessibilityViewId == providerHost.getAccessibilityViewId()) {
Svetoslav8e3feb12014-02-24 13:46:47 -08001318 final AccessibilityNodeInfo parent;
Phil Weaverf00cd142017-03-03 13:44:00 -08001319 parent = provider.createAccessibilityNodeInfo(virtualDescendantId);
Alan Viverette84feea112014-11-04 15:59:55 -08001320 if (parent == null) {
Svet Ganov75e58162016-02-19 16:29:24 -08001321 // Going up the parent relation we found a null predecessor,
1322 // so remove these disconnected nodes form the result.
1323 final int currentResultSize = outInfos.size();
1324 for (int i = currentResultSize - 1; i >= initialResultSize; i--) {
1325 outInfos.remove(i);
1326 }
Alan Viverette84feea112014-11-04 15:59:55 -08001327 // Couldn't obtain the parent, which means we have a
1328 // disconnected sub-tree. Abort prefetch immediately.
1329 return;
Svetoslav Ganov42138042012-03-20 11:51:39 -07001330 }
Alan Viverette84feea112014-11-04 15:59:55 -08001331 outInfos.add(parent);
Svetoslav Ganov42138042012-03-20 11:51:39 -07001332 parentNodeId = parent.getParentNodeId();
1333 accessibilityViewId = AccessibilityNodeInfo.getAccessibilityViewId(
1334 parentNodeId);
1335 } else {
1336 prefetchPredecessorsOfRealNode(providerHost, outInfos);
1337 return;
1338 }
1339 }
1340 }
1341
1342 private void prefetchSiblingsOfVirtualNode(AccessibilityNodeInfo current, View providerHost,
1343 AccessibilityNodeProvider provider, List<AccessibilityNodeInfo> outInfos) {
1344 final long parentNodeId = current.getParentNodeId();
1345 final int parentAccessibilityViewId =
1346 AccessibilityNodeInfo.getAccessibilityViewId(parentNodeId);
1347 final int parentVirtualDescendantId =
1348 AccessibilityNodeInfo.getVirtualDescendantId(parentNodeId);
Phil Weaverf00cd142017-03-03 13:44:00 -08001349 if (parentVirtualDescendantId != AccessibilityNodeProvider.HOST_VIEW_ID
Svetoslav Ganov42138042012-03-20 11:51:39 -07001350 || parentAccessibilityViewId == providerHost.getAccessibilityViewId()) {
Phil Weaverf00cd142017-03-03 13:44:00 -08001351 final AccessibilityNodeInfo parent =
1352 provider.createAccessibilityNodeInfo(parentVirtualDescendantId);
Svetoslav Ganov42138042012-03-20 11:51:39 -07001353 if (parent != null) {
Alan Viverettef0aed092013-11-06 15:33:03 -08001354 final int childCount = parent.getChildCount();
Svetoslav Ganov42138042012-03-20 11:51:39 -07001355 for (int i = 0; i < childCount; i++) {
1356 if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
1357 return;
1358 }
Alan Viverettef0aed092013-11-06 15:33:03 -08001359 final long childNodeId = parent.getChildId(i);
Svetoslav Ganov42138042012-03-20 11:51:39 -07001360 if (childNodeId != current.getSourceNodeId()) {
1361 final int childVirtualDescendantId =
1362 AccessibilityNodeInfo.getVirtualDescendantId(childNodeId);
1363 AccessibilityNodeInfo child = provider.createAccessibilityNodeInfo(
1364 childVirtualDescendantId);
1365 if (child != null) {
1366 outInfos.add(child);
1367 }
1368 }
1369 }
1370 }
1371 } else {
1372 prefetchSiblingsOfRealNode(providerHost, outInfos);
1373 }
1374 }
1375
1376 private void prefetchDescendantsOfVirtualNode(AccessibilityNodeInfo root,
1377 AccessibilityNodeProvider provider, List<AccessibilityNodeInfo> outInfos) {
Svetoslav Ganov42138042012-03-20 11:51:39 -07001378 final int initialOutInfosSize = outInfos.size();
Alan Viverettef0aed092013-11-06 15:33:03 -08001379 final int childCount = root.getChildCount();
Svetoslav Ganov42138042012-03-20 11:51:39 -07001380 for (int i = 0; i < childCount; i++) {
1381 if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
1382 return;
1383 }
Alan Viverettef0aed092013-11-06 15:33:03 -08001384 final long childNodeId = root.getChildId(i);
Svetoslav Ganov42138042012-03-20 11:51:39 -07001385 AccessibilityNodeInfo child = provider.createAccessibilityNodeInfo(
1386 AccessibilityNodeInfo.getVirtualDescendantId(childNodeId));
1387 if (child != null) {
1388 outInfos.add(child);
1389 }
1390 }
1391 if (outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
1392 final int addedChildCount = outInfos.size() - initialOutInfosSize;
1393 for (int i = 0; i < addedChildCount; i++) {
1394 AccessibilityNodeInfo child = outInfos.get(initialOutInfosSize + i);
1395 prefetchDescendantsOfVirtualNode(child, provider, outInfos);
1396 }
1397 }
1398 }
1399 }
1400
1401 private class PrivateHandler extends Handler {
Phil Weaverc2e28932016-12-08 12:29:25 -08001402 private static final int MSG_PERFORM_ACCESSIBILITY_ACTION = 1;
1403 private static final int MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID = 2;
1404 private static final int MSG_FIND_ACCESSIBILITY_NODE_INFOS_BY_VIEW_ID = 3;
1405 private static final int MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_TEXT = 4;
1406 private static final int MSG_FIND_FOCUS = 5;
1407 private static final int MSG_FOCUS_SEARCH = 6;
Phil Weaver08cccc12017-02-28 09:55:31 -08001408 private static final int MSG_PREPARE_FOR_EXTRA_DATA_REQUEST = 7;
1409 private static final int MSG_APP_PREPARATION_FINISHED = 8;
1410 private static final int MSG_APP_PREPARATION_TIMEOUT = 9;
Svetoslav Ganov42138042012-03-20 11:51:39 -07001411
Rhed Jao23813d92018-12-18 21:30:52 +08001412 // Uses FIRST_NO_ACCESSIBILITY_CALLBACK_MSG for messages that don't need to call back
1413 // results to interrogating client.
1414 private static final int FIRST_NO_ACCESSIBILITY_CALLBACK_MSG = 100;
1415 private static final int MSG_CLEAR_ACCESSIBILITY_FOCUS =
1416 FIRST_NO_ACCESSIBILITY_CALLBACK_MSG + 1;
Jackal Guo8bcc0a92019-04-16 15:26:00 +08001417 private static final int MSG_NOTIFY_OUTSIDE_TOUCH =
1418 FIRST_NO_ACCESSIBILITY_CALLBACK_MSG + 2;
Rhed Jao23813d92018-12-18 21:30:52 +08001419
Svetoslav Ganov005b83b2012-04-16 18:17:17 -07001420 public PrivateHandler(Looper looper) {
1421 super(looper);
Svetoslav Ganov42138042012-03-20 11:51:39 -07001422 }
1423
1424 @Override
1425 public String getMessageName(Message message) {
1426 final int type = message.what;
1427 switch (type) {
1428 case MSG_PERFORM_ACCESSIBILITY_ACTION:
1429 return "MSG_PERFORM_ACCESSIBILITY_ACTION";
Svet Ganov7498efd2014-09-03 21:33:00 -07001430 case MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID:
1431 return "MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID";
1432 case MSG_FIND_ACCESSIBILITY_NODE_INFOS_BY_VIEW_ID:
1433 return "MSG_FIND_ACCESSIBILITY_NODE_INFOS_BY_VIEW_ID";
1434 case MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_TEXT:
1435 return "MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_TEXT";
Svetoslav Ganov42138042012-03-20 11:51:39 -07001436 case MSG_FIND_FOCUS:
1437 return "MSG_FIND_FOCUS";
1438 case MSG_FOCUS_SEARCH:
1439 return "MSG_FOCUS_SEARCH";
Phil Weaver08cccc12017-02-28 09:55:31 -08001440 case MSG_PREPARE_FOR_EXTRA_DATA_REQUEST:
1441 return "MSG_PREPARE_FOR_EXTRA_DATA_REQUEST";
1442 case MSG_APP_PREPARATION_FINISHED:
1443 return "MSG_APP_PREPARATION_FINISHED";
1444 case MSG_APP_PREPARATION_TIMEOUT:
1445 return "MSG_APP_PREPARATION_TIMEOUT";
Rhed Jao23813d92018-12-18 21:30:52 +08001446 case MSG_CLEAR_ACCESSIBILITY_FOCUS:
1447 return "MSG_CLEAR_ACCESSIBILITY_FOCUS";
Jackal Guo8bcc0a92019-04-16 15:26:00 +08001448 case MSG_NOTIFY_OUTSIDE_TOUCH:
1449 return "MSG_NOTIFY_OUTSIDE_TOUCH";
Svetoslav Ganov42138042012-03-20 11:51:39 -07001450 default:
1451 throw new IllegalArgumentException("Unknown message type: " + type);
1452 }
1453 }
1454
1455 @Override
1456 public void handleMessage(Message message) {
1457 final int type = message.what;
1458 switch (type) {
Svet Ganov7498efd2014-09-03 21:33:00 -07001459 case MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID: {
Svetoslav Ganov42138042012-03-20 11:51:39 -07001460 findAccessibilityNodeInfoByAccessibilityIdUiThread(message);
1461 } break;
1462 case MSG_PERFORM_ACCESSIBILITY_ACTION: {
George Mount41725de2015-04-09 08:23:05 -07001463 performAccessibilityActionUiThread(message);
Svetoslav Ganov42138042012-03-20 11:51:39 -07001464 } break;
Svet Ganov7498efd2014-09-03 21:33:00 -07001465 case MSG_FIND_ACCESSIBILITY_NODE_INFOS_BY_VIEW_ID: {
Svetoslav Ganov80943d82013-01-02 10:25:37 -08001466 findAccessibilityNodeInfosByViewIdUiThread(message);
Svetoslav Ganov42138042012-03-20 11:51:39 -07001467 } break;
Svet Ganov7498efd2014-09-03 21:33:00 -07001468 case MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_TEXT: {
Svetoslav Ganov42138042012-03-20 11:51:39 -07001469 findAccessibilityNodeInfosByTextUiThread(message);
1470 } break;
1471 case MSG_FIND_FOCUS: {
1472 findFocusUiThread(message);
1473 } break;
1474 case MSG_FOCUS_SEARCH: {
1475 focusSearchUiThread(message);
1476 } break;
Phil Weaver08cccc12017-02-28 09:55:31 -08001477 case MSG_PREPARE_FOR_EXTRA_DATA_REQUEST: {
1478 prepareForExtraDataRequestUiThread(message);
1479 } break;
1480 case MSG_APP_PREPARATION_FINISHED: {
1481 requestPreparerDoneUiThread(message);
1482 } break;
1483 case MSG_APP_PREPARATION_TIMEOUT: {
1484 requestPreparerTimeoutUiThread();
1485 } break;
Rhed Jao23813d92018-12-18 21:30:52 +08001486 case MSG_CLEAR_ACCESSIBILITY_FOCUS: {
1487 clearAccessibilityFocusUiThread();
1488 } break;
Jackal Guo8bcc0a92019-04-16 15:26:00 +08001489 case MSG_NOTIFY_OUTSIDE_TOUCH: {
1490 notifyOutsideTouchUiThread();
1491 } break;
Svetoslav Ganov42138042012-03-20 11:51:39 -07001492 default:
1493 throw new IllegalArgumentException("Unknown message type: " + type);
1494 }
1495 }
Rhed Jao23813d92018-12-18 21:30:52 +08001496
1497 boolean hasAccessibilityCallback(Message message) {
1498 return message.what < FIRST_NO_ACCESSIBILITY_CALLBACK_MSG ? true : false;
1499 }
Svetoslav Ganov42138042012-03-20 11:51:39 -07001500 }
Svetoslav Ganov80943d82013-01-02 10:25:37 -08001501
1502 private final class AddNodeInfosForViewId implements Predicate<View> {
1503 private int mViewId = View.NO_ID;
1504 private List<AccessibilityNodeInfo> mInfos;
1505
1506 public void init(int viewId, List<AccessibilityNodeInfo> infos) {
1507 mViewId = viewId;
1508 mInfos = infos;
1509 }
1510
1511 public void reset() {
1512 mViewId = View.NO_ID;
1513 mInfos = null;
1514 }
1515
1516 @Override
Paul Duffinca4964c2017-02-07 15:04:10 +00001517 public boolean test(View view) {
Svetoslav Ganov80943d82013-01-02 10:25:37 -08001518 if (view.getId() == mViewId && isShown(view)) {
1519 mInfos.add(view.createAccessibilityNodeInfo());
1520 }
1521 return false;
1522 }
1523 }
Phil Weaver08cccc12017-02-28 09:55:31 -08001524
1525 private static final class MessageHolder {
1526 final Message mMessage;
1527 final int mInterrogatingPid;
1528 final long mInterrogatingTid;
1529
1530 MessageHolder(Message message, int interrogatingPid, long interrogatingTid) {
1531 mMessage = message;
1532 mInterrogatingPid = interrogatingPid;
1533 mInterrogatingTid = interrogatingTid;
1534 }
1535 }
Svetoslav Ganov42138042012-03-20 11:51:39 -07001536}