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 android.server.wm;
18 
19 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
20 import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
21 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
22 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
23 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
24 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
25 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
26 import static android.view.Display.DEFAULT_DISPLAY;
27 
28 import android.app.ActivityManager;
29 import android.content.Context;
30 import android.graphics.Rect;
31 import android.hardware.display.DisplayManager;
32 import android.os.Binder;
33 import android.os.IBinder;
34 import android.os.SystemClock;
35 import android.util.ArraySet;
36 import android.util.Log;
37 import android.view.Surface;
38 import android.view.SurfaceControl;
39 import android.view.WindowManager;
40 import android.window.TaskAppearedInfo;
41 import android.window.TaskOrganizer;
42 import android.window.WindowContainerToken;
43 import android.window.WindowContainerTransaction;
44 
45 import androidx.annotation.NonNull;
46 
47 import org.junit.Assert;
48 
49 import java.util.HashMap;
50 import java.util.List;
51 import java.util.concurrent.TimeUnit;
52 import java.util.function.Predicate;
53 
54 public class TestTaskOrganizer extends TaskOrganizer {
55     private static final String TAG = TestTaskOrganizer.class.getSimpleName();
56     public static final int INVALID_TASK_ID = -1;
57 
58     private boolean mRegistered;
59     private ActivityManager.RunningTaskInfo mRootPrimary;
60     private ActivityManager.RunningTaskInfo mRootSecondary;
61     private IBinder mPrimaryCookie;
62     private IBinder mSecondaryCookie;
63     private final HashMap<Integer, ActivityManager.RunningTaskInfo> mKnownTasks = new HashMap<>();
64     private final ArraySet<Integer> mPrimaryChildrenTaskIds = new ArraySet<>();
65     private final ArraySet<Integer> mSecondaryChildrenTaskIds = new ArraySet<>();
66     private final Rect mPrimaryBounds = new Rect();
67     private final Rect mSecondaryBounds = new Rect();
68     private final Context mContext;
69 
70     private static final int[] CONTROLLED_ACTIVITY_TYPES = {
71             ACTIVITY_TYPE_STANDARD,
72             ACTIVITY_TYPE_HOME,
73             ACTIVITY_TYPE_RECENTS,
74             ACTIVITY_TYPE_UNDEFINED
75     };
76     private static final int[] CONTROLLED_WINDOWING_MODES = {
77             WINDOWING_MODE_FULLSCREEN,
78             WINDOWING_MODE_MULTI_WINDOW,
79             WINDOWING_MODE_UNDEFINED
80     };
81 
TestTaskOrganizer(Context context)82     public TestTaskOrganizer(Context context) {
83         super();
84         //TODO(b/192572357): Verify if the context is a UI context when b/190019118 is fixed.
85         mContext = context;
86     }
87 
88     @Override
registerOrganizer()89     public List<TaskAppearedInfo> registerOrganizer() {
90         //TODO(b/192572357): Replace createDisplayContext with createWindowContext and
91         // getMaximumWindowMetrics with getCurrentWindowMetrics when b/190019118 is fixed.
92         final Rect bounds = mContext.createDisplayContext(
93                 mContext.getSystemService(DisplayManager.class)
94                         .getDisplay(DEFAULT_DISPLAY)).getSystemService(WindowManager.class)
95                 .getMaximumWindowMetrics()
96                 .getBounds();
97         final boolean isLandscape = bounds.width() > bounds.height();
98         if (isLandscape) {
99             bounds.splitVertically(mPrimaryBounds, mSecondaryBounds);
100         } else {
101             bounds.splitHorizontally(mPrimaryBounds, mSecondaryBounds);
102         }
103         Log.i(TAG, "registerOrganizer with PrimaryBounds=" + mPrimaryBounds
104                 + " SecondaryBounds=" + mSecondaryBounds);
105 
106         synchronized (this) {
107             final List<TaskAppearedInfo> taskInfos = super.registerOrganizer();
108             for (int i = 0; i < taskInfos.size(); i++) {
109                 final TaskAppearedInfo info = taskInfos.get(i);
110                 onTaskAppeared(info.getTaskInfo(), info.getLeash());
111             }
112             createRootTasksIfNeeded();
113             return taskInfos;
114         }
115     }
116 
createRootTasksIfNeeded()117     private void createRootTasksIfNeeded() {
118         synchronized (this) {
119             if (mPrimaryCookie != null) return;
120             mPrimaryCookie = new Binder();
121             mSecondaryCookie = new Binder();
122 
123             createRootTask(DEFAULT_DISPLAY, WINDOWING_MODE_MULTI_WINDOW, mPrimaryCookie);
124             createRootTask(DEFAULT_DISPLAY, WINDOWING_MODE_MULTI_WINDOW, mSecondaryCookie);
125 
126             waitForAndAssert(o -> mRootPrimary != null && mRootSecondary != null,
127                     "Failed to get root tasks");
128             Log.e(TAG, "createRootTasksIfNeeded primary=" + mRootPrimary.taskId
129                     + " secondary=" + mRootSecondary.taskId);
130 
131             // Set the roots as adjacent to each other.
132             final WindowContainerTransaction wct = new WindowContainerTransaction();
133             wct.setAdjacentRoots(mRootPrimary.getToken(), mRootSecondary.getToken());
134             applyTransaction(wct);
135         }
136     }
137 
waitForAndAssert(Predicate<Object> condition, String failureMessage)138     private void waitForAndAssert(Predicate<Object> condition, String failureMessage) {
139         waitFor(condition);
140         if (!condition.test(this)) {
141             Assert.fail(failureMessage);
142         }
143     }
144 
waitFor(Predicate<Object> condition)145     private void waitFor(Predicate<Object> condition) {
146         final long waitTillTime = SystemClock.elapsedRealtime() + TimeUnit.SECONDS.toMillis(5);
147         while (!condition.test(this)
148                 && SystemClock.elapsedRealtime() < waitTillTime) {
149             try {
150                 wait(TimeUnit.SECONDS.toMillis(5));
151             } catch (InterruptedException e) {
152                 e.printStackTrace();
153             }
154         }
155     }
156 
notifyOnEnd(Runnable r)157     private void notifyOnEnd(Runnable r) {
158         r.run();
159         notifyAll();
160     }
161 
registerOrganizerIfNeeded()162     private void registerOrganizerIfNeeded() {
163         if (mRegistered) return;
164 
165         registerOrganizer();
166         mRegistered = true;
167     }
168 
unregisterOrganizerIfNeeded()169     public void unregisterOrganizerIfNeeded() {
170         synchronized (this) {
171             if (!mRegistered) return;
172             mRegistered = false;
173 
174             NestedShellPermission.run(() -> {
175                 dismissSplitScreen();
176 
177                 deleteRootTask(mRootPrimary.getToken());
178                 mRootPrimary = null;
179                 mPrimaryCookie = null;
180                 mPrimaryChildrenTaskIds.clear();
181                 deleteRootTask(mRootSecondary.getToken());
182                 mRootSecondary = null;
183                 mSecondaryCookie = null;
184                 mSecondaryChildrenTaskIds.clear();
185 
186                 super.unregisterOrganizer();
187             });
188         }
189     }
190 
putTaskInSplitPrimary(int taskId)191     public void putTaskInSplitPrimary(int taskId) {
192         NestedShellPermission.run(() -> {
193             synchronized (this) {
194                 registerOrganizerIfNeeded();
195                 ActivityManager.RunningTaskInfo taskInfo = getTaskInfo(taskId);
196                 final WindowContainerTransaction t = new WindowContainerTransaction()
197                         .setBounds(mRootPrimary.getToken(), mPrimaryBounds)
198                         .setBounds(taskInfo.getToken(), null)
199                         .setWindowingMode(taskInfo.getToken(), WINDOWING_MODE_UNDEFINED)
200                         .reparent(taskInfo.getToken(), mRootPrimary.getToken(), true /* onTop */)
201                         .reorder(mRootPrimary.getToken(), true /* onTop */);
202                 applyTransaction(t);
203 
204                 waitForAndAssert(
205                         o -> mPrimaryChildrenTaskIds.contains(taskId),
206                         "Can't put putTaskInSplitPrimary taskId=" + taskId);
207 
208                 Log.e(TAG, "putTaskInSplitPrimary taskId=" + taskId);
209             }
210         });
211     }
212 
putTaskInSplitSecondary(int taskId)213     public void putTaskInSplitSecondary(int taskId) {
214         NestedShellPermission.run(() -> {
215             synchronized (this) {
216                 registerOrganizerIfNeeded();
217                 ActivityManager.RunningTaskInfo taskInfo = getTaskInfo(taskId);
218                 final WindowContainerTransaction t = new WindowContainerTransaction()
219                         .setBounds(mRootSecondary.getToken(), mSecondaryBounds)
220                         .setBounds(taskInfo.getToken(), null)
221                         .setWindowingMode(taskInfo.getToken(), WINDOWING_MODE_UNDEFINED)
222                         .reparent(taskInfo.getToken(), mRootSecondary.getToken(), true /* onTop */)
223                         .reorder(mRootSecondary.getToken(), true /* onTop */);
224                 applyTransaction(t);
225 
226                 waitForAndAssert(
227                         o -> mSecondaryChildrenTaskIds.contains(taskId),
228                         "Can't put putTaskInSplitSecondary taskId=" + taskId);
229 
230                 Log.e(TAG, "putTaskInSplitSecondary taskId=" + taskId);
231             }
232         });
233     }
234 
setLaunchRoot(int taskId)235     public void setLaunchRoot(int taskId) {
236         NestedShellPermission.run(() -> {
237             synchronized (this) {
238                 final WindowContainerTransaction t = new WindowContainerTransaction()
239                         .setLaunchRoot(mKnownTasks.get(taskId).getToken(),
240                                 CONTROLLED_WINDOWING_MODES, CONTROLLED_ACTIVITY_TYPES);
241                 applyTransaction(t);
242             }
243         });
244     }
245 
dismissSplitScreen()246     void dismissSplitScreen() {
247         dismissSplitScreen(false /* primaryOnTop */);
248     }
249 
dismissSplitScreen(boolean primaryOnTop)250     void dismissSplitScreen(boolean primaryOnTop) {
251         dismissSplitScreen(new WindowContainerTransaction(), primaryOnTop);
252     }
253 
dismissSplitScreen(WindowContainerTransaction t, boolean primaryOnTop)254     void dismissSplitScreen(WindowContainerTransaction t, boolean primaryOnTop) {
255         synchronized (this) {
256             NestedShellPermission.run(() -> {
257                 t.setLaunchRoot(mRootPrimary.getToken(), null, null)
258                         .setLaunchRoot(
259                                 mRootSecondary.getToken(),
260                                 null,
261                                 null)
262                         .reparentTasks(
263                                 primaryOnTop ? mRootSecondary.getToken() : mRootPrimary.getToken(),
264                                 null /* newParent */,
265                                 CONTROLLED_WINDOWING_MODES,
266                                 CONTROLLED_ACTIVITY_TYPES,
267                                 true /* onTop */)
268                         .reparentTasks(
269                                 primaryOnTop ? mRootPrimary.getToken() : mRootSecondary.getToken(),
270                                 null /* newParent */,
271                                 CONTROLLED_WINDOWING_MODES,
272                                 CONTROLLED_ACTIVITY_TYPES,
273                                 true /* onTop */);
274                 applyTransaction(t);
275             });
276         }
277     }
278 
setRootPrimaryTaskBounds(Rect bounds)279     void setRootPrimaryTaskBounds(Rect bounds) {
280         setTaskBounds(mRootPrimary.getToken(), bounds);
281     }
282 
setRootSecondaryTaskBounds(Rect bounds)283     void setRootSecondaryTaskBounds(Rect bounds) {
284         setTaskBounds(mRootSecondary.getToken(), bounds);
285     }
286 
getPrimaryTaskBounds()287     public Rect getPrimaryTaskBounds() {
288         return mPrimaryBounds;
289     }
290 
getSecondaryTaskBounds()291     public Rect getSecondaryTaskBounds() {
292         return mSecondaryBounds;
293     }
294 
setTaskBounds(WindowContainerToken container, Rect bounds)295     private void setTaskBounds(WindowContainerToken container, Rect bounds) {
296         synchronized (this) {
297             NestedShellPermission.run(() -> {
298                 final WindowContainerTransaction t = new WindowContainerTransaction()
299                         .setBounds(container, bounds);
300                 applyTransaction(t);
301             });
302         }
303     }
304 
getPrimarySplitTaskCount()305     int getPrimarySplitTaskCount() {
306         return mPrimaryChildrenTaskIds.size();
307     }
308 
getSecondarySplitTaskCount()309     int getSecondarySplitTaskCount() {
310         return mSecondaryChildrenTaskIds.size();
311     }
312 
getPrimarySplitTaskId()313     public int getPrimarySplitTaskId() {
314         return mRootPrimary != null ? mRootPrimary.taskId : INVALID_TASK_ID;
315     }
316 
getSecondarySplitTaskId()317     public int getSecondarySplitTaskId() {
318         return mRootSecondary != null ? mRootSecondary.taskId : INVALID_TASK_ID;
319     }
320 
getTaskInfo(int taskId)321     ActivityManager.RunningTaskInfo getTaskInfo(int taskId) {
322         synchronized (this) {
323             ActivityManager.RunningTaskInfo taskInfo = mKnownTasks.get(taskId);
324             if (taskInfo != null) return taskInfo;
325 
326             final List<ActivityManager.RunningTaskInfo> rootTasks = getRootTasks(DEFAULT_DISPLAY,
327                     null);
328             for (ActivityManager.RunningTaskInfo info : rootTasks) {
329                 addTask(info);
330             }
331 
332             return mKnownTasks.get(taskId);
333         }
334     }
335 
336     @Override
onTaskAppeared(@onNull ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash)337     public void onTaskAppeared(@NonNull ActivityManager.RunningTaskInfo taskInfo,
338             SurfaceControl leash) {
339         synchronized (this) {
340             notifyOnEnd(() -> {
341                 SurfaceControl.Transaction t = new SurfaceControl.Transaction();
342                 t.setVisibility(leash, true /* visible */);
343                 addTask(taskInfo, leash, t);
344                 t.apply();
345             });
346         }
347     }
348 
349     @Override
onTaskVanished(@onNull ActivityManager.RunningTaskInfo taskInfo)350     public void onTaskVanished(@NonNull ActivityManager.RunningTaskInfo taskInfo) {
351         synchronized (this) {
352             removeTask(taskInfo);
353         }
354     }
355 
356     @Override
onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo)357     public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
358         synchronized (this) {
359             notifyOnEnd(() -> addTask(taskInfo));
360         }
361     }
362 
addTask(ActivityManager.RunningTaskInfo taskInfo)363     private void addTask(ActivityManager.RunningTaskInfo taskInfo) {
364         addTask(taskInfo, null /* SurfaceControl */, null /* Transaction */);
365     }
366 
addTask(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash, SurfaceControl.Transaction t)367     private void addTask(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash,
368             SurfaceControl.Transaction t) {
369         mKnownTasks.put(taskInfo.taskId, taskInfo);
370         if (taskInfo.hasParentTask()){
371             if (mRootPrimary != null
372                     && mRootPrimary.taskId == taskInfo.getParentTaskId()) {
373                 mPrimaryChildrenTaskIds.add(taskInfo.taskId);
374             } else if (mRootSecondary != null
375                     && mRootSecondary.taskId == taskInfo.getParentTaskId()) {
376                 mSecondaryChildrenTaskIds.add(taskInfo.taskId);
377             }
378             return;
379         }
380 
381         if (mRootPrimary == null
382                 && mPrimaryCookie != null
383                 && taskInfo.containsLaunchCookie(mPrimaryCookie)) {
384             mRootPrimary = taskInfo;
385             if (t != null && leash != null) {
386                 t.setGeometry(leash, null, mPrimaryBounds, Surface.ROTATION_0);
387             }
388             return;
389         }
390 
391         if (mRootSecondary == null
392                 && mSecondaryCookie != null
393                 && taskInfo.containsLaunchCookie(mSecondaryCookie)) {
394             mRootSecondary = taskInfo;
395             if (t != null && leash != null) {
396                 t.setGeometry(leash, null, mSecondaryBounds, Surface.ROTATION_0);
397             }
398         }
399     }
400 
removeTask(ActivityManager.RunningTaskInfo taskInfo)401     private void removeTask(ActivityManager.RunningTaskInfo taskInfo) {
402         final int taskId = taskInfo.taskId;
403         // ignores cleanup on duplicated removal request
404         if (mKnownTasks.remove(taskId) == null) {
405             return;
406         }
407         mPrimaryChildrenTaskIds.remove(taskId);
408         mSecondaryChildrenTaskIds.remove(taskId);
409 
410         if ((mRootPrimary != null && taskId == mRootPrimary.taskId)
411                 || (mRootSecondary != null && taskId == mRootSecondary.taskId)) {
412             unregisterOrganizerIfNeeded();
413         }
414     }
415 }
416