summaryrefslogtreecommitdiff
path: root/core/java/android/window/TaskEmbedder.java
diff options
context:
space:
mode:
authorWale Ogunwale <ogunwale@google.com>2020-03-27 16:36:01 -0700
committerWale Ogunwale <ogunwale@google.com>2020-04-01 13:25:23 +0000
commitadf116ec95f5fdbf827f1ce81979af578d1508c9 (patch)
tree7069c387f68325cfeb06f03b69ad119ec000523d /core/java/android/window/TaskEmbedder.java
parentdcea9181e55a01002253c1c7b4c54be5ad14d140 (diff)
Add TestApi interfaces for window organizers
Enables testing the API surfaces from CTS. Bug: 149338177 Test: they pass! Change-Id: I7e1f2852585a10c20d299bd87e9a87f828d06d6a
Diffstat (limited to 'core/java/android/window/TaskEmbedder.java')
-rw-r--r--core/java/android/window/TaskEmbedder.java452
1 files changed, 452 insertions, 0 deletions
diff --git a/core/java/android/window/TaskEmbedder.java b/core/java/android/window/TaskEmbedder.java
new file mode 100644
index 000000000000..45ab310c5148
--- /dev/null
+++ b/core/java/android/window/TaskEmbedder.java
@@ -0,0 +1,452 @@
+/*
+ * Copyright (C) 2020 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.window;
+
+import static android.view.Display.INVALID_DISPLAY;
+
+import android.annotation.CallSuper;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ActivityOptions;
+import android.app.ActivityTaskManager;
+import android.app.ActivityView;
+import android.app.IActivityTaskManager;
+import android.app.PendingIntent;
+import android.app.TaskStackListener;
+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.RemoteException;
+import android.os.UserHandle;
+import android.util.Log;
+import android.view.IWindow;
+import android.view.IWindowManager;
+import android.view.KeyEvent;
+import android.view.SurfaceControl;
+
+import dalvik.system.CloseGuard;
+
+/**
+ * A component which handles embedded display of tasks within another window. The embedded task can
+ * be presented using the SurfaceControl provided from {@link #getSurfaceControl()}.
+ *
+ * @hide
+ */
+public abstract class TaskEmbedder {
+ private static final String TAG = "TaskEmbedder";
+
+ /**
+ * A component which will host the task.
+ */
+ public interface Host {
+ /** @return the screen area where touches should be dispatched to the embedded Task */
+ Region getTapExcludeRegion();
+
+ /** @return a matrix which transforms from screen-space to the embedded task surface */
+ Matrix getScreenToTaskMatrix();
+
+ /** @return the window containing the parent surface, if attached and available */
+ @Nullable IWindow getWindow();
+
+ /** @return the x/y offset from the origin of the window to the surface */
+ Point getPositionInWindow();
+
+ /** @return the screen bounds of the host */
+ Rect getScreenBounds();
+
+ /** @return whether this surface is able to receive pointer events */
+ boolean canReceivePointerEvents();
+
+ /** @return the width of the container for the embedded task */
+ int getWidth();
+
+ /** @return the height of the container for the embedded task */
+ int getHeight();
+
+ /**
+ * Called to inform the host of the task's background color. This can be used to
+ * fill unpainted areas if necessary.
+ */
+ void onTaskBackgroundColorChanged(TaskEmbedder ts, int bgColor);
+
+ /**
+ * Posts a runnable to be run on the host's handler.
+ */
+ boolean post(Runnable r);
+ }
+
+ /**
+ * Describes changes to the state of the TaskEmbedder as well the tasks within.
+ */
+ public interface Listener {
+ /** Called when the container is ready for launching activities. */
+ default void onInitialized() {}
+
+ /** Called when the container can no longer launch activities. */
+ default void onReleased() {}
+
+ /** Called when a task is created inside the container. */
+ default void onTaskCreated(int taskId, ComponentName name) {}
+
+ /** Called when a task visibility changes. */
+ default void onTaskVisibilityChanged(int taskId, boolean visible) {}
+
+ /** Called when a task is moved to the front of the stack inside the container. */
+ default void onTaskMovedToFront(int taskId) {}
+
+ /** Called when a task is about to be removed from the stack inside the container. */
+ default void onTaskRemovalStarted(int taskId) {}
+
+ /** Called when a task is created inside the container. */
+ default void onBackPressedOnTaskRoot(int taskId) {}
+ }
+
+ protected IActivityTaskManager mActivityTaskManager = ActivityTaskManager.getService();
+
+ protected final Context mContext;
+ protected TaskEmbedder.Host mHost;
+
+ protected SurfaceControl.Transaction mTransaction;
+ protected SurfaceControl mSurfaceControl;
+ protected TaskStackListener mTaskStackListener;
+ protected Listener mListener;
+ protected boolean mOpened; // Protected by mGuard.
+
+ private final CloseGuard mGuard = CloseGuard.get();
+
+
+ /**
+ * Constructs a new TaskEmbedder.
+ *
+ * @param context the context
+ * @param host the host for this embedded task
+ */
+ public TaskEmbedder(Context context, TaskEmbedder.Host host) {
+ mContext = context;
+ mHost = host;
+ }
+
+ /**
+ * Initialize this container when the ActivityView's SurfaceView is first created.
+ *
+ * @param parent the surface control for the parent surface
+ * @return true if initialized successfully
+ */
+ public boolean initialize(SurfaceControl parent) {
+ if (isInitialized()) {
+ throw new IllegalStateException("Trying to initialize for the second time.");
+ }
+
+ mTransaction = new SurfaceControl.Transaction();
+ // Create a container surface to which the task content will be reparented
+ final String name = "TaskEmbedder - " + Integer.toHexString(System.identityHashCode(this));
+ mSurfaceControl = new SurfaceControl.Builder()
+ .setContainerLayer()
+ .setParent(parent)
+ .setName(name)
+ .build();
+
+ if (!onInitialize()) {
+ return false;
+ }
+
+ mTaskStackListener = createTaskStackListener();
+ try {
+ mActivityTaskManager.registerTaskStackListener(mTaskStackListener);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to register task stack listener", e);
+ }
+ if (mListener != null && isInitialized()) {
+ mListener.onInitialized();
+ }
+ mOpened = true;
+ mGuard.open("release");
+ mTransaction.show(getSurfaceControl()).apply();
+ return true;
+ }
+
+ /**
+ * @return the task stack listener for this embedder
+ */
+ public abstract TaskStackListener createTaskStackListener();
+
+ /**
+ * Whether this container has been initialized.
+ *
+ * @return true if initialized
+ */
+ public abstract boolean isInitialized();
+
+ /**
+ * Called when the task embedder should be initialized.
+ * @return whether to report whether the embedder was initialized.
+ */
+ public abstract boolean onInitialize();
+
+ /**
+ * Called when the task embedder should be released.
+ * @return whether to report whether the embedder was released.
+ */
+ protected abstract boolean onRelease();
+
+ /**
+ * Starts presentation of tasks in this container.
+ */
+ public abstract void start();
+
+ /**
+ * Stops presentation of tasks in this container.
+ */
+ public abstract void stop();
+
+ /**
+ * This should be called whenever the position or size of the surface changes
+ * or if touchable areas above the surface are added or removed.
+ */
+ public abstract void notifyBoundsChanged();
+
+ /**
+ * Called to update the dimensions whenever the host size changes.
+ *
+ * @param width the new width of the surface
+ * @param height the new height of the surface
+ */
+ public void resizeTask(int width, int height) {
+ // Do nothing
+ }
+
+ /**
+ * Injects a pair of down/up key events with keycode {@link KeyEvent#KEYCODE_BACK} to the
+ * virtual display.
+ */
+ public abstract void performBackPress();
+
+ /**
+ * An opaque unique identifier for this task surface among others being managed by the app.
+ */
+ public abstract int getId();
+
+ /**
+ * Calculates and updates the {@param region} with the transparent region for this task
+ * embedder.
+ */
+ public boolean gatherTransparentRegion(Region region) {
+ // Do nothing
+ return false;
+ }
+
+ /**
+ * Returns the surface control for the task surface. This should be parented to a screen
+ * surface for display/embedding purposes.
+ *
+ * @return the surface control for the task
+ */
+ public SurfaceControl getSurfaceControl() {
+ return mSurfaceControl;
+ }
+
+ public int getDisplayId() {
+ return INVALID_DISPLAY;
+ }
+
+ /**
+ * Set forwarded insets on the task content.
+ *
+ * @see IWindowManager#setForwardedInsets
+ */
+ public void setForwardedInsets(Insets insets) {
+ // Do nothing
+ }
+
+ /**
+ * 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 Listener#onInitialized()} will be called from within this method call.
+ *
+ * @param listener The listener to report events to.
+ *
+ * @see ActivityView.StateCallback
+ * @see #startActivity(Intent)
+ */
+ public void setListener(TaskEmbedder.Listener listener) {
+ mListener = listener;
+ if (mListener != null && isInitialized()) {
+ mListener.onInitialized();
+ }
+ }
+
+ /**
+ * Launch a new activity into this container.
+ *
+ * @param intent Intent used to launch an activity
+ *
+ * @see #startActivity(PendingIntent)
+ */
+ public void startActivity(@NonNull Intent intent) {
+ final ActivityOptions options = prepareActivityOptions(null);
+ mContext.startActivity(intent, options.toBundle());
+ }
+
+ /**
+ * Launch a new activity into this container.
+ *
+ * @param intent Intent used to launch an activity
+ * @param user The UserHandle of the user to start this activity for
+ *
+ * @see #startActivity(PendingIntent)
+ */
+ public void startActivity(@NonNull Intent intent, UserHandle user) {
+ final ActivityOptions options = prepareActivityOptions(null);
+ mContext.startActivityAsUser(intent, options.toBundle(), user);
+ }
+
+ /**
+ * Launch a new activity into this container.
+ *
+ * @param pendingIntent Intent used to launch an activity
+ *
+ * @see #startActivity(Intent)
+ */
+ public void startActivity(@NonNull PendingIntent pendingIntent) {
+ final ActivityOptions options = prepareActivityOptions(null);
+ try {
+ pendingIntent.send(null /* context */, 0 /* code */, null /* intent */,
+ null /* onFinished */, null /* handler */, null /* requiredPermission */,
+ options.toBundle());
+ } catch (PendingIntent.CanceledException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Launch a new activity into this container.
+ *
+ * @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 #startActivity(Intent)
+ */
+ public void startActivity(@NonNull PendingIntent pendingIntent, @Nullable Intent fillInIntent,
+ @NonNull ActivityOptions options) {
+ prepareActivityOptions(options);
+ try {
+ pendingIntent.send(mContext, 0 /* code */, fillInIntent,
+ null /* onFinished */, null /* handler */, null /* requiredPermission */,
+ options.toBundle());
+ } catch (PendingIntent.CanceledException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * 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.
+ *
+ * @param shortcut the shortcut used to launch the activity.
+ * @param options options for the activity.
+ * @param sourceBounds the rect containing the source bounds of the clicked icon to open
+ * this shortcut.
+ *
+ * @see #startActivity(Intent)
+ */
+ public void startShortcutActivity(@NonNull ShortcutInfo shortcut,
+ @NonNull ActivityOptions options, @Nullable Rect sourceBounds) {
+ LauncherApps service =
+ (LauncherApps) mContext.getSystemService(Context.LAUNCHER_APPS_SERVICE);
+ prepareActivityOptions(options);
+ service.startShortcut(shortcut, sourceBounds, options.toBundle());
+ }
+
+ /**
+ * Check if container is ready to launch and modify {@param options} to target the virtual
+ * display, creating them if necessary.
+ */
+ @CallSuper
+ protected ActivityOptions prepareActivityOptions(ActivityOptions options) {
+ if (!isInitialized()) {
+ throw new IllegalStateException(
+ "Trying to start activity before ActivityView is ready.");
+ }
+ if (options == null) {
+ options = ActivityOptions.makeBasic();
+ }
+ return options;
+ }
+
+ /**
+ * Releases the resources for this TaskEmbedder. Tasks will no longer be launchable
+ * within this container.
+ *
+ * <p>Note: Calling this method is allowed after {@link Listener#onInitialized()} callback is
+ * triggered and before {@link Listener#onReleased()}.
+ */
+ public void release() {
+ if (!isInitialized()) {
+ throw new IllegalStateException("Trying to release container that is not initialized.");
+ }
+ performRelease();
+ }
+
+ private boolean performRelease() {
+ if (!mOpened) {
+ return false;
+ }
+
+ mTransaction.reparent(mSurfaceControl, null).apply();
+ mSurfaceControl.release();
+
+ boolean reportReleased = onRelease();
+
+ if (mTaskStackListener != null) {
+ try {
+ mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to unregister task stack listener", e);
+ }
+ mTaskStackListener = null;
+ }
+
+ if (mListener != null && reportReleased) {
+ mListener.onReleased();
+ }
+ mOpened = false;
+ mGuard.close();
+ return true;
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ if (mGuard != null) {
+ mGuard.warnIfOpen();
+ performRelease();
+ }
+ } finally {
+ super.finalize();
+ }
+ }
+}