From 502847f7eb29cd8fc3f515e9e5aa2647b0d9e72f Mon Sep 17 00:00:00 2001 From: Kevin Date: Wed, 13 Mar 2019 15:49:31 -0700 Subject: [PATCH] Add app to overview anim for Recents Go. This CL adds the first iteration of window animation to go from the app back to recents provided that the view is ready to be visible to the user. Bug: 114136250 Test: Go to recents, launch app, press overview to go back to recents Change-Id: Ic0567e7c87fa964bdad25d07eca61b78407a9ff5 --- go/quickstep/res/layout/task_item_view.xml | 8 +- go/quickstep/res/values/dimens.xml | 4 +- .../AppToOverviewAnimationProvider.java | 154 +++++++++++++++++- .../LauncherActivityControllerHelper.java | 7 +- .../com/android/quickstep/TaskAdapter.java | 24 +++ .../quickstep/views/IconRecentsView.java | 15 ++ .../android/quickstep/views/TaskItemView.java | 5 + 7 files changed, 204 insertions(+), 13 deletions(-) diff --git a/go/quickstep/res/layout/task_item_view.xml b/go/quickstep/res/layout/task_item_view.xml index ee67d49fc7..048e9c510a 100644 --- a/go/quickstep/res/layout/task_item_view.xml +++ b/go/quickstep/res/layout/task_item_view.xml @@ -21,15 +21,15 @@ android:orientation="horizontal"> - 60dp 8dp + 60dp + 60dp + 36dp 36dp \ No newline at end of file diff --git a/go/quickstep/src/com/android/quickstep/AppToOverviewAnimationProvider.java b/go/quickstep/src/com/android/quickstep/AppToOverviewAnimationProvider.java index f199643bce..defed845db 100644 --- a/go/quickstep/src/com/android/quickstep/AppToOverviewAnimationProvider.java +++ b/go/quickstep/src/com/android/quickstep/AppToOverviewAnimationProvider.java @@ -15,15 +15,29 @@ */ package com.android.quickstep; +import static com.android.launcher3.anim.Interpolators.ACCEL_2; +import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL; import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN; +import static com.android.quickstep.util.RemoteAnimationProvider.getLayer; +import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING; +import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_OPENING; import android.animation.AnimatorSet; import android.animation.ValueAnimator; +import android.graphics.Matrix; +import android.graphics.Rect; import android.util.Log; +import android.view.View; + +import androidx.annotation.NonNull; import com.android.launcher3.BaseDraggingActivity; +import com.android.quickstep.util.MultiValueUpdateListener; import com.android.quickstep.util.RemoteAnimationProvider; +import com.android.quickstep.views.IconRecentsView; import com.android.systemui.shared.system.RemoteAnimationTargetCompat; +import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat; +import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams; /** * Provider for the atomic remote window animation from the app to the overview. @@ -33,13 +47,17 @@ import com.android.systemui.shared.system.RemoteAnimationTargetCompat; final class AppToOverviewAnimationProvider implements RemoteAnimationProvider { - private static final long RECENTS_LAUNCH_DURATION = 250; + private static final long APP_TO_THUMBNAIL_FADE_DURATION = 50; + private static final long APP_SCALE_DOWN_DURATION = 400; private static final String TAG = "AppToOverviewAnimationProvider"; private final ActivityControlHelper mHelper; + private final int mTargetTaskId; + private IconRecentsView mRecentsView; AppToOverviewAnimationProvider(ActivityControlHelper helper, int targetTaskId) { mHelper = helper; + mTargetTaskId = targetTaskId; } /** @@ -54,35 +72,157 @@ final class AppToOverviewAnimationProvider imple false /* animate activity */, (controller) -> { controller.dispatchOnStart(); ValueAnimator anim = controller.getAnimationPlayer() - .setDuration(RECENTS_LAUNCH_DURATION); + .setDuration(getRecentsLaunchDuration()); anim.setInterpolator(FAST_OUT_SLOW_IN); anim.start(); }); factory.onRemoteAnimationReceived(null); - factory.createActivityController(RECENTS_LAUNCH_DURATION); + factory.createActivityController(getRecentsLaunchDuration()); + mRecentsView = activity.getOverviewPanel(); return false; } /** - * Create remote window animation from the currently running app to the overview panel. + * Create remote window animation from the currently running app to the overview panel. Should + * be called after {@link #onActivityReady}. * * @param targetCompats the target apps * @return animation from app to overview */ @Override public AnimatorSet createWindowAnimation(RemoteAnimationTargetCompat[] targetCompats) { - //TODO: Implement the overview to app window animation for Go. AnimatorSet anim = new AnimatorSet(); - anim.play(ValueAnimator.ofInt(0, 1).setDuration(RECENTS_LAUNCH_DURATION)); + if (mRecentsView == null) { + if (Log.isLoggable(TAG, Log.WARN)) { + Log.w(TAG, "No recents view. Using stub animation."); + } + anim.play(ValueAnimator.ofInt(0, 1).setDuration(getRecentsLaunchDuration())); + return anim; + } + + RemoteAnimationTargetCompat recentsTarget = null; + RemoteAnimationTargetCompat closingAppTarget = null; + + for (RemoteAnimationTargetCompat target : targetCompats) { + if (target.mode == MODE_OPENING) { + recentsTarget = target; + } else if (target.mode == MODE_CLOSING && target.taskId == mTargetTaskId) { + closingAppTarget = target; + } + } + + if (closingAppTarget == null) { + if (Log.isLoggable(TAG, Log.WARN)) { + Log.w(TAG, "No closing app target. Using stub animation."); + } + anim.play(ValueAnimator.ofInt(0, 1).setDuration(getRecentsLaunchDuration())); + return anim; + } + if (recentsTarget == null) { + if (Log.isLoggable(TAG, Log.WARN)) { + Log.w(TAG, "No recents target. Using stub animation."); + } + anim.play(ValueAnimator.ofInt(0, 1).setDuration(getRecentsLaunchDuration())); + return anim; + } + + View thumbnailView = mRecentsView.getThumbnailViewForTask(mTargetTaskId); + if (thumbnailView == null) { + // TODO: We should either 1) guarantee the view is loaded before attempting this + // or 2) have a backup animation. + if (Log.isLoggable(TAG, Log.WARN)) { + Log.w(TAG, "No thumbnail view for running task. Using stub animation."); + } + anim.play(ValueAnimator.ofInt(0, 1).setDuration(getRecentsLaunchDuration())); + return anim; + } + + playAppScaleDownAnim(anim, closingAppTarget, recentsTarget, thumbnailView); + return anim; } + /** + * Animate a closing app to scale down to the location of the thumbnail view in recents. + * + * @param anim animator set + * @param appTarget the app surface thats closing + * @param recentsTarget the surface containing recents + * @param thumbnailView the thumbnail view to animate to + */ + private void playAppScaleDownAnim(@NonNull AnimatorSet anim, + @NonNull RemoteAnimationTargetCompat appTarget, + @NonNull RemoteAnimationTargetCompat recentsTarget, @NonNull View thumbnailView) { + + // Identify where the entering remote app should animate to. + Rect endRect = new Rect(); + thumbnailView.getGlobalVisibleRect(endRect); + + Rect appBounds = appTarget.sourceContainerBounds; + + ValueAnimator valueAnimator = ValueAnimator.ofInt(0, 1); + valueAnimator.setDuration(APP_SCALE_DOWN_DURATION); + + SyncRtSurfaceTransactionApplierCompat surfaceApplier = + new SyncRtSurfaceTransactionApplierCompat(thumbnailView); + + // Keep recents visible throughout the animation. + SurfaceParams[] params = new SurfaceParams[2]; + params[0] = new SurfaceParams(recentsTarget.leash, 1f, null /* matrix */, + null /* windowCrop */, getLayer(recentsTarget, MODE_OPENING), 0 /* cornerRadius */); + + valueAnimator.addUpdateListener(new MultiValueUpdateListener() { + private final FloatProp mScaleX; + private final FloatProp mScaleY; + private final FloatProp mTranslationX; + private final FloatProp mTranslationY; + private final FloatProp mAlpha; + + { + // Scale down and move to view location. + float endScaleX = ((float) endRect.width()) / appBounds.width(); + mScaleX = new FloatProp(1f, endScaleX, 0, APP_SCALE_DOWN_DURATION, + ACCEL_DEACCEL); + float endScaleY = ((float) endRect.height()) / appBounds.height(); + mScaleY = new FloatProp(1f, endScaleY, 0, APP_SCALE_DOWN_DURATION, + ACCEL_DEACCEL); + float endTranslationX = endRect.left - + (appBounds.width() - thumbnailView.getWidth()) / 2.0f; + mTranslationX = new FloatProp(0, endTranslationX, 0, APP_SCALE_DOWN_DURATION, + ACCEL_DEACCEL); + float endTranslationY = endRect.top - + (appBounds.height() - thumbnailView.getHeight()) / 2.0f; + mTranslationY = new FloatProp(0, endTranslationY, 0, APP_SCALE_DOWN_DURATION, + ACCEL_DEACCEL); + + // Fade out quietly near the end to be replaced by the real view. + mAlpha = new FloatProp(1.0f, 0, + APP_SCALE_DOWN_DURATION - APP_TO_THUMBNAIL_FADE_DURATION, + APP_TO_THUMBNAIL_FADE_DURATION, ACCEL_2); + } + + @Override + public void onUpdate(float percent) { + Matrix m = new Matrix(); + m.setScale(mScaleX.value, mScaleY.value, + appBounds.width() / 2.0f, appBounds.height() / 2.0f); + m.postTranslate(mTranslationX.value, mTranslationY.value); + + params[1] = new SurfaceParams(appTarget.leash, mAlpha.value, m, + null /* windowCrop */, getLayer(appTarget, MODE_CLOSING), + 0 /* cornerRadius */); + surfaceApplier.scheduleApply(params); + } + }); + anim.play(valueAnimator); + } + /** * Get duration of animation from app to overview. * * @return duration of animation */ long getRecentsLaunchDuration() { - return RECENTS_LAUNCH_DURATION; + return APP_SCALE_DOWN_DURATION; } } diff --git a/go/quickstep/src/com/android/quickstep/LauncherActivityControllerHelper.java b/go/quickstep/src/com/android/quickstep/LauncherActivityControllerHelper.java index d55d2e5fd9..40db7ddbce 100644 --- a/go/quickstep/src/com/android/quickstep/LauncherActivityControllerHelper.java +++ b/go/quickstep/src/com/android/quickstep/LauncherActivityControllerHelper.java @@ -21,6 +21,7 @@ import static com.android.launcher3.LauncherState.OVERVIEW; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherInitListener; +import com.android.launcher3.LauncherState; import com.android.launcher3.anim.AnimatorPlaybackController; import com.android.launcher3.userevent.nano.LauncherLogProto; import com.android.quickstep.views.IconRecentsView; @@ -38,10 +39,14 @@ public final class LauncherActivityControllerHelper extends GoActivityControlHel public AnimationFactory prepareRecentsUI(Launcher activity, boolean activityVisible, boolean animateActivity, Consumer callback) { + LauncherState fromState = activity.getStateManager().getState(); //TODO: Implement this based off where the recents view needs to be for app => recents anim. return new AnimationFactory() { @Override - public void createActivityController(long transitionLength) {} + public void createActivityController(long transitionLength) { + callback.accept(activity.getStateManager().createAnimationToNewWorkspace( + fromState, OVERVIEW, transitionLength)); + } @Override public void onTransitionCancelled() {} diff --git a/go/quickstep/src/com/android/quickstep/TaskAdapter.java b/go/quickstep/src/com/android/quickstep/TaskAdapter.java index 4f3d1e4a03..9a2c0f88ab 100644 --- a/go/quickstep/src/com/android/quickstep/TaskAdapter.java +++ b/go/quickstep/src/com/android/quickstep/TaskAdapter.java @@ -15,10 +15,12 @@ */ package com.android.quickstep; +import android.util.ArrayMap; import android.view.LayoutInflater; import android.view.ViewGroup; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.recyclerview.widget.RecyclerView.Adapter; import com.android.launcher3.R; @@ -36,6 +38,7 @@ public final class TaskAdapter extends Adapter { private static final int MAX_TASKS_TO_DISPLAY = 6; private static final String TAG = "TaskAdapter"; private final TaskListLoader mLoader; + private final ArrayMap mTaskIdToViewMap = new ArrayMap<>(); private TaskInputController mInputController; public TaskAdapter(@NonNull TaskListLoader loader) { @@ -46,6 +49,16 @@ public final class TaskAdapter extends Adapter { mInputController = inputController; } + /** + * Get task item view for a given task id if it's attached to the view. + * + * @param taskId task id to search for + * @return corresponding task item view if it's attached, null otherwise + */ + public @Nullable TaskItemView getTaskItemView(int taskId) { + return mTaskIdToViewMap.get(taskId); + } + @Override public TaskHolder onCreateViewHolder(ViewGroup parent, int viewType) { TaskItemView itemView = (TaskItemView) LayoutInflater.from(parent.getContext()) @@ -63,6 +76,17 @@ public final class TaskAdapter extends Adapter { return; } holder.bindTask(tasks.get(position)); + + } + + @Override + public void onViewAttachedToWindow(@NonNull TaskHolder holder) { + mTaskIdToViewMap.put(holder.getTask().key.id, (TaskItemView) holder.itemView); + } + + @Override + public void onViewDetachedFromWindow(@NonNull TaskHolder holder) { + mTaskIdToViewMap.remove(holder.getTask().key.id); } @Override diff --git a/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java b/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java index e294282c3e..3fdaefe156 100644 --- a/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java +++ b/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java @@ -27,6 +27,7 @@ import android.view.ViewDebug; import android.widget.FrameLayout; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.recyclerview.widget.ItemTouchHelper; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; @@ -155,6 +156,20 @@ public final class IconRecentsView extends FrameLayout { }); } + /** + * Get the thumbnail view associated with a task for the purposes of animation. + * + * @param taskId task id of thumbnail view to get + * @return the thumbnail view for the task if attached, null otherwise + */ + public @Nullable View getThumbnailViewForTask(int taskId) { + TaskItemView view = mTaskAdapter.getTaskItemView(taskId); + if (view == null) { + return null; + } + return view.getThumbnailView(); + } + public void setTranslationYFactor(float translationFactor) { mTranslationYFactor = translationFactor; setTranslationY(computeTranslationYForFactor(mTranslationYFactor)); diff --git a/go/quickstep/src/com/android/quickstep/views/TaskItemView.java b/go/quickstep/src/com/android/quickstep/views/TaskItemView.java index 3818965b79..373f107910 100644 --- a/go/quickstep/src/com/android/quickstep/views/TaskItemView.java +++ b/go/quickstep/src/com/android/quickstep/views/TaskItemView.java @@ -19,6 +19,7 @@ import android.content.Context; import android.graphics.Bitmap; import android.graphics.drawable.Drawable; import android.util.AttributeSet; +import android.view.View; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; @@ -76,4 +77,8 @@ public final class TaskItemView extends LinearLayout { public void setThumbnail(Bitmap thumbnail) { mThumbnailView.setImageBitmap(thumbnail); } + + public View getThumbnailView() { + return mThumbnailView; + } }