Merge "Add gesture nav error state tracking and logging" into tm-qpr-dev
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 4ac816f..159c52e 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -46,6 +46,10 @@
 import static com.android.quickstep.GestureState.STATE_RECENTS_ANIMATION_CANCELED;
 import static com.android.quickstep.GestureState.STATE_RECENTS_SCROLLING_FINISHED;
 import static com.android.quickstep.MultiStateCallback.DEBUG_STATES;
+import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.CANCEL_RECENTS_ANIMATION;
+import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.FINISH_RECENTS_ANIMATION;
+import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.ON_SETTLED_ON_END_TARGET;
+import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.START_RECENTS_ANIMATION;
 import static com.android.quickstep.util.VibratorWrapper.OVERVIEW_HAPTIC;
 import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHOLD;
 import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
@@ -100,6 +104,7 @@
 import com.android.quickstep.BaseActivityInterface.AnimationFactory;
 import com.android.quickstep.GestureState.GestureEndTarget;
 import com.android.quickstep.RemoteTargetGluer.RemoteTargetHandle;
+import com.android.quickstep.util.ActiveGestureErrorDetector;
 import com.android.quickstep.util.ActiveGestureLog;
 import com.android.quickstep.util.ActivityInitListener;
 import com.android.quickstep.util.AnimatorControllerWithResistance;
@@ -320,8 +325,21 @@
         initStateCallbacks();
     }
 
+    @Nullable
+    private static ActiveGestureErrorDetector.GestureEvent getTrackedEventForState(int stateFlag) {
+        if (stateFlag == STATE_GESTURE_STARTED) {
+            return ActiveGestureErrorDetector.GestureEvent.STATE_GESTURE_STARTED;
+        } else if (stateFlag == STATE_GESTURE_COMPLETED) {
+            return ActiveGestureErrorDetector.GestureEvent.STATE_GESTURE_COMPLETED;
+        } else if (stateFlag == STATE_GESTURE_CANCELLED) {
+            return ActiveGestureErrorDetector.GestureEvent.STATE_GESTURE_CANCELLED;
+        }
+        return null;
+    }
+
     private void initStateCallbacks() {
-        mStateCallback = new MultiStateCallback(STATE_NAMES.toArray(new String[0]));
+        mStateCallback = new MultiStateCallback(
+                STATE_NAMES.toArray(new String[0]), AbsSwipeUpHandler::getTrackedEventForState);
 
         mStateCallback.runOnceAtState(STATE_LAUNCHER_PRESENT | STATE_GESTURE_STARTED,
                 this::onLauncherPresentAndGestureStarted);
@@ -800,7 +818,10 @@
     public void onRecentsAnimationStart(RecentsAnimationController controller,
             RecentsAnimationTargets targets) {
         super.onRecentsAnimationStart(controller, targets);
-        ActiveGestureLog.INSTANCE.addLog("startRecentsAnimationCallback", targets.apps.length);
+        ActiveGestureLog.INSTANCE.addLog(
+                /* event= */ "startRecentsAnimationCallback",
+                /* extras= */ targets.apps.length,
+                /* gestureEvent= */ START_RECENTS_ANIMATION);
         mRemoteTargetHandles = mTargetGluer.assignTargetsForSplitScreen(mContext, targets);
         mRecentsAnimationController = controller;
         mRecentsAnimationTargets = targets;
@@ -843,7 +864,9 @@
 
     @Override
     public void onRecentsAnimationCanceled(HashMap<Integer, ThumbnailData> thumbnailDatas) {
-        ActiveGestureLog.INSTANCE.addLog("cancelRecentsAnimation");
+        ActiveGestureLog.INSTANCE.addLog(
+                /* event= */ "cancelRecentsAnimation",
+                /* gestureEvent= */ CANCEL_RECENTS_ANIMATION);
         mActivityInitListener.unregister();
         // Cache the recents animation controller so we can defer its cleanup to after having
         // properly cleaned up the screenshot without accidentally using it.
@@ -1010,7 +1033,9 @@
                 }
                 break;
         }
-        ActiveGestureLog.INSTANCE.addLog("onSettledOnEndTarget " + endTarget);
+        ActiveGestureLog.INSTANCE.addLog(
+                /* event= */ "onSettledOnEndTarget " + endTarget,
+                /* gestureEvent= */ ON_SETTLED_ON_END_TARGET);
     }
 
     /** @return Whether this was the task we were waiting to appear, and thus handled it. */
@@ -1587,7 +1612,10 @@
     private void resumeLastTask() {
         if (mRecentsAnimationController != null) {
             mRecentsAnimationController.finish(false /* toRecents */, null);
-            ActiveGestureLog.INSTANCE.addLog("finishRecentsAnimation", false);
+            ActiveGestureLog.INSTANCE.addLog(
+                    /* event= */ "finishRecentsAnimation",
+                    /* extras= */ false,
+                    /* gestureEvent= */ FINISH_RECENTS_ANIMATION);
         }
         doLogGesture(LAST_TASK, null);
         reset();
@@ -1791,7 +1819,10 @@
             mRecentsAnimationController.finish(true /* toRecents */,
                     () -> mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED));
         }
-        ActiveGestureLog.INSTANCE.addLog("finishRecentsAnimation", true);
+        ActiveGestureLog.INSTANCE.addLog(
+                /* event= */ "finishRecentsAnimation",
+                /* extras= */ true,
+                /* gestureEvent= */ FINISH_RECENTS_ANIMATION);
     }
 
     private void finishCurrentTransitionToHome() {
@@ -1803,7 +1834,10 @@
             finishRecentsControllerToHome(
                     () -> mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED));
         }
