1 /* 2 * Copyright (C) 2023 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.server.policy; 17 18 import android.util.Log; 19 import android.util.SparseArray; 20 import android.view.KeyEvent; 21 22 import java.io.PrintWriter; 23 import java.util.ArrayList; 24 import java.util.List; 25 26 /** 27 * A class that is responsible for queueing deferred key actions which can be triggered at a later 28 * time. 29 */ 30 class DeferredKeyActionExecutor { 31 private static final boolean DEBUG = PhoneWindowManager.DEBUG_INPUT; 32 private static final String TAG = "DeferredKeyAction"; 33 34 private final SparseArray<TimedActionsBuffer> mBuffers = new SparseArray<>(); 35 36 /** 37 * Queue a key action which can be triggered at a later time. Note that this method will also 38 * delete any outdated actions belong to the same key code. 39 * 40 * <p>Warning: the queued actions will only be cleaned up lazily when a new gesture downTime is 41 * recorded. If no new gesture downTime is recorded and the existing gesture is not executable, 42 * the actions will be kept in the buffer indefinitely. This may cause memory leak if the action 43 * itself holds references to temporary objects, or if too many actions are queued for the same 44 * gesture. The risk scales as you track more key codes. Please use this method with caution and 45 * ensure you only queue small amount of actions with limited size. 46 * 47 * <p>If you need to queue a large amount of actions with large size, there are several 48 * potential solutions to relief the memory leak risks: 49 * 50 * <p>1. Add a timeout (e.g. ANR timeout) based clean-up mechanism. 51 * 52 * <p>2. Clean-up queued actions when we know they won't be needed. E.g., add a callback when 53 * the gesture is handled by apps, and clean up queued actions associated with the handled 54 * gesture. 55 * 56 * @param keyCode the key code which triggers the action. 57 * @param downTime the down time of the key gesture. For multi-press actions, this is the down 58 * time of the last press. For long-press or very long-press actions, this is the initial 59 * down time. 60 * @param action the action that will be triggered at a later time. 61 */ queueKeyAction(int keyCode, long downTime, Runnable action)62 public void queueKeyAction(int keyCode, long downTime, Runnable action) { 63 getActionsBufferWithLazyCleanUp(keyCode, downTime).addAction(action); 64 } 65 66 /** 67 * Make actions associated with the given key gesture executable. Actions already queued for the 68 * given gesture will be executed immediately. Any new actions belonging to this gesture will be 69 * executed as soon as they get queued. Note that this method will also delete any outdated 70 * actions belong to the same key code. 71 * 72 * @param keyCode the key code of the gesture. 73 * @param downTime the down time of the gesture. 74 */ setActionsExecutable(int keyCode, long downTime)75 public void setActionsExecutable(int keyCode, long downTime) { 76 getActionsBufferWithLazyCleanUp(keyCode, downTime).setExecutable(); 77 } 78 79 /** 80 * Clears all the queued action for given key code. 81 * 82 * @param keyCode the key code whose queued actions will be cleared. 83 */ cancelQueuedAction(int keyCode)84 public void cancelQueuedAction(int keyCode) { 85 TimedActionsBuffer actionsBuffer = mBuffers.get(keyCode); 86 if (actionsBuffer != null) { 87 actionsBuffer.clear(); 88 } 89 } 90 getActionsBufferWithLazyCleanUp(int keyCode, long downTime)91 private TimedActionsBuffer getActionsBufferWithLazyCleanUp(int keyCode, long downTime) { 92 TimedActionsBuffer buffer = mBuffers.get(keyCode); 93 if (buffer == null || buffer.getDownTime() != downTime) { 94 if (DEBUG && buffer != null) { 95 Log.d( 96 TAG, 97 "getActionsBufferWithLazyCleanUp: cleaning up gesture actions for key " 98 + KeyEvent.keyCodeToString(keyCode)); 99 } 100 buffer = new TimedActionsBuffer(keyCode, downTime); 101 mBuffers.put(keyCode, buffer); 102 } 103 return buffer; 104 } 105 dump(String prefix, PrintWriter pw)106 public void dump(String prefix, PrintWriter pw) { 107 pw.println(prefix + "Deferred key action executor:"); 108 if (mBuffers.size() == 0) { 109 pw.println(prefix + " empty"); 110 return; 111 } 112 for (int i = 0; i < mBuffers.size(); i++) { 113 mBuffers.valueAt(i).dump(prefix, pw); 114 } 115 } 116 117 /** A buffer holding a gesture down time and its corresponding actions. */ 118 private static class TimedActionsBuffer { 119 private final List<Runnable> mActions = new ArrayList<>(); 120 private final int mKeyCode; 121 private final long mDownTime; 122 private boolean mExecutable; 123 TimedActionsBuffer(int keyCode, long downTime)124 TimedActionsBuffer(int keyCode, long downTime) { 125 mKeyCode = keyCode; 126 mDownTime = downTime; 127 } 128 getDownTime()129 long getDownTime() { 130 return mDownTime; 131 } 132 addAction(Runnable action)133 void addAction(Runnable action) { 134 if (mExecutable) { 135 if (DEBUG) { 136 Log.i( 137 TAG, 138 "addAction: execute action for key " 139 + KeyEvent.keyCodeToString(mKeyCode)); 140 } 141 action.run(); 142 return; 143 } 144 mActions.add(action); 145 } 146 setExecutable()147 void setExecutable() { 148 mExecutable = true; 149 if (DEBUG && !mActions.isEmpty()) { 150 Log.i( 151 TAG, 152 "setExecutable: execute actions for key " 153 + KeyEvent.keyCodeToString(mKeyCode)); 154 } 155 for (Runnable action : mActions) { 156 action.run(); 157 } 158 mActions.clear(); 159 } 160 clear()161 void clear() { 162 mActions.clear(); 163 } 164 dump(String prefix, PrintWriter pw)165 void dump(String prefix, PrintWriter pw) { 166 if (mExecutable) { 167 pw.println(prefix + " " + KeyEvent.keyCodeToString(mKeyCode) + ": executable"); 168 } else { 169 pw.println( 170 prefix 171 + " " 172 + KeyEvent.keyCodeToString(mKeyCode) 173 + ": " 174 + mActions.size() 175 + " actions queued"); 176 } 177 } 178 } 179 } 180