Fix black flash when splitting task
- Draw the thumbnail view and align with the thumbnail bounds instead of
the whole task bounds with the icon
- Defer animating the task list until after the animation completes
Bug: 73118672
Test: Enter split screen
Change-Id: Ie10c079cb22ae82f3c5974296462abae335ef5a8
diff --git a/quickstep/src/com/android/launcher3/uioverrides/OverviewSwipeController.java b/quickstep/src/com/android/launcher3/uioverrides/OverviewSwipeController.java
index c8b54ad..8b73809 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/OverviewSwipeController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/OverviewSwipeController.java
@@ -198,8 +198,8 @@
}
} else {
if (goingUp) {
- mPendingAnimation = mRecentsView
- .createTaskDismissAnimation(mTaskBeingDragged, maxDuration);
+ mPendingAnimation = mRecentsView.createTaskDismissAnimation(mTaskBeingDragged,
+ true /* animateTaskView */, true /* removeTask */, maxDuration);
mCurrentAnimation = AnimatorPlaybackController
.wrap(mPendingAnimation.anim, maxDuration);
mEndDisplacement = -mTaskBeingDragged.getHeight();
diff --git a/quickstep/src/com/android/quickstep/TaskSystemShortcut.java b/quickstep/src/com/android/quickstep/TaskSystemShortcut.java
index 2022595..08be0c8 100644
--- a/quickstep/src/com/android/quickstep/TaskSystemShortcut.java
+++ b/quickstep/src/com/android/quickstep/TaskSystemShortcut.java
@@ -16,6 +16,8 @@
package com.android.quickstep;
+import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
+
import android.content.ComponentName;
import android.content.Intent;
import android.graphics.Bitmap;
@@ -31,12 +33,15 @@
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.BaseDraggingActivity;
+import com.android.launcher3.DeviceProfile;
import com.android.launcher3.ItemInfo;
import com.android.launcher3.R;
import com.android.launcher3.ShortcutInfo;
+import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.popup.SystemShortcut;
import com.android.launcher3.util.InstantAppResolver;
import com.android.quickstep.views.RecentsView;
+import com.android.quickstep.views.TaskThumbnailView;
import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.recents.ISystemUiProxy;
import com.android.systemui.shared.recents.model.Task;
@@ -57,6 +62,7 @@
public class TaskSystemShortcut<T extends SystemShortcut> extends SystemShortcut {
private static final String TAG = "TaskSystemShortcut";
+ private static final int DISMISS_TASK_DURATION = 300;
protected T mSystemShortcut;
@@ -99,10 +105,13 @@
}
}
- public static class SplitScreen extends TaskSystemShortcut implements OnPreDrawListener {
+ public static class SplitScreen extends TaskSystemShortcut implements OnPreDrawListener,
+ DeviceProfile.OnDeviceProfileChangeListener, View.OnLayoutChangeListener {
private Handler mHandler;
+ private RecentsView mRecentsView;
private TaskView mTaskView;
+ private BaseDraggingActivity mActivity;
public SplitScreen() {
super(R.drawable.ic_split_screen, R.string.recent_task_option_split_screen);
@@ -116,15 +125,19 @@
return null;
}
final Task task = taskView.getTask();
+ final int taskId = task.key.id;
if (!task.isDockable) {
return null;
}
+ mActivity = activity;
+ mRecentsView = activity.getOverviewPanel();
mTaskView = taskView;
+ final TaskThumbnailView thumbnailView = taskView.getThumbnail();
return (v -> {
AbstractFloatingView.closeOpenViews(activity, true,
AbstractFloatingView.TYPE_ALL & ~AbstractFloatingView.TYPE_REBIND_SAFE);
- if (ActivityManagerWrapper.getInstance().startActivityFromRecents(task.key.id,
+ if (ActivityManagerWrapper.getInstance().startActivityFromRecents(taskId,
ActivityOptionsCompat.makeSplitScreenOptions(true))) {
ISystemUiProxy sysUiProxy = RecentsModel.getInstance(activity).getSystemUiProxy();
try {
@@ -134,26 +147,35 @@
return;
}
+ // Add a device profile change listener to kick off animating the side tasks
+ // once we enter multiwindow mode and relayout
+ activity.addOnDeviceProfileChangeListener(this);
+
final Runnable animStartedListener = () -> {
+ // Hide the task view and wait for the window to be resized
+ // TODO: Consider animating in launcher and do an in-place start activity
+ // afterwards
+ mRecentsView.addIgnoreResetTask(mTaskView);
+ mTaskView.setAlpha(0f);
mTaskView.getViewTreeObserver().addOnPreDrawListener(SplitScreen.this);
- activity.<RecentsView>getOverviewPanel().removeView(taskView);
};
final int[] position = new int[2];
- taskView.getLocationOnScreen(position);
- final int width = (int) (taskView.getWidth() * taskView.getScaleX());
- final int height = (int) (taskView.getHeight() * taskView.getScaleY());
+ thumbnailView.getLocationOnScreen(position);
+ final int width = (int) (thumbnailView.getWidth() * taskView.getScaleX());
+ final int height = (int) (thumbnailView.getHeight() * taskView.getScaleY());
final Rect taskBounds = new Rect(position[0], position[1],
position[0] + width, position[1] + height);
Bitmap thumbnail = RecentsTransition.drawViewIntoHardwareBitmap(
- taskBounds.width(), taskBounds.height(), taskView, 1f, Color.BLACK);
+ taskBounds.width(), taskBounds.height(), thumbnailView, 1f,
+ Color.BLACK);
AppTransitionAnimationSpecsFuture future =
new AppTransitionAnimationSpecsFuture(mHandler) {
@Override
public List<AppTransitionAnimationSpecCompat> composeSpecs() {
return Collections.singletonList(new AppTransitionAnimationSpecCompat(
- task.key.id, thumbnail, taskBounds));
+ taskId, thumbnail, taskBounds));
}
};
WindowManagerWrapper.getInstance().overridePendingAppTransitionMultiThumbFuture(
@@ -168,6 +190,31 @@
WindowManagerWrapper.getInstance().endProlongedAnimations();
return true;
}
+
+ @Override
+ public void onDeviceProfileChanged(DeviceProfile dp) {
+ mActivity.removeOnDeviceProfileChangeListener(this);
+ if (dp.isMultiWindowMode) {
+ mTaskView.getRootView().addOnLayoutChangeListener(this);
+ }
+ }
+
+ @Override
+ public void onLayoutChange(View v, int l, int t, int r, int b,
+ int oldL, int oldT, int oldR, int oldB) {
+ mTaskView.getRootView().removeOnLayoutChangeListener(this);
+ mRecentsView.removeIgnoreResetTask(mTaskView);
+
+ // Start animating in the side pages once launcher has been resized
+ PendingAnimation pendingAnim = mRecentsView.createTaskDismissAnimation(mTaskView,
+ false, false, DISMISS_TASK_DURATION);
+ AnimatorPlaybackController controller = AnimatorPlaybackController.wrap(
+ pendingAnim.anim, DISMISS_TASK_DURATION);
+ controller.dispatchOnStart();
+ controller.setEndAction(() -> pendingAnim.finish(true));
+ controller.getAnimationPlayer().setInterpolator(FAST_OUT_SLOW_IN);
+ controller.start();
+ }
}
public static class Pin extends TaskSystemShortcut {
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 23e6e5b..68b9d45 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -30,6 +30,7 @@
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.graphics.Rect;
import android.os.Build;
+import android.util.ArraySet;
import android.util.AttributeSet;
import android.util.SparseBooleanArray;
import android.view.LayoutInflater;
@@ -104,6 +105,9 @@
private PendingAnimation mPendingAnimation;
+ // Keeps track of task views whose visual state should not be reset
+ private ArraySet<TaskView> mIgnoreResetTaskViews = new ArraySet<>();
+
public RecentsView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setPageSpacing(getResources().getDimensionPixelSize(R.dimen.recents_page_spacing));
@@ -261,7 +265,10 @@
public void resetTaskVisuals() {
for (int i = getChildCount() - 1; i >= 0; i--) {
- ((TaskView) getChildAt(i)).resetVisualProperties();
+ TaskView taskView = (TaskView) getChildAt(i);
+ if (!mIgnoreResetTaskViews.contains(taskView)) {
+ taskView.resetVisualProperties();
+ }
}
updateCurveProperties();
@@ -512,7 +519,16 @@
public float linearInterpolation;
}
- public PendingAnimation createTaskDismissAnimation(TaskView taskView, long duration) {
+ public void addIgnoreResetTask(TaskView taskView) {
+ mIgnoreResetTaskViews.add(taskView);
+ }
+
+ public void removeIgnoreResetTask(TaskView taskView) {
+ mIgnoreResetTaskViews.remove(taskView);
+ }
+
+ public PendingAnimation createTaskDismissAnimation(TaskView taskView, boolean animateTaskView,
+ boolean removeTask, long duration) {
if (FeatureFlags.IS_DOGFOOD_BUILD && mPendingAnimation != null) {
throw new IllegalStateException("Another pending animation is still running");
}
@@ -543,9 +559,11 @@
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
if (child == taskView) {
- addAnim(ObjectAnimator.ofFloat(taskView, ALPHA, 0), duration, ACCEL_2, anim);
- addAnim(ObjectAnimator.ofFloat(taskView, TRANSLATION_Y, -taskView.getHeight()),
- duration, LINEAR, anim);
+ if (animateTaskView) {
+ addAnim(ObjectAnimator.ofFloat(taskView, ALPHA, 0), duration, ACCEL_2, anim);
+ addAnim(ObjectAnimator.ofFloat(taskView, TRANSLATION_Y, -taskView.getHeight()),
+ duration, LINEAR, anim);
+ }
} else {
int scrollDiff = newScroll[i] - oldScroll[i] + maxScrollDiff;
if (scrollDiff != 0) {
@@ -563,12 +581,16 @@
}
// Add a tiny bit of translation Z, so that it draws on top of other views
- taskView.setTranslationZ(0.1f);
+ if (animateTaskView) {
+ taskView.setTranslationZ(0.1f);
+ }
mPendingAnimation = pendingAnimation;
mPendingAnimation.addEndListener((isSuccess) -> {
if (isSuccess) {
- ActivityManagerWrapper.getInstance().removeTask(taskView.getTask().key.id);
+ if (removeTask) {
+ ActivityManagerWrapper.getInstance().removeTask(taskView.getTask().key.id);
+ }
removeView(taskView);
if (getChildCount() == 0) {
onAllTasksRemoved();
diff --git a/src/com/android/launcher3/BaseActivity.java b/src/com/android/launcher3/BaseActivity.java
index 77a430b..02d70c4 100644
--- a/src/com/android/launcher3/BaseActivity.java
+++ b/src/com/android/launcher3/BaseActivity.java
@@ -98,9 +98,12 @@
mDPChangeListeners.add(listener);
}
+ public void removeOnDeviceProfileChangeListener(OnDeviceProfileChangeListener listener) {
+ mDPChangeListeners.remove(listener);
+ }
+
protected void dispatchDeviceProfileChanged() {
- int count = mDPChangeListeners.size();
- for (int i = 0; i < count; i++) {
+ for (int i = mDPChangeListeners.size() - 1; i >= 0; i--) {
mDPChangeListeners.get(i).onDeviceProfileChanged(mDeviceProfile);
}
}