aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIsaac Chai <ichai@google.com>2020-05-20 18:21:56 -0700
committerIsaac Chai <ichai@google.com>2020-05-29 01:38:11 +0000
commit244d8395bffb10d4df69b45c8c0c30a6b9baa8ee (patch)
tree240517dfcc370cd56f59a6238a4f495f0c842f28
parent03a9aabbe7cd204496d5954524c9b647ed02c463 (diff)
downloadlayoutlib-244d8395bffb10d4df69b45c8c0c30a6b9baa8ee.tar.gz
[ATF] add checks filter in policy
Allow studio to control which sets of validations to run. Also added few more tests. Bug: 150331000 Test: Manually tested + unit tests Change-Id: I5dd81feaea5ff7dc0c7903612f0f1bc5c3cf22a2 (cherry picked from commit 9d6a3607f6ee3db03fe9fc2e686550078ca234f0)
-rw-r--r--bridge/tests/src/com/android/tools/idea/validator/LayoutValidatorTests.java128
-rw-r--r--validator/src/com/android/tools/idea/validator/LayoutValidator.java6
-rw-r--r--validator/src/com/android/tools/idea/validator/ValidatorData.java95
-rw-r--r--validator/src/com/android/tools/idea/validator/accessibility/AccessibilityValidator.java61
4 files changed, 239 insertions, 51 deletions
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 23e40ff094..ec91e17560 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 org.junit.Test;
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 @@ public class LayoutValidatorTests extends RenderTestBase {
@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 @@ public class LayoutValidatorTests extends RenderTestBase {
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 7ec6f47188..dc34e908ff 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 @@ import java.util.EnumSet;
*/
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 @@ public class LayoutValidator {
@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 06974720a6..6d9d6b6422 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.NotNull;
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 class ValidatorData {
public enum Type {
ACCESSIBILITY,
RENDER,
+ INTERNAL_ERROR
}
/**
@@ -49,8 +53,9 @@ public class ValidatorData {
* 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 @@ public class ValidatorData {
/**
* 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 Issue(
+ 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;
+
+ 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 e679750321..a2ec2c45a8 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 @@ package com.android.tools.idea.validator.accessibility;
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 android.view.View;
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 @@ public class AccessibilityValidator {
* 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 @@ public class AccessibilityValidator {
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 @@ public class AccessibilityValidator {
@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)