-        ActiveGestureLog.INSTANCE.addLog("finishRecentsAnimation", true);
+        ActiveGestureLog.INSTANCE.addLog(
+                /* event= */ "finishRecentsAnimation",
+                /* extras= */ true,
+                /* gestureEvent= */ FINISH_RECENTS_ANIMATION);
         doLogGesture(HOME, mRecentsView == null ? null : mRecentsView.getCurrentPageTaskView());
     }
 
@@ -1991,7 +2025,10 @@
                 mRecentsAnimationController.finish(false /* toRecents */,
                         null /* onFinishComplete */);
                 mActivityInterface.onLaunchTaskSuccess();
-                ActiveGestureLog.INSTANCE.addLog("finishRecentsAnimation", false);
+                ActiveGestureLog.INSTANCE.addLog(
+                        /* event= */ "finishRecentsAnimation",
+                        /* extras= */ false,
+                        /* gestureEvent= */ FINISH_RECENTS_ANIMATION);
             }
         }
     }
diff --git a/quickstep/src/com/android/quickstep/GestureState.java b/quickstep/src/com/android/quickstep/GestureState.java
index acdbbbd..bc2f551 100644
--- a/quickstep/src/com/android/quickstep/GestureState.java
+++ b/quickstep/src/com/android/quickstep/GestureState.java
@@ -19,6 +19,7 @@
 import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_HOME;
 import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_OVERVIEW;
 import static com.android.quickstep.MultiStateCallback.DEBUG_STATES;
+import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.SET_END_TARGET;
 
 import android.annotation.Nullable;
 import android.annotation.TargetApi;
@@ -30,6 +31,7 @@
 import com.android.launcher3.tracing.GestureStateProto;
 import com.android.launcher3.tracing.SwipeHandlerProto;
 import com.android.quickstep.TopTaskTracker.CachedTaskInfo;
+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.RemoteAnimationTargetCompat;
@@ -153,7 +155,8 @@
         mHomeIntent = componentObserver.getHomeIntent();
         mOverviewIntent = componentObserver.getOverviewIntent();
         mActivityInterface = componentObserver.getActivityInterface();
-        mStateCallback = new MultiStateCallback(STATE_NAMES.toArray(new String[0]));
+        mStateCallback = new MultiStateCallback(
+                STATE_NAMES.toArray(new String[0]), GestureState::getTrackedEventForState);
         mGestureId = gestureId;
     }
 
@@ -175,10 +178,21 @@
         mHomeIntent = new Intent();
         mOverviewIntent = new Intent();
         mActivityInterface = null;
-        mStateCallback = new MultiStateCallback(STATE_NAMES.toArray(new String[0]));
+        mStateCallback = new MultiStateCallback(
+                STATE_NAMES.toArray(new String[0]), GestureState::getTrackedEventForState);
         mGestureId = -1;
     }
 
+    @Nullable
+    private static ActiveGestureErrorDetector.GestureEvent getTrackedEventForState(int stateFlag) {
+        if (stateFlag == STATE_END_TARGET_ANIMATION_FINISHED) {
+            return ActiveGestureErrorDetector.GestureEvent.STATE_END_TARGET_ANIMATION_FINISHED;
+        } else if (stateFlag == STATE_RECENTS_SCROLLING_FINISHED) {
+            return ActiveGestureErrorDetector.GestureEvent.STATE_RECENTS_SCROLLING_FINISHED;
+        }
+        return null;
+    }
+
     /**
      * @return whether the gesture state has the provided {@param stateMask} flags set.
      */
