/TalkBack/src/com/google/android/marvin/talkback/Utils.java

http://eyes-free.googlecode.com/ · Java · 290 lines · 178 code · 31 blank · 81 comment · 74 complexity · 77961e5a762938672fceb15cbb861664 MD5 · raw file

  1. /*
  2. * Copyright (C) 2010 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 com.google.android.marvin.talkback;
  17. import android.content.Context;
  18. import android.content.pm.PackageInfo;
  19. import android.content.pm.PackageManager;
  20. import android.content.pm.PackageManager.NameNotFoundException;
  21. import android.content.res.Resources;
  22. import android.os.Bundle;
  23. import android.text.TextUtils;
  24. import android.util.Log;
  25. import android.view.accessibility.AccessibilityEvent;
  26. import android.view.accessibility.AccessibilityNodeInfo;
  27. import java.util.ArrayList;
  28. import java.util.HashMap;
  29. import java.util.List;
  30. import java.util.Map;
  31. /**
  32. * This class contains utility methods.
  33. *
  34. * @author svetoslavganov@google.com (Svetoslav R. Ganov)
  35. * @author alanv@google.com (Alan Viverette)
  36. */
  37. public class Utils {
  38. private static final Context CONTEXT = TalkBackService.asContext();
  39. private static final CharSequence SEPARATOR = " ";
  40. /** Invalid version code for a package. */
  41. public static final int INVALID_VERSION_CODE = -1;
  42. /**
  43. * @return The package version code or {@link #INVALID_VERSION_CODE} if the
  44. * package does not exist.
  45. */
  46. public static int getVersionCode(Context context, String packageName) {
  47. final PackageManager packageManager = context.getPackageManager();
  48. try {
  49. final PackageInfo packageInfo = packageManager.getPackageInfo(packageName, 0);
  50. return packageInfo.versionCode;
  51. } catch (NameNotFoundException e) {
  52. LogUtils.log(Utils.class, Log.ERROR, "Could not find package: %s\n%s", packageName,
  53. e.toString());
  54. return INVALID_VERSION_CODE;
  55. }
  56. }
  57. /**
  58. * @return The package version name or <code>null</code> if the package does
  59. * not exist.
  60. */
  61. public static String getVersionName(Context context, String packageName) {
  62. final PackageManager packageManager = context.getPackageManager();
  63. try {
  64. final PackageInfo packageInfo = packageManager.getPackageInfo(packageName, 0);
  65. return packageInfo.versionName;
  66. } catch (NameNotFoundException e) {
  67. LogUtils.log(Utils.class, Log.ERROR, "Could not find package: %s\n%s", packageName,
  68. e.toString());
  69. return null;
  70. }
  71. }
  72. /**
  73. /**
  74. * Returns {@code true} if the specified node is a view group or has
  75. * children.
  76. *
  77. * @param node The node to test.
  78. * @return {@code true} if the specified node is a view group or has
  79. * children.
  80. */
  81. public static boolean isViewGroup(AccessibilityEvent event, AccessibilityNodeInfo node) {
  82. if (event != null && Utils.eventMatchesClassByType(event, android.view.ViewGroup.class)) {
  83. return true;
  84. }
  85. if (node != null
  86. && ((node.getChildCount() > 0) || AccessibilityNodeInfoUtils
  87. .nodeMatchesClassByType(node, android.view.ViewGroup.class))) {
  88. return true;
  89. }
  90. return false;
  91. }
  92. /**
  93. * Determines if the generating class of an {@link AccessibilityEvent}
  94. * matches a given {@link Class} by type.
  95. *
  96. * @param event An {@link AccessibilityEvent} dispatched by the
  97. * accessibility framework.
  98. * @param clazz A {@link Class} to match by type or inherited type.
  99. * @return {@code true} if the {@link AccessibilityNodeInfo} object matches
  100. * the {@link Class} by type or inherited type, {@code false}
  101. * otherwise.
  102. */
  103. public static boolean eventMatchesClassByType(AccessibilityEvent event, Class<?> clazz) {
  104. if (event == null || clazz == null) {
  105. return false;
  106. }
  107. final ClassLoadingManager classLoader = ClassLoadingManager.getInstance();
  108. final CharSequence className = event.getClassName();
  109. final Class<?> nodeClass = classLoader.loadOrGetCachedClass(CONTEXT, className, null);
  110. if (nodeClass == null) {
  111. return false;
  112. }
  113. return clazz.isAssignableFrom(nodeClass);
  114. }
  115. /**
  116. * Gets the text of an <code>event</code> by concatenating the text members
  117. * (regardless of their priority) using space as a delimiter.
  118. *
  119. * @param context The context from which to load required resources.
  120. * @param event The event.
  121. * @return The event text.
  122. */
  123. public static CharSequence getEventText(Context context, AccessibilityEvent event) {
  124. final CharSequence contentDescription = event.getContentDescription();
  125. if (!TextUtils.isEmpty(contentDescription)) {
  126. return new StringBuilder(contentDescription);
  127. } else {
  128. return getEventAggregateText(context, event);
  129. }
  130. }
  131. /**
  132. * Gets the text of an <code>event</code> by concatenating the text members
  133. * (regardless of their priority) using space as a delimiter.
  134. *
  135. * @param context The context from which to load required resources.
  136. * @param event The event.
  137. * @return The event text.
  138. */
  139. public static CharSequence getEventAggregateText(Context context, AccessibilityEvent event) {
  140. final StringBuilder aggregator = new StringBuilder();
  141. final List<CharSequence> eventText = event.getText();
  142. for (CharSequence text : event.getText()) {
  143. aggregator.append(text);
  144. aggregator.append(SEPARATOR);
  145. }
  146. return aggregator;
  147. }
  148. /**
  149. * Loads a map of key strings to value strings from array resources.
  150. *
  151. * @param context The parent context.
  152. * @param keysResource A resource identifier for the array of key strings.
  153. * @param valuesResource A resource identifier for the array of value
  154. * strings.
  155. * @return A map of keys to values.
  156. */
  157. public static Map<String, String> loadMapFromStringArrays(Context context, int keysResource,
  158. int valuesResource) {
  159. final Resources res = context.getResources();
  160. final String[] keys = res.getStringArray(keysResource);
  161. final String[] values = res.getStringArray(valuesResource);
  162. if (keys.length != values.length) {
  163. throw new IllegalArgumentException("Array size mismatch");
  164. }
  165. final Map<String, String> map = new HashMap<String, String>();
  166. for (int i = 0; i < keys.length; i++) {
  167. map.put(keys[i], values[i]);
  168. }
  169. return map;
  170. }
  171. /**
  172. * @return If the <code>first</code> event is equal to the
  173. * <code>second</code>.
  174. */
  175. public static boolean accessibilityEventEquals(AccessibilityEvent first,
  176. AccessibilityEvent second) {
  177. if (first == null || second == null) {
  178. return false;
  179. }
  180. if (first.getEventType() != second.getEventType()) {
  181. return false;
  182. }
  183. if (first.getPackageName() == null) {
  184. if (second.getPackageName() != null) {
  185. return false;
  186. }
  187. } else if (!first.getPackageName().equals(second.getPackageName())) {
  188. return false;
  189. }
  190. if (first.getClassName() == null) {
  191. if (second.getClassName() != null) {
  192. return false;
  193. }
  194. } else if (!first.getClassName().equals(second.getClassName())) {
  195. return false;
  196. }
  197. if (!first.getText().equals(second.getText())) { // never null
  198. return false;
  199. }
  200. if (first.getContentDescription() == null) {
  201. if (second.getContentDescription() != null) {
  202. return false;
  203. }
  204. } else if (!first.getContentDescription().equals(second.getContentDescription())) {
  205. return false;
  206. }
  207. if (first.getBeforeText() == null) {
  208. if (second.getBeforeText() != null) {
  209. return false;
  210. }
  211. } else if (!first.getBeforeText().equals(second.getBeforeText())) {
  212. return false;
  213. }
  214. if (first.getParcelableData() != null) {
  215. // do not compare parcelable data it may not implement equals
  216. // correctly
  217. return false;
  218. }
  219. if (first.getAddedCount() != second.getAddedCount()) {
  220. return false;
  221. }
  222. if (first.isChecked() != second.isChecked()) {
  223. return false;
  224. }
  225. if (first.isEnabled() != second.isEnabled()) {
  226. return false;
  227. }
  228. if (first.getFromIndex() != second.getFromIndex()) {
  229. return false;
  230. }
  231. if (first.isFullScreen() != second.isFullScreen()) {
  232. return false;
  233. }
  234. if (first.getCurrentItemIndex() != second.getCurrentItemIndex()) {
  235. return false;
  236. }
  237. if (first.getItemCount() != second.getItemCount()) {
  238. return false;
  239. }
  240. if (first.isPassword() != second.isPassword()) {
  241. return false;
  242. }
  243. if (first.getRemovedCount() != second.getRemovedCount()) {
  244. return false;
  245. }
  246. if (first.getEventTime() != second.getEventTime()) {
  247. return false;
  248. }
  249. return true;
  250. }
  251. public static void addToListOrCreate(Bundle bundle, String listKey, int value) {
  252. ArrayList<Integer> list = bundle.getIntegerArrayList(listKey);
  253. if (list == null) {
  254. list = new ArrayList<Integer>();
  255. bundle.putIntegerArrayList(listKey, list);
  256. }
  257. list.add(value);
  258. }
  259. }