Handle desktop mode setting change
Listen to system setting change for desktop mode. When it changes, update root display area windowing mode to either freeform or fullscreen. Bug: 241944030 Test: atest DesktopModeControllerTest ShellTaskOrganizerTests Change-Id: I7962deab1f287f7cb177f9673e88409b4273276e
This commit is contained in:
@@ -16,14 +16,20 @@
|
||||
|
||||
package com.android.wm.shell;
|
||||
|
||||
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE;
|
||||
|
||||
import android.app.WindowConfiguration;
|
||||
import android.util.SparseArray;
|
||||
import android.view.SurfaceControl;
|
||||
import android.window.DisplayAreaAppearedInfo;
|
||||
import android.window.DisplayAreaInfo;
|
||||
import android.window.DisplayAreaOrganizer;
|
||||
import android.window.WindowContainerTransaction;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.android.internal.protolog.common.ProtoLog;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executor;
|
||||
@@ -102,10 +108,44 @@ public class RootDisplayAreaOrganizer extends DisplayAreaOrganizer {
|
||||
mDisplayAreasInfo.put(displayId, displayAreaInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link WindowContainerTransaction} to update display windowing mode.
|
||||
*
|
||||
* @param displayId display id to update windowing mode for
|
||||
* @param windowingMode target {@link WindowConfiguration.WindowingMode}
|
||||
* @return {@link WindowContainerTransaction} with pending operation to set windowing mode
|
||||
*/
|
||||
public WindowContainerTransaction prepareWindowingModeChange(int displayId,
|
||||
@WindowConfiguration.WindowingMode int windowingMode) {
|
||||
WindowContainerTransaction wct = new WindowContainerTransaction();
|
||||
DisplayAreaInfo displayAreaInfo = mDisplayAreasInfo.get(displayId);
|
||||
if (displayAreaInfo == null) {
|
||||
ProtoLog.e(WM_SHELL_DESKTOP_MODE,
|
||||
"unable to update windowing mode for display %d display not found", displayId);
|
||||
return wct;
|
||||
}
|
||||
|
||||
ProtoLog.d(WM_SHELL_DESKTOP_MODE,
|
||||
"setWindowingMode: displayId=%d current wmMode=%d new wmMode=%d", displayId,
|
||||
displayAreaInfo.configuration.windowConfiguration.getWindowingMode(),
|
||||
windowingMode);
|
||||
|
||||
wct.setWindowingMode(displayAreaInfo.token, windowingMode);
|
||||
return wct;
|
||||
}
|
||||
|
||||
public void dump(@NonNull PrintWriter pw, String prefix) {
|
||||
final String innerPrefix = prefix + " ";
|
||||
final String childPrefix = innerPrefix + " ";
|
||||
pw.println(prefix + this);
|
||||
|
||||
for (int i = 0; i < mDisplayAreasInfo.size(); i++) {
|
||||
int displayId = mDisplayAreasInfo.keyAt(i);
|
||||
DisplayAreaInfo displayAreaInfo = mDisplayAreasInfo.get(displayId);
|
||||
int windowingMode =
|
||||
displayAreaInfo.configuration.windowConfiguration.getWindowingMode();
|
||||
pw.println(innerPrefix + "# displayId=" + displayId + " wmMode=" + windowingMode);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -22,6 +22,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
|
||||
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
|
||||
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
|
||||
|
||||
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE;
|
||||
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TASK_ORG;
|
||||
import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
|
||||
|
||||
@@ -46,6 +47,7 @@ import android.window.StartingWindowInfo;
|
||||
import android.window.StartingWindowRemovalInfo;
|
||||
import android.window.TaskAppearedInfo;
|
||||
import android.window.TaskOrganizer;
|
||||
import android.window.WindowContainerTransaction;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.protolog.common.ProtoLog;
|
||||
@@ -690,6 +692,49 @@ public class ShellTaskOrganizer extends TaskOrganizer implements
|
||||
taskListener.reparentChildSurfaceToTask(taskId, sc, t);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link WindowContainerTransaction} to clear task bounds.
|
||||
*
|
||||
* @param displayId display id for tasks that will have bounds cleared
|
||||
* @return {@link WindowContainerTransaction} with pending operations to clear bounds
|
||||
*/
|
||||
public WindowContainerTransaction prepareClearBoundsForTasks(int displayId) {
|
||||
ProtoLog.d(WM_SHELL_DESKTOP_MODE, "prepareClearBoundsForTasks: displayId=%d", displayId);
|
||||
WindowContainerTransaction wct = new WindowContainerTransaction();
|
||||
for (int i = 0; i < mTasks.size(); i++) {
|
||||
RunningTaskInfo taskInfo = mTasks.valueAt(i).getTaskInfo();
|
||||
if (taskInfo.displayId == displayId) {
|
||||
ProtoLog.d(WM_SHELL_DESKTOP_MODE, "clearing bounds for token=%s taskInfo=%s",
|
||||
taskInfo.token, taskInfo);
|
||||
wct.setBounds(taskInfo.token, null);
|
||||
}
|
||||
}
|
||||
return wct;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link WindowContainerTransaction} to clear task level freeform setting.
|
||||
*
|
||||
* @param displayId display id for tasks that will have windowing mode reset to {@link
|
||||
* WindowConfiguration#WINDOWING_MODE_UNDEFINED}
|
||||
* @return {@link WindowContainerTransaction} with pending operations to clear windowing mode
|
||||
*/
|
||||
public WindowContainerTransaction prepareClearFreeformForTasks(int displayId) {
|
||||
ProtoLog.d(WM_SHELL_DESKTOP_MODE, "prepareClearFreeformForTasks: displayId=%d", displayId);
|
||||
WindowContainerTransaction wct = new WindowContainerTransaction();
|
||||
for (int i = 0; i < mTasks.size(); i++) {
|
||||
RunningTaskInfo taskInfo = mTasks.valueAt(i).getTaskInfo();
|
||||
if (taskInfo.displayId == displayId
|
||||
&& taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
|
||||
ProtoLog.d(WM_SHELL_DESKTOP_MODE,
|
||||
"clearing windowing mode for token=%s taskInfo=%s", taskInfo.token,
|
||||
taskInfo);
|
||||
wct.setWindowingMode(taskInfo.token, WINDOWING_MODE_UNDEFINED);
|
||||
}
|
||||
}
|
||||
return wct;
|
||||
}
|
||||
|
||||
private void logSizeCompatRestartButtonEventReported(@NonNull TaskAppearedInfo info,
|
||||
int event) {
|
||||
ActivityInfo topActivityInfo = info.getTaskInfo().topActivityInfo;
|
||||
@@ -816,7 +861,14 @@ public class ShellTaskOrganizer extends TaskOrganizer implements
|
||||
final int key = mTasks.keyAt(i);
|
||||
final TaskAppearedInfo info = mTasks.valueAt(i);
|
||||
final TaskListener listener = getTaskListener(info.getTaskInfo());
|
||||
pw.println(innerPrefix + "#" + i + " task=" + key + " listener=" + listener);
|
||||
final int windowingMode = info.getTaskInfo().getWindowingMode();
|
||||
String pkg = "";
|
||||
if (info.getTaskInfo().baseActivity != null) {
|
||||
pkg = info.getTaskInfo().baseActivity.getPackageName();
|
||||
}
|
||||
Rect bounds = info.getTaskInfo().getConfiguration().windowConfiguration.getBounds();
|
||||
pw.println(innerPrefix + "#" + i + " task=" + key + " listener=" + listener
|
||||
+ " wmMode=" + windowingMode + " pkg=" + pkg + " bounds=" + bounds);
|
||||
}
|
||||
|
||||
pw.println();
|
||||
@@ -826,6 +878,7 @@ public class ShellTaskOrganizer extends TaskOrganizer implements
|
||||
final TaskListener listener = mLaunchCookieToListener.valueAt(i);
|
||||
pw.println(innerPrefix + "#" + i + " cookie=" + key + " listener=" + listener);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ import com.android.internal.jank.InteractionJankMonitor;
|
||||
import com.android.internal.logging.UiEventLogger;
|
||||
import com.android.internal.statusbar.IStatusBarService;
|
||||
import com.android.launcher3.icons.IconProvider;
|
||||
import com.android.wm.shell.RootDisplayAreaOrganizer;
|
||||
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
|
||||
import com.android.wm.shell.ShellTaskOrganizer;
|
||||
import com.android.wm.shell.TaskViewTransitions;
|
||||
@@ -48,6 +49,7 @@ import com.android.wm.shell.common.TaskStackListenerImpl;
|
||||
import com.android.wm.shell.common.TransactionPool;
|
||||
import com.android.wm.shell.common.annotations.ShellBackgroundThread;
|
||||
import com.android.wm.shell.common.annotations.ShellMainThread;
|
||||
import com.android.wm.shell.desktopmode.DesktopModeController;
|
||||
import com.android.wm.shell.draganddrop.DragAndDropController;
|
||||
import com.android.wm.shell.freeform.FreeformComponents;
|
||||
import com.android.wm.shell.freeform.FreeformTaskListener;
|
||||
@@ -573,6 +575,27 @@ public abstract class WMShellModule {
|
||||
);
|
||||
}
|
||||
|
||||
//
|
||||
// Desktop mode (optional feature)
|
||||
//
|
||||
|
||||
@WMSingleton
|
||||
@Provides
|
||||
static Optional<DesktopModeController> provideDesktopModeController(
|
||||
Context context, ShellInit shellInit,
|
||||
ShellTaskOrganizer shellTaskOrganizer,
|
||||
RootDisplayAreaOrganizer rootDisplayAreaOrganizer,
|
||||
@ShellMainThread Handler mainHandler
|
||||
) {
|
||||
if (DesktopModeController.IS_FEATURE_ENABLED) {
|
||||
return Optional.of(new DesktopModeController(context, shellInit, shellTaskOrganizer,
|
||||
rootDisplayAreaOrganizer,
|
||||
mainHandler));
|
||||
} else {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Misc
|
||||
//
|
||||
@@ -583,7 +606,8 @@ public abstract class WMShellModule {
|
||||
@ShellCreateTriggerOverride
|
||||
@Provides
|
||||
static Object provideIndependentShellComponentsToCreate(
|
||||
SplitscreenPipMixedHandler splitscreenPipMixedHandler) {
|
||||
SplitscreenPipMixedHandler splitscreenPipMixedHandler,
|
||||
Optional<DesktopModeController> desktopModeController) {
|
||||
return new Object();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.desktopmode;
|
||||
|
||||
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
|
||||
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
|
||||
|
||||
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.ContentObserver;
|
||||
import android.net.Uri;
|
||||
import android.os.Handler;
|
||||
import android.os.SystemProperties;
|
||||
import android.os.UserHandle;
|
||||
import android.provider.Settings;
|
||||
import android.window.WindowContainerTransaction;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.protolog.common.ProtoLog;
|
||||
import com.android.wm.shell.RootDisplayAreaOrganizer;
|
||||
import com.android.wm.shell.ShellTaskOrganizer;
|
||||
import com.android.wm.shell.common.annotations.ShellMainThread;
|
||||
import com.android.wm.shell.sysui.ShellInit;
|
||||
|
||||
/**
|
||||
* Handles windowing changes when desktop mode system setting changes
|
||||
*/
|
||||
public class DesktopModeController {
|
||||
|
||||
public static final boolean IS_FEATURE_ENABLED = SystemProperties.getBoolean(
|
||||
"persist.wm.debug.desktop_mode", false);
|
||||
|
||||
private final Context mContext;
|
||||
private final ShellTaskOrganizer mShellTaskOrganizer;
|
||||
private final RootDisplayAreaOrganizer mRootDisplayAreaOrganizer;
|
||||
private final SettingsObserver mSettingsObserver;
|
||||
|
||||
public DesktopModeController(Context context, ShellInit shellInit,
|
||||
ShellTaskOrganizer shellTaskOrganizer,
|
||||
RootDisplayAreaOrganizer rootDisplayAreaOrganizer,
|
||||
@ShellMainThread Handler mainHandler) {
|
||||
mContext = context;
|
||||
mShellTaskOrganizer = shellTaskOrganizer;
|
||||
mRootDisplayAreaOrganizer = rootDisplayAreaOrganizer;
|
||||
mSettingsObserver = new SettingsObserver(mContext, mainHandler);
|
||||
shellInit.addInitCallback(this::onInit, this);
|
||||
}
|
||||
|
||||
private void onInit() {
|
||||
ProtoLog.d(WM_SHELL_DESKTOP_MODE, "Initialize DesktopModeController");
|
||||
mSettingsObserver.observe();
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void updateDesktopModeEnabled(boolean enabled) {
|
||||
ProtoLog.d(WM_SHELL_DESKTOP_MODE, "updateDesktopModeState: enabled=%s", enabled);
|
||||
|
||||
int displayId = mContext.getDisplayId();
|
||||
|
||||
WindowContainerTransaction wct = new WindowContainerTransaction();
|
||||
// Reset freeform windowing mode that is set per task level (tasks should inherit
|
||||
// container value)
|
||||
wct.merge(mShellTaskOrganizer.prepareClearFreeformForTasks(displayId), true /* transfer */);
|
||||
int targetWindowingMode;
|
||||
if (enabled) {
|
||||
targetWindowingMode = WINDOWING_MODE_FREEFORM;
|
||||
} else {
|
||||
targetWindowingMode = WINDOWING_MODE_FULLSCREEN;
|
||||
// Clear any resized bounds
|
||||
wct.merge(mShellTaskOrganizer.prepareClearBoundsForTasks(displayId),
|
||||
true /* transfer */);
|
||||
}
|
||||
wct.merge(mRootDisplayAreaOrganizer.prepareWindowingModeChange(displayId,
|
||||
targetWindowingMode), true /* transfer */);
|
||||
mRootDisplayAreaOrganizer.applyTransaction(wct);
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@link ContentObserver} for listening to changes to {@link Settings.System#DESKTOP_MODE}
|
||||
*/
|
||||
private final class SettingsObserver extends ContentObserver {
|
||||
|
||||
private final Uri mDesktopModeSetting = Settings.System.getUriFor(
|
||||
Settings.System.DESKTOP_MODE);
|
||||
|
||||
private final Context mContext;
|
||||
|
||||
SettingsObserver(Context context, Handler handler) {
|
||||
super(handler);
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
public void observe() {
|
||||
// TODO(b/242867463): listen for setting change for all users
|
||||
mContext.getContentResolver().registerContentObserver(mDesktopModeSetting,
|
||||
false /* notifyForDescendants */, this /* observer */, UserHandle.USER_CURRENT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChange(boolean selfChange, @Nullable Uri uri) {
|
||||
if (mDesktopModeSetting.equals(uri)) {
|
||||
ProtoLog.d(WM_SHELL_DESKTOP_MODE, "Received update for desktop mode setting");
|
||||
desktopModeSettingChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private void desktopModeSettingChanged() {
|
||||
boolean enabled = isDesktopModeEnabled();
|
||||
updateDesktopModeEnabled(enabled);
|
||||
}
|
||||
|
||||
private boolean isDesktopModeEnabled() {
|
||||
try {
|
||||
int result = Settings.System.getIntForUser(mContext.getContentResolver(),
|
||||
Settings.System.DESKTOP_MODE, UserHandle.USER_CURRENT);
|
||||
ProtoLog.d(WM_SHELL_DESKTOP_MODE, "isDesktopModeEnabled=%s", result);
|
||||
return result != 0;
|
||||
} catch (Settings.SettingNotFoundException e) {
|
||||
ProtoLog.e(WM_SHELL_DESKTOP_MODE, "Failed to read DESKTOP_MODE setting %s", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -46,6 +46,8 @@ public enum ShellProtoLogGroup implements IProtoLogGroup {
|
||||
Consts.TAG_WM_SHELL),
|
||||
WM_SHELL_SYSUI_EVENTS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
|
||||
Consts.TAG_WM_SHELL),
|
||||
WM_SHELL_DESKTOP_MODE(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
|
||||
Consts.TAG_WM_SHELL),
|
||||
TEST_GROUP(true, true, false, "WindowManagerShellProtoLogTest");
|
||||
|
||||
private final boolean mEnabled;
|
||||
|
||||
@@ -16,9 +16,11 @@
|
||||
|
||||
package com.android.wm.shell;
|
||||
|
||||
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
|
||||
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
|
||||
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
|
||||
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
|
||||
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
|
||||
import static android.view.Display.DEFAULT_DISPLAY;
|
||||
|
||||
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
|
||||
@@ -30,6 +32,8 @@ import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIO
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.junit.Assume.assumeFalse;
|
||||
@@ -38,9 +42,11 @@ import static org.mockito.Mockito.clearInvocations;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.app.ActivityManager.RunningTaskInfo;
|
||||
import android.app.TaskInfo;
|
||||
import android.app.WindowConfiguration;
|
||||
import android.content.LocusId;
|
||||
import android.content.pm.ParceledListSlice;
|
||||
import android.os.Binder;
|
||||
@@ -53,6 +59,8 @@ import android.window.ITaskOrganizer;
|
||||
import android.window.ITaskOrganizerController;
|
||||
import android.window.TaskAppearedInfo;
|
||||
import android.window.WindowContainerToken;
|
||||
import android.window.WindowContainerTransaction;
|
||||
import android.window.WindowContainerTransaction.Change;
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import androidx.test.filters.SmallTest;
|
||||
@@ -628,6 +636,71 @@ public class ShellTaskOrganizerTests extends ShellTestCase {
|
||||
verify(mTaskOrganizerController).restartTaskTopActivityProcessIfVisible(task1.token);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPrepareClearBoundsForTasks() {
|
||||
RunningTaskInfo task1 = createTaskInfo(1, WINDOWING_MODE_UNDEFINED);
|
||||
task1.displayId = 1;
|
||||
MockToken token1 = new MockToken();
|
||||
task1.token = token1.token();
|
||||
mOrganizer.onTaskAppeared(task1, null);
|
||||
|
||||
RunningTaskInfo task2 = createTaskInfo(2, WINDOWING_MODE_UNDEFINED);
|
||||
task2.displayId = 1;
|
||||
MockToken token2 = new MockToken();
|
||||
task2.token = token2.token();
|
||||
mOrganizer.onTaskAppeared(task2, null);
|
||||
|
||||
RunningTaskInfo otherDisplayTask = createTaskInfo(3, WINDOWING_MODE_UNDEFINED);
|
||||
otherDisplayTask.displayId = 2;
|
||||
MockToken otherDisplayToken = new MockToken();
|
||||
otherDisplayTask.token = otherDisplayToken.token();
|
||||
mOrganizer.onTaskAppeared(otherDisplayTask, null);
|
||||
|
||||
WindowContainerTransaction wct = mOrganizer.prepareClearBoundsForTasks(1);
|
||||
|
||||
assertEquals(wct.getChanges().size(), 2);
|
||||
Change boundsChange1 = wct.getChanges().get(token1.binder());
|
||||
assertNotNull(boundsChange1);
|
||||
assertNotEquals(
|
||||
(boundsChange1.getWindowSetMask() & WindowConfiguration.WINDOW_CONFIG_BOUNDS), 0);
|
||||
assertTrue(boundsChange1.getConfiguration().windowConfiguration.getBounds().isEmpty());
|
||||
|
||||
Change boundsChange2 = wct.getChanges().get(token2.binder());
|
||||
assertNotNull(boundsChange2);
|
||||
assertNotEquals(
|
||||
(boundsChange2.getWindowSetMask() & WindowConfiguration.WINDOW_CONFIG_BOUNDS), 0);
|
||||
assertTrue(boundsChange2.getConfiguration().windowConfiguration.getBounds().isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPrepareClearFreeformForTasks() {
|
||||
RunningTaskInfo task1 = createTaskInfo(1, WINDOWING_MODE_FREEFORM);
|
||||
task1.displayId = 1;
|
||||
MockToken token1 = new MockToken();
|
||||
task1.token = token1.token();
|
||||
mOrganizer.onTaskAppeared(task1, null);
|
||||
|
||||
RunningTaskInfo task2 = createTaskInfo(2, WINDOWING_MODE_MULTI_WINDOW);
|
||||
task2.displayId = 1;
|
||||
MockToken token2 = new MockToken();
|
||||
task2.token = token2.token();
|
||||
mOrganizer.onTaskAppeared(task2, null);
|
||||
|
||||
RunningTaskInfo otherDisplayTask = createTaskInfo(3, WINDOWING_MODE_FREEFORM);
|
||||
otherDisplayTask.displayId = 2;
|
||||
MockToken otherDisplayToken = new MockToken();
|
||||
otherDisplayTask.token = otherDisplayToken.token();
|
||||
mOrganizer.onTaskAppeared(otherDisplayTask, null);
|
||||
|
||||
WindowContainerTransaction wct = mOrganizer.prepareClearFreeformForTasks(1);
|
||||
|
||||
// Only task with freeform windowing mode and the right display should be updated
|
||||
assertEquals(wct.getChanges().size(), 1);
|
||||
Change wmModeChange1 = wct.getChanges().get(token1.binder());
|
||||
assertNotNull(wmModeChange1);
|
||||
assertEquals(wmModeChange1.getWindowingMode(), WINDOWING_MODE_UNDEFINED);
|
||||
}
|
||||
|
||||
private static RunningTaskInfo createTaskInfo(int taskId, int windowingMode) {
|
||||
RunningTaskInfo taskInfo = new RunningTaskInfo();
|
||||
taskInfo.taskId = taskId;
|
||||
@@ -635,4 +708,22 @@ public class ShellTaskOrganizerTests extends ShellTestCase {
|
||||
return taskInfo;
|
||||
}
|
||||
|
||||
private static class MockToken {
|
||||
private final WindowContainerToken mToken;
|
||||
private final IBinder mBinder;
|
||||
|
||||
MockToken() {
|
||||
mToken = mock(WindowContainerToken.class);
|
||||
mBinder = mock(IBinder.class);
|
||||
when(mToken.asBinder()).thenReturn(mBinder);
|
||||
}
|
||||
|
||||
WindowContainerToken token() {
|
||||
return mToken;
|
||||
}
|
||||
|
||||
IBinder binder() {
|
||||
return mBinder;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,194 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.desktopmode;
|
||||
|
||||
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
|
||||
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
|
||||
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.app.WindowConfiguration;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.testing.AndroidTestingRunner;
|
||||
import android.window.WindowContainerToken;
|
||||
import android.window.WindowContainerTransaction;
|
||||
import android.window.WindowContainerTransaction.Change;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.android.wm.shell.RootDisplayAreaOrganizer;
|
||||
import com.android.wm.shell.ShellTaskOrganizer;
|
||||
import com.android.wm.shell.ShellTestCase;
|
||||
import com.android.wm.shell.common.ShellExecutor;
|
||||
import com.android.wm.shell.sysui.ShellInit;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidTestingRunner.class)
|
||||
public class DesktopModeControllerTest extends ShellTestCase {
|
||||
|
||||
@Mock
|
||||
private ShellTaskOrganizer mShellTaskOrganizer;
|
||||
@Mock
|
||||
private RootDisplayAreaOrganizer mRootDisplayAreaOrganizer;
|
||||
@Mock
|
||||
private ShellExecutor mTestExecutor;
|
||||
@Mock
|
||||
private Handler mMockHandler;
|
||||
|
||||
private DesktopModeController mController;
|
||||
private ShellInit mShellInit;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
mShellInit = Mockito.spy(new ShellInit(mTestExecutor));
|
||||
|
||||
mController = new DesktopModeController(mContext, mShellInit, mShellTaskOrganizer,
|
||||
mRootDisplayAreaOrganizer, mMockHandler);
|
||||
|
||||
mShellInit.init();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void instantiate_addInitCallback() {
|
||||
verify(mShellInit, times(1)).addInitCallback(any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDesktopModeEnabled_taskWmClearedDisplaySetToFreeform() {
|
||||
// Create a fake WCT to simulate setting task windowing mode to undefined
|
||||
WindowContainerTransaction taskWct = new WindowContainerTransaction();
|
||||
MockToken taskMockToken = new MockToken();
|
||||
taskWct.setWindowingMode(taskMockToken.token(), WINDOWING_MODE_UNDEFINED);
|
||||
when(mShellTaskOrganizer.prepareClearFreeformForTasks(mContext.getDisplayId())).thenReturn(
|
||||
taskWct);
|
||||
|
||||
// Create a fake WCT to simulate setting display windowing mode to freeform
|
||||
WindowContainerTransaction displayWct = new WindowContainerTransaction();
|
||||
MockToken displayMockToken = new MockToken();
|
||||
displayWct.setWindowingMode(displayMockToken.token(), WINDOWING_MODE_FREEFORM);
|
||||
when(mRootDisplayAreaOrganizer.prepareWindowingModeChange(mContext.getDisplayId(),
|
||||
WINDOWING_MODE_FREEFORM)).thenReturn(displayWct);
|
||||
|
||||
// The test
|
||||
mController.updateDesktopModeEnabled(true);
|
||||
|
||||
ArgumentCaptor<WindowContainerTransaction> arg = ArgumentCaptor.forClass(
|
||||
WindowContainerTransaction.class);
|
||||
verify(mRootDisplayAreaOrganizer).applyTransaction(arg.capture());
|
||||
|
||||
// WCT should have 2 changes - clear task wm mode and set display wm mode
|
||||
WindowContainerTransaction wct = arg.getValue();
|
||||
assertThat(wct.getChanges()).hasSize(2);
|
||||
|
||||
// Verify executed WCT has a change for setting task windowing mode to undefined
|
||||
Change taskWmModeChange = wct.getChanges().get(taskMockToken.binder());
|
||||
assertThat(taskWmModeChange).isNotNull();
|
||||
assertThat(taskWmModeChange.getWindowingMode()).isEqualTo(WINDOWING_MODE_UNDEFINED);
|
||||
|
||||
// Verify executed WCT has a change for setting display windowing mode to freeform
|
||||
Change displayWmModeChange = wct.getChanges().get(displayMockToken.binder());
|
||||
assertThat(displayWmModeChange).isNotNull();
|
||||
assertThat(displayWmModeChange.getWindowingMode()).isEqualTo(WINDOWING_MODE_FREEFORM);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDesktopModeDisabled_taskWmAndBoundsClearedDisplaySetToFullscreen() {
|
||||
// Create a fake WCT to simulate setting task windowing mode to undefined
|
||||
WindowContainerTransaction taskWmWct = new WindowContainerTransaction();
|
||||
MockToken taskWmMockToken = new MockToken();
|
||||
taskWmWct.setWindowingMode(taskWmMockToken.token(), WINDOWING_MODE_UNDEFINED);
|
||||
when(mShellTaskOrganizer.prepareClearFreeformForTasks(mContext.getDisplayId())).thenReturn(
|
||||
taskWmWct);
|
||||
|
||||
// Create a fake WCT to simulate clearing task bounds
|
||||
WindowContainerTransaction taskBoundsWct = new WindowContainerTransaction();
|
||||
MockToken taskBoundsMockToken = new MockToken();
|
||||
taskBoundsWct.setBounds(taskBoundsMockToken.token(), null);
|
||||
when(mShellTaskOrganizer.prepareClearBoundsForTasks(mContext.getDisplayId())).thenReturn(
|
||||
taskBoundsWct);
|
||||
|
||||
// Create a fake WCT to simulate setting display windowing mode to fullscreen
|
||||
WindowContainerTransaction displayWct = new WindowContainerTransaction();
|
||||
MockToken displayMockToken = new MockToken();
|
||||
displayWct.setWindowingMode(displayMockToken.token(), WINDOWING_MODE_FULLSCREEN);
|
||||
when(mRootDisplayAreaOrganizer.prepareWindowingModeChange(mContext.getDisplayId(),
|
||||
WINDOWING_MODE_FULLSCREEN)).thenReturn(displayWct);
|
||||
|
||||
// The test
|
||||
mController.updateDesktopModeEnabled(false);
|
||||
|
||||
ArgumentCaptor<WindowContainerTransaction> arg = ArgumentCaptor.forClass(
|
||||
WindowContainerTransaction.class);
|
||||
verify(mRootDisplayAreaOrganizer).applyTransaction(arg.capture());
|
||||
|
||||
// WCT should have 3 changes - clear task wm mode and bounds and set display wm mode
|
||||
WindowContainerTransaction wct = arg.getValue();
|
||||
assertThat(wct.getChanges()).hasSize(3);
|
||||
|
||||
// Verify executed WCT has a change for setting task windowing mode to undefined
|
||||
Change taskWmModeChange = wct.getChanges().get(taskWmMockToken.binder());
|
||||
assertThat(taskWmModeChange).isNotNull();
|
||||
assertThat(taskWmModeChange.getWindowingMode()).isEqualTo(WINDOWING_MODE_UNDEFINED);
|
||||
|
||||
// Verify executed WCT has a change for clearing task bounds
|
||||
Change taskBoundsChange = wct.getChanges().get(taskBoundsMockToken.binder());
|
||||
assertThat(taskBoundsChange).isNotNull();
|
||||
assertThat(taskBoundsChange.getWindowSetMask()
|
||||
& WindowConfiguration.WINDOW_CONFIG_BOUNDS).isNotEqualTo(0);
|
||||
assertThat(taskBoundsChange.getConfiguration().windowConfiguration.getBounds().isEmpty())
|
||||
.isTrue();
|
||||
|
||||
// Verify executed WCT has a change for setting display windowing mode to fullscreen
|
||||
Change displayWmModeChange = wct.getChanges().get(displayMockToken.binder());
|
||||
assertThat(displayWmModeChange).isNotNull();
|
||||
assertThat(displayWmModeChange.getWindowingMode()).isEqualTo(WINDOWING_MODE_FULLSCREEN);
|
||||
}
|
||||
|
||||
private static class MockToken {
|
||||
private final WindowContainerToken mToken;
|
||||
private final IBinder mBinder;
|
||||
|
||||
MockToken() {
|
||||
mToken = mock(WindowContainerToken.class);
|
||||
mBinder = mock(IBinder.class);
|
||||
when(mToken.asBinder()).thenReturn(mBinder);
|
||||
}
|
||||
|
||||
WindowContainerToken token() {
|
||||
return mToken;
|
||||
}
|
||||
|
||||
IBinder binder() {
|
||||
return mBinder;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user