diff options
9 files changed, 210 insertions, 22 deletions
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java index 44a467ffcf3d..cbd544cc4b86 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java @@ -18,9 +18,21 @@ package com.android.wm.shell.desktopmode; import com.android.wm.shell.common.annotations.ExternalThread; +import java.util.concurrent.Executor; + /** * Interface to interact with desktop mode feature in shell. */ @ExternalThread public interface DesktopMode { + + /** + * Adds a listener to find out about changes in the visibility of freeform tasks. + * + * @param listener the listener to add. + * @param callbackExecutor the executor to call the listener on. + */ + void addListener(DesktopModeTaskRepository.VisibleTasksListener listener, + Executor callbackExecutor); + } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java index b96facf4c46e..c8c36f504f76 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java @@ -60,6 +60,7 @@ import com.android.wm.shell.transition.Transitions; import java.util.ArrayList; import java.util.Comparator; +import java.util.concurrent.Executor; /** * Handles windowing changes when desktop mode system setting changes @@ -132,6 +133,17 @@ public class DesktopModeController implements RemoteCallable<DesktopModeControll return new IDesktopModeImpl(this); } + /** + * Adds a listener to find out about changes in the visibility of freeform tasks. + * + * @param listener the listener to add. + * @param callbackExecutor the executor to call the listener on. + */ + public void addListener(DesktopModeTaskRepository.VisibleTasksListener listener, + Executor callbackExecutor) { + mDesktopModeTaskRepository.addVisibleTasksListener(listener, callbackExecutor); + } + @VisibleForTesting void updateDesktopModeActive(boolean active) { ProtoLog.d(WM_SHELL_DESKTOP_MODE, "updateDesktopModeActive: active=%s", active); @@ -293,7 +305,14 @@ public class DesktopModeController implements RemoteCallable<DesktopModeControll */ @ExternalThread private final class DesktopModeImpl implements DesktopMode { - // Do nothing + + @Override + public void addListener(DesktopModeTaskRepository.VisibleTasksListener listener, + Executor callbackExecutor) { + mMainExecutor.execute(() -> { + DesktopModeController.this.addListener(listener, callbackExecutor); + }); + } } /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt index 988601c0e8a8..c91d54a62ae6 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt @@ -16,7 +16,9 @@ package com.android.wm.shell.desktopmode +import android.util.ArrayMap import android.util.ArraySet +import java.util.concurrent.Executor /** * Keeps track of task data related to desktop mode. @@ -30,20 +32,39 @@ class DesktopModeTaskRepository { * Task gets removed from this list when it vanishes. Or when desktop mode is turned off. */ private val activeTasks = ArraySet<Int>() - private val listeners = ArraySet<Listener>() + private val visibleTasks = ArraySet<Int>() + private val activeTasksListeners = ArraySet<ActiveTasksListener>() + // Track visible tasks separately because a task may be part of the desktop but not visible. + private val visibleTasksListeners = ArrayMap<VisibleTasksListener, Executor>() /** - * Add a [Listener] to be notified of updates to the repository. + * Add a [ActiveTasksListener] to be notified of updates to active tasks in the repository. */ - fun addListener(listener: Listener) { - listeners.add(listener) + fun addActiveTaskListener(activeTasksListener: ActiveTasksListener) { + activeTasksListeners.add(activeTasksListener) } /** - * Remove a previously registered [Listener] + * Add a [VisibleTasksListener] to be notified when freeform tasks are visible or not. */ - fun removeListener(listener: Listener) { - listeners.remove(listener) + fun addVisibleTasksListener(visibleTasksListener: VisibleTasksListener, executor: Executor) { + visibleTasksListeners.put(visibleTasksListener, executor) + executor.execute( + Runnable { visibleTasksListener.onVisibilityChanged(visibleTasks.size > 0) }) + } + + /** + * Remove a previously registered [ActiveTasksListener] + */ + fun removeActiveTasksListener(activeTasksListener: ActiveTasksListener) { + activeTasksListeners.remove(activeTasksListener) + } + + /** + * Remove a previously registered [VisibleTasksListener] + */ + fun removeVisibleTasksListener(visibleTasksListener: VisibleTasksListener) { + visibleTasksListeners.remove(visibleTasksListener) } /** @@ -52,7 +73,7 @@ class DesktopModeTaskRepository { fun addActiveTask(taskId: Int) { val added = activeTasks.add(taskId) if (added) { - listeners.onEach { it.onActiveTasksChanged() } + activeTasksListeners.onEach { it.onActiveTasksChanged() } } } @@ -62,7 +83,7 @@ class DesktopModeTaskRepository { fun removeActiveTask(taskId: Int) { val removed = activeTasks.remove(taskId) if (removed) { - listeners.onEach { it.onActiveTasksChanged() } + activeTasksListeners.onEach { it.onActiveTasksChanged() } } } @@ -81,9 +102,43 @@ class DesktopModeTaskRepository { } /** - * Defines interface for classes that can listen to changes in repository state. + * Updates whether a freeform task with this id is visible or not and notifies listeners. + */ + fun updateVisibleFreeformTasks(taskId: Int, visible: Boolean) { + val prevCount: Int = visibleTasks.size + if (visible) { + visibleTasks.add(taskId) + } else { + visibleTasks.remove(taskId) + } + if (prevCount == 0 && visibleTasks.size == 1 || + prevCount > 0 && visibleTasks.size == 0) { + for ((listener, executor) in visibleTasksListeners) { + executor.execute( + Runnable { listener.onVisibilityChanged(visibleTasks.size > 0) }) + } + } + } + + /** + * Defines interface for classes that can listen to changes for active tasks in desktop mode. + */ + interface ActiveTasksListener { + /** + * Called when the active tasks change in desktop mode. + */ + @JvmDefault + fun onActiveTasksChanged() {} + } + + /** + * Defines interface for classes that can listen to changes for visible tasks in desktop mode. */ - interface Listener { - fun onActiveTasksChanged() + interface VisibleTasksListener { + /** + * Called when the desktop starts or stops showing freeform tasks. + */ + @JvmDefault + fun onVisibilityChanged(hasVisibleFreeformTasks: Boolean) {} } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java index f82a34621262..eaa7158abbe5 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java @@ -90,6 +90,8 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE, "Adding active freeform task: #%d", taskInfo.taskId); mDesktopModeTaskRepository.ifPresent(it -> it.addActiveTask(taskInfo.taskId)); + mDesktopModeTaskRepository.ifPresent( + it -> it.updateVisibleFreeformTasks(taskInfo.taskId, true)); } } @@ -103,6 +105,8 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE, "Removing active freeform task: #%d", taskInfo.taskId); mDesktopModeTaskRepository.ifPresent(it -> it.removeActiveTask(taskInfo.taskId)); + mDesktopModeTaskRepository.ifPresent( + it -> it.updateVisibleFreeformTasks(taskInfo.taskId, false)); } if (!Transitions.ENABLE_SHELL_TRANSITIONS) { @@ -124,6 +128,8 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener { "Adding active freeform task: #%d", taskInfo.taskId); mDesktopModeTaskRepository.ifPresent(it -> it.addActiveTask(taskInfo.taskId)); } + mDesktopModeTaskRepository.ifPresent( + it -> it.updateVisibleFreeformTasks(taskInfo.taskId, taskInfo.isVisible)); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java index 08f3db65e62f..f9172ba183de 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java @@ -68,7 +68,7 @@ import java.util.function.Consumer; * Manages the recent task list from the system, caching it as necessary. */ public class RecentTasksController implements TaskStackListenerCallback, - RemoteCallable<RecentTasksController>, DesktopModeTaskRepository.Listener { + RemoteCallable<RecentTasksController>, DesktopModeTaskRepository.ActiveTasksListener { private static final String TAG = RecentTasksController.class.getSimpleName(); private final Context mContext; @@ -147,7 +147,7 @@ public class RecentTasksController implements TaskStackListenerCallback, this::createExternalInterface, this); mShellCommandHandler.addDumpCallback(this::dump, this); mTaskStackListener.addListener(this); - mDesktopModeTaskRepository.ifPresent(it -> it.addListener(this)); + mDesktopModeTaskRepository.ifPresent(it -> it.addActiveTaskListener(this)); } /** diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt index 9b28d11f6a9d..aaa5c8a35acb 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt @@ -19,6 +19,7 @@ package com.android.wm.shell.desktopmode import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest import com.android.wm.shell.ShellTestCase +import com.android.wm.shell.TestShellExecutor import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test @@ -38,7 +39,7 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() { @Test fun addActiveTask_listenerNotifiedAndTaskIsActive() { val listener = TestListener() - repo.addListener(listener) + repo.addActiveTaskListener(listener) repo.addActiveTask(1) assertThat(listener.activeTaskChangedCalls).isEqualTo(1) @@ -48,7 +49,7 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() { @Test fun addActiveTask_sameTaskDoesNotNotify() { val listener = TestListener() - repo.addListener(listener) + repo.addActiveTaskListener(listener) repo.addActiveTask(1) repo.addActiveTask(1) @@ -58,7 +59,7 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() { @Test fun addActiveTask_multipleTasksAddedNotifiesForEach() { val listener = TestListener() - repo.addListener(listener) + repo.addActiveTaskListener(listener) repo.addActiveTask(1) repo.addActiveTask(2) @@ -68,7 +69,7 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() { @Test fun removeActiveTask_listenerNotifiedAndTaskNotActive() { val listener = TestListener() - repo.addListener(listener) + repo.addActiveTaskListener(listener) repo.addActiveTask(1) repo.removeActiveTask(1) @@ -80,7 +81,7 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() { @Test fun removeActiveTask_removeNotExistingTaskDoesNotNotify() { val listener = TestListener() - repo.addListener(listener) + repo.addActiveTaskListener(listener) repo.removeActiveTask(99) assertThat(listener.activeTaskChangedCalls).isEqualTo(0) } @@ -90,10 +91,69 @@ class DesktopModeTaskRepositoryTest : ShellTestCase() { assertThat(repo.isActiveTask(99)).isFalse() } - class TestListener : DesktopModeTaskRepository.Listener { + @Test + fun addListener_notifiesVisibleFreeformTask() { + repo.updateVisibleFreeformTasks(1, true) + val listener = TestVisibilityListener() + val executor = TestShellExecutor() + repo.addVisibleTasksListener(listener, executor) + executor.flushAll() + + assertThat(listener.hasVisibleFreeformTasks).isTrue() + assertThat(listener.visibleFreeformTaskChangedCalls).isEqualTo(1) + } + + @Test + fun updateVisibleFreeformTasks_addVisibleTasksNotifiesListener() { + val listener = TestVisibilityListener() + val executor = TestShellExecutor() + repo.addVisibleTasksListener(listener, executor) + repo.updateVisibleFreeformTasks(1, true) + repo.updateVisibleFreeformTasks(2, true) + executor.flushAll() + + assertThat(listener.hasVisibleFreeformTasks).isTrue() + // Equal to 2 because adding the listener notifies the current state + assertThat(listener.visibleFreeformTaskChangedCalls).isEqualTo(2) + } + + @Test + fun updateVisibleFreeformTasks_removeVisibleTasksNotifiesListener() { + val listener = TestVisibilityListener() + val executor = TestShellExecutor() + repo.addVisibleTasksListener(listener, executor) + repo.updateVisibleFreeformTasks(1, true) + repo.updateVisibleFreeformTasks(2, true) + executor.flushAll() + + assertThat(listener.hasVisibleFreeformTasks).isTrue() + repo.updateVisibleFreeformTasks(1, false) + executor.flushAll() + + // Equal to 2 because adding the listener notifies the current state + assertThat(listener.visibleFreeformTaskChangedCalls).isEqualTo(2) + + repo.updateVisibleFreeformTasks(2, false) + executor.flushAll() + + assertThat(listener.hasVisibleFreeformTasks).isFalse() + assertThat(listener.visibleFreeformTaskChangedCalls).isEqualTo(3) + } + + class TestListener : DesktopModeTaskRepository.ActiveTasksListener { var activeTaskChangedCalls = 0 override fun onActiveTasksChanged() { activeTaskChangedCalls++ } } + + class TestVisibilityListener : DesktopModeTaskRepository.VisibleTasksListener { + var hasVisibleFreeformTasks = false + var visibleFreeformTaskChangedCalls = 0 + + override fun onVisibilityChanged(hasVisibleTasks: Boolean) { + hasVisibleFreeformTasks = hasVisibleTasks + visibleFreeformTaskChangedCalls++ + } + } } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java index f2742b7889b1..766266d9cc94 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java @@ -110,6 +110,9 @@ public class QuickStepContract { public static final int SYSUI_STATE_IMMERSIVE_MODE = 1 << 24; // The voice interaction session window is showing public static final int SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING = 1 << 25; + // Freeform windows are showing in desktop mode + public static final int SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE = 1 << 26; + @Retention(RetentionPolicy.SOURCE) @IntDef({SYSUI_STATE_SCREEN_PINNING, @@ -137,7 +140,8 @@ public class QuickStepContract { SYSUI_STATE_BACK_DISABLED, SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED, SYSUI_STATE_IMMERSIVE_MODE, - SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING + SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING, + SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE }) public @interface SystemUiStateFlags {} @@ -173,6 +177,8 @@ public class QuickStepContract { ? "bubbles_mange_menu_expanded" : ""); str.add((flags & SYSUI_STATE_IMMERSIVE_MODE) != 0 ? "immersive_mode" : ""); str.add((flags & SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING) != 0 ? "vis_win_showing" : ""); + str.add((flags & SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE) != 0 + ? "freeform_active_in_desktop_mode" : ""); return str.toString(); } diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java index fbc6a582da2e..309f1681b964 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java @@ -22,6 +22,7 @@ import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_B import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DIALOG_SHOWING; +import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_ONE_HANDED_ACTIVE; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED; @@ -55,6 +56,8 @@ import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.tracing.ProtoTracer; import com.android.systemui.tracing.nano.SystemUiTraceProto; +import com.android.wm.shell.desktopmode.DesktopMode; +import com.android.wm.shell.desktopmode.DesktopModeTaskRepository; import com.android.wm.shell.floating.FloatingTasks; import com.android.wm.shell.nano.WmShellTraceProto; import com.android.wm.shell.onehanded.OneHanded; @@ -111,6 +114,7 @@ public final class WMShell implements private final Optional<SplitScreen> mSplitScreenOptional; private final Optional<OneHanded> mOneHandedOptional; private final Optional<FloatingTasks> mFloatingTasksOptional; + private final Optional<DesktopMode> mDesktopModeOptional; private final CommandQueue mCommandQueue; private final ConfigurationController mConfigurationController; @@ -173,6 +177,7 @@ public final class WMShell implements Optional<SplitScreen> splitScreenOptional, Optional<OneHanded> oneHandedOptional, Optional<FloatingTasks> floatingTasksOptional, + Optional<DesktopMode> desktopMode, CommandQueue commandQueue, ConfigurationController configurationController, KeyguardStateController keyguardStateController, @@ -194,6 +199,7 @@ public final class WMShell implements mPipOptional = pipOptional; mSplitScreenOptional = splitScreenOptional; mOneHandedOptional = oneHandedOptional; + mDesktopModeOptional = desktopMode; mWakefulnessLifecycle = wakefulnessLifecycle; mProtoTracer = protoTracer; mUserTracker = userTracker; @@ -219,6 +225,7 @@ public final class WMShell implements mPipOptional.ifPresent(this::initPip); mSplitScreenOptional.ifPresent(this::initSplitScreen); mOneHandedOptional.ifPresent(this::initOneHanded); + mDesktopModeOptional.ifPresent(this::initDesktopMode); } @VisibleForTesting @@ -326,6 +333,16 @@ public final class WMShell implements }); } + void initDesktopMode(DesktopMode desktopMode) { + desktopMode.addListener(new DesktopModeTaskRepository.VisibleTasksListener() { + @Override + public void onVisibilityChanged(boolean hasFreeformTasks) { + mSysUiState.setFlag(SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE, hasFreeformTasks) + .commitUpdate(DEFAULT_DISPLAY); + } + }, mSysUiMainExecutor); + } + @Override public void writeToProto(SystemUiTraceProto proto) { if (proto.wmShell == null) { 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 cebe946a459c..7af66f641837 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java @@ -34,6 +34,8 @@ import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.tracing.ProtoTracer; import com.android.wm.shell.common.ShellExecutor; +import com.android.wm.shell.desktopmode.DesktopMode; +import com.android.wm.shell.desktopmode.DesktopModeTaskRepository; import com.android.wm.shell.floating.FloatingTasks; import com.android.wm.shell.onehanded.OneHanded; import com.android.wm.shell.onehanded.OneHandedEventCallback; @@ -49,6 +51,7 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.util.Optional; +import java.util.concurrent.Executor; /** * Tests for {@link WMShell}. @@ -76,12 +79,14 @@ public class WMShellTest extends SysuiTestCase { @Mock UserTracker mUserTracker; @Mock ShellExecutor mSysUiMainExecutor; @Mock FloatingTasks mFloatingTasks; + @Mock DesktopMode mDesktopMode; @Before public void setUp() { MockitoAnnotations.initMocks(this); mWMShell = new WMShell(mContext, mShellInterface, Optional.of(mPip), Optional.of(mSplitScreen), Optional.of(mOneHanded), Optional.of(mFloatingTasks), + Optional.of(mDesktopMode), mCommandQueue, mConfigurationController, mKeyguardStateController, mKeyguardUpdateMonitor, mScreenLifecycle, mSysUiState, mProtoTracer, mWakefulnessLifecycle, mUserTracker, mSysUiMainExecutor); @@ -103,4 +108,12 @@ public class WMShellTest extends SysuiTestCase { verify(mOneHanded).registerTransitionCallback(any(OneHandedTransitionCallback.class)); verify(mOneHanded).registerEventCallback(any(OneHandedEventCallback.class)); } + + @Test + public void initDesktopMode_registersListener() { + mWMShell.initDesktopMode(mDesktopMode); + verify(mDesktopMode).addListener( + any(DesktopModeTaskRepository.VisibleTasksListener.class), + any(Executor.class)); + } } |