Merge changes from topic "desktop-windowing-hide" into tm-qpr-dev

* changes:
  Hide some options when desktop mode is available
  Handle desktop mode setting change
This commit is contained in:
Ats Jenk
2022-08-20 00:37:53 +00:00
committed by Android (Google) Code Review
9 changed files with 583 additions and 3 deletions

View File

@@ -16,14 +16,20 @@
package com.android.wm.shell; 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.util.SparseArray;
import android.view.SurfaceControl; import android.view.SurfaceControl;
import android.window.DisplayAreaAppearedInfo; import android.window.DisplayAreaAppearedInfo;
import android.window.DisplayAreaInfo; import android.window.DisplayAreaInfo;
import android.window.DisplayAreaOrganizer; import android.window.DisplayAreaOrganizer;
import android.window.WindowContainerTransaction;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import com.android.internal.protolog.common.ProtoLog;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.util.List; import java.util.List;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
@@ -102,10 +108,44 @@ public class RootDisplayAreaOrganizer extends DisplayAreaOrganizer {
mDisplayAreasInfo.put(displayId, displayAreaInfo); 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) { public void dump(@NonNull PrintWriter pw, String prefix) {
final String innerPrefix = prefix + " "; final String innerPrefix = prefix + " ";
final String childPrefix = innerPrefix + " "; final String childPrefix = innerPrefix + " ";
pw.println(prefix + this); 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 @Override

View File

@@ -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_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; 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.protolog.ShellProtoLogGroup.WM_SHELL_TASK_ORG;
import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS; 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.StartingWindowRemovalInfo;
import android.window.TaskAppearedInfo; import android.window.TaskAppearedInfo;
import android.window.TaskOrganizer; import android.window.TaskOrganizer;
import android.window.WindowContainerTransaction;
import com.android.internal.annotations.VisibleForTesting; import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog; import com.android.internal.protolog.common.ProtoLog;
@@ -690,6 +692,49 @@ public class ShellTaskOrganizer extends TaskOrganizer implements
taskListener.reparentChildSurfaceToTask(taskId, sc, t); 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, private void logSizeCompatRestartButtonEventReported(@NonNull TaskAppearedInfo info,
int event) { int event) {
ActivityInfo topActivityInfo = info.getTaskInfo().topActivityInfo; ActivityInfo topActivityInfo = info.getTaskInfo().topActivityInfo;
@@ -816,7 +861,14 @@ public class ShellTaskOrganizer extends TaskOrganizer implements
final int key = mTasks.keyAt(i); final int key = mTasks.keyAt(i);
final TaskAppearedInfo info = mTasks.valueAt(i); final TaskAppearedInfo info = mTasks.valueAt(i);
final TaskListener listener = getTaskListener(info.getTaskInfo()); 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(); pw.println();
@@ -826,6 +878,7 @@ public class ShellTaskOrganizer extends TaskOrganizer implements
final TaskListener listener = mLaunchCookieToListener.valueAt(i); final TaskListener listener = mLaunchCookieToListener.valueAt(i);
pw.println(innerPrefix + "#" + i + " cookie=" + key + " listener=" + listener); pw.println(innerPrefix + "#" + i + " cookie=" + key + " listener=" + listener);
} }
} }
} }
} }

View File

@@ -27,6 +27,7 @@ import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.logging.UiEventLogger; import com.android.internal.logging.UiEventLogger;
import com.android.internal.statusbar.IStatusBarService; import com.android.internal.statusbar.IStatusBarService;
import com.android.launcher3.icons.IconProvider; import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.RootDisplayAreaOrganizer;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer; import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.TaskViewTransitions; import com.android.wm.shell.TaskViewTransitions;
@@ -48,6 +49,8 @@ import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.common.TransactionPool; import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.common.annotations.ShellBackgroundThread; import com.android.wm.shell.common.annotations.ShellBackgroundThread;
import com.android.wm.shell.common.annotations.ShellMainThread; import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.desktopmode.DesktopModeConstants;
import com.android.wm.shell.desktopmode.DesktopModeController;
import com.android.wm.shell.draganddrop.DragAndDropController; import com.android.wm.shell.draganddrop.DragAndDropController;
import com.android.wm.shell.freeform.FreeformComponents; import com.android.wm.shell.freeform.FreeformComponents;
import com.android.wm.shell.freeform.FreeformTaskListener; import com.android.wm.shell.freeform.FreeformTaskListener;
@@ -573,6 +576,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 (DesktopModeConstants.IS_FEATURE_ENABLED) {
return Optional.of(new DesktopModeController(context, shellInit, shellTaskOrganizer,
rootDisplayAreaOrganizer,
mainHandler));
} else {
return Optional.empty();
}
}
// //
// Misc // Misc
// //
@@ -583,7 +607,8 @@ public abstract class WMShellModule {
@ShellCreateTriggerOverride @ShellCreateTriggerOverride
@Provides @Provides
static Object provideIndependentShellComponentsToCreate( static Object provideIndependentShellComponentsToCreate(
SplitscreenPipMixedHandler splitscreenPipMixedHandler) { SplitscreenPipMixedHandler splitscreenPipMixedHandler,
Optional<DesktopModeController> desktopModeController) {
return new Object(); return new Object();
} }
} }

