1 /**
2  * Copyright (c) 2017 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.app;
18 
19 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL;
20 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
21 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC;
22 import static android.view.Display.INVALID_DISPLAY;
23 
24 import android.annotation.NonNull;
25 import android.annotation.TestApi;
26 import android.app.ActivityManager.StackInfo;
27 import android.content.ComponentName;
28 import android.content.Context;
29 import android.content.Intent;
30 import android.graphics.Insets;
31 import android.graphics.Matrix;
32 import android.graphics.Region;
33 import android.hardware.display.DisplayManager;
34 import android.hardware.display.VirtualDisplay;
35 import android.hardware.input.InputManager;
36 import android.os.RemoteException;
37 import android.os.SystemClock;
38 import android.os.UserHandle;
39 import android.util.AttributeSet;
40 import android.util.DisplayMetrics;
41 import android.util.Log;
42 import android.view.IWindowManager;
43 import android.view.InputDevice;
44 import android.view.KeyCharacterMap;
45 import android.view.KeyEvent;
46 import android.view.SurfaceControl;
47 import android.view.SurfaceHolder;
48 import android.view.SurfaceSession;
49 import android.view.SurfaceView;
50 import android.view.View;
51 import android.view.ViewGroup;
52 import android.view.ViewParent;
53 import android.view.WindowManager;
54 import android.view.WindowManagerGlobal;
55 import android.view.inputmethod.InputMethodManager;
56 
57 import dalvik.system.CloseGuard;
58 
59 import java.util.List;
60 
61 /**
62  * Activity container that allows launching activities into itself.
63  * <p>Activity launching into this container is restricted by the same rules that apply to launching
64  * on VirtualDisplays.
65  * @hide
66  */
67 @TestApi
68 public class ActivityView extends ViewGroup {
69 
70     private static final String DISPLAY_NAME = "ActivityViewVirtualDisplay";
71     private static final String TAG = "ActivityView";
72 
73     private VirtualDisplay mVirtualDisplay;
74     private final SurfaceView mSurfaceView;
75 
76     /**
77      * This is the root surface for the VirtualDisplay. The VirtualDisplay child surfaces will be
78      * re-parented to this surface. This will also be a child of the SurfaceView's SurfaceControl.
79      */
80     private SurfaceControl mRootSurfaceControl;
81 
82     private final SurfaceCallback mSurfaceCallback;
83     private StateCallback mActivityViewCallback;
84 
85     private IActivityTaskManager mActivityTaskManager;
86     // Temp container to store view coordinates in window.
87     private final int[] mLocationInWindow = new int[2];
88 
89     // The latest tap exclude region that we've sent to WM.
90     private final Region mTapExcludeRegion = new Region();
91 
92     private TaskStackListener mTaskStackListener;
93 
94     private final CloseGuard mGuard = CloseGuard.get();
95     private boolean mOpened; // Protected by mGuard.
96 
97     private final SurfaceControl.Transaction mTmpTransaction = new SurfaceControl.Transaction();
98 
99     /** The ActivityView is only allowed to contain one task. */
100     private final boolean mSingleTaskInstance;
101 
102     private Insets mForwardedInsets;
103 
ActivityView(Context context)104     public ActivityView(Context context) {
105         this(context, null /* attrs */);
106     }
107 
ActivityView(Context context, AttributeSet attrs)108     public ActivityView(Context context, AttributeSet attrs) {
109         this(context, attrs, 0 /* defStyle */);
110     }
111 
ActivityView(Context context, AttributeSet attrs, int defStyle)112     public ActivityView(Context context, AttributeSet attrs, int defStyle) {
113         this(context, attrs, defStyle, false /*singleTaskInstance*/);
114     }
115 
ActivityView( Context context, AttributeSet attrs, int defStyle, boolean singleTaskInstance)116     public ActivityView(
117             Context context, AttributeSet attrs, int defStyle, boolean singleTaskInstance) {
118         super(context, attrs, defStyle);
119         mSingleTaskInstance = singleTaskInstance;
120 
121         mActivityTaskManager = ActivityTaskManager.getService();
122         mSurfaceView = new SurfaceView(context);
123         // Since ActivityView#getAlpha has been overridden, we should use parent class's alpha
124         // as master to synchronize surface view's alpha value.
125         mSurfaceView.setAlpha(super.getAlpha());
126         mSurfaceView.setUseAlpha();
127         mSurfaceCallback = new SurfaceCallback();
128         mSurfaceView.getHolder().addCallback(mSurfaceCallback);
129         addView(mSurfaceView);
130 
131         mOpened = true;
132         mGuard.open("release");
133     }
134 
135     /** Callback that notifies when the container is ready or destroyed. */
136     public abstract static class StateCallback {
137 
138         /**
139          * Called when the container is ready for launching activities. Calling
140          * {@link #startActivity(Intent)} prior to this callback will result in an
141          * {@link IllegalStateException}.
142          *
143          * @see #startActivity(Intent)
144          */
onActivityViewReady(ActivityView view)145         public abstract void onActivityViewReady(ActivityView view);
146 
147         /**
148          * Called when the container can no longer launch activities. Calling
149          * {@link #startActivity(Intent)} after this callback will result in an
150          * {@link IllegalStateException}.
151          *
152          * @see #startActivity(Intent)
153          */
onActivityViewDestroyed(ActivityView view)154         public abstract void onActivityViewDestroyed(ActivityView view);
155 
156         /**
157          * Called when a task is created inside the container.
158          * This is a filtered version of {@link TaskStackListener}
159          */
onTaskCreated(int taskId, ComponentName componentName)160         public void onTaskCreated(int taskId, ComponentName componentName) { }
161 
162         /**
163          * Called when a task is moved to the front of the stack inside the container.
164          * This is a filtered version of {@link TaskStackListener}
165          */
onTaskMovedToFront(int taskId)166         public void onTaskMovedToFront(int taskId) { }
167 
168         /**
169          * Called when a task is about to be removed from the stack inside the container.
170          * This is a filtered version of {@link TaskStackListener}
171          */
onTaskRemovalStarted(int taskId)172         public void onTaskRemovalStarted(int taskId) { }
173     }
174 
175     /**
176      * Set the callback to be notified about state changes.
177      * <p>This class must finish initializing before {@link #startActivity(Intent)} can be called.
178      * <p>Note: If the instance was ready prior to this call being made, then
179      * {@link StateCallback#onActivityViewReady(ActivityView)} will be called from within
180      * this method call.
181      *
182      * @param callback The callback to report events to.
183      *
184      * @see StateCallback
185      * @see #startActivity(Intent)
186      */
setCallback(StateCallback callback)187     public void setCallback(StateCallback callback) {
188         mActivityViewCallback = callback;
189 
190         if (mVirtualDisplay != null && mActivityViewCallback != null) {
191             mActivityViewCallback.onActivityViewReady(this);
192         }
193     }
194 
195     /**
196      * Sets the corner radius for the Activity displayed here. The corners will be
197      * cropped from the window painted by the contained Activity.
198      *
199      * @param cornerRadius the radius for the corners, in pixels
200      * @hide
201      */
setCornerRadius(float cornerRadius)202     public void setCornerRadius(float cornerRadius) {
203         mSurfaceView.setCornerRadius(cornerRadius);
204     }
205 
206     /**
207      * Launch a new activity into this container.
208      * <p>Activity resolved by the provided {@link Intent} must have
209      * {@link android.R.attr#resizeableActivity} attribute set to {@code true} in order to be
210      * launched here. Also, if activity is not owned by the owner of this container, it must allow
211      * embedding and the caller must have permission to embed.
212      * <p>Note: This class must finish initializing and
213      * {@link StateCallback#onActivityViewReady(ActivityView)} callback must be triggered before
214      * this method can be called.
215      *
216      * @param intent Intent used to launch an activity.
217      *
218      * @see StateCallback
219      * @see #startActivity(PendingIntent)
220      */
startActivity(@onNull Intent intent)221     public void startActivity(@NonNull Intent intent) {
222         final ActivityOptions options = prepareActivityOptions();
223         getContext().startActivity(intent, options.toBundle());
224     }
225 
226     /**
227      * Launch a new activity into this container.
228      * <p>Activity resolved by the provided {@link Intent} must have
229      * {@link android.R.attr#resizeableActivity} attribute set to {@code true} in order to be
230      * launched here. Also, if activity is not owned by the owner of this container, it must allow
231      * embedding and the caller must have permission to embed.
232      * <p>Note: This class must finish initializing and
233      * {@link StateCallback#onActivityViewReady(ActivityView)} callback must be triggered before
234      * this method can be called.
235      *
236      * @param intent Intent used to launch an activity.
237      * @param user The UserHandle of the user to start this activity for.
238      *
239      *
240      * @see StateCallback
241      * @see #startActivity(PendingIntent)
242      */
startActivity(@onNull Intent intent, UserHandle user)243     public void startActivity(@NonNull Intent intent, UserHandle user) {
244         final ActivityOptions options = prepareActivityOptions();
245         getContext().startActivityAsUser(intent, options.toBundle(), user);
246     }
247 
248     /**
249      * Launch a new activity into this container.
250      * <p>Activity resolved by the provided {@link PendingIntent} must have
251      * {@link android.R.attr#resizeableActivity} attribute set to {@code true} in order to be
252      * launched here. Also, if activity is not owned by the owner of this container, it must allow
253      * embedding and the caller must have permission to embed.
254      * <p>Note: This class must finish initializing and
255      * {@link StateCallback#onActivityViewReady(ActivityView)} callback must be triggered before
256      * this method can be called.
257      *
258      * @param pendingIntent Intent used to launch an activity.
259      *
260      * @see StateCallback
261      * @see #startActivity(Intent)
262      */
startActivity(@onNull PendingIntent pendingIntent)263     public void startActivity(@NonNull PendingIntent pendingIntent) {
264         final ActivityOptions options = prepareActivityOptions();
265         try {
266             pendingIntent.send(null /* context */, 0 /* code */, null /* intent */,
267                     null /* onFinished */, null /* handler */, null /* requiredPermission */,
268                     options.toBundle());
269         } catch (PendingIntent.CanceledException e) {
270             throw new RuntimeException(e);
271         }
272     }
273 
274     /**
275      * Launch a new activity into this container.
276      * <p>Activity resolved by the provided {@link PendingIntent} must have
277      * {@link android.R.attr#resizeableActivity} attribute set to {@code true} in order to be
278      * launched here. Also, if activity is not owned by the owner of this container, it must allow
279      * embedding and the caller must have permission to embed.
280      * <p>Note: This class must finish initializing and
281      * {@link StateCallback#onActivityViewReady(ActivityView)} callback must be triggered before
282      * this method can be called.
283      *
284      * @param pendingIntent Intent used to launch an activity.
285      * @param options options for the activity
286      *
287      * @see StateCallback
288      * @see #startActivity(Intent)
289      */
startActivity(@onNull PendingIntent pendingIntent, @NonNull ActivityOptions options)290     public void startActivity(@NonNull PendingIntent pendingIntent,
291             @NonNull ActivityOptions options) {
292         options.setLaunchDisplayId(mVirtualDisplay.getDisplay().getDisplayId());
293         try {
294             pendingIntent.send(null /* context */, 0 /* code */, null /* intent */,
295                     null /* onFinished */, null /* handler */, null /* requiredPermission */,
296                     options.toBundle());
297         } catch (PendingIntent.CanceledException e) {
298             throw new RuntimeException(e);
299         }
300     }
301 
302     /**
303      * Check if container is ready to launch and create {@link ActivityOptions} to target the
304      * virtual display.
305      */
prepareActivityOptions()306     private ActivityOptions prepareActivityOptions() {
307         if (mVirtualDisplay == null) {
308             throw new IllegalStateException(
309                     "Trying to start activity before ActivityView is ready.");
310         }
311         final ActivityOptions options = ActivityOptions.makeBasic();
312         options.setLaunchDisplayId(mVirtualDisplay.getDisplay().getDisplayId());
313         return options;
314     }
315 
316     /**
317      * Release this container. Activity launching will no longer be permitted.
318      * <p>Note: Calling this method is allowed after
319      * {@link StateCallback#onActivityViewReady(ActivityView)} callback was triggered and before
320      * {@link StateCallback#onActivityViewDestroyed(ActivityView)}.
321      *
322      * @see StateCallback
323      */
release()324     public void release() {
325         if (mVirtualDisplay == null) {
326             throw new IllegalStateException(
327                     "Trying to release container that is not initialized.");
328         }
329         performRelease();
330     }
331 
332     /**
333      * Triggers an update of {@link ActivityView}'s location in window to properly set tap exclude
334      * regions and avoid focus switches by touches on this view.
335      */
onLocationChanged()336     public void onLocationChanged() {
337         updateLocationAndTapExcludeRegion();
338     }
339 
clearActivityViewGeometryForIme()340     private void clearActivityViewGeometryForIme() {
341         if (mVirtualDisplay == null) {
342             return;
343         }
344         final int displayId = mVirtualDisplay.getDisplay().getDisplayId();
345         mContext.getSystemService(InputMethodManager.class).reportActivityView(displayId, null);
346     }
347 
348     @Override
onLayout(boolean changed, int l, int t, int r, int b)349     public void onLayout(boolean changed, int l, int t, int r, int b) {
350         mSurfaceView.layout(0 /* left */, 0 /* top */, r - l /* right */, b - t /* bottom */);
351     }
352 
353     /**
354      * Sets the alpha value when the content of {@link SurfaceView} needs to show or hide.
355      * <p>Note: The surface view may ignore the alpha value in some cases. Refer to
356      * {@link SurfaceView#setAlpha} for more details.
357      *
358      * @param alpha The opacity of the view.
359      */
360     @Override
setAlpha(float alpha)361     public void setAlpha(float alpha) {
362         super.setAlpha(alpha);
363 
364         if (mSurfaceView != null) {
365             mSurfaceView.setAlpha(alpha);
366         }
367     }
368 
369     @Override
getAlpha()370     public float getAlpha() {
371         return mSurfaceView.getAlpha();
372     }
373 
374     @Override
gatherTransparentRegion(Region region)375     public boolean gatherTransparentRegion(Region region) {
376         // The tap exclude region may be affected by any view on top of it, so we detect the
377         // possible change by monitoring this function.
378         updateLocationAndTapExcludeRegion();
379         return super.gatherTransparentRegion(region);
380     }
381 
382     /**
383      * Sends current location in window and tap exclude region to WM for this view.
384      */
updateLocationAndTapExcludeRegion()385     private void updateLocationAndTapExcludeRegion() {
386         if (mVirtualDisplay == null || !isAttachedToWindow()) {
387             return;
388         }
389         try {
390             int x = mLocationInWindow[0];
391             int y = mLocationInWindow[1];
392             getLocationInWindow(mLocationInWindow);
393             if (x != mLocationInWindow[0] || y != mLocationInWindow[1]) {
394                 x = mLocationInWindow[0];
395                 y = mLocationInWindow[1];
396                 final int displayId = mVirtualDisplay.getDisplay().getDisplayId();
397                 WindowManagerGlobal.getWindowSession().updateDisplayContentLocation(
398                         getWindow(), x, y, displayId);
399 
400                 // Also report this geometry information to InputMethodManagerService.
401                 // TODO(b/115693908): Unify this logic into the above WMS-based one.
402                 final Matrix matrix = new Matrix();
403                 matrix.set(getMatrix());
404                 matrix.postTranslate(x, y);
405                 mContext.getSystemService(InputMethodManager.class)
406                         .reportActivityView(displayId, matrix);
407             }
408             updateTapExcludeRegion(x, y);
409         } catch (RemoteException e) {
410             e.rethrowAsRuntimeException();
411         }
412     }
413 
414     /** Computes and sends current tap exclude region to WM for this view. */
updateTapExcludeRegion(int x, int y)415     private void updateTapExcludeRegion(int x, int y) throws RemoteException {
416         if (!canReceivePointerEvents()) {
417             cleanTapExcludeRegion();
418             return;
419         }
420         mTapExcludeRegion.set(x, y, x + getWidth(), y + getHeight());
421 
422         // There might be views on top of us. We need to subtract those areas from the tap
423         // exclude region.
424         final ViewParent parent = getParent();
425         if (parent != null) {
426             parent.subtractObscuredTouchableRegion(mTapExcludeRegion, this);
427         }
428 
429         WindowManagerGlobal.getWindowSession().updateTapExcludeRegion(getWindow(), hashCode(),
430                 mTapExcludeRegion);
431     }
432 
433     private class SurfaceCallback implements SurfaceHolder.Callback {
434         @Override
surfaceCreated(SurfaceHolder surfaceHolder)435         public void surfaceCreated(SurfaceHolder surfaceHolder) {
436             if (mVirtualDisplay == null) {
437                 initVirtualDisplay(new SurfaceSession());
438                 if (mVirtualDisplay != null && mActivityViewCallback != null) {
439                     mActivityViewCallback.onActivityViewReady(ActivityView.this);
440                 }
441             } else {
442                 mTmpTransaction.reparent(mRootSurfaceControl,
443                         mSurfaceView.getSurfaceControl()).apply();
444             }
445 
446             if (mVirtualDisplay != null) {
447                 mVirtualDisplay.setDisplayState(true);
448             }
449 
450             updateLocationAndTapExcludeRegion();
451         }
452 
453         @Override
surfaceChanged(SurfaceHolder surfaceHolder, int format, int width, int height)454         public void surfaceChanged(SurfaceHolder surfaceHolder, int format, int width, int height) {
455             if (mVirtualDisplay != null) {
456                 mVirtualDisplay.resize(width, height, getBaseDisplayDensity());
457             }
458             updateLocationAndTapExcludeRegion();
459         }
460 
461         @Override
surfaceDestroyed(SurfaceHolder surfaceHolder)462         public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
463             if (mVirtualDisplay != null) {
464                 mVirtualDisplay.setDisplayState(false);
465             }
466             clearActivityViewGeometryForIme();
467             cleanTapExcludeRegion();
468         }
469     }
470 
471     @Override
onVisibilityChanged(View changedView, int visibility)472     protected void onVisibilityChanged(View changedView, int visibility) {
473         super.onVisibilityChanged(changedView, visibility);
474         mSurfaceView.setVisibility(visibility);
475     }
476 
477     /**
478      * @return the display id of the virtual display.
479      */
getVirtualDisplayId()480     public int getVirtualDisplayId() {
481         if (mVirtualDisplay != null) {
482             return mVirtualDisplay.getDisplay().getDisplayId();
483         }
484         return INVALID_DISPLAY;
485     }
486 
487     /**
488      * Injects a pair of down/up key events with keycode {@link KeyEvent#KEYCODE_BACK} to the
489      * virtual display.
490      */
performBackPress()491     public void performBackPress() {
492         if (mVirtualDisplay == null) {
493             return;
494         }
495         final int displayId = mVirtualDisplay.getDisplay().getDisplayId();
496         final InputManager im = InputManager.getInstance();
497         im.injectInputEvent(createKeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK, displayId),
498                 InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
499         im.injectInputEvent(createKeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK, displayId),
500                 InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
501     }
502 
createKeyEvent(int action, int code, int displayId)503     private static KeyEvent createKeyEvent(int action, int code, int displayId) {
504         long when = SystemClock.uptimeMillis();
505         final KeyEvent ev = new KeyEvent(when, when, action, code, 0 /* repeat */,
506                 0 /* metaState */, KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /* scancode */,
507                 KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY,
508                 InputDevice.SOURCE_KEYBOARD);
509         ev.setDisplayId(displayId);
510         return ev;
511     }
512 
initVirtualDisplay(SurfaceSession surfaceSession)513     private void initVirtualDisplay(SurfaceSession surfaceSession) {
514         if (mVirtualDisplay != null) {
515             throw new IllegalStateException("Trying to initialize for the second time.");
516         }
517 
518         final int width = mSurfaceView.getWidth();
519         final int height = mSurfaceView.getHeight();
520         final DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
521 
522         mVirtualDisplay = displayManager.createVirtualDisplay(
523                 DISPLAY_NAME + "@" + System.identityHashCode(this), width, height,
524                 getBaseDisplayDensity(), null,
525                 VIRTUAL_DISPLAY_FLAG_PUBLIC | VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY
526                         | VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL);
527         if (mVirtualDisplay == null) {
528             Log.e(TAG, "Failed to initialize ActivityView");
529             return;
530         }
531 
532         final int displayId = mVirtualDisplay.getDisplay().getDisplayId();
533         final IWindowManager wm = WindowManagerGlobal.getWindowManagerService();
534 
535         mRootSurfaceControl = new SurfaceControl.Builder(surfaceSession)
536                 .setContainerLayer()
537                 .setParent(mSurfaceView.getSurfaceControl())
538                 .setName(DISPLAY_NAME)
539                 .build();
540 
541         try {
542             // TODO: Find a way to consolidate these calls to the server.
543             WindowManagerGlobal.getWindowSession().reparentDisplayContent(
544                     getWindow(), mRootSurfaceControl, displayId);
545             wm.dontOverrideDisplayInfo(displayId);
546             if (mSingleTaskInstance) {
547                 mActivityTaskManager.setDisplayToSingleTaskInstance(displayId);
548             }
549             wm.setForwardedInsets(displayId, mForwardedInsets);
550         } catch (RemoteException e) {
551             e.rethrowAsRuntimeException();
552         }
553 
554         mTmpTransaction.show(mRootSurfaceControl).apply();
555         mTaskStackListener = new TaskStackListenerImpl();
556         try {
557             mActivityTaskManager.registerTaskStackListener(mTaskStackListener);
558         } catch (RemoteException e) {
559             Log.e(TAG, "Failed to register task stack listener", e);
560         }
561     }
562 
performRelease()563     private void performRelease() {
564         if (!mOpened) {
565             return;
566         }
567 
568         mSurfaceView.getHolder().removeCallback(mSurfaceCallback);
569 
570         cleanTapExcludeRegion();
571 
572         if (mTaskStackListener != null) {
573             try {
574                 mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener);
575             } catch (RemoteException e) {
576                 Log.e(TAG, "Failed to unregister task stack listener", e);
577             }
578             mTaskStackListener = null;
579         }
580 
581         final boolean displayReleased;
582         if (mVirtualDisplay != null) {
583             mVirtualDisplay.release();
584             mVirtualDisplay = null;
585             displayReleased = true;
586         } else {
587             displayReleased = false;
588         }
589 
590         if (displayReleased && mActivityViewCallback != null) {
591             mActivityViewCallback.onActivityViewDestroyed(this);
592         }
593 
594         mGuard.close();
595         mOpened = false;
596     }
597 
598     /** Report to server that tap exclude region on hosting display should be cleared. */
cleanTapExcludeRegion()599     private void cleanTapExcludeRegion() {
600         if (!isAttachedToWindow() || mTapExcludeRegion.isEmpty()) {
601             return;
602         }
603         // Update tap exclude region with a null region to clean the state on server.
604         try {
605             WindowManagerGlobal.getWindowSession().updateTapExcludeRegion(getWindow(), hashCode(),
606                     null /* region */);
607             mTapExcludeRegion.setEmpty();
608         } catch (RemoteException e) {
609             e.rethrowAsRuntimeException();
610         }
611     }
612 
613     /** Get density of the hosting display. */
getBaseDisplayDensity()614     private int getBaseDisplayDensity() {
615         final WindowManager wm = mContext.getSystemService(WindowManager.class);
616         final DisplayMetrics metrics = new DisplayMetrics();
617         wm.getDefaultDisplay().getMetrics(metrics);
618         return metrics.densityDpi;
619     }
620 
621     @Override
finalize()622     protected void finalize() throws Throwable {
623         try {
624             if (mGuard != null) {
625                 mGuard.warnIfOpen();
626                 performRelease();
627             }
628         } finally {
629             super.finalize();
630         }
631     }
632 
633     /**
634      * Set forwarded insets on the virtual display.
635      *
636      * @see IWindowManager#setForwardedInsets
637      */
setForwardedInsets(Insets insets)638     public void setForwardedInsets(Insets insets) {
639         mForwardedInsets = insets;
640         if (mVirtualDisplay == null) {
641             return;
642         }
643         try {
644             final IWindowManager wm = WindowManagerGlobal.getWindowManagerService();
645             wm.setForwardedInsets(mVirtualDisplay.getDisplay().getDisplayId(), mForwardedInsets);
646         } catch (RemoteException e) {
647             e.rethrowAsRuntimeException();
648         }
649     }
650 
651     /**
652      * A task change listener that detects background color change of the topmost stack on our
653      * virtual display and updates the background of the surface view. This background will be shown
654      * when surface view is resized, but the app hasn't drawn its content in new size yet.
655      * It also calls StateCallback.onTaskMovedToFront to notify interested parties that the stack
656      * associated with the {@link ActivityView} has had a Task moved to the front. This is useful
657      * when needing to also bring the host Activity to the foreground at the same time.
658      */
659     private class TaskStackListenerImpl extends TaskStackListener {
660 
661         @Override
onTaskDescriptionChanged(ActivityManager.RunningTaskInfo taskInfo)662         public void onTaskDescriptionChanged(ActivityManager.RunningTaskInfo taskInfo)
663                 throws RemoteException {
664             if (mVirtualDisplay == null
665                     || taskInfo.displayId != mVirtualDisplay.getDisplay().getDisplayId()) {
666                 return;
667             }
668 
669             StackInfo stackInfo = getTopMostStackInfo();
670             if (stackInfo == null) {
671                 return;
672             }
673             // Found the topmost stack on target display. Now check if the topmost task's
674             // description changed.
675             if (taskInfo.taskId == stackInfo.taskIds[stackInfo.taskIds.length - 1]) {
676                 mSurfaceView.setResizeBackgroundColor(
677                         taskInfo.taskDescription.getBackgroundColor());
678             }
679         }
680 
681         @Override
onTaskMovedToFront(ActivityManager.RunningTaskInfo taskInfo)682         public void onTaskMovedToFront(ActivityManager.RunningTaskInfo taskInfo)
683                 throws RemoteException {
684             if (mActivityViewCallback  == null || mVirtualDisplay == null
685                     || taskInfo.displayId != mVirtualDisplay.getDisplay().getDisplayId()) {
686                 return;
687             }
688 
689             StackInfo stackInfo = getTopMostStackInfo();
690             // if StackInfo was null or unrelated to the "move to front" then there's no use
691             // notifying the callback
692             if (stackInfo != null
693                     && taskInfo.taskId == stackInfo.taskIds[stackInfo.taskIds.length - 1]) {
694                 mActivityViewCallback.onTaskMovedToFront(taskInfo.taskId);
695             }
696         }
697 
698         @Override
onTaskCreated(int taskId, ComponentName componentName)699         public void onTaskCreated(int taskId, ComponentName componentName) throws RemoteException {
700             if (mActivityViewCallback == null || mVirtualDisplay == null) {
701                 return;
702             }
703 
704             StackInfo stackInfo = getTopMostStackInfo();
705             // if StackInfo was null or unrelated to the task creation then there's no use
706             // notifying the callback
707             if (stackInfo != null
708                     && taskId == stackInfo.taskIds[stackInfo.taskIds.length - 1]) {
709                 mActivityViewCallback.onTaskCreated(taskId, componentName);
710             }
711         }
712 
713         @Override
onTaskRemovalStarted(ActivityManager.RunningTaskInfo taskInfo)714         public void onTaskRemovalStarted(ActivityManager.RunningTaskInfo taskInfo)
715                 throws RemoteException {
716             if (mActivityViewCallback == null || mVirtualDisplay == null
717                     || taskInfo.displayId != mVirtualDisplay.getDisplay().getDisplayId()) {
718                 return;
719             }
720             mActivityViewCallback.onTaskRemovalStarted(taskInfo.taskId);
721         }
722 
getTopMostStackInfo()723         private StackInfo getTopMostStackInfo() throws RemoteException {
724             // Find the topmost task on our virtual display - it will define the background
725             // color of the surface view during resizing.
726             final int displayId = mVirtualDisplay.getDisplay().getDisplayId();
727             final List<StackInfo> stackInfoList = mActivityTaskManager.getAllStackInfos();
728 
729             // Iterate through stacks from top to bottom.
730             final int stackCount = stackInfoList.size();
731             for (int i = 0; i < stackCount; i++) {
732                 final StackInfo stackInfo = stackInfoList.get(i);
733                 // Only look for stacks on our virtual display.
734                 if (stackInfo.displayId != displayId) {
735                     continue;
736                 }
737                 // Found the topmost stack on target display.
738                 return stackInfo;
739             }
740             return null;
741         }
742     }
743 }
744