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