1 /* 2 * Copyright (C) 2019 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.quickstep.util; 17 18 import androidx.annotation.NonNull; 19 import androidx.annotation.Nullable; 20 21 import java.io.PrintWriter; 22 import java.text.SimpleDateFormat; 23 import java.util.ArrayList; 24 import java.util.Date; 25 import java.util.List; 26 import java.util.Locale; 27 import java.util.Objects; 28 29 /** 30 * A log to keep track of the active gesture. 31 */ 32 public class ActiveGestureLog { 33 34 private static final int MAX_GESTURES_TRACKED = 10; 35 36 public static final ActiveGestureLog INSTANCE = new ActiveGestureLog(); 37 38 /** 39 * NOTE: This value should be kept same as 40 * ActivityTaskManagerService#INTENT_EXTRA_LOG_TRACE_ID in platform 41 */ 42 public static final String INTENT_EXTRA_LOG_TRACE_ID = "INTENT_EXTRA_LOG_TRACE_ID"; 43 44 private static final int TYPE_ONE_OFF = 0; 45 private static final int TYPE_FLOAT = 1; 46 private static final int TYPE_INTEGER = 2; 47 private static final int TYPE_BOOL_TRUE = 3; 48 private static final int TYPE_BOOL_FALSE = 4; 49 private static final int TYPE_INPUT_CONSUMER = 5; 50 private static final int TYPE_GESTURE_EVENT = 6; 51 52 private final EventLog[] logs; 53 private int nextIndex; 54 private int mCurrentLogId = 100; 55 ActiveGestureLog()56 private ActiveGestureLog() { 57 this.logs = new EventLog[MAX_GESTURES_TRACKED]; 58 this.nextIndex = 0; 59 } 60 61 /** 62 * Track the given event for error detection. 63 * 64 * @param gestureEvent GestureEvent representing an event during the current gesture's 65 * execution. 66 */ trackEvent(@ullable ActiveGestureErrorDetector.GestureEvent gestureEvent)67 public void trackEvent(@Nullable ActiveGestureErrorDetector.GestureEvent gestureEvent) { 68 addLog(TYPE_GESTURE_EVENT, "", 0, CompoundString.NO_OP, gestureEvent); 69 } 70 addLog(String event)71 public void addLog(String event) { 72 addLog(event, null); 73 } 74 addLog(String event, int extras)75 public void addLog(String event, int extras) { 76 addLog(event, extras, null); 77 } 78 addLog(String event, boolean extras)79 public void addLog(String event, boolean extras) { 80 addLog(event, extras, null); 81 } 82 addLog(CompoundString compoundString)83 public void addLog(CompoundString compoundString) { 84 addLog(TYPE_INPUT_CONSUMER, "", 0, compoundString, null); 85 } 86 87 /** 88 * Adds a log and track the associated event for error detection. 89 * 90 * @param gestureEvent GestureEvent representing the event being logged. 91 */ addLog( String event, @Nullable ActiveGestureErrorDetector.GestureEvent gestureEvent)92 public void addLog( 93 String event, @Nullable ActiveGestureErrorDetector.GestureEvent gestureEvent) { 94 addLog(TYPE_ONE_OFF, event, 0, CompoundString.NO_OP, gestureEvent); 95 } 96 addLog( String event, int extras, @Nullable ActiveGestureErrorDetector.GestureEvent gestureEvent)97 public void addLog( 98 String event, 99 int extras, 100 @Nullable ActiveGestureErrorDetector.GestureEvent gestureEvent) { 101 addLog(TYPE_INTEGER, event, extras, CompoundString.NO_OP, gestureEvent); 102 } 103 addLog( String event, boolean extras, @Nullable ActiveGestureErrorDetector.GestureEvent gestureEvent)104 public void addLog( 105 String event, 106 boolean extras, 107 @Nullable ActiveGestureErrorDetector.GestureEvent gestureEvent) { 108 addLog( 109 extras ? TYPE_BOOL_TRUE : TYPE_BOOL_FALSE, 110 event, 111 0, 112 CompoundString.NO_OP, 113 gestureEvent); 114 } 115 addLog( int type, String event, float extras, CompoundString compoundString, @Nullable ActiveGestureErrorDetector.GestureEvent gestureEvent)116 private void addLog( 117 int type, 118 String event, 119 float extras, 120 CompoundString compoundString, 121 @Nullable ActiveGestureErrorDetector.GestureEvent gestureEvent) { 122 EventLog lastEventLog = logs[(nextIndex + logs.length - 1) % logs.length]; 123 if (lastEventLog == null || mCurrentLogId != lastEventLog.logId) { 124 EventLog eventLog = new EventLog(mCurrentLogId); 125 EventEntry eventEntry = new EventEntry(); 126 127 eventEntry.update(type, event, extras, compoundString, gestureEvent); 128 eventLog.eventEntries.add(eventEntry); 129 logs[nextIndex] = eventLog; 130 nextIndex = (nextIndex + 1) % logs.length; 131 return; 132 } 133 134 // Update the last EventLog 135 List<EventEntry> lastEventEntries = lastEventLog.eventEntries; 136 EventEntry lastEntry = lastEventEntries.size() > 0 137 ? lastEventEntries.get(lastEventEntries.size() - 1) : null; 138 139 // Update the last EventEntry if it's a duplicate 140 if (isEntrySame(lastEntry, type, event, extras, compoundString, gestureEvent)) { 141 lastEntry.duplicateCount++; 142 return; 143 } 144 EventEntry eventEntry = new EventEntry(); 145 146 eventEntry.update(type, event, extras, compoundString, gestureEvent); 147 lastEventEntries.add(eventEntry); 148 } 149 dump(String prefix, PrintWriter writer)150 public void dump(String prefix, PrintWriter writer) { 151 writer.println(prefix + "ActiveGestureErrorDetector:"); 152 for (int i = 0; i < logs.length; i++) { 153 EventLog eventLog = logs[(nextIndex + i) % logs.length]; 154 if (eventLog == null) { 155 continue; 156 } 157 ActiveGestureErrorDetector.analyseAndDump(prefix + '\t', writer, eventLog); 158 } 159 160 writer.println(prefix + "ActiveGestureLog history:"); 161 SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSSZ ", Locale.US); 162 Date date = new Date(); 163 for (int i = 0; i < logs.length; i++) { 164 EventLog eventLog = logs[(nextIndex + i) % logs.length]; 165 if (eventLog == null) { 166 continue; 167 } 168 169 writer.println(prefix + "\tLogs for logId: " + eventLog.logId); 170 for (EventEntry eventEntry : eventLog.eventEntries) { 171 date.setTime(eventEntry.time); 172 173 StringBuilder msg = new StringBuilder(prefix + "\t\t").append(sdf.format(date)) 174 .append(eventEntry.event); 175 switch (eventEntry.type) { 176 case TYPE_BOOL_FALSE: 177 msg.append(": false"); 178 break; 179 case TYPE_BOOL_TRUE: 180 msg.append(": true"); 181 break; 182 case TYPE_FLOAT: 183 msg.append(": ").append(eventEntry.extras); 184 break; 185 case TYPE_INTEGER: 186 msg.append(": ").append((int) eventEntry.extras); 187 break; 188 case TYPE_INPUT_CONSUMER: 189 msg.append(eventEntry.mCompoundString); 190 break; 191 case TYPE_GESTURE_EVENT: 192 continue; 193 default: // fall out 194 } 195 if (eventEntry.duplicateCount > 0) { 196 msg.append(" & ").append(eventEntry.duplicateCount).append(" similar events"); 197 } 198 writer.println(msg); 199 } 200 } 201 } 202 203 /** 204 * Increments and returns the current log ID. This should be used every time a new log trace 205 * is started. 206 */ incrementLogId()207 public int incrementLogId() { 208 return mCurrentLogId++; 209 } 210 211 /** Returns the current log ID. This should be used when a log trace is being reused. */ getLogId()212 public int getLogId() { 213 return mCurrentLogId; 214 } 215 isEntrySame( EventEntry entry, int type, String event, float extras, CompoundString compoundString, ActiveGestureErrorDetector.GestureEvent gestureEvent)216 private boolean isEntrySame( 217 EventEntry entry, 218 int type, 219 String event, 220 float extras, 221 CompoundString compoundString, 222 ActiveGestureErrorDetector.GestureEvent gestureEvent) { 223 return entry != null 224 && entry.type == type 225 && entry.event.equals(event) 226 && Float.compare(entry.extras, extras) == 0 227 && entry.mCompoundString.equals(compoundString) 228 && entry.gestureEvent == gestureEvent; 229 } 230 231 /** A single event entry. */ 232 protected static class EventEntry { 233 234 private int type; 235 private String event; 236 private float extras; 237 @NonNull private CompoundString mCompoundString; 238 private ActiveGestureErrorDetector.GestureEvent gestureEvent; 239 private long time; 240 private int duplicateCount; 241 EventEntry()242 private EventEntry() {} 243 244 @Nullable getGestureEvent()245 protected ActiveGestureErrorDetector.GestureEvent getGestureEvent() { 246 return gestureEvent; 247 } 248 update( int type, String event, float extras, @NonNull CompoundString compoundString, ActiveGestureErrorDetector.GestureEvent gestureEvent)249 private void update( 250 int type, 251 String event, 252 float extras, 253 @NonNull CompoundString compoundString, 254 ActiveGestureErrorDetector.GestureEvent gestureEvent) { 255 this.type = type; 256 this.event = event; 257 this.extras = extras; 258 this.mCompoundString = compoundString; 259 this.gestureEvent = gestureEvent; 260 time = System.currentTimeMillis(); 261 duplicateCount = 0; 262 } 263 } 264 265 /** An entire log of entries associated with a single log ID */ 266 protected static class EventLog { 267 268 protected final List<EventEntry> eventEntries = new ArrayList<>(); 269 protected final int logId; 270 EventLog(int logId)271 private EventLog(int logId) { 272 this.logId = logId; 273 } 274 } 275 276 /** A buildable string stored as an array for memory efficiency. */ 277 public static class CompoundString { 278 279 public static final CompoundString NO_OP = new CompoundString(); 280 281 private final List<String> mSubstrings; 282 283 private final boolean mIsNoOp; 284 CompoundString()285 private CompoundString() { 286 this(null); 287 } 288 CompoundString(String substring)289 public CompoundString(String substring) { 290 mIsNoOp = substring == null; 291 if (mIsNoOp) { 292 mSubstrings = null; 293 return; 294 } 295 mSubstrings = new ArrayList<>(); 296 mSubstrings.add(substring); 297 } 298 append(CompoundString substring)299 public CompoundString append(CompoundString substring) { 300 if (mIsNoOp) { 301 return this; 302 } 303 mSubstrings.addAll(substring.mSubstrings); 304 305 return this; 306 } 307 append(String substring)308 public CompoundString append(String substring) { 309 if (mIsNoOp) { 310 return this; 311 } 312 mSubstrings.add(substring); 313 314 return this; 315 } 316 317 @Override toString()318 public String toString() { 319 if (mIsNoOp) { 320 return "ERROR: cannot use No-Op compound string"; 321 } 322 StringBuilder sb = new StringBuilder(); 323 for (String substring : mSubstrings) { 324 sb.append(substring); 325 } 326 327 return sb.toString(); 328 } 329 330 @Override hashCode()331 public int hashCode() { 332 return Objects.hash(mIsNoOp, mSubstrings); 333 } 334 335 @Override equals(Object obj)336 public boolean equals(Object obj) { 337 if (!(obj instanceof CompoundString)) { 338 return false; 339 } 340 CompoundString other = (CompoundString) obj; 341 return (mIsNoOp == other.mIsNoOp) && Objects.equals(mSubstrings, other.mSubstrings); 342 } 343 } 344 } 345