├── .gitignore ├── .idea ├── compiler.xml ├── copyright │ └── profiles_settings.xml ├── encodings.xml ├── gradle.xml ├── misc.xml ├── modules.xml ├── runConfigurations.xml └── vcs.xml ├── A11yUtils ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── moba11y │ │ │ └── androida11yutils │ │ │ ├── A11yNodeInfo.java │ │ │ ├── A11yNodeInfoMatcher.java │ │ │ └── A11yNodeInfoMocked.java │ └── res │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── moba11y │ └── androida11yutils │ ├── A11yNodeInfoMatcherTest.java │ ├── A11yNodeInfoMockedTest.java │ └── A11yNodeInfoTest.java ├── README.md ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | .externalNativeBuild 10 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 19 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 24 | 25 | 37 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 64 | 65 | 66 | 67 | 68 | 1.7 69 | 70 | 75 | 76 | 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /A11yUtils/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /A11yUtils/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | def getVersionCodeString = { -> 4 | def stdout = new ByteArrayOutputStream() 5 | exec { 6 | commandLine 'git', 'rev-list', '--all', '--count' 7 | standardOutput = stdout 8 | } 9 | return stdout.toString().trim() 10 | } 11 | 12 | def getVersionCode = { -> 13 | return Integer.parseInt(getVersionCodeString()) 14 | } 15 | 16 | def getVersionName = { -> 17 | def stdout = new ByteArrayOutputStream() 18 | 19 | exec { 20 | commandLine 'git', 'describe', '--tags', '--abbrev=0' 21 | standardOutput = stdout 22 | } 23 | 24 | return stdout.toString().trim().concat("." + getVersionCodeString()) 25 | } 26 | 27 | android { 28 | compileSdkVersion 25 29 | buildToolsVersion "25.0.1" 30 | defaultConfig { 31 | minSdkVersion 18 32 | targetSdkVersion 25 33 | versionCode getVersionCode() 34 | versionName getVersionName() 35 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 36 | } 37 | buildTypes { 38 | release { 39 | minifyEnabled false 40 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 41 | } 42 | } 43 | } 44 | 45 | dependencies { 46 | androidTestCompile 'com.android.support.test:runner:0.5' 47 | androidTestCompile 'com.android.support.test:rules:0.5' 48 | androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.2' 49 | androidTestCompile 'com.android.support.test.uiautomator:uiautomator-v18:2.1.2' 50 | 51 | compile 'com.android.support:appcompat-v7:25.0.1' 52 | 53 | testCompile 'junit:junit:4.12' 54 | testCompile 'org.robolectric:robolectric:3.1' 55 | } 56 | -------------------------------------------------------------------------------- /A11yUtils/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/chrismcmeeking/Library/Android/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /A11yUtils/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /A11yUtils/src/main/java/com/moba11y/androida11yutils/A11yNodeInfo.java: -------------------------------------------------------------------------------- 1 | package com.moba11y.androida11yutils; 2 | 3 | import android.graphics.Rect; 4 | import android.os.Build; 5 | import android.support.v4.view.ViewPager; 6 | import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat; 7 | import android.view.View; 8 | import android.view.accessibility.AccessibilityNodeInfo; 9 | import android.widget.Button; 10 | import android.widget.CheckBox; 11 | import android.widget.EditText; 12 | import android.widget.Switch; 13 | 14 | import java.util.ArrayList; 15 | import java.util.Comparator; 16 | import java.util.Iterator; 17 | import java.util.List; 18 | 19 | /** 20 | * Created by chrismcmeeking on 2/25/17. 21 | */ 22 | 23 | public class A11yNodeInfo implements Iterable, Comparator { 24 | 25 | public static A11yNodeInfo wrap(AccessibilityNodeInfo node) { 26 | if (node == null) return null; 27 | 28 | return new A11yNodeInfo(node); 29 | } 30 | 31 | public static A11yNodeInfo wrap(AccessibilityNodeInfoCompat node) { 32 | if (node == null) return null; 33 | return new A11yNodeInfo(node); 34 | } 35 | 36 | private static final ArrayList> ACTIVE_CLASSES; 37 | 38 | static { 39 | ACTIVE_CLASSES = new ArrayList<>(); 40 | ACTIVE_CLASSES.add(Button.class); 41 | ACTIVE_CLASSES.add(Switch.class); 42 | ACTIVE_CLASSES.add(CheckBox.class); 43 | ACTIVE_CLASSES.add(EditText.class); 44 | } 45 | 46 | public enum Actions { 47 | 48 | ACCESSIBILITY_FOCUS(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS), 49 | CLEAR_ACCESSIBILITY_FOCUS(AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS), 50 | CLEAR_FOCUS(AccessibilityNodeInfo.ACTION_CLEAR_FOCUS), 51 | CLEAR_SELECTION(AccessibilityNodeInfo.ACTION_CLEAR_SELECTION), 52 | CLICK(AccessibilityNodeInfo.ACTION_CLICK), 53 | COLLAPSE(AccessibilityNodeInfo.ACTION_COLLAPSE), 54 | COPY(AccessibilityNodeInfo.ACTION_COPY), 55 | CUT(AccessibilityNodeInfo.ACTION_CUT), 56 | LONG_CLICK(AccessibilityNodeInfo.ACTION_LONG_CLICK), 57 | PASTE(AccessibilityNodeInfo.ACTION_PASTE), 58 | PREVIOUS_AT_MOVEMENT_GRANULARITY(AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY), 59 | PREVIOUS_HTML_ELEMENT(AccessibilityNodeInfo.ACTION_PREVIOUS_HTML_ELEMENT); 60 | 61 | private final int mAndroidValue; 62 | 63 | Actions(int androidValue) { 64 | mAndroidValue = androidValue; 65 | } 66 | 67 | int getAndroidValue() { 68 | return mAndroidValue; 69 | } 70 | } 71 | 72 | private final AccessibilityNodeInfoCompat mNodeInfo; 73 | 74 | //A special constructor for testing. 75 | protected A11yNodeInfo() { 76 | mNodeInfo = null; 77 | } 78 | 79 | protected A11yNodeInfo(AccessibilityNodeInfo nodeInfo) { 80 | this(new AccessibilityNodeInfoCompat(nodeInfo)); 81 | } 82 | 83 | protected A11yNodeInfo(AccessibilityNodeInfoCompat nodeInfoCompat) { 84 | if (nodeInfoCompat == null) throw new RuntimeException("Wrapping a null node doesn't make sense"); 85 | mNodeInfo = nodeInfoCompat; 86 | } 87 | 88 | @Override public int compare(A11yNodeInfo lhs, A11yNodeInfo rhs) { 89 | 90 | int result; 91 | 92 | result = lhs.getSpeakableText().compareTo(rhs.getSpeakableText()); 93 | 94 | if (result != 0) return result; 95 | 96 | Rect lhsRect = lhs.getBoundsInScreen(); 97 | Rect rhsRect = rhs.getBoundsInScreen(); 98 | 99 | 100 | if (result != 0) return result; 101 | 102 | if (lhsRect.top < rhsRect.top) return -1; 103 | else if (lhsRect.top > rhsRect.top) return 1; 104 | 105 | if (lhsRect.left < rhsRect.left) return -1; 106 | else if (lhsRect.left > rhsRect.left) return 1; 107 | 108 | if (lhsRect.right < rhsRect.right) return -1; 109 | else if (lhsRect.right > rhsRect.right) return 1; 110 | 111 | if (lhsRect.bottom < rhsRect.bottom) return -1; 112 | else if (lhsRect.bottom > rhsRect.bottom) return 1; 113 | 114 | if (result != 0) return result; 115 | 116 | return 0; 117 | } 118 | 119 | public List getActionList() { 120 | return mNodeInfo.getActionList(); 121 | } 122 | 123 | public int getActions() { 124 | return mNodeInfo.getActions(); 125 | } 126 | 127 | /** 128 | * Callbacks for iterating over the A11yNodeInfo heirarchy. 129 | */ 130 | public interface OnVisitListener { 131 | 132 | /** 133 | * Called for every node during heirarchy traversals. 134 | * @param nodeInfo The node that work will be doneon. 135 | * @return Return true to stop traversing, false to continue. 136 | */ 137 | boolean onVisit(A11yNodeInfo nodeInfo); 138 | } 139 | 140 | public boolean isActiveElement() { 141 | for (Class clazz : ACTIVE_CLASSES) { 142 | if (this.getClassName().equalsIgnoreCase(clazz.getName())) return true; 143 | } 144 | 145 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 146 | if (getActionList().contains(AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK)) return true; 147 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 148 | if (getActionList().contains(AccessibilityNodeInfo.AccessibilityAction.ACTION_CONTEXT_CLICK)) return true; 149 | } 150 | 151 | if (getActionList().contains(AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK)) return true; 152 | if (getActionList().contains(AccessibilityNodeInfo.AccessibilityAction.ACTION_SELECT)) return true; 153 | } 154 | 155 | final int actions = getActions(); 156 | 157 | return (actions & AccessibilityNodeInfo.ACTION_CLICK) != 0 || 158 | (actions & AccessibilityNodeInfo.ACTION_LONG_CLICK) != 0 || 159 | (actions & AccessibilityNodeInfo.ACTION_SELECT) != 0; 160 | } 161 | 162 | public boolean performAction(Actions action) { 163 | return mNodeInfo.performAction(action.getAndroidValue()); 164 | } 165 | 166 | public AccessibilityNodeInfoCompat getAccessibilityNodeInfoCompat() { 167 | return mNodeInfo; 168 | } 169 | 170 | public Rect getBoundsInScreen() { 171 | Rect result = new Rect(); 172 | mNodeInfo.getBoundsInScreen(result); 173 | return result; 174 | } 175 | 176 | public A11yNodeInfo getChild(final int i) { 177 | 178 | if (i >= mNodeInfo.getChildCount()) throw new IndexOutOfBoundsException(); 179 | 180 | return new A11yNodeInfo(mNodeInfo.getChild(i)); 181 | } 182 | 183 | public int getChildCount() { 184 | return mNodeInfo.getChildCount(); 185 | } 186 | 187 | public String getClassName() { 188 | return mNodeInfo.getClassName().toString(); 189 | } 190 | 191 | public CharSequence getContentDescription() { 192 | return mNodeInfo.getContentDescription(); 193 | } 194 | 195 | /** 196 | * I don't often use CharSequence's, and prefer strings. Note: null strings will return as empty strings! 197 | * @return The content descriptiong as a NotNull String. 198 | */ 199 | public String getContentDescriptionAsString() { 200 | if (mNodeInfo.getContentDescription() == null) return ""; 201 | 202 | return mNodeInfo.getContentDescription().toString(); 203 | } 204 | 205 | /** 206 | * Gets the depth of the child in the node info heirarchy. 207 | * @return The depth of the node. 208 | */ 209 | public int getDepthInTree() { 210 | 211 | int result = 0; 212 | 213 | A11yNodeInfo parentNode = getParent(); 214 | 215 | while (parentNode != null) { 216 | parentNode = parentNode.getParent(); 217 | result++; 218 | } 219 | 220 | return result; 221 | } 222 | 223 | public A11yNodeInfo getLabeledBy() { 224 | return A11yNodeInfo.wrap(mNodeInfo.getLabeledBy()); 225 | } 226 | 227 | public A11yNodeInfo getParent() { 228 | if (mNodeInfo.getParent() == null) return null; 229 | 230 | return new A11yNodeInfo(mNodeInfo.getParent()); 231 | } 232 | 233 | /** 234 | * Attempts to calculate the string that will be read off by TalkBack for a given 235 | * accessibility node. Eventually including role, trait, and value information. 236 | * If null, returns an empty string instead. 237 | * 238 | * @return The string representing the spoken text. 239 | */ 240 | public String getSpeakableText() { 241 | //Todo: Use Eyes Free project??? Or make this more advanced. 242 | if (getContentDescription() != null) return getContentDescriptionAsString(); 243 | if (getText() != null) return getTextAsString(); 244 | return ""; 245 | } 246 | 247 | public CharSequence getText() { 248 | return mNodeInfo.getText(); 249 | } 250 | 251 | /** 252 | * Don't like CharSequences, and random null string checks. This will get the Text 253 | * as a NotNull String. 254 | * @return The text as a NotNull String. 255 | */ 256 | public String getTextAsString() { 257 | if (getText() != null) return getText().toString(); 258 | else return ""; 259 | } 260 | 261 | public String getViewIdResourceName() { 262 | if (mNodeInfo.getViewIdResourceName() == null) return ""; 263 | return mNodeInfo.getViewIdResourceName(); 264 | } 265 | 266 | /** 267 | * Implenting the iterable interface to more easily navigate the node infos children. 268 | * @return An itarator over the children of this A11yNodeInfo. 269 | */ 270 | @Override public Iterator iterator() { 271 | return new Iterator() { 272 | private int mNextIndex = 0; 273 | 274 | @Override 275 | public boolean hasNext() { 276 | //ChildCount isn't always accurate. Nodes may get recycled depending on the vent. 277 | //So we check the child count AND that the child isn't null. 278 | return mNextIndex < getChildCount() && (mNodeInfo == null || mNodeInfo.getChild(mNextIndex) != null); 279 | } 280 | 281 | @Override 282 | public A11yNodeInfo next() { 283 | return getChild(mNextIndex++); 284 | } 285 | 286 | @Override 287 | public void remove() { 288 | 289 | } 290 | }; 291 | } 292 | 293 | /** 294 | * Get the entire node heirarchy as a string. 295 | * @return The node heirarchy. 296 | */ 297 | public String toViewHeirarchy() { 298 | final StringBuilder result = new StringBuilder(); 299 | 300 | result.append("--------------- Accessibility Node Hierarchy ---------------\n"); 301 | 302 | visitNodes(new A11yNodeInfo.OnVisitListener() { 303 | @Override 304 | public boolean onVisit(A11yNodeInfo nodeInfo) { 305 | 306 | for (int i = 0; i < nodeInfo.getDepthInTree(); i++) { 307 | result.append('-'); 308 | } 309 | 310 | result.append(nodeInfo.toString()); 311 | result.append('\n'); 312 | 313 | return false; 314 | } 315 | }); 316 | 317 | result.append("--------------- Accessibility Node Hierarchy ---------------"); 318 | 319 | return result.toString(); 320 | } 321 | 322 | /** 323 | * Get the first {@link A11yNodeInfo node} that matches the given {@link A11yNodeInfoMatcher matcher} 324 | * @param matcher The matcher with props to match. 325 | * @return The first node that matches. 326 | */ 327 | public A11yNodeInfo getFirstNodeThatMatches(final A11yNodeInfoMatcher matcher) { 328 | return visitNodes(new OnVisitListener() { 329 | @Override 330 | public boolean onVisit(A11yNodeInfo nodeInfo) { 331 | return matcher.match(nodeInfo); 332 | } 333 | }); 334 | } 335 | 336 | public boolean isClassType(Class clazz) { 337 | return (clazz.getName().equalsIgnoreCase(getClassName())); 338 | } 339 | 340 | public boolean isScrollable() { 341 | return mNodeInfo.isScrollable(); 342 | } 343 | 344 | 345 | public boolean isVisibleToUser() { 346 | return mNodeInfo.isVisibleToUser(); 347 | } 348 | 349 | public boolean isInVisibleScrollableField() { 350 | A11yNodeInfo tempNode = wrap(mNodeInfo); 351 | A11yNodeInfo scrollableView = null; 352 | 353 | while(tempNode.getParent() != null) { 354 | if(tempNode.isScrollable() && !tempNode.isClassType(ViewPager.class)) { 355 | scrollableView = tempNode; 356 | } 357 | tempNode = tempNode.getParent(); 358 | } 359 | 360 | return scrollableView != null && scrollableView.isVisibleToUser(); 361 | } 362 | 363 | 364 | @Override public String toString() { 365 | if (mNodeInfo == null) throw new RuntimeException("This shouldn't be null"); 366 | return mNodeInfo.toString(); 367 | } 368 | 369 | /** 370 | * Loop over children in the node heirarchy, until one of them returns true. Return the 371 | * first element where "onVisit" returns true. This can be used to create a very 372 | * simple "find first" type of method. Though most of the time, you likely want 373 | * to travel all, in which case, just return "false" from your onVisit method, and 374 | * you will visit every node. 375 | * @param onVisitListener {@link A11yNodeInfo.OnVisitListener#onVisit(A11yNodeInfo) onVisit} 376 | * will be alled for every node, until {@link A11yNodeInfo.OnVisitListener#onVisit(A11yNodeInfo) onVisit} 377 | * returns true. 378 | * @return The first node for which {@link A11yNodeInfo.OnVisitListener#onVisit(A11yNodeInfo) onVisit} returns true. 379 | */ 380 | public A11yNodeInfo visitNodes(OnVisitListener onVisitListener) { 381 | 382 | if (onVisitListener.onVisit(this)) return this; 383 | 384 | for (A11yNodeInfo child : this) { 385 | A11yNodeInfo result = child.visitNodes(onVisitListener); 386 | if (result != null) return result; 387 | } 388 | 389 | return null; 390 | } 391 | } 392 | -------------------------------------------------------------------------------- /A11yUtils/src/main/java/com/moba11y/androida11yutils/A11yNodeInfoMatcher.java: -------------------------------------------------------------------------------- 1 | package com.moba11y.androida11yutils; 2 | 3 | import android.graphics.Rect; 4 | import android.view.View; 5 | 6 | /** 7 | * Created by chrismcmeeking on 2/27/17. 8 | */ 9 | 10 | public class A11yNodeInfoMatcher { 11 | 12 | private String mContentDescription; 13 | private String mText; 14 | private Class mClass; 15 | 16 | private Rect mContainedIn = null; 17 | private Rect mPositionEqual = null; 18 | 19 | private String mViewIdResourceName = ""; 20 | 21 | public A11yNodeInfoMatcher() {} 22 | 23 | public A11yNodeInfoMatcher setContentDescription(final String contentDescription) { 24 | mContentDescription = contentDescription; 25 | return this; 26 | } 27 | 28 | public A11yNodeInfoMatcher setClass(final Class clazz) { 29 | mClass = clazz; 30 | return this; 31 | } 32 | 33 | public A11yNodeInfoMatcher setPositionContainedIn(final Rect rect) { 34 | mContainedIn = rect; 35 | return this; 36 | } 37 | 38 | public A11yNodeInfoMatcher setPositionEqualTo(final Rect rect) { 39 | mPositionEqual = rect; 40 | return this; 41 | } 42 | 43 | public A11yNodeInfoMatcher setText(final String text) { 44 | mText = text; 45 | return this; 46 | } 47 | 48 | public A11yNodeInfoMatcher setViewIdResourceName(final String viewIdResourceName) { 49 | mViewIdResourceName = viewIdResourceName; 50 | return this; 51 | } 52 | 53 | public boolean match(A11yNodeInfo nodeInfo) { 54 | 55 | Rect position = nodeInfo.getBoundsInScreen(); 56 | 57 | if (mContainedIn != null) { 58 | if (position.top < mContainedIn.top) return false; 59 | if (position.left < mContainedIn.left) return false; 60 | if (position.right > mContainedIn.right) return false; 61 | if (position.bottom > mContainedIn.bottom) return false; 62 | } 63 | 64 | if (mPositionEqual != null) { 65 | if (position.top != mPositionEqual.top) return false; 66 | if (position.bottom != mPositionEqual.bottom) return false; 67 | if (position.left != mPositionEqual.left) return false; 68 | if (position.right != mPositionEqual.right) return false; 69 | } 70 | 71 | if (mContentDescription != null && 72 | (nodeInfo.getContentDescription() == null 73 | || !mContentDescription.contentEquals(nodeInfo.getContentDescription()))) return false; 74 | 75 | if (mText != null && (nodeInfo.getText() == null || !mText.contentEquals(nodeInfo.getText()))) return false; 76 | 77 | if (mClass != null && !mClass.getName().contentEquals(nodeInfo.getClassName())) return false; 78 | 79 | if (!nodeInfo.getViewIdResourceName().contains(mViewIdResourceName)) return false; 80 | 81 | return true; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /A11yUtils/src/main/java/com/moba11y/androida11yutils/A11yNodeInfoMocked.java: -------------------------------------------------------------------------------- 1 | package com.moba11y.androida11yutils; 2 | 3 | import android.graphics.Rect; 4 | import android.view.View; 5 | 6 | import java.util.ArrayList; 7 | 8 | /** 9 | * Created by chrismcmeeking on 2/27/17. 10 | */ 11 | 12 | class A11yNodeInfoMocked extends A11yNodeInfo { 13 | 14 | private ArrayList mChildren = new ArrayList<>(); 15 | 16 | private String mContentDescription; 17 | 18 | private A11yNodeInfoMocked mParent = null; 19 | 20 | private String mText; 21 | 22 | private Class mClass; 23 | 24 | private Rect mBounds = new Rect(); 25 | 26 | private String mViewIdResourceName = ""; 27 | 28 | public static A11yNodeInfoMocked create() { return new A11yNodeInfoMocked();} 29 | 30 | A11yNodeInfoMocked addChild(A11yNodeInfoMocked child) { 31 | mChildren.add(child); 32 | 33 | child.setParent(this); 34 | 35 | return this; 36 | } 37 | 38 | A11yNodeInfoMocked addChildren(Iterable children) { 39 | for (A11yNodeInfoMocked child : children) { 40 | addChild(child); 41 | } 42 | 43 | return this; 44 | } 45 | 46 | @Override 47 | public Rect getBoundsInScreen() { 48 | return mBounds; 49 | } 50 | 51 | @Override 52 | public A11yNodeInfoMocked getChild(final int i) { 53 | 54 | return mChildren.get(i); 55 | } 56 | 57 | 58 | @Override 59 | public int getChildCount() { 60 | return mChildren.size(); 61 | } 62 | 63 | @Override 64 | public String getClassName() { 65 | if (mClass == null) return ""; 66 | return mClass.getName(); 67 | } 68 | 69 | @Override 70 | public CharSequence getContentDescription() { 71 | return mContentDescription; 72 | } 73 | 74 | @Override 75 | public A11yNodeInfoMocked getParent() { 76 | return mParent; 77 | } 78 | 79 | @Override 80 | public CharSequence getText() { return mText;} 81 | 82 | @Override 83 | public String getViewIdResourceName() { 84 | return mViewIdResourceName; 85 | } 86 | 87 | A11yNodeInfoMocked setBoundsInScreen(Rect bounds) { 88 | mBounds = bounds; 89 | return this; 90 | } 91 | 92 | A11yNodeInfoMocked setClass(Class clazz) { 93 | mClass = clazz; 94 | return this; 95 | } 96 | 97 | A11yNodeInfoMocked setContentDescription(String contentDescription) { 98 | mContentDescription = contentDescription; 99 | return this; 100 | } 101 | 102 | private A11yNodeInfoMocked setParent(A11yNodeInfoMocked parent) { 103 | mParent = parent; 104 | return this; 105 | } 106 | 107 | A11yNodeInfoMocked setText(String text) { 108 | mText = text; 109 | return this; 110 | } 111 | 112 | A11yNodeInfoMocked setViewIdResourceName(final String viewIdResourceName) { 113 | mViewIdResourceName = viewIdResourceName; 114 | return this; 115 | } 116 | 117 | @Override 118 | public String toString() { 119 | return "Hashcode: " + Integer.toString(this.hashCode()); 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /A11yUtils/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | 7 | -------------------------------------------------------------------------------- /A11yUtils/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | AndroidA11yUtils 3 | 4 | -------------------------------------------------------------------------------- /A11yUtils/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |