Merge "Add support to show apps on desktop in sysui proxy" into tm-qpr-dev
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;
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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])));
|
||||
}
|
||||
|
||||
@@ -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