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

Commit 20368bf5 authored by Qijing Yao's avatar Qijing Yao
Browse files

Consolidate Desktop Display State Checks

Introduces ShellDesktopState to consolidate logic for determining
display eligibility for desktop mode interactions.

DesktopModeWindowDecorViewModel now correctly consider if the display
has active freeform tasks OR if Home is focused on a desktop-first
display when determining eligibility for window drag, by leveraging the
new checker class.

Bug: b/383069176
Test: manual and atest
Flag: com.android.window.flags.enable_block_non_desktop_display_window_drag_bugfix
Change-Id: I9e994e858cf12cea0704aba2231d9d6352e2b30b
parent c89e6b1c
Loading
Loading
Loading
Loading
+16 −3
Original line number Original line Diff line number Diff line
@@ -120,6 +120,8 @@ import com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler;
import com.android.wm.shell.desktopmode.ExitDesktopTaskTransitionHandler;
import com.android.wm.shell.desktopmode.ExitDesktopTaskTransitionHandler;
import com.android.wm.shell.desktopmode.OverviewToDesktopTransitionObserver;
import com.android.wm.shell.desktopmode.OverviewToDesktopTransitionObserver;
import com.android.wm.shell.desktopmode.ReturnToDragStartAnimator;
import com.android.wm.shell.desktopmode.ReturnToDragStartAnimator;
import com.android.wm.shell.desktopmode.ShellDesktopState;
import com.android.wm.shell.desktopmode.ShellDesktopStateImpl;
import com.android.wm.shell.desktopmode.SpringDragToDesktopTransitionHandler;
import com.android.wm.shell.desktopmode.SpringDragToDesktopTransitionHandler;
import com.android.wm.shell.desktopmode.ToggleResizeDesktopTaskTransitionHandler;
import com.android.wm.shell.desktopmode.ToggleResizeDesktopTaskTransitionHandler;
import com.android.wm.shell.desktopmode.VisualIndicatorUpdateScheduler;
import com.android.wm.shell.desktopmode.VisualIndicatorUpdateScheduler;
@@ -1171,10 +1173,10 @@ public abstract class WMShellModule {
            MultiDisplayDragMoveIndicatorController multiDisplayDragMoveIndicatorController,
            MultiDisplayDragMoveIndicatorController multiDisplayDragMoveIndicatorController,
            Optional<CompatUIHandler> compatUI,
            Optional<CompatUIHandler> compatUI,
            DesksOrganizer desksOrganizer,
            DesksOrganizer desksOrganizer,
            DesktopState desktopState,
            ShellDesktopState shelldesktopState,
            DesktopConfig desktopConfig
            DesktopConfig desktopConfig
    ) {
    ) {
        if (!desktopState.canEnterDesktopModeOrShowAppHandle()) {
        if (!shelldesktopState.canEnterDesktopModeOrShowAppHandle()) {
            return Optional.empty();
            return Optional.empty();
        }
        }
        return Optional.of(new DesktopModeWindowDecorViewModel(context, shellExecutor, mainHandler,
        return Optional.of(new DesktopModeWindowDecorViewModel(context, shellExecutor, mainHandler,
@@ -1191,7 +1193,7 @@ public abstract class WMShellModule {
                desktopModeUiEventLogger, taskResourceLoader, recentsTransitionHandler,
                desktopModeUiEventLogger, taskResourceLoader, recentsTransitionHandler,
                desktopModeCompatPolicy, desktopTilingDecorViewModel,
                desktopModeCompatPolicy, desktopTilingDecorViewModel,
                multiDisplayDragMoveIndicatorController, compatUI.orElse(null),
                multiDisplayDragMoveIndicatorController, compatUI.orElse(null),
                desksOrganizer, desktopState, desktopConfig));
                desksOrganizer, shelldesktopState, desktopConfig));
    }
    }


    @WMSingleton
    @WMSingleton
