PageRenderTime 92ms CodeModel.GetById 21ms app.highlight 60ms RepoModel.GetById 1ms app.codeStats 1ms

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

http://eyes-free.googlecode.com/
Java | 1449 lines | 870 code | 163 blank | 416 comment | 263 complexity | 8c616b2939507a450b4618cdbd6a81e2 MD5 | raw file

Large files files are truncated, but you can click here to view the full 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 dalvik.system.DexFile;
  20
  21import org.w3c.dom.Document;
  22import org.w3c.dom.Node;
  23import org.w3c.dom.NodeList;
  24import org.w3c.dom.Text;
  25
  26import android.content.Context;
  27import android.os.Build;
  28import android.text.TextUtils;
  29import android.util.Log;
  30import android.view.accessibility.AccessibilityEvent;
  31
  32import java.io.File;
  33import java.io.IOException;
  34import java.util.ArrayList;
  35import java.util.HashMap;
  36import java.util.List;
  37import java.util.Map;
  38import java.util.MissingFormatArgumentException;
  39import java.util.regex.Matcher;
  40import java.util.regex.Pattern;
  41
  42/**
  43 * This class represents a speech rule for an {@link AccessibilityEvent}. The
  44 * rule has a filter which determines if the rule applies to a given event. If
  45 * the rule applies to an event the formatter for that rule is used to generate
  46 * a formatted utterance.
  47 * 
  48 * @author svetoslavganov@google.com (Svetoslav Ganov)
  49 */
  50public class SpeechRule {
  51
  52    /**
  53     * log tag to associate log messages with this class
  54     */
  55    private static final String LOG_TAG = "SpeechRule";
  56
  57    // string constants
  58
  59    private static final String SPACE = " ";
  60
  61    private static final String COLON = ":";
  62
  63    private static final String EMPTY_STRING = "";
  64
  65    // node names
  66
  67    private static final String NODE_NAME_METADATA = "metadata";
  68
  69    private static final String NODE_NAME_FILTER = "filter";
  70
  71    private static final String NODE_NAME_FORMATTER = "formatter";
  72
  73    private static final String NODE_NAME_CUSTOM = "custom";
  74
  75    // properties
  76
  77    private static final String PROPERTY_EVENT_TYPE = "eventType";
  78
  79    private static final String PROPERTY_PACKAGE_NAME = "packageName";
  80
  81    private static final String PROPERTY_CLASS_NAME = "className";
  82
  83    private static final String PROPERTY_TEXT = "text";
  84
  85    private static final String PROPERTY_BEFORE_TEXT = "beforeText";
  86
  87    private static final String PROPERTY_CONTENT_DESCRIPTION = "contentDescription";
  88
  89    private static final String PROPERTY_EVENT_TIME = "eventTime";
  90
  91    private static final String PROPERTY_ITEM_COUNT = "itemCount";
  92
  93    private static final String PROPERTY_CURRENT_ITEM_INDEX = "currentItemIndex";
  94
  95    private static final String PROPERTY_FROM_INDEX = "fromIndex";
  96
  97    private static final String PROPERTY_CHECKED = "checked";
  98
  99    private static final String PROPERTY_ENABLED = "enabled";
 100
 101    private static final String PROPERTY_FULL_SCREEN = "fullScreen";
 102
 103    private static final String PROPERTY_PASSWORD = "password";
 104
 105    private static final String PROPERTY_ADDED_COUNT = "addedCount";
 106
 107    private static final String PROPERTY_REMOVED_COUNT = "removedCount";
 108
 109    private static final String PROPERTY_QUEUING = "queuing";
 110
 111    private static final String PROPERTY_ACTIVITY = "activity";
 112
 113    private static final String PROPERTY_VERSION_CODE = "versionCode";
 114
 115    private static final String PROPERTY_VERSION_NAME = "versionName";
 116
 117    private static final String PROPERTY_PLATFORM_RELEASE = "platformRelease";
 118
 119    private static final String PROPERTY_PLATFORM_SDK = "platformSdk";
 120
 121    private static final String PROPERTY_SYSTEM_FEATURE = "systemFeature";
 122
 123    /**
 124     * Constant used for storing all speech rules that either do not
 125     * define a filter package or have custom filters.
 126     */
 127    private static final String UNDEFINED_PACKAGE_NAME = "undefined_package_name";
 128
 129    /**
 130     * reusable builder to avoid object creation
 131     */
 132    private static final StringBuilder sTempBuilder = new StringBuilder();
 133
 134    /**
 135     * standard, reusable string formatter for populating utterance template
 136     */
 137    private static final java.util.Formatter sStringFormatter = new java.util.Formatter();
 138
 139    /**
 140     * Mapping from event type name to its type.
 141     */
 142    private static final HashMap<String, Integer> sEventTypeNameToValueMap =
 143            new HashMap<String, Integer>();
 144    static {
 145        sEventTypeNameToValueMap.put("TYPE_VIEW_CLICKED", 1);
 146        sEventTypeNameToValueMap.put("TYPE_VIEW_LONG_CLICKED", 2);
 147        sEventTypeNameToValueMap.put("TYPE_VIEW_SELECTED", 4);
 148        sEventTypeNameToValueMap.put("TYPE_VIEW_FOCUSED", 8);
 149        sEventTypeNameToValueMap.put("TYPE_VIEW_TEXT_CHANGED", 16);
 150        sEventTypeNameToValueMap.put("TYPE_WINDOW_STATE_CHANGED", 32);
 151        sEventTypeNameToValueMap.put("TYPE_NOTIFICATION_STATE_CHANGED", 64);
 152        sEventTypeNameToValueMap.put("TYPE_VIEW_HOVER_ENTER", 128);
 153        sEventTypeNameToValueMap.put("TYPE_VIEW_HOVER_EXIT", 256);
 154    }
 155
 156    /**
 157     * Mapping from feature type name to its value. Note: Use strings to avoid
 158     * dependency on higher SDK version.
 159     */
 160    private static final HashMap<String, String> sFeatureTypeNameToValueMap = new HashMap<String, String>();
 161    static {
 162        sFeatureTypeNameToValueMap.put("FEATURE_BLUETOOTH", "android.hardware.bluetooth");
 163        sFeatureTypeNameToValueMap.put("FEATURE_CAMERA", "android.hardware.camera.flash");
 164        sFeatureTypeNameToValueMap.put("FEATURE_CAMERA_AUTOFOCUS",
 165                "android.hardware.camera.autofocus");
 166        sFeatureTypeNameToValueMap.put("FEATURE_CAMERA_FLASH", "android.hardware.camera.flash");
 167        sFeatureTypeNameToValueMap.put("FEATURE_LIVE_WALLPAPER", "android.software.live_wallpaper");
 168        sFeatureTypeNameToValueMap.put("FEATURE_LOCATION", "android.hardware.location");
 169        sFeatureTypeNameToValueMap.put("FEATURE_LOCATION_GPS", "android.hardware.location.gps");
 170        sFeatureTypeNameToValueMap.put("FEATURE_LOCATION_NETWORK",
 171                "android.hardware.location.network");
 172        sFeatureTypeNameToValueMap.put("FEATURE_MICROPHONE", "android.hardware.microphone");
 173        sFeatureTypeNameToValueMap.put("FEATURE_SENSOR_ACCELEROMETER",
 174                "android.hardware.sensor.accelerometer");
 175        sFeatureTypeNameToValueMap.put("FEATURE_SENSOR_COMPASS", "android.hardware.sensor.compass");
 176        sFeatureTypeNameToValueMap.put("FEATURE_SENSOR_LIGHT", "android.hardware.sensor.light");
 177        sFeatureTypeNameToValueMap.put("FEATURE_SENSOR_PROXIMITY",
 178                "android.hardware.sensor.proximity");
 179        sFeatureTypeNameToValueMap.put("FEATURE_TELEPHONY", "android.hardware.telephony");
 180        sFeatureTypeNameToValueMap.put("FEATURE_TELEPHONY_CDMA", "android.hardware.telephony.cdma");
 181        sFeatureTypeNameToValueMap.put("FEATURE_TELEPHONY_GSM", "android.hardware.telephony.gsm");
 182        sFeatureTypeNameToValueMap.put("FEATURE_TOUCHSCREEN", "android.hardware.touchscreen");
 183        sFeatureTypeNameToValueMap.put("FEATURE_TOUCHSCREEN_MULTITOUCH",
 184                "android.hardware.touchscreen.multitouch");
 185        sFeatureTypeNameToValueMap.put("FEATURE_TOUCHSCREEN_MULTITOUCH_DISTINCT",
 186                "android.hardware.touchscreen.multitouch.distinct");
 187    }
 188
 189    /**
 190     * Mapping from queue mode names to queue modes.
 191     */
 192    private static final HashMap<String, Integer> sQueueModeNameToQueueModeMap = new HashMap<String, Integer>();
 193    static {
 194        sQueueModeNameToQueueModeMap.put("INTERRUPT", 0);
 195        sQueueModeNameToQueueModeMap.put("QUEUE", 1);
 196        sQueueModeNameToQueueModeMap.put("COMPUTE_FROM_EVENT_CONTEXT", 2);
 197        sQueueModeNameToQueueModeMap.put("UNINTERRUPTIBLE", 3);
 198    }
 199
 200    /**
 201     * Meta-data of how the utterance should be spoken. It is a key value
 202     * mapping to enable extending the meta-data specification.
 203     */
 204    private final HashMap<String, Object> mMetadata = new HashMap<String, Object>();
 205
 206    /**
 207     * Mapping from property name to property matcher.
 208     */
 209    private final HashMap<String, PropertyMatcher> mPropertyMatchers = new HashMap<String, PropertyMatcher>();
 210
 211    /**
 212     * filter for matching an event
 213     */
 214    private final Filter mFilter;
 215
 216    /**
 217     * formatter for building an utterance
 218     */
 219    private final Formatter mFormatter;
 220
 221    // the index of this rule
 222    private final int mRuleIndex;
 223
 224    /**
 225     * The context in which this speech rule operates.
 226     */
 227    private final Context mContext;    
 228
 229    /**
 230     * The speech strategy defined the rule.
 231     * <p>
 232     * Note: This is either a resource name or a URI.
 233     * </p>
 234     */
 235    private final String mSpeechStrategy;
 236
 237    /**
 238     * The package targeted by this rule.
 239     */
 240    private String mPackageName;
 241
 242    /**
 243     * The location of the APK from which to load classes. This is required
 244     * since we need to load the plug-in classes through the TalkBack class
 245     * loader.
 246     */
 247    private final String mPublicSourceDir;
 248
 249    /**
 250     * The DOM node that defines this speech rule.
 251     */
 252    private final Node mNode;
 253
 254    /**
 255     * Creates a new speech rule that loads resources form the given
 256     * <code>context</code> and classes from the APK specified by the
 257     * <code>publicSourceDird</code>. The rule is defined in the
 258     * <code>speechStrategy</code> and is targeted to the
 259     * <code>packageName</code>. If the former is not provided resources are
 260     * loaded form the TalkBack context. If the latter is not provided class are
 261     * loaded from the current TalkBack APK. The speech rule content is loaded
 262     * from a DOM <code>node</code> and a <code>ruleIndex</code> is assigned to
 263     * the rule.
 264     * 
 265     * @throws IllegalStateException If the tries to load custom
 266     *             filter/formatter while <code>customInstancesSupported</code>
 267     *             is false;
 268     */
 269    private SpeechRule(Context context, String speechStrategy, String packageName,
 270            String publicSourceDird, Node node, int ruleIndex) {
 271        mContext = context;
 272        mSpeechStrategy = speechStrategy;
 273        mPackageName = packageName != null ? packageName : UNDEFINED_PACKAGE_NAME;
 274        mPublicSourceDir = publicSourceDird;
 275        mNode = node;
 276
 277        Filter filter = null;
 278        Formatter formatter = null;
 279
 280        // avoid call to Document#getNodesByTagName, it traverses the entire
 281        // document
 282        NodeList children = node.getChildNodes();
 283        for (int i = 0, count = children.getLength(); i < count; i++) {
 284            Node child = children.item(i);
 285
 286            if (child.getNodeType() != Node.ELEMENT_NODE) {
 287                continue;
 288            }
 289
 290            String nodeName = getUnqualifiedNodeName(child);
 291            if (NODE_NAME_METADATA.equalsIgnoreCase(nodeName)) {
 292                populateMetadata(child);
 293            } else if (NODE_NAME_FILTER.equals(nodeName)) {
 294                filter = createFilter(child);
 295            } else if (NODE_NAME_FORMATTER.equals(nodeName)) {
 296                formatter = createFormatter(child);
 297            }
 298        }
 299
 300        mFilter = filter;
 301        mFormatter = formatter;
 302        mRuleIndex = ruleIndex;
 303    }
 304
 305    /**
 306     * @return The XML representation of this rule.
 307     */
 308    public String asXmlString() {
 309        StringBuilder stringBuilder = new StringBuilder();
 310        asXmlStringRecursive(mNode, stringBuilder);
 311        return stringBuilder.toString();
 312    }
 313
 314    /**
 315     * @return The speech strategy that defined this rule.
 316     */
 317    public String getSpeechStrategy() {
 318        return mSpeechStrategy;
 319    }
 320
 321    /**
 322     * @return The package targeted by this rule.
 323     */
 324    public String getPackageName() {
 325        return mPackageName;
 326    }
 327
 328    /**
 329     * Returns the speech rule as an XML string.
 330     * <p>
 331     *   The implementation is simplified and utilizes knowledge about
 332     *   speech rule syntax. Node attributes are not processed, empty
 333     *   nodes are not avoided, and all nodes are assumed to be either
 334     *   Element or Text. This is required since we build against 1.6
 335     *   which does not support Node.getTextContent() API.
 336     * </p>
 337     * @param node The currently processed node.
 338     * @param stringBuilder The builder which accumulates the XML string.
 339     */
 340    private void asXmlStringRecursive(Node node, StringBuilder stringBuilder) {
 341        int nodeType = node.getNodeType();
 342        switch (nodeType) {
 343            case Node.ELEMENT_NODE:
 344                stringBuilder.append("<");
 345                stringBuilder.append(node.getNodeName());
 346                stringBuilder.append(">");
 347                NodeList childNodes = node.getChildNodes();
 348                for (int i = 0, count = childNodes.getLength(); i < count; i++) {
 349                    Node childNode = childNodes.item(i);
 350                    asXmlStringRecursive(childNode, stringBuilder);
 351                }
 352                stringBuilder.append("</");
 353                stringBuilder.append(node.getNodeName());
 354                stringBuilder.append(">");
 355                break;
 356            case Node.TEXT_NODE:
 357                Text text = (Text) node;
 358                stringBuilder.append(text.getData());
 359                break;
 360        }
 361    }
 362
 363    /**
 364     * @return This rule's filter.
 365     */
 366    public Filter getFilter() {
 367        return mFilter;
 368    }
 369
 370    /**
 371     * @return This rule's formatter.
 372     */
 373    public Formatter getFormatter() {
 374        return mFormatter;
 375    }
 376
 377    /**
 378     * Applies this rule to an {@link AccessibilityEvent}. If the event is
 379     * accepted by the {@link Filter} the rule's {@link Formatter} is used to
 380     * populate a formatted {@link Utterance}.
 381     * 
 382     * @param event The event to which to apply the rule.
 383     * @param activity The class name of the current activity.
 384     * @param utterance Utterance to populate if the event is accepted.
 385     * @param filterArgs Addition arguments to the filter.
 386     * @param formatterArgs Addition arguments to the formatter.
 387     * @return True if the event matched the filter, false otherwise.
 388     */
 389    public boolean apply(AccessibilityEvent event, String activity, Utterance utterance,
 390            Map<Object, Object> filterArgs, Map<Object, Object> formatterArgs) {
 391        // no filter matches all events
 392        // no formatter drops the event on the floor
 393        boolean matched = (mFilter == null
 394                || mFilter.accept(event, mContext, activity, filterArgs));
 395        boolean hasFormatter = (mFormatter != null);
 396
 397        if (matched) {
 398            utterance.getMetadata().putAll(mMetadata);
 399            if (hasFormatter) {
 400                mFormatter.format(event, mContext, utterance, formatterArgs);
 401            }
 402        }
 403
 404        return matched;
 405    }
 406
 407    /**
 408     * Populates the meta-data which determines how an {@link Utterance}
 409     * formatted by this rule should be announced.
 410     * 
 411     * @param node The meta-data node to parse.
 412     */
 413    private void populateMetadata(Node node) {
 414        NodeList metadata = node.getChildNodes();
 415
 416        for (int i = 0, count = metadata.getLength(); i < count; i++) {
 417            Node child = metadata.item(i);
 418
 419            if (child.getNodeType() != Node.ELEMENT_NODE) {
 420                continue;
 421            }
 422
 423            String unqualifiedName = getUnqualifiedNodeName(child);
 424            String textContent = getTextContent(child);
 425            Object parsedValue = null;
 426
 427            if (PROPERTY_QUEUING.equals(unqualifiedName)) {
 428                parsedValue = sQueueModeNameToQueueModeMap.get(textContent);
 429            } else {
 430                parsedValue = parsePropertyValue(unqualifiedName, textContent);
 431            }
 432
 433            mMetadata.put(unqualifiedName, parsedValue);
 434        }
 435    }
 436
 437    /**
 438     * Parses a property according to its expected type. Parsing failures are
 439     * logged and null is returned.
 440     * 
 441     * @param name The property name.
 442     * @param value The property value.
 443     * @return The parsed value or null if parse error occurs.
 444     */
 445    static Object parsePropertyValue(String name, String value) {
 446        if (PROPERTY_EVENT_TYPE.equals(name)) {
 447            return sEventTypeNameToValueMap.get(value);
 448        } else if (PROPERTY_SYSTEM_FEATURE.equals(name)) {
 449            return sFeatureTypeNameToValueMap.get(value);
 450        }
 451        if (isIntegerProperty(name)) {
 452            try {
 453                return Integer.parseInt(value);
 454            } catch (NumberFormatException nfe) {
 455                Log.w(LOG_TAG, "Property: '" + name + "' not interger. Ignoring!");
 456                return null;
 457            }
 458        } else if (isFloatProperty(name)) {
 459            try {
 460                return Float.parseFloat(value);
 461            } catch (NumberFormatException nfe) {
 462                Log.w(LOG_TAG, "Property: '" + name + "' not float. Ignoring!");
 463                return null;
 464            }
 465        } else if (isBooleanProperty(name)) {
 466            return Boolean.parseBoolean(value);
 467        } else if (isStringProperty(name)) {
 468            return value;
 469        } else {
 470            throw new IllegalArgumentException("Unknown property: " + name);
 471        }
 472    }
 473
 474    /**
 475     * Returns if a property is an integer.
 476     * 
 477     * @param propertyName The property name.
 478     * @return True if the property is an integer, false otherwise.
 479     */
 480    static boolean isIntegerProperty(String propertyName) {
 481        return (PROPERTY_EVENT_TYPE.equals(propertyName)
 482                || PROPERTY_ITEM_COUNT.equals(propertyName)
 483                || PROPERTY_CURRENT_ITEM_INDEX.equals(propertyName)
 484                || PROPERTY_FROM_INDEX.equals(propertyName)
 485                || PROPERTY_ADDED_COUNT.equals(propertyName)
 486                || PROPERTY_REMOVED_COUNT.equals(propertyName)
 487                || PROPERTY_QUEUING.equals(propertyName))
 488                || PROPERTY_VERSION_CODE.equals(propertyName)
 489                || PROPERTY_PLATFORM_SDK.equals(propertyName);
 490    }
 491
 492    /**
 493     * Returns if a property is a float.
 494     * 
 495     * @param propertyName The property name.
 496     * @return True if the property is a float, false otherwise.
 497     */
 498    static boolean isFloatProperty(String propertyName) {
 499        return PROPERTY_EVENT_TIME.equals(propertyName);
 500    }
 501
 502    /**
 503     * Returns if a property is a string.
 504     * 
 505     * @param propertyName The property name.
 506     * @return True if the property is a string, false otherwise.
 507     */
 508    static boolean isStringProperty(String propertyName) {
 509        return (PROPERTY_PACKAGE_NAME.equals(propertyName)
 510                || PROPERTY_CLASS_NAME.equals(propertyName)
 511                || PROPERTY_TEXT.equals(propertyName)
 512                || PROPERTY_BEFORE_TEXT.equals(propertyName)
 513                || PROPERTY_CONTENT_DESCRIPTION.equals(propertyName)
 514                || PROPERTY_ACTIVITY.equals(propertyName))
 515                || PROPERTY_VERSION_NAME.equals(propertyName)
 516                || PROPERTY_PLATFORM_RELEASE.equals(propertyName)
 517                || PROPERTY_SYSTEM_FEATURE.equals(propertyName);
 518    }
 519
 520    /**
 521     * Returns if a property is a boolean.
 522     * 
 523     * @param propertyName The property name.
 524     * @return True if the property is a boolean, false otherwise.
 525     */
 526    static boolean isBooleanProperty(String propertyName) {
 527        return (PROPERTY_CHECKED.equals(propertyName)
 528                || PROPERTY_ENABLED.equals(propertyName)
 529                || PROPERTY_FULL_SCREEN.equals(propertyName)
 530                || PROPERTY_PASSWORD.equals(propertyName));
 531    }
 532
 533    /**
 534     * Create a {@link Filter} given a DOM <code>node</code>.
 535     * 
 536     * @param node The node.
 537     * @return The created filter.
 538     */
 539    private Filter createFilter(Node node) {
 540        NodeList children = node.getChildNodes();
 541
 542        // do we have a custom filter
 543        for (int i = 0, count = children.getLength(); i < count; i++) {
 544            Node child = children.item(i);
 545
 546            if (child.getNodeType() != Node.ELEMENT_NODE) {
 547                continue;
 548            }
 549
 550            String nodeName = getUnqualifiedNodeName(child);
 551            if (NODE_NAME_CUSTOM.equals(nodeName)) {
 552                return createNewInstance(getTextContent(child), Filter.class);
 553            }
 554        }
 555
 556        return new DefaultFilter(mContext, node);
 557    }
 558
 559    /**
 560     * Creates a {@link Formatter} given a DOM <code>node</code>.
 561     * 
 562     * @param node The node.
 563     * @return The created formatter.
 564     */
 565    private Formatter createFormatter(Node node) {
 566        NodeList children = node.getChildNodes();
 567
 568        for (int i = 0, count = children.getLength(); i < count; i++) {
 569            Node child = children.item(i);
 570
 571            if (child.getNodeType() != Node.ELEMENT_NODE) {
 572                continue;
 573            }
 574
 575            String nodeName = getUnqualifiedNodeName(child);
 576            if (NODE_NAME_CUSTOM.equals(nodeName)) {
 577                return createNewInstance(getTextContent(child), Formatter.class);
 578            }
 579        }
 580
 581        return new DefaultFormatter(node);
 582    }
 583
 584    /**
 585     * Creates a new instance given a <code>className</code> and the
 586     * <code>expectedClass</code> that instance must belong to.
 587     * 
 588     * @param className the class name.
 589     * @return New instance if succeeded, null otherwise.
 590     */
 591    @SuppressWarnings("unchecked")
 592    // the possible ClassCastException is handled by the method
 593    private <T> T createNewInstance(String className, Class<T> expectedClass) {
 594        try {
 595            Class<T> clazz = null;
 596            // if we are loaded by the context class loader => use the latter
 597            if (mContext.getClassLoader() == this.getClass().getClassLoader()) {
 598                clazz = (Class<T>) mContext.getClassLoader().loadClass(className);
 599            } else {
 600                // It is important to load the plug-in classes via the TalkBack
 601                // ClassLoader to achieve interoperability of the classes of the
 602                // different APKs. Form VM perspective a class loaded by two
 603                // different class loaders is considered as two separate
 604                // classes.
 605                DexFile dexFile = new DexFile(new File(mPublicSourceDir));
 606                ClassLoader classLoader = TalkBackService.asContext().getClassLoader();
 607                clazz = dexFile.loadClass(className, classLoader);
 608            }
 609            return clazz.newInstance();
 610        } catch (ClassNotFoundException cnfe) {
 611            Log.e(LOG_TAG, "Rule: #" + mRuleIndex + ". Could not load class: '" + className + "'.",
 612                    cnfe);
 613        } catch (InstantiationException ie) {
 614            Log.e(LOG_TAG, "Rule: #" + mRuleIndex + ". Could not instantiate class: '" + className
 615                    + "'.", ie);
 616        } catch (IllegalAccessException iae) {
 617            Log.e(LOG_TAG, "Rule: #" + mRuleIndex + ". Could not instantiate class: '" + className
 618                    + "'.", iae);
 619        } catch (IOException ioe) {
 620            Log.e(LOG_TAG, "Rule: #" + mRuleIndex + ". Could not instantiate class: '" + className
 621                    + "'.", ioe);
 622        } catch (NullPointerException npe) {
 623            Log.e(LOG_TAG, "Rule: #" + mRuleIndex + ". Could not instantiate class: '" + className
 624                    + "'.", npe);
 625        }
 626
 627        return null;
 628    }
 629
 630    /**
 631     * Factory method that creates all speech rules from the DOM representation
 632     * of a speechstrategy.xml. This class does not verify if the
 633     * <code>document</code> is well-formed and it is responsibility of the
 634     * client to do that.
 635     *
 636     * @param context A {@link Context} instance for loading resources.
 637     * @param speechStrategy The speech strategy that defined the rules.
 638     * @param targetPackage The package targeted by the rules.
 639     * @param publicSourceDir The location of the plug-in APK for loading classes.
 640     * @param document The parsed XML.
 641     * @return The list of loaded speech rules.
 642     */
 643    public static ArrayList<SpeechRule> createSpeechRules(Context context, String speechStrategy,
 644            String targetPackage, String publicSourceDir, Document document)
 645            throws IllegalStateException {
 646        ArrayList<SpeechRule> speechRules = new ArrayList<SpeechRule>();
 647
 648        if (document == null || context == null) {
 649            return speechRules;
 650        }
 651
 652        NodeList children = document.getDocumentElement().getChildNodes();
 653        for (int i = 0, count = children.getLength(); i < count; i++) {
 654            Node child = children.item(i);
 655            if (child.getNodeType() == Node.ELEMENT_NODE) {
 656                try {
 657                    speechRules.add(new SpeechRule(context, speechStrategy, targetPackage,
 658                            publicSourceDir, child, i));
 659                } catch (IllegalStateException ise) {
 660                    Log.w(LOG_TAG, "Failed loading speech rule: " + getTextContent(child), ise);
 661                }
 662            }
 663        }
 664
 665        return speechRules;
 666    }
 667
 668    /**
 669     * Returns the form the given <code>context</code> the text content of a
 670     * <code>node</code> after it has been localized.
 671     */
 672    static String getLocalizedTextContent(Context context, Node node) {
 673        String textContent = getTextContent(node);
 674        return getStringResource(context, textContent);
 675    }
 676
 677    /**
 678     * Returns a string resource from a <code>context</code> given its
 679     * <code>resourceIndetifier</code>.
 680     * <p>
 681     * Note: The resource identifier format is: @<package name>:string/<resource
 682     * name>
 683     *</p>
 684     */
 685    static String getStringResource(Context context, String resourceIdentifier) {
 686        if (resourceIdentifier.startsWith("@")) {
 687            int id = context.getResources().getIdentifier(resourceIdentifier.substring(1), null,
 688                    null);
 689            return context.getString(id);
 690        }
 691        return resourceIdentifier;
 692    }
 693
 694    /**
 695     * Returns the text content of a given <code>node</code> by performing a
 696     * preorder traversal of the tree rooted at that node. </p> Note: Android
 697     * Java implementation is not compatible with Java 5.0 which provides such a
 698     * method.
 699     * 
 700     * @param node The node.
 701     * @return The text content.
 702     */
 703    private static String getTextContent(Node node) {
 704        StringBuilder builder = sTempBuilder;
 705        getTextContentRecursive(node, builder);
 706        String text = builder.toString();
 707        builder.delete(0, builder.length());
 708        return text;
 709    }
 710
 711    /**
 712     * Performs a recursive preorder traversal of a DOM tree and aggregating the
 713     * text content.
 714     * 
 715     * @param node The currently explored node.
 716     * @param builder Builder that aggregates the text content.
 717     */
 718    private static void getTextContentRecursive(Node node, StringBuilder builder) {
 719        NodeList children = node.getChildNodes();
 720        for (int i = 0, count = children.getLength(); i < count; i++) {
 721            Node child = children.item(i);
 722            if (child.getNodeType() == Node.TEXT_NODE) {
 723                builder.append(child.getNodeValue());
 724            }
 725            getTextContentRecursive(child, builder);
 726        }
 727    }
 728
 729    /**
 730     * Returns the unqualified <code>node</code> name i.e. without the prefix.
 731     * 
 732     * @param node The node.
 733     * @return The unqualified name.
 734     */
 735    private static String getUnqualifiedNodeName(Node node) {
 736        String nodeName = node.getNodeName();
 737        int colonIndex = nodeName.indexOf(COLON);
 738        if (colonIndex > -1) {
 739            nodeName = nodeName.substring(colonIndex + 1);
 740        }
 741        return nodeName;
 742    }
 743
 744    /**
 745     * Represents a default filter determining if the rule applies to a given
 746     * {@link AccessibilityEvent}.
 747     */
 748    class DefaultFilter implements Filter {
 749
 750        DefaultFilter(Context context, Node node) {
 751            NodeList properties = node.getChildNodes();
 752
 753            for (int i = 0, count = properties.getLength(); i < count; i++) {
 754                Node child = properties.item(i);
 755
 756                if (child.getNodeType() != Node.ELEMENT_NODE) {
 757                    continue;
 758                }
 759
 760                String unqualifiedName = getUnqualifiedNodeName(child);
 761                String textContent = getTextContent(child);
 762                PropertyMatcher propertyMatcher = new PropertyMatcher(context, unqualifiedName,
 763                        textContent);
 764                mPropertyMatchers.put(unqualifiedName, propertyMatcher);
 765
 766                // if the speech rule specifies a target package we use that value
 767                // rather the one passed as an argument to the rule constructor
 768                if (PROPERTY_PACKAGE_NAME.equals(unqualifiedName)) {
 769                    mPackageName = textContent;
 770                }
 771            }
 772        }
 773
 774        @Override
 775        public boolean accept(AccessibilityEvent event, Context context, String activity,
 776                Object args) {
 777            // the order here matters and is from most frequently used to less
 778            PropertyMatcher eventTypeMatcher = mPropertyMatchers.get(PROPERTY_EVENT_TYPE);
 779            if (eventTypeMatcher != null) {
 780                int eventType = event.getEventType(); 
 781                if (!eventTypeMatcher.accept(eventType)) {
 782                    return false;
 783                }
 784            }
 785            PropertyMatcher packageNameMatcher = mPropertyMatchers.get(PROPERTY_PACKAGE_NAME);
 786            if (packageNameMatcher != null) {
 787                CharSequence packageName = event.getPackageName();
 788                if (!packageNameMatcher.accept(packageName)) {
 789                    return false;
 790                }
 791            }
 792            // special case
 793            PropertyMatcher classNameMatcher = mPropertyMatchers.get(PROPERTY_CLASS_NAME);
 794            if (classNameMatcher != null) {
 795                String eventClass = event.getClassName().toString();
 796                String eventPackage = event.getPackageName().toString();
 797                String filteringPackage = null;
 798                if (packageNameMatcher != null) {
 799                    filteringPackage = (String) packageNameMatcher.getAcceptedValues()[0];
 800                }
 801                if (!classNameMatcher.accept(eventClass, eventPackage, filteringPackage)) {
 802                    return false;
 803                }
 804            }
 805            PropertyMatcher textMatcher = mPropertyMatchers.get(PROPERTY_TEXT);
 806            if (textMatcher != null) {
 807                CharSequence eventText = Utils.getEventText(context, event);
 808                if (!textMatcher.accept(eventText)) {
 809                    return false;
 810                }
 811            }
 812            PropertyMatcher beforeTextMatcher = mPropertyMatchers.get(PROPERTY_BEFORE_TEXT);
 813            if (beforeTextMatcher != null) {
 814                CharSequence beforeText = event.getBeforeText(); 
 815                if (!beforeTextMatcher.accept(beforeText)) {
 816                    return false;
 817                }
 818            }
 819            PropertyMatcher contentDescriptionMatcher =
 820                    mPropertyMatchers.get(PROPERTY_CONTENT_DESCRIPTION);
 821            if (contentDescriptionMatcher != null) {
 822                CharSequence contentDescription = event.getContentDescription();
 823                if (!contentDescriptionMatcher.accept(contentDescription)) {
 824                    return false;
 825                }
 826            }
 827            PropertyMatcher eventTimeMatcher = mPropertyMatchers.get(PROPERTY_EVENT_TIME);
 828            if (eventTimeMatcher != null) {
 829                long eventTime = event.getEventTime();
 830                if (!eventTimeMatcher.accept(eventTime)) {
 831                    return false;
 832                }
 833            }
 834            PropertyMatcher itemCountMatcher = mPropertyMatchers.get(PROPERTY_ITEM_COUNT);
 835            if (itemCountMatcher != null) {
 836                int itemCount = event.getItemCount();
 837                if (!itemCountMatcher.accept(itemCount)) {
 838                    return false;
 839                }
 840            }
 841            PropertyMatcher currentItemIndexMatcher =
 842                    mPropertyMatchers.get(PROPERTY_CURRENT_ITEM_INDEX);
 843            if (currentItemIndexMatcher != null) {
 844                int currentItemIndex = event.getCurrentItemIndex();
 845                if (!currentItemIndexMatcher.accept(currentItemIndex)) {
 846                    return false;
 847                }
 848            }
 849            PropertyMatcher fromIndexMatcher = mPropertyMatchers.get(PROPERTY_FROM_INDEX);
 850            if (fromIndexMatcher != null) {
 851                int fromIndex = event.getFromIndex();
 852                if (!fromIndexMatcher.accept(fromIndex)) {
 853                    return false;
 854                }
 855            }
 856            PropertyMatcher isCheckedMatcher = mPropertyMatchers.get(PROPERTY_CHECKED);
 857            if (isCheckedMatcher != null) {
 858                boolean isChecked = event.isChecked();
 859                if (!isCheckedMatcher.accept(isChecked)) {
 860                    return false;
 861                }
 862            }
 863            PropertyMatcher isEnabledMatcher = mPropertyMatchers.get(PROPERTY_ENABLED);
 864            if (isEnabledMatcher != null) {
 865                boolean isEnabled = event.isEnabled();
 866                if (!isEnabledMatcher.accept(isEnabled)) {
 867                    return false;
 868                }
 869            }
 870            PropertyMatcher isFullScreenMatcher = mPropertyMatchers.get(PROPERTY_FULL_SCREEN);
 871            if (isFullScreenMatcher != null) {
 872                boolean isFullScreen = event.isFullScreen();
 873                if (!isFullScreenMatcher.accept(isFullScreen)) {
 874                    return false;
 875                }
 876            }
 877            PropertyMatcher isPasswordMatcher = mPropertyMatchers.get(PROPERTY_PASSWORD);
 878            if (isPasswordMatcher != null) {
 879                boolean isPassword = event.isPassword();
 880                if (!isPasswordMatcher.accept(isPassword)) {
 881                    return false;
 882                }
 883            }
 884            PropertyMatcher addedCountMatcher = mPropertyMatchers.get(PROPERTY_ADDED_COUNT);
 885            if (addedCountMatcher != null) {
 886                int addedCount = event.getAddedCount();
 887                if (!addedCountMatcher.accept(addedCount)) {
 888                    return false;
 889                }
 890            }
 891            PropertyMatcher removedCountMatcher = mPropertyMatchers.get(PROPERTY_REMOVED_COUNT);
 892            if (removedCountMatcher != null) {
 893                int removedCount = event.getRemovedCount();
 894                if (!removedCountMatcher.accept(removedCount)) {
 895                    return false;
 896                }
 897            }            
 898            PropertyMatcher activityMatcher = mPropertyMatchers.get(PROPERTY_ACTIVITY);
 899            if (activityMatcher != null) {
 900                if (!activityMatcher.accept(activity)) {
 901                    return false;
 902                }
 903            }
 904            PropertyMatcher versionCodeMatcher = mPropertyMatchers.get(PROPERTY_VERSION_CODE);
 905            if (versionCodeMatcher != null) {
 906                String packageName = event.getPackageName().toString();
 907                int versionCode = Utils.getVersionCode(context, packageName);
 908                if (!versionCodeMatcher.accept(versionCode)) {
 909                    return false;
 910                }
 911            }
 912            PropertyMatcher versionNameMatcher = mPropertyMatchers.get(PROPERTY_VERSION_NAME);
 913            if (versionNameMatcher != null) {
 914                String packageName = event.getPackageName().toString();
 915                String versionName = Utils.getVersionName(context, packageName);
 916                if (!versionNameMatcher.accept(versionName)) {
 917                    return false;
 918                }
 919            }
 920            PropertyMatcher platformReleaseMatcher =
 921                    mPropertyMatchers.get(PROPERTY_PLATFORM_RELEASE);
 922            if (platformReleaseMatcher != null) {
 923                String platformRelease = Build.VERSION.RELEASE;
 924                if (!platformReleaseMatcher.accept(platformRelease)) {
 925                    return false;
 926                }
 927            }
 928            PropertyMatcher platformSdkMatcher = mPropertyMatchers.get(PROPERTY_PLATFORM_SDK);
 929            if (platformSdkMatcher != null) {
 930                int platformSdk = Build.VERSION.SDK_INT;
 931                if (!platformSdkMatcher.accept(platformSdk)) {
 932                    return false;
 933                }
 934            }
 935            // special case
 936            PropertyMatcher systemFeatureMatcher = mPropertyMatchers.get(PROPERTY_SYSTEM_FEATURE);
 937            if (systemFeatureMatcher != null) {
 938                if (!systemFeatureMatcher.accept(null)) {
 939                    return false;
 940                }
 941            }
 942            return true;
 943        }
 944    }
 945
 946    /**
 947     * This class is default formatter for building utterance for announcing an
 948     * {@link AccessibilityEvent}. The formatting strategy is to populate a
 949     * template with event properties or strings selected via a regular
 950     * expression from the event's text. If no template is provided the
 951     * properties or regular expression selections are concatenated with space
 952     * as delimiter.
 953     */
 954    class DefaultFormatter implements Formatter {
 955
 956        // node names
 957
 958        private static final String NODE_NAME_TEMPLATE = "template";
 959
 960        private static final String NODE_NAME_SPLIT = "split";
 961
 962        private static final String NODE_NAME_REGEX = "regex";
 963
 964        private static final String NODE_NAME_PROPERTY = "property";
 965
 966        /**
 967         * optional template to populate with selected values
 968         */
 969        private final String mTemplate;
 970
 971        private final List<StringPair> mSelectors;
 972
 973        /**
 974         * Creates a new formatter from a given DOM {@link Node}.
 975         * 
 976         * @param node The node.
 977         */
 978        DefaultFormatter(Node node) {
 979            mSelectors = new ArrayList<StringPair>();
 980            String template = null;
 981
 982            NodeList children = node.getChildNodes();
 983            for (int i = 0, count = children.getLength(); i < count; i++) {
 984                Node child = children.item(i);
 985
 986                if (child.getNodeType() != Node.ELEMENT_NODE) {
 987                    continue;
 988                }
 989
 990                String unqualifiedName = getUnqualifiedNodeName(child);
 991                // some elements contain mandatory reference to a string
 992                // resource
 993                if (NODE_NAME_TEMPLATE.equals(unqualifiedName)) {
 994                    template = getLocalizedTextContent(mContext, child);
 995                } else if (NODE_NAME_PROPERTY.equals(unqualifiedName)
 996                        || NODE_NAME_SPLIT.equals(unqualifiedName)) {
 997                    mSelectors.add(new StringPair(unqualifiedName, getLocalizedTextContent(
 998                            mContext, child)));
 999                } else {
1000                    mSelectors.add(new StringPair(unqualifiedName, getTextContent(child)));
1001                }
1002            }
1003
1004            mTemplate = template;
1005        }
1006
1007        @Override
1008        public void format(AccessibilityEvent event, Context context, Utterance utterance,
1009                Object args) {
1010            List<StringPair> selectors = mSelectors;
1011            Object[] arguments = new Object[selectors.size()];
1012
1013            for (int i = 0, count = selectors.size(); i < count; i++) {
1014                StringPair selector = selectors.get(i);
1015                String selectorType = selector.mFirst;
1016                String selectorValue = selector.mSecond;
1017
1018                if (NODE_NAME_SPLIT.equals(selectorType)) {
1019                    String text = Utils.getEventText(mContext, event).toString();
1020                    arguments = text.split(selectorValue);
1021                    break;
1022                } else if (NODE_NAME_PROPERTY.equals(selectorType)) {
1023                    Object propertyValue = getPropertyValue(selectorValue, event);
1024                    arguments[i] = propertyValue != null ? propertyValue : "";
1025                } else if (NODE_NAME_REGEX.equals(selectorType)) {
1026                    arguments[i] = getEventTextRegExpMatch(event, selectorValue);
1027                } else {
1028                    throw new IllegalArgumentException("Unknown selector type: [" + selector.mFirst
1029                            + selector.mSecond + "]");
1030                }
1031            }
1032
1033            formatTemplateOrAppendSpaceSeparatedValueIfNoTemplate(utterance, arguments);
1034        }
1035
1036        /**
1037         * Returns a substring from a <code>event</code> text that matches a
1038         * given <code>regExp</code>. If the regular expression does not find a
1039         * match the empty string is returned.
1040         * 
1041         * @param event The event.
1042         * @param regExp The regular expression.
1043         * @return The matched string, the empty string otherwise.
1044         */
1045        private String getEventTextRegExpMatch(AccessibilityEvent event, String regExp) {
1046            StringBuilder text = Utils.getEventText(mContext, event);
1047            Pattern pattern = Pattern.compile(regExp);
1048            Matcher matcher = pattern.matcher(text);
1049
1050            if (matcher.find()) {
1051                return text.substring(matcher.start(), matcher.end());
1052            } else {
1053                return EMPTY_STRING;
1054            }
1055        }
1056
1057        /**
1058         * Returns the value of a given <code>property</code> of an
1059         * <code>event</code>.
1060         * 
1061         * @param property The property
1062         * @param event The event.
1063         * @return the value.
1064         */
1065        private Object getPropertyValue(String property, AccessibilityEvent event) {
1066            // no reflection to avoid performance hit
1067            if (PROPERTY_EVENT_TYPE.equals(property)) {
1068                return event.getEventType();
1069            } else if (PROPERTY_PACKAGE_NAME.equals(property)) {
1070                return event.getPackageName();
1071            } else if (PROPERTY_CLASS_NAME.equals(property)) {
1072                return event.getClassName();
1073            } else if (PROPERTY_TEXT.equals(property)) {
1074                // special case
1075                CharSequence contentDesc = event.getContentDescription();
1076                if (contentDesc != null && contentDesc.length() > 0) {
1077                    return contentDesc;
1078                } else {
1079                    return Utils.getEventText(mContext, event);
1080                }
1081            } else if (PROPERTY_BEFORE_TEXT.equals(property)) {
1082                return event.getBeforeText();
1083            } else if (PROPERTY_CONTENT_DESCRIPTION.equals(property)) {
1084                return event.getContentDescription();
1085            } else if (PROPERTY_EVENT_TIME.equals(property)) {
1086                return event.getEventTime();
1087            } else if (PROPERTY_ITEM_COUNT.equals(property)) {
1088                return event.getItemCount();
1089            } else if (PROPERTY_CURRENT_ITEM_INDEX.equals(property)) {
1090                return event.getCurrentItemIndex();
1091            } else if (PROPERTY_FROM_INDEX.equals(property)) {
1092                return event.getFromIndex();
1093            } else if (PROPERTY_CHECKED.equals(property)) {
1094                return event.isChecked();
1095            } else if (PROPERTY_ENABLED.equals(property)) {
1096                return event.isEnabled();
1097            } else if (PROPERTY_FULL_SCREEN.equals(property)) {
1098                return event.isFullScreen();
1099            } else if (PROPERTY_PASSWORD.equals(property)) {
1100                return event.isPassword();
1101            } else if (PROPERTY_ADDED_COUNT.equals(property)) {
1102                return event.getAddedCount();
1103            } else if (PROPERTY_REMOVED_COUNT.equals(property)) {
1104                return event.getRemovedCount();
1105            } else {
1106                throw new IllegalArgumentException("Unknown property : " + property);
1107            }
1108        }
1109
1110        /**
1111         * Replaces template parameters in the template for this
1112         * {@link SpeechRule} with the <code>arguments</code> in case such a
1113         * template was provided. If no template was provided the arguments are
1114         * concatenated using space as delimiter. The <code>utterance</code> is
1115         * populated with the generated text.
1116         * 
1117         * @param utterance The builder to which to append the utterance.
1118         * @param arguments The formatting arguments.
1119         */
1120        private void formatTemplateOrAppendSpaceSeparatedValueIfNoTemplate(Utterance utterance,
1121                Object[] arguments) {
1122            final StringBuilder utteranceText = utterance.getText();
1123
1124            if (mTemplate != null) {
1125                try {
1126                    sStringFormatter.format(mTemplate, arguments);
1127                    StringBuilder formatterBuilder = (StringBuilder) sStringFormatter.out();
1128                    utteranceText.append(formatterBuilder);
1129                    // clear the builder of the formatter
1130                    formatterBuilder.delete(0, formatterBuilder.length());
1131                } catch (MissingFormatArgumentException mfae) {
1132                    Log.e(LOG_TAG, "Speech rule: '" + mRuleIndex + " has inconsistency between "
1133                            + "template: '" + mTemplate + "' and arguments: '" + arguments
1134                            + "'. Possibliy #template arguments does not match #parameters", mfae);
1135                }
1136            } else {
1137                for (Object arg : arguments) {
1138                    utteranceText.append(arg);
1139                    utteranceText.append(SPACE);
1140                }
1141                if (utteranceText.length() > 0) {
1142                    utteranceText.deleteCharAt(utteranceText.length() - 1);
1143                }
1144            }
1145        }
1146
1147        /**
1148         * Utility class to store a pair of elements.
1149         */
1150        class StringPair {
1151            String mFirst;
1152
1153            String mSecond;
1154
1155            /**
1156             * Creates a new instance.
1157             * 
1158             * @param first The first element of the pair.
1159             * @param second The second element of the pair.
1160             */
1161            StringPair(String first, String second) {
1162                mFirst = first;
1163                mSecond = second;
1164            }
1165        }
1166    }
1167
1168    /**
1169     * Helper class for matching properties.
1170     */
1171    static class PropertyMatcher {
1172
1173        /**
1174         * Match type if a property is equal to an expected value.
1175         */
1176        private static final int TYPE_EQUALS = 0;
1177
1178        /**
1179         * Match type if a numeric property is less than or equal to an expected value.
1180         */
1181        private static final int TYPE_LESS_THAN_OR_EQUAL = 1;
1182
1183        /**
1184         * Match type if a numeric property is greater than or equal to an expected value.
1185         */
1186        private static final int TYPE_GREATER_THAN_OR_EQUAL = 2;
1187
1188        /**
1189         * Match type if a numeric property is less than an expected value.
1190         */
1191        private static final int TYPE_LESS_THAN = 3;
1192
1193        /**
1194         * Match type if a numeric property is greater than an expected value.
1195         */
1196        private static final int TYPE_GREATER_THAN = 4;
1197
1198        /**
1199         * Match type if a numeric property is equal to one of several expected values.
1200         */
1201        private static final int TYPE_OR = 5;
1202
1203        /**
1204         * String for a regex pattern than matches float numbers.
1205         */
1206        private static final String PATTERN_STRING_FLOAT = "(\\s)*([+-])?((\\d)+(\\.(\\d)+)?|\\.(\\d)+)(\\s)*";
1207
1208        /**
1209         * Pattern to match a less than or equal a numeric value constraint.
1210         */
1211        private static final Pattern PATTERN_LESS_THAN_OR_EQUAL = Pattern.compile("(\\s)*<=" + PATTERN_STRING_FLOAT);
1212
1213        /**
1214         * Pattern to match a greater than or equal a numeric value constraint.
1215         */
1216        private static final Pattern PATTERN_GREATER_THAN_OR_EQUAL = Pattern.compile("(\\s)*>=" + PATTERN_STRING_FLOAT);
1217
1218        /**
1219         * Pattern to match a less than a numeric value constraint.
1220         */
1221        private static final Pattern PATTERN_LESS_THAN = Pattern.compile("(\\s)*<" + PATTERN_STRING_FLOAT);
1222
1223        /**
1224         * Pattern to match a greater than a numeric value constraint.
1225         */
1226        private static final Pattern PATTERN_GREATER_THAN = Pattern.compile("(\\s)*>" + PATTERN_STRING_FLOAT);
1227
1228        /**
1229         * Pattern to match an or constraint.
1230         */
1231        private static final Pattern PATTERN_OR = Pattern.compile("(.)+\\|\\|(.)+(\\|\\|(.)+)*");
1232
1233        /**
1234         * Pattern for splitting an or constraint into simple equal constraints.
1235         */
1236        private static final Pattern PATTERN_SPLIT_OR = Pattern.compile("(\\s)*\\|\\|(\\s)*");
1237
1238        /**
1239         * The less than or equal string.
1240         */
1241        private static final String LESS_THAN_OR_EQUAL = "<=";
1242
1243        /**
1244         * The greater than or equal string.
1245         */
1246        private static final String GREATER_THAN_OR_EQUAL = ">=";
1247
1248        /**
1249         * The less than string.
1250         */
1251        private static final String LESS_THAN = "<";
1252
1253        /**
1254         * The greater than string.
1255         */
1256        private static final String GREATER_THAN = ">";
1257
1258        /**
1259         * The name of the property matched by this instance.
1260         */
1261        private String mPropertyName;
1262
1263        /**
1264         * The type of matching to be performed.
1265         */
1266        private int mType;
1267
1268        /**
1269         * The values accepted by this matcher.
1270         */
1271        private Object[] mAcceptedValues;
1272
1273        /**
1274         * Context handled for accessing resources.
1275         */
1276        private

Large files files are truncated, but you can click here to view the full file