Merge "[ATF] add checks filter in policy" into rvc-dev am: 6274ade779

Change-Id: I2e249e09d3d151ac61c37445a7ec6cc624f89fa8
diff --git a/bridge/tests/src/com/android/tools/idea/validator/LayoutValidatorTests.java b/bridge/tests/src/com/android/tools/idea/validator/LayoutValidatorTests.java
index 23e40ff..ec91e17 100644
--- a/bridge/tests/src/com/android/tools/idea/validator/LayoutValidatorTests.java
+++ b/bridge/tests/src/com/android/tools/idea/validator/LayoutValidatorTests.java
@@ -29,7 +29,15 @@
 
 import android.view.View;
 
+import java.util.EnumSet;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import com.google.android.apps.common.testing.accessibility.framework.AccessibilityCheckPreset;
+import com.google.android.apps.common.testing.accessibility.framework.AccessibilityHierarchyCheck;
+
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 
 public class LayoutValidatorTests extends RenderTestBase {
 
@@ -52,19 +60,7 @@
 
     @Test
     public void testValidation() throws Exception {
-        LayoutPullParser parser = createParserFromPath("a11y_test1.xml");
-        LayoutLibTestCallback layoutLibCallback =
-                new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
-        layoutLibCallback.initResources();
-        SessionParams params = getSessionParamsBuilder()
-                .setParser(parser)
-                .setConfigGenerator(ConfigGenerator.NEXUS_5)
-                .setCallback(layoutLibCallback)
-                .disableDecoration()
-                .enableLayoutValidation()
-                .build();
-
-        render(sBridge, params, -1, session -> {
+        render(sBridge, generateParams(), -1, session -> {
             ValidatorResult result = LayoutValidator
                     .validate(((View) session.getRootViews().get(0).getViewObject()), null);
             assertEquals(3, result.getIssues().size());
@@ -73,16 +69,114 @@
                 assertEquals(Level.ERROR, issue.mLevel);
             }
 
+            Issue first = result.getIssues().get(0);
             assertEquals("This item may not have a label readable by screen readers.",
-                    result.getIssues().get(0).mMsg);
+                         first.mMsg);
+            assertEquals("https://support.google.com/accessibility/android/answer/7158690",
+                         first.mHelpfulUrl);
+            assertEquals("SpeakableTextPresentCheck", first.mSourceClass);
+
+            Issue second = result.getIssues().get(1);
             assertEquals("This item's size is 10dp x 10dp. Consider making this touch target " +
                             "48dp wide and 48dp high or larger.",
-                    result.getIssues().get(1).mMsg);
+                         second.mMsg);
+            assertEquals("https://support.google.com/accessibility/android/answer/7101858",
+                         second.mHelpfulUrl);
+            assertEquals("TouchTargetSizeCheck", second.mSourceClass);
+
+            Issue third = result.getIssues().get(2);
             assertEquals("The item's text contrast ratio is 1.00. This ratio is based on a text color " +
                             "of #000000 and background color of #000000. Consider increasing this item's" +
                             " text contrast ratio to 4.50 or greater.",
-                    result.getIssues().get(2).mMsg);
-            // TODO: It should recognize 10dp x 10dp button. Investigate why it's not.
+                         third.mMsg);
+            assertEquals("https://support.google.com/accessibility/android/answer/7158390",
+                         third.mHelpfulUrl);
+            assertEquals("TextContrastCheck", third.mSourceClass);
         });
     }
