Merge "Determine split leash position by taskId instead of bounds" into main
diff --git a/OWNERS b/OWNERS
index 654493f..e715b8b 100644
--- a/OWNERS
+++ b/OWNERS
@@ -28,6 +28,7 @@
tracyzhou@google.com
peanutbutter@google.com
jeremysim@google.com
+atsjenk@google.com
# Overview eng team
alexchau@google.com
diff --git a/aconfig/launcher.aconfig b/aconfig/launcher.aconfig
index 5df29bd..dd78ca4 100644
--- a/aconfig/launcher.aconfig
+++ b/aconfig/launcher.aconfig
@@ -267,3 +267,13 @@
description: "Enables smartspace removal toggle"
bug: "303471576"
}
+
+flag {
+ name: "enable_additional_home_animations"
+ namespace: "launcher"
+ description: "Enables custom home animations for non-running tasks"
+ bug: "237638627"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/quickstep/res/drawable/bg_bubble_expanded_view_drop_target.xml b/quickstep/res/drawable/bg_bubble_expanded_view_drop_target.xml
new file mode 100644
index 0000000..98aab67
--- /dev/null
+++ b/quickstep/res/drawable/bg_bubble_expanded_view_drop_target.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ 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.
+ -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:shape="rectangle">
+ <corners android:radius="@dimen/bubble_expanded_view_drop_target_corner_radius" />
+ <solid android:color="@color/bubblebar_drop_target_bg_color" />
+ <stroke
+ android:width="1dp"
+ android:color="?androidprv:attr/materialColorPrimaryContainer" />
+</shape>
diff --git a/quickstep/res/layout/bubble_expanded_view_drop_target.xml b/quickstep/res/layout/bubble_expanded_view_drop_target.xml
new file mode 100644
index 0000000..15ec49a
--- /dev/null
+++ b/quickstep/res/layout/bubble_expanded_view_drop_target.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ 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.
+ -->
+
+<!-- TODO(b/330585402): replace 600dp height with calculated value -->
+<View xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="@dimen/bubble_expanded_view_drop_target_width"
+ android:layout_height="600dp"
+ android:layout_margin="@dimen/bubble_expanded_view_drop_target_margin"
+ android:background="@drawable/bg_bubble_expanded_view_drop_target"
+ android:elevation="@dimen/bubblebar_elevation" />
\ No newline at end of file
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index b862d7c..c5f25ad 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -456,6 +456,9 @@
<!-- Bubble bar drop target -->
<dimen name="bubblebar_drop_target_corner_radius">36dp</dimen>
+ <dimen name="bubble_expanded_view_drop_target_corner_radius">16dp</dimen>
+ <dimen name="bubble_expanded_view_drop_target_width">412dp</dimen>
+ <dimen name="bubble_expanded_view_drop_target_margin">16dp</dimen>
<!-- Launcher splash screen -->
<!-- Note: keep this value in sync with the WindowManager/Shell dimens.xml -->
diff --git a/quickstep/src/com/android/launcher3/model/PredictionUpdateTask.java b/quickstep/src/com/android/launcher3/model/PredictionUpdateTask.java
index 2fcbe4e..d604742 100644
--- a/quickstep/src/com/android/launcher3/model/PredictionUpdateTask.java
+++ b/quickstep/src/com/android/launcher3/model/PredictionUpdateTask.java
@@ -15,8 +15,8 @@
*/
package com.android.launcher3.model;
-import static com.android.launcher3.LauncherPrefs.nonRestorableItem;
import static com.android.launcher3.EncryptionType.ENCRYPTED;
+import static com.android.launcher3.LauncherPrefs.nonRestorableItem;
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
import static com.android.quickstep.InstantAppResolverImpl.COMPONENT_CLASS_MARKER;
@@ -32,6 +32,7 @@
import com.android.launcher3.ConstantItem;
import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherModel.ModelUpdateTask;
import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.model.BgDataModel.FixedContainerItems;
import com.android.launcher3.model.QuickstepModelDelegate.PredictorState;
@@ -47,7 +48,7 @@
/**
* Task to update model as a result of predicted apps update
*/
-public class PredictionUpdateTask extends BaseModelUpdateTask {
+public class PredictionUpdateTask implements ModelUpdateTask {
public static final ConstantItem<Boolean> LAST_PREDICTION_ENABLED =
nonRestorableItem("last_prediction_enabled_state", true, ENCRYPTED);
@@ -61,8 +62,9 @@
}
@Override
- public void execute(@NonNull final LauncherAppState app, @NonNull final BgDataModel dataModel,
- @NonNull final AllAppsList apps) {
+ public void execute(@NonNull ModelTaskController taskController, @NonNull BgDataModel dataModel,
+ @NonNull AllAppsList apps) {
+ LauncherAppState app = taskController.getApp();
Context context = app.getContext();
// TODO: remove this
@@ -119,7 +121,7 @@
FixedContainerItems fci = new FixedContainerItems(mPredictorState.containerId, items);
dataModel.extraItems.put(fci.containerId, fci);
- bindExtraContainerItems(fci);
+ taskController.bindExtraContainerItems(fci);
usersForChangedShortcuts.forEach(
u -> dataModel.updateShortcutPinnedState(app.getContext(), u));
diff --git a/quickstep/src/com/android/launcher3/model/WidgetsPredictionUpdateTask.java b/quickstep/src/com/android/launcher3/model/WidgetsPredictionUpdateTask.java
index f4cbf17..39f2c00 100644
--- a/quickstep/src/com/android/launcher3/model/WidgetsPredictionUpdateTask.java
+++ b/quickstep/src/com/android/launcher3/model/WidgetsPredictionUpdateTask.java
@@ -24,7 +24,7 @@
import androidx.annotation.NonNull;
-import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherModel.ModelUpdateTask;
import com.android.launcher3.model.BgDataModel.FixedContainerItems;
import com.android.launcher3.model.QuickstepModelDelegate.PredictorState;
import com.android.launcher3.model.data.ItemInfo;
@@ -41,7 +41,7 @@
import java.util.stream.Collectors;
/** Task to update model as a result of predicted widgets update */
-public final class WidgetsPredictionUpdateTask extends BaseModelUpdateTask {
+public final class WidgetsPredictionUpdateTask implements ModelUpdateTask {
private final PredictorState mPredictorState;
private final List<AppTarget> mTargets;
@@ -58,8 +58,8 @@
* workspace.
*/
@Override
- public void execute(@NonNull final LauncherAppState appState,
- @NonNull final BgDataModel dataModel, @NonNull final AllAppsList apps) {
+ public void execute(@NonNull ModelTaskController taskController, @NonNull BgDataModel dataModel,
+ @NonNull AllAppsList apps) {
Set<ComponentKey> widgetsInWorkspace = dataModel.appWidgets.stream().map(
widget -> new ComponentKey(widget.providerName, widget.user)).collect(
Collectors.toSet());
@@ -98,7 +98,7 @@
List<ItemInfo> items;
if (enableCategorizedWidgetSuggestions()) {
- Context context = appState.getContext();
+ Context context = taskController.getApp().getContext();
WidgetRecommendationCategoryProvider categoryProvider =
WidgetRecommendationCategoryProvider.newInstance(context);
items = servicePredictedItems.stream()
@@ -115,7 +115,7 @@
new FixedContainerItems(mPredictorState.containerId, items);
dataModel.extraItems.put(mPredictorState.containerId, fixedContainerItems);
- bindExtraContainerItems(fixedContainerItems);
+ taskController.bindExtraContainerItems(fixedContainerItems);
// Don't store widgets prediction to disk because it is not used frequently.
}
diff --git a/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.java b/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.java
index 5ac5761..9eabb55 100644
--- a/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.java
+++ b/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.java
@@ -32,6 +32,7 @@
import com.android.launcher3.LauncherState;
import com.android.launcher3.statemanager.StatefulActivity;
import com.android.launcher3.uioverrides.QuickstepLauncher;
+import com.android.launcher3.util.DisplayController;
import com.android.quickstep.GestureState;
import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.views.DesktopAppSelectView;
@@ -229,6 +230,7 @@
for (DesktopVisibilityListener listener : mDesktopVisibilityListeners) {
listener.onDesktopVisibilityChanged(areDesktopTasksVisible);
}
+ DisplayController.handleInfoChangeForDesktopMode(mLauncher);
}
/**
diff --git a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
index 2c2311a..2ce6a41 100644
--- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
@@ -24,10 +24,6 @@
import android.animation.Animator;
import android.animation.AnimatorSet;
-import android.os.RemoteException;
-import android.util.Log;
-import android.view.TaskTransitionSpec;
-import android.view.WindowManagerGlobal;
import android.window.RemoteTransition;
import androidx.annotation.NonNull;
@@ -37,7 +33,6 @@
import com.android.launcher3.Flags;
import com.android.launcher3.LauncherState;
import com.android.launcher3.QuickstepTransitionManager;
-import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimatedFloat;
import com.android.launcher3.logging.InstanceId;
@@ -139,7 +134,6 @@
mLauncher.setTaskbarUIController(null);
mLauncher.removeOnDeviceProfileChangeListener(mOnDeviceProfileChangeListener);
mHomeState.removeListener(mVisibilityChangeListener);
- updateTaskTransitionSpec(true);
}
private void onInAppDisplayProgressChanged() {
@@ -278,26 +272,6 @@
private void onStashedInAppChanged(DeviceProfile deviceProfile) {
boolean taskbarStashedInApps = mControllers.taskbarStashController.isStashedInApp();
deviceProfile.isTaskbarPresentInApps = !taskbarStashedInApps;
- updateTaskTransitionSpec(taskbarStashedInApps);
- }
-
- private void updateTaskTransitionSpec(boolean taskbarIsHidden) {
- try {
- if (taskbarIsHidden) {
- // Clear custom task transition settings when the taskbar is stashed
- WindowManagerGlobal.getWindowManagerService().clearTaskTransitionSpec();
- } else {
- // Adjust task transition spec to account for taskbar being visible
- WindowManagerGlobal.getWindowManagerService().setTaskTransitionSpec(
- new TaskTransitionSpec(
- mLauncher.getColor(R.color.taskbar_background)));
- }
- } catch (RemoteException e) {
- // This shouldn't happen but if it does task animations won't look good until the
- // taskbar stashing state is changed.
- Log.e(TAG, "Failed to update task transition spec to account for new taskbar state",
- e);
- }
}
/**
diff --git a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
index d5306fb..3654e5f 100644
--- a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
@@ -378,6 +378,12 @@
return (flags & FLAG_DISABLE_BACK) == 0
&& ((flags & FLAG_KEYGUARD_VISIBLE) == 0 || showingOnKeyguard);
}));
+ // Hide back button in SUW if keyboard is showing (IME draws its own back).
+ if (mIsImeRenderingNavButtons) {
+ mPropertyHolders.add(new StatePropertyHolder(
+ mBackButtonAlpha.get(ALPHA_INDEX_SUW),
+ flags -> (flags & FLAG_IME_VISIBLE) == 0));
+ }
mPropertyHolders.add(new StatePropertyHolder(mBackButton,
flags -> (flags & FLAG_IME_VISIBLE) != 0,
ROTATION_DRAWABLE_PERCENT, 1f, 0f));
@@ -791,13 +797,6 @@
if (isInSetup) {
handleSetupUi();
-
- // Hide back button in SUW if keyboard is showing (IME draws its own back).
- if (mIsImeRenderingNavButtons) {
- mPropertyHolders.add(new StatePropertyHolder(
- mBackButtonAlpha.get(ALPHA_INDEX_SUW),
- flags -> (flags & FLAG_IME_VISIBLE) == 0));
- }
} else if (isInKidsMode) {
int iconSize = res.getDimensionPixelSize(
R.dimen.taskbar_icon_size_kids);
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index 1d772b5..e5396ee 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -106,6 +106,7 @@
import com.android.launcher3.taskbar.bubbles.BubbleControllers;
import com.android.launcher3.taskbar.bubbles.BubbleDismissController;
import com.android.launcher3.taskbar.bubbles.BubbleDragController;
+import com.android.launcher3.taskbar.bubbles.BubblePinController;
import com.android.launcher3.taskbar.bubbles.BubbleStashController;
import com.android.launcher3.taskbar.bubbles.BubbleStashedHandleViewController;
import com.android.launcher3.taskbar.navbutton.NearestTouchFrame;
@@ -259,6 +260,8 @@
new BubbleDragController(this),
new BubbleDismissController(this, mDragLayer),
new BubbleBarPinController(this, mDragLayer,
+ () -> getDeviceProfile().getDisplayInfo().currentSize),
+ new BubblePinController(this, mDragLayer,
() -> getDeviceProfile().getDisplayInfo().currentSize)
));
}
@@ -272,7 +275,7 @@
R.drawable.ic_sysbar_rotate_button_cw_start_0,
R.drawable.ic_sysbar_rotate_button_cw_start_90,
() -> getDisplay().getRotation());
- rotationButtonController.setBgExecutor(Executors.THREAD_POOL_EXECUTOR);
+ rotationButtonController.setBgExecutor(Executors.UI_HELPER_EXECUTOR);
mControllers = new TaskbarControllers(this,
new TaskbarDragController(this),
@@ -301,7 +304,8 @@
createTaskbarRecentAppsController(),
TaskbarEduTooltipController.newInstance(this),
new KeyboardQuickSwitchController(),
- new TaskbarPinningController(this),
+ new TaskbarPinningController(this, () ->
+ DisplayController.INSTANCE.get(this).getInfo().isInDesktopMode()),
bubbleControllersOptional);
mLauncherPrefs = LauncherPrefs.get(this);
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
index 30954ed..42e6edb 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
@@ -25,6 +25,7 @@
import static com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_NAVBAR_UNIFICATION;
import static com.android.launcher3.config.FeatureFlags.enableTaskbarNoRecreate;
import static com.android.launcher3.util.DisplayController.CHANGE_DENSITY;
+import static com.android.launcher3.util.DisplayController.CHANGE_DESKTOP_MODE;
import static com.android.launcher3.util.DisplayController.CHANGE_NAVIGATION_MODE;
import static com.android.launcher3.util.DisplayController.CHANGE_TASKBAR_PINNING;
import static com.android.launcher3.util.DisplayController.TASKBAR_NOT_DESTROYED_TAG;
@@ -142,7 +143,7 @@
private class RecreationListener implements DisplayController.DisplayInfoChangeListener {
@Override
public void onDisplayInfoChanged(Context context, DisplayController.Info info, int flags) {
- if ((flags & (CHANGE_DENSITY | CHANGE_NAVIGATION_MODE
+ if ((flags & (CHANGE_DENSITY | CHANGE_NAVIGATION_MODE | CHANGE_DESKTOP_MODE
| CHANGE_TASKBAR_PINNING)) != 0) {
recreateTaskbar();
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarPinningController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarPinningController.kt
index 2f2d636..6c9cc64 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarPinningController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarPinningController.kt
@@ -23,6 +23,7 @@
import com.android.app.animation.Interpolators
import com.android.launcher3.LauncherPrefs
import com.android.launcher3.LauncherPrefs.Companion.TASKBAR_PINNING
+import com.android.launcher3.LauncherPrefs.Companion.TASKBAR_PINNING_IN_DESKTOP_MODE
import com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_DIVIDER_MENU_CLOSE
import com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_DIVIDER_MENU_OPEN
import com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_PINNED
@@ -31,8 +32,10 @@
import java.io.PrintWriter
/** Controls taskbar pinning through a popup view. */
-class TaskbarPinningController(private val context: TaskbarActivityContext) :
- TaskbarControllers.LoggableTaskbarController {
+class TaskbarPinningController(
+ private val context: TaskbarActivityContext,
+ private val isInDesktopModeProvider: () -> Boolean,
+) : TaskbarControllers.LoggableTaskbarController {
private lateinit var controllers: TaskbarControllers
private lateinit var taskbarSharedState: TaskbarSharedState
@@ -54,14 +57,22 @@
if (!didPreferenceChange) {
return
}
+ val shouldPinTaskbar =
+ if (isInDesktopModeProvider()) {
+ !launcherPrefs.get(TASKBAR_PINNING_IN_DESKTOP_MODE)
+ } else {
+ !launcherPrefs.get(TASKBAR_PINNING)
+ }
+
val animateToValue =
- if (!launcherPrefs.get(TASKBAR_PINNING)) {
+ if (shouldPinTaskbar) {
statsLogManager.logger().log(LAUNCHER_TASKBAR_PINNED)
PINNING_PERSISTENT
} else {
statsLogManager.logger().log(LAUNCHER_TASKBAR_UNPINNED)
PINNING_TRANSIENT
}
+
taskbarSharedState.taskbarWasPinned = animateToValue == PINNING_TRANSIENT
animateTaskbarPinning(animateToValue)
}
@@ -123,13 +134,24 @@
@VisibleForTesting
fun recreateTaskbarAndUpdatePinningValue() {
updateIsAnimatingTaskbarPinningAndNotifyTaskbarDragLayer(false)
- launcherPrefs.put(TASKBAR_PINNING, !launcherPrefs.get(TASKBAR_PINNING))
+ if (isInDesktopModeProvider()) {
+ launcherPrefs.put(
+ TASKBAR_PINNING_IN_DESKTOP_MODE,
+ !launcherPrefs.get(TASKBAR_PINNING_IN_DESKTOP_MODE)
+ )
+ } else {
+ launcherPrefs.put(TASKBAR_PINNING, !launcherPrefs.get(TASKBAR_PINNING))
+ }
}
override fun dumpLogs(prefix: String, pw: PrintWriter) {
pw.println(prefix + "TaskbarPinningController:")
pw.println("$prefix\tisAnimatingTaskbarPinning=$isAnimatingTaskbarPinning")
pw.println("$prefix\tTASKBAR_PINNING shared pref =" + launcherPrefs.get(TASKBAR_PINNING))
+ pw.println(
+ "$prefix\tTASKBAR_PINNING_IN_DESKTOP_MODE shared pref in desktop mode =" +
+ launcherPrefs.get(TASKBAR_PINNING_IN_DESKTOP_MODE)
+ )
}
companion object {
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarBackground.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarBackground.kt
index ec47c4f..90c3ea7 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarBackground.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarBackground.kt
@@ -131,21 +131,15 @@
// Draw background.
val radius = backgroundHeight / 2f
- val left = if (anchorLeft) 0f else bounds.width().toFloat() - width
- val right = if (anchorLeft) width else bounds.width().toFloat()
- canvas.drawRoundRect(
- left,
- pointerVisibleHeight,
- right,
- bounds.height().toFloat(),
- radius,
- radius,
- paint
- )
+ val left = bounds.left + (if (anchorLeft) 0f else bounds.width().toFloat() - width)
+ val right = bounds.left + (if (anchorLeft) width else bounds.width().toFloat())
+ val top = bounds.top + pointerVisibleHeight
+ val bottom = bounds.top + bounds.height().toFloat()
+ canvas.drawRoundRect(left, top, right, bottom, radius, radius, paint)
if (showingArrow) {
// Draw arrow.
- val transX = arrowPositionX - pointerWidth / 2f
+ val transX = bounds.left + arrowPositionX - pointerWidth / 2f
canvas.translate(transX, 0f)
arrowDrawable.draw(canvas)
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
index 60e8abe..de93ba5 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
@@ -16,6 +16,7 @@
package com.android.launcher3.taskbar.bubbles;
import static com.android.app.animation.Interpolators.EMPHASIZED_ACCELERATE;
+import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA;
import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
import android.animation.Animator;
@@ -29,6 +30,7 @@
import android.graphics.PointF;
import android.graphics.Rect;
import android.util.AttributeSet;
+import android.util.FloatProperty;
import android.util.LayoutDirection;
import android.util.Log;
import android.view.Gravity;
@@ -79,6 +81,7 @@
// TODO: (b/273594744) calculate the amount of space we have and base the max on that
// if it's smaller than 5.
private static final int MAX_BUBBLES = 5;
+ private static final int MAX_VISIBLE_BUBBLES_COLLAPSED = 2;
private static final int ARROW_POSITION_ANIMATION_DURATION_MS = 200;
private static final int WIDTH_ANIMATION_DURATION_MS = 200;
@@ -94,6 +97,40 @@
// During fade in animation we shift the bubble bar 1/60th of the screen width
private static final float FADE_IN_ANIM_POSITION_SHIFT = 1 / 60f;
+ /**
+ * Custom property to set translationX value for the bar view while a bubble is being dragged.
+ * Skips applying translation to the dragged bubble.
+ */
+ private static final FloatProperty<BubbleBarView> BUBBLE_DRAG_TRANSLATION_X =
+ new FloatProperty<>("bubbleDragTranslationX") {
+ @Override
+ public void setValue(BubbleBarView bubbleBarView, float translationX) {
+ bubbleBarView.setTranslationXDuringBubbleDrag(translationX);
+ }
+
+ @Override
+ public Float get(BubbleBarView bubbleBarView) {
+ return bubbleBarView.mTranslationXDuringDrag;
+ }
+ };
+
+ /**
+ * Custom property to set alpha value for the bar view while a bubble is being dragged.
+ * Skips applying alpha to the dragged bubble.
+ */
+ private static final FloatProperty<BubbleBarView> BUBBLE_DRAG_ALPHA =
+ new FloatProperty<>("bubbleDragAlpha") {
+ @Override
+ public void setValue(BubbleBarView bubbleBarView, float alpha) {
+ bubbleBarView.setAlphaDuringBubbleDrag(alpha);
+ }
+
+ @Override
+ public Float get(BubbleBarView bubbleBarView) {
+ return bubbleBarView.mAlphaDuringDrag;
+ }
+ };
+
private final BubbleBarBackground mBubbleBarBackground;
private boolean mIsAnimatingNewBubble = false;
@@ -117,6 +154,8 @@
private final float mDragElevation;
private final int mPointerSize;
+ private final Rect mTempBackgroundBounds = new Rect();
+
// Whether the bar is expanded (i.e. the bubble activity is being displayed).
private boolean mIsBarExpanded = false;
// The currently selected bubble view.
@@ -150,6 +189,8 @@
@Nullable
private BubbleView mDraggedBubbleView;
+ private float mTranslationXDuringDrag = 0f;
+ private float mAlphaDuringDrag = 0f;
private int mPreviousLayoutDirection = LayoutDirection.UNDEFINED;
@@ -259,8 +300,10 @@
setPivotX(mRelativePivotX * getWidth());
setPivotY(mRelativePivotY * getHeight());
- // Position the views
- updateChildrenRenderNodeProperties(mBubbleBarLocation);
+ if (!mDragging) {
+ // Position the views when not dragging
+ updateChildrenRenderNodeProperties(mBubbleBarLocation);
+ }
}
@Override
@@ -302,17 +345,15 @@
mBubbleBarLocationAnimator.cancel();
mBubbleBarLocationAnimator = null;
}
- setTranslationX(0f);
- setAlpha(1f);
+ resetDragAnimation();
if (bubbleBarLocation != mBubbleBarLocation) {
mBubbleBarLocation = bubbleBarLocation;
onBubbleBarLocationChanged();
- invalidate();
}
}
/**
- * Set whether this view is being currently being dragged
+ * Set whether this view is currently being dragged
*/
public void setIsDragging(boolean dragging) {
if (mDragging == dragging) {
@@ -326,7 +367,7 @@
* Get translation for bubble bar when drag is released and it needs to animate back to the
* resting position.
* Resting position is based on the supplied location. If the supplied location is different
- * from the internal location that was used to lay out the bubble bar, translation values are
+ * from the internal location that was used during bubble bar layout, translation values are
* calculated to position the bar at the desired location.
*
* @param initialTranslation initial bubble bar translation at the start of drag
@@ -353,6 +394,30 @@
return dragEndTranslation;
}
+ /**
+ * Get translation for a bubble when drag is released and it needs to animate back to the
+ * resting position.
+ * Resting position is based on the supplied location. If the supplied location is different
+ * from the internal location that was used during bubble bar layout, translation values are
+ * calculated to position the bar at the desired location.
+ *
+ * @param initialTranslation initial bubble bar translation at the start of drag
+ * @param location desired location of the bubble bar when drag is released
+ * @return point with x and y values representing translation on x and y-axis
+ */
+ public PointF getDraggedBubbleReleaseTranslation(PointF initialTranslation,
+ BubbleBarLocation location) {
+ // Start with bubble bar translation
+ final PointF dragEndTranslation = new PointF(
+ getBubbleBarDragReleaseTranslation(initialTranslation, location));
+ // Apply individual bubble translation, as the order may have changed
+ int viewIndex = indexOfChild(mDraggedBubbleView);
+ dragEndTranslation.x += getExpandedBubbleTranslationX(viewIndex,
+ getChildCount(),
+ location.isOnLeft(isLayoutRtl()));
+ return dragEndTranslation;
+ }
+
private float getDistanceFromOtherSide() {
// Calculate the shift needed to position the bubble bar on the other side
int displayWidth = getResources().getDisplayMetrics().widthPixels;
@@ -393,17 +458,18 @@
mBubbleBarLocationAnimator.start();
}
- private Animator getLocationUpdateFadeOutAnimator(BubbleBarLocation bubbleBarLocation) {
+ private Animator getLocationUpdateFadeOutAnimator(BubbleBarLocation newLocation) {
+ final FloatProperty<? super BubbleBarView> txProp = getLocationAnimTranslationXProperty();
final float shift =
getResources().getDisplayMetrics().widthPixels * FADE_OUT_ANIM_POSITION_SHIFT;
- final boolean onLeft = bubbleBarLocation.isOnLeft(isLayoutRtl());
- final float tx = getTranslationX() + (onLeft ? shift : -shift);
+ final boolean onLeft = newLocation.isOnLeft(isLayoutRtl());
+ final float tx = txProp.get(this) + (onLeft ? -shift : shift);
- ObjectAnimator positionAnim = ObjectAnimator.ofFloat(this, TRANSLATION_X, tx)
- .setDuration(FADE_OUT_ANIM_POSITION_DURATION_MS);
+ ObjectAnimator positionAnim = ObjectAnimator.ofFloat(this, txProp, tx).setDuration(
+ FADE_OUT_ANIM_POSITION_DURATION_MS);
positionAnim.setInterpolator(EMPHASIZED_ACCELERATE);
- ObjectAnimator alphaAnim = ObjectAnimator.ofFloat(this, ALPHA, 0f)
+ ObjectAnimator alphaAnim = ObjectAnimator.ofFloat(this, getLocationAnimAlphaProperty(), 0f)
.setDuration(FADE_OUT_ANIM_ALPHA_DURATION_MS);
alphaAnim.setStartDelay(FADE_OUT_ANIM_ALPHA_DELAY_MS);
@@ -412,14 +478,14 @@
return animatorSet;
}
- private Animator getLocationUpdateFadeInAnimator(BubbleBarLocation animatedLocation) {
+ private Animator getLocationUpdateFadeInAnimator(BubbleBarLocation newLocation) {
final float shift =
getResources().getDisplayMetrics().widthPixels * FADE_IN_ANIM_POSITION_SHIFT;
- final boolean onLeft = animatedLocation.isOnLeft(isLayoutRtl());
+ final boolean onLeft = newLocation.isOnLeft(isLayoutRtl());
final float startTx;
final float finalTx;
- if (animatedLocation == mBubbleBarLocation) {
+ if (newLocation == mBubbleBarLocation) {
// Animated location matches layout location.
finalTx = 0;
} else {
@@ -439,9 +505,9 @@
.setEndValue(finalTx)
.setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY)
.setStiffness(FADE_IN_ANIM_POSITION_SPRING_STIFFNESS)
- .build(this, VIEW_TRANSLATE_X);
+ .build(this, getLocationAnimTranslationXProperty());
- ObjectAnimator alphaAnim = ObjectAnimator.ofFloat(this, ALPHA, 1f)
+ ObjectAnimator alphaAnim = ObjectAnimator.ofFloat(this, getLocationAnimAlphaProperty(), 1f)
.setDuration(FADE_IN_ANIM_ALPHA_DURATION_MS);
AnimatorSet animatorSet = new AnimatorSet();
@@ -450,6 +516,84 @@
}
/**
+ * Get property that can be used to animate the translation-x value for the bar.
+ * When a bubble is being dragged, uses {@link #BUBBLE_DRAG_TRANSLATION_X}.
+ * Falls back to {@link com.android.launcher3.LauncherAnimUtils#VIEW_TRANSLATE_X} otherwise.
+ */
+ private FloatProperty<? super BubbleBarView> getLocationAnimTranslationXProperty() {
+ return mDraggedBubbleView == null ? VIEW_TRANSLATE_X : BUBBLE_DRAG_TRANSLATION_X;
+ }
+
+ /**
+ * Get property that can be used to animate the alpha value for the bar.
+ * When a bubble is being dragged, uses {@link #BUBBLE_DRAG_ALPHA}.
+ * Falls back to {@link com.android.launcher3.LauncherAnimUtils#VIEW_ALPHA} otherwise.
+ */
+ private FloatProperty<? super BubbleBarView> getLocationAnimAlphaProperty() {
+ return mDraggedBubbleView == null ? VIEW_ALPHA : BUBBLE_DRAG_ALPHA;
+ }
+
+ /**
+ * Set translation-x value for the bar while a bubble is being dragged.
+ * We can not update translation on the bar directly because the dragged bubble would be
+ * affected as well. As it is a child view.
+ * Instead, while a bubble is being dragged, set translation on each child view, that is not the
+ * dragged view. And set a translation on the background.
+ * This allows for the dragged bubble view to remain in position while the bar moves during
+ * animation.
+ */
+ private void setTranslationXDuringBubbleDrag(float translationX) {
+ mTranslationXDuringDrag = translationX;
+ final int childCount = getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ BubbleView view = (BubbleView) getChildAt(i);
+ if (view != mDraggedBubbleView) {
+ view.setBubbleBarTranslationX(translationX);
+ }
+ }
+ if (mBubbleBarBackground != null) {
+ mTempBackgroundBounds.set(mBubbleBarBackground.getBounds());
+ mTempBackgroundBounds.offsetTo((int) translationX, 0);
+ mBubbleBarBackground.setBounds(mTempBackgroundBounds);
+ }
+ }
+
+ /**
+ * Set alpha value for the bar while a bubble is being dragged.
+ * We can not update the alpha on the bar directly because the dragged bubble would be affected
+ * as well. As it is a child view.
+ * Instead, while a bubble is being dragged, set alpha on each child view, that is not the
+ * dragged view. And set an alpha on the background.
+ * This allows for the dragged bubble to remain visible while the bar is hidden during
+ * animation.
+ */
+ private void setAlphaDuringBubbleDrag(float alpha) {
+ mAlphaDuringDrag = alpha;
+ final int childCount = getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ View view = getChildAt(i);
+ if (view != mDraggedBubbleView) {
+ view.setAlpha(alpha);
+ }
+ }
+ if (mBubbleBarBackground != null) {
+ mBubbleBarBackground.setAlpha((int) (255 * alpha));
+ }
+ }
+
+ private void resetDragAnimation() {
+ if (mBubbleBarLocationAnimator != null) {
+ mBubbleBarLocationAnimator.removeAllListeners();
+ mBubbleBarLocationAnimator.cancel();
+ mBubbleBarLocationAnimator = null;
+ }
+ setTranslationXDuringBubbleDrag(0f);
+ setAlphaDuringBubbleDrag(1f);
+ setTranslationX(0f);
+ setAlpha(1f);
+ }
+
+ /**
* Updates the bounds with translation that may have been applied and returns the result.
*/
public Rect getBubbleBarBounds() {
@@ -558,30 +702,21 @@
float elevationState = (1 - widthState);
for (int i = 0; i < bubbleCount; i++) {
BubbleView bv = (BubbleView) getChildAt(i);
+ if (bv == mDraggedBubbleView) {
+ // Skip the dragged bubble. Its translation is managed by the drag controller.
+ continue;
+ }
bv.setTranslationY(ty);
// the position of the bubble when the bar is fully expanded
- final float expandedX;
+ final float expandedX = getExpandedBubbleTranslationX(i, bubbleCount, onLeft);
// the position of the bubble when the bar is fully collapsed
- final float collapsedX;
- if (onLeft) {
- // If bar is on the left, bubbles are ordered right to left
- expandedX = (bubbleCount - i - 1) * (mIconSize + mExpandedBarIconsSpacing);
- // Shift the first bubble only if there are more bubbles in addition to overflow
- collapsedX = i == 0 && bubbleCount > 2 ? mIconOverlapAmount : 0;
- } else {
- // Bubbles ordered left to right, don't move the first bubble
- expandedX = i * (mIconSize + mExpandedBarIconsSpacing);
- collapsedX = i == 0 ? 0 : mIconOverlapAmount;
- }
- if (bv == mDraggedBubbleView) {
- // if bubble is dragged set the elevation to bubble drag elevation
- bv.setZ(mDragElevation);
- } else {
- // otherwise slowly animate elevation while keeping correct Z ordering
- float fullElevationForChild = (MAX_BUBBLES * mBubbleElevation) - i;
- bv.setZ(fullElevationForChild * elevationState);
- }
+ final float collapsedX = getCollapsedBubbleTranslationX(i, bubbleCount, onLeft);
+
+ // slowly animate elevation while keeping correct Z ordering
+ float fullElevationForChild = (MAX_BUBBLES * mBubbleElevation) - i;
+ bv.setZ(fullElevationForChild * elevationState);
+
if (mIsBarExpanded) {
// If bar is on the right, account for bubble bar expanding and shifting left
final float expandedBarShift = onLeft ? 0 : currentWidth - expandedWidth;
@@ -601,9 +736,10 @@
// If we're fully collapsed, hide all bubbles except for the first 2. If there are
// only 2 bubbles, hide the second bubble as well because it's the overflow.
if (widthState == 0) {
- if (i > 1) {
+ if (i > MAX_VISIBLE_BUBBLES_COLLAPSED - 1) {
bv.setAlpha(0);
- } else if (i == 1 && bubbleCount == 2) {
+ } else if (i == MAX_VISIBLE_BUBBLES_COLLAPSED - 1
+ && bubbleCount == MAX_VISIBLE_BUBBLES_COLLAPSED) {
bv.setAlpha(0);
}
}
@@ -636,6 +772,34 @@
mBubbleBarBackground.setWidth(interpolatedWidth);
}
+ private float getExpandedBubbleTranslationX(int bubbleIndex, int bubbleCount,
+ boolean onLeft) {
+ if (bubbleIndex < 0 || bubbleIndex >= bubbleCount) {
+ return 0;
+ }
+ if (onLeft) {
+ // If bar is on the left, bubbles are ordered right to left
+ return (bubbleCount - bubbleIndex - 1) * (mIconSize + mExpandedBarIconsSpacing);
+ } else {
+ // Bubbles ordered left to right, don't move the first bubble
+ return bubbleIndex * (mIconSize + mExpandedBarIconsSpacing);
+ }
+ }
+
+ private float getCollapsedBubbleTranslationX(int bubbleIndex, int bubbleCount,
+ boolean onLeft) {
+ if (bubbleIndex < 0 || bubbleIndex >= bubbleCount) {
+ return 0;
+ }
+ if (onLeft) {
+ // Shift the first bubble only if there are more bubbles in addition to overflow
+ return bubbleIndex == 0 && bubbleCount > MAX_VISIBLE_BUBBLES_COLLAPSED
+ ? mIconOverlapAmount : 0;
+ } else {
+ return bubbleIndex == 0 ? 0 : mIconOverlapAmount;
+ }
+ }
+
/**
* Reorders the views to match the provided list.
*/
@@ -685,8 +849,18 @@
* Sets the dragged bubble view to correctly apply Z order. Dragged view should appear on top
*/
public void setDraggedBubble(@Nullable BubbleView view) {
+ if (mDraggedBubbleView != null) {
+ mDraggedBubbleView.setZ(0);
+ if (view == null) {
+ // We are clearing the dragged bubble, reset drag
+ resetDragAnimation();
+ }
+ }
mDraggedBubbleView = view;
- requestLayout();
+ if (view != null) {
+ view.setZ(mDragElevation);
+ }
+ setIsDragging(view != null);
}
/**
@@ -745,7 +919,7 @@
if (bubbleBarLocation.isOnLeft(isLayoutRtl())) {
// Bubble positions are reversed. First bubble may be shifted, if there are more
// bubbles than the current bubble and overflow.
- bubblePosition = index == 0 && getChildCount() > 2 ? 1 : 0;
+ bubblePosition = index == 0 && getChildCount() > MAX_VISIBLE_BUBBLES_COLLAPSED ? 1 : 0;
} else {
bubblePosition = index;
}
@@ -807,7 +981,7 @@
final int horizontalPadding = getPaddingStart() + getPaddingEnd();
// If there are more than 2 bubbles, the first 2 should be visible when collapsed.
// Otherwise just the first bubble should be visible because we don't show the overflow.
- return childCount > 2
+ return childCount > MAX_VISIBLE_BUBBLES_COLLAPSED
? mIconSize + mIconOverlapAmount + horizontalPadding
: mIconSize + horizontalPadding;
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
index dc48a66..95dd24b 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
@@ -20,6 +20,7 @@
import android.content.res.Resources;
import android.graphics.Point;
+import android.graphics.PointF;
import android.graphics.Rect;
import android.util.DisplayMetrics;
import android.util.Log;
@@ -468,7 +469,8 @@
*/
public void onDragStart(@NonNull BubbleView bubbleView) {
if (bubbleView.getBubble() == null) return;
- mSystemUiProxy.onBubbleDrag(bubbleView.getBubble().getKey(), /* isBeingDragged = */ true);
+
+ mSystemUiProxy.startBubbleDrag(bubbleView.getBubble().getKey());
mBarView.setDraggedBubble(bubbleView);
}
@@ -476,19 +478,46 @@
* Notifies SystemUI to expand the selected bubble when the bubble is released.
* @param bubbleView dragged bubble view
*/
- public void onDragRelease(@NonNull BubbleView bubbleView) {
+ public void onDragRelease(@NonNull BubbleView bubbleView, BubbleBarLocation location) {
if (bubbleView.getBubble() == null) return;
- mSystemUiProxy.onBubbleDrag(bubbleView.getBubble().getKey(), /* isBeingDragged = */ false);
+ // TODO(b/330585402): send new bubble bar bounds to shell for the animation
+ mSystemUiProxy.stopBubbleDrag(bubbleView.getBubble().getKey(), location);
}
/**
- * Removes the dragged bubble view in the bubble bar view
+ * Notifies {@link BubbleBarView} that drag and all animations are finished.
*/
public void onDragEnd() {
mBarView.setDraggedBubble(null);
}
/**
+ * Get translation for bubble bar when drag is released.
+ *
+ * @see BubbleBarView#getBubbleBarDragReleaseTranslation(PointF, BubbleBarLocation)
+ */
+ public PointF getBubbleBarDragReleaseTranslation(PointF initialTranslation,
+ BubbleBarLocation location) {
+ if (location == mBarView.getBubbleBarLocation()) {
+ return initialTranslation;
+ }
+ return mBarView.getBubbleBarDragReleaseTranslation(initialTranslation, location);
+ }
+
+ /**
+ * Get translation for bubble view when drag is released.
+ *
+ * @see BubbleBarView#getDraggedBubbleReleaseTranslation(PointF, BubbleBarLocation)
+ */
+ public PointF getDraggedBubbleReleaseTranslation(PointF initialTranslation,
+ BubbleBarLocation location) {
+ if (location == mBarView.getBubbleBarLocation()) {
+ return initialTranslation;
+ }
+ return mBarView.getDraggedBubbleReleaseTranslation(initialTranslation, location);
+ }
+
+ /**
* Called when bubble was dragged into the dismiss target. Notifies System
* @param bubble dismissed bubble item
*/
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleControllers.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleControllers.java
index 90f1be3..295477c 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleControllers.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleControllers.java
@@ -30,6 +30,7 @@
public final BubbleDragController bubbleDragController;
public final BubbleDismissController bubbleDismissController;
public final BubbleBarPinController bubbleBarPinController;
+ public final BubblePinController bubblePinController;
private final RunnableList mPostInitRunnables = new RunnableList();
@@ -45,7 +46,8 @@
BubbleStashedHandleViewController bubbleStashedHandleViewController,
BubbleDragController bubbleDragController,
BubbleDismissController bubbleDismissController,
- BubbleBarPinController bubbleBarPinController) {
+ BubbleBarPinController bubbleBarPinController,
+ BubblePinController bubblePinController) {
this.bubbleBarController = bubbleBarController;
this.bubbleBarViewController = bubbleBarViewController;
this.bubbleStashController = bubbleStashController;
@@ -53,6 +55,7 @@
this.bubbleDragController = bubbleDragController;
this.bubbleDismissController = bubbleDismissController;
this.bubbleBarPinController = bubbleBarPinController;
+ this.bubblePinController = bubblePinController;
}
/**
@@ -68,6 +71,7 @@
bubbleDragController.init(/* bubbleControllers = */ this);
bubbleDismissController.init(/* bubbleControllers = */ this);
bubbleBarPinController.init(this);
+ bubblePinController.init(this);
mPostInitRunnables.executeAllAndDestroy();
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleDragController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleDragController.java
index d1c9da7..1764f75 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleDragController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleDragController.java
@@ -43,6 +43,7 @@
private BubbleBarViewController mBubbleBarViewController;
private BubbleDismissController mBubbleDismissController;
private BubbleBarPinController mBubbleBarPinController;
+ private BubblePinController mBubblePinController;
public BubbleDragController(TaskbarActivityContext activity) {
mActivity = activity;
@@ -58,8 +59,12 @@
mBubbleBarViewController = bubbleControllers.bubbleBarViewController;
mBubbleDismissController = bubbleControllers.bubbleDismissController;
mBubbleBarPinController = bubbleControllers.bubbleBarPinController;
+ mBubblePinController = bubbleControllers.bubblePinController;
mBubbleDismissController.setListener(
- stuck -> mBubbleBarPinController.setDropTargetHidden(stuck));
+ stuck -> {
+ mBubbleBarPinController.setDropTargetHidden(stuck);
+ mBubblePinController.setDropTargetHidden(stuck);
+ });
}
/**
@@ -73,19 +78,58 @@
}
bubbleView.setOnTouchListener(new BubbleTouchListener() {
+
+ private BubbleBarLocation mReleasedLocation = BubbleBarLocation.DEFAULT;
+
+ private final LocationChangeListener mLocationChangeListener =
+ new LocationChangeListener() {
+ @Override
+ public void onChange(@NonNull BubbleBarLocation location) {
+ mBubbleBarController.animateBubbleBarLocation(location);
+ }
+
+ @Override
+ public void onRelease(@NonNull BubbleBarLocation location) {
+ mReleasedLocation = location;
+ }
+ };
+
@Override
void onDragStart() {
+ mBubblePinController.setListener(mLocationChangeListener);
mBubbleBarViewController.onDragStart(bubbleView);
+ mBubblePinController.onDragStart(
+ mBubbleBarViewController.getBubbleBarLocation().isOnLeft(
+ bubbleView.isLayoutRtl()));
}
@Override
- void onDragEnd() {
- mBubbleBarViewController.onDragEnd();
+ protected void onDragUpdate(float x, float y) {
+ mBubblePinController.onDragUpdate(x, y);
}
@Override
protected void onDragRelease() {
- mBubbleBarViewController.onDragRelease(bubbleView);
+ mBubblePinController.onDragEnd();
+ mBubbleBarViewController.onDragRelease(bubbleView, mReleasedLocation);
+ }
+
+ @Override
+ protected void onDragDismiss() {
+ mBubblePinController.onDragEnd();
+ }
+
+ @Override
+ void onDragEnd() {
+ mBubbleBarController.updateBubbleBarLocation(mReleasedLocation);
+ mBubbleBarViewController.onDragEnd();
+ mBubblePinController.setListener(null);
+ }
+
+ @Override
+ protected PointF getRestingPosition() {
+ return mBubbleBarViewController.getDraggedBubbleReleaseTranslation(
+ getInitialPosition(), mReleasedLocation);
}
});
}
@@ -98,8 +142,7 @@
PointF initialRelativePivot = new PointF();
bubbleBarView.setOnTouchListener(new BubbleTouchListener() {
- @Nullable
- private BubbleBarLocation mReleasedLocation;
+ private BubbleBarLocation mReleasedLocation = BubbleBarLocation.DEFAULT;
private final LocationChangeListener mLocationChangeListener =
new LocationChangeListener() {
@@ -145,20 +188,18 @@
@Override
void onDragEnd() {
+ // Make sure to update location as the first thing. Pivot update causes a relayout
+ mBubbleBarController.updateBubbleBarLocation(mReleasedLocation);
+ bubbleBarView.setIsDragging(false);
// Restoring the initial pivot for the bubble bar view
bubbleBarView.setRelativePivot(initialRelativePivot.x, initialRelativePivot.y);
- bubbleBarView.setIsDragging(false);
- mBubbleBarController.updateBubbleBarLocation(mReleasedLocation);
+ mBubbleBarPinController.setListener(null);
}
@Override
protected PointF getRestingPosition() {
- if (mReleasedLocation == null
- || mReleasedLocation == bubbleBarView.getBubbleBarLocation()) {
- return getInitialPosition();
- }
- return bubbleBarView.getBubbleBarDragReleaseTranslation(getInitialPosition(),
- mReleasedLocation);
+ return mBubbleBarViewController.getBubbleBarDragReleaseTranslation(
+ getInitialPosition(), mReleasedLocation);
}
});
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubblePinController.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubblePinController.kt
new file mode 100644
index 0000000..fef7fa1
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubblePinController.kt
@@ -0,0 +1,97 @@
+/*
+ * 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.launcher3.taskbar.bubbles
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.graphics.Point
+import android.view.Gravity.BOTTOM
+import android.view.Gravity.LEFT
+import android.view.Gravity.RIGHT
+import android.view.LayoutInflater
+import android.view.View
+import android.widget.FrameLayout
+import androidx.core.view.updateLayoutParams
+import com.android.launcher3.R
+import com.android.wm.shell.common.bubbles.BaseBubblePinController
+import com.android.wm.shell.common.bubbles.BubbleBarLocation
+
+/** Controller to manage pinning bubble bar to left or right when dragging starts from a bubble */
+class BubblePinController(
+ private val context: Context,
+ private val container: FrameLayout,
+ screenSizeProvider: () -> Point
+) : BaseBubblePinController(screenSizeProvider) {
+
+ private lateinit var bubbleBarViewController: BubbleBarViewController
+ private lateinit var bubbleStashController: BubbleStashController
+ private var exclRectWidth: Float = 0f
+ private var exclRectHeight: Float = 0f
+
+ private var dropTargetView: View? = null
+ private var dropTargetMargin: Int = 0
+
+ fun init(bubbleControllers: BubbleControllers) {
+ bubbleBarViewController = bubbleControllers.bubbleBarViewController
+ bubbleStashController = bubbleControllers.bubbleStashController
+ exclRectWidth = context.resources.getDimension(R.dimen.bubblebar_dismiss_zone_width)
+ exclRectHeight = context.resources.getDimension(R.dimen.bubblebar_dismiss_zone_height)
+ dropTargetMargin =
+ context.resources.getDimensionPixelSize(R.dimen.bubble_expanded_view_drop_target_margin)
+ }
+
+ override fun getExclusionRectWidth(): Float {
+ return exclRectWidth
+ }
+
+ override fun getExclusionRectHeight(): Float {
+ return exclRectHeight
+ }
+
+ override fun getDropTargetView(): View? {
+ return dropTargetView
+ }
+
+ override fun removeDropTargetView(view: View) {
+ container.removeView(view)
+ dropTargetView = null
+ }
+
+ override fun createDropTargetView(): View {
+ return LayoutInflater.from(context)
+ .inflate(R.layout.bubble_expanded_view_drop_target, container, false)
+ .also { view ->
+ // TODO(b/330585402): dynamic height for the drop target based on actual height
+ dropTargetView = view
+ container.addView(view)
+ }
+ }
+
+ @SuppressLint("RtlHardcoded")
+ override fun updateLocation(location: BubbleBarLocation) {
+ val onLeft = location.isOnLeft(container.isLayoutRtl)
+
+ val bubbleBarBounds = bubbleBarViewController.bubbleBarBounds
+ dropTargetView?.updateLayoutParams<FrameLayout.LayoutParams> {
+ gravity = BOTTOM or (if (onLeft) LEFT else RIGHT)
+ bottomMargin =
+ -bubbleStashController.bubbleBarTranslationY.toInt() +
+ bubbleBarBounds.height() +
+ dropTargetMargin
+ }
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleView.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleView.java
index bcdc718..3dc4ebc 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleView.java
@@ -66,6 +66,9 @@
private final ImageView mAppIcon;
private final int mBubbleSize;
+ private float mBubbleBarTranslationX = 0f;
+ private float mTranslationX = 0f;
+
private DotRenderer mDotRenderer;
private DotRenderer.DrawParams mDrawParams;
private int mDotColor;
@@ -127,6 +130,35 @@
}
@Override
+ public void setTranslationX(float translationX) {
+ // Overriding setting translationX as it can be a combination of the parent translation
+ // and current view translation.
+ // When a BubbleView is being dragged to pin the bubble bar to other side, we animate the
+ // bar to the new location during the drag.
+ // One part of the animation is updating the translation of the bubble bar. But doing
+ // that also updates the translation for the child views, like the dragged bubble.
+ // To get around that, we instead apply translation on each child view of bubble bar. It
+ // is applied as bubble bar translation. This results in BubbleView's translation being a
+ // sum of the translation it has and the parent bubble bar translation.
+ mTranslationX = translationX;
+ applyTranslation();
+ }
+
+ /**
+ * Translation of the bubble bar that hosts this bubble.
+ * Is applied together with translation applied on the view through
+ * {@link #setTranslationX(float)}.
+ */
+ void setBubbleBarTranslationX(float translationX) {
+ mBubbleBarTranslationX = translationX;
+ applyTranslation();
+ }
+
+ private void applyTranslation() {
+ super.setTranslationX(mTranslationX + mBubbleBarTranslationX);
+ }
+
+ @Override
public void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index 4acddee..4184ab2 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -166,6 +166,8 @@
import com.android.launcher3.util.TouchController;
import com.android.launcher3.widget.LauncherWidgetHolder;
import com.android.quickstep.OverviewCommandHelper;
+import com.android.quickstep.OverviewComponentObserver;
+import com.android.quickstep.RecentsAnimationDeviceState;
import com.android.quickstep.RecentsModel;
import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.TaskUtils;
@@ -264,6 +266,10 @@
getDepthController(), getStatsLogManager(),
systemUiProxy, RecentsModel.INSTANCE.get(this),
() -> onStateBack());
+ RecentsAnimationDeviceState deviceState = new RecentsAnimationDeviceState(asContext());
+ // TODO(b/337863494): Explore use of the same OverviewComponentObserver across launcher
+ OverviewComponentObserver overviewComponentObserver = new OverviewComponentObserver(
+ asContext(), deviceState);
if (enableDesktopWindowingMode()) {
mDesktopRecentsTransitionController = new DesktopRecentsTransitionController(
getStateManager(), systemUiProxy, getIApplicationThread(),
@@ -272,7 +278,7 @@
overviewPanel.init(mActionsView, mSplitSelectStateController,
mDesktopRecentsTransitionController);
mSplitWithKeyboardShortcutController = new SplitWithKeyboardShortcutController(this,
- mSplitSelectStateController);
+ mSplitSelectStateController, overviewComponentObserver, deviceState);
mSplitToWorkspaceController = new SplitToWorkspaceController(this,
mSplitSelectStateController);
mActionsView.updateDimension(getDeviceProfile(), overviewPanel.getLastComputedTaskSize());
@@ -287,7 +293,8 @@
if (enableDesktopWindowingMode()) {
mDesktopVisibilityController = new DesktopVisibilityController(this);
mDesktopVisibilityController.registerSystemUiListener();
- mSplitSelectStateController.initSplitFromDesktopController(this);
+ mSplitSelectStateController.initSplitFromDesktopController(this,
+ overviewComponentObserver);
}
mHotseatPredictionController = new HotseatPredictionController(this);
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 8e4dde2..2ce40b4 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -30,6 +30,7 @@
import static com.android.launcher3.BaseActivity.EVENT_STARTED;
import static com.android.launcher3.BaseActivity.INVISIBLE_BY_STATE_HANDLER;
import static com.android.launcher3.BaseActivity.STATE_HANDLER_INVISIBILITY_FLAGS;
+import static com.android.launcher3.Flags.enableAdditionalHomeAnimations;
import static com.android.launcher3.Flags.enableGridOnlyOverview;
import static com.android.launcher3.PagedView.INVALID_PAGE;
import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_BACKGROUND;
@@ -152,6 +153,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
+import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.OptionalInt;
@@ -1480,8 +1482,12 @@
}
protected abstract HomeAnimationFactory createHomeAnimationFactory(
- ArrayList<IBinder> launchCookies, long duration, boolean isTargetTranslucent,
- boolean appCanEnterPip, RemoteAnimationTarget runningTaskTarget);
+ List<IBinder> launchCookies,
+ long duration,
+ boolean isTargetTranslucent,
+ boolean appCanEnterPip,
+ RemoteAnimationTarget runningTaskTarget,
+ @Nullable TaskView targetTaskView);
private final TaskStackChangeListener mActivityRestartListener = new TaskStackChangeListener() {
@Override
@@ -1555,9 +1561,16 @@
&& runningTaskTarget.allowEnterPip
&& runningTaskTarget.taskInfo.pictureInPictureParams != null
&& runningTaskTarget.taskInfo.pictureInPictureParams.isAutoEnterEnabled();
- HomeAnimationFactory homeAnimFactory =
- createHomeAnimationFactory(cookies, duration, isTranslucent, appCanEnterPip,
- runningTaskTarget);
+ HomeAnimationFactory homeAnimFactory = createHomeAnimationFactory(
+ cookies,
+ duration,
+ isTranslucent,
+ appCanEnterPip,
+ runningTaskTarget,
+ !enableAdditionalHomeAnimations()
+ || mRecentsView == null
+ || mRecentsView.getCurrentPage() == mRecentsView.getRunningTaskIndex()
+ ? null : mRecentsView.getCurrentPageTaskView());
SwipePipToHomeAnimator swipePipToHomeAnimator = !mIsSwipeForSplit && appCanEnterPip
? createWindowAnimationToPip(homeAnimFactory, runningTaskTarget, start)
: null;
diff --git a/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java b/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
index 92cdf72..625b6c6 100644
--- a/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
+++ b/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
@@ -68,11 +68,12 @@
import com.android.quickstep.util.SurfaceTransaction.SurfaceProperties;
import com.android.quickstep.util.TransformParams;
import com.android.quickstep.util.TransformParams.BuilderProxy;
+import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.recents.model.Task.TaskKey;
import com.android.systemui.shared.system.InputConsumerController;
import java.lang.ref.WeakReference;
-import java.util.ArrayList;
+import java.util.List;
import java.util.UUID;
import java.util.function.Consumer;
@@ -140,9 +141,13 @@
}
@Override
- protected HomeAnimationFactory createHomeAnimationFactory(ArrayList<IBinder> launchCookies,
- long duration, boolean isTargetTranslucent, boolean appCanEnterPip,
- RemoteAnimationTarget runningTaskTarget) {
+ protected HomeAnimationFactory createHomeAnimationFactory(
+ List<IBinder> launchCookies,
+ long duration,
+ boolean isTargetTranslucent,
+ boolean appCanEnterPip,
+ RemoteAnimationTarget runningTaskTarget,
+ @Nullable TaskView targetTaskView) {
mAppCanEnterPip = appCanEnterPip;
if (appCanEnterPip) {
return new FallbackPipToHomeAnimationFactory();
@@ -380,7 +385,7 @@
}
@Override
- public void update(RectF currentRect, float progress, float radius) {
+ public void update(RectF currentRect, float progress, float radius, int overlayAlpha) {
if (mSurfaceControl != null) {
currentRect.roundOut(mTempRect);
Transaction t = new Transaction();
diff --git a/quickstep/src/com/android/quickstep/GestureState.java b/quickstep/src/com/android/quickstep/GestureState.java
index c8a91df..81c9d4a 100644
--- a/quickstep/src/com/android/quickstep/GestureState.java
+++ b/quickstep/src/com/android/quickstep/GestureState.java
@@ -186,6 +186,7 @@
private final long mSwipeUpStartTimeMs = SystemClock.uptimeMillis();
private boolean mHandlingAtomicEvent;
+ private boolean mIsInExtendedSlopRegion;
public GestureState(OverviewComponentObserver componentObserver, int gestureId) {
mHomeIntent = componentObserver.getHomeIntent();
@@ -494,6 +495,25 @@
}
/**
+ * Set whether it's in long press nav handle (LPNH)'s extended touch slop region, e.g., second
+ * stage region in order to continue respect LPNH and ignore other touch slop logic.
+ * This will only be set to true when flag ENABLE_LPNH_TWO_STAGES is turned on.
+ */
+ public void setIsInExtendedSlopRegion(boolean isInExtendedSlopRegion) {
+ if (DeviceConfigWrapper.get().getEnableLpnhTwoStages()) {
+ mIsInExtendedSlopRegion = isInExtendedSlopRegion;
+ }
+ }
+
+ /**
+ * Returns whether it's in LPNH's extended touch slop region. This is only valid when flag
+ * ENABLE_LPNH_TWO_STAGES is turned on.
+ */
+ public boolean isInExtendedSlopRegion() {
+ return mIsInExtendedSlopRegion;
+ }
+
+ /**
* Returns and clears the canceled animation thumbnail data. This call only returns a value
* while STATE_RECENTS_ANIMATION_CANCELED state is being set, and the caller is responsible for
* calling {@link RecentsAnimationController#cleanupScreenshot()}.
diff --git a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
index af02ccf..4b5a15d 100644
--- a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
+++ b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
@@ -43,6 +43,7 @@
import com.android.launcher3.states.StateAnimationConfig;
import com.android.launcher3.uioverrides.QuickstepLauncher;
import com.android.launcher3.util.ObjectWrapper;
+import com.android.launcher3.views.ClipIconView;
import com.android.launcher3.views.FloatingIconView;
import com.android.launcher3.views.FloatingView;
import com.android.launcher3.widget.LauncherAppWidgetHostView;
@@ -55,7 +56,8 @@
import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.system.InputConsumerController;
-import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
/**
* Temporary class to allow easier refactoring
@@ -72,9 +74,13 @@
@Override
- protected HomeAnimationFactory createHomeAnimationFactory(ArrayList<IBinder> launchCookies,
- long duration, boolean isTargetTranslucent, boolean appCanEnterPip,
- RemoteAnimationTarget runningTaskTarget) {
+ protected HomeAnimationFactory createHomeAnimationFactory(
+ List<IBinder> launchCookies,
+ long duration,
+ boolean isTargetTranslucent,
+ boolean appCanEnterPip,
+ RemoteAnimationTarget runningTaskTarget,
+ @Nullable TaskView targetTaskView) {
if (mContainer == null) {
mStateCallback.addChangeListener(STATE_LAUNCHER_PRESENT | STATE_HANDLER_INVALIDATED,
isPresent -> mRecentsView.startHome());
@@ -86,8 +92,14 @@
};
}
- final View workspaceView = findWorkspaceView(launchCookies,
- mRecentsView.getRunningTaskView());
+ TaskView sourceTaskView = mRecentsView == null && targetTaskView == null
+ ? null
+ : targetTaskView == null
+ ? mRecentsView.getRunningTaskView()
+ : targetTaskView;
+ final View workspaceView = findWorkspaceView(
+ targetTaskView == null ? launchCookies : Collections.emptyList(),
+ sourceTaskView);
boolean canUseWorkspaceView = workspaceView != null
&& workspaceView.isAttachedToWindow()
&& workspaceView.getHeight() > 0
@@ -100,16 +112,24 @@
}
if (!canUseWorkspaceView || appCanEnterPip || mIsSwipeForSplit) {
- return new LauncherHomeAnimationFactory();
+ return new LauncherHomeAnimationFactory() {
+
+ @Nullable
+ @Override
+ public TaskView getTargetTaskView() {
+ return targetTaskView;
+ }
+ };
}
if (workspaceView instanceof LauncherAppWidgetHostView) {
return createWidgetHomeAnimationFactory((LauncherAppWidgetHostView) workspaceView,
isTargetTranslucent, runningTaskTarget);
}
- return createIconHomeAnimationFactory(workspaceView);
+ return createIconHomeAnimationFactory(workspaceView, targetTaskView);
}
- private HomeAnimationFactory createIconHomeAnimationFactory(View workspaceView) {
+ private HomeAnimationFactory createIconHomeAnimationFactory(
+ View workspaceView, @Nullable TaskView targetTaskView) {
RectF iconLocation = new RectF();
FloatingIconView floatingIconView = getFloatingIconView(mContainer, workspaceView, null,
mContainer.getTaskbarUIController() == null
@@ -175,10 +195,35 @@
}
@Override
- public void update(RectF currentRect, float progress, float radius) {
- super.update(currentRect, progress, radius);
+ public void update(
+ RectF currentRect,
+ float progress,
+ float radius,
+ int overlayAlpha) {
floatingIconView.update(1f /* alpha */, currentRect, progress, windowAlphaThreshold,
- radius, false);
+ radius, false, overlayAlpha);
+ }
+
+ @Override
+ public boolean isAnimationReady() {
+ return floatingIconView.isLaidOut();
+ }
+
+ @Override
+ public void setTaskViewArtist(ClipIconView.TaskViewArtist taskViewArtist) {
+ super.setTaskViewArtist(taskViewArtist);
+ floatingIconView.setOverlayArtist(taskViewArtist);
+ }
+
+ @Override
+ public boolean isAnimatingIntoIcon() {
+ return true;
+ }
+
+ @Nullable
+ @Override
+ public TaskView getTargetTaskView() {
+ return targetTaskView;
}
};
}
@@ -232,8 +277,8 @@
}
@Override
- public void update(RectF currentRect, float progress, float radius) {
- super.update(currentRect, progress, radius);
+ public void update(RectF currentRect, float progress, float radius, int overlayAlpha) {
+ super.update(currentRect, progress, radius, overlayAlpha);
final float fallbackBackgroundAlpha =
1 - mapBoundToRange(progress, 0.8f, 1, 0, 1, EXAGGERATED_EASE);
final float foregroundAlpha =
@@ -254,13 +299,14 @@
* associated with the running task.
*/
@Nullable
- private View findWorkspaceView(ArrayList<IBinder> launchCookies, TaskView runningTaskView) {
+ private View findWorkspaceView(List<IBinder> launchCookies, TaskView sourceTaskView) {
if (mIsSwipingPipToHome) {
// Disable if swiping to PIP
return null;
}
- if (runningTaskView == null || runningTaskView.getTask() == null
- || runningTaskView.getTask().key.getComponent() == null) {
+ if (sourceTaskView == null
+ || sourceTaskView.getTask() == null
+ || sourceTaskView.getTask().key.getComponent() == null) {
// Disable if it's an invalid task
return null;
}
@@ -277,8 +323,8 @@
}
return mContainer.getFirstMatchForAppClose(launchCookieItemId,
- runningTaskView.getTask().key.getComponent().getPackageName(),
- UserHandle.of(runningTaskView.getTask().key.userId),
+ sourceTaskView.getTask().key.getComponent().getPackageName(),
+ UserHandle.of(sourceTaskView.getTask().key.userId),
false /* supportsAllAppsState */);
}
diff --git a/quickstep/src/com/android/quickstep/OverviewComponentObserver.java b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
index 0a02e99..a71e314 100644
--- a/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
+++ b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
@@ -109,6 +109,8 @@
* Sets a listener for changes in {@link #isHomeAndOverviewSame()}
*/
public void setOverviewChangeListener(Consumer<Boolean> overviewChangeListener) {
+ // TODO(b/337861962): This method should be able to support multiple listeners instead of
+ // one so that we can reuse the same instance of this class across multiple places
mOverviewChangeListener = overviewChangeListener;
}
diff --git a/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java b/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
index 5ff9787..fb54241 100644
--- a/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
+++ b/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
@@ -17,6 +17,7 @@
import static com.android.app.animation.Interpolators.ACCELERATE_1_5;
import static com.android.app.animation.Interpolators.LINEAR;
+import static com.android.launcher3.PagedView.INVALID_PAGE;
import android.animation.Animator;
import android.content.Context;
@@ -27,6 +28,7 @@
import android.view.RemoteAnimationTarget;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
import com.android.launcher3.DeviceProfile;
@@ -36,6 +38,7 @@
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.touch.PagedOrientationHandler;
+import com.android.launcher3.views.ClipIconView;
import com.android.quickstep.RemoteTargetGluer.RemoteTargetHandle;
import com.android.quickstep.orientation.RecentsPagedOrientationHandler;
import com.android.quickstep.util.AnimatorControllerWithResistance;
@@ -46,6 +49,8 @@
import com.android.quickstep.util.TaskViewSimulator;
import com.android.quickstep.util.TransformParams;
import com.android.quickstep.util.TransformParams.BuilderProxy;
+import com.android.quickstep.views.RecentsView;
+import com.android.quickstep.views.TaskView;
import java.util.Arrays;
import java.util.function.Consumer;
@@ -201,7 +206,7 @@
public void setAnimation(RectFSpringAnim anim) { }
- public void update(RectF currentRect, float progress, float radius) { }
+ public void update(RectF currentRect, float progress, float radius, int overlayAlpha) { }
public void onCancel() { }
@@ -222,6 +227,33 @@
}
return Utilities.mapToRange(progress, start, end, 1, 0, ACCELERATE_1_5);
}
+
+ /**
+ * Sets a {@link com.android.launcher3.views.ClipIconView.TaskViewArtist} that should be
+ * used draw a {@link TaskView} during this home animation.
+ */
+ public void setTaskViewArtist(ClipIconView.TaskViewArtist taskViewArtist) { }
+
+ public boolean isAnimationReady() {
+ return true;
+ }
+
+ public boolean isAnimatingIntoIcon() {
+ return false;
+ }
+
+ @Nullable
+ public TaskView getTargetTaskView() {
+ return null;
+ }
+
+ public boolean isRtl() {
+ return Utilities.isRtl(mContext.getResources());
+ }
+
+ public boolean isPortrait() {
+ return !mDp.isLandscape && !mDp.isSeascape();
+ }
}
/**
@@ -276,9 +308,13 @@
for (int i = 0, mRemoteTargetHandlesLength = mRemoteTargetHandles.length;
i < mRemoteTargetHandlesLength; i++) {
RemoteTargetHandle remoteHandle = mRemoteTargetHandles[i];
- out[i] = getWindowAnimationToHomeInternal(homeAnimationFactory,
- targetRect, remoteHandle.getTransformParams(),
- remoteHandle.getTaskViewSimulator(), startRects[i], homeToWindowPositionMap[i]);
+ out[i] = getWindowAnimationToHomeInternal(
+ homeAnimationFactory,
+ targetRect,
+ remoteHandle.getTransformParams(),
+ remoteHandle.getTaskViewSimulator(),
+ startRects[i],
+ homeToWindowPositionMap[i]);
}
return out;
}
@@ -288,21 +324,39 @@
}
private RectFSpringAnim getWindowAnimationToHomeInternal(
- HomeAnimationFactory homeAnimationFactory, RectF targetRect,
- TransformParams transformParams, TaskViewSimulator taskViewSimulator,
- RectF startRect, Matrix homeToWindowPositionMap) {
+ HomeAnimationFactory homeAnimationFactory,
+ RectF targetRect,
+ TransformParams transformParams,
+ TaskViewSimulator taskViewSimulator,
+ RectF startRect,
+ Matrix homeToWindowPositionMap) {
RectF cropRectF = new RectF(taskViewSimulator.getCurrentCropRect());
// Move the startRect to Launcher space as floatingIconView runs in Launcher
Matrix windowToHomePositionMap = new Matrix();
- // If the start rect ends up overshooting too much to the left/right offscreen, bring it
- // back to fullscreen. This can happen when the recentsScroll value isn't aligned with
- // the pageScroll value for a given taskView, see b/228829958#comment12
- mRemoteTargetHandles[0].getTaskViewSimulator().getOrientationState().getOrientationHandler()
- .fixBoundsForHomeAnimStartRect(startRect, mDp);
+ TaskView targetTaskView = homeAnimationFactory.getTargetTaskView();
+ if (targetTaskView == null) {
+ // If the start rect ends up overshooting too much to the left/right offscreen, bring it
+ // back to fullscreen. This can happen when the recentsScroll value isn't aligned with
+ // the pageScroll value for a given taskView, see b/228829958#comment12
+ mRemoteTargetHandles[0].getTaskViewSimulator()
+ .getOrientationState()
+ .getOrientationHandler()
+ .fixBoundsForHomeAnimStartRect(startRect, mDp);
+ }
homeToWindowPositionMap.invert(windowToHomePositionMap);
windowToHomePositionMap.mapRect(startRect);
+ RectF invariantStartRect = new RectF(startRect);
+
+ if (targetTaskView != null) {
+ Rect thumbnailBounds = new Rect();
+ targetTaskView.getThumbnailBounds(thumbnailBounds, /* relativeToDragLayer= */ true);
+
+ invariantStartRect = new RectF(thumbnailBounds);
+ invariantStartRect.offsetTo(startRect.left, thumbnailBounds.top);
+ startRect = new RectF(thumbnailBounds);
+ }
boolean useTaskbarHotseatParams = mDp.isTaskbarPresent
&& homeAnimationFactory.isInHotseat();
@@ -312,8 +366,12 @@
homeAnimationFactory.setAnimation(anim);
SpringAnimationRunner runner = new SpringAnimationRunner(
- homeAnimationFactory, cropRectF, homeToWindowPositionMap,
- transformParams, taskViewSimulator);
+ homeAnimationFactory,
+ cropRectF,
+ homeToWindowPositionMap,
+ transformParams,
+ taskViewSimulator,
+ invariantStartRect);
anim.addAnimatorListener(runner);
anim.addOnUpdateListener(runner);
return anim;
@@ -336,9 +394,30 @@
final float mStartRadius;
final float mEndRadius;
- SpringAnimationRunner(HomeAnimationFactory factory, RectF cropRectF,
- Matrix homeToWindowPositionMap, TransformParams transformParams,
- TaskViewSimulator taskViewSimulator) {
+ final RectF mRunningTaskViewStartRectF;
+ @Nullable
+ final TaskView mTargetTaskView;
+ final float mRunningTaskViewScrollOffset;
+ final float mTaskViewWidth;
+ final float mTaskViewHeight;
+ final boolean mIsPortrait;
+ final Rect mThumbnailStartBounds = new Rect();
+
+ // Store the mTargetTaskView view properties onAnimationStart so that we can reset them
+ // when cleaning up.
+ float mTaskViewAlpha;
+ float mTaskViewTranslationX;
+ float mTaskViewTranslationY;
+ float mTaskViewScaleX;
+ float mTaskViewScaleY;
+
+ SpringAnimationRunner(
+ HomeAnimationFactory factory,
+ RectF cropRectF,
+ Matrix homeToWindowPositionMap,
+ TransformParams transformParams,
+ TaskViewSimulator taskViewSimulator,
+ RectF invariantStartRect) {
mAnimationFactory = factory;
mHomeAnim = factory.createActivityAnimationToHome();
mCropRectF = cropRectF;
@@ -351,22 +430,62 @@
// rounding at the end of the animation.
mStartRadius = taskViewSimulator.getCurrentCornerRadius();
mEndRadius = factory.getEndRadius(cropRectF);
+
+ mRunningTaskViewStartRectF = invariantStartRect;
+ mTargetTaskView = factory.getTargetTaskView();
+ mTaskViewWidth = mTargetTaskView == null ? 0 : mTargetTaskView.getWidth();
+ mTaskViewHeight = mTargetTaskView == null ? 0 : mTargetTaskView.getHeight();
+ mIsPortrait = factory.isPortrait();
+ // Use the running task's start position to determine how much it needs to be offset
+ // to end up offscreen.
+ mRunningTaskViewScrollOffset = factory.isRtl()
+ ? (Math.min(0, -invariantStartRect.right))
+ : (Math.max(0, mDp.widthPx - invariantStartRect.left));
}
@Override
public void onUpdate(RectF currentRect, float progress) {
- mHomeAnim.setPlayFraction(progress);
- mHomeToWindowPositionMap.mapRect(mWindowCurrentRect, currentRect);
-
- mMatrix.setRectToRect(mCropRectF, mWindowCurrentRect, ScaleToFit.FILL);
float cornerRadius = Utilities.mapRange(progress, mStartRadius, mEndRadius);
float alpha = mAnimationFactory.getWindowAlpha(progress);
- mLocalTransformParams
- .setTargetAlpha(alpha)
- .setCornerRadius(cornerRadius);
- mLocalTransformParams.applySurfaceParams(mLocalTransformParams
- .createSurfaceParams(this));
- mAnimationFactory.update(currentRect, progress, mMatrix.mapRadius(cornerRadius));
+
+ mHomeAnim.setPlayFraction(progress);
+ if (mTargetTaskView == null) {
+ mHomeToWindowPositionMap.mapRect(mWindowCurrentRect, currentRect);
+ mMatrix.setRectToRect(mCropRectF, mWindowCurrentRect, ScaleToFit.FILL);
+ mLocalTransformParams
+ .setTargetAlpha(alpha)
+ .setCornerRadius(cornerRadius);
+ } else {
+ mHomeToWindowPositionMap.mapRect(mWindowCurrentRect, mRunningTaskViewStartRectF);
+ mWindowCurrentRect.offset(mRunningTaskViewScrollOffset * progress, 0f);
+ mMatrix.setRectToRect(mCropRectF, mWindowCurrentRect, ScaleToFit.FILL);
+ mLocalTransformParams.setCornerRadius(mStartRadius);
+ }
+
+ mLocalTransformParams.applySurfaceParams(
+ mLocalTransformParams.createSurfaceParams(this));
+ mAnimationFactory.update(
+ currentRect, progress, mMatrix.mapRadius(cornerRadius), (int) (alpha * 255));
+
+ if (mTargetTaskView == null) {
+ return;
+ }
+ if (mAnimationFactory.isAnimatingIntoIcon() && mAnimationFactory.isAnimationReady()) {
+ mTargetTaskView.setAlpha(0f);
+ 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);
+
+ mTargetTaskView.setScaleX(scale);
+ mTargetTaskView.setScaleY(scale);
+ mTargetTaskView.setTranslationX(
+ currentRect.centerX() - mThumbnailStartBounds.centerX());
+ mTargetTaskView.setTranslationY(
+ currentRect.centerY() - mThumbnailStartBounds.centerY());
}
@Override
@@ -379,16 +498,71 @@
@Override
public void onCancel() {
+ cleanUp();
mAnimationFactory.onCancel();
}
@Override
public void onAnimationStart(Animator animation) {
+ setUp();
mHomeAnim.dispatchOnStart();
+ if (mTargetTaskView == null) {
+ return;
+ }
+ Rect thumbnailBounds = new Rect();
+ // Use bounds relative to mTargetTaskView since it will be scaled afterwards
+ mTargetTaskView.getThumbnailBounds(thumbnailBounds);
+ mAnimationFactory.setTaskViewArtist(new ClipIconView.TaskViewArtist(
+ mTargetTaskView::draw,
+ 0f,
+ -thumbnailBounds.top,
+ Math.min(mTaskViewHeight, mTaskViewWidth),
+ mIsPortrait));
+ }
+
+ private void setUp() {
+ if (mTargetTaskView == null) {
+ return;
+ }
+ RecentsView recentsView = mTargetTaskView.getRecentsView();
+ if (recentsView != null) {
+ recentsView.setOffsetMidpointIndexOverride(
+ recentsView.indexOfChild(mTargetTaskView));
+ }
+ mTargetTaskView.getThumbnailBounds(
+ mThumbnailStartBounds, /* relativeToDragLayer= */ true);
+ mTaskViewAlpha = mTargetTaskView.getAlpha();
+ if (mAnimationFactory.isAnimatingIntoIcon()) {
+ return;
+ }
+ mTaskViewTranslationX = mTargetTaskView.getTranslationX();
+ mTaskViewTranslationY = mTargetTaskView.getTranslationY();
+ mTaskViewScaleX = mTargetTaskView.getScaleX();
+ mTaskViewScaleY = mTargetTaskView.getScaleY();
+ }
+
+ private void cleanUp() {
+ if (mTargetTaskView == null) {
+ return;
+ }
+ RecentsView recentsView = mTargetTaskView.getRecentsView();
+ if (recentsView != null) {
+ recentsView.setOffsetMidpointIndexOverride(INVALID_PAGE);
+ }
+ mTargetTaskView.setAlpha(mTaskViewAlpha);
+ if (!mAnimationFactory.isAnimatingIntoIcon()) {
+ mTargetTaskView.setTranslationX(mTaskViewTranslationX);
+ mTargetTaskView.setTranslationY(mTaskViewTranslationY);
+ mTargetTaskView.setScaleX(mTaskViewScaleX);
+ mTargetTaskView.setScaleY(mTaskViewScaleY);
+ return;
+ }
+ mAnimationFactory.setTaskViewArtist(null);
}
@Override
public void onAnimationSuccess(Animator animator) {
+ cleanUp();
mHomeAnim.getAnimationPlayer().end();
}
}
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java
index fcf5ffc..0ad60b7 100644
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.java
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java
@@ -800,15 +800,29 @@
/**
* Tells SysUI when the bubble is being dragged.
* Should be called only when the bubble bar is expanded.
- * @param bubbleKey the key of the bubble to collapse/expand
- * @param isBeingDragged whether the bubble is being dragged
+ * @param bubbleKey key of the bubble being dragged
*/
- public void onBubbleDrag(@Nullable String bubbleKey, boolean isBeingDragged) {
+ public void startBubbleDrag(@Nullable String bubbleKey) {
if (mBubbles == null) return;
try {
- mBubbles.onBubbleDrag(bubbleKey, isBeingDragged);
+ mBubbles.startBubbleDrag(bubbleKey);
} catch (RemoteException e) {
- Log.w(TAG, "Failed call onBubbleDrag");
+ Log.w(TAG, "Failed call startBubbleDrag");
+ }
+ }
+
+ /**
+ * Tells SysUI when the bubble stops being dragged.
+ * Should be called only when the bubble bar is expanded.
+ * @param bubbleKey key of the bubble being dragged
+ * @param location location of the bubble bar
+ */
+ public void stopBubbleDrag(@Nullable String bubbleKey, BubbleBarLocation location) {
+ if (mBubbles == null) return;
+ try {
+ mBubbles.stopBubbleDrag(bubbleKey, location);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed call stopBubbleDrag");
}
}
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index 832f4e1..f94a29c 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -1018,7 +1018,7 @@
.append("TaskbarActivityContext != null, ")
.append("using TaskbarUnstashInputConsumer");
base = new TaskbarUnstashInputConsumer(this, base, mInputMonitorCompat, tac,
- mOverviewCommandHelper);
+ mOverviewCommandHelper, mGestureState);
}
}
if (enableBubblesLongPressNavHandle()) {
@@ -1046,7 +1046,7 @@
}
reasonString.append("using NavHandleLongPressInputConsumer");
base = new NavHandleLongPressInputConsumer(this, base, mInputMonitorCompat,
- mDeviceState, navHandle);
+ mDeviceState, navHandle, mGestureState);
}
if (!enableBubblesLongPressNavHandle()) {
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
index d881a1f..b79586b 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
@@ -53,6 +53,7 @@
import com.android.systemui.shared.recents.model.Task;
import java.util.ArrayList;
+import java.util.Arrays;
public class FallbackRecentsView extends RecentsView<RecentsActivity, RecentsState>
implements StateListener<RecentsState> {
@@ -144,8 +145,9 @@
@Override
public void setCurrentTask(int runningTaskViewId) {
super.setCurrentTask(runningTaskViewId);
- int runningTaskId = getTaskIdsForRunningTaskView()[0];
- if (mHomeTask != null && mHomeTask.key.id != runningTaskId) {
+ int[] runningTaskIds = getTaskIdsForRunningTaskView();
+ if (mHomeTask != null
+ && Arrays.stream(runningTaskIds).noneMatch(taskId -> taskId == mHomeTask.key.id)) {
mHomeTask = null;
setRunningTaskHidden(false);
}
@@ -182,13 +184,14 @@
// as well. This tile is never shown as we have setCurrentTaskHidden, but allows use to
// track the index of the next task appropriately, as if we are switching on any other app.
// TODO(b/195607777) Confirm home task info is front-most task and not mixed in with others
- int runningTaskId = getTaskIdsForRunningTaskView()[0];
- if (mHomeTask != null && mHomeTask.key.id == runningTaskId
+ int[] runningTaskIds = getTaskIdsForRunningTaskView();
+ if (mHomeTask != null
+ && Arrays.stream(runningTaskIds).allMatch(taskId -> taskId == mHomeTask.key.id)
&& !taskGroups.isEmpty()) {
// Check if the task list has running task
boolean found = false;
for (GroupTask group : taskGroups) {
- if (group.containsTask(runningTaskId)) {
+ if (Arrays.stream(runningTaskIds).allMatch(group::containsTask)) {
found = true;
break;
}
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumer.java
index 075e539..848a43a 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumer.java
@@ -31,6 +31,7 @@
import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.util.DisplayController;
import com.android.quickstep.DeviceConfigWrapper;
+import com.android.quickstep.GestureState;
import com.android.quickstep.InputConsumer;
import com.android.quickstep.NavHandle;
import com.android.quickstep.RecentsAnimationDeviceState;
@@ -61,13 +62,14 @@
private final NavHandle mNavHandle;
private final StatsLogManager mStatsLogManager;
private final TopTaskTracker mTopTaskTracker;
+ private final GestureState mGestureState;
private MotionEvent mCurrentDownEvent;
private boolean mDeepPressLogged; // Whether deep press has been logged for the current touch.
public NavHandleLongPressInputConsumer(Context context, InputConsumer delegate,
InputMonitorCompat inputMonitor, RecentsAnimationDeviceState deviceState,
- NavHandle navHandle) {
+ NavHandle navHandle, GestureState gestureState) {
super(delegate, inputMonitor);
mScreenWidth = DisplayController.INSTANCE.get(context).getInfo().currentSize.x;
mDeepPressEnabled = DeviceConfigWrapper.get().getEnableLpnhDeepPress();
@@ -82,6 +84,8 @@
mTouchSlopSquaredOriginal = deviceState.getSquaredTouchSlop();
mTouchSlopSquared = mTouchSlopSquaredOriginal;
mOuterTouchSlopSquared = mTouchSlopSquared * (twoStageMultiplier * twoStageMultiplier);
+ mGestureState = gestureState;
+ mGestureState.setIsInExtendedSlopRegion(false);
if (DEBUG_NAV_HANDLE) {
Log.d(TAG, "mLongPressTimeout=" + mLongPressTimeout);
Log.d(TAG, "mOuterLongPressTimeout=" + mOuterLongPressTimeout);
@@ -126,6 +130,7 @@
}
mCurrentDownEvent = MotionEvent.obtain(ev);
mTouchSlopSquared = mTouchSlopSquaredOriginal;
+ mGestureState.setIsInExtendedSlopRegion(false);
mDeepPressLogged = false;
if (isInNavBarHorizontalArea(ev.getRawX())) {
mNavHandleLongPressHandler.onTouchStarted(mNavHandle);
@@ -154,6 +159,7 @@
- (int) (ev.getEventTime() - ev.getDownTime());
MAIN_EXECUTOR.getHandler().postDelayed(mTriggerLongPress, delay);
mTouchSlopSquared = mOuterTouchSlopSquared;
+ mGestureState.setIsInExtendedSlopRegion(true);
if (DEBUG_NAV_HANDLE) {
Log.d(TAG, "Touch in middle region!");
}
@@ -219,6 +225,7 @@
if (DEBUG_NAV_HANDLE) {
Log.d(TAG, "cancelLongPress");
}
+ mGestureState.setIsInExtendedSlopRegion(false);
MAIN_EXECUTOR.getHandler().removeCallbacks(mTriggerLongPress);
mNavHandleLongPressHandler.onTouchFinished(mNavHandle, reason);
}
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
index 9f39476..0d450c6 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
@@ -284,8 +284,9 @@
float horizontalDist = Math.abs(displacementX);
float upDist = -displacement;
- boolean passedSlop = mGestureState.isTrackpadGesture() || squaredHypot(
- displacementX, displacementY) >= mSquaredTouchSlop;
+ boolean passedSlop = mGestureState.isTrackpadGesture()
+ || (squaredHypot(displacementX, displacementY) >= mSquaredTouchSlop
+ && !mGestureState.isInExtendedSlopRegion());
if (!mPassedSlopOnThisGesture && passedSlop) {
mPassedSlopOnThisGesture = true;
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
index bb8d1d7..c61f71d 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
@@ -48,6 +48,7 @@
private final BaseContainerInterface<?, T> mContainerInterface;
private final BaseDragLayer mTarget;
private final InputMonitorCompat mInputMonitor;
+ private final GestureState mGestureState;
private final int[] mLocationOnScreen = new int[2];
@@ -62,6 +63,7 @@
mInputMonitor = inputMonitor;
mStartingInActivityBounds = startingInActivityBounds;
mContainerInterface = gestureState.getContainerInterface();
+ mGestureState = gestureState;
mTarget = container.getDragLayer();
mTarget.getLocationOnScreen(mLocationOnScreen);
@@ -84,7 +86,10 @@
ev.setEdgeFlags(flags | Utilities.EDGE_NAV_BAR);
}
ev.offsetLocation(-mLocationOnScreen[0], -mLocationOnScreen[1]);
- boolean handled = mTarget.proxyTouchEvent(ev, mStartingInActivityBounds);
+ boolean handled = false;
+ if (mGestureState == null || !mGestureState.isInExtendedSlopRegion()) {
+ handled = mTarget.proxyTouchEvent(ev, mStartingInActivityBounds);
+ }
ev.offsetLocation(mLocationOnScreen[0], mLocationOnScreen[1]);
ev.setEdgeFlags(flags);
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/TaskbarUnstashInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/TaskbarUnstashInputConsumer.java
index cd180ba..6b3e6e9 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/TaskbarUnstashInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/TaskbarUnstashInputConsumer.java
@@ -42,6 +42,7 @@
import com.android.launcher3.taskbar.bubbles.BubbleControllers;
import com.android.launcher3.touch.OverScroll;
import com.android.launcher3.util.DisplayController;
+import com.android.quickstep.GestureState;
import com.android.quickstep.InputConsumer;
import com.android.quickstep.OverviewCommandHelper;
import com.android.systemui.shared.system.InputMonitorCompat;
@@ -76,10 +77,11 @@
private final int mStashedTaskbarBottomEdge;
private final @Nullable TransitionCallback mTransitionCallback;
+ private final GestureState mGestureState;
public TaskbarUnstashInputConsumer(Context context, InputConsumer delegate,
InputMonitorCompat inputMonitor, TaskbarActivityContext taskbarActivityContext,
- OverviewCommandHelper overviewCommandHelper) {
+ OverviewCommandHelper overviewCommandHelper, GestureState gestureState) {
super(delegate, inputMonitor);
mTaskbarActivityContext = taskbarActivityContext;
mOverviewCommandHelper = overviewCommandHelper;
@@ -103,6 +105,7 @@
mTransitionCallback = mIsTransientTaskbar
? taskbarActivityContext.getTranslationCallbacks()
: null;
+ mGestureState = gestureState;
}
@Override
@@ -111,6 +114,11 @@
}
@Override
+ public boolean allowInterceptByParent() {
+ return super.allowInterceptByParent() && !mHasPassedTaskbarNavThreshold;
+ }
+
+ @Override
public void onMotionEvent(MotionEvent ev) {
if (mState != STATE_ACTIVE) {
boolean isStashedTaskbarHovered = isMouseEvent(ev)
@@ -173,7 +181,8 @@
boolean passedTaskbarNavThreshold = dY < 0
&& Math.abs(dY) >= mTaskbarNavThreshold;
- if (!mHasPassedTaskbarNavThreshold && passedTaskbarNavThreshold) {
+ if (!mHasPassedTaskbarNavThreshold && passedTaskbarNavThreshold
+ && !mGestureState.isInExtendedSlopRegion()) {
mHasPassedTaskbarNavThreshold = true;
if (mIsInBubbleBarArea && mIsVerticalGestureOverBubbleBar) {
mTaskbarActivityContext.onSwipeToOpenBubblebar();
diff --git a/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java
index d5cc447..ad13efb 100644
--- a/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java
@@ -382,7 +382,7 @@
}
@Override
- public void update(RectF rect, float progress, float radius) {
+ public void update(RectF rect, float progress, float radius, int overlayAlpha) {
mFakeIconView.setVisibility(View.VISIBLE);
mFakeIconView.update(rect, progress,
1f - SHAPE_PROGRESS_DURATION /* shapeProgressStart */,
diff --git a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
index ed633df..e3e14ae 100644
--- a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
+++ b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
@@ -58,10 +58,6 @@
import com.android.launcher3.logger.LauncherAtomExtensions.ExtendedContainers;
import com.android.launcher3.logging.InstanceId;
import com.android.launcher3.logging.StatsLogManager;
-import com.android.launcher3.model.AllAppsList;
-import com.android.launcher3.model.BaseModelUpdateTask;
-import com.android.launcher3.model.BgDataModel;
-import com.android.launcher3.model.data.CollectionInfo;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.util.Executors;
import com.android.launcher3.util.LogConfig;
@@ -371,17 +367,9 @@
if (mItemInfo.container < 0 || !LauncherAppState.INSTANCE.executeIfCreated(app -> {
// Item is inside a collection, fetch collection info in a BG thread
// and then write to StatsLog.
- app.getModel().enqueueModelUpdateTask(
- new BaseModelUpdateTask() {
- @Override
- public void execute(@NonNull final LauncherAppState app,
- @NonNull final BgDataModel dataModel,
- @NonNull final AllAppsList apps) {
- CollectionInfo collectionInfo =
- dataModel.collections.get(mItemInfo.container);
- write(event, applyOverwrites(mItemInfo.buildProto(collectionInfo)));
- }
- });
+ app.getModel().enqueueModelUpdateTask((taskController, dataModel, apps) ->
+ write(event, applyOverwrites(mItemInfo.buildProto(
+ dataModel.collections.get(mItemInfo.container)))));
})) {
// Write log on the model thread so that logs do not go out of order
// (for eg: drop comes after drag)
diff --git a/quickstep/src/com/android/quickstep/util/DesktopTask.java b/quickstep/src/com/android/quickstep/util/DesktopTask.java
index b3f5d82..07f2d68 100644
--- a/quickstep/src/com/android/quickstep/util/DesktopTask.java
+++ b/quickstep/src/com/android/quickstep/util/DesktopTask.java
@@ -56,4 +56,10 @@
public DesktopTask copy() {
return new DesktopTask(tasks);
}
+
+ @Override
+ public String toString() {
+ return "type=" + taskViewType + " tasks=" + tasks;
+ }
+
}
diff --git a/quickstep/src/com/android/quickstep/util/GroupTask.java b/quickstep/src/com/android/quickstep/util/GroupTask.java
index 9c49647..7dd6afc 100644
--- a/quickstep/src/com/android/quickstep/util/GroupTask.java
+++ b/quickstep/src/com/android/quickstep/util/GroupTask.java
@@ -70,4 +70,10 @@
task2 != null ? new Task(task2) : null,
mSplitBounds);
}
+
+ @Override
+ public String toString() {
+ return "type=" + taskViewType + " task1=" + task1 + " task2=" + task2;
+ }
+
}
diff --git a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
index f430d79..ee2c2e1 100644
--- a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
+++ b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
@@ -23,6 +23,7 @@
import android.animation.ObjectAnimator
import android.animation.ValueAnimator
import android.app.ActivityManager.RunningTaskInfo
+import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW
import android.content.Context
import android.graphics.Bitmap
@@ -50,6 +51,7 @@
import com.android.launcher3.apppairs.AppPairIcon
import com.android.launcher3.config.FeatureFlags
import com.android.launcher3.logging.StatsLogManager.EventEnum
+import com.android.launcher3.model.data.WorkspaceItemInfo
import com.android.launcher3.statehandlers.DepthController
import com.android.launcher3.statemanager.StateManager
import com.android.launcher3.taskbar.TaskbarActivityContext
@@ -69,6 +71,7 @@
import com.android.quickstep.views.TaskView
import com.android.quickstep.views.TaskView.TaskIdAttributeContainer
import com.android.quickstep.views.TaskViewIcon
+import com.android.wm.shell.shared.TransitionUtil
import java.util.Optional
import java.util.function.Supplier
@@ -553,8 +556,14 @@
check(info != null && t != null) {
"trying to launch an app pair icon, but encountered an unexpected null"
}
-
- composeIconSplitLaunchAnimator(launchingIconView, info, t, finishCallback)
+ val appPairLaunchingAppIndex = hasChangesForBothAppPairs(launchingIconView, info)
+ if (appPairLaunchingAppIndex == -1) {
+ // Launch split app pair animation
+ composeIconSplitLaunchAnimator(launchingIconView, info, t, finishCallback)
+ } else {
+ composeFullscreenIconSplitLaunchAnimator(launchingIconView, info, t,
+ finishCallback, appPairLaunchingAppIndex)
+ }
} else {
// Fallback case: simple fade-in animation
check(info != null && t != null) {
@@ -619,6 +628,39 @@
}
/**
+ * @return -1 if [transitionInfo] contains both apps of the app pair to be animated, otherwise
+ * the integer index corresponding to [launchingIconView]'s contents for the single app
+ * to be animated
+ */
+ fun hasChangesForBothAppPairs(launchingIconView: AppPairIcon,
+ transitionInfo: TransitionInfo) : Int {
+ val intent1 = launchingIconView.info.getFirstApp().intent.component?.packageName
+ val intent2 = launchingIconView.info.getSecondApp().intent.component?.packageName
+ var launchFullscreenAppIndex = -1
+ for (change in transitionInfo.changes) {
+ val taskInfo: RunningTaskInfo = change.taskInfo ?: continue
+ if (TransitionUtil.isOpeningType(change.mode) &&
+ taskInfo.windowingMode == WINDOWING_MODE_FULLSCREEN) {
+ val baseIntent = taskInfo.baseIntent.component?.packageName
+ if (baseIntent == intent1) {
+ if (launchFullscreenAppIndex > -1) {
+ launchFullscreenAppIndex = -1
+ break
+ }
+ launchFullscreenAppIndex = 0
+ } else if (baseIntent == intent2) {
+ if (launchFullscreenAppIndex > -1) {
+ launchFullscreenAppIndex = -1
+ break
+ }
+ launchFullscreenAppIndex = 1
+ }
+ }
+ }
+ return launchFullscreenAppIndex
+ }
+
+ /**
* When the user taps an app pair icon to launch split, this will play the tasks' launch
* animation from the position of the icon.
*
@@ -653,7 +695,8 @@
// If launching an app pair from Taskbar inside of an app context (no access to Launcher),
// use the scale-up animation
if (launchingIconView.context is TaskbarActivityContext) {
- composeScaleUpLaunchAnimation(transitionInfo, t, finishCallback)
+ composeScaleUpLaunchAnimation(transitionInfo, t, finishCallback,
+ WINDOWING_MODE_MULTI_WINDOW)
return
}
@@ -663,11 +706,6 @@
// Create an AnimatorSet that will run both shell and launcher transitions together
val launchAnimation = AnimatorSet()
- val progressUpdater = ValueAnimator.ofFloat(0f, 1f)
- val timings = AnimUtils.getDeviceAppPairLaunchTimings(dp.isTablet)
- progressUpdater.setDuration(timings.getDuration().toLong())
- progressUpdater.interpolator = Interpolators.LINEAR
-
var rootCandidate: Change? = null
for (change in transitionInfo.changes) {
@@ -711,27 +749,13 @@
// Make sure nothing weird happened, like getChange() returning null.
check(rootCandidate != null) { "Failed to find a root leash" }
- // Shell animation: the apps are revealed toward end of the launch animation
- progressUpdater.addUpdateListener { valueAnimator: ValueAnimator ->
- val progress =
- Interpolators.clampToProgress(
- Interpolators.LINEAR,
- valueAnimator.animatedFraction,
- timings.appRevealStartOffset,
- timings.appRevealEndOffset
- )
-
- // Set the alpha of the shell layer (2 apps + divider)
- t.setAlpha(rootCandidate.leash, progress)
- t.apply()
- }
-
// Create a new floating view in Launcher, positioned above the launching icon
val drawableArea = launchingIconView.iconDrawableArea
val appIcon1 = launchingIconView.info.getFirstApp().newIcon(launchingIconView.context)
val appIcon2 = launchingIconView.info.getSecondApp().newIcon(launchingIconView.context)
appIcon1.setBounds(0, 0, dp.iconSizePx, dp.iconSizePx)
appIcon2.setBounds(0, 0, dp.iconSizePx, dp.iconSizePx)
+
val floatingView =
FloatingAppPairView.getFloatingAppPairView(
launcher,
@@ -742,84 +766,189 @@
)
floatingView.bringToFront()
- // Launcher animation: animate the floating view, expanding to fill the display surface
- progressUpdater.addUpdateListener(
- object : MultiValueUpdateListener() {
- var mDx =
- FloatProp(
- floatingView.startingPosition.left,
- dp.widthPx / 2f - floatingView.startingPosition.width() / 2f,
- Interpolators.clampToProgress(
- timings.getStagedRectXInterpolator(),
- timings.stagedRectSlideStartOffset,
- timings.stagedRectSlideEndOffset
- )
- )
- var mDy =
- FloatProp(
- floatingView.startingPosition.top,
- dp.heightPx / 2f - floatingView.startingPosition.height() / 2f,
- Interpolators.clampToProgress(
- Interpolators.EMPHASIZED,
- timings.stagedRectSlideStartOffset,
- timings.stagedRectSlideEndOffset
- )
- )
- var mScaleX =
- FloatProp(
- 1f /* start */,
- dp.widthPx / floatingView.startingPosition.width(),
- Interpolators.clampToProgress(
- Interpolators.EMPHASIZED,
- timings.stagedRectSlideStartOffset,
- timings.stagedRectSlideEndOffset
- )
- )
- var mScaleY =
- FloatProp(
- 1f /* start */,
- dp.heightPx / floatingView.startingPosition.height(),
- Interpolators.clampToProgress(
- Interpolators.EMPHASIZED,
- timings.stagedRectSlideStartOffset,
- timings.stagedRectSlideEndOffset
- )
- )
-
- override fun onUpdate(percent: Float, initOnly: Boolean) {
- floatingView.progress = percent
- floatingView.x = mDx.value
- floatingView.y = mDy.value
- floatingView.scaleX = mScaleX.value
- floatingView.scaleY = mScaleY.value
- floatingView.invalidate()
- }
- }
- )
-
- // When animation ends, remove the floating view and run finishCallback
- progressUpdater.addListener(
- object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator) {
- safeRemoveViewFromDragLayer(launcher, floatingView)
- finishCallback.run()
- }
- }
- )
-
- launchAnimation.play(progressUpdater)
+ launchAnimation.play(
+ getIconLaunchValueAnimator(t, dp, finishCallback, launcher, floatingView,
+ rootCandidate))
launchAnimation.start()
}
/**
+ * Similar to [composeIconSplitLaunchAnimator], but instructs [FloatingAppPairView] to animate
+ * a single fullscreen icon + background instead of for a pair
+ */
+ @VisibleForTesting
+ fun composeFullscreenIconSplitLaunchAnimator(
+ launchingIconView: AppPairIcon,
+ transitionInfo: TransitionInfo,
+ t: Transaction,
+ finishCallback: Runnable,
+ launchFullscreenIndex: Int
+ ) {
+ // If launching an app pair from Taskbar inside of an app context (no access to Launcher),
+ // use the scale-up animation
+ if (launchingIconView.context is TaskbarActivityContext) {
+ composeScaleUpLaunchAnimation(transitionInfo, t, finishCallback,
+ WINDOWING_MODE_FULLSCREEN)
+ return
+ }
+
+ // Else we are in Launcher and can launch with the full icon stretch-and-split animation.
+ val launcher = QuickstepLauncher.getLauncher(launchingIconView.context)
+ val dp = launcher.deviceProfile
+
+ // Create an AnimatorSet that will run both shell and launcher transitions together
+ val launchAnimation = AnimatorSet()
+
+ val appInfo = launchingIconView.info
+ .getContents()[launchFullscreenIndex] as WorkspaceItemInfo
+ val intentToLaunch = appInfo.intent.component?.packageName
+ var rootCandidate: Change? = null
+ for (change in transitionInfo.changes) {
+ val taskInfo: RunningTaskInfo = change.taskInfo ?: continue
+ val baseIntent = taskInfo.baseIntent.component?.packageName
+ if (TransitionUtil.isOpeningType(change.mode) &&
+ taskInfo.windowingMode == WINDOWING_MODE_FULLSCREEN &&
+ baseIntent == intentToLaunch) {
+ rootCandidate = change
+ }
+ }
+
+ // If we could not find a proper root candidate, something went wrong.
+ check(rootCandidate != null) { "Could not find a split root candidate" }
+
+ // 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
+ }
+
+ // Make sure nothing weird happened, like getChange() returning null.
+ check(rootCandidate != null) { "Failed to find a root leash" }
+
+ // Create a new floating view in Launcher, positioned above the launching icon
+ val drawableArea = launchingIconView.iconDrawableArea
+ val appIcon = appInfo.newIcon(launchingIconView.context)
+ appIcon.setBounds(0, 0, dp.iconSizePx, dp.iconSizePx)
+
+ val floatingView =
+ FloatingAppPairView.getFloatingAppPairView(
+ launcher,
+ drawableArea,
+ appIcon,
+ null /*appIcon2*/,
+ 0 /*dividerPos*/
+ )
+ floatingView.bringToFront()
+ launchAnimation.play(
+ getIconLaunchValueAnimator(t, dp, finishCallback, launcher, floatingView,
+ rootCandidate))
+ launchAnimation.start()
+ }
+
+ private fun getIconLaunchValueAnimator(t: Transaction,
+ dp: com.android.launcher3.DeviceProfile,
+ finishCallback: Runnable,
+ launcher: QuickstepLauncher,
+ floatingView: FloatingAppPairView,
+ rootCandidate: Change) : ValueAnimator {
+ val progressUpdater = ValueAnimator.ofFloat(0f, 1f)
+ val timings = AnimUtils.getDeviceAppPairLaunchTimings(dp.isTablet)
+ progressUpdater.setDuration(timings.getDuration().toLong())
+ progressUpdater.interpolator = Interpolators.LINEAR
+
+ // Shell animation: the apps are revealed toward end of the launch animation
+ progressUpdater.addUpdateListener { valueAnimator: ValueAnimator ->
+ val progress =
+ Interpolators.clampToProgress(
+ Interpolators.LINEAR,
+ valueAnimator.animatedFraction,
+ timings.appRevealStartOffset,
+ timings.appRevealEndOffset
+ )
+
+ // Set the alpha of the shell layer (2 apps + divider)
+ t.setAlpha(rootCandidate.leash, progress)
+ t.apply()
+ }
+
+ progressUpdater.addUpdateListener(
+ object : MultiValueUpdateListener() {
+ var mDx =
+ FloatProp(
+ floatingView.startingPosition.left,
+ dp.widthPx / 2f - floatingView.startingPosition.width() / 2f,
+ Interpolators.clampToProgress(
+ timings.getStagedRectXInterpolator(),
+ timings.stagedRectSlideStartOffset,
+ timings.stagedRectSlideEndOffset
+ )
+ )
+ var mDy =
+ FloatProp(
+ floatingView.startingPosition.top,
+ dp.heightPx / 2f - floatingView.startingPosition.height() / 2f,
+ Interpolators.clampToProgress(
+ Interpolators.EMPHASIZED,
+ timings.stagedRectSlideStartOffset,
+ timings.stagedRectSlideEndOffset
+ )
+ )
+ var mScaleX =
+ FloatProp(
+ 1f /* start */,
+ dp.widthPx / floatingView.startingPosition.width(),
+ Interpolators.clampToProgress(
+ Interpolators.EMPHASIZED,
+ timings.stagedRectSlideStartOffset,
+ timings.stagedRectSlideEndOffset
+ )
+ )
+ var mScaleY =
+ FloatProp(
+ 1f /* start */,
+ dp.heightPx / floatingView.startingPosition.height(),
+ Interpolators.clampToProgress(
+ Interpolators.EMPHASIZED,
+ timings.stagedRectSlideStartOffset,
+ timings.stagedRectSlideEndOffset
+ )
+ )
+
+ override fun onUpdate(percent: Float, initOnly: Boolean) {
+ floatingView.progress = percent
+ floatingView.x = mDx.value
+ floatingView.y = mDy.value
+ floatingView.scaleX = mScaleX.value
+ floatingView.scaleY = mScaleY.value
+ floatingView.invalidate()
+ }
+ }
+ )
+ progressUpdater.addListener(
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator) {
+ safeRemoveViewFromDragLayer(launcher, floatingView)
+ finishCallback.run()
+ }
+ }
+ )
+
+ return progressUpdater
+ }
+
+ /**
* This is a scale-up-and-fade-in animation (34% to 100%) for launching an app in Overview when
* there is no visible associated tile to expand from.
+ * [windowingMode] helps determine whether we are looking for a split or a single fullscreen
+ * [Change]
*/
@VisibleForTesting
fun composeScaleUpLaunchAnimation(
transitionInfo: TransitionInfo,
t: Transaction,
- finishCallback: Runnable
+ finishCallback: Runnable,
+ windowingMode: Int
) {
val launchAnimation = AnimatorSet()
val progressUpdater = ValueAnimator.ofFloat(0f, 1f)
@@ -833,9 +962,8 @@
// 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 &&
+ taskInfo.windowingMode == windowingMode &&
(change.mode == TRANSIT_OPEN || change.mode == TRANSIT_TO_FRONT)
) {
// Found one!
diff --git a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
index c257be6..df1879e 100644
--- a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
+++ b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
@@ -90,7 +90,6 @@
import com.android.quickstep.OverviewComponentObserver;
import com.android.quickstep.RecentsAnimationCallbacks;
import com.android.quickstep.RecentsAnimationController;
-import com.android.quickstep.RecentsAnimationDeviceState;
import com.android.quickstep.RecentsAnimationTargets;
import com.android.quickstep.RecentsModel;
import com.android.quickstep.SplitSelectionListener;
@@ -646,8 +645,13 @@
}
}
- public void initSplitFromDesktopController(QuickstepLauncher launcher) {
- initSplitFromDesktopController(new SplitFromDesktopController(launcher));
+ /**
+ * Init {@code SplitFromDesktopController}
+ */
+ public void initSplitFromDesktopController(QuickstepLauncher launcher,
+ OverviewComponentObserver overviewComponentObserver) {
+ initSplitFromDesktopController(
+ new SplitFromDesktopController(launcher, overviewComponentObserver));
}
@VisibleForTesting
@@ -956,12 +960,10 @@
private ISplitSelectListener mSplitSelectListener;
private Drawable mAppIcon;
- public SplitFromDesktopController(QuickstepLauncher launcher) {
+ public SplitFromDesktopController(QuickstepLauncher launcher,
+ OverviewComponentObserver overviewComponentObserver) {
mLauncher = launcher;
- RecentsAnimationDeviceState deviceState = new RecentsAnimationDeviceState(
- launcher.getApplicationContext());
- mOverviewComponentObserver =
- new OverviewComponentObserver(launcher.getApplicationContext(), deviceState);
+ mOverviewComponentObserver = overviewComponentObserver;
mSplitPlaceholderSize = mLauncher.getResources().getDimensionPixelSize(
R.dimen.split_placeholder_size);
mSplitPlaceholderInset = mLauncher.getResources().getDimensionPixelSize(
diff --git a/quickstep/src/com/android/quickstep/util/SplitWithKeyboardShortcutController.java b/quickstep/src/com/android/quickstep/util/SplitWithKeyboardShortcutController.java
index 555bf21..85d4f4b 100644
--- a/quickstep/src/com/android/quickstep/util/SplitWithKeyboardShortcutController.java
+++ b/quickstep/src/com/android/quickstep/util/SplitWithKeyboardShortcutController.java
@@ -62,12 +62,13 @@
private final int mSplitPlaceholderInset;
public SplitWithKeyboardShortcutController(QuickstepLauncher launcher,
- SplitSelectStateController controller) {
+ SplitSelectStateController controller,
+ OverviewComponentObserver overviewComponentObserver,
+ RecentsAnimationDeviceState deviceState) {
mLauncher = launcher;
mController = controller;
- mDeviceState = new RecentsAnimationDeviceState(launcher.getApplicationContext());
- mOverviewComponentObserver = new OverviewComponentObserver(launcher.getApplicationContext(),
- mDeviceState);
+ mDeviceState = deviceState;
+ mOverviewComponentObserver = overviewComponentObserver;
mSplitPlaceholderSize = mLauncher.getResources().getDimensionPixelSize(
R.dimen.split_placeholder_size);
diff --git a/quickstep/src/com/android/quickstep/util/SystemWindowManagerProxy.java b/quickstep/src/com/android/quickstep/util/SystemWindowManagerProxy.java
index 9268511..304b8f4 100644
--- a/quickstep/src/com/android/quickstep/util/SystemWindowManagerProxy.java
+++ b/quickstep/src/com/android/quickstep/util/SystemWindowManagerProxy.java
@@ -26,9 +26,11 @@
import android.view.WindowMetrics;
import com.android.internal.policy.SystemBarUtils;
+import com.android.launcher3.statehandlers.DesktopVisibilityController;
import com.android.launcher3.util.WindowBounds;
import com.android.launcher3.util.window.CachedDisplayInfo;
import com.android.launcher3.util.window.WindowManagerProxy;
+import com.android.quickstep.LauncherActivityInterface;
import java.util.List;
import java.util.Set;
@@ -49,6 +51,13 @@
}
@Override
+ public boolean isInDesktopMode() {
+ DesktopVisibilityController desktopController =
+ LauncherActivityInterface.INSTANCE.getDesktopVisibilityController();
+ return desktopController != null && desktopController.areDesktopTasksVisible();
+ }
+
+ @Override
public int getRotation(Context displayInfoContext) {
return displayInfoContext.getResources().getConfiguration().windowConfiguration
.getRotation();
diff --git a/quickstep/src/com/android/quickstep/views/ClearAllButton.java b/quickstep/src/com/android/quickstep/views/ClearAllButton.java
index b8afd9d..c3efc3c 100644
--- a/quickstep/src/com/android/quickstep/views/ClearAllButton.java
+++ b/quickstep/src/com/android/quickstep/views/ClearAllButton.java
@@ -34,7 +34,6 @@
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Flags;
import com.android.launcher3.R;
-import com.android.launcher3.statemanager.StatefulActivity;
import com.android.quickstep.orientation.RecentsPagedOrientationHandler;
import com.android.quickstep.util.BorderAnimator;
@@ -136,6 +135,10 @@
* Enable or disable showing border on focus change
*/
public void setBorderEnabled(boolean enabled) {
+ if (mBorderEnabled == enabled) {
+ return;
+ }
+
mBorderEnabled = enabled;
if (mFocusBorderAnimator != null) {
mFocusBorderAnimator.setBorderVisibility(/* visible= */
diff --git a/quickstep/src/com/android/quickstep/views/DesktopTaskView.java b/quickstep/src/com/android/quickstep/views/DesktopTaskView.java
index a0ec525..4915b62 100644
--- a/quickstep/src/com/android/quickstep/views/DesktopTaskView.java
+++ b/quickstep/src/com/android/quickstep/views/DesktopTaskView.java
@@ -141,9 +141,14 @@
}
@Override
- protected Unit updateBorderBounds(@NonNull Rect bounds) {
- bounds.set(mBackgroundView.getLeft(), mBackgroundView.getTop(), mBackgroundView.getRight(),
- mBackgroundView.getBottom());
+ public Unit getThumbnailBounds(@NonNull Rect bounds, boolean relativeToDragLayer) {
+ if (relativeToDragLayer) {
+ mContainer.getDragLayer().getDescendantRectRelativeToSelf(mBackgroundView, bounds);
+ } else {
+ bounds.set(mBackgroundView.getLeft(), mBackgroundView.getTop(),
+ mBackgroundView.getRight(),
+ mBackgroundView.getBottom());
+ }
return Unit.INSTANCE;
}
@@ -204,18 +209,14 @@
}
private void updateTaskIdContainer() {
- // TODO(b/249371338): TaskView expects the array to have at least 2 elements.
- // At least 2 elements in the array
- mTaskIdContainer = new int[Math.max(mTasks.size(), 2)];
+ mTaskIdContainer = new int[mTasks.size()];
for (int i = 0; i < mTasks.size(); i++) {
mTaskIdContainer[i] = mTasks.get(i).key.id;
}
}
private void updateTaskIdAttributeContainer() {
- // TODO(b/249371338): TaskView expects the array to have at least 2 elements.
- // At least 2 elements in the array
- mTaskIdAttributeContainer = new TaskIdAttributeContainer[Math.max(mTasks.size(), 2)];
+ mTaskIdAttributeContainer = new TaskIdAttributeContainer[mTasks.size()];
for (int i = 0; i < mTasks.size(); i++) {
Task task = mTasks.get(i);
TaskThumbnailViewDeprecated thumbnailView = mSnapshotViewMap.get(task.key.id);
@@ -248,12 +249,6 @@
}
@Override
- public boolean containsTaskId(int taskId) {
- // Thumbnail map contains taskId -> thumbnail map. Use the keys for contains
- return mSnapshotViewMap.contains(taskId);
- }
-
- @Override
public void onTaskListVisibilityChanged(boolean visible, int changes) {
cancelPendingLoadTasks();
if (visible) {
@@ -311,9 +306,13 @@
DesktopRecentsTransitionController recentsController =
recentsView.getDesktopRecentsController();
if (recentsController != null) {
- recentsController.launchDesktopFromRecents(this, success -> {
- endCallback.executeAllAndDestroy();
- });
+ recentsController.launchDesktopFromRecents(this,
+ success -> endCallback.executeAllAndDestroy());
+ Log.d(TAG, "launchTaskAnimated - launchDesktopFromRecents: " + Arrays.toString(
+ getTaskIds()));
+ } else {
+ Log.d(TAG, "launchTaskAnimated - recentsController is null: " + Arrays.toString(
+ getTaskIds()));
}
// Callbacks get run from recentsView for case when recents animation already running
diff --git a/quickstep/src/com/android/quickstep/views/FloatingAppPairBackground.kt b/quickstep/src/com/android/quickstep/views/FloatingAppPairBackground.kt
index 0d49309..e024995 100644
--- a/quickstep/src/com/android/quickstep/views/FloatingAppPairBackground.kt
+++ b/quickstep/src/com/android/quickstep/views/FloatingAppPairBackground.kt
@@ -36,17 +36,18 @@
* animation. Consists of a rectangular background that splits into two, and two app icons that
* increase in size during the animation.
*/
-class FloatingAppPairBackground(
- context: Context,
- private val floatingView: FloatingAppPairView, // the view that we will draw this background on
- private val appIcon1: Drawable,
- private val appIcon2: Drawable,
- dividerPos: Int
+open class FloatingAppPairBackground(
+ context: Context,
+ // the view that we will draw this background on
+ protected val floatingView: FloatingAppPairView,
+ private val appIcon1: Drawable,
+ private val appIcon2: Drawable?,
+ dividerPos: Int
) : Drawable() {
companion object {
// Design specs -- app icons start small and expand during the animation
- private val STARTING_ICON_SIZE_PX = Utilities.dpToPx(22f)
- private val ENDING_ICON_SIZE_PX = Utilities.dpToPx(66f)
+ internal val STARTING_ICON_SIZE_PX = Utilities.dpToPx(22f)
+ internal val ENDING_ICON_SIZE_PX = Utilities.dpToPx(66f)
// Null values to use with drawDoubleRoundRect(), since there doesn't seem to be any other
// API for drawing rectangles with 4 different corner radii.
@@ -58,13 +59,13 @@
private val backgroundPaint = Paint(Paint.ANTI_ALIAS_FLAG)
// Animation interpolators
- private val expandXInterpolator: Interpolator
- private val expandYInterpolator: Interpolator
+ protected val expandXInterpolator: Interpolator
+ protected val expandYInterpolator: Interpolator
private val cellSplitInterpolator: Interpolator
- private val iconFadeInterpolator: Interpolator
+ protected val iconFadeInterpolator: Interpolator
// Device-specific measurements
- private val deviceCornerRadius: Float
+ protected val deviceCornerRadius: Float
private val deviceHalfDividerSize: Float
private val desiredSplitRatio: Float
@@ -214,7 +215,7 @@
canvas.save()
canvas.translate(changingIcon2Left, changingIconTop)
canvas.scale(changingIconScaleX, changingIconScaleY)
- appIcon2.alpha = changingIconAlpha
+ appIcon2!!.alpha = changingIconAlpha
appIcon2.draw(canvas)
canvas.restore()
}
@@ -312,7 +313,7 @@
canvas.save()
canvas.translate(changingIconLeft, changingIcon2Top)
canvas.scale(changingIconScaleX, changingIconScaleY)
- appIcon2.alpha = changingIconAlpha
+ appIcon2!!.alpha = changingIconAlpha
appIcon2.draw(canvas)
canvas.restore()
}
@@ -325,7 +326,7 @@
* @param radii An array of 8 radii for the corners: top left x, top left y, top right x, top
* right y, bottom right x, and so on.
*/
- private fun drawCustomRoundedRect(c: Canvas, rect: RectF, radii: FloatArray) {
+ protected fun drawCustomRoundedRect(c: Canvas, rect: RectF, radii: FloatArray) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
// Canvas.drawDoubleRoundRect is supported from Q onward
c.drawDoubleRoundRect(rect, radii, EMPTY_RECT, ARRAY_OF_ZEROES, backgroundPaint)
diff --git a/quickstep/src/com/android/quickstep/views/FloatingAppPairView.kt b/quickstep/src/com/android/quickstep/views/FloatingAppPairView.kt
index e90aa13..e8d1cc1 100644
--- a/quickstep/src/com/android/quickstep/views/FloatingAppPairView.kt
+++ b/quickstep/src/com/android/quickstep/views/FloatingAppPairView.kt
@@ -40,8 +40,8 @@
fun getFloatingAppPairView(
launcher: StatefulActivity<*>,
originalView: View,
- appIcon1: Drawable,
- appIcon2: Drawable,
+ appIcon1: Drawable?,
+ appIcon2: Drawable?,
dividerPos: Int
): FloatingAppPairView {
val dragLayer: ViewGroup = launcher.getDragLayer()
@@ -64,8 +64,8 @@
fun init(
launcher: StatefulActivity<*>,
originalView: View,
- appIcon1: Drawable,
- appIcon2: Drawable,
+ appIcon1: Drawable?,
+ appIcon2: Drawable?,
dividerPos: Int
) {
val viewBounds = Rect(0, 0, originalView.width, originalView.height)
@@ -92,7 +92,14 @@
layoutParams = lp
// Prepare to draw app pair icon background
- background = FloatingAppPairBackground(context, this, appIcon1, appIcon2, dividerPos)
+ background = if (appIcon1 == null || appIcon2 == null) {
+ val iconToAnimate = appIcon1 ?: appIcon2
+ checkNotNull(iconToAnimate)
+ FloatingFullscreenAppPairBackground(context, this, iconToAnimate,
+ dividerPos)
+ } else {
+ FloatingAppPairBackground(context, this, appIcon1, appIcon2, dividerPos)
+ }
background.setBounds(0, 0, lp.width, lp.height)
}
diff --git a/quickstep/src/com/android/quickstep/views/FloatingFullscreenAppPairBackground.kt b/quickstep/src/com/android/quickstep/views/FloatingFullscreenAppPairBackground.kt
new file mode 100644
index 0000000..8cd997f
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/views/FloatingFullscreenAppPairBackground.kt
@@ -0,0 +1,95 @@
+/*
+ * 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
+
+import android.content.Context
+import android.graphics.Canvas
+import android.graphics.RectF
+import android.graphics.drawable.Drawable
+
+class FloatingFullscreenAppPairBackground(
+ context: Context,
+ floatingView: FloatingAppPairView,
+ private val iconToLaunch: Drawable,
+ dividerPos: Int) :
+ FloatingAppPairBackground(
+ context,
+ floatingView,
+ iconToLaunch,
+ null /*appIcon2*/,
+ dividerPos
+) {
+
+ /** Animates the background as if launching a fullscreen task. */
+ override fun draw(canvas: Canvas) {
+ val progress = floatingView.progress
+
+ // Since the entire floating app pair surface is scaling up during this animation, we
+ // scale down most of these drawn elements so that they appear the proper size on-screen.
+ val scaleFactorX = floatingView.scaleX
+ val scaleFactorY = floatingView.scaleY
+
+ // Get the bounds where we will draw the background image
+ val width = bounds.width().toFloat()
+ val height = bounds.height().toFloat()
+
+ // Get device-specific measurements
+ val cornerRadiusX = deviceCornerRadius / scaleFactorX
+ val cornerRadiusY = deviceCornerRadius / scaleFactorY
+
+ // Draw background
+ drawCustomRoundedRect(
+ canvas,
+ RectF(0f, 0f, width, height),
+ floatArrayOf(
+ cornerRadiusX,
+ cornerRadiusY,
+ cornerRadiusX,
+ cornerRadiusY,
+ cornerRadiusX,
+ cornerRadiusY,
+ cornerRadiusX,
+ cornerRadiusY,
+ )
+ )
+
+ // Calculate changing measurements for icon.
+ val changingIconSizeX =
+ (STARTING_ICON_SIZE_PX +
+ ((ENDING_ICON_SIZE_PX - STARTING_ICON_SIZE_PX) *
+ expandXInterpolator.getInterpolation(progress))) / scaleFactorX
+ val changingIconSizeY =
+ (STARTING_ICON_SIZE_PX +
+ ((ENDING_ICON_SIZE_PX - STARTING_ICON_SIZE_PX) *
+ expandYInterpolator.getInterpolation(progress))) / scaleFactorY
+
+ val changingIcon1Left = (width / 2f) - (changingIconSizeX / 2f)
+ val changingIconTop = (height / 2f) - (changingIconSizeY / 2f)
+ val changingIconScaleX = changingIconSizeX / iconToLaunch.bounds.width()
+ val changingIconScaleY = changingIconSizeY / iconToLaunch.bounds.height()
+ val changingIconAlpha =
+ (255 - (255 * iconFadeInterpolator.getInterpolation(progress))).toInt()
+
+ // Draw icon
+ canvas.save()
+ canvas.translate(changingIcon1Left, changingIconTop)
+ canvas.scale(changingIconScaleX, changingIconScaleY)
+ iconToLaunch.alpha = changingIconAlpha
+ iconToLaunch.draw(canvas)
+ canvas.restore()
+ }
+}
\ No newline at end of file
diff --git a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
index a593712..b2a8503 100644
--- a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
+++ b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
@@ -6,6 +6,7 @@
import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
import static com.android.quickstep.util.SplitScreenUtils.convertLauncherSplitBoundsToShell;
+import android.app.ActivityTaskManager;
import android.content.Context;
import android.graphics.Point;
import android.graphics.PointF;
@@ -31,6 +32,7 @@
import com.android.launcher3.util.SplitConfigurationOptions;
import com.android.launcher3.util.SplitConfigurationOptions.SplitBounds;
import com.android.launcher3.util.TransformingTouchDelegate;
+import com.android.launcher3.views.BaseDragLayer;
import com.android.quickstep.RecentsModel;
import com.android.quickstep.TaskIconCache;
import com.android.quickstep.TaskThumbnailCache;
@@ -46,6 +48,7 @@
import java.util.Arrays;
import java.util.HashMap;
+import java.util.Optional;
import java.util.function.Consumer;
/**
@@ -60,7 +63,7 @@
*/
public class GroupedTaskView extends TaskView {
- private static final String TAG = TaskView.class.getSimpleName();
+ private static final String TAG = GroupedTaskView.class.getSimpleName();
@Nullable
private Task mSecondaryTask;
// TODO(b/336612373): Support new TTV for GroupedTaskView
@@ -90,27 +93,37 @@
}
@Override
- protected Unit updateBorderBounds(@NonNull Rect bounds) {
+ public Unit getThumbnailBounds(@NonNull Rect bounds, boolean relativeToDragLayer) {
if (mSplitBoundsConfig == null) {
- super.updateBorderBounds(bounds);
+ super.getThumbnailBounds(bounds, relativeToDragLayer);
return Unit.INSTANCE;
}
- bounds.set(
- Math.min(mTaskThumbnailViewDeprecated.getLeft() + Math.round(
- mTaskThumbnailViewDeprecated.getTranslationX()),
- mSnapshotView2.getLeft() + Math.round(mSnapshotView2.getTranslationX())),
- Math.min(mTaskThumbnailViewDeprecated.getTop() + Math.round(
- mTaskThumbnailViewDeprecated.getTranslationY()),
- mSnapshotView2.getTop() + Math.round(mSnapshotView2.getTranslationY())),
- Math.max(mTaskThumbnailViewDeprecated.getRight() + Math.round(
- mTaskThumbnailViewDeprecated.getTranslationX()),
- mSnapshotView2.getRight() + Math.round(mSnapshotView2.getTranslationX())),
- Math.max(mTaskThumbnailViewDeprecated.getBottom() + Math.round(
- mTaskThumbnailViewDeprecated.getTranslationY()),
- mSnapshotView2.getBottom() + Math.round(mSnapshotView2.getTranslationY())));
+ if (relativeToDragLayer) {
+ Rect firstThumbnailBounds = new Rect();
+ Rect secondThumbnailBounds = new Rect();
+ BaseDragLayer dragLayer = mContainer.getDragLayer();
+ dragLayer.getDescendantRectRelativeToSelf(
+ mTaskThumbnailViewDeprecated, firstThumbnailBounds);
+ dragLayer.getDescendantRectRelativeToSelf(mSnapshotView2, secondThumbnailBounds);
+
+ bounds.set(firstThumbnailBounds);
+ bounds.union(secondThumbnailBounds);
+ } else {
+ bounds.set(getSnapshotViewBounds(mTaskThumbnailViewDeprecated));
+ bounds.union(getSnapshotViewBounds(mSnapshotView2));
+ }
return Unit.INSTANCE;
}
+ private Rect getSnapshotViewBounds(@NonNull View snapshotView) {
+ int snapshotViewX = Math.round(snapshotView.getX());
+ int snapshotViewY = Math.round(snapshotView.getY());
+ return new Rect(snapshotViewX,
+ snapshotViewY,
+ snapshotViewX + snapshotView.getWidth(),
+ snapshotViewY + snapshotView.getHeight());
+ }
+
@Override
protected void onFinishInflate() {
super.onFinishInflate();
@@ -129,9 +142,11 @@
@Nullable SplitBounds splitBoundsConfig) {
super.bind(primary, orientedState);
mSecondaryTask = secondary;
- mTaskIdContainer[1] = secondary.key.id;
- mTaskIdAttributeContainer[1] = new TaskIdAttributeContainer(secondary, mSnapshotView2,
- mIconView2, STAGE_POSITION_BOTTOM_OR_RIGHT);
+ mTaskIdContainer = new int[]{mTaskIdContainer[0], secondary.key.id};
+ mTaskIdAttributeContainer = new TaskIdAttributeContainer[]{
+ mTaskIdAttributeContainer[0],
+ new TaskIdAttributeContainer(secondary, mSnapshotView2,
+ mIconView2, STAGE_POSITION_BOTTOM_OR_RIGHT)};
mTaskIdAttributeContainer[0].setStagePosition(
SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT);
mSnapshotView2.bind(secondary);
@@ -154,6 +169,9 @@
public void setUpShowAllInstancesListener() {
// sets up the listener for the left/top task
super.setUpShowAllInstancesListener();
+ if (mTaskIdAttributeContainer.length < 2) {
+ return;
+ }
// right/bottom task's base package name
String taskPackageName = mTaskIdAttributeContainer[1].getTask().key.getPackageName();
@@ -308,16 +326,20 @@
}
@Override
- public boolean containsTaskId(int taskId) {
- return (mTask != null && mTask.key.id == taskId)
- || (mSecondaryTask != null && mSecondaryTask.key.id == taskId);
- }
-
- @Override
public TaskThumbnailViewDeprecated[] getThumbnails() {
return new TaskThumbnailViewDeprecated[]{mTaskThumbnailViewDeprecated, mSnapshotView2};
}
+ /**
+ * Returns taskId that split selection was initiated with,
+ * {@link ActivityTaskManager#INVALID_TASK_ID} if no tasks in this TaskView are part of
+ * split selection
+ */
+ protected int getThisTaskCurrentlyInSplitSelection() {
+ int initialTaskId = getRecentsView().getSplitSelectController().getInitialTaskId();
+ return containsTaskId(initialTaskId) ? initialTaskId : INVALID_TASK_ID;
+ }
+
@Override
protected int getLastSelectedChildTaskIndex() {
SplitSelectStateController splitSelectController =
@@ -382,13 +404,15 @@
} else {
// Currently being split with this taskView, let the non-split selected thumbnail
// take up full thumbnail area
- TaskIdAttributeContainer container =
- mTaskIdAttributeContainer[initSplitTaskId == mTask.key.id ? 1 : 0];
- container.getThumbnailView().measure(widthMeasureSpec,
- View.MeasureSpec.makeMeasureSpec(
- heightSize -
- mContainer.getDeviceProfile().overviewTaskThumbnailTopMarginPx,
- MeasureSpec.EXACTLY));
+ Optional<TaskIdAttributeContainer> nonSplitContainer = Arrays.stream(
+ mTaskIdAttributeContainer).filter(
+ container -> container.getTask().key.id != initSplitTaskId).findAny();
+ nonSplitContainer.ifPresent(
+ taskIdAttributeContainer -> taskIdAttributeContainer.getThumbnailView().measure(
+ widthMeasureSpec, MeasureSpec.makeMeasureSpec(
+ heightSize - mContainer.getDeviceProfile()
+ .overviewTaskThumbnailTopMarginPx,
+ MeasureSpec.EXACTLY)));
}
if (!enableOverviewIconMenu()) {
updateIconPlacement();
@@ -529,7 +553,7 @@
mDigitalWellBeingToast.setBannerVisibility(visibility);
mSnapshotView2.setVisibility(visibility);
mDigitalWellBeingToast2.setBannerVisibility(visibility);
- } else if (taskId == getTaskIds()[0]) {
+ } else if (mTaskIdContainer.length > 0 && mTaskIdContainer[0] == taskId) {
mTaskThumbnailViewDeprecated.setVisibility(visibility);
mDigitalWellBeingToast.setBannerVisibility(visibility);
} else {
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 5daafcf..077cd1b 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -626,7 +626,6 @@
*/
protected int mRunningTaskViewId = -1;
private int mTaskViewIdCount;
- private final int[] INVALID_TASK_IDS = new int[]{-1, -1};
protected boolean mRunningTaskTileHidden;
@Nullable
private Task[] mTmpRunningTasks;
@@ -771,6 +770,8 @@
// keeps track of the state of the filter for tasks in recents view
private final RecentsFilterState mFilterState = new RecentsFilterState();
+ private int mOffsetMidpointIndexOverride = INVALID_PAGE;
+
public RecentsView(Context context, @Nullable AttributeSet attrs, int defStyleAttr,
BaseContainerInterface sizeStrategy) {
super(context, attrs, defStyleAttr);
@@ -1413,7 +1414,7 @@
*/
@Nullable
public TaskView getTaskViewByTaskIds(int[] taskIds) {
- if (!hasAnyValidTaskIds(taskIds)) {
+ if (!hasAllValidTaskIds(taskIds)) {
return null;
}
@@ -1432,9 +1433,11 @@
return null;
}
- /** Returns false if {@code taskIds} is null or contains invalid values, true otherwise */
- private boolean hasAnyValidTaskIds(int[] taskIds) {
- return taskIds != null && !Arrays.equals(taskIds, INVALID_TASK_IDS);
+ /** Returns false if {@code taskIds} is null or contains any invalid values, true otherwise */
+ private boolean hasAllValidTaskIds(int[] taskIds) {
+ return taskIds != null
+ && taskIds.length > 0
+ && Arrays.stream(taskIds).noneMatch(taskId -> taskId == INVALID_TASK_ID);
}
public void setOverviewStateEnabled(boolean enabled) {
@@ -1707,6 +1710,12 @@
return;
}
+ if (taskGroups == null) {
+ Log.d(TAG, "applyLoadPlan - taskGroups is null");
+ } else {
+ Log.d(TAG, "applyLoadPlan - taskGroups: " + taskGroups.stream().map(
+ GroupTask::toString).toList());
+ }
mLoadPlanEverApplied = true;
if (taskGroups == null || taskGroups.isEmpty()) {
removeTasksViewsAndClearAllButton();
@@ -1720,10 +1729,12 @@
return;
}
- int[] currentTaskId = INVALID_TASK_IDS;
+ int[] currentTaskIds;
TaskView currentTaskView = getTaskViewAt(mCurrentPage);
if (currentTaskView != null && currentTaskView.getTask() != null) {
- currentTaskId = currentTaskView.getTaskIds();
+ currentTaskIds = currentTaskView.getTaskIds();
+ } else {
+ currentTaskIds = new int[0];
}
// Unload existing visible task data
@@ -1735,8 +1746,8 @@
// Save running task ID if it exists before rebinding all taskViews, otherwise the task from
// the runningTaskView currently bound could get assigned to another TaskView
- int[] runningTaskId = getTaskIdsForTaskViewId(mRunningTaskViewId);
- int[] focusedTaskId = getTaskIdsForTaskViewId(mFocusedTaskViewId);
+ int[] runningTaskIds = getTaskIdsForTaskViewId(mRunningTaskViewId);
+ int[] focusedTaskIds = getTaskIdsForTaskViewId(mFocusedTaskViewId);
// Reset the focused task to avoiding initializing TaskViews layout as focused task during
// binding. The focused task view will be updated after all the TaskViews are bound.
@@ -1819,7 +1830,7 @@
}
// Keep same previous focused task
- TaskView newFocusedTaskView = getTaskViewByTaskIds(focusedTaskId);
+ TaskView newFocusedTaskView = getTaskViewByTaskIds(focusedTaskIds);
// If the list changed, maybe the focused task doesn't exist anymore
if (newFocusedTaskView == null && getTaskViewCount() > 0) {
newFocusedTaskView = getTaskViewAt(0);
@@ -1830,10 +1841,10 @@
updateChildTaskOrientations();
TaskView newRunningTaskView = null;
- if (hasAnyValidTaskIds(runningTaskId)) {
+ if (hasAllValidTaskIds(runningTaskIds)) {
// Update mRunningTaskViewId to be the new TaskView that was assigned by binding
// the full list of tasks to taskViews
- newRunningTaskView = getTaskViewByTaskIds(runningTaskId);
+ newRunningTaskView = getTaskViewByTaskIds(runningTaskIds);
if (newRunningTaskView != null) {
setRunningTaskViewId(newRunningTaskView.getTaskViewId());
} else {
@@ -1853,8 +1864,8 @@
if (mNextPage != INVALID_PAGE) {
// Restore mCurrentPage but don't call setCurrentPage() as that clobbers the scroll.
mCurrentPage = previousCurrentPage;
- if (hasAnyValidTaskIds(currentTaskId)) {
- currentTaskView = getTaskViewByTaskIds(currentTaskId);
+ if (hasAllValidTaskIds(currentTaskIds)) {
+ currentTaskView = getTaskViewByTaskIds(currentTaskIds);
if (currentTaskView != null) {
targetPage = indexOfChild(currentTaskView);
}
@@ -1863,7 +1874,7 @@
targetPage = previousFocusedPage;
} else {
// Set the current page to the running task, but not if settling on new task.
- if (hasAnyValidTaskIds(runningTaskId)) {
+ if (hasAllValidTaskIds(runningTaskIds)) {
targetPage = indexOfChild(newRunningTaskView);
} else if (getTaskViewCount() > 0) {
targetPage = indexOfChild(requireTaskViewAt(0));
@@ -1963,7 +1974,8 @@
public void resetTaskVisuals() {
for (int i = getTaskViewCount() - 1; i >= 0; i--) {
TaskView taskView = requireTaskViewAt(i);
- if (mIgnoreResetTaskId != taskView.getTaskIds()[0]) {
+ if (Arrays.stream(taskView.getTaskIds()).noneMatch(
+ taskId -> taskId == mIgnoreResetTaskId)) {
taskView.resetViewTransforms();
taskView.setIconScaleAndDim(mTaskIconScaledDown ? 0 : 1);
taskView.setStableAlpha(mContentAlpha);
@@ -2343,7 +2355,7 @@
for (int i = 0; i < getTaskViewCount(); i++) {
TaskView taskView = requireTaskViewAt(i);
TaskIdAttributeContainer[] containers = taskView.getTaskIdAttributeContainers();
- if (containers[0] == null && containers[1] == null) {
+ if (containers.length == 0) {
continue;
}
int index = indexOfChild(taskView);
@@ -2498,7 +2510,7 @@
// For now 2 distinct task IDs is max for split screen
TaskView runningTaskView = getTaskViewFromTaskViewId(taskViewId);
if (runningTaskView == null) {
- return INVALID_TASK_IDS;
+ return new int[0];
}
return runningTaskView.getTaskIds();
@@ -2587,7 +2599,7 @@
*/
public void onGestureAnimationStart(
Task[] runningTasks, RotationTouchHelper rotationTouchHelper) {
- Log.d(TAG, "onGestureAnimationStart");
+ Log.d(TAG, "onGestureAnimationStart - runningTasks: " + Arrays.toString(runningTasks));
mActiveGestureRunningTasks = runningTasks;
// This needs to be called before the other states are set since it can create the task view
if (mOrientationState.setGestureActive(true)) {
@@ -2736,22 +2748,19 @@
* Returns true if we should add a stub taskView for the running task id
*/
protected boolean shouldAddStubTaskView(Task[] runningTasks) {
- TaskView taskView = getTaskViewByTaskId(runningTasks[0].key.id);
- if (taskView == null) {
- // No TaskView found, add a stub task.
- return true;
- }
-
- if (runningTasks.length > 1) {
- // Ensure all taskIds matches the TaskView, otherwise add a stub task.
- return Arrays.stream(runningTasks).anyMatch(
- runningTask -> !taskView.containsTaskId(runningTask.key.id));
+ int[] runningTaskIds = Arrays.stream(runningTasks).mapToInt(task -> task.key.id).toArray();
+ TaskView matchingTaskView = null;
+ if (hasDesktopTask(runningTasks) && runningTaskIds.length == 1) {
+ // TODO(b/249371338): Unsure if it's expected, desktop runningTasks only have a single
+ // taskId, therefore we match any DesktopTaskView that contains the runningTaskId.
+ TaskView taskview = getTaskViewByTaskId(runningTaskIds[0]);
+ if (taskview instanceof DesktopTaskView) {
+ matchingTaskView = taskview;
+ }
} else {
- // Ensure the TaskView only contains a single taskId, or is a DesktopTask,
- // otherwise add a stub task.
- // TODO(b/249371338): Figure out why DesktopTask only have a single runningTask.
- return taskView.containsMultipleTasks() && !taskView.isDesktopTask();
+ matchingTaskView = getTaskViewByTaskIds(runningTaskIds);
}
+ return matchingTaskView == null;
}
/**
@@ -2761,6 +2770,7 @@
* is called. Also scrolls the view to this task.
*/
private void showCurrentTask(Task[] runningTasks) {
+ Log.d(TAG, "showCurrentTask - runningTasks: " + Arrays.toString(runningTasks));
if (runningTasks.length == 0) {
return;
}
@@ -4273,13 +4283,10 @@
alpha = Utilities.boundToRange(alpha, 0, 1);
mContentAlpha = alpha;
- int runningTaskId = getTaskIdsForRunningTaskView()[0];
+ TaskView runningTaskView = getRunningTaskView();
for (int i = getTaskViewCount() - 1; i >= 0; i--) {
TaskView child = requireTaskViewAt(i);
- int[] childTaskIds = child.getTaskIds();
- if (runningTaskId != INVALID_TASK_ID
- && mRunningTaskTileHidden
- && (childTaskIds[0] == runningTaskId || childTaskIds[1] == runningTaskId)) {
+ if (runningTaskView != null && mRunningTaskTileHidden && child == runningTaskView) {
continue;
}
child.setStableAlpha(alpha);
@@ -4460,15 +4467,26 @@
setPivotY(mTempPointF.y);
}
+ /**
+ * Sets whether we should force-override the page offset mid-point to the current task, rather
+ * than the running task, when updating page offsets.
+ */
+ public void setOffsetMidpointIndexOverride(int offsetMidpointIndexOverride) {
+ mOffsetMidpointIndexOverride = offsetMidpointIndexOverride;
+ updatePageOffsets();
+ }
+
private void updatePageOffsets() {
float offset = mAdjacentPageHorizontalOffset;
float modalOffset = ACCELERATE_0_75.getInterpolation(mTaskModalness);
int count = getChildCount();
boolean showAsGrid = showAsGrid();
- TaskView runningTask = mRunningTaskViewId == -1 || !mRunningTaskTileHidden
+ TaskView runningTask = mRunningTaskViewId == INVALID_PAGE || !mRunningTaskTileHidden
? null : getRunningTaskView();
- int midpoint = runningTask == null ? -1 : indexOfChild(runningTask);
+ int midpoint = mOffsetMidpointIndexOverride == INVALID_PAGE
+ ? (runningTask == null ? INVALID_PAGE : indexOfChild(runningTask))
+ : mOffsetMidpointIndexOverride;
int modalMidpoint = getCurrentPage();
boolean isModalGridWithoutFocusedTask =
showAsGrid && enableGridOnlyOverview() && mTaskModalness > 0;
@@ -4753,7 +4771,7 @@
// Prevent dismissing whole task if we're only initiating from one of 2 tasks in split pair
mSplitSelectStateController.setDismissingFromSplitPair(mSplitHiddenTaskView != null
- && mSplitHiddenTaskView.containsMultipleTasks());
+ && mSplitHiddenTaskView instanceof GroupedTaskView);
mSplitSelectStateController.setInitialTaskSelect(splitSelectSource.intent,
splitSelectSource.position.stagePosition, splitSelectSource.itemInfo,
splitSelectSource.splitEvent, splitSelectSource.alreadyRunningTaskId);
@@ -4774,14 +4792,14 @@
mSplitSelectStateController.isAnimateCurrentTaskDismissal();
boolean isInitiatingTaskViewSplitPair =
mSplitSelectStateController.isDismissingFromSplitPair();
- if (isInitiatingSplitFromTaskView && isInitiatingTaskViewSplitPair) {
+ if (isInitiatingSplitFromTaskView && isInitiatingTaskViewSplitPair
+ && mSplitHiddenTaskView instanceof GroupedTaskView) {
// Splitting from Overview for split pair task
createInitialSplitSelectAnimation(builder);
// Animate pair thumbnail into full thumbnail
- boolean primaryTaskSelected =
- mSplitHiddenTaskView.getTaskIdAttributeContainers()[0].getTask().key.id ==
- mSplitSelectStateController.getInitialTaskId();
+ boolean primaryTaskSelected = mSplitHiddenTaskView.getTaskIds()[0]
+ == mSplitSelectStateController.getInitialTaskId();
TaskIdAttributeContainer taskIdAttributeContainer = mSplitHiddenTaskView
.getTaskIdAttributeContainers()[primaryTaskSelected ? 1 : 0];
TaskThumbnailViewDeprecated thumbnail = taskIdAttributeContainer.getThumbnailView();
@@ -5232,7 +5250,8 @@
mPendingAnimation.addOnFrameCallback(this::redrawLiveTile);
mPendingAnimation.addEndListener(isSuccess -> {
if (isSuccess) {
- if (tv.getTaskIds()[1] != -1 && mRemoteTargetHandles != null) {
+ if (tv instanceof GroupedTaskView && hasAllValidTaskIds(tv.getTaskIds())
+ && mRemoteTargetHandles != null) {
// TODO(b/194414938): make this part of the animations instead.
TaskViewUtils.createSplitAuxiliarySurfacesAnimator(
mRemoteTargetHandles[0].getTransformParams().getTargetSet().nonApps,
@@ -5457,8 +5476,9 @@
* Called when a running recents animation has finished or canceled.
*/
public void onRecentsAnimationComplete() {
- Log.d(TAG, "onRecentsAnimationComplete - mRecentsAnimationController: "
- + mRecentsAnimationController);
+ Log.d(TAG, "onRecentsAnimationComplete "
+ + "- mRecentsAnimationController: " + mRecentsAnimationController
+ + ", mSideTaskLaunchCallback: " + mSideTaskLaunchCallback);
// At this point, the recents animation is not running and if the animation was canceled
// by a display rotation then reset this state to show the screenshot
setRunningTaskViewShowScreenshot(true);
@@ -5879,8 +5899,7 @@
}
taskView.setShowScreenshot(true);
- for (TaskIdAttributeContainer container :
- taskView.getTaskIdAttributeContainers()) {
+ for (TaskIdAttributeContainer container : taskView.getTaskIdAttributeContainers()) {
if (container == null) {
continue;
}
@@ -6012,7 +6031,8 @@
}
public void cleanupRemoteTargets() {
- Log.d(TAG, "cleanupRemoteTargets");
+ Log.d(TAG, "cleanupRemoteTargets - mRemoteTargetHandles: " + Arrays.toString(
+ mRemoteTargetHandles));
mRemoteTargetHandles = null;
}
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index 8fd99de..f789686 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -16,7 +16,6 @@
package com.android.quickstep.views;
-import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.widget.Toast.LENGTH_SHORT;
@@ -48,7 +47,6 @@
import android.animation.ObjectAnimator;
import android.annotation.IdRes;
import android.app.ActivityOptions;
-import android.app.ActivityTaskManager;
import android.content.Context;
import android.content.Intent;
import android.content.res.TypedArray;
@@ -127,6 +125,7 @@
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
+import java.util.Optional;
import java.util.function.Consumer;
import java.util.stream.Stream;
@@ -150,7 +149,8 @@
*/
@Retention(SOURCE)
@IntDef({FLAG_UPDATE_ALL, FLAG_UPDATE_ICON, FLAG_UPDATE_THUMBNAIL, FLAG_UPDATE_CORNER_RADIUS})
- public @interface TaskDataChanges {}
+ public @interface TaskDataChanges {
+ }
/**
* Type of task view
@@ -371,12 +371,9 @@
private float mStableAlpha = 1;
private int mTaskViewId = -1;
- /**
- * Index 0 will contain taskID of left/top task, index 1 will contain taskId of bottom/right
- */
- protected int[] mTaskIdContainer = new int[]{-1, -1};
+ protected int[] mTaskIdContainer = new int[0];
protected TaskIdAttributeContainer[] mTaskIdAttributeContainer =
- new TaskIdAttributeContainer[2];
+ new TaskIdAttributeContainer[0];
private boolean mShowScreenshot;
private boolean mBorderEnabled;
@@ -395,9 +392,11 @@
private boolean mIsClickableAsLiveTile = true;
- @Nullable private final BorderAnimator mFocusBorderAnimator;
+ @Nullable
+ private final BorderAnimator mFocusBorderAnimator;
- @Nullable private final BorderAnimator mHoverBorderAnimator;
+ @Nullable
+ private final BorderAnimator mHoverBorderAnimator;
public TaskView(Context context) {
this(context, null);
@@ -444,7 +443,7 @@
/* borderRadiusPx= */ (int) mCurrentFullscreenParams.mCornerRadius,
/* borderWidthPx= */ context.getResources().getDimensionPixelSize(
R.dimen.keyboard_quick_switch_border_width),
- /* boundsBuilder= */ this::updateBorderBounds,
+ /* boundsBuilder= */ this::getThumbnailBounds,
/* targetView= */ this,
/* borderColor= */ styledAttrs.getColor(
R.styleable.TaskView_focusBorderColor, DEFAULT_BORDER_COLOR))
@@ -459,7 +458,7 @@
/* borderRadiusPx= */ (int) mCurrentFullscreenParams.mCornerRadius,
/* borderWidthPx= */ context.getResources().getDimensionPixelSize(
R.dimen.task_hover_border_width),
- /* boundsBuilder= */ this::updateBorderBounds,
+ /* boundsBuilder= */ this::getThumbnailBounds,
/* targetView= */ this,
/* borderColor= */ styledAttrs.getColor(
R.styleable.TaskView_hoverBorderColor, DEFAULT_BORDER_COLOR))
@@ -468,12 +467,23 @@
styledAttrs.recycle();
}
- protected Unit updateBorderBounds(@NonNull Rect bounds) {
+ /** Returns the thumbnail's bounds relative to this view. */
+ public Unit getThumbnailBounds(@NonNull Rect bounds) {
+ return getThumbnailBounds(bounds, false);
+ }
+
+ /** Returns the thumbnail's bounds, optionally relative to the screen. */
+ public Unit getThumbnailBounds(@NonNull Rect bounds, boolean relativeToDragLayer) {
View snapshotView = getSnapshotView();
- bounds.set(snapshotView.getLeft() + Math.round(snapshotView.getTranslationX()),
- snapshotView.getTop() + Math.round(snapshotView.getTranslationY()),
- snapshotView.getRight() + Math.round(snapshotView.getTranslationX()),
- snapshotView.getBottom() + Math.round(snapshotView.getTranslationY()));
+
+ if (relativeToDragLayer) {
+ mContainer.getDragLayer().getDescendantRectRelativeToSelf(snapshotView, bounds);
+ } else {
+ bounds.set(snapshotView.getLeft() + Math.round(snapshotView.getTranslationX()),
+ snapshotView.getTop() + Math.round(snapshotView.getTranslationY()),
+ snapshotView.getRight() + Math.round(snapshotView.getTranslationX()),
+ snapshotView.getBottom() + Math.round(snapshotView.getTranslationY()));
+ }
return Unit.INSTANCE;
}
@@ -583,6 +593,10 @@
* Enable or disable showing border on hover and focus change
*/
public void setBorderEnabled(boolean enabled) {
+ if (mBorderEnabled == enabled) {
+ return;
+ }
+
mBorderEnabled = enabled;
// Set the animation correctly in case it misses the hover/focus event during state
// transition
@@ -674,10 +688,10 @@
public void bind(Task task, RecentsOrientedState orientedState) {
cancelPendingLoadTasks();
mTask = task;
- mTaskIdContainer[0] = mTask.key.id;
- mTaskIdAttributeContainer[0] = new TaskIdAttributeContainer(task,
- mTaskThumbnailViewDeprecated, mIconView,
- STAGE_POSITION_UNDEFINED);
+ mTaskIdContainer = new int[]{mTask.key.id};
+ mTaskIdAttributeContainer = new TaskIdAttributeContainer[]{
+ new TaskIdAttributeContainer(task, mTaskThumbnailViewDeprecated, mIconView,
+ STAGE_POSITION_UNDEFINED)};
if (enableRefactorTaskThumbnail()) {
bindTaskThumbnailView();
} else {
@@ -696,6 +710,9 @@
* Sets up an on-click listener and the visibility for show_windows icon on top of the task.
*/
public void setUpShowAllInstancesListener() {
+ if (mTaskIdAttributeContainer.length == 0) {
+ return;
+ }
String taskPackageName = mTaskIdAttributeContainer[0].mTask.key.getPackageName();
// icon of the top/left task
@@ -751,19 +768,18 @@
* Check if given {@code taskId} is tracked in this view
*/
public boolean containsTaskId(int taskId) {
- return mTask != null && mTask.key.id == taskId;
+ return Arrays.stream(mTaskIdContainer).anyMatch(myTaskId -> myTaskId == taskId);
}
/**
- * @return integer array of two elements to be size consistent with max number of tasks possible
- * index 0 will contain the taskId, index 1 will be -1 indicating a null taskID value
+ * Returns a copy of integer array containing taskIds of all tasks in the TaskView.
*/
public int[] getTaskIds() {
return Arrays.copyOf(mTaskIdContainer, mTaskIdContainer.length);
}
public boolean containsMultipleTasks() {
- return mTaskIdContainer[1] != -1;
+ return mTaskIdContainer.length > 1;
}
/**
@@ -833,25 +849,6 @@
return super.dispatchTouchEvent(ev);
}
- /**
- * @return taskId that split selection was initiated with,
- * {@link ActivityTaskManager#INVALID_TASK_ID} if no tasks in this TaskView are part of
- * split selection
- */
- protected int getThisTaskCurrentlyInSplitSelection() {
- SplitSelectStateController splitSelectController =
- getRecentsView().getSplitSelectController();
- int initSplitTaskId = INVALID_TASK_ID;
- for (TaskIdAttributeContainer container : getTaskIdAttributeContainers()) {
- int taskId = container.getTask().key.id;
- if (taskId == splitSelectController.getInitialTaskId()) {
- initSplitTaskId = taskId;
- break;
- }
- }
- return initSplitTaskId;
- }
-
private void onClick(View view) {
if (getTask() == null) {
Log.d("b/310064698", "onClick - task is null");
@@ -864,7 +861,8 @@
RunnableList callbackList = launchTasks();
Log.d("b/310064698", mTask + " - onClick - callbackList: " + callbackList);
if (callbackList != null) {
- callbackList.add(() -> Log.d("b/310064698", mTask + " - onClick - launchCompleted"));
+ callbackList.add(() -> Log.d("b/310064698", Arrays.toString(
+ getTaskIds()) + " - onClick - launchCompleted"));
}
mContainer.getStatsLogManager().logger().withItemInfo(getItemInfo())
.log(LAUNCHER_TASK_LAUNCH_TAP);
@@ -872,10 +870,13 @@
/**
* @return {@code true} if user is already in split select mode and this tap was to choose the
- * second app. {@code false} otherwise
+ * second app. {@code false} otherwise
*/
protected boolean confirmSecondSplitSelectApp() {
int index = getLastSelectedChildTaskIndex();
+ if (index >= mTaskIdAttributeContainer.length) {
+ return false;
+ }
TaskIdAttributeContainer container = mTaskIdAttributeContainer[index];
if (container != null) {
return getRecentsView().confirmSplitSelect(this, container.getTask(),
@@ -897,6 +898,7 @@
/**
* Starts the task associated with this view and animates the startup.
+ *
* @return CompletionStage to indicate the animation completion or null if the launch failed.
*/
@Nullable
@@ -904,7 +906,7 @@
if (mTask != null) {
TestLogging.recordEvent(
TestProtocol.SEQUENCE_MAIN, "startActivityFromRecentsAsync", mTask);
- ActivityOptionsWrapper opts = mContainer.getActivityLaunchOptions(this, null);
+ ActivityOptionsWrapper opts = mContainer.getActivityLaunchOptions(this, null);
opts.options.setLaunchDisplayId(
getDisplay() == null ? DEFAULT_DISPLAY : getDisplay().getDisplayId());
if (ActivityManagerWrapper.getInstance()
@@ -936,7 +938,7 @@
return null;
}
} else {
- Log.d(TAG, "launchTaskAnimated - mTask is null");
+ Log.d(TAG, "launchTaskAnimated - mTask is null" + Arrays.toString(getTaskIds()));
return null;
}
}
@@ -1006,9 +1008,12 @@
callback.accept(false);
});
}
+ Log.d(TAG,
+ "launchTask - startActivityFromRecents: " + Arrays.toString(getTaskIds()));
});
} else {
callback.accept(false);
+ Log.d(TAG, "launchTask - mTask is null" + Arrays.toString(getTaskIds()));
}
}
@@ -1092,6 +1097,7 @@
/**
* See {@link TaskDataChanges}
+ *
* @param visible If this task view will be visible to the user in overview or hidden
*/
public void onTaskListVisibilityChanged(boolean visible) {
@@ -1100,6 +1106,7 @@
/**
* See {@link TaskDataChanges}
+ *
* @param visible If this task view will be visible to the user in overview or hidden
*/
public void onTaskListVisibilityChanged(boolean visible, @TaskDataChanges int changes) {
@@ -1188,29 +1195,34 @@
}
protected boolean showTaskMenuWithContainer(TaskViewIcon iconView) {
- TaskIdAttributeContainer menuContainer =
- mTaskIdAttributeContainer[iconView == mIconView ? 0 : 1];
+ Optional<TaskIdAttributeContainer> menuContainer = Arrays.stream(
+ mTaskIdAttributeContainer).filter(
+ container -> container.getIconView() == iconView).findAny();
+ if (menuContainer.isEmpty()) {
+ return false;
+ }
DeviceProfile dp = mContainer.getDeviceProfile();
if (enableOverviewIconMenu() && iconView instanceof IconAppChipView) {
((IconAppChipView) iconView).revealAnim(/* isRevealing= */ true);
- return TaskMenuView.showForTask(menuContainer,
+ return TaskMenuView.showForTask(menuContainer.get(),
() -> ((IconAppChipView) iconView).revealAnim(/* isRevealing= */ false));
} else if (dp.isTablet) {
int alignedOptionIndex = 0;
- if (getRecentsView().isOnGridBottomRow(menuContainer.getTaskView()) && dp.isLandscape) {
+ if (getRecentsView().isOnGridBottomRow(menuContainer.get().getTaskView())
+ && dp.isLandscape) {
if (Flags.enableGridOnlyOverview()) {
// With no focused task, there is less available space below the tasks, so align
// the arrow to the third option in the menu.
alignedOptionIndex = 2;
- } else {
+ } else {
// Bottom row of landscape grid aligns arrow to second option to avoid clipping
alignedOptionIndex = 1;
}
}
- return TaskMenuViewWithArrow.Companion.showForTask(menuContainer,
+ return TaskMenuViewWithArrow.Companion.showForTask(menuContainer.get(),
alignedOptionIndex);
} else {
- return TaskMenuView.showForTask(menuContainer);
+ return TaskMenuView.showForTask(menuContainer.get());
}
}
@@ -1664,9 +1676,6 @@
final Context context = getContext();
for (TaskIdAttributeContainer taskContainer : mTaskIdAttributeContainer) {
- if (taskContainer == null) {
- continue;
- }
for (SystemShortcut s : TraceHelper.allowIpcs(
"TV.a11yInfo", () -> getEnabledShortcuts(this, taskContainer))) {
info.addAction(s.createAccessibilityAction(context));
@@ -1702,9 +1711,6 @@
}
for (TaskIdAttributeContainer taskContainer : mTaskIdAttributeContainer) {
- if (taskContainer == null) {
- continue;
- }
for (SystemShortcut s : getEnabledShortcuts(this,
taskContainer)) {
if (s.hasHandlerForAction(action)) {
@@ -1903,13 +1909,13 @@
private int getRootViewDisplayId() {
- Display display = getRootView().getDisplay();
+ Display display = getRootView().getDisplay();
return display != null ? display.getDisplayId() : DEFAULT_DISPLAY;
}
/**
- * Sets visibility for the thumbnail and associated elements (DWB banners and action chips).
- * IconView is unaffected.
+ * Sets visibility for the thumbnail and associated elements (DWB banners and action chips).
+ * IconView is unaffected.
*
* @param taskId is only used when setting visibility to a non-{@link View#VISIBLE} value
*/
@@ -1966,7 +1972,8 @@
}
@Override
- public void close() { }
+ public void close() {
+ }
}
public class TaskIdAttributeContainer {
diff --git a/quickstep/tests/src/com/android/launcher3/taskbar/bubbles/OWNERS b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/OWNERS
similarity index 100%
rename from quickstep/tests/src/com/android/launcher3/taskbar/bubbles/OWNERS
rename to quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/OWNERS
diff --git a/quickstep/tests/src/com/android/quickstep/TaplPrivateSpaceTest.java b/quickstep/tests/src/com/android/quickstep/TaplPrivateSpaceTest.java
index 7708233..efe773b 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplPrivateSpaceTest.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplPrivateSpaceTest.java
@@ -27,6 +27,7 @@
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
+import com.android.launcher3.tapl.HomeAllApps;
import com.android.launcher3.tapl.LauncherInstrumentation;
import com.android.launcher3.tapl.PrivateSpaceContainer;
import com.android.launcher3.util.TestUtil;
@@ -100,10 +101,18 @@
public void testPrivateSpaceContainerIsPresent() {
// Scroll to the bottom of All Apps
executeOnLauncher(launcher -> launcher.getAppsView().resetAndScrollToPrivateSpaceHeader());
+ // Freeze All Apps
+ HomeAllApps homeAllApps = mLauncher.getAllApps();
+ homeAllApps.freeze();
- // Verify Unlocked View elements are present.
- assertNotNull("Private Space Unlocked View not found, or is not correct",
- mLauncher.getAllApps().getPrivateSpaceUnlockedView());
+ try {
+ // Verify Unlocked View elements are present.
+ assertNotNull("Private Space Unlocked View not found, or is not correct",
+ homeAllApps.getPrivateSpaceUnlockedView());
+ } finally {
+ // UnFreeze
+ homeAllApps.unfreeze();
+ }
}
@Test
@@ -117,10 +126,18 @@
waitForLauncherUIUpdate();
// Scroll to the bottom of All Apps
executeOnLauncher(launcher -> launcher.getAppsView().resetAndScrollToPrivateSpaceHeader());
+ // Freeze All Apps
+ HomeAllApps homeAllApps = mLauncher.getAllApps();
+ homeAllApps.freeze();
- // Verify the Installed App is displayed in correct position.
- PrivateSpaceContainer psContainer = mLauncher.getAllApps().getPrivateSpaceUnlockedView();
- psContainer.verifyInstalledAppIsPresent(INSTALLED_APP_NAME);
+ try {
+ // Verify the Installed App is displayed in correct position.
+ PrivateSpaceContainer psContainer = homeAllApps.getPrivateSpaceUnlockedView();
+ psContainer.verifyInstalledAppIsPresent(INSTALLED_APP_NAME);
+ } finally {
+ // UnFreeze
+ homeAllApps.unfreeze();
+ }
}
@Test
@@ -134,8 +151,17 @@
waitForLauncherUIUpdate();
// Scroll to the bottom of All Apps
executeOnLauncher(launcher -> launcher.getAppsView().resetAndScrollToPrivateSpaceHeader());
- // Get the "uninstall" menu item.
- mLauncher.getAllApps().getAppIcon(INSTALLED_APP_NAME).openMenu().getMenuItem("Uninstall");
+ // Freeze All Apps
+ HomeAllApps homeAllApps = mLauncher.getAllApps();
+ homeAllApps.freeze();
+
+ try {
+ // Get the "uninstall" menu item.
+ homeAllApps.getAppIcon(INSTALLED_APP_NAME).openMenu().getMenuItem("Uninstall");
+ } finally {
+ // UnFreeze
+ homeAllApps.unfreeze();
+ }
}
private void waitForPrivateSpaceSetup() {
diff --git a/quickstep/tests/src/com/android/quickstep/TaskViewTest.java b/quickstep/tests/src/com/android/quickstep/TaskViewTest.java
index 8eec903..512557b 100644
--- a/quickstep/tests/src/com/android/quickstep/TaskViewTest.java
+++ b/quickstep/tests/src/com/android/quickstep/TaskViewTest.java
@@ -17,9 +17,11 @@
package com.android.quickstep;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -34,7 +36,6 @@
import androidx.test.filters.SmallTest;
-import com.android.launcher3.statemanager.StatefulActivity;
import com.android.launcher3.uioverrides.QuickstepLauncher;
import com.android.quickstep.util.BorderAnimator;
import com.android.quickstep.views.TaskView;
@@ -74,6 +75,7 @@
@Test
public void notShowBorderOnBorderDisabled() {
+ presetBorderStatus(/* enabled= */ true);
mTaskView.setBorderEnabled(/* enabled= */ false);
MotionEvent event = MotionEvent.obtain(0, 0, MotionEvent.ACTION_HOVER_ENTER, 0.0f, 0.0f, 0);
mTaskView.onHoverEvent(MotionEvent.obtain(event));
@@ -86,7 +88,7 @@
}
@Test
- public void showBorderOnBorderEnabled() {
+ public void showBorderOnHoverEvent() {
mTaskView.setBorderEnabled(/* enabled= */ true);
MotionEvent event = MotionEvent.obtain(0, 0, MotionEvent.ACTION_HOVER_ENTER, 0.0f, 0.0f, 0);
mTaskView.onHoverEvent(MotionEvent.obtain(event));
@@ -98,7 +100,18 @@
}
@Test
+ public void showBorderOnBorderEnabled() {
+ presetBorderStatus(/* enabled= */ false);
+ mTaskView.setBorderEnabled(/* enabled= */ true);
+ verify(mHoverAnimator, times(1)).setBorderVisibility(/* visible= */ true, /* animated= */
+ true);
+ verify(mFocusAnimator, times(1)).setBorderVisibility(/* visible= */ true, /* animated= */
+ true);
+ }
+
+ @Test
public void hideBorderOnBorderDisabled() {
+ presetBorderStatus(/* enabled= */ true);
mTaskView.setBorderEnabled(/* enabled= */ false);
verify(mHoverAnimator, times(1)).setBorderVisibility(/* visible= */ false, /* animated= */
true);
@@ -107,13 +120,35 @@
}
@Test
+ public void notTriggerAnimatorWhenEnableStatusUnchanged() {
+ presetBorderStatus(/* enabled= */ false);
+ // Border is disabled by default, no animator is triggered after it is disabled again
+ mTaskView.setBorderEnabled(/* enabled= */ false);
+ verify(mHoverAnimator, never()).setBorderVisibility(/* visible= */
+ anyBoolean(), /* animated= */ anyBoolean());
+ verify(mFocusAnimator, never()).setBorderVisibility(/* visible= */
+ anyBoolean(), /* animated= */ anyBoolean());
+ }
+
+ private void presetBorderStatus(boolean enabled) {
+ // Make the task view focused and hovered
+ MotionEvent event = MotionEvent.obtain(0, 0, MotionEvent.ACTION_HOVER_ENTER, 0.0f, 0.0f, 0);
+ mTaskView.onHoverEvent(MotionEvent.obtain(event));
+ mTaskView.requestFocus();
+ mTaskView.setBorderEnabled(/* enabled= */ enabled);
+ // Reset invocation count after presetting status
+ reset(mHoverAnimator);
+ reset(mFocusAnimator);
+ }
+
+ @Test
public void notShowBorderByDefault() {
MotionEvent event = MotionEvent.obtain(0, 0, MotionEvent.ACTION_HOVER_ENTER, 0.0f, 0.0f, 0);
mTaskView.onHoverEvent(MotionEvent.obtain(event));
- verify(mHoverAnimator, never()).setBorderVisibility(/* visible= */ false, /* animated= */
- true);
+ verify(mHoverAnimator, never()).setBorderVisibility(/* visible= */
+ anyBoolean(), /* animated= */ anyBoolean());
mTaskView.onFocusChanged(true, 0, new Rect());
- verify(mHoverAnimator, never()).setBorderVisibility(/* visible= */ false, /* animated= */
- true);
+ verify(mHoverAnimator, never()).setBorderVisibility(/* visible= */
+ anyBoolean(), /* animated= */ anyBoolean());
}
}
diff --git a/quickstep/tests/src/com/android/quickstep/taskbar/controllers/TaskbarPinningControllerTest.kt b/quickstep/tests/src/com/android/quickstep/taskbar/controllers/TaskbarPinningControllerTest.kt
index dbe4624..4d10f0f 100644
--- a/quickstep/tests/src/com/android/quickstep/taskbar/controllers/TaskbarPinningControllerTest.kt
+++ b/quickstep/tests/src/com/android/quickstep/taskbar/controllers/TaskbarPinningControllerTest.kt
@@ -22,6 +22,7 @@
import androidx.test.filters.SmallTest
import com.android.launcher3.LauncherPrefs
import com.android.launcher3.LauncherPrefs.Companion.TASKBAR_PINNING
+import com.android.launcher3.LauncherPrefs.Companion.TASKBAR_PINNING_IN_DESKTOP_MODE
import com.android.launcher3.logging.StatsLogManager
import com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_DIVIDER_MENU_CLOSE
import com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_DIVIDER_MENU_OPEN
@@ -53,7 +54,13 @@
class TaskbarPinningControllerTest : TaskbarBaseTestCase() {
private val taskbarDragLayer = mock<TaskbarDragLayer>()
private val taskbarSharedState = mock<TaskbarSharedState>()
- private val launcherPrefs = mock<LauncherPrefs> { on { get(TASKBAR_PINNING) } doReturn false }
+ private var isInDesktopMode = false
+ private val isInDesktopModeProvider = { isInDesktopMode }
+ private val launcherPrefs =
+ mock<LauncherPrefs> {
+ on { get(TASKBAR_PINNING) } doReturn false
+ on { get(TASKBAR_PINNING_IN_DESKTOP_MODE) } doReturn false
+ }
private val statsLogger = mock<StatsLogManager.StatsLogger>()
private val statsLogManager = mock<StatsLogManager> { on { logger() } doReturn statsLogger }
private lateinit var pinningController: TaskbarPinningController
@@ -64,7 +71,8 @@
whenever(taskbarActivityContext.launcherPrefs).thenReturn(launcherPrefs)
whenever(taskbarActivityContext.dragLayer).thenReturn(taskbarDragLayer)
whenever(taskbarActivityContext.statsLogManager).thenReturn(statsLogManager)
- pinningController = spy(TaskbarPinningController(taskbarActivityContext))
+ pinningController =
+ spy(TaskbarPinningController(taskbarActivityContext, isInDesktopModeProvider))
pinningController.init(taskbarControllers, taskbarSharedState)
}
@@ -95,7 +103,7 @@
}
@Test
- fun testOnCloseCallback_whenPreferenceChanged_shouldAnimateToPinnedTaskbar() {
+ fun testOnCloseCallback_whenLauncherPreferenceChanged_shouldAnimateToPinnedTaskbar() {
whenever(launcherPrefs.get(TASKBAR_PINNING)).thenReturn(false)
doNothing().whenever(pinningController).animateTaskbarPinning(any())
@@ -106,7 +114,7 @@
}
@Test
- fun testOnCloseCallback_whenPreferenceChanged_shouldAnimateToTransientTaskbar() {
+ fun testOnCloseCallback_whenLauncherPreferenceChanged_shouldAnimateToTransientTaskbar() {
whenever(launcherPrefs.get(TASKBAR_PINNING)).thenReturn(true)
doNothing().whenever(pinningController).animateTaskbarPinning(any())
@@ -199,4 +207,13 @@
assertThat(pinningController.isAnimatingTaskbarPinning).isFalse()
verify(launcherPrefs, times(1)).put(TASKBAR_PINNING, true)
}
+
+ @Test
+ fun testRecreateTaskbarAndUpdatePinningValue_whenAnimationEnds_shouldUpdateTaskbarPinningDesktopModePref() {
+ isInDesktopMode = true
+ pinningController.recreateTaskbarAndUpdatePinningValue()
+ verify(taskbarDragLayer, times(1)).setAnimatingTaskbarPinning(false)
+ assertThat(pinningController.isAnimatingTaskbarPinning).isFalse()
+ verify(launcherPrefs, times(1)).put(TASKBAR_PINNING_IN_DESKTOP_MODE, true)
+ }
}
diff --git a/quickstep/tests/src/com/android/quickstep/util/SplitAnimationControllerTest.kt b/quickstep/tests/src/com/android/quickstep/util/SplitAnimationControllerTest.kt
index 4ffb6bd..de98703 100644
--- a/quickstep/tests/src/com/android/quickstep/util/SplitAnimationControllerTest.kt
+++ b/quickstep/tests/src/com/android/quickstep/util/SplitAnimationControllerTest.kt
@@ -17,6 +17,8 @@
package com.android.quickstep.util
+import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
+import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW
import android.graphics.Bitmap
import android.graphics.drawable.Drawable
import android.view.ContextThemeWrapper
@@ -39,8 +41,10 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.eq
import org.mockito.kotlin.any
import org.mockito.kotlin.doNothing
+import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
import org.mockito.kotlin.spy
import org.mockito.kotlin.verify
@@ -273,6 +277,9 @@
doNothing()
.whenever(spySplitAnimationController)
.composeIconSplitLaunchAnimator(any(), any(), any(), any())
+ doReturn(-1)
+ .whenever(spySplitAnimationController)
+ .hasChangesForBothAppPairs(any(), any())
spySplitAnimationController.playSplitLaunchAnimation(
null /* launchingTaskView */,
@@ -294,13 +301,45 @@
}
@Test
- fun playsAppropriateSplitLaunchAnimation_playsIconLaunchFromTaskbarContextCorrectly() {
+ fun playsAppropriateSplitLaunchAnimation_playsIconFullscreenLaunchCorrectly() {
+ val spySplitAnimationController = spy(splitAnimationController)
+ whenever(mockAppPairIcon.context).thenReturn(mockContextThemeWrapper)
+ doNothing()
+ .whenever(spySplitAnimationController)
+ .composeFullscreenIconSplitLaunchAnimator(any(), any(), any(), any(), any())
+ doReturn(0)
+ .whenever(spySplitAnimationController)
+ .hasChangesForBothAppPairs(any(), any())
+
+ spySplitAnimationController.playSplitLaunchAnimation(
+ null /* launchingTaskView */,
+ mockAppPairIcon,
+ taskId,
+ taskId2,
+ null /* apps */,
+ null /* wallpapers */,
+ null /* nonApps */,
+ stateManager,
+ depthController,
+ transitionInfo,
+ transaction,
+ {} /* finishCallback */
+ )
+
+ verify(spySplitAnimationController)
+ .composeFullscreenIconSplitLaunchAnimator(any(), any(), any(), any(), eq(0))
+ }
+
+ @Test
+ fun playsAppropriateSplitLaunchAnimation_playsIconLaunchFromTaskbarCMultiWindow() {
val spySplitAnimationController = spy(splitAnimationController)
whenever(mockAppPairIcon.context).thenReturn(mockTaskbarActivityContext)
doNothing()
.whenever(spySplitAnimationController)
- .composeScaleUpLaunchAnimation(any(), any(), any())
-
+ .composeScaleUpLaunchAnimation(any(), any(), any(), any())
+ doReturn(-1)
+ .whenever(spySplitAnimationController)
+ .hasChangesForBothAppPairs(any(), any())
spySplitAnimationController.playSplitLaunchAnimation(
null /* launchingTaskView */,
mockAppPairIcon,
@@ -316,7 +355,37 @@
{} /* finishCallback */
)
- verify(spySplitAnimationController).composeScaleUpLaunchAnimation(any(), any(), any())
+ verify(spySplitAnimationController).composeScaleUpLaunchAnimation(any(), any(), any(),
+ eq(WINDOWING_MODE_MULTI_WINDOW))
+ }
+
+ @Test
+ fun playsAppropriateSplitLaunchAnimation_playsIconLaunchFromTaskbarFullscreen() {
+ val spySplitAnimationController = spy(splitAnimationController)
+ whenever(mockAppPairIcon.context).thenReturn(mockTaskbarActivityContext)
+ doNothing()
+ .whenever(spySplitAnimationController)
+ .composeScaleUpLaunchAnimation(any(), any(), any(), any())
+ doReturn(0)
+ .whenever(spySplitAnimationController)
+ .hasChangesForBothAppPairs(any(), any())
+ spySplitAnimationController.playSplitLaunchAnimation(
+ null /* launchingTaskView */,
+ mockAppPairIcon,
+ taskId,
+ taskId2,
+ null /* apps */,
+ null /* wallpapers */,
+ null /* nonApps */,
+ stateManager,
+ depthController,
+ transitionInfo,
+ transaction,
+ {} /* finishCallback */
+ )
+
+ verify(spySplitAnimationController).composeScaleUpLaunchAnimation(any(), any(), any(),
+ eq(WINDOWING_MODE_FULLSCREEN))
}
@Test
diff --git a/res/drawable/ic_plus.xml b/res/drawable/ic_plus.xml
index 3ab926a..d004f42 100644
--- a/res/drawable/ic_plus.xml
+++ b/res/drawable/ic_plus.xml
@@ -14,7 +14,7 @@
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="19dp"
+ android:width="@dimen/widget_cell_add_button_drawable_width"
android:height="18dp"
android:viewportWidth="19"
android:viewportHeight="18">
diff --git a/res/layout/widget_cell_content.xml b/res/layout/widget_cell_content.xml
index 8f786bf..462bb52 100644
--- a/res/layout/widget_cell_content.xml
+++ b/res/layout/widget_cell_content.xml
@@ -46,20 +46,22 @@
android:layout_gravity="center_vertical"
android:id="@+id/widget_text_container"
android:orientation="vertical">
- <!-- The name of the widget. -->
- <TextView
- android:id="@+id/widget_name"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:ellipsize="end"
- android:fadingEdge="horizontal"
- android:layout_gravity="center_horizontal"
- android:gravity="center_horizontal|center_vertical"
- android:singleLine="true"
- android:maxLines="1"
- android:textColor="?android:attr/textColorPrimary"
- android:drawablePadding="@dimen/widget_cell_app_icon_padding"
- android:textSize="@dimen/widget_cell_font_size" />
+ <!-- The name of the widget. -->
+ <TextView
+ android:id="@+id/widget_name"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:ellipsize="end"
+ android:fadingEdge="horizontal"
+ android:layout_gravity="center_horizontal"
+ android:gravity="center_horizontal|center_vertical"
+ android:singleLine="true"
+ android:maxLines="1"
+ android:textColor="?attr/widgetCellTitleColor"
+ android:textSize="@dimen/widget_cell_title_font_size"
+ android:textFontWeight="@integer/widget_cell_title_font_weight"
+ android:lineHeight="@dimen/widget_cell_title_line_height"
+ android:drawablePadding="@dimen/widget_cell_app_icon_padding" />
<!-- The original dimensions of the widget -->
<TextView
@@ -67,21 +69,23 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
- android:textColor="?android:attr/textColorSecondary"
- android:textSize="@dimen/widget_cell_font_size"
- android:alpha="0.7" />
+ android:textColor="?attr/widgetCellSubtitleColor"
+ android:textSize="@dimen/widget_cell_dims_font_size"
+ android:textFontWeight="@integer/widget_cell_dims_font_weight"
+ android:lineHeight="@dimen/widget_cell_dims_line_height" />
<TextView
android:id="@+id/widget_description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
- android:textSize="@dimen/widget_cell_font_size"
- android:textColor="?android:attr/textColorSecondary"
+ android:textColor="?attr/widgetCellSubtitleColor"
+ android:textSize="@dimen/widget_cell_description_font_size"
+ android:textFontWeight="@integer/widget_cell_description_font_weight"
+ android:lineHeight="@dimen/widget_cell_description_line_height"
android:maxLines="3"
android:ellipsize="end"
- android:fadingEdge="horizontal"
- android:alpha="0.7" />
+ android:fadingEdge="horizontal" />
</LinearLayout>
<Button
@@ -94,12 +98,15 @@
android:paddingEnd="@dimen/widget_cell_add_button_end_padding"
android:text="@string/widget_add_button_label"
android:textColor="?attr/widgetPickerAddButtonTextColor"
- android:textSize="@dimen/widget_cell_font_size"
+ android:textSize="@dimen/widget_cell_add_button_font_size"
+ android:fontWeight="@integer/widget_cell_add_button_font_weight"
+ android:lineHeight="@dimen/widget_cell_add_button_line_height"
android:gravity="center"
android:visibility="gone"
android:drawableStart="@drawable/ic_plus"
- android:drawablePadding="8dp"
+ android:drawablePadding="@dimen/widget_cell_add_button_drawable_padding"
android:drawableTint="?attr/widgetPickerAddButtonTextColor"
+ android:maxLines="1"
android:background="@drawable/widget_cell_add_button_background" />
</FrameLayout>
</merge>
diff --git a/res/layout/widgets_list_row_header.xml b/res/layout/widgets_list_row_header.xml
index 6d26ce3..98f9dac 100644
--- a/res/layout/widgets_list_row_header.xml
+++ b/res/layout/widgets_list_row_header.xml
@@ -48,8 +48,10 @@
android:layout_gravity="start|center_vertical"
android:ellipsize="end"
android:maxLines="1"
- android:textColor="?android:attr/textColorPrimary"
- android:textSize="16sp"
+ android:textColor="?attr/widgetPickerHeaderAppTitleColor"
+ android:textSize="@dimen/widget_picker_header_app_title_font_size"
+ android:textFontWeight="@integer/widget_picker_header_app_title_font_weight"
+ android:lineHeight="@dimen/widget_picker_header_app_title_line_height"
tools:text="App name" />
<TextView
@@ -58,8 +60,10 @@
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="1"
- android:textColor="?android:attr/textColorSecondary"
- android:alpha="0.7"
+ android:textColor="?attr/widgetPickerHeaderAppSubtitleColor"
+ android:textSize="@dimen/widget_picker_header_app_subtitle_font_size"
+ android:textFontWeight="@integer/widget_picker_header_app_subtitle_font_weight"
+ android:lineHeight="@dimen/widget_picker_header_app_subtitle_line_height"
tools:text="m widgets, n shortcuts" />
</LinearLayout>
diff --git a/res/layout/widgets_list_row_header_two_pane.xml b/res/layout/widgets_list_row_header_two_pane.xml
index bdb2aed..d4baf0a 100644
--- a/res/layout/widgets_list_row_header_two_pane.xml
+++ b/res/layout/widgets_list_row_header_two_pane.xml
@@ -51,7 +51,9 @@
android:ellipsize="end"
android:maxLines="1"
android:textColor="?attr/widgetPickerHeaderAppTitleColor"
- android:textSize="16sp"
+ android:textSize="@dimen/widget_picker_header_app_title_font_size"
+ android:textFontWeight="@integer/widget_picker_header_app_title_font_weight"
+ android:lineHeight="@dimen/widget_picker_header_app_title_line_height"
android:duplicateParentState="true"
tools:text="App name" />
@@ -62,7 +64,9 @@
android:ellipsize="end"
android:maxLines="1"
android:textColor="?attr/widgetPickerHeaderAppSubtitleColor"
- android:alpha="0.7"
+ android:textSize="@dimen/widget_picker_header_app_subtitle_font_size"
+ android:textFontWeight="@integer/widget_picker_header_app_subtitle_font_weight"
+ android:lineHeight="@dimen/widget_picker_header_app_subtitle_line_height"
android:duplicateParentState="true"
tools:text="m widgets, n shortcuts" />
diff --git a/res/values-night-v34/colors.xml b/res/values-night-v34/colors.xml
new file mode 100644
index 0000000..af28119
--- /dev/null
+++ b/res/values-night-v34/colors.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 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.
+*/
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+ <color name="widget_picker_secondary_surface_color_dark">
+ @android:color/system_surface_bright_dark</color>
+ <color name="widget_picker_header_app_title_color_dark">
+ @android:color/system_on_surface_dark</color>
+ <color name="widget_picker_header_app_subtitle_color_dark">
+ @android:color/system_on_surface_variant_dark</color>
+ <color name="widget_cell_title_color_dark">
+ @android:color/system_on_surface_dark</color>
+ <color name="widget_cell_subtitle_color_dark">
+ @android:color/system_on_surface_variant_dark</color>
+</resources>
diff --git a/res/values-v34/colors.xml b/res/values-v34/colors.xml
new file mode 100644
index 0000000..26d3712
--- /dev/null
+++ b/res/values-v34/colors.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 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.
+*/
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+ <color name="widget_picker_secondary_surface_color_light">
+ @android:color/system_surface_bright_light</color>
+ <color name="widget_picker_header_app_title_color_light">
+ @android:color/system_on_surface_light</color>
+ <color name="widget_picker_header_app_subtitle_color_light">
+ @android:color/system_on_surface_variant_light</color>
+ <color name="widget_cell_title_color_light">
+ @android:color/system_on_surface_light</color>
+ <color name="widget_cell_subtitle_color_light">
+ @android:color/system_on_surface_variant_light</color>
+</resources>
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index f23c790..be8b2e1 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -76,6 +76,8 @@
<attr name="widgetPickerCollapseHandleColor" format="color"/>
<attr name="widgetPickerAddButtonBackgroundColor" format="color"/>
<attr name="widgetPickerAddButtonTextColor" format="color"/>
+ <attr name="widgetCellTitleColor" format="color" />
+ <attr name="widgetCellSubtitleColor" format="color" />
<!-- BubbleTextView specific attributes. -->
<declare-styleable name="BubbleTextView">
diff --git a/res/values/colors.xml b/res/values/colors.xml
index dfe40fc..ce80964 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -117,6 +117,8 @@
<color name="widget_picker_collapse_handle_color_light">#C4C7C5</color>
<color name="widget_picker_add_button_background_color_light">#0B57D0</color>
<color name="widget_picker_add_button_text_color_light">#0B57D0</color>
+ <color name="widget_cell_title_color_light">@color/material_color_on_surface</color>
+ <color name="widget_cell_subtitle_color_light">@color/material_color_on_surface_variant</color>
<color name="widget_picker_primary_surface_color_dark">#1F2020</color>
<color name="widget_picker_secondary_surface_color_dark">#393939</color>
@@ -134,6 +136,8 @@
<color name="widget_picker_collapse_handle_color_dark">#444746</color>
<color name="widget_picker_add_button_background_color_dark">#062E6F</color>
<color name="widget_picker_add_button_text_color_dark">#FFFFFF</color>
+ <color name="widget_cell_title_color_dark">@color/material_color_on_surface</color>
+ <color name="widget_cell_subtitle_color_dark">@color/material_color_on_surface_variant</color>
<color name="material_color_on_secondary_fixed_variant">#3F4759</color>
<color name="material_color_on_tertiary_fixed_variant">#583E5B</color>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 1bf59e8..4e5ef12 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -179,19 +179,39 @@
<!-- Widget tray -->
<dimen name="widget_cell_vertical_padding">8dp</dimen>
<dimen name="widget_cell_horizontal_padding">8dp</dimen>
- <dimen name="widget_cell_font_size">14sp</dimen>
+ <dimen name="widget_cell_title_font_size">14sp</dimen>
+ <integer name="widget_cell_title_font_weight">500</integer>
+ <dimen name="widget_cell_title_line_height">20sp</dimen>
+ <dimen name="widget_cell_dims_font_size">14sp</dimen>
+ <integer name="widget_cell_dims_font_weight">400</integer>
+ <dimen name="widget_cell_dims_line_height">20sp</dimen>
+ <dimen name="widget_cell_description_font_size">12sp</dimen>
+ <integer name="widget_cell_description_font_weight">400</integer>
+ <dimen name="widget_cell_description_line_height">16sp</dimen>
<dimen name="widget_cell_app_icon_size">24dp</dimen>
<dimen name="widget_cell_app_icon_padding">8dp</dimen>
<dimen name="widget_cell_add_button_height">48dp</dimen>
<dimen name="widget_cell_add_button_start_padding">8dp</dimen>
+ <dimen name="widget_cell_add_icon_button_start_padding">16dp</dimen>
<dimen name="widget_cell_add_button_end_padding">16dp</dimen>
<dimen name="widget_cell_add_button_scroll_padding">24dp</dimen>
+ <dimen name="widget_cell_add_button_font_size">14sp</dimen>
+ <integer name="widget_cell_add_button_font_weight">500</integer>
+ <dimen name="widget_cell_add_button_line_height">20sp</dimen>
+ <dimen name="widget_cell_add_button_drawable_padding">8dp</dimen>
+ <dimen name="widget_cell_add_button_drawable_width">19dp</dimen>
<dimen name="widget_tabs_button_horizontal_padding">4dp</dimen>
<dimen name="widget_tabs_horizontal_padding">16dp</dimen>
<dimen name="widget_apps_tabs_vertical_padding">6dp</dimen>
<dimen name="widget_picker_landscape_tablet_left_right_margin">117dp</dimen>
<dimen name="widget_picker_two_panels_left_right_margin">0dp</dimen>
+ <dimen name="widget_picker_header_app_title_font_size">16sp</dimen>
+ <integer name="widget_picker_header_app_title_font_weight">500</integer>
+ <dimen name="widget_picker_header_app_title_line_height">24sp</dimen>
+ <dimen name="widget_picker_header_app_subtitle_font_size">14sp</dimen>
+ <integer name="widget_picker_header_app_subtitle_font_weight">400</integer>
+ <dimen name="widget_picker_header_app_subtitle_line_height">20sp</dimen>
<dimen name="widget_recommendations_table_vertical_padding">8dp</dimen>
<!-- Bottom margin for the search and recommended widgets container without work profile -->
<dimen name="search_and_recommended_widgets_container_bottom_margin">16dp</dimen>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 00b962e..6d99084 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -268,6 +268,10 @@
@color/widget_picker_add_button_background_color_light</item>
<item name="widgetPickerAddButtonTextColor">
@color/widget_picker_add_button_text_color_light</item>
+ <item name="widgetCellTitleColor">
+ @color/widget_cell_title_color_light</item>
+ <item name="widgetCellSubtitleColor">
+ @color/widget_cell_subtitle_color_light</item>
</style>
<style name="WidgetContainerTheme.Dark" parent="AppTheme.Dark">
<item name="android:colorEdgeEffect">?android:attr/textColorSecondary</item>
@@ -303,6 +307,10 @@
@color/widget_picker_add_button_background_color_dark</item>
<item name="widgetPickerAddButtonTextColor">
@color/widget_picker_add_button_text_color_dark</item>
+ <item name="widgetCellTitleColor">
+ @color/widget_cell_title_color_dark</item>
+ <item name="widgetCellSubtitleColor">
+ @color/widget_cell_subtitle_color_dark</item>
</style>
<style name="FastScrollerPopup" parent="@android:style/TextAppearance.DeviceDefault.DialogWindowTitle">
diff --git a/src/com/android/launcher3/AppWidgetResizeFrame.java b/src/com/android/launcher3/AppWidgetResizeFrame.java
index b46d7e2..b51e850 100644
--- a/src/com/android/launcher3/AppWidgetResizeFrame.java
+++ b/src/com/android/launcher3/AppWidgetResizeFrame.java
@@ -136,6 +136,7 @@
private final Rect mWidgetViewNewRect = new Rect();
private final @Nullable LauncherAppWidgetHostView.CellChildViewPreLayoutListener
mCellChildViewPreLayoutListener;
+ private final @NonNull OnLayoutChangeListener mWidgetViewLayoutListener;
private int mXDown, mYDown;
@@ -177,6 +178,9 @@
mDragAcrossTwoPanelOpacityMargin = mLauncher.getResources().getDimensionPixelSize(
R.dimen.resize_frame_invalid_drag_across_two_panel_opacity_margin);
mDragLayerRelativeCoordinateHelper = new ViewGroupFocusHelper(mLauncher.getDragLayer());
+
+ mWidgetViewLayoutListener =
+ (v, l, t, r, b, oldL, oldT, oldR, oldB) -> setCornerRadiusFromWidget();
}
@Override
@@ -211,15 +215,6 @@
DragLayer dl = launcher.getDragLayer();
AppWidgetResizeFrame frame = (AppWidgetResizeFrame) launcher.getLayoutInflater()
.inflate(R.layout.app_widget_resize_frame, dl, false);
- if (widget.hasEnforcedCornerRadius()) {
- float enforcedCornerRadius = widget.getEnforcedCornerRadius();
- ImageView imageView = frame.findViewById(R.id.widget_resize_frame);
- Drawable d = imageView.getDrawable();
- if (d instanceof GradientDrawable) {
- GradientDrawable gd = (GradientDrawable) d.mutate();
- gd.setCornerRadius(enforcedCornerRadius);
- }
- }
frame.setupForWidget(widget, cellLayout, dl);
((DragLayer.LayoutParams) frame.getLayoutParams()).customPosition = true;
@@ -228,6 +223,18 @@
frame.post(() -> frame.snapToWidget(false));
}
+ private void setCornerRadiusFromWidget() {
+ if (mWidgetView != null && mWidgetView.hasEnforcedCornerRadius()) {
+ float enforcedCornerRadius = mWidgetView.getEnforcedCornerRadius();
+ ImageView imageView = findViewById(R.id.widget_resize_frame);
+ Drawable d = imageView.getDrawable();
+ if (d instanceof GradientDrawable) {
+ GradientDrawable gd = (GradientDrawable) d.mutate();
+ gd.setCornerRadius(enforcedCornerRadius);
+ }
+ }
+ }
+
private void setupForWidget(LauncherAppWidgetHostView widgetView, CellLayout cellLayout,
DragLayer dragLayer) {
mCellLayout = cellLayout;
@@ -317,6 +324,9 @@
.log(LAUNCHER_WIDGET_RESIZE_STARTED);
setOnKeyListener(this);
+
+ setCornerRadiusFromWidget();
+ mWidgetView.addOnLayoutChangeListener(mWidgetViewLayoutListener);
}
public boolean beginResizeIfPointInRegion(int x, int y) {
@@ -729,6 +739,7 @@
mWidgetView.setLayoutTransition(null);
}
mDragLayer.removeView(this);
+ mWidgetView.removeOnLayoutChangeListener(mWidgetViewLayoutListener);
}
private void updateInvalidResizeEffect(CellLayout cellLayout, CellLayout pairedCellLayout,
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index f405b93..54aea38 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -20,6 +20,7 @@
import static com.android.launcher3.Utilities.dpiFromPx;
import static com.android.launcher3.testing.shared.ResourceUtils.INVALID_RESOURCE_HANDLE;
import static com.android.launcher3.util.DisplayController.CHANGE_DENSITY;
+import static com.android.launcher3.util.DisplayController.CHANGE_DESKTOP_MODE;
import static com.android.launcher3.util.DisplayController.CHANGE_NAVIGATION_MODE;
import static com.android.launcher3.util.DisplayController.CHANGE_SUPPORTED_BOUNDS;
import static com.android.launcher3.util.DisplayController.CHANGE_TASKBAR_PINNING;
@@ -236,7 +237,8 @@
DisplayController.INSTANCE.get(context).setPriorityListener(
(displayContext, info, flags) -> {
if ((flags & (CHANGE_DENSITY | CHANGE_SUPPORTED_BOUNDS
- | CHANGE_NAVIGATION_MODE | CHANGE_TASKBAR_PINNING)) != 0) {
+ | CHANGE_NAVIGATION_MODE | CHANGE_TASKBAR_PINNING
+ | CHANGE_DESKTOP_MODE)) != 0) {
onConfigChanged(displayContext);
}
});
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index 9b0e0ec..be98589 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -48,7 +48,6 @@
import com.android.launcher3.model.AddWorkspaceItemsTask;
import com.android.launcher3.model.AllAppsList;
import com.android.launcher3.model.BaseLauncherBinder;
-import com.android.launcher3.model.BaseModelUpdateTask;
import com.android.launcher3.model.BgDataModel;
import com.android.launcher3.model.BgDataModel.Callbacks;
import com.android.launcher3.model.CacheDataUpdatedTask;
@@ -57,6 +56,7 @@
import com.android.launcher3.model.ModelDbController;
import com.android.launcher3.model.ModelDelegate;
import com.android.launcher3.model.ModelLauncherCallbacks;
+import com.android.launcher3.model.ModelTaskController;
import com.android.launcher3.model.ModelWriter;
import com.android.launcher3.model.PackageInstallStateChangedTask;
import com.android.launcher3.model.PackageUpdatedTask;
@@ -82,7 +82,6 @@
import java.util.HashSet;
import java.util.List;
import java.util.concurrent.CancellationException;
-import java.util.concurrent.Executor;
import java.util.function.Consumer;
import java.util.function.Supplier;
@@ -426,13 +425,9 @@
@Override
public void onInstallSessionCreated(@NonNull final PackageInstallInfo sessionInfo) {
if (FeatureFlags.PROMISE_APPS_IN_ALL_APPS.get()) {
- enqueueModelUpdateTask(new BaseModelUpdateTask() {
- @Override
- public void execute(@NonNull final LauncherAppState app,
- @NonNull final BgDataModel dataModel, @NonNull final AllAppsList apps) {
- apps.addPromiseApp(app.getContext(), sessionInfo);
- bindApplicationsIfNeeded();
- }
+ enqueueModelUpdateTask((taskController, dataModel, apps) -> {
+ apps.addPromiseApp(mApp.getContext(), sessionInfo);
+ taskController.bindApplicationsIfNeeded();
});
}
}
@@ -440,60 +435,56 @@
@Override
public void onSessionFailure(@NonNull final String packageName,
@NonNull final UserHandle user) {
- enqueueModelUpdateTask(new BaseModelUpdateTask() {
- @Override
- public void execute(@NonNull final LauncherAppState app,
- @NonNull final BgDataModel dataModel, @NonNull final AllAppsList apps) {
- IconCache iconCache = app.getIconCache();
- final IntSet removedIds = new IntSet();
- HashSet<WorkspaceItemInfo> archivedWorkspaceItemsToCacheRefresh = new HashSet<>();
- boolean isAppArchived = PackageManagerHelper.INSTANCE.get(mApp.getContext())
- .isAppArchivedForUser(packageName, user);
- synchronized (dataModel) {
- if (isAppArchived) {
- // Remove package icon cache entry for archived app in case of a session
- // failure.
- mApp.getIconCache().remove(
- new ComponentName(packageName, packageName + EMPTY_CLASS_NAME),
- user);
- }
+ enqueueModelUpdateTask((taskController, dataModel, apps) -> {
+ IconCache iconCache = mApp.getIconCache();
+ final IntSet removedIds = new IntSet();
+ HashSet<WorkspaceItemInfo> archivedWorkspaceItemsToCacheRefresh = new HashSet<>();
+ boolean isAppArchived = PackageManagerHelper.INSTANCE.get(mApp.getContext())
+ .isAppArchivedForUser(packageName, user);
+ synchronized (dataModel) {
+ if (isAppArchived) {
+ // Remove package icon cache entry for archived app in case of a session
+ // failure.
+ mApp.getIconCache().remove(
+ new ComponentName(packageName, packageName + EMPTY_CLASS_NAME),
+ user);
+ }
- for (ItemInfo info : dataModel.itemsIdMap) {
- if (info instanceof WorkspaceItemInfo
- && ((WorkspaceItemInfo) info).hasPromiseIconUi()
- && user.equals(info.user)
- && info.getIntent() != null) {
- if (TextUtils.equals(packageName, info.getIntent().getPackage())) {
- removedIds.add(info.id);
- }
- if (((WorkspaceItemInfo) info).isArchived()) {
- WorkspaceItemInfo workspaceItem = (WorkspaceItemInfo) info;
- // Refresh icons on the workspace for archived apps.
- iconCache.getTitleAndIcon(workspaceItem,
- workspaceItem.usingLowResIcon());
- archivedWorkspaceItemsToCacheRefresh.add(workspaceItem);
- }
+ for (ItemInfo info : dataModel.itemsIdMap) {
+ if (info instanceof WorkspaceItemInfo
+ && ((WorkspaceItemInfo) info).hasPromiseIconUi()
+ && user.equals(info.user)
+ && info.getIntent() != null) {
+ if (TextUtils.equals(packageName, info.getIntent().getPackage())) {
+ removedIds.add(info.id);
+ }
+ if (((WorkspaceItemInfo) info).isArchived()) {
+ WorkspaceItemInfo workspaceItem = (WorkspaceItemInfo) info;
+ // Refresh icons on the workspace for archived apps.
+ iconCache.getTitleAndIcon(workspaceItem,
+ workspaceItem.usingLowResIcon());
+ archivedWorkspaceItemsToCacheRefresh.add(workspaceItem);
}
}
-
- if (isAppArchived) {
- apps.updateIconsAndLabels(new HashSet<>(List.of(packageName)), user);
- }
}
- if (!removedIds.isEmpty()) {
- deleteAndBindComponentsRemoved(
- ItemInfoMatcher.ofItemIds(removedIds),
- "removed because install session failed");
- }
- if (!archivedWorkspaceItemsToCacheRefresh.isEmpty()) {
- bindUpdatedWorkspaceItems(
- archivedWorkspaceItemsToCacheRefresh.stream().toList());
- }
if (isAppArchived) {
- bindApplicationsIfNeeded();
+ apps.updateIconsAndLabels(new HashSet<>(List.of(packageName)), user);
}
}
+
+ if (!removedIds.isEmpty()) {
+ taskController.deleteAndBindComponentsRemoved(
+ ItemInfoMatcher.ofItemIds(removedIds),
+ "removed because install session failed");
+ }
+ if (!archivedWorkspaceItemsToCacheRefresh.isEmpty()) {
+ taskController.bindUpdatedWorkspaceItems(
+ archivedWorkspaceItemsToCacheRefresh.stream().toList());
+ }
+ if (isAppArchived) {
+ taskController.bindApplicationsIfNeeded();
+ }
});
}
@@ -583,13 +574,9 @@
*/
public void onWidgetLabelsUpdated(@NonNull final HashSet<String> updatedPackages,
@NonNull final UserHandle user) {
- enqueueModelUpdateTask(new BaseModelUpdateTask() {
- @Override
- public void execute(@NonNull final LauncherAppState app,
- @NonNull final BgDataModel dataModel, @NonNull final AllAppsList apps) {
- dataModel.widgetsModel.onPackageIconsUpdated(updatedPackages, user, app);
- bindUpdatedWidgets(dataModel);
- }
+ enqueueModelUpdateTask((taskController, dataModel, apps) -> {
+ dataModel.widgetsModel.onPackageIconsUpdated(updatedPackages, user, mApp);
+ taskController.bindUpdatedWidgets(dataModel);
});
}
@@ -597,8 +584,15 @@
if (mModelDestroyed) {
return;
}
- task.init(mApp, this, mBgDataModel, mBgAllAppsList, MAIN_EXECUTOR);
- MODEL_EXECUTOR.execute(task);
+ MODEL_EXECUTOR.execute(() -> {
+ if (!isModelLoaded()) {
+ // Loader has not yet run.
+ return;
+ }
+ ModelTaskController controller = new ModelTaskController(
+ mApp, mBgDataModel, mBgAllAppsList, this, MAIN_EXECUTOR);
+ task.execute(controller, mBgDataModel, mBgAllAppsList);
+ });
}
/**
@@ -610,18 +604,10 @@
void execute(@NonNull Callbacks callbacks);
}
- /**
- * A runnable which changes/updates the data model of the launcher based on certain events.
- */
- public interface ModelUpdateTask extends Runnable {
+ public interface ModelUpdateTask {
- /**
- * Called before the task is posted to initialize the internal state.
- */
- void init(@NonNull LauncherAppState app, @NonNull LauncherModel model,
- @NonNull BgDataModel dataModel, @NonNull AllAppsList allAppsList,
- @NonNull Executor uiExecutor);
-
+ void execute(@NonNull ModelTaskController taskController,
+ @NonNull BgDataModel dataModel, @NonNull AllAppsList apps);
}
public void updateAndBindWorkspaceItem(@NonNull final WorkspaceItemInfo si,
@@ -638,27 +624,19 @@
*/
public void updateAndBindWorkspaceItem(
@NonNull final Supplier<WorkspaceItemInfo> itemProvider) {
- enqueueModelUpdateTask(new BaseModelUpdateTask() {
- @Override
- public void execute(@NonNull final LauncherAppState app,
- @NonNull final BgDataModel dataModel, @NonNull final AllAppsList apps) {
- WorkspaceItemInfo info = itemProvider.get();
- getModelWriter().updateItemInDatabase(info);
- ArrayList<WorkspaceItemInfo> update = new ArrayList<>();
- update.add(info);
- bindUpdatedWorkspaceItems(update);
- }
+ enqueueModelUpdateTask((taskController, dataModel, apps) -> {
+ WorkspaceItemInfo info = itemProvider.get();
+ taskController.getModelWriter().updateItemInDatabase(info);
+ ArrayList<WorkspaceItemInfo> update = new ArrayList<>();
+ update.add(info);
+ taskController.bindUpdatedWorkspaceItems(update);
});
}
public void refreshAndBindWidgetsAndShortcuts(@Nullable final PackageUserKey packageUser) {
- enqueueModelUpdateTask(new BaseModelUpdateTask() {
- @Override
- public void execute(@NonNull final LauncherAppState app,
- @NonNull final BgDataModel dataModel, @NonNull final AllAppsList apps) {
- dataModel.widgetsModel.update(app, packageUser);
- bindUpdatedWidgets(dataModel);
- }
+ enqueueModelUpdateTask((taskController, dataModel, apps) -> {
+ dataModel.widgetsModel.update(taskController.getApp(), packageUser);
+ taskController.bindUpdatedWidgets(dataModel);
});
}
diff --git a/src/com/android/launcher3/LauncherPrefs.kt b/src/com/android/launcher3/LauncherPrefs.kt
index 6a5001b..13181e8 100644
--- a/src/com/android/launcher3/LauncherPrefs.kt
+++ b/src/com/android/launcher3/LauncherPrefs.kt
@@ -253,6 +253,7 @@
@JvmStatic fun get(context: Context): LauncherPrefs = INSTANCE.get(context)
const val TASKBAR_PINNING_KEY = "TASKBAR_PINNING_KEY"
+ const val TASKBAR_PINNING_DESKTOP_MODE_KEY = "TASKBAR_PINNING_DESKTOP_MODE_KEY"
const val SHOULD_SHOW_SMARTSPACE_KEY = "SHOULD_SHOW_SMARTSPACE_KEY"
@JvmField
val ICON_STATE = nonRestorableItem("pref_icon_shape_path", "", EncryptionType.ENCRYPTED)
@@ -272,6 +273,9 @@
@JvmField
val TASKBAR_PINNING =
backedUpItem(TASKBAR_PINNING_KEY, false, EncryptionType.DEVICE_PROTECTED)
+ @JvmField
+ val TASKBAR_PINNING_IN_DESKTOP_MODE =
+ backedUpItem(TASKBAR_PINNING_DESKTOP_MODE_KEY, true, EncryptionType.DEVICE_PROTECTED)
@JvmField
val DEVICE_TYPE =
diff --git a/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
index 2f623e2..0792641 100644
--- a/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
@@ -18,6 +18,7 @@
import static com.android.launcher3.Flags.enableExpandingPauseWorkButton;
import static com.android.launcher3.allapps.ActivityAllAppsContainerView.AdapterHolder.MAIN;
import static com.android.launcher3.allapps.ActivityAllAppsContainerView.AdapterHolder.SEARCH;
+import static com.android.launcher3.allapps.ActivityAllAppsContainerView.AdapterHolder.WORK;
import static com.android.launcher3.allapps.BaseAllAppsAdapter.VIEW_TYPE_PRIVATE_SPACE_HEADER;
import static com.android.launcher3.allapps.BaseAllAppsAdapter.VIEW_TYPE_WORK_DISABLED_CARD;
import static com.android.launcher3.allapps.BaseAllAppsAdapter.VIEW_TYPE_WORK_EDU_CARD;
@@ -470,8 +471,9 @@
* @param exitSearch Whether to force exit the search state and return to A-Z apps list.
*/
public void reset(boolean animate, boolean exitSearch) {
+ // Scroll Main and Work RV to top. Search RV is done in `resetSearch`.
for (int i = 0; i < mAH.size(); i++) {
- if (mAH.get(i).mRecyclerView != null) {
+ if (i != SEARCH && mAH.get(i).mRecyclerView != null) {
mAH.get(i).mRecyclerView.scrollToTop();
}
}
@@ -485,10 +487,8 @@
// Reset the base recycler view after transitioning home.
updateHeaderScroll(0);
if (exitSearch) {
- // Reset the search bar after transitioning home.
+ // Reset the search bar and search RV after transitioning home.
MAIN_EXECUTOR.getHandler().post(mSearchUiManager::resetSearch);
- // Animate to A-Z with 0 time to reset the animation with proper state management.
- animateToSearchState(false, 0);
}
if (isSearching()) {
mWorkManager.reset();
diff --git a/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java b/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
index 19c3ebe..ec45415 100644
--- a/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
+++ b/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
@@ -31,6 +31,7 @@
import com.android.launcher3.ExtendedEditText;
import com.android.launcher3.Utilities;
import com.android.launcher3.allapps.BaseAllAppsAdapter.AdapterItem;
+import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.search.SearchAlgorithm;
import com.android.launcher3.search.SearchCallback;
import com.android.launcher3.views.ActivityContext;
@@ -143,7 +144,7 @@
@Override
public void onFocusChange(View view, boolean hasFocus) {
- if (!hasFocus) {
+ if (!hasFocus && !FeatureFlags.ENABLE_DEVICE_SEARCH.get()) {
mInput.hideKeyboard();
}
}
diff --git a/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithm.java b/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithm.java
index ab47097..8121d2a 100644
--- a/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithm.java
+++ b/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithm.java
@@ -22,13 +22,9 @@
import android.os.Handler;
import androidx.annotation.AnyThread;
-import androidx.annotation.NonNull;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.allapps.BaseAllAppsAdapter.AdapterItem;
-import com.android.launcher3.model.AllAppsList;
-import com.android.launcher3.model.BaseModelUpdateTask;
-import com.android.launcher3.model.BgDataModel;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.search.SearchAlgorithm;
import com.android.launcher3.search.SearchCallback;
@@ -67,16 +63,12 @@
@Override
public void doSearch(String query, SearchCallback<AdapterItem> callback) {
- mAppState.getModel().enqueueModelUpdateTask(new BaseModelUpdateTask() {
- @Override
- public void execute(@NonNull final LauncherAppState app,
- @NonNull final BgDataModel dataModel, @NonNull final AllAppsList apps) {
- ArrayList<AdapterItem> result = getTitleMatchResult(apps.data, query);
- if (mAddNoResultsMessage && result.isEmpty()) {
- result.add(getEmptyMessageAdapterItem(query));
- }
- mResultHandler.post(() -> callback.onSearchResult(query, result));
+ mAppState.getModel().enqueueModelUpdateTask((taskController, dataModel, apps) -> {
+ ArrayList<AdapterItem> result = getTitleMatchResult(apps.data, query);
+ if (mAddNoResultsMessage && result.isEmpty()) {
+ result.add(getEmptyMessageAdapterItem(query));
}
+ mResultHandler.post(() -> callback.onSearchResult(query, result));
});
}
diff --git a/src/com/android/launcher3/folder/FolderNameProvider.java b/src/com/android/launcher3/folder/FolderNameProvider.java
index 5d2bb3a..be5f8f7 100644
--- a/src/com/android/launcher3/folder/FolderNameProvider.java
+++ b/src/com/android/launcher3/folder/FolderNameProvider.java
@@ -28,11 +28,12 @@
import androidx.annotation.WorkerThread;
import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherModel.ModelUpdateTask;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.model.AllAppsList;
-import com.android.launcher3.model.BaseModelUpdateTask;
import com.android.launcher3.model.BgDataModel;
+import com.android.launcher3.model.ModelTaskController;
import com.android.launcher3.model.StringCache;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.CollectionInfo;
@@ -191,10 +192,11 @@
nameInfos.setLabel(labels.length - 1, label, 1.0f);
}
- private class FolderNameWorker extends BaseModelUpdateTask {
+ private class FolderNameWorker implements ModelUpdateTask {
+
@Override
- public void execute(@NonNull final LauncherAppState app,
- @NonNull final BgDataModel dataModel, @NonNull final AllAppsList apps) {
+ public void execute(@NonNull ModelTaskController taskController,
+ @NonNull BgDataModel dataModel, @NonNull AllAppsList apps) {
mCollectionInfos = dataModel.collections.clone();
mAppInfos = Arrays.asList(apps.copyData());
}
diff --git a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
index 91632c6..1f388c2 100644
--- a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
+++ b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
@@ -221,10 +221,11 @@
launcherWidgetSpanInfo;
CellLayout firstScreen = mRootView.findViewById(R.id.workspace);
- firstScreen.setPadding(mDp.workspacePadding.left + mDp.cellLayoutPaddingPx.left,
+ firstScreen.setPadding(
+ mDp.workspacePadding.left + mDp.cellLayoutPaddingPx.left,
mDp.workspacePadding.top + mDp.cellLayoutPaddingPx.top,
- (mDp.isTwoPanels ? mDp.cellLayoutBorderSpacePx.x / 2
- : mDp.workspacePadding.right) + mDp.cellLayoutPaddingPx.right,
+ mDp.isTwoPanels ? (mDp.cellLayoutBorderSpacePx.x / 2)
+ : (mDp.workspacePadding.right + mDp.cellLayoutPaddingPx.right),
mDp.workspacePadding.bottom + mDp.cellLayoutPaddingPx.bottom
);
mWorkspaceScreens.put(FIRST_SCREEN_ID, firstScreen);
@@ -232,7 +233,7 @@
if (mDp.isTwoPanels) {
CellLayout rightPanel = mRootView.findViewById(R.id.workspace_right);
rightPanel.setPadding(
- mDp.cellLayoutBorderSpacePx.x / 2 + mDp.cellLayoutPaddingPx.left,
+ mDp.cellLayoutBorderSpacePx.x / 2,
mDp.workspacePadding.top + mDp.cellLayoutPaddingPx.top,
mDp.workspacePadding.right + mDp.cellLayoutPaddingPx.right,
mDp.workspacePadding.bottom + mDp.cellLayoutPaddingPx.bottom
diff --git a/src/com/android/launcher3/logging/StatsLogManager.java b/src/com/android/launcher3/logging/StatsLogManager.java
index 441bbb5..2f3c2b6 100644
--- a/src/com/android/launcher3/logging/StatsLogManager.java
+++ b/src/com/android/launcher3/logging/StatsLogManager.java
@@ -1205,7 +1205,7 @@
* Creates a new instance of {@link StatsLogManager} based on provided context.
*/
public static StatsLogManager newInstance(Context context) {
- return Overrides.getObject(StatsLogManager.class,
- context.getApplicationContext(), R.string.stats_log_manager_class);
+ return Overrides.getObject(
+ StatsLogManager.class, context, R.string.stats_log_manager_class);
}
}
diff --git a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
index 3fa6da4..427fb97 100644
--- a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
+++ b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
@@ -26,9 +26,10 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherModel.CallbackTask;
+import com.android.launcher3.LauncherModel.ModelUpdateTask;
import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.icons.IconCache;
import com.android.launcher3.logging.FileLog;
import com.android.launcher3.model.BgDataModel.Callbacks;
import com.android.launcher3.model.data.AppInfo;
@@ -50,7 +51,7 @@
/**
* Task to add auto-created workspace items.
*/
-public class AddWorkspaceItemsTask extends BaseModelUpdateTask {
+public class AddWorkspaceItemsTask implements ModelUpdateTask {
private static final String LOG = "AddWorkspaceItemsTask";
@@ -77,16 +78,17 @@
mItemSpaceFinder = itemSpaceFinder;
}
+
@Override
- public void execute(@NonNull final LauncherAppState app, @NonNull final BgDataModel dataModel,
- @NonNull final AllAppsList apps) {
+ public void execute(@NonNull ModelTaskController taskController, @NonNull BgDataModel dataModel,
+ @NonNull AllAppsList apps) {
if (mItemList.isEmpty()) {
return;
}
final ArrayList<ItemInfo> addedItemsFinal = new ArrayList<>();
final IntArray addedWorkspaceScreensFinal = new IntArray();
- final Context context = app.getContext();
+ final Context context = taskController.getApp().getContext();
synchronized (dataModel) {
IntArray workspaceScreens = dataModel.collectWorkspaceScreens();
@@ -128,8 +130,8 @@
for (ItemInfo item : filteredItems) {
// Find appropriate space for the item.
- int[] coords = mItemSpaceFinder.findSpaceForItem(app, dataModel, workspaceScreens,
- addedWorkspaceScreensFinal, item.spanX, item.spanY);
+ int[] coords = mItemSpaceFinder.findSpaceForItem(taskController.getApp(), dataModel,
+ workspaceScreens, addedWorkspaceScreensFinal, item.spanX, item.spanY);
int screenId = coords[0];
ItemInfo itemInfo;
@@ -176,8 +178,8 @@
if (hasActivity) {
// App was installed while launcher was in the background,
// or app was already installed for another user.
- itemInfo = new AppInfo(app.getContext(), activities.get(0), item.user)
- .makeWorkspaceItem(app.getContext());
+ itemInfo = new AppInfo(context, activities.get(0), item.user)
+ .makeWorkspaceItem(context);
if (shortcutExists(dataModel, itemInfo.getIntent(), itemInfo.user)) {
// We need this additional check here since we treat all auto added
@@ -187,16 +189,17 @@
continue;
}
+ IconCache cache = taskController.getApp().getIconCache();
WorkspaceItemInfo wii = (WorkspaceItemInfo) itemInfo;
wii.title = "";
- wii.bitmap = app.getIconCache().getDefaultIcon(item.user);
- app.getIconCache().getTitleAndIcon(wii,
+ wii.bitmap = cache.getDefaultIcon(item.user);
+ cache.getTitleAndIcon(wii,
((WorkspaceItemInfo) itemInfo).usingLowResIcon());
}
}
// Add the shortcut to the db
- getModelWriter().addItemToDatabase(itemInfo,
+ taskController.getModelWriter().addItemToDatabase(itemInfo,
LauncherSettings.Favorites.CONTAINER_DESKTOP, screenId,
coords[1], coords[2]);
@@ -209,7 +212,7 @@
}
if (!addedItemsFinal.isEmpty()) {
- scheduleCallbackTask(new CallbackTask() {
+ taskController.scheduleCallbackTask(new CallbackTask() {
@Override
public void execute(@NonNull Callbacks callbacks) {
final ArrayList<ItemInfo> addAnimated = new ArrayList<>();
diff --git a/src/com/android/launcher3/model/BaseModelUpdateTask.java b/src/com/android/launcher3/model/BaseModelUpdateTask.java
deleted file mode 100644
index 529c30a..0000000
--- a/src/com/android/launcher3/model/BaseModelUpdateTask.java
+++ /dev/null
@@ -1,160 +0,0 @@
-/*
- * Copyright (C) 2016 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.launcher3.model;
-
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.LauncherModel;
-import com.android.launcher3.LauncherModel.CallbackTask;
-import com.android.launcher3.LauncherModel.ModelUpdateTask;
-import com.android.launcher3.celllayout.CellPosMapper;
-import com.android.launcher3.model.BgDataModel.Callbacks;
-import com.android.launcher3.model.BgDataModel.FixedContainerItems;
-import com.android.launcher3.model.data.AppInfo;
-import com.android.launcher3.model.data.ItemInfo;
-import com.android.launcher3.model.data.WorkspaceItemInfo;
-import com.android.launcher3.util.ComponentKey;
-import com.android.launcher3.util.PackageUserKey;
-import com.android.launcher3.widget.model.WidgetsListBaseEntry;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.concurrent.Executor;
-import java.util.function.Predicate;
-import java.util.stream.Collectors;
-
-/**
- * Extension of {@link ModelUpdateTask} with some utility methods
- */
-public abstract class BaseModelUpdateTask implements ModelUpdateTask {
-
- private static final boolean DEBUG_TASKS = false;
- private static final String TAG = "BaseModelUpdateTask";
-
- // Nullabilities are explicitly omitted here because these are late-init fields,
- // They will be non-null after init(), which is always the case in enqueueModelUpdateTask().
- private LauncherAppState mApp;
- private LauncherModel mModel;
- private BgDataModel mDataModel;
- private AllAppsList mAllAppsList;
- private Executor mUiExecutor;
-
- public void init(@NonNull final LauncherAppState app, @NonNull final LauncherModel model,
- @NonNull final BgDataModel dataModel, @NonNull final AllAppsList allAppsList,
- @NonNull final Executor uiExecutor) {
- mApp = app;
- mModel = model;
- mDataModel = dataModel;
- mAllAppsList = allAppsList;
- mUiExecutor = uiExecutor;
- }
-
- @Override
- public final void run() {
- boolean isModelLoaded = Objects.requireNonNull(mModel).isModelLoaded();
- if (!isModelLoaded) {
- if (DEBUG_TASKS) {
- Log.d(TAG, "Ignoring model task since loader is pending=" + this);
- }
- // Loader has not yet run.
- return;
- }
- execute(mApp, mDataModel, mAllAppsList);
- }
-
- /**
- * Execute the actual task. Called on the worker thread.
- */
- public abstract void execute(@NonNull LauncherAppState app,
- @NonNull BgDataModel dataModel, @NonNull AllAppsList apps);
-
- /**
- * Schedules a {@param task} to be executed on the current callbacks.
- */
- public final void scheduleCallbackTask(@NonNull final CallbackTask task) {
- for (final Callbacks cb : mModel.getCallbacks()) {
- mUiExecutor.execute(() -> task.execute(cb));
- }
- }
-
- public ModelWriter getModelWriter() {
- // Updates from model task, do not deal with icon position in hotseat. Also no need to
- // verify changes as the ModelTasks always push the changes to callbacks
- return mModel.getWriter(false /* verifyChanges */, CellPosMapper.DEFAULT, null);
- }
-
- public void bindUpdatedWorkspaceItems(@NonNull final List<WorkspaceItemInfo> allUpdates) {
- // Bind workspace items
- List<WorkspaceItemInfo> workspaceUpdates = allUpdates.stream()
- .filter(info -> info.id != ItemInfo.NO_ID)
- .collect(Collectors.toList());
- if (!workspaceUpdates.isEmpty()) {
- scheduleCallbackTask(c -> c.bindWorkspaceItemsChanged(workspaceUpdates));
- }
-
- // Bind extra items if any
- allUpdates.stream()
- .mapToInt(info -> info.container)
- .distinct()
- .mapToObj(mDataModel.extraItems::get)
- .filter(Objects::nonNull)
- .forEach(this::bindExtraContainerItems);
- }
-
- public void bindExtraContainerItems(@NonNull final FixedContainerItems item) {
- scheduleCallbackTask(c -> c.bindExtraContainerItems(item));
- }
-
- public void bindDeepShortcuts(@NonNull final BgDataModel dataModel) {
- final HashMap<ComponentKey, Integer> shortcutMapCopy =
- new HashMap<>(dataModel.deepShortcutMap);
- scheduleCallbackTask(callbacks -> callbacks.bindDeepShortcutMap(shortcutMapCopy));
- }
-
- public void bindUpdatedWidgets(@NonNull final BgDataModel dataModel) {
- final ArrayList<WidgetsListBaseEntry> widgets =
- dataModel.widgetsModel.getWidgetsListForPicker(mApp.getContext());
- scheduleCallbackTask(c -> c.bindAllWidgets(widgets));
- }
-
- public void deleteAndBindComponentsRemoved(final Predicate<ItemInfo> matcher,
- @Nullable final String reason) {
- getModelWriter().deleteItemsFromDatabase(matcher, reason);
-
- // Call the components-removed callback
- scheduleCallbackTask(c -> c.bindWorkspaceComponentsRemoved(matcher));
- }
-
- public void bindApplicationsIfNeeded() {
- if (mAllAppsList.getAndResetChangeFlag()) {
- AppInfo[] apps = mAllAppsList.copyData();
- int flags = mAllAppsList.getFlags();
- Map<PackageUserKey, Integer> packageUserKeytoUidMap = Arrays.stream(apps).collect(
- Collectors.toMap(
- appInfo -> new PackageUserKey(appInfo.componentName.getPackageName(),
- appInfo.user), appInfo -> appInfo.uid, (a, b) -> a));
- scheduleCallbackTask(c -> c.bindAllApplications(apps, flags, packageUserKeytoUidMap));
- }
- }
-}
diff --git a/src/com/android/launcher3/model/CacheDataUpdatedTask.java b/src/com/android/launcher3/model/CacheDataUpdatedTask.java
index 57fefaa..66b4fd9 100644
--- a/src/com/android/launcher3/model/CacheDataUpdatedTask.java
+++ b/src/com/android/launcher3/model/CacheDataUpdatedTask.java
@@ -20,7 +20,7 @@
import androidx.annotation.NonNull;
-import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherModel.ModelUpdateTask;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.icons.IconCache;
import com.android.launcher3.model.data.WorkspaceItemInfo;
@@ -31,7 +31,7 @@
/**
* Handles changes due to cache updates.
*/
-public class CacheDataUpdatedTask extends BaseModelUpdateTask {
+public class CacheDataUpdatedTask implements ModelUpdateTask {
public static final int OP_CACHE_UPDATE = 1;
public static final int OP_SESSION_UPDATE = 2;
@@ -52,9 +52,9 @@
}
@Override
- public void execute(@NonNull final LauncherAppState app, @NonNull final BgDataModel dataModel,
- @NonNull final AllAppsList apps) {
- IconCache iconCache = app.getIconCache();
+ public void execute(@NonNull ModelTaskController taskController, @NonNull BgDataModel dataModel,
+ @NonNull AllAppsList apps) {
+ IconCache iconCache = taskController.getApp().getIconCache();
ArrayList<WorkspaceItemInfo> updatedShortcuts = new ArrayList<>();
synchronized (dataModel) {
@@ -69,8 +69,8 @@
});
apps.updateIconsAndLabels(mPackages, mUser);
}
- bindUpdatedWorkspaceItems(updatedShortcuts);
- bindApplicationsIfNeeded();
+ taskController.bindUpdatedWorkspaceItems(updatedShortcuts);
+ taskController.bindApplicationsIfNeeded();
}
public boolean isValidShortcut(@NonNull final WorkspaceItemInfo si) {
diff --git a/src/com/android/launcher3/model/ModelTaskController.kt b/src/com/android/launcher3/model/ModelTaskController.kt
new file mode 100644
index 0000000..266ed0c
--- /dev/null
+++ b/src/com/android/launcher3/model/ModelTaskController.kt
@@ -0,0 +1,104 @@
+/*
+ * 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.launcher3.model
+
+import com.android.launcher3.LauncherAppState
+import com.android.launcher3.LauncherModel
+import com.android.launcher3.LauncherModel.CallbackTask
+import com.android.launcher3.celllayout.CellPosMapper
+import com.android.launcher3.model.BgDataModel.FixedContainerItems
+import com.android.launcher3.model.data.ItemInfo
+import com.android.launcher3.model.data.WorkspaceItemInfo
+import com.android.launcher3.util.PackageUserKey
+import java.util.Objects
+import java.util.concurrent.Executor
+import java.util.function.Predicate
+
+/** Class with utility methods and properties for running a LauncherModel Task */
+class ModelTaskController(
+ val app: LauncherAppState,
+ val dataModel: BgDataModel,
+ val allAppsList: AllAppsList,
+ private val model: LauncherModel,
+ private val uiExecutor: Executor
+) {
+
+ /** Schedules a {@param task} to be executed on the current callbacks. */
+ fun scheduleCallbackTask(task: CallbackTask) {
+ for (cb in model.callbacks) {
+ uiExecutor.execute { task.execute(cb) }
+ }
+ }
+
+ /**
+ * Updates from model task, do not deal with icon position in hotseat. Also no need to verify
+ * changes as the ModelTasks always push the changes to callbacks
+ */
+ fun getModelWriter() = model.getWriter(false /* verifyChanges */, CellPosMapper.DEFAULT, null)
+
+ fun bindUpdatedWorkspaceItems(allUpdates: List<WorkspaceItemInfo>) {
+ // Bind workspace items
+ val workspaceUpdates =
+ allUpdates.stream().filter { info -> info.id != ItemInfo.NO_ID }.toList()
+ if (workspaceUpdates.isNotEmpty()) {
+ scheduleCallbackTask { it.bindWorkspaceItemsChanged(workspaceUpdates) }
+ }
+
+ // Bind extra items if any
+ allUpdates
+ .stream()
+ .mapToInt { info: WorkspaceItemInfo -> info.container }
+ .distinct()
+ .mapToObj { dataModel.extraItems.get(it) }
+ .filter { Objects.nonNull(it) }
+ .forEach { bindExtraContainerItems(it) }
+ }
+
+ fun bindExtraContainerItems(item: FixedContainerItems) {
+ scheduleCallbackTask { it.bindExtraContainerItems(item) }
+ }
+
+ fun bindDeepShortcuts(dataModel: BgDataModel) {
+ val shortcutMapCopy = HashMap(dataModel.deepShortcutMap)
+ scheduleCallbackTask { it.bindDeepShortcutMap(shortcutMapCopy) }
+ }
+
+ fun bindUpdatedWidgets(dataModel: BgDataModel) {
+ val widgets = dataModel.widgetsModel.getWidgetsListForPicker(app.context)
+ scheduleCallbackTask { it.bindAllWidgets(widgets) }
+ }
+
+ fun deleteAndBindComponentsRemoved(matcher: Predicate<ItemInfo?>, reason: String?) {
+ getModelWriter().deleteItemsFromDatabase(matcher, reason)
+
+ // Call the components-removed callback
+ scheduleCallbackTask { it.bindWorkspaceComponentsRemoved(matcher) }
+ }
+
+ fun bindApplicationsIfNeeded() {
+ if (allAppsList.getAndResetChangeFlag()) {
+ val apps = allAppsList.copyData()
+ val flags = allAppsList.flags
+ val packageUserKeyToUidMap =
+ apps.associateBy(
+ keySelector = { PackageUserKey(it.componentName!!.packageName, it.user) },
+ valueTransform = { it.uid }
+ )
+ scheduleCallbackTask { it.bindAllApplications(apps, flags, packageUserKeyToUidMap) }
+ }
+ }
+}
diff --git a/src/com/android/launcher3/model/PackageIncrementalDownloadUpdatedTask.java b/src/com/android/launcher3/model/PackageIncrementalDownloadUpdatedTask.java
index b9fba9d..f924a9f 100644
--- a/src/com/android/launcher3/model/PackageIncrementalDownloadUpdatedTask.java
+++ b/src/com/android/launcher3/model/PackageIncrementalDownloadUpdatedTask.java
@@ -19,7 +19,7 @@
import androidx.annotation.NonNull;
-import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherModel.ModelUpdateTask;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.ItemInfoWithIcon;
import com.android.launcher3.model.data.WorkspaceItemInfo;
@@ -31,7 +31,7 @@
/**
* Handles updates due to incremental download progress updates.
*/
-public class PackageIncrementalDownloadUpdatedTask extends BaseModelUpdateTask {
+public class PackageIncrementalDownloadUpdatedTask implements ModelUpdateTask {
@NonNull
private final UserHandle mUser;
@@ -49,8 +49,8 @@
}
@Override
- public void execute(@NonNull LauncherAppState app, @NonNull final BgDataModel dataModel,
- @NonNull final AllAppsList appsList) {
+ public void execute(@NonNull ModelTaskController taskController, @NonNull BgDataModel dataModel,
+ @NonNull AllAppsList appsList) {
PackageInstallInfo downloadInfo = new PackageInstallInfo(
mPackageName,
PackageInstallInfo.STATUS_INSTALLED_DOWNLOADING,
@@ -62,11 +62,11 @@
if (!updatedAppInfos.isEmpty()) {
for (AppInfo appInfo : updatedAppInfos) {
appInfo.runtimeStatusFlags &= ~ItemInfoWithIcon.FLAG_INSTALL_SESSION_ACTIVE;
- scheduleCallbackTask(
+ taskController.scheduleCallbackTask(
c -> c.bindIncrementalDownloadProgressUpdated(appInfo));
}
}
- bindApplicationsIfNeeded();
+ taskController.bindApplicationsIfNeeded();
}
final ArrayList<WorkspaceItemInfo> updatedWorkspaceItems = new ArrayList<>();
@@ -79,6 +79,6 @@
}
});
}
- bindUpdatedWorkspaceItems(updatedWorkspaceItems);
+ taskController.bindUpdatedWorkspaceItems(updatedWorkspaceItems);
}
}
diff --git a/src/com/android/launcher3/model/PackageInstallStateChangedTask.java b/src/com/android/launcher3/model/PackageInstallStateChangedTask.java
index 2457a42..d238213 100644
--- a/src/com/android/launcher3/model/PackageInstallStateChangedTask.java
+++ b/src/com/android/launcher3/model/PackageInstallStateChangedTask.java
@@ -15,12 +15,13 @@
*/
package com.android.launcher3.model;
+import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import androidx.annotation.NonNull;
-import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherModel.ModelUpdateTask;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
@@ -33,7 +34,7 @@
/**
* Handles changes due to a sessions updates for a currently installing app.
*/
-public class PackageInstallStateChangedTask extends BaseModelUpdateTask {
+public class PackageInstallStateChangedTask implements ModelUpdateTask {
@NonNull
private final PackageInstallInfo mInstallInfo;
@@ -43,16 +44,17 @@
}
@Override
- public void execute(@NonNull final LauncherAppState app, @NonNull final BgDataModel dataModel,
- @NonNull final AllAppsList apps) {
+ public void execute(@NonNull ModelTaskController taskController, @NonNull BgDataModel dataModel,
+ @NonNull AllAppsList apps) {
if (mInstallInfo.state == PackageInstallInfo.STATUS_INSTALLED) {
try {
// For instant apps we do not get package-add. Use setting events to update
// any pinned icons.
- ApplicationInfo ai = app.getContext()
+ Context context = taskController.getApp().getContext();
+ ApplicationInfo ai = context
.getPackageManager().getApplicationInfo(mInstallInfo.packageName, 0);
- if (InstantAppResolver.newInstance(app.getContext()).isInstantApp(ai)) {
- app.getModel().newModelCallbacks()
+ if (InstantAppResolver.newInstance(context).isInstantApp(ai)) {
+ taskController.getApp().getModel().newModelCallbacks()
.onPackageAdded(ai.packageName, mInstallInfo.user);
}
} catch (PackageManager.NameNotFoundException e) {
@@ -66,10 +68,11 @@
List<AppInfo> updatedAppInfos = apps.updatePromiseInstallInfo(mInstallInfo);
if (!updatedAppInfos.isEmpty()) {
for (AppInfo appInfo : updatedAppInfos) {
- scheduleCallbackTask(c -> c.bindIncrementalDownloadProgressUpdated(appInfo));
+ taskController.scheduleCallbackTask(
+ c -> c.bindIncrementalDownloadProgressUpdated(appInfo));
}
}
- bindApplicationsIfNeeded();
+ taskController.bindApplicationsIfNeeded();
}
synchronized (dataModel) {
@@ -90,7 +93,8 @@
}
if (!updates.isEmpty()) {
- scheduleCallbackTask(callbacks -> callbacks.bindRestoreItemsChange(updates));
+ taskController.scheduleCallbackTask(
+ callbacks -> callbacks.bindRestoreItemsChange(updates));
}
}
}
diff --git a/src/com/android/launcher3/model/PackageUpdatedTask.java b/src/com/android/launcher3/model/PackageUpdatedTask.java
index 6432d33..802faae 100644
--- a/src/com/android/launcher3/model/PackageUpdatedTask.java
+++ b/src/com/android/launcher3/model/PackageUpdatedTask.java
@@ -36,9 +36,9 @@
import com.android.launcher3.Flags;
import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherModel.ModelUpdateTask;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.LauncherSettings.Favorites;
-import com.android.launcher3.Utilities;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.icons.IconCache;
import com.android.launcher3.logging.FileLog;
@@ -71,7 +71,7 @@
* or when a user availability changes.
*/
@SuppressWarnings("NewApi")
-public class PackageUpdatedTask extends BaseModelUpdateTask {
+public class PackageUpdatedTask implements ModelUpdateTask {
// TODO(b/290090023): Set to false after root causing is done.
private static final boolean DEBUG = true;
@@ -102,8 +102,9 @@
}
@Override
- public void execute(@NonNull final LauncherAppState app, @NonNull final BgDataModel dataModel,
- @NonNull final AllAppsList appsList) {
+ public void execute(@NonNull ModelTaskController taskController, @NonNull BgDataModel dataModel,
+ @NonNull AllAppsList appsList) {
+ final LauncherAppState app = taskController.getApp();
final Context context = app.getContext();
final IconCache iconCache = app.getIconCache();
@@ -192,7 +193,7 @@
break;
}
- bindApplicationsIfNeeded();
+ taskController.bindApplicationsIfNeeded();
final IntSet removedShortcuts = new IntSet();
// Shortcuts to keep even if the corresponding app was removed
@@ -305,7 +306,7 @@
updatedWorkspaceItems.add(si);
}
if (infoUpdated && si.id != ItemInfo.NO_ID) {
- getModelWriter().updateItemInDatabase(si);
+ taskController.getModelWriter().updateItemInDatabase(si);
}
});
@@ -324,20 +325,20 @@
widgetInfo.restoreStatus |= LauncherAppWidgetInfo.FLAG_UI_NOT_READY;
widgets.add(widgetInfo);
- getModelWriter().updateItemInDatabase(widgetInfo);
+ taskController.getModelWriter().updateItemInDatabase(widgetInfo);
}
}
}
- bindUpdatedWorkspaceItems(updatedWorkspaceItems);
+ taskController.bindUpdatedWorkspaceItems(updatedWorkspaceItems);
if (!removedShortcuts.isEmpty()) {
- deleteAndBindComponentsRemoved(
+ taskController.deleteAndBindComponentsRemoved(
ItemInfoMatcher.ofItemIds(removedShortcuts),
"removed because the target component is invalid");
}
if (!widgets.isEmpty()) {
- scheduleCallbackTask(c -> c.bindWidgetsRestored(widgets));
+ taskController.scheduleCallbackTask(c -> c.bindWidgetsRestored(widgets));
}
}
@@ -363,7 +364,7 @@
ItemInfoMatcher.ofPackages(removedPackages, mUser)
.or(ItemInfoMatcher.ofComponents(removedComponents, mUser))
.and(ItemInfoMatcher.ofItemIds(forceKeepShortcuts).negate());
- deleteAndBindComponentsRemoved(removeMatch,
+ taskController.deleteAndBindComponentsRemoved(removeMatch,
"removed because the corresponding package or component is removed. "
+ "mOp=" + mOp + " removedPackages=" + removedPackages.stream().collect(
Collectors.joining(",", "[", "]"))
@@ -382,7 +383,7 @@
for (int i = 0; i < N; i++) {
dataModel.widgetsModel.update(app, new PackageUserKey(packages[i], mUser));
}
- bindUpdatedWidgets(dataModel);
+ taskController.bindUpdatedWidgets(dataModel);
}
}
diff --git a/src/com/android/launcher3/model/ReloadStringCacheTask.java b/src/com/android/launcher3/model/ReloadStringCacheTask.java
index 34f7057..3d974d6 100644
--- a/src/com/android/launcher3/model/ReloadStringCacheTask.java
+++ b/src/com/android/launcher3/model/ReloadStringCacheTask.java
@@ -17,13 +17,13 @@
import androidx.annotation.NonNull;
-import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherModel.ModelUpdateTask;
/**
* Handles updates due to changes in Device Policy Management resources triggered by
* {@link android.app.admin.DevicePolicyManager#ACTION_DEVICE_POLICY_RESOURCE_UPDATED}.
*/
-public class ReloadStringCacheTask extends BaseModelUpdateTask {
+public class ReloadStringCacheTask implements ModelUpdateTask {
@NonNull
private ModelDelegate mModelDelegate;
@@ -33,12 +33,12 @@
}
@Override
- public void execute(@NonNull final LauncherAppState app, @NonNull final BgDataModel dataModel,
- @NonNull final AllAppsList appsList) {
+ public void execute(@NonNull ModelTaskController taskController, @NonNull BgDataModel dataModel,
+ @NonNull AllAppsList apps) {
synchronized (dataModel) {
mModelDelegate.loadStringCache(dataModel.stringCache);
StringCache cloneSC = dataModel.stringCache.clone();
- scheduleCallbackTask(c -> c.bindStringCache(cloneSC));
+ taskController.scheduleCallbackTask(c -> c.bindStringCache(cloneSC));
}
}
}
diff --git a/src/com/android/launcher3/model/ShortcutsChangedTask.java b/src/com/android/launcher3/model/ShortcutsChangedTask.java
index 1cb5215..1916d23 100644
--- a/src/com/android/launcher3/model/ShortcutsChangedTask.java
+++ b/src/com/android/launcher3/model/ShortcutsChangedTask.java
@@ -22,6 +22,7 @@
import androidx.annotation.NonNull;
import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherModel.ModelUpdateTask;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.shortcuts.ShortcutKey;
@@ -38,7 +39,7 @@
/**
* Handles changes due to shortcut manager updates (deep shortcut changes)
*/
-public class ShortcutsChangedTask extends BaseModelUpdateTask {
+public class ShortcutsChangedTask implements ModelUpdateTask {
@NonNull
private final String mPackageName;
@@ -61,8 +62,9 @@
}
@Override
- public void execute(@NonNull final LauncherAppState app, @NonNull final BgDataModel dataModel,
- @NonNull final AllAppsList apps) {
+ public void execute(@NonNull ModelTaskController taskController, @NonNull BgDataModel dataModel,
+ @NonNull AllAppsList apps) {
+ final LauncherAppState app = taskController.getApp();
final Context context = app.getContext();
// Find WorkspaceItemInfo's that have changed on the workspace.
ArrayList<WorkspaceItemInfo> matchingWorkspaceItems = new ArrayList<>();
@@ -78,8 +80,8 @@
if (!matchingWorkspaceItems.isEmpty()) {
if (mShortcuts.isEmpty()) {
- PackageManagerHelper packageManagerHelper = PackageManagerHelper.INSTANCE.get(
- app.getContext());
+ PackageManagerHelper packageManagerHelper =
+ PackageManagerHelper.INSTANCE.get(context);
// Verify that the app is indeed installed.
if (!packageManagerHelper.isAppInstalled(mPackageName, mUser)
&& !packageManagerHelper.isAppArchivedForUser(mPackageName, mUser)) {
@@ -115,9 +117,9 @@
});
}
- bindUpdatedWorkspaceItems(updatedWorkspaceItemInfos);
+ taskController.bindUpdatedWorkspaceItems(updatedWorkspaceItemInfos);
if (!nonPinnedIds.isEmpty()) {
- deleteAndBindComponentsRemoved(ItemInfoMatcher.ofShortcutKeys(
+ taskController.deleteAndBindComponentsRemoved(ItemInfoMatcher.ofShortcutKeys(
nonPinnedIds.stream()
.map(id -> new ShortcutKey(mPackageName, mUser, id))
.collect(Collectors.toSet())),
@@ -128,7 +130,7 @@
if (mUpdateIdMap) {
// Update the deep shortcut map if the list of ids has changed for an activity.
dataModel.updateDeepShortcutCounts(mPackageName, mUser, mShortcuts);
- bindDeepShortcuts(dataModel);
+ taskController.bindDeepShortcuts(dataModel);
}
}
}
diff --git a/src/com/android/launcher3/model/UserLockStateChangedTask.java b/src/com/android/launcher3/model/UserLockStateChangedTask.java
index 63ca35b..3dc5ff3 100644
--- a/src/com/android/launcher3/model/UserLockStateChangedTask.java
+++ b/src/com/android/launcher3/model/UserLockStateChangedTask.java
@@ -24,6 +24,7 @@
import androidx.annotation.NonNull;
import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherModel.ModelUpdateTask;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.shortcuts.ShortcutKey;
@@ -40,7 +41,7 @@
/**
* Task to handle changing of lock state of the user
*/
-public class UserLockStateChangedTask extends BaseModelUpdateTask {
+public class UserLockStateChangedTask implements ModelUpdateTask {
@NonNull
private final UserHandle mUser;
@@ -52,8 +53,9 @@
}
@Override
- public void execute(@NonNull final LauncherAppState app, @NonNull final BgDataModel dataModel,
- @NonNull final AllAppsList apps) {
+ public void execute(@NonNull ModelTaskController taskController, @NonNull BgDataModel dataModel,
+ @NonNull AllAppsList apps) {
+ LauncherAppState app = taskController.getApp();
Context context = app.getContext();
HashMap<ShortcutKey, ShortcutInfo> pinnedShortcuts = new HashMap<>();
@@ -98,9 +100,10 @@
}
});
}
- bindUpdatedWorkspaceItems(updatedWorkspaceItemInfos);
+ taskController.bindUpdatedWorkspaceItems(updatedWorkspaceItemInfos);
if (!removedKeys.isEmpty()) {
- deleteAndBindComponentsRemoved(ItemInfoMatcher.ofShortcutKeys(removedKeys),
+ taskController.deleteAndBindComponentsRemoved(
+ ItemInfoMatcher.ofShortcutKeys(removedKeys),
"removed during unlock because it's no longer available"
+ " (possibly due to clear data)");
}
@@ -118,6 +121,6 @@
null, mUser,
new ShortcutRequest(context, mUser).query(ShortcutRequest.ALL));
}
- bindDeepShortcuts(dataModel);
+ taskController.bindDeepShortcuts(dataModel);
}
}
diff --git a/src/com/android/launcher3/statemanager/StateManager.java b/src/com/android/launcher3/statemanager/StateManager.java
index 51bc339..eea1a7d 100644
--- a/src/com/android/launcher3/statemanager/StateManager.java
+++ b/src/com/android/launcher3/statemanager/StateManager.java
@@ -51,7 +51,7 @@
public class StateManager<STATE_TYPE extends BaseState<STATE_TYPE>> {
public static final String TAG = "StateManager";
- // b/279059025
+ // b/279059025, b/325463989
private static final boolean DEBUG = true;
private final AnimationState mConfig = new AnimationState();
@@ -240,16 +240,8 @@
private void goToState(
STATE_TYPE state, boolean animated, long delay, AnimatorListener listener) {
if (DEBUG) {
- String stackTrace = Log.getStackTraceString(new Exception("tracing state transition"));
- String truncatedTrace =
- Arrays.stream(stackTrace.split("\\n"))
- .limit(5)
- .skip(1) // Removes the line "java.lang.Exception: tracing state
- // transition"
- .filter(traceLine -> !traceLine.contains("StateManager.goToState"))
- .collect(Collectors.joining("\n"));
Log.d(TAG, "goToState - fromState: " + mState + ", toState: " + state
- + ", partial trace:\n" + truncatedTrace);
+ + ", partial trace:\n" + getTrimmedStackTrace("StateManager.goToState"));
}
animated &= areAnimatorsEnabled();
@@ -336,17 +328,9 @@
public AnimatorSet createAtomicAnimation(
STATE_TYPE fromState, STATE_TYPE toState, StateAnimationConfig config) {
if (DEBUG) {
- String stackTrace = Log.getStackTraceString(new Exception("tracing state transition"));
- String truncatedTrace =
- Arrays.stream(stackTrace.split("\\n"))
- .limit(5)
- .skip(1) // Removes the line "java.lang.Exception: tracing state
- // transition"
- .filter(traceLine -> !traceLine.contains(
- "StateManager.createAtomicAnimation"))
- .collect(Collectors.joining("\n"));
Log.d(TAG, "createAtomicAnimation - fromState: " + fromState + ", toState: " + toState
- + ", partial trace:\n" + truncatedTrace);
+ + ", partial trace:\n" + getTrimmedStackTrace(
+ "StateManager.createAtomicAnimation"));
}
PendingAnimation builder = new PendingAnimation(config.duration);
@@ -481,7 +465,8 @@
*/
public void cancelAnimation() {
if (DEBUG && mConfig.currentAnimation != null) {
- Log.d(TAG, "cancelAnimation - with ongoing animation");
+ Log.d(TAG, "cancelAnimation - with ongoing animation"
+ + ", partial trace:\n" + getTrimmedStackTrace("StateManager.cancelAnimation"));
}
mConfig.reset();
// It could happen that a new animation is set as a result of an endListener on the
@@ -579,6 +564,15 @@
mConfig.playbackController = null;
}
+ private String getTrimmedStackTrace(String callingMethodName) {
+ String stackTrace = Log.getStackTraceString(new Exception());
+ return Arrays.stream(stackTrace.split("\\n"))
+ .skip(2) // Removes the line "java.lang.Exception" and "getTrimmedStackTrace".
+ .filter(traceLine -> !traceLine.contains(callingMethodName))
+ .limit(3)
+ .collect(Collectors.joining("\n"));
+ }
+
private class StartAnimRunnable implements Runnable {
private final AnimatorSet mAnim;
diff --git a/src/com/android/launcher3/util/DisplayController.java b/src/com/android/launcher3/util/DisplayController.java
index 8806e27..92fc38f 100644
--- a/src/com/android/launcher3/util/DisplayController.java
+++ b/src/com/android/launcher3/util/DisplayController.java
@@ -23,6 +23,8 @@
import static com.android.launcher3.InvariantDeviceProfile.TYPE_PHONE;
import static com.android.launcher3.InvariantDeviceProfile.TYPE_TABLET;
import static com.android.launcher3.LauncherPrefs.TASKBAR_PINNING;
+import static com.android.launcher3.LauncherPrefs.TASKBAR_PINNING_DESKTOP_MODE_KEY;
+import static com.android.launcher3.LauncherPrefs.TASKBAR_PINNING_IN_DESKTOP_MODE;
import static com.android.launcher3.LauncherPrefs.TASKBAR_PINNING_KEY;
import static com.android.launcher3.Utilities.dpiFromPx;
import static com.android.launcher3.config.FeatureFlags.enableTaskbarPinning;
@@ -88,10 +90,11 @@
public static final int CHANGE_SUPPORTED_BOUNDS = 1 << 3;
public static final int CHANGE_NAVIGATION_MODE = 1 << 4;
public static final int CHANGE_TASKBAR_PINNING = 1 << 5;
+ public static final int CHANGE_DESKTOP_MODE = 1 << 6;
public static final int CHANGE_ALL = CHANGE_ACTIVE_SCREEN | CHANGE_ROTATION
| CHANGE_DENSITY | CHANGE_SUPPORTED_BOUNDS | CHANGE_NAVIGATION_MODE
- | CHANGE_TASKBAR_PINNING;
+ | CHANGE_TASKBAR_PINNING | CHANGE_DESKTOP_MODE;
private static final String ACTION_OVERLAY_CHANGED = "android.intent.action.OVERLAY_CHANGED";
private static final String TARGET_OVERLAY_PACKAGE = "android";
@@ -145,16 +148,22 @@
private void attachTaskbarPinningSharedPreferenceChangeListener(Context context) {
mTaskbarPinningPreferenceChangeListener =
(sharedPreferences, key) -> {
- if (TASKBAR_PINNING_KEY.equals(key)
- && mInfo.mIsTaskbarPinned != LauncherPrefs.get(mContext).get(
- TASKBAR_PINNING)
- ) {
+ LauncherPrefs prefs = LauncherPrefs.get(mContext);
+ boolean isTaskbarPinningChanged = TASKBAR_PINNING_KEY.equals(key)
+ && mInfo.mIsTaskbarPinned != prefs.get(TASKBAR_PINNING);
+ boolean isTaskbarPinningDesktopModeChanged =
+ TASKBAR_PINNING_DESKTOP_MODE_KEY.equals(key)
+ && mInfo.mIsTaskbarPinnedInDesktopMode != prefs.get(
+ TASKBAR_PINNING_IN_DESKTOP_MODE);
+ if (isTaskbarPinningChanged || isTaskbarPinningDesktopModeChanged) {
handleInfoChange(mWindowContext.getDisplay());
}
};
LauncherPrefs.get(context).addListener(
mTaskbarPinningPreferenceChangeListener, TASKBAR_PINNING);
+ LauncherPrefs.get(context).addListener(
+ mTaskbarPinningPreferenceChangeListener, TASKBAR_PINNING_IN_DESKTOP_MODE);
}
/**
@@ -172,6 +181,13 @@
}
/**
+ * Handles info change for desktop mode.
+ */
+ public static void handleInfoChangeForDesktopMode(Context context) {
+ INSTANCE.get(context).handleInfoChange(context.getDisplay());
+ }
+
+ /**
* Enables transient taskbar status for tests.
*/
@VisibleForTesting
@@ -192,6 +208,8 @@
if (enableTaskbarPinning()) {
LauncherPrefs.get(mContext).removeListener(
mTaskbarPinningPreferenceChangeListener, TASKBAR_PINNING);
+ LauncherPrefs.get(mContext).removeListener(
+ mTaskbarPinningPreferenceChangeListener, TASKBAR_PINNING_IN_DESKTOP_MODE);
}
if (mWindowContext != null) {
mWindowContext.unregisterComponentCallbacks(this);
@@ -309,9 +327,15 @@
FileLog.w(TAG,
"(CHANGE_SUPPORTED_BOUNDS) perDisplayBounds: " + newInfo.mPerDisplayBounds);
}
- if (newInfo.mIsTaskbarPinned != oldInfo.mIsTaskbarPinned) {
+ if ((newInfo.mIsTaskbarPinned != oldInfo.mIsTaskbarPinned)
+ || (newInfo.mIsTaskbarPinnedInDesktopMode
+ != oldInfo.mIsTaskbarPinnedInDesktopMode)) {
change |= CHANGE_TASKBAR_PINNING;
}
+ if (newInfo.mIsInDesktopMode != oldInfo.mIsInDesktopMode) {
+ change |= CHANGE_DESKTOP_MODE;
+ }
+
if (DEBUG) {
Log.d(TAG, "handleInfoChange - change: " + getChangeFlagsString(change));
}
@@ -355,6 +379,9 @@
new ArrayMap<>();
private final boolean mIsTaskbarPinned;
+ private final boolean mIsTaskbarPinnedInDesktopMode;
+
+ private final boolean mIsInDesktopMode;
public Info(Context displayInfoContext) {
/* don't need system overrides for external displays */
@@ -414,6 +441,9 @@
}
mIsTaskbarPinned = LauncherPrefs.get(displayInfoContext).get(TASKBAR_PINNING);
+ mIsTaskbarPinnedInDesktopMode = LauncherPrefs.get(displayInfoContext).get(
+ TASKBAR_PINNING_IN_DESKTOP_MODE);
+ mIsInDesktopMode = wmProxy.isInDesktopMode();
}
/**
@@ -430,10 +460,14 @@
return sTransientTaskbarStatusForTests;
}
if (enableTaskbarPinning()) {
+ if (mIsInDesktopMode) {
+ return !mIsTaskbarPinnedInDesktopMode;
+ }
return !mIsTaskbarPinned;
}
return true;
}
+
/**
* Returns whether the taskbar is pinned in gesture navigation mode.
*/
@@ -441,6 +475,10 @@
return navigationMode == NavigationMode.NO_BUTTON && !isTransientTaskbar();
}
+ public boolean isInDesktopMode() {
+ return mIsInDesktopMode;
+ }
+
/**
* Returns {@code true} if the bounds represent a tablet.
*/
@@ -501,6 +539,7 @@
appendFlag(result, change, CHANGE_SUPPORTED_BOUNDS, "CHANGE_SUPPORTED_BOUNDS");
appendFlag(result, change, CHANGE_NAVIGATION_MODE, "CHANGE_NAVIGATION_MODE");
appendFlag(result, change, CHANGE_TASKBAR_PINNING, "CHANGE_TASKBAR_VARIANT");
+ appendFlag(result, change, CHANGE_DESKTOP_MODE, "CHANGE_DESKTOP_MODE");
return result.toString();
}
@@ -516,6 +555,8 @@
pw.println(" densityDpi=" + info.densityDpi);
pw.println(" navigationMode=" + info.navigationMode.name());
pw.println(" isTaskbarPinned=" + info.mIsTaskbarPinned);
+ pw.println(" isTaskbarPinnedInDesktopMode=" + info.mIsTaskbarPinnedInDesktopMode);
+ pw.println(" isInDesktopMode=" + info.mIsInDesktopMode);
pw.println(" currentSize=" + info.currentSize);
info.mPerDisplayBounds.forEach((key, value) -> pw.println(
" perDisplayBounds - " + key + ": " + value));
diff --git a/src/com/android/launcher3/util/window/WindowManagerProxy.java b/src/com/android/launcher3/util/window/WindowManagerProxy.java
index 4b004f3..0817c0a 100644
--- a/src/com/android/launcher3/util/window/WindowManagerProxy.java
+++ b/src/com/android/launcher3/util/window/WindowManagerProxy.java
@@ -115,6 +115,13 @@
}
/**
+ * Returns if we are in desktop mode or not.
+ */
+ public boolean isInDesktopMode() {
+ return false;
+ }
+
+ /**
* Returns the real bounds for the provided display after applying any insets normalization
*/
public WindowBounds getRealBounds(Context displayInfoContext, CachedDisplayInfo info) {
diff --git a/src/com/android/launcher3/views/ClipIconView.java b/src/com/android/launcher3/views/ClipIconView.java
index 172f968..325c1cd 100644
--- a/src/com/android/launcher3/views/ClipIconView.java
+++ b/src/com/android/launcher3/views/ClipIconView.java
@@ -40,6 +40,7 @@
import android.view.ViewOutlineProvider;
import androidx.annotation.Nullable;
+import androidx.core.util.Consumer;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.R;
@@ -74,6 +75,8 @@
private final Rect mOutline = new Rect();
private final Rect mFinalDrawableBounds = new Rect();
+ @Nullable private TaskViewArtist mTaskViewArtist;
+
public ClipIconView(Context context) {
this(context, null);
}
@@ -90,10 +93,28 @@
}
/**
+ * Sets a {@link TaskViewArtist} that will draw a {@link com.android.quickstep.views.TaskView}
+ * within the clip bounds of this view.
+ */
+ public void setTaskViewArtist(TaskViewArtist taskViewArtist) {
+ mTaskViewArtist = taskViewArtist;
+ invalidate();
+ }
+
+ /**
* Update the icon UI to match the provided parameters during an animation frame
*/
public void update(RectF rect, float progress, float shapeProgressStart, float cornerRadius,
boolean isOpening, View container, DeviceProfile dp) {
+ update(rect, progress, shapeProgressStart, cornerRadius, isOpening, container, dp, 255);
+ }
+
+ /**
+ * Update the icon UI to match the provided parameters during an animation frame, optionally
+ * varying the alpha of the {@link TaskViewArtist}
+ */
+ public void update(RectF rect, float progress, float shapeProgressStart, float cornerRadius,
+ boolean isOpening, View container, DeviceProfile dp, int taskViewDrawAlpha) {
MarginLayoutParams lp = (MarginLayoutParams) container.getLayoutParams();
float dX = mIsRtl
@@ -107,6 +128,14 @@
float scaleX = rect.width() / minSize;
float scaleY = rect.height() / minSize;
float scale = Math.max(1f, Math.min(scaleX, scaleY));
+ if (mTaskViewArtist != null) {
+ mTaskViewArtist.taskViewDrawWidth = lp.width;
+ mTaskViewArtist.taskViewDrawHeight = lp.height;
+ mTaskViewArtist.taskViewDrawAlpha = taskViewDrawAlpha;
+ mTaskViewArtist.taskViewDrawScale = (mTaskViewArtist.drawForPortraitLayout
+ ? Math.min(lp.height, lp.width) : Math.max(lp.height, lp.width))
+ / mTaskViewArtist.taskViewMinSize;
+ }
if (Float.isNaN(scale) || Float.isInfinite(scale)) {
// Views are no longer laid out, do not update.
@@ -287,6 +316,19 @@
if (mForeground != null) {
mForeground.draw(canvas);
}
+ if (mTaskViewArtist != null) {
+ canvas.saveLayerAlpha(
+ 0,
+ 0,
+ mTaskViewArtist.taskViewDrawWidth,
+ mTaskViewArtist.taskViewDrawHeight,
+ mTaskViewArtist.taskViewDrawAlpha);
+ float drawScale = mTaskViewArtist.taskViewDrawScale;
+ canvas.translate(drawScale * mTaskViewArtist.taskViewTranslationX,
+ drawScale * mTaskViewArtist.taskViewTranslationY);
+ canvas.scale(drawScale, drawScale);
+ mTaskViewArtist.taskViewDrawCallback.accept(canvas);
+ }
canvas.restoreToCount(count);
}
@@ -303,5 +345,37 @@
mRevealAnimator = null;
mTaskCornerRadius = 0;
mOutline.setEmpty();
+ mTaskViewArtist = null;
+ }
+
+ /**
+ * Utility class to help draw a {@link com.android.quickstep.views.TaskView} within
+ * a {@link ClipIconView} bounds.
+ */
+ public static class TaskViewArtist {
+
+ public final Consumer<Canvas> taskViewDrawCallback;
+ public final float taskViewTranslationX;
+ public final float taskViewTranslationY;
+ public final float taskViewMinSize;
+ public final boolean drawForPortraitLayout;
+
+ public int taskViewDrawAlpha;
+ public float taskViewDrawScale;
+ public int taskViewDrawWidth;
+ public int taskViewDrawHeight;
+
+ public TaskViewArtist(
+ Consumer<Canvas> taskViewDrawCallback,
+ float taskViewTranslationX,
+ float taskViewTranslationY,
+ float taskViewMinSize,
+ boolean drawForPortraitLayout) {
+ this.taskViewDrawCallback = taskViewDrawCallback;
+ this.taskViewTranslationX = taskViewTranslationX;
+ this.taskViewTranslationY = taskViewTranslationY;
+ this.taskViewMinSize = taskViewMinSize;
+ this.drawForPortraitLayout = drawForPortraitLayout;
+ }
}
}
diff --git a/src/com/android/launcher3/views/FloatingIconView.java b/src/com/android/launcher3/views/FloatingIconView.java
index f560311..0d07f63 100644
--- a/src/com/android/launcher3/views/FloatingIconView.java
+++ b/src/com/android/launcher3/views/FloatingIconView.java
@@ -55,7 +55,6 @@
import com.android.launcher3.graphics.PreloadIconDrawable;
import com.android.launcher3.icons.FastBitmapDrawable;
import com.android.launcher3.icons.LauncherIcons;
-import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.ItemInfoWithIcon;
import com.android.launcher3.popup.SystemShortcut;
@@ -146,18 +145,28 @@
}
/**
- * Positions this view to match the size and location of {@param rect}.
- * @param alpha The alpha[0, 1] of the entire floating view.
- * @param progress A value from [0, 1] that represents the animation progress.
- * @param shapeProgressStart The progress value at which to start the shape reveal.
- * @param cornerRadius The corner radius of {@param rect}.
- * @param isOpening True if view is used for app open animation, false for app close animation.
+ * Positions this view to match the size and location of {@code rect}.
*/
public void update(float alpha, RectF rect, float progress, float shapeProgressStart,
float cornerRadius, boolean isOpening) {
- setAlpha(alpha);
+ update(alpha, rect, progress, shapeProgressStart, cornerRadius, isOpening, 0);
+ }
+
+ /**
+ * Positions this view to match the size and location of {@code rect}.
+ * <p>
+ * @param alpha The alpha[0, 1] of the entire floating view.
+ * @param progress A value from [0, 1] that represents the animation progress.
+ * @param shapeProgressStart The progress value at which to start the shape reveal.
+ * @param cornerRadius The corner radius of {@code rect}.
+ * @param isOpening True if view is used for app open animation, false for app close animation.
+ * @param taskViewDrawAlpha the drawn {@link com.android.quickstep.views.TaskView} alpha
+ */
+ public void update(float alpha, RectF rect, float progress, float shapeProgressStart,
+ float cornerRadius, boolean isOpening, int taskViewDrawAlpha) {
+ setAlpha(isLaidOut() ? alpha : 0f);
mClipIconView.update(rect, progress, shapeProgressStart, cornerRadius, isOpening, this,
- mLauncher.getDeviceProfile());
+ mLauncher.getDeviceProfile(), taskViewDrawAlpha);
if (mFadeOutView != null) {
// The alpha goes from 1 to 0 when progress is 0 and 0.33 respectively.
@@ -165,6 +174,14 @@
}
}
+ /**
+ * Sets a {@link com.android.quickstep.views.TaskView} that will draw a
+ * {@link com.android.quickstep.views.TaskView} within the {@code mClipIconView} clip bounds
+ */
+ public void setOverlayArtist(ClipIconView.TaskViewArtist taskViewArtist) {
+ mClipIconView.setTaskViewArtist(taskViewArtist);
+ }
+
@Override
public void onAnimationEnd(Animator animator) {
if (mLoadIconSignal != null) {
@@ -179,8 +196,8 @@
}
/**
- * Sets the size and position of this view to match {@param v}.
- *
+ * Sets the size and position of this view to match {@code v}.
+ * <p>
* @param v The view to copy
* @param positionOut Rect that will hold the size and position of v.
*/
@@ -254,10 +271,11 @@
/**
* Loads the icon and saves the results to {@link #sIconLoadResult}.
+ * <p>
* Runs onIconLoaded callback (if any), which signifies that the FloatingIconView is
* ready to display the icon. Otherwise, the FloatingIconView will grab the results when its
* initialized.
- *
+ * <p>
* @param originalView The View that the FloatingIconView will replace.
* @param info ItemInfo of the originalView
* @param pos The position of the view.
@@ -324,8 +342,8 @@
}
/**
- * Sets the drawables of the {@param originalView} onto this view.
- *
+ * Sets the drawables of the {@code originalView} onto this view.
+ * <p>
* @param drawable The drawable of the original view.
* @param badge The badge of the original view.
* @param iconOffset The amount of offset needed to match this view with the original view.
@@ -368,11 +386,11 @@
/**
* Draws the drawable of the BubbleTextView behind ClipIconView
- *
+ * <p>
* This is used to:
* - Have icon displayed while Adaptive Icon is loading
* - Displays the built in shadow to ensure a clean handoff
- *
+ * <p>
* Allows nullable as this may be cleared when drawing is deferred to ClipIconView.
*/
private void setOriginalDrawableBackground(@Nullable Supplier<Drawable> btvIcon) {
@@ -573,11 +591,12 @@
}
/**
- * Creates a floating icon view for {@param originalView}.
+ * Creates a floating icon view for {@code originalView}.
+ * <p>
* @param originalView The view to copy
* @param visibilitySyncView A view whose visibility should update in sync with originalView.
* @param fadeOutView A view that will fade out as the animation progresses.
- * @param hideOriginal If true, it will hide {@param originalView} while this view is visible.
+ * @param hideOriginal If true, it will hide {@code originalView} while this view is visible.
* Else, we will not draw anything in this view.
* @param positionOut Rect that will hold the size and position of v.
* @param isOpening True if this view replaces the icon for app open animation.
diff --git a/src/com/android/launcher3/widget/WidgetCell.java b/src/com/android/launcher3/widget/WidgetCell.java
index 6dcaf75..5dacfb0 100644
--- a/src/com/android/launcher3/widget/WidgetCell.java
+++ b/src/com/android/launcher3/widget/WidgetCell.java
@@ -29,6 +29,7 @@
import android.animation.TimeInterpolator;
import android.content.Context;
import android.graphics.Bitmap;
+import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.text.TextUtils;
import android.util.AttributeSet;
@@ -544,12 +545,47 @@
if (mIsShowingAddButton) return;
mIsShowingAddButton = true;
+ setupIconOrTextButton();
mWidgetAddButton.setOnClickListener(callback);
fadeThrough(/* hide= */ mWidgetTextContainer, /* show= */ mWidgetAddButton,
ADD_BUTTON_FADE_DURATION_MS, Interpolators.LINEAR);
}
/**
+ * Depending on the width of the cell, set up the add button to be icon-only or icon+text.
+ */
+ private void setupIconOrTextButton() {
+ String addText = getResources().getString(R.string.widget_add_button_label);
+ Rect textSize = new Rect();
+ mWidgetAddButton.getPaint().getTextBounds(addText, 0, addText.length(), textSize);
+ int startPadding = getResources()
+ .getDimensionPixelSize(R.dimen.widget_cell_add_button_start_padding);
+ int endPadding = getResources()
+ .getDimensionPixelSize(R.dimen.widget_cell_add_button_end_padding);
+ int drawableWidth = getResources()
+ .getDimensionPixelSize(R.dimen.widget_cell_add_button_drawable_width);
+ int drawablePadding = getResources()
+ .getDimensionPixelSize(R.dimen.widget_cell_add_button_drawable_padding);
+ int textButtonWidth = textSize.width() + startPadding + endPadding + drawableWidth
+ + drawablePadding;
+ if (textButtonWidth > getMeasuredWidth()) {
+ // Setup icon-only button
+ mWidgetAddButton.setText(null);
+ int startIconPadding = getResources()
+ .getDimensionPixelSize(R.dimen.widget_cell_add_icon_button_start_padding);
+ mWidgetAddButton.setPaddingRelative(/* start= */ startIconPadding, /* top= */ 0,
+ /* end= */ endPadding, /* bottom= */ 0);
+ mWidgetAddButton.setCompoundDrawablePadding(0);
+ } else {
+ // Setup icon + text button
+ mWidgetAddButton.setText(addText);
+ mWidgetAddButton.setPaddingRelative(/* start= */ startPadding, /* top= */ 0,
+ /* end= */ endPadding, /* bottom= */ 0);
+ mWidgetAddButton.setCompoundDrawablePadding(drawablePadding);
+ }
+ }
+
+ /**
* Hide tap to add button.
*/
public void hideAddButton(boolean animate) {
diff --git a/src/com/android/launcher3/widget/picker/WidgetsRecommendationTableLayout.java b/src/com/android/launcher3/widget/picker/WidgetsRecommendationTableLayout.java
index 03af0cb..6dbad5c 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsRecommendationTableLayout.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsRecommendationTableLayout.java
@@ -58,12 +58,13 @@
public WidgetsRecommendationTableLayout(Context context, AttributeSet attrs) {
super(context, attrs);
- // There are 1 row for title, 1 row for dimension and 2 rows for description.
+ // There are 1 row for title, 1 row for dimension and max 3 rows for description.
mWidgetsRecommendationTableVerticalPadding = 2 * getResources()
.getDimensionPixelSize(R.dimen.widget_recommendations_table_vertical_padding);
mWidgetCellVerticalPadding = 2 * getResources()
.getDimensionPixelSize(R.dimen.widget_cell_vertical_padding);
- mWidgetCellTextViewsHeight = 4 * getResources().getDimension(R.dimen.widget_cell_font_size);
+ mWidgetCellTextViewsHeight =
+ getResources().getDimension(R.dimen.widget_cell_title_line_height);
}
/** Sets a {@link android.view.View.OnLongClickListener} for all widget cells in this table. */
diff --git a/tests/Launcher3Tests.xml b/tests/Launcher3Tests.xml
index 29c34be..270a610 100644
--- a/tests/Launcher3Tests.xml
+++ b/tests/Launcher3Tests.xml
@@ -44,6 +44,8 @@
<option name="run-command" value="settings put global airplane_mode_on 1" />
<option name="run-command" value="am broadcast -a android.intent.action.AIRPLANE_MODE" />
+ <option name="run-command" value="setprop debug.wm.disable_deprecated_target_sdk_dialog 1"/>
+
<option name="run-command" value="settings put system pointer_location 1" />
<option name="run-command" value="settings put system show_touches 1" />
</target_preparer>
diff --git a/tests/multivalentTests/shared/com/android/launcher3/testing/shared/TestProtocol.java b/tests/multivalentTests/shared/com/android/launcher3/testing/shared/TestProtocol.java
index 108db6c..8c47332 100644
--- a/tests/multivalentTests/shared/com/android/launcher3/testing/shared/TestProtocol.java
+++ b/tests/multivalentTests/shared/com/android/launcher3/testing/shared/TestProtocol.java
@@ -179,7 +179,6 @@
public static final String WIDGET_CONFIG_NULL_EXTRA_INTENT = "b/324419890";
public static final String ACTIVITY_NOT_RESUMED_AFTER_BACK = "b/322823209";
public static final String OVERVIEW_SELECT_TOOLTIP_MISALIGNED = "b/332485341";
-
public static final String REQUEST_EMULATE_DISPLAY = "emulate-display";
public static final String REQUEST_STOP_EMULATE_DISPLAY = "stop-emulate-display";
public static final String REQUEST_IS_EMULATE_DISPLAY_RUNNING = "is-emulate-display-running";
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/LauncherModelHelper.java b/tests/multivalentTests/src/com/android/launcher3/util/LauncherModelHelper.java
index 002f496..f18c02b 100644
--- a/tests/multivalentTests/src/com/android/launcher3/util/LauncherModelHelper.java
+++ b/tests/multivalentTests/src/com/android/launcher3/util/LauncherModelHelper.java
@@ -44,15 +44,12 @@
import android.test.mock.MockContentResolver;
import android.util.ArrayMap;
-import androidx.annotation.NonNull;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.uiautomator.UiDevice;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherModel;
-import com.android.launcher3.LauncherModel.ModelUpdateTask;
-import com.android.launcher3.model.AllAppsList;
import com.android.launcher3.model.BgDataModel;
import com.android.launcher3.model.BgDataModel.Callbacks;
import com.android.launcher3.testing.TestInformationProvider;
@@ -66,7 +63,6 @@
import java.util.UUID;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Executor;
/**
* Utility class to help manage Launcher Model and related objects for test.
@@ -115,17 +111,9 @@
public synchronized BgDataModel getBgDataModel() {
if (mDataModel == null) {
- getModel().enqueueModelUpdateTask(new ModelUpdateTask() {
- @Override
- public void init(@NonNull LauncherAppState app, @NonNull LauncherModel model,
- @NonNull BgDataModel dataModel, @NonNull AllAppsList allAppsList,
- @NonNull Executor uiExecutor) {
- mDataModel = dataModel;
- }
-
- @Override
- public void run() { }
- });
+ getModel().enqueueModelUpdateTask((taskController, dataModel, apps) ->
+ mDataModel = dataModel);
+ runOnExecutorSync(Executors.MODEL_EXECUTOR, () -> { });
}
return mDataModel;
}
diff --git a/tests/src/com/android/launcher3/AbstractDeviceProfileTest.kt b/tests/src/com/android/launcher3/AbstractDeviceProfileTest.kt
index aefc2db..e378733 100644
--- a/tests/src/com/android/launcher3/AbstractDeviceProfileTest.kt
+++ b/tests/src/com/android/launcher3/AbstractDeviceProfileTest.kt
@@ -306,6 +306,7 @@
context.putObject(LauncherPrefs.INSTANCE, launcherPrefs)
whenever(launcherPrefs.get(LauncherPrefs.TASKBAR_PINNING)).thenReturn(false)
+ whenever(launcherPrefs.get(LauncherPrefs.TASKBAR_PINNING_IN_DESKTOP_MODE)).thenReturn(true)
val info = spy(DisplayController.Info(context, windowManagerProxy, perDisplayBoundsCache))
whenever(displayController.info).thenReturn(info)
whenever(info.isTransientTaskbar).thenReturn(isGestureMode)
diff --git a/tests/src/com/android/launcher3/celllayout/ReorderAlgorithmUnitTest.java b/tests/src/com/android/launcher3/celllayout/ReorderAlgorithmUnitTest.java
index 0ff7c20..8a9711d 100644
--- a/tests/src/com/android/launcher3/celllayout/ReorderAlgorithmUnitTest.java
+++ b/tests/src/com/android/launcher3/celllayout/ReorderAlgorithmUnitTest.java
@@ -215,9 +215,11 @@
testCase.spanX, testCase.spanY, testCase.minSpanX, testCase.minSpanY,
isMultiCellLayout);
assertEquals("should be a valid solution", solution.isSolution, testCase.isValidSolution);
+ Log.d(TAG, "test case:" + testCase);
if (testCase.isValidSolution) {
CellLayoutBoard finishBoard = boardFromSolution(solution,
testCase.startBoard.getWidth(), testCase.startBoard.getHeight());
+ Log.d(TAG, "finishBoard case:" + finishBoard);
assertTrue("End result and test case result board doesn't match ",
finishBoard.compareTo(testCase.endBoard) == 0);
}
diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
index 99e15ba..115a6e6 100644
--- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
+++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
@@ -98,7 +98,7 @@
public abstract class AbstractLauncherUiTest<LAUNCHER_TYPE extends Launcher> {
public static final long DEFAULT_ACTIVITY_TIMEOUT = TimeUnit.SECONDS.toMillis(10);
- public static final long DEFAULT_BROADCAST_TIMEOUT_SECS = 5;
+ public static final long DEFAULT_BROADCAST_TIMEOUT_SECS = 10;
public static final long DEFAULT_UI_TIMEOUT = TestUtil.DEFAULT_UI_TIMEOUT;
private static final String TAG = "AbstractLauncherUiTest";
@@ -548,7 +548,7 @@
public Intent blockingGetIntent() throws InterruptedException {
Log.d(WIDGET_CONFIG_NULL_EXTRA_INTENT,
"AbstractLauncherUiTest.blockingGetIntent()");
- latch.await(DEFAULT_BROADCAST_TIMEOUT_SECS, TimeUnit.SECONDS);
+ assertTrue("Timed Out", latch.await(DEFAULT_BROADCAST_TIMEOUT_SECS, TimeUnit.SECONDS));
mTargetContext.unregisterReceiver(this);
Log.d(WIDGET_CONFIG_NULL_EXTRA_INTENT, mIntent == null
? "AbstractLauncherUiTest.onReceive(): mIntent NULL"
diff --git a/tests/src/com/android/launcher3/util/DisplayControllerTest.kt b/tests/src/com/android/launcher3/util/DisplayControllerTest.kt
index 2e57ad5..273f0c4 100644
--- a/tests/src/com/android/launcher3/util/DisplayControllerTest.kt
+++ b/tests/src/com/android/launcher3/util/DisplayControllerTest.kt
@@ -31,6 +31,7 @@
import androidx.test.filters.SmallTest
import com.android.launcher3.LauncherPrefs
import com.android.launcher3.LauncherPrefs.Companion.TASKBAR_PINNING
+import com.android.launcher3.LauncherPrefs.Companion.TASKBAR_PINNING_IN_DESKTOP_MODE
import com.android.launcher3.util.DisplayController.CHANGE_DENSITY
import com.android.launcher3.util.DisplayController.CHANGE_ROTATION
import com.android.launcher3.util.DisplayController.CHANGE_TASKBAR_PINNING
@@ -94,6 +95,7 @@
whenever(context.getObject(eq(WindowManagerProxy.INSTANCE))).thenReturn(windowManagerProxy)
whenever(context.getObject(eq(LauncherPrefs.INSTANCE))).thenReturn(launcherPrefs)
whenever(launcherPrefs.get(TASKBAR_PINNING)).thenReturn(false)
+ whenever(launcherPrefs.get(TASKBAR_PINNING_IN_DESKTOP_MODE)).thenReturn(true)
// Mock WindowManagerProxy
val displayInfo = CachedDisplayInfo(Point(width, height), Surface.ROTATION_0)
@@ -170,4 +172,13 @@
verify(displayInfoChangeListener)
.onDisplayInfoChanged(any(), any(), eq(CHANGE_TASKBAR_PINNING))
}
+
+ @Test
+ @UiThreadTest
+ fun testTaskbarPinningChangeInDesktopMode() {
+ whenever(launcherPrefs.get(TASKBAR_PINNING_IN_DESKTOP_MODE)).thenReturn(false)
+ displayController.handleInfoChange(display)
+ verify(displayInfoChangeListener)
+ .onDisplayInfoChanged(any(), any(), eq(CHANGE_TASKBAR_PINNING))
+ }
}
diff --git a/tests/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinderTest.java b/tests/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinderTest.java
index 8fc4481..d4e061a 100644
--- a/tests/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinderTest.java
+++ b/tests/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinderTest.java
@@ -31,6 +31,7 @@
import android.content.Context;
import android.graphics.Bitmap;
import android.os.UserHandle;
+import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
import android.widget.FrameLayout;
import android.widget.TextView;
@@ -79,7 +80,8 @@
public void setUp() {
MockitoAnnotations.initMocks(this);
- mContext = new ActivityContextWrapper(getApplicationContext());
+ mContext = new ActivityContextWrapper(new ContextThemeWrapper(getApplicationContext(),
+ R.style.WidgetContainerTheme));
mTestProfile = new InvariantDeviceProfile();
mTestProfile.numRows = 5;
mTestProfile.numColumns = 5;
diff --git a/tests/tapl/com/android/launcher3/tapl/AllApps.java b/tests/tapl/com/android/launcher3/tapl/AllApps.java
index b490124..f3ec852 100644
--- a/tests/tapl/com/android/launcher3/tapl/AllApps.java
+++ b/tests/tapl/com/android/launcher3/tapl/AllApps.java
@@ -433,7 +433,7 @@
public PrivateSpaceContainer getPrivateSpaceUnlockedView() {
final UiObject2 allAppsContainer = verifyActiveContainer();
final UiObject2 appListRecycler = getAppListRecycler(allAppsContainer);
- return new PrivateSpaceContainer(mLauncher, appListRecycler);
+ return new PrivateSpaceContainer(mLauncher, appListRecycler, this);
}
protected abstract void verifyVisibleContainerOnDismiss();
diff --git a/tests/tapl/com/android/launcher3/tapl/PrivateSpaceContainer.java b/tests/tapl/com/android/launcher3/tapl/PrivateSpaceContainer.java
index daddd2f..0e65ffb 100644
--- a/tests/tapl/com/android/launcher3/tapl/PrivateSpaceContainer.java
+++ b/tests/tapl/com/android/launcher3/tapl/PrivateSpaceContainer.java
@@ -30,11 +30,13 @@
private final LauncherInstrumentation mLauncher;
private final UiObject2 mAppListRecycler;
+ private final AllApps mAppList;
PrivateSpaceContainer(LauncherInstrumentation launcherInstrumentation,
- UiObject2 appListRecycler) {
+ UiObject2 appListRecycler, AllApps appList) {
mLauncher = launcherInstrumentation;
mAppListRecycler = appListRecycler;
+ mAppList = appList;
verifyHeaderIsPresent();
verifyInstallAppButtonIsPresent();
@@ -52,7 +54,7 @@
// Assert Install App Item is present in view.
private void verifyInstallAppButtonIsPresent() {
- mLauncher.getAllApps().getAppIcon(INSTALL_APP_TITLE);
+ mAppList.getAppIcon(INSTALL_APP_TITLE);
}
// Assert Sys App Divider is present in view.
@@ -64,7 +66,7 @@
* Verifies that a user installed app is present above the divider.
*/
public void verifyInstalledAppIsPresent(String appName) {
- HomeAppIcon appIcon = mLauncher.getAllApps().getAppIcon(appName);
+ AppIcon appIcon = mAppList.getAppIcon(appName);
final Point iconCenter = appIcon.mObject.getVisibleCenter();
UiObject2 divider = mLauncher.waitForObjectInContainer(mAppListRecycler, DIVIDER_RES_ID);
final Point dividerCenter = divider.getVisibleCenter();