blob: b56c00e44d3f2c9401b92dd34053df1515290ad9 [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;
Mark Renoufe574ad02019-08-12 16:22:15 -040032import android.graphics.Rect;
Tiger Huang2b210c22019-03-18 21:21:26 +080033import android.graphics.Region;
Andrii Kuliand3134692017-06-26 14:57:02 -070034import android.hardware.display.DisplayManager;
35import android.hardware.display.VirtualDisplay;
Mark Renouf041d7262019-02-06 12:09:41 -050036import android.hardware.input.InputManager;
Andrii Kuliand3134692017-06-26 14:57:02 -070037import android.os.RemoteException;
Mark Renouf041d7262019-02-06 12:09:41 -050038import android.os.SystemClock;
Brad Stenninga1dbe9c2018-05-02 08:29:28 -070039import android.os.UserHandle;
Andrii Kuliand3134692017-06-26 14:57:02 -070040import android.util.AttributeSet;
41import android.util.DisplayMetrics;
42import android.util.Log;
Andrii Kulianf0379de2018-03-14 16:24:07 -070043import android.view.IWindowManager;
Mark Renouf041d7262019-02-06 12:09:41 -050044import android.view.InputDevice;
45import android.view.KeyCharacterMap;
46import android.view.KeyEvent;
chaviwff2e7d82018-11-02 11:11:27 -070047import android.view.SurfaceControl;
Andrii Kuliand3134692017-06-26 14:57:02 -070048import android.view.SurfaceHolder;
chaviwff2e7d82018-11-02 11:11:27 -070049import android.view.SurfaceSession;
Andrii Kuliand3134692017-06-26 14:57:02 -070050import android.view.SurfaceView;
chaviwd44449d2019-01-02 16:33:12 -080051import android.view.View;
Andrii Kuliand3134692017-06-26 14:57:02 -070052import android.view.ViewGroup;
Tiger Huang2b210c22019-03-18 21:21:26 +080053import android.view.ViewParent;
Andrii Kuliand3134692017-06-26 14:57:02 -070054import android.view.WindowManager;
Andrii Kulian4b6599e2018-01-15 17:24:08 -080055import android.view.WindowManagerGlobal;
Yohei Yukawab4f328a2019-05-02 08:41:27 -070056import android.view.inputmethod.InputMethodManager;
Andrii Kuliand3134692017-06-26 14:57:02 -070057
58import dalvik.system.CloseGuard;
59
Andrii Kuliancf8f6832018-01-23 19:43:30 -080060import java.util.List;
61
Andrii Kuliand3134692017-06-26 14:57:02 -070062/**
Tiger Huang04dc4cc2019-01-17 18:41:41 +080063 * Activity container that allows launching activities into itself.
Andrii Kuliand3134692017-06-26 14:57:02 -070064 * <p>Activity launching into this container is restricted by the same rules that apply to launching
65 * on VirtualDisplays.
66 * @hide
67 */
Wale Ogunwale691af682019-02-11 03:09:10 -080068@TestApi
Andrii Kuliand3134692017-06-26 14:57:02 -070069public class ActivityView extends ViewGroup {
70
71 private static final String DISPLAY_NAME = "ActivityViewVirtualDisplay";
72 private static final String TAG = "ActivityView";
73
74 private VirtualDisplay mVirtualDisplay;
75 private final SurfaceView mSurfaceView;
chaviwff2e7d82018-11-02 11:11:27 -070076
77 /**
78 * This is the root surface for the VirtualDisplay. The VirtualDisplay child surfaces will be
79 * re-parented to this surface. This will also be a child of the SurfaceView's SurfaceControl.
80 */
81 private SurfaceControl mRootSurfaceControl;
Andrii Kuliand3134692017-06-26 14:57:02 -070082
83 private final SurfaceCallback mSurfaceCallback;
84 private StateCallback mActivityViewCallback;
85
Wale Ogunwale04d9cb52018-04-30 13:55:07 -070086 private IActivityTaskManager mActivityTaskManager;
Tiger Huang04dc4cc2019-01-17 18:41:41 +080087 // Temp container to store view coordinates in window.
88 private final int[] mLocationInWindow = new int[2];
Andrii Kuliand3134692017-06-26 14:57:02 -070089
Tiger Huang2b210c22019-03-18 21:21:26 +080090 // The latest tap exclude region that we've sent to WM.
91 private final Region mTapExcludeRegion = new Region();
92
Andrii Kuliancf8f6832018-01-23 19:43:30 -080093 private TaskStackListener mTaskStackListener;
94
Andrii Kuliand3134692017-06-26 14:57:02 -070095 private final CloseGuard mGuard = CloseGuard.get();
96 private boolean mOpened; // Protected by mGuard.
97
chaviwff2e7d82018-11-02 11:11:27 -070098 private final SurfaceControl.Transaction mTmpTransaction = new SurfaceControl.Transaction();
chaviwff2e7d82018-11-02 11:11:27 -070099
Wale Ogunwale9e737db2018-12-17 15:42:37 -0800100 /** The ActivityView is only allowed to contain one task. */
101 private final boolean mSingleTaskInstance;
102
Issei Suzukia5dbf522019-02-01 17:58:15 +0100103 private Insets mForwardedInsets;
104
Mark Renoufe574ad02019-08-12 16:22:15 -0400105 private float mCornerRadius;
106
Andrii Kuliand3134692017-06-26 14:57:02 -0700107 public ActivityView(Context context) {
108 this(context, null /* attrs */);
109 }
110
111 public ActivityView(Context context, AttributeSet attrs) {
112 this(context, attrs, 0 /* defStyle */);
113 }
114
115 public ActivityView(Context context, AttributeSet attrs, int defStyle) {
Wale Ogunwale9e737db2018-12-17 15:42:37 -0800116 this(context, attrs, defStyle, false /*singleTaskInstance*/);
117 }
118
119 public ActivityView(
120 Context context, AttributeSet attrs, int defStyle, boolean singleTaskInstance) {
Andrii Kuliand3134692017-06-26 14:57:02 -0700121 super(context, attrs, defStyle);
Wale Ogunwale9e737db2018-12-17 15:42:37 -0800122 mSingleTaskInstance = singleTaskInstance;
Andrii Kuliand3134692017-06-26 14:57:02 -0700123
Wale Ogunwale04d9cb52018-04-30 13:55:07 -0700124 mActivityTaskManager = ActivityTaskManager.getService();
Andrii Kuliand3134692017-06-26 14:57:02 -0700125 mSurfaceView = new SurfaceView(context);
lumark3f51e3c2019-07-23 21:18:17 +0800126 // Since ActivityView#getAlpha has been overridden, we should use parent class's alpha
127 // as master to synchronize surface view's alpha value.
128 mSurfaceView.setAlpha(super.getAlpha());
Issei Suzuki006b71f2019-06-17 15:56:57 +0200129 mSurfaceView.setUseAlpha();
Andrii Kuliand3134692017-06-26 14:57:02 -0700130 mSurfaceCallback = new SurfaceCallback();
131 mSurfaceView.getHolder().addCallback(mSurfaceCallback);
132 addView(mSurfaceView);
133
134 mOpened = true;
135 mGuard.open("release");
136 }
137
138 /** Callback that notifies when the container is ready or destroyed. */
139 public abstract static class StateCallback {
Mark Renouf7b71a5d2019-01-23 17:12:34 -0500140
Andrii Kuliand3134692017-06-26 14:57:02 -0700141 /**
142 * Called when the container is ready for launching activities. Calling
143 * {@link #startActivity(Intent)} prior to this callback will result in an
144 * {@link IllegalStateException}.
145 *
146 * @see #startActivity(Intent)
147 */
148 public abstract void onActivityViewReady(ActivityView view);
Mark Renouf7b71a5d2019-01-23 17:12:34 -0500149
Andrii Kuliand3134692017-06-26 14:57:02 -0700150 /**
151 * Called when the container can no longer launch activities. Calling
152 * {@link #startActivity(Intent)} after this callback will result in an
153 * {@link IllegalStateException}.
154 *
155 * @see #startActivity(Intent)
156 */
157 public abstract void onActivityViewDestroyed(ActivityView view);
Mark Renouf7b71a5d2019-01-23 17:12:34 -0500158
159 /**
160 * Called when a task is created inside the container.
161 * This is a filtered version of {@link TaskStackListener}
162 */
163 public void onTaskCreated(int taskId, ComponentName componentName) { }
164
Brad Stenninga1dbe9c2018-05-02 08:29:28 -0700165 /**
166 * Called when a task is moved to the front of the stack inside the container.
167 * This is a filtered version of {@link TaskStackListener}
168 */
Wale Ogunwale691af682019-02-11 03:09:10 -0800169 public void onTaskMovedToFront(int taskId) { }
Mark Renouf7b71a5d2019-01-23 17:12:34 -0500170
171 /**
172 * Called when a task is about to be removed from the stack inside the container.
173 * This is a filtered version of {@link TaskStackListener}
174 */
175 public void onTaskRemovalStarted(int taskId) { }
Andrii Kuliand3134692017-06-26 14:57:02 -0700176 }
177
178 /**
179 * Set the callback to be notified about state changes.
180 * <p>This class must finish initializing before {@link #startActivity(Intent)} can be called.
181 * <p>Note: If the instance was ready prior to this call being made, then
182 * {@link StateCallback#onActivityViewReady(ActivityView)} will be called from within
183 * this method call.
184 *
185 * @param callback The callback to report events to.
186 *
187 * @see StateCallback
188 * @see #startActivity(Intent)
189 */
190 public void setCallback(StateCallback callback) {
191 mActivityViewCallback = callback;
192
193 if (mVirtualDisplay != null && mActivityViewCallback != null) {
194 mActivityViewCallback.onActivityViewReady(this);
195 }
196 }
197
198 /**
Mark Renouf34d04f32019-05-13 15:53:18 -0400199 * Sets the corner radius for the Activity displayed here. The corners will be
200 * cropped from the window painted by the contained Activity.
201 *
202 * @param cornerRadius the radius for the corners, in pixels
203 * @hide
204 */
205 public void setCornerRadius(float cornerRadius) {
206 mSurfaceView.setCornerRadius(cornerRadius);
207 }
208
209 /**
Mark Renoufe574ad02019-08-12 16:22:15 -0400210 * @hide
211 */
212 public float getCornerRadius() {
213 return mSurfaceView.getCornerRadius();
214 }
215
216 /**
217 * Control whether the surface is clipped to the same bounds as the View. If true, then
218 * the bounds set by {@link #setSurfaceClipBounds(Rect)} are applied to the surface as
219 * window-crop.
220 *
221 * @param clippingEnabled whether to enable surface clipping
222 * @hide
223 */
224 public void setSurfaceClippingEnabled(boolean clippingEnabled) {
225 mSurfaceView.setEnableSurfaceClipping(clippingEnabled);
226 }
227
228 /**
229 * Sets an area on the contained surface to which it will be clipped
230 * when it is drawn. Setting the value to null will remove the clip bounds
231 * and the surface will draw normally, using its full bounds.
232 *
233 * @param clipBounds The rectangular area, in the local coordinates of
234 * this view, to which future drawing operations will be clipped.
235 * @hide
236 */
237 public void setSurfaceClipBounds(Rect clipBounds) {
238 mSurfaceView.setClipBounds(clipBounds);
239 }
240
241 /**
242 * @hide
243 */
244 public boolean getSurfaceClipBounds(Rect outRect) {
245 return mSurfaceView.getClipBounds(outRect);
246 }
247
248 /**
Andrii Kuliand3134692017-06-26 14:57:02 -0700249 * Launch a new activity into this container.
250 * <p>Activity resolved by the provided {@link Intent} 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 intent Intent used to launch an activity.
259 *
260 * @see StateCallback
261 * @see #startActivity(PendingIntent)
262 */
263 public void startActivity(@NonNull Intent intent) {
264 final ActivityOptions options = prepareActivityOptions();
265 getContext().startActivity(intent, options.toBundle());
266 }
267
268 /**
269 * Launch a new activity into this container.
Brad Stenninga1dbe9c2018-05-02 08:29:28 -0700270 * <p>Activity resolved by the provided {@link Intent} must have
271 * {@link android.R.attr#resizeableActivity} attribute set to {@code true} in order to be
272 * launched here. Also, if activity is not owned by the owner of this container, it must allow
273 * embedding and the caller must have permission to embed.
274 * <p>Note: This class must finish initializing and
275 * {@link StateCallback#onActivityViewReady(ActivityView)} callback must be triggered before
276 * this method can be called.
277 *
278 * @param intent Intent used to launch an activity.
279 * @param user The UserHandle of the user to start this activity for.
280 *
281 *
282 * @see StateCallback
283 * @see #startActivity(PendingIntent)
284 */
285 public void startActivity(@NonNull Intent intent, UserHandle user) {
286 final ActivityOptions options = prepareActivityOptions();
287 getContext().startActivityAsUser(intent, options.toBundle(), user);
288 }
289
290 /**
291 * Launch a new activity into this container.
Andrii Kuliand3134692017-06-26 14:57:02 -0700292 * <p>Activity resolved by the provided {@link PendingIntent} must have
293 * {@link android.R.attr#resizeableActivity} attribute set to {@code true} in order to be
294 * launched here. Also, if activity is not owned by the owner of this container, it must allow
295 * embedding and the caller must have permission to embed.
296 * <p>Note: This class must finish initializing and
297 * {@link StateCallback#onActivityViewReady(ActivityView)} callback must be triggered before
298 * this method can be called.
299 *
300 * @param pendingIntent Intent used to launch an activity.
301 *
302 * @see StateCallback
303 * @see #startActivity(Intent)
304 */
305 public void startActivity(@NonNull PendingIntent pendingIntent) {
306 final ActivityOptions options = prepareActivityOptions();
307 try {
308 pendingIntent.send(null /* context */, 0 /* code */, null /* intent */,
309 null /* onFinished */, null /* handler */, null /* requiredPermission */,
310 options.toBundle());
311 } catch (PendingIntent.CanceledException e) {
312 throw new RuntimeException(e);
313 }
314 }
315
316 /**
Mady Mellor60101c92019-04-11 19:04:00 -0700317 * Launch a new activity into this container.
318 * <p>Activity resolved by the provided {@link PendingIntent} must have
319 * {@link android.R.attr#resizeableActivity} attribute set to {@code true} in order to be
320 * launched here. Also, if activity is not owned by the owner of this container, it must allow
321 * embedding and the caller must have permission to embed.
322 * <p>Note: This class must finish initializing and
323 * {@link StateCallback#onActivityViewReady(ActivityView)} callback must be triggered before
324 * this method can be called.
325 *
326 * @param pendingIntent Intent used to launch an activity.
327 * @param options options for the activity
328 *
329 * @see StateCallback
330 * @see #startActivity(Intent)
331 */
332 public void startActivity(@NonNull PendingIntent pendingIntent,
333 @NonNull ActivityOptions options) {
334 options.setLaunchDisplayId(mVirtualDisplay.getDisplay().getDisplayId());
335 try {
336 pendingIntent.send(null /* context */, 0 /* code */, null /* intent */,
337 null /* onFinished */, null /* handler */, null /* requiredPermission */,
338 options.toBundle());
339 } catch (PendingIntent.CanceledException e) {
340 throw new RuntimeException(e);
341 }
342 }
343
344 /**
Andrii Kuliand3134692017-06-26 14:57:02 -0700345 * Check if container is ready to launch and create {@link ActivityOptions} to target the
346 * virtual display.
347 */
348 private ActivityOptions prepareActivityOptions() {
349 if (mVirtualDisplay == null) {
350 throw new IllegalStateException(
351 "Trying to start activity before ActivityView is ready.");
352 }
353 final ActivityOptions options = ActivityOptions.makeBasic();
354 options.setLaunchDisplayId(mVirtualDisplay.getDisplay().getDisplayId());
355 return options;
356 }
357
358 /**
359 * Release this container. Activity launching will no longer be permitted.
360 * <p>Note: Calling this method is allowed after
361 * {@link StateCallback#onActivityViewReady(ActivityView)} callback was triggered and before
362 * {@link StateCallback#onActivityViewDestroyed(ActivityView)}.
363 *
364 * @see StateCallback
365 */
366 public void release() {
367 if (mVirtualDisplay == null) {
368 throw new IllegalStateException(
369 "Trying to release container that is not initialized.");
370 }
371 performRelease();
372 }
373
Andrii Kulian4b6599e2018-01-15 17:24:08 -0800374 /**
Tiger Huang2b210c22019-03-18 21:21:26 +0800375 * Triggers an update of {@link ActivityView}'s location in window to properly set tap exclude
Andrii Kulian4b6599e2018-01-15 17:24:08 -0800376 * regions and avoid focus switches by touches on this view.
377 */
378 public void onLocationChanged() {
Tiger Huangd8ec9382019-04-18 14:35:09 -0700379 updateLocationAndTapExcludeRegion();
Andrii Kulian4b6599e2018-01-15 17:24:08 -0800380 }
381
Yohei Yukawab4f328a2019-05-02 08:41:27 -0700382 private void clearActivityViewGeometryForIme() {
383 if (mVirtualDisplay == null) {
384 return;
385 }
386 final int displayId = mVirtualDisplay.getDisplay().getDisplayId();
387 mContext.getSystemService(InputMethodManager.class).reportActivityView(displayId, null);
388 }
389
Andrii Kuliand3134692017-06-26 14:57:02 -0700390 @Override
391 public void onLayout(boolean changed, int l, int t, int r, int b) {
392 mSurfaceView.layout(0 /* left */, 0 /* top */, r - l /* right */, b - t /* bottom */);
393 }
394
lumark3f51e3c2019-07-23 21:18:17 +0800395 /**
396 * Sets the alpha value when the content of {@link SurfaceView} needs to show or hide.
397 * <p>Note: The surface view may ignore the alpha value in some cases. Refer to
398 * {@link SurfaceView#setAlpha} for more details.
399 *
400 * @param alpha The opacity of the view.
401 */
Tiger Huang2b210c22019-03-18 21:21:26 +0800402 @Override
Issei Suzukicac2a502019-04-16 16:52:50 +0200403 public void setAlpha(float alpha) {
lumark3f51e3c2019-07-23 21:18:17 +0800404 super.setAlpha(alpha);
405
406 if (mSurfaceView != null) {
407 mSurfaceView.setAlpha(alpha);
408 }
Issei Suzukicac2a502019-04-16 16:52:50 +0200409 }
410
411 @Override
412 public float getAlpha() {
413 return mSurfaceView.getAlpha();
414 }
415
416 @Override
Tiger Huang2b210c22019-03-18 21:21:26 +0800417 public boolean gatherTransparentRegion(Region region) {
418 // The tap exclude region may be affected by any view on top of it, so we detect the
419 // possible change by monitoring this function.
Tiger Huangd8ec9382019-04-18 14:35:09 -0700420 updateLocationAndTapExcludeRegion();
Tiger Huang2b210c22019-03-18 21:21:26 +0800421 return super.gatherTransparentRegion(region);
422 }
423
Tiger Huangd8ec9382019-04-18 14:35:09 -0700424 /**
425 * Sends current location in window and tap exclude region to WM for this view.
426 */
427 private void updateLocationAndTapExcludeRegion() {
428 if (mVirtualDisplay == null || !isAttachedToWindow()) {
Mark Renouf3856b4f2019-02-13 16:43:18 -0500429 return;
430 }
Tiger Huangd8ec9382019-04-18 14:35:09 -0700431 try {
432 int x = mLocationInWindow[0];
433 int y = mLocationInWindow[1];
434 getLocationInWindow(mLocationInWindow);
435 if (x != mLocationInWindow[0] || y != mLocationInWindow[1]) {
436 x = mLocationInWindow[0];
437 y = mLocationInWindow[1];
Yohei Yukawab4f328a2019-05-02 08:41:27 -0700438 final int displayId = mVirtualDisplay.getDisplay().getDisplayId();
Tiger Huangd8ec9382019-04-18 14:35:09 -0700439 WindowManagerGlobal.getWindowSession().updateDisplayContentLocation(
Yohei Yukawab4f328a2019-05-02 08:41:27 -0700440 getWindow(), x, y, displayId);
441
442 // Also report this geometry information to InputMethodManagerService.
443 // TODO(b/115693908): Unify this logic into the above WMS-based one.
lumark44fe4e42019-07-23 19:29:03 +0800444 // TODO(b/138175283): Address the location update when the host of this view is
445 // moving.
Yohei Yukawab4f328a2019-05-02 08:41:27 -0700446 final Matrix matrix = new Matrix();
lumark44fe4e42019-07-23 19:29:03 +0800447 final int[] locationOnScreen = new int[2];
448 getLocationOnScreen(locationOnScreen);
449 final int dx = locationOnScreen[0];
450 final int dy = locationOnScreen[1];
Yohei Yukawab4f328a2019-05-02 08:41:27 -0700451 matrix.set(getMatrix());
lumark44fe4e42019-07-23 19:29:03 +0800452 matrix.postTranslate(dx, dy);
Yohei Yukawab4f328a2019-05-02 08:41:27 -0700453 mContext.getSystemService(InputMethodManager.class)
454 .reportActivityView(displayId, matrix);
Tiger Huangd8ec9382019-04-18 14:35:09 -0700455 }
456 updateTapExcludeRegion(x, y);
457 } catch (RemoteException e) {
458 e.rethrowAsRuntimeException();
459 }
460 }
461
462 /** Computes and sends current tap exclude region to WM for this view. */
463 private void updateTapExcludeRegion(int x, int y) throws RemoteException {
Tiger Huang2b210c22019-03-18 21:21:26 +0800464 if (!canReceivePointerEvents()) {
465 cleanTapExcludeRegion();
466 return;
467 }
Tiger Huangd8ec9382019-04-18 14:35:09 -0700468 mTapExcludeRegion.set(x, y, x + getWidth(), y + getHeight());
Tiger Huang2b210c22019-03-18 21:21:26 +0800469
Tiger Huangd8ec9382019-04-18 14:35:09 -0700470 // There might be views on top of us. We need to subtract those areas from the tap
471 // exclude region.
472 final ViewParent parent = getParent();
473 if (parent != null) {
474 parent.subtractObscuredTouchableRegion(mTapExcludeRegion, this);
Andrii Kulian4b6599e2018-01-15 17:24:08 -0800475 }
Tiger Huangd8ec9382019-04-18 14:35:09 -0700476
477 WindowManagerGlobal.getWindowSession().updateTapExcludeRegion(getWindow(), hashCode(),
478 mTapExcludeRegion);
Andrii Kulian4b6599e2018-01-15 17:24:08 -0800479 }
480
Andrii Kuliand3134692017-06-26 14:57:02 -0700481 private class SurfaceCallback implements SurfaceHolder.Callback {
482 @Override
483 public void surfaceCreated(SurfaceHolder surfaceHolder) {
484 if (mVirtualDisplay == null) {
Robert Carr5fea55b2018-12-10 13:05:52 -0800485 initVirtualDisplay(new SurfaceSession());
Andrii Kuliand3134692017-06-26 14:57:02 -0700486 if (mVirtualDisplay != null && mActivityViewCallback != null) {
487 mActivityViewCallback.onActivityViewReady(ActivityView.this);
488 }
489 } else {
chaviwff2e7d82018-11-02 11:11:27 -0700490 mTmpTransaction.reparent(mRootSurfaceControl,
Robert Carr10584fa2019-01-14 15:55:19 -0800491 mSurfaceView.getSurfaceControl()).apply();
Andrii Kuliand3134692017-06-26 14:57:02 -0700492 }
chaviwda4c6942018-11-07 15:52:56 -0800493
494 if (mVirtualDisplay != null) {
495 mVirtualDisplay.setDisplayState(true);
496 }
497
Tiger Huangd8ec9382019-04-18 14:35:09 -0700498 updateLocationAndTapExcludeRegion();
Andrii Kuliand3134692017-06-26 14:57:02 -0700499 }
500
501 @Override
502 public void surfaceChanged(SurfaceHolder surfaceHolder, int format, int width, int height) {
503 if (mVirtualDisplay != null) {
504 mVirtualDisplay.resize(width, height, getBaseDisplayDensity());
505 }
Tiger Huangd8ec9382019-04-18 14:35:09 -0700506 updateLocationAndTapExcludeRegion();
Andrii Kuliand3134692017-06-26 14:57:02 -0700507 }
508
509 @Override
510 public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
Andrii Kuliand3134692017-06-26 14:57:02 -0700511 if (mVirtualDisplay != null) {
chaviwda4c6942018-11-07 15:52:56 -0800512 mVirtualDisplay.setDisplayState(false);
Andrii Kuliand3134692017-06-26 14:57:02 -0700513 }
Yohei Yukawab4f328a2019-05-02 08:41:27 -0700514 clearActivityViewGeometryForIme();
Andrii Kulian4b6599e2018-01-15 17:24:08 -0800515 cleanTapExcludeRegion();
Andrii Kuliand3134692017-06-26 14:57:02 -0700516 }
517 }
518
chaviwd44449d2019-01-02 16:33:12 -0800519 @Override
520 protected void onVisibilityChanged(View changedView, int visibility) {
521 super.onVisibilityChanged(changedView, visibility);
522 mSurfaceView.setVisibility(visibility);
523 }
524
Mark Renouf041d7262019-02-06 12:09:41 -0500525 /**
Mady Mellor390bff42019-04-05 15:09:01 -0700526 * @return the display id of the virtual display.
527 */
528 public int getVirtualDisplayId() {
529 if (mVirtualDisplay != null) {
530 return mVirtualDisplay.getDisplay().getDisplayId();
531 }
532 return INVALID_DISPLAY;
533 }
534
535 /**
Mark Renouf041d7262019-02-06 12:09:41 -0500536 * Injects a pair of down/up key events with keycode {@link KeyEvent#KEYCODE_BACK} to the
537 * virtual display.
538 */
539 public void performBackPress() {
540 if (mVirtualDisplay == null) {
541 return;
542 }
543 final int displayId = mVirtualDisplay.getDisplay().getDisplayId();
544 final InputManager im = InputManager.getInstance();
545 im.injectInputEvent(createKeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK, displayId),
546 InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
547 im.injectInputEvent(createKeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK, displayId),
548 InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
549 }
550
551 private static KeyEvent createKeyEvent(int action, int code, int displayId) {
552 long when = SystemClock.uptimeMillis();
553 final KeyEvent ev = new KeyEvent(when, when, action, code, 0 /* repeat */,
554 0 /* metaState */, KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /* scancode */,
555 KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY,
556 InputDevice.SOURCE_KEYBOARD);
557 ev.setDisplayId(displayId);
558 return ev;
559 }
560
chaviwff2e7d82018-11-02 11:11:27 -0700561 private void initVirtualDisplay(SurfaceSession surfaceSession) {
Andrii Kuliand3134692017-06-26 14:57:02 -0700562 if (mVirtualDisplay != null) {
563 throw new IllegalStateException("Trying to initialize for the second time.");
564 }
565
566 final int width = mSurfaceView.getWidth();
567 final int height = mSurfaceView.getHeight();
568 final DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
chaviwff2e7d82018-11-02 11:11:27 -0700569
Andrii Kuliand3134692017-06-26 14:57:02 -0700570 mVirtualDisplay = displayManager.createVirtualDisplay(
chaviwda4c6942018-11-07 15:52:56 -0800571 DISPLAY_NAME + "@" + System.identityHashCode(this), width, height,
572 getBaseDisplayDensity(), null,
573 VIRTUAL_DISPLAY_FLAG_PUBLIC | VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY
574 | VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL);
Andrii Kuliand3134692017-06-26 14:57:02 -0700575 if (mVirtualDisplay == null) {
576 Log.e(TAG, "Failed to initialize ActivityView");
577 return;
578 }
579
Andrii Kulianf0379de2018-03-14 16:24:07 -0700580 final int displayId = mVirtualDisplay.getDisplay().getDisplayId();
581 final IWindowManager wm = WindowManagerGlobal.getWindowManagerService();
chaviwff2e7d82018-11-02 11:11:27 -0700582
583 mRootSurfaceControl = new SurfaceControl.Builder(surfaceSession)
Chavi Weingarten6ef9cc62019-02-07 16:28:45 +0000584 .setContainerLayer()
Robert Carr5fea55b2018-12-10 13:05:52 -0800585 .setParent(mSurfaceView.getSurfaceControl())
chaviwff2e7d82018-11-02 11:11:27 -0700586 .setName(DISPLAY_NAME)
587 .build();
588
Andrii Kulianf0379de2018-03-14 16:24:07 -0700589 try {
Issei Suzukia5dbf522019-02-01 17:58:15 +0100590 // TODO: Find a way to consolidate these calls to the server.
Tiger Huangd8ec9382019-04-18 14:35:09 -0700591 WindowManagerGlobal.getWindowSession().reparentDisplayContent(
592 getWindow(), mRootSurfaceControl, displayId);
Andrii Kulianf0379de2018-03-14 16:24:07 -0700593 wm.dontOverrideDisplayInfo(displayId);
Wale Ogunwale9e737db2018-12-17 15:42:37 -0800594 if (mSingleTaskInstance) {
595 mActivityTaskManager.setDisplayToSingleTaskInstance(displayId);
596 }
Issei Suzukia5dbf522019-02-01 17:58:15 +0100597 wm.setForwardedInsets(displayId, mForwardedInsets);
Andrii Kulianf0379de2018-03-14 16:24:07 -0700598 } catch (RemoteException e) {
599 e.rethrowAsRuntimeException();
600 }
chaviwff2e7d82018-11-02 11:11:27 -0700601
602 mTmpTransaction.show(mRootSurfaceControl).apply();
Brad Stenninga1dbe9c2018-05-02 08:29:28 -0700603 mTaskStackListener = new TaskStackListenerImpl();
Andrii Kuliancf8f6832018-01-23 19:43:30 -0800604 try {
Wale Ogunwale04d9cb52018-04-30 13:55:07 -0700605 mActivityTaskManager.registerTaskStackListener(mTaskStackListener);
Andrii Kuliancf8f6832018-01-23 19:43:30 -0800606 } catch (RemoteException e) {
607 Log.e(TAG, "Failed to register task stack listener", e);
608 }
Andrii Kuliand3134692017-06-26 14:57:02 -0700609 }
610
611 private void performRelease() {
612 if (!mOpened) {
613 return;
614 }
615
616 mSurfaceView.getHolder().removeCallback(mSurfaceCallback);
617
Andrii Kulian4b6599e2018-01-15 17:24:08 -0800618 cleanTapExcludeRegion();
Andrii Kuliand3134692017-06-26 14:57:02 -0700619
Andrii Kuliancf8f6832018-01-23 19:43:30 -0800620 if (mTaskStackListener != null) {
621 try {
Wale Ogunwale04d9cb52018-04-30 13:55:07 -0700622 mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener);
Andrii Kuliancf8f6832018-01-23 19:43:30 -0800623 } catch (RemoteException e) {
624 Log.e(TAG, "Failed to unregister task stack listener", e);
625 }
626 mTaskStackListener = null;
627 }
628
Andrii Kuliand3134692017-06-26 14:57:02 -0700629 final boolean displayReleased;
630 if (mVirtualDisplay != null) {
631 mVirtualDisplay.release();
632 mVirtualDisplay = null;
633 displayReleased = true;
634 } else {
635 displayReleased = false;
636 }
637
Andrii Kuliand3134692017-06-26 14:57:02 -0700638 if (displayReleased && mActivityViewCallback != null) {
639 mActivityViewCallback.onActivityViewDestroyed(this);
640 }
641
642 mGuard.close();
643 mOpened = false;
644 }
645
Andrii Kulian4b6599e2018-01-15 17:24:08 -0800646 /** Report to server that tap exclude region on hosting display should be cleared. */
647 private void cleanTapExcludeRegion() {
Tiger Huang2b210c22019-03-18 21:21:26 +0800648 if (!isAttachedToWindow() || mTapExcludeRegion.isEmpty()) {
Mark Renouf3856b4f2019-02-13 16:43:18 -0500649 return;
650 }
Tiger Huang2b210c22019-03-18 21:21:26 +0800651 // Update tap exclude region with a null region to clean the state on server.
Andrii Kulian4b6599e2018-01-15 17:24:08 -0800652 try {
653 WindowManagerGlobal.getWindowSession().updateTapExcludeRegion(getWindow(), hashCode(),
Tiger Huang2b210c22019-03-18 21:21:26 +0800654 null /* region */);
655 mTapExcludeRegion.setEmpty();
Andrii Kulian4b6599e2018-01-15 17:24:08 -0800656 } catch (RemoteException e) {
657 e.rethrowAsRuntimeException();
658 }
659 }
660
Andrii Kuliand3134692017-06-26 14:57:02 -0700661 /** Get density of the hosting display. */
662 private int getBaseDisplayDensity() {
663 final WindowManager wm = mContext.getSystemService(WindowManager.class);
664 final DisplayMetrics metrics = new DisplayMetrics();
665 wm.getDefaultDisplay().getMetrics(metrics);
666 return metrics.densityDpi;
667 }
668
669 @Override
670 protected void finalize() throws Throwable {
671 try {
672 if (mGuard != null) {
673 mGuard.warnIfOpen();
674 performRelease();
675 }
676 } finally {
677 super.finalize();
678 }
679 }
Andrii Kuliancf8f6832018-01-23 19:43:30 -0800680
681 /**
Issei Suzukia5dbf522019-02-01 17:58:15 +0100682 * Set forwarded insets on the virtual display.
683 *
684 * @see IWindowManager#setForwardedInsets
685 */
686 public void setForwardedInsets(Insets insets) {
687 mForwardedInsets = insets;
688 if (mVirtualDisplay == null) {
689 return;
690 }
691 try {
692 final IWindowManager wm = WindowManagerGlobal.getWindowManagerService();
693 wm.setForwardedInsets(mVirtualDisplay.getDisplay().getDisplayId(), mForwardedInsets);
694 } catch (RemoteException e) {
695 e.rethrowAsRuntimeException();
696 }
697 }
698
699 /**
Andrii Kuliancf8f6832018-01-23 19:43:30 -0800700 * A task change listener that detects background color change of the topmost stack on our
701 * virtual display and updates the background of the surface view. This background will be shown
702 * when surface view is resized, but the app hasn't drawn its content in new size yet.
Brad Stenninga1dbe9c2018-05-02 08:29:28 -0700703 * It also calls StateCallback.onTaskMovedToFront to notify interested parties that the stack
704 * associated with the {@link ActivityView} has had a Task moved to the front. This is useful
705 * when needing to also bring the host Activity to the foreground at the same time.
Andrii Kuliancf8f6832018-01-23 19:43:30 -0800706 */
Brad Stenninga1dbe9c2018-05-02 08:29:28 -0700707 private class TaskStackListenerImpl extends TaskStackListener {
Andrii Kuliancf8f6832018-01-23 19:43:30 -0800708
709 @Override
Mark Renoufc808f062019-02-07 15:20:37 -0500710 public void onTaskDescriptionChanged(ActivityManager.RunningTaskInfo taskInfo)
Andrii Kuliancf8f6832018-01-23 19:43:30 -0800711 throws RemoteException {
Mark Renoufc808f062019-02-07 15:20:37 -0500712 if (mVirtualDisplay == null
713 || taskInfo.displayId != mVirtualDisplay.getDisplay().getDisplayId()) {
Andrii Kuliancf8f6832018-01-23 19:43:30 -0800714 return;
715 }
716
Brad Stenninga1dbe9c2018-05-02 08:29:28 -0700717 StackInfo stackInfo = getTopMostStackInfo();
718 if (stackInfo == null) {
719 return;
720 }
721 // Found the topmost stack on target display. Now check if the topmost task's
722 // description changed.
Mark Renoufc808f062019-02-07 15:20:37 -0500723 if (taskInfo.taskId == stackInfo.taskIds[stackInfo.taskIds.length - 1]) {
724 mSurfaceView.setResizeBackgroundColor(
725 taskInfo.taskDescription.getBackgroundColor());
Brad Stenninga1dbe9c2018-05-02 08:29:28 -0700726 }
727 }
728
729 @Override
Mark Renoufc808f062019-02-07 15:20:37 -0500730 public void onTaskMovedToFront(ActivityManager.RunningTaskInfo taskInfo)
731 throws RemoteException {
732 if (mActivityViewCallback == null || mVirtualDisplay == null
733 || taskInfo.displayId != mVirtualDisplay.getDisplay().getDisplayId()) {
Mark Renouf7b71a5d2019-01-23 17:12:34 -0500734 return;
735 }
736
737 StackInfo stackInfo = getTopMostStackInfo();
738 // if StackInfo was null or unrelated to the "move to front" then there's no use
739 // notifying the callback
740 if (stackInfo != null
Mark Renoufc808f062019-02-07 15:20:37 -0500741 && taskInfo.taskId == stackInfo.taskIds[stackInfo.taskIds.length - 1]) {
Wale Ogunwale691af682019-02-11 03:09:10 -0800742 mActivityViewCallback.onTaskMovedToFront(taskInfo.taskId);
Mark Renouf7b71a5d2019-01-23 17:12:34 -0500743 }
744 }
745
746 @Override
747 public void onTaskCreated(int taskId, ComponentName componentName) throws RemoteException {
Mark Renoufc808f062019-02-07 15:20:37 -0500748 if (mActivityViewCallback == null || mVirtualDisplay == null) {
Mark Renouf7b71a5d2019-01-23 17:12:34 -0500749 return;
750 }
751
752 StackInfo stackInfo = getTopMostStackInfo();
753 // if StackInfo was null or unrelated to the task creation then there's no use
754 // notifying the callback
755 if (stackInfo != null
756 && taskId == stackInfo.taskIds[stackInfo.taskIds.length - 1]) {
757 mActivityViewCallback.onTaskCreated(taskId, componentName);
758 }
759 }
760
761 @Override
Mark Renoufc808f062019-02-07 15:20:37 -0500762 public void onTaskRemovalStarted(ActivityManager.RunningTaskInfo taskInfo)
763 throws RemoteException {
764 if (mActivityViewCallback == null || mVirtualDisplay == null
765 || taskInfo.displayId != mVirtualDisplay.getDisplay().getDisplayId()) {
Mark Renouf7b71a5d2019-01-23 17:12:34 -0500766 return;
767 }
Mark Renoufc808f062019-02-07 15:20:37 -0500768 mActivityViewCallback.onTaskRemovalStarted(taskInfo.taskId);
Brad Stenninga1dbe9c2018-05-02 08:29:28 -0700769 }
770
771 private StackInfo getTopMostStackInfo() throws RemoteException {
Andrii Kuliancf8f6832018-01-23 19:43:30 -0800772 // Find the topmost task on our virtual display - it will define the background
773 // color of the surface view during resizing.
774 final int displayId = mVirtualDisplay.getDisplay().getDisplayId();
Wale Ogunwale04d9cb52018-04-30 13:55:07 -0700775 final List<StackInfo> stackInfoList = mActivityTaskManager.getAllStackInfos();
Andrii Kuliancf8f6832018-01-23 19:43:30 -0800776
777 // Iterate through stacks from top to bottom.
778 final int stackCount = stackInfoList.size();
779 for (int i = 0; i < stackCount; i++) {
780 final StackInfo stackInfo = stackInfoList.get(i);
781 // Only look for stacks on our virtual display.
782 if (stackInfo.displayId != displayId) {
783 continue;
784 }
Brad Stenninga1dbe9c2018-05-02 08:29:28 -0700785 // Found the topmost stack on target display.
786 return stackInfo;
Andrii Kuliancf8f6832018-01-23 19:43:30 -0800787 }
Brad Stenninga1dbe9c2018-05-02 08:29:28 -0700788 return null;
Andrii Kuliancf8f6832018-01-23 19:43:30 -0800789 }
790 }
Andrii Kuliand3134692017-06-26 14:57:02 -0700791}