+
+    @Test
+    public void testValidationPolicyType() throws Exception {
+        try {
+            ValidatorData.Policy newPolicy = new ValidatorData.Policy(
+                    EnumSet.of(Type.RENDER),
+                    EnumSet.of(Level.ERROR, Level.WARNING));
+            LayoutValidator.updatePolicy(newPolicy);
+
+            render(sBridge, generateParams(), -1, session -> {
+                ValidatorResult result = LayoutValidator.validate(
+                        ((View) session.getRootViews().get(0).getViewObject()), null);
+                assertTrue(result.getIssues().isEmpty());
+            });
+        } finally {
+            LayoutValidator.updatePolicy(LayoutValidator.DEFAULT_POLICY);
+        }
+    }
+
+    @Test
+    public void testValidationPolicyLevel() throws Exception {
+        try {
+            ValidatorData.Policy newPolicy = new ValidatorData.Policy(
+                    EnumSet.of(Type.ACCESSIBILITY, Type.RENDER),
+                    EnumSet.of(Level.VERBOSE));
+            LayoutValidator.updatePolicy(newPolicy);
+
+            render(sBridge, generateParams(), -1, session -> {
+                ValidatorResult result = LayoutValidator.validate(
+                        ((View) session.getRootViews().get(0).getViewObject()), null);
+                assertEquals(27, result.getIssues().size());
+                result.getIssues().forEach(issue ->assertEquals(Level.VERBOSE, issue.mLevel));
+            });
+        } finally {
+            LayoutValidator.updatePolicy(LayoutValidator.DEFAULT_POLICY);
+        }
+    }
+
+    @Test
+    public void testValidationPolicyChecks() throws Exception {
+        Set<AccessibilityHierarchyCheck> allChecks =
+                AccessibilityCheckPreset.getAccessibilityHierarchyChecksForPreset(
+                        AccessibilityCheckPreset.LATEST);
+        Set<AccessibilityHierarchyCheck> filtered =allChecks
+                .stream()
+                .filter(it -> it.getClass().getSimpleName().equals("TextContrastCheck"))
+                .collect(Collectors.toSet());
+        try {
+            ValidatorData.Policy newPolicy = new ValidatorData.Policy(
+                    EnumSet.of(Type.ACCESSIBILITY, Type.RENDER),
+                    EnumSet.of(Level.ERROR));
+            newPolicy.mChecks.addAll(filtered);
+            LayoutValidator.updatePolicy(newPolicy);
+
+            render(sBridge, generateParams(), -1, session -> {
+                ValidatorResult result = LayoutValidator.validate(
+                        ((View) session.getRootViews().get(0).getViewObject()), null);
+                assertEquals(1, result.getIssues().size());
+                Issue textCheck = result.getIssues().get(0);
+                assertEquals("The item's text contrast ratio is 1.00. This ratio is based on a text color " +
+                                "of #000000 and background color of #000000. Consider increasing this item's" +
+                                " text contrast ratio to 4.50 or greater.",
+                        textCheck.mMsg);
+                assertEquals("https://support.google.com/accessibility/android/answer/7158390",
+                        textCheck.mHelpfulUrl);
+                assertEquals("TextContrastCheck", textCheck.mSourceClass);
+            });
+        } finally {
+            LayoutValidator.updatePolicy(LayoutValidator.DEFAULT_POLICY);
+        }
+    }
+
+    private SessionParams generateParams() throws Exception {
+        LayoutPullParser parser = createParserFromPath("a11y_test1.xml");
+        LayoutLibTestCallback layoutLibCallback =
+                new LayoutLibTestCallback(getLogger(), mDefaultClassLoader);
+        layoutLibCallback.initResources();
+        return getSessionParamsBuilder()
+                .setParser(parser)
+                .setConfigGenerator(ConfigGenerator.NEXUS_5)
+                .setCallback(layoutLibCallback)
+                .disableDecoration()
+                .enableLayoutValidation()
+                .build();
+    }
 }
