diff --git a/packages/SystemUI/res/layout/tv_pip_controls.xml b/packages/SystemUI/res/layout/tv_pip_controls.xml index 61ac6f6991ffe..0b7bce13d7613 100644 --- a/packages/SystemUI/res/layout/tv_pip_controls.xml +++ b/packages/SystemUI/res/layout/tv_pip_controls.xml @@ -22,24 +22,24 @@ diff --git a/packages/SystemUI/res/layout/tv_pip_custom_control.xml b/packages/SystemUI/res/layout/tv_pip_custom_control.xml new file mode 100644 index 0000000000000..dd0fce4665769 --- /dev/null +++ b/packages/SystemUI/res/layout/tv_pip_custom_control.xml @@ -0,0 +1,23 @@ + + + diff --git a/packages/SystemUI/res/values-tvdpi/dimens.xml b/packages/SystemUI/res/values-tvdpi/dimens.xml index 5327cee7cae8f..4d978aacc65ff 100644 --- a/packages/SystemUI/res/values-tvdpi/dimens.xml +++ b/packages/SystemUI/res/values-tvdpi/dimens.xml @@ -24,4 +24,8 @@ 10% 1px + + + 100dp + -50dp diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java index 0f69f471bc24b..a5ee198245903 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java @@ -427,11 +427,7 @@ public class PipMenuActivity extends Activity { } else { actionsContainer.setVisibility(View.VISIBLE); if (mActionsGroup != null) { - // Hide extra views - for (int i = mActions.size(); i < mActionsGroup.getChildCount(); i++) { - mActionsGroup.getChildAt(i).setVisibility(View.GONE); - } - // Add needed views + // Ensure we have as many buttons as actions final LayoutInflater inflater = LayoutInflater.from(this); while (mActionsGroup.getChildCount() < mActions.size()) { final ImageView actionView = (ImageView) inflater.inflate( @@ -439,6 +435,13 @@ public class PipMenuActivity extends Activity { mActionsGroup.addView(actionView); } + // Update the visibility of all views + for (int i = 0; i < mActionsGroup.getChildCount(); i++) { + mActionsGroup.getChildAt(i).setVisibility(i < mActions.size() + ? View.VISIBLE + : View.GONE); + } + // Recreate the layout final boolean isLandscapePip = stackBounds != null && (stackBounds.width() > stackBounds.height()); @@ -460,10 +463,9 @@ public class PipMenuActivity extends Activity { Log.w(TAG, "Failed to send action", e); } }); - } else { - actionView.setAlpha(DISABLED_ACTION_ALPHA); } actionView.setEnabled(action.isEnabled()); + actionView.setAlpha(action.isEnabled() ? 1f : DISABLED_ACTION_ALPHA); // Update the margin between actions LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipControlButtonView.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipControlButtonView.java index 40a63d7d92c0f..b21cd95626a79 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipControlButtonView.java +++ b/packages/SystemUI/src/com/android/systemui/pip/tv/PipControlButtonView.java @@ -20,6 +20,7 @@ import android.animation.Animator; import android.animation.AnimatorInflater; import android.content.Context; import android.content.res.TypedArray; +import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.View; @@ -33,6 +34,7 @@ import com.android.systemui.R; * A view containing PIP controls including fullscreen, close, and media controls. */ public class PipControlButtonView extends RelativeLayout { + private OnFocusChangeListener mFocusChangeListener; private ImageView mIconImageView; ImageView mButtonImageView; @@ -121,19 +123,38 @@ public class PipControlButtonView extends RelativeLayout { mFocusChangeListener = listener; } + /** + * Sets the drawable for the button with the given drawable. + */ + public void setImageDrawable(Drawable d) { + mIconImageView.setImageDrawable(d); + } + /** * Sets the drawable for the button with the given resource id. */ public void setImageResource(int resId) { - mIconImageView.setImageResource(resId); + if (resId != 0) { + mIconImageView.setImageResource(resId); + } + } + + /** + * Sets the text for description the with the given string. + */ + public void setText(CharSequence text) { + mButtonImageView.setContentDescription(text); + mDescriptionTextView.setText(text); } /** * Sets the text for description the with the given resource id. */ public void setText(int resId) { - mButtonImageView.setContentDescription(getContext().getString(resId)); - mDescriptionTextView.setText(resId); + if (resId != 0) { + mButtonImageView.setContentDescription(getContext().getString(resId)); + mDescriptionTextView.setText(resId); + } } private static void cancelAnimator(Animator animator) { diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipControlsView.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipControlsView.java index acea3b6b12ad2..10206d492e3a0 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipControlsView.java +++ b/packages/SystemUI/src/com/android/systemui/pip/tv/PipControlsView.java @@ -16,12 +16,20 @@ package com.android.systemui.pip.tv; +import android.app.ActivityManager; +import android.app.PendingIntent.CanceledException; +import android.app.RemoteAction; import android.content.Context; +import android.graphics.Color; import android.media.session.MediaController; import android.media.session.PlaybackState; +import android.os.Handler; +import android.os.RemoteException; +import android.util.Log; import android.view.View; import android.view.Gravity; import android.view.LayoutInflater; +import android.widget.ImageView; import android.widget.LinearLayout; import android.util.AttributeSet; @@ -30,11 +38,19 @@ import com.android.systemui.R; import static android.media.session.PlaybackState.ACTION_PAUSE; import static android.media.session.PlaybackState.ACTION_PLAY; +import java.util.ArrayList; +import java.util.List; + /** * A view containing PIP controls including fullscreen, close, and media controls. */ public class PipControlsView extends LinearLayout { + + private static final String TAG = PipControlsView.class.getSimpleName(); + + private static final float DISABLED_ACTION_ALPHA = 0.54f; + /** * An interface to listen user action. */ @@ -47,19 +63,23 @@ public class PipControlsView extends LinearLayout { private MediaController mMediaController; - final PipManager mPipManager = PipManager.getInstance(); - Listener mListener; + private final PipManager mPipManager = PipManager.getInstance(); + private final LayoutInflater mLayoutInflater; + private final Handler mHandler; + private Listener mListener; private PipControlButtonView mFullButtonView; private PipControlButtonView mCloseButtonView; private PipControlButtonView mPlayPauseButtonView; + private ArrayList mCustomButtonViews = new ArrayList<>(); + private List mCustomActions = new ArrayList<>(); private PipControlButtonView mFocusedChild; private MediaController.Callback mMediaControllerCallback = new MediaController.Callback() { @Override public void onPlaybackStateChanged(PlaybackState state) { - updatePlayPauseView(); + updateUserActions(); } }; @@ -95,9 +115,10 @@ public class PipControlsView extends LinearLayout { public PipControlsView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); - LayoutInflater inflater = (LayoutInflater) getContext() + mLayoutInflater = (LayoutInflater) getContext() .getSystemService(Context.LAYOUT_INFLATER_SERVICE); - inflater.inflate(R.layout.tv_pip_controls, this); + mLayoutInflater.inflate(R.layout.tv_pip_controls, this); + mHandler = new Handler(); setOrientation(LinearLayout.HORIZONTAL); setGravity(Gravity.TOP | Gravity.CENTER_HORIZONTAL); @@ -176,21 +197,74 @@ public class PipControlsView extends LinearLayout { if (mMediaController != null) { mMediaController.registerCallback(mMediaControllerCallback); } - updatePlayPauseView(); + updateUserActions(); } - private void updatePlayPauseView() { - int state = mPipManager.getPlaybackState(); - if (state == PipManager.PLAYBACK_STATE_UNAVAILABLE) { + /** + * Updates the actions for the PIP. If there are no custom actions, then the media session + * actions are shown. + */ + private void updateUserActions() { + if (!mCustomActions.isEmpty()) { + // Ensure we have as many buttons as actions + while (mCustomButtonViews.size() < mCustomActions.size()) { + PipControlButtonView buttonView = (PipControlButtonView) mLayoutInflater.inflate( + R.layout.tv_pip_custom_control, this, false); + addView(buttonView); + mCustomButtonViews.add(buttonView); + } + + // Update the visibility of all views + for (int i = 0; i < mCustomButtonViews.size(); i++) { + mCustomButtonViews.get(i).setVisibility(i < mCustomActions.size() + ? View.VISIBLE + : View.GONE); + } + + // Update the state and visibility of the action buttons, and hide the rest + for (int i = 0; i < mCustomActions.size(); i++) { + final RemoteAction action = mCustomActions.get(i); + PipControlButtonView actionView = mCustomButtonViews.get(i); + + // TODO: Check if the action drawable has changed before we reload it + action.getIcon().loadDrawableAsync(getContext(), d -> { + d.setTint(Color.WHITE); + actionView.setImageDrawable(d); + }, mHandler); + actionView.setText(action.getContentDescription()); + if (action.isEnabled()) { + actionView.setOnClickListener(v -> { + try { + action.getActionIntent().send(); + } catch (CanceledException e) { + Log.w(TAG, "Failed to send action", e); + } + }); + } + actionView.setEnabled(action.isEnabled()); + actionView.setAlpha(action.isEnabled() ? 1f : DISABLED_ACTION_ALPHA); + } + + // Hide the media session buttons mPlayPauseButtonView.setVisibility(View.GONE); } else { - mPlayPauseButtonView.setVisibility(View.VISIBLE); - if (state == PipManager.PLAYBACK_STATE_PLAYING) { - mPlayPauseButtonView.setImageResource(R.drawable.ic_pause_white); - mPlayPauseButtonView.setText(R.string.pip_pause); + int state = mPipManager.getPlaybackState(); + if (state == PipManager.PLAYBACK_STATE_UNAVAILABLE) { + mPlayPauseButtonView.setVisibility(View.GONE); } else { - mPlayPauseButtonView.setImageResource(R.drawable.ic_play_arrow_white); - mPlayPauseButtonView.setText(R.string.pip_play); + mPlayPauseButtonView.setVisibility(View.VISIBLE); + if (state == PipManager.PLAYBACK_STATE_PLAYING) { + mPlayPauseButtonView.setImageResource(R.drawable.ic_pause_white); + mPlayPauseButtonView.setText(R.string.pip_pause); + } else { + mPlayPauseButtonView.setImageResource(R.drawable.ic_play_arrow_white); + mPlayPauseButtonView.setText(R.string.pip_play); + } + } + + // Hide all the custom action buttons + for (int i = 0; i < mCustomButtonViews.size(); i++) { + mCustomButtonViews.get(i).setVisibility(View.GONE); } } } @@ -203,6 +277,9 @@ public class PipControlsView extends LinearLayout { mCloseButtonView.reset(); mPlayPauseButtonView.reset(); mFullButtonView.requestFocus(); + for (int i = 0; i < mCustomButtonViews.size(); i++) { + mCustomButtonViews.get(i).reset(); + } } /** @@ -212,6 +289,15 @@ public class PipControlsView extends LinearLayout { mListener = listener; } + /** + * Updates the set of activity-defined actions. + */ + public void setActions(List actions) { + mCustomActions.clear(); + mCustomActions.addAll(actions); + updateUserActions(); + } + /** * Returns the focused control button view to animate focused button. */ diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java index f98310dd7aa1f..ca58080c02606 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java +++ b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java @@ -20,6 +20,7 @@ import android.app.ActivityManager; import android.app.ActivityManager.RunningTaskInfo; import android.app.ActivityManager.StackInfo; import android.app.IActivityManager; +import android.app.RemoteAction; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; @@ -124,6 +125,7 @@ public class PipManager implements BasePipManager { private MediaController mPipMediaController; private String[] mLastPackagesResourceGranted; private PipNotification mPipNotification; + private ParceledListSlice mCustomActions; private final PinnedStackListener mPinnedStackListener = new PinnedStackListener(); @@ -187,7 +189,14 @@ public class PipManager implements BasePipManager { } @Override - public void onActionsChanged(ParceledListSlice actions) {} + public void onActionsChanged(ParceledListSlice actions) { + mCustomActions = actions; + mHandler.post(() -> { + for (int i = mListeners.size() - 1; i >= 0; --i) { + mListeners.get(i).onPipMenuActionsChanged(mCustomActions); + } + }); + } } private PipManager() { } @@ -432,6 +441,7 @@ public class PipManager implements BasePipManager { } Intent intent = new Intent(mContext, PipMenuActivity.class); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.putExtra(PipMenuActivity.EXTRA_CUSTOM_ACTIONS, mCustomActions); mContext.startActivity(intent); } @@ -690,6 +700,8 @@ public class PipManager implements BasePipManager { void onPipActivityClosed(); /** Invoked when the PIP menu gets shown. */ void onShowPipMenu(); + /** Invoked when the PIP menu actions change. */ + void onPipMenuActionsChanged(ParceledListSlice actions); /** Invoked when the PIPed activity is about to return back to the fullscreen. */ void onMoveToFullscreen(); /** Invoked when we are above to start resizing the Pip. */ diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipMenuActivity.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipMenuActivity.java index ce1bea19ef609..82018ce9ddbef 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipMenuActivity.java +++ b/packages/SystemUI/src/com/android/systemui/pip/tv/PipMenuActivity.java @@ -19,22 +19,27 @@ package com.android.systemui.pip.tv; import android.animation.Animator; import android.animation.AnimatorInflater; import android.app.Activity; +import android.content.Intent; +import android.content.pm.ParceledListSlice; import android.os.Bundle; import android.view.View; import com.android.systemui.R; +import java.util.Collections; /** * Activity to show the PIP menu to control PIP. */ public class PipMenuActivity extends Activity implements PipManager.Listener { private static final String TAG = "PipMenuActivity"; + static final String EXTRA_CUSTOM_ACTIONS = "custom_actions"; + private final PipManager mPipManager = PipManager.getInstance(); private Animator mFadeInAnimation; private Animator mFadeOutAnimation; - private View mPipControlsView; + private PipControlsView mPipControlsView; private boolean mRestorePipSizeWhenClose; @Override @@ -51,6 +56,15 @@ public class PipMenuActivity extends Activity implements PipManager.Listener { mFadeOutAnimation = AnimatorInflater.loadAnimator( this, R.anim.tv_pip_menu_fade_out_animation); mFadeOutAnimation.setTarget(mPipControlsView); + + onPipMenuActionsChanged(getIntent().getParcelableExtra(EXTRA_CUSTOM_ACTIONS)); + } + + @Override + protected void onNewIntent(Intent intent) { + super.onNewIntent(intent); + + onPipMenuActionsChanged(getIntent().getParcelableExtra(EXTRA_CUSTOM_ACTIONS)); } private void restorePipAndFinish() { @@ -95,6 +109,12 @@ public class PipMenuActivity extends Activity implements PipManager.Listener { finish(); } + @Override + public void onPipMenuActionsChanged(ParceledListSlice actions) { + boolean hasCustomActions = actions != null && !actions.getList().isEmpty(); + mPipControlsView.setActions(hasCustomActions ? actions.getList() : Collections.EMPTY_LIST); + } + @Override public void onShowPipMenu() { } diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipNotification.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipNotification.java index c8f418554904e..f0745a0791ef8 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipNotification.java +++ b/packages/SystemUI/src/com/android/systemui/pip/tv/PipNotification.java @@ -23,6 +23,7 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.pm.ParceledListSlice; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.drawable.Icon; @@ -80,6 +81,11 @@ public class PipNotification { // no-op. } + @Override + public void onPipMenuActionsChanged(ParceledListSlice actions) { + // no-op. + } + @Override public void onMoveToFullscreen() { dismissPipNotification();