@@ -311,7 +325,9 @@
     public void setEndTarget(GestureEndTarget target, boolean isAtomic) {
         mEndTarget = target;
         mStateCallback.setState(STATE_END_TARGET_SET);
-        ActiveGestureLog.INSTANCE.addLog("setEndTarget " + mEndTarget);
+        ActiveGestureLog.INSTANCE.addLog(
+                /* event= */ "setEndTarget " + mEndTarget,
+                /* gestureEvent= */ SET_END_TARGET);
         if (isAtomic) {
             mStateCallback.setState(STATE_END_TARGET_ANIMATION_FINISHED);
         }
diff --git a/quickstep/src/com/android/quickstep/MultiStateCallback.java b/quickstep/src/com/android/quickstep/MultiStateCallback.java
index b3875ae..56e7fb5 100644
--- a/quickstep/src/com/android/quickstep/MultiStateCallback.java
+++ b/quickstep/src/com/android/quickstep/MultiStateCallback.java
@@ -22,7 +22,12 @@
 import android.util.Log;
 import android.util.SparseArray;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
 import com.android.launcher3.config.FeatureFlags;
+import com.android.quickstep.util.ActiveGestureErrorDetector;
+import com.android.quickstep.util.ActiveGestureLog;
 
 import java.util.ArrayList;
 import java.util.LinkedList;
@@ -41,17 +46,29 @@
     private final SparseArray<ArrayList<Consumer<Boolean>>> mStateChangeListeners =
             new SparseArray<>();
 
+    @NonNull private final TrackedEventsMapper mTrackedEventsMapper;
+
     private final String[] mStateNames;
 
     private int mState = 0;
 
     public MultiStateCallback(String[] stateNames) {
+        this(stateNames, stateFlag -> null);
+    }
+
+    public MultiStateCallback(
+            String[] stateNames,
+            @NonNull TrackedEventsMapper trackedEventsMapper) {
         mStateNames = DEBUG_STATES ? stateNames : null;
+        mTrackedEventsMapper = trackedEventsMapper;
     }
 
     /**
      * Adds the provided state flags to the global state on the UI thread and executes any callbacks
      * as a result.
+     *
+     * Also tracks the provided gesture events for error detection. Each provided event must be
+     * associated with one provided state flag.
      */
     public void setStateOnUiThread(int stateFlag) {
         if (Looper.myLooper() == Looper.getMainLooper()) {
@@ -69,7 +86,9 @@
             Log.d(TAG, "[" + System.identityHashCode(this) + "] Adding "
                     + convertToFlagNames(stateFlag) + " to " + convertToFlagNames(mState));
         }
-
+        if (FeatureFlags.ENABLE_GESTURE_ERROR_DETECTION.get()) {
+            trackGestureEvents(stateFlag);
+        }
         final int oldState = mState;
         mState = mState | stateFlag;
 
@@ -87,6 +106,20 @@
         notifyStateChangeListeners(oldState);
     }
 
+    private void trackGestureEvents(int stateFlags) {
+        for (int index = 0; (stateFlags >> index) != 0; index++) {
+            if ((stateFlags & (1 << index)) == 0) {
+                continue;
+            }
+            ActiveGestureErrorDetector.GestureEvent gestureEvent =
+                    mTrackedEventsMapper.getTrackedEventForState(1 << index);
+            if (gestureEvent == null) {
+                continue;
+            }
+            ActiveGestureLog.INSTANCE.trackEvent(gestureEvent);
+        }
+    }
+
     /**
      * Adds the provided state flags to the global state and executes any change handlers
      * as a result.
@@ -174,4 +207,7 @@
         return joiner.toString();
     }
 
+    public interface TrackedEventsMapper {
+        @Nullable ActiveGestureErrorDetector.GestureEvent getTrackedEventForState(int stateflag);
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java b/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
index c602324..887fd54 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
@@ -16,6 +16,7 @@
 package com.android.quickstep;
 
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.CANCEL_RECENTS_ANIMATION;
 
 import android.graphics.Rect;
 import android.util.ArraySet;
@@ -27,6 +28,7 @@
 
 import com.android.launcher3.Utilities;
 import com.android.launcher3.util.Preconditions;
+import com.android.quickstep.util.ActiveGestureLog;
 import com.android.systemui.shared.recents.model.ThumbnailData;
 import com.android.systemui.shared.system.RecentsAnimationControllerCompat;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
@@ -122,6 +124,9 @@
     @Override
     public final void onAnimationCanceled(HashMap<Integer, ThumbnailData> thumbnailDatas) {
         Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(), () -> {
+            ActiveGestureLog.INSTANCE.addLog(
+                    /* event= */ "onRecentsAnimationCancelled",
+                    /* gestureEvent= */ CANCEL_RECENTS_ANIMATION);
             for (RecentsAnimationListener listener : getListeners()) {
                 listener.onRecentsAnimationCanceled(thumbnailDatas);
             }
@@ -132,6 +137,7 @@
     @Override
     public void onTasksAppeared(RemoteAnimationTargetCompat[] apps) {
         Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(), () -> {
+            ActiveGestureLog.INSTANCE.addLog("onTasksAppeared");
             for (RecentsAnimationListener listener : getListeners()) {
                 listener.onTasksAppeared(apps);
             }
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
index 48f0557..b6cfbb0 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
@@ -580,12 +580,15 @@
                 && ((mSystemUiStateFlags & SYSUI_STATE_IME_SHOWING) != 0);
     }
 
+    public String getSystemUiStateString() {
+        return  QuickStepContract.getSystemUiStateString(mSystemUiStateFlags);
+    }
+
     public void dump(PrintWriter pw) {
         pw.println("DeviceState:");
         pw.println("  canStartSystemGesture=" + canStartSystemGesture());
         pw.println("  systemUiFlags=" + mSystemUiStateFlags);
-        pw.println("  systemUiFlagsDesc="
-                + QuickStepContract.getSystemUiStateString(mSystemUiStateFlags));
+        pw.println("  systemUiFlagsDesc=" + getSystemUiStateString());
         pw.println("  assistantAvailable=" + mAssistantAvailable);
         pw.println("  assistantDisabled="
                 + QuickStepContract.isAssistantGestureDisabled(mSystemUiStateFlags));
diff --git a/quickstep/src/com/android/quickstep/TaskUtils.java b/quickstep/src/com/android/quickstep/TaskUtils.java
index c9db153..d722778 100644
--- a/quickstep/src/com/android/quickstep/TaskUtils.java
+++ b/quickstep/src/com/android/quickstep/TaskUtils.java
@@ -18,6 +18,8 @@
 
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
@@ -25,6 +27,8 @@
 import android.os.UserHandle;
 import android.util.Log;
 
+import androidx.annotation.Nullable;
+
 import com.android.launcher3.pm.UserCache;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.PackageManagerHelper;
@@ -47,16 +51,32 @@
      * TODO: remove this once we switch to getting the icon and label from IconCache.
      */
     public static CharSequence getTitle(Context context, Task task) {
-        UserHandle user = UserHandle.of(task.key.userId);
+        return getTitle(context, task.key.userId, task.getTopComponent().getPackageName());
+    }
+
+    public static CharSequence getTitle(
+            @NonNull Context context,
+            @UserIdInt @Nullable Integer userId,
+            @Nullable String packageName) {
+        if (userId == null || packageName == null) {
+            if (userId == null) {
+                Log.e(TAG, "Failed to get title; missing userId");
+            }
+            if (packageName == null) {
+                Log.e(TAG, "Failed to get title; missing packageName");
+            }
+            return "";
+        }
+        UserHandle user = UserHandle.of(userId);
         ApplicationInfo applicationInfo = new PackageManagerHelper(context)
-                .getApplicationInfo(task.getTopComponent().getPackageName(), user, 0);
+                .getApplicationInfo(packageName, user, 0);
         if (applicationInfo == null) {
-            Log.e(TAG, "Failed to get title for task " + task);
+            Log.e(TAG, "Failed to get title for userId=" + userId + ", packageName=" + packageName);
             return "";
         }
         PackageManager packageManager = context.getPackageManager();
         return packageManager.getUserBadgedLabel(
-            applicationInfo.loadLabel(packageManager), user);
+                applicationInfo.loadLabel(packageManager), user);
     }
 
     public static ComponentKey getLaunchComponentKeyForTask(Task.TaskKey taskKey) {
diff --git a/quickstep/src/com/android/quickstep/TopTaskTracker.java b/quickstep/src/com/android/quickstep/TopTaskTracker.java
index cfcba4c..d4bf5c7 100644
--- a/quickstep/src/com/android/quickstep/TopTaskTracker.java
+++ b/quickstep/src/com/android/quickstep/TopTaskTracker.java
@@ -24,6 +24,7 @@
 
 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT;
 
+import android.annotation.UserIdInt;
 import android.app.ActivityManager.RunningTaskInfo;
 import android.content.Context;
 
@@ -282,5 +283,16 @@
             }
             return result;
         }
