179311c4af8b54d3cd47ab37a120c648bfc990511Svetoslav Ganov/* 279311c4af8b54d3cd47ab37a120c648bfc990511Svetoslav Ganov * Copyright (C) 2012 The Android Open Source Project 379311c4af8b54d3cd47ab37a120c648bfc990511Svetoslav Ganov * 479311c4af8b54d3cd47ab37a120c648bfc990511Svetoslav Ganov * Licensed under the Apache License, Version 2.0 (the "License"); 579311c4af8b54d3cd47ab37a120c648bfc990511Svetoslav Ganov * you may not use this file except in compliance with the License. 679311c4af8b54d3cd47ab37a120c648bfc990511Svetoslav Ganov * You may obtain a copy of the License at 779311c4af8b54d3cd47ab37a120c648bfc990511Svetoslav Ganov * 879311c4af8b54d3cd47ab37a120c648bfc990511Svetoslav Ganov * http://www.apache.org/licenses/LICENSE-2.0 979311c4af8b54d3cd47ab37a120c648bfc990511Svetoslav Ganov * 1079311c4af8b54d3cd47ab37a120c648bfc990511Svetoslav Ganov * Unless required by applicable law or agreed to in writing, software 1179311c4af8b54d3cd47ab37a120c648bfc990511Svetoslav Ganov * distributed under the License is distributed on an "AS IS" BASIS, 1279311c4af8b54d3cd47ab37a120c648bfc990511Svetoslav Ganov * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1379311c4af8b54d3cd47ab37a120c648bfc990511Svetoslav Ganov * See the License for the specific language governing permissions and 1479311c4af8b54d3cd47ab37a120c648bfc990511Svetoslav Ganov * limitations under the License. 1579311c4af8b54d3cd47ab37a120c648bfc990511Svetoslav Ganov */ 1679311c4af8b54d3cd47ab37a120c648bfc990511Svetoslav Ganov 1757c7fd5a43237afc5e8ef31a076e862c0c16c328Svetoslav Ganovpackage android.view.accessibility; 1879311c4af8b54d3cd47ab37a120c648bfc990511Svetoslav Ganov 19c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganovimport android.os.Build; 200d04e245534cf777dfaf16dce3c51553837c14ffSvetoslav Ganovimport android.util.Log; 2179311c4af8b54d3cd47ab37a120c648bfc990511Svetoslav Ganovimport android.util.LongSparseArray; 224213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganovimport android.util.SparseLongArray; 2379311c4af8b54d3cd47ab37a120c648bfc990511Svetoslav Ganov 24c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganovimport java.util.HashSet; 25c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganovimport java.util.LinkedList; 26c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganovimport java.util.Queue; 27c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov 2879311c4af8b54d3cd47ab37a120c648bfc990511Svetoslav Ganov/** 2979311c4af8b54d3cd47ab37a120c648bfc990511Svetoslav Ganov * Simple cache for AccessibilityNodeInfos. The cache is mapping an 3079311c4af8b54d3cd47ab37a120c648bfc990511Svetoslav Ganov * accessibility id to an info. The cache allows storing of 3179311c4af8b54d3cd47ab37a120c648bfc990511Svetoslav Ganov * <code>null</code> values. It also tracks accessibility events 3279311c4af8b54d3cd47ab37a120c648bfc990511Svetoslav Ganov * and invalidates accordingly. 3379311c4af8b54d3cd47ab37a120c648bfc990511Svetoslav Ganov * 3479311c4af8b54d3cd47ab37a120c648bfc990511Svetoslav Ganov * @hide 3579311c4af8b54d3cd47ab37a120c648bfc990511Svetoslav Ganov */ 3679311c4af8b54d3cd47ab37a120c648bfc990511Svetoslav Ganovpublic class AccessibilityNodeInfoCache { 3779311c4af8b54d3cd47ab37a120c648bfc990511Svetoslav Ganov 380d04e245534cf777dfaf16dce3c51553837c14ffSvetoslav Ganov private static final String LOG_TAG = AccessibilityNodeInfoCache.class.getSimpleName(); 390d04e245534cf777dfaf16dce3c51553837c14ffSvetoslav Ganov 400d04e245534cf777dfaf16dce3c51553837c14ffSvetoslav Ganov private static final boolean ENABLED = true; 410d04e245534cf777dfaf16dce3c51553837c14ffSvetoslav Ganov 420d04e245534cf777dfaf16dce3c51553837c14ffSvetoslav Ganov private static final boolean DEBUG = false; 4379311c4af8b54d3cd47ab37a120c648bfc990511Svetoslav Ganov 44c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov private static final boolean CHECK_INTEGRITY = true; 45c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov 4657c7fd5a43237afc5e8ef31a076e862c0c16c328Svetoslav Ganov private final Object mLock = new Object(); 4779311c4af8b54d3cd47ab37a120c648bfc990511Svetoslav Ganov 4879311c4af8b54d3cd47ab37a120c648bfc990511Svetoslav Ganov private final LongSparseArray<AccessibilityNodeInfo> mCacheImpl; 4979311c4af8b54d3cd47ab37a120c648bfc990511Svetoslav Ganov 50c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov private int mWindowId; 51c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov 5257c7fd5a43237afc5e8ef31a076e862c0c16c328Svetoslav Ganov public AccessibilityNodeInfoCache() { 5379311c4af8b54d3cd47ab37a120c648bfc990511Svetoslav Ganov if (ENABLED) { 5479311c4af8b54d3cd47ab37a120c648bfc990511Svetoslav Ganov mCacheImpl = new LongSparseArray<AccessibilityNodeInfo>(); 5579311c4af8b54d3cd47ab37a120c648bfc990511Svetoslav Ganov } else { 5679311c4af8b54d3cd47ab37a120c648bfc990511Svetoslav Ganov mCacheImpl = null; 5779311c4af8b54d3cd47ab37a120c648bfc990511Svetoslav Ganov } 5879311c4af8b54d3cd47ab37a120c648bfc990511Svetoslav Ganov } 5979311c4af8b54d3cd47ab37a120c648bfc990511Svetoslav Ganov 6079311c4af8b54d3cd47ab37a120c648bfc990511Svetoslav Ganov /** 6179311c4af8b54d3cd47ab37a120c648bfc990511Svetoslav Ganov * The cache keeps track of {@link AccessibilityEvent}s and invalidates 6279311c4af8b54d3cd47ab37a120c648bfc990511Svetoslav Ganov * cached nodes as appropriate. 6379311c4af8b54d3cd47ab37a120c648bfc990511Svetoslav Ganov * 6479311c4af8b54d3cd47ab37a120c648bfc990511Svetoslav Ganov * @param event An event. 6579311c4af8b54d3cd47ab37a120c648bfc990511Svetoslav Ganov */ 6679311c4af8b54d3cd47ab37a120c648bfc990511Svetoslav Ganov public void onAccessibilityEvent(AccessibilityEvent event) { 674213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov if (ENABLED) { 684213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov final int eventType = event.getEventType(); 694213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov switch (eventType) { 704213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED: { 71c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov // New window so we clear the cache. 72c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov mWindowId = event.getWindowId(); 734213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov clear(); 744213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov } break; 75c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov case AccessibilityEvent.TYPE_VIEW_HOVER_ENTER: 76c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov case AccessibilityEvent.TYPE_VIEW_HOVER_EXIT: { 77c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov final int windowId = event.getWindowId(); 78c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov if (mWindowId != windowId) { 79c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov // New window so we clear the cache. 80c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov mWindowId = windowId; 81c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov clear(); 82c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov } 83c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov } break; 844213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov case AccessibilityEvent.TYPE_VIEW_FOCUSED: 85c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED: 864213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov case AccessibilityEvent.TYPE_VIEW_SELECTED: 874213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov case AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED: 884213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov case AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED: { 89c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov // Since we prefetch the descendants of a node we 90c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov // just remove the entire subtree since when the node 91c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov // is fetched we will gets its descendant anyway. 92c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov synchronized (mLock) { 93c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov final long sourceId = event.getSourceNodeId(); 94c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov clearSubTreeLocked(sourceId); 95c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov if (eventType == AccessibilityEvent.TYPE_VIEW_FOCUSED) { 96c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov clearSubtreeWithOldInputFocusLocked(sourceId); 97c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov } 98c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov if (eventType == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED) { 99c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov clearSubtreeWithOldAccessibilityFocusLocked(sourceId); 100c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov } 101c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov } 1024213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov } break; 1034213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov case AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED: 1044213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov case AccessibilityEvent.TYPE_VIEW_SCROLLED: { 105c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov synchronized (mLock) { 106c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov final long accessibilityNodeId = event.getSourceNodeId(); 107c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov clearSubTreeLocked(accessibilityNodeId); 108c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov } 1094213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov } break; 1104213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov } 111c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov if (Build.IS_DEBUGGABLE && CHECK_INTEGRITY) { 112c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov checkIntegrity(); 113c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov } 11479311c4af8b54d3cd47ab37a120c648bfc990511Svetoslav Ganov } 11579311c4af8b54d3cd47ab37a120c648bfc990511Svetoslav Ganov } 11679311c4af8b54d3cd47ab37a120c648bfc990511Svetoslav Ganov 11779311c4af8b54d3cd47ab37a120c648bfc990511Svetoslav Ganov /** 11879311c4af8b54d3cd47ab37a120c648bfc990511Svetoslav Ganov * Gets a cached {@link AccessibilityNodeInfo} given its accessibility node id. 11979311c4af8b54d3cd47ab37a120c648bfc990511Svetoslav Ganov * 12079311c4af8b54d3cd47ab37a120c648bfc990511Svetoslav Ganov * @param accessibilityNodeId The info accessibility node id. 12179311c4af8b54d3cd47ab37a120c648bfc990511Svetoslav Ganov * @return The cached {@link AccessibilityNodeInfo} or null if such not found. 12279311c4af8b54d3cd47ab37a120c648bfc990511Svetoslav Ganov */ 12379311c4af8b54d3cd47ab37a120c648bfc990511Svetoslav Ganov public AccessibilityNodeInfo get(long accessibilityNodeId) { 12479311c4af8b54d3cd47ab37a120c648bfc990511Svetoslav Ganov if (ENABLED) { 12557c7fd5a43237afc5e8ef31a076e862c0c16c328Svetoslav Ganov synchronized(mLock) { 126afd5fab3ab001e90269dfef37d87e69e0e261826Svetoslav Ganov AccessibilityNodeInfo info = mCacheImpl.get(accessibilityNodeId); 127afd5fab3ab001e90269dfef37d87e69e0e261826Svetoslav Ganov if (info != null) { 128afd5fab3ab001e90269dfef37d87e69e0e261826Svetoslav Ganov // Return a copy since the client calls to AccessibilityNodeInfo#recycle() 129afd5fab3ab001e90269dfef37d87e69e0e261826Svetoslav Ganov // will wipe the data of the cached info. 130afd5fab3ab001e90269dfef37d87e69e0e261826Svetoslav Ganov info = AccessibilityNodeInfo.obtain(info); 131afd5fab3ab001e90269dfef37d87e69e0e261826Svetoslav Ganov } 13257c7fd5a43237afc5e8ef31a076e862c0c16c328Svetoslav Ganov if (DEBUG) { 133afd5fab3ab001e90269dfef37d87e69e0e261826Svetoslav Ganov Log.i(LOG_TAG, "get(" + accessibilityNodeId + ") = " + info); 13457c7fd5a43237afc5e8ef31a076e862c0c16c328Svetoslav Ganov } 135afd5fab3ab001e90269dfef37d87e69e0e261826Svetoslav Ganov return info; 1360d04e245534cf777dfaf16dce3c51553837c14ffSvetoslav Ganov } 13779311c4af8b54d3cd47ab37a120c648bfc990511Svetoslav Ganov } else { 13879311c4af8b54d3cd47ab37a120c648bfc990511Svetoslav Ganov return null; 13979311c4af8b54d3cd47ab37a120c648bfc990511Svetoslav Ganov } 14079311c4af8b54d3cd47ab37a120c648bfc990511Svetoslav Ganov } 14179311c4af8b54d3cd47ab37a120c648bfc990511Svetoslav Ganov 14279311c4af8b54d3cd47ab37a120c648bfc990511Svetoslav Ganov /** 14379311c4af8b54d3cd47ab37a120c648bfc990511Svetoslav Ganov * Caches an {@link AccessibilityNodeInfo} given its accessibility node id. 14479311c4af8b54d3cd47ab37a120c648bfc990511Svetoslav Ganov * 14579311c4af8b54d3cd47ab37a120c648bfc990511Svetoslav Ganov * @param info The {@link AccessibilityNodeInfo} to cache. 14679311c4af8b54d3cd47ab37a120c648bfc990511Svetoslav Ganov */ 147c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov public void add(AccessibilityNodeInfo info) { 14879311c4af8b54d3cd47ab37a120c648bfc990511Svetoslav Ganov if (ENABLED) { 14957c7fd5a43237afc5e8ef31a076e862c0c16c328Svetoslav Ganov synchronized(mLock) { 15057c7fd5a43237afc5e8ef31a076e862c0c16c328Svetoslav Ganov if (DEBUG) { 151c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov Log.i(LOG_TAG, "add(" + info + ")"); 15257c7fd5a43237afc5e8ef31a076e862c0c16c328Svetoslav Ganov } 15379311c4af8b54d3cd47ab37a120c648bfc990511Svetoslav Ganov 154c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov final long sourceId = info.getSourceNodeId(); 155c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov AccessibilityNodeInfo oldInfo = mCacheImpl.get(sourceId); 156c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov if (oldInfo != null) { 157c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov // If the added node is in the cache we have to be careful if 158c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov // the new one represents a source state where some of the 159c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov // children have been removed to avoid having disconnected 160c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov // subtrees in the cache. 161c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov SparseLongArray oldChildrenIds = oldInfo.getChildNodeIds(); 162c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov SparseLongArray newChildrenIds = info.getChildNodeIds(); 163c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov final int oldChildCount = oldChildrenIds.size(); 164c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov for (int i = 0; i < oldChildCount; i++) { 165c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov final long oldChildId = oldChildrenIds.valueAt(i); 166c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov if (newChildrenIds.indexOfValue(oldChildId) < 0) { 167c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov clearSubTreeLocked(oldChildId); 168c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov } 169c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov } 17079311c4af8b54d3cd47ab37a120c648bfc990511Svetoslav Ganov 171c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov // Also be careful if the parent has changed since the new 172c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov // parent may be a predecessor of the old parent which will 173c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov // make the cached tree cyclic. 174c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov final long oldParentId = oldInfo.getParentNodeId(); 175c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov if (info.getParentNodeId() != oldParentId) { 176c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov clearSubTreeLocked(oldParentId); 177c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov } 17857c7fd5a43237afc5e8ef31a076e862c0c16c328Svetoslav Ganov } 179c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov 180c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov // Cache a copy since the client calls to AccessibilityNodeInfo#recycle() 181c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov // will wipe the data of the cached info. 182c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov AccessibilityNodeInfo clone = AccessibilityNodeInfo.obtain(info); 183c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov mCacheImpl.put(sourceId, clone); 1840d04e245534cf777dfaf16dce3c51553837c14ffSvetoslav Ganov } 18579311c4af8b54d3cd47ab37a120c648bfc990511Svetoslav Ganov } 18679311c4af8b54d3cd47ab37a120c648bfc990511Svetoslav Ganov } 18779311c4af8b54d3cd47ab37a120c648bfc990511Svetoslav Ganov 18879311c4af8b54d3cd47ab37a120c648bfc990511Svetoslav Ganov /** 18979311c4af8b54d3cd47ab37a120c648bfc990511Svetoslav Ganov * Clears the cache. 19079311c4af8b54d3cd47ab37a120c648bfc990511Svetoslav Ganov */ 19179311c4af8b54d3cd47ab37a120c648bfc990511Svetoslav Ganov public void clear() { 19279311c4af8b54d3cd47ab37a120c648bfc990511Svetoslav Ganov if (ENABLED) { 19357c7fd5a43237afc5e8ef31a076e862c0c16c328Svetoslav Ganov synchronized(mLock) { 19457c7fd5a43237afc5e8ef31a076e862c0c16c328Svetoslav Ganov if (DEBUG) { 195afd5fab3ab001e90269dfef37d87e69e0e261826Svetoslav Ganov Log.i(LOG_TAG, "clear()"); 196afd5fab3ab001e90269dfef37d87e69e0e261826Svetoslav Ganov } 197afd5fab3ab001e90269dfef37d87e69e0e261826Svetoslav Ganov // Recycle the nodes before clearing the cache. 198afd5fab3ab001e90269dfef37d87e69e0e261826Svetoslav Ganov final int nodeCount = mCacheImpl.size(); 199afd5fab3ab001e90269dfef37d87e69e0e261826Svetoslav Ganov for (int i = 0; i < nodeCount; i++) { 200afd5fab3ab001e90269dfef37d87e69e0e261826Svetoslav Ganov AccessibilityNodeInfo info = mCacheImpl.valueAt(i); 201afd5fab3ab001e90269dfef37d87e69e0e261826Svetoslav Ganov info.recycle(); 20257c7fd5a43237afc5e8ef31a076e862c0c16c328Svetoslav Ganov } 20357c7fd5a43237afc5e8ef31a076e862c0c16c328Svetoslav Ganov mCacheImpl.clear(); 2040d04e245534cf777dfaf16dce3c51553837c14ffSvetoslav Ganov } 20579311c4af8b54d3cd47ab37a120c648bfc990511Svetoslav Ganov } 20679311c4af8b54d3cd47ab37a120c648bfc990511Svetoslav Ganov } 2074213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov 2084213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov /** 2094213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov * Clears a subtree rooted at the node with the given id. 2104213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov * 2114213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov * @param rootNodeId The root id. 2124213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov */ 213c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov private void clearSubTreeLocked(long rootNodeId) { 2144213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov AccessibilityNodeInfo current = mCacheImpl.get(rootNodeId); 2154213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov if (current == null) { 2164213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov return; 2174213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov } 2184213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov mCacheImpl.remove(rootNodeId); 2194213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov SparseLongArray childNodeIds = current.getChildNodeIds(); 2204213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov final int childCount = childNodeIds.size(); 2214213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov for (int i = 0; i < childCount; i++) { 2224213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov final long childNodeId = childNodeIds.valueAt(i); 223c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov clearSubTreeLocked(childNodeId); 224c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov } 225c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov } 226c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov 227c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov /** 228c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov * We are enforcing the invariant for a single input focus. 229c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov * 230c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov * @param currentInputFocusId The current input focused node. 231c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov */ 232c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov private void clearSubtreeWithOldInputFocusLocked(long currentInputFocusId) { 233c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov final int cacheSize = mCacheImpl.size(); 234c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov for (int i = 0; i < cacheSize; i++) { 235c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov AccessibilityNodeInfo info = mCacheImpl.valueAt(i); 236c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov final long infoSourceId = info.getSourceNodeId(); 237c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov if (infoSourceId != currentInputFocusId && info.isFocused()) { 238c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov clearSubTreeLocked(infoSourceId); 239c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov return; 240c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov } 241c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov } 242c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov } 243c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov 244c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov /** 245c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov * We are enforcing the invariant for a single accessibility focus. 246c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov * 2474528b4e882584745f48263fa6626987e63832a2aSvetoslav Ganov * @param currentAccessibilityFocusId The current input focused node. 248c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov */ 249c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov private void clearSubtreeWithOldAccessibilityFocusLocked(long currentAccessibilityFocusId) { 250c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov final int cacheSize = mCacheImpl.size(); 251c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov for (int i = 0; i < cacheSize; i++) { 252c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov AccessibilityNodeInfo info = mCacheImpl.valueAt(i); 253c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov final long infoSourceId = info.getSourceNodeId(); 254c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov if (infoSourceId != currentAccessibilityFocusId && info.isAccessibilityFocused()) { 255c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov clearSubTreeLocked(infoSourceId); 256c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov return; 257c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov } 258c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov } 259c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov } 260c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov 261c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov /** 262c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov * Check the integrity of the cache which is it does not have nodes 263c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov * from more than one window, there are no duplicates, all nodes are 264c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov * connected, there is a single input focused node, and there is a 265c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov * single accessibility focused node. 266c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov */ 267c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov private void checkIntegrity() { 268c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov synchronized (mLock) { 269c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov // Get the root. 270c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov if (mCacheImpl.size() <= 0) { 271c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov return; 272c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov } 273c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov 274c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov // If the cache is a tree it does not matter from 275c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov // which node we start to search for the root. 276c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov AccessibilityNodeInfo root = mCacheImpl.valueAt(0); 277c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov AccessibilityNodeInfo parent = root; 278c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov while (parent != null) { 279c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov root = parent; 280c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov parent = mCacheImpl.get(parent.getParentNodeId()); 281c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov } 282c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov 283c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov // Traverse the tree and do some checks. 284c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov final int windowId = root.getWindowId(); 285c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov AccessibilityNodeInfo accessFocus = null; 286c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov AccessibilityNodeInfo inputFocus = null; 287c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov HashSet<AccessibilityNodeInfo> seen = new HashSet<AccessibilityNodeInfo>(); 288c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov Queue<AccessibilityNodeInfo> fringe = new LinkedList<AccessibilityNodeInfo>(); 289c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov fringe.add(root); 290c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov 291c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov while (!fringe.isEmpty()) { 292c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov AccessibilityNodeInfo current = fringe.poll(); 293c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov // Check for duplicates 294c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov if (!seen.add(current)) { 295c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov Log.e(LOG_TAG, "Duplicate node: " + current); 296c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov return; 297c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov } 298c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov 299c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov // Check for one accessibility focus. 300c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov if (current.isAccessibilityFocused()) { 301c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov if (accessFocus != null) { 302c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov Log.e(LOG_TAG, "Duplicate accessibility focus:" + current); 303c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov } else { 304c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov accessFocus = current; 305c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov } 306c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov } 307c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov 308c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov // Check for one input focus. 309c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov if (current.isFocused()) { 310c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov if (inputFocus != null) { 311c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov Log.e(LOG_TAG, "Duplicate input focus: " + current); 312c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov } else { 313c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov inputFocus = current; 314c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov } 315c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov } 316c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov 317c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov SparseLongArray childIds = current.getChildNodeIds(); 318c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov final int childCount = childIds.size(); 319c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov for (int i = 0; i < childCount; i++) { 320c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov final long childId = childIds.valueAt(i); 321c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov AccessibilityNodeInfo child = mCacheImpl.get(childId); 322c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov if (child != null) { 323c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov fringe.add(child); 324c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov } 325c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov } 326c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov } 327c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov 328c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov // Check for disconnected nodes or ones from another window. 329c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov final int cacheSize = mCacheImpl.size(); 330c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov for (int i = 0; i < cacheSize; i++) { 331c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov AccessibilityNodeInfo info = mCacheImpl.valueAt(i); 332c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov if (!seen.contains(info)) { 333c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov if (info.getWindowId() == windowId) { 334c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov Log.e(LOG_TAG, "Disconneced node: "); 335c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov } else { 336c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov Log.e(LOG_TAG, "Node from: " + info.getWindowId() + " not from:" 337c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov + windowId + " " + info); 338c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov } 339c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov } 340c406be9036643ebe41bafcd94fe4aa861b4e4f4fSvetoslav Ganov } 3414213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov } 3424213804541a8b05cd0587b138a2fd9a3b7fd9350Svetoslav Ganov } 34379311c4af8b54d3cd47ab37a120c648bfc990511Svetoslav Ganov} 344