/* * 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 com.android.launcher3.LauncherState.BACKGROUND_APP; import static com.android.launcher3.LauncherState.NORMAL; import static com.android.launcher3.LauncherState.OVERVIEW; import static com.android.launcher3.LauncherState.QUICK_SWITCH; import static com.android.launcher3.anim.AnimatorListeners.forEndCallback; import static com.android.launcher3.anim.Interpolators.LINEAR; import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE; import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; import android.animation.Animator; import android.animation.AnimatorSet; import android.content.Context; import android.graphics.Rect; import android.view.MotionEvent; import androidx.annotation.Nullable; import androidx.annotation.UiThread; import com.android.launcher3.BaseQuickstepLauncher; import com.android.launcher3.DeviceProfile; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherInitListener; import com.android.launcher3.LauncherState; import com.android.launcher3.anim.PendingAnimation; import com.android.launcher3.statehandlers.DepthController; import com.android.launcher3.statehandlers.DepthController.ClampedDepthProperty; import com.android.launcher3.statemanager.StateManager; import com.android.launcher3.taskbar.LauncherTaskbarUIController; import com.android.launcher3.touch.PagedOrientationHandler; import com.android.quickstep.GestureState.GestureEndTarget; import com.android.quickstep.SysUINavigationMode.Mode; import com.android.quickstep.util.ActivityInitListener; import com.android.quickstep.util.AnimatorControllerWithResistance; import com.android.quickstep.util.LayoutUtils; import com.android.quickstep.views.RecentsView; import com.android.systemui.plugins.shared.LauncherOverlayManager; import com.android.systemui.shared.system.RemoteAnimationTargetCompat; import java.util.function.Consumer; import java.util.function.Predicate; /** * {@link BaseActivityInterface} for the in-launcher recents. */ public final class LauncherActivityInterface extends BaseActivityInterface { public static final LauncherActivityInterface INSTANCE = new LauncherActivityInterface(); private LauncherActivityInterface() { super(true, OVERVIEW, BACKGROUND_APP); } @Override public int getSwipeUpDestinationAndLength(DeviceProfile dp, Context context, Rect outRect, PagedOrientationHandler orientationHandler) { calculateTaskSize(context, dp, outRect); if (dp.isVerticalBarLayout() && SysUINavigationMode.getMode(context) != Mode.NO_BUTTON) { return dp.isSeascape() ? outRect.left : (dp.widthPx - outRect.right); } else { return LayoutUtils.getShelfTrackingDistance(context, dp, orientationHandler); } } @Override public void onSwipeUpToHomeComplete(RecentsAnimationDeviceState deviceState) { Launcher launcher = getCreatedActivity(); if (launcher == null) { return; } // When going to home, the state animator we use has SKIP_OVERVIEW because we assume that // setRecentsAttachedToAppWindow() will handle animating Overview instead. Thus, at the end // of the animation, we should ensure recents is at the correct position for NORMAL state. // For example, when doing a long swipe to home, RecentsView may be scaled down. This is // relatively expensive, so do it on the next frame instead of critical path. MAIN_EXECUTOR.getHandler().post(launcher.getStateManager()::reapplyState); launcher.getRootView().setForceHideBackArrow(false); notifyRecentsOfOrientation(deviceState.getRotationTouchHelper()); } @Override public void onAssistantVisibilityChanged(float visibility) { Launcher launcher = getCreatedActivity(); if (launcher == null) { return; } launcher.onAssistantVisibilityChanged(visibility); } @Override public void onOneHandedModeStateChanged(boolean activated) { Launcher launcher = getCreatedActivity(); if (launcher == null) { return; } launcher.onOneHandedStateChanged(activated); } @Override public AnimationFactory prepareRecentsUI(RecentsAnimationDeviceState deviceState, boolean activityVisible, Consumer callback) { notifyRecentsOfOrientation(deviceState.getRotationTouchHelper()); DefaultAnimationFactory factory = new DefaultAnimationFactory(callback) { @Override protected void createBackgroundToOverviewAnim(BaseQuickstepLauncher activity, PendingAnimation pa) { super.createBackgroundToOverviewAnim(activity, pa); // Animate the blur and wallpaper zoom float fromDepthRatio = BACKGROUND_APP.getDepth(activity); float toDepthRatio = OVERVIEW.getDepth(activity); pa.addFloat(getDepthController(), new ClampedDepthProperty(fromDepthRatio, toDepthRatio), fromDepthRatio, toDepthRatio, LINEAR); } }; BaseQuickstepLauncher launcher = factory.initUI(); // Since all apps is not visible, we can safely reset the scroll position. // This ensures then the next swipe up to all-apps starts from scroll 0. launcher.getAppsView().reset(false /* animate */); return factory; } @Override public ActivityInitListener createActivityInitListener(Predicate onInitListener) { return new LauncherInitListener((activity, alreadyOnHome) -> onInitListener.test(alreadyOnHome)); } @Override public void setOnDeferredActivityLaunchCallback(Runnable r) { Launcher launcher = getCreatedActivity(); if (launcher == null) { return; } launcher.setOnDeferredActivityLaunchCallback(r); } @Nullable @Override public BaseQuickstepLauncher getCreatedActivity() { return BaseQuickstepLauncher.ACTIVITY_TRACKER.getCreatedActivity(); } @Nullable @Override public DepthController getDepthController() { BaseQuickstepLauncher launcher = getCreatedActivity(); if (launcher == null) { return null; } return launcher.getDepthController(); } @Nullable @Override public LauncherTaskbarUIController getTaskbarController() { BaseQuickstepLauncher launcher = getCreatedActivity(); if (launcher == null) { return null; } return launcher.getTaskbarUIController(); } @Nullable @Override public RecentsView getVisibleRecentsView() { Launcher launcher = getVisibleLauncher(); RecentsView recentsView = launcher != null && launcher.getStateManager().getState().overviewUi ? launcher.getOverviewPanel() : null; if (recentsView == null || (!launcher.hasBeenResumed() && recentsView.getRunningTaskViewId() == -1)) { // If live tile has ended, return null. return null; } return recentsView; } @Nullable @UiThread private Launcher getVisibleLauncher() { Launcher launcher = getCreatedActivity(); return (launcher != null) && launcher.isStarted() && ((ENABLE_QUICKSTEP_LIVE_TILE.get() && isInLiveTileMode()) || launcher.hasBeenResumed()) ? launcher : null; } @Override public boolean switchToRecentsIfVisible(Runnable onCompleteCallback) { Launcher launcher = getVisibleLauncher(); if (launcher == null) { return false; } if (ENABLE_QUICKSTEP_LIVE_TILE.get() && isInLiveTileMode()) { RecentsView recentsView = getVisibleRecentsView(); if (recentsView == null) { return false; } } closeOverlay(); launcher.getStateManager().goToState(OVERVIEW, launcher.getStateManager().shouldAnimateStateChange(), onCompleteCallback == null ? null : forEndCallback(onCompleteCallback)); return true; } @Override public void onExitOverview(RotationTouchHelper deviceState, Runnable exitRunnable) { final StateManager stateManager = getCreatedActivity().getStateManager(); stateManager.addStateListener( new StateManager.StateListener() { @Override public void onStateTransitionComplete(LauncherState toState) { // Are we going from Recents to Workspace? if (toState == LauncherState.NORMAL || toState == LauncherState.ALL_APPS) { exitRunnable.run(); notifyRecentsOfOrientation(deviceState); stateManager.removeStateListener(this); } } }); } private void notifyRecentsOfOrientation(RotationTouchHelper rotationTouchHelper) { // reset layout on swipe to home RecentsView recentsView = getCreatedActivity().getOverviewPanel(); recentsView.setLayoutRotation(rotationTouchHelper.getCurrentActiveRotation(), rotationTouchHelper.getDisplayRotation()); } @Override public Rect getOverviewWindowBounds(Rect homeBounds, RemoteAnimationTargetCompat target) { return homeBounds; } @Override public boolean allowMinimizeSplitScreen() { return true; } @Override public boolean isInLiveTileMode() { Launcher launcher = getCreatedActivity(); return launcher != null && launcher.getStateManager().getState() == OVERVIEW && launcher.isStarted(); } @Override public void onLaunchTaskFailed() { Launcher launcher = getCreatedActivity(); if (launcher == null) { return; } launcher.getStateManager().goToState(OVERVIEW); } @Override public void closeOverlay() { Launcher launcher = getCreatedActivity(); if (launcher == null) { return; } LauncherOverlayManager om = launcher.getOverlayManager(); if (!launcher.isStarted() || launcher.isForceInvisible()) { om.hideOverlay(false /* animate */); } else { om.hideOverlay(150); } LauncherTaskbarUIController taskbarController = getTaskbarController(); if (taskbarController != null) { taskbarController.hideEdu(); } } @Override public @Nullable Animator getParallelAnimationToLauncher(GestureEndTarget endTarget, long duration, RecentsAnimationCallbacks callbacks) { LauncherTaskbarUIController uiController = getTaskbarController(); Animator superAnimator = super.getParallelAnimationToLauncher( endTarget, duration, callbacks); if (uiController == null || callbacks == null) { return superAnimator; } LauncherState toState = stateFromGestureEndTarget(endTarget); Animator taskbarAnimator = uiController.createAnimToLauncher(toState, callbacks, duration); if (superAnimator == null) { return taskbarAnimator; } else { AnimatorSet animatorSet = new AnimatorSet(); animatorSet.playTogether(superAnimator, taskbarAnimator); return animatorSet; } } @Override protected int getOverviewScrimColorForState(BaseQuickstepLauncher launcher, LauncherState state) { return state.getWorkspaceScrimColor(launcher); } @Override public boolean deferStartingActivity(RecentsAnimationDeviceState deviceState, MotionEvent ev) { LauncherTaskbarUIController uiController = getTaskbarController(); if (uiController == null) { return super.deferStartingActivity(deviceState, ev); } return uiController.isEventOverAnyTaskbarItem(ev); } @Override public boolean shouldCancelCurrentGesture() { LauncherTaskbarUIController uiController = getTaskbarController(); if (uiController == null) { return super.shouldCancelCurrentGesture(); } return uiController.isDraggingItem(); } @Override public LauncherState stateFromGestureEndTarget(GestureEndTarget endTarget) { switch (endTarget) { case RECENTS: return OVERVIEW; case NEW_TASK: case LAST_TASK: return QUICK_SWITCH; case HOME: default: return NORMAL; } } }