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