Merge "Track visible tasks in DesktopModeController and add a listener" into tm-qpr-dev am: 731095e5cf am: 9cec9fba06

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/19957743

Change-Id: Ic1116f1a7c606d2130f46ade7c9218b4b4a7940e
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
Mady Mellor
2022-10-24 18:04:24 +00:00
committed by Automerger Merge Worker
9 changed files with 210 additions and 22 deletions

View File

@@ -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);
}

View File

@@ -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);
});
}
}
/**

View File

@@ -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.
*/
interface Listener {
fun onActiveTasksChanged()
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 VisibleTasksListener {
/**
* Called when the desktop starts or stops showing freeform tasks.
*/
@JvmDefault
fun onVisibilityChanged(hasVisibleFreeformTasks: Boolean) {}
}
}

View File

@@ -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));
}
}

View File

@@ -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));
}
/**

View File

@@ -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++
}
}
}

View File

@@ -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();
}

View File

@@ -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) {

View File

@@ -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));
}
}