Exclude nav bar area from apps bounds when in kids mode
GKS requested that app's bounds should not overlap with the nav bar area
when kids mode is enabled except for Launcher.
Creates a dedicate task organizer for kids mode. When kids mode is
enabled, create a root task with its bounds excluding the nav bar area
and tasks except launcher will be launched into it.
Bug: 161689887
Test: manual:
1. adb shell settings put secure "nav_bar_force_visible" "1"
2. choose 3-buttons navigation mode in
Settings>System>Gestures>System navigation
3. check winscope and see if bounds are set correctly
Test: atest KidsModeTaskOrganizerTest
Test: atest WindowOrganizerTests
Change-Id: I93af1bc5b2696654d8ca5055dbbbebf5a41f89aa
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;
|
||||
}
|
||||
}
|
||||
@@ -2043,8 +2043,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