• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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