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