diff --git a/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml b/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml
index 70755e6cc3cfc..dcce4698c2524 100644
--- a/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml
+++ b/libs/WindowManager/Shell/res/layout/tv_pip_menu.xml
@@ -44,67 +44,15 @@
android:background="@color/tv_pip_menu_dim_layer"
android:alpha="0"/>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
diff --git a/libs/WindowManager/Shell/res/layout/tv_window_menu_action_button.xml b/libs/WindowManager/Shell/res/layout/tv_window_menu_action_button.xml
index c4dbd39c729a6..b2ac85b018be9 100644
--- a/libs/WindowManager/Shell/res/layout/tv_window_menu_action_button.xml
+++ b/libs/WindowManager/Shell/res/layout/tv_window_menu_action_button.xml
@@ -21,6 +21,7 @@
android:layout_width="@dimen/tv_window_menu_button_size"
android:layout_height="@dimen/tv_window_menu_button_size"
android:padding="@dimen/tv_window_menu_button_margin"
+ android:duplicateParentState="true"
android:stateListAnimator="@animator/tv_window_menu_action_button_animator"
android:focusable="true">
diff --git a/libs/WindowManager/Shell/res/values-tvdpi/dimen.xml b/libs/WindowManager/Shell/res/values-tvdpi/dimen.xml
index 9833a88a1c0a2..0b61d7a85d9ee 100644
--- a/libs/WindowManager/Shell/res/values-tvdpi/dimen.xml
+++ b/libs/WindowManager/Shell/res/values-tvdpi/dimen.xml
@@ -28,7 +28,7 @@
6dp
4dp
24dp
- 26dp
+ 30dp
20dp
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TvWindowMenuActionButton.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TvWindowMenuActionButton.java
index 39b0b5500ceaf..8ba785a1f03ab 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TvWindowMenuActionButton.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TvWindowMenuActionButton.java
@@ -19,6 +19,8 @@ package com.android.wm.shell.common;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
+import android.os.Handler;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
@@ -30,11 +32,11 @@ import com.android.wm.shell.R;
/**
* A common action button for TV window menu layouts.
*/
-public class TvWindowMenuActionButton extends RelativeLayout implements View.OnClickListener {
+public class TvWindowMenuActionButton extends RelativeLayout {
private final ImageView mIconImageView;
private final View mButtonBackgroundView;
- private final View mButtonView;
- private OnClickListener mOnClickListener;
+
+ private Icon mCurrentIcon;
public TvWindowMenuActionButton(Context context) {
this(context, null, 0, 0);
@@ -56,7 +58,6 @@ public class TvWindowMenuActionButton extends RelativeLayout implements View.OnC
inflater.inflate(R.layout.tv_window_menu_action_button, this);
mIconImageView = findViewById(R.id.icon);
- mButtonView = findViewById(R.id.button);
mButtonBackgroundView = findViewById(R.id.background);
final int[] values = new int[]{android.R.attr.src, android.R.attr.text};
@@ -71,23 +72,6 @@ public class TvWindowMenuActionButton extends RelativeLayout implements View.OnC
typedArray.recycle();
}
- @Override
- public void setOnClickListener(OnClickListener listener) {
- // We do not want to set an OnClickListener to the TvWindowMenuActionButton itself, but only
- // to the ImageView. So let's "cash" the listener we've been passed here and set a "proxy"
- // listener to the ImageView.
- mOnClickListener = listener;
- mButtonView.setOnClickListener(listener != null ? this : null);
- }
-
- @Override
- public void onClick(View v) {
- if (mOnClickListener != null) {
- // Pass the correct view - this.
- mOnClickListener.onClick(this);
- }
- }
-
/**
* Sets the drawable for the button with the given drawable.
*/
@@ -104,11 +88,24 @@ public class TvWindowMenuActionButton extends RelativeLayout implements View.OnC
}
}
+ public void setImageIconAsync(Icon icon, Handler handler) {
+ mCurrentIcon = icon;
+ // Remove old image while waiting for the new one to load.
+ mIconImageView.setImageDrawable(null);
+ icon.loadDrawableAsync(mContext, d -> {
+ // The image hasn't been set any other way and the drawable belongs to the most
+ // recently set Icon.
+ if (mIconImageView.getDrawable() == null && mCurrentIcon == icon) {
+ mIconImageView.setImageDrawable(d);
+ }
+ }, handler);
+ }
+
/**
* Sets the text for description the with the given string.
*/
public void setTextAndDescription(CharSequence text) {
- mButtonView.setContentDescription(text);
+ setContentDescription(text);
}
/**
@@ -118,16 +115,6 @@ public class TvWindowMenuActionButton extends RelativeLayout implements View.OnC
setTextAndDescription(getContext().getString(resId));
}
- @Override
- public void setEnabled(boolean enabled) {
- mButtonView.setEnabled(enabled);
- }
-
- @Override
- public boolean isEnabled() {
- return mButtonView.isEnabled();
- }
-
/**
* Marks this button as a custom close action button.
* This changes the style of the action button to highlight that this action finishes the
@@ -147,10 +134,10 @@ public class TvWindowMenuActionButton extends RelativeLayout implements View.OnC
@Override
public String toString() {
- if (mButtonView.getContentDescription() == null) {
+ if (getContentDescription() == null) {
return TvWindowMenuActionButton.class.getSimpleName();
}
- return mButtonView.getContentDescription().toString();
+ return getContentDescription().toString();
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipAction.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipAction.java
new file mode 100644
index 0000000000000..9f8f95c93fec2
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipAction.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.pip.tv;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.app.PendingIntent;
+import android.os.Handler;
+
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.common.TvWindowMenuActionButton;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+abstract class TvPipAction {
+
+ private static final String TAG = TvPipAction.class.getSimpleName();
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = {"ACTION_"}, value = {
+ ACTION_FULLSCREEN,
+ ACTION_CLOSE,
+ ACTION_MOVE,
+ ACTION_EXPAND_COLLAPSE,
+ ACTION_CUSTOM,
+ ACTION_CUSTOM_CLOSE
+ })
+ public @interface ActionType {
+ }
+
+ public static final int ACTION_FULLSCREEN = 0;
+ public static final int ACTION_CLOSE = 1;
+ public static final int ACTION_MOVE = 2;
+ public static final int ACTION_EXPAND_COLLAPSE = 3;
+ public static final int ACTION_CUSTOM = 4;
+ public static final int ACTION_CUSTOM_CLOSE = 5;
+
+ @ActionType
+ private final int mActionType;
+
+ TvPipAction(@ActionType int actionType) {
+ mActionType = actionType;
+ }
+
+ boolean isCloseAction() {
+ return mActionType == ACTION_CLOSE || mActionType == ACTION_CUSTOM_CLOSE;
+ }
+
+ @ActionType
+ int getActionType() {
+ return mActionType;
+ }
+
+ abstract void populateButton(@NonNull TvWindowMenuActionButton button, Handler mainHandler);
+
+ abstract PendingIntent getPendingIntent();
+
+ void executePendingIntent() {
+ if (getPendingIntent() == null) return;
+ try {
+ getPendingIntent().send();
+ } catch (PendingIntent.CanceledException e) {
+ ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: Failed to send action, %s", TAG, e);
+ }
+ }
+
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipCustomAction.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipCustomAction.java
new file mode 100644
index 0000000000000..5c0b1b3aa383d
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipCustomAction.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.pip.tv;
+
+import android.annotation.NonNull;
+import android.app.PendingIntent;
+import android.app.RemoteAction;
+import android.os.Handler;
+
+import com.android.wm.shell.common.TvWindowMenuActionButton;
+
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * A TvPipAction for actions that the app provides via {@link
+ * android.app.PictureInPictureParams.Builder#setCloseAction(RemoteAction)} or {@link
+ * android.app.PictureInPictureParams.Builder#setActions(List)}.
+ */
+public class TvPipCustomAction extends TvPipAction {
+
+ private final RemoteAction mRemoteAction;
+
+ TvPipCustomAction(@ActionType int actionType, @NonNull RemoteAction remoteAction) {
+ super(actionType);
+ Objects.requireNonNull(remoteAction);
+ mRemoteAction = remoteAction;
+ }
+
+ void populateButton(@NonNull TvWindowMenuActionButton button, Handler mainHandler) {
+ if (button == null || mainHandler == null) return;
+ if (mRemoteAction.getContentDescription().length() > 0) {
+ button.setTextAndDescription(mRemoteAction.getContentDescription());
+ } else {
+ button.setTextAndDescription(mRemoteAction.getTitle());
+ }
+ button.setImageIconAsync(mRemoteAction.getIcon(), mainHandler);
+ button.setEnabled(isCloseAction() || mRemoteAction.isEnabled());
+ }
+
+ PendingIntent getPendingIntent() {
+ return mRemoteAction.getActionIntent();
+ }
+
+}
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 c39400ab61e3f..e80602ea0cd92 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
@@ -146,7 +146,7 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
int pipMenuBorderWidth = mContext.getResources()
.getDimensionPixelSize(R.dimen.pip_menu_border_width);
mTvPipBoundsState.setPipMenuPermanentDecorInsets(Insets.of(-pipMenuBorderWidth,
- -pipMenuBorderWidth, -pipMenuBorderWidth, -pipMenuBorderWidth));
+ -pipMenuBorderWidth, -pipMenuBorderWidth, -pipMenuBorderWidth));
mTvPipBoundsState.setPipMenuTemporaryDecorInsets(Insets.of(0, 0, 0, -pipEduTextHeight));
}
@@ -221,7 +221,7 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
if (mInMoveMode) {
mPipMenuView.showMoveMenu(mDelegate.getPipGravity());
} else {
- mPipMenuView.showButtonsMenu();
+ mPipMenuView.showButtonsMenu(/* exitingMoveMode= */ false);
}
mPipMenuView.updateBounds(mTvPipBoundsState.getBounds());
}
@@ -294,7 +294,7 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
}
if (mInMoveMode) {
setInMoveMode(false);
- mPipMenuView.showButtonsMenu();
+ mPipMenuView.showButtonsMenu(/* exitingMoveMode= */ true);
return true;
}
return false;
@@ -360,9 +360,9 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
return;
}
if (!mAppActions.isEmpty()) {
- mPipMenuView.setAdditionalActions(mAppActions, mCloseAction, mMainHandler);
+ mPipMenuView.setAdditionalActions(mAppActions, mCloseAction);
} else {
- mPipMenuView.setAdditionalActions(mMediaActions, mCloseAction, mMainHandler);
+ mPipMenuView.setAdditionalActions(mMediaActions, mCloseAction);
}
}
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 57e95c416b3c4..d8dc022ad59c2 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
@@ -25,28 +25,30 @@ import static android.view.KeyEvent.KEYCODE_DPAD_RIGHT;
import static android.view.KeyEvent.KEYCODE_DPAD_UP;
import static android.view.KeyEvent.KEYCODE_ENTER;
-import android.app.PendingIntent;
+import static com.android.wm.shell.pip.tv.TvPipAction.ACTION_CLOSE;
+import static com.android.wm.shell.pip.tv.TvPipAction.ACTION_CUSTOM;
+import static com.android.wm.shell.pip.tv.TvPipAction.ACTION_CUSTOM_CLOSE;
+import static com.android.wm.shell.pip.tv.TvPipAction.ACTION_EXPAND_COLLAPSE;
+import static com.android.wm.shell.pip.tv.TvPipAction.ACTION_FULLSCREEN;
+import static com.android.wm.shell.pip.tv.TvPipAction.ACTION_MOVE;
+
import android.app.RemoteAction;
import android.content.Context;
import android.graphics.Rect;
import android.os.Handler;
import android.view.Gravity;
import android.view.KeyEvent;
-import android.view.SurfaceControl;
import android.view.View;
import android.view.ViewGroup;
-import android.view.ViewRootImpl;
import android.view.accessibility.AccessibilityManager;
import android.widget.FrameLayout;
-import android.widget.HorizontalScrollView;
import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.ScrollView;
import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.widget.LinearLayoutManager;
+import com.android.internal.widget.RecyclerView;
import com.android.wm.shell.R;
import com.android.wm.shell.common.TvWindowMenuActionButton;
import com.android.wm.shell.pip.PipUtils;
@@ -60,84 +62,92 @@ import java.util.List;
* actions: Fullscreen, Move and Close, but could also display "additional" actions, that may be set
* via a {@link #setAdditionalActions(List, RemoteAction, Handler)} call.
*/
-public class TvPipMenuView extends FrameLayout implements View.OnClickListener {
+public class TvPipMenuView extends FrameLayout {
private static final String TAG = "TvPipMenuView";
- private static final int FIRST_CUSTOM_ACTION_POSITION = 3;
+ private static final int CLOSE_ACTION_INDEX = 1;
+ private static final int FIRST_CUSTOM_ACTION_INDEX = 2;
- private final Listener mListener;
+ private final TvPipMenuView.Listener mListener;
+
+ private final List mActionsList;
+ private final TvPipSystemAction mDefaultCloseAction;
+ private final TvPipSystemAction mExpandCollapseAction;
+
+ private final RecyclerView mActionButtonsRecyclerView;
+ private final LinearLayoutManager mButtonLayoutManager;
+ private final RecyclerViewAdapter mRecyclerViewAdapter;
- private final LinearLayout mActionButtonsContainer;
- private final View mMenuFrameView;
- private final List mAdditionalButtons = new ArrayList<>();
private final View mPipFrameView;
+ private final View mMenuFrameView;
private final View mPipView;
+
+ private final View mPipBackground;
+ private final View mDimLayer;
+
private final TvPipMenuEduTextDrawer mEduTextDrawer;
+
private final int mPipMenuOuterSpace;
private final int mPipMenuBorderWidth;
+ private final int mPipMenuFadeAnimationDuration;
+ private final int mResizeAnimationDuration;
+
private final ImageView mArrowUp;
private final ImageView mArrowRight;
private final ImageView mArrowDown;
private final ImageView mArrowLeft;
private final TvWindowMenuActionButton mA11yDoneButton;
- private final View mPipBackground;
- private final View mDimLayer;
-
- private final ScrollView mScrollView;
- private final HorizontalScrollView mHorizontalScrollView;
- private View mFocusedButton;
-
private Rect mCurrentPipBounds;
private boolean mMoveMenuIsVisible;
private boolean mButtonMenuIsVisible;
-
- private final TvWindowMenuActionButton mExpandButton;
- private final TvWindowMenuActionButton mCloseButton;
-
private boolean mSwitchingOrientation;
- private final int mPipMenuFadeAnimationDuration;
- private final int mResizeAnimationDuration;
-
private final AccessibilityManager mA11yManager;
private final Handler mMainHandler;
public TvPipMenuView(@NonNull Context context, @NonNull Handler mainHandler,
@NonNull Listener listener) {
super(context, null, 0, 0);
-
inflate(context, R.layout.tv_pip_menu, this);
mMainHandler = mainHandler;
mListener = listener;
-
mA11yManager = context.getSystemService(AccessibilityManager.class);
- mActionButtonsContainer = findViewById(R.id.tv_pip_menu_action_buttons);
- mActionButtonsContainer.findViewById(R.id.tv_pip_menu_fullscreen_button)
- .setOnClickListener(this);
+ mActionButtonsRecyclerView = findViewById(R.id.tv_pip_menu_action_buttons);
+ mButtonLayoutManager = new LinearLayoutManager(mContext);
+ mActionButtonsRecyclerView.setLayoutManager(mButtonLayoutManager);
+ mActionButtonsRecyclerView.setPreserveFocusAfterLayout(true);
- mCloseButton = mActionButtonsContainer.findViewById(R.id.tv_pip_menu_close_button);
- mCloseButton.setOnClickListener(this);
- mCloseButton.setIsCustomCloseAction(true);
+ mDefaultCloseAction =
+ new TvPipSystemAction(ACTION_CLOSE, R.string.pip_close,
+ R.drawable.pip_ic_close_white);
+ mExpandCollapseAction =
+ new TvPipSystemAction(ACTION_EXPAND_COLLAPSE, R.string.pip_collapse,
+ R.drawable.pip_ic_collapse);
- mActionButtonsContainer.findViewById(R.id.tv_pip_menu_move_button)
- .setOnClickListener(this);
- mExpandButton = findViewById(R.id.tv_pip_menu_expand_button);
- mExpandButton.setOnClickListener(this);
+ mActionsList = new ArrayList<>();
+ mActionsList.add(
+ new TvPipSystemAction(ACTION_FULLSCREEN, R.string.pip_fullscreen,
+ R.drawable.pip_ic_fullscreen_white));
+ mActionsList.add(mDefaultCloseAction);
+ mActionsList.add(
+ new TvPipSystemAction(ACTION_MOVE, R.string.pip_move,
+ R.drawable.pip_ic_move_white));
+ mActionsList.add(mExpandCollapseAction);
- mPipBackground = findViewById(R.id.tv_pip_menu_background);
- mDimLayer = findViewById(R.id.tv_pip_menu_dim_layer);
-
- mScrollView = findViewById(R.id.tv_pip_menu_scroll);
- mHorizontalScrollView = findViewById(R.id.tv_pip_menu_horizontal_scroll);
+ mRecyclerViewAdapter = new RecyclerViewAdapter(mActionsList);
+ mActionButtonsRecyclerView.setAdapter(mRecyclerViewAdapter);
mMenuFrameView = findViewById(R.id.tv_pip_menu_frame);
mPipFrameView = findViewById(R.id.tv_pip_border);
mPipView = findViewById(R.id.tv_pip);
+ mPipBackground = findViewById(R.id.tv_pip_menu_background);
+ mDimLayer = findViewById(R.id.tv_pip_menu_dim_layer);
+
mArrowUp = findViewById(R.id.tv_pip_menu_arrow_up);
mArrowRight = findViewById(R.id.tv_pip_menu_arrow_right);
mArrowDown = findViewById(R.id.tv_pip_menu_arrow_down);
@@ -160,8 +170,12 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener {
}
void onPipTransitionToTargetBoundsStarted(Rect targetBounds) {
+ if (targetBounds == null) {
+ return;
+ }
+
// Fade out content by fading in view on top.
- if (mCurrentPipBounds != null && targetBounds != null) {
+ if (mCurrentPipBounds != null) {
boolean ratioChanged = PipUtils.aspectRatioChanged(
mCurrentPipBounds.width() / (float) mCurrentPipBounds.height(),
targetBounds.width() / (float) targetBounds.height());
@@ -177,7 +191,7 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener {
// Update buttons.
final boolean vertical = targetBounds.height() > targetBounds.width();
final boolean orientationChanged =
- vertical != (mActionButtonsContainer.getOrientation() == LinearLayout.VERTICAL);
+ vertical != (mButtonLayoutManager.getOrientation() == LinearLayoutManager.VERTICAL);
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: onPipTransitionToTargetBoundsStarted(), orientation changed %b",
TAG, orientationChanged);
@@ -187,19 +201,19 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener {
if (mButtonMenuIsVisible) {
mSwitchingOrientation = true;
- mActionButtonsContainer.animate()
+ mActionButtonsRecyclerView.animate()
.alpha(0)
.setInterpolator(TvPipInterpolators.EXIT)
.setDuration(mResizeAnimationDuration / 2)
.withEndAction(() -> {
- changeButtonScrollOrientation(targetBounds);
- updateButtonGravity(targetBounds);
+ mButtonLayoutManager.setOrientation(vertical
+ ? LinearLayoutManager.VERTICAL : LinearLayoutManager.HORIZONTAL);
// Only make buttons visible again in onPipTransitionFinished to keep in
// sync with PiP content alpha animation.
});
} else {
- changeButtonScrollOrientation(targetBounds);
- updateButtonGravity(targetBounds);
+ mButtonLayoutManager.setOrientation(vertical
+ ? LinearLayoutManager.VERTICAL : LinearLayoutManager.HORIZONTAL);
}
}
@@ -207,7 +221,7 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: onPipTransitionFinished()", TAG);
- // Fade in content by fading out view on top.
+ // Fade in content by fading out view on top (faded out at every aspect ratio change).
mPipBackground.animate()
.alpha(0f)
.setDuration(mResizeAnimationDuration / 2)
@@ -218,16 +232,14 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener {
mEduTextDrawer.init();
}
+ // Update buttons.
setIsExpanded(isTvPipExpanded);
- // Update buttons.
if (mSwitchingOrientation) {
- mActionButtonsContainer.animate()
+ mActionButtonsRecyclerView.animate()
.alpha(1)
.setInterpolator(TvPipInterpolators.ENTER)
.setDuration(mResizeAnimationDuration / 2);
- } else {
- refocusPreviousButton();
}
mSwitchingOrientation = false;
}
@@ -240,107 +252,9 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener {
"%s: updateLayout, width: %s, height: %s", TAG, updatedBounds.width(),
updatedBounds.height());
mCurrentPipBounds = updatedBounds;
- if (!mSwitchingOrientation) {
- updateButtonGravity(mCurrentPipBounds);
- }
-
updatePipFrameBounds();
}
- private void changeButtonScrollOrientation(Rect bounds) {
- final boolean vertical = bounds.height() > bounds.width();
-
- final ViewGroup oldScrollView = vertical ? mHorizontalScrollView : mScrollView;
- final ViewGroup newScrollView = vertical ? mScrollView : mHorizontalScrollView;
-
- if (oldScrollView.getChildCount() == 1) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: orientation changed", TAG);
- oldScrollView.removeView(mActionButtonsContainer);
- oldScrollView.setVisibility(GONE);
- mActionButtonsContainer.setOrientation(vertical ? LinearLayout.VERTICAL
- : LinearLayout.HORIZONTAL);
- newScrollView.addView(mActionButtonsContainer);
- newScrollView.setVisibility(VISIBLE);
- if (mFocusedButton != null) {
- mFocusedButton.requestFocus();
- }
- }
- }
-
- /**
- * Change button gravity based on new dimensions
- */
- private void updateButtonGravity(Rect bounds) {
- final boolean vertical = bounds.height() > bounds.width();
- // Use Math.max since the possible orientation change might not have been applied yet.
- final int buttonsSize = Math.max(mActionButtonsContainer.getHeight(),
- mActionButtonsContainer.getWidth());
-
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: buttons container width: %s, height: %s", TAG,
- mActionButtonsContainer.getWidth(), mActionButtonsContainer.getHeight());
-
- final boolean buttonsFit =
- vertical ? buttonsSize < bounds.height()
- : buttonsSize < bounds.width();
- final int buttonGravity = buttonsFit ? Gravity.CENTER
- : (vertical ? Gravity.CENTER_HORIZONTAL : Gravity.CENTER_VERTICAL);
-
- final LayoutParams params = (LayoutParams) mActionButtonsContainer.getLayoutParams();
- params.gravity = buttonGravity;
- mActionButtonsContainer.setLayoutParams(params);
-
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: vertical: %b, buttonsFit: %b, gravity: %s", TAG, vertical, buttonsFit,
- Gravity.toString(buttonGravity));
- }
-
- private void refocusPreviousButton() {
- if (mMoveMenuIsVisible || mCurrentPipBounds == null || mFocusedButton == null) {
- return;
- }
- final boolean vertical = mCurrentPipBounds.height() > mCurrentPipBounds.width();
-
- if (!mFocusedButton.hasFocus()) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: request focus from: %s", TAG, mFocusedButton);
- mFocusedButton.requestFocus();
- } else {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: already focused: %s", TAG, mFocusedButton);
- }
-
- // Do we need to scroll?
- final Rect buttonBounds = new Rect();
- final Rect scrollBounds = new Rect();
- if (vertical) {
- mScrollView.getDrawingRect(scrollBounds);
- } else {
- mHorizontalScrollView.getDrawingRect(scrollBounds);
- }
- mFocusedButton.getHitRect(buttonBounds);
-
- if (scrollBounds.contains(buttonBounds)) {
- // Button is already completely visible, don't scroll
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: not scrolling", TAG);
- return;
- }
-
- // Scrolling so the button is visible to the user.
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: scrolling to focused button", TAG);
-
- if (vertical) {
- mScrollView.smoothScrollTo((int) mFocusedButton.getX(),
- (int) mFocusedButton.getY());
- } else {
- mHorizontalScrollView.smoothScrollTo((int) mFocusedButton.getX(),
- (int) mFocusedButton.getY());
- }
- }
-
Rect getPipMenuContainerBounds(Rect pipBounds) {
final Rect menuUiBounds = new Rect(pipBounds);
menuUiBounds.inset(-mPipMenuOuterSpace, -mPipMenuOuterSpace);
@@ -370,20 +284,34 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener {
mPipView.setLayoutParams(pipViewParams);
}
-
+ // Keep focused button within the visible area while the PiP is changing size. Otherwise,
+ // the button would lose focus which would cause a need for scrolling and re-focusing after
+ // the animation finishes, which does not look good.
+ View focusedChild = mActionButtonsRecyclerView.getFocusedChild();
+ if (focusedChild != null) {
+ mActionButtonsRecyclerView.scrollToPosition(
+ mActionButtonsRecyclerView.getChildLayoutPosition(focusedChild));
+ }
}
void setExpandedModeEnabled(boolean enabled) {
- mExpandButton.setVisibility(enabled ? VISIBLE : GONE);
+ int actionIndex = mActionsList.indexOf(mExpandCollapseAction);
+ boolean actionInList = actionIndex != -1;
+ if (enabled && !actionInList) {
+ mActionsList.add(mExpandCollapseAction);
+ mRecyclerViewAdapter.notifyItemInserted(mActionsList.size() - 1);
+ } else if (!enabled && actionInList) {
+ mActionsList.remove(actionIndex);
+ mRecyclerViewAdapter.notifyItemRemoved(actionIndex);
+ }
}
void setIsExpanded(boolean expanded) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: setIsExpanded, expanded: %b", TAG, expanded);
- mExpandButton.setImageResource(
+ mExpandCollapseAction.update(expanded ? R.string.pip_collapse : R.string.pip_expand,
expanded ? R.drawable.pip_ic_collapse : R.drawable.pip_ic_expand);
- mExpandButton.setTextAndDescription(
- expanded ? R.string.pip_collapse : R.string.pip_expand);
+ mRecyclerViewAdapter.notifyItemChanged(mActionsList.indexOf(mExpandCollapseAction));
}
/**
@@ -391,48 +319,75 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener {
*/
void showMoveMenu(int gravity) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: showMoveMenu()", TAG);
- showButtonsMenu(false);
showMovementHints(gravity);
+ setMenuButtonsVisible(false);
setFrameHighlighted(true);
- mHorizontalScrollView.setFocusable(false);
- mScrollView.setFocusable(false);
+ animateAlphaTo(mA11yManager.isEnabled() ? 1f : 0f, mDimLayer);
mEduTextDrawer.closeIfNeeded();
}
- void showButtonsMenu() {
+
+ void showButtonsMenu(boolean exitingMoveMode) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: showButtonsMenu()", TAG);
- showButtonsMenu(true);
+ "%s: showButtonsMenu(), exitingMoveMode %b", TAG, exitingMoveMode);
+ setMenuButtonsVisible(true);
hideMovementHints();
setFrameHighlighted(true);
+ animateAlphaTo(1f, mDimLayer);
+ mEduTextDrawer.closeIfNeeded();
- mHorizontalScrollView.setFocusable(true);
- mScrollView.setFocusable(true);
-
- // Always focus on the first button when opening the menu, except directly after moving.
- if (mFocusedButton == null) {
- // Focus on first button (there is a Space at position 0)
- mFocusedButton = mActionButtonsContainer.getChildAt(1);
- // Reset scroll position.
- mScrollView.scrollTo(0, 0);
- mHorizontalScrollView.scrollTo(
- isLayoutRtl() ? mActionButtonsContainer.getWidth() : 0, 0);
+ if (exitingMoveMode) {
+ scrollAndRefocusButton(getFirstIndexOfAction(ACTION_MOVE),
+ /* alwaysScroll= */ false);
+ } else {
+ scrollAndRefocusButton(0, /* alwaysScroll= */ true);
+ }
+ }
+
+ 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));
}
- refocusPreviousButton();
}
/**
- * Hides all menu views, including the menu frame.
+ * @return true if focus was requested, false if focus request could not be carried out due to
+ * the view for the position not being available (scrolling beforehand will be necessary).
*/
+ private boolean refocusButton(int position) {
+ View itemToFocus = mButtonLayoutManager.findViewByPosition(position);
+ if (itemToFocus != null) {
+ itemToFocus.requestFocus();
+ itemToFocus.requestAccessibilityFocus();
+ }
+ return itemToFocus != null;
+ }
+
+ /**
+ * Returns the position of the first action of the given action type or -1 if none can be found.
+ */
+ private int getFirstIndexOfAction(@TvPipAction.ActionType int actionType) {
+ for (int i = 0; i < mActionsList.size(); i++) {
+ if (mActionsList.get(i).getActionType() == actionType) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
void hideAllUserControls() {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: hideAllUserControls()", TAG);
- mFocusedButton = null;
- showButtonsMenu(false);
+ setMenuButtonsVisible(false);
hideMovementHints();
setFrameHighlighted(false);
+ animateAlphaTo(0f, mDimLayer);
}
@Override
@@ -463,134 +418,63 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener {
});
}
- /**
- * Button order:
- * - Fullscreen
- * - Close
- * - Custom actions (app or media actions)
- * - System actions
- */
- void setAdditionalActions(List actions, RemoteAction closeAction,
- Handler mainHandler) {
+ void setAdditionalActions(List actions, RemoteAction closeAction) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: setAdditionalActions()", TAG);
+ "%s: setAdditionalActions(), %d actions", TAG, actions.size());
- // Replace system close action with custom close action if available
- if (closeAction != null) {
- setActionForButton(closeAction, mCloseButton, mainHandler);
- } else {
- mCloseButton.setTextAndDescription(R.string.pip_close);
- mCloseButton.setImageResource(R.drawable.pip_ic_close_white);
- }
- mCloseButton.setIsCustomCloseAction(closeAction != null);
- // Make sure the close action is always enabled
- mCloseButton.setEnabled(true);
-
- // Make sure we exactly as many additional buttons as we have actions to display.
- final int actionsNumber = actions.size();
- int buttonsNumber = mAdditionalButtons.size();
- if (actionsNumber > buttonsNumber) {
- // Add buttons until we have enough to display all the actions.
- while (actionsNumber > buttonsNumber) {
- TvWindowMenuActionButton button = new TvWindowMenuActionButton(mContext);
- button.setOnClickListener(this);
-
- mActionButtonsContainer.addView(button,
- FIRST_CUSTOM_ACTION_POSITION + buttonsNumber);
- mAdditionalButtons.add(button);
-
- buttonsNumber++;
- }
- } else if (actionsNumber < buttonsNumber) {
- // Hide buttons until we as many as the actions.
- while (actionsNumber < buttonsNumber) {
- final View button = mAdditionalButtons.get(buttonsNumber - 1);
- button.setVisibility(View.GONE);
- button.setTag(null);
-
- buttonsNumber--;
+ int oldCustomActionCount = 0;
+ for (TvPipAction action : mActionsList) {
+ if (action.getActionType() == ACTION_CUSTOM) {
+ oldCustomActionCount++;
}
}
- // "Assign" actions to the buttons.
- for (int index = 0; index < actionsNumber; index++) {
- final RemoteAction action = actions.get(index);
- final TvWindowMenuActionButton button = mAdditionalButtons.get(index);
+ // Update close action.
+ mActionsList.set(CLOSE_ACTION_INDEX,
+ closeAction == null ? mDefaultCloseAction
+ : new TvPipCustomAction(ACTION_CUSTOM_CLOSE, closeAction));
+ mRecyclerViewAdapter.notifyItemChanged(CLOSE_ACTION_INDEX);
- // Remove action if it matches the custom close action.
- if (PipUtils.remoteActionsMatch(action, closeAction)) {
- button.setVisibility(GONE);
+ // Replace custom actions with new ones.
+ mActionsList.removeIf(tvPipAction -> tvPipAction.getActionType() == ACTION_CUSTOM);
+ List customActions = new ArrayList<>(actions.size());
+ int newCustomActionCount = 0;
+ for (RemoteAction action : actions) {
+ if (action == null || PipUtils.remoteActionsMatch(action, closeAction)) {
+ // Don't show an action if it is the same as the custom close action
continue;
}
- setActionForButton(action, button, mainHandler);
+ customActions.add(new TvPipCustomAction(ACTION_CUSTOM, action));
+ newCustomActionCount++;
}
+ mActionsList.addAll(FIRST_CUSTOM_ACTION_INDEX, customActions);
- if (mCurrentPipBounds != null) {
- updateButtonGravity(mCurrentPipBounds);
- refocusPreviousButton();
- }
- }
+ mRecyclerViewAdapter.notifyItemRangeChanged(
+ FIRST_CUSTOM_ACTION_INDEX, Math.min(oldCustomActionCount, newCustomActionCount));
- private void setActionForButton(RemoteAction action, TvWindowMenuActionButton button,
- Handler mainHandler) {
- button.setVisibility(View.VISIBLE); // Ensure the button is visible.
- if (action.getContentDescription().length() > 0) {
- button.setTextAndDescription(action.getContentDescription());
- } else {
- button.setTextAndDescription(action.getTitle());
- }
- button.setEnabled(action.isEnabled());
- button.setTag(action);
- action.getIcon().loadDrawableAsync(mContext, button::setImageDrawable, mainHandler);
- }
+ if (newCustomActionCount > oldCustomActionCount) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: setAdditionalActions(), %d inserted starting at %d",
+ TAG, newCustomActionCount - oldCustomActionCount,
+ FIRST_CUSTOM_ACTION_INDEX + oldCustomActionCount);
+ mRecyclerViewAdapter.notifyItemRangeInserted(
+ FIRST_CUSTOM_ACTION_INDEX + oldCustomActionCount,
+ newCustomActionCount - oldCustomActionCount);
- @Nullable
- SurfaceControl getWindowSurfaceControl() {
- final ViewRootImpl root = getViewRootImpl();
- if (root == null) {
- return null;
- }
- final SurfaceControl out = root.getSurfaceControl();
- if (out != null && out.isValid()) {
- return out;
- }
- return null;
- }
-
- @Override
- public void onClick(View v) {
- final int id = v.getId();
- if (id == R.id.tv_pip_menu_fullscreen_button) {
- mListener.onFullscreenButtonClick();
- } else if (id == R.id.tv_pip_menu_move_button) {
- mListener.onEnterMoveMode();
- } else if (id == R.id.tv_pip_menu_close_button) {
- mListener.onCloseButtonClick();
- } else if (id == R.id.tv_pip_menu_expand_button) {
- mListener.onToggleExpandedMode();
- } else {
- // This should be an "additional action"
- final RemoteAction action = (RemoteAction) v.getTag();
- if (action != null) {
- try {
- action.getActionIntent().send();
- } catch (PendingIntent.CanceledException e) {
- ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: Failed to send action, %s", TAG, e);
- }
- } else {
- ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: RemoteAction is null", TAG);
- }
+ } else if (oldCustomActionCount > newCustomActionCount) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: setAdditionalActions(), %d removed starting at %d",
+ TAG, oldCustomActionCount - newCustomActionCount,
+ FIRST_CUSTOM_ACTION_INDEX + newCustomActionCount);
+ mRecyclerViewAdapter.notifyItemRangeRemoved(
+ FIRST_CUSTOM_ACTION_INDEX + newCustomActionCount,
+ oldCustomActionCount - newCustomActionCount);
}
}
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
if (event.getAction() == ACTION_UP) {
- if (!mMoveMenuIsVisible) {
- mFocusedButton = mActionButtonsContainer.getFocusedChild();
- }
if (event.getKeyCode() == KEYCODE_BACK) {
mListener.onBackPress();
@@ -624,10 +508,6 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener {
public void showMovementHints(int gravity) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: showMovementHints(), position: %s", TAG, Gravity.toString(gravity));
-
- if (mMoveMenuIsVisible) {
- return;
- }
mMoveMenuIsVisible = true;
animateAlphaTo(checkGravity(gravity, Gravity.BOTTOM) ? 1f : 0f, mArrowUp);
@@ -643,9 +523,12 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener {
animateAlphaTo(a11yEnabled ? 1f : 0f, mA11yDoneButton);
if (a11yEnabled) {
+ mA11yDoneButton.setVisibility(VISIBLE);
mA11yDoneButton.setOnClickListener(v -> {
mListener.onExitMoveMode();
});
+ mA11yDoneButton.requestFocus();
+ mA11yDoneButton.requestAccessibilityFocus();
}
}
@@ -684,27 +567,81 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener {
/**
* Show or hide the pip buttons menu.
*/
- public void showButtonsMenu(boolean show) {
+ private void setMenuButtonsVisible(boolean visible) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: showUserActions: %b", TAG, show);
- if (mButtonMenuIsVisible == show) {
- return;
- }
- mButtonMenuIsVisible = show;
-
- if (show) {
- mActionButtonsContainer.setVisibility(VISIBLE);
- refocusPreviousButton();
- }
- animateAlphaTo(show ? 1 : 0, mActionButtonsContainer);
- animateAlphaTo(show ? 1 : 0, mDimLayer);
- mEduTextDrawer.closeIfNeeded();
+ "%s: showUserActions: %b", TAG, visible);
+ mButtonMenuIsVisible = visible;
+ animateAlphaTo(visible ? 1 : 0, mActionButtonsRecyclerView);
}
private void setFrameHighlighted(boolean highlighted) {
mMenuFrameView.setActivated(highlighted);
}
+ private class RecyclerViewAdapter extends
+ RecyclerView.Adapter {
+
+ private final List mActionList;
+
+ RecyclerViewAdapter(List actionList) {
+ this.mActionList = actionList;
+ }
+
+ @NonNull
+ @Override
+ public ButtonViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+ return new ButtonViewHolder(new TvWindowMenuActionButton(mContext));
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull ButtonViewHolder holder, int position) {
+ TvPipAction action = mActionList.get(position);
+ action.populateButton(holder.mButton, mMainHandler);
+ }
+
+ @Override
+ public int getItemCount() {
+ return mActionList.size();
+ }
+
+ private class ButtonViewHolder extends RecyclerView.ViewHolder implements OnClickListener {
+ TvWindowMenuActionButton mButton;
+
+ ButtonViewHolder(@NonNull View itemView) {
+ super(itemView);
+ mButton = (TvWindowMenuActionButton) itemView;
+ mButton.setOnClickListener(this);
+ }
+
+ @Override
+ public void onClick(View v) {
+ TvPipAction action = mActionList.get(
+ mActionButtonsRecyclerView.getChildLayoutPosition(v));
+ switch (action.getActionType()) {
+ case ACTION_FULLSCREEN:
+ mListener.onFullscreenButtonClick();
+ return;
+ case ACTION_CLOSE:
+ case ACTION_CUSTOM_CLOSE:
+ mListener.onCloseButtonClick();
+ return;
+ case ACTION_MOVE:
+ mListener.onEnterMoveMode();
+ return;
+ case ACTION_EXPAND_COLLAPSE:
+ mListener.onToggleExpandedMode();
+ return;
+ case ACTION_CUSTOM:
+ action.executePendingIntent();
+ return;
+ default:
+ ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: No action available", TAG);
+ }
+ }
+ }
+ }
+
interface Listener extends TvPipMenuEduTextDrawer.Listener {
void onBackPress();
@@ -730,4 +667,4 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener {
void onToggleExpandedMode();
}
-}
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipSystemAction.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipSystemAction.java
new file mode 100644
index 0000000000000..83a26c351bcfe
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipSystemAction.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.pip.tv;
+
+import android.annotation.DrawableRes;
+import android.annotation.NonNull;
+import android.annotation.StringRes;
+import android.app.PendingIntent;
+import android.os.Handler;
+
+import com.android.wm.shell.common.TvWindowMenuActionButton;
+
+/**
+ * A TvPipAction for actions that the system provides, i.e. fullscreen, default close, move,
+ * expand/collapse.
+ */
+public class TvPipSystemAction extends TvPipAction {
+
+ @StringRes
+ private int mTitleResource;
+ @DrawableRes
+ private int mIconResource;
+
+ TvPipSystemAction(@ActionType int actionType, @StringRes int title, @DrawableRes int icon) {
+ super(actionType);
+ update(title, icon);
+ }
+
+ void update(@StringRes int title, @DrawableRes int icon) {
+ mTitleResource = title;
+ mIconResource = icon;
+ }
+
+ void populateButton(@NonNull TvWindowMenuActionButton button, Handler mainHandler) {
+ button.setTextAndDescription(mTitleResource);
+ button.setImageResource(mIconResource);
+ button.setEnabled(true);
+ }
+
+ PendingIntent getPendingIntent() {
+ return null;
+ }
+
+}