1 /* 2 * Copyright (C) 2015 Google Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 * use this file except in compliance with the License. You may obtain a copy of 6 * 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, WITHOUT 12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 * License for the specific language governing permissions and limitations under 14 * the License. 15 */ 16 package com.google.android.apps.common.testing.accessibility.framework.uielement; 17 18 import static com.google.common.base.Preconditions.checkNotNull; 19 import static com.google.common.base.Preconditions.checkState; 20 21 import com.google.android.apps.common.testing.accessibility.framework.uielement.proto.AccessibilityHierarchyProtos.AccessibilityHierarchyProto; 22 import com.google.android.apps.common.testing.accessibility.framework.uielement.proto.AccessibilityHierarchyProtos.ViewElementClassNamesProto; 23 import com.google.android.apps.common.testing.accessibility.framework.uielement.proto.AccessibilityHierarchyProtos.WindowHierarchyElementProto; 24 import com.google.common.collect.ImmutableBiMap; 25 import com.google.common.collect.ImmutableList; 26 import java.util.Collection; 27 import java.util.Map; 28 import java.util.NoSuchElementException; 29 import org.checkerframework.checker.nullness.qual.Nullable; 30 31 /** 32 * Representation of a UI hierarchy for accessibility checking 33 * 34 * <p>Such a hierarchy may contain a forest of {@link WindowHierarchyElement}s, each of which 35 * contain a tree of {@link ViewHierarchyElement}s. 36 */ 37 public class AccessibilityHierarchy { 38 /* A representation of the device's state at the time a hierarchy is initially captured */ 39 private final DeviceState deviceState; 40 41 /* The type of data source from which the AccessibilityHierarchy was originally constructed. */ 42 private final AccessibilityHierarchyOrigin origin; 43 44 /* The id of each window corresponds to its position in this list */ 45 private final ImmutableList<? extends WindowHierarchyElement> windowHierarchyElements; 46 47 /* A reference to the 'active' window. Exactly one such window exists in any hierarchy. */ 48 private final WindowHierarchyElement activeWindow; 49 50 /* A nested class that stores all unique view element class names. */ 51 protected final ViewElementClassNames viewElementClassNames; 52 AccessibilityHierarchy( DeviceState deviceState, AccessibilityHierarchyOrigin origin, ImmutableList<? extends WindowHierarchyElement> windowHierarchyElements, WindowHierarchyElement activeWindow, ViewElementClassNames viewElementClassNames)53 protected AccessibilityHierarchy( 54 DeviceState deviceState, 55 AccessibilityHierarchyOrigin origin, 56 ImmutableList<? extends WindowHierarchyElement> windowHierarchyElements, 57 WindowHierarchyElement activeWindow, 58 ViewElementClassNames viewElementClassNames) { 59 this.deviceState = deviceState; 60 this.origin = origin; 61 this.windowHierarchyElements = windowHierarchyElements; 62 this.activeWindow = activeWindow; 63 this.viewElementClassNames = viewElementClassNames; 64 } 65 66 /** Returns certain properties of the device at the time the hierarchy was originally captured. */ getDeviceState()67 public DeviceState getDeviceState() { 68 return deviceState; 69 } 70 71 /** Returns the type of data source from which the hierarchy was originally constructed. */ getOrigin()72 public AccessibilityHierarchyOrigin getOrigin() { 73 return origin; 74 } 75 76 /** 77 * Get all {@link WindowHierarchyElement}s in this hierarchy. 78 * 79 * @return An unmodifiable collection of all windows in hierarchy 80 */ getAllWindows()81 public Collection<? extends WindowHierarchyElement> getAllWindows() { 82 return windowHierarchyElements; 83 } 84 85 /** 86 * Returns the WindowHierarchyElement representing the active window in this hierarchy. If this 87 * hierarchy was originally constructed from an {@code AccessibilityNodeInfo} or {@code View}, 88 * this returns a WindowHierarchyElement that was implicitly created to hold the hierarchy. 89 */ getActiveWindow()90 public WindowHierarchyElement getActiveWindow() { 91 return activeWindow; 92 } 93 94 /** 95 * Returns the WindowHierarchyElement identified by the given ID in this hierarchy. 96 * 97 * @param id The identifier for the desired {@link WindowHierarchyElement}, as returned by {@link 98 * WindowHierarchyElement#getId()}. 99 * @return The {@link WindowHierarchyElement} identified by {@code id} in this hierarchy 100 * @throws NoSuchElementException if no window within this hierarchy matches the provided {@code 101 * id} 102 */ getWindowById(int id)103 public WindowHierarchyElement getWindowById(int id) { 104 if ((id < 0) || (id >= windowHierarchyElements.size())) { 105 throw new NoSuchElementException(); 106 } 107 return windowHierarchyElements.get(id); 108 } 109 110 /** 111 * Returns the ViewHierarchyElement identified by the given ID in this hierarchy. 112 * 113 * @param condensedUniqueId The identifier for the desired {@link ViewHierarchyElement}, as 114 * returned by {@link ViewHierarchyElement#getCondensedUniqueId()} 115 * @return The {@link ViewHierarchyElement} identified by {@code id} in this hierarchy 116 * @throws NoSuchElementException if no view within this hierarchy matches the provided {@code 117 * condensedUniqueId} 118 */ getViewById(long condensedUniqueId)119 public ViewHierarchyElement getViewById(long condensedUniqueId) { 120 int windowId = (int) (condensedUniqueId >>> 32); 121 int viewId = (int) condensedUniqueId; 122 return getWindowById(windowId).getViewById(viewId); 123 } 124 125 /** Returns a protocol buffer representation of this hierarchy. */ toProto()126 public AccessibilityHierarchyProto toProto() { 127 AccessibilityHierarchyProto.Builder builder = AccessibilityHierarchyProto.newBuilder(); 128 builder 129 .setDeviceState(getDeviceState().toProto()) 130 .setOrigin(getOrigin().toProto()) 131 .setActiveWindowId(getActiveWindow().getId()) 132 .setViewElementClassNames(getViewElementClassNames().toProto()); 133 for (WindowHierarchyElement window : windowHierarchyElements) { 134 builder.addWindows(window.toProto()); 135 } 136 137 return builder.build(); 138 } 139 140 /** Set backpointers from the windows to the accessibility hierarchy. */ setAccessibilityHierarchy()141 private void setAccessibilityHierarchy() { 142 for (WindowHierarchyElement window : this.windowHierarchyElements) { 143 window.setAccessibilityHierarchy(this); 144 } 145 } 146 147 /** 148 * Returns a new builder that can build an AccessibilityHierarchy from a proto. 149 * 150 * @param proto A protocol buffer representation of a hierarchy 151 */ newBuilder(AccessibilityHierarchyProto proto)152 public static Builder newBuilder(AccessibilityHierarchyProto proto) { 153 checkNotNull(proto); 154 return new Builder(proto); 155 } 156 157 /** Returns a {@link ViewElementClassNames} that contains view class name and identifiers. */ getViewElementClassNames()158 ViewElementClassNames getViewElementClassNames() { 159 return viewElementClassNames; 160 } 161 162 /** 163 * Maintains a bidirectional mapping between View class names and a unique integer identifier for 164 * each of those classes. 165 */ 166 static class ViewElementClassNames { 167 protected ImmutableBiMap<String, Integer> uniqueViewElementsClassNames; 168 ViewElementClassNames()169 protected ViewElementClassNames() {} 170 171 /** Populate bimap from a given map. */ ViewElementClassNames(Map<String, Integer> viewElementsMap)172 public ViewElementClassNames(Map<String, Integer> viewElementsMap) { 173 this.uniqueViewElementsClassNames = ImmutableBiMap.copyOf(viewElementsMap); 174 } 175 176 /** Populate bimap from a given proto. */ ViewElementClassNames(ViewElementClassNamesProto proto)177 public ViewElementClassNames(ViewElementClassNamesProto proto) { 178 this.uniqueViewElementsClassNames = ImmutableBiMap.copyOf(proto.getClassNameMap()); 179 } 180 181 /** Returns an identifier associated with a given class name. Returns null if not found. */ getIdentifierForClassName(String className)182 public @Nullable Integer getIdentifierForClassName(String className) { 183 return uniqueViewElementsClassNames.get(className); 184 } 185 186 /** Returns a class name associated with a given identifier. Returns null if not found. */ getClassNameForIdentifier(int id)187 public @Nullable String getClassNameForIdentifier(int id) { 188 return uniqueViewElementsClassNames.inverse().get(id); 189 } 190 toProto()191 ViewElementClassNamesProto toProto() { 192 return ViewElementClassNamesProto.newBuilder() 193 .putAllClassName(uniqueViewElementsClassNames) 194 .build(); 195 } 196 getMap()197 ImmutableBiMap<String, Integer> getMap() { 198 return uniqueViewElementsClassNames; 199 } 200 } 201 202 /** 203 * A builder for {@link AccessibilityHierarchy}; obtained using {@link 204 * AccessibilityHierarchy#newBuilder(AccessibilityHierarchyProto)}. 205 */ 206 public static class Builder { 207 private final AccessibilityHierarchyProto proto; 208 Builder(AccessibilityHierarchyProto proto)209 Builder(AccessibilityHierarchyProto proto) { 210 this.proto = proto; 211 } 212 build()213 public AccessibilityHierarchy build() { 214 DeviceState deviceState = new DeviceState(proto.getDeviceState()); 215 AccessibilityHierarchyOrigin origin = 216 AccessibilityHierarchyOrigin.fromProto(proto.getOrigin()); 217 int activeWindowId = proto.getActiveWindowId(); 218 219 ImmutableList.Builder<WindowHierarchyElement> windowHierarchyElementsBuilder = 220 ImmutableList.<WindowHierarchyElement>builderWithExpectedSize(proto.getWindowsCount()); 221 for (WindowHierarchyElementProto windowProto : proto.getWindowsList()) { 222 windowHierarchyElementsBuilder.add(WindowHierarchyElement.newBuilder(windowProto).build()); 223 } 224 ImmutableList<WindowHierarchyElement> windowHierarchyElements = 225 windowHierarchyElementsBuilder.build(); 226 checkState( 227 !windowHierarchyElements.isEmpty(), "Hierarchies must contain at least one window."); 228 WindowHierarchyElement activeWindow = windowHierarchyElements.get(activeWindowId); 229 ViewElementClassNames viewElementClassNames = 230 new ViewElementClassNames(proto.getViewElementClassNames()); 231 232 AccessibilityHierarchy hierarchy = 233 new AccessibilityHierarchy( 234 deviceState, origin, windowHierarchyElements, activeWindow, viewElementClassNames); 235 hierarchy.setAccessibilityHierarchy(); 236 return hierarchy; 237 } 238 } 239 } 240