• Home
  • 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     if (isActionableForAccessibility(element)) {
45       return true;
46     }
47 
48     if (isTopLevelScrollItem(element) && (isSpeakingNode(element))) {
49       return true;
50     }
51     return false;
52   }
53 
isTopLevelScrollItem(UiElement element)54   private static boolean isTopLevelScrollItem(UiElement element) {
55     UiElement parent = element.getParent();
56     return parent != null && parent.isScrollable();
57   }
58 
59   @SuppressWarnings("deprecation")
isActionableForAccessibility(UiElement element)60   private static boolean isActionableForAccessibility(UiElement element) {
61     if (element.isFocusable() || element.isClickable() || element.isLongClickable()) {
62       return true;
63     }
64 
65     if (element instanceof UiAutomationElement) {
66       AccessibilityNodeInfo node = ((UiAutomationElement) element).getRawElement();
67       return (node.getActions() & AccessibilityNodeInfo.ACTION_FOCUS) == AccessibilityNodeInfo.ACTION_FOCUS;
68     }
69     return false;
70   }
71 
isSpeakingNode(UiElement element)72   private static boolean isSpeakingNode(UiElement element) {
73     return hasContentDescriptionOrText(element) || element.isCheckable()
74         || hasNonActionableSpeakingChildren(element);
75   }
76 
hasNonActionableSpeakingChildren(UiElement element)77   private static boolean hasNonActionableSpeakingChildren(UiElement element) {
78     // Recursively check visible and non-focusable descendant nodes.
79     for (UiElement child : element.getChildren(UiElement.VISIBLE)) {
80       if (!isAccessibilityFocusable(child) && isSpeakingNode(child)) {
81         return true;
82       }
83     }
84     return false;
85   }
86 
hasContentDescriptionOrText(UiElement element)87   private static boolean hasContentDescriptionOrText(UiElement element) {
88     return !TextUtils.isEmpty(element.getContentDescription())
89         || !TextUtils.isEmpty(element.getText());
90   }
91 }
92