Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit ea896c4c authored by Tiger's avatar Tiger
Browse files

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
parent 150c1a9e
Loading
Loading
Loading
Loading
+0 −39
Original line number Diff line number Diff line
@@ -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();
    }
}
+0 −92
Original line number Diff line number Diff line
/*
 * 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;
    }
}
+0 −436
Original line number Diff line number Diff line
/*
 * 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);
    }
}
+0 −163

File deleted.

Preview size limit exceeded, changes collapsed.