Merge "Various swipe up animation optimizations" into ub-launcher3-master
diff --git a/quickstep/res/layout/overview_panel.xml b/quickstep/res/layout/overview_panel.xml
index a8b91c5..22f014a 100644
--- a/quickstep/res/layout/overview_panel.xml
+++ b/quickstep/res/layout/overview_panel.xml
@@ -19,7 +19,6 @@
android:theme="@style/HomeScreenElementTheme"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:layout_gravity="center"
android:clipChildren="false"
android:clipToPadding="false"
android:alpha="0.0"
diff --git a/quickstep/res/layout/task.xml b/quickstep/res/layout/task.xml
index fdf1adc..839d934 100644
--- a/quickstep/res/layout/task.xml
+++ b/quickstep/res/layout/task.xml
@@ -23,14 +23,14 @@
android:id="@+id/snapshot"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:layout_marginTop="24dp"
+ android:layout_marginTop="@dimen/task_thumbnail_top_margin"
android:scaleType="matrix"
android:background="@drawable/task_thumbnail_background"
android:elevation="4dp" />
<ImageView
android:id="@+id/icon"
- android:layout_width="48dp"
- android:layout_height="48dp"
+ android:layout_width="@dimen/task_thumbnail_icon_size"
+ android:layout_height="@dimen/task_thumbnail_icon_size"
android:layout_gravity="top|center_horizontal"
android:elevation="5dp"/>
</com.android.quickstep.TaskView>
\ No newline at end of file
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
new file mode 100644
index 0000000..4f85957
--- /dev/null
+++ b/quickstep/res/values/dimens.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 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.
+-->
+
+<resources>
+
+ <dimen name="task_thumbnail_top_margin">24dp</dimen>
+ <dimen name="task_thumbnail_icon_size">48dp</dimen>
+
+ <dimen name="quickstep_fling_threshold_velocity">500dp</dimen>
+ <dimen name="quickstep_fling_min_velocity">250dp</dimen>
+
+</resources>
diff --git a/quickstep/src/com/android/quickstep/AnimatedFloat.java b/quickstep/src/com/android/quickstep/AnimatedFloat.java
new file mode 100644
index 0000000..1f6781e
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/AnimatedFloat.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2017 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.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.util.FloatProperty;
+
+/**
+ * A mutable float which allows animating the value
+ */
+public class AnimatedFloat {
+
+ public static FloatProperty<AnimatedFloat> VALUE = new FloatProperty<AnimatedFloat>("value") {
+ @Override
+ public void setValue(AnimatedFloat obj, float v) {
+ obj.updateValue(v);
+ }
+
+ @Override
+ public Float get(AnimatedFloat obj) {
+ return obj.value;
+ }
+ };
+
+ private final Runnable mUpdateCallback;
+ private ObjectAnimator mValueAnimator;
+
+ public float value;
+
+ public AnimatedFloat(Runnable updateCallback) {
+ mUpdateCallback = updateCallback;
+ }
+
+ public ObjectAnimator animateToValue(float v) {
+ if (mValueAnimator != null) {
+ mValueAnimator.cancel();
+ }
+ mValueAnimator = ObjectAnimator.ofFloat(this, VALUE, v);
+ mValueAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animator) {
+ if (mValueAnimator == animator) {
+ mValueAnimator = null;
+ }
+ }
+ });
+ return mValueAnimator;
+ }
+
+ /**
+ * Changes the value and calls the callback.
+ * Note that the value can be directly accessed as well to avoid notifying the callback.
+ */
+ public void updateValue(float v) {
+ if (Float.compare(v, value) != 0) {
+ value = v;
+ mUpdateCallback.run();
+ }
+ }
+
+ public ObjectAnimator getCurrentAnimation() {
+ return mValueAnimator;
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/MultiStateCallback.java b/quickstep/src/com/android/quickstep/MultiStateCallback.java
new file mode 100644
index 0000000..cca2729
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/MultiStateCallback.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2017 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.util.SparseArray;
+
+/**
+ * Utility class to help manage multiple callbacks based on different states.
+ */
+public class MultiStateCallback {
+
+ private final SparseArray<Runnable> mCallbacks = new SparseArray<>();
+
+ private int mState = 0;
+
+ /**
+ * Adds the provided state flags to the global state and executes any callbacks as a result.
+ * @param stateFlag
+ */
+ public void setState(int stateFlag) {
+ mState = mState | stateFlag;
+
+ int count = mCallbacks.size();
+ for (int i = 0; i < count; i++) {
+ int state = mCallbacks.keyAt(i);
+
+ if ((mState & state) == state) {
+ Runnable callback = mCallbacks.valueAt(i);
+ if (callback != null) {
+ // Set the callback to null, so that it does not run again.
+ mCallbacks.setValueAt(i, null);
+ callback.run();
+ }
+ }
+ }
+ }
+
+ /**
+ * Sets the callbacks to be run when the provided states are enabled.
+ * The callback is only run once.
+ */
+ public void addCallback(int stateMask, Runnable callback) {
+ mCallbacks.put(stateMask, callback);
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/NavBarSwipeInteractionHandler.java b/quickstep/src/com/android/quickstep/NavBarSwipeInteractionHandler.java
index 34e3c4e..dc7d648 100644
--- a/quickstep/src/com/android/quickstep/NavBarSwipeInteractionHandler.java
+++ b/quickstep/src/com/android/quickstep/NavBarSwipeInteractionHandler.java
@@ -20,52 +20,57 @@
import android.animation.RectEvaluator;
import android.annotation.TargetApi;
import android.app.ActivityManager.RunningTaskInfo;
+import android.app.ActivityOptions;
+import android.content.Context;
+import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Rect;
import android.os.Build;
+import android.os.Handler;
+import android.os.UserHandle;
import android.support.annotation.BinderThread;
import android.support.annotation.UiThread;
-import android.util.FloatProperty;
+import android.util.DisplayMetrics;
import android.view.Choreographer;
import android.view.Choreographer.FrameCallback;
import android.view.View;
-import android.view.ViewGroup;
+import android.view.ViewTreeObserver.OnPreDrawListener;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.Hotseat;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
+import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimationSuccessListener;
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.states.InternalStateHandler;
import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan;
+import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.recents.model.Task.TaskKey;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.WindowManagerWrapper;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Future;
+import java.util.concurrent.atomic.AtomicBoolean;
@TargetApi(Build.VERSION_CODES.O)
public class NavBarSwipeInteractionHandler extends InternalStateHandler implements FrameCallback {
- private static FloatProperty<NavBarSwipeInteractionHandler> SHIFT =
- new FloatProperty<NavBarSwipeInteractionHandler>("currentShift") {
- @Override
- public void setValue(NavBarSwipeInteractionHandler handler, float v) {
- handler.setShift(v);
- }
+ private static final int STATE_LAUNCHER_READY = 1 << 0;
+ private static final int STATE_RECENTS_DELAY_COMPLETE = 1 << 1;
+ private static final int STATE_LOAD_PLAN_READY = 1 << 2;
+ private static final int STATE_RECENTS_FULLY_VISIBLE = 1 << 3;
+ private static final int STATE_ACTIVITY_MULTIPLIER_COMPLETE = 1 << 4;
+ private static final int STATE_SCALED_SNAPSHOT_RECENTS = 1 << 5;
+ private static final int STATE_SCALED_SNAPSHOT_APP = 1 << 6;
- @Override
- public Float get(NavBarSwipeInteractionHandler handler) {
- return handler.mCurrentShift;
- }
- };
+ private static final long RECENTS_VIEW_VISIBILITY_DELAY = 120;
+ private static final long RECENTS_VIEW_VISIBILITY_DURATION = 150;
+ private static final long DEFAULT_SWIPE_DURATION = 200;
- // The following constants need to be scaled based on density. The scaled versions will be
- // assigned to the corresponding member variables below.
- private static final int FLING_THRESHOLD_VELOCITY = 500;
- private static final int MIN_FLING_VELOCITY = 250;
+ // Ideal velocity for a smooth transition
+ private static final float PIXEL_PER_MS = 2f;
private static final float MIN_PROGRESS_FOR_OVERVIEW = 0.5f;
@@ -75,43 +80,102 @@
private final Rect mCurrentRect = new Rect();
private final RectEvaluator mRectEvaluator = new RectEvaluator(mCurrentRect);
- private final Bitmap mTaskSnapshot;
- private final int mRunningTaskId;
- private Future<RecentsTaskLoadPlan> mFutureLoadPlan;
-
- private Launcher mLauncher;
- private Choreographer mChoreographer;
- private SnapshotDragView mDragView;
- private RecentsView mRecentsView;
- private Hotseat mHotseat;
-
- private float mStartDelta;
- private float mLastDelta;
-
// Shift in the range of [0, 1].
// 0 => preview snapShot is completely visible, and hotseat is completely translated down
// 1 => preview snapShot is completely aligned with the recents view and hotseat is completely
// visible.
- private float mCurrentShift;
+ private final AnimatedFloat mCurrentShift = new AnimatedFloat(this::updateFinalShift);
+
+ // Activity multiplier in the range of [0, 1]. When the activity becomes visible, this is
+ // animated to 1, so allow for a smooth transition.
+ private final AnimatedFloat mActivityMultiplier = new AnimatedFloat(this::updateFinalShift);
+
+ private final Choreographer mChoreographer;
+ private final AtomicBoolean mFrameScheduled = new AtomicBoolean(false);
+
+ private final int mRunningTaskId;
+ private final Context mContext;
+
+ private final MultiStateCallback mStateCallback;
+
+ private Launcher mLauncher;
+ private SnapshotDragView mDragView;
+ private RecentsView mRecentsView;
+ private Hotseat mHotseat;
+ private RecentsTaskLoadPlan mLoadPlan;
+
+ private boolean mLauncherReady;
+ private boolean mTouchEndHandled;
+
+ private Bitmap mTaskSnapshot;
// These are updated on the binder thread, and eventually picked up on doFrame
private volatile float mCurrentDisplacement;
private volatile float mEndVelocity;
private volatile boolean mTouchEnded = false;
- NavBarSwipeInteractionHandler(Bitmap taskSnapShot, RunningTaskInfo runningTaskInfo) {
- mTaskSnapshot = taskSnapShot;
+ NavBarSwipeInteractionHandler(
+ RunningTaskInfo runningTaskInfo, Choreographer choreographer, Context context) {
mRunningTaskId = runningTaskInfo.id;
+ mChoreographer = choreographer;
+ mContext = context;
WindowManagerWrapper.getInstance().getStableInsets(mStableInsets);
+
+ // Build the state callback
+ mStateCallback = new MultiStateCallback();
+ mStateCallback.addCallback(STATE_LAUNCHER_READY, this::onLauncherReady);
+ mStateCallback.addCallback(STATE_LOAD_PLAN_READY | STATE_RECENTS_DELAY_COMPLETE,
+ this::setTaskPlanToUi);
+ mStateCallback.addCallback(STATE_SCALED_SNAPSHOT_APP, this::resumeLastTask);
+ mStateCallback.addCallback(STATE_RECENTS_FULLY_VISIBLE | STATE_SCALED_SNAPSHOT_RECENTS
+ | STATE_ACTIVITY_MULTIPLIER_COMPLETE,
+ this::onAnimationToLauncherComplete);
+ mStateCallback.addCallback(STATE_LAUNCHER_READY | STATE_SCALED_SNAPSHOT_APP,
+ this::cleanupLauncher);
+ }
+
+ private void onLauncherReady() {
+ mLauncherReady = true;
+ executeFrameUpdate();
+
+ // Wait for some time before loading recents so that the first frame is fast
+ new Handler().postDelayed(() -> mStateCallback.setState(STATE_RECENTS_DELAY_COMPLETE),
+ RECENTS_VIEW_VISIBILITY_DELAY);
+
+ long duration = Math.min(DEFAULT_SWIPE_DURATION,
+ Math.max((long) (-mCurrentDisplacement / PIXEL_PER_MS), 0));
+ if (mCurrentShift.getCurrentAnimation() != null) {
+ ObjectAnimator anim = mCurrentShift.getCurrentAnimation();
+ long theirDuration = anim.getDuration() - anim.getCurrentPlayTime();
+
+ // TODO: Find a better heuristic
+ duration = (duration + theirDuration) / 2;
+ }
+ ObjectAnimator anim = mActivityMultiplier.animateToValue(1)
+ .setDuration(duration);
+ anim.addListener(new AnimationSuccessListener() {
+ @Override
+ public void onAnimationSuccess(Animator animator) {
+ mStateCallback.setState(STATE_ACTIVITY_MULTIPLIER_COMPLETE);
+ }
+ });
+ anim.start();
+ }
+
+ public void setTaskSnapshot(Bitmap taskSnapshot) {
+ mTaskSnapshot = taskSnapshot;
}
@Override
public void onLauncherResume() {
- mStartDelta = mCurrentDisplacement;
- mLastDelta = mStartDelta;
- mChoreographer = Choreographer.getInstance();
-
- scheduleNextFrame();
+ mDragView.getViewTreeObserver().addOnPreDrawListener(new OnPreDrawListener() {
+ @Override
+ public boolean onPreDraw() {
+ mDragView.getViewTreeObserver().removeOnPreDrawListener(this);
+ mStateCallback.setState(STATE_LAUNCHER_READY);
+ return true;
+ }
+ });
}
@Override
@@ -129,9 +193,7 @@
// Optimization
mLauncher.getAppsView().setVisibility(View.GONE);
-
- // Launch overview, animate if already on home
- mRecentsView.update(consumeLastLoadPlan());
+ mRecentsView.setVisibility(View.GONE);
}
/**
@@ -143,121 +205,131 @@
@BinderThread
public void updateDisplacement(float displacement) {
mCurrentDisplacement = displacement;
+ scheduleFrameIfNeeded();
}
@BinderThread
public void endTouch(float endVelocity) {
mEndVelocity = endVelocity;
mTouchEnded = true;
+ scheduleFrameIfNeeded();
}
- @UiThread
- private void scheduleNextFrame() {
- if (!mTouchEnded) {
+ private void scheduleFrameIfNeeded() {
+ boolean alreadyScheduled = mFrameScheduled.getAndSet(true);
+ if (!alreadyScheduled) {
+ // TODO: Here we might end up scheduling one additional frame in some race conditions.
+ // This can be avoided by synchronising postFrameCallback as well
mChoreographer.postFrameCallback(this);
- } else {
- animateToFinalShift();
}
}
@Override
public void doFrame(long l) {
- mLastDelta = mCurrentDisplacement;
+ mFrameScheduled.set(false);
+ executeFrameUpdate();
+ }
- float translation = Utilities.boundToRange(mStartDelta - mLastDelta, 0,
- mHotseat.getHeight());
- int hotseatHeight = mHotseat.getHeight();
- float shift = hotseatHeight == 0 ? 0 : translation / hotseatHeight;
- setShift(shift);
- scheduleNextFrame();
+ private void executeFrameUpdate() {
+ if (mLauncherReady) {
+ final float displacement = -mCurrentDisplacement;
+ int hotseatHeight = mHotseat.getHeight();
+ float translation = Utilities.boundToRange(displacement, 0, hotseatHeight);
+ float shift = hotseatHeight == 0 ? 0 : translation / hotseatHeight;
+ mCurrentShift.updateValue(shift);
+ }
+
+ if (mTouchEnded) {
+ if (mTouchEndHandled) {
+ return;
+ }
+ mTouchEndHandled = true;
+ animateToFinalShift();
+ }
}
@UiThread
- private void setShift(float shift) {
+ private void updateFinalShift() {
+ if (!mLauncherReady) {
+ return;
+ }
+
if (mTargetRect.isEmpty()) {
DragLayer dl = mLauncher.getDragLayer();
-
- // Init target rect.
- View targetView = ((ViewGroup) mRecentsView.getChildAt(0)).getChildAt(0);
- dl.getViewRectRelativeToSelf(targetView, mTargetRect);
- mTargetRect.right = mTargetRect.left + mTargetRect.width();
- mTargetRect.bottom = mTargetRect.top + mTargetRect.height();
mSourceRect.set(0, 0, dl.getWidth(), dl.getHeight());
+ Rect targetPadding = RecentsView.getPadding(mLauncher);
+ Rect insets = dl.getInsets();
+ mTargetRect.set(
+ targetPadding.left + insets.left,
+ targetPadding.top + insets.top,
+ mSourceRect.right - targetPadding.right - insets.right,
+ mSourceRect.bottom - targetPadding.bottom - insets.bottom);
+ mTargetRect.top += mLauncher.getResources()
+ .getDimensionPixelSize(R.dimen.task_thumbnail_top_margin);
}
- if (!mSourceRect.isEmpty()) {
- mCurrentShift = shift;
- int hotseatHeight = mHotseat.getHeight();
- mHotseat.setTranslationY((1 - shift) * hotseatHeight);
+ float shift = mCurrentShift.value * mActivityMultiplier.value;
+ int hotseatHeight = mHotseat.getHeight();
- mRectEvaluator.evaluate(shift, mSourceRect, mTargetRect);
+ mHotseat.setTranslationY((1 - shift) * hotseatHeight);
- float scale = (float) mCurrentRect.width() / mSourceRect.width();
- mDragView.setTranslationX(mCurrentRect.left - mStableInsets.left * scale * shift);
- mDragView.setTranslationY(mCurrentRect.top - mStableInsets.top * scale * shift);
- mDragView.setScaleX(scale);
- mDragView.setScaleY(scale);
- mDragView.getViewBounds().setClipTop((int) (mStableInsets.top * shift));
- mDragView.getViewBounds().setClipBottom((int) (mStableInsets.bottom * shift));
- }
+ mRectEvaluator.evaluate(shift, mSourceRect, mTargetRect);
+
+ float scale = (float) mCurrentRect.width() / mSourceRect.width();
+ mDragView.setTranslationX(mCurrentRect.left - mStableInsets.left * scale * shift);
+ mDragView.setTranslationY(mCurrentRect.top - mStableInsets.top * scale * shift);
+ mDragView.setScaleX(scale);
+ mDragView.setScaleY(scale);
+ mDragView.getViewBounds().setClipTop((int) (mStableInsets.top * shift));
+ mDragView.getViewBounds().setClipBottom((int) (mStableInsets.bottom * shift));
}
- void setLastLoadPlan(Future<RecentsTaskLoadPlan> futureLoadPlan) {
- if (mFutureLoadPlan != null) {
- mFutureLoadPlan.cancel(true);
- }
- mFutureLoadPlan = futureLoadPlan;
+ @UiThread
+ public void setRecentsTaskLoadPlan(RecentsTaskLoadPlan loadPlan) {
+ mLoadPlan = loadPlan;
+ mStateCallback.setState(STATE_LOAD_PLAN_READY);
}
- private RecentsTaskLoadPlan consumeLastLoadPlan() {
- try {
- if (mFutureLoadPlan != null) {
- return mFutureLoadPlan.get();
- }
- } catch (InterruptedException | ExecutionException e) {
- e.printStackTrace();
- } finally {
- mFutureLoadPlan = null;
- }
- return null;
+ private void setTaskPlanToUi() {
+ mRecentsView.update(mLoadPlan);
+ mRecentsView.setVisibility(View.VISIBLE);
+
+ // Animate alpha
+ mRecentsView.setAlpha(0);
+ mRecentsView.animate().alpha(1).setDuration(RECENTS_VIEW_VISIBILITY_DURATION)
+ .withEndAction(() -> mStateCallback.setState(STATE_RECENTS_FULLY_VISIBLE));
}
@UiThread
private void animateToFinalShift() {
- float flingThreshold = Utilities.pxFromDp(FLING_THRESHOLD_VELOCITY,
- mLauncher.getResources().getDisplayMetrics());
+ Resources res = mContext.getResources();
+ float flingThreshold = res.getDimension(R.dimen.quickstep_fling_threshold_velocity);
boolean isFling = Math.abs(mEndVelocity) > flingThreshold;
- long duration = 200;
+ long duration = DEFAULT_SWIPE_DURATION;
final float endShift;
if (!isFling) {
- endShift = mCurrentShift >= MIN_PROGRESS_FOR_OVERVIEW ? 1 : 0;
+ endShift = mCurrentShift.value >= MIN_PROGRESS_FOR_OVERVIEW ? 1 : 0;
} else {
endShift = mEndVelocity < 0 ? 1 : 0;
- float minFlingVelocity = Utilities.pxFromDp(MIN_FLING_VELOCITY,
- mLauncher.getResources().getDisplayMetrics());
- if (Math.abs(mEndVelocity) > minFlingVelocity) {
- float distanceToTravel = (endShift - mCurrentShift) * mHotseat.getHeight();
+ float minFlingVelocity = res.getDimension(R.dimen.quickstep_fling_min_velocity);
+ if (Math.abs(mEndVelocity) > minFlingVelocity && mLauncherReady) {
+ float distanceToTravel = (endShift - mCurrentShift.value) * mHotseat.getHeight();
// we want the page's snap velocity to approximately match the velocity at
// which the user flings, so we scale the duration by a value near to the
- // derivative of the scroll interpolator at zero, ie. 5. We use 4 to make
- // it a little slower.
- duration = 4 * Math.round(1000 * Math.abs(distanceToTravel / mEndVelocity));
+ // derivative of the scroll interpolator at zero, ie. 5.
+ duration = 5 * Math.round(1000 * Math.abs(distanceToTravel / mEndVelocity));
}
}
- ObjectAnimator anim = ObjectAnimator.ofFloat(this, SHIFT, endShift)
- .setDuration(duration);
+ ObjectAnimator anim = mCurrentShift.animateToValue(endShift).setDuration(duration);
anim.setInterpolator(Interpolators.SCROLL);
anim.addListener(new AnimationSuccessListener() {
@Override
public void onAnimationSuccess(Animator animator) {
- if (Float.compare(mCurrentShift, 0) == 0) {
- resumeLastTask();
- } else {
- mDragView.close(false);
- }
+ mStateCallback.setState((Float.compare(mCurrentShift.value, 0) == 0)
+ ? STATE_SCALED_SNAPSHOT_APP : STATE_SCALED_SNAPSHOT_RECENTS);
}
});
anim.start();
@@ -265,12 +337,30 @@
@UiThread
private void resumeLastTask() {
+ TaskKey key = null;
+ if (mLoadPlan != null) {
+ Task task = mLoadPlan.getTaskStack().findTaskWithId(mRunningTaskId);
+ if (task != null) {
+ key = task.key;
+ }
+ }
+
+ if (key == null) {
+ // TODO: We need a better way for this
+ key = new TaskKey(mRunningTaskId, 0, null, UserHandle.myUserId(), 0);
+ }
+
+ ActivityOptions opts = ActivityOptions.makeCustomAnimation(mContext, 0, 0);
+ ActivityManagerWrapper.getInstance().startActivityFromRecentsAsync(key, opts, null, null);
+ }
+
+ private void cleanupLauncher() {
// TODO: These should be done as part of ActivityOptions#OnAnimationStarted
mHotseat.setTranslationY(0);
mLauncher.setOnResumeCallback(() -> mDragView.close(false));
+ }
- // TODO: For now, assume that the task stack will have loaded in the bg, will update
- // the lib api later for direct call
- mRecentsView.launchTaskWithId(mRunningTaskId);
+ private void onAnimationToLauncherComplete() {
+ mDragView.close(false);
}
}
diff --git a/quickstep/src/com/android/quickstep/RecentsView.java b/quickstep/src/com/android/quickstep/RecentsView.java
index 528b11d..675f456 100644
--- a/quickstep/src/com/android/quickstep/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/RecentsView.java
@@ -75,17 +75,7 @@
protected void onFinishInflate() {
super.onFinishInflate();
- // TODO: These are rough calculations which currently use the stable insets
- DeviceProfile profile = Launcher.getLauncher(getContext()).getDeviceProfile();
- Rect stableInsets = new Rect();
- WindowManagerWrapper.getInstance().getStableInsets(stableInsets);
- Rect padding = profile.getWorkspacePadding(null);
- float taskWidth = profile.getCurrentWidth() - stableInsets.left - stableInsets.right;
- float taskHeight = profile.getCurrentHeight() - stableInsets.top - stableInsets.bottom;
- float overviewHeight = profile.availableHeightPx - padding.top - padding.bottom
- - stableInsets.top;
- float overviewWidth = taskWidth * overviewHeight / taskHeight;
- padding.left = padding.right = (int) ((profile.availableWidthPx - overviewWidth) / 2);
+ Rect padding = getPadding(Launcher.getLauncher(getContext()));
setPadding(padding.left, padding.top, padding.right, padding.bottom);
}
@@ -166,4 +156,18 @@
mTaskStackListenerRegistered = registerStackListener;
}
}
+
+ public static Rect getPadding(Launcher launcher) {
+ DeviceProfile profile = launcher.getDeviceProfile();
+ Rect stableInsets = new Rect();
+ WindowManagerWrapper.getInstance().getStableInsets(stableInsets);
+ Rect padding = profile.getWorkspacePadding(null);
+ float taskWidth = profile.getCurrentWidth() - stableInsets.left - stableInsets.right;
+ float taskHeight = profile.getCurrentHeight() - stableInsets.top - stableInsets.bottom;
+ float overviewHeight = profile.availableHeightPx - padding.top - padding.bottom
+ - stableInsets.top;
+ float overviewWidth = taskWidth * overviewHeight / taskHeight;
+ padding.left = padding.right = (int) ((profile.availableWidthPx - overviewWidth) / 2);
+ return padding;
+ }
}
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index 1ff29cf..50f5528 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -33,12 +33,14 @@
import android.os.RemoteException;
import android.os.UserHandle;
import android.util.Log;
+import android.view.Choreographer;
import android.view.Display;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.ViewConfiguration;
import android.view.WindowManager;
+import com.android.launcher3.MainThreadExecutor;
import com.android.launcher3.R;
import com.android.systemui.shared.recents.IOverviewProxy;
import com.android.systemui.shared.recents.ISystemUiProxy;
@@ -47,8 +49,6 @@
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.BackgroundExecutor;
-import java.util.concurrent.Future;
-
/**
* Service connected by system-UI for handling touch interaction.
*/
@@ -75,6 +75,8 @@
private RunningTaskInfo mRunningTask;
private Intent mHomeIntent;
private ComponentName mLauncher;
+ private Choreographer mChoreographer;
+ private MainThreadExecutor mMainThreadExecutor;
private int mDisplayRotation;
private final Point mDisplaySize = new Point();
@@ -107,6 +109,9 @@
res.getInteger(R.integer.config_recentsMaxIconCacheSize), 0);
sRecentsTaskLoader.startLoader(this);
}
+
+ mChoreographer = Choreographer.getInstance();
+ mMainThreadExecutor = new MainThreadExecutor();
}
@Override
@@ -196,28 +201,34 @@
private void startTouchTracking() {
// Create the shared handler
- mInteractionHandler = new NavBarSwipeInteractionHandler(getCurrentTaskSnapshot(),
- mRunningTask);
+ final NavBarSwipeInteractionHandler handler =
+ new NavBarSwipeInteractionHandler(mRunningTask, mChoreographer, this);
// Preload and start the recents activity on a background thread
final Context context = this;
final RecentsTaskLoadPlan loadPlan = new RecentsTaskLoadPlan(context);
- Future<RecentsTaskLoadPlan> loadPlanFuture = BackgroundExecutor.get().submit(() -> {
- // Preload the plan
- RecentsTaskLoader loader = TouchInteractionService.getRecentsTaskLoader();
- loadPlan.preloadPlan(loader, mRunningTask.id, UserHandle.myUserId());
+ final int taskId = mRunningTask.id;
- // Start the activity with our custom handler
- Intent homeIntent = mInteractionHandler.addToIntent(new Intent(mHomeIntent));
+ BackgroundExecutor.get().submit(() -> {
+ // Get the snap shot before
+ handler.setTaskSnapshot(getCurrentTaskSnapshot());
+
+ // Start the launcher activity with our custom handler
+ Intent homeIntent = handler.addToIntent(new Intent(mHomeIntent));
startActivity(homeIntent, ActivityOptions.makeCustomAnimation(this, 0, 0).toBundle());
/*
ActivityManagerWrapper.getInstance().startRecentsActivity(null, options,
ActivityOptions.makeCustomAnimation(this, 0, 0), UserHandle.myUserId(),
null, null);
*/
- }, loadPlan);
- mInteractionHandler.setLastLoadPlan(loadPlanFuture);
+ // Preload the plan
+ RecentsTaskLoader loader = TouchInteractionService.getRecentsTaskLoader();
+ loadPlan.preloadPlan(loader, taskId, UserHandle.myUserId());
+ // Set the load plan on UI thread
+ mMainThreadExecutor.execute(() -> handler.setRecentsTaskLoadPlan(loadPlan));
+ });
+ mInteractionHandler = handler;
}
private void endInteraction() {