1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.quickstep.views;
17 
18 import static android.app.ActivityTaskManager.INVALID_TASK_ID;
19 
20 import static com.android.launcher3.LauncherState.ALL_APPS;
21 import static com.android.launcher3.LauncherState.CLEAR_ALL_BUTTON;
22 import static com.android.launcher3.LauncherState.EDIT_MODE;
23 import static com.android.launcher3.LauncherState.NORMAL;
24 import static com.android.launcher3.LauncherState.OVERVIEW;
25 import static com.android.launcher3.LauncherState.OVERVIEW_MODAL_TASK;
26 import static com.android.launcher3.LauncherState.OVERVIEW_SPLIT_SELECT;
27 import static com.android.launcher3.LauncherState.SPRING_LOADED;
28 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SPLIT_SELECTION_EXIT_HOME;
29 import static com.android.window.flags.Flags.enableDesktopWindowingWallpaperActivity;
30 
31 import android.annotation.TargetApi;
32 import android.content.Context;
33 import android.os.Build;
34 import android.util.AttributeSet;
35 import android.view.MotionEvent;
36 
37 import androidx.annotation.Nullable;
38 
39 import com.android.launcher3.AbstractFloatingView;
40 import com.android.launcher3.Launcher;
41 import com.android.launcher3.LauncherState;
42 import com.android.launcher3.config.FeatureFlags;
43 import com.android.launcher3.desktop.DesktopRecentsTransitionController;
44 import com.android.launcher3.logging.StatsLogManager;
45 import com.android.launcher3.statehandlers.DepthController;
46 import com.android.launcher3.statehandlers.DesktopVisibilityController;
47 import com.android.launcher3.statemanager.StateManager;
48 import com.android.launcher3.statemanager.StateManager.StateListener;
49 import com.android.launcher3.uioverrides.QuickstepLauncher;
50 import com.android.launcher3.util.PendingSplitSelectInfo;
51 import com.android.launcher3.util.SplitConfigurationOptions;
52 import com.android.launcher3.util.SplitConfigurationOptions.SplitSelectSource;
53 import com.android.quickstep.GestureState;
54 import com.android.quickstep.LauncherActivityInterface;
55 import com.android.quickstep.RotationTouchHelper;
56 import com.android.quickstep.SystemUiProxy;
57 import com.android.quickstep.util.SplitSelectStateController;
58 import com.android.systemui.shared.recents.model.Task;
59 
60 import kotlin.Unit;
61 
62 /**
63  * {@link RecentsView} used in Launcher activity
64  */
65 @TargetApi(Build.VERSION_CODES.O)
66 public class LauncherRecentsView extends RecentsView<QuickstepLauncher, LauncherState>
67         implements StateListener<LauncherState> {
68 
LauncherRecentsView(Context context)69     public LauncherRecentsView(Context context) {
70         this(context, null);
71     }
72 
LauncherRecentsView(Context context, @Nullable AttributeSet attrs)73     public LauncherRecentsView(Context context, @Nullable AttributeSet attrs) {
74         this(context, attrs, 0);
75     }
76 
LauncherRecentsView(Context context, @Nullable AttributeSet attrs, int defStyleAttr)77     public LauncherRecentsView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
78         super(context, attrs, defStyleAttr, LauncherActivityInterface.INSTANCE);
79         getStateManager().addStateListener(this);
80     }
81 
82     @Override
init(OverviewActionsView actionsView, SplitSelectStateController splitPlaceholderView, @Nullable DesktopRecentsTransitionController desktopRecentsTransitionController)83     public void init(OverviewActionsView actionsView,
84             SplitSelectStateController splitPlaceholderView,
85             @Nullable DesktopRecentsTransitionController desktopRecentsTransitionController) {
86         super.init(actionsView, splitPlaceholderView, desktopRecentsTransitionController);
87         setContentAlpha(0);
88     }
89 
90     @Override
handleStartHome(boolean animated)91     protected void handleStartHome(boolean animated) {
92         StateManager stateManager = getStateManager();
93         animated &= stateManager.shouldAnimateStateChange();
94         stateManager.goToState(NORMAL, animated);
95         if (FeatureFlags.enableSplitContextually()) {
96             mSplitSelectStateController.getSplitAnimationController()
97                     .playPlaceholderDismissAnim(mContainer, LAUNCHER_SPLIT_SELECTION_EXIT_HOME);
98         }
99         AbstractFloatingView.closeAllOpenViews(mContainer, animated);
100     }
101 
102     @Override
canStartHomeSafely()103     protected boolean canStartHomeSafely() {
104         return mContainer.canStartHomeSafely();
105     }
106 
107     @Override
getStateManager()108     public StateManager<LauncherState, Launcher> getStateManager() {
109         return mContainer.getStateManager();
110     }
111 
112     @Override
onTaskLaunchAnimationEnd(boolean success)113     protected Unit onTaskLaunchAnimationEnd(boolean success) {
114         if (success) {
115             getStateManager().moveToRestState();
116         } else {
117             LauncherState state = getStateManager().getState();
118             mContainer.getAllAppsController().setState(state);
119         }
120         super.onTaskLaunchAnimationEnd(success);
121         return Unit.INSTANCE;
122     }
123 
124     @Override
onTaskIconChanged(int taskId)125     public void onTaskIconChanged(int taskId) {
126         super.onTaskIconChanged(taskId);
127         // If Launcher needs to return to split select state, do it now, after the icon has updated.
128         if (mContainer.hasPendingSplitSelectInfo()) {
129             PendingSplitSelectInfo recoveryData = mContainer.getPendingSplitSelectInfo();
130             if (recoveryData.getStagedTaskId() == taskId) {
131                 initiateSplitSelect(
132                         getTaskViewByTaskId(recoveryData.getStagedTaskId()),
133                         recoveryData.getStagePosition(), recoveryData.getSource()
134                 );
135                 mContainer.finishSplitSelectRecovery();
136             }
137         }
138     }
139 
140     @Override
reset()141     public void reset() {
142         super.reset();
143 
144         int recentsActivityRotation = getPagedViewOrientedState().getRecentsActivityRotation();
145         setLayoutRotation(recentsActivityRotation, recentsActivityRotation);
146     }
147 
148     @Override
onStateTransitionStart(LauncherState toState)149     public void onStateTransitionStart(LauncherState toState) {
150         setOverviewStateEnabled(toState.isRecentsViewVisible);
151 
152         setOverviewGridEnabled(toState.displayOverviewTasksAsGrid(mContainer.getDeviceProfile()));
153         setOverviewFullscreenEnabled(toState.getOverviewFullscreenProgress() == 1);
154         if (toState == OVERVIEW_MODAL_TASK) {
155             setOverviewSelectEnabled(true);
156         } else {
157             resetModalVisuals();
158         }
159 
160         // Set border after select mode changes to avoid showing border during state transition
161         if (!toState.isRecentsViewVisible || toState == OVERVIEW_MODAL_TASK) {
162             setTaskBorderEnabled(false);
163         }
164 
165         setFreezeViewVisibility(true);
166         if (mContainer.getDesktopVisibilityController() != null) {
167             mContainer.getDesktopVisibilityController().onLauncherStateChanged(toState);
168         }
169     }
170 
171     @Override
onStateTransitionComplete(LauncherState finalState)172     public void onStateTransitionComplete(LauncherState finalState) {
173         if (finalState == NORMAL || finalState == SPRING_LOADED  || finalState == EDIT_MODE
174                 || finalState == ALL_APPS) {
175             // Clean-up logic that occurs when recents is no longer in use/visible.
176             reset();
177         }
178         boolean isOverlayEnabled = finalState == OVERVIEW || finalState == OVERVIEW_MODAL_TASK;
179         setOverlayEnabled(isOverlayEnabled);
180         setFreezeViewVisibility(false);
181         if (finalState != OVERVIEW_MODAL_TASK) {
182             setOverviewSelectEnabled(false);
183         }
184 
185         if (finalState.isRecentsViewVisible && finalState != OVERVIEW_MODAL_TASK) {
186             setTaskBorderEnabled(true);
187         }
188 
189         if (isOverlayEnabled) {
190             runActionOnRemoteHandles(remoteTargetHandle ->
191                     remoteTargetHandle.getTaskViewSimulator().setDrawsBelowRecents(true));
192         }
193     }
194 
195     @Override
setOverviewStateEnabled(boolean enabled)196     public void setOverviewStateEnabled(boolean enabled) {
197         super.setOverviewStateEnabled(enabled);
198         if (enabled) {
199             LauncherState state = getStateManager().getState();
200             boolean hasClearAllButton = (state.getVisibleElements(mContainer)
201                     & CLEAR_ALL_BUTTON) != 0;
202             setDisallowScrollToClearAll(!hasClearAllButton);
203         }
204     }
205 
206     @Override
onTouchEvent(MotionEvent ev)207     public boolean onTouchEvent(MotionEvent ev) {
208         boolean result = super.onTouchEvent(ev);
209         // Do not let touch escape to siblings below this view.
210         return result || getStateManager().getState().isRecentsViewVisible;
211     }
212 
213     @Override
getDepthController()214     protected DepthController getDepthController() {
215         return mContainer.getDepthController();
216     }
217 
218     @Override
setModalStateEnabled(int taskId, boolean animate)219     public void setModalStateEnabled(int taskId, boolean animate) {
220         if (taskId != INVALID_TASK_ID) {
221             setSelectedTask(taskId);
222             getStateManager().goToState(LauncherState.OVERVIEW_MODAL_TASK, animate);
223         } else if (mContainer.isInState(LauncherState.OVERVIEW_MODAL_TASK)) {
224             getStateManager().goToState(LauncherState.OVERVIEW, animate);
225         }
226     }
227 
228     @Override
onDismissAnimationEnds()229     protected void onDismissAnimationEnds() {
230         super.onDismissAnimationEnds();
231         if (mContainer.isInState(OVERVIEW_SPLIT_SELECT)) {
232             // We want to keep the tasks translations in this temporary state
233             // after resetting the rest above
234             setTaskViewsPrimarySplitTranslation(mTaskViewsPrimarySplitTranslation);
235             setTaskViewsSecondarySplitTranslation(mTaskViewsSecondarySplitTranslation);
236         }
237     }
238 
239     @Override
initiateSplitSelect(TaskView taskView, @SplitConfigurationOptions.StagePosition int stagePosition, StatsLogManager.EventEnum splitEvent)240     public void initiateSplitSelect(TaskView taskView,
241             @SplitConfigurationOptions.StagePosition int stagePosition,
242             StatsLogManager.EventEnum splitEvent) {
243         super.initiateSplitSelect(taskView, stagePosition, splitEvent);
244         getStateManager().goToState(LauncherState.OVERVIEW_SPLIT_SELECT);
245     }
246 
247     @Override
initiateSplitSelect(SplitSelectSource splitSelectSource)248     public void initiateSplitSelect(SplitSelectSource splitSelectSource) {
249         super.initiateSplitSelect(splitSelectSource);
250         getStateManager().goToState(LauncherState.OVERVIEW_SPLIT_SELECT);
251     }
252 
253     @Override
canLaunchFullscreenTask()254     protected boolean canLaunchFullscreenTask() {
255         if (FeatureFlags.enableSplitContextually()) {
256             return !mSplitSelectStateController.isSplitSelectActive();
257         } else {
258             return !mContainer.isInState(OVERVIEW_SPLIT_SELECT);
259         }
260     }
261 
262     @Override
onGestureAnimationStart(Task[] runningTasks, RotationTouchHelper rotationTouchHelper)263     public void onGestureAnimationStart(Task[] runningTasks,
264             RotationTouchHelper rotationTouchHelper) {
265         super.onGestureAnimationStart(runningTasks, rotationTouchHelper);
266         DesktopVisibilityController desktopVisibilityController =
267                 mContainer.getDesktopVisibilityController();
268         if (!enableDesktopWindowingWallpaperActivity() && desktopVisibilityController != null) {
269             // TODO: b/333533253 - Remove after flag rollout
270             desktopVisibilityController.setRecentsGestureStart();
271         }
272     }
273 
274     @Override
onGestureAnimationEnd()275     public void onGestureAnimationEnd() {
276         DesktopVisibilityController desktopVisibilityController =
277                 mContainer.getDesktopVisibilityController();
278         boolean showDesktopApps = false;
279         GestureState.GestureEndTarget endTarget = null;
280         if (desktopVisibilityController != null) {
281             desktopVisibilityController = mContainer.getDesktopVisibilityController();
282             endTarget = mCurrentGestureEndTarget;
283             if (endTarget == GestureState.GestureEndTarget.LAST_TASK
284                     && desktopVisibilityController.areDesktopTasksVisible()) {
285                 // Recents gesture was cancelled and we are returning to the previous task.
286                 // After super class has handled clean up, show desktop apps on top again
287                 showDesktopApps = true;
288             }
289         }
290         super.onGestureAnimationEnd();
291         if (!enableDesktopWindowingWallpaperActivity() && desktopVisibilityController != null) {
292             // TODO: b/333533253 - Remove after flag rollout
293             desktopVisibilityController.setRecentsGestureEnd(endTarget);
294         }
295         if (showDesktopApps) {
296             SystemUiProxy.INSTANCE.get(mContainer).showDesktopApps(mContainer.getDisplayId(),
297                     null /* transition */);
298         }
299     }
300 }
301