diff --git a/validator/src/com/android/tools/idea/validator/LayoutValidator.java b/validator/src/com/android/tools/idea/validator/LayoutValidator.java
index 7ec6f47..dc34e90 100644
--- a/validator/src/com/android/tools/idea/validator/LayoutValidator.java
+++ b/validator/src/com/android/tools/idea/validator/LayoutValidator.java
@@ -33,10 +33,12 @@
  */
 public class LayoutValidator {
 
-    private static ValidatorData.Policy sPolicy = new Policy(
+    public static final ValidatorData.Policy DEFAULT_POLICY = new Policy(
             EnumSet.of(Type.ACCESSIBILITY, Type.RENDER),
             EnumSet.of(Level.ERROR, Level.WARNING));
 
+    private static ValidatorData.Policy sPolicy = DEFAULT_POLICY;
+
     /**
      * Validate the layout using the default policy.
      * Precondition: View must be attached to the window.
@@ -46,7 +48,7 @@
     @NotNull
     public static ValidatorResult validate(@NotNull View view, @Nullable BufferedImage image) {
         if (view.isAttachedToWindow()) {
-            return AccessibilityValidator.validateAccessibility(view, image, sPolicy.mLevels);
+            return AccessibilityValidator.validateAccessibility(view, image, sPolicy);
         }
         // TODO: Add non-a11y layout validation later.
         return new ValidatorResult.Builder().build();
diff --git a/validator/src/com/android/tools/idea/validator/ValidatorData.java b/validator/src/com/android/tools/idea/validator/ValidatorData.java
index 0697472..6d9d6b6 100644
--- a/validator/src/com/android/tools/idea/validator/ValidatorData.java
+++ b/validator/src/com/android/tools/idea/validator/ValidatorData.java
@@ -20,6 +20,9 @@
 import com.android.tools.layoutlib.annotations.Nullable;
 
 import java.util.EnumSet;
+import java.util.HashSet;
+
+import com.google.android.apps.common.testing.accessibility.framework.AccessibilityHierarchyCheck;
 
 /**
  * Data used for layout validation.
@@ -32,6 +35,7 @@
     public enum Type {
         ACCESSIBILITY,
         RENDER,
+        INTERNAL_ERROR
     }
 
     /**
@@ -49,8 +53,9 @@
      * Determine what types and levels of validation to run.
      */
     public static class Policy {
-        @NotNull final EnumSet<Type> mTypes;
-        @NotNull final EnumSet<Level> mLevels;
+        @NotNull public final EnumSet<Type> mTypes;
+        @NotNull public final EnumSet<Level> mLevels;
+        @NotNull public final HashSet<AccessibilityHierarchyCheck> mChecks = new HashSet();
 
         public Policy(@NotNull EnumSet<Type> types, @NotNull EnumSet<Level> levels) {
             mTypes = types;
@@ -72,26 +77,90 @@
     /**
      * Issue describing the layout problem.
      */
-    public static class Issue{
-        @NotNull public final Type mType;
-        @NotNull public final String mMsg;
-        @NotNull public final Level mLevel;
-        @Nullable public final Long mSrcId;
-        @Nullable public final Fix mFix;
-        // Used for debugging.
-        @Nullable public String mSourceClass;
+    public static class Issue {
+        @NotNull
+        public final Type mType;
+        @NotNull
+        public final String mMsg;
+        @NotNull
+        public final Level mLevel;
+        @Nullable
+        public final Long mSrcId;
+        @Nullable
+        public final Fix mFix;
+        @NotNull
+        public final String mSourceClass;
+        @Nullable
+        public final String mHelpfulUrl;
 
-        public Issue(
+        private Issue(
                 @NotNull Type type,
                 @NotNull String msg,
                 @NotNull Level level,
                 @Nullable Long srcId,
-                @Nullable Fix fix) {
+                @Nullable Fix fix,
+                @NotNull String sourceClass,
+                @Nullable String helpfulUrl) {
             mType = type;
             mMsg = msg;
             mLevel = level;
             mSrcId = srcId;
             mFix = fix;
+            mSourceClass = sourceClass;
+            mHelpfulUrl = helpfulUrl;
+        }
+
+        public static class IssueBuilder {
+            private Type mType = Type.ACCESSIBILITY;
+            private String mMsg;
+            private Level mLevel;
+            private Long mSrcId;
+            private Fix mFix;
+            private String mSourceClass;
+            private String mHelpfulUrl;
+
+            public IssueBuilder setType(Type type) {
+                mType = type;
+                return this;
+            }
+
+            public IssueBuilder setMsg(String msg) {
+                mMsg = msg;
+                return this;
+            }
+
+            public IssueBuilder setLevel(Level level) {
+                mLevel = level;
+                return this;
+            }
+
+            public IssueBuilder setSrcId(Long srcId) {
+                mSrcId = srcId;
+                return this;
+            }
+
+            public IssueBuilder setFix(Fix fix) {
+                mFix = fix;
+                return this;
+            }
+
+            public IssueBuilder setSourceClass(String sourceClass) {
+                mSourceClass = sourceClass;
+                return this;
+            }
+
+            public IssueBuilder setHelpfulUrl(String url) {
+                mHelpfulUrl = url;
+                return this;
+            }
+
+            public Issue build() {
+                assert(mType != null);
+                assert(mMsg != null);
+                assert(mLevel != null);
+                assert(mSourceClass != null);
+                return new Issue(mType, mMsg, mLevel, mSrcId, mFix, mSourceClass, mHelpfulUrl);
+            }
         }
     }
 }
diff --git a/validator/src/com/android/tools/idea/validator/accessibility/AccessibilityValidator.java b/validator/src/com/android/tools/idea/validator/accessibility/AccessibilityValidator.java
index e679750..a2ec2c4 100644
--- a/validator/src/com/android/tools/idea/validator/accessibility/AccessibilityValidator.java
+++ b/validator/src/com/android/tools/idea/validator/accessibility/AccessibilityValidator.java
@@ -18,7 +18,7 @@
 
 import com.android.tools.idea.validator.ValidatorData;
 import com.android.tools.idea.validator.ValidatorData.Fix;
-import com.android.tools.idea.validator.ValidatorData.Issue;
+import com.android.tools.idea.validator.ValidatorData.Issue.IssueBuilder;
 import com.android.tools.idea.validator.ValidatorData.Level;
 import com.android.tools.idea.validator.ValidatorData.Type;
 import com.android.tools.idea.validator.ValidatorResult;
@@ -31,6 +31,7 @@
 import java.awt.image.BufferedImage;
 import java.util.ArrayList;
 import java.util.EnumSet;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Locale;
 import java.util.ResourceBundle;
@@ -70,20 +71,28 @@
      * Run Accessibility specific validation test and receive results.
      * @param view the root view
      * @param image the output image of the view. Null if not available.
-     * @param filter list of levels to allow
+     * @param policy e.g: list of levels to allow
      * @return results with all the accessibility issues and warnings.
      */
     @NotNull
     public static ValidatorResult validateAccessibility(
-            @NotNull View view, @Nullable BufferedImage image, @NotNull EnumSet<Level> filter) {
+            @NotNull View view,
+            @Nullable BufferedImage image,
+            @NotNull ValidatorData.Policy policy) {
+
+        EnumSet<Level> filter = policy.mLevels;
         ValidatorResult.Builder builder = new ValidatorResult.Builder();
         builder.mMetric.startTimer();
+        if (!policy.mTypes.contains(Type.ACCESSIBILITY)) {
+            return builder.build();
+        }
 
         List<AccessibilityHierarchyCheckResult> results = getHierarchyCheckResults(
                 builder.mMetric,
                 view,
                 builder.mSrcMap,
-                image);
+                image,
+                policy.mChecks);
 
         for (AccessibilityHierarchyCheckResult result : results) {
             ValidatorData.Level level = convertLevel(result.getType());
@@ -91,19 +100,30 @@
                 continue;
             }
 
-            ValidatorData.Fix fix = generateFix(result);
-            Long srcId = null;
-            if (result.getElement() != null) {
-                srcId = result.getElement().getCondensedUniqueId();
+            try {
+                IssueBuilder issueBuilder = new IssueBuilder()
+                        .setMsg(result.getMessage(Locale.ENGLISH).toString())
+                        .setLevel(level)
+                        .setFix(generateFix(result))
+                        .setSourceClass(result.getSourceCheckClass().getSimpleName());
+                if (result.getElement() != null) {
+                    issueBuilder.setSrcId(result.getElement().getCondensedUniqueId());
+                }
+                AccessibilityHierarchyCheck subclass = AccessibilityCheckPreset
+                        .getHierarchyCheckForClass(result
+                                .getSourceCheckClass()
+                                .asSubclass(AccessibilityHierarchyCheck.class));
+                if (subclass != null) {
+                    issueBuilder.setHelpfulUrl(subclass.getHelpUrl());
+                }
+                builder.mIssues.add(issueBuilder.build());
+            } catch (Exception e) {
+                builder.mIssues.add(new IssueBuilder()
+                        .setType(Type.INTERNAL_ERROR)
+                        .setMsg(e.getMessage())
+                        .setLevel(Level.ERROR)
+                        .setSourceClass("AccessibilityValidator").build());
             }
-            Issue issue = new Issue(
-                    Type.ACCESSIBILITY,
-                    result.getMessage(Locale.ENGLISH).toString(),
-                    level,
-                    srcId,
-                    fix);
-            issue.mSourceClass = result.getSourceCheckClass().getSimpleName();
-            builder.mIssues.add(issue);
         }
         builder.mMetric.endTimer();
         return builder.build();
@@ -137,10 +157,13 @@
             @NotNull Metric metric,
             @NotNull View view,
             @NotNull BiMap<Long, View> originMap,
-            @Nullable BufferedImage image) {
+            @Nullable BufferedImage image,
+            HashSet<AccessibilityHierarchyCheck> policyChecks) {
 
-        @NotNull Set<AccessibilityHierarchyCheck> checks = AccessibilityCheckPreset.getAccessibilityHierarchyChecksForPreset(
-                AccessibilityCheckPreset.LATEST);
+        @NotNull Set<AccessibilityHierarchyCheck> checks = policyChecks.isEmpty()
+                ? AccessibilityCheckPreset
+                        .getAccessibilityHierarchyChecksForPreset(AccessibilityCheckPreset.LATEST)
+                : policyChecks;
 
         @NotNull AccessibilityHierarchyAndroid hierarchy = AccessibilityHierarchyAndroid
                 .newBuilder(view)