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:
shawnlin
2022-02-17 20:12:37 +08:00
parent abd62d1f0f
commit 10d7cb3c0f
8 changed files with 540 additions and 6 deletions

View File

@@ -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, "");
}

View File

@@ -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

View File

@@ -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;

View File

@@ -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.
*/

View File

@@ -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);
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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;