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