PageRenderTime 34ms CodeModel.GetById 17ms app.highlight 13ms RepoModel.GetById 2ms app.codeStats 0ms

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