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