PageRenderTime 25ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

/core/java/android/view/accessibility/AccessibilityNodeInfoCache.java

https://bitbucket.org/Lloir/miragebase
Java | 343 lines | 224 code | 30 blank | 89 comment | 60 complexity | 2ea7a9eba2ef7c957a79b918a22c18a0 MD5 | raw file
  1. /*
  2. * Copyright (C) 2012 The Android Open Source Project
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package android.view.accessibility;
  17. import android.os.Build;
  18. import android.util.Log;
  19. import android.util.LongSparseArray;
  20. import android.util.SparseLongArray;
  21. import java.util.HashSet;
  22. import java.util.LinkedList;
  23. import java.util.Queue;
  24. /**
  25. * Simple cache for AccessibilityNodeInfos. The cache is mapping an
  26. * accessibility id to an info. The cache allows storing of
  27. * <code>null</code> values. It also tracks accessibility events
  28. * and invalidates accordingly.
  29. *
  30. * @hide
  31. */
  32. public class AccessibilityNodeInfoCache {
  33. private static final String LOG_TAG = AccessibilityNodeInfoCache.class.getSimpleName();
  34. private static final boolean ENABLED = true;
  35. private static final boolean DEBUG = false;
  36. private static final boolean CHECK_INTEGRITY = true;
  37. private final Object mLock = new Object();
  38. private final LongSparseArray<AccessibilityNodeInfo> mCacheImpl;
  39. private int mWindowId;
  40. public AccessibilityNodeInfoCache() {
  41. if (ENABLED) {
  42. mCacheImpl = new LongSparseArray<AccessibilityNodeInfo>();
  43. } else {
  44. mCacheImpl = null;
  45. }
  46. }
  47. /**
  48. * The cache keeps track of {@link AccessibilityEvent}s and invalidates
  49. * cached nodes as appropriate.
  50. *
  51. * @param event An event.
  52. */
  53. public void onAccessibilityEvent(AccessibilityEvent event) {
  54. if (ENABLED) {
  55. final int eventType = event.getEventType();
  56. switch (eventType) {
  57. case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED: {
  58. // New window so we clear the cache.
  59. mWindowId = event.getWindowId();
  60. clear();
  61. } break;
  62. case AccessibilityEvent.TYPE_VIEW_HOVER_ENTER:
  63. case AccessibilityEvent.TYPE_VIEW_HOVER_EXIT: {
  64. final int windowId = event.getWindowId();
  65. if (mWindowId != windowId) {
  66. // New window so we clear the cache.
  67. mWindowId = windowId;
  68. clear();
  69. }
  70. } break;
  71. case AccessibilityEvent.TYPE_VIEW_FOCUSED:
  72. case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED:
  73. case AccessibilityEvent.TYPE_VIEW_SELECTED:
  74. case AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED:
  75. case AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED: {
  76. // Since we prefetch the descendants of a node we
  77. // just remove the entire subtree since when the node
  78. // is fetched we will gets its descendant anyway.
  79. synchronized (mLock) {
  80. final long sourceId = event.getSourceNodeId();
  81. clearSubTreeLocked(sourceId);
  82. if (eventType == AccessibilityEvent.TYPE_VIEW_FOCUSED) {
  83. clearSubtreeWithOldInputFocusLocked(sourceId);
  84. }
  85. if (eventType == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED) {
  86. clearSubtreeWithOldAccessibilityFocusLocked(sourceId);
  87. }
  88. }
  89. } break;
  90. case AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED:
  91. case AccessibilityEvent.TYPE_VIEW_SCROLLED: {
  92. synchronized (mLock) {
  93. final long accessibilityNodeId = event.getSourceNodeId();
  94. clearSubTreeLocked(accessibilityNodeId);
  95. }
  96. } break;
  97. }
  98. if (Build.IS_DEBUGGABLE && CHECK_INTEGRITY) {
  99. checkIntegrity();
  100. }
  101. }
  102. }
  103. /**
  104. * Gets a cached {@link AccessibilityNodeInfo} given its accessibility node id.
  105. *
  106. * @param accessibilityNodeId The info accessibility node id.
  107. * @return The cached {@link AccessibilityNodeInfo} or null if such not found.
  108. */
  109. public AccessibilityNodeInfo get(long accessibilityNodeId) {
  110. if (ENABLED) {
  111. synchronized(mLock) {
  112. AccessibilityNodeInfo info = mCacheImpl.get(accessibilityNodeId);
  113. if (info != null) {
  114. // Return a copy since the client calls to AccessibilityNodeInfo#recycle()
  115. // will wipe the data of the cached info.
  116. info = AccessibilityNodeInfo.obtain(info);
  117. }
  118. if (DEBUG) {
  119. Log.i(LOG_TAG, "get(" + accessibilityNodeId + ") = " + info);
  120. }
  121. return info;
  122. }
  123. } else {
  124. return null;
  125. }
  126. }
  127. /**
  128. * Caches an {@link AccessibilityNodeInfo} given its accessibility node id.
  129. *
  130. * @param info The {@link AccessibilityNodeInfo} to cache.
  131. */
  132. public void add(AccessibilityNodeInfo info) {
  133. if (ENABLED) {
  134. synchronized(mLock) {
  135. if (DEBUG) {
  136. Log.i(LOG_TAG, "add(" + info + ")");
  137. }
  138. final long sourceId = info.getSourceNodeId();
  139. AccessibilityNodeInfo oldInfo = mCacheImpl.get(sourceId);
  140. if (oldInfo != null) {
  141. // If the added node is in the cache we have to be careful if
  142. // the new one represents a source state where some of the
  143. // children have been removed to avoid having disconnected
  144. // subtrees in the cache.
  145. SparseLongArray oldChildrenIds = oldInfo.getChildNodeIds();
  146. SparseLongArray newChildrenIds = info.getChildNodeIds();
  147. final int oldChildCount = oldChildrenIds.size();
  148. for (int i = 0; i < oldChildCount; i++) {
  149. final long oldChildId = oldChildrenIds.valueAt(i);
  150. if (newChildrenIds.indexOfValue(oldChildId) < 0) {
  151. clearSubTreeLocked(oldChildId);
  152. }
  153. }
  154. // Also be careful if the parent has changed since the new
  155. // parent may be a predecessor of the old parent which will
  156. // make the cached tree cyclic.
  157. final long oldParentId = oldInfo.getParentNodeId();
  158. if (info.getParentNodeId() != oldParentId) {
  159. clearSubTreeLocked(oldParentId);
  160. }
  161. }
  162. // Cache a copy since the client calls to AccessibilityNodeInfo#recycle()
  163. // will wipe the data of the cached info.
  164. AccessibilityNodeInfo clone = AccessibilityNodeInfo.obtain(info);
  165. mCacheImpl.put(sourceId, clone);
  166. }
  167. }
  168. }
  169. /**
  170. * Clears the cache.
  171. */
  172. public void clear() {
  173. if (ENABLED) {
  174. synchronized(mLock) {
  175. if (DEBUG) {
  176. Log.i(LOG_TAG, "clear()");
  177. }
  178. // Recycle the nodes before clearing the cache.
  179. final int nodeCount = mCacheImpl.size();
  180. for (int i = 0; i < nodeCount; i++) {
  181. AccessibilityNodeInfo info = mCacheImpl.valueAt(i);
  182. info.recycle();
  183. }
  184. mCacheImpl.clear();
  185. }
  186. }
  187. }
  188. /**
  189. * Clears a subtree rooted at the node with the given id.
  190. *
  191. * @param rootNodeId The root id.
  192. */
  193. private void clearSubTreeLocked(long rootNodeId) {
  194. AccessibilityNodeInfo current = mCacheImpl.get(rootNodeId);
  195. if (current == null) {
  196. return;
  197. }
  198. mCacheImpl.remove(rootNodeId);
  199. SparseLongArray childNodeIds = current.getChildNodeIds();
  200. final int childCount = childNodeIds.size();
  201. for (int i = 0; i < childCount; i++) {
  202. final long childNodeId = childNodeIds.valueAt(i);
  203. clearSubTreeLocked(childNodeId);
  204. }
  205. }
  206. /**
  207. * We are enforcing the invariant for a single input focus.
  208. *
  209. * @param currentInputFocusId The current input focused node.
  210. */
  211. private void clearSubtreeWithOldInputFocusLocked(long currentInputFocusId) {
  212. final int cacheSize = mCacheImpl.size();
  213. for (int i = 0; i < cacheSize; i++) {
  214. AccessibilityNodeInfo info = mCacheImpl.valueAt(i);
  215. final long infoSourceId = info.getSourceNodeId();
  216. if (infoSourceId != currentInputFocusId && info.isFocused()) {
  217. clearSubTreeLocked(infoSourceId);
  218. return;
  219. }
  220. }
  221. }
  222. /**
  223. * We are enforcing the invariant for a single accessibility focus.
  224. *
  225. * @param currentAccessibilityFocusId The current input focused node.
  226. */
  227. private void clearSubtreeWithOldAccessibilityFocusLocked(long currentAccessibilityFocusId) {
  228. final int cacheSize = mCacheImpl.size();
  229. for (int i = 0; i < cacheSize; i++) {
  230. AccessibilityNodeInfo info = mCacheImpl.valueAt(i);
  231. final long infoSourceId = info.getSourceNodeId();
  232. if (infoSourceId != currentAccessibilityFocusId && info.isAccessibilityFocused()) {
  233. clearSubTreeLocked(infoSourceId);
  234. return;
  235. }
  236. }
  237. }
  238. /**
  239. * Check the integrity of the cache which is it does not have nodes
  240. * from more than one window, there are no duplicates, all nodes are
  241. * connected, there is a single input focused node, and there is a
  242. * single accessibility focused node.
  243. */
  244. private void checkIntegrity() {
  245. synchronized (mLock) {
  246. // Get the root.
  247. if (mCacheImpl.size() <= 0) {
  248. return;
  249. }
  250. // If the cache is a tree it does not matter from
  251. // which node we start to search for the root.
  252. AccessibilityNodeInfo root = mCacheImpl.valueAt(0);
  253. AccessibilityNodeInfo parent = root;
  254. while (parent != null) {
  255. root = parent;
  256. parent = mCacheImpl.get(parent.getParentNodeId());
  257. }
  258. // Traverse the tree and do some checks.
  259. final int windowId = root.getWindowId();
  260. AccessibilityNodeInfo accessFocus = null;
  261. AccessibilityNodeInfo inputFocus = null;
  262. HashSet<AccessibilityNodeInfo> seen = new HashSet<AccessibilityNodeInfo>();
  263. Queue<AccessibilityNodeInfo> fringe = new LinkedList<AccessibilityNodeInfo>();
  264. fringe.add(root);
  265. while (!fringe.isEmpty()) {
  266. AccessibilityNodeInfo current = fringe.poll();
  267. // Check for duplicates
  268. if (!seen.add(current)) {
  269. Log.e(LOG_TAG, "Duplicate node: " + current);
  270. return;
  271. }
  272. // Check for one accessibility focus.
  273. if (current.isAccessibilityFocused()) {
  274. if (accessFocus != null) {
  275. Log.e(LOG_TAG, "Duplicate accessibility focus:" + current);
  276. } else {
  277. accessFocus = current;
  278. }
  279. }
  280. // Check for one input focus.
  281. if (current.isFocused()) {
  282. if (inputFocus != null) {
  283. Log.e(LOG_TAG, "Duplicate input focus: " + current);
  284. } else {
  285. inputFocus = current;
  286. }
  287. }
  288. SparseLongArray childIds = current.getChildNodeIds();
  289. final int childCount = childIds.size();
  290. for (int i = 0; i < childCount; i++) {
  291. final long childId = childIds.valueAt(i);
  292. AccessibilityNodeInfo child = mCacheImpl.get(childId);
  293. if (child != null) {
  294. fringe.add(child);
  295. }
  296. }
  297. }
  298. // Check for disconnected nodes or ones from another window.
  299. final int cacheSize = mCacheImpl.size();
  300. for (int i = 0; i < cacheSize; i++) {
  301. AccessibilityNodeInfo info = mCacheImpl.valueAt(i);
  302. if (!seen.contains(info)) {
  303. if (info.getWindowId() == windowId) {
  304. Log.e(LOG_TAG, "Disconneced node: ");
  305. } else {
  306. Log.e(LOG_TAG, "Node from: " + info.getWindowId() + " not from:"
  307. + windowId + " " + info);
  308. }
  309. }
  310. }
  311. }
  312. }
  313. }