blob: 4771f9f6ad04974c16cc7a1dfc6530b8eff01990 [file] [log] [blame]
Andrii Kuliand3134692017-06-26 14:57:02 -07001/**
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
17package android.app;
18
chaviwda4c6942018-11-07 15:52:56 -080019import 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;
Mady Mellor390bff42019-04-05 15:09:01 -070022import static android.view.Display.INVALID_DISPLAY;
chaviwda4c6942018-11-07 15:52:56 -080023
Andrii Kuliand3134692017-06-26 14:57:02 -070024import android.annotation.NonNull;
Wale Ogunwale691af682019-02-11 03:09:10 -080025import android.annotation.TestApi;
Andrii Kuliancf8f6832018-01-23 19:43:30 -080026import android.app.ActivityManager.StackInfo;
Mark Renouf7b71a5d2019-01-23 17:12:34 -050027import android.content.ComponentName;
Andrii Kuliand3134692017-06-26 14:57:02 -070028import android.content.Context;
29import android.content.Intent;
Issei Suzukia5dbf522019-02-01 17:58:15 +010030import android.graphics.Insets;
Yohei Yukawab4f328a2019-05-02 08:41:27 -070031import android.graphics.Matrix;
Tiger Huang2b210c22019-03-18 21:21:26 +080032import android.graphics.Region;
Andrii Kuliand3134692017-06-26 14:57:02 -070033import android.hardware.display.DisplayManager;
34import android.hardware.display.VirtualDisplay;
Mark Renouf041d7262019-02-06 12:09:41 -050035import android.hardware.input.InputManager;
Andrii Kuliand3134692017-06-26 14:57:02 -070036import android.os.RemoteException;
Mark Renouf041d7262019-02-06 12:09:41 -050037import android.os.SystemClock;
Brad Stenninga1dbe9c2018-05-02 08:29:28 -070038import android.os.UserHandle;
Andrii Kuliand3134692017-06-26 14:57:02 -070039import android.util.AttributeSet;
40import android.util.DisplayMetrics;
41import android.util.Log;
Andrii Kulianf0379de2018-03-14 16:24:07 -070042import android.view.IWindowManager;
Mark Renouf041d7262019-02-06 12:09:41 -050043import android.view.InputDevice;
44import android.view.KeyCharacterMap;
45import android.view.KeyEvent;
chaviwff2e7d82018-11-02 11:11:27 -070046import android.view.SurfaceControl;
Andrii Kuliand3134692017-06-26 14:57:02 -070047import android.view.SurfaceHolder;
chaviwff2e7d82018-11-02 11:11:27 -070048import android.view.SurfaceSession;
Andrii Kuliand3134692017-06-26 14:57:02 -070049import android.view.SurfaceView;
chaviwd44449d2019-01-02 16:33:12 -080050import android.view.View;
Andrii Kuliand3134692017-06-26 14:57:02 -070051import android.view.ViewGroup;
Tiger Huang2b210c22019-03-18 21:21:26 +080052import android.view.ViewParent;
Andrii Kuliand3134692017-06-26 14:57:02 -070053import android.view.WindowManager;
Andrii Kulian4b6599e2018-01-15 17:24:08 -080054import android.view.WindowManagerGlobal;
Yohei Yukawab4f328a2019-05-02 08:41:27 -070055import android.view.inputmethod.InputMethodManager;
Andrii Kuliand3134692017-06-26 14:57:02 -070056
57import dalvik.system.CloseGuard;
58
Andrii Kuliancf8f6832018-01-23 19:43:30 -080059import java.util.List;
60
Andrii Kuliand3134692017-06-26 14:57:02 -070061/**
Tiger Huang04dc4cc2019-01-17 18:41:41 +080062 * Activity container that allows launching activities into itself.
Andrii Kuliand3134692017-06-26 14:57:02 -070063 * <p>Activity launching into this container is restricted by the same rules that apply to launching
64 * on VirtualDisplays.
65 * @hide
66 */
Wale Ogunwale691af682019-02-11 03:09:10 -080067@TestApi
Andrii Kuliand3134692017-06-26 14:57:02 -070068public 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;
chaviwff2e7d82018-11-02 11:11:27 -070075
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;
Andrii Kuliand3134692017-06-26 14:57:02 -070081
82 private final SurfaceCallback mSurfaceCallback;
83 private StateCallback mActivityViewCallback;
84
Wale Ogunwale04d9cb52018-04-30 13:55:07 -070085 private IActivityTaskManager mActivityTaskManager;
Tiger Huang04dc4cc2019-01-17 18:41:41 +080086 // Temp container to store view coordinates in window.
87 private final int[] mLocationInWindow = new int[2];
Andrii Kuliand3134692017-06-26 14:57:02 -070088
Tiger Huang2b210c22019-03-18 21:21:26 +080089 // The latest tap exclude region that we've sent to WM.
90 private final Region mTapExcludeRegion = new Region();
91
Andrii Kuliancf8f6832018-01-23 19:43:30 -080092 private TaskStackListener mTaskStackListener;
93
Andrii Kuliand3134692017-06-26 14:57:02 -070094 private final CloseGuard mGuard = CloseGuard.get();
95 private boolean mOpened; // Protected by mGuard.
96
chaviwff2e7d82018-11-02 11:11:27 -070097 private final SurfaceControl.Transaction mTmpTransaction = new SurfaceControl.Transaction();
chaviwff2e7d82018-11-02 11:11:27 -070098
Wale Ogunwale9e737db2018-12-17 15:42:37 -080099 /** The ActivityView is only allowed to contain one task. */
100 private final boolean mSingleTaskInstance;
101
Issei Suzukia5dbf522019-02-01 17:58:15 +0100102 private Insets mForwardedInsets;
103
Andrii Kuliand3134692017-06-26 14:57:02 -0700104 public ActivityView(Context context) {
105 this(context, null /* attrs */);
106 }
107
108 public ActivityView(Context context, AttributeSet attrs) {
109 this(context, attrs, 0 /* defStyle */);
110 }
111
112 public ActivityView(Context context, AttributeSet attrs, int defStyle) {
Wale Ogunwale9e737db2018-12-17 15:42:37 -0800113 this(context, attrs, defStyle, false /*singleTaskInstance*/);
114 }
115
116 public ActivityView(
117 Context context, AttributeSet attrs, int defStyle, boolean singleTaskInstance) {
Andrii Kuliand3134692017-06-26 14:57:02 -0700118 super(context, attrs, defStyle);
Wale Ogunwale9e737db2018-12-17 15:42:37 -0800119 mSingleTaskInstance = singleTaskInstance;
Andrii Kuliand3134692017-06-26 14:57:02 -0700120
Wale Ogunwale04d9cb52018-04-30 13:55:07 -0700121 mActivityTaskManager = ActivityTaskManager.getService();
Andrii Kuliand3134692017-06-26 14:57:02 -0700122 mSurfaceView = new SurfaceView(context);
123 mSurfaceCallback = new SurfaceCallback();
124 mSurfaceView.getHolder().addCallback(mSurfaceCallback);
125 addView(mSurfaceView);
126
127 mOpened = true;
128 mGuard.open("release");
129 }
130
131 /** Callback that notifies when the container is ready or destroyed. */
132 public abstract static class StateCallback {
Mark Renouf7b71a5d2019-01-23 17:12:34 -0500133
Andrii Kuliand3134692017-06-26 14:57:02 -0700134 /**
135 * Called when the container is ready for launching activities. Calling
136 * {@link #startActivity(Intent)} prior to this callback will result in an
137 * {@link IllegalStateException}.
138 *
139 * @see #startActivity(Intent)
140 */
141 public abstract void onActivityViewReady(ActivityView view);
Mark Renouf7b71a5d2019-01-23 17:12:34 -0500142
Andrii Kuliand3134692017-06-26 14:57:02 -0700143 /**
144 * Called when the container can no longer launch activities. Calling
145 * {@link #startActivity(Intent)} after this callback will result in an
146 * {@link IllegalStateException}.
147 *
148 * @see #startActivity(Intent)
149 */
150 public abstract void onActivityViewDestroyed(ActivityView view);
Mark Renouf7b71a5d2019-01-23 17:12:34 -0500151
152 /**
153 * Called when a task is created inside the container.
154 * This is a filtered version of {@link TaskStackListener}
155 */
156 public void onTaskCreated(int taskId, ComponentName componentName) { }
157
Brad Stenninga1dbe9c2018-05-02 08:29:28 -0700158 /**
159 * Called when a task is moved to the front of the stack inside the container.
160 * This is a filtered version of {@link TaskStackListener}
161 */
Wale Ogunwale691af682019-02-11 03:09:10 -0800162 public void onTaskMovedToFront(int taskId) { }
Mark Renouf7b71a5d2019-01-23 17:12:34 -0500163
164 /**
165 * Called when a task is about to be removed from the stack inside the container.
166 * This is a filtered version of {@link TaskStackListener}
167 */
168 public void onTaskRemovalStarted(int taskId) { }
Andrii Kuliand3134692017-06-26 14:57:02 -0700169 }
170
171 /**
172 * Set the callback to be notified about state changes.
173 * <p>This class must finish initializing before {@link #startActivity(Intent)} can be called.
174 * <p>Note: If the instance was ready prior to this call being made, then
175 * {@link StateCallback#onActivityViewReady(ActivityView)} will be called from within
176 * this method call.
177 *
178 * @param callback The callback to report events to.
179 *
180 * @see StateCallback
181 * @see #startActivity(Intent)
182 */
183 public void setCallback(StateCallback callback) {
184 mActivityViewCallback = callback;
185
186 if (mVirtualDisplay != null && mActivityViewCallback != null) {
187 mActivityViewCallback.onActivityViewReady(this);
188 }
189 }
190
191 /**
Mark Renouf34d04f32019-05-13 15:53:18 -0400192 * Sets the corner radius for the Activity displayed here. The corners will be
193 * cropped from the window painted by the contained Activity.
194 *
195 * @param cornerRadius the radius for the corners, in pixels
196 * @hide
197 */
198 public void setCornerRadius(float cornerRadius) {
199 mSurfaceView.setCornerRadius(cornerRadius);
200 }
201
202 /**
Andrii Kuliand3134692017-06-26 14:57:02 -0700203 * Launch a new activity into this container.
204 * <p>Activity resolved by the provided {@link Intent} must have
205 * {@link android.R.attr#resizeableActivity} attribute set to {@code true} in order to be
206 * launched here. Also, if activity is not owned by the owner of this container, it must allow
207 * embedding and the caller must have permission to embed.
208 * <p>Note: This class must finish initializing and
209 * {@link StateCallback#onActivityViewReady(ActivityView)} callback must be triggered before
210 * this method can be called.
211 *
212 * @param intent Intent used to launch an activity.
213 *
214 * @see StateCallback
215 * @see #startActivity(PendingIntent)
216 */
217 public void startActivity(@NonNull Intent intent) {
218 final ActivityOptions options = prepareActivityOptions();
219 getContext().startActivity(intent, options.toBundle());
220 }
221
222 /**
223 * Launch a new activity into this container.
Brad Stenninga1dbe9c2018-05-02 08:29:28 -0700224 * <p>Activity resolved by the provided {@link Intent} must have
225 * {@link android.R.attr#resizeableActivity} attribute set to {@code true} in order to be
226 * launched here. Also, if activity is not owned by the owner of this container, it must allow
227 * embedding and the caller must have permission to embed.
228 * <p>Note: This class must finish initializing and
229 * {@link StateCallback#onActivityViewReady(ActivityView)} callback must be triggered before
230 * this method can be called.
231 *
232 * @param intent Intent used to launch an activity.
233 * @param user The UserHandle of the user to start this activity for.
234 *
235 *
236 * @see StateCallback
237 * @see #startActivity(PendingIntent)
238 */
239 public void startActivity(@NonNull Intent intent, UserHandle user) {
240 final ActivityOptions options = prepareActivityOptions();
241 getContext().startActivityAsUser(intent, options.toBundle(), user);
242 }
243
244 /**
245 * Launch a new activity into this container.
Andrii Kuliand3134692017-06-26 14:57:02 -0700246 * <p>Activity resolved by the provided {@link PendingIntent} must have
247 * {@link android.R.attr#resizeableActivity} attribute set to {@code true} in order to be
248 * launched here. Also, if activity is not owned by the owner of this container, it must allow
249 * embedding and the caller must have permission to embed.
250 * <p>Note: This class must finish initializing and
251 * {@link StateCallback#onActivityViewReady(ActivityView)} callback must be triggered before
252 * this method can be called.
253 *
254 * @param pendingIntent Intent used to launch an activity.
255 *
256 * @see StateCallback
257 * @see #startActivity(Intent)
258 */
259 public void startActivity(@NonNull PendingIntent pendingIntent) {
260 final ActivityOptions options = prepareActivityOptions();
261 try {
262 pendingIntent.send(null /* context */, 0 /* code */, null /* intent */,
263 null /* onFinished */, null /* handler */, null /* requiredPermission */,
264 options.toBundle());
265 } catch (PendingIntent.CanceledException e) {
266 throw new RuntimeException(e);
267 }
268 }
269
270 /**
Mady Mellor60101c92019-04-11 19:04:00 -0700271 * Launch a new activity into this container.
272 * <p>Activity resolved by the provided {@link PendingIntent} must have
273 * {@link android.R.attr#resizeableActivity} attribute set to {@code true} in order to be
274 * launched here. Also, if activity is not owned by the owner of this container, it must allow
275 * embedding and the caller must have permission to embed.
276 * <p>Note: This class must finish initializing and
277 * {@link StateCallback#onActivityViewReady(ActivityView)} callback must be triggered before
278 * this method can be called.
279 *
280 * @param pendingIntent Intent used to launch an activity.
281 * @param options options for the activity
282 *
283 * @see StateCallback
284 * @see #startActivity(Intent)
285 */
286 public void startActivity(@NonNull PendingIntent pendingIntent,
287 @NonNull ActivityOptions options) {
288 options.setLaunchDisplayId(mVirtualDisplay.getDisplay().getDisplayId());
289 try {
290 pendingIntent.send(null /* context */, 0 /* code */, null /* intent */,
291 null /* onFinished */, null /* handler */, null /* requiredPermission */,
292 options.toBundle());
293 } catch (PendingIntent.CanceledException e) {
294 throw new RuntimeException(e);
295 }
296 }
297
298 /**
Andrii Kuliand3134692017-06-26 14:57:02 -0700299 * Check if container is ready to launch and create {@link ActivityOptions} to target the
300 * virtual display.
301 */
302 private ActivityOptions prepareActivityOptions() {
303 if (mVirtualDisplay == null) {
304 throw new IllegalStateException(
305 "Trying to start activity before ActivityView is ready.");
306 }
307 final ActivityOptions options = ActivityOptions.makeBasic();
308 options.setLaunchDisplayId(mVirtualDisplay.getDisplay().getDisplayId());
309 return options;
310 }
311
312 /**
313 * Release this container. Activity launching will no longer be permitted.
314 * <p>Note: Calling this method is allowed after
315 * {@link StateCallback#onActivityViewReady(ActivityView)} callback was triggered and before
316 * {@link StateCallback#onActivityViewDestroyed(ActivityView)}.
317 *
318 * @see StateCallback
319 */
320 public void release() {
321 if (mVirtualDisplay == null) {
322 throw new IllegalStateException(
323 "Trying to release container that is not initialized.");
324 }
325 performRelease();
326 }
327
Andrii Kulian4b6599e2018-01-15 17:24:08 -0800328 /**
Tiger Huang2b210c22019-03-18 21:21:26 +0800329 * Triggers an update of {@link ActivityView}'s location in window to properly set tap exclude
Andrii Kulian4b6599e2018-01-15 17:24:08 -0800330 * regions and avoid focus switches by touches on this view.
331 */
332 public void onLocationChanged() {
Tiger Huangd8ec9382019-04-18 14:35:09 -0700333 updateLocationAndTapExcludeRegion();
Andrii Kulian4b6599e2018-01-15 17:24:08 -0800334 }
335
Yohei Yukawab4f328a2019-05-02 08:41:27 -0700336 private void clearActivityViewGeometryForIme() {
337 if (mVirtualDisplay == null) {
338 return;
339 }
340 final int displayId = mVirtualDisplay.getDisplay().getDisplayId();
341 mContext.getSystemService(InputMethodManager.class).reportActivityView(displayId, null);
342 }
343
Andrii Kuliand3134692017-06-26 14:57:02 -0700344 @Override
345 public void onLayout(boolean changed, int l, int t, int r, int b) {
346 mSurfaceView.layout(0 /* left */, 0 /* top */, r - l /* right */, b - t /* bottom */);
347 }
348
Tiger Huang2b210c22019-03-18 21:21:26 +0800349 @Override
350 public boolean gatherTransparentRegion(Region region) {
351 // The tap exclude region may be affected by any view on top of it, so we detect the
352 // possible change by monitoring this function.
Tiger Huangd8ec9382019-04-18 14:35:09 -0700353 updateLocationAndTapExcludeRegion();
Tiger Huang2b210c22019-03-18 21:21:26 +0800354 return super.gatherTransparentRegion(region);
355 }
356
Tiger Huangd8ec9382019-04-18 14:35:09 -0700357 /**
358 * Sends current location in window and tap exclude region to WM for this view.
359 */
360 private void updateLocationAndTapExcludeRegion() {
361 if (mVirtualDisplay == null || !isAttachedToWindow()) {
Mark Renouf3856b4f2019-02-13 16:43:18 -0500362 return;
363 }
Tiger Huangd8ec9382019-04-18 14:35:09 -0700364 try {
365 int x = mLocationInWindow[0];
366 int y = mLocationInWindow[1];
367 getLocationInWindow(mLocationInWindow);
368 if (x != mLocationInWindow[0] || y != mLocationInWindow[1]) {
369 x = mLocationInWindow[0];
370 y = mLocationInWindow[1];
Yohei Yukawab4f328a2019-05-02 08:41:27 -0700371 final int displayId = mVirtualDisplay.getDisplay().getDisplayId();
Tiger Huangd8ec9382019-04-18 14:35:09 -0700372 WindowManagerGlobal.getWindowSession().updateDisplayContentLocation(
Yohei Yukawab4f328a2019-05-02 08:41:27 -0700373 getWindow(), x, y, displayId);
374
375 // Also report this geometry information to InputMethodManagerService.
376 // TODO(b/115693908): Unify this logic into the above WMS-based one.
377 final Matrix matrix = new Matrix();
378 matrix.set(getMatrix());
379 matrix.postTranslate(x, y);
380 mContext.getSystemService(InputMethodManager.class)
381 .reportActivityView(displayId, matrix);
Tiger Huangd8ec9382019-04-18 14:35:09 -0700382 }
383 updateTapExcludeRegion(x, y);
384 } catch (RemoteException e) {
385 e.rethrowAsRuntimeException();
386 }
387 }
388
389 /** Computes and sends current tap exclude region to WM for this view. */
390 private void updateTapExcludeRegion(int x, int y) throws RemoteException {
Tiger Huang2b210c22019-03-18 21:21:26 +0800391 if (!canReceivePointerEvents()) {
392 cleanTapExcludeRegion();
393 return;
394 }
Tiger Huangd8ec9382019-04-18 14:35:09 -0700395 mTapExcludeRegion.set(x, y, x + getWidth(), y + getHeight());
Tiger Huang2b210c22019-03-18 21:21:26 +0800396
Tiger Huangd8ec9382019-04-18 14:35:09 -0700397 // There might be views on top of us. We need to subtract those areas from the tap
398 // exclude region.
399 final ViewParent parent = getParent();
400 if (parent != null) {
401 parent.subtractObscuredTouchableRegion(mTapExcludeRegion, this);
Andrii Kulian4b6599e2018-01-15 17:24:08 -0800402 }
Tiger Huangd8ec9382019-04-18 14:35:09 -0700403
404 WindowManagerGlobal.getWindowSession().updateTapExcludeRegion(getWindow(), hashCode(),
405 mTapExcludeRegion);
Andrii Kulian4b6599e2018-01-15 17:24:08 -0800406 }
407
Andrii Kuliand3134692017-06-26 14:57:02 -0700408 private class SurfaceCallback implements SurfaceHolder.Callback {
409 @Override
410 public void surfaceCreated(SurfaceHolder surfaceHolder) {
411 if (mVirtualDisplay == null) {
Robert Carr5fea55b2018-12-10 13:05:52 -0800412 initVirtualDisplay(new SurfaceSession());
Andrii Kuliand3134692017-06-26 14:57:02 -0700413 if (mVirtualDisplay != null && mActivityViewCallback != null) {
414 mActivityViewCallback.onActivityViewReady(ActivityView.this);
415 }
416 } else {
chaviwff2e7d82018-11-02 11:11:27 -0700417 mTmpTransaction.reparent(mRootSurfaceControl,
Robert Carr10584fa2019-01-14 15:55:19 -0800418 mSurfaceView.getSurfaceControl()).apply();
Andrii Kuliand3134692017-06-26 14:57:02 -0700419 }
chaviwda4c6942018-11-07 15:52:56 -0800420
421 if (mVirtualDisplay != null) {
422 mVirtualDisplay.setDisplayState(true);
423 }
424
Tiger Huangd8ec9382019-04-18 14:35:09 -0700425 updateLocationAndTapExcludeRegion();
Andrii Kuliand3134692017-06-26 14:57:02 -0700426 }
427
428 @Override
429 public void surfaceChanged(SurfaceHolder surfaceHolder, int format, int width, int height) {
430 if (mVirtualDisplay != null) {
431 mVirtualDisplay.resize(width, height, getBaseDisplayDensity());
432 }
Tiger Huangd8ec9382019-04-18 14:35:09 -0700433 updateLocationAndTapExcludeRegion();
Andrii Kuliand3134692017-06-26 14:57:02 -0700434 }
435
436 @Override
437 public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
Andrii Kuliand3134692017-06-26 14:57:02 -0700438 if (mVirtualDisplay != null) {
chaviwda4c6942018-11-07 15:52:56 -0800439 mVirtualDisplay.setDisplayState(false);
Andrii Kuliand3134692017-06-26 14:57:02 -0700440 }
Yohei Yukawab4f328a2019-05-02 08:41:27 -0700441 clearActivityViewGeometryForIme();
Andrii Kulian4b6599e2018-01-15 17:24:08 -0800442 cleanTapExcludeRegion();
Andrii Kuliand3134692017-06-26 14:57:02 -0700443 }
444 }
445
chaviwd44449d2019-01-02 16:33:12 -0800446 @Override
447 protected void onVisibilityChanged(View changedView, int visibility) {
448 super.onVisibilityChanged(changedView, visibility);
449 mSurfaceView.setVisibility(visibility);
450 }
451
Mark Renouf041d7262019-02-06 12:09:41 -0500452 /**
Mady Mellor390bff42019-04-05 15:09:01 -0700453 * @return the display id of the virtual display.
454 */
455 public int getVirtualDisplayId() {
456 if (mVirtualDisplay != null) {
457 return mVirtualDisplay.getDisplay().getDisplayId();
458 }
459 return INVALID_DISPLAY;
460 }
461
462 /**
Mark Renouf041d7262019-02-06 12:09:41 -0500463 * Injects a pair of down/up key events with keycode {@link KeyEvent#KEYCODE_BACK} to the
464 * virtual display.
465 */
466 public void performBackPress() {
467 if (mVirtualDisplay == null) {
468 return;
469 }
470 final int displayId = mVirtualDisplay.getDisplay().getDisplayId();
471 final InputManager im = InputManager.getInstance();
472 im.injectInputEvent(createKeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK, displayId),
473 InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
474 im.injectInputEvent(createKeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK, displayId),
475 InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
476 }
477
478 private static KeyEvent createKeyEvent(int action, int code, int displayId) {
479 long when = SystemClock.uptimeMillis();
480 final KeyEvent ev = new KeyEvent(when, when, action, code, 0 /* repeat */,
481 0 /* metaState */, KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /* scancode */,
482 KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY,
483 InputDevice.SOURCE_KEYBOARD);
484 ev.setDisplayId(displayId);
485 return ev;
486 }
487
chaviwff2e7d82018-11-02 11:11:27 -0700488 private void initVirtualDisplay(SurfaceSession surfaceSession) {
Andrii Kuliand3134692017-06-26 14:57:02 -0700489 if (mVirtualDisplay != null) {
490 throw new IllegalStateException("Trying to initialize for the second time.");
491 }
492
493 final int width = mSurfaceView.getWidth();
494 final int height = mSurfaceView.getHeight();
495 final DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
chaviwff2e7d82018-11-02 11:11:27 -0700496
Andrii Kuliand3134692017-06-26 14:57:02 -0700497 mVirtualDisplay = displayManager.createVirtualDisplay(
chaviwda4c6942018-11-07 15:52:56 -0800498 DISPLAY_NAME + "@" + System.identityHashCode(this), width, height,
499 getBaseDisplayDensity(), null,
500 VIRTUAL_DISPLAY_FLAG_PUBLIC | VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY
501 | VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL);
Andrii Kuliand3134692017-06-26 14:57:02 -0700502 if (mVirtualDisplay == null) {
503 Log.e(TAG, "Failed to initialize ActivityView");
504 return;
505 }
506
Andrii Kulianf0379de2018-03-14 16:24:07 -0700507 final int displayId = mVirtualDisplay.getDisplay().getDisplayId();
508 final IWindowManager wm = WindowManagerGlobal.getWindowManagerService();
chaviwff2e7d82018-11-02 11:11:27 -0700509
510 mRootSurfaceControl = new SurfaceControl.Builder(surfaceSession)
Chavi Weingarten6ef9cc62019-02-07 16:28:45 +0000511 .setContainerLayer()
Robert Carr5fea55b2018-12-10 13:05:52 -0800512 .setParent(mSurfaceView.getSurfaceControl())
chaviwff2e7d82018-11-02 11:11:27 -0700513 .setName(DISPLAY_NAME)
514 .build();
515
Andrii Kulianf0379de2018-03-14 16:24:07 -0700516 try {
Issei Suzukia5dbf522019-02-01 17:58:15 +0100517 // TODO: Find a way to consolidate these calls to the server.
Tiger Huangd8ec9382019-04-18 14:35:09 -0700518 WindowManagerGlobal.getWindowSession().reparentDisplayContent(
519 getWindow(), mRootSurfaceControl, displayId);
Andrii Kulianf0379de2018-03-14 16:24:07 -0700520 wm.dontOverrideDisplayInfo(displayId);
Wale Ogunwale9e737db2018-12-17 15:42:37 -0800521 if (mSingleTaskInstance) {
522 mActivityTaskManager.setDisplayToSingleTaskInstance(displayId);
523 }
Issei Suzukia5dbf522019-02-01 17:58:15 +0100524 wm.setForwardedInsets(displayId, mForwardedInsets);
Andrii Kulianf0379de2018-03-14 16:24:07 -0700525 } catch (RemoteException e) {
526 e.rethrowAsRuntimeException();
527 }
chaviwff2e7d82018-11-02 11:11:27 -0700528
529 mTmpTransaction.show(mRootSurfaceControl).apply();
Brad Stenninga1dbe9c2018-05-02 08:29:28 -0700530 mTaskStackListener = new TaskStackListenerImpl();
Andrii Kuliancf8f6832018-01-23 19:43:30 -0800531 try {
Wale Ogunwale04d9cb52018-04-30 13:55:07 -0700532 mActivityTaskManager.registerTaskStackListener(mTaskStackListener);
Andrii Kuliancf8f6832018-01-23 19:43:30 -0800533 } catch (RemoteException e) {
534 Log.e(TAG, "Failed to register task stack listener", e);
535 }
Andrii Kuliand3134692017-06-26 14:57:02 -0700536 }
537
538 private void performRelease() {
539 if (!mOpened) {
540 return;
541 }
542
543 mSurfaceView.getHolder().removeCallback(mSurfaceCallback);
544
Andrii Kulian4b6599e2018-01-15 17:24:08 -0800545 cleanTapExcludeRegion();
Andrii Kuliand3134692017-06-26 14:57:02 -0700546
Andrii Kuliancf8f6832018-01-23 19:43:30 -0800547 if (mTaskStackListener != null) {
548 try {
Wale Ogunwale04d9cb52018-04-30 13:55:07 -0700549 mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener);
Andrii Kuliancf8f6832018-01-23 19:43:30 -0800550 } catch (RemoteException e) {
551 Log.e(TAG, "Failed to unregister task stack listener", e);
552 }
553 mTaskStackListener = null;
554 }
555
Andrii Kuliand3134692017-06-26 14:57:02 -0700556 final boolean displayReleased;
557 if (mVirtualDisplay != null) {
558 mVirtualDisplay.release();
559 mVirtualDisplay = null;
560 displayReleased = true;
561 } else {
562 displayReleased = false;
563 }
564
Andrii Kuliand3134692017-06-26 14:57:02 -0700565 if (displayReleased && mActivityViewCallback != null) {
566 mActivityViewCallback.onActivityViewDestroyed(this);
567 }
568
569 mGuard.close();
570 mOpened = false;
571 }
572
Andrii Kulian4b6599e2018-01-15 17:24:08 -0800573 /** Report to server that tap exclude region on hosting display should be cleared. */
574 private void cleanTapExcludeRegion() {
Tiger Huang2b210c22019-03-18 21:21:26 +0800575 if (!isAttachedToWindow() || mTapExcludeRegion.isEmpty()) {
Mark Renouf3856b4f2019-02-13 16:43:18 -0500576 return;
577 }
Tiger Huang2b210c22019-03-18 21:21:26 +0800578 // Update tap exclude region with a null region to clean the state on server.
Andrii Kulian4b6599e2018-01-15 17:24:08 -0800579 try {
580 WindowManagerGlobal.getWindowSession().updateTapExcludeRegion(getWindow(), hashCode(),
Tiger Huang2b210c22019-03-18 21:21:26 +0800581 null /* region */);
582 mTapExcludeRegion.setEmpty();
Andrii Kulian4b6599e2018-01-15 17:24:08 -0800583 } catch (RemoteException e) {
584 e.rethrowAsRuntimeException();
585 }
586 }
587
Andrii Kuliand3134692017-06-26 14:57:02 -0700588 /** Get density of the hosting display. */
589 private int getBaseDisplayDensity() {
590 final WindowManager wm = mContext.getSystemService(WindowManager.class);
591 final DisplayMetrics metrics = new DisplayMetrics();
592 wm.getDefaultDisplay().getMetrics(metrics);
593 return metrics.densityDpi;
594 }
595
596 @Override
597 protected void finalize() throws Throwable {
598 try {
599 if (mGuard != null) {
600 mGuard.warnIfOpen();
601 performRelease();
602 }
603 } finally {
604 super.finalize();
605 }
606 }
Andrii Kuliancf8f6832018-01-23 19:43:30 -0800607
608 /**
Issei Suzukia5dbf522019-02-01 17:58:15 +0100609 * Set forwarded insets on the virtual display.
610 *
611 * @see IWindowManager#setForwardedInsets
612 */
613 public void setForwardedInsets(Insets insets) {
614 mForwardedInsets = insets;
615 if (mVirtualDisplay == null) {
616 return;
617 }
618 try {
619 final IWindowManager wm = WindowManagerGlobal.getWindowManagerService();
620 wm.setForwardedInsets(mVirtualDisplay.getDisplay().getDisplayId(), mForwardedInsets);
621 } catch (RemoteException e) {
622 e.rethrowAsRuntimeException();
623 }
624 }
625
626 /**
Andrii Kuliancf8f6832018-01-23 19:43:30 -0800627 * A task change listener that detects background color change of the topmost stack on our
628 * virtual display and updates the background of the surface view. This background will be shown
629 * when surface view is resized, but the app hasn't drawn its content in new size yet.
Brad Stenninga1dbe9c2018-05-02 08:29:28 -0700630 * It also calls StateCallback.onTaskMovedToFront to notify interested parties that the stack
631 * associated with the {@link ActivityView} has had a Task moved to the front. This is useful
632 * when needing to also bring the host Activity to the foreground at the same time.
Andrii Kuliancf8f6832018-01-23 19:43:30 -0800633 */
Brad Stenninga1dbe9c2018-05-02 08:29:28 -0700634 private class TaskStackListenerImpl extends TaskStackListener {
Andrii Kuliancf8f6832018-01-23 19:43:30 -0800635
636 @Override
Mark Renoufc808f062019-02-07 15:20:37 -0500637 public void onTaskDescriptionChanged(ActivityManager.RunningTaskInfo taskInfo)
Andrii Kuliancf8f6832018-01-23 19:43:30 -0800638 throws RemoteException {
Mark Renoufc808f062019-02-07 15:20:37 -0500639 if (mVirtualDisplay == null
640 || taskInfo.displayId != mVirtualDisplay.getDisplay().getDisplayId()) {
Andrii Kuliancf8f6832018-01-23 19:43:30 -0800641 return;
642 }
643
Brad Stenninga1dbe9c2018-05-02 08:29:28 -0700644 StackInfo stackInfo = getTopMostStackInfo();
645 if (stackInfo == null) {
646 return;
647 }
648 // Found the topmost stack on target display. Now check if the topmost task's
649 // description changed.
Mark Renoufc808f062019-02-07 15:20:37 -0500650 if (taskInfo.taskId == stackInfo.taskIds[stackInfo.taskIds.length - 1]) {
651 mSurfaceView.setResizeBackgroundColor(
652 taskInfo.taskDescription.getBackgroundColor());
Brad Stenninga1dbe9c2018-05-02 08:29:28 -0700653 }
654 }
655
656 @Override
Mark Renoufc808f062019-02-07 15:20:37 -0500657 public void onTaskMovedToFront(ActivityManager.RunningTaskInfo taskInfo)
658 throws RemoteException {
659 if (mActivityViewCallback == null || mVirtualDisplay == null
660 || taskInfo.displayId != mVirtualDisplay.getDisplay().getDisplayId()) {
Mark Renouf7b71a5d2019-01-23 17:12:34 -0500661 return;
662 }
663
664 StackInfo stackInfo = getTopMostStackInfo();
665 // if StackInfo was null or unrelated to the "move to front" then there's no use
666 // notifying the callback
667 if (stackInfo != null
Mark Renoufc808f062019-02-07 15:20:37 -0500668 && taskInfo.taskId == stackInfo.taskIds[stackInfo.taskIds.length - 1]) {
Wale Ogunwale691af682019-02-11 03:09:10 -0800669 mActivityViewCallback.onTaskMovedToFront(taskInfo.taskId);
Mark Renouf7b71a5d2019-01-23 17:12:34 -0500670 }
671 }
672
673 @Override
674 public void onTaskCreated(int taskId, ComponentName componentName) throws RemoteException {
Mark Renoufc808f062019-02-07 15:20:37 -0500675 if (mActivityViewCallback == null || mVirtualDisplay == null) {
Mark Renouf7b71a5d2019-01-23 17:12:34 -0500676 return;
677 }
678
679 StackInfo stackInfo = getTopMostStackInfo();
680 // if StackInfo was null or unrelated to the task creation then there's no use
681 // notifying the callback
682 if (stackInfo != null
683 && taskId == stackInfo.taskIds[stackInfo.taskIds.length - 1]) {
684 mActivityViewCallback.onTaskCreated(taskId, componentName);
685 }
686 }
687
688 @Override
Mark Renoufc808f062019-02-07 15:20:37 -0500689 public void onTaskRemovalStarted(ActivityManager.RunningTaskInfo taskInfo)
690 throws RemoteException {
691 if (mActivityViewCallback == null || mVirtualDisplay == null
692 || taskInfo.displayId != mVirtualDisplay.getDisplay().getDisplayId()) {
Mark Renouf7b71a5d2019-01-23 17:12:34 -0500693 return;
694 }
Mark Renoufc808f062019-02-07 15:20:37 -0500695 mActivityViewCallback.onTaskRemovalStarted(taskInfo.taskId);
Brad Stenninga1dbe9c2018-05-02 08:29:28 -0700696 }
697
698 private StackInfo getTopMostStackInfo() throws RemoteException {
Andrii Kuliancf8f6832018-01-23 19:43:30 -0800699 // Find the topmost task on our virtual display - it will define the background
700 // color of the surface view during resizing.
701 final int displayId = mVirtualDisplay.getDisplay().getDisplayId();
Wale Ogunwale04d9cb52018-04-30 13:55:07 -0700702 final List<StackInfo> stackInfoList = mActivityTaskManager.getAllStackInfos();
Andrii Kuliancf8f6832018-01-23 19:43:30 -0800703
704 // Iterate through stacks from top to bottom.
705 final int stackCount = stackInfoList.size();
706 for (int i = 0; i < stackCount; i++) {
707 final StackInfo stackInfo = stackInfoList.get(i);
708 // Only look for stacks on our virtual display.
709 if (stackInfo.displayId != displayId) {
710 continue;
711 }
Brad Stenninga1dbe9c2018-05-02 08:29:28 -0700712 // Found the topmost stack on target display.
713 return stackInfo;
Andrii Kuliancf8f6832018-01-23 19:43:30 -0800714 }
Brad Stenninga1dbe9c2018-05-02 08:29:28 -0700715 return null;
Andrii Kuliancf8f6832018-01-23 19:43:30 -0800716 }
717 }
Andrii Kuliand3134692017-06-26 14:57:02 -0700718}