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

Commit 178263c8 authored by Galia Peycheva's avatar Galia Peycheva
Browse files

Make tv menu mode switch wait for focus change

The TvPipMenuController switches between menu modes and requests focus
changes from the WM. However, the focus change is not directly tied to
the switch of the menu modes. Meaning that if something goes wrong in
the focus changing, we would end up in a state where e.g. the pip menu
is open, but the window doesn't have any focus, which looks broken.

This CL makes the mode switching happen after any needed focus changes
are complete.

Bug: 277045365
Test: atest TvPipMenuControllerTest

Change-Id: I1e728e78dd56eba172745bf5dc4b4efcee22de84
parent 4b187b2b
Loading
Loading
Loading
Loading
+95 −68
Original line number Diff line number Diff line
@@ -62,13 +62,16 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
    private SurfaceControl mLeash;
    private TvPipMenuView mPipMenuView;
    private TvPipBackgroundView mPipBackgroundView;
    private boolean mMenuIsFocused;

    @TvPipMenuMode
    private int mCurrentMenuMode = MODE_NO_MENU;
    @TvPipMenuMode
    private int mPrevMenuMode = MODE_NO_MENU;

    /** When the window gains focus, enter this menu mode */
    @TvPipMenuMode
    private int mMenuModeOnFocus = MODE_ALL_ACTIONS_MENU;

    @IntDef(prefix = { "MODE_" }, value = {
        MODE_NO_MENU,
        MODE_MOVE_MENU,
@@ -170,6 +173,9 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
        mPipMenuView = createTvPipMenuView();
        setUpViewSurfaceZOrder(mPipMenuView, 1);
        addPipMenuViewToSystemWindows(mPipMenuView, MENU_WINDOW_TITLE);
        mPipMenuView.getViewTreeObserver().addOnWindowFocusChangeListener(hasFocus -> {
            onPipWindowFocusChanged(hasFocus);
        });
    }

    @VisibleForTesting
@@ -224,13 +230,14 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
    void showMovementMenu() {
        ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
                "%s: showMovementMenu()", TAG);
        switchToMenuMode(MODE_MOVE_MENU);
        requestMenuMode(MODE_MOVE_MENU);
    }

    @Override
    public void showMenu() {
        ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: showMenu()", TAG);
        switchToMenuMode(MODE_ALL_ACTIONS_MENU, true);
        mPipMenuView.resetMenu();
        requestMenuMode(MODE_ALL_ACTIONS_MENU);
    }

    void onPipTransitionToTargetBoundsStarted(Rect targetBounds) {
@@ -250,7 +257,7 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
    void closeMenu() {
        ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
                "%s: closeMenu()", TAG);
        switchToMenuMode(MODE_NO_MENU);
        requestMenuMode(MODE_NO_MENU);
    }

    @Override
@@ -392,11 +399,15 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
        }
    }

    // Start methods handling {@link TvPipMenuMode}
    // Beginning of convenience methods for {@link TvPipMenuMode}

    @VisibleForTesting
    boolean isMenuOpen() {
        return mCurrentMenuMode != MODE_NO_MENU;
        return isMenuOpen(mCurrentMenuMode);
    }

    private static boolean isMenuOpen(@TvPipMenuMode int menuMode) {
        return menuMode != MODE_NO_MENU;
    }

    @VisibleForTesting
