• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.fallback;
17 
18 import static android.app.ActivityTaskManager.INVALID_TASK_ID;
19 
20 import static com.android.quickstep.GestureState.GestureEndTarget.RECENTS;
21 import static com.android.quickstep.fallback.RecentsState.DEFAULT;
22 import static com.android.quickstep.fallback.RecentsState.HOME;
23 import static com.android.quickstep.fallback.RecentsState.MODAL_TASK;
24 import static com.android.quickstep.fallback.RecentsState.OVERVIEW_SPLIT_SELECT;
25 
26 import android.animation.AnimatorSet;
27 import android.annotation.TargetApi;
28 import android.content.Context;
29 import android.os.Build;
30 import android.util.AttributeSet;
31 import android.view.MotionEvent;
32 
33 import androidx.annotation.Nullable;
34 
35 import com.android.launcher3.AbstractFloatingView;
36 import com.android.launcher3.anim.AnimatorPlaybackController;
37 import com.android.launcher3.anim.PendingAnimation;
38 import com.android.launcher3.config.FeatureFlags;
39 import com.android.launcher3.logging.StatsLogManager;
40 import com.android.launcher3.statemanager.StateManager.StateListener;
41 import com.android.launcher3.util.SplitConfigurationOptions;
42 import com.android.launcher3.util.SplitConfigurationOptions.SplitSelectSource;
43 import com.android.quickstep.FallbackActivityInterface;
44 import com.android.quickstep.GestureState;
45 import com.android.quickstep.RecentsActivity;
46 import com.android.quickstep.RotationTouchHelper;
47 import com.android.quickstep.util.GroupTask;
48 import com.android.quickstep.util.SplitSelectStateController;
49 import com.android.quickstep.util.TaskViewSimulator;
50 import com.android.quickstep.views.OverviewActionsView;
51 import com.android.quickstep.views.RecentsView;
52 import com.android.quickstep.views.TaskView;
53 import com.android.systemui.shared.recents.model.Task;
54 
55 import java.util.ArrayList;
56 
57 @TargetApi(Build.VERSION_CODES.R)
58 public class FallbackRecentsView extends RecentsView<RecentsActivity, RecentsState>
59         implements StateListener<RecentsState> {
60 
61     private static final int TASK_DISMISS_DURATION = 150;
62 
63     @Nullable
64     private Task mHomeTask;
65 
FallbackRecentsView(Context context, AttributeSet attrs)66     public FallbackRecentsView(Context context, AttributeSet attrs) {
67         this(context, attrs, 0);
68     }
69 
FallbackRecentsView(Context context, AttributeSet attrs, int defStyleAttr)70     public FallbackRecentsView(Context context, AttributeSet attrs, int defStyleAttr) {
71         super(context, attrs, defStyleAttr, FallbackActivityInterface.INSTANCE);
72         mActivity.getStateManager().addStateListener(this);
73     }
74 
75     @Override
init(OverviewActionsView actionsView, SplitSelectStateController splitController)76     public void init(OverviewActionsView actionsView, SplitSelectStateController splitController) {
77         super.init(actionsView, splitController);
78         setOverviewStateEnabled(true);
79         setOverlayEnabled(true);
80     }
81 
82     @Override
handleStartHome(boolean animated)83     protected void handleStartHome(boolean animated) {
84         mActivity.startHome();
85         AbstractFloatingView.closeAllOpenViews(mActivity, mActivity.isStarted());
86     }
87 
88     @Override
canStartHomeSafely()89     protected boolean canStartHomeSafely() {
90         return mActivity.canStartHomeSafely();
91     }
92 
93     /**
94      * When starting gesture interaction from home, we add a temporary invisible tile corresponding
95      * to the home task. This allows us to handle quick-switch similarly to a quick-switching
96      * from a foreground task.
97      */
onGestureAnimationStartOnHome(Task[] homeTask, RotationTouchHelper rotationTouchHelper)98     public void onGestureAnimationStartOnHome(Task[] homeTask,
99             RotationTouchHelper rotationTouchHelper) {
100         // TODO(b/195607777) General fallback love, but this might be correct
101         //  Home task should be defined as the front-most task info I think?
102         mHomeTask = homeTask.length > 0 ? homeTask[0] : null;
103         onGestureAnimationStart(homeTask, rotationTouchHelper);
104     }
105 
106     /**
107      * When the gesture ends and we're going to recents view, we also remove the temporary
108      * invisible tile added for the home task. This also pushes the remaining tiles back
109      * to the center.
110      */
111     @Override
onPrepareGestureEndAnimation( @ullable AnimatorSet animatorSet, GestureState.GestureEndTarget endTarget, TaskViewSimulator[] taskViewSimulators)112     public void onPrepareGestureEndAnimation(
113             @Nullable AnimatorSet animatorSet, GestureState.GestureEndTarget endTarget,
114             TaskViewSimulator[] taskViewSimulators) {
115         super.onPrepareGestureEndAnimation(animatorSet, endTarget, taskViewSimulators);
116         if (mHomeTask != null && endTarget == RECENTS && animatorSet != null) {
117             TaskView tv = getTaskViewByTaskId(mHomeTask.key.id);
118             if (tv != null) {
119                 PendingAnimation pa = new PendingAnimation(TASK_DISMISS_DURATION);
120                 createTaskDismissAnimation(pa, tv, true, false,
121                         TASK_DISMISS_DURATION, false /* dismissingForSplitSelection*/);
122                 pa.addEndListener(e -> setCurrentTask(-1));
123                 AnimatorPlaybackController controller = pa.createPlaybackController();
124                 controller.dispatchOnStart();
125                 animatorSet.play(controller.getAnimationPlayer());
126             }
127         }
128     }
129 
130     @Override
onGestureAnimationEnd()131     public void onGestureAnimationEnd() {
132         if (mCurrentGestureEndTarget == GestureState.GestureEndTarget.HOME) {
133             // Clean-up logic that occurs when recents is no longer in use/visible.
134             reset();
135         }
136         super.onGestureAnimationEnd();
137     }
138 
139     @Override
setCurrentTask(int runningTaskViewId)140     public void setCurrentTask(int runningTaskViewId) {
141         super.setCurrentTask(runningTaskViewId);
142         int runningTaskId = getTaskIdsForRunningTaskView()[0];
143         if (mHomeTask != null && mHomeTask.key.id != runningTaskId) {
144             mHomeTask = null;
145             setRunningTaskHidden(false);
146         }
147     }
148 
149     @Nullable
150     @Override
getHomeTaskView()151     protected TaskView getHomeTaskView() {
152         return mHomeTask != null ? getTaskViewByTaskId(mHomeTask.key.id) : null;
153     }
154 
155     @Override
shouldAddStubTaskView(Task[] runningTasks)156     protected boolean shouldAddStubTaskView(Task[] runningTasks) {
157         if (runningTasks.length > 1) {
158             // can't be in split screen w/ home task
159             return super.shouldAddStubTaskView(runningTasks);
160         }
161 
162         Task runningTask = runningTasks[0];
163         if (mHomeTask != null && runningTask != null
164                 && mHomeTask.key.id == runningTask.key.id
165                 && getTaskViewCount() == 0 && mLoadPlanEverApplied) {
166             // Do not add a stub task if we are running over home with empty recents, so that we
167             // show the empty recents message instead of showing a stub task and later removing it.
168             // Ignore empty task signal if applyLoadPlan has never run.
169             return false;
170         }
171         return super.shouldAddStubTaskView(runningTasks);
172     }
173 
174     @Override
applyLoadPlan(ArrayList<GroupTask> taskGroups)175     protected void applyLoadPlan(ArrayList<GroupTask> taskGroups) {
176         // When quick-switching on 3p-launcher, we add a "stub" tile corresponding to Launcher
177         // as well. This tile is never shown as we have setCurrentTaskHidden, but allows use to
178         // track the index of the next task appropriately, as if we are switching on any other app.
179         // TODO(b/195607777) Confirm home task info is front-most task and not mixed in with others
180         int runningTaskId = getTaskIdsForRunningTaskView()[0];
181         if (mHomeTask != null && mHomeTask.key.id == runningTaskId
182                 && !taskGroups.isEmpty()) {
183             // Check if the task list has running task
184             boolean found = false;
185             for (GroupTask group : taskGroups) {
186                 if (group.containsTask(runningTaskId)) {
187                     found = true;
188                     break;
189                 }
190             }
191             if (!found) {
192                 ArrayList<GroupTask> newList = new ArrayList<>(taskGroups.size() + 1);
193                 newList.addAll(taskGroups);
194                 newList.add(new GroupTask(mHomeTask, null, null));
195                 taskGroups = newList;
196             }
197         }
198         super.applyLoadPlan(taskGroups);
199     }
200 
201     @Override
setRunningTaskHidden(boolean isHidden)202     public void setRunningTaskHidden(boolean isHidden) {
203         if (mHomeTask != null) {
204             // Always keep the home task hidden
205             isHidden = true;
206         }
207         super.setRunningTaskHidden(isHidden);
208     }
209 
210     @Override
setModalStateEnabled(int taskId, boolean animate)211     public void setModalStateEnabled(int taskId, boolean animate) {
212         if (taskId != INVALID_TASK_ID) {
213             setSelectedTask(taskId);
214             mActivity.getStateManager().goToState(RecentsState.MODAL_TASK, animate);
215         } else {
216             if (mActivity.isInState(RecentsState.MODAL_TASK)) {
217                 mActivity.getStateManager().goToState(DEFAULT, animate);
218                 resetModalVisuals();
219             }
220         }
221     }
222 
223     @Override
initiateSplitSelect(TaskView taskView, @SplitConfigurationOptions.StagePosition int stagePosition, StatsLogManager.EventEnum splitEvent)224     public void initiateSplitSelect(TaskView taskView,
225             @SplitConfigurationOptions.StagePosition int stagePosition,
226             StatsLogManager.EventEnum splitEvent) {
227         super.initiateSplitSelect(taskView, stagePosition, splitEvent);
228         mActivity.getStateManager().goToState(OVERVIEW_SPLIT_SELECT);
229     }
230 
231     @Override
onStateTransitionStart(RecentsState toState)232     public void onStateTransitionStart(RecentsState toState) {
233         setOverviewStateEnabled(true);
234         setOverviewGridEnabled(toState.displayOverviewTasksAsGrid(mActivity.getDeviceProfile()));
235         setOverviewFullscreenEnabled(toState.isFullScreen());
236         if (toState == MODAL_TASK) {
237             setOverviewSelectEnabled(true);
238         }
239         setFreezeViewVisibility(true);
240     }
241 
242     @Override
onStateTransitionComplete(RecentsState finalState)243     public void onStateTransitionComplete(RecentsState finalState) {
244         if (finalState == HOME) {
245             // Clean-up logic that occurs when recents is no longer in use/visible.
246             reset();
247         }
248         boolean isOverlayEnabled = finalState == DEFAULT || finalState == MODAL_TASK;
249         setOverlayEnabled(isOverlayEnabled);
250         setFreezeViewVisibility(false);
251         if (finalState != MODAL_TASK) {
252             setOverviewSelectEnabled(false);
253         }
254         if (finalState != OVERVIEW_SPLIT_SELECT) {
255             if (FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get()) {
256                 mSplitSelectStateController.resetState();
257             } else {
258                 resetFromSplitSelectionState();
259             }
260         }
261 
262         if (isOverlayEnabled) {
263             runActionOnRemoteHandles(remoteTargetHandle ->
264                     remoteTargetHandle.getTaskViewSimulator().setDrawsBelowRecents(true));
265         }
266     }
267 
268     @Override
setOverviewStateEnabled(boolean enabled)269     public void setOverviewStateEnabled(boolean enabled) {
270         super.setOverviewStateEnabled(enabled);
271         if (enabled) {
272             RecentsState state = mActivity.getStateManager().getState();
273             setDisallowScrollToClearAll(!state.hasClearAllButton());
274         }
275     }
276 
277     @Override
onTouchEvent(MotionEvent ev)278     public boolean onTouchEvent(MotionEvent ev) {
279         boolean result = super.onTouchEvent(ev);
280         // Do not let touch escape to siblings below this view.
281         return result || mActivity.getStateManager().getState().overviewUi();
282     }
283 
284     @Override
initiateSplitSelect(SplitSelectSource splitSelectSource)285     public void initiateSplitSelect(SplitSelectSource splitSelectSource) {
286         super.initiateSplitSelect(splitSelectSource);
287         mActivity.getStateManager().goToState(OVERVIEW_SPLIT_SELECT);
288     }
289 
290     @Override
canLaunchFullscreenTask()291     protected boolean canLaunchFullscreenTask() {
292         return !mActivity.isInState(OVERVIEW_SPLIT_SELECT);
293     }
294 }
295