summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorWinson Chung <winsonc@google.com>2018-01-29 11:16:32 -0800
committerWinson Chung <winsonc@google.com>2018-02-09 12:40:10 -0800
commit3e803c7a4bec4d14006057d918fe8663c7d36769 (patch)
tree722cfb15c4bf00dfd45f492d662f8b848ff3df5d
parent7780fa7bc711afbf89fbdc6fe94b473f2e3db3f2 (diff)
downloadandroid_packages_apps_Trebuchet-3e803c7a4bec4d14006057d918fe8663c7d36769.tar.gz
android_packages_apps_Trebuchet-3e803c7a4bec4d14006057d918fe8663c7d36769.tar.bz2
android_packages_apps_Trebuchet-3e803c7a4bec4d14006057d918fe8663c7d36769.zip
Animate the visible task view if launching an app that resolves to the task
Test: Manual, launch app for associated visible task Change-Id: I7a56553197ad23e1269eb50523eca0ea88898f47
-rw-r--r--quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java149
-rw-r--r--quickstep/src/com/android/launcher3/LauncherTransitionAnimator.java12
-rw-r--r--quickstep/src/com/android/quickstep/RecentsAnimationInterpolator.java111
-rw-r--r--quickstep/src/com/android/quickstep/RecentsView.java22
-rw-r--r--quickstep/src/com/android/quickstep/TaskThumbnailView.java8
-rw-r--r--src/com/android/launcher3/Utilities.java19
-rw-r--r--src/com/android/launcher3/anim/Interpolators.java3
7 files changed, 315 insertions, 9 deletions
diff --git a/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java b/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
index 11bc88348..2f0cd7833 100644
--- a/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
+++ b/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
@@ -47,6 +47,10 @@ import com.android.launcher3.allapps.AllAppsTransitionController;
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.graphics.DrawableFactory;
+import com.android.quickstep.RecentsAnimationInterpolator;
+import com.android.quickstep.RecentsAnimationInterpolator.TaskWindowBounds;
+import com.android.quickstep.RecentsView;
+import com.android.quickstep.TaskView;
import com.android.systemui.shared.system.ActivityCompat;
import com.android.systemui.shared.system.ActivityOptionsCompat;
import com.android.systemui.shared.system.RemoteAnimationAdapterCompat;
@@ -70,6 +74,7 @@ public class LauncherAppTransitionManagerImpl extends LauncherAppTransitionManag
private static final String CONTROL_REMOTE_APP_TRANSITION_PERMISSION =
"android.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS";
+ private static final int RECENTS_LAUNCH_DURATION = 336;
private static final int LAUNCHER_RESUME_START_DELAY = 150;
private static final int CLOSING_TRANSITION_DURATION_MS = 350;
@@ -139,8 +144,18 @@ public class LauncherAppTransitionManagerImpl extends LauncherAppTransitionManag
// Post at front of queue ignoring sync barriers to make sure it gets
// processed before the next frame.
postAtFrontOfQueueAsynchronously(v.getHandler(), () -> {
- LauncherTransitionAnimator animator = new LauncherTransitionAnimator(
- getLauncherAnimators(v), getWindowAnimators(v, targets));
+ final boolean removeTrackingView;
+ LauncherTransitionAnimator animator =
+ composeRecentsLaunchAnimator(v, targets);
+ if (animator != null) {
+ // We are animating the task view directly, do not remove it after
+ removeTrackingView = false;
+ } else {
+ animator = composeAppLaunchAnimator(v, targets);
+ // A new floating view is created for the animation, remove it after
+ removeTrackingView = true;
+ }
+
setCurrentAnimator(animator);
mAnimator = animator.getAnimatorSet();
mAnimator.addListener(new AnimatorListenerAdapter() {
@@ -148,7 +163,10 @@ public class LauncherAppTransitionManagerImpl extends LauncherAppTransitionManag
public void onAnimationEnd(Animator animation) {
// Reset launcher to normal state
v.setVisibility(View.VISIBLE);
- ((ViewGroup) mDragLayer.getParent()).removeView(mFloatingView);
+ if (removeTrackingView) {
+ ((ViewGroup) mDragLayer.getParent()).removeView(
+ mFloatingView);
+ }
mDragLayer.setAlpha(1f);
mDragLayer.setTranslationY(0f);
@@ -179,6 +197,131 @@ public class LauncherAppTransitionManagerImpl extends LauncherAppTransitionManag
}
/**
+ * Composes the animations for a launch from the recents list if possible.
+ */
+ private LauncherTransitionAnimator composeRecentsLaunchAnimator(View v,
+ RemoteAnimationTargetCompat[] targets) {
+ // Ensure recents is actually visible
+ if (!mLauncher.isInState(LauncherState.OVERVIEW)) {
+ return null;
+ }
+
+ // Resolve the opening task id
+ int openingTaskId = -1;
+ for (RemoteAnimationTargetCompat target : targets) {
+ if (target.mode == RemoteAnimationTargetCompat.MODE_OPENING) {
+ openingTaskId = target.taskId;
+ break;
+ }
+ }
+
+ // If there is no opening task id, fall back to the normal app icon launch animation
+ if (openingTaskId == -1) {
+ return null;
+ }
+
+ // If the opening task id is not currently visible in overview, then fall back to normal app
+ // icon launch animation
+ RecentsView recentsView = mLauncher.getOverviewPanel();
+ TaskView taskView = recentsView.getTaskView(openingTaskId);
+ if (taskView == null || !recentsView.isTaskViewVisible(taskView)) {
+ return null;
+ }
+
+ // Found a visible recents task that matches the opening app, lets launch the app from there
+ return new LauncherTransitionAnimator(null, getRecentsWindowAnimator(taskView, targets));
+ }
+
+ /**
+ * @return Animator that controls the window of the opening targets for the recents launch
+ * animation.
+ */
+ private ValueAnimator getRecentsWindowAnimator(TaskView v,
+ RemoteAnimationTargetCompat[] targets) {
+ Rect taskViewBounds = new Rect();
+ mDragLayer.getDescendantRectRelativeToSelf(v, taskViewBounds);
+
+ // TODO: Use the actual target insets instead of the current thumbnail insets in case the
+ // device state has changed
+ RecentsAnimationInterpolator recentsInterpolator = new RecentsAnimationInterpolator(
+ new Rect(0, 0, mDeviceProfile.widthPx, mDeviceProfile.heightPx),
+ v.getThumbnail().getInsets(),
+ taskViewBounds, new Rect(0, v.getThumbnail().getTop(), 0, 0));
+
+ Rect crop = new Rect();
+ Matrix matrix = new Matrix();
+
+ ValueAnimator appAnimator = ValueAnimator.ofFloat(0, 1);
+ appAnimator.setDuration(RECENTS_LAUNCH_DURATION);
+ appAnimator.setInterpolator(Interpolators.TOUCH_RESPONSE_INTERPOLATOR);
+ appAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ boolean isFirstFrame = true;
+
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ final Surface surface = getSurface(v);
+ final long frameNumber = surface != null ? getNextFrameNumber(surface) : -1;
+ if (frameNumber == -1) {
+ // Booo, not cool! Our surface got destroyed, so no reason to animate anything.
+ Log.w(TAG, "Failed to animate, surface got destroyed.");
+ return;
+ }
+ final float percent = animation.getAnimatedFraction();
+ TaskWindowBounds tw = recentsInterpolator.interpolate(percent);
+
+ v.setScaleX(tw.taskScale);
+ v.setScaleY(tw.taskScale);
+ v.setTranslationX(tw.taskX);
+ v.setTranslationY(tw.taskY);
+ // Defer fading out the view until after the app window gets faded in
+ v.setAlpha(getValue(1f, 0f, 75, 75,
+ appAnimator.getDuration() * percent, Interpolators.LINEAR));
+
+ matrix.setScale(tw.winScale, tw.winScale);
+ matrix.postTranslate(tw.winX, tw.winY);
+ crop.set(tw.winCrop);
+
+ // Fade in the app window.
+ float alphaDelay = 0;
+ float alphaDuration = 75;
+ float alpha = getValue(0f, 1f, alphaDelay, alphaDuration,
+ appAnimator.getDuration() * percent, Interpolators.LINEAR);
+
+ TransactionCompat t = new TransactionCompat();
+ for (RemoteAnimationTargetCompat target : targets) {
+ if (target.mode == RemoteAnimationTargetCompat.MODE_OPENING) {
+ t.setAlpha(target.leash, alpha);
+
+ // TODO: This isn't correct at the beginning of the animation, but better
+ // than nothing.
+ matrix.postTranslate(target.position.x, target.position.y);
+ t.setMatrix(target.leash, matrix);
+ t.setWindowCrop(target.leash, crop);
+ t.deferTransactionUntil(target.leash, surface, getNextFrameNumber(surface));
+ }
+ if (isFirstFrame) {
+ t.show(target.leash);
+ }
+ }
+ t.apply();
+
+ matrix.reset();
+ isFirstFrame = false;
+ }
+ });
+ return appAnimator;
+ }
+
+ /**
+ * Composes the animations for a launch from an app icon.
+ */
+ private LauncherTransitionAnimator composeAppLaunchAnimator(View v,
+ RemoteAnimationTargetCompat[] targets) {
+ return new LauncherTransitionAnimator(getLauncherAnimators(v),
+ getWindowAnimators(v, targets));
+ }
+
+ /**
* @return Animators that control the movements of the Launcher and icon of the opening target.
*/
private AnimatorSet getLauncherAnimators(View v) {
diff --git a/quickstep/src/com/android/launcher3/LauncherTransitionAnimator.java b/quickstep/src/com/android/launcher3/LauncherTransitionAnimator.java
index 80eaef7e3..aec28697e 100644
--- a/quickstep/src/com/android/launcher3/LauncherTransitionAnimator.java
+++ b/quickstep/src/com/android/launcher3/LauncherTransitionAnimator.java
@@ -32,11 +32,15 @@ public class LauncherTransitionAnimator {
private Animator mWindowAnimator;
LauncherTransitionAnimator(Animator launcherAnimator, Animator windowAnimator) {
- mLauncherAnimator = launcherAnimator;
+ if (launcherAnimator != null) {
+ mLauncherAnimator = launcherAnimator;
+ }
mWindowAnimator = windowAnimator;
mAnimatorSet = new AnimatorSet();
- mAnimatorSet.play(launcherAnimator);
+ if (launcherAnimator != null) {
+ mAnimatorSet.play(launcherAnimator);
+ }
mAnimatorSet.play(windowAnimator);
}
@@ -53,6 +57,8 @@ public class LauncherTransitionAnimator {
}
public void finishLauncherAnimation() {
- mLauncherAnimator.end();
+ if (mLauncherAnimator != null) {
+ mLauncherAnimator.end();
+ }
}
}
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationInterpolator.java b/quickstep/src/com/android/quickstep/RecentsAnimationInterpolator.java
new file mode 100644
index 000000000..9cc038f0e
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationInterpolator.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep;
+
+import android.graphics.Rect;
+
+import com.android.launcher3.Utilities;
+
+/**
+ * Helper class to interpolate the animation between a task view representation and an actual
+ * window.
+ */
+public class RecentsAnimationInterpolator {
+
+ public static class TaskWindowBounds {
+ public float taskScale = 1f;
+ public float taskX = 0f;
+ public float taskY = 0f;
+
+ public float winScale = 1f;
+ public float winX = 0f;
+ public float winY = 0f;
+ public Rect winCrop = new Rect();
+
+ @Override
+ public String toString() {
+ return "taskScale=" + taskScale + " taskX=" + taskX + " taskY=" + taskY
+ + " winScale=" + winScale + " winX=" + winX + " winY=" + winY
+ + " winCrop=" + winCrop;
+ }
+ }
+
+ private TaskWindowBounds mTmpTaskWindowBounds = new TaskWindowBounds();
+ private Rect mTmpInsets = new Rect();
+
+ private Rect mWindow;
+ private Rect mInsetWindow;
+ private Rect mInsets;
+ private Rect mTask;
+ private Rect mTaskInsets;
+ private Rect mThumbnail;
+
+ private float mTaskScale;
+ private Rect mScaledTask;
+ private Rect mTargetTask;
+ private Rect mSrcWindow;
+
+ public RecentsAnimationInterpolator(Rect window, Rect insets, Rect task, Rect taskInsets) {
+ mWindow = window;
+ mInsets = insets;
+ mTask = task;
+ mTaskInsets = taskInsets;
+ mInsetWindow = new Rect(window);
+ Utilities.insetRect(mInsetWindow, insets);
+
+ mThumbnail = new Rect(task);
+ Utilities.insetRect(mThumbnail, taskInsets);
+ mTaskScale = (float) mInsetWindow.width() / mThumbnail.width();
+ mScaledTask = new Rect(task);
+ Utilities.scaleRectAboutCenter(mScaledTask, mTaskScale);
+ Rect finalScaledTaskInsets = new Rect(taskInsets);
+ Utilities.scaleRect(finalScaledTaskInsets, mTaskScale);
+ mTargetTask = new Rect(mInsetWindow);
+ mTargetTask.offsetTo(window.top + insets.top - finalScaledTaskInsets.top,
+ window.left + insets.left - finalScaledTaskInsets.left);
+
+ float initialWinScale = 1f / mTaskScale;
+ Rect scaledWindow = new Rect(mInsetWindow);
+ Utilities.scaleRectAboutCenter(scaledWindow, initialWinScale);
+ Rect scaledInsets = new Rect(insets);
+ Utilities.scaleRect(scaledInsets, initialWinScale);
+ mSrcWindow = new Rect(scaledWindow);
+ mSrcWindow.offsetTo(mThumbnail.left - scaledInsets.left,
+ mThumbnail.top - scaledInsets.top);
+ }
+
+ public TaskWindowBounds interpolate(float t) {
+ mTmpTaskWindowBounds.taskScale = Utilities.mapRange(t,
+ 1, (float) mInsetWindow.width() / mThumbnail.width());
+ mTmpTaskWindowBounds.taskX = Utilities.mapRange(t,
+ 0, mTargetTask.left - mScaledTask.left);
+ mTmpTaskWindowBounds.taskY = Utilities.mapRange(t,
+ 0, mTargetTask.top - mScaledTask.top);
+
+ mTmpTaskWindowBounds.winScale = mTmpTaskWindowBounds.taskScale / mTaskScale;
+ mTmpTaskWindowBounds.winX = Utilities.mapRange(t,
+ mSrcWindow.left, 0);
+ mTmpTaskWindowBounds.winY = Utilities.mapRange(t,
+ mSrcWindow.top, 0);
+
+ mTmpInsets.set(mInsets);
+ Utilities.scaleRect(mTmpInsets, (1f - t));
+ mTmpTaskWindowBounds.winCrop.set(mWindow);
+ Utilities.insetRect(mTmpTaskWindowBounds.winCrop, mTmpInsets);
+
+ return mTmpTaskWindowBounds;
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/RecentsView.java b/quickstep/src/com/android/quickstep/RecentsView.java
index 8e03f37bf..26fe54eaa 100644
--- a/quickstep/src/com/android/quickstep/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/RecentsView.java
@@ -215,6 +215,21 @@ public class RecentsView extends PagedView implements Insettable {
return mFirstTaskIndex;
}
+ public boolean isTaskViewVisible(TaskView tv) {
+ // For now, just check if it's the active task
+ return indexOfChild(tv) == getNextPage();
+ }
+
+ public TaskView getTaskView(int taskId) {
+ for (int i = getFirstTaskIndex(); i < getChildCount(); i++) {
+ TaskView tv = (TaskView) getChildAt(i);
+ if (tv.getTask().key.id == taskId) {
+ return tv;
+ }
+ }
+ return null;
+ }
+
public void setStateController(RecentsViewStateController stateController) {
mStateController = stateController;
}
@@ -254,11 +269,16 @@ public class RecentsView extends PagedView implements Insettable {
}
setLayoutTransition(mLayoutTransition);
- // Rebind all task views
+ // Rebind and reset all task views
for (int i = tasks.size() - 1; i >= 0; i--) {
final Task task = tasks.get(i);
final TaskView taskView = (TaskView) getChildAt(tasks.size() - i - 1 + mFirstTaskIndex);
taskView.bind(task);
+ taskView.setScaleX(1f);
+ taskView.setScaleY(1f);
+ taskView.setTranslationX(0f);
+ taskView.setTranslationY(0f);
+ taskView.setAlpha(1f);
loader.loadTaskData(task);
}
}
diff --git a/quickstep/src/com/android/quickstep/TaskThumbnailView.java b/quickstep/src/com/android/quickstep/TaskThumbnailView.java
index 36a0601c8..4f93b1c83 100644
--- a/quickstep/src/com/android/quickstep/TaskThumbnailView.java
+++ b/quickstep/src/com/android/quickstep/TaskThumbnailView.java
@@ -28,6 +28,7 @@ import android.graphics.LinearGradient;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PorterDuff.Mode;
+import android.graphics.Rect;
import android.graphics.Shader;
import android.util.AttributeSet;
import android.view.View;
@@ -108,6 +109,13 @@ public class TaskThumbnailView extends View {
updateThumbnailPaintFilter();
}
+ public Rect getInsets() {
+ if (mThumbnailData != null) {
+ return mThumbnailData.insets;
+ }
+ return new Rect();
+ }
+
@Override
protected void onDraw(Canvas canvas) {
canvas.drawRoundRect(0, 0, getMeasuredWidth(), getMeasuredHeight(),
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index 158c540ac..d559b44be 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -237,16 +237,27 @@ public final class Utilities {
int cx = r.centerX();
int cy = r.centerY();
r.offset(-cx, -cy);
+ scaleRect(r, scale);
+ r.offset(cx, cy);
+ }
+ }
+ public static void scaleRect(Rect r, float scale) {
+ if (scale != 1.0f) {
r.left = (int) (r.left * scale + 0.5f);
r.top = (int) (r.top * scale + 0.5f);
r.right = (int) (r.right * scale + 0.5f);
r.bottom = (int) (r.bottom * scale + 0.5f);
-
- r.offset(cx, cy);
}
}
+ public static void insetRect(Rect r, Rect insets) {
+ r.left = Math.min(r.right, r.left + insets.left);
+ r.top = Math.min(r.bottom, r.top + insets.top);
+ r.right = Math.max(r.left, r.right - insets.right);
+ r.bottom = Math.max(r.top, r.bottom - insets.bottom);
+ }
+
public static float shrinkRect(Rect r, float scaleX, float scaleY) {
float scale = Math.min(Math.min(scaleX, scaleY), 1.0f);
if (scale < 1.0f) {
@@ -261,6 +272,10 @@ public final class Utilities {
return scale;
}
+ public static float mapRange(float value, float min, float max) {
+ return min + (value * (max - min));
+ }
+
public static boolean isSystemApp(Context context, Intent intent) {
PackageManager pm = context.getPackageManager();
ComponentName cn = intent.getComponent();
diff --git a/src/com/android/launcher3/anim/Interpolators.java b/src/com/android/launcher3/anim/Interpolators.java
index 234365424..0dcebe38e 100644
--- a/src/com/android/launcher3/anim/Interpolators.java
+++ b/src/com/android/launcher3/anim/Interpolators.java
@@ -50,6 +50,9 @@ public class Interpolators {
public static final Interpolator OVERSHOOT_0 = new OvershootInterpolator(0);
+ public static final Interpolator TOUCH_RESPONSE_INTERPOLATOR =
+ new PathInterpolator(0.3f, 0f, 0.1f, 1f);
+
/**
* Inversion of zInterpolate, compounded with an ease-out.
*/