aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorandroid-build-team Robot <android-build-team-robot@google.com>2020-04-30 02:04:53 +0000
committerandroid-build-team Robot <android-build-team-robot@google.com>2020-04-30 02:04:53 +0000
commite9b35a9f92b66b3578c199acc9cbaa1323b7d021 (patch)
treeda7f98260bc139f9528cf0daa6bf823da006eca0
parentc148f02c383a29e42311536c5587c939ea52b214 (diff)
parentb143e6e2fc7b6f84259428cb8c320d70aa68d732 (diff)
downloadlayoutlib-e9b35a9f92b66b3578c199acc9cbaa1323b7d021.tar.gz
Snap for 6445658 from b143e6e2fc7b6f84259428cb8c320d70aa68d732 to rvc-d1-release
Change-Id: Ia9ec9c8f46b2e8a0befbe1f0497d2d4815bfd0e2
-rw-r--r--.idea/modules.xml1
-rw-r--r--validator/Android.bp40
-rw-r--r--validator/resources/strings.properties133
-rw-r--r--validator/src/android/os/Build.java35
-rw-r--r--validator/src/com/android/tools/idea/validator/LayoutValidator.java52
-rw-r--r--validator/src/com/android/tools/idea/validator/ValidatorData.java94
-rw-r--r--validator/src/com/android/tools/idea/validator/ValidatorResult.java90
-rw-r--r--validator/src/com/android/tools/idea/validator/accessibility/AccessibilityValidator.java141
-rw-r--r--validator/validator.iml54
9 files changed, 640 insertions, 0 deletions
diff --git a/.idea/modules.xml b/.idea/modules.xml
index 1d5950300a..50836c1e64 100644
--- a/.idea/modules.xml
+++ b/.idea/modules.xml
@@ -10,6 +10,7 @@
<module fileurl="file://$PROJECT_DIR$/remote/server/remote server.iml" filepath="$PROJECT_DIR$/remote/server/remote server.iml" group="remote" />
<module fileurl="file://$PROJECT_DIR$/remote/tests/remote tests.iml" filepath="$PROJECT_DIR$/remote/tests/remote tests.iml" group="remote" />
<module fileurl="file://$PROJECT_DIR$/studio-custom-widgets/studio-android-widgets.iml" filepath="$PROJECT_DIR$/studio-custom-widgets/studio-android-widgets.iml" />
+ <module fileurl="file://$PROJECT_DIR$/validator/validator.iml" filepath="$PROJECT_DIR$/validator/validator.iml" />
</modules>
</component>
</project> \ No newline at end of file
diff --git a/validator/Android.bp b/validator/Android.bp
new file mode 100644
index 0000000000..e55a4339d6
--- /dev/null
+++ b/validator/Android.bp
@@ -0,0 +1,40 @@
+//
+// 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.
+//
+
+java_library_host {
+ name: "layoutlib-validator",
+
+ srcs: ["src/**/*.java"],
+ java_resource_dirs: ["resources"],
+
+ libs: [
+ "tools-common-prebuilt",
+ "temp_layoutlib",
+ "layoutlib-common",
+ "guava",
+ ],
+
+ static_libs: [
+ "atf-prebuilt-jars",
+ "hamcrest",
+ "jsoup-1.6.3",
+ "protobuf-lite",
+ ],
+
+ dist: {
+ targets: ["layoutlib"],
+ },
+}
diff --git a/validator/resources/strings.properties b/validator/resources/strings.properties
new file mode 100644
index 0000000000..380a727a43
--- /dev/null
+++ b/validator/resources/strings.properties
@@ -0,0 +1,133 @@
+
+#
+# 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.
+#
+actionable = actionable
+button_item_type = button
+check_title_accessibility_traversal = Traversal order
+check_title_class_name_not_supported = Unsupported item type
+check_title_clickablespan = Link
+check_title_duplicate_clickable_bounds = Clickable items
+check_title_duplicate_speakable_text = Item descriptions
+check_title_editable_content_desc = Editable item label
+check_title_image_contrast = Image contrast
+check_title_item_exposed = Exposed items
+check_title_link_test = Link text
+check_title_reading_score = Readability
+check_title_redundant_description = Item type label
+check_title_speakable_text_present = Item label
+check_title_text_contrast = Text contrast
+check_title_text_style = Text Style
+check_title_touch_target_size = Touch target
+check_view_banned_word = Banned word
+clickable = clickable
+clickable_and_long_clickable = clickable and long clickable
+italic_text = italic
+italic_underline_text = italic and underline
+long_clickable = long clickable
+non_clickable = non-clickable
+question_id_message_confirm_foreground_background_colors = Are the detected foreground and background colors correct?
+question_id_message_provide_background_color = What is the correct background color?
+question_id_message_provide_foreground_color = What is the correct foreground color?
+question_message_identify_unexposed_items = Select regions of the screen with unidentified items.
+question_message_screen_has_unexposed_items = Does this screen contain important items that are not outlined?
+question_option_message_background_incorrect = Background incorrect
+question_option_message_both_correct = Both correct
+question_option_message_both_incorrect = Foreground and background incorrect
+question_option_message_foreground_incorrect = Foreground incorrect
+question_option_message_unknown = Unknown
+result_message_addendum_against_scrollable_edge = This item may be only partially visible within a scrollable container.
+result_message_addendum_clickable_ancestor = A parent container may be handling touch events for this item. If selecting the larger container performs the same action as selecting this item, consider defining this item as not clickable. If a different action is performed, consider increasing the size of this item.
+result_message_addendum_clipped_by_ancestor = A parent container may be clipping the size of this item, which has a drawing area of <tt>%1$ddp</tt> x <tt>%2$ddp</tt>. Consider increasing the size of this item\'s clipping ancestor, or allowing a larger parent container to handle actions on behalf of this item.
+result_message_addendum_opacity_description = Its actual opacity is %1$.2f%%.
+result_message_addendum_touch_delegate = A <tt>TouchDelegate</tt> has been detected on one of this item\'s ancestors. This message can be ignored if the delegate is of sufficient size and handles touches for this item.
+result_message_addendum_touch_delegate_with_hit_rect = A <tt>TouchDelegate</tt> with size <tt>%1$ddp</tt> x <tt>%2$ddp</tt> has been detected for this item. Consider increasing the size of its hit <tt>Rect</tt>.
+result_message_addendum_view_potentially_obscured = This item may be obscured by other on-screen content. Consider manually testing this item\'s contrast.
+result_message_ai4design_contrast_not_sufficient = The item\'s text contrast ratio is %1$.2f.Consider increasing this item\'s text contrast ratio to %2$.2f or greater.
+result_message_ai4design_text_not_determined = The item\'s text could not be determined.
+result_message_background_must_be_opaque = This items\'s background color is not opaque.
+result_message_banned_word = This item\'s text may contain an inappropriate word, "<tt>%1$s</tt>"
+result_message_brief_banned_word = Consider removing inappropriate words from this item\'s text
+result_message_brief_content_desc_contains_redundant_word = This item\'s <tt>android:contentDescription</tt> might contain unnecessary text.
+result_message_brief_image_contrast_not_sufficient = Consider increasing the contrast ratio between this image\'s foreground and background.
+result_message_brief_is_unexposed_item_screen_region = Consider exposing items in this region to accessibility services.
+result_message_brief_link_text_not_descriptive = Consider using more descriptive text in the link.
+result_message_brief_low_reading_score = This text may have a low readability score.
+result_message_brief_same_speakable_text = Multiple items have the same description.
+result_message_brief_same_view_bounds = Multiple %1$s items share this location on the screen.
+result_message_brief_small_touch_target = Consider making this clickable item larger.
+result_message_brief_styled_text = Consider removing %1$s styling on longer passages of text.
+result_message_brief_text_contrast_not_sufficient = Consider increasing this item\'s text foreground to background contrast ratio.
+result_message_brief_unpredictable_traversal = Traversal behavior with screen readers may be unpredictable.
+result_message_class_name_is_empty = This item\'s type may not be reported to accessibility services. Consider using a type defined by the Android SDK.
+result_message_class_name_is_unknown = This item\'s type could not be determined.
+result_message_class_name_not_supported_brief = This item\'s type may not be supported.
+result_message_class_name_not_supported_detail = This item\'s type <tt>%1$s</tt> may not be resolvable by accessibility services. Consider using a type defined by the Android SDK.
+result_message_clickablespan_no_determined_type = This item\'s type is undetermined.
+result_message_content_desc_contains_redundant_word = This item\'s <tt>android:contentDescription</tt>, \"<tt>%1$s</tt>\" contains the item type \"<tt>%2$s</tt>\".
+result_message_content_desc_ends_with_view_type = This item\'s <tt>android:contentDescription</tt>, \"<tt>%1$s</tt>\" ends with the item\'s type.
+result_message_could_not_get_background_color = This item\'s background color could not be determined.
+result_message_could_not_get_text_color = This item\'s text color could not be determined.
+result_message_customized_small_touch_target_height = This item\'s height is <tt>%1$ddp</tt>. Consider increasing the height of this touch target to at least the configured minimum height of <tt>%2$ddp</tt>.
+result_message_customized_small_touch_target_width = This item\'s width is <tt>%1$ddp</tt>. Consider increasing the width of this touch target to at least the configured minimum width of <tt>%2$ddp</tt>.
+result_message_customized_small_touch_target_width_and_height = This item\'s size is <tt>%1$ddp</tt> x <tt>%2$ddp</tt>. Consider increasing this touch target to at least the configured minimum size of <tt>%3$ddp</tt> x <tt>%4$ddp</tt>.
+result_message_disruptive_announcement = A disruptive accessibility announcement has been used.
+result_message_editable_textview_content_desc = This editable <tt>TextView</tt> has an <tt>android:contentDescription</tt>. A screen reader may read this attribute instead of the editable content when the user is navigating.
+result_message_english_locale_only = This check only runs on devices with locales set to English.
+result_message_has_unexposed_items = This screen may have items that are not exposed to accessibility services.
+result_message_image_contrast_not_sufficient = The image\'s contrast ratio is %1$.2f. This ratio is based on an estimated foreground color of <tt>#%3$06X</tt> and an estimated background color of <tt>#%4$06X</tt>. Consider increasing this ratio to %2$.2f or greater.
+result_message_image_customized_contrast_not_sufficient = The image\'s contrast ratio is %1$.2f. This ratio is based on an estimated foreground color of <tt>#%3$06X</tt> and an estimated background color of <tt>#%4$06X</tt>. Consider increasing this ratio to the configured ratio of %2$.2f or greater.
+result_message_is_unexposed_item_screen_region = The region with on-screen location <tt>%1$s</tt> contains at least one item that is not exposed to accessibility services.
+result_message_item_exposed_needs_manual_assessment = This screen needs manual inspection to ensure all items are exposed to accessibility services.
+result_message_link_text_not_descriptive = The link text \"<tt>%1$s</tt>\" may not independently convey the link\'s purpose.
+result_message_low_reading_score = This item\'s text has an approximate readability score of %1$.0f, which is lower than the recommended score of %2$.0f. Consider using simpler words or sentences to make the text easier to read.
+result_message_missing_speakable_text = This item may not have a label readable by screen readers.
+result_message_no_content_desc = This item has no <tt>android:contentDescription</tt>.
+result_message_no_screencapture = Screen capture data could not be obtained.
+result_message_no_typeface_info = This item\'s typeface could not be determined.
+result_message_not_clickable = This view is not clickable.
+result_message_not_editable_textview = This item is not an editable <tt>TextView</tt>.
+result_message_not_enabled = This item isn\'t enabled.
+result_message_not_imageview = This item is not an <tt>ImageView</tt>.
+result_message_not_important_for_accessibility = This item was not found to be important for accessibility.
+result_message_not_text_view = This item is not a <tt>TextView</tt>.
+result_message_not_visible = This item is not visible.
+result_message_same_speakable_text = This %1$s item\'s speakable text: \"<tt>%2$s</tt>\" is identical to that of %3$d other item(s).
+result_message_same_view_bounds = This %1$s item has the same on-screen location (<tt>%2$s</tt>) as %3$d other item(s) with those properties.
+result_message_screencapture_data_hidden = Screen capture information for this item was hidden.
+result_message_screencapture_uniform_color = Screen capture has a uniform color.
+result_message_sdk_version_not_applicable = This check is not applicable on devices running Android %1$s and above.
+result_message_short_text = This item\'s text is too short to be evaluated.
+result_message_should_not_focus = This item would not be focused by a screen reader.
+result_message_small_touch_target_height = This item\'s height is <tt>%1$ddp</tt>. Consider making the height of this touch target <tt>%2$ddp</tt> or larger.
+result_message_small_touch_target_width = This item\'s width is <tt>%1$ddp</tt>. Consider making the width of this touch target <tt>%2$ddp</tt> or larger.
+result_message_small_touch_target_width_and_height = This item\'s size is <tt>%1$ddp</tt> x <tt>%2$ddp</tt>. Consider making this touch target <tt>%3$ddp</tt> wide and <tt>%4$ddp</tt> high or larger.
+result_message_speakable_text = This %1$s item also has speakable text: \"<tt>%2$s</tt>\".
+result_message_styled_text = This item may use %1$s font for a long passage of text. Consider removing the style from the font to improve readability.
+result_message_text_must_be_opaque = This item\'s text color is not opaque.
+result_message_textview_contrast_not_sufficient = The item\'s text contrast ratio is %1$.2f. This ratio is based on a text color of <tt>#%2$06X</tt> and background color of <tt>#%3$06X</tt>. Consider increasing this item\'s text contrast ratio to %4$.2f or greater.
+result_message_textview_empty = This <tt>TextView</tt> is empty.
+result_message_textview_heuristic_contrast_not_sufficient = The item\'s text contrast ratio is %1$.2f. This ratio is based on an estimated foreground color of <tt>#%2$06X</tt> and an estimated background color of <tt>#%3$06X</tt>. Consider using colors that result in a contrast ratio greater than %4$.2f for small text, or %5$.2f for large text.
+result_message_textview_heuristic_contrast_not_sufficient_confirmed = The item\'s text contrast ratio is %1$.2f. This ratio is based on the provided foreground color of <tt>#%2$06X</tt> and provided background color of <tt>#%3$06X</tt>. Consider using colors that result in a contrast ratio greater than %4$.2f for small text, or %5$.2f for large text.
+result_message_textview_heuristic_customized_contrast_not_sufficient = The item\'s text contrast ratio is %1$.2f. This ratio is based on an estimated foreground color of <tt>#%2$06X</tt> and an estimated background color of <tt>#%3$06X</tt>. Consider increasing this item\'s text contrast ratio to the configured ratio of %4$.2f or greater.
+result_message_textview_heuristic_customized_contrast_not_sufficient_confirmed = The item\'s text contrast ratio is %1$.2f. This ratio is based on the provided foreground color of <tt>#%2$06X</tt> and provided background color of <tt>#%3$06X</tt>. Consider increasing this item\'s text contrast ratio to the configured ratio of %4$.2f or greater.
+result_message_traversal_cycle = This item may be part of a traversal ordering cycle due to its <tt>%1$s</tt> attribute. Traversal behavior with screen readers may be unpredictable.
+result_message_traversal_over_constrained = Traversal ordering for this item may be over constrained based on its <tt>android:accessibilityTraversalBefore</tt> and <tt>android:accessibilityTraversalAfter</tt> attributes. Traversal behavior with screen readers may be unpredictable.
+result_message_urlspan_invalid_url = Verify that the URL within this item\'s <tt>URLSpan</tt> is valid.
+result_message_urlspan_not_clickablespan = This item should use a <tt>URLSpan</tt> in place of a <tt>ClickableSpan</tt>.
+result_message_view_bounds = This %1$s item also has an on-screen location of <tt>%2$s</tt>.
+result_message_view_not_within_screencapture = This item\'s on-screen location (<tt>%1$s</tt>) were not within the screen capture on-screen location (<tt>%2$s</tt>).
+result_message_web_content = Web content is not evaluated.
+underline_text = underline
diff --git a/validator/src/android/os/Build.java b/validator/src/android/os/Build.java
new file mode 100644
index 0000000000..971f4667aa
--- /dev/null
+++ b/validator/src/android/os/Build.java
@@ -0,0 +1,35 @@
+/*
+ * 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
new file mode 100644
index 0000000000..0312ca9c48
--- /dev/null
+++ b/validator/src/com/android/tools/idea/validator/LayoutValidator.java
@@ -0,0 +1,52 @@
+/*
+ * 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 com.android.tools.idea.validator;
+
+import com.android.tools.idea.validator.ValidatorData.Level;
+import com.android.tools.idea.validator.ValidatorData.Policy;
+import com.android.tools.idea.validator.ValidatorData.Type;
+import com.android.tools.idea.validator.accessibility.AccessibilityValidator;
+import com.android.tools.layoutlib.annotations.NotNull;
+
+import android.view.View;
+
+import java.util.EnumSet;
+
+/**
+ * Main class for validating layout.
+ */
+public class LayoutValidator {
+
+ private static final ValidatorData.Policy DEFAULT_POLICY = new Policy(
+ EnumSet.of(Type.ACCESSIBILITY, Type.RENDER),
+ EnumSet.of(Level.ERROR, Level.WARNING));
+
+ /**
+ * Validate the layout using the default policy.
+ * Precondition: View must be attached to the window.
+ *
+ * @return The validation results. If no issue is found it'll return empty result.
+ */
+ @NotNull
+ public static ValidatorResult validate(@NotNull View view) {
+ if (view.isAttachedToWindow()) {
+ return AccessibilityValidator.validateAccessibility(view, DEFAULT_POLICY.mLevels);
+ }
+ // 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
new file mode 100644
index 0000000000..f2112a59af
--- /dev/null
+++ b/validator/src/com/android/tools/idea/validator/ValidatorData.java
@@ -0,0 +1,94 @@
+/*
+ * 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 com.android.tools.idea.validator;
+
+import com.android.tools.layoutlib.annotations.NotNull;
+import com.android.tools.layoutlib.annotations.Nullable;
+
+import java.util.EnumSet;
+
+/**
+ * Data used for layout validation.
+ */
+public class ValidatorData {
+
+ /**
+ * Category of validation
+ */
+ public enum Type {
+ ACCESSIBILITY,
+ RENDER,
+ }
+
+ /**
+ * Level of importance
+ */
+ public enum Level {
+ ERROR,
+ WARNING,
+ INFO,
+ VERBOSE
+ }
+
+ /**
+ * Determine what types and levels of validation to run.
+ */
+ public static class Policy {
+ @NotNull final EnumSet<Type> mTypes;
+ @NotNull final EnumSet<Level> mLevels;
+
+ public Policy(@NotNull EnumSet<Type> types, @NotNull EnumSet<Level> levels) {
+ mTypes = types;
+ mLevels = levels;
+ }
+ }
+
+ /**
+ * Suggested fix to the user or to the studio.
+ */
+ public static class Fix {
+ @NotNull public final String mFix;
+
+ public Fix(String fix) {
+ mFix = fix;
+ }
+ }
+
+ /**
+ * 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;
+
+ public Issue(
+ @NotNull Type type,
+ @NotNull String msg,
+ @NotNull Level level,
+ @Nullable Long srcId,
+ @Nullable Fix fix) {
+ mType = type;
+ mMsg = msg;
+ mLevel = level;
+ mSrcId = srcId;
+ mFix = fix;
+ }
+ }
+}
diff --git a/validator/src/com/android/tools/idea/validator/ValidatorResult.java b/validator/src/com/android/tools/idea/validator/ValidatorResult.java
new file mode 100644
index 0000000000..79129bc23a
--- /dev/null
+++ b/validator/src/com/android/tools/idea/validator/ValidatorResult.java
@@ -0,0 +1,90 @@
+/*
+ * 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 com.android.tools.idea.validator;
+
+import com.android.tools.idea.validator.ValidatorData.Issue;
+import com.android.tools.idea.validator.ValidatorData.Level;
+import com.android.tools.layoutlib.annotations.NotNull;
+
+import android.view.View;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import com.google.common.collect.BiMap;
+import com.google.common.collect.HashBiMap;
+import com.google.common.collect.ImmutableBiMap;
+
+/**
+ * Results of layout validation.
+ */
+public class ValidatorResult {
+
+ @NotNull private final ImmutableBiMap<Long, View> mSrcMap;
+ @NotNull private final ArrayList<Issue> mIssues;
+
+ /**
+ * Please use {@link Builder} for creating results.
+ */
+ private ValidatorResult(BiMap<Long, View> srcMap, ArrayList<Issue> issues) {
+ mSrcMap = ImmutableBiMap.<Long, View>builder().putAll(srcMap).build();
+ mIssues = issues;
+ }
+
+ /**
+ * @return the source map of all the Views.
+ */
+ public ImmutableBiMap<Long, View> getSrcMap() {
+ return mSrcMap;
+ }
+
+ /**
+ * @return list of issues.
+ */
+ public List<Issue> getIssues() {
+ return mIssues;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder()
+ .append("Result containing ")
+ .append(mIssues.size())
+ .append(" issues:\n");
+
+ for (Issue issue : mIssues) {
+ if (issue.mLevel == Level.ERROR) {
+ builder.append(" - [")
+ .append(issue.mLevel.name())
+ .append("] ")
+ .append(issue.mMsg)
+ .append("\n");
+ }
+ }
+ return builder.toString();
+ }
+
+ public static class Builder {
+ @NotNull public final BiMap<Long, View> mSrcMap = HashBiMap.create();
+ @NotNull public final ArrayList<Issue> mIssues = new ArrayList<>();
+
+ public ValidatorResult build() {
+ return new ValidatorResult(mSrcMap, mIssues);
+ }
+
+ }
+}
diff --git a/validator/src/com/android/tools/idea/validator/accessibility/AccessibilityValidator.java b/validator/src/com/android/tools/idea/validator/accessibility/AccessibilityValidator.java
new file mode 100644
index 0000000000..3f59ff925a
--- /dev/null
+++ b/validator/src/com/android/tools/idea/validator/accessibility/AccessibilityValidator.java
@@ -0,0 +1,141 @@
+/*
+ * 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 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.Level;
+import com.android.tools.idea.validator.ValidatorData.Type;
+import com.android.tools.idea.validator.ValidatorResult;
+import com.android.tools.layoutlib.annotations.NotNull;
+import com.android.tools.layoutlib.annotations.Nullable;
+
+import android.view.View;
+
+import java.util.ArrayList;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.ResourceBundle;
+import java.util.Set;
+
+import com.google.android.apps.common.testing.accessibility.framework.AccessibilityCheckPreset;
+import com.google.android.apps.common.testing.accessibility.framework.AccessibilityCheckResult.AccessibilityCheckResultType;
+import com.google.android.apps.common.testing.accessibility.framework.AccessibilityHierarchyCheck;
+import com.google.android.apps.common.testing.accessibility.framework.AccessibilityHierarchyCheckResult;
+import com.google.android.apps.common.testing.accessibility.framework.strings.StringManager;
+import com.google.android.apps.common.testing.accessibility.framework.uielement.AccessibilityHierarchyAndroid;
+import com.google.common.collect.BiMap;
+
+/**
+ * Validator specific for running Accessibility specific issues.
+ */
+public class AccessibilityValidator {
+
+ static {
+ /**
+ * Overriding default ResourceBundle ATF uses. ATF would use generic Java resources
+ * instead of Android's .xml.
+ *
+ * By default ATF generates ResourceBundle to support Android specific env/ classloader,
+ * which is quite different from Layoutlib, which supports multiple classloader depending
+ * on env (testing vs in studio).
+ *
+ * To support ATF in Layoutlib, easiest way is to convert resources from Android xml to
+ * generic Java resources (strings.properties), and have the default ResourceBundle ATF
+ * uses be redirected.
+ */
+ StringManager.setResourceBundleProvider(locale -> ResourceBundle.getBundle("strings"));
+ }
+
+ /**
+ * Run Accessibility specific validation test and receive results.
+ * @param view the root view
+ * @param filter list of levels to allow
+ * @return results with all the accessibility issues and warnings.
+ */
+ @NotNull
+ public static ValidatorResult validateAccessibility(
+ @NotNull View view,
+ @NotNull EnumSet<Level> filter) {
+ ValidatorResult.Builder builder = new ValidatorResult.Builder();
+
+ List<AccessibilityHierarchyCheckResult> results = getHierarchyCheckResults(view,
+ builder.mSrcMap);
+
+ for (AccessibilityHierarchyCheckResult result : results) {
+ ValidatorData.Level level = convertLevel(result.getType());
+ if (!filter.contains(level)) {
+ continue;
+ }
+
+ ValidatorData.Fix fix = generateFix(result);
+ Long srcId = null;
+ if (result.getElement() != null) {
+ srcId = result.getElement().getCondensedUniqueId();
+ }
+ Issue issue = new Issue(
+ Type.ACCESSIBILITY,
+ result.getMessage(Locale.ENGLISH).toString(),
+ level,
+ srcId,
+ fix);
+ builder.mIssues.add(issue);
+ }
+ return builder.build();
+ }
+
+ @NotNull
+ private static ValidatorData.Level convertLevel(@NotNull AccessibilityCheckResultType type) {
+ switch (type) {
+ case ERROR:
+ return Level.ERROR;
+ case WARNING:
+ return Level.WARNING;
+ case INFO:
+ return Level.INFO;
+ // TODO: Maybe useful later?
+ case SUPPRESSED:
+ case NOT_RUN:
+ default:
+ return Level.VERBOSE;
+ }
+ }
+
+ @Nullable
+ private static ValidatorData.Fix generateFix(@NotNull AccessibilityHierarchyCheckResult result) {
+ // TODO: Once ATF is ready to return us with appropriate fix, build proper fix here.
+ return new Fix("");
+ }
+
+ @NotNull
+ private static List<AccessibilityHierarchyCheckResult> getHierarchyCheckResults(
+ @NotNull View view,
+ @NotNull BiMap<Long, View> originMap) {
+ @NotNull Set<AccessibilityHierarchyCheck> checks = AccessibilityCheckPreset.getAccessibilityHierarchyChecksForPreset(
+ AccessibilityCheckPreset.LATEST);
+ @NotNull AccessibilityHierarchyAndroid hierarchy = AccessibilityHierarchyAndroid.newBuilder(view).setViewOriginMap(originMap).build();
+ ArrayList<AccessibilityHierarchyCheckResult> a11yResults = new ArrayList();
+
+ for (AccessibilityHierarchyCheck check : checks) {
+ a11yResults.addAll(check.runCheckOnHierarchy(hierarchy));
+ }
+
+ return a11yResults;
+ }
+}
diff --git a/validator/validator.iml b/validator/validator.iml
new file mode 100644
index 0000000000..e75d99a918
--- /dev/null
+++ b/validator/validator.iml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="JAVA_MODULE" version="4">
+ <component name="NewModuleRootManager" inherit-compiler-output="true">
+ <exclude-output />
+ <content url="file://$MODULE_DIR$">
+ <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
+ <sourceFolder url="file://$MODULE_DIR$/resources" type="java-resource" />
+ </content>
+ <orderEntry type="inheritedJdk" />
+ <orderEntry type="sourceFolder" forTests="false" />
+ <orderEntry type="library" name="framework.jar" level="project" />
+ <orderEntry type="module-library">
+ <library name="guava">
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../prebuilts/tools/common/m2/repository/com/google/guava/guava/22.0/guava-22.0.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES>
+ <root url="jar://$MODULE_DIR$/../../../prebuilts/tools/common/m2/repository/com/google/guava/guava/22.0/guava-22.0-sources.jar!/" />
+ </SOURCES>
+ </library>
+ </orderEntry>
+ <orderEntry type="module" module-name="common" />
+ <orderEntry type="library" name="framework.jar" level="project" />
+ <orderEntry type="library" scope="TEST" name="hamcrest" level="project" />
+ <orderEntry type="module-library">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../prebuilts/tools/common/m2/repository/com/google/protobuf/protobuf-lite/3.0.1/protobuf-lite-3.0.1.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+ </orderEntry>
+ <orderEntry type="module-library">
+ <library>
+ <CLASSES>
+ <root url="jar://$MODULE_DIR$/../../../prebuilts/tools/common/m2/repository/org/jsoup/jsoup/1.6.3/jsoup-1.6.3.jar!/" />
+ </CLASSES>
+ <JAVADOC />
+ <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