blob: e8918285777bbcd7f780c782eb3e1c91f8028351 [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);
lumark3f51e3c2019-07-23 21:18:17 +0800123 // 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());
Issei Suzuki006b71f2019-06-17 15:56:57 +0200126 mSurfaceView.setUseAlpha();
Andrii Kuliand3134692017-06-26 14:57:02 -0700127 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 {
Mark Renouf7b71a5d2019-01-23 17:12:34 -0500137
Andrii Kuliand3134692017-06-26 14:57:02 -0700138 /**
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 */
145 public abstract void onActivityViewReady(ActivityView view);
Mark Renouf7b71a5d2019-01-23 17:12:34 -0500146
Andrii Kuliand3134692017-06-26 14:57:02 -0700147 /**
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 */
154 public abstract void onActivityViewDestroyed(ActivityView view);
Mark Renouf7b71a5d2019-01-23 17:12:34 -0500155
156 /**
157 * Called when a task is created inside the container.
158 * This is a filtered version of {@link TaskStackListener}
159 */
160 public void onTaskCreated(int taskId, ComponentName componentName) { }
161
Brad Stenninga1dbe9c2018-05-02 08:29:28 -0700162 /**
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 */
Wale Ogunwale691af682019-02-11 03:09:10 -0800166 public void onTaskMovedToFront(int taskId) { }
Mark Renouf7b71a5d2019-01-23 17:12:34 -0500167
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 */
172 public void onTaskRemovalStarted(int taskId) { }
Andrii Kuliand3134692017-06-26 14:57:02 -0700173 }
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 */
187 public void setCallback(StateCallback callback) {
188 mActivityViewCallback = callback;
189
190 if (mVirtualDisplay != null && mActivityViewCallback != null) {
191 mActivityViewCallback.onActivityViewReady(this);
192 }
193 }
194
195 /**
Mark Renouf34d04f32019-05-13 15:53:18 -0400196 * 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 */
202 public void setCornerRadius(float cornerRadius) {
203 mSurfaceView.setCornerRadius(cornerRadius);
204 }
205
206 /**
Andrii Kuliand3134692017-06-26 14:57:02 -0700207 * 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 */
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.
Brad Stenninga1dbe9c2018-05-02 08:29:28 -0700228 * <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 */
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.
Andrii Kuliand3134692017-06-26 14:57:02 -0700250 * <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 */
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 /**
Mady Mellor60101c92019-04-11 19:04:00 -0700275 * 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 */
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 /**
Andrii Kuliand3134692017-06-26 14:57:02 -0700303 * Check if container is ready to launch and create {@link ActivityOptions} to target the
304 * virtual display.
305 */
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 */
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
Andrii Kulian4b6599e2018-01-15 17:24:08 -0800332 /**
Tiger Huang2b210c22019-03-18 21:21:26 +0800333 * Triggers an update of {@link ActivityView}'s location in window to properly set tap exclude
Andrii Kulian4b6599e2018-01-15 17:24:08 -0800334 * regions and avoid focus switches by touches on this view.
335 */
336 public void onLocationChanged() {
Tiger Huangd8ec9382019-04-18 14:35:09 -0700337 updateLocationAndTapExcludeRegion();
Andrii Kulian4b6599e2018-01-15 17:24:08 -0800338 }
339
Yohei Yukawab4f328a2019-05-02 08:41:27 -0700340 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
Andrii Kuliand3134692017-06-26 14:57:02 -0700348 @Override
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
lumark3f51e3c2019-07-23 21:18:17 +0800353 /**
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 */
Tiger Huang2b210c22019-03-18 21:21:26 +0800360 @Override
Issei Suzukicac2a502019-04-16 16:52:50 +0200361 public void setAlpha(float alpha) {
lumark3f51e3c2019-07-23 21:18:17 +0800362 super.setAlpha(alpha);
363
364 if (mSurfaceView != null) {
365 mSurfaceView.setAlpha(alpha);
366 }
Issei Suzukicac2a502019-04-16 16:52:50 +0200367 }
368
369 @Override
370 public float getAlpha() {
371 return mSurfaceView.getAlpha();
372 }
373
374 @Override
Tiger Huang2b210c22019-03-18 21:21:26 +0800375 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.
Tiger Huangd8ec9382019-04-18 14:35:09 -0700378 updateLocationAndTapExcludeRegion();
Tiger Huang2b210c22019-03-18 21:21:26 +0800379 return super.gatherTransparentRegion(region);
380 }
381
Tiger Huangd8ec9382019-04-18 14:35:09 -0700382 /**
383 * Sends current location in window and tap exclude region to WM for this view.
384 */
385 private void updateLocationAndTapExcludeRegion() {
386 if (mVirtualDisplay == null || !isAttachedToWindow()) {
Mark Renouf3856b4f2019-02-13 16:43:18 -0500387 return;
388 }
Tiger Huangd8ec9382019-04-18 14:35:09 -0700389 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];
Yohei Yukawab4f328a2019-05-02 08:41:27 -0700396 final int displayId = mVirtualDisplay.getDisplay().getDisplayId();
Tiger Huangd8ec9382019-04-18 14:35:09 -0700397 WindowManagerGlobal.getWindowSession().updateDisplayContentLocation(
Yohei Yukawab4f328a2019-05-02 08:41:27 -0700398 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.
lumark44fe4e42019-07-23 19:29:03 +0800402 // TODO(b/138175283): Address the location update when the host of this view is
403 // moving.
Yohei Yukawab4f328a2019-05-02 08:41:27 -0700404 final Matrix matrix = new Matrix();
lumark44fe4e42019-07-23 19:29:03 +0800405 final int[] locationOnScreen = new int[2];
406 getLocationOnScreen(locationOnScreen);
407 final int dx = locationOnScreen[0];
408 final int dy = locationOnScreen[1];
Yohei Yukawab4f328a2019-05-02 08:41:27 -0700409 matrix.set(getMatrix());
lumark44fe4e42019-07-23 19:29:03 +0800410 matrix.postTranslate(dx, dy);
Yohei Yukawab4f328a2019-05-02 08:41:27 -0700411 mContext.getSystemService(InputMethodManager.class)
412 .reportActivityView(displayId, matrix);
Tiger Huangd8ec9382019-04-18 14:35:09 -0700413 }
414 updateTapExcludeRegion(x, y);
415 } catch (RemoteException e) {
416 e.rethrowAsRuntimeException();
417 }
418 }
419
420 /** Computes and sends current tap exclude region to WM for this view. */
421 private void updateTapExcludeRegion(int x, int y) throws RemoteException {
Tiger Huang2b210c22019-03-18 21:21:26 +0800422 if (!canReceivePointerEvents()) {
423 cleanTapExcludeRegion();
424 return;
425 }
Tiger Huangd8ec9382019-04-18 14:35:09 -0700426 mTapExcludeRegion.set(x, y, x + getWidth(), y + getHeight());
Tiger Huang2b210c22019-03-18 21:21:26 +0800427
Tiger Huangd8ec9382019-04-18 14:35:09 -0700428 // There might be views on top of us. We need to subtract those areas from the tap
429 // exclude region.
430 final ViewParent parent = getParent();
431 if (parent != null) {
432 parent.subtractObscuredTouchableRegion(mTapExcludeRegion, this);
Andrii Kulian4b6599e2018-01-15 17:24:08 -0800433 }
Tiger Huangd8ec9382019-04-18 14:35:09 -0700434
435 WindowManagerGlobal.getWindowSession().updateTapExcludeRegion(getWindow(), hashCode(),
436 mTapExcludeRegion);
Andrii Kulian4b6599e2018-01-15 17:24:08 -0800437 }
438
Andrii Kuliand3134692017-06-26 14:57:02 -0700439 private class SurfaceCallback implements SurfaceHolder.Callback {
440 @Override
441 public void surfaceCreated(SurfaceHolder surfaceHolder) {
442 if (mVirtualDisplay == null) {
Robert Carr5fea55b2018-12-10 13:05:52 -0800443 initVirtualDisplay(new SurfaceSession());
Andrii Kuliand3134692017-06-26 14:57:02 -0700444 if (mVirtualDisplay != null && mActivityViewCallback != null) {
445 mActivityViewCallback.onActivityViewReady(ActivityView.this);
446 }
447 } else {
chaviwff2e7d82018-11-02 11:11:27 -0700448 mTmpTransaction.reparent(mRootSurfaceControl,
Robert Carr10584fa2019-01-14 15:55:19 -0800449 mSurfaceView.getSurfaceControl()).apply();
Andrii Kuliand3134692017-06-26 14:57:02 -0700450 }
chaviwda4c6942018-11-07 15:52:56 -0800451
452 if (mVirtualDisplay != null) {
453 mVirtualDisplay.setDisplayState(true);
454 }
455
Tiger Huangd8ec9382019-04-18 14:35:09 -0700456 updateLocationAndTapExcludeRegion();
Andrii Kuliand3134692017-06-26 14:57:02 -0700457 }
458
459 @Override
460 public void surfaceChanged(SurfaceHolder surfaceHolder, int format, int width, int height) {
461 if (mVirtualDisplay != null) {
462 mVirtualDisplay.resize(width, height, getBaseDisplayDensity());
463 }
Tiger Huangd8ec9382019-04-18 14:35:09 -0700464 updateLocationAndTapExcludeRegion();
Andrii Kuliand3134692017-06-26 14:57:02 -0700465 }
466
467 @Override
468 public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
Andrii Kuliand3134692017-06-26 14:57:02 -0700469 if (mVirtualDisplay != null) {
chaviwda4c6942018-11-07 15:52:56 -0800470 mVirtualDisplay.setDisplayState(false);
Andrii Kuliand3134692017-06-26 14:57:02 -0700471 }
Yohei Yukawab4f328a2019-05-02 08:41:27 -0700472 clearActivityViewGeometryForIme();
Andrii Kulian4b6599e2018-01-15 17:24:08 -0800473 cleanTapExcludeRegion();
Andrii Kuliand3134692017-06-26 14:57:02 -0700474 }
475 }
476
chaviwd44449d2019-01-02 16:33:12 -0800477 @Override
478 protected void onVisibilityChanged(View changedView, int visibility) {
479 super.onVisibilityChanged(changedView, visibility);
480 mSurfaceView.setVisibility(visibility);
481 }
482
Mark Renouf041d7262019-02-06 12:09:41 -0500483 /**
Mady Mellor390bff42019-04-05 15:09:01 -0700484 * @return the display id of the virtual display.
485 */
486 public int getVirtualDisplayId() {
487 if (mVirtualDisplay != null) {
488 return mVirtualDisplay.getDisplay().getDisplayId();
489 }
490 return INVALID_DISPLAY;
491 }
492
493 /**
Mark Renouf041d7262019-02-06 12:09:41 -0500494 * Injects a pair of down/up key events with keycode {@link KeyEvent#KEYCODE_BACK} to the
495 * virtual display.
496 */
497 public void performBackPress() {
498 if (mVirtualDisplay == null) {
499 return;
500 }
501 final int displayId = mVirtualDisplay.getDisplay().getDisplayId();
502 final InputManager im = InputManager.getInstance();
503 im.injectInputEvent(createKeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK, displayId),
504 InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
505 im.injectInputEvent(createKeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK, displayId),
506 InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
507 }
508
509 private static KeyEvent createKeyEvent(int action, int code, int displayId) {
510 long when = SystemClock.uptimeMillis();
511 final KeyEvent ev = new KeyEvent(when, when, action, code, 0 /* repeat */,
512 0 /* metaState */, KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /* scancode */,
513 KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY,
514 InputDevice.SOURCE_KEYBOARD);
515 ev.setDisplayId(displayId);
516 return ev;
517 }
518
chaviwff2e7d82018-11-02 11:11:27 -0700519 private void initVirtualDisplay(SurfaceSession surfaceSession) {
Andrii Kuliand3134692017-06-26 14:57:02 -0700520 if (mVirtualDisplay != null) {
521 throw new IllegalStateException("Trying to initialize for the second time.");
522 }
523
524 final int width = mSurfaceView.getWidth();
525 final int height = mSurfaceView.getHeight();
526 final DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
chaviwff2e7d82018-11-02 11:11:27 -0700527
Andrii Kuliand3134692017-06-26 14:57:02 -0700528 mVirtualDisplay = displayManager.createVirtualDisplay(
chaviwda4c6942018-11-07 15:52:56 -0800529 DISPLAY_NAME + "@" + System.identityHashCode(this), width, height,
530 getBaseDisplayDensity(), null,
531 VIRTUAL_DISPLAY_FLAG_PUBLIC | VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY
532 | VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL);
Andrii Kuliand3134692017-06-26 14:57:02 -0700533 if (mVirtualDisplay == null) {
534 Log.e(TAG, "Failed to initialize ActivityView");
535 return;
536 }
537
Andrii Kulianf0379de2018-03-14 16:24:07 -0700538 final int displayId = mVirtualDisplay.getDisplay().getDisplayId();
539 final IWindowManager wm = WindowManagerGlobal.getWindowManagerService();
chaviwff2e7d82018-11-02 11:11:27 -0700540
541 mRootSurfaceControl = new SurfaceControl.Builder(surfaceSession)
Chavi Weingarten6ef9cc62019-02-07 16:28:45 +0000542 .setContainerLayer()
Robert Carr5fea55b2018-12-10 13:05:52 -0800543 .setParent(mSurfaceView.getSurfaceControl())
chaviwff2e7d82018-11-02 11:11:27 -0700544 .setName(DISPLAY_NAME)
545 .build();
546
Andrii Kulianf0379de2018-03-14 16:24:07 -0700547 try {
Issei Suzukia5dbf522019-02-01 17:58:15 +0100548 // TODO: Find a way to consolidate these calls to the server.
Tiger Huangd8ec9382019-04-18 14:35:09 -0700549 WindowManagerGlobal.getWindowSession().reparentDisplayContent(
550 getWindow(), mRootSurfaceControl, displayId);
Andrii Kulianf0379de2018-03-14 16:24:07 -0700551 wm.dontOverrideDisplayInfo(displayId);
Wale Ogunwale9e737db2018-12-17 15:42:37 -0800552 if (mSingleTaskInstance) {
553 mActivityTaskManager.setDisplayToSingleTaskInstance(displayId);
554 }
Issei Suzukia5dbf522019-02-01 17:58:15 +0100555 wm.setForwardedInsets(displayId, mForwardedInsets);
Andrii Kulianf0379de2018-03-14 16:24:07 -0700556 } catch (RemoteException e) {
557 e.rethrowAsRuntimeException();
558 }
chaviwff2e7d82018-11-02 11:11:27 -0700559
560 mTmpTransaction.show(mRootSurfaceControl).apply();
Brad Stenninga1dbe9c2018-05-02 08:29:28 -0700561 mTaskStackListener = new TaskStackListenerImpl();
Andrii Kuliancf8f6832018-01-23 19:43:30 -0800562 try {
Wale Ogunwale04d9cb52018-04-30 13:55:07 -0700563 mActivityTaskManager.registerTaskStackListener(mTaskStackListener);
Andrii Kuliancf8f6832018-01-23 19:43:30 -0800564 } catch (RemoteException e) {
565 Log.e(TAG, "Failed to register task stack listener", e);
566 }
Andrii Kuliand3134692017-06-26 14:57:02 -0700567 }
568
569 private void performRelease() {
570 if (!mOpened) {
571 return;
572 }
573
574 mSurfaceView.getHolder().removeCallback(mSurfaceCallback);
575
Andrii Kulian4b6599e2018-01-15 17:24:08 -0800576 cleanTapExcludeRegion();
Andrii Kuliand3134692017-06-26 14:57:02 -0700577
Andrii Kuliancf8f6832018-01-23 19:43:30 -0800578 if (mTaskStackListener != null) {
579 try {
Wale Ogunwale04d9cb52018-04-30 13:55:07 -0700580 mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener);
Andrii Kuliancf8f6832018-01-23 19:43:30 -0800581 } catch (RemoteException e) {
582 Log.e(TAG, "Failed to unregister task stack listener", e);
583 }
584 mTaskStackListener = null;
585 }
586
Andrii Kuliand3134692017-06-26 14:57:02 -0700587 final boolean displayReleased;
588 if (mVirtualDisplay != null) {
589 mVirtualDisplay.release();
590 mVirtualDisplay = null;
591 displayReleased = true;
592 } else {
593 displayReleased = false;
594 }
595
Andrii Kuliand3134692017-06-26 14:57:02 -0700596 if (displayReleased && mActivityViewCallback != null) {
597 mActivityViewCallback.onActivityViewDestroyed(this);
598 }
599
600 mGuard.close();
601 mOpened = false;
602 }
603
Andrii Kulian4b6599e2018-01-15 17:24:08 -0800604 /** Report to server that tap exclude region on hosting display should be cleared. */
605 private void cleanTapExcludeRegion() {
Tiger Huang2b210c22019-03-18 21:21:26 +0800606 if (!isAttachedToWindow() || mTapExcludeRegion.isEmpty()) {
Mark Renouf3856b4f2019-02-13 16:43:18 -0500607 return;
608 }
Tiger Huang2b210c22019-03-18 21:21:26 +0800609 // Update tap exclude region with a null region to clean the state on server.
Andrii Kulian4b6599e2018-01-15 17:24:08 -0800610 try {
611 WindowManagerGlobal.getWindowSession().updateTapExcludeRegion(getWindow(), hashCode(),
Tiger Huang2b210c22019-03-18 21:21:26 +0800612 null /* region */);
613 mTapExcludeRegion.setEmpty();
Andrii Kulian4b6599e2018-01-15 17:24:08 -0800614 } catch (RemoteException e) {
615 e.rethrowAsRuntimeException();
616 }
617 }
618
Andrii Kuliand3134692017-06-26 14:57:02 -0700619 /** Get density of the hosting display. */
620 private int getBaseDisplayDensity() {
621 final WindowManager wm = mContext.getSystemService(WindowManager.class);
622 final DisplayMetrics metrics = new DisplayMetrics();
623 wm.getDefaultDisplay().getMetrics(metrics);
624 return metrics.densityDpi;
625 }
626
627 @Override
628 protected void finalize() throws Throwable {
629 try {
630 if (mGuard != null) {
631 mGuard.warnIfOpen();
632 performRelease();
633 }
634 } finally {
635 super.finalize();
636 }
637 }
Andrii Kuliancf8f6832018-01-23 19:43:30 -0800638
639 /**
Issei Suzukia5dbf522019-02-01 17:58:15 +0100640 * Set forwarded insets on the virtual display.
641 *
642 * @see IWindowManager#setForwardedInsets
643 */
644 public void setForwardedInsets(Insets insets) {
645 mForwardedInsets = insets;
646 if (mVirtualDisplay == null) {
647 return;
648 }
649 try {
650 final IWindowManager wm = WindowManagerGlobal.getWindowManagerService();
651 wm.setForwardedInsets(mVirtualDisplay.getDisplay().getDisplayId(), mForwardedInsets);
652 } catch (RemoteException e) {
653 e.rethrowAsRuntimeException();
654 }
655 }
656
657 /**
Andrii Kuliancf8f6832018-01-23 19:43:30 -0800658 * A task change listener that detects background color change of the topmost stack on our
659 * virtual display and updates the background of the surface view. This background will be shown
660 * when surface view is resized, but the app hasn't drawn its content in new size yet.
Brad Stenninga1dbe9c2018-05-02 08:29:28 -0700661 * It also calls StateCallback.onTaskMovedToFront to notify interested parties that the stack
662 * associated with the {@link ActivityView} has had a Task moved to the front. This is useful
663 * when needing to also bring the host Activity to the foreground at the same time.
Andrii Kuliancf8f6832018-01-23 19:43:30 -0800664 */
Brad Stenninga1dbe9c2018-05-02 08:29:28 -0700665 private class TaskStackListenerImpl extends TaskStackListener {
Andrii Kuliancf8f6832018-01-23 19:43:30 -0800666
667 @Override
Mark Renoufc808f062019-02-07 15:20:37 -0500668 public void onTaskDescriptionChanged(ActivityManager.RunningTaskInfo taskInfo)
Andrii Kuliancf8f6832018-01-23 19:43:30 -0800669 throws RemoteException {
Mark Renoufc808f062019-02-07 15:20:37 -0500670 if (mVirtualDisplay == null
671 || taskInfo.displayId != mVirtualDisplay.getDisplay().getDisplayId()) {
Andrii Kuliancf8f6832018-01-23 19:43:30 -0800672 return;
673 }
674
Brad Stenninga1dbe9c2018-05-02 08:29:28 -0700675 StackInfo stackInfo = getTopMostStackInfo();
676 if (stackInfo == null) {
677 return;
678 }
679 // Found the topmost stack on target display. Now check if the topmost task's
680 // description changed.
Mark Renoufc808f062019-02-07 15:20:37 -0500681 if (taskInfo.taskId == stackInfo.taskIds[stackInfo.taskIds.length - 1]) {
682 mSurfaceView.setResizeBackgroundColor(
683 taskInfo.taskDescription.getBackgroundColor());
Brad Stenninga1dbe9c2018-05-02 08:29:28 -0700684 }
685 }
686
687 @Override
Mark Renoufc808f062019-02-07 15:20:37 -0500688 public void onTaskMovedToFront(ActivityManager.RunningTaskInfo taskInfo)
689 throws RemoteException {
690 if (mActivityViewCallback == null || mVirtualDisplay == null
691 || taskInfo.displayId != mVirtualDisplay.getDisplay().getDisplayId()) {
Mark Renouf7b71a5d2019-01-23 17:12:34 -0500692 return;
693 }
694
695 StackInfo stackInfo = getTopMostStackInfo();
696 // if StackInfo was null or unrelated to the "move to front" then there's no use
697 // notifying the callback
698 if (stackInfo != null
Mark Renoufc808f062019-02-07 15:20:37 -0500699 && taskInfo.taskId == stackInfo.taskIds[stackInfo.taskIds.length - 1]) {
Wale Ogunwale691af682019-02-11 03:09:10 -0800700 mActivityViewCallback.onTaskMovedToFront(taskInfo.taskId);
Mark Renouf7b71a5d2019-01-23 17:12:34 -0500701 }
702 }
703
704 @Override
705 public void onTaskCreated(int taskId, ComponentName componentName) throws RemoteException {
Mark Renoufc808f062019-02-07 15:20:37 -0500706 if (mActivityViewCallback == null || mVirtualDisplay == null) {
Mark Renouf7b71a5d2019-01-23 17:12:34 -0500707 return;
708 }
709
710 StackInfo stackInfo = getTopMostStackInfo();
711 // if StackInfo was null or unrelated to the task creation then there's no use
712 // notifying the callback
713 if (stackInfo != null
714 && taskId == stackInfo.taskIds[stackInfo.taskIds.length - 1]) {
715 mActivityViewCallback.onTaskCreated(taskId, componentName);
716 }
717 }
718
719 @Override
Mark Renoufc808f062019-02-07 15:20:37 -0500720 public void onTaskRemovalStarted(ActivityManager.RunningTaskInfo taskInfo)
721 throws RemoteException {
722 if (mActivityViewCallback == null || mVirtualDisplay == null
723 || taskInfo.displayId != mVirtualDisplay.getDisplay().getDisplayId()) {
Mark Renouf7b71a5d2019-01-23 17:12:34 -0500724 return;
725 }
Mark Renoufc808f062019-02-07 15:20:37 -0500726 mActivityViewCallback.onTaskRemovalStarted(taskInfo.taskId);
Brad Stenninga1dbe9c2018-05-02 08:29:28 -0700727 }
728
729 private StackInfo getTopMostStackInfo() throws RemoteException {
Andrii Kuliancf8f6832018-01-23 19:43:30 -0800730 // Find the topmost task on our virtual display - it will define the background
731 // color of the surface view during resizing.
732 final int displayId = mVirtualDisplay.getDisplay().getDisplayId();
Wale Ogunwale04d9cb52018-04-30 13:55:07 -0700733 final List<StackInfo> stackInfoList = mActivityTaskManager.getAllStackInfos();
Andrii Kuliancf8f6832018-01-23 19:43:30 -0800734
735 // Iterate through stacks from top to bottom.
736 final int stackCount = stackInfoList.size();
737 for (int i = 0; i < stackCount; i++) {
738 final StackInfo stackInfo = stackInfoList.get(i);
739 // Only look for stacks on our virtual display.
740 if (stackInfo.displayId != displayId) {
741 continue;
742 }
Brad Stenninga1dbe9c2018-05-02 08:29:28 -0700743 // Found the topmost stack on target display.
744 return stackInfo;
Andrii Kuliancf8f6832018-01-23 19:43:30 -0800745 }
Brad Stenninga1dbe9c2018-05-02 08:29:28 -0700746 return null;
Andrii Kuliancf8f6832018-01-23 19:43:30 -0800747 }
748 }
Andrii Kuliand3134692017-06-26 14:57:02 -0700749}