blob: fbf1f59141a828b8aada2d2101eb4add126db26d [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;
Mady Mellorc41ed322019-09-25 11:19:26 -070025import android.annotation.Nullable;
Wale Ogunwale691af682019-02-11 03:09:10 -080026import android.annotation.TestApi;
Andrii Kuliancf8f6832018-01-23 19:43:30 -080027import android.app.ActivityManager.StackInfo;
Mark Renouf7b71a5d2019-01-23 17:12:34 -050028import android.content.ComponentName;
Andrii Kuliand3134692017-06-26 14:57:02 -070029import android.content.Context;
30import android.content.Intent;
Issei Suzukia5dbf522019-02-01 17:58:15 +010031import android.graphics.Insets;
Yohei Yukawab4f328a2019-05-02 08:41:27 -070032import android.graphics.Matrix;
Mark Renoufe574ad02019-08-12 16:22:15 -040033import android.graphics.Rect;
Tiger Huang2b210c22019-03-18 21:21:26 +080034import android.graphics.Region;
Andrii Kuliand3134692017-06-26 14:57:02 -070035import android.hardware.display.DisplayManager;
36import android.hardware.display.VirtualDisplay;
Mark Renouf041d7262019-02-06 12:09:41 -050037import android.hardware.input.InputManager;
Andrii Kuliand3134692017-06-26 14:57:02 -070038import android.os.RemoteException;
Mark Renouf041d7262019-02-06 12:09:41 -050039import android.os.SystemClock;
Brad Stenninga1dbe9c2018-05-02 08:29:28 -070040import android.os.UserHandle;
Andrii Kuliand3134692017-06-26 14:57:02 -070041import android.util.AttributeSet;
42import android.util.DisplayMetrics;
43import android.util.Log;
Andrii Kulianf0379de2018-03-14 16:24:07 -070044import android.view.IWindowManager;
Mark Renouf041d7262019-02-06 12:09:41 -050045import android.view.InputDevice;
46import android.view.KeyCharacterMap;
47import android.view.KeyEvent;
chaviwff2e7d82018-11-02 11:11:27 -070048import android.view.SurfaceControl;
Andrii Kuliand3134692017-06-26 14:57:02 -070049import android.view.SurfaceHolder;
chaviwff2e7d82018-11-02 11:11:27 -070050import android.view.SurfaceSession;
Andrii Kuliand3134692017-06-26 14:57:02 -070051import android.view.SurfaceView;
chaviwd44449d2019-01-02 16:33:12 -080052import android.view.View;
Andrii Kuliand3134692017-06-26 14:57:02 -070053import android.view.ViewGroup;
Tiger Huang2b210c22019-03-18 21:21:26 +080054import android.view.ViewParent;
Andrii Kuliand3134692017-06-26 14:57:02 -070055import android.view.WindowManager;
Andrii Kulian4b6599e2018-01-15 17:24:08 -080056import android.view.WindowManagerGlobal;
Yohei Yukawab4f328a2019-05-02 08:41:27 -070057import android.view.inputmethod.InputMethodManager;
Andrii Kuliand3134692017-06-26 14:57:02 -070058
59import dalvik.system.CloseGuard;
60
Andrii Kuliancf8f6832018-01-23 19:43:30 -080061import java.util.List;
62
Andrii Kuliand3134692017-06-26 14:57:02 -070063/**
Tiger Huang04dc4cc2019-01-17 18:41:41 +080064 * Activity container that allows launching activities into itself.
Andrii Kuliand3134692017-06-26 14:57:02 -070065 * <p>Activity launching into this container is restricted by the same rules that apply to launching
66 * on VirtualDisplays.
67 * @hide
68 */
Wale Ogunwale691af682019-02-11 03:09:10 -080069@TestApi
Andrii Kuliand3134692017-06-26 14:57:02 -070070public class ActivityView extends ViewGroup {
71
72 private static final String DISPLAY_NAME = "ActivityViewVirtualDisplay";
73 private static final String TAG = "ActivityView";
74
75 private VirtualDisplay mVirtualDisplay;
76 private final SurfaceView mSurfaceView;
chaviwff2e7d82018-11-02 11:11:27 -070077
78 /**
79 * This is the root surface for the VirtualDisplay. The VirtualDisplay child surfaces will be
80 * re-parented to this surface. This will also be a child of the SurfaceView's SurfaceControl.
81 */
82 private SurfaceControl mRootSurfaceControl;
Andrii Kuliand3134692017-06-26 14:57:02 -070083
84 private final SurfaceCallback mSurfaceCallback;
85 private StateCallback mActivityViewCallback;
86
Wale Ogunwale04d9cb52018-04-30 13:55:07 -070087 private IActivityTaskManager mActivityTaskManager;
Tiger Huang04dc4cc2019-01-17 18:41:41 +080088 // Temp container to store view coordinates in window.
89 private final int[] mLocationInWindow = new int[2];
Andrii Kuliand3134692017-06-26 14:57:02 -070090
Tiger Huang2b210c22019-03-18 21:21:26 +080091 // The latest tap exclude region that we've sent to WM.
92 private final Region mTapExcludeRegion = new Region();
93
Andrii Kuliancf8f6832018-01-23 19:43:30 -080094 private TaskStackListener mTaskStackListener;
95
Andrii Kuliand3134692017-06-26 14:57:02 -070096 private final CloseGuard mGuard = CloseGuard.get();
97 private boolean mOpened; // Protected by mGuard.
98
chaviwff2e7d82018-11-02 11:11:27 -070099 private final SurfaceControl.Transaction mTmpTransaction = new SurfaceControl.Transaction();
chaviwff2e7d82018-11-02 11:11:27 -0700100
Wale Ogunwale9e737db2018-12-17 15:42:37 -0800101 /** The ActivityView is only allowed to contain one task. */
102 private final boolean mSingleTaskInstance;
103
Issei Suzukia5dbf522019-02-01 17:58:15 +0100104 private Insets mForwardedInsets;
105
Mark Renoufe574ad02019-08-12 16:22:15 -0400106 private float mCornerRadius;
107
Andrii Kuliand3134692017-06-26 14:57:02 -0700108 public ActivityView(Context context) {
109 this(context, null /* attrs */);
110 }
111
112 public ActivityView(Context context, AttributeSet attrs) {
113 this(context, attrs, 0 /* defStyle */);
114 }
115
116 public ActivityView(Context context, AttributeSet attrs, int defStyle) {
Wale Ogunwale9e737db2018-12-17 15:42:37 -0800117 this(context, attrs, defStyle, false /*singleTaskInstance*/);
118 }
119
120 public ActivityView(
121 Context context, AttributeSet attrs, int defStyle, boolean singleTaskInstance) {
Andrii Kuliand3134692017-06-26 14:57:02 -0700122 super(context, attrs, defStyle);
Wale Ogunwale9e737db2018-12-17 15:42:37 -0800123 mSingleTaskInstance = singleTaskInstance;
Andrii Kuliand3134692017-06-26 14:57:02 -0700124
Wale Ogunwale04d9cb52018-04-30 13:55:07 -0700125 mActivityTaskManager = ActivityTaskManager.getService();
Andrii Kuliand3134692017-06-26 14:57:02 -0700126 mSurfaceView = new SurfaceView(context);
lumark3f51e3c2019-07-23 21:18:17 +0800127 // Since ActivityView#getAlpha has been overridden, we should use parent class's alpha
128 // as master to synchronize surface view's alpha value.
129 mSurfaceView.setAlpha(super.getAlpha());
Issei Suzuki006b71f2019-06-17 15:56:57 +0200130 mSurfaceView.setUseAlpha();
Andrii Kuliand3134692017-06-26 14:57:02 -0700131 mSurfaceCallback = new SurfaceCallback();
132 mSurfaceView.getHolder().addCallback(mSurfaceCallback);
133 addView(mSurfaceView);
134
135 mOpened = true;
136 mGuard.open("release");
137 }
138
139 /** Callback that notifies when the container is ready or destroyed. */
140 public abstract static class StateCallback {
Mark Renouf7b71a5d2019-01-23 17:12:34 -0500141
Andrii Kuliand3134692017-06-26 14:57:02 -0700142 /**
143 * Called when the container is ready for launching activities. Calling
144 * {@link #startActivity(Intent)} prior to this callback will result in an
145 * {@link IllegalStateException}.
146 *
147 * @see #startActivity(Intent)
148 */
149 public abstract void onActivityViewReady(ActivityView view);
Mark Renouf7b71a5d2019-01-23 17:12:34 -0500150
Andrii Kuliand3134692017-06-26 14:57:02 -0700151 /**
152 * Called when the container can no longer launch activities. Calling
153 * {@link #startActivity(Intent)} after this callback will result in an
154 * {@link IllegalStateException}.
155 *
156 * @see #startActivity(Intent)
157 */
158 public abstract void onActivityViewDestroyed(ActivityView view);
Mark Renouf7b71a5d2019-01-23 17:12:34 -0500159
160 /**
161 * Called when a task is created inside the container.
162 * This is a filtered version of {@link TaskStackListener}
163 */
164 public void onTaskCreated(int taskId, ComponentName componentName) { }
165
Brad Stenninga1dbe9c2018-05-02 08:29:28 -0700166 /**
167 * Called when a task is moved to the front of the stack inside the container.
168 * This is a filtered version of {@link TaskStackListener}
169 */
Wale Ogunwale691af682019-02-11 03:09:10 -0800170 public void onTaskMovedToFront(int taskId) { }
Mark Renouf7b71a5d2019-01-23 17:12:34 -0500171
172 /**
173 * Called when a task is about to be removed from the stack inside the container.
174 * This is a filtered version of {@link TaskStackListener}
175 */
176 public void onTaskRemovalStarted(int taskId) { }
Andrii Kuliand3134692017-06-26 14:57:02 -0700177 }
178
179 /**
180 * Set the callback to be notified about state changes.
181 * <p>This class must finish initializing before {@link #startActivity(Intent)} can be called.
182 * <p>Note: If the instance was ready prior to this call being made, then
183 * {@link StateCallback#onActivityViewReady(ActivityView)} will be called from within
184 * this method call.
185 *
186 * @param callback The callback to report events to.
187 *
188 * @see StateCallback
189 * @see #startActivity(Intent)
190 */
191 public void setCallback(StateCallback callback) {
192 mActivityViewCallback = callback;
193
194 if (mVirtualDisplay != null && mActivityViewCallback != null) {
195 mActivityViewCallback.onActivityViewReady(this);
196 }
197 }
198
199 /**
Mark Renouf34d04f32019-05-13 15:53:18 -0400200 * Sets the corner radius for the Activity displayed here. The corners will be
201 * cropped from the window painted by the contained Activity.
202 *
203 * @param cornerRadius the radius for the corners, in pixels
204 * @hide
205 */
206 public void setCornerRadius(float cornerRadius) {
207 mSurfaceView.setCornerRadius(cornerRadius);
208 }
209
210 /**
Mark Renoufe574ad02019-08-12 16:22:15 -0400211 * @hide
212 */
213 public float getCornerRadius() {
214 return mSurfaceView.getCornerRadius();
215 }
216
217 /**
218 * Control whether the surface is clipped to the same bounds as the View. If true, then
219 * the bounds set by {@link #setSurfaceClipBounds(Rect)} are applied to the surface as
220 * window-crop.
221 *
222 * @param clippingEnabled whether to enable surface clipping
223 * @hide
224 */
225 public void setSurfaceClippingEnabled(boolean clippingEnabled) {
226 mSurfaceView.setEnableSurfaceClipping(clippingEnabled);
227 }
228
229 /**
230 * Sets an area on the contained surface to which it will be clipped
231 * when it is drawn. Setting the value to null will remove the clip bounds
232 * and the surface will draw normally, using its full bounds.
233 *
234 * @param clipBounds The rectangular area, in the local coordinates of
235 * this view, to which future drawing operations will be clipped.
236 * @hide
237 */
238 public void setSurfaceClipBounds(Rect clipBounds) {
239 mSurfaceView.setClipBounds(clipBounds);
240 }
241
242 /**
243 * @hide
244 */
245 public boolean getSurfaceClipBounds(Rect outRect) {
246 return mSurfaceView.getClipBounds(outRect);
247 }
248
249 /**
Andrii Kuliand3134692017-06-26 14:57:02 -0700250 * Launch a new activity into this container.
251 * <p>Activity resolved by the provided {@link Intent} must have
252 * {@link android.R.attr#resizeableActivity} attribute set to {@code true} in order to be
253 * launched here. Also, if activity is not owned by the owner of this container, it must allow
254 * embedding and the caller must have permission to embed.
255 * <p>Note: This class must finish initializing and
256 * {@link StateCallback#onActivityViewReady(ActivityView)} callback must be triggered before
257 * this method can be called.
258 *
259 * @param intent Intent used to launch an activity.
260 *
261 * @see StateCallback
262 * @see #startActivity(PendingIntent)
263 */
264 public void startActivity(@NonNull Intent intent) {
265 final ActivityOptions options = prepareActivityOptions();
266 getContext().startActivity(intent, options.toBundle());
267 }
268
269 /**
270 * Launch a new activity into this container.
Brad Stenninga1dbe9c2018-05-02 08:29:28 -0700271 * <p>Activity resolved by the provided {@link Intent} must have
272 * {@link android.R.attr#resizeableActivity} attribute set to {@code true} in order to be
273 * launched here. Also, if activity is not owned by the owner of this container, it must allow
274 * embedding and the caller must have permission to embed.
275 * <p>Note: This class must finish initializing and
276 * {@link StateCallback#onActivityViewReady(ActivityView)} callback must be triggered before
277 * this method can be called.
278 *
279 * @param intent Intent used to launch an activity.
280 * @param user The UserHandle of the user to start this activity for.
281 *
282 *
283 * @see StateCallback
284 * @see #startActivity(PendingIntent)
285 */
286 public void startActivity(@NonNull Intent intent, UserHandle user) {
287 final ActivityOptions options = prepareActivityOptions();
288 getContext().startActivityAsUser(intent, options.toBundle(), user);
289 }
290
291 /**
292 * Launch a new activity into this container.
Andrii Kuliand3134692017-06-26 14:57:02 -0700293 * <p>Activity resolved by the provided {@link PendingIntent} must have
294 * {@link android.R.attr#resizeableActivity} attribute set to {@code true} in order to be
295 * launched here. Also, if activity is not owned by the owner of this container, it must allow
296 * embedding and the caller must have permission to embed.
297 * <p>Note: This class must finish initializing and
298 * {@link StateCallback#onActivityViewReady(ActivityView)} callback must be triggered before
299 * this method can be called.
300 *
301 * @param pendingIntent Intent used to launch an activity.
302 *
303 * @see StateCallback
304 * @see #startActivity(Intent)
305 */
306 public void startActivity(@NonNull PendingIntent pendingIntent) {
307 final ActivityOptions options = prepareActivityOptions();
308 try {
309 pendingIntent.send(null /* context */, 0 /* code */, null /* intent */,
310 null /* onFinished */, null /* handler */, null /* requiredPermission */,
311 options.toBundle());
312 } catch (PendingIntent.CanceledException e) {
313 throw new RuntimeException(e);
314 }
315 }
316
317 /**
Mady Mellor60101c92019-04-11 19:04:00 -0700318 * Launch a new activity into this container.
319 * <p>Activity resolved by the provided {@link PendingIntent} must have
320 * {@link android.R.attr#resizeableActivity} attribute set to {@code true} in order to be
321 * launched here. Also, if activity is not owned by the owner of this container, it must allow
322 * embedding and the caller must have permission to embed.
323 * <p>Note: This class must finish initializing and
324 * {@link StateCallback#onActivityViewReady(ActivityView)} callback must be triggered before
325 * this method can be called.
326 *
327 * @param pendingIntent Intent used to launch an activity.
Mady Mellorc41ed322019-09-25 11:19:26 -0700328 * @param fillInIntent Additional Intent data, see {@link Intent#fillIn Intent.fillIn()}.
Mady Mellor60101c92019-04-11 19:04:00 -0700329 * @param options options for the activity
330 *
331 * @see StateCallback
332 * @see #startActivity(Intent)
333 */
Mady Mellorc41ed322019-09-25 11:19:26 -0700334 public void startActivity(@NonNull PendingIntent pendingIntent, @Nullable Intent fillInIntent,
Mady Mellor60101c92019-04-11 19:04:00 -0700335 @NonNull ActivityOptions options) {
336 options.setLaunchDisplayId(mVirtualDisplay.getDisplay().getDisplayId());
337 try {
Mady Mellorc41ed322019-09-25 11:19:26 -0700338 pendingIntent.send(getContext(), 0 /* code */, fillInIntent,
Mady Mellor60101c92019-04-11 19:04:00 -0700339 null /* onFinished */, null /* handler */, null /* requiredPermission */,
340 options.toBundle());
341 } catch (PendingIntent.CanceledException e) {
342 throw new RuntimeException(e);
343 }
344 }
345
346 /**
Andrii Kuliand3134692017-06-26 14:57:02 -0700347 * Check if container is ready to launch and create {@link ActivityOptions} to target the
348 * virtual display.
349 */
350 private ActivityOptions prepareActivityOptions() {
351 if (mVirtualDisplay == null) {
352 throw new IllegalStateException(
353 "Trying to start activity before ActivityView is ready.");
354 }
355 final ActivityOptions options = ActivityOptions.makeBasic();
356 options.setLaunchDisplayId(mVirtualDisplay.getDisplay().getDisplayId());
357 return options;
358 }
359
360 /**
361 * Release this container. Activity launching will no longer be permitted.
362 * <p>Note: Calling this method is allowed after
363 * {@link StateCallback#onActivityViewReady(ActivityView)} callback was triggered and before
364 * {@link StateCallback#onActivityViewDestroyed(ActivityView)}.
365 *
366 * @see StateCallback
367 */
368 public void release() {
369 if (mVirtualDisplay == null) {
370 throw new IllegalStateException(
371 "Trying to release container that is not initialized.");
372 }
373 performRelease();
374 }
375
Andrii Kulian4b6599e2018-01-15 17:24:08 -0800376 /**
Tiger Huang2b210c22019-03-18 21:21:26 +0800377 * Triggers an update of {@link ActivityView}'s location in window to properly set tap exclude
Andrii Kulian4b6599e2018-01-15 17:24:08 -0800378 * regions and avoid focus switches by touches on this view.
379 */
380 public void onLocationChanged() {
Tiger Huangd8ec9382019-04-18 14:35:09 -0700381 updateLocationAndTapExcludeRegion();
Andrii Kulian4b6599e2018-01-15 17:24:08 -0800382 }
383
Yohei Yukawab4f328a2019-05-02 08:41:27 -0700384 private void clearActivityViewGeometryForIme() {
385 if (mVirtualDisplay == null) {
386 return;
387 }
388 final int displayId = mVirtualDisplay.getDisplay().getDisplayId();
389 mContext.getSystemService(InputMethodManager.class).reportActivityView(displayId, null);
390 }
391
Andrii Kuliand3134692017-06-26 14:57:02 -0700392 @Override
393 public void onLayout(boolean changed, int l, int t, int r, int b) {
394 mSurfaceView.layout(0 /* left */, 0 /* top */, r - l /* right */, b - t /* bottom */);
395 }
396
lumark3f51e3c2019-07-23 21:18:17 +0800397 /**
398 * Sets the alpha value when the content of {@link SurfaceView} needs to show or hide.
399 * <p>Note: The surface view may ignore the alpha value in some cases. Refer to
400 * {@link SurfaceView#setAlpha} for more details.
401 *
402 * @param alpha The opacity of the view.
403 */
Tiger Huang2b210c22019-03-18 21:21:26 +0800404 @Override
Issei Suzukicac2a502019-04-16 16:52:50 +0200405 public void setAlpha(float alpha) {
lumark3f51e3c2019-07-23 21:18:17 +0800406 super.setAlpha(alpha);
407
408 if (mSurfaceView != null) {
409 mSurfaceView.setAlpha(alpha);
410 }
Issei Suzukicac2a502019-04-16 16:52:50 +0200411 }
412
413 @Override
414 public float getAlpha() {
415 return mSurfaceView.getAlpha();
416 }
417
418 @Override
Tiger Huang2b210c22019-03-18 21:21:26 +0800419 public boolean gatherTransparentRegion(Region region) {
420 // The tap exclude region may be affected by any view on top of it, so we detect the
421 // possible change by monitoring this function.
Tiger Huangd8ec9382019-04-18 14:35:09 -0700422 updateLocationAndTapExcludeRegion();
Tiger Huang2b210c22019-03-18 21:21:26 +0800423 return super.gatherTransparentRegion(region);
424 }
425
Tiger Huangd8ec9382019-04-18 14:35:09 -0700426 /**
427 * Sends current location in window and tap exclude region to WM for this view.
428 */
429 private void updateLocationAndTapExcludeRegion() {
430 if (mVirtualDisplay == null || !isAttachedToWindow()) {
Mark Renouf3856b4f2019-02-13 16:43:18 -0500431 return;
432 }
Tiger Huangd8ec9382019-04-18 14:35:09 -0700433 try {
434 int x = mLocationInWindow[0];
435 int y = mLocationInWindow[1];
436 getLocationInWindow(mLocationInWindow);
437 if (x != mLocationInWindow[0] || y != mLocationInWindow[1]) {
438 x = mLocationInWindow[0];
439 y = mLocationInWindow[1];
Yohei Yukawab4f328a2019-05-02 08:41:27 -0700440 final int displayId = mVirtualDisplay.getDisplay().getDisplayId();
Tiger Huangd8ec9382019-04-18 14:35:09 -0700441 WindowManagerGlobal.getWindowSession().updateDisplayContentLocation(
Yohei Yukawab4f328a2019-05-02 08:41:27 -0700442 getWindow(), x, y, displayId);
443
444 // Also report this geometry information to InputMethodManagerService.
445 // TODO(b/115693908): Unify this logic into the above WMS-based one.
lumark44fe4e42019-07-23 19:29:03 +0800446 // TODO(b/138175283): Address the location update when the host of this view is
447 // moving.
Yohei Yukawab4f328a2019-05-02 08:41:27 -0700448 final Matrix matrix = new Matrix();
lumark44fe4e42019-07-23 19:29:03 +0800449 final int[] locationOnScreen = new int[2];
450 getLocationOnScreen(locationOnScreen);
451 final int dx = locationOnScreen[0];
452 final int dy = locationOnScreen[1];
Yohei Yukawab4f328a2019-05-02 08:41:27 -0700453 matrix.set(getMatrix());
lumark44fe4e42019-07-23 19:29:03 +0800454 matrix.postTranslate(dx, dy);
Yohei Yukawab4f328a2019-05-02 08:41:27 -0700455 mContext.getSystemService(InputMethodManager.class)
456 .reportActivityView(displayId, matrix);
Tiger Huangd8ec9382019-04-18 14:35:09 -0700457 }
458 updateTapExcludeRegion(x, y);
459 } catch (RemoteException e) {
460 e.rethrowAsRuntimeException();
461 }
462 }
463
464 /** Computes and sends current tap exclude region to WM for this view. */
465 private void updateTapExcludeRegion(int x, int y) throws RemoteException {
Tiger Huang2b210c22019-03-18 21:21:26 +0800466 if (!canReceivePointerEvents()) {
467 cleanTapExcludeRegion();
468 return;
469 }
Tiger Huangd8ec9382019-04-18 14:35:09 -0700470 mTapExcludeRegion.set(x, y, x + getWidth(), y + getHeight());
Tiger Huang2b210c22019-03-18 21:21:26 +0800471
Tiger Huangd8ec9382019-04-18 14:35:09 -0700472 // There might be views on top of us. We need to subtract those areas from the tap
473 // exclude region.
474 final ViewParent parent = getParent();
475 if (parent != null) {
476 parent.subtractObscuredTouchableRegion(mTapExcludeRegion, this);
Andrii Kulian4b6599e2018-01-15 17:24:08 -0800477 }
Tiger Huangd8ec9382019-04-18 14:35:09 -0700478
479 WindowManagerGlobal.getWindowSession().updateTapExcludeRegion(getWindow(), hashCode(),
480 mTapExcludeRegion);
Andrii Kulian4b6599e2018-01-15 17:24:08 -0800481 }
482
Andrii Kuliand3134692017-06-26 14:57:02 -0700483 private class SurfaceCallback implements SurfaceHolder.Callback {
484 @Override
485 public void surfaceCreated(SurfaceHolder surfaceHolder) {
486 if (mVirtualDisplay == null) {
Robert Carr5fea55b2018-12-10 13:05:52 -0800487 initVirtualDisplay(new SurfaceSession());
Andrii Kuliand3134692017-06-26 14:57:02 -0700488 if (mVirtualDisplay != null && mActivityViewCallback != null) {
489 mActivityViewCallback.onActivityViewReady(ActivityView.this);
490 }
491 } else {
chaviwff2e7d82018-11-02 11:11:27 -0700492 mTmpTransaction.reparent(mRootSurfaceControl,
Robert Carr10584fa2019-01-14 15:55:19 -0800493 mSurfaceView.getSurfaceControl()).apply();
Andrii Kuliand3134692017-06-26 14:57:02 -0700494 }
chaviwda4c6942018-11-07 15:52:56 -0800495
496 if (mVirtualDisplay != null) {
497 mVirtualDisplay.setDisplayState(true);
498 }
499
Tiger Huangd8ec9382019-04-18 14:35:09 -0700500 updateLocationAndTapExcludeRegion();
Andrii Kuliand3134692017-06-26 14:57:02 -0700501 }
502
503 @Override
504 public void surfaceChanged(SurfaceHolder surfaceHolder, int format, int width, int height) {
505 if (mVirtualDisplay != null) {
506 mVirtualDisplay.resize(width, height, getBaseDisplayDensity());
507 }
Tiger Huangd8ec9382019-04-18 14:35:09 -0700508 updateLocationAndTapExcludeRegion();
Andrii Kuliand3134692017-06-26 14:57:02 -0700509 }
510
511 @Override
512 public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
Andrii Kuliand3134692017-06-26 14:57:02 -0700513 if (mVirtualDisplay != null) {
chaviwda4c6942018-11-07 15:52:56 -0800514 mVirtualDisplay.setDisplayState(false);
Andrii Kuliand3134692017-06-26 14:57:02 -0700515 }
Yohei Yukawab4f328a2019-05-02 08:41:27 -0700516 clearActivityViewGeometryForIme();
Andrii Kulian4b6599e2018-01-15 17:24:08 -0800517 cleanTapExcludeRegion();
Andrii Kuliand3134692017-06-26 14:57:02 -0700518 }
519 }
520
chaviwd44449d2019-01-02 16:33:12 -0800521 @Override
522 protected void onVisibilityChanged(View changedView, int visibility) {
523 super.onVisibilityChanged(changedView, visibility);
524 mSurfaceView.setVisibility(visibility);
525 }
526
Mark Renouf041d7262019-02-06 12:09:41 -0500527 /**
Mady Mellor390bff42019-04-05 15:09:01 -0700528 * @return the display id of the virtual display.
529 */
530 public int getVirtualDisplayId() {
531 if (mVirtualDisplay != null) {
532 return mVirtualDisplay.getDisplay().getDisplayId();
533 }
534 return INVALID_DISPLAY;
535 }
536
537 /**
Mark Renouf041d7262019-02-06 12:09:41 -0500538 * Injects a pair of down/up key events with keycode {@link KeyEvent#KEYCODE_BACK} to the
539 * virtual display.
540 */
541 public void performBackPress() {
542 if (mVirtualDisplay == null) {
543 return;
544 }
545 final int displayId = mVirtualDisplay.getDisplay().getDisplayId();
546 final InputManager im = InputManager.getInstance();
547 im.injectInputEvent(createKeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK, displayId),
548 InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
549 im.injectInputEvent(createKeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK, displayId),
550 InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
551 }
552
553 private static KeyEvent createKeyEvent(int action, int code, int displayId) {
554 long when = SystemClock.uptimeMillis();
555 final KeyEvent ev = new KeyEvent(when, when, action, code, 0 /* repeat */,
556 0 /* metaState */, KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /* scancode */,
557 KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY,
558 InputDevice.SOURCE_KEYBOARD);
559 ev.setDisplayId(displayId);
560 return ev;
561 }
562
chaviwff2e7d82018-11-02 11:11:27 -0700563 private void initVirtualDisplay(SurfaceSession surfaceSession) {
Andrii Kuliand3134692017-06-26 14:57:02 -0700564 if (mVirtualDisplay != null) {
565 throw new IllegalStateException("Trying to initialize for the second time.");
566 }
567
568 final int width = mSurfaceView.getWidth();
569 final int height = mSurfaceView.getHeight();
570 final DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
chaviwff2e7d82018-11-02 11:11:27 -0700571
Andrii Kuliand3134692017-06-26 14:57:02 -0700572 mVirtualDisplay = displayManager.createVirtualDisplay(
chaviwda4c6942018-11-07 15:52:56 -0800573 DISPLAY_NAME + "@" + System.identityHashCode(this), width, height,
574 getBaseDisplayDensity(), null,
575 VIRTUAL_DISPLAY_FLAG_PUBLIC | VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY
576 | VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL);
Andrii Kuliand3134692017-06-26 14:57:02 -0700577 if (mVirtualDisplay == null) {
578 Log.e(TAG, "Failed to initialize ActivityView");
579 return;
580 }
581
Andrii Kulianf0379de2018-03-14 16:24:07 -0700582 final int displayId = mVirtualDisplay.getDisplay().getDisplayId();
583 final IWindowManager wm = WindowManagerGlobal.getWindowManagerService();
chaviwff2e7d82018-11-02 11:11:27 -0700584
585 mRootSurfaceControl = new SurfaceControl.Builder(surfaceSession)
Chavi Weingarten6ef9cc62019-02-07 16:28:45 +0000586 .setContainerLayer()
Robert Carr5fea55b2018-12-10 13:05:52 -0800587 .setParent(mSurfaceView.getSurfaceControl())
chaviwff2e7d82018-11-02 11:11:27 -0700588 .setName(DISPLAY_NAME)
589 .build();
590
Andrii Kulianf0379de2018-03-14 16:24:07 -0700591 try {
Issei Suzukia5dbf522019-02-01 17:58:15 +0100592 // TODO: Find a way to consolidate these calls to the server.
Tiger Huangd8ec9382019-04-18 14:35:09 -0700593 WindowManagerGlobal.getWindowSession().reparentDisplayContent(
594 getWindow(), mRootSurfaceControl, displayId);
Andrii Kulianf0379de2018-03-14 16:24:07 -0700595 wm.dontOverrideDisplayInfo(displayId);
Wale Ogunwale9e737db2018-12-17 15:42:37 -0800596 if (mSingleTaskInstance) {
597 mActivityTaskManager.setDisplayToSingleTaskInstance(displayId);
598 }
Issei Suzukia5dbf522019-02-01 17:58:15 +0100599 wm.setForwardedInsets(displayId, mForwardedInsets);
Andrii Kulianf0379de2018-03-14 16:24:07 -0700600 } catch (RemoteException e) {
601 e.rethrowAsRuntimeException();
602 }
chaviwff2e7d82018-11-02 11:11:27 -0700603
604 mTmpTransaction.show(mRootSurfaceControl).apply();
Brad Stenninga1dbe9c2018-05-02 08:29:28 -0700605 mTaskStackListener = new TaskStackListenerImpl();
Andrii Kuliancf8f6832018-01-23 19:43:30 -0800606 try {
Wale Ogunwale04d9cb52018-04-30 13:55:07 -0700607 mActivityTaskManager.registerTaskStackListener(mTaskStackListener);
Andrii Kuliancf8f6832018-01-23 19:43:30 -0800608 } catch (RemoteException e) {
609 Log.e(TAG, "Failed to register task stack listener", e);
610 }
Andrii Kuliand3134692017-06-26 14:57:02 -0700611 }
612
613 private void performRelease() {
614 if (!mOpened) {
615 return;
616 }
617
618 mSurfaceView.getHolder().removeCallback(mSurfaceCallback);
619
Andrii Kulian4b6599e2018-01-15 17:24:08 -0800620 cleanTapExcludeRegion();
Andrii Kuliand3134692017-06-26 14:57:02 -0700621
Andrii Kuliancf8f6832018-01-23 19:43:30 -0800622 if (mTaskStackListener != null) {
623 try {
Wale Ogunwale04d9cb52018-04-30 13:55:07 -0700624 mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener);
Andrii Kuliancf8f6832018-01-23 19:43:30 -0800625 } catch (RemoteException e) {
626 Log.e(TAG, "Failed to unregister task stack listener", e);
627 }
628 mTaskStackListener = null;
629 }
630
Andrii Kuliand3134692017-06-26 14:57:02 -0700631 final boolean displayReleased;
632 if (mVirtualDisplay != null) {
633 mVirtualDisplay.release();
634 mVirtualDisplay = null;
635 displayReleased = true;
636 } else {
637 displayReleased = false;
638 }
639
Andrii Kuliand3134692017-06-26 14:57:02 -0700640 if (displayReleased && mActivityViewCallback != null) {
641 mActivityViewCallback.onActivityViewDestroyed(this);
642 }
643
644 mGuard.close();
645 mOpened = false;
646 }
647
Andrii Kulian4b6599e2018-01-15 17:24:08 -0800648 /** Report to server that tap exclude region on hosting display should be cleared. */
649 private void cleanTapExcludeRegion() {
Tiger Huang2b210c22019-03-18 21:21:26 +0800650 if (!isAttachedToWindow() || mTapExcludeRegion.isEmpty()) {
Mark Renouf3856b4f2019-02-13 16:43:18 -0500651 return;
652 }
Tiger Huang2b210c22019-03-18 21:21:26 +0800653 // Update tap exclude region with a null region to clean the state on server.
Andrii Kulian4b6599e2018-01-15 17:24:08 -0800654 try {
655 WindowManagerGlobal.getWindowSession().updateTapExcludeRegion(getWindow(), hashCode(),
Tiger Huang2b210c22019-03-18 21:21:26 +0800656 null /* region */);
657 mTapExcludeRegion.setEmpty();
Andrii Kulian4b6599e2018-01-15 17:24:08 -0800658 } catch (RemoteException e) {
659 e.rethrowAsRuntimeException();
660 }
661 }
662
Andrii Kuliand3134692017-06-26 14:57:02 -0700663 /** Get density of the hosting display. */
664 private int getBaseDisplayDensity() {
665 final WindowManager wm = mContext.getSystemService(WindowManager.class);
666 final DisplayMetrics metrics = new DisplayMetrics();
667 wm.getDefaultDisplay().getMetrics(metrics);
668 return metrics.densityDpi;
669 }
670
671 @Override
672 protected void finalize() throws Throwable {
673 try {
674 if (mGuard != null) {
675 mGuard.warnIfOpen();
676 performRelease();
677 }
678 } finally {
679 super.finalize();
680 }
681 }
Andrii Kuliancf8f6832018-01-23 19:43:30 -0800682
683 /**
Issei Suzukia5dbf522019-02-01 17:58:15 +0100684 * Set forwarded insets on the virtual display.
685 *
686 * @see IWindowManager#setForwardedInsets
687 */
688 public void setForwardedInsets(Insets insets) {
689 mForwardedInsets = insets;
690 if (mVirtualDisplay == null) {
691 return;
692 }
693 try {
694 final IWindowManager wm = WindowManagerGlobal.getWindowManagerService();
695 wm.setForwardedInsets(mVirtualDisplay.getDisplay().getDisplayId(), mForwardedInsets);
696 } catch (RemoteException e) {
697 e.rethrowAsRuntimeException();
698 }
699 }
700
701 /**
Andrii Kuliancf8f6832018-01-23 19:43:30 -0800702 * A task change listener that detects background color change of the topmost stack on our
703 * virtual display and updates the background of the surface view. This background will be shown
704 * when surface view is resized, but the app hasn't drawn its content in new size yet.
Brad Stenninga1dbe9c2018-05-02 08:29:28 -0700705 * It also calls StateCallback.onTaskMovedToFront to notify interested parties that the stack
706 * associated with the {@link ActivityView} has had a Task moved to the front. This is useful
707 * when needing to also bring the host Activity to the foreground at the same time.
Andrii Kuliancf8f6832018-01-23 19:43:30 -0800708 */
Brad Stenninga1dbe9c2018-05-02 08:29:28 -0700709 private class TaskStackListenerImpl extends TaskStackListener {
Andrii Kuliancf8f6832018-01-23 19:43:30 -0800710
711 @Override
Mark Renoufc808f062019-02-07 15:20:37 -0500712 public void onTaskDescriptionChanged(ActivityManager.RunningTaskInfo taskInfo)
Andrii Kuliancf8f6832018-01-23 19:43:30 -0800713 throws RemoteException {
Mark Renoufc808f062019-02-07 15:20:37 -0500714 if (mVirtualDisplay == null
715 || taskInfo.displayId != mVirtualDisplay.getDisplay().getDisplayId()) {
Andrii Kuliancf8f6832018-01-23 19:43:30 -0800716 return;
717 }
718
Brad Stenninga1dbe9c2018-05-02 08:29:28 -0700719 StackInfo stackInfo = getTopMostStackInfo();
720 if (stackInfo == null) {
721 return;
722 }
723 // Found the topmost stack on target display. Now check if the topmost task's
724 // description changed.
Mark Renoufc808f062019-02-07 15:20:37 -0500725 if (taskInfo.taskId == stackInfo.taskIds[stackInfo.taskIds.length - 1]) {
726 mSurfaceView.setResizeBackgroundColor(
727 taskInfo.taskDescription.getBackgroundColor());
Brad Stenninga1dbe9c2018-05-02 08:29:28 -0700728 }
729 }
730
731 @Override
Mark Renoufc808f062019-02-07 15:20:37 -0500732 public void onTaskMovedToFront(ActivityManager.RunningTaskInfo taskInfo)
733 throws RemoteException {
734 if (mActivityViewCallback == null || mVirtualDisplay == null
735 || taskInfo.displayId != mVirtualDisplay.getDisplay().getDisplayId()) {
Mark Renouf7b71a5d2019-01-23 17:12:34 -0500736 return;
737 }
738
739 StackInfo stackInfo = getTopMostStackInfo();
740 // if StackInfo was null or unrelated to the "move to front" then there's no use
741 // notifying the callback
742 if (stackInfo != null
Mark Renoufc808f062019-02-07 15:20:37 -0500743 && taskInfo.taskId == stackInfo.taskIds[stackInfo.taskIds.length - 1]) {
Wale Ogunwale691af682019-02-11 03:09:10 -0800744 mActivityViewCallback.onTaskMovedToFront(taskInfo.taskId);
Mark Renouf7b71a5d2019-01-23 17:12:34 -0500745 }
746 }
747
748 @Override
749 public void onTaskCreated(int taskId, ComponentName componentName) throws RemoteException {
Mark Renoufc808f062019-02-07 15:20:37 -0500750 if (mActivityViewCallback == null || mVirtualDisplay == null) {
Mark Renouf7b71a5d2019-01-23 17:12:34 -0500751 return;
752 }
753
754 StackInfo stackInfo = getTopMostStackInfo();
755 // if StackInfo was null or unrelated to the task creation then there's no use
756 // notifying the callback
757 if (stackInfo != null
758 && taskId == stackInfo.taskIds[stackInfo.taskIds.length - 1]) {
759 mActivityViewCallback.onTaskCreated(taskId, componentName);
760 }
761 }
762
763 @Override
Mark Renoufc808f062019-02-07 15:20:37 -0500764 public void onTaskRemovalStarted(ActivityManager.RunningTaskInfo taskInfo)
765 throws RemoteException {
766 if (mActivityViewCallback == null || mVirtualDisplay == null
767 || taskInfo.displayId != mVirtualDisplay.getDisplay().getDisplayId()) {
Mark Renouf7b71a5d2019-01-23 17:12:34 -0500768 return;
769 }
Mark Renoufc808f062019-02-07 15:20:37 -0500770 mActivityViewCallback.onTaskRemovalStarted(taskInfo.taskId);
Brad Stenninga1dbe9c2018-05-02 08:29:28 -0700771 }
772
773 private StackInfo getTopMostStackInfo() throws RemoteException {
Andrii Kuliancf8f6832018-01-23 19:43:30 -0800774 // Find the topmost task on our virtual display - it will define the background
775 // color of the surface view during resizing.
776 final int displayId = mVirtualDisplay.getDisplay().getDisplayId();
Wale Ogunwale04d9cb52018-04-30 13:55:07 -0700777 final List<StackInfo> stackInfoList = mActivityTaskManager.getAllStackInfos();
Andrii Kuliancf8f6832018-01-23 19:43:30 -0800778
779 // Iterate through stacks from top to bottom.
780 final int stackCount = stackInfoList.size();
781 for (int i = 0; i < stackCount; i++) {
782 final StackInfo stackInfo = stackInfoList.get(i);
783 // Only look for stacks on our virtual display.
784 if (stackInfo.displayId != displayId) {
785 continue;
786 }
Brad Stenninga1dbe9c2018-05-02 08:29:28 -0700787 // Found the topmost stack on target display.
788 return stackInfo;
Andrii Kuliancf8f6832018-01-23 19:43:30 -0800789 }
Brad Stenninga1dbe9c2018-05-02 08:29:28 -0700790 return null;
Andrii Kuliancf8f6832018-01-23 19:43:30 -0800791 }
792 }
Andrii Kuliand3134692017-06-26 14:57:02 -0700793}