Refactor LauncherAppTransitionManager & polish for new app transitions.

* Add start delay when launcher resumes from all apps or landscape.
* Track the last app transition animator and cancel it before beginning a new one,
  otherwise the animators can conflict with each other.
  ie. Opening an app from all apps and then immediately pressing back to return to
      all apps.
* Use class overrride instead of UiFactory.

Bug: 70220260
Change-Id: I4755d45d820f9d551e443d6c4a148e8789c5bc57
This commit is contained in:
Jon Miranda 2018-01-24 15:38:25 -08:00
parent 82d010f0d2
commit 54441f5df9
8 changed files with 202 additions and 119 deletions

View File

@ -97,6 +97,11 @@
# support jar. # support jar.
-keep class android.support.v7.widget.RecyclerView { *; } -keep class android.support.v7.widget.RecyclerView { *; }
# LauncherAppTransitionManager
-keep class com.android.launcher3.LauncherAppTransitionManagerImpl {
public <init>(...);
}
-keep interface com.android.launcher3.userevent.nano.LauncherLogProto.** { -keep interface com.android.launcher3.userevent.nano.LauncherLogProto.** {
*; *;
} }

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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.
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_transition_manager_class" translatable="false">com.android.launcher3.LauncherAppTransitionManagerImpl</string>
</resources>

View File

