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

Commit 3fc369ff authored by Ats Jenk's avatar Ats Jenk
Browse files

Handle desktop mode setting change

Listen to system setting change for desktop mode.
When it changes, update root display area windowing mode to either
freeform or fullscreen.

Bug: 241944030
Test: atest DesktopModeControllerTest ShellTaskOrganizerTests
Change-Id: I7962deab1f287f7cb177f9673e88409b4273276e
parent 518b492a
Loading
Loading
Loading
Loading
+40 −0
Original line number Diff line number Diff line
@@ -16,14 +16,20 @@

package com.android.wm.shell;

import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE;

import android.app.WindowConfiguration;
import android.util.SparseArray;
import android.view.SurfaceControl;
import android.window.DisplayAreaAppearedInfo;
import android.window.DisplayAreaInfo;
import android.window.DisplayAreaOrganizer;
import android.window.WindowContainerTransaction;

import androidx.annotation.NonNull;

import com.android.internal.protolog.common.ProtoLog;

import java.io.PrintWriter;
import java.util.List;
import java.util.concurrent.Executor;
@@ -102,10 +108,44 @@ public class RootDisplayAreaOrganizer extends DisplayAreaOrganizer {
        mDisplayAreasInfo.put(displayId, displayAreaInfo);
    }

    /**
     * Create a {@link WindowContainerTransaction} to update display windowing mode.
     *
     * @param displayId display id to update windowing mode for
     * @param windowingMode target {@link WindowConfiguration.WindowingMode}
     * @return {@link WindowContainerTransaction} with pending operation to set windowing mode
     */
    public WindowContainerTransaction prepareWindowingModeChange(int displayId,
            @WindowConfiguration.WindowingMode int windowingMode) {
        WindowContainerTransaction wct = new WindowContainerTransaction();
        DisplayAreaInfo displayAreaInfo = mDisplayAreasInfo.get(displayId);
        if (displayAreaInfo == null) {
            ProtoLog.e(WM_SHELL_DESKTOP_MODE,
                    "unable to update windowing mode for display %d display not found", displayId);
            return wct;
        }

        ProtoLog.d(WM_SHELL_DESKTOP_MODE,
                "setWindowingMode: displayId=%d current wmMode=%d new wmMode=%d", displayId,
                displayAreaInfo.configuration.windowConfiguration.getWindowingMode(),
                windowingMode);

        wct.setWindowingMode(displayAreaInfo.token, windowingMode);
        return wct;
    }

    public void dump(@NonNull PrintWriter pw, String prefix) {
        final String innerPrefix = prefix + "  ";
        final String childPrefix = innerPrefix + "  ";
        pw.println(prefix + this);

        for (int i = 0; i < mDisplayAreasInfo.size(); i++) {
            int displayId = mDisplayAreasInfo.keyAt(i);
            DisplayAreaInfo displayAreaInfo = mDisplayAreasInfo.get(displayId);
            int windowingMode =
                    displayAreaInfo.configuration.windowConfiguration.getWindowingMode();
            pw.println(innerPrefix + "# displayId=" + displayId + " wmMode=" + windowingMode);
        }
    }

    @Override
+54 −1
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;

import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TASK_ORG;
import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;

