diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java index d1fbf31e2b993..9bbede311d6da 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java @@ -24,6 +24,7 @@ import com.android.wm.shell.common.DisplayImeController; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.annotations.ExternalThread; import com.android.wm.shell.draganddrop.DragAndDropController; +import com.android.wm.shell.freeform.FreeformTaskListener; import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController; import com.android.wm.shell.pip.phone.PipTouchHandler; import com.android.wm.shell.splitscreen.SplitScreenController; @@ -47,6 +48,7 @@ public class ShellInitImpl { private final Optional mAppPairsOptional; private final Optional mPipTouchHandlerOptional; private final FullscreenTaskListener mFullscreenTaskListener; + private final Optional mFreeformTaskListenerOptional; private final ShellExecutor mMainExecutor; private final Transitions mTransitions; private final StartingWindowController mStartingWindow; @@ -62,6 +64,7 @@ public class ShellInitImpl { Optional appPairsOptional, Optional pipTouchHandlerOptional, FullscreenTaskListener fullscreenTaskListener, + Optional> freeformTaskListenerOptional, Transitions transitions, StartingWindowController startingWindow, ShellExecutor mainExecutor) { @@ -74,6 +77,7 @@ public class ShellInitImpl { mAppPairsOptional = appPairsOptional; mFullscreenTaskListener = fullscreenTaskListener; mPipTouchHandlerOptional = pipTouchHandlerOptional; + mFreeformTaskListenerOptional = freeformTaskListenerOptional.flatMap(f -> f); mTransitions = transitions; mMainExecutor = mainExecutor; mStartingWindow = startingWindow; @@ -108,6 +112,11 @@ public class ShellInitImpl { // controller instead of the feature interface, can just initialize the touch handler if // needed mPipTouchHandlerOptional.ifPresent((handler) -> handler.init()); + + // Initialize optional freeform + mFreeformTaskListenerOptional.ifPresent(f -> + mShellTaskOrganizer.addListenerForType( + f, ShellTaskOrganizer.TASK_LISTENER_TYPE_FREEFORM)); } @ExternalThread diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java index ba0ab6db1003c..ab8a21c58e9c8 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java @@ -71,12 +71,14 @@ public class ShellTaskOrganizer extends TaskOrganizer implements public static final int TASK_LISTENER_TYPE_FULLSCREEN = -2; public static final int TASK_LISTENER_TYPE_MULTI_WINDOW = -3; public static final int TASK_LISTENER_TYPE_PIP = -4; + public static final int TASK_LISTENER_TYPE_FREEFORM = -5; @IntDef(prefix = {"TASK_LISTENER_TYPE_"}, value = { TASK_LISTENER_TYPE_UNDEFINED, TASK_LISTENER_TYPE_FULLSCREEN, TASK_LISTENER_TYPE_MULTI_WINDOW, TASK_LISTENER_TYPE_PIP, + TASK_LISTENER_TYPE_FREEFORM, }) public @interface TaskListenerType {} @@ -572,6 +574,7 @@ public class ShellTaskOrganizer extends TaskOrganizer implements case WINDOWING_MODE_PINNED: return TASK_LISTENER_TYPE_PIP; case WINDOWING_MODE_FREEFORM: + return TASK_LISTENER_TYPE_FREEFORM; case WINDOWING_MODE_UNDEFINED: default: return TASK_LISTENER_TYPE_UNDEFINED; @@ -586,6 +589,8 @@ public class ShellTaskOrganizer extends TaskOrganizer implements return "TASK_LISTENER_TYPE_MULTI_WINDOW"; case TASK_LISTENER_TYPE_PIP: return "TASK_LISTENER_TYPE_PIP"; + case TASK_LISTENER_TYPE_FREEFORM: + return "TASK_LISTENER_TYPE_FREEFORM"; case TASK_LISTENER_TYPE_UNDEFINED: return "TASK_LISTENER_TYPE_UNDEFINED"; default: diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java new file mode 100644 index 0000000000000..5fb3297aa6d30 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2021 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 com.android.wm.shell.freeform; + +import static android.content.pm.PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT; +import static android.provider.Settings.Global.DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT; + +import android.app.ActivityManager.RunningTaskInfo; +import android.content.Context; +import android.graphics.Point; +import android.graphics.Rect; +import android.provider.Settings; +import android.util.Slog; +import android.util.SparseArray; +import android.view.SurfaceControl; + +import com.android.internal.protolog.common.ProtoLog; +import com.android.wm.shell.ShellTaskOrganizer; +import com.android.wm.shell.common.SyncTransactionQueue; +import com.android.wm.shell.protolog.ShellProtoLogGroup; + +import java.io.PrintWriter; + +/** + * {@link ShellTaskOrganizer.TaskListener} for {@link + * ShellTaskOrganizer#TASK_LISTENER_TYPE_FREEFORM}. + */ +public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener { + private static final String TAG = "FreeformTaskListener"; + + private final SyncTransactionQueue mSyncQueue; + + private final SparseArray mTasks = new SparseArray<>(); + + private static class State { + RunningTaskInfo mTaskInfo; + SurfaceControl mLeash; + } + + public FreeformTaskListener(SyncTransactionQueue syncQueue) { + mSyncQueue = syncQueue; + } + + @Override + public void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) { + if (mTasks.get(taskInfo.taskId) != null) { + throw new RuntimeException("Task appeared more than once: #" + taskInfo.taskId); + } + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Freeform Task Appeared: #%d", + taskInfo.taskId); + final State state = new State(); + state.mTaskInfo = taskInfo; + state.mLeash = leash; + mTasks.put(taskInfo.taskId, state); + + final Rect taskBounds = taskInfo.configuration.windowConfiguration.getBounds(); + mSyncQueue.runInSync(t -> { + Point taskPosition = taskInfo.positionInParent; + t.setPosition(leash, taskPosition.x, taskPosition.y) + .setWindowCrop(leash, taskBounds.width(), taskBounds.height()) + .show(leash); + }); + } + + @Override + public void onTaskVanished(RunningTaskInfo taskInfo) { + State state = mTasks.get(taskInfo.taskId); + if (state == null) { + Slog.e(TAG, "Task already vanished: #" + taskInfo.taskId); + return; + } + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Freeform Task Vanished: #%d", + taskInfo.taskId); + mTasks.remove(taskInfo.taskId); + } + + @Override + public void onTaskInfoChanged(RunningTaskInfo taskInfo) { + State state = mTasks.get(taskInfo.taskId); + if (state == null) { + throw new RuntimeException( + "Task info changed before appearing: #" + taskInfo.taskId); + } + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Freeform Task Info Changed: #%d", + taskInfo.taskId); + state.mTaskInfo = taskInfo; + + final Rect taskBounds = taskInfo.configuration.windowConfiguration.getBounds(); + final SurfaceControl leash = state.mLeash; + mSyncQueue.runInSync(t -> { + Point taskPosition = taskInfo.positionInParent; + t.setPosition(leash, taskPosition.x, taskPosition.y) + .setWindowCrop(leash, taskBounds.width(), taskBounds.height()) + .show(leash); + }); + } + + @Override + public void dump(PrintWriter pw, String prefix) { + final String innerPrefix = prefix + " "; + pw.println(prefix + this); + pw.println(innerPrefix + mTasks.size() + " tasks"); + } + + @Override + public String toString() { + return TAG; + } + + /** + * Checks if freeform support is enabled in system. + * + * @param context context used to check settings and package manager. + * @return {@code true} if freeform is enabled, {@code false} if not. + */ + public static boolean isFreeformEnabled(Context context) { + return context.getPackageManager().hasSystemFeature(FEATURE_FREEFORM_WINDOW_MANAGEMENT) + || Settings.Global.getInt(context.getContentResolver(), + DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT, 0) != 0; + } + + /** + * Creates {@link FreeformTaskListener} if freeform is enabled. + */ + public static FreeformTaskListener create(Context context, + SyncTransactionQueue syncQueue) { + if (!isFreeformEnabled(context)) { + return null; + } + + return new FreeformTaskListener(syncQueue); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java index 8344b1bb38c5f..7e3553a63958e 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java @@ -55,6 +55,7 @@ import com.android.wm.shell.common.annotations.ShellAnimationThread; import com.android.wm.shell.common.annotations.ShellMainThread; import com.android.wm.shell.common.annotations.ShellSplashscreenThread; import com.android.wm.shell.draganddrop.DragAndDropController; +import com.android.wm.shell.freeform.FreeformTaskListener; import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout; import com.android.wm.shell.hidedisplaycutout.HideDisplayCutoutController; import com.android.wm.shell.legacysplitscreen.LegacySplitScreen; @@ -212,6 +213,13 @@ public abstract class WMShellBaseModule { return new FullscreenTaskListener(syncQueue); } + // + // Freeform (optional feature) + // + + @BindsOptionalOf + abstract Optional optionalFreeformTaskListener(); + // // Hide display cutout // @@ -453,6 +461,7 @@ public abstract class WMShellBaseModule { Optional appPairsOptional, Optional pipTouchHandlerOptional, FullscreenTaskListener fullscreenTaskListener, + Optional> freeformTaskListener, Transitions transitions, StartingWindowController startingWindow, @ShellMainThread ShellExecutor mainExecutor) { @@ -465,6 +474,7 @@ public abstract class WMShellBaseModule { appPairsOptional, pipTouchHandlerOptional, fullscreenTaskListener, + freeformTaskListener, transitions, startingWindow, mainExecutor); diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java index be7813e19b11d..6397ce680a828 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java @@ -36,6 +36,7 @@ import com.android.wm.shell.common.TaskStackListenerImpl; import com.android.wm.shell.common.TransactionPool; import com.android.wm.shell.common.annotations.ChoreographerSfVsync; import com.android.wm.shell.common.annotations.ShellMainThread; +import com.android.wm.shell.freeform.FreeformTaskListener; import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController; import com.android.wm.shell.onehanded.OneHandedController; import com.android.wm.shell.pip.Pip; @@ -88,6 +89,18 @@ public class WMShellModule { transactionPool); } + // + // Freeform + // + + @WMSingleton + @Provides + static Optional provideFreeformTaskListener( + Context context, + SyncTransactionQueue syncQueue) { + return Optional.ofNullable(FreeformTaskListener.create(context, syncQueue)); + } + // // Split/multiwindow // diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index dbd9eeb7ac48a..256d818061bab 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -4600,7 +4600,9 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp return true; } - if (task.isOrganized()) { + // TODO(b/165794880): Freeform task organizer doesn't support drag-resize yet. Remove + // the special case when it does. + if (task.isOrganized() && task.getWindowingMode() != WINDOWING_MODE_FREEFORM) { return true; } diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java index 08021f59eb066..d819557006a63 100644 --- a/services/core/java/com/android/server/wm/InputMonitor.java +++ b/services/core/java/com/android/server/wm/InputMonitor.java @@ -307,7 +307,10 @@ final class InputMonitor { boolean useSurfaceCrop = false; final Task task = w.getTask(); if (task != null) { - if (task.isOrganized() && task.getWindowingMode() != WINDOWING_MODE_FULLSCREEN) { + // TODO(b/165794636): Remove the special case for freeform window once drag resizing is + // handled by WM shell. + if (task.isOrganized() && task.getWindowingMode() != WINDOWING_MODE_FULLSCREEN + && !task.inFreeformWindowingMode()) { // If the window is in a TaskManaged by a TaskOrganizer then most cropping will // be applied using the SurfaceControl hierarchy from the Organizer. This means // we need to make sure that these changes in crop are reflected in the input diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 1e0aafa076fd0..fec7edefd66d1 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -4174,8 +4174,7 @@ class Task extends TaskFragment { } private boolean canBeOrganized() { - if (mForceNotOrganized || !mAtmService.mTaskOrganizerController - .isSupportedWindowingMode(getWindowingMode())) { + if (mForceNotOrganized) { return false; } // All root tasks can be organized @@ -4332,7 +4331,7 @@ class Task extends TaskFragment { final int windowingMode = getWindowingMode(); final TaskOrganizerController controller = mWmService.mAtmService.mTaskOrganizerController; - final ITaskOrganizer organizer = controller.getTaskOrganizer(windowingMode); + final ITaskOrganizer organizer = controller.getTaskOrganizer(); if (!forceUpdate && mTaskOrganizer == organizer) { return false; } diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java index 99f31ade2be79..b17a9396b9df5 100644 --- a/services/core/java/com/android/server/wm/TaskOrganizerController.java +++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java @@ -16,9 +16,6 @@ package com.android.server.wm; -import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; -import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; - import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_ORGANIZER; import static com.android.server.wm.ActivityTaskManagerService.enforceTaskPermission; import static com.android.server.wm.DisplayContent.IME_TARGET_LAYERING; @@ -67,14 +64,6 @@ import java.util.function.Consumer; class TaskOrganizerController extends ITaskOrganizerController.Stub { private static final String TAG = "TaskOrganizerController"; - // The set of modes that are currently supports - // TODO: Remove once the task organizer can support all modes - @VisibleForTesting - static final int[] UNSUPPORTED_WINDOWING_MODES = { - WINDOWING_MODE_UNDEFINED, - WINDOWING_MODE_FREEFORM - }; - private class DeathRecipient implements IBinder.DeathRecipient { ITaskOrganizer mTaskOrganizer; @@ -373,11 +362,6 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { final TaskOrganizerState state = mTaskOrganizerStates.get(organizer.asBinder()); mService.mRootWindowContainer.forAllTasks((task) -> { - if (ArrayUtils.contains(UNSUPPORTED_WINDOWING_MODES, - task.getWindowingMode())) { - return; - } - boolean returnTask = !task.mCreatedByOrganizer; task.updateTaskOrganizerState(true /* forceUpdate */, returnTask /* skipTaskAppeared */); @@ -433,14 +417,8 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { /** * @return the task organizer key for a given windowing mode. */ - ITaskOrganizer getTaskOrganizer(int windowingMode) { - return isSupportedWindowingMode(windowingMode) - ? mTaskOrganizers.peekLast() - : null; - } - - boolean isSupportedWindowingMode(int winMode) { - return !ArrayUtils.contains(UNSUPPORTED_WINDOWING_MODES, winMode); + ITaskOrganizer getTaskOrganizer() { + return mTaskOrganizers.peekLast(); } // Capture the animation surface control for activity's main window @@ -993,9 +971,6 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { for (int k = 0; k < tasks.size(); k++) { final Task task = tasks.get(k); final int mode = task.getWindowingMode(); - if (ArrayUtils.contains(UNSUPPORTED_WINDOWING_MODES, mode)) { - continue; - } pw.println(innerPrefix + " (" + WindowConfiguration.windowingModeToString(mode) + ") " + task); }