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