@@ -1217,6 +1219,17 @@ public abstract class WMShellModule {
        return new MultiDisplayDragMoveIndicatorSurface.Factory();
        return new MultiDisplayDragMoveIndicatorSurface.Factory();
    }
    }


    @WMSingleton
    @Provides
    static ShellDesktopState provideShellDesktopState(
            DesktopState desktopState,
            @DynamicOverride DesktopUserRepositories desktopUserRepositories,
            FocusTransitionObserver focusTransitionObserver,
            ShellController shellController) {
        return new ShellDesktopStateImpl(desktopState, desktopUserRepositories,
                focusTransitionObserver, shellController);
    }

    @WMSingleton
    @WMSingleton
    @Provides
    @Provides
    static AppHandleAndHeaderVisibilityHelper provideAppHandleAndHeaderVisibilityHelper(
    static AppHandleAndHeaderVisibilityHelper provideAppHandleAndHeaderVisibilityHelper(
+27 −0
Original line number Original line Diff line number Diff line
/*
 * Copyright (C) 2025 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 com.android.wm.shell.shared.desktopmode.DesktopState

/** Extends [DesktopState] with methods that integrate with shell-specific components and logic. */
interface ShellDesktopState : DesktopState {
    /**
     * Determines if a display with [displayId] is an eligible drop target for a window in the
     * context of desktop mode.
     */
    fun isEligibleWindowDropTarget(displayId: Int): Boolean
}
+58 −0
Original line number Original line Diff line number Diff line
/*
 * Copyright (C) 2025 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 android.app.WindowConfiguration.ACTIVITY_TYPE_HOME
import com.android.wm.shell.shared.desktopmode.DesktopState
import com.android.wm.shell.sysui.ShellController
import com.android.wm.shell.transition.FocusTransitionObserver

class ShellDesktopStateImpl(
    private val desktopState: DesktopState,
    private val desktopUserRepositories: DesktopUserRepositories,
    private val focusTransitionObserver: FocusTransitionObserver,
    private val shellController: ShellController,
) : ShellDesktopState, DesktopState by desktopState {
    /** Checks if the given display has an active desktop session (i.e., running freeform tasks). */
    private fun isInDesktop(displayId: Int): Boolean =
        desktopUserRepositories
            .getProfile(shellController.currentUserId)
            .getActiveDeskId(displayId) != null

    /** Checks if the currently focused task on the given display is the home screen. */
    private fun isHomeFocused(displayId: Int): Boolean {
        val focusedTask = focusTransitionObserver.getFocusedTaskOnDisplay(displayId)
        if (focusedTask == null) {
            // A null focused task can occur if the Home activity launched before Shell was
            // fully initialized, and this display has not yet received focus. In this case,
            // we assume that Home is the focused activity on the display.
            return true
        }
        return focusedTask.activityType == ACTIVITY_TYPE_HOME
    }

    /**
     * Determines if a display with [displayId] is an eligible drop target for a window in the
     * context of desktop mode.
     *
     * A display is considered an eligible target if either:
     * 1. It already has an active desktop session.
     * 2. It supports desktop mode and the home screen is currently focused.
     */
    override fun isEligibleWindowDropTarget(displayId: Int): Boolean =
        isInDesktop(displayId) ||
            (desktopState.isDesktopModeSupportedOnDisplay(displayId) && isHomeFocused(displayId))
}
+13 −0
Original line number Original line Diff line number Diff line
@@ -28,6 +28,7 @@ import static android.window.TransitionInfo.FLAG_MOVED_TO_TOP;
import static com.android.wm.shell.transition.Transitions.TransitionObserver;
import static com.android.wm.shell.transition.Transitions.TransitionObserver;


import android.annotation.NonNull;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.ActivityManager.RunningTaskInfo;
import android.os.RemoteException;
import android.os.RemoteException;
import android.util.ArraySet;
import android.util.ArraySet;
@@ -250,6 +251,18 @@ public class FocusTransitionObserver {
        return task.displayId == mFocusedDisplayId && isFocusedOnDisplay(task);
        return task.displayId == mFocusedDisplayId && isFocusedOnDisplay(task);
    }
    }


    /**
     * Gets the focused task on a specific display.
     *
     * @param displayId The ID of the display.
     * @return The {@link RunningTaskInfo} of the focused task on the given display,
     *         or {@code null} if no task is focused or display not recorded in the observer.
     */
    @Nullable
    public RunningTaskInfo getFocusedTaskOnDisplay(int displayId) {
        return mFocusedTaskOnDisplay.get(displayId);
    }

    /** Dumps focused display and tasks. */
    /** Dumps focused display and tasks. */
    public void dump(PrintWriter originalWriter, String prefix) {
    public void dump(PrintWriter originalWriter, String prefix) {
        final IndentingPrintWriter writer =
        final IndentingPrintWriter writer =
+18 −21
Original line number Original line Diff line number Diff line
@@ -121,6 +121,7 @@ import com.android.wm.shell.desktopmode.DesktopTasksController;
import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition;
import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition;
import com.android.wm.shell.desktopmode.DesktopTasksLimiter;
import com.android.wm.shell.desktopmode.DesktopTasksLimiter;
import com.android.wm.shell.desktopmode.DesktopUserRepositories;
import com.android.wm.shell.desktopmode.DesktopUserRepositories;
import com.android.wm.shell.desktopmode.ShellDesktopState;
import com.android.wm.shell.desktopmode.WindowDecorCaptionRepository;
import com.android.wm.shell.desktopmode.WindowDecorCaptionRepository;
import com.android.wm.shell.desktopmode.common.ToggleTaskSizeInteraction;
import com.android.wm.shell.desktopmode.common.ToggleTaskSizeInteraction;
import com.android.wm.shell.desktopmode.common.ToggleTaskSizeUtilsKt;
import com.android.wm.shell.desktopmode.common.ToggleTaskSizeUtilsKt;
@@ -215,7 +216,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
    private final AppHeaderViewHolder.Factory mAppHeaderViewHolderFactory;
    private final AppHeaderViewHolder.Factory mAppHeaderViewHolderFactory;
    private final AppHandleViewHolder.Factory mAppHandleViewHolderFactory;
    private final AppHandleViewHolder.Factory mAppHandleViewHolderFactory;
    private final DesksOrganizer mDesksOrganizer;
    private final DesksOrganizer mDesksOrganizer;
    private final DesktopState mDesktopState;
    private final ShellDesktopState mShellDesktopState;
    private final DesktopConfig mDesktopConfig;
    private final DesktopConfig mDesktopConfig;
    private boolean mTransitionDragActive;
    private boolean mTransitionDragActive;


@@ -304,7 +305,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
            MultiDisplayDragMoveIndicatorController multiDisplayDragMoveIndicatorController,
            MultiDisplayDragMoveIndicatorController multiDisplayDragMoveIndicatorController,
            CompatUIHandler compatUI,
            CompatUIHandler compatUI,
            DesksOrganizer desksOrganizer,
            DesksOrganizer desksOrganizer,
            DesktopState desktopState,
            ShellDesktopState shellDesktopState,
            DesktopConfig desktopConfig) {
            DesktopConfig desktopConfig) {
        this(
        this(
                context,
                context,
@@ -355,7 +356,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
                multiDisplayDragMoveIndicatorController,
                multiDisplayDragMoveIndicatorController,
                compatUI,
                compatUI,
                desksOrganizer,
                desksOrganizer,
                desktopState,
                shellDesktopState,
                desktopConfig);
                desktopConfig);
    }
    }


