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.WINDOWING_MODE_MULTI_WINDOW; 21 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; 22 import static android.content.res.Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED; 23 import static android.view.RemoteAnimationTarget.MODE_OPENING; 24 25 import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_ACTIVITY_TYPES; 26 import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_WINDOWING_MODES; 27 import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_WINDOWING_MODES_WHEN_ACTIVE; 28 import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS; 29 30 import android.annotation.CallSuper; 31 import android.annotation.Nullable; 32 import android.app.ActivityManager; 33 import android.content.Context; 34 import android.graphics.Point; 35 import android.graphics.Rect; 36 import android.os.IBinder; 37 import android.util.Slog; 38 import android.util.SparseArray; 39 import android.view.RemoteAnimationTarget; 40 import android.view.SurfaceControl; 41 import android.view.SurfaceSession; 42 import android.window.WindowContainerToken; 43 import android.window.WindowContainerTransaction; 44 45 import androidx.annotation.NonNull; 46 47 import com.android.internal.util.ArrayUtils; 48 import com.android.launcher3.icons.IconProvider; 49 import com.android.wm.shell.ShellTaskOrganizer; 50 import com.android.wm.shell.common.SurfaceUtils; 51 import com.android.wm.shell.common.SyncTransactionQueue; 52 import com.android.wm.shell.common.split.SplitDecorManager; 53 import com.android.wm.shell.splitscreen.SplitScreen.StageType; 54 import com.android.wm.shell.windowdecor.WindowDecorViewModel; 55 56 import java.io.PrintWriter; 57 import java.util.Optional; 58 import java.util.function.Consumer; 59 import java.util.function.Predicate; 60 61 /** 62 * Base class that handle common task org. related for split-screen stages. 63 * Note that this class and its sub-class do not directly perform hierarchy operations. 64 * They only serve to hold a collection of tasks and provide APIs like 65 * {@link #addTask(ActivityManager.RunningTaskInfo, WindowContainerTransaction)} for the centralized 66 * {@link StageCoordinator} to perform hierarchy operations in-sync with other containers. 67 * 68 * @see StageCoordinator 69 */ 70 class StageTaskListener implements ShellTaskOrganizer.TaskListener { 71 private static final String TAG = StageTaskListener.class.getSimpleName(); 72 73 /** Callback interface for listening to changes in a split-screen stage. */ 74 public interface StageListenerCallbacks { onRootTaskAppeared()75 void onRootTaskAppeared(); 76 onChildTaskAppeared(int taskId)77 void onChildTaskAppeared(int taskId); 78 onStatusChanged(boolean visible, boolean hasChildren)79 void onStatusChanged(boolean visible, boolean hasChildren); 80 onChildTaskStatusChanged(int taskId, boolean present, boolean visible)81 void onChildTaskStatusChanged(int taskId, boolean present, boolean visible); 82 onRootTaskVanished()83 void onRootTaskVanished(); 84 onNoLongerSupportMultiWindow(ActivityManager.RunningTaskInfo taskInfo)85 void onNoLongerSupportMultiWindow(ActivityManager.RunningTaskInfo taskInfo); 86 } 87 88 private final Context mContext; 89 private final StageListenerCallbacks mCallbacks; 90 private final SurfaceSession mSurfaceSession; 91 private final SyncTransactionQueue mSyncQueue; 92 private final IconProvider mIconProvider; 93 private final Optional<WindowDecorViewModel> mWindowDecorViewModel; 94 95 protected ActivityManager.RunningTaskInfo mRootTaskInfo; 96 protected SurfaceControl mRootLeash; 97 protected SurfaceControl mDimLayer; 98 protected SparseArray<ActivityManager.RunningTaskInfo> mChildrenTaskInfo = new SparseArray<>(); 99 private final SparseArray<SurfaceControl> mChildrenLeashes = new SparseArray<>(); 100 // TODO(b/204308910): Extracts SplitDecorManager related code to common package. 101 private SplitDecorManager mSplitDecorManager; 102 StageTaskListener(Context context, ShellTaskOrganizer taskOrganizer, int displayId, StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue, SurfaceSession surfaceSession, IconProvider iconProvider, Optional<WindowDecorViewModel> windowDecorViewModel)103 StageTaskListener(Context context, ShellTaskOrganizer taskOrganizer, int displayId, 104 StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue, 105 SurfaceSession surfaceSession, IconProvider iconProvider, 106 Optional<WindowDecorViewModel> windowDecorViewModel) { 107 mContext = context; 108 mCallbacks = callbacks; 109 mSyncQueue = syncQueue; 110 mSurfaceSession = surfaceSession; 111 mIconProvider = iconProvider; 112 mWindowDecorViewModel = windowDecorViewModel; 113 taskOrganizer.createRootTask(displayId, WINDOWING_MODE_MULTI_WINDOW, this); 114 } 115 getChildCount()116 int getChildCount() { 117 return mChildrenTaskInfo.size(); 118 } 119 containsTask(int taskId)120 boolean containsTask(int taskId) { 121 return mChildrenTaskInfo.contains(taskId); 122 } 123 containsToken(WindowContainerToken token)124 boolean containsToken(WindowContainerToken token) { 125 return contains(t -> t.token.equals(token)); 126 } 127 containsContainer(IBinder binder)128 boolean containsContainer(IBinder binder) { 129 return contains(t -> t.token.asBinder() == binder); 130 } 131 132 /** 133 * Returns the top visible child task's id. 134 */ getTopVisibleChildTaskId()135 int getTopVisibleChildTaskId() { 136 final ActivityManager.RunningTaskInfo taskInfo = getChildTaskInfo(t -> t.isVisible 137 && t.isVisibleRequested); 138 return taskInfo != null ? taskInfo.taskId : INVALID_TASK_ID; 139 } 140 141 /** 142 * Returns the top activity uid for the top child task. 143 */ getTopChildTaskUid()144 int getTopChildTaskUid() { 145 final ActivityManager.RunningTaskInfo taskInfo = 146 getChildTaskInfo(t -> t.topActivityInfo != null); 147 return taskInfo != null ? taskInfo.topActivityInfo.applicationInfo.uid : 0; 148 } 149 150 /** @return {@code true} if this listener contains the currently focused task. */ isFocused()151 boolean isFocused() { 152 return contains(t -> t.isFocused); 153 } 154 contains(Predicate<ActivityManager.RunningTaskInfo> predicate)155 private boolean contains(Predicate<ActivityManager.RunningTaskInfo> predicate) { 156 if (mRootTaskInfo != null && predicate.test(mRootTaskInfo)) { 157 return true; 158 } 159 160 return getChildTaskInfo(predicate) != null; 161 } 162 163 @Nullable getChildTaskInfo( Predicate<ActivityManager.RunningTaskInfo> predicate)164 private ActivityManager.RunningTaskInfo getChildTaskInfo( 165 Predicate<ActivityManager.RunningTaskInfo> predicate) { 166 for (int i = mChildrenTaskInfo.size() - 1; i >= 0; --i) { 167 final ActivityManager.RunningTaskInfo taskInfo = mChildrenTaskInfo.valueAt(i); 168 if (predicate.test(taskInfo)) { 169 return taskInfo; 170 } 171 } 172 return null; 173 } 174 175 @Override 176 @CallSuper onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash)177 public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) { 178 if (mRootTaskInfo == null) { 179 mRootLeash = leash; 180 mRootTaskInfo = taskInfo; 181 mSplitDecorManager = new SplitDecorManager( 182 mRootTaskInfo.configuration, 183 mIconProvider, 184 mSurfaceSession); 185 mCallbacks.onRootTaskAppeared(); 186 sendStatusChanged(); 187 mSyncQueue.runInSync(t -> mDimLayer = 188 SurfaceUtils.makeDimLayer(t, mRootLeash, "Dim layer", mSurfaceSession)); 189 } else if (taskInfo.parentTaskId == mRootTaskInfo.taskId) { 190 final int taskId = taskInfo.taskId; 191 mChildrenLeashes.put(taskId, leash); 192 mChildrenTaskInfo.put(taskId, taskInfo); 193 mCallbacks.onChildTaskStatusChanged(taskId, true /* present */, 194 taskInfo.isVisible && taskInfo.isVisibleRequested); 195 if (ENABLE_SHELL_TRANSITIONS) { 196 // Status is managed/synchronized by the transition lifecycle. 197 return; 198 } 199 updateChildTaskSurface(taskInfo, leash, true /* firstAppeared */); 200 mCallbacks.onChildTaskAppeared(taskId); 201 sendStatusChanged(); 202 } else { 203 throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo 204 + "\n mRootTaskInfo: " + mRootTaskInfo); 205 } 206 } 207 208 @Override 209 @CallSuper onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo)210 public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) { 211 mWindowDecorViewModel.ifPresent(viewModel -> viewModel.onTaskInfoChanged(taskInfo)); 212 if (mRootTaskInfo.taskId == taskInfo.taskId) { 213 // Inflates split decor view only when the root task is visible. 214 if (!ENABLE_SHELL_TRANSITIONS && mRootTaskInfo.isVisible != taskInfo.isVisible) { 215 if (taskInfo.isVisible) { 216 mSplitDecorManager.inflate(mContext, mRootLeash, 217 taskInfo.configuration.windowConfiguration.getBounds()); 218 } else { 219 mSyncQueue.runInSync(t -> mSplitDecorManager.release(t)); 220 } 221 } 222 mRootTaskInfo = taskInfo; 223 } else if (taskInfo.parentTaskId == mRootTaskInfo.taskId) { 224 if (!taskInfo.supportsMultiWindow 225 || !ArrayUtils.contains(CONTROLLED_ACTIVITY_TYPES, taskInfo.getActivityType()) 226 || !ArrayUtils.contains(CONTROLLED_WINDOWING_MODES_WHEN_ACTIVE, 227 taskInfo.getWindowingMode())) { 228 // Leave split screen if the task no longer supports multi window or have 229 // uncontrolled task. 230 mCallbacks.onNoLongerSupportMultiWindow(taskInfo); 231 return; 232 } 233 mChildrenTaskInfo.put(taskInfo.taskId, taskInfo); 234 mCallbacks.onChildTaskStatusChanged(taskInfo.taskId, true /* present */, 235 taskInfo.isVisible && taskInfo.isVisibleRequested); 236 if (!ENABLE_SHELL_TRANSITIONS) { 237 updateChildTaskSurface( 238 taskInfo, mChildrenLeashes.get(taskInfo.taskId), false /* firstAppeared */); 239 } 240 } else { 241 throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo 242 + "\n mRootTaskInfo: " + mRootTaskInfo); 243 } 244 if (ENABLE_SHELL_TRANSITIONS) { 245 // Status is managed/synchronized by the transition lifecycle. 246 return; 247 } 248 sendStatusChanged(); 249 } 250 251 @Override 252 @CallSuper onTaskVanished(ActivityManager.RunningTaskInfo taskInfo)253 public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) { 254 final int taskId = taskInfo.taskId; 255 if (mRootTaskInfo.taskId == taskId) { 256 mCallbacks.onRootTaskVanished(); 257 mRootTaskInfo = null; 258 mRootLeash = null; 259 mSyncQueue.runInSync(t -> { 260 t.remove(mDimLayer); 261 mSplitDecorManager.release(t); 262 }); 263 } else if (mChildrenTaskInfo.contains(taskId)) { 264 mChildrenTaskInfo.remove(taskId); 265 mChildrenLeashes.remove(taskId); 266 mCallbacks.onChildTaskStatusChanged(taskId, false /* present */, taskInfo.isVisible); 267 if (ENABLE_SHELL_TRANSITIONS) { 268 // Status is managed/synchronized by the transition lifecycle. 269 return; 270 } 271 sendStatusChanged(); 272 } else { 273 throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo 274 + "\n mRootTaskInfo: " + mRootTaskInfo); 275 } 276 } 277 278 @Override attachChildSurfaceToTask(int taskId, SurfaceControl.Builder b)279 public void attachChildSurfaceToTask(int taskId, SurfaceControl.Builder b) { 280 b.setParent(findTaskSurface(taskId)); 281 } 282 283 @Override reparentChildSurfaceToTask(int taskId, SurfaceControl sc, SurfaceControl.Transaction t)284 public void reparentChildSurfaceToTask(int taskId, SurfaceControl sc, 285 SurfaceControl.Transaction t) { 286 t.reparent(sc, findTaskSurface(taskId)); 287 } 288 findTaskSurface(int taskId)289 private SurfaceControl findTaskSurface(int taskId) { 290 if (mRootTaskInfo.taskId == taskId) { 291 return mRootLeash; 292 } else if (mChildrenLeashes.contains(taskId)) { 293 return mChildrenLeashes.get(taskId); 294 } else { 295 throw new IllegalArgumentException("There is no surface for taskId=" + taskId); 296 } 297 } 298 isRootTaskId(int taskId)299 boolean isRootTaskId(int taskId) { 300 return mRootTaskInfo != null && mRootTaskInfo.taskId == taskId; 301 } 302 onResizing(Rect newBounds, Rect sideBounds, SurfaceControl.Transaction t, int offsetX, int offsetY, boolean immediately)303 void onResizing(Rect newBounds, Rect sideBounds, SurfaceControl.Transaction t, int offsetX, 304 int offsetY, boolean immediately) { 305 if (mSplitDecorManager != null && mRootTaskInfo != null) { 306 mSplitDecorManager.onResizing(mRootTaskInfo, newBounds, sideBounds, t, offsetX, 307 offsetY, immediately); 308 } 309 } 310 onResized(SurfaceControl.Transaction t)311 void onResized(SurfaceControl.Transaction t) { 312 if (mSplitDecorManager != null) { 313 mSplitDecorManager.onResized(t, null); 314 } 315 } 316 screenshotIfNeeded(SurfaceControl.Transaction t)317 void screenshotIfNeeded(SurfaceControl.Transaction t) { 318 if (mSplitDecorManager != null) { 319 mSplitDecorManager.screenshotIfNeeded(t); 320 } 321 } 322 fadeOutDecor(Runnable finishedCallback)323 void fadeOutDecor(Runnable finishedCallback) { 324 if (mSplitDecorManager != null) { 325 mSplitDecorManager.fadeOutDecor(finishedCallback); 326 } else { 327 finishedCallback.run(); 328 } 329 } 330 getSplitDecorManager()331 SplitDecorManager getSplitDecorManager() { 332 return mSplitDecorManager; 333 } 334 addTask(ActivityManager.RunningTaskInfo task, WindowContainerTransaction wct)335 void addTask(ActivityManager.RunningTaskInfo task, WindowContainerTransaction wct) { 336 // Clear overridden bounds and windowing mode to make sure the child task can inherit 337 // windowing mode and bounds from split root. 338 wct.setWindowingMode(task.token, WINDOWING_MODE_UNDEFINED) 339 .setBounds(task.token, null); 340 341 wct.reparent(task.token, mRootTaskInfo.token, true /* onTop*/); 342 } 343 reorderChild(int taskId, boolean onTop, WindowContainerTransaction wct)344 void reorderChild(int taskId, boolean onTop, WindowContainerTransaction wct) { 345 if (!containsTask(taskId)) { 346 return; 347 } 348 wct.reorder(mChildrenTaskInfo.get(taskId).token, onTop /* onTop */); 349 } 350 doForAllChildTasks(Consumer<Integer> consumer)351 void doForAllChildTasks(Consumer<Integer> consumer) { 352 for (int i = mChildrenTaskInfo.size() - 1; i >= 0; i--) { 353 final ActivityManager.RunningTaskInfo taskInfo = mChildrenTaskInfo.valueAt(i); 354 consumer.accept(taskInfo.taskId); 355 } 356 } 357 358 /** Collects all the current child tasks and prepares transaction to evict them to display. */ evictAllChildren(WindowContainerTransaction wct)359 void evictAllChildren(WindowContainerTransaction wct) { 360 for (int i = mChildrenTaskInfo.size() - 1; i >= 0; i--) { 361 final ActivityManager.RunningTaskInfo taskInfo = mChildrenTaskInfo.valueAt(i); 362 wct.reparent(taskInfo.token, null /* parent */, false /* onTop */); 363 } 364 } 365 evictOtherChildren(WindowContainerTransaction wct, int taskId)366 void evictOtherChildren(WindowContainerTransaction wct, int taskId) { 367 for (int i = mChildrenTaskInfo.size() - 1; i >= 0; i--) { 368 final ActivityManager.RunningTaskInfo taskInfo = mChildrenTaskInfo.valueAt(i); 369 if (taskId == taskInfo.taskId) continue; 370 wct.reparent(taskInfo.token, null /* parent */, false /* onTop */); 371 } 372 } 373 evictNonOpeningChildren(RemoteAnimationTarget[] apps, WindowContainerTransaction wct)374 void evictNonOpeningChildren(RemoteAnimationTarget[] apps, WindowContainerTransaction wct) { 375 final SparseArray<ActivityManager.RunningTaskInfo> toBeEvict = mChildrenTaskInfo.clone(); 376 for (int i = 0; i < apps.length; i++) { 377 if (apps[i].mode == MODE_OPENING) { 378 toBeEvict.remove(apps[i].taskId); 379 } 380 } 381 for (int i = toBeEvict.size() - 1; i >= 0; i--) { 382 final ActivityManager.RunningTaskInfo taskInfo = toBeEvict.valueAt(i); 383 wct.reparent(taskInfo.token, null /* parent */, false /* onTop */); 384 } 385 } 386 evictInvisibleChildren(WindowContainerTransaction wct)387 void evictInvisibleChildren(WindowContainerTransaction wct) { 388 for (int i = mChildrenTaskInfo.size() - 1; i >= 0; i--) { 389 final ActivityManager.RunningTaskInfo taskInfo = mChildrenTaskInfo.valueAt(i); 390 if (!taskInfo.isVisible) { 391 wct.reparent(taskInfo.token, null /* parent */, false /* onTop */); 392 } 393 } 394 } 395 evictChildren(WindowContainerTransaction wct, int taskId)396 void evictChildren(WindowContainerTransaction wct, int taskId) { 397 final ActivityManager.RunningTaskInfo taskInfo = mChildrenTaskInfo.get(taskId); 398 if (taskInfo != null) { 399 wct.reparent(taskInfo.token, null /* parent */, false /* onTop */); 400 } 401 } 402 reparentTopTask(WindowContainerTransaction wct)403 void reparentTopTask(WindowContainerTransaction wct) { 404 wct.reparentTasks(null /* currentParent */, mRootTaskInfo.token, 405 CONTROLLED_WINDOWING_MODES, CONTROLLED_ACTIVITY_TYPES, 406 true /* onTop */, true /* reparentTopOnly */); 407 } 408 resetBounds(WindowContainerTransaction wct)409 void resetBounds(WindowContainerTransaction wct) { 410 wct.setBounds(mRootTaskInfo.token, null); 411 wct.setAppBounds(mRootTaskInfo.token, null); 412 wct.setSmallestScreenWidthDp(mRootTaskInfo.token, SMALLEST_SCREEN_WIDTH_DP_UNDEFINED); 413 } 414 onSplitScreenListenerRegistered(SplitScreen.SplitScreenListener listener, @StageType int stage)415 void onSplitScreenListenerRegistered(SplitScreen.SplitScreenListener listener, 416 @StageType int stage) { 417 for (int i = mChildrenTaskInfo.size() - 1; i >= 0; --i) { 418 int taskId = mChildrenTaskInfo.keyAt(i); 419 listener.onTaskStageChanged(taskId, stage, 420 mChildrenTaskInfo.get(taskId).isVisible); 421 } 422 } 423 updateChildTaskSurface(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash, boolean firstAppeared)424 private void updateChildTaskSurface(ActivityManager.RunningTaskInfo taskInfo, 425 SurfaceControl leash, boolean firstAppeared) { 426 final Point taskPositionInParent = taskInfo.positionInParent; 427 mSyncQueue.runInSync(t -> { 428 // The task surface might be released before running in the sync queue for the case like 429 // trampoline launch, so check if the surface is valid before processing it. 430 if (!leash.isValid()) { 431 Slog.w(TAG, "Skip updating invalid child task surface of task#" + taskInfo.taskId); 432 return; 433 } 434 t.setCrop(leash, null); 435 t.setPosition(leash, taskPositionInParent.x, taskPositionInParent.y); 436 if (firstAppeared) { 437 t.setAlpha(leash, 1f); 438 t.setMatrix(leash, 1, 0, 0, 1); 439 t.show(leash); 440 } 441 }); 442 } 443 sendStatusChanged()444 private void sendStatusChanged() { 445 mCallbacks.onStatusChanged(mRootTaskInfo.isVisible, mChildrenTaskInfo.size() > 0); 446 } 447 448 @Override 449 @CallSuper dump(@onNull PrintWriter pw, String prefix)450 public void dump(@NonNull PrintWriter pw, String prefix) { 451 final String innerPrefix = prefix + " "; 452 final String childPrefix = innerPrefix + " "; 453 if (mChildrenTaskInfo.size() > 0) { 454 pw.println(prefix + "Children list:"); 455 for (int i = mChildrenTaskInfo.size() - 1; i >= 0; --i) { 456 final ActivityManager.RunningTaskInfo taskInfo = mChildrenTaskInfo.valueAt(i); 457 pw.println(childPrefix + "Task#" + i + " taskID=" + taskInfo.taskId 458 + " baseActivity=" + taskInfo.baseActivity); 459 } 460 } 461 } 462 } 463