Loading quickstep/src/com/android/launcher3/uioverrides/TwoStepSwipeController.java +78 −99 Original line number Diff line number Diff line Loading @@ -29,7 +29,6 @@ import android.animation.ValueAnimator.AnimatorUpdateListener; import android.support.animation.SpringAnimation; import android.util.Log; import android.view.MotionEvent; import android.view.animation.Interpolator; import com.android.launcher3.AbstractFloatingView; import com.android.launcher3.Launcher; Loading @@ -42,7 +41,6 @@ import com.android.launcher3.allapps.AllAppsContainerView; import com.android.launcher3.anim.AnimationSuccessListener; import com.android.launcher3.anim.AnimatorPlaybackController; import com.android.launcher3.anim.AnimatorSetBuilder; import com.android.launcher3.anim.Interpolators; import com.android.launcher3.anim.SpringAnimationHandler; import com.android.launcher3.touch.SwipeDetector; import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction; Loading @@ -50,6 +48,10 @@ import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch; import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType; import com.android.launcher3.util.FloatRange; import com.android.launcher3.util.TouchController; import com.android.quickstep.RecentsModel; import com.android.quickstep.RecentsView; import com.android.quickstep.TouchInteractionService; import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan; import java.util.ArrayList; Loading Loading @@ -86,6 +88,8 @@ public class TwoStepSwipeController extends AnimatorListenerAdapter private static final int FLAG_OVERVIEW_DISABLED_OUT_OF_RANGE = 1 << 0; private static final int FLAG_OVERVIEW_DISABLED_FLING = 1 << 1; private static final int FLAG_OVERVIEW_DISABLED_CANCEL_STATE = 1 << 2; private static final int FLAG_RECENTS_PLAN_LOADING = 1 << 3; private static final int FLAG_OVERVIEW_DISABLED = 1 << 4; private final Launcher mLauncher; private final SwipeDetector mDetector; Loading @@ -98,9 +102,10 @@ public class TwoStepSwipeController extends AnimatorListenerAdapter private TaggedAnimatorSetBuilder mTaggedAnimatorSetBuilder; private AnimatorSet mQuickOverviewAnimation; private boolean mAnimatingToOverview; private TwoStateAnimationController mTwoStateAnimationController; private CroppedAnimationController mCroppedAnimationController; private AnimatorPlaybackController mCurrentAnimation; private LauncherState mFromState; private LauncherState mToState; private float mStartProgress; Loading Loading @@ -240,11 +245,27 @@ public class TwoStepSwipeController extends AnimatorListenerAdapter + MAX_PROGRESS_TO_OVERVIEW - MIN_PROGRESS_TO_OVERVIEW; // Build current animation mFromState = mLauncher.getStateManager().getState(); mToState = mLauncher.isInState(ALL_APPS) ? NORMAL : ALL_APPS; mTaggedAnimatorSetBuilder = new TaggedAnimatorSetBuilder(); mCurrentAnimation = mLauncher.getStateManager().createAnimationToNewWorkspace( mToState, mTaggedAnimatorSetBuilder, maxAccuracy); if (TouchInteractionService.isConnected()) { // Load recents plan RecentsModel recentsModel = RecentsModel.getInstance(mLauncher); if (recentsModel.getLastLoadPlan() != null) { onRecentsPlanLoaded(recentsModel.getLastLoadPlan()); } else { mDragPauseDetector.addDisabledFlags(FLAG_RECENTS_PLAN_LOADING); } // Reload again so that we get the latest list // TODO: Use callback instead of polling everytime recentsModel.loadTasks(-1, this::onRecentsPlanLoaded); } else { mDragPauseDetector.addDisabledFlags(FLAG_OVERVIEW_DISABLED); } mCurrentAnimation.getTarget().addListener(this); mStartProgress = 0; mProgressMultiplier = (mLauncher.isInState(ALL_APPS) ? 1 : -1) / range; Loading @@ -262,6 +283,14 @@ public class TwoStepSwipeController extends AnimatorListenerAdapter } } private void onRecentsPlanLoaded(RecentsTaskLoadPlan plan) { RecentsView recentsView = mLauncher.getOverviewPanel(); recentsView.update(plan); recentsView.initToPage(0); mDragPauseDetector.clearDisabledFlags(FLAG_RECENTS_PLAN_LOADING); } private float getShiftRange() { return mLauncher.getAllAppsController().getShiftRange(); } Loading @@ -287,16 +316,11 @@ public class TwoStepSwipeController extends AnimatorListenerAdapter @Override public void onDragEnd(float velocity, boolean fling) { if (!fling && mDragPauseDetector.isEnabled() && mDragPauseDetector.isTriggered()) { snapToOverview(velocity); return; } mDragPauseDetector.addDisabledFlags(FLAG_OVERVIEW_DISABLED_FLING); final long animationDuration; final int logAction; final LauncherState targetState; LauncherState targetState; final float progress = mCurrentAnimation.getProgressFraction(); if (fling) { Loading @@ -317,7 +341,7 @@ public class TwoStepSwipeController extends AnimatorListenerAdapter targetState = mToState; animationDuration = SwipeDetector.calculateDuration(velocity, 1 - progress); } else { targetState = mToState == ALL_APPS ? NORMAL : ALL_APPS; targetState = mFromState; animationDuration = SwipeDetector.calculateDuration(velocity, progress); } } Loading @@ -328,7 +352,13 @@ public class TwoStepSwipeController extends AnimatorListenerAdapter h.animateToFinalPosition(0 /* pos */, 1 /* startValue */); } } mCurrentAnimation.setEndAction(() -> onSwipeInteractionCompleted(targetState, logAction)); mCurrentAnimation.setEndAction(() -> { LauncherState finalState = targetState; if (mDragPauseDetector.isTriggered() && targetState == NORMAL) { finalState = OVERVIEW; } onSwipeInteractionCompleted(finalState, logAction); }); float nextFrameProgress = Utilities.boundToRange( progress + velocity * SINGLE_FRAME_MS / getShiftRange(), 0f, 1f); Loading @@ -341,7 +371,7 @@ public class TwoStepSwipeController extends AnimatorListenerAdapter } private void onSwipeInteractionCompleted(LauncherState targetState, int logAction) { if (targetState == mToState) { if (targetState != mFromState) { // Transition complete. log the action mLauncher.getUserEventDispatcher().logActionOnContainer(logAction, mToState == ALL_APPS ? Direction.UP : Direction.DOWN, Loading @@ -354,33 +384,6 @@ public class TwoStepSwipeController extends AnimatorListenerAdapter mLauncher.getStateManager().goToState(targetState, false /* animated */); } private void snapToOverview(float velocity) { mAnimatingToOverview = true; final float progress = mCurrentAnimation.getProgressFraction(); float endProgress = mToState == NORMAL ? 1f : 0f; long animationDuration = SwipeDetector.calculateDuration( velocity, Math.abs(endProgress - progress)); float nextFrameProgress = Utilities.boundToRange( progress + velocity * SINGLE_FRAME_MS / getShiftRange(), 0f, 1f); mCurrentAnimation.setEndAction(() -> { // TODO: Add logging clearState(); mLauncher.getStateManager().goToState(OVERVIEW, true /* animated */); }); if (mTwoStateAnimationController != null) { mTwoStateAnimationController.goBackToStart(endProgress); } ValueAnimator anim = mCurrentAnimation.getAnimationPlayer(); anim.setFloatValues(nextFrameProgress, endProgress); anim.setDuration(animationDuration); anim.setInterpolator(scrollInterpolatorForVelocity(velocity)); anim.start(); } private void onDragPauseDetected() { final ValueAnimator twoStepAnimator = ValueAnimator.ofFloat(0, 1); twoStepAnimator.setDuration(mCurrentAnimation.getDuration()); Loading Loading @@ -409,33 +412,29 @@ public class TwoStepSwipeController extends AnimatorListenerAdapter mQuickOverviewAnimation.start(); } private void onQuickOverviewAnimationComplete(ValueAnimator twoStepAnimator) { private void onQuickOverviewAnimationComplete(ValueAnimator animator) { if (mAnimatingToOverview) { return; } // The remaining state handlers are on the OVERVIEW state. Create two animations, one // towards the NORMAL state and one towards ALL_APPS state and control them based on the // swipe progress. // For the remainder to the interaction, the user can either go to the ALL_APPS state or // the OVERVIEW state. // The remaining state handlers are on the OVERVIEW state. Create one animation towards the // ALL_APPS state and only call it when the user moved above the current range. AnimationConfig config = new AnimationConfig(); config.duration = (long) (2 * getShiftRange()); config.userControlled = true; LauncherState fromState = mToState == ALL_APPS ? NORMAL : ALL_APPS; AnimatorSetBuilder builderToTargetState = new AnimatorSetBuilder(); AnimatorSetBuilder builderToSourceState = new AnimatorSetBuilder(); AnimatorSetBuilder builderToAllAppsState = new AnimatorSetBuilder(); StateHandler[] handlers = mLauncher.getStateManager().getStateHandlers(); for (int i = OTHER_HANDLERS_START_INDEX; i < handlers.length; i++) { handlers[i].setStateWithAnimation(mToState, builderToTargetState, config); handlers[i].setStateWithAnimation(fromState, builderToSourceState, config); handlers[i].setStateWithAnimation(ALL_APPS, builderToAllAppsState, config); } mTwoStateAnimationController = new TwoStateAnimationController( AnimatorPlaybackController.wrap(builderToSourceState.build(), config.duration), AnimatorPlaybackController.wrap(builderToTargetState.build(), config.duration), twoStepAnimator.getAnimatedFraction()); twoStepAnimator.addUpdateListener(mTwoStateAnimationController); mCroppedAnimationController = new CroppedAnimationController( AnimatorPlaybackController.wrap(builderToAllAppsState.build(), config.duration), new FloatRange(animator.getAnimatedFraction(), mToState == ALL_APPS ? 1 : 0)); animator.addUpdateListener(mCroppedAnimationController); } private void clearState() { Loading @@ -450,69 +449,49 @@ public class TwoStepSwipeController extends AnimatorListenerAdapter mQuickOverviewAnimation.cancel(); mQuickOverviewAnimation = null; } mTwoStateAnimationController = null; mCroppedAnimationController = null; mAnimatingToOverview = false; mDetector.finishedScrolling(); } /** * {@link AnimatorUpdateListener} which interpolates two animations based the progress * {@link AnimatorUpdateListener} which controls another animation for a fraction of range */ private static class TwoStateAnimationController implements AnimatorUpdateListener { private final AnimatorPlaybackController mControllerTowardsStart; private final AnimatorPlaybackController mControllerTowardsEnd; private static class CroppedAnimationController implements AnimatorUpdateListener { private Interpolator mInterpolator = Interpolators.LINEAR; private float mStartFraction; private float mLastFraction; private final AnimatorPlaybackController mTarget; private final FloatRange mRange; TwoStateAnimationController(AnimatorPlaybackController controllerTowardsStart, AnimatorPlaybackController controllerTowardsEnd, float startFraction) { mControllerTowardsStart = controllerTowardsStart; mControllerTowardsEnd = controllerTowardsEnd; mLastFraction = mStartFraction = startFraction; CroppedAnimationController(AnimatorPlaybackController target, FloatRange range) { mTarget = target; mRange = range; } @Override public void onAnimationUpdate(ValueAnimator valueAnimator) { mLastFraction = mInterpolator.getInterpolation(valueAnimator.getAnimatedFraction()); if (mLastFraction > mStartFraction) { if (mStartFraction >= 1) { mControllerTowardsEnd.setPlayFraction(0); float fraction = valueAnimator.getAnimatedFraction(); if (mRange.start < mRange.end) { if (fraction <= mRange.start) { mTarget.setPlayFraction(0); } else if (fraction >= mRange.end) { mTarget.setPlayFraction(1); } else { mControllerTowardsEnd.setPlayFraction( (mLastFraction - mStartFraction) / (1 - mStartFraction)); mTarget.setPlayFraction((fraction - mRange.start) / (mRange.end - mRange.start)); } } else if (mRange.start > mRange.end) { if (fraction >= mRange.start) { mTarget.setPlayFraction(0); } else if (fraction <= mRange.end) { mTarget.setPlayFraction(1); } else { if (mStartFraction <= 0) { mControllerTowardsStart.setPlayFraction(0); } else { mControllerTowardsStart.setPlayFraction( (mStartFraction - mLastFraction) / mStartFraction); mTarget.setPlayFraction((fraction - mRange.start) / (mRange.end - mRange.start)); } } } /** * Changes the interpolator such that from this point ({@link #mLastFraction}), the * animation run towards {@link #mStartFraction}. This allows us to animate the UI back * to the original point. * @param endFraction expected end point for this animation. Should either be 0 or 1. */ public void goBackToStart(float endFraction) { if (mLastFraction == mStartFraction || mLastFraction == endFraction) { mInterpolator = (v) -> mStartFraction; } else if (mLastFraction > mStartFraction && endFraction < mStartFraction) { mInterpolator = (v) -> Math.max(v, mStartFraction); } else if (mLastFraction < mStartFraction && endFraction > mStartFraction) { mInterpolator = (v) -> Math.min(mStartFraction, v); } else { final float start = mLastFraction; final float range = endFraction - mLastFraction; mInterpolator = (v) -> SwipeDetector.interpolate(start, mStartFraction, (v - start) / range); // mRange.start == mRange.end mTarget.setPlayFraction(0); } } } Loading quickstep/src/com/android/quickstep/NavBarSwipeInteractionHandler.java +7 −1 Original line number Diff line number Diff line Loading @@ -44,6 +44,7 @@ import com.android.launcher3.dragndrop.DragLayer; import com.android.launcher3.states.InternalStateHandler; import com.android.launcher3.uioverrides.RecentsViewStateController; import com.android.launcher3.util.TraceHelper; import com.android.launcher3.views.AllAppsScrim; 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; Loading Loading @@ -97,6 +98,7 @@ public class NavBarSwipeInteractionHandler extends InternalStateHandler { private RecentsView mRecentsView; private RecentsViewStateController mStateController; private Hotseat mHotseat; private AllAppsScrim mAllAppsScrim; private RecentsTaskLoadPlan mLoadPlan; private boolean mLauncherReady; Loading Loading @@ -182,6 +184,7 @@ public class NavBarSwipeInteractionHandler extends InternalStateHandler { mRecentsView = mLauncher.getOverviewPanel(); mStateController = mRecentsView.getStateController(); mHotseat = mLauncher.getHotseat(); mAllAppsScrim = mLauncher.findViewById(R.id.all_apps_scrim); // Optimization mLauncher.getAppsView().setVisibility(View.GONE); Loading Loading @@ -222,7 +225,9 @@ public class NavBarSwipeInteractionHandler extends InternalStateHandler { float shift = mCurrentShift.value * mActivityMultiplier.value; int hotseatSize = getHotseatSize(); mHotseat.setTranslationY((1 - shift) * hotseatSize); float hotseatTranslation = (1 - shift) * hotseatSize; mHotseat.setTranslationY(hotseatTranslation); mAllAppsScrim.setTranslationY(hotseatTranslation); mRectEvaluator.evaluate(shift, mSourceRect, mTargetRect); Loading Loading @@ -324,6 +329,7 @@ public class NavBarSwipeInteractionHandler extends InternalStateHandler { private void cleanupLauncher() { // TODO: These should be done as part of ActivityOptions#OnAnimationStarted mHotseat.setTranslationY(0); mAllAppsScrim.setTranslationY(0); mLauncher.setOnResumeCallback(() -> mDragView.close(false)); } Loading quickstep/src/com/android/quickstep/RecentsModel.java 0 → 100644 +104 −0 Original line number Diff line number Diff line /* * 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 android.content.res.Resources; import android.os.Looper; import android.os.UserHandle; import com.android.launcher3.MainThreadExecutor; import com.android.launcher3.R; import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan; import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan.PreloadOptions; import com.android.systemui.shared.recents.model.RecentsTaskLoader; import com.android.systemui.shared.system.BackgroundExecutor; import java.util.concurrent.ExecutionException; import java.util.function.Consumer; /** * Singleton class to load and manage recents model. */ public class RecentsModel { // We do not need any synchronization for this variable as its only written on UI thread. private static RecentsModel INSTANCE; public static RecentsModel getInstance(final Context context) { if (INSTANCE == null) { if (Looper.myLooper() == Looper.getMainLooper()) { INSTANCE = new RecentsModel(context.getApplicationContext()); } else { try { return new MainThreadExecutor().submit( () -> RecentsModel.getInstance(context)).get(); } catch (InterruptedException|ExecutionException e) { throw new RuntimeException(e); } } } return INSTANCE; } private final Context mContext; private final RecentsTaskLoader mRecentsTaskLoader; private final MainThreadExecutor mMainThreadExecutor; private RecentsTaskLoadPlan mLastLoadPlan; private RecentsModel(Context context) { mContext = context; Resources res = context.getResources(); mRecentsTaskLoader = new RecentsTaskLoader(mContext, res.getInteger(R.integer.config_recentsMaxThumbnailCacheSize), res.getInteger(R.integer.config_recentsMaxIconCacheSize), 0); mRecentsTaskLoader.startLoader(mContext); mMainThreadExecutor = new MainThreadExecutor(); } public RecentsTaskLoader getRecentsTaskLoader() { return mRecentsTaskLoader; } /** * Preloads the task plan * @param taskId The running task id or -1 * @param callback The callback to receive the task plan once its complete or null. This is * always called on the UI thread. */ public void loadTasks(int taskId, Consumer<RecentsTaskLoadPlan> callback) { BackgroundExecutor.get().submit(() -> { // Preload the plan RecentsTaskLoadPlan loadPlan = new RecentsTaskLoadPlan(mContext); PreloadOptions opts = new PreloadOptions(); opts.loadTitles = false; loadPlan.preloadPlan(opts, mRecentsTaskLoader, taskId, UserHandle.myUserId()); // Set the load plan on UI thread mMainThreadExecutor.execute(() -> { mLastLoadPlan = loadPlan; if (callback != null) { callback.accept(loadPlan); } }); }); } public RecentsTaskLoadPlan getLastLoadPlan() { return mLastLoadPlan; } } quickstep/src/com/android/quickstep/RecentsView.java +2 −1 Original line number Diff line number Diff line Loading @@ -146,7 +146,8 @@ public class RecentsView extends PagedView { } public void update(RecentsTaskLoadPlan loadPlan) { final RecentsTaskLoader loader = TouchInteractionService.getRecentsTaskLoader(); final RecentsTaskLoader loader = RecentsModel.getInstance(getContext()) .getRecentsTaskLoader(); TaskStack stack = loadPlan != null ? loadPlan.getTaskStack() : null; if (stack == null) { removeAllViews(); Loading quickstep/src/com/android/quickstep/TaskThumbnailView.java +6 −0 Original line number Diff line number Diff line Loading @@ -173,4 +173,10 @@ public class TaskThumbnailView extends FrameLayout { } invalidate(); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); updateThumbnailMatrix(); } } Loading
quickstep/src/com/android/launcher3/uioverrides/TwoStepSwipeController.java +78 −99 Original line number Diff line number Diff line Loading @@ -29,7 +29,6 @@ import android.animation.ValueAnimator.AnimatorUpdateListener; import android.support.animation.SpringAnimation; import android.util.Log; import android.view.MotionEvent; import android.view.animation.Interpolator; import com.android.launcher3.AbstractFloatingView; import com.android.launcher3.Launcher; Loading @@ -42,7 +41,6 @@ import com.android.launcher3.allapps.AllAppsContainerView; import com.android.launcher3.anim.AnimationSuccessListener; import com.android.launcher3.anim.AnimatorPlaybackController; import com.android.launcher3.anim.AnimatorSetBuilder; import com.android.launcher3.anim.Interpolators; import com.android.launcher3.anim.SpringAnimationHandler; import com.android.launcher3.touch.SwipeDetector; import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction; Loading @@ -50,6 +48,10 @@ import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch; import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType; import com.android.launcher3.util.FloatRange; import com.android.launcher3.util.TouchController; import com.android.quickstep.RecentsModel; import com.android.quickstep.RecentsView; import com.android.quickstep.TouchInteractionService; import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan; import java.util.ArrayList; Loading Loading @@ -86,6 +88,8 @@ public class TwoStepSwipeController extends AnimatorListenerAdapter private static final int FLAG_OVERVIEW_DISABLED_OUT_OF_RANGE = 1 << 0; private static final int FLAG_OVERVIEW_DISABLED_FLING = 1 << 1; private static final int FLAG_OVERVIEW_DISABLED_CANCEL_STATE = 1 << 2; private static final int FLAG_RECENTS_PLAN_LOADING = 1 << 3; private static final int FLAG_OVERVIEW_DISABLED = 1 << 4; private final Launcher mLauncher; private final SwipeDetector mDetector; Loading @@ -98,9 +102,10 @@ public class TwoStepSwipeController extends AnimatorListenerAdapter private TaggedAnimatorSetBuilder mTaggedAnimatorSetBuilder; private AnimatorSet mQuickOverviewAnimation; private boolean mAnimatingToOverview; private TwoStateAnimationController mTwoStateAnimationController; private CroppedAnimationController mCroppedAnimationController; private AnimatorPlaybackController mCurrentAnimation; private LauncherState mFromState; private LauncherState mToState; private float mStartProgress; Loading Loading @@ -240,11 +245,27 @@ public class TwoStepSwipeController extends AnimatorListenerAdapter + MAX_PROGRESS_TO_OVERVIEW - MIN_PROGRESS_TO_OVERVIEW; // Build current animation mFromState = mLauncher.getStateManager().getState(); mToState = mLauncher.isInState(ALL_APPS) ? NORMAL : ALL_APPS; mTaggedAnimatorSetBuilder = new TaggedAnimatorSetBuilder(); mCurrentAnimation = mLauncher.getStateManager().createAnimationToNewWorkspace( mToState, mTaggedAnimatorSetBuilder, maxAccuracy); if (TouchInteractionService.isConnected()) { // Load recents plan RecentsModel recentsModel = RecentsModel.getInstance(mLauncher); if (recentsModel.getLastLoadPlan() != null) { onRecentsPlanLoaded(recentsModel.getLastLoadPlan()); } else { mDragPauseDetector.addDisabledFlags(FLAG_RECENTS_PLAN_LOADING); } // Reload again so that we get the latest list // TODO: Use callback instead of polling everytime recentsModel.loadTasks(-1, this::onRecentsPlanLoaded); } else { mDragPauseDetector.addDisabledFlags(FLAG_OVERVIEW_DISABLED); } mCurrentAnimation.getTarget().addListener(this); mStartProgress = 0; mProgressMultiplier = (mLauncher.isInState(ALL_APPS) ? 1 : -1) / range; Loading @@ -262,6 +283,14 @@ public class TwoStepSwipeController extends AnimatorListenerAdapter } } private void onRecentsPlanLoaded(RecentsTaskLoadPlan plan) { RecentsView recentsView = mLauncher.getOverviewPanel(); recentsView.update(plan); recentsView.initToPage(0); mDragPauseDetector.clearDisabledFlags(FLAG_RECENTS_PLAN_LOADING); } private float getShiftRange() { return mLauncher.getAllAppsController().getShiftRange(); } Loading @@ -287,16 +316,11 @@ public class TwoStepSwipeController extends AnimatorListenerAdapter @Override public void onDragEnd(float velocity, boolean fling) { if (!fling && mDragPauseDetector.isEnabled() && mDragPauseDetector.isTriggered()) { snapToOverview(velocity); return; } mDragPauseDetector.addDisabledFlags(FLAG_OVERVIEW_DISABLED_FLING); final long animationDuration; final int logAction; final LauncherState targetState; LauncherState targetState; final float progress = mCurrentAnimation.getProgressFraction(); if (fling) { Loading @@ -317,7 +341,7 @@ public class TwoStepSwipeController extends AnimatorListenerAdapter targetState = mToState; animationDuration = SwipeDetector.calculateDuration(velocity, 1 - progress); } else { targetState = mToState == ALL_APPS ? NORMAL : ALL_APPS; targetState = mFromState; animationDuration = SwipeDetector.calculateDuration(velocity, progress); } } Loading @@ -328,7 +352,13 @@ public class TwoStepSwipeController extends AnimatorListenerAdapter h.animateToFinalPosition(0 /* pos */, 1 /* startValue */); } } mCurrentAnimation.setEndAction(() -> onSwipeInteractionCompleted(targetState, logAction)); mCurrentAnimation.setEndAction(() -> { LauncherState finalState = targetState; if (mDragPauseDetector.isTriggered() && targetState == NORMAL) { finalState = OVERVIEW; } onSwipeInteractionCompleted(finalState, logAction); }); float nextFrameProgress = Utilities.boundToRange( progress + velocity * SINGLE_FRAME_MS / getShiftRange(), 0f, 1f); Loading @@ -341,7 +371,7 @@ public class TwoStepSwipeController extends AnimatorListenerAdapter } private void onSwipeInteractionCompleted(LauncherState targetState, int logAction) { if (targetState == mToState) { if (targetState != mFromState) { // Transition complete. log the action mLauncher.getUserEventDispatcher().logActionOnContainer(logAction, mToState == ALL_APPS ? Direction.UP : Direction.DOWN, Loading @@ -354,33 +384,6 @@ public class TwoStepSwipeController extends AnimatorListenerAdapter mLauncher.getStateManager().goToState(targetState, false /* animated */); } private void snapToOverview(float velocity) { mAnimatingToOverview = true; final float progress = mCurrentAnimation.getProgressFraction(); float endProgress = mToState == NORMAL ? 1f : 0f; long animationDuration = SwipeDetector.calculateDuration( velocity, Math.abs(endProgress - progress)); float nextFrameProgress = Utilities.boundToRange( progress + velocity * SINGLE_FRAME_MS / getShiftRange(), 0f, 1f); mCurrentAnimation.setEndAction(() -> { // TODO: Add logging clearState(); mLauncher.getStateManager().goToState(OVERVIEW, true /* animated */); }); if (mTwoStateAnimationController != null) { mTwoStateAnimationController.goBackToStart(endProgress); } ValueAnimator anim = mCurrentAnimation.getAnimationPlayer(); anim.setFloatValues(nextFrameProgress, endProgress); anim.setDuration(animationDuration); anim.setInterpolator(scrollInterpolatorForVelocity(velocity)); anim.start(); } private void onDragPauseDetected() { final ValueAnimator twoStepAnimator = ValueAnimator.ofFloat(0, 1); twoStepAnimator.setDuration(mCurrentAnimation.getDuration()); Loading Loading @@ -409,33 +412,29 @@ public class TwoStepSwipeController extends AnimatorListenerAdapter mQuickOverviewAnimation.start(); } private void onQuickOverviewAnimationComplete(ValueAnimator twoStepAnimator) { private void onQuickOverviewAnimationComplete(ValueAnimator animator) { if (mAnimatingToOverview) { return; } // The remaining state handlers are on the OVERVIEW state. Create two animations, one // towards the NORMAL state and one towards ALL_APPS state and control them based on the // swipe progress. // For the remainder to the interaction, the user can either go to the ALL_APPS state or // the OVERVIEW state. // The remaining state handlers are on the OVERVIEW state. Create one animation towards the // ALL_APPS state and only call it when the user moved above the current range. AnimationConfig config = new AnimationConfig(); config.duration = (long) (2 * getShiftRange()); config.userControlled = true; LauncherState fromState = mToState == ALL_APPS ? NORMAL : ALL_APPS; AnimatorSetBuilder builderToTargetState = new AnimatorSetBuilder(); AnimatorSetBuilder builderToSourceState = new AnimatorSetBuilder(); AnimatorSetBuilder builderToAllAppsState = new AnimatorSetBuilder(); StateHandler[] handlers = mLauncher.getStateManager().getStateHandlers(); for (int i = OTHER_HANDLERS_START_INDEX; i < handlers.length; i++) { handlers[i].setStateWithAnimation(mToState, builderToTargetState, config); handlers[i].setStateWithAnimation(fromState, builderToSourceState, config); handlers[i].setStateWithAnimation(ALL_APPS, builderToAllAppsState, config); } mTwoStateAnimationController = new TwoStateAnimationController( AnimatorPlaybackController.wrap(builderToSourceState.build(), config.duration), AnimatorPlaybackController.wrap(builderToTargetState.build(), config.duration), twoStepAnimator.getAnimatedFraction()); twoStepAnimator.addUpdateListener(mTwoStateAnimationController); mCroppedAnimationController = new CroppedAnimationController( AnimatorPlaybackController.wrap(builderToAllAppsState.build(), config.duration), new FloatRange(animator.getAnimatedFraction(), mToState == ALL_APPS ? 1 : 0)); animator.addUpdateListener(mCroppedAnimationController); } private void clearState() { Loading @@ -450,69 +449,49 @@ public class TwoStepSwipeController extends AnimatorListenerAdapter mQuickOverviewAnimation.cancel(); mQuickOverviewAnimation = null; } mTwoStateAnimationController = null; mCroppedAnimationController = null; mAnimatingToOverview = false; mDetector.finishedScrolling(); } /** * {@link AnimatorUpdateListener} which interpolates two animations based the progress * {@link AnimatorUpdateListener} which controls another animation for a fraction of range */ private static class TwoStateAnimationController implements AnimatorUpdateListener { private final AnimatorPlaybackController mControllerTowardsStart; private final AnimatorPlaybackController mControllerTowardsEnd; private static class CroppedAnimationController implements AnimatorUpdateListener { private Interpolator mInterpolator = Interpolators.LINEAR; private float mStartFraction; private float mLastFraction; private final AnimatorPlaybackController mTarget; private final FloatRange mRange; TwoStateAnimationController(AnimatorPlaybackController controllerTowardsStart, AnimatorPlaybackController controllerTowardsEnd, float startFraction) { mControllerTowardsStart = controllerTowardsStart; mControllerTowardsEnd = controllerTowardsEnd; mLastFraction = mStartFraction = startFraction; CroppedAnimationController(AnimatorPlaybackController target, FloatRange range) { mTarget = target; mRange = range; } @Override public void onAnimationUpdate(ValueAnimator valueAnimator) { mLastFraction = mInterpolator.getInterpolation(valueAnimator.getAnimatedFraction()); if (mLastFraction > mStartFraction) { if (mStartFraction >= 1) { mControllerTowardsEnd.setPlayFraction(0); float fraction = valueAnimator.getAnimatedFraction(); if (mRange.start < mRange.end) { if (fraction <= mRange.start) { mTarget.setPlayFraction(0); } else if (fraction >= mRange.end) { mTarget.setPlayFraction(1); } else { mControllerTowardsEnd.setPlayFraction( (mLastFraction - mStartFraction) / (1 - mStartFraction)); mTarget.setPlayFraction((fraction - mRange.start) / (mRange.end - mRange.start)); } } else if (mRange.start > mRange.end) { if (fraction >= mRange.start) { mTarget.setPlayFraction(0); } else if (fraction <= mRange.end) { mTarget.setPlayFraction(1); } else { if (mStartFraction <= 0) { mControllerTowardsStart.setPlayFraction(0); } else { mControllerTowardsStart.setPlayFraction( (mStartFraction - mLastFraction) / mStartFraction); mTarget.setPlayFraction((fraction - mRange.start) / (mRange.end - mRange.start)); } } } /** * Changes the interpolator such that from this point ({@link #mLastFraction}), the * animation run towards {@link #mStartFraction}. This allows us to animate the UI back * to the original point. * @param endFraction expected end point for this animation. Should either be 0 or 1. */ public void goBackToStart(float endFraction) { if (mLastFraction == mStartFraction || mLastFraction == endFraction) { mInterpolator = (v) -> mStartFraction; } else if (mLastFraction > mStartFraction && endFraction < mStartFraction) { mInterpolator = (v) -> Math.max(v, mStartFraction); } else if (mLastFraction < mStartFraction && endFraction > mStartFraction) { mInterpolator = (v) -> Math.min(mStartFraction, v); } else { final float start = mLastFraction; final float range = endFraction - mLastFraction; mInterpolator = (v) -> SwipeDetector.interpolate(start, mStartFraction, (v - start) / range); // mRange.start == mRange.end mTarget.setPlayFraction(0); } } } Loading
quickstep/src/com/android/quickstep/NavBarSwipeInteractionHandler.java +7 −1 Original line number Diff line number Diff line Loading @@ -44,6 +44,7 @@ import com.android.launcher3.dragndrop.DragLayer; import com.android.launcher3.states.InternalStateHandler; import com.android.launcher3.uioverrides.RecentsViewStateController; import com.android.launcher3.util.TraceHelper; import com.android.launcher3.views.AllAppsScrim; 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; Loading Loading @@ -97,6 +98,7 @@ public class NavBarSwipeInteractionHandler extends InternalStateHandler { private RecentsView mRecentsView; private RecentsViewStateController mStateController; private Hotseat mHotseat; private AllAppsScrim mAllAppsScrim; private RecentsTaskLoadPlan mLoadPlan; private boolean mLauncherReady; Loading Loading @@ -182,6 +184,7 @@ public class NavBarSwipeInteractionHandler extends InternalStateHandler { mRecentsView = mLauncher.getOverviewPanel(); mStateController = mRecentsView.getStateController(); mHotseat = mLauncher.getHotseat(); mAllAppsScrim = mLauncher.findViewById(R.id.all_apps_scrim); // Optimization mLauncher.getAppsView().setVisibility(View.GONE); Loading Loading @@ -222,7 +225,9 @@ public class NavBarSwipeInteractionHandler extends InternalStateHandler { float shift = mCurrentShift.value * mActivityMultiplier.value; int hotseatSize = getHotseatSize(); mHotseat.setTranslationY((1 - shift) * hotseatSize); float hotseatTranslation = (1 - shift) * hotseatSize; mHotseat.setTranslationY(hotseatTranslation); mAllAppsScrim.setTranslationY(hotseatTranslation); mRectEvaluator.evaluate(shift, mSourceRect, mTargetRect); Loading Loading @@ -324,6 +329,7 @@ public class NavBarSwipeInteractionHandler extends InternalStateHandler { private void cleanupLauncher() { // TODO: These should be done as part of ActivityOptions#OnAnimationStarted mHotseat.setTranslationY(0); mAllAppsScrim.setTranslationY(0); mLauncher.setOnResumeCallback(() -> mDragView.close(false)); } Loading
quickstep/src/com/android/quickstep/RecentsModel.java 0 → 100644 +104 −0 Original line number Diff line number Diff line /* * 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 android.content.res.Resources; import android.os.Looper; import android.os.UserHandle; import com.android.launcher3.MainThreadExecutor; import com.android.launcher3.R; import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan; import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan.PreloadOptions; import com.android.systemui.shared.recents.model.RecentsTaskLoader; import com.android.systemui.shared.system.BackgroundExecutor; import java.util.concurrent.ExecutionException; import java.util.function.Consumer; /** * Singleton class to load and manage recents model. */ public class RecentsModel { // We do not need any synchronization for this variable as its only written on UI thread. private static RecentsModel INSTANCE; public static RecentsModel getInstance(final Context context) { if (INSTANCE == null) { if (Looper.myLooper() == Looper.getMainLooper()) { INSTANCE = new RecentsModel(context.getApplicationContext()); } else { try { return new MainThreadExecutor().submit( () -> RecentsModel.getInstance(context)).get(); } catch (InterruptedException|ExecutionException e) { throw new RuntimeException(e); } } } return INSTANCE; } private final Context mContext; private final RecentsTaskLoader mRecentsTaskLoader; private final MainThreadExecutor mMainThreadExecutor; private RecentsTaskLoadPlan mLastLoadPlan; private RecentsModel(Context context) { mContext = context; Resources res = context.getResources(); mRecentsTaskLoader = new RecentsTaskLoader(mContext, res.getInteger(R.integer.config_recentsMaxThumbnailCacheSize), res.getInteger(R.integer.config_recentsMaxIconCacheSize), 0); mRecentsTaskLoader.startLoader(mContext); mMainThreadExecutor = new MainThreadExecutor(); } public RecentsTaskLoader getRecentsTaskLoader() { return mRecentsTaskLoader; } /** * Preloads the task plan * @param taskId The running task id or -1 * @param callback The callback to receive the task plan once its complete or null. This is * always called on the UI thread. */ public void loadTasks(int taskId, Consumer<RecentsTaskLoadPlan> callback) { BackgroundExecutor.get().submit(() -> { // Preload the plan RecentsTaskLoadPlan loadPlan = new RecentsTaskLoadPlan(mContext); PreloadOptions opts = new PreloadOptions(); opts.loadTitles = false; loadPlan.preloadPlan(opts, mRecentsTaskLoader, taskId, UserHandle.myUserId()); // Set the load plan on UI thread mMainThreadExecutor.execute(() -> { mLastLoadPlan = loadPlan; if (callback != null) { callback.accept(loadPlan); } }); }); } public RecentsTaskLoadPlan getLastLoadPlan() { return mLastLoadPlan; } }
quickstep/src/com/android/quickstep/RecentsView.java +2 −1 Original line number Diff line number Diff line Loading @@ -146,7 +146,8 @@ public class RecentsView extends PagedView { } public void update(RecentsTaskLoadPlan loadPlan) { final RecentsTaskLoader loader = TouchInteractionService.getRecentsTaskLoader(); final RecentsTaskLoader loader = RecentsModel.getInstance(getContext()) .getRecentsTaskLoader(); TaskStack stack = loadPlan != null ? loadPlan.getTaskStack() : null; if (stack == null) { removeAllViews(); Loading
quickstep/src/com/android/quickstep/TaskThumbnailView.java +6 −0 Original line number Diff line number Diff line Loading @@ -173,4 +173,10 @@ public class TaskThumbnailView extends FrameLayout { } invalidate(); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); updateThumbnailMatrix(); } }