View File

@@ -0,0 +1,31 @@
/*
* 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 android.os.SystemProperties;
/**
* Constants for desktop mode feature
*/
public class DesktopModeConstants {
/**
* Flag to indicate whether desktop mode is available on the device
*/
public static final boolean IS_FEATURE_ENABLED = SystemProperties.getBoolean(
"persist.wm.debug.desktop_mode", false);
}

View File

@@ -0,0 +1,137 @@
/*
* 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.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 {
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;
}
}
}
}

View File

@@ -46,6 +46,8 @@ public enum ShellProtoLogGroup implements IProtoLogGroup {
Consts.TAG_WM_SHELL), Consts.TAG_WM_SHELL),
WM_SHELL_SYSUI_EVENTS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false, WM_SHELL_SYSUI_EVENTS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
Consts.TAG_WM_SHELL), 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"); TEST_GROUP(true, true, false, "WindowManagerShellProtoLogTest");
private final boolean mEnabled; private final boolean mEnabled;

View File

@@ -34,6 +34,7 @@ import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.desktopmode.DesktopModeConstants;
/** /**
* Defines visuals and behaviors of a window decoration of a caption bar and shadows. It works with * Defines visuals and behaviors of a window decoration of a caption bar and shadows. It works with
@@ -163,7 +164,13 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
View caption = mResult.mRootView.findViewById(R.id.caption); View caption = mResult.mRootView.findViewById(R.id.caption);
caption.setOnTouchListener(mOnCaptionTouchListener); caption.setOnTouchListener(mOnCaptionTouchListener);
View maximize = caption.findViewById(R.id.maximize_window); View maximize = caption.findViewById(R.id.maximize_window);
maximize.setOnClickListener(mOnCaptionButtonClickListener); if (DesktopModeConstants.IS_FEATURE_ENABLED) {
// Hide maximize button when desktop mode is available
maximize.setVisibility(View.GONE);
} else {
maximize.setVisibility(View.VISIBLE);
maximize.setOnClickListener(mOnCaptionButtonClickListener);
}
View close = caption.findViewById(R.id.close_window); View close = caption.findViewById(R.id.close_window);
close.setOnClickListener(mOnCaptionButtonClickListener); close.setOnClickListener(mOnCaptionButtonClickListener);
View minimize = caption.findViewById(R.id.minimize_window); View minimize = caption.findViewById(R.id.minimize_window);

View File

@@ -16,9 +16,11 @@
package com.android.wm.shell; 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_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; 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 android.view.Display.DEFAULT_DISPLAY;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; 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.assertEquals;
import static org.junit.Assert.assertFalse; 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.assertTrue;
import static org.junit.Assert.fail; import static org.junit.Assert.fail;
import static org.junit.Assume.assumeFalse; 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.mock;
import static org.mockito.Mockito.times; import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.ActivityManager.RunningTaskInfo; import android.app.ActivityManager.RunningTaskInfo;
import android.app.TaskInfo; import android.app.TaskInfo;
import android.app.WindowConfiguration;
import android.content.LocusId; import android.content.LocusId;
import android.content.pm.ParceledListSlice; import android.content.pm.ParceledListSlice;
import android.os.Binder; import android.os.Binder;
@@ -53,6 +59,8 @@ import android.window.ITaskOrganizer;
import android.window.ITaskOrganizerController; import android.window.ITaskOrganizerController;
import android.window.TaskAppearedInfo; import android.window.TaskAppearedInfo;
import android.window.WindowContainerToken; import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
import android.window.WindowContainerTransaction.Change;
import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest; import androidx.test.filters.SmallTest;
@@ -628,6 +636,71 @@ public class ShellTaskOrganizerTests extends ShellTestCase {
verify(mTaskOrganizerController).restartTaskTopActivityProcessIfVisible(task1.token); 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) { private static RunningTaskInfo createTaskInfo(int taskId, int windowingMode) {
RunningTaskInfo taskInfo = new RunningTaskInfo(); RunningTaskInfo taskInfo = new RunningTaskInfo();
taskInfo.taskId = taskId; taskInfo.taskId = taskId;
@@ -635,4 +708,22 @@ public class ShellTaskOrganizerTests extends ShellTestCase {
return taskInfo; 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;
}
}
} }

View File

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