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; + } }