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