Merge "Remove unused Assistant gesture tutorial and add error checking" into udc-dev
diff --git a/Android.bp b/Android.bp
index e730c9d..a7edf2a 100644
--- a/Android.bp
+++ b/Android.bp
@@ -79,7 +79,7 @@
"androidx.test.uiautomator_uiautomator",
"androidx.preference_preference",
"SystemUISharedLib",
- "SystemUIAnimationLib",
+ "animationlib",
"launcher-testing-shared",
],
srcs: [
@@ -243,7 +243,7 @@
"lottie",
"SystemUISharedLib",
"SystemUI-statsd",
- "SystemUIAnimationLib",
+ "animationlib",
],
manifest: "quickstep/AndroidManifest.xml",
min_sdk_version: "current",
@@ -305,7 +305,7 @@
"SystemUISharedLib",
"Launcher3CommonDepsLib",
"QuickstepResLib",
- "SystemUIAnimationLib",
+ "animationlib",
],
manifest: "quickstep/AndroidManifest.xml",
platform_apis: true,
diff --git a/OWNERS b/OWNERS
index 76644b3..b684460 100644
--- a/OWNERS
+++ b/OWNERS
@@ -12,6 +12,7 @@
vadimt@google.com
winsonc@google.com
jonmiranda@google.com
+alexchau@google.com
per-file FeatureFlags.java, globs = set noparent
per-file FeatureFlags.java = sunnygoyal@google.com, winsonc@google.com, adamcohen@google.com, hyunyoungs@google.com, captaincole@google.com
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index 9db03f5..7e0530b 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -674,18 +674,6 @@
}
/**
- * Notify system to inset the rounded corner frame based on the task bar insets.
- */
- public void updateInsetRoundedCornerFrame(boolean shouldInsetsRoundedCorner) {
- if (!mDragLayer.isAttachedToWindow()
- || mWindowLayoutParams.insetsRoundedCornerFrame == shouldInsetsRoundedCorner) {
- return;
- }
- mWindowLayoutParams.insetsRoundedCornerFrame = shouldInsetsRoundedCorner;
- mWindowManager.updateViewLayout(mDragLayer, mWindowLayoutParams);
- }
-
- /**
* Updates the TaskbarContainer height (pass {@link #getDefaultTaskbarWindowHeight()} to reset).
*/
public void setTaskbarWindowHeight(int height) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarEduTooltip.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarEduTooltip.kt
index 7dda73f..7f65e41 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarEduTooltip.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarEduTooltip.kt
@@ -26,15 +26,15 @@
import android.view.View
import android.view.ViewGroup
import android.view.animation.Interpolator
+import com.android.app.animation.Interpolators.EMPHASIZED_ACCELERATE
+import com.android.app.animation.Interpolators.EMPHASIZED_DECELERATE
+import com.android.app.animation.Interpolators.STANDARD
import com.android.launcher3.AbstractFloatingView
import com.android.launcher3.R
import com.android.launcher3.anim.AnimatorListeners
import com.android.launcher3.popup.RoundedArrowDrawable
import com.android.launcher3.util.Themes
import com.android.launcher3.views.ActivityContext
-import com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE
-import com.android.systemui.animation.Interpolators.EMPHASIZED_DECELERATE
-import com.android.systemui.animation.Interpolators.STANDARD
private const val ENTER_DURATION_MS = 300L
private const val EXIT_DURATION_MS = 150L
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt
index c029097..19b9a18 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt
@@ -71,7 +71,6 @@
fun init(controllers: TaskbarControllers) {
this.controllers = controllers
windowLayoutParams = context.windowLayoutParams
- windowLayoutParams.insetsRoundedCornerFrame = true
onTaskbarWindowHeightOrInsetsChanged()
context.addOnDeviceProfileChangeListener(deviceProfileChangeListener)
@@ -86,23 +85,23 @@
fun onTaskbarWindowHeightOrInsetsChanged() {
if (context.isGestureNav) {
windowLayoutParams.providedInsets =
- arrayOf(
- InsetsFrameProvider(insetsOwner, 0, navigationBars())
- .setFlags(FLAG_SUPPRESS_SCRIM, FLAG_SUPPRESS_SCRIM),
- InsetsFrameProvider(insetsOwner, 0, tappableElement()),
- InsetsFrameProvider(insetsOwner, 0, mandatorySystemGestures()),
- InsetsFrameProvider(insetsOwner, INDEX_LEFT, systemGestures())
- .setSource(SOURCE_DISPLAY),
- InsetsFrameProvider(insetsOwner, INDEX_RIGHT, systemGestures())
- .setSource(SOURCE_DISPLAY)
- )
+ arrayOf(
+ InsetsFrameProvider(insetsOwner, 0, navigationBars())
+ .setFlags(FLAG_SUPPRESS_SCRIM, FLAG_SUPPRESS_SCRIM),
+ InsetsFrameProvider(insetsOwner, 0, tappableElement()),
+ InsetsFrameProvider(insetsOwner, 0, mandatorySystemGestures()),
+ InsetsFrameProvider(insetsOwner, INDEX_LEFT, systemGestures())
+ .setSource(SOURCE_DISPLAY),
+ InsetsFrameProvider(insetsOwner, INDEX_RIGHT, systemGestures())
+ .setSource(SOURCE_DISPLAY)
+ )
} else {
windowLayoutParams.providedInsets =
- arrayOf(
- InsetsFrameProvider(insetsOwner, 0, navigationBars()),
- InsetsFrameProvider(insetsOwner, 0, tappableElement()),
- InsetsFrameProvider(insetsOwner, 0, mandatorySystemGestures())
- )
+ arrayOf(
+ InsetsFrameProvider(insetsOwner, 0, navigationBars()),
+ InsetsFrameProvider(insetsOwner, 0, tappableElement()),
+ InsetsFrameProvider(insetsOwner, 0, mandatorySystemGestures())
+ )
}
val touchableHeight = controllers.taskbarStashController.touchableHeight
@@ -162,6 +161,11 @@
provider.insetsSizeOverrides = insetsSizeOverride
}
}
+
+ // We only report tappableElement height for unstashed, persistent taskbar,
+ // which is also when we draw the rounded corners above taskbar.
+ windowLayoutParams.insetsRoundedCornerFrame = tappableHeight > 0
+
context.notifyUpdateLayoutParams()
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
index 59b5e74..ed78e2d 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
@@ -15,13 +15,13 @@
*/
package com.android.launcher3.taskbar;
+import static com.android.app.animation.Interpolators.EMPHASIZED;
import static com.android.launcher3.taskbar.TaskbarKeyguardController.MASK_ANY_SYSUI_LOCKED;
import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_IN_APP;
import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_IN_STASHED_LAUNCHER_STATE;
import static com.android.launcher3.taskbar.TaskbarViewController.ALPHA_INDEX_HOME;
import static com.android.launcher3.util.FlagDebugUtils.appendFlag;
import static com.android.launcher3.util.FlagDebugUtils.formatFlagChange;
-import static com.android.systemui.animation.Interpolators.EMPHASIZED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_AWAKE;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DEVICE_DREAMING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_WAKEFULNESS_MASK;
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
index e334d05..b2f9378 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
@@ -106,9 +106,6 @@
| FLAG_STASHED_IN_APP_IME | FLAG_STASHED_IN_TASKBAR_ALL_APPS
| FLAG_STASHED_SMALL_SCREEN | FLAG_STASHED_IN_APP_AUTO;
- private static final int FLAGS_STASHED_IN_APP_IGNORING_IME =
- FLAGS_STASHED_IN_APP & ~FLAG_STASHED_IN_APP_IME;
-
// If any of these flags are enabled, inset apps by our stashed height instead of our unstashed
// height. This way the reported insets are consistent even during transitions out of the app.
// Currently any flag that causes us to stash in an app is included, except for IME or All Apps
@@ -414,13 +411,6 @@
}
/**
- * Returns whether the taskbar should be stashed in apps regardless of the IME visibility.
- */
- public boolean isStashedInAppIgnoringIme() {
- return hasAnyFlag(FLAGS_STASHED_IN_APP_IGNORING_IME);
- }
-
- /**
* Returns whether the taskbar should be stashed in the current LauncherState.
*/
public boolean isInStashedLauncherState() {
@@ -1059,11 +1049,6 @@
private void notifyStashChange(boolean visible, boolean stashed) {
mSystemUiProxy.notifyTaskbarStatus(visible, stashed);
setUpTaskbarSystemAction(visible);
- // If stashing taskbar is caused by IME visibility, we could just skip updating rounded
- // corner insets since the rounded corners will be covered by IME during IME is showing and
- // taskbar will be restored back to unstashed when IME is hidden.
- mControllers.taskbarActivityContext.updateInsetRoundedCornerFrame(
- visible && !isStashedInAppIgnoringIme());
mControllers.rotationButtonController.onTaskbarStateChange(visible, stashed);
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index d67dbae..65f449c 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -1063,7 +1063,8 @@
}
@Override
- public void onInitialBindComplete(IntSet boundPages, RunnableList pendingTasks) {
+ public void onInitialBindComplete(IntSet boundPages, RunnableList pendingTasks,
+ int workspaceItemCount, boolean isBindSync) {
pendingTasks.add(() -> {
// This is added in pending task as we need to wait for views to be positioned
// correctly before registering them for the animation.
@@ -1073,7 +1074,7 @@
mLauncherUnfoldAnimationController.updateRegisteredViewsIfNeeded();
}
});
- super.onInitialBindComplete(boundPages, pendingTasks);
+ super.onInitialBindComplete(boundPages, pendingTasks, workspaceItemCount, isBindSync);
}
@Override
@@ -1317,5 +1318,8 @@
writer.println("\nQuickstepLauncher:");
writer.println(prefix + "\tmOrientationState: " + (recentsView == null ? "recentsNull" :
recentsView.getPagedViewOrientedState()));
+ if (recentsView != null) {
+ recentsView.getSplitSelectController().dump(prefix, writer);
+ }
}
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepWidgetHolder.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepWidgetHolder.java
index 36e78fb..39543b0 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepWidgetHolder.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepWidgetHolder.java
@@ -95,7 +95,11 @@
i -> MAIN_EXECUTOR.execute(() ->
sHolders.forEach(h -> h.mAppWidgetRemovedCallback.accept(i))),
() -> MAIN_EXECUTOR.execute(() ->
- sHolders.forEach(h -> h.mProviderChangedListeners.forEach(
+ sHolders.forEach(h ->
+ // Listeners might remove themselves from the list during the
+ // iteration. Creating a copy of the list to avoid exceptions
+ // for concurrent modification.
+ new ArrayList<>(h.mProviderChangedListeners).forEach(
ProviderChangedListener::notifyWidgetProvidersChanged))),
UI_HELPER_EXECUTOR.getLooper());
if (!WidgetsModel.GO_DISABLE_WIDGETS) {
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index b7a29e0..5333cbe 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -177,7 +177,7 @@
protected @Nullable RecentsAnimationController mDeferredCleanupRecentsAnimationController;
protected RecentsAnimationTargets mRecentsAnimationTargets;
protected T mActivity;
- protected Q mRecentsView;
+ protected @Nullable Q mRecentsView;
protected Runnable mGestureEndCallback;
protected MultiStateCallback mStateCallback;
protected boolean mCanceled;
@@ -1895,7 +1895,9 @@
private void invalidateHandlerWithLauncher() {
endLauncherTransitionController();
- mRecentsView.onGestureAnimationEnd();
+ if (mRecentsView != null) {
+ mRecentsView.onGestureAnimationEnd();
+ }
resetLauncherListeners();
}
@@ -1922,7 +1924,9 @@
private void resetLauncherListeners() {
mActivity.getRootView().setOnApplyWindowInsetsListener(null);
- mRecentsView.removeOnScrollChangedListener(mOnRecentsScrollListener);
+ if (mRecentsView != null) {
+ mRecentsView.removeOnScrollChangedListener(mOnRecentsScrollListener);
+ }
}
private void resetStateForAnimationCancel() {
@@ -1981,8 +1985,10 @@
private boolean updateThumbnail(int runningTaskId, boolean refreshView) {
boolean finishTransitionPosted = false;
final TaskView taskView;
- if (mGestureState.getEndTarget() == HOME || mGestureState.getEndTarget() == NEW_TASK
- || mGestureState.getEndTarget() == ALL_APPS) {
+ if (mGestureState.getEndTarget() == HOME
+ || mGestureState.getEndTarget() == NEW_TASK
+ || mGestureState.getEndTarget() == ALL_APPS
+ || mRecentsView == null) {
// Capture the screenshot before finishing the transition to home or quickswitching to
// ensure it's taken in the correct orientation, but no need to update the thumbnail.
taskView = null;
@@ -2135,7 +2141,7 @@
protected void startNewTask(Consumer<Boolean> resultCallback) {
// Launch the task user scrolled to (mRecentsView.getNextPage()).
if (!mCanceled) {
- TaskView nextTask = mRecentsView.getNextPageTaskView();
+ TaskView nextTask = mRecentsView == null ? null : mRecentsView.getNextPageTaskView();
if (nextTask != null) {
Task.TaskKey nextTaskKey = nextTask.getTask().key;
int taskId = nextTaskKey.id;
@@ -2237,7 +2243,8 @@
return;
}
RemoteAnimationTarget taskTarget = taskTargetOptional.get();
- TaskView taskView = mRecentsView.getTaskViewByTaskId(taskTarget.taskId);
+ TaskView taskView = mRecentsView == null
+ ? null : mRecentsView.getTaskViewByTaskId(taskTarget.taskId);
if (taskView == null || !taskView.getThumbnail().shouldShowSplashView()) {
finishRecentsAnimationOnTasksAppeared(null /* onFinishComplete */);
return;
@@ -2296,9 +2303,11 @@
* resume if we finish the controller.
*/
protected int getLastAppearedTaskIndex() {
- return mGestureState.getLastAppearedTaskId() != -1
- ? mRecentsView.getTaskIndexForId(mGestureState.getLastAppearedTaskId())
- : mRecentsView.getRunningTaskIndex();
+ return mRecentsView == null
+ ? -1
+ : mGestureState.getLastAppearedTaskId() != -1
+ ? mRecentsView.getTaskIndexForId(mGestureState.getLastAppearedTaskId())
+ : mRecentsView.getRunningTaskIndex();
}
/**
diff --git a/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java b/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
index 4e892e2..ab3ae9f 100644
--- a/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
+++ b/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
@@ -149,6 +149,11 @@
case TestProtocol.REQUEST_DISABLE_TRANSIENT_TASKBAR:
enableTransientTaskbar(false);
return response;
+
+ case TestProtocol.REQUEST_SHELL_DRAG_READY:
+ response.putBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD,
+ SystemUiProxy.INSTANCE.get(mContext).isDragAndDropReady());
+ return response;
}
return super.call(method, arg, extras);
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java
index 616ddef..89f06f6 100644
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.java
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java
@@ -70,6 +70,7 @@
import com.android.systemui.unfold.progress.IUnfoldTransitionListener;
import com.android.wm.shell.back.IBackAnimation;
import com.android.wm.shell.desktopmode.IDesktopMode;
+import com.android.wm.shell.draganddrop.IDragAndDrop;
import com.android.wm.shell.onehanded.IOneHanded;
import com.android.wm.shell.pip.IPip;
import com.android.wm.shell.pip.IPipAnimationListener;
@@ -128,6 +129,7 @@
private IBinder mOriginalTransactionToken = null;
private IOnBackInvokedCallback mBackToLauncherCallback;
private IRemoteAnimationRunner mBackToLauncherRunner;
+ private IDragAndDrop mDragAndDrop;
// Used to dedupe calls to SystemUI
private int mLastShelfHeight;
@@ -203,7 +205,7 @@
IStartingWindow startingWindow, IRecentTasks recentTasks,
ISysuiUnlockAnimationController sysuiUnlockAnimationController,
IBackAnimation backAnimation, IDesktopMode desktopMode,
- IUnfoldAnimation unfoldAnimation) {
+ IUnfoldAnimation unfoldAnimation, IDragAndDrop dragAndDrop) {
unlinkToDeath();
mSystemUiProxy = proxy;
mPip = pip;
@@ -216,6 +218,7 @@
mBackAnimation = backAnimation;
mDesktopMode = desktopMode;
mUnfoldAnimation = unfoldAnimation;
+ mDragAndDrop = dragAndDrop;
linkToDeath();
// re-attach the listeners once missing due to setProxy has not been initialized yet.
setPipAnimationListener(mPipAnimationListener);
@@ -230,7 +233,7 @@
}
public void clearProxy() {
- setProxy(null, null, null, null, null, null, null, null, null, null, null);
+ setProxy(null, null, null, null, null, null, null, null, null, null, null, null);
}
// TODO(141886704): Find a way to remove this
@@ -1099,6 +1102,11 @@
Log.e(TAG, "Failed call setUnfoldAnimationListener", e);
}
}
+
+ //
+ // Recents
+ //
+
/**
* Starts the recents activity. The caller should manage the thread on which this is called.
*/
@@ -1131,10 +1139,30 @@
try {
mRecentTasks.startRecentsTransition(mRecentsPendingIntent, intent, optsBundle,
mContext.getIApplicationThread(), runner);
+ return true;
} catch (RemoteException e) {
Log.e(TAG, "Error starting recents via shell", e);
return false;
}
- return true;
+ }
+
+ //
+ // Drag and drop
+ //
+
+ /**
+ * For testing purposes. Returns `true` only if the shell drop target has shown and
+ * drawn and is ready to handle drag events and the subsequent drop.
+ */
+ public boolean isDragAndDropReady() {
+ if (mDragAndDrop == null) {
+ return false;
+ }
+ try {
+ return mDragAndDrop.isReadyToHandleDrag();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error querying drag state", e);
+ return false;
+ }
}
}
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index 038c674..6ea171e 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -43,6 +43,7 @@
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_TRACING_ENABLED;
import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_BACK_ANIMATION;
import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_DESKTOP_MODE;
+import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_DRAG_AND_DROP;
import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_ONE_HANDED;
import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_PIP;
import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_RECENT_TASKS;
@@ -125,6 +126,7 @@
import com.android.systemui.unfold.progress.IUnfoldAnimation;
import com.android.wm.shell.back.IBackAnimation;
import com.android.wm.shell.desktopmode.IDesktopMode;
+import com.android.wm.shell.draganddrop.IDragAndDrop;
import com.android.wm.shell.onehanded.IOneHanded;
import com.android.wm.shell.pip.IPip;
import com.android.wm.shell.recents.IRecentTasks;
@@ -185,11 +187,13 @@
bundle.getBinder(KEY_EXTRA_SHELL_DESKTOP_MODE));
IUnfoldAnimation unfoldTransition = IUnfoldAnimation.Stub.asInterface(
bundle.getBinder(KEY_EXTRA_UNFOLD_ANIMATION_FORWARDER));
+ IDragAndDrop dragAndDrop = IDragAndDrop.Stub.asInterface(
+ bundle.getBinder(KEY_EXTRA_SHELL_DRAG_AND_DROP));
MAIN_EXECUTOR.execute(() -> {
SystemUiProxy.INSTANCE.get(TouchInteractionService.this).setProxy(proxy, pip,
splitscreen, onehanded, shellTransitions, startingWindow,
recentTasks, launcherUnlockAnimationController, backAnimation, desktopMode,
- unfoldTransition);
+ unfoldTransition, dragAndDrop);
TouchInteractionService.this.initInputMonitor("TISBinder#onInitialize()");
preloadOverview(true /* fromInit */);
});
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
index cd98e7a..64165b6 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
@@ -61,14 +61,6 @@
mTarget = activity.getDragLayer();
mTarget.getLocationOnScreen(mLocationOnScreen);
-
- // When Overview is launched via meta+tab or swipe up from an app,
- // the touch mode somehow is not changed to false by the Android framework.
- // The subsequent key events (e.g. DPAD_LEFT, DPAD_RIGHT) can only be dispatched to
- // focused views, while focus can only be requested in
- // {@link View#requestFocusNoSearch(int, Rect)} when touch mode is false. To note,
- // here we launch overview with live tile.
- mActivity.getRootView().getViewRootImpl().touchModeChanged(false);
}
@Override
diff --git a/quickstep/src/com/android/quickstep/util/SplitSelectDataHolder.kt b/quickstep/src/com/android/quickstep/util/SplitSelectDataHolder.kt
new file mode 100644
index 0000000..ebea58c
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/SplitSelectDataHolder.kt
@@ -0,0 +1,364 @@
+/*
+ * Copyright (C) 2023 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.annotation.IntDef
+import android.app.ActivityManager.RunningTaskInfo
+import android.app.ActivityTaskManager.INVALID_TASK_ID
+import android.app.PendingIntent
+import android.content.Context
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.content.pm.ShortcutInfo
+import android.os.UserHandle
+import android.util.Log
+import com.android.internal.annotations.VisibleForTesting
+import com.android.launcher3.logging.StatsLogManager.EventEnum
+import com.android.launcher3.model.data.ItemInfo
+import com.android.launcher3.shortcuts.ShortcutKey
+import com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_UNDEFINED
+import com.android.launcher3.util.SplitConfigurationOptions.StagePosition
+import com.android.launcher3.util.SplitConfigurationOptions.getOppositeStagePosition
+import com.android.quickstep.util.SplitSelectDataHolder.Companion.SplitLaunchType
+import java.io.PrintWriter
+
+/**
+ * Holds/transforms/signs/seals/delivers information for the transient state of the user
+ * selecting a first app to start split with and then choosing a second app.
+ * This class DOES NOT associate itself with drag-and-drop split screen starts because they come
+ * from the bad part of town.
+ *
+ * After setting the correct fields for initial/second.* variables, this converts them into the
+ * correct [PendingIntent] and [ShortcutInfo] objects where applicable and sends the necessary
+ * data back via [getSplitLaunchData].
+ * [SplitLaunchType] indicates the type of tasks/apps/intents being launched given the provided
+ * state
+ */
+class SplitSelectDataHolder(
+ val context: Context
+) {
+ val TAG = SplitSelectDataHolder::class.simpleName
+
+ /**
+ * Order of the constant indicates the order of which task/app was selected.
+ * Ex. SPLIT_TASK_SHORTCUT means primary split app identified by task, secondary is shortcut
+ * SPLIT_SHORTCUT_TASK means primary split app is determined by shortcut, secondary is task
+ */
+ companion object {
+ @IntDef(SPLIT_TASK_TASK, SPLIT_TASK_PENDINGINTENT, SPLIT_TASK_SHORTCUT,
+ SPLIT_PENDINGINTENT_TASK, SPLIT_PENDINGINTENT_PENDINGINTENT, SPLIT_SHORTCUT_TASK)
+ @Retention(AnnotationRetention.SOURCE)
+ annotation class SplitLaunchType
+
+ const val SPLIT_TASK_TASK = 0
+ const val SPLIT_TASK_PENDINGINTENT = 1
+ const val SPLIT_TASK_SHORTCUT = 2
+ const val SPLIT_PENDINGINTENT_TASK = 3
+ const val SPLIT_SHORTCUT_TASK = 4
+ const val SPLIT_PENDINGINTENT_PENDINGINTENT = 5
+ }
+
+
+ @StagePosition
+ private var initialStagePosition: Int = STAGE_POSITION_UNDEFINED
+ private var initialTaskId: Int = INVALID_TASK_ID
+ private var secondTaskId: Int = INVALID_TASK_ID
+ private var initialUser: UserHandle? = null
+ private var secondUser: UserHandle? = null
+ private var initialIntent: Intent? = null
+ private var secondIntent: Intent? = null
+ private var secondPendingIntent: PendingIntent? = null
+ private var itemInfo: ItemInfo? = null
+ private var splitEvent: EventEnum? = null
+ private var initialShortcut: ShortcutInfo? = null
+ private var secondShortcut: ShortcutInfo? = null
+ private var initialPendingIntent: PendingIntent? = null
+
+ /**
+ * @param alreadyRunningTask if set to [android.app.ActivityTaskManager.INVALID_TASK_ID]
+ * then @param intent will be used to launch the initial task
+ * @param intent will be ignored if @param alreadyRunningTask is set
+ */
+ fun setInitialTaskSelect(intent: Intent?, @StagePosition stagePosition: Int,
+ itemInfo: ItemInfo?, splitEvent: EventEnum?,
+ alreadyRunningTask: Int) {
+ if (alreadyRunningTask != INVALID_TASK_ID) {
+ initialTaskId = alreadyRunningTask
+ } else {
+ initialIntent = intent!!
+ initialUser = itemInfo!!.user
+ }
+ setInitialData(stagePosition, splitEvent, itemInfo)
+ }
+
+ /**
+ * To be called after first task selected from using a split shortcut from the fullscreen
+ * running app.
+ */
+ fun setInitialTaskSelect(info: RunningTaskInfo,
+ @StagePosition stagePosition: Int, itemInfo: ItemInfo?,
+ splitEvent: EventEnum?) {
+ initialTaskId = info.taskId
+ setInitialData(stagePosition, splitEvent, itemInfo)
+ }
+
+ private fun setInitialData(@StagePosition stagePosition: Int,
+ event: EventEnum?, item: ItemInfo?) {
+ itemInfo = item
+ initialStagePosition = stagePosition
+ splitEvent = event
+ }
+
+ /**
+ * To be called as soon as user selects the second task (even if animations aren't complete)
+ * @param taskId The second task that will be launched.
+ */
+ fun setSecondTask(taskId: Int) {
+ secondTaskId = taskId
+ }
+
+ /**
+ * To be called as soon as user selects the second app (even if animations aren't complete)
+ * @param intent The second intent that will be launched.
+ * @param user The user of that intent.
+ */
+ fun setSecondTask(intent: Intent, user: UserHandle) {
+ secondIntent = intent
+ secondUser = user
+ }
+
+ /**
+ * To be called as soon as user selects the second app (even if animations aren't complete)
+ * Sets [secondUser] from that of the pendingIntent
+ * @param pendingIntent The second PendingIntent that will be launched.
+ */
+ fun setSecondTask(pendingIntent: PendingIntent) {
+ secondPendingIntent = pendingIntent
+ secondUser = pendingIntent.creatorUserHandle!!
+ }
+
+ private fun getShortcutInfo(intent: Intent?, user: UserHandle?): ShortcutInfo? {
+ if (intent?.getPackage() == null) {
+ return null
+ }
+ val shortcutId = intent.getStringExtra(ShortcutKey.EXTRA_SHORTCUT_ID)
+ ?: return null
+ try {
+ val context: Context = context.createPackageContextAsUser(
+ intent.getPackage(), 0 /* flags */, user)
+ return ShortcutInfo.Builder(context, shortcutId).build()
+ } catch (e: PackageManager.NameNotFoundException) {
+ Log.w(TAG, "Failed to create a ShortcutInfo for " + intent.getPackage())
+ }
+ return null
+ }
+
+ /**
+ * Converts intents to pendingIntents, associating the [user] with the intent if provided
+ */
+ private fun getPendingIntent(intent: Intent?, user: UserHandle?): PendingIntent? {
+ if (intent != initialIntent && intent != secondIntent) {
+ throw IllegalStateException("Invalid intent to convert to PendingIntent")
+ }
+
+ return if (intent == null) {
+ null
+ } else if (user != null) {
+ PendingIntent.getActivityAsUser(context, 0, intent,
+ PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_ALLOW_UNSAFE_IMPLICIT_INTENT,
+ null /* options */, user)
+ } else {
+ PendingIntent.getActivity(context, 0, intent,
+ PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_ALLOW_UNSAFE_IMPLICIT_INTENT)
+ }
+ }
+
+ /**
+ * @return [SplitLaunchData] with the necessary fields populated as determined by
+ * [SplitLaunchData.splitLaunchType]
+ */
+ fun getSplitLaunchData() : SplitLaunchData {
+ // Convert all intents to shortcut infos to see if determine if we launch shortcut or intent
+ convertIntentsToFinalTypes()
+ val splitLaunchType = getSplitLaunchType()
+ if (splitLaunchType == SPLIT_TASK_PENDINGINTENT || splitLaunchType == SPLIT_TASK_SHORTCUT) {
+ // need to get opposite stage position
+ initialStagePosition = getOppositeStagePosition(initialStagePosition)
+ }
+
+ return SplitLaunchData(
+ splitLaunchType,
+ initialTaskId,
+ secondTaskId,
+ initialPendingIntent,
+ secondPendingIntent,
+ initialShortcut,
+ secondShortcut,
+ itemInfo,
+ splitEvent,
+ initialStagePosition)
+ }
+
+ /**
+ * Converts our [initialIntent] and [secondIntent] into shortcuts and pendingIntents, if
+ * possible.
+ *
+ * Note that both [initialIntent] and [secondIntent] will be nullified on method return
+ *
+ * One caveat is that if [secondPendingIntent] is set, we will use that and *not* attempt to
+ * convert [secondIntent]
+ */
+ private fun convertIntentsToFinalTypes() {
+ initialShortcut = getShortcutInfo(initialIntent, initialUser)
+ initialPendingIntent = getPendingIntent(initialIntent, initialUser)
+ initialIntent = null
+
+ // Only one of the two is currently allowed (secondPendingIntent directly set for widgets)
+ if (secondIntent != null && secondPendingIntent != null) {
+ throw IllegalStateException("Both secondIntent and secondPendingIntent non-null")
+ }
+ // If secondPendingIntent already set, no need to convert. Prioritize using that
+ if (secondPendingIntent != null) {
+ secondIntent = null
+ return
+ }
+
+ secondShortcut = getShortcutInfo(secondIntent, secondUser)
+ secondPendingIntent = getPendingIntent(secondIntent, secondUser)
+ secondIntent = null
+ }
+
+ /**
+ * Only valid data fields at this point should be tasks, shortcuts, or pendingIntents
+ * Intents need to be converted in [convertIntentsToFinalTypes] prior to calling this method
+ */
+ @VisibleForTesting
+ @SplitLaunchType
+ fun getSplitLaunchType(): Int {
+ if (initialIntent != null || secondIntent != null) {
+ throw IllegalStateException("Intents need to be converted")
+ }
+
+ // Prioritize task launches first
+ if (initialTaskId != INVALID_TASK_ID) {
+ if (secondTaskId != INVALID_TASK_ID) {
+ return SPLIT_TASK_TASK
+ }
+ if (secondShortcut != null) {
+ return SPLIT_TASK_SHORTCUT
+ }
+ if (secondPendingIntent != null) {
+ return SPLIT_TASK_PENDINGINTENT
+ }
+ }
+
+ if (secondTaskId != INVALID_TASK_ID) {
+ if (initialShortcut != null) {
+ return SPLIT_SHORTCUT_TASK
+ }
+ if (initialPendingIntent != null) {
+ return SPLIT_PENDINGINTENT_TASK
+ }
+ }
+
+ // All task+shortcut combinations are handled above, only launch left is with multiple
+ // intents (and respective shortcut infos, if necessary)
+ if (initialPendingIntent != null && secondPendingIntent != null) {
+ return SPLIT_PENDINGINTENT_PENDINGINTENT
+ }
+ throw IllegalStateException("Unidentified split launch type")
+ }
+
+ data class SplitLaunchData(
+ @SplitLaunchType
+ val splitLaunchType: Int,
+ var initialTaskId: Int = INVALID_TASK_ID,
+ var secondTaskId: Int = INVALID_TASK_ID,
+ var initialPendingIntent: PendingIntent? = null,
+ var secondPendingIntent: PendingIntent? = null,
+ var initialShortcut: ShortcutInfo? = null,
+ var secondShortcut: ShortcutInfo? = null,
+ var itemInfo: ItemInfo? = null,
+ var splitEvent: EventEnum? = null,
+ val initialStagePosition: Int = STAGE_POSITION_UNDEFINED
+ )
+
+ /**
+ * @return `true` if first task has been selected and waiting for the second task to be
+ * chosen
+ */
+ fun isSplitSelectActive(): Boolean {
+ return isInitialTaskIntentSet() && !isSecondTaskIntentSet()
+ }
+
+ /**
+ * @return `true` if the first and second task have been chosen and split is waiting to
+ * be launched
+ */
+ fun isBothSplitAppsConfirmed(): Boolean {
+ return isInitialTaskIntentSet() && isSecondTaskIntentSet()
+ }
+
+ private fun isInitialTaskIntentSet(): Boolean {
+ return initialTaskId != INVALID_TASK_ID || initialIntent != null
+ }
+
+ fun getInitialTaskId(): Int {
+ return initialTaskId
+ }
+
+ fun getSecondTaskId(): Int {
+ return secondTaskId
+ }
+
+ private fun isSecondTaskIntentSet(): Boolean {
+ return secondTaskId != INVALID_TASK_ID || secondIntent != null
+ || secondPendingIntent != null
+ }
+
+ fun resetState() {
+ initialStagePosition = STAGE_POSITION_UNDEFINED
+ initialTaskId = INVALID_TASK_ID
+ secondTaskId = INVALID_TASK_ID
+ initialUser = null
+ secondUser = null
+ initialIntent = null
+ secondIntent = null
+ secondPendingIntent = null
+ itemInfo = null
+ splitEvent = null
+ initialShortcut = null
+ secondShortcut = null
+ }
+
+ fun dump(prefix: String, writer: PrintWriter) {
+ writer.println("$prefix ${javaClass.simpleName}")
+ writer.println("$prefix\tinitialStagePosition= $initialStagePosition")
+ writer.println("$prefix\tinitialTaskId= $initialTaskId")
+ writer.println("$prefix\tsecondTaskId= $secondTaskId")
+ writer.println("$prefix\tinitialUser= $initialUser")
+ writer.println("$prefix\tsecondUser= $secondUser")
+ writer.println("$prefix\tinitialIntent= $initialIntent")
+ writer.println("$prefix\tsecondIntent= $secondIntent")
+ writer.println("$prefix\tsecondPendingIntent= $secondPendingIntent")
+ writer.println("$prefix\titemInfo= $itemInfo")
+ writer.println("$prefix\tsplitEvent= $splitEvent")
+ writer.println("$prefix\tinitialShortcut= $initialShortcut")
+ writer.println("$prefix\tsecondShortcut= $secondShortcut")
+ }
+}
\ No newline at end of file
diff --git a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
index 8b21115..acc3ba1 100644
--- a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
+++ b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
@@ -24,6 +24,12 @@
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.SplitConfigurationOptions.DEFAULT_SPLIT_RATIO;
import static com.android.launcher3.util.SplitConfigurationOptions.getOppositeStagePosition;
+import static com.android.quickstep.util.SplitSelectDataHolder.SPLIT_PENDINGINTENT_PENDINGINTENT;
+import static com.android.quickstep.util.SplitSelectDataHolder.SPLIT_PENDINGINTENT_TASK;
+import static com.android.quickstep.util.SplitSelectDataHolder.SPLIT_SHORTCUT_TASK;
+import static com.android.quickstep.util.SplitSelectDataHolder.SPLIT_TASK_PENDINGINTENT;
+import static com.android.quickstep.util.SplitSelectDataHolder.SPLIT_TASK_SHORTCUT;
+import static com.android.quickstep.util.SplitSelectDataHolder.SPLIT_TASK_TASK;
import android.annotation.NonNull;
import android.app.ActivityManager;
@@ -34,6 +40,7 @@
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ShortcutInfo;
+import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
@@ -51,6 +58,7 @@
import androidx.annotation.Nullable;
import com.android.internal.logging.InstanceId;
+import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.shortcuts.ShortcutKey;
@@ -71,6 +79,7 @@
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.system.RemoteAnimationRunnerCompat;
+import java.io.PrintWriter;
import java.util.function.Consumer;
/**
@@ -85,6 +94,7 @@
private final RecentsModel mRecentTasksModel;
private final SplitAnimationController mSplitAnimationController;
private final AppPairsController mAppPairsController;
+ private final SplitSelectDataHolder mSplitSelectDataHolder;
private StatsLogManager mStatsLogManager;
private final SystemUiProxy mSystemUiProxy;
private final StateManager mStateManager;
@@ -137,6 +147,7 @@
mRecentTasksModel = recentsModel;
mSplitAnimationController = new SplitAnimationController(this);
mAppPairsController = new AppPairsController(context, this);
+ mSplitSelectDataHolder = new SplitSelectDataHolder(mContext);
}
/**
@@ -155,6 +166,11 @@
}
setInitialData(stagePosition, splitEvent, itemInfo);
+
+ if (FeatureFlags.ENABLE_SPLIT_LAUNCH_DATA_REFACTOR.get()) {
+ mSplitSelectDataHolder.setInitialTaskSelect(intent, stagePosition, itemInfo, splitEvent,
+ alreadyRunningTask);
+ }
}
/**
@@ -166,6 +182,10 @@
StatsLogManager.EventEnum splitEvent) {
mInitialTaskId = info.taskId;
setInitialData(stagePosition, splitEvent, itemInfo);
+
+ if (FeatureFlags.ENABLE_SPLIT_LAUNCH_DATA_REFACTOR.get()) {
+ mSplitSelectDataHolder.setInitialTaskSelect(info, stagePosition, itemInfo, splitEvent);
+ }
}
private void setInitialData(@StagePosition int stagePosition,
@@ -243,6 +263,10 @@
*/
public void setSecondTask(Task task) {
mSecondTaskId = task.key.id;
+
+ if (FeatureFlags.ENABLE_SPLIT_LAUNCH_DATA_REFACTOR.get()) {
+ mSplitSelectDataHolder.setSecondTask(task.key.id);
+ }
}
/**
@@ -253,6 +277,10 @@
public void setSecondTask(Intent intent, UserHandle user) {
mSecondTaskIntent = intent;
mSecondUser = user;
+
+ if (FeatureFlags.ENABLE_SPLIT_LAUNCH_DATA_REFACTOR.get()) {
+ mSplitSelectDataHolder.setSecondTask(intent, user);
+ }
}
/**
@@ -263,6 +291,10 @@
public void setSecondTask(PendingIntent pendingIntent) {
mSecondPendingIntent = pendingIntent;
mSecondUser = pendingIntent.getCreatorUserHandle();
+
+ if (FeatureFlags.ENABLE_SPLIT_LAUNCH_DATA_REFACTOR.get()) {
+ mSplitSelectDataHolder.setSecondTask(pendingIntent);
+ }
}
/**
@@ -285,6 +317,12 @@
*/
public void launchTasks(int taskId1, int taskId2, @StagePosition int stagePosition,
Consumer<Boolean> callback, boolean freezeTaskList, float splitRatio) {
+ if (FeatureFlags.ENABLE_SPLIT_LAUNCH_DATA_REFACTOR.get()) {
+ mSplitSelectDataHolder.setInitialTaskSelect(null /*intent*/,
+ stagePosition, null /*itemInfo*/, null /*splitEvent*/,
+ taskId1);
+ mSplitSelectDataHolder.setSecondTask(taskId2);
+ }
launchTasks(taskId1, null /* intent1 */, taskId2, null /* intent2 */, stagePosition,
callback, freezeTaskList, splitRatio, null);
}
@@ -305,6 +343,11 @@
@Nullable InstanceId shellInstanceId) {
TestLogging.recordEvent(
TestProtocol.SEQUENCE_MAIN, "launchSplitTasks");
+ if (FeatureFlags.ENABLE_SPLIT_LAUNCH_DATA_REFACTOR.get()) {
+ launchTasksRefactored(callback, freezeTaskList, splitRatio, shellInstanceId);
+ return;
+ }
+
final ActivityOptions options1 = ActivityOptions.makeBasic();
if (freezeTaskList) {
options1.setFreezeRecentTasksReordering();
@@ -367,6 +410,101 @@
}
}
+ private void launchTasksRefactored(Consumer<Boolean> callback, boolean freezeTaskList,
+ float splitRatio, @Nullable InstanceId shellInstanceId) {
+ final ActivityOptions options1 = ActivityOptions.makeBasic();
+ if (freezeTaskList) {
+ options1.setFreezeRecentTasksReordering();
+ }
+
+ SplitSelectDataHolder.SplitLaunchData launchData =
+ mSplitSelectDataHolder.getSplitLaunchData();
+ int firstTaskId = launchData.getInitialTaskId();
+ int secondTaskId = launchData.getSecondTaskId();
+ ShortcutInfo firstShortcut = launchData.getInitialShortcut();
+ ShortcutInfo secondShortcut = launchData.getSecondShortcut();
+ PendingIntent firstPI = launchData.getInitialPendingIntent();
+ PendingIntent secondPI = launchData.getSecondPendingIntent();
+ int initialStagePosition = launchData.getInitialStagePosition();
+ Bundle optionsBundle = options1.toBundle();
+
+ if (TaskAnimationManager.ENABLE_SHELL_TRANSITIONS) {
+ final RemoteSplitLaunchTransitionRunner animationRunner =
+ new RemoteSplitLaunchTransitionRunner(firstTaskId, secondTaskId, callback);
+ final RemoteTransition remoteTransition = new RemoteTransition(animationRunner,
+ ActivityThread.currentActivityThread().getApplicationThread(),
+ "LaunchSplitPair");
+ switch (launchData.getSplitLaunchType()) {
+ case SPLIT_TASK_TASK ->
+ mSystemUiProxy.startTasks(firstTaskId, optionsBundle, secondTaskId,
+ null /* options2 */, initialStagePosition, splitRatio,
+ remoteTransition, shellInstanceId);
+
+ case SPLIT_TASK_PENDINGINTENT ->
+ mSystemUiProxy.startIntentAndTask(secondPI, optionsBundle, firstTaskId,
+ null /*options2*/, initialStagePosition, splitRatio,
+ remoteTransition, shellInstanceId);
+
+ case SPLIT_TASK_SHORTCUT ->
+ mSystemUiProxy.startShortcutAndTask(secondShortcut, optionsBundle,
+ firstTaskId, null /*options2*/, initialStagePosition, splitRatio,
+ remoteTransition, shellInstanceId);
+
+ case SPLIT_PENDINGINTENT_TASK ->
+ mSystemUiProxy.startIntentAndTask(firstPI, optionsBundle, secondTaskId,
+ null /*options2*/, initialStagePosition, splitRatio,
+ remoteTransition, shellInstanceId);
+
+ case SPLIT_PENDINGINTENT_PENDINGINTENT ->
+ mSystemUiProxy.startIntents(firstPI, firstShortcut, optionsBundle, secondPI,
+ secondShortcut, null /*options2*/, initialStagePosition, splitRatio,
+ remoteTransition, shellInstanceId);
+
+ case SPLIT_SHORTCUT_TASK ->
+ mSystemUiProxy.startShortcutAndTask(firstShortcut, optionsBundle,
+ secondTaskId, null /*options2*/, initialStagePosition, splitRatio,
+ remoteTransition, shellInstanceId);
+ }
+ } else {
+ final RemoteSplitLaunchAnimationRunner animationRunner =
+ new RemoteSplitLaunchAnimationRunner(firstTaskId, secondTaskId, callback);
+ final RemoteAnimationAdapter adapter = new RemoteAnimationAdapter(
+ animationRunner, 300, 150,
+ ActivityThread.currentActivityThread().getApplicationThread());
+ switch (launchData.getSplitLaunchType()) {
+ case SPLIT_TASK_TASK ->
+ mSystemUiProxy.startTasksWithLegacyTransition(firstTaskId, optionsBundle,
+ secondTaskId, null /* options2 */, initialStagePosition,
+ splitRatio, adapter, shellInstanceId);
+
+ case SPLIT_TASK_PENDINGINTENT ->
+ mSystemUiProxy.startIntentAndTaskWithLegacyTransition(secondPI,
+ optionsBundle, firstTaskId, null /*options2*/, initialStagePosition,
+ splitRatio, adapter, shellInstanceId);
+
+ case SPLIT_TASK_SHORTCUT ->
+ mSystemUiProxy.startShortcutAndTaskWithLegacyTransition(secondShortcut,
+ optionsBundle, firstTaskId, null /*options2*/, initialStagePosition,
+ splitRatio, adapter, shellInstanceId);
+
+ case SPLIT_PENDINGINTENT_TASK ->
+ mSystemUiProxy.startIntentAndTaskWithLegacyTransition(firstPI,
+ optionsBundle, secondTaskId, null /*options2*/,
+ initialStagePosition, splitRatio, adapter, shellInstanceId);
+
+ case SPLIT_PENDINGINTENT_PENDINGINTENT ->
+ mSystemUiProxy.startIntentsWithLegacyTransition(firstPI, firstShortcut,
+ optionsBundle, secondPI, secondShortcut, null /*options2*/,
+ initialStagePosition, splitRatio, adapter, shellInstanceId);
+
+ case SPLIT_SHORTCUT_TASK ->
+ mSystemUiProxy.startShortcutAndTaskWithLegacyTransition(firstShortcut,
+ optionsBundle, secondTaskId, null /*options2*/,
+ initialStagePosition, splitRatio, adapter, shellInstanceId);
+ }
+ }
+ }
+
private void launchIntentOrShortcut(Intent intent, UserHandle user, ActivityOptions options1,
int taskId, @StagePosition int stagePosition, float splitRatio,
RemoteTransition remoteTransition, @Nullable InstanceId shellInstanceId) {
@@ -572,6 +710,9 @@
* To be called if split select was cancelled
*/
public void resetState() {
+ if (FeatureFlags.ENABLE_SPLIT_LAUNCH_DATA_REFACTOR.get()) {
+ mSplitSelectDataHolder.resetState();
+ }
mInitialTaskId = INVALID_TASK_ID;
mInitialTaskIntent = null;
mSecondTaskId = INVALID_TASK_ID;
@@ -593,7 +734,11 @@
* chosen
*/
public boolean isSplitSelectActive() {
- return isInitialTaskIntentSet() && !isSecondTaskIntentSet();
+ if (FeatureFlags.ENABLE_SPLIT_LAUNCH_DATA_REFACTOR.get()) {
+ return mSplitSelectDataHolder.isSplitSelectActive();
+ } else {
+ return isInitialTaskIntentSet() && !isSecondTaskIntentSet();
+ }
}
/**
@@ -601,7 +746,11 @@
* be launched
*/
public boolean isBothSplitAppsConfirmed() {
- return isInitialTaskIntentSet() && isSecondTaskIntentSet();
+ if (FeatureFlags.ENABLE_SPLIT_LAUNCH_DATA_REFACTOR.get()) {
+ return mSplitSelectDataHolder.isBothSplitAppsConfirmed();
+ } else {
+ return isInitialTaskIntentSet() && isSecondTaskIntentSet();
+ }
}
private boolean isInitialTaskIntentSet() {
@@ -609,11 +758,19 @@
}
public int getInitialTaskId() {
- return mInitialTaskId;
+ if (FeatureFlags.ENABLE_SPLIT_LAUNCH_DATA_REFACTOR.get()) {
+ return mSplitSelectDataHolder.getInitialTaskId();
+ } else {
+ return mInitialTaskId;
+ }
}
public int getSecondTaskId() {
- return mSecondTaskId;
+ if (FeatureFlags.ENABLE_SPLIT_LAUNCH_DATA_REFACTOR.get()) {
+ return mSplitSelectDataHolder.getSecondTaskId();
+ } else {
+ return mSecondTaskId;
+ }
}
private boolean isSecondTaskIntentSet() {
@@ -632,4 +789,10 @@
public AppPairsController getAppPairsController() {
return mAppPairsController;
}
+
+ public void dump(String prefix, PrintWriter writer) {
+ if (mSplitSelectDataHolder != null) {
+ mSplitSelectDataHolder.dump(prefix, writer);
+ }
+ }
}
diff --git a/res/drawable/ic_note_taking_widget_category.xml b/res/drawable/ic_note_taking_widget_category.xml
index 2b59157..96cc523 100644
--- a/res/drawable/ic_note_taking_widget_category.xml
+++ b/res/drawable/ic_note_taking_widget_category.xml
@@ -20,14 +20,9 @@
android:viewportHeight="48">
<path
- android:fillColor="#F5F5F5"
- android:pathData="M 0 0 H 48 V 48 H 0 V 0 Z" />
- <group>
- <path
- android:fillColor="#0B57D0"
- android:pathData="M48 24C48 10.7452 37.2548 0 24 0C10.7452 0 0 10.7452 0 24C0 37.2548 10.7452 48 24 48C37.2548 48 48 37.2548 48 24Z" />
- <path
- android:fillColor="#ffffff"
- android:pathData="M32.892 16.8L31.2 15.108C30.756 14.652 30.144 14.4 29.508 14.4C28.872 14.4 28.26 14.652 27.816 15.108L18.468 24.456L15.708 27.216L14.448 32.28C14.412 32.352 14.4 32.448 14.4 32.532C14.4 33.12 14.88 33.6 15.468 33.6C15.552 33.6 15.648 33.588 15.732 33.564L20.796 32.304L23.556 29.544L32.904 20.196C33.348 19.74 33.6 19.128 33.6 18.492C33.6 17.856 33.348 17.244 32.892 16.8ZM21.852 27.852L20.652 29.052L18.96 27.36L20.16 26.16L29.508 16.8L31.2 18.492L21.852 27.852Z" />
- </group>
+ android:fillColor="#0B57D0"
+ android:pathData="M48 24C48 10.7452 37.2548 0 24 0C10.7452 0 0 10.7452 0 24C0 37.2548 10.7452 48 24 48C37.2548 48 48 37.2548 48 24Z" />
+ <path
+ android:fillColor="#ffffff"
+ android:pathData="M32.892 16.8L31.2 15.108C30.756 14.652 30.144 14.4 29.508 14.4C28.872 14.4 28.26 14.652 27.816 15.108L18.468 24.456L15.708 27.216L14.448 32.28C14.412 32.352 14.4 32.448 14.4 32.532C14.4 33.12 14.88 33.6 15.468 33.6C15.552 33.6 15.648 33.588 15.732 33.564L20.796 32.304L23.556 29.544L32.904 20.196C33.348 19.74 33.6 19.128 33.6 18.492C33.6 17.856 33.348 17.244 32.892 16.8ZM21.852 27.852L20.652 29.052L18.96 27.36L20.16 26.16L29.508 16.8L31.2 18.492L21.852 27.852Z" />
</vector>
\ No newline at end of file
diff --git a/src/com/android/launcher3/BaseDraggingActivity.java b/src/com/android/launcher3/BaseDraggingActivity.java
index 6f3e948..45b03c2 100644
--- a/src/com/android/launcher3/BaseDraggingActivity.java
+++ b/src/com/android/launcher3/BaseDraggingActivity.java
@@ -18,6 +18,7 @@
import static com.android.launcher3.util.DisplayController.CHANGE_ROTATION;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import static com.android.launcher3.util.Executors.THREAD_POOL_EXECUTOR;
import android.app.WallpaperColors;
import android.app.WallpaperManager;
@@ -76,8 +77,8 @@
// Update theme
if (Utilities.ATLEAST_P) {
- getSystemService(WallpaperManager.class)
- .addOnColorsChangedListener(this, MAIN_EXECUTOR.getHandler());
+ THREAD_POOL_EXECUTOR.execute(() -> getSystemService(WallpaperManager.class)
+ .addOnColorsChangedListener(this, MAIN_EXECUTOR.getHandler()));
}
int themeRes = Themes.getActivityThemeRes(this);
if (themeRes != mThemeRes) {
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 4dfd918..86a5f9f 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -56,6 +56,14 @@
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SWIPELEFT;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SWIPERIGHT;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_WIDGET_RECONFIGURED;
+import static com.android.launcher3.logging.StatsLogManager.LauncherLatencyEvent.LAUNCHER_LATENCY_STARTUP_ACTIVITY_ON_CREATE;
+import static com.android.launcher3.logging.StatsLogManager.LauncherLatencyEvent.LAUNCHER_LATENCY_STARTUP_TOTAL_DURATION;
+import static com.android.launcher3.logging.StatsLogManager.LauncherLatencyEvent.LAUNCHER_LATENCY_STARTUP_VIEW_INFLATION;
+import static com.android.launcher3.logging.StatsLogManager.LauncherLatencyEvent.LAUNCHER_LATENCY_STARTUP_WORKSPACE_LOADER_ASYNC;
+import static com.android.launcher3.logging.StatsLogManager.LauncherLatencyEvent.LAUNCHER_LATENCY_STARTUP_WORKSPACE_LOADER_SYNC;
+import static com.android.launcher3.logging.StatsLogManager.StatsLatencyLogger.LatencyType.COLD;
+import static com.android.launcher3.logging.StatsLogManager.StatsLatencyLogger.LatencyType.COLD_DEVICE_REBOOTING;
+import static com.android.launcher3.logging.StatsLogManager.StatsLatencyLogger.LatencyType.WARM;
import static com.android.launcher3.model.ItemInstallQueue.FLAG_ACTIVITY_PAUSED;
import static com.android.launcher3.model.ItemInstallQueue.FLAG_DRAG_AND_DROP;
import static com.android.launcher3.popup.SystemShortcut.APP_INFO;
@@ -64,7 +72,6 @@
import static com.android.launcher3.states.RotationHelper.REQUEST_LOCK;
import static com.android.launcher3.states.RotationHelper.REQUEST_NONE;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
-import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
import static com.android.launcher3.util.ItemInfoMatcher.forFolderMatch;
import android.animation.Animator;
@@ -111,6 +118,7 @@
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
import android.view.ViewTreeObserver.OnPreDrawListener;
import android.view.WindowManager.LayoutParams;
import android.view.accessibility.AccessibilityEvent;
@@ -157,6 +165,7 @@
import com.android.launcher3.logging.FileLog;
import com.android.launcher3.logging.InstanceId;
import com.android.launcher3.logging.InstanceIdSequence;
+import com.android.launcher3.logging.StartupLatencyLogger;
import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.model.BgDataModel.Callbacks;
import com.android.launcher3.model.ItemInstallQueue;
@@ -190,6 +199,7 @@
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.IntSet;
+import com.android.launcher3.util.LockedUserState;
import com.android.launcher3.util.OnboardingPrefs;
import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.util.PendingRequestArgs;
@@ -293,6 +303,8 @@
public static final String ON_RESUME_EVT = "Launcher.onResume";
public static final String ON_NEW_INTENT_EVT = "Launcher.onNewIntent";
+ private static boolean sIsNewProcess = true;
+
private StateManager<LauncherState> mStateManager;
private static final int ON_ACTIVITY_RESULT_ANIMATION_DELAY = 500;
@@ -404,12 +416,22 @@
private StringCache mStringCache;
private BaseSearchConfig mBaseSearchConfig;
-
+ private StartupLatencyLogger mStartupLatencyLogger;
private CellPosMapper mCellPosMapper = CellPosMapper.DEFAULT;
@Override
@TargetApi(Build.VERSION_CODES.S)
protected void onCreate(Bundle savedInstanceState) {
+ mStartupLatencyLogger = createStartupLatencyLogger(
+ sIsNewProcess
+ ? LockedUserState.get(this).isUserUnlockedAtLauncherStartup()
+ ? COLD
+ : COLD_DEVICE_REBOOTING
+ : WARM);
+ sIsNewProcess = false;
+ mStartupLatencyLogger
+ .logStart(LAUNCHER_LATENCY_STARTUP_TOTAL_DURATION)
+ .logStart(LAUNCHER_LATENCY_STARTUP_ACTIVITY_ON_CREATE);
// Only use a hard-coded cookie since we only want to trace this once.
if (Utilities.ATLEAST_S) {
Trace.beginAsyncSection(
@@ -518,6 +540,7 @@
}
}
+ mStartupLatencyLogger.logWorkspaceLoadStartTime();
if (!mModel.addCallbacksAndLoad(this)) {
if (!internalStateHandled) {
// If we are not binding synchronously, pause drawing until initial bind complete,
@@ -557,6 +580,17 @@
getWindow().setSoftInputMode(LayoutParams.SOFT_INPUT_ADJUST_NOTHING);
}
setTitle(R.string.home_screen);
+ mStartupLatencyLogger.logEnd(LAUNCHER_LATENCY_STARTUP_ACTIVITY_ON_CREATE);
+ }
+
+ /**
+ * Create {@link StartupLatencyLogger} that only collects launcher startup latency metrics
+ * without sending them anywhere. Child class can override this method to create logger
+ * that overrides {@link StartupLatencyLogger#log()} to report those metrics.
+ */
+ protected StartupLatencyLogger createStartupLatencyLogger(
+ StatsLogManager.StatsLatencyLogger.LatencyType latencyType) {
+ return new StartupLatencyLogger(latencyType);
}
/**
@@ -1290,7 +1324,10 @@
* Finds all the views we need and configure them properly.
*/
protected void setupViews() {
+ mStartupLatencyLogger.logStart(LAUNCHER_LATENCY_STARTUP_VIEW_INFLATION);
inflateRootView(R.layout.launcher);
+ mStartupLatencyLogger.logEnd(LAUNCHER_LATENCY_STARTUP_VIEW_INFLATION);
+
mDragLayer = findViewById(R.id.drag_layer);
mFocusHandler = mDragLayer.getFocusIndicatorHelper();
mWorkspace = mDragLayer.findViewById(R.id.workspace);
@@ -2695,7 +2732,8 @@
@Override
@TargetApi(Build.VERSION_CODES.S)
- public void onInitialBindComplete(IntSet boundPages, RunnableList pendingTasks) {
+ public void onInitialBindComplete(IntSet boundPages, RunnableList pendingTasks,
+ int workspaceItemCount, boolean isBindSync) {
mSynchronouslyBoundPages = boundPages;
mPagesToBindSynchronously = new IntSet();
@@ -2719,6 +2757,26 @@
Trace.endAsyncSection(DISPLAY_WORKSPACE_TRACE_METHOD_NAME,
DISPLAY_WORKSPACE_TRACE_COOKIE);
}
+ mStartupLatencyLogger
+ .logCardinality(workspaceItemCount)
+ .logEnd(isBindSync
+ ? LAUNCHER_LATENCY_STARTUP_WORKSPACE_LOADER_SYNC
+ : LAUNCHER_LATENCY_STARTUP_WORKSPACE_LOADER_ASYNC);
+ // In the first rootview's onDraw after onInitialBindComplete(), log end of startup latency.
+ getRootView().getViewTreeObserver().addOnDrawListener(
+ new ViewTreeObserver.OnDrawListener() {
+
+ @Override
+ public void onDraw() {
+ mStartupLatencyLogger
+ .logEnd(LAUNCHER_LATENCY_STARTUP_TOTAL_DURATION)
+ .log()
+ .reset();
+ MAIN_EXECUTOR.getHandler().postAtFrontOfQueue(
+ () -> getRootView().getViewTreeObserver()
+ .removeOnDrawListener(this));
+ }
+ });
}
/**
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index 6798667d..d3e94e1 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -417,7 +417,7 @@
if (bindDirectly) {
// Divide the set of loaded items into those that we are binding synchronously,
// and everything else that is to be bound normally (asynchronously).
- launcherBinder.bindWorkspace(bindAllCallbacks);
+ launcherBinder.bindWorkspace(bindAllCallbacks, /* isBindSync= */ true);
// For now, continue posting the binding of AllApps as there are other
// issues that arise from that.
launcherBinder.bindAllApps();
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index 2a3ad9a..331ae5d 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -407,7 +407,12 @@
"USE_SEARCH_REQUEST_TIMEOUT_OVERRIDES", DISABLED,
"Use local overrides for search request timeout");
- // TODO(Block 31): Empty block
+ // TODO(Block 31)
+ public static final BooleanFlag ENABLE_SPLIT_LAUNCH_DATA_REFACTOR = getDebugFlag(279494325,
+ "ENABLE_SPLIT_LAUNCH_DATA_REFACTOR", DISABLED,
+ "Use refactored split launching code path");
+
+ // TODO(Block 32): Empty block
public static class BooleanFlag {
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index c9fe745..3c31b7a 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -733,10 +733,11 @@
}
mPageIndicator.stopAllAnimations();
- startAnimation(anim);
+
// Because t=0 has the folder match the folder icon, we can skip the
// first frame and have the same movement one frame earlier.
anim.setCurrentPlayTime(Math.min(getSingleFrameMs(getContext()), anim.getTotalDuration()));
+ startAnimation(anim);
// Make sure the folder picks up the last drag move even if the finger doesn't move.
if (mDragController.isDragging()) {
diff --git a/src/com/android/launcher3/logging/StartupLatencyLogger.kt b/src/com/android/launcher3/logging/StartupLatencyLogger.kt
new file mode 100644
index 0000000..435f798
--- /dev/null
+++ b/src/com/android/launcher3/logging/StartupLatencyLogger.kt
@@ -0,0 +1,203 @@
+package com.android.launcher3.logging
+
+import android.os.SystemClock
+import android.util.Log
+import android.util.SparseLongArray
+import androidx.annotation.MainThread
+import androidx.annotation.VisibleForTesting
+import androidx.core.util.contains
+import androidx.core.util.isEmpty
+import com.android.launcher3.BuildConfig
+import com.android.launcher3.logging.StatsLogManager.LauncherLatencyEvent
+import com.android.launcher3.logging.StatsLogManager.StatsLatencyLogger.LatencyType
+import com.android.launcher3.util.Preconditions
+
+/** Logger for logging Launcher activity's startup latency. */
+open class StartupLatencyLogger(val latencyType: LatencyType) {
+
+ companion object {
+ const val TAG = "LauncherStartupLatencyLogger"
+ const val UNSET_INT = -1
+ const val UNSET_LONG = -1L
+ }
+
+ @VisibleForTesting val startTimeByEvent = SparseLongArray()
+ @VisibleForTesting val endTimeByEvent = SparseLongArray()
+
+ @VisibleForTesting var cardinality: Int = UNSET_INT
+ @VisibleForTesting var workspaceLoadStartTime: Long = UNSET_LONG
+
+ private var isInTest = false
+
+ /** Subclass can override this method to handle collected latency metrics. */
+ @MainThread
+ open fun log(): StartupLatencyLogger {
+ return this
+ }
+
+ @MainThread
+ fun logWorkspaceLoadStartTime() = logWorkspaceLoadStartTime(SystemClock.elapsedRealtime())
+
+ @VisibleForTesting
+ @MainThread
+ fun logWorkspaceLoadStartTime(startTimeMs: Long): StartupLatencyLogger {
+ Preconditions.assertUIThread()
+ workspaceLoadStartTime = startTimeMs
+ return this
+ }
+
+ /**
+ * Log size of workspace. Larger number of workspace items (icons, folders, widgets) means
+ * longer latency to initialize workspace.
+ */
+ @MainThread
+ fun logCardinality(cardinality: Int): StartupLatencyLogger {
+ Preconditions.assertUIThread()
+ this.cardinality = cardinality
+ return this
+ }
+
+ @MainThread
+ fun logStart(event: LauncherLatencyEvent) = logStart(event, SystemClock.elapsedRealtime())
+
+ @MainThread
+ fun logStart(event: LauncherLatencyEvent, startTimeMs: Long): StartupLatencyLogger {
+ // In unit test no looper is attached to current thread
+ Preconditions.assertUIThread()
+ if (validateLoggingEventAtStart(event)) {
+ startTimeByEvent.put(event.id, startTimeMs)
+ }
+ return this
+ }
+
+ @MainThread
+ fun logEnd(event: LauncherLatencyEvent) = logEnd(event, SystemClock.elapsedRealtime())
+
+ @MainThread
+ fun logEnd(event: LauncherLatencyEvent, endTimeMs: Long): StartupLatencyLogger {
+ // In unit test no looper is attached to current thread
+ Preconditions.assertUIThread()
+ maybeLogStartOfWorkspaceLoadTime(event)
+ if (validateLoggingEventAtEnd(event)) {
+ endTimeByEvent.put(event.id, endTimeMs)
+ }
+
+ return this
+ }
+
+ @MainThread
+ fun reset() {
+ // In unit test no looper is attached to current thread
+ Preconditions.assertUIThread()
+ startTimeByEvent.clear()
+ endTimeByEvent.clear()
+ cardinality = UNSET_INT
+ workspaceLoadStartTime = UNSET_LONG
+ }
+
+ @MainThread
+ private fun maybeLogStartOfWorkspaceLoadTime(event: LauncherLatencyEvent) {
+ if (workspaceLoadStartTime == UNSET_LONG) {
+ return
+ }
+ if (
+ event == LauncherLatencyEvent.LAUNCHER_LATENCY_STARTUP_WORKSPACE_LOADER_SYNC ||
+ event == LauncherLatencyEvent.LAUNCHER_LATENCY_STARTUP_WORKSPACE_LOADER_ASYNC
+ ) {
+ logStart(event, workspaceLoadStartTime)
+ workspaceLoadStartTime = UNSET_LONG
+ }
+ }
+
+ /** @return true if we can log start of [LauncherLatencyEvent] and vice versa. */
+ @MainThread
+ private fun validateLoggingEventAtStart(event: LauncherLatencyEvent): Boolean {
+ if (!BuildConfig.IS_STUDIO_BUILD && !isInTest) {
+ return true
+ }
+ if (startTimeByEvent.contains(event.id)) {
+ Log.e(TAG, String.format("Cannot restart same %s event", event.name))
+ return false
+ } else if (
+ startTimeByEvent.isEmpty() &&
+ event != LauncherLatencyEvent.LAUNCHER_LATENCY_STARTUP_TOTAL_DURATION
+ ) {
+ Log.e(
+ TAG,
+ String.format(
+ "The first log start event must be %s.",
+ LauncherLatencyEvent.LAUNCHER_LATENCY_STARTUP_TOTAL_DURATION.name
+ )
+ )
+ return false
+ } else if (
+ event == LauncherLatencyEvent.LAUNCHER_LATENCY_STARTUP_WORKSPACE_LOADER_SYNC &&
+ startTimeByEvent.get(
+ LauncherLatencyEvent.LAUNCHER_LATENCY_STARTUP_WORKSPACE_LOADER_ASYNC.id
+ ) != 0L
+ ) {
+ Log.e(
+ TAG,
+ String.format(
+ "Cannot start %s event after %s starts",
+ LauncherLatencyEvent.LAUNCHER_LATENCY_STARTUP_WORKSPACE_LOADER_SYNC.name,
+ LauncherLatencyEvent.LAUNCHER_LATENCY_STARTUP_WORKSPACE_LOADER_ASYNC.name
+ )
+ )
+ return false
+ } else if (
+ event == LauncherLatencyEvent.LAUNCHER_LATENCY_STARTUP_WORKSPACE_LOADER_ASYNC &&
+ startTimeByEvent.get(
+ LauncherLatencyEvent.LAUNCHER_LATENCY_STARTUP_WORKSPACE_LOADER_SYNC.id
+ ) != 0L
+ ) {
+ Log.e(
+ TAG,
+ String.format(
+ "Cannot start %s event after %s starts",
+ LauncherLatencyEvent.LAUNCHER_LATENCY_STARTUP_WORKSPACE_LOADER_ASYNC.name,
+ LauncherLatencyEvent.LAUNCHER_LATENCY_STARTUP_WORKSPACE_LOADER_SYNC.name
+ )
+ )
+ return false
+ }
+
+ return true
+ }
+
+ /** @return true if we can log end of [LauncherLatencyEvent] and vice versa. */
+ @MainThread
+ private fun validateLoggingEventAtEnd(event: LauncherLatencyEvent): Boolean {
+ if (!BuildConfig.IS_STUDIO_BUILD && !isInTest) {
+ return true
+ }
+ if (!startTimeByEvent.contains(event.id)) {
+ Log.e(TAG, String.format("Cannot end %s event before starting it", event.name))
+ return false
+ } else if (endTimeByEvent.contains(event.id)) {
+ Log.e(TAG, String.format("Cannot end same %s event again", event.name))
+ return false
+ } else if (
+ event != LauncherLatencyEvent.LAUNCHER_LATENCY_STARTUP_TOTAL_DURATION &&
+ endTimeByEvent.contains(
+ LauncherLatencyEvent.LAUNCHER_LATENCY_STARTUP_TOTAL_DURATION.id
+ )
+ ) {
+ Log.e(
+ TAG,
+ String.format(
+ "Cannot end %s event after %s",
+ event.name,
+ LauncherLatencyEvent.LAUNCHER_LATENCY_STARTUP_TOTAL_DURATION.name
+ )
+ )
+ return false
+ }
+ return true
+ }
+
+ @VisibleForTesting
+ fun setIsInTest() {
+ isInTest = true
+ }
+}
diff --git a/src/com/android/launcher3/logging/StatsLogManager.java b/src/com/android/launcher3/logging/StatsLogManager.java
index 5b4a02b..8197b73 100644
--- a/src/com/android/launcher3/logging/StatsLogManager.java
+++ b/src/com/android/launcher3/logging/StatsLogManager.java
@@ -657,6 +657,39 @@
}
}
+ /** Launcher's latency events. */
+ public enum LauncherLatencyEvent implements EventEnum {
+ // Details of below 6 events with prefix of "LAUNCHER_LATENCY_STARTUP_" are discussed in
+ // go/launcher-startup-latency
+ @UiEvent(doc = "The total duration of launcher startup latency.")
+ LAUNCHER_LATENCY_STARTUP_TOTAL_DURATION(1362),
+
+ @UiEvent(doc = "The duration of launcher activity's onCreate().")
+ LAUNCHER_LATENCY_STARTUP_ACTIVITY_ON_CREATE(1363),
+
+ @UiEvent(doc =
+ "The duration to inflate launcher root view in launcher activity's onCreate().")
+ LAUNCHER_LATENCY_STARTUP_VIEW_INFLATION(1364),
+
+ @UiEvent(doc = "The duration of synchronous loading workspace")
+ LAUNCHER_LATENCY_STARTUP_WORKSPACE_LOADER_SYNC(1366),
+
+ @UiEvent(doc = "The duration of asynchronous loading workspace")
+ LAUNCHER_LATENCY_STARTUP_WORKSPACE_LOADER_ASYNC(1367),
+ ;
+
+ private final int mId;
+
+ LauncherLatencyEvent(int id) {
+ mId = id;
+ }
+
+ @Override
+ public int getId() {
+ return mId;
+ }
+ }
+
/**
* Launcher specific ranking related events.
*/
@@ -807,7 +840,10 @@
CONTROLLED(7),
CACHED(8),
// example: device is rebooting via power key or shell command `adb reboot`
- COLD_DEVICE_REBOOTING(9);
+ COLD_DEVICE_REBOOTING(9),
+ // Tracking warm startup latency:
+ // https://developer.android.com/topic/performance/vitals/launch-time#warm
+ WARM(10);
private final int mId;
LatencyType(int id) {
diff --git a/src/com/android/launcher3/model/BaseLauncherBinder.java b/src/com/android/launcher3/model/BaseLauncherBinder.java
index c946e2c..ba9eb20 100644
--- a/src/com/android/launcher3/model/BaseLauncherBinder.java
+++ b/src/com/android/launcher3/model/BaseLauncherBinder.java
@@ -78,14 +78,14 @@
/**
* Binds all loaded data to actual views on the main thread.
*/
- public void bindWorkspace(boolean incrementBindId) {
+ public void bindWorkspace(boolean incrementBindId, boolean isBindSync) {
if (FeatureFlags.ENABLE_WORKSPACE_LOADING_OPTIMIZATION.get()) {
DisjointWorkspaceBinder workspaceBinder =
initWorkspaceBinder(incrementBindId, mBgDataModel.collectWorkspaceScreens());
- workspaceBinder.bindCurrentWorkspacePages();
+ workspaceBinder.bindCurrentWorkspacePages(isBindSync);
workspaceBinder.bindOtherWorkspacePages();
} else {
- bindWorkspaceAllAtOnce(incrementBindId);
+ bindWorkspaceAllAtOnce(incrementBindId, isBindSync);
}
}
@@ -108,13 +108,13 @@
}
}
- private void bindWorkspaceAllAtOnce(boolean incrementBindId) {
+ private void bindWorkspaceAllAtOnce(boolean incrementBindId, boolean isBindSync) {
// Save a copy of all the bg-thread collections
ArrayList<ItemInfo> workspaceItems = new ArrayList<>();
ArrayList<LauncherAppWidgetInfo> appWidgets = new ArrayList<>();
final IntArray orderedScreenIds = new IntArray();
ArrayList<FixedContainerItems> extraItems = new ArrayList<>();
-
+ final int workspaceItemCount;
synchronized (mBgDataModel) {
workspaceItems.addAll(mBgDataModel.workspaceItems);
appWidgets.addAll(mBgDataModel.appWidgets);
@@ -124,11 +124,13 @@
mBgDataModel.lastBindId++;
}
mMyBindingId = mBgDataModel.lastBindId;
+ workspaceItemCount = mBgDataModel.itemsIdMap.size();
}
for (Callbacks cb : mCallbacksList) {
new UnifiedWorkspaceBinder(cb, mUiExecutor, mApp, mBgDataModel, mMyBindingId,
- workspaceItems, appWidgets, extraItems, orderedScreenIds).bind();
+ workspaceItems, appWidgets, extraItems, orderedScreenIds)
+ .bind(isBindSync, workspaceItemCount);
}
}
@@ -246,7 +248,7 @@
mOrderedScreenIds = orderedScreenIds;
}
- private void bind() {
+ private void bind(boolean isBindSync, int workspaceItemCount) {
final IntSet currentScreenIds =
mCallbacks.getPagesToBindSynchronously(mOrderedScreenIds);
Objects.requireNonNull(currentScreenIds, "Null screen ids provided by " + mCallbacks);
@@ -297,7 +299,8 @@
executeCallbacksTask(
c -> {
MODEL_EXECUTOR.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
- c.onInitialBindComplete(currentScreenIds, pendingTasks);
+ c.onInitialBindComplete(
+ currentScreenIds, pendingTasks, workspaceItemCount, isBindSync);
}, mUiExecutor);
mCallbacks.bindStringCache(mBgDataModel.stringCache.clone());
@@ -361,18 +364,19 @@
* loaded, it will bind all workspace items immediately, and bindOtherWorkspacePages() will
* not bind any items.
*/
- protected void bindCurrentWorkspacePages() {
+ protected void bindCurrentWorkspacePages(boolean isBindSync) {
// Save a copy of all the bg-thread collections
ArrayList<ItemInfo> workspaceItems;
ArrayList<LauncherAppWidgetInfo> appWidgets;
ArrayList<FixedContainerItems> fciList = new ArrayList<>();
-
+ final int workspaceItemCount;
synchronized (mBgDataModel) {
workspaceItems = new ArrayList<>(mBgDataModel.workspaceItems);
appWidgets = new ArrayList<>(mBgDataModel.appWidgets);
if (!FeatureFlags.CHANGE_MODEL_DELEGATE_LOADING_ORDER.get()) {
mBgDataModel.extraItems.forEach(fciList::add);
}
+ workspaceItemCount = mBgDataModel.itemsIdMap.size();
}
workspaceItems.forEach(it -> mBoundItemIds.add(it.id));
@@ -395,10 +399,10 @@
bindWorkspaceItems(workspaceItems);
bindAppWidgets(appWidgets);
-
executeCallbacksTask(c -> {
MODEL_EXECUTOR.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
- c.onInitialBindComplete(mCurrentScreenIds, new RunnableList());
+ c.onInitialBindComplete(
+ mCurrentScreenIds, new RunnableList(), workspaceItemCount, isBindSync);
}, mUiExecutor);
}
diff --git a/src/com/android/launcher3/model/BgDataModel.java b/src/com/android/launcher3/model/BgDataModel.java
index 0e3b06c..0861e9d 100644
--- a/src/com/android/launcher3/model/BgDataModel.java
+++ b/src/com/android/launcher3/model/BgDataModel.java
@@ -484,7 +484,9 @@
default void bindWorkspaceComponentsRemoved(Predicate<ItemInfo> matcher) { }
default void bindAllWidgets(List<WidgetsListBaseEntry> widgets) { }
- default void onInitialBindComplete(IntSet boundPages, RunnableList pendingTasks) {
+ /** Called when workspace has been bound. */
+ default void onInitialBindComplete(IntSet boundPages, RunnableList pendingTasks,
+ int workspaceItemCount, boolean isBindSync) {
pendingTasks.executeAllAndDestroy();
}
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index 481cc6e..9053d19 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -232,7 +232,7 @@
}
verifyNotStopped();
- mLauncherBinder.bindWorkspace(true /* incrementBindId */);
+ mLauncherBinder.bindWorkspace(true /* incrementBindId */, /* isBindSync= */ false);
logASplit(timingLogger, "bindWorkspace");
mModelDelegate.workspaceLoadComplete();
diff --git a/src/com/android/launcher3/util/LockedUserState.kt b/src/com/android/launcher3/util/LockedUserState.kt
index f5e13d2..1231604 100644
--- a/src/com/android/launcher3/util/LockedUserState.kt
+++ b/src/com/android/launcher3/util/LockedUserState.kt
@@ -7,6 +7,7 @@
import androidx.annotation.VisibleForTesting
class LockedUserState(private val mContext: Context) : SafeCloseable {
+ val isUserUnlockedAtLauncherStartup: Boolean
var isUserUnlocked: Boolean
private set
private val mUserUnlockedActions: RunnableList = RunnableList()
@@ -20,10 +21,17 @@
}
init {
+ // 1) when user reboots devices, launcher process starts at lock screen and both
+ // isUserUnlocked and isUserUnlockedAtLauncherStartup are init as false. After user unlocks
+ // screen, isUserUnlocked will be updated to true via Intent.ACTION_USER_UNLOCKED,
+ // yet isUserUnlockedAtLauncherStartup will remains as false.
+ // 2) when launcher process restarts after user has unlocked screen, both variable are
+ // init as true and will not change.
isUserUnlocked =
mContext
.getSystemService(UserManager::class.java)!!
.isUserUnlocked(Process.myUserHandle())
+ isUserUnlockedAtLauncherStartup = isUserUnlocked
if (isUserUnlocked) {
notifyUserUnlocked()
} else {
diff --git a/tests/shared/com/android/launcher3/testing/shared/TestProtocol.java b/tests/shared/com/android/launcher3/testing/shared/TestProtocol.java
index 36255b4..b472cdb 100644
--- a/tests/shared/com/android/launcher3/testing/shared/TestProtocol.java
+++ b/tests/shared/com/android/launcher3/testing/shared/TestProtocol.java
@@ -111,6 +111,7 @@
public static final String REQUEST_IS_TABLET = "is-tablet";
public static final String REQUEST_IS_TWO_PANELS = "is-two-panel";
public static final String REQUEST_START_DRAG_THRESHOLD = "start-drag-threshold";
+ public static final String REQUEST_SHELL_DRAG_READY = "shell-drag-ready";
public static final String REQUEST_GET_ACTIVITIES_CREATED_COUNT =
"get-activities-created-count";
public static final String REQUEST_GET_ACTIVITIES = "get-activities";
diff --git a/tests/src/com/android/launcher3/logging/StartupLatencyLoggerTest.kt b/tests/src/com/android/launcher3/logging/StartupLatencyLoggerTest.kt
new file mode 100644
index 0000000..fffa6d7
--- /dev/null
+++ b/tests/src/com/android/launcher3/logging/StartupLatencyLoggerTest.kt
@@ -0,0 +1,368 @@
+package com.android.launcher3.logging
+
+import androidx.core.util.isEmpty
+import androidx.test.annotation.UiThreadTest
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/** Unit test for [StartupLatencyLogger]. */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class StartupLatencyLoggerTest {
+
+ private val underTest: StartupLatencyLogger =
+ StartupLatencyLogger(StatsLogManager.StatsLatencyLogger.LatencyType.COLD)
+
+ @Before
+ fun setup() {
+ underTest.setIsInTest()
+ }
+
+ @Test
+ @UiThreadTest
+ fun logTotalDurationStart() {
+ underTest.logStart(
+ StatsLogManager.LauncherLatencyEvent.LAUNCHER_LATENCY_STARTUP_TOTAL_DURATION,
+ 100
+ )
+
+ val startTime =
+ underTest.startTimeByEvent.get(
+ StatsLogManager.LauncherLatencyEvent.LAUNCHER_LATENCY_STARTUP_TOTAL_DURATION.id
+ )
+ assertThat(startTime).isEqualTo(100)
+ assertThat(underTest.endTimeByEvent.isEmpty()).isTrue()
+ }
+
+ @Test
+ @UiThreadTest
+ fun logTotalDurationEnd() {
+ underTest.logStart(
+ StatsLogManager.LauncherLatencyEvent.LAUNCHER_LATENCY_STARTUP_TOTAL_DURATION,
+ 100
+ )
+
+ underTest.logEnd(
+ StatsLogManager.LauncherLatencyEvent.LAUNCHER_LATENCY_STARTUP_TOTAL_DURATION,
+ 100
+ )
+
+ val endTime =
+ underTest.endTimeByEvent.get(
+ StatsLogManager.LauncherLatencyEvent.LAUNCHER_LATENCY_STARTUP_TOTAL_DURATION.id
+ )
+ assertThat(endTime).isEqualTo(100)
+ }
+
+ @Test
+ @UiThreadTest
+ fun logStartOfOtherEvents_withoutLogStartOfTotalDuration_noOp() {
+ underTest
+ .logStart(
+ StatsLogManager.LauncherLatencyEvent.LAUNCHER_LATENCY_STARTUP_ACTIVITY_ON_CREATE,
+ 100
+ )
+ .logStart(
+ StatsLogManager.LauncherLatencyEvent.LAUNCHER_LATENCY_STARTUP_VIEW_INFLATION,
+ 101
+ )
+ .logStart(
+ StatsLogManager.LauncherLatencyEvent
+ .LAUNCHER_LATENCY_STARTUP_WORKSPACE_LOADER_ASYNC,
+ 102
+ )
+
+ assertThat(underTest.startTimeByEvent.isEmpty()).isTrue()
+ }
+
+ @Test
+ @UiThreadTest
+ fun logStartOfOtherEvents_afterLogStartOfTotalDuration_logged() {
+ underTest.logStart(
+ StatsLogManager.LauncherLatencyEvent.LAUNCHER_LATENCY_STARTUP_TOTAL_DURATION,
+ 100
+ )
+
+ underTest
+ .logStart(
+ StatsLogManager.LauncherLatencyEvent.LAUNCHER_LATENCY_STARTUP_ACTIVITY_ON_CREATE,
+ 100
+ )
+ .logStart(
+ StatsLogManager.LauncherLatencyEvent.LAUNCHER_LATENCY_STARTUP_VIEW_INFLATION,
+ 101
+ )
+ .logStart(
+ StatsLogManager.LauncherLatencyEvent
+ .LAUNCHER_LATENCY_STARTUP_WORKSPACE_LOADER_ASYNC,
+ 102
+ )
+
+ assertThat(underTest.startTimeByEvent.size()).isEqualTo(4)
+ }
+
+ @Test
+ @UiThreadTest
+ fun logDuplicatedStartEvent_2ndEvent_notLogged() {
+ underTest.logStart(
+ StatsLogManager.LauncherLatencyEvent.LAUNCHER_LATENCY_STARTUP_TOTAL_DURATION,
+ 100
+ )
+
+ underTest.logStart(
+ StatsLogManager.LauncherLatencyEvent.LAUNCHER_LATENCY_STARTUP_TOTAL_DURATION,
+ 101
+ )
+
+ assertThat(underTest.startTimeByEvent.size()).isEqualTo(1)
+ assertThat(
+ underTest.startTimeByEvent.get(
+ StatsLogManager.LauncherLatencyEvent.LAUNCHER_LATENCY_STARTUP_TOTAL_DURATION.id
+ )
+ )
+ .isEqualTo(100)
+ }
+
+ @Test
+ @UiThreadTest
+ fun loadStartOfWorkspace_thenEndWithAsync_logAsyncStart() {
+ underTest
+ .logStart(
+ StatsLogManager.LauncherLatencyEvent.LAUNCHER_LATENCY_STARTUP_TOTAL_DURATION,
+ 100
+ )
+ .logWorkspaceLoadStartTime(111)
+
+ underTest.logEnd(
+ StatsLogManager.LauncherLatencyEvent.LAUNCHER_LATENCY_STARTUP_WORKSPACE_LOADER_ASYNC,
+ 120
+ )
+
+ assertThat(underTest.startTimeByEvent.size()).isEqualTo(2)
+ assertThat(
+ underTest.startTimeByEvent.get(
+ StatsLogManager.LauncherLatencyEvent
+ .LAUNCHER_LATENCY_STARTUP_WORKSPACE_LOADER_ASYNC
+ .id
+ )
+ )
+ .isEqualTo(111)
+ }
+
+ @Test
+ @UiThreadTest
+ fun loadStartOfWorkspace_thenEndWithSync_logSyncStart() {
+ underTest
+ .logStart(
+ StatsLogManager.LauncherLatencyEvent.LAUNCHER_LATENCY_STARTUP_TOTAL_DURATION,
+ 100
+ )
+ .logWorkspaceLoadStartTime(111)
+
+ underTest.logEnd(
+ StatsLogManager.LauncherLatencyEvent.LAUNCHER_LATENCY_STARTUP_WORKSPACE_LOADER_SYNC,
+ 120
+ )
+
+ assertThat(underTest.startTimeByEvent.size()).isEqualTo(2)
+ assertThat(
+ underTest.startTimeByEvent.get(
+ StatsLogManager.LauncherLatencyEvent
+ .LAUNCHER_LATENCY_STARTUP_WORKSPACE_LOADER_SYNC
+ .id
+ )
+ )
+ .isEqualTo(111)
+ }
+
+ @Test
+ @UiThreadTest
+ fun loadStartOfWorkspaceLoadSync_thenAsync_asyncNotLogged() {
+ underTest
+ .logStart(
+ StatsLogManager.LauncherLatencyEvent.LAUNCHER_LATENCY_STARTUP_TOTAL_DURATION,
+ 100
+ )
+ .logStart(
+ StatsLogManager.LauncherLatencyEvent.LAUNCHER_LATENCY_STARTUP_WORKSPACE_LOADER_SYNC,
+ 110
+ )
+
+ underTest.logStart(
+ StatsLogManager.LauncherLatencyEvent.LAUNCHER_LATENCY_STARTUP_WORKSPACE_LOADER_ASYNC,
+ 111
+ )
+
+ assertThat(underTest.startTimeByEvent.size()).isEqualTo(2)
+ assertThat(
+ underTest.startTimeByEvent.get(
+ StatsLogManager.LauncherLatencyEvent
+ .LAUNCHER_LATENCY_STARTUP_WORKSPACE_LOADER_SYNC
+ .id
+ )
+ )
+ .isEqualTo(110)
+ assertThat(
+ underTest.startTimeByEvent.get(
+ StatsLogManager.LauncherLatencyEvent
+ .LAUNCHER_LATENCY_STARTUP_WORKSPACE_LOADER_ASYNC
+ .id
+ )
+ )
+ .isEqualTo(0)
+ }
+
+ @Test
+ @UiThreadTest
+ fun loadStartOfWorkspaceLoadAsync_thenSync_syncNotLogged() {
+ underTest.logStart(
+ StatsLogManager.LauncherLatencyEvent.LAUNCHER_LATENCY_STARTUP_TOTAL_DURATION,
+ 100
+ )
+ underTest.logStart(
+ StatsLogManager.LauncherLatencyEvent.LAUNCHER_LATENCY_STARTUP_WORKSPACE_LOADER_ASYNC,
+ 111
+ )
+
+ underTest.logStart(
+ StatsLogManager.LauncherLatencyEvent.LAUNCHER_LATENCY_STARTUP_WORKSPACE_LOADER_SYNC,
+ 112
+ )
+
+ assertThat(underTest.startTimeByEvent.size()).isEqualTo(2)
+ assertThat(
+ underTest.startTimeByEvent.get(
+ StatsLogManager.LauncherLatencyEvent
+ .LAUNCHER_LATENCY_STARTUP_WORKSPACE_LOADER_ASYNC
+ .id
+ )
+ )
+ .isEqualTo(111)
+ assertThat(
+ underTest.startTimeByEvent.get(
+ StatsLogManager.LauncherLatencyEvent
+ .LAUNCHER_LATENCY_STARTUP_WORKSPACE_LOADER_SYNC
+ .id
+ )
+ )
+ .isEqualTo(0)
+ }
+
+ @Test
+ @UiThreadTest
+ fun logEndOfEvent_withoutStartEvent_notLogged() {
+ underTest.logStart(
+ StatsLogManager.LauncherLatencyEvent.LAUNCHER_LATENCY_STARTUP_TOTAL_DURATION,
+ 100
+ )
+
+ underTest.logEnd(
+ StatsLogManager.LauncherLatencyEvent.LAUNCHER_LATENCY_STARTUP_WORKSPACE_LOADER_SYNC,
+ 120
+ )
+
+ assertThat(underTest.endTimeByEvent.size()).isEqualTo(0)
+ assertThat(
+ underTest.endTimeByEvent.get(
+ StatsLogManager.LauncherLatencyEvent
+ .LAUNCHER_LATENCY_STARTUP_WORKSPACE_LOADER_SYNC
+ .id
+ )
+ )
+ .isEqualTo(0)
+ }
+
+ @Test
+ @UiThreadTest
+ fun logEndOfEvent_afterEndOfTotalDuration_notLogged() {
+ underTest
+ .logStart(
+ StatsLogManager.LauncherLatencyEvent.LAUNCHER_LATENCY_STARTUP_TOTAL_DURATION,
+ 100
+ )
+ .logEnd(
+ StatsLogManager.LauncherLatencyEvent.LAUNCHER_LATENCY_STARTUP_TOTAL_DURATION,
+ 120
+ )
+
+ underTest.logEnd(
+ StatsLogManager.LauncherLatencyEvent.LAUNCHER_LATENCY_STARTUP_WORKSPACE_LOADER_SYNC,
+ 121
+ )
+
+ assertThat(underTest.endTimeByEvent.size()).isEqualTo(1)
+ assertThat(
+ underTest.endTimeByEvent.get(
+ StatsLogManager.LauncherLatencyEvent
+ .LAUNCHER_LATENCY_STARTUP_WORKSPACE_LOADER_SYNC
+ .id
+ )
+ )
+ .isEqualTo(0)
+ }
+
+ @Test
+ @UiThreadTest
+ fun logCardinality_setCardinality() {
+ underTest.logCardinality(-1)
+ underTest.logStart(
+ StatsLogManager.LauncherLatencyEvent.LAUNCHER_LATENCY_STARTUP_TOTAL_DURATION,
+ 100
+ )
+
+ underTest.logCardinality(235)
+
+ assertThat(underTest.cardinality).isEqualTo(235)
+ }
+
+ @Test
+ @UiThreadTest
+ fun reset_clearState() {
+ underTest
+ .logStart(
+ StatsLogManager.LauncherLatencyEvent.LAUNCHER_LATENCY_STARTUP_TOTAL_DURATION,
+ 100
+ )
+ .logStart(
+ StatsLogManager.LauncherLatencyEvent.LAUNCHER_LATENCY_STARTUP_ACTIVITY_ON_CREATE,
+ 100
+ )
+ .logStart(
+ StatsLogManager.LauncherLatencyEvent.LAUNCHER_LATENCY_STARTUP_VIEW_INFLATION,
+ 110
+ )
+ .logEnd(
+ StatsLogManager.LauncherLatencyEvent.LAUNCHER_LATENCY_STARTUP_VIEW_INFLATION,
+ 115
+ )
+ .logWorkspaceLoadStartTime(120)
+ .logCardinality(235)
+ .logEnd(
+ StatsLogManager.LauncherLatencyEvent.LAUNCHER_LATENCY_STARTUP_ACTIVITY_ON_CREATE,
+ 100
+ )
+ .logEnd(
+ StatsLogManager.LauncherLatencyEvent
+ .LAUNCHER_LATENCY_STARTUP_WORKSPACE_LOADER_ASYNC,
+ 140
+ )
+ .logEnd(
+ StatsLogManager.LauncherLatencyEvent.LAUNCHER_LATENCY_STARTUP_TOTAL_DURATION,
+ 160
+ )
+ assertThat(underTest.startTimeByEvent.size()).isEqualTo(4)
+ assertThat(underTest.endTimeByEvent.size()).isEqualTo(4)
+ assertThat(underTest.cardinality).isEqualTo(235)
+
+ underTest.reset()
+
+ assertThat(underTest.startTimeByEvent.isEmpty()).isTrue()
+ assertThat(underTest.endTimeByEvent.isEmpty()).isTrue()
+ assertThat(underTest.cardinality).isEqualTo(StartupLatencyLogger.UNSET_INT)
+ assertThat(underTest.workspaceLoadStartTime).isEqualTo(StartupLatencyLogger.UNSET_LONG)
+ }
+}
diff --git a/tests/src/com/android/launcher3/model/ModelMultiCallbacksTest.java b/tests/src/com/android/launcher3/model/ModelMultiCallbacksTest.java
index 42c9f11..b9da16a 100644
--- a/tests/src/com/android/launcher3/model/ModelMultiCallbacksTest.java
+++ b/tests/src/com/android/launcher3/model/ModelMultiCallbacksTest.java
@@ -180,7 +180,8 @@
}
@Override
- public void onInitialBindComplete(IntSet boundPages, RunnableList pendingTasks) {
+ public void onInitialBindComplete(IntSet boundPages, RunnableList pendingTasks,
+ int workspaceItemCount, boolean isBindSync) {
mPageBoundSync = boundPages;
mPendingTasks = pendingTasks;
}
diff --git a/tests/tapl/com/android/launcher3/tapl/LaunchedAppState.java b/tests/tapl/com/android/launcher3/tapl/LaunchedAppState.java
index 4a3507e..58d5a36 100644
--- a/tests/tapl/com/android/launcher3/tapl/LaunchedAppState.java
+++ b/tests/tapl/com/android/launcher3/tapl/LaunchedAppState.java
@@ -16,11 +16,14 @@
package com.android.launcher3.tapl;
+import static com.android.launcher3.tapl.LauncherInstrumentation.DEFAULT_POLL_INTERVAL;
import static com.android.launcher3.tapl.LauncherInstrumentation.TASKBAR_RES_ID;
+import static com.android.launcher3.tapl.LauncherInstrumentation.WAIT_TIME_MS;
import static com.android.launcher3.testing.shared.TestProtocol.REQUEST_DISABLE_BLOCK_TIMEOUT;
import static com.android.launcher3.testing.shared.TestProtocol.REQUEST_DISABLE_MANUAL_TASKBAR_STASHING;
import static com.android.launcher3.testing.shared.TestProtocol.REQUEST_ENABLE_BLOCK_TIMEOUT;
import static com.android.launcher3.testing.shared.TestProtocol.REQUEST_ENABLE_MANUAL_TASKBAR_STASHING;
+import static com.android.launcher3.testing.shared.TestProtocol.REQUEST_SHELL_DRAG_READY;
import static com.android.launcher3.testing.shared.TestProtocol.REQUEST_STASHED_TASKBAR_HEIGHT;
import android.graphics.Point;
@@ -146,6 +149,12 @@
try (LauncherInstrumentation.Closable c2 = launcher.addContextLayer(
"started item drag")) {
+ launcher.assertTrue("Shell drag not marked as ready", launcher.waitAndGet(() -> {
+ LauncherInstrumentation.log("Checking shell drag ready");
+ return launcher.getTestInfo(REQUEST_SHELL_DRAG_READY)
+ .getBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD, false);
+ }, WAIT_TIME_MS, DEFAULT_POLL_INTERVAL));
+
launcher.movePointer(
dragStart,
endPoint,