Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit a1f869d1 authored by Winson Chung's avatar Winson Chung
Browse files

Add task organizer based task embedder

- Split TaskEmbedder into its current VirtualDisplay implementation
  and an implementation that uses task org to create and manage
  the task
- Use the task org embedder implementation in separate bubble task view
- Skip task org tasks from triggering task resizing
- Add task org callback for back press on task root if requested

Bug: 148977538
Test: atest CtsWindowManagerDeviceTestCases:ActivityViewTest
Test: atest WmTests:TaskOrganizerTests
Change-Id: Id422bb2547197c617f914ed7cf5085e02a1c3fb5
parent 7873b387
Loading
Loading
Loading
Loading
+48 −8
Original line number Diff line number Diff line
@@ -69,6 +69,7 @@ public class ActivityView extends ViewGroup implements TaskEmbedder.Host {
    // 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();

@@ -84,10 +85,14 @@ public class ActivityView extends ViewGroup implements TaskEmbedder.Host {
        this(context, attrs, defStyle, false /*singleTaskInstance*/);
    }

    public ActivityView(
            Context context, AttributeSet attrs, int defStyle, boolean singleTaskInstance) {
    public ActivityView(Context context, AttributeSet attrs, int defStyle,
            boolean singleTaskInstance) {
        super(context, attrs, defStyle);
        mTaskEmbedder = new TaskEmbedder(getContext(), this, singleTaskInstance);
        if (useTaskOrganizer()) {
            mTaskEmbedder = new TaskOrganizerTaskEmbedder(context, this);
        } else {
            mTaskEmbedder = new VirtualDisplayTaskEmbedder(context, 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.
@@ -128,6 +133,12 @@ public class ActivityView extends ViewGroup implements TaskEmbedder.Host {
         */
        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}
@@ -139,6 +150,12 @@ public class ActivityView extends ViewGroup implements TaskEmbedder.Host {
         * 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) { }
    }

    /**
@@ -370,10 +387,8 @@ public class ActivityView extends ViewGroup implements TaskEmbedder.Host {

    @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);
        return mTaskEmbedder.gatherTransparentRegion(region)
                || super.gatherTransparentRegion(region);
    }

    private class SurfaceCallback implements SurfaceHolder.Callback {
@@ -432,7 +447,6 @@ public class ActivityView extends ViewGroup implements TaskEmbedder.Host {
            Log.e(TAG, "Failed to initialize ActivityView");
            return false;
        }
        mTmpTransaction.show(mTaskEmbedder.getSurfaceControl()).apply();
        return true;
    }

@@ -518,6 +532,13 @@ public class ActivityView extends ViewGroup implements TaskEmbedder.Host {
        return mWindowPosition;
    }

    /** @hide */
    @Override
    public Rect getScreenBounds() {
        getBoundsOnScreen(mTmpRect);
        return mTmpRect;
    }

    /** @hide */
    @Override
    public IWindow getWindow() {
@@ -530,6 +551,15 @@ public class ActivityView extends ViewGroup implements TaskEmbedder.Host {
        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;

@@ -552,6 +582,11 @@ public class ActivityView extends ViewGroup implements TaskEmbedder.Host {
            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);
@@ -561,5 +596,10 @@ public class ActivityView extends ViewGroup implements TaskEmbedder.Host {
        public void onTaskRemovalStarted(int taskId) {
            mCallback.onTaskRemovalStarted(taskId);
        }

        @Override
        public void onBackPressedOnTaskRoot(int taskId) {
            mCallback.onBackPressedOnTaskRoot(taskId);
        }
    }
}
+122 −368

File changed.

Preview size limit exceeded, changes collapsed.

+325 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2019 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 static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;

import android.content.Context;
import android.graphics.Rect;
import android.os.RemoteException;
import android.util.Log;
import android.view.KeyEvent;
import android.view.SurfaceControl;
import android.window.ITaskOrganizer;
import android.window.IWindowContainer;
import android.window.WindowContainerTransaction;
import android.window.WindowOrganizer;
import android.window.WindowOrganizer.TaskOrganizer;

/**
 * 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 class TaskOrganizerTaskEmbedder extends TaskEmbedder {
    private static final String TAG = "TaskOrgTaskEmbedder";
    private static final boolean DEBUG = false;

    private ITaskOrganizer.Stub mTaskOrganizer;
    private ActivityManager.RunningTaskInfo mTaskInfo;
    private IWindowContainer mTaskToken;
    private SurfaceControl mTaskLeash;
    private boolean mPendingNotifyBoundsChanged;

    /**
     * Constructs a new TaskEmbedder.
     *
     * @param context the context
     * @param host the host for this embedded task
     */
    public TaskOrganizerTaskEmbedder(Context context, TaskOrganizerTaskEmbedder.Host host) {
        super(context, host);
    }

    @Override
    public TaskStackListener createTaskStackListener() {
        return new TaskStackListenerImpl();
    }

    /**
     * Whether this container has been initialized.
     *
     * @return true if initialized
     */
    @Override
    public boolean isInitialized() {
        return mTaskOrganizer != null;
    }

    @Override
    public boolean onInitialize() {
        if (DEBUG) {
            log("onInitialize");
        }
        // Register the task organizer
        mTaskOrganizer =  new TaskOrganizerImpl();
        try {
            // TODO(wm-shell): This currently prevents other organizers from controlling MULT_WINDOW
            // windowing mode tasks. Plan is to migrate this to a wm-shell front-end when that
            // infrastructure is ready.
            TaskOrganizer.registerOrganizer(mTaskOrganizer, WINDOWING_MODE_MULTI_WINDOW);
            TaskOrganizer.setInterceptBackPressedOnTaskRoot(mTaskOrganizer, true);
        } catch (RemoteException e) {
            Log.e(TAG, "Failed to initialize TaskEmbedder", e);
            return false;
        }
        return true;
    }

    @Override
    protected boolean onRelease() {
        if (DEBUG) {
            log("onRelease");
        }
        if (!isInitialized()) {
            return false;
        }
        try {
            TaskOrganizer.unregisterOrganizer(mTaskOrganizer);
        } catch (RemoteException e) {
            Log.e(TAG, "Failed to remove task");
        }
        resetTaskInfo();
        return true;
    }

    /**
     * Starts presentation of tasks in this container.
     */
    @Override
    public void start() {
        if (DEBUG) {
            log("start");
        }
        if (!isInitialized()) {
            return;
        }
        if (mTaskToken == null) {
            return;
        }
        WindowContainerTransaction wct = new WindowContainerTransaction();
        wct.setHidden(mTaskToken, false /* hidden */);
        try {
            WindowOrganizer.applyTransaction(wct);
        } catch (RemoteException e) {
            Log.e(TAG, "Failed to unset hidden in transaction");
        }
        // TODO(b/151449487): Only call callback once we enable synchronization
        if (mListener != null) {
            mListener.onTaskVisibilityChanged(getTaskId(), true);
        }
    }

    /**
     * Stops presentation of tasks in this container.
     */
    @Override
    public void stop() {
        if (DEBUG) {
            log("stop");
        }
        if (!isInitialized()) {
            return;
        }
        if (mTaskToken == null) {
            return;
        }
        WindowContainerTransaction wct = new WindowContainerTransaction();
        wct.setHidden(mTaskToken, true /* hidden */);
        try {
            WindowOrganizer.applyTransaction(wct);
        } catch (RemoteException e) {
            Log.e(TAG, "Failed to set hidden in transaction");
        }
        // TODO(b/151449487): Only call callback once we enable synchronization
        if (mListener != null) {
            mListener.onTaskVisibilityChanged(getTaskId(), false);
        }
    }

    /**
     * This should be called whenever the position or size of the surface changes
     * or if touchable areas above the surface are added or removed.
     */
    @Override
    public void notifyBoundsChanged() {
        if (DEBUG) {
            log("notifyBoundsChanged: screenBounds=" + mHost.getScreenBounds());
        }
        if (mTaskToken == null) {
            mPendingNotifyBoundsChanged = true;
            return;
        }
        mPendingNotifyBoundsChanged = false;

        // Update based on the screen bounds
        Rect screenBounds = mHost.getScreenBounds();
        if (screenBounds.left < 0 || screenBounds.top < 0) {
            screenBounds.offsetTo(0, 0);
        }

        WindowContainerTransaction wct = new WindowContainerTransaction();
        wct.setBounds(mTaskToken, screenBounds);
        try {
            // TODO(b/151449487): Enable synchronization
            WindowOrganizer.applyTransaction(wct);
        } catch (RemoteException e) {
            Log.e(TAG, "Failed to set bounds in transaction");
        }
    }

    /**
     * Injects a pair of down/up key events with keycode {@link KeyEvent#KEYCODE_BACK} to the
     * virtual display.
     */
    @Override
    public void performBackPress() {
        // Do nothing, the task org task should already have focus if the caller is not focused
        return;
    }

    /** An opaque unique identifier for this task surface among others being managed by the app. */
    @Override
    public int getId() {
        return getTaskId();
    }

    /**
     * Check if container is ready to launch and create {@link ActivityOptions} to target the
     * virtual display.
     * @param options The existing options to amend, or null if the caller wants new options to be
     *                created
     */
    @Override
    protected ActivityOptions prepareActivityOptions(ActivityOptions options) {
        options = super.prepareActivityOptions(options);
        options.setLaunchWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
        return options;
    }

    private int getTaskId() {
        return mTaskInfo != null
                ? mTaskInfo.taskId
                : INVALID_TASK_ID;
    }

    private void resetTaskInfo() {
        if (DEBUG) {
            log("resetTaskInfo");
        }
        mTaskInfo = null;
        mTaskToken = null;
        mTaskLeash = null;
    }

    private void log(String msg) {
        Log.d(TAG, "[" + System.identityHashCode(this) + "] " + msg);
    }

    /**
     * A task change listener that detects background color change of the topmost stack on our
     * virtual display and updates the background of the surface view. This background will be shown
     * when surface view is resized, but the app hasn't drawn its content in new size yet.
     * It also calls StateCallback.onTaskMovedToFront to notify interested parties that the stack
     * associated with the {@link ActivityView} has had a Task moved to the front. This is useful
     * when needing to also bring the host Activity to the foreground at the same time.
     */
    private class TaskStackListenerImpl extends TaskStackListener {

        @Override
        public void onTaskDescriptionChanged(ActivityManager.RunningTaskInfo taskInfo)
                throws RemoteException {
            if (!isInitialized()) {
                return;
            }
            if (taskInfo.taskId == mTaskInfo.taskId) {
                mTaskInfo.taskDescription = taskInfo.taskDescription;
                mHost.onTaskBackgroundColorChanged(TaskOrganizerTaskEmbedder.this,
                        taskInfo.taskDescription.getBackgroundColor());
            }
        }
    }

    private class TaskOrganizerImpl extends ITaskOrganizer.Stub {
        @Override
        public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo)
                throws RemoteException {
            if (DEBUG) {
                log("taskAppeared: " + taskInfo.taskId);
            }

            // TODO: Ensure visibility/alpha of the leash in its initial state?
            mTaskInfo = taskInfo;
            mTaskToken = taskInfo.token;
            mTaskLeash = mTaskToken.getLeash();
            mTransaction.reparent(mTaskLeash, mSurfaceControl)
                    .show(mSurfaceControl).apply();
            if (mPendingNotifyBoundsChanged) {
                // TODO: Either defer show or hide and synchronize show with the resize
                notifyBoundsChanged();
            }
            mHost.post(() -> mHost.onTaskBackgroundColorChanged(TaskOrganizerTaskEmbedder.this,
                    taskInfo.taskDescription.getBackgroundColor()));

            if (mListener != null) {
                mListener.onTaskCreated(taskInfo.taskId, taskInfo.baseActivity);
            }
        }

        @Override
        public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo)
                throws RemoteException {
            if (DEBUG) {
                log("taskVanished: " + taskInfo.taskId);
            }

            if (mTaskToken != null && (taskInfo == null
                    || mTaskToken.asBinder().equals(taskInfo.token.asBinder()))) {
                if (mListener != null) {
                    mListener.onTaskRemovalStarted(taskInfo.taskId);
                }
                resetTaskInfo();
            }
        }

        @Override
        public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo)
                throws RemoteException {
            // Do nothing
        }

        @Override
        public void onBackPressedOnTaskRoot(ActivityManager.RunningTaskInfo taskInfo)
                throws RemoteException {
            if (mListener != null) {
                mListener.onBackPressedOnTaskRoot(taskInfo.taskId);
            }
        }
    }
}
+451 −0

File added.

Preview size limit exceeded, changes collapsed.

+8 −1
Original line number Diff line number Diff line
@@ -41,5 +41,12 @@ oneway interface ITaskOrganizer {
     * has children. The Divider impl looks at the info and can see that the secondary root task
     * has adopted an ActivityType of HOME and proceeds to show the minimized dock UX.
     */
    void onTaskInfoChanged(in ActivityManager.RunningTaskInfo info);
    void onTaskInfoChanged(in ActivityManager.RunningTaskInfo taskInfo);

    /**
     * Called when the task organizer has requested
     * {@link ITaskOrganizerController.setInterceptBackPressedOnTaskRoot} to get notified when the
     * user has pressed back on the root activity of a task controlled by the task organizer.
     */
    void onBackPressedOnTaskRoot(in ActivityManager.RunningTaskInfo taskInfo);
}
Loading