diff options
author | Isaac Chai <ichai@google.com> | 2020-05-20 18:21:56 -0700 |
---|---|---|
committer | Isaac Chai <ichai@google.com> | 2020-05-29 01:38:11 +0000 |
commit | 244d8395bffb10d4df69b45c8c0c30a6b9baa8ee (patch) | |
tree | 240517dfcc370cd56f59a6238a4f495f0c842f28 | |
parent | 03a9aabbe7cd204496d5954524c9b647ed02c463 (diff) | |
download | layoutlib-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)
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) |