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