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