Merge "Introduce TvPipMenuMode" into udc-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
2835276515
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user