Merge "Add support to show apps on desktop in sysui proxy" into tm-qpr-dev

This commit is contained in:
Ats Jenk
2022-09-26 23:43:18 +00:00
committed by Android (Google) Code Review
19 changed files with 361 additions and 79 deletions

View File

@@ -57,6 +57,8 @@ import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.common.annotations.ShellSplashscreenThread;
import com.android.wm.shell.compatui.CompatUIController;
import com.android.wm.shell.desktopmode.DesktopMode;
import com.android.wm.shell.desktopmode.DesktopModeController;
import com.android.wm.shell.desktopmode.DesktopModeStatus;
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
import com.android.wm.shell.displayareahelper.DisplayAreaHelper;
import com.android.wm.shell.displayareahelper.DisplayAreaHelperController;
@@ -716,15 +718,36 @@ public abstract class WMShellBaseModule {
// Desktop mode (optional feature)
//
@WMSingleton
@Provides
static Optional<DesktopMode> provideDesktopMode(
Optional<DesktopModeController> desktopModeController) {
return desktopModeController.map(DesktopModeController::asDesktopMode);
}
@BindsOptionalOf
@DynamicOverride
abstract DesktopModeController optionalDesktopModeController();
@WMSingleton
@Provides
static Optional<DesktopModeController> providesDesktopModeController(
@DynamicOverride Optional<DesktopModeController> desktopModeController) {
if (DesktopModeStatus.IS_SUPPORTED) {
return desktopModeController;
}
return Optional.empty();
}
@BindsOptionalOf
@DynamicOverride
abstract DesktopModeTaskRepository optionalDesktopModeTaskRepository();
@WMSingleton
@Provides
static Optional<DesktopModeTaskRepository> providesDesktopModeTaskRepository(
static Optional<DesktopModeTaskRepository> providesDesktopTaskRepository(
@DynamicOverride Optional<DesktopModeTaskRepository> desktopModeTaskRepository) {
if (DesktopMode.IS_SUPPORTED) {
if (DesktopModeStatus.IS_SUPPORTED) {
return desktopModeTaskRepository;
}
return Optional.empty();

View File

@@ -48,7 +48,6 @@ 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.DesktopMode;
import com.android.wm.shell.desktopmode.DesktopModeController;
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
import com.android.wm.shell.draganddrop.DragAndDropController;
@@ -595,19 +594,18 @@ public abstract class WMShellModule {
@WMSingleton
@Provides
static Optional<DesktopModeController> provideDesktopModeController(
Context context, ShellInit shellInit,
@DynamicOverride
static DesktopModeController provideDesktopModeController(Context context, ShellInit shellInit,
ShellTaskOrganizer shellTaskOrganizer,
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
Transitions transitions,
@DynamicOverride DesktopModeTaskRepository desktopModeTaskRepository,
@ShellMainThread Handler mainHandler,
Transitions transitions
@ShellMainThread ShellExecutor mainExecutor
) {
if (DesktopMode.IS_SUPPORTED) {
return Optional.of(new DesktopModeController(context, shellInit, shellTaskOrganizer,
rootTaskDisplayAreaOrganizer, mainHandler, transitions));
} else {
return Optional.empty();
}
return new DesktopModeController(context, shellInit, shellTaskOrganizer,
rootTaskDisplayAreaOrganizer, transitions, desktopModeTaskRepository, mainHandler,
mainExecutor);
}
@WMSingleton

View File

@@ -16,43 +16,16 @@
package com.android.wm.shell.desktopmode;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE;
import android.content.Context;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.provider.Settings;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.common.annotations.ExternalThread;
/**
* Constants for desktop mode feature
* Interface to interact with desktop mode feature in shell.
*/
public class DesktopMode {
@ExternalThread
public interface DesktopMode {
/**
* Flag to indicate whether desktop mode is available on the device
*/
public static final boolean IS_SUPPORTED = SystemProperties.getBoolean(
"persist.wm.debug.desktop_mode", false);
/**
* Check if desktop mode is active
*
* @return {@code true} if active
*/
public static boolean isActive(Context context) {
if (!IS_SUPPORTED) {
return false;
}
try {
int result = Settings.System.getIntForUser(context.getContentResolver(),
Settings.System.DESKTOP_MODE, UserHandle.USER_CURRENT);
ProtoLog.d(WM_SHELL_DESKTOP_MODE, "isDesktopModeEnabled=%s", result);
return result != 0;
} catch (Exception e) {
ProtoLog.e(WM_SHELL_DESKTOP_MODE, "Failed to read DESKTOP_MODE setting %s", e);
return false;
}
/** Returns a binder that can be passed to an external process to manipulate DesktopMode. */
default IDesktopMode createExternalInterface() {
return null;
}
}

View File

@@ -20,8 +20,10 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.WindowConfiguration;
import android.content.Context;
import android.database.ContentObserver;
@@ -29,51 +31,83 @@ import android.net.Uri;
import android.os.Handler;
import android.os.UserHandle;
import android.provider.Settings;
import android.util.ArraySet;
import android.window.DisplayAreaInfo;
import android.window.WindowContainerTransaction;
import androidx.annotation.BinderThread;
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.annotations.ExternalThread;
import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
import java.util.ArrayList;
import java.util.Comparator;
/**
* Handles windowing changes when desktop mode system setting changes
*/
public class DesktopModeController {
public class DesktopModeController implements RemoteCallable<DesktopModeController> {
private final Context mContext;
private final ShellTaskOrganizer mShellTaskOrganizer;
private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
private final SettingsObserver mSettingsObserver;
private final Transitions mTransitions;
private final DesktopModeTaskRepository mDesktopModeTaskRepository;
private final ShellExecutor mMainExecutor;
private final DesktopMode mDesktopModeImpl = new DesktopModeImpl();
private final SettingsObserver mSettingsObserver;
public DesktopModeController(Context context, ShellInit shellInit,
ShellTaskOrganizer shellTaskOrganizer,
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
Transitions transitions,
DesktopModeTaskRepository desktopModeTaskRepository,
@ShellMainThread Handler mainHandler,
Transitions transitions) {
@ShellMainThread ShellExecutor mainExecutor) {
mContext = context;
mShellTaskOrganizer = shellTaskOrganizer;
mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer;
mSettingsObserver = new SettingsObserver(mContext, mainHandler);
mTransitions = transitions;
mDesktopModeTaskRepository = desktopModeTaskRepository;
mMainExecutor = mainExecutor;
mSettingsObserver = new SettingsObserver(mContext, mainHandler);
shellInit.addInitCallback(this::onInit, this);
}
private void onInit() {
ProtoLog.d(WM_SHELL_DESKTOP_MODE, "Initialize DesktopModeController");
mSettingsObserver.observe();
if (DesktopMode.isActive(mContext)) {
if (DesktopModeStatus.isActive(mContext)) {
updateDesktopModeActive(true);
}
}
@Override
public Context getContext() {
return mContext;
}
@Override
public ShellExecutor getRemoteCallExecutor() {
return mMainExecutor;
}
/**
* Get connection interface between sysui and shell
*/
public DesktopMode asDesktopMode() {
return mDesktopModeImpl;
}
@VisibleForTesting
void updateDesktopModeActive(boolean active) {
ProtoLog.d(WM_SHELL_DESKTOP_MODE, "updateDesktopModeActive: active=%s", active);
@@ -120,6 +154,28 @@ public class DesktopModeController {
wct.setWindowingMode(displayAreaInfo.token, windowingMode);
}
/**
* Show apps on desktop
*/
public void showDesktopApps() {
ArraySet<Integer> activeTasks = mDesktopModeTaskRepository.getActiveTasks();
ProtoLog.d(WM_SHELL_DESKTOP_MODE, "bringDesktopAppsToFront: tasks=%s", activeTasks.size());
ArrayList<RunningTaskInfo> taskInfos = new ArrayList<>();
for (Integer taskId : activeTasks) {
RunningTaskInfo taskInfo = mShellTaskOrganizer.getRunningTaskInfo(taskId);
if (taskInfo != null) {
taskInfos.add(taskInfo);
}
}
// Order by lastActiveTime, descending
taskInfos.sort(Comparator.comparingLong(task -> -task.lastActiveTime));
WindowContainerTransaction wct = new WindowContainerTransaction();
for (RunningTaskInfo task : taskInfos) {
wct.reorder(task.token, true);
}
mShellTaskOrganizer.applyTransaction(wct);
}
/**
* A {@link ContentObserver} for listening to changes to {@link Settings.System#DESKTOP_MODE}
*/
@@ -150,8 +206,51 @@ public class DesktopModeController {
}
private void desktopModeSettingChanged() {
boolean enabled = DesktopMode.isActive(mContext);
boolean enabled = DesktopModeStatus.isActive(mContext);
updateDesktopModeActive(enabled);
}
}
/**
* The interface for calls from outside the shell, within the host process.
*/
@ExternalThread
private final class DesktopModeImpl implements DesktopMode {
private IDesktopModeImpl mIDesktopMode;
@Override
public IDesktopMode createExternalInterface() {
if (mIDesktopMode != null) {
mIDesktopMode.invalidate();
}
mIDesktopMode = new IDesktopModeImpl(DesktopModeController.this);
return mIDesktopMode;
}
}
/**
* The interface for calls from outside the host process.
*/
@BinderThread
private static class IDesktopModeImpl extends IDesktopMode.Stub {
private DesktopModeController mController;
IDesktopModeImpl(DesktopModeController controller) {
mController = controller;
}
/**
* Invalidates this instance, preventing future calls from updating the controller.
*/
void invalidate() {
mController = null;
}
public void showDesktopApps() {
executeRemoteCallWithTaskPermission(mController, "showDesktopApps",
DesktopModeController::showDesktopApps);
}
}
}

View File

@@ -0,0 +1,58 @@
/*
* 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 com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE;
import android.content.Context;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.provider.Settings;
import com.android.internal.protolog.common.ProtoLog;
/**
* Constants for desktop mode feature
*/
public class DesktopModeStatus {
/**
* Flag to indicate whether desktop mode is available on the device
*/
public static final boolean IS_SUPPORTED = SystemProperties.getBoolean(
"persist.wm.debug.desktop_mode", false);
/**
* Check if desktop mode is active
*
* @return {@code true} if active
*/
public static boolean isActive(Context context) {
if (!IS_SUPPORTED) {
return false;
}
try {
int result = Settings.System.getIntForUser(context.getContentResolver(),
Settings.System.DESKTOP_MODE, UserHandle.USER_CURRENT);
ProtoLog.d(WM_SHELL_DESKTOP_MODE, "isDesktopModeEnabled=%s", result);
return result != 0;
} catch (Exception e) {
ProtoLog.e(WM_SHELL_DESKTOP_MODE, "Failed to read DESKTOP_MODE setting %s", e);
return false;
}
}
}

View File

@@ -0,0 +1,26 @@
/*
* 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;
/**
* Interface that is exposed to remote callers to manipulate desktop mode features.
*/
interface IDesktopMode {
/** Show apps on the desktop */
void showDesktopApps();
}

View File

@@ -28,7 +28,7 @@ import androidx.annotation.Nullable;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.desktopmode.DesktopMode;
import com.android.wm.shell.desktopmode.DesktopModeStatus;
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.sysui.ShellInit;
@@ -90,7 +90,7 @@ public class FreeformTaskListener<T extends AutoCloseable>
t.apply();
}
if (DesktopMode.IS_SUPPORTED && taskInfo.isVisible) {
if (DesktopModeStatus.IS_SUPPORTED && taskInfo.isVisible) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
"Adding active freeform task: #%d", taskInfo.taskId);
mDesktopModeTaskRepository.ifPresent(it -> it.addActiveTask(taskInfo.taskId));
@@ -123,7 +123,7 @@ public class FreeformTaskListener<T extends AutoCloseable>
taskInfo.taskId);
mTasks.remove(taskInfo.taskId);
if (DesktopMode.IS_SUPPORTED) {
if (DesktopModeStatus.IS_SUPPORTED) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
"Removing active freeform task: #%d", taskInfo.taskId);
mDesktopModeTaskRepository.ifPresent(it -> it.removeActiveTask(taskInfo.taskId));
@@ -150,7 +150,7 @@ public class FreeformTaskListener<T extends AutoCloseable>
mWindowDecorationViewModel.onTaskInfoChanged(state.mTaskInfo, state.mWindowDecoration);
}
if (DesktopMode.IS_SUPPORTED) {
if (DesktopModeStatus.IS_SUPPORTED) {
if (taskInfo.isVisible) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
"Adding active freeform task: #%d", taskInfo.taskId);

View File

@@ -44,7 +44,7 @@ import com.android.wm.shell.common.TaskStackListenerCallback;
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.common.annotations.ExternalThread;
import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.desktopmode.DesktopMode;
import com.android.wm.shell.desktopmode.DesktopModeStatus;
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.sysui.ShellCommandHandler;
@@ -287,7 +287,7 @@ public class RecentTasksController implements TaskStackListenerCallback,
rawMapping.put(taskInfo.taskId, taskInfo);
}
boolean desktopModeActive = DesktopMode.isActive(mContext);
boolean desktopModeActive = DesktopModeStatus.isActive(mContext);
ArrayList<ActivityManager.RecentTaskInfo> freeformTasks = new ArrayList<>();
// Pull out the pairs as we iterate back in the list
@@ -320,7 +320,6 @@ public class RecentTasksController implements TaskStackListenerCallback,
// Add a special entry for freeform tasks
if (!freeformTasks.isEmpty()) {
// First task is added separately
recentTasks.add(0, GroupedRecentTaskInfo.forFreeformTasks(
freeformTasks.toArray(new ActivityManager.RecentTaskInfo[0])));
}

View File

@@ -36,7 +36,7 @@ import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.desktopmode.DesktopMode;
import com.android.wm.shell.desktopmode.DesktopModeStatus;
import com.android.wm.shell.freeform.FreeformTaskTransitionStarter;
import com.android.wm.shell.transition.Transitions;
@@ -239,7 +239,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel<Caption
private boolean shouldShowWindowDecor(RunningTaskInfo taskInfo) {
if (taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) return true;
return DesktopMode.IS_SUPPORTED
return DesktopModeStatus.IS_SUPPORTED
&& mDisplayController.getDisplayContext(taskInfo.displayId)
.getResources().getConfiguration().smallestScreenWidthDp >= 600;
}

View File

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

View File

@@ -44,6 +44,7 @@ public final class TestRunningTaskInfoBuilder {
private ActivityManager.TaskDescription.Builder mTaskDescriptionBuilder = null;
private final Point mPositionInParent = new Point();
private boolean mIsVisible = false;
private long mLastActiveTime;
public static WindowContainerToken createMockWCToken() {
final IWindowContainerToken itoken = mock(IWindowContainerToken.class);
@@ -52,6 +53,11 @@ public final class TestRunningTaskInfoBuilder {
return new WindowContainerToken(itoken);
}
public TestRunningTaskInfoBuilder setToken(WindowContainerToken token) {
mToken = token;
return this;
}
public TestRunningTaskInfoBuilder setBounds(Rect bounds) {
mBounds.set(bounds);
return this;
@@ -95,6 +101,11 @@ public final class TestRunningTaskInfoBuilder {
return this;
}
public TestRunningTaskInfoBuilder setLastActiveTime(long lastActiveTime) {
mLastActiveTime = lastActiveTime;
return this;
}
public ActivityManager.RunningTaskInfo build() {
final ActivityManager.RunningTaskInfo info = new ActivityManager.RunningTaskInfo();
info.taskId = sNextTaskId++;
@@ -110,6 +121,7 @@ public final class TestRunningTaskInfoBuilder {
mTaskDescriptionBuilder != null ? mTaskDescriptionBuilder.build() : null;
info.positionInParent = mPositionInParent;
info.isVisible = mIsVisible;
info.lastActiveTime = mLastActiveTime;
return info;
}
}

View File

@@ -19,16 +19,22 @@ 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 android.app.WindowConfiguration.WINDOW_CONFIG_BOUNDS;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
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.WindowConfiguration;
import android.app.ActivityManager;
import android.os.Handler;
import android.os.IBinder;
import android.testing.AndroidTestingRunner;
@@ -39,13 +45,17 @@ import android.window.WindowContainerTransaction.Change;
import androidx.test.filters.SmallTest;
import com.android.dx.mockito.inline.extended.StaticMockitoSession;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestRunningTaskInfoBuilder;
import com.android.wm.shell.TestShellExecutor;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -67,18 +77,38 @@ public class DesktopModeControllerTest extends ShellTestCase {
private Handler mMockHandler;
@Mock
private Transitions mMockTransitions;
private TestShellExecutor mExecutor;
private DesktopModeController mController;
private DesktopModeTaskRepository mDesktopModeTaskRepository;
private ShellInit mShellInit;
private StaticMockitoSession mMockitoSession;
@Before
public void setUp() {
mMockitoSession = mockitoSession().mockStatic(DesktopModeStatus.class).startMocking();
when(DesktopModeStatus.isActive(any())).thenReturn(true);
mShellInit = Mockito.spy(new ShellInit(mTestExecutor));
mExecutor = new TestShellExecutor();
mDesktopModeTaskRepository = new DesktopModeTaskRepository();
mController = new DesktopModeController(mContext, mShellInit, mShellTaskOrganizer,
mRootTaskDisplayAreaOrganizer, mMockHandler, mMockTransitions);
mRootTaskDisplayAreaOrganizer, mMockTransitions,
mDesktopModeTaskRepository, mMockHandler, mExecutor);
when(mShellTaskOrganizer.prepareClearFreeformForStandardTasks(anyInt())).thenReturn(
new WindowContainerTransaction());
mShellInit.init();
clearInvocations(mShellTaskOrganizer);
clearInvocations(mRootTaskDisplayAreaOrganizer);
}
@After
public void tearDown() {
mMockitoSession.finishMocking();
}
@Test
@@ -159,17 +189,15 @@ public class DesktopModeControllerTest extends ShellTestCase {
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);
Change taskWmMode = wct.getChanges().get(taskWmMockToken.binder());
assertThat(taskWmMode).isNotNull();
assertThat(taskWmMode.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();
Change bounds = wct.getChanges().get(taskBoundsMockToken.binder());
assertThat(bounds).isNotNull();
assertThat(bounds.getWindowSetMask() & WINDOW_CONFIG_BOUNDS).isNotEqualTo(0);
assertThat(bounds.getConfiguration().windowConfiguration.getBounds().isEmpty()).isTrue();
// Verify executed WCT has a change for setting display windowing mode to fullscreen
Change displayWmModeChange = wct.getChanges().get(displayAreaInfo.token.asBinder());
@@ -177,6 +205,41 @@ public class DesktopModeControllerTest extends ShellTestCase {
assertThat(displayWmModeChange.getWindowingMode()).isEqualTo(WINDOWING_MODE_FULLSCREEN);
}
@Test
public void testShowDesktopApps() {
// Set up two active tasks on desktop
mDesktopModeTaskRepository.addActiveTask(1);
mDesktopModeTaskRepository.addActiveTask(2);
MockToken token1 = new MockToken();
MockToken token2 = new MockToken();
ActivityManager.RunningTaskInfo taskInfo1 = new TestRunningTaskInfoBuilder().setToken(
token1.token()).setLastActiveTime(100).build();
ActivityManager.RunningTaskInfo taskInfo2 = new TestRunningTaskInfoBuilder().setToken(
token2.token()).setLastActiveTime(200).build();
when(mShellTaskOrganizer.getRunningTaskInfo(1)).thenReturn(taskInfo1);
when(mShellTaskOrganizer.getRunningTaskInfo(2)).thenReturn(taskInfo2);
// Run show desktop apps logic
mController.showDesktopApps();
ArgumentCaptor<WindowContainerTransaction> wctCaptor = ArgumentCaptor.forClass(
WindowContainerTransaction.class);
verify(mShellTaskOrganizer).applyTransaction(wctCaptor.capture());
WindowContainerTransaction wct = wctCaptor.getValue();
// Check wct has reorder calls
assertThat(wct.getHierarchyOps()).hasSize(2);
// Task 2 has activity later, must be first
WindowContainerTransaction.HierarchyOp op1 = wct.getHierarchyOps().get(0);
assertThat(op1.getType()).isEqualTo(HIERARCHY_OP_TYPE_REORDER);
assertThat(op1.getContainer()).isEqualTo(token2.binder());
// Task 1 should be second
WindowContainerTransaction.HierarchyOp op2 = wct.getHierarchyOps().get(0);
assertThat(op2.getType()).isEqualTo(HIERARCHY_OP_TYPE_REORDER);
assertThat(op2.getContainer()).isEqualTo(token2.binder());
}
private static class MockToken {
private final WindowContainerToken mToken;
private final IBinder mBinder;

View File

@@ -54,7 +54,7 @@ import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestShellExecutor;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.desktopmode.DesktopMode;
import com.android.wm.shell.desktopmode.DesktopModeStatus;
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellInit;
@@ -190,8 +190,8 @@ public class RecentTasksControllerTest extends ShellTestCase {
@Test
public void testGetRecentTasks_groupActiveFreeformTasks() {
StaticMockitoSession mockitoSession = mockitoSession().mockStatic(
DesktopMode.class).startMocking();
when(DesktopMode.isActive(any())).thenReturn(true);
DesktopModeStatus.class).startMocking();
when(DesktopModeStatus.isActive(any())).thenReturn(true);
ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1);
ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2);

View File

@@ -237,6 +237,13 @@ public class Task {
public ActivityManager.RecentTaskInfo.PersistedTaskSnapshotData lastSnapshotData =
new ActivityManager.RecentTaskInfo.PersistedTaskSnapshotData();
/**
* Indicates that this task for the desktop tile in recents.
*
* Used when desktop mode feature is enabled.
*/
public boolean desktopTile;
public Task() {
// Do nothing
}
@@ -267,6 +274,7 @@ public class Task {
this(other.key, other.colorPrimary, other.colorBackground, other.isDockable,
other.isLocked, other.taskDescription, other.topActivity);
lastSnapshotData.set(other.lastSnapshotData);
desktopTile = other.desktopTile;
}
/**

View File

@@ -62,6 +62,8 @@ public class QuickStepContract {
// See IRecentTasks.aidl
public static final String KEY_EXTRA_RECENT_TASKS = "recent_tasks";
public static final String KEY_EXTRA_SHELL_BACK_ANIMATION = "extra_shell_back_animation";
// See IDesktopMode.aidl
public static final String KEY_EXTRA_SHELL_DESKTOP_MODE = "extra_shell_desktop_mode";
public static final String NAV_BAR_MODE_3BUTTON_OVERLAY =
WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY;

View File

@@ -97,7 +97,8 @@ public abstract class SystemUIInitializer {
.setDisplayAreaHelper(mWMComponent.getDisplayAreaHelper())
.setRecentTasks(mWMComponent.getRecentTasks())
.setBackAnimation(mWMComponent.getBackAnimation())
.setFloatingTasks(mWMComponent.getFloatingTasks());
.setFloatingTasks(mWMComponent.getFloatingTasks())
.setDesktopMode(mWMComponent.getDesktopMode());
// Only initialize when not starting from tests since this currently initializes some
// components that shouldn't be run in the test environment
@@ -117,7 +118,8 @@ public abstract class SystemUIInitializer {
.setStartingSurface(Optional.ofNullable(null))
.setRecentTasks(Optional.ofNullable(null))
.setBackAnimation(Optional.ofNullable(null))
.setFloatingTasks(Optional.ofNullable(null));
.setFloatingTasks(Optional.ofNullable(null))
.setDesktopMode(Optional.ofNullable(null));
}
mSysUIComponent = builder.build();
if (initializeComponents) {

View File

@@ -40,6 +40,7 @@ import com.android.systemui.unfold.util.NaturalRotationUnfoldProgressProvider;
import com.android.wm.shell.TaskViewFactory;
import com.android.wm.shell.back.BackAnimation;
import com.android.wm.shell.bubbles.Bubbles;
import com.android.wm.shell.desktopmode.DesktopMode;
import com.android.wm.shell.displayareahelper.DisplayAreaHelper;
import com.android.wm.shell.floating.FloatingTasks;
import com.android.wm.shell.onehanded.OneHanded;
@@ -113,6 +114,9 @@ public interface SysUIComponent {
@BindsInstance
Builder setFloatingTasks(Optional<FloatingTasks> f);
@BindsInstance
Builder setDesktopMode(Optional<DesktopMode> d);
SysUIComponent build();
}

View File

@@ -30,6 +30,7 @@ import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.dagger.TvWMShellModule;
import com.android.wm.shell.dagger.WMShellModule;
import com.android.wm.shell.dagger.WMSingleton;
import com.android.wm.shell.desktopmode.DesktopMode;
import com.android.wm.shell.displayareahelper.DisplayAreaHelper;
import com.android.wm.shell.floating.FloatingTasks;
import com.android.wm.shell.onehanded.OneHanded;
@@ -112,4 +113,10 @@ public interface WMComponent {
@WMSingleton
Optional<FloatingTasks> getFloatingTasks();
/**
* Optional {@link DesktopMode} component for interacting with desktop mode.
*/
@WMSingleton
Optional<DesktopMode> getDesktopMode();
}

View File

@@ -27,6 +27,7 @@ import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON;
import static com.android.internal.accessibility.common.ShortcutConstants.CHOOSER_PACKAGE_NAME;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_RECENT_TASKS;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_BACK_ANIMATION;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_DESKTOP_MODE;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_FLOATING_TASKS;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_ONE_HANDED;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_PIP;
@@ -110,6 +111,7 @@ import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.StatusBarWindowCallback;
import com.android.systemui.statusbar.policy.CallbackController;
import com.android.wm.shell.back.BackAnimation;
import com.android.wm.shell.desktopmode.DesktopMode;
import com.android.wm.shell.floating.FloatingTasks;
import com.android.wm.shell.onehanded.OneHanded;
import com.android.wm.shell.pip.Pip;
@@ -169,6 +171,7 @@ public class OverviewProxyService extends CurrentUserTracker implements
private final KeyguardUnlockAnimationController mSysuiUnlockAnimationController;
private final Optional<RecentTasks> mRecentTasks;
private final Optional<BackAnimation> mBackAnimation;
private final Optional<DesktopMode> mDesktopModeOptional;
private final UiEventLogger mUiEventLogger;
private Region mActiveNavBarRegion;
@@ -488,6 +491,9 @@ public class OverviewProxyService extends CurrentUserTracker implements
mBackAnimation.ifPresent((backAnimation) -> params.putBinder(
KEY_EXTRA_SHELL_BACK_ANIMATION,
backAnimation.createExternalInterface().asBinder()));
mDesktopModeOptional.ifPresent((desktopMode -> params.putBinder(
KEY_EXTRA_SHELL_DESKTOP_MODE,
desktopMode.createExternalInterface().asBinder())));
try {
Log.d(TAG_OPS, "OverviewProxyService connected, initializing overview proxy");
@@ -573,6 +579,7 @@ public class OverviewProxyService extends CurrentUserTracker implements
Optional<RecentTasks> recentTasks,
Optional<BackAnimation> backAnimation,
Optional<StartingSurface> startingSurface,
Optional<DesktopMode> desktopModeOptional,
BroadcastDispatcher broadcastDispatcher,
ShellTransitions shellTransitions,
ScreenLifecycle screenLifecycle,
@@ -607,6 +614,7 @@ public class OverviewProxyService extends CurrentUserTracker implements
mShellTransitions = shellTransitions;
mRecentTasks = recentTasks;
mBackAnimation = backAnimation;
mDesktopModeOptional = desktopModeOptional;
mUiEventLogger = uiEventLogger;
dumpManager.registerDumpable(getClass().getSimpleName(), this);