+
+        @UserIdInt
+        @Nullable
+        public Integer getUserId() {
+            return mTopTask == null ? null : mTopTask.userId;
+        }
+
+        @Nullable
+        public String getPackageName() {
+            return mTopTask == null ? null : mTopTask.baseActivity.getPackageName();
+        }
     }
 }
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index 7c22726..82be3ec 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -23,6 +23,8 @@
 import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.quickstep.GestureState.DEFAULT_STATE;
+import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.MOTION_DOWN;
+import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.MOTION_UP;
 import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
 import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_RECENT_TASKS;
 import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_BACK_ANIMATION;
@@ -650,12 +652,17 @@
             switch (event.getActionMasked()) {
                 case ACTION_DOWN:
                 case ACTION_UP:
-                    ActiveGestureLog.INSTANCE.addLog("onMotionEvent("
-                            + (int) event.getRawX() + ", " + (int) event.getRawY() + ")",
-                            event.getActionMasked());
+                    ActiveGestureLog.INSTANCE.addLog(
+                            /* event= */ "onMotionEvent(" + (int) event.getRawX() + ", "
+                                    + (int) event.getRawY() + "): "
+                                    + MotionEvent.actionToString(event.getActionMasked()),
+                            /* gestureEvent= */ event.getActionMasked() == ACTION_DOWN
+                                    ? MOTION_DOWN
+                                    : MOTION_UP);
                     break;
                 default:
-                    ActiveGestureLog.INSTANCE.addLog("onMotionEvent", event.getActionMasked());
+                    ActiveGestureLog.INSTANCE.addLog("onMotionEvent: "
+                            + MotionEvent.actionToString(event.getActionMasked()));
                     break;
             }
         }
@@ -702,15 +709,20 @@
     public GestureState createGestureState(GestureState previousGestureState) {
         GestureState gestureState = new GestureState(mOverviewComponentObserver,
                 ActiveGestureLog.INSTANCE.incrementLogId());
+        TopTaskTracker.CachedTaskInfo taskInfo;
         if (mTaskAnimationManager.isRecentsAnimationRunning()) {
-            gestureState.updateRunningTask(previousGestureState.getRunningTask());
+            taskInfo = previousGestureState.getRunningTask();
+            gestureState.updateRunningTask(taskInfo);
             gestureState.updateLastStartedTaskId(previousGestureState.getLastStartedTaskId());
             gestureState.updatePreviouslyAppearedTaskIds(
                     previousGestureState.getPreviouslyAppearedTaskIds());
         } else {
-            gestureState.updateRunningTask(
-                    TopTaskTracker.INSTANCE.get(this).getCachedTopTask(false));
+            taskInfo = TopTaskTracker.INSTANCE.get(this).getCachedTopTask(false);
+            gestureState.updateRunningTask(taskInfo);
         }
+        // Log initial state for the gesture.
+        ActiveGestureLog.INSTANCE.addLog(
+                "Current SystemUi state flags= " + mDeviceState.getSystemUiStateString());
         return gestureState;
     }
 
@@ -1190,6 +1202,7 @@
     private void printAvailableCommands(PrintWriter pw) {
         pw.println("Available commands:");
         pw.println("  clear-touch-log: Clears the touch interaction log");
+        pw.println("  print-gesture-log: only prints the ActiveGestureLog dump");
     }
 
     private void onCommand(PrintWriter pw, LinkedList<String> args) {
@@ -1197,6 +1210,8 @@
             case "clear-touch-log":
                 ActiveGestureLog.INSTANCE.clear();
                 break;
+            case "print-gesture-log":
+                ActiveGestureLog.INSTANCE.dump("", pw);
         }
     }
 
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
index 92d3d23..7ccd8af 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
@@ -30,6 +30,7 @@
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.launcher3.util.TraceHelper.FLAG_CHECK_FOR_RACE_CONDITIONS;
 import static com.android.launcher3.util.VelocityUtils.PX_PER_MS;
