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