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