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