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
diff --git a/go/quickstep/res/layout/task_item_view.xml b/go/quickstep/res/layout/task_item_view.xml
index ee67d49..048e9c5 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">
<FrameLayout
android:id="@+id/task_icon_and_thumbnail"
- android:layout_width="@dimen/task_item_height"
- android:layout_height="@dimen/task_item_height"
+ android:layout_width="@dimen/task_thumbnail_and_icon_view_size"
+ android:layout_height="@dimen/task_thumbnail_and_icon_view_size"
android:layout_gravity="center_vertical"
android:layout_marginHorizontal="8dp"
android:layout_marginVertical="@dimen/task_item_half_vert_margin">
<ImageView
android:id="@+id/task_thumbnail"
- android:layout_width="wrap_content"
- android:layout_height="match_parent"
+ android:layout_width="@dimen/task_thumbnail_width"
+ android:layout_height="@dimen/task_thumbnail_height"
android:layout_gravity="top|start"/>
<ImageView
android:id="@+id/task_icon"
diff --git a/go/quickstep/res/values/dimens.xml b/go/quickstep/res/values/dimens.xml
index 28cc1eb..7269704 100644
--- a/go/quickstep/res/values/dimens.xml
+++ b/go/quickstep/res/values/dimens.xml
@@ -15,7 +15,9 @@
limitations under the License.
-->
<resources>
- <dimen name="task_item_height">60dp</dimen>
<dimen name="task_item_half_vert_margin">8dp</dimen>
+ <dimen name="task_thumbnail_and_icon_view_size">60dp</dimen>
+ <dimen name="task_thumbnail_height">60dp</dimen>
+ <dimen name="task_thumbnail_width">36dp</dimen>
<dimen name="task_icon_size">36dp</dimen>
</resources>
\ 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 f199643..defed84 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 @@
final class AppToOverviewAnimationProvider<T extends BaseDraggingActivity> 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<T> mHelper;
+ private final int mTargetTaskId;
+ private IconRecentsView mRecentsView;
AppToOverviewAnimationProvider(ActivityControlHelper<T> helper, int targetTaskId) {
mHelper = helper;
+ mTargetTaskId = targetTaskId;
}
/**
@@ -54,35 +72,157 @@
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 d55d2e5..40db7dd 100644
--- a/go/quickstep/src/com/android/quickstep/LauncherActivityControllerHelper.java
+++ b/go/quickstep/src/com/android/quickstep/LauncherActivityControllerHelper.java
@@ -21,6 +21,7 @@
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 AnimationFactory prepareRecentsUI(Launcher activity,
boolean activityVisible, boolean animateActivity,
Consumer<AnimatorPlaybackController> 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 4f3d1e4..9a2c0f8 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 @@
private static final int MAX_TASKS_TO_DISPLAY = 6;
private static final String TAG = "TaskAdapter";
private final TaskListLoader mLoader;
+ private final ArrayMap<Integer, TaskItemView> mTaskIdToViewMap = new ArrayMap<>();
private TaskInputController mInputController;
public TaskAdapter(@NonNull TaskListLoader loader) {
@@ -46,6 +49,16 @@
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 @@
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 e294282..3fdaefe 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.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 @@
});
}
+ /**
+ * 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 3818965..373f107 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.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 void setThumbnail(Bitmap thumbnail) {
mThumbnailView.setImageBitmap(thumbnail);
}
+
+ public View getThumbnailView() {
+ return mThumbnailView;
+ }
}