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