Snap for 6542686 from 6274ade7798a686ebb236c441cc82df958e3be50 to rvc-release

Change-Id: I1a3a50083e5be8df19533b1ae74caee3117b63c5
diff --git a/Android.bp b/Android.bp
index acdbffd..b38baa2 100644
--- a/Android.bp
+++ b/Android.bp
@@ -25,6 +25,7 @@
     tools: ["layoutlib_create"],
     out: ["temp_layoutlib.jar"],
     srcs: [
+        ":atf-prebuilt{.jar}",
         ":core-icu4j{.jar}",
         ":core-libart{.jar}",
         ":framework-all{.jar}",
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/create/src/com/android/tools/layoutlib/create/Main.java b/create/src/com/android/tools/layoutlib/create/Main.java
index 4636a1c..b56416e 100644
--- a/create/src/com/android/tools/layoutlib/create/Main.java
+++ b/create/src/com/android/tools/layoutlib/create/Main.java
@@ -134,6 +134,8 @@
                         "android.annotation.Nullable",      // annotations
                         "com.android.internal.transition.EpicenterTranslateClipReveal",
                         "com.android.internal.graphics.drawable.AnimationScaleListDrawable",
+                        "com.google.android.apps.common.testing.accessibility.**",
+                        "com.google.android.libraries.accessibility.**",
                     },
                     info.getExcludedClasses(),
                     new String[] {
diff --git a/validator/Android.bp b/validator/Android.bp
index 0a93158..0eff99b 100644
--- a/validator/Android.bp
+++ b/validator/Android.bp
@@ -28,7 +28,6 @@
     ],
 
     static_libs: [
-        "atf-prebuilt-jars",
         "hamcrest",
         "jsoup-1.6.3",
         "protobuf-lite",
diff --git a/validator/src/android/os/Build.java b/validator/src/android/os/Build.java
deleted file mode 100644
index 971f466..0000000
--- a/validator/src/android/os/Build.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * 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 the License.
- */
-
-package android.os;
-
-public class Build {
-    public static class VERSION {
-        public static int SDK_INT = _Original_Build.VERSION.SDK_INT;
-    }
-
-    public static class VERSION_CODES {
-        public final static int Q = _Original_Build.VERSION_CODES.Q;
-        public final static int N = _Original_Build.VERSION_CODES.N;
-        public final static int LOLLIPOP_MR1 = _Original_Build.VERSION_CODES.LOLLIPOP_MR1;
-        public final static int LOLLIPOP = _Original_Build.VERSION_CODES.LOLLIPOP;
-
-        public final static int JELLY_BEAN = _Original_Build.VERSION_CODES.JELLY_BEAN;
-        public final static int HONEYCOMB = _Original_Build.VERSION_CODES.HONEYCOMB;
-        public final static int JELLY_BEAN_MR2 = _Original_Build.VERSION_CODES.JELLY_BEAN_MR2;
-        public final static int JELLY_BEAN_MR1 = _Original_Build.VERSION_CODES.JELLY_BEAN_MR1;
-    }
-}
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)
diff --git a/validator/validator.iml b/validator/validator.iml
index e75d99a..f580aa3 100644
--- a/validator/validator.iml
+++ b/validator/validator.iml
@@ -41,14 +41,5 @@
         <SOURCES />
       </library>
     </orderEntry>
-    <orderEntry type="module-library">
-      <library>
-        <CLASSES>
-          <root url="jar://$MODULE_DIR$/../../../prebuilts/misc/common/atf/atf_classes.jar!/" />
-        </CLASSES>
-        <JAVADOC />
-        <SOURCES />
-      </library>
-    </orderEntry>
   </component>
 </module>
\ No newline at end of file