blob: 635ed138cc65ece9dfbf0af5556f3f12ea273f50 [file] [log] [blame]
/**
* Copyright (c) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.app;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.TestApi;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.LauncherApps;
import android.content.pm.ShortcutInfo;
import android.graphics.Insets;
import android.graphics.Matrix;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.Region;
import android.hardware.display.VirtualDisplay;
import android.os.Bundle;
import android.os.UserHandle;
import android.util.AttributeSet;
import android.util.Log;
import android.view.IWindow;
import android.view.IWindowManager;
import android.view.KeyEvent;
import android.view.SurfaceControl;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.window.TaskEmbedder;
import android.window.TaskOrganizerTaskEmbedder;
import android.window.VirtualDisplayTaskEmbedder;
import dalvik.system.CloseGuard;
/**
* Task container that allows launching activities into itself.
* <p>Activity launching into this container is restricted by the same rules that apply to launching
* on VirtualDisplays.
* @hide
*/
@TestApi
public class ActivityView extends ViewGroup implements android.window.TaskEmbedder.Host {
private static final String TAG = "ActivityView";
private android.window.TaskEmbedder mTaskEmbedder;
private final SurfaceView mSurfaceView;
private final SurfaceCallback mSurfaceCallback;
private final CloseGuard mGuard = CloseGuard.get();
private boolean mOpened; // Protected by mGuard.
private final SurfaceControl.Transaction mTmpTransaction = new SurfaceControl.Transaction();
// For Host
private final Point mWindowPosition = new Point();
private final int[] mTmpArray = new int[2];
private final Rect mTmpRect = new Rect();
private final Matrix mScreenSurfaceMatrix = new Matrix();
private final Region mTapExcludeRegion = new Region();
public ActivityView(Context context) {
this(context, null /* attrs */);
}
public ActivityView(Context context, AttributeSet attrs) {
this(context, attrs, 0 /* defStyle */);
}
public ActivityView(Context context, AttributeSet attrs, int defStyle) {
this(context, attrs, defStyle, false /*singleTaskInstance*/);
}
public ActivityView(Context context, AttributeSet attrs, int defStyle,
boolean singleTaskInstance) {
this(context, attrs, defStyle, singleTaskInstance, false /* usePublicVirtualDisplay */);
}
/**
* This constructor let's the caller explicitly request a public virtual display as the backing
* display. Using a public display is not recommended as it exposes it to other applications,
* but it might be needed for backwards compatibility.
*/
public ActivityView(
@NonNull Context context, @NonNull AttributeSet attrs, int defStyle,
boolean singleTaskInstance, boolean usePublicVirtualDisplay) {
super(context, attrs, defStyle);
if (useTaskOrganizer()) {
mTaskEmbedder = new TaskOrganizerTaskEmbedder(context, this);
} else {
mTaskEmbedder = new VirtualDisplayTaskEmbedder(context, this, singleTaskInstance,
usePublicVirtualDisplay);
}
mSurfaceView = new SurfaceView(context);
// Since ActivityView#getAlpha has been overridden, we should use parent class's alpha
// as master to synchronize surface view's alpha value.
mSurfaceView.setAlpha(super.getAlpha());
mSurfaceView.setUseAlpha();
mSurfaceCallback = new SurfaceCallback();
mSurfaceView.getHolder().addCallback(mSurfaceCallback);
addView(mSurfaceView);
mOpened = true;
mGuard.open("release");
}
/** Callback that notifies when the container is ready or destroyed. */
public abstract static class StateCallback {
/**
* Called when the container is ready for launching activities. Calling
* {@link #startActivity(Intent)} prior to this callback will result in an
* {@link IllegalStateException}.
*
* @see #startActivity(Intent)
*/
public abstract void onActivityViewReady(ActivityView view);
/**
* Called when the container can no longer launch activities. Calling
* {@link #startActivity(Intent)} after this callback will result in an
* {@link IllegalStateException}.
*
* @see #startActivity(Intent)
*/
public abstract void onActivityViewDestroyed(ActivityView view);
/**
* Called when a task is created inside the container.
* This is a filtered version of {@link TaskStackListener}
*/
public void onTaskCreated(int taskId, ComponentName componentName) { }
/**
* Called when a task visibility changes.
* @hide
*/
public void onTaskVisibilityChanged(int taskId, boolean visible) { }
/**
* Called when a task is moved to the front of the stack inside the container.
* This is a filtered version of {@link TaskStackListener}
*/
public void onTaskMovedToFront(int taskId) { }
/**
* Called when a task is about to be removed from the stack inside the container.
* This is a filtered version of {@link TaskStackListener}
*/
public void onTaskRemovalStarted(int taskId) { }
/**
* Called when back is pressed on the root activity of the task.
* @hide
*/
public void onBackPressedOnTaskRoot(int taskId) { }
}
/**
* Set the callback to be notified about state changes.
* <p>This class must finish initializing before {@link #startActivity(Intent)} can be called.
* <p>Note: If the instance was ready prior to this call being made, then
* {@link StateCallback#onActivityViewReady(ActivityView)} will be called from within
* this method call.
*
* @param callback The callback to report events to.
*
* @see StateCallback
* @see #startActivity(Intent)
*/
public void setCallback(StateCallback callback) {
if (callback == null) {
mTaskEmbedder.setListener(null);
return;
}
mTaskEmbedder.setListener(new StateCallbackAdapter(callback));
}
/**
* Sets the corner radius for the Activity displayed here. The corners will be
* cropped from the window painted by the contained Activity.
*
* @param cornerRadius the radius for the corners, in pixels
* @hide
*/
public void setCornerRadius(float cornerRadius) {
mSurfaceView.setCornerRadius(cornerRadius);
}
/**
* @hide
*/
public float getCornerRadius() {
return mSurfaceView.getCornerRadius();
}
/**
* Control whether the surface is clipped to the same bounds as the View. If true, then
* the bounds set by {@link #setSurfaceClipBounds(Rect)} are applied to the surface as
* window-crop.
*
* @param clippingEnabled whether to enable surface clipping
* @hide
*/
public void setSurfaceClippingEnabled(boolean clippingEnabled) {
mSurfaceView.setEnableSurfaceClipping(clippingEnabled);
}
/**
* Sets an area on the contained surface to which it will be clipped
* when it is drawn. Setting the value to null will remove the clip bounds
* and the surface will draw normally, using its full bounds.
*
* @param clipBounds The rectangular area, in the local coordinates of
* this view, to which future drawing operations will be clipped.
* @hide
*/
public void setSurfaceClipBounds(Rect clipBounds) {
mSurfaceView.setClipBounds(clipBounds);
}
/**
* @hide
*/
public boolean getSurfaceClipBounds(Rect outRect) {
return mSurfaceView.getClipBounds(outRect);
}
/**
* Launch an activity represented by {@link ShortcutInfo} into this container.
* <p>The owner of this container must be allowed to access the shortcut information,
* as defined in {@link LauncherApps#hasShortcutHostPermission()} to use this method.
* <p>Activity resolved by the provided {@link ShortcutInfo} must have
* {@link android.R.attr#resizeableActivity} attribute set to {@code true} in order to be
* launched here. Also, if activity is not owned by the owner of this container, it must allow
* embedding and the caller must have permission to embed.
* <p>Note: This class must finish initializing and
* {@link StateCallback#onActivityViewReady(ActivityView)} callback must be triggered before
* this method can be called.
*
* @param shortcut the shortcut used to launch the activity.
* @param options for the activity.
* @param sourceBounds the rect containing the source bounds of the clicked icon to open
* this shortcut.
* @see StateCallback
* @see LauncherApps#startShortcut(ShortcutInfo, Rect, Bundle)
*
* @hide
*/
public void startShortcutActivity(@NonNull ShortcutInfo shortcut,
@NonNull ActivityOptions options, @Nullable Rect sourceBounds) {
mTaskEmbedder.startShortcutActivity(shortcut, options, sourceBounds);
}
/**
* Launch a new activity into this container.
* <p>Activity resolved by the provided {@link Intent} must have
* {@link android.R.attr#resizeableActivity} attribute set to {@code true} in order to be
* launched here. Also, if activity is not owned by the owner of this container, it must allow
* embedding and the caller must have permission to embed.
* <p>Note: This class must finish initializing and
* {@link StateCallback#onActivityViewReady(ActivityView)} callback must be triggered before
* this method can be called.
*
* @param intent Intent used to launch an activity.
*
* @see StateCallback
* @see #startActivity(PendingIntent)
*/
public void startActivity(@NonNull Intent intent) {
mTaskEmbedder.startActivity(intent);
}
/**
* Launch a new activity into this container.
* <p>Activity resolved by the provided {@link Intent} must have
* {@link android.R.attr#resizeableActivity} attribute set to {@code true} in order to be
* launched here. Also, if activity is not owned by the owner of this container, it must allow
* embedding and the caller must have permission to embed.
* <p>Note: This class must finish initializing and
* {@link StateCallback#onActivityViewReady(ActivityView)} callback must be triggered before
* this method can be called.
*
* @param intent Intent used to launch an activity.
* @param user The UserHandle of the user to start this activity for.
*
*
* @see StateCallback
* @see #startActivity(PendingIntent)
*/
public void startActivity(@NonNull Intent intent, UserHandle user) {
mTaskEmbedder.startActivity(intent, user);
}
/**
* Launch a new activity into this container.
* <p>Activity resolved by the provided {@link PendingIntent} must have
* {@link android.R.attr#resizeableActivity} attribute set to {@code true} in order to be
* launched here. Also, if activity is not owned by the owner of this container, it must allow
* embedding and the caller must have permission to embed.
* <p>Note: This class must finish initializing and
* {@link StateCallback#onActivityViewReady(ActivityView)} callback must be triggered before
* this method can be called.
*
* @param pendingIntent Intent used to launch an activity.
*
* @see StateCallback
* @see #startActivity(Intent)
*/
public void startActivity(@NonNull PendingIntent pendingIntent) {
mTaskEmbedder.startActivity(pendingIntent);
}
/**
* Launch a new activity into this container.
* <p>Activity resolved by the provided {@link PendingIntent} must have
* {@link android.R.attr#resizeableActivity} attribute set to {@code true} in order to be
* launched here. Also, if activity is not owned by the owner of this container, it must allow
* embedding and the caller must have permission to embed.
* <p>Note: This class must finish initializing and
* {@link StateCallback#onActivityViewReady(ActivityView)} callback must be triggered before
* this method can be called.
*
* @param pendingIntent Intent used to launch an activity.
* @param fillInIntent Additional Intent data, see {@link Intent#fillIn Intent.fillIn()}.
* @param options options for the activity
*
* @see StateCallback
* @see #startActivity(Intent)
*/
public void startActivity(@NonNull PendingIntent pendingIntent, @Nullable Intent fillInIntent,
@NonNull ActivityOptions options) {
mTaskEmbedder.startActivity(pendingIntent, fillInIntent, options);
}
/**
* Release this container. Activity launching will no longer be permitted.
* <p>Note: Calling this method is allowed after
* {@link StateCallback#onActivityViewReady(ActivityView)} callback was triggered and before
* {@link StateCallback#onActivityViewDestroyed(ActivityView)}.
*
* @see StateCallback
*/
public void release() {
if (!mTaskEmbedder.isInitialized()) {
throw new IllegalStateException(
"Trying to release container that is not initialized.");
}
performRelease();
}
/**
* Triggers an update of {@link ActivityView}'s location in window to properly set tap exclude
* regions and avoid focus switches by touches on this view.
*/
public void onLocationChanged() {
mTaskEmbedder.notifyBoundsChanged();
}
@Override
public void onLayout(boolean changed, int l, int t, int r, int b) {
mSurfaceView.layout(0 /* left */, 0 /* top */, r - l /* right */, b - t /* bottom */);
}
/**
* Sets the alpha value when the content of {@link SurfaceView} needs to show or hide.
* <p>Note: The surface view may ignore the alpha value in some cases. Refer to
* {@link SurfaceView#setAlpha} for more details.
*
* @param alpha The opacity of the view.
*/
@Override
public void setAlpha(float alpha) {
super.setAlpha(alpha);
if (mSurfaceView != null) {
mSurfaceView.setAlpha(alpha);
}
}
@Override
public float getAlpha() {
return mSurfaceView.getAlpha();
}
@Override
public boolean gatherTransparentRegion(Region region) {
return mTaskEmbedder.gatherTransparentRegion(region)
|| super.gatherTransparentRegion(region);
}
private class SurfaceCallback implements SurfaceHolder.Callback {
@Override
public void surfaceCreated(SurfaceHolder surfaceHolder) {
if (!mTaskEmbedder.isInitialized()) {
initTaskEmbedder(mSurfaceView.getSurfaceControl());
} else {
mTmpTransaction.reparent(mTaskEmbedder.getSurfaceControl(),
mSurfaceView.getSurfaceControl()).apply();
}
mTaskEmbedder.start();
}
@Override
public void surfaceChanged(SurfaceHolder surfaceHolder, int format, int width, int height) {
mTaskEmbedder.resizeTask(width, height);
mTaskEmbedder.notifyBoundsChanged();
}
@Override
public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
mTaskEmbedder.stop();
}
}
@Override
protected void onVisibilityChanged(View changedView, int visibility) {
super.onVisibilityChanged(changedView, visibility);
mSurfaceView.setVisibility(visibility);
}
/**
* @return the display id of the virtual display.
*/
public int getVirtualDisplayId() {
return mTaskEmbedder.getDisplayId();
}
/**
* @hide
* @return virtual display.
*/
public VirtualDisplay getVirtualDisplay() {
return mTaskEmbedder.getVirtualDisplay();
}
/**
* Injects a pair of down/up key events with keycode {@link KeyEvent#KEYCODE_BACK} to the
* virtual display.
*/
public void performBackPress() {
mTaskEmbedder.performBackPress();
}
/**
* Initializes the task embedder.
*
* @param parent control for the surface to parent to
* @return true if the task embedder has been initialized
*/
private boolean initTaskEmbedder(SurfaceControl parent) {
if (!mTaskEmbedder.initialize(parent)) {
Log.e(TAG, "Failed to initialize ActivityView");
return false;
}
return true;
}
private void performRelease() {
if (!mOpened) {
return;
}
mSurfaceView.getHolder().removeCallback(mSurfaceCallback);
mTaskEmbedder.release();
mTaskEmbedder.setListener(null);
mGuard.close();
mOpened = false;
}
@Override
protected void finalize() throws Throwable {
try {
if (mGuard != null) {
mGuard.warnIfOpen();
performRelease();
}
} finally {
super.finalize();
}
}
/**
* Set forwarded insets on the virtual display.
*
* @see IWindowManager#setForwardedInsets
*/
public void setForwardedInsets(Insets insets) {
mTaskEmbedder.setForwardedInsets(insets);
}
// Host
/** @hide */
@Override
public void onTaskBackgroundColorChanged(android.window.TaskEmbedder ts, int bgColor) {
if (mSurfaceView != null) {
mSurfaceView.setResizeBackgroundColor(bgColor);
}
}
/** @hide */
@Override
public Region getTapExcludeRegion() {
if (isAttachedToWindow() && canReceivePointerEvents()) {
Point windowPos = getPositionInWindow();
mTapExcludeRegion.set(
windowPos.x,
windowPos.y,
windowPos.x + getWidth(),
windowPos.y + getHeight());
// There might be views on top of us. We need to subtract those areas from the tap
// exclude region.
final ViewParent parent = getParent();
if (parent != null) {
parent.subtractObscuredTouchableRegion(mTapExcludeRegion, this);
}
} else {
mTapExcludeRegion.setEmpty();
}
return mTapExcludeRegion;
}
/** @hide */
@Override
public Matrix getScreenToTaskMatrix() {
getLocationOnScreen(mTmpArray);
mScreenSurfaceMatrix.set(getMatrix());
mScreenSurfaceMatrix.postTranslate(mTmpArray[0], mTmpArray[1]);
return mScreenSurfaceMatrix;
}
/** @hide */
@Override
public Point getPositionInWindow() {
getLocationInWindow(mTmpArray);
mWindowPosition.set(mTmpArray[0], mTmpArray[1]);
return mWindowPosition;
}
/** @hide */
@Override
public Rect getScreenBounds() {
getBoundsOnScreen(mTmpRect);
return mTmpRect;
}
/** @hide */
@Override
public IWindow getWindow() {
return super.getWindow();
}
/** @hide */
@Override
public boolean canReceivePointerEvents() {
return super.canReceivePointerEvents();
}
/**
* Overridden by instances that require the use of the task organizer implementation instead of
* the virtual display implementation. Not for general use.
* @hide
*/
protected boolean useTaskOrganizer() {
return false;
}
private final class StateCallbackAdapter implements TaskEmbedder.Listener {
private final StateCallback mCallback;
private StateCallbackAdapter(ActivityView.StateCallback cb) {
mCallback = cb;
}
@Override
public void onInitialized() {
mCallback.onActivityViewReady(ActivityView.this);
}
@Override
public void onReleased() {
mCallback.onActivityViewDestroyed(ActivityView.this);
}
@Override
public void onTaskCreated(int taskId, ComponentName name) {
mCallback.onTaskCreated(taskId, name);
}
@Override
public void onTaskVisibilityChanged(int taskId, boolean visible) {
mCallback.onTaskVisibilityChanged(taskId, visible);
}
@Override
public void onTaskMovedToFront(int taskId) {
mCallback.onTaskMovedToFront(taskId);
}
@Override
public void onTaskRemovalStarted(int taskId) {
mCallback.onTaskRemovalStarted(taskId);
}
@Override
public void onBackPressedOnTaskRoot(int taskId) {
mCallback.onBackPressedOnTaskRoot(taskId);
}
}
}