summaryrefslogtreecommitdiffstats
path: root/quickstep/src/com/android
diff options
context:
space:
mode:
Diffstat (limited to 'quickstep/src/com/android')
-rw-r--r--quickstep/src/com/android/launcher3/LauncherAnimationRunner.java127
-rw-r--r--quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java608
-rw-r--r--quickstep/src/com/android/launcher3/LauncherInitListener.java2
-rw-r--r--quickstep/src/com/android/launcher3/uioverrides/AllAppsState.java15
-rw-r--r--quickstep/src/com/android/launcher3/uioverrides/DisplayRotationListener.java48
-rw-r--r--quickstep/src/com/android/launcher3/uioverrides/FastOverviewState.java12
-rw-r--r--quickstep/src/com/android/launcher3/uioverrides/LandscapeEdgeSwipeController.java24
-rw-r--r--quickstep/src/com/android/launcher3/uioverrides/OverviewState.java29
-rw-r--r--quickstep/src/com/android/launcher3/uioverrides/OverviewToAllAppsTouchController.java (renamed from quickstep/src/com/android/launcher3/uioverrides/LandscapeStatesTouchController.java)14
-rw-r--r--quickstep/src/com/android/launcher3/uioverrides/PortraitStatesTouchController.java159
-rw-r--r--quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java37
-rw-r--r--quickstep/src/com/android/launcher3/uioverrides/TaskViewTouchController.java92
-rw-r--r--quickstep/src/com/android/launcher3/uioverrides/UiFactory.java138
-rw-r--r--quickstep/src/com/android/launcher3/uioverrides/WallpaperColorInfo.java115
-rw-r--r--quickstep/src/com/android/quickstep/ActivityControlHelper.java386
-rw-r--r--quickstep/src/com/android/quickstep/LongSwipeHelper.java179
-rw-r--r--quickstep/src/com/android/quickstep/MotionEventQueue.java2
-rw-r--r--quickstep/src/com/android/quickstep/MultiStateCallback.java4
-rw-r--r--quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java70
-rw-r--r--quickstep/src/com/android/quickstep/OverviewCallbacks.java45
-rw-r--r--quickstep/src/com/android/quickstep/OverviewCommandHelper.java240
-rw-r--r--quickstep/src/com/android/quickstep/OverviewInteractionState.java150
-rw-r--r--quickstep/src/com/android/quickstep/QuickScrubController.java111
-rw-r--r--quickstep/src/com/android/quickstep/RecentsActivity.java190
-rw-r--r--quickstep/src/com/android/quickstep/RecentsActivityTracker.java94
-rw-r--r--quickstep/src/com/android/quickstep/RecentsAnimationInterpolator.java117
-rw-r--r--quickstep/src/com/android/quickstep/RecentsAnimationWrapper.java93
-rw-r--r--quickstep/src/com/android/quickstep/RecentsModel.java28
-rw-r--r--quickstep/src/com/android/quickstep/TaskSystemShortcut.java80
-rw-r--r--quickstep/src/com/android/quickstep/TaskUtils.java172
-rw-r--r--quickstep/src/com/android/quickstep/TouchConsumer.java2
-rw-r--r--quickstep/src/com/android/quickstep/TouchInteractionService.java65
-rw-r--r--quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java590
-rw-r--r--quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java14
-rw-r--r--quickstep/src/com/android/quickstep/fallback/RecentsRootView.java27
-rw-r--r--quickstep/src/com/android/quickstep/logging/UserEventDispatcherExtension.java85
-rw-r--r--quickstep/src/com/android/quickstep/util/ClipAnimationHelper.java207
-rw-r--r--quickstep/src/com/android/quickstep/util/LayoutUtils.java59
-rw-r--r--quickstep/src/com/android/quickstep/util/MultiValueUpdateListener.java68
-rw-r--r--quickstep/src/com/android/quickstep/util/RemoteAnimationProvider.java25
-rw-r--r--quickstep/src/com/android/quickstep/util/RemoteAnimationTargetSet.java61
-rw-r--r--quickstep/src/com/android/quickstep/util/SysuiEventLogger.java47
-rw-r--r--quickstep/src/com/android/quickstep/util/TaskViewDrawable.java142
-rw-r--r--quickstep/src/com/android/quickstep/views/ClearAllButton.java46
-rw-r--r--quickstep/src/com/android/quickstep/views/IconView.java91
-rw-r--r--quickstep/src/com/android/quickstep/views/LauncherLayoutListener.java2
-rw-r--r--quickstep/src/com/android/quickstep/views/LauncherRecentsView.java37
-rw-r--r--quickstep/src/com/android/quickstep/views/QuickstepDragIndicator.java70
-rw-r--r--quickstep/src/com/android/quickstep/views/RecentsView.java583
-rw-r--r--quickstep/src/com/android/quickstep/views/RecentsViewContainer.java107
-rw-r--r--quickstep/src/com/android/quickstep/views/ShelfScrimView.java259
-rw-r--r--quickstep/src/com/android/quickstep/views/TaskThumbnailView.java87
-rw-r--r--quickstep/src/com/android/quickstep/views/TaskView.java145
53 files changed, 4512 insertions, 1688 deletions
diff --git a/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java b/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java
index f91933979..e34631042 100644
--- a/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java
+++ b/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java
@@ -15,8 +15,9 @@
*/
package com.android.launcher3;
-import static com.android.systemui.shared.recents.utilities.Utilities
- .postAtFrontOfQueueAsynchronously;
+import static com.android.launcher3.Utilities.SINGLE_FRAME_MS;
+import static com.android.launcher3.Utilities.postAsyncCallback;
+import static com.android.systemui.shared.recents.utilities.Utilities.postAtFrontOfQueueAsynchronously;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -31,53 +32,49 @@ import com.android.systemui.shared.system.RemoteAnimationRunnerCompat;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
@TargetApi(Build.VERSION_CODES.P)
-public abstract class LauncherAnimationRunner extends AnimatorListenerAdapter
- implements RemoteAnimationRunnerCompat {
-
- private static final int REFRESH_RATE_MS = 16;
+public abstract class LauncherAnimationRunner implements RemoteAnimationRunnerCompat {
private final Handler mHandler;
+ private final boolean mStartAtFrontOfQueue;
+ private AnimationResult mAnimationResult;
- private Runnable mSysFinishRunnable;
-
- private AnimatorSet mAnimator;
-
- public LauncherAnimationRunner(Handler handler) {
+ /**
+ * @param startAtFrontOfQueue If true, the animation start will be posted at the front of the
+ * queue to minimize latency.
+ */
+ public LauncherAnimationRunner(Handler handler, boolean startAtFrontOfQueue) {
mHandler = handler;
+ mStartAtFrontOfQueue = startAtFrontOfQueue;
}
@BinderThread
@Override
public void onAnimationStart(RemoteAnimationTargetCompat[] targetCompats, Runnable runnable) {
- postAtFrontOfQueueAsynchronously(mHandler, () -> {
- // Finish any previous animation
- finishSystemAnimation();
-
- mSysFinishRunnable = runnable;
- mAnimator = getAnimator(targetCompats);
- if (mAnimator == null) {
- finishSystemAnimation();
- return;
- }
- mAnimator.addListener(this);
- mAnimator.start();
- // Because t=0 has the app icon in its original spot, we can skip the
- // first frame and have the same movement one frame earlier.
- mAnimator.setCurrentPlayTime(REFRESH_RATE_MS);
-
- });
+ Runnable r = () -> {
+ finishExistingAnimation();
+ mAnimationResult = new AnimationResult(runnable);
+ onCreateAnimation(targetCompats, mAnimationResult);
+ };
+ if (mStartAtFrontOfQueue) {
+ postAtFrontOfQueueAsynchronously(mHandler, r);
+ } else {
+ postAsyncCallback(mHandler, r);
+ }
}
-
+ /**
+ * Called on the UI thread when the animation targets are received. The implementation must
+ * call {@link AnimationResult#setAnimation(AnimatorSet)} with the target animation to be run.
+ */
@UiThread
- public abstract AnimatorSet getAnimator(RemoteAnimationTargetCompat[] targetCompats);
+ public abstract void onCreateAnimation(
+ RemoteAnimationTargetCompat[] targetCompats, AnimationResult result);
@UiThread
- @Override
- public void onAnimationEnd(Animator animation) {
- if (animation == mAnimator) {
- mAnimator = null;
- finishSystemAnimation();
+ private void finishExistingAnimation() {
+ if (mAnimationResult != null) {
+ mAnimationResult.finish();
+ mAnimationResult = null;
}
}
@@ -87,20 +84,56 @@ public abstract class LauncherAnimationRunner extends AnimatorListenerAdapter
@BinderThread
@Override
public void onAnimationCancelled() {
- postAtFrontOfQueueAsynchronously(mHandler, () -> {
- if (mAnimator != null) {
- mAnimator.removeListener(this);
- mAnimator.end();
- mAnimator = null;
- }
- });
+ postAsyncCallback(mHandler, this::finishExistingAnimation);
}
- @UiThread
- private void finishSystemAnimation() {
- if (mSysFinishRunnable != null) {
- mSysFinishRunnable.run();
- mSysFinishRunnable = null;
+ public static final class AnimationResult {
+
+ private final Runnable mFinishRunnable;
+
+ private AnimatorSet mAnimator;
+ private boolean mFinished = false;
+ private boolean mInitialized = false;
+
+ private AnimationResult(Runnable finishRunnable) {
+ mFinishRunnable = finishRunnable;
+ }
+
+ @UiThread
+ private void finish() {
+ if (!mFinished) {
+ mFinishRunnable.run();
+ mFinished = true;
+ }
+ }
+
+ @UiThread
+ public void setAnimation(AnimatorSet animation) {
+ if (mInitialized) {
+ throw new IllegalStateException("Animation already initialized");
+ }
+ mInitialized = true;
+ mAnimator = animation;
+ if (mAnimator == null) {
+ finish();
+ } else if (mFinished) {
+ // Animation callback was already finished, skip the animation.
+ mAnimator.start();
+ mAnimator.end();
+ } else {
+ // Start the animation
+ mAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ finish();
+ }
+ });
+ mAnimator.start();
+
+ // Because t=0 has the app icon in its original spot, we can skip the
+ // first frame and have the same movement one frame earlier.
+ mAnimator.setCurrentPlayTime(SINGLE_FRAME_MS);
+ }
}
}
} \ No newline at end of file
diff --git a/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java b/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
index ad0b7344b..2e31ef239 100644
--- a/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
+++ b/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
@@ -16,9 +16,21 @@
package com.android.launcher3;
+import static com.android.launcher3.BaseActivity.INVISIBLE_ALL;
+import static com.android.launcher3.BaseActivity.INVISIBLE_BY_APP_TRANSITIONS;
import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
+import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.LauncherState.OVERVIEW;
+import static com.android.launcher3.Utilities.postAsyncCallback;
import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PROGRESS;
+import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE;
+import static com.android.launcher3.anim.Interpolators.DEACCEL_1_7;
+import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.launcher3.dragndrop.DragLayer.ALPHA_INDEX_TRANSITIONS;
+import static com.android.quickstep.TaskUtils.findTaskViewToLaunch;
+import static com.android.quickstep.TaskUtils.getRecentsWindowAnimator;
+import static com.android.quickstep.TaskUtils.taskIsATargetWithMode;
import static com.android.systemui.shared.recents.utilities.Utilities.getNextFrameNumber;
import static com.android.systemui.shared.recents.utilities.Utilities.getSurface;
import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
@@ -31,7 +43,6 @@ import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.annotation.TargetApi;
import android.app.ActivityOptions;
-import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.res.Resources;
@@ -42,10 +53,10 @@ import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
+import android.util.Pair;
import android.view.Surface;
import android.view.View;
import android.view.ViewGroup;
-import android.view.animation.Interpolator;
import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
import com.android.launcher3.InsettableFrameLayout.LayoutParams;
@@ -55,12 +66,13 @@ import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.graphics.DrawableFactory;
import com.android.launcher3.shortcuts.DeepShortcutView;
-import com.android.quickstep.RecentsAnimationInterpolator;
-import com.android.quickstep.RecentsAnimationInterpolator.TaskWindowBounds;
+import com.android.launcher3.util.MultiValueAlpha;
+import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
+import com.android.quickstep.util.ClipAnimationHelper;
+import com.android.quickstep.util.MultiValueUpdateListener;
import com.android.quickstep.util.RemoteAnimationProvider;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
-import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.system.ActivityCompat;
import com.android.systemui.shared.system.ActivityOptionsCompat;
import com.android.systemui.shared.system.RemoteAnimationAdapterCompat;
@@ -79,54 +91,66 @@ public class LauncherAppTransitionManagerImpl extends LauncherAppTransitionManag
implements OnDeviceProfileChangeListener {
private static final String TAG = "LauncherTransition";
- private static final int STATUS_BAR_TRANSITION_DURATION = 120;
+ public static final int STATUS_BAR_TRANSITION_DURATION = 120;
private static final String CONTROL_REMOTE_APP_TRANSITION_PERMISSION =
"android.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS";
private static final int APP_LAUNCH_DURATION = 500;
// Use a shorter duration for x or y translation to create a curve effect
- private static final int APP_LAUNCH_CURVED_DURATION = 233;
- private static final int RECENTS_LAUNCH_DURATION = 336;
+ private static final int APP_LAUNCH_CURVED_DURATION = APP_LAUNCH_DURATION / 2;
+ // 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 int APP_LAUNCH_ALPHA_START_DELAY = 32;
+ private static final int APP_LAUNCH_ALPHA_DURATION = 50;
+
+ 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 = 350;
+ private static final int CLOSING_TRANSITION_DURATION_MS = 250;
// Progress = 0: All apps is fully pulled up, Progress = 1: All apps is fully pulled down.
public static final float ALL_APPS_PROGRESS_OFF_SCREEN = 1.3059858f;
- public static final float ALL_APPS_PROGRESS_OVERSHOOT = 0.99581414f;
- private final DragLayer mDragLayer;
private final Launcher mLauncher;
+ private final DragLayer mDragLayer;
+ private final AlphaProperty mDragLayerAlpha;
private final Handler mHandler;
private final boolean mIsRtl;
private final float mContentTransY;
private final float mWorkspaceTransY;
+ private final float mClosingWindowTransY;
private DeviceProfile mDeviceProfile;
private View mFloatingView;
private RemoteAnimationProvider mRemoteAnimationProvider;
- private final AnimatorListenerAdapter mReapplyStateListener = new AnimatorListenerAdapter() {
+ 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.getStateManager().reapplyState();
+ mLauncher.clearForceInvisibleFlag(INVISIBLE_BY_APP_TRANSITIONS);
}
};
public LauncherAppTransitionManagerImpl(Context context) {
mLauncher = Launcher.getLauncher(context);
mDragLayer = mLauncher.getDragLayer();
+ mDragLayerAlpha = mDragLayer.getAlphaProperty(ALPHA_INDEX_TRANSITIONS);
mHandler = new Handler(Looper.getMainLooper());
mIsRtl = Utilities.isRtl(mLauncher.getResources());
mDeviceProfile = mLauncher.getDeviceProfile();
-
Resources res = mLauncher.getResources();
mContentTransY = res.getDimensionPixelSize(R.dimen.content_trans_y);
mWorkspaceTransY = res.getDimensionPixelSize(R.dimen.workspace_trans_y);
+ mClosingWindowTransY = res.getDimensionPixelSize(R.dimen.closing_window_trans_y);
mLauncher.addOnDeviceProfileChangeListener(this);
registerRemoteAnimations();
@@ -144,102 +168,56 @@ public class LauncherAppTransitionManagerImpl extends LauncherAppTransitionManag
@Override
public ActivityOptions getActivityLaunchOptions(Launcher launcher, View v) {
if (hasControlRemoteAppTransitionPermission()) {
- try {
- RemoteAnimationRunnerCompat runner = new LauncherAnimationRunner(mHandler) {
+ RemoteAnimationRunnerCompat runner = new LauncherAnimationRunner(mHandler,
+ true /* startAtFrontOfQueue */) {
- @Override
- public AnimatorSet getAnimator(RemoteAnimationTargetCompat[] targetCompats) {
- AnimatorSet anim = new AnimatorSet();
+ @Override
+ public void onCreateAnimation(RemoteAnimationTargetCompat[] targetCompats,
+ AnimationResult result) {
+ AnimatorSet anim = new AnimatorSet();
+ boolean launcherClosing =
+ launcherIsATargetWithMode(targetCompats, MODE_CLOSING);
- if (!composeRecentsLaunchAnimator(v, targetCompats, anim)) {
- // Set the state animation first so that any state listeners are called
- // before our internal listeners.
- mLauncher.getStateManager().setCurrentAnimation(anim);
+ if (!composeRecentsLaunchAnimator(v, targetCompats, anim)) {
+ // Set the state animation first so that any state listeners are called
+ // before our internal listeners.
+ mLauncher.getStateManager().setCurrentAnimation(anim);
- anim.play(getIconAnimator(v));
- if (launcherIsATargetWithMode(targetCompats, MODE_CLOSING)) {
- anim.play(getLauncherContentAnimator(false /* show */));
- }
- anim.play(getWindowAnimators(v, targetCompats));
+ anim.play(getIconAnimator(v));
+ if (launcherClosing) {
+ Pair<AnimatorSet, Runnable> launcherContentAnimator =
+ getLauncherContentAnimator(true /* isAppOpening */);
+ anim.play(launcherContentAnimator.first);
+ anim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ launcherContentAnimator.second.run();
+ }
+ });
}
- return anim;
+ anim.play(getOpeningWindowAnimators(v, targetCompats));
}
- };
-
- int duration = findTaskViewToLaunch(launcher, v, null) != null
- ? RECENTS_LAUNCH_DURATION : APP_LAUNCH_DURATION;
- int statusBarTransitionDelay = duration - STATUS_BAR_TRANSITION_DURATION;
- return ActivityOptionsCompat.makeRemoteAnimation(new RemoteAnimationAdapterCompat(
- runner, duration, statusBarTransitionDelay));
- } catch (NoClassDefFoundError e) {
- // Gracefully fall back to default launch options if the user's platform doesn't
- // have the latest changes.
- }
- }
- return getDefaultActivityLaunchOptions(launcher, v);
- }
- public void setRemoteAnimationProvider(RemoteAnimationProvider animationProvider) {
- mRemoteAnimationProvider = animationProvider;
- }
-
- /**
- * Try to find a TaskView that corresponds with the component of the launched view.
- *
- * If this method returns a non-null TaskView, it will be used in composeRecentsLaunchAnimation.
- * Otherwise, we will assume we are using a normal app transition, but it's possible that the
- * opening remote target (which we don't get until onAnimationStart) will resolve to a TaskView.
- */
- private TaskView findTaskViewToLaunch(
- BaseDraggingActivity activity, View v, RemoteAnimationTargetCompat[] targets) {
- if (v instanceof TaskView) {
- return (TaskView) v;
- }
- RecentsView recentsView = activity.getOverviewPanel();
-
- // It's possible that the launched view can still be resolved to a visible task view, check
- // the task id of the opening task and see if we can find a match.
- if (v.getTag() instanceof ItemInfo) {
- ItemInfo itemInfo = (ItemInfo) v.getTag();
- ComponentName componentName = itemInfo.getTargetComponent();
- if (componentName != null) {
- for (int i = 0; i < recentsView.getChildCount(); i++) {
- TaskView taskView = (TaskView) recentsView.getPageAt(i);
- if (recentsView.isTaskViewVisible(taskView)) {
- Task task = taskView.getTask();
- if (componentName.equals(task.key.getComponent())) {
- return taskView;
- }
+ if (launcherClosing) {
+ anim.addListener(mForceInvisibleListener);
}
- }
- }
- }
- if (targets == null) {
- return null;
- }
- // Resolve the opening task id
- int openingTaskId = -1;
- for (RemoteAnimationTargetCompat target : targets) {
- if (target.mode == MODE_OPENING) {
- openingTaskId = target.taskId;
- break;
- }
- }
+ result.setAnimation(anim);
+ }
+ };
- // If there is no opening task id, fall back to the normal app icon launch animation
- if (openingTaskId == -1) {
- return null;
+ int duration = findTaskViewToLaunch(launcher, v, null) != null
+ ? RECENTS_LAUNCH_DURATION : APP_LAUNCH_DURATION;
+ int statusBarTransitionDelay = duration - STATUS_BAR_TRANSITION_DURATION;
+ return ActivityOptionsCompat.makeRemoteAnimation(new RemoteAnimationAdapterCompat(
+ runner, duration, statusBarTransitionDelay));
}
+ return getDefaultActivityLaunchOptions(launcher, v);
+ }
- // If the opening task id is not currently visible in overview, then fall back to normal app
- // icon launch animation
- TaskView taskView = recentsView.getTaskView(openingTaskId);
- if (taskView == null || !recentsView.isTaskViewVisible(taskView)) {
- return null;
- }
- return taskView;
+ public void setRemoteAnimationProvider(RemoteAnimationProvider animationProvider) {
+ mRemoteAnimationProvider = animationProvider;
}
/**
@@ -261,21 +239,33 @@ public class LauncherAppTransitionManagerImpl extends LauncherAppTransitionManag
return false;
}
+ ClipAnimationHelper helper = new ClipAnimationHelper();
+ target.play(getRecentsWindowAnimator(taskView, skipLauncherChanges, targets, helper)
+ .setDuration(RECENTS_LAUNCH_DURATION));
+
+ Animator childStateAnimation = null;
// Found a visible recents task that matches the opening app, lets launch the app from there
Animator launcherAnim;
final AnimatorListenerAdapter windowAnimEndListener;
if (launcherClosing) {
- launcherAnim = recentsView.createAdjacentPageAnimForTaskLaunch(taskView);
+ launcherAnim = recentsView.createAdjacentPageAnimForTaskLaunch(taskView, helper);
launcherAnim.setInterpolator(Interpolators.TOUCH_RESPONSE_INTERPOLATOR);
launcherAnim.setDuration(RECENTS_LAUNCH_DURATION);
// Make sure recents gets fixed up by resetting task alphas and scales, etc.
- windowAnimEndListener = mReapplyStateListener;
+ windowAnimEndListener = new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mLauncher.getStateManager().moveToRestState();
+ mLauncher.getStateManager().reapplyState();
+ }
+ };
} else {
AnimatorPlaybackController controller =
mLauncher.getStateManager()
.createAnimationToNewWorkspace(NORMAL, RECENTS_LAUNCH_DURATION);
controller.dispatchOnStart();
+ childStateAnimation = controller.getTarget();
launcherAnim = controller.getAnimationPlayer().setDuration(RECENTS_LAUNCH_DURATION);
windowAnimEndListener = new AnimatorListenerAdapter() {
@Override
@@ -284,112 +274,34 @@ public class LauncherAppTransitionManagerImpl extends LauncherAppTransitionManag
}
};
}
-
- target.play(getRecentsWindowAnimator(taskView, skipLauncherChanges, targets));
target.play(launcherAnim);
// Set the current animation first, before adding windowAnimEndListener. Setting current
// animation adds some listeners which need to be called before windowAnimEndListener
// (the ordering of listeners matter in this case).
- mLauncher.getStateManager().setCurrentAnimation(target);
+ mLauncher.getStateManager().setCurrentAnimation(target, childStateAnimation);
target.addListener(windowAnimEndListener);
return true;
}
/**
- * @return Animator that controls the window of the opening targets for the recents launch
- * animation.
- */
- private ValueAnimator getRecentsWindowAnimator(TaskView v, boolean skipLauncherChanges,
- RemoteAnimationTargetCompat[] targets) {
- final RecentsAnimationInterpolator recentsInterpolator = v.getRecentsInterpolator();
-
- Rect crop = new Rect();
- Matrix matrix = new Matrix();
-
- ValueAnimator appAnimator = ValueAnimator.ofFloat(0, 1);
- appAnimator.setDuration(RECENTS_LAUNCH_DURATION);
- appAnimator.setInterpolator(Interpolators.TOUCH_RESPONSE_INTERPOLATOR);
- appAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
- boolean isFirstFrame = true;
-
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- final Surface surface = getSurface(v);
- final long frameNumber = surface != null ? getNextFrameNumber(surface) : -1;
- if (frameNumber == -1) {
- // Booo, not cool! Our surface got destroyed, so no reason to animate anything.
- Log.w(TAG, "Failed to animate, surface got destroyed.");
- return;
- }
- final float percent = animation.getAnimatedFraction();
- TaskWindowBounds tw = recentsInterpolator.interpolate(percent);
-
- float alphaDuration = 75;
- if (!skipLauncherChanges) {
- v.setScaleX(tw.taskScale);
- v.setScaleY(tw.taskScale);
- v.setTranslationX(tw.taskX);
- v.setTranslationY(tw.taskY);
- // Defer fading out the view until after the app window gets faded in
- v.setAlpha(getValue(1f, 0f, alphaDuration, alphaDuration,
- appAnimator.getDuration() * percent, Interpolators.LINEAR));
- }
-
- matrix.setScale(tw.winScale, tw.winScale);
- matrix.postTranslate(tw.winX, tw.winY);
- crop.set(tw.winCrop);
-
- // Fade in the app window.
- float alpha = getValue(0f, 1f, 0, alphaDuration,
- appAnimator.getDuration() * percent, Interpolators.LINEAR);
-
- TransactionCompat t = new TransactionCompat();
- for (RemoteAnimationTargetCompat target : targets) {
- if (target.mode == RemoteAnimationTargetCompat.MODE_OPENING) {
- t.setAlpha(target.leash, alpha);
-
- // TODO: This isn't correct at the beginning of the animation, but better
- // than nothing.
- matrix.postTranslate(target.position.x, target.position.y);
- t.setMatrix(target.leash, matrix);
- t.setWindowCrop(target.leash, crop);
-
- if (!skipLauncherChanges) {
- t.deferTransactionUntil(target.leash, surface, frameNumber);
- }
- }
- if (isFirstFrame) {
- t.show(target.leash);
- }
- }
- t.setEarlyWakeup();
- t.apply();
-
- matrix.reset();
- isFirstFrame = false;
- }
- });
- return appAnimator;
- }
-
- /**
* Content is everything on screen except the background and the floating view (if any).
*
- * @param show If true: Animate the content so that it moves upwards and fades in.
- * Else: Animate the content so that it moves downwards and fades out.
+ * @param isAppOpening True when this is called when an app is opening.
+ * False when this is called when an app is closing.
*/
- private AnimatorSet getLauncherContentAnimator(boolean show) {
+ private Pair<AnimatorSet, Runnable> getLauncherContentAnimator(boolean isAppOpening) {
AnimatorSet launcherAnimator = new AnimatorSet();
+ Runnable endListener;
- float[] alphas = show
- ? new float[] {0, 1}
- : new float[] {1, 0};
- float[] trans = show
- ? new float[] {mContentTransY, 0,}
- : new float[] {0, mContentTransY};
+ float[] alphas = isAppOpening
+ ? new float[] {1, 0}
+ : new float[] {0, 1};
+ float[] trans = isAppOpening
+ ? new float[] {0, mContentTransY}
+ : new float[] {-mContentTransY, 0};
- if (mLauncher.isInState(LauncherState.ALL_APPS) && !mDeviceProfile.isVerticalBarLayout()) {
+ 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();
@@ -399,44 +311,72 @@ public class LauncherAppTransitionManagerImpl extends LauncherAppTransitionManag
ObjectAnimator alpha = ObjectAnimator.ofFloat(appsView, View.ALPHA, alphas);
alpha.setDuration(217);
- alpha.setInterpolator(Interpolators.LINEAR);
+ 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 transY = ObjectAnimator.ofFloat(appsView, View.TRANSLATION_Y, trans);
- transY.setInterpolator(Interpolators.AGGRESSIVE_EASE);
+ transY.setInterpolator(AGGRESSIVE_EASE);
transY.setDuration(350);
launcherAnimator.play(alpha);
launcherAnimator.play(transY);
- launcherAnimator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- appsView.setAlpha(startAlpha);
- appsView.setTranslationY(startY);
- }
- });
+ endListener = () -> {
+ appsView.setAlpha(startAlpha);
+ appsView.setTranslationY(startY);
+ appsView.setLayerType(View.LAYER_TYPE_NONE, null);
+ };
+ } else if (mLauncher.isInState(OVERVIEW)) {
+ AllAppsTransitionController allAppsController = mLauncher.getAllAppsController();
+ launcherAnimator.play(ObjectAnimator.ofFloat(allAppsController, ALL_APPS_PROGRESS,
+ allAppsController.getProgress(), ALL_APPS_PROGRESS_OFF_SCREEN));
+
+ View overview = mLauncher.getOverviewPanelContainer();
+ ObjectAnimator alpha = ObjectAnimator.ofFloat(overview, View.ALPHA, alphas);
+ alpha.setDuration(217);
+ alpha.setInterpolator(LINEAR);
+ launcherAnimator.play(alpha);
+
+ ObjectAnimator transY = ObjectAnimator.ofFloat(overview, View.TRANSLATION_Y, trans);
+ transY.setInterpolator(AGGRESSIVE_EASE);
+ transY.setDuration(350);
+ launcherAnimator.play(transY);
+
+ overview.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+
+ endListener = () -> {
+ overview.setLayerType(View.LAYER_TYPE_NONE, null);
+ overview.setAlpha(1f);
+ overview.setTranslationY(0f);
+ mLauncher.getStateManager().reapplyState();
+ };
} else {
- mDragLayer.setAlpha(alphas[0]);
+ mDragLayerAlpha.setValue(alphas[0]);
+ ObjectAnimator alpha =
+ ObjectAnimator.ofFloat(mDragLayerAlpha, MultiValueAlpha.VALUE, alphas);
+ alpha.setDuration(217);
+ alpha.setInterpolator(LINEAR);
+ launcherAnimator.play(alpha);
+
mDragLayer.setTranslationY(trans[0]);
+ ObjectAnimator transY = ObjectAnimator.ofFloat(mDragLayer, View.TRANSLATION_Y, trans);
+ transY.setInterpolator(AGGRESSIVE_EASE);
+ transY.setDuration(350);
+ launcherAnimator.play(transY);
- ObjectAnimator dragLayerAlpha = ObjectAnimator.ofFloat(mDragLayer, View.ALPHA, alphas);
- dragLayerAlpha.setDuration(217);
- dragLayerAlpha.setInterpolator(Interpolators.LINEAR);
- ObjectAnimator dragLayerTransY = ObjectAnimator.ofFloat(mDragLayer, View.TRANSLATION_Y,
- trans);
- dragLayerTransY.setInterpolator(Interpolators.AGGRESSIVE_EASE);
- dragLayerTransY.setDuration(350);
-
- launcherAnimator.play(dragLayerAlpha);
- launcherAnimator.play(dragLayerTransY);
- launcherAnimator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- mDragLayer.setAlpha(1);
- mDragLayer.setTranslationY(0);
- }
- });
+ mDragLayer.getScrim().hideSysUiScrim(true);
+ // Pause page indicator animations as they lead to layer trashing.
+ mLauncher.getWorkspace().getPageIndicator().pauseAnimations();
+ mDragLayer.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+
+ endListener = this::resetContentView;
}
- return launcherAnimator;
+ return new Pair<>(launcherAnimator, endListener);
}
/**
@@ -511,12 +451,19 @@ public class LauncherAppTransitionManagerImpl extends LauncherAppTransitionManag
ObjectAnimator x = ObjectAnimator.ofFloat(mFloatingView, View.TRANSLATION_X, 0f, dX);
ObjectAnimator y = ObjectAnimator.ofFloat(mFloatingView, View.TRANSLATION_Y, 0f, dY);
- // Adjust the duration to change the "curve" of the app icon to the center.
- boolean isBelowCenterY = lp.topMargin < centerY;
- x.setDuration(isBelowCenterY ? APP_LAUNCH_DURATION : APP_LAUNCH_CURVED_DURATION);
- y.setDuration(isBelowCenterY ? APP_LAUNCH_CURVED_DURATION : APP_LAUNCH_DURATION);
- x.setInterpolator(Interpolators.AGGRESSIVE_EASE);
- y.setInterpolator(Interpolators.AGGRESSIVE_EASE);
+ // Use upward animation for apps that are either on the bottom half of the screen, or are
+ // relatively close to the center.
+ boolean useUpwardAnimation = lp.topMargin > centerY
+ || Math.abs(dY) < mLauncher.getDeviceProfile().cellHeightPx;
+ if (useUpwardAnimation) {
+ x.setDuration(APP_LAUNCH_CURVED_DURATION);
+ y.setDuration(APP_LAUNCH_DURATION);
+ } else {
+ x.setDuration((long) (APP_LAUNCH_DOWN_DUR_SCALE_FACTOR * APP_LAUNCH_DURATION));
+ y.setDuration((long) (APP_LAUNCH_DOWN_DUR_SCALE_FACTOR * APP_LAUNCH_CURVED_DURATION));
+ }
+ x.setInterpolator(AGGRESSIVE_EASE);
+ y.setInterpolator(AGGRESSIVE_EASE);
appIconAnimatorSet.play(x);
appIconAnimatorSet.play(y);
@@ -527,14 +474,21 @@ public class LauncherAppTransitionManagerImpl extends LauncherAppTransitionManag
float scale = Math.max(maxScaleX, maxScaleY);
ObjectAnimator scaleAnim = ObjectAnimator
.ofFloat(mFloatingView, SCALE_PROPERTY, startScale, scale);
- scaleAnim.setDuration(APP_LAUNCH_DURATION).setInterpolator(Interpolators.EXAGGERATED_EASE);
+ scaleAnim.setDuration(APP_LAUNCH_DURATION)
+ .setInterpolator(Interpolators.EXAGGERATED_EASE);
appIconAnimatorSet.play(scaleAnim);
// Fade out the app icon.
ObjectAnimator alpha = ObjectAnimator.ofFloat(mFloatingView, View.ALPHA, 1f, 0f);
- alpha.setStartDelay(32);
- alpha.setDuration(50);
- alpha.setInterpolator(Interpolators.LINEAR);
+ if (useUpwardAnimation) {
+ alpha.setStartDelay(APP_LAUNCH_ALPHA_START_DELAY);
+ alpha.setDuration(APP_LAUNCH_ALPHA_DURATION);
+ } else {
+ alpha.setStartDelay((long) (APP_LAUNCH_DOWN_DUR_SCALE_FACTOR
+ * APP_LAUNCH_ALPHA_START_DELAY));
+ alpha.setDuration((long) (APP_LAUNCH_DOWN_DUR_SCALE_FACTOR * APP_LAUNCH_ALPHA_DURATION));
+ }
+ alpha.setInterpolator(LINEAR);
appIconAnimatorSet.play(alpha);
appIconAnimatorSet.addListener(new AnimatorListenerAdapter() {
@@ -551,7 +505,7 @@ public class LauncherAppTransitionManagerImpl extends LauncherAppTransitionManag
/**
* @return Animator that controls the window of the opening targets.
*/
- private ValueAnimator getWindowAnimators(View v, RemoteAnimationTargetCompat[] targets) {
+ private ValueAnimator getOpeningWindowAnimators(View v, RemoteAnimationTargetCompat[] targets) {
Rect bounds = new Rect();
if (v.getParent() instanceof DeepShortcutView) {
// Deep shortcut views have their icon drawn in a separate view.
@@ -569,11 +523,13 @@ public class LauncherAppTransitionManagerImpl extends LauncherAppTransitionManag
ValueAnimator appAnimator = ValueAnimator.ofFloat(0, 1);
appAnimator.setDuration(APP_LAUNCH_DURATION);
- appAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ appAnimator.addUpdateListener(new MultiValueUpdateListener() {
+ // Fade alpha for the app window.
+ FloatProp mAlpha = new FloatProp(0f, 1f, 0, 60, LINEAR);
boolean isFirstFrame = true;
@Override
- public void onAnimationUpdate(ValueAnimator animation) {
+ public void onUpdate(float percent) {
final Surface surface = getSurface(mFloatingView);
final long frameNumber = surface != null ? getNextFrameNumber(surface) : -1;
if (frameNumber == -1) {
@@ -581,8 +537,7 @@ public class LauncherAppTransitionManagerImpl extends LauncherAppTransitionManag
Log.w(TAG, "Failed to animate, surface got destroyed.");
return;
}
- final float percent = animation.getAnimatedFraction();
- final float easePercent = Interpolators.AGGRESSIVE_EASE.getInterpolation(percent);
+ final float easePercent = AGGRESSIVE_EASE.getInterpolation(percent);
// Calculate app icon size.
float iconWidth = bounds.width() * mFloatingView.getScaleX();
@@ -607,11 +562,6 @@ public class LauncherAppTransitionManagerImpl extends LauncherAppTransitionManag
float transY0 = floatingViewBounds[1] - offsetY;
matrix.postTranslate(transX0, transY0);
- // Fade in the app window.
- float alphaDuration = 60;
- float alpha = getValue(0f, 1f, 0, alphaDuration,
- appAnimator.getDuration() * percent, Interpolators.LINEAR);
-
// Animate the window crop so that it starts off as a square, and then reveals
// horizontally.
float cropHeight = deviceHeight * easePercent + deviceWidth * (1 - easePercent);
@@ -622,9 +572,13 @@ public class LauncherAppTransitionManagerImpl extends LauncherAppTransitionManag
crop.bottom = (int) (crop.top + cropHeight);
TransactionCompat t = new TransactionCompat();
+ if (isFirstFrame) {
+ RemoteAnimationProvider.prepareTargetsForFirstFrame(targets, t, MODE_OPENING);
+ isFirstFrame = false;
+ }
for (RemoteAnimationTargetCompat target : targets) {
if (target.mode == MODE_OPENING) {
- t.setAlpha(target.leash, alpha);
+ t.setAlpha(target.leash, mAlpha.value);
// TODO: This isn't correct at the beginning of the animation, but better
// than nothing.
@@ -633,15 +587,11 @@ public class LauncherAppTransitionManagerImpl extends LauncherAppTransitionManag
t.setWindowCrop(target.leash, crop);
t.deferTransactionUntil(target.leash, surface, getNextFrameNumber(surface));
}
- if (isFirstFrame) {
- t.show(target.leash);
- }
}
t.setEarlyWakeup();
t.apply();
matrix.reset();
- isFirstFrame = false;
}
});
return appAnimator;
@@ -653,30 +603,19 @@ public class LauncherAppTransitionManagerImpl extends LauncherAppTransitionManag
private void registerRemoteAnimations() {
// Unregister this
if (hasControlRemoteAppTransitionPermission()) {
- try {
- RemoteAnimationDefinitionCompat definition = new RemoteAnimationDefinitionCompat();
- definition.addRemoteAnimation(WindowManagerWrapper.TRANSIT_WALLPAPER_OPEN,
- WindowManagerWrapper.ACTIVITY_TYPE_STANDARD,
- new RemoteAnimationAdapterCompat(getWallpaperOpenRunner(),
- CLOSING_TRANSITION_DURATION_MS, 0 /* statusBarTransitionDelay */));
-
-// TODO: App controlled transition for unlock to home TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER
-
- new ActivityCompat(mLauncher).registerRemoteAnimations(definition);
- } catch (NoClassDefFoundError e) {
- // Gracefully fall back if the user's platform doesn't have the latest changes
- }
+ RemoteAnimationDefinitionCompat definition = new RemoteAnimationDefinitionCompat();
+ definition.addRemoteAnimation(WindowManagerWrapper.TRANSIT_WALLPAPER_OPEN,
+ WindowManagerWrapper.ACTIVITY_TYPE_STANDARD,
+ new RemoteAnimationAdapterCompat(getWallpaperOpenRunner(),
+ CLOSING_TRANSITION_DURATION_MS, 0 /* statusBarTransitionDelay */));
+
+ // TODO: Transition for unlock to home TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER
+ new ActivityCompat(mLauncher).registerRemoteAnimations(definition);
}
}
private boolean launcherIsATargetWithMode(RemoteAnimationTargetCompat[] targets, int mode) {
- int launcherTaskId = mLauncher.getTaskId();
- for (RemoteAnimationTargetCompat target : targets) {
- if (target.mode == mode && target.taskId == launcherTaskId) {
- return true;
- }
- }
- return false;
+ return taskIsATargetWithMode(targets, mLauncher.getTaskId(), mode);
}
/**
@@ -684,9 +623,18 @@ public class LauncherAppTransitionManagerImpl extends LauncherAppTransitionManag
* ie. pressing home, swiping up from nav bar.
*/
private RemoteAnimationRunnerCompat getWallpaperOpenRunner() {
- return new LauncherAnimationRunner(mHandler) {
+ return new LauncherAnimationRunner(mHandler, false /* startAtFrontOfQueue */) {
@Override
- public AnimatorSet getAnimator(RemoteAnimationTargetCompat[] targetCompats) {
+ public void onCreateAnimation(RemoteAnimationTargetCompat[] targetCompats,
+ AnimationResult result) {
+ if (!mLauncher.hasBeenResumed()) {
+ // If launcher is not resumed, wait until new async-frame after resume
+ mLauncher.setOnResumeCallback(() ->
+ postAsyncCallback(mHandler, () ->
+ onCreateAnimation(targetCompats, result)));
+ return;
+ }
+
AnimatorSet anim = null;
RemoteAnimationProvider provider = mRemoteAnimationProvider;
if (provider != null) {
@@ -713,8 +661,8 @@ public class LauncherAppTransitionManagerImpl extends LauncherAppTransitionManag
}
}
- mLauncher.setForceInvisible(false);
- return anim;
+ mLauncher.clearForceInvisibleFlag(INVISIBLE_ALL);
+ result.setAnimation(anim);
}
};
}
@@ -724,54 +672,41 @@ public class LauncherAppTransitionManagerImpl extends LauncherAppTransitionManag
*/
private Animator getClosingWindowAnimators(RemoteAnimationTargetCompat[] targets) {
Matrix matrix = new Matrix();
- float height = mLauncher.getDeviceProfile().heightPx;
- float width = mLauncher.getDeviceProfile().widthPx;
- float endX = (mLauncher.<RecentsView>getOverviewPanel().isRtl() ? -width : width) * 1.16f;
-
ValueAnimator closingAnimator = ValueAnimator.ofFloat(0, 1);
- closingAnimator.setDuration(CLOSING_TRANSITION_DURATION_MS);
+ int duration = CLOSING_TRANSITION_DURATION_MS;
+ 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);
- closingAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
boolean isFirstFrame = true;
@Override
- public void onAnimationUpdate(ValueAnimator animation) {
- final float percent = animation.getAnimatedFraction();
- float currentPlayTime = percent * closingAnimator.getDuration();
-
- float scale = getValue(1f, 0.8f, 0, 267, currentPlayTime,
- Interpolators.AGGRESSIVE_EASE);
-
- float dX = getValue(0, endX, 0, 350, currentPlayTime,
- Interpolators.AGGRESSIVE_EASE_IN_OUT);
-
+ public void onUpdate(float percent) {
TransactionCompat t = new TransactionCompat();
+ if (isFirstFrame) {
+ RemoteAnimationProvider.prepareTargetsForFirstFrame(targets, t, MODE_CLOSING);
+ isFirstFrame = false;
+ }
for (RemoteAnimationTargetCompat app : targets) {
if (app.mode == RemoteAnimationTargetCompat.MODE_CLOSING) {
- t.setAlpha(app.leash, getValue(1f, 0f, 0, 350, currentPlayTime,
- Interpolators.APP_CLOSE_ALPHA));
- matrix.setScale(scale, scale,
+ t.setAlpha(app.leash, mAlpha.value);
+ matrix.setScale(mScale.value, mScale.value,
app.sourceContainerBounds.centerX(),
app.sourceContainerBounds.centerY());
- matrix.postTranslate(dX, 0);
+ matrix.postTranslate(0, mDy.value);
matrix.postTranslate(app.position.x, app.position.y);
t.setMatrix(app.leash, matrix);
}
- if (isFirstFrame) {
- int layer = app.mode == RemoteAnimationTargetCompat.MODE_CLOSING
- ? Integer.MAX_VALUE
- : app.prefixOrderIndex;
- t.setLayer(app.leash, layer);
- t.show(app.leash);
- }
}
t.setEarlyWakeup();
t.apply();
matrix.reset();
- isFirstFrame = false;
}
});
+
return closingAnimator;
}
@@ -779,69 +714,58 @@ public class LauncherAppTransitionManagerImpl extends LauncherAppTransitionManag
* Creates an animator that modifies Launcher as a result from {@link #getWallpaperOpenRunner}.
*/
private void createLauncherResumeAnimation(AnimatorSet anim) {
- if (mLauncher.isInState(LauncherState.ALL_APPS)
- || mLauncher.getDeviceProfile().isVerticalBarLayout()) {
- AnimatorSet contentAnimator = getLauncherContentAnimator(true /* show */);
- contentAnimator.setStartDelay(LAUNCHER_RESUME_START_DELAY);
- anim.play(contentAnimator);
+ if (mLauncher.isInState(LauncherState.ALL_APPS)) {
+ Pair<AnimatorSet, Runnable> contentAnimator =
+ getLauncherContentAnimator(false /* isAppOpening */);
+ contentAnimator.first.setStartDelay(LAUNCHER_RESUME_START_DELAY);
+ anim.play(contentAnimator.first);
+ anim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ contentAnimator.second.run();
+ }
+ });
} else {
AnimatorSet workspaceAnimator = new AnimatorSet();
- mLauncher.getWorkspace().setTranslationY(mWorkspaceTransY);
- workspaceAnimator.play(ObjectAnimator.ofFloat(mLauncher.getWorkspace(),
- View.TRANSLATION_Y, mWorkspaceTransY, 0));
+ mDragLayer.setTranslationY(-mWorkspaceTransY);;
+ workspaceAnimator.play(ObjectAnimator.ofFloat(mDragLayer, View.TRANSLATION_Y,
+ -mWorkspaceTransY, 0));
- View currentPage = ((CellLayout) mLauncher.getWorkspace()
- .getChildAt(mLauncher.getWorkspace().getCurrentPage()))
- .getShortcutsAndWidgets();
- currentPage.setAlpha(0f);
- workspaceAnimator.play(ObjectAnimator.ofFloat(currentPage, View.ALPHA, 0, 1f));
+ mDragLayerAlpha.setValue(0);
+ workspaceAnimator.play(ObjectAnimator.ofFloat(
+ mDragLayerAlpha, MultiValueAlpha.VALUE, 0, 1f));
workspaceAnimator.setStartDelay(LAUNCHER_RESUME_START_DELAY);
workspaceAnimator.setDuration(333);
- workspaceAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
+ workspaceAnimator.setInterpolator(Interpolators.DEACCEL_1_7);
- // Animate the shelf in two parts: slide in, and overeshoot.
- AllAppsTransitionController allAppsController = mLauncher.getAllAppsController();
- // The shelf will start offscreen
- final float startY = ALL_APPS_PROGRESS_OFF_SCREEN;
- // And will end slightly pulled up, so that there is something to overshoot back to 1f.
- final float slideEnd = ALL_APPS_PROGRESS_OVERSHOOT;
-
- allAppsController.setProgress(startY);
-
- Animator allAppsSlideIn =
- ObjectAnimator.ofFloat(allAppsController, ALL_APPS_PROGRESS, startY, slideEnd);
- allAppsSlideIn.setStartDelay(LAUNCHER_RESUME_START_DELAY);
- allAppsSlideIn.setDuration(317);
- allAppsSlideIn.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
-
- Animator allAppsOvershoot =
- ObjectAnimator.ofFloat(allAppsController, ALL_APPS_PROGRESS, slideEnd, 1f);
- allAppsOvershoot.setDuration(153);
- allAppsOvershoot.setInterpolator(Interpolators.OVERSHOOT_0);
+ mDragLayer.getScrim().hideSysUiScrim(true);
+ // Pause page indicator animations as they lead to layer trashing.
+ mLauncher.getWorkspace().getPageIndicator().pauseAnimations();
+ mDragLayer.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+ workspaceAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ resetContentView();
+ }
+ });
anim.play(workspaceAnimator);
- anim.playSequentially(allAppsSlideIn, allAppsOvershoot);
- anim.addListener(mReapplyStateListener);
}
}
+ private void resetContentView() {
+ mLauncher.getWorkspace().getPageIndicator().skipAnimationsToEnd();
+ mDragLayerAlpha.setValue(1f);
+ mDragLayer.setLayerType(View.LAYER_TYPE_NONE, null);
+ mDragLayer.setTranslationY(0f);
+ mDragLayer.getScrim().hideSysUiScrim(false);
+ }
+
private boolean hasControlRemoteAppTransitionPermission() {
return mLauncher.checkSelfPermission(CONTROL_REMOTE_APP_TRANSITION_PERMISSION)
== PackageManager.PERMISSION_GRANTED;
}
-
- /**
- * Helper method that allows us to get interpolated values for embedded
- * animations with a delay and/or different duration.
- */
- private static float getValue(float start, float end, float delay, float duration,
- float currentPlayTime, Interpolator i) {
- float time = Math.max(0, currentPlayTime - delay);
- float newPercent = Math.min(1f, time / duration);
- newPercent = i.getInterpolation(newPercent);
- return end * newPercent + start * (1 - newPercent);
- }
}
diff --git a/quickstep/src/com/android/launcher3/LauncherInitListener.java b/quickstep/src/com/android/launcher3/LauncherInitListener.java
index 27f169834..e5e377f83 100644
--- a/quickstep/src/com/android/launcher3/LauncherInitListener.java
+++ b/quickstep/src/com/android/launcher3/LauncherInitListener.java
@@ -24,6 +24,7 @@ import android.os.Handler;
import com.android.launcher3.states.InternalStateHandler;
import com.android.quickstep.ActivityControlHelper.ActivityInitListener;
+import com.android.quickstep.OverviewCallbacks;
import com.android.quickstep.util.RemoteAnimationProvider;
import java.util.function.BiPredicate;
@@ -60,6 +61,7 @@ public class LauncherInitListener extends InternalStateHandler implements Activi
return null;
});
}
+ OverviewCallbacks.get(launcher).onInitOverviewTransition();
return mOnInitListener.test(launcher, alreadyOnHome);
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/AllAppsState.java b/quickstep/src/com/android/launcher3/uioverrides/AllAppsState.java
index d2f54874f..d86ba6aa6 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/AllAppsState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/AllAppsState.java
@@ -16,7 +16,6 @@
package com.android.launcher3.uioverrides;
import static com.android.launcher3.LauncherAnimUtils.ALL_APPS_TRANSITION_MS;
-import static com.android.launcher3.allapps.DiscoveryBounce.APPS_VIEW_SHOWN;
import static com.android.launcher3.anim.Interpolators.DEACCEL_2;
import android.view.View;
@@ -32,7 +31,7 @@ import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
*/
public class AllAppsState extends LauncherState {
- private static final int STATE_FLAGS = FLAG_DISABLE_ACCESSIBILITY | FLAG_ALL_APPS_SCRIM;
+ private static final int STATE_FLAGS = FLAG_DISABLE_ACCESSIBILITY;
private static final PageAlphaProvider PAGE_ALPHA_PROVIDER = new PageAlphaProvider(DEACCEL_2) {
@Override
@@ -47,10 +46,6 @@ public class AllAppsState extends LauncherState {
@Override
public void onStateEnabled(Launcher launcher) {
- if (!launcher.getSharedPrefs().getBoolean(APPS_VIEW_SHOWN, false)) {
- launcher.getSharedPrefs().edit().putBoolean(APPS_VIEW_SHOWN, true).apply();
- }
-
AbstractFloatingView.closeAllOpenViews(launcher);
dispatchWindowStateChanged(launcher);
}
@@ -73,8 +68,10 @@ public class AllAppsState extends LauncherState {
@Override
public float[] getWorkspaceScaleAndTranslation(Launcher launcher) {
- // TODO: interpolate
- return LauncherState.OVERVIEW.getWorkspaceScaleAndTranslation(launcher);
+ float[] scaleAndTranslation = LauncherState.OVERVIEW.getWorkspaceScaleAndTranslation(
+ launcher);
+ scaleAndTranslation[0] = 1;
+ return scaleAndTranslation;
}
@Override
@@ -89,7 +86,7 @@ public class AllAppsState extends LauncherState {
@Override
public float[] getOverviewScaleAndTranslationYFactor(Launcher launcher) {
- return new float[] {1f, -0.2f};
+ return new float[] {0.9f, -0.2f};
}
@Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/DisplayRotationListener.java b/quickstep/src/com/android/launcher3/uioverrides/DisplayRotationListener.java
new file mode 100644
index 000000000..2d9a16147
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/uioverrides/DisplayRotationListener.java
@@ -0,0 +1,48 @@
+/*
+ * 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.uioverrides;
+
+import android.content.Context;
+import android.os.Handler;
+
+import com.android.systemui.shared.system.RotationWatcher;
+
+/**
+ * Utility class for listening for rotation changes
+ */
+public class DisplayRotationListener extends RotationWatcher {
+
+ private final Runnable mCallback;
+ private Handler mHandler;
+
+ public DisplayRotationListener(Context context, Runnable callback) {
+ super(context);
+ mCallback = callback;
+ }
+
+ @Override
+ public void enable() {
+ if (mHandler == null) {
+ mHandler = new Handler();
+ }
+ super.enable();
+ }
+
+ @Override
+ protected void onRotationChanged(int i) {
+ mHandler.post(mCallback);
+ }
+}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/FastOverviewState.java b/quickstep/src/com/android/launcher3/uioverrides/FastOverviewState.java
index f98f7a5ff..a11625a34 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/FastOverviewState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/FastOverviewState.java
@@ -15,7 +15,6 @@
*/
package com.android.launcher3.uioverrides;
-import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.Launcher;
import com.android.quickstep.QuickScrubController;
import com.android.quickstep.views.RecentsView;
@@ -25,11 +24,11 @@ import com.android.quickstep.views.RecentsView;
*/
public class FastOverviewState extends OverviewState {
- private static final int STATE_FLAGS = FLAG_SHOW_SCRIM | FLAG_DISABLE_RESTORE
- | FLAG_DISABLE_INTERACTION | FLAG_OVERVIEW_UI | FLAG_HIDE_BACK_BUTTON;
+ private static final int STATE_FLAGS = FLAG_DISABLE_RESTORE | FLAG_DISABLE_INTERACTION
+ | FLAG_OVERVIEW_UI | FLAG_HIDE_BACK_BUTTON | FLAG_DISABLE_ACCESSIBILITY;
public FastOverviewState(int id) {
- super(id, QuickScrubController.QUICK_SCRUB_START_DURATION, STATE_FLAGS);
+ super(id, QuickScrubController.QUICK_SCRUB_FROM_HOME_START_DURATION, STATE_FLAGS);
}
@Override
@@ -39,11 +38,6 @@ public class FastOverviewState extends OverviewState {
recentsView.getQuickScrubController().onFinishedTransitionToQuickScrub();
}
- public void onStateEnabled(Launcher launcher) {
- super.onStateEnabled(launcher);
- AbstractFloatingView.closeAllOpenViews(launcher);
- }
-
@Override
public int getVisibleElements(Launcher launcher) {
return NONE;
diff --git a/quickstep/src/com/android/launcher3/uioverrides/LandscapeEdgeSwipeController.java b/quickstep/src/com/android/launcher3/uioverrides/LandscapeEdgeSwipeController.java
index 3622fc425..68773b418 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/LandscapeEdgeSwipeController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/LandscapeEdgeSwipeController.java
@@ -9,16 +9,20 @@ import android.view.MotionEvent;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
+import com.android.launcher3.LauncherStateManager.AnimationComponents;
import com.android.launcher3.touch.AbstractStateChangeTouchController;
import com.android.launcher3.touch.SwipeDetector;
+import com.android.launcher3.userevent.nano.LauncherLogProto;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
-import com.android.quickstep.util.SysuiEventLogger;
+import com.android.quickstep.RecentsModel;
/**
* Touch controller for handling edge swipes in landscape/seascape UI
*/
public class LandscapeEdgeSwipeController extends AbstractStateChangeTouchController {
+ private static final String TAG = "LandscapeEdgeSwipeCtrl";
+
public LandscapeEdgeSwipeController(Launcher l) {
super(l, SwipeDetector.HORIZONTAL);
}
@@ -36,27 +40,27 @@ public class LandscapeEdgeSwipeController extends AbstractStateChangeTouchContro
}
@Override
- protected int getSwipeDirection(MotionEvent ev) {
- return SwipeDetector.DIRECTION_BOTH;
- }
-
- @Override
protected LauncherState getTargetState(LauncherState fromState, boolean isDragTowardPositive) {
boolean draggingFromNav = mLauncher.getDeviceProfile().isSeascape() != isDragTowardPositive;
return draggingFromNav ? OVERVIEW : NORMAL;
}
@Override
+ protected int getLogContainerTypeForNormalState() {
+ return LauncherLogProto.ContainerType.NAVBAR;
+ }
+
+ @Override
protected float getShiftRange() {
return mLauncher.getDragLayer().getWidth();
}
@Override
- protected float initCurrentAnimation() {
+ protected float initCurrentAnimation(@AnimationComponents int animComponent) {
float range = getShiftRange();
long maxAccuracy = (long) (2 * range);
- mCurrentAnimation = mLauncher.getStateManager()
- .createAnimationToNewWorkspace(mToState, maxAccuracy);
+ mCurrentAnimation = mLauncher.getStateManager().createAnimationToNewWorkspace(mToState,
+ maxAccuracy, animComponent);
return (mLauncher.getDeviceProfile().isSeascape() ? 2 : -2) / range;
}
@@ -69,7 +73,7 @@ public class LandscapeEdgeSwipeController extends AbstractStateChangeTouchContro
protected void onSwipeInteractionCompleted(LauncherState targetState, int logAction) {
super.onSwipeInteractionCompleted(targetState, logAction);
if (mFromState == NORMAL && targetState == OVERVIEW) {
- SysuiEventLogger.writeDummyRecentsTransition(0);
+ RecentsModel.getInstance(mLauncher).onOverviewShown(true, TAG);
}
}
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java b/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java
index 9c7db3093..3a49294c5 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java
@@ -21,9 +21,12 @@ import static com.android.launcher3.states.RotationHelper.REQUEST_ROTATE;
import android.view.View;
+import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
+import com.android.launcher3.Workspace;
+import com.android.launcher3.allapps.DiscoveryBounce;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
import com.android.quickstep.views.RecentsView;
@@ -32,8 +35,8 @@ import com.android.quickstep.views.RecentsView;
*/
public class OverviewState extends LauncherState {
- private static final int STATE_FLAGS = FLAG_SHOW_SCRIM | FLAG_WORKSPACE_ICONS_CAN_BE_DRAGGED
- | FLAG_DISABLE_RESTORE | FLAG_OVERVIEW_UI;
+ private static final int STATE_FLAGS = FLAG_WORKSPACE_ICONS_CAN_BE_DRAGGED
+ | FLAG_DISABLE_RESTORE | FLAG_OVERVIEW_UI | FLAG_DISABLE_ACCESSIBILITY;
public OverviewState(int id) {
this(id, OVERVIEW_TRANSITION_MS, STATE_FLAGS);
@@ -45,8 +48,15 @@ public class OverviewState extends LauncherState {
@Override
public float[] getWorkspaceScaleAndTranslation(Launcher launcher) {
- // TODO: provide a valid value
- return new float[]{1, 0, -launcher.getDeviceProfile().hotseatBarSizePx / 2};
+ RecentsView recentsView = launcher.getOverviewPanel();
+ Workspace workspace = launcher.getWorkspace();
+ View workspacePage = workspace.getPageAt(workspace.getCurrentPage());
+ float workspacePageWidth = workspacePage != null && workspacePage.getWidth() != 0
+ ? workspacePage.getWidth() : launcher.getDeviceProfile().availableWidthPx;
+ recentsView.getTaskSize(sTempRect);
+ float scale = (float) sTempRect.width() / workspacePageWidth;
+ float parallaxFactor = 0.5f;
+ return new float[]{scale, 0, -getDefaultSwipeHeight(launcher) * parallaxFactor};
}
@Override
@@ -58,6 +68,7 @@ public class OverviewState extends LauncherState {
public void onStateEnabled(Launcher launcher) {
RecentsView rv = launcher.getOverviewPanel();
rv.setOverviewStateEnabled(true);
+ AbstractFloatingView.closeAllOpenViews(launcher);
}
@Override
@@ -69,6 +80,7 @@ public class OverviewState extends LauncherState {
@Override
public void onStateTransitionEnd(Launcher launcher) {
launcher.getRotationHelper().setCurrentStateRequest(REQUEST_ROTATE);
+ DiscoveryBounce.showForOverviewIfNeeded(launcher);
}
@Override
@@ -88,15 +100,20 @@ public class OverviewState extends LauncherState {
@Override
public int getVisibleElements(Launcher launcher) {
if (launcher.getDeviceProfile().isVerticalBarLayout()) {
- return DRAG_HANDLE_INDICATOR;
+ return 0;
} else {
- return HOTSEAT_SEARCH_BOX | DRAG_HANDLE_INDICATOR |
+ return HOTSEAT_SEARCH_BOX |
(launcher.getAppsView().getFloatingHeaderView().hasVisibleContent()
? ALL_APPS_HEADER_EXTRA : HOTSEAT_ICONS);
}
}
@Override
+ public float getWorkspaceScrimAlpha(Launcher launcher) {
+ return 0.5f;
+ }
+
+ @Override
public float getVerticalProgress(Launcher launcher) {
if ((getVisibleElements(launcher) & ALL_APPS_HEADER_EXTRA) == 0) {
// We have no all apps content, so we're still at the fully down progress.
diff --git a/quickstep/src/com/android/launcher3/uioverrides/LandscapeStatesTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/OverviewToAllAppsTouchController.java
index 30ceb43f9..0f9b57f03 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/LandscapeStatesTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/OverviewToAllAppsTouchController.java
@@ -24,15 +24,18 @@ import android.view.MotionEvent;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
+import com.android.launcher3.userevent.nano.LauncherLogProto;
import com.android.quickstep.TouchInteractionService;
import com.android.quickstep.views.RecentsView;
/**
- * Touch controller from going from OVERVIEW to ALL_APPS
+ * Touch controller from going from OVERVIEW to ALL_APPS.
+ *
+ * This is used in landscape mode. It is also used in portrait mode for the fallback recents.
*/
-public class LandscapeStatesTouchController extends PortraitStatesTouchController {
+public class OverviewToAllAppsTouchController extends PortraitStatesTouchController {
- public LandscapeStatesTouchController(Launcher l) {
+ public OverviewToAllAppsTouchController(Launcher l) {
super(l);
}
@@ -69,4 +72,9 @@ public class LandscapeStatesTouchController extends PortraitStatesTouchControlle
}
return fromState;
}
+
+ @Override
+ protected int getLogContainerTypeForNormalState() {
+ return LauncherLogProto.ContainerType.WORKSPACE;
+ }
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/PortraitStatesTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/PortraitStatesTouchController.java
index 012b54521..987f952ba 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/PortraitStatesTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/PortraitStatesTouchController.java
@@ -18,20 +18,20 @@ package com.android.launcher3.uioverrides;
import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.LauncherState.OVERVIEW;
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_TRANSLATION;
import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_VERTICAL_PROGRESS;
-import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
import android.view.MotionEvent;
import android.view.animation.Interpolator;
+import android.view.animation.OvershootInterpolator;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
+import com.android.launcher3.LauncherStateManager.AnimationComponents;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.AnimatorSetBuilder;
import com.android.launcher3.anim.Interpolators;
@@ -39,8 +39,8 @@ import com.android.launcher3.touch.AbstractStateChangeTouchController;
import com.android.launcher3.touch.SwipeDetector;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
+import com.android.quickstep.RecentsModel;
import com.android.quickstep.TouchInteractionService;
-import com.android.quickstep.util.SysuiEventLogger;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
@@ -49,43 +49,13 @@ import com.android.quickstep.views.TaskView;
*/
public class PortraitStatesTouchController extends AbstractStateChangeTouchController {
- private static final float TOTAL_DISTANCE_MULTIPLIER = 3f;
- private static final float LINEAR_SCALE_LIMIT = 1 / TOTAL_DISTANCE_MULTIPLIER;
-
- // Must be greater than LINEAR_SCALE_LIMIT;
- private static final float MAXIMUM_DISTANCE_FACTOR = 0.9f;
-
- // Maximum amount to overshoot.
- private static final float MAX_OVERSHOOT = 0.3f;
-
- private static final double PI_BY_2 = Math.PI / 2;
+ private static final String TAG = "PortraitStatesTouchCtrl";
private InterpolatorWrapper mAllAppsInterpolatorWrapper = new InterpolatorWrapper();
// If true, we will finish the current animation instantly on second touch.
private boolean mFinishFastOnSecondTouch;
- private final Interpolator mAllAppsDampedInterpolator = new Interpolator() {
-
- private final double mAngleMultiplier = Math.PI /
- (2 * (MAXIMUM_DISTANCE_FACTOR - LINEAR_SCALE_LIMIT));
-
- @Override
- public float getInterpolation(float v) {
- if (v <= LINEAR_SCALE_LIMIT) {
- return v * TOTAL_DISTANCE_MULTIPLIER;
- }
- float overshoot = (v - LINEAR_SCALE_LIMIT);
- return (float) (1 + MAX_OVERSHOOT * Math.sin(overshoot * mAngleMultiplier));
- }
- };
-
- private final Interpolator mOverviewBoundInterpolator = (v) -> {
- if (v >= MAXIMUM_DISTANCE_FACTOR) {
- return 1;
- }
- return FAST_OUT_SLOW_IN.getInterpolation(v / MAXIMUM_DISTANCE_FACTOR);
- };
public PortraitStatesTouchController(Launcher l) {
super(l, SwipeDetector.VERTICAL);
@@ -122,24 +92,6 @@ public class PortraitStatesTouchController extends AbstractStateChangeTouchContr
}
@Override
- protected int getSwipeDirection(MotionEvent ev) {
- final int directionsToDetectScroll;
- if (mLauncher.isInState(ALL_APPS)) {
- directionsToDetectScroll = SwipeDetector.DIRECTION_NEGATIVE;
- mStartContainerType = ContainerType.ALLAPPS;
- } else if (mLauncher.isInState(NORMAL)) {
- directionsToDetectScroll = SwipeDetector.DIRECTION_POSITIVE;
- mStartContainerType = ContainerType.HOTSEAT;
- } else if (mLauncher.isInState(OVERVIEW)) {
- directionsToDetectScroll = SwipeDetector.DIRECTION_BOTH;
- mStartContainerType = ContainerType.TASKSWITCHER;
- } else {
- return 0;
- }
- return directionsToDetectScroll;
- }
-
- @Override
protected LauncherState getTargetState(LauncherState fromState, boolean isDragTowardPositive) {
if (fromState == ALL_APPS && !isDragTowardPositive) {
// Should swipe down go to OVERVIEW instead?
@@ -147,24 +99,28 @@ public class PortraitStatesTouchController extends AbstractStateChangeTouchContr
mLauncher.getStateManager().getLastState() : NORMAL;
} else if (fromState == OVERVIEW) {
return isDragTowardPositive ? ALL_APPS : NORMAL;
- } else if (isDragTowardPositive) {
+ } else if (fromState == NORMAL && isDragTowardPositive) {
return TouchInteractionService.isConnected() ? OVERVIEW : ALL_APPS;
}
return fromState;
}
+ @Override
+ protected int getLogContainerTypeForNormalState() {
+ return ContainerType.HOTSEAT;
+ }
+
private AnimatorSetBuilder getNormalToOverviewAnimation() {
- mAllAppsInterpolatorWrapper.baseInterpolator = mAllAppsDampedInterpolator;
+ mAllAppsInterpolatorWrapper.baseInterpolator = LINEAR;
AnimatorSetBuilder builder = new AnimatorSetBuilder();
builder.setInterpolator(ANIM_VERTICAL_PROGRESS, mAllAppsInterpolatorWrapper);
- builder.setInterpolator(ANIM_OVERVIEW_TRANSLATION, mOverviewBoundInterpolator);
return builder;
}
@Override
- protected float initCurrentAnimation() {
+ protected float initCurrentAnimation(@AnimationComponents int animComponents) {
float range = getShiftRange();
long maxAccuracy = (long) (2 * range);
@@ -177,15 +133,11 @@ public class PortraitStatesTouchController extends AbstractStateChangeTouchContr
if (mFromState == NORMAL && mToState == OVERVIEW && totalShift != 0) {
builder = getNormalToOverviewAnimation();
- totalShift = totalShift * TOTAL_DISTANCE_MULTIPLIER;
} else {
builder = new AnimatorSetBuilder();
}
- if (mPendingAnimation != null) {
- mPendingAnimation.finish(false, Touch.SWIPE);
- mPendingAnimation = null;
- }
+ cancelPendingAnim();
RecentsView recentsView = mLauncher.getOverviewPanel();
TaskView taskView = (TaskView) recentsView.getChildAt(recentsView.getNextPage());
@@ -194,10 +146,17 @@ public class PortraitStatesTouchController extends AbstractStateChangeTouchContr
mPendingAnimation = recentsView.createTaskLauncherAnimation(taskView, maxAccuracy);
mPendingAnimation.anim.setInterpolator(Interpolators.ZOOM_IN);
- mCurrentAnimation = AnimatorPlaybackController.wrap(mPendingAnimation.anim, maxAccuracy);
+ Runnable onCancelRunnable = () -> {
+ cancelPendingAnim();
+ clearState();
+ };
+ mCurrentAnimation = AnimatorPlaybackController.wrap(mPendingAnimation.anim, maxAccuracy,
+ onCancelRunnable);
+ mLauncher.getStateManager().setCurrentUserControlledAnimation(mCurrentAnimation);
} else {
mCurrentAnimation = mLauncher.getStateManager()
- .createAnimationToNewWorkspace(mToState, builder, maxAccuracy);
+ .createAnimationToNewWorkspace(mToState, builder, maxAccuracy, this::clearState,
+ animComponents);
}
if (totalShift == 0) {
@@ -207,12 +166,19 @@ public class PortraitStatesTouchController extends AbstractStateChangeTouchContr
return 1 / totalShift;
}
+ private void cancelPendingAnim() {
+ if (mPendingAnimation != null) {
+ mPendingAnimation.finish(false, Touch.SWIPE);
+ mPendingAnimation = null;
+ }
+ }
+
@Override
protected void updateSwipeCompleteAnimation(ValueAnimator animator, long expectedDuration,
LauncherState targetState, float velocity, boolean isFling) {
- handleFirstSwipeToOverview(animator, expectedDuration, targetState, velocity, isFling);
super.updateSwipeCompleteAnimation(animator, expectedDuration, targetState,
velocity, isFling);
+ handleFirstSwipeToOverview(animator, expectedDuration, targetState, velocity, isFling);
}
private void handleFirstSwipeToOverview(final ValueAnimator animator,
@@ -220,69 +186,24 @@ public class PortraitStatesTouchController extends AbstractStateChangeTouchContr
final boolean isFling) {
if (mFromState == NORMAL && mToState == OVERVIEW && targetState == OVERVIEW) {
mFinishFastOnSecondTouch = true;
-
- // Update all apps interpolator
- float currentFraction = mCurrentAnimation.getProgressFraction();
- float absVelocity = Math.abs(velocity);
- float currentValue = mAllAppsDampedInterpolator.getInterpolation(currentFraction);
-
- if (isFling && absVelocity > 1 && currentFraction < LINEAR_SCALE_LIMIT) {
-
- // TODO: Clean up these magic calculations
- // Linearly interpolate the max value based on the velocity.
- float maxValue = Math.max(absVelocity > 4 ? 1 + MAX_OVERSHOOT :
- 1 + (absVelocity - 1) * MAX_OVERSHOOT / 3,
- currentValue);
- double angleToPeak = PI_BY_2 - Math.asin(currentValue / maxValue);
-
- if (expectedDuration != 0 && angleToPeak != 0) {
-
- float distanceLeft = 1 - currentFraction;
- mAllAppsInterpolatorWrapper.baseInterpolator = (f) -> {
- float scaledF = (f - currentFraction) / distanceLeft;
-
- if (scaledF < 0.5f) {
- double angle = PI_BY_2 - angleToPeak + scaledF * angleToPeak / 0.5f;
- return (float) (maxValue * Math.sin(angle));
- }
-
- scaledF = ((scaledF - .5f) / .5f);
- double angle = PI_BY_2 + 3 * scaledF * PI_BY_2;
- float amplitude = (1 - scaledF) * (1 - scaledF) * (maxValue - 1);
- return 1 + (float) (amplitude * Math.sin(angle));
- };
-
- animator.setDuration(expectedDuration).setInterpolator(LINEAR);
- return;
- }
+ if (isFling && expectedDuration != 0) {
+ // Update all apps interpolator to add a bit of overshoot starting from currFraction
+ final float currFraction = mCurrentAnimation.getProgressFraction();
+ mAllAppsInterpolatorWrapper.baseInterpolator = Interpolators.clampToProgress(
+ new OvershootInterpolator(Math.min(Math.abs(velocity), 3f)), currFraction, 1);
+ animator.setDuration(Math.min(expectedDuration, ATOMIC_DURATION))
+ .setInterpolator(LINEAR);
}
-
- if (currentFraction < LINEAR_SCALE_LIMIT) {
- mAllAppsInterpolatorWrapper.baseInterpolator = LINEAR;
- return;
- }
- float extraValue = mAllAppsDampedInterpolator.getInterpolation(currentFraction) - 1;
- float distanceLeft = 1 - currentFraction;
-
- animator.setFloatValues(currentFraction, 1);
- mAllAppsInterpolatorWrapper.baseInterpolator = (f) -> {
- float scaledF = (f - currentFraction) / distanceLeft;
-
- double angle = scaledF * 1.5 * Math.PI;
- float amplitude = (1 - scaledF) * (1 - scaledF) * extraValue;
- return 1 + (float) (amplitude * Math.sin(angle));
- };
- animator.setDuration(200).setInterpolator(LINEAR);
- return;
+ } else {
+ mFinishFastOnSecondTouch = false;
}
- mFinishFastOnSecondTouch = false;
}
@Override
protected void onSwipeInteractionCompleted(LauncherState targetState, int logAction) {
super.onSwipeInteractionCompleted(targetState, logAction);
if (mFromState == NORMAL && targetState == OVERVIEW) {
- SysuiEventLogger.writeDummyRecentsTransition(0);
+ RecentsModel.getInstance(mLauncher).onOverviewShown(true, TAG);
}
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
index 124ec202d..e43b24a6a 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
@@ -15,39 +15,49 @@
*/
package com.android.launcher3.uioverrides;
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_TRANSLATION;
+import static com.android.launcher3.LauncherState.FAST_OVERVIEW;
+import static com.android.launcher3.LauncherState.OVERVIEW;
+import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_FADE;
+import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_SCALE;
import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE_IN_OUT;
import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.quickstep.QuickScrubController.QUICK_SCRUB_START_INTERPOLATOR;
+import static com.android.quickstep.QuickScrubController.QUICK_SCRUB_TRANSLATION_Y_FACTOR;
import static com.android.quickstep.views.LauncherRecentsView.TRANSLATION_Y_FACTOR;
import static com.android.quickstep.views.RecentsView.ADJACENT_SCALE;
-import static com.android.quickstep.views.RecentsView.CONTENT_ALPHA;
+import static com.android.quickstep.views.RecentsViewContainer.CONTENT_ALPHA;
import android.animation.ValueAnimator;
import android.annotation.TargetApi;
import android.os.Build;
+import android.view.animation.Interpolator;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.LauncherStateManager.AnimationConfig;
import com.android.launcher3.LauncherStateManager.StateHandler;
import com.android.launcher3.anim.AnimatorSetBuilder;
+import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.anim.PropertySetter;
import com.android.quickstep.views.LauncherRecentsView;
+import com.android.quickstep.views.RecentsViewContainer;
@TargetApi(Build.VERSION_CODES.O)
public class RecentsViewStateController implements StateHandler {
private final Launcher mLauncher;
private final LauncherRecentsView mRecentsView;
+ private final RecentsViewContainer mRecentsViewContainer;
public RecentsViewStateController(Launcher launcher) {
mLauncher = launcher;
mRecentsView = launcher.getOverviewPanel();
+ mRecentsViewContainer = launcher.getOverviewPanelContainer();
}
@Override
public void setState(LauncherState state) {
- mRecentsView.setContentAlpha(state.overviewUi ? 1 : 0);
+ mRecentsViewContainer.setContentAlpha(state.overviewUi ? 1 : 0);
float[] scaleTranslationYFactor = state.getOverviewScaleAndTranslationYFactor(mLauncher);
mRecentsView.setAdjacentScale(scaleTranslationYFactor[0]);
mRecentsView.setTranslationYFactor(scaleTranslationYFactor[1]);
@@ -60,14 +70,23 @@ public class RecentsViewStateController implements StateHandler {
@Override
public void setStateWithAnimation(final LauncherState toState,
AnimatorSetBuilder builder, AnimationConfig config) {
- PropertySetter setter = config.getProperSetter(builder);
+ if (!config.playAtomicComponent()) {
+ // The entire recents animation is played atomically.
+ return;
+ }
+ PropertySetter setter = config.getPropertySetter(builder);
float[] scaleTranslationYFactor = toState.getOverviewScaleAndTranslationYFactor(mLauncher);
- setter.setFloat(mRecentsView, ADJACENT_SCALE, scaleTranslationYFactor[0],
- builder.getInterpolator(ANIM_OVERVIEW_TRANSLATION, LINEAR));
+ Interpolator scaleInterpolator = builder.getInterpolator(ANIM_OVERVIEW_SCALE, LINEAR);
+ setter.setFloat(mRecentsView, ADJACENT_SCALE, scaleTranslationYFactor[0], scaleInterpolator);
+ Interpolator transYInterpolator = scaleInterpolator;
+ if (mLauncher.getStateManager().getState() == OVERVIEW && toState == FAST_OVERVIEW) {
+ transYInterpolator = Interpolators.clampToProgress(QUICK_SCRUB_START_INTERPOLATOR, 0,
+ QUICK_SCRUB_TRANSLATION_Y_FACTOR);
+ }
setter.setFloat(mRecentsView, TRANSLATION_Y_FACTOR, scaleTranslationYFactor[1],
- builder.getInterpolator(ANIM_OVERVIEW_TRANSLATION, LINEAR));
- setter.setFloat(mRecentsView, CONTENT_ALPHA, toState.overviewUi ? 1 : 0,
- AGGRESSIVE_EASE_IN_OUT);
+ transYInterpolator);
+ setter.setFloat(mRecentsViewContainer, CONTENT_ALPHA, toState.overviewUi ? 1 : 0,
+ builder.getInterpolator(ANIM_OVERVIEW_FADE, AGGRESSIVE_EASE_IN_OUT));
if (!toState.overviewUi) {
builder.addOnFinishRunnable(mRecentsView::resetTaskVisuals);
diff --git a/quickstep/src/com/android/launcher3/uioverrides/TaskViewTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/TaskViewTouchController.java
index 84a60bd9a..a40573500 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/TaskViewTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/TaskViewTouchController.java
@@ -15,25 +15,27 @@
*/
package com.android.launcher3.uioverrides;
+import static com.android.launcher3.Utilities.SINGLE_FRAME_MS;
import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
-import android.util.Log;
import android.view.MotionEvent;
-import android.view.View;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.BaseDraggingActivity;
+import com.android.launcher3.LauncherAnimUtils;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.touch.SwipeDetector;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
+import com.android.launcher3.util.FlingBlockCheck;
import com.android.launcher3.util.PendingAnimation;
import com.android.launcher3.util.TouchController;
import com.android.launcher3.views.BaseDragLayer;
+import com.android.quickstep.OverviewInteractionState;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
@@ -45,9 +47,6 @@ public abstract class TaskViewTouchController<T extends BaseDraggingActivity>
private static final String TAG = "OverviewSwipeController";
- private static final float ALLOWED_FLING_DIRECTION_CHANGE_PROGRESS = 0.1f;
- private static final int SINGLE_FRAME_MS = 16;
-
// Progress after which the transition is assumed to be a success in case user does not fling
private static final float SUCCESS_TRANSITION_PROGRESS = 0.5f;
@@ -65,6 +64,7 @@ public abstract class TaskViewTouchController<T extends BaseDraggingActivity>
private float mDisplacementShift;
private float mProgressMultiplier;
private float mEndDisplacement;
+ private FlingBlockCheck mFlingBlockCheck = new FlingBlockCheck();
private TaskView mTaskBeingDragged;
@@ -87,12 +87,13 @@ public abstract class TaskViewTouchController<T extends BaseDraggingActivity>
protected abstract boolean isRecentsInteractive();
+ protected void onUserControlledAnimationCreated(AnimatorPlaybackController animController) {
+ }
+
@Override
public void onAnimationCancel(Animator animation) {
if (mCurrentAnimation != null && animation == mCurrentAnimation.getTarget()) {
- Log.e(TAG, "Who dare cancel the animation when I am in control", new Exception());
- mDetector.finishedScrolling();
- mCurrentAnimation = null;
+ clearState();
}
}
@@ -106,7 +107,7 @@ public abstract class TaskViewTouchController<T extends BaseDraggingActivity>
// Now figure out which direction scroll events the controller will start
// calling the callbacks.
- final int directionsToDetectScroll;
+ int directionsToDetectScroll = 0;
boolean ignoreSlopWhenSettling = false;
if (mCurrentAnimation != null) {
directionsToDetectScroll = SwipeDetector.DIRECTION_BOTH;
@@ -114,12 +115,26 @@ public abstract class TaskViewTouchController<T extends BaseDraggingActivity>
} else {
mTaskBeingDragged = null;
- View view = mRecentsView.getChildAt(mRecentsView.getCurrentPage());
- if (view instanceof TaskView && mActivity.getDragLayer().isEventOverView(view, ev)) {
- // The tile can be dragged down to open the task.
- mTaskBeingDragged = (TaskView) view;
- directionsToDetectScroll = SwipeDetector.DIRECTION_BOTH;
- } else {
+ for (int i = 0; i < mRecentsView.getChildCount(); i++) {
+ TaskView view = mRecentsView.getPageAt(i);
+ if (mRecentsView.isTaskViewVisible(view) && mActivity.getDragLayer()
+ .isEventOverView(view, ev)) {
+ mTaskBeingDragged = view;
+ if (!OverviewInteractionState.getInstance(mActivity)
+ .isSwipeUpGestureEnabled()) {
+ // Don't allow swipe down to open if we don't support swipe up
+ // to enter overview.
+ directionsToDetectScroll = SwipeDetector.DIRECTION_POSITIVE;
+ } else {
+ // The task can be dragged up to dismiss it,
+ // and down to open if it's the current page.
+ directionsToDetectScroll = i == mRecentsView.getCurrentPage()
+ ? SwipeDetector.DIRECTION_BOTH : SwipeDetector.DIRECTION_POSITIVE;
+ }
+ break;
+ }
+ }
+ if (mTaskBeingDragged == null) {
mNoIntercept = true;
return false;
}
@@ -147,6 +162,12 @@ public abstract class TaskViewTouchController<T extends BaseDraggingActivity>
// No need to init
return;
}
+ int scrollDirections = mDetector.getScrollDirections();
+ if (goingUp && ((scrollDirections & SwipeDetector.DIRECTION_POSITIVE) == 0)
+ || !goingUp && ((scrollDirections & SwipeDetector.DIRECTION_NEGATIVE) == 0)) {
+ // Trying to re-init in an unsupported direction.
+ return;
+ }
if (mCurrentAnimation != null) {
mCurrentAnimation.setPlayFraction(0);
}
@@ -174,8 +195,12 @@ public abstract class TaskViewTouchController<T extends BaseDraggingActivity>
mEndDisplacement = dl.getHeight() - mTempCords[1];
}
+ if (mCurrentAnimation != null) {
+ mCurrentAnimation.setOnCancelRunnable(null);
+ }
mCurrentAnimation = AnimatorPlaybackController
- .wrap(mPendingAnimation.anim, maxDuration);
+ .wrap(mPendingAnimation.anim, maxDuration, this::clearState);
+ onUserControlledAnimationCreated(mCurrentAnimation);
mCurrentAnimation.getTarget().addListener(this);
mCurrentAnimation.dispatchOnStart();
mProgressMultiplier = 1 / mEndDisplacement;
@@ -190,6 +215,7 @@ public abstract class TaskViewTouchController<T extends BaseDraggingActivity>
mDisplacementShift = mCurrentAnimation.getProgressFraction() / mProgressMultiplier;
mCurrentAnimation.pause();
}
+ mFlingBlockCheck.unblockFling();
}
@Override
@@ -199,6 +225,9 @@ public abstract class TaskViewTouchController<T extends BaseDraggingActivity>
totalDisplacement == 0 ? mCurrentAnimationIsGoingUp : totalDisplacement < 0;
if (isGoingUp != mCurrentAnimationIsGoingUp) {
reInitAnimationController(isGoingUp);
+ mFlingBlockCheck.blockFling();
+ } else {
+ mFlingBlockCheck.onEvent();
}
mCurrentAnimation.setPlayFraction(totalDisplacement * mProgressMultiplier);
return true;
@@ -208,23 +237,14 @@ public abstract class TaskViewTouchController<T extends BaseDraggingActivity>
public void onDragEnd(float velocity, boolean fling) {
final boolean goingToEnd;
final int logAction;
+ boolean blockedFling = fling && mFlingBlockCheck.isBlocked();
+ if (blockedFling) {
+ fling = false;
+ }
if (fling) {
logAction = Touch.FLING;
boolean goingUp = velocity < 0;
- if (goingUp != mCurrentAnimationIsGoingUp) {
- // In case the fling is in opposite direction, make sure if is close enough
- // from the start position
- if (mCurrentAnimation.getProgressFraction()
- >= ALLOWED_FLING_DIRECTION_CHANGE_PROGRESS) {
- // Not allowed
- goingToEnd = false;
- } else {
- reInitAnimationController(goingUp);
- goingToEnd = true;
- }
- } else {
- goingToEnd = true;
- }
+ goingToEnd = goingUp == mCurrentAnimationIsGoingUp;
} else {
logAction = Touch.SWIPE;
goingToEnd = mCurrentAnimation.getProgressFraction() > SUCCESS_TRANSITION_PROGRESS;
@@ -233,6 +253,9 @@ public abstract class TaskViewTouchController<T extends BaseDraggingActivity>
float progress = mCurrentAnimation.getProgressFraction();
long animationDuration = SwipeDetector.calculateDuration(
velocity, goingToEnd ? (1 - progress) : progress);
+ if (blockedFling && !goingToEnd) {
+ animationDuration *= LauncherAnimUtils.blockedFlingDurationFactor(velocity);
+ }
float nextFrameProgress = Utilities.boundToRange(
progress + velocity * SINGLE_FRAME_MS / Math.abs(mEndDisplacement), 0f, 1f);
@@ -251,8 +274,17 @@ public abstract class TaskViewTouchController<T extends BaseDraggingActivity>
mPendingAnimation.finish(wasSuccess, logAction);
mPendingAnimation = null;
}
+ clearState();
+ }
+
+ private void clearState() {
mDetector.finishedScrolling();
+ mDetector.setDetectableScrollConditions(0, false);
mTaskBeingDragged = null;
mCurrentAnimation = null;
+ if (mPendingAnimation != null) {
+ mPendingAnimation.finish(false, Touch.SWIPE);
+ mPendingAnimation = null;
+ }
}
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java b/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
index c1590f63d..b371677f3 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java
@@ -16,51 +16,67 @@
package com.android.launcher3.uioverrides;
+import static android.view.View.VISIBLE;
+import static com.android.launcher3.AbstractFloatingView.TYPE_ALL;
+import static com.android.launcher3.AbstractFloatingView.TYPE_HIDE_BACK_BUTTON;
+import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.LauncherState.OVERVIEW;
-import static com.android.launcher3.Utilities.getPrefs;
-import static com.android.quickstep.OverviewInteractionState.KEY_SWIPE_UP_ENABLED;
-import static com.android.launcher3.LauncherState.ALL_APPS;
+import static com.android.launcher3.allapps.DiscoveryBounce.HOME_BOUNCE_SEEN;
+import static com.android.launcher3.allapps.DiscoveryBounce.SHELF_BOUNCE_SEEN;
+import android.app.Activity;
import android.content.Context;
-import android.content.SharedPreferences;
+import android.util.Base64;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
+import com.android.launcher3.LauncherStateManager;
import com.android.launcher3.LauncherStateManager.StateHandler;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.util.TouchController;
import com.android.quickstep.OverviewInteractionState;
import com.android.quickstep.RecentsModel;
import com.android.quickstep.views.RecentsView;
+import com.android.systemui.shared.system.ActivityCompat;
import com.android.systemui.shared.system.WindowManagerWrapper;
+import java.io.ByteArrayOutputStream;
+import java.io.PrintWriter;
+import java.util.zip.Deflater;
+
public class UiFactory {
public static TouchController[] createTouchControllers(Launcher launcher) {
- SharedPreferences prefs = getPrefs(launcher);
- boolean swipeUpEnabled = prefs.getBoolean(KEY_SWIPE_UP_ENABLED, true);
+ boolean swipeUpEnabled = OverviewInteractionState.getInstance(launcher)
+ .isSwipeUpGestureEnabled();
if (!swipeUpEnabled) {
return new TouchController[] {
launcher.getDragController(),
- new LandscapeStatesTouchController(launcher),
- new LauncherTaskViewcontroller(launcher)};
+ new OverviewToAllAppsTouchController(launcher),
+ new LauncherTaskViewController(launcher)};
}
if (launcher.getDeviceProfile().isVerticalBarLayout()) {
return new TouchController[] {
launcher.getDragController(),
- new LandscapeStatesTouchController(launcher),
+ new OverviewToAllAppsTouchController(launcher),
new LandscapeEdgeSwipeController(launcher),
- new LauncherTaskViewcontroller(launcher)};
+ new LauncherTaskViewController(launcher)};
} else {
return new TouchController[] {
launcher.getDragController(),
new PortraitStatesTouchController(launcher),
- new LauncherTaskViewcontroller(launcher)};
+ new LauncherTaskViewController(launcher)};
}
}
+ public static void setOnTouchControllersChangedListener(Context context, Runnable listener) {
+ OverviewInteractionState.getInstance(context).setOnSwipeUpSettingChangedListener(listener);
+ }
+
public static StateHandler[] getStateHandler(Launcher launcher) {
return new StateHandler[] {
launcher.getAllAppsController(), launcher.getWorkspace(),
@@ -73,10 +89,15 @@ public class UiFactory {
&& launcher.hasWindowFocus();
if (shouldBackButtonBeHidden) {
// Show the back button if there is a floating view visible.
- shouldBackButtonBeHidden = AbstractFloatingView.getTopOpenView(launcher) == null;
+ shouldBackButtonBeHidden = AbstractFloatingView.getTopOpenViewWithType(launcher,
+ TYPE_ALL & ~TYPE_HIDE_BACK_BUTTON) == null;
}
OverviewInteractionState.getInstance(launcher)
- .setBackButtonVisible(!shouldBackButtonBeHidden);
+ .setBackButtonAlpha(shouldBackButtonBeHidden ? 0 : 1, true /* animate */);
+ }
+
+ public static void setBackButtonAlpha(Launcher launcher, float alpha, boolean animate) {
+ OverviewInteractionState.getInstance(launcher).setBackButtonAlpha(alpha,animate);
}
public static void resetOverview(Launcher launcher) {
@@ -84,6 +105,55 @@ public class UiFactory {
recents.reset();
}
+ public static void onCreate(Launcher launcher) {
+ if (!launcher.getSharedPrefs().getBoolean(HOME_BOUNCE_SEEN, false)) {
+ launcher.getStateManager().addStateListener(new LauncherStateManager.StateListener() {
+ @Override
+ public void onStateSetImmediately(LauncherState state) {
+ }
+
+ @Override
+ public void onStateTransitionStart(LauncherState toState) {
+ }
+
+ @Override
+ public void onStateTransitionComplete(LauncherState finalState) {
+ boolean swipeUpEnabled = OverviewInteractionState.getInstance(launcher)
+ .isSwipeUpGestureEnabled();
+ LauncherState prevState = launcher.getStateManager().getLastState();
+
+ if (((swipeUpEnabled && finalState == OVERVIEW) || (!swipeUpEnabled
+ && finalState == ALL_APPS && prevState == NORMAL))) {
+ launcher.getSharedPrefs().edit().putBoolean(HOME_BOUNCE_SEEN, true).apply();
+ launcher.getStateManager().removeStateListener(this);
+ }
+ }
+ });
+ }
+
+ if (!launcher.getSharedPrefs().getBoolean(SHELF_BOUNCE_SEEN, false)) {
+ launcher.getStateManager().addStateListener(new LauncherStateManager.StateListener() {
+ @Override
+ public void onStateSetImmediately(LauncherState state) {
+ }
+
+ @Override
+ public void onStateTransitionStart(LauncherState toState) {
+ }
+
+ @Override
+ public void onStateTransitionComplete(LauncherState finalState) {
+ LauncherState prevState = launcher.getStateManager().getLastState();
+
+ if (finalState == ALL_APPS && prevState == OVERVIEW) {
+ launcher.getSharedPrefs().edit().putBoolean(SHELF_BOUNCE_SEEN, true).apply();
+ launcher.getStateManager().removeStateListener(this);
+ }
+ }
+ });
+ }
+ }
+
public static void onStart(Context context) {
RecentsModel model = RecentsModel.getInstance(context);
if (model != null) {
@@ -110,9 +180,42 @@ public class UiFactory {
}
}
- private static class LauncherTaskViewcontroller extends TaskViewTouchController<Launcher> {
+ public static boolean dumpActivity(Activity activity, PrintWriter writer) {
+ if (!Utilities.IS_DEBUG_DEVICE) {
+ return false;
+ }
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ if (!(new ActivityCompat(activity).encodeViewHierarchy(out))) {
+ return false;
+ }
+
+ Deflater deflater = new Deflater();
+ deflater.setInput(out.toByteArray());
+ deflater.finish();
+
+ out.reset();
+ byte[] buffer = new byte[1024];
+ while (!deflater.finished()) {
+ int count = deflater.deflate(buffer); // returns the generated code... index
+ out.write(buffer, 0, count);
+ }
+
+ writer.println("--encoded-view-dump-v0--");
+ writer.println(Base64.encodeToString(
+ out.toByteArray(), Base64.NO_WRAP | Base64.NO_PADDING));
+ return true;
+ }
+
+ public static void prepareToShowOverview(Launcher launcher) {
+ RecentsView overview = launcher.getOverviewPanel();
+ if (overview.getVisibility() != VISIBLE || overview.getContentAlpha() == 0) {
+ overview.setAdjacentScale(1.33f);
+ }
+ }
+
+ private static class LauncherTaskViewController extends TaskViewTouchController<Launcher> {
- public LauncherTaskViewcontroller(Launcher activity) {
+ public LauncherTaskViewController(Launcher activity) {
super(activity);
}
@@ -120,5 +223,10 @@ public class UiFactory {
protected boolean isRecentsInteractive() {
return mActivity.isInState(OVERVIEW);
}
+
+ @Override
+ protected void onUserControlledAnimationCreated(AnimatorPlaybackController animController) {
+ mActivity.getStateManager().setCurrentUserControlledAnimation(animController);
+ }
}
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/WallpaperColorInfo.java b/quickstep/src/com/android/launcher3/uioverrides/WallpaperColorInfo.java
new file mode 100644
index 000000000..8218517dc
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/uioverrides/WallpaperColorInfo.java
@@ -0,0 +1,115 @@
+/*
+ * 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.uioverrides;
+
+import static android.app.WallpaperManager.FLAG_SYSTEM;
+
+import android.annotation.TargetApi;
+import android.app.WallpaperColors;
+import android.app.WallpaperManager;
+import android.app.WallpaperManager.OnColorsChangedListener;
+import android.content.Context;
+import android.os.Build;
+import android.os.Handler;
+import android.os.Looper;
+
+import com.android.systemui.shared.system.TonalCompat;
+import com.android.systemui.shared.system.TonalCompat.ExtractionInfo;
+
+import java.util.ArrayList;
+
+@TargetApi(Build.VERSION_CODES.P)
+public class WallpaperColorInfo implements OnColorsChangedListener {
+
+ private static final Object sInstanceLock = new Object();
+ private static WallpaperColorInfo sInstance;
+
+ public static WallpaperColorInfo getInstance(Context context) {
+ synchronized (sInstanceLock) {
+ if (sInstance == null) {
+ sInstance = new WallpaperColorInfo(context.getApplicationContext());
+ }
+ return sInstance;
+ }
+ }
+
+ private final ArrayList<OnChangeListener> mListeners = new ArrayList<>();
+ private final WallpaperManager mWallpaperManager;
+ private final TonalCompat mTonalCompat;
+
+ private ExtractionInfo mExtractionInfo;
+
+ private OnChangeListener[] mTempListeners = new OnChangeListener[0];
+
+ private WallpaperColorInfo(Context context) {
+ mWallpaperManager = context.getSystemService(WallpaperManager.class);
+ mTonalCompat = new TonalCompat(context);
+
+ mWallpaperManager.addOnColorsChangedListener(this, new Handler(Looper.getMainLooper()));
+ update(mWallpaperManager.getWallpaperColors(FLAG_SYSTEM));
+ }
+
+ public int getMainColor() {
+ return mExtractionInfo.mainColor;
+ }
+
+ public int getSecondaryColor() {
+ return mExtractionInfo.secondaryColor;
+ }
+
+ public boolean isDark() {
+ return mExtractionInfo.supportsDarkTheme;
+ }
+
+ public boolean supportsDarkText() {
+ return mExtractionInfo.supportsDarkText;
+ }
+
+ @Override
+ public void onColorsChanged(WallpaperColors colors, int which) {
+ if ((which & FLAG_SYSTEM) != 0) {
+ update(colors);
+ notifyChange();
+ }
+ }
+
+ private void update(WallpaperColors wallpaperColors) {
+ mExtractionInfo = mTonalCompat.extractDarkColors(wallpaperColors);
+ }
+
+ public void addOnChangeListener(OnChangeListener listener) {
+ mListeners.add(listener);
+ }
+
+ public void removeOnChangeListener(OnChangeListener listener) {
+ mListeners.remove(listener);
+ }
+
+ private void notifyChange() {
+ // Create a new array to avoid concurrent modification when the activity destroys itself.
+ mTempListeners = mListeners.toArray(mTempListeners);
+ for (OnChangeListener listener : mTempListeners) {
+ if (listener != null) {
+ listener.onExtractedColorsChanged(this);
+ }
+ }
+ }
+
+ public interface OnChangeListener {
+ void onExtractedColorsChanged(WallpaperColorInfo wallpaperColorInfo);
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/ActivityControlHelper.java b/quickstep/src/com/android/quickstep/ActivityControlHelper.java
index 584128585..ae0affee0 100644
--- a/quickstep/src/com/android/quickstep/ActivityControlHelper.java
+++ b/quickstep/src/com/android/quickstep/ActivityControlHelper.java
@@ -15,16 +15,23 @@
*/
package com.android.quickstep;
+import static com.android.launcher3.LauncherAnimUtils.OVERVIEW_TRANSITION_MS;
import static com.android.launcher3.LauncherState.FAST_OVERVIEW;
import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PROGRESS;
import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_BACK;
+import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_ROTATION;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
+import android.annotation.TargetApi;
+import android.app.ActivityManager.RunningTaskInfo;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.graphics.Rect;
+import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.support.annotation.Nullable;
@@ -38,38 +45,41 @@ import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherInitListener;
import com.android.launcher3.LauncherState;
import com.android.launcher3.allapps.AllAppsTransitionController;
+import com.android.launcher3.allapps.DiscoveryBounce;
import com.android.launcher3.anim.AnimatorPlaybackController;
-import com.android.launcher3.util.ViewOnDrawExecutor;
-import com.android.quickstep.fallback.FallbackRecentsView;
+import com.android.launcher3.dragndrop.DragLayer;
+import com.android.launcher3.userevent.nano.LauncherLogProto;
+import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
+import com.android.quickstep.util.LayoutUtils;
import com.android.quickstep.util.RemoteAnimationProvider;
+import com.android.quickstep.util.RemoteAnimationTargetSet;
import com.android.quickstep.views.LauncherLayoutListener;
import com.android.quickstep.views.LauncherRecentsView;
import com.android.quickstep.views.RecentsView;
-import com.android.quickstep.views.TaskView;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
-import com.android.systemui.shared.system.AssistDataReceiver;
-import com.android.systemui.shared.system.RecentsAnimationListener;
+import com.android.quickstep.views.RecentsViewContainer;
+import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
+import java.util.Objects;
import java.util.function.BiPredicate;
+import java.util.function.Consumer;
/**
* Utility class which abstracts out the logical differences between Launcher and RecentsActivity.
*/
+@TargetApi(Build.VERSION_CODES.P)
public interface ActivityControlHelper<T extends BaseDraggingActivity> {
LayoutListener createLayoutListener(T activity);
- void onQuickstepGestureStarted(T activity, boolean activityVisible);
-
/**
* Updates the UI to indicate quick interaction.
- * @return true if there any any UI change as a result of this
*/
- boolean onQuickInteractionStart(T activity, boolean activityVisible);
+ void onQuickInteractionStart(T activity, @Nullable RunningTaskInfo taskInfo,
+ boolean activityVisible);
- void executeOnWindowAvailable(T activity, Runnable action);
+ float getTranslationYForQuickScrub(T activity);
- void executeOnNextDraw(T activity, TaskView targetView, Runnable action);
+ void executeOnWindowAvailable(T activity, Runnable action);
void onTransitionCancelled(T activity, boolean activityVisible);
@@ -77,17 +87,11 @@ public interface ActivityControlHelper<T extends BaseDraggingActivity> {
void onSwipeUpComplete(T activity);
- void prepareRecentsUI(T activity, boolean activityVisible);
-
- AnimatorPlaybackController createControllerForVisibleActivity(T activity);
-
- AnimatorPlaybackController createControllerForHiddenActivity(T activity, int transitionLength);
+ AnimationFactory prepareRecentsUI(T activity, boolean activityVisible,
+ Consumer<AnimatorPlaybackController> callback);
ActivityInitListener createActivityInitListener(BiPredicate<T, Boolean> onInitListener);
- void startRecentsFromSwipe(Intent intent, AssistDataReceiver assistDataReceiver,
- final RecentsAnimationListener remoteAnimationListener);
-
@Nullable
T getCreatedActivity();
@@ -96,7 +100,31 @@ public interface ActivityControlHelper<T extends BaseDraggingActivity> {
RecentsView getVisibleRecentsView();
@UiThread
- boolean switchToRecentsIfVisible();
+ boolean switchToRecentsIfVisible(boolean fromRecentsButton);
+
+ Rect getOverviewWindowBounds(Rect homeBounds, RemoteAnimationTargetCompat target);
+
+ boolean shouldMinimizeSplitScreen();
+
+ /**
+ * @return {@code true} if recents activity should be started immediately on touchDown,
+ * {@code false} if it should deferred until some threshold is crossed.
+ */
+ boolean deferStartingActivity(int downHitTarget);
+
+ boolean supportsLongSwipe(T activity);
+
+ AlphaProperty getAlphaProperty(T activity);
+
+ /**
+ * Must return a non-null controller is supportsLongSwipe was true.
+ */
+ LongSwipeHelper getLongSwipeController(T activity, RemoteAnimationTargetSet targetSet);
+
+ /**
+ * Used for containerType in {@link com.android.launcher3.logging.UserEventDispatcher}
+ */
+ int getContainerType();
class LauncherActivityControllerHelper implements ActivityControlHelper<Launcher> {
@@ -106,42 +134,31 @@ public interface ActivityControlHelper<T extends BaseDraggingActivity> {
}
@Override
- public void onQuickstepGestureStarted(Launcher activity, boolean activityVisible) {
- activity.onQuickstepGestureStarted(activityVisible);
- }
-
- @Override
- public boolean onQuickInteractionStart(Launcher activity, boolean activityVisible) {
+ public void onQuickInteractionStart(Launcher activity, RunningTaskInfo taskInfo,
+ boolean activityVisible) {
LauncherState fromState = activity.getStateManager().getState();
activity.getStateManager().goToState(FAST_OVERVIEW, activityVisible);
- return !fromState.overviewUi;
+
+ QuickScrubController controller = activity.<RecentsView>getOverviewPanel()
+ .getQuickScrubController();
+ controller.onQuickScrubStart(activityVisible && !fromState.overviewUi, this);
}
@Override
- public void executeOnWindowAvailable(Launcher activity, Runnable action) {
- if (activity.getWorkspace().runOnOverlayHidden(action)) {
- // Notify the activity that qiuckscrub has started
- onQuickstepGestureStarted(activity, true);
- }
+ public float getTranslationYForQuickScrub(Launcher activity) {
+ LauncherRecentsView recentsView = activity.getOverviewPanel();
+ float transYFactor = FAST_OVERVIEW.getOverviewScaleAndTranslationYFactor(activity)[1];
+ return recentsView.computeTranslationYForFactor(transYFactor);
}
@Override
- public void executeOnNextDraw(Launcher activity, TaskView targetView, Runnable action) {
- ViewOnDrawExecutor executor = new ViewOnDrawExecutor() {
- @Override
- public void onViewDetachedFromWindow(View v) {
- if (!isCompleted()) {
- runAllTasks();
- }
- }
- };
- executor.attachTo(activity, targetView, false /* waitForLoadAnimation */);
- executor.execute(action);
+ public void executeOnWindowAvailable(Launcher activity, Runnable action) {
+ activity.getWorkspace().runOnOverlayHidden(action);
}
@Override
public int getSwipeUpDestinationAndLength(DeviceProfile dp, Context context, Rect outRect) {
- LauncherRecentsView.getPageRect(dp, context, outRect);
+ LayoutUtils.calculateLauncherTaskSize(context, dp, outRect);
if (dp.isVerticalBarLayout()) {
Rect targetInsets = dp.getInsets();
int hotseatInset = dp.isSeascape() ? targetInsets.left : targetInsets.right;
@@ -161,15 +178,19 @@ public interface ActivityControlHelper<T extends BaseDraggingActivity> {
public void onSwipeUpComplete(Launcher activity) {
// Re apply state in case we did something funky during the transition.
activity.getStateManager().reapplyState();
+ DiscoveryBounce.showForOverviewIfNeeded(activity);
}
@Override
- public void prepareRecentsUI(Launcher activity, boolean activityVisible) {
- LauncherState startState = activity.getStateManager().getState();
+ public AnimationFactory prepareRecentsUI(Launcher activity, boolean activityVisible,
+ Consumer<AnimatorPlaybackController> callback) {
+ final LauncherState startState = activity.getStateManager().getState();
+
+ LauncherState resetState = startState;
if (startState.disableRestore) {
- startState = activity.getStateManager().getRestState();
+ resetState = activity.getStateManager().getRestState();
}
- activity.getStateManager().setRestState(startState);
+ activity.getStateManager().setRestState(resetState);
if (!activityVisible) {
// Since the launcher is not visible, we can safely reset the scroll position.
@@ -180,38 +201,53 @@ public interface ActivityControlHelper<T extends BaseDraggingActivity> {
// Optimization, hide the all apps view to prevent layout while initializing
activity.getAppsView().getContentView().setVisibility(View.GONE);
}
- }
- @Override
- public AnimatorPlaybackController createControllerForVisibleActivity(Launcher activity) {
- DeviceProfile dp = activity.getDeviceProfile();
- long accuracy = 2 * Math.max(dp.widthPx, dp.heightPx);
- return activity.getStateManager().createAnimationToNewWorkspace(OVERVIEW, accuracy);
+ return new AnimationFactory() {
+ @Override
+ public void createActivityController(long transitionLength) {
+ createActivityControllerInternal(activity, activityVisible, startState,
+ transitionLength, callback);
+ }
+
+ @Override
+ public void onTransitionCancelled() {
+ activity.getStateManager().goToState(startState, false /* animate */);
+ }
+ };
}
- @Override
- public AnimatorPlaybackController createControllerForHiddenActivity(
- Launcher activity, int transitionLength) {
- AllAppsTransitionController controller = activity.getAllAppsController();
- AnimatorSet anim = new AnimatorSet();
+ private void createActivityControllerInternal(Launcher activity, boolean wasVisible,
+ LauncherState startState, long transitionLength,
+ Consumer<AnimatorPlaybackController> callback) {
+ if (wasVisible) {
+ DeviceProfile dp = activity.getDeviceProfile();
+ long accuracy = 2 * Math.max(dp.widthPx, dp.heightPx);
+ activity.getStateManager().goToState(startState, false);
+ callback.accept(activity.getStateManager()
+ .createAnimationToNewWorkspace(OVERVIEW, accuracy));
+ return;
+ }
+
if (activity.getDeviceProfile().isVerticalBarLayout()) {
- // TODO:
- } else {
- float scrollRange = Math.max(controller.getShiftRange(), 1);
- float progressDelta = (transitionLength / scrollRange);
-
- float endProgress = OVERVIEW.getVerticalProgress(activity);
- float startProgress = endProgress + progressDelta;
- ObjectAnimator shiftAnim = ObjectAnimator.ofFloat(
- controller, ALL_APPS_PROGRESS, startProgress, endProgress);
- shiftAnim.setInterpolator(LINEAR);
- anim.play(shiftAnim);
+ return;
}
- // TODO: Link this animation to state animation, so that it is cancelled
- // automatically on state change
+ AllAppsTransitionController controller = activity.getAllAppsController();
+ AnimatorSet anim = new AnimatorSet();
+
+ float scrollRange = Math.max(controller.getShiftRange(), 1);
+ float progressDelta = (transitionLength / scrollRange);
+
+ float endProgress = OVERVIEW.getVerticalProgress(activity);
+ float startProgress = endProgress + progressDelta;
+ ObjectAnimator shiftAnim = ObjectAnimator.ofFloat(
+ controller, ALL_APPS_PROGRESS, startProgress, endProgress);
+ shiftAnim.setInterpolator(LINEAR);
+ anim.play(shiftAnim);
+
anim.setDuration(transitionLength * 2);
- return AnimatorPlaybackController.wrap(anim, transitionLength * 2);
+ activity.getStateManager().setCurrentAnimation(anim);
+ callback.accept(AnimatorPlaybackController.wrap(anim, transitionLength * 2));
}
@Override
@@ -220,13 +256,6 @@ public interface ActivityControlHelper<T extends BaseDraggingActivity> {
return new LauncherInitListener(onInitListener);
}
- @Override
- public void startRecentsFromSwipe(Intent intent, AssistDataReceiver assistDataReceiver,
- final RecentsAnimationListener remoteAnimationListener) {
- ActivityManagerWrapper.getInstance().startRecentsActivity(
- intent, assistDataReceiver, remoteAnimationListener, null, null);
- }
-
@Nullable
@Override
public Launcher getCreatedActivity() {
@@ -249,44 +278,101 @@ public interface ActivityControlHelper<T extends BaseDraggingActivity> {
@Override
public RecentsView getVisibleRecentsView() {
Launcher launcher = getVisibleLaucher();
- return launcher != null && launcher.isInState(OVERVIEW)
+ return launcher != null && launcher.getStateManager().getState().overviewUi
? launcher.getOverviewPanel() : null;
}
@Override
- public boolean switchToRecentsIfVisible() {
+ public boolean switchToRecentsIfVisible(boolean fromRecentsButton) {
Launcher launcher = getVisibleLaucher();
if (launcher != null) {
+ if (fromRecentsButton) {
+ launcher.getUserEventDispatcher().logActionCommand(
+ LauncherLogProto.Action.Command.RECENTS_BUTTON,
+ getContainerType(),
+ LauncherLogProto.ContainerType.TASKSWITCHER);
+ }
launcher.getStateManager().goToState(OVERVIEW);
return true;
}
return false;
}
+
+ @Override
+ public boolean deferStartingActivity(int downHitTarget) {
+ return downHitTarget == HIT_TARGET_BACK || downHitTarget == HIT_TARGET_ROTATION;
+ }
+
+ @Override
+ public Rect getOverviewWindowBounds(Rect homeBounds, RemoteAnimationTargetCompat target) {
+ return homeBounds;
+ }
+
+ @Override
+ public boolean shouldMinimizeSplitScreen() {
+ return true;
+ }
+
+ @Override
+ public boolean supportsLongSwipe(Launcher activity) {
+ return !activity.getDeviceProfile().isVerticalBarLayout();
+ }
+
+ @Override
+ public LongSwipeHelper getLongSwipeController(Launcher activity,
+ RemoteAnimationTargetSet targetSet) {
+ if (activity.getDeviceProfile().isVerticalBarLayout()) {
+ return null;
+ }
+ return new LongSwipeHelper(activity, targetSet);
+ }
+
+ @Override
+ public AlphaProperty getAlphaProperty(Launcher activity) {
+ return activity.getDragLayer().getAlphaProperty(DragLayer.ALPHA_INDEX_SWIPE_UP);
+ }
+
+ @Override
+ public int getContainerType() {
+ final Launcher launcher = getVisibleLaucher();
+ return launcher != null ? launcher.getStateManager().getState().containerType
+ : LauncherLogProto.ContainerType.APP;
+ }
}
class FallbackActivityControllerHelper implements ActivityControlHelper<RecentsActivity> {
- @Override
- public void onQuickstepGestureStarted(RecentsActivity activity, boolean activityVisible) {
- // TODO:
+ private final ComponentName mHomeComponent;
+ private final Handler mUiHandler = new Handler(Looper.getMainLooper());
+
+ public FallbackActivityControllerHelper(ComponentName homeComponent) {
+ mHomeComponent = homeComponent;
}
@Override
- public boolean onQuickInteractionStart(RecentsActivity activity, boolean activityVisible) {
- // Activity does not need any UI change for quickscrub.
- return false;
+ public void onQuickInteractionStart(RecentsActivity activity, RunningTaskInfo taskInfo,
+ boolean activityVisible) {
+ QuickScrubController controller = activity.<RecentsView>getOverviewPanel()
+ .getQuickScrubController();
+
+ // TODO: match user is as well
+ boolean startingFromHome = !activityVisible &&
+ (taskInfo == null || Objects.equals(taskInfo.topActivity, mHomeComponent));
+ controller.onQuickScrubStart(startingFromHome, this);
+ if (activityVisible) {
+ mUiHandler.postDelayed(controller::onFinishedTransitionToQuickScrub,
+ OVERVIEW_TRANSITION_MS);
+ }
}
@Override
- public void executeOnWindowAvailable(RecentsActivity activity, Runnable action) {
- action.run();
+ public float getTranslationYForQuickScrub(RecentsActivity activity) {
+ return 0;
}
@Override
- public void executeOnNextDraw(RecentsActivity activity, TaskView targetView,
- Runnable action) {
- // TODO:
- new Handler(Looper.getMainLooper()).post(action);
+ public void executeOnWindowAvailable(RecentsActivity activity, Runnable action) {
+ action.run();
}
@Override
@@ -296,7 +382,7 @@ public interface ActivityControlHelper<T extends BaseDraggingActivity> {
@Override
public int getSwipeUpDestinationAndLength(DeviceProfile dp, Context context, Rect outRect) {
- FallbackRecentsView.getPageRect(dp, context, outRect);
+ LayoutUtils.calculateFallbackTaskSize(context, dp, outRect);
if (dp.isVerticalBarLayout()) {
Rect targetInsets = dp.getInsets();
int hotseatInset = dp.isSeascape() ? targetInsets.left : targetInsets.right;
@@ -312,23 +398,43 @@ public interface ActivityControlHelper<T extends BaseDraggingActivity> {
}
@Override
- public void prepareRecentsUI(RecentsActivity activity, boolean activityVisible) {
- // TODO:
- }
+ public AnimationFactory prepareRecentsUI(RecentsActivity activity, boolean activityVisible,
+ Consumer<AnimatorPlaybackController> callback) {
+ if (activityVisible) {
+ return (transitionLength) -> { };
+ }
- @Override
- public AnimatorPlaybackController createControllerForVisibleActivity(
- RecentsActivity activity) {
- DeviceProfile dp = activity.getDeviceProfile();
- return createControllerForHiddenActivity(activity, Math.max(dp.widthPx, dp.heightPx));
- }
+ RecentsViewContainer rv = activity.getOverviewPanelContainer();
+ rv.setContentAlpha(0);
- @Override
- public AnimatorPlaybackController createControllerForHiddenActivity(
- RecentsActivity activity, int transitionLength) {
- // We do not animate anything. Create a empty controller
- AnimatorSet anim = new AnimatorSet();
- return AnimatorPlaybackController.wrap(anim, transitionLength * 2);
+ return new AnimationFactory() {
+
+ boolean isAnimatingHome = false;
+
+ @Override
+ public void onRemoteAnimationReceived(RemoteAnimationTargetSet targets) {
+ isAnimatingHome = targets != null && targets.isAnimatingHome();
+ if (!isAnimatingHome) {
+ rv.setContentAlpha(1);
+ }
+ createActivityController(getSwipeUpDestinationAndLength(
+ activity.getDeviceProfile(), activity, new Rect()));
+ }
+
+ @Override
+ public void createActivityController(long transitionLength) {
+ if (!isAnimatingHome) {
+ return;
+ }
+
+ ObjectAnimator anim = ObjectAnimator
+ .ofFloat(rv, RecentsViewContainer.CONTENT_ALPHA, 0, 1);
+ anim.setDuration(transitionLength).setInterpolator(LINEAR);
+ AnimatorSet animatorSet = new AnimatorSet();
+ animatorSet.play(anim);
+ callback.accept(AnimatorPlaybackController.wrap(animatorSet, transitionLength));
+ }
+ };
}
@Override
@@ -353,14 +459,6 @@ public interface ActivityControlHelper<T extends BaseDraggingActivity> {
return new RecentsActivityTracker(onInitListener);
}
- @Override
- public void startRecentsFromSwipe(Intent intent, AssistDataReceiver assistDataReceiver,
- final RecentsAnimationListener remoteAnimationListener) {
- // We can use the normal recents animation for swipe up
- ActivityManagerWrapper.getInstance().startRecentsActivity(
- intent, assistDataReceiver, remoteAnimationListener, null, null);
- }
-
@Nullable
@Override
public RecentsActivity getCreatedActivity() {
@@ -378,9 +476,48 @@ public interface ActivityControlHelper<T extends BaseDraggingActivity> {
}
@Override
- public boolean switchToRecentsIfVisible() {
+ public boolean switchToRecentsIfVisible(boolean fromRecentsButton) {
+ return false;
+ }
+
+ @Override
+ public boolean deferStartingActivity(int downHitTarget) {
+ // Always defer starting the activity when using fallback
+ return true;
+ }
+
+ @Override
+ public Rect getOverviewWindowBounds(Rect homeBounds, RemoteAnimationTargetCompat target) {
+ // TODO: Remove this once b/77875376 is fixed
+ return target.sourceContainerBounds;
+ }
+
+ @Override
+ public boolean shouldMinimizeSplitScreen() {
+ // TODO: Remove this once b/77875376 is fixed
+ return false;
+ }
+
+ @Override
+ public boolean supportsLongSwipe(RecentsActivity activity) {
return false;
}
+
+ @Override
+ public LongSwipeHelper getLongSwipeController(RecentsActivity activity,
+ RemoteAnimationTargetSet targetSet) {
+ return null;
+ }
+
+ @Override
+ public AlphaProperty getAlphaProperty(RecentsActivity activity) {
+ return activity.getDragLayer().getAlphaProperty(0);
+ }
+
+ @Override
+ public int getContainerType() {
+ return LauncherLogProto.ContainerType.SIDELOADED_LAUNCHER;
+ }
}
interface LayoutListener {
@@ -401,4 +538,13 @@ public interface ActivityControlHelper<T extends BaseDraggingActivity> {
void registerAndStartActivity(Intent intent, RemoteAnimationProvider animProvider,
Context context, Handler handler, long duration);
}
+
+ interface AnimationFactory {
+
+ default void onRemoteAnimationReceived(RemoteAnimationTargetSet targets) { }
+
+ void createActivityController(long transitionLength);
+
+ default void onTransitionCancelled() { }
+ }
}
diff --git a/quickstep/src/com/android/quickstep/LongSwipeHelper.java b/quickstep/src/com/android/quickstep/LongSwipeHelper.java
new file mode 100644
index 000000000..fbcde8bba
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/LongSwipeHelper.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep;
+
+import static com.android.launcher3.LauncherAnimUtils.MIN_PROGRESS_TO_ALL_APPS;
+import static com.android.launcher3.LauncherState.ALL_APPS;
+import static com.android.launcher3.LauncherState.OVERVIEW;
+import static com.android.launcher3.anim.Interpolators.DEACCEL;
+import static com.android.quickstep.WindowTransformSwipeHandler.MAX_SWIPE_DURATION;
+import static com.android.systemui.shared.recents.utilities.Utilities.getNextFrameNumber;
+import static com.android.systemui.shared.recents.utilities.Utilities.getSurface;
+
+import android.animation.ValueAnimator;
+import android.view.Surface;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAnimUtils;
+import com.android.launcher3.R;
+import com.android.launcher3.allapps.AllAppsTransitionController;
+import com.android.launcher3.allapps.DiscoveryBounce;
+import com.android.launcher3.anim.AnimatorPlaybackController;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
+import com.android.launcher3.util.FlingBlockCheck;
+import com.android.quickstep.util.RemoteAnimationTargetSet;
+import com.android.quickstep.views.RecentsView;
+import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
+import com.android.systemui.shared.system.TransactionCompat;
+
+/**
+ * Utility class to handle long swipe from an app.
+ * This assumes the presence of Launcher activity as long swipe is not supported on the
+ * fallback activity.
+ */
+public class LongSwipeHelper {
+
+ private static final float SWIPE_DURATION_MULTIPLIER =
+ Math.min(1 / MIN_PROGRESS_TO_ALL_APPS, 1 / (1 - MIN_PROGRESS_TO_ALL_APPS));
+
+ private final Launcher mLauncher;
+ private final RemoteAnimationTargetSet mTargetSet;
+
+ private float mMaxSwipeDistance = 1;
+ private AnimatorPlaybackController mAnimator;
+ private FlingBlockCheck mFlingBlockCheck = new FlingBlockCheck();
+
+ LongSwipeHelper(Launcher launcher, RemoteAnimationTargetSet targetSet) {
+ mLauncher = launcher;
+ mTargetSet = targetSet;
+ init();
+ }
+
+ private void init() {
+ setTargetAlpha(0, true);
+ mFlingBlockCheck.blockFling();
+
+ // Init animations
+ AllAppsTransitionController controller = mLauncher.getAllAppsController();
+ // TODO: Scale it down so that we can reach all-apps in screen space
+ mMaxSwipeDistance = Math.max(1, controller.getProgress() * controller.getShiftRange());
+ mAnimator = mLauncher.getStateManager()
+ .createAnimationToNewWorkspace(ALL_APPS, Math.round(2 * mMaxSwipeDistance));
+ mAnimator.dispatchOnStart();
+ }
+
+ public void onMove(float displacement) {
+ mAnimator.setPlayFraction(displacement / mMaxSwipeDistance);
+ mFlingBlockCheck.onEvent();
+ }
+
+ public void destroy() {
+ // TODO: We can probably also hide the task view
+ setTargetAlpha(1, false);
+
+ mLauncher.getStateManager().goToState(OVERVIEW, false);
+ }
+
+ public void end(float velocity, boolean isFling, Runnable callback) {
+ long duration = MAX_SWIPE_DURATION;
+
+ final float currentFraction = mAnimator.getProgressFraction();
+ final boolean toAllApps;
+ float endProgress;
+
+ boolean blockedFling = isFling && mFlingBlockCheck.isBlocked();
+ if (blockedFling) {
+ isFling = false;
+ }
+
+ if (!isFling) {
+ toAllApps = currentFraction > MIN_PROGRESS_TO_ALL_APPS;
+ endProgress = toAllApps ? 1 : 0;
+
+ long expectedDuration = Math.abs(Math.round((endProgress - currentFraction)
+ * MAX_SWIPE_DURATION * SWIPE_DURATION_MULTIPLIER));
+ duration = Math.min(MAX_SWIPE_DURATION, expectedDuration);
+ } else {
+ toAllApps = velocity < 0;
+ endProgress = toAllApps ? 1 : 0;
+
+ float minFlingVelocity = mLauncher.getResources()
+ .getDimension(R.dimen.quickstep_fling_min_velocity);
+ if (Math.abs(velocity) > minFlingVelocity && mMaxSwipeDistance > 0) {
+ float distanceToTravel = (endProgress - currentFraction) * mMaxSwipeDistance;
+
+ // we want the page's snap velocity to approximately match the velocity at
+ // which the user flings, so we scale the duration by a value near to the
+ // derivative of the scroll interpolator at zero, ie. 2.
+ long baseDuration = Math.round(1000 * Math.abs(distanceToTravel / velocity));
+ duration = Math.min(MAX_SWIPE_DURATION, 2 * baseDuration);
+ }
+ }
+
+ if (blockedFling && !toAllApps) {
+ duration *= LauncherAnimUtils.blockedFlingDurationFactor(0);
+ }
+ final boolean finalIsFling = isFling;
+ mAnimator.setEndAction(() -> onSwipeAnimationComplete(toAllApps, finalIsFling, callback));
+ ValueAnimator animator = mAnimator.getAnimationPlayer();
+ animator.setDuration(duration).setInterpolator(DEACCEL);
+ animator.setFloatValues(currentFraction, endProgress);
+ animator.start();
+ }
+
+ private void setTargetAlpha(float alpha, boolean defer) {
+ final Surface surface = getSurface(mLauncher.getDragLayer());
+ final long frameNumber = defer && surface != null ? getNextFrameNumber(surface) : -1;
+ if (defer) {
+ if (frameNumber == -1) {
+ defer = false;
+ } else {
+ mLauncher.getDragLayer().invalidate();
+ }
+ }
+
+ TransactionCompat transaction = new TransactionCompat();
+ for (RemoteAnimationTargetCompat app : mTargetSet.apps) {
+ if (!(app.isNotInRecents
+ || app.activityType == RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME)) {
+ transaction.setAlpha(app.leash, alpha);
+ if (defer) {
+ transaction.deferTransactionUntil(app.leash, surface, frameNumber);
+ }
+ }
+ }
+ transaction.setEarlyWakeup();
+ transaction.apply();
+ }
+
+ private void onSwipeAnimationComplete(boolean toAllApps, boolean isFling, Runnable callback) {
+ mLauncher.getStateManager().goToState(toAllApps ? ALL_APPS : OVERVIEW, false);
+ if (!toAllApps) {
+ DiscoveryBounce.showForOverviewIfNeeded(mLauncher);
+ mLauncher.<RecentsView>getOverviewPanel().setSwipeDownShouldLaunchApp(true);
+ }
+
+ mLauncher.getUserEventDispatcher().logStateChangeAction(
+ isFling ? Touch.FLING : Touch.SWIPE, Direction.UP,
+ ContainerType.NAVBAR, ContainerType.APP,
+ toAllApps ? ContainerType.ALLAPPS : ContainerType.TASKSWITCHER,
+ 0);
+
+ callback.run();
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/MotionEventQueue.java b/quickstep/src/com/android/quickstep/MotionEventQueue.java
index 538e23c3a..15f5aa524 100644
--- a/quickstep/src/com/android/quickstep/MotionEventQueue.java
+++ b/quickstep/src/com/android/quickstep/MotionEventQueue.java
@@ -163,7 +163,7 @@ public class MotionEventQueue {
mConsumer.updateTouchTracking(INTERACTION_QUICK_SCRUB);
break;
case ACTION_QUICK_STEP:
- mConsumer.onQuickStep(event.getX(), event.getY(), event.getEventTime());
+ mConsumer.onQuickStep(event);
break;
default:
Log.e(TAG, "Invalid virtual event: " + event.getAction());
diff --git a/quickstep/src/com/android/quickstep/MultiStateCallback.java b/quickstep/src/com/android/quickstep/MultiStateCallback.java
index 7a741764f..bda3d06aa 100644
--- a/quickstep/src/com/android/quickstep/MultiStateCallback.java
+++ b/quickstep/src/com/android/quickstep/MultiStateCallback.java
@@ -59,4 +59,8 @@ public class MultiStateCallback {
public int getState() {
return mState;
}
+
+ public boolean hasStates(int stateMask) {
+ return (mState & stateMask) == stateMask;
+ }
}
diff --git a/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java b/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java
index 4d695de90..23738fb25 100644
--- a/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java
+++ b/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java
@@ -21,9 +21,10 @@ import static android.view.MotionEvent.ACTION_MOVE;
import static android.view.MotionEvent.ACTION_POINTER_UP;
import static android.view.MotionEvent.ACTION_UP;
import static android.view.MotionEvent.INVALID_POINTER_ID;
-import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_BACK;
-import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_OVERVIEW;
-import static com.android.systemui.shared.system.NavigationBarCompat.QUICK_STEP_DRAG_SLOP_PX;
+
+import static com.android.systemui.shared.system.ActivityManagerWrapper
+ .CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
+import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
import android.annotation.TargetApi;
import android.app.ActivityManager.RunningTaskInfo;
@@ -46,16 +47,17 @@ import android.view.WindowManager;
import com.android.launcher3.MainThreadExecutor;
import com.android.launcher3.util.TraceHelper;
+import com.android.quickstep.util.RemoteAnimationTargetSet;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.AssistDataReceiver;
import com.android.systemui.shared.system.BackgroundExecutor;
+import com.android.systemui.shared.system.NavigationBarCompat;
import com.android.systemui.shared.system.NavigationBarCompat.HitTarget;
import com.android.systemui.shared.system.RecentsAnimationControllerCompat;
import com.android.systemui.shared.system.RecentsAnimationListener;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
import com.android.systemui.shared.system.WindowManagerWrapper;
-import java.util.Arrays;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -66,8 +68,6 @@ import java.util.concurrent.TimeUnit;
public class OtherActivityTouchConsumer extends ContextWrapper implements TouchConsumer {
private static final long LAUNCHER_DRAW_TIMEOUT_MS = 150;
- private static final int[] DEFERRED_HIT_TARGETS = false
- ? new int[] {HIT_TARGET_BACK, HIT_TARGET_OVERVIEW} : new int[] {HIT_TARGET_BACK};
private final RunningTaskInfo mRunningTask;
private final RecentsModel mRecentsModel;
@@ -75,12 +75,15 @@ public class OtherActivityTouchConsumer extends ContextWrapper implements TouchC
private final ActivityControlHelper mActivityControlHelper;
private final MainThreadExecutor mMainThreadExecutor;
private final Choreographer mBackgroundThreadChoreographer;
+ private final OverviewCallbacks mOverviewCallbacks;
private final boolean mIsDeferredDownTarget;
private final PointF mDownPos = new PointF();
private final PointF mLastPos = new PointF();
private int mActivePointerId = INVALID_POINTER_ID;
private boolean mPassedInitialSlop;
+ // Used for non-deferred gestures to determine when to start dragging
+ private int mQuickStepDragSlop;
private float mStartDisplacement;
private WindowTransformSwipeHandler mInteractionHandler;
private int mDisplayRotation;
@@ -93,8 +96,10 @@ public class OtherActivityTouchConsumer extends ContextWrapper implements TouchC
public OtherActivityTouchConsumer(Context base, RunningTaskInfo runningTaskInfo,
RecentsModel recentsModel, Intent homeIntent, ActivityControlHelper activityControl,
MainThreadExecutor mainThreadExecutor, Choreographer backgroundThreadChoreographer,
- @HitTarget int downHitTarget, VelocityTracker velocityTracker) {
+ @HitTarget int downHitTarget, OverviewCallbacks overviewCallbacks,
+ VelocityTracker velocityTracker) {
super(base);
+
mRunningTask = runningTaskInfo;
mRecentsModel = recentsModel;
mHomeIntent = homeIntent;
@@ -102,7 +107,8 @@ public class OtherActivityTouchConsumer extends ContextWrapper implements TouchC
mActivityControlHelper = activityControl;
mMainThreadExecutor = mainThreadExecutor;
mBackgroundThreadChoreographer = backgroundThreadChoreographer;
- mIsDeferredDownTarget = Arrays.binarySearch(DEFERRED_HIT_TARGETS, downHitTarget) >= 0;
+ mIsDeferredDownTarget = activityControl.deferStartingActivity(downHitTarget);
+ mOverviewCallbacks = overviewCallbacks;
}
@Override
@@ -122,6 +128,7 @@ public class OtherActivityTouchConsumer extends ContextWrapper implements TouchC
mDownPos.set(ev.getX(), ev.getY());
mLastPos.set(mDownPos);
mPassedInitialSlop = false;
+ mQuickStepDragSlop = NavigationBarCompat.getQuickStepDragSlopPx();
// Start the window animation on down to give more time for launcher to draw if the
// user didn't start the gesture over the back button
@@ -154,14 +161,14 @@ public class OtherActivityTouchConsumer extends ContextWrapper implements TouchC
}
mLastPos.set(ev.getX(pointerIndex), ev.getY(pointerIndex));
float displacement = getDisplacement(ev);
- if (!mPassedInitialSlop && Math.abs(displacement) > QUICK_STEP_DRAG_SLOP_PX) {
- mPassedInitialSlop = true;
- mStartDisplacement = displacement;
-
- // If we deferred starting the window animation on touch down, then
- // start tracking now
- if (mIsDeferredDownTarget) {
- startTouchTrackingForWindowAnimation(ev.getEventTime());
+ if (!mPassedInitialSlop) {
+ if (!mIsDeferredDownTarget) {
+ // Normal gesture, ensure we pass the drag slop before we start tracking
+ // the gesture
+ if (Math.abs(displacement) > mQuickStepDragSlop) {
+ mPassedInitialSlop = true;
+ mStartDisplacement = displacement;
+ }
}
}
@@ -186,6 +193,11 @@ public class OtherActivityTouchConsumer extends ContextWrapper implements TouchC
if (mInteractionHandler == null) {
return;
}
+
+ mOverviewCallbacks.closeAllWindows();
+ ActivityManagerWrapper.getInstance().closeSystemWindows(
+ CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
+
// Notify the handler that the gesture has actually started
mInteractionHandler.onGestureStarted();
}
@@ -218,7 +230,8 @@ public class OtherActivityTouchConsumer extends ContextWrapper implements TouchC
handler.initWhenReady();
TraceHelper.beginSection("RecentsController");
- Runnable startActivity = () -> mActivityControlHelper.startRecentsFromSwipe(mHomeIntent,
+ Runnable startActivity = () -> ActivityManagerWrapper.getInstance().startRecentsActivity(
+ mHomeIntent,
new AssistDataReceiver() {
@Override
public void onHandleAssistData(Bundle bundle) {
@@ -232,8 +245,9 @@ public class OtherActivityTouchConsumer extends ContextWrapper implements TouchC
Rect minimizedHomeBounds) {
if (mInteractionHandler == handler) {
TraceHelper.partitionSection("RecentsController", "Received");
- handler.onRecentsAnimationStart(controller, apps, homeContentInsets,
- minimizedHomeBounds);
+ handler.onRecentsAnimationStart(controller,
+ new RemoteAnimationTargetSet(apps, MODE_CLOSING),
+ homeContentInsets, minimizedHomeBounds);
} else {
TraceHelper.endSection("RecentsController", "Finishing no handler");
controller.finish(false /* toHome */);
@@ -247,7 +261,7 @@ public class OtherActivityTouchConsumer extends ContextWrapper implements TouchC
handler.onRecentsAnimationCanceled();
}
}
- });
+ }, null, null);
if (Looper.myLooper() != Looper.getMainLooper()) {
startActivity.run();
@@ -305,6 +319,13 @@ public class OtherActivityTouchConsumer extends ContextWrapper implements TouchC
@Override
public void updateTouchTracking(int interactionType) {
+ if (!mPassedInitialSlop && mIsDeferredDownTarget && mInteractionHandler == null) {
+ // If we deferred starting the window animation on touch down, then
+ // start tracking now
+ startTouchTrackingForWindowAnimation(SystemClock.uptimeMillis());
+ mPassedInitialSlop = true;
+ }
+
notifyGestureStarted();
if (mInteractionHandler != null) {
mInteractionHandler.updateInteractionType(interactionType);
@@ -332,7 +353,14 @@ public class OtherActivityTouchConsumer extends ContextWrapper implements TouchC
}
@Override
- public void onQuickStep(float eventX, float eventY, long eventTime) {
+ public void onQuickStep(MotionEvent ev) {
+ if (mIsDeferredDownTarget) {
+ // Deferred gesture, start the animation and gesture tracking once we pass the actual
+ // touch slop
+ startTouchTrackingForWindowAnimation(ev.getEventTime());
+ mPassedInitialSlop = true;
+ mStartDisplacement = getDisplacement(ev);
+ }
notifyGestureStarted();
}
diff --git a/quickstep/src/com/android/quickstep/OverviewCallbacks.java b/quickstep/src/com/android/quickstep/OverviewCallbacks.java
new file mode 100644
index 000000000..ac4a40b98
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/OverviewCallbacks.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep;
+
+import android.content.Context;
+
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.util.Preconditions;
+
+/**
+ * Callbacks related to overview/quicksteps.
+ */
+public class OverviewCallbacks {
+
+ private static OverviewCallbacks sInstance;
+
+ public static OverviewCallbacks get(Context context) {
+ Preconditions.assertUIThread();
+ if (sInstance == null) {
+ sInstance = Utilities.getOverrideObject(OverviewCallbacks.class,
+ context.getApplicationContext(), R.string.overview_callbacks_class);
+ }
+ return sInstance;
+ }
+
+ public void onInitOverviewTransition() { }
+
+ public void onResetOverview() { }
+
+ public void closeAllWindows() { }
+}
diff --git a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
index d76c49a09..7b2932383 100644
--- a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
+++ b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
@@ -15,18 +15,32 @@
*/
package com.android.quickstep;
+import static android.content.Intent.ACTION_PACKAGE_ADDED;
+import static android.content.Intent.ACTION_PACKAGE_CHANGED;
+import static android.content.Intent.ACTION_PACKAGE_REMOVED;
+
import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
+import static com.android.launcher3.anim.Interpolators.TOUCH_RESPONSE_INTERPOLATOR;
+import static com.android.systemui.shared.system.ActivityManagerWrapper
+ .CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
+import static com.android.systemui.shared.system.PackageManagerWrapper
+ .ACTION_PREFERRED_ACTIVITY_CHANGED;
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.AnimatorSet;
import android.animation.ValueAnimator;
import android.annotation.TargetApi;
+import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.IntentFilter;
import android.content.pm.ResolveInfo;
import android.graphics.Rect;
import android.os.Build;
+import android.os.PatternMatcher;
import android.os.SystemClock;
import android.util.Log;
import android.view.View;
@@ -34,17 +48,26 @@ import android.view.ViewConfiguration;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.BaseDraggingActivity;
+import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.MainThreadExecutor;
-import com.android.launcher3.anim.AnimatorPlaybackController;
+import com.android.launcher3.anim.AnimationSuccessListener;
+import com.android.launcher3.logging.UserEventDispatcher;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
import com.android.quickstep.ActivityControlHelper.ActivityInitListener;
+import com.android.quickstep.ActivityControlHelper.AnimationFactory;
import com.android.quickstep.ActivityControlHelper.FallbackActivityControllerHelper;
import com.android.quickstep.ActivityControlHelper.LauncherActivityControllerHelper;
import com.android.quickstep.util.ClipAnimationHelper;
-import com.android.quickstep.util.RemoteAnimationProvider;
-import com.android.quickstep.util.SysuiEventLogger;
+import com.android.quickstep.util.RemoteAnimationTargetSet;
import com.android.quickstep.views.RecentsView;
import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.shared.system.LatencyTrackerCompat;
+import com.android.systemui.shared.system.PackageManagerWrapper;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
+import com.android.systemui.shared.system.TransactionCompat;
+
+import java.util.ArrayList;
/**
* Helper class to handle various atomic commands for switching between Overview.
@@ -52,18 +75,33 @@ import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
@TargetApi(Build.VERSION_CODES.P)
public class OverviewCommandHelper {
- private static final long RECENTS_LAUNCH_DURATION = 200;
+ private static final long RECENTS_LAUNCH_DURATION = 250;
private static final String TAG = "OverviewCommandHelper";
- private static final boolean DEBUG_START_FALLBACK_ACTIVITY = false;
private final Context mContext;
private final ActivityManagerWrapper mAM;
private final RecentsModel mRecentsModel;
private final MainThreadExecutor mMainThreadExecutor;
+ private final ComponentName mMyHomeComponent;
+
+ private final BroadcastReceiver mUserPreferenceChangeReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ initOverviewTargets();
+ }
+ };
+ private final BroadcastReceiver mOtherHomeAppUpdateReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ initOverviewTargets();
+ }
+ };
+ private String mUpdateRegisteredPackage;
- public final Intent homeIntent;
- public final ComponentName launcher;
+ public Intent overviewIntent;
+ public ComponentName overviewComponent;
+ private ActivityControlHelper mActivityControlHelper;
private long mLastToggleTime;
@@ -73,22 +111,72 @@ public class OverviewCommandHelper {
mMainThreadExecutor = new MainThreadExecutor();
mRecentsModel = RecentsModel.getInstance(mContext);
- homeIntent = new Intent(Intent.ACTION_MAIN)
+ Intent myHomeIntent = new Intent(Intent.ACTION_MAIN)
.addCategory(Intent.CATEGORY_HOME)
- .setPackage(context.getPackageName())
- .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- ResolveInfo info = context.getPackageManager().resolveActivity(homeIntent, 0);
+ .setPackage(mContext.getPackageName());
+ ResolveInfo info = context.getPackageManager().resolveActivity(myHomeIntent, 0);
+ mMyHomeComponent = new ComponentName(context.getPackageName(), info.activityInfo.name);
+
+ mContext.registerReceiver(mUserPreferenceChangeReceiver,
+ new IntentFilter(ACTION_PREFERRED_ACTIVITY_CHANGED));
+ initOverviewTargets();
+ }
- if (DEBUG_START_FALLBACK_ACTIVITY) {
- launcher = new ComponentName(context, RecentsActivity.class);
- homeIntent.addCategory(Intent.CATEGORY_DEFAULT)
- .removeCategory(Intent.CATEGORY_HOME);
+ private void initOverviewTargets() {
+ ComponentName defaultHome = PackageManagerWrapper.getInstance()
+ .getHomeActivities(new ArrayList<>());
+
+ final String overviewIntentCategory;
+ if (defaultHome == null || mMyHomeComponent.equals(defaultHome)) {
+ // User default home is same as out home app. Use Overview integrated in Launcher.
+ overviewComponent = mMyHomeComponent;
+ mActivityControlHelper = new LauncherActivityControllerHelper();
+ overviewIntentCategory = Intent.CATEGORY_HOME;
+
+ if (mUpdateRegisteredPackage != null) {
+ // Remove any update listener as we don't care about other packages.
+ mContext.unregisterReceiver(mOtherHomeAppUpdateReceiver);
+ mUpdateRegisteredPackage = null;
+ }
} else {
- launcher = new ComponentName(context.getPackageName(), info.activityInfo.name);
+ // The default home app is a different launcher. Use the fallback Overview instead.
+ overviewComponent = new ComponentName(mContext, RecentsActivity.class);
+ mActivityControlHelper = new FallbackActivityControllerHelper(defaultHome);
+ overviewIntentCategory = Intent.CATEGORY_DEFAULT;
+
+ // User's default home app can change as a result of package updates of this app (such
+ // as uninstalling the app or removing the "Launcher" feature in an update).
+ // Listen for package updates of this app (and remove any previously attached
+ // package listener).
+ if (!defaultHome.getPackageName().equals(mUpdateRegisteredPackage)) {
+ if (mUpdateRegisteredPackage != null) {
+ mContext.unregisterReceiver(mOtherHomeAppUpdateReceiver);
+ }
+
+ mUpdateRegisteredPackage = defaultHome.getPackageName();
+ IntentFilter updateReceiver = new IntentFilter(ACTION_PACKAGE_ADDED);
+ updateReceiver.addAction(ACTION_PACKAGE_CHANGED);
+ updateReceiver.addAction(ACTION_PACKAGE_REMOVED);
+ updateReceiver.addDataScheme("package");
+ updateReceiver.addDataSchemeSpecificPart(mUpdateRegisteredPackage,
+ PatternMatcher.PATTERN_LITERAL);
+ mContext.registerReceiver(mOtherHomeAppUpdateReceiver, updateReceiver);
+ }
}
- // Clear the packageName as system can fail to dedupe it b/64108432
- homeIntent.setComponent(launcher).setPackage(null);
+ overviewIntent = new Intent(Intent.ACTION_MAIN)
+ .addCategory(overviewIntentCategory)
+ .setComponent(overviewComponent)
+ .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ }
+
+ public void onDestroy() {
+ mContext.unregisterReceiver(mUserPreferenceChangeReceiver);
+
+ if (mUpdateRegisteredPackage != null) {
+ mContext.unregisterReceiver(mOtherHomeAppUpdateReceiver);
+ mUpdateRegisteredPackage = null;
+ }
}
public void onOverviewToggle() {
@@ -97,7 +185,7 @@ public class OverviewCommandHelper {
return;
}
- mAM.closeSystemWindows("recentapps");
+ mAM.closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
mMainThreadExecutor.execute(new RecentsActivityCommand<>());
}
@@ -105,25 +193,26 @@ public class OverviewCommandHelper {
mMainThreadExecutor.execute(new ShowRecentsCommand());
}
+ public void onTip(int actionType, int viewType) {
+ mMainThreadExecutor.execute(new Runnable() {
+ @Override
+ public void run() {
+ UserEventDispatcher.newInstance(mContext,
+ new InvariantDeviceProfile(mContext).getDeviceProfile(mContext))
+ .logActionTip(actionType, viewType);
+ }
+ });
+ }
+
public ActivityControlHelper getActivityControlHelper() {
- if (DEBUG_START_FALLBACK_ACTIVITY) {
- return new FallbackActivityControllerHelper();
- } else {
- return new LauncherActivityControllerHelper();
- }
+ return mActivityControlHelper;
}
private class ShowRecentsCommand extends RecentsActivityCommand {
@Override
protected boolean handleCommand(long elapsedTime) {
- RecentsView recents = mHelper.getVisibleRecentsView();
- if (recents != null) {
- recents.snapToTaskAfterNext();
- return true;
- } else {
- return false;
- }
+ return mHelper.getVisibleRecentsView() != null;
}
}
@@ -135,6 +224,9 @@ public class OverviewCommandHelper {
private ActivityInitListener mListener;
private T mActivity;
+ private RecentsView mRecentsView;
+ private final long mToggleClickedTime = SystemClock.uptimeMillis();
+ private boolean mUserEventLogged;
public RecentsActivityCommand() {
mHelper = getActivityControlHelper();
@@ -152,12 +244,9 @@ public class OverviewCommandHelper {
if (!handleCommand(elapsedTime)) {
// Start overview
- if (mHelper.switchToRecentsIfVisible()) {
- SysuiEventLogger.writeDummyRecentsTransition(0);
- // Do nothing
- } else {
+ if (!mHelper.switchToRecentsIfVisible(true)) {
mListener = mHelper.createActivityInitListener(this::onActivityReady);
- mListener.registerAndStartActivity(homeIntent, this::createWindowAnimation,
+ mListener.registerAndStartActivity(overviewIntent, this::createWindowAnimation,
mContext, mMainThreadExecutor.getHandler(), RECENTS_LAUNCH_DURATION);
}
}
@@ -183,41 +272,60 @@ public class OverviewCommandHelper {
private boolean onActivityReady(T activity, Boolean wasVisible) {
activity.<RecentsView>getOverviewPanel().setCurrentTask(mRunningTaskId);
AbstractFloatingView.closeAllOpenViews(activity, wasVisible);
- mHelper.prepareRecentsUI(activity, wasVisible);
+ AnimationFactory factory = mHelper.prepareRecentsUI(activity, wasVisible,
+ (controller) -> {
+ controller.dispatchOnStart();
+ ValueAnimator anim = controller.getAnimationPlayer()
+ .setDuration(RECENTS_LAUNCH_DURATION);
+ anim.setInterpolator(FAST_OUT_SLOW_IN);
+ anim.start();
+ });
+ factory.onRemoteAnimationReceived(null);
if (wasVisible) {
- AnimatorPlaybackController controller =
- mHelper.createControllerForVisibleActivity(activity);
- controller.dispatchOnStart();
- ValueAnimator anim =
- controller.getAnimationPlayer().setDuration(RECENTS_LAUNCH_DURATION);
- anim.setInterpolator(FAST_OUT_SLOW_IN);
- anim.start();
+ factory.createActivityController(RECENTS_LAUNCH_DURATION);
}
mActivity = activity;
+ mRecentsView = mActivity.getOverviewPanel();
+ mRecentsView.setRunningTaskIconScaledDown(true /* isScaledDown */, false /* animate */);
+ if (!mUserEventLogged) {
+ activity.getUserEventDispatcher().logActionCommand(Action.Command.RECENTS_BUTTON,
+ mHelper.getContainerType(), ContainerType.TASKSWITCHER);
+ mUserEventLogged = true;
+ }
return false;
}
private AnimatorSet createWindowAnimation(RemoteAnimationTargetCompat[] targetCompats) {
+ if (LatencyTrackerCompat.isEnabled(mContext)) {
+ LatencyTrackerCompat.logToggleRecents(
+ (int) (SystemClock.uptimeMillis() - mToggleClickedTime));
+ }
+
if (mListener != null) {
mListener.unregister();
}
- RemoteAnimationProvider.showOpeningTarget(targetCompats);
AnimatorSet anim = new AnimatorSet();
+ anim.addListener(new AnimationSuccessListener() {
+ @Override
+ public void onAnimationSuccess(Animator animator) {
+ if (mRecentsView != null) {
+ mRecentsView.setRunningTaskIconScaledDown(false /* isScaledDown */,
+ true /* animate */);
+ }
+ }
+ });
if (mActivity == null) {
Log.e(TAG, "Animation created, before activity");
anim.play(ValueAnimator.ofInt(0, 1).setDuration(100));
return anim;
}
- RemoteAnimationTargetCompat closingTarget = null;
+ RemoteAnimationTargetSet targetSet =
+ new RemoteAnimationTargetSet(targetCompats, MODE_CLOSING);
+
// Use the top closing app to determine the insets for the animation
- for (RemoteAnimationTargetCompat target : targetCompats) {
- if (target.mode == MODE_CLOSING) {
- closingTarget = target;
- break;
- }
- }
- if (closingTarget == null) {
+ RemoteAnimationTargetCompat runningTaskTarget = targetSet.findTask(mRunningTaskId);
+ if (runningTaskTarget == null) {
Log.e(TAG, "No closing app");
anim.play(ValueAnimator.ofInt(0, 1).setDuration(100));
return anim;
@@ -232,19 +340,33 @@ public class OverviewCommandHelper {
rootView.getLocationOnScreen(loc);
Rect homeBounds = new Rect(loc[0], loc[1],
loc[0] + rootView.getWidth(), loc[1] + rootView.getHeight());
- clipHelper.updateSource(homeBounds, closingTarget);
+ clipHelper.updateSource(homeBounds, runningTaskTarget);
Rect targetRect = new Rect();
mHelper.getSwipeUpDestinationAndLength(
mActivity.getDeviceProfile(), mActivity, targetRect);
clipHelper.updateTargetRect(targetRect);
-
+ clipHelper.prepareAnimation(false /* isOpening */);
ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, 1);
- valueAnimator.setDuration(RECENTS_LAUNCH_DURATION).setInterpolator(FAST_OUT_SLOW_IN);
- valueAnimator.addUpdateListener((v) -> {
- clipHelper.applyTransform(targetCompats, (float) v.getAnimatedValue());
- });
+ valueAnimator.setDuration(RECENTS_LAUNCH_DURATION);
+ valueAnimator.setInterpolator(TOUCH_RESPONSE_INTERPOLATOR);
+ valueAnimator.addUpdateListener((v) ->
+ clipHelper.applyTransform(targetSet, (float) v.getAnimatedValue()));
+
+ if (targetSet.isAnimatingHome()) {
+ // If we are animating home, fade in the opening targets
+ RemoteAnimationTargetSet openingSet =
+ new RemoteAnimationTargetSet(targetCompats, MODE_OPENING);
+
+ TransactionCompat transaction = new TransactionCompat();
+ valueAnimator.addUpdateListener((v) -> {
+ for (RemoteAnimationTargetCompat app : openingSet.apps) {
+ transaction.setAlpha(app.leash, (float) v.getAnimatedValue());
+ }
+ transaction.apply();
+ });
+ }
anim.play(valueAnimator);
return anim;
}
diff --git a/quickstep/src/com/android/quickstep/OverviewInteractionState.java b/quickstep/src/com/android/quickstep/OverviewInteractionState.java
index 22b175779..d60574676 100644
--- a/quickstep/src/com/android/quickstep/OverviewInteractionState.java
+++ b/quickstep/src/com/android/quickstep/OverviewInteractionState.java
@@ -15,23 +15,26 @@
*/
package com.android.quickstep;
-import static com.android.launcher3.Utilities.getPrefs;
import static com.android.systemui.shared.system.NavigationBarCompat.FLAG_DISABLE_QUICK_SCRUB;
import static com.android.systemui.shared.system.NavigationBarCompat.FLAG_DISABLE_SWIPE_UP;
-import static com.android.systemui.shared.system.NavigationBarCompat.FLAG_HIDE_BACK_BUTTON;
import static com.android.systemui.shared.system.NavigationBarCompat.FLAG_SHOW_OVERVIEW_BUTTON;
+import static com.android.systemui.shared.system.SettingsCompat.SWIPE_UP_SETTING_NAME;
+import android.content.ContentResolver;
import android.content.Context;
-import android.content.SharedPreferences;
-import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
+import android.content.res.Resources;
+import android.database.ContentObserver;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
+import android.provider.Settings;
import android.support.annotation.WorkerThread;
import android.util.Log;
import com.android.launcher3.MainThreadExecutor;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.allapps.DiscoveryBounce;
import com.android.launcher3.util.UiThreadHelper;
import com.android.systemui.shared.recents.ISystemUiProxy;
@@ -42,15 +45,20 @@ import java.util.concurrent.ExecutionException;
*
* - FLAG_DISABLE_QUICK_SCRUB
* - FLAG_DISABLE_SWIPE_UP
- * - FLAG_HIDE_BACK_BUTTON
* - FLAG_SHOW_OVERVIEW_BUTTON
*
* @see com.android.systemui.shared.system.NavigationBarCompat.InteractionType and associated flags.
*/
-public class OverviewInteractionState implements OnSharedPreferenceChangeListener {
+public class OverviewInteractionState {
private static final String TAG = "OverviewFlags";
+ private static final String HAS_ENABLED_QUICKSTEP_ONCE = "launcher.has_enabled_quickstep_once";
+ private static final String SWIPE_UP_SETTING_AVAILABLE_RES_NAME =
+ "config_swipe_up_gesture_setting_available";
+ private static final String SWIPE_UP_ENABLED_DEFAULT_RES_NAME =
+ "config_swipe_up_gesture_default";
+
// We do not need any synchronization for this variable as its only written on UI thread.
private static OverviewInteractionState INSTANCE;
@@ -70,42 +78,48 @@ public class OverviewInteractionState implements OnSharedPreferenceChangeListene
return INSTANCE;
}
- public static final String KEY_SWIPE_UP_ENABLED = "pref_enable_quickstep";
-
private static final int MSG_SET_PROXY = 200;
- private static final int MSG_SET_BACK_BUTTON_VISIBLE = 201;
+ private static final int MSG_SET_BACK_BUTTON_ALPHA = 201;
private static final int MSG_SET_SWIPE_UP_ENABLED = 202;
+ private final SwipeUpGestureEnabledSettingObserver mSwipeUpSettingObserver;
+
+ private final Context mContext;
private final Handler mUiHandler;
private final Handler mBgHandler;
// These are updated on the background thread
private ISystemUiProxy mISystemUiProxy;
- private boolean mBackButtonVisible = true;
private boolean mSwipeUpEnabled = true;
+ private Runnable mOnSwipeUpSettingChangedListener;
+
private OverviewInteractionState(Context context) {
+ mContext = context;
+
+ // Data posted to the uihandler will be sent to the bghandler. Data is sent to uihandler
+ // because of its high send frequency and data may be very different than the previous value
+ // For example, send back alpha on uihandler to avoid flickering when setting its visibility
mUiHandler = new Handler(this::handleUiMessage);
mBgHandler = new Handler(UiThreadHelper.getBackgroundLooper(), this::handleBgMessage);
- SharedPreferences prefs = getPrefs(context);
- prefs.registerOnSharedPreferenceChangeListener(this);
- onSharedPreferenceChanged(prefs, KEY_SWIPE_UP_ENABLED);
+ if (getSystemBooleanRes(SWIPE_UP_SETTING_AVAILABLE_RES_NAME)) {
+ mSwipeUpSettingObserver = new SwipeUpGestureEnabledSettingObserver(mUiHandler,
+ context.getContentResolver());
+ mSwipeUpSettingObserver.register();
+ } else {
+ mSwipeUpSettingObserver = null;
+ mSwipeUpEnabled = getSystemBooleanRes(SWIPE_UP_ENABLED_DEFAULT_RES_NAME);
+ }
}
- @Override
- public void onSharedPreferenceChanged(SharedPreferences prefs, String s) {
- if (KEY_SWIPE_UP_ENABLED.equals(s)) {
- mUiHandler.removeMessages(MSG_SET_SWIPE_UP_ENABLED);
- boolean swipeUpEnabled = prefs.getBoolean(s, true);
- mUiHandler.obtainMessage(MSG_SET_SWIPE_UP_ENABLED,
- swipeUpEnabled ? 1 : 0, 0).sendToTarget();
- }
+ public boolean isSwipeUpGestureEnabled() {
+ return mSwipeUpEnabled;
}
- public void setBackButtonVisible(boolean visible) {
- mUiHandler.removeMessages(MSG_SET_BACK_BUTTON_VISIBLE);
- mUiHandler.obtainMessage(MSG_SET_BACK_BUTTON_VISIBLE, visible ? 1 : 0, 0)
+ public void setBackButtonAlpha(float alpha, boolean animate) {
+ mUiHandler.removeMessages(MSG_SET_BACK_BUTTON_ALPHA);
+ mUiHandler.obtainMessage(MSG_SET_BACK_BUTTON_ALPHA, animate ? 1 : 0, 0, alpha)
.sendToTarget();
}
@@ -114,7 +128,7 @@ public class OverviewInteractionState implements OnSharedPreferenceChangeListene
}
private boolean handleUiMessage(Message msg) {
- mBgHandler.obtainMessage(msg.what, msg.arg1, msg.arg2).sendToTarget();
+ mBgHandler.obtainMessage(msg.what, msg.arg1, msg.arg2, msg.obj).sendToTarget();
return true;
}
@@ -123,27 +137,34 @@ public class OverviewInteractionState implements OnSharedPreferenceChangeListene
case MSG_SET_PROXY:
mISystemUiProxy = (ISystemUiProxy) msg.obj;
break;
- case MSG_SET_BACK_BUTTON_VISIBLE:
- mBackButtonVisible = msg.arg1 != 0;
- break;
+ case MSG_SET_BACK_BUTTON_ALPHA:
+ applyBackButtonAlpha((float) msg.obj, msg.arg1 == 1);
+ return true;
case MSG_SET_SWIPE_UP_ENABLED:
mSwipeUpEnabled = msg.arg1 != 0;
+ resetHomeBounceSeenOnQuickstepEnabledFirstTime();
+
+ if (mOnSwipeUpSettingChangedListener != null) {
+ mOnSwipeUpSettingChangedListener.run();
+ }
break;
}
applyFlags();
return true;
}
+ public void setOnSwipeUpSettingChangedListener(Runnable listener) {
+ mOnSwipeUpSettingChangedListener = listener;
+ }
+
@WorkerThread
private void applyFlags() {
if (mISystemUiProxy == null) {
return;
}
- int flags;
- if (mSwipeUpEnabled) {
- flags = mBackButtonVisible ? 0 : FLAG_HIDE_BACK_BUTTON;
- } else {
+ int flags = 0;
+ if (!mSwipeUpEnabled) {
flags = FLAG_DISABLE_SWIPE_UP | FLAG_DISABLE_QUICK_SCRUB | FLAG_SHOW_OVERVIEW_BUTTON;
}
try {
@@ -152,4 +173,69 @@ public class OverviewInteractionState implements OnSharedPreferenceChangeListene
Log.w(TAG, "Unable to update overview interaction flags", e);
}
}
+
+ @WorkerThread
+ private void applyBackButtonAlpha(float alpha, boolean animate) {
+ if (mISystemUiProxy == null) {
+ return;
+ }
+ try {
+ mISystemUiProxy.setBackButtonAlpha(alpha, animate);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Unable to update overview back button alpha", e);
+ }
+ }
+
+ private class SwipeUpGestureEnabledSettingObserver extends ContentObserver {
+ private Handler mHandler;
+ private ContentResolver mResolver;
+ private final int defaultValue;
+
+ SwipeUpGestureEnabledSettingObserver(Handler handler, ContentResolver resolver) {
+ super(handler);
+ mHandler = handler;
+ mResolver = resolver;
+ defaultValue = getSystemBooleanRes(SWIPE_UP_ENABLED_DEFAULT_RES_NAME) ? 1 : 0;
+ }
+
+ public void register() {
+ mResolver.registerContentObserver(Settings.Secure.getUriFor(SWIPE_UP_SETTING_NAME),
+ false, this);
+ mSwipeUpEnabled = getValue();
+ resetHomeBounceSeenOnQuickstepEnabledFirstTime();
+ }
+
+ @Override
+ public void onChange(boolean selfChange) {
+ super.onChange(selfChange);
+ mHandler.removeMessages(MSG_SET_SWIPE_UP_ENABLED);
+ mHandler.obtainMessage(MSG_SET_SWIPE_UP_ENABLED, getValue() ? 1 : 0, 0).sendToTarget();
+ }
+
+ private boolean getValue() {
+ return Settings.Secure.getInt(mResolver, SWIPE_UP_SETTING_NAME, defaultValue) == 1;
+ }
+ }
+
+ private boolean getSystemBooleanRes(String resName) {
+ Resources res = Resources.getSystem();
+ int resId = res.getIdentifier(resName, "bool", "android");
+
+ if (resId != 0) {
+ return res.getBoolean(resId);
+ } else {
+ Log.e(TAG, "Failed to get system resource ID. Incompatible framework version?");
+ return false;
+ }
+ }
+
+ private void resetHomeBounceSeenOnQuickstepEnabledFirstTime() {
+ if (mSwipeUpEnabled && !Utilities.getPrefs(mContext).getBoolean(
+ HAS_ENABLED_QUICKSTEP_ONCE, true)) {
+ Utilities.getPrefs(mContext).edit()
+ .putBoolean(HAS_ENABLED_QUICKSTEP_ONCE, true)
+ .putBoolean(DiscoveryBounce.HOME_BOUNCE_SEEN, false)
+ .apply();
+ }
+ }
}
diff --git a/quickstep/src/com/android/quickstep/QuickScrubController.java b/quickstep/src/com/android/quickstep/QuickScrubController.java
index fd089b592..abb479dea 100644
--- a/quickstep/src/com/android/quickstep/QuickScrubController.java
+++ b/quickstep/src/com/android/quickstep/QuickScrubController.java
@@ -16,15 +16,18 @@
package com.android.quickstep;
+import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
+
+import android.util.Log;
import android.view.HapticFeedbackConstants;
+import android.view.animation.Interpolator;
import com.android.launcher3.Alarm;
import com.android.launcher3.BaseActivity;
import com.android.launcher3.OnAlarmListener;
import com.android.launcher3.Utilities;
+import com.android.launcher3.userevent.nano.LauncherLogProto;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
-import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
-import com.android.launcher3.userevent.nano.LauncherLogProto.ControlType;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
@@ -36,15 +39,20 @@ import com.android.quickstep.views.TaskView;
*/
public class QuickScrubController implements OnAlarmListener {
- public static final int QUICK_SCRUB_START_DURATION = 210;
+ public static final int QUICK_SCRUB_FROM_APP_START_DURATION = 240;
+ public static final int QUICK_SCRUB_FROM_HOME_START_DURATION = 150;
+ // We want the translation y to finish faster than the rest of the animation.
+ public static final float QUICK_SCRUB_TRANSLATION_Y_FACTOR = 5f / 6;
+ public static final Interpolator QUICK_SCRUB_START_INTERPOLATOR = FAST_OUT_SLOW_IN;
/**
* Snap to a new page when crossing these thresholds. The first and last auto-advance.
*/
private static final float[] QUICK_SCRUB_THRESHOLDS = new float[] {
- 0.05f, 0.35f, 0.65f, 0.95f
+ 0.04f, 0.27f, 0.50f, 0.73f, 0.96f
};
+ private static final String TAG = "QuickScrubController";
private static final boolean ENABLE_AUTO_ADVANCE = true;
private static final long AUTO_ADVANCE_DELAY = 500;
private static final int QUICKSCRUB_SNAP_DURATION_PER_PAGE = 325;
@@ -55,9 +63,12 @@ public class QuickScrubController implements OnAlarmListener {
private final BaseActivity mActivity;
private boolean mInQuickScrub;
+ private boolean mWaitingForTaskLaunch;
private int mQuickScrubSection;
private boolean mStartedFromHome;
private boolean mFinishedTransitionToQuickScrub;
+ private Runnable mOnFinishedTransitionToQuickScrubRunnable;
+ private ActivityControlHelper mActivityControlHelper;
public QuickScrubController(BaseActivity activity, RecentsView recentsView) {
mActivity = activity;
@@ -68,11 +79,13 @@ public class QuickScrubController implements OnAlarmListener {
}
}
- public void onQuickScrubStart(boolean startingFromHome) {
+ public void onQuickScrubStart(boolean startingFromHome, ActivityControlHelper controlHelper) {
+ prepareQuickScrub(TAG);
mInQuickScrub = true;
mStartedFromHome = startingFromHome;
mQuickScrubSection = 0;
mFinishedTransitionToQuickScrub = false;
+ mActivityControlHelper = controlHelper;
snapToNextTaskIfAvailable();
mActivity.getUserEventDispatcher().resetActionDurationMillis();
@@ -85,26 +98,61 @@ public class QuickScrubController implements OnAlarmListener {
}
int page = mRecentsView.getNextPage();
Runnable launchTaskRunnable = () -> {
- TaskView taskView = ((TaskView) mRecentsView.getPageAt(page));
+ TaskView taskView = mRecentsView.getPageAt(page);
if (taskView != null) {
- taskView.launchTask(true);
+ mWaitingForTaskLaunch = true;
+ taskView.launchTask(true, (result) -> {
+ if (!result) {
+ taskView.notifyTaskLaunchFailed(TAG);
+ breakOutOfQuickScrub();
+ } else {
+ mActivity.getUserEventDispatcher().logTaskLaunchOrDismiss(Touch.DRAGDROP,
+ LauncherLogProto.Action.Direction.NONE, page,
+ TaskUtils.getComponentKeyForTask(taskView.getTask().key));
+ }
+ mWaitingForTaskLaunch = false;
+ }, taskView.getHandler());
} else {
- // Break out of quick scrub so user can interact with launcher.
- mActivity.onBackPressed();
+ breakOutOfQuickScrub();
}
+ mActivityControlHelper = null;
};
int snapDuration = Math.abs(page - mRecentsView.getPageNearestToCenterOfScreen())
* QUICKSCRUB_END_SNAP_DURATION_PER_PAGE;
- if (mRecentsView.snapToPage(page, snapDuration)) {
+ if (mRecentsView.getChildCount() > 0 && mRecentsView.snapToPage(page, snapDuration)) {
// Settle on the page then launch it
mRecentsView.setNextPageSwitchRunnable(launchTaskRunnable);
} else {
// No page move needed, just launch it
- launchTaskRunnable.run();
+ if (mFinishedTransitionToQuickScrub) {
+ launchTaskRunnable.run();
+ } else {
+ mOnFinishedTransitionToQuickScrubRunnable = launchTaskRunnable;
+ }
+ }
+ }
+
+ /**
+ * Initializes the UI for quick scrub, returns true if success.
+ */
+ public boolean prepareQuickScrub(String tag) {
+ if (mWaitingForTaskLaunch || mInQuickScrub) {
+ Log.d(tag, "Waiting for last scrub to finish, will skip this interaction");
+ return false;
+ }
+ mOnFinishedTransitionToQuickScrubRunnable = null;
+ mRecentsView.setNextPageSwitchRunnable(null);
+ return true;
+ }
+
+ /**
+ * Attempts to go to normal overview or back to home, so UI doesn't prevent user interaction.
+ */
+ private void breakOutOfQuickScrub() {
+ if (mRecentsView.getChildCount() == 0 || mActivityControlHelper == null
+ || !mActivityControlHelper.switchToRecentsIfVisible(false)) {
+ mActivity.onBackPressed();
}
- mActivity.getUserEventDispatcher().logActionOnControl(Touch.DRAGDROP,
- ControlType.QUICK_SCRUB_BUTTON, null, mStartedFromHome ?
- ContainerType.WORKSPACE : ContainerType.APP);
}
public void onQuickScrubProgress(float progress) {
@@ -135,20 +183,39 @@ public class QuickScrubController implements OnAlarmListener {
public void onFinishedTransitionToQuickScrub() {
mFinishedTransitionToQuickScrub = true;
+ Runnable action = mOnFinishedTransitionToQuickScrubRunnable;
+ // Clear the runnable before executing it, to prevent potential recursion.
+ mOnFinishedTransitionToQuickScrubRunnable = null;
+ if (action != null) {
+ action.run();
+ }
}
public void snapToNextTaskIfAvailable() {
- if (!mStartedFromHome && mInQuickScrub && mRecentsView.getChildCount() > 0) {
- mRecentsView.snapToPage(mRecentsView.getNextPage() + 1, QUICK_SCRUB_START_DURATION);
+ if (mInQuickScrub && mRecentsView.getChildCount() > 0) {
+ int duration = mStartedFromHome ? QUICK_SCRUB_FROM_HOME_START_DURATION
+ : QUICK_SCRUB_FROM_APP_START_DURATION;
+ int pageToGoTo = mStartedFromHome ? 0 : mRecentsView.getNextPage() + 1;
+ goToPageWithHaptic(pageToGoTo, duration, true /* forceHaptic */,
+ QUICK_SCRUB_START_INTERPOLATOR);
}
}
private void goToPageWithHaptic(int pageToGoTo) {
+ goToPageWithHaptic(pageToGoTo, -1 /* overrideDuration */, false /* forceHaptic */, null);
+ }
+
+ private void goToPageWithHaptic(int pageToGoTo, int overrideDuration, boolean forceHaptic,
+ Interpolator interpolator) {
pageToGoTo = Utilities.boundToRange(pageToGoTo, 0, mRecentsView.getPageCount() - 1);
- if (pageToGoTo != mRecentsView.getNextPage()) {
- int duration = Math.abs(pageToGoTo - mRecentsView.getNextPage())
+ boolean snappingToPage = pageToGoTo != mRecentsView.getNextPage();
+ if (snappingToPage) {
+ int duration = overrideDuration > -1 ? overrideDuration
+ : Math.abs(pageToGoTo - mRecentsView.getNextPage())
* QUICKSCRUB_SNAP_DURATION_PER_PAGE;
- mRecentsView.snapToPage(pageToGoTo, duration);
+ mRecentsView.snapToPage(pageToGoTo, duration, interpolator);
+ }
+ if (snappingToPage || forceHaptic) {
mRecentsView.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY,
HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
}
@@ -157,6 +224,12 @@ public class QuickScrubController implements OnAlarmListener {
@Override
public void onAlarm(Alarm alarm) {
int currPage = mRecentsView.getNextPage();
+ boolean recentsVisible = mActivityControlHelper != null
+ && mActivityControlHelper.getVisibleRecentsView() != null;
+ if (!recentsVisible) {
+ Log.w(TAG, "Failed to auto advance; recents not visible");
+ return;
+ }
if (mQuickScrubSection == QUICK_SCRUB_THRESHOLDS.length
&& currPage < mRecentsView.getPageCount() - 1) {
goToPageWithHaptic(currPage + 1);
diff --git a/quickstep/src/com/android/quickstep/RecentsActivity.java b/quickstep/src/com/android/quickstep/RecentsActivity.java
index cf60fdff6..b472d611a 100644
--- a/quickstep/src/com/android/quickstep/RecentsActivity.java
+++ b/quickstep/src/com/android/quickstep/RecentsActivity.java
@@ -15,15 +15,35 @@
*/
package com.android.quickstep;
+import static android.content.pm.ActivityInfo.CONFIG_ORIENTATION;
+import static android.content.pm.ActivityInfo.CONFIG_SCREEN_SIZE;
+
+import static com.android.launcher3.LauncherAppTransitionManagerImpl.RECENTS_LAUNCH_DURATION;
+import static com.android.launcher3.LauncherAppTransitionManagerImpl.STATUS_BAR_TRANSITION_DURATION;
+import static com.android.quickstep.TaskUtils.getRecentsWindowAnimator;
+import static com.android.quickstep.TaskUtils.taskIsATargetWithMode;
+import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
import android.app.ActivityOptions;
+import android.content.Intent;
+import android.content.res.Configuration;
import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
import android.view.View;
+import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.BaseDraggingActivity;
+import com.android.launcher3.DeviceProfile;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.ItemInfo;
+import com.android.launcher3.LauncherAnimationRunner;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.R;
+import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.badge.BadgeInfo;
import com.android.launcher3.uioverrides.UiFactory;
import com.android.launcher3.util.SystemUiController;
@@ -31,29 +51,40 @@ import com.android.launcher3.util.Themes;
import com.android.launcher3.views.BaseDragLayer;
import com.android.quickstep.fallback.FallbackRecentsView;
import com.android.quickstep.fallback.RecentsRootView;
+import com.android.quickstep.util.ClipAnimationHelper;
+import com.android.quickstep.views.RecentsViewContainer;
+import com.android.quickstep.views.TaskView;
+import com.android.systemui.shared.system.ActivityOptionsCompat;
+import com.android.systemui.shared.system.RemoteAnimationAdapterCompat;
+import com.android.systemui.shared.system.RemoteAnimationRunnerCompat;
+import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
/**
* A simple activity to show the recently launched tasks
*/
public class RecentsActivity extends BaseDraggingActivity {
+ private Handler mUiHandler = new Handler(Looper.getMainLooper());
private RecentsRootView mRecentsRootView;
private FallbackRecentsView mFallbackRecentsView;
+ private RecentsViewContainer mOverviewPanelContainer;
+
+ private Configuration mOldConfig;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- // In case we are reusing IDP, create a copy so that we dont conflict with Launcher
- // activity.
- LauncherAppState appState = LauncherAppState.getInstanceNoCreate();
- setDeviceProfile(appState != null
- ? appState.getInvariantDeviceProfile().getDeviceProfile(this).copy(this)
- : new InvariantDeviceProfile(this).getDeviceProfile(this));
+ mOldConfig = new Configuration(getResources().getConfiguration());
+ initDeviceProfile();
setContentView(R.layout.fallback_recents_activity);
mRecentsRootView = findViewById(R.id.drag_layer);
mFallbackRecentsView = findViewById(R.id.overview_panel);
+ mOverviewPanelContainer = findViewById(R.id.overview_panel_container);
mRecentsRootView.setup();
@@ -63,6 +94,64 @@ public class RecentsActivity extends BaseDraggingActivity {
}
@Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ int diff = newConfig.diff(mOldConfig);
+ if ((diff & (CONFIG_ORIENTATION | CONFIG_SCREEN_SIZE)) != 0) {
+ onHandleConfigChanged();
+ }
+ mOldConfig.setTo(newConfig);
+ super.onConfigurationChanged(newConfig);
+ }
+
+ @Override
+ public void onMultiWindowModeChanged(boolean isInMultiWindowMode, Configuration newConfig) {
+ onHandleConfigChanged();
+ super.onMultiWindowModeChanged(isInMultiWindowMode, newConfig);
+ }
+
+ public void onRootViewSizeChanged() {
+ if (isInMultiWindowModeCompat()) {
+ onHandleConfigChanged();
+ }
+ }
+
+ private void onHandleConfigChanged() {
+ mUserEventDispatcher = null;
+ initDeviceProfile();
+
+ AbstractFloatingView.closeOpenViews(this, true,
+ AbstractFloatingView.TYPE_ALL & ~AbstractFloatingView.TYPE_REBIND_SAFE);
+ dispatchDeviceProfileChanged();
+
+ mRecentsRootView.setup();
+ reapplyUi();
+ }
+
+ @Override
+ protected void reapplyUi() {
+ mRecentsRootView.dispatchInsets();
+ }
+
+ private void initDeviceProfile() {
+ // In case we are reusing IDP, create a copy so that we dont conflict with Launcher
+ // activity.
+ LauncherAppState appState = LauncherAppState.getInstanceNoCreate();
+ if (isInMultiWindowModeCompat()) {
+ InvariantDeviceProfile idp = appState == null
+ ? new InvariantDeviceProfile(this) : appState.getInvariantDeviceProfile();
+ DeviceProfile dp = idp.getDeviceProfile(this);
+ mDeviceProfile = mRecentsRootView == null ? dp.copy(this)
+ : dp.getMultiWindowProfile(this, mRecentsRootView.getLastKnownSize());
+ } else {
+ // If we are reusing the Invariant device profile, make a copy.
+ mDeviceProfile = appState == null
+ ? new InvariantDeviceProfile(this).getDeviceProfile(this)
+ : appState.getInvariantDeviceProfile().getDeviceProfile(this).copy(this);
+ }
+ onDeviceProfileInitiated();
+ }
+
+ @Override
public BaseDragLayer getDragLayer() {
return mRecentsRootView;
}
@@ -77,14 +166,62 @@ public class RecentsActivity extends BaseDraggingActivity {
return (T) mFallbackRecentsView;
}
+ public RecentsViewContainer getOverviewPanelContainer() {
+ return mOverviewPanelContainer;
+ }
+
@Override
public BadgeInfo getBadgeInfoForItem(ItemInfo info) {
return null;
}
@Override
- public ActivityOptions getActivityLaunchOptions(View v, boolean useDefaultLaunchOptions) {
- return null;
+ public ActivityOptions getActivityLaunchOptions(final View v, boolean useDefaultLaunchOptions) {
+ if (useDefaultLaunchOptions || !(v instanceof TaskView)) {
+ return null;
+ }
+
+ final TaskView taskView = (TaskView) v;
+ RemoteAnimationRunnerCompat runner = new LauncherAnimationRunner(mUiHandler,
+ true /* startAtFrontOfQueue */) {
+
+ @Override
+ public void onCreateAnimation(RemoteAnimationTargetCompat[] targetCompats,
+ AnimationResult result) {
+ result.setAnimation(composeRecentsLaunchAnimator(taskView, targetCompats));
+ }
+ };
+ return ActivityOptionsCompat.makeRemoteAnimation(new RemoteAnimationAdapterCompat(
+ runner, RECENTS_LAUNCH_DURATION,
+ RECENTS_LAUNCH_DURATION - STATUS_BAR_TRANSITION_DURATION));
+ }
+
+ /**
+ * Composes the animations for a launch from the recents list if possible.
+ */
+ private AnimatorSet composeRecentsLaunchAnimator(TaskView taskView,
+ RemoteAnimationTargetCompat[] targets) {
+ AnimatorSet target = new AnimatorSet();
+ boolean activityClosing = taskIsATargetWithMode(targets, getTaskId(), MODE_CLOSING);
+ ClipAnimationHelper helper = new ClipAnimationHelper();
+ target.play(getRecentsWindowAnimator(taskView, !activityClosing, targets, helper)
+ .setDuration(RECENTS_LAUNCH_DURATION));
+
+ // Found a visible recents task that matches the opening app, lets launch the app from there
+ if (activityClosing) {
+ Animator adjacentAnimation = mFallbackRecentsView
+ .createAdjacentPageAnimForTaskLaunch(taskView, helper);
+ adjacentAnimation.setInterpolator(Interpolators.TOUCH_RESPONSE_INTERPOLATOR);
+ adjacentAnimation.setDuration(RECENTS_LAUNCH_DURATION);
+ adjacentAnimation.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mFallbackRecentsView.resetTaskVisuals();
+ }
+ });
+ target.play(adjacentAnimation);
+ }
+ return target;
}
@Override
@@ -92,8 +229,20 @@ public class RecentsActivity extends BaseDraggingActivity {
@Override
protected void onStart() {
+ // Set the alpha to 1 before calling super, as it may get set back to 0 due to
+ // onActivityStart callback.
+ mFallbackRecentsView.setContentAlpha(1);
super.onStart();
UiFactory.onStart(this);
+ mFallbackRecentsView.resetTaskVisuals();
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+
+ // Workaround for b/78520668, explicitly trim memory once UI is hidden
+ onTrimMemory(TRIM_MEMORY_UI_HIDDEN);
}
@Override
@@ -103,8 +252,33 @@ public class RecentsActivity extends BaseDraggingActivity {
}
@Override
+ protected void onNewIntent(Intent intent) {
+ super.onNewIntent(intent);
+ RecentsActivityTracker.onRecentsActivityNewIntent(this);
+ }
+
+ @Override
protected void onDestroy() {
super.onDestroy();
RecentsActivityTracker.onRecentsActivityDestroy(this);
}
+
+ @Override
+ public void onBackPressed() {
+ // TODO: Launch the task we came from
+ startHome();
+ }
+
+ public void startHome() {
+ startActivity(new Intent(Intent.ACTION_MAIN)
+ .addCategory(Intent.CATEGORY_HOME)
+ .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
+ }
+
+ @Override
+ public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
+ super.dump(prefix, fd, writer, args);
+ writer.println(prefix + "Misc:");
+ dumpMisc(writer);
+ }
}
diff --git a/quickstep/src/com/android/quickstep/RecentsActivityTracker.java b/quickstep/src/com/android/quickstep/RecentsActivityTracker.java
index 77f0e7a53..fb6090e2a 100644
--- a/quickstep/src/com/android/quickstep/RecentsActivityTracker.java
+++ b/quickstep/src/com/android/quickstep/RecentsActivityTracker.java
@@ -22,6 +22,7 @@ import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
+import com.android.launcher3.MainThreadExecutor;
import com.android.quickstep.ActivityControlHelper.ActivityInitListener;
import com.android.quickstep.util.RemoteAnimationProvider;
@@ -34,9 +35,8 @@ import java.util.function.BiPredicate;
@TargetApi(Build.VERSION_CODES.P)
public class RecentsActivityTracker implements ActivityInitListener {
- private static final Object LOCK = new Object();
- private static WeakReference<RecentsActivityTracker> sTracker = new WeakReference<>(null);
private static WeakReference<RecentsActivity> sCurrentActivity = new WeakReference<>(null);
+ private static final Scheduler sScheduler = new Scheduler();
private final BiPredicate<RecentsActivity, Boolean> mOnInitListener;
@@ -46,42 +46,20 @@ public class RecentsActivityTracker implements ActivityInitListener {
@Override
public void register() {
- synchronized (LOCK) {
- sTracker = new WeakReference<>(this);
- }
+ sScheduler.schedule(this);
}
@Override
public void unregister() {
- synchronized (LOCK) {
- if (sTracker.get() == this) {
- sTracker.clear();
- }
- }
+ sScheduler.clearReference(this);
}
- public static void onRecentsActivityCreate(RecentsActivity activity) {
- synchronized (LOCK) {
- RecentsActivityTracker tracker = sTracker.get();
- if (tracker != null && tracker.mOnInitListener.test(activity, false)) {
- sTracker.clear();
- }
- sCurrentActivity = new WeakReference<>(activity);
- }
- }
-
- public static void onRecentsActivityDestroy(RecentsActivity activity) {
- synchronized (LOCK) {
- if (sCurrentActivity.get() == activity) {
- sCurrentActivity.clear();
- }
- }
+ private boolean init(RecentsActivity activity, boolean visible) {
+ return mOnInitListener.test(activity, visible);
}
public static RecentsActivity getCurrentActivity() {
- synchronized (LOCK) {
- return sCurrentActivity.get();
- }
+ return sCurrentActivity.get();
}
@Override
@@ -92,4 +70,62 @@ public class RecentsActivityTracker implements ActivityInitListener {
Bundle options = animProvider.toActivityOptions(handler, duration).toBundle();
context.startActivity(intent, options);
}
+
+ public static void onRecentsActivityCreate(RecentsActivity activity) {
+ sCurrentActivity = new WeakReference<>(activity);
+ sScheduler.initIfPending(activity, false);
+ }
+
+
+ public static void onRecentsActivityNewIntent(RecentsActivity activity) {
+ sScheduler.initIfPending(activity, activity.isStarted());
+ }
+
+ public static void onRecentsActivityDestroy(RecentsActivity activity) {
+ if (sCurrentActivity.get() == activity) {
+ sCurrentActivity.clear();
+ }
+ }
+
+
+ private static class Scheduler implements Runnable {
+
+ private WeakReference<RecentsActivityTracker> mPendingTracker = new WeakReference<>(null);
+ private MainThreadExecutor mMainThreadExecutor;
+
+ public synchronized void schedule(RecentsActivityTracker tracker) {
+ mPendingTracker = new WeakReference<>(tracker);
+ if (mMainThreadExecutor == null) {
+ mMainThreadExecutor = new MainThreadExecutor();
+ }
+ mMainThreadExecutor.execute(this);
+ }
+
+ @Override
+ public void run() {
+ RecentsActivity activity = sCurrentActivity.get();
+ if (activity != null) {
+ initIfPending(activity, activity.isStarted());
+ }
+ }
+
+ public synchronized boolean initIfPending(RecentsActivity activity, boolean alreadyOnHome) {
+ RecentsActivityTracker tracker = mPendingTracker.get();
+ if (tracker != null) {
+ if (!tracker.init(activity, alreadyOnHome)) {
+ mPendingTracker.clear();
+ }
+ return true;
+ }
+ return false;
+ }
+
+ public synchronized boolean clearReference(RecentsActivityTracker tracker) {
+ if (mPendingTracker.get() == tracker) {
+ mPendingTracker.clear();
+ return true;
+ }
+ return false;
+ }
+ }
}
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationInterpolator.java b/quickstep/src/com/android/quickstep/RecentsAnimationInterpolator.java
deleted file mode 100644
index fdeb0c170..000000000
--- a/quickstep/src/com/android/quickstep/RecentsAnimationInterpolator.java
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.quickstep;
-
-import android.graphics.Rect;
-
-import com.android.launcher3.Utilities;
-
-/**
- * Helper class to interpolate the animation between a task view representation and an actual
- * window.
- */
-public class RecentsAnimationInterpolator {
-
- public static class TaskWindowBounds {
- public float taskScale = 1f;
- public float taskX = 0f;
- public float taskY = 0f;
-
- public float winScale = 1f;
- public float winX = 0f;
- public float winY = 0f;
- public Rect winCrop = new Rect();
-
- @Override
- public String toString() {
- return "taskScale=" + taskScale + " taskX=" + taskX + " taskY=" + taskY
- + " winScale=" + winScale + " winX=" + winX + " winY=" + winY
- + " winCrop=" + winCrop;
- }
- }
-
- private TaskWindowBounds mTmpTaskWindowBounds = new TaskWindowBounds();
- private Rect mTmpInsets = new Rect();
-
- private Rect mWindow;
- private Rect mInsetWindow;
- private Rect mInsets;
- private Rect mTask;
- private Rect mTaskInsets;
- private Rect mThumbnail;
-
- private float mInitialTaskScale;
- private float mInitialTaskTranslationX;
- private float mFinalTaskScale;
- private Rect mScaledTask;
- private Rect mTargetTask;
- private Rect mSrcWindow;
-
- public RecentsAnimationInterpolator(Rect window, Rect insets, Rect task, Rect taskInsets,
- float taskScale, float taskTranslationX) {
- mWindow = window;
- mInsets = insets;
- mTask = task;
- mTaskInsets = taskInsets;
- mInsetWindow = new Rect(window);
- Utilities.insetRect(mInsetWindow, insets);
-
- mThumbnail = new Rect(task);
- Utilities.insetRect(mThumbnail, taskInsets);
- mInitialTaskScale = taskScale;
- mInitialTaskTranslationX = taskTranslationX;
- mFinalTaskScale = (float) mInsetWindow.width() / mThumbnail.width();
- mScaledTask = new Rect(task);
- Utilities.scaleRectAboutCenter(mScaledTask, mFinalTaskScale);
- Rect finalScaledTaskInsets = new Rect(taskInsets);
- Utilities.scaleRect(finalScaledTaskInsets, mFinalTaskScale);
- mTargetTask = new Rect(mInsetWindow);
- mTargetTask.offsetTo(window.left + insets.left - finalScaledTaskInsets.left,
- window.top + insets.top - finalScaledTaskInsets.top);
-
- float initialWinScale = 1f / mFinalTaskScale;
- Rect scaledWindow = new Rect(mInsetWindow);
- Utilities.scaleRectAboutCenter(scaledWindow, initialWinScale);
- Rect scaledInsets = new Rect(insets);
- Utilities.scaleRect(scaledInsets, initialWinScale);
- mSrcWindow = new Rect(scaledWindow);
- mSrcWindow.offsetTo(mThumbnail.left - scaledInsets.left,
- mThumbnail.top - scaledInsets.top);
- }
-
- public TaskWindowBounds interpolate(float t) {
- mTmpTaskWindowBounds.taskScale = Utilities.mapRange(t,
- mInitialTaskScale, mFinalTaskScale);
- mTmpTaskWindowBounds.taskX = Utilities.mapRange(t,
- mInitialTaskTranslationX, mTargetTask.left - mScaledTask.left);
- mTmpTaskWindowBounds.taskY = Utilities.mapRange(t,
- 0, mTargetTask.top - mScaledTask.top);
-
- float taskScale = Utilities.mapRange(t, 1, mFinalTaskScale);
- mTmpTaskWindowBounds.winScale = taskScale / mFinalTaskScale;
- mTmpTaskWindowBounds.winX = Utilities.mapRange(t,
- mSrcWindow.left, 0);
- mTmpTaskWindowBounds.winY = Utilities.mapRange(t,
- mSrcWindow.top, 0);
-
- mTmpInsets.set(mInsets);
- Utilities.scaleRect(mTmpInsets, (1f - t));
- mTmpTaskWindowBounds.winCrop.set(mWindow);
- Utilities.insetRect(mTmpTaskWindowBounds.winCrop, mTmpInsets);
-
- return mTmpTaskWindowBounds;
- }
-}
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationWrapper.java b/quickstep/src/com/android/quickstep/RecentsAnimationWrapper.java
index 12f8d52b8..30b10b0ea 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationWrapper.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationWrapper.java
@@ -15,28 +15,33 @@
*/
package com.android.quickstep;
+import com.android.launcher3.util.LooperExecutor;
import com.android.launcher3.util.TraceHelper;
-import com.android.systemui.shared.system.BackgroundExecutor;
+import com.android.launcher3.util.UiThreadHelper;
+import com.android.quickstep.util.RemoteAnimationTargetSet;
import com.android.systemui.shared.system.RecentsAnimationControllerCompat;
-import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
+import java.util.concurrent.ExecutorService;
/**
* Wrapper around RecentsAnimationController to help with some synchronization
*/
public class RecentsAnimationWrapper {
- public RecentsAnimationControllerCompat controller;
- public RemoteAnimationTargetCompat[] targets;
+ public RemoteAnimationTargetSet targetSet;
+ private RecentsAnimationControllerCompat mController;
private boolean mInputConsumerEnabled = false;
private boolean mBehindSystemBars = true;
private boolean mSplitScreenMinimized = false;
+ private final ExecutorService mExecutorService =
+ new LooperExecutor(UiThreadHelper.getBackgroundLooper());
+
public synchronized void setController(
- RecentsAnimationControllerCompat controller, RemoteAnimationTargetCompat[] targets) {
+ RecentsAnimationControllerCompat controller, RemoteAnimationTargetSet targetSet) {
TraceHelper.partitionSection("RecentsController", "Set controller " + controller);
- this.controller = controller;
- this.targets = targets;
+ this.mController = controller;
+ this.targetSet = targetSet;
if (mInputConsumerEnabled) {
enableInputConsumer();
@@ -48,16 +53,16 @@ public class RecentsAnimationWrapper {
* on the background thread.
*/
public void finish(boolean toHome, Runnable onFinishComplete) {
- BackgroundExecutor.get().submit(() -> {
- synchronized (this) {
- TraceHelper.endSection("RecentsController",
- "Finish " + controller + ", toHome=" + toHome);
- if (controller != null) {
- controller.setInputConsumerEnabled(false);
- controller.finish(toHome);
- if (onFinishComplete != null) {
- onFinishComplete.run();
- }
+ mExecutorService.submit(() -> {
+ RecentsAnimationControllerCompat controller = mController;
+ mController = null;
+ TraceHelper.endSection("RecentsController",
+ "Finish " + controller + ", toHome=" + toHome);
+ if (controller != null) {
+ controller.setInputConsumerEnabled(false);
+ controller.finish(toHome);
+ if (onFinishComplete != null) {
+ onFinishComplete.run();
}
}
});
@@ -66,13 +71,12 @@ public class RecentsAnimationWrapper {
public void enableInputConsumer() {
mInputConsumerEnabled = true;
if (mInputConsumerEnabled) {
- BackgroundExecutor.get().submit(() -> {
- synchronized (this) {
- TraceHelper.partitionSection("RecentsController",
- "Enabling consumer on " + controller);
- if (controller != null) {
- controller.setInputConsumerEnabled(true);
- }
+ mExecutorService.submit(() -> {
+ RecentsAnimationControllerCompat controller = mController;
+ TraceHelper.partitionSection("RecentsController",
+ "Enabling consumer on " + controller);
+ if (controller != null) {
+ controller.setInputConsumerEnabled(true);
}
});
}
@@ -83,13 +87,12 @@ public class RecentsAnimationWrapper {
return;
}
mBehindSystemBars = behindSystemBars;
- BackgroundExecutor.get().submit(() -> {
- synchronized (this) {
- TraceHelper.partitionSection("RecentsController",
- "Setting behind system bars on " + controller);
- if (controller != null) {
- controller.setAnimationTargetsBehindSystemBars(behindSystemBars);
- }
+ mExecutorService.submit(() -> {
+ RecentsAnimationControllerCompat controller = mController;
+ TraceHelper.partitionSection("RecentsController",
+ "Setting behind system bars on " + controller);
+ if (controller != null) {
+ controller.setAnimationTargetsBehindSystemBars(behindSystemBars);
}
});
}
@@ -105,14 +108,28 @@ public class RecentsAnimationWrapper {
return;
}
mSplitScreenMinimized = minimized;
- BackgroundExecutor.get().submit(() -> {
- synchronized (this) {
- TraceHelper.partitionSection("RecentsController",
- "Setting minimize dock on " + controller);
- if (controller != null) {
- controller.setSplitScreenMinimized(minimized);
- }
+ mExecutorService.submit(() -> {
+ RecentsAnimationControllerCompat controller = mController;
+ TraceHelper.partitionSection("RecentsController",
+ "Setting minimize dock on " + controller);
+ if (controller != null) {
+ controller.setSplitScreenMinimized(minimized);
+ }
+ });
+ }
+
+ public void hideCurrentInputMethod() {
+ mExecutorService.submit(() -> {
+ RecentsAnimationControllerCompat controller = mController;
+ TraceHelper.partitionSection("RecentsController",
+ "Hiding currentinput method on " + controller);
+ if (controller != null) {
+ controller.hideCurrentInputMethod();
}
});
}
+
+ public RecentsAnimationControllerCompat getController() {
+ return mController;
+ }
}
diff --git a/quickstep/src/com/android/quickstep/RecentsModel.java b/quickstep/src/com/android/quickstep/RecentsModel.java
index 4652f2d65..9c2c8b313 100644
--- a/quickstep/src/com/android/quickstep/RecentsModel.java
+++ b/quickstep/src/com/android/quickstep/RecentsModel.java
@@ -15,6 +15,8 @@
*/
package com.android.quickstep;
+import static com.android.quickstep.TaskUtils.checkCurrentOrManagedUserId;
+
import android.annotation.TargetApi;
import android.app.ActivityManager;
import android.content.ComponentCallbacks2;
@@ -26,8 +28,10 @@ import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
import android.os.Looper;
+import android.os.RemoteException;
import android.os.UserHandle;
import android.support.annotation.WorkerThread;
+import android.util.Log;
import android.util.LruCache;
import android.util.SparseArray;
import android.view.accessibility.AccessibilityManager;
@@ -85,7 +89,8 @@ public class RecentsModel extends TaskStackChangeListener {
private int mTaskChangeId;
private ISystemUiProxy mSystemUiProxy;
private boolean mClearAssistCacheOnStackChange = true;
- private final boolean mPreloadTasksInBackground;
+ private final boolean mIsLowRamDevice;
+ private boolean mPreloadTasksInBackground;
private final AccessibilityManager mAccessibilityManager;
private RecentsModel(Context context) {
@@ -93,7 +98,7 @@ public class RecentsModel extends TaskStackChangeListener {
ActivityManager activityManager =
(ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
- mPreloadTasksInBackground = !activityManager.isLowRamDevice();
+ mIsLowRamDevice = activityManager.isLowRamDevice();
mMainThreadExecutor = new MainThreadExecutor();
Resources res = context.getResources();
@@ -158,6 +163,10 @@ public class RecentsModel extends TaskStackChangeListener {
return requestId;
}
+ public void setPreloadTasksInBackground(boolean preloadTasksInBackground) {
+ mPreloadTasksInBackground = preloadTasksInBackground && !mIsLowRamDevice;
+ }
+
@Override
public void onActivityPinned(String packageName, int userId, int taskId, int stackId) {
mTaskChangeId++;
@@ -183,7 +192,7 @@ public class RecentsModel extends TaskStackChangeListener {
@Override
public void onTaskStackChangedBackground() {
int userId = UserHandle.myUserId();
- if (!mPreloadTasksInBackground || !checkCurrentUserId(userId, false /* debug */)) {
+ if (!mPreloadTasksInBackground || !checkCurrentOrManagedUserId(userId, mContext)) {
// TODO: Only register this for the current user
return;
}
@@ -234,6 +243,19 @@ public class RecentsModel extends TaskStackChangeListener {
mRecentsTaskLoader.onTrimMemory(level);
}
+ public void onOverviewShown(boolean fromHome, String tag) {
+ if (mSystemUiProxy == null) {
+ return;
+ }
+ try {
+ mSystemUiProxy.onOverviewShown(fromHome);
+ } catch (RemoteException e) {
+ Log.w(tag,
+ "Failed to notify SysUI of overview shown from " + (fromHome ? "home" : "app")
+ + ": ", e);
+ }
+ }
+
@WorkerThread
public void preloadAssistData(int taskId, Bundle data) {
mMainThreadExecutor.execute(() -> {
diff --git a/quickstep/src/com/android/quickstep/TaskSystemShortcut.java b/quickstep/src/com/android/quickstep/TaskSystemShortcut.java
index 2ebf2525f..228af8e90 100644
--- a/quickstep/src/com/android/quickstep/TaskSystemShortcut.java
+++ b/quickstep/src/com/android/quickstep/TaskSystemShortcut.java
@@ -27,7 +27,6 @@ import android.os.RemoteException;
import android.os.UserHandle;
import android.util.Log;
import android.view.View;
-import android.view.ViewTreeObserver.OnPreDrawListener;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.BaseDraggingActivity;
@@ -101,13 +100,9 @@ public class TaskSystemShortcut<T extends SystemShortcut> extends SystemShortcut
}
}
- public static class SplitScreen extends TaskSystemShortcut implements OnPreDrawListener,
- DeviceProfile.OnDeviceProfileChangeListener, View.OnLayoutChangeListener {
+ public static class SplitScreen extends TaskSystemShortcut {
private Handler mHandler;
- private RecentsView mRecentsView;
- private TaskView mTaskView;
- private BaseDraggingActivity mActivity;
public SplitScreen() {
super(R.drawable.ic_split_screen, R.string.recent_task_option_split_screen);
@@ -125,16 +120,45 @@ public class TaskSystemShortcut<T extends SystemShortcut> extends SystemShortcut
if (!task.isDockable) {
return null;
}
- mActivity = activity;
- mRecentsView = activity.getOverviewPanel();
- mTaskView = taskView;
+ final RecentsView recentsView = activity.getOverviewPanel();
+
final TaskThumbnailView thumbnailView = taskView.getThumbnail();
return (v -> {
+ final View.OnLayoutChangeListener onLayoutChangeListener =
+ new View.OnLayoutChangeListener() {
+ @Override
+ public void onLayoutChange(View v, int l, int t, int r, int b,
+ int oldL, int oldT, int oldR, int oldB) {
+ taskView.getRootView().removeOnLayoutChangeListener(this);
+ recentsView.removeIgnoreResetTask(taskView);
+
+ // Start animating in the side pages once launcher has been resized
+ recentsView.dismissTask(taskView, false, false);
+ }
+ };
+
+ final DeviceProfile.OnDeviceProfileChangeListener onDeviceProfileChangeListener =
+ new DeviceProfile.OnDeviceProfileChangeListener() {
+ @Override
+ public void onDeviceProfileChanged(DeviceProfile dp) {
+ activity.removeOnDeviceProfileChangeListener(this);
+ if (dp.isMultiWindowMode) {
+ taskView.getRootView().addOnLayoutChangeListener(
+ onLayoutChangeListener);
+ }
+ }
+ };
+
AbstractFloatingView.closeOpenViews(activity, true,
AbstractFloatingView.TYPE_ALL & ~AbstractFloatingView.TYPE_REBIND_SAFE);
+ final int navBarPosition = WindowManagerWrapper.getInstance().getNavBarPosition();
+ if (navBarPosition == WindowManagerWrapper.NAV_BAR_POS_INVALID) {
+ return;
+ }
+ boolean dockTopOrLeft = navBarPosition != WindowManagerWrapper.NAV_BAR_POS_LEFT;
if (ActivityManagerWrapper.getInstance().startActivityFromRecents(taskId,
- ActivityOptionsCompat.makeSplitScreenOptions(true))) {
+ ActivityOptionsCompat.makeSplitScreenOptions(dockTopOrLeft))) {
ISystemUiProxy sysUiProxy = RecentsModel.getInstance(activity).getSystemUiProxy();
try {
sysUiProxy.onSplitScreenInvoked();
@@ -145,15 +169,14 @@ public class TaskSystemShortcut<T extends SystemShortcut> extends SystemShortcut
// Add a device profile change listener to kick off animating the side tasks
// once we enter multiwindow mode and relayout
- activity.addOnDeviceProfileChangeListener(this);
+ activity.addOnDeviceProfileChangeListener(onDeviceProfileChangeListener);
final Runnable animStartedListener = () -> {
// Hide the task view and wait for the window to be resized
// TODO: Consider animating in launcher and do an in-place start activity
// afterwards
- mRecentsView.addIgnoreResetTask(mTaskView);
- mTaskView.setAlpha(0f);
- mTaskView.getViewTreeObserver().addOnPreDrawListener(SplitScreen.this);
+ recentsView.addIgnoreResetTask(taskView);
+ taskView.setAlpha(0f);
};
final int[] position = new int[2];
@@ -179,35 +202,12 @@ public class TaskSystemShortcut<T extends SystemShortcut> extends SystemShortcut
}
});
}
-
- @Override
- public boolean onPreDraw() {
- mTaskView.getViewTreeObserver().removeOnPreDrawListener(this);
- WindowManagerWrapper.getInstance().endProlongedAnimations();
- return true;
- }
-
- @Override
- public void onDeviceProfileChanged(DeviceProfile dp) {
- mActivity.removeOnDeviceProfileChangeListener(this);
- if (dp.isMultiWindowMode) {
- mTaskView.getRootView().addOnLayoutChangeListener(this);
- }
- }
-
- @Override
- public void onLayoutChange(View v, int l, int t, int r, int b,
- int oldL, int oldT, int oldR, int oldB) {
- mTaskView.getRootView().removeOnLayoutChangeListener(this);
- mRecentsView.removeIgnoreResetTask(mTaskView);
-
- // Start animating in the side pages once launcher has been resized
- mRecentsView.dismissTask(mTaskView, false, false);
- }
}
public static class Pin extends TaskSystemShortcut {
+ private static final String TAG = Pin.class.getSimpleName();
+
private Handler mHandler;
public Pin() {
@@ -237,6 +237,8 @@ public class TaskSystemShortcut<T extends SystemShortcut> extends SystemShortcut
} catch (RemoteException e) {
Log.w(TAG, "Failed to start screen pinning: ", e);
}
+ } else {
+ taskView.notifyTaskLaunchFailed(TAG);
}
};
taskView.launchTask(true, resultCallback, mHandler);
diff --git a/quickstep/src/com/android/quickstep/TaskUtils.java b/quickstep/src/com/android/quickstep/TaskUtils.java
index 5bf1d07db..2b0c98f93 100644
--- a/quickstep/src/com/android/quickstep/TaskUtils.java
+++ b/quickstep/src/com/android/quickstep/TaskUtils.java
@@ -16,25 +16,49 @@
package com.android.quickstep;
+import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.launcher3.anim.Interpolators.TOUCH_RESPONSE_INTERPOLATOR;
+import static com.android.systemui.shared.recents.utilities.Utilities.getNextFrameNumber;
+import static com.android.systemui.shared.recents.utilities.Utilities.getSurface;
+import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_OPENING;
+
+import android.animation.ValueAnimator;
+import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
+import android.graphics.RectF;
import android.os.UserHandle;
import android.util.Log;
+import android.view.Surface;
+import android.view.View;
+import com.android.launcher3.BaseDraggingActivity;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.Utilities;
import com.android.launcher3.compat.LauncherAppsCompat;
import com.android.launcher3.compat.UserManagerCompat;
import com.android.launcher3.util.ComponentKey;
+import com.android.quickstep.util.ClipAnimationHelper;
+import com.android.quickstep.util.MultiValueUpdateListener;
+import com.android.quickstep.util.RemoteAnimationTargetSet;
+import com.android.quickstep.views.RecentsView;
+import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
+
+import java.util.List;
/**
* Contains helpful methods for retrieving data from {@link Task}s.
- * TODO: remove this once we switch to getting the icon and label from IconCache.
*/
public class TaskUtils {
private static final String TAG = "TaskUtils";
+ /**
+ * TODO: remove this once we switch to getting the icon and label from IconCache.
+ */
public static CharSequence getTitle(Context context, Task task) {
LauncherAppsCompat launcherAppsCompat = LauncherAppsCompat.getInstance(context);
UserManagerCompat userManagerCompat = UserManagerCompat.getInstance(context);
@@ -53,4 +77,150 @@ public class TaskUtils {
public static ComponentKey getComponentKeyForTask(Task.TaskKey taskKey) {
return new ComponentKey(taskKey.getComponent(), UserHandle.of(taskKey.userId));
}
+
+
+ /**
+ * Try to find a TaskView that corresponds with the component of the launched view.
+ *
+ * If this method returns a non-null TaskView, it will be used in composeRecentsLaunchAnimation.
+ * Otherwise, we will assume we are using a normal app transition, but it's possible that the
+ * opening remote target (which we don't get until onAnimationStart) will resolve to a TaskView.
+ */
+ public static TaskView findTaskViewToLaunch(
+ BaseDraggingActivity activity, View v, RemoteAnimationTargetCompat[] targets) {
+ if (v instanceof TaskView) {
+ return (TaskView) v;
+ }
+ RecentsView recentsView = activity.getOverviewPanel();
+
+ // It's possible that the launched view can still be resolved to a visible task view, check
+ // the task id of the opening task and see if we can find a match.
+ if (v.getTag() instanceof ItemInfo) {
+ ItemInfo itemInfo = (ItemInfo) v.getTag();
+ ComponentName componentName = itemInfo.getTargetComponent();
+ int userId = itemInfo.user.getIdentifier();
+ if (componentName != null) {
+ for (int i = 0; i < recentsView.getChildCount(); i++) {
+ TaskView taskView = recentsView.getPageAt(i);
+ if (recentsView.isTaskViewVisible(taskView)) {
+ Task.TaskKey key = taskView.getTask().key;
+ if (componentName.equals(key.getComponent()) && userId == key.userId) {
+ return taskView;
+ }
+ }
+ }
+ }
+ }
+
+ if (targets == null) {
+ return null;
+ }
+ // Resolve the opening task id
+ int openingTaskId = -1;
+ for (RemoteAnimationTargetCompat target : targets) {
+ if (target.mode == MODE_OPENING) {
+ openingTaskId = target.taskId;
+ break;
+ }
+ }
+
+ // If there is no opening task id, fall back to the normal app icon launch animation
+ if (openingTaskId == -1) {
+ return null;
+ }
+
+ // If the opening task id is not currently visible in overview, then fall back to normal app
+ // icon launch animation
+ TaskView taskView = recentsView.getTaskView(openingTaskId);
+ if (taskView == null || !recentsView.isTaskViewVisible(taskView)) {
+ return null;
+ }
+ return taskView;
+ }
+
+ /**
+ * @return Animator that controls the window of the opening targets for the recents launch
+ * animation.
+ */
+ public static ValueAnimator getRecentsWindowAnimator(TaskView v, boolean skipViewChanges,
+ RemoteAnimationTargetCompat[] targets, final ClipAnimationHelper inOutHelper) {
+ final ValueAnimator appAnimator = ValueAnimator.ofFloat(0, 1);
+ appAnimator.setInterpolator(TOUCH_RESPONSE_INTERPOLATOR);
+ appAnimator.addUpdateListener(new MultiValueUpdateListener() {
+
+ // Defer fading out the view until after the app window gets faded in
+ final FloatProp mViewAlpha = new FloatProp(1f, 0f, 75, 75, LINEAR);
+ final FloatProp mTaskAlpha = new FloatProp(0f, 1f, 0, 75, LINEAR);
+
+ final RemoteAnimationTargetSet mTargetSet;
+
+ final RectF mThumbnailRect;
+ private Surface mSurface;
+ private long mFrameNumber;
+
+ {
+ mTargetSet = new RemoteAnimationTargetSet(targets, MODE_OPENING);
+ inOutHelper.setTaskTransformCallback((t, app) -> {
+ t.setAlpha(app.leash, mTaskAlpha.value);
+
+ if (!skipViewChanges) {
+ t.deferTransactionUntil(app.leash, mSurface, mFrameNumber);
+ }
+ });
+
+ inOutHelper.prepareAnimation(true /* isOpening */);
+ inOutHelper.fromTaskThumbnailView(v.getThumbnail(), (RecentsView) v.getParent(),
+ mTargetSet.apps.length == 0 ? null : mTargetSet.apps[0]);
+
+ mThumbnailRect = new RectF(inOutHelper.getTargetRect());
+ mThumbnailRect.offset(-v.getTranslationX(), -v.getTranslationY());
+ Utilities.scaleRectFAboutCenter(mThumbnailRect, 1 / v.getScaleX());
+ }
+
+ @Override
+ public void onUpdate(float percent) {
+ mSurface = getSurface(v);
+ mFrameNumber = mSurface != null ? getNextFrameNumber(mSurface) : -1;
+ if (mFrameNumber == -1) {
+ // Booo, not cool! Our surface got destroyed, so no reason to animate anything.
+ Log.w(TAG, "Failed to animate, surface got destroyed.");
+ return;
+ }
+
+ RectF taskBounds = inOutHelper.applyTransform(mTargetSet, 1 - percent);
+ if (!skipViewChanges) {
+ float scale = taskBounds.width() / mThumbnailRect.width();
+ v.setScaleX(scale);
+ v.setScaleY(scale);
+ v.setTranslationX(taskBounds.centerX() - mThumbnailRect.centerX());
+ v.setTranslationY(taskBounds.centerY() - mThumbnailRect.centerY());
+ v.setAlpha(mViewAlpha.value);
+ }
+ }
+ });
+ return appAnimator;
+ }
+
+ public static boolean taskIsATargetWithMode(RemoteAnimationTargetCompat[] targets,
+ int taskId, int mode) {
+ for (RemoteAnimationTargetCompat target : targets) {
+ if (target.mode == mode && target.taskId == taskId) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public static boolean checkCurrentOrManagedUserId(int currentUserId, Context context) {
+ if (currentUserId == UserHandle.myUserId()) {
+ return true;
+ }
+ List<UserHandle> allUsers = UserManagerCompat.getInstance(context).getUserProfiles();
+ for (int i = allUsers.size() - 1; i >= 0; i--) {
+ if (currentUserId == allUsers.get(i).getIdentifier()) {
+ return true;
+ }
+ }
+ return false;
+ }
}
diff --git a/quickstep/src/com/android/quickstep/TouchConsumer.java b/quickstep/src/com/android/quickstep/TouchConsumer.java
index 1290ec337..aa844d80d 100644
--- a/quickstep/src/com/android/quickstep/TouchConsumer.java
+++ b/quickstep/src/com/android/quickstep/TouchConsumer.java
@@ -46,7 +46,7 @@ public interface TouchConsumer extends Consumer<MotionEvent> {
default void onQuickScrubProgress(float progress) { }
- default void onQuickStep(float eventX, float eventY, long eventTime) { }
+ default void onQuickStep(MotionEvent ev) { }
/**
* Called on the binder thread to allow the consumer to process the motion event before it is
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index 84d8983e0..aecb66c77 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -21,7 +21,7 @@ import static android.view.MotionEvent.ACTION_MOVE;
import static android.view.MotionEvent.ACTION_POINTER_DOWN;
import static android.view.MotionEvent.ACTION_POINTER_UP;
import static android.view.MotionEvent.ACTION_UP;
-
+import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_NONE;
import android.annotation.TargetApi;
@@ -39,13 +39,12 @@ import android.util.SparseArray;
import android.view.Choreographer;
import android.view.MotionEvent;
import android.view.VelocityTracker;
-import android.view.View;
import android.view.ViewConfiguration;
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.MainThreadExecutor;
-import com.android.launcher3.R;
import com.android.launcher3.util.TraceHelper;
+import com.android.launcher3.views.BaseDragLayer;
import com.android.quickstep.views.RecentsView;
import com.android.systemui.shared.recents.IOverviewProxy;
import com.android.systemui.shared.recents.ISystemUiProxy;
@@ -99,8 +98,6 @@ public class TouchInteractionService extends Service {
public void onBind(ISystemUiProxy iSystemUiProxy) {
mISystemUiProxy = iSystemUiProxy;
mRecentsModel.setSystemUiProxy(mISystemUiProxy);
- RemoteRunnable.executeSafely(() -> mISystemUiProxy.setRecentsOnboardingText(
- getResources().getString(R.string.recents_swipe_up_onboarding)));
mOverviewInteractionState.setSystemUiProxy(mISystemUiProxy);
}
@@ -150,6 +147,11 @@ public class TouchInteractionService extends Service {
TraceHelper.endSection("SysUiBinder", "onQuickStep");
}
+
+ @Override
+ public void onTip(int actionType, int viewType) {
+ mOverviewCommandHelper.onTip(actionType, viewType);
+ }
};
private final TouchConsumer mNoOpTouchConsumer = (ev) -> {};
@@ -167,6 +169,7 @@ public class TouchInteractionService extends Service {
private ISystemUiProxy mISystemUiProxy;
private OverviewCommandHelper mOverviewCommandHelper;
private OverviewInteractionState mOverviewInteractionState;
+ private OverviewCallbacks mOverviewCallbacks;
private Choreographer mMainThreadChoreographer;
private Choreographer mBackgroundThreadChoreographer;
@@ -176,11 +179,13 @@ public class TouchInteractionService extends Service {
super.onCreate();
mAM = ActivityManagerWrapper.getInstance();
mRecentsModel = RecentsModel.getInstance(this);
+ mRecentsModel.setPreloadTasksInBackground(true);
mMainThreadExecutor = new MainThreadExecutor();
mOverviewCommandHelper = new OverviewCommandHelper(this);
mMainThreadChoreographer = Choreographer.getInstance();
mEventQueue = new MotionEventQueue(mMainThreadChoreographer, mNoOpTouchConsumer);
mOverviewInteractionState = OverviewInteractionState.getInstance(this);
+ mOverviewCallbacks = OverviewCallbacks.get(this);
sConnected = true;
@@ -191,6 +196,7 @@ public class TouchInteractionService extends Service {
@Override
public void onDestroy() {
+ mOverviewCommandHelper.onDestroy();
sConnected = false;
super.onDestroy();
}
@@ -222,16 +228,17 @@ public class TouchInteractionService extends Service {
if (runningTaskInfo == null && !forceToLauncher) {
return mNoOpTouchConsumer;
} else if (forceToLauncher ||
- runningTaskInfo.topActivity.equals(mOverviewCommandHelper.launcher)) {
+ runningTaskInfo.topActivity.equals(mOverviewCommandHelper.overviewComponent)) {
return getOverviewConsumer();
} else {
if (tracker == null) {
tracker = VelocityTracker.obtain();
}
return new OtherActivityTouchConsumer(this, runningTaskInfo, mRecentsModel,
- mOverviewCommandHelper.homeIntent,
+ mOverviewCommandHelper.overviewIntent,
mOverviewCommandHelper.getActivityControlHelper(), mMainThreadExecutor,
- mBackgroundThreadChoreographer, downHitTarget, tracker);
+ mBackgroundThreadChoreographer, downHitTarget, mOverviewCallbacks,
+ tracker);
}
}
@@ -249,7 +256,7 @@ public class TouchInteractionService extends Service {
private final ActivityControlHelper<T> mActivityHelper;
private final T mActivity;
- private final View mTarget;
+ private final BaseDragLayer mTarget;
private final int[] mLocationOnScreen = new int[2];
private final PointF mDownPos = new PointF();
private final int mTouchSlop;
@@ -257,7 +264,6 @@ public class TouchInteractionService extends Service {
private boolean mTrackingStarted = false;
private boolean mInvalidated = false;
- private boolean mHadWindowFocusOnDown;
private float mLastProgress = 0;
private boolean mStartPending = false;
@@ -282,8 +288,7 @@ public class TouchInteractionService extends Service {
if (action == ACTION_DOWN) {
mTrackingStarted = false;
mDownPos.set(ev.getX(), ev.getY());
- mHadWindowFocusOnDown = mTarget.hasWindowFocus();
- } else if (!mTrackingStarted && mHadWindowFocusOnDown) {
+ } else if (!mTrackingStarted) {
switch (action) {
case ACTION_POINTER_UP:
case ACTION_POINTER_DOWN:
@@ -294,7 +299,6 @@ public class TouchInteractionService extends Service {
case ACTION_MOVE: {
float displacement = ev.getY() - mDownPos.y;
if (Math.abs(displacement) >= mTouchSlop) {
- mTrackingStarted = true;
mTarget.getLocationOnScreen(mLocationOnScreen);
// Send a down event only when mTouchSlop is crossed.
@@ -302,6 +306,7 @@ public class TouchInteractionService extends Service {
down.setAction(ACTION_DOWN);
sendEvent(down);
down.recycle();
+ mTrackingStarted = true;
}
}
}
@@ -320,22 +325,45 @@ public class TouchInteractionService extends Service {
int flags = ev.getEdgeFlags();
ev.setEdgeFlags(flags | EDGE_NAV_BAR);
ev.offsetLocation(-mLocationOnScreen[0], -mLocationOnScreen[1]);
- mTarget.dispatchTouchEvent(ev);
+ if (!mTrackingStarted) {
+ mTarget.onInterceptTouchEvent(ev);
+ }
+ mTarget.onTouchEvent(ev);
ev.offsetLocation(mLocationOnScreen[0], mLocationOnScreen[1]);
ev.setEdgeFlags(flags);
}
@Override
+ public void onQuickStep(MotionEvent ev) {
+ if (mInvalidated) {
+ return;
+ }
+ OverviewCallbacks.get(mActivity).closeAllWindows();
+ ActivityManagerWrapper.getInstance()
+ .closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
+ }
+
+ @Override
public void updateTouchTracking(int interactionType) {
if (mInvalidated) {
return;
}
if (interactionType == INTERACTION_QUICK_SCRUB) {
- mStartPending = true;
+ if (!mQuickScrubController.prepareQuickScrub(TAG)) {
+ mInvalidated = true;
+ return;
+ }
+ OverviewCallbacks.get(mActivity).closeAllWindows();
+ ActivityManagerWrapper.getInstance()
+ .closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
+ mStartPending = true;
Runnable action = () -> {
- mQuickScrubController.onQuickScrubStart(
- mActivityHelper.onQuickInteractionStart(mActivity, true));
+ if (!mQuickScrubController.prepareQuickScrub(TAG)) {
+ mInvalidated = true;
+ return;
+ }
+ mActivityHelper.onQuickInteractionStart(mActivity, null, true);
mQuickScrubController.onQuickScrubProgress(mLastProgress);
mStartPending = false;
@@ -343,7 +371,6 @@ public class TouchInteractionService extends Service {
mQuickScrubController.onQuickScrubEnd();
mEndPending = false;
}
-
};
mActivityHelper.executeOnWindowAvailable(mActivity, action);
@@ -365,7 +392,7 @@ public class TouchInteractionService extends Service {
@Override
public void onQuickScrubProgress(float progress) {
mLastProgress = progress;
- if (mInvalidated || mEndPending) {
+ if (mInvalidated || mStartPending) {
return;
}
mQuickScrubController.onQuickScrubProgress(progress);
diff --git a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
index d4c35e0b1..84b217648 100644
--- a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
+++ b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
@@ -15,54 +15,57 @@
*/
package com.android.quickstep;
-import static com.android.launcher3.anim.Interpolators.ACCEL_2;
+import static com.android.launcher3.BaseActivity.INVISIBLE_BY_STATE_HANDLER;
+import static com.android.launcher3.Utilities.postAsyncCallback;
+import static com.android.launcher3.anim.Interpolators.DEACCEL;
import static com.android.launcher3.anim.Interpolators.LINEAR;
-import static com.android.quickstep.QuickScrubController.QUICK_SCRUB_START_DURATION;
+import static com.android.quickstep.QuickScrubController.QUICK_SCRUB_FROM_APP_START_DURATION;
import static com.android.quickstep.TouchConsumer.INTERACTION_NORMAL;
import static com.android.quickstep.TouchConsumer.INTERACTION_QUICK_SCRUB;
-import static com.android.systemui.shared.recents.utilities.Utilities
- .postAtFrontOfQueueAsynchronously;
-import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.annotation.TargetApi;
import android.app.ActivityManager.RunningTaskInfo;
import android.content.Context;
-import android.content.res.Resources;
+import android.graphics.Canvas;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.os.SystemClock;
+import android.support.annotation.AnyThread;
import android.support.annotation.UiThread;
import android.support.annotation.WorkerThread;
import android.util.Log;
import android.view.View;
import android.view.ViewTreeObserver.OnDrawListener;
+import android.view.WindowManager;
import android.view.animation.Interpolator;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.MainThreadExecutor;
import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimationSuccessListener;
import com.android.launcher3.anim.AnimatorPlaybackController;
-import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.logging.UserEventDispatcher;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
+import com.android.launcher3.util.MultiValueAlpha;
+import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
import com.android.launcher3.util.TraceHelper;
import com.android.quickstep.ActivityControlHelper.ActivityInitListener;
+import com.android.quickstep.ActivityControlHelper.AnimationFactory;
import com.android.quickstep.ActivityControlHelper.LayoutListener;
import com.android.quickstep.TouchConsumer.InteractionType;
import com.android.quickstep.util.ClipAnimationHelper;
-import com.android.quickstep.util.SysuiEventLogger;
+import com.android.quickstep.util.RemoteAnimationTargetSet;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.recents.model.ThumbnailData;
@@ -70,7 +73,7 @@ import com.android.systemui.shared.system.InputConsumerController;
import com.android.systemui.shared.system.LatencyTrackerCompat;
import com.android.systemui.shared.system.RecentsAnimationControllerCompat;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
-import com.android.systemui.shared.system.TransactionCompat;
+import com.android.systemui.shared.system.WindowCallbacksCompat;
import com.android.systemui.shared.system.WindowManagerWrapper;
import java.util.StringJoiner;
@@ -96,16 +99,30 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> {
private static final int STATE_HANDLER_INVALIDATED = 1 << 7;
private static final int STATE_GESTURE_STARTED = 1 << 8;
private static final int STATE_GESTURE_CANCELLED = 1 << 9;
+ private static final int STATE_GESTURE_COMPLETED = 1 << 10;
// States for quick switch/scrub
- private static final int STATE_SWITCH_TO_SCREENSHOT_COMPLETE = 1 << 10;
- private static final int STATE_QUICK_SCRUB_START = 1 << 11;
- private static final int STATE_QUICK_SCRUB_END = 1 << 12;
+ private static final int STATE_CURRENT_TASK_FINISHED = 1 << 11;
+ private static final int STATE_QUICK_SCRUB_START = 1 << 12;
+ private static final int STATE_QUICK_SCRUB_END = 1 << 13;
+
+ private static final int STATE_CAPTURE_SCREENSHOT = 1 << 14;
+ private static final int STATE_SCREENSHOT_CAPTURED = 1 << 15;
+
+ private static final int STATE_RESUME_LAST_TASK = 1 << 16;
private static final int LAUNCHER_UI_STATES =
STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_DRAWN | STATE_ACTIVITY_MULTIPLIER_COMPLETE
| STATE_LAUNCHER_STARTED;
+ private static final int LONG_SWIPE_ENTER_STATE =
+ STATE_ACTIVITY_MULTIPLIER_COMPLETE | STATE_LAUNCHER_STARTED
+ | STATE_APP_CONTROLLER_RECEIVED;
+
+ private static final int LONG_SWIPE_START_STATE =
+ STATE_ACTIVITY_MULTIPLIER_COMPLETE | STATE_LAUNCHER_STARTED
+ | STATE_APP_CONTROLLER_RECEIVED | STATE_SCREENSHOT_CAPTURED;
+
// For debugging, keep in sync with above states
private static final String[] STATES = new String[] {
"STATE_LAUNCHER_PRESENT",
@@ -118,16 +135,21 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> {
"STATE_HANDLER_INVALIDATED",
"STATE_GESTURE_STARTED",
"STATE_GESTURE_CANCELLED",
- "STATE_SWITCH_TO_SCREENSHOT_COMPLETE",
- "STATE_QUICK_SWITCH",
+ "STATE_GESTURE_COMPLETED",
+ "STATE_CURRENT_TASK_FINISHED",
"STATE_QUICK_SCRUB_START",
- "STATE_QUICK_SCRUB_END"
+ "STATE_QUICK_SCRUB_END",
+ "STATE_CAPTURE_SCREENSHOT",
+ "STATE_SCREENSHOT_CAPTURED",
+ "STATE_RESUME_LAST_TASK",
};
- private static final long MAX_SWIPE_DURATION = 200;
- private static final long MIN_SWIPE_DURATION = 80;
+ public static final long MAX_SWIPE_DURATION = 350;
+ public static final long MIN_SWIPE_DURATION = 80;
private static final float MIN_PROGRESS_FOR_OVERVIEW = 0.5f;
+ private static final float SWIPE_DURATION_MULTIPLIER =
+ Math.min(1 / MIN_PROGRESS_FOR_OVERVIEW, 1 / (1 - MIN_PROGRESS_FOR_OVERVIEW));
private final ClipAnimationHelper mClipAnimationHelper = new ClipAnimationHelper();
@@ -142,13 +164,16 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> {
// visible.
private final AnimatedFloat mCurrentShift = new AnimatedFloat(this::updateFinalShift);
- private final MainThreadExecutor mMainExecutor = new MainThreadExecutor();
+ private final Handler mMainThreadHandler = new Handler(Looper.getMainLooper());
private final Context mContext;
- private final int mRunningTaskId;
private final ActivityControlHelper<T> mActivityControlHelper;
private final ActivityInitListener mActivityInitListener;
+ private final int mRunningTaskId;
+ private final RunningTaskInfo mRunningTaskInfo;
+ private ThumbnailData mTaskSnapshot;
+
private MultiStateCallback mStateCallback;
private AnimatorPlaybackController mLauncherTransitionController;
@@ -156,14 +181,16 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> {
private LayoutListener mLayoutListener;
private RecentsView mRecentsView;
private QuickScrubController mQuickScrubController;
+ private AnimationFactory mAnimationFactory = (t) -> { };
private Runnable mLauncherDrawnCallback;
private boolean mWasLauncherAlreadyVisible;
- private float mCurrentDisplacement;
private boolean mGestureStarted;
private int mLogAction = Touch.SWIPE;
+ private float mCurrentQuickScrubProgress;
+ private boolean mQuickScrubBlocked;
private @InteractionType int mInteractionType = INTERACTION_NORMAL;
@@ -174,19 +201,25 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> {
private final long mTouchTimeMs;
private long mLauncherFrameDrawnTime;
+ private boolean mBgLongSwipeMode = false;
+ private boolean mUiLongSwipeMode = false;
+ private float mLongSwipeDisplacement = 0;
+ private LongSwipeHelper mLongSwipeController;
+
WindowTransformSwipeHandler(RunningTaskInfo runningTaskInfo, Context context, long touchTimeMs,
ActivityControlHelper<T> controller) {
mContext = context;
+ mRunningTaskInfo = runningTaskInfo;
mRunningTaskId = runningTaskInfo.id;
mTouchTimeMs = touchTimeMs;
mActivityControlHelper = controller;
mActivityInitListener = mActivityControlHelper
.createActivityInitListener(this::onActivityInit);
+ initStateCallbacks();
// Register the input consumer on the UI thread, to ensure that it runs after any pending
// unregister calls
- mMainExecutor.execute(mInputConsumer::registerInputConsumer);
- initStateCallbacks();
+ executeOnUiThread(mInputConsumer::registerInputConsumer);
}
private void initStateCallbacks() {
@@ -203,45 +236,66 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> {
mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_DRAWN,
this::launcherFrameDrawn);
mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_GESTURE_STARTED,
- this::notifyGestureStarted);
+ this::notifyGestureStartedAsync);
mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_STARTED
| STATE_GESTURE_CANCELLED,
this::resetStateForAnimationCancel);
- mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_APP_CONTROLLER_RECEIVED
- | STATE_SCALED_CONTROLLER_APP,
+ mStateCallback.addCallback(STATE_LAUNCHER_STARTED | STATE_APP_CONTROLLER_RECEIVED,
+ this::sendRemoteAnimationsToAnimationFactory);
+
+ mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_SCALED_CONTROLLER_APP,
+ this::resumeLastTaskForQuickstep);
+ mStateCallback.addCallback(STATE_RESUME_LAST_TASK | STATE_APP_CONTROLLER_RECEIVED,
this::resumeLastTask);
+
mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_APP_CONTROLLER_RECEIVED
| STATE_ACTIVITY_MULTIPLIER_COMPLETE
- | STATE_SCALED_CONTROLLER_RECENTS,
+ | STATE_CAPTURE_SCREENSHOT,
this::switchToScreenshot);
+
+ mStateCallback.addCallback(STATE_SCREENSHOT_CAPTURED | STATE_GESTURE_COMPLETED
+ | STATE_SCALED_CONTROLLER_RECENTS,
+ this::finishCurrentTransitionToHome);
+
mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_APP_CONTROLLER_RECEIVED
| STATE_ACTIVITY_MULTIPLIER_COMPLETE
| STATE_SCALED_CONTROLLER_RECENTS
- | STATE_SWITCH_TO_SCREENSHOT_COMPLETE,
+ | STATE_CURRENT_TASK_FINISHED
+ | STATE_GESTURE_COMPLETED,
this::setupLauncherUiAfterSwipeUpAnimation);
- mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_SCALED_CONTROLLER_APP,
- this::reset);
-
mStateCallback.addCallback(STATE_HANDLER_INVALIDATED, this::invalidateHandler);
mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_HANDLER_INVALIDATED,
this::invalidateHandlerWithLauncher);
+ mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_HANDLER_INVALIDATED
+ | STATE_SCALED_CONTROLLER_APP,
+ this::notifyTransitionCancelled);
- mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_QUICK_SCRUB_START,
+ mStateCallback.addCallback(STATE_LAUNCHER_STARTED | STATE_QUICK_SCRUB_START,
this::onQuickScrubStart);
- mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_QUICK_SCRUB_START
+ mStateCallback.addCallback(STATE_LAUNCHER_STARTED | STATE_QUICK_SCRUB_START
| STATE_SCALED_CONTROLLER_RECENTS, this::onFinishedTransitionToQuickScrub);
- mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_SWITCH_TO_SCREENSHOT_COMPLETE
+ mStateCallback.addCallback(STATE_LAUNCHER_STARTED | STATE_CURRENT_TASK_FINISHED
| STATE_QUICK_SCRUB_END, this::switchToFinalAppAfterQuickScrub);
+
+ mStateCallback.addCallback(LONG_SWIPE_ENTER_STATE, this::checkLongSwipeCanEnter);
+ mStateCallback.addCallback(LONG_SWIPE_START_STATE, this::checkLongSwipeCanStart);
+ }
+
+ private void executeOnUiThread(Runnable action) {
+ if (Looper.myLooper() == mMainThreadHandler.getLooper()) {
+ action.run();
+ } else {
+ postAsyncCallback(mMainThreadHandler, action);
+ }
}
private void setStateOnUiThread(int stateFlag) {
- Handler handler = mMainExecutor.getHandler();
- if (Looper.myLooper() == handler.getLooper()) {
+ if (Looper.myLooper() == mMainThreadHandler.getLooper()) {
mStateCallback.setState(stateFlag);
} else {
- postAtFrontOfQueueAsynchronously(handler, () -> mStateCallback.setState(stateFlag));
+ postAsyncCallback(mMainThreadHandler, () -> mStateCallback.setState(stateFlag));
}
}
@@ -285,7 +339,11 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> {
mActivity = activity;
// Override the visibility of the activity until the gesture actually starts and we swipe
// up, or until we transition home and the home animation is composed
- mActivity.setForceInvisible(true);
+ if (alreadyOnHome) {
+ mActivity.clearForceInvisibleFlag(INVISIBLE_BY_STATE_HANDLER);
+ } else {
+ mActivity.addForceInvisibleFlag(INVISIBLE_BY_STATE_HANDLER);
+ }
mRecentsView = activity.getOverviewPanel();
mQuickScrubController = mRecentsView.getQuickScrubController();
@@ -304,33 +362,27 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> {
if (mActivity != activity) {
return;
}
- if ((mStateCallback.getState() & STATE_HANDLER_INVALIDATED) != 0) {
+ if (mStateCallback.hasStates(STATE_HANDLER_INVALIDATED)) {
return;
}
- mStateCallback.setState(STATE_LAUNCHER_STARTED);
- mActivityControlHelper.prepareRecentsUI(mActivity, mWasLauncherAlreadyVisible);
+ mAnimationFactory = mActivityControlHelper.prepareRecentsUI(mActivity,
+ mWasLauncherAlreadyVisible, this::onAnimatorPlaybackControllerCreated);
AbstractFloatingView.closeAllOpenViews(activity, mWasLauncherAlreadyVisible);
if (mWasLauncherAlreadyVisible) {
- mLauncherTransitionController = mActivityControlHelper
- .createControllerForVisibleActivity(activity);
- mLauncherTransitionController.dispatchOnStart();
- mLauncherTransitionController.setPlayFraction(mCurrentShift.value);
-
mStateCallback.setState(STATE_ACTIVITY_MULTIPLIER_COMPLETE | STATE_LAUNCHER_DRAWN);
} else {
TraceHelper.beginSection("WTS-init");
- // TODO: Implement a better animation for fading in
- View rootView = activity.getRootView();
- rootView.setAlpha(0);
- rootView.getViewTreeObserver().addOnDrawListener(new OnDrawListener() {
+ View dragLayer = activity.getDragLayer();
+ mActivityControlHelper.getAlphaProperty(activity).setValue(0);
+ dragLayer.getViewTreeObserver().addOnDrawListener(new OnDrawListener() {
@Override
public void onDraw() {
TraceHelper.endSection("WTS-init", "Launcher frame is drawn");
- rootView.post(() ->
- rootView.getViewTreeObserver().removeOnDrawListener(this));
+ dragLayer.post(() ->
+ dragLayer.getViewTreeObserver().removeOnDrawListener(this));
if (activity != mActivity) {
return;
}
@@ -341,8 +393,10 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> {
}
mRecentsView.showTask(mRunningTaskId);
- mRecentsView.setFirstTaskIconScaledDown(true /* isScaledDown */, false /* animate */);
+ mRecentsView.setRunningTaskHidden(true);
+ mRecentsView.setRunningTaskIconScaledDown(true /* isScaledDown */, false /* animate */);
mLayoutListener.open();
+ mStateCallback.setState(STATE_LAUNCHER_STARTED);
}
public void setLauncherOnDrawCallback(Runnable callback) {
@@ -350,15 +404,22 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> {
}
private void launcherFrameDrawn() {
- View rootView = mActivity.getRootView();
- if (rootView.getAlpha() < 1) {
+ AlphaProperty property = mActivityControlHelper.getAlphaProperty(mActivity);
+ if (property.getValue() < 1) {
if (mGestureStarted) {
final MultiStateCallback callback = mStateCallback;
- rootView.animate().alpha(1)
- .setDuration(getFadeInDuration())
- .withEndAction(() -> callback.setState(STATE_ACTIVITY_MULTIPLIER_COMPLETE));
+ ObjectAnimator animator = ObjectAnimator.ofFloat(
+ property, MultiValueAlpha.VALUE, 1);
+ animator.setDuration(getFadeInDuration()).addListener(
+ new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ callback.setState(STATE_ACTIVITY_MULTIPLIER_COMPLETE);
+ }
+ });
+ animator.start();
} else {
- rootView.setAlpha(1);
+ property.setValue(1);
mStateCallback.setState(STATE_ACTIVITY_MULTIPLIER_COMPLETE);
}
}
@@ -368,15 +429,16 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> {
mLauncherFrameDrawnTime = SystemClock.uptimeMillis();
}
+ private void sendRemoteAnimationsToAnimationFactory() {
+ mAnimationFactory.onRemoteAnimationReceived(mRecentsAnimationWrapper.targetSet);
+ }
+
private void initializeLauncherAnimationController() {
mLayoutListener.setHandler(this);
- onLauncherLayoutChanged();
-
- final long transitionDelay = mLauncherFrameDrawnTime - mTouchTimeMs;
- SysuiEventLogger.writeDummyRecentsTransition(transitionDelay);
+ buildAnimationController();
if (LatencyTrackerCompat.isEnabled(mContext)) {
- LatencyTrackerCompat.logToggleRecents((int) transitionDelay);
+ LatencyTrackerCompat.logToggleRecents((int) (mLauncherFrameDrawnTime - mTouchTimeMs));
}
}
@@ -391,110 +453,108 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> {
}
mInteractionType = interactionType;
- setStateOnUiThread(STATE_QUICK_SCRUB_START);
+ setStateOnUiThread(STATE_QUICK_SCRUB_START | STATE_GESTURE_COMPLETED);
// Start the window animation without waiting for launcher.
- animateToProgress(1f, QUICK_SCRUB_START_DURATION);
+ animateToProgress(1f, QUICK_SCRUB_FROM_APP_START_DURATION, LINEAR);
}
@WorkerThread
public void updateDisplacement(float displacement) {
- mCurrentDisplacement = displacement;
-
- float translation = Utilities.boundToRange(-mCurrentDisplacement, 0, mTransitionDragLength);
- float shift = mTransitionDragLength == 0 ? 0 : translation / mTransitionDragLength;
- mCurrentShift.updateValue(shift);
+ // We are moving in the negative x/y direction
+ displacement = -displacement;
+ if (displacement > mTransitionDragLength) {
+ mCurrentShift.updateValue(1);
+
+ if (!mBgLongSwipeMode) {
+ mBgLongSwipeMode = true;
+ executeOnUiThread(this::onLongSwipeEnabledUi);
+ }
+ mLongSwipeDisplacement = displacement - mTransitionDragLength;
+ executeOnUiThread(this::onLongSwipeDisplacementUpdated);
+ } else {
+ if (mBgLongSwipeMode) {
+ mBgLongSwipeMode = false;
+ executeOnUiThread(this::onLongSwipeDisabledUi);
+ }
+ float translation = Math.max(displacement, 0);
+ float shift = mTransitionDragLength == 0 ? 0 : translation / mTransitionDragLength;
+ mCurrentShift.updateValue(shift);
+ }
}
/**
* Called by {@link #mLayoutListener} when launcher layout changes
*/
- public void onLauncherLayoutChanged() {
+ public void buildAnimationController() {
initTransitionEndpoints(mActivity.getDeviceProfile());
+ mAnimationFactory.createActivityController(mTransitionDragLength);
+ }
- if (!mWasLauncherAlreadyVisible) {
- mLauncherTransitionController = mActivityControlHelper
- .createControllerForHiddenActivity(mActivity, mTransitionDragLength);
- mLauncherTransitionController.setPlayFraction(mCurrentShift.value);
- }
+ private void onAnimatorPlaybackControllerCreated(AnimatorPlaybackController anim) {
+ mLauncherTransitionController = anim;
+ mLauncherTransitionController.dispatchOnStart();
+ mLauncherTransitionController.setPlayFraction(mCurrentShift.value);
}
@WorkerThread
private void updateFinalShift() {
float shift = mCurrentShift.value;
- synchronized (mRecentsAnimationWrapper) {
- if (mRecentsAnimationWrapper.controller != null) {
- Interpolator interpolator = mInteractionType == INTERACTION_QUICK_SCRUB
- ? ACCEL_2 : LINEAR;
- float interpolated = interpolator.getInterpolation(shift);
- mClipAnimationHelper.applyTransform(mRecentsAnimationWrapper.targets, interpolated);
+ RecentsAnimationControllerCompat controller = mRecentsAnimationWrapper.getController();
+ if (controller != null) {
+ mClipAnimationHelper.applyTransform(mRecentsAnimationWrapper.targetSet, shift);
+
+ // TODO: This logic is spartanic!
+ boolean passedThreshold = shift > 0.12f;
+ mRecentsAnimationWrapper.setAnimationTargetsBehindSystemBars(!passedThreshold);
+ if (mActivityControlHelper.shouldMinimizeSplitScreen()) {
+ mRecentsAnimationWrapper.setSplitScreenMinimizedForTransaction(passedThreshold);
}
}
- if (mLauncherTransitionController != null) {
- Runnable runOnUi = () -> {
- if (mLauncherTransitionController == null) {
- return;
- }
- mLauncherTransitionController.setPlayFraction(shift);
-
- // Make sure the window follows the first task if it moves, e.g. during quick scrub.
- View firstTask = mRecentsView.getPageAt(0);
- // The first task may be null if we are swiping up from a task that does not
- // appear in the list (ie. the assistant)
- if (firstTask != null) {
- int scrollForFirstTask = mRecentsView.getScrollForPage(0);
- int offsetFromFirstTask = (scrollForFirstTask - mRecentsView.getScrollX());
- mClipAnimationHelper.offsetTarget(firstTask.getScaleX(),
- offsetFromFirstTask + firstTask.getTranslationX(),
- mRecentsView.getTranslationY());
- }
- if (mRecentsAnimationWrapper.controller != null) {
- // TODO: This logic is spartanic!
- boolean passedThreshold = shift > 0.12f;
- mRecentsAnimationWrapper.setAnimationTargetsBehindSystemBars(!passedThreshold);
- mRecentsAnimationWrapper.setSplitScreenMinimizedForTransaction(passedThreshold);
- }
- };
- if (Looper.getMainLooper() == Looper.myLooper()) {
- runOnUi.run();
- } else {
- // The fling operation completed even before the launcher was drawn
- mMainExecutor.execute(runOnUi);
- }
+ executeOnUiThread(this::updateFinalShiftUi);
+ }
+
+ private void updateFinalShiftUi() {
+ if (mLauncherTransitionController == null) {
+ return;
}
+ mLauncherTransitionController.setPlayFraction(mCurrentShift.value);
}
public void onRecentsAnimationStart(RecentsAnimationControllerCompat controller,
- RemoteAnimationTargetCompat[] apps, Rect homeContentInsets, Rect minimizedHomeBounds) {
- if (apps != null) {
- // Use the top closing app to determine the insets for the animation
- for (RemoteAnimationTargetCompat target : apps) {
- if (target.mode == MODE_CLOSING) {
- DeviceProfile dp = LauncherAppState.getIDP(mContext).getDeviceProfile(mContext);
- final Rect homeStackBounds;
-
- if (minimizedHomeBounds != null) {
- homeStackBounds = minimizedHomeBounds;
- dp = dp.getMultiWindowProfile(mContext,
- new Point(minimizedHomeBounds.width(), minimizedHomeBounds.height()));
- dp.updateInsets(homeContentInsets);
- } else {
- homeStackBounds = new Rect(0, 0, dp.widthPx, dp.heightPx);
- // TODO: Workaround for an existing issue where the home content insets are
- // not valid immediately after rotation, just use the stable insets for now
- Rect insets = new Rect();
- WindowManagerWrapper.getInstance().getStableInsets(insets);
- dp.updateInsets(insets);
- }
+ RemoteAnimationTargetSet targets, Rect homeContentInsets, Rect minimizedHomeBounds) {
+ LauncherAppState appState = LauncherAppState.getInstanceNoCreate();
+ InvariantDeviceProfile idp = appState == null ?
+ new InvariantDeviceProfile(mContext) : appState.getInvariantDeviceProfile();
+ DeviceProfile dp = idp.getDeviceProfile(mContext);
+ final Rect overviewStackBounds;
+ RemoteAnimationTargetCompat runningTaskTarget = targets.findTask(mRunningTaskId);
+
+ if (minimizedHomeBounds != null && runningTaskTarget != null) {
+ overviewStackBounds = mActivityControlHelper
+ .getOverviewWindowBounds(minimizedHomeBounds, runningTaskTarget);
+ dp = dp.getMultiWindowProfile(mContext,
+ new Point(minimizedHomeBounds.width(), minimizedHomeBounds.height()));
+ dp.updateInsets(homeContentInsets);
+ } else {
+ overviewStackBounds = new Rect(0, 0, dp.widthPx, dp.heightPx);
+ // If we are not in multi-window mode, home insets should be same as system insets.
+ Rect insets = new Rect();
+ WindowManagerWrapper.getInstance().getStableInsets(insets);
+ dp = dp.copy(mContext);
+ dp.updateInsets(insets);
+ }
+ dp.updateIsSeascape(mContext.getSystemService(WindowManager.class));
- mClipAnimationHelper.updateSource(homeStackBounds, target);
- initTransitionEndpoints(dp);
- }
- }
+ if (runningTaskTarget != null) {
+ mClipAnimationHelper.updateSource(overviewStackBounds, runningTaskTarget);
}
- mRecentsAnimationWrapper.setController(controller, apps);
+ mClipAnimationHelper.prepareAnimation(false /* isOpening */);
+ initTransitionEndpoints(dp);
+
+ mRecentsAnimationWrapper.setController(controller, targets);
setStateOnUiThread(STATE_APP_CONTROLLER_RECEIVED);
}
@@ -505,9 +565,10 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> {
}
public void onGestureStarted() {
- notifyGestureStarted();
+ notifyGestureStartedAsync();
setStateOnUiThread(STATE_GESTURE_STARTED);
mGestureStarted = true;
+ mRecentsAnimationWrapper.hideCurrentInputMethod();
mRecentsAnimationWrapper.enableInputConsumer();
}
@@ -515,43 +576,56 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> {
* Notifies the launcher that the swipe gesture has started. This can be called multiple times
* on both background and UI threads
*/
- private void notifyGestureStarted() {
+ @AnyThread
+ private void notifyGestureStartedAsync() {
final T curActivity = mActivity;
if (curActivity != null) {
// Once the gesture starts, we can no longer transition home through the button, so
// reset the force override of the activity visibility
- mActivity.setForceInvisible(false);
- mActivityControlHelper.onQuickstepGestureStarted(
- curActivity, mWasLauncherAlreadyVisible);
+ mActivity.clearForceInvisibleFlag(INVISIBLE_BY_STATE_HANDLER);
}
}
@WorkerThread
public void onGestureEnded(float endVelocity) {
- Resources res = mContext.getResources();
- float flingThreshold = res.getDimension(R.dimen.quickstep_fling_threshold_velocity);
- boolean isFling = Math.abs(endVelocity) > flingThreshold;
+ float flingThreshold = mContext.getResources()
+ .getDimension(R.dimen.quickstep_fling_threshold_velocity);
+ boolean isFling = mGestureStarted && Math.abs(endVelocity) > flingThreshold;
+ setStateOnUiThread(STATE_GESTURE_COMPLETED);
+
+ mLogAction = isFling ? Touch.FLING : Touch.SWIPE;
+
+ if (mBgLongSwipeMode) {
+ executeOnUiThread(() -> onLongSwipeGestureFinishUi(endVelocity, isFling));
+ } else {
+ handleNormalGestureEnd(endVelocity, isFling);
+ }
+ }
+ private void handleNormalGestureEnd(float endVelocity, boolean isFling) {
long duration = MAX_SWIPE_DURATION;
final float endShift;
if (!isFling) {
- endShift = mCurrentShift.value >= MIN_PROGRESS_FOR_OVERVIEW ? 1 : 0;
- mLogAction = Touch.SWIPE;
+ endShift = mCurrentShift.value >= MIN_PROGRESS_FOR_OVERVIEW && mGestureStarted ? 1 : 0;
+ long expectedDuration = Math.abs(Math.round((endShift - mCurrentShift.value)
+ * MAX_SWIPE_DURATION * SWIPE_DURATION_MULTIPLIER));
+ duration = Math.min(MAX_SWIPE_DURATION, expectedDuration);
} else {
endShift = endVelocity < 0 ? 1 : 0;
- float minFlingVelocity = res.getDimension(R.dimen.quickstep_fling_min_velocity);
+ float minFlingVelocity = mContext.getResources()
+ .getDimension(R.dimen.quickstep_fling_min_velocity);
if (Math.abs(endVelocity) > minFlingVelocity && mTransitionDragLength > 0) {
float distanceToTravel = (endShift - mCurrentShift.value) * mTransitionDragLength;
// we want the page's snap velocity to approximately match the velocity at
// which the user flings, so we scale the duration by a value near to the
- // derivative of the scroll interpolator at zero, ie. 5.
- duration = 5 * Math.round(1000 * Math.abs(distanceToTravel / endVelocity));
+ // derivative of the scroll interpolator at zero, ie. 2.
+ long baseDuration = Math.round(1000 * Math.abs(distanceToTravel / endVelocity));
+ duration = Math.min(MAX_SWIPE_DURATION, 2 * baseDuration);
}
- mLogAction = Touch.FLING;
}
- animateToProgress(endShift, duration);
+ animateToProgress(endShift, duration, DEACCEL);
}
private void doLogGesture(boolean toLauncher) {
@@ -571,24 +645,31 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> {
}
/** Animates to the given progress, where 0 is the current app and 1 is overview. */
- private void animateToProgress(float progress, long duration) {
+ private void animateToProgress(float progress, long duration, Interpolator interpolator) {
mIsGoingToHome = Float.compare(progress, 1) == 0;
ObjectAnimator anim = mCurrentShift.animateToValue(progress).setDuration(duration);
- anim.setInterpolator(Interpolators.SCROLL);
+ anim.setInterpolator(interpolator);
anim.addListener(new AnimationSuccessListener() {
@Override
public void onAnimationSuccess(Animator animator) {
- setStateOnUiThread(mIsGoingToHome ?
- STATE_SCALED_CONTROLLER_RECENTS : STATE_SCALED_CONTROLLER_APP);
+ setStateOnUiThread(mIsGoingToHome
+ ? (STATE_SCALED_CONTROLLER_RECENTS | STATE_CAPTURE_SCREENSHOT)
+ : STATE_SCALED_CONTROLLER_APP);
}
});
anim.start();
}
@UiThread
+ private void resumeLastTaskForQuickstep() {
+ setStateOnUiThread(STATE_RESUME_LAST_TASK);
+ doLogGesture(false /* toLauncher */);
+ reset();
+ }
+
+ @UiThread
private void resumeLastTask() {
mRecentsAnimationWrapper.finish(false /* toHome */, null);
- doLogGesture(false /* toLauncher */);
}
public void reset() {
@@ -608,13 +689,20 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> {
mActivityInitListener.unregister();
mInputConsumer.unregisterInputConsumer();
+ mTaskSnapshot = null;
}
private void invalidateHandlerWithLauncher() {
mLauncherTransitionController = null;
mLayoutListener.finish();
+ mActivityControlHelper.getAlphaProperty(mActivity).setValue(1);
+
+ mRecentsView.setRunningTaskHidden(false);
+ mRecentsView.setRunningTaskIconScaledDown(false /* isScaledDown */, false /* animate */);
+ }
- mRecentsView.setFirstTaskIconScaledDown(false /* isScaledDown */, false /* animate */);
+ private void notifyTransitionCancelled() {
+ mAnimationFactory.onTransitionCancelled();
}
private void resetStateForAnimationCancel() {
@@ -630,40 +718,38 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> {
private void switchToScreenshot() {
boolean finishTransitionPosted = false;
- final Runnable finishTransitionRunnable = () -> {
- synchronized (mRecentsAnimationWrapper) {
- mRecentsAnimationWrapper.finish(true /* toHome */,
- () -> setStateOnUiThread(STATE_SWITCH_TO_SCREENSHOT_COMPLETE));
+ RecentsAnimationControllerCompat controller = mRecentsAnimationWrapper.getController();
+ if (controller != null) {
+ // Update the screenshot of the task
+ if (mTaskSnapshot == null) {
+ mTaskSnapshot = controller.screenshotTask(mRunningTaskId);
}
- };
-
- synchronized (mRecentsAnimationWrapper) {
- if (mRecentsAnimationWrapper.controller != null) {
- TransactionCompat transaction = new TransactionCompat();
- for (RemoteAnimationTargetCompat app : mRecentsAnimationWrapper.targets) {
- if (app.mode == MODE_CLOSING) {
- // Update the screenshot of the task
- ThumbnailData thumbnail =
- mRecentsAnimationWrapper.controller.screenshotTask(app.taskId);
- TaskView taskView = mRecentsView.updateThumbnail(app.taskId, thumbnail);
- if (taskView != null) {
- taskView.setAlpha(1);
- // Defer finishing the animation until the next launcher frame with the
- // new thumbnail
- mActivityControlHelper.executeOnNextDraw(mActivity, taskView,
- finishTransitionRunnable);
- finishTransitionPosted = true;
- }
+ TaskView taskView = mRecentsView.updateThumbnail(mRunningTaskId, mTaskSnapshot);
+ mRecentsView.setRunningTaskHidden(false);
+ if (taskView != null) {
+ // Defer finishing the animation until the next launcher frame with the
+ // new thumbnail
+ finishTransitionPosted = new WindowCallbacksCompat(taskView) {
+
+ @Override
+ public void onPostDraw(Canvas canvas) {
+ setStateOnUiThread(STATE_SCREENSHOT_CAPTURED);
+ detach();
}
- }
- transaction.apply();
+ }.attach();
}
}
if (!finishTransitionPosted) {
- // If we haven't posted the transition end runnable, run it now
- finishTransitionRunnable.run();
+ // If we haven't posted a draw callback, set the state immediately.
+ setStateOnUiThread(STATE_SCREENSHOT_CAPTURED);
+ }
+ }
+
+ private void finishCurrentTransitionToHome() {
+ synchronized (mRecentsAnimationWrapper) {
+ mRecentsAnimationWrapper.finish(true /* toHome */,
+ () -> setStateOnUiThread(STATE_CURRENT_TASK_FINISHED));
}
- doLogGesture(true /* toLauncher */);
}
private void setupLauncherUiAfterSwipeUpAnimation() {
@@ -674,27 +760,64 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> {
mActivityControlHelper.onSwipeUpComplete(mActivity);
// Animate the first icon.
- mRecentsView.setFirstTaskIconScaledDown(false /* isScaledDown */, true /* animate */);
-
+ mRecentsView.setRunningTaskIconScaledDown(false /* isScaledDown */, true /* animate */);
mRecentsView.setSwipeDownShouldLaunchApp(true);
+ RecentsModel.getInstance(mContext).onOverviewShown(false, TAG);
+
+ doLogGesture(true /* toLauncher */);
reset();
}
private void onQuickScrubStart() {
- mActivityControlHelper.onQuickInteractionStart(mActivity, mWasLauncherAlreadyVisible);
- mQuickScrubController.onQuickScrubStart(false);
+ if (!mQuickScrubController.prepareQuickScrub(TAG)) {
+ mQuickScrubBlocked = true;
+ setStateOnUiThread(STATE_RESUME_LAST_TASK | STATE_HANDLER_INVALIDATED);
+ return;
+ }
+ if (mLauncherTransitionController != null) {
+ mLauncherTransitionController.getAnimationPlayer().end();
+ mLauncherTransitionController = null;
+ }
+
+ mActivityControlHelper.onQuickInteractionStart(mActivity, mRunningTaskInfo, false);
+
+ // Inform the last progress in case we skipped before.
+ mQuickScrubController.onQuickScrubProgress(mCurrentQuickScrubProgress);
+
+ // Make sure the window follows the first task if it moves, e.g. during quick scrub.
+ TaskView firstTask = mRecentsView.getPageAt(0);
+ // The first task may be null if we are swiping up from a task that does not
+ // appear in the list (i.e. the assistant)
+ if (firstTask != null) {
+ int scrollForFirstTask = mRecentsView.getScrollForPage(0);
+ int scrollForSecondTask = mRecentsView.getChildCount() > 1
+ ? mRecentsView.getScrollForPage(1) : scrollForFirstTask;
+ int offsetFromFirstTask = scrollForFirstTask - scrollForSecondTask;
+ final float interpolation;
+ if (mRecentsView.getWidth() == 0) {
+ interpolation = scrollForSecondTask == scrollForFirstTask ? 0 : 1;
+ } else {
+ interpolation = (float) offsetFromFirstTask / (mRecentsView.getWidth() / 2);
+ }
+ mClipAnimationHelper.offsetTarget(
+ firstTask.getCurveScaleForInterpolation(interpolation), offsetFromFirstTask,
+ mActivityControlHelper.getTranslationYForQuickScrub(mActivity),
+ QuickScrubController.QUICK_SCRUB_START_INTERPOLATOR);
+ }
}
private void onFinishedTransitionToQuickScrub() {
+ if (mQuickScrubBlocked) {
+ return;
+ }
mQuickScrubController.onFinishedTransitionToQuickScrub();
}
public void onQuickScrubProgress(float progress) {
- if (Looper.myLooper() != Looper.getMainLooper() || mQuickScrubController == null) {
- // TODO: We can still get progress events while launcher is not ready on the worker
- // thread. Keep track of last received progress and apply that progress when launcher
- // is ready
+ mCurrentQuickScrubProgress = progress;
+ if (Looper.myLooper() != Looper.getMainLooper() || mQuickScrubController == null
+ || mQuickScrubBlocked) {
return;
}
mQuickScrubController.onQuickScrubProgress(progress);
@@ -705,6 +828,9 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> {
}
private void switchToFinalAppAfterQuickScrub() {
+ if (mQuickScrubBlocked) {
+ return;
+ }
mQuickScrubController.onQuickScrubEnd();
// Normally this is handled in reset(), but since we are still scrubbing after the
@@ -736,4 +862,72 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> {
public void setGestureEndCallback(Runnable gestureEndCallback) {
mGestureEndCallback = gestureEndCallback;
}
+
+ // Handling long swipe
+ private void onLongSwipeEnabledUi() {
+ mUiLongSwipeMode = true;
+ checkLongSwipeCanEnter();
+ checkLongSwipeCanStart();
+ }
+
+ private void onLongSwipeDisabledUi() {
+ mUiLongSwipeMode = false;
+
+ if (mLongSwipeController != null) {
+ mLongSwipeController.destroy();
+
+ // Rebuild animations
+ buildAnimationController();
+ }
+ }
+
+ private void onLongSwipeDisplacementUpdated() {
+ if (!mUiLongSwipeMode || mLongSwipeController == null) {
+ return;
+ }
+
+ mLongSwipeController.onMove(mLongSwipeDisplacement);
+ }
+
+ private void checkLongSwipeCanEnter() {
+ if (!mUiLongSwipeMode || !mStateCallback.hasStates(LONG_SWIPE_ENTER_STATE)
+ || !mActivityControlHelper.supportsLongSwipe(mActivity)) {
+ return;
+ }
+
+ // We are entering long swipe mode, make sure the screen shot is captured.
+ mStateCallback.setState(STATE_CAPTURE_SCREENSHOT);
+
+ }
+
+ private void checkLongSwipeCanStart() {
+ if (!mUiLongSwipeMode || !mStateCallback.hasStates(LONG_SWIPE_START_STATE)
+ || !mActivityControlHelper.supportsLongSwipe(mActivity)) {
+ return;
+ }
+
+ RemoteAnimationTargetSet targetSet = mRecentsAnimationWrapper.targetSet;
+ if (targetSet == null) {
+ // This can happen when cancelAnimation comes on the background thread, while we are
+ // processing the long swipe on the UI thread.
+ return;
+ }
+
+ mLongSwipeController = mActivityControlHelper.getLongSwipeController(
+ mActivity, mRecentsAnimationWrapper.targetSet);
+ onLongSwipeDisplacementUpdated();
+ }
+
+ private void onLongSwipeGestureFinishUi(float velocity, boolean isFling) {
+ if (!mUiLongSwipeMode || mLongSwipeController == null) {
+ mUiLongSwipeMode = false;
+ handleNormalGestureEnd(velocity, isFling);
+ return;
+ }
+ mUiLongSwipeMode = false;
+ finishCurrentTransitionToHome();
+ mLongSwipeController.end(velocity, isFling,
+ () -> setStateOnUiThread(STATE_HANDLER_INVALIDATED));
+
+ }
}
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
index 4ed165699..9e2de3395 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
@@ -18,7 +18,6 @@ package com.android.quickstep.fallback;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Rect;
-import android.support.annotation.AnyThread;
import android.util.AttributeSet;
import android.view.View;
@@ -36,12 +35,12 @@ public class FallbackRecentsView extends RecentsView<RecentsActivity> {
public FallbackRecentsView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setOverviewStateEnabled(true);
- updateEmptyMessage();
+ getQuickScrubController().onFinishedTransitionToQuickScrub();
}
@Override
protected void onAllTasksRemoved() {
- mActivity.finish();
+ mActivity.startHome();
}
@Override
@@ -64,11 +63,12 @@ public class FallbackRecentsView extends RecentsView<RecentsActivity> {
@Override
protected void getTaskSize(DeviceProfile dp, Rect outRect) {
- LayoutUtils.calculateTaskSize(getContext(), dp, 0, outRect);
+ LayoutUtils.calculateFallbackTaskSize(getContext(), dp, outRect);
}
- @AnyThread
- public static void getPageRect(DeviceProfile grid, Context context, Rect outRect) {
- LayoutUtils.calculateTaskSize(context, grid, 0, outRect);
+ @Override
+ public boolean shouldUseMultiWindowTaskSizeStrategy() {
+ // Just use the activity task size for multi-window as well.
+ return false;
}
}
diff --git a/quickstep/src/com/android/quickstep/fallback/RecentsRootView.java b/quickstep/src/com/android/quickstep/fallback/RecentsRootView.java
index 7aaa88c38..ca8c2520c 100644
--- a/quickstep/src/com/android/quickstep/fallback/RecentsRootView.java
+++ b/quickstep/src/com/android/quickstep/fallback/RecentsRootView.java
@@ -17,6 +17,7 @@ package com.android.quickstep.fallback;
import android.annotation.TargetApi;
import android.content.Context;
+import android.graphics.Point;
import android.graphics.Rect;
import android.util.AttributeSet;
@@ -31,18 +32,37 @@ public class RecentsRootView extends BaseDragLayer<RecentsActivity> {
private final RecentsActivity mActivity;
+ private final Point mLastKnownSize = new Point(10, 10);
+
public RecentsRootView(Context context, AttributeSet attrs) {
- super(context, attrs);
+ super(context, attrs, 1 /* alphaChannelCount */);
mActivity = (RecentsActivity) BaseActivity.fromContext(context);
setSystemUiVisibility(SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| SYSTEM_UI_FLAG_LAYOUT_STABLE);
}
+ public Point getLastKnownSize() {
+ return mLastKnownSize;
+ }
+
public void setup() {
mControllers = new TouchController[] { new RecentsTaskController(mActivity) };
}
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ // Check size changes before the actual measure, to avoid multiple measure calls.
+ int width = MeasureSpec.getSize(widthMeasureSpec);
+ int height = MeasureSpec.getSize(heightMeasureSpec);
+ if (mLastKnownSize.x != width || mLastKnownSize.y != height) {
+ mLastKnownSize.set(width, height);
+ mActivity.onRootViewSizeChanged();
+ }
+
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ }
+
@TargetApi(23)
@Override
protected boolean fitSystemWindows(Rect insets) {
@@ -62,4 +82,9 @@ public class RecentsRootView extends BaseDragLayer<RecentsActivity> {
setBackground(insets.top == 0 ? null
: Themes.getAttrDrawable(getContext(), R.attr.workspaceStatusBarScrim));
}
+
+ public void dispatchInsets() {
+ mActivity.getDeviceProfile().updateInsets(mInsets);
+ super.setInsets(mInsets);
+ }
} \ No newline at end of file
diff --git a/quickstep/src/com/android/quickstep/logging/UserEventDispatcherExtension.java b/quickstep/src/com/android/quickstep/logging/UserEventDispatcherExtension.java
new file mode 100644
index 000000000..04153cc99
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/logging/UserEventDispatcherExtension.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep.logging;
+
+import android.util.Log;
+
+import static com.android.launcher3.logging.LoggerUtils.newAction;
+import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
+import static com.android.launcher3.logging.LoggerUtils.newLauncherEvent;
+import static com.android.launcher3.userevent.nano.LauncherLogProto.ControlType.CANCEL_TARGET;
+import static com.android.systemui.shared.system.LauncherEventUtil.VISIBLE;
+import static com.android.systemui.shared.system.LauncherEventUtil.DISMISS;
+import static com.android.systemui.shared.system.LauncherEventUtil.RECENTS_QUICK_SCRUB_ONBOARDING_TIP;
+import static com.android.systemui.shared.system.LauncherEventUtil.RECENTS_SWIPE_UP_ONBOARDING_TIP;
+
+import com.android.launcher3.logging.UserEventDispatcher;
+import com.android.launcher3.model.nano.LauncherDumpProto;
+import com.android.launcher3.userevent.nano.LauncherLogExtensions;
+import com.android.launcher3.userevent.nano.LauncherLogProto;
+import com.android.systemui.shared.system.LauncherEventUtil;
+import com.android.systemui.shared.system.MetricsLoggerCompat;
+
+/**
+ * This class handles AOSP MetricsLogger function calls and logging around
+ * quickstep interactions.
+ */
+public class UserEventDispatcherExtension extends UserEventDispatcher {
+
+ private static final String TAG = "UserEventDispatcher";
+
+ public void logStateChangeAction(int action, int dir, int srcChildTargetType,
+ int srcParentContainerType, int dstContainerType,
+ int pageIndex) {
+ new MetricsLoggerCompat().visibility(MetricsLoggerCompat.OVERVIEW_ACTIVITY,
+ dstContainerType == LauncherLogProto.ContainerType.TASKSWITCHER);
+ super.logStateChangeAction(action, dir, srcChildTargetType, srcParentContainerType,
+ dstContainerType, pageIndex);
+ }
+
+ public void logActionTip(int actionType, int viewType) {
+ LauncherLogProto.Action action = new LauncherLogProto.Action();
+ LauncherLogProto.Target target = new LauncherLogProto.Target();
+ switch(actionType) {
+ case VISIBLE:
+ action.type = LauncherLogProto.Action.Type.TIP;
+ target.type = LauncherLogProto.Target.Type.CONTAINER;
+ target.containerType = LauncherLogProto.ContainerType.TIP;
+ break;
+ case DISMISS:
+ action.type = LauncherLogProto.Action.Type.TOUCH;
+ action.touch = LauncherLogProto.Action.Touch.TAP;
+ target.type = LauncherLogProto.Target.Type.CONTROL;
+ target.controlType = CANCEL_TARGET;
+ break;
+ default:
+ Log.e(TAG, "Unexpected action type = " + actionType);
+ }
+
+ switch(viewType) {
+ case RECENTS_QUICK_SCRUB_ONBOARDING_TIP:
+ target.tipType = LauncherLogProto.TipType.QUICK_SCRUB_TEXT;
+ break;
+ case RECENTS_SWIPE_UP_ONBOARDING_TIP:
+ target.tipType = LauncherLogProto.TipType.SWIPE_UP_TEXT;
+ break;
+ default:
+ Log.e(TAG, "Unexpected viewType = " + viewType);
+ }
+ LauncherLogProto.LauncherEvent event = newLauncherEvent(action, target);
+ dispatchUserEvent(event, null);
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/util/ClipAnimationHelper.java b/quickstep/src/com/android/quickstep/util/ClipAnimationHelper.java
index 493e9e238..8c7f104a6 100644
--- a/quickstep/src/com/android/quickstep/util/ClipAnimationHelper.java
+++ b/quickstep/src/com/android/quickstep/util/ClipAnimationHelper.java
@@ -15,21 +15,44 @@
*/
package com.android.quickstep.util;
+import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.quickstep.QuickScrubController.QUICK_SCRUB_TRANSLATION_Y_FACTOR;
import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
+import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_OPENING;
+import android.annotation.TargetApi;
+import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Matrix.ScaleToFit;
+import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
+import android.os.Build;
+import android.os.RemoteException;
+import android.support.annotation.Nullable;
+import android.view.animation.Interpolator;
+import com.android.launcher3.BaseDraggingActivity;
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.R;
import com.android.launcher3.Utilities;
+import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.views.BaseDragLayer;
+import com.android.quickstep.RecentsModel;
+import com.android.quickstep.views.RecentsView;
+import com.android.quickstep.views.TaskThumbnailView;
+import com.android.systemui.shared.recents.ISystemUiProxy;
import com.android.systemui.shared.recents.utilities.RectFEvaluator;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
import com.android.systemui.shared.system.TransactionCompat;
+import com.android.systemui.shared.system.WindowManagerWrapper;
+
+import java.util.function.BiConsumer;
/**
* Utility class to handle window clip animation
*/
+@TargetApi(Build.VERSION_CODES.P)
public class ClipAnimationHelper {
// The bounds of the source app in device coordinates
@@ -40,8 +63,8 @@ public class ClipAnimationHelper {
private final RectF mSourceRect = new RectF();
// The bounds of the task view in launcher window coordinates
private final RectF mTargetRect = new RectF();
- // Doesn't change after initialized, used as an anchor when changing mTargetRect
- private final RectF mInitialTargetRect = new RectF();
+ // Set when the final window destination is changed, such as offsetting for quick scrub
+ private final PointF mTargetOffset = new PointF();
// The insets to be used for clipping the app window, which can be larger than mSourceInsets
// if the aspect ratio of the target is smaller than the aspect ratio of the source rect. In
// app window coordinates.
@@ -54,15 +77,33 @@ public class ClipAnimationHelper {
private final Rect mClipRect = new Rect();
private final RectFEvaluator mRectFEvaluator = new RectFEvaluator();
private final Matrix mTmpMatrix = new Matrix();
+ private final RectF mTmpRectF = new RectF();
+ private float mTargetScale = 1f;
+ private Interpolator mInterpolator = LINEAR;
+ // We translate y slightly faster than the rest of the animation for quick scrub.
+ private Interpolator mOffsetYInterpolator = LINEAR;
- public void updateSource(Rect homeStackBounds, RemoteAnimationTargetCompat target) {
- mHomeStackBounds.set(homeStackBounds);
- mSourceInsets.set(target.getContentInsets());
+ // Whether to boost the opening animation target layers, or the closing
+ private int mBoostModeTargetLayers = -1;
+ // Wether or not applyTransform has been called yet since prepareAnimation()
+ private boolean mIsFirstFrame = true;
+
+ private BiConsumer<TransactionCompat, RemoteAnimationTargetCompat> mTaskTransformCallback =
+ (t, a) -> { };
+
+ private void updateSourceStack(RemoteAnimationTargetCompat target) {
+ mSourceInsets.set(target.contentInsets);
mSourceStackBounds.set(target.sourceContainerBounds);
// TODO: Should sourceContainerBounds already have this offset?
mSourceStackBounds.offsetTo(target.position.x, target.position.y);
+
+ }
+
+ public void updateSource(Rect homeStackBounds, RemoteAnimationTargetCompat target) {
+ mHomeStackBounds.set(homeStackBounds);
+ updateSourceStack(target);
}
public void updateTargetRect(Rect targetRect) {
@@ -73,8 +114,6 @@ public class ClipAnimationHelper {
mTargetRect.offset(mHomeStackBounds.left - mSourceStackBounds.left,
mHomeStackBounds.top - mSourceStackBounds.top);
- mInitialTargetRect.set(mTargetRect);
-
// Calculate the clip based on the target rect (since the content insets and the
// launcher insets may differ, so the aspect ratio of the target rect can differ
// from the source rect. The difference between the target rect (scaled to the
@@ -91,12 +130,22 @@ public class ClipAnimationHelper {
mSourceRect.set(scaledTargetRect);
}
- public void applyTransform(RemoteAnimationTargetCompat[] targets, float progress) {
+ public void prepareAnimation(boolean isOpening) {
+ mIsFirstFrame = true;
+ mBoostModeTargetLayers = isOpening ? MODE_OPENING : MODE_CLOSING;
+ }
+
+ public RectF applyTransform(RemoteAnimationTargetSet targetSet, float progress) {
RectF currentRect;
- synchronized (mTargetRect) {
- currentRect = mRectFEvaluator.evaluate(progress, mSourceRect, mTargetRect);
+ mTmpRectF.set(mTargetRect);
+ Utilities.scaleRectFAboutCenter(mTmpRectF, mTargetScale);
+ float offsetYProgress = mOffsetYInterpolator.getInterpolation(progress);
+ progress = mInterpolator.getInterpolation(progress);
+ currentRect = mRectFEvaluator.evaluate(progress, mSourceRect, mTmpRectF);
+
+ synchronized (mTargetOffset) {
// Stay lined up with the center of the target, since it moves for quick scrub.
- currentRect.offset(mTargetRect.centerX() - currentRect.centerX(), 0);
+ currentRect.offset(mTargetOffset.x * progress, mTargetOffset.y * offsetYProgress);
}
mClipRect.left = (int) (mSourceWindowClipInsets.left * progress);
@@ -106,30 +155,140 @@ public class ClipAnimationHelper {
mClipRect.bottom = (int)
(mSourceStackBounds.height() - (mSourceWindowClipInsets.bottom * progress));
- mTmpMatrix.setRectToRect(mSourceRect, currentRect, ScaleToFit.FILL);
-
TransactionCompat transaction = new TransactionCompat();
- for (RemoteAnimationTargetCompat app : targets) {
- if (app.mode == MODE_CLOSING) {
+ if (mIsFirstFrame) {
+ RemoteAnimationProvider.prepareTargetsForFirstFrame(targetSet.unfilteredApps,
+ transaction, mBoostModeTargetLayers);
+ mIsFirstFrame = false;
+ }
+ for (RemoteAnimationTargetCompat app : targetSet.apps) {
+ if (app.activityType != RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME) {
+ mTmpMatrix.setRectToRect(mSourceRect, currentRect, ScaleToFit.FILL);
mTmpMatrix.postTranslate(app.position.x, app.position.y);
transaction.setMatrix(app.leash, mTmpMatrix)
.setWindowCrop(app.leash, mClipRect);
- if (app.isNotInRecents) {
- transaction.setAlpha(app.leash, 1 - progress);
- }
+ }
- transaction.show(app.leash);
+ if (app.isNotInRecents
+ || app.activityType == RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME) {
+ transaction.setAlpha(app.leash, 1 - progress);
}
+
+ mTaskTransformCallback.accept(transaction, app);
}
transaction.setEarlyWakeup();
transaction.apply();
+ return currentRect;
}
- public void offsetTarget(float scale, float offsetX, float offsetY) {
- synchronized (mTargetRect) {
- mTargetRect.set(mInitialTargetRect);
- Utilities.scaleRectFAboutCenter(mTargetRect, scale);
- mTargetRect.offset(offsetX, offsetY);
+ public void setTaskTransformCallback
+ (BiConsumer<TransactionCompat, RemoteAnimationTargetCompat> callback) {
+ mTaskTransformCallback = callback;
+ }
+
+ public void offsetTarget(float scale, float offsetX, float offsetY, Interpolator interpolator) {
+ synchronized (mTargetOffset) {
+ mTargetOffset.set(offsetX, offsetY);
}
+ mTargetScale = scale;
+ mInterpolator = interpolator;
+ mOffsetYInterpolator = Interpolators.clampToProgress(mInterpolator, 0,
+ QUICK_SCRUB_TRANSLATION_Y_FACTOR);
+ }
+
+ public void fromTaskThumbnailView(TaskThumbnailView ttv, RecentsView rv) {
+ fromTaskThumbnailView(ttv, rv, null);
+ }
+
+ public void fromTaskThumbnailView(TaskThumbnailView ttv, RecentsView rv,
+ @Nullable RemoteAnimationTargetCompat target) {
+ BaseDraggingActivity activity = BaseDraggingActivity.fromContext(ttv.getContext());
+ BaseDragLayer dl = activity.getDragLayer();
+
+ int[] pos = new int[2];
+ dl.getLocationOnScreen(pos);
+ mHomeStackBounds.set(0, 0, dl.getWidth(), dl.getHeight());
+ mHomeStackBounds.offset(pos[0], pos[1]);
+
+ if (target != null) {
+ updateSourceStack(target);
+ } else if (rv.shouldUseMultiWindowTaskSizeStrategy()) {
+ updateStackBoundsToMultiWindowTaskSize(activity);
+ } else {
+ mSourceStackBounds.set(mHomeStackBounds);
+ mSourceInsets.set(activity.getDeviceProfile().getInsets());
+ }
+
+ Rect targetRect = new Rect();
+ dl.getDescendantRectRelativeToSelf(ttv, targetRect);
+ updateTargetRect(targetRect);
+
+ // Transform the clip relative to the target rect.
+ float scale = mTargetRect.width() / mSourceRect.width();
+ mSourceWindowClipInsets.left = mSourceWindowClipInsets.left * scale;
+ mSourceWindowClipInsets.top = mSourceWindowClipInsets.top * scale;
+ mSourceWindowClipInsets.right = mSourceWindowClipInsets.right * scale;
+ mSourceWindowClipInsets.bottom = mSourceWindowClipInsets.bottom * scale;
+ }
+
+ private void updateStackBoundsToMultiWindowTaskSize(BaseDraggingActivity activity) {
+ ISystemUiProxy sysUiProxy = RecentsModel.getInstance(activity).getSystemUiProxy();
+ if (sysUiProxy != null) {
+ try {
+ mSourceStackBounds.set(sysUiProxy.getNonMinimizedSplitScreenSecondaryBounds());
+ return;
+ } catch (RemoteException e) {
+ // Use half screen size
+ }
+ }
+
+ // Assume that the task size is half screen size (minus the insets and the divider size)
+ DeviceProfile fullDp = activity.getDeviceProfile().getFullScreenProfile();
+ // Use availableWidthPx and availableHeightPx instead of widthPx and heightPx to
+ // account for system insets
+ int taskWidth = fullDp.availableWidthPx;
+ int taskHeight = fullDp.availableHeightPx;
+ int halfDividerSize = activity.getResources()
+ .getDimensionPixelSize(R.dimen.multi_window_task_divider_size) / 2;
+
+ Rect insets = new Rect();
+ WindowManagerWrapper.getInstance().getStableInsets(insets);
+ if (fullDp.isLandscape) {
+ taskWidth = taskWidth / 2 - halfDividerSize;
+ } else {
+ taskHeight = taskHeight / 2 - halfDividerSize;
+ }
+
+ // Align the task to bottom left/right edge (closer to nav bar).
+ int left = activity.getDeviceProfile().isSeascape() ? insets.left
+ : (insets.left + fullDp.availableWidthPx - taskWidth);
+ mSourceStackBounds.set(0, 0, taskWidth, taskHeight);
+ mSourceStackBounds.offset(left, insets.top + fullDp.availableHeightPx - taskHeight);
+ }
+
+ public void drawForProgress(TaskThumbnailView ttv, Canvas canvas, float progress) {
+ RectF currentRect = mRectFEvaluator.evaluate(progress, mSourceRect, mTargetRect);
+ canvas.translate(mSourceStackBounds.left - mHomeStackBounds.left,
+ mSourceStackBounds.top - mHomeStackBounds.top);
+ mTmpMatrix.setRectToRect(mTargetRect, currentRect, ScaleToFit.FILL);
+
+ canvas.concat(mTmpMatrix);
+ canvas.translate(mTargetRect.left, mTargetRect.top);
+
+ float insetProgress = (1 - progress);
+ ttv.drawOnCanvas(canvas,
+ -mSourceWindowClipInsets.left * insetProgress,
+ -mSourceWindowClipInsets.top * insetProgress,
+ ttv.getMeasuredWidth() + mSourceWindowClipInsets.right * insetProgress,
+ ttv.getMeasuredHeight() + mSourceWindowClipInsets.bottom * insetProgress,
+ ttv.getCornerRadius() * progress);
+ }
+
+ public RectF getTargetRect() {
+ return mTargetRect;
+ }
+
+ public RectF getSourceRect() {
+ return mSourceRect;
}
}
diff --git a/quickstep/src/com/android/quickstep/util/LayoutUtils.java b/quickstep/src/com/android/quickstep/util/LayoutUtils.java
index f29f9e4e9..ec9c7eaed 100644
--- a/quickstep/src/com/android/quickstep/util/LayoutUtils.java
+++ b/quickstep/src/com/android/quickstep/util/LayoutUtils.java
@@ -15,39 +15,68 @@
*/
package com.android.quickstep.util;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Rect;
-import android.graphics.RectF;
+import android.support.annotation.AnyThread;
+import android.support.annotation.IntDef;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.R;
+import java.lang.annotation.Retention;
+
public class LayoutUtils {
+ private static final int MULTI_WINDOW_STRATEGY_HALF_SCREEN = 1;
+ private static final int MULTI_WINDOW_STRATEGY_DEVICE_PROFILE = 2;
+
+ @Retention(SOURCE)
+ @IntDef({MULTI_WINDOW_STRATEGY_HALF_SCREEN, MULTI_WINDOW_STRATEGY_DEVICE_PROFILE})
+ private @interface MultiWindowStrategy {}
+
public static void calculateLauncherTaskSize(Context context, DeviceProfile dp, Rect outRect) {
- float extraSpace = dp.isVerticalBarLayout() ? 0 : dp.hotseatBarSizePx;
- calculateTaskSize(context, dp, extraSpace, outRect);
+ float extraSpace;
+ if (dp.isVerticalBarLayout()) {
+ extraSpace = 0;
+ } else {
+ extraSpace = dp.hotseatBarSizePx + dp.verticalDragHandleSizePx;
+ }
+ calculateTaskSize(context, dp, extraSpace, MULTI_WINDOW_STRATEGY_HALF_SCREEN, outRect);
+ }
+
+ public static void calculateFallbackTaskSize(Context context, DeviceProfile dp, Rect outRect) {
+ calculateTaskSize(context, dp, 0, MULTI_WINDOW_STRATEGY_DEVICE_PROFILE, outRect);
}
+ @AnyThread
public static void calculateTaskSize(Context context, DeviceProfile dp,
- float extraVerticalSpace, Rect outRect) {
+ float extraVerticalSpace, @MultiWindowStrategy int multiWindowStrategy, Rect outRect) {
float taskWidth, taskHeight, paddingHorz;
Resources res = context.getResources();
Rect insets = dp.getInsets();
if (dp.isMultiWindowMode) {
- DeviceProfile fullDp = dp.getFullScreenProfile();
- // Use availableWidthPx and availableHeightPx instead of widthPx and heightPx to
- // account for system insets
- taskWidth = fullDp.availableWidthPx;
- taskHeight = fullDp.availableHeightPx;
- float halfDividerSize = res.getDimension(R.dimen.multi_window_task_divider_size) / 2;
-
- if (fullDp.isLandscape) {
- taskWidth = taskWidth / 2 - halfDividerSize;
+ if (multiWindowStrategy == MULTI_WINDOW_STRATEGY_HALF_SCREEN) {
+ DeviceProfile fullDp = dp.getFullScreenProfile();
+ // Use availableWidthPx and availableHeightPx instead of widthPx and heightPx to
+ // account for system insets
+ taskWidth = fullDp.availableWidthPx;
+ taskHeight = fullDp.availableHeightPx;
+ float halfDividerSize = res.getDimension(R.dimen.multi_window_task_divider_size)
+ / 2;
+
+ if (fullDp.isLandscape) {
+ taskWidth = taskWidth / 2 - halfDividerSize;
+ } else {
+ taskHeight = taskHeight / 2 - halfDividerSize;
+ }
} else {
- taskHeight = taskHeight / 2 - halfDividerSize;
+ // multiWindowStrategy == MULTI_WINDOW_STRATEGY_DEVICE_PROFILE
+ taskWidth = dp.widthPx;
+ taskHeight = dp.heightPx;
}
paddingHorz = res.getDimension(R.dimen.multi_window_task_card_horz_space);
} else {
@@ -75,7 +104,7 @@ public class LayoutUtils {
float outHeight = scale * taskHeight;
// Center in the visible space
- float x = insets.left + (taskWidth - outWidth) / 2;
+ float x = insets.left + (launcherVisibleWidth - outWidth) / 2;
float y = insets.top + Math.max(topIconMargin,
(launcherVisibleHeight - extraVerticalSpace - outHeight) / 2);
outRect.set(Math.round(x), Math.round(y),
diff --git a/quickstep/src/com/android/quickstep/util/MultiValueUpdateListener.java b/quickstep/src/com/android/quickstep/util/MultiValueUpdateListener.java
new file mode 100644
index 000000000..e798d5cbc
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/MultiValueUpdateListener.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep.util;
+
+import android.animation.ValueAnimator;
+import android.view.animation.Interpolator;
+
+import java.util.ArrayList;
+
+/**
+ * Utility class to update multiple values with different interpolators and durations during
+ * the same animation.
+ */
+public abstract class MultiValueUpdateListener implements ValueAnimator.AnimatorUpdateListener {
+
+ private final ArrayList<FloatProp> mAllProperties = new ArrayList<>();
+
+ @Override
+ public final void onAnimationUpdate(ValueAnimator animator) {
+ final float percent = animator.getAnimatedFraction();
+ final float currentPlayTime = percent * animator.getDuration();
+
+ for (int i = mAllProperties.size() - 1; i >= 0; i--) {
+ FloatProp prop = mAllProperties.get(i);
+ float time = Math.max(0, currentPlayTime - prop.mDelay);
+ float newPercent = Math.min(1f, time / prop.mDuration);
+ newPercent = prop.mInterpolator.getInterpolation(newPercent);
+ prop.value = prop.mEnd * newPercent + prop.mStart * (1 - newPercent);
+ }
+ onUpdate(percent);
+ }
+
+ public abstract void onUpdate(float percent);
+
+ public final class FloatProp {
+
+ public float value;
+
+ private final float mStart;
+ private final float mEnd;
+ private final float mDelay;
+ private final float mDuration;
+ private final Interpolator mInterpolator;
+
+ public FloatProp(float start, float end, float delay, float duration, Interpolator i) {
+ value = mStart = start;
+ mEnd = end;
+ mDelay = delay;
+ mDuration = duration;
+ mInterpolator = i;
+
+ mAllProperties.add(this);
+ }
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/util/RemoteAnimationProvider.java b/quickstep/src/com/android/quickstep/util/RemoteAnimationProvider.java
index 2ffcae38e..bbf223d1e 100644
--- a/quickstep/src/com/android/quickstep/util/RemoteAnimationProvider.java
+++ b/quickstep/src/com/android/quickstep/util/RemoteAnimationProvider.java
@@ -31,25 +31,34 @@ public interface RemoteAnimationProvider {
AnimatorSet createWindowAnimation(RemoteAnimationTargetCompat[] targets);
default ActivityOptions toActivityOptions(Handler handler, long duration) {
- LauncherAnimationRunner runner = new LauncherAnimationRunner(handler) {
+ LauncherAnimationRunner runner = new LauncherAnimationRunner(handler,
+ false /* startAtFrontOfQueue */) {
+
@Override
- public AnimatorSet getAnimator(RemoteAnimationTargetCompat[] targetCompats) {
- return createWindowAnimation(targetCompats);
+ public void onCreateAnimation(RemoteAnimationTargetCompat[] targetCompats,
+ AnimationResult result) {
+ result.setAnimation(createWindowAnimation(targetCompats));
}
};
return ActivityOptionsCompat.makeRemoteAnimation(
new RemoteAnimationAdapterCompat(runner, duration, 0));
}
- static void showOpeningTarget(RemoteAnimationTargetCompat[] targetCompats) {
- TransactionCompat t = new TransactionCompat();
- for (RemoteAnimationTargetCompat target : targetCompats) {
- int layer = target.mode == RemoteAnimationTargetCompat.MODE_CLOSING
+ /**
+ * Prepares the given {@param targets} for a remote animation, and should be called with the
+ * transaction from the first frame of animation.
+ *
+ * @param boostModeTargets The mode indicating which targets to boost in z-order above other
+ * targets.
+ */
+ static void prepareTargetsForFirstFrame(RemoteAnimationTargetCompat[] targets,
+ TransactionCompat t, int boostModeTargets) {
+ for (RemoteAnimationTargetCompat target : targets) {
+ int layer = target.mode == boostModeTargets
? Integer.MAX_VALUE
: target.prefixOrderIndex;
t.setLayer(target.leash, layer);
t.show(target.leash);
}
- t.apply();
}
}
diff --git a/quickstep/src/com/android/quickstep/util/RemoteAnimationTargetSet.java b/quickstep/src/com/android/quickstep/util/RemoteAnimationTargetSet.java
new file mode 100644
index 000000000..04b8be58b
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/RemoteAnimationTargetSet.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep.util;
+
+import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
+
+import java.util.ArrayList;
+
+/**
+ * Holds a collection of RemoteAnimationTargets, filtered by different properties.
+ */
+public class RemoteAnimationTargetSet {
+
+ public final RemoteAnimationTargetCompat[] unfilteredApps;
+ public final RemoteAnimationTargetCompat[] apps;
+
+ public RemoteAnimationTargetSet(RemoteAnimationTargetCompat[] apps, int targetMode) {
+ ArrayList<RemoteAnimationTargetCompat> filteredApps = new ArrayList<>();
+ if (apps != null) {
+ for (RemoteAnimationTargetCompat target : apps) {
+ if (target.mode == targetMode) {
+ filteredApps.add(target);
+ }
+ }
+ }
+
+ this.unfilteredApps = apps;
+ this.apps = filteredApps.toArray(new RemoteAnimationTargetCompat[filteredApps.size()]);
+ }
+
+ public RemoteAnimationTargetCompat findTask(int taskId) {
+ for (RemoteAnimationTargetCompat target : apps) {
+ if (target.taskId == taskId) {
+ return target;
+ }
+ }
+ return null;
+ }
+
+ public boolean isAnimatingHome() {
+ for (RemoteAnimationTargetCompat target : apps) {
+ if (target.activityType == RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/util/SysuiEventLogger.java b/quickstep/src/com/android/quickstep/util/SysuiEventLogger.java
deleted file mode 100644
index d474ded90..000000000
--- a/quickstep/src/com/android/quickstep/util/SysuiEventLogger.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.quickstep.util;
-
-import android.metrics.LogMaker;
-import android.util.EventLog;
-
-/**
- * Utility class for writing logs on behalf of systemUI
- */
-public class SysuiEventLogger {
-
- /** 524292 sysui_multi_action (content|4) */
- public static final int SYSUI_MULTI_ACTION = 524292;
-
- private static void write(LogMaker content) {
- if (content.getType() == 0/*MetricsEvent.TYPE_UNKNOWN*/) {
- content.setType(4/*MetricsEvent.TYPE_ACTION*/);
- }
- EventLog.writeEvent(SYSUI_MULTI_ACTION, content.serialize());
- }
-
- public static void writeDummyRecentsTransition(long transitionDelay) {
- // Mimic ActivityMetricsLogger.logAppTransitionMultiEvents() logging for
- // "Recents" activity for app transition tests for the app-to-recents case.
- final LogMaker builder = new LogMaker(761/*APP_TRANSITION*/);
- builder.setPackageName("com.android.systemui");
- builder.addTaggedData(871/*FIELD_CLASS_NAME*/,
- "com.android.systemui.recents.RecentsActivity");
- builder.addTaggedData(319/*APP_TRANSITION_DELAY_MS*/,
- transitionDelay);
- write(builder);
- }
-}
diff --git a/quickstep/src/com/android/quickstep/util/TaskViewDrawable.java b/quickstep/src/com/android/quickstep/util/TaskViewDrawable.java
new file mode 100644
index 000000000..48b07a714
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/TaskViewDrawable.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep.util;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.PixelFormat;
+import android.graphics.drawable.Drawable;
+import android.util.FloatProperty;
+import android.view.View;
+
+import com.android.launcher3.Utilities;
+import com.android.quickstep.views.RecentsView;
+import com.android.quickstep.views.TaskThumbnailView;
+import com.android.quickstep.views.TaskView;
+
+public class TaskViewDrawable extends Drawable {
+
+ public static final FloatProperty<TaskViewDrawable> PROGRESS =
+ new FloatProperty<TaskViewDrawable>("progress") {
+ @Override
+ public void setValue(TaskViewDrawable taskViewDrawable, float v) {
+ taskViewDrawable.setProgress(v);
+ }
+
+ @Override
+ public Float get(TaskViewDrawable taskViewDrawable) {
+ return taskViewDrawable.mProgress;
+ }
+ };
+
+ /**
+ * The progress at which we play the atomic icon scale animation.
+ */
+ private static final float ICON_SCALE_THRESHOLD = 0.95f;
+
+ private final RecentsView mParent;
+ private final View mIconView;
+ private final int[] mIconPos;
+
+ private final TaskThumbnailView mThumbnailView;
+
+ private final ClipAnimationHelper mClipAnimationHelper;
+
+ private float mProgress = 1;
+ private boolean mPassedIconScaleThreshold;
+ private ValueAnimator mIconScaleAnimator;
+ private float mIconScale;
+
+ public TaskViewDrawable(TaskView tv, RecentsView parent) {
+ mParent = parent;
+ mIconView = tv.getIconView();
+ mIconPos = new int[2];
+ mIconScale = mIconView.getScaleX();
+ Utilities.getDescendantCoordRelativeToAncestor(mIconView, parent, mIconPos, true);
+
+ mThumbnailView = tv.getThumbnail();
+ mClipAnimationHelper = new ClipAnimationHelper();
+ mClipAnimationHelper.fromTaskThumbnailView(mThumbnailView, parent);
+ }
+
+ public void setProgress(float progress) {
+ mProgress = progress;
+ mParent.invalidate();
+ boolean passedIconScaleThreshold = progress <= ICON_SCALE_THRESHOLD;
+ if (mPassedIconScaleThreshold != passedIconScaleThreshold) {
+ mPassedIconScaleThreshold = passedIconScaleThreshold;
+ animateIconScale(mPassedIconScaleThreshold ? 0 : 1);
+ }
+ }
+
+ private void animateIconScale(float toScale) {
+ if (mIconScaleAnimator != null) {
+ mIconScaleAnimator.cancel();
+ }
+ mIconScaleAnimator = ValueAnimator.ofFloat(mIconScale, toScale);
+ mIconScaleAnimator.addUpdateListener(valueAnimator -> {
+ mIconScale = (float) valueAnimator.getAnimatedValue();
+ if (mProgress > ICON_SCALE_THRESHOLD) {
+ // Speed up the icon scale to ensure it is 1 when progress is 1.
+ float iconProgress = (mProgress - ICON_SCALE_THRESHOLD) / (1 - ICON_SCALE_THRESHOLD);
+ if (iconProgress > mIconScale) {
+ mIconScale = iconProgress;
+ }
+ }
+ invalidateSelf();
+ });
+ mIconScaleAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mIconScaleAnimator = null;
+ }
+ });
+ mIconScaleAnimator.setDuration(TaskView.SCALE_ICON_DURATION);
+ mIconScaleAnimator.start();
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ canvas.save();
+ canvas.translate(mParent.getScrollX(), mParent.getScrollY());
+ mClipAnimationHelper.drawForProgress(mThumbnailView, canvas, mProgress);
+ canvas.restore();
+
+ canvas.save();
+ canvas.translate(mIconPos[0], mIconPos[1]);
+ canvas.scale(mIconScale, mIconScale, mIconView.getWidth() / 2, mIconView.getHeight() / 2);
+ mIconView.draw(canvas);
+ canvas.restore();
+ }
+
+ public ClipAnimationHelper getClipAnimationHelper() {
+ return mClipAnimationHelper;
+ }
+
+ @Override
+ public void setAlpha(int i) { }
+
+ @Override
+ public void setColorFilter(ColorFilter colorFilter) { }
+
+ @Override
+ public int getOpacity() {
+ return PixelFormat.TRANSLUCENT;
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/views/ClearAllButton.java b/quickstep/src/com/android/quickstep/views/ClearAllButton.java
new file mode 100644
index 000000000..d5c43a0f5
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/views/ClearAllButton.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.views;
+
+import static android.view.accessibility.AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.util.AttributeSet;
+import android.widget.Button;
+
+public class ClearAllButton extends Button {
+ RecentsView mRecentsView;
+
+ public ClearAllButton(Context context, @Nullable AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public void setRecentsView(RecentsView recentsView) {
+ mRecentsView = recentsView;
+ }
+
+ @Override
+ public boolean performAccessibilityAction(int action, Bundle arguments) {
+ final boolean res = super.performAccessibilityAction(action, arguments);
+ if (action == ACTION_ACCESSIBILITY_FOCUS) {
+ mRecentsView.revealClearAllButton();
+ }
+ return res;
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/views/IconView.java b/quickstep/src/com/android/quickstep/views/IconView.java
new file mode 100644
index 000000000..c359966df
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/views/IconView.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep.views;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.view.View;
+
+/**
+ * A view which draws a drawable stretched to fit its size. Unlike ImageView, it avoids relayout
+ * when the drawable changes.
+ */
+public class IconView extends View {
+
+ private Drawable mDrawable;
+
+ public IconView(Context context) {
+ super(context);
+ }
+
+ public IconView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public IconView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ public void setDrawable(Drawable d) {
+ if (mDrawable != null) {
+ mDrawable.setCallback(null);
+ }
+ mDrawable = d;
+ if (mDrawable != null) {
+ mDrawable.setCallback(this);
+ mDrawable.setBounds(0, 0, getWidth(), getHeight());
+ }
+ invalidate();
+ }
+
+ @Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ super.onSizeChanged(w, h, oldw, oldh);
+ if (mDrawable != null) {
+ mDrawable.setBounds(0, 0, w, h);
+ }
+ }
+
+ @Override
+ protected boolean verifyDrawable(Drawable who) {
+ return super.verifyDrawable(who) || who == mDrawable;
+ }
+
+ @Override
+ protected void drawableStateChanged() {
+ super.drawableStateChanged();
+
+ final Drawable drawable = mDrawable;
+ if (drawable != null && drawable.isStateful()
+ && drawable.setState(getDrawableState())) {
+ invalidateDrawable(drawable);
+ }
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ if (mDrawable != null) {
+ mDrawable.draw(canvas);
+ }
+ }
+
+ @Override
+ public boolean hasOverlappingRendering() {
+ return false;
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/views/LauncherLayoutListener.java b/quickstep/src/com/android/quickstep/views/LauncherLayoutListener.java
index ac34d90b1..c149de54f 100644
--- a/quickstep/src/com/android/quickstep/views/LauncherLayoutListener.java
+++ b/quickstep/src/com/android/quickstep/views/LauncherLayoutListener.java
@@ -54,7 +54,7 @@ public class LauncherLayoutListener extends AbstractFloatingView
@Override
public void setInsets(Rect insets) {
if (mHandler != null) {
- mHandler.onLauncherLayoutChanged();
+ mHandler.buildAnimationController();
}
}
diff --git a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
index 4b4af3fa7..950f7fb99 100644
--- a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -27,7 +27,6 @@ import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.os.Build;
-import android.support.annotation.AnyThread;
import android.util.AttributeSet;
import android.util.FloatProperty;
import android.view.View;
@@ -36,6 +35,8 @@ import android.view.ViewDebug;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
+import com.android.quickstep.OverviewInteractionState;
+import com.android.quickstep.util.ClipAnimationHelper;
import com.android.quickstep.util.LayoutUtils;
/**
@@ -87,7 +88,11 @@ public class LauncherRecentsView extends RecentsView<Launcher> {
public void setTranslationYFactor(float translationFactor) {
mTranslationYFactor = translationFactor;
- setTranslationY(mTranslationYFactor * (getPaddingBottom() - getPaddingTop()));
+ setTranslationY(computeTranslationYForFactor(mTranslationYFactor));
+ }
+
+ public float computeTranslationYForFactor(float translationYFactor) {
+ return translationYFactor * (getPaddingBottom() - getPaddingTop());
}
@Override
@@ -112,8 +117,15 @@ public class LauncherRecentsView extends RecentsView<Launcher> {
* Animates adjacent tasks and translate hotseat off screen as well.
*/
@Override
- public AnimatorSet createAdjacentPageAnimForTaskLaunch(TaskView tv) {
- AnimatorSet anim = super.createAdjacentPageAnimForTaskLaunch(tv);
+ public AnimatorSet createAdjacentPageAnimForTaskLaunch(TaskView tv,
+ ClipAnimationHelper helper) {
+ AnimatorSet anim = super.createAdjacentPageAnimForTaskLaunch(tv, helper);
+
+ if (!OverviewInteractionState.getInstance(mActivity).isSwipeUpGestureEnabled()) {
+ // Hotseat doesn't move when opening recents with the button,
+ // so don't animate it here either.
+ return anim;
+ }
float allAppsProgressOffscreen = ALL_APPS_PROGRESS_OFF_SCREEN;
LauncherState state = mActivity.getStateManager().getState();
@@ -132,8 +144,19 @@ public class LauncherRecentsView extends RecentsView<Launcher> {
LayoutUtils.calculateLauncherTaskSize(getContext(), dp, outRect);
}
- @AnyThread
- public static void getPageRect(DeviceProfile grid, Context context, Rect outRect) {
- LayoutUtils.calculateLauncherTaskSize(context, grid, outRect);
+ @Override
+ protected void onTaskLaunched(boolean success) {
+ if (success) {
+ mActivity.getStateManager().goToState(NORMAL, false /* animate */);
+ } else {
+ LauncherState state = mActivity.getStateManager().getState();
+ mActivity.getAllAppsController().setProgress(state.getVerticalProgress(mActivity));
+ }
+ super.onTaskLaunched(success);
+ }
+
+ @Override
+ public boolean shouldUseMultiWindowTaskSizeStrategy() {
+ return mActivity.isInMultiWindowModeCompat();
}
}
diff --git a/quickstep/src/com/android/quickstep/views/QuickstepDragIndicator.java b/quickstep/src/com/android/quickstep/views/QuickstepDragIndicator.java
deleted file mode 100644
index 5e9cd6e4f..000000000
--- a/quickstep/src/com/android/quickstep/views/QuickstepDragIndicator.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.quickstep.views;
-
-import static com.android.launcher3.LauncherState.ALL_APPS;
-import static com.android.launcher3.LauncherState.OVERVIEW;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.View;
-import android.view.accessibility.AccessibilityNodeInfo;
-
-import com.android.launcher3.R;
-import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
-import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
-import com.android.launcher3.userevent.nano.LauncherLogProto.ControlType;
-import com.android.launcher3.views.LauncherDragIndicator;
-
-public class QuickstepDragIndicator extends LauncherDragIndicator {
-
- public QuickstepDragIndicator(Context context) {
- super(context);
- }
-
- public QuickstepDragIndicator(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- public QuickstepDragIndicator(Context context, AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
- }
-
- private boolean isInOverview() {
- return mLauncher.isInState(OVERVIEW);
- }
-
- @Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setContentDescription(getContext().getString(R.string.all_apps_button_label));
- }
-
- @Override
- protected void initCustomActions(AccessibilityNodeInfo info) {
- if (!isInOverview()) {
- super.initCustomActions(info);
- }
- }
-
- @Override
- public void onClick(View view) {
- mLauncher.getUserEventDispatcher().logActionOnControl(Action.Touch.TAP,
- ControlType.ALL_APPS_BUTTON,
- isInOverview() ? ContainerType.TASKSWITCHER : ContainerType.WORKSPACE);
- mLauncher.getStateManager().goToState(ALL_APPS);
- }
-}
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 7e81ba902..a6da89f20 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -16,11 +16,15 @@
package com.android.quickstep.views;
+import static com.android.launcher3.BaseActivity.INVISIBLE_BY_STATE_HANDLER;
import static com.android.launcher3.anim.Interpolators.ACCEL;
import static com.android.launcher3.anim.Interpolators.ACCEL_2;
import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.launcher3.util.SystemUiController.UI_STATE_OVERVIEW;
+import static com.android.quickstep.TaskUtils.checkCurrentOrManagedUserId;
+import android.animation.Animator;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.TimeInterpolator;
@@ -30,13 +34,13 @@ import android.app.ActivityManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
-import android.content.SharedPreferences;
-import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.graphics.Canvas;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Build;
+import android.os.Handler;
+import android.os.UserHandle;
import android.text.Layout;
import android.text.StaticLayout;
import android.text.TextPaint;
@@ -49,7 +53,6 @@ import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewDebug;
-import android.view.accessibility.AccessibilityEvent;
import com.android.launcher3.BaseActivity;
import com.android.launcher3.DeviceProfile;
@@ -64,42 +67,34 @@ import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
import com.android.launcher3.util.PendingAnimation;
import com.android.launcher3.util.Themes;
+import com.android.quickstep.OverviewCallbacks;
import com.android.quickstep.QuickScrubController;
-import com.android.quickstep.RecentsAnimationInterpolator;
-import com.android.quickstep.RecentsAnimationInterpolator.TaskWindowBounds;
import com.android.quickstep.RecentsModel;
import com.android.quickstep.TaskUtils;
+import com.android.quickstep.util.ClipAnimationHelper;
+import com.android.quickstep.util.TaskViewDrawable;
import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan;
import com.android.systemui.shared.recents.model.RecentsTaskLoader;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.TaskStack;
import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.shared.system.BackgroundExecutor;
+import com.android.systemui.shared.system.PackageManagerWrapper;
import com.android.systemui.shared.system.TaskStackChangeListener;
import java.util.ArrayList;
+import java.util.function.Consumer;
/**
* A list of recent tasks.
*/
@TargetApi(Build.VERSION_CODES.P)
-public abstract class RecentsView<T extends BaseActivity>
- extends PagedView implements OnSharedPreferenceChangeListener, Insettable {
+public abstract class RecentsView<T extends BaseActivity> extends PagedView implements Insettable {
- private final Rect mTempRect = new Rect();
-
- public static final FloatProperty<RecentsView> CONTENT_ALPHA =
- new FloatProperty<RecentsView>("contentAlpha") {
- @Override
- public void setValue(RecentsView recentsView, float v) {
- recentsView.setContentAlpha(v);
- }
+ private static final String TAG = RecentsView.class.getSimpleName();
- @Override
- public Float get(RecentsView recentsView) {
- return recentsView.mContentAlpha;
- }
- };
+ private final Rect mTempRect = new Rect();
public static final FloatProperty<RecentsView> ADJACENT_SCALE =
new FloatProperty<RecentsView>("adjacentScale") {
@@ -113,8 +108,12 @@ public abstract class RecentsView<T extends BaseActivity>
return recentsView.mAdjacentScale;
}
};
- private static final String PREF_FLIP_RECENTS = "pref_flip_recents";
+ public static final boolean FLIP_RECENTS = true;
private static final int DISMISS_TASK_DURATION = 300;
+ // The threshold at which we update the SystemUI flags when animating from the task into the app
+ private static final float UPDATE_SYSUI_FLAGS_THRESHOLD = 0.6f;
+
+ private static final float[] sTempFloatArray = new float[3];
protected final T mActivity;
private final QuickScrubController mQuickScrubController;
@@ -125,19 +124,27 @@ public abstract class RecentsView<T extends BaseActivity>
// Keeps track of the previously known visible tasks for purposes of loading/unloading task data
private final SparseBooleanArray mHasVisibleTaskData = new SparseBooleanArray();
+ private boolean mIsClearAllButtonFullyRevealed;
+
/**
* TODO: Call reloadIdNeeded in onTaskStackChanged.
*/
private final TaskStackChangeListener mTaskStackListener = new TaskStackChangeListener() {
@Override
public void onTaskSnapshotChanged(int taskId, ThumbnailData snapshot) {
+ if (!mHandleTaskStackChanges) {
+ return;
+ }
updateThumbnail(taskId, snapshot);
}
@Override
public void onActivityPinned(String packageName, int userId, int taskId, int stackId) {
+ if (!mHandleTaskStackChanges) {
+ return;
+ }
// Check this is for the right user
- if (!checkCurrentUserId(userId, false /* debug */)) {
+ if (!checkCurrentOrManagedUserId(userId, getContext())) {
return;
}
@@ -150,21 +157,71 @@ public abstract class RecentsView<T extends BaseActivity>
@Override
public void onActivityUnpinned() {
+ if (!mHandleTaskStackChanges) {
+ return;
+ }
// TODO: Re-enable layout transitions for addition of the unpinned task
reloadIfNeeded();
}
+
+ @Override
+ public void onTaskRemoved(int taskId) {
+ if (!mHandleTaskStackChanges) {
+ return;
+ }
+ BackgroundExecutor.get().submit(() -> {
+ TaskView taskView = getTaskView(taskId);
+ if (taskView == null) {
+ return;
+ }
+ Handler handler = taskView.getHandler();
+ if (handler == null) {
+ return;
+ }
+
+ // TODO: Add callbacks from AM reflecting adding/removing from the recents list, and
+ // remove all these checks
+ Task.TaskKey taskKey = taskView.getTask().key;
+ if (PackageManagerWrapper.getInstance().getActivityInfo(taskKey.getComponent(),
+ taskKey.userId) == null) {
+ // The package was uninstalled
+ handler.post(() ->
+ dismissTask(taskView, true /* animate */, false /* removeTask */));
+ } else {
+ RecentsTaskLoadPlan loadPlan = new RecentsTaskLoadPlan(getContext());
+ RecentsTaskLoadPlan.PreloadOptions opts =
+ new RecentsTaskLoadPlan.PreloadOptions();
+ opts.loadTitles = false;
+ loadPlan.preloadPlan(opts, mModel.getRecentsTaskLoader(), -1,
+ UserHandle.myUserId());
+ if (loadPlan.getTaskStack().findTaskWithId(taskId) == null) {
+ // The task was removed from the recents list
+ handler.post(() ->
+ dismissTask(taskView, true /* animate */, false /* removeTask */));
+ }
+ }
+ });
+ }
+
+ @Override
+ public void onPinnedStackAnimationStarted() {
+ // Needed for activities that auto-enter PiP, which will not trigger a remote
+ // animation to be created
+ mActivity.clearForceInvisibleFlag(INVISIBLE_BY_STATE_HANDLER);
+ }
};
private int mLoadPlanId = -1;
// Only valid until the launcher state changes to NORMAL
private int mRunningTaskId = -1;
+ private boolean mRunningTaskTileHidden;
private Task mTmpRunningTask;
- private boolean mFirstTaskIconScaledDown = false;
+ private boolean mRunningTaskIconScaledDown = false;
private boolean mOverviewStateEnabled;
- private boolean mTaskStackListenerRegistered;
+ private boolean mHandleTaskStackChanges;
private Runnable mNextPageSwitchRunnable;
private boolean mSwipeDownShouldLaunchApp;
@@ -178,6 +235,8 @@ public abstract class RecentsView<T extends BaseActivity>
// Keeps track of task views whose visual state should not be reset
private ArraySet<TaskView> mIgnoreResetTaskViews = new ArraySet<>();
+ private View mClearAllButton;
+
// Variables for empty state
private final Drawable mEmptyIcon;
private final CharSequence mEmptyMessage;
@@ -187,6 +246,14 @@ public abstract class RecentsView<T extends BaseActivity>
private boolean mShowEmptyMessage;
private Layout mEmptyTextLayout;
+ private BaseActivity.MultiWindowModeChangedListener mMultiWindowModeChangedListener =
+ (inMultiWindowMode) -> {
+ if (!inMultiWindowMode && mOverviewStateEnabled) {
+ // TODO: Re-enable layout transitions for addition of the unpinned task
+ reloadIfNeeded();
+ }
+ };
+
public RecentsView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setPageSpacing(getResources().getDimensionPixelSize(R.dimen.recents_page_spacing));
@@ -199,7 +266,11 @@ public abstract class RecentsView<T extends BaseActivity>
mQuickScrubController = new QuickScrubController(mActivity, this);
mModel = RecentsModel.getInstance(context);
- onSharedPreferenceChanged(Utilities.getPrefs(context), PREF_FLIP_RECENTS);
+ mIsRtl = Utilities.isRtl(getResources());
+ if (FLIP_RECENTS) {
+ mIsRtl = !mIsRtl;
+ }
+ setLayoutDirection(mIsRtl ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR);
mEmptyIcon = context.getDrawable(R.drawable.ic_empty_recents);
mEmptyIcon.setCallback(this);
@@ -211,17 +282,7 @@ public abstract class RecentsView<T extends BaseActivity>
mEmptyMessagePadding = getResources()
.getDimensionPixelSize(R.dimen.recents_empty_message_text_padding);
setWillNotDraw(false);
- }
-
- @Override
- public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String s) {
- if (s.equals(PREF_FLIP_RECENTS)) {
- mIsRtl = Utilities.isRtl(getResources());
- if (sharedPreferences.getBoolean(PREF_FLIP_RECENTS, false)) {
- mIsRtl = !mIsRtl;
- }
- setLayoutDirection(mIsRtl ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR);
- }
+ updateEmptyMessage();
}
public boolean isRtl() {
@@ -229,14 +290,11 @@ public abstract class RecentsView<T extends BaseActivity>
}
public TaskView updateThumbnail(int taskId, ThumbnailData thumbnailData) {
- for (int i = 0; i < getChildCount(); i++) {
- final TaskView taskView = (TaskView) getChildAt(i);
- if (taskView.getTask().key.id == taskId) {
- taskView.onTaskDataLoaded(taskView.getTask(), thumbnailData);
- return taskView;
- }
+ TaskView taskView = getTaskView(taskId);
+ if (taskView != null) {
+ taskView.onTaskDataLoaded(taskView.getTask(), thumbnailData);
}
- return null;
+ return taskView;
}
@Override
@@ -249,14 +307,16 @@ public abstract class RecentsView<T extends BaseActivity>
protected void onAttachedToWindow() {
super.onAttachedToWindow();
updateTaskStackListenerState();
- Utilities.getPrefs(getContext()).registerOnSharedPreferenceChangeListener(this);
+ mActivity.addMultiWindowModeChangedListener(mMultiWindowModeChangedListener);
+ ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener);
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
updateTaskStackListenerState();
- Utilities.getPrefs(getContext()).unregisterOnSharedPreferenceChangeListener(this);
+ mActivity.removeMultiWindowModeChangedListener(mMultiWindowModeChangedListener);
+ ActivityManagerWrapper.getInstance().unregisterTaskStackListener(mTaskStackListener);
}
@Override
@@ -271,6 +331,7 @@ public abstract class RecentsView<T extends BaseActivity>
loader.unloadTaskData(task);
loader.getHighResThumbnailLoader().onTaskInvisible(task);
}
+ onChildViewsChanged();
}
public boolean isTaskViewVisible(TaskView tv) {
@@ -309,11 +370,69 @@ public abstract class RecentsView<T extends BaseActivity>
}
}
+ private int getScrollEnd() {
+ return mIsRtl ? 0 : mMaxScrollX;
+ }
+
+ private float calculateClearAllButtonAlpha() {
+ final int childCount = getChildCount();
+ if (mShowEmptyMessage || childCount == 0 || mPageScrolls == null
+ || childCount != mPageScrolls.length) {
+ return 0;
+ }
+
+ final int scrollEnd = getScrollEnd();
+ final int oldestChildScroll = getScrollForPage(childCount - 1);
+
+ final int clearAllButtonMotionRange = scrollEnd - oldestChildScroll;
+ if (clearAllButtonMotionRange == 0) return 0;
+
+ final float alphaUnbound = ((float) (getScrollX() - oldestChildScroll)) /
+ clearAllButtonMotionRange;
+ if (alphaUnbound > 1) return 0;
+
+ return Math.max(alphaUnbound, 0);
+ }
+
+ private void updateClearAllButtonAlpha() {
+ if (mClearAllButton != null) {
+ final float alpha = calculateClearAllButtonAlpha();
+ mIsClearAllButtonFullyRevealed = alpha == 1;
+ mClearAllButton.setAlpha(alpha * mContentAlpha);
+ }
+ }
+
+ @Override
+ protected void onScrollChanged(int l, int t, int oldl, int oldt) {
+ super.onScrollChanged(l, t, oldl, oldt);
+ updateClearAllButtonAlpha();
+ }
+
+ @Override
+ protected void restoreScrollOnLayout() {
+ if (mIsClearAllButtonFullyRevealed) {
+ scrollAndForceFinish(getScrollEnd());
+ } else {
+ super.restoreScrollOnLayout();
+ }
+ }
+
@Override
public boolean onTouchEvent(MotionEvent ev) {
- super.onTouchEvent(ev);
- // Do not let touch escape to siblings below this view.
- return true;
+ if (ev.getAction() == MotionEvent.ACTION_DOWN && mTouchState == TOUCH_STATE_REST
+ && mScroller.isFinished() && mIsClearAllButtonFullyRevealed) {
+ mClearAllButton.getHitRect(mTempRect);
+ mTempRect.offset(-getLeft(), -getTop());
+ if (mTempRect.contains((int) ev.getX(), (int) ev.getY())) {
+ // If nothing is in motion, let the Clear All button process the event.
+ return false;
+ }
+ }
+
+ if (ev.getAction() == MotionEvent.ACTION_UP && mShowEmptyMessage) {
+ onAllTasksRemoved();
+ }
+ return super.onTouchEvent(ev);
}
private void applyLoadPlan(RecentsTaskLoadPlan loadPlan) {
@@ -356,7 +475,6 @@ public abstract class RecentsView<T extends BaseActivity>
taskView.bind(task);
}
resetTaskVisuals();
- applyIconScale(false /* animate */);
if (oldChildCount != getChildCount()) {
mQuickScrubController.snapToNextTaskIfAvailable();
@@ -373,6 +491,10 @@ public abstract class RecentsView<T extends BaseActivity>
taskView.resetVisualProperties();
}
}
+ if (mRunningTaskTileHidden) {
+ setRunningTaskHidden(mRunningTaskTileHidden);
+ }
+ applyIconScale(false /* animate */);
updateCurveProperties();
// Update the set of visible task's data
@@ -380,18 +502,13 @@ public abstract class RecentsView<T extends BaseActivity>
}
private void updateTaskStackListenerState() {
- boolean registerStackListener = mOverviewStateEnabled && isAttachedToWindow()
+ boolean handleTaskStackChanges = mOverviewStateEnabled && isAttachedToWindow()
&& getWindowVisibility() == VISIBLE;
- if (registerStackListener != mTaskStackListenerRegistered) {
- if (registerStackListener) {
- ActivityManagerWrapper.getInstance()
- .registerTaskStackListener(mTaskStackListener);
+ if (handleTaskStackChanges != mHandleTaskStackChanges) {
+ mHandleTaskStackChanges = handleTaskStackChanges;
+ if (handleTaskStackChanges) {
reloadIfNeeded();
- } else {
- ActivityManagerWrapper.getInstance()
- .unregisterTaskStackListener(mTaskStackListener);
}
- mTaskStackListenerRegistered = registerStackListener;
}
}
@@ -400,15 +517,20 @@ public abstract class RecentsView<T extends BaseActivity>
mInsets.set(insets);
DeviceProfile dp = mActivity.getDeviceProfile();
getTaskSize(dp, mTempRect);
+
mTempRect.top -= getResources()
.getDimensionPixelSize(R.dimen.task_thumbnail_top_margin);
setPadding(mTempRect.left - mInsets.left, mTempRect.top - mInsets.top,
- dp.widthPx - mTempRect.right - mInsets.right,
- dp.heightPx - mTempRect.bottom - mInsets.bottom);
+ dp.availableWidthPx + mInsets.left - mTempRect.right,
+ dp.availableHeightPx + mInsets.top - mTempRect.bottom);
}
protected abstract void getTaskSize(DeviceProfile dp, Rect outRect);
+ public void getTaskSize(Rect outRect) {
+ getTaskSize(mActivity.getDeviceProfile(), outRect);
+ }
+
@Override
protected boolean computeScrollHelper() {
boolean scrolling = super.computeScrollHelper();
@@ -506,13 +628,16 @@ public abstract class RecentsView<T extends BaseActivity>
mHasVisibleTaskData.clear();
}
-
protected abstract void onAllTasksRemoved();
public void reset() {
- unloadVisibleTaskData();
mRunningTaskId = -1;
+ mRunningTaskTileHidden = false;
+
+ unloadVisibleTaskData();
setCurrentPage(0);
+
+ OverviewCallbacks.get(getContext()).onResetOverview();
}
/**
@@ -546,12 +671,17 @@ public abstract class RecentsView<T extends BaseActivity>
new ActivityManager.TaskDescription(), 0, new ComponentName("", ""), false);
taskView.bind(mTmpRunningTask);
}
- setCurrentTask(mRunningTaskId);
+ setCurrentTask(runningTaskId);
+ }
- // Hide the task that we are animating into, ignore if there is no associated task (ie. the
- // assistant)
- if (getPageAt(mCurrentPage) != null) {
- getPageAt(mCurrentPage).setAlpha(0);
+ /**
+ * Hides the tile associated with {@link #mRunningTaskId}
+ */
+ public void setRunningTaskHidden(boolean isHidden) {
+ mRunningTaskTileHidden = isHidden;
+ TaskView runningTask = getTaskView(mRunningTaskId);
+ if (runningTask != null) {
+ runningTask.setAlpha(isHidden ? 0 : mContentAlpha);
}
}
@@ -559,7 +689,15 @@ public abstract class RecentsView<T extends BaseActivity>
* Similar to {@link #showTask(int)} but does not put any restrictions on the first tile.
*/
public void setCurrentTask(int runningTaskId) {
+ boolean runningTaskTileHidden = mRunningTaskTileHidden;
+ boolean runningTaskIconScaledDown = mRunningTaskIconScaledDown;
+
+ setRunningTaskIconScaledDown(false, false);
+ setRunningTaskHidden(false);
mRunningTaskId = runningTaskId;
+ setRunningTaskIconScaledDown(runningTaskIconScaledDown, false);
+ setRunningTaskHidden(runningTaskTileHidden);
+
setCurrentPage(0);
// Load the tasks (if the loading is already
@@ -587,22 +725,22 @@ public abstract class RecentsView<T extends BaseActivity>
return mQuickScrubController;
}
- public void setFirstTaskIconScaledDown(boolean isScaledDown, boolean animate) {
- if (mFirstTaskIconScaledDown == isScaledDown) {
+ public void setRunningTaskIconScaledDown(boolean isScaledDown, boolean animate) {
+ if (mRunningTaskIconScaledDown == isScaledDown) {
return;
}
- mFirstTaskIconScaledDown = isScaledDown;
+ mRunningTaskIconScaledDown = isScaledDown;
applyIconScale(animate);
}
private void applyIconScale(boolean animate) {
- float scale = mFirstTaskIconScaledDown ? 0 : 1;
- TaskView firstTask = (TaskView) getChildAt(0);
+ float scale = mRunningTaskIconScaledDown ? 0 : 1;
+ TaskView firstTask = getTaskView(mRunningTaskId);
if (firstTask != null) {
if (animate) {
- firstTask.animateIconToScale(scale);
+ firstTask.animateIconToScaleAndDim(scale);
} else {
- firstTask.setIconScale(scale);
+ firstTask.setIconScaleAndDim(scale);
}
}
}
@@ -640,8 +778,26 @@ public abstract class RecentsView<T extends BaseActivity>
mIgnoreResetTaskViews.remove(taskView);
}
+ private void addDismissedTaskAnimations(View taskView, AnimatorSet anim, long duration) {
+ addAnim(ObjectAnimator.ofFloat(taskView, ALPHA, 0), duration, ACCEL_2, anim);
+ addAnim(ObjectAnimator.ofFloat(taskView, TRANSLATION_Y, -taskView.getHeight()),
+ duration, LINEAR, anim);
+ }
+
+ private void removeTask(Task task, int index, PendingAnimation.OnEndListener onEndListener,
+ boolean shouldLog) {
+ if (task != null) {
+ ActivityManagerWrapper.getInstance().removeTask(task.key.id);
+ if (shouldLog) {
+ mActivity.getUserEventDispatcher().logTaskLaunchOrDismiss(
+ onEndListener.logAction, Direction.UP, index,
+ TaskUtils.getComponentKeyForTask(task.key));
+ }
+ }
+ }
+
public PendingAnimation createTaskDismissAnimation(TaskView taskView, boolean animateTaskView,
- boolean removeTask, long duration) {
+ boolean shouldRemoveTask, long duration) {
if (FeatureFlags.IS_DOGFOOD_BUILD && mPendingAnimation != null) {
throw new IllegalStateException("Another pending animation is still running");
}
@@ -659,26 +815,43 @@ public abstract class RecentsView<T extends BaseActivity>
int[] newScroll = new int[count];
getPageScrolls(newScroll, false, (v) -> v.getVisibility() != GONE && v != taskView);
- int maxScrollDiff = 0;
- int lastPage = mIsRtl ? 0 : count - 1;
- if (getChildAt(lastPage) == taskView) {
- if (count > 1) {
- int secondLastPage = mIsRtl ? 1 : count - 2;
- maxScrollDiff = oldScroll[lastPage] - newScroll[secondLastPage];
- }
+ int scrollDiffPerPage = 0;
+ int leftmostPage = mIsRtl ? count -1 : 0;
+ int rightmostPage = mIsRtl ? 0 : count - 1;
+ if (count > 1) {
+ int secondRightmostPage = mIsRtl ? 1 : count - 2;
+ scrollDiffPerPage = oldScroll[rightmostPage] - oldScroll[secondRightmostPage];
}
+ int draggedIndex = indexOfChild(taskView);
boolean needsCurveUpdates = false;
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
if (child == taskView) {
if (animateTaskView) {
- addAnim(ObjectAnimator.ofFloat(taskView, ALPHA, 0), duration, ACCEL_2, anim);
- addAnim(ObjectAnimator.ofFloat(taskView, TRANSLATION_Y, -taskView.getHeight()),
- duration, LINEAR, anim);
+ addDismissedTaskAnimations(taskView, anim, duration);
}
} else {
- int scrollDiff = newScroll[i] - oldScroll[i] + maxScrollDiff;
+ // If we just take newScroll - oldScroll, everything to the right of dragged task
+ // translates to the left. We need to offset this in some cases:
+ // - In RTL, add page offset to all pages, since we want pages to move to the right
+ // Additionally, add a page offset if:
+ // - Current page is rightmost page (leftmost for RTL)
+ // - Dragging an adjacent page on the left side (right side for RTL)
+ int offset = mIsRtl ? scrollDiffPerPage : 0;
+ if (mCurrentPage == draggedIndex) {
+ int lastPage = mIsRtl ? leftmostPage : rightmostPage;
+ if (mCurrentPage == lastPage) {
+ offset += mIsRtl ? -scrollDiffPerPage : scrollDiffPerPage;
+ }
+ } else {
+ // Dragging an adjacent page.
+ int negativeAdjacent = mCurrentPage - 1; // (Right in RTL, left in LTR)
+ if (draggedIndex == negativeAdjacent) {
+ offset += mIsRtl ? -scrollDiffPerPage : scrollDiffPerPage;
+ }
+ }
+ int scrollDiff = newScroll[i] - oldScroll[i] + offset;
if (scrollDiff != 0) {
addAnim(ObjectAnimator.ofFloat(child, TRANSLATION_X, scrollDiff),
duration, ACCEL, anim);
@@ -701,18 +874,18 @@ public abstract class RecentsView<T extends BaseActivity>
mPendingAnimation = pendingAnimation;
mPendingAnimation.addEndListener((onEndListener) -> {
if (onEndListener.isSuccess) {
- if (removeTask) {
- Task task = taskView.getTask();
- if (task != null) {
- ActivityManagerWrapper.getInstance().removeTask(task.key.id);
- mActivity.getUserEventDispatcher().logTaskLaunchOrDismiss(
- onEndListener.logAction, Direction.UP,
- TaskUtils.getComponentKeyForTask(task.key));
- }
+ if (shouldRemoveTask) {
+ removeTask(taskView.getTask(), draggedIndex, onEndListener, true);
+ }
+ int pageToSnapTo = mCurrentPage;
+ if (draggedIndex < pageToSnapTo) {
+ pageToSnapTo -= 1;
}
removeView(taskView);
if (getChildCount() == 0) {
onAllTasksRemoved();
+ } else if (!mIsClearAllButtonFullyRevealed) {
+ snapToPageImmediately(pageToSnapTo);
}
}
resetTaskVisuals();
@@ -721,6 +894,33 @@ public abstract class RecentsView<T extends BaseActivity>
return pendingAnimation;
}
+ public PendingAnimation createAllTasksDismissAnimation(long duration) {
+ if (FeatureFlags.IS_DOGFOOD_BUILD && mPendingAnimation != null) {
+ throw new IllegalStateException("Another pending animation is still running");
+ }
+ AnimatorSet anim = new AnimatorSet();
+ PendingAnimation pendingAnimation = new PendingAnimation(anim);
+
+ int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ addDismissedTaskAnimations(getChildAt(i), anim, duration);
+ }
+
+ mPendingAnimation = pendingAnimation;
+ mPendingAnimation.addEndListener((onEndListener) -> {
+ if (onEndListener.isSuccess) {
+ while (getChildCount() != 0) {
+ TaskView taskView = getPageAt(getChildCount() - 1);
+ removeTask(taskView.getTask(), -1, onEndListener, false);
+ removeView(taskView);
+ }
+ onAllTasksRemoved();
+ }
+ mPendingAnimation = null;
+ });
+ return pendingAnimation;
+ }
+
private static void addAnim(ObjectAnimator anim, long duration,
TimeInterpolator interpolator, AnimatorSet set) {
anim.setDuration(duration).setInterpolator(interpolator);
@@ -744,9 +944,7 @@ public abstract class RecentsView<T extends BaseActivity>
}
}
- public void dismissTask(TaskView taskView, boolean animateTaskView, boolean removeTask) {
- PendingAnimation pendingAnim = createTaskDismissAnimation(taskView, animateTaskView,
- removeTask, DISMISS_TASK_DURATION);
+ private void runDismissAnimation(PendingAnimation pendingAnim) {
AnimatorPlaybackController controller = AnimatorPlaybackController.wrap(
pendingAnim.anim, DISMISS_TASK_DURATION);
controller.dispatchOnStart();
@@ -755,6 +953,15 @@ public abstract class RecentsView<T extends BaseActivity>
controller.start();
}
+ public void dismissTask(TaskView taskView, boolean animateTaskView, boolean removeTask) {
+ runDismissAnimation(createTaskDismissAnimation(taskView, animateTaskView, removeTask,
+ DISMISS_TASK_DURATION));
+ }
+
+ public void dismissAllTasks() {
+ runDismissAnimation(createAllTasksDismissAnimation(DISMISS_TASK_DURATION));
+ }
+
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
if (event.getAction() == KeyEvent.ACTION_DOWN) {
@@ -789,21 +996,24 @@ public abstract class RecentsView<T extends BaseActivity>
snapToPageRelative(1);
}
- public void setContentAlpha(float alpha) {
- if (mContentAlpha == alpha) {
- return;
- }
+ public float getContentAlpha() {
+ return mContentAlpha;
+ }
+ public void setContentAlpha(float alpha) {
+ alpha = Utilities.boundToRange(alpha, 0, 1);
mContentAlpha = alpha;
for (int i = getChildCount() - 1; i >= 0; i--) {
- getChildAt(i).setAlpha(alpha);
+ TaskView child = getPageAt(i);
+ if (!mRunningTaskTileHidden || child.getTask().key.id != mRunningTaskId) {
+ getChildAt(i).setAlpha(alpha);
+ }
}
int alphaInt = Math.round(alpha * 255);
mEmptyMessagePaint.setAlpha(alphaInt);
mEmptyIcon.setAlpha(alphaInt);
-
- setVisibility(alpha > 0 ? VISIBLE : GONE);
+ updateClearAllButtonAlpha();
}
public void setAdjacentScale(float adjacentScale) {
@@ -815,15 +1025,13 @@ public abstract class RecentsView<T extends BaseActivity>
if (currTask == null) {
return;
}
- currTask.setScaleX(mAdjacentScale);
- currTask.setScaleY(mAdjacentScale);
+ currTask.setZoomScale(mAdjacentScale);
if (mCurrentPage - 1 >= 0) {
TaskView adjacentTask = getPageAt(mCurrentPage - 1);
float[] scaleAndTranslation = getAdjacentScaleAndTranslation(currTask, adjacentTask,
mAdjacentScale, 0);
- adjacentTask.setScaleX(scaleAndTranslation[0]);
- adjacentTask.setScaleY(scaleAndTranslation[0]);
+ adjacentTask.setZoomScale(scaleAndTranslation[0]);
adjacentTask.setTranslationX(-scaleAndTranslation[1]);
adjacentTask.setTranslationY(scaleAndTranslation[2]);
}
@@ -831,8 +1039,7 @@ public abstract class RecentsView<T extends BaseActivity>
TaskView adjacentTask = getPageAt(mCurrentPage + 1);
float[] scaleAndTranslation = getAdjacentScaleAndTranslation(currTask, adjacentTask,
mAdjacentScale, 0);
- adjacentTask.setScaleX(scaleAndTranslation[0]);
- adjacentTask.setScaleY(scaleAndTranslation[0]);
+ adjacentTask.setZoomScale(scaleAndTranslation[0]);
adjacentTask.setTranslationX(scaleAndTranslation[1]);
adjacentTask.setTranslationY(scaleAndTranslation[2]);
}
@@ -841,11 +1048,10 @@ public abstract class RecentsView<T extends BaseActivity>
private float[] getAdjacentScaleAndTranslation(TaskView currTask, TaskView adjacentTask,
float currTaskToScale, float currTaskToTranslationY) {
float displacement = currTask.getWidth() * (currTaskToScale - currTask.getCurveScale());
- return new float[] {
- currTaskToScale * adjacentTask.getCurveScale(),
- mIsRtl ? -displacement : displacement,
- currTaskToTranslationY
- };
+ sTempFloatArray[0] = currTaskToScale;
+ sTempFloatArray[1] = mIsRtl ? -displacement : displacement;
+ sTempFloatArray[2] = currTaskToTranslationY;
+ return sTempFloatArray;
}
@Override
@@ -853,6 +1059,7 @@ public abstract class RecentsView<T extends BaseActivity>
super.onViewAdded(child);
child.setAlpha(mContentAlpha);
setAdjacentScale(mAdjacentScale);
+ onChildViewsChanged();
}
@Override
@@ -883,10 +1090,11 @@ public abstract class RecentsView<T extends BaseActivity>
boolean hasValidSize = getWidth() > 0 && getHeight() > 0;
if (sizeChanged && hasValidSize) {
mEmptyTextLayout = null;
+ mLastMeasureSize.set(getWidth(), getHeight());
}
+ updateClearAllButtonAlpha();
if (mShowEmptyMessage && hasValidSize && mEmptyTextLayout == null) {
- mLastMeasureSize.set(getWidth(), getHeight());
int availableWidth = mLastMeasureSize.x - mEmptyMessagePadding - mEmptyMessagePadding;
mEmptyTextLayout = StaticLayout.Builder.obtain(mEmptyMessage, 0, mEmptyMessage.length(),
mEmptyMessagePaint, availableWidth)
@@ -909,8 +1117,13 @@ public abstract class RecentsView<T extends BaseActivity>
protected void maybeDrawEmptyMessage(Canvas canvas) {
if (mShowEmptyMessage && mEmptyTextLayout != null) {
- mEmptyIcon.draw(canvas);
+ // Offset to center in the visible (non-padded) part of RecentsView
+ mTempRect.set(mInsets.left + getPaddingLeft(), mInsets.top + getPaddingTop(),
+ mInsets.right + getPaddingRight(), mInsets.bottom + getPaddingBottom());
canvas.save();
+ canvas.translate(getScrollX() + (mTempRect.left - mTempRect.right) / 2,
+ (mTempRect.top - mTempRect.bottom) / 2);
+ mEmptyIcon.draw(canvas);
canvas.translate(mEmptyMessagePadding,
mEmptyIcon.getBounds().bottom + mEmptyMessagePadding);
mEmptyTextLayout.draw(canvas);
@@ -924,17 +1137,18 @@ public abstract class RecentsView<T extends BaseActivity>
* If launching one of the adjacent tasks, parallax the center task and other adjacent task
* to the right.
*/
- public AnimatorSet createAdjacentPageAnimForTaskLaunch(TaskView tv) {
+ public AnimatorSet createAdjacentPageAnimForTaskLaunch(
+ TaskView tv, ClipAnimationHelper clipAnimationHelper) {
AnimatorSet anim = new AnimatorSet();
int taskIndex = indexOfChild(tv);
int centerTaskIndex = getCurrentPage();
boolean launchingCenterTask = taskIndex == centerTaskIndex;
- TaskWindowBounds endInterpolation = tv.getRecentsInterpolator().interpolate(1);
- float toScale = endInterpolation.taskScale;
- float toTranslationY = endInterpolation.taskY;
-
+ float toScale = clipAnimationHelper.getSourceRect().width()
+ / clipAnimationHelper.getTargetRect().width();
+ float toTranslationY = clipAnimationHelper.getSourceRect().centerY()
+ - clipAnimationHelper.getTargetRect().centerY();
if (launchingCenterTask) {
TaskView centerTask = getPageAt(centerTaskIndex);
if (taskIndex - 1 >= 0) {
@@ -968,68 +1182,143 @@ public abstract class RecentsView<T extends BaseActivity>
return anim;
}
- private ObjectAnimator createAnimForChild(View child, float[] toScaleAndTranslation) {
- return ObjectAnimator.ofPropertyValuesHolder(child,
+ private Animator createAnimForChild(TaskView child, float[] toScaleAndTranslation) {
+ AnimatorSet anim = new AnimatorSet();
+ anim.play(ObjectAnimator.ofFloat(child, TaskView.ZOOM_SCALE, toScaleAndTranslation[0]));
+ anim.play(ObjectAnimator.ofPropertyValuesHolder(child,
new PropertyListBuilder()
- .scale(child.getScaleX() * toScaleAndTranslation[0])
.translationX(toScaleAndTranslation[1])
.translationY(toScaleAndTranslation[2])
- .build());
+ .build()));
+ return anim;
}
public PendingAnimation createTaskLauncherAnimation(TaskView tv, long duration) {
if (FeatureFlags.IS_DOGFOOD_BUILD && mPendingAnimation != null) {
throw new IllegalStateException("Another pending animation is still running");
}
- AnimatorSet anim = createAdjacentPageAnimForTaskLaunch(tv);
int count = getChildCount();
if (count == 0) {
- return new PendingAnimation(anim);
- }
-
- final RecentsAnimationInterpolator recentsInterpolator = tv.getRecentsInterpolator();
- ValueAnimator targetViewAnim = ValueAnimator.ofFloat(0, 1);
- targetViewAnim.addUpdateListener((animation) -> {
- float percent = animation.getAnimatedFraction();
- TaskWindowBounds tw = recentsInterpolator.interpolate(percent);
- tv.setScaleX(tw.taskScale);
- tv.setScaleY(tw.taskScale);
- tv.setTranslationX(tw.taskX);
- tv.setTranslationY(tw.taskY);
+ return new PendingAnimation(new AnimatorSet());
+ }
+
+ tv.setVisibility(INVISIBLE);
+ int targetSysUiFlags = tv.getThumbnail().getSysUiStatusNavFlags();
+ TaskViewDrawable drawable = new TaskViewDrawable(tv, this);
+ getOverlay().add(drawable);
+
+ ObjectAnimator drawableAnim =
+ ObjectAnimator.ofFloat(drawable, TaskViewDrawable.PROGRESS, 1, 0);
+ drawableAnim.setInterpolator(LINEAR);
+ drawableAnim.addUpdateListener((animator) -> {
+ // Once we pass a certain threshold, update the sysui flags to match the target tasks'
+ // flags
+ mActivity.getSystemUiController().updateUiState(UI_STATE_OVERVIEW,
+ animator.getAnimatedFraction() > UPDATE_SYSUI_FLAGS_THRESHOLD
+ ? targetSysUiFlags
+ : 0);
});
- anim.play(targetViewAnim);
+
+ AnimatorSet anim = createAdjacentPageAnimForTaskLaunch(tv,
+ drawable.getClipAnimationHelper());
+ anim.play(drawableAnim);
anim.setDuration(duration);
+ Consumer<Boolean> onTaskLaunchFinish = (result) -> {
+ onTaskLaunched(result);
+ tv.setVisibility(VISIBLE);
+ getOverlay().remove(drawable);
+ };
+
mPendingAnimation = new PendingAnimation(anim);
mPendingAnimation.addEndListener((onEndListener) -> {
if (onEndListener.isSuccess) {
- tv.launchTask(false);
+ Consumer<Boolean> onLaunchResult = (result) -> {
+ onTaskLaunchFinish.accept(result);
+ if (!result) {
+ tv.notifyTaskLaunchFailed(TAG);
+ }
+ };
+ tv.launchTask(false, onLaunchResult, getHandler());
Task task = tv.getTask();
if (task != null) {
mActivity.getUserEventDispatcher().logTaskLaunchOrDismiss(
- onEndListener.logAction, Direction.DOWN,
+ onEndListener.logAction, Direction.DOWN, indexOfChild(tv),
TaskUtils.getComponentKeyForTask(task.key));
}
} else {
- resetTaskVisuals();
+ onTaskLaunchFinish.accept(false);
}
mPendingAnimation = null;
});
return mPendingAnimation;
}
+ public abstract boolean shouldUseMultiWindowTaskSizeStrategy();
+
+ protected void onTaskLaunched(boolean success) {
+ resetTaskVisuals();
+ }
+
@Override
protected void notifyPageSwitchListener(int prevPage) {
super.notifyPageSwitchListener(prevPage);
- View currChild = getChildAt(mCurrentPage);
- if (currChild != null) {
- currChild.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
- }
+ loadVisibleTaskData();
}
@Override
protected String getCurrentPageDescription() {
return "";
}
+
+ private int additionalScrollForClearAllButton() {
+ return (int) getResources().getDimension(
+ R.dimen.clear_all_container_width) - getPaddingEnd();
+ }
+
+ @Override
+ protected int computeMaxScrollX() {
+ if (getChildCount() == 0) {
+ return super.computeMaxScrollX();
+ }
+
+ // Allow a clear_all_container_width-sized gap after the last task.
+ return super.computeMaxScrollX() + (mIsRtl ? 0 : additionalScrollForClearAllButton());
+ }
+
+ @Override
+ protected int offsetForPageScrolls() {
+ return mIsRtl ? additionalScrollForClearAllButton() : 0;
+ }
+
+ public void setClearAllButton(View clearAllButton) {
+ mClearAllButton = clearAllButton;
+ updateClearAllButtonAlpha();
+ }
+
+ private void onChildViewsChanged() {
+ final int childCount = getChildCount();
+ mClearAllButton.setVisibility(childCount == 0 ? INVISIBLE : VISIBLE);
+ }
+
+ public void revealClearAllButton() {
+ scrollTo(mIsRtl ? 0 : computeMaxScrollX(), 0);
+ }
+
+ @Override
+ public void addChildrenForAccessibility(ArrayList<View> outChildren) {
+ if (FLIP_RECENTS) {
+ for (int i = getChildCount() - 1; i >= 0; --i) {
+ outChildren.add(getChildAt(i));
+ }
+ } else {
+ super.addChildrenForAccessibility(outChildren);
+ }
+ }
+
+ @Override
+ protected boolean isPageOrderFlipped() {
+ return FLIP_RECENTS;
+ }
}
diff --git a/quickstep/src/com/android/quickstep/views/RecentsViewContainer.java b/quickstep/src/com/android/quickstep/views/RecentsViewContainer.java
new file mode 100644
index 000000000..429432b2f
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/views/RecentsViewContainer.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.views;
+
+import static com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch.TAP;
+import static com.android.launcher3.userevent.nano.LauncherLogProto.ControlType.CLEAR_ALL_BUTTON;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.util.FloatProperty;
+import android.view.Gravity;
+import android.view.MotionEvent;
+
+import com.android.launcher3.InsettableFrameLayout;
+import com.android.launcher3.R;
+
+public class RecentsViewContainer extends InsettableFrameLayout {
+ public static final FloatProperty<RecentsViewContainer> CONTENT_ALPHA =
+ new FloatProperty<RecentsViewContainer>("contentAlpha") {
+ @Override
+ public void setValue(RecentsViewContainer view, float v) {
+ view.setContentAlpha(v);
+ }
+
+ @Override
+ public Float get(RecentsViewContainer view) {
+ return view.mRecentsView.getContentAlpha();
+ }
+ };
+
+ private final Rect mTempRect = new Rect();
+
+ private RecentsView mRecentsView;
+ private ClearAllButton mClearAllButton;
+
+ public RecentsViewContainer(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+
+ mClearAllButton = findViewById(R.id.clear_all_button);
+ mClearAllButton.setOnClickListener((v) -> {
+ mRecentsView.mActivity.getUserEventDispatcher()
+ .logActionOnControl(TAP, CLEAR_ALL_BUTTON);
+ mRecentsView.dismissAllTasks();
+ });
+
+ mRecentsView = findViewById(R.id.overview_panel);
+ final InsettableFrameLayout.LayoutParams params =
+ (InsettableFrameLayout.LayoutParams) mClearAllButton.getLayoutParams();
+ params.gravity = Gravity.TOP | (RecentsView.FLIP_RECENTS ? Gravity.START : Gravity.END);
+ mClearAllButton.setLayoutParams(params);
+ mClearAllButton.forceHasOverlappingRendering(false);
+
+ mRecentsView.setClearAllButton(mClearAllButton);
+ mClearAllButton.setRecentsView(mRecentsView);
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ super.onLayout(changed, left, top, right, bottom);
+
+ mRecentsView.getTaskSize(mTempRect);
+
+ mClearAllButton.setTranslationX(
+ (mRecentsView.isRtl() ? 1 : -1) *
+ (getResources().getDimension(R.dimen.clear_all_container_width)
+ - mClearAllButton.getMeasuredWidth()) / 2);
+ mClearAllButton.setTranslationY(
+ mTempRect.top + (mTempRect.height() - mClearAllButton.getMeasuredHeight()) / 2
+ - mClearAllButton.getTop());
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent ev) {
+ super.onTouchEvent(ev);
+ // Do not let touch escape to siblings below this view. This prevents scrolling of the
+ // workspace while in Recents.
+ return true;
+ }
+
+ public void setContentAlpha(float alpha) {
+ if (alpha == mRecentsView.getContentAlpha()) {
+ return;
+ }
+ mRecentsView.setContentAlpha(alpha);
+ setVisibility(alpha > 0 ? VISIBLE : GONE);
+ }
+} \ No newline at end of file
diff --git a/quickstep/src/com/android/quickstep/views/ShelfScrimView.java b/quickstep/src/com/android/quickstep/views/ShelfScrimView.java
new file mode 100644
index 000000000..24afd4868
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/views/ShelfScrimView.java
@@ -0,0 +1,259 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep.views;
+
+import static android.support.v4.graphics.ColorUtils.compositeColors;
+import static android.support.v4.graphics.ColorUtils.setAlphaComponent;
+
+import static com.android.launcher3.LauncherState.OVERVIEW;
+import static com.android.launcher3.anim.Interpolators.ACCEL_2;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.Path.Direction;
+import android.graphics.Path.Op;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
+import android.util.AttributeSet;
+
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.R;
+import com.android.launcher3.uioverrides.OverviewState;
+import com.android.launcher3.util.Themes;
+import com.android.launcher3.views.ScrimView;
+
+/**
+ * Scrim used for all-apps and shelf in Overview
+ * In transposed layout, it behaves as a simple color scrim.
+ * In portrait layout, it draws a rounded rect such that
+ * From normal state to overview state, the shelf just fades in and does not move
+ * From overview state to all-apps state the shelf moves up and fades in to cover the screen
+ */
+public class ShelfScrimView extends ScrimView {
+
+ private static final int THRESHOLD_ALPHA_DARK = 102;
+ private static final int THRESHOLD_ALPHA_LIGHT = 46;
+ private static final int THRESHOLD_ALPHA_SUPER_LIGHT = 128;
+ private static final int CLEAR_ALL_TASKS = R.string.recents_clear_all;
+
+ // In transposed layout, we simply draw a flat color.
+ private boolean mDrawingFlatColor;
+
+ // For shelf mode
+ private final int mEndAlpha;
+ private final int mThresholdAlpha;
+ private final float mRadius;
+ private final float mMaxScrimAlpha;
+ private final Paint mPaint;
+
+ // Max vertical progress after which the scrim stops moving.
+ private float mMoveThreshold;
+ // Minimum visible size of the scrim.
+ private int mMinSize;
+
+ private float mScrimMoveFactor = 0;
+ private int mShelfColor;
+ private int mRemainingScreenColor;
+
+ private final Path mTempPath = new Path();
+ private final Path mRemainingScreenPath = new Path();
+ private boolean mRemainingScreenPathValid = false;
+
+ public ShelfScrimView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ mMaxScrimAlpha = OVERVIEW.getWorkspaceScrimAlpha(mLauncher);
+
+ mEndAlpha = Color.alpha(mEndScrim);
+ if (Themes.getAttrBoolean(mLauncher, R.attr.isMainColorDark)) {
+ mThresholdAlpha = THRESHOLD_ALPHA_DARK;
+ } else if (Themes.getAttrBoolean(mLauncher, R.attr.isWorkspaceDarkText)) {
+ mThresholdAlpha = THRESHOLD_ALPHA_SUPER_LIGHT;
+ } else {
+ mThresholdAlpha = THRESHOLD_ALPHA_LIGHT;
+ }
+ mRadius = mLauncher.getResources().getDimension(R.dimen.shelf_surface_radius);
+ mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+
+ // Just assume the easiest UI for now, until we have the proper layout information.
+ mDrawingFlatColor = true;
+ }
+
+ @Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ super.onSizeChanged(w, h, oldw, oldh);
+ mRemainingScreenPathValid = false;
+ }
+
+ @Override
+ public void reInitUi() {
+ DeviceProfile dp = mLauncher.getDeviceProfile();
+ mDrawingFlatColor = dp.isVerticalBarLayout();
+
+ if (!mDrawingFlatColor) {
+ float swipeLength = OverviewState.getDefaultSwipeHeight(mLauncher);
+ mMoveThreshold = 1 - swipeLength / mLauncher.getAllAppsController().getShiftRange();
+ mMinSize = dp.hotseatBarSizePx + dp.getInsets().bottom;
+ mRemainingScreenPathValid = false;
+ updateColors();
+ }
+ updateDragHandleAlpha();
+ invalidate();
+ }
+
+ @Override
+ public void updateColors() {
+ super.updateColors();
+ if (mDrawingFlatColor) {
+ return;
+ }
+
+ if (mProgress >= mMoveThreshold) {
+ mScrimMoveFactor = 1;
+
+ if (mProgress >= 1) {
+ mShelfColor = 0;
+ } else {
+ int alpha = Math.round(mThresholdAlpha * ACCEL_2.getInterpolation(
+ (1 - mProgress) / (1 - mMoveThreshold)));
+ mShelfColor = setAlphaComponent(mEndScrim, alpha);
+ }
+
+ mRemainingScreenColor = 0;
+ } else if (mProgress <= 0) {
+ mScrimMoveFactor = 0;
+ mShelfColor = mCurrentFlatColor;
+ mRemainingScreenColor = 0;
+
+ } else {
+ mScrimMoveFactor = mProgress / mMoveThreshold;
+ mRemainingScreenColor = setAlphaComponent(mScrimColor,
+ Math.round((1 - mScrimMoveFactor) * mMaxScrimAlpha * 255));
+
+ // Merge the remainingScreenColor and shelfColor in one to avoid overdraw.
+ int alpha = mEndAlpha - Math.round((mEndAlpha - mThresholdAlpha) * mScrimMoveFactor);
+ mShelfColor = compositeColors(setAlphaComponent(mEndScrim, alpha),
+ mRemainingScreenColor);
+ }
+ }
+
+ @Override
+ protected void updateDragHandleAlpha() {
+ if (mDrawingFlatColor) {
+ super.updateDragHandleAlpha();
+ } else if (mDragHandle != null) {
+ mDragHandle.setAlpha(255);
+ }
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ float translate = drawBackground(canvas);
+
+ if (mDragHandle != null) {
+ canvas.translate(0, -translate);
+ mDragHandle.draw(canvas);
+ canvas.translate(0, translate);
+ }
+ }
+
+ private float drawBackground(Canvas canvas) {
+ if (mDrawingFlatColor) {
+ if (mCurrentFlatColor != 0) {
+ canvas.drawColor(mCurrentFlatColor);
+ }
+ return 0;
+ }
+
+ if (mShelfColor == 0) {
+ return 0;
+ } else if (mScrimMoveFactor <= 0) {
+ canvas.drawColor(mShelfColor);
+ return getHeight();
+ }
+
+ float minTop = getHeight() - mMinSize;
+ float top = minTop * mScrimMoveFactor - mDragHandleSize;
+
+ // Draw the scrim over the remaining screen if needed.
+ if (mRemainingScreenColor != 0) {
+ if (!mRemainingScreenPathValid) {
+ mTempPath.reset();
+ // Using a arbitrary '+10' in the bottom to avoid any left-overs at the
+ // corners due to rounding issues.
+ mTempPath.addRoundRect(0, minTop, getWidth(), getHeight() + mRadius + 10,
+ mRadius, mRadius, Direction.CW);
+
+ mRemainingScreenPath.reset();
+ mRemainingScreenPath.addRect(0, 0, getWidth(), getHeight(), Direction.CW);
+ mRemainingScreenPath.op(mTempPath, Op.DIFFERENCE);
+ }
+
+ float offset = minTop - top;
+ canvas.translate(0, -offset);
+ mPaint.setColor(mRemainingScreenColor);
+ canvas.drawPath(mRemainingScreenPath, mPaint);
+ canvas.translate(0, offset);
+ }
+
+ mPaint.setColor(mShelfColor);
+ canvas.drawRoundRect(0, top, getWidth(), getHeight() + mRadius,
+ mRadius, mRadius, mPaint);
+ return minTop - mDragHandleSize - top;
+ }
+
+ @NonNull
+ @Override
+ protected AccessibilityHelper createAccessibilityHelper() {
+ return new ShelfScrimAccessibilityHelper();
+ }
+
+ protected class ShelfScrimAccessibilityHelper extends AccessibilityHelper {
+ @Override
+ protected void onPopulateNodeForVirtualView(int virtualViewId,
+ AccessibilityNodeInfoCompat node) {
+ super.onPopulateNodeForVirtualView(virtualViewId, node);
+
+ if (mLauncher.isInState(OVERVIEW)) {
+ final RecentsView overviewPanel = mLauncher.getOverviewPanel();
+ if (overviewPanel.getChildCount() != 0) {
+ node.addAction(
+ new AccessibilityNodeInfoCompat.AccessibilityActionCompat(
+ CLEAR_ALL_TASKS,
+ getContext().getText(CLEAR_ALL_TASKS)));
+ }
+ }
+ }
+
+ @Override
+ protected boolean onPerformActionForVirtualView(
+ int virtualViewId, int action, Bundle arguments) {
+ if (super.onPerformActionForVirtualView(virtualViewId, action, arguments)) return true;
+
+ if (action == CLEAR_ALL_TASKS) {
+ if (mLauncher.isInState(OVERVIEW)) {
+ mLauncher.<RecentsView>getOverviewPanel().dismissAllTasks();
+ }
+ return true;
+ }
+
+ return false;
+ }
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
index 58b7db7b2..128a19e06 100644
--- a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
@@ -16,6 +16,8 @@
package com.android.quickstep.views;
+import static com.android.systemui.shared.system.WindowManagerWrapper.WINDOWING_MODE_FULLSCREEN;
+
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Bitmap;
@@ -28,12 +30,15 @@ import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Shader;
import android.util.AttributeSet;
+import android.util.FloatProperty;
+import android.util.Property;
import android.view.View;
import com.android.launcher3.BaseActivity;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.R;
import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.util.SystemUiController;
import com.android.quickstep.TaskOverlayFactory;
import com.android.quickstep.TaskOverlayFactory.TaskOverlay;
import com.android.systemui.shared.recents.model.Task;
@@ -46,6 +51,19 @@ public class TaskThumbnailView extends View {
private static final LightingColorFilter[] sDimFilterCache = new LightingColorFilter[256];
+ public static final Property<TaskThumbnailView, Float> DIM_ALPHA_MULTIPLIER =
+ new FloatProperty<TaskThumbnailView>("dimAlphaMultiplier") {
+ @Override
+ public void setValue(TaskThumbnailView thumbnail, float dimAlphaMultiplier) {
+ thumbnail.setDimAlphaMultipler(dimAlphaMultiplier);
+ }
+
+ @Override
+ public Float get(TaskThumbnailView thumbnailView) {
+ return thumbnailView.mDimAlphaMultiplier;
+ }
+ };
+
private final float mCornerRadius;
private final BaseActivity mActivity;
@@ -62,6 +80,7 @@ public class TaskThumbnailView extends View {
protected BitmapShader mBitmapShader;
private float mDimAlpha = 1f;
+ private float mDimAlphaMultiplier = 1f;
public TaskThumbnailView(Context context) {
this(context, null);
@@ -109,8 +128,15 @@ public class TaskThumbnailView extends View {
updateThumbnailPaintFilter();
}
+ public void setDimAlphaMultipler(float dimAlphaMultipler) {
+ mDimAlphaMultiplier = dimAlphaMultipler;
+ setDimAlpha(mDimAlpha);
+ }
+
/**
* Sets the alpha of the dim layer on top of this view.
+ *
+ * If dimAlpha is 0, no dimming is applied; if dimAlpha is 1, the thumbnail will be black.
*/
public void setDimAlpha(float dimAlpha) {
mDimAlpha = dimAlpha;
@@ -124,32 +150,53 @@ public class TaskThumbnailView extends View {
return new Rect();
}
+ public int getSysUiStatusNavFlags() {
+ if (mThumbnailData != null) {
+ int flags = 0;
+ flags |= (mThumbnailData.systemUiVisibility & SYSTEM_UI_FLAG_LIGHT_STATUS_BAR) != 0
+ ? SystemUiController.FLAG_LIGHT_STATUS
+ : SystemUiController.FLAG_DARK_STATUS;
+ flags |= (mThumbnailData.systemUiVisibility & SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR) != 0
+ ? SystemUiController.FLAG_LIGHT_NAV
+ : SystemUiController.FLAG_DARK_NAV;
+ return flags;
+ }
+ return 0;
+ }
+
@Override
protected void onDraw(Canvas canvas) {
- if (mTask == null) {
- return;
+ drawOnCanvas(canvas, 0, 0, getMeasuredWidth(), getMeasuredHeight(), mCornerRadius);
+ }
+
+ public float getCornerRadius() {
+ return mCornerRadius;
+ }
+
+ public void drawOnCanvas(Canvas canvas, float x, float y, float width, float height,
+ float cornerRadius) {
+ // Draw the background in all cases, except when the thumbnail data is opaque
+ final boolean drawBackgroundOnly = mTask == null || mTask.isLocked || mBitmapShader == null
+ || mThumbnailData == null;
+ if (drawBackgroundOnly || mClipBottom > 0 || mThumbnailData.isTranslucent) {
+ canvas.drawRoundRect(x, y, width, height, cornerRadius, cornerRadius, mBackgroundPaint);
+ if (drawBackgroundOnly) {
+ return;
+ }
}
- int width = getMeasuredWidth();
- int height = getMeasuredHeight();
- if (mClipBottom > 0 && !mTask.isLocked) {
- canvas.save();
- canvas.clipRect(0, 0, width, mClipBottom);
- canvas.drawRoundRect(0, 0, width, height, mCornerRadius, mCornerRadius, mPaint);
- canvas.restore();
+ if (mClipBottom > 0) {
canvas.save();
- canvas.clipRect(0, mClipBottom, width, height);
- canvas.drawRoundRect(0, 0, width, height, mCornerRadius, mCornerRadius,
- mBackgroundPaint);
+ canvas.clipRect(x, y, width, mClipBottom);
+ canvas.drawRoundRect(x, y, width, height, cornerRadius, cornerRadius, mPaint);
canvas.restore();
} else {
- canvas.drawRoundRect(0, 0, width, height, mCornerRadius,
- mCornerRadius, mTask.isLocked ? mBackgroundPaint : mPaint);
+ canvas.drawRoundRect(x, y, width, height, cornerRadius, cornerRadius, mPaint);
}
}
private void updateThumbnailPaintFilter() {
- int mul = (int) (mDimAlpha * 255);
+ int mul = (int) ((1 - mDimAlpha * mDimAlphaMultiplier) * 255);
if (mBitmapShader != null) {
LightingColorFilter filter = getLightingColorFilter(mul);
mPaint.setColorFilter(filter);
@@ -167,9 +214,9 @@ public class TaskThumbnailView extends View {
if (mBitmapShader != null && mThumbnailData != null) {
float scale = mThumbnailData.scale;
Rect thumbnailInsets = mThumbnailData.insets;
- float thumbnailWidth = mThumbnailData.thumbnail.getWidth() -
+ final float thumbnailWidth = mThumbnailData.thumbnail.getWidth() -
(thumbnailInsets.left + thumbnailInsets.right) * scale;
- float thumbnailHeight = mThumbnailData.thumbnail.getHeight() -
+ final float thumbnailHeight = mThumbnailData.thumbnail.getHeight() -
(thumbnailInsets.top + thumbnailInsets.bottom) * scale;
final float thumbnailScale;
@@ -185,7 +232,8 @@ public class TaskThumbnailView extends View {
// Rotate the screenshot if not in multi-window mode
rotate = FeatureFlags.OVERVIEW_USE_SCREENSHOT_ORIENTATION &&
configuration.orientation != mThumbnailData.orientation &&
- !mActivity.isInMultiWindowModeCompat();
+ !mActivity.isInMultiWindowModeCompat() &&
+ mThumbnailData.windowingMode == WINDOWING_MODE_FULLSCREEN;
// Scale the screenshot to always fit the width of the card.
thumbnailScale = rotate
? getMeasuredWidth() / thumbnailHeight
@@ -216,7 +264,8 @@ public class TaskThumbnailView extends View {
mMatrix.postScale(thumbnailScale, thumbnailScale);
mBitmapShader.setLocalMatrix(mMatrix);
- float bitmapHeight = Math.max(thumbnailHeight * thumbnailScale, 0);
+ float bitmapHeight = Math.max((rotate ? thumbnailWidth : thumbnailHeight)
+ * thumbnailScale, 0);
if (Math.round(bitmapHeight) < getMeasuredHeight()) {
mClipBottom = bitmapHeight;
}
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index f04acaf12..82aa45a18 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -16,28 +16,34 @@
package com.android.quickstep.views;
+import static android.widget.Toast.LENGTH_SHORT;
+import static com.android.quickstep.views.TaskThumbnailView.DIM_ALPHA_MULTIPLIER;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
import android.animation.TimeInterpolator;
import android.app.ActivityOptions;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Outline;
-import android.graphics.Rect;
import android.os.Bundle;
import android.os.Handler;
import android.util.AttributeSet;
+import android.util.FloatProperty;
+import android.util.Log;
+import android.util.Property;
import android.view.View;
import android.view.ViewOutlineProvider;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.FrameLayout;
-import android.widget.ImageView;
+import android.widget.Toast;
import com.android.launcher3.BaseActivity;
import com.android.launcher3.BaseDraggingActivity;
-import com.android.launcher3.DeviceProfile;
import com.android.launcher3.R;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
-import com.android.quickstep.RecentsAnimationInterpolator;
import com.android.quickstep.TaskSystemShortcut;
import com.android.quickstep.TaskUtils;
import com.android.quickstep.views.RecentsView.PageCallbacks;
@@ -54,6 +60,8 @@ import java.util.function.Consumer;
*/
public class TaskView extends FrameLayout implements TaskCallbacks, PageCallbacks {
+ private static final String TAG = TaskView.class.getSimpleName();
+
/** A curve of x from 0 to 1, where 0 is the center of the screen and 1 is the edge. */
private static final TimeInterpolator CURVE_INTERPOLATOR
= x -> (float) -Math.cos(x * Math.PI) / 2f + .5f;
@@ -69,12 +77,28 @@ public class TaskView extends FrameLayout implements TaskCallbacks, PageCallback
*/
private static final float EDGE_SCALE_DOWN_FACTOR = 0.03f;
- private static final long SCALE_ICON_DURATION = 120;
+ public static final long SCALE_ICON_DURATION = 120;
+ private static final long DIM_ANIM_DURATION = 700;
+
+ public static final Property<TaskView, Float> ZOOM_SCALE =
+ new FloatProperty<TaskView>("zoomScale") {
+ @Override
+ public void setValue(TaskView taskView, float v) {
+ taskView.setZoomScale(v);
+ }
+
+ @Override
+ public Float get(TaskView taskView) {
+ return taskView.mZoomScale;
+ }
+ };
private Task mTask;
private TaskThumbnailView mSnapshotView;
- private ImageView mIconView;
+ private IconView mIconView;
private float mCurveScale;
+ private float mZoomScale;
+ private Animator mDimAlphaAnim;
public TaskView(Context context) {
this(context, null);
@@ -87,11 +111,13 @@ public class TaskView extends FrameLayout implements TaskCallbacks, PageCallback
public TaskView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setOnClickListener((view) -> {
- if (mTask != null) {
- launchTask(true /* animate */);
- BaseActivity.fromContext(context).getUserEventDispatcher().logTaskLaunchOrDismiss(
- Touch.TAP, Direction.NONE, TaskUtils.getComponentKeyForTask(mTask.key));
+ if (getTask() == null) {
+ return;
}
+ launchTask(true /* animate */);
+ BaseActivity.fromContext(context).getUserEventDispatcher().logTaskLaunchOrDismiss(
+ Touch.TAP, Direction.NONE, ((RecentsView) getParent()).indexOfChild(this),
+ TaskUtils.getComponentKeyForTask(getTask().key));
});
setOutlineProvider(new TaskOutlineProvider(getResources()));
}
@@ -124,8 +150,16 @@ public class TaskView extends FrameLayout implements TaskCallbacks, PageCallback
return mSnapshotView;
}
+ public IconView getIconView() {
+ return mIconView;
+ }
+
public void launchTask(boolean animate) {
- launchTask(animate, null, null);
+ launchTask(animate, (result) -> {
+ if (!result) {
+ notifyTaskLaunchFailed(TAG);
+ }
+ }, getHandler());
}
public void launchTask(boolean animate, Consumer<Boolean> resultCallback,
@@ -146,7 +180,7 @@ public class TaskView extends FrameLayout implements TaskCallbacks, PageCallback
@Override
public void onTaskDataLoaded(Task task, ThumbnailData thumbnailData) {
mSnapshotView.setThumbnail(task, thumbnailData);
- mIconView.setImageDrawable(task.icon);
+ mIconView.setDrawable(task.icon);
mIconView.setOnClickListener(icon -> TaskMenuView.showForTask(this));
mIconView.setOnLongClickListener(icon -> {
requestDisallowInterceptTouchEvent(true);
@@ -157,7 +191,7 @@ public class TaskView extends FrameLayout implements TaskCallbacks, PageCallback
@Override
public void onTaskDataUnloaded() {
mSnapshotView.setThumbnail(null, null);
- mIconView.setImageDrawable(null);
+ mIconView.setDrawable(null);
mIconView.setOnLongClickListener(null);
}
@@ -166,23 +200,37 @@ public class TaskView extends FrameLayout implements TaskCallbacks, PageCallback
// Do nothing
}
- public void animateIconToScale(float scale) {
+ public void animateIconToScaleAndDim(float scale) {
mIconView.animate().scaleX(scale).scaleY(scale).setDuration(SCALE_ICON_DURATION).start();
+ mDimAlphaAnim = ObjectAnimator.ofFloat(mSnapshotView, DIM_ALPHA_MULTIPLIER, 1 - scale,
+ scale);
+ mDimAlphaAnim.setDuration(DIM_ANIM_DURATION);
+ mDimAlphaAnim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mDimAlphaAnim = null;
+ }
+ });
+ mDimAlphaAnim.start();
}
- protected void setIconScale(float iconScale) {
+ protected void setIconScaleAndDim(float iconScale) {
mIconView.animate().cancel();
mIconView.setScaleX(iconScale);
mIconView.setScaleY(iconScale);
+ if (mDimAlphaAnim != null) {
+ mDimAlphaAnim.cancel();
+ }
+ mSnapshotView.setDimAlphaMultipler(iconScale);
}
public void resetVisualProperties() {
- setScaleX(1f);
- setScaleY(1f);
+ setZoomScale(1);
setTranslationX(0f);
setTranslationY(0f);
setTranslationZ(0);
setAlpha(1f);
+ setIconScaleAndDim(1);
}
@Override
@@ -190,40 +238,52 @@ public class TaskView extends FrameLayout implements TaskCallbacks, PageCallback
float curveInterpolation =
CURVE_INTERPOLATOR.getInterpolation(scrollState.linearInterpolation);
- mSnapshotView.setDimAlpha(1 - curveInterpolation * MAX_PAGE_SCRIM_ALPHA);
+ mSnapshotView.setDimAlpha(curveInterpolation * MAX_PAGE_SCRIM_ALPHA);
+ setCurveScale(getCurveScaleForCurveInterpolation(curveInterpolation));
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ super.onLayout(changed, left, top, right, bottom);
+ setPivotX((right - left) * 0.5f);
+ setPivotY(mSnapshotView.getTop() + mSnapshotView.getHeight() * 0.5f);
+ }
+
+ public float getCurveScaleForInterpolation(float linearInterpolation) {
+ float curveInterpolation = CURVE_INTERPOLATOR.getInterpolation(linearInterpolation);
+ return getCurveScaleForCurveInterpolation(curveInterpolation);
+ }
+
+ private float getCurveScaleForCurveInterpolation(float curveInterpolation) {
+ return 1 - curveInterpolation * EDGE_SCALE_DOWN_FACTOR;
+ }
- mCurveScale = 1 - curveInterpolation * EDGE_SCALE_DOWN_FACTOR;
- setScaleX(mCurveScale);
- setScaleY(mCurveScale);
+ private void setCurveScale(float curveScale) {
+ mCurveScale = curveScale;
+ onScaleChanged();
}
public float getCurveScale() {
return mCurveScale;
}
+ public void setZoomScale(float adjacentScale) {
+ mZoomScale = adjacentScale;
+ onScaleChanged();
+ }
+
+ private void onScaleChanged() {
+ float scale = mCurveScale * mZoomScale;
+ setScaleX(scale);
+ setScaleY(scale);
+ }
+
@Override
public boolean hasOverlappingRendering() {
// TODO: Clip-out the icon region from the thumbnail, since they are overlapping.
return false;
}
- public RecentsAnimationInterpolator getRecentsInterpolator() {
- Rect taskViewBounds = new Rect();
- BaseDraggingActivity activity = BaseDraggingActivity.fromContext(getContext());
- DeviceProfile dp = activity.getDeviceProfile();
- activity.getDragLayer().getDescendantRectRelativeToSelf(this, taskViewBounds);
-
- // TODO: Use the actual target insets instead of the current thumbnail insets in case the
- // device state has changed
- return new RecentsAnimationInterpolator(
- new Rect(0, 0, dp.widthPx, dp.heightPx),
- getThumbnail().getInsets(),
- taskViewBounds,
- new Rect(0, getThumbnail().getTop(), 0, 0),
- getScaleX(),
- getTranslationX());
- }
-
private static final class TaskOutlineProvider extends ViewOutlineProvider {
private final int mMarginTop;
@@ -281,4 +341,13 @@ public class TaskView extends FrameLayout implements TaskCallbacks, PageCallback
return super.performAccessibilityAction(action, arguments);
}
+
+ public void notifyTaskLaunchFailed(String tag) {
+ String msg = "Failed to launch task";
+ if (mTask != null) {
+ msg += " (task=" + mTask.key.baseIntent + " userId=" + mTask.key.userId + ")";
+ }
+ Log.w(tag, msg);
+ Toast.makeText(getContext(), R.string.activity_not_available, LENGTH_SHORT).show();
+ }
}