18bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov/* 28bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov ** Copyright 2011, The Android Open Source Project 38bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov ** 48bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov ** Licensed under the Apache License, Version 2.0 (the "License"); 58bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov ** you may not use this file except in compliance with the License. 68bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov ** You may obtain a copy of the License at 78bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov ** 88bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov ** http://www.apache.org/licenses/LICENSE-2.0 98bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov ** 108bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov ** Unless required by applicable law or agreed to in writing, software 118bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov ** distributed under the License is distributed on an "AS IS" BASIS, 128bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 138bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov ** See the License for the specific language governing permissions and 148bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov ** limitations under the License. 158bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov */ 168bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov 178bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganovpackage android.view.accessibility; 188bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov 198b5a814f6a36045b06bee36f44703503c03714d4Svetoslav Ganovimport android.accessibilityservice.IAccessibilityServiceConnection; 204213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganovimport android.os.Binder; 214528b4e882584745f48263fa6626987e63832a2aSvetoslav Ganovimport android.os.Build; 22aa780c110922148a6a4ba06734bb2b0bb8c98f93Svetoslav Ganovimport android.os.Bundle; 238bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganovimport android.os.Message; 244213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganovimport android.os.Process; 258bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganovimport android.os.RemoteException; 268bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganovimport android.os.SystemClock; 27d116d7c78a9c53f30a73bf273bd7618312cf3847Svetoslav Ganovimport android.util.Log; 288b5a814f6a36045b06bee36f44703503c03714d4Svetoslav Ganovimport android.util.LongSparseArray; 29d116d7c78a9c53f30a73bf273bd7618312cf3847Svetoslav Ganovimport android.util.SparseArray; 308bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov 3179311c4af8b54d3cd47ab37a120c648bfc990511Svetoslav Ganovimport java.util.ArrayList; 328bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganovimport java.util.Collections; 334528b4e882584745f48263fa6626987e63832a2aSvetoslav Ganovimport java.util.HashSet; 344528b4e882584745f48263fa6626987e63832a2aSvetoslav Ganovimport java.util.LinkedList; 358bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganovimport java.util.List; 364528b4e882584745f48263fa6626987e63832a2aSvetoslav Ganovimport java.util.Queue; 378bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganovimport java.util.concurrent.atomic.AtomicInteger; 388bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov 398bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov/** 408bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov * This class is a singleton that performs accessibility interaction 418bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov * which is it queries remote view hierarchies about snapshots of their 428bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov * views as well requests from these hierarchies to perform certain 438bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov * actions on their views. 448bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov * 458bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov * Rationale: The content retrieval APIs are synchronous from a client's 468bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov * perspective but internally they are asynchronous. The client thread 478bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov * calls into the system requesting an action and providing a callback 488bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov * to receive the result after which it waits up to a timeout for that 498bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov * result. The system enforces security and the delegates the request 508bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov * to a given view hierarchy where a message is posted (from a binder 518bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov * thread) describing what to be performed by the main UI thread the 528bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov * result of which it delivered via the mentioned callback. However, 538bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov * the blocked client thread and the main UI thread of the target view 548bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov * hierarchy can be the same thread, for example an accessibility service 558bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov * and an activity run in the same process, thus they are executed on the 568bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov * same main thread. In such a case the retrieval will fail since the UI 578bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov * thread that has to process the message describing the work to be done 588bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov * is blocked waiting for a result is has to compute! To avoid this scenario 598bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov * when making a call the client also passes its process and thread ids so 608bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov * the accessed view hierarchy can detect if the client making the request 618bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov * is running in its main UI thread. In such a case the view hierarchy, 628bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov * specifically the binder thread performing the IPC to it, does not post a 638bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov * message to be run on the UI thread but passes it to the singleton 648bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov * interaction client through which all interactions occur and the latter is 658bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov * responsible to execute the message before starting to wait for the 668bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov * asynchronous result delivered via the callback. In this case the expected 678bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov * result is already received so no waiting is performed. 688bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov * 698bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov * @hide 708bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov */ 718bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganovpublic final class AccessibilityInteractionClient 728bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov extends IAccessibilityInteractionConnectionCallback.Stub { 738bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov 74d116d7c78a9c53f30a73bf273bd7618312cf3847Svetoslav Ganov public static final int NO_ID = -1; 75d116d7c78a9c53f30a73bf273bd7618312cf3847Svetoslav Ganov 76d116d7c78a9c53f30a73bf273bd7618312cf3847Svetoslav Ganov private static final String LOG_TAG = "AccessibilityInteractionClient"; 77d116d7c78a9c53f30a73bf273bd7618312cf3847Svetoslav Ganov 78d116d7c78a9c53f30a73bf273bd7618312cf3847Svetoslav Ganov private static final boolean DEBUG = false; 79d116d7c78a9c53f30a73bf273bd7618312cf3847Svetoslav Ganov 804528b4e882584745f48263fa6626987e63832a2aSvetoslav Ganov private static final boolean CHECK_INTEGRITY = true; 814528b4e882584745f48263fa6626987e63832a2aSvetoslav Ganov 828bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov private static final long TIMEOUT_INTERACTION_MILLIS = 5000; 838bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov 848bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov private static final Object sStaticLock = new Object(); 858bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov 86021078554b902179442a345a9d080a165c3b5139Svetoslav Ganov private static final LongSparseArray<AccessibilityInteractionClient> sClients = 87a4725efd0bfa52cbddf6ca587d37fc4ebcbfaf72Svetoslav new LongSparseArray<>(); 888bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov 898bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov private final AtomicInteger mInteractionIdCounter = new AtomicInteger(); 908bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov 918bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov private final Object mInstanceLock = new Object(); 928bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov 93fefd20e927b7252d63acb7bb1852c5188e3c1b2eSvetoslav Ganov private volatile int mInteractionId = -1; 948bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov 958bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov private AccessibilityNodeInfo mFindAccessibilityNodeInfoResult; 968bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov 978bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov private List<AccessibilityNodeInfo> mFindAccessibilityNodeInfosResult; 988bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov 998bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov private boolean mPerformAccessibilityActionResult; 1008bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov 1018bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov private Message mSameThreadMessage; 1028bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov 10336bcdb535e14a8a2e2c8643fb577569f7a2b6aedSvetoslav Ganov private static final SparseArray<IAccessibilityServiceConnection> sConnectionCache = 104a4725efd0bfa52cbddf6ca587d37fc4ebcbfaf72Svetoslav new SparseArray<>(); 105d116d7c78a9c53f30a73bf273bd7618312cf3847Svetoslav Ganov 1068e3feb15c5aec2c72b0ef120a1da325e1e8f0ddaSvetoslav private static final AccessibilityCache sAccessibilityCache = 107b010b1270fc524befdb9ebc3b3687a37ef1b71b9Phil Weaver new AccessibilityCache(new AccessibilityCache.AccessibilityNodeRefresher()); 10879311c4af8b54d3cd47ab37a120c648bfc990511Svetoslav Ganov 1098bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov /** 110021078554b902179442a345a9d080a165c3b5139Svetoslav Ganov * @return The client for the current thread. 1118bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov */ 1128bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov public static AccessibilityInteractionClient getInstance() { 113021078554b902179442a345a9d080a165c3b5139Svetoslav Ganov final long threadId = Thread.currentThread().getId(); 114021078554b902179442a345a9d080a165c3b5139Svetoslav Ganov return getInstanceForThread(threadId); 115021078554b902179442a345a9d080a165c3b5139Svetoslav Ganov } 116021078554b902179442a345a9d080a165c3b5139Svetoslav Ganov 117021078554b902179442a345a9d080a165c3b5139Svetoslav Ganov /** 118021078554b902179442a345a9d080a165c3b5139Svetoslav Ganov * <strong>Note:</strong> We keep one instance per interrogating thread since 119021078554b902179442a345a9d080a165c3b5139Svetoslav Ganov * the instance contains state which can lead to undesired thread interleavings. 120021078554b902179442a345a9d080a165c3b5139Svetoslav Ganov * We do not have a thread local variable since other threads should be able to 121021078554b902179442a345a9d080a165c3b5139Svetoslav Ganov * look up the correct client knowing a thread id. See ViewRootImpl for details. 122021078554b902179442a345a9d080a165c3b5139Svetoslav Ganov * 123021078554b902179442a345a9d080a165c3b5139Svetoslav Ganov * @return The client for a given <code>threadId</code>. 124021078554b902179442a345a9d080a165c3b5139Svetoslav Ganov */ 125021078554b902179442a345a9d080a165c3b5139Svetoslav Ganov public static AccessibilityInteractionClient getInstanceForThread(long threadId) { 1268bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov synchronized (sStaticLock) { 127021078554b902179442a345a9d080a165c3b5139Svetoslav Ganov AccessibilityInteractionClient client = sClients.get(threadId); 128021078554b902179442a345a9d080a165c3b5139Svetoslav Ganov if (client == null) { 129021078554b902179442a345a9d080a165c3b5139Svetoslav Ganov client = new AccessibilityInteractionClient(); 130021078554b902179442a345a9d080a165c3b5139Svetoslav Ganov sClients.put(threadId, client); 1318bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov } 132021078554b902179442a345a9d080a165c3b5139Svetoslav Ganov return client; 1338bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov } 1348bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov } 1358bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov 136021078554b902179442a345a9d080a165c3b5139Svetoslav Ganov private AccessibilityInteractionClient() { 137021078554b902179442a345a9d080a165c3b5139Svetoslav Ganov /* reducing constructor visibility */ 138021078554b902179442a345a9d080a165c3b5139Svetoslav Ganov } 139021078554b902179442a345a9d080a165c3b5139Svetoslav Ganov 1408bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov /** 1418bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov * Sets the message to be processed if the interacted view hierarchy 1428bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov * and the interacting client are running in the same thread. 1438bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov * 1448bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov * @param message The message. 1458bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov */ 1468bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov public void setSameThreadMessage(Message message) { 1478bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov synchronized (mInstanceLock) { 1488bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov mSameThreadMessage = message; 1496bc5e530016928027c7b390a8368ecdd5bff072fSvetoslav Ganov mInstanceLock.notifyAll(); 1508bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov } 1518bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov } 1528bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov 1538bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov /** 154fefd20e927b7252d63acb7bb1852c5188e3c1b2eSvetoslav Ganov * Gets the root {@link AccessibilityNodeInfo} in the currently active window. 155fefd20e927b7252d63acb7bb1852c5188e3c1b2eSvetoslav Ganov * 156fefd20e927b7252d63acb7bb1852c5188e3c1b2eSvetoslav Ganov * @param connectionId The id of a connection for interacting with the system. 157fefd20e927b7252d63acb7bb1852c5188e3c1b2eSvetoslav Ganov * @return The root {@link AccessibilityNodeInfo} if found, null otherwise. 158fefd20e927b7252d63acb7bb1852c5188e3c1b2eSvetoslav Ganov */ 159fefd20e927b7252d63acb7bb1852c5188e3c1b2eSvetoslav Ganov public AccessibilityNodeInfo getRootInActiveWindow(int connectionId) { 160fefd20e927b7252d63acb7bb1852c5188e3c1b2eSvetoslav Ganov return findAccessibilityNodeInfoByAccessibilityId(connectionId, 161f00cd14f17c0acd6bffe78947d32ea0a2900d139Phil Weaver AccessibilityWindowInfo.ACTIVE_WINDOW_ID, AccessibilityNodeInfo.ROOT_NODE_ID, 162c2e28932d22faece6e7179c78d4e7656dc63052cPhil Weaver false, AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS, null); 1631e0d4af9986c8c2a658769a63bf8b385d25e0435Svetoslav } 1641e0d4af9986c8c2a658769a63bf8b385d25e0435Svetoslav 1651e0d4af9986c8c2a658769a63bf8b385d25e0435Svetoslav /** 1668e3feb15c5aec2c72b0ef120a1da325e1e8f0ddaSvetoslav * Gets the info for a window. 1678e3feb15c5aec2c72b0ef120a1da325e1e8f0ddaSvetoslav * 1688e3feb15c5aec2c72b0ef120a1da325e1e8f0ddaSvetoslav * @param connectionId The id of a connection for interacting with the system. 1698e3feb15c5aec2c72b0ef120a1da325e1e8f0ddaSvetoslav * @param accessibilityWindowId A unique window id. Use 1708e3feb15c5aec2c72b0ef120a1da325e1e8f0ddaSvetoslav * {@link android.view.accessibility.AccessibilityNodeInfo#ACTIVE_WINDOW_ID} 1718e3feb15c5aec2c72b0ef120a1da325e1e8f0ddaSvetoslav * to query the currently active window. 1728e3feb15c5aec2c72b0ef120a1da325e1e8f0ddaSvetoslav * @return The {@link AccessibilityWindowInfo}. 1738e3feb15c5aec2c72b0ef120a1da325e1e8f0ddaSvetoslav */ 1748e3feb15c5aec2c72b0ef120a1da325e1e8f0ddaSvetoslav public AccessibilityWindowInfo getWindow(int connectionId, int accessibilityWindowId) { 1758e3feb15c5aec2c72b0ef120a1da325e1e8f0ddaSvetoslav try { 1768e3feb15c5aec2c72b0ef120a1da325e1e8f0ddaSvetoslav IAccessibilityServiceConnection connection = getConnection(connectionId); 1778e3feb15c5aec2c72b0ef120a1da325e1e8f0ddaSvetoslav if (connection != null) { 1788e3feb15c5aec2c72b0ef120a1da325e1e8f0ddaSvetoslav AccessibilityWindowInfo window = sAccessibilityCache.getWindow( 1798e3feb15c5aec2c72b0ef120a1da325e1e8f0ddaSvetoslav accessibilityWindowId); 1808e3feb15c5aec2c72b0ef120a1da325e1e8f0ddaSvetoslav if (window != null) { 1818e3feb15c5aec2c72b0ef120a1da325e1e8f0ddaSvetoslav if (DEBUG) { 1828e3feb15c5aec2c72b0ef120a1da325e1e8f0ddaSvetoslav Log.i(LOG_TAG, "Window cache hit"); 1838e3feb15c5aec2c72b0ef120a1da325e1e8f0ddaSvetoslav } 1848e3feb15c5aec2c72b0ef120a1da325e1e8f0ddaSvetoslav return window; 1858e3feb15c5aec2c72b0ef120a1da325e1e8f0ddaSvetoslav } 1868e3feb15c5aec2c72b0ef120a1da325e1e8f0ddaSvetoslav if (DEBUG) { 1878e3feb15c5aec2c72b0ef120a1da325e1e8f0ddaSvetoslav Log.i(LOG_TAG, "Window cache miss"); 1888e3feb15c5aec2c72b0ef120a1da325e1e8f0ddaSvetoslav } 18956bbeff99a628be8e1d805db2638a4b87fedb106Alan Viverette final long identityToken = Binder.clearCallingIdentity(); 1908e3feb15c5aec2c72b0ef120a1da325e1e8f0ddaSvetoslav window = connection.getWindow(accessibilityWindowId); 19156bbeff99a628be8e1d805db2638a4b87fedb106Alan Viverette Binder.restoreCallingIdentity(identityToken); 1928e3feb15c5aec2c72b0ef120a1da325e1e8f0ddaSvetoslav if (window != null) { 1938e3feb15c5aec2c72b0ef120a1da325e1e8f0ddaSvetoslav sAccessibilityCache.addWindow(window); 1948e3feb15c5aec2c72b0ef120a1da325e1e8f0ddaSvetoslav return window; 1958e3feb15c5aec2c72b0ef120a1da325e1e8f0ddaSvetoslav } 1968e3feb15c5aec2c72b0ef120a1da325e1e8f0ddaSvetoslav } else { 1978e3feb15c5aec2c72b0ef120a1da325e1e8f0ddaSvetoslav if (DEBUG) { 1988e3feb15c5aec2c72b0ef120a1da325e1e8f0ddaSvetoslav Log.w(LOG_TAG, "No connection for connection id: " + connectionId); 1998e3feb15c5aec2c72b0ef120a1da325e1e8f0ddaSvetoslav } 2008e3feb15c5aec2c72b0ef120a1da325e1e8f0ddaSvetoslav } 2018e3feb15c5aec2c72b0ef120a1da325e1e8f0ddaSvetoslav } catch (RemoteException re) { 2028e3feb15c5aec2c72b0ef120a1da325e1e8f0ddaSvetoslav Log.e(LOG_TAG, "Error while calling remote getWindow", re); 2038e3feb15c5aec2c72b0ef120a1da325e1e8f0ddaSvetoslav } 2048e3feb15c5aec2c72b0ef120a1da325e1e8f0ddaSvetoslav return null; 2058e3feb15c5aec2c72b0ef120a1da325e1e8f0ddaSvetoslav } 2068e3feb15c5aec2c72b0ef120a1da325e1e8f0ddaSvetoslav 2078e3feb15c5aec2c72b0ef120a1da325e1e8f0ddaSvetoslav /** 2088e3feb15c5aec2c72b0ef120a1da325e1e8f0ddaSvetoslav * Gets the info for all windows. 2098e3feb15c5aec2c72b0ef120a1da325e1e8f0ddaSvetoslav * 2108e3feb15c5aec2c72b0ef120a1da325e1e8f0ddaSvetoslav * @param connectionId The id of a connection for interacting with the system. 2118e3feb15c5aec2c72b0ef120a1da325e1e8f0ddaSvetoslav * @return The {@link AccessibilityWindowInfo} list. 2128e3feb15c5aec2c72b0ef120a1da325e1e8f0ddaSvetoslav */ 2138e3feb15c5aec2c72b0ef120a1da325e1e8f0ddaSvetoslav public List<AccessibilityWindowInfo> getWindows(int connectionId) { 2148e3feb15c5aec2c72b0ef120a1da325e1e8f0ddaSvetoslav try { 2158e3feb15c5aec2c72b0ef120a1da325e1e8f0ddaSvetoslav IAccessibilityServiceConnection connection = getConnection(connectionId); 2168e3feb15c5aec2c72b0ef120a1da325e1e8f0ddaSvetoslav if (connection != null) { 217a4725efd0bfa52cbddf6ca587d37fc4ebcbfaf72Svetoslav List<AccessibilityWindowInfo> windows = sAccessibilityCache.getWindows(); 218a4725efd0bfa52cbddf6ca587d37fc4ebcbfaf72Svetoslav if (windows != null) { 219a4725efd0bfa52cbddf6ca587d37fc4ebcbfaf72Svetoslav if (DEBUG) { 220a4725efd0bfa52cbddf6ca587d37fc4ebcbfaf72Svetoslav Log.i(LOG_TAG, "Windows cache hit"); 221a4725efd0bfa52cbddf6ca587d37fc4ebcbfaf72Svetoslav } 222a4725efd0bfa52cbddf6ca587d37fc4ebcbfaf72Svetoslav return windows; 223a4725efd0bfa52cbddf6ca587d37fc4ebcbfaf72Svetoslav } 224a4725efd0bfa52cbddf6ca587d37fc4ebcbfaf72Svetoslav if (DEBUG) { 225a4725efd0bfa52cbddf6ca587d37fc4ebcbfaf72Svetoslav Log.i(LOG_TAG, "Windows cache miss"); 226a4725efd0bfa52cbddf6ca587d37fc4ebcbfaf72Svetoslav } 22756bbeff99a628be8e1d805db2638a4b87fedb106Alan Viverette final long identityToken = Binder.clearCallingIdentity(); 228a4725efd0bfa52cbddf6ca587d37fc4ebcbfaf72Svetoslav windows = connection.getWindows(); 22956bbeff99a628be8e1d805db2638a4b87fedb106Alan Viverette Binder.restoreCallingIdentity(identityToken); 2308e3feb15c5aec2c72b0ef120a1da325e1e8f0ddaSvetoslav if (windows != null) { 231c3281200fa8a27a2a89328f7db552f540ca92d1dMaxim Bogatov sAccessibilityCache.setWindows(windows); 2328e3feb15c5aec2c72b0ef120a1da325e1e8f0ddaSvetoslav return windows; 2338e3feb15c5aec2c72b0ef120a1da325e1e8f0ddaSvetoslav } 2348e3feb15c5aec2c72b0ef120a1da325e1e8f0ddaSvetoslav } else { 2358e3feb15c5aec2c72b0ef120a1da325e1e8f0ddaSvetoslav if (DEBUG) { 2368e3feb15c5aec2c72b0ef120a1da325e1e8f0ddaSvetoslav Log.w(LOG_TAG, "No connection for connection id: " + connectionId); 2378e3feb15c5aec2c72b0ef120a1da325e1e8f0ddaSvetoslav } 2388e3feb15c5aec2c72b0ef120a1da325e1e8f0ddaSvetoslav } 2398e3feb15c5aec2c72b0ef120a1da325e1e8f0ddaSvetoslav } catch (RemoteException re) { 2408e3feb15c5aec2c72b0ef120a1da325e1e8f0ddaSvetoslav Log.e(LOG_TAG, "Error while calling remote getWindows", re); 2418e3feb15c5aec2c72b0ef120a1da325e1e8f0ddaSvetoslav } 2428e3feb15c5aec2c72b0ef120a1da325e1e8f0ddaSvetoslav return Collections.emptyList(); 2438e3feb15c5aec2c72b0ef120a1da325e1e8f0ddaSvetoslav } 2448e3feb15c5aec2c72b0ef120a1da325e1e8f0ddaSvetoslav 2458e3feb15c5aec2c72b0ef120a1da325e1e8f0ddaSvetoslav /** 2468bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov * Finds an {@link AccessibilityNodeInfo} by accessibility id. 2478bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov * 248d116d7c78a9c53f30a73bf273bd7618312cf3847Svetoslav Ganov * @param connectionId The id of a connection for interacting with the system. 24979311c4af8b54d3cd47ab37a120c648bfc990511Svetoslav Ganov * @param accessibilityWindowId A unique window id. Use 2500d04e245534cf777dfaf16dce3c51553837c14ffSvetoslav Ganov * {@link android.view.accessibility.AccessibilityNodeInfo#ACTIVE_WINDOW_ID} 25179311c4af8b54d3cd47ab37a120c648bfc990511Svetoslav Ganov * to query the currently active window. 2520d04e245534cf777dfaf16dce3c51553837c14ffSvetoslav Ganov * @param accessibilityNodeId A unique view id or virtual descendant id from 2530d04e245534cf777dfaf16dce3c51553837c14ffSvetoslav Ganov * where to start the search. Use 2540d04e245534cf777dfaf16dce3c51553837c14ffSvetoslav Ganov * {@link android.view.accessibility.AccessibilityNodeInfo#ROOT_NODE_ID} 2550d04e245534cf777dfaf16dce3c51553837c14ffSvetoslav Ganov * to start from the root. 2566254f4806dd3db53b7380e77fbb183065685573eSvetoslav * @param bypassCache Whether to bypass the cache while looking for the node. 25757c7fd5a43237afc5e8ef31a076e862c0c16c328Svetoslav Ganov * @param prefetchFlags flags to guide prefetching. 2588bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov * @return An {@link AccessibilityNodeInfo} if found, null otherwise. 2598bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov */ 260d116d7c78a9c53f30a73bf273bd7618312cf3847Svetoslav Ganov public AccessibilityNodeInfo findAccessibilityNodeInfoByAccessibilityId(int connectionId, 2616254f4806dd3db53b7380e77fbb183065685573eSvetoslav int accessibilityWindowId, long accessibilityNodeId, boolean bypassCache, 262c2e28932d22faece6e7179c78d4e7656dc63052cPhil Weaver int prefetchFlags, Bundle arguments) { 2638e3feb15c5aec2c72b0ef120a1da325e1e8f0ddaSvetoslav if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS) != 0 2648e3feb15c5aec2c72b0ef120a1da325e1e8f0ddaSvetoslav && (prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) == 0) { 2658e3feb15c5aec2c72b0ef120a1da325e1e8f0ddaSvetoslav throw new IllegalArgumentException("FLAG_PREFETCH_SIBLINGS" 2668e3feb15c5aec2c72b0ef120a1da325e1e8f0ddaSvetoslav + " requires FLAG_PREFETCH_PREDECESSORS"); 2678e3feb15c5aec2c72b0ef120a1da325e1e8f0ddaSvetoslav } 2688bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov try { 269d116d7c78a9c53f30a73bf273bd7618312cf3847Svetoslav Ganov IAccessibilityServiceConnection connection = getConnection(connectionId); 270d116d7c78a9c53f30a73bf273bd7618312cf3847Svetoslav Ganov if (connection != null) { 2716254f4806dd3db53b7380e77fbb183065685573eSvetoslav if (!bypassCache) { 2728e3feb15c5aec2c72b0ef120a1da325e1e8f0ddaSvetoslav AccessibilityNodeInfo cachedInfo = sAccessibilityCache.getNode( 2738e3feb15c5aec2c72b0ef120a1da325e1e8f0ddaSvetoslav accessibilityWindowId, accessibilityNodeId); 2746254f4806dd3db53b7380e77fbb183065685573eSvetoslav if (cachedInfo != null) { 2758e3feb15c5aec2c72b0ef120a1da325e1e8f0ddaSvetoslav if (DEBUG) { 2768e3feb15c5aec2c72b0ef120a1da325e1e8f0ddaSvetoslav Log.i(LOG_TAG, "Node cache hit"); 2778e3feb15c5aec2c72b0ef120a1da325e1e8f0ddaSvetoslav } 2786254f4806dd3db53b7380e77fbb183065685573eSvetoslav return cachedInfo; 2796254f4806dd3db53b7380e77fbb183065685573eSvetoslav } 2808e3feb15c5aec2c72b0ef120a1da325e1e8f0ddaSvetoslav if (DEBUG) { 2818e3feb15c5aec2c72b0ef120a1da325e1e8f0ddaSvetoslav Log.i(LOG_TAG, "Node cache miss"); 2828e3feb15c5aec2c72b0ef120a1da325e1e8f0ddaSvetoslav } 28379311c4af8b54d3cd47ab37a120c648bfc990511Svetoslav Ganov } 284d116d7c78a9c53f30a73bf273bd7618312cf3847Svetoslav Ganov final int interactionId = mInteractionIdCounter.getAndIncrement(); 28556bbeff99a628be8e1d805db2638a4b87fedb106Alan Viverette final long identityToken = Binder.clearCallingIdentity(); 286152e9bb81aa5b2ab4637f4b2dae04b3ce89fa891Svetoslav Ganov final boolean success = connection.findAccessibilityNodeInfoByAccessibilityId( 287f3b4f3163b5b4c0a54a2643f07c97c47b14a1eb7Svetoslav Ganov accessibilityWindowId, accessibilityNodeId, interactionId, this, 288c2e28932d22faece6e7179c78d4e7656dc63052cPhil Weaver prefetchFlags, Thread.currentThread().getId(), arguments); 28956bbeff99a628be8e1d805db2638a4b87fedb106Alan Viverette Binder.restoreCallingIdentity(identityToken); 290152e9bb81aa5b2ab4637f4b2dae04b3ce89fa891Svetoslav Ganov if (success) { 29179311c4af8b54d3cd47ab37a120c648bfc990511Svetoslav Ganov List<AccessibilityNodeInfo> infos = getFindAccessibilityNodeInfosResultAndClear( 292d116d7c78a9c53f30a73bf273bd7618312cf3847Svetoslav Ganov interactionId); 293152e9bb81aa5b2ab4637f4b2dae04b3ce89fa891Svetoslav Ganov finalizeAndCacheAccessibilityNodeInfos(infos, connectionId); 29479311c4af8b54d3cd47ab37a120c648bfc990511Svetoslav Ganov if (infos != null && !infos.isEmpty()) { 295b010b1270fc524befdb9ebc3b3687a37ef1b71b9Phil Weaver for (int i = 1; i < infos.size(); i++) { 296b010b1270fc524befdb9ebc3b3687a37ef1b71b9Phil Weaver infos.get(i).recycle(); 297b010b1270fc524befdb9ebc3b3687a37ef1b71b9Phil Weaver } 29879311c4af8b54d3cd47ab37a120c648bfc990511Svetoslav Ganov return infos.get(0); 29979311c4af8b54d3cd47ab37a120c648bfc990511Svetoslav Ganov } 300d116d7c78a9c53f30a73bf273bd7618312cf3847Svetoslav Ganov } 301d116d7c78a9c53f30a73bf273bd7618312cf3847Svetoslav Ganov } else { 302d116d7c78a9c53f30a73bf273bd7618312cf3847Svetoslav Ganov if (DEBUG) { 303d116d7c78a9c53f30a73bf273bd7618312cf3847Svetoslav Ganov Log.w(LOG_TAG, "No connection for connection id: " + connectionId); 304d116d7c78a9c53f30a73bf273bd7618312cf3847Svetoslav Ganov } 3058bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov } 3068bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov } catch (RemoteException re) { 3078e3feb15c5aec2c72b0ef120a1da325e1e8f0ddaSvetoslav Log.e(LOG_TAG, "Error while calling remote" 3088e3feb15c5aec2c72b0ef120a1da325e1e8f0ddaSvetoslav + " findAccessibilityNodeInfoByAccessibilityId", re); 3098bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov } 3108bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov return null; 3118bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov } 3128bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov 3138bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov /** 31479311c4af8b54d3cd47ab37a120c648bfc990511Svetoslav Ganov * Finds an {@link AccessibilityNodeInfo} by View id. The search is performed in 31579311c4af8b54d3cd47ab37a120c648bfc990511Svetoslav Ganov * the window whose id is specified and starts from the node whose accessibility 31679311c4af8b54d3cd47ab37a120c648bfc990511Svetoslav Ganov * id is specified. 3178bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov * 318d116d7c78a9c53f30a73bf273bd7618312cf3847Svetoslav Ganov * @param connectionId The id of a connection for interacting with the system. 31979311c4af8b54d3cd47ab37a120c648bfc990511Svetoslav Ganov * @param accessibilityWindowId A unique window id. Use 3200d04e245534cf777dfaf16dce3c51553837c14ffSvetoslav Ganov * {@link android.view.accessibility.AccessibilityNodeInfo#ACTIVE_WINDOW_ID} 32179311c4af8b54d3cd47ab37a120c648bfc990511Svetoslav Ganov * to query the currently active window. 3220d04e245534cf777dfaf16dce3c51553837c14ffSvetoslav Ganov * @param accessibilityNodeId A unique view id or virtual descendant id from 3230d04e245534cf777dfaf16dce3c51553837c14ffSvetoslav Ganov * where to start the search. Use 3240d04e245534cf777dfaf16dce3c51553837c14ffSvetoslav Ganov * {@link android.view.accessibility.AccessibilityNodeInfo#ROOT_NODE_ID} 32579311c4af8b54d3cd47ab37a120c648bfc990511Svetoslav Ganov * to start from the root. 32680943d8daa6ab31ab5c486d57aea406aa0730d58Svetoslav Ganov * @param viewId The fully qualified resource name of the view id to find. 32780943d8daa6ab31ab5c486d57aea406aa0730d58Svetoslav Ganov * @return An list of {@link AccessibilityNodeInfo} if found, empty list otherwise. 3288bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov */ 32980943d8daa6ab31ab5c486d57aea406aa0730d58Svetoslav Ganov public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByViewId(int connectionId, 33080943d8daa6ab31ab5c486d57aea406aa0730d58Svetoslav Ganov int accessibilityWindowId, long accessibilityNodeId, String viewId) { 3318bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov try { 332d116d7c78a9c53f30a73bf273bd7618312cf3847Svetoslav Ganov IAccessibilityServiceConnection connection = getConnection(connectionId); 333d116d7c78a9c53f30a73bf273bd7618312cf3847Svetoslav Ganov if (connection != null) { 334d116d7c78a9c53f30a73bf273bd7618312cf3847Svetoslav Ganov final int interactionId = mInteractionIdCounter.getAndIncrement(); 33556bbeff99a628be8e1d805db2638a4b87fedb106Alan Viverette final long identityToken = Binder.clearCallingIdentity(); 33680943d8daa6ab31ab5c486d57aea406aa0730d58Svetoslav Ganov final boolean success = connection.findAccessibilityNodeInfosByViewId( 337152e9bb81aa5b2ab4637f4b2dae04b3ce89fa891Svetoslav Ganov accessibilityWindowId, accessibilityNodeId, viewId, interactionId, this, 338152e9bb81aa5b2ab4637f4b2dae04b3ce89fa891Svetoslav Ganov Thread.currentThread().getId()); 33956bbeff99a628be8e1d805db2638a4b87fedb106Alan Viverette Binder.restoreCallingIdentity(identityToken); 340152e9bb81aa5b2ab4637f4b2dae04b3ce89fa891Svetoslav Ganov if (success) { 34180943d8daa6ab31ab5c486d57aea406aa0730d58Svetoslav Ganov List<AccessibilityNodeInfo> infos = getFindAccessibilityNodeInfosResultAndClear( 342d116d7c78a9c53f30a73bf273bd7618312cf3847Svetoslav Ganov interactionId); 34380943d8daa6ab31ab5c486d57aea406aa0730d58Svetoslav Ganov if (infos != null) { 34480943d8daa6ab31ab5c486d57aea406aa0730d58Svetoslav Ganov finalizeAndCacheAccessibilityNodeInfos(infos, connectionId); 34580943d8daa6ab31ab5c486d57aea406aa0730d58Svetoslav Ganov return infos; 34680943d8daa6ab31ab5c486d57aea406aa0730d58Svetoslav Ganov } 347d116d7c78a9c53f30a73bf273bd7618312cf3847Svetoslav Ganov } 348d116d7c78a9c53f30a73bf273bd7618312cf3847Svetoslav Ganov } else { 349d116d7c78a9c53f30a73bf273bd7618312cf3847Svetoslav Ganov if (DEBUG) { 350d116d7c78a9c53f30a73bf273bd7618312cf3847Svetoslav Ganov Log.w(LOG_TAG, "No connection for connection id: " + connectionId); 351d116d7c78a9c53f30a73bf273bd7618312cf3847Svetoslav Ganov } 3528bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov } 3538bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov } catch (RemoteException re) { 3548e3feb15c5aec2c72b0ef120a1da325e1e8f0ddaSvetoslav Log.w(LOG_TAG, "Error while calling remote" 3558e3feb15c5aec2c72b0ef120a1da325e1e8f0ddaSvetoslav + " findAccessibilityNodeInfoByViewIdInActiveWindow", re); 3568bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov } 35780943d8daa6ab31ab5c486d57aea406aa0730d58Svetoslav Ganov return Collections.emptyList(); 3588bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov } 3598bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov 3608bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov /** 3618bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov * Finds {@link AccessibilityNodeInfo}s by View text. The match is case 3628bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov * insensitive containment. The search is performed in the window whose 36379311c4af8b54d3cd47ab37a120c648bfc990511Svetoslav Ganov * id is specified and starts from the node whose accessibility id is 3648bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov * specified. 3658bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov * 366d116d7c78a9c53f30a73bf273bd7618312cf3847Svetoslav Ganov * @param connectionId The id of a connection for interacting with the system. 36779311c4af8b54d3cd47ab37a120c648bfc990511Svetoslav Ganov * @param accessibilityWindowId A unique window id. Use 3680d04e245534cf777dfaf16dce3c51553837c14ffSvetoslav Ganov * {@link android.view.accessibility.AccessibilityNodeInfo#ACTIVE_WINDOW_ID} 36979311c4af8b54d3cd47ab37a120c648bfc990511Svetoslav Ganov * to query the currently active window. 3700d04e245534cf777dfaf16dce3c51553837c14ffSvetoslav Ganov * @param accessibilityNodeId A unique view id or virtual descendant id from 3710d04e245534cf777dfaf16dce3c51553837c14ffSvetoslav Ganov * where to start the search. Use 3720d04e245534cf777dfaf16dce3c51553837c14ffSvetoslav Ganov * {@link android.view.accessibility.AccessibilityNodeInfo#ROOT_NODE_ID} 3730d04e245534cf777dfaf16dce3c51553837c14ffSvetoslav Ganov * to start from the root. 3748bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov * @param text The searched text. 3758bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov * @return A list of found {@link AccessibilityNodeInfo}s. 3768bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov */ 37766922db828eab153c15bf3ca0b007313d9376e5eSvetoslav Ganov public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByText(int connectionId, 37879311c4af8b54d3cd47ab37a120c648bfc990511Svetoslav Ganov int accessibilityWindowId, long accessibilityNodeId, String text) { 3798bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov try { 380d116d7c78a9c53f30a73bf273bd7618312cf3847Svetoslav Ganov IAccessibilityServiceConnection connection = getConnection(connectionId); 381d116d7c78a9c53f30a73bf273bd7618312cf3847Svetoslav Ganov if (connection != null) { 382d116d7c78a9c53f30a73bf273bd7618312cf3847Svetoslav Ganov final int interactionId = mInteractionIdCounter.getAndIncrement(); 38356bbeff99a628be8e1d805db2638a4b87fedb106Alan Viverette final long identityToken = Binder.clearCallingIdentity(); 384152e9bb81aa5b2ab4637f4b2dae04b3ce89fa891Svetoslav Ganov final boolean success = connection.findAccessibilityNodeInfosByText( 38579311c4af8b54d3cd47ab37a120c648bfc990511Svetoslav Ganov accessibilityWindowId, accessibilityNodeId, text, interactionId, this, 386d116d7c78a9c53f30a73bf273bd7618312cf3847Svetoslav Ganov Thread.currentThread().getId()); 38756bbeff99a628be8e1d805db2638a4b87fedb106Alan Viverette Binder.restoreCallingIdentity(identityToken); 388152e9bb81aa5b2ab4637f4b2dae04b3ce89fa891Svetoslav Ganov if (success) { 389d116d7c78a9c53f30a73bf273bd7618312cf3847Svetoslav Ganov List<AccessibilityNodeInfo> infos = getFindAccessibilityNodeInfosResultAndClear( 390d116d7c78a9c53f30a73bf273bd7618312cf3847Svetoslav Ganov interactionId); 39180943d8daa6ab31ab5c486d57aea406aa0730d58Svetoslav Ganov if (infos != null) { 39280943d8daa6ab31ab5c486d57aea406aa0730d58Svetoslav Ganov finalizeAndCacheAccessibilityNodeInfos(infos, connectionId); 39380943d8daa6ab31ab5c486d57aea406aa0730d58Svetoslav Ganov return infos; 39480943d8daa6ab31ab5c486d57aea406aa0730d58Svetoslav Ganov } 395d116d7c78a9c53f30a73bf273bd7618312cf3847Svetoslav Ganov } 396d116d7c78a9c53f30a73bf273bd7618312cf3847Svetoslav Ganov } else { 397d116d7c78a9c53f30a73bf273bd7618312cf3847Svetoslav Ganov if (DEBUG) { 398d116d7c78a9c53f30a73bf273bd7618312cf3847Svetoslav Ganov Log.w(LOG_TAG, "No connection for connection id: " + connectionId); 399d116d7c78a9c53f30a73bf273bd7618312cf3847Svetoslav Ganov } 4008bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov } 4018bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov } catch (RemoteException re) { 4028e3feb15c5aec2c72b0ef120a1da325e1e8f0ddaSvetoslav Log.w(LOG_TAG, "Error while calling remote" 4038e3feb15c5aec2c72b0ef120a1da325e1e8f0ddaSvetoslav + " findAccessibilityNodeInfosByViewText", re); 4048bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov } 4058bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov return Collections.emptyList(); 4068bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov } 4078bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov 4088bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov /** 4094213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov * Finds the {@link android.view.accessibility.AccessibilityNodeInfo} that has the 4104213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov * specified focus type. The search is performed in the window whose id is specified 4114213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov * and starts from the node whose accessibility id is specified. 4124213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov * 4134213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov * @param connectionId The id of a connection for interacting with the system. 4144213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov * @param accessibilityWindowId A unique window id. Use 4154213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov * {@link android.view.accessibility.AccessibilityNodeInfo#ACTIVE_WINDOW_ID} 4164213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov * to query the currently active window. 4174213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov * @param accessibilityNodeId A unique view id or virtual descendant id from 4184213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov * where to start the search. Use 4194213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov * {@link android.view.accessibility.AccessibilityNodeInfo#ROOT_NODE_ID} 4204213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov * to start from the root. 4214213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov * @param focusType The focus type. 4224213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov * @return The accessibility focused {@link AccessibilityNodeInfo}. 4234213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov */ 4244213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov public AccessibilityNodeInfo findFocus(int connectionId, int accessibilityWindowId, 4254213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov long accessibilityNodeId, int focusType) { 4264213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov try { 4274213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov IAccessibilityServiceConnection connection = getConnection(connectionId); 4284213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov if (connection != null) { 4294213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov final int interactionId = mInteractionIdCounter.getAndIncrement(); 43056bbeff99a628be8e1d805db2638a4b87fedb106Alan Viverette final long identityToken = Binder.clearCallingIdentity(); 431152e9bb81aa5b2ab4637f4b2dae04b3ce89fa891Svetoslav Ganov final boolean success = connection.findFocus(accessibilityWindowId, 4324213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov accessibilityNodeId, focusType, interactionId, this, 4334213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov Thread.currentThread().getId()); 43456bbeff99a628be8e1d805db2638a4b87fedb106Alan Viverette Binder.restoreCallingIdentity(identityToken); 435152e9bb81aa5b2ab4637f4b2dae04b3ce89fa891Svetoslav Ganov if (success) { 4364213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov AccessibilityNodeInfo info = getFindAccessibilityNodeInfoResultAndClear( 4374213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov interactionId); 438152e9bb81aa5b2ab4637f4b2dae04b3ce89fa891Svetoslav Ganov finalizeAndCacheAccessibilityNodeInfo(info, connectionId); 4394213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov return info; 4404213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov } 4414213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov } else { 4424213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov if (DEBUG) { 4434213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov Log.w(LOG_TAG, "No connection for connection id: " + connectionId); 4444213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov } 4454213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov } 4464213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov } catch (RemoteException re) { 4478e3feb15c5aec2c72b0ef120a1da325e1e8f0ddaSvetoslav Log.w(LOG_TAG, "Error while calling remote findFocus", re); 4484213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov } 4494213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov return null; 4504213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov } 4514213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov 4524213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov /** 4534213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov * Finds the accessibility focused {@link android.view.accessibility.AccessibilityNodeInfo}. 4544213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov * The search is performed in the window whose id is specified and starts from the 4554213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov * node whose accessibility id is specified. 4564213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov * 4574213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov * @param connectionId The id of a connection for interacting with the system. 4584213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov * @param accessibilityWindowId A unique window id. Use 4594213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov * {@link android.view.accessibility.AccessibilityNodeInfo#ACTIVE_WINDOW_ID} 4604213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov * to query the currently active window. 4614213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov * @param accessibilityNodeId A unique view id or virtual descendant id from 4624213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov * where to start the search. Use 4634213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov * {@link android.view.accessibility.AccessibilityNodeInfo#ROOT_NODE_ID} 4644213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov * to start from the root. 4654213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov * @param direction The direction in which to search for focusable. 4664213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov * @return The accessibility focused {@link AccessibilityNodeInfo}. 4674213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov */ 4684213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov public AccessibilityNodeInfo focusSearch(int connectionId, int accessibilityWindowId, 4694213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov long accessibilityNodeId, int direction) { 4704213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov try { 4714213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov IAccessibilityServiceConnection connection = getConnection(connectionId); 4724213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov if (connection != null) { 4734213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov final int interactionId = mInteractionIdCounter.getAndIncrement(); 47456bbeff99a628be8e1d805db2638a4b87fedb106Alan Viverette final long identityToken = Binder.clearCallingIdentity(); 475152e9bb81aa5b2ab4637f4b2dae04b3ce89fa891Svetoslav Ganov final boolean success = connection.focusSearch(accessibilityWindowId, 4764213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov accessibilityNodeId, direction, interactionId, this, 4774213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov Thread.currentThread().getId()); 47856bbeff99a628be8e1d805db2638a4b87fedb106Alan Viverette Binder.restoreCallingIdentity(identityToken); 479152e9bb81aa5b2ab4637f4b2dae04b3ce89fa891Svetoslav Ganov if (success) { 4804213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov AccessibilityNodeInfo info = getFindAccessibilityNodeInfoResultAndClear( 4814213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov interactionId); 482152e9bb81aa5b2ab4637f4b2dae04b3ce89fa891Svetoslav Ganov finalizeAndCacheAccessibilityNodeInfo(info, connectionId); 4834213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov return info; 4844213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov } 4854213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov } else { 4864213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov if (DEBUG) { 4874213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov Log.w(LOG_TAG, "No connection for connection id: " + connectionId); 4884213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov } 4894213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov } 4904213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov } catch (RemoteException re) { 4918e3feb15c5aec2c72b0ef120a1da325e1e8f0ddaSvetoslav Log.w(LOG_TAG, "Error while calling remote accessibilityFocusSearch", re); 4924213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov } 4934213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov return null; 4944213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov } 4954213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov 4964213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov /** 4978bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov * Performs an accessibility action on an {@link AccessibilityNodeInfo}. 4988bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov * 499d116d7c78a9c53f30a73bf273bd7618312cf3847Svetoslav Ganov * @param connectionId The id of a connection for interacting with the system. 50079311c4af8b54d3cd47ab37a120c648bfc990511Svetoslav Ganov * @param accessibilityWindowId A unique window id. Use 5010d04e245534cf777dfaf16dce3c51553837c14ffSvetoslav Ganov * {@link android.view.accessibility.AccessibilityNodeInfo#ACTIVE_WINDOW_ID} 50279311c4af8b54d3cd47ab37a120c648bfc990511Svetoslav Ganov * to query the currently active window. 5030d04e245534cf777dfaf16dce3c51553837c14ffSvetoslav Ganov * @param accessibilityNodeId A unique view id or virtual descendant id from 5040d04e245534cf777dfaf16dce3c51553837c14ffSvetoslav Ganov * where to start the search. Use 5050d04e245534cf777dfaf16dce3c51553837c14ffSvetoslav Ganov * {@link android.view.accessibility.AccessibilityNodeInfo#ROOT_NODE_ID} 5060d04e245534cf777dfaf16dce3c51553837c14ffSvetoslav Ganov * to start from the root. 5078bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov * @param action The action to perform. 508aa780c110922148a6a4ba06734bb2b0bb8c98f93Svetoslav Ganov * @param arguments Optional action arguments. 5098bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov * @return Whether the action was performed. 5108bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov */ 511d116d7c78a9c53f30a73bf273bd7618312cf3847Svetoslav Ganov public boolean performAccessibilityAction(int connectionId, int accessibilityWindowId, 512aa780c110922148a6a4ba06734bb2b0bb8c98f93Svetoslav Ganov long accessibilityNodeId, int action, Bundle arguments) { 5138bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov try { 514d116d7c78a9c53f30a73bf273bd7618312cf3847Svetoslav Ganov IAccessibilityServiceConnection connection = getConnection(connectionId); 515d116d7c78a9c53f30a73bf273bd7618312cf3847Svetoslav Ganov if (connection != null) { 516d116d7c78a9c53f30a73bf273bd7618312cf3847Svetoslav Ganov final int interactionId = mInteractionIdCounter.getAndIncrement(); 51756bbeff99a628be8e1d805db2638a4b87fedb106Alan Viverette final long identityToken = Binder.clearCallingIdentity(); 518d116d7c78a9c53f30a73bf273bd7618312cf3847Svetoslav Ganov final boolean success = connection.performAccessibilityAction( 519aa780c110922148a6a4ba06734bb2b0bb8c98f93Svetoslav Ganov accessibilityWindowId, accessibilityNodeId, action, arguments, 520aa780c110922148a6a4ba06734bb2b0bb8c98f93Svetoslav Ganov interactionId, this, Thread.currentThread().getId()); 52156bbeff99a628be8e1d805db2638a4b87fedb106Alan Viverette Binder.restoreCallingIdentity(identityToken); 522d116d7c78a9c53f30a73bf273bd7618312cf3847Svetoslav Ganov if (success) { 52379311c4af8b54d3cd47ab37a120c648bfc990511Svetoslav Ganov return getPerformAccessibilityActionResultAndClear(interactionId); 524d116d7c78a9c53f30a73bf273bd7618312cf3847Svetoslav Ganov } 525d116d7c78a9c53f30a73bf273bd7618312cf3847Svetoslav Ganov } else { 526d116d7c78a9c53f30a73bf273bd7618312cf3847Svetoslav Ganov if (DEBUG) { 527d116d7c78a9c53f30a73bf273bd7618312cf3847Svetoslav Ganov Log.w(LOG_TAG, "No connection for connection id: " + connectionId); 528d116d7c78a9c53f30a73bf273bd7618312cf3847Svetoslav Ganov } 5298bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov } 5308bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov } catch (RemoteException re) { 5318e3feb15c5aec2c72b0ef120a1da325e1e8f0ddaSvetoslav Log.w(LOG_TAG, "Error while calling remote performAccessibilityAction", re); 5328bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov } 5338bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov return false; 5348bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov } 5358bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov 53679311c4af8b54d3cd47ab37a120c648bfc990511Svetoslav Ganov public void clearCache() { 5378e3feb15c5aec2c72b0ef120a1da325e1e8f0ddaSvetoslav sAccessibilityCache.clear(); 53879311c4af8b54d3cd47ab37a120c648bfc990511Svetoslav Ganov } 53979311c4af8b54d3cd47ab37a120c648bfc990511Svetoslav Ganov 54079311c4af8b54d3cd47ab37a120c648bfc990511Svetoslav Ganov public void onAccessibilityEvent(AccessibilityEvent event) { 5418e3feb15c5aec2c72b0ef120a1da325e1e8f0ddaSvetoslav sAccessibilityCache.onAccessibilityEvent(event); 5428e3feb15c5aec2c72b0ef120a1da325e1e8f0ddaSvetoslav } 5438e3feb15c5aec2c72b0ef120a1da325e1e8f0ddaSvetoslav 5448bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov /** 5458bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov * Gets the the result of an async request that returns an {@link AccessibilityNodeInfo}. 5468bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov * 5478bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov * @param interactionId The interaction id to match the result with the request. 5488bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov * @return The result {@link AccessibilityNodeInfo}. 5498bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov */ 5508bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov private AccessibilityNodeInfo getFindAccessibilityNodeInfoResultAndClear(int interactionId) { 5518bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov synchronized (mInstanceLock) { 5528bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov final boolean success = waitForResultTimedLocked(interactionId); 5538bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov AccessibilityNodeInfo result = success ? mFindAccessibilityNodeInfoResult : null; 5548bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov clearResultLocked(); 5558bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov return result; 5568bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov } 5578bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov } 5588bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov 5598bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov /** 5608bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov * {@inheritDoc} 5618bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov */ 5628bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov public void setFindAccessibilityNodeInfoResult(AccessibilityNodeInfo info, 5638bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov int interactionId) { 5648bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov synchronized (mInstanceLock) { 5658bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov if (interactionId > mInteractionId) { 5668bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov mFindAccessibilityNodeInfoResult = info; 5678bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov mInteractionId = interactionId; 5688bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov } 5698bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov mInstanceLock.notifyAll(); 5708bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov } 5718bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov } 5728bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov 5738bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov /** 5748bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov * Gets the the result of an async request that returns {@link AccessibilityNodeInfo}s. 5758bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov * 5768bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov * @param interactionId The interaction id to match the result with the request. 5778bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov * @return The result {@link AccessibilityNodeInfo}s. 5788bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov */ 5798bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov private List<AccessibilityNodeInfo> getFindAccessibilityNodeInfosResultAndClear( 5808bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov int interactionId) { 5818bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov synchronized (mInstanceLock) { 5828bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov final boolean success = waitForResultTimedLocked(interactionId); 5834213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov List<AccessibilityNodeInfo> result = null; 5844213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov if (success) { 5854213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov result = mFindAccessibilityNodeInfosResult; 5864213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov } else { 5874213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov result = Collections.emptyList(); 5884213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov } 5898bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov clearResultLocked(); 5904528b4e882584745f48263fa6626987e63832a2aSvetoslav Ganov if (Build.IS_DEBUGGABLE && CHECK_INTEGRITY) { 5914528b4e882584745f48263fa6626987e63832a2aSvetoslav Ganov checkFindAccessibilityNodeInfoResultIntegrity(result); 5924528b4e882584745f48263fa6626987e63832a2aSvetoslav Ganov } 5938bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov return result; 5948bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov } 5958bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov } 5968bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov 5978bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov /** 5988bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov * {@inheritDoc} 5998bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov */ 6008bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov public void setFindAccessibilityNodeInfosResult(List<AccessibilityNodeInfo> infos, 6018bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov int interactionId) { 6028bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov synchronized (mInstanceLock) { 6038bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov if (interactionId > mInteractionId) { 6044213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov if (infos != null) { 6054213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov // If the call is not an IPC, i.e. it is made from the same process, we need to 6064213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov // instantiate new result list to avoid passing internal instances to clients. 6074213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov final boolean isIpcCall = (Binder.getCallingPid() != Process.myPid()); 6084213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov if (!isIpcCall) { 609a4725efd0bfa52cbddf6ca587d37fc4ebcbfaf72Svetoslav mFindAccessibilityNodeInfosResult = new ArrayList<>(infos); 6104213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov } else { 6114213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov mFindAccessibilityNodeInfosResult = infos; 6124213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov } 61379311c4af8b54d3cd47ab37a120c648bfc990511Svetoslav Ganov } else { 6144213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov mFindAccessibilityNodeInfosResult = Collections.emptyList(); 61579311c4af8b54d3cd47ab37a120c648bfc990511Svetoslav Ganov } 6168bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov mInteractionId = interactionId; 6178bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov } 6188bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov mInstanceLock.notifyAll(); 6198bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov } 6208bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov } 6218bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov 6228bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov /** 6238bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov * Gets the result of a request to perform an accessibility action. 6248bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov * 6258bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov * @param interactionId The interaction id to match the result with the request. 6268bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov * @return Whether the action was performed. 6278bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov */ 62879311c4af8b54d3cd47ab37a120c648bfc990511Svetoslav Ganov private boolean getPerformAccessibilityActionResultAndClear(int interactionId) { 6298bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov synchronized (mInstanceLock) { 6308bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov final boolean success = waitForResultTimedLocked(interactionId); 6318bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov final boolean result = success ? mPerformAccessibilityActionResult : false; 6328bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov clearResultLocked(); 6338bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov return result; 6348bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov } 6358bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov } 6368bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov 6378bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov /** 6388bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov * {@inheritDoc} 6398bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov */ 6408bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov public void setPerformAccessibilityActionResult(boolean succeeded, int interactionId) { 6418bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov synchronized (mInstanceLock) { 6428bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov if (interactionId > mInteractionId) { 6438bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov mPerformAccessibilityActionResult = succeeded; 6448bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov mInteractionId = interactionId; 6458bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov } 6468bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov mInstanceLock.notifyAll(); 6478bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov } 6488bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov } 6498bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov 6508bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov /** 6518bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov * Clears the result state. 6528bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov */ 6538bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov private void clearResultLocked() { 6548bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov mInteractionId = -1; 6558bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov mFindAccessibilityNodeInfoResult = null; 6568bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov mFindAccessibilityNodeInfosResult = null; 6578bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov mPerformAccessibilityActionResult = false; 6588bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov } 6598bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov 6608bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov /** 6618bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov * Waits up to a given bound for a result of a request and returns it. 6628bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov * 6638bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov * @param interactionId The interaction id to match the result with the request. 6648bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov * @return Whether the result was received. 6658bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov */ 6668bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov private boolean waitForResultTimedLocked(int interactionId) { 6678bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov long waitTimeMillis = TIMEOUT_INTERACTION_MILLIS; 6688bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov final long startTimeMillis = SystemClock.uptimeMillis(); 6698bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov while (true) { 6708bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov try { 6716bc5e530016928027c7b390a8368ecdd5bff072fSvetoslav Ganov Message sameProcessMessage = getSameProcessMessageAndClear(); 6726bc5e530016928027c7b390a8368ecdd5bff072fSvetoslav Ganov if (sameProcessMessage != null) { 6736bc5e530016928027c7b390a8368ecdd5bff072fSvetoslav Ganov sameProcessMessage.getTarget().handleMessage(sameProcessMessage); 6746bc5e530016928027c7b390a8368ecdd5bff072fSvetoslav Ganov } 6756bc5e530016928027c7b390a8368ecdd5bff072fSvetoslav Ganov 6768bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov if (mInteractionId == interactionId) { 6778bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov return true; 6788bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov } 6798bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov if (mInteractionId > interactionId) { 6808bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov return false; 6818bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov } 6828bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis; 6838bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov waitTimeMillis = TIMEOUT_INTERACTION_MILLIS - elapsedTimeMillis; 6848bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov if (waitTimeMillis <= 0) { 6858bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov return false; 6868bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov } 6878bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov mInstanceLock.wait(waitTimeMillis); 6888bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov } catch (InterruptedException ie) { 6898bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov /* ignore */ 6908bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov } 6918bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov } 6928bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov } 6938bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov 6948bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov /** 6958bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov * Finalize an {@link AccessibilityNodeInfo} before passing it to the client. 6968bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov * 6978bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov * @param info The info. 698d116d7c78a9c53f30a73bf273bd7618312cf3847Svetoslav Ganov * @param connectionId The id of the connection to the system. 6998bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov */ 700152e9bb81aa5b2ab4637f4b2dae04b3ce89fa891Svetoslav Ganov private void finalizeAndCacheAccessibilityNodeInfo(AccessibilityNodeInfo info, 701152e9bb81aa5b2ab4637f4b2dae04b3ce89fa891Svetoslav Ganov int connectionId) { 7028bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov if (info != null) { 703d116d7c78a9c53f30a73bf273bd7618312cf3847Svetoslav Ganov info.setConnectionId(connectionId); 7048bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov info.setSealed(true); 7058e3feb15c5aec2c72b0ef120a1da325e1e8f0ddaSvetoslav sAccessibilityCache.add(info); 7068bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov } 7078bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov } 7088bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov 7098bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov /** 7108bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov * Finalize {@link AccessibilityNodeInfo}s before passing them to the client. 7118bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov * 7128bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov * @param infos The {@link AccessibilityNodeInfo}s. 713d116d7c78a9c53f30a73bf273bd7618312cf3847Svetoslav Ganov * @param connectionId The id of the connection to the system. 7148bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov */ 7150d04e245534cf777dfaf16dce3c51553837c14ffSvetoslav Ganov private void finalizeAndCacheAccessibilityNodeInfos(List<AccessibilityNodeInfo> infos, 716152e9bb81aa5b2ab4637f4b2dae04b3ce89fa891Svetoslav Ganov int connectionId) { 7178bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov if (infos != null) { 7188bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov final int infosCount = infos.size(); 7198bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov for (int i = 0; i < infosCount; i++) { 7208bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov AccessibilityNodeInfo info = infos.get(i); 721152e9bb81aa5b2ab4637f4b2dae04b3ce89fa891Svetoslav Ganov finalizeAndCacheAccessibilityNodeInfo(info, connectionId); 7228bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov } 7238bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov } 7248bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov } 7258bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov 7268bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov /** 7278bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov * Gets the message stored if the interacted and interacting 7288bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov * threads are the same. 7298bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov * 7308bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov * @return The message. 7318bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov */ 7328bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov private Message getSameProcessMessageAndClear() { 7338bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov synchronized (mInstanceLock) { 7348bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov Message result = mSameThreadMessage; 7358bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov mSameThreadMessage = null; 7368bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov return result; 7378bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov } 7388bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov } 739d116d7c78a9c53f30a73bf273bd7618312cf3847Svetoslav Ganov 740d116d7c78a9c53f30a73bf273bd7618312cf3847Svetoslav Ganov /** 741d116d7c78a9c53f30a73bf273bd7618312cf3847Svetoslav Ganov * Gets a cached accessibility service connection. 742d116d7c78a9c53f30a73bf273bd7618312cf3847Svetoslav Ganov * 743d116d7c78a9c53f30a73bf273bd7618312cf3847Svetoslav Ganov * @param connectionId The connection id. 744d116d7c78a9c53f30a73bf273bd7618312cf3847Svetoslav Ganov * @return The cached connection if such. 745d116d7c78a9c53f30a73bf273bd7618312cf3847Svetoslav Ganov */ 746d116d7c78a9c53f30a73bf273bd7618312cf3847Svetoslav Ganov public IAccessibilityServiceConnection getConnection(int connectionId) { 74736bcdb535e14a8a2e2c8643fb577569f7a2b6aedSvetoslav Ganov synchronized (sConnectionCache) { 74836bcdb535e14a8a2e2c8643fb577569f7a2b6aedSvetoslav Ganov return sConnectionCache.get(connectionId); 749d116d7c78a9c53f30a73bf273bd7618312cf3847Svetoslav Ganov } 750d116d7c78a9c53f30a73bf273bd7618312cf3847Svetoslav Ganov } 751d116d7c78a9c53f30a73bf273bd7618312cf3847Svetoslav Ganov 752d116d7c78a9c53f30a73bf273bd7618312cf3847Svetoslav Ganov /** 753d116d7c78a9c53f30a73bf273bd7618312cf3847Svetoslav Ganov * Adds a cached accessibility service connection. 754d116d7c78a9c53f30a73bf273bd7618312cf3847Svetoslav Ganov * 755d116d7c78a9c53f30a73bf273bd7618312cf3847Svetoslav Ganov * @param connectionId The connection id. 756d116d7c78a9c53f30a73bf273bd7618312cf3847Svetoslav Ganov * @param connection The connection. 757d116d7c78a9c53f30a73bf273bd7618312cf3847Svetoslav Ganov */ 758d116d7c78a9c53f30a73bf273bd7618312cf3847Svetoslav Ganov public void addConnection(int connectionId, IAccessibilityServiceConnection connection) { 75936bcdb535e14a8a2e2c8643fb577569f7a2b6aedSvetoslav Ganov synchronized (sConnectionCache) { 76036bcdb535e14a8a2e2c8643fb577569f7a2b6aedSvetoslav Ganov sConnectionCache.put(connectionId, connection); 761d116d7c78a9c53f30a73bf273bd7618312cf3847Svetoslav Ganov } 762d116d7c78a9c53f30a73bf273bd7618312cf3847Svetoslav Ganov } 763d116d7c78a9c53f30a73bf273bd7618312cf3847Svetoslav Ganov 764d116d7c78a9c53f30a73bf273bd7618312cf3847Svetoslav Ganov /** 765d116d7c78a9c53f30a73bf273bd7618312cf3847Svetoslav Ganov * Removes a cached accessibility service connection. 766d116d7c78a9c53f30a73bf273bd7618312cf3847Svetoslav Ganov * 767d116d7c78a9c53f30a73bf273bd7618312cf3847Svetoslav Ganov * @param connectionId The connection id. 768d116d7c78a9c53f30a73bf273bd7618312cf3847Svetoslav Ganov */ 769d116d7c78a9c53f30a73bf273bd7618312cf3847Svetoslav Ganov public void removeConnection(int connectionId) { 77036bcdb535e14a8a2e2c8643fb577569f7a2b6aedSvetoslav Ganov synchronized (sConnectionCache) { 77136bcdb535e14a8a2e2c8643fb577569f7a2b6aedSvetoslav Ganov sConnectionCache.remove(connectionId); 772d116d7c78a9c53f30a73bf273bd7618312cf3847Svetoslav Ganov } 773d116d7c78a9c53f30a73bf273bd7618312cf3847Svetoslav Ganov } 7744528b4e882584745f48263fa6626987e63832a2aSvetoslav Ganov 7754528b4e882584745f48263fa6626987e63832a2aSvetoslav Ganov /** 7764528b4e882584745f48263fa6626987e63832a2aSvetoslav Ganov * Checks whether the infos are a fully connected tree with no duplicates. 7774528b4e882584745f48263fa6626987e63832a2aSvetoslav Ganov * 7784528b4e882584745f48263fa6626987e63832a2aSvetoslav Ganov * @param infos The result list to check. 7794528b4e882584745f48263fa6626987e63832a2aSvetoslav Ganov */ 7804528b4e882584745f48263fa6626987e63832a2aSvetoslav Ganov private void checkFindAccessibilityNodeInfoResultIntegrity(List<AccessibilityNodeInfo> infos) { 7814528b4e882584745f48263fa6626987e63832a2aSvetoslav Ganov if (infos.size() == 0) { 7824528b4e882584745f48263fa6626987e63832a2aSvetoslav Ganov return; 7834528b4e882584745f48263fa6626987e63832a2aSvetoslav Ganov } 7844528b4e882584745f48263fa6626987e63832a2aSvetoslav Ganov // Find the root node. 7854528b4e882584745f48263fa6626987e63832a2aSvetoslav Ganov AccessibilityNodeInfo root = infos.get(0); 7864528b4e882584745f48263fa6626987e63832a2aSvetoslav Ganov final int infoCount = infos.size(); 7874528b4e882584745f48263fa6626987e63832a2aSvetoslav Ganov for (int i = 1; i < infoCount; i++) { 7884528b4e882584745f48263fa6626987e63832a2aSvetoslav Ganov for (int j = i; j < infoCount; j++) { 7894528b4e882584745f48263fa6626987e63832a2aSvetoslav Ganov AccessibilityNodeInfo candidate = infos.get(j); 7904528b4e882584745f48263fa6626987e63832a2aSvetoslav Ganov if (root.getParentNodeId() == candidate.getSourceNodeId()) { 7914528b4e882584745f48263fa6626987e63832a2aSvetoslav Ganov root = candidate; 7924528b4e882584745f48263fa6626987e63832a2aSvetoslav Ganov break; 7934528b4e882584745f48263fa6626987e63832a2aSvetoslav Ganov } 7944528b4e882584745f48263fa6626987e63832a2aSvetoslav Ganov } 7954528b4e882584745f48263fa6626987e63832a2aSvetoslav Ganov } 7964528b4e882584745f48263fa6626987e63832a2aSvetoslav Ganov if (root == null) { 7974528b4e882584745f48263fa6626987e63832a2aSvetoslav Ganov Log.e(LOG_TAG, "No root."); 7984528b4e882584745f48263fa6626987e63832a2aSvetoslav Ganov } 7994528b4e882584745f48263fa6626987e63832a2aSvetoslav Ganov // Check for duplicates. 800a4725efd0bfa52cbddf6ca587d37fc4ebcbfaf72Svetoslav HashSet<AccessibilityNodeInfo> seen = new HashSet<>(); 801a4725efd0bfa52cbddf6ca587d37fc4ebcbfaf72Svetoslav Queue<AccessibilityNodeInfo> fringe = new LinkedList<>(); 8024528b4e882584745f48263fa6626987e63832a2aSvetoslav Ganov fringe.add(root); 8034528b4e882584745f48263fa6626987e63832a2aSvetoslav Ganov while (!fringe.isEmpty()) { 8044528b4e882584745f48263fa6626987e63832a2aSvetoslav Ganov AccessibilityNodeInfo current = fringe.poll(); 8054528b4e882584745f48263fa6626987e63832a2aSvetoslav Ganov if (!seen.add(current)) { 8064528b4e882584745f48263fa6626987e63832a2aSvetoslav Ganov Log.e(LOG_TAG, "Duplicate node."); 8074528b4e882584745f48263fa6626987e63832a2aSvetoslav Ganov return; 8084528b4e882584745f48263fa6626987e63832a2aSvetoslav Ganov } 809f0aed09ed8153043e40b3ac99788d47ba0831306Alan Viverette final int childCount = current.getChildCount(); 8104528b4e882584745f48263fa6626987e63832a2aSvetoslav Ganov for (int i = 0; i < childCount; i++) { 811f0aed09ed8153043e40b3ac99788d47ba0831306Alan Viverette final long childId = current.getChildId(i); 8124528b4e882584745f48263fa6626987e63832a2aSvetoslav Ganov for (int j = 0; j < infoCount; j++) { 8134528b4e882584745f48263fa6626987e63832a2aSvetoslav Ganov AccessibilityNodeInfo child = infos.get(j); 8144528b4e882584745f48263fa6626987e63832a2aSvetoslav Ganov if (child.getSourceNodeId() == childId) { 8154528b4e882584745f48263fa6626987e63832a2aSvetoslav Ganov fringe.add(child); 8164528b4e882584745f48263fa6626987e63832a2aSvetoslav Ganov } 8174528b4e882584745f48263fa6626987e63832a2aSvetoslav Ganov } 8184528b4e882584745f48263fa6626987e63832a2aSvetoslav Ganov } 8194528b4e882584745f48263fa6626987e63832a2aSvetoslav Ganov } 8204528b4e882584745f48263fa6626987e63832a2aSvetoslav Ganov final int disconnectedCount = infos.size() - seen.size(); 8214528b4e882584745f48263fa6626987e63832a2aSvetoslav Ganov if (disconnectedCount > 0) { 8224528b4e882584745f48263fa6626987e63832a2aSvetoslav Ganov Log.e(LOG_TAG, disconnectedCount + " Disconnected nodes."); 8234528b4e882584745f48263fa6626987e63832a2aSvetoslav Ganov } 8244528b4e882584745f48263fa6626987e63832a2aSvetoslav Ganov } 8258bd69610aafc6995126965d1d23b771fe02a9084Svetoslav Ganov} 826