Merge "Exclude nav bar area from apps bounds when in kids mode" into tm-dev

This commit is contained in:
Shawn Lin
2022-03-09 06:18:00 +00:00
committed by Android (Google) Code Review
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

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