| /** |
| * 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.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 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 TaskEmbedder.Host { |
| |
| private static final String TAG = "ActivityView"; |
| |
| private 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 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) { |
| super(context, attrs, defStyle); |
| mTaskEmbedder = new TaskEmbedder(getContext(), this, singleTaskInstance); |
| 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 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) { } |
| } |
| |
| /** |
| * 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) { |
| // The tap exclude region may be affected by any view on top of it, so we detect the |
| // possible change by monitoring this function. |
| mTaskEmbedder.notifyBoundsChanged(); |
| return 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(); |
| } |
| |
| /** |
| * 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; |
| } |
| mTmpTransaction.show(mTaskEmbedder.getSurfaceControl()).apply(); |
| 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(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 IWindow getWindow() { |
| return super.getWindow(); |
| } |
| |
| /** @hide */ |
| @Override |
| public boolean canReceivePointerEvents() { |
| return super.canReceivePointerEvents(); |
| } |
| |
| 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 onTaskMovedToFront(int taskId) { |
| mCallback.onTaskMovedToFront(taskId); |
| } |
| |
| @Override |
| public void onTaskRemovalStarted(int taskId) { |
| mCallback.onTaskRemovalStarted(taskId); |
| } |
| } |
| } |