Preparing for 2.0 release.
diff --git a/README b/README
index d5a4a63..2f8e153 100644
--- a/README
+++ b/README
@@ -21,13 +21,13 @@
 
 Sample Usage
 ------------
-Given a View view, the following code runs all accessibility checks and throws 
-an exception if any errors are found:
+Given a root view, the following code runs all accessibility checks on all views in the
+hierarchy and and throws an exception if any errors are found:
 
-Set<AccessibilityCheck> checks = getAllChecksForPreset(VIEW_CHECKS);
+Set<AccessibilityViewHierarchyCheck> checks = getViewChecksForPreset(LATEST);
 List<AccessibilityViewCheckResult> results = new LinkedList<AccessibilityViewCheckResult>();
-for (AccessibilityCheck check : checks) {
-  results.addAll(((AccessibilityViewCheck) check).runCheckOnView(view));
+for (AccessibilityViewHierarchyCheck check : checks) {
+  results.addAll(check.runCheckOnViewHierarchy(rootView));
 }
 List<AccessibilityViewCheckResult> errors = AccessibilityCheckResultUtils.getResultsForType(
         results, AccessibilityCheckResultType.ERROR);
diff --git a/pom.xml b/pom.xml
index 371823a..ef01e3b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -4,12 +4,48 @@
 
   <groupId>com.google.android.apps.common.testing.accessibility.framework</groupId>
   <artifactId>accessibility-test-framework</artifactId>
-  <version>1.0-SNAPSHOT</version>
+  <version>2.0</version>
   <packaging>jar</packaging>
 
   <name>Accessibility Test Framework</name>
+  <description>Library used to test for common accessibility issues.</description>
   <url>https://code.google.com/p/eyes-free/</url>
 
+  <licenses>
+    <license>
+      <name>Apache License, Version 2.0</name>
+      <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
+      <distribution>repo</distribution>
+    </license>
+  </licenses>
+
+  <developers>
+    <developer>
+      <name>Phil Weaver</name>
+      <email>[email protected]</email>
+      <organization>Google, Inc</organization>
+      <organizationUrl>http://www.google.com</organizationUrl>
+    </developer>
+    <developer>
+      <name>Casey Burkhardt</name>
+      <email>[email protected]</email>
+      <organization>Google, Inc</organization>
+      <organizationUrl>http://www.google.com</organizationUrl>
+    </developer>
+    <developer>
+      <name>Samuel Rush</name>
+      <email>[email protected]</email>
+      <organization>Google, Inc</organization>
+      <organizationUrl>http://www.google.com</organizationUrl>
+    </developer>
+  </developers>
+
+  <scm>
+    <connection>scm:svn:http://eyes-free.googlecode.com/svn/trunk/</connection>
+    <developerConnection>scm:svn:https://eyes-free.googlecode.com/svn/trunk/</developerConnection>
+    <url>https://code.google.com/p/eyes-free/source/browse</url>
+  </scm>
+
   <dependencies>
     <dependency>
       <groupId>com.android.support</groupId>
@@ -48,6 +84,33 @@
        </sdk>
       </configuration>
      </plugin>
+     <plugin>
+      <groupId>org.apache.maven.plugins</groupId>
+      <artifactId>maven-source-plugin</artifactId>
+      <executions>
+       <execution>
+        <id>attach-sources</id>
+        <goals>
+         <goal>jar</goal>
+        </goals>
+        </execution>
+       </executions>
+     </plugin>
+     <plugin>
+      <groupId>org.apache.maven.plugins</groupId>
+      <artifactId>maven-javadoc-plugin</artifactId>
+      <configuration>
+       <sourcepath>src</sourcepath>
+      </configuration>
+      <executions>
+       <execution>
+        <id>attach-javadocs</id>
+        <goals>
+         <goal>jar</goal>
+        </goals>
+       </execution>
+      </executions>
+     </plugin>
     </plugins>
    </build>
-</project>
\ No newline at end of file
+</project>
diff --git a/src/main/java/com/google/android/apps/common/testing/accessibility/framework/AccessibilityCheck.java b/src/main/java/com/google/android/apps/common/testing/accessibility/framework/AccessibilityCheck.java
index 7900aaf..50ce688 100644
--- a/src/main/java/com/google/android/apps/common/testing/accessibility/framework/AccessibilityCheck.java
+++ b/src/main/java/com/google/android/apps/common/testing/accessibility/framework/AccessibilityCheck.java
@@ -22,9 +22,8 @@
  * {@code AccessibilityInfoCheck}, the base class for checks that run against
  * {@code AccessibilityNodeInfo}s.
  *
- * Classes extending this one must implement {@code runCheck...} that return {@code List}s of
+ * <p>Classes extending this one must implement {@code runCheck...} that return {@code List}s of
  * a subclass of {@code AccessibilityCheckResult}.
  */
 public abstract class AccessibilityCheck {
-
 }
diff --git a/src/main/java/com/google/android/apps/common/testing/accessibility/framework/AccessibilityCheckPreset.java b/src/main/java/com/google/android/apps/common/testing/accessibility/framework/AccessibilityCheckPreset.java
index 2d86da8..ac7dbf0 100644
--- a/src/main/java/com/google/android/apps/common/testing/accessibility/framework/AccessibilityCheckPreset.java
+++ b/src/main/java/com/google/android/apps/common/testing/accessibility/framework/AccessibilityCheckPreset.java
@@ -16,7 +16,6 @@
 
 package com.google.android.apps.common.testing.accessibility.framework;
 
-import java.util.Collections;
 import java.util.HashSet;
 import java.util.Set;
 
@@ -24,64 +23,121 @@
  * Pre-sets for check configurations used with {@code getConfigForPreset}
  */
 public enum AccessibilityCheckPreset {
+  /** The latest set of checks (in general the most comprehensive list */
+  LATEST,
+
+  /** The set of checks available in the 1.0 release of the framework */
+  VERSION_1_0_CHECKS,
+
   /** Don't check anything */
   NO_CHECKS,
 
-  /** Check everything on a single View */
-  VIEW_CHECKS,
-
-  /** Check everything on a single AccessibilityNodeInfo */
-  INFO_CHECKS,
-
-  /** Check everything on all Views in a hierarchy */
-  VIEW_HIERARCHY_CHECKS,
-
-  /** Check everything on all AccessibilityNodeInfos in a hierarchy */
-  INFO_HIERARCHY_CHECKS;
-
-  private static final AccessibilityCheck[] ALL_ACCESSIBILITY_CHECKS = {
-      new DuplicateSpeakableTextViewHierarchyCheck(),
-      new EditableContentDescInfoCheck(),
-      new EditableContentDescViewCheck(),
-      new SpeakableTextPresentInfoCheck(),
-      new SpeakableTextPresentViewCheck(),
-      new TextContrastViewCheck(),
-      new TouchTargetSizeViewCheck()
-    };
+  /**
+   * Preset used occasionally to hold checks that are about to be part of {@code LATEST}.
+   * Includes all checks in {@code LATEST}.
+   */
+  PRERELEASE;
 
   /**
    * @param preset The preset of interest
-   * @return An unmodifiable set of all checks with scopes that are covered by the specified scope
+   * @return A set of all checks for {@code View}s with scopes for the preset
    */
-  public static Set<AccessibilityCheck> getAllChecksForPreset(AccessibilityCheckPreset preset) {
-    Set<AccessibilityCheck> checks = new HashSet<AccessibilityCheck>();
-    for (AccessibilityCheck check : ALL_ACCESSIBILITY_CHECKS) {
-      switch (preset) {
-        case VIEW_CHECKS:
-          if (check instanceof AccessibilityViewCheck) {
-            checks.add(check);
-          }
-          break;
-        case VIEW_HIERARCHY_CHECKS:
-          if (check instanceof AccessibilityViewHierarchyCheck) {
-            checks.add(check);
-          }
-          break;
-        case INFO_CHECKS:
-          if (check instanceof AccessibilityInfoCheck) {
-            checks.add(check);
-          }
-          break;
-        case INFO_HIERARCHY_CHECKS:
-          if (check instanceof AccessibilityInfoHierarchyCheck) {
-            checks.add(check);
-          }
-          break;
-        case NO_CHECKS:
-        default:
-          break;
-      }
+  public static Set<AccessibilityViewHierarchyCheck>
+      getViewChecksForPreset(AccessibilityCheckPreset preset) {
+    Set<AccessibilityViewHierarchyCheck> checks = new HashSet<>();
+    if (preset == NO_CHECKS) {
+      return checks;
     }
-    return Collections.unmodifiableSet(checks);
+
+    /* Checks included in version 1.0 */
+    checks.add(new TouchTargetSizeViewCheck());
+    checks.add(new TextContrastViewCheck());
+    checks.add(new DuplicateSpeakableTextViewHierarchyCheck());
+    checks.add(new SpeakableTextPresentViewCheck());
+    checks.add(new EditableContentDescViewCheck());
+    if (preset == VERSION_1_0_CHECKS) {
+      return checks;
+    }
+
+    /* Checks added since last release */
+    checks.add(new ClickableSpanViewCheck());
+    checks.add(new RedundantContentDescViewCheck());
+    checks.add(new DuplicateClickableBoundsViewCheck());
+    if (preset == LATEST) {
+      return checks;
+    }
+
+    if (preset == PRERELEASE) {
+      return checks;
+    }
+    /*
+     * Throw an exception if we didn't handle a preset. This code should be unreachable, but it
+     * makes writing a test for unhandled presets trivial.
+     */
+    throw new IllegalArgumentException();
+  }
+
+  /**
+   * @param preset The preset of interest
+   * @return A set of all checks for {@code AccessibilityNodeInfo}s with scopes for the preset
+   */
+  public static Set<AccessibilityInfoHierarchyCheck>
+      getInfoChecksForPreset(AccessibilityCheckPreset preset) {
+    Set<AccessibilityInfoHierarchyCheck> checks = new HashSet<>();
+    if (preset == NO_CHECKS) {
+      return checks;
+    }
+
+    /* Checks included in version 1.0 */
+    checks.add(new EditableContentDescInfoCheck());
+    checks.add(new SpeakableTextPresentInfoCheck());
+    if (preset == VERSION_1_0_CHECKS) {
+      return checks;
+    }
+
+    /* Checks added since last release */
+    checks.add(new ClickableSpanInfoCheck());
+    checks.add(new TouchTargetSizeInfoCheck());
+    checks.add(new RedundantContentDescInfoCheck());
+    checks.add(new DuplicateClickableBoundsInfoCheck());
+    if (preset == LATEST) {
+      return checks;
+    }
+    if (preset == PRERELEASE) {
+      return checks;
+    }
+
+    /*
+     * Throw an exception if we didn't handle a preset. This code should be unreachable, but it
+     * makes writing a test for unhandled presets trivial.
+     */
+    throw new IllegalArgumentException();
+  }
+
+  /**
+   * @param preset The preset of interest
+   * @return A set of all checks for {@code AccessibilityNodeInfo}s with scopes for the preset
+   */
+  public static Set<AccessibilityEventCheck>
+      getEventChecksForPreset(AccessibilityCheckPreset preset) {
+    Set<AccessibilityEventCheck> checks = new HashSet<>();
+    if ((preset == NO_CHECKS) || (preset == VERSION_1_0_CHECKS)) {
+      return checks;
+    }
+
+    /* Checks added since last release */
+    checks.add(new AnnouncementEventCheck());
+    if (preset == LATEST) {
+      return checks;
+    }
+    if (preset == PRERELEASE) {
+      return checks;
+    }
+
+    /*
+     * Throw an exception if we didn't handle a preset. This code should be unreachable, but it
+     * makes writing a test for unhandled presets trivial.
+     */
+    throw new IllegalArgumentException();
   }
 }
diff --git a/src/main/java/com/google/android/apps/common/testing/accessibility/framework/AccessibilityCheckResult.java b/src/main/java/com/google/android/apps/common/testing/accessibility/framework/AccessibilityCheckResult.java
index 5c5cf0c..8b1dcd3 100644
--- a/src/main/java/com/google/android/apps/common/testing/accessibility/framework/AccessibilityCheckResult.java
+++ b/src/main/java/com/google/android/apps/common/testing/accessibility/framework/AccessibilityCheckResult.java
@@ -1,65 +1,74 @@
 /*
  * Copyright (C) 2014 Google Inc.
  *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
  *
  * http://www.apache.org/licenses/LICENSE-2.0
  *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
  * the License.
  */
 
 package com.google.android.apps.common.testing.accessibility.framework;
 
+import com.google.android.apps.common.testing.accessibility.framework.proto.FrameworkProtos.AccessibilityCheckResultProto;
+
 /**
- * The result of an accessibility check. The results are "interesting" in the sense
- * that they indicate some sort of accessibility issue. {@code AccessibilityCheck}s return lists
- * of classes that extend this one. There is no "passing" result; checks that return lists that
- * contain no {@code AccessibilityCheckResultType.ERROR}s have passed.
+ * The result of an accessibility check. The results are "interesting" in the sense that they
+ * indicate some sort of accessibility issue. {@code AccessibilityCheck}s return lists of classes
+ * that extend this one. There is no "passing" result; checks that return lists that contain no
+ * {@code AccessibilityCheckResultType.ERROR}s have passed.
+ * <p>
+ * NOTE: Some subtypes of this class retain copies of resources that should be explicitly recycled.
+ * Callers should use {@link #recycle()} to dispose of data in this object and release these
+ * resources.
  */
 public abstract class AccessibilityCheckResult {
   /**
-   * Types of results
+   * Types of results. This must be kept consistent (other than UNKNOWN) with the ResultType enum in
+   * {@code AccessibilityCheckResultProto}.
    */
   public enum AccessibilityCheckResultType {
     /** Clearly an accessibility bug, for example no speakable text on a clicked button */
     ERROR,
     /**
-     * Potentially an accessibility bug, for example finding another view with the same
-     * speakable text as a clicked view
+     * Potentially an accessibility bug, for example finding another view with the same speakable
+     * text as a clicked view
      */
     WARNING,
     /**
-     * Information that may be helpful when evaluating accessibility, for example a listing of
-     * all speakable text in a view hierarchy in the traversal order used by an accessibility
-     * service.
+     * Information that may be helpful when evaluating accessibility, for example a listing of all
+     * speakable text in a view hierarchy in the traversal order used by an accessibility service.
      */
     INFO,
     /**
      * A signal that the check was not run at all (ex. because the API level was too low)
      */
-    NOT_RUN
+    NOT_RUN,
+    /**
+     * A result that has been explicitly suppressed from throwing any Exceptions, used to allow for
+     * known issues.
+     */
+    SUPPRESSED
   }
 
-  protected final AccessibilityCheck check;
+  protected Class<? extends AccessibilityCheck> checkClass;
 
-  protected final AccessibilityCheckResultType type;
+  protected AccessibilityCheckResultType type;
 
-  protected final CharSequence message;
+  protected CharSequence message;
 
   /**
-   * @param check The check that generated the error
+   * @param checkClass The class of the check that generated the error
    * @param type The type of the result
    * @param message A human-readable message explaining the error
    */
-  protected AccessibilityCheckResult(AccessibilityCheck check, AccessibilityCheckResultType type,
-      CharSequence message) {
-    this.check = check;
+  public AccessibilityCheckResult(Class<? extends AccessibilityCheck> checkClass,
+      AccessibilityCheckResultType type, CharSequence message) {
+    this.checkClass = checkClass;
     this.type = type;
     this.message = message;
   }
@@ -67,8 +76,8 @@
   /**
    * @return The check that generated the result.
    */
-  public AccessibilityCheck getSourceCheck() {
-    return check;
+  public Class<? extends AccessibilityCheck> getSourceCheckClass() {
+    return checkClass;
   }
 
   /**
@@ -79,9 +88,43 @@
   }
 
   /**
+   * @param type The type of the result.
+   */
+  /* package */ void setType(AccessibilityCheckResultType type) {
+    this.type = type;
+  }
+
+  /**
    * @return A human-readable message explaining the result.
    */
   public CharSequence getMessage() {
     return message;
   }
+
+  /**
+   * Recycles all resources held by this object.
+   */
+  public void recycle() {
+    checkClass = null;
+    type = null;
+    message = null;
+  }
+
+  /**
+   * Returns a populated {@link AccessibilityCheckResultProto}
+   */
+  public AccessibilityCheckResultProto toProto() {
+    AccessibilityCheckResultProto.Builder builder = AccessibilityCheckResultProto.newBuilder();
+    if (type != null) {
+      // enum in this class and proto are consistent, one can resolve the string name in the other
+      builder.setResultType(AccessibilityCheckResultProto.ResultType.valueOf(type.name()));
+    }
+    if (message != null) {
+      builder.setMsg(message.toString());
+    }
+    if (checkClass != null) {
+      builder.setSourceCheckClass(checkClass.getName());
+    }
+    return builder.build();
+  }
 }
diff --git a/src/main/java/com/google/android/apps/common/testing/accessibility/framework/AccessibilityCheckResultUtils.java b/src/main/java/com/google/android/apps/common/testing/accessibility/framework/AccessibilityCheckResultUtils.java
index 432209d..24a10f2 100644
--- a/src/main/java/com/google/android/apps/common/testing/accessibility/framework/AccessibilityCheckResultUtils.java
+++ b/src/main/java/com/google/android/apps/common/testing/accessibility/framework/AccessibilityCheckResultUtils.java
@@ -21,20 +21,27 @@
 import android.view.View;
 import android.view.accessibility.AccessibilityNodeInfo;
 
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.hamcrest.TypeSafeMatcher;
+
 import java.util.ArrayList;
 import java.util.List;
 
 /**
  * Utility class for dealing with {@code AccessibilityCheckResult}s
  */
-public class AccessibilityCheckResultUtils {
+public final class AccessibilityCheckResultUtils {
 
-  public AccessibilityCheckResultUtils() {
+  private AccessibilityCheckResultUtils() {
   }
 
   /**
-   * Takes a list of {@code AccessibilityCheckResult}s and returns a list with only results
-   * obtained from the given {@code AccessibilityCheck}.
+   * Takes a list of {@code AccessibilityCheckResult}s and returns a list with only results obtained
+   * from the given {@code AccessibilityCheck}.
+   * <p>
+   * NOTE: This method explicitly does not take subtypes of {@code checkClass} into account when
+   * filtering results.
    *
    * @param results a list of {@code AccessibilityCheckResult}s
    * @param checkClass the {@code Class} of the {@code AccessibilityCheck} to get results for
@@ -45,7 +52,7 @@
       Iterable<T> results, Class<? extends AccessibilityCheck> checkClass) {
     List<T> resultsForCheck = new ArrayList<T>();
     for (T result : results) {
-      if (checkClass.isInstance(result.getSourceCheck())) {
+      if (checkClass.equals(result.getSourceCheckClass())) {
         resultsForCheck.add(result);
       }
     }
@@ -112,4 +119,127 @@
     }
     return resultsForInfo;
   }
+
+  /**
+   * Returns a {@link Matcher} for an {@link AccessibilityCheckResult} whose result type matches the
+   * given matcher for {@link AccessibilityCheckResultType}.
+   *
+   * @param typeMatcher a {@code Matcher} for an {@code AccessibilityCheckResultType}
+   * @return a {@code Matcher} for an {@code AccessibilityCheckResult}
+   */
+  public static Matcher<AccessibilityCheckResult> matchesTypes(
+      final Matcher<? super AccessibilityCheckResultType> typeMatcher) {
+    if (typeMatcher == null) {
+      return null;
+    }
+    return new TypeSafeMemberMatcher<AccessibilityCheckResult>("result type", typeMatcher) {
+      @Override
+      public boolean matchesSafely(AccessibilityCheckResult result) {
+        return typeMatcher.matches(result.getType());
+      }
+    };
+  }
+
+  /**
+   * Returns a {@link Matcher} for an {@link AccessibilityCheckResult} whose source check class has
+   * a simple name that matches the given matcher for a {@code String}.
+   *
+   * @param classNameMatcher a {@code Matcher} for a {@code String}
+   * @return a {@code Matcher} for an {@code AccessibilityCheckResult}
+   */
+  public static Matcher<AccessibilityCheckResult> matchesCheckNames(
+      final Matcher<? super String> classNameMatcher) {
+    if (classNameMatcher == null) {
+      return null;
+    }
+    return new TypeSafeMemberMatcher<AccessibilityCheckResult>("source check name",
+        classNameMatcher) {
+      @Override
+      public boolean matchesSafely(AccessibilityCheckResult result) {
+        return classNameMatcher.matches(result.getSourceCheckClass().getSimpleName());
+      }
+    };
+  }
+
+  /**
+   * Returns a {@link Matcher} for an {@link AccessibilityViewCheckResult} whose view
+   * matches the given matcher for a {@link View}.
+   *
+   * @param viewMatcher a {@code Matcher} for a {@code View}
+   * @return a {@code Matcher} for an {@code AccessibilityCheckResult}
+   */
+  public static Matcher<AccessibilityViewCheckResult> matchesViews(
+      final Matcher<? super View> viewMatcher) {
+    if (viewMatcher == null) {
+      return null;
+    }
+    return new TypeSafeMemberMatcher<AccessibilityViewCheckResult>("View", viewMatcher) {
+      @Override
+      public boolean matchesSafely(AccessibilityViewCheckResult result) {
+        return viewMatcher.matches(result.getView());
+      }
+    };
+  }
+
+  /**
+   * Returns a {@link Matcher} for an {@link AccessibilityNodeInfo} whose
+   * {@link AccessibilityNodeInfo} matches the given matcher.
+   *
+   * @param infoMatcher a {@code Matcher} for a {@code AccessibilityNodeInfo}
+   * @return a {@code Matcher} for an {@code AccessibilityCheckResult}
+   */
+  public static Matcher<AccessibilityInfoCheckResult> matchesInfos(
+      final Matcher<? super AccessibilityNodeInfo> infoMatcher) {
+    if (infoMatcher == null) {
+      return null;
+    }
+    return new TypeSafeMemberMatcher<AccessibilityInfoCheckResult>("AccessibilityNodeInfo",
+        infoMatcher) {
+      @Override
+      public boolean matchesSafely(AccessibilityInfoCheckResult result) {
+        return infoMatcher.matches(result.getInfo());
+      }
+    };
+  }
+
+  /**
+   * Change the result type to {@code SUPPRESSED} for all results in the given list that match the
+   * given matcher.
+   *
+   * @param results a list of {@code AccessibilityCheckResult}s to be matched against
+   * @param matcher a Matcher that determines whether a given {@code AccessibilityCheckResult}
+   *        should be suppressed
+   */
+  public static <T extends AccessibilityCheckResult> void suppressMatchingResults(List<T> results,
+      Matcher<? super T> matcher) {
+    modifyResultType(results, matcher, AccessibilityCheckResultType.SUPPRESSED);
+  }
+
+  private static <T extends AccessibilityCheckResult> void modifyResultType(List<T> results,
+      Matcher<? super T> matcher, AccessibilityCheckResultType newType) {
+    if (results == null || matcher == null) {
+      return;
+    }
+    for (T result : results) {
+      if (matcher.matches(result)) {
+        result.setType(newType);
+      }
+    }
+  }
+
+  private abstract static class TypeSafeMemberMatcher<T> extends TypeSafeMatcher<T> {
+    private static final String DESCRIPTION_FORMAT_STRING = "with %s: ";
+    private String memberDescription;
+    private Matcher<?> matcher;
+    public TypeSafeMemberMatcher(String member, Matcher<?> matcher) {
+      memberDescription = String.format(DESCRIPTION_FORMAT_STRING, member);
+      this.matcher = matcher;
+    }
+
+    @Override
+    public void describeTo(Description description) {
+      description.appendText(memberDescription);
+      matcher.describeTo(description);
+    }
+  }
 }
diff --git a/src/main/java/com/google/android/apps/common/testing/accessibility/framework/AccessibilityCheckUtils.java b/src/main/java/com/google/android/apps/common/testing/accessibility/framework/AccessibilityCheckUtils.java
index 9b4e10d..9d828ca 100644
--- a/src/main/java/com/google/android/apps/common/testing/accessibility/framework/AccessibilityCheckUtils.java
+++ b/src/main/java/com/google/android/apps/common/testing/accessibility/framework/AccessibilityCheckUtils.java
@@ -1,33 +1,32 @@
 /*
  * Copyright (C) 2014 Google Inc.
  *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
  *
  * http://www.apache.org/licenses/LICENSE-2.0
  *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
  * the License.
  */
 
 package com.google.android.apps.common.testing.accessibility.framework;
 
+import android.os.Build;
 import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
 import android.text.SpannableStringBuilder;
 import android.text.TextUtils;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.accessibility.AccessibilityNodeInfo;
+import android.widget.CompoundButton;
 import android.widget.TextView;
 
 import com.googlecode.eyesfree.utils.AccessibilityNodeInfoUtils;
+import com.googlecode.eyesfree.utils.StringBuilderUtils;
 
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
 import java.util.HashSet;
 import java.util.Set;
 
@@ -37,54 +36,14 @@
  * <p>All methods that take {@link View} or {@link AccessibilityNodeInfo} objects as arguments
  * require them to be fully initialized and in a valid hierarchy. Neither a newly constructed
  * {@link View} nor an object returned by {@code View.createAccessibilityNodeInfo()} have the
- * required properties because calls like {@code View.getRootView()} and {@code
- * AccessibilityNodeInfo.getParent} do not return reasonable values unless the objects are part
- * of a valid hierarchy.
+ * required properties because calls like {@code View.getRootView()} and
+ * {@code AccessibilityNodeInfo.getParent} do not return reasonable values unless the objects are
+ * part of a valid hierarchy.
  */
-public final class AccessibilityCheckUtils {
+final class AccessibilityCheckUtils {
 
   private AccessibilityCheckUtils() {}
 
-  private static Method isImportantForAccessibilityMethod;
-
-  static {
-    try {
-      isImportantForAccessibilityMethod = View.class.getMethod("isImportantForAccessibility");
-    } catch (NoSuchMethodException e) {
-      isImportantForAccessibilityMethod = null;
-    } catch (SecurityException e) {
-      isImportantForAccessibilityMethod = null;
-    }
-  }
-  /**
-   * @param rootView The root of a View hierarchy
-   * @return A Set containing the root view and all views below it in the hierarchy
-   */
-  public static Set<View> getAllViewsInHierarchy(View rootView) {
-    Set<View> allViews = new HashSet<View>();
-    allViews.add(rootView);
-    addAllChildrenToSet(rootView, allViews);
-    return allViews;
-  }
-
-  /**
-   * Add all children in the view tree rooted at rootView to a set
-   * @param rootView The root of the view tree desired
-   * @param theSet The set to add views to
-   */
-  private static void addAllChildrenToSet(View rootView, Set<View> theSet) {
-    if (!(rootView instanceof ViewGroup)) {
-      return;
-    }
-
-    ViewGroup rootViewGroup = (ViewGroup) rootView;
-    for (int i = 0; i < rootViewGroup.getChildCount(); ++i) {
-      View nextView = rootViewGroup.getChildAt(i);
-      theSet.add(nextView);
-      addAllChildrenToSet(nextView, theSet);
-    }
-  }
-
   /**
    * Retrieve text for a node, which may include text from children of the node. This text is an
    * approximation of, but not always identical to, what TalkBack would speak for the node. One
@@ -92,13 +51,45 @@
    *
    * @param info The info whose text should be returned.
    *
-   * @return Speakable text derived from the info and its children. Returns an empty string if
-   * there is no such text, and {@code null} if {@code info == null}.
+   * @return Speakable text derived from the info and its children. Returns an empty string if there
+   *         is no such text, and {@code null} if {@code info == null}.
    */
-  public static CharSequence getSpeakableTextForInfo(AccessibilityNodeInfo info) {
+  static CharSequence getSpeakableTextForInfo(AccessibilityNodeInfo info) {
     if (info == null) {
       return null;
     }
+
+    /* getLabeledBy for API 17+ */
+    if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN) {
+
+        AccessibilityNodeInfo labeledBy = info.getLabeledBy();
+        if (labeledBy != null) {
+          /* There could be a chain of labeledBy. Make sure it isn't a loop */
+          Set<AccessibilityNodeInfo> infosVisited = new HashSet<>();
+          infosVisited.add(info);
+          infosVisited.add(labeledBy);
+          AccessibilityNodeInfo endOfLabeledByChain = labeledBy.getLabeledBy();
+          while (endOfLabeledByChain != null) {
+            if (infosVisited.contains(endOfLabeledByChain)) {
+              infosVisited.remove(info);
+              for (AccessibilityNodeInfo infoVisited : infosVisited) {
+                infoVisited.recycle();
+              }
+              return null;
+            }
+            infosVisited.add(endOfLabeledByChain);
+            labeledBy = endOfLabeledByChain;
+            endOfLabeledByChain = labeledBy.getLabeledBy();
+          }
+          CharSequence labelText = getSpeakableTextForInfo(labeledBy);
+          infosVisited.remove(info);
+          for (AccessibilityNodeInfo infoVisited : infosVisited) {
+            infoVisited.recycle();
+          }
+          return labelText;
+        }
+    }
+
     AccessibilityNodeInfoCompat compat = new AccessibilityNodeInfoCompat(info);
 
     // TODO(caseburkhardt) Pull in TalkBack's actual logic
@@ -110,8 +101,8 @@
         AccessibilityNodeInfoCompat child = compat.getChild(i);
         if (AccessibilityNodeInfoUtils.isVisibleOrLegacy(child)
             && !AccessibilityNodeInfoUtils.isActionableForAccessibility(child)) {
-          returnStringBuilder
-              .append(getSpeakableTextForInfo((AccessibilityNodeInfo) child.getInfo()));
+          returnStringBuilder.append(
+              getSpeakableTextForInfo((AccessibilityNodeInfo) child.getInfo()));
         }
       }
     }
@@ -131,74 +122,55 @@
    * @param view The {@link View} whose text should be returned.
    *
    * @return Speakable text derived from the {@link View} and its children. Returns an empty string
-   * if there is no such text, and {@code null} if {@code view == null}.
+   *         if there is no such text, and {@code null} if {@code view == null}.
    */
-  public static CharSequence getSpeakableTextForView(View view) {
+  static CharSequence getSpeakableTextForView(View view) {
     if (view == null) {
       return null;
     }
 
-    SpannableStringBuilder returnStringBuilder = new SpannableStringBuilder("");
-
-    boolean isImportantForAccessibility;
-    /*
-     * TODO(pweaver): Call view.isImportantForAccessibility() if (Build.VERSION.SDK_INT). Need
-     * ViewCompat to support method
-     */
-    if (isImportantForAccessibilityMethod != null) {
-      try {
-        isImportantForAccessibility = (Boolean) isImportantForAccessibilityMethod.invoke(view);
-      } catch (IllegalAccessException e) {
-        e.printStackTrace();
-        isImportantForAccessibility = true;
-      } catch (IllegalArgumentException e) {
-        e.printStackTrace();
-        isImportantForAccessibility = true;
-      } catch (InvocationTargetException e) {
-        e.printStackTrace();
-        isImportantForAccessibility = true;
-      }
-    } else {
-      isImportantForAccessibility = true;
+    View labelForThisView = ViewAccessibilityUtils.getLabelForView(view);
+    if (labelForThisView != null) {
+      return getSpeakableTextForView(labelForThisView);
     }
 
-    /* Content descriptions override everything else */
-    if (isImportantForAccessibility) {
-        if (!TextUtils.isEmpty(view.getContentDescription())) {
-          return view.getContentDescription();
+    SpannableStringBuilder returnStringBuilder = new SpannableStringBuilder("");
+
+    // Accessibility importance is considered only on Jelly Bean and above
+    if ((Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN)
+        || ViewAccessibilityUtils.isImportantForAccessibility(view)) {
+      if (!TextUtils.isEmpty(view.getContentDescription())) {
+        // contentDescription always wins out over other properties
+        return view.getContentDescription();
+      }
+      if (view instanceof TextView) {
+        if (!TextUtils.isEmpty(((TextView) view).getText())) {
+          returnStringBuilder.append(((TextView) view).getText());
+        } else if (!TextUtils.isEmpty(((TextView) view).getHint())) {
+          returnStringBuilder.append(((TextView) view).getHint());
         }
-        if (view instanceof TextView) {
-          if (!TextUtils.isEmpty(((TextView) view).getText())) {
-            returnStringBuilder.append(((TextView) view).getText());
-          } else if (!TextUtils.isEmpty(((TextView) view).getHint())) {
-            returnStringBuilder.append(((TextView) view).getHint());
-          }
-        }
+      }
     }
 
     if (view instanceof ViewGroup) {
       ViewGroup group = (ViewGroup) view;
+      // TODO(sjrush): Only evaluate child views if they're importantForAccessibility.
       for (int i = 0; i < group.getChildCount(); ++i) {
         View childView = group.getChildAt(i);
         if ((childView.getVisibility() == View.VISIBLE)
-            && !isActionableForAccessibility(childView)) {
+            && !ViewAccessibilityUtils.isActionableForAccessibility(childView)) {
           returnStringBuilder.append(getSpeakableTextForView(childView));
         }
       }
     }
-    return returnStringBuilder;
-  }
 
-  /**
-   * @param view
-   * @return {@code true} if view's {@link AccessibilityNodeInfo} would be deemed actionable for
-   * accessibility.
-   */
-  private static boolean isActionableForAccessibility(View view) {
-    if (view == null) {
-      return false;
+    if (view instanceof CompoundButton) {
+      if (((CompoundButton) view).isChecked()) {
+        StringBuilderUtils.appendWithSeparator(returnStringBuilder, "Checked");
+      } else {
+        StringBuilderUtils.appendWithSeparator(returnStringBuilder, "Not checked");
+      }
     }
-
-    return ((view.isClickable() || view.isLongClickable()) || view.isFocusable());
+    return returnStringBuilder;
   }
 }
diff --git a/src/main/java/com/google/android/apps/common/testing/accessibility/framework/AccessibilityInfoCheck.java b/src/main/java/com/google/android/apps/common/testing/accessibility/framework/AccessibilityInfoCheck.java
index 6e0e1d6..379aa58 100644
--- a/src/main/java/com/google/android/apps/common/testing/accessibility/framework/AccessibilityInfoCheck.java
+++ b/src/main/java/com/google/android/apps/common/testing/accessibility/framework/AccessibilityInfoCheck.java
@@ -16,20 +16,37 @@
 
 package com.google.android.apps.common.testing.accessibility.framework;
 
+import android.content.Context;
+import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
 import android.view.accessibility.AccessibilityNodeInfo;
 
+import java.util.ArrayList;
 import java.util.List;
 
 /**
  * Base class to check the accessibility of {@code AccessibilityNodeInfo}s.
  */
-public abstract class AccessibilityInfoCheck extends AccessibilityCheck {
+public abstract class AccessibilityInfoCheck extends AccessibilityInfoHierarchyCheck {
+  @Override
+  public List<AccessibilityInfoCheckResult> runCheckOnInfoHierarchy(
+      AccessibilityNodeInfo root, Context context) {
+    List<AccessibilityInfoCheckResult> results = new ArrayList<>();
+    List<AccessibilityNodeInfoCompat> compatInfos = getAllInfoCompatsInHierarchy(context, root);
+    for (AccessibilityNodeInfoCompat compatInfo : compatInfos) {
+      AccessibilityNodeInfo info = (AccessibilityNodeInfo) compatInfo.getInfo();
+      results.addAll(runCheckOnInfo(info, context));
+    }
+    return results;
+  }
+
   /**
    * Run the check on the view.
    * @param info The node info to check. The info should be fully initialized and part of a valid
    * hierarchy so all of its methods can be called.
+   * @param context The context of the service.
    * @return A list of interesting results encountered while running the check. The list will be
    * empty if the check passes without incident.
    */
-  public abstract List<AccessibilityInfoCheckResult> runCheckOnInfo(AccessibilityNodeInfo info);
+  public abstract List<AccessibilityInfoCheckResult> runCheckOnInfo(AccessibilityNodeInfo info,
+      Context context);
 }
diff --git a/src/main/java/com/google/android/apps/common/testing/accessibility/framework/AccessibilityInfoCheckResult.java b/src/main/java/com/google/android/apps/common/testing/accessibility/framework/AccessibilityInfoCheckResult.java
index f297e27..382573a 100644
--- a/src/main/java/com/google/android/apps/common/testing/accessibility/framework/AccessibilityInfoCheckResult.java
+++ b/src/main/java/com/google/android/apps/common/testing/accessibility/framework/AccessibilityInfoCheckResult.java
@@ -1,47 +1,132 @@
 /*
  * Copyright (C) 2014 Google Inc.
  *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
  *
  * http://www.apache.org/licenses/LICENSE-2.0
  *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
  * the License.
  */
 
 package com.google.android.apps.common.testing.accessibility.framework;
 
+import android.annotation.TargetApi;
+import android.os.Build;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.Log;
 import android.view.accessibility.AccessibilityNodeInfo;
 
+import com.googlecode.eyesfree.utils.LogUtils;
+
 /**
  * Result generated when an accessibility check runs on a {@code AccessibilityNodeInfo}.
  */
-public class AccessibilityInfoCheckResult extends AccessibilityCheckResult {
+@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
+public final class AccessibilityInfoCheckResult extends AccessibilityCheckResult implements
+    Parcelable {
 
   private AccessibilityNodeInfo info;
 
   /**
-   * @param check The check that generated the error
+   * @param checkClass The check that generated the error
    * @param type The type of the result
    * @param message A human-readable message explaining the error
    * @param info The info that was responsible for generating the error
    */
-  AccessibilityInfoCheckResult(AccessibilityCheck check, AccessibilityCheckResultType type,
-      CharSequence message, AccessibilityNodeInfo info) {
-    super(check, type, message);
-    this.info = info;
+  public AccessibilityInfoCheckResult(Class<? extends AccessibilityCheck> checkClass,
+      AccessibilityCheckResultType type, CharSequence message, AccessibilityNodeInfo info) {
+    super(checkClass, type, message);
+    if (info != null) {
+      this.info = AccessibilityNodeInfo.obtain(info);
+    }
+  }
+
+  private AccessibilityInfoCheckResult(Parcel in) {
+    super(null, null, null);
+    readFromParcel(in);
   }
 
   /**
    * @return The info to which the result applies.
    */
-  AccessibilityNodeInfo getInfo() {
+  public AccessibilityNodeInfo getInfo() {
     return info;
   }
 
+  @Override
+  public void recycle() {
+    super.recycle();
+
+    if (info != null) {
+      info.recycle();
+      info = null;
+    }
+  }
+
+  @Override
+  public int describeContents() {
+    return 0;
+  }
+
+  @Override
+  public void writeToParcel(Parcel dest, int flags) {
+    dest.writeString((checkClass != null) ? checkClass.getName() : "");
+    dest.writeInt((type != null) ? type.ordinal() : -1);
+    TextUtils.writeToParcel(message, dest, flags);
+    // Info requires a presence flag
+    if (info != null) {
+      dest.writeInt(1);
+      info.writeToParcel(dest, flags);
+    } else {
+      dest.writeInt(0);
+    }
+  }
+
+  @SuppressWarnings("unchecked")
+  private void readFromParcel(Parcel in) {
+    // Check class (unchecked cast checked by isAssignableFrom)
+    checkClass = null;
+    String checkClassName = in.readString();
+    if (!("".equals(checkClassName))) {
+      try {
+        Class<?> uncheckedClass = Class.forName(checkClassName);
+        if (AccessibilityCheck.class.isAssignableFrom(uncheckedClass)) {
+          checkClass = (Class<? extends AccessibilityCheck>) uncheckedClass;
+        }
+      } catch (ClassNotFoundException e) {
+        // If the reference can't be resolved by our class loader, remain null.
+        LogUtils.log(this, Log.WARN, "Attempt to obtain unknown class %1$s", checkClassName);
+      }
+    }
+
+    // Type
+    final int type = in.readInt();
+    this.type = (type != -1) ? AccessibilityCheckResultType.values()[type] : null;
+
+    // Message
+    this.message = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+
+    // Info
+    this.info = (in.readInt() == 1) ? AccessibilityNodeInfo.CREATOR.createFromParcel(in) : null;
+
+  }
+
+  public static final Parcelable.Creator<AccessibilityInfoCheckResult> CREATOR =
+      new Parcelable.Creator<AccessibilityInfoCheckResult>() {
+        @Override
+        public AccessibilityInfoCheckResult createFromParcel(Parcel in) {
+          return new AccessibilityInfoCheckResult(in);
+        }
+
+        @Override
+        public AccessibilityInfoCheckResult[] newArray(int size) {
+          return new AccessibilityInfoCheckResult[size];
+        }
+      };
 }
diff --git a/src/main/java/com/google/android/apps/common/testing/accessibility/framework/AccessibilityInfoHierarchyCheck.java b/src/main/java/com/google/android/apps/common/testing/accessibility/framework/AccessibilityInfoHierarchyCheck.java
index 83eeb26..8e85858 100644
--- a/src/main/java/com/google/android/apps/common/testing/accessibility/framework/AccessibilityInfoHierarchyCheck.java
+++ b/src/main/java/com/google/android/apps/common/testing/accessibility/framework/AccessibilityInfoHierarchyCheck.java
@@ -16,24 +16,44 @@
 
 package com.google.android.apps.common.testing.accessibility.framework;
 
+import android.content.Context;
+import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
 import android.view.accessibility.AccessibilityNodeInfo;
 
+import com.googlecode.eyesfree.utils.AccessibilityNodeInfoUtils;
+import com.googlecode.eyesfree.utils.NodeFilter;
+
 import java.util.List;
 
 /**
  * Base class to check the accessibility of all {@code Info}s in a hierarchy.
  */
 public abstract class AccessibilityInfoHierarchyCheck extends AccessibilityCheck {
+  private static final NodeFilter WIDE_OPEN_FILTER = new NodeFilter() {
+    @Override
+    public boolean accept(Context context, AccessibilityNodeInfoCompat node) {
+      return true;
+    }
+  };
+
   public AccessibilityInfoHierarchyCheck() {
   }
 
   /**
    * Run the check on the info.
    * @param root The root of the AccessibilityNodeInfo hierarchy to check.
+   * @param context The context of the service.
    * @return A list of interesting results encountered while running the check. The list will be
    * empty if the check passes without incident.
    */
   public abstract List<AccessibilityInfoCheckResult> runCheckOnInfoHierarchy(
-      AccessibilityNodeInfo root);
+      AccessibilityNodeInfo root, Context context);
+
+  static List<AccessibilityNodeInfoCompat> getAllInfoCompatsInHierarchy(Context context,
+      AccessibilityNodeInfo root) {
+    return AccessibilityNodeInfoUtils.searchAllFromBfs(context,
+        new AccessibilityNodeInfoCompat(root),
+        AccessibilityInfoHierarchyCheck.WIDE_OPEN_FILTER);
+  }
 
 }
diff --git a/src/main/java/com/google/android/apps/common/testing/accessibility/framework/AccessibilityViewCheck.java b/src/main/java/com/google/android/apps/common/testing/accessibility/framework/AccessibilityViewCheck.java
index 0865ab8..f156975 100644
--- a/src/main/java/com/google/android/apps/common/testing/accessibility/framework/AccessibilityViewCheck.java
+++ b/src/main/java/com/google/android/apps/common/testing/accessibility/framework/AccessibilityViewCheck.java
@@ -18,15 +18,30 @@
 
 import android.view.View;
 
+import java.util.ArrayList;
 import java.util.List;
+import java.util.Set;
 
 /**
- * Base class to check the accessibility of {@code View}s.
+ * Base class for checking a single View.
+ * Use hierarchy checks instead. All single-view checks are being rewritten to be
+ * hierarchy checks instead.
  */
-public abstract class AccessibilityViewCheck extends AccessibilityCheck {
+@Deprecated
+public abstract class AccessibilityViewCheck extends AccessibilityViewHierarchyCheck {
   public AccessibilityViewCheck() {
   }
 
+  @Override
+  public List<AccessibilityViewCheckResult> runCheckOnViewHierarchy(View root) {
+    List<AccessibilityViewCheckResult> results = new ArrayList<>();
+    Set<View> viewsToCheck = ViewAccessibilityUtils.getAllViewsInHierarchy(root);
+    for (View view : viewsToCheck) {
+      results.addAll(runCheckOnView(view));
+    }
+    return results;
+  }
+
   /**
    * Run the check on the view.
    * @param view The view to check. The view should be fully initialized and part of a valid view
@@ -35,5 +50,4 @@
    * empty if the check passes without incident.
    */
   public abstract List<AccessibilityViewCheckResult> runCheckOnView(View view);
-
 }
diff --git a/src/main/java/com/google/android/apps/common/testing/accessibility/framework/AccessibilityViewCheckResult.java b/src/main/java/com/google/android/apps/common/testing/accessibility/framework/AccessibilityViewCheckResult.java
index 30962b3..1f0dbd4 100644
--- a/src/main/java/com/google/android/apps/common/testing/accessibility/framework/AccessibilityViewCheckResult.java
+++ b/src/main/java/com/google/android/apps/common/testing/accessibility/framework/AccessibilityViewCheckResult.java
@@ -1,16 +1,14 @@
 /*
  * Copyright (C) 2014 Google Inc.
  *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
  *
  * http://www.apache.org/licenses/LICENSE-2.0
  *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
  * the License.
  */
 
@@ -26,14 +24,14 @@
   private View view;
 
   /**
-   * @param check The check that generated the error
+   * @param checkClass The check that generated the error
    * @param type The type of the result
    * @param message A human-readable message explaining the error
    * @param view The view that was responsible for generating the error
    */
-  AccessibilityViewCheckResult(AccessibilityCheck check, AccessibilityCheckResultType type,
-      CharSequence message, View view) {
-    super(check, type, message);
+  public AccessibilityViewCheckResult(Class<? extends AccessibilityCheck> checkClass,
+      AccessibilityCheckResultType type, CharSequence message, View view) {
+    super(checkClass, type, message);
     this.view = view;
   }
 
@@ -44,4 +42,9 @@
     return view;
   }
 
+  @Override
+  public void recycle() {
+    super.recycle();
+    view = null;
+  }
 }
diff --git a/src/main/java/com/google/android/apps/common/testing/accessibility/framework/DuplicateSpeakableTextViewHierarchyCheck.java b/src/main/java/com/google/android/apps/common/testing/accessibility/framework/DuplicateSpeakableTextViewHierarchyCheck.java
index 1f42e3b..b934a2e 100644
--- a/src/main/java/com/google/android/apps/common/testing/accessibility/framework/DuplicateSpeakableTextViewHierarchyCheck.java
+++ b/src/main/java/com/google/android/apps/common/testing/accessibility/framework/DuplicateSpeakableTextViewHierarchyCheck.java
@@ -1,16 +1,14 @@
 /*
  * Copyright (C) 2014 Google Inc.
  *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
  *
  * http://www.apache.org/licenses/LICENSE-2.0
  *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
  * the License.
  */
 
@@ -18,20 +16,22 @@
 
 import com.google.android.apps.common.testing.accessibility.framework.AccessibilityCheckResult.AccessibilityCheckResultType;
 
+import android.os.Build;
 import android.text.TextUtils;
 import android.view.View;
 
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 import java.util.Set;
 
 /**
- * If two Views in a hierarchy have the same speakable text, that could be confusing for users.
- * Two Views with the same text, and at least one of them is clickable we warn in that situation.
- * If we find two non-clickable Views with the same speakable text, we report that fact as info.
- * If no Views in the hierarchy have any speakable text, we report that the test was not run.
+ * If two Views in a hierarchy have the same speakable text, that could be confusing for users. Two
+ * Views with the same text, and at least one of them is clickable we warn in that situation. If we
+ * find two non-clickable Views with the same speakable text, we report that fact as info. If no
+ * Views in the hierarchy have any speakable text, we report that the test was not run.
  */
 public class DuplicateSpeakableTextViewHierarchyCheck extends AccessibilityViewHierarchyCheck {
 
@@ -39,8 +39,15 @@
   public List<AccessibilityViewCheckResult> runCheckOnViewHierarchy(View root) {
     List<AccessibilityViewCheckResult> results = new ArrayList<AccessibilityViewCheckResult>();
 
+    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
+      results.add(new AccessibilityViewCheckResult(this.getClass(),
+          AccessibilityCheckResultType.NOT_RUN, "This check only runs on Andorid 4.1 and above.",
+          root));
+      return results;
+    }
+
     /* Find all text and the views that have that text */
-    Set<View> allViews = AccessibilityCheckUtils.getAllViewsInHierarchy(root);
+    Set<View> allViews = ViewAccessibilityUtils.getAllViewsInHierarchy(root);
     Map<String, List<View>> textToViewMap = getSpeakableTextToViewMap(allViews);
 
     /* Deal with any duplicated text */
@@ -62,36 +69,40 @@
 
       if (clickableViews.size() > 0) {
         /* Display warning */
-        results.add(new AccessibilityViewCheckResult(this, AccessibilityCheckResultType.WARNING,
-            String.format("Clickable view's speakable text: \"%s\" is identical to that of %d "
+        results.add(new AccessibilityViewCheckResult(this.getClass(),
+            AccessibilityCheckResultType.WARNING, String.format(Locale.US,
+                "Clickable view's speakable text: \"%s\" is identical to that of %d "
                 + "other view(s)", speakableText, nonClickableViews.size()),
-                clickableViews.get(0)));
+            clickableViews.get(0)));
         clickableViews.remove(0);
       } else {
         /* Only duplication is on non-clickable views */
-        results.add(new AccessibilityViewCheckResult(this, AccessibilityCheckResultType.INFO,
-            String.format("Non-clickable view's speakable text: \"%s\" is identical to that of %d "
+        results.add(new AccessibilityViewCheckResult(this.getClass(),
+            AccessibilityCheckResultType.INFO, String.format(Locale.US,
+                "Non-clickable view's speakable text: \"%s\" is identical to that of %d "
                 + "other non-clickable view(s)", speakableText, nonClickableViews.size() - 1),
-                nonClickableViews.get(0)));
+            nonClickableViews.get(0)));
         nonClickableViews.remove(0);
       }
 
       /* Add infos to help track down the duplication */
       for (View clickableView : clickableViews) {
-        results.add(new AccessibilityViewCheckResult(this, AccessibilityCheckResultType.INFO,
+        results.add(new AccessibilityViewCheckResult(this.getClass(),
+            AccessibilityCheckResultType.INFO,
             String.format("  Clickable View has speakable text: \"%s\".", speakableText),
             clickableView));
       }
       for (View clickableView : nonClickableViews) {
-        results.add(new AccessibilityViewCheckResult(this, AccessibilityCheckResultType.INFO,
+        results.add(new AccessibilityViewCheckResult(this.getClass(),
+            AccessibilityCheckResultType.INFO,
             String.format("  Non-clickable View has speakable text: \"%s\".", speakableText),
             clickableView));
       }
     }
 
     if (textToViewMap.size() == 0) {
-      results.add(new AccessibilityViewCheckResult(this, AccessibilityCheckResultType.NOT_RUN,
-          "No Views in hierarchy have speakable text", root));
+      results.add(new AccessibilityViewCheckResult(this.getClass(),
+          AccessibilityCheckResultType.NOT_RUN, "No Views in hierarchy have speakable text", root));
       return results;
     }
 
@@ -106,8 +117,8 @@
     Map<String, List<View>> textToViewMap = new HashMap<String, List<View>>();
 
     for (View view : allViews) {
-      String speakableText = AccessibilityCheckUtils.getSpeakableTextForView(view)
-          .toString().trim();
+      String speakableText =
+          AccessibilityCheckUtils.getSpeakableTextForView(view).toString().trim();
       if (TextUtils.isEmpty(speakableText)) {
         continue;
       }
diff --git a/src/main/java/com/google/android/apps/common/testing/accessibility/framework/EditableContentDescInfoCheck.java b/src/main/java/com/google/android/apps/common/testing/accessibility/framework/EditableContentDescInfoCheck.java
index 076b96a..6515ebf 100644
--- a/src/main/java/com/google/android/apps/common/testing/accessibility/framework/EditableContentDescInfoCheck.java
+++ b/src/main/java/com/google/android/apps/common/testing/accessibility/framework/EditableContentDescInfoCheck.java
@@ -1,16 +1,14 @@
 /*
  * Copyright (C) 2014 Google Inc.
  *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
  *
  * http://www.apache.org/licenses/LICENSE-2.0
  *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
  * the License.
  */
 
@@ -18,6 +16,7 @@
 
 import com.google.android.apps.common.testing.accessibility.framework.AccessibilityCheckResult.AccessibilityCheckResultType;
 
+import android.content.Context;
 import android.os.Build;
 import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
 import android.text.TextUtils;
@@ -35,19 +34,21 @@
 public class EditableContentDescInfoCheck extends AccessibilityInfoCheck {
 
   @Override
-  public List<AccessibilityInfoCheckResult> runCheckOnInfo(AccessibilityNodeInfo info) {
+  public List<AccessibilityInfoCheckResult> runCheckOnInfo(AccessibilityNodeInfo info,
+      Context context) {
     List<AccessibilityInfoCheckResult> results = new ArrayList<AccessibilityInfoCheckResult>(1);
     AccessibilityNodeInfoCompat compatInfo = new AccessibilityNodeInfoCompat(info);
 
-    if (getAndroidVersion() >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
+    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
       if (info.isEditable()) {
         if (!TextUtils.isEmpty(info.getContentDescription())) {
-        results.add(new AccessibilityInfoCheckResult(this, AccessibilityCheckResultType.ERROR,
-            "Editable view should not have a contentDescription", info));
+          results.add(new AccessibilityInfoCheckResult(this.getClass(),
+              AccessibilityCheckResultType.ERROR,
+              "Editable view should not have a contentDescription", info));
         }
       } else {
-        results.add(new AccessibilityInfoCheckResult(this, AccessibilityCheckResultType.NOT_RUN,
-            "Associated view must be editable", info));
+        results.add(new AccessibilityInfoCheckResult(this.getClass(),
+            AccessibilityCheckResultType.NOT_RUN, "Associated view must be editable", info));
       }
     } else {
       // Earlier API versions don't allow us to filter based on editable state, so we'll fall back
@@ -57,24 +58,15 @@
       // defined within other packages.
       if (AccessibilityNodeInfoUtils.nodeMatchesAnyClassByType(null, compatInfo, EditText.class)) {
         if (!TextUtils.isEmpty(compatInfo.getContentDescription())) {
-          results.add(new AccessibilityInfoCheckResult(this, AccessibilityCheckResultType.ERROR,
-              "EditText should not have a contentDescription", info));
+          results.add(new AccessibilityInfoCheckResult(this.getClass(),
+              AccessibilityCheckResultType.ERROR, "EditText should not have a contentDescription",
+              info));
         }
       } else {
-        results.add(new AccessibilityInfoCheckResult(this, AccessibilityCheckResultType.NOT_RUN,
-            "Associated view must be an EditText", info));
+        results.add(new AccessibilityInfoCheckResult(this.getClass(),
+            AccessibilityCheckResultType.NOT_RUN, "Associated view must be an EditText", info));
       }
     }
     return results;
   }
-
-  /**
-   * @return The Android Build.VERSION.SDK_INT value for the current runtime.
-   *
-   * NOTE: This wrapper exists only for testing.
-   */
-  /*package*/ int getAndroidVersion() {
-    // TODO(caseyburkhardt): Find a more robust way to support testing of this.
-    return Build.VERSION.SDK_INT;
-  }
 }
diff --git a/src/main/java/com/google/android/apps/common/testing/accessibility/framework/EditableContentDescViewCheck.java b/src/main/java/com/google/android/apps/common/testing/accessibility/framework/EditableContentDescViewCheck.java
index ead3f6d..342f917 100644
--- a/src/main/java/com/google/android/apps/common/testing/accessibility/framework/EditableContentDescViewCheck.java
+++ b/src/main/java/com/google/android/apps/common/testing/accessibility/framework/EditableContentDescViewCheck.java
@@ -37,16 +37,17 @@
       TextView textView = (TextView) view;
       if ((textView.getEditableText() != null)) {
         if (!TextUtils.isEmpty(textView.getContentDescription())) {
-          results.add(new AccessibilityViewCheckResult(this, AccessibilityCheckResultType.ERROR,
+          results.add(new AccessibilityViewCheckResult(this.getClass(),
+              AccessibilityCheckResultType.ERROR,
               "Editable TextView should not have a contentDescription.", textView));
         }
       } else {
-        results.add(new AccessibilityViewCheckResult(this, AccessibilityCheckResultType.NOT_RUN,
-            "TextView must be editable", textView));
+        results.add(new AccessibilityViewCheckResult(this.getClass(),
+            AccessibilityCheckResultType.NOT_RUN, "TextView must be editable", textView));
       }
     } else {
-      results.add(new AccessibilityViewCheckResult(this, AccessibilityCheckResultType.NOT_RUN,
-          "View must be a TextView", view));
+      results.add(new AccessibilityViewCheckResult(this.getClass(),
+          AccessibilityCheckResultType.NOT_RUN, "View must be a TextView", view));
     }
 
     return results;
diff --git a/src/main/java/com/google/android/apps/common/testing/accessibility/framework/SpeakableTextPresentInfoCheck.java b/src/main/java/com/google/android/apps/common/testing/accessibility/framework/SpeakableTextPresentInfoCheck.java
index 99f985a..f048141 100644
--- a/src/main/java/com/google/android/apps/common/testing/accessibility/framework/SpeakableTextPresentInfoCheck.java
+++ b/src/main/java/com/google/android/apps/common/testing/accessibility/framework/SpeakableTextPresentInfoCheck.java
@@ -1,16 +1,14 @@
 /*
  * Copyright (C) 2014 Google Inc.
  *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
  *
  * http://www.apache.org/licenses/LICENSE-2.0
  *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
  * the License.
  */
 
@@ -18,23 +16,57 @@
 
 import com.google.android.apps.common.testing.accessibility.framework.AccessibilityCheckResult.AccessibilityCheckResultType;
 
+import android.content.Context;
+import android.support.v4.view.ViewPager;
+import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
 import android.text.TextUtils;
+import android.view.ViewGroup;
 import android.view.accessibility.AccessibilityNodeInfo;
+import android.webkit.WebView;
+import android.widget.ListView;
+import android.widget.ScrollView;
+
+import com.googlecode.eyesfree.utils.AccessibilityNodeInfoUtils;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 
 /**
  * Check to ensure that an info has speakable text for a screen reader
  */
 public class SpeakableTextPresentInfoCheck extends AccessibilityInfoCheck {
+  private static List<Class<? extends ViewGroup>> blacklistedViewTypes =
+      /*
+       * TODO(pweaver) Revisit this list once we have robust testing for info checks. It should
+       * only contain classes that can't be handled any other way. ShouldFocusNode likely
+       * handles many of them, for example.
+       */
+      Arrays.asList(ListView.class, ScrollView.class, ViewPager.class, WebView.class);
 
   @Override
-  public List<AccessibilityInfoCheckResult> runCheckOnInfo(AccessibilityNodeInfo info) {
+  public List<AccessibilityInfoCheckResult> runCheckOnInfo(AccessibilityNodeInfo info,
+      Context context) {
     List<AccessibilityInfoCheckResult> results = new ArrayList<AccessibilityInfoCheckResult>();
-    if (TextUtils.isEmpty(AccessibilityCheckUtils.getSpeakableTextForInfo(info))) {
-      results.add(new AccessibilityInfoCheckResult(this, AccessibilityCheckResultType.ERROR,
-          "View is missing speakable text needed for a screen reader", info));
+    AccessibilityNodeInfoCompat compatInfo = new AccessibilityNodeInfoCompat(info);
+    for (Class<? extends ViewGroup> clazz : blacklistedViewTypes) {
+      if (AccessibilityNodeInfoUtils.nodeMatchesAnyClassByType(null, compatInfo, clazz)) {
+        String msg =
+            String.format("Views of type %s are not checked for speakable text.", clazz.getName());
+        results.add(new AccessibilityInfoCheckResult(this.getClass(),
+            AccessibilityCheckResultType.NOT_RUN, msg, info));
+        return results;
+      }
+    }
+    if (AccessibilityNodeInfoUtils.shouldFocusNode(context, compatInfo)) {
+      if (TextUtils.isEmpty(AccessibilityCheckUtils.getSpeakableTextForInfo(info))) {
+        results.add(new AccessibilityInfoCheckResult(this.getClass(),
+            AccessibilityCheckResultType.ERROR,
+            "View is missing speakable text needed for a screen reader", info));
+      }
+    } else {
+      results.add(new AccessibilityInfoCheckResult(this.getClass(),
+          AccessibilityCheckResultType.NOT_RUN, "View is not focused by screen readers", info));
     }
     return results;
   }
diff --git a/src/main/java/com/google/android/apps/common/testing/accessibility/framework/SpeakableTextPresentViewCheck.java b/src/main/java/com/google/android/apps/common/testing/accessibility/framework/SpeakableTextPresentViewCheck.java
index 7de3708..5c99eee 100644
--- a/src/main/java/com/google/android/apps/common/testing/accessibility/framework/SpeakableTextPresentViewCheck.java
+++ b/src/main/java/com/google/android/apps/common/testing/accessibility/framework/SpeakableTextPresentViewCheck.java
@@ -1,16 +1,14 @@
 /*
  * Copyright (C) 2014 Google Inc.
  *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
  *
  * http://www.apache.org/licenses/LICENSE-2.0
  *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
  * the License.
  */
 
@@ -18,22 +16,66 @@
 
 import com.google.android.apps.common.testing.accessibility.framework.AccessibilityCheckResult.AccessibilityCheckResultType;
 
+import android.os.Build;
+import android.support.v4.view.ViewPager;
 import android.text.TextUtils;
 import android.view.View;
+import android.view.ViewGroup;
+import android.webkit.WebView;
+import android.widget.ListView;
+import android.widget.ScrollView;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 
 /**
  * Check to ensure that a view has a content description for a screen reader
  */
 public class SpeakableTextPresentViewCheck extends AccessibilityViewCheck {
+  /*
+   * TODO(pweaver) Determine if we can reduce this list, and add notes for those that need to
+   * be here.
+   * WebView: This class doesn't behave consistently across platforms, and it can report
+   * that it has no children even when it is displaying content.
+   */
+  private static final List<Class<? extends ViewGroup>> blacklistedViewTypes =
+      Arrays.asList(ListView.class, ScrollView.class, ViewPager.class, WebView.class);
+
   @Override
   public List<AccessibilityViewCheckResult> runCheckOnView(View view) {
     List<AccessibilityViewCheckResult> results = new ArrayList<AccessibilityViewCheckResult>();
-    if (TextUtils.isEmpty(AccessibilityCheckUtils.getSpeakableTextForView(view))) {
-      results.add(new AccessibilityViewCheckResult(this, AccessibilityCheckResultType.ERROR,
-          "View is missing speakable text needed for a screen reader", view));
+
+    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
+      results.add(new AccessibilityViewCheckResult(this.getClass(),
+          AccessibilityCheckResultType.NOT_RUN, "This check only runs on Android 4.1 and above.",
+          view));
+      return results;
+    }
+    if (!ViewAccessibilityUtils.isImportantForAccessibility(view)) {
+      results.add(new AccessibilityViewCheckResult(this.getClass(),
+          AccessibilityCheckResultType.NOT_RUN, "View is not important for accessibility.", view));
+      return results;
+    }
+    for (Class<? extends ViewGroup> clazz : blacklistedViewTypes) {
+      if (clazz.isAssignableFrom(view.getClass())) {
+        String msg =
+            String.format("Views of type %s are not checked for speakable text.", clazz.getName());
+        results.add(new AccessibilityViewCheckResult(this.getClass(),
+            AccessibilityCheckResultType.NOT_RUN, msg, view));
+        return results;
+      }
+    }
+    if (ViewAccessibilityUtils.shouldFocusView(view)) {
+      // We must evaluate this view for speakable text
+      if (TextUtils.isEmpty(AccessibilityCheckUtils.getSpeakableTextForView(view))) {
+        results.add(new AccessibilityViewCheckResult(this.getClass(),
+            AccessibilityCheckResultType.ERROR,
+            "View is missing speakable text needed for a screen reader", view));
+      }
+    } else {
+      results.add(new AccessibilityViewCheckResult(this.getClass(),
+          AccessibilityCheckResultType.NOT_RUN, "View is not focused by a screen reader", view));
     }
     return results;
   }
diff --git a/src/main/java/com/google/android/apps/common/testing/accessibility/framework/TextContrastViewCheck.java b/src/main/java/com/google/android/apps/common/testing/accessibility/framework/TextContrastViewCheck.java
index 4122370..3bac93e 100644
--- a/src/main/java/com/google/android/apps/common/testing/accessibility/framework/TextContrastViewCheck.java
+++ b/src/main/java/com/google/android/apps/common/testing/accessibility/framework/TextContrastViewCheck.java
@@ -1,16 +1,14 @@
 /*
  * Copyright (C) 2014 Google Inc.
  *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
  *
  * http://www.apache.org/licenses/LICENSE-2.0
  *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
  * the License.
  */
 
@@ -22,6 +20,7 @@
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
 import android.os.Build;
+import android.text.TextUtils;
 import android.view.View;
 import android.widget.TextView;
 
@@ -29,6 +28,7 @@
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Locale;
 
 /**
  * Check to ensure that a TextView has sufficient contrast between text color and background color
@@ -39,7 +39,7 @@
   public List<AccessibilityViewCheckResult> runCheckOnView(View view) {
     ArrayList<AccessibilityViewCheckResult> results = new ArrayList<AccessibilityViewCheckResult>();
     // TODO(caseyburkhardt): Implement this for other types of views
-    if (view instanceof TextView) {
+    if ((view instanceof TextView) && (!TextUtils.isEmpty(((TextView) view).getText()))) {
       TextView textView = (TextView) view;
       int textColor = textView.getCurrentTextColor();
       Drawable background = textView.getBackground();
@@ -53,34 +53,36 @@
               ContrastUtils.calculateLuminance(textColor),
               ContrastUtils.calculateLuminance(backgroundColor));
           double backgroundAlpha = Color.alpha(backgroundColor);
-          double requiredContrast = isLargeText(textView)
-              ? ContrastUtils.CONTRAST_RATIO_WCAG_LARGE_TEXT
-              : ContrastUtils.CONTRAST_RATIO_WCAG_NORMAL_TEXT;
+          double requiredContrast =
+              isLargeText(textView) ? ContrastUtils.CONTRAST_RATIO_WCAG_LARGE_TEXT
+                  : ContrastUtils.CONTRAST_RATIO_WCAG_NORMAL_TEXT;
           if (contrast < requiredContrast) {
             if (backgroundAlpha < 255) {
               // Cannot guarantee contrast ratio if the background is not opaque
               String message = "View's background color must be opaque";
-              results.add(new AccessibilityViewCheckResult(
-                  this, AccessibilityCheckResultType.NOT_RUN, message, view));
+              results.add(new AccessibilityViewCheckResult(this.getClass(),
+                  AccessibilityCheckResultType.NOT_RUN, message, view));
             } else {
-              String message = String.format("TextView does not have required contrast of "
-                  + "%f. Actual contrast is %f", requiredContrast, contrast);
-              results.add(new AccessibilityViewCheckResult(
-                  this, AccessibilityCheckResultType.ERROR, message, view));
+              String message = String.format(Locale.US,
+                  "TextView does not have required contrast of " + "%f. Actual contrast is %f",
+                  requiredContrast, contrast);
+              results.add(new AccessibilityViewCheckResult(this.getClass(),
+                  AccessibilityCheckResultType.ERROR, message, view));
             }
           }
         } else {
           String message = "Cannot be run on API levels lower than 11";
-          results.add(new AccessibilityViewCheckResult(
-                  this, AccessibilityCheckResultType.NOT_RUN, message, view));
+          results.add(new AccessibilityViewCheckResult(this.getClass(),
+              AccessibilityCheckResultType.NOT_RUN, message, view));
         }
-      } else {  // non-solid background color
-        results.add(new AccessibilityViewCheckResult(this, AccessibilityCheckResultType.NOT_RUN,
-            "TextView does not have a solid background color", view));
+      } else { // non-solid background color
+        results.add(new AccessibilityViewCheckResult(this.getClass(),
+            AccessibilityCheckResultType.NOT_RUN, "TextView does not have a solid background color",
+            view));
       }
     } else {
-      results.add(new AccessibilityViewCheckResult(this, AccessibilityCheckResultType.NOT_RUN,
-          "View must be a TextView", view));
+      results.add(new AccessibilityViewCheckResult(this.getClass(),
+          AccessibilityCheckResultType.NOT_RUN, "View must be a non-empty TextView", view));
     }
     return results;
   }
@@ -91,9 +93,9 @@
    */
   private static boolean isLargeText(TextView textView) {
     float textSize = textView.getTextSize();
-    if ((textSize >= ContrastUtils.WCAG_LARGE_TEXT_MIN_SIZE)
-        || ((textSize >= ContrastUtils.WCAG_LARGE_BOLD_TEXT_MIN_SIZE)
-            && textView.getTypeface().isBold())) {
+    if ((textSize >= ContrastUtils.WCAG_LARGE_TEXT_MIN_SIZE) || (
+        (textSize >= ContrastUtils.WCAG_LARGE_BOLD_TEXT_MIN_SIZE)
+        && textView.getTypeface().isBold())) {
       return true;
     }
     return false;
diff --git a/src/main/java/com/google/android/apps/common/testing/accessibility/framework/TouchTargetSizeViewCheck.java b/src/main/java/com/google/android/apps/common/testing/accessibility/framework/TouchTargetSizeViewCheck.java
index 1cceced..2504d8f 100644
--- a/src/main/java/com/google/android/apps/common/testing/accessibility/framework/TouchTargetSizeViewCheck.java
+++ b/src/main/java/com/google/android/apps/common/testing/accessibility/framework/TouchTargetSizeViewCheck.java
@@ -1,16 +1,14 @@
 /*
  * Copyright (C) 2014 Google Inc.
  *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
  *
  * http://www.apache.org/licenses/LICENSE-2.0
  *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
  * the License.
  */
 
@@ -19,6 +17,7 @@
 import com.google.android.apps.common.testing.accessibility.framework.AccessibilityCheckResult.AccessibilityCheckResultType;
 
 import android.view.View;
+import android.view.ViewParent;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -29,8 +28,8 @@
 public class TouchTargetSizeViewCheck extends AccessibilityViewCheck {
 
   /**
-   * Minimum height and width are set according to
-   * <a href="http://developer.android.com/design/patterns/accessibility.html"></a>
+   * Minimum height and width are set according to <a
+   * href="http://developer.android.com/design/patterns/accessibility.html"></a>
    */
   private static final int TOUCH_TARGET_MIN_HEIGHT = 48;
   private static final int TOUCH_TARGET_MIN_WIDTH = 48;
@@ -39,23 +38,69 @@
   public List<AccessibilityViewCheckResult> runCheckOnView(View view) {
     ArrayList<AccessibilityViewCheckResult> results = new ArrayList<AccessibilityViewCheckResult>();
 
+    if (!(view.isClickable() || view.isLongClickable())) {
+      results.add(new AccessibilityViewCheckResult(this.getClass(),
+          AccessibilityCheckResultType.NOT_RUN, "View is not clickable", view));
+      return results;
+    }
+
+    if (!ViewAccessibilityUtils.isVisibleToUser(view)) {
+      results.add(new AccessibilityViewCheckResult(this.getClass(),
+          AccessibilityCheckResultType.NOT_RUN, "View is not visible", view));
+      return results;
+    }
+
     // dp calculation is pixels/density
     float density = view.getContext().getResources().getDisplayMetrics().density;
     float targetHeight = view.getHeight() / density;
     float targetWidth = view.getWidth() / density;
-    // If this object passes touches to a different (possibly larger) object, don't check its bounds
-    if (view.getTouchDelegate() == null) {
-      if (targetHeight < TOUCH_TARGET_MIN_HEIGHT || targetWidth < TOUCH_TARGET_MIN_WIDTH) {
-        String message = String.format("View is too small of a touch target. Minimum touch target "
-            + "size is %dx%ddp. Actual size is %.1fx%.1fdp (screen density is %.1f).",
-            TOUCH_TARGET_MIN_WIDTH, TOUCH_TARGET_MIN_HEIGHT, targetWidth, targetHeight, density);
-        results.add(new AccessibilityViewCheckResult(
-            this, AccessibilityCheckResultType.ERROR, message, view));
+
+    if (targetHeight < TOUCH_TARGET_MIN_HEIGHT || targetWidth < TOUCH_TARGET_MIN_WIDTH) {
+      // Before we know a view fails this check, we must check if one of the view's ancestors may be
+      // handling touches on its behalf.
+      boolean hasDelegate = hasAncestorWithTouchDelegate(view);
+
+      // We can't get the delegated view from a TouchDelegate, so any TouchDelegate in the view's
+      // lineage will demote ERROR to WARNING.
+      AccessibilityCheckResultType resultType =
+          hasDelegate ? AccessibilityCheckResultType.WARNING : AccessibilityCheckResultType.ERROR;
+
+      StringBuilder messageBuilder = new StringBuilder(String.format(
+          "View falls below the minimum recommended size for touch targets. Minimum touch target "
+          + "size is %dx%ddp. Actual size is %.1fx%.1fdp (screen density is %.1f).",
+          TOUCH_TARGET_MIN_WIDTH,
+          TOUCH_TARGET_MIN_HEIGHT,
+          targetWidth,
+          targetHeight,
+          density));
+      if (hasDelegate) {
+        messageBuilder.append(
+            " A TouchDelegate has been detected on one of this view's ancestors. If the delegate "
+            + "is of sufficient size and handles touches for this view, this warning may be "
+            + "ignored.");
       }
-    } else {
-      results.add(new AccessibilityViewCheckResult(this, AccessibilityCheckResultType.NOT_RUN,
-          "View must not have a TouchDelegate", view));
+
+      results.add(
+          new AccessibilityViewCheckResult(this.getClass(), resultType, messageBuilder, view));
     }
+
     return results;
   }
+
+  private static boolean hasAncestorWithTouchDelegate(View view) {
+    if (view == null) {
+      return false;
+    }
+
+    View evalView = null;
+    ViewParent parent = view.getParent();
+    if (parent instanceof View) {
+      evalView = (View) parent;
+      if (evalView.getTouchDelegate() != null) {
+        return true;
+      }
+    }
+
+    return hasAncestorWithTouchDelegate(evalView);
+  }
 }