blob: 635ed138cc65ece9dfbf0af5556f3f12ea273f50 [file] [log] [blame]
Alan Viverette3da604b2020-06-10 18:34:39 +00001/**
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
19import android.annotation.NonNull;
20import android.annotation.Nullable;
21import android.annotation.TestApi;
22import android.content.ComponentName;
23import android.content.Context;
24import android.content.Intent;
25import android.content.pm.LauncherApps;
26import android.content.pm.ShortcutInfo;
27import android.graphics.Insets;
28import android.graphics.Matrix;
29import android.graphics.Point;
30import android.graphics.Rect;
31import android.graphics.Region;
32import android.hardware.display.VirtualDisplay;
33import android.os.Bundle;
34import android.os.UserHandle;
35import android.util.AttributeSet;
36import android.util.Log;
37import android.view.IWindow;
38import android.view.IWindowManager;
39import android.view.KeyEvent;
40import android.view.SurfaceControl;
41import android.view.SurfaceHolder;
42import android.view.SurfaceView;
43import android.view.View;
44import android.view.ViewGroup;
45import android.view.ViewParent;
46import android.window.TaskEmbedder;
47import android.window.TaskOrganizerTaskEmbedder;
48import android.window.VirtualDisplayTaskEmbedder;
49
50import dalvik.system.CloseGuard;
51
52/**
53 * Task container that allows launching activities into itself.
54 * <p>Activity launching into this container is restricted by the same rules that apply to launching
55 * on VirtualDisplays.
56 * @hide
57 */
58@TestApi
59public class ActivityView extends ViewGroup implements android.window.TaskEmbedder.Host {
60
61 private static final String TAG = "ActivityView";
62
63 private android.window.TaskEmbedder mTaskEmbedder;
64
65 private final SurfaceView mSurfaceView;
66 private final SurfaceCallback mSurfaceCallback;
67
68 private final CloseGuard mGuard = CloseGuard.get();
69 private boolean mOpened; // Protected by mGuard.
70
71 private final SurfaceControl.Transaction mTmpTransaction = new SurfaceControl.Transaction();
72
73 // For Host
74 private final Point mWindowPosition = new Point();
75 private final int[] mTmpArray = new int[2];
76 private final Rect mTmpRect = new Rect();
77 private final Matrix mScreenSurfaceMatrix = new Matrix();
78 private final Region mTapExcludeRegion = new Region();
79
80 public ActivityView(Context context) {
81 this(context, null /* attrs */);
82 }
83
84 public ActivityView(Context context, AttributeSet attrs) {
85 this(context, attrs, 0 /* defStyle */);
86 }
87
88 public ActivityView(Context context, AttributeSet attrs, int defStyle) {
89 this(context, attrs, defStyle, false /*singleTaskInstance*/);
90 }
91
92 public ActivityView(Context context, AttributeSet attrs, int defStyle,
93 boolean singleTaskInstance) {
94 this(context, attrs, defStyle, singleTaskInstance, false /* usePublicVirtualDisplay */);
95 }
96
97 /**
98 * This constructor let's the caller explicitly request a public virtual display as the backing
99 * display. Using a public display is not recommended as it exposes it to other applications,
100 * but it might be needed for backwards compatibility.
101 */
102 public ActivityView(
103 @NonNull Context context, @NonNull AttributeSet attrs, int defStyle,
104 boolean singleTaskInstance, boolean usePublicVirtualDisplay) {
105 super(context, attrs, defStyle);
106 if (useTaskOrganizer()) {
107 mTaskEmbedder = new TaskOrganizerTaskEmbedder(context, this);
108 } else {
109 mTaskEmbedder = new VirtualDisplayTaskEmbedder(context, this, singleTaskInstance,
110 usePublicVirtualDisplay);
111 }
112 mSurfaceView = new SurfaceView(context);
113 // Since ActivityView#getAlpha has been overridden, we should use parent class's alpha
114 // as master to synchronize surface view's alpha value.
115 mSurfaceView.setAlpha(super.getAlpha());
116 mSurfaceView.setUseAlpha();
117 mSurfaceCallback = new SurfaceCallback();
118 mSurfaceView.getHolder().addCallback(mSurfaceCallback);
119 addView(mSurfaceView);
120
121 mOpened = true;
122 mGuard.open("release");
123 }
124
125 /** Callback that notifies when the container is ready or destroyed. */
126 public abstract static class StateCallback {
127
128 /**
129 * Called when the container is ready for launching activities. Calling
130 * {@link #startActivity(Intent)} prior to this callback will result in an
131 * {@link IllegalStateException}.
132 *
133 * @see #startActivity(Intent)
134 */
135 public abstract void onActivityViewReady(ActivityView view);
136
137 /**
138 * Called when the container can no longer launch activities. Calling
139 * {@link #startActivity(Intent)} after this callback will result in an
140 * {@link IllegalStateException}.
141 *
142 * @see #startActivity(Intent)
143 */
144 public abstract void onActivityViewDestroyed(ActivityView view);
145
146 /**
147 * Called when a task is created inside the container.
148 * This is a filtered version of {@link TaskStackListener}
149 */
150 public void onTaskCreated(int taskId, ComponentName componentName) { }
151
152 /**
153 * Called when a task visibility changes.
154 * @hide
155 */
156 public void onTaskVisibilityChanged(int taskId, boolean visible) { }
157
158 /**
159 * Called when a task is moved to the front of the stack inside the container.
160 * This is a filtered version of {@link TaskStackListener}
161 */
162 public void onTaskMovedToFront(int taskId) { }
163
164 /**
165 * Called when a task is about to be removed from the stack inside the container.
166 * This is a filtered version of {@link TaskStackListener}
167 */
168 public void onTaskRemovalStarted(int taskId) { }
169
170 /**
171 * Called when back is pressed on the root activity of the task.
172 * @hide
173 */
174 public void onBackPressedOnTaskRoot(int taskId) { }
175 }
176
177 /**
178 * Set the callback to be notified about state changes.
179 * <p>This class must finish initializing before {@link #startActivity(Intent)} can be called.
180 * <p>Note: If the instance was ready prior to this call being made, then
181 * {@link StateCallback#onActivityViewReady(ActivityView)} will be called from within
182 * this method call.
183 *
184 * @param callback The callback to report events to.
185 *
186 * @see StateCallback
187 * @see #startActivity(Intent)
188 */
189 public void setCallback(StateCallback callback) {
190 if (callback == null) {
191 mTaskEmbedder.setListener(null);
192 return;
193 }
194 mTaskEmbedder.setListener(new StateCallbackAdapter(callback));
195 }
196
197 /**
198 * Sets the corner radius for the Activity displayed here. The corners will be
199 * cropped from the window painted by the contained Activity.
200 *
201 * @param cornerRadius the radius for the corners, in pixels
202 * @hide
203 */
204 public void setCornerRadius(float cornerRadius) {
205 mSurfaceView.setCornerRadius(cornerRadius);
206 }
207
208 /**
209 * @hide
210 */
211 public float getCornerRadius() {
212 return mSurfaceView.getCornerRadius();
213 }
214
215 /**
216 * Control whether the surface is clipped to the same bounds as the View. If true, then
217 * the bounds set by {@link #setSurfaceClipBounds(Rect)} are applied to the surface as
218 * window-crop.
219 *
220 * @param clippingEnabled whether to enable surface clipping
221 * @hide
222 */
223 public void setSurfaceClippingEnabled(boolean clippingEnabled) {
224 mSurfaceView.setEnableSurfaceClipping(clippingEnabled);
225 }
226
227 /**
228 * Sets an area on the contained surface to which it will be clipped
229 * when it is drawn. Setting the value to null will remove the clip bounds
230 * and the surface will draw normally, using its full bounds.
231 *
232 * @param clipBounds The rectangular area, in the local coordinates of
233 * this view, to which future drawing operations will be clipped.
234 * @hide
235 */
236 public void setSurfaceClipBounds(Rect clipBounds) {
237 mSurfaceView.setClipBounds(clipBounds);
238 }
239
240 /**
241 * @hide
242 */
243 public boolean getSurfaceClipBounds(Rect outRect) {
244 return mSurfaceView.getClipBounds(outRect);
245 }
246
247 /**
248 * Launch an activity represented by {@link ShortcutInfo} into this container.
249 * <p>The owner of this container must be allowed to access the shortcut information,
250 * as defined in {@link LauncherApps#hasShortcutHostPermission()} to use this method.
251 * <p>Activity resolved by the provided {@link ShortcutInfo} 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 shortcut the shortcut used to launch the activity.
260 * @param options for the activity.
261 * @param sourceBounds the rect containing the source bounds of the clicked icon to open
262 * this shortcut.
263 * @see StateCallback
264 * @see LauncherApps#startShortcut(ShortcutInfo, Rect, Bundle)
265 *
266 * @hide
267 */
268 public void startShortcutActivity(@NonNull ShortcutInfo shortcut,
269 @NonNull ActivityOptions options, @Nullable Rect sourceBounds) {
270 mTaskEmbedder.startShortcutActivity(shortcut, options, sourceBounds);
271 }
272
273 /**
274 * Launch a new activity into this container.
275 * <p>Activity resolved by the provided {@link Intent} must have
276 * {@link android.R.attr#resizeableActivity} attribute set to {@code true} in order to be
277 * launched here. Also, if activity is not owned by the owner of this container, it must allow
278 * embedding and the caller must have permission to embed.
279 * <p>Note: This class must finish initializing and
280 * {@link StateCallback#onActivityViewReady(ActivityView)} callback must be triggered before
281 * this method can be called.
282 *
283 * @param intent Intent used to launch an activity.
284 *
285 * @see StateCallback
286 * @see #startActivity(PendingIntent)
287 */
288 public void startActivity(@NonNull Intent intent) {
289 mTaskEmbedder.startActivity(intent);
290 }
291
292 /**
293 * Launch a new activity into this container.
294 * <p>Activity resolved by the provided {@link Intent} must have
295 * {@link android.R.attr#resizeableActivity} attribute set to {@code true} in order to be
296 * launched here. Also, if activity is not owned by the owner of this container, it must allow
297 * embedding and the caller must have permission to embed.
298 * <p>Note: This class must finish initializing and
299 * {@link StateCallback#onActivityViewReady(ActivityView)} callback must be triggered before
300 * this method can be called.
301 *
302 * @param intent Intent used to launch an activity.
303 * @param user The UserHandle of the user to start this activity for.
304 *
305 *
306 * @see StateCallback
307 * @see #startActivity(PendingIntent)
308 */
309 public void startActivity(@NonNull Intent intent, UserHandle user) {
310 mTaskEmbedder.startActivity(intent, user);
311 }
312
313 /**
314 * Launch a new activity into this container.
315 * <p>Activity resolved by the provided {@link PendingIntent} must have
316 * {@link android.R.attr#resizeableActivity} attribute set to {@code true} in order to be
317 * launched here. Also, if activity is not owned by the owner of this container, it must allow
318 * embedding and the caller must have permission to embed.
319 * <p>Note: This class must finish initializing and
320 * {@link StateCallback#onActivityViewReady(ActivityView)} callback must be triggered before
321 * this method can be called.
322 *
323 * @param pendingIntent Intent used to launch an activity.
324 *
325 * @see StateCallback
326 * @see #startActivity(Intent)
327 */
328 public void startActivity(@NonNull PendingIntent pendingIntent) {
329 mTaskEmbedder.startActivity(pendingIntent);
330 }
331
332 /**
333 * Launch a new activity into this container.
334 * <p>Activity resolved by the provided {@link PendingIntent} must have
335 * {@link android.R.attr#resizeableActivity} attribute set to {@code true} in order to be
336 * launched here. Also, if activity is not owned by the owner of this container, it must allow
337 * embedding and the caller must have permission to embed.
338 * <p>Note: This class must finish initializing and
339 * {@link StateCallback#onActivityViewReady(ActivityView)} callback must be triggered before
340 * this method can be called.
341 *
342 * @param pendingIntent Intent used to launch an activity.
343 * @param fillInIntent Additional Intent data, see {@link Intent#fillIn Intent.fillIn()}.
344 * @param options options for the activity
345 *
346 * @see StateCallback
347 * @see #startActivity(Intent)
348 */
349 public void startActivity(@NonNull PendingIntent pendingIntent, @Nullable Intent fillInIntent,
350 @NonNull ActivityOptions options) {
351 mTaskEmbedder.startActivity(pendingIntent, fillInIntent, options);
352 }
353
354 /**
355 * Release this container. Activity launching will no longer be permitted.
356 * <p>Note: Calling this method is allowed after
357 * {@link StateCallback#onActivityViewReady(ActivityView)} callback was triggered and before
358 * {@link StateCallback#onActivityViewDestroyed(ActivityView)}.
359 *
360 * @see StateCallback
361 */
362 public void release() {
363 if (!mTaskEmbedder.isInitialized()) {
364 throw new IllegalStateException(
365 "Trying to release container that is not initialized.");
366 }
367 performRelease();
368 }
369
370 /**
371 * Triggers an update of {@link ActivityView}'s location in window to properly set tap exclude
372 * regions and avoid focus switches by touches on this view.
373 */
374 public void onLocationChanged() {
375 mTaskEmbedder.notifyBoundsChanged();
376 }
377
378 @Override
379 public void onLayout(boolean changed, int l, int t, int r, int b) {
380 mSurfaceView.layout(0 /* left */, 0 /* top */, r - l /* right */, b - t /* bottom */);
381 }
382
383 /**
384 * Sets the alpha value when the content of {@link SurfaceView} needs to show or hide.
385 * <p>Note: The surface view may ignore the alpha value in some cases. Refer to
386 * {@link SurfaceView#setAlpha} for more details.
387 *
388 * @param alpha The opacity of the view.
389 */
390 @Override
391 public void setAlpha(float alpha) {
392 super.setAlpha(alpha);
393
394 if (mSurfaceView != null) {
395 mSurfaceView.setAlpha(alpha);
396 }
397 }
398
399 @Override
400 public float getAlpha() {
401 return mSurfaceView.getAlpha();
402 }
403
404 @Override
405 public boolean gatherTransparentRegion(Region region) {
406 return mTaskEmbedder.gatherTransparentRegion(region)
407 || super.gatherTransparentRegion(region);
408 }
409
410 private class SurfaceCallback implements SurfaceHolder.Callback {
411 @Override
412 public void surfaceCreated(SurfaceHolder surfaceHolder) {
413 if (!mTaskEmbedder.isInitialized()) {
414 initTaskEmbedder(mSurfaceView.getSurfaceControl());
415 } else {
416 mTmpTransaction.reparent(mTaskEmbedder.getSurfaceControl(),
417 mSurfaceView.getSurfaceControl()).apply();
418 }
419 mTaskEmbedder.start();
420 }
421
422 @Override
423 public void surfaceChanged(SurfaceHolder surfaceHolder, int format, int width, int height) {
424 mTaskEmbedder.resizeTask(width, height);
425 mTaskEmbedder.notifyBoundsChanged();
426 }
427
428 @Override
429 public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
430 mTaskEmbedder.stop();
431 }
432 }
433
434 @Override
435 protected void onVisibilityChanged(View changedView, int visibility) {
436 super.onVisibilityChanged(changedView, visibility);
437 mSurfaceView.setVisibility(visibility);
438 }
439
440 /**
441 * @return the display id of the virtual display.
442 */
443 public int getVirtualDisplayId() {
444 return mTaskEmbedder.getDisplayId();
445 }
446
447 /**
448 * @hide
449 * @return virtual display.
450 */
451 public VirtualDisplay getVirtualDisplay() {
452 return mTaskEmbedder.getVirtualDisplay();
453 }
454
455 /**
456 * Injects a pair of down/up key events with keycode {@link KeyEvent#KEYCODE_BACK} to the
457 * virtual display.
458 */
459 public void performBackPress() {
460 mTaskEmbedder.performBackPress();
461 }
462
463 /**
464 * Initializes the task embedder.
465 *
466 * @param parent control for the surface to parent to
467 * @return true if the task embedder has been initialized
468 */
469 private boolean initTaskEmbedder(SurfaceControl parent) {
470 if (!mTaskEmbedder.initialize(parent)) {
471 Log.e(TAG, "Failed to initialize ActivityView");
472 return false;
473 }
474 return true;
475 }
476
477 private void performRelease() {
478 if (!mOpened) {
479 return;
480 }
481 mSurfaceView.getHolder().removeCallback(mSurfaceCallback);
482 mTaskEmbedder.release();
483 mTaskEmbedder.setListener(null);
484
485 mGuard.close();
486 mOpened = false;
487 }
488
489 @Override
490 protected void finalize() throws Throwable {
491 try {
492 if (mGuard != null) {
493 mGuard.warnIfOpen();
494 performRelease();
495 }
496 } finally {
497 super.finalize();
498 }
499 }
500
501 /**
502 * Set forwarded insets on the virtual display.
503 *
504 * @see IWindowManager#setForwardedInsets
505 */
506 public void setForwardedInsets(Insets insets) {
507 mTaskEmbedder.setForwardedInsets(insets);
508 }
509
510 // Host
511
512 /** @hide */
513 @Override
514 public void onTaskBackgroundColorChanged(android.window.TaskEmbedder ts, int bgColor) {
515 if (mSurfaceView != null) {
516 mSurfaceView.setResizeBackgroundColor(bgColor);
517 }
518 }
519
520 /** @hide */
521 @Override
522 public Region getTapExcludeRegion() {
523 if (isAttachedToWindow() && canReceivePointerEvents()) {
524 Point windowPos = getPositionInWindow();
525 mTapExcludeRegion.set(
526 windowPos.x,
527 windowPos.y,
528 windowPos.x + getWidth(),
529 windowPos.y + getHeight());
530 // There might be views on top of us. We need to subtract those areas from the tap
531 // exclude region.
532 final ViewParent parent = getParent();
533 if (parent != null) {
534 parent.subtractObscuredTouchableRegion(mTapExcludeRegion, this);
535 }
536 } else {
537 mTapExcludeRegion.setEmpty();
538 }
539 return mTapExcludeRegion;
540 }
541
542 /** @hide */
543 @Override
544 public Matrix getScreenToTaskMatrix() {
545 getLocationOnScreen(mTmpArray);
546 mScreenSurfaceMatrix.set(getMatrix());
547 mScreenSurfaceMatrix.postTranslate(mTmpArray[0], mTmpArray[1]);
548 return mScreenSurfaceMatrix;
549 }
550
551 /** @hide */
552 @Override
553 public Point getPositionInWindow() {
554 getLocationInWindow(mTmpArray);
555 mWindowPosition.set(mTmpArray[0], mTmpArray[1]);
556 return mWindowPosition;
557 }
558
559 /** @hide */
560 @Override
561 public Rect getScreenBounds() {
562 getBoundsOnScreen(mTmpRect);
563 return mTmpRect;
564 }
565
566 /** @hide */
567 @Override
568 public IWindow getWindow() {
569 return super.getWindow();
570 }
571
572 /** @hide */
573 @Override
574 public boolean canReceivePointerEvents() {
575 return super.canReceivePointerEvents();
576 }
577
578 /**
579 * Overridden by instances that require the use of the task organizer implementation instead of
580 * the virtual display implementation. Not for general use.
581 * @hide
582 */
583 protected boolean useTaskOrganizer() {
584 return false;
585 }
586
587 private final class StateCallbackAdapter implements TaskEmbedder.Listener {
588 private final StateCallback mCallback;
589
590 private StateCallbackAdapter(ActivityView.StateCallback cb) {
591 mCallback = cb;
592 }
593
594 @Override
595 public void onInitialized() {
596 mCallback.onActivityViewReady(ActivityView.this);
597 }
598
599 @Override
600 public void onReleased() {
601 mCallback.onActivityViewDestroyed(ActivityView.this);
602 }
603
604 @Override
605 public void onTaskCreated(int taskId, ComponentName name) {
606 mCallback.onTaskCreated(taskId, name);
607 }
608
609 @Override
610 public void onTaskVisibilityChanged(int taskId, boolean visible) {
611 mCallback.onTaskVisibilityChanged(taskId, visible);
612 }
613
614 @Override
615 public void onTaskMovedToFront(int taskId) {
616 mCallback.onTaskMovedToFront(taskId);
617 }
618
619 @Override
620 public void onTaskRemovalStarted(int taskId) {
621 mCallback.onTaskRemovalStarted(taskId);
622 }
623
624 @Override
625 public void onBackPressedOnTaskRoot(int taskId) {
626 mCallback.onBackPressedOnTaskRoot(taskId);
627 }
628 }
629}