@@ -409,7 +410,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
            MultiDisplayDragMoveIndicatorController multiDisplayDragMoveIndicatorController,
            MultiDisplayDragMoveIndicatorController multiDisplayDragMoveIndicatorController,
            CompatUIHandler compatUI,
            CompatUIHandler compatUI,
            DesksOrganizer desksOrganizer,
            DesksOrganizer desksOrganizer,
            DesktopState desktopState,
            ShellDesktopState shellDesktopState,
            DesktopConfig desktopConfig) {
            DesktopConfig desktopConfig) {
        mContext = context;
        mContext = context;
        mMainExecutor = shellExecutor;
        mMainExecutor = shellExecutor;
@@ -489,7 +490,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
        mMultiDisplayDragMoveIndicatorController = multiDisplayDragMoveIndicatorController;
        mMultiDisplayDragMoveIndicatorController = multiDisplayDragMoveIndicatorController;
        mLatencyTracker = LatencyTracker.getInstance(mContext);
        mLatencyTracker = LatencyTracker.getInstance(mContext);
        mDesksOrganizer = desksOrganizer;
        mDesksOrganizer = desksOrganizer;
        mDesktopState = desktopState;
        mShellDesktopState = shellDesktopState;
        mDesktopConfig = desktopConfig;
        mDesktopConfig = desktopConfig;
        mWindowDecorationActions =
        mWindowDecorationActions =
                new DefaultWindowDecorationActions(this, mDesktopTasksController,
                new DefaultWindowDecorationActions(this, mDesktopTasksController,
@@ -519,7 +520,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
                    new DesktopModeRecentsTransitionStateListener());
                    new DesktopModeRecentsTransitionStateListener());
        }
        }
        mDisplayController.addDisplayChangingController(mOnDisplayChangingListener);
        mDisplayController.addDisplayChangingController(mOnDisplayChangingListener);
        if (mDesktopState.canEnterDesktopModeOrShowAppHandle()
        if (mShellDesktopState.canEnterDesktopModeOrShowAppHandle()
                && Flags.enableDesktopWindowingAppHandleEducation()) {
                && Flags.enableDesktopWindowingAppHandleEducation()) {
            mAppHandleEducationController.setAppHandleEducationTooltipCallbacks(
            mAppHandleEducationController.setAppHandleEducationTooltipCallbacks(
                    /* appHandleTooltipClickCallback= */(taskId) -> {
                    /* appHandleTooltipClickCallback= */(taskId) -> {
@@ -1298,7 +1299,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
        public boolean handleMotionEvent(@Nullable View v, MotionEvent e) {
        public boolean handleMotionEvent(@Nullable View v, MotionEvent e) {
            final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId);
            final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId);
            final RunningTaskInfo taskInfo = decoration.mTaskInfo;
            final RunningTaskInfo taskInfo = decoration.mTaskInfo;
            if (mDesktopState.canEnterDesktopModeOrShowAppHandle()
            if (mShellDesktopState.canEnterDesktopModeOrShowAppHandle()
                    && !taskInfo.isFreeform()) {
                    && !taskInfo.isFreeform()) {
                return handleNonFreeformMotionEvent(decoration, v, e);
                return handleNonFreeformMotionEvent(decoration, v, e);
            } else {
            } else {
@@ -1386,8 +1387,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,


                    if (DesktopExperienceFlags
                    if (DesktopExperienceFlags
                            .ENABLE_BLOCK_NON_DESKTOP_DISPLAY_WINDOW_DRAG_BUGFIX.isTrue()) {
                            .ENABLE_BLOCK_NON_DESKTOP_DISPLAY_WINDOW_DRAG_BUGFIX.isTrue()) {
                        final boolean inDesktopModeDisplay = isDisplayInDesktopMode(
                        final boolean inDesktopModeDisplay = mShellDesktopState
                                e.getDisplayId());
                                .isEligibleWindowDropTarget(e.getDisplayId());
                        // TODO: b/418651425 - Use a more specific pointer icon when available.
                        // TODO: b/418651425 - Use a more specific pointer icon when available.
                        updatePointerIcon(e, dragPointerIdx, v.getViewRootImpl().getInputToken(),
                        updatePointerIcon(e, dragPointerIdx, v.getViewRootImpl().getInputToken(),
                                inDesktopModeDisplay ? PointerIcon.TYPE_ARROW
                                inDesktopModeDisplay ? PointerIcon.TYPE_ARROW
@@ -1439,7 +1440,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
                                PointerIcon.TYPE_ARROW);
                                PointerIcon.TYPE_ARROW);
                        // If the cursor ends on a non-desktop-mode display, revert the window
                        // If the cursor ends on a non-desktop-mode display, revert the window
                        // to its initial bounds prior to the drag starting.
                        // to its initial bounds prior to the drag starting.
                        if (!isDisplayInDesktopMode(e.getDisplayId())) {
                        if (!mShellDesktopState
                                .isEligibleWindowDropTarget(e.getDisplayId())) {
                            newTaskBounds.set(mOnDragStartInitialBounds);
                            newTaskBounds.set(mOnDragStartInitialBounds);
                        }
                        }
                    }
                    }
@@ -1477,11 +1479,6 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
            mCurrentPointerIconType = iconType;
            mCurrentPointerIconType = iconType;
        }
        }


        private boolean isDisplayInDesktopMode(int displayId) {
            return mDesktopState.isDesktopModeSupportedOnDisplay(displayId)
                    && mDesktopTasksController.getActiveDeskId(displayId) != null;
        }

        private void updateDragStatus(DesktopModeWindowDecoration decor, MotionEvent e) {
        private void updateDragStatus(DesktopModeWindowDecoration decor, MotionEvent e) {
            switch (e.getActionMasked()) {
            switch (e.getActionMasked()) {
                case MotionEvent.ACTION_DOWN:
                case MotionEvent.ACTION_DOWN:
@@ -1612,7 +1609,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
     */
     */
    private void handleReceivedMotionEvent(MotionEvent ev, InputMonitor inputMonitor) {
    private void handleReceivedMotionEvent(MotionEvent ev, InputMonitor inputMonitor) {
        final DesktopModeWindowDecoration relevantDecor = getRelevantWindowDecor(ev);
        final DesktopModeWindowDecoration relevantDecor = getRelevantWindowDecor(ev);
        if (mDesktopState.canEnterDesktopMode()) {
        if (mShellDesktopState.canEnterDesktopMode()) {
            if (!mInImmersiveMode && (relevantDecor == null
            if (!mInImmersiveMode && (relevantDecor == null
                    || relevantDecor.mTaskInfo.getWindowingMode() != WINDOWING_MODE_FREEFORM
                    || relevantDecor.mTaskInfo.getWindowingMode() != WINDOWING_MODE_FREEFORM
                    || mTransitionDragActive)) {
                    || mTransitionDragActive)) {
@@ -1622,7 +1619,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
        }
        }
        handleEventOutsideCaption(ev, relevantDecor);
        handleEventOutsideCaption(ev, relevantDecor);
        // Prevent status bar from reacting to a caption drag.
        // Prevent status bar from reacting to a caption drag.
        if (mDesktopState.canEnterDesktopMode()) {
        if (mShellDesktopState.canEnterDesktopMode()) {
            if (mTransitionDragActive) {
            if (mTransitionDragActive) {
                inputMonitor.pilferPointers();
                inputMonitor.pilferPointers();
            }
            }
@@ -1674,7 +1671,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
                        relevantDecor.mTaskInfo.configuration.windowConfiguration.getBounds());
                        relevantDecor.mTaskInfo.configuration.windowConfiguration.getBounds());
                boolean dragFromStatusBarAllowed = false;
                boolean dragFromStatusBarAllowed = false;
                final int windowingMode = relevantDecor.mTaskInfo.getWindowingMode();
                final int windowingMode = relevantDecor.mTaskInfo.getWindowingMode();
                if (mDesktopState.canEnterDesktopMode()
                if (mShellDesktopState.canEnterDesktopMode()
                        || BubbleAnythingFlagHelper.enableBubbleToFullscreen()) {
                        || BubbleAnythingFlagHelper.enableBubbleToFullscreen()) {
                    // In proto2 any full screen or multi-window task can be dragged to
                    // In proto2 any full screen or multi-window task can be dragged to
                    // freeform.
                    // freeform.
@@ -1936,7 +1933,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
                        mDesktopModeEventLogger,
                        mDesktopModeEventLogger,
                        mDesktopModeUiEventLogger,
                        mDesktopModeUiEventLogger,
                        mDesktopModeCompatPolicy,
                        mDesktopModeCompatPolicy,
                        mDesktopState,
                        mShellDesktopState,
                        mDesktopConfig,
                        mDesktopConfig,
                        mWindowDecorationActions);
                        mWindowDecorationActions);
        mWindowDecorByTaskId.put(taskInfo.taskId, windowDecoration);
        mWindowDecorByTaskId.put(taskInfo.taskId, windowDecoration);
@@ -1950,7 +1947,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
                mTransactionFactory,
                mTransactionFactory,
                mMainHandler,
                mMainHandler,
                mMultiDisplayDragMoveIndicatorController,
                mMultiDisplayDragMoveIndicatorController,
                mDesktopState,
                mShellDesktopState,
                mDesktopConfig);
                mDesktopConfig);
        windowDecoration.setTaskDragResizer(taskPositioner);
        windowDecoration.setTaskDragResizer(taskPositioner);


@@ -1987,7 +1984,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
        final String innerPrefix = prefix + "  ";
        final String innerPrefix = prefix + "  ";
        pw.println(prefix + "DesktopModeWindowDecorViewModel");
        pw.println(prefix + "DesktopModeWindowDecorViewModel");
        pw.println(innerPrefix + "DesktopModeStatus="
        pw.println(innerPrefix + "DesktopModeStatus="
                + mDesktopState.canEnterDesktopMode());
                + mShellDesktopState.canEnterDesktopMode());
        pw.println(innerPrefix + "mTransitionDragActive=" + mTransitionDragActive);
        pw.println(innerPrefix + "mTransitionDragActive=" + mTransitionDragActive);
        pw.println(innerPrefix + "mEventReceiversByDisplay=" + mEventReceiversByDisplay);
        pw.println(innerPrefix + "mEventReceiversByDisplay=" + mEventReceiversByDisplay);
        pw.println(innerPrefix + "mGestureExclusionTracker="
        pw.println(innerPrefix + "mGestureExclusionTracker="
Loading