@@ -46,6 +47,7 @@ import android.window.StartingWindowInfo;
import android.window.StartingWindowRemovalInfo;
import android.window.TaskAppearedInfo;
import android.window.TaskOrganizer;
import android.window.WindowContainerTransaction;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
@@ -690,6 +692,49 @@ public class ShellTaskOrganizer extends TaskOrganizer implements
        taskListener.reparentChildSurfaceToTask(taskId, sc, t);
    }

    /**
     * Create a {@link WindowContainerTransaction} to clear task bounds.
     *
     * @param displayId display id for tasks that will have bounds cleared
     * @return {@link WindowContainerTransaction} with pending operations to clear bounds
     */
    public WindowContainerTransaction prepareClearBoundsForTasks(int displayId) {
        ProtoLog.d(WM_SHELL_DESKTOP_MODE, "prepareClearBoundsForTasks: displayId=%d", displayId);
        WindowContainerTransaction wct = new WindowContainerTransaction();
        for (int i = 0; i < mTasks.size(); i++) {
            RunningTaskInfo taskInfo = mTasks.valueAt(i).getTaskInfo();
            if (taskInfo.displayId == displayId) {
                ProtoLog.d(WM_SHELL_DESKTOP_MODE, "clearing bounds for token=%s taskInfo=%s",
                        taskInfo.token, taskInfo);
                wct.setBounds(taskInfo.token, null);
            }
        }
        return wct;
    }

    /**
     * Create a {@link WindowContainerTransaction} to clear task level freeform setting.
     *
     * @param displayId display id for tasks that will have windowing mode reset to {@link
     *                  WindowConfiguration#WINDOWING_MODE_UNDEFINED}
     * @return {@link WindowContainerTransaction} with pending operations to clear windowing mode
     */
    public WindowContainerTransaction prepareClearFreeformForTasks(int displayId) {
        ProtoLog.d(WM_SHELL_DESKTOP_MODE, "prepareClearFreeformForTasks: displayId=%d", displayId);
        WindowContainerTransaction wct = new WindowContainerTransaction();
        for (int i = 0; i < mTasks.size(); i++) {
            RunningTaskInfo taskInfo = mTasks.valueAt(i).getTaskInfo();
            if (taskInfo.displayId == displayId
                    && taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
                ProtoLog.d(WM_SHELL_DESKTOP_MODE,
                        "clearing windowing mode for token=%s taskInfo=%s", taskInfo.token,
                        taskInfo);
                wct.setWindowingMode(taskInfo.token, WINDOWING_MODE_UNDEFINED);
            }
        }
        return wct;
    }

    private void logSizeCompatRestartButtonEventReported(@NonNull TaskAppearedInfo info,
            int event) {
        ActivityInfo topActivityInfo = info.getTaskInfo().topActivityInfo;
@@ -816,7 +861,14 @@ public class ShellTaskOrganizer extends TaskOrganizer implements
                final int key = mTasks.keyAt(i);
                final TaskAppearedInfo info = mTasks.valueAt(i);
                final TaskListener listener = getTaskListener(info.getTaskInfo());
                pw.println(innerPrefix + "#" + i + " task=" + key + " listener=" + listener);
                final int windowingMode = info.getTaskInfo().getWindowingMode();
                String pkg = "";
                if (info.getTaskInfo().baseActivity != null) {
                    pkg = info.getTaskInfo().baseActivity.getPackageName();
                }
                Rect bounds = info.getTaskInfo().getConfiguration().windowConfiguration.getBounds();
                pw.println(innerPrefix + "#" + i + " task=" + key + " listener=" + listener
                        + " wmMode=" + windowingMode + " pkg=" + pkg + " bounds=" + bounds);
            }

            pw.println();
@@ -826,6 +878,7 @@ public class ShellTaskOrganizer extends TaskOrganizer implements
                final TaskListener listener = mLaunchCookieToListener.valueAt(i);
                pw.println(innerPrefix + "#" + i + " cookie=" + key + " listener=" + listener);
            }

        }
    }
}
+25 −1
Original line number Diff line number Diff line
@@ -27,6 +27,7 @@ import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.statusbar.IStatusBarService;
import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.RootDisplayAreaOrganizer;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.TaskViewTransitions;
@@ -48,6 +49,7 @@ import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.common.annotations.ShellBackgroundThread;
import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.desktopmode.DesktopModeController;
import com.android.wm.shell.draganddrop.DragAndDropController;
import com.android.wm.shell.freeform.FreeformComponents;
import com.android.wm.shell.freeform.FreeformTaskListener;
@@ -573,6 +575,27 @@ public abstract class WMShellModule {
        );
    }

    //
    // Desktop mode (optional feature)
    //

    @WMSingleton
    @Provides
    static Optional<DesktopModeController> provideDesktopModeController(
            Context context, ShellInit shellInit,
            ShellTaskOrganizer shellTaskOrganizer,
            RootDisplayAreaOrganizer rootDisplayAreaOrganizer,
            @ShellMainThread Handler mainHandler
    ) {
        if (DesktopModeController.IS_FEATURE_ENABLED) {
            return Optional.of(new DesktopModeController(context, shellInit, shellTaskOrganizer,
                    rootDisplayAreaOrganizer,
                    mainHandler));
        } else {
            return Optional.empty();
        }
    }

    //
    // Misc
    //
@@ -583,7 +606,8 @@ public abstract class WMShellModule {
    @ShellCreateTriggerOverride
    @Provides
    static Object provideIndependentShellComponentsToCreate(
            SplitscreenPipMixedHandler splitscreenPipMixedHandler) {
            SplitscreenPipMixedHandler splitscreenPipMixedHandler,
            Optional<DesktopModeController> desktopModeController) {
        return new Object();
    }
}
+141 −0
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.desktopmode;

import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;

import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE;

import android.content.Context;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Handler;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.provider.Settings;
import android.window.WindowContainerTransaction;

import androidx.annotation.Nullable;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.RootDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.sysui.ShellInit;

