Merge "Exclude nav bar area from apps bounds when in kids mode" into tm-dev
This commit is contained in:
@@ -21,6 +21,7 @@ import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSIT
|
||||
import com.android.wm.shell.apppairs.AppPairsController;
|
||||
import com.android.wm.shell.common.ShellExecutor;
|
||||
import com.android.wm.shell.hidedisplaycutout.HideDisplayCutoutController;
|
||||
import com.android.wm.shell.kidsmode.KidsModeTaskOrganizer;
|
||||
import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController;
|
||||
import com.android.wm.shell.onehanded.OneHandedController;
|
||||
import com.android.wm.shell.pip.Pip;
|
||||
@@ -46,11 +47,13 @@ public final class ShellCommandHandlerImpl {
|
||||
private final Optional<AppPairsController> mAppPairsOptional;
|
||||
private final Optional<RecentTasksController> mRecentTasks;
|
||||
private final ShellTaskOrganizer mShellTaskOrganizer;
|
||||
private final KidsModeTaskOrganizer mKidsModeTaskOrganizer;
|
||||
private final ShellExecutor mMainExecutor;
|
||||
private final HandlerImpl mImpl = new HandlerImpl();
|
||||
|
||||
public ShellCommandHandlerImpl(
|
||||
ShellTaskOrganizer shellTaskOrganizer,
|
||||
KidsModeTaskOrganizer kidsModeTaskOrganizer,
|
||||
Optional<LegacySplitScreenController> legacySplitScreenOptional,
|
||||
Optional<SplitScreenController> splitScreenOptional,
|
||||
Optional<Pip> pipOptional,
|
||||
@@ -60,6 +63,7 @@ public final class ShellCommandHandlerImpl {
|
||||
Optional<RecentTasksController> recentTasks,
|
||||
ShellExecutor mainExecutor) {
|
||||
mShellTaskOrganizer = shellTaskOrganizer;
|
||||
mKidsModeTaskOrganizer = kidsModeTaskOrganizer;
|
||||
mRecentTasks = recentTasks;
|
||||
mLegacySplitScreenOptional = legacySplitScreenOptional;
|
||||
mSplitScreenOptional = splitScreenOptional;
|
||||
@@ -92,6 +96,9 @@ public final class ShellCommandHandlerImpl {
|
||||
pw.println();
|
||||
pw.println();
|
||||
mRecentTasks.ifPresent(recentTasks -> recentTasks.dump(pw, ""));
|
||||
pw.println();
|
||||
pw.println();
|
||||
mKidsModeTaskOrganizer.dump(pw, "");
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ import com.android.wm.shell.draganddrop.DragAndDropController;
|
||||
import com.android.wm.shell.freeform.FreeformTaskListener;
|
||||
import com.android.wm.shell.fullscreen.FullscreenTaskListener;
|
||||
import com.android.wm.shell.fullscreen.FullscreenUnfoldController;
|
||||
import com.android.wm.shell.kidsmode.KidsModeTaskOrganizer;
|
||||
import com.android.wm.shell.pip.phone.PipTouchHandler;
|
||||
import com.android.wm.shell.recents.RecentTasksController;
|
||||
import com.android.wm.shell.splitscreen.SplitScreenController;
|
||||
@@ -48,6 +49,7 @@ public class ShellInitImpl {
|
||||
private final DisplayInsetsController mDisplayInsetsController;
|
||||
private final DragAndDropController mDragAndDropController;
|
||||
private final ShellTaskOrganizer mShellTaskOrganizer;
|
||||
private final KidsModeTaskOrganizer mKidsModeTaskOrganizer;
|
||||
private final Optional<BubbleController> mBubblesOptional;
|
||||
private final Optional<SplitScreenController> mSplitScreenOptional;
|
||||
private final Optional<AppPairsController> mAppPairsOptional;
|
||||
@@ -68,6 +70,7 @@ public class ShellInitImpl {
|
||||
DisplayInsetsController displayInsetsController,
|
||||
DragAndDropController dragAndDropController,
|
||||
ShellTaskOrganizer shellTaskOrganizer,
|
||||
KidsModeTaskOrganizer kidsModeTaskOrganizer,
|
||||
Optional<BubbleController> bubblesOptional,
|
||||
Optional<SplitScreenController> splitScreenOptional,
|
||||
Optional<AppPairsController> appPairsOptional,
|
||||
@@ -84,6 +87,7 @@ public class ShellInitImpl {
|
||||
mDisplayInsetsController = displayInsetsController;
|
||||
mDragAndDropController = dragAndDropController;
|
||||
mShellTaskOrganizer = shellTaskOrganizer;
|
||||
mKidsModeTaskOrganizer = kidsModeTaskOrganizer;
|
||||
mBubblesOptional = bubblesOptional;
|
||||
mSplitScreenOptional = splitScreenOptional;
|
||||
mAppPairsOptional = appPairsOptional;
|
||||
@@ -136,6 +140,9 @@ public class ShellInitImpl {
|
||||
|
||||
mFullscreenUnfoldController.ifPresent(FullscreenUnfoldController::init);
|
||||
mRecentTasks.ifPresent(RecentTasksController::init);
|
||||
|
||||
// Initialize kids mode task organizer
|
||||
mKidsModeTaskOrganizer.initialize(mStartingWindow);
|
||||
}
|
||||
|
||||
@ExternalThread
|
||||
|
||||
@@ -196,8 +196,8 @@ public class ShellTaskOrganizer extends TaskOrganizer implements
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
ShellTaskOrganizer(ITaskOrganizerController taskOrganizerController, ShellExecutor mainExecutor,
|
||||
Context context, @Nullable CompatUIController compatUI,
|
||||
protected ShellTaskOrganizer(ITaskOrganizerController taskOrganizerController,
|
||||
ShellExecutor mainExecutor, Context context, @Nullable CompatUIController compatUI,
|
||||
Optional<RecentTasksController> recentTasks) {
|
||||
super(taskOrganizerController, mainExecutor);
|
||||
mCompatUI = compatUI;
|
||||
|
||||
@@ -109,6 +109,14 @@ public class DisplayController {
|
||||
return r != null ? r.mContext : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the InsetsState of a display.
|
||||
*/
|
||||
public InsetsState getInsetsState(int displayId) {
|
||||
final DisplayRecord r = mDisplays.get(displayId);
|
||||
return r != null ? r.mInsetsState : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the insets for a given display.
|
||||
*/
|
||||
|
||||
@@ -68,6 +68,7 @@ import com.android.wm.shell.fullscreen.FullscreenTaskListener;
|
||||
import com.android.wm.shell.fullscreen.FullscreenUnfoldController;
|
||||
import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout;
|
||||
import com.android.wm.shell.hidedisplaycutout.HideDisplayCutoutController;
|
||||
import com.android.wm.shell.kidsmode.KidsModeTaskOrganizer;
|
||||
import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
|
||||
import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController;
|
||||
import com.android.wm.shell.onehanded.OneHanded;
|
||||
@@ -184,7 +185,23 @@ public abstract class WMShellBaseModule {
|
||||
|
||||
@WMSingleton
|
||||
@Provides
|
||||
static Optional<CompatUI> provideCompatUI(CompatUIController compatUIController) {
|
||||
static KidsModeTaskOrganizer provideKidsModeTaskOrganizer(
|
||||
@ShellMainThread ShellExecutor mainExecutor,
|
||||
@ShellMainThread Handler mainHandler,
|
||||
Context context,
|
||||
CompatUIController compatUI,
|
||||
SyncTransactionQueue syncTransactionQueue,
|
||||
DisplayController displayController,
|
||||
DisplayInsetsController displayInsetsController,
|
||||
Optional<RecentTasksController> recentTasksOptional
|
||||
) {
|
||||
return new KidsModeTaskOrganizer(mainExecutor, mainHandler, context, compatUI,
|
||||
syncTransactionQueue, displayController, displayInsetsController,
|
||||
recentTasksOptional);
|
||||
}
|
||||
|
||||
@WMSingleton
|
||||
@Provides static Optional<CompatUI> provideCompatUI(CompatUIController compatUIController) {
|
||||
return Optional.of(compatUIController.asCompatUI());
|
||||
}
|
||||
|
||||
@@ -637,6 +654,7 @@ public abstract class WMShellBaseModule {
|
||||
DisplayInsetsController displayInsetsController,
|
||||
DragAndDropController dragAndDropController,
|
||||
ShellTaskOrganizer shellTaskOrganizer,
|
||||
KidsModeTaskOrganizer kidsModeTaskOrganizer,
|
||||
Optional<BubbleController> bubblesOptional,
|
||||
Optional<SplitScreenController> splitScreenOptional,
|
||||
Optional<AppPairsController> appPairsOptional,
|
||||
@@ -653,6 +671,7 @@ public abstract class WMShellBaseModule {
|
||||
displayInsetsController,
|
||||
dragAndDropController,
|
||||
shellTaskOrganizer,
|
||||
kidsModeTaskOrganizer,
|
||||
bubblesOptional,
|
||||
splitScreenOptional,
|
||||
appPairsOptional,
|
||||
@@ -680,6 +699,7 @@ public abstract class WMShellBaseModule {
|
||||
@Provides
|
||||
static ShellCommandHandlerImpl provideShellCommandHandlerImpl(
|
||||
ShellTaskOrganizer shellTaskOrganizer,
|
||||
KidsModeTaskOrganizer kidsModeTaskOrganizer,
|
||||
Optional<LegacySplitScreenController> legacySplitScreenOptional,
|
||||
Optional<SplitScreenController> splitScreenOptional,
|
||||
Optional<Pip> pipOptional,
|
||||
@@ -688,7 +708,7 @@ public abstract class WMShellBaseModule {
|
||||
Optional<AppPairsController> appPairsOptional,
|
||||
Optional<RecentTasksController> recentTasksOptional,
|
||||
@ShellMainThread ShellExecutor mainExecutor) {
|
||||
return new ShellCommandHandlerImpl(shellTaskOrganizer,
|
||||
return new ShellCommandHandlerImpl(shellTaskOrganizer, kidsModeTaskOrganizer,
|
||||
legacySplitScreenOptional, splitScreenOptional, pipOptional, oneHandedOptional,
|
||||
hideDisplayCutout, appPairsOptional, recentTasksOptional, mainExecutor);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,341 @@
|
||||
/*
|
||||
* 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.kidsmode;
|
||||
|
||||
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
|
||||
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
|
||||
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
|
||||
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
|
||||
import static android.view.Display.DEFAULT_DISPLAY;
|
||||
|
||||
import android.app.ActivityManager;
|
||||
import android.content.Context;
|
||||
import android.content.res.Configuration;
|
||||
import android.graphics.Rect;
|
||||
import android.os.Binder;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.view.InsetsSource;
|
||||
import android.view.InsetsState;
|
||||
import android.view.SurfaceControl;
|
||||
import android.window.ITaskOrganizerController;
|
||||
import android.window.TaskAppearedInfo;
|
||||
import android.window.WindowContainerToken;
|
||||
import android.window.WindowContainerTransaction;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.policy.ForceShowNavigationBarSettingsObserver;
|
||||
import com.android.wm.shell.ShellTaskOrganizer;
|
||||
import com.android.wm.shell.common.DisplayController;
|
||||
import com.android.wm.shell.common.DisplayInsetsController;
|
||||
import com.android.wm.shell.common.DisplayLayout;
|
||||
import com.android.wm.shell.common.ShellExecutor;
|
||||
import com.android.wm.shell.common.SyncTransactionQueue;
|
||||
import com.android.wm.shell.compatui.CompatUIController;
|
||||
import com.android.wm.shell.recents.RecentTasksController;
|
||||
import com.android.wm.shell.startingsurface.StartingWindowController;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* A dedicated task organizer when kids mode is enabled.
|
||||
* - Creates a root task with bounds that exclude the navigation bar area
|
||||
* - Launch all task into the root task except for Launcher
|
||||
*/
|
||||
public class KidsModeTaskOrganizer extends ShellTaskOrganizer {
|
||||
private static final String TAG = "KidsModeTaskOrganizer";
|
||||
|
||||
private static final int[] CONTROLLED_ACTIVITY_TYPES =
|
||||
{ACTIVITY_TYPE_UNDEFINED, ACTIVITY_TYPE_STANDARD};
|
||||
private static final int[] CONTROLLED_WINDOWING_MODES =
|
||||
{WINDOWING_MODE_FULLSCREEN, WINDOWING_MODE_UNDEFINED};
|
||||
|
||||
private final Handler mMainHandler;
|
||||
private final Context mContext;
|
||||
private final SyncTransactionQueue mSyncQueue;
|
||||
private final DisplayController mDisplayController;
|
||||
private final DisplayInsetsController mDisplayInsetsController;
|
||||
|
||||
@VisibleForTesting
|
||||
ActivityManager.RunningTaskInfo mLaunchRootTask;
|
||||
@VisibleForTesting
|
||||
SurfaceControl mLaunchRootLeash;
|
||||
@VisibleForTesting
|
||||
final IBinder mCookie = new Binder();
|
||||
|
||||
private final InsetsState mInsetsState = new InsetsState();
|
||||
private int mDisplayWidth;
|
||||
private int mDisplayHeight;
|
||||
|
||||
private ForceShowNavigationBarSettingsObserver mForceShowNavigationBarSettingsObserver;
|
||||
private boolean mEnabled;
|
||||
|
||||
DisplayController.OnDisplaysChangedListener mOnDisplaysChangedListener =
|
||||
new DisplayController.OnDisplaysChangedListener() {
|
||||
@Override
|
||||
public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) {
|
||||
if (displayId != DEFAULT_DISPLAY) {
|
||||
return;
|
||||
}
|
||||
final DisplayLayout displayLayout =
|
||||
mDisplayController.getDisplayLayout(DEFAULT_DISPLAY);
|
||||
if (displayLayout == null) {
|
||||
return;
|
||||
}
|
||||
final int displayWidth = displayLayout.width();
|
||||
final int displayHeight = displayLayout.height();
|
||||
if (displayWidth == mDisplayWidth || displayHeight == mDisplayHeight) {
|
||||
return;
|
||||
}
|
||||
mDisplayWidth = displayWidth;
|
||||
mDisplayHeight = displayHeight;
|
||||
updateBounds();
|
||||
}
|
||||
};
|
||||
|
||||
DisplayInsetsController.OnInsetsChangedListener mOnInsetsChangedListener =
|
||||
new DisplayInsetsController.OnInsetsChangedListener() {
|
||||
@Override
|
||||
public void insetsChanged(InsetsState insetsState) {
|
||||
// Update bounds only when the insets of navigation bar or task bar is changed.
|
||||
if (Objects.equals(insetsState.peekSource(InsetsState.ITYPE_NAVIGATION_BAR),
|
||||
mInsetsState.peekSource(InsetsState.ITYPE_NAVIGATION_BAR))
|
||||
&& Objects.equals(insetsState.peekSource(
|
||||
InsetsState.ITYPE_EXTRA_NAVIGATION_BAR),
|
||||
mInsetsState.peekSource(InsetsState.ITYPE_EXTRA_NAVIGATION_BAR))) {
|
||||
return;
|
||||
}
|
||||
mInsetsState.set(insetsState);
|
||||
updateBounds();
|
||||
}
|
||||
};
|
||||
|
||||
@VisibleForTesting
|
||||
KidsModeTaskOrganizer(
|
||||
ITaskOrganizerController taskOrganizerController,
|
||||
ShellExecutor mainExecutor,
|
||||
Handler mainHandler,
|
||||
Context context,
|
||||
@Nullable CompatUIController compatUI,
|
||||
SyncTransactionQueue syncTransactionQueue,
|
||||
DisplayController displayController,
|
||||
DisplayInsetsController displayInsetsController,
|
||||
Optional<RecentTasksController> recentTasks,
|
||||
ForceShowNavigationBarSettingsObserver forceShowNavigationBarSettingsObserver) {
|
||||
super(taskOrganizerController, mainExecutor, context, compatUI, recentTasks);
|
||||
mContext = context;
|
||||
mMainHandler = mainHandler;
|
||||
mSyncQueue = syncTransactionQueue;
|
||||
mDisplayController = displayController;
|
||||
mDisplayInsetsController = displayInsetsController;
|
||||
mForceShowNavigationBarSettingsObserver = forceShowNavigationBarSettingsObserver;
|
||||
}
|
||||
|
||||
public KidsModeTaskOrganizer(
|
||||
ShellExecutor mainExecutor,
|
||||
Handler mainHandler,
|
||||
Context context,
|
||||
@Nullable CompatUIController compatUI,
|
||||
SyncTransactionQueue syncTransactionQueue,
|
||||
DisplayController displayController,
|
||||
DisplayInsetsController displayInsetsController,
|
||||
Optional<RecentTasksController> recentTasks) {
|
||||
super(mainExecutor, context, compatUI, recentTasks);
|
||||
mContext = context;
|
||||
mMainHandler = mainHandler;
|
||||
mSyncQueue = syncTransactionQueue;
|
||||
mDisplayController = displayController;
|
||||
mDisplayInsetsController = displayInsetsController;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes kids mode status.
|
||||
*/
|
||||
public void initialize(StartingWindowController startingWindowController) {
|
||||
initStartingWindow(startingWindowController);
|
||||
if (mForceShowNavigationBarSettingsObserver == null) {
|
||||
mForceShowNavigationBarSettingsObserver = new ForceShowNavigationBarSettingsObserver(
|
||||
mMainHandler, mContext);
|
||||
}
|
||||
mForceShowNavigationBarSettingsObserver.setOnChangeRunnable(() -> updateKidsModeState());
|
||||
updateKidsModeState();
|
||||
mForceShowNavigationBarSettingsObserver.register();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
|
||||
if (mEnabled && mLaunchRootTask == null && taskInfo.launchCookies != null
|
||||
&& taskInfo.launchCookies.contains(mCookie)) {
|
||||
mLaunchRootTask = taskInfo;
|
||||
mLaunchRootLeash = leash;
|
||||
updateTask();
|
||||
}
|
||||
super.onTaskAppeared(taskInfo, leash);
|
||||
|
||||
mSyncQueue.runInSync(t -> {
|
||||
// Reset several properties back to fullscreen (PiP, for example, leaves all these
|
||||
// properties in a bad state).
|
||||
t.setCrop(leash, null);
|
||||
t.setPosition(leash, 0, 0);
|
||||
t.setAlpha(leash, 1f);
|
||||
t.setMatrix(leash, 1, 0, 0, 1);
|
||||
t.show(leash);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
|
||||
if (mLaunchRootTask != null && mLaunchRootTask.taskId == taskInfo.taskId
|
||||
&& !taskInfo.equals(mLaunchRootTask)) {
|
||||
mLaunchRootTask = taskInfo;
|
||||
}
|
||||
|
||||
super.onTaskInfoChanged(taskInfo);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void updateKidsModeState() {
|
||||
final boolean enabled = mForceShowNavigationBarSettingsObserver.isEnabled();
|
||||
if (mEnabled == enabled) {
|
||||
return;
|
||||
}
|
||||
mEnabled = enabled;
|
||||
if (mEnabled) {
|
||||
enable();
|
||||
} else {
|
||||
disable();
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void enable() {
|
||||
final DisplayLayout displayLayout = mDisplayController.getDisplayLayout(DEFAULT_DISPLAY);
|
||||
if (displayLayout != null) {
|
||||
mDisplayWidth = displayLayout.width();
|
||||
mDisplayHeight = displayLayout.height();
|
||||
}
|
||||
mInsetsState.set(mDisplayController.getInsetsState(DEFAULT_DISPLAY));
|
||||
mDisplayInsetsController.addInsetsChangedListener(DEFAULT_DISPLAY,
|
||||
mOnInsetsChangedListener);
|
||||
mDisplayController.addDisplayWindowListener(mOnDisplaysChangedListener);
|
||||
List<TaskAppearedInfo> taskAppearedInfos = registerOrganizer();
|
||||
for (int i = 0; i < taskAppearedInfos.size(); i++) {
|
||||
final TaskAppearedInfo info = taskAppearedInfos.get(i);
|
||||
onTaskAppeared(info.getTaskInfo(), info.getLeash());
|
||||
}
|
||||
createRootTask(DEFAULT_DISPLAY, WINDOWING_MODE_FULLSCREEN, mCookie);
|
||||
updateTask();
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void disable() {
|
||||
mDisplayInsetsController.removeInsetsChangedListener(DEFAULT_DISPLAY,
|
||||
mOnInsetsChangedListener);
|
||||
mDisplayController.removeDisplayWindowListener(mOnDisplaysChangedListener);
|
||||
updateTask();
|
||||
final WindowContainerToken token = mLaunchRootTask.token;
|
||||
if (token != null) {
|
||||
deleteRootTask(token);
|
||||
}
|
||||
mLaunchRootTask = null;
|
||||
mLaunchRootLeash = null;
|
||||
unregisterOrganizer();
|
||||
}
|
||||
|
||||
private void updateTask() {
|
||||
updateTask(getWindowContainerTransaction());
|
||||
}
|
||||
|
||||
private void updateTask(WindowContainerTransaction wct) {
|
||||
if (mLaunchRootTask == null || mLaunchRootLeash == null) {
|
||||
return;
|
||||
}
|
||||
final Rect taskBounds = calculateBounds();
|
||||
final WindowContainerToken rootToken = mLaunchRootTask.token;
|
||||
wct.setBounds(rootToken, mEnabled ? taskBounds : null);
|
||||
wct.setLaunchRoot(rootToken,
|
||||
mEnabled ? CONTROLLED_WINDOWING_MODES : null,
|
||||
mEnabled ? CONTROLLED_ACTIVITY_TYPES : null);
|
||||
wct.reparentTasks(
|
||||
mEnabled ? null : rootToken /* currentParent */,
|
||||
mEnabled ? rootToken : null /* newParent */,
|
||||
CONTROLLED_WINDOWING_MODES,
|
||||
CONTROLLED_ACTIVITY_TYPES,
|
||||
true /* onTop */);
|
||||
wct.reorder(rootToken, mEnabled /* onTop */);
|
||||
mSyncQueue.queue(wct);
|
||||
final SurfaceControl rootLeash = mLaunchRootLeash;
|
||||
mSyncQueue.runInSync(t -> {
|
||||
t.setPosition(rootLeash, taskBounds.left, taskBounds.top);
|
||||
t.setWindowCrop(rootLeash, taskBounds.width(), taskBounds.height());
|
||||
});
|
||||
}
|
||||
|
||||
private Rect calculateBounds() {
|
||||
final Rect bounds = new Rect(0, 0, mDisplayWidth, mDisplayHeight);
|
||||
final InsetsSource navBarSource = mInsetsState.peekSource(InsetsState.ITYPE_NAVIGATION_BAR);
|
||||
final InsetsSource taskBarSource = mInsetsState.peekSource(
|
||||
InsetsState.ITYPE_EXTRA_NAVIGATION_BAR);
|
||||
if (navBarSource != null && !navBarSource.getFrame().isEmpty()) {
|
||||
bounds.inset(navBarSource.calculateInsets(bounds, false /* ignoreVisibility */));
|
||||
} else if (taskBarSource != null && !taskBarSource.getFrame().isEmpty()) {
|
||||
bounds.inset(taskBarSource.calculateInsets(bounds, false /* ignoreVisibility */));
|
||||
} else {
|
||||
bounds.setEmpty();
|
||||
}
|
||||
return bounds;
|
||||
}
|
||||
|
||||
private void updateBounds() {
|
||||
if (mLaunchRootTask == null) {
|
||||
return;
|
||||
}
|
||||
final WindowContainerTransaction wct = getWindowContainerTransaction();
|
||||
final Rect taskBounds = calculateBounds();
|
||||
wct.setBounds(mLaunchRootTask.token, taskBounds);
|
||||
mSyncQueue.queue(wct);
|
||||
final SurfaceControl finalLeash = mLaunchRootLeash;
|
||||
mSyncQueue.runInSync(t -> {
|
||||
t.setPosition(finalLeash, taskBounds.left, taskBounds.top);
|
||||
t.setWindowCrop(finalLeash, taskBounds.width(), taskBounds.height());
|
||||
});
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
WindowContainerTransaction getWindowContainerTransaction() {
|
||||
return new WindowContainerTransaction();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dump(@NonNull PrintWriter pw, String prefix) {
|
||||
final String innerPrefix = prefix + " ";
|
||||
pw.println(prefix + TAG);
|
||||
pw.println(innerPrefix + " mEnabled=" + mEnabled);
|
||||
pw.println(innerPrefix + " mLaunchRootTask=" + mLaunchRootTask);
|
||||
pw.println(innerPrefix + " mLaunchRootLeash=" + mLaunchRootLeash);
|
||||
pw.println(innerPrefix + " mDisplayWidth=" + mDisplayWidth);
|
||||
pw.println(innerPrefix + " mDisplayHeight=" + mDisplayHeight);
|
||||
pw.println(innerPrefix + " mInsetsState=" + mInsetsState);
|
||||
super.dump(pw, innerPrefix);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
/*
|
||||
* 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.kidsmode;
|
||||
|
||||
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
|
||||
import static android.view.Display.DEFAULT_DISPLAY;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
import android.app.ActivityManager;
|
||||
import android.content.Context;
|
||||
import android.content.pm.ParceledListSlice;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteException;
|
||||
import android.view.InsetsState;
|
||||
import android.view.SurfaceControl;
|
||||
import android.window.ITaskOrganizerController;
|
||||
import android.window.TaskAppearedInfo;
|
||||
import android.window.WindowContainerToken;
|
||||
import android.window.WindowContainerTransaction;
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.android.internal.policy.ForceShowNavigationBarSettingsObserver;
|
||||
import com.android.wm.shell.common.DisplayController;
|
||||
import com.android.wm.shell.common.DisplayInsetsController;
|
||||
import com.android.wm.shell.common.ShellExecutor;
|
||||
import com.android.wm.shell.common.SyncTransactionQueue;
|
||||
import com.android.wm.shell.compatui.CompatUIController;
|
||||
import com.android.wm.shell.startingsurface.StartingWindowController;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Optional;
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class KidsModeTaskOrganizerTest {
|
||||
@Mock private ITaskOrganizerController mTaskOrganizerController;
|
||||
@Mock private Context mContext;
|
||||
@Mock private Handler mHandler;
|
||||
@Mock private CompatUIController mCompatUI;
|
||||
@Mock private SyncTransactionQueue mSyncTransactionQueue;
|
||||
@Mock private ShellExecutor mTestExecutor;
|
||||
@Mock private DisplayController mDisplayController;
|
||||
@Mock private SurfaceControl mLeash;
|
||||
@Mock private WindowContainerToken mToken;
|
||||
@Mock private WindowContainerTransaction mTransaction;
|
||||
@Mock private ForceShowNavigationBarSettingsObserver mObserver;
|
||||
@Mock private StartingWindowController mStartingWindowController;
|
||||
@Mock private DisplayInsetsController mDisplayInsetsController;
|
||||
|
||||
KidsModeTaskOrganizer mOrganizer;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
try {
|
||||
doReturn(ParceledListSlice.<TaskAppearedInfo>emptyList())
|
||||
.when(mTaskOrganizerController).registerTaskOrganizer(any());
|
||||
} catch (RemoteException e) { }
|
||||
mOrganizer = spy(new KidsModeTaskOrganizer(mTaskOrganizerController, mTestExecutor,
|
||||
mHandler, mContext, mCompatUI, mSyncTransactionQueue, mDisplayController,
|
||||
mDisplayInsetsController, Optional.empty(), mObserver));
|
||||
mOrganizer.initialize(mStartingWindowController);
|
||||
doReturn(mTransaction).when(mOrganizer).getWindowContainerTransaction();
|
||||
doReturn(new InsetsState()).when(mDisplayController).getInsetsState(DEFAULT_DISPLAY);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testKidsModeOn() {
|
||||
doReturn(true).when(mObserver).isEnabled();
|
||||
|
||||
mOrganizer.updateKidsModeState();
|
||||
|
||||
verify(mOrganizer, times(1)).enable();
|
||||
verify(mOrganizer, times(1)).registerOrganizer();
|
||||
verify(mOrganizer, times(1)).createRootTask(
|
||||
eq(DEFAULT_DISPLAY), eq(WINDOWING_MODE_FULLSCREEN), eq(mOrganizer.mCookie));
|
||||
|
||||
final ActivityManager.RunningTaskInfo rootTask = createTaskInfo(12,
|
||||
WINDOWING_MODE_FULLSCREEN, mOrganizer.mCookie);
|
||||
mOrganizer.onTaskAppeared(rootTask, mLeash);
|
||||
|
||||
assertThat(mOrganizer.mLaunchRootLeash).isEqualTo(mLeash);
|
||||
assertThat(mOrganizer.mLaunchRootTask).isEqualTo(rootTask);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testKidsModeOff() {
|
||||
doReturn(true).when(mObserver).isEnabled();
|
||||
mOrganizer.updateKidsModeState();
|
||||
final ActivityManager.RunningTaskInfo rootTask = createTaskInfo(12,
|
||||
WINDOWING_MODE_FULLSCREEN, mOrganizer.mCookie);
|
||||
mOrganizer.onTaskAppeared(rootTask, mLeash);
|
||||
|
||||
doReturn(false).when(mObserver).isEnabled();
|
||||
mOrganizer.updateKidsModeState();
|
||||
|
||||
|
||||
verify(mOrganizer, times(1)).disable();
|
||||
verify(mOrganizer, times(1)).unregisterOrganizer();
|
||||
verify(mOrganizer, times(1)).deleteRootTask(rootTask.token);
|
||||
assertThat(mOrganizer.mLaunchRootLeash).isNull();
|
||||
assertThat(mOrganizer.mLaunchRootTask).isNull();
|
||||
}
|
||||
|
||||
private ActivityManager.RunningTaskInfo createTaskInfo(
|
||||
int taskId, int windowingMode, IBinder cookies) {
|
||||
ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo();
|
||||
taskInfo.taskId = taskId;
|
||||
taskInfo.token = mToken;
|
||||
taskInfo.configuration.windowConfiguration.setWindowingMode(windowingMode);
|
||||
final ArrayList<IBinder> launchCookies = new ArrayList<>();
|
||||
if (cookies != null) {
|
||||
launchCookies.add(cookies);
|
||||
}
|
||||
taskInfo.launchCookies = launchCookies;
|
||||
return taskInfo;
|
||||
}
|
||||
}
|
||||
@@ -2036,8 +2036,10 @@ class Task extends TaskFragment {
|
||||
Rect outOverrideBounds = getResolvedOverrideConfiguration().windowConfiguration.getBounds();
|
||||
|
||||
if (windowingMode == WINDOWING_MODE_FULLSCREEN) {
|
||||
// Use empty bounds to indicate "fill parent".
|
||||
outOverrideBounds.setEmpty();
|
||||
if (!mCreatedByOrganizer) {
|
||||
// Use empty bounds to indicate "fill parent".
|
||||
outOverrideBounds.setEmpty();
|
||||
}
|
||||
// The bounds for fullscreen mode shouldn't be adjusted by minimal size. Otherwise if
|
||||
// the parent or display is smaller than the size, the content may be cropped.
|
||||
return;
|
||||
|
||||
Reference in New Issue
Block a user