@ -26,6 +26,8 @@ import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet; import android.animation.AnimatorSet;
import android.animation.ObjectAnimator; import android.animation.ObjectAnimator;
import android.animation.ValueAnimator; import android.animation.ValueAnimator;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.res.Resources; import android.content.res.Resources;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.Matrix; import android.graphics.Matrix;
@ -55,11 +57,15 @@ import com.android.systemui.shared.system.WindowManagerWrapper;
/** /**
* Manages the opening and closing app transitions from Launcher. * Manages the opening and closing app transitions from Launcher.
*/ */
public class LauncherAppTransitionManager { public class LauncherAppTransitionManagerImpl extends LauncherAppTransitionManager {
private static final String TAG = "LauncherTransition"; private static final String TAG = "LauncherTransition";
private static final int REFRESH_RATE_MS = 16; private static final int REFRESH_RATE_MS = 16;
private static final String CONTROL_REMOTE_APP_TRANSITION_PERMISSION =
"android.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS";
private static final int LAUNCHER_RESUME_START_DELAY = 150;
private static final int CLOSING_TRANSITION_DURATION_MS = 350; private static final int CLOSING_TRANSITION_DURATION_MS = 350;
// Progress = 0: All apps is fully pulled up, Progress = 1: All apps is fully pulled down. // Progress = 0: All apps is fully pulled up, Progress = 1: All apps is fully pulled down.
@ -76,31 +82,44 @@ public class LauncherAppTransitionManager {
private ImageView mFloatingView; private ImageView mFloatingView;
private boolean mIsRtl; private boolean mIsRtl;
public LauncherAppTransitionManager(Launcher launcher) { private Animator mCurrentAnimator;
mLauncher = launcher;
mDragLayer = launcher.getDragLayer();
mDeviceProfile = launcher.getDeviceProfile();
mIsRtl = Utilities.isRtl(launcher.getResources()); public LauncherAppTransitionManagerImpl(Context context) {
mLauncher = Launcher.getLauncher(context);
mDragLayer = mLauncher.getDragLayer();
mDeviceProfile = mLauncher.getDeviceProfile();
Resources res = launcher.getResources(); mIsRtl = Utilities.isRtl(mLauncher.getResources());
Resources res = mLauncher.getResources();
mContentTransY = res.getDimensionPixelSize(R.dimen.content_trans_y); mContentTransY = res.getDimensionPixelSize(R.dimen.content_trans_y);
mWorkspaceTransY = res.getDimensionPixelSize(R.dimen.workspace_trans_y); mWorkspaceTransY = res.getDimensionPixelSize(R.dimen.workspace_trans_y);
} }
private void setCurrentAnimator(Animator animator) {
if (mCurrentAnimator != null && mCurrentAnimator.isRunning()) {
mCurrentAnimator.cancel();
}
mCurrentAnimator = animator;
}
/** /**
* @return A Bundle with remote animations that controls how the window of the opening * @return A Bundle with remote animations that controls how the window of the opening
* targets are displayed. * targets are displayed.
*/ */
public Bundle getActivityLauncherOptions(View v) { @Override
public Bundle getActivityLaunchOptions(Launcher launcher, View v) {
if (hasControlRemoteAppTransitionPermission()) {
try {
RemoteAnimationRunnerCompat runner = new LauncherAnimationRunner(mLauncher) { RemoteAnimationRunnerCompat runner = new LauncherAnimationRunner(mLauncher) {
@Override @Override
public void onAnimationStart(RemoteAnimationTargetCompat[] targets, public void onAnimationStart(RemoteAnimationTargetCompat[] targets,
Runnable finishedCallback) { Runnable finishedCallback) {
// Post at front of queue ignoring sync barriers to make sure it gets processed // Post at front of queue ignoring sync barriers to make sure it gets
// before the next frame. // processed before the next frame.
postAtFrontOfQueueAsynchronously(v.getHandler(), () -> { postAtFrontOfQueueAsynchronously(v.getHandler(), () -> {
mAnimator = new AnimatorSet(); mAnimator = new AnimatorSet();
setCurrentAnimator(mAnimator);
mAnimator.play(getLauncherAnimators(v)); mAnimator.play(getLauncherAnimators(v));
mAnimator.play(getWindowAnimators(v, targets)); mAnimator.play(getWindowAnimators(v, targets));
mAnimator.addListener(new AnimatorListenerAdapter() { mAnimator.addListener(new AnimatorListenerAdapter() {
@ -121,8 +140,8 @@ public class LauncherAppTransitionManager {
} }
}); });
mAnimator.start(); mAnimator.start();
// Because t=0 has the app icon in its original spot, we can skip the first // Because t=0 has the app icon in its original spot, we can skip the
// frame and have the same movement one frame earlier. // first frame and have the same movement one frame earlier.
mAnimator.setCurrentPlayTime(REFRESH_RATE_MS); mAnimator.setCurrentPlayTime(REFRESH_RATE_MS);
}); });
} }
@ -130,6 +149,12 @@ public class LauncherAppTransitionManager {
return ActivityOptionsCompat.makeRemoteAnimation( return ActivityOptionsCompat.makeRemoteAnimation(
new RemoteAnimationAdapterCompat(runner, 500, 380)).toBundle(); new RemoteAnimationAdapterCompat(runner, 500, 380)).toBundle();
} catch (NoClassDefFoundError e) {
// Gracefully fall back to default launch options if the user's platform doesn't
// have the latest changes.
}
}
return getDefaultActivityLaunchOptions(launcher, v);
} }
/** /**
@ -149,7 +174,7 @@ public class LauncherAppTransitionManager {
* Else: Animate the content so that it moves downwards and fades out. * Else: Animate the content so that it moves downwards and fades out.
*/ */
private AnimatorSet getLauncherContentAnimator(boolean show) { private AnimatorSet getLauncherContentAnimator(boolean show) {
AnimatorSet hideLauncher = new AnimatorSet(); AnimatorSet launcherAnimator = new AnimatorSet();
float[] alphas = show float[] alphas = show
? new float[] {0, 1} ? new float[] {0, 1}
@ -161,6 +186,9 @@ public class LauncherAppTransitionManager {
if (mLauncher.isInState(LauncherState.ALL_APPS) && !mDeviceProfile.isVerticalBarLayout()) { if (mLauncher.isInState(LauncherState.ALL_APPS) && !mDeviceProfile.isVerticalBarLayout()) {
// All Apps in portrait mode is full screen, so we only animate AllAppsContainerView. // All Apps in portrait mode is full screen, so we only animate AllAppsContainerView.
View appsView = mLauncher.getAppsView(); View appsView = mLauncher.getAppsView();
appsView.setAlpha(alphas[0]);
appsView.setTranslationY(trans[0]);
ObjectAnimator alpha = ObjectAnimator.ofFloat(appsView, View.ALPHA, alphas); ObjectAnimator alpha = ObjectAnimator.ofFloat(appsView, View.ALPHA, alphas);
alpha.setDuration(217); alpha.setDuration(217);
alpha.setInterpolator(Interpolators.LINEAR); alpha.setInterpolator(Interpolators.LINEAR);
@ -168,9 +196,12 @@ public class LauncherAppTransitionManager {
transY.setInterpolator(Interpolators.AGGRESSIVE_EASE); transY.setInterpolator(Interpolators.AGGRESSIVE_EASE);
transY.setDuration(350); transY.setDuration(350);
hideLauncher.play(alpha); launcherAnimator.play(alpha);
hideLauncher.play(transY); launcherAnimator.play(transY);
} else { } else {
mDragLayer.setAlpha(alphas[0]);
mDragLayer.setTranslationY(trans[0]);
ObjectAnimator dragLayerAlpha = ObjectAnimator.ofFloat(mDragLayer, View.ALPHA, alphas); ObjectAnimator dragLayerAlpha = ObjectAnimator.ofFloat(mDragLayer, View.ALPHA, alphas);
dragLayerAlpha.setDuration(217); dragLayerAlpha.setDuration(217);
dragLayerAlpha.setInterpolator(Interpolators.LINEAR); dragLayerAlpha.setInterpolator(Interpolators.LINEAR);
@ -179,10 +210,10 @@ public class LauncherAppTransitionManager {
dragLayerTransY.setInterpolator(Interpolators.AGGRESSIVE_EASE); dragLayerTransY.setInterpolator(Interpolators.AGGRESSIVE_EASE);
dragLayerTransY.setDuration(350); dragLayerTransY.setDuration(350);
hideLauncher.play(dragLayerAlpha); launcherAnimator.play(dragLayerAlpha);
hideLauncher.play(dragLayerTransY); launcherAnimator.play(dragLayerTransY);
} }
return hideLauncher; return launcherAnimator;
} }
/** /**
@ -361,7 +392,10 @@ public class LauncherAppTransitionManager {
/** /**
* Registers remote animations used when closing apps to home screen. * Registers remote animations used when closing apps to home screen.
*/ */
@Override
public void registerRemoteAnimations() { public void registerRemoteAnimations() {
if (hasControlRemoteAppTransitionPermission()) {
try {
RemoteAnimationDefinitionCompat definition = new RemoteAnimationDefinitionCompat(); RemoteAnimationDefinitionCompat definition = new RemoteAnimationDefinitionCompat();
definition.addRemoteAnimation(WindowManagerWrapper.TRANSIT_WALLPAPER_OPEN, definition.addRemoteAnimation(WindowManagerWrapper.TRANSIT_WALLPAPER_OPEN,
new RemoteAnimationAdapterCompat(getWallpaperOpenRunner(), 0, new RemoteAnimationAdapterCompat(getWallpaperOpenRunner(), 0,
@ -370,6 +404,10 @@ public class LauncherAppTransitionManager {
// TODO: App controlled transition for unlock to home TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER // TODO: App controlled transition for unlock to home TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER
new ActivityCompat(mLauncher).registerRemoteAnimations(definition); new ActivityCompat(mLauncher).registerRemoteAnimations(definition);
} catch (NoClassDefFoundError e) {
// Gracefully fall back if the user's platform doesn't have the latest changes
}
}
} }
/** /**
@ -385,11 +423,13 @@ public class LauncherAppTransitionManager {
postAtFrontOfQueueAsynchronously(handler, () -> { postAtFrontOfQueueAsynchronously(handler, () -> {
// We use a separate transition for Overview mode. // We use a separate transition for Overview mode.
if (mLauncher.isInState(LauncherState.OVERVIEW)) { if (mLauncher.isInState(LauncherState.OVERVIEW)) {
setCurrentAnimator(null);
finishedCallback.run(); finishedCallback.run();
return; return;
} }
mAnimator = new AnimatorSet(); mAnimator = new AnimatorSet();
setCurrentAnimator(mAnimator);
mAnimator.addListener(new AnimatorListenerAdapter() { mAnimator.addListener(new AnimatorListenerAdapter() {
@Override @Override
public void onAnimationEnd(Animator animation) { public void onAnimationEnd(Animator animation) {
@ -465,7 +505,9 @@ public class LauncherAppTransitionManager {
private AnimatorSet getLauncherResumeAnimation() { private AnimatorSet getLauncherResumeAnimation() {
if (mLauncher.isInState(LauncherState.ALL_APPS) if (mLauncher.isInState(LauncherState.ALL_APPS)
|| mLauncher.getDeviceProfile().isVerticalBarLayout()) { || mLauncher.getDeviceProfile().isVerticalBarLayout()) {
return getLauncherContentAnimator(true /* show */); AnimatorSet contentAnimator = getLauncherContentAnimator(true /* show */);
contentAnimator.setStartDelay(LAUNCHER_RESUME_START_DELAY);
return contentAnimator;
} else { } else {
AnimatorSet workspaceAnimator = new AnimatorSet(); AnimatorSet workspaceAnimator = new AnimatorSet();
mLauncher.getWorkspace().setTranslationY(mWorkspaceTransY); mLauncher.getWorkspace().setTranslationY(mWorkspaceTransY);
@ -474,7 +516,7 @@ public class LauncherAppTransitionManager {
View.TRANSLATION_Y, mWorkspaceTransY, 0)); View.TRANSLATION_Y, mWorkspaceTransY, 0));
workspaceAnimator.play(ObjectAnimator.ofFloat(mLauncher.getWorkspace(), View.ALPHA, workspaceAnimator.play(ObjectAnimator.ofFloat(mLauncher.getWorkspace(), View.ALPHA,
0, 1f)); 0, 1f));
workspaceAnimator.setStartDelay(150); workspaceAnimator.setStartDelay(LAUNCHER_RESUME_START_DELAY);
workspaceAnimator.setDuration(333); workspaceAnimator.setDuration(333);
workspaceAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN); workspaceAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
@ -489,7 +531,7 @@ public class LauncherAppTransitionManager {
Animator allAppsSlideIn = Animator allAppsSlideIn =
ObjectAnimator.ofFloat(allAppsController, ALL_APPS_PROGRESS, startY, slideEnd); ObjectAnimator.ofFloat(allAppsController, ALL_APPS_PROGRESS, startY, slideEnd);
allAppsSlideIn.setStartDelay(150); allAppsSlideIn.setStartDelay(LAUNCHER_RESUME_START_DELAY);
allAppsSlideIn.setDuration(317); allAppsSlideIn.setDuration(317);
allAppsSlideIn.setInterpolator(Interpolators.FAST_OUT_SLOW_IN); allAppsSlideIn.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
@ -505,6 +547,11 @@ public class LauncherAppTransitionManager {
} }
} }
private boolean hasControlRemoteAppTransitionPermission() {
return mLauncher.checkSelfPermission(CONTROL_REMOTE_APP_TRANSITION_PERMISSION)
== PackageManager.PERMISSION_GRANTED;
}
/** /**
* Helper method that allows us to get interpolated values for embedded * Helper method that allows us to get interpolated values for embedded
* animations with a delay and/or different duration. * animations with a delay and/or different duration.

View File

@ -25,7 +25,6 @@ import android.view.View;
import android.view.View.AccessibilityDelegate; import android.view.View.AccessibilityDelegate;
import com.android.launcher3.Launcher; import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppTransitionManager;
import com.android.launcher3.LauncherStateManager.StateHandler; import com.android.launcher3.LauncherStateManager.StateHandler;
import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.graphics.BitmapRenderer; import com.android.launcher3.graphics.BitmapRenderer;
@ -84,31 +83,4 @@ public class UiFactory {
RecentsView recents = launcher.getOverviewPanel(); RecentsView recents = launcher.getOverviewPanel();
recents.reset(); recents.reset();
} }
private static boolean hasControlRemoteAppTransitionPermission(Launcher launcher) {
return launcher.checkSelfPermission(CONTROL_REMOTE_APP_TRANSITION_PERMISSION)
== PackageManager.PERMISSION_GRANTED;
}
public static Bundle getActivityLaunchOptions(Launcher launcher, View v) {
if (hasControlRemoteAppTransitionPermission(launcher)) {
try {
return new LauncherAppTransitionManager(launcher).getActivityLauncherOptions(v);
} catch (NoClassDefFoundError e) {
// Gracefully fall back to default launch options if the user's platform doesn't
// have the latest changes.
}
}
return launcher.getDefaultActivityLaunchOptions(v);
}
public static void registerRemoteAnimations(Launcher launcher) {
if (hasControlRemoteAppTransitionPermission(launcher)) {
try {
new LauncherAppTransitionManager(launcher).registerRemoteAnimations();
} catch (NoClassDefFoundError e) {
// Gracefully fall back if the user's platform doesn't have the latest changes
}
}
}
} }

View File

@ -92,6 +92,9 @@
<!-- Name of a user event dispatcher class. --> <!-- Name of a user event dispatcher class. -->
<string name="user_event_dispatcher_class" translatable="false"></string> <string name="user_event_dispatcher_class" translatable="false"></string>
<!-- Name of an app transition manager class. -->
<string name="app_transition_manager_class" translatable="false"></string>
<!-- Name of a color extraction implementation class. --> <!-- Name of a color extraction implementation class. -->
<string name="color_extraction_impl_class" translatable="false"></string> <string name="color_extraction_impl_class" translatable="false"></string>

View File

@ -214,6 +214,8 @@ public class Launcher extends BaseActivity
private static final int NEW_APPS_ANIMATION_INACTIVE_TIMEOUT_SECONDS = 5; private static final int NEW_APPS_ANIMATION_INACTIVE_TIMEOUT_SECONDS = 5;
@Thunk static final int NEW_APPS_ANIMATION_DELAY = 500; @Thunk static final int NEW_APPS_ANIMATION_DELAY = 500;
private LauncherAppTransitionManager mAppTransitionManager;
@Thunk Workspace mWorkspace; @Thunk Workspace mWorkspace;
private View mLauncherView; private View mLauncherView;
@Thunk DragLayer mDragLayer; @Thunk DragLayer mDragLayer;
@ -403,8 +405,11 @@ public class Launcher extends BaseActivity
getSystemUiController().updateUiState(SystemUiController.UI_STATE_BASE_WINDOW, getSystemUiController().updateUiState(SystemUiController.UI_STATE_BASE_WINDOW,
Themes.getAttrBoolean(this, R.attr.isWorkspaceDarkText)); Themes.getAttrBoolean(this, R.attr.isWorkspaceDarkText));
mAppTransitionManager = Utilities.getOverrideObject(LauncherAppTransitionManager.class,
this, R.string.app_transition_manager_class);
if (!isInMultiWindowModeCompat()) { if (!isInMultiWindowModeCompat()) {
UiFactory.registerRemoteAnimations(this); mAppTransitionManager.registerRemoteAnimations();
} }
if (mLauncherCallbacks != null) { if (mLauncherCallbacks != null) {
@ -1918,38 +1923,11 @@ public class Launcher extends BaseActivity
} }
} }
public Bundle getDefaultActivityLaunchOptions(View v) {
if (Utilities.ATLEAST_MARSHMALLOW) {
int left = 0, top = 0;
int width = v.getMeasuredWidth(), height = v.getMeasuredHeight();
if (v instanceof BubbleTextView) {
// Launch from center of icon, not entire view
Drawable icon = ((BubbleTextView) v).getIcon();
if (icon != null) {
Rect bounds = icon.getBounds();
left = (width - bounds.width()) / 2;
top = v.getPaddingTop();
width = bounds.width();
height = bounds.height();
}
}
return ActivityOptions.makeClipRevealAnimation(v, left, top, width, height)
.toBundle();
} else if (Utilities.ATLEAST_LOLLIPOP_MR1) {
// On L devices, we use the device default slide-up transition.
// On L MR1 devices, we use a custom version of the slide-up transition which
// doesn't have the delay present in the device default.
return ActivityOptions.makeCustomAnimation(
this, R.anim.task_open_enter, R.anim.no_anim).toBundle();
}
return null;
}
@TargetApi(Build.VERSION_CODES.M) @TargetApi(Build.VERSION_CODES.M)
public Bundle getActivityLaunchOptions(View v, boolean useDefaultLaunchOptions) { public Bundle getActivityLaunchOptions(View v, boolean useDefaultLaunchOptions) {
return useDefaultLaunchOptions return useDefaultLaunchOptions
? getDefaultActivityLaunchOptions(v) ? mAppTransitionManager.getDefaultActivityLaunchOptions(this, v)
: UiFactory.getActivityLaunchOptions(this, v); : mAppTransitionManager.getActivityLaunchOptions(this, v);
} }
public Rect getViewBounds(View v) { public Rect getViewBounds(View v) {

View File

@ -0,0 +1,64 @@
/*
* 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.launcher3;
import android.app.ActivityOptions;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.view.View;
/**
* Manages the opening and closing app transitions from Launcher.
*/
public class LauncherAppTransitionManager {
public Bundle getDefaultActivityLaunchOptions(Launcher launcher, View v) {
if (Utilities.ATLEAST_MARSHMALLOW) {
int left = 0, top = 0;
int width = v.getMeasuredWidth(), height = v.getMeasuredHeight();
if (v instanceof BubbleTextView) {
// Launch from center of icon, not entire view
Drawable icon = ((BubbleTextView) v).getIcon();
if (icon != null) {
Rect bounds = icon.getBounds();
left = (width - bounds.width()) / 2;
top = v.getPaddingTop();
width = bounds.width();
height = bounds.height();
}
}
return ActivityOptions.makeClipRevealAnimation(v, left, top, width, height)
.toBundle();
} else if (Utilities.ATLEAST_LOLLIPOP_MR1) {
// On L devices, we use the device default slide-up transition.
// On L MR1 devices, we use a custom version of the slide-up transition which
// doesn't have the delay present in the device default.
return ActivityOptions.makeCustomAnimation(launcher, R.anim.task_open_enter,
R.anim.no_anim).toBundle();
}
return null;
}
public Bundle getActivityLaunchOptions(Launcher launcher, View v) {
return getDefaultActivityLaunchOptions(launcher, v);
}
public void registerRemoteAnimations() {
}
}

View File

@ -61,10 +61,4 @@ public class UiFactory {
} }
public static void resetOverview(Launcher launcher) { } public static void resetOverview(Launcher launcher) { }
public static Bundle getActivityLaunchOptions(Launcher launcher, View v) {
return launcher.getDefaultActivityLaunchOptions(v);
}
public static void registerRemoteAnimations(Launcher launcher) { }
} }