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:
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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])));
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user