blob: 79d88fd897166c463c0379294db37e8a2f423141 [file] [log] [blame]
Mark Renouf89ac9882019-10-07 16:28:18 -04001/*
2 * Copyright (C) 2019 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
17package android.app;
18
19import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL;
20import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
21import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC;
22
23import android.annotation.NonNull;
24import android.annotation.Nullable;
25import android.content.ComponentName;
26import android.content.Context;
27import android.content.Intent;
Mady Mellor1bd16752019-10-25 12:30:09 -070028import android.content.pm.LauncherApps;
29import android.content.pm.ShortcutInfo;
Mark Renouf89ac9882019-10-07 16:28:18 -040030import android.graphics.Insets;
31import android.graphics.Matrix;
32import android.graphics.Point;
Mady Mellor1bd16752019-10-25 12:30:09 -070033import android.graphics.Rect;
Mark Renouf89ac9882019-10-07 16:28:18 -040034import android.graphics.Region;
35import android.hardware.display.DisplayManager;
36import android.hardware.display.VirtualDisplay;
37import android.hardware.input.InputManager;
38import android.os.RemoteException;
39import android.os.SystemClock;
40import android.os.UserHandle;
41import android.util.DisplayMetrics;
42import android.util.Log;
43import android.view.Display;
44import android.view.IWindow;
45import android.view.IWindowManager;
46import android.view.IWindowSession;
47import android.view.InputDevice;
48import android.view.KeyCharacterMap;
49import android.view.KeyEvent;
50import android.view.SurfaceControl;
51import android.view.WindowManager;
52import android.view.WindowManagerGlobal;
53import android.view.inputmethod.InputMethodManager;
54
55import dalvik.system.CloseGuard;
56
57import java.util.List;
58
59/**
60 * A component which handles embedded display of tasks within another window. The embedded task can
61 * be presented using the SurfaceControl provided from {@link #getSurfaceControl()}.
62 *
63 * @hide
64 */
65public class TaskEmbedder {
66 private static final String TAG = "TaskEmbedder";
67 private static final String DISPLAY_NAME = "TaskVirtualDisplay";
68
69 /**
70 * A component which will host the task.
71 */
72 public interface Host {
73 /** @return the screen area where touches should be dispatched to the embedded Task */
74 Region getTapExcludeRegion();
75
76 /** @return a matrix which transforms from screen-space to the embedded task surface */
77 Matrix getScreenToTaskMatrix();
78
79 /** @return the window containing the parent surface, if attached and available */
80 @Nullable IWindow getWindow();
81
82 /** @return the x/y offset from the origin of the window to the surface */
83 Point getPositionInWindow();
84
85 /** @return whether this surface is able to receive pointer events */
86 boolean canReceivePointerEvents();
87
88 /** @return the width of the container for the embedded task */
89 int getWidth();
90
91 /** @return the height of the container for the embedded task */
92 int getHeight();
93
94 /**
95 * Called to inform the host of the task's background color. This can be used to
96 * fill unpainted areas if necessary.
97 */
98 void onTaskBackgroundColorChanged(TaskEmbedder ts, int bgColor);
99 }
100
101 /**
102 * Describes changes to the state of the TaskEmbedder as well the tasks within.
103 */
104 public interface Listener {
105 /** Called when the container is ready for launching activities. */
106 default void onInitialized() {}
107
108 /** Called when the container can no longer launch activities. */
109 default void onReleased() {}
110
111 /** Called when a task is created inside the container. */
112 default void onTaskCreated(int taskId, ComponentName name) {}
113
114 /** Called when a task is moved to the front of the stack inside the container. */
115 default void onTaskMovedToFront(int taskId) {}
116
117 /** Called when a task is about to be removed from the stack inside the container. */
118 default void onTaskRemovalStarted(int taskId) {}
119 }
120
121 private IActivityTaskManager mActivityTaskManager = ActivityTaskManager.getService();
122
123 private final Context mContext;
124 private TaskEmbedder.Host mHost;
125 private int mDisplayDensityDpi;
126 private final boolean mSingleTaskInstance;
127 private SurfaceControl.Transaction mTransaction;
128 private SurfaceControl mSurfaceControl;
129 private VirtualDisplay mVirtualDisplay;
130 private Insets mForwardedInsets;
131 private TaskStackListener mTaskStackListener;
132 private Listener mListener;
133 private boolean mOpened; // Protected by mGuard.
134
135 private final CloseGuard mGuard = CloseGuard.get();
136
137
138 /**
139 * Constructs a new TaskEmbedder.
140 *
141 * @param context the context
142 * @param host the host for this embedded task
143 * @param singleTaskInstance whether to apply a single-task constraint to this container
144 */
145 public TaskEmbedder(Context context, TaskEmbedder.Host host, boolean singleTaskInstance) {
146 mContext = context;
147 mHost = host;
148 mSingleTaskInstance = singleTaskInstance;
149 }
150
151 /**
152 * Whether this container has been initialized.
153 *
154 * @return true if initialized
155 */
156 public boolean isInitialized() {
157 return mVirtualDisplay != null;
158 }
159
160 /**
161 * Initialize this container.
162 *
163 * @param parent the surface control for the parent surface
164 * @return true if initialized successfully
165 */
166 public boolean initialize(SurfaceControl parent) {
167 if (mVirtualDisplay != null) {
168 throw new IllegalStateException("Trying to initialize for the second time.");
169 }
170
171 mTransaction = new SurfaceControl.Transaction();
172
173 final DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
174 mDisplayDensityDpi = getBaseDisplayDensity();
175
176 mVirtualDisplay = displayManager.createVirtualDisplay(
177 DISPLAY_NAME + "@" + System.identityHashCode(this), mHost.getWidth(),
178 mHost.getHeight(), mDisplayDensityDpi, null,
179 VIRTUAL_DISPLAY_FLAG_PUBLIC | VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY
180 | VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL);
181
182 if (mVirtualDisplay == null) {
183 Log.e(TAG, "Failed to initialize TaskEmbedder");
184 return false;
185 }
186
187 // Create a container surface to which the ActivityDisplay will be reparented
188 final String name = "TaskEmbedder - " + Integer.toHexString(System.identityHashCode(this));
189 mSurfaceControl = new SurfaceControl.Builder()
190 .setContainerLayer()
191 .setParent(parent)
192 .setName(name)
193 .build();
194
195 final int displayId = getDisplayId();
196
197 final IWindowManager wm = WindowManagerGlobal.getWindowManagerService();
198 try {
199 // TODO: Find a way to consolidate these calls to the server.
200 WindowManagerGlobal.getWindowSession().reparentDisplayContent(
201 mHost.getWindow(), mSurfaceControl, displayId);
202 wm.dontOverrideDisplayInfo(displayId);
203 if (mSingleTaskInstance) {
204 mContext.getSystemService(ActivityTaskManager.class)
205 .setDisplayToSingleTaskInstance(displayId);
206 }
207 setForwardedInsets(mForwardedInsets);
208 if (mHost.getWindow() != null) {
209 updateLocationAndTapExcludeRegion();
210 }
211 mTaskStackListener = new TaskStackListenerImpl();
212 try {
213 mActivityTaskManager.registerTaskStackListener(mTaskStackListener);
214 } catch (RemoteException e) {
215 Log.e(TAG, "Failed to register task stack listener", e);
216 }
217 } catch (RemoteException e) {
218 e.rethrowAsRuntimeException();
219 }
220 if (mListener != null && mVirtualDisplay != null) {
221 mListener.onInitialized();
222 }
223 mOpened = true;
224 mGuard.open("release");
225 return true;
226 }
227
228 /**
229 * Returns the surface control for the task surface. This should be parented to a screen
230 * surface for display/embedding purposes.
231 *
232 * @return the surface control for the task
233 */
234 public SurfaceControl getSurfaceControl() {
235 return mSurfaceControl;
236 }
237
238 /**
239 * Set forwarded insets on the virtual display.
240 *
241 * @see IWindowManager#setForwardedInsets
242 */
243 public void setForwardedInsets(Insets insets) {
244 mForwardedInsets = insets;
245 if (mVirtualDisplay == null) {
246 return;
247 }
248 try {
249 final IWindowManager wm = WindowManagerGlobal.getWindowManagerService();
250 wm.setForwardedInsets(getDisplayId(), mForwardedInsets);
251 } catch (RemoteException e) {
252 e.rethrowAsRuntimeException();
253 }
254 }
255
256 /** An opaque unique identifier for this task surface among others being managed by the app. */
257 public int getId() {
258 return getDisplayId();
259 }
260
261 int getDisplayId() {
262 if (mVirtualDisplay != null) {
263 return mVirtualDisplay.getDisplay().getDisplayId();
264 }
265 return Display.INVALID_DISPLAY;
266 }
267
268 /**
269 * Set the callback to be notified about state changes.
270 * <p>This class must finish initializing before {@link #startActivity(Intent)} can be called.
271 * <p>Note: If the instance was ready prior to this call being made, then
272 * {@link Listener#onInitialized()} will be called from within this method call.
273 *
274 * @param listener The listener to report events to.
275 *
276 * @see ActivityView.StateCallback
277 * @see #startActivity(Intent)
278 */
279 void setListener(TaskEmbedder.Listener listener) {
280 mListener = listener;
281 if (mListener != null && isInitialized()) {
282 mListener.onInitialized();
283 }
284 }
285
286 /**
287 * Launch a new activity into this container.
288 *
289 * @param intent Intent used to launch an activity
290 *
291 * @see #startActivity(PendingIntent)
292 */
293 public void startActivity(@NonNull Intent intent) {
294 final ActivityOptions options = prepareActivityOptions();
295 mContext.startActivity(intent, options.toBundle());
296 }
297
298 /**
299 * Launch a new activity into this container.
300 *
301 * @param intent Intent used to launch an activity
302 * @param user The UserHandle of the user to start this activity for
303 *
304 * @see #startActivity(PendingIntent)
305 */
306 public void startActivity(@NonNull Intent intent, UserHandle user) {
307 final ActivityOptions options = prepareActivityOptions();
308 mContext.startActivityAsUser(intent, options.toBundle(), user);
309 }
310
311 /**
312 * Launch a new activity into this container.
313 *
314 * @param pendingIntent Intent used to launch an activity
315 *
316 * @see #startActivity(Intent)
317 */
318 public void startActivity(@NonNull PendingIntent pendingIntent) {
319 final ActivityOptions options = prepareActivityOptions();
320 try {
321 pendingIntent.send(null /* context */, 0 /* code */, null /* intent */,
322 null /* onFinished */, null /* handler */, null /* requiredPermission */,
323 options.toBundle());
324 } catch (PendingIntent.CanceledException e) {
325 throw new RuntimeException(e);
326 }
327 }
328
329 /**
330 * Launch a new activity into this container.
331 *
332 * @param pendingIntent Intent used to launch an activity
333 * @param fillInIntent Additional Intent data, see {@link Intent#fillIn Intent.fillIn()}
334 * @param options options for the activity
335 *
336 * @see #startActivity(Intent)
337 */
338 public void startActivity(@NonNull PendingIntent pendingIntent, @Nullable Intent fillInIntent,
339 @NonNull ActivityOptions options) {
340
341 options.setLaunchDisplayId(mVirtualDisplay.getDisplay().getDisplayId());
342 try {
343 pendingIntent.send(mContext, 0 /* code */, fillInIntent,
344 null /* onFinished */, null /* handler */, null /* requiredPermission */,
345 options.toBundle());
346 } catch (PendingIntent.CanceledException e) {
347 throw new RuntimeException(e);
348 }
349 }
350
351 /**
Mady Mellor1bd16752019-10-25 12:30:09 -0700352 * Launch an activity represented by {@link ShortcutInfo} into this container.
353 * <p>The owner of this container must be allowed to access the shortcut information,
354 * as defined in {@link LauncherApps#hasShortcutHostPermission()} to use this method.
355 *
356 * @param shortcut the shortcut used to launch the activity.
357 * @param options options for the activity.
358 * @param sourceBounds the rect containing the source bounds of the clicked icon to open
359 * this shortcut.
360 *
361 * @see #startActivity(Intent)
362 */
363 public void startShortcutActivity(@NonNull ShortcutInfo shortcut,
364 @NonNull ActivityOptions options, @Nullable Rect sourceBounds) {
365 LauncherApps service =
366 (LauncherApps) mContext.getSystemService(Context.LAUNCHER_APPS_SERVICE);
367 options.setLaunchDisplayId(mVirtualDisplay.getDisplay().getDisplayId());
368 service.startShortcut(shortcut, sourceBounds, options.toBundle());
369 }
370
371 /**
Mark Renouf89ac9882019-10-07 16:28:18 -0400372 * Check if container is ready to launch and create {@link ActivityOptions} to target the
373 * virtual display.
374 */
375 private ActivityOptions prepareActivityOptions() {
376 if (mVirtualDisplay == null) {
377 throw new IllegalStateException(
378 "Trying to start activity before ActivityView is ready.");
379 }
380 final ActivityOptions options = ActivityOptions.makeBasic();
381 options.setLaunchDisplayId(getDisplayId());
382 return options;
383 }
384
385 /**
386 * Stops presentation of tasks in this container.
387 */
388 public void stop() {
389 if (mVirtualDisplay != null) {
390 mVirtualDisplay.setDisplayState(false);
391 clearActivityViewGeometryForIme();
392 clearTapExcludeRegion();
393 }
394 }
395
396 /**
397 * Starts presentation of tasks in this container.
398 */
399 public void start() {
400 if (mVirtualDisplay != null) {
401 mVirtualDisplay.setDisplayState(true);
402 updateLocationAndTapExcludeRegion();
403 }
404 }
405
406 /**
407 * This should be called whenever the position or size of the surface changes
408 * or if touchable areas above the surface are added or removed.
409 */
410 public void notifyBoundsChanged() {
411 updateLocationAndTapExcludeRegion();
412 }
413
414 /**
415 * Updates position and bounds information needed by WM and IME to manage window
416 * focus and touch events properly.
417 * <p>
418 * This should be called whenever the position or size of the surface changes
419 * or if touchable areas above the surface are added or removed.
420 */
421 private void updateLocationAndTapExcludeRegion() {
422 if (mVirtualDisplay == null || mHost.getWindow() == null) {
423 return;
424 }
425 reportLocation(mHost.getScreenToTaskMatrix(), mHost.getPositionInWindow());
426 applyTapExcludeRegion(mHost.getWindow(), hashCode(), mHost.getTapExcludeRegion());
427 }
428
429 /**
430 * Call to update the position and transform matrix for the embedded surface.
431 * <p>
432 * This should not normally be called directly, but through
433 * {@link #updateLocationAndTapExcludeRegion()}. This method
434 * is provided as an optimization when managing multiple TaskSurfaces within a view.
435 *
436 * @param screenToViewMatrix the matrix/transform from screen space to view space
437 * @param positionInWindow the window-relative position of the surface
438 *
439 * @see InputMethodManager#reportActivityView(int, Matrix)
440 */
441 private void reportLocation(Matrix screenToViewMatrix, Point positionInWindow) {
442 try {
443 final int displayId = getDisplayId();
444 mContext.getSystemService(InputMethodManager.class)
445 .reportActivityView(displayId, screenToViewMatrix);
446 IWindowSession session = WindowManagerGlobal.getWindowSession();
447 session.updateDisplayContentLocation(mHost.getWindow(), positionInWindow.x,
448 positionInWindow.y, displayId);
449 } catch (RemoteException e) {
450 e.rethrowAsRuntimeException();
451 }
452 }
453
454 /**
455 * Call to update the tap exclude region for the window.
456 * <p>
457 * This should not normally be called directly, but through
458 * {@link #updateLocationAndTapExcludeRegion()}. This method
459 * is provided as an optimization when managing multiple TaskSurfaces within a view.
460 *
461 * @see IWindowSession#updateTapExcludeRegion(IWindow, int, Region)
462 */
463 private void applyTapExcludeRegion(IWindow window, int regionId,
464 @Nullable Region tapExcludeRegion) {
465 try {
466 IWindowSession session = WindowManagerGlobal.getWindowSession();
467 session.updateTapExcludeRegion(window, regionId, tapExcludeRegion);
468 } catch (RemoteException e) {
469 e.rethrowAsRuntimeException();
470 }
471 }
472
473 /**
474 * @see InputMethodManager#reportActivityView(int, Matrix)
475 */
476 private void clearActivityViewGeometryForIme() {
477 final int displayId = getDisplayId();
478 mContext.getSystemService(InputMethodManager.class).reportActivityView(displayId, null);
479 }
480
481 /**
482 * Removes the tap exclude region set by {@link #updateLocationAndTapExcludeRegion()}.
483 */
484 private void clearTapExcludeRegion() {
485 if (mHost.getWindow() == null) {
486 Log.w(TAG, "clearTapExcludeRegion: not attached to window!");
487 return;
488 }
489 applyTapExcludeRegion(mHost.getWindow(), hashCode(), null);
490 }
491
492 /**
493 * Called to update the dimensions whenever the host size changes.
494 *
495 * @param width the new width of the surface
496 * @param height the new height of the surface
497 */
498 public void resizeTask(int width, int height) {
499 mDisplayDensityDpi = getBaseDisplayDensity();
500 if (mVirtualDisplay != null) {
501 mVirtualDisplay.resize(width, height, mDisplayDensityDpi);
502 }
503 }
504
505 /**
506 * Injects a pair of down/up key events with keycode {@link KeyEvent#KEYCODE_BACK} to the
507 * virtual display.
508 */
509 public void performBackPress() {
510 if (mVirtualDisplay == null) {
511 return;
512 }
513 final int displayId = mVirtualDisplay.getDisplay().getDisplayId();
514 final InputManager im = InputManager.getInstance();
515 im.injectInputEvent(createKeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK, displayId),
516 InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
517 im.injectInputEvent(createKeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK, displayId),
518 InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
519 }
520
521 private static KeyEvent createKeyEvent(int action, int code, int displayId) {
522 long when = SystemClock.uptimeMillis();
523 final KeyEvent ev = new KeyEvent(when, when, action, code, 0 /* repeat */,
524 0 /* metaState */, KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /* scancode */,
525 KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY,
526 InputDevice.SOURCE_KEYBOARD);
527 ev.setDisplayId(displayId);
528 return ev;
529 }
530
531 /**
532 * Releases the resources for this TaskEmbedder. Tasks will no longer be launchable
533 * within this container.
534 *
535 * <p>Note: Calling this method is allowed after {@link Listener#onInitialized()} callback is
536 * triggered and before {@link Listener#onReleased()}.
537 */
538 public void release() {
539 if (mVirtualDisplay == null) {
540 throw new IllegalStateException(
541 "Trying to release container that is not initialized.");
542 }
543 performRelease();
544 }
545
546 private boolean performRelease() {
547 if (!mOpened) {
548 return false;
549 }
550 mTransaction.reparent(mSurfaceControl, null).apply();
551 mSurfaceControl.release();
552
553 // Clear activity view geometry for IME on this display
554 clearActivityViewGeometryForIme();
555
556 // Clear tap-exclude region (if any) for this window.
557 clearTapExcludeRegion();
558
559 if (mTaskStackListener != null) {
560 try {
561 mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener);
562 } catch (RemoteException e) {
563 Log.e(TAG, "Failed to unregister task stack listener", e);
564 }
565 mTaskStackListener = null;
566 }
567
568 boolean reportReleased = false;
569 if (mVirtualDisplay != null) {
570 mVirtualDisplay.release();
571 mVirtualDisplay = null;
572 reportReleased = true;
573
574 }
575
576 if (mListener != null && reportReleased) {
577 mListener.onReleased();
578 }
579 mOpened = false;
580 mGuard.close();
581 return true;
582 }
583
584 @Override
585 protected void finalize() throws Throwable {
586 try {
587 if (mGuard != null) {
588 mGuard.warnIfOpen();
589 performRelease();
590 }
591 } finally {
592 super.finalize();
593 }
594 }
595
596 /** Get density of the hosting display. */
597 private int getBaseDisplayDensity() {
598 final WindowManager wm = mContext.getSystemService(WindowManager.class);
599 final DisplayMetrics metrics = new DisplayMetrics();
600 wm.getDefaultDisplay().getMetrics(metrics);
601 return metrics.densityDpi;
602 }
603
604 /**
605 * A task change listener that detects background color change of the topmost stack on our
606 * virtual display and updates the background of the surface view. This background will be shown
607 * when surface view is resized, but the app hasn't drawn its content in new size yet.
608 * It also calls StateCallback.onTaskMovedToFront to notify interested parties that the stack
609 * associated with the {@link ActivityView} has had a Task moved to the front. This is useful
610 * when needing to also bring the host Activity to the foreground at the same time.
611 */
612 private class TaskStackListenerImpl extends TaskStackListener {
613
614 @Override
615 public void onTaskDescriptionChanged(ActivityManager.RunningTaskInfo taskInfo)
616 throws RemoteException {
617 if (!isInitialized()
618 || taskInfo.displayId != getDisplayId()) {
619 return;
620 }
621
622 ActivityManager.StackInfo stackInfo = getTopMostStackInfo();
623 if (stackInfo == null) {
624 return;
625 }
626 // Found the topmost stack on target display. Now check if the topmost task's
627 // description changed.
628 if (taskInfo.taskId == stackInfo.taskIds[stackInfo.taskIds.length - 1]) {
629 mHost.onTaskBackgroundColorChanged(TaskEmbedder.this,
630 taskInfo.taskDescription.getBackgroundColor());
631 }
632 }
633
634 @Override
635 public void onTaskMovedToFront(ActivityManager.RunningTaskInfo taskInfo)
636 throws RemoteException {
637 if (!isInitialized() || mListener == null
638 || taskInfo.displayId != getDisplayId()) {
639 return;
640 }
641
642 ActivityManager.StackInfo stackInfo = getTopMostStackInfo();
643 // if StackInfo was null or unrelated to the "move to front" then there's no use
644 // notifying the callback
645 if (stackInfo != null
646 && taskInfo.taskId == stackInfo.taskIds[stackInfo.taskIds.length - 1]) {
647 mListener.onTaskMovedToFront(taskInfo.taskId);
648 }
649 }
650
651 @Override
652 public void onTaskCreated(int taskId, ComponentName componentName) throws RemoteException {
653 if (mListener == null || !isInitialized()) {
654 return;
655 }
656
657 ActivityManager.StackInfo stackInfo = getTopMostStackInfo();
658 // if StackInfo was null or unrelated to the task creation then there's no use
659 // notifying the callback
660 if (stackInfo != null
661 && taskId == stackInfo.taskIds[stackInfo.taskIds.length - 1]) {
662 mListener.onTaskCreated(taskId, componentName);
663 }
664 }
665
666 @Override
667 public void onTaskRemovalStarted(ActivityManager.RunningTaskInfo taskInfo)
668 throws RemoteException {
669 if (mListener == null || !isInitialized()
670 || taskInfo.displayId != getDisplayId()) {
671 return;
672 }
673 mListener.onTaskRemovalStarted(taskInfo.taskId);
674 }
675
676 private ActivityManager.StackInfo getTopMostStackInfo() throws RemoteException {
677 // Find the topmost task on our virtual display - it will define the background
678 // color of the surface view during resizing.
679 final int displayId = getDisplayId();
680 final List<ActivityManager.StackInfo> stackInfoList =
Evan Roskyfd439692019-11-06 16:12:59 -0800681 mActivityTaskManager.getAllStackInfosOnDisplay(displayId);
682 if (stackInfoList.isEmpty()) {
683 return null;
Mark Renouf89ac9882019-10-07 16:28:18 -0400684 }
Evan Roskyfd439692019-11-06 16:12:59 -0800685 return stackInfoList.get(0);
Mark Renouf89ac9882019-10-07 16:28:18 -0400686 }
687 }
688}