/* * 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 static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_NONE; import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SPLASH_SCREEN; import static com.android.launcher3.BaseActivity.INVISIBLE_ALL; import static com.android.launcher3.BaseActivity.INVISIBLE_BY_APP_TRANSITIONS; import static com.android.launcher3.BaseActivity.INVISIBLE_BY_PENDING_FLAGS; import static com.android.launcher3.BaseActivity.PENDING_INVISIBLE_BY_WALLPAPER_ANIMATION; import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY; import static com.android.launcher3.LauncherAnimUtils.VIEW_BACKGROUND_COLOR; import static com.android.launcher3.LauncherState.ALL_APPS; import static com.android.launcher3.LauncherState.BACKGROUND_APP; import static com.android.launcher3.LauncherState.OVERVIEW; import static com.android.launcher3.Utilities.postAsyncCallback; import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE; import static com.android.launcher3.anim.Interpolators.DEACCEL_1_5; import static com.android.launcher3.anim.Interpolators.DEACCEL_1_7; import static com.android.launcher3.anim.Interpolators.EXAGGERATED_EASE; import static com.android.launcher3.anim.Interpolators.LINEAR; import static com.android.launcher3.config.FeatureFlags.ENABLE_SCRIM_FOR_APP_LAUNCH; import static com.android.launcher3.config.FeatureFlags.KEYGUARD_ANIMATION; import static com.android.launcher3.config.FeatureFlags.SEPARATE_RECENTS_ACTIVITY; import static com.android.launcher3.dragndrop.DragLayer.ALPHA_INDEX_TRANSITIONS; import static com.android.launcher3.statehandlers.DepthController.DEPTH; import static com.android.launcher3.util.DisplayController.getSingleFrameMs; import static com.android.quickstep.TaskUtils.taskIsATargetWithMode; import static com.android.quickstep.TaskViewUtils.findTaskViewToLaunch; import static com.android.systemui.shared.system.QuickStepContract.getWindowCornerRadius; import static com.android.systemui.shared.system.QuickStepContract.supportsRoundedCornersOnWindows; import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING; import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_OPENING; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.animation.ValueAnimator; import android.content.Context; import android.content.pm.PackageManager; import android.content.res.Resources; import android.graphics.Color; import android.graphics.Matrix; import android.graphics.Point; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.os.CancellationSignal; import android.os.Handler; import android.os.Looper; import android.os.SystemProperties; import android.util.Pair; import android.util.Size; import android.view.SurfaceControl; import android.view.View; import android.view.ViewRootImpl; import android.view.ViewTreeObserver; import android.view.animation.Interpolator; import android.view.animation.PathInterpolator; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.core.graphics.ColorUtils; import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener; import com.android.launcher3.LauncherAnimationRunner.RemoteAnimationFactory; import com.android.launcher3.anim.AnimationSuccessListener; import com.android.launcher3.dragndrop.DragLayer; import com.android.launcher3.icons.FastBitmapDrawable; import com.android.launcher3.shortcuts.DeepShortcutView; import com.android.launcher3.statehandlers.DepthController; import com.android.launcher3.util.ActivityOptionsWrapper; import com.android.launcher3.util.MultiValueAlpha.AlphaProperty; import com.android.launcher3.util.RunnableList; import com.android.launcher3.util.Themes; import com.android.launcher3.views.FloatingIconView; import com.android.launcher3.views.ScrimView; import com.android.launcher3.widget.LauncherAppWidgetHostView; import com.android.quickstep.RemoteAnimationTargets; import com.android.quickstep.SystemUiProxy; import com.android.quickstep.TaskViewUtils; import com.android.quickstep.util.MultiValueUpdateListener; import com.android.quickstep.util.RemoteAnimationProvider; import com.android.quickstep.util.SurfaceTransactionApplier; import com.android.quickstep.util.WorkspaceRevealAnim; import com.android.quickstep.views.FloatingWidgetView; import com.android.quickstep.views.RecentsView; import com.android.systemui.shared.system.ActivityCompat; import com.android.systemui.shared.system.ActivityOptionsCompat; import com.android.systemui.shared.system.BlurUtils; import com.android.systemui.shared.system.InteractionJankMonitorWrapper; import com.android.systemui.shared.system.QuickStepContract; import com.android.systemui.shared.system.RemoteAnimationAdapterCompat; import com.android.systemui.shared.system.RemoteAnimationDefinitionCompat; import com.android.systemui.shared.system.RemoteAnimationRunnerCompat; import com.android.systemui.shared.system.RemoteAnimationTargetCompat; import com.android.systemui.shared.system.RemoteTransitionCompat; import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams; import com.android.systemui.shared.system.WindowManagerWrapper; import com.android.wm.shell.startingsurface.IStartingWindowListener; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; /** * Manages the opening and closing app transitions from Launcher */ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener { private static final String TAG = "QuickstepTransition"; private static final boolean ENABLE_SHELL_STARTING_SURFACE = SystemProperties.getBoolean("persist.debug.shell_starting_surface", true); /** Duration of status bar animations. */ public static final int STATUS_BAR_TRANSITION_DURATION = 120; /** * Since our animations decelerate heavily when finishing, we want to start status bar * animations x ms before the ending. */ public static final int STATUS_BAR_TRANSITION_PRE_DELAY = 96; private static final String CONTROL_REMOTE_APP_TRANSITION_PERMISSION = "android.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS"; private static final long APP_LAUNCH_DURATION = 450; // Use a shorter duration for x or y translation to create a curve effect private static final long APP_LAUNCH_CURVED_DURATION = 250; private static final long APP_LAUNCH_ALPHA_DURATION = 50; private static final long APP_LAUNCH_ALPHA_START_DELAY = 25; // We scale the durations for the downward app launch animations (minus the scale animation). private static final float APP_LAUNCH_DOWN_DUR_SCALE_FACTOR = 0.8f; private static final long APP_LAUNCH_DOWN_DURATION = (long) (APP_LAUNCH_DURATION * APP_LAUNCH_DOWN_DUR_SCALE_FACTOR); private static final long APP_LAUNCH_DOWN_CURVED_DURATION = (long) (APP_LAUNCH_CURVED_DURATION * APP_LAUNCH_DOWN_DUR_SCALE_FACTOR); private static final long APP_LAUNCH_ALPHA_DOWN_DURATION = (long) (APP_LAUNCH_ALPHA_DURATION * APP_LAUNCH_DOWN_DUR_SCALE_FACTOR); public static final int ANIMATION_NAV_FADE_IN_DURATION = 266; public static final int ANIMATION_NAV_FADE_OUT_DURATION = 133; public static final long ANIMATION_DELAY_NAV_FADE_IN = APP_LAUNCH_DURATION - ANIMATION_NAV_FADE_IN_DURATION; public static final Interpolator NAV_FADE_IN_INTERPOLATOR = new PathInterpolator(0f, 0f, 0f, 1f); public static final Interpolator NAV_FADE_OUT_INTERPOLATOR = new PathInterpolator(0.2f, 0f, 1f, 1f); private static final long CROP_DURATION = 375; private static final long RADIUS_DURATION = 375; public static final int RECENTS_LAUNCH_DURATION = 336; private static final int LAUNCHER_RESUME_START_DELAY = 100; private static final int CLOSING_TRANSITION_DURATION_MS = 250; public static final int CONTENT_ALPHA_DURATION = 217; protected static final int CONTENT_SCALE_DURATION = 350; protected static final int CONTENT_SCRIM_DURATION = 350; private static final int MAX_NUM_TASKS = 5; // Cross-fade duration between App Widget and App private static final int WIDGET_CROSSFADE_DURATION_MILLIS = 125; protected final BaseQuickstepLauncher mLauncher; private final DragLayer mDragLayer; private final AlphaProperty mDragLayerAlpha; final Handler mHandler; private final float mContentScale; private final float mClosingWindowTransY; private final float mMaxShadowRadius; private final StartingWindowListener mStartingWindowListener = new StartingWindowListener(); private DeviceProfile mDeviceProfile; private RemoteAnimationProvider mRemoteAnimationProvider; // Strong refs to runners which are cleared when the launcher activity is destroyed private RemoteAnimationFactory mWallpaperOpenRunner; private RemoteAnimationFactory mAppLaunchRunner; private RemoteAnimationFactory mKeyguardGoingAwayRunner; private RemoteAnimationFactory mWallpaperOpenTransitionRunner; private RemoteTransitionCompat mLauncherOpenTransition; private final AnimatorListenerAdapter mForceInvisibleListener = new AnimatorListenerAdapter() { @Override public void onAnimationStart(Animator animation) { mLauncher.addForceInvisibleFlag(INVISIBLE_BY_APP_TRANSITIONS); } @Override public void onAnimationEnd(Animator animation) { mLauncher.clearForceInvisibleFlag(INVISIBLE_BY_APP_TRANSITIONS); } }; // Pairs of window starting type and starting window background color for starting tasks // Will never be larger than MAX_NUM_TASKS private LinkedHashMap> mTaskStartParams; public QuickstepTransitionManager(Context context) { mLauncher = Launcher.cast(Launcher.getLauncher(context)); mDragLayer = mLauncher.getDragLayer(); mDragLayerAlpha = mDragLayer.getAlphaProperty(ALPHA_INDEX_TRANSITIONS); mHandler = new Handler(Looper.getMainLooper()); mDeviceProfile = mLauncher.getDeviceProfile(); Resources res = mLauncher.getResources(); mContentScale = res.getFloat(R.dimen.content_scale); mClosingWindowTransY = res.getDimensionPixelSize(R.dimen.closing_window_trans_y); mMaxShadowRadius = res.getDimensionPixelSize(R.dimen.max_shadow_radius); mLauncher.addOnDeviceProfileChangeListener(this); if (supportsSSplashScreen()) { mTaskStartParams = new LinkedHashMap>(MAX_NUM_TASKS) { @Override protected boolean removeEldestEntry(Entry> entry) { return size() > MAX_NUM_TASKS; } }; mStartingWindowListener.setTransitionManager(this); SystemUiProxy.INSTANCE.get(mLauncher).setStartingWindowListener( mStartingWindowListener); } } @Override public void onDeviceProfileChanged(DeviceProfile dp) { mDeviceProfile = dp; } /** * @return ActivityOptions with remote animations that controls how the window of the opening * targets are displayed. */ public ActivityOptionsWrapper getActivityLaunchOptions(View v) { boolean fromRecents = isLaunchingFromRecents(v, null /* targets */); RunnableList onEndCallback = new RunnableList(); mAppLaunchRunner = new AppLaunchAnimationRunner(v, onEndCallback); RemoteAnimationRunnerCompat runner = new LauncherAnimationRunner( mHandler, mAppLaunchRunner, true /* startAtFrontOfQueue */); // Note that this duration is a guess as we do not know if the animation will be a // recents launch or not for sure until we know the opening app targets. long duration = fromRecents ? RECENTS_LAUNCH_DURATION : APP_LAUNCH_DURATION; long statusBarTransitionDelay = duration - STATUS_BAR_TRANSITION_DURATION - STATUS_BAR_TRANSITION_PRE_DELAY; RemoteAnimationAdapterCompat adapterCompat = new RemoteAnimationAdapterCompat(runner, duration, statusBarTransitionDelay); return new ActivityOptionsWrapper( ActivityOptionsCompat.makeRemoteAnimation(adapterCompat), onEndCallback); } /** * Whether the launch is a recents app transition and we should do a launch animation * from the recents view. Note that if the remote animation targets are not provided, this * may not always be correct as we may resolve the opening app to a task when the animation * starts. * * @param v the view to launch from * @param targets apps that are opening/closing * @return true if the app is launching from recents, false if it most likely is not */ protected boolean isLaunchingFromRecents(@NonNull View v, @Nullable RemoteAnimationTargetCompat[] targets) { return mLauncher.getStateManager().getState().overviewUi && findTaskViewToLaunch(mLauncher.getOverviewPanel(), v, targets) != null; } /** * Composes the animations for a launch from the recents list. * * @param anim the animator set to add to * @param v the launching view * @param appTargets the apps that are opening/closing * @param launcherClosing true if the launcher app is closing */ protected void composeRecentsLaunchAnimator(@NonNull AnimatorSet anim, @NonNull View v, @NonNull RemoteAnimationTargetCompat[] appTargets, @NonNull RemoteAnimationTargetCompat[] wallpaperTargets, @NonNull RemoteAnimationTargetCompat[] nonAppTargets, boolean launcherClosing) { TaskViewUtils.composeRecentsLaunchAnimator(anim, v, appTargets, wallpaperTargets, nonAppTargets, launcherClosing, mLauncher.getStateManager(), mLauncher.getOverviewPanel(), mLauncher.getDepthController()); } private boolean areAllTargetsTranslucent(@NonNull RemoteAnimationTargetCompat[] targets) { boolean isAllOpeningTargetTrs = true; for (int i = 0; i < targets.length; i++) { RemoteAnimationTargetCompat target = targets[i]; if (target.mode == MODE_OPENING) { isAllOpeningTargetTrs &= target.isTranslucent; } if (!isAllOpeningTargetTrs) break; } return isAllOpeningTargetTrs; } /** * Compose the animations for a launch from the app icon. * * @param anim the animation to add to * @param v the launching view with the icon * @param appTargets the list of opening/closing apps * @param launcherClosing true if launcher is closing */ private void composeIconLaunchAnimator(@NonNull AnimatorSet anim, @NonNull View v, @NonNull RemoteAnimationTargetCompat[] appTargets, @NonNull RemoteAnimationTargetCompat[] wallpaperTargets, @NonNull RemoteAnimationTargetCompat[] nonAppTargets, boolean launcherClosing) { // Set the state animation first so that any state listeners are called // before our internal listeners. mLauncher.getStateManager().setCurrentAnimation(anim); final int rotationChange = getRotationChange(appTargets); // Note: the targetBounds are relative to the launcher int startDelay = getSingleFrameMs(mLauncher); Rect windowTargetBounds = getWindowTargetBounds(appTargets, rotationChange); Animator windowAnimator = getOpeningWindowAnimators(v, appTargets, wallpaperTargets, nonAppTargets, windowTargetBounds, areAllTargetsTranslucent(appTargets), rotationChange); windowAnimator.setStartDelay(startDelay); anim.play(windowAnimator); if (launcherClosing) { // Delay animation by a frame to avoid jank. Pair launcherContentAnimator = getLauncherContentAnimator(true /* isAppOpening */, startDelay); anim.play(launcherContentAnimator.first); anim.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { launcherContentAnimator.second.run(); } }); } else { anim.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationStart(Animator animation) { mLauncher.addOnResumeCallback(() -> ObjectAnimator.ofFloat(mLauncher.getDepthController(), DEPTH, mLauncher.getStateManager().getState().getDepth( mLauncher)).start()); } }); } } private void composeWidgetLaunchAnimator( @NonNull AnimatorSet anim, @NonNull LauncherAppWidgetHostView v, @NonNull RemoteAnimationTargetCompat[] appTargets, @NonNull RemoteAnimationTargetCompat[] wallpaperTargets, @NonNull RemoteAnimationTargetCompat[] nonAppTargets) { mLauncher.getStateManager().setCurrentAnimation(anim); Rect windowTargetBounds = getWindowTargetBounds(appTargets, getRotationChange(appTargets)); anim.play(getOpeningWindowAnimatorsForWidget(v, appTargets, wallpaperTargets, nonAppTargets, windowTargetBounds, areAllTargetsTranslucent(appTargets))); anim.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationStart(Animator animation) { mLauncher.addOnResumeCallback(() -> ObjectAnimator.ofFloat(mLauncher.getDepthController(), DEPTH, mLauncher.getStateManager().getState().getDepth( mLauncher)).start()); } }); } /** * Return the window bounds of the opening target. * In multiwindow mode, we need to get the final size of the opening app window target to help * figure out where the floating view should animate to. */ private Rect getWindowTargetBounds(@NonNull RemoteAnimationTargetCompat[] appTargets, int rotationChange) { RemoteAnimationTargetCompat target = null; for (RemoteAnimationTargetCompat t : appTargets) { if (t.mode != MODE_OPENING) continue; target = t; break; } if (target == null) return new Rect(0, 0, mDeviceProfile.widthPx, mDeviceProfile.heightPx); final Rect bounds = new Rect(target.screenSpaceBounds); if (target.localBounds != null) { bounds.set(target.localBounds); } else { bounds.offsetTo(target.position.x, target.position.y); } if (rotationChange != 0) { if ((rotationChange % 2) == 1) { // undoing rotation, so our "original" parent size is actually flipped Utilities.rotateBounds(bounds, mDeviceProfile.heightPx, mDeviceProfile.widthPx, 4 - rotationChange); } else { Utilities.rotateBounds(bounds, mDeviceProfile.widthPx, mDeviceProfile.heightPx, 4 - rotationChange); } } return bounds; } public void setRemoteAnimationProvider(final RemoteAnimationProvider animationProvider, CancellationSignal cancellationSignal) { mRemoteAnimationProvider = animationProvider; cancellationSignal.setOnCancelListener(() -> { if (animationProvider == mRemoteAnimationProvider) { mRemoteAnimationProvider = null; } }); } /** * Content is everything on screen except the background and the floating view (if any). * * @param isAppOpening True when this is called when an app is opening. * False when this is called when an app is closing. * @param startDelay Start delay duration. */ private Pair getLauncherContentAnimator(boolean isAppOpening, int startDelay) { AnimatorSet launcherAnimator = new AnimatorSet(); Runnable endListener; float[] alphas = isAppOpening ? new float[]{1, 0} : new float[]{0, 1}; float[] scales = isAppOpening ? new float[]{1, mContentScale} : new float[]{mContentScale, 1}; if (mLauncher.isInState(ALL_APPS)) { // All Apps in portrait mode is full screen, so we only animate AllAppsContainerView. final View appsView = mLauncher.getAppsView(); final float startAlpha = appsView.getAlpha(); final float startScale = SCALE_PROPERTY.get(appsView); appsView.setAlpha(alphas[0]); SCALE_PROPERTY.set(appsView, scales[0]); ObjectAnimator alpha = ObjectAnimator.ofFloat(appsView, View.ALPHA, alphas); alpha.setDuration(CONTENT_ALPHA_DURATION); alpha.setInterpolator(LINEAR); appsView.setLayerType(View.LAYER_TYPE_HARDWARE, null); alpha.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { appsView.setLayerType(View.LAYER_TYPE_NONE, null); } }); ObjectAnimator scale = ObjectAnimator.ofFloat(appsView, SCALE_PROPERTY, scales); scale.setInterpolator(AGGRESSIVE_EASE); scale.setDuration(CONTENT_SCALE_DURATION); launcherAnimator.play(alpha); launcherAnimator.play(scale); endListener = () -> { appsView.setAlpha(startAlpha); SCALE_PROPERTY.set(appsView, startScale); appsView.setLayerType(View.LAYER_TYPE_NONE, null); }; } else if (mLauncher.isInState(OVERVIEW)) { endListener = composeViewContentAnimator(launcherAnimator, alphas, scales); } else { List viewsToAnimate = new ArrayList<>(); Workspace workspace = mLauncher.getWorkspace(); workspace.forEachVisiblePage( view -> viewsToAnimate.add(((CellLayout) view).getShortcutsAndWidgets())); viewsToAnimate.add(mLauncher.getHotseat()); viewsToAnimate.forEach(view -> { view.setLayerType(View.LAYER_TYPE_HARDWARE, null); ObjectAnimator scaleAnim = ObjectAnimator.ofFloat(view, SCALE_PROPERTY, scales) .setDuration(CONTENT_SCALE_DURATION); scaleAnim.setInterpolator(DEACCEL_1_5); launcherAnimator.play(scaleAnim); }); final boolean scrimEnabled = ENABLE_SCRIM_FOR_APP_LAUNCH.get(); if (scrimEnabled) { int scrimColor = Themes.getAttrColor(mLauncher, R.attr.overviewScrimColor); int scrimColorTrans = ColorUtils.setAlphaComponent(scrimColor, 0); int[] colors = isAppOpening ? new int[]{scrimColorTrans, scrimColor} : new int[]{scrimColor, scrimColorTrans}; ScrimView scrimView = mLauncher.getScrimView(); if (scrimView.getBackground() instanceof ColorDrawable) { scrimView.setBackgroundColor(colors[0]); ObjectAnimator scrim = ObjectAnimator.ofArgb(scrimView, VIEW_BACKGROUND_COLOR, colors); scrim.setDuration(CONTENT_SCRIM_DURATION); scrim.setInterpolator(DEACCEL_1_5); launcherAnimator.play(scrim); } } // Pause page indicator animations as they lead to layer trashing. mLauncher.getWorkspace().getPageIndicator().pauseAnimations(); endListener = () -> { viewsToAnimate.forEach(view -> { SCALE_PROPERTY.set(view, 1f); view.setLayerType(View.LAYER_TYPE_NONE, null); }); if (scrimEnabled) { mLauncher.getScrimView().setBackgroundColor(Color.TRANSPARENT); } mLauncher.getWorkspace().getPageIndicator().skipAnimationsToEnd(); }; } launcherAnimator.setStartDelay(startDelay); return new Pair<>(launcherAnimator, endListener); } /** * Compose recents view alpha and translation Y animation when launcher opens/closes apps. * * @param anim the animator set to add to * @param alphas the alphas to animate to over time * @param scales the scale values to animator to over time * @return listener to run when the animation ends */ protected Runnable composeViewContentAnimator(@NonNull AnimatorSet anim, float[] alphas, float[] scales) { RecentsView overview = mLauncher.getOverviewPanel(); ObjectAnimator alpha = ObjectAnimator.ofFloat(overview, RecentsView.CONTENT_ALPHA, alphas); alpha.setDuration(CONTENT_ALPHA_DURATION); alpha.setInterpolator(LINEAR); anim.play(alpha); overview.setFreezeViewVisibility(true); ObjectAnimator scaleAnim = ObjectAnimator.ofFloat(overview, SCALE_PROPERTY, scales); scaleAnim.setInterpolator(AGGRESSIVE_EASE); scaleAnim.setDuration(CONTENT_SCALE_DURATION); anim.play(scaleAnim); return () -> { overview.setFreezeViewVisibility(false); SCALE_PROPERTY.set(overview, 1f); mLauncher.getStateManager().reapplyState(); }; } /** * @return Animator that controls the window of the opening targets from app icons. */ private Animator getOpeningWindowAnimators(View v, RemoteAnimationTargetCompat[] appTargets, RemoteAnimationTargetCompat[] wallpaperTargets, RemoteAnimationTargetCompat[] nonAppTargets, Rect windowTargetBounds, boolean appTargetsAreTranslucent, int rotationChange) { RectF launcherIconBounds = new RectF(); FloatingIconView floatingView = FloatingIconView.getFloatingIconView(mLauncher, v, !appTargetsAreTranslucent, launcherIconBounds, true /* isOpening */); Rect crop = new Rect(); Matrix matrix = new Matrix(); RemoteAnimationTargets openingTargets = new RemoteAnimationTargets(appTargets, wallpaperTargets, nonAppTargets, MODE_OPENING); SurfaceTransactionApplier surfaceApplier = new SurfaceTransactionApplier(floatingView); openingTargets.addReleaseCheck(surfaceApplier); RemoteAnimationTargetCompat navBarTarget = openingTargets.getNavBarRemoteAnimationTarget(); int[] dragLayerBounds = new int[2]; mDragLayer.getLocationOnScreen(dragLayerBounds); final boolean hasSplashScreen; if (supportsSSplashScreen()) { int taskId = openingTargets.getFirstAppTargetTaskId(); Pair defaultParams = Pair.create(STARTING_WINDOW_TYPE_NONE, 0); Pair taskParams = mTaskStartParams.getOrDefault(taskId, defaultParams); mTaskStartParams.remove(taskId); hasSplashScreen = taskParams.first == STARTING_WINDOW_TYPE_SPLASH_SCREEN; } else { hasSplashScreen = false; } AnimOpenProperties prop = new AnimOpenProperties(mLauncher.getResources(), mDeviceProfile, windowTargetBounds, launcherIconBounds, v, dragLayerBounds[0], dragLayerBounds[1], hasSplashScreen, floatingView.isDifferentFromAppIcon()); int left = (int) (prop.cropCenterXStart - prop.cropWidthStart / 2); int top = (int) (prop.cropCenterYStart - prop.cropHeightStart / 2); int right = (int) (left + prop.cropWidthStart); int bottom = (int) (top + prop.cropHeightStart); // Set the crop here so we can calculate the corner radius below. crop.set(left, top, right, bottom); RectF floatingIconBounds = new RectF(); RectF tmpRectF = new RectF(); Point tmpPos = new Point(); AnimatorSet animatorSet = new AnimatorSet(); ValueAnimator appAnimator = ValueAnimator.ofFloat(0, 1); appAnimator.setDuration(APP_LAUNCH_DURATION); appAnimator.setInterpolator(LINEAR); appAnimator.addListener(floatingView); appAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { if (v instanceof BubbleTextView) { ((BubbleTextView) v).setStayPressed(false); } openingTargets.release(); } }); final float initialWindowRadius = supportsRoundedCornersOnWindows(mLauncher.getResources()) ? Math.max(crop.width(), crop.height()) / 2f : 0f; final float finalWindowRadius = mDeviceProfile.isMultiWindowMode ? 0 : getWindowCornerRadius(mLauncher.getResources()); final float finalShadowRadius = appTargetsAreTranslucent ? 0 : mMaxShadowRadius; MultiValueUpdateListener listener = new MultiValueUpdateListener() { FloatProp mDx = new FloatProp(0, prop.dX, 0, prop.xDuration, AGGRESSIVE_EASE); FloatProp mDy = new FloatProp(0, prop.dY, 0, prop.yDuration, AGGRESSIVE_EASE); FloatProp mIconScaleToFitScreen = new FloatProp(prop.initialAppIconScale, prop.finalAppIconScale, 0, APP_LAUNCH_DURATION, EXAGGERATED_EASE); FloatProp mIconAlpha = new FloatProp(prop.iconAlphaStart, 0f, APP_LAUNCH_ALPHA_START_DELAY, prop.alphaDuration, LINEAR); FloatProp mWindowRadius = new FloatProp(initialWindowRadius, finalWindowRadius, 0, RADIUS_DURATION, EXAGGERATED_EASE); FloatProp mShadowRadius = new FloatProp(0, finalShadowRadius, 0, APP_LAUNCH_DURATION, EXAGGERATED_EASE); FloatProp mCropRectCenterX = new FloatProp(prop.cropCenterXStart, prop.cropCenterXEnd, 0, CROP_DURATION, EXAGGERATED_EASE); FloatProp mCropRectCenterY = new FloatProp(prop.cropCenterYStart, prop.cropCenterYEnd, 0, CROP_DURATION, EXAGGERATED_EASE); FloatProp mCropRectWidth = new FloatProp(prop.cropWidthStart, prop.cropWidthEnd, 0, CROP_DURATION, EXAGGERATED_EASE); FloatProp mCropRectHeight = new FloatProp(prop.cropHeightStart, prop.cropHeightEnd, 0, CROP_DURATION, EXAGGERATED_EASE); FloatProp mNavFadeOut = new FloatProp(1f, 0f, 0, ANIMATION_NAV_FADE_OUT_DURATION, NAV_FADE_OUT_INTERPOLATOR); FloatProp mNavFadeIn = new FloatProp(0f, 1f, ANIMATION_DELAY_NAV_FADE_IN, ANIMATION_NAV_FADE_IN_DURATION, NAV_FADE_IN_INTERPOLATOR); @Override public void onUpdate(float percent, boolean initOnly) { // Calculate the size of the scaled icon. float iconWidth = launcherIconBounds.width() * mIconScaleToFitScreen.value; float iconHeight = launcherIconBounds.height() * mIconScaleToFitScreen.value; int left = (int) (mCropRectCenterX.value - mCropRectWidth.value / 2); int top = (int) (mCropRectCenterY.value - mCropRectHeight.value / 2); int right = (int) (left + mCropRectWidth.value); int bottom = (int) (top + mCropRectHeight.value); crop.set(left, top, right, bottom); final int windowCropWidth = crop.width(); final int windowCropHeight = crop.height(); if (rotationChange != 0) { Utilities.rotateBounds(crop, mDeviceProfile.widthPx, mDeviceProfile.heightPx, rotationChange); } // Scale the size of the icon to match the size of the window crop. float scaleX = iconWidth / windowCropWidth; float scaleY = iconHeight / windowCropHeight; float scale = Math.min(1f, Math.max(scaleX, scaleY)); float scaledCropWidth = windowCropWidth * scale; float scaledCropHeight = windowCropHeight * scale; float offsetX = (scaledCropWidth - iconWidth) / 2; float offsetY = (scaledCropHeight - iconHeight) / 2; // Calculate the window position to match the icon position. tmpRectF.set(launcherIconBounds); tmpRectF.offset(dragLayerBounds[0], dragLayerBounds[1]); tmpRectF.offset(mDx.value, mDy.value); Utilities.scaleRectFAboutCenter(tmpRectF, mIconScaleToFitScreen.value); float windowTransX0 = tmpRectF.left - offsetX - crop.left * scale; float windowTransY0 = tmpRectF.top - offsetY - crop.top * scale; // Calculate the icon position. floatingIconBounds.set(launcherIconBounds); floatingIconBounds.offset(mDx.value, mDy.value); Utilities.scaleRectFAboutCenter(floatingIconBounds, mIconScaleToFitScreen.value); floatingIconBounds.left -= offsetX; floatingIconBounds.top -= offsetY; floatingIconBounds.right += offsetX; floatingIconBounds.bottom += offsetY; if (initOnly) { // For the init pass, we want full alpha since the window is not yet ready. floatingView.update(1f, 255, floatingIconBounds, percent, 0f, mWindowRadius.value * scale, true /* isOpening */); return; } ArrayList params = new ArrayList<>(); for (int i = appTargets.length - 1; i >= 0; i--) { RemoteAnimationTargetCompat target = appTargets[i]; SurfaceParams.Builder builder = new SurfaceParams.Builder(target.leash); if (target.mode == MODE_OPENING) { matrix.setScale(scale, scale); if (rotationChange == 1) { matrix.postTranslate(windowTransY0, mDeviceProfile.widthPx - (windowTransX0 + scaledCropWidth)); } else if (rotationChange == 2) { matrix.postTranslate( mDeviceProfile.widthPx - (windowTransX0 + scaledCropWidth), mDeviceProfile.heightPx - (windowTransY0 + scaledCropHeight)); } else if (rotationChange == 3) { matrix.postTranslate( mDeviceProfile.heightPx - (windowTransY0 + scaledCropHeight), windowTransX0); } else { matrix.postTranslate(windowTransX0, windowTransY0); } floatingView.update(mIconAlpha.value, 255, floatingIconBounds, percent, 0f, mWindowRadius.value * scale, true /* isOpening */); builder.withMatrix(matrix) .withWindowCrop(crop) .withAlpha(1f - mIconAlpha.value) .withCornerRadius(mWindowRadius.value) .withShadowRadius(mShadowRadius.value); } else if (target.mode == MODE_CLOSING) { if (target.localBounds != null) { final Rect localBounds = target.localBounds; tmpPos.set(target.localBounds.left, target.localBounds.top); } else { tmpPos.set(target.position.x, target.position.y); } final Rect crop = new Rect(target.screenSpaceBounds); crop.offsetTo(0, 0); if ((rotationChange % 2) == 1) { int tmp = crop.right; crop.right = crop.bottom; crop.bottom = tmp; tmp = tmpPos.x; tmpPos.x = tmpPos.y; tmpPos.y = tmp; } matrix.setTranslate(tmpPos.x, tmpPos.y); builder.withMatrix(matrix) .withWindowCrop(crop) .withAlpha(1f); } params.add(builder.build()); } if (navBarTarget != null) { final SurfaceParams.Builder navBuilder = new SurfaceParams.Builder(navBarTarget.leash); if (mNavFadeIn.value > mNavFadeIn.getStartValue()) { matrix.setScale(scale, scale); matrix.postTranslate(windowTransX0, windowTransY0); navBuilder.withMatrix(matrix) .withWindowCrop(crop) .withAlpha(mNavFadeIn.value); } else { navBuilder.withAlpha(mNavFadeOut.value); } params.add(navBuilder.build()); } surfaceApplier.scheduleApply(params.toArray(new SurfaceParams[params.size()])); } }; appAnimator.addUpdateListener(listener); // Since we added a start delay, call update here to init the FloatingIconView properly. listener.onUpdate(0, true /* initOnly */); animatorSet.playTogether(appAnimator, getBackgroundAnimator(appTargets)); return animatorSet; } private Animator getOpeningWindowAnimatorsForWidget(LauncherAppWidgetHostView v, RemoteAnimationTargetCompat[] appTargets, RemoteAnimationTargetCompat[] wallpaperTargets, RemoteAnimationTargetCompat[] nonAppTargets, Rect windowTargetBounds, boolean appTargetsAreTranslucent) { final RectF widgetBackgroundBounds = new RectF(); final Rect appWindowCrop = new Rect(); final Matrix matrix = new Matrix(); RemoteAnimationTargets openingTargets = new RemoteAnimationTargets(appTargets, wallpaperTargets, nonAppTargets, MODE_OPENING); RemoteAnimationTargetCompat openingTarget = openingTargets.getFirstAppTarget(); int fallbackBackgroundColor = 0; if (openingTarget != null && supportsSSplashScreen()) { fallbackBackgroundColor = mTaskStartParams.containsKey(openingTarget.taskId) ? mTaskStartParams.get(openingTarget.taskId).second : 0; mTaskStartParams.remove(openingTarget.taskId); } if (fallbackBackgroundColor == 0) { fallbackBackgroundColor = FloatingWidgetView.getDefaultBackgroundColor(mLauncher, openingTarget); } final float finalWindowRadius = mDeviceProfile.isMultiWindowMode ? 0 : getWindowCornerRadius(mLauncher.getResources()); final FloatingWidgetView floatingView = FloatingWidgetView.getFloatingWidgetView(mLauncher, v, widgetBackgroundBounds, new Size(windowTargetBounds.width(), windowTargetBounds.height()), finalWindowRadius, appTargetsAreTranslucent, fallbackBackgroundColor); final float initialWindowRadius = supportsRoundedCornersOnWindows(mLauncher.getResources()) ? floatingView.getInitialCornerRadius() : 0; SurfaceTransactionApplier surfaceApplier = new SurfaceTransactionApplier(floatingView); openingTargets.addReleaseCheck(surfaceApplier); RemoteAnimationTargetCompat navBarTarget = openingTargets.getNavBarRemoteAnimationTarget(); AnimatorSet animatorSet = new AnimatorSet(); ValueAnimator appAnimator = ValueAnimator.ofFloat(0, 1); appAnimator.setDuration(APP_LAUNCH_DURATION); appAnimator.setInterpolator(LINEAR); appAnimator.addListener(floatingView); appAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { openingTargets.release(); } }); floatingView.setFastFinishRunnable(animatorSet::end); appAnimator.addUpdateListener(new MultiValueUpdateListener() { float mAppWindowScale = 1; final FloatProp mWidgetForegroundAlpha = new FloatProp(1 /* start */, 0 /* end */, 0 /* delay */, WIDGET_CROSSFADE_DURATION_MILLIS / 2 /* duration */, LINEAR); final FloatProp mWidgetFallbackBackgroundAlpha = new FloatProp(0 /* start */, 1 /* end */, 0 /* delay */, 75 /* duration */, LINEAR); final FloatProp mPreviewAlpha = new FloatProp(0 /* start */, 1 /* end */, WIDGET_CROSSFADE_DURATION_MILLIS / 2 /* delay */, WIDGET_CROSSFADE_DURATION_MILLIS / 2 /* duration */, LINEAR); final FloatProp mWindowRadius = new FloatProp(initialWindowRadius, finalWindowRadius, 0 /* start */, RADIUS_DURATION, LINEAR); final FloatProp mCornerRadiusProgress = new FloatProp(0, 1, 0, RADIUS_DURATION, LINEAR); // Window & widget background positioning bounds final FloatProp mDx = new FloatProp(widgetBackgroundBounds.centerX(), windowTargetBounds.centerX(), 0 /* delay */, APP_LAUNCH_CURVED_DURATION, EXAGGERATED_EASE); final FloatProp mDy = new FloatProp(widgetBackgroundBounds.centerY(), windowTargetBounds.centerY(), 0 /* delay */, APP_LAUNCH_DURATION, EXAGGERATED_EASE); final FloatProp mWidth = new FloatProp(widgetBackgroundBounds.width(), windowTargetBounds.width(), 0 /* delay */, APP_LAUNCH_DURATION, EXAGGERATED_EASE); final FloatProp mHeight = new FloatProp(widgetBackgroundBounds.height(), windowTargetBounds.height(), 0 /* delay */, APP_LAUNCH_DURATION, EXAGGERATED_EASE); final FloatProp mNavFadeOut = new FloatProp(1f, 0f, 0, ANIMATION_NAV_FADE_OUT_DURATION, NAV_FADE_OUT_INTERPOLATOR); final FloatProp mNavFadeIn = new FloatProp(0f, 1f, ANIMATION_DELAY_NAV_FADE_IN, ANIMATION_NAV_FADE_IN_DURATION, NAV_FADE_IN_INTERPOLATOR); @Override public void onUpdate(float percent, boolean initOnly) { widgetBackgroundBounds.set(mDx.value - mWidth.value / 2f, mDy.value - mHeight.value / 2f, mDx.value + mWidth.value / 2f, mDy.value + mHeight.value / 2f); // Set app window scaling factor to match widget background width mAppWindowScale = widgetBackgroundBounds.width() / windowTargetBounds.width(); // Crop scaled app window to match widget appWindowCrop.set(0 /* left */, 0 /* top */, Math.round(windowTargetBounds.width()) /* right */, Math.round(widgetBackgroundBounds.height() / mAppWindowScale) /* bottom */); matrix.setTranslate(widgetBackgroundBounds.left, widgetBackgroundBounds.top); matrix.postScale(mAppWindowScale, mAppWindowScale, widgetBackgroundBounds.left, widgetBackgroundBounds.top); ArrayList params = new ArrayList<>(); float floatingViewAlpha = appTargetsAreTranslucent ? 1 - mPreviewAlpha.value : 1; for (int i = appTargets.length - 1; i >= 0; i--) { RemoteAnimationTargetCompat target = appTargets[i]; SurfaceParams.Builder builder = new SurfaceParams.Builder(target.leash); if (target.mode == MODE_OPENING) { floatingView.update(widgetBackgroundBounds, floatingViewAlpha, mWidgetForegroundAlpha.value, mWidgetFallbackBackgroundAlpha.value, mCornerRadiusProgress.value); builder.withMatrix(matrix) .withWindowCrop(appWindowCrop) .withAlpha(mPreviewAlpha.value) .withCornerRadius(mWindowRadius.value / mAppWindowScale); } params.add(builder.build()); } if (navBarTarget != null) { final SurfaceParams.Builder navBuilder = new SurfaceParams.Builder(navBarTarget.leash); if (mNavFadeIn.value > mNavFadeIn.getStartValue()) { navBuilder.withMatrix(matrix) .withWindowCrop(appWindowCrop) .withAlpha(mNavFadeIn.value); } else { navBuilder.withAlpha(mNavFadeOut.value); } params.add(navBuilder.build()); } surfaceApplier.scheduleApply(params.toArray(new SurfaceParams[params.size()])); } }); animatorSet.playTogether(appAnimator, getBackgroundAnimator(appTargets)); return animatorSet; } private ObjectAnimator getBackgroundAnimator(RemoteAnimationTargetCompat[] appTargets) { // When launching an app from overview that doesn't map to a task, we still want to just // blur the wallpaper instead of the launcher surface as well boolean allowBlurringLauncher = mLauncher.getStateManager().getState() != OVERVIEW; DepthController depthController = mLauncher.getDepthController(); ObjectAnimator backgroundRadiusAnim = ObjectAnimator.ofFloat(depthController, DEPTH, BACKGROUND_APP.getDepth(mLauncher)) .setDuration(APP_LAUNCH_DURATION); if (allowBlurringLauncher) { final SurfaceControl dimLayer; if (BlurUtils.supportsBlursOnWindows()) { // Create a temporary effect layer, that lives on top of launcher, so we can apply // the blur to it. The EffectLayer will be fullscreen, which will help with caching // optimizations on the SurfaceFlinger side: // - Results would be able to be cached as a texture // - There won't be texture allocation overhead, because EffectLayers don't have // buffers ViewRootImpl viewRootImpl = mLauncher.getDragLayer().getViewRootImpl(); SurfaceControl parent = viewRootImpl != null ? viewRootImpl.getSurfaceControl() : null; dimLayer = new SurfaceControl.Builder() .setName("Blur layer") .setParent(parent) .setOpaque(false) .setHidden(false) .setEffectLayer() .build(); } else { dimLayer = null; } depthController.setSurface(dimLayer); backgroundRadiusAnim.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationStart(Animator animation) { depthController.setIsInLaunchTransition(true); } @Override public void onAnimationEnd(Animator animation) { depthController.setIsInLaunchTransition(false); depthController.setSurface(null); if (dimLayer != null) { new SurfaceControl.Transaction() .remove(dimLayer) .apply(); } } }); } return backgroundRadiusAnim; } /** * Registers remote animations used when closing apps to home screen. */ public void registerRemoteAnimations() { if (SEPARATE_RECENTS_ACTIVITY.get()) { return; } if (hasControlRemoteAppTransitionPermission()) { mWallpaperOpenRunner = createWallpaperOpenRunner(false /* fromUnlock */); RemoteAnimationDefinitionCompat definition = new RemoteAnimationDefinitionCompat(); definition.addRemoteAnimation(WindowManagerWrapper.TRANSIT_WALLPAPER_OPEN, WindowManagerWrapper.ACTIVITY_TYPE_STANDARD, new RemoteAnimationAdapterCompat( new LauncherAnimationRunner(mHandler, mWallpaperOpenRunner, false /* startAtFrontOfQueue */), CLOSING_TRANSITION_DURATION_MS, 0 /* statusBarTransitionDelay */)); if (KEYGUARD_ANIMATION.get()) { mKeyguardGoingAwayRunner = createWallpaperOpenRunner(true /* fromUnlock */); definition.addRemoteAnimation( WindowManagerWrapper.TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER, new RemoteAnimationAdapterCompat( new LauncherAnimationRunner( mHandler, mKeyguardGoingAwayRunner, true /* startAtFrontOfQueue */), CLOSING_TRANSITION_DURATION_MS, 0 /* statusBarTransitionDelay */)); } new ActivityCompat(mLauncher).registerRemoteAnimations(definition); } } /** * Registers remote animations used when closing apps to home screen. */ public void registerRemoteTransitions() { if (SEPARATE_RECENTS_ACTIVITY.get()) { return; } if (hasControlRemoteAppTransitionPermission()) { mWallpaperOpenTransitionRunner = createWallpaperOpenRunner(false /* fromUnlock */); mLauncherOpenTransition = RemoteAnimationAdapterCompat.buildRemoteTransition( new LauncherAnimationRunner(mHandler, mWallpaperOpenTransitionRunner, false /* startAtFrontOfQueue */)); mLauncherOpenTransition.addHomeOpenCheck(); SystemUiProxy.INSTANCE.getNoCreate().registerRemoteTransition(mLauncherOpenTransition); } } public void onActivityDestroyed() { unregisterRemoteAnimations(); unregisterRemoteTransitions(); mStartingWindowListener.setTransitionManager(null); SystemUiProxy.INSTANCE.getNoCreate().setStartingWindowListener(null); } private void unregisterRemoteAnimations() { if (SEPARATE_RECENTS_ACTIVITY.get()) { return; } if (hasControlRemoteAppTransitionPermission()) { new ActivityCompat(mLauncher).unregisterRemoteAnimations(); // Also clear strong references to the runners registered with the remote animation // definition so we don't have to wait for the system gc mWallpaperOpenRunner = null; mAppLaunchRunner = null; mKeyguardGoingAwayRunner = null; } } private void unregisterRemoteTransitions() { if (SEPARATE_RECENTS_ACTIVITY.get()) { return; } if (hasControlRemoteAppTransitionPermission()) { if (mLauncherOpenTransition == null) return; SystemUiProxy.INSTANCE.getNoCreate().unregisterRemoteTransition( mLauncherOpenTransition); mLauncherOpenTransition = null; mWallpaperOpenTransitionRunner = null; } } private boolean launcherIsATargetWithMode(RemoteAnimationTargetCompat[] targets, int mode) { return taskIsATargetWithMode(targets, mLauncher.getTaskId(), mode); } /** * @return Runner that plays when user goes to Launcher * ie. pressing home, swiping up from nav bar. */ RemoteAnimationFactory createWallpaperOpenRunner(boolean fromUnlock) { return new WallpaperOpenLauncherAnimationRunner(mHandler, fromUnlock); } /** * Animator that controls the transformations of the windows when unlocking the device. */ private Animator getUnlockWindowAnimator(RemoteAnimationTargetCompat[] appTargets, RemoteAnimationTargetCompat[] wallpaperTargets) { SurfaceTransactionApplier surfaceApplier = new SurfaceTransactionApplier(mDragLayer); ValueAnimator unlockAnimator = ValueAnimator.ofFloat(0, 1); unlockAnimator.setDuration(CLOSING_TRANSITION_DURATION_MS); float cornerRadius = mDeviceProfile.isMultiWindowMode ? 0 : QuickStepContract.getWindowCornerRadius(mLauncher.getResources()); unlockAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationStart(Animator animation) { SurfaceParams[] params = new SurfaceParams[appTargets.length]; for (int i = appTargets.length - 1; i >= 0; i--) { RemoteAnimationTargetCompat target = appTargets[i]; params[i] = new SurfaceParams.Builder(target.leash) .withAlpha(1f) .withWindowCrop(target.screenSpaceBounds) .withCornerRadius(cornerRadius) .build(); } surfaceApplier.scheduleApply(params); } }); return unlockAnimator; } private static int getRotationChange(RemoteAnimationTargetCompat[] appTargets) { int rotationChange = 0; for (RemoteAnimationTargetCompat target : appTargets) { if (Math.abs(target.rotationChange) > Math.abs(rotationChange)) { rotationChange = target.rotationChange; } } return rotationChange; } /** * Animator that controls the transformations of the windows the targets that are closing. */ private Animator getClosingWindowAnimators(RemoteAnimationTargetCompat[] appTargets, RemoteAnimationTargetCompat[] wallpaperTargets) { final int rotationChange = getRotationChange(appTargets); SurfaceTransactionApplier surfaceApplier = new SurfaceTransactionApplier(mDragLayer); Matrix matrix = new Matrix(); Point tmpPos = new Point(); Rect tmpRect = new Rect(); ValueAnimator closingAnimator = ValueAnimator.ofFloat(0, 1); int duration = CLOSING_TRANSITION_DURATION_MS; float windowCornerRadius = mDeviceProfile.isMultiWindowMode ? 0 : getWindowCornerRadius(mLauncher.getResources()); float startShadowRadius = areAllTargetsTranslucent(appTargets) ? 0 : mMaxShadowRadius; closingAnimator.setDuration(duration); closingAnimator.addUpdateListener(new MultiValueUpdateListener() { FloatProp mDy = new FloatProp(0, mClosingWindowTransY, 0, duration, DEACCEL_1_7); FloatProp mScale = new FloatProp(1f, 1f, 0, duration, DEACCEL_1_7); FloatProp mAlpha = new FloatProp(1f, 0f, 25, 125, LINEAR); FloatProp mShadowRadius = new FloatProp(startShadowRadius, 0, 0, duration, DEACCEL_1_7); @Override public void onUpdate(float percent, boolean initOnly) { SurfaceParams[] params = new SurfaceParams[appTargets.length]; for (int i = appTargets.length - 1; i >= 0; i--) { RemoteAnimationTargetCompat target = appTargets[i]; SurfaceParams.Builder builder = new SurfaceParams.Builder(target.leash); if (target.localBounds != null) { tmpPos.set(target.localBounds.left, target.localBounds.top); } else { tmpPos.set(target.position.x, target.position.y); } final Rect crop = new Rect(target.screenSpaceBounds); crop.offsetTo(0, 0); if (target.mode == MODE_CLOSING) { tmpRect.set(target.screenSpaceBounds); if ((rotationChange % 2) != 0) { final int right = crop.right; crop.right = crop.bottom; crop.bottom = right; } matrix.setScale(mScale.value, mScale.value, tmpRect.centerX(), tmpRect.centerY()); matrix.postTranslate(0, mDy.value); matrix.postTranslate(tmpPos.x, tmpPos.y); builder.withMatrix(matrix) .withWindowCrop(crop) .withAlpha(mAlpha.value) .withCornerRadius(windowCornerRadius) .withShadowRadius(mShadowRadius.value); } else if (target.mode == MODE_OPENING) { matrix.setTranslate(tmpPos.x, tmpPos.y); builder.withMatrix(matrix) .withWindowCrop(crop) .withAlpha(1f); } params[i] = builder.build(); } surfaceApplier.scheduleApply(params); } }); return closingAnimator; } private boolean supportsSSplashScreen() { return hasControlRemoteAppTransitionPermission() && Utilities.ATLEAST_S && ENABLE_SHELL_STARTING_SURFACE; } /** * Returns true if we have permission to control remote app transisions */ public boolean hasControlRemoteAppTransitionPermission() { return mLauncher.checkSelfPermission(CONTROL_REMOTE_APP_TRANSITION_PERMISSION) == PackageManager.PERMISSION_GRANTED; } private void addCujInstrumentation(Animator anim, int cuj) { anim.addListener(new AnimationSuccessListener() { @Override public void onAnimationStart(Animator animation) { mDragLayer.getViewTreeObserver().addOnDrawListener( new ViewTreeObserver.OnDrawListener() { boolean mHandled = false; @Override public void onDraw() { if (mHandled) { return; } mHandled = true; InteractionJankMonitorWrapper.begin(mDragLayer, cuj); mDragLayer.post(() -> mDragLayer.getViewTreeObserver().removeOnDrawListener( this)); } }); super.onAnimationStart(animation); } @Override public void onAnimationCancel(Animator animation) { super.onAnimationCancel(animation); InteractionJankMonitorWrapper.cancel(cuj); } @Override public void onAnimationSuccess(Animator animator) { InteractionJankMonitorWrapper.end(cuj); } }); } /** * Remote animation runner for animation from the app to Launcher, including recents. */ protected class WallpaperOpenLauncherAnimationRunner implements RemoteAnimationFactory { private final Handler mHandler; private final boolean mFromUnlock; public WallpaperOpenLauncherAnimationRunner(Handler handler, boolean fromUnlock) { mHandler = handler; mFromUnlock = fromUnlock; } @Override public void onCreateAnimation(int transit, RemoteAnimationTargetCompat[] appTargets, RemoteAnimationTargetCompat[] wallpaperTargets, RemoteAnimationTargetCompat[] nonAppTargets, LauncherAnimationRunner.AnimationResult result) { if (mLauncher.isDestroyed()) { AnimatorSet anim = new AnimatorSet(); anim.play(getClosingWindowAnimators(appTargets, wallpaperTargets)); result.setAnimation(anim, mLauncher.getApplicationContext()); return; } if (!mLauncher.hasBeenResumed()) { // If launcher is not resumed, wait until new async-frame after resume mLauncher.addOnResumeCallback(() -> postAsyncCallback(mHandler, () -> onCreateAnimation(transit, appTargets, wallpaperTargets, nonAppTargets, result))); return; } if (mLauncher.hasSomeInvisibleFlag(PENDING_INVISIBLE_BY_WALLPAPER_ANIMATION)) { mLauncher.addForceInvisibleFlag(INVISIBLE_BY_PENDING_FLAGS); mLauncher.getStateManager().moveToRestState(); } AnimatorSet anim = null; RemoteAnimationProvider provider = mRemoteAnimationProvider; if (provider != null) { anim = provider.createWindowAnimation(appTargets, wallpaperTargets); } if (anim == null) { anim = new AnimatorSet(); anim.play(mFromUnlock ? getUnlockWindowAnimator(appTargets, wallpaperTargets) : getClosingWindowAnimators(appTargets, wallpaperTargets)); // Normally, we run the launcher content animation when we are transitioning // home, but if home is already visible, then we don't want to animate the // contents of launcher unless we know that we are animating home as a result // of the home button press with quickstep, which will result in launcher being // started on touch down, prior to the animation home (and won't be in the // targets list because it is already visible). In that case, we force // invisibility on touch down, and only reset it after the animation to home // is initialized. if (launcherIsATargetWithMode(appTargets, MODE_OPENING) || mLauncher.isForceInvisible()) { addCujInstrumentation( anim, InteractionJankMonitorWrapper.CUJ_APP_CLOSE_TO_HOME); // Only register the content animation for cancellation when state changes mLauncher.getStateManager().setCurrentAnimation(anim); if (mLauncher.isInState(LauncherState.ALL_APPS)) { Pair contentAnimator = getLauncherContentAnimator(false, LAUNCHER_RESUME_START_DELAY); anim.play(contentAnimator.first); anim.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { contentAnimator.second.run(); } }); } else { anim.play(new WorkspaceRevealAnim(mLauncher, false).getAnimators()); } } } mLauncher.clearForceInvisibleFlag(INVISIBLE_ALL); result.setAnimation(anim, mLauncher); } } /** * Remote animation runner for animation to launch an app. */ private class AppLaunchAnimationRunner implements RemoteAnimationFactory { private final View mV; private final RunnableList mOnEndCallback; AppLaunchAnimationRunner(View v, RunnableList onEndCallback) { mV = v; mOnEndCallback = onEndCallback; } @Override public void onCreateAnimation(int transit, RemoteAnimationTargetCompat[] appTargets, RemoteAnimationTargetCompat[] wallpaperTargets, RemoteAnimationTargetCompat[] nonAppTargets, LauncherAnimationRunner.AnimationResult result) { AnimatorSet anim = new AnimatorSet(); boolean launcherClosing = launcherIsATargetWithMode(appTargets, MODE_CLOSING); final boolean launchingFromWidget = mV instanceof LauncherAppWidgetHostView; final boolean launchingFromRecents = isLaunchingFromRecents(mV, appTargets); final boolean skipFirstFrame; if (launchingFromWidget) { composeWidgetLaunchAnimator(anim, (LauncherAppWidgetHostView) mV, appTargets, wallpaperTargets, nonAppTargets); addCujInstrumentation( anim, InteractionJankMonitorWrapper.CUJ_APP_LAUNCH_FROM_WIDGET); skipFirstFrame = true; } else if (launchingFromRecents) { composeRecentsLaunchAnimator(anim, mV, appTargets, wallpaperTargets, nonAppTargets, launcherClosing); addCujInstrumentation( anim, InteractionJankMonitorWrapper.CUJ_APP_LAUNCH_FROM_RECENTS); skipFirstFrame = true; } else { composeIconLaunchAnimator(anim, mV, appTargets, wallpaperTargets, nonAppTargets, launcherClosing); addCujInstrumentation(anim, InteractionJankMonitorWrapper.CUJ_APP_LAUNCH_FROM_ICON); skipFirstFrame = false; } if (launcherClosing) { anim.addListener(mForceInvisibleListener); } result.setAnimation(anim, mLauncher, mOnEndCallback::executeAllAndDestroy, skipFirstFrame); } @Override public void onAnimationCancelled() { mOnEndCallback.executeAllAndDestroy(); } } /** * Class that holds all the variables for the app open animation. */ static class AnimOpenProperties { public final int cropCenterXStart; public final int cropCenterYStart; public final int cropWidthStart; public final int cropHeightStart; public final int cropCenterXEnd; public final int cropCenterYEnd; public final int cropWidthEnd; public final int cropHeightEnd; public final float dX; public final float dY; public final long xDuration; public final long yDuration; public final long alphaDuration; public final float initialAppIconScale; public final float finalAppIconScale; public final float iconAlphaStart; AnimOpenProperties(Resources r, DeviceProfile dp, Rect windowTargetBounds, RectF launcherIconBounds, View view, int dragLayerLeft, int dragLayerTop, boolean hasSplashScreen, boolean hasDifferentAppIcon) { // Scale the app icon to take up the entire screen. This simplifies the math when // animating the app window position / scale. float smallestSize = Math.min(windowTargetBounds.height(), windowTargetBounds.width()); float maxScaleX = smallestSize / launcherIconBounds.width(); float maxScaleY = smallestSize / launcherIconBounds.height(); float iconStartScale = 1f; if (view instanceof BubbleTextView && !(view.getParent() instanceof DeepShortcutView)) { Drawable dr = ((BubbleTextView) view).getIcon(); if (dr instanceof FastBitmapDrawable) { iconStartScale = ((FastBitmapDrawable) dr).getAnimatedScale(); } } initialAppIconScale = iconStartScale; finalAppIconScale = Math.max(maxScaleX, maxScaleY); // Animate the app icon to the center of the window bounds in screen coordinates. float centerX = windowTargetBounds.centerX() - dragLayerLeft; float centerY = windowTargetBounds.centerY() - dragLayerTop; dX = centerX - launcherIconBounds.centerX(); dY = centerY - launcherIconBounds.centerY(); boolean useUpwardAnimation = launcherIconBounds.top > centerY || Math.abs(dY) < dp.cellHeightPx; xDuration = useUpwardAnimation ? APP_LAUNCH_CURVED_DURATION : APP_LAUNCH_DOWN_DURATION; yDuration = useUpwardAnimation ? APP_LAUNCH_DURATION : APP_LAUNCH_DOWN_CURVED_DURATION; alphaDuration = useUpwardAnimation ? APP_LAUNCH_ALPHA_DURATION : APP_LAUNCH_ALPHA_DOWN_DURATION; iconAlphaStart = hasSplashScreen && !hasDifferentAppIcon ? 0 : 1f; final int windowIconSize = ResourceUtils.getDimenByName("starting_surface_icon_size", r, 108); cropCenterXStart = windowTargetBounds.centerX(); cropCenterYStart = windowTargetBounds.centerY(); cropWidthStart = windowIconSize; cropHeightStart = windowIconSize; cropWidthEnd = windowTargetBounds.width(); cropHeightEnd = windowTargetBounds.height(); cropCenterXEnd = windowTargetBounds.centerX(); cropCenterYEnd = windowTargetBounds.centerY(); } } private static class StartingWindowListener extends IStartingWindowListener.Stub { private QuickstepTransitionManager mTransitionManager; public void setTransitionManager(QuickstepTransitionManager transitionManager) { mTransitionManager = transitionManager; } @Override public void onTaskLaunching(int taskId, int supportedType, int color) { mTransitionManager.mTaskStartParams.put(taskId, Pair.create(supportedType, color)); } } }