aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorandroid-build-team Robot <android-build-team-robot@google.com>2020-05-30 01:05:02 +0000
committerandroid-build-team Robot <android-build-team-robot@google.com>2020-05-30 01:05:02 +0000
commit0f8ce68ff8fa8b0c5c0d484ac13ef9b95e7a63c5 (patch)
treefa0a9561cd4a514b6541da8e72b0f89a3bc98d43
parent4ff5a059f09b6f914c81f6e793168f09d3e1a4ff (diff)
parent42a5c76904a017ac55e747230731bdfdbf44b02a (diff)
downloadlayoutlib-0f8ce68ff8fa8b0c5c0d484ac13ef9b95e7a63c5.tar.gz
Change-Id: Iae0b930630b5e2413dcdb631d43b3861eb2bf289
-rw-r--r--Android.bp1
-rw-r--r--bridge/tests/src/com/android/tools/idea/validator/LayoutValidatorTests.java128
-rw-r--r--create/src/com/android/tools/layoutlib/create/Main.java2
-rw-r--r--validator/Android.bp1
-rw-r--r--validator/src/android/os/Build.java35
-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
-rw-r--r--validator/validator.iml9
9 files changed, 242 insertions, 96 deletions
diff --git a/Android.bp b/Android.bp
index acdbffdbfa..b38baa2b8a 100644
--- a/Android.bp
+++ b/Android.bp
@@ -25,6 +25,7 @@ java_genrule_host {
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 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/create/src/com/android/tools/layoutlib/create/Main.java b/create/src/com/android/tools/layoutlib/create/Main.java
index 4636a1c4c5..b56416e5aa 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 @@ public class Main {
"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 0a93158511..0eff99b19e 100644
--- a/validator/Android.bp
+++ b/validator/Android.bp
@@ -28,7 +28,6 @@ java_library_host {
],
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 971f4667aa..0000000000
--- 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 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)
diff --git a/validator/validator.iml b/validator/validator.iml
index e75d99a918..f580aa31c0 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