• Home
  • History
  • Annotate
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2013 DroidDriver committers
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package io.appium.droiddriver.validators;
18 
19 import android.annotation.TargetApi;
20 import android.text.TextUtils;
21 import android.view.accessibility.AccessibilityNodeInfo;
22 
23 import io.appium.droiddriver.UiElement;
24 import io.appium.droiddriver.actions.Action;
25 import io.appium.droiddriver.uiautomation.UiAutomationElement;
26 
27 /**
28  * Fall-back Validator for accessibility.
29  */
30 @TargetApi(14)
31 public class DefaultAccessibilityValidator implements Validator {
32   @Override
isApplicable(UiElement element, Action action)33   public boolean isApplicable(UiElement element, Action action) {
34     return true;
35   }
36 
37   @Override
validate(UiElement element, Action action)38   public String validate(UiElement element, Action action) {
39     return isSpeakingNode(element) ? null : "TalkBack cannot speak about it";
40   }
41 
42   // Logic from TalkBack
isAccessibilityFocusable(UiElement element)43   private static boolean isAccessibilityFocusable(UiElement element) {
44     return isActionableForAccessibility(element)
45         || (isTopLevelScrollItem(element) && isSpeakingNode(element));
46   }
47 
isTopLevelScrollItem(UiElement element)48   private static boolean isTopLevelScrollItem(UiElement element) {
49     UiElement parent = element.getParent();
50     return parent != null && parent.isScrollable();
51   }
52 
53   @SuppressWarnings("deprecation")
isActionableForAccessibility(UiElement element)54   private static boolean isActionableForAccessibility(UiElement element) {
55     if (element.isFocusable() || element.isClickable() || element.isLongClickable()) {
56       return true;
57     }
58 
59     if (element instanceof UiAutomationElement) {
60       AccessibilityNodeInfo node = ((UiAutomationElement) element).getRawElement();
61       return (node.getActions() & AccessibilityNodeInfo.ACTION_FOCUS) == AccessibilityNodeInfo.ACTION_FOCUS;
62     }
63     return false;
64   }
65 
isSpeakingNode(UiElement element)66   private static boolean isSpeakingNode(UiElement element) {
67     return hasContentDescriptionOrText(element) || element.isCheckable()
68         || hasNonActionableSpeakingChildren(element);
69   }
70 
hasNonActionableSpeakingChildren(UiElement element)71   private static boolean hasNonActionableSpeakingChildren(UiElement element) {
72     // Recursively check visible and non-focusable descendant nodes.
73     for (UiElement child : element.getChildren(UiElement.VISIBLE)) {
74       if (!isAccessibilityFocusable(child) && isSpeakingNode(child)) {
75         return true;
76       }
77     }
78     return false;
79   }
80 
hasContentDescriptionOrText(UiElement element)81   private static boolean hasContentDescriptionOrText(UiElement element) {
82     return !TextUtils.isEmpty(element.getContentDescription())
83         || !TextUtils.isEmpty(element.getText());
84   }
85 }
86