1 /* 2 * Copyright (C) 2020 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 17 package com.android.wm.shell.splitscreen; 18 19 import static android.app.ActivityTaskManager.INVALID_TASK_ID; 20 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; 21 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; 22 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; 23 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; 24 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; 25 26 import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS; 27 28 import android.annotation.CallSuper; 29 import android.annotation.Nullable; 30 import android.app.ActivityManager; 31 import android.content.Context; 32 import android.graphics.Point; 33 import android.graphics.Rect; 34 import android.util.SparseArray; 35 import android.view.SurfaceControl; 36 import android.view.SurfaceSession; 37 import android.window.WindowContainerToken; 38 import android.window.WindowContainerTransaction; 39 40 import androidx.annotation.NonNull; 41 42 import com.android.internal.R; 43 import com.android.launcher3.icons.IconProvider; 44 import com.android.wm.shell.ShellTaskOrganizer; 45 import com.android.wm.shell.common.SurfaceUtils; 46 import com.android.wm.shell.common.SyncTransactionQueue; 47 import com.android.wm.shell.common.split.SplitDecorManager; 48 import com.android.wm.shell.splitscreen.SplitScreen.StageType; 49 50 import java.io.PrintWriter; 51 52 /** 53 * Base class that handle common task org. related for split-screen stages. 54 * Note that this class and its sub-class do not directly perform hierarchy operations. 55 * They only serve to hold a collection of tasks and provide APIs like 56 * {@link #setBounds(Rect, WindowContainerTransaction)} for the centralized {@link StageCoordinator} 57 * to perform operations in-sync with other containers. 58 * 59 * @see StageCoordinator 60 */ 61 class StageTaskListener implements ShellTaskOrganizer.TaskListener { 62 private static final String TAG = StageTaskListener.class.getSimpleName(); 63 64 protected static final int[] CONTROLLED_ACTIVITY_TYPES = {ACTIVITY_TYPE_STANDARD}; 65 protected static final int[] CONTROLLED_WINDOWING_MODES = 66 {WINDOWING_MODE_FULLSCREEN, WINDOWING_MODE_UNDEFINED}; 67 protected static final int[] CONTROLLED_WINDOWING_MODES_WHEN_ACTIVE = 68 {WINDOWING_MODE_FULLSCREEN, WINDOWING_MODE_UNDEFINED, WINDOWING_MODE_MULTI_WINDOW}; 69 70 /** Callback interface for listening to changes in a split-screen stage. */ 71 public interface StageListenerCallbacks { onRootTaskAppeared()72 void onRootTaskAppeared(); 73 onStatusChanged(boolean visible, boolean hasChildren)74 void onStatusChanged(boolean visible, boolean hasChildren); 75 onChildTaskStatusChanged(int taskId, boolean present, boolean visible)76 void onChildTaskStatusChanged(int taskId, boolean present, boolean visible); 77 onChildTaskEnterPip(int taskId)78 void onChildTaskEnterPip(int taskId); 79 onRootTaskVanished()80 void onRootTaskVanished(); 81 onNoLongerSupportMultiWindow()82 void onNoLongerSupportMultiWindow(); 83 } 84 85 private final Context mContext; 86 private final StageListenerCallbacks mCallbacks; 87 private final SurfaceSession mSurfaceSession; 88 private final SyncTransactionQueue mSyncQueue; 89 private final IconProvider mIconProvider; 90 91 protected ActivityManager.RunningTaskInfo mRootTaskInfo; 92 protected SurfaceControl mRootLeash; 93 protected SurfaceControl mDimLayer; 94 protected SparseArray<ActivityManager.RunningTaskInfo> mChildrenTaskInfo = new SparseArray<>(); 95 private final SparseArray<SurfaceControl> mChildrenLeashes = new SparseArray<>(); 96 // TODO(b/204308910): Extracts SplitDecorManager related code to common package. 97 private SplitDecorManager mSplitDecorManager; 98 99 private final StageTaskUnfoldController mStageTaskUnfoldController; 100 StageTaskListener(Context context, ShellTaskOrganizer taskOrganizer, int displayId, StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue, SurfaceSession surfaceSession, IconProvider iconProvider, @Nullable StageTaskUnfoldController stageTaskUnfoldController)101 StageTaskListener(Context context, ShellTaskOrganizer taskOrganizer, int displayId, 102 StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue, 103 SurfaceSession surfaceSession, IconProvider iconProvider, 104 @Nullable StageTaskUnfoldController stageTaskUnfoldController) { 105 mContext = context; 106 mCallbacks = callbacks; 107 mSyncQueue = syncQueue; 108 mSurfaceSession = surfaceSession; 109 mIconProvider = iconProvider; 110 mStageTaskUnfoldController = stageTaskUnfoldController; 111 112 // No need to create root task if the device is using legacy split screen. 113 // TODO(b/199236198): Remove this check after totally deprecated legacy split. 114 if (!context.getResources().getBoolean(R.bool.config_useLegacySplit)) { 115 taskOrganizer.createRootTask(displayId, WINDOWING_MODE_MULTI_WINDOW, this); 116 } 117 } 118 getChildCount()119 int getChildCount() { 120 return mChildrenTaskInfo.size(); 121 } 122 containsTask(int taskId)123 boolean containsTask(int taskId) { 124 return mChildrenTaskInfo.contains(taskId); 125 } 126 127 /** 128 * Returns the top visible child task's id. 129 */ getTopVisibleChildTaskId()130 int getTopVisibleChildTaskId() { 131 for (int i = mChildrenTaskInfo.size() - 1; i >= 0; --i) { 132 final ActivityManager.RunningTaskInfo info = mChildrenTaskInfo.valueAt(i); 133 if (info.isVisible) { 134 return info.taskId; 135 } 136 } 137 return INVALID_TASK_ID; 138 } 139 140 /** 141 * Returns the top activity uid for the top child task. 142 */ getTopChildTaskUid()143 int getTopChildTaskUid() { 144 for (int i = mChildrenTaskInfo.size() - 1; i >= 0; --i) { 145 final ActivityManager.RunningTaskInfo info = mChildrenTaskInfo.valueAt(i); 146 if (info.topActivityInfo == null) { 147 continue; 148 } 149 return info.topActivityInfo.applicationInfo.uid; 150 } 151 return 0; 152 } 153 154 /** @return {@code true} if this listener contains the currently focused task. */ isFocused()155 boolean isFocused() { 156 if (mRootTaskInfo == null) { 157 return false; 158 } 159 160 if (mRootTaskInfo.isFocused) { 161 return true; 162 } 163 164 for (int i = mChildrenTaskInfo.size() - 1; i >= 0; --i) { 165 if (mChildrenTaskInfo.valueAt(i).isFocused) { 166 return true; 167 } 168 } 169 170 return false; 171 } 172 173 @Override 174 @CallSuper onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash)175 public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) { 176 if (mRootTaskInfo == null && !taskInfo.hasParentTask()) { 177 mRootLeash = leash; 178 mRootTaskInfo = taskInfo; 179 mSplitDecorManager = new SplitDecorManager( 180 mRootTaskInfo.configuration, 181 mIconProvider, 182 mSurfaceSession); 183 mCallbacks.onRootTaskAppeared(); 184 sendStatusChanged(); 185 mSyncQueue.runInSync(t -> mDimLayer = 186 SurfaceUtils.makeDimLayer(t, mRootLeash, "Dim layer", mSurfaceSession)); 187 } else if (taskInfo.parentTaskId == mRootTaskInfo.taskId) { 188 final int taskId = taskInfo.taskId; 189 mChildrenLeashes.put(taskId, leash); 190 mChildrenTaskInfo.put(taskId, taskInfo); 191 updateChildTaskSurface(taskInfo, leash, true /* firstAppeared */); 192 mCallbacks.onChildTaskStatusChanged(taskId, true /* present */, taskInfo.isVisible); 193 if (ENABLE_SHELL_TRANSITIONS) { 194 // Status is managed/synchronized by the transition lifecycle. 195 return; 196 } 197 sendStatusChanged(); 198 } else { 199 throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo 200 + "\n mRootTaskInfo: " + mRootTaskInfo); 201 } 202 203 if (mStageTaskUnfoldController != null) { 204 mStageTaskUnfoldController.onTaskAppeared(taskInfo, leash); 205 } 206 } 207 208 @Override 209 @CallSuper onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo)210 public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) { 211 if (!taskInfo.supportsMultiWindow) { 212 // Leave split screen if the task no longer supports multi window. 213 mCallbacks.onNoLongerSupportMultiWindow(); 214 return; 215 } 216 if (mRootTaskInfo.taskId == taskInfo.taskId) { 217 // Inflates split decor view only when the root task is visible. 218 if (mRootTaskInfo.isVisible != taskInfo.isVisible) { 219 mSyncQueue.runInSync(t -> { 220 if (taskInfo.isVisible) { 221 mSplitDecorManager.inflate(mContext, mRootLeash, 222 taskInfo.configuration.windowConfiguration.getBounds()); 223 } else { 224 mSplitDecorManager.release(t); 225 } 226 }); 227 } 228 mRootTaskInfo = taskInfo; 229 } else if (taskInfo.parentTaskId == mRootTaskInfo.taskId) { 230 mChildrenTaskInfo.put(taskInfo.taskId, taskInfo); 231 mCallbacks.onChildTaskStatusChanged(taskInfo.taskId, true /* present */, 232 taskInfo.isVisible); 233 if (!ENABLE_SHELL_TRANSITIONS) { 234 updateChildTaskSurface( 235 taskInfo, mChildrenLeashes.get(taskInfo.taskId), false /* firstAppeared */); 236 } 237 } else { 238 throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo 239 + "\n mRootTaskInfo: " + mRootTaskInfo); 240 } 241 if (ENABLE_SHELL_TRANSITIONS) { 242 // Status is managed/synchronized by the transition lifecycle. 243 return; 244 } 245 sendStatusChanged(); 246 } 247 248 @Override 249 @CallSuper onTaskVanished(ActivityManager.RunningTaskInfo taskInfo)250 public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) { 251 final int taskId = taskInfo.taskId; 252 if (mRootTaskInfo.taskId == taskId) { 253 mCallbacks.onRootTaskVanished(); 254 mRootTaskInfo = null; 255 mSyncQueue.runInSync(t -> { 256 t.remove(mDimLayer); 257 mSplitDecorManager.release(t); 258 }); 259 } else if (mChildrenTaskInfo.contains(taskId)) { 260 mChildrenTaskInfo.remove(taskId); 261 mChildrenLeashes.remove(taskId); 262 mCallbacks.onChildTaskStatusChanged(taskId, false /* present */, taskInfo.isVisible); 263 if (taskInfo.getWindowingMode() == WINDOWING_MODE_PINNED) { 264 mCallbacks.onChildTaskEnterPip(taskId); 265 } 266 if (ENABLE_SHELL_TRANSITIONS) { 267 // Status is managed/synchronized by the transition lifecycle. 268 return; 269 } 270 sendStatusChanged(); 271 } else { 272 throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo 273 + "\n mRootTaskInfo: " + mRootTaskInfo); 274 } 275 276 if (mStageTaskUnfoldController != null) { 277 mStageTaskUnfoldController.onTaskVanished(taskInfo); 278 } 279 } 280 281 @Override attachChildSurfaceToTask(int taskId, SurfaceControl.Builder b)282 public void attachChildSurfaceToTask(int taskId, SurfaceControl.Builder b) { 283 if (mRootTaskInfo.taskId == taskId) { 284 b.setParent(mRootLeash); 285 } else if (mChildrenLeashes.contains(taskId)) { 286 b.setParent(mChildrenLeashes.get(taskId)); 287 } else { 288 throw new IllegalArgumentException("There is no surface for taskId=" + taskId); 289 } 290 } 291 onResizing(Rect newBounds, SurfaceControl.Transaction t)292 void onResizing(Rect newBounds, SurfaceControl.Transaction t) { 293 if (mSplitDecorManager != null && mRootTaskInfo != null) { 294 mSplitDecorManager.onResizing(mRootTaskInfo, newBounds, t); 295 } 296 } 297 onResized(Rect newBounds, SurfaceControl.Transaction t)298 void onResized(Rect newBounds, SurfaceControl.Transaction t) { 299 if (mSplitDecorManager != null) { 300 mSplitDecorManager.onResized(newBounds, t); 301 } 302 } 303 addTask(ActivityManager.RunningTaskInfo task, WindowContainerTransaction wct)304 void addTask(ActivityManager.RunningTaskInfo task, WindowContainerTransaction wct) { 305 wct.reparent(task.token, mRootTaskInfo.token, true /* onTop*/); 306 } 307 moveToTop(Rect rootBounds, WindowContainerTransaction wct)308 void moveToTop(Rect rootBounds, WindowContainerTransaction wct) { 309 final WindowContainerToken rootToken = mRootTaskInfo.token; 310 wct.setBounds(rootToken, rootBounds).reorder(rootToken, true /* onTop */); 311 } 312 setBounds(Rect bounds, WindowContainerTransaction wct)313 void setBounds(Rect bounds, WindowContainerTransaction wct) { 314 wct.setBounds(mRootTaskInfo.token, bounds); 315 } 316 reorderChild(int taskId, boolean onTop, WindowContainerTransaction wct)317 void reorderChild(int taskId, boolean onTop, WindowContainerTransaction wct) { 318 if (!containsTask(taskId)) { 319 return; 320 } 321 wct.reorder(mChildrenTaskInfo.get(taskId).token, onTop /* onTop */); 322 } 323 324 /** Collects all the current child tasks and prepares transaction to evict them to display. */ evictAllChildren(WindowContainerTransaction wct)325 void evictAllChildren(WindowContainerTransaction wct) { 326 for (int i = mChildrenTaskInfo.size() - 1; i >= 0; i--) { 327 final ActivityManager.RunningTaskInfo taskInfo = mChildrenTaskInfo.valueAt(i); 328 wct.reparent(taskInfo.token, null /* parent */, false /* onTop */); 329 } 330 } 331 setVisibility(boolean visible, WindowContainerTransaction wct)332 void setVisibility(boolean visible, WindowContainerTransaction wct) { 333 wct.reorder(mRootTaskInfo.token, visible /* onTop */); 334 } 335 onSplitScreenListenerRegistered(SplitScreen.SplitScreenListener listener, @StageType int stage)336 void onSplitScreenListenerRegistered(SplitScreen.SplitScreenListener listener, 337 @StageType int stage) { 338 for (int i = mChildrenTaskInfo.size() - 1; i >= 0; --i) { 339 int taskId = mChildrenTaskInfo.keyAt(i); 340 listener.onTaskStageChanged(taskId, stage, 341 mChildrenTaskInfo.get(taskId).isVisible); 342 } 343 } 344 updateChildTaskSurface(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash, boolean firstAppeared)345 private void updateChildTaskSurface(ActivityManager.RunningTaskInfo taskInfo, 346 SurfaceControl leash, boolean firstAppeared) { 347 final Point taskPositionInParent = taskInfo.positionInParent; 348 mSyncQueue.runInSync(t -> { 349 t.setWindowCrop(leash, null); 350 t.setPosition(leash, taskPositionInParent.x, taskPositionInParent.y); 351 if (firstAppeared && !ENABLE_SHELL_TRANSITIONS) { 352 t.setAlpha(leash, 1f); 353 t.setMatrix(leash, 1, 0, 0, 1); 354 t.show(leash); 355 } 356 }); 357 } 358 sendStatusChanged()359 private void sendStatusChanged() { 360 mCallbacks.onStatusChanged(mRootTaskInfo.isVisible, mChildrenTaskInfo.size() > 0); 361 } 362 363 @Override 364 @CallSuper dump(@onNull PrintWriter pw, String prefix)365 public void dump(@NonNull PrintWriter pw, String prefix) { 366 final String innerPrefix = prefix + " "; 367 final String childPrefix = innerPrefix + " "; 368 pw.println(prefix + this); 369 } 370 } 371