Merge "Use real IDP in DeviceProfileTest" into tm-qpr-dev
diff --git a/quickstep/src/com/android/launcher3/appprediction/AppsDividerView.java b/quickstep/src/com/android/launcher3/appprediction/AppsDividerView.java
index f42b39f..e8374b8 100644
--- a/quickstep/src/com/android/launcher3/appprediction/AppsDividerView.java
+++ b/quickstep/src/com/android/launcher3/appprediction/AppsDividerView.java
@@ -35,6 +35,7 @@
import com.android.launcher3.R;
import com.android.launcher3.allapps.FloatingHeaderRow;
import com.android.launcher3.allapps.FloatingHeaderView;
+import com.android.launcher3.util.OnboardingPrefs;
import com.android.launcher3.util.Themes;
import com.android.launcher3.views.ActivityContext;
@@ -92,8 +93,10 @@
? R.color.all_apps_label_text_dark
: R.color.all_apps_label_text);
- mShowAllAppsLabel = !ActivityContext.lookupContext(
- getContext()).getOnboardingPrefs().hasReachedMaxCount(ALL_APPS_VISITED_COUNT);
+ OnboardingPrefs<?> onboardingPrefs = ActivityContext.lookupContext(
+ getContext()).getOnboardingPrefs();
+ mShowAllAppsLabel = onboardingPrefs == null || !onboardingPrefs.hasReachedMaxCount(
+ ALL_APPS_VISITED_COUNT);
}
public void setup(FloatingHeaderView parent, FloatingHeaderRow[] rows, boolean tabsHidden) {
@@ -216,8 +219,8 @@
CharSequence allAppsLabelText = getResources().getText(R.string.all_apps_label);
mAllAppsLabelLayout = StaticLayout.Builder.obtain(
- allAppsLabelText, 0, allAppsLabelText.length(), mPaint,
- Math.round(mPaint.measureText(allAppsLabelText.toString())))
+ allAppsLabelText, 0, allAppsLabelText.length(), mPaint,
+ Math.round(mPaint.measureText(allAppsLabelText.toString())))
.setAlignment(Layout.Alignment.ALIGN_CENTER)
.setMaxLines(1)
.setIncludePad(true)
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index aafaf6f..0a99204 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -120,10 +120,12 @@
import com.android.launcher3.util.PendingRequestArgs;
import com.android.launcher3.util.PendingSplitSelectInfo;
import com.android.launcher3.util.RunnableList;
+import com.android.launcher3.util.SafeCloseable;
import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
import com.android.launcher3.util.TouchController;
import com.android.launcher3.util.UiThreadHelper;
import com.android.launcher3.util.UiThreadHelper.AsyncCommand;
+import com.android.launcher3.util.ViewCapture;
import com.android.launcher3.widget.LauncherAppWidgetHost;
import com.android.quickstep.OverviewCommandHelper;
import com.android.quickstep.RecentsModel;
@@ -195,6 +197,8 @@
*/
private PendingSplitSelectInfo mPendingSplitSelectInfo = null;
+ private SafeCloseable mViewCapture;
+
@Override
protected void setupViews() {
super.setupViews();
@@ -410,6 +414,7 @@
super.onDestroy();
mHotseatPredictionController.destroy();
+ mViewCapture.close();
}
@Override
@@ -509,6 +514,7 @@
}
addMultiWindowModeChangedListener(mDepthController);
initUnfoldTransitionProgressProvider();
+ mViewCapture = ViewCapture.INSTANCE.get(this).startCapture(getWindow());
}
@Override
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index eff6fed..b7a50fc 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -334,6 +334,12 @@
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 @@
// 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 @@
* 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 bc2f551..38bf1fd 100644
--- a/quickstep/src/com/android/quickstep/GestureState.java
+++ b/quickstep/src/com/android/quickstep/GestureState.java
@@ -189,6 +189,8 @@
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 fefef2f..542c0d4 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationController.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationController.java
@@ -32,6 +32,8 @@
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 @@
*/
@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 0314761..5fb806d 100644
--- a/quickstep/src/com/android/quickstep/TaskAnimationManager.java
+++ b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
@@ -36,6 +36,8 @@
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 @@
// 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 82be3ec..c650f29 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -82,6 +82,7 @@
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.OnboardingPrefs;
import com.android.launcher3.util.TraceHelper;
+import com.android.launcher3.util.ViewCapture;
import com.android.launcher3.util.WindowBounds;
import com.android.quickstep.inputconsumers.AccessibilityInputConsumer;
import com.android.quickstep.inputconsumers.AssistantInputConsumer;
@@ -721,8 +722,10 @@
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 +1027,27 @@
.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,
@@ -1196,6 +1214,8 @@
createdOverviewActivity.getDeviceProfile().dump(this, "", pw);
}
mTaskbarManager.dumpLogs("", pw);
+
+ ViewCapture.INSTANCE.get(this).dump(pw, fd);
}
}
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/ProgressDelegateInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/ProgressDelegateInputConsumer.java
index 2462394..b9b5e7c 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.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.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 @@
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 @@
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 78075de..54f632a 100644
--- a/quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java
+++ b/quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java
@@ -29,11 +29,24 @@
*/
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 @@
+ "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 @@
+ "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 @@
+ "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 9f08010..40eb31b 100644
--- a/quickstep/src/com/android/quickstep/util/ActiveGestureLog.java
+++ b/quickstep/src/com/android/quickstep/util/ActiveGestureLog.java
@@ -138,14 +138,10 @@
List<EventEntry> 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 @@
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 @@
return false;
}
CompoundString other = (CompoundString) obj;
- return mIsNoOp && other.mIsNoOp && Objects.equals(mSubstrings, other.mSubstrings);
+ return (mIsNoOp == other.mIsNoOp) && Objects.equals(mSubstrings, other.mSubstrings);
}
}
}
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 4a52d3e..4312c3d 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -195,7 +195,6 @@
import com.android.launcher3.util.TouchController;
import com.android.launcher3.util.TraceHelper;
import com.android.launcher3.util.UiThreadHelper;
-import com.android.launcher3.util.ViewCapture;
import com.android.launcher3.util.ViewOnDrawExecutor;
import com.android.launcher3.views.ActivityContext;
import com.android.launcher3.views.FloatingIconView;
@@ -393,7 +392,6 @@
private LauncherState mPrevLauncherState;
private StringCache mStringCache;
- private ViewCapture mViewCapture;
@Override
@TargetApi(Build.VERSION_CODES.S)
@@ -1488,23 +1486,11 @@
public void onAttachedToWindow() {
super.onAttachedToWindow();
mOverlayManager.onAttachedToWindow();
- if (FeatureFlags.CONTINUOUS_VIEW_TREE_CAPTURE.get()) {
- View root = getDragLayer().getRootView();
- if (mViewCapture != null) {
- mViewCapture.detach();
- }
- mViewCapture = new ViewCapture(getWindow());
- mViewCapture.attach();
- }
}
@Override
public void onDetachedFromWindow() {
super.onDetachedFromWindow();
- if (mViewCapture != null) {
- mViewCapture.detach();
- mViewCapture = null;
- }
mOverlayManager.onDetachedFromWindow();
closeContextMenu();
}
@@ -2985,7 +2971,6 @@
*/
@Override
public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
- SafeCloseable viewDump = mViewCapture == null ? null : mViewCapture.beginDump(writer, fd);
super.dump(prefix, fd, writer, args);
if (args.length > 0 && TextUtils.equals(args[0], "--all")) {
@@ -3026,10 +3011,6 @@
mPopupDataProvider.dump(prefix, writer);
mDeviceProfile.dump(this, prefix, writer);
- if (viewDump != null) {
- viewDump.close();
- }
-
try {
FileLog.flushAll(writer);
} catch (Exception e) {
diff --git a/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java b/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
index 33b2f55..a55f7e3 100644
--- a/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
+++ b/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
@@ -73,11 +73,11 @@
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mModel = LauncherAppState.getInstance(this).getModel();
+ mOnboardingPrefs = new OnboardingPrefs<>(this, Utilities.getPrefs(this));
+ mSecondaryDisplayPredictions = SecondaryDisplayPredictions.newInstance(this);
if (getWindow().getDecorView().isAttachedToWindow()) {
initUi();
}
- mOnboardingPrefs = new OnboardingPrefs<>(this, Utilities.getPrefs(this));
- mSecondaryDisplayPredictions = SecondaryDisplayPredictions.newInstance(this);
}
@Override
diff --git a/src/com/android/launcher3/util/ViewCapture.java b/src/com/android/launcher3/util/ViewCapture.java
index e368ac3..0cf3ad7 100644
--- a/src/com/android/launcher3/util/ViewCapture.java
+++ b/src/com/android/launcher3/util/ViewCapture.java
@@ -17,18 +17,25 @@
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+import static com.android.launcher3.util.Executors.createAndStartNewLooper;
import static java.util.stream.Collectors.toList;
+import android.content.Context;
import android.content.res.Resources;
import android.os.Handler;
+import android.os.Looper;
import android.os.Message;
+import android.os.Process;
import android.os.Trace;
import android.text.TextUtils;
import android.util.Base64;
import android.util.Base64OutputStream;
import android.util.Log;
+import android.util.Pair;
+import android.util.SparseArray;
import android.view.View;
+import android.view.View.OnAttachStateChangeListener;
import android.view.ViewGroup;
import android.view.ViewTreeObserver.OnDrawListener;
import android.view.Window;
@@ -36,6 +43,7 @@
import androidx.annotation.UiThread;
import androidx.annotation.WorkerThread;
+import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.view.ViewCaptureData.ExportedData;
import com.android.launcher3.view.ViewCaptureData.FrameData;
import com.android.launcher3.view.ViewCaptureData.ViewNode;
@@ -45,13 +53,14 @@
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.List;
import java.util.concurrent.Future;
import java.util.zip.GZIPOutputStream;
/**
* Utility class for capturing view data every frame
*/
-public class ViewCapture implements OnDrawListener {
+public class ViewCapture {
private static final String TAG = "ViewCapture";
@@ -61,104 +70,27 @@
// Launcher. This allows the first free frames avoid object allocation during view capture.
private static final int INIT_POOL_SIZE = 300;
- private final Window mWindow;
- private final View mRoot;
- private final Resources mResources;
+ public static final MainThreadInitializedObject<ViewCapture> INSTANCE =
+ new MainThreadInitializedObject<>(ViewCapture::new);
- private final Handler mHandler;
- private final ViewRef mViewRef = new ViewRef();
+ private final List<WindowListener> mListeners = new ArrayList<>();
- private int mFrameIndexBg = -1;
- private final long[] mFrameTimesBg = new long[MEMORY_SIZE];
- private final ViewPropertyRef[] mNodesBg = new ViewPropertyRef[MEMORY_SIZE];
+ private final Context mContext;
+ private final LooperExecutor mExecutor;
// Pool used for capturing view tree on the UI thread.
private ViewRef mPool = new ViewRef();
- /**
- * @param window the window for the capture data
- */
- public ViewCapture(Window window) {
- mWindow = window;
- mRoot = mWindow.getDecorView();
- mResources = mRoot.getResources();
- mHandler = new Handler(UI_HELPER_EXECUTOR.getLooper(), this::captureViewPropertiesBg);
- }
-
- /**
- * Attaches the ViewCapture to the root
- */
- public void attach() {
- mHandler.post(this::initPool);
- }
-
- /**
- * Removes a previously attached ViewCapture from the root
- */
- public void detach() {
- mHandler.post(() -> MAIN_EXECUTOR.execute(
- () -> mRoot.getViewTreeObserver().removeOnDrawListener(this)));
- }
-
- @Override
- public void onDraw() {
- Trace.beginSection("view_capture");
- captureViewTree(mRoot, mViewRef);
- Message m = Message.obtain(mHandler);
- m.obj = mViewRef.next;
- mHandler.sendMessage(m);
- Trace.endSection();
- }
-
- /**
- * Captures the View property on the background thread, and transfer all the ViewRef objects
- * back to the pool
- */
- @WorkerThread
- private boolean captureViewPropertiesBg(Message msg) {
- ViewRef start = (ViewRef) msg.obj;
- long time = msg.getWhen();
- if (start == null) {
- return false;
+ private ViewCapture(Context context) {
+ mContext = context;
+ if (FeatureFlags.CONTINUOUS_VIEW_TREE_CAPTURE.get()) {
+ Looper looper = createAndStartNewLooper("ViewCapture",
+ Process.THREAD_PRIORITY_FOREGROUND);
+ mExecutor = new LooperExecutor(looper);
+ mExecutor.execute(this::initPool);
+ } else {
+ mExecutor = UI_HELPER_EXECUTOR;
}
- mFrameIndexBg++;
- if (mFrameIndexBg >= MEMORY_SIZE) {
- mFrameIndexBg = 0;
- }
- mFrameTimesBg[mFrameIndexBg] = time;
-
- ViewPropertyRef recycle = mNodesBg[mFrameIndexBg];
-
- ViewPropertyRef result = null;
- ViewPropertyRef resultEnd = null;
-
- ViewRef current = start;
- ViewRef last = start;
- while (current != null) {
- ViewPropertyRef propertyRef = recycle;
- if (propertyRef == null) {
- propertyRef = new ViewPropertyRef();
- } else {
- recycle = recycle.next;
- propertyRef.next = null;
- }
-
- propertyRef.transfer(current);
- last = current;
- current = current.next;
-
- if (result == null) {
- result = propertyRef;
- resultEnd = result;
- } else {
- resultEnd.next = propertyRef;
- resultEnd = propertyRef;
- }
- }
- mNodesBg[mFrameIndexBg] = result;
- ViewRef end = last;
- MAIN_EXECUTOR.execute(() -> addToPool(start, end));
- return true;
}
@UiThread
@@ -177,36 +109,59 @@
current = current.next;
}
- ViewRef end = current;
- MAIN_EXECUTOR.execute(() -> {
- addToPool(start, end);
- if (mRoot.isAttachedToWindow()) {
- mRoot.getViewTreeObserver().addOnDrawListener(this);
- }
- });
- }
-
- private String getName() {
- String title = mWindow.getAttributes().getTitle().toString();
- return TextUtils.isEmpty(title) ? mWindow.toString() : title;
+ ViewRef finalCurrent = current;
+ MAIN_EXECUTOR.execute(() -> addToPool(start, finalCurrent));
}
/**
- * Starts the dump process which is completed on closing the returned object.
+ * Attaches the ViewCapture to the provided window and returns a handle to detach the listener
*/
- public SafeCloseable beginDump(PrintWriter writer, FileDescriptor out) {
- Future<ExportedData> task = UI_HELPER_EXECUTOR.submit(this::dumpToProto);
+ public SafeCloseable startCapture(Window window) {
+ String title = window.getAttributes().getTitle().toString();
+ String name = TextUtils.isEmpty(title) ? window.toString() : title;
+ return startCapture(window.getDecorView(), name);
+ }
+ /**
+ * Attaches the ViewCapture to the provided window and returns a handle to detach the listener
+ */
+ public SafeCloseable startCapture(View view, String name) {
+ if (!FeatureFlags.CONTINUOUS_VIEW_TREE_CAPTURE.get()) {
+ return () -> { };
+ }
+
+ WindowListener listener = new WindowListener(view, name);
+ mExecutor.execute(() -> MAIN_EXECUTOR.execute(listener::attachToRoot));
+ mListeners.add(listener);
return () -> {
+ mListeners.remove(listener);
+ listener.destroy();
+ };
+ }
+
+ /**
+ * Dumps all the active view captures
+ */
+ public void dump(PrintWriter writer, FileDescriptor out) {
+ if (!FeatureFlags.CONTINUOUS_VIEW_TREE_CAPTURE.get()) {
+ return;
+ }
+ ViewIdProvider idProvider = new ViewIdProvider(mContext.getResources());
+
+ // Collect all the tasks first so that all the tasks are posted on the executor
+ List<Pair<String, Future<ExportedData>>> tasks = mListeners.stream()
+ .map(l -> Pair.create(l.name, mExecutor.submit(() -> l.dumpToProto(idProvider))))
+ .collect(toList());
+
+ tasks.forEach(pair -> {
writer.println();
writer.println(" ContinuousViewCapture:");
- writer.println(" window " + getName() + ":");
- writer.println(" pkg:" + mRoot.getContext().getPackageName());
+ writer.println(" window " + pair.first + ":");
+ writer.println(" pkg:" + mContext.getPackageName());
writer.print(" data:");
writer.flush();
-
try (OutputStream os = new FileOutputStream(out)) {
- ExportedData data = task.get();
+ ExportedData data = pair.second.get();
OutputStream encodedOS = new GZIPOutputStream(new Base64OutputStream(os,
Base64.NO_CLOSE | Base64.NO_PADDING | Base64.NO_WRAP));
data.writeTo(encodedOS);
@@ -217,51 +172,156 @@
}
writer.println();
writer.println("--end--");
- };
+ });
}
- @WorkerThread
- private ExportedData dumpToProto() {
- ExportedData.Builder dataBuilder = ExportedData.newBuilder();
- Resources res = mResources;
- ArrayList<Class> classList = new ArrayList<>();
+ private class WindowListener implements OnDrawListener {
- int size = (mNodesBg[MEMORY_SIZE - 1] == null) ? mFrameIndexBg + 1 : MEMORY_SIZE;
- for (int i = size - 1; i >= 0; i--) {
- int index = (MEMORY_SIZE + mFrameIndexBg - i) % MEMORY_SIZE;
- ViewNode.Builder nodeBuilder = ViewNode.newBuilder();
- mNodesBg[index].toProto(res, classList, nodeBuilder);
- dataBuilder.addFrameData(FrameData.newBuilder()
- .setNode(nodeBuilder)
- .setTimestamp(mFrameTimesBg[index]));
- }
- return dataBuilder
- .addAllClassname(classList.stream().map(Class::getName).collect(toList()))
- .build();
- }
+ private final View mRoot;
+ public final String name;
- private ViewRef captureViewTree(View view, ViewRef start) {
- ViewRef ref;
- if (mPool != null) {
- ref = mPool;
- mPool = mPool.next;
- ref.next = null;
- } else {
- ref = new ViewRef();
+ private final Handler mHandler;
+ private final ViewRef mViewRef = new ViewRef();
+
+ private int mFrameIndexBg = -1;
+ private final long[] mFrameTimesBg = new long[MEMORY_SIZE];
+ private final ViewPropertyRef[] mNodesBg = new ViewPropertyRef[MEMORY_SIZE];
+
+ private boolean mDestroyed = false;
+
+ WindowListener(View view, String name) {
+ mRoot = view;
+ this.name = name;
+ mHandler = new Handler(mExecutor.getLooper(), this::captureViewPropertiesBg);
}
- ref.view = view;
- start.next = ref;
- if (view instanceof ViewGroup) {
- ViewRef result = ref;
- ViewGroup parent = (ViewGroup) view;
- int childCount = ref.childCount = parent.getChildCount();
- for (int i = 0; i < childCount; i++) {
- result = captureViewTree(parent.getChildAt(i), result);
+
+ @Override
+ public void onDraw() {
+ Trace.beginSection("view_capture");
+ captureViewTree(mRoot, mViewRef);
+ Message m = Message.obtain(mHandler);
+ m.obj = mViewRef.next;
+ mHandler.sendMessage(m);
+ Trace.endSection();
+ }
+
+ /**
+ * Captures the View property on the background thread, and transfer all the ViewRef objects
+ * back to the pool
+ */
+ @WorkerThread
+ private boolean captureViewPropertiesBg(Message msg) {
+ ViewRef start = (ViewRef) msg.obj;
+ long time = msg.getWhen();
+ if (start == null) {
+ return false;
}
- return result;
- } else {
- ref.childCount = 0;
- return ref;
+ mFrameIndexBg++;
+ if (mFrameIndexBg >= MEMORY_SIZE) {
+ mFrameIndexBg = 0;
+ }
+ mFrameTimesBg[mFrameIndexBg] = time;
+
+ ViewPropertyRef recycle = mNodesBg[mFrameIndexBg];
+
+ ViewPropertyRef result = null;
+ ViewPropertyRef resultEnd = null;
+
+ ViewRef current = start;
+ ViewRef last = start;
+ while (current != null) {
+ ViewPropertyRef propertyRef = recycle;
+ if (propertyRef == null) {
+ propertyRef = new ViewPropertyRef();
+ } else {
+ recycle = recycle.next;
+ propertyRef.next = null;
+ }
+
+ propertyRef.transfer(current);
+ last = current;
+ current = current.next;
+
+ if (result == null) {
+ result = propertyRef;
+ resultEnd = result;
+ } else {
+ resultEnd.next = propertyRef;
+ resultEnd = propertyRef;
+ }
+ }
+ mNodesBg[mFrameIndexBg] = result;
+ ViewRef end = last;
+ MAIN_EXECUTOR.execute(() -> addToPool(start, end));
+ return true;
+ }
+
+ void attachToRoot() {
+ if (mRoot.isAttachedToWindow()) {
+ mRoot.getViewTreeObserver().addOnDrawListener(this);
+ } else {
+ mRoot.addOnAttachStateChangeListener(new OnAttachStateChangeListener() {
+ @Override
+ public void onViewAttachedToWindow(View v) {
+ if (!mDestroyed) {
+ mRoot.getViewTreeObserver().addOnDrawListener(WindowListener.this);
+ }
+ mRoot.removeOnAttachStateChangeListener(this);
+ }
+
+ @Override
+ public void onViewDetachedFromWindow(View v) { }
+ });
+ }
+ }
+
+ void destroy() {
+ mRoot.getViewTreeObserver().removeOnDrawListener(this);
+ mDestroyed = true;
+ }
+
+ @WorkerThread
+ private ExportedData dumpToProto(ViewIdProvider idProvider) {
+ ExportedData.Builder dataBuilder = ExportedData.newBuilder();
+ ArrayList<Class> classList = new ArrayList<>();
+
+ int size = (mNodesBg[MEMORY_SIZE - 1] == null) ? mFrameIndexBg + 1 : MEMORY_SIZE;
+ for (int i = size - 1; i >= 0; i--) {
+ int index = (MEMORY_SIZE + mFrameIndexBg - i) % MEMORY_SIZE;
+ ViewNode.Builder nodeBuilder = ViewNode.newBuilder();
+ mNodesBg[index].toProto(idProvider, classList, nodeBuilder);
+ dataBuilder.addFrameData(FrameData.newBuilder()
+ .setNode(nodeBuilder)
+ .setTimestamp(mFrameTimesBg[index]));
+ }
+ return dataBuilder
+ .addAllClassname(classList.stream().map(Class::getName).collect(toList()))
+ .build();
+ }
+
+ private ViewRef captureViewTree(View view, ViewRef start) {
+ ViewRef ref;
+ if (mPool != null) {
+ ref = mPool;
+ mPool = mPool.next;
+ ref.next = null;
+ } else {
+ ref = new ViewRef();
+ }
+ ref.view = view;
+ start.next = ref;
+ if (view instanceof ViewGroup) {
+ ViewRef result = ref;
+ ViewGroup parent = (ViewGroup) view;
+ int childCount = ref.childCount = parent.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ result = captureViewTree(parent.getChildAt(i), result);
+ }
+ return result;
+ } else {
+ ref.childCount = 0;
+ return ref;
+ }
}
}
@@ -318,18 +378,8 @@
* at the end of the iteration.
* @return
*/
- public ViewPropertyRef toProto(Resources res, ArrayList<Class> classList,
+ public ViewPropertyRef toProto(ViewIdProvider idProvider, ArrayList<Class> classList,
ViewNode.Builder outBuilder) {
- String resolvedId;
- if (id >= 0) {
- try {
- resolvedId = res.getResourceTypeName(id) + '/' + res.getResourceEntryName(id);
- } catch (Resources.NotFoundException e) {
- resolvedId = "id/" + "0x" + Integer.toHexString(id).toUpperCase();
- }
- } else {
- resolvedId = "NO_ID";
- }
int classnameIndex = classList.indexOf(clazz);
if (classnameIndex < 0) {
classnameIndex = classList.size();
@@ -338,7 +388,7 @@
outBuilder
.setClassnameIndex(classnameIndex)
.setHashcode(hashCode)
- .setId(resolvedId)
+ .setId(idProvider.getName(id))
.setLeft(left)
.setTop(top)
.setWidth(right - left)
@@ -356,7 +406,7 @@
ViewPropertyRef result = next;
for (int i = 0; (i < childCount) && (result != null); i++) {
ViewNode.Builder childBuilder = ViewNode.newBuilder();
- result = result.toProto(res, classList, childBuilder);
+ result = result.toProto(idProvider, classList, childBuilder);
outBuilder.addChildren(childBuilder);
}
return result;
@@ -368,4 +418,31 @@
public int childCount = 0;
public ViewRef next;
}
+
+ private static final class ViewIdProvider {
+
+ private final SparseArray<String> mNames = new SparseArray<>();
+ private final Resources mRes;
+
+ ViewIdProvider(Resources res) {
+ mRes = res;
+ }
+
+ String getName(int id) {
+ String name = mNames.get(id);
+ if (name == null) {
+ if (id >= 0) {
+ try {
+ name = mRes.getResourceTypeName(id) + '/' + mRes.getResourceEntryName(id);
+ } catch (Resources.NotFoundException e) {
+ name = "id/" + "0x" + Integer.toHexString(id).toUpperCase();
+ }
+ } else {
+ name = "NO_ID";
+ }
+ mNames.put(id, name);
+ }
+ return name;
+ }
+ }
}
diff --git a/src/com/android/launcher3/views/ActivityContext.java b/src/com/android/launcher3/views/ActivityContext.java
index 93078e4..e2dc34f 100644
--- a/src/com/android/launcher3/views/ActivityContext.java
+++ b/src/com/android/launcher3/views/ActivityContext.java
@@ -141,6 +141,7 @@
default void applyOverwritesToLogItem(LauncherAtom.ItemInfo.Builder itemInfoBuilder) { }
/** Onboarding preferences for any onboarding data within this context. */
+ @Nullable
default OnboardingPrefs<?> getOnboardingPrefs() {
return null;
}