/* * 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.app.ActivityTaskManager.INVALID_TASK_ID; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import android.app.ActivityManager; import android.app.ActivityOptions; import android.content.Context; import android.graphics.Rect; import android.util.Log; import android.view.KeyEvent; import android.view.SurfaceControl; /** * 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 TaskOrganizer mTaskOrganizer; private ActivityManager.RunningTaskInfo mTaskInfo; private WindowContainerToken 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); } /** * 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(); // 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. mTaskOrganizer.registerOrganizer(WINDOWING_MODE_MULTI_WINDOW); mTaskOrganizer.setInterceptBackPressedOnTaskRoot(true); return super.onInitialize(); } @Override protected boolean onRelease() { if (DEBUG) { log("onRelease"); } if (!isInitialized()) { return false; } mTaskOrganizer.unregisterOrganizer(); resetTaskInfo(); return true; } /** * Starts presentation of tasks in this container. */ @Override public void start() { super.start(); if (DEBUG) { log("start"); } if (!isInitialized()) { return; } if (mTaskToken == null) { return; } WindowContainerTransaction wct = new WindowContainerTransaction(); wct.setHidden(mTaskToken, false /* hidden */); WindowOrganizer.applyTransaction(wct); // 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() { super.stop(); if (DEBUG) { log("stop"); } if (!isInitialized()) { return; } if (mTaskToken == null) { return; } WindowContainerTransaction wct = new WindowContainerTransaction(); wct.setHidden(mTaskToken, true /* hidden */); WindowOrganizer.applyTransaction(wct); // 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() { super.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); // TODO(b/151449487): Enable synchronization WindowOrganizer.applyTransaction(wct); } /** * 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); } private class TaskOrganizerImpl extends TaskOrganizer { @Override public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) { if (DEBUG) { log("taskAppeared: " + taskInfo.taskId); } mTaskInfo = taskInfo; mTaskToken = taskInfo.token; mTaskLeash = leash; mTransaction.reparent(mTaskLeash, mSurfaceControl) .show(mTaskLeash) .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 onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) { mTaskInfo.taskDescription = taskInfo.taskDescription; mHost.post(() -> mHost.onTaskBackgroundColorChanged(TaskOrganizerTaskEmbedder.this, taskInfo.taskDescription.getBackgroundColor())); } @Override public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) { 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 onBackPressedOnTaskRoot(ActivityManager.RunningTaskInfo taskInfo) { if (mListener != null) { mListener.onBackPressedOnTaskRoot(taskInfo.taskId); } } } }