From 8a968fab7249c9645420e8a562ceab7b75b6f0ea Mon Sep 17 00:00:00 2001 From: Winson Chung Date: Thu, 15 Mar 2018 17:59:04 -0700 Subject: [PATCH] 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 --- .../uioverrides/OverviewSwipeController.java | 4 +- .../android/quickstep/TaskSystemShortcut.java | 63 ++++++++++++++++--- .../android/quickstep/views/RecentsView.java | 36 ++++++++--- src/com/android/launcher3/BaseActivity.java | 7 ++- 4 files changed, 91 insertions(+), 19 deletions(-) diff --git a/quickstep/src/com/android/launcher3/uioverrides/OverviewSwipeController.java b/quickstep/src/com/android/launcher3/uioverrides/OverviewSwipeController.java index c8b54adaf0..8b738091a5 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/OverviewSwipeController.java +++ b/quickstep/src/com/android/launcher3/uioverrides/OverviewSwipeController.java @@ -198,8 +198,8 @@ public class OverviewSwipeController extends AnimatorListenerAdapter } } 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 202259530a..08be0c8f2e 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 android.view.ViewTreeObserver.OnPreDrawListener; 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 @@ import java.util.function.Consumer; public class TaskSystemShortcut extends SystemShortcut { private static final String TAG = "TaskSystemShortcut"; + private static final int DISMISS_TASK_DURATION = 300; protected T mSystemShortcut; @@ -99,10 +105,13 @@ public class TaskSystemShortcut extends SystemShortcut } } - 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 @@ public class TaskSystemShortcut extends SystemShortcut 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 @@ public class TaskSystemShortcut extends SystemShortcut 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.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 composeSpecs() { return Collections.singletonList(new AppTransitionAnimationSpecCompat( - task.key.id, thumbnail, taskBounds)); + taskId, thumbnail, taskBounds)); } }; WindowManagerWrapper.getInstance().overridePendingAppTransitionMultiThumbFuture( @@ -168,6 +190,31 @@ public class TaskSystemShortcut extends SystemShortcut 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 23e6e5bb20..68b9d458d5 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; 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 @@ public abstract class RecentsView private PendingAnimation mPendingAnimation; + // Keeps track of task views whose visual state should not be reset + private ArraySet 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 abstract class RecentsView 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 abstract class RecentsView 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 @@ public abstract class RecentsView 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 @@ public abstract class RecentsView } // 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 77a430bb33..02d70c42ca 100644 --- a/src/com/android/launcher3/BaseActivity.java +++ b/src/com/android/launcher3/BaseActivity.java @@ -98,9 +98,12 @@ public abstract class BaseActivity extends Activity { 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); } }