1 /* 2 * Copyright (C) 2020 The Android Open Source Project 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 package com.android.car.rotary; 17 18 import android.view.accessibility.AccessibilityNodeInfo; 19 import android.view.accessibility.AccessibilityWindowInfo; 20 21 import androidx.annotation.NonNull; 22 import androidx.annotation.Nullable; 23 import androidx.annotation.VisibleForTesting; 24 25 import java.io.FileDescriptor; 26 import java.io.PrintWriter; 27 import java.util.HashMap; 28 import java.util.Map; 29 import java.util.Stack; 30 31 /** Cache of window type and most recently focused node for each window ID. */ 32 class WindowCache { 33 /** Window IDs. */ 34 private final Stack<Integer> mWindowIds; 35 /** Window types keyed by window IDs. */ 36 private final Map<Integer, Integer> mWindowTypes; 37 /** Most recent focused nodes keyed by window IDs. */ 38 private final Map<Integer, AccessibilityNodeInfo> mFocusedNodes; 39 40 @NonNull 41 private NodeCopier mNodeCopier = new NodeCopier(); 42 WindowCache()43 WindowCache() { 44 mWindowIds = new Stack<>(); 45 mWindowTypes = new HashMap<>(); 46 mFocusedNodes = new HashMap<>(); 47 } 48 49 /** 50 * Saves the focused node of the given window, removing the existing entry, if any. This method 51 * should be called when the focused node changed. 52 */ saveFocusedNode(int windowId, @NonNull AccessibilityNodeInfo focusedNode)53 void saveFocusedNode(int windowId, @NonNull AccessibilityNodeInfo focusedNode) { 54 if (mFocusedNodes.containsKey(windowId)) { 55 // Call remove(Integer) to remove. 56 AccessibilityNodeInfo oldNode = mFocusedNodes.remove(windowId); 57 oldNode.recycle(); 58 } 59 mFocusedNodes.put(windowId, copyNode(focusedNode)); 60 } 61 62 /** 63 * Saves the type of the given window, removing the existing entry, if any. This method should 64 * be called when a window was just added. 65 */ saveWindowType(int windowId, int windowType)66 void saveWindowType(int windowId, int windowType) { 67 Integer id = windowId; 68 if (mWindowIds.contains(id)) { 69 // Call remove(Integer) to remove. 70 mWindowIds.remove(id); 71 } 72 mWindowIds.push(id); 73 74 mWindowTypes.put(windowId, windowType); 75 } 76 77 /** 78 * Removes an entry if it exists. This method should be called when a window was just removed. 79 */ remove(int windowId)80 void remove(int windowId) { 81 Integer id = windowId; 82 if (mWindowIds.contains(id)) { 83 // Call remove(Integer) to remove. 84 mWindowIds.remove(id); 85 mWindowTypes.remove(id); 86 AccessibilityNodeInfo node = mFocusedNodes.remove(id); 87 Utils.recycleNode(node); 88 } 89 } 90 91 /** Gets the window type keyed by {@code windowId}, or null if none. */ 92 @Nullable getWindowType(int windowId)93 Integer getWindowType(int windowId) { 94 return mWindowTypes.get(windowId); 95 } 96 97 /** 98 * Returns a copy of the most recently focused node in the most recently added window, or null 99 * if none. 100 */ 101 @Nullable getMostRecentFocusedNode()102 AccessibilityNodeInfo getMostRecentFocusedNode() { 103 if (mWindowIds.isEmpty()) { 104 return null; 105 } 106 Integer recentWindowId = mWindowIds.peek(); 107 if (recentWindowId == null) { 108 return null; 109 } 110 return copyNode(mFocusedNodes.get(recentWindowId)); 111 } 112 113 /** Sets a mock NodeCopier instance for testing. */ 114 @VisibleForTesting setNodeCopier(@onNull NodeCopier nodeCopier)115 void setNodeCopier(@NonNull NodeCopier nodeCopier) { 116 mNodeCopier = nodeCopier; 117 } 118 copyNode(@ullable AccessibilityNodeInfo node)119 private AccessibilityNodeInfo copyNode(@Nullable AccessibilityNodeInfo node) { 120 return mNodeCopier.copy(node); 121 } 122 dump(FileDescriptor fd, PrintWriter writer, String[] args)123 void dump(FileDescriptor fd, PrintWriter writer, String[] args) { 124 writer.println(" windowIds: " + mWindowIds); 125 writer.println(" windowTypes:"); 126 for (Map.Entry<Integer, Integer> entry : mWindowTypes.entrySet()) { 127 writer.println(" windowId: " + entry.getKey() 128 + ", type: " + AccessibilityWindowInfo.typeToString(entry.getValue())); 129 } 130 writer.println(" focusedNodes:"); 131 for (Map.Entry<Integer, AccessibilityNodeInfo> entry : mFocusedNodes.entrySet()) { 132 writer.println(" windowId: " + entry.getKey() + ", node: " + entry.getValue()); 133 } 134 } 135 } 136