• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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