Remove KidsModeTaskOrganizer
To prevent app content from being obscured by navigation bar forcibly
shown in Kids Space, DisplayPolicy sets mForceConsumeSystemBars to true.
With that, we don't need KidsModeTaskOrganizer anymore.
Fix: 285036830
Fix: 283317142
Fix: 270458958
Test: Rotate the device in the home screen of Kids Space. See if the
home screen layout matches the current orientation.
Test: Exit Kids Space and make sure the device doesn't crash.
Change-Id: I7b81b161f3de8cfdc288934acb75d233523b1c9a
This commit is contained in:
@@ -61,7 +61,6 @@ import com.android.wm.shell.freeform.FreeformTaskListener;
|
||||
import com.android.wm.shell.freeform.FreeformTaskTransitionHandler;
|
||||
import com.android.wm.shell.freeform.FreeformTaskTransitionObserver;
|
||||
import com.android.wm.shell.keyguard.KeyguardTransitionHandler;
|
||||
import com.android.wm.shell.kidsmode.KidsModeTaskOrganizer;
|
||||
import com.android.wm.shell.onehanded.OneHandedController;
|
||||
import com.android.wm.shell.pip.Pip;
|
||||
import com.android.wm.shell.pip.PipAnimationController;
|
||||
@@ -709,42 +708,4 @@ public abstract class WMShellModule {
|
||||
static DesktopModeTaskRepository provideDesktopModeTaskRepository() {
|
||||
return new DesktopModeTaskRepository();
|
||||
}
|
||||
|
||||
//
|
||||
// Kids mode
|
||||
//
|
||||
@WMSingleton
|
||||
@Provides
|
||||
static KidsModeTaskOrganizer provideKidsModeTaskOrganizer(
|
||||
Context context,
|
||||
ShellInit shellInit,
|
||||
ShellCommandHandler shellCommandHandler,
|
||||
SyncTransactionQueue syncTransactionQueue,
|
||||
DisplayController displayController,
|
||||
DisplayInsetsController displayInsetsController,
|
||||
Optional<UnfoldAnimationController> unfoldAnimationController,
|
||||
Optional<RecentTasksController> recentTasksOptional,
|
||||
@ShellMainThread ShellExecutor mainExecutor,
|
||||
@ShellMainThread Handler mainHandler
|
||||
) {
|
||||
return new KidsModeTaskOrganizer(context, shellInit, shellCommandHandler,
|
||||
syncTransactionQueue, displayController, displayInsetsController,
|
||||
unfoldAnimationController, recentTasksOptional, mainExecutor, mainHandler);
|
||||
}
|
||||
|
||||
//
|
||||
// Misc
|
||||
//
|
||||
|
||||
// TODO: Temporarily move dependencies to this instead of ShellInit since that is needed to add
|
||||
// the callback. We will be moving to a different explicit startup mechanism in a follow- up CL.
|
||||
@WMSingleton
|
||||
@ShellCreateTriggerOverride
|
||||
@Provides
|
||||
static Object provideIndependentShellComponentsToCreate(
|
||||
DefaultMixedHandler defaultMixedHandler,
|
||||
KidsModeTaskOrganizer kidsModeTaskOrganizer,
|
||||
Optional<DesktopModeController> desktopModeController) {
|
||||
return new Object();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,92 +0,0 @@
|
||||
/*
|
||||
* 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 android.annotation.NonNull;
|
||||
import android.app.ActivityManager;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.database.ContentObserver;
|
||||
import android.net.Uri;
|
||||
import android.os.Handler;
|
||||
import android.os.UserHandle;
|
||||
import android.provider.Settings;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* A ContentObserver for listening kids mode relative setting keys:
|
||||
* - {@link Settings.Secure#NAVIGATION_MODE}
|
||||
* - {@link Settings.Secure#NAV_BAR_KIDS_MODE}
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public class KidsModeSettingsObserver extends ContentObserver {
|
||||
private Context mContext;
|
||||
private Runnable mOnChangeRunnable;
|
||||
|
||||
public KidsModeSettingsObserver(Handler handler, Context context) {
|
||||
super(handler);
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
public void setOnChangeRunnable(Runnable r) {
|
||||
mOnChangeRunnable = r;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the observer.
|
||||
*/
|
||||
public void register() {
|
||||
final ContentResolver r = mContext.getContentResolver();
|
||||
r.registerContentObserver(
|
||||
Settings.Secure.getUriFor(Settings.Secure.NAVIGATION_MODE),
|
||||
false, this, UserHandle.USER_ALL);
|
||||
r.registerContentObserver(
|
||||
Settings.Secure.getUriFor(Settings.Secure.NAV_BAR_KIDS_MODE),
|
||||
false, this, UserHandle.USER_ALL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters the observer.
|
||||
*/
|
||||
public void unregister() {
|
||||
mContext.getContentResolver().unregisterContentObserver(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChange(boolean selfChange, @NonNull Collection<Uri> uris, int flags, int userId) {
|
||||
if (userId != ActivityManager.getCurrentUser()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mOnChangeRunnable != null) {
|
||||
mOnChangeRunnable.run();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true only when it's in three button nav mode and the kid nav bar mode is enabled.
|
||||
* Otherwise, return false.
|
||||
*/
|
||||
public boolean isEnabled() {
|
||||
return Settings.Secure.getIntForUser(mContext.getContentResolver(),
|
||||
Settings.Secure.NAVIGATION_MODE, 0, UserHandle.USER_CURRENT) == 0
|
||||
&& Settings.Secure.getIntForUser(mContext.getContentResolver(),
|
||||
Settings.Secure.NAV_BAR_KIDS_MODE, 0, UserHandle.USER_CURRENT) == 1;
|
||||
}
|
||||
}
|
||||
@@ -1,436 +0,0 @@
|
||||
/*
|
||||
* 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_HOME;
|
||||
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.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
|
||||
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE;
|
||||
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE;
|
||||
import static android.view.Display.DEFAULT_DISPLAY;
|
||||
|
||||
import android.app.ActivityManager;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.res.Configuration;
|
||||
import android.graphics.Rect;
|
||||
import android.os.Binder;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.view.Display;
|
||||
import android.view.InsetsSource;
|
||||
import android.view.InsetsState;
|
||||
import android.view.SurfaceControl;
|
||||
import android.view.WindowInsets;
|
||||
import android.window.ITaskOrganizerController;
|
||||
import android.window.TaskAppearedInfo;
|
||||
import android.window.WindowContainerToken;
|
||||
import android.window.WindowContainerTransaction;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.android.internal.R;
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
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.recents.RecentTasksController;
|
||||
import com.android.wm.shell.sysui.ShellCommandHandler;
|
||||
import com.android.wm.shell.sysui.ShellInit;
|
||||
import com.android.wm.shell.unfold.UnfoldAnimationController;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.util.List;
|
||||
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, ACTIVITY_TYPE_HOME};
|
||||
private static final int[] CONTROLLED_WINDOWING_MODES =
|
||||
{WINDOWING_MODE_FULLSCREEN, WINDOWING_MODE_UNDEFINED};
|
||||
|
||||
private final Handler mMainHandler;
|
||||
private final Context mContext;
|
||||
private final ShellCommandHandler mShellCommandHandler;
|
||||
private final SyncTransactionQueue mSyncQueue;
|
||||
private final DisplayController mDisplayController;
|
||||
private final DisplayInsetsController mDisplayInsetsController;
|
||||
|
||||
/**
|
||||
* The value of the {@link R.bool.config_reverseDefaultRotation} property which defines how
|
||||
* {@link Display#getRotation} values are mapped to screen orientations
|
||||
*/
|
||||
private final boolean mReverseDefaultRotationEnabled;
|
||||
|
||||
@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 KidsModeSettingsObserver mKidsModeSettingsObserver;
|
||||
private boolean mEnabled;
|
||||
|
||||
private ActivityManager.RunningTaskInfo mHomeTask;
|
||||
|
||||
private final BroadcastReceiver mUserSwitchIntentReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
updateKidsModeState();
|
||||
}
|
||||
};
|
||||
|
||||
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) {
|
||||
final boolean[] navigationBarChanged = {false};
|
||||
InsetsState.traverse(insetsState, mInsetsState, new InsetsState.OnTraverseCallbacks() {
|
||||
@Override
|
||||
public void onIdMatch(InsetsSource source1, InsetsSource source2) {
|
||||
if (source1.getType() == WindowInsets.Type.navigationBars()
|
||||
&& !source1.equals(source2)) {
|
||||
navigationBarChanged[0] = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onIdNotFoundInState1(int index2, InsetsSource source2) {
|
||||
if (source2.getType() == WindowInsets.Type.navigationBars()) {
|
||||
navigationBarChanged[0] = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onIdNotFoundInState2(int index1, InsetsSource source1) {
|
||||
if (source1.getType() == WindowInsets.Type.navigationBars()) {
|
||||
navigationBarChanged[0] = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
if (!navigationBarChanged[0]) {
|
||||
return;
|
||||
}
|
||||
// Update bounds only when the insets of navigation bar or task bar is changed.
|
||||
mInsetsState.set(insetsState);
|
||||
updateBounds();
|
||||
}
|
||||
};
|
||||
|
||||
@VisibleForTesting
|
||||
KidsModeTaskOrganizer(
|
||||
Context context,
|
||||
ShellInit shellInit,
|
||||
ShellCommandHandler shellCommandHandler,
|
||||
ITaskOrganizerController taskOrganizerController,
|
||||
SyncTransactionQueue syncTransactionQueue,
|
||||
DisplayController displayController,
|
||||
DisplayInsetsController displayInsetsController,
|
||||
Optional<UnfoldAnimationController> unfoldAnimationController,
|
||||
Optional<RecentTasksController> recentTasks,
|
||||
KidsModeSettingsObserver kidsModeSettingsObserver,
|
||||
ShellExecutor mainExecutor,
|
||||
Handler mainHandler) {
|
||||
// Note: we don't call super with the shell init because we will be initializing manually
|
||||
super(/* shellInit= */ null, /* shellCommandHandler= */ null, taskOrganizerController,
|
||||
/* compatUI= */ null, unfoldAnimationController, recentTasks, mainExecutor);
|
||||
mContext = context;
|
||||
mShellCommandHandler = shellCommandHandler;
|
||||
mMainHandler = mainHandler;
|
||||
mSyncQueue = syncTransactionQueue;
|
||||
mDisplayController = displayController;
|
||||
mDisplayInsetsController = displayInsetsController;
|
||||
mKidsModeSettingsObserver = kidsModeSettingsObserver;
|
||||
shellInit.addInitCallback(this::onInit, this);
|
||||
mReverseDefaultRotationEnabled = context.getResources().getBoolean(
|
||||
R.bool.config_reverseDefaultRotation);
|
||||
}
|
||||
|
||||
public KidsModeTaskOrganizer(
|
||||
Context context,
|
||||
ShellInit shellInit,
|
||||
ShellCommandHandler shellCommandHandler,
|
||||
SyncTransactionQueue syncTransactionQueue,
|
||||
DisplayController displayController,
|
||||
DisplayInsetsController displayInsetsController,
|
||||
Optional<UnfoldAnimationController> unfoldAnimationController,
|
||||
Optional<RecentTasksController> recentTasks,
|
||||
ShellExecutor mainExecutor,
|
||||
Handler mainHandler) {
|
||||
// Note: we don't call super with the shell init because we will be initializing manually
|
||||
super(/* shellInit= */ null, /* taskOrganizerController= */ null, /* compatUI= */ null,
|
||||
unfoldAnimationController, recentTasks, mainExecutor);
|
||||
mContext = context;
|
||||
mShellCommandHandler = shellCommandHandler;
|
||||
mMainHandler = mainHandler;
|
||||
mSyncQueue = syncTransactionQueue;
|
||||
mDisplayController = displayController;
|
||||
mDisplayInsetsController = displayInsetsController;
|
||||
shellInit.addInitCallback(this::onInit, this);
|
||||
mReverseDefaultRotationEnabled = context.getResources().getBoolean(
|
||||
R.bool.config_reverseDefaultRotation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes kids mode status.
|
||||
*/
|
||||
public void onInit() {
|
||||
if (mShellCommandHandler != null) {
|
||||
mShellCommandHandler.addDumpCallback(this::dump, this);
|
||||
}
|
||||
if (mKidsModeSettingsObserver == null) {
|
||||
mKidsModeSettingsObserver = new KidsModeSettingsObserver(mMainHandler, mContext);
|
||||
}
|
||||
mKidsModeSettingsObserver.setOnChangeRunnable(() -> updateKidsModeState());
|
||||
updateKidsModeState();
|
||||
mKidsModeSettingsObserver.register();
|
||||
|
||||
final IntentFilter filter = new IntentFilter();
|
||||
filter.addAction(Intent.ACTION_USER_SWITCHED);
|
||||
mContext.registerReceiverForAllUsers(mUserSwitchIntentReceiver, filter, null, mMainHandler);
|
||||
}
|
||||
|
||||
@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);
|
||||
|
||||
// Only allow home to draw under system bars.
|
||||
if (taskInfo.getActivityType() == ACTIVITY_TYPE_HOME) {
|
||||
final WindowContainerTransaction wct = getWindowContainerTransaction();
|
||||
wct.setBounds(taskInfo.token, new Rect(0, 0, mDisplayWidth, mDisplayHeight));
|
||||
mSyncQueue.queue(wct);
|
||||
mHomeTask = taskInfo;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
if (mHomeTask != null && mHomeTask.taskId == taskInfo.taskId
|
||||
&& !taskInfo.equals(mHomeTask)) {
|
||||
mHomeTask = taskInfo;
|
||||
}
|
||||
|
||||
super.onTaskInfoChanged(taskInfo);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void updateKidsModeState() {
|
||||
final boolean enabled = mKidsModeSettingsObserver.isEnabled();
|
||||
if (mEnabled == enabled) {
|
||||
return;
|
||||
}
|
||||
mEnabled = enabled;
|
||||
if (mEnabled) {
|
||||
enable();
|
||||
} else {
|
||||
disable();
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void enable() {
|
||||
// Needed since many Kids apps aren't optimised to support both orientations and it will be
|
||||
// hard for kids to understand the app compat mode.
|
||||
// TODO(229961548): Remove ignoreOrientationRequest exception for Kids Mode once possible.
|
||||
if (mReverseDefaultRotationEnabled) {
|
||||
setOrientationRequestPolicy(/* isIgnoreOrientationRequestDisabled */ true,
|
||||
/* fromOrientations */
|
||||
new int[]{SCREEN_ORIENTATION_LANDSCAPE, SCREEN_ORIENTATION_REVERSE_LANDSCAPE},
|
||||
/* toOrientations */
|
||||
new int[]{SCREEN_ORIENTATION_SENSOR_LANDSCAPE,
|
||||
SCREEN_ORIENTATION_SENSOR_LANDSCAPE});
|
||||
} else {
|
||||
setOrientationRequestPolicy(/* isIgnoreOrientationRequestDisabled */ true,
|
||||
/* fromOrientations */ null, /* toOrientations */ null);
|
||||
}
|
||||
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() {
|
||||
setOrientationRequestPolicy(/* isIgnoreOrientationRequestDisabled */ false,
|
||||
/* fromOrientations */ null, /* toOrientations */ null);
|
||||
mDisplayInsetsController.removeInsetsChangedListener(DEFAULT_DISPLAY,
|
||||
mOnInsetsChangedListener);
|
||||
mDisplayController.removeDisplayWindowListener(mOnDisplaysChangedListener);
|
||||
updateTask();
|
||||
final WindowContainerToken token = mLaunchRootTask.token;
|
||||
if (token != null) {
|
||||
deleteRootTask(token);
|
||||
}
|
||||
mLaunchRootTask = null;
|
||||
mLaunchRootLeash = null;
|
||||
if (mHomeTask != null && mHomeTask.token != null) {
|
||||
final WindowContainerToken homeToken = mHomeTask.token;
|
||||
final WindowContainerTransaction wct = getWindowContainerTransaction();
|
||||
wct.setBounds(homeToken, null);
|
||||
mSyncQueue.queue(wct);
|
||||
}
|
||||
mHomeTask = 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);
|
||||
if (mEnabled) {
|
||||
final SurfaceControl rootLeash = mLaunchRootLeash;
|
||||
mSyncQueue.runInSync(t -> {
|
||||
t.setPosition(rootLeash, taskBounds.left, taskBounds.top);
|
||||
t.setWindowCrop(rootLeash, mDisplayWidth, mDisplayHeight);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private Rect calculateBounds() {
|
||||
final Rect bounds = new Rect(0, 0, mDisplayWidth, mDisplayHeight);
|
||||
bounds.inset(mInsetsState.calculateInsets(
|
||||
bounds, WindowInsets.Type.navigationBars(), false /* ignoreVisibility */));
|
||||
return bounds;
|
||||
}
|
||||
|
||||
private void updateBounds() {
|
||||
if (mLaunchRootTask == null) {
|
||||
return;
|
||||
}
|
||||
final WindowContainerTransaction wct = getWindowContainerTransaction();
|
||||
final Rect taskBounds = calculateBounds();
|
||||
wct.setBounds(mLaunchRootTask.token, taskBounds);
|
||||
wct.setBounds(mHomeTask.token, new Rect(0, 0, mDisplayWidth, mDisplayHeight));
|
||||
mSyncQueue.queue(wct);
|
||||
final SurfaceControl finalLeash = mLaunchRootLeash;
|
||||
mSyncQueue.runInSync(t -> {
|
||||
t.setPosition(finalLeash, taskBounds.left, taskBounds.top);
|
||||
t.setWindowCrop(finalLeash, mDisplayWidth, mDisplayHeight);
|
||||
});
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
}
|
||||
@@ -1,163 +0,0 @@
|
||||
/*
|
||||
* 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.content.res.Resources;
|
||||
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.wm.shell.ShellTestCase;
|
||||
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.sysui.ShellCommandHandler;
|
||||
import com.android.wm.shell.sysui.ShellInit;
|
||||
|
||||
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 extends ShellTestCase {
|
||||
@Mock private ITaskOrganizerController mTaskOrganizerController;
|
||||
@Mock private Context mContext;
|
||||
@Mock private Handler mHandler;
|
||||
@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 KidsModeSettingsObserver mObserver;
|
||||
@Mock private ShellInit mShellInit;
|
||||
@Mock private ShellCommandHandler mShellCommandHandler;
|
||||
@Mock private DisplayInsetsController mDisplayInsetsController;
|
||||
@Mock private Resources mResources;
|
||||
|
||||
KidsModeTaskOrganizer mOrganizer;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
try {
|
||||
doReturn(ParceledListSlice.<TaskAppearedInfo>emptyList())
|
||||
.when(mTaskOrganizerController).registerTaskOrganizer(any());
|
||||
} catch (RemoteException e) {
|
||||
}
|
||||
// NOTE: KidsModeTaskOrganizer should have a null CompatUIController.
|
||||
doReturn(mResources).when(mContext).getResources();
|
||||
final KidsModeTaskOrganizer kidsModeTaskOrganizer = new KidsModeTaskOrganizer(mContext,
|
||||
mShellInit, mShellCommandHandler, mTaskOrganizerController, mSyncTransactionQueue,
|
||||
mDisplayController, mDisplayInsetsController, Optional.empty(), Optional.empty(),
|
||||
mObserver, mTestExecutor, mHandler);
|
||||
mOrganizer = spy(kidsModeTaskOrganizer);
|
||||
doReturn(mTransaction).when(mOrganizer).getWindowContainerTransaction();
|
||||
doReturn(new InsetsState()).when(mDisplayController).getInsetsState(DEFAULT_DISPLAY);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void instantiateController_addInitCallback() {
|
||||
verify(mShellInit, times(1)).addInitCallback(any(), any());
|
||||
}
|
||||
|
||||
@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));
|
||||
verify(mOrganizer, times(1))
|
||||
.setOrientationRequestPolicy(eq(true), any(), any());
|
||||
|
||||
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);
|
||||
verify(mOrganizer, times(1))
|
||||
.setOrientationRequestPolicy(eq(false), any(), any());
|
||||
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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user