diff options
Diffstat (limited to 'quickstep/recents_ui_overrides/src/com/android')
15 files changed, 663 insertions, 163 deletions
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionAppTracker.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionAppTracker.java index af67e1bbb..8f1282ded 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionAppTracker.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionAppTracker.java @@ -56,7 +56,7 @@ public class PredictionAppTracker extends AppLaunchTracker { private static final int MSG_LAUNCH = 2; private static final int MSG_PREDICT = 3; - private final Context mContext; + protected final Context mContext; private final Handler mMessageHandler; // Accessed only on worker thread diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionRowView.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionRowView.java index 55f4c98e9..4a486f8e5 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionRowView.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionRowView.java @@ -28,7 +28,6 @@ import android.os.Build; import android.util.AttributeSet; import android.util.IntProperty; import android.util.Log; -import android.view.LayoutInflater; import android.view.View; import android.view.animation.Interpolator; import android.widget.LinearLayout; @@ -115,8 +114,6 @@ public class PredictionRowView extends LinearLayout implements private final AnimatedFloat mOverviewScrollFactor = new AnimatedFloat(this::updateTranslationAndAlpha); - private View mLoadingProgress; - private boolean mPredictionsEnabled = false; public PredictionRowView(@NonNull Context context) { @@ -165,7 +162,6 @@ public class PredictionRowView extends LinearLayout implements public void setup(FloatingHeaderView parent, FloatingHeaderRow[] rows, boolean tabsHidden) { mParent = parent; - setPredictionsEnabled(mPredictionUiStateManager.arePredictionsEnabled()); } private void setPredictionsEnabled(boolean predictionsEnabled) { @@ -205,7 +201,7 @@ public class PredictionRowView extends LinearLayout implements @Override public boolean hasVisibleContent() { - return mPredictionUiStateManager.arePredictionsEnabled(); + return mPredictionsEnabled; } /** @@ -241,9 +237,6 @@ public class PredictionRowView extends LinearLayout implements } private void applyPredictionApps() { - if (mLoadingProgress != null) { - removeView(mLoadingProgress); - } if (!mPredictionsEnabled) { mParent.onHeightUpdated(); return; @@ -290,15 +283,8 @@ public class PredictionRowView extends LinearLayout implements } if (predictionCount == 0) { - if (mLoadingProgress == null) { - mLoadingProgress = LayoutInflater.from(getContext()) - .inflate(R.layout.prediction_load_progress, this, false); - } - addView(mLoadingProgress); - } else { - mLoadingProgress = null; + setPredictionsEnabled(false); } - mParent.onHeightUpdated(); } @@ -342,11 +328,8 @@ public class PredictionRowView extends LinearLayout implements public void setTextAlpha(int alpha) { mIconCurrentTextAlpha = alpha; int iconColor = setColorAlphaBound(mIconTextColor, mIconCurrentTextAlpha); - - if (mLoadingProgress == null) { - for (int i = 0; i < getChildCount(); i++) { - ((BubbleTextView) getChildAt(i)).setTextColor(iconColor); - } + for (int i = 0; i < getChildCount(); i++) { + ((BubbleTextView) getChildAt(i)).setTextColor(iconColor); } } diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionUiStateManager.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionUiStateManager.java index 6dad9afe5..64cb4b465 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionUiStateManager.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionUiStateManager.java @@ -24,7 +24,6 @@ import android.app.prediction.AppPredictor; import android.app.prediction.AppTarget; import android.content.ComponentName; import android.content.Context; -import android.os.Handler; import android.view.ViewTreeObserver.OnGlobalLayoutListener; import com.android.launcher3.AppInfo; @@ -63,7 +62,6 @@ public class PredictionUiStateManager implements OnGlobalLayoutListener, ItemInf OnIDPChangeListener, OnUpdateListener { public static final String LAST_PREDICTION_ENABLED_STATE = "last_prediction_enabled_state"; - private static final long INITIAL_CALLBACK_WAIT_TIMEOUT_MS = 5000; // TODO (b/129421797): Update the client constants public enum Client { @@ -110,13 +108,8 @@ public class PredictionUiStateManager implements OnGlobalLayoutListener, ItemInf for (int i = 0; i < mPredictionServicePredictions.length; i++) { mPredictionServicePredictions[i] = Collections.emptyList(); } - mGettingValidPredictionResults = Utilities.getDevicePrefs(context) .getBoolean(LAST_PREDICTION_ENABLED_STATE, true); - if (mGettingValidPredictionResults) { - new Handler().postDelayed( - this::updatePredictionStateAfterCallback, INITIAL_CALLBACK_WAIT_TIMEOUT_MS); - } // Call this last mCurrentState = parseLastState(); @@ -293,10 +286,6 @@ public class PredictionUiStateManager implements OnGlobalLayoutListener, ItemInf dispatchOnChange(false); } - public boolean arePredictionsEnabled() { - return mCurrentState.isEnabled; - } - private boolean canApplyPredictions(PredictionState newState) { if (mAppsView == null) { // If there is no apps view, no need to schedule. diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java index 7a6cd2d55..e3e339add 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java @@ -22,6 +22,7 @@ import static com.android.launcher3.LauncherState.OVERVIEW_PEEK; import static com.android.launcher3.LauncherStateManager.ANIM_ALL; import static com.android.launcher3.LauncherStateManager.ATOMIC_OVERVIEW_PEEK_COMPONENT; import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2; +import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; @@ -34,8 +35,10 @@ import com.android.launcher3.Launcher; import com.android.launcher3.LauncherState; import com.android.launcher3.anim.AnimatorSetBuilder; import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch; +import com.android.quickstep.OverviewInteractionState; import com.android.quickstep.util.MotionPauseDetector; import com.android.quickstep.views.RecentsView; +import com.android.systemui.shared.system.QuickStepContract; /** * Touch controller which handles swipe and hold to go to Overview @@ -99,7 +102,8 @@ public class FlingAndHoldTouchController extends PortraitStatesTouchController { * having it as part of the existing animation to the target state. */ private boolean handlingOverviewAnim() { - return mStartState == NORMAL; + int stateFlags = OverviewInteractionState.INSTANCE.get(mLauncher).getSystemUiStateFlags(); + return mStartState == NORMAL && (stateFlags & SYSUI_STATE_OVERVIEW_DISABLED) == 0; } @Override diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java index e1dd124a9..18b8af4fa 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java @@ -30,6 +30,7 @@ import static com.android.launcher3.anim.Interpolators.INSTANT; import static com.android.launcher3.anim.Interpolators.LINEAR; import static com.android.launcher3.util.SystemUiController.UI_STATE_OVERVIEW; import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHOLD; +import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED; import android.view.MotionEvent; @@ -44,10 +45,12 @@ import com.android.launcher3.touch.AbstractStateChangeTouchController; import com.android.launcher3.touch.SwipeDetector; import com.android.launcher3.userevent.nano.LauncherLogProto; import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction; +import com.android.quickstep.OverviewInteractionState; import com.android.quickstep.SysUINavigationMode; import com.android.quickstep.SysUINavigationMode.Mode; import com.android.quickstep.views.RecentsView; import com.android.quickstep.views.TaskView; +import com.android.systemui.shared.system.QuickStepContract; /** * Handles quick switching to a recent task from the home screen. @@ -80,6 +83,10 @@ public class QuickSwitchTouchController extends AbstractStateChangeTouchControll @Override protected LauncherState getTargetState(LauncherState fromState, boolean isDragTowardPositive) { + int stateFlags = OverviewInteractionState.INSTANCE.get(mLauncher).getSystemUiStateFlags(); + if ((stateFlags & SYSUI_STATE_OVERVIEW_DISABLED) != 0) { + return NORMAL; + } return isDragTowardPositive ? QUICK_SWITCH : NORMAL; } diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java index 4b2e487da..5af09f7fd 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityControllerHelper.java @@ -30,6 +30,7 @@ import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; +import android.animation.TimeInterpolator; import android.content.Context; import android.graphics.Rect; import android.graphics.RectF; @@ -56,6 +57,7 @@ import com.android.launcher3.userevent.nano.LauncherLogProto; import com.android.launcher3.views.FloatingIconView; import com.android.quickstep.SysUINavigationMode.Mode; import com.android.quickstep.util.LayoutUtils; +import com.android.quickstep.util.StaggeredWorkspaceAnim; import com.android.quickstep.views.RecentsView; import com.android.quickstep.views.TaskView; import com.android.systemui.shared.system.RemoteAnimationTargetCompat; @@ -68,6 +70,8 @@ import java.util.function.Consumer; */ public final class LauncherActivityControllerHelper implements ActivityControlHelper<Launcher> { + private Runnable mAdjustInterpolatorsRunnable; + @Override public int getSwipeUpDestinationAndLength(DeviceProfile dp, Context context, Rect outRect) { LayoutUtils.calculateLauncherTaskSize(context, dp, outRect); @@ -148,8 +152,21 @@ public final class LauncherActivityControllerHelper implements ActivityControlHe @NonNull @Override public AnimatorPlaybackController createActivityAnimationToHome() { + // Return an empty APC here since we have an non-user controlled animation to home. long accuracy = 2 * Math.max(dp.widthPx, dp.heightPx); - return activity.getStateManager().createAnimationToNewWorkspace(NORMAL, accuracy); + AnimatorSet as = new AnimatorSet(); + as.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + activity.getStateManager().goToState(NORMAL, false); + } + }); + return AnimatorPlaybackController.wrap(as, accuracy); + } + + @Override + public void playAtomicAnimation(float velocity) { + new StaggeredWorkspaceAnim(activity, workspaceView, velocity).start(); } }; } @@ -194,6 +211,13 @@ public final class LauncherActivityControllerHelper implements ActivityControlHe } @Override + public void adjustActivityControllerInterpolators() { + if (mAdjustInterpolatorsRunnable != null) { + mAdjustInterpolatorsRunnable.run(); + } + } + + @Override public void onTransitionCancelled() { activity.getStateManager().goToState(startState, false /* animate */); } @@ -275,6 +299,7 @@ public final class LauncherActivityControllerHelper implements ActivityControlHe playScaleDownAnim(anim, activity, fromState, endState); anim.setDuration(transitionLength * 2); + anim.setInterpolator(LINEAR); AnimatorPlaybackController controller = AnimatorPlaybackController.wrap(anim, transitionLength * 2); activity.getStateManager().setCurrentUserControlledAnimation(controller); @@ -291,7 +316,6 @@ public final class LauncherActivityControllerHelper implements ActivityControlHe Animator shiftAnim = new SpringObjectAnimator<>(activity.getAllAppsController(), "allAppsSpringFromACH", activity.getAllAppsController().getShiftRange(), SPRING_DAMPING_RATIO, SPRING_STIFFNESS, progressValues); - shiftAnim.setInterpolator(LINEAR); return shiftAnim; } @@ -310,19 +334,37 @@ public final class LauncherActivityControllerHelper implements ActivityControlHe = fromState.getOverviewScaleAndTranslation(launcher); LauncherState.ScaleAndTranslation endScaleAndTranslation = endState.getOverviewScaleAndTranslation(launcher); + float fromTranslationY = fromScaleAndTranslation.translationY; + float endTranslationY = endScaleAndTranslation.translationY; float fromFullscreenProgress = fromState.getOverviewFullscreenProgress(); float endFullscreenProgress = endState.getOverviewFullscreenProgress(); Animator scale = ObjectAnimator.ofFloat(recentsView, SCALE_PROPERTY, fromScaleAndTranslation.scale, endScaleAndTranslation.scale); Animator translateY = ObjectAnimator.ofFloat(recentsView, TRANSLATION_Y, - fromScaleAndTranslation.translationY, endScaleAndTranslation.translationY); + fromTranslationY, endTranslationY); Animator applyFullscreenProgress = ObjectAnimator.ofFloat(recentsView, RecentsView.FULLSCREEN_PROGRESS, fromFullscreenProgress, endFullscreenProgress); - scale.setInterpolator(LINEAR); - translateY.setInterpolator(LINEAR); - applyFullscreenProgress.setInterpolator(LINEAR); anim.playTogether(scale, translateY, applyFullscreenProgress); + + mAdjustInterpolatorsRunnable = () -> { + // Adjust the translateY interpolator to account for the running task's top inset. + // When progress <= 1, this is handled by each task view as they set their fullscreen + // progress. However, once we go to progress > 1, fullscreen progress stays at 0, so + // recents as a whole needs to translate further to keep up with the app window. + TaskView runningTaskView = recentsView.getRunningTaskView(); + if (runningTaskView == null) { + runningTaskView = recentsView.getTaskViewAt(recentsView.getCurrentPage()); + } + TimeInterpolator oldInterpolator = translateY.getInterpolator(); + Rect fallbackInsets = launcher.getDeviceProfile().getInsets(); + float extraTranslationY = runningTaskView.getThumbnail().getInsets(fallbackInsets).top; + float normalizedTranslationY = extraTranslationY / (fromTranslationY - endTranslationY); + translateY.setInterpolator(t -> { + float newT = oldInterpolator.getInterpolation(t); + return newT <= 1f ? newT : newT + normalizedTranslationY * (newT - 1); + }); + }; } @Override diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/LockScreenRecentsActivity.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/LockScreenRecentsActivity.java new file mode 100644 index 000000000..65f323c7d --- /dev/null +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LockScreenRecentsActivity.java @@ -0,0 +1,31 @@ +/* + * 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 android.app.Activity; +import android.os.Bundle; + +/** + * Empty activity to start a recents transition + */ +public class LockScreenRecentsActivity extends Activity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + finish(); + } +} diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java index 0c997dd59..7563c3f55 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java @@ -22,8 +22,11 @@ import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_INP import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SYSUI_PROXY; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_CLICKABLE; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE; +import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_HOME_DISABLED; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NAV_BAR_HIDDEN; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED; +import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED; +import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING; import android.annotation.TargetApi; import android.app.ActivityManager; @@ -79,6 +82,7 @@ import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.InputChannelCompat.InputEventReceiver; import com.android.systemui.shared.system.InputConsumerController; import com.android.systemui.shared.system.InputMonitorCompat; +import com.android.systemui.shared.system.QuickStepContract; import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags; import java.io.FileDescriptor; @@ -197,6 +201,8 @@ public class TouchInteractionService extends Service implements public void onSystemUiStateChanged(int stateFlags) { mSystemUiStateFlags = stateFlags; + mOverviewInteractionState.setSystemUiStateFlags(stateFlags); + mOverviewComponentObserver.onSystemUiStateChanged(stateFlags); } /** Deprecated methods **/ @@ -331,16 +337,8 @@ public class TouchInteractionService extends Service implements defaultDisplay.getRealSize(realSize); mSwipeTouchRegion.set(0, 0, realSize.x, realSize.y); if (mMode == Mode.NO_BUTTON) { - switch (defaultDisplay.getRotation()) { - case Surface.ROTATION_90: - case Surface.ROTATION_270: - mSwipeTouchRegion.top = mSwipeTouchRegion.bottom - getNavbarSize( - ResourceUtils.NAVBAR_LANDSCAPE_BOTTOM_SIZE); - break; - default: - mSwipeTouchRegion.top = mSwipeTouchRegion.bottom - getNavbarSize( - ResourceUtils.NAVBAR_PORTRAIT_BOTTOM_SIZE); - } + mSwipeTouchRegion.top = mSwipeTouchRegion.bottom - + getNavbarSize(ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE); } else { switch (defaultDisplay.getRotation()) { case Surface.ROTATION_90: @@ -353,7 +351,7 @@ public class TouchInteractionService extends Service implements break; default: mSwipeTouchRegion.top = mSwipeTouchRegion.bottom - - getNavbarSize(ResourceUtils.NAVBAR_PORTRAIT_BOTTOM_SIZE); + - getNavbarSize(ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE); } } } @@ -472,22 +470,19 @@ public class TouchInteractionService extends Service implements private boolean validSystemUiFlags() { return (mSystemUiStateFlags & SYSUI_STATE_NAV_BAR_HIDDEN) == 0 - && (mSystemUiStateFlags & SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED) == 0; - } - - private boolean topTaskLocked() { - return ActivityManagerWrapper.getInstance().isLockToAppActive(); + && (mSystemUiStateFlags & SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED) == 0 + && ((mSystemUiStateFlags & SYSUI_STATE_HOME_DISABLED) == 0 + || (mSystemUiStateFlags & SYSUI_STATE_OVERVIEW_DISABLED) == 0); } private InputConsumer newConsumer(boolean useSharedState, MotionEvent event) { - boolean topTaskLocked = topTaskLocked(); - boolean isInValidSystemUiState = validSystemUiFlags() && !topTaskLocked; + boolean isInValidSystemUiState = validSystemUiFlags(); if (!mIsUserUnlocked) { if (isInValidSystemUiState) { // This handles apps launched in direct boot mode (e.g. dialer) as well as apps // launched while device is locked even after exiting direct boot mode (e.g. camera). - return new DeviceLockedInputConsumer(this); + return createDeviceLockedInputConsumer(mAM.getRunningTask(0)); } else { return InputConsumer.NO_OP; } @@ -498,13 +493,15 @@ public class TouchInteractionService extends Service implements if (mMode == Mode.NO_BUTTON) { final ActivityControlHelper activityControl = mOverviewComponentObserver.getActivityControlHelper(); - if (mAssistantAvailable && !topTaskLocked - && AssistantTouchConsumer.withinTouchRegion(this, event)) { + if (mAssistantAvailable + && !QuickStepContract.isAssistantGestureDisabled(mSystemUiStateFlags) + && AssistantTouchConsumer.withinTouchRegion(this, event) + && !ActivityManagerWrapper.getInstance().isLockToAppActive()) { base = new AssistantTouchConsumer(this, mISystemUiProxy, activityControl, base, mInputMonitorCompat); } - if (ActivityManagerWrapper.getInstance().isScreenPinningActive()) { + if ((mSystemUiStateFlags & SYSUI_STATE_SCREEN_PINNING) != 0) { // Note: we only allow accessibility to wrap this, and it replaces the previous // base input consumer (which should be NO_OP anyway since topTaskLocked == true). base = new ScreenPinnedInputConsumer(this, mISystemUiProxy, activityControl); @@ -520,16 +517,15 @@ public class TouchInteractionService extends Service implements } private InputConsumer newBaseConsumer(boolean useSharedState, MotionEvent event) { - if (mKM.isDeviceLocked()) { - // This handles apps launched in direct boot mode (e.g. dialer) as well as apps launched - // while device is locked even after exiting direct boot mode (e.g. camera). - return new DeviceLockedInputConsumer(this); - } - final RunningTaskInfo runningTaskInfo = mAM.getRunningTask(0); if (!useSharedState) { mSwipeSharedState.clearAllState(); } + if (mKM.isDeviceLocked()) { + // This handles apps launched in direct boot mode (e.g. dialer) as well as apps launched + // while device is locked even after exiting direct boot mode (e.g. camera). + return createDeviceLockedInputConsumer(runningTaskInfo); + } final ActivityControlHelper activityControl = mOverviewComponentObserver.getActivityControlHelper(); @@ -567,6 +563,15 @@ public class TouchInteractionService extends Service implements mSwipeSharedState, mInputMonitorCompat, mSwipeTouchRegion); } + private InputConsumer createDeviceLockedInputConsumer(RunningTaskInfo taskInfo) { + if (mMode == Mode.NO_BUTTON && taskInfo != null) { + return new DeviceLockedInputConsumer(this, mSwipeSharedState, mInputMonitorCompat, + mSwipeTouchRegion, taskInfo.taskId); + } else { + return InputConsumer.NO_OP; + } + } + /** * To be called by the consumer when it's no longer active. */ @@ -593,17 +598,14 @@ public class TouchInteractionService extends Service implements // Dump everything pw.println("TouchState:"); pw.println(" navMode=" + mMode); - pw.println(" validSystemUiFlags=" + validSystemUiFlags() - + " flags=" + mSystemUiStateFlags); - pw.println(" topTaskLocked=" + topTaskLocked()); + pw.println(" validSystemUiFlags=" + validSystemUiFlags()); + pw.println(" systemUiFlags=" + mSystemUiStateFlags); + pw.println(" systemUiFlagsDesc=" + + QuickStepContract.getSystemUiStateString(mSystemUiStateFlags)); pw.println(" isDeviceLocked=" + mKM.isDeviceLocked()); - pw.println(" screenPinned=" + - ActivityManagerWrapper.getInstance().isScreenPinningActive()); pw.println(" assistantAvailable=" + mAssistantAvailable); - pw.println(" a11yClickable=" - + ((mSystemUiStateFlags & SYSUI_STATE_A11Y_BUTTON_CLICKABLE) != 0)); - pw.println(" a11yLongClickable=" - + ((mSystemUiStateFlags & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE) != 0)); + pw.println(" assistantDisabled=" + + QuickStepContract.isAssistantGestureDisabled(mSystemUiStateFlags)); pw.println(" resumed=" + mOverviewComponentObserver.getActivityControlHelper().isResumed()); pw.println(" useSharedState=" + mConsumer.useSharedSwipeState()); diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java index 2ff5c0c6a..0d0478ae0 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java @@ -39,6 +39,7 @@ import static com.android.quickstep.WindowTransformSwipeHandler.GestureEndTarget import static com.android.quickstep.WindowTransformSwipeHandler.GestureEndTarget.NEW_TASK; import static com.android.quickstep.WindowTransformSwipeHandler.GestureEndTarget.RECENTS; import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHOLD; +import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED; import android.animation.Animator; import android.animation.AnimatorSet; @@ -105,6 +106,7 @@ import com.android.quickstep.views.TaskView; import com.android.systemui.shared.recents.model.ThumbnailData; import com.android.systemui.shared.system.InputConsumerController; import com.android.systemui.shared.system.LatencyTrackerCompat; +import com.android.systemui.shared.system.QuickStepContract; import com.android.systemui.shared.system.RemoteAnimationTargetCompat; import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat; import com.android.systemui.shared.system.WindowCallbacksCompat; @@ -170,7 +172,8 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_DRAWN | STATE_LAUNCHER_STARTED; public enum GestureEndTarget { - HOME(1, STATE_SCALED_CONTROLLER_HOME, true, false, ContainerType.WORKSPACE, false), + HOME(1, STATE_SCALED_CONTROLLER_HOME | STATE_CAPTURE_SCREENSHOT, true, false, + ContainerType.WORKSPACE, false), RECENTS(1, STATE_SCALED_CONTROLLER_RECENTS | STATE_CAPTURE_SCREENSHOT | STATE_SCREENSHOT_VIEW_SHOWN, true, false, ContainerType.TASKSWITCHER, true), @@ -216,6 +219,12 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> private static final long SHELF_ANIM_DURATION = 120; public static final long RECENTS_ATTACH_DURATION = 300; + // 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; + /** * Used as the page index for logging when we return to the last task at the end of the gesture. */ @@ -230,7 +239,10 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> private RunningWindowAnim mRunningWindowAnim; private boolean mIsShelfPeeking; private DeviceProfile mDp; + // The distance needed to drag to reach the task size in recents. private int mTransitionDragLength; + // How much further we can drag past recents, as a factor of mTransitionDragLength. + private float mDragLengthFactor = 1; // Shift in the range of [0, 1]. // 0 => preview snapShot is completely visible, and hotseat is completely translated down @@ -331,9 +343,8 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> | STATE_SCALED_CONTROLLER_RECENTS, this::finishCurrentTransitionToRecents); - mStateCallback.addCallback(STATE_LAUNCHER_PRESENT | STATE_GESTURE_COMPLETED - | STATE_SCALED_CONTROLLER_HOME | STATE_APP_CONTROLLER_RECEIVED - | STATE_LAUNCHER_DRAWN, + mStateCallback.addCallback(STATE_SCREENSHOT_CAPTURED | STATE_GESTURE_COMPLETED + | STATE_SCALED_CONTROLLER_HOME, this::finishCurrentTransitionToHome); mStateCallback.addCallback(STATE_SCALED_CONTROLLER_HOME | STATE_CURRENT_TASK_FINISHED, this::reset); @@ -375,6 +386,10 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> mTransitionDragLength = mActivityControlHelper.getSwipeUpDestinationAndLength( dp, mContext, tempRect); mClipAnimationHelper.updateTargetRect(tempRect); + if (mMode == Mode.NO_BUTTON) { + // We can drag all the way to the top of the screen. + mDragLengthFactor = (float) dp.heightPx / mTransitionDragLength; + } } private long getFadeInDuration() { @@ -546,11 +561,18 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> public void updateDisplacement(float displacement) { // We are moving in the negative x/y direction displacement = -displacement; - if (displacement > mTransitionDragLength && mTransitionDragLength > 0) { - mCurrentShift.updateValue(1); + if (displacement > mTransitionDragLength * mDragLengthFactor && mTransitionDragLength > 0) { + mCurrentShift.updateValue(mDragLengthFactor); } else { float translation = Math.max(displacement, 0); float 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); } } @@ -638,6 +660,8 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> private void onAnimatorPlaybackControllerCreated(AnimatorPlaybackController anim) { mLauncherTransitionController = anim; + mLauncherTransitionController.dispatchSetInterpolator(t -> t * mDragLengthFactor); + mAnimationFactory.adjustActivityControllerInterpolators(); mLauncherTransitionController.dispatchOnStart(); updateLauncherTransitionProgress(); } @@ -690,7 +714,9 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> if (mGestureEndTarget == HOME) { return; } - float progress = mCurrentShift.value; + // Normalize the progress to 0 to 1, as the animation controller will clamp it to that + // anyway. The controller mimics the drag length factor by applying it to its interpolators. + float progress = mCurrentShift.value / mDragLengthFactor; mLauncherTransitionController.setPlayFraction( progress <= mShiftAtGestureStart || mShiftAtGestureStart >= 1 ? 0 : (progress - mShiftAtGestureStart) / (1 - mShiftAtGestureStart)); @@ -835,16 +861,9 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> } } - @UiThread - private void handleNormalGestureEnd(float endVelocity, boolean isFling, PointF velocity, + private GestureEndTarget calculateEndTarget(PointF velocity, float endVelocity, boolean isFling, boolean isCancel) { - PointF velocityPxPerMs = new PointF(velocity.x / 1000, velocity.y / 1000); - long duration = MAX_SWIPE_DURATION; - float currentShift = mCurrentShift.value; final GestureEndTarget endTarget; - float endShift; - final float startShift; - Interpolator interpolator = DEACCEL; final boolean goingToNewTask; if (mRecentsView != null) { if (!mRecentsAnimationWrapper.hasTargets()) { @@ -859,7 +878,7 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> } else { goingToNewTask = false; } - final boolean reachedOverviewThreshold = currentShift >= MIN_PROGRESS_FOR_OVERVIEW; + final boolean reachedOverviewThreshold = mCurrentShift.value >= MIN_PROGRESS_FOR_OVERVIEW; if (!isFling) { if (isCancel) { endTarget = LAST_TASK; @@ -869,7 +888,7 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> } else if (goingToNewTask) { endTarget = NEW_TASK; } else { - endTarget = currentShift < MIN_PROGRESS_FOR_OVERVIEW ? LAST_TASK : HOME; + endTarget = !reachedOverviewThreshold ? LAST_TASK : HOME; } } else { endTarget = reachedOverviewThreshold && mGestureStarted @@ -878,12 +897,6 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> ? NEW_TASK : LAST_TASK; } - endShift = endTarget.endShift; - long expectedDuration = Math.abs(Math.round((endShift - currentShift) - * MAX_SWIPE_DURATION * SWIPE_DURATION_MULTIPLIER)); - duration = Math.min(MAX_SWIPE_DURATION, expectedDuration); - startShift = currentShift; - interpolator = endTarget == RECENTS ? OVERSHOOT_1_2 : DEACCEL; } else { if (mMode == Mode.NO_BUTTON && endVelocity < 0 && !mIsShelfPeeking) { // If swiping at a diagonal, base end target on the faster velocity. @@ -896,9 +909,36 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> } else { endTarget = goingToNewTask ? NEW_TASK : LAST_TASK; } - endShift = endTarget.endShift; + } + + int stateFlags = OverviewInteractionState.INSTANCE.get(mActivity).getSystemUiStateFlags(); + if ((stateFlags & SYSUI_STATE_OVERVIEW_DISABLED) != 0 + && (endTarget == RECENTS || endTarget == LAST_TASK)) { + return LAST_TASK; + } + return endTarget; + } + + @UiThread + private void handleNormalGestureEnd(float endVelocity, boolean isFling, PointF velocity, + boolean isCancel) { + PointF velocityPxPerMs = new PointF(velocity.x / 1000, velocity.y / 1000); + long duration = MAX_SWIPE_DURATION; + float currentShift = mCurrentShift.value; + final GestureEndTarget endTarget = calculateEndTarget(velocity, endVelocity, + isFling, isCancel); + float endShift = endTarget.endShift; + final float startShift; + Interpolator interpolator = DEACCEL; + if (!isFling) { + long expectedDuration = Math.abs(Math.round((endShift - currentShift) + * MAX_SWIPE_DURATION * SWIPE_DURATION_MULTIPLIER)); + duration = Math.min(MAX_SWIPE_DURATION, expectedDuration); + startShift = currentShift; + interpolator = endTarget == RECENTS ? OVERSHOOT_1_2 : DEACCEL; + } else { startShift = Utilities.boundToRange(currentShift - velocityPxPerMs.y - * SINGLE_FRAME_MS / mTransitionDragLength, 0, 1); + * SINGLE_FRAME_MS / mTransitionDragLength, 0, mDragLengthFactor); float minFlingVelocity = mContext.getResources() .getDimension(R.dimen.quickstep_fling_min_velocity); if (Math.abs(endVelocity) > minFlingVelocity && mTransitionDragLength > 0) { @@ -936,8 +976,10 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> } else if (endTarget == RECENTS) { mLiveTileOverlay.startIconAnimation(); if (mRecentsView != null) { - duration = Utilities.boundToRange(mRecentsView.getScroller().getDuration(), - duration, MAX_SWIPE_DURATION); + if (mRecentsView.getScroller().getDuration() > MAX_SWIPE_DURATION) { + mRecentsView.snapToPage(mRecentsView.getNextPage(), (int) MAX_SWIPE_DURATION); + } + duration = Math.max(duration, mRecentsView.getScroller().getDuration()); } if (mMode == Mode.NO_BUTTON) { setShelfState(ShelfAnimState.OVERVIEW, interpolator, duration); @@ -1015,6 +1057,7 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> setStateOnUiThread(target.endState); } }); + homeAnimFactory.playAtomicAnimation(velocityPxPerMs.y); windowAnim.start(velocityPxPerMs); mRunningWindowAnim = RunningWindowAnim.wrap(windowAnim); mLauncherTransitionController = null; @@ -1055,6 +1098,7 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> mLauncherTransitionController.getAnimationPlayer().end(); } else { mLauncherTransitionController.dispatchSetInterpolator(adjustedInterpolator); + mAnimationFactory.adjustActivityControllerInterpolators(); mLauncherTransitionController.getAnimationPlayer().setDuration(duration); if (QUICKSTEP_SPRINGS.get()) { @@ -1091,12 +1135,10 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> // FolderIconView can be seen morphing into the icon shape. final float windowAlphaThreshold = isFloatingIconView ? 1f - SHAPE_PROGRESS_DURATION : 1f; anim.addOnUpdateListener((currentRect, progress) -> { - float interpolatedProgress = Interpolators.ACCEL_1_5.getInterpolation(progress); - homeAnim.setPlayFraction(progress); - float windowAlpha = Utilities.mapToRange(interpolatedProgress, 0, - windowAlphaThreshold, 1f, 0f, Interpolators.LINEAR); + float windowAlpha = Math.max(0, Utilities.mapToRange(progress, 0, + windowAlphaThreshold, 1f, 0f, Interpolators.LINEAR)); mTransformParams.setProgress(progress) .setCurrentRectAndTargetAlpha(currentRect, windowAlpha); mClipAnimationHelper.applyTransform(targetSet, mTransformParams, @@ -1251,8 +1293,15 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> if (mTaskSnapshot == null) { mTaskSnapshot = controller.screenshotTask(mRunningTaskId); } - TaskView taskView = mRecentsView.updateThumbnail(mRunningTaskId, mTaskSnapshot); - if (taskView != null) { + final TaskView taskView; + if (mGestureEndTarget == HOME) { + // Capture the screenshot before finishing the transition to home to ensure it's + // taken in the correct orientation, but no need to update the thumbnail. + taskView = null; + } else { + taskView = mRecentsView.updateThumbnail(mRunningTaskId, mTaskSnapshot); + } + if (taskView != null && !mCanceled) { // Defer finishing the animation until the next launcher frame with the // new thumbnail finishTransitionPosted = new WindowCallbacksCompat(taskView) { @@ -1262,6 +1311,13 @@ public class WindowTransformSwipeHandler<T extends BaseDraggingActivity> @Override public void onPostDraw(Canvas canvas) { + // If we were cancelled after this was attached, do not update + // the state. + if (mCanceled) { + detach(); + return; + } + if (mDeferFrameCount > 0) { mDeferFrameCount--; // Workaround, detach and reattach to invalidate the root node for diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/RecentsRootView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/RecentsRootView.java index 09d323ee6..182072957 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/RecentsRootView.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/RecentsRootView.java @@ -20,7 +20,6 @@ import android.content.Context; import android.graphics.Point; import android.graphics.Rect; import android.util.AttributeSet; -import android.view.WindowInsets; import com.android.launcher3.BaseActivity; import com.android.launcher3.R; @@ -71,7 +70,7 @@ public class RecentsRootView extends BaseDragLayer<RecentsActivity> { // Update device profile before notifying the children. mActivity.getDeviceProfile().updateInsets(insets); setInsets(insets); - return true; // I'll take it from here + return false; // Let children get the full insets } @Override @@ -89,10 +88,4 @@ public class RecentsRootView extends BaseDragLayer<RecentsActivity> { mActivity.getDeviceProfile().updateInsets(mInsets); super.setInsets(mInsets); } - - @Override - public WindowInsets dispatchApplyWindowInsets(WindowInsets insets) { - updateTouchExcludeRegion(insets); - return super.dispatchApplyWindowInsets(insets); - } }
\ No newline at end of file diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java index d01b5ec19..db2af59ac 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java @@ -15,26 +15,102 @@ */ package com.android.quickstep.inputconsumers; +import static android.view.MotionEvent.ACTION_CANCEL; +import static android.view.MotionEvent.ACTION_POINTER_DOWN; +import static android.view.MotionEvent.ACTION_UP; + import static com.android.launcher3.Utilities.squaredHypot; import static com.android.launcher3.Utilities.squaredTouchSlop; +import static com.android.quickstep.MultiStateCallback.DEBUG_STATES; +import static com.android.quickstep.WindowTransformSwipeHandler.MIN_PROGRESS_FOR_OVERVIEW; +import android.content.ComponentName; 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.view.MotionEvent; +import android.view.VelocityTracker; +import android.view.ViewConfiguration; +import android.view.WindowManager; + +import com.android.launcher3.R; +import com.android.launcher3.Utilities; +import com.android.quickstep.LockScreenRecentsActivity; +import com.android.quickstep.MultiStateCallback; +import com.android.quickstep.SwipeSharedState; +import com.android.quickstep.util.ClipAnimationHelper; +import com.android.quickstep.util.RecentsAnimationListenerSet; +import com.android.quickstep.util.SwipeAnimationTargetSet; +import com.android.systemui.shared.system.ActivityManagerWrapper; +import com.android.systemui.shared.system.BackgroundExecutor; +import com.android.systemui.shared.system.InputMonitorCompat; +import com.android.systemui.shared.system.RemoteAnimationTargetCompat; /** * A dummy input consumer used when the device is still locked, e.g. from secure camera. */ -public class DeviceLockedInputConsumer implements InputConsumer { +public class DeviceLockedInputConsumer implements InputConsumer, + SwipeAnimationTargetSet.SwipeAnimationListener { + + private static final float SCALE_DOWN = 0.75f; + + private static final String[] STATE_NAMES = DEBUG_STATES ? new String[2] : null; + private static int getFlagForIndex(int index, String name) { + if (DEBUG_STATES) { + STATE_NAMES[index] = name; + } + return 1 << index; + } + + private static final int STATE_TARGET_RECEIVED = + getFlagForIndex(0, "STATE_TARGET_RECEIVED"); + private static final int STATE_HANDLER_INVALIDATED = + getFlagForIndex(1, "STATE_HANDLER_INVALIDATED"); private final Context mContext; private final float mTouchSlopSquared; + private final SwipeSharedState mSwipeSharedState; + private final InputMonitorCompat mInputMonitorCompat; + private final PointF mTouchDown = new PointF(); + private final ClipAnimationHelper mClipAnimationHelper; + private final ClipAnimationHelper.TransformParams mTransformParams; + private final Point mDisplaySize; + private final MultiStateCallback mStateCallback; + private final RectF mSwipeTouchRegion; + public final int mRunningTaskId; + + private VelocityTracker mVelocityTracker; + private float mProgress; - public DeviceLockedInputConsumer(Context context) { + private boolean mThresholdCrossed = false; + + private SwipeAnimationTargetSet mTargetSet; + + public DeviceLockedInputConsumer(Context context, SwipeSharedState swipeSharedState, + InputMonitorCompat inputMonitorCompat, RectF swipeTouchRegion, int runningTaskId) { mContext = context; mTouchSlopSquared = squaredTouchSlop(context); + mSwipeSharedState = swipeSharedState; + mClipAnimationHelper = new ClipAnimationHelper(context); + mTransformParams = new ClipAnimationHelper.TransformParams(); + mInputMonitorCompat = inputMonitorCompat; + mSwipeTouchRegion = swipeTouchRegion; + mRunningTaskId = runningTaskId; + + // Do not use DeviceProfile as the user data might be locked + mDisplaySize = new Point(); + context.getSystemService(WindowManager.class).getDefaultDisplay().getRealSize(mDisplaySize); + + // Init states + mStateCallback = new MultiStateCallback(STATE_NAMES); + mStateCallback.addCallback(STATE_TARGET_RECEIVED | STATE_HANDLER_INVALIDATED, + this::endRemoteAnimation); + + mVelocityTracker = VelocityTracker.obtain(); } @Override @@ -44,17 +120,137 @@ public class DeviceLockedInputConsumer implements InputConsumer { @Override public void onMotionEvent(MotionEvent ev) { + if (mVelocityTracker == null) { + return; + } + mVelocityTracker.addMovement(ev); + float x = ev.getX(); float y = ev.getY(); - if (ev.getAction() == MotionEvent.ACTION_DOWN) { - mTouchDown.set(x, y); - } else if (ev.getAction() == MotionEvent.ACTION_MOVE) { - if (squaredHypot(x - mTouchDown.x, y - mTouchDown.y) > mTouchSlopSquared) { + switch (ev.getAction()) { + case MotionEvent.ACTION_DOWN: + mTouchDown.set(x, y); + break; + case ACTION_POINTER_DOWN: { + if (!mThresholdCrossed) { + // Cancel interaction in case of multi-touch interaction + int ptrIdx = ev.getActionIndex(); + if (!mSwipeTouchRegion.contains(ev.getX(ptrIdx), ev.getY(ptrIdx))) { + int action = ev.getAction(); + ev.setAction(ACTION_CANCEL); + finishTouchTracking(ev); + ev.setAction(action); + } + } + break; + } + case MotionEvent.ACTION_MOVE: { + if (!mThresholdCrossed) { + if (squaredHypot(x - mTouchDown.x, y - mTouchDown.y) > mTouchSlopSquared) { + startRecentsTransition(); + } + } else { + float dy = Math.max(mTouchDown.y - y, 0); + mProgress = dy / mDisplaySize.y; + mTransformParams.setProgress(mProgress); + if (mTargetSet != null) { + mClipAnimationHelper.applyTransform(mTargetSet, mTransformParams); + } + } + break; + } + case MotionEvent.ACTION_CANCEL: + case MotionEvent.ACTION_UP: + finishTouchTracking(ev); + break; + } + } + + /** + * 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(MotionEvent ev) { + mStateCallback.setState(STATE_HANDLER_INVALIDATED); + if (mThresholdCrossed && ev.getAction() == ACTION_UP) { + mVelocityTracker.computeCurrentVelocity(1000, + ViewConfiguration.get(mContext).getScaledMaximumFlingVelocity()); + + float velocityY = mVelocityTracker.getYVelocity(); + float flingThreshold = mContext.getResources() + .getDimension(R.dimen.quickstep_fling_threshold_velocity); + + boolean dismissTask; + if (Math.abs(velocityY) > flingThreshold) { + // Is fling + dismissTask = velocityY < 0; + } else { + dismissTask = mProgress >= (1 - MIN_PROGRESS_FOR_OVERVIEW); + } + if (dismissTask) { // For now, just start the home intent so user is prompted to unlock the device. mContext.startActivity(new Intent(Intent.ACTION_MAIN) .addCategory(Intent.CATEGORY_HOME) .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); } } + mVelocityTracker.recycle(); + mVelocityTracker = null; + } + + private void startRecentsTransition() { + mThresholdCrossed = true; + RecentsAnimationListenerSet newListenerSet = + mSwipeSharedState.newRecentsAnimationListenerSet(); + newListenerSet.addListener(this); + Intent intent = new Intent(Intent.ACTION_MAIN) + .addCategory(Intent.CATEGORY_DEFAULT) + .setComponent(new ComponentName(mContext, LockScreenRecentsActivity.class)) + .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); + + mInputMonitorCompat.pilferPointers(); + BackgroundExecutor.get().submit( + () -> ActivityManagerWrapper.getInstance().startRecentsActivity( + intent, null, newListenerSet, null, null)); + } + + @Override + public void onRecentsAnimationStart(SwipeAnimationTargetSet targetSet) { + mTargetSet = targetSet; + + Rect displaySize = new Rect(0, 0, mDisplaySize.x, mDisplaySize.y); + RemoteAnimationTargetCompat targetCompat = targetSet.findTask(mRunningTaskId); + if (targetCompat != null) { + mClipAnimationHelper.updateSource(displaySize, targetCompat); + } + + Utilities.scaleRectAboutCenter(displaySize, SCALE_DOWN); + displaySize.offsetTo(displaySize.left, 0); + mClipAnimationHelper.updateTargetRect(displaySize); + mClipAnimationHelper.applyTransform(mTargetSet, mTransformParams); + + mStateCallback.setState(STATE_TARGET_RECEIVED); + } + + @Override + public void onRecentsAnimationCanceled() { + mTargetSet = null; + } + + private void endRemoteAnimation() { + if (mTargetSet != null) { + mTargetSet.finishController( + false /* toRecents */, null /* callback */, false /* sendUserLeaveHint */); + } + } + + @Override + public void onConsumerAboutToBeSwitched() { + mStateCallback.setState(STATE_HANDLER_INVALIDATED); + } + + @Override + public boolean allowInterceptByParent() { + return !mThresholdCrossed; } } diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ClipAnimationHelper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ClipAnimationHelper.java index 805cf3328..0ae469c70 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ClipAnimationHelper.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ClipAnimationHelper.java @@ -23,7 +23,6 @@ import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MOD import android.annotation.TargetApi; import android.content.Context; -import android.graphics.Canvas; import android.graphics.Matrix; import android.graphics.Matrix.ScaleToFit; import android.graphics.Rect; @@ -160,14 +159,16 @@ public class ClipAnimationHelper { public RectF applyTransform(RemoteAnimationTargetSet targetSet, TransformParams params, boolean launcherOnTop) { + float progress = params.progress; if (params.currentRect == null) { RectF currentRect; mTmpRectF.set(mTargetRect); Utilities.scaleRectFAboutCenter(mTmpRectF, params.offsetScale); - float progress = params.progress; currentRect = mRectFEvaluator.evaluate(progress, mSourceRect, mTmpRectF); currentRect.offset(params.offsetX, 0); + // Don't clip past progress > 1. + progress = Math.min(1, progress); final RectF sourceWindowClipInsets = params.forLiveTile ? mSourceWindowClipInsetsForLiveTile : mSourceWindowClipInsets; mClipRectF.left = sourceWindowClipInsets.left * progress; @@ -189,7 +190,7 @@ public class ClipAnimationHelper { float alpha = 1f; int layer = RemoteAnimationProvider.getLayer(app, mBoostModeTargetLayers); float cornerRadius = 0f; - float scale = params.currentRect.width() / crop.width(); + float scale = Math.max(params.currentRect.width(), mTargetRect.width()) / crop.width(); if (app.mode == targetSet.targetMode) { if (app.activityType != RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME) { mTmpMatrix.setRectToRect(mSourceRect, params.currentRect, ScaleToFit.FILL); @@ -198,7 +199,7 @@ public class ClipAnimationHelper { if (mSupportsRoundedCornersOnWindows) { float windowCornerRadius = mUseRoundedCornersOnWindows ? mWindowCornerRadius : 0; - cornerRadius = Utilities.mapRange(params.progress, windowCornerRadius, + cornerRadius = Utilities.mapRange(progress, windowCornerRadius, mTaskCornerRadius); mCurrentCornerRadius = cornerRadius; } diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RectFSpringAnim.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RectFSpringAnim.java index 3f4ad58ab..77dc6f32e 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RectFSpringAnim.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RectFSpringAnim.java @@ -16,26 +16,22 @@ package com.android.quickstep.util; import android.animation.Animator; -import android.animation.ObjectAnimator; -import android.animation.PropertyValuesHolder; -import android.animation.ValueAnimator; import android.content.res.Resources; import android.graphics.PointF; import android.graphics.RectF; -import android.util.FloatProperty; import androidx.dynamicanimation.animation.DynamicAnimation.OnAnimationEndListener; import androidx.dynamicanimation.animation.FloatPropertyCompat; +import androidx.dynamicanimation.animation.SpringAnimation; +import androidx.dynamicanimation.animation.SpringForce; import com.android.launcher3.R; import com.android.launcher3.Utilities; -import com.android.launcher3.anim.AnimationSuccessListener; import com.android.launcher3.anim.FlingSpringAnim; import java.util.ArrayList; import java.util.List; -import static com.android.launcher3.anim.Interpolators.DEACCEL; /** * Applies spring forces to animate from a starting rect to a target rect, @@ -43,14 +39,6 @@ import static com.android.launcher3.anim.Interpolators.DEACCEL; */ public class RectFSpringAnim { - /** - * Although the rect position animation takes an indefinite amount of time since it depends on - * the initial velocity and applied forces, scaling from the starting rect to the target rect - * can be done in parallel at a fixed duration. Update callbacks are sent based on the progress - * of this animation, while the end callback is sent after all animations finish. - */ - private static final long RECT_SCALE_DURATION = 250; - private static final FloatPropertyCompat<RectFSpringAnim> RECT_CENTER_X = new FloatPropertyCompat<RectFSpringAnim>("rectCenterXSpring") { @Override @@ -79,17 +67,17 @@ public class RectFSpringAnim { } }; - private static final FloatProperty<RectFSpringAnim> RECT_SCALE_PROGRESS = - new FloatProperty<RectFSpringAnim>("rectScaleProgress") { + private static final FloatPropertyCompat<RectFSpringAnim> RECT_SCALE_PROGRESS = + new FloatPropertyCompat<RectFSpringAnim>("rectScaleProgress") { @Override - public Float get(RectFSpringAnim anim) { - return anim.mCurrentScaleProgress; + public float getValue(RectFSpringAnim object) { + return object.mCurrentScaleProgress; } @Override - public void setValue(RectFSpringAnim anim, float currentScaleProgress) { - anim.mCurrentScaleProgress = currentScaleProgress; - anim.onUpdate(); + public void setValue(RectFSpringAnim object, float value) { + object.mCurrentScaleProgress = value; + object.onUpdate(); } }; @@ -106,7 +94,7 @@ public class RectFSpringAnim { private float mCurrentScaleProgress; private FlingSpringAnim mRectXAnim; private FlingSpringAnim mRectYAnim; - private ValueAnimator mRectScaleAnim; + private SpringAnimation mRectScaleAnim; private boolean mAnimsStarted; private boolean mRectXAnimEnded; private boolean mRectYAnimEnded; @@ -177,17 +165,18 @@ public class RectFSpringAnim { mRectYAnim = new FlingSpringAnim(this, RECT_Y, startY, endY, startVelocityY, mMinVisChange, minYValue, maxYValue, springVelocityFactor, onYEndListener); - mRectScaleAnim = ObjectAnimator.ofPropertyValuesHolder(this, - PropertyValuesHolder.ofFloat(RECT_SCALE_PROGRESS, 1)) - .setDuration(RECT_SCALE_DURATION); - mRectScaleAnim.setInterpolator(DEACCEL); - mRectScaleAnim.addListener(new AnimationSuccessListener() { - @Override - public void onAnimationSuccess(Animator animator) { - mRectScaleAnimEnded = true; - maybeOnEnd(); - } - }); + float minVisibleChange = 1f / mStartRect.height(); + mRectScaleAnim = new SpringAnimation(this, RECT_SCALE_PROGRESS) + .setSpring(new SpringForce(1f) + .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY) + .setStiffness(SpringForce.STIFFNESS_LOW)) + .setStartVelocity(velocityPxPerMs.y * minVisibleChange) + .setMaxValue(1f) + .setMinimumVisibleChange(minVisibleChange) + .addEndListener((animation, canceled, value, velocity) -> { + mRectScaleAnimEnded = true; + maybeOnEnd(); + }); mRectXAnim.start(); mRectYAnim.start(); @@ -202,7 +191,9 @@ public class RectFSpringAnim { if (mAnimsStarted) { mRectXAnim.end(); mRectYAnim.end(); - mRectScaleAnim.end(); + if (mRectScaleAnim.canSkipToEnd()) { + mRectScaleAnim.skipToEnd(); + } } } diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java new file mode 100644 index 000000000..93b6e4ba5 --- /dev/null +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java @@ -0,0 +1,154 @@ +/* + * 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.util; + +import android.animation.Animator; +import android.animation.ObjectAnimator; +import android.animation.ValueAnimator; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.Nullable; +import androidx.dynamicanimation.animation.SpringForce; + +import com.android.launcher3.CellLayout; +import com.android.launcher3.DeviceProfile; +import com.android.launcher3.Launcher; +import com.android.launcher3.LauncherAnimUtils.ViewProgressProperty; +import com.android.launcher3.R; +import com.android.launcher3.ShortcutAndWidgetContainer; +import com.android.launcher3.anim.SpringObjectAnimator; + +import java.util.ArrayList; +import java.util.List; + +import static com.android.launcher3.anim.Interpolators.LINEAR; + +/** + * Creates an animation where all the workspace items are moved into their final location, + * staggered row by row from the bottom up. + * This is used in conjunction with the swipe up to home animation. + */ +public class StaggeredWorkspaceAnim { + + private static final int APP_CLOSE_ROW_START_DELAY_MS = 16; + private static final int ALPHA_DURATION_MS = 200; + + private static final float MAX_VELOCITY_PX_PER_S = 22f; + + private static final float DAMPING_RATIO = + (SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY + SpringForce.DAMPING_RATIO_LOW_BOUNCY) / 2f; + private static final float STIFFNESS = SpringForce.STIFFNESS_LOW; + + private final float mVelocity; + private final float mSpringTransY; + private final View mViewToIgnore; + + private final List<ValueAnimator> mAnimators = new ArrayList<>(); + + /** + * @param floatingViewOriginalView The FloatingIconView's original view. + */ + public StaggeredWorkspaceAnim(Launcher launcher, @Nullable View floatingViewOriginalView, + float velocity) { + mVelocity = velocity; + // We ignore this view since it's visibility and position is controlled by + // the FloatingIconView. + mViewToIgnore = floatingViewOriginalView; + + // Scale the translationY based on the initial velocity to better sync the workspace items + // with the floating view. + float transFactor = 0.1f + 0.9f * Math.abs(velocity) / MAX_VELOCITY_PX_PER_S; + mSpringTransY = transFactor * launcher.getResources() + .getDimensionPixelSize(R.dimen.swipe_up_max_workspace_trans_y);; + + DeviceProfile grid = launcher.getDeviceProfile(); + ShortcutAndWidgetContainer currentPage = ((CellLayout) launcher.getWorkspace() + .getChildAt(launcher.getWorkspace().getCurrentPage())) + .getShortcutsAndWidgets(); + + // Hotseat and QSB takes up two additional rows. + int totalRows = grid.inv.numRows + (grid.isVerticalBarLayout() ? 0 : 2); + + // Set up springs on workspace items. + for (int i = currentPage.getChildCount() - 1; i >= 0; i--) { + View child = currentPage.getChildAt(i); + CellLayout.LayoutParams lp = ((CellLayout.LayoutParams) child.getLayoutParams()); + addStaggeredAnimationForView(child, lp.cellY + lp.cellVSpan, totalRows); + } + + // Set up springs for the hotseat and qsb. + if (grid.isVerticalBarLayout()) { + ViewGroup hotseat = (ViewGroup) launcher.getHotseat().getChildAt(0); + for (int i = hotseat.getChildCount() - 1; i >= 0; i--) { + View child = hotseat.getChildAt(i); + CellLayout.LayoutParams lp = ((CellLayout.LayoutParams) child.getLayoutParams()); + addStaggeredAnimationForView(child, lp.cellY + 1, totalRows); + } + } else { + View hotseat = launcher.getHotseat().getChildAt(0); + addStaggeredAnimationForView(hotseat, grid.inv.numRows + 1, totalRows); + + View qsb = launcher.findViewById(R.id.search_container_all_apps); + addStaggeredAnimationForView(qsb, grid.inv.numRows + 2, totalRows); + } + } + + /** + * Starts the animation. + */ + public void start() { + for (Animator a : mAnimators) { + if (a instanceof SpringObjectAnimator) { + ((SpringObjectAnimator) a).startSpring(1f, mVelocity, null); + } else { + a.start(); + } + } + } + + /** + * Adds an alpha/trans animator for {@param v}, with a start delay based on the view's row. + * + * @param v A view on the workspace. + * @param row The bottom-most row that contains the view. + * @param totalRows Total number of rows. + */ + private void addStaggeredAnimationForView(View v, int row, int totalRows) { + if (v == mViewToIgnore) { + return; + } + + // Invert the rows, because we stagger starting from the bottom of the screen. + int invertedRow = totalRows - row; + // Add 1 to the inverted row so that the bottom most row has a start delay. + long startDelay = (long) ((invertedRow + 1) * APP_CLOSE_ROW_START_DELAY_MS); + + v.setTranslationY(mSpringTransY); + SpringObjectAnimator springTransY = new SpringObjectAnimator<>( + new ViewProgressProperty(v, View.TRANSLATION_Y), "staggeredSpringTransY", 1f, + DAMPING_RATIO, STIFFNESS, mSpringTransY, 0); + springTransY.setStartDelay(startDelay); + mAnimators.add(springTransY); + + v.setAlpha(0); + ObjectAnimator alpha = ObjectAnimator.ofFloat(v, View.ALPHA, 0f, 1f); + alpha.setInterpolator(LINEAR); + alpha.setDuration(ALPHA_DURATION_MS); + alpha.setStartDelay(startDelay); + mAnimators.add(alpha); + } +} diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java index 053b7389c..f8d454f34 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java @@ -52,6 +52,7 @@ import com.android.launcher3.Utilities; import com.android.launcher3.anim.AnimatorPlaybackController; import com.android.launcher3.anim.Interpolators; import com.android.launcher3.logging.UserEventDispatcher; +import com.android.launcher3.testing.TestProtocol; import com.android.launcher3.userevent.nano.LauncherLogProto; import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction; import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch; @@ -145,11 +146,13 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable { }; private final TaskOutlineProvider mOutlineProvider; + private final FooterOutlineProvider mFooterOutlineProvider; private Task mTask; private TaskThumbnailView mSnapshotView; private TaskMenuView mMenuView; private IconView mIconView; + private View mTaskFooterContainer; private DigitalWellBeingToast mDigitalWellBeingToast; private float mCurveScale; private float mFullscreenProgress; @@ -180,6 +183,9 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable { super(context, attrs, defStyleAttr); mActivity = BaseDraggingActivity.fromContext(context); setOnClickListener((view) -> { + if (com.android.launcher3.testing.TestProtocol.sDebugTracing) { + android.util.Log.d(TestProtocol.NO_START_TASK_TAG, "TaskView onClick"); + } if (getTask() == null) { return; } @@ -203,6 +209,7 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable { mWindowCornerRadius = QuickStepContract.getWindowCornerRadius(context.getResources()); mCurrentFullscreenParams = new FullscreenDrawParams(mCornerRadius); mOutlineProvider = new TaskOutlineProvider(getResources(), mCurrentFullscreenParams); + mFooterOutlineProvider = new FooterOutlineProvider(mCurrentFullscreenParams); setOutlineProvider(mOutlineProvider); } @@ -212,6 +219,9 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable { mSnapshotView = findViewById(R.id.snapshot); mIconView = findViewById(R.id.icon); mDigitalWellBeingToast = findViewById(R.id.digital_well_being_toast); + mTaskFooterContainer = findViewById(R.id.task_footer_container); + mTaskFooterContainer.setOutlineProvider(mFooterOutlineProvider); + mTaskFooterContainer.setClipToOutline(true); } public TaskMenuView getMenuView() { @@ -279,6 +289,9 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable { public void launchTask(boolean animate, boolean freezeTaskList, Consumer<Boolean> resultCallback, Handler resultCallbackHandler) { + if (com.android.launcher3.testing.TestProtocol.sDebugTracing) { + android.util.Log.d(TestProtocol.NO_START_TASK_TAG, "launchTask"); + } if (ENABLE_QUICKSTEP_LIVE_TILE.get()) { if (isRunningTask()) { getRecentsView().finishRecentsAnimation(false /* toRecents */, @@ -293,6 +306,9 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable { private void launchTaskInternal(boolean animate, boolean freezeTaskList, Consumer<Boolean> resultCallback, Handler resultCallbackHandler) { + if (com.android.launcher3.testing.TestProtocol.sDebugTracing) { + android.util.Log.d(TestProtocol.NO_START_TASK_TAG, "launchTaskInternal"); + } if (mTask != null) { final ActivityOptions opts; if (animate) { @@ -410,6 +426,15 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable { .getInterpolation(progress); mIconView.setScaleX(scale); mIconView.setScaleY(scale); + + int footerVerticalOffset = (int) (mTaskFooterContainer.getHeight() * (1.0f - scale)); + mTaskFooterContainer.setTranslationY( + mCurrentFullscreenParams.mCurrentDrawnInsets.bottom + + mCurrentFullscreenParams.mCurrentDrawnInsets.top + + footerVerticalOffset); + mFooterOutlineProvider.setFullscreenDrawParams( + mCurrentFullscreenParams, footerVerticalOffset); + mTaskFooterContainer.invalidateOutline(); } public void setIconScaleAnimStartProgress(float startProgress) { @@ -550,6 +575,29 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable { } } + private static final class FooterOutlineProvider extends ViewOutlineProvider { + + private FullscreenDrawParams mFullscreenDrawParams; + private int mVerticalOffset; + private final Rect mOutlineRect = new Rect(); + + FooterOutlineProvider(FullscreenDrawParams params) { + mFullscreenDrawParams = params; + } + + void setFullscreenDrawParams(FullscreenDrawParams params, int verticalOffset) { + mFullscreenDrawParams = params; + mVerticalOffset = verticalOffset; + } + + @Override + public void getOutline(View view, Outline outline) { + mOutlineRect.set(0, 0, view.getWidth(), view.getHeight()); + mOutlineRect.offset(0, -mVerticalOffset); + outline.setRoundRect(mOutlineRect, mFullscreenDrawParams.mCurrentDrawnCornerRadius); + } + } + @Override public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { super.onInitializeAccessibilityNodeInfo(info); @@ -633,12 +681,12 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable { * @param progress: 0 = show icon and no insets; 1 = don't show icon and show full insets. */ public void setFullscreenProgress(float progress) { + progress = Utilities.boundToRange(progress, 0, 1); if (progress == mFullscreenProgress) { return; } mFullscreenProgress = progress; boolean isFullscreen = mFullscreenProgress > 0; - setIconScaleAndDim(progress, true /* invert */); mIconView.setVisibility(progress < 1 ? VISIBLE : INVISIBLE); setClipChildren(!isFullscreen); setClipToPadding(!isFullscreen); @@ -662,6 +710,9 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable { / (getWidth() + currentInsetsLeft + currentInsetsRight)); } + // Some of the items in here are dependent on the current fullscreen params + setIconScaleAndDim(progress, true /* invert */); + thumbnail.setFullscreenParams(mCurrentFullscreenParams); mOutlineProvider.setFullscreenParams(mCurrentFullscreenParams); invalidateOutline(); |