/**
 * Handles windowing changes when desktop mode system setting changes
 */
public class DesktopModeController {

    public static final boolean IS_FEATURE_ENABLED = SystemProperties.getBoolean(
            "persist.wm.debug.desktop_mode", false);

    private final Context mContext;
    private final ShellTaskOrganizer mShellTaskOrganizer;
    private final RootDisplayAreaOrganizer mRootDisplayAreaOrganizer;
    private final SettingsObserver mSettingsObserver;

    public DesktopModeController(Context context, ShellInit shellInit,
            ShellTaskOrganizer shellTaskOrganizer,
            RootDisplayAreaOrganizer rootDisplayAreaOrganizer,
            @ShellMainThread Handler mainHandler) {
        mContext = context;
        mShellTaskOrganizer = shellTaskOrganizer;
        mRootDisplayAreaOrganizer = rootDisplayAreaOrganizer;
        mSettingsObserver = new SettingsObserver(mContext, mainHandler);
        shellInit.addInitCallback(this::onInit, this);
    }

    private void onInit() {
        ProtoLog.d(WM_SHELL_DESKTOP_MODE, "Initialize DesktopModeController");
        mSettingsObserver.observe();
    }

    @VisibleForTesting
    void updateDesktopModeEnabled(boolean enabled) {
        ProtoLog.d(WM_SHELL_DESKTOP_MODE, "updateDesktopModeState: enabled=%s", enabled);

        int displayId = mContext.getDisplayId();

        WindowContainerTransaction wct = new WindowContainerTransaction();
        // Reset freeform windowing mode that is set per task level (tasks should inherit
        // container value)
        wct.merge(mShellTaskOrganizer.prepareClearFreeformForTasks(displayId), true /* transfer */);
        int targetWindowingMode;
        if (enabled) {
            targetWindowingMode = WINDOWING_MODE_FREEFORM;
        } else {
            targetWindowingMode = WINDOWING_MODE_FULLSCREEN;
            // Clear any resized bounds
            wct.merge(mShellTaskOrganizer.prepareClearBoundsForTasks(displayId),
                    true /* transfer */);
        }
        wct.merge(mRootDisplayAreaOrganizer.prepareWindowingModeChange(displayId,
                targetWindowingMode), true /* transfer */);
        mRootDisplayAreaOrganizer.applyTransaction(wct);
    }

    /**
     * A {@link ContentObserver} for listening to changes to {@link Settings.System#DESKTOP_MODE}
     */
    private final class SettingsObserver extends ContentObserver {

        private final Uri mDesktopModeSetting = Settings.System.getUriFor(
                Settings.System.DESKTOP_MODE);

        private final Context mContext;

        SettingsObserver(Context context, Handler handler) {
            super(handler);
            mContext = context;
        }

        public void observe() {
            // TODO(b/242867463): listen for setting change for all users
            mContext.getContentResolver().registerContentObserver(mDesktopModeSetting,
                    false /* notifyForDescendants */, this /* observer */, UserHandle.USER_CURRENT);
        }

        @Override
        public void onChange(boolean selfChange, @Nullable Uri uri) {
            if (mDesktopModeSetting.equals(uri)) {
                ProtoLog.d(WM_SHELL_DESKTOP_MODE, "Received update for desktop mode setting");
                desktopModeSettingChanged();
            }
        }

        private void desktopModeSettingChanged() {
            boolean enabled = isDesktopModeEnabled();
            updateDesktopModeEnabled(enabled);
        }

        private boolean isDesktopModeEnabled() {
            try {
                int result = Settings.System.getIntForUser(mContext.getContentResolver(),
                        Settings.System.DESKTOP_MODE, UserHandle.USER_CURRENT);
                ProtoLog.d(WM_SHELL_DESKTOP_MODE, "isDesktopModeEnabled=%s", result);
                return result != 0;
            } catch (Settings.SettingNotFoundException e) {
                ProtoLog.e(WM_SHELL_DESKTOP_MODE, "Failed to read DESKTOP_MODE setting %s", e);
                return false;
            }
        }
    }
}
+2 −0
Original line number Diff line number Diff line
@@ -46,6 +46,8 @@ public enum ShellProtoLogGroup implements IProtoLogGroup {
            Consts.TAG_WM_SHELL),
    WM_SHELL_SYSUI_EVENTS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
            Consts.TAG_WM_SHELL),
    WM_SHELL_DESKTOP_MODE(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
            Consts.TAG_WM_SHELL),
    TEST_GROUP(true, true, false, "WindowManagerShellProtoLogTest");

    private final boolean mEnabled;
Loading