diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java index eff6fed030..b7a50fc35e 100644 --- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java +++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java @@ -334,6 +334,12 @@ public abstract class AbsSwipeUpHandler, return ActiveGestureErrorDetector.GestureEvent.STATE_GESTURE_COMPLETED; } else if (stateFlag == STATE_GESTURE_CANCELLED) { return ActiveGestureErrorDetector.GestureEvent.STATE_GESTURE_CANCELLED; + } else if (stateFlag == STATE_SCREENSHOT_CAPTURED) { + return ActiveGestureErrorDetector.GestureEvent.STATE_SCREENSHOT_CAPTURED; + } else if (stateFlag == STATE_CAPTURE_SCREENSHOT) { + return ActiveGestureErrorDetector.GestureEvent.STATE_CAPTURE_SCREENSHOT; + } else if (stateFlag == STATE_HANDLER_INVALIDATED) { + return ActiveGestureErrorDetector.GestureEvent.STATE_HANDLER_INVALIDATED; } return null; } @@ -1222,6 +1228,8 @@ public abstract class AbsSwipeUpHandler, // Let RecentsView handle the scrolling to the task, which we launch in startNewTask() // or resumeLastTask(). if (mRecentsView != null) { + ActiveGestureLog.INSTANCE.trackEvent(ActiveGestureErrorDetector.GestureEvent + .SET_ON_PAGE_TRANSITION_END_CALLBACK); mRecentsView.setOnPageTransitionEndCallback( () -> mGestureState.setState(STATE_RECENTS_SCROLLING_FINISHED)); } else { @@ -1699,6 +1707,9 @@ public abstract class AbsSwipeUpHandler, * handler (in case of quick switch). */ private void cancelCurrentAnimation() { + ActiveGestureLog.INSTANCE.addLog( + "AbsSwipeUpHandler.cancelCurrentAnimation", + ActiveGestureErrorDetector.GestureEvent.CANCEL_CURRENT_ANIMATION); mCanceled = true; mCurrentShift.cancelAnimation(); diff --git a/quickstep/src/com/android/quickstep/GestureState.java b/quickstep/src/com/android/quickstep/GestureState.java index bc2f55137b..38bf1fd6d8 100644 --- a/quickstep/src/com/android/quickstep/GestureState.java +++ b/quickstep/src/com/android/quickstep/GestureState.java @@ -189,6 +189,8 @@ public class GestureState implements RecentsAnimationCallbacks.RecentsAnimationL return ActiveGestureErrorDetector.GestureEvent.STATE_END_TARGET_ANIMATION_FINISHED; } else if (stateFlag == STATE_RECENTS_SCROLLING_FINISHED) { return ActiveGestureErrorDetector.GestureEvent.STATE_RECENTS_SCROLLING_FINISHED; + } else if (stateFlag == STATE_RECENTS_ANIMATION_CANCELED) { + return ActiveGestureErrorDetector.GestureEvent.STATE_RECENTS_ANIMATION_CANCELED; } return null; } diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationController.java b/quickstep/src/com/android/quickstep/RecentsAnimationController.java index fefef2f45e..542c0d4b21 100644 --- a/quickstep/src/com/android/quickstep/RecentsAnimationController.java +++ b/quickstep/src/com/android/quickstep/RecentsAnimationController.java @@ -32,6 +32,8 @@ import androidx.annotation.UiThread; import com.android.launcher3.util.Preconditions; import com.android.launcher3.util.RunnableList; +import com.android.quickstep.util.ActiveGestureErrorDetector; +import com.android.quickstep.util.ActiveGestureLog; import com.android.systemui.shared.recents.model.ThumbnailData; import com.android.systemui.shared.system.InteractionJankMonitorWrapper; import com.android.systemui.shared.system.RecentsAnimationControllerCompat; @@ -172,7 +174,12 @@ public class RecentsAnimationController { */ @UiThread public void cleanupScreenshot() { - UI_HELPER_EXECUTOR.execute(() -> mController.cleanupScreenshot()); + UI_HELPER_EXECUTOR.execute(() -> { + ActiveGestureLog.INSTANCE.addLog( + "cleanupScreenshot", + ActiveGestureErrorDetector.GestureEvent.CLEANUP_SCREENSHOT); + mController.cleanupScreenshot(); + }); } /** diff --git a/quickstep/src/com/android/quickstep/TaskAnimationManager.java b/quickstep/src/com/android/quickstep/TaskAnimationManager.java index 03147619e5..5fb806dacf 100644 --- a/quickstep/src/com/android/quickstep/TaskAnimationManager.java +++ b/quickstep/src/com/android/quickstep/TaskAnimationManager.java @@ -36,6 +36,8 @@ import androidx.annotation.UiThread; import com.android.launcher3.Utilities; import com.android.launcher3.config.FeatureFlags; import com.android.quickstep.TopTaskTracker.CachedTaskInfo; +import com.android.quickstep.util.ActiveGestureErrorDetector; +import com.android.quickstep.util.ActiveGestureLog; import com.android.quickstep.views.RecentsView; import com.android.systemui.shared.recents.model.ThumbnailData; import com.android.systemui.shared.system.ActivityManagerWrapper; @@ -136,6 +138,8 @@ public class TaskAnimationManager implements RecentsAnimationCallbacks.RecentsAn // handling this call entirely return; } + ActiveGestureLog.INSTANCE.addLog("TaskAnimationManager.startRecentsAnimation", + ActiveGestureErrorDetector.GestureEvent.START_RECENTS_ANIMATION); mController = controller; mTargets = targets; mLastAppearedTaskTarget = mTargets.findTask(mLastGestureState.getRunningTaskId()); diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java index 82be3ec811..ba4f549bbb 100644 --- a/quickstep/src/com/android/quickstep/TouchInteractionService.java +++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java @@ -721,8 +721,10 @@ public class TouchInteractionService extends Service gestureState.updateRunningTask(taskInfo); } // Log initial state for the gesture. - ActiveGestureLog.INSTANCE.addLog( - "Current SystemUi state flags= " + mDeviceState.getSystemUiStateString()); + ActiveGestureLog.INSTANCE.addLog(new CompoundString("Current running task package name=") + .append(taskInfo == null ? "no running task" : taskInfo.getPackageName())); + ActiveGestureLog.INSTANCE.addLog(new CompoundString("Current SystemUi state flags=") + .append(mDeviceState.getSystemUiStateString())); return gestureState; } @@ -1024,12 +1026,27 @@ public class TouchInteractionService extends Service .append("activity == null, trying to use default input consumer")); } - if (activity.getRootView().hasWindowFocus() - || previousGestureState.isRunningAnimationToLauncher() - || (ASSISTANT_GIVES_LAUNCHER_FOCUS.get() - && forceOverviewInputConsumer) - || (ENABLE_QUICKSTEP_LIVE_TILE.get() - && gestureState.getActivityInterface().isInLiveTileMode())) { + boolean hasWindowFocus = activity.getRootView().hasWindowFocus(); + boolean isPreviousGestureAnimatingToLauncher = + previousGestureState.isRunningAnimationToLauncher(); + boolean forcingOverviewInputConsumer = + ASSISTANT_GIVES_LAUNCHER_FOCUS.get() && forceOverviewInputConsumer; + boolean isInLiveTileMode = ENABLE_QUICKSTEP_LIVE_TILE.get() + && gestureState.getActivityInterface().isInLiveTileMode(); + reasonString.append(SUBSTRING_PREFIX) + .append(hasWindowFocus + ? "activity has window focus" + : (isPreviousGestureAnimatingToLauncher + ? "previous gesture is still animating to launcher" + : (forcingOverviewInputConsumer + ? "assistant gives launcher focus and forcing focus" + : (isInLiveTileMode + ? "device is in live mode" + : "all overview focus conditions failed")))); + if (hasWindowFocus + || isPreviousGestureAnimatingToLauncher + || forcingOverviewInputConsumer + || isInLiveTileMode) { reasonString.append(SUBSTRING_PREFIX) .append("overview should have focus, using OverviewInputConsumer"); return new OverviewInputConsumer(gestureState, activity, mInputMonitorCompat, diff --git a/quickstep/src/com/android/quickstep/inputconsumers/ProgressDelegateInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/ProgressDelegateInputConsumer.java index 246239449f..b9b5e7c2b9 100644 --- a/quickstep/src/com/android/quickstep/inputconsumers/ProgressDelegateInputConsumer.java +++ b/quickstep/src/com/android/quickstep/inputconsumers/ProgressDelegateInputConsumer.java @@ -28,6 +28,8 @@ import android.content.Intent; import android.graphics.Point; import android.view.MotionEvent; +import androidx.annotation.Nullable; + import com.android.launcher3.anim.AnimatorListeners; import com.android.launcher3.testing.TestLogging; import com.android.launcher3.testing.shared.TestProtocol; @@ -41,6 +43,7 @@ import com.android.quickstep.RecentsAnimationCallbacks; import com.android.quickstep.RecentsAnimationController; import com.android.quickstep.RecentsAnimationTargets; import com.android.quickstep.TaskAnimationManager; +import com.android.quickstep.util.ActiveGestureErrorDetector; import com.android.systemui.shared.recents.model.ThumbnailData; import com.android.systemui.shared.system.InputMonitorCompat; @@ -99,7 +102,8 @@ public class ProgressDelegateInputConsumer implements InputConsumer, mDisplaySize = DisplayController.INSTANCE.get(context).getInfo().currentSize; // Init states - mStateCallback = new MultiStateCallback(STATE_NAMES); + mStateCallback = new MultiStateCallback( + STATE_NAMES, ProgressDelegateInputConsumer::getTrackedEventForState); mStateCallback.runOnceAtState(STATE_TARGET_RECEIVED | STATE_HANDLER_INVALIDATED, this::endRemoteAnimation); mStateCallback.runOnceAtState(STATE_TARGET_RECEIVED | STATE_FLING_FINISHED, @@ -109,6 +113,14 @@ public class ProgressDelegateInputConsumer implements InputConsumer, mSwipeDetector.setDetectableScrollConditions(DIRECTION_POSITIVE, false); } + @Nullable + private static ActiveGestureErrorDetector.GestureEvent getTrackedEventForState(int stateFlag) { + if (stateFlag == STATE_HANDLER_INVALIDATED) { + return ActiveGestureErrorDetector.GestureEvent.STATE_HANDLER_INVALIDATED; + } + return null; + } + @Override public int getType() { return TYPE_PROGRESS_DELEGATE; diff --git a/quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java b/quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java index 78075def8f..54f632afdb 100644 --- a/quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java +++ b/quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java @@ -29,11 +29,24 @@ import java.util.Set; */ public class ActiveGestureErrorDetector { + /** + * Enums associated to gesture navigation events. + */ public enum GestureEvent { MOTION_DOWN, MOTION_UP, SET_END_TARGET, ON_SETTLED_ON_END_TARGET, START_RECENTS_ANIMATION, - FINISH_RECENTS_ANIMATION, CANCEL_RECENTS_ANIMATION, STATE_GESTURE_STARTED, - STATE_GESTURE_COMPLETED, STATE_GESTURE_CANCELLED, STATE_END_TARGET_ANIMATION_FINISHED, - STATE_RECENTS_SCROLLING_FINISHED + FINISH_RECENTS_ANIMATION, CANCEL_RECENTS_ANIMATION, SET_ON_PAGE_TRANSITION_END_CALLBACK, + CANCEL_CURRENT_ANIMATION, CLEANUP_SCREENSHOT, + + /** + * These GestureEvents are specifically associated to state flags that get set in + * {@link com.android.quickstep.MultiStateCallback}. If a state flag needs to be tracked + * for error detection, an enum should be added here and that state flag-enum pair should + * be added to the state flag's container class' {@code getTrackedEventForState} method. + */ + STATE_GESTURE_STARTED, STATE_GESTURE_COMPLETED, STATE_GESTURE_CANCELLED, + STATE_END_TARGET_ANIMATION_FINISHED, STATE_RECENTS_SCROLLING_FINISHED, + STATE_CAPTURE_SCREENSHOT, STATE_SCREENSHOT_CAPTURED, STATE_HANDLER_INVALIDATED, + STATE_RECENTS_ANIMATION_CANCELED } private ActiveGestureErrorDetector() {} @@ -90,6 +103,14 @@ public class ActiveGestureErrorDetector { + "before/without startRecentsAnimation.", writer); break; + case CLEANUP_SCREENSHOT: + errorDetected |= printErrorIfTrue( + !encounteredEvents.contains(GestureEvent.STATE_SCREENSHOT_CAPTURED), + /* errorMessage= */ prefix + "\t\trecents activity screenshot was " + + "cleaned up before/without STATE_SCREENSHOT_CAPTURED " + + "being set.", + writer); + break; case STATE_GESTURE_COMPLETED: errorDetected |= printErrorIfTrue( !encounteredEvents.contains(GestureEvent.MOTION_UP), @@ -114,12 +135,39 @@ public class ActiveGestureErrorDetector { + "before/without STATE_GESTURE_STARTED.", writer); break; + case STATE_SCREENSHOT_CAPTURED: + errorDetected |= printErrorIfTrue( + !encounteredEvents.contains(GestureEvent.STATE_CAPTURE_SCREENSHOT), + /* errorMessage= */ prefix + "\t\tSTATE_SCREENSHOT_CAPTURED set " + + "before/without STATE_CAPTURE_SCREENSHOT.", + writer); + break; + case STATE_RECENTS_SCROLLING_FINISHED: + errorDetected |= printErrorIfTrue( + !encounteredEvents.contains( + GestureEvent.SET_ON_PAGE_TRANSITION_END_CALLBACK), + /* errorMessage= */ prefix + "\t\tSTATE_RECENTS_SCROLLING_FINISHED " + + "set before/without calling " + + "setOnPageTransitionEndCallback.", + writer); + break; + case STATE_RECENTS_ANIMATION_CANCELED: + errorDetected |= printErrorIfTrue( + !encounteredEvents.contains( + GestureEvent.START_RECENTS_ANIMATION), + /* errorMessage= */ prefix + "\t\tSTATE_RECENTS_ANIMATION_CANCELED " + + "set before/without startRecentsAnimation.", + writer); + break; case MOTION_DOWN: case SET_END_TARGET: case START_RECENTS_ANIMATION: + case SET_ON_PAGE_TRANSITION_END_CALLBACK: + case CANCEL_CURRENT_ANIMATION: case STATE_GESTURE_STARTED: case STATE_END_TARGET_ANIMATION_FINISHED: - case STATE_RECENTS_SCROLLING_FINISHED: + case STATE_CAPTURE_SCREENSHOT: + case STATE_HANDLER_INVALIDATED: default: // No-Op } @@ -183,6 +231,39 @@ public class ActiveGestureErrorDetector { + "STATE_GESTURE_COMPLETED and STATE_GESTURE_CANCELLED weren't.", writer); + errorDetected |= printErrorIfTrue( + /* condition= */ encounteredEvents.contains( + GestureEvent.STATE_CAPTURE_SCREENSHOT) + && !encounteredEvents.contains(GestureEvent.STATE_SCREENSHOT_CAPTURED), + /* errorMessage= */ prefix + "\t\tSTATE_CAPTURE_SCREENSHOT was set, but " + + "STATE_SCREENSHOT_CAPTURED wasn't.", + writer); + + errorDetected |= printErrorIfTrue( + /* condition= */ encounteredEvents.contains( + GestureEvent.SET_ON_PAGE_TRANSITION_END_CALLBACK) + && !encounteredEvents.contains( + GestureEvent.STATE_RECENTS_SCROLLING_FINISHED), + /* errorMessage= */ prefix + "\t\tsetOnPageTransitionEndCallback called, but " + + "STATE_RECENTS_SCROLLING_FINISHED wasn't set.", + writer); + + errorDetected |= printErrorIfTrue( + /* condition= */ !encounteredEvents.contains( + GestureEvent.CANCEL_CURRENT_ANIMATION) + && !encounteredEvents.contains(GestureEvent.STATE_HANDLER_INVALIDATED), + /* errorMessage= */ prefix + "\t\tAbsSwipeUpHandler.cancelCurrentAnimation " + + "wasn't called and STATE_HANDLER_INVALIDATED wasn't set.", + writer); + + errorDetected |= printErrorIfTrue( + /* condition= */ encounteredEvents.contains( + GestureEvent.STATE_RECENTS_ANIMATION_CANCELED) + && !encounteredEvents.contains(GestureEvent.CLEANUP_SCREENSHOT), + /* errorMessage= */ prefix + "\t\tSTATE_RECENTS_ANIMATION_CANCELED was set but " + + "the task screenshot wasn't cleaned up.", + writer); + if (!errorDetected) { writer.println(prefix + "\t\tNo errors detected."); } diff --git a/quickstep/src/com/android/quickstep/util/ActiveGestureLog.java b/quickstep/src/com/android/quickstep/util/ActiveGestureLog.java index 9f08010b2b..40eb31b727 100644 --- a/quickstep/src/com/android/quickstep/util/ActiveGestureLog.java +++ b/quickstep/src/com/android/quickstep/util/ActiveGestureLog.java @@ -138,14 +138,10 @@ public class ActiveGestureLog { List lastEventEntries = lastEventLog.eventEntries; EventEntry lastEntry = lastEventEntries.size() > 0 ? lastEventEntries.get(lastEventEntries.size() - 1) : null; - EventEntry secondLastEntry = lastEventEntries.size() > 1 - ? lastEventEntries.get(lastEventEntries.size() - 2) : null; // Update the last EventEntry if it's a duplicate - if (isEntrySame(lastEntry, type, event, compoundString, gestureEvent) - && isEntrySame(secondLastEntry, type, event, compoundString, gestureEvent)) { - lastEntry.update(type, event, extras, compoundString, gestureEvent); - secondLastEntry.duplicateCount++; + if (isEntrySame(lastEntry, type, event, extras, compoundString, gestureEvent)) { + lastEntry.duplicateCount++; return; } EventEntry eventEntry = new EventEntry(); @@ -223,11 +219,13 @@ public class ActiveGestureLog { EventEntry entry, int type, String event, + float extras, CompoundString compoundString, ActiveGestureErrorDetector.GestureEvent gestureEvent) { return entry != null && entry.type == type && entry.event.equals(event) + && Float.compare(entry.extras, extras) == 0 && entry.mCompoundString.equals(compoundString) && entry.gestureEvent == gestureEvent; } @@ -342,7 +340,7 @@ public class ActiveGestureLog { return false; } CompoundString other = (CompoundString) obj; - return mIsNoOp && other.mIsNoOp && Objects.equals(mSubstrings, other.mSubstrings); + return (mIsNoOp == other.mIsNoOp) && Objects.equals(mSubstrings, other.mSubstrings); } } }