Merge "Import translations. DO NOT MERGE ANYWHERE" into sc-v2-dev
diff --git a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
index 38e8e72..4269f2d 100644
--- a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
@@ -320,6 +320,11 @@
         mOverviewCommandHelper = binder.getOverviewCommandHelper();
     }
 
+    @Override
+    public void runOnBindToTouchInteractionService(Runnable r) {
+        mTISBindHelper.runOnBindToTouchInteractionService(r);
+    }
+
     private void initUnfoldTransitionProgressProvider() {
         final UnfoldTransitionConfig config = UnfoldTransitionFactory.createConfig(this);
         if (config.isEnabled()) {
diff --git a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
index 0123c4f..a8a085b 100644
--- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
+++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
@@ -660,10 +660,10 @@
         AnimOpenProperties prop = new AnimOpenProperties(mLauncher.getResources(), mDeviceProfile,
                 windowTargetBounds, launcherIconBounds, v, dragLayerBounds[0], dragLayerBounds[1],
                 hasSplashScreen, floatingView.isDifferentFromAppIcon());
-        int left = (int) (prop.cropCenterXStart - prop.cropWidthStart / 2);
-        int top = (int) (prop.cropCenterYStart - prop.cropHeightStart / 2);
-        int right = (int) (left + prop.cropWidthStart);
-        int bottom = (int) (top + prop.cropHeightStart);
+        int left = prop.cropCenterXStart - prop.cropWidthStart / 2;
+        int top = prop.cropCenterYStart - prop.cropHeightStart / 2;
+        int right = left + prop.cropWidthStart;
+        int bottom = top + prop.cropHeightStart;
         // Set the crop here so we can calculate the corner radius below.
         crop.set(left, top, right, bottom);
 
@@ -1329,6 +1329,8 @@
                 mDeviceProfile);
 
         // Hook up floating views to the closing window animators.
+        final int rotationChange = getRotationChange(targets);
+        Rect windowTargetBounds = getWindowTargetBounds(targets, rotationChange);
         if (floatingIconView != null) {
             anim.addAnimatorListener(floatingIconView);
             floatingIconView.setOnTargetChangeListener(anim::onTargetPositionChanged);
@@ -1339,7 +1341,8 @@
             // FolderIconView can be seen morphing into the icon shape.
             final float windowAlphaThreshold = 1f - SHAPE_PROGRESS_DURATION;
 
-            RectFSpringAnim.OnUpdateListener runner = new SpringAnimRunner(targets, targetRect) {
+            RectFSpringAnim.OnUpdateListener runner = new SpringAnimRunner(targets, targetRect,
+                    windowTargetBounds) {
                 @Override
                 public void onUpdate(RectF currentRectF, float progress) {
                     finalFloatingIconView.update(1f, 255 /* fgAlpha */, currentRectF, progress,
@@ -1356,7 +1359,8 @@
 
             final float floatingWidgetAlpha = isTransluscent ? 0 : 1;
             FloatingWidgetView finalFloatingWidget = floatingWidget;
-            RectFSpringAnim.OnUpdateListener  runner = new SpringAnimRunner(targets, targetRect) {
+            RectFSpringAnim.OnUpdateListener  runner = new SpringAnimRunner(targets, targetRect,
+                    windowTargetBounds) {
                 @Override
                 public void onUpdate(RectF currentRectF, float progress) {
                     final float fallbackBackgroundAlpha =
@@ -1767,12 +1771,17 @@
         private final float mStartRadius;
         private final float mEndRadius;
         private final SurfaceTransactionApplier mSurfaceApplier;
+        private final Rect mWindowTargetBounds = new Rect();
 
-        SpringAnimRunner(RemoteAnimationTargetCompat[] appTargets, RectF targetRect) {
+        private final Rect mTmpRect = new Rect();
+
+        SpringAnimRunner(RemoteAnimationTargetCompat[] appTargets, RectF targetRect,
+                Rect windowTargetBounds) {
             mAppTargets = appTargets;
             mStartRadius = QuickStepContract.getWindowCornerRadius(mLauncher);
             mEndRadius = Math.max(1, targetRect.width()) / 2f;
             mSurfaceApplier = new SurfaceTransactionApplier(mDragLayer);
+            mWindowTargetBounds.set(windowTargetBounds);
         }
 
         public float getCornerRadius(float progress) {
@@ -1793,13 +1802,36 @@
                 }
 
                 if (target.mode == MODE_CLOSING) {
-                    float alpha = getWindowAlpha(progress);
                     currentRectF.round(mCurrentRect);
 
+                    // Scale the target window to match the currentRectF.
+                    final float scale;
+
+                    // We need to infer the crop (we crop the window to match the currentRectF).
+                    if (mWindowTargetBounds.height() > mWindowTargetBounds.width()) {
+                        scale = Math.min(1f, currentRectF.width() / mWindowTargetBounds.width());
+
+                        int unscaledHeight = (int) (mCurrentRect.height() * (1f / scale));
+                        int croppedHeight = mWindowTargetBounds.height() - unscaledHeight;
+                        mTmpRect.set(0, 0, mWindowTargetBounds.width(),
+                                mWindowTargetBounds.height() - croppedHeight);
+                    } else {
+                        scale = Math.min(1f, currentRectF.height() / mWindowTargetBounds.height());
+
+                        int unscaledWidth = (int) (mCurrentRect.width() * (1f / scale));
+                        int croppedWidth = mWindowTargetBounds.width() - unscaledWidth;
+                        mTmpRect.set(0, 0, mWindowTargetBounds.width() - croppedWidth,
+                                mWindowTargetBounds.height());
+                    }
+
+                    // Match size and position of currentRect.
+                    mMatrix.setScale(scale, scale);
+                    mMatrix.postTranslate(mCurrentRect.left, mCurrentRect.top);
+
                     builder.withMatrix(mMatrix)
-                            .withWindowCrop(mCurrentRect)
-                            .withAlpha(alpha)
-                            .withCornerRadius(getCornerRadius(progress));
+                            .withWindowCrop(mTmpRect)
+                            .withAlpha(getWindowAlpha(progress))
+                            .withCornerRadius(getCornerRadius(progress) / scale);
                 } else if (target.mode == MODE_OPENING) {
                     mMatrix.setTranslate(mTmpPos.x, mTmpPos.y);
                     builder.withMatrix(mMatrix)
diff --git a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
index 48b0f6d..6de876c 100644
--- a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
@@ -40,6 +40,7 @@
 import android.annotation.LayoutRes;
 import android.content.pm.ActivityInfo.Config;
 import android.content.res.ColorStateList;
+import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.graphics.Region;
 import android.graphics.Region.Op;
@@ -60,7 +61,6 @@
 import com.android.launcher3.anim.AlphaUpdateListener;
 import com.android.launcher3.taskbar.TaskbarNavButtonController.TaskbarButton;
 import com.android.launcher3.util.MultiValueAlpha;
-import com.android.launcher3.util.Themes;
 import com.android.quickstep.AnimatedFloat;
 import com.android.systemui.shared.rotation.FloatingRotationButton;
 import com.android.systemui.shared.rotation.RotationButton;
@@ -198,15 +198,12 @@
                 navButtonsLayoutParams.gravity = Gravity.START;
                 mNavButtonContainer.requestLayout();
 
-                if (!isThreeButtonNav) {
-                    // Tint all the nav buttons since there's no taskbar background in SUW.
-                    for (int i = 0; i < mNavButtonContainer.getChildCount(); i++) {
-                        if (!(mNavButtonContainer.getChildAt(i) instanceof ImageView)) continue;
-                        ImageView button = (ImageView) mNavButtonContainer.getChildAt(i);
-                        button.setImageTintList(ColorStateList.valueOf(Themes.getAttrColor(
-                                button.getContext(), android.R.attr.textColorPrimary)));
-                    }
-                }
+                // TODO(b/210906568) Dark intensity is currently not propagated during setup, so set
+                //  it based on dark theme for now.
+                int mode = mContext.getResources().getConfiguration().uiMode
+                        & Configuration.UI_MODE_NIGHT_MASK;
+                boolean isDarkTheme = mode == Configuration.UI_MODE_NIGHT_YES;
+                mTaskbarNavButtonDarkIntensity.updateValue(isDarkTheme ? 0 : 1);
             }
 
             // Animate taskbar background when any of these flags are enabled
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 0e5282a..5c266bb 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -180,45 +180,51 @@
             getFlagForIndex(0, "STATE_LAUNCHER_PRESENT");
     protected static final int STATE_LAUNCHER_STARTED =
             getFlagForIndex(1, "STATE_LAUNCHER_STARTED");
-    protected static final int STATE_LAUNCHER_DRAWN = getFlagForIndex(2, "STATE_LAUNCHER_DRAWN");
+    protected static final int STATE_LAUNCHER_DRAWN =
+            getFlagForIndex(2, "STATE_LAUNCHER_DRAWN");
+    // Called when the Launcher has connected to the touch interaction service (and the taskbar
+    // ui controller is initialized)
+    protected static final int STATE_LAUNCHER_BIND_TO_SERVICE =
+            getFlagForIndex(3, "STATE_LAUNCHER_BIND_TO_SERVICE");
 
     // Internal initialization states
     private static final int STATE_APP_CONTROLLER_RECEIVED =
-            getFlagForIndex(3, "STATE_APP_CONTROLLER_RECEIVED");
+            getFlagForIndex(4, "STATE_APP_CONTROLLER_RECEIVED");
 
     // Interaction finish states
     private static final int STATE_SCALED_CONTROLLER_HOME =
-            getFlagForIndex(4, "STATE_SCALED_CONTROLLER_HOME");
+            getFlagForIndex(5, "STATE_SCALED_CONTROLLER_HOME");
     private static final int STATE_SCALED_CONTROLLER_RECENTS =
-            getFlagForIndex(5, "STATE_SCALED_CONTROLLER_RECENTS");
+            getFlagForIndex(6, "STATE_SCALED_CONTROLLER_RECENTS");
 
     protected static final int STATE_HANDLER_INVALIDATED =
-            getFlagForIndex(6, "STATE_HANDLER_INVALIDATED");
+            getFlagForIndex(7, "STATE_HANDLER_INVALIDATED");
     private static final int STATE_GESTURE_STARTED =
-            getFlagForIndex(7, "STATE_GESTURE_STARTED");
+            getFlagForIndex(8, "STATE_GESTURE_STARTED");
     private static final int STATE_GESTURE_CANCELLED =
-            getFlagForIndex(8, "STATE_GESTURE_CANCELLED");
+            getFlagForIndex(9, "STATE_GESTURE_CANCELLED");
     private static final int STATE_GESTURE_COMPLETED =
-            getFlagForIndex(9, "STATE_GESTURE_COMPLETED");
+            getFlagForIndex(10, "STATE_GESTURE_COMPLETED");
 
     private static final int STATE_CAPTURE_SCREENSHOT =
-            getFlagForIndex(10, "STATE_CAPTURE_SCREENSHOT");
+            getFlagForIndex(11, "STATE_CAPTURE_SCREENSHOT");
     protected static final int STATE_SCREENSHOT_CAPTURED =
-            getFlagForIndex(11, "STATE_SCREENSHOT_CAPTURED");
+            getFlagForIndex(12, "STATE_SCREENSHOT_CAPTURED");
     private static final int STATE_SCREENSHOT_VIEW_SHOWN =
-            getFlagForIndex(12, "STATE_SCREENSHOT_VIEW_SHOWN");
+            getFlagForIndex(13, "STATE_SCREENSHOT_VIEW_SHOWN");
 
     private static final int STATE_RESUME_LAST_TASK =
-            getFlagForIndex(13, "STATE_RESUME_LAST_TASK");
+            getFlagForIndex(14, "STATE_RESUME_LAST_TASK");
     private static final int STATE_START_NEW_TASK =
-            getFlagForIndex(14, "STATE_START_NEW_TASK");
+            getFlagForIndex(15, "STATE_START_NEW_TASK");
     private static final int STATE_CURRENT_TASK_FINISHED =
-            getFlagForIndex(15, "STATE_CURRENT_TASK_FINISHED");
+            getFlagForIndex(16, "STATE_CURRENT_TASK_FINISHED");
     private static final int STATE_FINISH_WITH_NO_END =
-            getFlagForIndex(16, "STATE_FINISH_WITH_NO_END");
+            getFlagForIndex(17, "STATE_FINISH_WITH_NO_END");
 
     private static final int LAUNCHER_UI_STATES =
-            STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_DRAWN | STATE_LAUNCHER_STARTED;
+            STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_DRAWN | STATE_LAUNCHER_STARTED |
+                    STATE_LAUNCHER_BIND_TO_SERVICE;
 
     public static final long MAX_SWIPE_DURATION = 350;
     public static final long HOME_DURATION = StaggeredWorkspaceAnim.DURATION_MS;
@@ -429,6 +435,7 @@
 
         setupRecentsViewUi();
         linkRecentsViewScroll();
+        activity.runOnBindToTouchInteractionService(this::onLauncherBindToService);
 
         mActivity.registerActivityLifecycleCallbacks(mLifecycleCallbacks);
         return true;
@@ -510,6 +517,11 @@
         mStateCallback.setState(STATE_LAUNCHER_STARTED);
     }
 
+    private void onLauncherBindToService() {
+        mStateCallback.setState(STATE_LAUNCHER_BIND_TO_SERVICE);
+        flushOnRecentsAnimationAndLauncherBound();
+    }
+
     private void onLauncherPresentAndGestureStarted() {
         // Re-setup the recents UI when gesture starts, as the state could have been changed during
         // that time by a previous window transition.
@@ -557,9 +569,14 @@
             runningTasks = new ActivityManager.RunningTaskInfo[splitTaskIds.length];
             for (int i = 0; i < splitTaskIds.length; i++) {
                 int taskId = splitTaskIds[i];
-                ActivityManager.RunningTaskInfo rti = new ActivityManager.RunningTaskInfo();
-                rti.taskId = taskId;
-                runningTasks[i] = rti;
+                // Order matters here, we want first indexed RunningTaskInfo to be leftTop task
+                for (ActivityManager.RunningTaskInfo rti : mGestureState.getRunningTasks()) {
+                    if (taskId == rti.taskId) {
+                        runningTasks[i] = rti;
+                        break;
+                    }
+
+                }
             }
         } else {
             runningTasks = new ActivityManager.RunningTaskInfo[]{mGestureState.getRunningTask()};
@@ -812,12 +829,7 @@
         }
 
         // Notify when the animation starts
-        if (!mRecentsAnimationStartCallbacks.isEmpty()) {
-            for (Runnable action : new ArrayList<>(mRecentsAnimationStartCallbacks)) {
-                action.run();
-            }
-            mRecentsAnimationStartCallbacks.clear();
-        }
+        flushOnRecentsAnimationAndLauncherBound();
 
         TaskViewUtils.setSplitAuxiliarySurfacesShown(mRecentsAnimationTargets.nonApps, false);
 
@@ -1195,7 +1207,7 @@
     @UiThread
     private void animateToProgress(float start, float end, long duration, Interpolator interpolator,
             GestureEndTarget target, PointF velocityPxPerMs) {
-        runOnRecentsAnimationStart(() -> animateToProgressInternal(start, end, duration,
+        runOnRecentsAnimationAndLauncherBound(() -> animateToProgressInternal(start, end, duration,
                 interpolator, target, velocityPxPerMs));
     }
 
@@ -1820,12 +1832,12 @@
         SurfaceTransactionApplier.create(mRecentsView, applier -> {
             runActionOnRemoteHandles(remoteTargetHandle -> remoteTargetHandle.getTransformParams()
                             .setSyncTransactionApplier(applier));
-            runOnRecentsAnimationStart(() ->
+            runOnRecentsAnimationAndLauncherBound(() ->
                     mRecentsAnimationTargets.addReleaseCheck(applier));
         });
 
         mRecentsView.addOnScrollChangedListener(mOnRecentsScrollListener);
-        runOnRecentsAnimationStart(() ->
+        runOnRecentsAnimationAndLauncherBound(() ->
                 mRecentsView.setRecentsAnimationTargets(mRecentsAnimationController,
                         mRecentsAnimationTargets));
         mRecentsViewScrollLinked = true;
@@ -1871,14 +1883,26 @@
     }
 
     /**
-     * Runs the given {@param action} if the recents animation has already started, or queues it to
-     * be run when it is next started.
+     * Runs the given {@param action} if the recents animation has already started and Launcher has
+     * been created and bound to the TouchInteractionService, or queues it to be run when it this
+     * next happens.
      */
-    protected void runOnRecentsAnimationStart(Runnable action) {
-        if (mRecentsAnimationTargets == null) {
-            mRecentsAnimationStartCallbacks.add(action);
-        } else {
-            action.run();
+    private void runOnRecentsAnimationAndLauncherBound(Runnable action) {
+        mRecentsAnimationStartCallbacks.add(action);
+        flushOnRecentsAnimationAndLauncherBound();
+    }
+
+    private void flushOnRecentsAnimationAndLauncherBound() {
+        if (mRecentsAnimationTargets == null ||
+                !mStateCallback.hasStates(STATE_LAUNCHER_BIND_TO_SERVICE)) {
+            return;
+        }
+
+        if (!mRecentsAnimationStartCallbacks.isEmpty()) {
+            for (Runnable action : new ArrayList<>(mRecentsAnimationStartCallbacks)) {
+                action.run();
+            }
+            mRecentsAnimationStartCallbacks.clear();
         }
     }
 
diff --git a/quickstep/src/com/android/quickstep/GestureState.java b/quickstep/src/com/android/quickstep/GestureState.java
index 9bc98d6..01f6e23 100644
--- a/quickstep/src/com/android/quickstep/GestureState.java
+++ b/quickstep/src/com/android/quickstep/GestureState.java
@@ -135,6 +135,7 @@
     private final int mGestureId;
 
     private ActivityManager.RunningTaskInfo mRunningTask;
+    private ActivityManager.RunningTaskInfo[] mRunningTasks;
     private GestureEndTarget mEndTarget;
     private RemoteAnimationTargetCompat mLastAppearedTaskTarget;
     private Set<Integer> mPreviouslyAppearedTaskIds = new HashSet<>();
@@ -235,6 +236,14 @@
     }
 
     /**
+     * This will array will contain the task returned by {@link #getRunningTask()}
+     * @return the running tasks for this gesture.
+     */
+    public ActivityManager.RunningTaskInfo[] getRunningTasks() {
+        return mRunningTasks;
+    }
+
+    /**
      * @return the running task id for this gesture.
      */
     public int getRunningTaskId() {
@@ -249,6 +258,15 @@
     }
 
     /**
+     * TODO(b/210903248) refactor to consolidate w/ method above
+     * Updates the running task for the gesture to be the given {@param runningTask}.
+     */
+    public void updateRunningTasks(ActivityManager.RunningTaskInfo[] runningTasks) {
+        mRunningTasks = runningTasks;
+        updateRunningTask(runningTasks[0]);
+    }
+
+    /**
      * Updates the last task that appeared during this gesture.
      */
     public void updateLastAppearedTaskTarget(RemoteAnimationTargetCompat lastAppearedTaskTarget) {
diff --git a/quickstep/src/com/android/quickstep/RecentsActivity.java b/quickstep/src/com/android/quickstep/RecentsActivity.java
index d6efc71..103f350 100644
--- a/quickstep/src/com/android/quickstep/RecentsActivity.java
+++ b/quickstep/src/com/android/quickstep/RecentsActivity.java
@@ -141,6 +141,11 @@
         mTaskbarManager.setActivity(this);
     }
 
+    @Override
+    public void runOnBindToTouchInteractionService(Runnable r) {
+        mTISBindHelper.runOnBindToTouchInteractionService(r);
+    }
+
     public void setTaskbarUIController(FallbackTaskbarUIController taskbarUIController) {
         mTaskbarUIController = taskbarUIController;
     }
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index f6f2cf9..38d6448 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -665,8 +665,8 @@
             gestureState.updatePreviouslyAppearedTaskIds(
                     previousGestureState.getPreviouslyAppearedTaskIds());
         } else {
-            gestureState.updateRunningTask(TraceHelper.allowIpcs("getRunningTask.0",
-                    () -> mAM.getRunningTask(false /* filterOnlyVisibleRecents */)));
+            gestureState.updateRunningTasks(TraceHelper.allowIpcs("getRunningTask.0",
+                    () -> mAM.getRunningTasks(false /* filterOnlyVisibleRecents */)));
         }
         return gestureState;
     }
diff --git a/quickstep/src/com/android/quickstep/util/TISBindHelper.java b/quickstep/src/com/android/quickstep/util/TISBindHelper.java
index 92c60c8..7b122c6 100644
--- a/quickstep/src/com/android/quickstep/util/TISBindHelper.java
+++ b/quickstep/src/com/android/quickstep/util/TISBindHelper.java
@@ -26,6 +26,7 @@
 import com.android.quickstep.TouchInteractionService;
 import com.android.quickstep.TouchInteractionService.TISBinder;
 
+import java.util.ArrayList;
 import java.util.function.Consumer;
 
 /**
@@ -44,9 +45,11 @@
     private final Runnable mConnectionRunnable = this::internalBindToTIS;
     private final Context mContext;
     private final Consumer<TISBinder> mConnectionCallback;
+    private final ArrayList<Runnable> mPendingConnectedCallbacks = new ArrayList<>();
 
     private short mConnectionAttempts;
     private boolean mTisServiceBound;
+    private boolean mIsConnected;
 
     public TISBindHelper(Context context, Consumer<TISBinder> connectionCallback) {
         mContext = context;
@@ -66,7 +69,13 @@
         }
 
         Log.d(TAG, "TIS service connected");
+        mIsConnected = true;
         mConnectionCallback.accept((TISBinder) iBinder);
+        // Flush the pending callbacks
+        for (Runnable r : mPendingConnectedCallbacks) {
+            r.run();
+        }
+        mPendingConnectedCallbacks.clear();
         resetServiceBindRetryState();
     }
 
@@ -79,6 +88,16 @@
         internalBindToTIS();
     }
 
+    /**
+     * Runs the given {@param r} runnable when the service is connected.
+     */
+    public void runOnBindToTouchInteractionService(Runnable r) {
+        if (mIsConnected) {
+            r.run();
+        } else {
+            mPendingConnectedCallbacks.add(r);
+        }
+    }
 
     /**
      * Binds to {@link TouchInteractionService}. If the binding fails, attempts to retry via
@@ -120,5 +139,7 @@
     public void onDestroy() {
         internalUnbindToTIS();
         resetServiceBindRetryState();
+        mIsConnected = false;
+        mPendingConnectedCallbacks.clear();
     }
 }
diff --git a/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java b/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java
index dbdcf19..e5664c6 100644
--- a/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java
+++ b/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java
@@ -19,6 +19,7 @@
 import static android.provider.Settings.ACTION_APP_USAGE_SETTINGS;
 import static android.view.Gravity.BOTTOM;
 import static android.view.Gravity.CENTER_HORIZONTAL;
+import static android.view.Gravity.START;
 
 import static com.android.launcher3.Utilities.prefixTextWithIcon;
 import static com.android.launcher3.util.Executors.THREAD_POOL_EXECUTOR;
@@ -38,26 +39,51 @@
 import android.os.Build;
 import android.os.UserHandle;
 import android.util.Log;
+import android.util.Pair;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewOutlineProvider;
 import android.widget.FrameLayout;
 import android.widget.TextView;
 
+import androidx.annotation.IntDef;
 import androidx.annotation.Nullable;
 import androidx.annotation.StringRes;
 
 import com.android.launcher3.BaseActivity;
 import com.android.launcher3.BaseDraggingActivity;
+import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.touch.PagedOrientationHandler;
+import com.android.launcher3.util.SplitConfigurationOptions.StagedSplitBounds;
 import com.android.systemui.shared.recents.model.Task;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.time.Duration;
 import java.util.Locale;
 
 @TargetApi(Build.VERSION_CODES.Q)
 public final class DigitalWellBeingToast {
+
+    private static final float THRESHOLD_LEFT_ICON_ONLY = 0.4f;
+    private static final float THRESHOLD_RIGHT_ICON_ONLY = 0.6f;
+
+    /** Will span entire width of taskView with full text */
+    private static final int SPLIT_BANNER_FULLSCREEN = 0;
+    /** Used for grid task view, only showing icon and time */
+    private static final int SPLIT_GRID_BANNER_LARGE = 1;
+    /** Used for grid task view, only showing icon */
+    private static final int SPLIT_GRID_BANNER_SMALL = 2;
+    @IntDef(value = {
+            SPLIT_BANNER_FULLSCREEN,
+            SPLIT_GRID_BANNER_LARGE,
+            SPLIT_GRID_BANNER_SMALL,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface SPLIT_BANNER_CONFIG{}
+
     static final Intent OPEN_APP_USAGE_SETTINGS_TEMPLATE = new Intent(ACTION_APP_USAGE_SETTINGS);
     static final int MINUTE_MS = 60000;
 
@@ -74,7 +100,16 @@
     private View mBanner;
     private ViewOutlineProvider mOldBannerOutlineProvider;
     private float mBannerOffsetPercentage;
-    private float mVerticalOffset = 0f;
+    /**
+     * Clips rect provided by {@link #mOldBannerOutlineProvider} when in the model state to
+     * hide this banner as the taskView scales up and down
+     */
+    private float mModalOffset = 0f;
+    @Nullable
+    private StagedSplitBounds mStagedSplitBounds;
+    private int mSplitBannerConfig = SPLIT_BANNER_FULLSCREEN;
+    private float mSplitOffsetTranslationY;
+    private float mSplitOffsetTranslationX;
 
     public DigitalWellBeingToast(BaseDraggingActivity activity, TaskView taskView) {
         mActivity = activity;
@@ -103,7 +138,7 @@
     }
 
     public String getText() {
-        return getText(mAppRemainingTimeMs);
+        return getText(mAppRemainingTimeMs, false /* forContentDesc */);
     }
 
     public boolean hasLimit() {
@@ -138,6 +173,31 @@
         });
     }
 
+    public void setSplitConfiguration(StagedSplitBounds stagedSplitBounds) {
+        mStagedSplitBounds = stagedSplitBounds;
+        if (mStagedSplitBounds == null ||
+                !mActivity.getDeviceProfile().overviewShowAsGrid ||
+                mTaskView.isFocusedTask()) {
+            mSplitBannerConfig = SPLIT_BANNER_FULLSCREEN;
+            return;
+        }
+
+        // For portrait grid only height of task changes, not width. So we keep the text the same
+        if (!mActivity.getDeviceProfile().isLandscape) {
+            mSplitBannerConfig = SPLIT_GRID_BANNER_LARGE;
+            return;
+        }
+
+        // For landscape grid, for 30% width we only show icon, otherwise show icon and time
+        if (mTask.key.id == mStagedSplitBounds.leftTopTaskId) {
+            mSplitBannerConfig = mStagedSplitBounds.leftTaskPercent < THRESHOLD_LEFT_ICON_ONLY ?
+                    SPLIT_GRID_BANNER_SMALL : SPLIT_GRID_BANNER_LARGE;
+        } else {
+            mSplitBannerConfig = mStagedSplitBounds.leftTaskPercent > THRESHOLD_RIGHT_ICON_ONLY ?
+                    SPLIT_GRID_BANNER_SMALL : SPLIT_GRID_BANNER_LARGE;
+        }
+    }
+
     private String getReadableDuration(
             Duration duration,
             FormatWidth formatWidthHourAndMinute,
@@ -181,30 +241,33 @@
                 .formatMeasures(new Measure(0, MeasureUnit.MINUTE));
     }
 
-    private String getReadableDuration(
-            Duration duration,
-            FormatWidth formatWidthHourAndMinute,
-            @StringRes int durationLessThanOneMinuteStringId) {
-        return getReadableDuration(
-                duration,
-                formatWidthHourAndMinute,
-                durationLessThanOneMinuteStringId,
-                /* forceFormatWidth= */ false);
-    }
-
-    private String getRoundedUpToMinuteReadableDuration(long remainingTime) {
+    /**
+     * Returns text to show for the banner depending on {@link #mSplitBannerConfig}
+     * If {@param forContentDesc} is {@code true}, this will always return the full
+     * string corresponding to {@link #SPLIT_BANNER_FULLSCREEN}
+     */
+    private String getText(long remainingTime, boolean forContentDesc) {
         final Duration duration = Duration.ofMillis(
                 remainingTime > MINUTE_MS ?
                         (remainingTime + MINUTE_MS - 1) / MINUTE_MS * MINUTE_MS :
                         remainingTime);
-        return getReadableDuration(
-                duration, FormatWidth.NARROW, R.string.shorter_duration_less_than_one_minute);
-    }
+        String readableDuration = getReadableDuration(duration,
+                FormatWidth.NARROW,
+                R.string.shorter_duration_less_than_one_minute,
+                false /* forceFormatWidth */);
+        if (forContentDesc || mSplitBannerConfig == SPLIT_BANNER_FULLSCREEN) {
+            return mActivity.getString(
+                    R.string.time_left_for_app,
+                    readableDuration);
+        }
 
-    private String getText(long remainingTime) {
-        return mActivity.getString(
-                R.string.time_left_for_app,
-                getRoundedUpToMinuteReadableDuration(remainingTime));
+        if (mSplitBannerConfig == SPLIT_GRID_BANNER_SMALL) {
+            // show no text
+            return "";
+        } else { // SPLIT_GRID_BANNER_LARGE
+            // only show time
+            return readableDuration;
+        }
     }
 
     public void openAppUsageSettings(View view) {
@@ -232,7 +295,7 @@
                 mActivity.getString(
                         R.string.task_contents_description_with_remaining_time,
                         task.titleDescription,
-                        getText(appRemainingTimeMs)) :
+                        getText(appRemainingTimeMs, true /* forContentDesc */)) :
                 task.titleDescription;
     }
 
@@ -261,10 +324,18 @@
     private void setupAndAddBanner() {
         FrameLayout.LayoutParams layoutParams =
                 (FrameLayout.LayoutParams) mBanner.getLayoutParams();
-        layoutParams.gravity = BOTTOM | CENTER_HORIZONTAL;
+        DeviceProfile deviceProfile = mActivity.getDeviceProfile();
         layoutParams.bottomMargin = ((ViewGroup.MarginLayoutParams)
                 mTaskView.getThumbnail().getLayoutParams()).bottomMargin;
-        mBanner.setTranslationY(mBannerOffsetPercentage * mBanner.getHeight());
+        PagedOrientationHandler orientationHandler = mTaskView.getPagedOrientationHandler();
+        Pair<Float, Float> translations = orientationHandler
+                .setDwbLayoutParamsAndGetTranslations(mTaskView.getMeasuredWidth(),
+                        mTaskView.getMeasuredHeight(), mStagedSplitBounds, deviceProfile,
+                        mTaskView.getThumbnails(), mTask.key.id, mBanner);
+        mSplitOffsetTranslationX = translations.first;
+        mSplitOffsetTranslationY = translations.second;
+        updateTranslationY();
+        updateTranslationX();
         mTaskView.addView(mBanner);
     }
 
@@ -274,7 +345,9 @@
             @Override
             public void getOutline(View view, Outline outline) {
                 mOldBannerOutlineProvider.getOutline(view, outline);
-                outline.offset(0, Math.round(-view.getTranslationY() + mVerticalOffset));
+                float verticalTranslation = -view.getTranslationY() + mModalOffset
+                        + mSplitOffsetTranslationY;
+                outline.offset(0, Math.round(verticalTranslation));
             }
         });
         mBanner.setClipToOutline(true);
@@ -282,13 +355,33 @@
 
     void updateBannerOffset(float offsetPercentage, float verticalOffset) {
         if (mBanner != null && mBannerOffsetPercentage != offsetPercentage) {
-            mVerticalOffset = verticalOffset;
+            mModalOffset = verticalOffset;
             mBannerOffsetPercentage = offsetPercentage;
-            mBanner.setTranslationY(offsetPercentage * mBanner.getHeight() + mVerticalOffset);
+            updateTranslationY();
             mBanner.invalidateOutline();
         }
     }
 
+    private void updateTranslationY() {
+        if (mBanner == null) {
+            return;
+        }
+
+        mBanner.setTranslationY(
+                (mBannerOffsetPercentage * mBanner.getHeight()) +
+                        mModalOffset +
+                        mSplitOffsetTranslationY
+        );
+    }
+
+    private void updateTranslationX() {
+        if (mBanner == null) {
+            return;
+        }
+
+        mBanner.setTranslationX(mSplitOffsetTranslationX);
+    }
+
     void setBannerColorTint(int color, float amount) {
         if (mBanner == null) {
             return;
diff --git a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
index b215ef1..7e4f9d0 100644
--- a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
+++ b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
@@ -50,17 +50,20 @@
     private final float[] mIcon2CenterCoords = new float[2];
     private TransformingTouchDelegate mIcon2TouchDelegate;
     @Nullable private StagedSplitBounds mSplitBoundsConfig;
+    private final DigitalWellBeingToast mDigitalWellBeingToast2;
+
 
     public GroupedTaskView(Context context) {
-        super(context);
+        this(context, null);
     }
 
     public GroupedTaskView(Context context, AttributeSet attrs) {
-        super(context, attrs);
+        this(context, attrs, 0);
     }
 
     public GroupedTaskView(Context context, AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
+        mDigitalWellBeingToast2 = new DigitalWellBeingToast(mActivity, this);
     }
 
     @Override
@@ -102,7 +105,9 @@
                 mIconLoadRequest2 = iconCache.updateIconInBackground(mSecondaryTask,
                         (task) -> {
                             setIcon(mIconView2, task.icon);
-                            // TODO(199936292) Digital Wellbeing for individual tasks?
+                            mDigitalWellBeingToast2.initialize(mSecondaryTask);
+                            mDigitalWellBeingToast2.setSplitConfiguration(mSplitBoundsConfig);
+                            mDigitalWellBeingToast.setSplitConfiguration(mSplitBoundsConfig);
                         });
             }
         } else {
@@ -262,6 +267,19 @@
     @Override
     protected void setIconAndDimTransitionProgress(float progress, boolean invert) {
         super.setIconAndDimTransitionProgress(progress, invert);
-        mIconView2.setAlpha(mIconView.getAlpha());
+        // Value set by super call
+        float scale = mIconView.getAlpha();
+        mIconView2.setAlpha(scale);
+        mDigitalWellBeingToast2.updateBannerOffset(1f - scale,
+                mCurrentFullscreenParams.mCurrentDrawnInsets.top
+                        + mCurrentFullscreenParams.mCurrentDrawnInsets.bottom);
+    }
+
+    @Override
+    public void setColorTint(float amount, int tintColor) {
+        super.setColorTint(amount, tintColor);
+        mIconView2.setIconColorTint(tintColor, amount);
+        mSnapshotView2.setDimAlpha(amount);
+        mDigitalWellBeingToast2.setBannerColorTint(tintColor, amount);
     }
 }
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index e33d650..a71e558 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -366,7 +366,7 @@
     protected Task mTask;
     protected TaskThumbnailView mSnapshotView;
     protected IconView mIconView;
-    private final DigitalWellBeingToast mDigitalWellBeingToast;
+    protected final DigitalWellBeingToast mDigitalWellBeingToast;
     private float mFullscreenProgress;
     private float mGridProgress;
     private float mNonGridScale = 1;
diff --git a/src/com/android/launcher3/statemanager/StatefulActivity.java b/src/com/android/launcher3/statemanager/StatefulActivity.java
index 7a23caa..e036943 100644
--- a/src/com/android/launcher3/statemanager/StatefulActivity.java
+++ b/src/com/android/launcher3/statemanager/StatefulActivity.java
@@ -185,4 +185,11 @@
     @RequiresApi(api = Build.VERSION_CODES.R)
     public void updateWindowInsets(WindowInsets.Builder updatedInsetsBuilder,
             WindowInsets oldInsets) { }
+
+    /**
+     * Runs the given {@param r} runnable when this activity binds to the touch interaction service.
+     */
+    public void runOnBindToTouchInteractionService(Runnable r) {
+        r.run();
+    }
 }
diff --git a/src/com/android/launcher3/touch/LandscapePagedViewHandler.java b/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
index 9a2d6d8..a94ad7c 100644
--- a/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
+++ b/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
@@ -309,6 +309,46 @@
         return new PointF(margin, 0);
     }
 
+    @Override
+    public Pair<Float, Float> setDwbLayoutParamsAndGetTranslations(int taskViewWidth,
+            int taskViewHeight, StagedSplitBounds splitBounds, DeviceProfile deviceProfile,
+            View[] thumbnailViews, int desiredTaskId, View banner) {
+        float translationX = 0;
+        float translationY = 0;
+        FrameLayout.LayoutParams bannerParams = (FrameLayout.LayoutParams) banner.getLayoutParams();
+        banner.setPivotX(0);
+        banner.setPivotY(0);
+        banner.setRotation(getDegreesRotated());
+        translationX = banner.getHeight();
+        FrameLayout.LayoutParams snapshotParams =
+                (FrameLayout.LayoutParams) thumbnailViews[0]
+                        .getLayoutParams();
+        bannerParams.gravity = TOP | START;
+        if (splitBounds == null) {
+            // Single, fullscreen case
+            bannerParams.width = taskViewHeight - snapshotParams.topMargin;
+            return new Pair<>(translationX, Integer.valueOf(snapshotParams.topMargin).floatValue());
+        }
+
+        // Set correct width
+        if (desiredTaskId == splitBounds.leftTopTaskId) {
+            bannerParams.width = thumbnailViews[0].getMeasuredHeight();
+        } else {
+            bannerParams.width = thumbnailViews[1].getMeasuredHeight();
+        }
+
+        // Set translations
+        if (desiredTaskId == splitBounds.rightBottomTaskId) {
+            translationY = (snapshotParams.topMargin + taskViewHeight)
+                    * (splitBounds.leftTaskPercent) +
+                    (taskViewHeight * splitBounds.dividerWidthPercent);
+        }
+        if (desiredTaskId == splitBounds.leftTopTaskId) {
+            translationY = snapshotParams.topMargin;
+        }
+        return new Pair<>(translationX, translationY);
+    }
+
     /* ---------- The following are only used by TaskViewTouchHandler. ---------- */
 
     @Override
diff --git a/src/com/android/launcher3/touch/PagedOrientationHandler.java b/src/com/android/launcher3/touch/PagedOrientationHandler.java
index 95168fb..19c4639 100644
--- a/src/com/android/launcher3/touch/PagedOrientationHandler.java
+++ b/src/com/android/launcher3/touch/PagedOrientationHandler.java
@@ -191,6 +191,10 @@
      */
     PointF getAdditionalInsetForTaskMenu(float margin);
 
+    Pair<Float, Float> setDwbLayoutParamsAndGetTranslations(int taskViewWidth,
+            int taskViewHeight, StagedSplitBounds splitBounds, DeviceProfile deviceProfile,
+            View[] thumbnailViews, int desiredTaskId, View banner);
+
     // The following are only used by TaskViewTouchHandler.
     /** @return Either VERTICAL or HORIZONTAL. */
     SingleAxisSwipeDetector.Direction getUpDownSwipeDirection();
diff --git a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
index 7d70f77..ad9f95c 100644
--- a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
+++ b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
@@ -16,9 +16,11 @@
 
 package com.android.launcher3.touch;
 
+import static android.view.Gravity.BOTTOM;
 import static android.view.Gravity.CENTER_HORIZONTAL;
 import static android.view.Gravity.START;
 import static android.view.Gravity.TOP;
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
 
 import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
@@ -321,6 +323,50 @@
         return new PointF(0, 0);
     }
 
+    @Override
+    public Pair<Float, Float> setDwbLayoutParamsAndGetTranslations(int taskViewWidth,
+            int taskViewHeight, StagedSplitBounds splitBounds, DeviceProfile deviceProfile,
+            View[] thumbnailViews, int desiredTaskId, View banner) {
+        float translationX = 0;
+        float translationY = 0;
+        FrameLayout.LayoutParams bannerParams = (FrameLayout.LayoutParams) banner.getLayoutParams();
+        banner.setPivotX(0);
+        banner.setPivotY(0);
+        banner.setRotation(getDegreesRotated());
+        if (splitBounds == null) {
+            // Single, fullscreen case
+            bannerParams.width = MATCH_PARENT;
+            bannerParams.gravity = BOTTOM | CENTER_HORIZONTAL;
+            return new Pair<>(translationX, translationY);
+        }
+
+        bannerParams.gravity = BOTTOM | ((deviceProfile.isLandscape) ? START : CENTER_HORIZONTAL);
+
+        // Set correct width
+        if (desiredTaskId == splitBounds.leftTopTaskId) {
+            bannerParams.width = thumbnailViews[0].getMeasuredWidth();
+        } else {
+            bannerParams.width = thumbnailViews[1].getMeasuredWidth();
+        }
+
+        // Set translations
+        if (deviceProfile.isLandscape) {
+            if (desiredTaskId == splitBounds.rightBottomTaskId) {
+                translationX = ((taskViewWidth * splitBounds.leftTaskPercent)
+                                + (taskViewWidth * splitBounds.dividerWidthPercent));
+            }
+        } else {
+            if (desiredTaskId == splitBounds.leftTopTaskId) {
+                FrameLayout.LayoutParams snapshotParams =
+                        (FrameLayout.LayoutParams) thumbnailViews[0]
+                                .getLayoutParams();
+                translationY = -((taskViewHeight - snapshotParams.topMargin)
+                        * (1f - splitBounds.topTaskPercent));
+            }
+        }
+        return new Pair<>(translationX, translationY);
+    }
+
     /* ---------- The following are only used by TaskViewTouchHandler. ---------- */
 
     @Override
diff --git a/src/com/android/launcher3/touch/SeascapePagedViewHandler.java b/src/com/android/launcher3/touch/SeascapePagedViewHandler.java
index a921f94..de5f99c 100644
--- a/src/com/android/launcher3/touch/SeascapePagedViewHandler.java
+++ b/src/com/android/launcher3/touch/SeascapePagedViewHandler.java
@@ -16,6 +16,7 @@
 
 package com.android.launcher3.touch;
 
+import static android.view.Gravity.BOTTOM;
 import static android.view.Gravity.CENTER_VERTICAL;
 import static android.view.Gravity.END;
 import static android.view.Gravity.START;
@@ -29,6 +30,7 @@
 import android.content.res.Resources;
 import android.graphics.PointF;
 import android.graphics.Rect;
+import android.util.Pair;
 import android.view.Surface;
 import android.view.View;
 import android.widget.FrameLayout;
@@ -106,6 +108,47 @@
     }
 
     @Override
+    public Pair<Float, Float> setDwbLayoutParamsAndGetTranslations(int taskViewWidth,
+            int taskViewHeight, StagedSplitBounds splitBounds, DeviceProfile deviceProfile,
+            View[] thumbnailViews, int desiredTaskId, View banner) {
+        float translationX = 0;
+        float translationY = 0;
+        FrameLayout.LayoutParams bannerParams = (FrameLayout.LayoutParams) banner.getLayoutParams();
+        banner.setPivotX(0);
+        banner.setPivotY(0);
+        banner.setRotation(getDegreesRotated());
+        FrameLayout.LayoutParams snapshotParams =
+                (FrameLayout.LayoutParams) thumbnailViews[0]
+                        .getLayoutParams();
+        bannerParams.gravity = BOTTOM | END;
+        translationX = taskViewWidth - banner.getHeight();
+        if (splitBounds == null) {
+            // Single, fullscreen case
+            bannerParams.width = taskViewHeight - snapshotParams.topMargin;
+            translationY = banner.getHeight();
+            return new Pair<>(translationX, translationY);
+        }
+
+        // Set correct width
+        if (desiredTaskId == splitBounds.leftTopTaskId) {
+            bannerParams.width = thumbnailViews[1].getMeasuredHeight();
+        } else {
+            bannerParams.width = thumbnailViews[0].getMeasuredHeight();
+        }
+
+        // Set translations
+        if (desiredTaskId == splitBounds.rightBottomTaskId) {
+            translationY = -(taskViewHeight - snapshotParams.topMargin)
+                    * (1f - splitBounds.leftTaskPercent)
+                    + banner.getHeight();
+        }
+        if (desiredTaskId == splitBounds.leftTopTaskId) {
+            translationY = banner.getHeight();
+        }
+        return new Pair<>(translationX, translationY);
+    }
+
+    @Override
     public int getDistanceToBottomOfRect(DeviceProfile dp, Rect rect) {
         return dp.widthPx - rect.right;
     }