Add support to show apps on desktop in sysui proxy

This enabled launcher to show all desktop apps when user clicks on the
desktop tile in recents.
This is only enabled when Desktop Mode feature flag is enabled.

Bug: 244348395
Test: atest DesktopModeControllerTest
Change-Id: I3c89ef01fd7d1f457a7afabda94b481711c0a350
This commit is contained in:
Ats Jenk
2022-09-09 14:50:47 -07:00
parent fcfa500483
commit d849498a69
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;
@@ -714,15 +716,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

@@ -43,7 +43,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;
@@ -286,7 +286,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
@@ -319,7 +319,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);