summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Galia Peycheva <galinap@google.com> 2023-03-28 15:39:34 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2023-03-28 15:39:34 +0000
commit2835276515a0b9cc53a6b0435d91f6fb35e4bed5 (patch)
tree9ba86f04bd17f2f50485b7a820d9e88dc4ef90e2
parent16717f7461a3b7417a89c80526abd190742a2bb5 (diff)
parent656d052e0d51561ee3a092d482a0bf0e5736819a (diff)
Merge "Introduce TvPipMenuMode" into udc-dev
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java248
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java148
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipMenuControllerTest.java324
3 files changed, 572 insertions, 148 deletions
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
index 73123b153382..2c6ca1af62a6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
@@ -18,7 +18,7 @@ package com.android.wm.shell.pip.tv;
import static android.view.WindowManager.SHELL_ROOT_LAYER_PIP;
-import android.app.ActivityManager;
+import android.annotation.IntDef;
import android.app.RemoteAction;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -36,6 +36,7 @@ import android.window.SurfaceSyncGroup;
import androidx.annotation.Nullable;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.R;
import com.android.wm.shell.common.SystemWindows;
@@ -61,13 +62,36 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
private SurfaceControl mLeash;
private TvPipMenuView mPipMenuView;
private View mPipBackgroundView;
+ private boolean mMenuIsFocused;
- private boolean mMenuIsOpen;
- // User can actively move the PiP via the DPAD.
- private boolean mInMoveMode;
- // Used when only showing the move menu since we want to close the menu completely when
- // exiting the move menu instead of showing the regular button menu.
- private boolean mCloseAfterExitMoveMenu;
+ @TvPipMenuMode
+ private int mCurrentMenuMode = MODE_NO_MENU;
+ @TvPipMenuMode
+ private int mPrevMenuMode = MODE_NO_MENU;
+
+ @IntDef(prefix = { "MODE_" }, value = {
+ MODE_NO_MENU,
+ MODE_MOVE_MENU,
+ MODE_ALL_ACTIONS_MENU,
+ })
+ public @interface TvPipMenuMode {}
+
+ /**
+ * In this mode the PiP menu is not focused and no user controls are displayed.
+ */
+ static final int MODE_NO_MENU = 0;
+
+ /**
+ * In this mode the PiP menu is focused and the user can use the DPAD controls to move the PiP
+ * to a different position on the screen. We draw arrows in all possible movement directions.
+ */
+ static final int MODE_MOVE_MENU = 1;
+
+ /**
+ * In this mode the PiP menu is focused and we display an array of actions that the user can
+ * select. See {@link TvPipActionsProvider} for the types of available actions.
+ */
+ static final int MODE_ALL_ACTIONS_MENU = 2;
public TvPipMenuController(Context context, TvPipBoundsState tvPipBoundsState,
SystemWindows systemWindows, Handler mainHandler) {
@@ -143,11 +167,16 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
"%s: Actions provider is not set", TAG);
return;
}
- mPipMenuView = new TvPipMenuView(mContext, mMainHandler, this, mTvPipActionsProvider);
+ mPipMenuView = createTvPipMenuView();
setUpViewSurfaceZOrder(mPipMenuView, 1);
addPipMenuViewToSystemWindows(mPipMenuView, MENU_WINDOW_TITLE);
}
+ @VisibleForTesting
+ TvPipMenuView createTvPipMenuView() {
+ return new TvPipMenuView(mContext, mMainHandler, this, mTvPipActionsProvider);
+ }
+
private void attachPipBackgroundView() {
mPipBackgroundView = LayoutInflater.from(mContext)
.inflate(R.layout.tv_pip_menu_background, null);
@@ -188,37 +217,14 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
void showMovementMenu() {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: showMovementMenuOnly()", TAG);
- setInMoveMode(true);
- if (mMenuIsOpen) {
- mPipMenuView.showMoveMenu(mTvPipBoundsState.getTvPipGravity());
- } else {
- mCloseAfterExitMoveMenu = true;
- showMenuInternal();
- }
+ "%s: showMovementMenu()", TAG);
+ switchToMenuMode(MODE_MOVE_MENU);
}
@Override
public void showMenu() {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: showMenu()", TAG);
- setInMoveMode(false);
- mCloseAfterExitMoveMenu = false;
- showMenuInternal();
- }
-
- private void showMenuInternal() {
- if (mPipMenuView == null) {
- return;
- }
-
- mMenuIsOpen = true;
- grantPipMenuFocus(true);
- if (mInMoveMode) {
- mPipMenuView.showMoveMenu(mTvPipBoundsState.getTvPipGravity());
- } else {
- mPipMenuView.showButtonsMenu(/* exitingMoveMode= */ false);
- }
- mPipMenuView.updateBounds(mTvPipBoundsState.getBounds());
+ switchToMenuMode(MODE_ALL_ACTIONS_MENU, true);
}
void onPipTransitionToTargetBoundsStarted(Rect targetBounds) {
@@ -228,9 +234,7 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
}
void updateGravity(int gravity) {
- if (mInMoveMode) {
- mPipMenuView.showMovementHints(gravity);
- }
+ mPipMenuView.setPipGravity(gravity);
}
private Rect calculateMenuSurfaceBounds(Rect pipBounds) {
@@ -240,58 +244,7 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
void closeMenu() {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: closeMenu()", TAG);
-
- if (mPipMenuView == null) {
- return;
- }
-
- mMenuIsOpen = false;
- mPipMenuView.hideAllUserControls();
- grantPipMenuFocus(false);
- mDelegate.onMenuClosed();
- }
-
- boolean isInMoveMode() {
- return mInMoveMode;
- }
-
- private void setInMoveMode(boolean moveMode) {
- if (mInMoveMode == moveMode) {
- return;
- }
- mInMoveMode = moveMode;
- if (mDelegate != null) {
- mDelegate.onInMoveModeChanged();
- }
- }
-
- @Override
- public boolean onExitMoveMode() {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: onExitMoveMode - %b, close when exiting move menu: %b",
- TAG, mInMoveMode, mCloseAfterExitMoveMenu);
-
- if (mInMoveMode) {
- setInMoveMode(false);
- if (mCloseAfterExitMoveMenu) {
- mCloseAfterExitMoveMenu = false;
- closeMenu();
- } else {
- mPipMenuView.showButtonsMenu(/* exitingMoveMode= */ true);
- }
- return true;
- }
- return false;
- }
-
- @Override
- public boolean onPipMovement(int keycode) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: onPipMovement - %b", TAG, mInMoveMode);
- if (mInMoveMode) {
- mDelegate.movePip(keycode);
- }
- return mInMoveMode;
+ switchToMenuMode(MODE_NO_MENU);
}
@Override
@@ -422,13 +375,90 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
getPipMenuLayoutParams(mContext, MENU_WINDOW_TITLE, menuBounds.width(),
menuBounds.height()));
if (mPipMenuView != null) {
- mPipMenuView.updateBounds(pipBounds);
+ mPipMenuView.setPipBounds(pipBounds);
+ }
+ }
+
+ // Start methods handling {@link TvPipMenuMode}
+
+ @VisibleForTesting
+ boolean isMenuOpen() {
+ return mCurrentMenuMode != MODE_NO_MENU;
+ }
+
+ @VisibleForTesting
+ boolean isInMoveMode() {
+ return mCurrentMenuMode == MODE_MOVE_MENU;
+ }
+
+ @VisibleForTesting
+ boolean isInAllActionsMode() {
+ return mCurrentMenuMode == MODE_ALL_ACTIONS_MENU;
+ }
+
+ private void switchToMenuMode(@TvPipMenuMode int menuMode) {
+ switchToMenuMode(menuMode, false);
+ }
+
+ private void switchToMenuMode(@TvPipMenuMode int menuMode, boolean resetMenu) {
+ // Note: we intentionally don't return early here, because the TvPipMenuView needs to
+ // refresh the Ui even if there is no menu mode change.
+ mPrevMenuMode = mCurrentMenuMode;
+ mCurrentMenuMode = menuMode;
+
+ ProtoLog.i(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: switchToMenuMode: setting mCurrentMenuMode=%s, mPrevMenuMode=%s", TAG,
+ getMenuModeString(), getMenuModeString(mPrevMenuMode));
+
+ updateUiOnNewMenuModeRequest(resetMenu);
+ updateDelegateOnNewMenuModeRequest();
+ }
+
+ private void updateUiOnNewMenuModeRequest(boolean resetMenu) {
+ if (mPipMenuView == null) return;
+
+ mPipMenuView.setPipGravity(mTvPipBoundsState.getTvPipGravity());
+ mPipMenuView.transitionToMenuMode(mCurrentMenuMode, resetMenu);
+ grantPipMenuFocus(mCurrentMenuMode != MODE_NO_MENU);
+ }
+
+ private void updateDelegateOnNewMenuModeRequest() {
+ if (mPrevMenuMode == mCurrentMenuMode) return;
+ if (mDelegate == null) return;
+
+ if (mPrevMenuMode == MODE_MOVE_MENU || isInMoveMode()) {
+ mDelegate.onInMoveModeChanged();
+ }
+
+ if (mCurrentMenuMode == MODE_NO_MENU) {
+ 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
- public void onFocusTaskChanged(ActivityManager.RunningTaskInfo taskInfo) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: onFocusTaskChanged", TAG);
+ public void onCloseEduText() {
+ mTvPipBoundsState.setPipMenuTemporaryDecorInsets(Insets.NONE);
+ mDelegate.closeEduText();
}
@Override
@@ -439,9 +469,35 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
}
@Override
- public void onCloseEduText() {
- mTvPipBoundsState.setPipMenuTemporaryDecorInsets(Insets.NONE);
- mDelegate.closeEduText();
+ public boolean onExitMoveMode() {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: onExitMoveMode - mCurrentMenuMode=%s", TAG, getMenuModeString());
+
+ final int saveMenuMode = mCurrentMenuMode;
+ if (isInMoveMode()) {
+ switchToMenuMode(mPrevMenuMode);
+ }
+ return saveMenuMode == MODE_MOVE_MENU;
+ }
+
+ @Override
+ public boolean onPipMovement(int keycode) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: onPipMovement - mCurrentMenuMode=%s", TAG, getMenuModeString());
+ if (isInMoveMode()) {
+ mDelegate.movePip(keycode);
+ }
+ return isInMoveMode();
+ }
+
+ @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 {
@@ -455,6 +511,8 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
}
private void grantPipMenuFocus(boolean grantFocus) {
+ if (mMenuIsFocused == grantFocus) return;
+
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: grantWindowFocus(%b)", TAG, grantFocus);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
index 56c602a1d4f3..ccf65c299613 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
@@ -26,6 +26,9 @@ import static android.view.KeyEvent.KEYCODE_DPAD_UP;
import static android.view.KeyEvent.KEYCODE_ENTER;
import static com.android.wm.shell.pip.tv.TvPipAction.ACTION_MOVE;
+import static com.android.wm.shell.pip.tv.TvPipMenuController.MODE_ALL_ACTIONS_MENU;
+import static com.android.wm.shell.pip.tv.TvPipMenuController.MODE_MOVE_MENU;
+import static com.android.wm.shell.pip.tv.TvPipMenuController.MODE_NO_MENU;
import android.content.Context;
import android.graphics.Rect;
@@ -86,9 +89,9 @@ public class TvPipMenuView extends FrameLayout implements TvPipActionsProvider.L
private final ImageView mArrowLeft;
private final TvWindowMenuActionButton mA11yDoneButton;
- private Rect mCurrentPipBounds;
- private boolean mMoveMenuIsVisible;
- private boolean mButtonMenuIsVisible;
+ private @TvPipMenuController.TvPipMenuMode int mCurrentMenuMode = MODE_NO_MENU;
+ private final Rect mCurrentPipBounds = new Rect();
+ private int mCurrentPipGravity;
private boolean mSwitchingOrientation;
private final AccessibilityManager mA11yManager;
@@ -172,7 +175,7 @@ public class TvPipMenuView extends FrameLayout implements TvPipActionsProvider.L
return;
}
- if (mButtonMenuIsVisible) {
+ if (mCurrentMenuMode == MODE_ALL_ACTIONS_MENU) {
mSwitchingOrientation = true;
mActionButtonsRecyclerView.animate()
.alpha(0)
@@ -217,19 +220,14 @@ public class TvPipMenuView extends FrameLayout implements TvPipActionsProvider.L
/**
* Also updates the button gravity.
*/
- void updateBounds(Rect updatedBounds) {
+ void setPipBounds(Rect updatedPipBounds) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: updateLayout, width: %s, height: %s", TAG, updatedBounds.width(),
- updatedBounds.height());
- mCurrentPipBounds = updatedBounds;
- updatePipFrameBounds();
- }
+ "%s: updateLayout, width: %s, height: %s", TAG, updatedPipBounds.width(),
+ updatedPipBounds.height());
+ if (updatedPipBounds.equals(mCurrentPipBounds)) return;
- Rect getPipMenuContainerBounds(Rect pipBounds) {
- final Rect menuUiBounds = new Rect(pipBounds);
- menuUiBounds.inset(-mPipMenuOuterSpace, -mPipMenuOuterSpace);
- menuUiBounds.bottom += mEduTextDrawer.getHeight();
- return menuUiBounds;
+ mCurrentPipBounds.set(updatedPipBounds);
+ updatePipFrameBounds();
}
/**
@@ -264,12 +262,39 @@ public class TvPipMenuView extends FrameLayout implements TvPipActionsProvider.L
}
}
- /**
- * @param gravity for the arrow hints
- */
- void showMoveMenu(int gravity) {
+ Rect getPipMenuContainerBounds(Rect pipBounds) {
+ final Rect menuUiBounds = new Rect(pipBounds);
+ menuUiBounds.inset(-mPipMenuOuterSpace, -mPipMenuOuterSpace);
+ menuUiBounds.bottom += mEduTextDrawer.getHeight();
+ return menuUiBounds;
+ }
+
+ void transitionToMenuMode(int menuMode, boolean resetMenu) {
+ switch (menuMode) {
+ case MODE_NO_MENU:
+ hideAllUserControls();
+ break;
+ case MODE_MOVE_MENU:
+ showMoveMenu();
+ break;
+ case MODE_ALL_ACTIONS_MENU:
+ showAllActionsMenu(resetMenu);
+ break;
+ default:
+ throw new IllegalArgumentException(
+ "Unknown TV PiP menu mode: "
+ + TvPipMenuController.getMenuModeString(mCurrentMenuMode));
+ }
+
+ mCurrentMenuMode = menuMode;
+ }
+
+ private void showMoveMenu() {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: showMoveMenu()", TAG);
- showMovementHints(gravity);
+
+ if (mCurrentMenuMode == MODE_MOVE_MENU) return;
+
+ showMovementHints();
setMenuButtonsVisible(false);
setFrameHighlighted(true);
@@ -278,32 +303,38 @@ public class TvPipMenuView extends FrameLayout implements TvPipActionsProvider.L
mEduTextDrawer.closeIfNeeded();
}
-
- void showButtonsMenu(boolean exitingMoveMode) {
+ private void showAllActionsMenu(boolean resetMenu) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: showButtonsMenu(), exitingMoveMode %b", TAG, exitingMoveMode);
+ "%s: showAllActionsMenu(), resetMenu %b", TAG, resetMenu);
+
+ if (resetMenu) {
+ scrollToFirstAction();
+ }
+
+ if (mCurrentMenuMode == MODE_ALL_ACTIONS_MENU) return;
+
setMenuButtonsVisible(true);
hideMovementHints();
setFrameHighlighted(true);
animateAlphaTo(1f, mDimLayer);
mEduTextDrawer.closeIfNeeded();
- if (exitingMoveMode) {
- scrollAndRefocusButton(mTvPipActionsProvider.getFirstIndexOfAction(ACTION_MOVE),
- /* alwaysScroll= */ false);
- } else {
- scrollAndRefocusButton(0, /* alwaysScroll= */ true);
+ if (mCurrentMenuMode == MODE_MOVE_MENU) {
+ refocusButton(mTvPipActionsProvider.getFirstIndexOfAction(ACTION_MOVE));
}
- }
- private void scrollAndRefocusButton(int position, boolean alwaysScroll) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: scrollAndRefocusButton, target: %d", TAG, position);
+ }
- if (alwaysScroll || !refocusButton(position)) {
- mButtonLayoutManager.scrollToPositionWithOffset(position, 0);
- mActionButtonsRecyclerView.post(() -> refocusButton(position));
+ private void scrollToFirstAction() {
+ // Clearing the focus here is necessary to allow a smooth scroll even if the first action
+ // is currently not visible.
+ final View focusedChild = mActionButtonsRecyclerView.getFocusedChild();
+ if (focusedChild != null) {
+ focusedChild.clearFocus();
}
+
+ mButtonLayoutManager.scrollToPosition(0);
+ mActionButtonsRecyclerView.post(() -> refocusButton(0));
}
/**
@@ -311,6 +342,9 @@ public class TvPipMenuView extends FrameLayout implements TvPipActionsProvider.L
* the view for the position not being available (scrolling beforehand will be necessary).
*/
private boolean refocusButton(int position) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: refocusButton, position: %d", TAG, position);
+
View itemToFocus = mButtonLayoutManager.findViewByPosition(position);
if (itemToFocus != null) {
itemToFocus.requestFocus();
@@ -319,21 +353,29 @@ public class TvPipMenuView extends FrameLayout implements TvPipActionsProvider.L
return itemToFocus != null;
}
- void hideAllUserControls() {
+ private void hideAllUserControls() {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: hideAllUserControls()", TAG);
+
+ if (mCurrentMenuMode == MODE_NO_MENU) return;
+
setMenuButtonsVisible(false);
hideMovementHints();
setFrameHighlighted(false);
animateAlphaTo(0f, mDimLayer);
}
+ void setPipGravity(int gravity) {
+ mCurrentPipGravity = gravity;
+ if (mCurrentMenuMode == MODE_MOVE_MENU) {
+ showMovementHints();
+ }
+ }
+
@Override
public void onWindowFocusChanged(boolean hasWindowFocus) {
super.onWindowFocusChanged(hasWindowFocus);
- if (!hasWindowFocus) {
- hideAllUserControls();
- }
+ mListener.onPipWindowFocusChanged(hasWindowFocus);
}
private void animateAlphaTo(float alpha, View view) {
@@ -399,15 +441,13 @@ public class TvPipMenuView extends FrameLayout implements TvPipActionsProvider.L
/**
* Shows user hints for moving the PiP, e.g. arrows.
*/
- public void showMovementHints(int gravity) {
+ public void showMovementHints() {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: showMovementHints(), position: %s", TAG, Gravity.toString(gravity));
- mMoveMenuIsVisible = true;
-
- animateAlphaTo(checkGravity(gravity, Gravity.BOTTOM) ? 1f : 0f, mArrowUp);
- animateAlphaTo(checkGravity(gravity, Gravity.TOP) ? 1f : 0f, mArrowDown);
- animateAlphaTo(checkGravity(gravity, Gravity.RIGHT) ? 1f : 0f, mArrowLeft);
- animateAlphaTo(checkGravity(gravity, Gravity.LEFT) ? 1f : 0f, mArrowRight);
+ "%s: showMovementHints(), position: %s", TAG, Gravity.toString(mCurrentPipGravity));
+ animateAlphaTo(checkGravity(mCurrentPipGravity, Gravity.BOTTOM) ? 1f : 0f, mArrowUp);
+ animateAlphaTo(checkGravity(mCurrentPipGravity, Gravity.TOP) ? 1f : 0f, mArrowDown);
+ animateAlphaTo(checkGravity(mCurrentPipGravity, Gravity.RIGHT) ? 1f : 0f, mArrowLeft);
+ animateAlphaTo(checkGravity(mCurrentPipGravity, Gravity.LEFT) ? 1f : 0f, mArrowRight);
boolean a11yEnabled = mA11yManager.isEnabled();
setArrowA11yEnabled(mArrowUp, a11yEnabled, KEYCODE_DPAD_UP);
@@ -446,10 +486,7 @@ public class TvPipMenuView extends FrameLayout implements TvPipActionsProvider.L
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: hideMovementHints()", TAG);
- if (!mMoveMenuIsVisible) {
- return;
- }
- mMoveMenuIsVisible = false;
+ if (mCurrentMenuMode != MODE_MOVE_MENU) return;
animateAlphaTo(0, mArrowUp);
animateAlphaTo(0, mArrowRight);
@@ -464,7 +501,6 @@ public class TvPipMenuView extends FrameLayout implements TvPipActionsProvider.L
private void setMenuButtonsVisible(boolean visible) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: showUserActions: %b", TAG, visible);
- mButtonMenuIsVisible = visible;
animateAlphaTo(visible ? 1 : 0, mActionButtonsRecyclerView);
}
@@ -534,5 +570,11 @@ public class TvPipMenuView extends FrameLayout implements TvPipActionsProvider.L
* @return whether pip movement was handled.
*/
boolean 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);
}
-} \ No newline at end of file
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipMenuControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipMenuControllerTest.java
new file mode 100644
index 000000000000..7c6037c96360
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipMenuControllerTest.java
@@ -0,0 +1,324 @@
+/*
+ * Copyright (C) 2023 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.pip.tv;
+
+import static android.view.KeyEvent.KEYCODE_DPAD_UP;
+
+import static com.android.wm.shell.pip.tv.TvPipMenuController.MODE_ALL_ACTIONS_MENU;
+import static com.android.wm.shell.pip.tv.TvPipMenuController.MODE_MOVE_MENU;
+import static com.android.wm.shell.pip.tv.TvPipMenuController.MODE_NO_MENU;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.os.Handler;
+import android.view.SurfaceControl;
+
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.common.SystemWindows;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+public class TvPipMenuControllerTest extends ShellTestCase {
+ private static final int TEST_MOVE_KEYCODE = KEYCODE_DPAD_UP;
+
+ @Mock
+ private TvPipMenuController.Delegate mMockDelegate;
+ @Mock
+ private TvPipBoundsState mMockTvPipBoundsState;
+ @Mock
+ private SystemWindows mMockSystemWindows;
+ @Mock
+ private SurfaceControl mMockPipLeash;
+ @Mock
+ private Handler mMockHandler;
+ @Mock
+ private TvPipActionsProvider mMockActionsProvider;
+ @Mock
+ private TvPipMenuView mMockTvPipMenuView;
+
+ private TvPipMenuController mTvPipMenuController;
+
+ @Before
+ public void setUp() {
+ assumeTrue(isTelevision());
+
+ MockitoAnnotations.initMocks(this);
+
+ mTvPipMenuController = new TestTvPipMenuController();
+ mTvPipMenuController.setDelegate(mMockDelegate);
+ mTvPipMenuController.setTvPipActionsProvider(mMockActionsProvider);
+ mTvPipMenuController.attach(mMockPipLeash);
+ }
+
+ @Test
+ public void testMenuNotOpenByDefault() {
+ assertMenuIsOpen(false);
+ }
+
+ @Test
+ public void testSwitch_FromNoMenuMode_ToMoveMode() {
+ showAndAssertMoveMenu();
+ }
+
+ @Test
+ public void testSwitch_FromNoMenuMode_ToAllActionsMode() {
+ showAndAssertAllActionsMenu();
+ }
+
+ @Test
+ public void testSwitch_FromMoveMode_ToAllActionsMode() {
+ showAndAssertMoveMenu();
+ showAndAssertAllActionsMenu();
+ }
+
+ @Test
+ public void testSwitch_FromAllActionsMode_ToMoveMode() {
+ showAndAssertAllActionsMenu();
+ showAndAssertMoveMenu();
+ }
+
+ @Test
+ public void testCloseMenu_NoMenuMode() {
+ mTvPipMenuController.closeMenu();
+ assertMenuIsOpen(false);
+ verify(mMockDelegate, never()).onMenuClosed();
+ }
+
+ @Test
+ public void testCloseMenu_MoveMode() {
+ showAndAssertMoveMenu();
+
+ closeMenuAndAssertMenuClosed();
+ verify(mMockDelegate, times(2)).onInMoveModeChanged();
+ }
+
+ @Test
+ public void testCloseMenu_AllActionsMode() {
+ showAndAssertAllActionsMenu();
+
+ closeMenuAndAssertMenuClosed();
+ }
+
+ @Test
+ public void testCloseMenu_MoveModeFollowedByAllActionsMode() {
+ showAndAssertMoveMenu();
+ showAndAssertAllActionsMenu();
+ verify(mMockDelegate, times(2)).onInMoveModeChanged();
+
+ closeMenuAndAssertMenuClosed();
+ }
+
+ @Test
+ public void testCloseMenu_AllActionsModeFollowedByMoveMode() {
+ showAndAssertAllActionsMenu();
+ showAndAssertMoveMenu();
+
+ closeMenuAndAssertMenuClosed();
+ verify(mMockDelegate, times(2)).onInMoveModeChanged();
+ }
+
+ @Test
+ public void testExitMoveMode_NoMenuMode() {
+ mTvPipMenuController.onExitMoveMode();
+ assertMenuIsOpen(false);
+ verify(mMockDelegate, never()).onMenuClosed();
+ }
+
+ @Test
+ public void testExitMoveMode_MoveMode() {
+ showAndAssertMoveMenu();
+
+ mTvPipMenuController.onExitMoveMode();
+ assertMenuClosed();
+ verify(mMockDelegate, times(2)).onInMoveModeChanged();
+ }
+
+ @Test
+ public void testExitMoveMode_AllActionsMode() {
+ showAndAssertAllActionsMenu();
+
+ mTvPipMenuController.onExitMoveMode();
+ assertMenuIsInAllActionsMode();
+
+ }
+
+ @Test
+ public void testExitMoveMode_AllActionsModeFollowedByMoveMode() {
+ showAndAssertAllActionsMenu();
+ showAndAssertMoveMenu();
+
+ mTvPipMenuController.onExitMoveMode();
+ assertMenuIsInAllActionsMode();
+ verify(mMockDelegate, times(2)).onInMoveModeChanged();
+ verify(mMockTvPipMenuView).transitionToMenuMode(eq(MODE_ALL_ACTIONS_MENU), eq(false));
+ }
+
+ @Test
+ public void testOnBackPress_NoMenuMode() {
+ mTvPipMenuController.onBackPress();
+ assertMenuIsOpen(false);
+ verify(mMockDelegate, never()).onMenuClosed();
+ }
+
+ @Test
+ public void testOnBackPress_MoveMode() {
+ showAndAssertMoveMenu();
+
+ pressBackAndAssertMenuClosed();
+ verify(mMockDelegate, times(2)).onInMoveModeChanged();
+ }
+
+ @Test
+ public void testOnBackPress_AllActionsMode() {
+ showAndAssertAllActionsMenu();
+
+ pressBackAndAssertMenuClosed();
+ }
+
+ @Test
+ public void testOnBackPress_MoveModeFollowedByAllActionsMode() {
+ showAndAssertMoveMenu();
+ showAndAssertAllActionsMenu();
+ verify(mMockDelegate, times(2)).onInMoveModeChanged();
+
+ pressBackAndAssertMenuClosed();
+ }
+
+ @Test
+ public void testOnBackPress_AllActionsModeFollowedByMoveMode() {
+ showAndAssertAllActionsMenu();
+ showAndAssertMoveMenu();
+
+ mTvPipMenuController.onBackPress();
+ assertMenuIsInAllActionsMode();
+ verify(mMockDelegate, times(2)).onInMoveModeChanged();
+ verify(mMockTvPipMenuView).transitionToMenuMode(eq(MODE_ALL_ACTIONS_MENU), eq(false));
+
+ pressBackAndAssertMenuClosed();
+ }
+
+ @Test
+ public void testOnPipMovement_NoMenuMode() {
+ assertPipMoveSuccessful(false, mTvPipMenuController.onPipMovement(TEST_MOVE_KEYCODE));
+ }
+
+ @Test
+ public void testOnPipMovement_MoveMode() {
+ showAndAssertMoveMenu();
+ assertPipMoveSuccessful(true, mTvPipMenuController.onPipMovement(TEST_MOVE_KEYCODE));
+ verify(mMockDelegate).movePip(eq(TEST_MOVE_KEYCODE));
+ }
+
+ @Test
+ public void testOnPipMovement_AllActionsMode() {
+ showAndAssertAllActionsMenu();
+ assertPipMoveSuccessful(false, mTvPipMenuController.onPipMovement(TEST_MOVE_KEYCODE));
+ }
+
+ @Test
+ public void testOnPipWindowFocusChanged_NoMenuMode() {
+ mTvPipMenuController.onPipWindowFocusChanged(false);
+ assertMenuIsOpen(false);
+ }
+
+ @Test
+ public void testOnPipWindowFocusChanged_MoveMode() {
+ showAndAssertMoveMenu();
+ mTvPipMenuController.onPipWindowFocusChanged(false);
+ assertMenuClosed();
+ }
+
+ @Test
+ public void testOnPipWindowFocusChanged_AllActionsMode() {
+ showAndAssertAllActionsMenu();
+ mTvPipMenuController.onPipWindowFocusChanged(false);
+ assertMenuClosed();
+ }
+
+ private void showAndAssertMoveMenu() {
+ mTvPipMenuController.showMovementMenu();
+ assertMenuIsInMoveMode();
+ verify(mMockDelegate).onInMoveModeChanged();
+ verify(mMockTvPipMenuView).transitionToMenuMode(eq(MODE_MOVE_MENU), eq(false));
+ }
+
+ private void showAndAssertAllActionsMenu() {
+ mTvPipMenuController.showMenu();
+ assertMenuIsInAllActionsMode();
+ verify(mMockTvPipMenuView).transitionToMenuMode(eq(MODE_ALL_ACTIONS_MENU), eq(true));
+ }
+
+ private void closeMenuAndAssertMenuClosed() {
+ mTvPipMenuController.closeMenu();
+ assertMenuClosed();
+ }
+
+ private void pressBackAndAssertMenuClosed() {
+ mTvPipMenuController.onBackPress();
+ assertMenuClosed();
+ }
+
+ private void assertMenuClosed() {
+ assertMenuIsOpen(false);
+ verify(mMockDelegate).onMenuClosed();
+ verify(mMockTvPipMenuView).transitionToMenuMode(eq(MODE_NO_MENU), eq(false));
+ }
+
+ private void assertMenuIsOpen(boolean open) {
+ assertTrue("The TV PiP menu should " + (open ? "" : "not ") + "be open, but it"
+ + " is in mode " + mTvPipMenuController.getMenuModeString(),
+ mTvPipMenuController.isMenuOpen() == open);
+ }
+
+ private void assertMenuIsInMoveMode() {
+ assertTrue("Expected MODE_MOVE_MENU, but got " + mTvPipMenuController.getMenuModeString(),
+ mTvPipMenuController.isInMoveMode());
+ assertMenuIsOpen(true);
+ }
+
+ private void assertMenuIsInAllActionsMode() {
+ assertTrue("Expected MODE_ALL_ACTIONS_MENU, but got "
+ + mTvPipMenuController.getMenuModeString(),
+ mTvPipMenuController.isInAllActionsMode());
+ assertMenuIsOpen(true);
+ }
+
+ private void assertPipMoveSuccessful(boolean expected, boolean actual) {
+ assertTrue("Should " + (expected ? "" : "not ") + "move PiP when the menu is in mode "
+ + mTvPipMenuController.getMenuModeString(), expected == actual);
+ }
+
+ private class TestTvPipMenuController extends TvPipMenuController {
+
+ TestTvPipMenuController() {
+ super(mContext, mMockTvPipBoundsState, mMockSystemWindows, mMockHandler);
+ }
+
+ @Override
+ TvPipMenuView createTvPipMenuView() {
+ return mMockTvPipMenuView;
+ }
+ }
+}