1 /* 2 * Copyright (C) 2022 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 android.util.ArraySet; 19 20 import androidx.annotation.NonNull; 21 22 import java.io.PrintWriter; 23 import java.util.Set; 24 25 /** 26 * Utility class for tracking gesture navigation events as they happen, then detecting and reporting 27 * known issues at log dump time. 28 */ 29 public class ActiveGestureErrorDetector { 30 31 /** 32 * Enums associated to gesture navigation events. 33 */ 34 public enum GestureEvent { 35 MOTION_DOWN, MOTION_UP, MOTION_MOVE, SET_END_TARGET, SET_END_TARGET_HOME, 36 SET_END_TARGET_NEW_TASK, SET_END_TARGET_ALL_APPS, ON_SETTLED_ON_END_TARGET, 37 ON_START_RECENTS_ANIMATION, ON_FINISH_RECENTS_ANIMATION, ON_CANCEL_RECENTS_ANIMATION, 38 START_RECENTS_ANIMATION, FINISH_RECENTS_ANIMATION, CANCEL_RECENTS_ANIMATION, 39 SET_ON_PAGE_TRANSITION_END_CALLBACK, CANCEL_CURRENT_ANIMATION, CLEANUP_SCREENSHOT, 40 SCROLLER_ANIMATION_ABORTED, TASK_APPEARED, EXPECTING_TASK_APPEARED, 41 FLAG_USING_OTHER_ACTIVITY_INPUT_CONSUMER, LAUNCHER_DESTROYED, RECENT_TASKS_MISSING, 42 INVALID_VELOCITY_ON_SWIPE_UP, RECENTS_ANIMATION_START_PENDING, 43 44 /** 45 * These GestureEvents are specifically associated to state flags that get set in 46 * {@link com.android.quickstep.MultiStateCallback}. If a state flag needs to be tracked 47 * for error detection, an enum should be added here and that state flag-enum pair should 48 * be added to the state flag's container class' {@code getTrackedEventForState} method. 49 */ 50 STATE_GESTURE_STARTED, STATE_GESTURE_COMPLETED, STATE_GESTURE_CANCELLED, 51 STATE_END_TARGET_ANIMATION_FINISHED, STATE_RECENTS_SCROLLING_FINISHED, 52 STATE_CAPTURE_SCREENSHOT, STATE_SCREENSHOT_CAPTURED, STATE_HANDLER_INVALIDATED, 53 STATE_RECENTS_ANIMATION_CANCELED, STATE_LAUNCHER_DRAWN(true, false); 54 55 public final boolean mLogEvent; 56 public final boolean mTrackEvent; 57 GestureEvent()58 GestureEvent() { 59 this(false, true); 60 } 61 GestureEvent(boolean logEvent, boolean trackEvent)62 GestureEvent(boolean logEvent, boolean trackEvent) { 63 mLogEvent = logEvent; 64 mTrackEvent = trackEvent; 65 } 66 } 67 ActiveGestureErrorDetector()68 private ActiveGestureErrorDetector() {} 69 70 private static final long ON_START_RECENT_ANIMATION_TIME_LIMIT = 500; 71 analyseAndDump( @onNull String prefix, @NonNull PrintWriter writer, @NonNull ActiveGestureLog.EventLog eventLog)72 protected static void analyseAndDump( 73 @NonNull String prefix, 74 @NonNull PrintWriter writer, 75 @NonNull ActiveGestureLog.EventLog eventLog) { 76 writer.println(prefix + "Error messages for gesture ID: " + eventLog.logId); 77 if (!eventLog.mIsFullyGesturalNavMode) { 78 writer.println(prefix 79 + "\tSkipping gesture error detection because gesture navigation not enabled"); 80 return; 81 } 82 83 boolean errorDetected = false; 84 // Use a Set since the order is inherently checked in the loop. 85 final Set<GestureEvent> encounteredEvents = new ArraySet<>(); 86 // Set flags and check order of operations. 87 long lastStartRecentAnimationEventEntryTime = 0; 88 for (ActiveGestureLog.EventEntry eventEntry : eventLog.eventEntries) { 89 GestureEvent gestureEvent = eventEntry.getGestureEvent(); 90 if (gestureEvent == null) { 91 continue; 92 } 93 encounteredEvents.add(gestureEvent); 94 95 switch (gestureEvent) { 96 case MOTION_UP: 97 errorDetected |= printErrorIfTrue( 98 !encounteredEvents.contains(GestureEvent.MOTION_DOWN), 99 prefix, 100 /* errorMessage= */ "Motion up detected before/without" 101 + " motion down.", 102 writer); 103 break; 104 case ON_SETTLED_ON_END_TARGET: 105 errorDetected |= printErrorIfTrue( 106 !encounteredEvents.contains(GestureEvent.SET_END_TARGET), 107 prefix, 108 /* errorMessage= */ "onSettledOnEndTarget called " 109 + "before/without setEndTarget.", 110 writer); 111 break; 112 case FINISH_RECENTS_ANIMATION: 113 errorDetected |= printErrorIfTrue( 114 !encounteredEvents.contains(GestureEvent.START_RECENTS_ANIMATION), 115 prefix, 116 /* errorMessage= */ "finishRecentsAnimation called " 117 + "before/without startRecentsAnimation.", 118 writer); 119 break; 120 case CANCEL_RECENTS_ANIMATION: 121 errorDetected |= printErrorIfTrue( 122 !encounteredEvents.contains(GestureEvent.START_RECENTS_ANIMATION), 123 prefix, 124 /* errorMessage= */ "cancelRecentsAnimation called " 125 + "before/without startRecentsAnimation.", 126 writer); 127 break; 128 case CLEANUP_SCREENSHOT: 129 errorDetected |= printErrorIfTrue( 130 !encounteredEvents.contains(GestureEvent.STATE_SCREENSHOT_CAPTURED), 131 prefix, 132 /* errorMessage= */ "recents activity screenshot was " 133 + "cleaned up before/without STATE_SCREENSHOT_CAPTURED " 134 + "being set.", 135 writer); 136 break; 137 case SCROLLER_ANIMATION_ABORTED: 138 errorDetected |= printErrorIfTrue( 139 encounteredEvents.contains(GestureEvent.SET_END_TARGET_HOME) 140 && !encounteredEvents.contains( 141 GestureEvent.ON_SETTLED_ON_END_TARGET), 142 prefix, 143 /* errorMessage= */ "recents view scroller animation " 144 + "aborted after setting end target HOME, but before" 145 + " settling on end target.", 146 writer); 147 break; 148 case TASK_APPEARED: 149 errorDetected |= printErrorIfTrue( 150 !encounteredEvents.contains(GestureEvent.EXPECTING_TASK_APPEARED), 151 prefix, 152 /* errorMessage= */ "onTasksAppeared was not expected to be called", 153 writer); 154 if (encounteredEvents.contains(GestureEvent.EXPECTING_TASK_APPEARED)) { 155 // Remove both events so that we can properly detect following errors. 156 encounteredEvents.remove(GestureEvent.EXPECTING_TASK_APPEARED); 157 encounteredEvents.remove(GestureEvent.TASK_APPEARED); 158 } 159 break; 160 case LAUNCHER_DESTROYED: 161 errorDetected |= printErrorIfTrue( 162 true, 163 prefix, 164 /* errorMessage= */ "Launcher destroyed mid-gesture", 165 writer); 166 break; 167 case STATE_GESTURE_COMPLETED: 168 errorDetected |= printErrorIfTrue( 169 !encounteredEvents.contains(GestureEvent.MOTION_UP), 170 prefix, 171 /* errorMessage= */ "STATE_GESTURE_COMPLETED set " 172 + "before/without motion up.", 173 writer); 174 errorDetected |= printErrorIfTrue( 175 !encounteredEvents.contains(GestureEvent.STATE_GESTURE_STARTED), 176 prefix, 177 /* errorMessage= */ "STATE_GESTURE_COMPLETED set " 178 + "before/without STATE_GESTURE_STARTED.", 179 writer); 180 break; 181 case STATE_GESTURE_CANCELLED: 182 errorDetected |= printErrorIfTrue( 183 !encounteredEvents.contains(GestureEvent.MOTION_UP), 184 prefix, 185 /* errorMessage= */ "STATE_GESTURE_CANCELLED set " 186 + "before/without motion up.", 187 writer); 188 errorDetected |= printErrorIfTrue( 189 !encounteredEvents.contains(GestureEvent.STATE_GESTURE_STARTED), 190 prefix, 191 /* errorMessage= */ "STATE_GESTURE_CANCELLED set " 192 + "before/without STATE_GESTURE_STARTED.", 193 writer); 194 break; 195 case STATE_SCREENSHOT_CAPTURED: 196 errorDetected |= printErrorIfTrue( 197 !encounteredEvents.contains(GestureEvent.STATE_CAPTURE_SCREENSHOT), 198 prefix, 199 /* errorMessage= */ "STATE_SCREENSHOT_CAPTURED set " 200 + "before/without STATE_CAPTURE_SCREENSHOT.", 201 writer); 202 break; 203 case STATE_RECENTS_SCROLLING_FINISHED: 204 errorDetected |= printErrorIfTrue( 205 !encounteredEvents.contains( 206 GestureEvent.SET_ON_PAGE_TRANSITION_END_CALLBACK), 207 prefix, 208 /* errorMessage= */ "STATE_RECENTS_SCROLLING_FINISHED " 209 + "set before/without calling " 210 + "setOnPageTransitionEndCallback.", 211 writer); 212 break; 213 case STATE_RECENTS_ANIMATION_CANCELED: 214 errorDetected |= printErrorIfTrue( 215 !encounteredEvents.contains( 216 GestureEvent.START_RECENTS_ANIMATION), 217 prefix, 218 /* errorMessage= */ "STATE_RECENTS_ANIMATION_CANCELED " 219 + "set before/without startRecentsAnimation.", 220 writer); 221 break; 222 case RECENT_TASKS_MISSING: 223 errorDetected |= printErrorIfTrue( 224 true, 225 prefix, 226 /* errorMessage= */ "SystemUiProxy.mRecentTasks missing," 227 + " couldn't start the recents activity", 228 writer); 229 break; 230 case ON_START_RECENTS_ANIMATION: 231 errorDetected |= printErrorIfTrue( 232 !encounteredEvents.contains(GestureEvent.START_RECENTS_ANIMATION), 233 prefix, 234 /* errorMessage= */ "ON_START_RECENTS_ANIMATION " 235 + "onAnimationStart callback ran before startRecentsAnimation", 236 writer); 237 errorDetected |= printErrorIfTrue( 238 eventEntry.getTime() - lastStartRecentAnimationEventEntryTime 239 > ON_START_RECENT_ANIMATION_TIME_LIMIT, 240 prefix, 241 /* errorMessage= */"ON_START_RECENTS_ANIMATION " 242 + "startRecentsAnimation was never called or onAnimationStart " 243 + "callback was called more than 500 ms after " 244 + "startRecentsAnimation.", 245 writer); 246 lastStartRecentAnimationEventEntryTime = 0; 247 break; 248 case ON_CANCEL_RECENTS_ANIMATION: 249 errorDetected |= printErrorIfTrue( 250 !encounteredEvents.contains(GestureEvent.ON_START_RECENTS_ANIMATION), 251 prefix, 252 /* errorMessage= */ "ON_CANCEL_RECENTS_ANIMATION " 253 + "onAnimationCanceled callback ran before onAnimationStart " 254 + "callback", 255 writer); 256 break; 257 case ON_FINISH_RECENTS_ANIMATION: 258 errorDetected |= printErrorIfTrue( 259 !encounteredEvents.contains(GestureEvent.ON_START_RECENTS_ANIMATION), 260 prefix, 261 /* errorMessage= */ "ON_FINISH_RECENTS_ANIMATION " 262 + "onAnimationFinished callback ran before onAnimationStart " 263 + "callback", 264 writer); 265 break; 266 case INVALID_VELOCITY_ON_SWIPE_UP: 267 errorDetected |= printErrorIfTrue( 268 true, 269 prefix, 270 /* errorMessage= */ "invalid velocity on swipe up gesture.", 271 writer); 272 break; 273 case START_RECENTS_ANIMATION: 274 lastStartRecentAnimationEventEntryTime = eventEntry.getTime(); 275 break; 276 case RECENTS_ANIMATION_START_PENDING: 277 errorDetected |= printErrorIfTrue( 278 true, 279 prefix, 280 /* errorMessage= */ (eventEntry.getDuplicateCount() + 1) 281 + " gesture(s) attempted while a requested recents" 282 + " animation is still pending.", 283 writer); 284 break; 285 case EXPECTING_TASK_APPEARED: 286 case MOTION_DOWN: 287 case SET_END_TARGET: 288 case SET_END_TARGET_HOME: 289 case SET_END_TARGET_ALL_APPS: 290 case SET_END_TARGET_NEW_TASK: 291 case SET_ON_PAGE_TRANSITION_END_CALLBACK: 292 case CANCEL_CURRENT_ANIMATION: 293 case FLAG_USING_OTHER_ACTIVITY_INPUT_CONSUMER: 294 case STATE_GESTURE_STARTED: 295 case STATE_END_TARGET_ANIMATION_FINISHED: 296 case STATE_CAPTURE_SCREENSHOT: 297 case STATE_HANDLER_INVALIDATED: 298 case STATE_LAUNCHER_DRAWN: 299 default: 300 // No-Op 301 } 302 } 303 304 // Check that all required events were found. 305 errorDetected |= printErrorIfTrue( 306 !encounteredEvents.contains(GestureEvent.MOTION_DOWN), 307 prefix, 308 /* errorMessage= */ "Motion down never detected.", 309 writer); 310 errorDetected |= printErrorIfTrue( 311 !encounteredEvents.contains(GestureEvent.MOTION_UP), 312 prefix, 313 /* errorMessage= */ "Motion up never detected.", 314 writer); 315 316 errorDetected |= printErrorIfTrue( 317 /* condition= */ encounteredEvents.contains(GestureEvent.SET_END_TARGET) 318 && !encounteredEvents.contains(GestureEvent.ON_SETTLED_ON_END_TARGET), 319 prefix, 320 /* errorMessage= */ "setEndTarget was called, but " 321 + "onSettledOnEndTarget wasn't.", 322 writer); 323 errorDetected |= printErrorIfTrue( 324 /* condition= */ encounteredEvents.contains(GestureEvent.SET_END_TARGET) 325 && !encounteredEvents.contains( 326 GestureEvent.STATE_END_TARGET_ANIMATION_FINISHED), 327 prefix, 328 /* errorMessage= */ "setEndTarget was called, but " 329 + "STATE_END_TARGET_ANIMATION_FINISHED was never set.", 330 writer); 331 errorDetected |= printErrorIfTrue( 332 /* condition= */ encounteredEvents.contains(GestureEvent.SET_END_TARGET) 333 && !encounteredEvents.contains( 334 GestureEvent.STATE_RECENTS_SCROLLING_FINISHED), 335 prefix, 336 /* errorMessage= */ "setEndTarget was called, but " 337 + "STATE_RECENTS_SCROLLING_FINISHED was never set.", 338 writer); 339 errorDetected |= printErrorIfTrue( 340 /* condition= */ encounteredEvents.contains( 341 GestureEvent.STATE_END_TARGET_ANIMATION_FINISHED) 342 && encounteredEvents.contains( 343 GestureEvent.STATE_RECENTS_SCROLLING_FINISHED) 344 && !encounteredEvents.contains(GestureEvent.ON_SETTLED_ON_END_TARGET), 345 prefix, 346 /* errorMessage= */ "STATE_END_TARGET_ANIMATION_FINISHED and " 347 + "STATE_RECENTS_SCROLLING_FINISHED were set, but onSettledOnEndTarget " 348 + "wasn't called.", 349 writer); 350 351 errorDetected |= printErrorIfTrue( 352 /* condition= */ encounteredEvents.contains( 353 GestureEvent.START_RECENTS_ANIMATION) 354 && !encounteredEvents.contains(GestureEvent.FINISH_RECENTS_ANIMATION) 355 && !encounteredEvents.contains(GestureEvent.CANCEL_RECENTS_ANIMATION), 356 prefix, 357 /* errorMessage= */ "startRecentsAnimation was called, but " 358 + "finishRecentsAnimation and cancelRecentsAnimation weren't.", 359 writer); 360 361 errorDetected |= printErrorIfTrue( 362 /* condition= */ encounteredEvents.contains(GestureEvent.STATE_GESTURE_STARTED) 363 && !encounteredEvents.contains(GestureEvent.STATE_GESTURE_COMPLETED) 364 && !encounteredEvents.contains(GestureEvent.STATE_GESTURE_CANCELLED), 365 prefix, 366 /* errorMessage= */ "STATE_GESTURE_STARTED was set, but " 367 + "STATE_GESTURE_COMPLETED and STATE_GESTURE_CANCELLED weren't.", 368 writer); 369 370 errorDetected |= printErrorIfTrue( 371 /* condition= */ encounteredEvents.contains( 372 GestureEvent.STATE_CAPTURE_SCREENSHOT) 373 && !encounteredEvents.contains(GestureEvent.STATE_SCREENSHOT_CAPTURED), 374 prefix, 375 /* errorMessage= */ "STATE_CAPTURE_SCREENSHOT was set, but " 376 + "STATE_SCREENSHOT_CAPTURED wasn't.", 377 writer); 378 379 errorDetected |= printErrorIfTrue( 380 /* condition= */ encounteredEvents.contains( 381 GestureEvent.SET_ON_PAGE_TRANSITION_END_CALLBACK) 382 && !encounteredEvents.contains( 383 GestureEvent.STATE_RECENTS_SCROLLING_FINISHED), 384 prefix, 385 /* errorMessage= */ "setOnPageTransitionEndCallback called, but " 386 + "STATE_RECENTS_SCROLLING_FINISHED wasn't set.", 387 writer); 388 389 errorDetected |= printErrorIfTrue( 390 /* condition= */ encounteredEvents.contains( 391 GestureEvent.FLAG_USING_OTHER_ACTIVITY_INPUT_CONSUMER) 392 && !encounteredEvents.contains(GestureEvent.CANCEL_CURRENT_ANIMATION) 393 && !encounteredEvents.contains(GestureEvent.STATE_HANDLER_INVALIDATED), 394 prefix, 395 /* errorMessage= */ "AbsSwipeUpHandler.cancelCurrentAnimation " 396 + "wasn't called and STATE_HANDLER_INVALIDATED wasn't set.", 397 writer); 398 399 errorDetected |= printErrorIfTrue( 400 /* condition= */ encounteredEvents.contains( 401 GestureEvent.STATE_RECENTS_ANIMATION_CANCELED) 402 && !encounteredEvents.contains(GestureEvent.CLEANUP_SCREENSHOT), 403 prefix, 404 /* errorMessage= */ "STATE_RECENTS_ANIMATION_CANCELED was set but " 405 + "the task screenshot wasn't cleaned up.", 406 writer); 407 408 errorDetected |= printErrorIfTrue( 409 /* condition= */ encounteredEvents.contains( 410 GestureEvent.EXPECTING_TASK_APPEARED) 411 && !encounteredEvents.contains(GestureEvent.TASK_APPEARED), 412 prefix, 413 /* errorMessage= */ "onTaskAppeared was expected to be called but wasn't.", 414 writer); 415 416 errorDetected |= printErrorIfTrue( 417 /* condition= */ encounteredEvents.contains(GestureEvent.START_RECENTS_ANIMATION) 418 && !encounteredEvents.contains(GestureEvent.ON_START_RECENTS_ANIMATION), 419 prefix, 420 /* errorMessage= */ 421 "startRecentAnimation was called but onAnimationStart callback was not", 422 writer); 423 errorDetected |= printErrorIfTrue( 424 /* condition= */ 425 encounteredEvents.contains(GestureEvent.FINISH_RECENTS_ANIMATION) 426 && !encounteredEvents.contains(GestureEvent.ON_FINISH_RECENTS_ANIMATION), 427 prefix, 428 /* errorMessage= */ 429 "finishController was called but onAnimationFinished callback was not", 430 writer); 431 errorDetected |= printErrorIfTrue( 432 /* condition= */ 433 encounteredEvents.contains(GestureEvent.CANCEL_RECENTS_ANIMATION) 434 && !encounteredEvents.contains(GestureEvent.ON_CANCEL_RECENTS_ANIMATION), 435 prefix, 436 /* errorMessage= */ 437 "onRecentsAnimationCanceled was called but onAnimationCanceled was not", 438 writer); 439 440 if (!errorDetected) { 441 writer.println(prefix + "\tNo errors detected."); 442 } 443 } 444 printErrorIfTrue( boolean condition, String prefix, String errorMessage, PrintWriter writer)445 private static boolean printErrorIfTrue( 446 boolean condition, String prefix, String errorMessage, PrintWriter writer) { 447 if (!condition) { 448 return false; 449 } 450 writer.println(prefix + "\t- " + errorMessage); 451 452 return true; 453 } 454 } 455