From e3d75ac262f7a101f56df08c2fb7ea0077fd42f9 Mon Sep 17 00:00:00 2001 From: Winson Chung Date: Wed, 4 Nov 2020 14:17:07 -0800 Subject: [PATCH 1/2] Clean up task stack listeners - Have shell provided a task stack listener that various controllers can add to, and remove registration from outside the controllers (this also removes methods on the interface exposed only for use from the task stack listeners) - Move copy of InputConsumerController into shell (shared lib version can probably be refactored into use just by launcher for gesture nav) - Hide remaining wm components that are exposed now that all the features are in the Shell Bug: 169894807 Test: atest WMShellUnitTests WMShellTest Test: Verify Pip/Split/One handed still behaves as expected Change-Id: If0f50f6b701ac4b013a9e41fe6f88857ea35bf5b --- .../wm/shell/common/PipInputConsumer.java | 182 ++++++++ .../common/TaskStackListenerCallback.java | 116 +++++ .../shell/common/TaskStackListenerImpl.java | 437 ++++++++++++++++++ .../shell/onehanded/OneHandedController.java | 24 +- .../src/com/android/wm/shell/pip/Pip.java | 30 -- .../wm/shell/pip/phone/PipController.java | 126 +++-- .../wm/shell/pip/tv/PipController.java | 35 +- .../wm/shell/splitscreen/SplitScreen.java | 12 - .../splitscreen/SplitScreenController.java | 61 ++- .../common/TaskStackListenerImplTest.java | 258 +++++++++++ .../onehanded/OneHandedControllerTest.java | 6 +- .../OneHandedTutorialHandlerTest.java | 6 +- .../wm/shell/pip/phone/PipControllerTest.java | 8 +- .../system/InputConsumerController.java | 11 +- .../com/android/systemui/SystemUIFactory.java | 5 +- .../systemui/dagger/SysUIComponent.java | 8 - .../android/systemui/dagger/WMComponent.java | 10 - .../android/systemui/wmshell/TvPipModule.java | 3 + .../systemui/wmshell/TvWMShellModule.java | 6 +- .../com/android/systemui/wmshell/WMShell.java | 108 ----- .../systemui/wmshell/WMShellBaseModule.java | 18 +- .../systemui/wmshell/WMShellModule.java | 10 +- .../android/systemui/wmshell/WMShellTest.java | 13 +- 23 files changed, 1211 insertions(+), 282 deletions(-) create mode 100644 libs/WindowManager/Shell/src/com/android/wm/shell/common/PipInputConsumer.java create mode 100644 libs/WindowManager/Shell/src/com/android/wm/shell/common/TaskStackListenerCallback.java create mode 100644 libs/WindowManager/Shell/src/com/android/wm/shell/common/TaskStackListenerImpl.java create mode 100644 libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/TaskStackListenerImplTest.java diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/PipInputConsumer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/PipInputConsumer.java new file mode 100644 index 0000000000000..87ddb181dcce3 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/PipInputConsumer.java @@ -0,0 +1,182 @@ +/* + * 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 com.android.wm.shell.common; + +import static android.view.Display.DEFAULT_DISPLAY; +import static android.view.WindowManager.INPUT_CONSUMER_PIP; +import static android.view.WindowManager.INPUT_CONSUMER_RECENTS_ANIMATION; + +import android.os.Binder; +import android.os.IBinder; +import android.os.Looper; +import android.os.RemoteException; +import android.util.Log; +import android.view.BatchedInputEventReceiver; +import android.view.Choreographer; +import android.view.IWindowManager; +import android.view.InputChannel; +import android.view.InputEvent; +import android.view.WindowManagerGlobal; + +import java.io.PrintWriter; + +/** + * Manages the input consumer that allows the Shell to directly receive input. + */ +public class PipInputConsumer { + + private static final String TAG = PipInputConsumer.class.getSimpleName(); + + /** + * Listener interface for callers to subscribe to input events. + */ + public interface InputListener { + /** Handles any input event. */ + boolean onInputEvent(InputEvent ev); + } + + /** + * Listener interface for callers to learn when this class is registered or unregistered with + * window manager + */ + public interface RegistrationListener { + void onRegistrationChanged(boolean isRegistered); + } + + /** + * Input handler used for the input consumer. Input events are batched and consumed with the + * SurfaceFlinger vsync. + */ + private final class InputEventReceiver extends BatchedInputEventReceiver { + + InputEventReceiver(InputChannel inputChannel, Looper looper, + Choreographer choreographer) { + super(inputChannel, looper, choreographer); + } + + @Override + public void onInputEvent(InputEvent event) { + boolean handled = true; + try { + if (mListener != null) { + handled = mListener.onInputEvent(event); + } + } finally { + finishInputEvent(event, handled); + } + } + } + + private final IWindowManager mWindowManager; + private final IBinder mToken; + private final String mName; + + private InputEventReceiver mInputEventReceiver; + private InputListener mListener; + private RegistrationListener mRegistrationListener; + + /** + * @param name the name corresponding to the input consumer that is defined in the system. + */ + public PipInputConsumer(IWindowManager windowManager, String name) { + mWindowManager = windowManager; + mToken = new Binder(); + mName = name; + } + + /** + * Sets the input listener. + */ + public void setInputListener(InputListener listener) { + mListener = listener; + } + + /** + * Sets the registration listener. + */ + public void setRegistrationListener(RegistrationListener listener) { + mRegistrationListener = listener; + if (mRegistrationListener != null) { + mRegistrationListener.onRegistrationChanged(mInputEventReceiver != null); + } + } + + /** + * Check if the InputConsumer is currently registered with WindowManager + * + * @return {@code true} if registered, {@code false} if not. + */ + public boolean isRegistered() { + return mInputEventReceiver != null; + } + + /** + * Registers the input consumer. + */ + public void registerInputConsumer() { + registerInputConsumer(false); + } + + /** + * Registers the input consumer. + * @param withSfVsync the flag set using sf vsync signal or no + */ + public void registerInputConsumer(boolean withSfVsync) { + if (mInputEventReceiver != null) { + return; + } + final InputChannel inputChannel = new InputChannel(); + try { + // TODO(b/113087003): Support Picture-in-picture in multi-display. + mWindowManager.destroyInputConsumer(mName, DEFAULT_DISPLAY); + mWindowManager.createInputConsumer(mToken, mName, DEFAULT_DISPLAY, inputChannel); + } catch (RemoteException e) { + Log.e(TAG, "Failed to create input consumer", e); + } + mInputEventReceiver = new InputEventReceiver(inputChannel, Looper.myLooper(), + withSfVsync ? Choreographer.getSfInstance() : Choreographer.getInstance()); + if (mRegistrationListener != null) { + mRegistrationListener.onRegistrationChanged(true /* isRegistered */); + } + } + + /** + * Unregisters the input consumer. + */ + public void unregisterInputConsumer() { + if (mInputEventReceiver == null) { + return; + } + try { + // TODO(b/113087003): Support Picture-in-picture in multi-display. + mWindowManager.destroyInputConsumer(mName, DEFAULT_DISPLAY); + } catch (RemoteException e) { + Log.e(TAG, "Failed to destroy input consumer", e); + } + mInputEventReceiver.dispose(); + mInputEventReceiver = null; + if (mRegistrationListener != null) { + mRegistrationListener.onRegistrationChanged(false /* isRegistered */); + } + } + + public void dump(PrintWriter pw, String prefix) { + final String innerPrefix = prefix + " "; + pw.println(prefix + TAG); + pw.println(innerPrefix + "registered=" + (mInputEventReceiver != null)); + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TaskStackListenerCallback.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TaskStackListenerCallback.java new file mode 100644 index 0000000000000..0f6dd93f9c163 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TaskStackListenerCallback.java @@ -0,0 +1,116 @@ +/* + * 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 com.android.wm.shell.common; + +import android.app.ActivityManager; +import android.app.ActivityManager.RunningTaskInfo; +import android.app.ITaskStackListener; +import android.app.TaskInfo; +import android.content.ComponentName; +import android.os.IBinder; + +import androidx.annotation.BinderThread; +import androidx.annotation.MainThread; + +/** + * An interface to track task stack changes. Classes should implement this instead of + * {@link ITaskStackListener} to reduce IPC calls from system services. + */ +public interface TaskStackListenerCallback { + + @MainThread + default void onRecentTaskListUpdated() { } + + @MainThread + default void onRecentTaskListFrozenChanged(boolean frozen) { } + + @BinderThread + default void onTaskStackChangedBackground() { } + + @MainThread + default void onTaskStackChanged() { } + + @MainThread + default void onTaskProfileLocked(int taskId, int userId) { } + + @MainThread + default void onTaskDisplayChanged(int taskId, int newDisplayId) { } + + @MainThread + default void onTaskCreated(int taskId, ComponentName componentName) { } + + @MainThread + default void onTaskRemoved(int taskId) { } + + @MainThread + default void onTaskMovedToFront(int taskId) { } + + @MainThread + default void onTaskMovedToFront(RunningTaskInfo taskInfo) { + onTaskMovedToFront(taskInfo.taskId); + } + + @MainThread + default void onTaskDescriptionChanged(RunningTaskInfo taskInfo) { } + + @MainThread + default void onTaskSnapshotChanged(int taskId, ActivityManager.TaskSnapshot snapshot) { } + + @MainThread + default void onBackPressedOnTaskRoot(RunningTaskInfo taskInfo) { } + + @MainThread + default void onActivityRestartAttempt(RunningTaskInfo task, boolean homeTaskVisible, + boolean clearedTask, boolean wasVisible) { } + + @MainThread + default void onActivityPinned(String packageName, int userId, int taskId, int stackId) { } + + @MainThread + default void onActivityUnpinned() { } + + @MainThread + default void onActivityForcedResizable(String packageName, int taskId, int reason) { } + + @MainThread + default void onActivityDismissingDockedStack() { } + + @MainThread + default void onActivityLaunchOnSecondaryDisplayFailed() { } + + @MainThread + default void onActivityLaunchOnSecondaryDisplayFailed(RunningTaskInfo taskInfo) { + onActivityLaunchOnSecondaryDisplayFailed(); + } + + @MainThread + default void onActivityLaunchOnSecondaryDisplayRerouted() { } + + @MainThread + default void onActivityLaunchOnSecondaryDisplayRerouted(RunningTaskInfo taskInfo) { + onActivityLaunchOnSecondaryDisplayRerouted(); + } + + @MainThread + default void onActivityRequestedOrientationChanged(int taskId, int requestedOrientation) { } + + @MainThread + default void onActivityRotation(int displayId) { } + + @MainThread + default void onSizeCompatModeActivityChanged(int displayId, IBinder activityToken) { } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TaskStackListenerImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TaskStackListenerImpl.java new file mode 100644 index 0000000000000..7efacc7b2c1f8 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TaskStackListenerImpl.java @@ -0,0 +1,437 @@ +/* + * 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 com.android.wm.shell.common; + +import android.app.ActivityManager; +import android.app.ActivityTaskManager; +import android.app.IActivityTaskManager; +import android.app.TaskStackListener; +import android.content.ComponentName; +import android.os.Handler; +import android.os.IBinder; +import android.os.Message; +import android.os.Trace; +import android.util.Log; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.os.SomeArgs; + +import java.util.ArrayList; +import java.util.List; + +/** + * Implementation of a {@link android.app.TaskStackListener}. + */ +public class TaskStackListenerImpl extends TaskStackListener implements Handler.Callback { + private static final String TAG = TaskStackListenerImpl.class.getSimpleName(); + + private static final int ON_TASK_STACK_CHANGED = 1; + private static final int ON_TASK_SNAPSHOT_CHANGED = 2; + private static final int ON_ACTIVITY_PINNED = 3; + private static final int ON_ACTIVITY_RESTART_ATTEMPT = 4; + private static final int ON_ACTIVITY_FORCED_RESIZABLE = 5; + private static final int ON_ACTIVITY_DISMISSING_DOCKED_STACK = 6; + private static final int ON_TASK_PROFILE_LOCKED = 7; + private static final int ON_ACTIVITY_UNPINNED = 8; + private static final int ON_ACTIVITY_LAUNCH_ON_SECONDARY_DISPLAY_FAILED = 9; + private static final int ON_TASK_CREATED = 10; + private static final int ON_TASK_REMOVED = 11; + private static final int ON_TASK_MOVED_TO_FRONT = 12; + private static final int ON_ACTIVITY_REQUESTED_ORIENTATION_CHANGE = 13; + private static final int ON_ACTIVITY_LAUNCH_ON_SECONDARY_DISPLAY_REROUTED = 14; + private static final int ON_SIZE_COMPAT_MODE_ACTIVITY_CHANGED = 15; + private static final int ON_BACK_PRESSED_ON_TASK_ROOT = 16; + private static final int ON_TASK_DISPLAY_CHANGED = 17; + private static final int ON_TASK_LIST_UPDATED = 18; + private static final int ON_TASK_LIST_FROZEN_UNFROZEN = 19; + private static final int ON_TASK_DESCRIPTION_CHANGED = 20; + private static final int ON_ACTIVITY_ROTATION = 21; + + /** + * List of {@link TaskStackListenerCallback} registered from {@link #addListener}. + */ + private final List mTaskStackListeners = new ArrayList<>(); + private final List mTmpListeners = new ArrayList<>(); + + private final IActivityTaskManager mActivityTaskManager; + // NOTE: In this case we do want to use a handler since we rely on the message system to + // efficiently dedupe sequential calls + private Handler mHandler; + + public TaskStackListenerImpl(Handler handler) { + mActivityTaskManager = ActivityTaskManager.getService(); + mHandler = new Handler(handler.getLooper(), this); + } + + @VisibleForTesting + TaskStackListenerImpl(IActivityTaskManager activityTaskManager) { + mActivityTaskManager = activityTaskManager; + } + + @VisibleForTesting + void setHandler(Handler handler) { + mHandler = handler; + } + + public void addListener(TaskStackListenerCallback listener) { + final boolean wasEmpty; + synchronized (mTaskStackListeners) { + wasEmpty = mTaskStackListeners.isEmpty(); + mTaskStackListeners.add(listener); + } + if (wasEmpty) { + // Register mTaskStackListener to IActivityManager only once if needed. + try { + mActivityTaskManager.registerTaskStackListener(this); + } catch (Exception e) { + Log.w(TAG, "Failed to call registerTaskStackListener", e); + } + } + } + + public void removeListener(TaskStackListenerCallback listener) { + final boolean wasEmpty; + final boolean isEmpty; + synchronized (mTaskStackListeners) { + wasEmpty = mTaskStackListeners.isEmpty(); + mTaskStackListeners.remove(listener); + isEmpty = mTaskStackListeners.isEmpty(); + } + if (!wasEmpty && isEmpty) { + // Unregister mTaskStackListener once we have no more listeners + try { + mActivityTaskManager.unregisterTaskStackListener(this); + } catch (Exception e) { + Log.w(TAG, "Failed to call unregisterTaskStackListener", e); + } + } + } + + @Override + public void onRecentTaskListUpdated() { + mHandler.obtainMessage(ON_TASK_LIST_UPDATED).sendToTarget(); + } + + @Override + public void onRecentTaskListFrozenChanged(boolean frozen) { + mHandler.obtainMessage(ON_TASK_LIST_FROZEN_UNFROZEN, frozen ? 1 : 0, 0 /* unused */) + .sendToTarget(); + } + + @Override + public void onTaskStackChanged() { + // Call the task changed callback for the non-ui thread listeners first. Copy to a set + // of temp listeners so that we don't lock on mTaskStackListeners while calling all the + // callbacks. This call is always on the same binder thread, so we can just synchronize + // on the copying of the listener list. + synchronized (mTaskStackListeners) { + mTmpListeners.addAll(mTaskStackListeners); + } + for (int i = mTmpListeners.size() - 1; i >= 0; i--) { + mTmpListeners.get(i).onTaskStackChangedBackground(); + } + mTmpListeners.clear(); + + mHandler.removeMessages(ON_TASK_STACK_CHANGED); + mHandler.sendEmptyMessage(ON_TASK_STACK_CHANGED); + } + + @Override + public void onTaskProfileLocked(int taskId, int userId) { + mHandler.obtainMessage(ON_TASK_PROFILE_LOCKED, taskId, userId).sendToTarget(); + } + + @Override + public void onTaskDisplayChanged(int taskId, int newDisplayId) { + mHandler.obtainMessage(ON_TASK_DISPLAY_CHANGED, taskId, newDisplayId).sendToTarget(); + } + + @Override + public void onTaskCreated(int taskId, ComponentName componentName) { + mHandler.obtainMessage(ON_TASK_CREATED, taskId, 0, componentName).sendToTarget(); + } + + @Override + public void onTaskRemoved(int taskId) { + mHandler.obtainMessage(ON_TASK_REMOVED, taskId, 0).sendToTarget(); + } + + @Override + public void onTaskMovedToFront(ActivityManager.RunningTaskInfo taskInfo) { + mHandler.obtainMessage(ON_TASK_MOVED_TO_FRONT, taskInfo).sendToTarget(); + } + + @Override + public void onTaskDescriptionChanged(ActivityManager.RunningTaskInfo taskInfo) { + mHandler.obtainMessage(ON_TASK_DESCRIPTION_CHANGED, taskInfo).sendToTarget(); + } + + @Override + public void onTaskSnapshotChanged(int taskId, ActivityManager.TaskSnapshot snapshot) { + mHandler.obtainMessage(ON_TASK_SNAPSHOT_CHANGED, taskId, 0, snapshot).sendToTarget(); + } + + @Override + public void onBackPressedOnTaskRoot(ActivityManager.RunningTaskInfo taskInfo) { + mHandler.obtainMessage(ON_BACK_PRESSED_ON_TASK_ROOT, taskInfo).sendToTarget(); + } + + @Override + public void onActivityPinned(String packageName, int userId, int taskId, int stackId) { + SomeArgs args = SomeArgs.obtain(); + args.arg1 = packageName; + args.argi1 = userId; + args.argi2 = taskId; + args.argi3 = stackId; + mHandler.removeMessages(ON_ACTIVITY_PINNED); + mHandler.obtainMessage(ON_ACTIVITY_PINNED, args).sendToTarget(); + } + + @Override + public void onActivityUnpinned() { + mHandler.removeMessages(ON_ACTIVITY_UNPINNED); + mHandler.sendEmptyMessage(ON_ACTIVITY_UNPINNED); + } + + @Override + public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task, boolean homeTaskVisible, + boolean clearedTask, boolean wasVisible) { + final SomeArgs args = SomeArgs.obtain(); + args.arg1 = task; + args.argi1 = homeTaskVisible ? 1 : 0; + args.argi2 = clearedTask ? 1 : 0; + args.argi3 = wasVisible ? 1 : 0; + mHandler.removeMessages(ON_ACTIVITY_RESTART_ATTEMPT); + mHandler.obtainMessage(ON_ACTIVITY_RESTART_ATTEMPT, args).sendToTarget(); + } + + @Override + public void onActivityForcedResizable(String packageName, int taskId, int reason) { + mHandler.obtainMessage(ON_ACTIVITY_FORCED_RESIZABLE, taskId, reason, packageName) + .sendToTarget(); + } + + @Override + public void onActivityDismissingDockedStack() { + mHandler.sendEmptyMessage(ON_ACTIVITY_DISMISSING_DOCKED_STACK); + } + + @Override + public void onActivityLaunchOnSecondaryDisplayFailed( + ActivityManager.RunningTaskInfo taskInfo, + int requestedDisplayId) { + mHandler.obtainMessage(ON_ACTIVITY_LAUNCH_ON_SECONDARY_DISPLAY_FAILED, + requestedDisplayId, + 0 /* unused */, + taskInfo).sendToTarget(); + } + + @Override + public void onActivityLaunchOnSecondaryDisplayRerouted( + ActivityManager.RunningTaskInfo taskInfo, + int requestedDisplayId) { + mHandler.obtainMessage(ON_ACTIVITY_LAUNCH_ON_SECONDARY_DISPLAY_REROUTED, + requestedDisplayId, 0 /* unused */, taskInfo).sendToTarget(); + } + + @Override + public void onActivityRequestedOrientationChanged(int taskId, int requestedOrientation) { + mHandler.obtainMessage(ON_ACTIVITY_REQUESTED_ORIENTATION_CHANGE, taskId, + requestedOrientation).sendToTarget(); + } + + @Override + public void onActivityRotation(int displayId) { + mHandler.obtainMessage(ON_ACTIVITY_ROTATION, displayId, 0 /* unused */) + .sendToTarget(); + } + + @Override + public void onSizeCompatModeActivityChanged(int displayId, IBinder activityToken) { + mHandler.obtainMessage(ON_SIZE_COMPAT_MODE_ACTIVITY_CHANGED, displayId, + 0 /* unused */, + activityToken).sendToTarget(); + } + + @Override + public boolean handleMessage(Message msg) { + synchronized (mTaskStackListeners) { + switch (msg.what) { + case ON_TASK_STACK_CHANGED: { + Trace.beginSection("onTaskStackChanged"); + for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) { + mTaskStackListeners.get(i).onTaskStackChanged(); + } + Trace.endSection(); + break; + } + case ON_TASK_SNAPSHOT_CHANGED: { + Trace.beginSection("onTaskSnapshotChanged"); + for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) { + mTaskStackListeners.get(i).onTaskSnapshotChanged(msg.arg1, + (ActivityManager.TaskSnapshot) msg.obj); + } + Trace.endSection(); + break; + } + case ON_ACTIVITY_PINNED: { + final SomeArgs args = (SomeArgs) msg.obj; + for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) { + mTaskStackListeners.get(i).onActivityPinned((String) args.arg1, args.argi1, + args.argi2, args.argi3); + } + break; + } + case ON_ACTIVITY_UNPINNED: { + for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) { + mTaskStackListeners.get(i).onActivityUnpinned(); + } + break; + } + case ON_ACTIVITY_RESTART_ATTEMPT: { + final SomeArgs args = (SomeArgs) msg.obj; + final ActivityManager.RunningTaskInfo + task = (ActivityManager.RunningTaskInfo) args.arg1; + final boolean homeTaskVisible = args.argi1 != 0; + final boolean clearedTask = args.argi2 != 0; + final boolean wasVisible = args.argi3 != 0; + for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) { + mTaskStackListeners.get(i).onActivityRestartAttempt(task, + homeTaskVisible, clearedTask, wasVisible); + } + break; + } + case ON_ACTIVITY_FORCED_RESIZABLE: { + for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) { + mTaskStackListeners.get(i).onActivityForcedResizable( + (String) msg.obj, msg.arg1, msg.arg2); + } + break; + } + case ON_ACTIVITY_DISMISSING_DOCKED_STACK: { + for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) { + mTaskStackListeners.get(i).onActivityDismissingDockedStack(); + } + break; + } + case ON_ACTIVITY_LAUNCH_ON_SECONDARY_DISPLAY_FAILED: { + final ActivityManager.RunningTaskInfo + info = (ActivityManager.RunningTaskInfo) msg.obj; + for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) { + mTaskStackListeners.get(i) + .onActivityLaunchOnSecondaryDisplayFailed(info); + } + break; + } + case ON_ACTIVITY_LAUNCH_ON_SECONDARY_DISPLAY_REROUTED: { + final ActivityManager.RunningTaskInfo + info = (ActivityManager.RunningTaskInfo) msg.obj; + for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) { + mTaskStackListeners.get(i) + .onActivityLaunchOnSecondaryDisplayRerouted(info); + } + break; + } + case ON_TASK_PROFILE_LOCKED: { + for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) { + mTaskStackListeners.get(i).onTaskProfileLocked(msg.arg1, msg.arg2); + } + break; + } + case ON_TASK_CREATED: { + for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) { + mTaskStackListeners.get(i).onTaskCreated(msg.arg1, + (ComponentName) msg.obj); + } + break; + } + case ON_TASK_REMOVED: { + for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) { + mTaskStackListeners.get(i).onTaskRemoved(msg.arg1); + } + break; + } + case ON_TASK_MOVED_TO_FRONT: { + final ActivityManager.RunningTaskInfo + info = (ActivityManager.RunningTaskInfo) msg.obj; + for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) { + mTaskStackListeners.get(i).onTaskMovedToFront(info); + } + break; + } + case ON_ACTIVITY_REQUESTED_ORIENTATION_CHANGE: { + for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) { + mTaskStackListeners.get(i) + .onActivityRequestedOrientationChanged(msg.arg1, msg.arg2); + } + break; + } + case ON_SIZE_COMPAT_MODE_ACTIVITY_CHANGED: { + for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) { + mTaskStackListeners.get(i).onSizeCompatModeActivityChanged( + msg.arg1, (IBinder) msg.obj); + } + break; + } + case ON_BACK_PRESSED_ON_TASK_ROOT: { + for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) { + mTaskStackListeners.get(i).onBackPressedOnTaskRoot( + (ActivityManager.RunningTaskInfo) msg.obj); + } + break; + } + case ON_TASK_DISPLAY_CHANGED: { + for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) { + mTaskStackListeners.get(i).onTaskDisplayChanged(msg.arg1, msg.arg2); + } + break; + } + case ON_TASK_LIST_UPDATED: { + for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) { + mTaskStackListeners.get(i).onRecentTaskListUpdated(); + } + break; + } + case ON_TASK_LIST_FROZEN_UNFROZEN: { + for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) { + mTaskStackListeners.get(i).onRecentTaskListFrozenChanged( + msg.arg1 != 0); + } + break; + } + case ON_TASK_DESCRIPTION_CHANGED: { + final ActivityManager.RunningTaskInfo + info = (ActivityManager.RunningTaskInfo) msg.obj; + for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) { + mTaskStackListeners.get(i).onTaskDescriptionChanged(info); + } + break; + } + case ON_ACTIVITY_ROTATION: { + for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) { + mTaskStackListeners.get(i).onActivityRotation(msg.arg1); + } + break; + } + } + } + if (msg.obj instanceof SomeArgs) { + ((SomeArgs) msg.obj).recycle(); + } + return true; + } +} \ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java index 7d039d4d92f6d..f84936e5f3861 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java @@ -19,6 +19,7 @@ package com.android.wm.shell.onehanded; import static android.os.UserHandle.USER_CURRENT; import static android.view.Display.DEFAULT_DISPLAY; +import android.content.ComponentName; import android.content.Context; import android.content.om.IOverlayManager; import android.content.om.OverlayInfo; @@ -38,6 +39,8 @@ import androidx.annotation.VisibleForTesting; import com.android.wm.shell.common.DisplayChangeController; import com.android.wm.shell.common.DisplayController; +import com.android.wm.shell.common.TaskStackListenerCallback; +import com.android.wm.shell.common.TaskStackListenerImpl; import com.android.wm.shell.onehanded.OneHandedGestureHandler.OneHandedGestureEventCallback; import java.io.PrintWriter; @@ -164,7 +167,8 @@ public class OneHandedController implements OneHanded { */ @Nullable public static OneHandedController create( - Context context, DisplayController displayController) { + Context context, DisplayController displayController, + TaskStackListenerImpl taskStackListener) { if (!SystemProperties.getBoolean(SUPPORT_ONE_HANDED_MODE, false)) { Slog.w(TAG, "Device doesn't support OneHanded feature"); return null; @@ -181,7 +185,7 @@ public class OneHandedController implements OneHanded { IOverlayManager overlayManager = IOverlayManager.Stub.asInterface( ServiceManager.getService(Context.OVERLAY_SERVICE)); return new OneHandedController(context, displayController, organizer, touchHandler, - tutorialHandler, gestureHandler, overlayManager); + tutorialHandler, gestureHandler, overlayManager, taskStackListener); } @VisibleForTesting @@ -191,7 +195,8 @@ public class OneHandedController implements OneHanded { OneHandedTouchHandler touchHandler, OneHandedTutorialHandler tutorialHandler, OneHandedGestureHandler gestureHandler, - IOverlayManager overlayManager) { + IOverlayManager overlayManager, + TaskStackListenerImpl taskStackListener) { mContext = context; mDisplayAreaOrganizer = displayAreaOrganizer; mDisplayController = displayController; @@ -216,6 +221,19 @@ public class OneHandedController implements OneHanded { setupTimeoutListener(); setupGesturalOverlay(); updateSettings(); + + taskStackListener.addListener( + new TaskStackListenerCallback() { + @Override + public void onTaskCreated(int taskId, ComponentName componentName) { + stopOneHanded(OneHandedEvents.EVENT_ONE_HANDED_TRIGGER_APP_TAPS_OUT); + } + + @Override + public void onTaskMovedToFront(int taskId) { + stopOneHanded(OneHandedEvents.EVENT_ONE_HANDED_TRIGGER_APP_TAPS_OUT); + } + }); } /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java index 8d5da1a5ffcbc..46270cf24f489 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java @@ -80,30 +80,6 @@ public interface Pip { default void movePipToFullscreen() { } - /** - * Called whenever an Activity is moved to the pinned stack from another stack. - */ - default void onActivityPinned(String packageName) { - } - - /** - * Called whenever an Activity is moved from the pinned stack to another stack - */ - default void onActivityUnpinned(ComponentName topActivity) { - } - - /** - * Called whenever IActivityManager.startActivity is called on an activity that is already - * running, but the task is either brought to the front or a new Intent is delivered to it. - * - * @param task information about the task the activity was relaunched into - * @param clearedTask whether or not the launch activity also cleared the task as a part of - * starting - */ - default void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task, - boolean clearedTask) { - } - /** * Called when display size or font size of settings changed */ @@ -131,12 +107,6 @@ public interface Pip { default void onSystemUiStateChanged(boolean isSysUiStateValid, int flag) { } - /** - * Called when task stack changed. - */ - default void onTaskStackChanged() { - } - /** * Resize the Pip to the appropriate size for the input state. * diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java index 6e1fc83a87fb7..251f8d4722459 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java @@ -16,12 +16,15 @@ package com.android.wm.shell.pip.phone; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE; +import static android.view.WindowManager.INPUT_CONSUMER_PIP; import static com.android.wm.shell.pip.PipAnimationController.isOutPipDirection; import android.app.ActivityManager; +import android.app.ActivityTaskManager; import android.app.PictureInPictureParams; import android.app.RemoteAction; import android.content.ComponentName; @@ -32,9 +35,12 @@ import android.graphics.Rect; import android.os.RemoteException; import android.os.UserHandle; import android.os.UserManager; +import android.util.Log; +import android.util.Pair; import android.util.Slog; import android.view.DisplayInfo; import android.view.IPinnedStackController; +import android.view.WindowManagerGlobal; import android.window.WindowContainerTransaction; import androidx.annotation.NonNull; @@ -44,13 +50,17 @@ import com.android.wm.shell.WindowManagerShellWrapper; import com.android.wm.shell.common.DisplayChangeController; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayLayout; +import com.android.wm.shell.common.PipInputConsumer; import com.android.wm.shell.common.ShellExecutor; +import com.android.wm.shell.common.TaskStackListenerCallback; +import com.android.wm.shell.common.TaskStackListenerImpl; import com.android.wm.shell.pip.PinnedStackListenerForwarder; import com.android.wm.shell.pip.Pip; import com.android.wm.shell.pip.PipBoundsHandler; import com.android.wm.shell.pip.PipBoundsState; import com.android.wm.shell.pip.PipMediaController; import com.android.wm.shell.pip.PipTaskOrganizer; +import com.android.wm.shell.pip.PipUtils; import java.io.PrintWriter; import java.util.function.Consumer; @@ -63,22 +73,22 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac private Context mContext; private ShellExecutor mMainExecutor; + private DisplayController mDisplayController; + private PipInputConsumer mPipInputConsumer; + private WindowManagerShellWrapper mWindowManagerShellWrapper; + private PipAppOpsListener mAppOpsListener; + private PipMediaController mMediaController; + private PipBoundsHandler mPipBoundsHandler; + private PipBoundsState mPipBoundsState; + private PipTouchHandler mTouchHandler; private final DisplayInfo mTmpDisplayInfo = new DisplayInfo(); private final Rect mTmpInsetBounds = new Rect(); private final Rect mTmpNormalBounds = new Rect(); protected final Rect mReentryBounds = new Rect(); - private DisplayController mDisplayController; - private PipAppOpsListener mAppOpsListener; - private PipBoundsHandler mPipBoundsHandler; - private @NonNull PipBoundsState mPipBoundsState; - private PipMediaController mMediaController; - private PipTouchHandler mTouchHandler; - private Consumer mPinnedStackAnimationRecentsCallback; - private WindowManagerShellWrapper mWindowManagerShellWrapper; - private boolean mIsInFixedRotation; + private Consumer mPinnedStackAnimationRecentsCallback; protected PipMenuActivityController mMenuController; protected PipTaskOrganizer mPipTaskOrganizer; @@ -221,6 +231,7 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac PipTaskOrganizer pipTaskOrganizer, PipTouchHandler pipTouchHandler, WindowManagerShellWrapper windowManagerShellWrapper, + TaskStackListenerImpl taskStackListener, ShellExecutor mainExecutor ) { // Ensure that we are the primary user's SystemUI. @@ -247,7 +258,14 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac }); mMediaController = pipMediaController; mMenuController = pipMenuActivityController; + mPipInputConsumer = new PipInputConsumer(WindowManagerGlobal.getWindowManagerService(), + INPUT_CONSUMER_PIP); mTouchHandler = pipTouchHandler; + if (mTouchHandler != null) { + // Register the listener for input consumer touch events. Only for Phone + mPipInputConsumer.setInputListener(mTouchHandler::handleTouchEvent); + mPipInputConsumer.setRegistrationListener(mTouchHandler::onRegistrationChanged); + } mAppOpsListener = pipAppOpsListener; displayController.addDisplayChangingController(mRotationController); displayController.addDisplayWindowListener(mFixedRotationListener); @@ -263,6 +281,57 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac } catch (RemoteException e) { Slog.e(TAG, "Failed to register pinned stack listener", e); } + + try { + ActivityTaskManager.RootTaskInfo taskInfo = ActivityTaskManager.getService() + .getRootTaskInfo(WINDOWING_MODE_PINNED, ACTIVITY_TYPE_UNDEFINED); + if (taskInfo != null) { + // If SystemUI restart, and it already existed a pinned stack, + // register the pip input consumer to ensure touch can send to it. + mPipInputConsumer.registerInputConsumer(true /* withSfVsync */); + } + } catch (RemoteException | UnsupportedOperationException e) { + Log.e(TAG, "Failed to register pinned stack listener", e); + e.printStackTrace(); + } + + // Handle for system task stack changes. + taskStackListener.addListener( + new TaskStackListenerCallback() { + @Override + public void onActivityPinned(String packageName, int userId, int taskId, + int stackId) { + mMainExecutor.execute(() -> { + mTouchHandler.onActivityPinned(); + mMediaController.onActivityPinned(); + mAppOpsListener.onActivityPinned(packageName); + }); + mPipInputConsumer.registerInputConsumer(true /* withSfVsync */); + } + + @Override + public void onActivityUnpinned() { + final Pair topPipActivityInfo = + PipUtils.getTopPipActivity(mContext); + final ComponentName topActivity = topPipActivityInfo.first; + mMainExecutor.execute(() -> { + mTouchHandler.onActivityUnpinned(topActivity); + mAppOpsListener.onActivityUnpinned(); + }); + mPipInputConsumer.unregisterInputConsumer(); + } + + @Override + public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task, + boolean homeTaskVisible, boolean clearedTask, boolean wasVisible) { + if (task.configuration.windowConfiguration.getWindowingMode() + != WINDOWING_MODE_PINNED) { + return; + } + mTouchHandler.getMotionHelper().expandLeavePip( + clearedTask /* skipAnimation */); + } + }); } @Override @@ -272,33 +341,6 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac }); } - @Override - public void onActivityPinned(String packageName) { - mMainExecutor.execute(() -> { - mTouchHandler.onActivityPinned(); - mMediaController.onActivityPinned(); - mAppOpsListener.onActivityPinned(packageName); - }); - } - - @Override - public void onActivityUnpinned(ComponentName topActivity) { - mMainExecutor.execute(() -> { - mTouchHandler.onActivityUnpinned(topActivity); - mAppOpsListener.onActivityUnpinned(); - }); - } - - @Override - public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task, - boolean clearedTask) { - if (task.configuration.windowConfiguration.getWindowingMode() - != WINDOWING_MODE_PINNED) { - return; - } - mTouchHandler.getMotionHelper().expandLeavePip(clearedTask /* skipAnimation */); - } - @Override public void onOverlayChanged() { mMainExecutor.execute(() -> { @@ -467,6 +509,7 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac mPipBoundsHandler.dump(pw, innerPrefix); mPipTaskOrganizer.dump(pw, innerPrefix); mPipBoundsState.dump(pw, innerPrefix); + mPipInputConsumer.dump(pw, innerPrefix); } /** @@ -476,17 +519,16 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac public static PipController create(Context context, DisplayController displayController, PipAppOpsListener pipAppOpsListener, PipBoundsHandler pipBoundsHandler, PipBoundsState pipBoundsState, PipMediaController pipMediaController, - PipMenuActivityController pipMenuActivityController, - PipTaskOrganizer pipTaskOrganizer, PipTouchHandler pipTouchHandler, - WindowManagerShellWrapper windowManagerShellWrapper, - ShellExecutor mainExecutor) { + PipMenuActivityController pipMenuActivityController, PipTaskOrganizer pipTaskOrganizer, + PipTouchHandler pipTouchHandler, WindowManagerShellWrapper windowManagerShellWrapper, + TaskStackListenerImpl taskStackListener, ShellExecutor mainExecutor) { if (!context.getPackageManager().hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)) { Slog.w(TAG, "Device doesn't support Pip feature"); return null; } return new PipController(context, displayController, pipAppOpsListener, pipBoundsHandler, - pipBoundsState, pipMediaController, pipMenuActivityController, - pipTaskOrganizer, pipTouchHandler, windowManagerShellWrapper, mainExecutor); + pipBoundsState, pipMediaController, pipMenuActivityController, pipTaskOrganizer, + pipTouchHandler, windowManagerShellWrapper, taskStackListener, mainExecutor); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/PipController.java index 3468b888c06a5..70b5446213298 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/PipController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/PipController.java @@ -44,16 +44,20 @@ import android.os.RemoteException; import android.os.UserHandle; import android.text.TextUtils; import android.util.Log; +import android.util.Pair; import android.view.DisplayInfo; import com.android.wm.shell.R; import com.android.wm.shell.WindowManagerShellWrapper; +import com.android.wm.shell.common.TaskStackListenerCallback; +import com.android.wm.shell.common.TaskStackListenerImpl; import com.android.wm.shell.pip.PinnedStackListenerForwarder; import com.android.wm.shell.pip.Pip; import com.android.wm.shell.pip.PipBoundsHandler; import com.android.wm.shell.pip.PipBoundsState; import com.android.wm.shell.pip.PipMediaController; import com.android.wm.shell.pip.PipTaskOrganizer; +import com.android.wm.shell.pip.PipUtils; import java.util.ArrayList; import java.util.List; @@ -225,6 +229,7 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac PipTaskOrganizer pipTaskOrganizer, PipMediaController pipMediaController, PipNotification pipNotification, + TaskStackListenerImpl taskStackListener, WindowManagerShellWrapper windowManagerShellWrapper) { mContext = context; mPipBoundsState = pipBoundsState; @@ -263,6 +268,27 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac Log.e(TAG, "Failed to register pinned stack listener", e); } + // Handle for system task stack changes. + taskStackListener.addListener( + new TaskStackListenerCallback() { + @Override + public void onTaskStackChanged() { + PipController.this.onTaskStackChanged(); + } + + @Override + public void onActivityPinned(String packageName, int userId, int taskId, + int stackId) { + PipController.this.onActivityPinned(packageName); + } + + @Override + public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task, + boolean homeTaskVisible, boolean clearedTask, boolean wasVisible) { + PipController.this.onActivityRestartAttempt(task, clearedTask); + } + }); + // TODO(b/169395392) Refactor PipMenuActivity to PipMenuView PipMenuActivity.setPipController(this); } @@ -351,8 +377,7 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac resizePinnedStack(STATE_NO_PIP); } - @Override - public void onActivityPinned(String packageName) { + private void onActivityPinned(String packageName) { if (DEBUG) Log.d(TAG, "onActivityPinned()"); RootTaskInfo taskInfo = getPinnedTaskInfo(); @@ -371,8 +396,7 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac } } - @Override - public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task, + private void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task, boolean clearedTask) { if (task.configuration.windowConfiguration.getWindowingMode() != WINDOWING_MODE_PINNED) { @@ -384,8 +408,7 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac movePipToFullscreen(); } - @Override - public void onTaskStackChanged() { + private void onTaskStackChanged() { if (DEBUG) Log.d(TAG, "onTaskStackChanged()"); if (getState() != STATE_NO_PIP) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java index d1176733deb95..cfe9b9157eaa1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java @@ -27,9 +27,6 @@ import java.util.function.Consumer; * Interface to engage split screen feature. */ public interface SplitScreen { - /** Returns {@code true} if split screen is supported on the device. */ - boolean isSplitScreenSupported(); - /** Called when keyguard showing state changed. */ void onKeyguardVisibilityChanged(boolean isShowing); @@ -48,15 +45,6 @@ public interface SplitScreen { /** Switch to minimized state if appropriate. */ void setMinimized(boolean minimized); - /** Called when there's an activity forced resizable. */ - void onActivityForcedResizable(String packageName, int taskId, int reason); - - /** Called when there's an activity dismissing split screen. */ - void onActivityDismissingSplitScreen(); - - /** Called when there's an activity launch on secondary display failed. */ - void onActivityLaunchOnSecondaryDisplayFailed(); - /** Called when there's a task undocking. */ void onUndockingTask(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java index 341a459902dba..49f11592a7fa6 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java @@ -20,9 +20,11 @@ import static android.app.ActivityManager.LOCK_TASK_MODE_PINNED; import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; +import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; import static android.view.Display.DEFAULT_DISPLAY; +import android.app.ActivityManager; import android.app.ActivityManager.RunningTaskInfo; import android.app.ActivityTaskManager; import android.content.Context; @@ -48,6 +50,8 @@ import com.android.wm.shell.common.DisplayImeController; import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.SystemWindows; +import com.android.wm.shell.common.TaskStackListenerCallback; +import com.android.wm.shell.common.TaskStackListenerImpl; import com.android.wm.shell.common.TransactionPool; import java.io.PrintWriter; @@ -107,7 +111,8 @@ public class SplitScreenController implements SplitScreen, public SplitScreenController(Context context, DisplayController displayController, SystemWindows systemWindows, DisplayImeController imeController, Handler handler, TransactionPool transactionPool, - ShellTaskOrganizer shellTaskOrganizer, SyncTransactionQueue syncQueue) { + ShellTaskOrganizer shellTaskOrganizer, SyncTransactionQueue syncQueue, + TaskStackListenerImpl taskStackListener) { mContext = context; mDisplayController = displayController; mSystemWindows = systemWindows; @@ -162,6 +167,40 @@ public class SplitScreenController implements SplitScreen, mWindowManager = new DividerWindowManager(mSystemWindows); mDisplayController.addDisplayWindowListener(this); // Don't initialize the divider or anything until we get the default display. + + taskStackListener.addListener( + new TaskStackListenerCallback() { + @Override + public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task, + boolean homeTaskVisible, boolean clearedTask, boolean wasVisible) { + if (!wasVisible || task.configuration.windowConfiguration.getWindowingMode() + != WINDOWING_MODE_SPLIT_SCREEN_PRIMARY + || !mSplits.isSplitScreenSupported()) { + return; + } + + if (isMinimized()) { + onUndockingTask(); + } + } + + @Override + public void onActivityForcedResizable(String packageName, int taskId, + int reason) { + mForcedResizableController.activityForcedResizable(packageName, taskId, + reason); + } + + @Override + public void onActivityDismissingDockedStack() { + mForcedResizableController.activityDismissingSplitScreen(); + } + + @Override + public void onActivityLaunchOnSecondaryDisplayFailed() { + mForcedResizableController.activityLaunchOnSecondaryDisplayFailed(); + } + }); } void onSplitScreenSupported() { @@ -172,11 +211,6 @@ public class SplitScreenController implements SplitScreen, mTaskOrganizer.applyTransaction(tct); } - @Override - public boolean isSplitScreenSupported() { - return mSplits.isSplitScreenSupported(); - } - @Override public void onKeyguardVisibilityChanged(boolean showing) { if (!isSplitActive() || mView == null) { @@ -403,21 +437,6 @@ public class SplitScreenController implements SplitScreen, mWindowManager.setTouchable(!mAdjustedForIme); } - @Override - public void onActivityForcedResizable(String packageName, int taskId, int reason) { - mForcedResizableController.activityForcedResizable(packageName, taskId, reason); - } - - @Override - public void onActivityDismissingSplitScreen() { - mForcedResizableController.activityDismissingSplitScreen(); - } - - @Override - public void onActivityLaunchOnSecondaryDisplayFailed() { - mForcedResizableController.activityLaunchOnSecondaryDisplayFailed(); - } - @Override public void onUndockingTask() { if (mView != null) { diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/TaskStackListenerImplTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/TaskStackListenerImplTest.java new file mode 100644 index 0000000000000..884287209d535 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/TaskStackListenerImplTest.java @@ -0,0 +1,258 @@ +/* + * 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 com.android.wm.shell.common; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import android.app.ActivityManager; +import android.app.IActivityTaskManager; +import android.content.ComponentName; +import android.os.Handler; +import android.os.IBinder; +import android.os.Message; +import android.os.RemoteException; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; + +import androidx.test.filters.SmallTest; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Tests for {@link com.android.wm.shell.common.TaskStackListenerImpl}. + */ +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper +@SmallTest +public class TaskStackListenerImplTest { + + @Mock + private IActivityTaskManager mActivityTaskManager; + + @Mock + private TaskStackListenerCallback mCallback; + + @Mock + private TaskStackListenerCallback mOtherCallback; + + private TaskStackListenerImpl mImpl; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mImpl = new TaskStackListenerImpl(mActivityTaskManager); + mImpl.setHandler(new ProxyToListenerImplHandler(mImpl)); + mImpl.addListener(mCallback); + mImpl.addListener(mOtherCallback); + } + + @Test + public void testAddRemoveMultipleListeners_ExpectRegisterUnregisterOnce() + throws RemoteException { + TaskStackListenerImpl impl = new TaskStackListenerImpl(mActivityTaskManager); + impl.setHandler(new ProxyToListenerImplHandler(impl)); + reset(mActivityTaskManager); + impl.addListener(mCallback); + impl.addListener(mOtherCallback); + verify(mActivityTaskManager, times(1)).registerTaskStackListener(any()); + + impl.removeListener(mOtherCallback); + impl.removeListener(mCallback); + verify(mActivityTaskManager, times(1)).unregisterTaskStackListener(any()); + } + + @Test + public void testOnRecentTaskListUpdated() { + mImpl.onRecentTaskListUpdated(); + verify(mCallback).onRecentTaskListUpdated(); + verify(mOtherCallback).onRecentTaskListUpdated(); + } + + @Test + public void testOnRecentTaskListFrozenChanged() { + mImpl.onRecentTaskListFrozenChanged(true); + verify(mCallback).onRecentTaskListFrozenChanged(eq(true)); + verify(mOtherCallback).onRecentTaskListFrozenChanged(eq(true)); + } + + @Test + public void testOnTaskStackChanged() { + mImpl.onTaskStackChanged(); + verify(mCallback).onTaskStackChangedBackground(); + verify(mCallback).onTaskStackChanged(); + verify(mOtherCallback).onTaskStackChangedBackground(); + verify(mOtherCallback).onTaskStackChanged(); + } + + @Test + public void testOnTaskProfileLocked() { + mImpl.onTaskProfileLocked(1, 2); + verify(mCallback).onTaskProfileLocked(eq(1), eq(2)); + verify(mOtherCallback).onTaskProfileLocked(eq(1), eq(2)); + } + + @Test + public void testOnTaskDisplayChanged() { + mImpl.onTaskDisplayChanged(1, 2); + verify(mCallback).onTaskDisplayChanged(eq(1), eq(2)); + verify(mOtherCallback).onTaskDisplayChanged(eq(1), eq(2)); + } + + @Test + public void testOnTaskCreated() { + mImpl.onTaskCreated(1, new ComponentName("a", "b")); + verify(mCallback).onTaskCreated(eq(1), eq(new ComponentName("a", "b"))); + verify(mOtherCallback).onTaskCreated(eq(1), eq(new ComponentName("a", "b"))); + } + + @Test + public void testOnTaskRemoved() { + mImpl.onTaskRemoved(123); + verify(mCallback).onTaskRemoved(eq(123)); + verify(mOtherCallback).onTaskRemoved(eq(123)); + } + + @Test + public void testOnTaskMovedToFront() { + ActivityManager.RunningTaskInfo info = mock(ActivityManager.RunningTaskInfo.class); + mImpl.onTaskMovedToFront(info); + verify(mCallback).onTaskMovedToFront(eq(info)); + verify(mOtherCallback).onTaskMovedToFront(eq(info)); + } + + @Test + public void testOnTaskDescriptionChanged() { + ActivityManager.RunningTaskInfo info = mock(ActivityManager.RunningTaskInfo.class); + mImpl.onTaskDescriptionChanged(info); + verify(mCallback).onTaskDescriptionChanged(eq(info)); + verify(mOtherCallback).onTaskDescriptionChanged(eq(info)); + } + + @Test + public void testOnTaskSnapshotChanged() { + ActivityManager.TaskSnapshot snapshot = mock(ActivityManager.TaskSnapshot.class); + mImpl.onTaskSnapshotChanged(123, snapshot); + verify(mCallback).onTaskSnapshotChanged(eq(123), eq(snapshot)); + verify(mOtherCallback).onTaskSnapshotChanged(eq(123), eq(snapshot)); + } + + @Test + public void testOnBackPressedOnTaskRoot() { + ActivityManager.RunningTaskInfo info = mock(ActivityManager.RunningTaskInfo.class); + mImpl.onBackPressedOnTaskRoot(info); + verify(mCallback).onBackPressedOnTaskRoot(eq(info)); + verify(mOtherCallback).onBackPressedOnTaskRoot(eq(info)); + } + + @Test + public void testOnActivityRestartAttempt() { + ActivityManager.RunningTaskInfo info = mock(ActivityManager.RunningTaskInfo.class); + mImpl.onActivityRestartAttempt(info, true, true, true); + verify(mCallback).onActivityRestartAttempt(eq(info), eq(true), eq(true), eq(true)); + verify(mOtherCallback).onActivityRestartAttempt(eq(info), eq(true), eq(true), eq(true)); + } + + @Test + public void testOnActivityPinned() { + mImpl.onActivityPinned("abc", 1, 2, 3); + verify(mCallback).onActivityPinned(eq("abc"), eq(1), eq(2), eq(3)); + verify(mOtherCallback).onActivityPinned(eq("abc"), eq(1), eq(2), eq(3)); + } + + @Test + public void testOnActivityUnpinned() { + mImpl.onActivityUnpinned(); + verify(mCallback).onActivityUnpinned(); + verify(mOtherCallback).onActivityUnpinned(); + } + + @Test + public void testOnActivityForcedResizable() { + mImpl.onActivityForcedResizable("abc", 1, 2); + verify(mCallback).onActivityForcedResizable(eq("abc"), eq(1), eq(2)); + verify(mOtherCallback).onActivityForcedResizable(eq("abc"), eq(1), eq(2)); + } + + @Test + public void testOnActivityDismissingDockedStack() { + mImpl.onActivityDismissingDockedStack(); + verify(mCallback).onActivityDismissingDockedStack(); + verify(mOtherCallback).onActivityDismissingDockedStack(); + } + + @Test + public void testOnActivityLaunchOnSecondaryDisplayFailed() { + ActivityManager.RunningTaskInfo info = mock(ActivityManager.RunningTaskInfo.class); + mImpl.onActivityLaunchOnSecondaryDisplayFailed(info, 1); + verify(mCallback).onActivityLaunchOnSecondaryDisplayFailed(eq(info)); + verify(mOtherCallback).onActivityLaunchOnSecondaryDisplayFailed(eq(info)); + } + + @Test + public void testOnActivityLaunchOnSecondaryDisplayRerouted() { + ActivityManager.RunningTaskInfo info = mock(ActivityManager.RunningTaskInfo.class); + mImpl.onActivityLaunchOnSecondaryDisplayRerouted(info, 1); + verify(mCallback).onActivityLaunchOnSecondaryDisplayRerouted(eq(info)); + verify(mOtherCallback).onActivityLaunchOnSecondaryDisplayRerouted(eq(info)); + } + + @Test + public void testOnActivityRequestedOrientationChanged() { + mImpl.onActivityRequestedOrientationChanged(1, 2); + verify(mCallback).onActivityRequestedOrientationChanged(eq(1), eq(2)); + verify(mOtherCallback).onActivityRequestedOrientationChanged(eq(1), eq(2)); + } + + @Test + public void testOnActivityRotation() { + mImpl.onActivityRotation(123); + verify(mCallback).onActivityRotation(eq(123)); + verify(mOtherCallback).onActivityRotation(eq(123)); + } + + @Test + public void testOnSizeCompatModeActivityChanged() { + IBinder b = mock(IBinder.class); + mImpl.onSizeCompatModeActivityChanged(123, b); + verify(mCallback).onSizeCompatModeActivityChanged(eq(123), eq(b)); + verify(mOtherCallback).onSizeCompatModeActivityChanged(eq(123), eq(b)); + } + + /** + * Handler that synchronously calls TaskStackListenerImpl#handleMessage() when it receives a + * message. + */ + private class ProxyToListenerImplHandler extends Handler { + public ProxyToListenerImplHandler(Callback callback) { + super(callback); + } + + @Override + public boolean sendMessageAtTime(Message msg, long uptimeMillis) { + return mImpl.handleMessage(msg); + } + } +} \ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java index 3645f1e56f923..8ef077e5f8578 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java @@ -33,6 +33,7 @@ import android.view.Display; import androidx.test.filters.SmallTest; import com.android.wm.shell.common.DisplayController; +import com.android.wm.shell.common.TaskStackListenerImpl; import org.junit.Before; import org.junit.Ignore; @@ -64,6 +65,8 @@ public class OneHandedControllerTest extends OneHandedTestCase { OneHandedTimeoutHandler mMockTimeoutHandler; @Mock IOverlayManager mMockOverlayManager; + @Mock + TaskStackListenerImpl mMockTaskStackListener; @Before public void setUp() throws Exception { @@ -76,7 +79,8 @@ public class OneHandedControllerTest extends OneHandedTestCase { mMockTouchHandler, mMockTutorialHandler, mMockGestureHandler, - mMockOverlayManager); + mMockOverlayManager, + mMockTaskStackListener); mOneHandedController = Mockito.spy(oneHandedController); mTimeoutHandler = Mockito.spy(OneHandedTimeoutHandler.get()); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedTutorialHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedTutorialHandlerTest.java index 3341c9cbacb9b..ba8c737924f40 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedTutorialHandlerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedTutorialHandlerTest.java @@ -25,6 +25,7 @@ import android.testing.TestableLooper; import androidx.test.filters.SmallTest; import com.android.wm.shell.common.DisplayController; +import com.android.wm.shell.common.TaskStackListenerImpl; import org.junit.Before; import org.junit.Test; @@ -47,6 +48,8 @@ public class OneHandedTutorialHandlerTest extends OneHandedTestCase { OneHandedDisplayAreaOrganizer mMockDisplayAreaOrganizer; @Mock IOverlayManager mMockOverlayManager; + @Mock + TaskStackListenerImpl mMockTaskStackListener; @Before public void setUp() { @@ -60,7 +63,8 @@ public class OneHandedTutorialHandlerTest extends OneHandedTestCase { mTouchHandler, mTutorialHandler, mGestureHandler, - mMockOverlayManager); + mMockOverlayManager, + mMockTaskStackListener); } @Test diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java index a00a3b6a654ed..745d18804f0ba 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java @@ -39,6 +39,7 @@ import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.WindowManagerShellWrapper; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.ShellExecutor; +import com.android.wm.shell.common.TaskStackListenerImpl; import com.android.wm.shell.pip.PipBoundsHandler; import com.android.wm.shell.pip.PipBoundsState; import com.android.wm.shell.pip.PipMediaController; @@ -68,6 +69,7 @@ public class PipControllerTest extends ShellTestCase { @Mock private PipTouchHandler mMockPipTouchHandler; @Mock private WindowManagerShellWrapper mMockWindowManagerShellWrapper; @Mock private PipBoundsState mMockPipBoundsState; + @Mock private TaskStackListenerImpl mMockTaskStackListener; @Mock private ShellExecutor mMockExecutor; @Before @@ -76,7 +78,8 @@ public class PipControllerTest extends ShellTestCase { mPipController = new PipController(mContext, mMockDisplayController, mMockPipAppOpsListener, mMockPipBoundsHandler, mMockPipBoundsState, mMockPipMediaController, mMockPipMenuActivityController, mMockPipTaskOrganizer, - mMockPipTouchHandler, mMockWindowManagerShellWrapper, mMockExecutor); + mMockPipTouchHandler, mMockWindowManagerShellWrapper, mMockTaskStackListener, + mMockExecutor); doAnswer(invocation -> { ((Runnable) invocation.getArgument(0)).run(); return null; @@ -108,7 +111,8 @@ public class PipControllerTest extends ShellTestCase { assertNull(PipController.create(spyContext, mMockDisplayController, mMockPipAppOpsListener, mMockPipBoundsHandler, mMockPipBoundsState, mMockPipMediaController, mMockPipMenuActivityController, mMockPipTaskOrganizer, - mMockPipTouchHandler, mMockWindowManagerShellWrapper, mMockExecutor)); + mMockPipTouchHandler, mMockWindowManagerShellWrapper, mMockTaskStackListener, + mMockExecutor)); } @Test diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/InputConsumerController.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InputConsumerController.java index 27e4c85e1e025..bf23a49df2c3f 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/InputConsumerController.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InputConsumerController.java @@ -36,6 +36,7 @@ import java.io.PrintWriter; /** * Manages the input consumer that allows the SystemUI to directly receive input. + * TODO: Refactor this for the gesture nav case */ public class InputConsumerController { @@ -98,14 +99,6 @@ public class InputConsumerController { mName = name; } - /** - * @return A controller for the pip input consumer. - */ - public static InputConsumerController getPipInputConsumer() { - return new InputConsumerController(WindowManagerGlobal.getWindowManagerService(), - INPUT_CONSUMER_PIP); - } - /** * @return A controller for the recents animation input consumer. */ @@ -155,7 +148,6 @@ public class InputConsumerController { if (mInputEventReceiver == null) { final InputChannel inputChannel = new InputChannel(); try { - // TODO(b/113087003): Support Picture-in-picture in multi-display. mWindowManager.destroyInputConsumer(mName, DEFAULT_DISPLAY); mWindowManager.createInputConsumer(mToken, mName, DEFAULT_DISPLAY, inputChannel); } catch (RemoteException e) { @@ -175,7 +167,6 @@ public class InputConsumerController { public void unregisterInputConsumer() { if (mInputEventReceiver != null) { try { - // TODO(b/113087003): Support Picture-in-picture in multi-display. mWindowManager.destroyInputConsumer(mName, DEFAULT_DISPLAY); } catch (RemoteException e) { Log.e(TAG, "Failed to destroy input consumer", e); diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java index 17bb40e69e27a..f073ced0bcee0 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java @@ -118,10 +118,7 @@ public class SystemUIFactory { .setBubbles(Optional.ofNullable(null)) .setShellDump(Optional.ofNullable(null)); } - mSysUIComponent = builder - .setInputConsumerController(mWMComponent.getInputConsumerController()) - .setShellTaskOrganizer(mWMComponent.getShellTaskOrganizer()) - .build(); + mSysUIComponent = builder.build(); if (initializeComponents) { mSysUIComponent.init(); } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java index b94a68b2441de..54aeab50763ff 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java @@ -22,11 +22,9 @@ import com.android.systemui.InitController; import com.android.systemui.SystemUIAppComponentFactory; import com.android.systemui.dump.DumpManager; import com.android.systemui.keyguard.KeyguardSliceProvider; -import com.android.systemui.shared.system.InputConsumerController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.util.InjectionInflationController; import com.android.wm.shell.ShellDump; -import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.bubbles.Bubbles; import com.android.wm.shell.onehanded.OneHanded; import com.android.wm.shell.pip.Pip; @@ -67,12 +65,6 @@ public interface SysUIComponent { @BindsInstance Builder setBubbles(Optional b); - @BindsInstance - Builder setInputConsumerController(InputConsumerController i); - - @BindsInstance - Builder setShellTaskOrganizer(ShellTaskOrganizer s); - @BindsInstance Builder setShellDump(Optional shellDump); diff --git a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java index 8f3d8eaac2d34..9154ddba5d880 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java @@ -16,11 +16,9 @@ package com.android.systemui.dagger; -import com.android.systemui.shared.system.InputConsumerController; import com.android.systemui.wmshell.WMShellModule; import com.android.wm.shell.ShellDump; import com.android.wm.shell.ShellInit; -import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.bubbles.Bubbles; import com.android.wm.shell.onehanded.OneHanded; import com.android.wm.shell.pip.Pip; @@ -60,14 +58,6 @@ public interface WMComponent { @WMSingleton Optional getShellDump(); - // TODO(b/162923491): Refactor this out so Pip doesn't need to inject this - @WMSingleton - InputConsumerController getInputConsumerController(); - - // TODO(b/162923491): To be removed once Bubbles migrates over to the Shell - @WMSingleton - ShellTaskOrganizer getShellTaskOrganizer(); - // TODO(b/162923491): We currently pass the instances through to SysUI, but that may change // depending on the threading mechanism we go with @WMSingleton diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/TvPipModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/TvPipModule.java index 5caab6ab0e7ee..694b4a03fa64e 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/TvPipModule.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/TvPipModule.java @@ -22,6 +22,7 @@ import com.android.systemui.dagger.WMSingleton; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.WindowManagerShellWrapper; import com.android.wm.shell.common.DisplayController; +import com.android.wm.shell.common.TaskStackListenerImpl; import com.android.wm.shell.pip.Pip; import com.android.wm.shell.pip.PipBoundsHandler; import com.android.wm.shell.pip.PipBoundsState; @@ -54,6 +55,7 @@ public abstract class TvPipModule { PipTaskOrganizer pipTaskOrganizer, PipMediaController pipMediaController, PipNotification pipNotification, + TaskStackListenerImpl taskStackListener, WindowManagerShellWrapper windowManagerShellWrapper) { return Optional.of( new PipController( @@ -63,6 +65,7 @@ public abstract class TvPipModule { pipTaskOrganizer, pipMediaController, pipNotification, + taskStackListener, windowManagerShellWrapper)); } diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/TvWMShellModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/TvWMShellModule.java index 294c749a2abe1..f88bedd88d9f9 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/TvWMShellModule.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/TvWMShellModule.java @@ -27,6 +27,7 @@ import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayImeController; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.SystemWindows; +import com.android.wm.shell.common.TaskStackListenerImpl; import com.android.wm.shell.common.TransactionPool; import com.android.wm.shell.splitscreen.SplitScreen; import com.android.wm.shell.splitscreen.SplitScreenController; @@ -57,8 +58,9 @@ public class TvWMShellModule { DisplayController displayController, SystemWindows systemWindows, DisplayImeController displayImeController, @Main Handler handler, TransactionPool transactionPool, ShellTaskOrganizer shellTaskOrganizer, - SyncTransactionQueue syncQueue) { + SyncTransactionQueue syncQueue, TaskStackListenerImpl taskStackListener) { return new SplitScreenController(context, displayController, systemWindows, - displayImeController, handler, transactionPool, shellTaskOrganizer, syncQueue); + displayImeController, handler, transactionPool, shellTaskOrganizer, syncQueue, + taskStackListener); } } diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java index f896891c5039d..7a244389a3bcf 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java @@ -55,7 +55,6 @@ import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.keyguard.ScreenLifecycle; import com.android.systemui.model.SysUiState; import com.android.systemui.navigationbar.NavigationModeController; -import com.android.systemui.shared.system.InputConsumerController; import com.android.systemui.shared.system.TaskStackChangeListener; import com.android.systemui.shared.system.TaskStackChangeListeners; import com.android.systemui.shared.tracing.ProtoTraceable; @@ -71,7 +70,6 @@ import com.android.wm.shell.onehanded.OneHandedEvents; import com.android.wm.shell.onehanded.OneHandedGestureHandler.OneHandedGestureEventCallback; import com.android.wm.shell.onehanded.OneHandedTransitionCallback; import com.android.wm.shell.pip.Pip; -import com.android.wm.shell.pip.PipUtils; import com.android.wm.shell.protolog.ShellProtoLogImpl; import com.android.wm.shell.splitscreen.SplitScreen; @@ -100,9 +98,7 @@ public final class WMShell extends SystemUI private final CommandQueue mCommandQueue; private final ConfigurationController mConfigurationController; - private final InputConsumerController mInputConsumerController; private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; - private final TaskStackChangeListeners mTaskStackChangeListeners; private final NavigationModeController mNavigationModeController; private final ScreenLifecycle mScreenLifecycle; private final SysUiState mSysUiState; @@ -120,9 +116,7 @@ public final class WMShell extends SystemUI @Inject public WMShell(Context context, CommandQueue commandQueue, ConfigurationController configurationController, - InputConsumerController inputConsumerController, KeyguardUpdateMonitor keyguardUpdateMonitor, - TaskStackChangeListeners taskStackChangeListeners, NavigationModeController navigationModeController, ScreenLifecycle screenLifecycle, SysUiState sysUiState, @@ -134,9 +128,7 @@ public final class WMShell extends SystemUI super(context); mCommandQueue = commandQueue; mConfigurationController = configurationController; - mInputConsumerController = inputConsumerController; mKeyguardUpdateMonitor = keyguardUpdateMonitor; - mTaskStackChangeListeners = taskStackChangeListeners; mNavigationModeController = navigationModeController; mScreenLifecycle = screenLifecycle; mSysUiState = sysUiState; @@ -192,58 +184,6 @@ public final class WMShell extends SystemUI } }); - // TODO: Move this into the shell - // Handle for system task stack changes. - mTaskStackChangeListeners.registerTaskStackListener( - new TaskStackChangeListener() { - @Override - public void onTaskStackChanged() { - pip.onTaskStackChanged(); - } - - @Override - public void onActivityPinned(String packageName, int userId, int taskId, - int stackId) { - pip.onActivityPinned(packageName); - mInputConsumerController.registerInputConsumer(true /* withSfVsync */); - } - - @Override - public void onActivityUnpinned() { - final Pair topPipActivityInfo = - PipUtils.getTopPipActivity(mContext); - final ComponentName topActivity = topPipActivityInfo.first; - pip.onActivityUnpinned(topActivity); - mInputConsumerController.unregisterInputConsumer(); - } - - @Override - public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task, - boolean homeTaskVisible, boolean clearedTask, boolean wasVisible) { - pip.onActivityRestartAttempt(task, clearedTask); - } - }); - - try { - RootTaskInfo taskInfo = ActivityTaskManager.getService().getRootTaskInfo( - WINDOWING_MODE_PINNED, ACTIVITY_TYPE_UNDEFINED); - if (taskInfo != null) { - // If SystemUI restart, and it already existed a pinned stack, - // register the pip input consumer to ensure touch can send to it. - mInputConsumerController.registerInputConsumer(true /* withSfVsync */); - } - } catch (RemoteException | UnsupportedOperationException e) { - Log.e(TAG, "Failed to register pinned stack listener", e); - e.printStackTrace(); - } - - // Register the listener for input consumer touch events. Only for Phone - if (pip.getPipTouchHandler() != null) { - mInputConsumerController.setInputListener(pip.getPipTouchHandler()::handleTouchEvent); - mInputConsumerController.setRegistrationListener( - pip.getPipTouchHandler()::onRegistrationChanged); - } - // The media session listener needs to be re-registered when switching users UserInfoController userInfoController = Dependency.get(UserInfoController.class); userInfoController.addCallback((String name, Drawable picture, String userAccount) -> @@ -263,39 +203,6 @@ public final class WMShell extends SystemUI } }; mKeyguardUpdateMonitor.registerCallback(mSplitScreenKeyguardCallback); - - mTaskStackChangeListeners.registerTaskStackListener( - new TaskStackChangeListener() { - @Override - public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task, - boolean homeTaskVisible, boolean clearedTask, boolean wasVisible) { - if (!wasVisible || task.configuration.windowConfiguration.getWindowingMode() - != WINDOWING_MODE_SPLIT_SCREEN_PRIMARY - || !splitScreen.isSplitScreenSupported()) { - return; - } - - if (splitScreen.isMinimized()) { - splitScreen.onUndockingTask(); - } - } - - @Override - public void onActivityForcedResizable(String packageName, int taskId, - int reason) { - splitScreen.onActivityForcedResizable(packageName, taskId, reason); - } - - @Override - public void onActivityDismissingDockedStack() { - splitScreen.onActivityDismissingSplitScreen(); - } - - @Override - public void onActivityLaunchOnSecondaryDisplayFailed() { - splitScreen.onActivityLaunchOnSecondaryDisplayFailed(); - } - }); } @VisibleForTesting @@ -375,21 +282,6 @@ public final class WMShell extends SystemUI } } }); - - mTaskStackChangeListeners.registerTaskStackListener( - new TaskStackChangeListener() { - @Override - public void onTaskCreated(int taskId, ComponentName componentName) { - oneHanded.stopOneHanded( - OneHandedEvents.EVENT_ONE_HANDED_TRIGGER_APP_TAPS_OUT); - } - - @Override - public void onTaskMovedToFront(int taskId) { - oneHanded.stopOneHanded( - OneHandedEvents.EVENT_ONE_HANDED_TRIGGER_APP_TAPS_OUT); - } - }); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java index bdca503f40c1e..6546ed5577b19 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java @@ -43,6 +43,7 @@ import com.android.wm.shell.common.HandlerExecutor; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.SystemWindows; +import com.android.wm.shell.common.TaskStackListenerImpl; import com.android.wm.shell.common.TransactionPool; import com.android.wm.shell.draganddrop.DragAndDropController; import com.android.wm.shell.onehanded.OneHanded; @@ -114,12 +115,6 @@ public abstract class WMShellBaseModule { return new DragAndDropController(context, displayController); } - @WMSingleton - @Provides - static InputConsumerController provideInputConsumerController() { - return InputConsumerController.getPipInputConsumer(); - } - @WMSingleton @Provides static FloatingContentCoordinator provideFloatingContentCoordinator() { @@ -181,6 +176,12 @@ public abstract class WMShellBaseModule { mainExecutor, AnimationThread.instance().getExecutor()); } + @WMSingleton + @Provides + static TaskStackListenerImpl providerTaskStackListenerImpl(@Main Handler handler) { + return new TaskStackListenerImpl(handler); + } + @BindsOptionalOf abstract SplitScreen optionalSplitScreen(); @@ -203,8 +204,9 @@ public abstract class WMShellBaseModule { @WMSingleton @Provides static Optional provideOneHandedController(Context context, - DisplayController displayController) { - return Optional.ofNullable(OneHandedController.create(context, displayController)); + DisplayController displayController, TaskStackListenerImpl taskStackListener) { + return Optional.ofNullable(OneHandedController.create(context, displayController, + taskStackListener)); } @WMSingleton diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java index 25ca7ade60776..1cdec27af8703 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java @@ -30,6 +30,7 @@ import com.android.wm.shell.common.FloatingContentCoordinator; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.SystemWindows; +import com.android.wm.shell.common.TaskStackListenerImpl; import com.android.wm.shell.common.TransactionPool; import com.android.wm.shell.pip.Pip; import com.android.wm.shell.pip.PipBoundsHandler; @@ -72,9 +73,10 @@ public class WMShellModule { DisplayController displayController, SystemWindows systemWindows, DisplayImeController displayImeController, @Main Handler handler, TransactionPool transactionPool, ShellTaskOrganizer shellTaskOrganizer, - SyncTransactionQueue syncQueue) { + SyncTransactionQueue syncQueue, TaskStackListenerImpl taskStackListener) { return new SplitScreenController(context, displayController, systemWindows, - displayImeController, handler, transactionPool, shellTaskOrganizer, syncQueue); + displayImeController, handler, transactionPool, shellTaskOrganizer, syncQueue, + taskStackListener); } @WMSingleton @@ -84,11 +86,11 @@ public class WMShellModule { PipBoundsState pipBoundsState, PipMediaController pipMediaController, PipMenuActivityController pipMenuActivityController, PipTaskOrganizer pipTaskOrganizer, PipTouchHandler pipTouchHandler, WindowManagerShellWrapper windowManagerShellWrapper, - ShellExecutor mainExecutor) { + TaskStackListenerImpl taskStackListener, ShellExecutor mainExecutor) { return Optional.ofNullable(PipController.create(context, displayController, pipAppOpsListener, pipBoundsHandler, pipBoundsState, pipMediaController, pipMenuActivityController, pipTaskOrganizer, pipTouchHandler, - windowManagerShellWrapper, mainExecutor)); + windowManagerShellWrapper, taskStackListener, mainExecutor)); } @WMSingleton diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java index d0bf281f343f9..6a303a9419605 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java @@ -62,7 +62,6 @@ public class WMShellTest extends SysuiTestCase { @Mock CommandQueue mCommandQueue; @Mock ConfigurationController mConfigurationController; @Mock KeyguardUpdateMonitor mKeyguardUpdateMonitor; - @Mock TaskStackChangeListeners mTaskStackChangeListeners; @Mock InputConsumerController mMockInputConsumerController; @Mock NavigationModeController mNavigationModeController; @Mock ScreenLifecycle mScreenLifecycle; @@ -77,13 +76,11 @@ public class WMShellTest extends SysuiTestCase { @Before public void setUp() { MockitoAnnotations.initMocks(this); - mInputConsumerController = InputConsumerController.getPipInputConsumer(); mWMShell = new WMShell(mContext, mCommandQueue, mConfigurationController, - mInputConsumerController, mKeyguardUpdateMonitor, mTaskStackChangeListeners, - mNavigationModeController, mScreenLifecycle, mSysUiState, Optional.of(mPip), - Optional.of(mSplitScreen), Optional.of(mOneHanded), mProtoTracer, - Optional.of(mShellDump)); + mKeyguardUpdateMonitor, mNavigationModeController, + mScreenLifecycle, mSysUiState, Optional.of(mPip), Optional.of(mSplitScreen), + Optional.of(mOneHanded), mProtoTracer, Optional.of(mShellDump)); when(mPip.getPipTouchHandler()).thenReturn(mPipTouchHandler); } @@ -100,8 +97,6 @@ public class WMShellTest extends SysuiTestCase { mWMShell.initSplitScreen(mSplitScreen); verify(mKeyguardUpdateMonitor).registerCallback(any(KeyguardUpdateMonitorCallback.class)); - verify(mTaskStackChangeListeners).registerTaskStackListener( - any(TaskStackChangeListener.class)); } @Test @@ -113,8 +108,6 @@ public class WMShellTest extends SysuiTestCase { verify(mScreenLifecycle).addObserver(any(ScreenLifecycle.Observer.class)); verify(mNavigationModeController).addListener( any(NavigationModeController.ModeChangedListener.class)); - verify(mTaskStackChangeListeners).registerTaskStackListener( - any(TaskStackChangeListener.class)); verify(mOneHanded).registerGestureCallback(any( OneHandedGestureHandler.OneHandedGestureEventCallback.class)); From 6dd73e98d77af599253ae26a2076abceaa44a78c Mon Sep 17 00:00:00 2001 From: Winson Chung Date: Thu, 5 Nov 2020 15:57:36 -0800 Subject: [PATCH 2/2] 6/ Fix some flakey issues with dragging to split - Ensure that the task info activity type always matches the actual task activity type (can be different than the window configuration type) - Ensure that the targets hit rects respect the insets, and that dragging out of all targets (but still within the window) will hide the last target - Work around race with launching secondary task when splitting, wait until the split screen task org has moved existing tasks to secondary split root before starting the new task, and also update the home task position when going home (b/172686383) - Add a timeout to clean up drag surfaces if SysUI crashes while mid-drag - Clean up some other calls to get activity type/win mode Bug: 169894807 Test: atest DragDropControllerTests Test: atest DragAndDropPolicyTest Test: atest SplitScreenTests Change-Id: I7ae11cc38b1e927d955798ef1056e17c9e435758 --- .../android/wm/shell/ShellTaskOrganizer.java | 8 +-- .../shell/draganddrop/DragAndDropPolicy.java | 59 +++++++++++++------ .../wm/shell/draganddrop/DragLayout.java | 18 ++++-- .../wm/shell/pip/phone/PipController.java | 3 +- .../wm/shell/pip/tv/PipController.java | 3 +- .../wm/shell/splitscreen/SplitScreen.java | 3 + .../splitscreen/SplitScreenController.java | 22 +++++-- .../splitscreen/SplitScreenTaskListener.java | 19 +++++- .../shell/splitscreen/WindowManagerProxy.java | 7 +-- .../draganddrop/DragAndDropPolicyTest.java | 10 ++++ .../android/server/wm/DragDropController.java | 9 +++ .../java/com/android/server/wm/DragState.java | 5 ++ .../core/java/com/android/server/wm/Task.java | 4 ++ 13 files changed, 124 insertions(+), 46 deletions(-) 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 51ddb17daa009..2d20feeb0832b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java @@ -346,15 +346,9 @@ public class ShellTaskOrganizer extends TaskOrganizer { return mTaskListeners.get(taskListenerType); } - @WindowingMode - public static int getWindowingMode(RunningTaskInfo taskInfo) { - return taskInfo.configuration.windowConfiguration.getWindowingMode(); - } - @VisibleForTesting static @TaskListenerType int taskInfoToTaskListenerType(RunningTaskInfo runningTaskInfo) { - final int windowingMode = getWindowingMode(runningTaskInfo); - switch (windowingMode) { + switch (runningTaskInfo.getWindowingMode()) { case WINDOWING_MODE_FULLSCREEN: return runningTaskInfo.letterboxActivityBounds != null ? TASK_LISTENER_TYPE_LETTERBOX diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java index 25890bc31b3c5..8a547b4477fd9 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java @@ -18,6 +18,7 @@ package com.android.wm.shell.draganddrop; import static android.app.ActivityTaskManager.INVALID_TASK_ID; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.content.ClipDescription.EXTRA_ACTIVITY_OPTIONS; @@ -67,6 +68,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.List; +import java.util.function.Consumer; /** * The policy for handling drag and drop operations to shell. @@ -133,20 +135,20 @@ public class DragAndDropPolicy { // TODO(b/169894807): For now, only allow splitting to the right/bottom until we // have split pairs mTargets.add(new Target(TYPE_FULLSCREEN, - new Rect(0, 0, w, h / 2), + new Rect(l, t, l + iw, t + ih / 2), new Rect(l, t, l + iw, t + ih), new Rect(0, 0, w, h))); mTargets.add(new Target(TYPE_SPLIT_BOTTOM, - new Rect(0, h / 2, w, h), + new Rect(l, t + ih / 2, l + iw, t + ih), new Rect(l, t + ih / 2, l + iw, t + ih), new Rect(0, h / 2, w, h))); } else { mTargets.add(new Target(TYPE_FULLSCREEN, - new Rect(0, 0, w / 2, h), + new Rect(l, t, l + iw / 2, t + ih), new Rect(l, t, l + iw, t + ih), new Rect(0, 0, w, h))); mTargets.add(new Target(TYPE_SPLIT_RIGHT, - new Rect(w / 2, 0, w, h), + new Rect(l + iw / 2, t, l + iw, t + ih), new Rect(l + iw / 2, t, l + iw, t + ih), new Rect(w / 2, 0, w, h))); } @@ -162,12 +164,12 @@ public class DragAndDropPolicy { secondarySplitBounds.intersect(new Rect(l, t, l + iw, t + ih)); if (isVerticalSplit) { mTargets.add(new Target(TYPE_FULLSCREEN, - new Rect(0, 0, w, secondarySplitRawBounds.top), + new Rect(l, t, l + iw, secondarySplitRawBounds.top), new Rect(l, t, l + iw, t + ih), new Rect(0, 0, w, secondarySplitRawBounds.top))); } else { mTargets.add(new Target(TYPE_FULLSCREEN, - new Rect(0, 0, secondarySplitRawBounds.left, h), + new Rect(l, t, secondarySplitRawBounds.left, t + ih), new Rect(l, t, l + iw, t + ih), new Rect(0, 0, w, h))); } @@ -178,7 +180,7 @@ public class DragAndDropPolicy { } else { // Otherwise only show the fullscreen target mTargets.add(new Target(TYPE_FULLSCREEN, - new Rect(0, 0, w, h), + new Rect(l, t, l + iw, t + ih), new Rect(l, t, l + iw, t + ih), new Rect(0, 0, w, h))); } @@ -210,6 +212,7 @@ public class DragAndDropPolicy { final boolean isShortcut = description.hasMimeType(MIMETYPE_APPLICATION_SHORTCUT); final Intent dragData = mSession.dragData; + boolean deferAppLaunchUntilSplit = false; if (target.type == TYPE_FULLSCREEN) { if (mSplitScreen != null && mSplitScreen.isDividerVisible()) { // If in split, remove split and launch fullscreen @@ -226,20 +229,40 @@ public class DragAndDropPolicy { // Not in split, enter split now mStarter.enterSplitScreen(mSession.runningTaskId, target.type == TYPE_SPLIT_LEFT || target.type == TYPE_SPLIT_TOP); + deferAppLaunchUntilSplit = true; } } - Bundle opts = dragData.hasExtra(EXTRA_ACTIVITY_OPTIONS) - ? dragData.getBundleExtra(EXTRA_ACTIVITY_OPTIONS) - : null; - if (isTask) { - mStarter.startTask(dragData.getIntExtra(EXTRA_TASK_ID, INVALID_TASK_ID), opts); - } else if (isShortcut) { - mStarter.startShortcut(dragData.getStringExtra(EXTRA_PACKAGE_NAME), - dragData.getStringExtra(EXTRA_SHORTCUT_ID), - opts, dragData.getParcelableExtra(EXTRA_USER)); + final Runnable startAppRunnable = () -> { + Bundle opts = dragData.hasExtra(EXTRA_ACTIVITY_OPTIONS) + ? dragData.getBundleExtra(EXTRA_ACTIVITY_OPTIONS) + : null; + if (isTask) { + mStarter.startTask(dragData.getIntExtra(EXTRA_TASK_ID, INVALID_TASK_ID), opts); + } else if (isShortcut) { + mStarter.startShortcut(dragData.getStringExtra(EXTRA_PACKAGE_NAME), + dragData.getStringExtra(EXTRA_SHORTCUT_ID), + opts, dragData.getParcelableExtra(EXTRA_USER)); + } else { + mStarter.startIntent(dragData.getParcelableExtra(EXTRA_PENDING_INTENT), opts); + } + }; + if (deferAppLaunchUntilSplit) { + // TODO(b/169894807): The enterSplitScreen() call above will trigger the current task + // into split, and we should wait for home and other tasks to be moved to + // split-secondary before trying to launch the new secondary task. This can be removed + // once we have app-pairs. + mSplitScreen.registerInSplitScreenListener(new Consumer() { + @Override + public void accept(Boolean inSplit) { + if (inSplit) { + startAppRunnable.run(); + mSplitScreen.unregisterInSplitScreenListener(this); + } + } + }); } else { - mStarter.startIntent(dragData.getParcelableExtra(EXTRA_PENDING_INTENT), opts); + startAppRunnable.run(); } } @@ -274,7 +297,7 @@ public class DragAndDropPolicy { * Updates the session data based on the current state of the system. */ void update() { - final ClipDescription description = mInitialDragData.getDescription(); + try { List tasks = mIActivityTaskManager.getFilteredTasks(1, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java index fa857cdd174c3..5b7531c09a7b7 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java @@ -135,17 +135,25 @@ public class DragLayout extends View { // visibility of the current region DragAndDropPolicy.Target target = mPolicy.getTargetAtLocation( (int) event.getX(), (int) event.getY()); - if (target != null && mCurrentTarget != target) { + if (mCurrentTarget != target) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, "Current target: %s", target); - Interpolator boundsInterpolator = FAST_OUT_SLOW_IN; - if (mCurrentTarget == null) { + if (target == null) { + // Animating to no target + mDropOutline.startVisibilityAnimation(false, LINEAR); + Rect finalBounds = new Rect(mCurrentTarget.drawRegion); + finalBounds.inset(mDisplayMargin, mDisplayMargin); + mDropOutline.startBoundsAnimation(finalBounds, FAST_OUT_LINEAR_IN); + } else if (mCurrentTarget == null) { + // Animating to first target mDropOutline.startVisibilityAnimation(true, LINEAR); Rect initialBounds = new Rect(target.drawRegion); initialBounds.inset(mDisplayMargin, mDisplayMargin); mDropOutline.setRegionBounds(initialBounds); - boundsInterpolator = LINEAR_OUT_SLOW_IN; + mDropOutline.startBoundsAnimation(target.drawRegion, LINEAR_OUT_SLOW_IN); + } else { + // Bounds change + mDropOutline.startBoundsAnimation(target.drawRegion, FAST_OUT_SLOW_IN); } - mDropOutline.startBoundsAnimation(target.drawRegion, boundsInterpolator); mCurrentTarget = target; } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java index 251f8d4722459..854096c1d12f1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java @@ -324,8 +324,7 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac @Override public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task, boolean homeTaskVisible, boolean clearedTask, boolean wasVisible) { - if (task.configuration.windowConfiguration.getWindowingMode() - != WINDOWING_MODE_PINNED) { + if (task.getWindowingMode() != WINDOWING_MODE_PINNED) { return; } mTouchHandler.getMotionHelper().expandLeavePip( diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/PipController.java index 70b5446213298..402d79c6fbd1b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/PipController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/PipController.java @@ -398,8 +398,7 @@ public class PipController implements Pip, PipTaskOrganizer.PipTransitionCallbac private void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task, boolean clearedTask) { - if (task.configuration.windowConfiguration.getWindowingMode() - != WINDOWING_MODE_PINNED) { + if (task.getWindowingMode() != WINDOWING_MODE_PINNED) { return; } if (DEBUG) Log.d(TAG, "onPinnedActivityRestartAttempt()"); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java index cfe9b9157eaa1..e55f065c1bb21 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java @@ -57,6 +57,9 @@ public interface SplitScreen { /** Registers listener that gets called whenever the existence of the divider changes. */ void registerInSplitScreenListener(Consumer listener); + /** Unregisters listener that gets called whenever the existence of the divider changes. */ + void unregisterInSplitScreenListener(Consumer listener); + /** Registers listener that gets called whenever the split screen bounds changes. */ void registerBoundsChangeListener(BiConsumer listener); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java index 49f11592a7fa6..07af289c4f352 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java @@ -58,6 +58,7 @@ import java.io.PrintWriter; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; import java.util.function.BiConsumer; import java.util.function.Consumer; @@ -85,8 +86,8 @@ public class SplitScreenController implements SplitScreen, private final WindowManagerProxy mWindowManagerProxy; private final TaskOrganizer mTaskOrganizer; - private final ArrayList>> mDockedStackExistsListeners = - new ArrayList<>(); + private final CopyOnWriteArrayList>> mDockedStackExistsListeners + = new CopyOnWriteArrayList<>(); private final ArrayList>> mBoundsChangedListeners = new ArrayList<>(); @@ -173,7 +174,7 @@ public class SplitScreenController implements SplitScreen, @Override public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task, boolean homeTaskVisible, boolean clearedTask, boolean wasVisible) { - if (!wasVisible || task.configuration.windowConfiguration.getWindowingMode() + if (!wasVisible || task.getWindowingMode() != WINDOWING_MODE_SPLIT_SCREEN_PRIMARY || !mSplits.isSplitScreenSupported()) { return; @@ -477,6 +478,17 @@ public class SplitScreenController implements SplitScreen, } } + @Override + public void unregisterInSplitScreenListener(Consumer listener) { + synchronized (mDockedStackExistsListeners) { + for (int i = mDockedStackExistsListeners.size() - 1; i >= 0; i--) { + if (mDockedStackExistsListeners.get(i) == listener) { + mDockedStackExistsListeners.remove(i); + } + } + } + } + @Override public void registerBoundsChangeListener(BiConsumer listener) { synchronized (mBoundsChangedListeners) { @@ -500,9 +512,7 @@ public class SplitScreenController implements SplitScreen, } // Note: The set of running tasks from the system is ordered by recency. final RunningTaskInfo topRunningTask = runningTasks.get(0); - - final int activityType = topRunningTask.configuration.windowConfiguration - .getActivityType(); + final int activityType = topRunningTask.getActivityType(); if (activityType == ACTIVITY_TYPE_HOME || activityType == ACTIVITY_TYPE_RECENTS) { return false; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTaskListener.java index 64e9d66183900..5b2b38ba81899 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTaskListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTaskListener.java @@ -23,7 +23,6 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMAR import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; import static android.view.Display.DEFAULT_DISPLAY; -import static com.android.wm.shell.ShellTaskOrganizer.getWindowingMode; import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TASK_ORG; import android.app.ActivityManager.RunningTaskInfo; @@ -42,6 +41,7 @@ import com.android.wm.shell.Transitions; import com.android.wm.shell.common.SyncTransactionQueue; import java.io.PrintWriter; +import java.util.ArrayList; class SplitScreenTaskListener implements ShellTaskOrganizer.TaskListener { private static final String TAG = "SplitScreenTaskListener"; @@ -106,7 +106,7 @@ class SplitScreenTaskListener implements ShellTaskOrganizer.TaskListener { return; } - final int winMode = getWindowingMode(taskInfo); + final int winMode = taskInfo.getWindowingMode(); if (winMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) { ProtoLog.v(WM_SHELL_TASK_ORG, "%s onTaskAppeared Primary taskId=%d", TAG, taskInfo.taskId); @@ -283,6 +283,21 @@ class SplitScreenTaskListener implements ShellTaskOrganizer.TaskListener { mSplitScreenController.startEnterSplit(); } } else if (secondaryImpliesMinimize) { + // Workaround for b/172686383, we can't rely on the sync bounds change transaction for + // the home task to finish before the last updateChildTaskSurface() call even if it's + // queued on the sync transaction queue, so ensure that the home task surface is updated + // again before we minimize + final ArrayList tasks = new ArrayList<>(); + mSplitScreenController.getWmProxy().getHomeAndRecentsTasks(tasks, + mSplitScreenController.getSecondaryRoot()); + for (int i = 0; i < tasks.size(); i++) { + final RunningTaskInfo taskInfo = tasks.get(i); + final SurfaceControl leash = mLeashByTaskId.get(taskInfo.taskId); + if (leash != null) { + updateChildTaskSurface(taskInfo, leash, false /* firstAppeared */); + } + } + // Both splits are populated but the secondary split has a home/recents stack on top, // so enter minimized mode. mSplitScreenController.ensureMinimizedSplit(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/WindowManagerProxy.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/WindowManagerProxy.java index c51bbeb7b6c28..0307206e2defa 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/WindowManagerProxy.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/WindowManagerProxy.java @@ -120,7 +120,7 @@ class WindowManagerProxy { new WindowOrganizer().applyTransaction(t); } - private boolean getHomeAndRecentsTasks(List out, + boolean getHomeAndRecentsTasks(List out, WindowContainerToken parent) { boolean resizable = false; List rootTasks = parent == null @@ -209,8 +209,7 @@ class WindowManagerProxy { continue; } // Only move fullscreen tasks to split secondary. - if (rootTask.configuration.windowConfiguration.getWindowingMode() - != WINDOWING_MODE_FULLSCREEN) { + if (rootTask.getWindowingMode() != WINDOWING_MODE_FULLSCREEN) { continue; } // Since this iterates from bottom to top, update topHomeTask for every fullscreen task @@ -232,7 +231,7 @@ class WindowManagerProxy { } boolean isHomeOrRecentTask(ActivityManager.RunningTaskInfo ti) { - final int atype = ti.configuration.windowConfiguration.getActivityType(); + final int atype = ti.getActivityType(); return atype == ACTIVITY_TYPE_HOME || atype == ACTIVITY_TYPE_RECENTS; } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java index affd7367f4729..fad1f057267ab 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java @@ -35,6 +35,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.reset; @@ -68,10 +69,13 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; +import java.util.function.Consumer; /** * Tests for the drag and drop policy. @@ -124,6 +128,12 @@ public class DragAndDropPolicyTest { doReturn(new Rect(50, 0, 100, 100)).when(divider) .getNonMinimizedSplitScreenSecondaryBounds(); + doAnswer((Answer) invocation -> { + Consumer callback = invocation.getArgument(0); + callback.accept(true); + return null; + }).when(mSplitScreen).registerInSplitScreenListener(any()); + mPolicy = new DragAndDropPolicy(mContext, mIActivityTaskManager, mSplitScreen, mStarter); mActivityClipData = createClipData(MIMETYPE_APPLICATION_ACTIVITY); mNonResizeableActivityClipData = createClipData(MIMETYPE_APPLICATION_ACTIVITY); diff --git a/services/core/java/com/android/server/wm/DragDropController.java b/services/core/java/com/android/server/wm/DragDropController.java index 3eac1be3ebeed..627af9149fe52 100644 --- a/services/core/java/com/android/server/wm/DragDropController.java +++ b/services/core/java/com/android/server/wm/DragDropController.java @@ -49,6 +49,7 @@ class DragDropController { static final int MSG_DRAG_END_TIMEOUT = 0; static final int MSG_TEAR_DOWN_DRAG_AND_DROP_INPUT = 1; static final int MSG_ANIMATION_END = 2; + static final int MSG_REMOVE_DRAG_SURFACE_TIMEOUT = 3; /** * Drag state per operation. @@ -384,6 +385,14 @@ class DragDropController { } break; } + + case MSG_REMOVE_DRAG_SURFACE_TIMEOUT: { + synchronized (mService.mGlobalLock) { + mService.mTransactionFactory.get() + .reparent((SurfaceControl) msg.obj, null).apply(); + } + break; + } } } } diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java index 8c80205a9e457..e7d8ad690bc4c 100644 --- a/services/core/java/com/android/server/wm/DragState.java +++ b/services/core/java/com/android/server/wm/DragState.java @@ -23,6 +23,7 @@ import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION; import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_TRANSACTIONS; import static com.android.server.wm.DragDropController.MSG_ANIMATION_END; import static com.android.server.wm.DragDropController.MSG_DRAG_END_TIMEOUT; +import static com.android.server.wm.DragDropController.MSG_REMOVE_DRAG_SURFACE_TIMEOUT; import static com.android.server.wm.DragDropController.MSG_TEAR_DOWN_DRAG_AND_DROP_INPUT; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DRAG; import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS; @@ -59,6 +60,7 @@ import android.view.WindowManager; import android.view.animation.DecelerateInterpolator; import android.view.animation.Interpolator; +import com.android.internal.os.SomeArgs; import com.android.internal.protolog.common.ProtoLog; import com.android.internal.view.IDragAndDropPermissions; import com.android.server.LocalServices; @@ -248,6 +250,9 @@ class DragState { if (mSurfaceControl != null) { if (!mRelinquishDragSurface) { mTransaction.reparent(mSurfaceControl, null).apply(); + } else { + mDragDropController.sendTimeoutMessage(MSG_REMOVE_DRAG_SURFACE_TIMEOUT, + mSurfaceControl); } mSurfaceControl = null; } diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 0b2ba8727abe5..3290282791266 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -4072,6 +4072,10 @@ class Task extends WindowContainer { info.taskDescription = new ActivityManager.TaskDescription(getTaskDescription()); info.supportsSplitScreenMultiWindow = supportsSplitScreenWindowingMode(); info.configuration.setTo(getConfiguration()); + // Update to the task's current activity type and windowing mode which may differ from the + // window configuration + info.configuration.windowConfiguration.setActivityType(getActivityType()); + info.configuration.windowConfiguration.setWindowingMode(getWindowingMode()); info.token = mRemoteToken.toWindowContainerToken(); //TODO (AM refactor): Just use local once updateEffectiveIntent is run during all child