/talkback_preics/src/com/google/android/marvin/talkback/SpeechRuleProcessor.java

http://eyes-free.googlecode.com/ · Java · 303 lines · 148 code · 24 blank · 131 comment · 17 complexity · 8f693f38bab57ab049557e1dd244d662 MD5 · raw file

  1. /*
  2. * Copyright (C) 2009 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 org.w3c.dom.Document;
  18. import org.xml.sax.SAXException;
  19. import android.content.Context;
  20. import android.util.Log;
  21. import android.view.accessibility.AccessibilityEvent;
  22. import java.io.File;
  23. import java.io.FileInputStream;
  24. import java.io.FileNotFoundException;
  25. import java.io.IOException;
  26. import java.io.InputStream;
  27. import java.util.ArrayList;
  28. import java.util.HashMap;
  29. import java.util.Iterator;
  30. import java.util.Map;
  31. import javax.xml.parsers.DocumentBuilder;
  32. import javax.xml.parsers.DocumentBuilderFactory;
  33. import javax.xml.parsers.ParserConfigurationException;
  34. /**
  35. * This class is a {@link SpeechRule} processor responsible for loading from
  36. * speech strategy XML files sets of {@link SpeechRule}s used for processing
  37. * {@link AccessibilityEvent}s such that utterances are generated. Speech
  38. * strategies can be registered for handling events from a given package or
  39. * their rules to be appended to the default speech rules which are examined as
  40. * fall-back if no package specific ones have matched the event. The rules are
  41. * processed in the order they are defined and in case a rule is successfully
  42. * applied i.e. an utterance is formatted, processing stops. In other words, the
  43. * first applicable speech rule wins.
  44. *
  45. * @author svetoslavganov@google.com (Svetoslav Ganov)
  46. */
  47. public class SpeechRuleProcessor {
  48. /**
  49. * Constant used for storing all speech rules that either do not
  50. * define a filter package or have custom filters.
  51. */
  52. private static final String UNDEFINED_PACKAGE_NAME = "undefined_package_name";
  53. /**
  54. * Tag for logging.
  55. */
  56. private static final String LOG_TAG = "SpeechRuleProcessor";
  57. /**
  58. * Mutex lock for accessing the speech rules.
  59. */
  60. private final Object mLock = new Object();
  61. /**
  62. * Context for accessing resources.
  63. */
  64. private final Context mContext;
  65. /**
  66. * Mapping from package name to speech rules for that package.
  67. */
  68. private HashMap<String, ArrayList<SpeechRule>> mPackageNameToSpeechRulesMap = new HashMap<String, ArrayList<SpeechRule>>();
  69. /**
  70. * Creates a new instance.
  71. */
  72. SpeechRuleProcessor(Context context) {
  73. mContext = context;
  74. }
  75. /**
  76. * Loads a speech strategy from a given <code>resourceId</code> to handle
  77. * events from all packages and use the resources of the TalkBack context.
  78. */
  79. void addSpeechStrategy(int resourceId) {
  80. addSpeechStrategy(mContext, null, null, resourceId);
  81. }
  82. /**
  83. * Removes the speech rules defined by a speech strategy with the
  84. * given <code>resourceId</code>
  85. */
  86. void removeSpeechStrategy(int resourceId) {
  87. String speechStrategy = mContext.getResources().getResourceName(resourceId);
  88. removeSpeechStrategy(speechStrategy);
  89. }
  90. /**
  91. * Loads a speech strategy from a given <code>file</code>.
  92. */
  93. void addSpeechStrategy(File file) {
  94. String speechStrategy = file.toURI().toString();
  95. try {
  96. InputStream inputStream = new FileInputStream(file);
  97. addSpeechStrategy(mContext, speechStrategy, null, null, inputStream);
  98. } catch (FileNotFoundException fnfe) {
  99. Log.e(LOG_TAG, "Error loading speech strategy: " + speechStrategy, fnfe);
  100. }
  101. }
  102. /**
  103. * Removes the speech rules defined by a speech strategy in the
  104. * given <code>file</code>
  105. */
  106. void removeSpeechStrategy(File file) {
  107. String speechStrategy = file.toURI().toString();
  108. removeSpeechStrategy(speechStrategy);
  109. }
  110. /**
  111. * Loads a speech strategy from a given <code>resourceId</code> to handle
  112. * events from the specified <code>targetPackage</code> and use the resources
  113. * from a given <code>context</code>. If the target package is
  114. * <code>null</code> the rules of the loaded speech strategy are appended to
  115. * the default speech rules. While for loading of resources is used the provided
  116. * context instance, for loading plug-in classes (custom Filters and Formatters)
  117. * the <code>publicSourceDir</code> which specifies the location of the APK that
  118. * defines them is used to enabled using the TalkBack {@link ClassLoader}.
  119. */
  120. void addSpeechStrategy(Context context, String publicSourceDir, String targetPackage,
  121. int resourceId) {
  122. String speechStrategy = context.getResources().getResourceName(resourceId);
  123. InputStream inputStream = context.getResources().openRawResource(resourceId);
  124. addSpeechStrategy(context, speechStrategy, targetPackage, publicSourceDir, inputStream);
  125. }
  126. /**
  127. * Loads a <code>speechStrategy</code> from a given <code>inputStream</code> to handle
  128. * events from the specified <code>targetPackage</code> and use the resources
  129. * from a given <code>context</code>. If the target package is
  130. * <code>null</code> the rules of the loaded speech strategy are appended to
  131. * the default speech rules. While for loading of resources is used the provided
  132. * context instance, for loading plug-in classes (custom Filters and Formatters)
  133. * from the <code>publicSourceDir</code> (specifies the location of the APK that
  134. * defines them) is used the TalkBack {@link ClassLoader}.
  135. */
  136. private void addSpeechStrategy(Context context, String speechStrategy, String targetPackage,
  137. String publicSourceDir, InputStream inputStream) {
  138. Document document = parseSpeechStrategy(context, inputStream);
  139. ArrayList<SpeechRule> speechRules = SpeechRule.createSpeechRules(context, speechStrategy,
  140. targetPackage, publicSourceDir, document);
  141. synchronized (mLock) {
  142. for (int i = 0, count = speechRules.size(); i < count; i++) {
  143. SpeechRule speechRule = speechRules.get(i);
  144. addSpeechRuleLocked(speechRule);
  145. }
  146. }
  147. Log.d(LOG_TAG, speechRules.size() + " speech rules appended form: " + speechStrategy);
  148. }
  149. /**
  150. * Removes the speech rules defined by a given <code>speechStrategy</code>
  151. */
  152. private void removeSpeechStrategy(String speechStrategy) {
  153. int removedCount = 0;
  154. for (String key : mPackageNameToSpeechRulesMap.keySet()) {
  155. ArrayList<SpeechRule> speechRules = mPackageNameToSpeechRulesMap.get(key);
  156. Iterator<SpeechRule> speechRulesIterator = speechRules.iterator();
  157. while (speechRulesIterator.hasNext()) {
  158. SpeechRule speechRule = speechRulesIterator.next();
  159. if (speechStrategy.equals(speechRule.getSpeechStrategy())) {
  160. speechRulesIterator.remove();
  161. removedCount++;
  162. }
  163. }
  164. }
  165. Log.d(LOG_TAG, removedCount + " speech rules removed from: " + speechStrategy);
  166. }
  167. /**
  168. * Adds a <code>speechRule</code>.
  169. */
  170. private boolean addSpeechRuleLocked(SpeechRule speechRule) {
  171. String packageName = speechRule.getPackageName();
  172. ArrayList<SpeechRule> packageSpeechRules = mPackageNameToSpeechRulesMap.get(packageName);
  173. if (packageSpeechRules == null) {
  174. packageSpeechRules = new ArrayList<SpeechRule>();
  175. mPackageNameToSpeechRulesMap.put(packageName, packageSpeechRules);
  176. }
  177. return packageSpeechRules.add(speechRule);
  178. }
  179. /**
  180. * Removes the speech rules for a given <code>packageName</code>.
  181. *
  182. * @return True if speech rules were removed.
  183. */
  184. boolean removeSpeechRulesForPackage(String packageName) {
  185. synchronized (mLock) {
  186. ArrayList<SpeechRule> speechRules = mPackageNameToSpeechRulesMap.remove(packageName);
  187. if (speechRules != null) {
  188. Log.i(LOG_TAG, speechRules.size() + " speech rules removed");
  189. }
  190. return (speechRules != null);
  191. }
  192. }
  193. /**
  194. * Processes an <code>event</code> utilizing the optional
  195. * <code>activity</code> name by sequentially trying to apply all
  196. * {@link SpeechRule}s in the order they are defined for the package source
  197. * of the event. If no package specific rules exist the default speech rules
  198. * are examined in the same manner. If a rule is successfully applied the
  199. * result is used to populate an <code>utterance</code>. In other words, the
  200. * first matching rule wins. Optionally <code>filterArguments</code> and
  201. * <code>formatterArguments</code> can be provided.
  202. *
  203. * @return True if the event was processed false otherwise.
  204. */
  205. boolean processEvent(AccessibilityEvent event, String activity, Utterance utterance,
  206. Map<Object, Object> filterArguments, Map<Object, Object> formatterArguments) {
  207. synchronized (mLock) {
  208. // try package specific speech rules first
  209. ArrayList<SpeechRule> speechRules = mPackageNameToSpeechRulesMap
  210. .get(event.getPackageName());
  211. if (speechRules != null
  212. && processEvent(speechRules, event, activity, utterance, filterArguments,
  213. formatterArguments)) {
  214. return true;
  215. }
  216. // package specific rule not found - try undefined package ones
  217. speechRules = mPackageNameToSpeechRulesMap.get(UNDEFINED_PACKAGE_NAME);
  218. if (speechRules != null
  219. && processEvent(speechRules, event, activity, utterance, filterArguments,
  220. formatterArguments)) {
  221. return true;
  222. }
  223. }
  224. return false;
  225. }
  226. /**
  227. * Processes an <code>event</code> utilizing the optional
  228. * <code>activity</code> name by sequentially trying to apply all
  229. * <code>speechRules</code> in the order they are defined for the package
  230. * source of the event. If no package specific rules exist the default
  231. * speech rules are examined in the same manner. If a rule is successfully
  232. * applied the result is used to populate an <code>utterance</code>. In
  233. * other words, the first matching rule wins. Optionally
  234. * <code>filterArguments</code> and <code>formatterArguments</code> can
  235. * be provided.
  236. *
  237. * @return True if the event was processed false otherwise.
  238. */
  239. private boolean processEvent(ArrayList<SpeechRule> speechRules, AccessibilityEvent event,
  240. String activity, Utterance utterance, Map<Object, Object> filterArguments,
  241. Map<Object, Object> formatterArguments) {
  242. for (int i = 0, count = speechRules.size(); i < count; i++) {
  243. // make sure we never crash because of a bug in speech rules
  244. SpeechRule speechRule = speechRules.get(i);
  245. try {
  246. if (speechRule.apply(event, activity, utterance, filterArguments,
  247. formatterArguments)) {
  248. return true;
  249. }
  250. } catch (Throwable t) {
  251. Log.e(LOG_TAG, "Error while processing rule:\n" + speechRule.asXmlString(), t);
  252. }
  253. }
  254. return false;
  255. }
  256. /**
  257. * Parses a speech strategy XML file specified by <code>resourceId</code>
  258. * and returns a <code>document</code>. If an error occurs during the
  259. * parsing, it is logged and <code>null</code> is returned.
  260. *
  261. * @param context A {@link Context} instance.
  262. * @param inputStream An {@link InputStream} to the speech strategy XML file.
  263. * @return The parsed {@link Document} or <code>null</code> if an error
  264. * occurred.
  265. */
  266. private Document parseSpeechStrategy(Context context, InputStream inputStream) {
  267. try {
  268. DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
  269. return builder.parse(inputStream);
  270. } catch (IOException ioe) {
  271. Log.e(LOG_TAG, "Could not open speechstrategy xml file", ioe);
  272. } catch (ParserConfigurationException pce) {
  273. Log.e(LOG_TAG, "Could not open speechstrategy xml file", pce);
  274. } catch (SAXException se) {
  275. Log.e(LOG_TAG, "Could not open speechstrategy xml file", se);
  276. }
  277. return null;
  278. }
  279. }