Merge "Can now swipe away to dismiss second task" into ub-launcher3-master
diff --git a/proguard.flags b/proguard.flags
index 086337d..e9f6db4 100644
--- a/proguard.flags
+++ b/proguard.flags
@@ -107,6 +107,11 @@
public <init>(...);
}
+# MainProcessInitializer
+-keep class com.android.quickstep.QuickstepProcessInitializer {
+ public <init>(...);
+}
+
-keep interface com.android.launcher3.userevent.nano.LauncherLogProto.** {
*;
}
diff --git a/quickstep/libs/sysui_shared.jar b/quickstep/libs/sysui_shared.jar
index b75a193..69ce74a 100644
--- a/quickstep/libs/sysui_shared.jar
+++ b/quickstep/libs/sysui_shared.jar
Binary files differ
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index ab6d8af..c741913 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -20,7 +20,6 @@
<dimen name="task_thumbnail_icon_size">48dp</dimen>
<dimen name="task_menu_background_radius">12dp</dimen>
<dimen name="task_corner_radius">2dp</dimen>
- <dimen name="task_fade_length">20dp</dimen>
<dimen name="recents_page_spacing">10dp</dimen>
<!-- The speed in dp/s at which the user needs to be scrolling in recents such that we start
@@ -37,4 +36,13 @@
<dimen name="recents_empty_message_text_size">16sp</dimen>
<dimen name="recents_empty_message_text_padding">16dp</dimen>
+ <!-- Total space (start + end) between the task card and the edge of the screen
+ in various configurations -->
+ <dimen name="task_card_vert_space">40dp</dimen>
+ <dimen name="portrait_task_card_horz_space">136dp</dimen>
+ <dimen name="landscape_task_card_horz_space">200dp</dimen>
+ <dimen name="multi_window_task_card_horz_space">100dp</dimen>
+ <!-- Copied from framework resource:
+ docked_stack_divider_thickness - 2 * docked_stack_divider_insets -->
+ <dimen name="multi_window_task_divider_size">10dp</dimen>
</resources>
diff --git a/quickstep/res/values/override.xml b/quickstep/res/values/override.xml
index 2bd9f8f..d683659 100644
--- a/quickstep/res/values/override.xml
+++ b/quickstep/res/values/override.xml
@@ -18,5 +18,7 @@
<string name="app_transition_manager_class" translatable="false">com.android.launcher3.LauncherAppTransitionManagerImpl</string>
<string name="instant_app_resolver_class" translatable="false">com.android.quickstep.InstantAppResolverImpl</string>
+
+ <string name="main_process_initializer_class" translatable="false">com.android.quickstep.QuickstepProcessInitializer</string>
</resources>
diff --git a/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java b/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
index af81a59..8fbb512 100644
--- a/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
+++ b/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
@@ -19,6 +19,13 @@
import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
import static com.android.launcher3.LauncherState.NORMAL;
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.AGGRESSIVE_EASE_IN_OUT;
+import static com.android.launcher3.anim.Interpolators.APP_CLOSE_ALPHA;
+import static com.android.launcher3.anim.Interpolators.LINEAR;
+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 +38,6 @@
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;
@@ -45,7 +51,6 @@
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,11 +60,10 @@
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.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;
@@ -78,7 +82,7 @@
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";
@@ -86,7 +90,7 @@
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;
+ 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;
@@ -106,7 +110,7 @@
private DeviceProfile mDeviceProfile;
private View mFloatingView;
- private RemoteAnimationRunnerCompat mRemoteAnimationOverride;
+ private RemoteAnimationProvider mRemoteAnimationProvider;
private final AnimatorListenerAdapter mReapplyStateListener = new AnimatorListenerAdapter() {
@Override
@@ -179,66 +183,8 @@
return getDefaultActivityLaunchOptions(launcher, v);
}
- public void setRemoteAnimationOverride(RemoteAnimationRunnerCompat remoteAnimationOverride) {
- mRemoteAnimationOverride = remoteAnimationOverride;
- }
-
- /**
- * 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 (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;
+ public void setRemoteAnimationProvider(RemoteAnimationProvider animationProvider) {
+ mRemoteAnimationProvider = animationProvider;
}
/**
@@ -284,7 +230,8 @@
};
}
- target.play(getRecentsWindowAnimator(taskView, skipLauncherChanges, targets));
+ target.play(getRecentsWindowAnimator(taskView, skipLauncherChanges, targets)
+ .setDuration(RECENTS_LAUNCH_DURATION));
target.play(launcherAnim);
// Set the current animation first, before adding windowAnimEndListener. Setting current
@@ -296,82 +243,6 @@
}
/**
- * @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.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.
@@ -397,9 +268,9 @@
ObjectAnimator alpha = ObjectAnimator.ofFloat(appsView, View.ALPHA, alphas);
alpha.setDuration(217);
- alpha.setInterpolator(Interpolators.LINEAR);
+ alpha.setInterpolator(LINEAR);
ObjectAnimator transY = ObjectAnimator.ofFloat(appsView, View.TRANSLATION_Y, trans);
- transY.setInterpolator(Interpolators.AGGRESSIVE_EASE);
+ transY.setInterpolator(AGGRESSIVE_EASE);
transY.setDuration(350);
launcherAnimator.play(alpha);
@@ -418,10 +289,10 @@
ObjectAnimator dragLayerAlpha = ObjectAnimator.ofFloat(mDragLayer, View.ALPHA, alphas);
dragLayerAlpha.setDuration(217);
- dragLayerAlpha.setInterpolator(Interpolators.LINEAR);
+ dragLayerAlpha.setInterpolator(LINEAR);
ObjectAnimator dragLayerTransY = ObjectAnimator.ofFloat(mDragLayer, View.TRANSLATION_Y,
trans);
- dragLayerTransY.setInterpolator(Interpolators.AGGRESSIVE_EASE);
+ dragLayerTransY.setInterpolator(AGGRESSIVE_EASE);
dragLayerTransY.setDuration(350);
launcherAnimator.play(dragLayerAlpha);
@@ -513,8 +384,8 @@
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);
+ x.setInterpolator(AGGRESSIVE_EASE);
+ y.setInterpolator(AGGRESSIVE_EASE);
appIconAnimatorSet.play(x);
appIconAnimatorSet.play(y);
@@ -532,7 +403,7 @@
ObjectAnimator alpha = ObjectAnimator.ofFloat(mFloatingView, View.ALPHA, 1f, 0f);
alpha.setStartDelay(32);
alpha.setDuration(50);
- alpha.setInterpolator(Interpolators.LINEAR);
+ alpha.setInterpolator(LINEAR);
appIconAnimatorSet.play(alpha);
appIconAnimatorSet.addListener(new AnimatorListenerAdapter() {
@@ -567,11 +438,14 @@
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) {
@@ -579,8 +453,7 @@
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();
@@ -605,11 +478,6 @@
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,7 +490,7 @@
TransactionCompat t = new TransactionCompat();
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.
@@ -635,6 +503,7 @@
t.show(target.leash);
}
}
+ t.setEarlyWakeup();
t.apply();
matrix.reset();
@@ -667,13 +536,7 @@
}
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);
}
/**
@@ -683,52 +546,33 @@
private RemoteAnimationRunnerCompat getWallpaperOpenRunner() {
return new LauncherAnimationRunner(mHandler) {
@Override
- public void onAnimationStart(RemoteAnimationTargetCompat[] targetCompats,
- Runnable runnable) {
- if (mLauncher.getStateManager().getState().overviewUi
- && mRemoteAnimationOverride != null) {
- // This transition is only used for the fallback activity and should not be
- // managed here (but necessary to implement here since the defined remote
- // animation currently takes precendence over the one defined in the activity
- // options).
- mRemoteAnimationOverride.onAnimationStart(targetCompats, runnable);
- return;
- }
- super.onAnimationStart(targetCompats, runnable);
- }
-
- @Override
- public void onAnimationCancelled() {
- if (mLauncher.getStateManager().getState().overviewUi
- && mRemoteAnimationOverride != null) {
- // This transition is only used for the fallback activity and should not be
- // managed here (but necessary to implement here since the defined remote
- // animation currently takes precendence over the one defined in the activity
- // options).
- mRemoteAnimationOverride.onAnimationCancelled();
- return;
- }
- super.onAnimationCancelled();
- }
-
- @Override
public AnimatorSet getAnimator(RemoteAnimationTargetCompat[] targetCompats) {
- AnimatorSet anim = new AnimatorSet();
- anim.play(getClosingWindowAnimators(targetCompats));
-
- // Normally, we run the launcher content animation when we are transitioning home,
- // but if home is already visible, then we don't want to animate the contents of
- // launcher unless we know that we are animating home as a result of the home button
- // press with quickstep, which will result in launcher being started on touch down,
- // prior to the animation home (and won't be in the targets list because it is
- // already visible). In that case, we force invisibility on touch down, and only
- // reset it after the animation to home is initialized.
- if (launcherIsATargetWithMode(targetCompats, MODE_OPENING)
- || mLauncher.isForceInvisible()) {
- // Only register the content animation for cancellation when state changes
- mLauncher.getStateManager().setCurrentAnimation(anim);
- createLauncherResumeAnimation(anim);
+ AnimatorSet anim = null;
+ RemoteAnimationProvider provider = mRemoteAnimationProvider;
+ if (provider != null) {
+ anim = provider.createWindowAnimation(targetCompats);
}
+
+ if (anim == null) {
+ anim = new AnimatorSet();
+ anim.play(getClosingWindowAnimators(targetCompats));
+
+ // Normally, we run the launcher content animation when we are transitioning
+ // home, but if home is already visible, then we don't want to animate the
+ // contents of launcher unless we know that we are animating home as a result
+ // of the home button press with quickstep, which will result in launcher being
+ // started on touch down, prior to the animation home (and won't be in the
+ // targets list because it is already visible). In that case, we force
+ // invisibility on touch down, and only reset it after the animation to home
+ // is initialized.
+ if (launcherIsATargetWithMode(targetCompats, MODE_OPENING)
+ || mLauncher.isForceInvisible()) {
+ // Only register the content animation for cancellation when state changes
+ mLauncher.getStateManager().setCurrentAnimation(anim);
+ createLauncherResumeAnimation(anim);
+ }
+ }
+
mLauncher.setForceInvisible(false);
return anim;
}
@@ -746,30 +590,23 @@
ValueAnimator closingAnimator = ValueAnimator.ofFloat(0, 1);
closingAnimator.setDuration(CLOSING_TRANSITION_DURATION_MS);
+ closingAnimator.addUpdateListener(new MultiValueUpdateListener() {
+ FloatProp mDx = new FloatProp(0, endX, 0, 350, AGGRESSIVE_EASE_IN_OUT);
+ FloatProp mScale = new FloatProp(1f, 0.8f, 0, 267, AGGRESSIVE_EASE);
+ FloatProp mAlpha = new FloatProp(1f, 0f, 0, 350, APP_CLOSE_ALPHA);
- 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();
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(mDx.value, 0);
matrix.postTranslate(app.position.x, app.position.y);
t.setMatrix(app.leash, matrix);
}
@@ -781,12 +618,14 @@
t.show(app.leash);
}
}
+ t.setEarlyWakeup();
t.apply();
matrix.reset();
isFirstFrame = false;
}
});
+
return closingAnimator;
}
@@ -847,16 +686,4 @@
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 0d1038a..27f1698 100644
--- a/quickstep/src/com/android/launcher3/LauncherInitListener.java
+++ b/quickstep/src/com/android/launcher3/LauncherInitListener.java
@@ -15,13 +15,16 @@
*/
package com.android.launcher3;
-import static com.android.launcher3.states.RotationHelper.REQUEST_LOCK;
-
import android.annotation.TargetApi;
+import android.content.Context;
+import android.content.Intent;
import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
import com.android.launcher3.states.InternalStateHandler;
import com.android.quickstep.ActivityControlHelper.ActivityInitListener;
+import com.android.quickstep.util.RemoteAnimationProvider;
import java.util.function.BiPredicate;
@@ -30,15 +33,33 @@
private final BiPredicate<Launcher, Boolean> mOnInitListener;
+ private RemoteAnimationProvider mRemoteAnimationProvider;
+
public LauncherInitListener(BiPredicate<Launcher, Boolean> onInitListener) {
mOnInitListener = onInitListener;
}
@Override
protected boolean init(Launcher launcher, boolean alreadyOnHome) {
- // For the duration of the gesture, lock the screen orientation to ensure that we do not
- // rotate mid-quickscrub
- launcher.getRotationHelper().setStateHandlerRequest(REQUEST_LOCK);
+ if (mRemoteAnimationProvider != null) {
+ LauncherAppTransitionManagerImpl appTransitionManager =
+ (LauncherAppTransitionManagerImpl) launcher.getAppTransitionManager();
+
+ // Set a one-time animation provider. After the first call, this will get cleared.
+ // TODO: Probably also check the intended target id.
+ appTransitionManager.setRemoteAnimationProvider((targets) -> {
+
+ // On the first call clear the reference.
+ appTransitionManager.setRemoteAnimationProvider(null);
+ RemoteAnimationProvider provider = mRemoteAnimationProvider;
+ mRemoteAnimationProvider = null;
+
+ if (provider != null && launcher.getStateManager().getState().overviewUi) {
+ return provider.createWindowAnimation(targets);
+ }
+ return null;
+ });
+ }
return mOnInitListener.test(launcher, alreadyOnHome);
}
@@ -49,6 +70,18 @@
@Override
public void unregister() {
+ mRemoteAnimationProvider = null;
clearReference();
}
+
+ @Override
+ public void registerAndStartActivity(Intent intent, RemoteAnimationProvider animProvider,
+ Context context, Handler handler, long duration) {
+ mRemoteAnimationProvider = animProvider;
+
+ register();
+
+ Bundle options = animProvider.toActivityOptions(handler, duration).toBundle();
+ context.startActivity(addToIntent(new Intent((intent))), options);
+ }
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/LandscapeStatesTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/LandscapeStatesTouchController.java
index 355b88d..30ceb43 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/LandscapeStatesTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/LandscapeStatesTouchController.java
@@ -60,12 +60,13 @@
@Override
protected LauncherState getTargetState(LauncherState fromState, boolean isDragTowardPositive) {
- if (fromState == ALL_APPS) {
+ if (fromState == ALL_APPS && !isDragTowardPositive) {
// Should swipe down go to OVERVIEW instead?
return TouchInteractionService.isConnected() ?
mLauncher.getStateManager().getLastState() : NORMAL;
- } else {
+ } else if (isDragTowardPositive) {
return ALL_APPS;
}
+ return fromState;
}
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java b/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java
index d97b7b2..9c7db30 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/OverviewState.java
@@ -19,13 +19,11 @@
import static com.android.launcher3.anim.Interpolators.DEACCEL_2;
import static com.android.launcher3.states.RotationHelper.REQUEST_ROTATE;
-import android.graphics.Rect;
import android.view.View;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
-import com.android.launcher3.Workspace;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
import com.android.quickstep.views.RecentsView;
@@ -47,14 +45,8 @@
@Override
public float[] getWorkspaceScaleAndTranslation(Launcher launcher) {
- Rect pageRect = new Rect();
- RecentsView.getPageRect(launcher.getDeviceProfile(), launcher, pageRect);
-
- if (launcher.getWorkspace().getNormalChildWidth() <= 0 || pageRect.isEmpty()) {
- return super.getWorkspaceScaleAndTranslation(launcher);
- }
-
- return getScaleAndTranslationForPageRect(launcher, pageRect);
+ // TODO: provide a valid value
+ return new float[]{1, 0, -launcher.getDeviceProfile().hotseatBarSizePx / 2};
}
@Override
@@ -93,24 +85,10 @@
};
}
- public static float[] getScaleAndTranslationForPageRect(Launcher launcher, Rect pageRect) {
- Workspace ws = launcher.getWorkspace();
- float childWidth = ws.getNormalChildWidth();
-
- float scale = pageRect.width() / childWidth;
- Rect insets = launcher.getDragLayer().getInsets();
-
- float halfHeight = ws.getExpectedHeight() / 2;
- float childTop = halfHeight - scale * (halfHeight - ws.getPaddingTop() - insets.top);
- float translationY = pageRect.top - childTop;
-
- return new float[] {scale, 0, translationY};
- }
-
@Override
public int getVisibleElements(Launcher launcher) {
if (launcher.getDeviceProfile().isVerticalBarLayout()) {
- return NONE;
+ return DRAG_HANDLE_INDICATOR;
} else {
return HOTSEAT_SEARCH_BOX | DRAG_HANDLE_INDICATOR |
(launcher.getAppsView().getFloatingHeaderView().hasVisibleContent()
diff --git a/quickstep/src/com/android/launcher3/uioverrides/PortraitStatesTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/PortraitStatesTouchController.java
index 1b65ca0..012b545 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/PortraitStatesTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/PortraitStatesTouchController.java
@@ -37,6 +37,7 @@
import com.android.launcher3.anim.Interpolators;
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.TouchInteractionService;
import com.android.quickstep.util.SysuiEventLogger;
@@ -140,7 +141,7 @@
@Override
protected LauncherState getTargetState(LauncherState fromState, boolean isDragTowardPositive) {
- if (fromState == ALL_APPS) {
+ if (fromState == ALL_APPS && !isDragTowardPositive) {
// Should swipe down go to OVERVIEW instead?
return TouchInteractionService.isConnected() ?
mLauncher.getStateManager().getLastState() : NORMAL;
@@ -182,7 +183,7 @@
}
if (mPendingAnimation != null) {
- mPendingAnimation.finish(false);
+ mPendingAnimation.finish(false, Touch.SWIPE);
mPendingAnimation = null;
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/TaskViewTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/TaskViewTouchController.java
index 42f8105..4c9fd5a 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/TaskViewTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/TaskViewTouchController.java
@@ -29,7 +29,6 @@
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.Direction;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
import com.android.launcher3.util.PendingAnimation;
import com.android.launcher3.util.TouchController;
@@ -164,7 +163,7 @@
mCurrentAnimation.setPlayFraction(0);
}
if (mPendingAnimation != null) {
- mPendingAnimation.finish(false);
+ mPendingAnimation.finish(false, Touch.SWIPE);
mPendingAnimation = null;
}
@@ -261,15 +260,9 @@
private void onCurrentAnimationEnd(boolean wasSuccess, int logAction) {
if (mPendingAnimation != null) {
- mPendingAnimation.finish(wasSuccess);
+ mPendingAnimation.finish(wasSuccess, logAction);
mPendingAnimation = null;
}
- if (wasSuccess) {
- if (!mCurrentAnimationIsGoingUp) {
- mActivity.getUserEventDispatcher().logTaskLaunch(logAction,
- Direction.DOWN, mTaskBeingDragged.getTask().getTopComponent());
- }
- }
mDetector.finishedScrolling();
mTaskBeingDragged = null;
mCurrentAnimation = null;
diff --git a/quickstep/src/com/android/quickstep/ActivityControlHelper.java b/quickstep/src/com/android/quickstep/ActivityControlHelper.java
index 3e96c44..95947d7 100644
--- a/quickstep/src/com/android/quickstep/ActivityControlHelper.java
+++ b/quickstep/src/com/android/quickstep/ActivityControlHelper.java
@@ -19,10 +19,10 @@
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 android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
-import android.app.ActivityOptions;
import android.content.Context;
import android.content.Intent;
import android.graphics.Rect;
@@ -36,22 +36,17 @@
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.LauncherAppTransitionManagerImpl;
import com.android.launcher3.LauncherInitListener;
import com.android.launcher3.LauncherState;
-import com.android.launcher3.MainThreadExecutor;
import com.android.launcher3.allapps.AllAppsTransitionController;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.util.ViewOnDrawExecutor;
import com.android.quickstep.fallback.FallbackRecentsView;
+import com.android.quickstep.util.RemoteAnimationProvider;
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.ActivityOptionsCompat;
-import com.android.systemui.shared.system.AssistDataReceiver;
-import com.android.systemui.shared.system.RecentsAnimationListener;
-import com.android.systemui.shared.system.RemoteAnimationAdapterCompat;
import java.util.function.BiPredicate;
@@ -64,7 +59,13 @@
void onQuickstepGestureStarted(T activity, boolean activityVisible);
- void onQuickInteractionStart(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 executeOnWindowAvailable(T activity, Runnable action);
void executeOnNextDraw(T activity, TaskView targetView, Runnable action);
@@ -82,11 +83,8 @@
ActivityInitListener createActivityInitListener(BiPredicate<T, Boolean> onInitListener);
- void startRecentsFromSwipe(Intent intent, AssistDataReceiver assistDataReceiver,
- final RecentsAnimationListener remoteAnimationListener);
-
- void startRecentsFromButton(Context context, Intent intent,
- RecentsAnimationListener remoteAnimationListener);
+ @Nullable
+ T getCreatedActivity();
@UiThread
@Nullable
@@ -95,6 +93,12 @@
@UiThread
boolean switchToRecentsIfVisible();
+ /**
+ * @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);
+
class LauncherActivityControllerHelper implements ActivityControlHelper<Launcher> {
@Override
@@ -108,8 +112,18 @@
}
@Override
- public void onQuickInteractionStart(Launcher activity, boolean activityVisible) {
+ public boolean onQuickInteractionStart(Launcher activity, boolean activityVisible) {
+ LauncherState fromState = activity.getStateManager().getState();
activity.getStateManager().goToState(FAST_OVERVIEW, activityVisible);
+ return !fromState.overviewUi;
+ }
+
+ @Override
+ public void executeOnWindowAvailable(Launcher activity, Runnable action) {
+ if (activity.getWorkspace().runOnOverlayHidden(action)) {
+ // Notify the activity that qiuckscrub has started
+ onQuickstepGestureStarted(activity, true);
+ }
}
@Override
@@ -128,7 +142,7 @@
@Override
public int getSwipeUpDestinationAndLength(DeviceProfile dp, Context context, Rect outRect) {
- RecentsView.getPageRect(dp, context, outRect);
+ LauncherRecentsView.getPageRect(dp, context, outRect);
if (dp.isVerticalBarLayout()) {
Rect targetInsets = dp.getInsets();
int hotseatInset = dp.isSeascape() ? targetInsets.left : targetInsets.right;
@@ -207,34 +221,9 @@
return new LauncherInitListener(onInitListener);
}
- @Override
- public void startRecentsFromSwipe(Intent intent, AssistDataReceiver assistDataReceiver,
- final RecentsAnimationListener remoteAnimationListener) {
- ActivityManagerWrapper.getInstance().startRecentsActivity(
- intent, assistDataReceiver, remoteAnimationListener, null, null);
- }
-
- @Override
- public void startRecentsFromButton(Context context, Intent intent,
- RecentsAnimationListener remoteAnimationListener) {
- // We should use the remove animation for the fallback activity recents button case,
- // it works better with PiP. In Launcher, we have already registered the remote
- // animation definition, which takes priority over explicitly defined remote
- // animations in the provided activity options when starting the activity, so we
- // just register a remote animation factory to get a callback to handle this.
- LauncherAppTransitionManagerImpl appTransitionManager =
- (LauncherAppTransitionManagerImpl) getLauncher().getAppTransitionManager();
- appTransitionManager.setRemoteAnimationOverride(new RecentsAnimationActivityOptions(
- remoteAnimationListener, () -> {
- // Once the controller is finished, also reset the remote animation override
- appTransitionManager.setRemoteAnimationOverride(null);
- }));
- context.startActivity(intent);
- }
-
@Nullable
- @UiThread
- private Launcher getLauncher() {
+ @Override
+ public Launcher getCreatedActivity() {
LauncherAppState app = LauncherAppState.getInstanceNoCreate();
if (app == null) {
return null;
@@ -245,7 +234,7 @@
@Nullable
@UiThread
private Launcher getVisibleLaucher() {
- Launcher launcher = getLauncher();
+ Launcher launcher = getCreatedActivity();
return (launcher != null) && launcher.isStarted() && launcher.hasWindowFocus() ?
launcher : null;
}
@@ -267,6 +256,11 @@
}
return false;
}
+
+ @Override
+ public boolean deferStartingActivity(int downHitTarget) {
+ return downHitTarget == HIT_TARGET_BACK;
+ }
}
class FallbackActivityControllerHelper implements ActivityControlHelper<RecentsActivity> {
@@ -277,8 +271,14 @@
}
@Override
- public void onQuickInteractionStart(RecentsActivity activity, boolean activityVisible) {
- // TODO:
+ public boolean onQuickInteractionStart(RecentsActivity activity, boolean activityVisible) {
+ // Activity does not need any UI change for quickscrub.
+ return false;
+ }
+
+ @Override
+ public void executeOnWindowAvailable(RecentsActivity activity, Runnable action) {
+ action.run();
}
@Override
@@ -295,7 +295,7 @@
@Override
public int getSwipeUpDestinationAndLength(DeviceProfile dp, Context context, Rect outRect) {
- FallbackRecentsView.getCenterPageRect(dp, context, outRect);
+ FallbackRecentsView.getPageRect(dp, context, outRect);
if (dp.isVerticalBarLayout()) {
Rect targetInsets = dp.getInsets();
int hotseatInset = dp.isSeascape() ? targetInsets.left : targetInsets.right;
@@ -352,31 +352,16 @@
return new RecentsActivityTracker(onInitListener);
}
+ @Nullable
@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);
- }
-
- @Override
- public void startRecentsFromButton(Context context, Intent intent,
- RecentsAnimationListener remoteAnimationListener) {
- // We should use the remove animation for the fallback activity recents button case,
- // it works better with PiP. For the fallback activity, we should not have registered
- // the launcher app transition manager, so we should just start the remote animation here.
- ActivityOptions options = ActivityOptionsCompat.makeRemoteAnimation(
- new RemoteAnimationAdapterCompat(
- new RecentsAnimationActivityOptions(remoteAnimationListener, null),
- 10000, 10000));
- context.startActivity(intent, options.toBundle());
+ public RecentsActivity getCreatedActivity() {
+ return RecentsActivityTracker.getCurrentActivity();
}
@Nullable
@Override
public RecentsView getVisibleRecentsView() {
- RecentsActivity activity = RecentsActivityTracker.getCurrentActivity();
+ RecentsActivity activity = getCreatedActivity();
if (activity != null && activity.hasWindowFocus()) {
return activity.getOverviewPanel();
}
@@ -387,6 +372,12 @@
public boolean switchToRecentsIfVisible() {
return false;
}
+
+ @Override
+ public boolean deferStartingActivity(int downHitTarget) {
+ // Always defer starting the activity when using fallback
+ return true;
+ }
}
interface LayoutListener {
@@ -403,5 +394,8 @@
void register();
void unregister();
+
+ void registerAndStartActivity(Intent intent, RemoteAnimationProvider animProvider,
+ Context context, Handler handler, long duration);
}
}
diff --git a/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java b/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java
index 4d695de..28c950b 100644
--- a/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java
+++ b/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java
@@ -21,8 +21,7 @@
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 android.annotation.TargetApi;
@@ -55,7 +54,6 @@
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 +64,6 @@
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;
@@ -102,7 +98,7 @@
mActivityControlHelper = activityControl;
mMainThreadExecutor = mainThreadExecutor;
mBackgroundThreadChoreographer = backgroundThreadChoreographer;
- mIsDeferredDownTarget = Arrays.binarySearch(DEFERRED_HIT_TARGETS, downHitTarget) >= 0;
+ mIsDeferredDownTarget = activityControl.deferStartingActivity(downHitTarget);
}
@Override
@@ -218,7 +214,8 @@
handler.initWhenReady();
TraceHelper.beginSection("RecentsController");
- Runnable startActivity = () -> mActivityControlHelper.startRecentsFromSwipe(mHomeIntent,
+ Runnable startActivity = () -> ActivityManagerWrapper.getInstance().startRecentsActivity(
+ mHomeIntent,
new AssistDataReceiver() {
@Override
public void onHandleAssistData(Bundle bundle) {
@@ -247,7 +244,7 @@
handler.onRecentsAnimationCanceled();
}
}
- });
+ }, null, null);
if (Looper.myLooper() != Looper.getMainLooper()) {
startActivity.run();
@@ -305,6 +302,13 @@
@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);
diff --git a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
index 8e59578..e7ed517 100644
--- a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
+++ b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
@@ -15,92 +15,86 @@
*/
package com.android.quickstep;
-import static com.android.launcher3.LauncherState.OVERVIEW;
+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 android.animation.Animator;
+import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
+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 android.animation.AnimatorSet;
import android.animation.ValueAnimator;
import android.annotation.TargetApi;
-import android.app.ActivityManager.RecentTaskInfo;
-import android.app.ActivityManager.RunningTaskInfo;
-import android.app.ActivityOptions;
+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.Point;
import android.graphics.Rect;
-import android.graphics.RectF;
import android.os.Build;
-import android.os.Bundle;
+import android.os.PatternMatcher;
import android.os.SystemClock;
-import android.os.UserHandle;
-import android.support.annotation.UiThread;
-import android.support.annotation.WorkerThread;
-import android.util.SparseArray;
+import android.util.Log;
+import android.view.View;
import android.view.ViewConfiguration;
import com.android.launcher3.AbstractFloatingView;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.MainThreadExecutor;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.anim.AnimationSuccessListener;
-import com.android.launcher3.anim.Interpolators;
-import com.android.launcher3.states.InternalStateHandler;
-import com.android.launcher3.util.TraceHelper;
+import com.android.launcher3.anim.AnimatorPlaybackController;
+import com.android.quickstep.ActivityControlHelper.ActivityInitListener;
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.views.RecentsView;
-import com.android.quickstep.views.TaskView;
-import com.android.systemui.shared.recents.model.ThumbnailData;
-import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecCompat;
-import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecsFuture;
-import com.android.systemui.shared.recents.view.RecentsTransition;
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.RecentsAnimationControllerCompat;
-import com.android.systemui.shared.system.RecentsAnimationListener;
+import com.android.systemui.shared.system.PackageManagerWrapper;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
-import java.util.Collections;
-import java.util.List;
-import java.util.function.Consumer;
+import java.util.ArrayList;
/**
* Helper class to handle various atomic commands for switching between Overview.
*/
@TargetApi(Build.VERSION_CODES.P)
-public class OverviewCommandHelper extends InternalStateHandler {
-
- private static final int RID_RESET_SWIPE_HANDLER = 0;
- private static final int RID_CANCEL_CONTROLLER = 1;
- private static final int RID_CANCEL_ZOOM_OUT_ANIMATION = 2;
+public class OverviewCommandHelper {
private static final long RECENTS_LAUNCH_DURATION = 200;
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;
- public final Intent homeIntent;
- public final ComponentName launcher;
+ 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;
- private final SparseArray<Runnable> mCurrentCommandFinishRunnables = new SparseArray<>();
- // Monotonically increasing command ids.
- private int mCurrentCommandId = 0;
+ public Intent overviewIntent;
+ public ComponentName overviewComponent;
+ private ActivityControlHelper mActivityControlHelper;
private long mLastToggleTime;
- private WindowTransformSwipeHandler mWindowTransformSwipeHandler;
-
- private final Point mWindowSize = new Point();
- private final Rect mTaskTargetRect = new Rect();
- private final RectF mTempTaskTargetRect = new RectF();
public OverviewCommandHelper(Context context) {
mContext = context;
@@ -108,266 +102,226 @@
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())
+ .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();
+ }
+
+ 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 {
+ // The default home app is a different launcher. Use the fallback Overview instead.
+ overviewComponent = new ComponentName(mContext, RecentsActivity.class);
+ mActivityControlHelper = new FallbackActivityControllerHelper();
+ 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);
+ }
+ }
+
+ overviewIntent = new Intent(Intent.ACTION_MAIN)
+ .addCategory(overviewIntentCategory)
+ .setComponent(overviewComponent)
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- ResolveInfo info = context.getPackageManager().resolveActivity(homeIntent, 0);
+ }
- if (DEBUG_START_FALLBACK_ACTIVITY) {
- launcher = new ComponentName(context, RecentsActivity.class);
- homeIntent.addCategory(Intent.CATEGORY_DEFAULT)
- .removeCategory(Intent.CATEGORY_HOME);
- } else {
- launcher = new ComponentName(context.getPackageName(), info.activityInfo.name);
+ public void onDestroy() {
+ mContext.unregisterReceiver(mUserPreferenceChangeReceiver);
+
+ if (mUpdateRegisteredPackage != null) {
+ mContext.unregisterReceiver(mOtherHomeAppUpdateReceiver);
+ mUpdateRegisteredPackage = null;
}
-
- // Clear the packageName as system can fail to dedupe it b/64108432
- homeIntent.setComponent(launcher).setPackage(null);
- }
-
- private void openRecents() {
- Intent intent = addToIntent(new Intent(homeIntent));
- mContext.startActivity(intent);
- initWhenReady();
- }
-
- @UiThread
- private void addFinishCommand(int requestId, int id, Runnable action) {
- if (requestId < mCurrentCommandId) {
- action.run();
- } else {
- mCurrentCommandFinishRunnables.put(id, action);
- }
- }
-
- @UiThread
- private void clearFinishCommand(int requestId, int id) {
- if (requestId == mCurrentCommandId) {
- mCurrentCommandFinishRunnables.remove(id);
- }
- }
-
- @UiThread
- private void initSwipeHandler(ActivityControlHelper helper, long time,
- Consumer<WindowTransformSwipeHandler> onAnimationInitCallback) {
- final int commandId = mCurrentCommandId;
- final RunningTaskInfo runningTask = ActivityManagerWrapper.getInstance().getRunningTask();
- final int runningTaskId = runningTask.id;
- final WindowTransformSwipeHandler handler =
- new WindowTransformSwipeHandler(runningTask, mContext, time, helper);
-
- // Preload the plan
- mRecentsModel.loadTasks(runningTaskId, null);
- mWindowTransformSwipeHandler = handler;
-
- mTempTaskTargetRect.setEmpty();
- handler.setGestureEndCallback(() -> {
- if (mWindowTransformSwipeHandler == handler) {
- mWindowTransformSwipeHandler = null;
- mTempTaskTargetRect.setEmpty();
- }
- clearFinishCommand(commandId, RID_RESET_SWIPE_HANDLER);
- clearFinishCommand(commandId, RID_CANCEL_CONTROLLER);
- });
- handler.initWhenReady();
- addFinishCommand(commandId, RID_RESET_SWIPE_HANDLER, handler::reset);
-
- TraceHelper.beginSection(TAG);
- Runnable startActivity = () -> helper.startRecentsFromButton(mContext,
- addToIntent(homeIntent),
- new RecentsAnimationListener() {
- public void onAnimationStart(
- RecentsAnimationControllerCompat controller,
- RemoteAnimationTargetCompat[] apps, Rect homeContentInsets,
- Rect minimizedHomeBounds) {
- if (mWindowTransformSwipeHandler == handler) {
- TraceHelper.partitionSection(TAG, "Received");
- handler.onRecentsAnimationStart(controller, apps, homeContentInsets,
- minimizedHomeBounds);
- mTempTaskTargetRect.set(handler.getTargetRect(mWindowSize));
-
- ThumbnailData thumbnail = mAM.getTaskThumbnail(runningTaskId,
- true /* reducedResolution */);
- mMainThreadExecutor.execute(() -> {
- addFinishCommand(commandId,
- RID_CANCEL_CONTROLLER, () -> controller.finish(true));
- if (commandId == mCurrentCommandId) {
- onAnimationInitCallback.accept(handler);
-
- // The animation has started, which means the other activity
- // should be paused, lets update the thumbnail
- handler.switchToScreenshotImmediate(thumbnail);
- }
- });
- } else {
- TraceHelper.endSection(TAG, "Finishing no handler");
- controller.finish(false /* toHome */);
- }
- }
-
- public void onAnimationCanceled() {
- TraceHelper.endSection(TAG, "Cancelled: " + handler);
- if (mWindowTransformSwipeHandler == handler) {
- handler.onRecentsAnimationCanceled();
- }
- }
- });
-
- // We should almost always get touch-town on background thread. This is an edge case
- // when the background Choreographer has not yet initialized.
- BackgroundExecutor.get().submit(startActivity);
- }
-
- @UiThread
- private void startZoomOutAnim(final WindowTransformSwipeHandler handler) {
- final int commandId = mCurrentCommandId;
- ValueAnimator anim = ValueAnimator.ofInt(0, -handler.getTransitionLength());
- anim.addUpdateListener((a) -> handler.updateDisplacement((Integer) a.getAnimatedValue()));
- anim.addListener(new AnimationSuccessListener() {
- @Override
- public void onAnimationSuccess(Animator animator) {
- handler.onGestureEnded(0);
- clearFinishCommand(commandId, RID_CANCEL_ZOOM_OUT_ANIMATION);
- }
- });
- handler.onGestureStarted();
- anim.setDuration(RECENTS_LAUNCH_DURATION);
- anim.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
- anim.start();
- addFinishCommand(commandId, RID_CANCEL_ZOOM_OUT_ANIMATION, anim::cancel);
}
public void onOverviewToggle() {
// If currently screen pinning, do not enter overview
- if (ActivityManagerWrapper.getInstance().isScreenPinningActive()) {
+ if (mAM.isScreenPinningActive()) {
return;
}
- ActivityManagerWrapper.getInstance().closeSystemWindows("recentapps");
- long time = SystemClock.elapsedRealtime();
- mMainThreadExecutor.execute(() -> {
- long elapsedTime = time - mLastToggleTime;
- mLastToggleTime = time;
-
- mCurrentCommandId++;
- mTempTaskTargetRect.round(mTaskTargetRect);
- int runnableCount = mCurrentCommandFinishRunnables.size();
- if (runnableCount > 0) {
- for (int i = 0; i < runnableCount; i++) {
- mCurrentCommandFinishRunnables.valueAt(i).run();
- }
- mCurrentCommandFinishRunnables.clear();
- }
-
- // TODO: We need to fix this case with PIP, when an activity first enters PIP, it shows
- // the menu activity which takes window focus, prevening the right condition from
- // being run below
- ActivityControlHelper helper = getActivityControlHelper();
- RecentsView recents = helper.getVisibleRecentsView();
- if (recents != null) {
- // Launch the next task
- recents.showNextTask();
- } else {
- if (elapsedTime < ViewConfiguration.getDoubleTapTimeout()) {
- // The user tried to launch back into overview too quickly, either after
- // launching an app, or before overview has actually shown, just ignore for now
- return;
- }
-
- // Start overview
- if (helper.switchToRecentsIfVisible()) {
- SysuiEventLogger.writeDummyRecentsTransition(0);
- // Do nothing
- } else {
- initSwipeHandler(helper, time, this::startZoomOutAnim);
- }
- }
- });
+ mAM.closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
+ mMainThreadExecutor.execute(new RecentsActivityCommand<>());
}
public void onOverviewShown() {
- getLauncher().runOnUiThread(() -> {
- if (isOverviewAlmostVisible()) {
- final RecentsView rv = getLauncher().getOverviewPanel();
- rv.snapToTaskAfterNext();
- } else {
- openRecents();
- }
- }
- );
- }
-
- public void onOverviewHidden() {
- getLauncher().runOnUiThread(() -> {
- if (isOverviewAlmostVisible()) {
- final RecentsView rv = getLauncher().getOverviewPanel();
- rv.launchNextTask();
- }
- }
- );
- }
-
- @WorkerThread
- private void startLastTask() {
- // TODO: This should go through recents model.
- List<RecentTaskInfo> tasks = mAM.getRecentTasks(2, UserHandle.myUserId());
- if (tasks.size() > 1) {
- RecentTaskInfo rti = tasks.get(1);
-
- final ActivityOptions options;
- if (!mTaskTargetRect.isEmpty()) {
- final Rect targetRect = new Rect(mTaskTargetRect);
- targetRect.offset(Utilities.isRtl(mContext.getResources())
- ? - mTaskTargetRect.width() : mTaskTargetRect.width(), 0);
- final AppTransitionAnimationSpecCompat specCompat =
- new AppTransitionAnimationSpecCompat(rti.id, null, targetRect);
- AppTransitionAnimationSpecsFuture specFuture =
- new AppTransitionAnimationSpecsFuture(mMainThreadExecutor.getHandler()) {
-
- @Override
- public List<AppTransitionAnimationSpecCompat> composeSpecs() {
- return Collections.singletonList(specCompat);
- }
- };
- options = RecentsTransition.createAspectScaleAnimation(mContext,
- mMainThreadExecutor.getHandler(), true /* scaleUp */,
- specFuture, () -> {});
- } else {
- options = ActivityOptions.makeBasic();
- }
- mAM.startActivityFromRecents(rti.id, options);
- }
- }
-
- private boolean isOverviewAlmostVisible() {
- if (clearReference()) {
- return true;
- }
- if (!mAM.getRunningTask().topActivity.equals(launcher)) {
- return false;
- }
- Launcher launcher = getLauncher();
- return launcher != null && launcher.isStarted() && launcher.isInState(OVERVIEW);
- }
-
- private Launcher getLauncher() {
- return (Launcher) LauncherAppState.getInstance(mContext).getModel().getCallback();
- }
-
- @Override
- protected boolean init(Launcher launcher, boolean alreadyOnHome) {
- AbstractFloatingView.closeAllOpenViews(launcher, alreadyOnHome);
- launcher.getStateManager().goToState(OVERVIEW, alreadyOnHome);
- clearReference();
- return false;
+ mMainThreadExecutor.execute(new ShowRecentsCommand());
}
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;
+ }
+ }
+ }
+
+ private class RecentsActivityCommand<T extends BaseDraggingActivity> implements Runnable {
+
+ protected final ActivityControlHelper<T> mHelper;
+ private final long mCreateTime;
+ private final int mRunningTaskId;
+
+ private ActivityInitListener mListener;
+ private T mActivity;
+
+ public RecentsActivityCommand() {
+ mHelper = getActivityControlHelper();
+ mCreateTime = SystemClock.elapsedRealtime();
+ mRunningTaskId = mAM.getRunningTask().id;
+
+ // Preload the plan
+ mRecentsModel.loadTasks(mRunningTaskId, null);
+ }
+
+ @Override
+ public void run() {
+ long elapsedTime = mCreateTime - mLastToggleTime;
+ mLastToggleTime = mCreateTime;
+
+ if (!handleCommand(elapsedTime)) {
+ // Start overview
+ if (mHelper.switchToRecentsIfVisible()) {
+ SysuiEventLogger.writeDummyRecentsTransition(0);
+ // Do nothing
+ } else {
+ mListener = mHelper.createActivityInitListener(this::onActivityReady);
+ mListener.registerAndStartActivity(overviewIntent, this::createWindowAnimation,
+ mContext, mMainThreadExecutor.getHandler(), RECENTS_LAUNCH_DURATION);
+ }
+ }
+ }
+
+ protected boolean handleCommand(long elapsedTime) {
+ // TODO: We need to fix this case with PIP, when an activity first enters PIP, it shows
+ // the menu activity which takes window focus, preventing the right condition from
+ // being run below
+ RecentsView recents = mHelper.getVisibleRecentsView();
+ if (recents != null) {
+ // Launch the next task
+ recents.showNextTask();
+ return true;
+ } else if (elapsedTime < ViewConfiguration.getDoubleTapTimeout()) {
+ // The user tried to launch back into overview too quickly, either after
+ // launching an app, or before overview has actually shown, just ignore for now
+ return true;
+ }
+ return false;
+ }
+
+ private boolean onActivityReady(T activity, Boolean wasVisible) {
+ activity.<RecentsView>getOverviewPanel().setCurrentTask(mRunningTaskId);
+ AbstractFloatingView.closeAllOpenViews(activity, wasVisible);
+ mHelper.prepareRecentsUI(activity, wasVisible);
+ 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();
+ }
+ mActivity = activity;
+ return false;
+ }
+
+ private AnimatorSet createWindowAnimation(RemoteAnimationTargetCompat[] targetCompats) {
+ if (mListener != null) {
+ mListener.unregister();
+ }
+ RemoteAnimationProvider.showOpeningTarget(targetCompats);
+ AnimatorSet anim = new AnimatorSet();
+ if (mActivity == null) {
+ Log.e(TAG, "Animation created, before activity");
+ anim.play(ValueAnimator.ofInt(0, 1).setDuration(100));
+ return anim;
+ }
+
+ RemoteAnimationTargetCompat closingTarget = null;
+ // 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) {
+ Log.e(TAG, "No closing app");
+ anim.play(ValueAnimator.ofInt(0, 1).setDuration(100));
+ return anim;
+ }
+
+ final ClipAnimationHelper clipHelper = new ClipAnimationHelper();
+
+ // At this point, the activity is already started and laid-out. Get the home-bounds
+ // relative to the screen using the rootView of the activity.
+ int loc[] = new int[2];
+ View rootView = mActivity.getRootView();
+ rootView.getLocationOnScreen(loc);
+ Rect homeBounds = new Rect(loc[0], loc[1],
+ loc[0] + rootView.getWidth(), loc[1] + rootView.getHeight());
+ clipHelper.updateSource(homeBounds, closingTarget);
+
+ Rect targetRect = new Rect();
+ mHelper.getSwipeUpDestinationAndLength(
+ mActivity.getDeviceProfile(), mActivity, targetRect);
+ clipHelper.updateTargetRect(targetRect);
+
+
+ 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());
+ });
+ anim.play(valueAnimator);
+ return anim;
}
}
}
diff --git a/quickstep/src/com/android/quickstep/QuickstepProcessInitializer.java b/quickstep/src/com/android/quickstep/QuickstepProcessInitializer.java
new file mode 100644
index 0000000..aed9959
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/QuickstepProcessInitializer.java
@@ -0,0 +1,34 @@
+/*
+ * 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.MainProcessInitializer;
+import com.android.systemui.shared.system.ThreadedRendererCompat;
+
+public class QuickstepProcessInitializer extends MainProcessInitializer {
+
+ public QuickstepProcessInitializer(Context context) { }
+
+ @Override
+ protected void init(Context context) {
+ super.init(context);
+
+ // Elevate GPU priority for Quickstep and Remote animations.
+ ThreadedRendererCompat.setContextPriority(ThreadedRendererCompat.EGL_CONTEXT_PRIORITY_HIGH_IMG);
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/RecentsActivity.java b/quickstep/src/com/android/quickstep/RecentsActivity.java
index cf60fdf..eb0be89 100644
--- a/quickstep/src/com/android/quickstep/RecentsActivity.java
+++ b/quickstep/src/com/android/quickstep/RecentsActivity.java
@@ -15,15 +15,29 @@
*/
package com.android.quickstep;
+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.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
import android.view.View;
import com.android.launcher3.BaseDraggingActivity;
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,12 +45,18 @@
import com.android.launcher3.views.BaseDragLayer;
import com.android.quickstep.fallback.FallbackRecentsView;
import com.android.quickstep.fallback.RecentsRootView;
+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;
/**
* 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;
@@ -83,8 +103,49 @@
}
@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) {
+
+ @Override
+ public AnimatorSet getAnimator(RemoteAnimationTargetCompat[] targetCompats) {
+ return 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);
+ target.play(getRecentsWindowAnimator(taskView, !activityClosing, targets)
+ .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);
+ 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
@@ -94,6 +155,7 @@
protected void onStart() {
super.onStart();
UiFactory.onStart(this);
+ mFallbackRecentsView.resetTaskVisuals();
}
@Override
@@ -103,8 +165,26 @@
}
@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));
+ }
}
diff --git a/quickstep/src/com/android/quickstep/RecentsActivityTracker.java b/quickstep/src/com/android/quickstep/RecentsActivityTracker.java
index 5bd606e..fb6090e 100644
--- a/quickstep/src/com/android/quickstep/RecentsActivityTracker.java
+++ b/quickstep/src/com/android/quickstep/RecentsActivityTracker.java
@@ -16,9 +16,15 @@
package com.android.quickstep;
import android.annotation.TargetApi;
+import android.content.Context;
+import android.content.Intent;
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;
import java.lang.ref.WeakReference;
import java.util.function.BiPredicate;
@@ -29,9 +35,8 @@
@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;
@@ -41,41 +46,86 @@
@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
+ public void registerAndStartActivity(Intent intent, RemoteAnimationProvider animProvider,
+ Context context, Handler handler, long duration) {
+ register();
+
+ 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/RecentsAnimationActivityOptions.java b/quickstep/src/com/android/quickstep/RecentsAnimationActivityOptions.java
deleted file mode 100644
index a25e192..0000000
--- a/quickstep/src/com/android/quickstep/RecentsAnimationActivityOptions.java
+++ /dev/null
@@ -1,104 +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.systemui.shared.recents.model.ThumbnailData;
-import com.android.systemui.shared.system.RecentsAnimationControllerCompat;
-import com.android.systemui.shared.system.RecentsAnimationListener;
-import com.android.systemui.shared.system.RemoteAnimationRunnerCompat;
-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.Consumer;
-
-/**
- * Class to create activity options to emulate recents transition.
- */
-public class RecentsAnimationActivityOptions implements RemoteAnimationRunnerCompat {
-
- private final RecentsAnimationListener mListener;
- private final Runnable mFinishCallback;
-
- public RecentsAnimationActivityOptions(RecentsAnimationListener listener,
- Runnable finishCallback) {
- mListener = listener;
- mFinishCallback = finishCallback;
- }
-
- @Override
- public void onAnimationStart(RemoteAnimationTargetCompat[] targetCompats,
- Runnable runnable) {
- showOpeningTarget(targetCompats);
- RemoteRecentsAnimationControllerCompat dummyRecentsAnim =
- new RemoteRecentsAnimationControllerCompat(() -> {
- runnable.run();
- if (mFinishCallback != null) {
- mFinishCallback.run();
- }
- });
-
- Rect insets = new Rect();
- WindowManagerWrapper.getInstance().getStableInsets(insets);
- mListener.onAnimationStart(dummyRecentsAnim, targetCompats, insets, null);
- }
-
- @Override
- public void onAnimationCancelled() {
- mListener.onAnimationCanceled();
- }
-
- private void showOpeningTarget(RemoteAnimationTargetCompat[] targetCompats) {
- TransactionCompat t = new TransactionCompat();
- for (RemoteAnimationTargetCompat target : targetCompats) {
- int layer = target.mode == RemoteAnimationTargetCompat.MODE_CLOSING
- ? Integer.MAX_VALUE
- : target.prefixOrderIndex;
- t.setLayer(target.leash, layer);
- t.show(target.leash);
- }
- t.apply();
- }
-
- private class RemoteRecentsAnimationControllerCompat extends RecentsAnimationControllerCompat {
-
- final Runnable mFinishCallback;
-
- public RemoteRecentsAnimationControllerCompat(Runnable finishCallback) {
- mFinishCallback = finishCallback;
- }
-
- @Override
- public ThumbnailData screenshotTask(int taskId) {
- return new ThumbnailData();
- }
-
- @Override
- public void setInputConsumerEnabled(boolean enabled) { }
-
- @Override
- public void setAnimationTargetsBehindSystemBars(boolean behindSystemBars) { }
-
- @Override
- public void finish(boolean toHome) {
- // This should never be called with toHome == false
- if (mFinishCallback != null) {
- mFinishCallback.run();
- }
- }
- }
-}
diff --git a/quickstep/src/com/android/quickstep/TaskUtils.java b/quickstep/src/com/android/quickstep/TaskUtils.java
index 2df951b..c66f00f 100644
--- a/quickstep/src/com/android/quickstep/TaskUtils.java
+++ b/quickstep/src/com/android/quickstep/TaskUtils.java
@@ -16,24 +16,47 @@
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.Matrix;
+import android.graphics.Rect;
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.compat.LauncherAppsCompat;
import com.android.launcher3.compat.UserManagerCompat;
+import com.android.launcher3.util.ComponentKey;
+import com.android.quickstep.RecentsAnimationInterpolator.TaskWindowBounds;
+import com.android.quickstep.util.MultiValueUpdateListener;
+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 com.android.systemui.shared.system.TransactionCompat;
/**
* 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);
@@ -48,4 +71,152 @@
return userManagerCompat.getBadgedLabelForUser(
applicationInfo.loadLabel(packageManager), user);
}
+
+ 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();
+ if (componentName != null) {
+ for (int i = 0; i < recentsView.getChildCount(); i++) {
+ TaskView taskView = recentsView.getPageAt(i);
+ if (recentsView.isTaskViewVisible(taskView)) {
+ Task task = taskView.getTask();
+ if (componentName.equals(task.key.getComponent())) {
+ 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 RecentsAnimationInterpolator recentsInterpolator = v.getRecentsInterpolator();
+
+ Rect crop = new Rect();
+ Matrix matrix = new Matrix();
+
+ 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
+ FloatProp mViewAlpha = new FloatProp(1f, 0f, 75, 75, LINEAR);
+ FloatProp mTaskAlpha = new FloatProp(0f, 1f, 0, 75, LINEAR);
+
+ boolean isFirstFrame = true;
+
+ @Override
+ public void onUpdate(float percent) {
+
+ 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;
+ }
+ TaskWindowBounds tw = recentsInterpolator.interpolate(percent);
+
+ if (!skipViewChanges) {
+ v.setScaleX(tw.taskScale);
+ v.setScaleY(tw.taskScale);
+ v.setTranslationX(tw.taskX);
+ v.setTranslationY(tw.taskY);
+ v.setAlpha(mViewAlpha.value);
+ }
+
+ matrix.setScale(tw.winScale, tw.winScale);
+ matrix.postTranslate(tw.winX, tw.winY);
+ crop.set(tw.winCrop);
+
+ TransactionCompat t = new TransactionCompat();
+ for (RemoteAnimationTargetCompat target : targets) {
+ if (target.mode == RemoteAnimationTargetCompat.MODE_OPENING) {
+ t.setAlpha(target.leash, mTaskAlpha.value);
+
+ // 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 (!skipViewChanges) {
+ t.deferTransactionUntil(target.leash, surface, frameNumber);
+ }
+ }
+ if (isFirstFrame) {
+ t.show(target.leash);
+ }
+ }
+ t.setEarlyWakeup();
+ t.apply();
+
+ matrix.reset();
+ isFirstFrame = false;
+ }
+ });
+ 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;
+ }
}
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index b610f4d..33b922d 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -21,8 +21,7 @@
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.launcher3.LauncherState.FAST_OVERVIEW;
-import static com.android.launcher3.LauncherState.NORMAL;
+
import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_NONE;
import android.annotation.TargetApi;
@@ -43,9 +42,7 @@
import android.view.View;
import android.view.ViewConfiguration;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.LauncherState;
+import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.MainThreadExecutor;
import com.android.launcher3.R;
import com.android.launcher3.util.TraceHelper;
@@ -194,6 +191,7 @@
@Override
public void onDestroy() {
+ mOverviewCommandHelper.onDestroy();
sConnected = false;
super.onDestroy();
}
@@ -220,36 +218,38 @@
private TouchConsumer getCurrentTouchConsumer(
@HitTarget int downHitTarget, boolean forceToLauncher, VelocityTracker tracker) {
- RunningTaskInfo runningTaskInfo = mAM.getRunningTask();
+ RunningTaskInfo runningTaskInfo = mAM.getRunningTask(0);
if (runningTaskInfo == null && !forceToLauncher) {
return mNoOpTouchConsumer;
} else if (forceToLauncher ||
- runningTaskInfo.topActivity.equals(mOverviewCommandHelper.launcher)) {
- return getLauncherConsumer();
+ 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);
}
}
- private TouchConsumer getLauncherConsumer() {
- Launcher launcher = (Launcher) LauncherAppState.getInstance(this).getModel().getCallback();
- if (launcher == null) {
+ private TouchConsumer getOverviewConsumer() {
+ ActivityControlHelper activityHelper = mOverviewCommandHelper.getActivityControlHelper();
+ BaseDraggingActivity activity = activityHelper.getCreatedActivity();
+ if (activity == null) {
return mNoOpTouchConsumer;
}
- View target = launcher.getDragLayer();
- return new LauncherTouchConsumer(launcher, target);
+ return new OverviewTouchConsumer(activityHelper, activity);
}
- private static class LauncherTouchConsumer implements TouchConsumer {
+ private static class OverviewTouchConsumer<T extends BaseDraggingActivity>
+ implements TouchConsumer {
- private final Launcher mLauncher;
+ private final ActivityControlHelper<T> mActivityHelper;
+ private final T mActivity;
private final View mTarget;
private final int[] mLocationOnScreen = new int[2];
private final PointF mDownPos = new PointF();
@@ -260,12 +260,17 @@
private boolean mInvalidated = false;
private boolean mHadWindowFocusOnDown;
- LauncherTouchConsumer(Launcher launcher, View target) {
- mLauncher = launcher;
- mTarget = target;
+ private float mLastProgress = 0;
+ private boolean mStartPending = false;
+ private boolean mEndPending = false;
+
+ OverviewTouchConsumer(ActivityControlHelper<T> activityHelper, T activity) {
+ mActivityHelper = activityHelper;
+ mActivity = activity;
+ mTarget = activity.getDragLayer();
mTouchSlop = ViewConfiguration.get(mTarget.getContext()).getScaledTouchSlop();
- mQuickScrubController = mLauncher.<RecentsView>getOverviewPanel()
+ mQuickScrubController = mActivity.<RecentsView>getOverviewPanel()
.getQuickScrubController();
}
@@ -327,16 +332,22 @@
return;
}
if (interactionType == INTERACTION_QUICK_SCRUB) {
+ mStartPending = true;
+
Runnable action = () -> {
- LauncherState fromState = mLauncher.getStateManager().getState();
- mLauncher.getStateManager().goToState(FAST_OVERVIEW, true);
- mQuickScrubController.onQuickScrubStart(fromState == NORMAL);
+ mQuickScrubController.onQuickScrubStart(
+ mActivityHelper.onQuickInteractionStart(mActivity, true));
+ mQuickScrubController.onQuickScrubProgress(mLastProgress);
+ mStartPending = false;
+
+ if (mEndPending) {
+ mQuickScrubController.onQuickScrubEnd();
+ mEndPending = false;
+ }
+
};
- if (mLauncher.getWorkspace().runOnOverlayHidden(action)) {
- // Hide the minus one overlay so launcher can get window focus.
- mLauncher.onQuickstepGestureStarted(true);
- }
+ mActivityHelper.executeOnWindowAvailable(mActivity, action);
}
}
@@ -345,12 +356,17 @@
if (mInvalidated) {
return;
}
- mQuickScrubController.onQuickScrubEnd();
+ if (mStartPending) {
+ mEndPending = true;
+ } else {
+ mQuickScrubController.onQuickScrubEnd();
+ }
}
@Override
public void onQuickScrubProgress(float progress) {
- if (mInvalidated) {
+ mLastProgress = progress;
+ if (mInvalidated || mEndPending) {
return;
}
mQuickScrubController.onQuickScrubProgress(progress);
diff --git a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
index f6cf85a..fb061d0 100644
--- a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
+++ b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
@@ -30,11 +30,8 @@
import android.app.ActivityManager.RunningTaskInfo;
import android.content.Context;
import android.content.res.Resources;
-import android.graphics.Matrix;
-import android.graphics.Matrix.ScaleToFit;
import android.graphics.Point;
import android.graphics.Rect;
-import android.graphics.RectF;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
@@ -64,11 +61,11 @@
import com.android.quickstep.ActivityControlHelper.ActivityInitListener;
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.views.RecentsView;
import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.recents.model.ThumbnailData;
-import com.android.systemui.shared.recents.utilities.RectFEvaluator;
import com.android.systemui.shared.system.InputConsumerController;
import com.android.systemui.shared.system.LatencyTrackerCompat;
import com.android.systemui.shared.system.RecentsAnimationControllerCompat;
@@ -132,26 +129,8 @@
private static final float MIN_PROGRESS_FOR_OVERVIEW = 0.5f;
- // The bounds of the source app in device coordinates
- private final Rect mSourceStackBounds = new Rect();
- // The insets of the source app
- private final Rect mSourceInsets = new Rect();
- // The source app bounds with the source insets applied, in the source app window coordinates
- 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();
- // 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.
- private final RectF mSourceWindowClipInsets = new RectF();
+ private final ClipAnimationHelper mClipAnimationHelper = new ClipAnimationHelper();
- // The bounds of launcher (not including insets) in device coordinates
- private final Rect mHomeStackBounds = new Rect();
- // The clip rect in source app window coordinates
- private final Rect mClipRect = new Rect();
- private final RectFEvaluator mRectFEvaluator = new RectFEvaluator();
protected Runnable mGestureEndCallback;
protected boolean mIsGoingToHome;
private DeviceProfile mDp;
@@ -185,6 +164,7 @@
private float mCurrentDisplacement;
private boolean mGestureStarted;
private int mLogAction = Touch.SWIPE;
+ private float mCurrentQuickScrubProgress;
private @InteractionType int mInteractionType = INTERACTION_NORMAL;
@@ -192,14 +172,9 @@
InputConsumerController.getRecentsAnimationInputConsumer();
private final RecentsAnimationWrapper mRecentsAnimationWrapper = new RecentsAnimationWrapper();
- private Matrix mTmpMatrix = new Matrix();
private final long mTouchTimeMs;
private long mLauncherFrameDrawnTime;
- // Only used with the recents activity, when the screenshot should be fetched at the beginning
- // of the animation and not at the end when the activity is already paused
- private boolean mSkipScreenshotAtEndOfTransition;
-
WindowTransformSwipeHandler(RunningTaskInfo runningTaskInfo, Context context, long touchTimeMs,
ActivityControlHelper<T> controller) {
mContext = context;
@@ -254,11 +229,11 @@
mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_HANDLER_INVALIDATED,
this::invalidateHandlerWithLauncher);
- 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_SWITCH_TO_SCREENSHOT_COMPLETE
| STATE_QUICK_SCRUB_END, this::switchToFinalAppAfterQuickScrub);
}
@@ -273,42 +248,11 @@
private void initTransitionEndpoints(DeviceProfile dp) {
mDp = dp;
- mSourceRect.set(mSourceInsets.left, mSourceInsets.top,
- mSourceStackBounds.width() - mSourceInsets.right,
- mSourceStackBounds.height() - mSourceInsets.bottom);
Rect tempRect = new Rect();
mTransitionDragLength = mActivityControlHelper
.getSwipeUpDestinationAndLength(dp, mContext, tempRect);
-
- mTargetRect.set(tempRect);
- 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
- // source rect) is the amount to clip on each edge.
- RectF scaledTargetRect = new RectF(mTargetRect);
- Utilities.scaleRectFAboutCenter(scaledTargetRect,
- mSourceRect.width() / mTargetRect.width());
- scaledTargetRect.offsetTo(mSourceRect.left, mSourceRect.top);
- mSourceWindowClipInsets.set(
- Math.max(scaledTargetRect.left, 0),
- Math.max(scaledTargetRect.top, 0),
- Math.max(mSourceStackBounds.width() - scaledTargetRect.right, 0),
- Math.max(mSourceStackBounds.height() - scaledTargetRect.bottom, 0));
- mSourceRect.set(scaledTargetRect);
- }
-
- public int getTransitionLength() {
- return mTransitionDragLength;
- }
-
- public RectF getTargetRect(Point outWindowSize) {
- outWindowSize.set(mDp.widthPx, mDp.heightPx);
- return mInitialTargetRect;
+ mClipAnimationHelper.updateTargetRect(tempRect);
}
private long getFadeInDuration() {
@@ -365,7 +309,6 @@
return;
}
- mStateCallback.setState(STATE_LAUNCHER_STARTED);
mActivityControlHelper.prepareRecentsUI(mActivity, mWasLauncherAlreadyVisible);
AbstractFloatingView.closeAllOpenViews(activity, mWasLauncherAlreadyVisible);
@@ -400,6 +343,7 @@
mRecentsView.showTask(mRunningTaskId);
mRecentsView.setFirstTaskIconScaledDown(true /* isScaledDown */, false /* animate */);
mLayoutListener.open();
+ mStateCallback.setState(STATE_LAUNCHER_STARTED);
}
public void setLauncherOnDrawCallback(Runnable callback) {
@@ -482,40 +426,10 @@
synchronized (mRecentsAnimationWrapper) {
if (mRecentsAnimationWrapper.controller != null) {
- RectF currentRect;
- synchronized (mTargetRect) {
- Interpolator interpolator = mInteractionType == INTERACTION_QUICK_SCRUB
- ? ACCEL_2 : LINEAR;
- float interpolated = interpolator.getInterpolation(shift);
- currentRect = mRectFEvaluator.evaluate(interpolated, mSourceRect, mTargetRect);
- // Stay lined up with the center of the target, since it moves for quick scrub.
- currentRect.offset(mTargetRect.centerX() - currentRect.centerX(), 0);
- }
-
- mClipRect.left = (int) (mSourceWindowClipInsets.left * shift);
- mClipRect.top = (int) (mSourceWindowClipInsets.top * shift);
- mClipRect.right = (int)
- (mSourceStackBounds.width() - (mSourceWindowClipInsets.right * shift));
- mClipRect.bottom = (int)
- (mSourceStackBounds.height() - (mSourceWindowClipInsets.bottom * shift));
-
- mTmpMatrix.setRectToRect(mSourceRect, currentRect, ScaleToFit.FILL);
-
- TransactionCompat transaction = new TransactionCompat();
- for (RemoteAnimationTargetCompat app : mRecentsAnimationWrapper.targets) {
- if (app.mode == MODE_CLOSING) {
- 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 - shift);
- }
-
- transaction.show(app.leash);
- }
- }
- transaction.apply();
+ Interpolator interpolator = mInteractionType == INTERACTION_QUICK_SCRUB
+ ? ACCEL_2 : LINEAR;
+ float interpolated = interpolator.getInterpolation(shift);
+ mClipAnimationHelper.applyTransform(mRecentsAnimationWrapper.targets, interpolated);
}
}
@@ -533,13 +447,9 @@
if (firstTask != null) {
int scrollForFirstTask = mRecentsView.getScrollForPage(0);
int offsetFromFirstTask = (scrollForFirstTask - mRecentsView.getScrollX());
- synchronized (mTargetRect) {
- mTargetRect.set(mInitialTargetRect);
- Utilities.scaleRectFAboutCenter(mTargetRect, firstTask.getScaleX());
- float offsetX = offsetFromFirstTask + firstTask.getTranslationX();
- float offsetY = mRecentsView.getTranslationY();
- mTargetRect.offset(offsetX, offsetY);
- }
+ mClipAnimationHelper.offsetTarget(firstTask.getScaleX(),
+ offsetFromFirstTask + firstTask.getTranslationX(),
+ mRecentsView.getTranslationY());
}
if (mRecentsAnimationWrapper.controller != null) {
// TODO: This logic is spartanic!
@@ -564,13 +474,15 @@
for (RemoteAnimationTargetCompat target : apps) {
if (target.mode == MODE_CLOSING) {
DeviceProfile dp = LauncherAppState.getIDP(mContext).getDeviceProfile(mContext);
+ final Rect homeStackBounds;
+
if (minimizedHomeBounds != null) {
- mHomeStackBounds.set(minimizedHomeBounds);
+ homeStackBounds = minimizedHomeBounds;
dp = dp.getMultiWindowProfile(mContext,
new Point(minimizedHomeBounds.width(), minimizedHomeBounds.height()));
dp.updateInsets(homeContentInsets);
} else {
- mHomeStackBounds.set(new Rect(0, 0, dp.widthPx, dp.heightPx));
+ 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();
@@ -578,16 +490,7 @@
dp.updateInsets(insets);
}
- // Initialize the start and end animation bounds
- // TODO: Remove once platform is updated
- try {
- mSourceInsets.set(target.getContentInsets());
- } catch (Error e) {
- // TODO: Remove once platform is updated, use stable insets as fallback
- WindowManagerWrapper.getInstance().getStableInsets(mSourceInsets);
- }
- mSourceStackBounds.set(target.sourceContainerBounds);
-
+ mClipAnimationHelper.updateSource(homeStackBounds, target);
initTransitionEndpoints(dp);
}
}
@@ -736,7 +639,7 @@
};
synchronized (mRecentsAnimationWrapper) {
- if (mRecentsAnimationWrapper.controller != null && !mSkipScreenshotAtEndOfTransition) {
+ if (mRecentsAnimationWrapper.controller != null) {
TransactionCompat transaction = new TransactionCompat();
for (RemoteAnimationTargetCompat app : mRecentsAnimationWrapper.targets) {
if (app.mode == MODE_CLOSING) {
@@ -745,6 +648,7 @@
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,
@@ -763,12 +667,6 @@
doLogGesture(true /* toLauncher */);
}
- @UiThread
- public void switchToScreenshotImmediate(ThumbnailData thumbnail) {
- mRecentsView.updateThumbnail(mRunningTaskId, thumbnail);
- mSkipScreenshotAtEndOfTransition = true;
- }
-
private void setupLauncherUiAfterSwipeUpAnimation() {
if (mLauncherTransitionController != null) {
mLauncherTransitionController.getAnimationPlayer().end();
@@ -787,6 +685,9 @@
private void onQuickScrubStart() {
mActivityControlHelper.onQuickInteractionStart(mActivity, mWasLauncherAlreadyVisible);
mQuickScrubController.onQuickScrubStart(false);
+
+ // Inform the last progress in case we skipped before.
+ mQuickScrubController.onQuickScrubProgress(mCurrentQuickScrubProgress);
}
private void onFinishedTransitionToQuickScrub() {
@@ -794,10 +695,8 @@
}
public void onQuickScrubProgress(float progress) {
+ mCurrentQuickScrubProgress = 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
return;
}
mQuickScrubController.onQuickScrubProgress(progress);
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
index 7d7f88f..89422af 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
@@ -18,15 +18,16 @@
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;
import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.Insettable;
import com.android.quickstep.RecentsActivity;
+import com.android.quickstep.util.LayoutUtils;
import com.android.quickstep.views.RecentsView;
-public class FallbackRecentsView extends RecentsView<RecentsActivity> implements Insettable {
+public class FallbackRecentsView extends RecentsView<RecentsActivity> {
public FallbackRecentsView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
@@ -40,7 +41,7 @@
@Override
protected void onAllTasksRemoved() {
- mActivity.finish();
+ mActivity.startHome();
}
@Override
@@ -56,30 +57,18 @@
}
@Override
- public void setInsets(Rect insets) {
- mInsets.set(insets);
- DeviceProfile dp = mActivity.getDeviceProfile();
- Rect padding = getPadding(dp, getContext());
- verticalCenter(padding, dp);
- setPadding(padding.left, padding.top, padding.right, padding.bottom);
- }
-
- private static void verticalCenter(Rect padding, DeviceProfile dp) {
- Rect insets = dp.getInsets();
- int totalSpace = (padding.top + padding.bottom - insets.top - insets.bottom) / 2;
- padding.top = insets.top + totalSpace;
- padding.bottom = insets.bottom + totalSpace;
- }
-
- public static void getCenterPageRect(DeviceProfile grid, Context context, Rect outRect) {
- Rect targetPadding = getPadding(grid, context);
- verticalCenter(targetPadding, grid);
- getPageRect(grid, context, outRect, targetPadding);
- }
-
- @Override
public void draw(Canvas canvas) {
maybeDrawEmptyMessage(canvas);
super.draw(canvas);
}
+
+ @Override
+ protected void getTaskSize(DeviceProfile dp, Rect outRect) {
+ LayoutUtils.calculateTaskSize(getContext(), dp, 0, outRect);
+ }
+
+ @AnyThread
+ public static void getPageRect(DeviceProfile grid, Context context, Rect outRect) {
+ LayoutUtils.calculateTaskSize(context, grid, 0, outRect);
+ }
}
diff --git a/quickstep/src/com/android/quickstep/util/ClipAnimationHelper.java b/quickstep/src/com/android/quickstep/util/ClipAnimationHelper.java
new file mode 100644
index 0000000..493e9e2
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/ClipAnimationHelper.java
@@ -0,0 +1,135 @@
+/*
+ * 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 static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
+
+import android.graphics.Matrix;
+import android.graphics.Matrix.ScaleToFit;
+import android.graphics.Rect;
+import android.graphics.RectF;
+
+import com.android.launcher3.Utilities;
+import com.android.systemui.shared.recents.utilities.RectFEvaluator;
+import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
+import com.android.systemui.shared.system.TransactionCompat;
+
+/**
+ * Utility class to handle window clip animation
+ */
+public class ClipAnimationHelper {
+
+ // The bounds of the source app in device coordinates
+ private final Rect mSourceStackBounds = new Rect();
+ // The insets of the source app
+ private final Rect mSourceInsets = new Rect();
+ // The source app bounds with the source insets applied, in the source app window coordinates
+ 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();
+ // 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.
+ private final RectF mSourceWindowClipInsets = new RectF();
+
+ // The bounds of launcher (not including insets) in device coordinates
+ public final Rect mHomeStackBounds = new Rect();
+
+ // The clip rect in source app window coordinates
+ private final Rect mClipRect = new Rect();
+ private final RectFEvaluator mRectFEvaluator = new RectFEvaluator();
+ private final Matrix mTmpMatrix = new Matrix();
+
+
+ public void updateSource(Rect homeStackBounds, RemoteAnimationTargetCompat target) {
+ mHomeStackBounds.set(homeStackBounds);
+ mSourceInsets.set(target.getContentInsets());
+ mSourceStackBounds.set(target.sourceContainerBounds);
+
+ // TODO: Should sourceContainerBounds already have this offset?
+ mSourceStackBounds.offsetTo(target.position.x, target.position.y);
+ }
+
+ public void updateTargetRect(Rect targetRect) {
+ mSourceRect.set(mSourceInsets.left, mSourceInsets.top,
+ mSourceStackBounds.width() - mSourceInsets.right,
+ mSourceStackBounds.height() - mSourceInsets.bottom);
+ mTargetRect.set(targetRect);
+ 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
+ // source rect) is the amount to clip on each edge.
+ RectF scaledTargetRect = new RectF(mTargetRect);
+ Utilities.scaleRectFAboutCenter(scaledTargetRect,
+ mSourceRect.width() / mTargetRect.width());
+ scaledTargetRect.offsetTo(mSourceRect.left, mSourceRect.top);
+ mSourceWindowClipInsets.set(
+ Math.max(scaledTargetRect.left, 0),
+ Math.max(scaledTargetRect.top, 0),
+ Math.max(mSourceStackBounds.width() - scaledTargetRect.right, 0),
+ Math.max(mSourceStackBounds.height() - scaledTargetRect.bottom, 0));
+ mSourceRect.set(scaledTargetRect);
+ }
+
+ public void applyTransform(RemoteAnimationTargetCompat[] targets, float progress) {
+ RectF currentRect;
+ synchronized (mTargetRect) {
+ currentRect = mRectFEvaluator.evaluate(progress, mSourceRect, mTargetRect);
+ // Stay lined up with the center of the target, since it moves for quick scrub.
+ currentRect.offset(mTargetRect.centerX() - currentRect.centerX(), 0);
+ }
+
+ mClipRect.left = (int) (mSourceWindowClipInsets.left * progress);
+ mClipRect.top = (int) (mSourceWindowClipInsets.top * progress);
+ mClipRect.right = (int)
+ (mSourceStackBounds.width() - (mSourceWindowClipInsets.right * progress));
+ 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) {
+ 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);
+ }
+ }
+ transaction.setEarlyWakeup();
+ transaction.apply();
+ }
+
+ public void offsetTarget(float scale, float offsetX, float offsetY) {
+ synchronized (mTargetRect) {
+ mTargetRect.set(mInitialTargetRect);
+ Utilities.scaleRectFAboutCenter(mTargetRect, scale);
+ mTargetRect.offset(offsetX, offsetY);
+ }
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/util/LayoutUtils.java b/quickstep/src/com/android/quickstep/util/LayoutUtils.java
new file mode 100644
index 0000000..f29f9e4
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/LayoutUtils.java
@@ -0,0 +1,84 @@
+/*
+ * 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.content.Context;
+import android.content.res.Resources;
+import android.graphics.Rect;
+import android.graphics.RectF;
+
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.R;
+
+public class LayoutUtils {
+
+ public static void calculateLauncherTaskSize(Context context, DeviceProfile dp, Rect outRect) {
+ float extraSpace = dp.isVerticalBarLayout() ? 0 : dp.hotseatBarSizePx;
+ calculateTaskSize(context, dp, extraSpace, outRect);
+ }
+
+ public static void calculateTaskSize(Context context, DeviceProfile dp,
+ float extraVerticalSpace, 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;
+ } else {
+ taskHeight = taskHeight / 2 - halfDividerSize;
+ }
+ paddingHorz = res.getDimension(R.dimen.multi_window_task_card_horz_space);
+ } else {
+ taskWidth = dp.availableWidthPx;
+ taskHeight = dp.availableHeightPx;
+ paddingHorz = res.getDimension(dp.isVerticalBarLayout()
+ ? R.dimen.landscape_task_card_horz_space
+ : R.dimen.portrait_task_card_horz_space);
+ }
+
+ float topIconMargin = res.getDimension(R.dimen.task_thumbnail_top_margin);
+ float paddingVert = res.getDimension(R.dimen.task_card_vert_space);
+
+ // Note this should be same as dp.availableWidthPx and dp.availableHeightPx unless
+ // we override the insets ourselves.
+ int launcherVisibleWidth = dp.widthPx - insets.left - insets.right;
+ int launcherVisibleHeight = dp.heightPx - insets.top - insets.bottom;
+
+ float availableHeight = launcherVisibleHeight
+ - topIconMargin - extraVerticalSpace - paddingVert;
+ float availableWidth = launcherVisibleWidth - paddingHorz;
+
+ float scale = Math.min(availableWidth / taskWidth, availableHeight / taskHeight);
+ float outWidth = scale * taskWidth;
+ float outHeight = scale * taskHeight;
+
+ // Center in the visible space
+ float x = insets.left + (taskWidth - outWidth) / 2;
+ float y = insets.top + Math.max(topIconMargin,
+ (launcherVisibleHeight - extraVerticalSpace - outHeight) / 2);
+ outRect.set(Math.round(x), Math.round(y),
+ Math.round(x + outWidth), Math.round(y + outHeight));
+ }
+}
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 0000000..e798d5c
--- /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
new file mode 100644
index 0000000..2ffcae3
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/RemoteAnimationProvider.java
@@ -0,0 +1,55 @@
+/*
+ * 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.AnimatorSet;
+import android.app.ActivityOptions;
+import android.os.Handler;
+
+import com.android.launcher3.LauncherAnimationRunner;
+import com.android.systemui.shared.system.ActivityOptionsCompat;
+import com.android.systemui.shared.system.RemoteAnimationAdapterCompat;
+import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
+import com.android.systemui.shared.system.TransactionCompat;
+
+@FunctionalInterface
+public interface RemoteAnimationProvider {
+
+ AnimatorSet createWindowAnimation(RemoteAnimationTargetCompat[] targets);
+
+ default ActivityOptions toActivityOptions(Handler handler, long duration) {
+ LauncherAnimationRunner runner = new LauncherAnimationRunner(handler) {
+ @Override
+ public AnimatorSet getAnimator(RemoteAnimationTargetCompat[] targetCompats) {
+ return 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
+ ? Integer.MAX_VALUE
+ : target.prefixOrderIndex;
+ t.setLayer(target.leash, layer);
+ t.show(target.leash);
+ }
+ t.apply();
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/views/LauncherLayoutListener.java b/quickstep/src/com/android/quickstep/views/LauncherLayoutListener.java
index 6b7143d..ac34d90 100644
--- a/quickstep/src/com/android/quickstep/views/LauncherLayoutListener.java
+++ b/quickstep/src/com/android/quickstep/views/LauncherLayoutListener.java
@@ -15,6 +15,7 @@
*/
package com.android.quickstep.views;
+import static com.android.launcher3.states.RotationHelper.REQUEST_LOCK;
import static com.android.launcher3.states.RotationHelper.REQUEST_NONE;
import android.graphics.Rect;
@@ -39,6 +40,10 @@
super(launcher, null);
mLauncher = launcher;
setVisibility(INVISIBLE);
+
+ // For the duration of the gesture, lock the screen orientation to ensure that we do not
+ // rotate mid-quickscrub
+ launcher.getRotationHelper().setStateHandlerRequest(REQUEST_LOCK);
}
@Override
diff --git a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
index 6788827..4b4af3f 100644
--- a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -27,22 +27,22 @@
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;
import android.view.ViewDebug;
import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.Insettable;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
-import com.android.launcher3.R;
+import com.android.quickstep.util.LayoutUtils;
/**
* {@link RecentsView} used in Launcher activity
*/
@TargetApi(Build.VERSION_CODES.O)
-public class LauncherRecentsView extends RecentsView<Launcher> implements Insettable {
+public class LauncherRecentsView extends RecentsView<Launcher> {
public static final FloatProperty<LauncherRecentsView> TRANSLATION_Y_FACTOR =
new FloatProperty<LauncherRecentsView>("translationYFactor") {
@@ -61,8 +61,6 @@
@ViewDebug.ExportedProperty(category = "launcher")
private float mTranslationYFactor;
- private Rect mPagePadding = new Rect();
-
public LauncherRecentsView(Context context) {
this(context, null);
}
@@ -77,16 +75,6 @@
}
@Override
- public void setInsets(Rect insets) {
- mInsets.set(insets);
- DeviceProfile dp = mActivity.getDeviceProfile();
- Rect padding = getPadding(dp, getContext());
- setPadding(padding.left, padding.top, padding.right, padding.bottom);
- mPagePadding.set(padding);
- mPagePadding.top += getResources().getDimensionPixelSize(R.dimen.task_thumbnail_top_margin);
- }
-
- @Override
protected void onAllTasksRemoved() {
mActivity.getStateManager().goToState(NORMAL);
}
@@ -99,7 +87,7 @@
public void setTranslationYFactor(float translationFactor) {
mTranslationYFactor = translationFactor;
- setTranslationY(mTranslationYFactor * (mPagePadding.bottom - mPagePadding.top));
+ setTranslationY(mTranslationYFactor * (getPaddingBottom() - getPaddingTop()));
}
@Override
@@ -138,4 +126,14 @@
mActivity.getAllAppsController(), ALL_APPS_PROGRESS, allAppsProgressOffscreen));
return anim;
}
+
+ @Override
+ protected void getTaskSize(DeviceProfile dp, Rect outRect) {
+ LayoutUtils.calculateLauncherTaskSize(getContext(), dp, outRect);
+ }
+
+ @AnyThread
+ public static void getPageRect(DeviceProfile grid, Context context, Rect outRect) {
+ LayoutUtils.calculateLauncherTaskSize(context, grid, outRect);
+ }
}
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 644b098..faaa40d 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -53,18 +53,22 @@
import com.android.launcher3.BaseActivity;
import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Insettable;
import com.android.launcher3.PagedView;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.PropertyListBuilder;
import com.android.launcher3.config.FeatureFlags;
+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.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.systemui.shared.recents.model.RecentsTaskLoadPlan;
import com.android.systemui.shared.recents.model.RecentsTaskLoader;
import com.android.systemui.shared.recents.model.Task;
@@ -72,7 +76,6 @@
import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.TaskStackChangeListener;
-import com.android.systemui.shared.system.WindowManagerWrapper;
import java.util.ArrayList;
@@ -81,7 +84,9 @@
*/
@TargetApi(Build.VERSION_CODES.P)
public abstract class RecentsView<T extends BaseActivity>
- extends PagedView implements OnSharedPreferenceChangeListener {
+ extends PagedView implements OnSharedPreferenceChangeListener, Insettable {
+
+ private final Rect mTempRect = new Rect();
public static final FloatProperty<RecentsView> CONTENT_ALPHA =
new FloatProperty<RecentsView>("contentAlpha") {
@@ -96,8 +101,6 @@
}
};
-
-
public static final FloatProperty<RecentsView> ADJACENT_SCALE =
new FloatProperty<RecentsView>("adjacentScale") {
@Override
@@ -113,8 +116,6 @@
private static final String PREF_FLIP_RECENTS = "pref_flip_recents";
private static final int DISMISS_TASK_DURATION = 300;
- private static final Rect sTempStableInsets = new Rect();
-
protected final T mActivity;
private final QuickScrubController mQuickScrubController;
private final float mFastFlingVelocity;
@@ -130,13 +131,7 @@
private final TaskStackChangeListener mTaskStackListener = new TaskStackChangeListener() {
@Override
public void onTaskSnapshotChanged(int taskId, ThumbnailData snapshot) {
- for (int i = 0; i < getChildCount(); i++) {
- final TaskView taskView = (TaskView) getChildAt(i);
- if (taskView.getTask().key.id == taskId) {
- taskView.getThumbnail().setThumbnail(taskView.getTask(), snapshot);
- return;
- }
- }
+ updateThumbnail(taskId, snapshot);
}
@Override
@@ -238,7 +233,6 @@
final TaskView taskView = (TaskView) getChildAt(i);
if (taskView.getTask().key.id == taskId) {
taskView.onTaskDataLoaded(taskView.getTask(), thumbnailData);
- taskView.setAlpha(1);
return taskView;
}
}
@@ -324,7 +318,7 @@
private void applyLoadPlan(RecentsTaskLoadPlan loadPlan) {
if (mPendingAnimation != null) {
- mPendingAnimation.addEndListener((b) -> applyLoadPlan(loadPlan));
+ mPendingAnimation.addEndListener((onEndListener) -> applyLoadPlan(loadPlan));
return;
}
TaskStack stack = loadPlan != null ? loadPlan.getTaskStack() : null;
@@ -401,69 +395,20 @@
}
}
- protected static Rect getPadding(DeviceProfile profile, Context context) {
- WindowManagerWrapper.getInstance().getStableInsets(sTempStableInsets);
- Rect padding = new Rect(profile.workspacePadding);
-
- float taskWidth = profile.widthPx - sTempStableInsets.left - sTempStableInsets.right;
- float taskHeight = profile.heightPx - sTempStableInsets.top - sTempStableInsets.bottom;
-
- float overviewHeight, overviewWidth;
- if (profile.isVerticalBarLayout()) {
- float maxPadding = Math.max(padding.left, padding.right);
-
- // Use the same padding on both sides for symmetry.
- float availableWidth = taskWidth - 2 * maxPadding;
- float availableHeight = profile.availableHeightPx - padding.top - padding.bottom
- - sTempStableInsets.top;
- float scaledRatio = Math.min(availableWidth / taskWidth, availableHeight / taskHeight);
- overviewHeight = taskHeight * scaledRatio;
- overviewWidth = taskWidth * scaledRatio;
-
- } else {
- overviewHeight = profile.availableHeightPx - padding.top - padding.bottom
- - sTempStableInsets.top;
- overviewWidth = taskWidth * overviewHeight / taskHeight;
- }
-
- padding.bottom = profile.availableHeightPx - padding.top - sTempStableInsets.top
- - Math.round(overviewHeight);
- padding.left = padding.right = (int) ((profile.availableWidthPx - overviewWidth) / 2);
-
- // If the height ratio is larger than the width ratio, the screenshot will get cropped
- // at the bottom when swiping up. In this case, increase the top/bottom padding to make it
- // the same aspect ratio.
- Rect pageRect = new Rect();
- getPageRect(profile, context, pageRect, padding);
- float widthRatio = (float) pageRect.width() / taskWidth;
- float heightRatio = (float) pageRect.height() / taskHeight;
- if (heightRatio > widthRatio) {
- float additionalVerticalPadding = pageRect.height() - widthRatio * taskHeight;
- additionalVerticalPadding = Math.round(additionalVerticalPadding);
- padding.top += additionalVerticalPadding / 2;
- padding.bottom += additionalVerticalPadding / 2;
- }
-
- return padding;
- }
-
- public static void getPageRect(DeviceProfile grid, Context context, Rect outRect) {
- Rect targetPadding = getPadding(grid, context);
- getPageRect(grid, context, outRect, targetPadding);
- }
-
- protected static void getPageRect(DeviceProfile grid, Context context, Rect outRect,
- Rect targetPadding) {
- Rect insets = grid.getInsets();
- outRect.set(
- targetPadding.left + insets.left,
- targetPadding.top + insets.top,
- grid.widthPx - targetPadding.right - insets.right,
- grid.heightPx - targetPadding.bottom - insets.bottom);
- outRect.top += context.getResources()
+ @Override
+ public void setInsets(Rect insets) {
+ 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);
}
+ protected abstract void getTaskSize(DeviceProfile dp, Rect outRect);
+
@Override
protected boolean computeScrollHelper() {
boolean scrolling = super.computeScrollHelper();
@@ -601,12 +546,7 @@
new ActivityManager.TaskDescription(), 0, new ComponentName("", ""), false);
taskView.bind(mTmpRunningTask);
}
-
- mRunningTaskId = runningTaskId;
- setCurrentPage(0);
-
- // Load the tasks (if the loading is already
- mLoadPlanId = mModel.loadTasks(runningTaskId, this::applyLoadPlan);
+ setCurrentTask(mRunningTaskId);
// Hide the task that we are animating into, ignore if there is no associated task (ie. the
// assistant)
@@ -615,6 +555,17 @@
}
}
+ /**
+ * Similar to {@link #showTask(int)} but does not put any restrictions on the first tile.
+ */
+ public void setCurrentTask(int runningTaskId) {
+ mRunningTaskId = runningTaskId;
+ setCurrentPage(0);
+
+ // Load the tasks (if the loading is already
+ mLoadPlanId = mModel.loadTasks(runningTaskId, this::applyLoadPlan);
+ }
+
public void showNextTask() {
TaskView runningTaskView = getTaskView(mRunningTaskId);
if (runningTaskView == null) {
@@ -767,10 +718,16 @@
}
mPendingAnimation = pendingAnimation;
- mPendingAnimation.addEndListener((isSuccess) -> {
- if (isSuccess) {
+ mPendingAnimation.addEndListener((onEndListener) -> {
+ if (onEndListener.isSuccess) {
if (removeTask) {
- ActivityManagerWrapper.getInstance().removeTask(taskView.getTask().key.id);
+ Task task = taskView.getTask();
+ if (task != null) {
+ ActivityManagerWrapper.getInstance().removeTask(task.key.id);
+ mActivity.getUserEventDispatcher().logTaskLaunchOrDismiss(
+ onEndListener.logAction, Direction.UP,
+ TaskUtils.getComponentKeyForTask(task.key));
+ }
}
int pageToSnapTo = mCurrentPage;
if (draggedIndex < pageToSnapTo) {
@@ -818,7 +775,7 @@
AnimatorPlaybackController controller = AnimatorPlaybackController.wrap(
pendingAnim.anim, DISMISS_TASK_DURATION);
controller.dispatchOnStart();
- controller.setEndAction(() -> pendingAnim.finish(true));
+ controller.setEndAction(() -> pendingAnim.finish(true, Touch.SWIPE));
controller.getAnimationPlayer().setInterpolator(FAST_OUT_SLOW_IN);
controller.start();
}
@@ -857,11 +814,6 @@
snapToPageRelative(1);
}
- public void launchNextTask() {
- final TaskView nextTask = (TaskView) getChildAt(getNextPage());
- nextTask.launchTask(true);
- }
-
public void setContentAlpha(float alpha) {
if (mContentAlpha == alpha) {
return;
@@ -1075,9 +1027,15 @@
anim.setDuration(duration);
mPendingAnimation = new PendingAnimation(anim);
- mPendingAnimation.addEndListener((isSuccess) -> {
- if (isSuccess) {
+ mPendingAnimation.addEndListener((onEndListener) -> {
+ if (onEndListener.isSuccess) {
tv.launchTask(false);
+ Task task = tv.getTask();
+ if (task != null) {
+ mActivity.getUserEventDispatcher().logTaskLaunchOrDismiss(
+ onEndListener.logAction, Direction.DOWN,
+ TaskUtils.getComponentKeyForTask(task.key));
+ }
} else {
resetTaskVisuals();
}
@@ -1089,7 +1047,10 @@
@Override
protected void notifyPageSwitchListener(int prevPage) {
super.notifyPageSwitchListener(prevPage);
- getChildAt(mCurrentPage).sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
+ View currChild = getChildAt(mCurrentPage);
+ if (currChild != null) {
+ currChild.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
+ }
}
@Override
diff --git a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
index 8b41b58..58b7db7 100644
--- a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
@@ -22,12 +22,9 @@
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Color;
-import android.graphics.ComposeShader;
import android.graphics.LightingColorFilter;
-import android.graphics.LinearGradient;
import android.graphics.Matrix;
import android.graphics.Paint;
-import android.graphics.PorterDuff.Mode;
import android.graphics.Rect;
import android.graphics.Shader;
import android.util.AttributeSet;
@@ -50,14 +47,16 @@
private static final LightingColorFilter[] sDimFilterCache = new LightingColorFilter[256];
private final float mCornerRadius;
- private final float mFadeLength;
+ private final BaseActivity mActivity;
private final TaskOverlay mOverlay;
private final Paint mPaint = new Paint();
- private final Paint mLockedPaint = new Paint();
+ private final Paint mBackgroundPaint = new Paint();
private final Matrix mMatrix = new Matrix();
+ private float mClipBottom = -1;
+
private Task mTask;
private ThumbnailData mThumbnailData;
protected BitmapShader mBitmapShader;
@@ -75,10 +74,10 @@
public TaskThumbnailView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mCornerRadius = getResources().getDimension(R.dimen.task_corner_radius);
- mFadeLength = getResources().getDimension(R.dimen.task_fade_length);
mOverlay = TaskOverlayFactory.get(context).createOverlay(this);
mPaint.setFilterBitmap(true);
- mLockedPaint.setColor(Color.WHITE);
+ mBackgroundPaint.setColor(Color.WHITE);
+ mActivity = BaseActivity.fromContext(context);
}
public void bind() {
@@ -90,7 +89,9 @@
*/
public void setThumbnail(Task task, ThumbnailData thumbnailData) {
mTask = task;
- mPaint.setColor(task == null ? Color.BLACK : task.colorBackground | 0xFF000000);
+ int color = task == null ? Color.BLACK : task.colorBackground | 0xFF000000;
+ mPaint.setColor(color);
+ mBackgroundPaint.setColor(color);
if (thumbnailData != null && thumbnailData.thumbnail != null) {
Bitmap bm = thumbnailData.thumbnail;
@@ -128,8 +129,23 @@
if (mTask == null) {
return;
}
- canvas.drawRoundRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), mCornerRadius,
- mCornerRadius, mTask.isLocked ? mLockedPaint : mPaint);
+ 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();
+ canvas.save();
+ canvas.clipRect(0, mClipBottom, width, height);
+ canvas.drawRoundRect(0, 0, width, height, mCornerRadius, mCornerRadius,
+ mBackgroundPaint);
+ canvas.restore();
+ } else {
+ canvas.drawRoundRect(0, 0, width, height, mCornerRadius,
+ mCornerRadius, mTask.isLocked ? mBackgroundPaint : mPaint);
+ }
}
private void updateThumbnailPaintFilter() {
@@ -137,7 +153,7 @@
if (mBitmapShader != null) {
LightingColorFilter filter = getLightingColorFilter(mul);
mPaint.setColorFilter(filter);
- mLockedPaint.setColorFilter(filter);
+ mBackgroundPaint.setColorFilter(filter);
} else {
mPaint.setColorFilter(null);
mPaint.setColor(Color.argb(255, mul, mul, mul));
@@ -147,47 +163,38 @@
private void updateThumbnailMatrix() {
boolean rotate = false;
+ mClipBottom = -1;
if (mBitmapShader != null && mThumbnailData != null) {
float scale = mThumbnailData.scale;
+ Rect thumbnailInsets = mThumbnailData.insets;
float thumbnailWidth = mThumbnailData.thumbnail.getWidth() -
- (mThumbnailData.insets.left + mThumbnailData.insets.right) * scale;
+ (thumbnailInsets.left + thumbnailInsets.right) * scale;
float thumbnailHeight = mThumbnailData.thumbnail.getHeight() -
- (mThumbnailData.insets.top + mThumbnailData.insets.bottom) * scale;
+ (thumbnailInsets.top + thumbnailInsets.bottom) * scale;
+
final float thumbnailScale;
- final DeviceProfile profile = BaseActivity.fromContext(getContext())
- .getDeviceProfile();
+ final DeviceProfile profile = mActivity.getDeviceProfile();
+
if (getMeasuredWidth() == 0) {
// If we haven't measured , skip the thumbnail drawing and only draw the background
// color
thumbnailScale = 0f;
} else {
final Configuration configuration =
- getContext().getApplicationContext().getResources().getConfiguration();
- if (configuration.orientation == mThumbnailData.orientation) {
- // If we are in the same orientation as the screenshot, just scale it to the
- // width of the task view
- thumbnailScale = getMeasuredWidth() / thumbnailWidth;
- } else {
- if (FeatureFlags.OVERVIEW_USE_SCREENSHOT_ORIENTATION) {
- rotate = true;
- // Scale the height (will be width after rotation) to the width of this view
- thumbnailScale = getMeasuredWidth() / thumbnailHeight;
- } else {
- if (configuration.orientation == Configuration.ORIENTATION_PORTRAIT) {
- // Scale the landscape thumbnail up to app size, then scale that to the
- // task view size to match other portrait screenshots
- thumbnailScale = ((float) getMeasuredWidth() / profile.widthPx);
- } else {
- // Otherwise, scale the screenshot to fit 1:1 in the current orientation
- thumbnailScale = 1;
- }
- }
- }
+ getContext().getResources().getConfiguration();
+ // Rotate the screenshot if not in multi-window mode
+ rotate = FeatureFlags.OVERVIEW_USE_SCREENSHOT_ORIENTATION &&
+ configuration.orientation != mThumbnailData.orientation &&
+ !mActivity.isInMultiWindowModeCompat();
+ // Scale the screenshot to always fit the width of the card.
+ thumbnailScale = rotate
+ ? getMeasuredWidth() / thumbnailHeight
+ : getMeasuredWidth() / thumbnailWidth;
}
+
if (rotate) {
int rotationDir = profile.isVerticalBarLayout() && !profile.isSeascape() ? -1 : 1;
mMatrix.setRotate(90 * rotationDir);
- Rect thumbnailInsets = mThumbnailData.insets;
int newLeftInset = rotationDir == 1 ? thumbnailInsets.bottom : thumbnailInsets.top;
int newTopInset = rotationDir == 1 ? thumbnailInsets.left : thumbnailInsets.right;
mMatrix.postTranslate(-newLeftInset * scale, -newTopInset * scale);
@@ -209,27 +216,11 @@
mMatrix.postScale(thumbnailScale, thumbnailScale);
mBitmapShader.setLocalMatrix(mMatrix);
- Shader shader = mBitmapShader;
- if (!FeatureFlags.OVERVIEW_USE_SCREENSHOT_ORIENTATION) {
- float bitmapHeight = Math.max(thumbnailHeight * thumbnailScale, 0);
- if (Math.round(bitmapHeight) < getMeasuredHeight()) {
- int color = mPaint.getColor();
- LinearGradient fade = new LinearGradient(
- 0, bitmapHeight - mFadeLength, 0, bitmapHeight,
- color & 0x00FFFFFF, color, Shader.TileMode.CLAMP);
- shader = new ComposeShader(fade, shader, Mode.DST_OVER);
- }
-
- float bitmapWidth = Math.max(thumbnailWidth * thumbnailScale, 0);
- if (Math.round(bitmapWidth) < getMeasuredWidth()) {
- int color = mPaint.getColor();
- LinearGradient fade = new LinearGradient(
- bitmapWidth - mFadeLength, 0, bitmapWidth, 0,
- color & 0x00FFFFFF, color, Shader.TileMode.CLAMP);
- shader = new ComposeShader(fade, shader, Mode.DST_OVER);
- }
+ float bitmapHeight = Math.max(thumbnailHeight * thumbnailScale, 0);
+ if (Math.round(bitmapHeight) < getMeasuredHeight()) {
+ mClipBottom = bitmapHeight;
}
- mPaint.setShader(shader);
+ mPaint.setShader(mBitmapShader);
}
if (rotate) {
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index 42da472..f04acaf 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -31,11 +31,15 @@
import android.widget.FrameLayout;
import android.widget.ImageView;
+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;
import com.android.quickstep.views.RecentsView.ScrollState;
import com.android.systemui.shared.recents.model.Task;
@@ -82,7 +86,13 @@
public TaskView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
- setOnClickListener((view) -> launchTask(true /* animate */));
+ setOnClickListener((view) -> {
+ if (mTask != null) {
+ launchTask(true /* animate */);
+ BaseActivity.fromContext(context).getUserEventDispatcher().logTaskLaunchOrDismiss(
+ Touch.TAP, Direction.NONE, TaskUtils.getComponentKeyForTask(mTask.key));
+ }
+ });
setOutlineProvider(new TaskOutlineProvider(getResources()));
}
@@ -138,7 +148,10 @@
mSnapshotView.setThumbnail(task, thumbnailData);
mIconView.setImageDrawable(task.icon);
mIconView.setOnClickListener(icon -> TaskMenuView.showForTask(this));
- mIconView.setOnLongClickListener(icon -> TaskMenuView.showForTask(this));
+ mIconView.setOnLongClickListener(icon -> {
+ requestDisallowInterceptTouchEvent(true);
+ return TaskMenuView.showForTask(this);
+ });
}
@Override
diff --git a/res/drawable/ic_close.xml b/res/drawable/ic_close.xml
index fc9ed49..8b2f55f 100644
--- a/res/drawable/ic_close.xml
+++ b/res/drawable/ic_close.xml
@@ -19,5 +19,5 @@
android:viewportWidth="24.0">
<path
android:fillColor="@android:color/white"
- android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z"/>
+ android:pathData="M19,6.41L17.59,5L12,10.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L12,13.41L17.59,19L19,17.59L13.41,12L19,6.41z" />
</vector>
\ No newline at end of file
diff --git a/res/drawable/ic_uninstall_no_shadow.xml b/res/drawable/ic_uninstall_no_shadow.xml
index 2a86e10..37632d1 100644
--- a/res/drawable/ic_uninstall_no_shadow.xml
+++ b/res/drawable/ic_uninstall_no_shadow.xml
@@ -21,6 +21,11 @@
android:tint="?android:attr/textColorPrimary" >
<path
android:fillColor="#FFFFFFFF"
- android:pathData="M6,19c0,1.1,0.9,2,2,2h8c1.1,0,2-0.9,2-2V7H6V19z M18,4h-2.5l-0.71-0.71C14.61,3.11,14.35,3,14.09,
- 3H9.9C9.64,3,9.38,3.11,9.2,3.29L8.49,4h-2.5c-0.55,0-1,0.45-1,1s0.45,1,1,1h12c0.55,0,1-0.45,1-1C19,4.45,18.55,4,18,4L18,4z"/>
+ android:pathData="M15,4V3H9v1H4v2h1v13c0,1.1,0.9,2,2,2h10c1.1,0,2-0.9,2-2V6h1V4H15z M17,19H7V6h10V19z" />
+ <path
+ android:fillColor="#FFFFFFFF"
+ android:pathData="M 9 8 H 11 V 17 H 9 V 8 Z" />
+ <path
+ android:fillColor="#FFFFFFFF"
+ android:pathData="M 13 8 H 15 V 17 H 13 V 8 Z" />
</vector>
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 6f35752..4deed73 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -41,7 +41,7 @@
public final boolean transposeLayoutWithOrientation;
// Device properties in current orientation
- private final boolean isLandscape;
+ public final boolean isLandscape;
public final boolean isMultiWindowMode;
public final int widthPx;
diff --git a/src/com/android/launcher3/ItemInfoWithIcon.java b/src/com/android/launcher3/ItemInfoWithIcon.java
index bf985c3..4677d31 100644
--- a/src/com/android/launcher3/ItemInfoWithIcon.java
+++ b/src/com/android/launcher3/ItemInfoWithIcon.java
@@ -91,6 +91,11 @@
public static final int FLAG_ADAPTIVE_ICON = 1 << 8;
/**
+ * Flag indicating that the icon is badged.
+ */
+ public static final int FLAG_ICON_BADGED = 1 << 9;
+
+ /**
* Status associated with the system state of the underlying item. This is calculated every
* time a new info is created and not persisted on the disk.
*/
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index b410f4f..90c55c9 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -294,6 +294,7 @@
mPopupDataProvider = new PopupDataProvider(this);
mRotationHelper = new RotationHelper(this);
+ mAppTransitionManager = LauncherAppTransitionManager.newInstance(this);
boolean internalStateHandled = InternalStateHandler.handleCreate(this, getIntent());
if (internalStateHandled) {
@@ -341,8 +342,6 @@
getSystemUiController().updateUiState(SystemUiController.UI_STATE_BASE_WINDOW,
Themes.getAttrBoolean(this, R.attr.isWorkspaceDarkText));
- mAppTransitionManager = LauncherAppTransitionManager.newInstance(this);
-
if (mLauncherCallbacks != null) {
mLauncherCallbacks.onCreate(savedInstanceState);
}
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index cabccbf..ba96d4a 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -105,7 +105,8 @@
* Indicates if the device has a debug build. Should only be used to store additional info or
* add extra logging and not for changing the app behavior.
*/
- public static final boolean IS_DEBUG_DEVICE = Build.TYPE.toLowerCase().contains("debug");
+ public static final boolean IS_DEBUG_DEVICE = Build.TYPE.toLowerCase().contains("debug")
+ || Build.TYPE.toLowerCase().equals("eng");
// An intent extra to indicate the horizontal scroll of the wallpaper.
public static final String EXTRA_WALLPAPER_OFFSET = "com.android.launcher3.WALLPAPER_OFFSET";
diff --git a/src/com/android/launcher3/dragndrop/DragView.java b/src/com/android/launcher3/dragndrop/DragView.java
index 8d4f2ef..e1e1f83 100644
--- a/src/com/android/launcher3/dragndrop/DragView.java
+++ b/src/com/android/launcher3/dragndrop/DragView.java
@@ -46,6 +46,7 @@
import com.android.launcher3.FastBitmapDrawable;
import com.android.launcher3.ItemInfo;
+import com.android.launcher3.ItemInfoWithIcon;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAnimUtils;
import com.android.launcher3.LauncherAppState;
@@ -69,6 +70,8 @@
import java.util.Arrays;
import java.util.List;
+import static com.android.launcher3.ItemInfoWithIcon.FLAG_ICON_BADGED;
+
public class DragView extends View {
private static final ColorMatrix sTempMatrix1 = new ColorMatrix();
private static final ColorMatrix sTempMatrix2 = new ColorMatrix();
@@ -364,7 +367,10 @@
private Drawable getBadge(ItemInfo info, LauncherAppState appState, Object obj) {
int iconSize = appState.getInvariantDeviceProfile().iconBitmapSize;
if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
- if (info.id == ItemInfo.NO_ID || !(obj instanceof ShortcutInfoCompat)) {
+ boolean iconBadged = (info instanceof ItemInfoWithIcon)
+ && (((ItemInfoWithIcon) info).runtimeStatusFlags & FLAG_ICON_BADGED) > 0;
+ if ((info.id == ItemInfo.NO_ID && !iconBadged)
+ || !(obj instanceof ShortcutInfoCompat)) {
// The item is not yet added on home screen.
return new FixedSizeEmptyDrawable(iconSize);
}
diff --git a/src/com/android/launcher3/logging/UserEventDispatcher.java b/src/com/android/launcher3/logging/UserEventDispatcher.java
index 90355bd..bf870cc 100644
--- a/src/com/android/launcher3/logging/UserEventDispatcher.java
+++ b/src/com/android/launcher3/logging/UserEventDispatcher.java
@@ -16,6 +16,14 @@
package com.android.launcher3.logging;
+import static com.android.launcher3.logging.LoggerUtils.newCommandAction;
+import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
+import static com.android.launcher3.logging.LoggerUtils.newDropTarget;
+import static com.android.launcher3.logging.LoggerUtils.newItemTarget;
+import static com.android.launcher3.logging.LoggerUtils.newLauncherEvent;
+import static com.android.launcher3.logging.LoggerUtils.newTarget;
+import static com.android.launcher3.logging.LoggerUtils.newTouchAction;
+
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
@@ -38,20 +46,13 @@
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
import com.android.launcher3.userevent.nano.LauncherLogProto.LauncherEvent;
import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
+import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.InstantAppResolver;
import com.android.launcher3.util.LogConfig;
import java.util.Locale;
import java.util.UUID;
-import static com.android.launcher3.logging.LoggerUtils.newCommandAction;
-import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
-import static com.android.launcher3.logging.LoggerUtils.newDropTarget;
-import static com.android.launcher3.logging.LoggerUtils.newItemTarget;
-import static com.android.launcher3.logging.LoggerUtils.newLauncherEvent;
-import static com.android.launcher3.logging.LoggerUtils.newTarget;
-import static com.android.launcher3.logging.LoggerUtils.newTouchAction;
-
/**
* Manages the creation of {@link LauncherEvent}.
* To debug this class, execute following command before side loading a new apk.
@@ -162,14 +163,15 @@
dispatchUserEvent(event, intent);
}
- public void logTaskLaunch(int action, int direction, ComponentName componentName){
- LauncherEvent event = newLauncherEvent(newTouchAction(action), // TAP or SWIPE
+ public void logTaskLaunchOrDismiss(int action, int direction, ComponentKey componentKey) {
+ LauncherEvent event = newLauncherEvent(newTouchAction(action), // TAP or SWIPE or FLING
newTarget(Target.Type.ITEM));
if (action == Action.Touch.SWIPE || action == Action.Touch.FLING) {
+ // Direction DOWN means the task was launched, UP means it was dismissed.
event.action.dir = direction;
}
event.srcTarget[0].itemType = LauncherLogProto.ItemType.TASK;
- fillComponentInfo(event.srcTarget[0], componentName);
+ fillComponentInfo(event.srcTarget[0], componentKey.componentName);
dispatchUserEvent(event, null);
}
diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
index 454fc7d..b522b55 100644
--- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java
+++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
@@ -517,6 +517,12 @@
}
@Override
+ protected void closeComplete() {
+ super.closeComplete();
+ mOriginalIcon.setTextVisibility(mOriginalIcon.shouldTextBeVisible());
+ }
+
+ @Override
public boolean onTouch(View v, MotionEvent ev) {
// Touched a shortcut, update where it was touched so we can drag from there on long click.
switch (ev.getAction()) {
diff --git a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
index 9726704..f1195ed 100644
--- a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
+++ b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
@@ -124,6 +124,10 @@
return mLauncher.getAllAppsController().getShiftRange();
}
+ /**
+ * Returns the state to go to from fromState given the drag direction. If there is no state in
+ * that direction, returns fromState.
+ */
protected abstract LauncherState getTargetState(LauncherState fromState,
boolean isDragTowardPositive);
@@ -246,24 +250,24 @@
}
protected void onSwipeInteractionCompleted(LauncherState targetState, int logAction) {
- if (targetState != mFromState) {
- // Transition complete. log the action
- mLauncher.getUserEventDispatcher().logStateChangeAction(logAction,
- getDirectionForLog(),
- mStartContainerType,
- mFromState.containerType,
- mToState.containerType,
- mLauncher.getWorkspace().getCurrentPage());
- }
clearState();
boolean shouldGoToTargetState = true;
if (mPendingAnimation != null) {
boolean reachedTarget = mToState == targetState;
- mPendingAnimation.finish(reachedTarget);
+ mPendingAnimation.finish(reachedTarget, logAction);
mPendingAnimation = null;
shouldGoToTargetState = !reachedTarget;
}
if (shouldGoToTargetState) {
+ if (targetState != mFromState) {
+ // Transition complete. log the action
+ mLauncher.getUserEventDispatcher().logStateChangeAction(logAction,
+ getDirectionForLog(),
+ mStartContainerType,
+ mFromState.containerType,
+ mToState.containerType,
+ mLauncher.getWorkspace().getCurrentPage());
+ }
mLauncher.getStateManager().goToState(targetState, false /* animated */);
}
}
diff --git a/src/com/android/launcher3/util/PendingAnimation.java b/src/com/android/launcher3/util/PendingAnimation.java
index 4116d56..617a38b 100644
--- a/src/com/android/launcher3/util/PendingAnimation.java
+++ b/src/com/android/launcher3/util/PendingAnimation.java
@@ -32,7 +32,7 @@
@TargetApi(Build.VERSION_CODES.O)
public class PendingAnimation {
- private final ArrayList<Consumer<Boolean>> mEndListeners = new ArrayList<>();
+ private final ArrayList<Consumer<OnEndListener>> mEndListeners = new ArrayList<>();
public final AnimatorSet anim;
@@ -40,14 +40,24 @@
this.anim = anim;
}
- public void finish(boolean isSuccess) {
- for (Consumer<Boolean> listeners : mEndListeners) {
- listeners.accept(isSuccess);
+ public void finish(boolean isSuccess, int logAction) {
+ for (Consumer<OnEndListener> listeners : mEndListeners) {
+ listeners.accept(new OnEndListener(isSuccess, logAction));
}
mEndListeners.clear();
}
- public void addEndListener(Consumer<Boolean> listener) {
+ public void addEndListener(Consumer<OnEndListener> listener) {
mEndListeners.add(listener);
}
+
+ public static class OnEndListener {
+ public boolean isSuccess;
+ public int logAction;
+
+ public OnEndListener(boolean isSuccess, int logAction) {
+ this.isSuccess = isSuccess;
+ this.logAction = logAction;
+ }
+ }
}
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/AllAppsSwipeController.java b/src_ui_overrides/com/android/launcher3/uioverrides/AllAppsSwipeController.java
index c97c3cc..d1cddc1 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/AllAppsSwipeController.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/AllAppsSwipeController.java
@@ -54,7 +54,12 @@
@Override
protected LauncherState getTargetState(LauncherState fromState, boolean isDragTowardPositive) {
- return fromState == ALL_APPS ? NORMAL : ALL_APPS;
+ if (fromState == NORMAL && isDragTowardPositive) {
+ return ALL_APPS;
+ } else if (fromState == ALL_APPS && !isDragTowardPositive) {
+ return NORMAL;
+ }
+ return fromState;
}
@Override