Merge "Fix All Apps Search plugin shortcut icon redrawing issue." into ub-launcher3-master
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 23d62b3..a1988a1 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -95,8 +95,10 @@
import com.android.quickstep.util.InputConsumerProxy;
import com.android.quickstep.util.MotionPauseDetector;
import com.android.quickstep.util.ProtoTracer;
+import com.android.quickstep.util.RecentsOrientedState;
import com.android.quickstep.util.RectFSpringAnim;
import com.android.quickstep.util.SurfaceTransactionApplier;
+import com.android.quickstep.util.SwipePipToHomeAnimator;
import com.android.quickstep.util.TransformParams;
import com.android.quickstep.views.LiveTileOverlay;
import com.android.quickstep.views.RecentsView;
@@ -233,6 +235,10 @@
private final Runnable mOnDeferredActivityLaunch = this::onDeferredActivityLaunch;
+ private static final long SWIPE_PIP_TO_HOME_DURATION = 425;
+ private SwipePipToHomeAnimator mSwipePipToHomeAnimator;
+ protected boolean mIsSwipingPipToHome;
+
public AbsSwipeUpHandler(Context context, RecentsAnimationDeviceState deviceState,
TaskAnimationManager taskAnimationManager, GestureState gestureState,
long touchTimeMs, boolean continuingLastGesture,
@@ -1048,25 +1054,46 @@
if (mGestureState.getEndTarget() == HOME) {
mTaskViewSimulator.setDrawsBelowRecents(false);
- HomeAnimationFactory homeAnimFactory = createHomeAnimationFactory(duration);
- RectFSpringAnim windowAnim = createWindowAnimationToHome(start, homeAnimFactory);
- windowAnim.addAnimatorListener(new AnimationSuccessListener() {
- @Override
- public void onAnimationSuccess(Animator animator) {
- if (mRecentsAnimationController == null) {
- // If the recents animation is interrupted, we still end the running
- // animation (not canceled) so this is still called. In that case, we can
- // skip doing any future work here for the current gesture.
- return;
- }
- // Finalize the state and notify of the change
- mGestureState.setState(STATE_END_TARGET_ANIMATION_FINISHED);
- }
- });
getOrientationHandler().adjustFloatingIconStartVelocity(velocityPxPerMs);
- windowAnim.start(mContext, velocityPxPerMs);
+ final RemoteAnimationTargetCompat runningTaskTarget = mRecentsAnimationTargets != null
+ ? mRecentsAnimationTargets.findTask(mGestureState.getRunningTaskId())
+ : null;
+ HomeAnimationFactory homeAnimFactory = createHomeAnimationFactory(duration);
+ mIsSwipingPipToHome = homeAnimFactory.supportSwipePipToHome()
+ && runningTaskTarget != null
+ && runningTaskTarget.pictureInPictureParams != null
+ && TaskInfoCompat.isAutoEnterPipEnabled(
+ runningTaskTarget.pictureInPictureParams)
+ && TaskInfoCompat.getPipSourceRectHint(
+ runningTaskTarget.pictureInPictureParams) != null;
+ if (mIsSwipingPipToHome) {
+ mSwipePipToHomeAnimator = getSwipePipToHomeAnimator(
+ homeAnimFactory, runningTaskTarget);
+ mSwipePipToHomeAnimator.setDuration(SWIPE_PIP_TO_HOME_DURATION);
+ mSwipePipToHomeAnimator.setInterpolator(interpolator);
+ mSwipePipToHomeAnimator.setFloatValues(0f, 1f);
+ mSwipePipToHomeAnimator.start();
+ mRunningWindowAnim = RunningWindowAnim.wrap(mSwipePipToHomeAnimator);
+ } else {
+ mSwipePipToHomeAnimator = null;
+ RectFSpringAnim windowAnim = createWindowAnimationToHome(start, homeAnimFactory);
+ windowAnim.addAnimatorListener(new AnimationSuccessListener() {
+ @Override
+ public void onAnimationSuccess(Animator animator) {
+ if (mRecentsAnimationController == null) {
+ // If the recents animation is interrupted, we still end the running
+ // animation (not canceled) so this is still called. In that case,
+ // we can skip doing any future work here for the current gesture.
+ return;
+ }
+ // Finalize the state and notify of the change
+ mGestureState.setState(STATE_END_TARGET_ANIMATION_FINISHED);
+ }
+ });
+ windowAnim.start(mContext, velocityPxPerMs);
+ mRunningWindowAnim = RunningWindowAnim.wrap(windowAnim);
+ }
homeAnimFactory.playAtomicAnimation(velocityPxPerMs.y);
- mRunningWindowAnim = RunningWindowAnim.wrap(windowAnim);
mLauncherTransitionController = null;
} else {
ValueAnimator windowAnim = mCurrentShift.animateToValue(start, end);
@@ -1110,6 +1137,47 @@
}
}
+ private SwipePipToHomeAnimator getSwipePipToHomeAnimator(HomeAnimationFactory homeAnimFactory,
+ RemoteAnimationTargetCompat runningTaskTarget) {
+ // Directly animate the app to PiP (picture-in-picture) mode
+ final ActivityManager.RunningTaskInfo taskInfo = mGestureState.getRunningTask();
+ final RecentsOrientedState orientationState = mTaskViewSimulator.getOrientationState();
+ final Rect destinationBounds = SystemUiProxy.INSTANCE.get(mContext)
+ .startSwipePipToHome(taskInfo.topActivity,
+ TaskInfoCompat.getTopActivityInfo(taskInfo),
+ runningTaskTarget.pictureInPictureParams,
+ orientationState.getRecentsActivityRotation(),
+ mDp.hotseatBarSizePx);
+ final SwipePipToHomeAnimator swipePipToHomeAnimator = new SwipePipToHomeAnimator(
+ runningTaskTarget.taskId,
+ taskInfo.topActivity,
+ runningTaskTarget.leash.getSurfaceControl(),
+ TaskInfoCompat.getPipSourceRectHint(runningTaskTarget.pictureInPictureParams),
+ TaskInfoCompat.getWindowConfigurationBounds(taskInfo),
+ destinationBounds);
+ swipePipToHomeAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ // Ensure Launcher ends in NORMAL state, we intentionally skip other callbacks
+ // since they are not relevant in this swipe-pip-to-home case.
+ homeAnimFactory.createActivityAnimationToHome().dispatchOnStart();
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (mRecentsAnimationController == null) {
+ // If the recents animation is interrupted, we still end the running
+ // animation (not canceled) so this is still called. In that case, we can
+ // skip doing any future work here for the current gesture.
+ return;
+ }
+ // Finalize the state and notify of the change
+ mGestureState.setState(STATE_END_TARGET_ANIMATION_FINISHED);
+ }
+ });
+ return swipePipToHomeAnimator;
+ }
+
private void computeRecentsScrollIfInvisible() {
if (mRecentsView != null && mRecentsView.getVisibility() != View.VISIBLE) {
// Views typically don't compute scroll when invisible as an optimization,
@@ -1367,6 +1435,7 @@
// If there are no targets or the animation not started, then there is nothing to finish
mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED);
} else {
+ maybeFinishSwipePipToHome();
finishRecentsControllerToHome(
() -> mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED));
}
@@ -1374,6 +1443,22 @@
doLogGesture(HOME, mRecentsView == null ? null : mRecentsView.getCurrentPageTaskView());
}
+ /**
+ * Resets the {@link #mIsSwipingPipToHome} and notifies SysUI that transition is finished
+ * if applicable. This should happen before {@link #finishRecentsControllerToHome(Runnable)}.
+ */
+ private void maybeFinishSwipePipToHome() {
+ if (mIsSwipingPipToHome && mSwipePipToHomeAnimator != null) {
+ SystemUiProxy.INSTANCE.get(mContext).stopSwipePipToHome(
+ mSwipePipToHomeAnimator.getComponentName(),
+ mSwipePipToHomeAnimator.getDestinationBounds());
+ mRecentsAnimationController.setFinishTaskBounds(
+ mSwipePipToHomeAnimator.getTaskId(),
+ mSwipePipToHomeAnimator.getDestinationBounds());
+ mIsSwipingPipToHome = false;
+ }
+ }
+
protected abstract void finishRecentsControllerToHome(Runnable callback);
private void setupLauncherUiAfterSwipeUpToRecentsAnimation() {
diff --git a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
index 4411455..842fb84 100644
--- a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
+++ b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
@@ -56,6 +56,7 @@
final TaskView runningTaskView = mRecentsView.getRunningTaskView();
final View workspaceView;
if (runningTaskView != null
+ && !mIsSwipingPipToHome
&& runningTaskView.getTask().key.getComponent() != null) {
workspaceView = mActivity.getWorkspace().getFirstMatchForAppClose(
runningTaskView.getTask().key.getComponent().getPackageName(),
@@ -140,5 +141,10 @@
new StaggeredWorkspaceAnim(mActivity, velocity,
true /* animateOverviewScrim */).start();
}
+
+ @Override
+ public boolean supportSwipePipToHome() {
+ return true;
+ }
}
}
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationController.java b/quickstep/src/com/android/quickstep/RecentsAnimationController.java
index 62bbf4d..7de658c 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationController.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationController.java
@@ -18,6 +18,8 @@
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+import android.graphics.Rect;
+
import androidx.annotation.NonNull;
import androidx.annotation.UiThread;
@@ -140,6 +142,18 @@
}
/**
+ * Sets the final bounds on a Task. This is used by Launcher to notify the system that
+ * animating Activity to PiP has completed and the associated task surface should be updated
+ * accordingly. This should be called before `finish`
+ * @param taskId for which the leash should be updated
+ * @param destinationBounds bounds of the final PiP window
+ */
+ public void setFinishTaskBounds(int taskId, Rect destinationBounds) {
+ UI_HELPER_EXECUTOR.execute(
+ () -> mController.setFinishTaskBounds(taskId, destinationBounds));
+ }
+
+ /**
* Enables the input consumer to start intercepting touches in the app window.
*/
public void enableInputConsumer() {
diff --git a/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java b/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
index 7406dea..8f7ec3b 100644
--- a/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
+++ b/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
@@ -165,6 +165,14 @@
public void update(RectF currentRect, float progress, float radius) { }
public void onCancel() { }
+
+ /**
+ * @return {@code true} if this factory supports animating an Activity to PiP window on
+ * swiping up to home.
+ */
+ public boolean supportSwipePipToHome() {
+ return false;
+ }
}
/**
diff --git a/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java b/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
new file mode 100644
index 0000000..87fee79
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2020 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.RectEvaluator;
+import android.animation.ValueAnimator;
+import android.content.ComponentName;
+import android.graphics.Rect;
+import android.view.SurfaceControl;
+
+import androidx.annotation.NonNull;
+
+import com.android.systemui.shared.pip.PipSurfaceTransactionHelper;
+
+/**
+ * An {@link Animator} that animates an Activity to PiP (picture-in-picture) window when
+ * swiping up (in gesture navigation mode). Note that this class is derived from
+ * {@link com.android.wm.shell.pip.PipAnimationController.PipTransitionAnimator}.
+ *
+ * TODO: consider sharing this class including the animator and leash operations between
+ * Launcher and SysUI. Also, there should be one source of truth for the corner radius of the
+ * PiP window, which would ideally be on SysUI side as well.
+ */
+public class SwipePipToHomeAnimator extends ValueAnimator implements
+ ValueAnimator.AnimatorUpdateListener {
+ private final int mTaskId;
+ private final ComponentName mComponentName;
+ private final SurfaceControl mLeash;
+ private final Rect mStartBounds = new Rect();
+ private final Rect mDestinationBounds = new Rect();
+ private final PipSurfaceTransactionHelper mSurfaceTransactionHelper;
+
+ /** for calculating the transform in {@link #onAnimationUpdate(ValueAnimator)} */
+ private final RectEvaluator mRectEvaluator = new RectEvaluator(new Rect());
+ private final RectEvaluator mInsetsEvaluator = new RectEvaluator(new Rect());
+ private final Rect mSourceHintRectInsets = new Rect();
+ private final Rect mSourceInsets = new Rect();
+
+ /**
+ * Flag to avoid the double-end problem since the leash would have been released
+ * after the first end call and any further operations upon it would lead to NPE.
+ */
+ private boolean mHasAnimationEnded;
+
+ public SwipePipToHomeAnimator(int taskId,
+ @NonNull ComponentName componentName,
+ @NonNull SurfaceControl leash,
+ @NonNull Rect sourceRectHint,
+ @NonNull Rect startBounds,
+ @NonNull Rect destinationBounds) {
+ mTaskId = taskId;
+ mComponentName = componentName;
+ mLeash = leash;
+ mStartBounds.set(startBounds);
+ mDestinationBounds.set(destinationBounds);
+ mSurfaceTransactionHelper = new PipSurfaceTransactionHelper();
+
+ mSourceHintRectInsets.set(sourceRectHint.left - startBounds.left,
+ sourceRectHint.top - startBounds.top,
+ startBounds.right - sourceRectHint.right,
+ startBounds.bottom - sourceRectHint.bottom);
+
+ addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ SwipePipToHomeAnimator.this.onAnimationEnd();
+ }
+ });
+ addUpdateListener(this);
+ }
+
+ @Override
+ public void onAnimationUpdate(ValueAnimator animator) {
+ if (mHasAnimationEnded) return;
+
+ final float fraction = animator.getAnimatedFraction();
+ final Rect bounds = mRectEvaluator.evaluate(fraction, mStartBounds, mDestinationBounds);
+ final Rect insets = mInsetsEvaluator.evaluate(fraction, mSourceInsets,
+ mSourceHintRectInsets);
+ final SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
+ mSurfaceTransactionHelper.scaleAndCrop(tx, mLeash, mStartBounds, bounds, insets);
+ mSurfaceTransactionHelper.resetCornerRadius(tx, mLeash);
+ tx.apply();
+ }
+
+ public int getTaskId() {
+ return mTaskId;
+ }
+
+ public ComponentName getComponentName() {
+ return mComponentName;
+ }
+
+ public Rect getDestinationBounds() {
+ return mDestinationBounds;
+ }
+
+ private void onAnimationEnd() {
+ if (mHasAnimationEnded) return;
+
+ final SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
+ mSurfaceTransactionHelper.reset(tx, mLeash, mDestinationBounds);
+ tx.apply();
+ mHasAnimationEnded = true;
+ }
+}
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index dfff358..777ea3c 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -139,6 +139,10 @@
public static final int DEFAULT_PAGE = 0;
+ private static final int DEFAULT_SMARTSPACE_HEIGHT = 1;
+
+ private static final int EXPANDED_SMARTSPACE_HEIGHT = 2;
+
private LayoutTransition mLayoutTransition;
@Thunk final WallpaperManager mWallpaperManager;
@@ -507,7 +511,10 @@
.inflate(R.layout.search_container_workspace, firstPage, false);
}
- CellLayout.LayoutParams lp = new CellLayout.LayoutParams(0, 0, firstPage.getCountX(), 1);
+ int cellVSpan = FeatureFlags.EXPANDED_SMARTSPACE.get()
+ ? EXPANDED_SMARTSPACE_HEIGHT : DEFAULT_SMARTSPACE_HEIGHT;
+ CellLayout.LayoutParams lp = new CellLayout.LayoutParams(0, 0, firstPage.getCountX(),
+ cellVSpan);
lp.canReorder = false;
if (!firstPage.addViewToCellLayout(qsb, 0, R.id.search_container_workspace, lp, true)) {
Log.e(TAG, "Failed to add to item at (0, 0) to CellLayout");
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index 4beaf6e..8e6c2a7 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -185,6 +185,10 @@
"ENABLE_MINIMAL_DEVICE", false,
"Allow user to toggle minimal device mode in launcher.");
+ public static final BooleanFlag EXPANDED_SMARTSPACE = new DeviceFlag(
+ "EXPANDED_SMARTSPACE", false, "Expands smartspace height to two rows. "
+ + "Any apps occupying the first row will be removed from workspace.");
+
public static void initialize(Context context) {
synchronized (sDebugFlags) {
for (DebugFlag flag : sDebugFlags) {
diff --git a/src/com/android/launcher3/model/LoaderCursor.java b/src/com/android/launcher3/model/LoaderCursor.java
index a27ac23..532834e 100644
--- a/src/com/android/launcher3/model/LoaderCursor.java
+++ b/src/com/android/launcher3/model/LoaderCursor.java
@@ -445,7 +445,8 @@
if (item.screenId == Workspace.FIRST_SCREEN_ID) {
// Mark the first row as occupied (if the feature is enabled)
// in order to account for the QSB.
- screen.markCells(0, 0, countX + 1, 1, FeatureFlags.QSB_ON_FIRST_SCREEN);
+ int spanY = FeatureFlags.EXPANDED_SMARTSPACE.get() ? 2 : 1;
+ screen.markCells(0, 0, countX + 1, spanY, FeatureFlags.QSB_ON_FIRST_SCREEN);
}
occupied.put(item.screenId, screen);
}