• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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