Merge "Taskbar running apps: show one icon per task + tap opens that task" into main
diff --git a/aconfig/launcher.aconfig b/aconfig/launcher.aconfig
index 88804b7..21b9863 100644
--- a/aconfig/launcher.aconfig
+++ b/aconfig/launcher.aconfig
@@ -310,3 +310,9 @@
bug: "341795751"
}
+flag {
+ name: "enable_new_archiving_icon"
+ namespace: "launcher"
+ description: "Archived apps will use new icon in app title"
+ bug: "350758155"
+}
diff --git a/quickstep/res/layout/task_desktop.xml b/quickstep/res/layout/task_desktop.xml
index 89e9b3d..453057c 100644
--- a/quickstep/res/layout/task_desktop.xml
+++ b/quickstep/res/layout/task_desktop.xml
@@ -36,15 +36,6 @@
android:layout_width="match_parent"
android:layout_height="match_parent" />
- <!--
- TODO(b249371338): DesktopTaskView extends from TaskView. TaskView expects TaskThumbnailView
- and IconView with these ids to be present. Need to refactor RecentsView to accept child
- views that do not inherint from TaskView only or create a generic TaskView that have
- N number of tasks.
- -->
- <include layout="@layout/task_thumbnail"
- android:visibility="gone" />
-
<ViewStub
android:id="@+id/icon"
android:inflatedId="@id/icon"
diff --git a/quickstep/src/com/android/launcher3/WidgetPickerActivity.java b/quickstep/src/com/android/launcher3/WidgetPickerActivity.java
index 7235b63..8984086 100644
--- a/quickstep/src/com/android/launcher3/WidgetPickerActivity.java
+++ b/quickstep/src/com/android/launcher3/WidgetPickerActivity.java
@@ -44,7 +44,6 @@
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.popup.PopupDataProvider;
import com.android.launcher3.util.ComponentKey;
-import com.android.launcher3.widget.BaseWidgetSheet;
import com.android.launcher3.widget.WidgetCell;
import com.android.launcher3.widget.model.WidgetsListBaseEntry;
import com.android.launcher3.widget.model.WidgetsListContentEntry;
@@ -81,6 +80,15 @@
*/
private static final String EXTRA_ADDED_APP_WIDGETS = "added_app_widgets";
/**
+ * Intent extra for the string representing the title displayed within the picker header.
+ */
+ private static final String EXTRA_PICKER_TITLE = "picker_title";
+ /**
+ * Intent extra for the string representing the description displayed within the picker header.
+ */
+ private static final String EXTRA_PICKER_DESCRIPTION = "picker_description";
+
+ /**
* A unique identifier of the surface hosting the widgets;
* <p>"widgets" is reserved for home screen surface.</p>
* <p>"widgets_hub" is reserved for glanceable hub surface.</p>
@@ -108,6 +116,10 @@
// Widgets existing on the host surface.
@NonNull
private List<AppWidgetProviderInfo> mAddedWidgets = new ArrayList<>();
+ @Nullable
+ private String mTitle;
+ @Nullable
+ private String mDescription;
/** A set of user ids that should be filtered out from the selected widgets. */
@NonNull
@@ -137,6 +149,9 @@
}
private void parseIntentExtras() {
+ mTitle = getIntent().getStringExtra(EXTRA_PICKER_TITLE);
+ mDescription = getIntent().getStringExtra(EXTRA_PICKER_DESCRIPTION);
+
// A value of 0 for either size means that no filtering will occur in that dimension. If
// both values are 0, then no size filtering will occur.
mDesiredWidgetWidth =
@@ -241,7 +256,7 @@
/** Updates the model with widgets, applies filters and launches the widgets sheet once
* widgets are available */
private void refreshAndBindWidgets() {
- MODEL_EXECUTOR.getHandler().postDelayed(() -> {
+ MODEL_EXECUTOR.execute(() -> {
LauncherAppState app = LauncherAppState.getInstance(this);
mModel.update(app, null);
final List<WidgetsListBaseEntry> allWidgets =
@@ -271,7 +286,7 @@
mUiSurface, allWidgetItems);
mWidgetPredictionsRequester.request(mAddedWidgets, this::bindRecommendedWidgets);
}
- }, mDeviceProfile.bottomSheetOpenDuration);
+ });
}
private void bindWidgets(List<WidgetsListBaseEntry> widgets) {
@@ -280,7 +295,8 @@
private void openWidgetsSheet() {
MAIN_EXECUTOR.execute(() -> {
- BaseWidgetSheet widgetSheet = WidgetsFullSheet.show(this, true);
+ WidgetsFullSheet widgetSheet = WidgetsFullSheet.show(this, true);
+ widgetSheet.mayUpdateTitleAndDescription(mTitle, mDescription);
widgetSheet.disableNavBarScrim(true);
widgetSheet.addOnCloseListener(this::finish);
});
diff --git a/quickstep/src/com/android/launcher3/model/WidgetPredictionsRequester.java b/quickstep/src/com/android/launcher3/model/WidgetPredictionsRequester.java
index 41fcf61..8c98bab 100644
--- a/quickstep/src/com/android/launcher3/model/WidgetPredictionsRequester.java
+++ b/quickstep/src/com/android/launcher3/model/WidgetPredictionsRequester.java
@@ -59,6 +59,11 @@
public class WidgetPredictionsRequester {
private static final int NUM_OF_RECOMMENDED_WIDGETS_PREDICATION = 20;
private static final String BUNDLE_KEY_ADDED_APP_WIDGETS = "added_app_widgets";
+ // container/screenid/[positionx,positiony]/[spanx,spany]
+ // Matches the format passed used by PredictionHelper; But, position and size values aren't
+ // used, so, we pass default values.
+ @VisibleForTesting
+ static final String LAUNCH_LOCATION = "workspace/1/[0,0]/[2,2]";
@Nullable
private AppPredictor mAppPredictor;
@@ -86,7 +91,7 @@
*/
public void request(List<AppWidgetProviderInfo> existingWidgets,
Consumer<List<ItemInfo>> callback) {
- Bundle bundle = buildBundleForPredictionSession(existingWidgets, mUiSurface);
+ Bundle bundle = buildBundleForPredictionSession(existingWidgets);
Predicate<WidgetItem> filter = notOnUiSurfaceFilter(existingWidgets);
MODEL_EXECUTOR.execute(() -> {
@@ -112,17 +117,14 @@
* Returns a bundle that can be passed in a prediction session
*
* @param addedWidgets widgets that are already added by the user in the ui surface
- * @param uiSurface a unique identifier of the surface hosting widgets; format
- * "widgets_xx"; note - "widgets" is reserved for home screen surface.
*/
@VisibleForTesting
- static Bundle buildBundleForPredictionSession(List<AppWidgetProviderInfo> addedWidgets,
- String uiSurface) {
+ static Bundle buildBundleForPredictionSession(List<AppWidgetProviderInfo> addedWidgets) {
Bundle bundle = new Bundle();
ArrayList<AppTargetEvent> addedAppTargetEvents = new ArrayList<>();
for (AppWidgetProviderInfo info : addedWidgets) {
ComponentName componentName = info.provider;
- AppTargetEvent appTargetEvent = buildAppTargetEvent(uiSurface, info, componentName);
+ AppTargetEvent appTargetEvent = buildAppTargetEvent(info, componentName);
addedAppTargetEvents.add(appTargetEvent);
}
bundle.putParcelableArrayList(BUNDLE_KEY_ADDED_APP_WIDGETS, addedAppTargetEvents);
@@ -134,13 +136,13 @@
* predictor.
* Also see {@link PredictionHelper}
*/
- private static AppTargetEvent buildAppTargetEvent(String uiSurface, AppWidgetProviderInfo info,
+ private static AppTargetEvent buildAppTargetEvent(AppWidgetProviderInfo info,
ComponentName componentName) {
AppTargetId appTargetId = new AppTargetId("widget:" + componentName.getPackageName());
AppTarget appTarget = new AppTarget.Builder(appTargetId, componentName.getPackageName(),
/*user=*/ info.getProfile()).setClassName(componentName.getClassName()).build();
- return new AppTargetEvent.Builder(appTarget, AppTargetEvent.ACTION_PIN)
- .setLaunchLocation(uiSurface).build();
+ return new AppTargetEvent.Builder(appTarget, AppTargetEvent.ACTION_PIN).setLaunchLocation(
+ LAUNCH_LOCATION).build();
}
/**
diff --git a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchView.java b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchView.java
index 5d47212..0ba5de1 100644
--- a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchView.java
@@ -46,6 +46,7 @@
import androidx.core.content.res.ResourcesCompat;
import com.android.app.animation.Interpolators;
+import com.android.internal.jank.Cuj;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimatedFloat;
@@ -53,6 +54,7 @@
import com.android.launcher3.testing.shared.TestProtocol;
import com.android.quickstep.util.DesktopTask;
import com.android.quickstep.util.GroupTask;
+import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
import java.util.HashMap;
import java.util.List;
@@ -331,6 +333,8 @@
@Override
public void onAnimationStart(Animator animation) {
super.onAnimationStart(animation);
+ InteractionJankMonitorWrapper.begin(
+ KeyboardQuickSwitchView.this, Cuj.CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_OPEN);
setClipToPadding(false);
setOutlineProvider(new ViewOutlineProvider() {
@Override
@@ -366,12 +370,19 @@
}
@Override
+ public void onAnimationCancel(Animator animation) {
+ super.onAnimationCancel(animation);
+ InteractionJankMonitorWrapper.cancel(Cuj.CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_OPEN);
+ }
+
+ @Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
setClipToPadding(true);
setOutlineProvider(outlineProvider);
invalidateOutline();
mOpenAnimation = null;
+ InteractionJankMonitorWrapper.end(Cuj.CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_OPEN);
}
});
diff --git a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java
index 73819b3..d411ba6 100644
--- a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java
@@ -16,6 +16,7 @@
package com.android.launcher3.taskbar;
import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
import android.view.KeyEvent;
import android.view.animation.AnimationUtils;
import android.window.RemoteTransition;
@@ -23,6 +24,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.internal.jank.Cuj;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimatorListeners;
import com.android.launcher3.taskbar.overlay.TaskbarOverlayContext;
@@ -30,6 +32,7 @@
import com.android.quickstep.util.SlideInRemoteTransition;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.ThumbnailData;
+import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
import com.android.systemui.shared.system.QuickStepContract;
import java.io.PrintWriter;
@@ -93,18 +96,28 @@
protected void closeQuickSwitchView(boolean animate) {
if (isCloseAnimationRunning()) {
- // Let currently-running animation finish.
if (!animate) {
mCloseAnimation.end();
}
+ // Let currently-running animation finish.
return;
}
if (!animate) {
+ InteractionJankMonitorWrapper.begin(
+ mKeyboardQuickSwitchView, Cuj.CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_CLOSE);
onCloseComplete();
return;
}
mCloseAnimation = mKeyboardQuickSwitchView.getCloseAnimation();
+ mCloseAnimation.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ super.onAnimationStart(animation);
+ InteractionJankMonitorWrapper.begin(
+ mKeyboardQuickSwitchView, Cuj.CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_CLOSE);
+ }
+ });
mCloseAnimation.addListener(AnimatorListeners.forEndCallback(this::onCloseComplete));
mCloseAnimation.start();
}
@@ -142,16 +155,26 @@
return -1;
}
+ Runnable onStartCallback = () -> InteractionJankMonitorWrapper.begin(
+ mKeyboardQuickSwitchView, Cuj.CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_APP_LAUNCH);
+ Runnable onFinishCallback = () -> InteractionJankMonitorWrapper.end(
+ Cuj.CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_APP_LAUNCH);
TaskbarActivityContext context = mControllers.taskbarActivityContext;
RemoteTransition remoteTransition = new RemoteTransition(new SlideInRemoteTransition(
Utilities.isRtl(mControllers.taskbarActivityContext.getResources()),
context.getDeviceProfile().overviewPageSpacing,
QuickStepContract.getWindowCornerRadius(context),
AnimationUtils.loadInterpolator(
- context, android.R.interpolator.fast_out_extra_slow_in)),
+ context, android.R.interpolator.fast_out_extra_slow_in),
+ onStartCallback,
+ onFinishCallback),
"SlideInTransition");
mControllers.taskbarActivityContext.handleGroupTaskLaunch(
- task, remoteTransition, mOnDesktop);
+ task,
+ remoteTransition,
+ mOnDesktop,
+ onStartCallback,
+ onFinishCallback);
return -1;
}
@@ -159,6 +182,7 @@
mCloseAnimation = null;
mOverlayContext.getDragLayer().removeView(mKeyboardQuickSwitchView);
mControllerCallbacks.onCloseComplete();
+ InteractionJankMonitorWrapper.end(Cuj.CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_CLOSE);
}
protected void onDestroy() {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index 3fbdc89..1166cf7 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -1221,22 +1221,44 @@
}
}
+ public void handleGroupTaskLaunch(
+ GroupTask task,
+ @Nullable RemoteTransition remoteTransition,
+ boolean onDesktop) {
+ handleGroupTaskLaunch(task, remoteTransition, onDesktop, null, null);
+ }
+
/**
* Launches the given GroupTask with the following behavior:
* - If the GroupTask is a DesktopTask, launch the tasks in that Desktop.
* - If {@code onDesktop}, bring the given GroupTask to the front.
* - If the GroupTask is a single task, launch it via startActivityFromRecents.
* - Otherwise, we assume the GroupTask is a Split pair and launch them together.
+ * <p>
+ * Given start and/or finish callbacks, they will be run before an after the app launch
+ * respectively in cases where we can't use the remote transition, otherwise we will assume that
+ * these callbacks are included in the remote transition.
*/
- public void handleGroupTaskLaunch(GroupTask task, @Nullable RemoteTransition remoteTransition,
- boolean onDesktop) {
+ public void handleGroupTaskLaunch(
+ GroupTask task,
+ @Nullable RemoteTransition remoteTransition,
+ boolean onDesktop,
+ @Nullable Runnable onStartCallback,
+ @Nullable Runnable onFinishCallback) {
if (task instanceof DesktopTask) {
UI_HELPER_EXECUTOR.execute(() ->
SystemUiProxy.INSTANCE.get(this).showDesktopApps(getDisplay().getDisplayId(),
remoteTransition));
} else if (onDesktop) {
- UI_HELPER_EXECUTOR.execute(() ->
- SystemUiProxy.INSTANCE.get(this).showDesktopApp(task.task1.key.id));
+ UI_HELPER_EXECUTOR.execute(() -> {
+ if (onStartCallback != null) {
+ onStartCallback.run();
+ }
+ SystemUiProxy.INSTANCE.get(this).showDesktopApp(task.task1.key.id);
+ if (onFinishCallback != null) {
+ onFinishCallback.run();
+ }
+ });
} else if (task.task2 == null) {
UI_HELPER_EXECUTOR.execute(() -> {
ActivityOptions activityOptions =
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java
index d26a36d..ea091ca 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java
@@ -166,8 +166,12 @@
if (buttonType == BUTTON_SPACE) {
return false;
}
- // Provide the same haptic feedback that the system offers for virtual keys.
- view.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
+
+ // Provide the same haptic feedback that the system offers for long press.
+ // The haptic feedback from long pressing on the home button is handled by circle to search.
+ if (buttonType != BUTTON_HOME) {
+ view.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
+ }
switch (buttonType) {
case BUTTON_HOME:
logEvent(LAUNCHER_TASKBAR_HOME_BUTTON_LONGPRESS);
@@ -179,10 +183,12 @@
return true;
case BUTTON_BACK:
logEvent(LAUNCHER_TASKBAR_BACK_BUTTON_LONGPRESS);
- return backRecentsLongpress(buttonType);
+ backRecentsLongpress(buttonType);
+ return true;
case BUTTON_RECENTS:
logEvent(LAUNCHER_TASKBAR_OVERVIEW_BUTTON_LONGPRESS);
- return backRecentsLongpress(buttonType);
+ backRecentsLongpress(buttonType);
+ return true;
case BUTTON_IME_SWITCH:
default:
return false;
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 867533c..f020c8f 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -152,8 +152,6 @@
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.startingsurface.SplashScreenExitAnimationUtils;
-import kotlin.Unit;
-
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
@@ -163,6 +161,8 @@
import java.util.OptionalInt;
import java.util.function.Consumer;
+import kotlin.Unit;
+
/**
* Handles the navigation gestures when Launcher is the default home activity.
*/
@@ -2423,34 +2423,42 @@
if (mRecentsAnimationController == null) {
return;
}
+ final Runnable onFinishComplete = () -> {
+ ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
+ "AbsSwipeUpHandler.onTasksAppeared: ")
+ .append("force finish recents animation complete; clearing state callback."));
+ mStateCallback.setStateOnUiThread(STATE_GESTURE_CANCELLED | STATE_HANDLER_INVALIDATED);
+ };
+ ActiveGestureLog.CompoundString forceFinishReason = new ActiveGestureLog.CompoundString(
+ "Forcefully finishing recents animation: ");
if (!mStateCallback.hasStates(STATE_GESTURE_COMPLETED)
&& !hasStartedTaskBefore(appearedTaskTargets)) {
// This is a special case, if a task is started mid-gesture that wasn't a part of a
// previous quickswitch task launch, then cancel the animation back to the app
RemoteAnimationTarget appearedTaskTarget = appearedTaskTargets[0];
TaskInfo taskInfo = appearedTaskTarget.taskInfo;
- ActiveGestureLog.INSTANCE.addLog(
- new ActiveGestureLog.CompoundString("Unexpected task appeared")
- .append(" id=")
+ ActiveGestureLog.INSTANCE.addLog(forceFinishReason
+ .append("Unexpected task appeared id=")
.append(taskInfo.taskId)
.append(" pkg=")
.append(taskInfo.baseIntent.getComponent().getPackageName()));
- finishRecentsAnimationOnTasksAppeared(null /* onFinishComplete */);
+ finishRecentsAnimationOnTasksAppeared(onFinishComplete);
return;
}
ActiveGestureLog.CompoundString handleTaskFailureReason =
new ActiveGestureLog.CompoundString("handleTaskAppeared check failed: ");
if (!handleTaskAppeared(appearedTaskTargets, handleTaskFailureReason)) {
- ActiveGestureLog.INSTANCE.addLog(handleTaskFailureReason);
- finishRecentsAnimationOnTasksAppeared(null /* onFinishComplete */);
+ ActiveGestureLog.INSTANCE.addLog(forceFinishReason.append(handleTaskFailureReason));
+ finishRecentsAnimationOnTasksAppeared(onFinishComplete);
return;
}
RemoteAnimationTarget[] taskTargets = Arrays.stream(appearedTaskTargets)
.filter(mGestureState.mLastStartedTaskIdPredicate)
.toArray(RemoteAnimationTarget[]::new);
if (taskTargets.length == 0) {
- ActiveGestureLog.INSTANCE.addLog("No appeared task matching started task id");
- finishRecentsAnimationOnTasksAppeared(null /* onFinishComplete */);
+ ActiveGestureLog.INSTANCE.addLog(
+ forceFinishReason.append("No appeared task matching started task id"));
+ finishRecentsAnimationOnTasksAppeared(onFinishComplete);
return;
}
RemoteAnimationTarget taskTarget = taskTargets[0];
@@ -2458,13 +2466,13 @@
? null : mRecentsView.getTaskViewByTaskId(taskTarget.taskId);
if (taskView == null || taskView.getTaskContainers().stream().noneMatch(
TaskContainer::getShouldShowSplashView)) {
- ActiveGestureLog.INSTANCE.addLog("Splash not needed");
- finishRecentsAnimationOnTasksAppeared(null /* onFinishComplete */);
+ ActiveGestureLog.INSTANCE.addLog(forceFinishReason.append("Splash not needed"));
+ finishRecentsAnimationOnTasksAppeared(onFinishComplete);
return;
}
if (mContainer == null) {
- ActiveGestureLog.INSTANCE.addLog("Activity destroyed");
- finishRecentsAnimationOnTasksAppeared(null /* onFinishComplete */);
+ ActiveGestureLog.INSTANCE.addLog(forceFinishReason.append("Activity destroyed"));
+ finishRecentsAnimationOnTasksAppeared(onFinishComplete);
return;
}
animateSplashScreenExit(mContainer, appearedTaskTargets, taskTargets);
diff --git a/quickstep/src/com/android/quickstep/DesktopSystemShortcut.kt b/quickstep/src/com/android/quickstep/DesktopSystemShortcut.kt
index 45e5554..358f644 100644
--- a/quickstep/src/com/android/quickstep/DesktopSystemShortcut.kt
+++ b/quickstep/src/com/android/quickstep/DesktopSystemShortcut.kt
@@ -25,7 +25,7 @@
import com.android.quickstep.views.RecentsViewContainer
import com.android.quickstep.views.TaskContainer
import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource
-import com.android.wm.shell.shared.DesktopModeStatus
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
/** A menu item, "Desktop", that allows the user to bring the current app into Desktop Windowing. */
class DesktopSystemShortcut(
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
index a7d3890..f902284 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
@@ -302,6 +302,10 @@
return mNavBarPosition;
}
+ public NavigationMode getMode() {
+ return mMode;
+ }
+
/**
* @return whether the current nav mode is fully gestural.
*/
diff --git a/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java b/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
index ba33c62..f813d9a 100644
--- a/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
+++ b/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
@@ -26,6 +26,7 @@
import android.graphics.Matrix.ScaleToFit;
import android.graphics.Rect;
import android.graphics.RectF;
+import android.util.Log;
import android.view.RemoteAnimationTarget;
import androidx.annotation.NonNull;
@@ -381,6 +382,8 @@
protected class SpringAnimationRunner extends AnimationSuccessListener
implements RectFSpringAnim.OnUpdateListener, BuilderProxy {
+ private static final String TAG = "SpringAnimationRunner";
+
final Rect mCropRect = new Rect();
final Matrix mMatrix = new Matrix();
@@ -481,10 +484,26 @@
return;
}
mTargetTaskView.setAlpha(mAnimationFactory.isAnimatingIntoIcon() ? 1f : alpha);
- float width = mThumbnailStartBounds.width();
- float height = mThumbnailStartBounds.height();
- float scale = Math.min(currentRect.width(), currentRect.height())
- / Math.min(width, height);
+ float startWidth = mThumbnailStartBounds.width();
+ float startHeight = mThumbnailStartBounds.height();
+ float currentWidth = currentRect.width();
+ float currentHeight = currentRect.height();
+ float scale;
+
+ boolean isStartWidthValid = Float.compare(startWidth, 0f) > 0;
+ boolean isStartHeightValid = Float.compare(startHeight, 0f) > 0;
+ if (isStartWidthValid && isStartHeightValid) {
+ scale = Math.min(currentWidth, currentHeight) / Math.min(startWidth, startHeight);
+ } else {
+ Log.e(TAG, "TaskView starting bounds are invalid: " + mThumbnailStartBounds);
+ if (isStartWidthValid) {
+ scale = currentWidth / startWidth;
+ } else if (isStartHeightValid) {
+ scale = currentHeight / startHeight;
+ } else {
+ scale = 1f;
+ }
+ }
mTargetTaskView.setScaleX(scale);
mTargetTaskView.setScaleY(scale);
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index ee93cd6..2896979 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -42,6 +42,7 @@
import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.MOTION_DOWN;
import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.MOTION_MOVE;
import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.MOTION_UP;
+import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.NAVIGATION_MODE_SWITCHED;
import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.RECENTS_ANIMATION_START_PENDING;
import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SYSUI_PROXY;
@@ -70,7 +71,6 @@
import android.os.Bundle;
import android.os.IBinder;
import android.os.Looper;
-import android.os.RemoteException;
import android.os.SystemClock;
import android.os.Trace;
import android.util.ArraySet;
@@ -102,6 +102,7 @@
import com.android.launcher3.testing.shared.TestProtocol;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.LockedUserState;
+import com.android.launcher3.util.NavigationMode;
import com.android.launcher3.util.PluginManagerWrapper;
import com.android.launcher3.util.SafeCloseable;
import com.android.launcher3.util.ScreenOnTracker;
@@ -622,6 +623,8 @@
private InputManager mInputManager;
private final Set<Integer> mTrackpadsConnected = new ArraySet<>();
+ private NavigationMode mGestureStartNavMode = null;
+
@Override
public void onCreate() {
super.onCreate();
@@ -836,14 +839,26 @@
TestLogging.recordMotionEvent(
TestProtocol.SEQUENCE_TIS, "TouchInteractionService.onInputEvent", event);
- boolean isUserUnlocked = LockedUserState.get(this).isUserUnlocked();
- if (!isUserUnlocked || (mDeviceState.isButtonNavMode()
- && !isTrackpadMotionEvent(event))) {
+ if (!LockedUserState.get(this).isUserUnlocked()) {
+ ActiveGestureLog.INSTANCE.addLog(new CompoundString("TIS.onInputEvent: ")
+ .append("Cannot process input event: user is locked"));
+ return;
+ }
+
+ NavigationMode currentNavMode = mDeviceState.getMode();
+ if (mGestureStartNavMode != null && mGestureStartNavMode != currentNavMode) {
+ ActiveGestureLog.INSTANCE.addLog(new CompoundString("TIS.onInputEvent: ")
+ .append("Navigation mode switched mid-gesture (")
+ .append(mGestureStartNavMode.name())
+ .append(" -> ")
+ .append(currentNavMode.name())
+ .append("); cancelling gesture."),
+ NAVIGATION_MODE_SWITCHED);
+ event.setAction(ACTION_CANCEL);
+ } else if (mDeviceState.isButtonNavMode() && !isTrackpadMotionEvent(event)) {
ActiveGestureLog.INSTANCE.addLog(new CompoundString("TIS.onInputEvent: ")
.append("Cannot process input event: ")
- .append(!isUserUnlocked
- ? "user is locked"
- : "using 3-button nav and event is not a trackpad event"));
+ .append("using 3-button nav and event is not a trackpad event"));
return;
}
@@ -870,6 +885,12 @@
}
}
+ if (action == ACTION_DOWN || isHoverActionWithoutConsumer) {
+ mGestureStartNavMode = currentNavMode;
+ } else if (action == ACTION_UP || action == ACTION_CANCEL) {
+ mGestureStartNavMode = null;
+ }
+
SafeCloseable traceToken = TraceHelper.INSTANCE.allowIpcs("TIS.onInputEvent");
CompoundString reasonString = action == ACTION_DOWN
diff --git a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt
index dbe2b19..20a081b 100644
--- a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt
+++ b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt
@@ -30,6 +30,7 @@
import android.view.ViewOutlineProvider
import androidx.annotation.ColorInt
import com.android.launcher3.Utilities
+import com.android.launcher3.util.ViewPool
import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.BackgroundOnly
import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.LiveTile
import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Snapshot
@@ -42,7 +43,7 @@
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.launch
-class TaskThumbnailView : View {
+class TaskThumbnailView : View, ViewPool.Reusable {
// TODO(b/335649589): Ideally create and obtain this from DI. This ViewModel should be scoped
// to [TaskView], and also shared between [TaskView] and [TaskThumbnailView]
// This is using a lazy for now because the dependencies cannot be obtained without DI.
@@ -71,7 +72,7 @@
return _measuredBounds
}
- private var cornerRadius: Float = TaskCornerRadius.get(context)
+ private var overviewCornerRadius: Float = TaskCornerRadius.get(context)
private var fullscreenCornerRadius: Float = QuickStepContract.getWindowCornerRadius(context)
constructor(context: Context?) : super(context)
@@ -100,7 +101,7 @@
invalidate()
}
}
- MainScope().launch { viewModel.recentsFullscreenProgress.collect { invalidateOutline() } }
+ MainScope().launch { viewModel.cornerRadiusProgress.collect { invalidateOutline() } }
MainScope().launch {
viewModel.inheritedScale.collect { viewModelInheritedScale ->
inheritedScale = viewModelInheritedScale
@@ -117,6 +118,11 @@
}
}
+ override fun onRecycle() {
+ // Do nothing
+ uiState = Uninitialized
+ }
+
override fun onDraw(canvas: Canvas) {
when (val uiStateVal = uiState) {
is Uninitialized -> drawBackgroundOnly(canvas, Color.BLACK)
@@ -138,7 +144,7 @@
override fun onConfigurationChanged(newConfig: Configuration?) {
super.onConfigurationChanged(newConfig)
- cornerRadius = TaskCornerRadius.get(context)
+ overviewCornerRadius = TaskCornerRadius.get(context)
fullscreenCornerRadius = QuickStepContract.getWindowCornerRadius(context)
invalidateOutline()
}
@@ -159,8 +165,8 @@
private fun getCurrentCornerRadius() =
Utilities.mapRange(
- viewModel.recentsFullscreenProgress.value,
- cornerRadius,
+ viewModel.cornerRadiusProgress.value,
+ overviewCornerRadius,
fullscreenCornerRadius
) / inheritedScale
diff --git a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModel.kt b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModel.kt
index fe21174..d8729a6 100644
--- a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModel.kt
+++ b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModel.kt
@@ -31,6 +31,7 @@
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
@@ -47,7 +48,13 @@
private val task = MutableStateFlow<Flow<Task?>>(flowOf(null))
private var boundTaskIsRunning = false
- val recentsFullscreenProgress = recentsViewData.fullscreenProgress
+ /**
+ * Progress for changes in corner radius. progress: 0 = overview corner radius; 1 = fullscreen
+ * corner radius.
+ */
+ val cornerRadiusProgress =
+ if (taskViewData.isOutlineFormedByThumbnailView) recentsViewData.fullscreenProgress
+ else MutableStateFlow(1f).asStateFlow()
val inheritedScale =
combine(recentsViewData.scale, taskViewData.scale) { recentsScale, taskScale ->
recentsScale * taskScale
diff --git a/quickstep/src/com/android/quickstep/task/viewmodel/TaskViewData.kt b/quickstep/src/com/android/quickstep/task/viewmodel/TaskViewData.kt
index a8b5112..7a9ecf2 100644
--- a/quickstep/src/com/android/quickstep/task/viewmodel/TaskViewData.kt
+++ b/quickstep/src/com/android/quickstep/task/viewmodel/TaskViewData.kt
@@ -16,9 +16,14 @@
package com.android.quickstep.task.viewmodel
+import com.android.quickstep.views.TaskViewType
import kotlinx.coroutines.flow.MutableStateFlow
-class TaskViewData {
+class TaskViewData(taskViewType: TaskViewType) {
// This is typically a View concern but it is used to invalidate rendering in other Views
val scale = MutableStateFlow(1f)
+
+ // TODO(b/331753115): This property should not be in TaskViewData once TaskView is MVVM.
+ /** Whether outline of TaskView is formed by outline thumbnail view(s). */
+ val isOutlineFormedByThumbnailView: Boolean = taskViewType != TaskViewType.DESKTOP
}
diff --git a/quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java b/quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java
index 3140fff..2398e66 100644
--- a/quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java
+++ b/quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java
@@ -40,7 +40,7 @@
SCROLLER_ANIMATION_ABORTED, TASK_APPEARED, EXPECTING_TASK_APPEARED,
FLAG_USING_OTHER_ACTIVITY_INPUT_CONSUMER, LAUNCHER_DESTROYED, RECENT_TASKS_MISSING,
INVALID_VELOCITY_ON_SWIPE_UP, RECENTS_ANIMATION_START_PENDING,
- QUICK_SWITCH_FROM_HOME_FALLBACK, QUICK_SWITCH_FROM_HOME_FAILED,
+ QUICK_SWITCH_FROM_HOME_FALLBACK, QUICK_SWITCH_FROM_HOME_FAILED, NAVIGATION_MODE_SWITCHED,
/**
* These GestureEvents are specifically associated to state flags that get set in
@@ -299,6 +299,13 @@
+ "the current page index and index 0 were missing.",
writer);
break;
+ case NAVIGATION_MODE_SWITCHED:
+ errorDetected |= printErrorIfTrue(
+ true,
+ prefix,
+ /* errorMessage= */ "Navigation mode switched mid-gesture.",
+ writer);
+ break;
case EXPECTING_TASK_APPEARED:
case MOTION_DOWN:
case SET_END_TARGET:
diff --git a/quickstep/src/com/android/quickstep/util/DesktopTask.java b/quickstep/src/com/android/quickstep/util/DesktopTask.java
index 307b2fa..a727aa2 100644
--- a/quickstep/src/com/android/quickstep/util/DesktopTask.java
+++ b/quickstep/src/com/android/quickstep/util/DesktopTask.java
@@ -18,7 +18,7 @@
import androidx.annotation.NonNull;
-import com.android.quickstep.views.TaskView;
+import com.android.quickstep.views.TaskViewType;
import com.android.systemui.shared.recents.model.Task;
import java.util.List;
@@ -34,7 +34,7 @@
public final List<Task> tasks;
public DesktopTask(@NonNull List<Task> tasks) {
- super(tasks.get(0), null, null, TaskView.Type.DESKTOP);
+ super(tasks.get(0), null, null, TaskViewType.DESKTOP);
this.tasks = tasks;
}
diff --git a/quickstep/src/com/android/quickstep/util/GroupTask.java b/quickstep/src/com/android/quickstep/util/GroupTask.java
index e8b611c..fba08a9 100644
--- a/quickstep/src/com/android/quickstep/util/GroupTask.java
+++ b/quickstep/src/com/android/quickstep/util/GroupTask.java
@@ -20,7 +20,7 @@
import androidx.annotation.Nullable;
import com.android.launcher3.util.SplitConfigurationOptions.SplitBounds;
-import com.android.quickstep.views.TaskView;
+import com.android.quickstep.views.TaskViewType;
import com.android.systemui.shared.recents.model.Task;
import java.util.Arrays;
@@ -39,19 +39,18 @@
public final Task task2;
@Nullable
public final SplitBounds mSplitBounds;
- @TaskView.Type
- public final int taskViewType;
+ public final TaskViewType taskViewType;
public GroupTask(@NonNull Task task) {
this(task, null, null);
}
public GroupTask(@NonNull Task t1, @Nullable Task t2, @Nullable SplitBounds splitBounds) {
- this(t1, t2, splitBounds, t2 != null ? TaskView.Type.GROUPED : TaskView.Type.SINGLE);
+ this(t1, t2, splitBounds, t2 != null ? TaskViewType.GROUPED : TaskViewType.SINGLE);
}
protected GroupTask(@NonNull Task t1, @Nullable Task t2, @Nullable SplitBounds splitBounds,
- @TaskView.Type int taskViewType) {
+ TaskViewType taskViewType) {
task1 = t1;
task2 = t2;
mSplitBounds = splitBounds;
diff --git a/quickstep/src/com/android/quickstep/util/SlideInRemoteTransition.kt b/quickstep/src/com/android/quickstep/util/SlideInRemoteTransition.kt
index dbeedd3..ece9583 100644
--- a/quickstep/src/com/android/quickstep/util/SlideInRemoteTransition.kt
+++ b/quickstep/src/com/android/quickstep/util/SlideInRemoteTransition.kt
@@ -36,6 +36,8 @@
private val pageSpacing: Int,
private val cornerRadius: Float,
private val interpolator: TimeInterpolator,
+ private val onStartCallback: Runnable,
+ private val onFinishCallback: Runnable,
) : RemoteTransitionStub() {
private val animationDurationMs = 500L
@@ -68,6 +70,7 @@
startT.setCrop(leash, chg.endAbsBounds).setCornerRadius(leash, cornerRadius)
}
}
+ onStartCallback.run()
startT.apply()
anim.addUpdateListener {
@@ -97,6 +100,7 @@
val t = Transaction()
try {
finishCB.onTransitionFinished(null, t)
+ onFinishCallback.run()
} catch (e: RemoteException) {
// Ignore
}
diff --git a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
index 49e1c88..0cd36f4 100644
--- a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
+++ b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
@@ -27,8 +27,10 @@
import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW
import android.content.Context
import android.graphics.Bitmap
+import android.graphics.Color
import android.graphics.Rect
import android.graphics.RectF
+import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.Drawable
import android.view.RemoteAnimationTarget
import android.view.SurfaceControl
@@ -121,7 +123,7 @@
return SplitAnimInitProps(
container.snapshotView,
container.thumbnail,
- drawable!!,
+ drawable,
fadeWithThumbnail = true,
isStagedTask = true,
iconView = container.iconView.asView()
@@ -140,7 +142,7 @@
return SplitAnimInitProps(
it.snapshotView,
it.thumbnail,
- drawable!!,
+ drawable,
fadeWithThumbnail = true,
isStagedTask = true,
iconView = it.iconView.asView()
@@ -152,15 +154,15 @@
/**
* Returns the drawable that's provided in iconView, however if that is null it falls back to
* the drawable that's in splitSelectSource. TaskView's icon drawable can be null if the
- * TaskView is scrolled far enough off screen
+ * TaskView is scrolled far enough off screen.
*
- * @return [Drawable]
+ * @return the [Drawable] icon, or a translucent drawable if none was found
*/
- fun getDrawable(iconView: TaskViewIcon, splitSelectSource: SplitSelectSource?): Drawable? {
- if (iconView.drawable == null && splitSelectSource != null) {
- return splitSelectSource.drawable
- }
- return iconView.drawable
+ fun getDrawable(iconView: TaskViewIcon, splitSelectSource: SplitSelectSource?): Drawable {
+ val drawable =
+ if (iconView.drawable == null && splitSelectSource != null) splitSelectSource.drawable
+ else iconView.drawable
+ return drawable ?: ColorDrawable(Color.TRANSPARENT)
}
/**
@@ -534,7 +536,8 @@
val appPairLaunchingAppIndex = hasChangesForBothAppPairs(launchingIconView, info)
if (appPairLaunchingAppIndex == -1) {
// Launch split app pair animation
- composeIconSplitLaunchAnimator(launchingIconView, info, t, finishCallback)
+ composeIconSplitLaunchAnimator(launchingIconView, info, t, finishCallback,
+ cornerRadius)
} else {
composeFullscreenIconSplitLaunchAnimator(
launchingIconView,
@@ -652,15 +655,15 @@
* To find the root shell leash that we want to fade in, we do the following: The Changes we
* receive in transitionInfo are structured like this
*
- * Root (grandparent)
+ * (0) Root (grandparent)
* |
- * |--> Split Root 1 (left/top side parent) (WINDOWING_MODE_MULTI_WINDOW)
+ * |--> (1) Split Root 1 (left/top side parent) (WINDOWING_MODE_MULTI_WINDOW)
* | |
- * | --> App 1 (left/top side child) (WINDOWING_MODE_MULTI_WINDOW)
+ * | --> (1a) App 1 (left/top side child) (WINDOWING_MODE_MULTI_WINDOW)
* |--> Divider
- * |--> Split Root 2 (right/bottom side parent) (WINDOWING_MODE_MULTI_WINDOW)
+ * |--> (2) Split Root 2 (right/bottom side parent) (WINDOWING_MODE_MULTI_WINDOW)
* |
- * --> App 2 (right/bottom side child) (WINDOWING_MODE_MULTI_WINDOW)
+ * --> (2a) App 2 (right/bottom side child) (WINDOWING_MODE_MULTI_WINDOW)
*
* We want to animate the Root (grandparent) so that it affects both apps and the divider. To do
* this, we find one of the nodes with WINDOWING_MODE_MULTI_WINDOW (one of the left-side ones,
@@ -675,7 +678,8 @@
launchingIconView: AppPairIcon,
transitionInfo: TransitionInfo,
t: Transaction,
- finishCallback: Runnable
+ finishCallback: Runnable,
+ windowRadius: Float
) {
// If launching an app pair from Taskbar inside of an app context (no access to Launcher),
// use the scale-up animation
@@ -695,48 +699,25 @@
// Create an AnimatorSet that will run both shell and launcher transitions together
val launchAnimation = AnimatorSet()
- var rootCandidate: Change? = null
- for (change in transitionInfo.changes) {
- val taskInfo: RunningTaskInfo = change.taskInfo ?: continue
+ val splitRoots: Pair<Change, List<Change>>? =
+ SplitScreenUtils.extractTopParentAndChildren(transitionInfo)
+ check(splitRoots != null) { "Could not find split roots" }
- // TODO (b/316490565): Replace this logic when SplitBounds is available to
- // startAnimation() and we can know the precise taskIds of launching tasks.
- // Find a change that has WINDOWING_MODE_MULTI_WINDOW.
- if (
- taskInfo.windowingMode == WINDOWING_MODE_MULTI_WINDOW &&
- (change.mode == TRANSIT_OPEN || change.mode == TRANSIT_TO_FRONT)
- ) {
- // Check if it is a left/top app.
- val isLeftTopApp =
- (dp.isLeftRightSplit && change.endAbsBounds.left == 0) ||
- (!dp.isLeftRightSplit && change.endAbsBounds.top == 0)
- if (isLeftTopApp) {
- // Found one!
- rootCandidate = change
- break
- }
- }
- }
-
- // If we could not find a proper root candidate, something went wrong.
- check(rootCandidate != null) { "Could not find a split root candidate" }
+ // Will point to change (0) in diagram above
+ val mainRootCandidate = splitRoots.first
+ // Will contain changes (1) and (2) in diagram above
+ val leafRoots: List<Change> = splitRoots.second
// Find the place where our left/top app window meets the divider (used for the
// launcher side animation)
- val dividerPos =
- if (dp.isLeftRightSplit) rootCandidate.endAbsBounds.right
- else rootCandidate.endAbsBounds.bottom
-
- // Recurse up the tree until parent is null, then we've found our root.
- var parentToken: WindowContainerToken? = rootCandidate.parent
- while (parentToken != null) {
- rootCandidate = transitionInfo.getChange(parentToken) ?: break
- parentToken = rootCandidate.parent
+ val leftTopApp = leafRoots.single { change ->
+ (dp.isLeftRightSplit && change.endAbsBounds.left == 0) ||
+ (!dp.isLeftRightSplit && change.endAbsBounds.top == 0)
}
-
- // Make sure nothing weird happened, like getChange() returning null.
- check(rootCandidate != null) { "Failed to find a root leash" }
+ val dividerPos =
+ if (dp.isLeftRightSplit) leftTopApp.endAbsBounds.right
+ else leftTopApp.endAbsBounds.bottom
// Create a new floating view in Launcher, positioned above the launching icon
val drawableArea = launchingIconView.iconDrawableArea
@@ -755,9 +736,19 @@
)
floatingView.bringToFront()
- launchAnimation.play(
- getIconLaunchValueAnimator(t, dp, finishCallback, launcher, floatingView, rootCandidate)
+ val iconLaunchValueAnimator = getIconLaunchValueAnimator(t, dp, finishCallback, launcher,
+ floatingView, mainRootCandidate)
+ iconLaunchValueAnimator.addListener(
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationStart(animation: Animator, isReverse: Boolean) {
+ for (c in leafRoots) {
+ t.setCornerRadius(c.leash, windowRadius)
+ t.apply()
+ }
+ }
+ }
)
+ launchAnimation.play(iconLaunchValueAnimator)
launchAnimation.start()
}
diff --git a/quickstep/src/com/android/quickstep/util/SplitScreenUtils.kt b/quickstep/src/com/android/quickstep/util/SplitScreenUtils.kt
index 4820c35..d58cb91 100644
--- a/quickstep/src/com/android/quickstep/util/SplitScreenUtils.kt
+++ b/quickstep/src/com/android/quickstep/util/SplitScreenUtils.kt
@@ -16,11 +16,21 @@
package com.android.quickstep.util
+import android.util.Log
+import android.view.WindowManager
+import android.view.WindowManager.TRANSIT_OPEN
+import android.view.WindowManager.TRANSIT_TO_FRONT
+import android.window.TransitionInfo
+import android.window.TransitionInfo.Change
+import android.window.TransitionInfo.FLAG_FIRST_CUSTOM
import com.android.launcher3.util.SplitConfigurationOptions
import com.android.wm.shell.util.SplitBounds
+import java.lang.IllegalStateException
class SplitScreenUtils {
companion object {
+ private const val TAG = "SplitScreenUtils"
+
// TODO(b/254378592): Remove these methods when the two classes are reunited
/** Converts the shell version of SplitBounds to the launcher version */
@JvmStatic
@@ -39,5 +49,51 @@
)
}
}
+
+ /**
+ * Given a TransitionInfo, generates the tree structure for those changes and extracts out
+ * the top most root and it's two immediate children.
+ * Changes can be provided in any order.
+ *
+ * @return a [Pair] where first -> top most split root,
+ * second -> [List] of 2, leftTop/bottomRight stage roots
+ */
+ fun extractTopParentAndChildren(transitionInfo: TransitionInfo):
+ Pair<Change, List<Change>>? {
+ val parentToChildren = mutableMapOf<Change, MutableList<Change>>()
+ val hasParent = mutableSetOf<Change>()
+ // filter out anything that isn't opening and the divider
+ val taskChanges: List<Change> = transitionInfo.changes
+ .filter { change -> (change.mode == TRANSIT_OPEN ||
+ change.mode == TRANSIT_TO_FRONT) && change.flags < FLAG_FIRST_CUSTOM}
+ .toList()
+
+ // 1. Build Parent-Child Relationships
+ for (change in taskChanges) {
+ // TODO (b/316490565): Replace this logic when SplitBounds is available to
+ // startAnimation() and we can know the precise taskIds of launching tasks.
+ change.parent?.let { parent ->
+ parentToChildren
+ .getOrPut(transitionInfo.getChange(parent)!!) { mutableListOf() }
+ .add(change)
+ hasParent.add(change)
+ }
+ }
+
+ // 2. Find Top Parent
+ val topParent = taskChanges.firstOrNull { it !in hasParent }
+
+ // 3. Extract Immediate Children
+ return if (topParent != null) {
+ val immediateChildren = parentToChildren.getOrDefault(topParent, emptyList())
+ if (immediateChildren.size != 2) {
+ throw IllegalStateException("incorrect split stage root size")
+ }
+ Pair(topParent, immediateChildren)
+ } else {
+ Log.w(TAG, "No top parent found")
+ null
+ }
+ }
}
}
diff --git a/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt b/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt
index 55bbd50..4333c8b 100644
--- a/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt
+++ b/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt
@@ -27,6 +27,7 @@
import android.view.View
import android.view.ViewGroup
import androidx.core.view.updateLayoutParams
+import com.android.launcher3.Flags.enableRefactorTaskThumbnail
import com.android.launcher3.R
import com.android.launcher3.util.RunnableList
import com.android.launcher3.util.SplitConfigurationOptions
@@ -35,12 +36,13 @@
import com.android.launcher3.util.rects.set
import com.android.quickstep.BaseContainerInterface
import com.android.quickstep.TaskOverlayFactory
+import com.android.quickstep.task.thumbnail.TaskThumbnailView
import com.android.quickstep.util.RecentsOrientedState
import com.android.systemui.shared.recents.model.Task
/** TaskView that contains all tasks that are part of the desktop. */
class DesktopTaskView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
- TaskView(context, attrs) {
+ TaskView(context, attrs, type = TaskViewType.DESKTOP) {
private val snapshotDrawParams =
object : FullscreenDrawParams(context) {
@@ -48,7 +50,7 @@
override fun computeTaskCornerRadius(context: Context) =
computeWindowCornerRadius(context)
}
- private val taskThumbnailViewPool =
+ private val taskThumbnailViewDeprecatedPool =
ViewPool<TaskThumbnailViewDeprecated>(
context,
this,
@@ -89,6 +91,66 @@
childCountAtInflation = childCount
}
+ /** Updates this desktop task to the gives task list defined in `tasks` */
+ fun bind(
+ tasks: List<Task>,
+ orientedState: RecentsOrientedState,
+ taskOverlayFactory: TaskOverlayFactory
+ ) {
+ if (DEBUG) {
+ val sb = StringBuilder()
+ sb.append("bind tasks=").append(tasks.size).append("\n")
+ tasks.forEach { sb.append(" key=${it.key}\n") }
+ Log.d(TAG, sb.toString())
+ }
+ cancelPendingLoadTasks()
+ taskContainers =
+ tasks.map { task ->
+ val snapshotView =
+ if (enableRefactorTaskThumbnail()) {
+ TaskThumbnailView(context)
+ } else {
+ taskThumbnailViewDeprecatedPool.view
+ }
+ .also { snapshotView ->
+ addView(
+ snapshotView,
+ // Add snapshotView to the front after initial views e.g. icon and
+ // background.
+ childCountAtInflation,
+ LayoutParams(
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT
+ )
+ )
+ }
+ TaskContainer(
+ this,
+ task,
+ snapshotView,
+ iconView,
+ TransformingTouchDelegate(iconView.asView()),
+ SplitConfigurationOptions.STAGE_POSITION_UNDEFINED,
+ digitalWellBeingToast = null,
+ showWindowsView = null,
+ taskOverlayFactory
+ )
+ }
+ taskContainers.forEach { it.bind() }
+ setOrientationState(orientedState)
+ }
+
+ override fun onRecycle() {
+ super.onRecycle()
+ visibility = VISIBLE
+ taskContainers.forEach {
+ if (!enableRefactorTaskThumbnail()) {
+ removeView(it.thumbnailViewDeprecated)
+ taskThumbnailViewDeprecatedPool.recycle(it.thumbnailViewDeprecated)
+ }
+ }
+ }
+
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
val containerWidth = MeasureSpec.getSize(widthMeasureSpec)
@@ -151,77 +213,6 @@
}
}
- override fun onRecycle() {
- super.onRecycle()
- visibility = VISIBLE
- }
-
- /** Updates this desktop task to the gives task list defined in `tasks` */
- fun bind(
- tasks: List<Task>,
- orientedState: RecentsOrientedState,
- taskOverlayFactory: TaskOverlayFactory
- ) {
- if (DEBUG) {
- val sb = StringBuilder()
- sb.append("bind tasks=").append(tasks.size).append("\n")
- tasks.forEach { sb.append(" key=${it.key}\n") }
- Log.d(TAG, sb.toString())
- }
- cancelPendingLoadTasks()
-
- if (!isTaskContainersInitialized()) {
- taskContainers = arrayListOf()
- }
- val taskContainers = taskContainers as ArrayList
- taskContainers.ensureCapacity(tasks.size)
- tasks.forEachIndexed { index, task ->
- val thumbnailViewDeprecated: TaskThumbnailViewDeprecated
- if (index >= taskContainers.size) {
- thumbnailViewDeprecated = taskThumbnailViewPool.view
- // Add thumbnailView from to position after the initial child views.
- addView(
- thumbnailViewDeprecated,
- childCountAtInflation,
- LayoutParams(
- ViewGroup.LayoutParams.WRAP_CONTENT,
- ViewGroup.LayoutParams.WRAP_CONTENT
- )
- )
- } else {
- thumbnailViewDeprecated = taskContainers[index].thumbnailViewDeprecated
- }
- val taskContainer =
- TaskContainer(
- this,
- task,
- // TODO(b/338360089): Support new TTV for DesktopTaskView
- thumbnailView = null,
- thumbnailViewDeprecated,
- iconView,
- TransformingTouchDelegate(iconView.asView()),
- SplitConfigurationOptions.STAGE_POSITION_UNDEFINED,
- digitalWellBeingToast = null,
- showWindowsView = null,
- taskOverlayFactory
- )
- if (index >= taskContainers.size) {
- taskContainers.add(taskContainer)
- } else {
- taskContainers[index] = taskContainer
- }
- taskContainer.bind()
- }
- repeat(taskContainers.size - tasks.size) {
- with(taskContainers.removeLast()) {
- removeView(thumbnailViewDeprecated)
- taskThumbnailViewPool.recycle(thumbnailViewDeprecated)
- }
- }
-
- setOrientationState(orientedState)
- }
-
override fun needsUpdate(dataChange: Int, flag: Int) =
if (flag == FLAG_UPDATE_THUMBNAIL) super.needsUpdate(dataChange, flag) else false
diff --git a/quickstep/src/com/android/quickstep/views/FloatingAppPairBackground.kt b/quickstep/src/com/android/quickstep/views/FloatingAppPairBackground.kt
index e024995..6bbd6b2 100644
--- a/quickstep/src/com/android/quickstep/views/FloatingAppPairBackground.kt
+++ b/quickstep/src/com/android/quickstep/views/FloatingAppPairBackground.kt
@@ -57,6 +57,7 @@
private val container: RecentsViewContainer
private val backgroundPaint = Paint(Paint.ANTI_ALIAS_FLAG)
+ private val dividerPaint = Paint(Paint.ANTI_ALIAS_FLAG)
// Animation interpolators
protected val expandXInterpolator: Interpolator
@@ -105,13 +106,15 @@
)
// Find device-specific measurements
- deviceCornerRadius = QuickStepContract.getWindowCornerRadius(container.asContext())
+ val resources = context.resources
+ deviceCornerRadius = QuickStepContract.getWindowCornerRadius(context)
deviceHalfDividerSize =
- container.asContext().resources.getDimensionPixelSize(R.dimen.multi_window_task_divider_size) / 2f
+ resources.getDimensionPixelSize(R.dimen.multi_window_task_divider_size) / 2f
val dividerCenterPos = dividerPos + deviceHalfDividerSize
desiredSplitRatio =
if (dp.isLeftRightSplit) dividerCenterPos / dp.widthPx
else dividerCenterPos / dp.heightPx
+ dividerPaint.color = resources.getColor(R.color.taskbar_background_dark, null /*theme*/)
}
override fun draw(canvas: Canvas) {
@@ -153,8 +156,12 @@
val leftSide = RectF(0f, 0f, dividerCenterPos - changingDividerSize, height)
// The right half of the background image
val rightSide = RectF(dividerCenterPos + changingDividerSize, 0f, width, height)
+ // Middle part is for divider background
+ val middleRect = RectF(leftSide.right - deviceHalfDividerSize, 0f,
+ rightSide.left + deviceHalfDividerSize, height)
// Draw background
+ canvas.drawRect(middleRect, dividerPaint)
drawCustomRoundedRect(
canvas,
leftSide,
@@ -251,8 +258,12 @@
val topSide = RectF(0f, 0f, width, dividerCenterPos - changingDividerSize)
// The bottom half of the background image
val bottomSide = RectF(0f, dividerCenterPos + changingDividerSize, width, height)
+ // Middle part is for divider background
+ val middleRect = RectF(0f, topSide.bottom - deviceHalfDividerSize,
+ width, bottomSide.top + deviceHalfDividerSize)
// Draw background
+ canvas.drawRect(middleRect, dividerPaint)
drawCustomRoundedRect(
canvas,
topSide,
diff --git a/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt b/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt
index b070244..6523ba7 100644
--- a/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt
+++ b/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt
@@ -49,7 +49,7 @@
* (Icon loading sold separately, fees may apply. Shipping & Handling for Overlays not included).
*/
class GroupedTaskView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
- TaskView(context, attrs) {
+ TaskView(context, attrs, type = TaskViewType.GROUPED) {
// TODO(b/336612373): Support new TTV for GroupedTaskView
var splitBoundsConfig: SplitConfigurationOptions.SplitBounds? = null
private set
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index ee1b3e7..7b6d383 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -221,7 +221,7 @@
import com.android.systemui.shared.system.TaskStackChangeListeners;
import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource;
import com.android.wm.shell.common.pip.IPipAnimationListener;
-import com.android.wm.shell.shared.DesktopModeStatus;
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import kotlin.Unit;
@@ -1824,7 +1824,7 @@
// If we need to remove half of a pair of tasks, force a TaskView with Type.SINGLE
// to be a temporary container for the remaining task.
TaskView taskView = getTaskViewFromPool(
- isRemovalNeeded ? TaskView.Type.SINGLE : groupTask.taskViewType);
+ isRemovalNeeded ? TaskViewType.SINGLE : groupTask.taskViewType);
if (taskView instanceof GroupedTaskView) {
boolean firstTaskIsLeftTopTask =
groupTask.mSplitBounds.leftTopTaskId == groupTask.task1.key.id;
@@ -2600,16 +2600,16 @@
* Handle the edge case where Recents could increment task count very high over long
* period of device usage. Probably will never happen, but meh.
*/
- private TaskView getTaskViewFromPool(@TaskView.Type int type) {
+ private TaskView getTaskViewFromPool(TaskViewType type) {
TaskView taskView;
switch (type) {
- case TaskView.Type.GROUPED:
+ case GROUPED:
taskView = mGroupedTaskViewPool.getView();
break;
- case TaskView.Type.DESKTOP:
+ case DESKTOP:
taskView = mDesktopTaskViewPool.getView();
break;
- case TaskView.Type.SINGLE:
+ case SINGLE:
default:
taskView = mTaskViewPool.getView();
}
@@ -2840,12 +2840,12 @@
// Add an empty view for now until the task plan is loaded and applied
final TaskView taskView;
if (needDesktopTask) {
- taskView = getTaskViewFromPool(TaskView.Type.DESKTOP);
+ taskView = getTaskViewFromPool(TaskViewType.DESKTOP);
mTmpRunningTasks = Arrays.copyOf(runningTasks, runningTasks.length);
((DesktopTaskView) taskView).bind(Arrays.asList(mTmpRunningTasks),
mOrientationState, mTaskOverlayFactory);
} else if (needGroupTaskView) {
- taskView = getTaskViewFromPool(TaskView.Type.GROUPED);
+ taskView = getTaskViewFromPool(TaskViewType.GROUPED);
mTmpRunningTasks = new Task[]{runningTasks[0], runningTasks[1]};
// When we create a placeholder task view mSplitBoundsConfig will be null, but with
// the actual app running we won't need to show the thumbnail until all the tasks
@@ -2853,7 +2853,7 @@
((GroupedTaskView)taskView).bind(mTmpRunningTasks[0], mTmpRunningTasks[1],
mOrientationState, mTaskOverlayFactory, mSplitBoundsConfig);
} else {
- taskView = getTaskViewFromPool(TaskView.Type.SINGLE);
+ taskView = getTaskViewFromPool(TaskViewType.SINGLE);
// The temporary running task is only used for the duration between the start of the
// gesture and the task list is loaded and applied
mTmpRunningTasks = new Task[]{runningTasks[0]};
diff --git a/quickstep/src/com/android/quickstep/views/TaskContainer.kt b/quickstep/src/com/android/quickstep/views/TaskContainer.kt
index 2e01e7e..0648986 100644
--- a/quickstep/src/com/android/quickstep/views/TaskContainer.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskContainer.kt
@@ -38,8 +38,7 @@
class TaskContainer(
val taskView: TaskView,
val task: Task,
- val thumbnailView: TaskThumbnailView?,
- val thumbnailViewDeprecated: TaskThumbnailViewDeprecated,
+ val snapshotView: View,
val iconView: TaskViewIcon,
/**
* This technically can be a vanilla [android.view.TouchDelegate] class, however that class
@@ -57,12 +56,29 @@
val overlay: TaskOverlayFactory.TaskOverlay<*> = taskOverlayFactory.createOverlay(this)
val taskContainerData = TaskContainerData()
- val snapshotView: View
- get() = thumbnailView ?: thumbnailViewDeprecated
+ init {
+ if (enableRefactorTaskThumbnail()) {
+ require(snapshotView is TaskThumbnailView)
+ } else {
+ require(snapshotView is TaskThumbnailViewDeprecated)
+ }
+ }
+
+ val thumbnailView: TaskThumbnailView
+ get() {
+ require(enableRefactorTaskThumbnail())
+ return snapshotView as TaskThumbnailView
+ }
+
+ val thumbnailViewDeprecated: TaskThumbnailViewDeprecated
+ get() {
+ require(!enableRefactorTaskThumbnail())
+ return snapshotView as TaskThumbnailViewDeprecated
+ }
// TODO(b/349120849): Extract ThumbnailData from TaskContainerData/TaskThumbnailViewModel
val thumbnail: Bitmap?
- get() = thumbnailViewDeprecated.thumbnail
+ get() = if (enableRefactorTaskThumbnail()) null else thumbnailViewDeprecated.thumbnail
// TODO(b/334826842): Support shouldShowSplashView for new TTV.
val shouldShowSplashView: Boolean
@@ -100,13 +116,14 @@
fun destroy() {
digitalWellBeingToast?.destroy()
- thumbnailView?.let { taskView.removeView(it) }
+ if (enableRefactorTaskThumbnail()) {
+ taskView.removeView(thumbnailView)
+ }
overlay.destroy()
}
fun bind() {
- if (enableRefactorTaskThumbnail() && thumbnailView != null) {
- thumbnailViewDeprecated.setTaskOverlay(overlay)
+ if (enableRefactorTaskThumbnail()) {
bindThumbnailView()
overlay.init()
} else {
@@ -119,7 +136,7 @@
fun bindThumbnailView() {
// TODO(b/343364498): Existing view has shouldShowScreenshot as an override as well but
// this should be decided inside TaskThumbnailViewModel.
- thumbnailView?.viewModel?.bind(TaskThumbnail(task.key.id, taskView.isRunningTask))
+ thumbnailView.viewModel.bind(TaskThumbnail(task.key.id, taskView.isRunningTask))
}
fun setOverlayEnabled(enabled: Boolean) {
diff --git a/quickstep/src/com/android/quickstep/views/TaskThumbnailViewDeprecated.java b/quickstep/src/com/android/quickstep/views/TaskThumbnailViewDeprecated.java
index 5b7e6c7..56ca043 100644
--- a/quickstep/src/com/android/quickstep/views/TaskThumbnailViewDeprecated.java
+++ b/quickstep/src/com/android/quickstep/views/TaskThumbnailViewDeprecated.java
@@ -165,17 +165,6 @@
}
/**
- * Sets TaskOverlay without binding a task.
- *
- * @deprecated Should only be used when using new
- * {@link com.android.quickstep.task.thumbnail.TaskThumbnailView}.
- */
- @Deprecated
- public void setTaskOverlay(TaskOverlay<?> overlay) {
- mOverlay = overlay;
- }
-
- /**
* Updates the thumbnail.
*
* @param refreshNow whether the {@code thumbnailData} will be used to redraw immediately.
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.kt b/quickstep/src/com/android/quickstep/views/TaskView.kt
index 9977d30..2e07e36 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskView.kt
@@ -102,7 +102,8 @@
defStyleAttr: Int = 0,
defStyleRes: Int = 0,
focusBorderAnimator: BorderAnimator? = null,
- hoverBorderAnimator: BorderAnimator? = null
+ hoverBorderAnimator: BorderAnimator? = null,
+ type: TaskViewType = TaskViewType.SINGLE
) : FrameLayout(context, attrs), ViewPool.Reusable {
/**
* Used in conjunction with [onTaskListVisibilityChanged], providing more granularity on which
@@ -112,18 +113,7 @@
@IntDef(FLAG_UPDATE_ALL, FLAG_UPDATE_ICON, FLAG_UPDATE_THUMBNAIL, FLAG_UPDATE_CORNER_RADIUS)
annotation class TaskDataChanges
- /** Type of task view */
- @Retention(AnnotationRetention.SOURCE)
- @IntDef(Type.SINGLE, Type.GROUPED, Type.DESKTOP)
- annotation class Type {
- companion object {
- const val SINGLE = 1
- const val GROUPED = 2
- const val DESKTOP = 3
- }
- }
-
- val taskViewData = TaskViewData()
+ val taskViewData = TaskViewData(type)
val taskIds: IntArray
/** Returns a copy of integer array containing taskIds of all tasks in the TaskView. */
get() = taskContainers.map { it.task.key.id }.toIntArray()
@@ -671,24 +661,22 @@
taskOverlayFactory: TaskOverlayFactory
): TaskContainer {
val thumbnailViewDeprecated: TaskThumbnailViewDeprecated = findViewById(thumbnailViewId)!!
- val thumbnailView: TaskThumbnailView?
- if (enableRefactorTaskThumbnail()) {
- val indexOfSnapshotView = indexOfChild(thumbnailViewDeprecated)
- thumbnailView =
+ val snapshotView =
+ if (enableRefactorTaskThumbnail()) {
+ thumbnailViewDeprecated.visibility = GONE
+ val indexOfSnapshotView = indexOfChild(thumbnailViewDeprecated)
TaskThumbnailView(context).apply {
layoutParams = thumbnailViewDeprecated.layoutParams
addView(this, indexOfSnapshotView)
}
- thumbnailViewDeprecated.visibility = GONE
- } else {
- thumbnailView = null
- }
+ } else {
+ thumbnailViewDeprecated
+ }
val iconView = getOrInflateIconView(iconViewId)
return TaskContainer(
this,
task,
- thumbnailView,
- thumbnailViewDeprecated,
+ snapshotView,
iconView,
TransformingTouchDelegate(iconView.asView()),
stagePosition,
@@ -710,8 +698,6 @@
.inflate() as TaskViewIcon
}
- protected fun isTaskContainersInitialized() = this::taskContainers.isInitialized
-
fun containsMultipleTasks() = taskContainers.size > 1
/**
diff --git a/quickstep/src/com/android/quickstep/views/TaskViewType.kt b/quickstep/src/com/android/quickstep/views/TaskViewType.kt
new file mode 100644
index 0000000..b2a32a9
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/views/TaskViewType.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2024 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.views
+
+/** Type of the [TaskView] */
+enum class TaskViewType {
+ SINGLE,
+ GROUPED,
+ DESKTOP
+}
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/model/WidgetsPredictionsRequesterTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/model/WidgetsPredictionsRequesterTest.kt
index 039dce4..4ea74df 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/model/WidgetsPredictionsRequesterTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/model/WidgetsPredictionsRequesterTest.kt
@@ -30,6 +30,7 @@
import com.android.launcher3.InvariantDeviceProfile
import com.android.launcher3.LauncherAppState
import com.android.launcher3.icons.IconCache
+import com.android.launcher3.model.WidgetPredictionsRequester.LAUNCH_LOCATION
import com.android.launcher3.model.WidgetPredictionsRequester.buildBundleForPredictionSession
import com.android.launcher3.model.WidgetPredictionsRequester.filterPredictions
import com.android.launcher3.model.WidgetPredictionsRequester.notOnUiSurfaceFilter
@@ -103,7 +104,7 @@
fun buildBundleForPredictionSession_includesAddedAppWidgets() {
val existingWidgets = arrayListOf(widget1aInfo, widget1bInfo, widget2Info)
- val bundle = buildBundleForPredictionSession(existingWidgets, TEST_UI_SURFACE)
+ val bundle = buildBundleForPredictionSession(existingWidgets)
val addedWidgetsBundleExtra =
bundle.getParcelableArrayList(BUNDLE_KEY_ADDED_APP_WIDGETS, AppTarget::class.java)
@@ -213,7 +214,7 @@
.setClassName(providerClassName)
.build()
return AppTargetEvent.Builder(appTarget, AppTargetEvent.ACTION_PIN)
- .setLaunchLocation(TEST_UI_SURFACE)
+ .setLaunchLocation(LAUNCH_LOCATION)
.build()
}
}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModelTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModelTest.kt
index a394b65..b78f871 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModelTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModelTest.kt
@@ -30,6 +30,7 @@
import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Uninitialized
import com.android.quickstep.task.viewmodel.TaskContainerData
import com.android.quickstep.task.viewmodel.TaskViewData
+import com.android.quickstep.views.TaskViewType
import com.android.systemui.shared.recents.model.Task
import com.android.systemui.shared.recents.model.ThumbnailData
import com.google.common.truth.Truth.assertThat
@@ -42,12 +43,14 @@
@RunWith(AndroidJUnit4::class)
class TaskThumbnailViewModelTest {
+ private var taskViewType = TaskViewType.SINGLE
private val recentsViewData = RecentsViewData()
- private val taskViewData = TaskViewData()
+ private val taskViewData by lazy { TaskViewData(taskViewType) }
private val taskContainerData = TaskContainerData()
private val tasksRepository = FakeTasksRepository()
- private val systemUnderTest =
+ private val systemUnderTest by lazy {
TaskThumbnailViewModel(recentsViewData, taskViewData, taskContainerData, tasksRepository)
+ }
private val tasks = (0..5).map(::createTaskWithId)
@@ -66,14 +69,26 @@
}
@Test
- fun setRecentsFullscreenProgress_thenProgressIsPassedThrough() = runTest {
+ fun setRecentsFullscreenProgress_thenCornerRadiusProgressIsPassedThrough() = runTest {
recentsViewData.fullscreenProgress.value = 0.5f
- assertThat(systemUnderTest.recentsFullscreenProgress.first()).isEqualTo(0.5f)
+ assertThat(systemUnderTest.cornerRadiusProgress.first()).isEqualTo(0.5f)
recentsViewData.fullscreenProgress.value = 0.6f
- assertThat(systemUnderTest.recentsFullscreenProgress.first()).isEqualTo(0.6f)
+ assertThat(systemUnderTest.cornerRadiusProgress.first()).isEqualTo(0.6f)
+ }
+
+ @Test
+ fun setRecentsFullscreenProgress_thenCornerRadiusProgressIsConstantForDesktop() = runTest {
+ taskViewType = TaskViewType.DESKTOP
+ recentsViewData.fullscreenProgress.value = 0.5f
+
+ assertThat(systemUnderTest.cornerRadiusProgress.first()).isEqualTo(1f)
+
+ recentsViewData.fullscreenProgress.value = 0.6f
+
+ assertThat(systemUnderTest.cornerRadiusProgress.first()).isEqualTo(1f)
}
@Test
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/GroupTaskTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/GroupTaskTest.kt
index a6d3887..f11cd0b 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/GroupTaskTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/GroupTaskTest.kt
@@ -21,7 +21,7 @@
import android.graphics.Rect
import com.android.launcher3.util.LauncherMultivalentJUnit
import com.android.launcher3.util.SplitConfigurationOptions
-import com.android.quickstep.views.TaskView
+import com.android.quickstep.views.TaskViewType
import com.android.systemui.shared.recents.model.Task
import com.android.wm.shell.common.split.SplitScreenConstants
import com.google.common.truth.Truth.assertThat
@@ -68,8 +68,8 @@
2,
SplitScreenConstants.SNAP_TO_50_50
)
- val task1 = GroupTask(createTask(1), createTask(2), splitBounds, TaskView.Type.GROUPED)
- val task2 = GroupTask(createTask(1), createTask(2), splitBounds, TaskView.Type.GROUPED)
+ val task1 = GroupTask(createTask(1), createTask(2), splitBounds, TaskViewType.GROUPED)
+ val task2 = GroupTask(createTask(1), createTask(2), splitBounds, TaskViewType.GROUPED)
assertThat(task1).isEqualTo(task2)
}
@@ -91,15 +91,15 @@
2,
SplitScreenConstants.SNAP_TO_30_70
)
- val task1 = GroupTask(createTask(1), createTask(2), splitBounds1, TaskView.Type.GROUPED)
- val task2 = GroupTask(createTask(1), createTask(2), splitBounds2, TaskView.Type.GROUPED)
+ val task1 = GroupTask(createTask(1), createTask(2), splitBounds1, TaskViewType.GROUPED)
+ val task2 = GroupTask(createTask(1), createTask(2), splitBounds2, TaskViewType.GROUPED)
assertThat(task1).isNotEqualTo(task2)
}
@Test
fun testGroupTask_differentType_isNotEqual() {
- val task1 = GroupTask(createTask(1), null, null, TaskView.Type.SINGLE)
- val task2 = GroupTask(createTask(1), null, null, TaskView.Type.DESKTOP)
+ val task1 = GroupTask(createTask(1), null, null, TaskViewType.SINGLE)
+ val task2 = GroupTask(createTask(1), null, null, TaskViewType.DESKTOP)
assertThat(task1).isNotEqualTo(task2)
}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitAnimationControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitAnimationControllerTest.kt
index fd7ecb0..a9f5dcd 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitAnimationControllerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitAnimationControllerTest.kt
@@ -276,7 +276,7 @@
whenever(mockAppPairIcon.context).thenReturn(mockContextThemeWrapper)
doNothing()
.whenever(spySplitAnimationController)
- .composeIconSplitLaunchAnimator(any(), any(), any(), any())
+ .composeIconSplitLaunchAnimator(any(), any(), any(), any(), any())
doReturn(-1).whenever(spySplitAnimationController).hasChangesForBothAppPairs(any(), any())
spySplitAnimationController.playSplitLaunchAnimation(
@@ -296,7 +296,7 @@
)
verify(spySplitAnimationController)
- .composeIconSplitLaunchAnimator(any(), any(), any(), any())
+ .composeIconSplitLaunchAnimator(any(), any(), any(), any(), any())
}
@Test
diff --git a/quickstep/tests/src/com/android/quickstep/DesktopSystemShortcutTest.kt b/quickstep/tests/src/com/android/quickstep/DesktopSystemShortcutTest.kt
index f160ce2..d9d5585 100644
--- a/quickstep/tests/src/com/android/quickstep/DesktopSystemShortcutTest.kt
+++ b/quickstep/tests/src/com/android/quickstep/DesktopSystemShortcutTest.kt
@@ -40,7 +40,7 @@
import com.android.systemui.shared.recents.model.Task.TaskKey
import com.android.window.flags.Flags
import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource
-import com.android.wm.shell.shared.DesktopModeStatus
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import com.google.common.truth.Truth.assertThat
import org.junit.After
import org.junit.Before
@@ -191,7 +191,6 @@
return TaskContainer(
taskView,
task,
- thumbnailView = null,
thumbnailViewDeprecated,
iconView,
transformingTouchDelegate,
diff --git a/quickstep/tests/src/com/android/quickstep/RecentTasksListTest.java b/quickstep/tests/src/com/android/quickstep/RecentTasksListTest.java
index ce16b70..5d00255 100644
--- a/quickstep/tests/src/com/android/quickstep/RecentTasksListTest.java
+++ b/quickstep/tests/src/com/android/quickstep/RecentTasksListTest.java
@@ -32,7 +32,7 @@
import com.android.launcher3.util.LooperExecutor;
import com.android.quickstep.util.GroupTask;
-import com.android.quickstep.views.TaskView;
+import com.android.quickstep.views.TaskViewType;
import com.android.systemui.shared.recents.model.Task;
import com.android.wm.shell.util.GroupedRecentTaskInfo;
@@ -125,7 +125,7 @@
Integer.MAX_VALUE /* numTasks */, -1 /* requestId */, false /* loadKeysOnly */);
assertEquals(1, taskList.size());
- assertEquals(TaskView.Type.DESKTOP, taskList.get(0).taskViewType);
+ assertEquals(TaskViewType.DESKTOP, taskList.get(0).taskViewType);
List<Task> actualFreeformTasks = taskList.get(0).getTasks();
assertEquals(3, actualFreeformTasks.size());
assertEquals(1, actualFreeformTasks.get(0).key.id);
diff --git a/res/layout/widgets_two_pane_sheet.xml b/res/layout/widgets_two_pane_sheet.xml
index bb2b7bd..ce5eed9 100644
--- a/res/layout/widgets_two_pane_sheet.xml
+++ b/res/layout/widgets_two_pane_sheet.xml
@@ -48,20 +48,23 @@
android:textSize="24sp" />
<TextView
- android:id="@+id/no_widgets_text"
- style="@style/PrimaryHeadline"
+ android:id="@+id/widget_picker_description"
android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:gravity="center"
- android:textSize="18sp"
+ android:layout_height="wrap_content"
+ android:gravity="center_horizontal"
+ android:layout_below="@id/title"
+ android:maxLines="1"
+ android:paddingHorizontal="@dimen/widget_list_horizontal_margin_two_pane"
+ android:textColor="?attr/widgetPickerDescriptionColor"
android:visibility="gone"
- tools:text="@string/no_widgets_available" />
+ android:lineHeight="20sp"
+ android:textSize="14sp" />
<LinearLayout
android:id="@+id/linear_layout_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:layout_below="@id/title">
+ android:layout_below="@id/widget_picker_description">
<FrameLayout
android:id="@+id/recycler_view_container"
@@ -124,6 +127,16 @@
android:background="@drawable/widgets_surface_background"
android:importantForAccessibility="yes"
android:id="@+id/right_pane">
+ <TextView
+ android:id="@+id/no_widgets_text"
+ style="@style/PrimaryHeadline"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center"
+ android:textSize="18sp"
+ android:visibility="gone"
+ tools:text="@string/no_widgets_available" />
+
<!-- Shown when there are recommendations to display -->
<LinearLayout
android:id="@+id/widget_recommendations_container"
diff --git a/res/values-night-v31/colors.xml b/res/values-night-v31/colors.xml
index d23f4d1..2688b83 100644
--- a/res/values-night-v31/colors.xml
+++ b/res/values-night-v31/colors.xml
@@ -26,6 +26,8 @@
<color name="home_settings_track_off_color">@android:color/system_neutral1_700</color>
<color name="widget_picker_title_color_dark">@android:color/system_neutral1_100</color>
+ <color name="widget_picker_description_color_dark">
+ @android:color/system_neutral2_200</color>
<color name="widget_picker_header_app_title_color_dark">
@android:color/system_neutral1_100</color>
<color name="widget_picker_header_app_subtitle_color_dark">
diff --git a/res/values-v31/colors.xml b/res/values-v31/colors.xml
index fa87221..7270366 100644
--- a/res/values-v31/colors.xml
+++ b/res/values-v31/colors.xml
@@ -75,6 +75,8 @@
<color name="widget_picker_title_color_light">
@android:color/system_neutral1_900</color>
+ <color name="widget_picker_description_color_light">
+ @android:color/system_neutral2_700</color>
<color name="widget_picker_header_app_title_color_light">
@android:color/system_neutral1_900</color>
<color name="widget_picker_header_app_subtitle_color_light">
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index be8b2e1..e4e047e 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -61,6 +61,7 @@
<attr name="preloadIconAccentColor" format="color" />
<attr name="preloadIconBackgroundColor" format="color" />
<attr name="widgetPickerTitleColor" format="color"/>
+ <attr name="widgetPickerDescriptionColor" format="color"/>
<attr name="widgetPickerPrimarySurfaceColor" format="color"/>
<attr name="widgetPickerSecondarySurfaceColor" format="color"/>
<attr name="widgetPickerHeaderAppTitleColor" format="color"/>
diff --git a/res/values/colors.xml b/res/values/colors.xml
index ce80964..8fa1992 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -104,6 +104,7 @@
<color name="widget_picker_primary_surface_color_light">#EFEDED</color>
<color name="widget_picker_secondary_surface_color_light">#FAF9F8</color>
<color name="widget_picker_title_color_light">#1F1F1F</color>
+ <color name="widget_picker_description_color_light">#4C4D50</color>
<color name="widget_picker_header_app_title_color_light">#1F1F1F</color>
<color name="widget_picker_header_app_subtitle_color_light">#444746</color>
<color name="widget_picker_header_background_color_light">#C2E7FF</color>
@@ -123,6 +124,7 @@
<color name="widget_picker_primary_surface_color_dark">#1F2020</color>
<color name="widget_picker_secondary_surface_color_dark">#393939</color>
<color name="widget_picker_title_color_dark">#E3E3E3</color>
+ <color name="widget_picker_description_color_dark">#CCCDCF</color>
<color name="widget_picker_header_app_title_color_dark">#E3E3E3</color>
<color name="widget_picker_header_app_subtitle_color_dark">#C4C7C5</color>
<color name="widget_picker_header_background_color_dark">#004A77</color>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index ae3d3b3..f7273a0 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -243,6 +243,7 @@
<item name="widgetPickerSecondarySurfaceColor">
@color/widget_picker_secondary_surface_color_light</item>
<item name="widgetPickerTitleColor">@color/widget_picker_title_color_light</item>
+ <item name="widgetPickerDescriptionColor">@color/widget_picker_description_color_light</item>
<item name="widgetPickerHeaderAppTitleColor">
@color/widget_picker_header_app_title_color_light</item>
<item name="widgetPickerHeaderAppSubtitleColor">
@@ -282,6 +283,7 @@
@color/widget_picker_secondary_surface_color_dark</item>
<item name="widgetPickerTitleColor">
@color/widget_picker_title_color_dark</item>
+ <item name="widgetPickerDescriptionColor">@color/widget_picker_description_color_dark</item>
<item name="widgetPickerHeaderAppTitleColor">
@color/widget_picker_header_app_title_color_dark</item>
<item name="widgetPickerHeaderAppSubtitleColor">
diff --git a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
index fd15677..2e36583 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
@@ -679,6 +679,18 @@
return sheet;
}
+ /**
+ * Updates the widget picker's title and description in the header to the provided values (if
+ * present).
+ */
+ public void mayUpdateTitleAndDescription(@Nullable String title,
+ @Nullable String descriptionRes) {
+ if (title != null) {
+ mHeaderTitle.setText(title);
+ }
+ // Full sheet doesn't support a description.
+ }
+
@Override
public void saveHierarchyState(SparseArray<Parcelable> sparseArray) {
Bundle bundle = new Bundle();
diff --git a/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java b/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java
index 840d98a..5b7bbc2 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java
@@ -36,6 +36,7 @@
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.ScrollView;
+import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -60,9 +61,6 @@
* Popup for showing the full list of available widgets with a two-pane layout.
*/
public class WidgetsTwoPaneSheet extends WidgetsFullSheet {
-
- private static final int PERSONAL_TAB = 0;
- private static final int WORK_TAB = 1;
private static final int MINIMUM_WIDTH_LEFT_PANE_FOLDABLE_DP = 268;
private static final int MAXIMUM_WIDTH_LEFT_PANE_FOLDABLE_DP = 395;
private static final String SUGGESTIONS_PACKAGE_NAME = "widgets_list_suggestions_entry";
@@ -83,6 +81,7 @@
private int mActivePage = -1;
@Nullable
private PackageUserKey mSelectedHeader;
+ private TextView mHeaderDescription;
public WidgetsTwoPaneSheet(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
@@ -126,6 +125,8 @@
newPage -> mRecommendationsCurrentPage = newPage);
mHeaderTitle = mContent.findViewById(R.id.title);
+ mHeaderDescription = mContent.findViewById(R.id.widget_picker_description);
+
mRightPane = mContent.findViewById(R.id.right_pane);
mRightPaneScrollView = mContent.findViewById(R.id.right_pane_scroll_view);
mRightPaneScrollView.setOverScrollMode(View.OVER_SCROLL_NEVER);
@@ -141,6 +142,17 @@
}
@Override
+ public void mayUpdateTitleAndDescription(@Nullable String title, @Nullable String description) {
+ if (title != null) {
+ mHeaderTitle.setText(title);
+ }
+ if (description != null) {
+ mHeaderDescription.setText(description);
+ mHeaderDescription.setVisibility(VISIBLE);
+ }
+ }
+
+ @Override
protected int getTabletHorizontalMargin(DeviceProfile deviceProfile) {
if (enableCategorizedWidgetSuggestions()) {
// two pane picker is full width for fold as well as tablet.
@@ -371,9 +383,10 @@
protected void updateRecyclerViewVisibility(AdapterHolder adapterHolder) {
// The first item is always an empty space entry. Look for any more items.
boolean isWidgetAvailable = adapterHolder.mWidgetsListAdapter.hasVisibleEntries();
-
- mRightPane.setVisibility(isWidgetAvailable ? VISIBLE : GONE);
-
+ if (!isWidgetAvailable) {
+ mRightPane.removeAllViews();
+ mRightPane.addView(mNoWidgetsView);
+ }
super.updateRecyclerViewVisibility(adapterHolder);
}
diff --git a/tests/src/com/android/launcher3/ui/workspace/TaplThemeIconsTest.java b/tests/src/com/android/launcher3/ui/workspace/TaplThemeIconsTest.java
index 5dee322..a148744 100644
--- a/tests/src/com/android/launcher3/ui/workspace/TaplThemeIconsTest.java
+++ b/tests/src/com/android/launcher3/ui/workspace/TaplThemeIconsTest.java
@@ -40,6 +40,7 @@
import com.android.launcher3.tapl.HomeAppIconMenuItem;
import com.android.launcher3.ui.AbstractLauncherUiTest;
import com.android.launcher3.util.Executors;
+import com.android.launcher3.util.rule.ScreenRecordRule;
import com.android.launcher3.util.rule.TestStabilityRule;
import org.junit.Test;
@@ -115,6 +116,7 @@
@Test
@TestStabilityRule.Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT) // b/350557998
+ @ScreenRecordRule.ScreenRecord // b/350557998
public void testShortcutIconWithTheme() throws Exception {
setThemeEnabled(true);
initialize(this);
diff --git a/tests/src/com/android/launcher3/ui/workspace/TaplTwoPanelWorkspaceTest.java b/tests/src/com/android/launcher3/ui/workspace/TaplTwoPanelWorkspaceTest.java
index bc26c00..ae24a57 100644
--- a/tests/src/com/android/launcher3/ui/workspace/TaplTwoPanelWorkspaceTest.java
+++ b/tests/src/com/android/launcher3/ui/workspace/TaplTwoPanelWorkspaceTest.java
@@ -352,11 +352,8 @@
}
private void assertPagesExist(Launcher launcher, int... pageIds) {
- waitForLauncherCondition("Existing page count does NOT match. "
- + "Expected: " + pageIds.length
- + ". Actual: " + launcher.getWorkspace().getPageCount(),
- l -> pageIds.length == l.getWorkspace().getPageCount());
int pageCount = launcher.getWorkspace().getPageCount();
+ assertEquals("Existing page count does NOT match.", pageIds.length, pageCount);
for (int i = 0; i < pageCount; i++) {
CellLayout page = (CellLayout) launcher.getWorkspace().getPageAt(i);
int pageId = launcher.getWorkspace().getCellLayoutId(page);