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");