@@ -409,34 +420,93 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
        return mCurrentMenuMode == MODE_ALL_ACTIONS_MENU;
    }

    private void switchToMenuMode(@TvPipMenuMode int menuMode) {
        switchToMenuMode(menuMode, false);
    @VisibleForTesting
    String getMenuModeString() {
        return getMenuModeString(mCurrentMenuMode);
    }

    static String getMenuModeString(@TvPipMenuMode int menuMode) {
        switch(menuMode) {
            case MODE_NO_MENU:
                return "MODE_NO_MENU";
            case MODE_MOVE_MENU:
                return "MODE_MOVE_MENU";
            case MODE_ALL_ACTIONS_MENU:
                return "MODE_ALL_ACTIONS_MENU";
            default:
                return "Unknown";
        }
    }

    // Beginning of methods handling switching between menu modes

    private void requestMenuMode(@TvPipMenuMode int menuMode) {
        if (isMenuOpen() == isMenuOpen(menuMode)) {
            // No need to request a focus change. We can directly switch to the new mode.
            switchToMenuMode(menuMode);
        } else {
            if (isMenuOpen(menuMode)) {
                mMenuModeOnFocus = menuMode;
            }

            // Send a request to gain window focus if the menu is open, or lose window focus
            // otherwise. Once the focus change happens, we will request the new mode in the
            // callback {@link #onPipWindowFocusChanged}.
            requestPipMenuFocus(isMenuOpen(menuMode));
        }
        // Note: we don't handle cases where there is a focus change currently in flight, because
        // this is very unlikely to happen in practice and would complicate the logic.
    }

    private void requestPipMenuFocus(boolean focus) {
        ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
                "%s: requestPipMenuFocus(%b)", TAG, focus);

        try {
            WindowManagerGlobal.getWindowSession().grantEmbeddedWindowFocus(null /* window */,
                    mSystemWindows.getFocusGrantToken(mPipMenuView), focus);
        } catch (Exception e) {
            ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
                    "%s: Unable to update focus, %s", TAG, e);
        }
    }

    /**
     * Called when the menu window gains or loses focus.
     */
    @VisibleForTesting
    void onPipWindowFocusChanged(boolean focused) {
        ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
                "%s: onPipWindowFocusChanged - focused=%b", TAG, focused);
        switchToMenuMode(focused ? mMenuModeOnFocus : MODE_NO_MENU);

        // Reset the default menu mode for focused state.
        mMenuModeOnFocus = MODE_ALL_ACTIONS_MENU;
    }

    private void switchToMenuMode(@TvPipMenuMode int menuMode, boolean resetMenu) {
        ProtoLog.i(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
    /**
     * Immediately switches to the menu mode in the given request. Updates the mDelegate and the UI.
     * Doesn't handle any focus changes.
     */
    private void switchToMenuMode(@TvPipMenuMode int menuMode) {
        ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
                "%s: switchToMenuMode: from=%s, to=%s", TAG, getMenuModeString(),
                getMenuModeString(menuMode));

        if (mCurrentMenuMode != menuMode) {
        if (mCurrentMenuMode == menuMode) return;

        mPrevMenuMode = mCurrentMenuMode;
        mCurrentMenuMode = menuMode;
            updateUiOnNewMenuModeRequest(resetMenu);
        updateUiOnNewMenuModeRequest();
        updateDelegateOnNewMenuModeRequest();
        } else if (resetMenu) {
            // Note: we intentionally update the Ui even if the menu mode hasn't changed, because
            // the Ui may have to be updated when resetting the menu.
            updateUiOnNewMenuModeRequest(resetMenu);
        }
    }

    private void updateUiOnNewMenuModeRequest(boolean resetMenu) {
    private void updateUiOnNewMenuModeRequest() {
        if (mPipMenuView == null || mPipBackgroundView == null) return;

        mPipMenuView.setPipGravity(mTvPipBoundsState.getTvPipGravity());
        mPipMenuView.transitionToMenuMode(mCurrentMenuMode, resetMenu);
        mPipMenuView.transitionToMenuMode(mCurrentMenuMode);
        mPipBackgroundView.transitionToMenuMode(mCurrentMenuMode);
        grantPipMenuFocus(mCurrentMenuMode != MODE_NO_MENU);
    }

    private void updateDelegateOnNewMenuModeRequest() {
@@ -447,29 +517,11 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
            mDelegate.onInMoveModeChanged();
        }

        if (mCurrentMenuMode == MODE_NO_MENU) {
        if (!isMenuOpen()) {
            mDelegate.onMenuClosed();
        }
    }

    @VisibleForTesting
    String getMenuModeString() {
        return getMenuModeString(mCurrentMenuMode);
    }

    static String getMenuModeString(@TvPipMenuMode int menuMode) {
        switch(menuMode) {
            case MODE_NO_MENU:
                return "MODE_NO_MENU";
            case MODE_MOVE_MENU:
                return "MODE_MOVE_MENU";
            case MODE_ALL_ACTIONS_MENU:
                return "MODE_ALL_ACTIONS_MENU";
            default:
                return "Unknown";
        }
    }

    // Start {@link TvPipMenuView.Delegate} methods

    @Override
@@ -482,7 +534,7 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
    public void onExitCurrentMenuMode() {
        ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
                "%s: onExitCurrentMenuMode - mCurrentMenuMode=%s", TAG, getMenuModeString());
        switchToMenuMode(isInMoveMode() ? mPrevMenuMode : MODE_NO_MENU);
        requestMenuMode(isInMoveMode() ? mPrevMenuMode : MODE_NO_MENU);
    }

    @Override
@@ -494,16 +546,6 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
        }
    }

    @Override
    public void onPipWindowFocusChanged(boolean focused) {
        ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
                "%s: onPipWindowFocusChanged - focused=%b", TAG, focused);
        mMenuIsFocused = focused;
        if (!focused && isMenuOpen()) {
            closeMenu();
        }
    }

    interface Delegate {
        void movePip(int keycode);

@@ -514,21 +556,6 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
        void closeEduText();
    }

    private void grantPipMenuFocus(boolean grantFocus) {
        if (mMenuIsFocused == grantFocus) return;

        ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
                "%s: grantWindowFocus(%b)", TAG, grantFocus);

        try {
            WindowManagerGlobal.getWindowSession().grantEmbeddedWindowFocus(null /* window */,
                    mSystemWindows.getFocusGrantToken(mPipMenuView), grantFocus);
        } catch (Exception e) {
            ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
                    "%s: Unable to update focus, %s", TAG, e);
        }
    }

    private class PipMenuSurfaceChangedCallback implements ViewRootImpl.SurfaceChangedCallback {
        private final View mView;
        private final int mZOrder;
+9 −22
Original line number Diff line number Diff line
@@ -328,7 +328,7 @@ public class TvPipMenuView extends FrameLayout implements TvPipActionsProvider.L
        return menuUiBounds;
    }

    void transitionToMenuMode(int menuMode, boolean resetMenu) {
    void transitionToMenuMode(int menuMode) {
        switch (menuMode) {
            case MODE_NO_MENU:
                hideAllUserControls();
@@ -337,7 +337,7 @@ public class TvPipMenuView extends FrameLayout implements TvPipActionsProvider.L
                showMoveMenu();
                break;
            case MODE_ALL_ACTIONS_MENU:
                showAllActionsMenu(resetMenu);
                showAllActionsMenu();
                break;
            default:
                throw new IllegalArgumentException(
@@ -362,14 +362,14 @@ public class TvPipMenuView extends FrameLayout implements TvPipActionsProvider.L
        mEduTextDrawer.closeIfNeeded();
    }

    private void showAllActionsMenu(boolean resetMenu) {
        ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
                "%s: showAllActionsMenu(), resetMenu %b", TAG, resetMenu);

        if (resetMenu) {
    void resetMenu() {
        scrollToFirstAction();
    }

    private void showAllActionsMenu() {
        ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
                "%s: showAllActionsMenu()", TAG);

        if (mCurrentMenuMode == MODE_ALL_ACTIONS_MENU) return;

        setMenuButtonsVisible(true);
@@ -378,7 +378,7 @@ public class TvPipMenuView extends FrameLayout implements TvPipActionsProvider.L
        animateAlphaTo(1f, mDimLayer);
        mEduTextDrawer.closeIfNeeded();

        if (mCurrentMenuMode == MODE_MOVE_MENU && !resetMenu) {
        if (mCurrentMenuMode == MODE_MOVE_MENU) {
            refocusButton(mTvPipActionsProvider.getFirstIndexOfAction(ACTION_MOVE));
        }

@@ -431,12 +431,6 @@ public class TvPipMenuView extends FrameLayout implements TvPipActionsProvider.L
        }
    }

    @Override
    public void onWindowFocusChanged(boolean hasWindowFocus) {
        super.onWindowFocusChanged(hasWindowFocus);
        mListener.onPipWindowFocusChanged(hasWindowFocus);
    }

    private void animateAlphaTo(float alpha, View view) {
        if (view.getAlpha() == alpha) {
            return;
@@ -628,7 +622,6 @@ public class TvPipMenuView extends FrameLayout implements TvPipActionsProvider.L

        /**
         * Called when a button for exiting the current menu mode was pressed.
         *
         */
        void onExitCurrentMenuMode();

@@ -637,12 +630,6 @@ public class TvPipMenuView extends FrameLayout implements TvPipActionsProvider.L
         */
        void onPipMovement(int keycode);

        /**
         * Called when the TvPipMenuView loses focus. This also means that the TV PiP menu window
         * has lost focus.
         */
        void onPipWindowFocusChanged(boolean focused);

        /**
         *  The edu text closing impacts the size of the Picture-in-Picture window and influences
         *  how it is positioned on the screen.
+167 −68

File changed.

Preview size limit exceeded, changes collapsed.