Merge "Clean up some old (pre-aconfig) flags." into main
diff --git a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
index 89dfff3..81581b8 100644
--- a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
@@ -45,6 +45,7 @@
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SHORTCUT_HELPER_SHOWING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING;
import android.animation.ArgbEvaluator;
@@ -134,6 +135,7 @@
private static final int FLAG_VOICE_INTERACTION_WINDOW_SHOWING = 1 << 12;
private static final int FLAG_SMALL_SCREEN = 1 << 13;
private static final int FLAG_SLIDE_IN_VIEW_VISIBLE = 1 << 14;
+ private static final int FLAG_KEYBOARD_SHORTCUT_HELPER_SHOWING = 1 << 15;
/**
* Flags where a UI could be over Taskbar surfaces, so the color override should be disabled.
@@ -286,8 +288,9 @@
// - Notification shade is expanded
// - IME is showing (add separate translation for IME)
// - VoiceInteractionWindow (assistant) is showing
+ // - Keyboard shortcuts helper is showing
int flagsToRemoveTranslation = FLAG_NOTIFICATION_SHADE_EXPANDED | FLAG_IME_VISIBLE
- | FLAG_VOICE_INTERACTION_WINDOW_SHOWING;
+ | FLAG_VOICE_INTERACTION_WINDOW_SHOWING | FLAG_KEYBOARD_SHORTCUT_HELPER_SHOWING;
mPropertyHolders.add(new StatePropertyHolder(mNavButtonInAppDisplayProgressForSysui,
flags -> (flags & flagsToRemoveTranslation) != 0, AnimatedFloat.VALUE,
1, 0));
@@ -458,6 +461,8 @@
boolean isScreenPinningActive = (sysUiStateFlags & SYSUI_STATE_SCREEN_PINNING) != 0;
boolean isVoiceInteractionWindowShowing =
(sysUiStateFlags & SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING) != 0;
+ boolean isKeyboardShortcutHelperShowing =
+ (sysUiStateFlags & SYSUI_STATE_SHORTCUT_HELPER_SHOWING) != 0;
// TODO(b/202218289) we're getting IME as not visible on lockscreen from system
updateStateForFlag(FLAG_IME_VISIBLE, isImeVisible);
@@ -469,6 +474,7 @@
updateStateForFlag(FLAG_NOTIFICATION_SHADE_EXPANDED, isNotificationShadeExpanded);
updateStateForFlag(FLAG_SCREEN_PINNING_ACTIVE, isScreenPinningActive);
updateStateForFlag(FLAG_VOICE_INTERACTION_WINDOW_SHOWING, isVoiceInteractionWindowShowing);
+ updateStateForFlag(FLAG_KEYBOARD_SHORTCUT_HELPER_SHOWING, isKeyboardShortcutHelperShowing);
if (mA11yButton != null) {
// Only used in 3 button
@@ -1056,6 +1062,8 @@
appendFlag(str, flags, FLAG_SCREEN_PINNING_ACTIVE, "FLAG_SCREEN_PINNING_ACTIVE");
appendFlag(str, flags, FLAG_VOICE_INTERACTION_WINDOW_SHOWING,
"FLAG_VOICE_INTERACTION_WINDOW_SHOWING");
+ appendFlag(str, flags, FLAG_KEYBOARD_SHORTCUT_HELPER_SHOWING,
+ "FLAG_KEYBOARD_SHORTCUT_HELPER_SHOWING");
return str.toString();
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
index 400c3ab..46c2e25 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
@@ -67,7 +67,6 @@
import com.android.launcher3.icons.BitmapInfo;
import com.android.launcher3.icons.BubbleIconFactory;
import com.android.launcher3.shortcuts.ShortcutRequest;
-import com.android.launcher3.taskbar.TaskbarControllers;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.Executors.SimpleThreadFactory;
import com.android.quickstep.SystemUiProxy;
@@ -148,6 +147,7 @@
private BubbleBarItem mSelectedBubble;
private BubbleBarOverflow mOverflowBubble;
+ private ImeVisibilityChecker mImeVisibilityChecker;
private BubbleBarViewController mBubbleBarViewController;
private BubbleStashController mBubbleStashController;
private BubbleStashedHandleViewController mBubbleStashedHandleViewController;
@@ -216,7 +216,10 @@
mSystemUiProxy.setBubblesListener(null);
}
- public void init(TaskbarControllers controllers, BubbleControllers bubbleControllers) {
+ /** Initializes controllers. */
+ public void init(BubbleControllers bubbleControllers,
+ ImeVisibilityChecker imeVisibilityChecker) {
+ mImeVisibilityChecker = imeVisibilityChecker;
mBubbleBarViewController = bubbleControllers.bubbleBarViewController;
mBubbleStashController = bubbleControllers.bubbleStashController;
mBubbleStashedHandleViewController = bubbleControllers.bubbleStashedHandleViewController;
@@ -321,9 +324,9 @@
// enabling gesture nav. also suppress animation if the bubble bar is hidden for sysui e.g.
// the shade is open, or we're locked.
final boolean suppressAnimation =
- update.initialState || mBubbleBarViewController.isHiddenForSysui();
+ update.initialState || mBubbleBarViewController.isHiddenForSysui()
+ || mImeVisibilityChecker.isImeVisible();
- BubbleBarItem previouslySelectedBubble = mSelectedBubble;
BubbleBarBubble bubbleToSelect = null;
if (!update.removedBubbles.isEmpty()) {
for (int i = 0; i < update.removedBubbles.size(); i++) {
@@ -379,6 +382,7 @@
BubbleBarBubble bb = mBubbles.get(update.updatedBubble.getKey());
// If we're not stashed, we're visible so animate
bb.getView().updateDotVisibility(!mBubbleStashController.isStashed() /* animate */);
+ mBubbleBarViewController.animateBubbleNotification(bb, /* isExpanding= */ false);
}
if (update.bubbleKeysInOrder != null && !update.bubbleKeysInOrder.isEmpty()) {
// Create the new list
@@ -667,4 +671,10 @@
displayBounds.bottom = displaySize.y - translation;
return displayBounds;
}
+
+ /** Interface for checking whether the IME is visible. */
+ public interface ImeVisibilityChecker {
+ /** Whether the IME is visible. */
+ boolean isImeVisible();
+ }
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
index fbdb2ed..0722e46 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
@@ -414,31 +414,31 @@
b.getView().setOnClickListener(mBubbleClickListener);
mBubbleDragController.setupBubbleView(b.getView());
- if (suppressAnimation) {
+ if (suppressAnimation || !(b instanceof BubbleBarBubble bubble)) {
return;
}
-
- if (!(b instanceof BubbleBarBubble bubble)) {
- return;
- }
-
- boolean isInApp = mTaskbarStashController.isInApp();
- // if this is the first bubble, animate to the initial state. one bubble is the overflow
- // so check for at most 2 children.
- if (mBarView.getChildCount() <= 2) {
- mBubbleBarViewAnimator.animateToInitialState(bubble, isInApp, isExpanding);
- return;
- }
-
- // only animate the new bubble if we're in an app and not auto expanding
- if (isInApp && !isExpanding && !isExpanded()) {
- mBubbleBarViewAnimator.animateBubbleInForStashed(bubble);
- }
+ animateBubbleNotification(bubble, isExpanding);
} else {
Log.w(TAG, "addBubble, bubble was null!");
}
}
+ /** Animates the bubble bar to notify the user about a bubble change. */
+ public void animateBubbleNotification(BubbleBarBubble bubble, boolean isExpanding) {
+ boolean isInApp = mTaskbarStashController.isInApp();
+ // if this is the first bubble, animate to the initial state. one bubble is the overflow
+ // so check for at most 2 children.
+ if (mBarView.getChildCount() <= 2) {
+ mBubbleBarViewAnimator.animateToInitialState(bubble, isInApp, isExpanding);
+ return;
+ }
+
+ // only animate the new bubble if we're in an app and not auto expanding
+ if (isInApp && !isExpanding && !isExpanded()) {
+ mBubbleBarViewAnimator.animateBubbleInForStashed(bubble);
+ }
+ }
+
/**
* Reorders the bubbles based on the provided list.
*/
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleControllers.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleControllers.java
index 295477c..32d6375 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleControllers.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleControllers.java
@@ -64,7 +64,8 @@
* in constructors for now, as some controllers may still be waiting for init().
*/
public void init(TaskbarControllers taskbarControllers) {
- bubbleBarController.init(taskbarControllers, this);
+ bubbleBarController.init(this,
+ taskbarControllers.navbarButtonsViewController::isImeVisible);
bubbleBarViewController.init(taskbarControllers, this);
bubbleStashedHandleViewController.init(taskbarControllers, this);
bubbleStashController.init(taskbarControllers, this);
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index efcc7d3..c01581f 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -197,6 +197,8 @@
import com.android.systemui.unfold.progress.RemoteUnfoldTransitionReceiver;
import com.android.systemui.unfold.updates.RotationChangeProvider;
+import kotlin.Unit;
+
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -586,6 +588,7 @@
} else {
getStateManager().moveToRestState();
}
+ return Unit.INSTANCE;
});
} else {
getStateManager().goToState(NORMAL);
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 81f1090..10e327d 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -152,6 +152,8 @@
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.startingsurface.SplashScreenExitAnimationUtils;
+import kotlin.Unit;
+
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
@@ -2332,6 +2334,7 @@
mRecentsAnimationController.finish(true /* toRecents */, null);
}
}
+ return Unit.INSTANCE;
}, true /* freezeTaskList */);
} else {
mContainerInterface.onLaunchTaskFailed();
diff --git a/quickstep/src/com/android/quickstep/DesktopModeStatus.java b/quickstep/src/com/android/quickstep/DesktopModeStatus.java
deleted file mode 100644
index b1aae16..0000000
--- a/quickstep/src/com/android/quickstep/DesktopModeStatus.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * 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;
-
-import android.content.Context;
-import android.os.SystemProperties;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.window.flags.Flags;
-
-// TODO(b/335401172): Explore unifying logic across core and shell
-public class DesktopModeStatus {
-
- /**
- * Flag to indicate whether to restrict desktop mode to supported devices.
- */
- private static final boolean ENFORCE_DEVICE_RESTRICTIONS = SystemProperties.getBoolean(
- "persist.wm.debug.desktop_mode_enforce_device_restrictions", true);
-
- /**
- * Return {@code true} if desktop mode should be restricted to supported devices.
- */
- @VisibleForTesting
- public static boolean enforceDeviceRestrictions() {
- return ENFORCE_DEVICE_RESTRICTIONS;
- }
-
- /**
- * Return {@code true} if the current device supports desktop mode.
- */
- @VisibleForTesting
- public static boolean isDesktopModeSupported(Context context) {
- return context.getResources().getBoolean(
- com.android.internal.R.bool.config_isDesktopModeSupported);
- }
-
- /**
- * Return {@code true} if desktop mode can be entered on the current device.
- */
- public static boolean canEnterDesktopMode(Context context) {
- return Flags.enableDesktopWindowingMode()
- && (!enforceDeviceRestrictions() || isDesktopModeSupported(context));
- }
-}
diff --git a/quickstep/src/com/android/quickstep/DesktopSystemShortcut.kt b/quickstep/src/com/android/quickstep/DesktopSystemShortcut.kt
index e33ef7f..fdf4574 100644
--- a/quickstep/src/com/android/quickstep/DesktopSystemShortcut.kt
+++ b/quickstep/src/com/android/quickstep/DesktopSystemShortcut.kt
@@ -24,6 +24,7 @@
import com.android.quickstep.views.RecentsView
import com.android.quickstep.views.RecentsViewContainer
import com.android.quickstep.views.TaskView.TaskContainer
+import com.android.wm.shell.shared.DesktopModeStatus
/** A menu item, "Desktop", that allows the user to bring the current app into Desktop Windowing. */
class DesktopSystemShortcut(
diff --git a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
index 6a9f509..080e03a 100644
--- a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
+++ b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
@@ -304,9 +304,7 @@
// Disable if swiping to PIP
return null;
}
- if (sourceTaskView == null
- || sourceTaskView.getFirstTask() == null
- || sourceTaskView.getFirstTask().key.getComponent() == null) {
+ if (sourceTaskView == null || sourceTaskView.getFirstTask().key.getComponent() == null) {
// Disable if it's an invalid task
return null;
}
diff --git a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
index 5c6e3f8..3091e3d 100644
--- a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
+++ b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
@@ -176,7 +176,7 @@
RunnableList callbackList = null;
if (taskView != null) {
mWaitForToggleCommandComplete = true;
- taskView.setEndQuickswitchCuj(true);
+ taskView.setEndQuickSwitchCuj(true);
callbackList = taskView.launchTasks();
}
diff --git a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
index a53d91f..537f432 100644
--- a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
@@ -42,10 +42,8 @@
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Flags;
import com.android.launcher3.R;
-import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.logging.StatsLogManager.LauncherEvent;
import com.android.launcher3.model.WellbeingModel;
-import com.android.launcher3.model.data.ItemInfoWithIcon;
import com.android.launcher3.popup.SystemShortcut;
import com.android.launcher3.popup.SystemShortcut.AppInfo;
import com.android.launcher3.util.InstantAppResolver;
@@ -463,8 +461,7 @@
boolean isGridOnlyOverview = isTablet && Flags.enableGridOnlyOverview();
// Extra conditions if it's not grid-only overview
if (!isGridOnlyOverview) {
- RecentsOrientedState orientedState =
- taskContainer.getTaskView().getRecentsView().getPagedViewOrientedState();
+ RecentsOrientedState orientedState = taskContainer.getTaskView().getOrientedState();
boolean isFakeLandscape = !orientedState.isRecentsActivityRotationAllowed()
&& orientedState.getTouchRotation() != ROTATION_0;
if (!isFakeLandscape) {
@@ -493,8 +490,7 @@
boolean isGridOnlyOverview = isTablet && Flags.enableGridOnlyOverview();
// Extra conditions if it's not grid-only overview
if (!isGridOnlyOverview) {
- RecentsOrientedState orientedState =
- taskContainer.getTaskView().getRecentsView().getPagedViewOrientedState();
+ RecentsOrientedState orientedState = taskContainer.getTaskView().getOrientedState();
boolean isFakeLandscape = !orientedState.isRecentsActivityRotationAllowed()
&& orientedState.getTouchRotation() != ROTATION_0;
if (!isFakeLandscape) {
diff --git a/quickstep/src/com/android/quickstep/TaskViewUtils.java b/quickstep/src/com/android/quickstep/TaskViewUtils.java
index d2560e6..ecd84f8 100644
--- a/quickstep/src/com/android/quickstep/TaskViewUtils.java
+++ b/quickstep/src/com/android/quickstep/TaskViewUtils.java
@@ -165,8 +165,8 @@
@NonNull RemoteAnimationTarget[] nonAppTargets,
@Nullable DepthController depthController,
PendingAnimation out) {
- boolean isQuickSwitch = v.isEndQuickswitchCuj();
- v.setEndQuickswitchCuj(false);
+ boolean isQuickSwitch = v.isEndQuickSwitchCuj();
+ v.setEndQuickSwitchCuj(false);
final RemoteAnimationTargets targets =
new RemoteAnimationTargets(appTargets, wallpaperTargets, nonAppTargets,
diff --git a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt
index b466f3f..8762976 100644
--- a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt
+++ b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt
@@ -46,7 +46,7 @@
RecentsViewContainer.containerFromContext<RecentsViewContainer>(context)
.getOverviewPanel<RecentsView<*, *>>()
.mRecentsViewData,
- (parent as TaskView).mTaskViewData
+ (parent as TaskView).taskViewData
)
}
diff --git a/quickstep/src/com/android/quickstep/util/BorderAnimator.kt b/quickstep/src/com/android/quickstep/util/BorderAnimator.kt
index 44eb070..85238ed 100644
--- a/quickstep/src/com/android/quickstep/util/BorderAnimator.kt
+++ b/quickstep/src/com/android/quickstep/util/BorderAnimator.kt
@@ -86,7 +86,7 @@
fun createSimpleBorderAnimator(
@Px borderRadiusPx: Int,
@Px borderWidthPx: Int,
- boundsBuilder: (rect: Rect?) -> Unit,
+ boundsBuilder: (Rect) -> Unit,
targetView: View,
@ColorInt borderColor: Int = DEFAULT_BORDER_COLOR,
appearanceDurationMs: Long = DEFAULT_APPEARANCE_ANIMATION_DURATION_MS,
@@ -250,7 +250,7 @@
/** BorderAnimationParams that simply draws the border outside the bounds of the target view. */
private class SimpleParams(
@Px borderWidthPx: Int,
- boundsBuilder: (rect: Rect?) -> Unit,
+ boundsBuilder: (Rect) -> Unit,
targetView: View,
) : BorderAnimationParams(borderWidthPx, boundsBuilder, targetView) {
override val alignmentAdjustmentInset = 0
diff --git a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
index 9da4985..49f4e5f 100644
--- a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
+++ b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
@@ -551,7 +551,7 @@
* TaskView
*/
public float getCurrentCornerRadius() {
- float visibleRadius = mCurrentFullscreenParams.mCurrentDrawnCornerRadius;
+ float visibleRadius = mCurrentFullscreenParams.getCurrentDrawnCornerRadius();
mTempPoint[0] = visibleRadius;
mTempPoint[1] = 0;
mInversePositionMatrix.mapVectors(mTempPoint);
diff --git a/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt b/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt
index 3565174..3935c67 100644
--- a/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt
+++ b/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt
@@ -27,34 +27,30 @@
import android.view.View
import android.view.ViewGroup
import androidx.core.view.updateLayoutParams
-import com.android.launcher3.LauncherState
import com.android.launcher3.R
-import com.android.launcher3.Utilities
import com.android.launcher3.util.CancellableTask
import com.android.launcher3.util.RunnableList
import com.android.launcher3.util.SplitConfigurationOptions
import com.android.launcher3.util.ViewPool
+import com.android.launcher3.util.rects.set
import com.android.quickstep.BaseContainerInterface
import com.android.quickstep.RecentsModel
+import com.android.quickstep.TaskOverlayFactory
import com.android.quickstep.util.RecentsOrientedState
import com.android.systemui.shared.recents.model.Task
import com.android.systemui.shared.recents.model.ThumbnailData
-import com.android.systemui.shared.system.QuickStepContract
-import java.util.function.Consumer
/** TaskView that contains all tasks that are part of the desktop. */
// TODO(b/249371338): TaskView needs to be refactored to have better support for N tasks.
-class DesktopTaskView @JvmOverloads constructor(context: Context?, attrs: AttributeSet? = null) :
+class DesktopTaskView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
TaskView(context, attrs) {
private val pendingThumbnailRequests = mutableListOf<CancellableTask<*>>()
private val snapshotDrawParams =
object : FullscreenDrawParams(context) {
+ // DesktopTaskView thumbnail's corner radius is independent of fullscreenProgress.
override fun computeTaskCornerRadius(context: Context) =
- QuickStepContract.getWindowCornerRadius(context)
-
- override fun computeWindowCornerRadius(context: Context) =
- QuickStepContract.getWindowCornerRadius(context)
+ computeWindowCornerRadius(context)
}
private val taskThumbnailViewPool =
ViewPool<TaskThumbnailViewDeprecated>(
@@ -69,15 +65,11 @@
private lateinit var backgroundView: View
private var childCountAtInflation = 0
- init {
- mTaskContainers = ArrayList()
- }
-
override fun onFinishInflate() {
super.onFinishInflate()
backgroundView = findViewById(R.id.background)!!
- val topMarginPx = mContainer.deviceProfile.overviewTaskThumbnailTopMarginPx
+ val topMarginPx = container.deviceProfile.overviewTaskThumbnailTopMarginPx
backgroundView.updateLayoutParams<LayoutParams> { topMargin = topMarginPx }
val outerRadii = FloatArray(8) { taskCornerRadius }
@@ -88,174 +80,25 @@
val iconBackground = resources.getDrawable(R.drawable.bg_circle, context.theme)
val icon = resources.getDrawable(R.drawable.ic_desktop, context.theme)
- setIcon(mIconView, LayerDrawable(arrayOf(iconBackground, icon)))
+ setIcon(iconView, LayerDrawable(arrayOf(iconBackground, icon)))
childCountAtInflation = childCount
}
- override fun getThumbnailBounds(bounds: Rect, relativeToDragLayer: Boolean) {
- if (relativeToDragLayer) {
- mContainer.dragLayer.getDescendantRectRelativeToSelf(backgroundView, bounds)
- } else {
- bounds.set(
- backgroundView.left,
- backgroundView.top,
- backgroundView.right,
- backgroundView.bottom
- )
- }
- }
-
- override fun bind(task: Task, orientedState: RecentsOrientedState) {
- bind(listOf(task), orientedState)
- }
-
- /** Updates this desktop task to the gives task list defined in `tasks` */
- fun bind(tasks: List<Task>, orientedState: RecentsOrientedState) {
- if (DEBUG) {
- val sb = StringBuilder()
- sb.append("bind tasks=").append(tasks.size).append("\n")
- tasks.forEach { sb.append(" key=${it.key}\n") }
- Log.d(TAG, sb.toString())
- }
- cancelPendingLoadTasks()
-
- (mTaskContainers as ArrayList).ensureCapacity(tasks.size)
- tasks.forEachIndexed { index, task ->
- val thumbnailView: TaskThumbnailViewDeprecated
- if (index >= mTaskContainers.size) {
- thumbnailView = taskThumbnailViewPool.view
- // Add thumbnailView from to position after the initial child views.
- addView(
- thumbnailView,
- childCountAtInflation,
- LayoutParams(
- ViewGroup.LayoutParams.WRAP_CONTENT,
- ViewGroup.LayoutParams.WRAP_CONTENT
- )
- )
- } else {
- thumbnailView = mTaskContainers[index].thumbnailView
- }
- thumbnailView.bind(task)
- val taskContainer =
- TaskContainer(
- task,
- thumbnailView,
- mIconView,
- SplitConfigurationOptions.STAGE_POSITION_UNDEFINED,
- null
- )
- if (index >= mTaskContainers.size) {
- mTaskContainers.add(taskContainer)
- } else {
- mTaskContainers[index] = taskContainer
- }
- }
- while (mTaskContainers.size > tasks.size) {
- mTaskContainers.removeLast().apply {
- removeView(thumbnailView)
- taskThumbnailViewPool.recycle(thumbnailView)
- }
- }
-
- setOrientationState(orientedState)
- }
-
- override fun onTaskListVisibilityChanged(visible: Boolean, changes: Int) {
- cancelPendingLoadTasks()
- if (needsUpdate(changes, FLAG_UPDATE_THUMBNAIL)) {
- mTaskContainers.forEach {
- if (visible) {
- RecentsModel.INSTANCE.get(context)
- .thumbnailCache
- .updateThumbnailInBackground(it.task) { thumbnailData: ThumbnailData ->
- it.thumbnailView.setThumbnail(it.task, thumbnailData)
- }
- ?.apply { pendingThumbnailRequests.add(this) }
- } else {
- it.thumbnailView.setThumbnail(null, null)
- // Reset the task thumbnail ref
- it.task.thumbnail = null
- }
- }
- }
- }
-
- // thumbnailView is laid out differently and is handled in onMeasure
- override fun setThumbnailOrientation(orientationState: RecentsOrientedState) {}
-
- override fun cancelPendingLoadTasks() {
- pendingThumbnailRequests.forEach { it.cancel() }
- pendingThumbnailRequests.clear()
- }
-
- override fun launchTaskAnimated(): RunnableList? {
- val recentsView = recentsView ?: return null
- val endCallback = RunnableList()
- val desktopController = recentsView.desktopRecentsController
- if (desktopController != null) {
- desktopController.launchDesktopFromRecents(this) { endCallback.executeAllAndDestroy() }
- Log.d(
- TAG,
- "launchTaskAnimated - launchDesktopFromRecents: ${taskIds.contentToString()}"
- )
- } else {
- Log.d(
- TAG,
- "launchTaskAnimated - recentsController is null: ${taskIds.contentToString()}"
- )
- }
-
- // Callbacks get run from recentsView for case when recents animation already running
- recentsView.addSideTaskLaunchCallback(endCallback)
- return endCallback
- }
-
- override fun launchTask(callback: Consumer<Boolean>, isQuickswitch: Boolean) {
- launchTasks()
- callback.accept(true)
- }
-
- public override fun refreshThumbnails(thumbnailDatas: HashMap<Int, ThumbnailData?>?) {
- // Sets new thumbnails based on the incoming data and refreshes the rest.
- thumbnailDatas?.let {
- mTaskContainers.forEach {
- val thumbnailData = thumbnailDatas[it.task.key.id]
- if (thumbnailData != null) {
- it.thumbnailView.setThumbnail(it.task, thumbnailData)
- } else {
- // Refresh the rest that were not updated.
- it.thumbnailView.refresh()
- }
- }
- }
- }
-
- override fun onRecycle() {
- resetPersistentViewTransforms()
- // Clear any references to the thumbnail (it will be re-read either from the cache or the
- // system on next bind)
- mTaskContainers.forEach { it.thumbnailView.setThumbnail(it.task, null) }
- setOverlayEnabled(false)
- onTaskListVisibilityChanged(false)
- visibility = VISIBLE
- }
-
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
val containerWidth = MeasureSpec.getSize(widthMeasureSpec)
var containerHeight = MeasureSpec.getSize(heightMeasureSpec)
setMeasuredDimension(containerWidth, containerHeight)
- if (mTaskContainers.isEmpty()) {
+ if (taskContainers.isEmpty()) {
return
}
- val thumbnailTopMarginPx = mContainer.deviceProfile.overviewTaskThumbnailTopMarginPx
+ val thumbnailTopMarginPx = container.deviceProfile.overviewTaskThumbnailTopMarginPx
containerHeight -= thumbnailTopMarginPx
- BaseContainerInterface.getTaskDimension(mContext, mContainer.deviceProfile, tempPointF)
+ BaseContainerInterface.getTaskDimension(mContext, container.deviceProfile, tempPointF)
val windowWidth = tempPointF.x.toInt()
val windowHeight = tempPointF.y.toInt()
val scaleWidth = containerWidth / windowWidth.toFloat()
@@ -269,7 +112,7 @@
}
// Desktop tile is a shrunk down version of launcher and freeform task thumbnails.
- mTaskContainers.forEach {
+ taskContainers.forEach {
// Default to quarter of the desktop if we did not get app bounds.
val taskSize =
it.task.appBounds
@@ -304,54 +147,191 @@
}
}
- // TODO(b/330685808) support overlay for Screenshot action
- override fun setOverlayEnabled(overlayEnabled: Boolean) {}
+ override fun onRecycle() {
+ resetPersistentViewTransforms()
+ // Clear any references to the thumbnail (it will be re-read either from the cache or the
+ // system on next bind)
+ taskContainers.forEach { it.thumbnailView.setThumbnail(it.task, null) }
+ setOverlayEnabled(false)
+ onTaskListVisibilityChanged(false)
+ visibility = VISIBLE
+ }
- override fun setFullscreenProgress(progress: Float) {
- // TODO(b/249371338): this copies parent implementation and makes it work for N thumbs
- val boundProgress = Utilities.boundToRange(progress, 0f, 1f)
- mFullscreenProgress = boundProgress
- mIconView.setVisibility(if (boundProgress < 1) VISIBLE else INVISIBLE)
- // Don't show background while we are transitioning to/from fullscreen
- backgroundView.visibility = if (mFullscreenProgress > 0) INVISIBLE else VISIBLE
- mTaskContainers.forEach {
- it.thumbnailView.taskOverlay.setFullscreenProgress(boundProgress)
+ override fun bind(
+ task: Task,
+ orientedState: RecentsOrientedState,
+ taskOverlayFactory: TaskOverlayFactory
+ ) {
+ bind(listOf(task), orientedState, taskOverlayFactory)
+ }
+
+ /** Updates this desktop task to the gives task list defined in `tasks` */
+ fun bind(
+ tasks: List<Task>,
+ orientedState: RecentsOrientedState,
+ taskOverlayFactory: TaskOverlayFactory
+ ) {
+ if (DEBUG) {
+ val sb = StringBuilder()
+ sb.append("bind tasks=").append(tasks.size).append("\n")
+ tasks.forEach { sb.append(" key=${it.key}\n") }
+ Log.d(TAG, sb.toString())
}
- // Animate icons and DWB banners in/out, except in QuickSwitch state, when tiles are
- // oversized and banner would look disproportionately large.
- if (
- mContainer.getOverviewPanel<RecentsView<*, *>>().getStateManager().state !=
- LauncherState.BACKGROUND_APP
- ) {
- setIconsAndBannersTransitionProgress(boundProgress, true)
+ cancelPendingLoadTasks()
+
+ if (!isTaskContainersInitialized()) {
+ taskContainers = arrayListOf()
}
- updateSnapshotRadius()
+ val taskContainers = taskContainers as ArrayList
+ taskContainers.ensureCapacity(tasks.size)
+ tasks.forEachIndexed { index, task ->
+ val thumbnailView: TaskThumbnailViewDeprecated
+ if (index >= taskContainers.size) {
+ thumbnailView = taskThumbnailViewPool.view
+ // Add thumbnailView from to position after the initial child views.
+ addView(
+ thumbnailView,
+ childCountAtInflation,
+ LayoutParams(
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT
+ )
+ )
+ } else {
+ thumbnailView = taskContainers[index].thumbnailView
+ }
+ thumbnailView.bind(task, taskOverlayFactory)
+ val taskContainer =
+ TaskContainer(
+ task,
+ thumbnailView,
+ iconView,
+ SplitConfigurationOptions.STAGE_POSITION_UNDEFINED,
+ null
+ )
+ if (index >= taskContainers.size) {
+ taskContainers.add(taskContainer)
+ } else {
+ taskContainers[index] = taskContainer
+ }
+ }
+ repeat(taskContainers.size - tasks.size) {
+ taskContainers.removeLast().apply {
+ removeView(thumbnailView)
+ taskThumbnailViewPool.recycle(thumbnailView)
+ }
+ }
+
+ setOrientationState(orientedState)
}
- override fun updateSnapshotRadius() {
- super.updateSnapshotRadius()
- updateFullscreenParams(snapshotDrawParams)
- mTaskContainers.forEach { it.thumbnailView.setFullscreenParams(snapshotDrawParams) }
+ // thumbnailView is laid out differently and is handled in onMeasure
+ override fun setThumbnailOrientation(orientationState: RecentsOrientedState) {}
+
+ override fun onTaskListVisibilityChanged(visible: Boolean, changes: Int) {
+ cancelPendingLoadTasks()
+ if (needsUpdate(changes, FLAG_UPDATE_THUMBNAIL)) {
+ taskContainers.forEach {
+ if (visible) {
+ RecentsModel.INSTANCE.get(context)
+ .thumbnailCache
+ .updateThumbnailInBackground(it.task) { thumbnailData: ThumbnailData ->
+ it.thumbnailView.setThumbnail(it.task, thumbnailData)
+ }
+ ?.apply { pendingThumbnailRequests.add(this) }
+ } else {
+ it.thumbnailView.setThumbnail(null, null)
+ // Reset the task thumbnail ref
+ it.task.thumbnail = null
+ }
+ }
+ }
}
- override fun setColorTint(amount: Float, tintColor: Int) {
- mTaskContainers.forEach { it.thumbnailView.dimAlpha = amount }
+ override fun cancelPendingLoadTasks() {
+ pendingThumbnailRequests.forEach { it.cancel() }
+ pendingThumbnailRequests.clear()
}
- override fun applyThumbnailSplashAlpha() {
- mTaskContainers.forEach { it.thumbnailView.setSplashAlpha(mTaskThumbnailSplashAlpha) }
+ override fun getThumbnailBounds(bounds: Rect, relativeToDragLayer: Boolean) {
+ if (relativeToDragLayer) {
+ container.dragLayer.getDescendantRectRelativeToSelf(backgroundView, bounds)
+ } else {
+ bounds.set(backgroundView)
+ }
}
- public override fun setThumbnailVisibility(visibility: Int, taskId: Int) {
- mTaskContainers.forEach { it.thumbnailView.visibility = visibility }
+ override fun launchTaskAnimated(): RunnableList? {
+ val recentsView = recentsView ?: return null
+ val endCallback = RunnableList()
+ val desktopController = recentsView.desktopRecentsController
+ checkNotNull(desktopController) { "recentsController is null" }
+ desktopController.launchDesktopFromRecents(this) { endCallback.executeAllAndDestroy() }
+ Log.d(TAG, "launchTaskAnimated - launchDesktopFromRecents: ${taskIds.contentToString()}")
+
+ // Callbacks get run from recentsView for case when recents animation already running
+ recentsView.addSideTaskLaunchCallback(endCallback)
+ return endCallback
+ }
+
+ override fun launchTask(callback: (launched: Boolean) -> Unit, isQuickSwitch: Boolean) {
+ launchTasks()
+ callback(true)
+ }
+
+ override fun refreshThumbnails(thumbnailDatas: HashMap<Int, ThumbnailData?>?) {
+ // Sets new thumbnails based on the incoming data and refreshes the rest.
+ thumbnailDatas?.let {
+ taskContainers.forEach {
+ val thumbnailData = thumbnailDatas[it.task.key.id]
+ if (thumbnailData != null) {
+ it.thumbnailView.setThumbnail(it.task, thumbnailData)
+ } else {
+ // Refresh the rest that were not updated.
+ it.thumbnailView.refresh()
+ }
+ }
+ }
}
// Desktop tile can't be in split screen
override fun confirmSecondSplitSelectApp(): Boolean = false
+ override fun setColorTint(amount: Float, tintColor: Int) {
+ taskContainers.forEach { it.thumbnailView.dimAlpha = amount }
+ }
+
+ override fun setThumbnailVisibility(visibility: Int, taskId: Int) {
+ taskContainers.forEach { it.thumbnailView.visibility = visibility }
+ }
+
+ // TODO(b/330685808) support overlay for Screenshot action
+ override fun setOverlayEnabled(overlayEnabled: Boolean) {}
+
+ override fun onFullscreenProgressChanged(fullscreenProgress: Float) {
+ // TODO(b/249371338): this copies parent implementation and makes it work for N thumbs
+ iconView.setVisibility(if (fullscreenProgress < 1) VISIBLE else INVISIBLE)
+ // Don't show background while we are transitioning to/from fullscreen
+ backgroundView.visibility = if (fullscreenProgress > 0) INVISIBLE else VISIBLE
+ taskContainers.forEach {
+ it.thumbnailView.taskOverlay.setFullscreenProgress(fullscreenProgress)
+ }
+ setIconsAndBannersFullscreenProgress(fullscreenProgress)
+ updateSnapshotRadius()
+ }
+
+ override fun updateSnapshotRadius() {
+ updateFullscreenParams(snapshotDrawParams)
+ taskContainers.forEach { it.thumbnailView.setFullscreenParams(snapshotDrawParams) }
+ }
+
+ override fun applyThumbnailSplashAlpha() {
+ taskContainers.forEach { it.thumbnailView.setSplashAlpha(taskThumbnailSplashAlpha) }
+ }
+
companion object {
private const val TAG = "DesktopTaskView"
- private const val DEBUG = true
+ private const val DEBUG = false
private val ORIGIN = Point(0, 0)
}
}
diff --git a/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt b/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt
index 93a7200..f6ae038 100644
--- a/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt
+++ b/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt
@@ -32,8 +32,12 @@
import com.android.launcher3.util.CancellableTask
import com.android.launcher3.util.RunnableList
import com.android.launcher3.util.SplitConfigurationOptions
+import com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT
+import com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT
+import com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_UNDEFINED
import com.android.launcher3.util.TransformingTouchDelegate
import com.android.quickstep.RecentsModel
+import com.android.quickstep.TaskOverlayFactory
import com.android.quickstep.util.RecentsOrientedState
import com.android.quickstep.util.SplitScreenUtils.Companion.convertLauncherSplitBoundsToShell
import com.android.quickstep.util.SplitSelectStateController
@@ -42,7 +46,6 @@
import com.android.systemui.shared.recents.utilities.PreviewPositionHelper
import com.android.systemui.shared.system.InteractionJankMonitorWrapper
import com.android.wm.shell.common.split.SplitScreenConstants.PersistentSnapPosition
-import java.util.function.Consumer
/**
* TaskView that contains and shows thumbnails for not one, BUT TWO(!!) tasks
@@ -54,63 +57,31 @@
*
* (Icon loading sold separately, fees may apply. Shipping & Handling for Overlays not included).
*/
-class GroupedTaskView @JvmOverloads constructor(context: Context?, attrs: AttributeSet? = null) :
+class GroupedTaskView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
TaskView(context, attrs) {
// TODO(b/336612373): Support new TTV for GroupedTaskView
- private val icon2CenterCoords = FloatArray(2)
+ private val icon2CenterCoordinates = FloatArray(2)
private val digitalWellBeingToast2: DigitalWellBeingToast =
- DigitalWellBeingToast(mContainer, this)
+ DigitalWellBeingToast(container, this)
+
private lateinit var snapshotView2: TaskThumbnailViewDeprecated
private lateinit var iconView2: TaskViewIcon
private lateinit var icon2TouchDelegate: TransformingTouchDelegate
- private var thumbnailLoadRequest2: CancellableTask<ThumbnailData>? = null
- private var iconLoadRequest2: CancellableTask<*>? = null
+
var splitBoundsConfig: SplitConfigurationOptions.SplitBounds? = null
private set
-
- @get:Deprecated("Use {@link #mTaskContainers} instead.")
- private val secondTask: Task
- /** Returns the second task bound to this TaskView. */
- get() {
- assert(mTaskContainers.size > 1) { "GroupedTaskView is not bound" }
- return mTaskContainers[1].task
- }
+ private var thumbnailLoadRequest2: CancellableTask<ThumbnailData>? = null
+ private var iconLoadRequest2: CancellableTask<*>? = null
@get:PersistentSnapPosition
val snapPosition: Int
/** Returns the [PersistentSnapPosition] of this pair of tasks. */
- get() {
- checkNotNull(splitBoundsConfig) { "mSplitBoundsConfig is null" }
- return splitBoundsConfig!!.snapPosition
- }
+ get() = splitBoundsConfig?.snapPosition ?: STAGE_POSITION_UNDEFINED
- override fun getThumbnailBounds(bounds: Rect, relativeToDragLayer: Boolean) {
- splitBoundsConfig ?: return super.getThumbnailBounds(bounds, relativeToDragLayer)
- if (relativeToDragLayer) {
- val firstThumbnailBounds = Rect()
- val secondThumbnailBounds = Rect()
- with(mContainer.dragLayer) {
- getDescendantRectRelativeToSelf(mTaskThumbnailViewDeprecated, firstThumbnailBounds)
- getDescendantRectRelativeToSelf(snapshotView2, secondThumbnailBounds)
- }
- bounds.set(firstThumbnailBounds)
- bounds.union(secondThumbnailBounds)
- } else {
- bounds.set(getSnapshotViewBounds(mTaskThumbnailViewDeprecated))
- bounds.union(getSnapshotViewBounds(snapshotView2))
- }
- }
-
- private fun getSnapshotViewBounds(snapshotView: View): Rect {
- val snapshotViewX = Math.round(snapshotView.x)
- val snapshotViewY = Math.round(snapshotView.y)
- return Rect(
- snapshotViewX,
- snapshotViewY,
- snapshotViewX + snapshotView.width,
- snapshotViewY + snapshotView.height
- )
- }
+ @get:Deprecated("Use {@link #mTaskContainers} instead.")
+ private val secondTask: Task
+ /** Returns the second task bound to this TaskView. */
+ get() = taskContainers[1].task
override fun onFinishInflate() {
super.onFinishInflate()
@@ -125,31 +96,84 @@
icon2TouchDelegate = TransformingTouchDelegate(iconView2.asView())
}
+ override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec)
+ val widthSize = MeasureSpec.getSize(widthMeasureSpec)
+ val heightSize = MeasureSpec.getSize(heightMeasureSpec)
+ setMeasuredDimension(widthSize, heightSize)
+ val splitBoundsConfig = splitBoundsConfig ?: return
+ val initSplitTaskId = getThisTaskCurrentlyInSplitSelection()
+ if (initSplitTaskId == INVALID_TASK_ID) {
+ pagedOrientationHandler.measureGroupedTaskViewThumbnailBounds(
+ taskThumbnailViewDeprecated,
+ snapshotView2,
+ widthSize,
+ heightSize,
+ splitBoundsConfig,
+ container.deviceProfile,
+ layoutDirection == LAYOUT_DIRECTION_RTL
+ )
+ // Should we be having a separate translation step apart from the measuring above?
+ // The following only applies to large screen for now, but for future reference
+ // we'd want to abstract this out in PagedViewHandlers to get the primary/secondary
+ // translation directions
+ taskThumbnailViewDeprecated.applySplitSelectTranslateX(
+ taskThumbnailViewDeprecated.translationX
+ )
+ taskThumbnailViewDeprecated.applySplitSelectTranslateY(
+ taskThumbnailViewDeprecated.translationY
+ )
+ snapshotView2.applySplitSelectTranslateX(snapshotView2.translationX)
+ snapshotView2.applySplitSelectTranslateY(snapshotView2.translationY)
+ } else {
+ // Currently being split with this taskView, let the non-split selected thumbnail
+ // take up full thumbnail area
+ taskContainers
+ .firstOrNull { it.task.key.id != initSplitTaskId }
+ ?.thumbnailView
+ ?.measure(
+ widthMeasureSpec,
+ MeasureSpec.makeMeasureSpec(
+ heightSize - container.deviceProfile.overviewTaskThumbnailTopMarginPx,
+ MeasureSpec.EXACTLY
+ )
+ )
+ }
+ if (!enableOverviewIconMenu()) {
+ updateIconPlacement()
+ }
+ }
+
+ override fun onRecycle() {
+ super.onRecycle()
+ snapshotView2.setThumbnail(secondTask, null)
+ splitBoundsConfig = null
+ }
+
fun bind(
primaryTask: Task,
secondaryTask: Task,
orientedState: RecentsOrientedState,
+ taskOverlayFactory: TaskOverlayFactory,
splitBoundsConfig: SplitConfigurationOptions.SplitBounds?,
) {
cancelPendingLoadTasks()
- setupTaskContainers(primaryTask)
- mTaskContainers =
+ setupTaskContainers(primaryTask, taskOverlayFactory)
+ taskContainers =
listOf(
- mTaskContainers[0].apply {
- stagePosition = SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT
- },
+ taskContainers[0].apply { stagePosition = STAGE_POSITION_TOP_OR_LEFT },
TaskContainer(
secondaryTask,
findViewById(R.id.bottomright_snapshot)!!,
iconView2,
- SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT,
+ STAGE_POSITION_BOTTOM_OR_RIGHT,
digitalWellBeingToast2
)
)
- snapshotView2.bind(secondaryTask)
+ snapshotView2.bind(secondaryTask, taskOverlayFactory)
this.splitBoundsConfig = splitBoundsConfig
this.splitBoundsConfig?.let {
- mTaskThumbnailViewDeprecated.previewPositionHelper.setSplitBounds(
+ taskThumbnailViewDeprecated.previewPositionHelper.setSplitBounds(
convertLauncherSplitBoundsToShell(it),
PreviewPositionHelper.STAGE_POSITION_TOP_OR_LEFT
)
@@ -161,27 +185,88 @@
setOrientationState(orientedState)
}
- /**
- * Sets up an on-click listener and the visibility for show_windows icon on top of each task.
- */
- override fun setUpShowAllInstancesListener() {
- // sets up the listener for the left/top task
- super.setUpShowAllInstancesListener()
- if (mTaskContainers.size < 2) {
- return
+ override fun setOrientationState(orientationState: RecentsOrientedState) {
+ if (enableOverviewIconMenu()) {
+ splitBoundsConfig?.let {
+ val groupedTaskViewSizes =
+ orientationState.orientationHandler.getGroupedTaskViewSizes(
+ container.deviceProfile,
+ it,
+ layoutParams.width,
+ layoutParams.height
+ )
+ val iconViewMarginStart =
+ resources.getDimensionPixelSize(
+ R.dimen.task_thumbnail_icon_menu_expanded_top_start_margin
+ )
+ val iconViewBackgroundMarginStart =
+ resources.getDimensionPixelSize(
+ R.dimen.task_thumbnail_icon_menu_background_margin_top_start
+ )
+ val iconMargins = (iconViewMarginStart + iconViewBackgroundMarginStart) * 2
+ // setMaxWidth() needs to be called before mIconView.setIconOrientation which is
+ // called in the super below.
+ (iconView as IconAppChipView).setMaxWidth(
+ groupedTaskViewSizes.first.x - iconMargins
+ )
+ (iconView2 as IconAppChipView).setMaxWidth(
+ groupedTaskViewSizes.second.x - iconMargins
+ )
+ }
}
+ super.setOrientationState(orientationState)
+ iconView2.setIconOrientation(orientationState, isGridTask)
+ updateIconPlacement()
+ }
- // right/bottom task's base package name
- val taskPackageName = mTaskContainers[1].task.key.packageName
+ override fun setThumbnailOrientation(orientationState: RecentsOrientedState) {
+ super.setThumbnailOrientation(orientationState)
+ digitalWellBeingToast2.initialize(secondTask)
+ }
- // icon of the right/bottom task
- val showWindowsView = findViewById<View>(R.id.show_windows_right)!!
- updateFilterCallback(showWindowsView, getFilterUpdateCallback(taskPackageName))
+ private fun updateIconPlacement() {
+ val splitBoundsConfig = splitBoundsConfig ?: return
+ val taskIconHeight = container.deviceProfile.overviewTaskIconSizePx
+ val isRtl = layoutDirection == LAYOUT_DIRECTION_RTL
+ if (enableOverviewIconMenu()) {
+ val groupedTaskViewSizes =
+ pagedOrientationHandler.getGroupedTaskViewSizes(
+ container.deviceProfile,
+ splitBoundsConfig,
+ layoutParams.width,
+ layoutParams.height
+ )
+ pagedOrientationHandler.setSplitIconParams(
+ iconView.asView(),
+ iconView2.asView(),
+ taskIconHeight,
+ groupedTaskViewSizes.first.x,
+ groupedTaskViewSizes.first.y,
+ layoutParams.height,
+ layoutParams.width,
+ isRtl,
+ container.deviceProfile,
+ splitBoundsConfig
+ )
+ } else {
+ pagedOrientationHandler.setSplitIconParams(
+ iconView.asView(),
+ iconView2.asView(),
+ taskIconHeight,
+ taskThumbnailViewDeprecated.measuredWidth,
+ taskThumbnailViewDeprecated.measuredHeight,
+ measuredHeight,
+ measuredWidth,
+ isRtl,
+ container.deviceProfile,
+ splitBoundsConfig
+ )
+ }
}
override fun onTaskListVisibilityChanged(visible: Boolean, changes: Int) {
super.onTaskListVisibilityChanged(visible, changes)
- val model = RecentsModel.INSTANCE[context]
+ val model = RecentsModel.INSTANCE.get(context)
if (needsUpdate(changes, FLAG_UPDATE_THUMBNAIL)) {
if (visible) {
thumbnailLoadRequest2 =
@@ -205,7 +290,7 @@
}
digitalWellBeingToast2.initialize(secondTask)
digitalWellBeingToast2.setSplitConfiguration(splitBoundsConfig)
- mDigitalWellBeingToast.setSplitConfiguration(splitBoundsConfig)
+ digitalWellBeingToast.setSplitConfiguration(splitBoundsConfig)
}
} else {
setIcon(iconView2, null)
@@ -216,18 +301,6 @@
}
}
- fun updateSplitBoundsConfig(splitBounds: SplitConfigurationOptions.SplitBounds?) {
- splitBoundsConfig = splitBounds
- invalidate()
- }
-
- override fun offerTouchToChildren(event: MotionEvent): Boolean {
- computeAndSetIconTouchDelegate(iconView2, icon2CenterCoords, icon2TouchDelegate)
- return if (icon2TouchDelegate.onTouchEvent(event)) {
- true
- } else super.offerTouchToChildren(event)
- }
-
override fun cancelPendingLoadTasks() {
super.cancelPendingLoadTasks()
thumbnailLoadRequest2?.cancel()
@@ -236,8 +309,61 @@
iconLoadRequest2 = null
}
+ override fun getThumbnailBounds(bounds: Rect, relativeToDragLayer: Boolean) {
+ splitBoundsConfig ?: return super.getThumbnailBounds(bounds, relativeToDragLayer)
+ if (relativeToDragLayer) {
+ val firstThumbnailBounds = Rect()
+ val secondThumbnailBounds = Rect()
+ with(container.dragLayer) {
+ getDescendantRectRelativeToSelf(snapshotView, firstThumbnailBounds)
+ getDescendantRectRelativeToSelf(snapshotView2, secondThumbnailBounds)
+ }
+ bounds.set(firstThumbnailBounds)
+ bounds.union(secondThumbnailBounds)
+ } else {
+ bounds.set(getSnapshotViewBounds(snapshotView))
+ bounds.union(getSnapshotViewBounds(snapshotView2))
+ }
+ }
+
+ private fun getSnapshotViewBounds(snapshotView: View): Rect {
+ val snapshotViewX = Math.round(snapshotView.x)
+ val snapshotViewY = Math.round(snapshotView.y)
+ return Rect(
+ snapshotViewX,
+ snapshotViewY,
+ snapshotViewX + snapshotView.width,
+ snapshotViewY + snapshotView.height
+ )
+ }
+
+ /**
+ * Sets up an on-click listener and the visibility for show_windows icon on top of each task.
+ */
+ override fun setUpShowAllInstancesListener() {
+ // sets up the listener for the left/top task
+ super.setUpShowAllInstancesListener()
+ // right/bottom task's base package name
+ val taskPackageName = taskContainers[1].task.key.packageName
+ // icon of the right/bottom task
+ val showWindowsView = findViewById<View>(R.id.show_windows_right)!!
+ updateFilterCallback(showWindowsView, getFilterUpdateCallback(taskPackageName))
+ }
+
+ fun updateSplitBoundsConfig(splitBounds: SplitConfigurationOptions.SplitBounds?) {
+ splitBoundsConfig = splitBounds
+ invalidate()
+ }
+
+ override fun offerTouchToChildren(event: MotionEvent): Boolean {
+ computeAndSetIconTouchDelegate(iconView2, icon2CenterCoordinates, icon2TouchDelegate)
+ return if (icon2TouchDelegate.onTouchEvent(event)) {
+ true
+ } else super.offerTouchToChildren(event)
+ }
+
override fun launchTaskAnimated(): RunnableList? {
- if (mTaskContainers.isEmpty()) {
+ if (taskContainers.isEmpty()) {
Log.d(TAG, "launchTaskAnimated - task is not bound")
return null
}
@@ -259,8 +385,8 @@
return endCallback
}
- override fun launchTask(callback: Consumer<Boolean>, isQuickswitch: Boolean) {
- launchTaskInternal(isQuickswitch, false, callback /*launchingExistingTaskview*/)
+ override fun launchTask(callback: (launched: Boolean) -> Unit, isQuickSwitch: Boolean) {
+ launchTaskInternal(isQuickSwitch, false, callback /*launchingExistingTaskview*/)
}
/**
@@ -272,15 +398,14 @@
private fun launchTaskInternal(
isQuickSwitch: Boolean,
launchingExistingTaskView: Boolean,
- callback: Consumer<Boolean>
+ callback: (launched: Boolean) -> Unit
) {
- check(mTaskContainers.size >= 2) { "task not bound" }
recentsView?.let {
it.splitSelectController.launchExistingSplitPair(
if (launchingExistingTaskView) this else null,
- mTaskContainers[0].task.key.id,
- mTaskContainers[1].task.key.id,
- SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT,
+ taskContainers[0].task.key.id,
+ taskContainers[1].task.key.id,
+ STAGE_POSITION_TOP_OR_LEFT,
callback,
isQuickSwitch,
snapPosition
@@ -289,7 +414,7 @@
}
}
- public override fun refreshThumbnails(thumbnailDatas: HashMap<Int, ThumbnailData?>?) {
+ override fun refreshThumbnails(thumbnailDatas: HashMap<Int, ThumbnailData?>?) {
super.refreshThumbnails(thumbnailDatas)
thumbnailDatas?.get(secondTask.key.id)?.let { snapshotView2.setThumbnail(secondTask, it) }
?: { snapshotView2.refresh() }
@@ -312,7 +437,7 @@
// checks below aren't reliable since both of those views may be gone/transformed
val initSplitTaskId = getThisTaskCurrentlyInSplitSelection()
if (initSplitTaskId != INVALID_TASK_ID) {
- return if (initSplitTaskId == firstTask!!.key.id) 1 else 0
+ return if (initSplitTaskId == firstTask.key.id) 1 else 0
}
}
@@ -332,57 +457,33 @@
return Utilities.pointInView(this, localPos[0], localPos[1], 0f /* slop */)
}
- override fun onRecycle() {
- super.onRecycle()
- snapshotView2.setThumbnail(secondTask, null)
- splitBoundsConfig = null
+ override fun setIconsAndBannersTransitionProgress(progress: Float, invert: Boolean) {
+ super.setIconsAndBannersTransitionProgress(progress, invert)
+ getIconContentScale(invert).let {
+ iconView2.setContentAlpha(it)
+ digitalWellBeingToast2.updateBannerOffset(1f - it)
+ }
}
- override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec)
- val widthSize = MeasureSpec.getSize(widthMeasureSpec)
- val heightSize = MeasureSpec.getSize(heightMeasureSpec)
- setMeasuredDimension(widthSize, heightSize)
- val splitBoundsConfig = splitBoundsConfig ?: return
- val initSplitTaskId = getThisTaskCurrentlyInSplitSelection()
- if (initSplitTaskId == INVALID_TASK_ID) {
- pagedOrientationHandler.measureGroupedTaskViewThumbnailBounds(
- mTaskThumbnailViewDeprecated,
- snapshotView2,
- widthSize,
- heightSize,
- splitBoundsConfig,
- mContainer.deviceProfile,
- layoutDirection == LAYOUT_DIRECTION_RTL
- )
- // Should we be having a separate translation step apart from the measuring above?
- // The following only applies to large screen for now, but for future reference
- // we'd want to abstract this out in PagedViewHandlers to get the primary/secondary
- // translation directions
- mTaskThumbnailViewDeprecated.applySplitSelectTranslateX(
- mTaskThumbnailViewDeprecated.translationX
- )
- mTaskThumbnailViewDeprecated.applySplitSelectTranslateY(
- mTaskThumbnailViewDeprecated.translationY
- )
- snapshotView2.applySplitSelectTranslateX(snapshotView2.translationX)
- snapshotView2.applySplitSelectTranslateY(snapshotView2.translationY)
- } else {
- // Currently being split with this taskView, let the non-split selected thumbnail
- // take up full thumbnail area
- mTaskContainers
- .firstOrNull { it.task.key.id != initSplitTaskId }
- ?.thumbnailView
- ?.measure(
- widthMeasureSpec,
- MeasureSpec.makeMeasureSpec(
- heightSize - mContainer.deviceProfile.overviewTaskThumbnailTopMarginPx,
- MeasureSpec.EXACTLY
- )
- )
- }
- if (!enableOverviewIconMenu()) {
- updateIconPlacement()
+ override fun setColorTint(amount: Float, tintColor: Int) {
+ super.setColorTint(amount, tintColor)
+ iconView2.setIconColorTint(tintColor, amount)
+ snapshotView2.dimAlpha = amount
+ digitalWellBeingToast2.setBannerColorTint(tintColor, amount)
+ }
+
+ /**
+ * Sets visibility for thumbnails and associated elements (DWB banners). IconView is unaffected.
+ *
+ * When setting INVISIBLE, sets the visibility for the last selected child task. When setting
+ * VISIBLE (as a reset), sets the visibility for both tasks.
+ */
+ override fun setThumbnailVisibility(visibility: Int, taskId: Int) {
+ taskContainers.forEach {
+ if (visibility == VISIBLE || it.task.key.id == taskId) {
+ it.thumbnailView.visibility = visibility
+ it.digitalWellBeingToast?.setBannerVisibility(visibility)
+ }
}
}
@@ -394,135 +495,26 @@
}
}
- override fun setOrientationState(orientationState: RecentsOrientedState) {
- if (enableOverviewIconMenu()) {
- splitBoundsConfig?.let {
- val groupedTaskViewSizes =
- orientationState.orientationHandler.getGroupedTaskViewSizes(
- mContainer.deviceProfile,
- it,
- layoutParams.width,
- layoutParams.height
- )
- val iconViewMarginStart =
- resources.getDimensionPixelSize(
- R.dimen.task_thumbnail_icon_menu_expanded_top_start_margin
- )
- val iconViewBackgroundMarginStart =
- resources.getDimensionPixelSize(
- R.dimen.task_thumbnail_icon_menu_background_margin_top_start
- )
- val iconMargins = (iconViewMarginStart + iconViewBackgroundMarginStart) * 2
- // setMaxWidth() needs to be called before mIconView.setIconOrientation which is
- // called in the super below.
- (mIconView as IconAppChipView).setMaxWidth(
- groupedTaskViewSizes.first.x - iconMargins
- )
- (iconView2 as IconAppChipView).setMaxWidth(
- groupedTaskViewSizes.second.x - iconMargins
- )
- }
- }
- super.setOrientationState(orientationState)
- iconView2.setIconOrientation(orientationState, isGridTask())
- updateIconPlacement()
- }
-
- override fun setThumbnailOrientation(orientationState: RecentsOrientedState?) {
- super.setThumbnailOrientation(orientationState)
- digitalWellBeingToast2.initialize(secondTask)
- }
-
- private fun updateIconPlacement() {
- val splitBoundsConfig = splitBoundsConfig ?: return
- val taskIconHeight = mContainer.deviceProfile.overviewTaskIconSizePx
- val isRtl = layoutDirection == LAYOUT_DIRECTION_RTL
- if (enableOverviewIconMenu()) {
- val groupedTaskViewSizes =
- pagedOrientationHandler.getGroupedTaskViewSizes(
- mContainer.deviceProfile,
- splitBoundsConfig,
- layoutParams.width,
- layoutParams.height
- )
- pagedOrientationHandler.setSplitIconParams(
- mIconView.asView(),
- iconView2.asView(),
- taskIconHeight,
- groupedTaskViewSizes.first.x,
- groupedTaskViewSizes.first.y,
- layoutParams.height,
- layoutParams.width,
- isRtl,
- mContainer.deviceProfile,
- splitBoundsConfig
- )
- } else {
- pagedOrientationHandler.setSplitIconParams(
- mIconView.asView(),
- iconView2.asView(),
- taskIconHeight,
- mTaskThumbnailViewDeprecated.measuredWidth,
- mTaskThumbnailViewDeprecated.measuredHeight,
- measuredHeight,
- measuredWidth,
- isRtl,
- mContainer.deviceProfile,
- splitBoundsConfig
- )
- }
- }
-
- override fun updateSnapshotRadius() {
- super.updateSnapshotRadius()
- snapshotView2.setFullscreenParams(mCurrentFullscreenParams)
- }
-
- override fun setIconsAndBannersTransitionProgress(progress: Float, invert: Boolean) {
- super.setIconsAndBannersTransitionProgress(progress, invert)
- // Value set by super call
- val scale = mIconView.alpha
- iconView2.setContentAlpha(scale)
- digitalWellBeingToast2.updateBannerOffset(1f - scale)
- }
-
- override fun setColorTint(amount: Float, tintColor: Int) {
- super.setColorTint(amount, tintColor)
- iconView2.setIconColorTint(tintColor, amount)
- snapshotView2.dimAlpha = amount
- digitalWellBeingToast2.setBannerColorTint(tintColor, amount)
- }
-
- override fun applyThumbnailSplashAlpha() {
- super.applyThumbnailSplashAlpha()
- snapshotView2.setSplashAlpha(mTaskThumbnailSplashAlpha)
- }
-
override fun refreshTaskThumbnailSplash() {
super.refreshTaskThumbnailSplash()
snapshotView2.refreshSplashView()
}
+ override fun updateSnapshotRadius() {
+ super.updateSnapshotRadius()
+ snapshotView2.setFullscreenParams(currentFullscreenParams)
+ }
+
+ override fun applyThumbnailSplashAlpha() {
+ super.applyThumbnailSplashAlpha()
+ snapshotView2.setSplashAlpha(taskThumbnailSplashAlpha)
+ }
+
override fun resetViewTransforms() {
super.resetViewTransforms()
snapshotView2.resetViewTransforms()
}
- /**
- * Sets visibility for thumbnails and associated elements (DWB banners). IconView is unaffected.
- *
- * When setting INVISIBLE, sets the visibility for the last selected child task. When setting
- * VISIBLE (as a reset), sets the visibility for both tasks.
- */
- public override fun setThumbnailVisibility(visibility: Int, taskId: Int) {
- mTaskContainers.forEach {
- if (visibility == VISIBLE || it.task.key.id == taskId) {
- it.thumbnailView.visibility = visibility
- it.digitalWellBeingToast.setBannerVisibility(visibility)
- }
- }
- }
-
companion object {
private const val TAG = "GroupedTaskView"
}
diff --git a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
index f6bae9b..5284b44 100644
--- a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -56,6 +56,8 @@
import com.android.quickstep.util.SplitSelectStateController;
import com.android.systemui.shared.recents.model.Task;
+import kotlin.Unit;
+
/**
* {@link RecentsView} used in Launcher activity
*/
@@ -107,7 +109,7 @@
}
@Override
- protected void onTaskLaunchAnimationEnd(boolean success) {
+ protected Unit onTaskLaunchAnimationEnd(boolean success) {
if (success) {
getStateManager().moveToRestState();
} else {
@@ -115,6 +117,7 @@
mContainer.getAllAppsController().setState(state);
}
super.onTaskLaunchAnimationEnd(success);
+ return Unit.INSTANCE;
}
@Override
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 18f537c..3c0445f 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -170,7 +170,6 @@
import com.android.launcher3.util.VibratorWrapper;
import com.android.launcher3.util.ViewPool;
import com.android.quickstep.BaseContainerInterface;
-import com.android.quickstep.DesktopModeStatus;
import com.android.quickstep.GestureState;
import com.android.quickstep.OverviewCommandHelper;
import com.android.quickstep.RecentsAnimationController;
@@ -218,6 +217,9 @@
import com.android.systemui.shared.system.TaskStackChangeListener;
import com.android.systemui.shared.system.TaskStackChangeListeners;
import com.android.wm.shell.common.pip.IPipAnimationListener;
+import com.android.wm.shell.shared.DesktopModeStatus;
+
+import kotlin.Unit;
import java.util.ArrayList;
import java.util.Arrays;
@@ -1016,11 +1018,10 @@
for (int i = 0; i < getTaskViewCount(); i++) {
TaskView tv = requireTaskViewAt(i);
Task task = tv.getFirstTask();
- if (task != null && task.key != null && pkg.equals(task.key.getPackageName())
- && task.key.userId == user.getIdentifier()) {
+ if (pkg.equals(task.key.getPackageName()) && task.key.userId == user.getIdentifier()) {
task.icon = null;
TaskViewIcon firstIconView = tv.getFirstIconView();
- if (firstIconView != null && firstIconView.getDrawable() != null) {
+ if (firstIconView.getDrawable() != null) {
tv.onTaskListVisibilityChanged(true /* visible */);
}
}
@@ -1740,7 +1741,7 @@
int[] currentTaskIds;
TaskView currentTaskView = getTaskViewAt(mCurrentPage);
- if (currentTaskView != null && currentTaskView.getFirstTask() != null) {
+ if (currentTaskView != null) {
currentTaskIds = currentTaskView.getTaskIds();
} else {
currentTaskIds = new int[0];
@@ -1770,12 +1771,12 @@
// If we are entering Overview as a result of initiating a split from somewhere else
// (e.g. split from Home), we need to make sure the staged app is not drawn as a thumbnail.
- int stagedTaskIdToBeRemovedFromGrid;
+ int stagedTaskIdToBeRemoved;
if (isSplitSelectionActive()) {
- stagedTaskIdToBeRemovedFromGrid = mSplitSelectStateController.getInitialTaskId();
+ stagedTaskIdToBeRemoved = mSplitSelectStateController.getInitialTaskId();
updateCurrentTaskActionsVisibility();
} else {
- stagedTaskIdToBeRemovedFromGrid = INVALID_TASK_ID;
+ stagedTaskIdToBeRemoved = INVALID_TASK_ID;
}
// update the map of instance counts
mFilterState.updateInstanceCountMap(taskGroups);
@@ -1787,46 +1788,36 @@
// taskGroups backwards populates the thumbnail grid from least recent to most recent.
for (int i = taskGroups.size() - 1; i >= 0; i--) {
GroupTask groupTask = taskGroups.get(i);
- boolean isRemovalNeeded = stagedTaskIdToBeRemovedFromGrid != INVALID_TASK_ID
- && groupTask.containsTask(stagedTaskIdToBeRemovedFromGrid);
+ boolean isRemovalNeeded = stagedTaskIdToBeRemoved != INVALID_TASK_ID
+ && groupTask.containsTask(stagedTaskIdToBeRemoved);
- TaskView taskView;
- if (isRemovalNeeded && groupTask.hasMultipleTasks()) {
- // If we need to remove half of a pair of tasks, force a TaskView with Type.SINGLE
- // to be a temporary container for the remaining task.
- taskView = getTaskViewFromPool(TaskView.Type.SINGLE);
- } else {
- taskView = getTaskViewFromPool(groupTask.taskViewType);
+ if (isRemovalNeeded && !groupTask.hasMultipleTasks()) {
+ // If the task we need to remove is not part of a pair, avoiding creating the
+ // TaskView.
+ continue;
}
- addView(taskView);
-
- if (isRemovalNeeded && groupTask.hasMultipleTasks()) {
- if (groupTask.task1.key.id == stagedTaskIdToBeRemovedFromGrid) {
- taskView.bind(groupTask.task2, mOrientationState);
- } else {
- taskView.bind(groupTask.task1, mOrientationState);
- }
- } else if (isRemovalNeeded) {
- // If the task we need to remove is not part of a pair, bind it to the TaskView
- // first (to prevent problems), then remove the whole thing.
- taskView.bind(groupTask.task1, mOrientationState);
- removeView(taskView);
- } else if (taskView instanceof GroupedTaskView) {
+ // If we need to remove half of a pair of tasks, force a TaskView with Type.SINGLE
+ // to be a temporary container for the remaining task.
+ TaskView taskView = getTaskViewFromPool(
+ isRemovalNeeded ? TaskView.Type.SINGLE : groupTask.taskViewType);
+ if (taskView instanceof GroupedTaskView) {
boolean firstTaskIsLeftTopTask =
groupTask.mSplitBounds.leftTopTaskId == groupTask.task1.key.id;
Task leftTopTask = firstTaskIsLeftTopTask ? groupTask.task1 : groupTask.task2;
Task rightBottomTask = firstTaskIsLeftTopTask ? groupTask.task2 : groupTask.task1;
-
((GroupedTaskView) taskView).bind(leftTopTask, rightBottomTask, mOrientationState,
- groupTask.mSplitBounds);
+ mTaskOverlayFactory, groupTask.mSplitBounds);
} else if (taskView instanceof DesktopTaskView) {
((DesktopTaskView) taskView).bind(((DesktopTask) groupTask).tasks,
- mOrientationState);
+ mOrientationState, mTaskOverlayFactory);
mDesktopTaskView = (DesktopTaskView) taskView;
} else {
- taskView.bind(groupTask.task1, mOrientationState);
+ Task task = groupTask.task1.key.id == stagedTaskIdToBeRemoved ? groupTask.task2
+ : groupTask.task1;
+ taskView.bind(task, mOrientationState, mTaskOverlayFactory);
}
+ addView(taskView);
// enables instance filtering if the feature flag for it is on
if (FeatureFlags.ENABLE_MULTI_INSTANCE.get()) {
@@ -2182,7 +2173,8 @@
}
for (int i = 0; i < taskCount; i++) {
TaskView taskView = requireTaskViewAt(i);
- taskView.updateTaskSize();
+ taskView.updateTaskSize(mLastComputedTaskSize, mLastComputedGridTaskSize,
+ mLastComputedCarouselTaskSize);
taskView.setNonGridTranslationX(accumulatedTranslationX);
taskView.setNonGridPivotTranslationX(translateXToMiddle);
// Compensate space caused by TaskView scaling.
@@ -2801,26 +2793,24 @@
if (needDesktopTask) {
taskView = getTaskViewFromPool(TaskView.Type.DESKTOP);
mTmpRunningTasks = Arrays.copyOf(runningTasks, runningTasks.length);
- addView(taskView, 0);
((DesktopTaskView) taskView).bind(Arrays.asList(mTmpRunningTasks),
- mOrientationState);
+ mOrientationState, mTaskOverlayFactory);
} else if (needGroupTaskView) {
taskView = getTaskViewFromPool(TaskView.Type.GROUPED);
mTmpRunningTasks = new Task[]{runningTasks[0], runningTasks[1]};
- addView(taskView, 0);
// When we create a placeholder task view mSplitBoundsConfig will be null, but with
// the actual app running we won't need to show the thumbnail until all the tasks
// load later anyways
((GroupedTaskView)taskView).bind(mTmpRunningTasks[0], mTmpRunningTasks[1],
- mOrientationState, mSplitBoundsConfig);
+ mOrientationState, mTaskOverlayFactory, mSplitBoundsConfig);
} else {
taskView = getTaskViewFromPool(TaskView.Type.SINGLE);
- addView(taskView, 0);
// The temporary running task is only used for the duration between the start of the
// gesture and the task list is loaded and applied
mTmpRunningTasks = new Task[]{runningTasks[0]};
- taskView.bind(mTmpRunningTasks[0], mOrientationState);
+ taskView.bind(mTmpRunningTasks[0], mOrientationState, mTaskOverlayFactory);
}
+ addView(taskView, 0);
runningTaskViewId = taskView.getTaskViewId();
if (wasEmpty) {
addView(mClearAllButton);
@@ -2918,7 +2908,7 @@
mRunningTaskShowScreenshot = showScreenshot;
TaskView runningTaskView = getRunningTaskView();
if (runningTaskView != null) {
- runningTaskView.setShowScreenshot(mRunningTaskShowScreenshot);
+ runningTaskView.setShouldShowScreenshot(mRunningTaskShowScreenshot);
}
}
@@ -3762,7 +3752,7 @@
if (taskView == nextFocusedTaskView) {
// Enlarge the task to be focused next, and translate into focus position.
float scale = mTaskWidth / (float) mLastComputedGridTaskSize.width();
- anim.setFloat(taskView, TaskView.SNAPSHOT_SCALE, scale,
+ anim.setFloat(taskView, TaskView.DISMISS_SCALE, scale,
clampToProgress(LINEAR, animationStartProgress,
dismissTranslationInterpolationEnd));
anim.setFloat(taskView, taskView.getPrimaryDismissTranslationProperty(),
@@ -3833,19 +3823,17 @@
if (success) {
if (shouldRemoveTask) {
- if (dismissedTaskView.getFirstTask() != null) {
- if (dismissedTaskView.isRunningTask()) {
- finishRecentsAnimation(true /* toRecents */, false /* shouldPip */,
- () -> removeTaskInternal(dismissedTaskViewId));
- } else {
- removeTaskInternal(dismissedTaskViewId);
- }
- announceForAccessibility(
- getResources().getString(R.string.task_view_closed));
- mContainer.getStatsLogManager().logger()
- .withItemInfo(dismissedTaskView.getItemInfo())
- .log(LAUNCHER_TASK_DISMISS_SWIPE_UP);
+ if (dismissedTaskView.isRunningTask()) {
+ finishRecentsAnimation(true /* toRecents */, false /* shouldPip */,
+ () -> removeTaskInternal(dismissedTaskViewId));
+ } else {
+ removeTaskInternal(dismissedTaskViewId);
}
+ announceForAccessibility(
+ getResources().getString(R.string.task_view_closed));
+ mContainer.getStatsLogManager().logger()
+ .withItemInfo(dismissedTaskView.getItemInfo())
+ .log(LAUNCHER_TASK_DISMISS_SWIPE_UP);
}
int pageToSnapTo = mCurrentPage;
@@ -5291,11 +5279,8 @@
} else {
tv.launchTask(this::onTaskLaunchAnimationEnd);
}
- Task task = tv.getFirstTask();
- if (task != null) {
- mContainer.getStatsLogManager().logger().withItemInfo(tv.getItemInfo())
- .log(LAUNCHER_TASK_LAUNCH_SWIPE_DOWN);
- }
+ mContainer.getStatsLogManager().logger().withItemInfo(tv.getItemInfo())
+ .log(LAUNCHER_TASK_LAUNCH_SWIPE_DOWN);
} else {
onTaskLaunchAnimationEnd(false);
}
@@ -5304,10 +5289,11 @@
return mPendingAnimation;
}
- protected void onTaskLaunchAnimationEnd(boolean success) {
+ protected Unit onTaskLaunchAnimationEnd(boolean success) {
if (success) {
resetTaskVisuals();
}
+ return Unit.INSTANCE;
}
@Override
@@ -5923,7 +5909,7 @@
return;
}
- taskView.setShowScreenshot(true);
+ taskView.setShouldShowScreenshot(true);
for (TaskContainer container : taskView.getTaskContainers()) {
if (container == null) {
continue;
@@ -5952,7 +5938,7 @@
Runnable onFinishRunnable) {
final TaskView taskView = getRunningTaskView();
if (taskView != null) {
- taskView.setShowScreenshot(true);
+ taskView.setShouldShowScreenshot(true);
taskView.refreshThumbnails(thumbnailDatas);
ViewUtils.postFrameDrawn(taskView, onFinishRunnable);
} else {
diff --git a/quickstep/src/com/android/quickstep/views/TaskMenuViewWithArrow.kt b/quickstep/src/com/android/quickstep/views/TaskMenuViewWithArrow.kt
index 7adc32e..659cc0c 100644
--- a/quickstep/src/com/android/quickstep/views/TaskMenuViewWithArrow.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskMenuViewWithArrow.kt
@@ -43,10 +43,7 @@
companion object {
const val TAG = "TaskMenuViewWithArrow"
- fun <T> showForTask(
- taskContainer: TaskContainer,
- alignedOptionIndex: Int = 0
- ): Boolean where T : RecentsViewContainer, T : Context {
+ fun showForTask(taskContainer: TaskContainer, alignedOptionIndex: Int = 0): Boolean {
val container: RecentsViewContainer =
RecentsViewContainer.containerFromContext(taskContainer.taskView.context)
val taskMenuViewWithArrow =
@@ -54,7 +51,7 @@
R.layout.task_menu_with_arrow,
container.dragLayer,
false
- ) as TaskMenuViewWithArrow<T>
+ ) as TaskMenuViewWithArrow<*>
return taskMenuViewWithArrow.populateAndShowForTask(taskContainer, alignedOptionIndex)
}
diff --git a/quickstep/src/com/android/quickstep/views/TaskThumbnailViewDeprecated.java b/quickstep/src/com/android/quickstep/views/TaskThumbnailViewDeprecated.java
index 21c6ca8..f7b6496 100644
--- a/quickstep/src/com/android/quickstep/views/TaskThumbnailViewDeprecated.java
+++ b/quickstep/src/com/android/quickstep/views/TaskThumbnailViewDeprecated.java
@@ -54,6 +54,7 @@
import com.android.launcher3.util.SystemUiController;
import com.android.launcher3.util.SystemUiController.SystemUiControllerFlags;
import com.android.launcher3.util.ViewPool;
+import com.android.quickstep.TaskOverlayFactory;
import com.android.quickstep.TaskOverlayFactory.TaskOverlay;
import com.android.quickstep.orientation.RecentsPagedOrientationHandler;
import com.android.quickstep.views.TaskView.FullscreenDrawParams;
@@ -128,6 +129,7 @@
};
private final RecentsViewContainer mContainer;
+ private TaskOverlayFactory mTaskOverlayFactory;
@Nullable
private TaskOverlay mOverlay;
private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
@@ -188,7 +190,8 @@
/**
* Updates the thumbnail to draw the provided task
*/
- public void bind(Task task) {
+ public void bind(Task task, TaskOverlayFactory taskOverlayFactory) {
+ mTaskOverlayFactory = taskOverlayFactory;
getTaskOverlay().reset();
mTask = task;
int color = task == null ? Color.BLACK : task.colorBackground | 0xFF000000;
@@ -281,7 +284,7 @@
public TaskOverlay getTaskOverlay() {
if (mOverlay == null) {
- mOverlay = getTaskView().getRecentsView().getTaskOverlayFactory().createOverlay(this);
+ mOverlay = mTaskOverlayFactory.createOverlay(this);
}
return mOverlay;
}
@@ -348,7 +351,7 @@
canvas.save();
// Draw the insets if we're being drawn fullscreen (we do this for quick switch).
drawOnCanvas(canvas, 0, 0, getMeasuredWidth(), getMeasuredHeight(),
- mFullscreenParams.mCurrentDrawnCornerRadius);
+ mFullscreenParams.getCurrentDrawnCornerRadius());
canvas.restore();
}
@@ -364,7 +367,8 @@
public void drawOnCanvas(Canvas canvas, float x, float y, float width, float height,
float cornerRadius) {
- if (mTask != null && getTaskView().isRunningTask() && !getTaskView().showScreenshot()) {
+ if (mTask != null && getTaskView().isRunningTask()
+ && !getTaskView().getShouldShowScreenshot()) {
canvas.drawRoundRect(x, y, width, height, cornerRadius, cornerRadius, mClearPaint);
canvas.drawRoundRect(x, y, width, height, cornerRadius, cornerRadius,
mDimmingPaintAfterClearing);
@@ -561,8 +565,7 @@
if (mBitmapShader != null && mThumbnailData != null) {
mPreviewRect.set(0, 0, mThumbnailData.thumbnail.getWidth(),
mThumbnailData.thumbnail.getHeight());
- int currentRotation = getTaskView().getRecentsView().getPagedViewOrientedState()
- .getRecentsActivityRotation();
+ int currentRotation = getTaskView().getOrientedState().getRecentsActivityRotation();
boolean isRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
mPreviewPositionHelper.updateThumbnailMatrix(mPreviewRect, mThumbnailData,
getMeasuredWidth(), getMeasuredHeight(), dp.isTablet, currentRotation, isRtl);
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
deleted file mode 100644
index cc24bc3..0000000
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ /dev/null
@@ -1,2060 +0,0 @@
-/*
- * Copyright (C) 2017 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 static android.view.Display.DEFAULT_DISPLAY;
-import static android.widget.Toast.LENGTH_SHORT;
-
-import static com.android.app.animation.Interpolators.FAST_OUT_SLOW_IN;
-import static com.android.app.animation.Interpolators.LINEAR;
-import static com.android.launcher3.Flags.enableCursorHoverStates;
-import static com.android.launcher3.Flags.enableGridOnlyOverview;
-import static com.android.launcher3.Flags.enableOverviewIconMenu;
-import static com.android.launcher3.Flags.enableRefactorTaskThumbnail;
-import static com.android.launcher3.LauncherState.BACKGROUND_APP;
-import static com.android.launcher3.Utilities.getDescendantCoordRelativeToAncestor;
-import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_ICON_TAP_OR_LONGPRESS;
-import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_LAUNCH_TAP;
-import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_NOT_PINNABLE;
-import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
-import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
-import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
-import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_UNDEFINED;
-import static com.android.launcher3.util.SplitConfigurationOptions.getLogEventForPosition;
-import static com.android.quickstep.TaskOverlayFactory.getEnabledShortcuts;
-import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.EXPECTING_TASK_APPEARED;
-import static com.android.quickstep.util.BorderAnimator.DEFAULT_BORDER_COLOR;
-
-import static java.lang.annotation.RetentionPolicy.SOURCE;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
-import android.annotation.IdRes;
-import android.app.ActivityOptions;
-import android.content.Context;
-import android.content.Intent;
-import android.content.res.TypedArray;
-import android.graphics.Canvas;
-import android.graphics.PointF;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.os.Bundle;
-import android.util.AttributeSet;
-import android.util.FloatProperty;
-import android.util.Log;
-import android.view.Display;
-import android.view.MotionEvent;
-import android.view.RemoteAnimationTarget;
-import android.view.TouchDelegate;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewStub;
-import android.view.accessibility.AccessibilityNodeInfo;
-import android.widget.FrameLayout;
-import android.widget.Toast;
-
-import androidx.annotation.IntDef;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.VisibleForTesting;
-
-import com.android.app.animation.Interpolators;
-import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.Flags;
-import com.android.launcher3.LauncherSettings;
-import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.model.data.WorkspaceItemInfo;
-import com.android.launcher3.pm.UserCache;
-import com.android.launcher3.popup.SystemShortcut;
-import com.android.launcher3.testing.TestLogging;
-import com.android.launcher3.testing.shared.TestProtocol;
-import com.android.launcher3.util.ActivityOptionsWrapper;
-import com.android.launcher3.util.CancellableTask;
-import com.android.launcher3.util.ComponentKey;
-import com.android.launcher3.util.RunnableList;
-import com.android.launcher3.util.SafeCloseable;
-import com.android.launcher3.util.SplitConfigurationOptions;
-import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
-import com.android.launcher3.util.TraceHelper;
-import com.android.launcher3.util.TransformingTouchDelegate;
-import com.android.launcher3.util.ViewPool.Reusable;
-import com.android.quickstep.RecentsModel;
-import com.android.quickstep.RemoteAnimationTargets;
-import com.android.quickstep.RemoteTargetGluer.RemoteTargetHandle;
-import com.android.quickstep.TaskAnimationManager;
-import com.android.quickstep.TaskIconCache;
-import com.android.quickstep.TaskThumbnailCache;
-import com.android.quickstep.TaskUtils;
-import com.android.quickstep.TaskViewUtils;
-import com.android.quickstep.orientation.RecentsPagedOrientationHandler;
-import com.android.quickstep.task.thumbnail.TaskThumbnail;
-import com.android.quickstep.task.thumbnail.TaskThumbnailView;
-import com.android.quickstep.task.viewmodel.TaskViewData;
-import com.android.quickstep.util.ActiveGestureLog;
-import com.android.quickstep.util.BorderAnimator;
-import com.android.quickstep.util.RecentsOrientedState;
-import com.android.quickstep.util.SplitSelectStateController;
-import com.android.quickstep.util.TaskCornerRadius;
-import com.android.quickstep.util.TaskRemovedDuringLaunchListener;
-import com.android.systemui.shared.recents.model.Task;
-import com.android.systemui.shared.recents.model.ThumbnailData;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
-import com.android.systemui.shared.system.QuickStepContract;
-
-import kotlin.Unit;
-
-import java.lang.annotation.Retention;
-import java.util.Arrays;
-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;
-
-
-/**
- * A task in the Recents view.
- */
-public class TaskView extends FrameLayout implements Reusable {
-
- private static final String TAG = "TaskView";
- public static final int FLAG_UPDATE_ICON = 1;
- public static final int FLAG_UPDATE_THUMBNAIL = FLAG_UPDATE_ICON << 1;
- public static final int FLAG_UPDATE_CORNER_RADIUS = FLAG_UPDATE_THUMBNAIL << 1;
-
- public static final int FLAG_UPDATE_ALL = FLAG_UPDATE_ICON | FLAG_UPDATE_THUMBNAIL
- | FLAG_UPDATE_CORNER_RADIUS;
-
- /**
- * Used in conjunction with {@link #onTaskListVisibilityChanged(boolean, int)}, providing more
- * granularity on which components of this task require an update
- */
- @Retention(SOURCE)
- @IntDef({FLAG_UPDATE_ALL, FLAG_UPDATE_ICON, FLAG_UPDATE_THUMBNAIL, FLAG_UPDATE_CORNER_RADIUS})
- public @interface TaskDataChanges {
- }
-
- /**
- * Type of task view
- */
- @Retention(SOURCE)
- @IntDef({Type.SINGLE, Type.GROUPED, Type.DESKTOP})
- public @interface Type {
- int SINGLE = 1;
- int GROUPED = 2;
- int DESKTOP = 3;
- }
-
- /** The maximum amount that a task view can be scrimmed, dimmed or tinted. */
- public static final float MAX_PAGE_SCRIM_ALPHA = 0.4f;
-
- private static final float EDGE_SCALE_DOWN_FACTOR_CAROUSEL = 0.03f;
- private static final float EDGE_SCALE_DOWN_FACTOR_GRID = 0.00f;
-
- public static final long SCALE_ICON_DURATION = 120;
- private static final long DIM_ANIM_DURATION = 700;
-
- /**
- * This technically can be a vanilla {@link TouchDelegate} class, however that class requires
- * setting the touch bounds at construction, so we'd repeatedly be created many instances
- * unnecessarily as scrolling occurs, whereas {@link TransformingTouchDelegate} allows touch
- * delegated bounds only to be updated.
- */
- private TransformingTouchDelegate mIconTouchDelegate;
-
- private static final List<Rect> SYSTEM_GESTURE_EXCLUSION_RECT =
- Collections.singletonList(new Rect());
-
- public static final FloatProperty<TaskView> FOCUS_TRANSITION =
- new FloatProperty<>("focusTransition") {
- @Override
- public void setValue(TaskView taskView, float v) {
- taskView.setIconsAndBannersTransitionProgress(v, false /* invert */);
- }
-
- @Override
- public Float get(TaskView taskView) {
- return taskView.mFocusTransitionProgress;
- }
- };
-
- private static final FloatProperty<TaskView> SPLIT_SELECT_TRANSLATION_X =
- new FloatProperty<>("splitSelectTranslationX") {
- @Override
- public void setValue(TaskView taskView, float v) {
- taskView.setSplitSelectTranslationX(v);
- }
-
- @Override
- public Float get(TaskView taskView) {
- return taskView.mSplitSelectTranslationX;
- }
- };
-
- private static final FloatProperty<TaskView> SPLIT_SELECT_TRANSLATION_Y =
- new FloatProperty<>("splitSelectTranslationY") {
- @Override
- public void setValue(TaskView taskView, float v) {
- taskView.setSplitSelectTranslationY(v);
- }
-
- @Override
- public Float get(TaskView taskView) {
- return taskView.mSplitSelectTranslationY;
- }
- };
-
- private static final FloatProperty<TaskView> DISMISS_TRANSLATION_X =
- new FloatProperty<>("dismissTranslationX") {
- @Override
- public void setValue(TaskView taskView, float v) {
- taskView.setDismissTranslationX(v);
- }
-
- @Override
- public Float get(TaskView taskView) {
- return taskView.mDismissTranslationX;
- }
- };
-
- private static final FloatProperty<TaskView> DISMISS_TRANSLATION_Y =
- new FloatProperty<>("dismissTranslationY") {
- @Override
- public void setValue(TaskView taskView, float v) {
- taskView.setDismissTranslationY(v);
- }
-
- @Override
- public Float get(TaskView taskView) {
- return taskView.mDismissTranslationY;
- }
- };
-
- private static final FloatProperty<TaskView> TASK_OFFSET_TRANSLATION_X =
- new FloatProperty<>("taskOffsetTranslationX") {
- @Override
- public void setValue(TaskView taskView, float v) {
- taskView.setTaskOffsetTranslationX(v);
- }
-
- @Override
- public Float get(TaskView taskView) {
- return taskView.mTaskOffsetTranslationX;
- }
- };
-
- private static final FloatProperty<TaskView> TASK_OFFSET_TRANSLATION_Y =
- new FloatProperty<>("taskOffsetTranslationY") {
- @Override
- public void setValue(TaskView taskView, float v) {
- taskView.setTaskOffsetTranslationY(v);
- }
-
- @Override
- public Float get(TaskView taskView) {
- return taskView.mTaskOffsetTranslationY;
- }
- };
-
- private static final FloatProperty<TaskView> TASK_RESISTANCE_TRANSLATION_X =
- new FloatProperty<>("taskResistanceTranslationX") {
- @Override
- public void setValue(TaskView taskView, float v) {
- taskView.setTaskResistanceTranslationX(v);
- }
-
- @Override
- public Float get(TaskView taskView) {
- return taskView.mTaskResistanceTranslationX;
- }
- };
-
- private static final FloatProperty<TaskView> TASK_RESISTANCE_TRANSLATION_Y =
- new FloatProperty<>("taskResistanceTranslationY") {
- @Override
- public void setValue(TaskView taskView, float v) {
- taskView.setTaskResistanceTranslationY(v);
- }
-
- @Override
- public Float get(TaskView taskView) {
- return taskView.mTaskResistanceTranslationY;
- }
- };
-
- public static final FloatProperty<TaskView> GRID_END_TRANSLATION_X =
- new FloatProperty<>("gridEndTranslationX") {
- @Override
- public void setValue(TaskView taskView, float v) {
- taskView.setGridEndTranslationX(v);
- }
-
- @Override
- public Float get(TaskView taskView) {
- return taskView.mGridEndTranslationX;
- }
- };
-
- public static final FloatProperty<TaskView> SNAPSHOT_SCALE =
- new FloatProperty<>("snapshotScale") {
- @Override
- public void setValue(TaskView taskView, float v) {
- taskView.setSnapshotScale(v);
- }
-
- @Override
- public Float get(TaskView taskView) {
- return taskView.mTaskThumbnailViewDeprecated.getScaleX();
- }
- };
-
- public TaskViewData mTaskViewData = new TaskViewData();
- protected TaskThumbnailViewDeprecated mTaskThumbnailViewDeprecated;
- protected TaskThumbnailView mTaskThumbnailView;
- protected TaskViewIcon mIconView;
- protected final DigitalWellBeingToast mDigitalWellBeingToast;
- protected float mFullscreenProgress;
- private float mGridProgress;
- protected float mTaskThumbnailSplashAlpha;
- private float mNonGridScale = 1;
- private float mDismissScale = 1;
- protected final FullscreenDrawParams mCurrentFullscreenParams;
- protected final RecentsViewContainer mContainer;
-
- // Various causes of changing primary translation, which we aggregate to setTranslationX/Y().
- private float mDismissTranslationX;
- private float mDismissTranslationY;
- private float mTaskOffsetTranslationX;
- private float mTaskOffsetTranslationY;
- private float mTaskResistanceTranslationX;
- private float mTaskResistanceTranslationY;
- // The following translation variables should only be used in the same orientation as Launcher.
- private float mBoxTranslationY;
- // The following grid translations scales with mGridProgress.
- private float mGridTranslationX;
- private float mGridTranslationY;
- // The following grid translation is used to animate closing the gap between grid and clear all.
- private float mGridEndTranslationX;
- // Applied as a complement to gridTranslation, for adjusting the carousel overview and quick
- // switch.
- private float mNonGridTranslationX;
- private float mNonGridPivotTranslationX;
- // Used when in SplitScreenSelectState
- private float mSplitSelectTranslationY;
- private float mSplitSelectTranslationX;
-
- @Nullable
- private ObjectAnimator mIconAndDimAnimator;
- private float mIconScaleAnimStartProgress = 0;
- private float mFocusTransitionProgress = 1;
- private float mModalness = 0;
- private float mStableAlpha = 1;
-
- private int mTaskViewId = -1;
- protected List<TaskContainer> mTaskContainers = Collections.emptyList();
-
- private boolean mShowScreenshot;
- private boolean mBorderEnabled;
-
- // The current background requests to load the task thumbnail and icon
- @Nullable
- private CancellableTask mThumbnailLoadRequest;
- @Nullable
- private CancellableTask mIconLoadRequest;
-
- private boolean mEndQuickswitchCuj;
-
- private final float[] mIconCenterCoords = new float[2];
-
- protected final PointF mLastTouchDownPosition = new PointF();
-
- private boolean mIsClickableAsLiveTile = true;
-
- @Nullable
- private final BorderAnimator mFocusBorderAnimator;
-
- @Nullable
- private final BorderAnimator mHoverBorderAnimator;
-
- public TaskView(Context context) {
- this(context, null);
- }
-
- public TaskView(Context context, @Nullable AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public TaskView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
- this(context, attrs, defStyleAttr, 0);
- }
-
- public TaskView(Context context, @Nullable AttributeSet attrs, int defStyleAttr,
- int defStyleRes) {
- this(context, attrs, defStyleAttr, defStyleRes, null, null);
- }
-
- @VisibleForTesting
- public TaskView(Context context, @Nullable AttributeSet attrs, int defStyleAttr,
- int defStyleRes, BorderAnimator focusBorderAnimator,
- BorderAnimator hoverBorderAnimator) {
- super(context, attrs, defStyleAttr, defStyleRes);
- mContainer = RecentsViewContainer.containerFromContext(context);
- setOnClickListener(this::onClick);
-
- mCurrentFullscreenParams = new FullscreenDrawParams(context);
- mDigitalWellBeingToast = new DigitalWellBeingToast(mContainer, this);
-
- boolean keyboardFocusHighlightEnabled = FeatureFlags.ENABLE_KEYBOARD_QUICK_SWITCH.get()
- || Flags.enableFocusOutline();
- boolean cursorHoverStatesEnabled = enableCursorHoverStates();
-
- setWillNotDraw(!keyboardFocusHighlightEnabled && !cursorHoverStatesEnabled);
-
- TypedArray styledAttrs = context.obtainStyledAttributes(
- attrs, R.styleable.TaskView, defStyleAttr, defStyleRes);
-
- if (focusBorderAnimator != null) {
- mFocusBorderAnimator = focusBorderAnimator;
- } else {
- mFocusBorderAnimator = keyboardFocusHighlightEnabled
- ? BorderAnimator.createSimpleBorderAnimator(
- /* borderRadiusPx= */ (int) mCurrentFullscreenParams.mCornerRadius,
- /* borderWidthPx= */ context.getResources().getDimensionPixelSize(
- R.dimen.keyboard_quick_switch_border_width),
- /* boundsBuilder= */ this::getThumbnailBounds,
- /* targetView= */ this,
- /* borderColor= */ styledAttrs.getColor(
- R.styleable.TaskView_focusBorderColor, DEFAULT_BORDER_COLOR))
- : null;
- }
-
- if (hoverBorderAnimator != null) {
- mHoverBorderAnimator = hoverBorderAnimator;
- } else {
- mHoverBorderAnimator = cursorHoverStatesEnabled
- ? BorderAnimator.createSimpleBorderAnimator(
- /* borderRadiusPx= */ (int) mCurrentFullscreenParams.mCornerRadius,
- /* borderWidthPx= */ context.getResources().getDimensionPixelSize(
- R.dimen.task_hover_border_width),
- /* boundsBuilder= */ this::getThumbnailBounds,
- /* targetView= */ this,
- /* borderColor= */ styledAttrs.getColor(
- R.styleable.TaskView_hoverBorderColor, DEFAULT_BORDER_COLOR))
- : null;
- }
- styledAttrs.recycle();
- }
-
- /** 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();
-
- 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;
- }
-
- public void setTaskViewId(int id) {
- this.mTaskViewId = id;
- }
-
- public int getTaskViewId() {
- return mTaskViewId;
- }
-
- /**
- * Updates [TaskThumbnailView] to reflect the latest [Task] state (i.e., task isRunning).
- */
- public void notifyIsRunningTaskUpdated() {
- // TODO(b/335649589): TaskView's VM will already have access to TaskThumbnailView's VM
- // so there will be no need to access TaskThumbnailView's VM through the TaskThumbnailView
- if (!mTaskContainers.isEmpty()) {
- bindTaskThumbnailView();
- }
- }
-
- /**
- * Builds proto for logging.
- *
- * @deprecated Use {@link #getItemInfo(Task)} instead.
- */
- @Deprecated
- public WorkspaceItemInfo getItemInfo() {
- return getItemInfo(getFirstTask());
- }
-
- /**
- * Builds proto for logging
- */
- @VisibleForTesting
- public WorkspaceItemInfo getItemInfo(@Nullable Task task) {
- WorkspaceItemInfo stubInfo = new WorkspaceItemInfo();
- stubInfo.itemType = LauncherSettings.Favorites.ITEM_TYPE_TASK;
- stubInfo.container = LauncherSettings.Favorites.CONTAINER_TASKSWITCHER;
- if (task == null) {
- return stubInfo;
- }
-
- ComponentKey componentKey = TaskUtils.getLaunchComponentKeyForTask(task.key);
- stubInfo.user = componentKey.user;
- stubInfo.intent = new Intent().setComponent(componentKey.componentName);
- stubInfo.title = task.title;
- if (getRecentsView() != null) {
- stubInfo.screenId = getRecentsView().indexOfChild(this);
- }
- if (Flags.privateSpaceRestrictAccessibilityDrag()) {
- if (UserCache.getInstance(getContext()).getUserInfo(componentKey.user).isPrivate()) {
- stubInfo.runtimeStatusFlags |= FLAG_NOT_PINNABLE;
- }
- }
- return stubInfo;
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- mTaskThumbnailViewDeprecated = findViewById(R.id.snapshot);
- if (enableRefactorTaskThumbnail()) {
- mTaskThumbnailView = new TaskThumbnailView(mContext);
- mTaskThumbnailView.setLayoutParams(mTaskThumbnailViewDeprecated.getLayoutParams());
- int indexOfSnapshotView = indexOfChild(mTaskThumbnailViewDeprecated);
- addView(mTaskThumbnailView, indexOfSnapshotView);
- mTaskThumbnailViewDeprecated.setVisibility(View.GONE);
- }
- ViewStub iconViewStub = findViewById(R.id.icon);
- if (enableOverviewIconMenu()) {
- iconViewStub.setLayoutResource(R.layout.icon_app_chip_view);
- } else {
- iconViewStub.setLayoutResource(R.layout.icon_view);
- }
- mIconView = (TaskViewIcon) iconViewStub.inflate();
- mIconTouchDelegate = new TransformingTouchDelegate(mIconView.asView());
- }
-
- @Override
- @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
- public void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
- super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
- if (mFocusBorderAnimator != null && mBorderEnabled) {
- mFocusBorderAnimator.setBorderVisibility(gainFocus, /* animated= */ true);
- }
- }
-
- @Override
- public boolean onHoverEvent(MotionEvent event) {
- if (mHoverBorderAnimator != null && mBorderEnabled) {
- switch (event.getAction()) {
- case MotionEvent.ACTION_HOVER_ENTER:
- mHoverBorderAnimator.setBorderVisibility(/* visible= */ true, /* animated= */
- true);
- break;
- case MotionEvent.ACTION_HOVER_EXIT:
- mHoverBorderAnimator.setBorderVisibility(/* visible= */ false, /* animated= */
- true);
- break;
- default:
- break;
- }
- }
- return super.onHoverEvent(event);
- }
-
- /**
- * 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
- if (mHoverBorderAnimator != null) {
- mHoverBorderAnimator.setBorderVisibility(/* visible= */
- enabled && isHovered(), /* animated= */ true);
- }
-
- if (mFocusBorderAnimator != null) {
- mFocusBorderAnimator.setBorderVisibility(/* visible= */
- enabled && isFocused(), /* animated= */true);
- }
- }
-
- @Override
- public boolean onInterceptHoverEvent(MotionEvent event) {
- if (enableCursorHoverStates()) {
- // avoid triggering hover event on child elements which would cause HOVER_EXIT for this
- // task view
- return true;
- } else {
- return super.onInterceptHoverEvent(event);
- }
- }
-
- @Override
- public void draw(Canvas canvas) {
- // Draw border first so any child views outside of the thumbnail bounds are drawn above it.
- if (mFocusBorderAnimator != null) {
- mFocusBorderAnimator.drawBorder(canvas);
- }
- if (mHoverBorderAnimator != null) {
- mHoverBorderAnimator.drawBorder(canvas);
- }
- super.draw(canvas);
- }
-
- /**
- * Whether the taskview should take the touch event from parent. Events passed to children
- * that might require special handling.
- */
- public boolean offerTouchToChildren(MotionEvent event) {
- if (event.getAction() == MotionEvent.ACTION_DOWN) {
- computeAndSetIconTouchDelegate(mIconView, mIconCenterCoords, mIconTouchDelegate);
- }
- return mIconTouchDelegate != null && mIconTouchDelegate.onTouchEvent(event);
- }
-
- protected void computeAndSetIconTouchDelegate(TaskViewIcon view, float[] tempCenterCoords,
- TransformingTouchDelegate transformingTouchDelegate) {
- if (view == null) {
- return;
- }
- float viewHalfWidth = view.getWidth() / 2f;
- float viewHalfHeight = view.getHeight() / 2f;
- tempCenterCoords[0] = viewHalfWidth;
- tempCenterCoords[1] = viewHalfHeight;
- getDescendantCoordRelativeToAncestor(view.asView(), mContainer.getDragLayer(),
- tempCenterCoords, false);
- transformingTouchDelegate.setBounds(
- (int) (tempCenterCoords[0] - viewHalfWidth),
- (int) (tempCenterCoords[1] - viewHalfHeight),
- (int) (tempCenterCoords[0] + viewHalfWidth),
- (int) (tempCenterCoords[1] + viewHalfHeight));
- }
-
- /**
- * The modalness of this view is how it should be displayed when it is shown on its own in the
- * modal state of overview.
- *
- * @param modalness [0, 1] 0 being in context with other tasks, 1 being shown on its own.
- */
- public void setModalness(float modalness) {
- if (mModalness == modalness) {
- return;
- }
- mModalness = modalness;
- mIconView.setModalAlpha(1 - modalness);
- mDigitalWellBeingToast.updateBannerOffset(modalness);
- }
-
- public DigitalWellBeingToast getDigitalWellBeingToast() {
- return mDigitalWellBeingToast;
- }
-
- /**
- * Updates this task view to the given {@param task}.
- */
- public void bind(Task task, RecentsOrientedState orientedState) {
- cancelPendingLoadTasks();
- setupTaskContainers(task);
- setOrientationState(orientedState);
- }
-
- protected void setupTaskContainers(Task task) {
- mTaskContainers = Collections.singletonList(
- new TaskContainer(task, mTaskThumbnailViewDeprecated, mIconView,
- STAGE_POSITION_UNDEFINED, mDigitalWellBeingToast));
- if (enableRefactorTaskThumbnail()) {
- bindTaskThumbnailView();
- } else {
- mTaskThumbnailViewDeprecated.bind(task);
- }
- }
-
- private void bindTaskThumbnailView() {
- // TODO(b/335649589): TaskView's VM will already have access to TaskThumbnailView's VM
- // so there will be no need to access TaskThumbnailView's VM through the TaskThumbnailView
- mTaskThumbnailView.getViewModel().bind(new TaskThumbnail(getFirstTask(), isRunningTask()));
- }
-
- /**
- * Sets up an on-click listener and the visibility for show_windows icon on top of the task.
- */
- public void setUpShowAllInstancesListener() {
- if (mTaskContainers.isEmpty()) {
- return;
- }
- String taskPackageName = mTaskContainers.get(0).getTask().key.getPackageName();
-
- // icon of the top/left task
- View showWindowsView = findViewById(R.id.show_windows);
- updateFilterCallback(showWindowsView, getFilterUpdateCallback(taskPackageName));
- }
-
- /**
- * Returns a callback that updates the state of the filter and the recents overview
- *
- * @param taskPackageName package name of the task to filter by
- */
- @Nullable
- protected View.OnClickListener getFilterUpdateCallback(String taskPackageName) {
- View.OnClickListener cb = (view) -> {
- // update and apply a new filter
- getRecentsView().setAndApplyFilter(taskPackageName);
- };
-
- if (!getRecentsView().getFilterState().shouldShowFilterUI(taskPackageName)) {
- cb = null;
- }
- return cb;
- }
-
- /**
- * Sets the correct visibility and callback on the provided filterView based on whether
- * the callback is null or not
- */
- protected void updateFilterCallback(@NonNull View filterView,
- @Nullable View.OnClickListener callback) {
- // Filtering changes alpha instead of the visibility since visibility
- // can be altered separately through RecentsView#resetFromSplitSelectionState()
- if (callback == null) {
- filterView.setAlpha(0);
- } else {
- filterView.setAlpha(1);
- }
-
- filterView.setOnClickListener(callback);
- }
-
- /**
- * Returns a list of all TaskContainers in the TaskView.
- */
- public List<TaskContainer> getTaskContainers() {
- return mTaskContainers;
- }
-
- /**
- * Returns the first task bound to this TaskView.
- *
- * @deprecated Use {@link #mTaskContainers} instead.
- */
- @Deprecated
- @Nullable
- public Task getFirstTask() {
- return !mTaskContainers.isEmpty() ? mTaskContainers.get(0).getTask() : null;
- }
-
- /**
- * Check if given {@code taskId} is tracked in this view
- */
- public boolean containsTaskId(int taskId) {
- return getTaskContainerById(taskId) != null;
- }
-
- /**
- * Returns a copy of integer array containing taskIds of all tasks in the TaskView.
- */
- public int[] getTaskIds() {
- return mTaskContainers.stream().mapToInt(
- container -> container.getTask().key.id).toArray();
- }
-
- public boolean containsMultipleTasks() {
- return mTaskContainers.size() > 1;
- }
-
- /**
- * Returns the TaskContainer corresponding to a given taskId, or null if the TaskView does
- * not contain a Task with that ID.
- */
- @Nullable
- public TaskContainer getTaskContainerById(int taskId) {
- return mTaskContainers.stream().filter(
- container -> container.getTask().key.id == taskId).findFirst().orElse(null);
- }
-
- /**
- * Returns the first thumbnailView of the TaskView.
- *
- * @deprecated Use {@link #mTaskContainers} instead.
- */
- @Deprecated
- public TaskThumbnailViewDeprecated getFirstThumbnailView() {
- return !mTaskContainers.isEmpty() ? mTaskContainers.get(0).getThumbnailView()
- : mTaskThumbnailViewDeprecated;
- }
-
- void refreshThumbnails(@Nullable HashMap<Integer, ThumbnailData> thumbnailDatas) {
- if (enableRefactorTaskThumbnail()) {
- // TODO(b/334825222) add thumbnail logic
- return;
- }
- if (getFirstTask() != null && thumbnailDatas != null) {
- final ThumbnailData thumbnailData = thumbnailDatas.get(getFirstTask().key.id);
- if (thumbnailData != null) {
- mTaskThumbnailViewDeprecated.setThumbnail(getFirstTask(), thumbnailData);
- return;
- }
- }
-
- mTaskThumbnailViewDeprecated.refresh();
- }
-
- public TaskThumbnailViewDeprecated[] getThumbnailViews() {
- return mTaskContainers.stream().map(
- TaskContainer::getThumbnailView).toArray(
- TaskThumbnailViewDeprecated[]::new);
- }
-
- /**
- * Returns the first iconView of the TaskView.
- *
- * @deprecated Use {@link #mTaskContainers} instead.
- */
- @Deprecated
- @Nullable
- public TaskViewIcon getFirstIconView() {
- return !mTaskContainers.isEmpty() ? mTaskContainers.get(0).getIconView() : null;
- }
-
- @Override
- public boolean dispatchTouchEvent(MotionEvent ev) {
- RecentsView recentsView = getRecentsView();
- if (recentsView == null || getFirstTask() == null) {
- return false;
- }
- SplitSelectStateController splitSelectStateController =
- recentsView.getSplitSelectController();
- // Disable taps for split selection animation unless we have multiple tasks
- boolean disableTapsForSplitSelect =
- splitSelectStateController.isSplitSelectActive()
- && splitSelectStateController.getInitialTaskId() == getFirstTask().key.id
- && !containsMultipleTasks();
- if (disableTapsForSplitSelect) {
- return false;
- }
-
- if (ev.getAction() == MotionEvent.ACTION_DOWN) {
- mLastTouchDownPosition.set(ev.getX(), ev.getY());
- }
- return super.dispatchTouchEvent(ev);
- }
-
- private void onClick(View view) {
- if (getFirstTask() == null) {
- Log.d("b/310064698", "onClick - task is null");
- return;
- }
- if (confirmSecondSplitSelectApp()) {
- Log.d("b/310064698",
- Arrays.toString(getTaskIds()) + " - onClick - split select is active");
- return;
- }
- RunnableList callbackList = launchTasks();
- Log.d("b/310064698",
- Arrays.toString(getTaskIds()) + " - onClick - callbackList: " + callbackList);
- if (callbackList != null) {
- callbackList.add(() -> Log.d("b/310064698",
- Arrays.toString(getTaskIds()) + " - onClick - launchCompleted"));
- }
- mContainer.getStatsLogManager().logger().withItemInfo(getItemInfo())
- .log(LAUNCHER_TASK_LAUNCH_TAP);
- }
-
- /**
- * @return {@code true} if user is already in split select mode and this tap was to choose the
- * second app. {@code false} otherwise
- */
- protected boolean confirmSecondSplitSelectApp() {
- int index = getLastSelectedChildTaskIndex();
- if (index >= mTaskContainers.size()) {
- return false;
- }
- TaskContainer container = mTaskContainers.get(index);
- if (container != null) {
- return getRecentsView().confirmSplitSelect(this, container.getTask(),
- container.getIconView().getDrawable(), container.getThumbnailView(),
- container.getThumbnailView().getThumbnail(), /* intent */ null,
- /* user */ null, container.getItemInfo());
- }
- return false;
- }
-
- /**
- * Returns the task index of the last selected child task (0 or 1).
- * If we contain multiple tasks and this TaskView is used as part of split selection, the
- * selected child task index will be that of the remaining task.
- */
- protected int getLastSelectedChildTaskIndex() {
- return 0;
- }
-
- /**
- * 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
- public RunnableList launchTaskAnimated() {
- if (getFirstTask() != null) {
- TestLogging.recordEvent(
- TestProtocol.SEQUENCE_MAIN, "startActivityFromRecentsAsync", Arrays.toString(
- getTaskIds()));
- ActivityOptionsWrapper opts = mContainer.getActivityLaunchOptions(this, null);
- opts.options.setLaunchDisplayId(
- getDisplay() == null ? DEFAULT_DISPLAY : getDisplay().getDisplayId());
- if (ActivityManagerWrapper.getInstance()
- .startActivityFromRecents(getFirstTask().key, opts.options)) {
- Log.d(TAG, "launchTaskAnimated - startActivityFromRecents: " + Arrays.toString(
- getTaskIds()));
- ActiveGestureLog.INSTANCE.trackEvent(EXPECTING_TASK_APPEARED);
- RecentsView recentsView = getRecentsView();
- if (recentsView.getRunningTaskViewId() != -1) {
- recentsView.onTaskLaunchedInLiveTileMode();
-
- // Return a fresh callback in the live tile case, so that it's not accidentally
- // triggered by QuickstepTransitionManager.AppLaunchAnimationRunner.
- RunnableList callbackList = new RunnableList();
- recentsView.addSideTaskLaunchCallback(callbackList);
- return callbackList;
- }
- if (TaskAnimationManager.ENABLE_SHELL_TRANSITIONS) {
- // If the recents transition is running (ie. in live tile mode), then the start
- // of a new task will merge into the existing transition and it currently will
- // not be run independently, so we need to rely on the onTaskAppeared() call
- // for the new task to trigger the side launch callback to flush this runnable
- // list (which is usually flushed when the app launch animation finishes)
- recentsView.addSideTaskLaunchCallback(opts.onEndCallback);
- }
- return opts.onEndCallback;
- } else {
- notifyTaskLaunchFailed(TAG);
- return null;
- }
- } else {
- Log.d(TAG, "launchTaskAnimated - getTask() is null" + Arrays.toString(getTaskIds()));
- return null;
- }
- }
-
- /**
- * Starts the task associated with this view without any animation
- */
- public void launchTask(@NonNull Consumer<Boolean> callback) {
- launchTask(callback, false /* isQuickswitch */);
- }
-
- /**
- * Starts the task associated with this view without any animation
- */
- public void launchTask(@NonNull Consumer<Boolean> callback, boolean isQuickswitch) {
- if (getFirstTask() != null) {
- TestLogging.recordEvent(
- TestProtocol.SEQUENCE_MAIN, "startActivityFromRecentsAsync", Arrays.toString(
- getTaskIds()));
-
- TaskRemovedDuringLaunchListener failureListener = new TaskRemovedDuringLaunchListener(
- getContext().getApplicationContext());
- if (isQuickswitch) {
- // We only listen for failures to launch in quickswitch because the during this
- // gesture launcher is in the background state, vs other launches which are in
- // the actual overview state
- failureListener.register(mContainer, getFirstTask().key.id, () -> {
- notifyTaskLaunchFailed(TAG);
- RecentsView rv = getRecentsView();
- if (rv != null) {
- // Disable animations for now, as it is an edge case and the app usually
- // covers launcher and also any state transition animation also gets
- // clobbered by QuickstepTransitionManager.createWallpaperOpenAnimations
- // when launcher shows again
- rv.startHome(false /* animated */);
- if (rv.mSizeStrategy.getTaskbarController() != null) {
- // LauncherTaskbarUIController depends on the launcher state when
- // checking whether to handle resume, but that can come in before
- // startHome() changes the state, so force-refresh here to ensure the
- // taskbar is updated
- rv.mSizeStrategy.getTaskbarController().refreshResumedState();
- }
- }
- });
- }
- // Indicate success once the system has indicated that the transition has started
- ActivityOptions opts = ActivityOptions.makeCustomTaskAnimation(getContext(), 0, 0,
- MAIN_EXECUTOR.getHandler(),
- elapsedRealTime -> callback.accept(true),
- elapsedRealTime -> failureListener.onTransitionFinished());
- opts.setLaunchDisplayId(
- getDisplay() == null ? DEFAULT_DISPLAY : getDisplay().getDisplayId());
- if (isQuickswitch) {
- opts.setFreezeRecentTasksReordering();
- }
- // TODO(b/334826842) add splash functionality to new TTV
- if (!enableRefactorTaskThumbnail()) {
- opts.setDisableStartingWindow(mTaskThumbnailViewDeprecated.shouldShowSplashView());
- }
- Task.TaskKey key = getFirstTask().key;
- UI_HELPER_EXECUTOR.execute(() -> {
- if (!ActivityManagerWrapper.getInstance().startActivityFromRecents(key, opts)) {
- // If the call to start activity failed, then post the result immediately,
- // otherwise, wait for the animation start callback from the activity options
- // above
- MAIN_EXECUTOR.post(() -> {
- notifyTaskLaunchFailed(TAG);
- callback.accept(false);
- });
- }
- Log.d(TAG,
- "launchTask - startActivityFromRecents: " + Arrays.toString(getTaskIds()));
- });
- } else {
- callback.accept(false);
- Log.d(TAG, "launchTask - getTask() is null" + Arrays.toString(getTaskIds()));
- }
- }
-
- /**
- * Launch of the current task (both live and inactive tasks) with an animation.
- */
- @Nullable
- public RunnableList launchTasks() {
- RecentsView recentsView = getRecentsView();
- RemoteTargetHandle[] remoteTargetHandles = recentsView.mRemoteTargetHandles;
- if (isRunningTask() && remoteTargetHandles != null) {
- if (!mIsClickableAsLiveTile) {
- Log.e(TAG, "TaskView is not clickable as a live tile; returning to home.");
- return null;
- }
-
- mIsClickableAsLiveTile = false;
- RemoteAnimationTargets targets;
- if (remoteTargetHandles.length == 1) {
- targets = remoteTargetHandles[0].getTransformParams().getTargetSet();
- } else {
- RemoteAnimationTarget[] apps = Arrays.stream(remoteTargetHandles)
- .flatMap(handle -> Stream.of(
- handle.getTransformParams().getTargetSet().apps))
- .toArray(RemoteAnimationTarget[]::new);
- RemoteAnimationTarget[] wallpapers = Arrays.stream(remoteTargetHandles)
- .flatMap(handle -> Stream.of(
- handle.getTransformParams().getTargetSet().wallpapers))
- .toArray(RemoteAnimationTarget[]::new);
- targets = new RemoteAnimationTargets(apps, wallpapers,
- remoteTargetHandles[0].getTransformParams().getTargetSet().nonApps,
- remoteTargetHandles[0].getTransformParams().getTargetSet().targetMode);
- }
- if (targets == null) {
- // If the recents animation is cancelled somehow between the parent if block and
- // here, try to launch the task as a non live tile task.
- RunnableList runnableList = launchTaskAnimated();
- if (runnableList == null) {
- Log.e(TAG, "Recents animation cancelled and cannot launch task as non-live tile"
- + "; returning to home");
- }
- mIsClickableAsLiveTile = true;
- return runnableList;
- }
-
- RunnableList runnableList = new RunnableList();
- AnimatorSet anim = new AnimatorSet();
- TaskViewUtils.composeRecentsLaunchAnimator(
- anim, this, targets.apps,
- targets.wallpapers, targets.nonApps, true /* launcherClosing */,
- recentsView.getStateManager(), recentsView,
- recentsView.getDepthController());
- anim.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animator) {
- if (getFirstTask() != null
- && getFirstTask().key.displayId != getRootViewDisplayId()) {
- launchTaskAnimated();
- }
- mIsClickableAsLiveTile = true;
- runEndCallback();
- }
-
- @Override
- public void onAnimationCancel(Animator animation) {
- runEndCallback();
- }
-
- private void runEndCallback() {
- runnableList.executeAllAndDestroy();
- }
- });
- anim.start();
- Log.d(TAG, "launchTasks - composeRecentsLaunchAnimator: " + Arrays.toString(
- getTaskIds()));
- recentsView.onTaskLaunchedInLiveTileMode();
- return runnableList;
- } else {
- return launchTaskAnimated();
- }
- }
-
- /**
- * See {@link TaskDataChanges}
- *
- * @param visible If this task view will be visible to the user in overview or hidden
- */
- public void onTaskListVisibilityChanged(boolean visible) {
- onTaskListVisibilityChanged(visible, FLAG_UPDATE_ALL);
- }
-
- /**
- * 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) {
- if (getFirstTask() == null) {
- return;
- }
- cancelPendingLoadTasks();
- if (visible) {
- // These calls are no-ops if the data is already loaded, try and load the high
- // resolution thumbnail if the state permits
- RecentsModel model = RecentsModel.INSTANCE.get(getContext());
- TaskThumbnailCache thumbnailCache = model.getThumbnailCache();
- TaskIconCache iconCache = model.getIconCache();
-
- if (needsUpdate(changes, FLAG_UPDATE_THUMBNAIL)) {
- mThumbnailLoadRequest = thumbnailCache.updateThumbnailInBackground(
- getFirstTask(), thumbnail -> {
- if (!enableRefactorTaskThumbnail()) {
- // TODO(b/334825222) add thumbnail state
- mTaskThumbnailViewDeprecated.setThumbnail(getFirstTask(),
- thumbnail);
- }
- });
- }
- if (needsUpdate(changes, FLAG_UPDATE_ICON)) {
- mIconLoadRequest = iconCache.updateIconInBackground(getFirstTask(),
- (task) -> {
- setIcon(mIconView, task.icon);
- if (enableOverviewIconMenu()) {
- setText(mIconView, task.title);
- }
- mDigitalWellBeingToast.initialize(task);
- });
- }
- if (needsUpdate(changes, FLAG_UPDATE_CORNER_RADIUS)) {
- mCurrentFullscreenParams.updateCornerRadius(getContext());
- }
- } else {
- if (needsUpdate(changes, FLAG_UPDATE_THUMBNAIL)) {
- if (!enableRefactorTaskThumbnail()) {
- // TODO(b/334825222) add thumbnail state
- mTaskThumbnailViewDeprecated.setThumbnail(null, null);
- }
- // Reset the task thumbnail reference as well (it will be fetched from the cache or
- // reloaded next time we need it)
- getFirstTask().thumbnail = null;
- }
- if (needsUpdate(changes, FLAG_UPDATE_ICON)) {
- setIcon(mIconView, null);
- if (enableOverviewIconMenu()) {
- setText(mIconView, null);
- }
- }
- }
- }
-
- protected boolean needsUpdate(@TaskDataChanges int dataChange, @TaskDataChanges int flag) {
- return (dataChange & flag) == flag;
- }
-
- protected void cancelPendingLoadTasks() {
- if (mThumbnailLoadRequest != null) {
- mThumbnailLoadRequest.cancel();
- mThumbnailLoadRequest = null;
- }
- if (mIconLoadRequest != null) {
- mIconLoadRequest.cancel();
- mIconLoadRequest = null;
- }
- }
-
- private boolean showTaskMenu(TaskViewIcon iconView) {
- if (!getRecentsView().canLaunchFullscreenTask()) {
- // Don't show menu when selecting second split screen app
- return true;
- }
-
- if (!mContainer.getDeviceProfile().isTablet
- && !getRecentsView().isClearAllHidden()) {
- getRecentsView().snapToPage(getRecentsView().indexOfChild(this));
- return false;
- } else {
- mContainer.getStatsLogManager().logger().withItemInfo(getItemInfo())
- .log(LAUNCHER_TASK_ICON_TAP_OR_LONGPRESS);
- return showTaskMenuWithContainer(iconView);
- }
- }
-
- protected boolean showTaskMenuWithContainer(TaskViewIcon iconView) {
- Optional<TaskContainer> menuContainer = mTaskContainers.stream().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.get(),
- () -> ((IconAppChipView) iconView).revealAnim(/* isRevealing= */ false));
- } else if (dp.isTablet) {
- int alignedOptionIndex = 0;
- 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 {
- // Bottom row of landscape grid aligns arrow to second option to avoid clipping
- alignedOptionIndex = 1;
- }
- }
- return TaskMenuViewWithArrow.Companion.showForTask(menuContainer.get(),
- alignedOptionIndex);
- } else {
- return TaskMenuView.showForTask(menuContainer.get());
- }
- }
-
- protected void setIcon(TaskViewIcon iconView, @Nullable Drawable icon) {
- if (icon != null) {
- iconView.setDrawable(icon);
- iconView.setOnClickListener(v -> {
- if (confirmSecondSplitSelectApp()) {
- return;
- }
- showTaskMenu(iconView);
- });
- iconView.setOnLongClickListener(v -> {
- requestDisallowInterceptTouchEvent(true);
- return showTaskMenu(iconView);
- });
- } else {
- iconView.setDrawable(null);
- iconView.setOnClickListener(null);
- iconView.setOnLongClickListener(null);
- }
- }
-
- protected void setText(TaskViewIcon iconView, CharSequence text) {
- iconView.setText(text);
- }
-
- public void setOrientationState(RecentsOrientedState orientationState) {
- mIconView.setIconOrientation(orientationState, isGridTask());
- setThumbnailOrientation(orientationState);
- }
-
- protected void setThumbnailOrientation(RecentsOrientedState orientationState) {
- DeviceProfile deviceProfile = mContainer.getDeviceProfile();
- int thumbnailTopMargin = deviceProfile.overviewTaskThumbnailTopMarginPx;
-
- // TODO(b/271468547), we should default to setting trasnlations only on the snapshot instead
- // of a hybrid of both margins and translations
- LayoutParams snapshotParams = (LayoutParams) getSnapshotView().getLayoutParams();
- snapshotParams.topMargin = thumbnailTopMargin;
- getSnapshotView().setLayoutParams(snapshotParams);
-
- // TODO(b/335606129) Investigate the usage of [TaskOverlay] in the new TaskThumbnailView.
- // and if it's still necessary we should support that in the new TTV class.
- if (!enableRefactorTaskThumbnail()) {
- mTaskThumbnailViewDeprecated.getTaskOverlay().updateOrientationState(orientationState);
- }
- mDigitalWellBeingToast.initialize(getFirstTask());
- }
-
- /**
- * Returns whether the task is part of overview grid and not being focused.
- */
- public boolean isGridTask() {
- DeviceProfile deviceProfile = mContainer.getDeviceProfile();
- return deviceProfile.isTablet && !isFocusedTask();
- }
-
- /**
- * Called to animate a smooth transition when going directly from an app into Overview (and
- * vice versa). Icons fade in, and DWB banners slide in with a "shift up" animation.
- */
- protected void setIconsAndBannersTransitionProgress(float progress, boolean invert) {
- if (invert) {
- progress = 1 - progress;
- }
- mFocusTransitionProgress = progress;
- float iconScalePercentage = (float) SCALE_ICON_DURATION / DIM_ANIM_DURATION;
- float lowerClamp = invert ? 1f - iconScalePercentage : 0;
- float upperClamp = invert ? 1 : iconScalePercentage;
- float scale = Interpolators.clampToProgress(FAST_OUT_SLOW_IN, lowerClamp, upperClamp)
- .getInterpolation(progress);
- mIconView.setContentAlpha(scale);
- mDigitalWellBeingToast.updateBannerOffset(1f - scale);
- }
-
- public void setIconScaleAnimStartProgress(float startProgress) {
- mIconScaleAnimStartProgress = startProgress;
- }
-
- public void animateIconScaleAndDimIntoView() {
- if (mIconAndDimAnimator != null) {
- mIconAndDimAnimator.cancel();
- }
- mIconAndDimAnimator = ObjectAnimator.ofFloat(this, FOCUS_TRANSITION, 1);
- mIconAndDimAnimator.setCurrentFraction(mIconScaleAnimStartProgress);
- mIconAndDimAnimator.setDuration(DIM_ANIM_DURATION).setInterpolator(LINEAR);
- mIconAndDimAnimator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- mIconAndDimAnimator = null;
- }
- });
- mIconAndDimAnimator.start();
- }
-
- protected void setIconScaleAndDim(float iconScale) {
- setIconScaleAndDim(iconScale, false);
- }
-
- private void setIconScaleAndDim(float iconScale, boolean invert) {
- if (mIconAndDimAnimator != null) {
- mIconAndDimAnimator.cancel();
- }
- setIconsAndBannersTransitionProgress(iconScale, invert);
- }
-
- protected void resetPersistentViewTransforms() {
- mNonGridTranslationX = mGridTranslationX =
- mGridTranslationY = mBoxTranslationY = mNonGridPivotTranslationX = 0f;
- resetViewTransforms();
- }
-
- protected void resetViewTransforms() {
- // fullscreenTranslation and accumulatedTranslation should not be reset, as
- // resetViewTransforms is called during Quickswitch scrolling.
- mDismissTranslationX = mTaskOffsetTranslationX =
- mTaskResistanceTranslationX = mSplitSelectTranslationX = mGridEndTranslationX = 0f;
- mDismissTranslationY = mTaskOffsetTranslationY = mTaskResistanceTranslationY = 0f;
- if (getRecentsView() == null || !getRecentsView().isSplitSelectionActive()) {
- mSplitSelectTranslationY = 0f;
- }
-
- setSnapshotScale(1f);
- applyTranslationX();
- applyTranslationY();
- setTranslationZ(0);
- setAlpha(mStableAlpha);
- setIconScaleAndDim(1);
- setColorTint(0, 0);
- if (!enableRefactorTaskThumbnail()) {
- // TODO(b/335399428) add split select functionality to new TTV
- mTaskThumbnailViewDeprecated.resetViewTransforms();
- }
- }
-
- public void setStableAlpha(float parentAlpha) {
- mStableAlpha = parentAlpha;
- setAlpha(mStableAlpha);
- }
-
- @Override
- public void onRecycle() {
- resetPersistentViewTransforms();
- // Clear any references to the thumbnail (it will be re-read either from the cache or the
- // system on next bind)
- // TODO(b/334825222): Implement thumbnail/snapshot for the new [TaskThumbnailView].
- if (enableRefactorTaskThumbnail()) {
- notifyIsRunningTaskUpdated();
- } else {
- mTaskThumbnailViewDeprecated.setThumbnail(getFirstTask(), null);
- }
- setOverlayEnabled(false);
- onTaskListVisibilityChanged(false);
- mBorderEnabled = false;
- }
-
- public float getTaskCornerRadius() {
- return mCurrentFullscreenParams.mCornerRadius;
- }
-
- @Override
- protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- super.onLayout(changed, left, top, right, bottom);
- DeviceProfile deviceProfile = mContainer.getDeviceProfile();
- int thumbnailTopMargin = deviceProfile.overviewTaskThumbnailTopMarginPx;
- if (deviceProfile.isTablet) {
- setPivotX(getLayoutDirection() == LAYOUT_DIRECTION_RTL ? 0 : right - left);
- setPivotY(thumbnailTopMargin);
- } else {
- setPivotX((right - left) * 0.5f);
- setPivotY(thumbnailTopMargin + (getHeight() - thumbnailTopMargin) * 0.5f);
- }
- SYSTEM_GESTURE_EXCLUSION_RECT.get(0).set(0, 0, getWidth(), getHeight());
- setSystemGestureExclusionRects(SYSTEM_GESTURE_EXCLUSION_RECT);
- }
-
- /**
- * How much to scale down pages near the edge of the screen.
- */
- public static float getEdgeScaleDownFactor(DeviceProfile deviceProfile) {
- return deviceProfile.isTablet ? EDGE_SCALE_DOWN_FACTOR_GRID
- : EDGE_SCALE_DOWN_FACTOR_CAROUSEL;
- }
-
- private void setNonGridScale(float nonGridScale) {
- mNonGridScale = nonGridScale;
- applyScale();
- }
-
- public float getNonGridScale() {
- return mNonGridScale;
- }
-
- private void setSnapshotScale(float dismissScale) {
- mDismissScale = dismissScale;
- applyScale();
- }
-
- /**
- * Moves TaskView between carousel and 2 row grid.
- *
- * @param gridProgress 0 = carousel; 1 = 2 row grid.
- */
- public void setGridProgress(float gridProgress) {
- mGridProgress = gridProgress;
- applyTranslationX();
- applyTranslationY();
- applyScale();
- }
-
- private void applyScale() {
- float scale = 1;
- scale *= getPersistentScale();
- scale *= mDismissScale;
- setScaleX(scale);
- setScaleY(scale);
- if (enableRefactorTaskThumbnail()) {
- mTaskViewData.getScale().setValue(scale);
- }
- updateSnapshotRadius();
- }
-
- /**
- * Returns multiplication of scale that is persistent (e.g. fullscreen and grid), and does not
- * change according to a temporary state.
- */
- public float getPersistentScale() {
- float scale = 1;
- scale *= Utilities.mapRange(mGridProgress, mNonGridScale, 1f);
- return scale;
- }
-
- /**
- * Updates alpha of task thumbnail splash on swipe up/down.
- */
- public void setTaskThumbnailSplashAlpha(float taskThumbnailSplashAlpha) {
- mTaskThumbnailSplashAlpha = taskThumbnailSplashAlpha;
- applyThumbnailSplashAlpha();
- }
-
- protected void applyThumbnailSplashAlpha() {
- if (!enableRefactorTaskThumbnail()) {
- // TODO(b/334826842) add splash functionality to new TTV
- mTaskThumbnailViewDeprecated.setSplashAlpha(mTaskThumbnailSplashAlpha);
- }
- }
-
- protected void refreshTaskThumbnailSplash() {
- if (!enableRefactorTaskThumbnail()) {
- // TODO(b/334826842) add splash functionality to new TTV
- mTaskThumbnailViewDeprecated.refreshSplashView();
- }
- }
-
- private void setSplitSelectTranslationX(float x) {
- mSplitSelectTranslationX = x;
- applyTranslationX();
- }
-
- private void setSplitSelectTranslationY(float y) {
- mSplitSelectTranslationY = y;
- applyTranslationY();
- }
-
- private void setDismissTranslationX(float x) {
- mDismissTranslationX = x;
- applyTranslationX();
- }
-
- private void setDismissTranslationY(float y) {
- mDismissTranslationY = y;
- applyTranslationY();
- }
-
- private void setTaskOffsetTranslationX(float x) {
- mTaskOffsetTranslationX = x;
- applyTranslationX();
- }
-
- private void setTaskOffsetTranslationY(float y) {
- mTaskOffsetTranslationY = y;
- applyTranslationY();
- }
-
- private void setTaskResistanceTranslationX(float x) {
- mTaskResistanceTranslationX = x;
- applyTranslationX();
- }
-
- private void setTaskResistanceTranslationY(float y) {
- mTaskResistanceTranslationY = y;
- applyTranslationY();
- }
-
- public float getNonGridTranslationX() {
- return mNonGridTranslationX;
- }
-
- /**
- * Updates X coordinate of non-grid translation.
- */
- public void setNonGridTranslationX(float nonGridTranslationX) {
- mNonGridTranslationX = nonGridTranslationX;
- applyTranslationX();
- }
-
- public void setGridTranslationX(float gridTranslationX) {
- mGridTranslationX = gridTranslationX;
- applyTranslationX();
- }
-
- public float getGridTranslationX() {
- return mGridTranslationX;
- }
-
- public void setGridTranslationY(float gridTranslationY) {
- mGridTranslationY = gridTranslationY;
- applyTranslationY();
- }
-
- public float getGridTranslationY() {
- return mGridTranslationY;
- }
-
- private void setGridEndTranslationX(float gridEndTranslationX) {
- mGridEndTranslationX = gridEndTranslationX;
- applyTranslationX();
- }
-
- /**
- * Set translation X for non-grid pivot
- */
- public void setNonGridPivotTranslationX(float nonGridPivotTranslationX) {
- mNonGridPivotTranslationX = nonGridPivotTranslationX;
- applyTranslationX();
- }
-
- public float getScrollAdjustment(boolean gridEnabled) {
- float scrollAdjustment = 0;
- if (gridEnabled) {
- scrollAdjustment += mGridTranslationX;
- } else {
- scrollAdjustment += getNonGridTranslationX();
- }
- return scrollAdjustment;
- }
-
- public float getOffsetAdjustment(boolean gridEnabled) {
- return getScrollAdjustment(gridEnabled);
- }
-
- public float getSizeAdjustment(boolean fullscreenEnabled) {
- float sizeAdjustment = 1;
- if (fullscreenEnabled) {
- sizeAdjustment *= mNonGridScale;
- }
- return sizeAdjustment;
- }
-
- private void setBoxTranslationY(float boxTranslationY) {
- mBoxTranslationY = boxTranslationY;
- applyTranslationY();
- }
-
- private void applyTranslationX() {
- setTranslationX(mDismissTranslationX + mTaskOffsetTranslationX + mTaskResistanceTranslationX
- + mSplitSelectTranslationX + mGridEndTranslationX + getPersistentTranslationX());
- }
-
- private void applyTranslationY() {
- setTranslationY(mDismissTranslationY + mTaskOffsetTranslationY + mTaskResistanceTranslationY
- + mSplitSelectTranslationY + getPersistentTranslationY());
- }
-
- /**
- * Returns addition of translationX that is persistent (e.g. fullscreen and grid), and does not
- * change according to a temporary state (e.g. task offset).
- */
- public float getPersistentTranslationX() {
- return getNonGridTrans(mNonGridTranslationX) + getGridTrans(mGridTranslationX)
- + getNonGridTrans(mNonGridPivotTranslationX);
- }
-
- /**
- * Returns addition of translationY that is persistent (e.g. fullscreen and grid), and does not
- * change according to a temporary state (e.g. task offset).
- */
- public float getPersistentTranslationY() {
- return mBoxTranslationY + getGridTrans(mGridTranslationY);
- }
-
- public FloatProperty<TaskView> getPrimarySplitTranslationProperty() {
- return getPagedOrientationHandler().getPrimaryValue(
- SPLIT_SELECT_TRANSLATION_X, SPLIT_SELECT_TRANSLATION_Y);
- }
-
- public FloatProperty<TaskView> getSecondarySplitTranslationProperty() {
- return getPagedOrientationHandler().getSecondaryValue(
- SPLIT_SELECT_TRANSLATION_X, SPLIT_SELECT_TRANSLATION_Y);
- }
-
- public FloatProperty<TaskView> getPrimaryDismissTranslationProperty() {
- return getPagedOrientationHandler().getPrimaryValue(
- DISMISS_TRANSLATION_X, DISMISS_TRANSLATION_Y);
- }
-
- public FloatProperty<TaskView> getSecondaryDismissTranslationProperty() {
- return getPagedOrientationHandler().getSecondaryValue(
- DISMISS_TRANSLATION_X, DISMISS_TRANSLATION_Y);
- }
-
- public FloatProperty<TaskView> getPrimaryTaskOffsetTranslationProperty() {
- return getPagedOrientationHandler().getPrimaryValue(
- TASK_OFFSET_TRANSLATION_X, TASK_OFFSET_TRANSLATION_Y);
- }
-
- public FloatProperty<TaskView> getSecondaryTaskOffsetTranslationProperty() {
- return getPagedOrientationHandler().getSecondaryValue(
- TASK_OFFSET_TRANSLATION_X, TASK_OFFSET_TRANSLATION_Y);
- }
-
- public FloatProperty<TaskView> getTaskResistanceTranslationProperty() {
- return getPagedOrientationHandler().getSecondaryValue(
- TASK_RESISTANCE_TRANSLATION_X, TASK_RESISTANCE_TRANSLATION_Y);
- }
-
- @Override
- public boolean hasOverlappingRendering() {
- // TODO: Clip-out the icon region from the thumbnail, since they are overlapping.
- return false;
- }
-
- public boolean isEndQuickswitchCuj() {
- return mEndQuickswitchCuj;
- }
-
- public void setEndQuickswitchCuj(boolean endQuickswitchCuj) {
- mEndQuickswitchCuj = endQuickswitchCuj;
- }
-
- @Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
-
- info.addAction(
- new AccessibilityNodeInfo.AccessibilityAction(R.string.accessibility_close,
- getContext().getText(R.string.accessibility_close)));
-
- final Context context = getContext();
- for (TaskContainer taskContainer : mTaskContainers) {
- for (SystemShortcut s : TraceHelper.allowIpcs(
- "TV.a11yInfo", () -> getEnabledShortcuts(this, taskContainer))) {
- info.addAction(s.createAccessibilityAction(context));
- }
- }
-
- if (mDigitalWellBeingToast.hasLimit()) {
- info.addAction(
- new AccessibilityNodeInfo.AccessibilityAction(
- R.string.accessibility_app_usage_settings,
- getContext().getText(R.string.accessibility_app_usage_settings)));
- }
-
- final RecentsView recentsView = getRecentsView();
- final AccessibilityNodeInfo.CollectionItemInfo itemInfo =
- AccessibilityNodeInfo.CollectionItemInfo.obtain(
- 0, 1, recentsView.getTaskViewCount() - recentsView.indexOfChild(this) - 1,
- 1, false);
- info.setCollectionItemInfo(itemInfo);
- }
-
- @Override
- public boolean performAccessibilityAction(int action, Bundle arguments) {
- if (action == R.string.accessibility_close) {
- getRecentsView().dismissTask(this, true /*animateTaskView*/,
- true /*removeTask*/);
- return true;
- }
-
- if (action == R.string.accessibility_app_usage_settings) {
- mDigitalWellBeingToast.openAppUsageSettings(this);
- return true;
- }
-
- for (TaskContainer taskContainer : mTaskContainers) {
- for (SystemShortcut s : getEnabledShortcuts(this,
- taskContainer)) {
- if (s.hasHandlerForAction(action)) {
- s.onClick(this);
- return true;
- }
- }
- }
-
- return super.performAccessibilityAction(action, arguments);
- }
-
- @Nullable
- public RecentsView<?, ?> getRecentsView() {
- return (RecentsView<?, ?>) getParent();
- }
-
- RecentsPagedOrientationHandler getPagedOrientationHandler() {
- return getRecentsView().mOrientationState.getOrientationHandler();
- }
-
- private void notifyTaskLaunchFailed(String tag) {
- String msg = "Failed to launch task";
- if (getFirstTask() != null) {
- msg += " (task=" + getFirstTask().key.baseIntent + " userId="
- + getFirstTask().key.userId + ")";
- }
- Log.w(tag, msg);
- Toast.makeText(getContext(), R.string.activity_not_available, LENGTH_SHORT).show();
- }
-
- /**
- * Hides the icon and shows insets when this TaskView is about to be shown fullscreen.
- *
- * @param progress: 0 = show icon and no insets; 1 = don't show icon and show full insets.
- */
- public void setFullscreenProgress(float progress) {
- progress = Utilities.boundToRange(progress, 0, 1);
- mFullscreenProgress = progress;
- mIconView.setVisibility(progress < 1 ? VISIBLE : INVISIBLE);
- mTaskThumbnailViewDeprecated.getTaskOverlay().setFullscreenProgress(progress);
-
- RecentsView recentsView = mContainer.getOverviewPanel();
- // Animate icons and DWB banners in/out, except in QuickSwitch state, when tiles are
- // oversized and banner would look disproportionately large.
- if (recentsView.getStateManager().getState() != BACKGROUND_APP) {
- setIconsAndBannersTransitionProgress(progress, true);
- }
-
- updateSnapshotRadius();
- }
-
- protected void updateSnapshotRadius() {
- updateCurrentFullscreenParams();
- mTaskThumbnailViewDeprecated.setFullscreenParams(mCurrentFullscreenParams);
- }
-
- void updateCurrentFullscreenParams() {
- updateFullscreenParams(mCurrentFullscreenParams);
- }
-
- protected void updateFullscreenParams(TaskView.FullscreenDrawParams fullscreenParams) {
- if (getRecentsView() == null) {
- return;
- }
- fullscreenParams.setProgress(
- mFullscreenProgress, getRecentsView().getScaleX(), getScaleX());
- }
-
- /**
- * Updates TaskView scaling and translation required to support variable width if enabled, while
- * ensuring TaskView fits into screen in fullscreen.
- */
- void updateTaskSize() {
- ViewGroup.LayoutParams params = getLayoutParams();
- float nonGridScale;
- float boxTranslationY;
- int expectedWidth;
- int expectedHeight;
- DeviceProfile deviceProfile = mContainer.getDeviceProfile();
- final int thumbnailPadding = deviceProfile.overviewTaskThumbnailTopMarginPx;
- final Rect lastComputedTaskSize = getRecentsView().getLastComputedTaskSize();
- final int taskWidth = lastComputedTaskSize.width();
- final int taskHeight = lastComputedTaskSize.height();
- if (deviceProfile.isTablet) {
- int boxWidth;
- int boxHeight;
- boolean isFocusedTask = isFocusedTask();
- if (isFocusedTask) {
- // Task will be focused and should use focused task size. Use focusTaskRatio
- // that is associated with the original orientation of the focused task.
- boxWidth = taskWidth;
- boxHeight = taskHeight;
- } else {
- // Otherwise task is in grid, and should use lastComputedGridTaskSize.
- Rect lastComputedGridTaskSize = getRecentsView().getLastComputedGridTaskSize();
- boxWidth = lastComputedGridTaskSize.width();
- boxHeight = lastComputedGridTaskSize.height();
- }
-
- // Bound width/height to the box size.
- expectedWidth = boxWidth;
- expectedHeight = boxHeight + thumbnailPadding;
-
- // Scale to to fit task Rect.
- if (enableGridOnlyOverview()) {
- final Rect lastComputedCarouselTaskSize =
- getRecentsView().getLastComputedCarouselTaskSize();
- nonGridScale = lastComputedCarouselTaskSize.width() / (float) taskWidth;
- } else {
- nonGridScale = taskWidth / (float) boxWidth;
- }
-
- // Align to top of task Rect.
- boxTranslationY = (expectedHeight - thumbnailPadding - taskHeight) / 2.0f;
- } else {
- nonGridScale = 1f;
- boxTranslationY = 0f;
- expectedWidth = enableOverviewIconMenu() ? taskWidth : LayoutParams.MATCH_PARENT;
- expectedHeight = enableOverviewIconMenu()
- ? taskHeight + thumbnailPadding
- : LayoutParams.MATCH_PARENT;
- }
-
- setNonGridScale(nonGridScale);
- setBoxTranslationY(boxTranslationY);
- if (params.width != expectedWidth || params.height != expectedHeight) {
- params.width = expectedWidth;
- params.height = expectedHeight;
- setLayoutParams(params);
- }
- }
-
- private float getGridTrans(float endTranslation) {
- return Utilities.mapRange(mGridProgress, 0, endTranslation);
- }
-
- private float getNonGridTrans(float endTranslation) {
- return endTranslation - getGridTrans(endTranslation);
- }
-
- public boolean isRunningTask() {
- if (getRecentsView() == null) {
- return false;
- }
- return this == getRecentsView().getRunningTaskView();
- }
-
- public boolean isFocusedTask() {
- if (getRecentsView() == null) {
- return false;
- }
- return this == getRecentsView().getFocusedTaskView();
- }
-
- public void setShowScreenshot(boolean showScreenshot) {
- mShowScreenshot = showScreenshot;
- }
-
- public boolean showScreenshot() {
- if (!isRunningTask()) {
- return true;
- }
- return mShowScreenshot;
- }
-
- public void setOverlayEnabled(boolean overlayEnabled) {
- // TODO(b/335606129) Investigate the usage of [TaskOverlay] in the new TaskThumbnailView.
- // and if it's still necessary we should support that in the new TTV class.
- if (!enableRefactorTaskThumbnail()) {
- mTaskThumbnailViewDeprecated.setOverlayEnabled(overlayEnabled);
- }
- }
-
- public void initiateSplitSelect(SplitPositionOption splitPositionOption) {
- getRecentsView().initiateSplitSelect(this, splitPositionOption.stagePosition,
- getLogEventForPosition(splitPositionOption.stagePosition));
- }
-
- /**
- * Set a color tint on the snapshot and supporting views.
- */
- public void setColorTint(float amount, int tintColor) {
- if (!enableRefactorTaskThumbnail()) {
- // TODO(b/334832108) Add scrim to new TTV
- mTaskThumbnailViewDeprecated.setDimAlpha(amount);
- }
- mIconView.setIconColorTint(tintColor, amount);
- mDigitalWellBeingToast.setBannerColorTint(tintColor, amount);
- }
-
-
- private int getRootViewDisplayId() {
- 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.
- *
- * @param taskId is only used when setting visibility to a non-{@link View#VISIBLE} value
- */
- void setThumbnailVisibility(int visibility, int taskId) {
- for (int i = 0; i < getChildCount(); i++) {
- View child = getChildAt(i);
- if (child != mIconView) {
- child.setVisibility(visibility);
- }
- }
- }
-
- private View getSnapshotView() {
- return enableRefactorTaskThumbnail() ? mTaskThumbnailView : mTaskThumbnailViewDeprecated;
- }
-
- /**
- * We update and subsequently draw these in {@link #setFullscreenProgress(float)}.
- */
- public static class FullscreenDrawParams implements SafeCloseable {
-
- private float mCornerRadius;
- private float mWindowCornerRadius;
-
- public float mCurrentDrawnCornerRadius;
-
- public FullscreenDrawParams(Context context) {
- updateCornerRadius(context);
- }
-
- /** Recomputes the start and end corner radius for the given Context. */
- public void updateCornerRadius(Context context) {
- mCornerRadius = computeTaskCornerRadius(context);
- mWindowCornerRadius = computeWindowCornerRadius(context);
- }
-
- @VisibleForTesting
- public float computeTaskCornerRadius(Context context) {
- return TaskCornerRadius.get(context);
- }
-
- @VisibleForTesting
- public float computeWindowCornerRadius(Context context) {
- return QuickStepContract.getWindowCornerRadius(context);
- }
-
- /**
- * Sets the progress in range [0, 1]
- */
- public void setProgress(float fullscreenProgress, float parentScale, float taskViewScale) {
- mCurrentDrawnCornerRadius =
- Utilities.mapRange(fullscreenProgress, mCornerRadius, mWindowCornerRadius)
- / parentScale / taskViewScale;
- }
-
- @Override
- public void close() {
- }
- }
-
- /**
- * Holder for all Task dependent information.
- */
- public class TaskContainer {
- private final TaskThumbnailViewDeprecated mThumbnailView;
- private final Task mTask;
- private final TaskViewIcon mIconView;
- /** Defaults to STAGE_POSITION_UNDEFINED if in not a split screen task view */
- private @SplitConfigurationOptions.StagePosition int mStagePosition;
- @IdRes
- private final int mA11yNodeId;
- private final DigitalWellBeingToast mDigitalWellBeingToast;
-
- public TaskContainer(Task task, TaskThumbnailViewDeprecated thumbnailView,
- TaskViewIcon iconView, int stagePosition,
- DigitalWellBeingToast digitalWellBeingToast) {
- this.mTask = task;
- this.mThumbnailView = thumbnailView;
- this.mIconView = iconView;
- this.mStagePosition = stagePosition;
- this.mA11yNodeId = (stagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT) ?
- R.id.split_bottomRight_appInfo : R.id.split_topLeft_appInfo;
- this.mDigitalWellBeingToast = digitalWellBeingToast;
- }
-
- public TaskThumbnailViewDeprecated getThumbnailView() {
- return mThumbnailView;
- }
-
- public Task getTask() {
- return mTask;
- }
-
- public WorkspaceItemInfo getItemInfo() {
- return TaskView.this.getItemInfo(mTask);
- }
-
- public TaskView getTaskView() {
- return TaskView.this;
- }
-
- public TaskViewIcon getIconView() {
- return mIconView;
- }
-
- public int getStagePosition() {
- return mStagePosition;
- }
-
- void setStagePosition(@SplitConfigurationOptions.StagePosition int stagePosition) {
- this.mStagePosition = stagePosition;
- }
-
- public int getA11yNodeId() {
- return mA11yNodeId;
- }
-
- public DigitalWellBeingToast getDigitalWellBeingToast() {
- return mDigitalWellBeingToast;
- }
- }
-}
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.kt b/quickstep/src/com/android/quickstep/views/TaskView.kt
new file mode 100644
index 0000000..d19f09c
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/views/TaskView.kt
@@ -0,0 +1,1633 @@
+/*
+ * Copyright (C) 2017 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.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.AnimatorSet
+import android.animation.ObjectAnimator
+import android.annotation.IdRes
+import android.app.ActivityOptions
+import android.content.Context
+import android.content.Intent
+import android.graphics.Canvas
+import android.graphics.PointF
+import android.graphics.Rect
+import android.graphics.drawable.Drawable
+import android.os.Bundle
+import android.util.AttributeSet
+import android.util.FloatProperty
+import android.util.Log
+import android.view.Display
+import android.view.MotionEvent
+import android.view.View
+import android.view.View.OnClickListener
+import android.view.ViewGroup
+import android.view.ViewStub
+import android.view.accessibility.AccessibilityNodeInfo
+import android.widget.FrameLayout
+import android.widget.Toast
+import androidx.annotation.IntDef
+import androidx.annotation.VisibleForTesting
+import androidx.core.view.children
+import androidx.core.view.updateLayoutParams
+import com.android.app.animation.Interpolators
+import com.android.launcher3.Flags.enableCursorHoverStates
+import com.android.launcher3.Flags.enableFocusOutline
+import com.android.launcher3.Flags.enableGridOnlyOverview
+import com.android.launcher3.Flags.enableOverviewIconMenu
+import com.android.launcher3.Flags.enableRefactorTaskThumbnail
+import com.android.launcher3.Flags.privateSpaceRestrictAccessibilityDrag
+import com.android.launcher3.LauncherSettings
+import com.android.launcher3.LauncherState
+import com.android.launcher3.R
+import com.android.launcher3.Utilities
+import com.android.launcher3.config.FeatureFlags.ENABLE_KEYBOARD_QUICK_SWITCH
+import com.android.launcher3.logging.StatsLogManager.LauncherEvent
+import com.android.launcher3.model.data.ItemInfoWithIcon
+import com.android.launcher3.model.data.WorkspaceItemInfo
+import com.android.launcher3.pm.UserCache
+import com.android.launcher3.testing.TestLogging
+import com.android.launcher3.testing.shared.TestProtocol
+import com.android.launcher3.util.CancellableTask
+import com.android.launcher3.util.Executors
+import com.android.launcher3.util.RunnableList
+import com.android.launcher3.util.SafeCloseable
+import com.android.launcher3.util.SplitConfigurationOptions
+import com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT
+import com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_UNDEFINED
+import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption
+import com.android.launcher3.util.SplitConfigurationOptions.StagePosition
+import com.android.launcher3.util.TraceHelper
+import com.android.launcher3.util.TransformingTouchDelegate
+import com.android.launcher3.util.ViewPool
+import com.android.launcher3.util.rects.set
+import com.android.quickstep.RecentsModel
+import com.android.quickstep.RemoteAnimationTargets
+import com.android.quickstep.TaskAnimationManager
+import com.android.quickstep.TaskOverlayFactory
+import com.android.quickstep.TaskUtils
+import com.android.quickstep.TaskViewUtils
+import com.android.quickstep.orientation.RecentsPagedOrientationHandler
+import com.android.quickstep.task.thumbnail.TaskThumbnail
+import com.android.quickstep.task.thumbnail.TaskThumbnailView
+import com.android.quickstep.task.viewmodel.TaskViewData
+import com.android.quickstep.util.ActiveGestureErrorDetector
+import com.android.quickstep.util.ActiveGestureLog
+import com.android.quickstep.util.BorderAnimator
+import com.android.quickstep.util.BorderAnimator.Companion.createSimpleBorderAnimator
+import com.android.quickstep.util.RecentsOrientedState
+import com.android.quickstep.util.TaskCornerRadius
+import com.android.quickstep.util.TaskRemovedDuringLaunchListener
+import com.android.systemui.shared.recents.model.Task
+import com.android.systemui.shared.recents.model.ThumbnailData
+import com.android.systemui.shared.system.ActivityManagerWrapper
+import com.android.systemui.shared.system.QuickStepContract
+
+/** A task in the Recents view. */
+open class TaskView
+@JvmOverloads
+constructor(
+ context: Context,
+ attrs: AttributeSet? = null,
+ defStyleAttr: Int = 0,
+ defStyleRes: Int = 0,
+ focusBorderAnimator: BorderAnimator? = null,
+ hoverBorderAnimator: BorderAnimator? = null
+) : FrameLayout(context, attrs), ViewPool.Reusable {
+ /**
+ * Used in conjunction with [onTaskListVisibilityChanged], providing more granularity on which
+ * components of this task require an update
+ */
+ @Retention(AnnotationRetention.SOURCE)
+ @IntDef(FLAG_UPDATE_ALL, FLAG_UPDATE_ICON, FLAG_UPDATE_THUMBNAIL, FLAG_UPDATE_CORNER_RADIUS)
+ annotation class TaskDataChanges
+
+ /** Type of task view */
+ @Retention(AnnotationRetention.SOURCE)
+ @IntDef(Type.SINGLE, Type.GROUPED, Type.DESKTOP)
+ annotation class Type {
+ companion object {
+ const val SINGLE = 1
+ const val GROUPED = 2
+ const val DESKTOP = 3
+ }
+ }
+
+ val taskViewData = TaskViewData()
+ val taskIds: IntArray
+ /** Returns a copy of integer array containing taskIds of all tasks in the TaskView. */
+ get() = taskContainers.map { it.task.key.id }.toIntArray()
+ val thumbnailViews: Array<TaskThumbnailViewDeprecated>
+ get() = taskContainers.map { it.thumbnailView }.toTypedArray()
+ val isGridTask: Boolean
+ /** Returns whether the task is part of overview grid and not being focused. */
+ get() = container.deviceProfile.isTablet && !isFocusedTask
+ val isRunningTask: Boolean
+ get() = this === recentsView?.runningTaskView
+ val isFocusedTask: Boolean
+ get() = this === recentsView?.focusedTaskView
+ val taskCornerRadius: Float
+ get() = currentFullscreenParams.cornerRadius
+ val recentsView: RecentsView<*, *>?
+ get() = parent as? RecentsView<*, *>
+ val pagedOrientationHandler: RecentsPagedOrientationHandler
+ get() = orientedState.orientationHandler
+
+ @get:Deprecated("Use {@link #mTaskContainers} instead.")
+ val firstTask: Task
+ /** Returns the first task bound to this TaskView. */
+ get() = taskContainers[0].task
+ @get:Deprecated("Use {@link #mTaskContainers} instead.")
+ val firstThumbnailView: TaskThumbnailViewDeprecated
+ /** Returns the first thumbnailView of the TaskView. */
+ get() = taskContainers[0].thumbnailView
+ @get:Deprecated("Use {@link #mTaskContainers} instead.")
+ val firstIconView: TaskViewIcon
+ /** Returns the first iconView of the TaskView. */
+ get() = taskContainers[0].iconView
+ protected val currentFullscreenParams = FullscreenDrawParams(context)
+ protected val container: RecentsViewContainer =
+ RecentsViewContainer.containerFromContext(context)
+ @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
+ val digitalWellBeingToast = DigitalWellBeingToast(container, this)
+ protected val mLastTouchDownPosition = PointF()
+ protected val snapshotView: View
+ get() =
+ if (enableRefactorTaskThumbnail()) taskThumbnailView!! else taskThumbnailViewDeprecated
+
+ // Derived view properties
+ protected val persistentScale: Float
+ /**
+ * Returns multiplication of scale that is persistent (e.g. fullscreen and grid), and does
+ * not change according to a temporary state.
+ */
+ get() = Utilities.mapRange(gridProgress, nonGridScale, 1f)
+ protected val persistentTranslationX: Float
+ /**
+ * Returns addition of translationX that is persistent (e.g. fullscreen and grid), and does
+ * not change according to a temporary state (e.g. task offset).
+ */
+ get() =
+ (getNonGridTrans(nonGridTranslationX) +
+ getGridTrans(this.gridTranslationX) +
+ getNonGridTrans(nonGridPivotTranslationX))
+ protected val persistentTranslationY: Float
+ /**
+ * Returns addition of translationY that is persistent (e.g. fullscreen and grid), and does
+ * not change according to a temporary state (e.g. task offset).
+ */
+ get() = boxTranslationY + getGridTrans(gridTranslationY)
+ protected val primarySplitTranslationProperty: FloatProperty<TaskView>
+ get() =
+ pagedOrientationHandler.getPrimaryValue(
+ SPLIT_SELECT_TRANSLATION_X,
+ SPLIT_SELECT_TRANSLATION_Y
+ )
+ protected val secondarySplitTranslationProperty: FloatProperty<TaskView>
+ get() =
+ pagedOrientationHandler.getSecondaryValue(
+ SPLIT_SELECT_TRANSLATION_X,
+ SPLIT_SELECT_TRANSLATION_Y
+ )
+ protected val primaryDismissTranslationProperty: FloatProperty<TaskView>
+ get() =
+ pagedOrientationHandler.getPrimaryValue(DISMISS_TRANSLATION_X, DISMISS_TRANSLATION_Y)
+ protected val secondaryDismissTranslationProperty: FloatProperty<TaskView>
+ get() =
+ pagedOrientationHandler.getSecondaryValue(DISMISS_TRANSLATION_X, DISMISS_TRANSLATION_Y)
+ protected val primaryTaskOffsetTranslationProperty: FloatProperty<TaskView>
+ get() =
+ pagedOrientationHandler.getPrimaryValue(
+ TASK_OFFSET_TRANSLATION_X,
+ TASK_OFFSET_TRANSLATION_Y
+ )
+ protected val secondaryTaskOffsetTranslationProperty: FloatProperty<TaskView>
+ get() =
+ pagedOrientationHandler.getSecondaryValue(
+ TASK_OFFSET_TRANSLATION_X,
+ TASK_OFFSET_TRANSLATION_Y
+ )
+ protected val taskResistanceTranslationProperty: FloatProperty<TaskView>
+ get() =
+ pagedOrientationHandler.getSecondaryValue(
+ TASK_RESISTANCE_TRANSLATION_X,
+ TASK_RESISTANCE_TRANSLATION_Y
+ )
+
+ private val mIconCenterCoordinates = FloatArray(2)
+ private val mFocusBorderAnimator: BorderAnimator?
+ private val mHoverBorderAnimator: BorderAnimator?
+ private val rootViewDisplayId: Int
+ get() = rootView.display?.displayId ?: Display.DEFAULT_DISPLAY
+
+ /** Returns a list of all TaskContainers in the TaskView. */
+ lateinit var taskContainers: List<TaskContainer>
+ protected set
+ lateinit var orientedState: RecentsOrientedState
+ protected lateinit var taskThumbnailViewDeprecated: TaskThumbnailViewDeprecated
+ protected lateinit var iconView: TaskViewIcon
+ /**
+ * This technically can be a vanilla [android.view.TouchDelegate] class, however that class
+ * requires setting the touch bounds at construction, so we'd repeatedly be created many
+ * instances unnecessarily as scrolling occurs, whereas [TransformingTouchDelegate] allows touch
+ * delegated bounds only to be updated.
+ */
+ private lateinit var iconTouchDelegate: TransformingTouchDelegate
+ private var taskThumbnailView: TaskThumbnailView? = null
+
+ var taskViewId = -1
+ var isEndQuickSwitchCuj = false
+
+ // Various animation progress variables.
+ // progress: 0 = show icon and no insets; 1 = don't show icon and show full insets.
+ protected var fullscreenProgress = 0f
+ set(value) {
+ field = Utilities.boundToRange(value, 0f, 1f)
+ onFullscreenProgressChanged(field)
+ }
+ // gridProgress 0 = carousel; 1 = 2 row grid.
+ protected var gridProgress = 0f
+ set(value) {
+ field = value
+ onGridProgressChanged()
+ }
+ /**
+ * The modalness of this view is how it should be displayed when it is shown on its own in the
+ * modal state of overview. 0 being in context with other tasks, 1 being shown on its own.
+ */
+ protected var modalness = 0f
+ set(value) {
+ field = value
+ onModalnessUpdated(field)
+ }
+ protected var taskThumbnailSplashAlpha = 0f
+ set(value) {
+ field = value
+ applyThumbnailSplashAlpha()
+ }
+ protected var nonGridScale = 1f
+ set(value) {
+ field = value
+ applyScale()
+ }
+ private var dismissScale = 1f
+ set(value) {
+ field = value
+ applyScale()
+ }
+ private var dismissTranslationX = 0f
+ set(value) {
+ field = value
+ applyTranslationX()
+ }
+ private var dismissTranslationY = 0f
+ set(value) {
+ field = value
+ applyTranslationY()
+ }
+ private var taskOffsetTranslationX = 0f
+ set(value) {
+ field = value
+ applyTranslationX()
+ }
+ private var taskOffsetTranslationY = 0f
+ set(value) {
+ field = value
+ applyTranslationY()
+ }
+ private var taskResistanceTranslationX = 0f
+ set(value) {
+ field = value
+ applyTranslationX()
+ }
+ private var taskResistanceTranslationY = 0f
+ set(value) {
+ field = value
+ applyTranslationY()
+ }
+ // The following translation variables should only be used in the same orientation as Launcher.
+ private var boxTranslationY = 0f
+ set(value) {
+ field = value
+ applyTranslationY()
+ }
+ // The following grid translations scales with mGridProgress.
+ protected var gridTranslationX = 0f
+ set(value) {
+ field = value
+ applyTranslationX()
+ }
+ var gridTranslationY = 0f
+ protected set(value) {
+ field = value
+ applyTranslationY()
+ }
+ // The following grid translation is used to animate closing the gap between grid and clear all.
+ private var gridEndTranslationX = 0f
+ set(value) {
+ field = value
+ applyTranslationX()
+ }
+ // Applied as a complement to gridTranslation, for adjusting the carousel overview and quick
+ // switch.
+ protected var nonGridTranslationX = 0f
+ set(value) {
+ field = value
+ applyTranslationX()
+ }
+ protected var nonGridPivotTranslationX = 0f
+ set(value) {
+ field = value
+ applyTranslationX()
+ }
+ // Used when in SplitScreenSelectState
+ private var splitSelectTranslationY = 0f
+ set(value) {
+ field = value
+ applyTranslationY()
+ }
+ private var splitSelectTranslationX = 0f
+ set(value) {
+ field = value
+ applyTranslationX()
+ }
+ protected var stableAlpha = 1f
+ set(value) {
+ field = value
+ alpha = stableAlpha
+ }
+ protected var shouldShowScreenshot = false
+ get() = !isRunningTask || field
+ /** Enable or disable showing border on hover and focus change */
+ @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
+ var borderEnabled = false
+ set(value) {
+ if (field == value) {
+ return
+ }
+ field = value
+ // Set the animation correctly in case it misses the hover/focus event during state
+ // transition
+ mHoverBorderAnimator?.setBorderVisibility(visible = field && isHovered, animated = true)
+ mFocusBorderAnimator?.setBorderVisibility(visible = field && isFocused, animated = true)
+ }
+ protected var iconScaleAnimStartProgress = 0f
+ private var focusTransitionProgress = 1f
+
+ private var iconAndDimAnimator: ObjectAnimator? = null
+ // The current background requests to load the task thumbnail and icon
+ private var thumbnailLoadRequest: CancellableTask<*>? = null
+ private var iconLoadRequest: CancellableTask<*>? = null
+ private var isClickableAsLiveTile = true
+
+ init {
+ setOnClickListener { view: View -> onClick(view) }
+ val keyboardFocusHighlightEnabled =
+ (ENABLE_KEYBOARD_QUICK_SWITCH.get() || enableFocusOutline())
+ val cursorHoverStatesEnabled = enableCursorHoverStates()
+ setWillNotDraw(!keyboardFocusHighlightEnabled && !cursorHoverStatesEnabled)
+ context.obtainStyledAttributes(attrs, R.styleable.TaskView, defStyleAttr, defStyleRes).use {
+ mFocusBorderAnimator =
+ focusBorderAnimator
+ ?: if (keyboardFocusHighlightEnabled)
+ createSimpleBorderAnimator(
+ currentFullscreenParams.cornerRadius.toInt(),
+ context.resources.getDimensionPixelSize(
+ R.dimen.keyboard_quick_switch_border_width
+ ),
+ { bounds: Rect -> getThumbnailBounds(bounds) },
+ this,
+ it.getColor(
+ R.styleable.TaskView_focusBorderColor,
+ BorderAnimator.DEFAULT_BORDER_COLOR
+ )
+ )
+ else null
+ mHoverBorderAnimator =
+ hoverBorderAnimator
+ ?: if (cursorHoverStatesEnabled)
+ createSimpleBorderAnimator(
+ currentFullscreenParams.cornerRadius.toInt(),
+ context.resources.getDimensionPixelSize(
+ R.dimen.task_hover_border_width
+ ),
+ { bounds: Rect -> getThumbnailBounds(bounds) },
+ this,
+ it.getColor(
+ R.styleable.TaskView_hoverBorderColor,
+ BorderAnimator.DEFAULT_BORDER_COLOR
+ )
+ )
+ else null
+ }
+ }
+
+ override fun onFinishInflate() {
+ super.onFinishInflate()
+ taskThumbnailViewDeprecated = findViewById(R.id.snapshot)!!
+ if (enableRefactorTaskThumbnail()) {
+ val indexOfSnapshotView = indexOfChild(taskThumbnailViewDeprecated)
+ taskThumbnailView =
+ TaskThumbnailView(mContext).apply {
+ layoutParams = taskThumbnailViewDeprecated.layoutParams
+ addView(this, indexOfSnapshotView)
+ }
+ taskThumbnailViewDeprecated.visibility = GONE
+ }
+ val iconViewStub =
+ findViewById<ViewStub>(R.id.icon)!!.apply {
+ layoutResource =
+ if (enableOverviewIconMenu()) R.layout.icon_app_chip_view
+ else R.layout.icon_view
+ }
+ iconView = iconViewStub.inflate() as TaskViewIcon
+ iconTouchDelegate = TransformingTouchDelegate(iconView.asView())
+ }
+
+ @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
+ public override fun onFocusChanged(
+ gainFocus: Boolean,
+ direction: Int,
+ previouslyFocusedRect: Rect?
+ ) {
+ super.onFocusChanged(gainFocus, direction, previouslyFocusedRect)
+ if (borderEnabled) {
+ mFocusBorderAnimator?.setBorderVisibility(gainFocus, /* animated= */ true)
+ }
+ }
+
+ override fun onHoverEvent(event: MotionEvent): Boolean {
+ if (borderEnabled) {
+ when (event.action) {
+ MotionEvent.ACTION_HOVER_ENTER ->
+ mHoverBorderAnimator?.setBorderVisibility(visible = true, animated = true)
+ MotionEvent.ACTION_HOVER_EXIT ->
+ mHoverBorderAnimator?.setBorderVisibility(visible = false, animated = true)
+ else -> {}
+ }
+ }
+ return super.onHoverEvent(event)
+ }
+
+ // avoid triggering hover event on child elements which would cause HOVER_EXIT for this
+ // task view
+ override fun onInterceptHoverEvent(event: MotionEvent) =
+ if (enableCursorHoverStates()) true else super.onInterceptHoverEvent(event)
+
+ override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
+ val recentsView = recentsView ?: return false
+ val splitSelectStateController = recentsView.splitSelectController
+ // Disable taps for split selection animation unless we have multiple tasks
+ if (
+ splitSelectStateController.isSplitSelectActive &&
+ splitSelectStateController.initialTaskId == firstTask.key.id &&
+ !containsMultipleTasks()
+ ) {
+ return false
+ }
+ if (ev.action == MotionEvent.ACTION_DOWN) {
+ mLastTouchDownPosition.apply {
+ x = ev.x
+ y = ev.y
+ }
+ }
+ return super.dispatchTouchEvent(ev)
+ }
+
+ override fun draw(canvas: Canvas) {
+ // Draw border first so any child views outside of the thumbnail bounds are drawn above it.
+ mFocusBorderAnimator?.drawBorder(canvas)
+ mHoverBorderAnimator?.drawBorder(canvas)
+ super.draw(canvas)
+ }
+
+ override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
+ super.onLayout(changed, left, top, right, bottom)
+ val thumbnailTopMargin = container.deviceProfile.overviewTaskThumbnailTopMarginPx
+ if (container.deviceProfile.isTablet) {
+ pivotX = (if (layoutDirection == LAYOUT_DIRECTION_RTL) 0 else right - left).toFloat()
+ pivotY = thumbnailTopMargin.toFloat()
+ } else {
+ pivotX = (right - left) * 0.5f
+ pivotY = thumbnailTopMargin + (height - thumbnailTopMargin) * 0.5f
+ }
+ systemGestureExclusionRects =
+ SYSTEM_GESTURE_EXCLUSION_RECT.onEach {
+ it.right = width
+ it.bottom = height
+ }
+ }
+
+ override fun onRecycle() {
+ resetPersistentViewTransforms()
+ // Clear any references to the thumbnail (it will be re-read either from the cache or the
+ // system on next bind)
+ // TODO(b/334825222): Implement thumbnail/snapshot for the new [TaskThumbnailView].
+ if (enableRefactorTaskThumbnail()) {
+ notifyIsRunningTaskUpdated()
+ } else {
+ taskThumbnailViewDeprecated.setThumbnail(firstTask, null)
+ }
+ setOverlayEnabled(false)
+ onTaskListVisibilityChanged(false)
+ borderEnabled = false
+ }
+
+ // TODO: Clip-out the icon region from the thumbnail, since they are overlapping.
+ override fun hasOverlappingRendering() = false
+
+ override fun onInitializeAccessibilityNodeInfo(info: AccessibilityNodeInfo) {
+ super.onInitializeAccessibilityNodeInfo(info)
+ with(info) {
+ addAction(
+ AccessibilityNodeInfo.AccessibilityAction(
+ R.string.accessibility_close,
+ context.getText(R.string.accessibility_close)
+ )
+ )
+ taskContainers.forEach {
+ TraceHelper.allowIpcs("TV.a11yInfo") {
+ TaskOverlayFactory.getEnabledShortcuts(this@TaskView, it).forEach { shortcut ->
+ addAction(shortcut.createAccessibilityAction(context))
+ }
+ }
+ }
+ if (digitalWellBeingToast.hasLimit()) {
+ addAction(
+ AccessibilityNodeInfo.AccessibilityAction(
+ R.string.accessibility_app_usage_settings,
+ context.getText(R.string.accessibility_app_usage_settings)
+ )
+ )
+ }
+ recentsView?.let {
+ collectionItemInfo =
+ AccessibilityNodeInfo.CollectionItemInfo.obtain(
+ 0,
+ 1,
+ it.taskViewCount - it.indexOfChild(this@TaskView) - 1,
+ 1,
+ false
+ )
+ }
+ }
+ }
+
+ override fun performAccessibilityAction(action: Int, arguments: Bundle?): Boolean {
+ if (action == R.string.accessibility_close) {
+ recentsView?.dismissTask(this, true /*animateTaskView*/, true /*removeTask*/)
+ return true
+ }
+ if (action == R.string.accessibility_app_usage_settings) {
+ digitalWellBeingToast.openAppUsageSettings(this)
+ return true
+ }
+ taskContainers.forEach {
+ TaskOverlayFactory.getEnabledShortcuts(this, it).forEach { shortcut ->
+ if (shortcut.hasHandlerForAction(action)) {
+ shortcut.onClick(this)
+ return true
+ }
+ }
+ }
+ return super.performAccessibilityAction(action, arguments)
+ }
+
+ /** Updates this task view to the given {@param task}. */
+ open fun bind(
+ task: Task,
+ orientedState: RecentsOrientedState,
+ taskOverlayFactory: TaskOverlayFactory
+ ) {
+ cancelPendingLoadTasks()
+ setupTaskContainers(task, taskOverlayFactory)
+ setOrientationState(orientedState)
+ }
+
+ protected fun setupTaskContainers(task: Task, taskOverlayFactory: TaskOverlayFactory) {
+ taskContainers =
+ listOf(
+ TaskContainer(
+ task,
+ taskThumbnailViewDeprecated,
+ iconView,
+ STAGE_POSITION_UNDEFINED,
+ digitalWellBeingToast
+ )
+ )
+ if (enableRefactorTaskThumbnail()) {
+ bindTaskThumbnailView()
+ } else {
+ taskThumbnailViewDeprecated.bind(task, taskOverlayFactory)
+ }
+ }
+
+ protected fun isTaskContainersInitialized() = this::taskContainers.isInitialized
+
+ fun containsMultipleTasks() = taskContainers.size > 1
+
+ /**
+ * Returns the TaskContainer corresponding to a given taskId, or null if the TaskView does not
+ * contain a Task with that ID.
+ */
+ fun getTaskContainerById(taskId: Int) = taskContainers.firstOrNull { it.task.key.id == taskId }
+
+ /** Check if given `taskId` is tracked in this view */
+ fun containsTaskId(taskId: Int) = getTaskContainerById(taskId) != null
+
+ // TODO(b/335649589): TaskView's VM will already have access to TaskThumbnailView's VM
+ // so there will be no need to access TaskThumbnailView's VM through the TaskThumbnailView
+ private fun bindTaskThumbnailView() {
+ taskThumbnailView!!.viewModel.bind(TaskThumbnail(firstTask, isRunningTask))
+ }
+
+ open fun setOrientationState(orientationState: RecentsOrientedState) {
+ this.orientedState = orientationState
+ iconView.setIconOrientation(orientationState, isGridTask)
+ setThumbnailOrientation(orientationState)
+ }
+
+ protected open fun setThumbnailOrientation(orientationState: RecentsOrientedState) {
+ val thumbnailTopMargin = container.deviceProfile.overviewTaskThumbnailTopMarginPx
+
+ // TODO(b/271468547), we should default to setting translations only on the snapshot instead
+ // of a hybrid of both margins and translations
+ snapshotView.updateLayoutParams<LayoutParams> { topMargin = thumbnailTopMargin }
+
+ // TODO(b/335606129) Investigate the usage of [TaskOverlay] in the new TaskThumbnailView.
+ // and if it's still necessary we should support that in the new TTV class.
+ if (!enableRefactorTaskThumbnail()) {
+ taskThumbnailViewDeprecated.taskOverlay.updateOrientationState(orientationState)
+ }
+ digitalWellBeingToast.initialize(firstTask)
+ }
+
+ /**
+ * Updates TaskView scaling and translation required to support variable width if enabled, while
+ * ensuring TaskView fits into screen in fullscreen.
+ */
+ fun updateTaskSize(
+ lastComputedTaskSize: Rect,
+ lastComputedGridTaskSize: Rect,
+ lastComputedCarouselTaskSize: Rect
+ ) {
+ val thumbnailPadding = container.deviceProfile.overviewTaskThumbnailTopMarginPx
+ val taskWidth = lastComputedTaskSize.width()
+ val taskHeight = lastComputedTaskSize.height()
+ val nonGridScale: Float
+ val boxTranslationY: Float
+ val expectedWidth: Int
+ val expectedHeight: Int
+ if (container.deviceProfile.isTablet) {
+ val boxWidth: Int
+ val boxHeight: Int
+ val isFocusedTask = isFocusedTask
+ if (isFocusedTask) {
+ // Task will be focused and should use focused task size. Use focusTaskRatio
+ // that is associated with the original orientation of the focused task.
+ boxWidth = taskWidth
+ boxHeight = taskHeight
+ } else {
+ // Otherwise task is in grid, and should use lastComputedGridTaskSize.
+ boxWidth = lastComputedGridTaskSize.width()
+ boxHeight = lastComputedGridTaskSize.height()
+ }
+
+ // Bound width/height to the box size.
+ expectedWidth = boxWidth
+ expectedHeight = boxHeight + thumbnailPadding
+
+ // Scale to to fit task Rect.
+ nonGridScale =
+ if (enableGridOnlyOverview()) {
+ lastComputedCarouselTaskSize.width() / taskWidth.toFloat()
+ } else {
+ taskWidth / boxWidth.toFloat()
+ }
+
+ // Align to top of task Rect.
+ boxTranslationY = (expectedHeight - thumbnailPadding - taskHeight) / 2.0f
+ } else {
+ nonGridScale = 1f
+ boxTranslationY = 0f
+ expectedWidth = if (enableOverviewIconMenu()) taskWidth else LayoutParams.MATCH_PARENT
+ expectedHeight =
+ if (enableOverviewIconMenu()) taskHeight + thumbnailPadding
+ else LayoutParams.MATCH_PARENT
+ }
+ this.nonGridScale = nonGridScale
+ this.boxTranslationY = boxTranslationY
+ updateLayoutParams<ViewGroup.LayoutParams> {
+ width = expectedWidth
+ height = expectedHeight
+ }
+ }
+
+ /** Returns the thumbnail's bounds, optionally relative to the screen. */
+ @JvmOverloads
+ open fun getThumbnailBounds(bounds: Rect, relativeToDragLayer: Boolean = false) {
+ val snapshotView = snapshotView
+ if (relativeToDragLayer) {
+ container.dragLayer.getDescendantRectRelativeToSelf(snapshotView, bounds)
+ } else {
+ bounds.apply {
+ set(snapshotView)
+ offset(Math.round(snapshotView.translationX), Math.round(snapshotView.translationY))
+ }
+ }
+ }
+
+ /**
+ * See [TaskDataChanges]
+ *
+ * @param visible If this task view will be visible to the user in overview or hidden
+ */
+ fun onTaskListVisibilityChanged(visible: Boolean) {
+ onTaskListVisibilityChanged(visible, FLAG_UPDATE_ALL)
+ }
+
+ /**
+ * See [TaskDataChanges]
+ *
+ * @param visible If this task view will be visible to the user in overview or hidden
+ */
+ open fun onTaskListVisibilityChanged(visible: Boolean, @TaskDataChanges changes: Int) {
+ cancelPendingLoadTasks()
+ val model = RecentsModel.INSTANCE.get(context)
+ // These calls are no-ops if the data is already loaded, try and load the high
+ // resolution thumbnail if the state permits
+ if (needsUpdate(changes, FLAG_UPDATE_THUMBNAIL)) {
+ if (visible) {
+ thumbnailLoadRequest =
+ model.thumbnailCache.updateThumbnailInBackground(firstTask) {
+ if (!enableRefactorTaskThumbnail()) {
+ // TODO(b/334825222) add thumbnail state
+ taskThumbnailViewDeprecated.setThumbnail(firstTask, it)
+ }
+ }
+ } else {
+ if (!enableRefactorTaskThumbnail()) {
+ // TODO(b/334825222) add thumbnail state
+ taskThumbnailViewDeprecated.setThumbnail(null, null)
+ }
+ // Reset the task thumbnail reference as well (it will be fetched from the cache or
+ // reloaded next time we need it)
+ firstTask.thumbnail = null
+ }
+ }
+ if (needsUpdate(changes, FLAG_UPDATE_ICON)) {
+ if (visible) {
+ iconLoadRequest =
+ model.iconCache.updateIconInBackground(firstTask) {
+ setIcon(iconView, it.icon)
+ if (enableOverviewIconMenu()) {
+ setText(iconView, it.title)
+ }
+ digitalWellBeingToast.initialize(it)
+ }
+ } else {
+ setIcon(iconView, null)
+ if (enableOverviewIconMenu()) {
+ setText(iconView, null)
+ }
+ }
+ }
+ if (needsUpdate(changes, FLAG_UPDATE_CORNER_RADIUS)) {
+ currentFullscreenParams.updateCornerRadius(context)
+ }
+ }
+
+ protected fun needsUpdate(@TaskDataChanges dataChange: Int, @TaskDataChanges flag: Int) =
+ (dataChange and flag) == flag
+
+ protected open fun cancelPendingLoadTasks() {
+ thumbnailLoadRequest?.cancel()
+ thumbnailLoadRequest = null
+ iconLoadRequest?.cancel()
+ iconLoadRequest = null
+ }
+
+ protected fun setIcon(iconView: TaskViewIcon, icon: Drawable?) {
+ with(iconView) {
+ if (icon != null) {
+ setDrawable(icon)
+ setOnClickListener {
+ if (!confirmSecondSplitSelectApp()) {
+ showTaskMenu(this)
+ }
+ }
+ setOnLongClickListener {
+ requestDisallowInterceptTouchEvent(true)
+ showTaskMenu(this)
+ }
+ } else {
+ setDrawable(null)
+ setOnClickListener(null)
+ setOnLongClickListener(null)
+ }
+ }
+ }
+
+ protected fun setText(iconView: TaskViewIcon, text: CharSequence?) {
+ iconView.setText(text)
+ }
+
+ open fun refreshThumbnails(thumbnailDatas: HashMap<Int, ThumbnailData?>?) {
+ if (enableRefactorTaskThumbnail()) {
+ // TODO(b/334825222) add thumbnail logic
+ return
+ }
+ thumbnailDatas?.get(firstTask.key.id)?.let {
+ taskThumbnailViewDeprecated.setThumbnail(firstTask, it)
+ }
+ ?: { taskThumbnailViewDeprecated.refresh() }
+ }
+
+ private fun onClick(view: View) {
+ if (confirmSecondSplitSelectApp()) {
+ Log.d("b/310064698", "${taskIds.contentToString()} - onClick - split select is active")
+ return
+ }
+ val callbackList =
+ launchTasks()?.apply {
+ add {
+ Log.d("b/310064698", "${taskIds.contentToString()} - onClick - launchCompleted")
+ }
+ }
+ Log.d("b/310064698", "${taskIds.contentToString()} - onClick - callbackList: $callbackList")
+ container.statsLogManager
+ .logger()
+ .withItemInfo(getItemInfo())
+ .log(LauncherEvent.LAUNCHER_TASK_LAUNCH_TAP)
+ }
+
+ /**
+ * Starts the task associated with this view and animates the startup.
+ *
+ * @return CompletionStage to indicate the animation completion or null if the launch failed.
+ */
+ open fun launchTaskAnimated(): RunnableList? {
+ TestLogging.recordEvent(
+ TestProtocol.SEQUENCE_MAIN,
+ "startActivityFromRecentsAsync",
+ taskIds.contentToString()
+ )
+ val opts =
+ container.getActivityLaunchOptions(this, null).apply {
+ options.launchDisplayId = display?.displayId ?: Display.DEFAULT_DISPLAY
+ }
+ if (
+ ActivityManagerWrapper.getInstance()
+ .startActivityFromRecents(firstTask.key, opts.options)
+ ) {
+ Log.d(
+ TAG,
+ "launchTaskAnimated - startActivityFromRecents: ${taskIds.contentToString()}"
+ )
+ ActiveGestureLog.INSTANCE.trackEvent(
+ ActiveGestureErrorDetector.GestureEvent.EXPECTING_TASK_APPEARED
+ )
+ val recentsView = recentsView ?: return null
+ if (recentsView.runningTaskViewId != -1) {
+ recentsView.onTaskLaunchedInLiveTileMode()
+
+ // Return a fresh callback in the live tile case, so that it's not accidentally
+ // triggered by QuickstepTransitionManager.AppLaunchAnimationRunner.
+ return RunnableList().also { recentsView.addSideTaskLaunchCallback(it) }
+ }
+ if (TaskAnimationManager.ENABLE_SHELL_TRANSITIONS) {
+ // If the recents transition is running (ie. in live tile mode), then the start
+ // of a new task will merge into the existing transition and it currently will
+ // not be run independently, so we need to rely on the onTaskAppeared() call
+ // for the new task to trigger the side launch callback to flush this runnable
+ // list (which is usually flushed when the app launch animation finishes)
+ recentsView.addSideTaskLaunchCallback(opts.onEndCallback)
+ }
+ return opts.onEndCallback
+ } else {
+ notifyTaskLaunchFailed()
+ return null
+ }
+ }
+
+ /** Starts the task associated with this view without any animation */
+ fun launchTask(callback: (launched: Boolean) -> Unit) {
+ launchTask(callback, isQuickSwitch = false)
+ }
+
+ /** Starts the task associated with this view without any animation */
+ open fun launchTask(callback: (launched: Boolean) -> Unit, isQuickSwitch: Boolean) {
+ TestLogging.recordEvent(
+ TestProtocol.SEQUENCE_MAIN,
+ "startActivityFromRecentsAsync",
+ taskIds.contentToString()
+ )
+ val failureListener = TaskRemovedDuringLaunchListener(context.applicationContext)
+ if (isQuickSwitch) {
+ // We only listen for failures to launch in quickswitch because the during this
+ // gesture launcher is in the background state, vs other launches which are in
+ // the actual overview state
+ failureListener.register(container, firstTask.key.id) {
+ notifyTaskLaunchFailed()
+ recentsView?.let {
+ // Disable animations for now, as it is an edge case and the app usually
+ // covers launcher and also any state transition animation also gets
+ // clobbered by QuickstepTransitionManager.createWallpaperOpenAnimations
+ // when launcher shows again
+ it.startHome(false /* animated */)
+ // LauncherTaskbarUIController depends on the launcher state when
+ // checking whether to handle resume, but that can come in before
+ // startHome() changes the state, so force-refresh here to ensure the
+ // taskbar is updated
+ it.mSizeStrategy.taskbarController?.refreshResumedState()
+ }
+ }
+ }
+ // Indicate success once the system has indicated that the transition has started
+ val opts =
+ ActivityOptions.makeCustomTaskAnimation(
+ context,
+ 0,
+ 0,
+ Executors.MAIN_EXECUTOR.handler,
+ { callback(true) }
+ ) {
+ failureListener.onTransitionFinished()
+ }
+ .apply {
+ launchDisplayId = display?.displayId ?: Display.DEFAULT_DISPLAY
+ if (isQuickSwitch) {
+ setFreezeRecentTasksReordering()
+ }
+ // TODO(b/334826842) add splash functionality to new TTV
+ if (!enableRefactorTaskThumbnail()) {
+ disableStartingWindow = taskThumbnailViewDeprecated.shouldShowSplashView()
+ }
+ }
+ Executors.UI_HELPER_EXECUTOR.execute {
+ if (
+ !ActivityManagerWrapper.getInstance().startActivityFromRecents(firstTask.key, opts)
+ ) {
+ // If the call to start activity failed, then post the result immediately,
+ // otherwise, wait for the animation start callback from the activity options
+ // above
+ Executors.MAIN_EXECUTOR.post {
+ notifyTaskLaunchFailed()
+ callback(false)
+ }
+ }
+ Log.d(TAG, "launchTask - startActivityFromRecents: ${taskIds.contentToString()}")
+ }
+ }
+
+ /** Launch of the current task (both live and inactive tasks) with an animation. */
+ fun launchTasks(): RunnableList? {
+ val recentsView = recentsView ?: return null
+ val remoteTargetHandles = recentsView.mRemoteTargetHandles
+ if (!isRunningTask || remoteTargetHandles == null) {
+ return launchTaskAnimated()
+ }
+ if (!isClickableAsLiveTile) {
+ Log.e(TAG, "TaskView is not clickable as a live tile; returning to home.")
+ return null
+ }
+ isClickableAsLiveTile = false
+ val targets =
+ if (remoteTargetHandles.size == 1) {
+ remoteTargetHandles[0].transformParams.targetSet
+ } else {
+ val apps =
+ remoteTargetHandles.flatMap { it.transformParams.targetSet.apps.asIterable() }
+ val wallpapers =
+ remoteTargetHandles.flatMap {
+ it.transformParams.targetSet.wallpapers.asIterable()
+ }
+ RemoteAnimationTargets(
+ apps.toTypedArray(),
+ wallpapers.toTypedArray(),
+ remoteTargetHandles[0].transformParams.targetSet.nonApps,
+ remoteTargetHandles[0].transformParams.targetSet.targetMode
+ )
+ }
+ if (targets == null) {
+ // If the recents animation is cancelled somehow between the parent if block and
+ // here, try to launch the task as a non live tile task.
+ val runnableList = launchTaskAnimated()
+ if (runnableList == null) {
+ Log.e(
+ TAG,
+ "Recents animation cancelled and cannot launch task as non-live tile" +
+ "; returning to home"
+ )
+ }
+ isClickableAsLiveTile = true
+ return runnableList
+ }
+ val runnableList = RunnableList()
+ AnimatorSet().apply {
+ TaskViewUtils.composeRecentsLaunchAnimator(
+ this,
+ this@TaskView,
+ targets.apps,
+ targets.wallpapers,
+ targets.nonApps,
+ true /* launcherClosing */,
+ recentsView.stateManager,
+ recentsView,
+ recentsView.depthController
+ )
+ addListener(
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animator: Animator) {
+ if (firstTask.key.displayId != rootViewDisplayId) {
+ launchTaskAnimated()
+ }
+ isClickableAsLiveTile = true
+ runEndCallback()
+ }
+
+ override fun onAnimationCancel(animation: Animator) {
+ runEndCallback()
+ }
+
+ private fun runEndCallback() {
+ runnableList.executeAllAndDestroy()
+ }
+ }
+ )
+ start()
+ }
+ Log.d(TAG, "launchTasks - composeRecentsLaunchAnimator: ${taskIds.contentToString()}")
+ recentsView.onTaskLaunchedInLiveTileMode()
+ return runnableList
+ }
+
+ private fun notifyTaskLaunchFailed() {
+ Log.w(
+ TAG,
+ "Failed to launch task (task=${firstTask.key.baseIntent} userId=${firstTask.key.userId})"
+ )
+ Toast.makeText(context, R.string.activity_not_available, Toast.LENGTH_SHORT).show()
+ }
+
+ fun initiateSplitSelect(splitPositionOption: SplitPositionOption) {
+ recentsView?.initiateSplitSelect(
+ this,
+ splitPositionOption.stagePosition,
+ SplitConfigurationOptions.getLogEventForPosition(splitPositionOption.stagePosition)
+ )
+ }
+
+ /**
+ * Returns `true` if user is already in split select mode and this tap was to choose the second
+ * app. `false` otherwise
+ */
+ protected open fun confirmSecondSplitSelectApp(): Boolean {
+ val index = getLastSelectedChildTaskIndex()
+ if (index >= taskContainers.size) {
+ return false
+ }
+ val container = taskContainers[index]
+ val recentsView = recentsView ?: return false
+ return recentsView.confirmSplitSelect(
+ this,
+ container.task,
+ container.iconView.drawable,
+ container.thumbnailView,
+ container.thumbnailView.thumbnail, /* intent */
+ null, /* user */
+ null,
+ container.itemInfo
+ )
+ }
+
+ /**
+ * Returns the task index of the last selected child task (0 or 1). If we contain multiple tasks
+ * and this TaskView is used as part of split selection, the selected child task index will be
+ * that of the remaining task.
+ */
+ protected open fun getLastSelectedChildTaskIndex() = 0
+
+ private fun showTaskMenu(iconView: TaskViewIcon): Boolean {
+ val recentsView = recentsView ?: return false
+ if (!recentsView.canLaunchFullscreenTask()) {
+ // Don't show menu when selecting second split screen app
+ return true
+ }
+ if (!container.deviceProfile.isTablet && !recentsView.isClearAllHidden) {
+ recentsView.snapToPage(recentsView.indexOfChild(this))
+ return false
+ }
+ val menuContainer = taskContainers.firstOrNull { it.iconView === iconView } ?: return false
+ container.statsLogManager
+ .logger()
+ .withItemInfo(menuContainer.itemInfo)
+ .log(LauncherEvent.LAUNCHER_TASK_ICON_TAP_OR_LONGPRESS)
+ return showTaskMenuWithContainer(menuContainer)
+ }
+
+ private fun showTaskMenuWithContainer(menuContainer: TaskContainer): Boolean {
+ val recentsView = recentsView ?: return false
+ return if (enableOverviewIconMenu() && menuContainer.iconView is IconAppChipView) {
+ menuContainer.iconView.revealAnim(/* isRevealing= */ true)
+ TaskMenuView.showForTask(menuContainer) {
+ menuContainer.iconView.revealAnim(/* isRevealing= */ false)
+ }
+ } else if (container.deviceProfile.isTablet) {
+ val alignedOptionIndex =
+ if (
+ recentsView.isOnGridBottomRow(menuContainer.taskView) &&
+ container.deviceProfile.isLandscape
+ ) {
+ if (enableGridOnlyOverview()) {
+ // With no focused task, there is less available space below the tasks, so
+ // align the arrow to the third option in the menu.
+ 2
+ } else {
+ // Bottom row of landscape grid aligns arrow to second option to avoid
+ // clipping
+ 1
+ }
+ } else {
+ 0
+ }
+ TaskMenuViewWithArrow.showForTask(menuContainer, alignedOptionIndex)
+ } else {
+ TaskMenuView.showForTask(menuContainer)
+ }
+ }
+
+ @Deprecated("Use {@link #getItemInfo(Task)} instead.")
+ /** Builds proto for logging. */
+ fun getItemInfo() = getItemInfo(firstTask)
+
+ /** Builds proto for logging */
+ @VisibleForTesting
+ fun getItemInfo(task: Task): WorkspaceItemInfo {
+ return WorkspaceItemInfo().apply {
+ itemType = LauncherSettings.Favorites.ITEM_TYPE_TASK
+ container = LauncherSettings.Favorites.CONTAINER_TASKSWITCHER
+ val componentKey = TaskUtils.getLaunchComponentKeyForTask(task.key)
+ user = componentKey.user
+ intent = Intent().setComponent(componentKey.componentName)
+ title = task.title
+ recentsView?.let { screenId = it.indexOfChild(this@TaskView) }
+ if (privateSpaceRestrictAccessibilityDrag()) {
+ if (UserCache.getInstance(context).getUserInfo(componentKey.user).isPrivate) {
+ runtimeStatusFlags = runtimeStatusFlags or ItemInfoWithIcon.FLAG_NOT_PINNABLE
+ }
+ }
+ }
+ }
+
+ /**
+ * Whether the taskview should take the touch event from parent. Events passed to children that
+ * might require special handling.
+ */
+ open fun offerTouchToChildren(event: MotionEvent): Boolean {
+ if (event.action == MotionEvent.ACTION_DOWN) {
+ computeAndSetIconTouchDelegate(iconView, mIconCenterCoordinates, iconTouchDelegate)
+ }
+ return iconTouchDelegate.onTouchEvent(event)
+ }
+
+ protected fun computeAndSetIconTouchDelegate(
+ view: TaskViewIcon,
+ tempCenterCoordinates: FloatArray,
+ transformingTouchDelegate: TransformingTouchDelegate
+ ) {
+ val viewHalfWidth = view.width / 2f
+ val viewHalfHeight = view.height / 2f
+ Utilities.getDescendantCoordRelativeToAncestor(
+ view.asView(),
+ container.dragLayer,
+ tempCenterCoordinates.apply {
+ this[0] = viewHalfWidth
+ this[1] = viewHalfHeight
+ },
+ false
+ )
+ transformingTouchDelegate.setBounds(
+ (tempCenterCoordinates[0] - viewHalfWidth).toInt(),
+ (tempCenterCoordinates[1] - viewHalfHeight).toInt(),
+ (tempCenterCoordinates[0] + viewHalfWidth).toInt(),
+ (tempCenterCoordinates[1] + viewHalfHeight).toInt()
+ )
+ }
+
+ /** Sets up an on-click listener and the visibility for show_windows icon on top of the task. */
+ open fun setUpShowAllInstancesListener() {
+ val taskPackageName = taskContainers[0].task.key.packageName
+ // icon of the top/left task
+ val showWindowsView = findViewById<View>(R.id.show_windows)!!
+ updateFilterCallback(showWindowsView, getFilterUpdateCallback(taskPackageName))
+ }
+
+ /**
+ * Returns a callback that updates the state of the filter and the recents overview
+ *
+ * @param taskPackageName package name of the task to filter by
+ */
+ protected fun getFilterUpdateCallback(taskPackageName: String?) =
+ if (recentsView?.filterState?.shouldShowFilterUI(taskPackageName) == true)
+ OnClickListener { recentsView?.setAndApplyFilter(taskPackageName) }
+ else null
+
+ /**
+ * Sets the correct visibility and callback on the provided filterView based on whether the
+ * callback is null or not
+ */
+ protected fun updateFilterCallback(filterView: View, callback: OnClickListener?) {
+ // Filtering changes alpha instead of the visibility since visibility
+ // can be altered separately through RecentsView#resetFromSplitSelectionState()
+ filterView.alpha = if (callback == null) 0f else 1f
+ filterView.setOnClickListener(callback)
+ }
+
+ protected open fun setIconsAndBannersFullscreenProgress(progress: Float) {
+ // Animate icons and DWB banners in/out, except in QuickSwitch state, when tiles are
+ // oversized and banner would look disproportionately large.
+ if (recentsView?.stateManager?.state == LauncherState.BACKGROUND_APP) {
+ return
+ }
+ setIconsAndBannersTransitionProgress(progress, invert = true)
+ }
+
+ /**
+ * Called to animate a smooth transition when going directly from an app into Overview (and vice
+ * versa). Icons fade in, and DWB banners slide in with a "shift up" animation.
+ */
+ protected open fun setIconsAndBannersTransitionProgress(progress: Float, invert: Boolean) {
+ focusTransitionProgress = if (invert) 1 - progress else progress
+ getIconContentScale(invert).let { iconContentScale ->
+ iconView.setContentAlpha(iconContentScale)
+ digitalWellBeingToast.updateBannerOffset(1f - iconContentScale)
+ }
+ }
+
+ protected fun getIconContentScale(invert: Boolean): Float {
+ val iconScalePercentage = SCALE_ICON_DURATION.toFloat() / DIM_ANIM_DURATION
+ val lowerClamp = if (invert) 1f - iconScalePercentage else 0f
+ val upperClamp = if (invert) 1f else iconScalePercentage
+ return Interpolators.clampToProgress(Interpolators.FAST_OUT_SLOW_IN, lowerClamp, upperClamp)
+ .getInterpolation(focusTransitionProgress)
+ }
+
+ fun animateIconScaleAndDimIntoView() {
+ iconAndDimAnimator?.cancel()
+ iconAndDimAnimator =
+ ObjectAnimator.ofFloat(this, FOCUS_TRANSITION, 1f).apply {
+ setCurrentFraction(iconScaleAnimStartProgress)
+ setDuration(DIM_ANIM_DURATION).interpolator = Interpolators.LINEAR
+ addListener(
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator) {
+ iconAndDimAnimator = null
+ }
+ }
+ )
+ start()
+ }
+ }
+
+ fun setIconScaleAndDim(iconScale: Float) {
+ iconAndDimAnimator?.cancel()
+ setIconsAndBannersTransitionProgress(iconScale, invert = false)
+ }
+
+ /** Set a color tint on the snapshot and supporting views. */
+ open fun setColorTint(amount: Float, tintColor: Int) {
+ if (!enableRefactorTaskThumbnail()) {
+ // TODO(b/334832108) Add scrim to new TTV
+ taskThumbnailViewDeprecated.setDimAlpha(amount)
+ }
+ iconView.setIconColorTint(tintColor, amount)
+ digitalWellBeingToast.setBannerColorTint(tintColor, amount)
+ }
+
+ /**
+ * 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-[View.VISIBLE] value
+ */
+ open fun setThumbnailVisibility(visibility: Int, taskId: Int) {
+ children.filterNot { it === iconView }.forEach { it.visibility = visibility }
+ }
+
+ open fun setOverlayEnabled(overlayEnabled: Boolean) {
+ // TODO(b/335606129) Investigate the usage of [TaskOverlay] in the new TaskThumbnailView.
+ // and if it's still necessary we should support that in the new TTV class.
+ if (!enableRefactorTaskThumbnail()) {
+ taskThumbnailViewDeprecated.setOverlayEnabled(overlayEnabled)
+ }
+ }
+
+ protected open fun refreshTaskThumbnailSplash() {
+ if (!enableRefactorTaskThumbnail()) {
+ // TODO(b/334826842) add splash functionality to new TTV
+ taskThumbnailViewDeprecated.refreshSplashView()
+ }
+ }
+
+ protected fun getScrollAdjustment(gridEnabled: Boolean) =
+ if (gridEnabled) gridTranslationX else nonGridTranslationX
+
+ protected fun getOffsetAdjustment(gridEnabled: Boolean) = getScrollAdjustment(gridEnabled)
+
+ fun getSizeAdjustment(fullscreenEnabled: Boolean) = if (fullscreenEnabled) nonGridScale else 1f
+
+ private fun applyScale() {
+ val scale = persistentScale * dismissScale
+ scaleX = scale
+ scaleY = scale
+ if (enableRefactorTaskThumbnail()) {
+ taskViewData.scale.value = scale
+ }
+ updateSnapshotRadius()
+ }
+
+ protected open fun applyThumbnailSplashAlpha() {
+ if (!enableRefactorTaskThumbnail()) {
+ // TODO(b/334826842) add splash functionality to new TTV
+ taskThumbnailViewDeprecated.setSplashAlpha(taskThumbnailSplashAlpha)
+ }
+ }
+
+ private fun applyTranslationX() {
+ translationX =
+ dismissTranslationX +
+ taskOffsetTranslationX +
+ taskResistanceTranslationX +
+ splitSelectTranslationX +
+ gridEndTranslationX +
+ persistentTranslationX
+ }
+
+ private fun applyTranslationY() {
+ translationY =
+ dismissTranslationY +
+ taskOffsetTranslationY +
+ taskResistanceTranslationY +
+ splitSelectTranslationY +
+ persistentTranslationY
+ }
+
+ private fun onGridProgressChanged() {
+ applyTranslationX()
+ applyTranslationY()
+ applyScale()
+ }
+
+ protected open fun onFullscreenProgressChanged(fullscreenProgress: Float) {
+ iconView.setVisibility(if (fullscreenProgress < 1) VISIBLE else INVISIBLE)
+ taskThumbnailViewDeprecated.taskOverlay.setFullscreenProgress(fullscreenProgress)
+ setIconsAndBannersFullscreenProgress(fullscreenProgress)
+ updateSnapshotRadius()
+ }
+
+ protected open fun updateSnapshotRadius() {
+ updateCurrentFullscreenParams()
+ taskThumbnailViewDeprecated.setFullscreenParams(currentFullscreenParams)
+ }
+
+ protected fun updateCurrentFullscreenParams() {
+ updateFullscreenParams(currentFullscreenParams)
+ }
+
+ protected fun updateFullscreenParams(fullscreenParams: FullscreenDrawParams) {
+ recentsView?.let { fullscreenParams.setProgress(fullscreenProgress, it.scaleX, scaleX) }
+ }
+
+ private fun onModalnessUpdated(modalness: Float) {
+ iconView.setModalAlpha(1 - modalness)
+ digitalWellBeingToast.updateBannerOffset(modalness)
+ }
+
+ /** Updates [TaskThumbnailView] to reflect the latest [Task] state (i.e., task isRunning). */
+ fun notifyIsRunningTaskUpdated() {
+ // TODO(b/335649589): TaskView's VM will already have access to TaskThumbnailView's VM
+ // so there will be no need to access TaskThumbnailView's VM through the TaskThumbnailView
+ if (taskContainers.isNotEmpty()) {
+ bindTaskThumbnailView()
+ }
+ }
+
+ fun resetPersistentViewTransforms() {
+ nonGridTranslationX = 0f
+ gridTranslationX = 0f
+ gridTranslationY = 0f
+ boxTranslationY = 0f
+ nonGridPivotTranslationX = 0f
+ resetViewTransforms()
+ }
+
+ open fun resetViewTransforms() {
+ // fullscreenTranslation and accumulatedTranslation should not be reset, as
+ // resetViewTransforms is called during QuickSwitch scrolling.
+ dismissTranslationX = 0f
+ taskOffsetTranslationX = 0f
+ taskResistanceTranslationX = 0f
+ splitSelectTranslationX = 0f
+ gridEndTranslationX = 0f
+ dismissTranslationY = 0f
+ taskOffsetTranslationY = 0f
+ taskResistanceTranslationY = 0f
+ if (recentsView?.isSplitSelectionActive != true) {
+ splitSelectTranslationY = 0f
+ }
+ dismissScale = 1f
+ translationZ = 0f
+ alpha = stableAlpha
+ setIconScaleAndDim(1f)
+ setColorTint(0f, 0)
+ if (!enableRefactorTaskThumbnail()) {
+ // TODO(b/335399428) add split select functionality to new TTV
+ taskThumbnailViewDeprecated.resetViewTransforms()
+ }
+ }
+
+ private fun getGridTrans(endTranslation: Float) =
+ Utilities.mapRange(gridProgress, 0f, endTranslation)
+
+ private fun getNonGridTrans(endTranslation: Float) =
+ endTranslation - getGridTrans(endTranslation)
+
+ /** We update and subsequently draw these in [fullscreenProgress]. */
+ open class FullscreenDrawParams(context: Context) : SafeCloseable {
+ var cornerRadius = 0f
+ private var windowCornerRadius = 0f
+ var currentDrawnCornerRadius = 0f
+
+ init {
+ updateCornerRadius(context)
+ }
+
+ /** Recomputes the start and end corner radius for the given Context. */
+ fun updateCornerRadius(context: Context) {
+ cornerRadius = computeTaskCornerRadius(context)
+ windowCornerRadius = computeWindowCornerRadius(context)
+ }
+
+ @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
+ open fun computeTaskCornerRadius(context: Context): Float {
+ return TaskCornerRadius.get(context)
+ }
+
+ @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
+ open fun computeWindowCornerRadius(context: Context): Float {
+ return QuickStepContract.getWindowCornerRadius(context)
+ }
+
+ /** Sets the progress in range [0, 1] */
+ fun setProgress(fullscreenProgress: Float, parentScale: Float, taskViewScale: Float) {
+ currentDrawnCornerRadius =
+ Utilities.mapRange(fullscreenProgress, cornerRadius, windowCornerRadius) /
+ parentScale /
+ taskViewScale
+ }
+
+ override fun close() {}
+ }
+
+ /** Holder for all Task dependent information. */
+ inner class TaskContainer(
+ val task: Task,
+ val thumbnailView: TaskThumbnailViewDeprecated,
+ val iconView: TaskViewIcon,
+ /** Defaults to STAGE_POSITION_UNDEFINED if in not a split screen task view */
+ @field:StagePosition var stagePosition: Int,
+ val digitalWellBeingToast: DigitalWellBeingToast?
+ ) {
+ @IdRes
+ val a11yNodeId: Int =
+ if (stagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT) R.id.split_bottomRight_appInfo
+ else R.id.split_topLeft_appInfo
+
+ val itemInfo: WorkspaceItemInfo
+ get() = getItemInfo(task)
+ val taskView: TaskView
+ get() = this@TaskView
+ }
+
+ companion object {
+ private const val TAG = "TaskView"
+ const val FLAG_UPDATE_ICON = 1
+ const val FLAG_UPDATE_THUMBNAIL = FLAG_UPDATE_ICON shl 1
+ const val FLAG_UPDATE_CORNER_RADIUS = FLAG_UPDATE_THUMBNAIL shl 1
+ const val FLAG_UPDATE_ALL =
+ (FLAG_UPDATE_ICON or FLAG_UPDATE_THUMBNAIL or FLAG_UPDATE_CORNER_RADIUS)
+
+ /** The maximum amount that a task view can be scrimmed, dimmed or tinted. */
+ const val MAX_PAGE_SCRIM_ALPHA = 0.4f
+ const val SCALE_ICON_DURATION: Long = 120
+ private const val DIM_ANIM_DURATION: Long = 700
+ private val SYSTEM_GESTURE_EXCLUSION_RECT = listOf(Rect())
+
+ @JvmField
+ val FOCUS_TRANSITION: FloatProperty<TaskView> =
+ object : FloatProperty<TaskView>("focusTransition") {
+ override fun setValue(taskView: TaskView, v: Float) {
+ taskView.setIconsAndBannersTransitionProgress(v, false /* invert */)
+ }
+
+ override fun get(taskView: TaskView) = taskView.focusTransitionProgress
+ }
+ private val SPLIT_SELECT_TRANSLATION_X: FloatProperty<TaskView> =
+ object : FloatProperty<TaskView>("splitSelectTranslationX") {
+ override fun setValue(taskView: TaskView, v: Float) {
+ taskView.splitSelectTranslationX = v
+ }
+
+ override fun get(taskView: TaskView) = taskView.splitSelectTranslationX
+ }
+ private val SPLIT_SELECT_TRANSLATION_Y: FloatProperty<TaskView> =
+ object : FloatProperty<TaskView>("splitSelectTranslationY") {
+ override fun setValue(taskView: TaskView, v: Float) {
+ taskView.splitSelectTranslationY = v
+ }
+
+ override fun get(taskView: TaskView) = taskView.splitSelectTranslationY
+ }
+ private val DISMISS_TRANSLATION_X: FloatProperty<TaskView> =
+ object : FloatProperty<TaskView>("dismissTranslationX") {
+ override fun setValue(taskView: TaskView, v: Float) {
+ taskView.dismissTranslationX = v
+ }
+
+ override fun get(taskView: TaskView) = taskView.dismissTranslationX
+ }
+ private val DISMISS_TRANSLATION_Y: FloatProperty<TaskView> =
+ object : FloatProperty<TaskView>("dismissTranslationY") {
+ override fun setValue(taskView: TaskView, v: Float) {
+ taskView.dismissTranslationY = v
+ }
+
+ override fun get(taskView: TaskView) = taskView.dismissTranslationY
+ }
+ private val TASK_OFFSET_TRANSLATION_X: FloatProperty<TaskView> =
+ object : FloatProperty<TaskView>("taskOffsetTranslationX") {
+ override fun setValue(taskView: TaskView, v: Float) {
+ taskView.taskOffsetTranslationX = v
+ }
+
+ override fun get(taskView: TaskView) = taskView.taskOffsetTranslationX
+ }
+ private val TASK_OFFSET_TRANSLATION_Y: FloatProperty<TaskView> =
+ object : FloatProperty<TaskView>("taskOffsetTranslationY") {
+ override fun setValue(taskView: TaskView, v: Float) {
+ taskView.taskOffsetTranslationY = v
+ }
+
+ override fun get(taskView: TaskView) = taskView.taskOffsetTranslationY
+ }
+ private val TASK_RESISTANCE_TRANSLATION_X: FloatProperty<TaskView> =
+ object : FloatProperty<TaskView>("taskResistanceTranslationX") {
+ override fun setValue(taskView: TaskView, v: Float) {
+ taskView.taskResistanceTranslationX = v
+ }
+
+ override fun get(taskView: TaskView) = taskView.taskResistanceTranslationX
+ }
+ private val TASK_RESISTANCE_TRANSLATION_Y: FloatProperty<TaskView> =
+ object : FloatProperty<TaskView>("taskResistanceTranslationY") {
+ override fun setValue(taskView: TaskView, v: Float) {
+ taskView.taskResistanceTranslationY = v
+ }
+
+ override fun get(taskView: TaskView) = taskView.taskResistanceTranslationY
+ }
+ @JvmField
+ val GRID_END_TRANSLATION_X: FloatProperty<TaskView> =
+ object : FloatProperty<TaskView>("gridEndTranslationX") {
+ override fun setValue(taskView: TaskView, v: Float) {
+ taskView.gridEndTranslationX = v
+ }
+
+ override fun get(taskView: TaskView) = taskView.gridEndTranslationX
+ }
+ @JvmField
+ val DISMISS_SCALE: FloatProperty<TaskView> =
+ object : FloatProperty<TaskView>("dismissScale") {
+ override fun setValue(taskView: TaskView, v: Float) {
+ taskView.dismissScale = v
+ }
+
+ override fun get(taskView: TaskView) = taskView.dismissScale
+ }
+ }
+}
diff --git a/quickstep/tests/src/com/android/quickstep/DesktopSystemShortcutTest.kt b/quickstep/tests/src/com/android/quickstep/DesktopSystemShortcutTest.kt
index d59aafb..33a8e79 100644
--- a/quickstep/tests/src/com/android/quickstep/DesktopSystemShortcutTest.kt
+++ b/quickstep/tests/src/com/android/quickstep/DesktopSystemShortcutTest.kt
@@ -30,10 +30,13 @@
import com.android.launcher3.uioverrides.QuickstepLauncher
import com.android.launcher3.util.SplitConfigurationOptions
import com.android.quickstep.views.LauncherRecentsView
+import com.android.quickstep.views.TaskThumbnailViewDeprecated
import com.android.quickstep.views.TaskView
+import com.android.quickstep.views.TaskViewIcon
import com.android.systemui.shared.recents.model.Task
import com.android.systemui.shared.recents.model.Task.TaskKey
import com.android.window.flags.Flags
+import com.android.wm.shell.shared.DesktopModeStatus
import com.google.common.truth.Truth.assertThat
import org.junit.After
import org.junit.Before
@@ -58,6 +61,8 @@
private val taskView: TaskView = mock()
private val workspaceItemInfo: WorkspaceItemInfo = mock()
private val abstractFloatingViewHelper: AbstractFloatingViewHelper = mock()
+ private val thumbnailView: TaskThumbnailViewDeprecated = mock()
+ private val iconView: TaskViewIcon = mock()
private val factory: TaskShortcutFactory =
DesktopSystemShortcut.createFactory(abstractFloatingViewHelper)
@@ -90,8 +95,8 @@
val taskContainer =
taskView.TaskContainer(
task,
- null,
- null,
+ thumbnailView,
+ iconView,
SplitConfigurationOptions.STAGE_POSITION_UNDEFINED,
null
)
@@ -112,8 +117,8 @@
val taskContainer =
taskView.TaskContainer(
task,
- null,
- null,
+ thumbnailView,
+ iconView,
SplitConfigurationOptions.STAGE_POSITION_UNDEFINED,
null
)
@@ -135,8 +140,8 @@
val taskContainer =
taskView.TaskContainer(
task,
- null,
- null,
+ thumbnailView,
+ iconView,
SplitConfigurationOptions.STAGE_POSITION_UNDEFINED,
null
)
@@ -156,8 +161,8 @@
val taskContainer =
taskView.TaskContainer(
task,
- null,
- null,
+ thumbnailView,
+ iconView,
SplitConfigurationOptions.STAGE_POSITION_UNDEFINED,
null
)
@@ -177,8 +182,8 @@
val taskContainer =
taskView.TaskContainer(
task,
- null,
- null,
+ thumbnailView,
+ iconView,
SplitConfigurationOptions.STAGE_POSITION_UNDEFINED,
null
)
diff --git a/quickstep/tests/src/com/android/quickstep/FullscreenDrawParamsTest.kt b/quickstep/tests/src/com/android/quickstep/FullscreenDrawParamsTest.kt
index db06b6b..5d62a4c 100644
--- a/quickstep/tests/src/com/android/quickstep/FullscreenDrawParamsTest.kt
+++ b/quickstep/tests/src/com/android/quickstep/FullscreenDrawParamsTest.kt
@@ -53,7 +53,7 @@
)
val expectedRadius = TaskCornerRadius.get(context)
- assertThat(params.mCurrentDrawnCornerRadius).isEqualTo(expectedRadius)
+ assertThat(params.currentDrawnCornerRadius).isEqualTo(expectedRadius)
}
@Test
@@ -67,7 +67,7 @@
)
val expectedRadius = QuickStepContract.getWindowCornerRadius(context)
- assertThat(params.mCurrentDrawnCornerRadius).isEqualTo(expectedRadius)
+ assertThat(params.currentDrawnCornerRadius).isEqualTo(expectedRadius)
}
@Test
@@ -81,7 +81,7 @@
)
val expectedRadius = TaskCornerRadius.get(context)
- assertThat(params.mCurrentDrawnCornerRadius).isEqualTo(expectedRadius)
+ assertThat(params.currentDrawnCornerRadius).isEqualTo(expectedRadius)
}
@Test
@@ -95,7 +95,7 @@
)
val expectedRadius = QuickStepContract.getWindowCornerRadius(context)
- assertThat(params.mCurrentDrawnCornerRadius).isEqualTo(expectedRadius)
+ assertThat(params.currentDrawnCornerRadius).isEqualTo(expectedRadius)
}
@Test
@@ -117,7 +117,7 @@
/* parentScale= */ 1.0f,
/* taskViewScale= */ 1.0f
)
- assertThat(spyParams.mCurrentDrawnCornerRadius).isEqualTo(display1TaskRadius)
+ assertThat(spyParams.currentDrawnCornerRadius).isEqualTo(display1TaskRadius)
spyParams.updateCornerRadius(display2Context)
spyParams.setProgress(
@@ -125,7 +125,7 @@
/* parentScale= */ 1.0f,
/* taskViewScale= */ 1.0f
)
- assertThat(spyParams.mCurrentDrawnCornerRadius).isEqualTo(display2TaskRadius)
+ assertThat(spyParams.currentDrawnCornerRadius).isEqualTo(display2TaskRadius)
}
@Test
@@ -147,7 +147,7 @@
/* parentScale= */ 1.0f,
/* taskViewScale= */ 1.0f
)
- assertThat(spyParams.mCurrentDrawnCornerRadius).isEqualTo(display1WindowRadius)
+ assertThat(spyParams.currentDrawnCornerRadius).isEqualTo(display1WindowRadius)
spyParams.updateCornerRadius(display2Context)
spyParams.setProgress(
@@ -155,6 +155,6 @@
/* parentScale= */ 1.0f,
/* taskViewScale= */ 1.0f,
)
- assertThat(spyParams.mCurrentDrawnCornerRadius).isEqualTo(display2WindowRadius)
+ assertThat(spyParams.currentDrawnCornerRadius).isEqualTo(display2WindowRadius)
}
}
diff --git a/src/com/android/launcher3/model/PackageUpdatedTask.java b/src/com/android/launcher3/model/PackageUpdatedTask.java
index 29d2269..079987b 100644
--- a/src/com/android/launcher3/model/PackageUpdatedTask.java
+++ b/src/com/android/launcher3/model/PackageUpdatedTask.java
@@ -244,7 +244,16 @@
.query(ShortcutRequest.PINNED);
if (shortcut.isEmpty()) {
isTargetValid = false;
+ if (DEBUG) {
+ Log.d(TAG, "Pinned Shortcut not found for updated"
+ + " package=" + si.getTargetPackage());
+ }
} else {
+ if (DEBUG) {
+ Log.d(TAG, "Found pinned shortcut for updated"
+ + " package=" + si.getTargetPackage()
+ + ", isTargetValid=" + isTargetValid);
+ }
si.updateFromDeepShortcutInfo(shortcut.get(0), context);
infoUpdated = true;
}
@@ -252,6 +261,7 @@
isTargetValid = context.getSystemService(LauncherApps.class)
.isActivityEnabled(cn, mUser);
}
+
if (!isTargetValid && (si.hasStatusFlag(
FLAG_RESTORED_ICON | FLAG_AUTOINSTALL_ICON)
|| si.isArchived())) {
@@ -260,20 +270,24 @@
} else if (si.hasPromiseIconUi()) {
removedShortcuts.add(si.id);
if (DEBUG) {
- Log.d(TAG, "Removing restored shortcut promise icon"
+ FileLog.w(TAG, "Removing restored shortcut promise icon"
+ " that no longer points to valid component."
+ " id=" + si.id
- + ", package=" + si.getTargetPackage());
+ + ", package=" + si.getTargetPackage()
+ + ", status=" + si.status
+ + ", isArchived=" + si.isArchived());
}
return;
}
} else if (!isTargetValid) {
removedShortcuts.add(si.id);
- FileLog.e(TAG, "Removing shortcut that no longer points to"
- + " valid component."
- + " id=" + si.id
- + " package=" + si.getTargetPackage()
- + " status=" + si.status);
+ if (DEBUG) {
+ FileLog.w(TAG, "Removing shortcut that no longer points to"
+ + " valid component."
+ + " id=" + si.id
+ + " package=" + si.getTargetPackage()
+ + " status=" + si.status);
+ }
return;
} else {
si.status = WorkspaceItemInfo.DEFAULT;
diff --git a/src/com/android/launcher3/model/data/WorkspaceItemInfo.java b/src/com/android/launcher3/model/data/WorkspaceItemInfo.java
index 2c533ac..40e3813 100644
--- a/src/com/android/launcher3/model/data/WorkspaceItemInfo.java
+++ b/src/com/android/launcher3/model/data/WorkspaceItemInfo.java
@@ -22,6 +22,7 @@
import android.content.Intent;
import android.content.pm.ShortcutInfo;
import android.text.TextUtils;
+import android.util.Log;
import androidx.annotation.NonNull;
@@ -186,9 +187,12 @@
if (shortcutInfo.isEnabled()) {
runtimeStatusFlags &= ~FLAG_DISABLED_BY_PUBLISHER;
} else {
+ Log.w(TAG, "updateFromDeepShortcutInfo: Updated shortcut has been disabled. "
+ + " package=" + shortcutInfo.getPackage()
+ + " disabledReason=" + shortcutInfo.getDisabledReason());
runtimeStatusFlags |= FLAG_DISABLED_BY_PUBLISHER;
}
- disabledMessage = shortcutInfo.getDisabledMessage();
+
if (shortcutInfo.getDisabledReason() == ShortcutInfo.DISABLED_REASON_VERSION_LOWER) {
runtimeStatusFlags |= FLAG_DISABLED_VERSION_LOWER;
} else {
diff --git a/src/com/android/launcher3/util/rects/Rects.kt b/src/com/android/launcher3/util/rects/Rects.kt
new file mode 100644
index 0000000..ac1f3f8
--- /dev/null
+++ b/src/com/android/launcher3/util/rects/Rects.kt
@@ -0,0 +1,25 @@
+/*
+ * 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.util.rects
+
+import android.graphics.Rect
+import android.view.View
+
+/** Copy the coordinates of the [view] relative to its parent into this rectangle. */
+fun Rect.set(view: View) {
+ set(view.left, view.top, view.right, view.bottom)
+}
diff --git a/tests/src/com/android/launcher3/FakeInvariantDeviceProfileTest.kt b/tests/src/com/android/launcher3/FakeInvariantDeviceProfileTest.kt
index 13d7499..0538870 100644
--- a/tests/src/com/android/launcher3/FakeInvariantDeviceProfileTest.kt
+++ b/tests/src/com/android/launcher3/FakeInvariantDeviceProfileTest.kt
@@ -46,7 +46,7 @@
@IgnoreLimit(ignoreLimit = BuildConfig.IS_STUDIO_BUILD)
abstract class FakeInvariantDeviceProfileTest {
- protected var context: Context? = null
+ protected lateinit var context: Context
protected var inv: InvariantDeviceProfile? = null
protected val info: Info = mock()
protected var windowBounds: WindowBounds? = null
@@ -257,10 +257,10 @@
}
protected fun initializeVarsForTwoPanel(
- isLandscape: Boolean = false,
- isGestureMode: Boolean = true,
- rows: Int = 4,
- cols: Int = 4,
+ isLandscape: Boolean = false,
+ isGestureMode: Boolean = true,
+ rows: Int = 4,
+ cols: Int = 4,
) {
val (x, y) = if (isLandscape) Pair(2208, 1840) else Pair(1840, 2208)