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 START_RECENTS_ANIMATION, FINISH_RECENTS_ANIMATION, CANCEL_RECENTS_ANIMATION, 38 SET_ON_PAGE_TRANSITION_END_CALLBACK, CANCEL_CURRENT_ANIMATION, CLEANUP_SCREENSHOT, 39 SCROLLER_ANIMATION_ABORTED, TASK_APPEARED, EXPECTING_TASK_APPEARED, 40 FLAG_USING_OTHER_ACTIVITY_INPUT_CONSUMER, LAUNCHER_DESTROYED, 41 42 /** 43 * These GestureEvents are specifically associated to state flags that get set in 44 * {@link com.android.quickstep.MultiStateCallback}. If a state flag needs to be tracked 45 * for error detection, an enum should be added here and that state flag-enum pair should 46 * be added to the state flag's container class' {@code getTrackedEventForState} method. 47 */ 48 STATE_GESTURE_STARTED, STATE_GESTURE_COMPLETED, STATE_GESTURE_CANCELLED, 49 STATE_END_TARGET_ANIMATION_FINISHED, STATE_RECENTS_SCROLLING_FINISHED, 50 STATE_CAPTURE_SCREENSHOT, STATE_SCREENSHOT_CAPTURED, STATE_HANDLER_INVALIDATED, 51 STATE_RECENTS_ANIMATION_CANCELED, STATE_LAUNCHER_DRAWN(true, false); 52 53 public final boolean mLogEvent; 54 public final boolean mTrackEvent; 55 GestureEvent()56 GestureEvent() { 57 this(false, true); 58 } 59 GestureEvent(boolean logEvent, boolean trackEvent)60 GestureEvent(boolean logEvent, boolean trackEvent) { 61 mLogEvent = logEvent; 62 mTrackEvent = trackEvent; 63 } 64 } 65 ActiveGestureErrorDetector()66 private ActiveGestureErrorDetector() {} 67 analyseAndDump( @onNull String prefix, @NonNull PrintWriter writer, @NonNull ActiveGestureLog.EventLog eventLog)68 protected static void analyseAndDump( 69 @NonNull String prefix, 70 @NonNull PrintWriter writer, 71 @NonNull ActiveGestureLog.EventLog eventLog) { 72 writer.println(prefix + "Error messages for gesture ID: " + eventLog.logId); 73 74 boolean errorDetected = false; 75 // Use a Set since the order is inherently checked in the loop. 76 final Set<GestureEvent> encounteredEvents = new ArraySet<>(); 77 // Set flags and check order of operations. 78 for (ActiveGestureLog.EventEntry eventEntry : eventLog.eventEntries) { 79 GestureEvent gestureEvent = eventEntry.getGestureEvent(); 80 if (gestureEvent == null) { 81 continue; 82 } 83 encounteredEvents.add(gestureEvent); 84 85 switch (gestureEvent) { 86 case MOTION_UP: 87 errorDetected |= printErrorIfTrue( 88 !encounteredEvents.contains(GestureEvent.MOTION_DOWN), 89 prefix, 90 /* errorMessage= */ "Motion up detected before/without" 91 + " motion down.", 92 writer); 93 break; 94 case ON_SETTLED_ON_END_TARGET: 95 errorDetected |= printErrorIfTrue( 96 !encounteredEvents.contains(GestureEvent.SET_END_TARGET), 97 prefix, 98 /* errorMessage= */ "onSettledOnEndTarget called " 99 + "before/without setEndTarget.", 100 writer); 101 break; 102 case FINISH_RECENTS_ANIMATION: 103 errorDetected |= printErrorIfTrue( 104 !encounteredEvents.contains(GestureEvent.START_RECENTS_ANIMATION), 105 prefix, 106 /* errorMessage= */ "finishRecentsAnimation called " 107 + "before/without startRecentsAnimation.", 108 writer); 109 break; 110 case CANCEL_RECENTS_ANIMATION: 111 errorDetected |= printErrorIfTrue( 112 !encounteredEvents.contains(GestureEvent.START_RECENTS_ANIMATION), 113 prefix, 114 /* errorMessage= */ "cancelRecentsAnimation called " 115 + "before/without startRecentsAnimation.", 116 writer); 117 break; 118 case CLEANUP_SCREENSHOT: 119 errorDetected |= printErrorIfTrue( 120 !encounteredEvents.contains(GestureEvent.STATE_SCREENSHOT_CAPTURED), 121 prefix, 122 /* errorMessage= */ "recents activity screenshot was " 123 + "cleaned up before/without STATE_SCREENSHOT_CAPTURED " 124 + "being set.", 125 writer); 126 break; 127 case SCROLLER_ANIMATION_ABORTED: 128 errorDetected |= printErrorIfTrue( 129 encounteredEvents.contains(GestureEvent.SET_END_TARGET_HOME) 130 && !encounteredEvents.contains( 131 GestureEvent.ON_SETTLED_ON_END_TARGET), 132 prefix, 133 /* errorMessage= */ "recents view scroller animation " 134 + "aborted after setting end target HOME, but before" 135 + " settling on end target.", 136 writer); 137 break; 138 case TASK_APPEARED: 139 errorDetected |= printErrorIfTrue( 140 !encounteredEvents.contains(GestureEvent.SET_END_TARGET_NEW_TASK), 141 prefix, 142 /* errorMessage= */ "onTasksAppeared called " 143 + "before/without setting end target to new task", 144 writer); 145 errorDetected |= printErrorIfTrue( 146 !encounteredEvents.contains(GestureEvent.EXPECTING_TASK_APPEARED), 147 prefix, 148 /* errorMessage= */ "onTasksAppeared was not expected to be called", 149 writer); 150 break; 151 case EXPECTING_TASK_APPEARED: 152 errorDetected |= printErrorIfTrue( 153 !encounteredEvents.contains(GestureEvent.SET_END_TARGET_NEW_TASK), 154 prefix, 155 /* errorMessage= */ "expecting onTasksAppeared to be called " 156 + "before/without setting end target to new task", 157 writer); 158 break; 159 case LAUNCHER_DESTROYED: 160 errorDetected |= printErrorIfTrue( 161 true, 162 prefix, 163 /* errorMessage= */ "Launcher destroyed mid-gesture", 164 writer); 165 break; 166 case STATE_GESTURE_COMPLETED: 167 errorDetected |= printErrorIfTrue( 168 !encounteredEvents.contains(GestureEvent.MOTION_UP), 169 prefix, 170 /* errorMessage= */ "STATE_GESTURE_COMPLETED set " 171 + "before/without motion up.", 172 writer); 173 errorDetected |= printErrorIfTrue( 174 !encounteredEvents.contains(GestureEvent.STATE_GESTURE_STARTED), 175 prefix, 176 /* errorMessage= */ "STATE_GESTURE_COMPLETED set " 177 + "before/without STATE_GESTURE_STARTED.", 178 writer); 179 break; 180 case STATE_GESTURE_CANCELLED: 181 errorDetected |= printErrorIfTrue( 182 !encounteredEvents.contains(GestureEvent.MOTION_UP), 183 prefix, 184 /* errorMessage= */ "STATE_GESTURE_CANCELLED set " 185 + "before/without motion up.", 186 writer); 187 errorDetected |= printErrorIfTrue( 188 !encounteredEvents.contains(GestureEvent.STATE_GESTURE_STARTED), 189 prefix, 190 /* errorMessage= */ "STATE_GESTURE_CANCELLED set " 191 + "before/without STATE_GESTURE_STARTED.", 192 writer); 193 break; 194 case STATE_SCREENSHOT_CAPTURED: 195 errorDetected |= printErrorIfTrue( 196 !encounteredEvents.contains(GestureEvent.STATE_CAPTURE_SCREENSHOT), 197 prefix, 198 /* errorMessage= */ "STATE_SCREENSHOT_CAPTURED set " 199 + "before/without STATE_CAPTURE_SCREENSHOT.", 200 writer); 201 break; 202 case STATE_RECENTS_SCROLLING_FINISHED: 203 errorDetected |= printErrorIfTrue( 204 !encounteredEvents.contains( 205 GestureEvent.SET_ON_PAGE_TRANSITION_END_CALLBACK), 206 prefix, 207 /* errorMessage= */ "STATE_RECENTS_SCROLLING_FINISHED " 208 + "set before/without calling " 209 + "setOnPageTransitionEndCallback.", 210 writer); 211 break; 212 case STATE_RECENTS_ANIMATION_CANCELED: 213 errorDetected |= printErrorIfTrue( 214 !encounteredEvents.contains( 215 GestureEvent.START_RECENTS_ANIMATION), 216 prefix, 217 /* errorMessage= */ "STATE_RECENTS_ANIMATION_CANCELED " 218 + "set before/without startRecentsAnimation.", 219 writer); 220 break; 221 case MOTION_DOWN: 222 case SET_END_TARGET: 223 case SET_END_TARGET_HOME: 224 case SET_END_TARGET_ALL_APPS: 225 case SET_END_TARGET_NEW_TASK: 226 case START_RECENTS_ANIMATION: 227 case SET_ON_PAGE_TRANSITION_END_CALLBACK: 228 case CANCEL_CURRENT_ANIMATION: 229 case FLAG_USING_OTHER_ACTIVITY_INPUT_CONSUMER: 230 case STATE_GESTURE_STARTED: 231 case STATE_END_TARGET_ANIMATION_FINISHED: 232 case STATE_CAPTURE_SCREENSHOT: 233 case STATE_HANDLER_INVALIDATED: 234 case STATE_LAUNCHER_DRAWN: 235 default: 236 // No-Op 237 } 238 } 239 240 // Check that all required events were found. 241 errorDetected |= printErrorIfTrue( 242 !encounteredEvents.contains(GestureEvent.MOTION_DOWN), 243 prefix, 244 /* errorMessage= */ "Motion down never detected.", 245 writer); 246 errorDetected |= printErrorIfTrue( 247 !encounteredEvents.contains(GestureEvent.MOTION_UP), 248 prefix, 249 /* errorMessage= */ "Motion up never detected.", 250 writer); 251 252 errorDetected |= printErrorIfTrue( 253 /* condition= */ encounteredEvents.contains(GestureEvent.SET_END_TARGET) 254 && !encounteredEvents.contains(GestureEvent.ON_SETTLED_ON_END_TARGET), 255 prefix, 256 /* errorMessage= */ "setEndTarget was called, but " 257 + "onSettledOnEndTarget wasn't.", 258 writer); 259 errorDetected |= printErrorIfTrue( 260 /* condition= */ encounteredEvents.contains(GestureEvent.SET_END_TARGET) 261 && !encounteredEvents.contains( 262 GestureEvent.STATE_END_TARGET_ANIMATION_FINISHED), 263 prefix, 264 /* errorMessage= */ "setEndTarget was called, but " 265 + "STATE_END_TARGET_ANIMATION_FINISHED was never set.", 266 writer); 267 errorDetected |= printErrorIfTrue( 268 /* condition= */ encounteredEvents.contains(GestureEvent.SET_END_TARGET) 269 && !encounteredEvents.contains( 270 GestureEvent.STATE_RECENTS_SCROLLING_FINISHED), 271 prefix, 272 /* errorMessage= */ "setEndTarget was called, but " 273 + "STATE_RECENTS_SCROLLING_FINISHED was never set.", 274 writer); 275 errorDetected |= printErrorIfTrue( 276 /* condition= */ encounteredEvents.contains( 277 GestureEvent.STATE_END_TARGET_ANIMATION_FINISHED) 278 && encounteredEvents.contains( 279 GestureEvent.STATE_RECENTS_SCROLLING_FINISHED) 280 && !encounteredEvents.contains(GestureEvent.ON_SETTLED_ON_END_TARGET), 281 prefix, 282 /* errorMessage= */ "STATE_END_TARGET_ANIMATION_FINISHED and " 283 + "STATE_RECENTS_SCROLLING_FINISHED were set, but onSettledOnEndTarget " 284 + "wasn't called.", 285 writer); 286 287 errorDetected |= printErrorIfTrue( 288 /* condition= */ encounteredEvents.contains( 289 GestureEvent.START_RECENTS_ANIMATION) 290 && !encounteredEvents.contains(GestureEvent.FINISH_RECENTS_ANIMATION) 291 && !encounteredEvents.contains(GestureEvent.CANCEL_RECENTS_ANIMATION), 292 prefix, 293 /* errorMessage= */ "startRecentsAnimation was called, but " 294 + "finishRecentsAnimation and cancelRecentsAnimation weren't.", 295 writer); 296 297 errorDetected |= printErrorIfTrue( 298 /* condition= */ encounteredEvents.contains(GestureEvent.STATE_GESTURE_STARTED) 299 && !encounteredEvents.contains(GestureEvent.STATE_GESTURE_COMPLETED) 300 && !encounteredEvents.contains(GestureEvent.STATE_GESTURE_CANCELLED), 301 prefix, 302 /* errorMessage= */ "STATE_GESTURE_STARTED was set, but " 303 + "STATE_GESTURE_COMPLETED and STATE_GESTURE_CANCELLED weren't.", 304 writer); 305 306 errorDetected |= printErrorIfTrue( 307 /* condition= */ encounteredEvents.contains( 308 GestureEvent.STATE_CAPTURE_SCREENSHOT) 309 && !encounteredEvents.contains(GestureEvent.STATE_SCREENSHOT_CAPTURED), 310 prefix, 311 /* errorMessage= */ "STATE_CAPTURE_SCREENSHOT was set, but " 312 + "STATE_SCREENSHOT_CAPTURED wasn't.", 313 writer); 314 315 errorDetected |= printErrorIfTrue( 316 /* condition= */ encounteredEvents.contains( 317 GestureEvent.SET_ON_PAGE_TRANSITION_END_CALLBACK) 318 && !encounteredEvents.contains( 319 GestureEvent.STATE_RECENTS_SCROLLING_FINISHED), 320 prefix, 321 /* errorMessage= */ "setOnPageTransitionEndCallback called, but " 322 + "STATE_RECENTS_SCROLLING_FINISHED wasn't set.", 323 writer); 324 325 errorDetected |= printErrorIfTrue( 326 /* condition= */ encounteredEvents.contains( 327 GestureEvent.FLAG_USING_OTHER_ACTIVITY_INPUT_CONSUMER) 328 && !encounteredEvents.contains(GestureEvent.CANCEL_CURRENT_ANIMATION) 329 && !encounteredEvents.contains(GestureEvent.STATE_HANDLER_INVALIDATED), 330 prefix, 331 /* errorMessage= */ "AbsSwipeUpHandler.cancelCurrentAnimation " 332 + "wasn't called and STATE_HANDLER_INVALIDATED wasn't set.", 333 writer); 334 335 errorDetected |= printErrorIfTrue( 336 /* condition= */ encounteredEvents.contains( 337 GestureEvent.STATE_RECENTS_ANIMATION_CANCELED) 338 && !encounteredEvents.contains(GestureEvent.CLEANUP_SCREENSHOT), 339 prefix, 340 /* errorMessage= */ "STATE_RECENTS_ANIMATION_CANCELED was set but " 341 + "the task screenshot wasn't cleaned up.", 342 writer); 343 344 errorDetected |= printErrorIfTrue( 345 /* condition= */ encounteredEvents.contains( 346 GestureEvent.EXPECTING_TASK_APPEARED) 347 && !encounteredEvents.contains(GestureEvent.TASK_APPEARED), 348 prefix, 349 /* errorMessage= */ "onTaskAppeared was expected to be called but wasn't.", 350 writer); 351 352 if (!errorDetected) { 353 writer.println(prefix + "\tNo errors detected."); 354 } 355 } 356 printErrorIfTrue( boolean condition, String prefix, String errorMessage, PrintWriter writer)357 private static boolean printErrorIfTrue( 358 boolean condition, String prefix, String errorMessage, PrintWriter writer) { 359 if (!condition) { 360 return false; 361 } 362 writer.println(prefix + "\t- " + errorMessage); 363 364 return true; 365 } 366 } 367