/** * 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. *
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) { this(context, attrs, defStyle, singleTaskInstance, usePublicVirtualDisplay, false); } /** @hide */ public ActivityView( @NonNull Context context, @NonNull AttributeSet attrs, int defStyle, boolean singleTaskInstance, boolean usePublicVirtualDisplay, boolean disableSurfaceViewBackgroundLayer) { super(context, attrs, defStyle); if (useTaskOrganizer()) { mTaskEmbedder = new TaskOrganizerTaskEmbedder(context, this); } else { mTaskEmbedder = new VirtualDisplayTaskEmbedder(context, this, singleTaskInstance, usePublicVirtualDisplay); } mSurfaceView = new SurfaceView(context, null, 0, 0, disableSurfaceViewBackgroundLayer); // 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. *
This class must finish initializing before {@link #startActivity(Intent)} can be called. *
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. *
The owner of this container must be allowed to access the shortcut information, * as defined in {@link LauncherApps#hasShortcutHostPermission()} to use this method. *
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. *
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. *
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. *
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. *
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. *
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. *
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. *
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. *
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. *
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. *
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. *
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); } } }