mirror of
https://github.com/LawnchairLauncher/lawnchair.git
synced 2026-03-01 00:06:47 +00:00
- Reuse existing TaskSwitcherContainer for Overview metric logging - Log current orientation state for Overview when interacting with gestures or via three button nav - Log current orientation state on each phone rotation Bug: 332870519 Test: Manual Flag: NA Change-Id: Ia10cf1acb809432175daab55151998f0d77362f9
2194 lines
102 KiB
Java
2194 lines
102 KiB
Java
/*
|
|
* 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.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
|
|
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
|
|
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
|
|
import static android.provider.Settings.Secure.LAUNCHER_TASKBAR_EDUCATION_SHOWING;
|
|
import static android.view.RemoteAnimationTarget.MODE_CLOSING;
|
|
import static android.view.RemoteAnimationTarget.MODE_OPENING;
|
|
import static android.view.Surface.ROTATION_0;
|
|
import static android.view.Surface.ROTATION_180;
|
|
import static android.view.WindowManager.TRANSIT_CLOSE;
|
|
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
|
|
import static android.view.WindowManager.TRANSIT_OPEN;
|
|
import static android.view.WindowManager.TRANSIT_TO_BACK;
|
|
import static android.view.WindowManager.TRANSIT_TO_FRONT;
|
|
import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_NONE;
|
|
import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SPLASH_SCREEN;
|
|
import static android.window.TransitionFilter.CONTAINER_ORDER_TOP;
|
|
|
|
import static com.android.app.animation.Interpolators.ACCELERATE_1_5;
|
|
import static com.android.app.animation.Interpolators.AGGRESSIVE_EASE;
|
|
import static com.android.app.animation.Interpolators.DECELERATE_1_5;
|
|
import static com.android.app.animation.Interpolators.DECELERATE_1_7;
|
|
import static com.android.app.animation.Interpolators.EXAGGERATED_EASE;
|
|
import static com.android.app.animation.Interpolators.LINEAR;
|
|
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.Flags.enableScalingRevealHomeAnimation;
|
|
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.NORMAL;
|
|
import static com.android.launcher3.LauncherState.OVERVIEW;
|
|
import static com.android.launcher3.Utilities.mapBoundToRange;
|
|
import static com.android.launcher3.config.FeatureFlags.ENABLE_BACK_SWIPE_HOME_ANIMATION;
|
|
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.model.data.ItemInfo.NO_MATCHING_ID;
|
|
import static com.android.launcher3.testing.shared.TestProtocol.WALLPAPER_OPEN_ANIMATION_FINISHED_MESSAGE;
|
|
import static com.android.launcher3.util.DisplayController.isTransientTaskbar;
|
|
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
|
|
import static com.android.launcher3.util.Executors.ORDERED_BG_EXECUTOR;
|
|
import static com.android.launcher3.util.MultiPropertyFactory.MULTI_PROPERTY_VALUE;
|
|
import static com.android.launcher3.util.window.RefreshRateTracker.getSingleFrameMs;
|
|
import static com.android.launcher3.views.FloatingIconView.SHAPE_PROGRESS_DURATION;
|
|
import static com.android.launcher3.views.FloatingIconView.getFloatingIconView;
|
|
import static com.android.quickstep.TaskAnimationManager.ENABLE_SHELL_TRANSITIONS;
|
|
import static com.android.quickstep.TaskViewUtils.findTaskViewToLaunch;
|
|
import static com.android.quickstep.util.AnimUtils.clampToDuration;
|
|
import static com.android.quickstep.util.AnimUtils.completeRunnableListCallback;
|
|
import static com.android.systemui.shared.system.QuickStepContract.getWindowCornerRadius;
|
|
import static com.android.systemui.shared.system.QuickStepContract.supportsRoundedCornersOnWindows;
|
|
|
|
import android.animation.Animator;
|
|
import android.animation.AnimatorListenerAdapter;
|
|
import android.animation.AnimatorSet;
|
|
import android.animation.ObjectAnimator;
|
|
import android.animation.ValueAnimator;
|
|
import android.app.ActivityOptions;
|
|
import android.app.WindowConfiguration;
|
|
import android.content.ComponentName;
|
|
import android.content.Context;
|
|
import android.content.res.Resources;
|
|
import android.database.ContentObserver;
|
|
import android.graphics.Color;
|
|
import android.graphics.Matrix;
|
|
import android.graphics.Point;
|
|
import android.graphics.PointF;
|
|
import android.graphics.Rect;
|
|
import android.graphics.RectF;
|
|
import android.graphics.drawable.ColorDrawable;
|
|
import android.graphics.drawable.Drawable;
|
|
import android.os.Handler;
|
|
import android.os.IBinder;
|
|
import android.os.IRemoteCallback;
|
|
import android.os.Looper;
|
|
import android.os.SystemProperties;
|
|
import android.os.UserHandle;
|
|
import android.provider.Settings;
|
|
import android.provider.Settings.Global;
|
|
import android.util.Pair;
|
|
import android.util.Size;
|
|
import android.view.CrossWindowBlurListeners;
|
|
import android.view.IRemoteAnimationFinishedCallback;
|
|
import android.view.RemoteAnimationAdapter;
|
|
import android.view.RemoteAnimationDefinition;
|
|
import android.view.RemoteAnimationTarget;
|
|
import android.view.SurfaceControl;
|
|
import android.view.View;
|
|
import android.view.ViewRootImpl;
|
|
import android.view.ViewTreeObserver;
|
|
import android.view.WindowManager;
|
|
import android.view.animation.AnimationUtils;
|
|
import android.view.animation.Interpolator;
|
|
import android.view.animation.PathInterpolator;
|
|
import android.window.RemoteTransition;
|
|
import android.window.TransitionFilter;
|
|
|
|
import androidx.annotation.NonNull;
|
|
import androidx.annotation.Nullable;
|
|
import androidx.core.graphics.ColorUtils;
|
|
|
|
import com.android.internal.jank.Cuj;
|
|
import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
|
|
import com.android.launcher3.LauncherAnimationRunner.RemoteAnimationFactory;
|
|
import com.android.launcher3.anim.AnimationSuccessListener;
|
|
import com.android.launcher3.anim.AnimatorListeners;
|
|
import com.android.launcher3.compat.AccessibilityManagerCompat;
|
|
import com.android.launcher3.dragndrop.DragLayer;
|
|
import com.android.launcher3.icons.FastBitmapDrawable;
|
|
import com.android.launcher3.model.data.ItemInfo;
|
|
import com.android.launcher3.shortcuts.DeepShortcutView;
|
|
import com.android.launcher3.statehandlers.DepthController;
|
|
import com.android.launcher3.taskbar.LauncherTaskbarUIController;
|
|
import com.android.launcher3.testing.shared.ResourceUtils;
|
|
import com.android.launcher3.touch.PagedOrientationHandler;
|
|
import com.android.launcher3.uioverrides.QuickstepLauncher;
|
|
import com.android.launcher3.util.ActivityOptionsWrapper;
|
|
import com.android.launcher3.util.DynamicResource;
|
|
import com.android.launcher3.util.ObjectWrapper;
|
|
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.LauncherBackAnimationController;
|
|
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.RectFSpringAnim;
|
|
import com.android.quickstep.util.RectFSpringAnim.DefaultSpringConfig;
|
|
import com.android.quickstep.util.RectFSpringAnim.TaskbarHotseatSpringConfig;
|
|
import com.android.quickstep.util.StaggeredWorkspaceAnim;
|
|
import com.android.quickstep.util.SurfaceTransaction;
|
|
import com.android.quickstep.util.SurfaceTransaction.SurfaceProperties;
|
|
import com.android.quickstep.util.SurfaceTransactionApplier;
|
|
import com.android.quickstep.util.TaskRestartedDuringLaunchListener;
|
|
import com.android.quickstep.util.WorkspaceRevealAnim;
|
|
import com.android.quickstep.views.FloatingWidgetView;
|
|
import com.android.quickstep.views.RecentsView;
|
|
import com.android.systemui.animation.ActivityTransitionAnimator;
|
|
import com.android.systemui.animation.DelegateTransitionAnimatorController;
|
|
import com.android.systemui.animation.LaunchableView;
|
|
import com.android.systemui.animation.RemoteAnimationDelegate;
|
|
import com.android.systemui.animation.RemoteAnimationRunnerCompat;
|
|
import com.android.systemui.shared.system.BlurUtils;
|
|
import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
|
|
import com.android.systemui.shared.system.QuickStepContract;
|
|
import com.android.wm.shell.startingsurface.IStartingWindowListener;
|
|
|
|
import java.io.PrintWriter;
|
|
import java.lang.ref.WeakReference;
|
|
import java.util.ArrayList;
|
|
import java.util.LinkedHashMap;
|
|
import java.util.List;
|
|
import java.util.Map.Entry;
|
|
|
|
/**
|
|
* Manages the opening and closing app transitions from Launcher
|
|
*/
|
|
public class QuickstepTransitionManager implements OnDeviceProfileChangeListener {
|
|
|
|
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;
|
|
|
|
public static final long APP_LAUNCH_DURATION = 500;
|
|
|
|
private static final long APP_LAUNCH_ALPHA_DURATION = 50;
|
|
private static final long APP_LAUNCH_ALPHA_START_DELAY = 25;
|
|
|
|
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);
|
|
|
|
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 SPLIT_LAUNCH_DURATION = 370;
|
|
public static final int SPLIT_DIVIDER_ANIM_DURATION = 100;
|
|
|
|
public static final int CONTENT_ALPHA_DURATION = 217;
|
|
public static final int TRANSIENT_TASKBAR_TRANSITION_DURATION = 417;
|
|
public static final int TASKBAR_TO_APP_DURATION = 600;
|
|
// TODO(b/236145847): Tune TASKBAR_TO_HOME_DURATION to 383 after conflict with unlock animation
|
|
// is solved.
|
|
private static final int TASKBAR_TO_HOME_DURATION_FAST = 300;
|
|
private static final int TASKBAR_TO_HOME_DURATION_SLOW = 1000;
|
|
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 QuickstepLauncher mLauncher;
|
|
protected final DragLayer mDragLayer;
|
|
|
|
protected final Handler mHandler;
|
|
|
|
private final float mClosingWindowTransY;
|
|
private final float mMaxShadowRadius;
|
|
|
|
private final StartingWindowListener mStartingWindowListener =
|
|
new StartingWindowListener(this);
|
|
private ContentObserver mAnimationRemovalObserver = new ContentObserver(
|
|
ORDERED_BG_EXECUTOR.getHandler()) {
|
|
@Override
|
|
public void onChange(boolean selfChange) {
|
|
mAreAnimationsEnabled = Global.getFloat(mLauncher.getContentResolver(),
|
|
Global.ANIMATOR_DURATION_SCALE, 1f) > 0
|
|
|| Global.getFloat(mLauncher.getContentResolver(),
|
|
Global.TRANSITION_ANIMATION_SCALE, 1f) > 0;
|
|
}
|
|
};
|
|
|
|
private DeviceProfile mDeviceProfile;
|
|
|
|
// 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 RemoteTransition mLauncherOpenTransition;
|
|
|
|
private final RemoteAnimationCoordinateTransfer mCoordinateTransfer;
|
|
|
|
private LauncherBackAnimationController mBackAnimationController;
|
|
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<Integer, Pair<Integer, Integer>> mTaskStartParams;
|
|
private boolean mAreAnimationsEnabled = true;
|
|
|
|
private final Interpolator mOpeningXInterpolator;
|
|
private final Interpolator mOpeningInterpolator;
|
|
|
|
public QuickstepTransitionManager(Context context) {
|
|
mLauncher = Launcher.cast(Launcher.getLauncher(context));
|
|
mDragLayer = mLauncher.getDragLayer();
|
|
mHandler = new Handler(Looper.getMainLooper());
|
|
mDeviceProfile = mLauncher.getDeviceProfile();
|
|
mBackAnimationController = new LauncherBackAnimationController(mLauncher, this);
|
|
checkAndMonitorIfAnimationsAreEnabled();
|
|
|
|
Resources res = mLauncher.getResources();
|
|
mClosingWindowTransY = res.getDimensionPixelSize(R.dimen.closing_window_trans_y);
|
|
mMaxShadowRadius = res.getDimensionPixelSize(R.dimen.max_shadow_radius);
|
|
|
|
mLauncher.addOnDeviceProfileChangeListener(this);
|
|
|
|
if (ENABLE_SHELL_STARTING_SURFACE) {
|
|
mTaskStartParams = new LinkedHashMap<>(MAX_NUM_TASKS) {
|
|
@Override
|
|
protected boolean removeEldestEntry(Entry<Integer, Pair<Integer, Integer>> entry) {
|
|
return size() > MAX_NUM_TASKS;
|
|
}
|
|
};
|
|
|
|
SystemUiProxy.INSTANCE.get(mLauncher).setStartingWindowListener(
|
|
mStartingWindowListener);
|
|
}
|
|
|
|
mOpeningXInterpolator = AnimationUtils.loadInterpolator(context, R.interpolator.app_open_x);
|
|
mOpeningInterpolator = AnimationUtils.loadInterpolator(context,
|
|
R.interpolator.emphasized_interpolator);
|
|
mCoordinateTransfer = new RemoteAnimationCoordinateTransfer(mLauncher);
|
|
}
|
|
|
|
@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();
|
|
|
|
// Handle the case where an already visible task is launched which results in no transition
|
|
TaskRestartedDuringLaunchListener restartedListener =
|
|
new TaskRestartedDuringLaunchListener();
|
|
restartedListener.register(onEndCallback::executeAllAndDestroy);
|
|
onEndCallback.add(restartedListener::unregister);
|
|
|
|
mAppLaunchRunner = new AppLaunchAnimationRunner(v, onEndCallback);
|
|
ItemInfo tag = (ItemInfo) v.getTag();
|
|
if (tag != null && tag.shouldUseBackgroundAnimation()) {
|
|
ContainerAnimationRunner containerAnimationRunner = ContainerAnimationRunner.from(
|
|
v, mLauncher, mStartingWindowListener, onEndCallback);
|
|
if (containerAnimationRunner != null) {
|
|
mAppLaunchRunner = containerAnimationRunner;
|
|
}
|
|
}
|
|
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;
|
|
ActivityOptions options = ActivityOptions.makeRemoteAnimation(
|
|
new RemoteAnimationAdapter(runner, duration, statusBarTransitionDelay),
|
|
new RemoteTransition(runner.toRemoteTransition(),
|
|
mLauncher.getIApplicationThread(), "QuickstepLaunch"));
|
|
IRemoteCallback endCallback = completeRunnableListCallback(onEndCallback);
|
|
options.setOnAnimationAbortListener(endCallback);
|
|
options.setOnAnimationFinishedListener(endCallback);
|
|
return new ActivityOptionsWrapper(options, 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 RemoteAnimationTarget[] targets) {
|
|
return mLauncher.getStateManager().getState().isRecentsViewVisible
|
|
&& 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 RemoteAnimationTarget[] appTargets,
|
|
@NonNull RemoteAnimationTarget[] wallpaperTargets,
|
|
@NonNull RemoteAnimationTarget[] nonAppTargets, boolean launcherClosing) {
|
|
TaskViewUtils.composeRecentsLaunchAnimator(anim, v, appTargets, wallpaperTargets,
|
|
nonAppTargets, launcherClosing, mLauncher.getStateManager(),
|
|
mLauncher.getOverviewPanel(), mLauncher.getDepthController());
|
|
}
|
|
|
|
private boolean areAllTargetsTranslucent(@NonNull RemoteAnimationTarget[] targets) {
|
|
boolean isAllOpeningTargetTrs = true;
|
|
for (int i = 0; i < targets.length; i++) {
|
|
RemoteAnimationTarget 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 RemoteAnimationTarget[] appTargets,
|
|
@NonNull RemoteAnimationTarget[] wallpaperTargets,
|
|
@NonNull RemoteAnimationTarget[] nonAppTargets,
|
|
boolean launcherClosing) {
|
|
// Set the state animation first so that any state listeners are called
|
|
// before our internal listeners.
|
|
mLauncher.getStateManager().setCurrentAnimation(anim);
|
|
|
|
// Note: the targetBounds are relative to the launcher
|
|
int startDelay = getSingleFrameMs(mLauncher);
|
|
Animator windowAnimator = getOpeningWindowAnimators(
|
|
v, appTargets, wallpaperTargets, nonAppTargets, launcherClosing);
|
|
windowAnimator.setStartDelay(startDelay);
|
|
anim.play(windowAnimator);
|
|
if (launcherClosing) {
|
|
// Delay animation by a frame to avoid jank.
|
|
Pair<AnimatorSet, Runnable> launcherContentAnimator =
|
|
getLauncherContentAnimator(true /* isAppOpening */, startDelay, false);
|
|
anim.play(launcherContentAnimator.first);
|
|
anim.addListener(new AnimatorListenerAdapter() {
|
|
@Override
|
|
public void onAnimationEnd(Animator animation) {
|
|
launcherContentAnimator.second.run();
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
private void composeWidgetLaunchAnimator(
|
|
@NonNull AnimatorSet anim,
|
|
@NonNull LauncherAppWidgetHostView v,
|
|
@NonNull RemoteAnimationTarget[] appTargets,
|
|
@NonNull RemoteAnimationTarget[] wallpaperTargets,
|
|
@NonNull RemoteAnimationTarget[] nonAppTargets,
|
|
boolean launcherClosing) {
|
|
mLauncher.getStateManager().setCurrentAnimation(anim);
|
|
anim.play(getOpeningWindowAnimatorsForWidget(
|
|
v, appTargets, wallpaperTargets, nonAppTargets, launcherClosing));
|
|
}
|
|
|
|
/**
|
|
* 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 RemoteAnimationTarget[] appTargets,
|
|
int rotationChange) {
|
|
RemoteAnimationTarget target = null;
|
|
for (RemoteAnimationTarget 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);
|
|
}
|
|
}
|
|
if (mDeviceProfile.isTaskbarPresentInApps
|
|
&& !target.willShowImeOnTarget
|
|
&& !isTransientTaskbar(mLauncher)) {
|
|
// Animate to above the taskbar.
|
|
bounds.bottom -= target.contentInsets.bottom;
|
|
}
|
|
return bounds;
|
|
}
|
|
|
|
/** Dump debug logs to bug report. */
|
|
public void dump(@NonNull String prefix, @NonNull PrintWriter printWriter) {}
|
|
|
|
/**
|
|
* 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.
|
|
* @param skipAllAppsScale True if we want to avoid scaling All Apps
|
|
*/
|
|
private Pair<AnimatorSet, Runnable> getLauncherContentAnimator(boolean isAppOpening,
|
|
int startDelay, boolean skipAllAppsScale) {
|
|
AnimatorSet launcherAnimator = new AnimatorSet();
|
|
Runnable endListener;
|
|
|
|
float[] alphas = isAppOpening
|
|
? new float[]{1, 0}
|
|
: new float[]{0, 1};
|
|
|
|
float[] scales = isAppOpening
|
|
? new float[]{1, mDeviceProfile.workspaceContentScale}
|
|
: new float[]{mDeviceProfile.workspaceContentScale, 1};
|
|
|
|
// Pause expensive view updates as they can lead to layer thrashing and skipped frames.
|
|
mLauncher.pauseExpensiveViewUpdates();
|
|
|
|
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);
|
|
if (mDeviceProfile.isTablet) {
|
|
// AllApps should not fade at all in tablets.
|
|
alphas = new float[]{1, 1};
|
|
}
|
|
appsView.setAlpha(alphas[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);
|
|
}
|
|
});
|
|
|
|
if (!skipAllAppsScale) {
|
|
SCALE_PROPERTY.set(appsView, scales[0]);
|
|
ObjectAnimator scale = ObjectAnimator.ofFloat(appsView, SCALE_PROPERTY, scales);
|
|
scale.setInterpolator(AGGRESSIVE_EASE);
|
|
scale.setDuration(CONTENT_SCALE_DURATION);
|
|
launcherAnimator.play(scale);
|
|
}
|
|
|
|
launcherAnimator.play(alpha);
|
|
|
|
endListener = () -> {
|
|
appsView.setAlpha(startAlpha);
|
|
SCALE_PROPERTY.set(appsView, startScale);
|
|
appsView.setLayerType(View.LAYER_TYPE_NONE, null);
|
|
mLauncher.resumeExpensiveViewUpdates();
|
|
};
|
|
} else if (mLauncher.isInState(OVERVIEW)) {
|
|
endListener = composeViewContentAnimator(launcherAnimator, alphas, scales);
|
|
} else {
|
|
List<View> viewsToAnimate = new ArrayList<>();
|
|
Workspace<?> workspace = mLauncher.getWorkspace();
|
|
workspace.forEachVisiblePage(
|
|
view -> viewsToAnimate.add(((CellLayout) view).getShortcutsAndWidgets()));
|
|
|
|
// Do not scale hotseat as a whole when taskbar is present, and scale QSB only if it's
|
|
// not inline.
|
|
if (mDeviceProfile.isTaskbarPresent) {
|
|
if (!mDeviceProfile.isQsbInline) {
|
|
viewsToAnimate.add(mLauncher.getHotseat().getQsb());
|
|
}
|
|
} else {
|
|
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(DECELERATE_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(DECELERATE_1_5);
|
|
|
|
launcherAnimator.play(scrim);
|
|
}
|
|
}
|
|
|
|
endListener = () -> {
|
|
viewsToAnimate.forEach(view -> {
|
|
SCALE_PROPERTY.set(view, 1f);
|
|
view.setLayerType(View.LAYER_TYPE_NONE, null);
|
|
});
|
|
if (scrimEnabled) {
|
|
mLauncher.getScrimView().setBackgroundColor(Color.TRANSPARENT);
|
|
}
|
|
mLauncher.resumeExpensiveViewUpdates();
|
|
};
|
|
}
|
|
|
|
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();
|
|
mLauncher.resumeExpensiveViewUpdates();
|
|
};
|
|
}
|
|
|
|
/**
|
|
* @return Animator that controls the window of the opening targets from app icons.
|
|
*/
|
|
private Animator getOpeningWindowAnimators(View v,
|
|
RemoteAnimationTarget[] appTargets,
|
|
RemoteAnimationTarget[] wallpaperTargets,
|
|
RemoteAnimationTarget[] nonAppTargets,
|
|
boolean launcherClosing) {
|
|
int rotationChange = getRotationChange(appTargets);
|
|
Rect windowTargetBounds = getWindowTargetBounds(appTargets, rotationChange);
|
|
boolean appTargetsAreTranslucent = areAllTargetsTranslucent(appTargets);
|
|
|
|
RectF launcherIconBounds = new RectF();
|
|
FloatingIconView floatingView = getFloatingIconView(mLauncher, v,
|
|
(mLauncher.getTaskbarUIController() == null || !isTransientTaskbar(mLauncher))
|
|
? null
|
|
: mLauncher.getTaskbarUIController().findMatchingView(v),
|
|
null /* fadeOutView */, !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);
|
|
RemoteAnimationTarget navBarTarget = openingTargets.getNavBarRemoteAnimationTarget();
|
|
|
|
int[] dragLayerBounds = new int[2];
|
|
mDragLayer.getLocationOnScreen(dragLayerBounds);
|
|
|
|
final boolean hasSplashScreen;
|
|
if (ENABLE_SHELL_STARTING_SURFACE) {
|
|
int taskId = openingTargets.getFirstAppTargetTaskId();
|
|
Pair<Integer, Integer> defaultParams = Pair.create(STARTING_WINDOW_TYPE_NONE, 0);
|
|
Pair<Integer, Integer> 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 = prop.cropCenterXStart - prop.cropWidthStart / 2;
|
|
int top = prop.cropCenterYStart - prop.cropHeightStart / 2;
|
|
int right = left + prop.cropWidthStart;
|
|
int bottom = 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 onAnimationStart(Animator animation) {
|
|
LauncherTaskbarUIController taskbarController = mLauncher.getTaskbarUIController();
|
|
if (taskbarController != null && taskbarController.shouldShowEduOnAppLaunch()) {
|
|
// LAUNCHER_TASKBAR_EDUCATION_SHOWING is set to true here, when the education
|
|
// flow is about to start, to avoid a race condition with other components
|
|
// that would show something else to the user as soon as the app is opened.
|
|
Settings.Secure.putInt(mLauncher.getContentResolver(),
|
|
LAUNCHER_TASKBAR_EDUCATION_SHOWING, 1);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onAnimationEnd(Animator animation) {
|
|
if (v instanceof BubbleTextView) {
|
|
((BubbleTextView) v).setStayPressed(false);
|
|
}
|
|
LauncherTaskbarUIController taskbarController = mLauncher.getTaskbarUIController();
|
|
if (taskbarController != null) {
|
|
taskbarController.showEduOnAppLaunch();
|
|
}
|
|
openingTargets.release();
|
|
}
|
|
});
|
|
|
|
final float initialWindowRadius = supportsRoundedCornersOnWindows(mLauncher.getResources())
|
|
? Math.max(crop.width(), crop.height()) / 2f
|
|
: 0f;
|
|
final float finalWindowRadius = mDeviceProfile.isMultiWindowMode
|
|
? 0 : getWindowCornerRadius(mLauncher);
|
|
final float finalShadowRadius = appTargetsAreTranslucent ? 0 : mMaxShadowRadius;
|
|
|
|
MultiValueUpdateListener listener = new MultiValueUpdateListener() {
|
|
FloatProp mDx = new FloatProp(0, prop.dX, mOpeningXInterpolator);
|
|
FloatProp mDy = new FloatProp(0, prop.dY, mOpeningInterpolator);
|
|
|
|
FloatProp mIconScaleToFitScreen = new FloatProp(prop.initialAppIconScale,
|
|
prop.finalAppIconScale, mOpeningInterpolator);
|
|
FloatProp mIconAlpha = new FloatProp(prop.iconAlphaStart, 0f,
|
|
clampToDuration(LINEAR, APP_LAUNCH_ALPHA_START_DELAY, APP_LAUNCH_ALPHA_DURATION,
|
|
APP_LAUNCH_DURATION));
|
|
|
|
FloatProp mWindowRadius = new FloatProp(initialWindowRadius, finalWindowRadius,
|
|
mOpeningInterpolator);
|
|
FloatProp mShadowRadius = new FloatProp(0, finalShadowRadius,
|
|
mOpeningInterpolator);
|
|
|
|
FloatProp mCropRectCenterX = new FloatProp(prop.cropCenterXStart, prop.cropCenterXEnd,
|
|
mOpeningInterpolator);
|
|
FloatProp mCropRectCenterY = new FloatProp(prop.cropCenterYStart, prop.cropCenterYEnd,
|
|
mOpeningInterpolator);
|
|
FloatProp mCropRectWidth = new FloatProp(prop.cropWidthStart, prop.cropWidthEnd,
|
|
mOpeningInterpolator);
|
|
FloatProp mCropRectHeight = new FloatProp(prop.cropHeightStart, prop.cropHeightEnd,
|
|
mOpeningInterpolator);
|
|
|
|
FloatProp mNavFadeOut = new FloatProp(1f, 0f, clampToDuration(
|
|
NAV_FADE_OUT_INTERPOLATOR, 0, ANIMATION_NAV_FADE_OUT_DURATION,
|
|
APP_LAUNCH_DURATION));
|
|
FloatProp mNavFadeIn = new FloatProp(0f, 1f, clampToDuration(
|
|
NAV_FADE_IN_INTERPOLATOR, ANIMATION_DELAY_NAV_FADE_IN,
|
|
ANIMATION_NAV_FADE_IN_DURATION, APP_LAUNCH_DURATION));
|
|
|
|
@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, floatingIconBounds, percent, 0f,
|
|
mWindowRadius.value * scale, true /* isOpening */);
|
|
return;
|
|
}
|
|
|
|
SurfaceTransaction transaction = new SurfaceTransaction();
|
|
|
|
for (int i = appTargets.length - 1; i >= 0; i--) {
|
|
RemoteAnimationTarget target = appTargets[i];
|
|
SurfaceProperties builder = transaction.forSurface(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, floatingIconBounds, percent, 0f,
|
|
mWindowRadius.value * scale, true /* isOpening */);
|
|
builder.setMatrix(matrix)
|
|
.setWindowCrop(crop)
|
|
.setAlpha(1f - mIconAlpha.value)
|
|
.setCornerRadius(mWindowRadius.value)
|
|
.setShadowRadius(mShadowRadius.value);
|
|
} else if (target.mode == MODE_CLOSING) {
|
|
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 ((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.setMatrix(matrix)
|
|
.setWindowCrop(crop)
|
|
.setAlpha(1f);
|
|
}
|
|
}
|
|
|
|
if (navBarTarget != null) {
|
|
SurfaceProperties navBuilder =
|
|
transaction.forSurface(navBarTarget.leash);
|
|
if (mNavFadeIn.value > mNavFadeIn.getStartValue()) {
|
|
matrix.setScale(scale, scale);
|
|
matrix.postTranslate(windowTransX0, windowTransY0);
|
|
navBuilder.setMatrix(matrix)
|
|
.setWindowCrop(crop)
|
|
.setAlpha(mNavFadeIn.value);
|
|
} else {
|
|
navBuilder.setAlpha(mNavFadeOut.value);
|
|
}
|
|
}
|
|
surfaceApplier.scheduleApply(transaction);
|
|
}
|
|
};
|
|
appAnimator.addUpdateListener(listener);
|
|
// Since we added a start delay, call update here to init the FloatingIconView properly.
|
|
listener.onUpdate(0, true /* initOnly */);
|
|
|
|
// If app targets are translucent, do not animate the background as it causes a visible
|
|
// flicker when it resets itself at the end of its animation.
|
|
if (appTargetsAreTranslucent || !launcherClosing) {
|
|
animatorSet.play(appAnimator);
|
|
} else {
|
|
animatorSet.playTogether(appAnimator, getBackgroundAnimator());
|
|
}
|
|
return animatorSet;
|
|
}
|
|
|
|
private Animator getOpeningWindowAnimatorsForWidget(LauncherAppWidgetHostView v,
|
|
RemoteAnimationTarget[] appTargets,
|
|
RemoteAnimationTarget[] wallpaperTargets,
|
|
RemoteAnimationTarget[] nonAppTargets, boolean launcherClosing) {
|
|
Rect windowTargetBounds = getWindowTargetBounds(appTargets, getRotationChange(appTargets));
|
|
boolean appTargetsAreTranslucent = areAllTargetsTranslucent(appTargets);
|
|
|
|
final RectF widgetBackgroundBounds = new RectF();
|
|
final Rect appWindowCrop = new Rect();
|
|
final Matrix matrix = new Matrix();
|
|
RemoteAnimationTargets openingTargets = new RemoteAnimationTargets(appTargets,
|
|
wallpaperTargets, nonAppTargets, MODE_OPENING);
|
|
|
|
RemoteAnimationTarget openingTarget = openingTargets.getFirstAppTarget();
|
|
int fallbackBackgroundColor = 0;
|
|
if (openingTarget != null && ENABLE_SHELL_STARTING_SURFACE) {
|
|
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);
|
|
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);
|
|
|
|
RemoteAnimationTarget 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, 0, clampToDuration(
|
|
LINEAR, 0, WIDGET_CROSSFADE_DURATION_MILLIS / 2, APP_LAUNCH_DURATION));
|
|
|
|
final FloatProp mWidgetFallbackBackgroundAlpha = new FloatProp(0, 1,
|
|
clampToDuration(LINEAR, 0, 75, APP_LAUNCH_DURATION));
|
|
final FloatProp mPreviewAlpha = new FloatProp(0, 1, clampToDuration(
|
|
LINEAR,
|
|
WIDGET_CROSSFADE_DURATION_MILLIS / 2 /* delay */,
|
|
WIDGET_CROSSFADE_DURATION_MILLIS / 2 /* duration */,
|
|
APP_LAUNCH_DURATION));
|
|
final FloatProp mWindowRadius = new FloatProp(initialWindowRadius, finalWindowRadius,
|
|
mOpeningInterpolator);
|
|
final FloatProp mCornerRadiusProgress = new FloatProp(0, 1, mOpeningInterpolator);
|
|
|
|
// Window & widget background positioning bounds
|
|
final FloatProp mDx = new FloatProp(widgetBackgroundBounds.centerX(),
|
|
windowTargetBounds.centerX(), mOpeningXInterpolator);
|
|
final FloatProp mDy = new FloatProp(widgetBackgroundBounds.centerY(),
|
|
windowTargetBounds.centerY(), mOpeningInterpolator);
|
|
final FloatProp mWidth = new FloatProp(widgetBackgroundBounds.width(),
|
|
windowTargetBounds.width(), mOpeningInterpolator);
|
|
final FloatProp mHeight = new FloatProp(widgetBackgroundBounds.height(),
|
|
windowTargetBounds.height(), mOpeningInterpolator);
|
|
|
|
final FloatProp mNavFadeOut = new FloatProp(1f, 0f, clampToDuration(
|
|
NAV_FADE_OUT_INTERPOLATOR, 0, ANIMATION_NAV_FADE_OUT_DURATION,
|
|
APP_LAUNCH_DURATION));
|
|
final FloatProp mNavFadeIn = new FloatProp(0f, 1f, clampToDuration(
|
|
NAV_FADE_IN_INTERPOLATOR, ANIMATION_DELAY_NAV_FADE_IN,
|
|
ANIMATION_NAV_FADE_IN_DURATION, APP_LAUNCH_DURATION));
|
|
|
|
@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 */,
|
|
windowTargetBounds.width() /* right */,
|
|
Math.round(widgetBackgroundBounds.height() / mAppWindowScale) /* bottom */);
|
|
matrix.setTranslate(widgetBackgroundBounds.left, widgetBackgroundBounds.top);
|
|
matrix.postScale(mAppWindowScale, mAppWindowScale, widgetBackgroundBounds.left,
|
|
widgetBackgroundBounds.top);
|
|
|
|
SurfaceTransaction transaction = new SurfaceTransaction();
|
|
float floatingViewAlpha = appTargetsAreTranslucent ? 1 - mPreviewAlpha.value : 1;
|
|
for (int i = appTargets.length - 1; i >= 0; i--) {
|
|
RemoteAnimationTarget target = appTargets[i];
|
|
SurfaceProperties builder = transaction.forSurface(target.leash);
|
|
if (target.mode == MODE_OPENING) {
|
|
floatingView.update(widgetBackgroundBounds, floatingViewAlpha,
|
|
mWidgetForegroundAlpha.value, mWidgetFallbackBackgroundAlpha.value,
|
|
mCornerRadiusProgress.value);
|
|
builder.setMatrix(matrix)
|
|
.setWindowCrop(appWindowCrop)
|
|
.setAlpha(mPreviewAlpha.value)
|
|
.setCornerRadius(mWindowRadius.value / mAppWindowScale);
|
|
}
|
|
}
|
|
|
|
if (navBarTarget != null) {
|
|
SurfaceProperties navBuilder = transaction.forSurface(navBarTarget.leash);
|
|
if (mNavFadeIn.value > mNavFadeIn.getStartValue()) {
|
|
navBuilder.setMatrix(matrix)
|
|
.setWindowCrop(appWindowCrop)
|
|
.setAlpha(mNavFadeIn.value);
|
|
} else {
|
|
navBuilder.setAlpha(mNavFadeOut.value);
|
|
}
|
|
}
|
|
surfaceApplier.scheduleApply(transaction);
|
|
}
|
|
});
|
|
|
|
// If app targets are translucent, do not animate the background as it causes a visible
|
|
// flicker when it resets itself at the end of its animation.
|
|
if (appTargetsAreTranslucent || !launcherClosing) {
|
|
animatorSet.play(appAnimator);
|
|
} else {
|
|
animatorSet.playTogether(appAnimator, getBackgroundAnimator());
|
|
}
|
|
return animatorSet;
|
|
}
|
|
|
|
/**
|
|
* Returns animator that controls depth/blur of the background.
|
|
*/
|
|
private ObjectAnimator getBackgroundAnimator() {
|
|
// 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
|
|
&& BlurUtils.supportsBlursOnWindows();
|
|
|
|
LaunchDepthController depthController = new LaunchDepthController(mLauncher);
|
|
ObjectAnimator backgroundRadiusAnim = ObjectAnimator.ofFloat(depthController.stateDepth,
|
|
MULTI_PROPERTY_VALUE, BACKGROUND_APP.getDepth(mLauncher))
|
|
.setDuration(APP_LAUNCH_DURATION);
|
|
|
|
if (allowBlurringLauncher) {
|
|
// 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;
|
|
SurfaceControl dimLayer = new SurfaceControl.Builder()
|
|
.setName("Blur layer")
|
|
.setParent(parent)
|
|
.setOpaque(false)
|
|
.setHidden(false)
|
|
.setEffectLayer()
|
|
.build();
|
|
|
|
backgroundRadiusAnim.addListener(AnimatorListeners.forEndCallback(() ->
|
|
new SurfaceControl.Transaction().remove(dimLayer).apply()));
|
|
}
|
|
|
|
backgroundRadiusAnim.addListener(
|
|
AnimatorListeners.forEndCallback(() -> {
|
|
// reset the depth to match the main depth controller's depth
|
|
depthController.stateDepth
|
|
.setValue(mLauncher.getDepthController().stateDepth.getValue());
|
|
depthController.dispose();
|
|
}));
|
|
|
|
return backgroundRadiusAnim;
|
|
}
|
|
|
|
/**
|
|
* Registers remote animations used when closing apps to home screen.
|
|
*/
|
|
public void registerRemoteAnimations() {
|
|
if (SEPARATE_RECENTS_ACTIVITY.get()) {
|
|
return;
|
|
}
|
|
RemoteAnimationDefinition definition = new RemoteAnimationDefinition();
|
|
addRemoteAnimations(definition);
|
|
mLauncher.registerRemoteAnimations(definition);
|
|
}
|
|
|
|
/**
|
|
* Adds remote animations to a {@link RemoteAnimationDefinition}. May be overridden to add
|
|
* additional animations.
|
|
*/
|
|
private void addRemoteAnimations(RemoteAnimationDefinition definition) {
|
|
mWallpaperOpenRunner = createWallpaperOpenRunner(false /* fromUnlock */);
|
|
definition.addRemoteAnimation(WindowManager.TRANSIT_OLD_WALLPAPER_OPEN,
|
|
WindowConfiguration.ACTIVITY_TYPE_STANDARD,
|
|
new RemoteAnimationAdapter(
|
|
new LauncherAnimationRunner(mHandler, mWallpaperOpenRunner,
|
|
false /* startAtFrontOfQueue */),
|
|
CLOSING_TRANSITION_DURATION_MS, 0 /* statusBarTransitionDelay */));
|
|
|
|
if (KEYGUARD_ANIMATION.get()) {
|
|
mKeyguardGoingAwayRunner = createWallpaperOpenRunner(true /* fromUnlock */);
|
|
definition.addRemoteAnimation(
|
|
WindowManager.TRANSIT_OLD_KEYGUARD_GOING_AWAY_ON_WALLPAPER,
|
|
new RemoteAnimationAdapter(
|
|
new LauncherAnimationRunner(
|
|
mHandler, mKeyguardGoingAwayRunner,
|
|
true /* startAtFrontOfQueue */),
|
|
CLOSING_TRANSITION_DURATION_MS, 0 /* statusBarTransitionDelay */));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Registers remote animations used when closing apps to home screen.
|
|
*/
|
|
public void registerRemoteTransitions() {
|
|
if (ENABLE_SHELL_TRANSITIONS) {
|
|
SystemUiProxy.INSTANCE.get(mLauncher).shareTransactionQueue();
|
|
}
|
|
if (SEPARATE_RECENTS_ACTIVITY.get()) {
|
|
return;
|
|
}
|
|
|
|
mWallpaperOpenTransitionRunner = createWallpaperOpenRunner(false /* fromUnlock */);
|
|
mLauncherOpenTransition = new RemoteTransition(
|
|
new LauncherAnimationRunner(mHandler, mWallpaperOpenTransitionRunner,
|
|
false /* startAtFrontOfQueue */).toRemoteTransition(),
|
|
mLauncher.getIApplicationThread(), "QuickstepLaunchHome");
|
|
|
|
TransitionFilter homeCheck = new TransitionFilter();
|
|
// No need to handle the transition that also dismisses keyguard.
|
|
homeCheck.mNotFlags = TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
|
|
homeCheck.mRequirements =
|
|
new TransitionFilter.Requirement[]{new TransitionFilter.Requirement(),
|
|
new TransitionFilter.Requirement()};
|
|
homeCheck.mRequirements[0].mActivityType = ACTIVITY_TYPE_HOME;
|
|
homeCheck.mRequirements[0].mTopActivity = mLauncher.getComponentName();
|
|
homeCheck.mRequirements[0].mModes = new int[]{TRANSIT_OPEN, TRANSIT_TO_FRONT};
|
|
homeCheck.mRequirements[0].mOrder = CONTAINER_ORDER_TOP;
|
|
homeCheck.mRequirements[1].mActivityType = ACTIVITY_TYPE_STANDARD;
|
|
homeCheck.mRequirements[1].mModes = new int[]{TRANSIT_CLOSE, TRANSIT_TO_BACK};
|
|
SystemUiProxy.INSTANCE.get(mLauncher)
|
|
.registerRemoteTransition(mLauncherOpenTransition, homeCheck);
|
|
if (mBackAnimationController != null) {
|
|
mBackAnimationController.registerComponentCallbacks();
|
|
mBackAnimationController.registerBackCallbacks(mHandler);
|
|
}
|
|
}
|
|
|
|
public void onActivityDestroyed() {
|
|
unregisterRemoteAnimations();
|
|
unregisterRemoteTransitions();
|
|
mLauncher.removeOnDeviceProfileChangeListener(this);
|
|
SystemUiProxy.INSTANCE.get(mLauncher).setStartingWindowListener(null);
|
|
ORDERED_BG_EXECUTOR.execute(() -> mLauncher.getContentResolver()
|
|
.unregisterContentObserver(mAnimationRemovalObserver));
|
|
}
|
|
|
|
private void unregisterRemoteAnimations() {
|
|
if (SEPARATE_RECENTS_ACTIVITY.get()) {
|
|
return;
|
|
}
|
|
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;
|
|
}
|
|
|
|
protected void unregisterRemoteTransitions() {
|
|
if (ENABLE_SHELL_TRANSITIONS) {
|
|
SystemUiProxy.INSTANCE.get(mLauncher).unshareTransactionQueue();
|
|
}
|
|
if (SEPARATE_RECENTS_ACTIVITY.get()) {
|
|
return;
|
|
}
|
|
if (mLauncherOpenTransition == null) return;
|
|
SystemUiProxy.INSTANCE.get(mLauncher).unregisterRemoteTransition(
|
|
mLauncherOpenTransition);
|
|
mLauncherOpenTransition = null;
|
|
mWallpaperOpenTransitionRunner = null;
|
|
if (mBackAnimationController != null) {
|
|
mBackAnimationController.unregisterBackCallbacks();
|
|
mBackAnimationController.unregisterComponentCallbacks();
|
|
mBackAnimationController = null;
|
|
}
|
|
}
|
|
|
|
private void checkAndMonitorIfAnimationsAreEnabled() {
|
|
ORDERED_BG_EXECUTOR.execute(() -> {
|
|
mAnimationRemovalObserver.onChange(true);
|
|
mLauncher.getContentResolver().registerContentObserver(Global.getUriFor(
|
|
Global.ANIMATOR_DURATION_SCALE), false, mAnimationRemovalObserver);
|
|
mLauncher.getContentResolver().registerContentObserver(Global.getUriFor(
|
|
Global.TRANSITION_ANIMATION_SCALE), false, mAnimationRemovalObserver);
|
|
|
|
});
|
|
}
|
|
|
|
private boolean launcherIsATargetWithMode(RemoteAnimationTarget[] targets, int mode) {
|
|
for (RemoteAnimationTarget target : targets) {
|
|
if (target.mode == mode && target.taskInfo != null
|
|
// Compare component name instead of task-id because transitions will promote
|
|
// the target up to the root task while getTaskId returns the leaf.
|
|
&& target.taskInfo.topActivity != null
|
|
&& target.taskInfo.topActivity.equals(mLauncher.getComponentName())) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private boolean shouldPlayFallbackClosingAnimation(RemoteAnimationTarget[] targets) {
|
|
int numTargets = 0;
|
|
for (RemoteAnimationTarget target : targets) {
|
|
if (target.mode == MODE_CLOSING) {
|
|
numTargets++;
|
|
if (numTargets > 1 || target.windowConfiguration.getWindowingMode()
|
|
== WINDOWING_MODE_MULTI_WINDOW) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* @return Runner that plays when user goes to Launcher
|
|
* ie. pressing home, swiping up from nav bar.
|
|
*/
|
|
RemoteAnimationFactory createWallpaperOpenRunner(boolean fromUnlock) {
|
|
return new WallpaperOpenLauncherAnimationRunner(fromUnlock);
|
|
}
|
|
|
|
/**
|
|
* Animator that controls the transformations of the windows when unlocking the device.
|
|
*/
|
|
private Animator getUnlockWindowAnimator(RemoteAnimationTarget[] appTargets,
|
|
RemoteAnimationTarget[] 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);
|
|
unlockAnimator.addListener(new AnimatorListenerAdapter() {
|
|
@Override
|
|
public void onAnimationStart(Animator animation) {
|
|
SurfaceTransaction transaction = new SurfaceTransaction();
|
|
for (int i = appTargets.length - 1; i >= 0; i--) {
|
|
RemoteAnimationTarget target = appTargets[i];
|
|
transaction.forSurface(target.leash)
|
|
.setAlpha(1f)
|
|
.setWindowCrop(target.screenSpaceBounds)
|
|
.setCornerRadius(cornerRadius);
|
|
}
|
|
surfaceApplier.scheduleApply(transaction);
|
|
}
|
|
});
|
|
return unlockAnimator;
|
|
}
|
|
|
|
private static int getRotationChange(RemoteAnimationTarget[] appTargets) {
|
|
int rotationChange = 0;
|
|
for (RemoteAnimationTarget target : appTargets) {
|
|
if (Math.abs(target.rotationChange) > Math.abs(rotationChange)) {
|
|
rotationChange = target.rotationChange;
|
|
}
|
|
}
|
|
return rotationChange;
|
|
}
|
|
|
|
/**
|
|
* Returns view on launcher that corresponds to the closing app in the list of app targets
|
|
*/
|
|
public @Nullable View findLauncherView(RemoteAnimationTarget[] appTargets) {
|
|
for (RemoteAnimationTarget appTarget : appTargets) {
|
|
if (appTarget.mode == MODE_CLOSING) {
|
|
View launcherView = findLauncherView(appTarget);
|
|
if (launcherView != null) {
|
|
return launcherView;
|
|
}
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Returns view on launcher that corresponds to the {@param runningTaskTarget}.
|
|
*/
|
|
private @Nullable View findLauncherView(RemoteAnimationTarget runningTaskTarget) {
|
|
if (runningTaskTarget == null || runningTaskTarget.taskInfo == null) {
|
|
return null;
|
|
}
|
|
|
|
final ComponentName[] taskInfoActivities = new ComponentName[]{
|
|
runningTaskTarget.taskInfo.baseActivity,
|
|
runningTaskTarget.taskInfo.origActivity,
|
|
runningTaskTarget.taskInfo.realActivity,
|
|
runningTaskTarget.taskInfo.topActivity};
|
|
|
|
String packageName = null;
|
|
for (ComponentName component : taskInfoActivities) {
|
|
if (component != null && component.getPackageName() != null) {
|
|
packageName = component.getPackageName();
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (packageName == null) {
|
|
return null;
|
|
}
|
|
|
|
// Find the associated item info for the launch cookie (if available), note that predicted
|
|
// apps actually have an id of -1, so use another default id here
|
|
final ArrayList<IBinder> launchCookies = runningTaskTarget.taskInfo.launchCookies == null
|
|
? new ArrayList<>()
|
|
: runningTaskTarget.taskInfo.launchCookies;
|
|
|
|
int launchCookieItemId = NO_MATCHING_ID;
|
|
for (IBinder cookie : launchCookies) {
|
|
Integer itemId = ObjectWrapper.unwrap(cookie);
|
|
if (itemId != null) {
|
|
launchCookieItemId = itemId;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return mLauncher.getFirstMatchForAppClose(launchCookieItemId, packageName,
|
|
UserHandle.of(runningTaskTarget.taskInfo.userId), true /* supportsAllAppsState */);
|
|
}
|
|
|
|
private @NonNull RectF getDefaultWindowTargetRect() {
|
|
RecentsView recentsView = mLauncher.getOverviewPanel();
|
|
PagedOrientationHandler orientationHandler = recentsView.getPagedOrientationHandler();
|
|
DeviceProfile dp = mLauncher.getDeviceProfile();
|
|
final int halfIconSize = dp.iconSizePx / 2;
|
|
float primaryDimension = orientationHandler
|
|
.getPrimaryValue(dp.availableWidthPx, dp.availableHeightPx);
|
|
float secondaryDimension = orientationHandler
|
|
.getSecondaryValue(dp.availableWidthPx, dp.availableHeightPx);
|
|
final float targetX = primaryDimension / 2f;
|
|
final float targetY = secondaryDimension - dp.hotseatBarSizePx;
|
|
return new RectF(targetX - halfIconSize, targetY - halfIconSize,
|
|
targetX + halfIconSize, targetY + halfIconSize);
|
|
}
|
|
|
|
/**
|
|
* Closing animator that animates the window into its final location on the workspace.
|
|
*/
|
|
protected RectFSpringAnim getClosingWindowAnimators(AnimatorSet animation,
|
|
RemoteAnimationTarget[] targets, View launcherView, PointF velocityPxPerS,
|
|
RectF closingWindowStartRectF, float startWindowCornerRadius) {
|
|
FloatingIconView floatingIconView = null;
|
|
FloatingWidgetView floatingWidget = null;
|
|
RectF targetRect = new RectF();
|
|
|
|
RemoteAnimationTarget runningTaskTarget = null;
|
|
boolean isTransluscent = false;
|
|
for (RemoteAnimationTarget target : targets) {
|
|
if (target.mode == MODE_CLOSING) {
|
|
runningTaskTarget = target;
|
|
isTransluscent = runningTaskTarget.isTranslucent;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Get floating view and target rect.
|
|
boolean isInHotseat = false;
|
|
if (launcherView instanceof LauncherAppWidgetHostView) {
|
|
Size windowSize = new Size(mDeviceProfile.availableWidthPx,
|
|
mDeviceProfile.availableHeightPx);
|
|
int fallbackBackgroundColor =
|
|
FloatingWidgetView.getDefaultBackgroundColor(mLauncher, runningTaskTarget);
|
|
floatingWidget = FloatingWidgetView.getFloatingWidgetView(mLauncher,
|
|
(LauncherAppWidgetHostView) launcherView, targetRect, windowSize,
|
|
mDeviceProfile.isMultiWindowMode ? 0 : getWindowCornerRadius(mLauncher),
|
|
isTransluscent, fallbackBackgroundColor);
|
|
} else if (launcherView != null && mAreAnimationsEnabled) {
|
|
floatingIconView = getFloatingIconView(mLauncher, launcherView, null,
|
|
mLauncher.getTaskbarUIController() == null
|
|
? null
|
|
: mLauncher.getTaskbarUIController().findMatchingView(launcherView),
|
|
true /* hideOriginal */, targetRect, false /* isOpening */);
|
|
isInHotseat = launcherView.getTag() instanceof ItemInfo
|
|
&& ((ItemInfo) launcherView.getTag()).isInHotseat();
|
|
} else {
|
|
targetRect.set(getDefaultWindowTargetRect());
|
|
}
|
|
|
|
boolean useTaskbarHotseatParams = mDeviceProfile.isTaskbarPresent && isInHotseat;
|
|
RectFSpringAnim anim = new RectFSpringAnim(useTaskbarHotseatParams
|
|
? new TaskbarHotseatSpringConfig(mLauncher, closingWindowStartRectF, targetRect)
|
|
: new DefaultSpringConfig(mLauncher, mDeviceProfile, closingWindowStartRectF,
|
|
targetRect));
|
|
|
|
// Hook up floating views to the closing window animators.
|
|
// note the coordinate of closingWindowStartRect is based on launcher
|
|
Rect closingWindowStartRect = new Rect();
|
|
closingWindowStartRectF.round(closingWindowStartRect);
|
|
Rect closingWindowOriginalRect =
|
|
new Rect(0, 0, mDeviceProfile.widthPx, mDeviceProfile.heightPx);
|
|
if (floatingIconView != null) {
|
|
anim.addAnimatorListener(floatingIconView);
|
|
floatingIconView.setOnTargetChangeListener(anim::onTargetPositionChanged);
|
|
floatingIconView.setFastFinishRunnable(anim::end);
|
|
FloatingIconView finalFloatingIconView = floatingIconView;
|
|
|
|
// We want the window alpha to be 0 once this threshold is met, so that the
|
|
// FolderIconView can be seen morphing into the icon shape.
|
|
final float windowAlphaThreshold = 1f - SHAPE_PROGRESS_DURATION;
|
|
|
|
RectFSpringAnim.OnUpdateListener runner = new SpringAnimRunner(targets, targetRect,
|
|
closingWindowStartRect, closingWindowOriginalRect, startWindowCornerRadius) {
|
|
@Override
|
|
public void onUpdate(RectF currentRectF, float progress) {
|
|
finalFloatingIconView.update(1f, currentRectF, progress, windowAlphaThreshold,
|
|
getCornerRadius(progress), false);
|
|
|
|
super.onUpdate(currentRectF, progress);
|
|
}
|
|
};
|
|
anim.addOnUpdateListener(runner);
|
|
} else if (floatingWidget != null) {
|
|
anim.addAnimatorListener(floatingWidget);
|
|
floatingWidget.setOnTargetChangeListener(anim::onTargetPositionChanged);
|
|
floatingWidget.setFastFinishRunnable(anim::end);
|
|
|
|
final float floatingWidgetAlpha = isTransluscent ? 0 : 1;
|
|
FloatingWidgetView finalFloatingWidget = floatingWidget;
|
|
RectFSpringAnim.OnUpdateListener runner = new SpringAnimRunner(targets, targetRect,
|
|
closingWindowStartRect, closingWindowOriginalRect, startWindowCornerRadius) {
|
|
@Override
|
|
public void onUpdate(RectF currentRectF, float progress) {
|
|
final float fallbackBackgroundAlpha =
|
|
1 - mapBoundToRange(progress, 0.8f, 1, 0, 1, EXAGGERATED_EASE);
|
|
final float foregroundAlpha =
|
|
mapBoundToRange(progress, 0.5f, 1, 0, 1, EXAGGERATED_EASE);
|
|
finalFloatingWidget.update(currentRectF, floatingWidgetAlpha, foregroundAlpha,
|
|
fallbackBackgroundAlpha, 1 - progress);
|
|
|
|
super.onUpdate(currentRectF, progress);
|
|
}
|
|
};
|
|
anim.addOnUpdateListener(runner);
|
|
} else {
|
|
// If no floating icon or widget is present, animate the to the default window
|
|
// target rect.
|
|
anim.addOnUpdateListener(new SpringAnimRunner(
|
|
targets, targetRect, closingWindowStartRect, closingWindowOriginalRect,
|
|
startWindowCornerRadius));
|
|
}
|
|
|
|
// Use a fixed velocity to start the animation.
|
|
animation.addListener(new AnimatorListenerAdapter() {
|
|
@Override
|
|
public void onAnimationStart(Animator animation) {
|
|
anim.start(mLauncher, mDeviceProfile, velocityPxPerS);
|
|
}
|
|
});
|
|
return anim;
|
|
}
|
|
|
|
/**
|
|
* Closing window animator that moves the window down and offscreen.
|
|
*/
|
|
private Animator getFallbackClosingWindowAnimators(RemoteAnimationTarget[] appTargets) {
|
|
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);
|
|
float startShadowRadius = areAllTargetsTranslucent(appTargets) ? 0 : mMaxShadowRadius;
|
|
closingAnimator.setDuration(duration);
|
|
closingAnimator.addUpdateListener(new MultiValueUpdateListener() {
|
|
FloatProp mDy = new FloatProp(0, mClosingWindowTransY, DECELERATE_1_7);
|
|
FloatProp mScale = new FloatProp(1f, 1f, DECELERATE_1_7);
|
|
FloatProp mAlpha = new FloatProp(1f, 0f, clampToDuration(LINEAR, 25, 125, duration));
|
|
FloatProp mShadowRadius = new FloatProp(startShadowRadius, 0, DECELERATE_1_7);
|
|
|
|
@Override
|
|
public void onUpdate(float percent, boolean initOnly) {
|
|
SurfaceTransaction transaction = new SurfaceTransaction();
|
|
for (int i = appTargets.length - 1; i >= 0; i--) {
|
|
RemoteAnimationTarget target = appTargets[i];
|
|
SurfaceProperties builder = transaction.forSurface(target.leash);
|
|
|
|
if (target.screenSpaceBounds != null) {
|
|
tmpPos.set(target.screenSpaceBounds.left, target.screenSpaceBounds.top);
|
|
} else {
|
|
tmpPos.set(target.position.x, target.position.y);
|
|
}
|
|
|
|
final Rect crop = new Rect(target.localBounds);
|
|
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.setMatrix(matrix)
|
|
.setWindowCrop(crop)
|
|
.setAlpha(mAlpha.value)
|
|
.setCornerRadius(windowCornerRadius)
|
|
.setShadowRadius(mShadowRadius.value);
|
|
} else if (target.mode == MODE_OPENING) {
|
|
matrix.setTranslate(tmpPos.x, tmpPos.y);
|
|
builder.setMatrix(matrix)
|
|
.setWindowCrop(crop)
|
|
.setAlpha(1f);
|
|
}
|
|
}
|
|
surfaceApplier.scheduleApply(transaction);
|
|
}
|
|
});
|
|
|
|
return closingAnimator;
|
|
}
|
|
|
|
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);
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Creates the {@link RectFSpringAnim} and {@link AnimatorSet} required to animate
|
|
* the transition.
|
|
*/
|
|
public Pair<RectFSpringAnim, AnimatorSet> createWallpaperOpenAnimations(
|
|
RemoteAnimationTarget[] appTargets,
|
|
RemoteAnimationTarget[] wallpaperTargets,
|
|
boolean fromUnlock,
|
|
RectF startRect,
|
|
float startWindowCornerRadius,
|
|
boolean fromPredictiveBack) {
|
|
AnimatorSet anim = new AnimatorSet();
|
|
RectFSpringAnim rectFSpringAnim = null;
|
|
|
|
final boolean launcherIsForceInvisibleOrOpening = mLauncher.isForceInvisible()
|
|
|| launcherIsATargetWithMode(appTargets, MODE_OPENING);
|
|
|
|
View launcherView = findLauncherView(appTargets);
|
|
boolean playFallBackAnimation = (launcherView == null
|
|
&& launcherIsForceInvisibleOrOpening)
|
|
|| mLauncher.getWorkspace().isOverlayShown()
|
|
|| shouldPlayFallbackClosingAnimation(appTargets);
|
|
|
|
boolean playWorkspaceReveal = !fromPredictiveBack;
|
|
boolean skipAllAppsScale = false;
|
|
if (fromUnlock) {
|
|
anim.play(getUnlockWindowAnimator(appTargets, wallpaperTargets));
|
|
} else if (ENABLE_BACK_SWIPE_HOME_ANIMATION.get()
|
|
&& !playFallBackAnimation) {
|
|
// Use a fixed velocity to start the animation.
|
|
float velocityPxPerS = DynamicResource.provider(mLauncher)
|
|
.getDimension(R.dimen.unlock_staggered_velocity_dp_per_s);
|
|
PointF velocity = new PointF(0, -velocityPxPerS);
|
|
rectFSpringAnim = getClosingWindowAnimators(
|
|
anim, appTargets, launcherView, velocity, startRect,
|
|
startWindowCornerRadius);
|
|
if (mLauncher.isInState(LauncherState.ALL_APPS)) {
|
|
// Skip scaling all apps, otherwise FloatingIconView will get wrong
|
|
// layout bounds.
|
|
skipAllAppsScale = true;
|
|
} else if (!fromPredictiveBack) {
|
|
anim.play(new StaggeredWorkspaceAnim(mLauncher, velocity.y,
|
|
true /* animateOverviewScrim */, launcherView).getAnimators());
|
|
|
|
if (!areAllTargetsTranslucent(appTargets)) {
|
|
anim.play(ObjectAnimator.ofFloat(mLauncher.getDepthController().stateDepth,
|
|
MULTI_PROPERTY_VALUE,
|
|
BACKGROUND_APP.getDepth(mLauncher), NORMAL.getDepth(mLauncher)));
|
|
}
|
|
|
|
// We play StaggeredWorkspaceAnim as a part of the closing window animation.
|
|
playWorkspaceReveal = false;
|
|
}
|
|
} else {
|
|
anim.play(getFallbackClosingWindowAnimators(appTargets));
|
|
}
|
|
|
|
// 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 (launcherIsForceInvisibleOrOpening || fromPredictiveBack) {
|
|
addCujInstrumentation(anim, playFallBackAnimation
|
|
? Cuj.CUJ_LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK
|
|
: Cuj.CUJ_LAUNCHER_APP_CLOSE_TO_HOME);
|
|
|
|
AnimatorListenerAdapter endListener = new AnimatorListenerAdapter() {
|
|
@Override
|
|
public void onAnimationEnd(Animator animation) {
|
|
super.onAnimationEnd(animation);
|
|
AccessibilityManagerCompat.sendTestProtocolEventToTest(
|
|
mLauncher, WALLPAPER_OPEN_ANIMATION_FINISHED_MESSAGE);
|
|
}
|
|
};
|
|
|
|
if (fromPredictiveBack && rectFSpringAnim != null) {
|
|
rectFSpringAnim.addAnimatorListener(endListener);
|
|
} else {
|
|
anim.addListener(endListener);
|
|
}
|
|
|
|
// Only register the content animation for cancellation when state changes
|
|
mLauncher.getStateManager().setCurrentAnimation(anim);
|
|
|
|
if (mLauncher.isInState(LauncherState.ALL_APPS) && !fromPredictiveBack) {
|
|
Pair<AnimatorSet, Runnable> contentAnimator =
|
|
getLauncherContentAnimator(false, LAUNCHER_RESUME_START_DELAY,
|
|
skipAllAppsScale);
|
|
anim.play(contentAnimator.first);
|
|
anim.addListener(new AnimatorListenerAdapter() {
|
|
@Override
|
|
public void onAnimationEnd(Animator animation) {
|
|
contentAnimator.second.run();
|
|
}
|
|
});
|
|
} else if (playWorkspaceReveal) {
|
|
anim.play(new WorkspaceRevealAnim(mLauncher, false).getAnimators());
|
|
}
|
|
}
|
|
|
|
return new Pair(rectFSpringAnim, anim);
|
|
}
|
|
|
|
public static int getTaskbarToHomeDuration() {
|
|
if (enableScalingRevealHomeAnimation()) {
|
|
return TASKBAR_TO_HOME_DURATION_SLOW;
|
|
} else {
|
|
return TASKBAR_TO_HOME_DURATION_FAST;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Remote animation runner for animation from the app to Launcher, including recents.
|
|
*/
|
|
protected class WallpaperOpenLauncherAnimationRunner implements RemoteAnimationFactory {
|
|
|
|
private final boolean mFromUnlock;
|
|
|
|
public WallpaperOpenLauncherAnimationRunner(boolean fromUnlock) {
|
|
mFromUnlock = fromUnlock;
|
|
}
|
|
|
|
@Override
|
|
public void onAnimationStart(int transit,
|
|
RemoteAnimationTarget[] appTargets,
|
|
RemoteAnimationTarget[] wallpaperTargets,
|
|
RemoteAnimationTarget[] nonAppTargets,
|
|
LauncherAnimationRunner.AnimationResult result) {
|
|
if (mLauncher.isDestroyed()) {
|
|
AnimatorSet anim = new AnimatorSet();
|
|
anim.play(getFallbackClosingWindowAnimators(appTargets));
|
|
result.setAnimation(anim, mLauncher.getApplicationContext());
|
|
return;
|
|
}
|
|
|
|
if (mLauncher.hasSomeInvisibleFlag(PENDING_INVISIBLE_BY_WALLPAPER_ANIMATION)) {
|
|
mLauncher.addForceInvisibleFlag(INVISIBLE_BY_PENDING_FLAGS);
|
|
mLauncher.getStateManager().moveToRestState();
|
|
}
|
|
|
|
RectF windowTargetBounds =
|
|
new RectF(getWindowTargetBounds(appTargets, getRotationChange(appTargets)));
|
|
|
|
final RectF resolveRectF = new RectF(windowTargetBounds);
|
|
for (RemoteAnimationTarget t : appTargets) {
|
|
if (t.mode == MODE_CLOSING) {
|
|
transferRectToTargetCoordinate(
|
|
t, windowTargetBounds, true, resolveRectF);
|
|
break;
|
|
}
|
|
}
|
|
|
|
Pair<RectFSpringAnim, AnimatorSet> pair = createWallpaperOpenAnimations(
|
|
appTargets, wallpaperTargets, mFromUnlock, resolveRectF,
|
|
QuickStepContract.getWindowCornerRadius(mLauncher),
|
|
false /* fromPredictiveBack */);
|
|
|
|
TaskViewUtils.createSplitAuxiliarySurfacesAnimator(nonAppTargets, false, null);
|
|
mLauncher.clearForceInvisibleFlag(INVISIBLE_ALL);
|
|
result.setAnimation(pair.second, 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 onAnimationStart(int transit,
|
|
RemoteAnimationTarget[] appTargets,
|
|
RemoteAnimationTarget[] wallpaperTargets,
|
|
RemoteAnimationTarget[] 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, launcherClosing);
|
|
addCujInstrumentation(anim, Cuj.CUJ_LAUNCHER_APP_LAUNCH_FROM_WIDGET);
|
|
skipFirstFrame = true;
|
|
} else if (launchingFromRecents) {
|
|
composeRecentsLaunchAnimator(anim, mV, appTargets, wallpaperTargets, nonAppTargets,
|
|
launcherClosing);
|
|
addCujInstrumentation(
|
|
anim, Cuj.CUJ_LAUNCHER_APP_LAUNCH_FROM_RECENTS);
|
|
skipFirstFrame = true;
|
|
} else {
|
|
composeIconLaunchAnimator(anim, mV, appTargets, wallpaperTargets, nonAppTargets,
|
|
launcherClosing);
|
|
addCujInstrumentation(anim, Cuj.CUJ_LAUNCHER_APP_LAUNCH_FROM_ICON);
|
|
skipFirstFrame = false;
|
|
}
|
|
|
|
if (launcherClosing) {
|
|
anim.addListener(mForceInvisibleListener);
|
|
}
|
|
|
|
result.setAnimation(anim, mLauncher, mOnEndCallback::executeAllAndDestroy,
|
|
skipFirstFrame);
|
|
}
|
|
|
|
@Override
|
|
public void onAnimationCancelled() {
|
|
mOnEndCallback.executeAllAndDestroy();
|
|
}
|
|
}
|
|
|
|
/** Remote animation runner to launch an app using System UI's animation library. */
|
|
private static class ContainerAnimationRunner implements RemoteAnimationFactory {
|
|
|
|
/** The delegate runner that handles the actual animation. */
|
|
private final RemoteAnimationDelegate<IRemoteAnimationFinishedCallback> mDelegate;
|
|
|
|
private ContainerAnimationRunner(
|
|
RemoteAnimationDelegate<IRemoteAnimationFinishedCallback> delegate) {
|
|
mDelegate = delegate;
|
|
}
|
|
|
|
@Nullable
|
|
private static ContainerAnimationRunner from(View v, Launcher launcher,
|
|
StartingWindowListener startingWindowListener, RunnableList onEndCallback) {
|
|
View viewToUse = findLaunchableViewWithBackground(v);
|
|
if (viewToUse == null) {
|
|
return null;
|
|
}
|
|
|
|
// The CUJ is logged by the click handler, so we don't log it inside the animation
|
|
// library.
|
|
ActivityTransitionAnimator.Controller controllerDelegate =
|
|
ActivityTransitionAnimator.Controller.fromView(viewToUse, null /* cujType */);
|
|
|
|
if (controllerDelegate == null) {
|
|
return null;
|
|
}
|
|
|
|
// This wrapper allows us to override the default value, telling the controller that the
|
|
// current window is below the animating window.
|
|
ActivityTransitionAnimator.Controller controller =
|
|
new DelegateTransitionAnimatorController(controllerDelegate) {
|
|
@Override
|
|
public boolean isBelowAnimatingWindow() {
|
|
return true;
|
|
}
|
|
};
|
|
|
|
ActivityTransitionAnimator.Callback callback = task -> {
|
|
final int backgroundColor =
|
|
startingWindowListener.mBackgroundColor == Color.TRANSPARENT
|
|
? launcher.getScrimView().getBackgroundColor()
|
|
: startingWindowListener.mBackgroundColor;
|
|
return ColorUtils.setAlphaComponent(backgroundColor, 255);
|
|
};
|
|
|
|
ActivityTransitionAnimator.Listener listener =
|
|
new ActivityTransitionAnimator.Listener() {
|
|
@Override
|
|
public void onTransitionAnimationEnd() {
|
|
onEndCallback.executeAllAndDestroy();
|
|
}
|
|
};
|
|
|
|
return new ContainerAnimationRunner(
|
|
new ActivityTransitionAnimator.AnimationDelegate(
|
|
MAIN_EXECUTOR, controller, callback, listener));
|
|
}
|
|
|
|
/**
|
|
* Finds the closest parent of [view] (inclusive) that implements {@link LaunchableView} and
|
|
* has a background drawable.
|
|
*/
|
|
@Nullable
|
|
private static <T extends View & LaunchableView> T findLaunchableViewWithBackground(
|
|
View view) {
|
|
View current = view;
|
|
while (current.getBackground() == null || !(current instanceof LaunchableView)) {
|
|
if (!(current.getParent() instanceof View)) {
|
|
return null;
|
|
}
|
|
|
|
current = (View) current.getParent();
|
|
}
|
|
|
|
return (T) current;
|
|
}
|
|
|
|
@Override
|
|
public void onAnimationStart(int transit, RemoteAnimationTarget[] appTargets,
|
|
RemoteAnimationTarget[] wallpaperTargets, RemoteAnimationTarget[] nonAppTargets,
|
|
LauncherAnimationRunner.AnimationResult result) {
|
|
mDelegate.onAnimationStart(
|
|
transit, appTargets, wallpaperTargets, nonAppTargets, result);
|
|
}
|
|
|
|
@Override
|
|
public void onAnimationCancelled() {
|
|
mDelegate.onAnimationCancelled();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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 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();
|
|
|
|
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 final WeakReference<QuickstepTransitionManager> mTransitionManagerRef;
|
|
private int mBackgroundColor;
|
|
|
|
private StartingWindowListener(QuickstepTransitionManager transitionManager) {
|
|
mTransitionManagerRef = new WeakReference<>(transitionManager);
|
|
}
|
|
|
|
@Override
|
|
public void onTaskLaunching(int taskId, int supportedType, int color) {
|
|
QuickstepTransitionManager transitionManager = mTransitionManagerRef.get();
|
|
if (transitionManager != null) {
|
|
transitionManager.mTaskStartParams.put(taskId, Pair.create(supportedType, color));
|
|
}
|
|
mBackgroundColor = color;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Transfer the rectangle to another coordinate if needed.
|
|
*
|
|
* @param toLauncher which one is the anchor of this transfer, if true then transfer from
|
|
* animation target to launcher, false transfer from launcher to animation
|
|
* target.
|
|
*/
|
|
public void transferRectToTargetCoordinate(RemoteAnimationTarget target, RectF currentRect,
|
|
boolean toLauncher, RectF resultRect) {
|
|
mCoordinateTransfer.transferRectToTargetCoordinate(
|
|
target, currentRect, toLauncher, resultRect);
|
|
}
|
|
|
|
private static class RemoteAnimationCoordinateTransfer {
|
|
private final QuickstepLauncher mLauncher;
|
|
private final Rect mDisplayRect = new Rect();
|
|
private final Rect mTmpResult = new Rect();
|
|
|
|
RemoteAnimationCoordinateTransfer(QuickstepLauncher launcher) {
|
|
mLauncher = launcher;
|
|
}
|
|
|
|
void transferRectToTargetCoordinate(RemoteAnimationTarget target, RectF currentRect,
|
|
boolean toLauncher, RectF resultRect) {
|
|
final int taskRotation = target.windowConfiguration.getRotation();
|
|
final DeviceProfile profile = mLauncher.getDeviceProfile();
|
|
|
|
final int rotationDelta = toLauncher
|
|
? android.util.RotationUtils.deltaRotation(taskRotation, profile.rotationHint)
|
|
: android.util.RotationUtils.deltaRotation(profile.rotationHint, taskRotation);
|
|
if (rotationDelta != ROTATION_0) {
|
|
// Get original display size when task is on top but with different rotation
|
|
if (rotationDelta % 2 != 0 && toLauncher && (profile.rotationHint == ROTATION_0
|
|
|| profile.rotationHint == ROTATION_180)) {
|
|
mDisplayRect.set(0, 0, profile.heightPx, profile.widthPx);
|
|
} else {
|
|
mDisplayRect.set(0, 0, profile.widthPx, profile.heightPx);
|
|
}
|
|
currentRect.round(mTmpResult);
|
|
android.util.RotationUtils.rotateBounds(mTmpResult, mDisplayRect, rotationDelta);
|
|
resultRect.set(mTmpResult);
|
|
} else {
|
|
resultRect.set(currentRect);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* RectFSpringAnim update listener to be used for app to home animation.
|
|
*/
|
|
private class SpringAnimRunner implements RectFSpringAnim.OnUpdateListener {
|
|
private final RemoteAnimationTarget[] mAppTargets;
|
|
private final Matrix mMatrix = new Matrix();
|
|
private final Point mTmpPos = new Point();
|
|
private final RectF mCurrentRectF = new RectF();
|
|
private final float mStartRadius;
|
|
private final float mEndRadius;
|
|
private final SurfaceTransactionApplier mSurfaceApplier;
|
|
private final Rect mWindowStartBounds = new Rect();
|
|
private final Rect mWindowOriginalBounds = new Rect();
|
|
|
|
private final Rect mTmpRect = new Rect();
|
|
|
|
/**
|
|
* Constructor for SpringAnimRunner
|
|
*
|
|
* @param appTargets the list of opening/closing apps
|
|
* @param targetRect target rectangle
|
|
* @param closingWindowStartRect start position of the window when the spring animation
|
|
* is started. In the predictive back to home case this
|
|
* will be smaller than closingWindowOriginalRect because
|
|
* the window is already scaled by the user gesture
|
|
* @param closingWindowOriginalRect Original unscaled window rect
|
|
* @param startWindowCornerRadius corner radius of window at the start position
|
|
*/
|
|
SpringAnimRunner(RemoteAnimationTarget[] appTargets, RectF targetRect,
|
|
Rect closingWindowStartRect, Rect closingWindowOriginalRect,
|
|
float startWindowCornerRadius) {
|
|
mAppTargets = appTargets;
|
|
mStartRadius = startWindowCornerRadius;
|
|
mEndRadius = Math.max(1, targetRect.width()) / 2f;
|
|
mSurfaceApplier = new SurfaceTransactionApplier(mDragLayer);
|
|
mWindowStartBounds.set(closingWindowStartRect);
|
|
mWindowOriginalBounds.set(closingWindowOriginalRect);
|
|
|
|
// transfer the coordinate based on animation target.
|
|
if (mAppTargets != null) {
|
|
for (RemoteAnimationTarget t : mAppTargets) {
|
|
if (t.mode == MODE_CLOSING) {
|
|
final RectF transferRect = new RectF(mWindowStartBounds);
|
|
final RectF result = new RectF();
|
|
transferRectToTargetCoordinate(t, transferRect, false, result);
|
|
result.round(mWindowStartBounds);
|
|
|
|
transferRect.set(closingWindowOriginalRect);
|
|
transferRectToTargetCoordinate(t, transferRect, false, result);
|
|
result.round(mWindowOriginalBounds);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public float getCornerRadius(float progress) {
|
|
return Utilities.mapRange(progress, mStartRadius, mEndRadius);
|
|
}
|
|
|
|
@Override
|
|
public void onUpdate(RectF currentRectF, float progress) {
|
|
SurfaceTransaction transaction = new SurfaceTransaction();
|
|
for (int i = mAppTargets.length - 1; i >= 0; i--) {
|
|
RemoteAnimationTarget target = mAppTargets[i];
|
|
SurfaceProperties builder = transaction.forSurface(target.leash);
|
|
|
|
if (target.localBounds != null) {
|
|
mTmpPos.set(target.localBounds.left, target.localBounds.top);
|
|
} else {
|
|
mTmpPos.set(target.position.x, target.position.y);
|
|
}
|
|
|
|
if (target.mode == MODE_CLOSING) {
|
|
transferRectToTargetCoordinate(target, currentRectF, false, mCurrentRectF);
|
|
|
|
// Scale the target window to match the currentRectF.
|
|
final float scale;
|
|
|
|
// We need to infer the crop (we crop the window to match the currentRectF).
|
|
if (mWindowStartBounds.height() > mWindowStartBounds.width()) {
|
|
scale = Math.min(1f, mCurrentRectF.width() / mWindowOriginalBounds.width());
|
|
|
|
int unscaledHeight = (int) (mCurrentRectF.height() * (1f / scale));
|
|
int croppedHeight = mWindowStartBounds.height() - unscaledHeight;
|
|
mTmpRect.set(0, 0, mWindowOriginalBounds.width(),
|
|
mWindowStartBounds.height() - croppedHeight);
|
|
} else {
|
|
scale = Math.min(1f, mCurrentRectF.height()
|
|
/ mWindowOriginalBounds.height());
|
|
|
|
int unscaledWidth = (int) (mCurrentRectF.width() * (1f / scale));
|
|
int croppedWidth = mWindowStartBounds.width() - unscaledWidth;
|
|
mTmpRect.set(0, 0, mWindowStartBounds.width() - croppedWidth,
|
|
mWindowOriginalBounds.height());
|
|
}
|
|
|
|
// Match size and position of currentRect.
|
|
mMatrix.setScale(scale, scale);
|
|
mMatrix.postTranslate(mCurrentRectF.left, mCurrentRectF.top);
|
|
|
|
builder.setMatrix(mMatrix)
|
|
.setWindowCrop(mTmpRect)
|
|
.setAlpha(getWindowAlpha(progress))
|
|
.setCornerRadius(getCornerRadius(progress) / scale);
|
|
} else if (target.mode == MODE_OPENING) {
|
|
mMatrix.setTranslate(mTmpPos.x, mTmpPos.y);
|
|
builder.setMatrix(mMatrix)
|
|
.setAlpha(1f);
|
|
}
|
|
}
|
|
mSurfaceApplier.scheduleApply(transaction);
|
|
}
|
|
|
|
protected float getWindowAlpha(float progress) {
|
|
// Alpha interpolates between [1, 0] between progress values [start, end]
|
|
final float start = 0f;
|
|
final float end = 0.85f;
|
|
|
|
if (progress <= start) {
|
|
return 1f;
|
|
}
|
|
if (progress >= end) {
|
|
return 0f;
|
|
}
|
|
return Utilities.mapToRange(progress, start, end, 1, 0, ACCELERATE_1_5);
|
|
}
|
|
}
|
|
|
|
private static class LaunchDepthController extends DepthController {
|
|
LaunchDepthController(QuickstepLauncher launcher) {
|
|
super(launcher);
|
|
setCrossWindowBlursEnabled(
|
|
CrossWindowBlurListeners.getInstance().isCrossWindowBlurEnabled());
|
|
// Make sure that the starting value matches the current depth set by the main
|
|
// controller.
|
|
stateDepth.setValue(launcher.getDepthController().stateDepth.getValue());
|
|
}
|
|
}
|
|
}
|