diff options
9 files changed, 206 insertions, 32 deletions
diff --git a/packages/SystemUI/res/layout/tv_pip_controls.xml b/packages/SystemUI/res/layout/tv_pip_controls.xml index 61ac6f6991ff..0b7bce13d761 100644 --- a/packages/SystemUI/res/layout/tv_pip_controls.xml +++ b/packages/SystemUI/res/layout/tv_pip_controls.xml @@ -22,24 +22,24 @@ <com.android.systemui.pip.tv.PipControlButtonView android:id="@+id/full_button" - android:layout_width="100dp" + android:layout_width="@dimen/picture_in_picture_button_width" android:layout_height="wrap_content" android:src="@drawable/ic_fullscreen_white_24dp" android:text="@string/pip_fullscreen" /> <com.android.systemui.pip.tv.PipControlButtonView android:id="@+id/close_button" - android:layout_width="100dp" + android:layout_width="@dimen/picture_in_picture_button_width" android:layout_height="wrap_content" - android:layout_marginStart="-50dp" + android:layout_marginStart="@dimen/picture_in_picture_button_start_margin" android:src="@drawable/ic_close_white" android:text="@string/pip_close" /> <com.android.systemui.pip.tv.PipControlButtonView android:id="@+id/play_pause_button" - android:layout_width="100dp" + android:layout_width="@dimen/picture_in_picture_button_width" android:layout_height="wrap_content" - android:layout_marginStart="-50dp" + android:layout_marginStart="@dimen/picture_in_picture_button_start_margin" android:src="@drawable/ic_pause_white" android:text="@string/pip_pause" android:visibility="gone" /> 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 000000000000..dd0fce466576 --- /dev/null +++ b/packages/SystemUI/res/layout/tv_pip_custom_control.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +** +** Copyright 2017, 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. +*/ +--> +<com.android.systemui.pip.tv.PipControlButtonView + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="@dimen/picture_in_picture_button_width" + android:layout_height="wrap_content" + android:layout_marginStart="@dimen/picture_in_picture_button_start_margin" /> diff --git a/packages/SystemUI/res/values-tvdpi/dimens.xml b/packages/SystemUI/res/values-tvdpi/dimens.xml index 5327cee7cae8..4d978aacc65f 100644 --- a/packages/SystemUI/res/values-tvdpi/dimens.xml +++ b/packages/SystemUI/res/values-tvdpi/dimens.xml @@ -24,4 +24,8 @@ <fraction name="battery_subpixel_smoothing_right">10%</fraction> <dimen name="battery_margin_bottom">1px</dimen> + + <!-- The dimensions to user for picture-in-picture action buttons. --> + <dimen name="picture_in_picture_button_width">100dp</dimen> + <dimen name="picture_in_picture_button_start_margin">-50dp</dimen> </resources> 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 0f69f471bc24..a5ee19824590 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 40a63d7d92c0..b21cd95626a7 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; @@ -122,18 +124,37 @@ public class PipControlButtonView extends RelativeLayout { } /** + * 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 acea3b6b12ad..10206d492e3a 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<PipControlButtonView> mCustomButtonViews = new ArrayList<>(); + private List<RemoteAction> 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(); + } } /** @@ -213,6 +290,15 @@ public class PipControlsView extends LinearLayout { } /** + * Updates the set of activity-defined actions. + */ + public void setActions(List<RemoteAction> actions) { + mCustomActions.clear(); + mCustomActions.addAll(actions); + updateUserActions(); + } + + /** * Returns the focused control button view to animate focused button. */ PipControlButtonView getFocusedButton() { 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 f98310dd7aa1..ca58080c0260 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 ce1bea19ef60..82018ce9ddbe 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() { @@ -96,6 +110,12 @@ public class PipMenuActivity extends Activity implements PipManager.Listener { } @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() { } @Override 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 c8f418554904..f0745a0791ef 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; @@ -81,6 +82,11 @@ public class PipNotification { } @Override + public void onPipMenuActionsChanged(ParceledListSlice actions) { + // no-op. + } + + @Override public void onMoveToFullscreen() { dismissPipNotification(); } |