diff options
-rw-r--r-- | quickstep/libs/sysui_shared.jar | bin | 103039 -> 110447 bytes | |||
-rw-r--r-- | quickstep/src/com/android/quickstep/BaseSwipeInteractionHandler.java | 42 | ||||
-rw-r--r-- | quickstep/src/com/android/quickstep/LauncherLayoutListener.java | 77 | ||||
-rw-r--r-- | quickstep/src/com/android/quickstep/MultiStateCallback.java | 4 | ||||
-rw-r--r-- | quickstep/src/com/android/quickstep/NavBarSwipeInteractionHandler.java | 26 | ||||
-rw-r--r-- | quickstep/src/com/android/quickstep/RecentsView.java | 10 | ||||
-rw-r--r-- | quickstep/src/com/android/quickstep/RemoteRunnable.java | 33 | ||||
-rw-r--r-- | quickstep/src/com/android/quickstep/TouchInteractionService.java | 154 | ||||
-rw-r--r-- | quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java | 541 | ||||
-rw-r--r-- | src/com/android/launcher3/Launcher.java | 4 | ||||
-rw-r--r-- | src/com/android/launcher3/dragndrop/BaseItemDragListener.java | 6 | ||||
-rw-r--r-- | src/com/android/launcher3/states/InternalStateHandler.java | 56 |
12 files changed, 886 insertions, 67 deletions
diff --git a/quickstep/libs/sysui_shared.jar b/quickstep/libs/sysui_shared.jar Binary files differindex 85b40d031..420ecef1a 100644 --- a/quickstep/libs/sysui_shared.jar +++ b/quickstep/libs/sysui_shared.jar diff --git a/quickstep/src/com/android/quickstep/BaseSwipeInteractionHandler.java b/quickstep/src/com/android/quickstep/BaseSwipeInteractionHandler.java new file mode 100644 index 000000000..0551938c1 --- /dev/null +++ b/quickstep/src/com/android/quickstep/BaseSwipeInteractionHandler.java @@ -0,0 +1,42 @@ +/* + * 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 com.android.launcher3.states.InternalStateHandler; +import com.android.quickstep.TouchInteractionService.InteractionType; + +public abstract class BaseSwipeInteractionHandler extends InternalStateHandler { + + protected Runnable mGestureEndCallback; + + public void setGestureEndCallback(Runnable gestureEndCallback) { + mGestureEndCallback = gestureEndCallback; + } + + public void reset() {} + + public abstract void onGestureStarted(); + + public abstract void onGestureEnded(float endVelocity); + + public abstract void updateInteractionType(@InteractionType int interactionType); + + public abstract void onQuickScrubEnd(); + + public abstract void onQuickScrubProgress(float progress); + + public abstract void updateDisplacement(float displacement); +} diff --git a/quickstep/src/com/android/quickstep/LauncherLayoutListener.java b/quickstep/src/com/android/quickstep/LauncherLayoutListener.java new file mode 100644 index 000000000..285434297 --- /dev/null +++ b/quickstep/src/com/android/quickstep/LauncherLayoutListener.java @@ -0,0 +1,77 @@ +/* + * 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.graphics.Rect; +import android.view.MotionEvent; + +import com.android.launcher3.AbstractFloatingView; +import com.android.launcher3.Insettable; +import com.android.launcher3.Launcher; + +/** + * Floating view which shows the task snapshot allowing it to be dragged and placed. + */ +public class LauncherLayoutListener extends AbstractFloatingView implements Insettable { + + private final Launcher mLauncher; + private final WindowTransformSwipeHandler mHandler; + + public LauncherLayoutListener(Launcher launcher, WindowTransformSwipeHandler handler) { + super(launcher, null); + mLauncher = launcher; + mHandler = handler; + setVisibility(INVISIBLE); + } + + @Override + public void setInsets(Rect insets) { + requestLayout(); + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + super.onLayout(changed, l, t, r, b); + mHandler.onLauncherLayoutChanged(); + } + + @Override + public boolean onControllerInterceptTouchEvent(MotionEvent ev) { + return false; + } + + @Override + protected void handleClose(boolean animate) { + // We dont suupport animate. + mLauncher.getDragLayer().removeView(this); + mHandler.layoutListenerClosed(); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + setMeasuredDimension(1, 1); + } + + @Override + public void logActionCommand(int command) { + // We should probably log the weather + } + + @Override + protected boolean isOfType(int type) { + return (type & TYPE_QUICKSTEP_PREVIEW) != 0; + } +} diff --git a/quickstep/src/com/android/quickstep/MultiStateCallback.java b/quickstep/src/com/android/quickstep/MultiStateCallback.java index cca27299b..7a741764f 100644 --- a/quickstep/src/com/android/quickstep/MultiStateCallback.java +++ b/quickstep/src/com/android/quickstep/MultiStateCallback.java @@ -55,4 +55,8 @@ public class MultiStateCallback { public void addCallback(int stateMask, Runnable callback) { mCallbacks.put(stateMask, callback); } + + public int getState() { + return mState; + } } diff --git a/quickstep/src/com/android/quickstep/NavBarSwipeInteractionHandler.java b/quickstep/src/com/android/quickstep/NavBarSwipeInteractionHandler.java index b295df0f9..944804ba9 100644 --- a/quickstep/src/com/android/quickstep/NavBarSwipeInteractionHandler.java +++ b/quickstep/src/com/android/quickstep/NavBarSwipeInteractionHandler.java @@ -41,6 +41,7 @@ import com.android.launcher3.AbstractFloatingView; import com.android.launcher3.DeviceProfile; import com.android.launcher3.Hotseat; import com.android.launcher3.Launcher; +import com.android.launcher3.Launcher.OnResumeCallback; import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherState; import com.android.launcher3.R; @@ -48,7 +49,6 @@ import com.android.launcher3.Utilities; import com.android.launcher3.allapps.AllAppsTransitionController; import com.android.launcher3.anim.AnimationSuccessListener; import com.android.launcher3.anim.Interpolators; -import com.android.launcher3.states.InternalStateHandler; import com.android.launcher3.util.Preconditions; import com.android.launcher3.util.TraceHelper; import com.android.quickstep.TouchInteractionService.InteractionType; @@ -59,7 +59,8 @@ import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.WindowManagerWrapper; @TargetApi(Build.VERSION_CODES.O) -public class NavBarSwipeInteractionHandler extends InternalStateHandler { +public class NavBarSwipeInteractionHandler extends BaseSwipeInteractionHandler implements + OnResumeCallback { private static final int STATE_LAUNCHER_READY = 1 << 0; private static final int STATE_ACTIVITY_MULTIPLIER_COMPLETE = 1 << 4; @@ -187,7 +188,8 @@ public class NavBarSwipeInteractionHandler extends InternalStateHandler { } @Override - protected void init(Launcher launcher, boolean alreadyOnHome) { + protected boolean init(Launcher launcher, boolean alreadyOnHome) { + launcher.setOnResumeCallback(this); mLauncher = launcher; mRecentsView = launcher.getOverviewPanel(); mRecentsView.showTask(mRunningTask); @@ -212,9 +214,9 @@ public class NavBarSwipeInteractionHandler extends InternalStateHandler { mLauncher.getAppsView().setVisibility(View.GONE); } TraceHelper.partitionSection("TouchInt", "Launcher on new intent"); + return false; } - public void updateInteractionType(@InteractionType int interactionType) { Preconditions.assertUIThread(); if (mInteractionType != INTERACTION_NORMAL) { @@ -288,8 +290,11 @@ public class NavBarSwipeInteractionHandler extends InternalStateHandler { ? mHotseat.getWidth() : mHotseat.getHeight(); } + @Override + public void onGestureStarted() { } + @UiThread - public void endTouch(float endVelocity) { + public void onGestureEnded(float endVelocity) { if (mTouchEndHandled) { return; } @@ -349,13 +354,24 @@ public class NavBarSwipeInteractionHandler extends InternalStateHandler { ActivityManagerWrapper.getInstance().startActivityFromRecentsAsync(key, opts, null, null); } + public void reset() { + mCurrentShift.cancelAnimation(); + if (mGestureEndCallback != null) { + mGestureEndCallback.run(); + } + } + private void cleanupLauncher() { + reset(); + // TODO: These should be done as part of ActivityOptions#OnAnimationStarted mLauncher.getStateManager().reapplyState(); mLauncher.setOnResumeCallback(() -> mDragView.close(false)); } private void onAnimationToLauncherComplete() { + reset(); + mDragView.close(false); View currentRecentsPage = mRecentsView.getPageAt(mRecentsView.getCurrentPage()); if (currentRecentsPage instanceof TaskView) { diff --git a/quickstep/src/com/android/quickstep/RecentsView.java b/quickstep/src/com/android/quickstep/RecentsView.java index b35d31bfe..6937c1fc2 100644 --- a/quickstep/src/com/android/quickstep/RecentsView.java +++ b/quickstep/src/com/android/quickstep/RecentsView.java @@ -128,6 +128,16 @@ public class RecentsView extends PagedView implements Insettable { mScrollState.isRtl = mIsRtl; } + public void updateThumbnail(int taskId, ThumbnailData thumbnailData) { + for (int i = mFirstTaskIndex; i < getChildCount(); i++) { + final TaskView taskView = (TaskView) getChildAt(i); + if (taskView.getTask().key.id == taskId) { + taskView.onTaskDataLoaded(taskView.getTask(), thumbnailData); + return; + } + } + } + private void setupLayoutTransition() { // We want to show layout transitions when pages are deleted, to close the gap. mLayoutTransition = new LayoutTransition(); diff --git a/quickstep/src/com/android/quickstep/RemoteRunnable.java b/quickstep/src/com/android/quickstep/RemoteRunnable.java new file mode 100644 index 000000000..ec7cad4ba --- /dev/null +++ b/quickstep/src/com/android/quickstep/RemoteRunnable.java @@ -0,0 +1,33 @@ +/* + * 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.os.RemoteException; +import android.util.Log; + +@FunctionalInterface +public interface RemoteRunnable { + + void run() throws RemoteException; + + static void executeSafely(RemoteRunnable r) { + try { + r.run(); + } catch (final RemoteException e) { + Log.e("RemoteRunnable", "Error calling remote method", e); + } + } +}
\ No newline at end of file diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java index 509ffa9dc..c0b12f78b 100644 --- a/quickstep/src/com/android/quickstep/TouchInteractionService.java +++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java @@ -23,6 +23,8 @@ 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.quickstep.RemoteRunnable.executeSafely; + import android.annotation.TargetApi; import android.app.ActivityManager.RunningTaskInfo; import android.app.ActivityOptions; @@ -37,8 +39,8 @@ import android.graphics.Point; import android.graphics.PointF; import android.graphics.Rect; import android.os.Build; +import android.os.Bundle; import android.os.IBinder; -import android.os.RemoteException; import android.support.annotation.IntDef; import android.util.Log; import android.view.Choreographer; @@ -53,11 +55,16 @@ import android.view.WindowManager; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherAppState; import com.android.launcher3.MainThreadExecutor; +import com.android.launcher3.Utilities; import com.android.launcher3.util.TraceHelper; import com.android.systemui.shared.recents.IOverviewProxy; import com.android.systemui.shared.recents.ISystemUiProxy; 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.RemoteAnimationTargetCompat; import com.android.systemui.shared.system.WindowManagerWrapper; import java.lang.annotation.Retention; @@ -93,19 +100,18 @@ public class TouchInteractionService extends Service { } @Override - public void onBind(ISystemUiProxy iSystemUiProxy) throws RemoteException { + public void onBind(ISystemUiProxy iSystemUiProxy) { mISystemUiProxy = iSystemUiProxy; } @Override public void onQuickSwitch() { - startTouchTracking(INTERACTION_QUICK_SWITCH); - mInteractionHandler = null; + updateTouchTracking(INTERACTION_QUICK_SWITCH); } @Override public void onQuickScrubStart() { - startTouchTracking(INTERACTION_QUICK_SCRUB); + updateTouchTracking(INTERACTION_QUICK_SCRUB); sQuickScrubEnabled = true; } @@ -113,7 +119,6 @@ public class TouchInteractionService extends Service { public void onQuickScrubEnd() { if (mInteractionHandler != null) { mInteractionHandler.onQuickScrubEnd(); - mInteractionHandler = null; } sQuickScrubEnabled = false; } @@ -153,9 +158,10 @@ public class TouchInteractionService extends Service { private final PointF mLastPos = new PointF(); private int mActivePointerId = INVALID_POINTER_ID; private VelocityTracker mVelocityTracker; + private boolean mTouchThresholdCrossed; private int mTouchSlop; private float mStartDisplacement; - private NavBarSwipeInteractionHandler mInteractionHandler; + private BaseSwipeInteractionHandler mInteractionHandler; private int mDisplayRotation; private Rect mStableInsets = new Rect(); @@ -229,9 +235,10 @@ public class TouchInteractionService extends Service { } mVelocityTracker.addMovement(ev); if (mInteractionHandler != null) { - mInteractionHandler.endTouch(0); + mInteractionHandler.reset(); mInteractionHandler = null; } + mTouchThresholdCrossed = false; Display display = getSystemService(WindowManager.class).getDefaultDisplay(); mDisplayRotation = display.getRotation(); @@ -266,10 +273,18 @@ public class TouchInteractionService extends Service { } else if (isNavBarOnLeft()) { displacement = mDownPos.x - ev.getX(pointerIndex); } - if (mInteractionHandler == null) { - if (Math.abs(displacement) >= mTouchSlop) { + if (!mTouchThresholdCrossed) { + mTouchThresholdCrossed = Math.abs(displacement) >= mTouchSlop; + if (mTouchThresholdCrossed) { mStartDisplacement = Math.signum(displacement) * mTouchSlop; - startTouchTracking(INTERACTION_NORMAL); + + startTouchTracking(); + mInteractionHandler.onGestureStarted(); + + // Notify the system that we have started tracking the event + if (mISystemUiProxy != null) { + executeSafely(mISystemUiProxy::onRecentsAnimationStarted); + } } } else { // Move @@ -282,7 +297,7 @@ public class TouchInteractionService extends Service { case ACTION_UP: { TraceHelper.endSection("TouchInt"); - endInteraction(); + finishTouchTracking(); mCurrentConsumer = mNoOpTouchConsumer; break; } @@ -297,55 +312,92 @@ public class TouchInteractionService extends Service { return mDisplayRotation == Surface.ROTATION_270 && mStableInsets.left > 0; } - - private void startTouchTracking(@InteractionType int interactionType) { - if (isInteractionQuick(interactionType)) { - // TODO: Send action cancel if its the Launcher consumer - } - if (mInteractionHandler != null) { - final NavBarSwipeInteractionHandler handler = mInteractionHandler; - mMainThreadExecutor.execute(() -> handler.updateInteractionType(interactionType)); - return; + /** + * Called when the gesture has started. + */ + private void startTouchTracking() { + if (Utilities.getPrefs(this).getBoolean("pref_use_screenshot_animation", true)) { + // Create the shared handler + final NavBarSwipeInteractionHandler handler = + new NavBarSwipeInteractionHandler(mRunningTask, this, INTERACTION_NORMAL); + + TraceHelper.partitionSection("TouchInt", "Thershold crossed "); + + // Start the recents activity on a background thread + 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()); + TraceHelper.partitionSection("TouchInt", "Home started"); + }); + + // Preload the plan + mRecentsModel.loadTasks(mRunningTask.id, null); + mInteractionHandler = handler; + mInteractionHandler.setGestureEndCallback(() -> mInteractionHandler = null); + } else { + + // Create the shared handler + final WindowTransformSwipeHandler handler = + new WindowTransformSwipeHandler(mRunningTask, this); + + BackgroundExecutor.get().submit(() -> { + ActivityManagerWrapper.getInstance().startRecentsActivity(mHomeIntent, + new AssistDataReceiver() { + @Override + public void onHandleAssistData(Bundle bundle) { + // Pass to AIAI + } + }, + new RecentsAnimationListener() { + public void onAnimationStart( + RecentsAnimationControllerCompat controller, + RemoteAnimationTargetCompat[] apps) { + if (mInteractionHandler == handler) { + handler.setRecentsAnimation(controller, apps); + + } else { + controller.finish(false /* toHome */); + } + } + + public void onAnimationCanceled() { + if (mInteractionHandler == handler) { + handler.setRecentsAnimation(null, null); + } + } + }, null, null); + }); + + // Preload the plan + mRecentsModel.loadTasks(mRunningTask.id, null); + mInteractionHandler = handler; + mInteractionHandler.initWhenReady(); + mInteractionHandler.setGestureEndCallback(() -> mInteractionHandler = null); } + } - // Create the shared handler - final NavBarSwipeInteractionHandler handler = - new NavBarSwipeInteractionHandler(mRunningTask, this, interactionType); - - TraceHelper.partitionSection("TouchInt", "Thershold crossed "); - - // Start the recents activity on a background thread - 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()); - TraceHelper.partitionSection("TouchInt", "Home started"); - - /* - ActivityManagerWrapper.getInstance().startRecentsActivity(null, options, - ActivityOptions.makeCustomAnimation(this, 0, 0), UserHandle.myUserId(), - null, null); - */ - }); - - // Preload the plan - mRecentsModel.loadTasks(mRunningTask.id, null); - mInteractionHandler = handler; + private void updateTouchTracking(@InteractionType int interactionType) { + final BaseSwipeInteractionHandler handler = mInteractionHandler; + mMainThreadExecutor.execute(() -> handler.updateInteractionType(interactionType)); } - private void endInteraction() { - if (mInteractionHandler != null) { + /** + * Called when the gesture has ended. Does not correlate to the completion of the interaction as + * the animation can still be running. + */ + private void finishTouchTracking() { + if (mTouchThresholdCrossed) { mVelocityTracker.computeCurrentVelocity(1000, ViewConfiguration.get(this).getScaledMaximumFlingVelocity()); float velocity = isNavBarOnRight() ? mVelocityTracker.getXVelocity(mActivePointerId) : isNavBarOnLeft() ? -mVelocityTracker.getXVelocity(mActivePointerId) : mVelocityTracker.getYVelocity(mActivePointerId); - mInteractionHandler.endTouch(velocity); - mInteractionHandler = null; + mInteractionHandler.onGestureEnded(velocity); } mVelocityTracker.recycle(); mVelocityTracker = null; diff --git a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java new file mode 100644 index 000000000..e9b22a25e --- /dev/null +++ b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java @@ -0,0 +1,541 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.quickstep; + +import static android.view.MotionEvent.ACTION_UP; + +import static com.android.launcher3.LauncherState.OVERVIEW; +import static com.android.launcher3.anim.Interpolators.LINEAR; +import static com.android.quickstep.TouchInteractionService.INTERACTION_NORMAL; +import static com.android.quickstep.TouchInteractionService.INTERACTION_QUICK_SCRUB; +import static com.android.quickstep.TouchInteractionService.INTERACTION_QUICK_SWITCH; +import static com.android.quickstep.TouchInteractionService.isInteractionQuick; +import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING; + +import android.animation.Animator; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.animation.RectEvaluator; +import android.annotation.TargetApi; +import android.app.ActivityManager.RunningTaskInfo; +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Color; +import android.graphics.Matrix; +import android.graphics.Rect; +import android.os.Build; +import android.os.UserHandle; +import android.support.annotation.UiThread; +import android.view.View; + +import com.android.launcher3.AbstractFloatingView; +import com.android.launcher3.DeviceProfile; +import com.android.launcher3.Hotseat; +import com.android.launcher3.Launcher; +import com.android.launcher3.LauncherAppState; +import com.android.launcher3.R; +import com.android.launcher3.Utilities; +import com.android.launcher3.allapps.AllAppsTransitionController; +import com.android.launcher3.anim.AnimationSuccessListener; +import com.android.launcher3.anim.AnimatorPlaybackController; +import com.android.launcher3.anim.Interpolators; +import com.android.launcher3.states.InternalStateHandler; +import com.android.launcher3.util.Preconditions; +import com.android.quickstep.TouchInteractionService.InteractionType; +import com.android.systemui.shared.recents.model.Task; +import com.android.systemui.shared.recents.model.Task.TaskKey; +import com.android.systemui.shared.recents.model.ThumbnailData; +import com.android.systemui.shared.system.BackgroundExecutor; +import com.android.systemui.shared.system.InputConsumerController; +import com.android.systemui.shared.system.RecentsAnimationControllerCompat; +import com.android.systemui.shared.system.RemoteAnimationTargetCompat; +import com.android.systemui.shared.system.TransactionCompat; +import com.android.systemui.shared.system.WindowManagerWrapper; + +@TargetApi(Build.VERSION_CODES.O) +public class WindowTransformSwipeHandler extends BaseSwipeInteractionHandler { + + // Launcher UI related states + private static final int STATE_LAUNCHER_READY = 1 << 0; + private static final int STATE_ACTIVITY_MULTIPLIER_COMPLETE = 1 << 1; + + // Internal initialization states + private static final int STATE_APP_CONTROLLER_RECEIVED = 1 << 2; + + // Interaction finish states + private static final int STATE_SCALED_SNAPSHOT_RECENTS = 1 << 3; + private static final int STATE_SCALED_SNAPSHOT_APP = 1 << 4; + + private static final int LAUNCHER_UI_STATES = + STATE_LAUNCHER_READY | STATE_ACTIVITY_MULTIPLIER_COMPLETE; + + private static final long MAX_SWIPE_DURATION = 200; + private static final long MIN_SWIPE_DURATION = 80; + private static final int QUICK_SWITCH_SNAP_DURATION = 120; + + private static final float MIN_PROGRESS_FOR_OVERVIEW = 0.5f; + + private final Rect mStableInsets = new Rect(); + private final Rect mSourceRect = new Rect(); + private final Rect mTargetRect = new Rect(); + private final Rect mCurrentRect = new Rect(); + private final Rect mClipRect = new Rect(); + private final RectEvaluator mRectEvaluator = new RectEvaluator(mCurrentRect); + private DeviceProfile mDp; + private int mTransitionDragLength; + + // 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 final AnimatedFloat mCurrentShift = new AnimatedFloat(this::updateFinalShift); + + private final Task mRunningTask; + private final Context mContext; + + private MultiStateCallback mStateCallback; + private boolean mControllerStateAnimation; + private AnimatorPlaybackController mLauncherTransitionController; + + private Launcher mLauncher; + private LauncherLayoutListener mLauncherLayoutListener; + private RecentsView mRecentsView; + private QuickScrubController mQuickScrubController; + + private boolean mWasLauncherAlreadyVisible; + + private float mCurrentDisplacement; + + private @InteractionType int mInteractionType = INTERACTION_NORMAL; + private boolean mStartedQuickScrubFromHome; + + private RecentsAnimationControllerCompat mRecentsAnimationController; + private RemoteAnimationTargetCompat[] mRecentsAnimationApps; + private boolean mRecentsAnimationInputConsumerEnabled; + private Matrix mTmpMatrix = new Matrix(); + + private final InputConsumerController mInputConsumerController; + private final InputConsumerController.TouchListener mInputConsumerTouchListener = + (ev) -> { + if (ev.getActionMasked() == ACTION_UP) { + onGestureInterruptEnd(); + } + return true; + }; + + WindowTransformSwipeHandler(RunningTaskInfo runningTaskInfo, Context context) { + // TODO: We need a better way for this + TaskKey taskKey = new TaskKey(runningTaskInfo.id, 0, null, UserHandle.myUserId(), 0); + mRunningTask = new Task(taskKey, null, null, "", "", Color.BLACK, Color.BLACK, + true, false, false, false, null, 0, null, false); + mContext = context; + mInputConsumerController = InputConsumerController.getRecentsAnimationInputConsumer(); + + + WindowManagerWrapper.getInstance().getStableInsets(mStableInsets); + + DeviceProfile dp = LauncherAppState.getIDP(mContext).getDeviceProfile(mContext); + // TODO: If in multi window mode, dp = dp.getMultiWindowProfile() + dp = dp.copy(mContext); + // TODO: Use different insets for multi-window mode + dp.updateInsets(mStableInsets); + + initTransitionEndpoints(dp); + initStateCallbacks(); + } + + private void initStateCallbacks() { + mStateCallback = new MultiStateCallback(); + mStateCallback.addCallback(STATE_SCALED_SNAPSHOT_APP | STATE_APP_CONTROLLER_RECEIVED, + this::resumeLastTask); + mStateCallback.addCallback(STATE_SCALED_SNAPSHOT_RECENTS + | STATE_ACTIVITY_MULTIPLIER_COMPLETE + | STATE_APP_CONTROLLER_RECEIVED, + this::switchToScreenshot); + mStateCallback.addCallback(STATE_SCALED_SNAPSHOT_RECENTS + | STATE_ACTIVITY_MULTIPLIER_COMPLETE, + this::animateFirstTaskIcon); + + mStateCallback.addCallback(STATE_LAUNCHER_READY | STATE_SCALED_SNAPSHOT_APP, + this::reset); + mStateCallback.addCallback(STATE_LAUNCHER_READY | STATE_SCALED_SNAPSHOT_RECENTS, + this::reset); + } + + private void initTransitionEndpoints(DeviceProfile dp) { + mDp = dp; + RecentsView.getPageRect(dp, mContext, mTargetRect); + mSourceRect.set(0, 0, dp.widthPx - mStableInsets.left - mStableInsets.right, + dp.heightPx - mStableInsets.top - mStableInsets.bottom); + + mTransitionDragLength = dp.hotseatBarSizePx + (dp.isVerticalBarLayout() + ? (dp.hotseatBarSidePaddingPx + (dp.isSeascape() ? mStableInsets.left : mStableInsets.right)) + : mStableInsets.bottom); + } + + private long getFadeInDuration() { + if (mCurrentShift.getCurrentAnimation() != null) { + ObjectAnimator anim = mCurrentShift.getCurrentAnimation(); + long theirDuration = anim.getDuration() - anim.getCurrentPlayTime(); + + // TODO: Find a better heuristic + return Math.min(MAX_SWIPE_DURATION, Math.max(theirDuration, MIN_SWIPE_DURATION)); + } else { + return MAX_SWIPE_DURATION; + } + } + + @Override + protected boolean init(final Launcher launcher, boolean alreadyOnHome) { + if (launcher == mLauncher) { + return true; + } + if (mLauncher != null) { + // The launcher may have been recreated as a result of device rotation. + int oldState = mStateCallback.getState() & ~LAUNCHER_UI_STATES; + initStateCallbacks(); + mStateCallback.setState(oldState); + } + mLauncher = launcher; + + AbstractFloatingView.closeAllOpenViews(launcher, alreadyOnHome); + mControllerStateAnimation = alreadyOnHome; + if (mControllerStateAnimation) { + DeviceProfile dp = mLauncher.getDeviceProfile(); + long accuracy = 2 * Math.max(dp.widthPx, dp.heightPx); + mLauncherTransitionController = launcher.getStateManager() + .createAnimationToNewWorkspace(OVERVIEW, accuracy); + mLauncherTransitionController.setPlayFraction(mCurrentShift.value); + + mStateCallback.setState(STATE_ACTIVITY_MULTIPLIER_COMPLETE); + } else { + launcher.getStateManager().goToState(OVERVIEW, false); + + // TODO: Implement a better animation for fading in + View rootView = launcher.getRootView(); + rootView.setAlpha(0); + rootView.animate().alpha(1) + .setDuration(getFadeInDuration()) + .withEndAction(() -> mStateCallback.setState( + launcher == mLauncher ? STATE_ACTIVITY_MULTIPLIER_COMPLETE : 0)); + } + + mRecentsView = mLauncher.getOverviewPanel(); + mRecentsView.showTask(mRunningTask); + mWasLauncherAlreadyVisible = alreadyOnHome; + mLauncherLayoutListener = new LauncherLayoutListener(mLauncher, this); + mLauncher.getDragLayer().addView(mLauncherLayoutListener); + + // Optimization + if (!mLauncher.getDeviceProfile().isVerticalBarLayout()) { + // All-apps search box is visible in vertical bar layout. + mLauncher.getAppsView().setVisibility(View.GONE); + } + + onLauncherLayoutChanged(); + mStateCallback.setState(STATE_LAUNCHER_READY); + return true; + } + + public void updateInteractionType(@InteractionType int interactionType) { + Preconditions.assertUIThread(); + if (mInteractionType != INTERACTION_NORMAL) { + throw new IllegalArgumentException( + "Can't change interaction type from " + mInteractionType); + } + if (!isInteractionQuick(interactionType)) { + throw new IllegalArgumentException( + "Can't change interaction type to " + interactionType); + } + mInteractionType = interactionType; + + if (mLauncher != null) { + updateUiForQuickScrub(); + } + } + + private void updateUiForQuickScrub() { + mStartedQuickScrubFromHome = mWasLauncherAlreadyVisible; + mQuickScrubController = mRecentsView.getQuickScrubController(); + mQuickScrubController.onQuickScrubStart(mStartedQuickScrubFromHome); + animateToProgress(1f, MAX_SWIPE_DURATION); + if (mStartedQuickScrubFromHome) { + mLauncherLayoutListener.setVisibility(View.INVISIBLE); + } + } + + @UiThread + public void updateDisplacement(float displacement) { + mCurrentDisplacement = displacement; + + float translation = Utilities.boundToRange(-mCurrentDisplacement, 0, mTransitionDragLength); + float shift = mTransitionDragLength == 0 ? 0 : translation / mTransitionDragLength; + mCurrentShift.updateValue(shift); + } + + /** + * Called by {@link #mLauncherLayoutListener} when launcher layout changes + */ + public void onLauncherLayoutChanged() { + Hotseat hotseat = mLauncher.getHotseat(); + + WindowManagerWrapper.getInstance().getStableInsets(mStableInsets); + initTransitionEndpoints(mLauncher.getDeviceProfile()); + + if (!mControllerStateAnimation) { + AnimatorSet anim = new AnimatorSet(); + if (mLauncher.getDeviceProfile().isVerticalBarLayout()) { + mLauncher.getAllAppsController().setProgress(1); + ObjectAnimator shiftAnim = ObjectAnimator.ofFloat(mLauncher.getAllAppsController(), + AllAppsTransitionController.ALL_APPS_PROGRESS, + 1, OVERVIEW.getVerticalProgress(mLauncher)); + shiftAnim.setInterpolator(LINEAR); + anim.play(shiftAnim); + + hotseat.setAlpha(0); + ObjectAnimator fadeAnim = ObjectAnimator.ofFloat(hotseat, View.ALPHA, 1); + fadeAnim.setInterpolator(LINEAR); + anim.play(fadeAnim); + } else { + hotseat.setTranslationY(mTransitionDragLength); + ObjectAnimator hotseatAnim = ObjectAnimator.ofFloat(hotseat, View.TRANSLATION_Y, 0); + hotseatAnim.setInterpolator(LINEAR); + anim.play(hotseatAnim); + + View scrim = mLauncher.findViewById(R.id.all_apps_scrim); + scrim.setTranslationY(mTransitionDragLength); + ObjectAnimator scrimAnim = ObjectAnimator.ofFloat(scrim, View.TRANSLATION_Y, 0); + scrimAnim.setInterpolator(LINEAR); + anim.play(scrimAnim); + } + + // TODO: Link this animation to state animation, so that it is cancelled + // automatically on state change + anim.setDuration(mTransitionDragLength * 2); + mLauncherTransitionController = + AnimatorPlaybackController.wrap(anim, mTransitionDragLength * 2); + mLauncherTransitionController.setPlayFraction(mCurrentShift.value); + } + + // In case the transition length changed (which should never happen, redo everything + updateDisplacement(mCurrentDisplacement); + } + + @UiThread + private void updateFinalShift() { + if (mStartedQuickScrubFromHome) { + return; + } + + float shift = mCurrentShift.value; + mRectEvaluator.evaluate(shift, mSourceRect, mTargetRect); + float scale = (float) mCurrentRect.width() / mSourceRect.width(); + if (mRecentsAnimationApps != null) { + mClipRect.left = mSourceRect.left; + mClipRect.top = (int) (mStableInsets.top * shift); + mClipRect.bottom = (int) (mDp.heightPx - (mStableInsets.bottom * shift)); + mClipRect.right = mSourceRect.right; + + mTmpMatrix.setScale(scale, scale, 0, 0); + mTmpMatrix.postTranslate(mCurrentRect.left - mStableInsets.left * scale * shift, + mCurrentRect.top - mStableInsets.top * scale * shift); + TransactionCompat transaction = new TransactionCompat(); + for (RemoteAnimationTargetCompat app : mRecentsAnimationApps) { + if (app.mode == MODE_CLOSING) { + transaction.setMatrix(app.leash, mTmpMatrix) + .setWindowCrop(app.leash, mClipRect) + .show(app.leash); + } + } + transaction.apply(); + } + + if (mLauncherTransitionController != null) { + mLauncherTransitionController.setPlayFraction(shift); + } + } + + public void setRecentsAnimation(RecentsAnimationControllerCompat controller, + RemoteAnimationTargetCompat[] apps) { + mRecentsAnimationController = controller; + if (mRecentsAnimationInputConsumerEnabled) { + BackgroundExecutor.get().submit(() -> + mRecentsAnimationController.setInputConsumerEnabled(true)); + } + mRecentsAnimationApps = apps; + mStateCallback.setState(STATE_APP_CONTROLLER_RECEIVED); + } + + public void onGestureStarted() { + mInputConsumerController.unregisterInputConsumer(); + mInputConsumerController.registerInputConsumer(); + mInputConsumerController.setTouchListener(mInputConsumerTouchListener); + + if (mRecentsAnimationController != null) { + BackgroundExecutor.get().submit(() -> + mRecentsAnimationController.setInputConsumerEnabled(true)); + } else { + mRecentsAnimationInputConsumerEnabled = true; + } + } + + @UiThread + public void onGestureEnded(float endVelocity) { + Resources res = mContext.getResources(); + float flingThreshold = res.getDimension(R.dimen.quickstep_fling_threshold_velocity); + boolean isFling = Math.abs(endVelocity) > flingThreshold; + + long duration = MAX_SWIPE_DURATION; + final float endShift; + if (!isFling) { + endShift = mCurrentShift.value >= MIN_PROGRESS_FOR_OVERVIEW ? 1 : 0; + } else { + endShift = endVelocity < 0 ? 1 : 0; + float minFlingVelocity = res.getDimension(R.dimen.quickstep_fling_min_velocity); + if (Math.abs(endVelocity) > minFlingVelocity && mTransitionDragLength > 0) { + float distanceToTravel = (endShift - mCurrentShift.value) * mTransitionDragLength; + + // we want the page's snap velocity to approximately match the velocity at + // which the user flings, so we scale the duration by a value near to the + // derivative of the scroll interpolator at zero, ie. 5. + duration = 5 * Math.round(1000 * Math.abs(distanceToTravel / endVelocity)); + } + } + + if (endShift == mCurrentShift.value) { + mStateCallback.setState((Float.compare(mCurrentShift.value, 0) == 0) + ? STATE_SCALED_SNAPSHOT_APP : STATE_SCALED_SNAPSHOT_RECENTS); + } else { + animateToProgress(endShift, duration); + } + } + + @UiThread + public void onGestureInterruptEnd() { + final float endShift = 0; + final long duration = MAX_SWIPE_DURATION; + animateToProgress(endShift, duration); + } + + /** Animates to the given progress, where 0 is the current app and 1 is overview. */ + private void animateToProgress(float progress, long duration) { + ObjectAnimator anim = mCurrentShift.animateToValue(progress).setDuration(duration); + anim.setInterpolator(Interpolators.SCROLL); + anim.addListener(new AnimationSuccessListener() { + @Override + public void onAnimationSuccess(Animator animator) { + mStateCallback.setState((Float.compare(mCurrentShift.value, 0) == 0) + ? STATE_SCALED_SNAPSHOT_APP : STATE_SCALED_SNAPSHOT_RECENTS); + } + }); + anim.start(); + } + + @UiThread + private void resumeLastTask() { + if (mRecentsAnimationController != null) { + BackgroundExecutor.get().submit(() -> { + mRecentsAnimationController.setInputConsumerEnabled(false); + mRecentsAnimationController.finish(false /* toHome */); + }); + } + } + + public void reset() { + mCurrentShift.cancelAnimation(); + + if (mGestureEndCallback != null) { + mGestureEndCallback.run(); + } + mInputConsumerController.unregisterInputConsumer(); + + // TODO: These should be done as part of ActivityOptions#OnAnimationStarted + mLauncher.getStateManager().reapplyState(); + mLauncher.setOnResumeCallback(() -> mLauncherLayoutListener.close(false)); + mLauncherTransitionController.setPlayFraction(1); + clearReference(); + } + + public void layoutListenerClosed() { + if (mControllerStateAnimation) { + mLauncherTransitionController.setPlayFraction(1); + } + } + + private void switchToScreenshot() { + mLauncherLayoutListener.close(false); + View currentRecentsPage = mRecentsView.getPageAt(mRecentsView.getCurrentPage()); + if (currentRecentsPage instanceof TaskView) { + ((TaskView) currentRecentsPage).animateIconToScale(1f); + } + if (mInteractionType == INTERACTION_QUICK_SWITCH) { + for (int i = mRecentsView.getFirstTaskIndex(); i < mRecentsView.getPageCount(); i++) { + TaskView taskView = (TaskView) mRecentsView.getPageAt(i); + // TODO: Match the keys directly + if (taskView.getTask().key.id != mRunningTask.key.id) { + mRecentsView.snapToPage(i, QUICK_SWITCH_SNAP_DURATION); + taskView.postDelayed(() -> {taskView.launchTask(true);}, + QUICK_SWITCH_SNAP_DURATION); + break; + } + } + } else if (mInteractionType == INTERACTION_QUICK_SCRUB) { + if (mQuickScrubController != null) { + mQuickScrubController.snapToPageForCurrentQuickScrubSection(); + } + } else { + if (mRecentsAnimationController != null) { + TransactionCompat transaction = new TransactionCompat(); + for (RemoteAnimationTargetCompat app : mRecentsAnimationApps) { + if (app.mode == MODE_CLOSING) { + // Update the screenshot of the task + final ThumbnailData thumbnail = + mRecentsAnimationController.screenshotTask(app.taskId); + mRecentsView.updateThumbnail(app.taskId, thumbnail); + } + } + transaction.apply(); + BackgroundExecutor.get().submit(() -> { + mRecentsAnimationController.setInputConsumerEnabled(false); + mRecentsAnimationController.finish(true /* toHome */); + }); + } + } + } + + private void animateFirstTaskIcon() { + View currentRecentsPage = mRecentsView.getPageAt(mRecentsView.getCurrentPage()); + if (currentRecentsPage instanceof TaskView) { + ((TaskView) currentRecentsPage).animateIconToScale(1f); + } + } + + public void onQuickScrubEnd() { + if (mQuickScrubController != null) { + mQuickScrubController.onQuickScrubEnd(); + } else { + // TODO: + } + } + + public void onQuickScrubProgress(float progress) { + if (mQuickScrubController != null) { + mQuickScrubController.onQuickScrubProgress(progress); + } else { + // TODO: + } + } +} diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index a91907d44..c1883b1d1 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -1174,6 +1174,10 @@ public class Launcher extends BaseActivity return mAllAppsController; } + public LauncherRootView getRootView() { + return (LauncherRootView) mLauncherView; + } + public DragLayer getDragLayer() { return mDragLayer; } diff --git a/src/com/android/launcher3/dragndrop/BaseItemDragListener.java b/src/com/android/launcher3/dragndrop/BaseItemDragListener.java index 4629dad3a..9638a75d8 100644 --- a/src/com/android/launcher3/dragndrop/BaseItemDragListener.java +++ b/src/com/android/launcher3/dragndrop/BaseItemDragListener.java @@ -75,13 +75,14 @@ public abstract class BaseItemDragListener extends InternalStateHandler implemen } @Override - public void init(Launcher launcher, boolean alreadyOnHome) { + public boolean init(Launcher launcher, boolean alreadyOnHome) { AbstractFloatingView.closeAllOpenViews(launcher, alreadyOnHome); launcher.getStateManager().goToState(NORMAL, alreadyOnHome /* animated */); launcher.getDragLayer().setOnDragListener(this); mLauncher = launcher; mDragController = launcher.getDragController(); + return false; } @Override @@ -176,7 +177,4 @@ public abstract class BaseItemDragListener extends InternalStateHandler implemen mLauncher.getDragLayer().setOnDragListener(null); } } - - @Override - public void onLauncherResume() { } } diff --git a/src/com/android/launcher3/states/InternalStateHandler.java b/src/com/android/launcher3/states/InternalStateHandler.java index f084fd256..4c3ef4b2a 100644 --- a/src/com/android/launcher3/states/InternalStateHandler.java +++ b/src/com/android/launcher3/states/InternalStateHandler.java @@ -21,7 +21,11 @@ import android.os.Bundle; import android.os.IBinder; import com.android.launcher3.Launcher; -import com.android.launcher3.Launcher.OnResumeCallback; +import com.android.launcher3.LauncherAppState; +import com.android.launcher3.LauncherModel.Callbacks; +import com.android.launcher3.util.Preconditions; + +import java.lang.ref.WeakReference; /** * Utility class to sending state handling logic to Launcher from within the same process. @@ -29,11 +33,17 @@ import com.android.launcher3.Launcher.OnResumeCallback; * Extending {@link Binder} ensures that the platform maintains a single instance of each object * which allows this object to safely navigate the system process. */ -public abstract class InternalStateHandler extends Binder implements OnResumeCallback { +public abstract class InternalStateHandler extends Binder { public static final String EXTRA_STATE_HANDLER = "launcher.state_handler"; - protected abstract void init(Launcher launcher, boolean alreadyOnHome); + private static WeakReference<InternalStateHandler> sPendingHandler = new WeakReference<>(null); + + /** + * Initializes the handler when the launcher is ready. + * @return true if the handler wants to stay alive. + */ + protected abstract boolean init(Launcher launcher, boolean alreadyOnHome); public final Intent addToIntent(Intent intent) { Bundle extras = new Bundle(); @@ -42,6 +52,29 @@ public abstract class InternalStateHandler extends Binder implements OnResumeCal return intent; } + public final void initWhenReady() { + Preconditions.assertUIThread(); + sPendingHandler = new WeakReference<>(this); + LauncherAppState app = LauncherAppState.getInstanceNoCreate(); + if (app == null) { + return; + } + Callbacks cb = app.getModel().getCallback(); + if (!(cb instanceof Launcher)) { + return; + } + Launcher launcher = (Launcher) cb; + if (!init(launcher, launcher.isStarted())) { + sPendingHandler.clear(); + } + } + + public void clearReference() { + if (sPendingHandler.get() == this) { + sPendingHandler.clear(); + } + } + public static boolean handleCreate(Launcher launcher, Intent intent) { return handleIntent(launcher, intent, false); } @@ -57,12 +90,21 @@ public abstract class InternalStateHandler extends Binder implements OnResumeCal IBinder stateBinder = intent.getExtras().getBinder(EXTRA_STATE_HANDLER); if (stateBinder instanceof InternalStateHandler) { InternalStateHandler handler = (InternalStateHandler) stateBinder; - launcher.setOnResumeCallback(handler); - handler.init(launcher, alreadyOnHome); + if (!handler.init(launcher, alreadyOnHome)) { + intent.getExtras().remove(EXTRA_STATE_HANDLER); + } + result = true; + } + } + if (!result) { + InternalStateHandler pendingHandler = sPendingHandler.get(); + if (pendingHandler != null) { + if (!pendingHandler.init(launcher, alreadyOnHome)) { + sPendingHandler.clear(); + } result = true; } - intent.getExtras().remove(EXTRA_STATE_HANDLER); } return result; } -} +}
\ No newline at end of file |