• 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, 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