+import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.START_RECENTS_ANIMATION;
 import static com.android.quickstep.util.ActiveGestureLog.INTENT_EXTRA_LOG_TRACE_ID;
 
 import android.annotation.TargetApi;
@@ -359,7 +360,6 @@
     }
 
     private void notifyGestureStarted(boolean isLikelyToStartNewTask) {
-        ActiveGestureLog.INSTANCE.addLog("startQuickstep");
         if (mInteractionHandler == null) {
             return;
         }
@@ -373,7 +373,9 @@
     }
 
     private void startTouchTrackingForWindowAnimation(long touchTimeMs) {
-        ActiveGestureLog.INSTANCE.addLog("startRecentsAnimation");
+        ActiveGestureLog.INSTANCE.addLog(
+                /* event= */ "startRecentsAnimation",
+                /* gestureEvent= */ START_RECENTS_ANIMATION);
 
         mInteractionHandler = mHandlerFactory.newHandler(mGestureState, touchTimeMs);
         mInteractionHandler.setGestureEndCallback(this::onInteractionGestureFinished);
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
index 7899c55..6f35928 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
@@ -35,7 +35,6 @@
 import com.android.quickstep.GestureState;
 import com.android.quickstep.InputConsumer;
 import com.android.quickstep.TaskUtils;
-import com.android.quickstep.util.ActiveGestureLog;
 import com.android.systemui.shared.system.InputMonitorCompat;
 
 /**
@@ -91,7 +90,6 @@
             if (!mStartingInActivityBounds) {
                 mActivityInterface.closeOverlay();
                 TaskUtils.closeSystemWindowsAsync(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
-                ActiveGestureLog.INSTANCE.addLog("startQuickstep");
             }
             if (mInputMonitor != null) {
                 TestLogging.recordEvent(TestProtocol.SEQUENCE_PILFER, "pilferPointers");
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java
index bde4240..b70fe8e 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java
@@ -32,7 +32,6 @@
 import com.android.quickstep.GestureState;
 import com.android.quickstep.InputConsumer;
 import com.android.quickstep.RecentsAnimationDeviceState;
-import com.android.quickstep.util.ActiveGestureLog;
 import com.android.quickstep.util.TriggerSwipeUpTouchTracker;
 import com.android.systemui.shared.system.InputMonitorCompat;
 
@@ -79,7 +78,6 @@
     @Override
     public void onSwipeUp(boolean wasFling, PointF finalVelocity) {
         startHomeIntentSafely(mContext, mGestureState.getHomeIntent(), null);
-        ActiveGestureLog.INSTANCE.addLog("startQuickstep");
         BaseActivity activity = BaseDraggingActivity.fromContext(mContext);
         int state = (mGestureState != null && mGestureState.getEndTarget() != null)
                 ? mGestureState.getEndTarget().containerType
diff --git a/quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java b/quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java
new file mode 100644
index 0000000..78075de
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep.util;
+
+import android.util.ArraySet;
+
+import androidx.annotation.NonNull;
+
+import java.io.PrintWriter;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Utility class for tracking gesture navigation events as they happen, then detecting and reporting
+ * known issues at log dump time.
+ */
+public class ActiveGestureErrorDetector {
+
+    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
+    }
+
+    private ActiveGestureErrorDetector() {}
+
+    protected static void analyseAndDump(
+            @NonNull String prefix,
+            @NonNull PrintWriter writer,
+            List<ActiveGestureLog.EventLog> eventLogs) {
+        writer.println(prefix + "ActiveGestureErrorDetector:");
+        for (int i = 0; i < eventLogs.size(); i++) {
+            ActiveGestureLog.EventLog eventLog = eventLogs.get(i);
+            if (eventLog == null) {
+                continue;
+            }
+            int gestureId = eventLog.logId;
+            writer.println(prefix + "\tError messages for gesture ID: " + gestureId);
+
+            boolean errorDetected = false;
+            // Use a Set since the order is inherently checked in the loop.
+            final Set<GestureEvent> encounteredEvents = new ArraySet<>();
+            // Set flags and check order of operations.
+            for (ActiveGestureLog.EventEntry eventEntry : eventLog.eventEntries) {
+                GestureEvent gestureEvent = eventEntry.getGestureEvent();
+                if (gestureEvent == null) {
+                    continue;
+                }
+                encounteredEvents.add(gestureEvent);
+                switch (gestureEvent) {
+                    case MOTION_UP:
+                        errorDetected |= printErrorIfTrue(
+                                !encounteredEvents.contains(GestureEvent.MOTION_DOWN),
+                                /* errorMessage= */ prefix + "\t\tMotion up detected before/without"
+                                        + " motion down.",
+                                writer);
+                        break;
+                    case ON_SETTLED_ON_END_TARGET:
+                        errorDetected |= printErrorIfTrue(
+                                !encounteredEvents.contains(GestureEvent.SET_END_TARGET),
+                                /* errorMessage= */ prefix + "\t\tonSettledOnEndTarget called "
+                                        + "before/without setEndTarget.",
+                                writer);
+                        break;
+                    case FINISH_RECENTS_ANIMATION:
+                        errorDetected |= printErrorIfTrue(
+                                !encounteredEvents.contains(GestureEvent.START_RECENTS_ANIMATION),
+                                /* errorMessage= */ prefix + "\t\tfinishRecentsAnimation called "
+                                        + "before/without startRecentsAnimation.",
+                                writer);
+                        break;
+                    case CANCEL_RECENTS_ANIMATION:
+                        errorDetected |= printErrorIfTrue(
+                                !encounteredEvents.contains(GestureEvent.START_RECENTS_ANIMATION),
+                                /* errorMessage= */ prefix + "\t\tcancelRecentsAnimation called "
+                                        + "before/without startRecentsAnimation.",
+                                writer);
+                        break;
+                    case STATE_GESTURE_COMPLETED:
+                        errorDetected |= printErrorIfTrue(
+                                !encounteredEvents.contains(GestureEvent.MOTION_UP),
+                                /* errorMessage= */ prefix + "\t\tSTATE_GESTURE_COMPLETED set "
+                                        + "before/without motion up.",
+                                writer);
+                        errorDetected |= printErrorIfTrue(
+                                !encounteredEvents.contains(GestureEvent.STATE_GESTURE_STARTED),
+                                /* errorMessage= */ prefix + "\t\tSTATE_GESTURE_COMPLETED set "
+                                        + "before/without STATE_GESTURE_STARTED.",
+                                writer);
+                        break;
+                    case STATE_GESTURE_CANCELLED:
+                        errorDetected |= printErrorIfTrue(
+                                !encounteredEvents.contains(GestureEvent.MOTION_UP),
+                                /* errorMessage= */ prefix + "\t\tSTATE_GESTURE_CANCELLED set "
+                                        + "before/without motion up.",
+                                writer);
+                        errorDetected |= printErrorIfTrue(
+                                !encounteredEvents.contains(GestureEvent.STATE_GESTURE_STARTED),
+                                /* errorMessage= */ prefix + "\t\tSTATE_GESTURE_CANCELLED set "
+                                        + "before/without STATE_GESTURE_STARTED.",
+                                writer);
+                        break;
+                    case MOTION_DOWN:
+                    case SET_END_TARGET:
+                    case START_RECENTS_ANIMATION:
+                    case STATE_GESTURE_STARTED:
+                    case STATE_END_TARGET_ANIMATION_FINISHED:
+                    case STATE_RECENTS_SCROLLING_FINISHED:
+                    default:
+                        // No-Op
+                }
+            }
+
+            // Check that all required events were found.
+            errorDetected |= printErrorIfTrue(
+                    !encounteredEvents.contains(GestureEvent.MOTION_DOWN),
+                    /* errorMessage= */ prefix + "\t\tMotion down never detected.",
+                    writer);
+            errorDetected |= printErrorIfTrue(
+                    !encounteredEvents.contains(GestureEvent.MOTION_UP),
+                    /* errorMessage= */ prefix + "\t\tMotion up never detected.",
+                    writer);
+
+            errorDetected |= printErrorIfTrue(
+                    /* condition= */ encounteredEvents.contains(GestureEvent.SET_END_TARGET)
+                            && !encounteredEvents.contains(GestureEvent.ON_SETTLED_ON_END_TARGET),
+                    /* errorMessage= */ prefix + "\t\tsetEndTarget was called, but "
+                            + "onSettledOnEndTarget wasn't.",
+                    writer);
+            errorDetected |= printErrorIfTrue(
+                    /* condition= */ encounteredEvents.contains(GestureEvent.SET_END_TARGET)
+                            && !encounteredEvents.contains(
+                                    GestureEvent.STATE_END_TARGET_ANIMATION_FINISHED),
+                    /* errorMessage= */ prefix + "\t\tsetEndTarget was called, but "
+                            + "STATE_END_TARGET_ANIMATION_FINISHED was never set.",
+                    writer);
+            errorDetected |= printErrorIfTrue(
+                    /* condition= */ encounteredEvents.contains(GestureEvent.SET_END_TARGET)
+                            && !encounteredEvents.contains(
+                                    GestureEvent.STATE_RECENTS_SCROLLING_FINISHED),
+                    /* errorMessage= */ prefix + "\t\tsetEndTarget was called, but "
+                            + "STATE_RECENTS_SCROLLING_FINISHED was never set.",
+                    writer);
+            errorDetected |= printErrorIfTrue(
+                    /* condition= */ encounteredEvents.contains(
+                            GestureEvent.STATE_END_TARGET_ANIMATION_FINISHED)
+                            && encounteredEvents.contains(
+                                    GestureEvent.STATE_RECENTS_SCROLLING_FINISHED)
+                            && !encounteredEvents.contains(GestureEvent.ON_SETTLED_ON_END_TARGET),
+                    /* errorMessage= */ prefix + "\t\tSTATE_END_TARGET_ANIMATION_FINISHED and "
+                            + "STATE_RECENTS_SCROLLING_FINISHED were set, but onSettledOnEndTarget "
+                            + "wasn't called.",
+                    writer);
+
+            errorDetected |= printErrorIfTrue(
+                    /* condition= */ encounteredEvents.contains(
+                            GestureEvent.START_RECENTS_ANIMATION)
+                            && !encounteredEvents.contains(GestureEvent.FINISH_RECENTS_ANIMATION)
+                            && !encounteredEvents.contains(GestureEvent.CANCEL_RECENTS_ANIMATION),
+                    /* errorMessage= */ prefix + "\t\tstartRecentsAnimation was called, but "
+                            + "finishRecentsAnimation and cancelRecentsAnimation weren't.",
+                    writer);
+
+            errorDetected |= printErrorIfTrue(
+                    /* condition= */ encounteredEvents.contains(GestureEvent.STATE_GESTURE_STARTED)
+                            && !encounteredEvents.contains(GestureEvent.STATE_GESTURE_COMPLETED)
+                            && !encounteredEvents.contains(GestureEvent.STATE_GESTURE_CANCELLED),
+                    /* errorMessage= */ prefix + "\t\tSTATE_GESTURE_STARTED was set, but "
+                            + "STATE_GESTURE_COMPLETED and STATE_GESTURE_CANCELLED weren't.",
+                    writer);
+
+            if (!errorDetected) {
+                writer.println(prefix + "\t\tNo errors detected.");
+            }
+        }
+    }
+
+    private static boolean printErrorIfTrue(
+            boolean condition, String errorMessage, PrintWriter writer) {
+        if (!condition) {
+            return false;
+        }
+        writer.println(errorMessage);
+        return true;
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/util/ActiveGestureLog.java b/quickstep/src/com/android/quickstep/util/ActiveGestureLog.java
index be45f63..9f08010 100644
--- a/quickstep/src/com/android/quickstep/util/ActiveGestureLog.java
+++ b/quickstep/src/com/android/quickstep/util/ActiveGestureLog.java
@@ -16,6 +16,9 @@
 package com.android.quickstep.util;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.config.FeatureFlags;
 
 import java.io.PrintWriter;
 import java.text.SimpleDateFormat;
@@ -47,6 +50,7 @@
     private static final int TYPE_BOOL_TRUE = 3;
     private static final int TYPE_BOOL_FALSE = 4;
     private static final int TYPE_INPUT_CONSUMER = 5;
+    private static final int TYPE_GESTURE_EVENT = 6;
 
     private final EventLog[] logs;
     private int nextIndex;
@@ -57,30 +61,73 @@
         this.nextIndex = 0;
     }
 
+    /**
+     * Track the given event for error detection.
+     *
+     * @param gestureEvent GestureEvent representing an event during the current gesture's
+     *                   execution.
+     */
+    public void trackEvent(@Nullable ActiveGestureErrorDetector.GestureEvent gestureEvent) {
+        addLog(TYPE_GESTURE_EVENT, "", 0, CompoundString.NO_OP, gestureEvent);
+    }
+
     public void addLog(String event) {
-        addLog(TYPE_ONE_OFF, event, 0, CompoundString.NO_OP);
+        addLog(event, null);
     }
 
     public void addLog(String event, int extras) {
-        addLog(TYPE_INTEGER, event, extras, CompoundString.NO_OP);
+        addLog(event, extras, null);
     }
 
     public void addLog(String event, boolean extras) {
-        addLog(extras ? TYPE_BOOL_TRUE : TYPE_BOOL_FALSE, event, 0, CompoundString.NO_OP);
+        addLog(event, extras, null);
     }
 
     public void addLog(CompoundString compoundString) {
-        addLog(TYPE_INPUT_CONSUMER, "", 0, compoundString);
+        addLog(TYPE_INPUT_CONSUMER, "", 0, compoundString, null);
+    }
+
+    /**
+     * Adds a log and track the associated event for error detection.
+     *
+     * @param gestureEvent GestureEvent representing the event being logged.
+     */
+    public void addLog(
+            String event, @Nullable ActiveGestureErrorDetector.GestureEvent gestureEvent) {
+        addLog(TYPE_ONE_OFF, event, 0, CompoundString.NO_OP, gestureEvent);
+    }
+
+    public void addLog(
+            String event,
+            int extras,
+            @Nullable ActiveGestureErrorDetector.GestureEvent gestureEvent) {
+        addLog(TYPE_INTEGER, event, extras, CompoundString.NO_OP, gestureEvent);
+    }
+
+    public void addLog(
+            String event,
+            boolean extras,
+            @Nullable ActiveGestureErrorDetector.GestureEvent gestureEvent) {
+        addLog(
+                extras ? TYPE_BOOL_TRUE : TYPE_BOOL_FALSE,
+                event,
+                0,
+                CompoundString.NO_OP,
+                gestureEvent);
     }
 
     private void addLog(
-            int type, String event, float extras, @NonNull CompoundString compoundString) {
+            int type,
+            String event,
+            float extras,
+            CompoundString compoundString,
+            @Nullable ActiveGestureErrorDetector.GestureEvent gestureEvent) {
         EventLog lastEventLog = logs[(nextIndex + logs.length - 1) % logs.length];
         if (lastEventLog == null || mCurrentLogId != lastEventLog.logId) {
             EventLog eventLog = new EventLog(mCurrentLogId);
             EventEntry eventEntry = new EventEntry();
 
-            eventEntry.update(type, event, extras, compoundString);
+            eventEntry.update(type, event, extras, compoundString, gestureEvent);
             eventLog.eventEntries.add(eventEntry);
             logs[nextIndex] = eventLog;
             nextIndex = (nextIndex + 1) % logs.length;
@@ -95,15 +142,15 @@
                 ? lastEventEntries.get(lastEventEntries.size() - 2) : null;
 
         // Update the last EventEntry if it's a duplicate
-        if (isEntrySame(lastEntry, type, event, compoundString)
-                && isEntrySame(secondLastEntry, type, event, compoundString)) {
-            lastEntry.update(type, event, extras, compoundString);
+        if (isEntrySame(lastEntry, type, event, compoundString, gestureEvent)
+                && isEntrySame(secondLastEntry, type, event, compoundString, gestureEvent)) {
+            lastEntry.update(type, event, extras, compoundString, gestureEvent);
             secondLastEntry.duplicateCount++;
             return;
         }
         EventEntry eventEntry = new EventEntry();
 
-        eventEntry.update(type, event, extras, compoundString);
+        eventEntry.update(type, event, extras, compoundString, gestureEvent);
         lastEventEntries.add(eventEntry);
     }
 
@@ -115,12 +162,14 @@
         writer.println(prefix + "ActiveGestureLog history:");
         SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSSZ  ", Locale.US);
         Date date = new Date();
+        ArrayList<EventLog> eventLogs = new ArrayList<>();
 
         for (int i = 0; i < logs.length; i++) {
             EventLog eventLog = logs[(nextIndex + logs.length - i - 1) % logs.length];
             if (eventLog == null) {
                 continue;
             }
+            eventLogs.add(eventLog);
             writer.println(prefix + "\tLogs for logId: " + eventLog.logId);
 
             List<EventEntry> eventEntries = eventLog.eventEntries;
@@ -146,6 +195,8 @@
                     case TYPE_INPUT_CONSUMER:
                         msg.append(eventEntry.mCompoundString);
                         break;
+                    case TYPE_GESTURE_EVENT:
+                        continue;
                     default: // fall out
                 }
                 if (eventEntry.duplicateCount > 0) {
@@ -154,6 +205,10 @@
                 writer.println(msg);
             }
         }
+
+        if (FeatureFlags.ENABLE_GESTURE_ERROR_DETECTION.get()) {
+            ActiveGestureErrorDetector.analyseAndDump(prefix + '\t', writer, eventLogs);
+        }
     }
 
     /**
@@ -165,44 +220,59 @@
     }
 
     private boolean isEntrySame(
-            EventEntry entry, int type, String event, CompoundString compoundString) {
+            EventEntry entry,
+            int type,
+            String event,
+            CompoundString compoundString,
+            ActiveGestureErrorDetector.GestureEvent gestureEvent) {
         return entry != null
                 && entry.type == type
                 && entry.event.equals(event)
-                && entry.mCompoundString.equals(compoundString);
+                && entry.mCompoundString.equals(compoundString)
+                && entry.gestureEvent == gestureEvent;
     }
 
     /** A single event entry. */
-    private static class EventEntry {
+    protected static class EventEntry {
 
         private int type;
         private String event;
         private float extras;
         @NonNull private CompoundString mCompoundString;
+        private ActiveGestureErrorDetector.GestureEvent gestureEvent;
         private long time;
         private int duplicateCount;
 
-        public void update(
+        private EventEntry() {}
+
+        @Nullable
+        protected ActiveGestureErrorDetector.GestureEvent getGestureEvent() {
+            return gestureEvent;
+        }
+
+        private void update(
                 int type,
                 String event,
                 float extras,
-                @NonNull CompoundString compoundString) {
+                @NonNull CompoundString compoundString,
+                ActiveGestureErrorDetector.GestureEvent gestureEvent) {
             this.type = type;
             this.event = event;
             this.extras = extras;
             this.mCompoundString = compoundString;
+            this.gestureEvent = gestureEvent;
             time = System.currentTimeMillis();
             duplicateCount = 0;
         }
     }
 
     /** An entire log of entries associated with a single log ID */
-    private static class EventLog {
+    protected static class EventLog {
 
-        private final List<EventEntry> eventEntries = new ArrayList<>();
-        private final int logId;
+        protected final List<EventEntry> eventEntries = new ArrayList<>();
+        protected final int logId;
 
-        protected EventLog(int logId) {
+        private EventLog(int logId) {
             this.logId = logId;
         }
     }
diff --git a/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java b/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java
index e758f5b..69ed2f8 100644
--- a/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java
+++ b/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java
@@ -194,7 +194,11 @@
         }
         if (mIsPaused != isPaused) {
             mIsPaused = isPaused;
-            Log.d(TAG, "onMotionPauseChanged, paused=" + mIsPaused + " reason=" + reason);
+            String logString = "onMotionPauseChanged, paused=" + mIsPaused + " reason=" + reason;
+            if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
+                Log.d(TAG, logString);
+            }
+            ActiveGestureLog.INSTANCE.addLog(logString);
             boolean isFirstDetectedPause = !mHasEverBeenPaused && mIsPaused;
             if (mIsPaused) {
                 AccessibilityManagerCompat.sendPauseDetectedEventToTest(mContext);
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index 005973c..82a47b0 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -68,6 +68,11 @@
             false,
             "Log the reason why an Input Consumer was selected for a gesture.");
 
+    public static final BooleanFlag ENABLE_GESTURE_ERROR_DETECTION = getDebugFlag(
+            "ENABLE_GESTURE_ERROR_DETECTION",
+            false,
+            "Analyze gesture events and log detected errors");
+
     // When enabled the promise icon is visible in all apps while installation an app.
     public static final BooleanFlag PROMISE_APPS_IN_ALL_APPS = getDebugFlag(
             "PROMISE_APPS_IN_ALL_APPS", false, "Add promise icon in all-apps");