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