getContainerInterface(int displayId);
-
/**
* Set all the task views to color tint scrim mode, dimming or tinting them all. Allows the
* tasks to be dimmed while other elements in the recents view are left alone.
@@ -6763,11 +6055,12 @@ public abstract class RecentsView<
}
/** Tint the RecentsView and TaskViews in to simulate a scrim. */
+ // TODO(b/187528071): Replace this tinting with a scrim on top of RecentsView
private void setColorTint(float tintAmount) {
mColorTint = tintAmount;
- for (TaskView taskView : getTaskViews()) {
- taskView.setColorTint(mColorTint, mTintingColor);
+ for (int i = 0; i < getTaskViewCount(); i++) {
+ requireTaskViewAt(i).setColorTint(mColorTint, mTintingColor);
}
Drawable scrimBg = mContainer.getScrimView().getBackground();
@@ -6794,10 +6087,10 @@ public abstract class RecentsView<
public boolean showAsGrid() {
return mOverviewGridEnabled || (mCurrentGestureEndTarget != null
&& mSizeStrategy.stateFromGestureEndTarget(mCurrentGestureEndTarget)
- .displayOverviewTasksAsGrid(mContainer.getDeviceProfile()));
+ .displayOverviewTasksAsGrid(mContainer.getDeviceProfile()));
}
- protected boolean showAsFullscreen() {
+ private boolean showAsFullscreen() {
return mOverviewFullscreenEnabled
&& mCurrentGestureEndTarget != GestureState.GestureEndTarget.RECENTS;
}
@@ -6835,7 +6128,7 @@ public abstract class RecentsView<
/**
* @return Corner radius in pixel value for PiP window, which is updated via
- * {@link #mIPipAnimationListener}
+ * {@link #mIPipAnimationListener}
*/
public int getPipCornerRadius() {
return mPipCornerRadius;
@@ -6843,7 +6136,7 @@ public abstract class RecentsView<
/**
* @return Shadow radius in pixel value for PiP window, which is updated via
- * {@link #mIPipAnimationListener}
+ * {@link #mIPipAnimationListener}
*/
public int getPipShadowRadius() {
return mPipShadowRadius;
@@ -7007,68 +6300,6 @@ public abstract class RecentsView<
}
}
- @Override
- public void onCanCreateDesksChanged(boolean canCreateDesks) {
- // TODO: b/389209338 - update the AddDesktopButton's visibility on this.
- }
-
- @Override
- public void onDeskAdded(int displayId, int deskId) {
- // Ignore desk changes that don't belong to this display.
- if (displayId != mContainer.getDisplay().getDisplayId()) {
- return;
- }
-
- if (mUtils.getDesktopTaskViewForDeskId(deskId) != null) {
- Log.e(TAG, "A task view for this desk has already been added.");
- return;
- }
-
- TaskView currentTaskView = getTaskViewAt(mCurrentPage);
-
- // We assume that a newly added desk is always empty and gets added to the left of the
- // `AddNewDesktopButton`.
- DesktopTaskView desktopTaskView =
- (DesktopTaskView) getTaskViewFromPool(TaskViewType.DESKTOP);
- desktopTaskView.bind(new DesktopTask(deskId, displayId, new ArrayList<>()),
- mOrientationState, mTaskOverlayFactory);
-
- Objects.requireNonNull(mAddDesktopButton);
- final int insertionIndex = 1 + indexOfChild(mAddDesktopButton);
- addView(desktopTaskView, insertionIndex);
-
- updateTaskSize();
- mUtils.updateChildTaskOrientations();
- updateScrollSynchronously();
-
- // Set Current Page based on the stored TaskView.
- if (currentTaskView != null) {
- setCurrentPage(indexOfChild(currentTaskView));
- }
- }
-
- @Override
- public void onDeskRemoved(int displayId, int deskId) {
- // Ignore desk changes that don't belong to this display.
- if (displayId != mContainer.getDisplay().getDisplayId()) {
- return;
- }
-
- // We need to distinguish between desk removals that are triggered from outside of overview
- // vs. the ones that were initiated from overview by dismissing the corresponding desktop
- // task view.
- var taskView = mUtils.getDesktopTaskViewForDeskId(deskId);
- if (taskView != null) {
- dismissTaskView(taskView, true, true);
- }
- }
-
- @Override
- public void onActiveDeskChanged(int displayId, int newActiveDesk, int oldActiveDesk) {
- // TODO: b/400870600 - We may need to add code here to special case when an empty desk gets
- // activated, since `RemoteDesktopLaunchTransitionRunner` doesn't always get triggered.
- }
-
/** Get the color used for foreground scrimming the RecentsView for sharing. */
public static int getForegroundScrimDimColor(Context context) {
return context.getColor(R.color.overview_foreground_scrim_color);
@@ -7119,31 +6350,11 @@ public abstract class RecentsView<
if (mDesktopRecentsTransitionController == null) {
return;
}
-
- mDesktopRecentsTransitionController.moveToDesktop(taskContainer, transitionSource,
- successCallback);
- }
-
- /**
- * Move the provided task into external display and invoke {@code successCallback} if succeeded.
- */
- public void moveTaskToExternalDisplay(TaskContainer taskContainer, Runnable successCallback) {
- if (!DesktopModeStatus.canEnterDesktopMode(mContext)) {
- return;
- }
- switchToScreenshot(() -> finishRecentsAnimation(/* toRecents= */true, /* shouldPip= */false,
- () -> moveTaskToDesktopInternal(taskContainer, successCallback)));
- }
-
- private void moveTaskToDesktopInternal(TaskContainer taskContainer, Runnable successCallback) {
- if (mDesktopRecentsTransitionController == null) {
- return;
- }
- mDesktopRecentsTransitionController.moveToExternalDisplay(taskContainer.getTask().key.id);
+ mDesktopRecentsTransitionController.moveToDesktop(taskContainer.getTask().key.id,
+ transitionSource);
successCallback.run();
}
-
// Logs when the orientation of Overview changes. We log both real and fake orientation changes.
private void logOrientationChanged() {
// Only log when Overview is showing.
@@ -7162,47 +6373,7 @@ public abstract class RecentsView<
}
}
- private int getFontWeight() {
- int fontWeightAdjustment = getResources().getConfiguration().fontWeightAdjustment;
- if (fontWeightAdjustment != Configuration.FONT_WEIGHT_ADJUSTMENT_UNDEFINED) {
- return Typeface.Builder.NORMAL_WEIGHT + fontWeightAdjustment;
- }
- return Typeface.Builder.NORMAL_WEIGHT;
- }
-
- /**
- * Creates the spring animations which run as a task settles back into its place in overview.
- *
- * When a task dismiss is cancelled, the task will return to its original position via a
- * spring animation. As it passes the threshold of its settling state, its neighbors will
- * spring in response to the perceived impact of the settling task.
- */
- public SpringAnimation createTaskDismissSettlingSpringAnimation(TaskView draggedTaskView,
- float velocity, boolean isDismissing, int dismissLength,
- Function0 onEndRunnable) {
- return mDismissUtils.createTaskDismissSettlingSpringAnimation(draggedTaskView, velocity,
- isDismissing, dismissLength, onEndRunnable);
- }
-
- /**
- * Animates RecentsView's scale to the provided value, using spring animations.
- */
- public SpringAnimation animateRecentsScale(float scale) {
- return mDismissUtils.animateRecentsScale(scale);
- }
-
public interface TaskLaunchListener {
void onTaskLaunched();
}
-
- /**
- * Sets whether the remote animation targets should draw below the recents view.
- *
- * @param drawBelowRecents whether the surface should draw below Recents.
- * @param remoteTargetHandles collection of remoteTargetHandles in Recents.
- */
- public void setDrawBelowRecents(boolean drawBelowRecents,
- RemoteTargetHandle[] remoteTargetHandles) {
- mBlurUtils.setDrawBelowRecents(drawBelowRecents, remoteTargetHandles);
- }
}
diff --git a/quickstep/src/com/android/quickstep/views/RecentsViewContainer.java b/quickstep/src/com/android/quickstep/views/RecentsViewContainer.java
index e61d402698..060c71e446 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsViewContainer.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsViewContainer.java
@@ -16,6 +16,7 @@
package com.android.quickstep.views;
+import android.app.Activity;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.LocusId;
@@ -25,11 +26,9 @@ import android.view.MotionEvent;
import android.view.View;
import android.view.Window;
-import androidx.annotation.Nullable;
-
import com.android.launcher3.BaseActivity;
import com.android.launcher3.logger.LauncherAtom;
-import com.android.launcher3.taskbar.TaskbarUIController;
+import com.android.launcher3.util.SystemUiController;
import com.android.launcher3.views.ActivityContext;
import com.android.launcher3.views.ScrimView;
@@ -42,7 +41,7 @@ public interface RecentsViewContainer extends ActivityContext {
* Returns an instance of an implementation of RecentsViewContainer
* @param context will find instance of recentsViewContainer from given context.
*/
- static T containerFromContext(Context context) {
+ static T containerFromContext(Context context) {
if (context instanceof RecentsViewContainer) {
return (T) context;
} else if (context instanceof ContextWrapper) {
@@ -52,6 +51,11 @@ public interface RecentsViewContainer extends ActivityContext {
}
}
+ /**
+ * Returns {@link SystemUiController} to manage various window flags to control system UI.
+ */
+ SystemUiController getSystemUiController();
+
/**
* Returns {@link ScrimView}
*/
@@ -62,6 +66,19 @@ public interface RecentsViewContainer extends ActivityContext {
*/
T getOverviewPanel();
+ /**
+ * Returns the RootView
+ */
+ View getRootView();
+
+ /**
+ * Dispatches a generic motion event to the view hierarchy.
+ * Returns the current RecentsViewContainer as context
+ */
+ default Context asContext() {
+ return (Context) this;
+ }
+
/**
* @see Window.Callback#dispatchGenericMotionEvent(MotionEvent)
*/
@@ -75,7 +92,7 @@ public interface RecentsViewContainer extends ActivityContext {
/**
* Returns overview actions view as a view
*/
- OverviewActionsView getActionsView();
+ View getActionsView();
/**
* @see BaseActivity#addForceInvisibleFlag(int)
@@ -122,6 +139,12 @@ public interface RecentsViewContainer extends ActivityContext {
*/
void runOnBindToTouchInteractionService(Runnable r);
+ /**
+ * @see Activity#getWindow()
+ * @return Window
+ */
+ Window getWindow();
+
/**
* @see
* BaseActivity#addMultiWindowModeChangedListener(BaseActivity.MultiWindowModeChangedListener)
@@ -150,25 +173,6 @@ public interface RecentsViewContainer extends ActivityContext {
*/
boolean isRecentsViewVisible();
- /**
- * Begins transition to start home through container
- */
- default void startHome(){
- // no op
- }
-
- /**
- * Checks container to see if we can start home transition safely
- */
- boolean canStartHomeSafely();
-
-
- /**
- * Enter staged split directly from the current running app.
- * @param leftOrTop if the staged split will be positioned left or top.
- */
- default void enterStageSplitFromRunningApp(boolean leftOrTop){}
-
/**
* Overwrites any logged item in Launcher that doesn't have a container with the
* {@link com.android.launcher3.touch.PagedOrientationHandler} in use for Overview.
@@ -194,8 +198,4 @@ public interface RecentsViewContainer extends ActivityContext {
.setOrientationHandler(orientationForLogging))
.build());
}
-
- void setTaskbarUIController(@Nullable TaskbarUIController taskbarUIController);
-
- @Nullable TaskbarUIController getTaskbarUIController();
}
diff --git a/quickstep/src/com/android/quickstep/views/SplitInstructionsView.java b/quickstep/src/com/android/quickstep/views/SplitInstructionsView.java
index fb23039c62..e86b5a0fd9 100644
--- a/quickstep/src/com/android/quickstep/views/SplitInstructionsView.java
+++ b/quickstep/src/com/android/quickstep/views/SplitInstructionsView.java
@@ -16,10 +16,12 @@
package com.android.quickstep.views;
+import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SPLIT_SELECTION_EXIT_CANCEL_BUTTON;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
@@ -38,11 +40,11 @@ import com.android.app.animation.Interpolators;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.PendingAnimation;
+import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.statemanager.BaseState;
import com.android.launcher3.statemanager.StateManager;
-import com.android.quickstep.util.AnimUtils;
+import com.android.launcher3.states.StateAnimationConfig;
import com.android.quickstep.util.SplitSelectStateController;
-import com.android.wm.shell.shared.TypefaceUtils;
-import com.android.wm.shell.shared.TypefaceUtils.FontFamily;
/**
* A rounded rectangular component containing a single TextView.
@@ -54,6 +56,7 @@ import com.android.wm.shell.shared.TypefaceUtils.FontFamily;
public class SplitInstructionsView extends LinearLayout {
private static final int BOUNCE_DURATION = 250;
private static final float BOUNCE_HEIGHT = 20;
+ private static final int DURATION_DEFAULT_SPLIT_DISMISS = 350;
private final RecentsViewContainer mContainer;
public boolean mIsCurrentlyAnimating = false;
@@ -123,35 +126,36 @@ public class SplitInstructionsView extends LinearLayout {
}
private void init() {
- TextView cancelTextView = findViewById(R.id.split_instructions_text_cancel);
+ TextView cancelTextView = findViewById(R.id.split_instructions_text);
TextView instructionTextView = findViewById(R.id.split_instructions_text);
- cancelTextView.setVisibility(VISIBLE);
- cancelTextView.setOnClickListener((v) -> exitSplitSelection());
- instructionTextView.setText(R.string.toast_contextual_split_select_app);
- TypefaceUtils.setTypeface(instructionTextView, FontFamily.GSF_BODY_MEDIUM);
+ if (FeatureFlags.enableSplitContextually()) {
+ cancelTextView.setVisibility(VISIBLE);
+ cancelTextView.setOnClickListener((v) -> exitSplitSelection());
+ instructionTextView.setText(R.string.toast_contextual_split_select_app);
- // After layout, expand touch target of cancel button to meet minimum a11y measurements.
- post(() -> {
- int minTouchSize = getResources()
- .getDimensionPixelSize(R.dimen.settingslib_preferred_minimum_touch_target);
- Rect r = new Rect();
- cancelTextView.getHitRect(r);
+ // After layout, expand touch target of cancel button to meet minimum a11y measurements.
+ post(() -> {
+ int minTouchSize = getResources()
+ .getDimensionPixelSize(R.dimen.settingslib_preferred_minimum_touch_target);
+ Rect r = new Rect();
+ cancelTextView.getHitRect(r);
- if (r.width() < minTouchSize) {
- // add 1 to ensure ceiling on int division
- int expandAmount = (minTouchSize + 1 - r.width()) / 2;
- r.left -= expandAmount;
- r.right += expandAmount;
- }
- if (r.height() < minTouchSize) {
- int expandAmount = (minTouchSize + 1 - r.height()) / 2;
- r.top -= expandAmount;
- r.bottom += expandAmount;
- }
+ if (r.width() < minTouchSize) {
+ // add 1 to ensure ceiling on int division
+ int expandAmount = (minTouchSize + 1 - r.width()) / 2;
+ r.left -= expandAmount;
+ r.right += expandAmount;
+ }
+ if (r.height() < minTouchSize) {
+ int expandAmount = (minTouchSize + 1 - r.height()) / 2;
+ r.top -= expandAmount;
+ r.bottom += expandAmount;
+ }
- setTouchDelegate(new TouchDelegate(r, cancelTextView));
- });
+ setTouchDelegate(new TouchDelegate(r, cancelTextView));
+ });
+ }
// Set accessibility title, will be announced by a11y tools.
if (Utilities.ATLEAST_P) {
@@ -162,11 +166,25 @@ public class SplitInstructionsView extends LinearLayout {
private void exitSplitSelection() {
RecentsView recentsView = mContainer.getOverviewPanel();
SplitSelectStateController splitSelectController = recentsView.getSplitSelectController();
- StateManager stateManager = recentsView.getStateManager();
- AnimUtils.goToNormalStateWithSplitDismissal(stateManager, mContainer,
- LAUNCHER_SPLIT_SELECTION_EXIT_CANCEL_BUTTON,
- splitSelectController.getSplitAnimationController());
+ StateManager stateManager = recentsView.getStateManager();
+ BaseState startState = stateManager.getState();
+ long duration = startState.getTransitionDuration(mContainer.asContext(), false);
+ if (duration == 0) {
+ // Case where we're in contextual on workspace (NORMAL), which by default has 0
+ // transition duration
+ duration = DURATION_DEFAULT_SPLIT_DISMISS;
+ }
+ StateAnimationConfig config = new StateAnimationConfig();
+ config.duration = duration;
+ AnimatorSet stateAnim = stateManager.createAtomicAnimation(
+ startState, NORMAL, config);
+ AnimatorSet dismissAnim = splitSelectController.getSplitAnimationController()
+ .createPlaceholderDismissAnim(mContainer,
+ LAUNCHER_SPLIT_SELECTION_EXIT_CANCEL_BUTTON, duration);
+ stateAnim.play(dismissAnim);
+ stateManager.setCurrentAnimation(stateAnim, NORMAL);
+ stateAnim.start();
}
void ensureProperRotation() {
diff --git a/quickstep/src/com/android/quickstep/views/TaskMenuViewWithArrow.kt b/quickstep/src/com/android/quickstep/views/TaskMenuViewWithArrow.kt
index 833683b59e..346eb2ff65 100644
--- a/quickstep/src/com/android/quickstep/views/TaskMenuViewWithArrow.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskMenuViewWithArrow.kt
@@ -30,6 +30,7 @@ import android.view.ViewGroup
import android.widget.FrameLayout
import android.widget.LinearLayout
import app.lawnchair.theme.color.tokens.ColorTokens
+import com.android.launcher3.BaseDraggingActivity
import com.android.launcher3.DeviceProfile
import com.android.launcher3.InsettableFrameLayout
import com.android.launcher3.R
@@ -38,16 +39,13 @@ import com.android.launcher3.popup.RoundedArrowDrawable
import com.android.launcher3.popup.SystemShortcut
import com.android.launcher3.util.Themes
import com.android.quickstep.TaskOverlayFactory
+import com.android.quickstep.views.TaskView.TaskContainer
class TaskMenuViewWithArrow : ArrowPopup where T : RecentsViewContainer, T : Context {
companion object {
const val TAG = "TaskMenuViewWithArrow"
- fun showForTask(
- taskContainer: TaskContainer,
- alignedOptionIndex: Int = 0,
- onClosedCallback: Runnable? = null
- ): Boolean {
+ fun showForTask(taskContainer: TaskContainer, alignedOptionIndex: Int = 0): Boolean {
val container: RecentsViewContainer =
RecentsViewContainer.containerFromContext(taskContainer.taskView.context)
val taskMenuViewWithArrow =
@@ -57,18 +55,12 @@ class TaskMenuViewWithArrow : ArrowPopup where T : RecentsViewContainer, T
false
) as TaskMenuViewWithArrow<*>
- return taskMenuViewWithArrow.populateAndShowForTask(
- taskContainer,
- alignedOptionIndex,
- onClosedCallback
- )
+ return taskMenuViewWithArrow.populateAndShowForTask(taskContainer, alignedOptionIndex)
}
}
constructor(context: Context) : super(context)
-
constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
-
constructor(
context: Context,
attrs: AttributeSet,
@@ -90,7 +82,6 @@ class TaskMenuViewWithArrow : ArrowPopup where T : RecentsViewContainer, T
private var alignedOptionIndex: Int = 0
private val extraSpaceForRowAlignment: Int
get() = optionMeasuredHeight * alignedOptionIndex
-
private val menuPaddingEnd = context.resources.getDimensionPixelSize(R.dimen.task_card_margin)
private lateinit var taskView: TaskView
@@ -100,14 +91,13 @@ class TaskMenuViewWithArrow : ArrowPopup where T : RecentsViewContainer, T
private var optionMeasuredHeight = 0
private val arrowHorizontalPadding: Int
get() =
- if (taskView.isLargeTile)
+ if (taskView.isFocusedTask)
resources.getDimensionPixelSize(R.dimen.task_menu_horizontal_padding)
else 0
private var iconView: IconView? = null
private var scrim: View? = null
private val scrimAlpha = 0.8f
- private var onClosedCallback: Runnable? = null
override fun isOfType(type: Int): Boolean = type and TYPE_TASK_MENU != 0
@@ -151,8 +141,7 @@ class TaskMenuViewWithArrow : ArrowPopup where T : RecentsViewContainer, T
private fun populateAndShowForTask(
taskContainer: TaskContainer,
- alignedOptionIndex: Int,
- onClosedCallback: Runnable?
+ alignedOptionIndex: Int
): Boolean {
if (isAttachedToWindow) {
return false
@@ -161,7 +150,6 @@ class TaskMenuViewWithArrow : ArrowPopup where T : RecentsViewContainer, T
taskView = taskContainer.taskView
this.taskContainer = taskContainer
this.alignedOptionIndex = alignedOptionIndex
- this.onClosedCallback = onClosedCallback
if (!populateMenu()) return false
addScrim()
show()
@@ -264,7 +252,6 @@ class TaskMenuViewWithArrow : ArrowPopup where T : RecentsViewContainer, T
super.closeComplete()
popupContainer.removeView(scrim)
popupContainer.removeView(iconView)
- onClosedCallback?.run()
}
/**
diff --git a/quickstep/src/com/android/quickstep/views/TaskThumbnailViewDeprecated.java b/quickstep/src/com/android/quickstep/views/TaskThumbnailViewDeprecated.java
index 74d76e6432..4283d0e4cf 100644
--- a/quickstep/src/com/android/quickstep/views/TaskThumbnailViewDeprecated.java
+++ b/quickstep/src/com/android/quickstep/views/TaskThumbnailViewDeprecated.java
@@ -28,13 +28,16 @@ import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
+import android.graphics.Insets;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
+import android.graphics.RectF;
import android.graphics.Shader;
import android.graphics.drawable.Drawable;
+import android.os.Build;
import android.util.AttributeSet;
import android.util.FloatProperty;
import android.util.Property;
@@ -42,16 +45,18 @@ import android.view.View;
import android.widget.ImageView;
import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
import androidx.core.graphics.ColorUtils;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Utilities;
+import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.launcher3.util.SystemUiController;
import com.android.launcher3.util.SystemUiController.SystemUiControllerFlags;
import com.android.launcher3.util.ViewPool;
-import com.android.quickstep.FullscreenDrawParams;
import com.android.quickstep.TaskOverlayFactory.TaskOverlay;
import com.android.quickstep.orientation.RecentsPagedOrientationHandler;
+import com.android.quickstep.views.TaskView.FullscreenDrawParams;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.recents.utilities.PreviewPositionHelper;
@@ -65,6 +70,8 @@ import java.util.Objects;
*/
@Deprecated
public class TaskThumbnailViewDeprecated extends View implements ViewPool.Reusable {
+ private static final MainThreadInitializedObject TEMP_PARAMS =
+ new MainThreadInitializedObject<>(FullscreenDrawParams::new);
public static final Property DIM_ALPHA =
new FloatProperty("dimAlpha") {
@@ -92,6 +99,36 @@ public class TaskThumbnailViewDeprecated extends View implements ViewPool.Reusab
}
};
+ /** Use to animate thumbnail translationX while first app in split selection is initiated */
+ public static final Property SPLIT_SELECT_TRANSLATE_X =
+ new FloatProperty("splitSelectTranslateX") {
+ @Override
+ public void setValue(TaskThumbnailViewDeprecated thumbnail,
+ float splitSelectTranslateX) {
+ thumbnail.applySplitSelectTranslateX(splitSelectTranslateX);
+ }
+
+ @Override
+ public Float get(TaskThumbnailViewDeprecated thumbnailView) {
+ return thumbnailView.mSplitSelectTranslateX;
+ }
+ };
+
+ /** Use to animate thumbnail translationY while first app in split selection is initiated */
+ public static final Property SPLIT_SELECT_TRANSLATE_Y =
+ new FloatProperty("splitSelectTranslateY") {
+ @Override
+ public void setValue(TaskThumbnailViewDeprecated thumbnail,
+ float splitSelectTranslateY) {
+ thumbnail.applySplitSelectTranslateY(splitSelectTranslateY);
+ }
+
+ @Override
+ public Float get(TaskThumbnailViewDeprecated thumbnailView) {
+ return thumbnailView.mSplitSelectTranslateY;
+ }
+ };
+
private final RecentsViewContainer mContainer;
private TaskOverlay> mOverlay;
private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
@@ -104,10 +141,9 @@ public class TaskThumbnailViewDeprecated extends View implements ViewPool.Reusab
// Contains the portion of the thumbnail that is clipped when fullscreen progress = 0.
private final Rect mPreviewRect = new Rect();
private final PreviewPositionHelper mPreviewPositionHelper = new PreviewPositionHelper();
- private FullscreenDrawParams mFullscreenParams;
+ private TaskView.FullscreenDrawParams mFullscreenParams;
private ImageView mSplashView;
private Drawable mSplashViewDrawable;
- private TaskView mTaskView;
@Nullable
private Task mTask;
@@ -124,6 +160,8 @@ public class TaskThumbnailViewDeprecated extends View implements ViewPool.Reusab
private boolean mOverlayEnabled;
/** Used as a placeholder when the original thumbnail animates out to. */
private boolean mShowSplashForSplitSelection;
+ private float mSplitSelectTranslateX;
+ private float mSplitSelectTranslateY;
public TaskThumbnailViewDeprecated(Context context) {
this(context, null);
@@ -142,7 +180,8 @@ public class TaskThumbnailViewDeprecated extends View implements ViewPool.Reusab
mClearPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
mContainer = RecentsViewContainer.containerFromContext(context);
// Initialize with placeholder value. It is overridden later by TaskView
- mFullscreenParams = new FullscreenDrawParams(context, __ -> 0f, __ -> 0f);
+ mFullscreenParams = TEMP_PARAMS.get(context);
+
mDimColor = RecentsView.getForegroundScrimDimColor(context);
mDimmingPaintAfterClearing.setColor(mDimColor);
}
@@ -150,11 +189,10 @@ public class TaskThumbnailViewDeprecated extends View implements ViewPool.Reusab
/**
* Updates the thumbnail to draw the provided task
*/
- public void bind(Task task, TaskOverlay> overlay, TaskView taskView) {
+ public void bind(Task task, TaskOverlay> overlay) {
mOverlay = overlay;
mOverlay.reset();
mTask = task;
- mTaskView = taskView;
int color = task == null ? Color.BLACK : task.colorBackground | 0xFF000000;
mPaint.setColor(color);
mBackgroundPaint.setColor(color);
@@ -162,6 +200,17 @@ public class TaskThumbnailViewDeprecated extends View implements ViewPool.Reusab
updateSplashView(mTask.icon);
}
+ /**
+ * Sets TaskOverlay without binding a task.
+ *
+ * @deprecated Should only be used when using new
+ * {@link com.android.quickstep.task.thumbnail.TaskThumbnailView}.
+ */
+ @Deprecated
+ public void setTaskOverlay(TaskOverlay> overlay) {
+ mOverlay = overlay;
+ }
+
/**
* Updates the thumbnail.
*
@@ -249,6 +298,40 @@ public class TaskThumbnailViewDeprecated extends View implements ViewPool.Reusab
return mDimAlpha;
}
+ /**
+ * Get the scaled insets that are being used to draw the task view. This is a subsection of
+ * the full snapshot.
+ *
+ * @return the insets in snapshot bitmap coordinates.
+ */
+ @RequiresApi(api = Build.VERSION_CODES.Q)
+ public Insets getScaledInsets() {
+ if (mThumbnailData == null) {
+ return Insets.NONE;
+ }
+
+ RectF bitmapRect = new RectF(
+ 0,
+ 0,
+ mThumbnailData.getThumbnail().getWidth(),
+ mThumbnailData.getThumbnail().getHeight());
+ RectF viewRect = new RectF(0, 0, getMeasuredWidth(), getMeasuredHeight());
+
+ // The position helper matrix tells us how to transform the bitmap to fit the view, the
+ // inverse tells us where the view would be in the bitmaps coordinates. The insets are the
+ // difference between the bitmap bounds and the projected view bounds.
+ Matrix boundsToBitmapSpace = new Matrix();
+ mPreviewPositionHelper.getMatrix().invert(boundsToBitmapSpace);
+ RectF boundsInBitmapSpace = new RectF();
+ boundsToBitmapSpace.mapRect(boundsInBitmapSpace, viewRect);
+
+ DeviceProfile dp = mContainer.getDeviceProfile();
+ int bottomInset = dp.isTablet
+ ? Math.round(bitmapRect.bottom - boundsInBitmapSpace.bottom) : 0;
+ return Insets.of(0, 0, 0, bottomInset);
+ }
+
+
@SystemUiControllerFlags
public int getSysUiStatusNavFlags() {
if (mThumbnailData != null) {
@@ -275,7 +358,7 @@ public class TaskThumbnailViewDeprecated extends View implements ViewPool.Reusab
canvas.save();
// Draw the insets if we're being drawn fullscreen (we do this for quick switch).
drawOnCanvas(canvas, 0, 0, getMeasuredWidth(), getMeasuredHeight(),
- mFullscreenParams.getCurrentCornerRadius());
+ mFullscreenParams.getCurrentDrawnCornerRadius());
canvas.restore();
}
@@ -283,15 +366,15 @@ public class TaskThumbnailViewDeprecated extends View implements ViewPool.Reusab
return mPreviewPositionHelper;
}
- public void setFullscreenParams(FullscreenDrawParams fullscreenParams) {
+ public void setFullscreenParams(TaskView.FullscreenDrawParams fullscreenParams) {
mFullscreenParams = fullscreenParams;
invalidate();
}
public void drawOnCanvas(Canvas canvas, float x, float y, float width, float height,
float cornerRadius) {
- if (mTask != null && mTaskView.isRunningTask()
- && !mTaskView.getShouldShowScreenshot()) {
+ 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);
@@ -332,6 +415,35 @@ public class TaskThumbnailViewDeprecated extends View implements ViewPool.Reusab
}
}
+ /** See {@link #SPLIT_SELECT_TRANSLATE_X} */
+ protected void applySplitSelectTranslateX(float splitSelectTranslateX) {
+ mSplitSelectTranslateX = splitSelectTranslateX;
+ applyTranslateX();
+ }
+
+ /** See {@link #SPLIT_SELECT_TRANSLATE_Y} */
+ protected void applySplitSelectTranslateY(float splitSelectTranslateY) {
+ mSplitSelectTranslateY = splitSelectTranslateY;
+ applyTranslateY();
+ }
+
+ private void applyTranslateX() {
+ setTranslationX(mSplitSelectTranslateX);
+ }
+
+ private void applyTranslateY() {
+ setTranslationY(mSplitSelectTranslateY);
+ }
+
+ protected void resetViewTransforms() {
+ mSplitSelectTranslateX = 0;
+ mSplitSelectTranslateY = 0;
+ }
+
+ public TaskView getTaskView() {
+ return (TaskView) getParent();
+ }
+
public void setOverlayEnabled(boolean overlayEnabled) {
if (mOverlayEnabled != overlayEnabled) {
mOverlayEnabled = overlayEnabled;
@@ -384,9 +496,9 @@ public class TaskThumbnailViewDeprecated extends View implements ViewPool.Reusab
float viewCenterY = viewHeight / 2f;
float centeredDrawableLeft = (viewWidth - drawableWidth) / 2f;
float centeredDrawableTop = (viewHeight - drawableHeight) / 2f;
- float nonGridScale = mTaskView == null ? 1 : 1 / mTaskView.getNonGridScale();
- float recentsMaxScale = mTaskView == null || mTaskView.getRecentsView() == null
- ? 1 : 1 / mTaskView.getRecentsView().getMaxScaleForFullScreen();
+ float nonGridScale = getTaskView() == null ? 1 : 1 / getTaskView().getNonGridScale();
+ float recentsMaxScale = getTaskView() == null || getTaskView().getRecentsView() == null
+ ? 1 : 1 / getTaskView().getRecentsView().getMaxScaleForFullScreen();
float scaleX = nonGridScale * recentsMaxScale * (1 / getScaleX());
float scaleY = nonGridScale * recentsMaxScale * (1 / getScaleY());
@@ -412,13 +524,8 @@ public class TaskThumbnailViewDeprecated extends View implements ViewPool.Reusab
thumbnailDataAspect, MAX_PCT_BEFORE_ASPECT_RATIOS_CONSIDERED_DIFFERENT);
}
- /**
- * Returns whether or not the current thumbnail is a different orientation to the task.
- *
- * Used to disable modal state when screenshot doesn't match the device orientation.
- */
- public boolean isThumbnailRotationDifferentFromTask() {
- RecentsView recents = mTaskView.getRecentsView();
+ private boolean isThumbnailRotationDifferentFromTask() {
+ RecentsView recents = getTaskView().getRecentsView();
if (recents == null || mThumbnailData == null) {
return false;
}
@@ -437,9 +544,7 @@ public class TaskThumbnailViewDeprecated extends View implements ViewPool.Reusab
*/
private void refreshOverlay() {
if (mOverlayEnabled) {
- mOverlay.initOverlay(mTask,
- mThumbnailData != null ? mThumbnailData.getThumbnail() : null,
- mPreviewPositionHelper.getMatrix(),
+ mOverlay.initOverlay(mTask, mThumbnailData, mPreviewPositionHelper.getMatrix(),
mPreviewPositionHelper.isOrientationChanged());
} else {
mOverlay.reset();
@@ -466,7 +571,7 @@ public class TaskThumbnailViewDeprecated extends View implements ViewPool.Reusab
if (mBitmapShader != null && mThumbnailData != null) {
mPreviewRect.set(0, 0, mThumbnailData.getThumbnail().getWidth(),
mThumbnailData.getThumbnail().getHeight());
- int currentRotation = mTaskView.getOrientedState().getRecentsActivityRotation();
+ int currentRotation = getTaskView().getOrientedState().getRecentsActivityRotation();
boolean isRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
mPreviewPositionHelper.updateThumbnailMatrix(mPreviewRect, mThumbnailData,
getMeasuredWidth(), getMeasuredHeight(), dp.isTablet, currentRotation, isRtl);
@@ -474,7 +579,7 @@ public class TaskThumbnailViewDeprecated extends View implements ViewPool.Reusab
mBitmapShader.setLocalMatrix(mPreviewPositionHelper.getMatrix());
mPaint.setShader(mBitmapShader);
}
- mTaskView.updateFullscreenParams();
+ getTaskView().updateCurrentFullscreenParams();
invalidate();
}
@@ -512,10 +617,6 @@ public class TaskThumbnailViewDeprecated extends View implements ViewPool.Reusab
return mThumbnailData.isRealSnapshot && !mTask.isLocked;
}
- public Matrix getThumbnailMatrix() {
- return mPreviewPositionHelper.getMatrix();
- }
-
@Override
public void onRecycle() {
// Do nothing
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.kt b/quickstep/src/com/android/quickstep/views/TaskView.kt
index 97bbe87d45..3b2be643ec 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskView.kt
@@ -22,6 +22,7 @@ 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
@@ -38,6 +39,7 @@ import android.view.View.OnClickListener
import android.view.ViewGroup
import android.view.ViewStub
import android.view.accessibility.AccessibilityNodeInfo
+import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction
import android.widget.FrameLayout
import android.widget.Toast
import androidx.annotation.IntDef
@@ -45,51 +47,51 @@ import androidx.annotation.RequiresApi
import androidx.annotation.VisibleForTesting
import androidx.core.view.updateLayoutParams
import com.android.app.animation.Interpolators
-import com.android.launcher3.AbstractFloatingView
import com.android.launcher3.Flags.enableCursorHoverStates
-import com.android.launcher3.Flags.enableDesktopExplodedView
+import com.android.launcher3.Flags.enableFocusOutline
import com.android.launcher3.Flags.enableGridOnlyOverview
-import com.android.launcher3.Flags.enableHoverOfChildElementsInTaskview
-import com.android.launcher3.Flags.enableLargeDesktopWindowingTile
import com.android.launcher3.Flags.enableOverviewIconMenu
import com.android.launcher3.Flags.enableRefactorTaskThumbnail
-import com.android.launcher3.Flags.enableSeparateExternalDisplayTasks
+import com.android.launcher3.Flags.privateSpaceRestrictAccessibilityDrag
+import com.android.launcher3.LauncherSettings
import com.android.launcher3.R
import com.android.launcher3.Utilities
import com.android.launcher3.anim.AnimatedFloat
+import com.android.launcher3.config.FeatureFlags.ENABLE_KEYBOARD_QUICK_SWITCH
import com.android.launcher3.logging.StatsLogManager.LauncherEvent
import com.android.launcher3.model.data.ItemInfo
-import com.android.launcher3.model.data.TaskViewItemInfo
+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.DisplayController
import com.android.launcher3.util.Executors
-import com.android.launcher3.util.KFloatProperty
-import com.android.launcher3.util.MultiPropertyDelegate
import com.android.launcher3.util.MultiPropertyFactory
-import com.android.launcher3.util.MultiValueAlpha
+import com.android.launcher3.util.MultiPropertyFactory.MULTI_PROPERTY_VALUE
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_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.coroutines.DispatcherProvider
import com.android.launcher3.util.rects.set
-import com.android.quickstep.FullscreenDrawParams
+import com.android.launcher3.views.ActivityContext
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.TaskOverlayFactory
+import com.android.quickstep.TaskOverlayFactory.TaskOverlay
+import com.android.quickstep.TaskUtils
import com.android.quickstep.TaskViewUtils
import com.android.quickstep.orientation.RecentsPagedOrientationHandler
-import com.android.quickstep.recents.di.RecentsDependencies
-import com.android.quickstep.recents.di.get
-import com.android.quickstep.recents.di.inject
-import com.android.quickstep.recents.domain.usecase.ThumbnailPosition
-import com.android.quickstep.recents.ui.viewmodel.TaskData
-import com.android.quickstep.recents.ui.viewmodel.TaskTileUiState
-import com.android.quickstep.recents.ui.viewmodel.TaskViewModel
+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
@@ -97,20 +99,11 @@ import com.android.quickstep.util.BorderAnimator.Companion.createSimpleBorderAni
import com.android.quickstep.util.RecentsOrientedState
import com.android.quickstep.util.TaskCornerRadius
import com.android.quickstep.util.TaskRemovedDuringLaunchListener
-import com.android.quickstep.util.isExternalDisplay
-import com.android.quickstep.util.safeDisplayId
-import com.android.quickstep.views.IconAppChipView.AppChipStatus
-import com.android.quickstep.views.OverviewActionsView.DISABLED_NO_THUMBNAIL
-import com.android.quickstep.views.OverviewActionsView.DISABLED_ROTATED
import com.android.quickstep.views.RecentsView.UNBOUND_TASK_VIEW_ID
import com.android.systemui.shared.recents.model.Task
import com.android.systemui.shared.recents.model.ThumbnailData
import com.android.systemui.shared.system.ActivityManagerWrapper
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.cancel
-import kotlinx.coroutines.flow.collectLatest
-import kotlinx.coroutines.launch
+import com.android.systemui.shared.system.QuickStepContract
/** A task in the Recents view. */
open class TaskView
@@ -121,9 +114,7 @@ constructor(
defStyleAttr: Int = 0,
defStyleRes: Int = 0,
focusBorderAnimator: BorderAnimator? = null,
- hoverBorderAnimator: BorderAnimator? = null,
- val type: TaskViewType = TaskViewType.SINGLE,
- protected val thumbnailFullscreenParams: FullscreenDrawParams = FullscreenDrawParams(context),
+ hoverBorderAnimator: BorderAnimator? = null
) : FrameLayout(context, attrs), ViewPool.Reusable {
/**
* Used in conjunction with [onTaskListVisibilityChanged], providing more granularity on which
@@ -133,38 +124,37 @@ constructor(
@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 taskIdSet: Set
- /** Returns a copy of integer array containing taskIds of all tasks in the TaskView. */
- get() = taskContainers.map { it.task.key.id }.toSet()
-
- val snapshotViews: Array
- get() = taskContainers.map { it.snapshotView }.toTypedArray()
+ val thumbnailViews: Array
+ get() = taskContainers.map { it.thumbnailViewDeprecated }.toTypedArray()
val isGridTask: Boolean
/** Returns whether the task is part of overview grid and not being focused. */
- get() = container.deviceProfile.isTablet && !isLargeTile
+ get() = container.deviceProfile.isTablet && !isFocusedTask
val isRunningTask: Boolean
get() = this === recentsView?.runningTaskView
- private val isSelectedTask: Boolean
- get() = this === recentsView?.selectedTaskView
+ val isFocusedTask: Boolean
+ get() = this === recentsView?.focusedTaskView
- open val displayId: Int
- get() = taskContainers.firstOrNull()?.task.safeDisplayId
-
- val isExternalDisplay: Boolean
- get() = displayId.isExternalDisplay
-
- val isLargeTile: Boolean
- get() =
- this == recentsView?.focusedTaskView ||
- (enableLargeDesktopWindowingTile() && type == TaskViewType.DESKTOP) ||
- (enableSeparateExternalDisplayTasks() && isExternalDisplay)
+ val taskCornerRadius: Float
+ get() = currentFullscreenParams.cornerRadius
val recentsView: RecentsView<*, *>?
get() = parent as? RecentsView<*, *>
@@ -172,25 +162,21 @@ constructor(
val pagedOrientationHandler: RecentsPagedOrientationHandler
get() = orientedState.orientationHandler
- val firstTaskContainer: TaskContainer?
- get() = taskContainers.firstOrNull()
-
- val firstTask: Task?
+ @get:Deprecated("Use [taskContainers] instead.")
+ val firstTask: Task
/** Returns the first task bound to this TaskView. */
- get() = firstTaskContainer?.task
+ get() = taskContainers[0].task
- val firstItemInfo: ItemInfo?
- get() = firstTaskContainer?.itemInfo
+ @get:Deprecated("Use [taskContainers] instead.")
+ val firstThumbnailViewDeprecated: TaskThumbnailViewDeprecated
+ /** Returns the first thumbnailView of the TaskView. */
+ get() = taskContainers[0].thumbnailViewDeprecated
- /**
- * A [TaskViewItemInfo] of this TaskView. The [firstTaskContainer] will be used to get some
- * specific information like user, title etc of the Task. However, these task specific
- * information will be skipped if the TaskView has no [taskContainers]. Note, please use
- * [TaskContainer.itemInfo] for [TaskViewItemInfo] on a specific [TaskContainer].
- */
- val itemInfo: TaskViewItemInfo
- get() = TaskViewItemInfo(this, firstTaskContainer)
+ @get:Deprecated("Use [taskContainers] instead.")
+ val firstItemInfo: ItemInfo
+ get() = taskContainers[0].itemInfo
+ private val currentFullscreenParams = FullscreenDrawParams(context)
protected val container: RecentsViewContainer =
RecentsViewContainer.containerFromContext(context)
protected val lastTouchDownPosition = PointF()
@@ -208,9 +194,12 @@ constructor(
* 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))
+ get() =
+ (getNonGridTrans(nonGridTranslationX) +
+ getGridTrans(this.gridTranslationX) +
+ getNonGridTrans(nonGridPivotTranslationX))
- val persistentTranslationY: Float
+ 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).
@@ -221,21 +210,21 @@ constructor(
get() =
pagedOrientationHandler.getPrimaryValue(
SPLIT_SELECT_TRANSLATION_X,
- SPLIT_SELECT_TRANSLATION_Y,
+ SPLIT_SELECT_TRANSLATION_Y
)
protected val secondarySplitTranslationProperty: FloatProperty
get() =
pagedOrientationHandler.getSecondaryValue(
SPLIT_SELECT_TRANSLATION_X,
- SPLIT_SELECT_TRANSLATION_Y,
+ SPLIT_SELECT_TRANSLATION_Y
)
- val primaryDismissTranslationProperty: FloatProperty
+ protected val primaryDismissTranslationProperty: FloatProperty
get() =
pagedOrientationHandler.getPrimaryValue(DISMISS_TRANSLATION_X, DISMISS_TRANSLATION_Y)
- val secondaryDismissTranslationProperty: FloatProperty
+ protected val secondaryDismissTranslationProperty: FloatProperty
get() =
pagedOrientationHandler.getSecondaryValue(DISMISS_TRANSLATION_X, DISMISS_TRANSLATION_Y)
@@ -243,61 +232,26 @@ constructor(
get() =
pagedOrientationHandler.getPrimaryValue(
TASK_OFFSET_TRANSLATION_X,
- TASK_OFFSET_TRANSLATION_Y,
+ TASK_OFFSET_TRANSLATION_Y
)
protected val secondaryTaskOffsetTranslationProperty: FloatProperty
get() =
pagedOrientationHandler.getSecondaryValue(
TASK_OFFSET_TRANSLATION_X,
- TASK_OFFSET_TRANSLATION_Y,
+ TASK_OFFSET_TRANSLATION_Y
)
protected val taskResistanceTranslationProperty: FloatProperty
get() =
pagedOrientationHandler.getSecondaryValue(
TASK_RESISTANCE_TRANSLATION_X,
- TASK_RESISTANCE_TRANSLATION_Y,
+ TASK_RESISTANCE_TRANSLATION_Y
)
private val tempCoordinates = FloatArray(2)
- private val focusBorderAnimator: BorderAnimator? =
- focusBorderAnimator
- ?: createSimpleBorderAnimator(
- TaskCornerRadius.get(context).toInt(),
- context.resources.getDimensionPixelSize(R.dimen.keyboard_quick_switch_border_width),
- this::getThumbnailBounds,
- this,
- context
- .obtainStyledAttributes(attrs, R.styleable.TaskView, defStyleAttr, defStyleRes)
- .getColor(
- R.styleable.TaskView_focusBorderColor,
- BorderAnimator.DEFAULT_BORDER_COLOR,
- ),
- )
-
- private val hoverBorderAnimator: BorderAnimator? =
- hoverBorderAnimator
- ?: if (enableCursorHoverStates())
- createSimpleBorderAnimator(
- TaskCornerRadius.get(context).toInt(),
- context.resources.getDimensionPixelSize(R.dimen.task_hover_border_width),
- this::getThumbnailBounds,
- this,
- context
- .obtainStyledAttributes(
- attrs,
- R.styleable.TaskView,
- defStyleAttr,
- defStyleRes,
- )
- .getColor(
- R.styleable.TaskView_hoverBorderColor,
- BorderAnimator.DEFAULT_BORDER_COLOR,
- ),
- )
- else null
-
+ private val focusBorderAnimator: BorderAnimator?
+ private val hoverBorderAnimator: BorderAnimator?
private val rootViewDisplayId: Int
get() = rootView.display?.displayId ?: Display.DEFAULT_DISPLAY
@@ -309,17 +263,11 @@ constructor(
var taskViewId = UNBOUND_TASK_VIEW_ID
var isEndQuickSwitchCuj = false
- var sysUiStatusNavFlags: Int = 0
- get() =
- if (enableRefactorTaskThumbnail()) field
- else firstTaskContainer?.thumbnailViewDeprecated?.sysUiStatusNavFlags ?: 0
- private set
// 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) {
- if (value == field && enableOverviewIconMenu()) return
field = Utilities.boundToRange(value, 0f, 1f)
onFullscreenProgressChanged(field)
}
@@ -344,18 +292,6 @@ constructor(
onModalnessUpdated(field)
}
- var modalPivot: PointF? = null
- set(value) {
- field = value
- updatePivots()
- }
-
- var splitSplashAlpha = 0f
- set(value) {
- field = value
- applyThumbnailSplashAlpha()
- }
-
protected var taskThumbnailSplashAlpha = 0f
set(value) {
field = value
@@ -374,12 +310,6 @@ constructor(
applyScale()
}
- var modalScale = 1f
- set(value) {
- field = value
- applyScale()
- }
-
private var dismissTranslationX = 0f
set(value) {
field = value
@@ -451,6 +381,12 @@ constructor(
applyTranslationX()
}
+ protected var nonGridPivotTranslationX = 0f
+ set(value) {
+ field = value
+ applyTranslationX()
+ }
+
// Used when in SplitScreenSelectState
private var splitSelectTranslationY = 0f
set(value) {
@@ -464,15 +400,14 @@ constructor(
applyTranslationX()
}
- private val taskViewAlpha = MultiValueAlpha(this, Alpha.entries.size)
- protected var stableAlpha by MultiPropertyDelegate(taskViewAlpha, Alpha.Stable)
- var attachAlpha by MultiPropertyDelegate(taskViewAlpha, Alpha.Attach)
- var splitAlpha by MultiPropertyDelegate(taskViewAlpha, Alpha.Split)
- private var modalAlpha by MultiPropertyDelegate(taskViewAlpha, Alpha.Modal)
+ protected var stableAlpha = 1f
+ set(value) {
+ field = value
+ alpha = stableAlpha
+ }
protected var shouldShowScreenshot = false
get() = !isRunningTask || field
- private set
/** Enable or disable showing border on hover and focus change */
@VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
@@ -488,79 +423,37 @@ constructor(
focusBorderAnimator?.setBorderVisibility(visible = field && isFocused, animated = true)
}
- /**
- * Used to cache the hover border state so we don't repeatedly call the border animator with
- * every hover event when the user hasn't crossed the threshold of the [thumbnailBounds].
- */
- private var hoverBorderVisible = false
+ private var focusTransitionProgress = 1f
set(value) {
- if (field == value) {
- return
- }
field = value
- Log.d(
- TAG,
- "${taskIds.contentToString()} - setting border animator visibility to: $field",
- )
- hoverBorderAnimator?.setBorderVisibility(visible = field, animated = true)
+ onFocusTransitionProgressUpdated(field)
}
- // Used to cache thumbnail bounds to avoid recalculating on every hover move.
- private var thumbnailBounds = Rect()
-
- // Progress variable indicating if the TaskView is in a settled state:
- // 0 = The TaskView is in a transitioning state e.g. during gesture, in quickswitch carousel,
- // becoming focus task etc.
- // 1 = The TaskView is settled and no longer transitioning
- private var settledProgress = 1f
- set(value) {
- if (value == field && enableOverviewIconMenu()) return
- field = value
- onSettledProgressUpdated(field)
- }
-
- private val settledProgressPropertyFactory =
+ private val focusTransitionPropertyFactory =
MultiPropertyFactory(
this,
- SETTLED_PROGRESS,
- SettledProgress.entries.size,
+ FOCUS_TRANSITION,
+ FOCUS_TRANSITION_INDEX_COUNT,
{ x: Float, y: Float -> x * y },
- 1f,
+ 1f
)
- private var settledProgressFullscreen by
- MultiPropertyDelegate(settledProgressPropertyFactory, SettledProgress.Fullscreen)
- private var settledProgressGesture by
- MultiPropertyDelegate(settledProgressPropertyFactory, SettledProgress.Gesture)
- private var settledProgressDismiss by
- MultiPropertyDelegate(settledProgressPropertyFactory, SettledProgress.Dismiss)
-
- private var viewModel: TaskViewModel? = null
- private val dispatcherProvider: DispatcherProvider by RecentsDependencies.inject()
- private val coroutineScope: CoroutineScope by RecentsDependencies.inject()
- private val coroutineJobs = mutableListOf()
+ private val focusTransitionFullscreen =
+ focusTransitionPropertyFactory.get(FOCUS_TRANSITION_INDEX_FULLSCREEN)
+ private val focusTransitionScaleAndDim =
+ focusTransitionPropertyFactory.get(FOCUS_TRANSITION_INDEX_SCALE_AND_DIM)
/**
- * Returns an animator of [settledProgressDismiss] that transition in with a built-in
+ * Returns an animator of [focusTransitionScaleAndDim] that transition out with a built-in
* interpolator.
*/
- fun getDismissIconFadeInAnimator(): ObjectAnimator =
- ObjectAnimator.ofFloat(this, SETTLED_PROGRESS_DISMISS, 1f).apply {
- duration = FADE_IN_ICON_DURATION
- interpolator = FADE_IN_ICON_INTERPOLATOR
- }
-
- /**
- * Returns an animator of [settledProgressDismiss] that transition out with a built-in
- * interpolator. [AnimatedFloat] is used to apply another level of interpolation, on top of
- * interpolator set to the [Animator] by the caller.
- */
- fun getDismissIconFadeOutAnimator(): ObjectAnimator =
+ fun getFocusTransitionScaleAndDimOutAnimator(): ObjectAnimator =
AnimatedFloat { v ->
- settledProgressDismiss = SETTLED_PROGRESS_FAST_OUT_INTERPOLATOR.getInterpolation(v)
+ focusTransitionScaleAndDim.value =
+ FOCUS_TRANSITION_FAST_OUT_INTERPOLATOR.getInterpolation(v)
}
.animateToValue(1f, 0f)
- private var iconFadeInOnGestureCompleteAnimator: ObjectAnimator? = null
+ private var iconAndDimAnimator: ObjectAnimator? = null
// The current background requests to load the task thumbnail and icon
private val pendingThumbnailLoadRequests = mutableListOf>()
private val pendingIconLoadRequests = mutableListOf>()
@@ -568,15 +461,56 @@ constructor(
init {
setOnClickListener { _ -> onClick() }
-
- setWillNotDraw(!enableCursorHoverStates())
+ val keyboardFocusHighlightEnabled =
+ (ENABLE_KEYBOARD_QUICK_SWITCH.get() || enableFocusOutline())
+ val cursorHoverStatesEnabled = enableCursorHoverStates()
+ setWillNotDraw(!keyboardFocusHighlightEnabled && !cursorHoverStatesEnabled)
+ val attrs =
+ context.obtainStyledAttributes(attrs, R.styleable.TaskView, defStyleAttr, defStyleRes)
+ try {
+ this.focusBorderAnimator =
+ focusBorderAnimator
+ ?: if (keyboardFocusHighlightEnabled)
+ createSimpleBorderAnimator(
+ currentFullscreenParams.cornerRadius.toInt(),
+ context.resources.getDimensionPixelSize(
+ R.dimen.keyboard_quick_switch_border_width
+ ),
+ { bounds: Rect -> getThumbnailBounds(bounds) },
+ this,
+ attrs.getColor(
+ R.styleable.TaskView_focusBorderColor,
+ BorderAnimator.DEFAULT_BORDER_COLOR
+ )
+ )
+ else null
+ this.hoverBorderAnimator =
+ hoverBorderAnimator
+ ?: if (cursorHoverStatesEnabled)
+ createSimpleBorderAnimator(
+ currentFullscreenParams.cornerRadius.toInt(),
+ context.resources.getDimensionPixelSize(
+ R.dimen.task_hover_border_width
+ ),
+ { bounds: Rect -> getThumbnailBounds(bounds) },
+ this,
+ attrs.getColor(
+ R.styleable.TaskView_hoverBorderColor,
+ BorderAnimator.DEFAULT_BORDER_COLOR
+ )
+ )
+ else null
+ } finally {
+ attrs.recycle()
+ }
}
+
@VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
public override fun onFocusChanged(
gainFocus: Boolean,
direction: Int,
- previouslyFocusedRect: Rect?,
+ previouslyFocusedRect: Rect?
) {
super.onFocusChanged(gainFocus, direction, previouslyFocusedRect)
if (borderEnabled) {
@@ -587,28 +521,20 @@ constructor(
override fun onHoverEvent(event: MotionEvent): Boolean {
if (borderEnabled) {
when (event.action) {
- MotionEvent.ACTION_HOVER_ENTER -> {
- hoverBorderVisible =
- if (enableHoverOfChildElementsInTaskview()) {
- getThumbnailBounds(thumbnailBounds)
- event.isWithinThumbnailBounds()
- } else {
- true
- }
- }
- MotionEvent.ACTION_HOVER_MOVE ->
- if (enableHoverOfChildElementsInTaskview())
- hoverBorderVisible = event.isWithinThumbnailBounds()
- MotionEvent.ACTION_HOVER_EXIT -> hoverBorderVisible = false
+ MotionEvent.ACTION_HOVER_ENTER ->
+ hoverBorderAnimator?.setBorderVisibility(visible = true, animated = true)
+ MotionEvent.ACTION_HOVER_EXIT ->
+ hoverBorderAnimator?.setBorderVisibility(visible = false, animated = true)
else -> {}
}
}
return super.onHoverEvent(event)
}
- override fun onInterceptHoverEvent(event: MotionEvent): Boolean =
- if (enableHoverOfChildElementsInTaskview()) super.onInterceptHoverEvent(event)
- else if (enableCursorHoverStates()) true else super.onInterceptHoverEvent(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
@@ -636,70 +562,37 @@ constructor(
super.draw(canvas)
}
- override fun setLayoutDirection(layoutDirection: Int) {
- super.setLayoutDirection(layoutDirection)
- if (enableOverviewIconMenu()) {
- val deviceLayoutDirection = resources.configuration.layoutDirection
- taskContainers.forEach {
- (it.iconView as IconAppChipView).layoutDirection = deviceLayoutDirection
- }
- }
- }
-
@RequiresApi(Build.VERSION_CODES.Q)
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
super.onLayout(changed, left, top, right, bottom)
- updatePivots()
+ 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
}
- if (enableHoverOfChildElementsInTaskview()) {
- getThumbnailBounds(thumbnailBounds)
- }
- }
-
- private fun updatePivots() {
- val modalPivot = modalPivot
- if (modalPivot != null) {
- pivotX = modalPivot.x
- pivotY = modalPivot.y
- } else {
- 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
- }
- }
}
override fun onRecycle() {
resetPersistentViewTransforms()
-
- viewModel = null
- attachAlpha = 1f
- splitAlpha = 1f
- splitSplashAlpha = 0f
- modalAlpha = 1f
- modalScale = 1f
- modalPivot = null
- taskThumbnailSplashAlpha = 0f
// Clear any references to the thumbnail (it will be re-read either from the cache or the
// system on next bind)
- if (!enableRefactorTaskThumbnail()) {
+ if (enableRefactorTaskThumbnail()) {
+ notifyIsRunningTaskUpdated()
+ } else {
taskContainers.forEach { it.thumbnailViewDeprecated.setThumbnail(it.task, null) }
}
setOverlayEnabled(false)
onTaskListVisibilityChanged(false)
borderEnabled = false
- hoverBorderVisible = false
taskViewId = UNBOUND_TASK_VIEW_ID
- // TODO(b/390583187): Clean the components UI State when TaskView is recycled.
taskContainers.forEach { it.destroy() }
}
@@ -709,36 +602,34 @@ constructor(
override fun onInitializeAccessibilityNodeInfo(info: AccessibilityNodeInfo) {
super.onInitializeAccessibilityNodeInfo(info)
with(info) {
- // Only make actions available if the app icon menu is visible to the user.
- // When modalness is >0, the user is in select mode and the icon menu is hidden.
- // When split selection is active, they should only be able to select the app and not
- // take any other action.
- val shouldPopulateAccessibilityMenu =
- modalness == 0f && recentsView?.isSplitSelectionActive == false
- if (shouldPopulateAccessibilityMenu) {
- taskContainers.forEach {
- TraceHelper.allowIpcs("TV.a11yInfo") {
- TaskOverlayFactory.getEnabledShortcuts(this@TaskView, it).forEach { shortcut
- ->
- addAction(shortcut.createAccessibilityAction(context))
- }
+ addAction(
+ AccessibilityAction(
+ R.id.action_close,
+ context.getText(R.string.accessibility_close)
+ )
+ )
+
+ taskContainers.forEach {
+ TraceHelper.allowIpcs("TV.a11yInfo") {
+ TaskOverlayFactory.getEnabledShortcuts(this@TaskView, it).forEach { shortcut ->
+ addAction(shortcut.createAccessibilityAction(context))
}
}
+ }
- // Add DWB accessibility action at the end of the list
- taskContainers.forEach {
- it.digitalWellBeingToast?.getDWBAccessibilityAction()?.let(::addAction)
- }
+ // Add DWB accessibility action at the end of the list
+ taskContainers.forEach {
+ it.digitalWellBeingToast?.getDWBAccessibilityAction()?.let(::addAction)
}
recentsView?.let {
collectionItemInfo =
- AccessibilityNodeInfo.CollectionItemInfo(
+ AccessibilityNodeInfo.CollectionItemInfo.obtain(
0,
1,
- it.getAccessibilityChildren().indexOf(this@TaskView),
+ it.taskViewCount - it.indexOfChild(this@TaskView) - 1,
1,
- false,
+ false
)
}
}
@@ -746,6 +637,11 @@ constructor(
override fun performAccessibilityAction(action: Int, arguments: Bundle?): Boolean {
// TODO(b/343708271): Add support for multiple tasks per action.
+ if (action == R.id.action_close) {
+ recentsView?.dismissTask(this, true /*animateTaskView*/, true /*removeTask*/)
+ return true
+ }
+
taskContainers.forEach {
if (it.digitalWellBeingToast?.handleAccessibilityAction(action) == true) {
return true
@@ -762,155 +658,13 @@ constructor(
return super.performAccessibilityAction(action, arguments)
}
- override fun onFinishInflate() {
- super.onFinishInflate()
- inflateViewStubs()
- }
-
- protected open fun inflateViewStubs() {
- findViewById(R.id.snapshot)
- ?.apply {
- layoutResource =
- if (enableRefactorTaskThumbnail()) R.layout.task_thumbnail
- else R.layout.task_thumbnail_deprecated
- }
- ?.inflate()
- findViewById(R.id.icon)
- ?.apply {
- layoutResource =
- if (enableOverviewIconMenu()) R.layout.icon_app_chip_view
- else R.layout.icon_view
- }
- ?.inflate()
- }
-
- override fun onAttachedToWindow() {
- super.onAttachedToWindow()
- if (enableRefactorTaskThumbnail()) {
- // The TaskView lifecycle is starts the ViewModel during onBind, and cleans it in
- // onRecycle. So it should be initialized at this point. TaskView Lifecycle:
- // `bind` -> `onBind` -> onAttachedToWindow() -> onDetachFromWindow -> onRecycle
- coroutineJobs +=
- coroutineScope.launch(dispatcherProvider.main) {
- viewModel!!.state.collectLatest(::updateTaskViewState)
- }
- }
- }
-
- private fun updateTaskViewState(state: TaskTileUiState) {
- sysUiStatusNavFlags = state.sysUiStatusNavFlags
-
- // Updating containers
- val mapOfTasks = state.tasks.associateBy { it.taskId }
- taskContainers.forEach { container ->
- val taskId = container.task.key.id
- val containerState = mapOfTasks[taskId]
- val shouldHaveHeader = (type == TaskViewType.DESKTOP) && enableDesktopExplodedView()
- container.setState(
- state = containerState,
- liveTile = state.isLiveTile,
- hasHeader = shouldHaveHeader,
- clickCloseListener =
- if (shouldHaveHeader) {
- {
- // Update the layout UI to remove this task from the layout grid,
- // and remove the task from ActivityManager afterwards.
- recentsView?.dismissTask(
- taskId,
- /* animate= */ true,
- /* removeTask= */ true,
- )
- }
- } else {
- null
- },
- )
- updateThumbnailValidity(container)
- val thumbnailPosition =
- updateThumbnailMatrix(
- container = container,
- width = container.thumbnailView.width,
- height = container.thumbnailView.height,
- )
- container.setOverlayEnabled(state.taskOverlayEnabled, thumbnailPosition)
- if (state.isCentralTask) {
- this.container.actionsView.let {
- it.updateDisabledFlags(
- DISABLED_ROTATED,
- thumbnailPosition?.isRotated ?: false,
- )
- it.updateDisabledFlags(
- DISABLED_NO_THUMBNAIL,
- state.tasks.any { taskData ->
- (taskData as? TaskData.Data)?.thumbnailData?.thumbnail == null
- },
- )
- }
- }
-
- if (enableOverviewIconMenu()) {
- setIconState(container, containerState)
- }
- }
- }
-
- private fun updateThumbnailValidity(container: TaskContainer) {
- container.isThumbnailValid =
- viewModel?.isThumbnailValid(
- thumbnail = container.thumbnailData,
- width = container.thumbnailView.width,
- height = container.thumbnailView.height,
- ) ?: return
- applyThumbnailSplashAlpha()
- }
-
- /**
- * Updates the thumbnail's transformation matrix and rotation state within a TaskContainer.
- *
- * This function is called to reposition the thumbnail in the following scenarios:
- * - When the TTV's size changes (onSizeChanged), and it's displaying a SnapshotSplash.
- * - When drawing a snapshot (drawSnapshot).
- *
- * @param container The TaskContainer holding the thumbnail to be updated.
- * @param width The desired width of the thumbnail's container.
- * @param height The desired height of the thumbnail's container.
- */
- private fun updateThumbnailMatrix(
- container: TaskContainer,
- width: Int,
- height: Int,
- ): ThumbnailPosition? {
- val thumbnailPosition =
- viewModel?.getThumbnailPosition(container.thumbnailData, width, height, isLayoutRtl)
- ?: return null
- container.updateThumbnailMatrix(thumbnailPosition.matrix)
- return thumbnailPosition
- }
-
- override fun onDetachedFromWindow() {
- super.onDetachedFromWindow()
- if (enableRefactorTaskThumbnail()) {
- // The jobs are being cancelled in the background thread. So we make a copy of the
- // list to prevent cleaning a new job that might be added to this list during
- // onAttach or another moment in the lifecycle.
- val coroutineJobsToCancel = coroutineJobs.toList()
- coroutineJobs.clear()
- coroutineScope.launch(dispatcherProvider.background) {
- coroutineJobsToCancel.forEach {
- it.cancel("TaskView detaching from window")
- }
- }
- }
- }
-
/** Updates this task view to the given {@param task}. */
open fun bind(
task: Task,
orientedState: RecentsOrientedState,
- taskOverlayFactory: TaskOverlayFactory,
+ taskOverlayFactory: TaskOverlayFactory
) {
cancelPendingLoadTasks()
- this.orientedState = orientedState // Needed for dependencies
taskContainers =
listOf(
createTaskContainer(
@@ -918,83 +672,69 @@ constructor(
R.id.snapshot,
R.id.icon,
R.id.show_windows,
- R.id.digital_wellbeing_toast,
STAGE_POSITION_UNDEFINED,
- taskOverlayFactory,
+ taskOverlayFactory
)
)
- onBind(orientedState)
+ setOrientationState(orientedState)
}
- protected open fun onBind(orientedState: RecentsOrientedState) {
- if (enableRefactorTaskThumbnail()) {
- val scopeId = context
- Log.d(TAG, "onBind $scopeId ${orientedState.containerInterface}")
- viewModel =
- TaskViewModel(
- taskViewType = type,
- recentsViewData = RecentsDependencies.get(scopeId),
- getTaskUseCase = RecentsDependencies.get(scopeId),
- getSysUiStatusNavFlagsUseCase = RecentsDependencies.get(scopeId),
- isThumbnailValidUseCase = RecentsDependencies.get(scopeId),
- getThumbnailPositionUseCase = RecentsDependencies.get(scopeId),
- dispatcherProvider = RecentsDependencies.get(scopeId),
- )
- .apply { bind(*taskIds) }
- }
-
- taskContainers.forEach { container ->
- container.bind()
- if (enableRefactorTaskThumbnail()) {
- container.thumbnailView.cornerRadius =
- thumbnailFullscreenParams.currentCornerRadius
- container.thumbnailView.doOnSizeChange { width, height ->
- updateThumbnailValidity(container)
- val thumbnailPosition = updateThumbnailMatrix(container, width, height)
- container.refreshOverlay(thumbnailPosition)
- }
- }
- }
- setOrientationState(orientedState)
- }
-
- private fun applyThumbnailSplashAlpha() {
- val alpha = getSplashAlphaProgress()
- taskContainers.forEach { it.updateThumbnailSplashProgress(alpha) }
- }
-
- private fun getSplashAlphaProgress(): Float =
- when {
- !enableRefactorTaskThumbnail() -> taskThumbnailSplashAlpha
- splitSplashAlpha > 0f -> splitSplashAlpha
- shouldShowSplash() -> taskThumbnailSplashAlpha
- else -> 0f
- }
-
- internal fun shouldShowSplash(): Boolean = taskContainers.any { !it.isThumbnailValid }
-
protected fun createTaskContainer(
task: Task,
@IdRes thumbnailViewId: Int,
@IdRes iconViewId: Int,
@IdRes showWindowViewId: Int,
- @IdRes digitalWellbeingBannerId: Int,
@StagePosition stagePosition: Int,
- taskOverlayFactory: TaskOverlayFactory,
+ taskOverlayFactory: TaskOverlayFactory
): TaskContainer {
- val iconView = findViewById(iconViewId) as TaskViewIcon
- return TaskContainer(
- this,
+ val thumbnailViewDeprecated: TaskThumbnailViewDeprecated = findViewById(thumbnailViewId)!!
+ val thumbnailView: TaskThumbnailView?
+ if (enableRefactorTaskThumbnail()) {
+ val indexOfSnapshotView = indexOfChild(thumbnailViewDeprecated)
+ thumbnailView =
+ TaskThumbnailView(context).apply {
+ layoutParams = thumbnailViewDeprecated.layoutParams
+ addView(this, indexOfSnapshotView)
+ }
+ thumbnailViewDeprecated.visibility = GONE
+ } else {
+ thumbnailView = null
+ }
+ val iconView = getOrInflateIconView(iconViewId)
+ return TaskContainer(
task,
- findViewById(thumbnailViewId),
+ thumbnailView,
+ thumbnailViewDeprecated,
iconView,
TransformingTouchDelegate(iconView.asView()),
stagePosition,
- findViewById(digitalWellbeingBannerId)!!,
+ DigitalWellBeingToast(container, this),
findViewById(showWindowViewId)!!,
- taskOverlayFactory,
+ taskOverlayFactory
)
- }
+ .apply {
+ if (enableRefactorTaskThumbnail()) {
+ thumbnailViewDeprecated.setTaskOverlay(overlay)
+ bindThumbnailView()
+ } else {
+ thumbnailViewDeprecated.bind(task, overlay)
+ }
+ }
+ }
+
+ protected fun getOrInflateIconView(@IdRes iconViewId: Int): TaskViewIcon {
+ val iconView = findViewById(iconViewId)!!
+ return iconView as? TaskViewIcon
+ ?: (iconView as ViewStub)
+ .apply {
+ layoutResource =
+ if (enableOverviewIconMenu()) R.layout.icon_app_chip_view
+ else R.layout.icon_view
+ }
+ .inflate() as TaskViewIcon
+ }
+
+ protected fun isTaskContainersInitialized() = this::taskContainers.isInitialized
fun containsMultipleTasks() = taskContainers.size > 1
@@ -1008,15 +748,15 @@ constructor(
fun containsTaskId(taskId: Int) = getTaskContainerById(taskId) != null
open fun setOrientationState(orientationState: RecentsOrientedState) {
- this.orientedState = orientationState
- taskContainers.forEach { it.iconView.setIconOrientation(orientationState, isGridTask) }
- setThumbnailOrientation(orientationState)
- }
+ this.orientedState = orientationState
+ taskContainers.forEach { it.iconView.setIconOrientation(orientationState, isGridTask) }
+ setThumbnailOrientation(orientationState)
+ }
protected open fun setThumbnailOrientation(orientationState: RecentsOrientedState) {
taskContainers.forEach {
it.overlay.updateOrientationState(orientationState)
- it.digitalWellBeingToast?.initialize()
+ it.digitalWellBeingToast?.initialize(it.task)
}
}
@@ -1024,7 +764,11 @@ constructor(
* Updates TaskView scaling and translation required to support variable width if enabled, while
* ensuring TaskView fits into screen in fullscreen.
*/
- open fun updateTaskSize(lastComputedTaskSize: Rect, lastComputedGridTaskSize: Rect) {
+ fun updateTaskSize(
+ lastComputedTaskSize: Rect,
+ lastComputedGridTaskSize: Rect,
+ lastComputedCarouselTaskSize: Rect
+ ) {
val thumbnailPadding = container.deviceProfile.overviewTaskThumbnailTopMarginPx
val taskWidth = lastComputedTaskSize.width()
val taskHeight = lastComputedTaskSize.height()
@@ -1035,10 +779,9 @@ constructor(
if (container.deviceProfile.isTablet) {
val boxWidth: Int
val boxHeight: Int
-
- // Focused task and Desktop tasks should use focusTaskRatio that is associated
- // with the original orientation of the focused task.
- if (isLargeTile) {
+ 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 {
@@ -1052,15 +795,22 @@ constructor(
expectedHeight = boxHeight + thumbnailPadding
// Scale to to fit task Rect.
- nonGridScale = taskWidth / boxWidth.toFloat()
+ 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 = taskWidth
- expectedHeight = taskHeight + thumbnailPadding
+ expectedWidth = if (enableOverviewIconMenu()) taskWidth else LayoutParams.MATCH_PARENT
+ expectedHeight =
+ if (enableOverviewIconMenu()) taskHeight + thumbnailPadding
+ else LayoutParams.MATCH_PARENT
}
this.nonGridScale = nonGridScale
this.boxTranslationY = boxTranslationY
@@ -1074,10 +824,9 @@ constructor(
protected open fun updateThumbnailSize() {
// TODO(b/271468547), we should default to setting translations only on the snapshot instead
// of a hybrid of both margins and translations
- firstTaskContainer?.snapshotView?.updateLayoutParams {
+ taskContainers[0].snapshotView.updateLayoutParams {
topMargin = container.deviceProfile.overviewTaskThumbnailTopMarginPx
}
- taskContainers.forEach { it.digitalWellBeingToast?.setupLayout() }
}
/** Returns the thumbnail's bounds, optionally relative to the screen. */
@@ -1089,7 +838,7 @@ constructor(
if (relativeToDragLayer) {
container.dragLayer.getDescendantRectRelativeToSelf(
it.snapshotView,
- thumbnailBounds,
+ thumbnailBounds
)
} else {
thumbnailBounds.set(it.snapshotView)
@@ -1121,8 +870,7 @@ constructor(
taskContainers.forEach {
if (visible) {
recentsModel.thumbnailCache
- .getThumbnailInBackground(it.task) { thumbnailData ->
- it.task.thumbnail = thumbnailData
+ .updateThumbnailInBackground(it.task) { thumbnailData ->
it.thumbnailViewDeprecated.setThumbnail(it.task, thumbnailData)
}
?.also { request -> pendingThumbnailLoadRequests.add(request) }
@@ -1134,24 +882,28 @@ constructor(
}
}
}
- if (needsUpdate(changes, FLAG_UPDATE_ICON) && !enableOverviewIconMenu()) {
+ if (needsUpdate(changes, FLAG_UPDATE_ICON)) {
taskContainers.forEach {
if (visible) {
recentsModel.iconCache
- .getIconInBackground(it.task) { icon, contentDescription, title ->
- it.task.icon = icon
- it.task.titleDescription = contentDescription
- it.task.title = title
- onIconLoaded(it)
+ .updateIconInBackground(it.task) { task ->
+ setIcon(it.iconView, task.icon)
+ if (enableOverviewIconMenu()) {
+ setText(it.iconView, task.title)
+ }
+ it.digitalWellBeingToast?.initialize(task)
}
?.also { request -> pendingIconLoadRequests.add(request) }
} else {
- onIconUnloaded(it)
+ setIcon(it.iconView, null)
+ if (enableOverviewIconMenu()) {
+ setText(it.iconView, null)
+ }
}
}
}
if (needsUpdate(changes, FLAG_UPDATE_CORNER_RADIUS)) {
- thumbnailFullscreenParams.updateCornerRadius(context)
+ currentFullscreenParams.updateCornerRadius(context)
}
}
@@ -1159,38 +911,10 @@ constructor(
(dataChange and flag) == flag
protected open fun cancelPendingLoadTasks() {
- pendingThumbnailLoadRequests.forEach { it.cancel() }
- pendingThumbnailLoadRequests.clear()
- pendingIconLoadRequests.forEach { it.cancel() }
- pendingIconLoadRequests.clear()
- }
-
- protected open fun setIconState(container: TaskContainer, state: TaskData?) {
- if (enableOverviewIconMenu()) {
- if (state is TaskData.Data) {
- setIcon(container.iconView, state.icon)
- container.iconView.setText(state.title)
- container.digitalWellBeingToast?.initialize()
- } else {
- setIcon(container.iconView, null)
- container.iconView.setText(null)
- }
- }
- }
-
- protected open fun onIconLoaded(taskContainer: TaskContainer) {
- setIcon(taskContainer.iconView, taskContainer.task.icon)
- if (enableOverviewIconMenu()) {
- taskContainer.iconView.setText(taskContainer.task.title)
- }
- taskContainer.digitalWellBeingToast?.initialize()
- }
-
- protected open fun onIconUnloaded(taskContainer: TaskContainer) {
- setIcon(taskContainer.iconView, null)
- if (enableOverviewIconMenu()) {
- taskContainer.iconView.setText(null)
- }
+ pendingThumbnailLoadRequests.forEach { it.cancel() }
+ pendingThumbnailLoadRequests.clear()
+ pendingIconLoadRequests.forEach { it.cancel() }
+ pendingIconLoadRequests.clear()
}
protected fun setIcon(iconView: TaskViewIcon, icon: Drawable?) {
@@ -1214,14 +938,13 @@ constructor(
}
}
- @JvmOverloads
- open fun setShouldShowScreenshot(
- shouldShowScreenshot: Boolean,
- thumbnailDatas: Map? = null,
- ) {
- if (this.shouldShowScreenshot == shouldShowScreenshot) return
- this.shouldShowScreenshot = shouldShowScreenshot
+ protected fun setText(iconView: TaskViewIcon, text: CharSequence?) {
+ iconView.setText(text)
+ }
+
+ open fun refreshThumbnails(thumbnailDatas: HashMap?) {
if (enableRefactorTaskThumbnail()) {
+ // TODO(b/342560598) add thumbnail logic
return
}
@@ -1241,7 +964,7 @@ constructor(
return
}
val callbackList =
- launchWithAnimation()?.apply {
+ launchTasks()?.apply {
add {
Log.d("b/310064698", "${taskIds.contentToString()} - onClick - launchCompleted")
}
@@ -1249,180 +972,79 @@ constructor(
Log.d("b/310064698", "${taskIds.contentToString()} - onClick - callbackList: $callbackList")
container.statsLogManager
.logger()
- .withItemInfo(itemInfo)
+ .withItemInfo(firstItemInfo)
.log(LauncherEvent.LAUNCHER_TASK_LAUNCH_TAP)
}
- /** Launch of the current task (both live and inactive tasks) with an animation. */
- fun launchWithAnimation(): RunnableList? {
- return if (isRunningTask && recentsView?.remoteTargetHandles != null) {
- launchAsLiveTile(recentsView?.remoteTargetHandles!!)
- } else {
- launchAsStaticTile()
- }
- }
-
- private fun launchAsLiveTile(remoteTargetHandles: Array): RunnableList? {
- val recentsView = recentsView ?: return null
- if (!isClickableAsLiveTile) {
- Log.e(
- TAG,
- "launchAsLiveTile - TaskView is not clickable as a live tile; returning to home: ${taskIds.contentToString()}",
- )
- return null
- }
- isClickableAsLiveTile = false
- val targets =
- if (remoteTargetHandles.isNotEmpty()) {
- 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,
- )
- }
- } else {
- null
- }
- 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 = launchAsStaticTile()
- if (runnableList == null) {
- Log.e(
- TAG,
- "launchAsLiveTile - Recents animation cancelled and cannot launch task as non-live tile; returning to home: ${taskIds.contentToString()}",
- )
- }
- isClickableAsLiveTile = true
- return runnableList
- }
- TestLogging.recordEvent(
- TestProtocol.SEQUENCE_MAIN,
- "composeRecentsLaunchAnimator",
- taskIds.contentToString(),
- )
- val runnableList = RunnableList()
- with(AnimatorSet()) {
- TaskViewUtils.composeRecentsLaunchAnimator(
- this,
- this@TaskView,
- targets.apps,
- targets.wallpapers,
- targets.nonApps,
- true, /* launcherClosing */
- recentsView.stateManager,
- recentsView,
- recentsView.depthController,
- /* transitionInfo= */ null,
- )
- addListener(
- object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animator: Animator) {
- if (taskContainers.any { it.task.key.displayId != rootViewDisplayId }) {
- launchAsStaticTile()
- }
- isClickableAsLiveTile = true
- runEndCallback()
- }
-
- override fun onAnimationCancel(animation: Animator) {
- runEndCallback()
- }
-
- private fun runEndCallback() {
- runnableList.executeAllAndDestroy()
- }
- }
- )
- start()
- }
- Log.d(TAG, "launchAsLiveTile - composeRecentsLaunchAnimator: ${taskIds.contentToString()}")
- recentsView.onTaskLaunchedInLiveTileMode()
- return runnableList
- }
-
/**
* 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 launchAsStaticTile(): RunnableList? {
- val firstTaskContainer = firstTaskContainer ?: return null
+ open fun launchTaskAnimated(): RunnableList? {
TestLogging.recordEvent(
TestProtocol.SEQUENCE_MAIN,
"startActivityFromRecentsAsync",
- taskIds.contentToString(),
+ taskIds.contentToString()
)
val opts =
container.getActivityLaunchOptions(this, null).apply {
- options.launchDisplayId = displayId
+ options.launchDisplayId = display?.displayId ?: Display.DEFAULT_DISPLAY
}
if (
ActivityManagerWrapper.getInstance()
- .startActivityFromRecents(firstTaskContainer.task.key, opts.options)
+ .startActivityFromRecents(taskContainers[0].task.key, opts.options)
) {
Log.d(
TAG,
- "launchAsStaticTile - startActivityFromRecents: ${taskIds.contentToString()}",
+ "launchTaskAnimated - startActivityFromRecents: ${taskIds.contentToString()}"
)
ActiveGestureLog.INSTANCE.trackEvent(
ActiveGestureErrorDetector.GestureEvent.EXPECTING_TASK_APPEARED
)
val recentsView = recentsView ?: return null
- if (
- recentsView.runningTaskViewId != -1 &&
- recentsView.mRecentsAnimationController != 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 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)
+ 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("launchAsStaticTile")
+ notifyTaskLaunchFailed()
return null
}
}
/** Starts the task associated with this view without any animation */
- @JvmOverloads
- open fun launchWithoutAnimation(
- isQuickSwitch: Boolean = false,
- callback: (launched: Boolean) -> Unit,
- ) {
- val firstTaskContainer = firstTaskContainer ?: return
+ 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(),
+ taskIds.contentToString()
)
+ val firstContainer = taskContainers[0]
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, firstTaskContainer.task.key.id) {
- notifyTaskLaunchFailed("launchWithoutAnimation")
+ failureListener.register(container, firstContainer.task.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
@@ -1444,7 +1066,7 @@ constructor(
0,
0,
Executors.MAIN_EXECUTOR.handler,
- { callback(true) },
+ { callback(true) }
) {
failureListener.onTransitionFinished()
}
@@ -1453,32 +1075,113 @@ constructor(
if (isQuickSwitch) {
setFreezeRecentTasksReordering()
}
- // TODO(b/331754864): Update this to use TV.shouldShowSplash
- disableStartingWindow = firstTaskContainer.shouldShowSplashView
+ // TODO(b/334826842) add splash functionality to new TTV
+ if (!enableRefactorTaskThumbnail()) {
+ disableStartingWindow =
+ firstContainer.thumbnailViewDeprecated.shouldShowSplashView()
+ }
}
Executors.UI_HELPER_EXECUTOR.execute {
if (
!ActivityManagerWrapper.getInstance()
- .startActivityFromRecents(firstTaskContainer.task.key, if (Utilities.ATLEAST_Q) opts else null)
+ .startActivityFromRecents(firstContainer.task.key, if (Utilities.ATLEAST_Q) opts else null)
) {
// 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("launchTask")
+ notifyTaskLaunchFailed()
callback(false)
}
}
- Log.d(
- TAG,
- "launchWithoutAnimation - startActivityFromRecents: ${taskIds.contentToString()}",
- )
+ Log.d(TAG, "launchTask - startActivityFromRecents: ${taskIds.contentToString()}")
}
}
- private fun notifyTaskLaunchFailed(launchMethod: String) {
- val sb =
- StringBuilder("$launchMethod - Failed to launch task: ${taskIds.contentToString()}\n")
+ /** 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()
+ with(AnimatorSet()) {
+ 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 (taskContainers.any { it.task.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() {
+ val sb = StringBuilder("Failed to launch task \n")
taskContainers.forEach {
sb.append("(task=${it.task.key.baseIntent} userId=${it.task.key.userId})\n")
}
@@ -1486,6 +1189,14 @@ constructor(
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
@@ -1501,11 +1212,11 @@ constructor(
this,
container.task,
container.iconView.drawable,
- container.snapshotView,
- container.thumbnail,
- /* intent */ null,
- /* user */ null,
- container.itemInfo,
+ container.thumbnailViewDeprecated,
+ container.thumbnailViewDeprecated.thumbnail, /* intent */
+ null, /* user */
+ null,
+ container.itemInfo
)
}
@@ -1534,38 +1245,12 @@ constructor(
return showTaskMenuWithContainer(menuContainer)
}
- private fun closeTaskMenu(): Boolean {
- val floatingView: AbstractFloatingView? =
- AbstractFloatingView.getTopOpenViewWithType(
- container,
- AbstractFloatingView.TYPE_TASK_MENU,
- )
- if (floatingView?.isOpen == true) {
- floatingView.close(true)
- return true
- } else {
- return false
- }
- }
-
private fun showTaskMenuWithContainer(menuContainer: TaskContainer): Boolean {
val recentsView = recentsView ?: return false
- if (enableHoverOfChildElementsInTaskview()) {
- // Disable hover on all TaskView's whilst menu is showing.
- recentsView.setTaskBorderEnabled(false)
- }
return if (enableOverviewIconMenu() && menuContainer.iconView is IconAppChipView) {
- if (menuContainer.iconView.status == AppChipStatus.Expanded) {
- closeTaskMenu()
- } else {
- menuContainer.iconView.revealAnim(/* isRevealing= */ true)
- TaskMenuView.showForTask(menuContainer) {
- val isAnimated = !recentsView.isSplitSelectionActive
- menuContainer.iconView.revealAnim(/* isRevealing= */ false, isAnimated)
- if (enableHoverOfChildElementsInTaskview()) {
- recentsView.setTaskBorderEnabled(true)
- }
- }
+ menuContainer.iconView.revealAnim(/* isRevealing= */ true)
+ TaskMenuView.showForTask(menuContainer) {
+ menuContainer.iconView.revealAnim(/* isRevealing= */ false)
}
} else if (container.deviceProfile.isTablet) {
val alignedOptionIndex =
@@ -1585,17 +1270,9 @@ constructor(
} else {
0
}
- TaskMenuViewWithArrow.showForTask(menuContainer, alignedOptionIndex) {
- if (enableHoverOfChildElementsInTaskview()) {
- recentsView.setTaskBorderEnabled(true)
- }
- }
+ TaskMenuViewWithArrow.showForTask(menuContainer, alignedOptionIndex)
} else {
- TaskMenuView.showForTask(menuContainer) {
- if (enableHoverOfChildElementsInTaskview()) {
- recentsView.setTaskBorderEnabled(true)
- }
- }
+ TaskMenuView.showForTask(menuContainer)
}
}
@@ -1618,7 +1295,7 @@ constructor(
private fun computeAndSetIconTouchDelegate(
view: TaskViewIcon,
tempCenterCoordinates: FloatArray,
- transformingTouchDelegate: TransformingTouchDelegate,
+ transformingTouchDelegate: TransformingTouchDelegate
) {
val viewHalfWidth = view.width / 2f
val viewHalfHeight = view.height / 2f
@@ -1629,13 +1306,13 @@ constructor(
this[0] = viewHalfWidth
this[1] = viewHalfHeight
},
- false,
+ false
)
transformingTouchDelegate.setBounds(
(tempCenterCoordinates[0] - viewHalfWidth).toInt(),
(tempCenterCoordinates[1] - viewHalfHeight).toInt(),
(tempCenterCoordinates[0] + viewHalfWidth).toInt(),
- (tempCenterCoordinates[1] + viewHalfHeight).toInt(),
+ (tempCenterCoordinates[1] + viewHalfHeight).toInt()
)
}
@@ -1645,7 +1322,7 @@ constructor(
it.showWindowsView?.let { showWindowsView ->
updateFilterCallback(
showWindowsView,
- getFilterUpdateCallback(it.task.key.packageName),
+ getFilterUpdateCallback(it.task.key.packageName)
)
}
}
@@ -1678,23 +1355,23 @@ constructor(
* 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.
*/
- private fun onSettledProgressUpdated(settledProgress: Float) {
+ private fun onFocusTransitionProgressUpdated(focusTransitionProgress: Float) {
taskContainers.forEach {
- it.iconView.setContentAlpha(settledProgress)
- it.digitalWellBeingToast?.bannerOffsetPercentage = 1f - settledProgress
+ it.iconView.setContentAlpha(focusTransitionProgress)
+ it.digitalWellBeingToast?.updateBannerOffset(1f - focusTransitionProgress)
}
}
- fun startIconFadeInOnGestureComplete() {
- iconFadeInOnGestureCompleteAnimator?.cancel()
- iconFadeInOnGestureCompleteAnimator =
- ObjectAnimator.ofFloat(this, SETTLED_PROGRESS_GESTURE, 1f).apply {
- duration = FADE_IN_ICON_DURATION
+ fun animateIconScaleAndDimIntoView() {
+ iconAndDimAnimator?.cancel()
+ iconAndDimAnimator =
+ ObjectAnimator.ofFloat(focusTransitionScaleAndDim, MULTI_PROPERTY_VALUE, 0f, 1f).apply {
+ duration = SCALE_ICON_DURATION
interpolator = Interpolators.LINEAR
addListener(
object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
- iconFadeInOnGestureCompleteAnimator = null
+ iconAndDimAnimator = null
}
}
)
@@ -1702,21 +1379,20 @@ constructor(
}
}
- fun setIconVisibleForGesture(isVisible: Boolean) {
- iconFadeInOnGestureCompleteAnimator?.cancel()
- settledProgressGesture = if (isVisible) 1f else 0f
+ fun setIconScaleAndDim(iconScale: Float) {
+ iconAndDimAnimator?.cancel()
+ focusTransitionScaleAndDim.value = iconScale
}
/** Set a color tint on the snapshot and supporting views. */
open fun setColorTint(amount: Float, tintColor: Int) {
taskContainers.forEach {
- if (enableRefactorTaskThumbnail()) {
- it.updateTintAmount(amount)
- } else {
+ if (!enableRefactorTaskThumbnail()) {
+ // TODO(b/334832108) Add scrim to new TTV
it.thumbnailViewDeprecated.dimAlpha = amount
}
it.iconView.setIconColorTint(tintColor, amount)
- it.digitalWellBeingToast?.setColorTint(tintColor, amount)
+ it.digitalWellBeingToast?.setBannerColorTint(tintColor, amount)
}
}
@@ -1730,7 +1406,7 @@ constructor(
taskContainers.forEach {
if (visibility == VISIBLE || it.task.key.id == taskId) {
it.snapshotView.visibility = visibility
- it.digitalWellBeingToast?.visibility = visibility
+ it.digitalWellBeingToast?.setBannerVisibility(visibility)
it.showWindowsView?.visibility = visibility
it.overlay.setVisibility(visibility)
}
@@ -1738,13 +1414,16 @@ constructor(
}
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()) {
- taskContainers.forEach { it.setOverlayEnabled(overlayEnabled) }
+ taskContainers.forEach { it.thumbnailViewDeprecated.setOverlayEnabled(overlayEnabled) }
}
}
protected open fun refreshTaskThumbnailSplash() {
if (!enableRefactorTaskThumbnail()) {
+ // TODO(b/334826842) add splash functionality to new TTV
taskContainers.forEach { it.thumbnailViewDeprecated.refreshSplashView() }
}
}
@@ -1752,15 +1431,27 @@ constructor(
protected fun getScrollAdjustment(gridEnabled: Boolean) =
if (gridEnabled) gridTranslationX else nonGridTranslationX
- fun getOffsetAdjustment(gridEnabled: Boolean) = getScrollAdjustment(gridEnabled)
+ protected fun getOffsetAdjustment(gridEnabled: Boolean) = getScrollAdjustment(gridEnabled)
fun getSizeAdjustment(fullscreenEnabled: Boolean) = if (fullscreenEnabled) nonGridScale else 1f
private fun applyScale() {
- val scale = persistentScale * dismissScale * Utilities.mapRange(modalness, 1f, modalScale)
+ val scale = persistentScale * dismissScale
scaleX = scale
scaleY = scale
- updateFullscreenParams()
+ if (enableRefactorTaskThumbnail()) {
+ taskViewData.scale.value = scale
+ }
+ updateSnapshotRadius()
+ }
+
+ protected open fun applyThumbnailSplashAlpha() {
+ if (!enableRefactorTaskThumbnail()) {
+ // TODO(b/334826842) add splash functionality to new TTV
+ taskContainers.forEach {
+ it.thumbnailViewDeprecated.setSplashAlpha(taskThumbnailSplashAlpha)
+ }
+ }
}
private fun applyTranslationX() {
@@ -1790,57 +1481,57 @@ constructor(
protected open fun onFullscreenProgressChanged(fullscreenProgress: Float) {
taskContainers.forEach {
- if (!enableOverviewIconMenu()) {
- it.iconView.setVisibility(if (fullscreenProgress < 1) VISIBLE else INVISIBLE)
- }
+ it.iconView.setVisibility(if (fullscreenProgress < 1) VISIBLE else INVISIBLE)
it.overlay.setFullscreenProgress(fullscreenProgress)
}
- settledProgressFullscreen =
- SETTLED_PROGRESS_FAST_OUT_INTERPOLATOR.getInterpolation(1 - fullscreenProgress)
- updateFullscreenParams()
+ focusTransitionFullscreen.value =
+ FOCUS_TRANSITION_FAST_OUT_INTERPOLATOR.getInterpolation(1 - fullscreenProgress)
+ updateSnapshotRadius()
}
- protected open fun updateFullscreenParams() {
- updateFullscreenParams(thumbnailFullscreenParams)
+ protected open fun updateSnapshotRadius() {
+ updateCurrentFullscreenParams()
taskContainers.forEach {
- if (enableRefactorTaskThumbnail()) {
- it.thumbnailView.cornerRadius = thumbnailFullscreenParams.currentCornerRadius
- } else {
- it.thumbnailViewDeprecated.setFullscreenParams(thumbnailFullscreenParams)
- }
- it.overlay.setFullscreenParams(thumbnailFullscreenParams)
+ it.thumbnailViewDeprecated.setFullscreenParams(getThumbnailFullscreenParams())
+ it.overlay.setFullscreenParams(getThumbnailFullscreenParams())
}
}
+ protected open fun updateCurrentFullscreenParams() {
+ updateFullscreenParams(currentFullscreenParams)
+ }
+
protected fun updateFullscreenParams(fullscreenParams: FullscreenDrawParams) {
recentsView?.let { fullscreenParams.setProgress(fullscreenProgress, it.scaleX, scaleX) }
}
+ protected open fun getThumbnailFullscreenParams(): FullscreenDrawParams =
+ currentFullscreenParams
+
private fun onModalnessUpdated(modalness: Float) {
- isClickable = modalness == 0f
taskContainers.forEach {
- it.iconView.setModalAlpha(1f - modalness)
- it.digitalWellBeingToast?.bannerOffsetPercentage = modalness
- }
- if (enableGridOnlyOverview()) {
- modalAlpha = if (isSelectedTask) 1f else (1f - modalness)
- applyScale()
+ it.iconView.setModalAlpha(1 - modalness)
+ it.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
+ taskContainers.forEach { it.bindThumbnailView() }
+ }
+
fun resetPersistentViewTransforms() {
nonGridTranslationX = 0f
gridTranslationX = 0f
gridTranslationY = 0f
boxTranslationY = 0f
- taskContainers.forEach {
- it.snapshotView.translationX = 0f
- it.snapshotView.translationY = 0f
- }
+ nonGridPivotTranslationX = 0f
resetViewTransforms()
}
- fun resetViewTransforms() {
+ open fun resetViewTransforms() {
// fullscreenTranslation and accumulatedTranslation should not be reset, as
// resetViewTransforms is called during QuickSwitch scrolling.
dismissTranslationX = 0f
@@ -1856,9 +1547,13 @@ constructor(
}
dismissScale = 1f
translationZ = 0f
- setIconVisibleForGesture(true)
- settledProgressDismiss = 1f
+ alpha = stableAlpha
+ setIconScaleAndDim(1f)
setColorTint(0f, 0)
+ if (!enableRefactorTaskThumbnail()) {
+ // TODO(b/335399428) add split select functionality to new TTV
+ taskContainers.forEach { it.thumbnailViewDeprecated.resetViewTransforms() }
+ }
}
private fun getGridTrans(endTranslation: Float) =
@@ -1867,93 +1562,243 @@ constructor(
private fun getNonGridTrans(endTranslation: Float) =
endTranslation - getGridTrans(endTranslation)
- private fun MotionEvent.isWithinThumbnailBounds(): Boolean {
- return thumbnailBounds.contains(x.toInt(), y.toInt())
+ /** 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 {
+ val activityContext: ActivityContext? = ActivityContext.lookupContextNoThrow(context)
+
+ // The corner radius is fixed to match when Taskbar is persistent mode
+ return if (
+ activityContext != null &&
+ activityContext.deviceProfile?.isTaskbarPresent == true &&
+ DisplayController.isTransientTaskbar(context)
+ ) {
+ context.resources
+ .getDimensionPixelSize(R.dimen.persistent_taskbar_corner_radius)
+ .toFloat()
+ } else {
+ 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() {}
}
- override fun addChildrenForAccessibility(outChildren: ArrayList) {
- (if (isLayoutRtl) taskContainers.reversed() else taskContainers).forEach {
- it.addChildForAccessibility(outChildren)
+ /** Holder for all Task dependent information. */
+ inner class TaskContainer(
+ val task: Task,
+ val thumbnailView: TaskThumbnailView?,
+ val thumbnailViewDeprecated: TaskThumbnailViewDeprecated,
+ val 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.
+ */
+ val iconTouchDelegate: TransformingTouchDelegate,
+ /** Defaults to STAGE_POSITION_UNDEFINED if in not a split screen task view */
+ @StagePosition val stagePosition: Int,
+ val digitalWellBeingToast: DigitalWellBeingToast?,
+ val showWindowsView: View?,
+ taskOverlayFactory: TaskOverlayFactory
+ ) {
+ val overlay: TaskOverlay<*> = taskOverlayFactory.createOverlay(this)
+
+ val snapshotView: View
+ get() = thumbnailView ?: thumbnailViewDeprecated
+
+ /** Builds proto for logging */
+ val itemInfo: WorkspaceItemInfo
+ get() =
+ 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
+ }
+ }
+ }
+
+ val taskView: TaskView
+ get() = this@TaskView
+
+ fun destroy() {
+ digitalWellBeingToast?.destroy()
+ thumbnailView?.let { taskView.removeView(it) }
+ }
+
+ // 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
+ fun bindThumbnailView() {
+ // TODO(b/343364498): Existing view has shouldShowScreenshot as an override as well but
+ // this should be decided inside TaskThumbnailViewModel.
+ thumbnailView?.viewModel?.bind(TaskThumbnail(task.key.id, isRunningTask))
}
}
companion object {
private const val TAG = "TaskView"
-
- private enum class Alpha {
- Stable,
- Attach,
- Split,
- Modal,
- }
-
- private enum class SettledProgress {
- Fullscreen,
- Gesture,
- Dismiss,
- }
-
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)
+ const val FOCUS_TRANSITION_INDEX_FULLSCREEN = 0
+ const val FOCUS_TRANSITION_INDEX_SCALE_AND_DIM = 1
+ const val FOCUS_TRANSITION_INDEX_COUNT = 2
+
/** The maximum amount that a task view can be scrimmed, dimmed or tinted. */
const val MAX_PAGE_SCRIM_ALPHA = 0.4f
- const val FADE_IN_ICON_DURATION: Long = 120
+ const val SCALE_ICON_DURATION: Long = 120
private const val DIM_ANIM_DURATION: Long = 700
- private const val SETTLE_TRANSITION_THRESHOLD =
- FADE_IN_ICON_DURATION.toFloat() / DIM_ANIM_DURATION
- val SETTLED_PROGRESS_FAST_OUT_INTERPOLATOR =
+ private const val FOCUS_TRANSITION_THRESHOLD =
+ SCALE_ICON_DURATION.toFloat() / DIM_ANIM_DURATION
+ val FOCUS_TRANSITION_FAST_OUT_INTERPOLATOR =
Interpolators.clampToProgress(
Interpolators.FAST_OUT_SLOW_IN,
- 1f - SETTLE_TRANSITION_THRESHOLD,
- 1f,
+ 1f - FOCUS_TRANSITION_THRESHOLD,
+ 1f
)!!
- private val FADE_IN_ICON_INTERPOLATOR = Interpolators.LINEAR
private val SYSTEM_GESTURE_EXCLUSION_RECT = listOf(Rect())
- private val SETTLED_PROGRESS: FloatProperty =
- KFloatProperty(TaskView::settledProgress)
+ private val FOCUS_TRANSITION: FloatProperty =
+ object : FloatProperty("focusTransition") {
+ override fun setValue(taskView: TaskView, v: Float) {
+ taskView.focusTransitionProgress = v
+ }
- private val SETTLED_PROGRESS_GESTURE: FloatProperty =
- KFloatProperty(TaskView::settledProgressGesture)
-
- private val SETTLED_PROGRESS_DISMISS: FloatProperty =
- KFloatProperty(TaskView::settledProgressDismiss)
+ override fun get(taskView: TaskView) = taskView.focusTransitionProgress
+ }
private val SPLIT_SELECT_TRANSLATION_X: FloatProperty =
- KFloatProperty(TaskView::splitSelectTranslationX)
+ object : FloatProperty("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 =
- KFloatProperty(TaskView::splitSelectTranslationY)
+ object : FloatProperty("splitSelectTranslationY") {
+ override fun setValue(taskView: TaskView, v: Float) {
+ taskView.splitSelectTranslationY = v
+ }
+
+ override fun get(taskView: TaskView) = taskView.splitSelectTranslationY
+ }
private val DISMISS_TRANSLATION_X: FloatProperty =
- KFloatProperty(TaskView::dismissTranslationX)
+ object : FloatProperty("dismissTranslationX") {
+ override fun setValue(taskView: TaskView, v: Float) {
+ taskView.dismissTranslationX = v
+ }
+
+ override fun get(taskView: TaskView) = taskView.dismissTranslationX
+ }
private val DISMISS_TRANSLATION_Y: FloatProperty =
- KFloatProperty(TaskView::dismissTranslationY)
+ object : FloatProperty("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 =
- KFloatProperty(TaskView::taskOffsetTranslationX)
+ object : FloatProperty("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 =
- KFloatProperty(TaskView::taskOffsetTranslationY)
+ object : FloatProperty("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 =
- KFloatProperty(TaskView::taskResistanceTranslationX)
+ object : FloatProperty("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 =
- KFloatProperty(TaskView::taskResistanceTranslationY)
+ object : FloatProperty("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 =
- KFloatProperty(TaskView::gridEndTranslationX)
+ object : FloatProperty("gridEndTranslationX") {
+ override fun setValue(taskView: TaskView, v: Float) {
+ taskView.gridEndTranslationX = v
+ }
+
+ override fun get(taskView: TaskView) = taskView.gridEndTranslationX
+ }
@JvmField
- val DISMISS_SCALE: FloatProperty = KFloatProperty(TaskView::dismissScale)
+ val DISMISS_SCALE: FloatProperty =
+ object : FloatProperty