Loading go/quickstep/src/com/android/quickstep/TouchInteractionService.java +9 −0 Original line number Diff line number Diff line Loading @@ -34,6 +34,8 @@ import android.view.MotionEvent; import com.android.launcher3.Utilities; import com.android.launcher3.compat.UserManagerCompat; import com.android.launcher3.util.LooperExecutor; import com.android.launcher3.util.UiThreadHelper; import com.android.systemui.shared.recents.IOverviewProxy; import com.android.systemui.shared.recents.ISystemUiProxy; Loading Loading @@ -137,6 +139,9 @@ public class TouchInteractionService extends Service { return sConnected; } public static final LooperExecutor BACKGROUND_EXECUTOR = new LooperExecutor(UiThreadHelper.getBackgroundLooper()); private RecentsModel mRecentsModel; private OverviewComponentObserver mOverviewComponentObserver; private OverviewCommandHelper mOverviewCommandHelper; Loading Loading @@ -180,4 +185,8 @@ public class TouchInteractionService extends Service { } return mMyBinder; } public static boolean isInputMonitorInitialized() { return true; } } quickstep/AndroidManifest.xml +1 −0 Original line number Diff line number Diff line Loading @@ -23,6 +23,7 @@ package="com.android.launcher3" > <uses-permission android:name="android.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS" /> <uses-permission android:name="android.permission.VIBRATE" /> <application android:backupAgent="com.android.launcher3.LauncherBackupAgent" Loading quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java +15 −0 Original line number Diff line number Diff line Loading @@ -167,6 +167,21 @@ public class FlingAndHoldTouchController extends PortraitStatesTouchController { mMotionPauseDetector.clear(); } @Override protected void goToTargetState(LauncherState targetState, int logAction) { if (mPeekAnim != null && mPeekAnim.isStarted()) { // Don't jump to the target state until overview is no longer peeking. mPeekAnim.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { FlingAndHoldTouchController.super.goToTargetState(targetState, logAction); } }); } else { super.goToTargetState(targetState, logAction); } } @Override protected void updateAnimatorBuilderOnReinit(AnimatorSetBuilder builder) { if (handlingOverviewAnim()) { Loading quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java 0 → 100644 +496 −0 Original line number Diff line number Diff line /* * Copyright (C) 2019 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.os.VibrationEffect.EFFECT_CLICK; import static android.os.VibrationEffect.createPredefined; import static com.android.launcher3.Utilities.postAsyncCallback; import static com.android.launcher3.anim.Interpolators.ACCEL_1_5; import static com.android.launcher3.anim.Interpolators.DEACCEL; import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE; import static com.android.launcher3.views.FloatingIconView.SHAPE_PROGRESS_DURATION; import static com.android.quickstep.TouchInteractionService.BACKGROUND_EXECUTOR; import static com.android.quickstep.TouchInteractionService.MAIN_THREAD_EXECUTOR; import static com.android.quickstep.TouchInteractionService.TOUCH_INTERACTION_LOG; import android.animation.Animator; import android.annotation.TargetApi; import android.app.ActivityManager.RunningTaskInfo; import android.content.Context; import android.content.Intent; import android.graphics.Point; import android.graphics.PointF; import android.graphics.Rect; import android.graphics.RectF; import android.os.Build; import android.os.Handler; import android.os.Looper; import android.os.VibrationEffect; import android.os.Vibrator; import android.provider.Settings; import android.view.MotionEvent; import android.view.View; import android.view.WindowManager; import android.view.animation.Interpolator; import com.android.launcher3.BaseDraggingActivity; import com.android.launcher3.DeviceProfile; import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.R; import com.android.launcher3.Utilities; import com.android.launcher3.anim.AnimationSuccessListener; import com.android.launcher3.anim.AnimatorPlaybackController; import com.android.launcher3.anim.Interpolators; import com.android.launcher3.graphics.RotationMode; import com.android.launcher3.views.FloatingIconView; import com.android.quickstep.ActivityControlHelper.ActivityInitListener; import com.android.quickstep.ActivityControlHelper.HomeAnimationFactory; import com.android.quickstep.SysUINavigationMode.Mode; import com.android.quickstep.inputconsumers.InputConsumer; import com.android.quickstep.util.ClipAnimationHelper; import com.android.quickstep.util.ClipAnimationHelper.TransformParams; import com.android.quickstep.util.RectFSpringAnim; import com.android.quickstep.util.RemoteAnimationTargetSet; import com.android.quickstep.util.SwipeAnimationTargetSet; import com.android.quickstep.util.SwipeAnimationTargetSet.SwipeAnimationListener; import com.android.quickstep.views.RecentsView; import com.android.quickstep.views.TaskView; import com.android.systemui.shared.system.InputConsumerController; import com.android.systemui.shared.system.RemoteAnimationTargetCompat; import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat; import java.util.function.Consumer; import androidx.annotation.UiThread; /** * Base class for swipe up handler with some utility methods */ @TargetApi(Build.VERSION_CODES.Q) public abstract class BaseSwipeUpHandler<T extends BaseDraggingActivity, Q extends RecentsView> implements SwipeAnimationListener { private static final String TAG = "BaseSwipeUpHandler"; protected static final Rect TEMP_RECT = new Rect(); // Start resisting when swiping past this factor of mTransitionDragLength. private static final float DRAG_LENGTH_FACTOR_START_PULLBACK = 1.4f; // This is how far down we can scale down, where 0f is full screen and 1f is recents. private static final float DRAG_LENGTH_FACTOR_MAX_PULLBACK = 1.8f; private static final Interpolator PULLBACK_INTERPOLATOR = DEACCEL; // The distance needed to drag to reach the task size in recents. protected int mTransitionDragLength; // How much further we can drag past recents, as a factor of mTransitionDragLength. protected float mDragLengthFactor = 1; protected final Context mContext; protected final OverviewComponentObserver mOverviewComponentObserver; protected final ActivityControlHelper<T> mActivityControlHelper; protected final RecentsModel mRecentsModel; protected final int mRunningTaskId; protected final ClipAnimationHelper mClipAnimationHelper; protected final TransformParams mTransformParams = new TransformParams(); private final Vibrator mVibrator; protected final Mode mMode; // 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. protected final AnimatedFloat mCurrentShift = new AnimatedFloat(this::updateFinalShift); protected final ActivityInitListener mActivityInitListener; protected final RecentsAnimationWrapper mRecentsAnimationWrapper; protected T mActivity; protected Q mRecentsView; protected DeviceProfile mDp; private final int mPageSpacing; protected Runnable mGestureEndCallback; protected final Handler mMainThreadHandler = MAIN_THREAD_EXECUTOR.getHandler(); protected MultiStateCallback mStateCallback; protected boolean mCanceled; protected int mFinishingRecentsAnimationForNewTaskId = -1; protected BaseSwipeUpHandler(Context context, OverviewComponentObserver overviewComponentObserver, RecentsModel recentsModel, InputConsumerController inputConsumer, int runningTaskId) { mContext = context; mOverviewComponentObserver = overviewComponentObserver; mActivityControlHelper = overviewComponentObserver.getActivityControlHelper(); mRecentsModel = recentsModel; mActivityInitListener = mActivityControlHelper.createActivityInitListener(this::onActivityInit); mRunningTaskId = runningTaskId; mRecentsAnimationWrapper = new RecentsAnimationWrapper(inputConsumer, this::createNewInputProxyHandler); mMode = SysUINavigationMode.getMode(context); mClipAnimationHelper = new ClipAnimationHelper(context); mPageSpacing = context.getResources().getDimensionPixelSize(R.dimen.recents_page_spacing); mVibrator = context.getSystemService(Vibrator.class); initTransitionEndpoints(InvariantDeviceProfile.INSTANCE.get(mContext) .getDeviceProfile(mContext)); } protected void setStateOnUiThread(int stateFlag) { if (Looper.myLooper() == mMainThreadHandler.getLooper()) { mStateCallback.setState(stateFlag); } else { postAsyncCallback(mMainThreadHandler, () -> mStateCallback.setState(stateFlag)); } } protected void performHapticFeedback() { if (!mVibrator.hasVibrator()) { return; } if (Settings.System.getInt( mContext.getContentResolver(), Settings.System.HAPTIC_FEEDBACK_ENABLED, 0) == 0) { return; } VibrationEffect effect = createPredefined(EFFECT_CLICK); if (effect == null) { return; } BACKGROUND_EXECUTOR.execute(() -> mVibrator.vibrate(effect)); } public Consumer<MotionEvent> getRecentsViewDispatcher(RotationMode rotationMode) { return mRecentsView != null ? mRecentsView.getEventDispatcher(rotationMode) : null; } @UiThread public void updateDisplacement(float displacement) { // We are moving in the negative x/y direction displacement = -displacement; float shift; if (displacement > mTransitionDragLength * mDragLengthFactor && mTransitionDragLength > 0) { shift = mDragLengthFactor; } else { float translation = Math.max(displacement, 0); shift = mTransitionDragLength == 0 ? 0 : translation / mTransitionDragLength; if (shift > DRAG_LENGTH_FACTOR_START_PULLBACK) { float pullbackProgress = Utilities.getProgress(shift, DRAG_LENGTH_FACTOR_START_PULLBACK, mDragLengthFactor); pullbackProgress = PULLBACK_INTERPOLATOR.getInterpolation(pullbackProgress); shift = DRAG_LENGTH_FACTOR_START_PULLBACK + pullbackProgress * (DRAG_LENGTH_FACTOR_MAX_PULLBACK - DRAG_LENGTH_FACTOR_START_PULLBACK); } } mCurrentShift.updateValue(shift); } public void setGestureEndCallback(Runnable gestureEndCallback) { mGestureEndCallback = gestureEndCallback; } public abstract Intent getLaunchIntent(); protected void linkRecentsViewScroll() { SyncRtSurfaceTransactionApplierCompat.create(mRecentsView, applier -> { mTransformParams.setSyncTransactionApplier(applier); mRecentsAnimationWrapper.runOnInit(() -> mRecentsAnimationWrapper.targetSet.addDependentTransactionApplier(applier)); }); mRecentsView.setOnScrollChangeListener((v, scrollX, scrollY, oldScrollX, oldScrollY) -> { if (moveWindowWithRecentsScroll()) { updateFinalShift(); } }); mRecentsView.setRecentsAnimationWrapper(mRecentsAnimationWrapper); mRecentsView.setClipAnimationHelper(mClipAnimationHelper); } protected void startNewTask(int successStateFlag, Consumer<Boolean> resultCallback) { // Launch the task user scrolled to (mRecentsView.getNextPage()). if (ENABLE_QUICKSTEP_LIVE_TILE.get()) { // We finish recents animation inside launchTask() when live tile is enabled. mRecentsView.getTaskViewAt(mRecentsView.getNextPage()).launchTask(false /* animate */, true /* freezeTaskList */); } else { int taskId = mRecentsView.getTaskViewAt(mRecentsView.getNextPage()).getTask().key.id; mFinishingRecentsAnimationForNewTaskId = taskId; mRecentsAnimationWrapper.finish(true /* toRecents */, () -> { if (!mCanceled) { TaskView nextTask = mRecentsView.getTaskView(taskId); if (nextTask != null) { nextTask.launchTask(false /* animate */, true /* freezeTaskList */, success -> { resultCallback.accept(success); if (!success) { mActivityControlHelper.onLaunchTaskFailed(mActivity); nextTask.notifyTaskLaunchFailed(TAG); } else { mActivityControlHelper.onLaunchTaskSuccess(mActivity); } }, mMainThreadHandler); } setStateOnUiThread(successStateFlag); } mCanceled = false; mFinishingRecentsAnimationForNewTaskId = -1; }); } TOUCH_INTERACTION_LOG.addLog("finishRecentsAnimation", true); } @Override public void onRecentsAnimationStart(SwipeAnimationTargetSet targetSet) { DeviceProfile dp = InvariantDeviceProfile.INSTANCE.get(mContext).getDeviceProfile(mContext); final Rect overviewStackBounds; RemoteAnimationTargetCompat runningTaskTarget = targetSet.findTask(mRunningTaskId); if (targetSet.minimizedHomeBounds != null && runningTaskTarget != null) { overviewStackBounds = mActivityControlHelper .getOverviewWindowBounds(targetSet.minimizedHomeBounds, runningTaskTarget); dp = dp.getMultiWindowProfile(mContext, new Point( overviewStackBounds.width(), overviewStackBounds.height())); } else { // If we are not in multi-window mode, home insets should be same as system insets. dp = dp.copy(mContext); overviewStackBounds = getStackBounds(dp); } dp.updateInsets(targetSet.homeContentInsets); dp.updateIsSeascape(mContext.getSystemService(WindowManager.class)); if (runningTaskTarget != null) { mClipAnimationHelper.updateSource(overviewStackBounds, runningTaskTarget); } mClipAnimationHelper.prepareAnimation(dp, false /* isOpening */); initTransitionEndpoints(dp); mRecentsAnimationWrapper.setController(targetSet); } private Rect getStackBounds(DeviceProfile dp) { if (mActivity != null) { int loc[] = new int[2]; View rootView = mActivity.getRootView(); rootView.getLocationOnScreen(loc); return new Rect(loc[0], loc[1], loc[0] + rootView.getWidth(), loc[1] + rootView.getHeight()); } else { return new Rect(0, 0, dp.widthPx, dp.heightPx); } } protected void initTransitionEndpoints(DeviceProfile dp) { mDp = dp; mTransitionDragLength = mActivityControlHelper.getSwipeUpDestinationAndLength( dp, mContext, TEMP_RECT); if (!dp.isMultiWindowMode) { // When updating the target rect, also update the home bounds since the location on // screen of the launcher window may be stale (position is not updated until first // traversal after the window is resized). We only do this for non-multiwindow because // we otherwise use the minimized home bounds provided by the system. mClipAnimationHelper.updateHomeBounds(getStackBounds(dp)); } mClipAnimationHelper.updateTargetRect(TEMP_RECT); if (mMode == Mode.NO_BUTTON) { // We can drag all the way to the top of the screen. mDragLengthFactor = (float) dp.heightPx / mTransitionDragLength; } } /** * Return true if the window should be translated horizontally if the recents view scrolls */ protected abstract boolean moveWindowWithRecentsScroll(); protected abstract boolean onActivityInit(final T activity, Boolean alreadyOnHome); /** * Called to create a input proxy for the running task */ @UiThread protected abstract InputConsumer createNewInputProxyHandler(); /** * Called when the value of {@link #mCurrentShift} changes */ @UiThread public abstract void updateFinalShift(); /** * Called when motion pause is detected */ public abstract void onMotionPauseChanged(boolean isPaused); @UiThread public void onGestureStarted() { } @UiThread public abstract void onGestureCancelled(); @UiThread public abstract void onGestureEnded(float endVelocity, PointF velocity, PointF downPos); public abstract void onConsumerAboutToBeSwitched(SwipeSharedState sharedState); public void setIsLikelyToStartNewTask(boolean isLikelyToStartNewTask) { } public void initWhenReady() { // Preload the plan mRecentsModel.getTasks(null); mActivityInitListener.register(); } /** * Applies the transform on the recents animation without any additional null checks */ protected void applyTransformUnchecked() { float shift = mCurrentShift.value; float offsetX = mRecentsView == null ? 0 : mRecentsView.getScrollOffset(); float offsetScale = getTaskCurveScaleForOffsetX(offsetX, mClipAnimationHelper.getTargetRect().width()); mTransformParams.setProgress(shift).setOffsetX(offsetX).setOffsetScale(offsetScale); mClipAnimationHelper.applyTransform(mRecentsAnimationWrapper.targetSet, mTransformParams); } private float getTaskCurveScaleForOffsetX(float offsetX, float taskWidth) { float distanceToReachEdge = mDp.widthPx / 2 + taskWidth / 2 + mPageSpacing; float interpolation = Math.min(1, offsetX / distanceToReachEdge); return TaskView.getCurveScaleForInterpolation(interpolation); } /** * Creates an animation that transforms the current app window into the home app. * @param startProgress The progress of {@link #mCurrentShift} to start the window from. * @param homeAnimationFactory The home animation factory. */ protected RectFSpringAnim createWindowAnimationToHome(float startProgress, HomeAnimationFactory homeAnimationFactory) { final RemoteAnimationTargetSet targetSet = mRecentsAnimationWrapper.targetSet; final RectF startRect = new RectF(mClipAnimationHelper.applyTransform(targetSet, mTransformParams.setProgress(startProgress), false /* launcherOnTop */)); final RectF targetRect = homeAnimationFactory.getWindowTargetRect(); final View floatingView = homeAnimationFactory.getFloatingView(); final boolean isFloatingIconView = floatingView instanceof FloatingIconView; RectFSpringAnim anim = new RectFSpringAnim(startRect, targetRect, mContext.getResources()); if (isFloatingIconView) { FloatingIconView fiv = (FloatingIconView) floatingView; anim.addAnimatorListener(fiv); fiv.setOnTargetChangeListener(anim::onTargetPositionChanged); } AnimatorPlaybackController homeAnim = homeAnimationFactory.createActivityAnimationToHome(); // End on a "round-enough" radius so that the shape reveal doesn't have to do too much // rounding at the end of the animation. float startRadius = mClipAnimationHelper.getCurrentCornerRadius(); float endRadius = startRect.width() / 6f; // We want the window alpha to be 0 once this threshold is met, so that the // FolderIconView can be seen morphing into the icon shape. final float windowAlphaThreshold = isFloatingIconView ? 1f - SHAPE_PROGRESS_DURATION : 1f; anim.addOnUpdateListener(new RectFSpringAnim.OnUpdateListener() { @Override public void onUpdate(RectF currentRect, float progress) { homeAnim.setPlayFraction(progress); float alphaProgress = ACCEL_1_5.getInterpolation(progress); float windowAlpha = Utilities.boundToRange(Utilities.mapToRange(alphaProgress, 0, windowAlphaThreshold, 1.5f, 0f, Interpolators.LINEAR), 0, 1); mTransformParams.setProgress(progress) .setCurrentRectAndTargetAlpha(currentRect, windowAlpha); if (isFloatingIconView) { mTransformParams.setCornerRadius(endRadius * progress + startRadius * (1f - progress)); } mClipAnimationHelper.applyTransform(targetSet, mTransformParams, false /* launcherOnTop */); if (isFloatingIconView) { ((FloatingIconView) floatingView).update(currentRect, 1f, progress, windowAlphaThreshold, mClipAnimationHelper.getCurrentCornerRadius(), false); } } @Override public void onCancel() { if (isFloatingIconView) { ((FloatingIconView) floatingView).fastFinish(); } } }); anim.addAnimatorListener(new AnimationSuccessListener() { @Override public void onAnimationStart(Animator animation) { homeAnim.dispatchOnStart(); } @Override public void onAnimationSuccess(Animator animator) { homeAnim.getAnimationPlayer().end(); } }); return anim; } public interface Factory { BaseSwipeUpHandler newHandler(RunningTaskInfo runningTask, long touchTimeMs, boolean continuingLastGesture, boolean isLikelyToStartNewTask); } protected interface RunningWindowAnim { void end(); void cancel(); static RunningWindowAnim wrap(Animator animator) { return new RunningWindowAnim() { @Override public void end() { animator.end(); } @Override public void cancel() { animator.cancel(); } }; } static RunningWindowAnim wrap(RectFSpringAnim rectFSpringAnim) { return new RunningWindowAnim() { @Override public void end() { rectFSpringAnim.end(); } @Override public void cancel() { rectFSpringAnim.cancel(); } }; } } } quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java +1 −10 Original line number Diff line number Diff line Loading @@ -151,16 +151,10 @@ public final class LauncherActivityControllerHelper implements ActivityControlHe @NonNull @Override public RectF getWindowTargetRect() { final int halfIconSize = dp.iconSizePx / 2; final float targetCenterX = dp.availableWidthPx / 2f; final float targetCenterY = dp.availableHeightPx - dp.hotseatBarSizePx; if (canUseWorkspaceView) { return iconLocation; } else { // Fallback to animate to center of screen. return new RectF(targetCenterX - halfIconSize, targetCenterY - halfIconSize, targetCenterX + halfIconSize, targetCenterY + halfIconSize); return HomeAnimationFactory.getDefaultWindowTargetRect(dp); } } Loading Loading @@ -194,9 +188,6 @@ public final class LauncherActivityControllerHelper implements ActivityControlHe @Override public AnimationFactory prepareRecentsUI(Launcher activity, boolean activityVisible, boolean animateActivity, Consumer<AnimatorPlaybackController> callback) { if (TestProtocol.sDebugTracing) { Log.d(TestProtocol.NO_OVERVIEW_EVENT_TAG, "prepareRecentsUI"); } final LauncherState startState = activity.getStateManager().getState(); LauncherState resetState = startState; Loading Loading
go/quickstep/src/com/android/quickstep/TouchInteractionService.java +9 −0 Original line number Diff line number Diff line Loading @@ -34,6 +34,8 @@ import android.view.MotionEvent; import com.android.launcher3.Utilities; import com.android.launcher3.compat.UserManagerCompat; import com.android.launcher3.util.LooperExecutor; import com.android.launcher3.util.UiThreadHelper; import com.android.systemui.shared.recents.IOverviewProxy; import com.android.systemui.shared.recents.ISystemUiProxy; Loading Loading @@ -137,6 +139,9 @@ public class TouchInteractionService extends Service { return sConnected; } public static final LooperExecutor BACKGROUND_EXECUTOR = new LooperExecutor(UiThreadHelper.getBackgroundLooper()); private RecentsModel mRecentsModel; private OverviewComponentObserver mOverviewComponentObserver; private OverviewCommandHelper mOverviewCommandHelper; Loading Loading @@ -180,4 +185,8 @@ public class TouchInteractionService extends Service { } return mMyBinder; } public static boolean isInputMonitorInitialized() { return true; } }
quickstep/AndroidManifest.xml +1 −0 Original line number Diff line number Diff line Loading @@ -23,6 +23,7 @@ package="com.android.launcher3" > <uses-permission android:name="android.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS" /> <uses-permission android:name="android.permission.VIBRATE" /> <application android:backupAgent="com.android.launcher3.LauncherBackupAgent" Loading
quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java +15 −0 Original line number Diff line number Diff line Loading @@ -167,6 +167,21 @@ public class FlingAndHoldTouchController extends PortraitStatesTouchController { mMotionPauseDetector.clear(); } @Override protected void goToTargetState(LauncherState targetState, int logAction) { if (mPeekAnim != null && mPeekAnim.isStarted()) { // Don't jump to the target state until overview is no longer peeking. mPeekAnim.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { FlingAndHoldTouchController.super.goToTargetState(targetState, logAction); } }); } else { super.goToTargetState(targetState, logAction); } } @Override protected void updateAnimatorBuilderOnReinit(AnimatorSetBuilder builder) { if (handlingOverviewAnim()) { Loading
quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java 0 → 100644 +496 −0 Original line number Diff line number Diff line /* * Copyright (C) 2019 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.os.VibrationEffect.EFFECT_CLICK; import static android.os.VibrationEffect.createPredefined; import static com.android.launcher3.Utilities.postAsyncCallback; import static com.android.launcher3.anim.Interpolators.ACCEL_1_5; import static com.android.launcher3.anim.Interpolators.DEACCEL; import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE; import static com.android.launcher3.views.FloatingIconView.SHAPE_PROGRESS_DURATION; import static com.android.quickstep.TouchInteractionService.BACKGROUND_EXECUTOR; import static com.android.quickstep.TouchInteractionService.MAIN_THREAD_EXECUTOR; import static com.android.quickstep.TouchInteractionService.TOUCH_INTERACTION_LOG; import android.animation.Animator; import android.annotation.TargetApi; import android.app.ActivityManager.RunningTaskInfo; import android.content.Context; import android.content.Intent; import android.graphics.Point; import android.graphics.PointF; import android.graphics.Rect; import android.graphics.RectF; import android.os.Build; import android.os.Handler; import android.os.Looper; import android.os.VibrationEffect; import android.os.Vibrator; import android.provider.Settings; import android.view.MotionEvent; import android.view.View; import android.view.WindowManager; import android.view.animation.Interpolator; import com.android.launcher3.BaseDraggingActivity; import com.android.launcher3.DeviceProfile; import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.R; import com.android.launcher3.Utilities; import com.android.launcher3.anim.AnimationSuccessListener; import com.android.launcher3.anim.AnimatorPlaybackController; import com.android.launcher3.anim.Interpolators; import com.android.launcher3.graphics.RotationMode; import com.android.launcher3.views.FloatingIconView; import com.android.quickstep.ActivityControlHelper.ActivityInitListener; import com.android.quickstep.ActivityControlHelper.HomeAnimationFactory; import com.android.quickstep.SysUINavigationMode.Mode; import com.android.quickstep.inputconsumers.InputConsumer; import com.android.quickstep.util.ClipAnimationHelper; import com.android.quickstep.util.ClipAnimationHelper.TransformParams; import com.android.quickstep.util.RectFSpringAnim; import com.android.quickstep.util.RemoteAnimationTargetSet; import com.android.quickstep.util.SwipeAnimationTargetSet; import com.android.quickstep.util.SwipeAnimationTargetSet.SwipeAnimationListener; import com.android.quickstep.views.RecentsView; import com.android.quickstep.views.TaskView; import com.android.systemui.shared.system.InputConsumerController; import com.android.systemui.shared.system.RemoteAnimationTargetCompat; import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat; import java.util.function.Consumer; import androidx.annotation.UiThread; /** * Base class for swipe up handler with some utility methods */ @TargetApi(Build.VERSION_CODES.Q) public abstract class BaseSwipeUpHandler<T extends BaseDraggingActivity, Q extends RecentsView> implements SwipeAnimationListener { private static final String TAG = "BaseSwipeUpHandler"; protected static final Rect TEMP_RECT = new Rect(); // Start resisting when swiping past this factor of mTransitionDragLength. private static final float DRAG_LENGTH_FACTOR_START_PULLBACK = 1.4f; // This is how far down we can scale down, where 0f is full screen and 1f is recents. private static final float DRAG_LENGTH_FACTOR_MAX_PULLBACK = 1.8f; private static final Interpolator PULLBACK_INTERPOLATOR = DEACCEL; // The distance needed to drag to reach the task size in recents. protected int mTransitionDragLength; // How much further we can drag past recents, as a factor of mTransitionDragLength. protected float mDragLengthFactor = 1; protected final Context mContext; protected final OverviewComponentObserver mOverviewComponentObserver; protected final ActivityControlHelper<T> mActivityControlHelper; protected final RecentsModel mRecentsModel; protected final int mRunningTaskId; protected final ClipAnimationHelper mClipAnimationHelper; protected final TransformParams mTransformParams = new TransformParams(); private final Vibrator mVibrator; protected final Mode mMode; // 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. protected final AnimatedFloat mCurrentShift = new AnimatedFloat(this::updateFinalShift); protected final ActivityInitListener mActivityInitListener; protected final RecentsAnimationWrapper mRecentsAnimationWrapper; protected T mActivity; protected Q mRecentsView; protected DeviceProfile mDp; private final int mPageSpacing; protected Runnable mGestureEndCallback; protected final Handler mMainThreadHandler = MAIN_THREAD_EXECUTOR.getHandler(); protected MultiStateCallback mStateCallback; protected boolean mCanceled; protected int mFinishingRecentsAnimationForNewTaskId = -1; protected BaseSwipeUpHandler(Context context, OverviewComponentObserver overviewComponentObserver, RecentsModel recentsModel, InputConsumerController inputConsumer, int runningTaskId) { mContext = context; mOverviewComponentObserver = overviewComponentObserver; mActivityControlHelper = overviewComponentObserver.getActivityControlHelper(); mRecentsModel = recentsModel; mActivityInitListener = mActivityControlHelper.createActivityInitListener(this::onActivityInit); mRunningTaskId = runningTaskId; mRecentsAnimationWrapper = new RecentsAnimationWrapper(inputConsumer, this::createNewInputProxyHandler); mMode = SysUINavigationMode.getMode(context); mClipAnimationHelper = new ClipAnimationHelper(context); mPageSpacing = context.getResources().getDimensionPixelSize(R.dimen.recents_page_spacing); mVibrator = context.getSystemService(Vibrator.class); initTransitionEndpoints(InvariantDeviceProfile.INSTANCE.get(mContext) .getDeviceProfile(mContext)); } protected void setStateOnUiThread(int stateFlag) { if (Looper.myLooper() == mMainThreadHandler.getLooper()) { mStateCallback.setState(stateFlag); } else { postAsyncCallback(mMainThreadHandler, () -> mStateCallback.setState(stateFlag)); } } protected void performHapticFeedback() { if (!mVibrator.hasVibrator()) { return; } if (Settings.System.getInt( mContext.getContentResolver(), Settings.System.HAPTIC_FEEDBACK_ENABLED, 0) == 0) { return; } VibrationEffect effect = createPredefined(EFFECT_CLICK); if (effect == null) { return; } BACKGROUND_EXECUTOR.execute(() -> mVibrator.vibrate(effect)); } public Consumer<MotionEvent> getRecentsViewDispatcher(RotationMode rotationMode) { return mRecentsView != null ? mRecentsView.getEventDispatcher(rotationMode) : null; } @UiThread public void updateDisplacement(float displacement) { // We are moving in the negative x/y direction displacement = -displacement; float shift; if (displacement > mTransitionDragLength * mDragLengthFactor && mTransitionDragLength > 0) { shift = mDragLengthFactor; } else { float translation = Math.max(displacement, 0); shift = mTransitionDragLength == 0 ? 0 : translation / mTransitionDragLength; if (shift > DRAG_LENGTH_FACTOR_START_PULLBACK) { float pullbackProgress = Utilities.getProgress(shift, DRAG_LENGTH_FACTOR_START_PULLBACK, mDragLengthFactor); pullbackProgress = PULLBACK_INTERPOLATOR.getInterpolation(pullbackProgress); shift = DRAG_LENGTH_FACTOR_START_PULLBACK + pullbackProgress * (DRAG_LENGTH_FACTOR_MAX_PULLBACK - DRAG_LENGTH_FACTOR_START_PULLBACK); } } mCurrentShift.updateValue(shift); } public void setGestureEndCallback(Runnable gestureEndCallback) { mGestureEndCallback = gestureEndCallback; } public abstract Intent getLaunchIntent(); protected void linkRecentsViewScroll() { SyncRtSurfaceTransactionApplierCompat.create(mRecentsView, applier -> { mTransformParams.setSyncTransactionApplier(applier); mRecentsAnimationWrapper.runOnInit(() -> mRecentsAnimationWrapper.targetSet.addDependentTransactionApplier(applier)); }); mRecentsView.setOnScrollChangeListener((v, scrollX, scrollY, oldScrollX, oldScrollY) -> { if (moveWindowWithRecentsScroll()) { updateFinalShift(); } }); mRecentsView.setRecentsAnimationWrapper(mRecentsAnimationWrapper); mRecentsView.setClipAnimationHelper(mClipAnimationHelper); } protected void startNewTask(int successStateFlag, Consumer<Boolean> resultCallback) { // Launch the task user scrolled to (mRecentsView.getNextPage()). if (ENABLE_QUICKSTEP_LIVE_TILE.get()) { // We finish recents animation inside launchTask() when live tile is enabled. mRecentsView.getTaskViewAt(mRecentsView.getNextPage()).launchTask(false /* animate */, true /* freezeTaskList */); } else { int taskId = mRecentsView.getTaskViewAt(mRecentsView.getNextPage()).getTask().key.id; mFinishingRecentsAnimationForNewTaskId = taskId; mRecentsAnimationWrapper.finish(true /* toRecents */, () -> { if (!mCanceled) { TaskView nextTask = mRecentsView.getTaskView(taskId); if (nextTask != null) { nextTask.launchTask(false /* animate */, true /* freezeTaskList */, success -> { resultCallback.accept(success); if (!success) { mActivityControlHelper.onLaunchTaskFailed(mActivity); nextTask.notifyTaskLaunchFailed(TAG); } else { mActivityControlHelper.onLaunchTaskSuccess(mActivity); } }, mMainThreadHandler); } setStateOnUiThread(successStateFlag); } mCanceled = false; mFinishingRecentsAnimationForNewTaskId = -1; }); } TOUCH_INTERACTION_LOG.addLog("finishRecentsAnimation", true); } @Override public void onRecentsAnimationStart(SwipeAnimationTargetSet targetSet) { DeviceProfile dp = InvariantDeviceProfile.INSTANCE.get(mContext).getDeviceProfile(mContext); final Rect overviewStackBounds; RemoteAnimationTargetCompat runningTaskTarget = targetSet.findTask(mRunningTaskId); if (targetSet.minimizedHomeBounds != null && runningTaskTarget != null) { overviewStackBounds = mActivityControlHelper .getOverviewWindowBounds(targetSet.minimizedHomeBounds, runningTaskTarget); dp = dp.getMultiWindowProfile(mContext, new Point( overviewStackBounds.width(), overviewStackBounds.height())); } else { // If we are not in multi-window mode, home insets should be same as system insets. dp = dp.copy(mContext); overviewStackBounds = getStackBounds(dp); } dp.updateInsets(targetSet.homeContentInsets); dp.updateIsSeascape(mContext.getSystemService(WindowManager.class)); if (runningTaskTarget != null) { mClipAnimationHelper.updateSource(overviewStackBounds, runningTaskTarget); } mClipAnimationHelper.prepareAnimation(dp, false /* isOpening */); initTransitionEndpoints(dp); mRecentsAnimationWrapper.setController(targetSet); } private Rect getStackBounds(DeviceProfile dp) { if (mActivity != null) { int loc[] = new int[2]; View rootView = mActivity.getRootView(); rootView.getLocationOnScreen(loc); return new Rect(loc[0], loc[1], loc[0] + rootView.getWidth(), loc[1] + rootView.getHeight()); } else { return new Rect(0, 0, dp.widthPx, dp.heightPx); } } protected void initTransitionEndpoints(DeviceProfile dp) { mDp = dp; mTransitionDragLength = mActivityControlHelper.getSwipeUpDestinationAndLength( dp, mContext, TEMP_RECT); if (!dp.isMultiWindowMode) { // When updating the target rect, also update the home bounds since the location on // screen of the launcher window may be stale (position is not updated until first // traversal after the window is resized). We only do this for non-multiwindow because // we otherwise use the minimized home bounds provided by the system. mClipAnimationHelper.updateHomeBounds(getStackBounds(dp)); } mClipAnimationHelper.updateTargetRect(TEMP_RECT); if (mMode == Mode.NO_BUTTON) { // We can drag all the way to the top of the screen. mDragLengthFactor = (float) dp.heightPx / mTransitionDragLength; } } /** * Return true if the window should be translated horizontally if the recents view scrolls */ protected abstract boolean moveWindowWithRecentsScroll(); protected abstract boolean onActivityInit(final T activity, Boolean alreadyOnHome); /** * Called to create a input proxy for the running task */ @UiThread protected abstract InputConsumer createNewInputProxyHandler(); /** * Called when the value of {@link #mCurrentShift} changes */ @UiThread public abstract void updateFinalShift(); /** * Called when motion pause is detected */ public abstract void onMotionPauseChanged(boolean isPaused); @UiThread public void onGestureStarted() { } @UiThread public abstract void onGestureCancelled(); @UiThread public abstract void onGestureEnded(float endVelocity, PointF velocity, PointF downPos); public abstract void onConsumerAboutToBeSwitched(SwipeSharedState sharedState); public void setIsLikelyToStartNewTask(boolean isLikelyToStartNewTask) { } public void initWhenReady() { // Preload the plan mRecentsModel.getTasks(null); mActivityInitListener.register(); } /** * Applies the transform on the recents animation without any additional null checks */ protected void applyTransformUnchecked() { float shift = mCurrentShift.value; float offsetX = mRecentsView == null ? 0 : mRecentsView.getScrollOffset(); float offsetScale = getTaskCurveScaleForOffsetX(offsetX, mClipAnimationHelper.getTargetRect().width()); mTransformParams.setProgress(shift).setOffsetX(offsetX).setOffsetScale(offsetScale); mClipAnimationHelper.applyTransform(mRecentsAnimationWrapper.targetSet, mTransformParams); } private float getTaskCurveScaleForOffsetX(float offsetX, float taskWidth) { float distanceToReachEdge = mDp.widthPx / 2 + taskWidth / 2 + mPageSpacing; float interpolation = Math.min(1, offsetX / distanceToReachEdge); return TaskView.getCurveScaleForInterpolation(interpolation); } /** * Creates an animation that transforms the current app window into the home app. * @param startProgress The progress of {@link #mCurrentShift} to start the window from. * @param homeAnimationFactory The home animation factory. */ protected RectFSpringAnim createWindowAnimationToHome(float startProgress, HomeAnimationFactory homeAnimationFactory) { final RemoteAnimationTargetSet targetSet = mRecentsAnimationWrapper.targetSet; final RectF startRect = new RectF(mClipAnimationHelper.applyTransform(targetSet, mTransformParams.setProgress(startProgress), false /* launcherOnTop */)); final RectF targetRect = homeAnimationFactory.getWindowTargetRect(); final View floatingView = homeAnimationFactory.getFloatingView(); final boolean isFloatingIconView = floatingView instanceof FloatingIconView; RectFSpringAnim anim = new RectFSpringAnim(startRect, targetRect, mContext.getResources()); if (isFloatingIconView) { FloatingIconView fiv = (FloatingIconView) floatingView; anim.addAnimatorListener(fiv); fiv.setOnTargetChangeListener(anim::onTargetPositionChanged); } AnimatorPlaybackController homeAnim = homeAnimationFactory.createActivityAnimationToHome(); // End on a "round-enough" radius so that the shape reveal doesn't have to do too much // rounding at the end of the animation. float startRadius = mClipAnimationHelper.getCurrentCornerRadius(); float endRadius = startRect.width() / 6f; // We want the window alpha to be 0 once this threshold is met, so that the // FolderIconView can be seen morphing into the icon shape. final float windowAlphaThreshold = isFloatingIconView ? 1f - SHAPE_PROGRESS_DURATION : 1f; anim.addOnUpdateListener(new RectFSpringAnim.OnUpdateListener() { @Override public void onUpdate(RectF currentRect, float progress) { homeAnim.setPlayFraction(progress); float alphaProgress = ACCEL_1_5.getInterpolation(progress); float windowAlpha = Utilities.boundToRange(Utilities.mapToRange(alphaProgress, 0, windowAlphaThreshold, 1.5f, 0f, Interpolators.LINEAR), 0, 1); mTransformParams.setProgress(progress) .setCurrentRectAndTargetAlpha(currentRect, windowAlpha); if (isFloatingIconView) { mTransformParams.setCornerRadius(endRadius * progress + startRadius * (1f - progress)); } mClipAnimationHelper.applyTransform(targetSet, mTransformParams, false /* launcherOnTop */); if (isFloatingIconView) { ((FloatingIconView) floatingView).update(currentRect, 1f, progress, windowAlphaThreshold, mClipAnimationHelper.getCurrentCornerRadius(), false); } } @Override public void onCancel() { if (isFloatingIconView) { ((FloatingIconView) floatingView).fastFinish(); } } }); anim.addAnimatorListener(new AnimationSuccessListener() { @Override public void onAnimationStart(Animator animation) { homeAnim.dispatchOnStart(); } @Override public void onAnimationSuccess(Animator animator) { homeAnim.getAnimationPlayer().end(); } }); return anim; } public interface Factory { BaseSwipeUpHandler newHandler(RunningTaskInfo runningTask, long touchTimeMs, boolean continuingLastGesture, boolean isLikelyToStartNewTask); } protected interface RunningWindowAnim { void end(); void cancel(); static RunningWindowAnim wrap(Animator animator) { return new RunningWindowAnim() { @Override public void end() { animator.end(); } @Override public void cancel() { animator.cancel(); } }; } static RunningWindowAnim wrap(RectFSpringAnim rectFSpringAnim) { return new RunningWindowAnim() { @Override public void end() { rectFSpringAnim.end(); } @Override public void cancel() { rectFSpringAnim.cancel(); } }; } } }
quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java +1 −10 Original line number Diff line number Diff line Loading @@ -151,16 +151,10 @@ public final class LauncherActivityControllerHelper implements ActivityControlHe @NonNull @Override public RectF getWindowTargetRect() { final int halfIconSize = dp.iconSizePx / 2; final float targetCenterX = dp.availableWidthPx / 2f; final float targetCenterY = dp.availableHeightPx - dp.hotseatBarSizePx; if (canUseWorkspaceView) { return iconLocation; } else { // Fallback to animate to center of screen. return new RectF(targetCenterX - halfIconSize, targetCenterY - halfIconSize, targetCenterX + halfIconSize, targetCenterY + halfIconSize); return HomeAnimationFactory.getDefaultWindowTargetRect(dp); } } Loading Loading @@ -194,9 +188,6 @@ public final class LauncherActivityControllerHelper implements ActivityControlHe @Override public AnimationFactory prepareRecentsUI(Launcher activity, boolean activityVisible, boolean animateActivity, Consumer<AnimatorPlaybackController> callback) { if (TestProtocol.sDebugTracing) { Log.d(TestProtocol.NO_OVERVIEW_EVENT_TAG, "prepareRecentsUI"); } final LauncherState startState = activity.getStateManager().getState(); LauncherState resetState = startState; Loading