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