Merge "Update TV to show custom actions." into oc-dev

am: 1a01d1298f

Change-Id: I1d23e5ba9e061a50bea052b2eb6cb7114e99ff63
This commit is contained in:
Winson Chung
2017-06-15 21:48:08 +00:00
committed by android-build-merger
9 changed files with 206 additions and 32 deletions

View File

@@ -22,24 +22,24 @@
<com.android.systemui.pip.tv.PipControlButtonView <com.android.systemui.pip.tv.PipControlButtonView
android:id="@+id/full_button" android:id="@+id/full_button"
android:layout_width="100dp" android:layout_width="@dimen/picture_in_picture_button_width"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:src="@drawable/ic_fullscreen_white_24dp" android:src="@drawable/ic_fullscreen_white_24dp"
android:text="@string/pip_fullscreen" /> android:text="@string/pip_fullscreen" />
<com.android.systemui.pip.tv.PipControlButtonView <com.android.systemui.pip.tv.PipControlButtonView
android:id="@+id/close_button" 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_height="wrap_content"
android:layout_marginStart="-50dp" android:layout_marginStart="@dimen/picture_in_picture_button_start_margin"
android:src="@drawable/ic_close_white" android:src="@drawable/ic_close_white"
android:text="@string/pip_close" /> android:text="@string/pip_close" />
<com.android.systemui.pip.tv.PipControlButtonView <com.android.systemui.pip.tv.PipControlButtonView
android:id="@+id/play_pause_button" 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_height="wrap_content"
android:layout_marginStart="-50dp" android:layout_marginStart="@dimen/picture_in_picture_button_start_margin"
android:src="@drawable/ic_pause_white" android:src="@drawable/ic_pause_white"
android:text="@string/pip_pause" android:text="@string/pip_pause"
android:visibility="gone" /> android:visibility="gone" />

View File

@@ -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" />

View File

@@ -24,4 +24,8 @@
<fraction name="battery_subpixel_smoothing_right">10%</fraction> <fraction name="battery_subpixel_smoothing_right">10%</fraction>
<dimen name="battery_margin_bottom">1px</dimen> <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> </resources>

View File

@@ -427,11 +427,7 @@ public class PipMenuActivity extends Activity {
} else { } else {
actionsContainer.setVisibility(View.VISIBLE); actionsContainer.setVisibility(View.VISIBLE);
if (mActionsGroup != null) { if (mActionsGroup != null) {
// Hide extra views // Ensure we have as many buttons as actions
for (int i = mActions.size(); i < mActionsGroup.getChildCount(); i++) {
mActionsGroup.getChildAt(i).setVisibility(View.GONE);
}
// Add needed views
final LayoutInflater inflater = LayoutInflater.from(this); final LayoutInflater inflater = LayoutInflater.from(this);
while (mActionsGroup.getChildCount() < mActions.size()) { while (mActionsGroup.getChildCount() < mActions.size()) {
final ImageView actionView = (ImageView) inflater.inflate( final ImageView actionView = (ImageView) inflater.inflate(
@@ -439,6 +435,13 @@ public class PipMenuActivity extends Activity {
mActionsGroup.addView(actionView); 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 // Recreate the layout
final boolean isLandscapePip = stackBounds != null && final boolean isLandscapePip = stackBounds != null &&
(stackBounds.width() > stackBounds.height()); (stackBounds.width() > stackBounds.height());
@@ -460,10 +463,9 @@ public class PipMenuActivity extends Activity {
Log.w(TAG, "Failed to send action", e); Log.w(TAG, "Failed to send action", e);
} }
}); });
} else {
actionView.setAlpha(DISABLED_ACTION_ALPHA);
} }
actionView.setEnabled(action.isEnabled()); actionView.setEnabled(action.isEnabled());
actionView.setAlpha(action.isEnabled() ? 1f : DISABLED_ACTION_ALPHA);
// Update the margin between actions // Update the margin between actions
LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)

View File

@@ -20,6 +20,7 @@ import android.animation.Animator;
import android.animation.AnimatorInflater; import android.animation.AnimatorInflater;
import android.content.Context; import android.content.Context;
import android.content.res.TypedArray; import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
@@ -33,6 +34,7 @@ import com.android.systemui.R;
* A view containing PIP controls including fullscreen, close, and media controls. * A view containing PIP controls including fullscreen, close, and media controls.
*/ */
public class PipControlButtonView extends RelativeLayout { public class PipControlButtonView extends RelativeLayout {
private OnFocusChangeListener mFocusChangeListener; private OnFocusChangeListener mFocusChangeListener;
private ImageView mIconImageView; private ImageView mIconImageView;
ImageView mButtonImageView; ImageView mButtonImageView;
@@ -121,19 +123,38 @@ public class PipControlButtonView extends RelativeLayout {
mFocusChangeListener = listener; 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. * Sets the drawable for the button with the given resource id.
*/ */
public void setImageResource(int resId) { 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. * Sets the text for description the with the given resource id.
*/ */
public void setText(int resId) { public void setText(int resId) {
mButtonImageView.setContentDescription(getContext().getString(resId)); if (resId != 0) {
mDescriptionTextView.setText(resId); mButtonImageView.setContentDescription(getContext().getString(resId));
mDescriptionTextView.setText(resId);
}
} }
private static void cancelAnimator(Animator animator) { private static void cancelAnimator(Animator animator) {

View File

@@ -16,12 +16,20 @@
package com.android.systemui.pip.tv; 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.content.Context;
import android.graphics.Color;
import android.media.session.MediaController; import android.media.session.MediaController;
import android.media.session.PlaybackState; import android.media.session.PlaybackState;
import android.os.Handler;
import android.os.RemoteException;
import android.util.Log;
import android.view.View; import android.view.View;
import android.view.Gravity; import android.view.Gravity;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.widget.ImageView;
import android.widget.LinearLayout; import android.widget.LinearLayout;
import android.util.AttributeSet; 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_PAUSE;
import static android.media.session.PlaybackState.ACTION_PLAY; 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. * A view containing PIP controls including fullscreen, close, and media controls.
*/ */
public class PipControlsView extends LinearLayout { 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. * An interface to listen user action.
*/ */
@@ -47,19 +63,23 @@ public class PipControlsView extends LinearLayout {
private MediaController mMediaController; private MediaController mMediaController;
final PipManager mPipManager = PipManager.getInstance(); private final PipManager mPipManager = PipManager.getInstance();
Listener mListener; private final LayoutInflater mLayoutInflater;
private final Handler mHandler;
private Listener mListener;
private PipControlButtonView mFullButtonView; private PipControlButtonView mFullButtonView;
private PipControlButtonView mCloseButtonView; private PipControlButtonView mCloseButtonView;
private PipControlButtonView mPlayPauseButtonView; private PipControlButtonView mPlayPauseButtonView;
private ArrayList<PipControlButtonView> mCustomButtonViews = new ArrayList<>();
private List<RemoteAction> mCustomActions = new ArrayList<>();
private PipControlButtonView mFocusedChild; private PipControlButtonView mFocusedChild;
private MediaController.Callback mMediaControllerCallback = new MediaController.Callback() { private MediaController.Callback mMediaControllerCallback = new MediaController.Callback() {
@Override @Override
public void onPlaybackStateChanged(PlaybackState state) { 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) { public PipControlsView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes); super(context, attrs, defStyleAttr, defStyleRes);
LayoutInflater inflater = (LayoutInflater) getContext() mLayoutInflater = (LayoutInflater) getContext()
.getSystemService(Context.LAYOUT_INFLATER_SERVICE); .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); setOrientation(LinearLayout.HORIZONTAL);
setGravity(Gravity.TOP | Gravity.CENTER_HORIZONTAL); setGravity(Gravity.TOP | Gravity.CENTER_HORIZONTAL);
@@ -176,21 +197,74 @@ public class PipControlsView extends LinearLayout {
if (mMediaController != null) { if (mMediaController != null) {
mMediaController.registerCallback(mMediaControllerCallback); mMediaController.registerCallback(mMediaControllerCallback);
} }
updatePlayPauseView(); updateUserActions();
} }
private void updatePlayPauseView() { /**
int state = mPipManager.getPlaybackState(); * Updates the actions for the PIP. If there are no custom actions, then the media session
if (state == PipManager.PLAYBACK_STATE_UNAVAILABLE) { * 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); mPlayPauseButtonView.setVisibility(View.GONE);
} else { } else {
mPlayPauseButtonView.setVisibility(View.VISIBLE); int state = mPipManager.getPlaybackState();
if (state == PipManager.PLAYBACK_STATE_PLAYING) { if (state == PipManager.PLAYBACK_STATE_UNAVAILABLE) {
mPlayPauseButtonView.setImageResource(R.drawable.ic_pause_white); mPlayPauseButtonView.setVisibility(View.GONE);
mPlayPauseButtonView.setText(R.string.pip_pause);
} else { } else {
mPlayPauseButtonView.setImageResource(R.drawable.ic_play_arrow_white); mPlayPauseButtonView.setVisibility(View.VISIBLE);
mPlayPauseButtonView.setText(R.string.pip_play); 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(); mCloseButtonView.reset();
mPlayPauseButtonView.reset(); mPlayPauseButtonView.reset();
mFullButtonView.requestFocus(); 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; mListener = listener;
} }
/**
* 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. * Returns the focused control button view to animate focused button.
*/ */

View File

@@ -20,6 +20,7 @@ import android.app.ActivityManager;
import android.app.ActivityManager.RunningTaskInfo; import android.app.ActivityManager.RunningTaskInfo;
import android.app.ActivityManager.StackInfo; import android.app.ActivityManager.StackInfo;
import android.app.IActivityManager; import android.app.IActivityManager;
import android.app.RemoteAction;
import android.content.BroadcastReceiver; import android.content.BroadcastReceiver;
import android.content.ComponentName; import android.content.ComponentName;
import android.content.Context; import android.content.Context;
@@ -124,6 +125,7 @@ public class PipManager implements BasePipManager {
private MediaController mPipMediaController; private MediaController mPipMediaController;
private String[] mLastPackagesResourceGranted; private String[] mLastPackagesResourceGranted;
private PipNotification mPipNotification; private PipNotification mPipNotification;
private ParceledListSlice mCustomActions;
private final PinnedStackListener mPinnedStackListener = new PinnedStackListener(); private final PinnedStackListener mPinnedStackListener = new PinnedStackListener();
@@ -187,7 +189,14 @@ public class PipManager implements BasePipManager {
} }
@Override @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() { } private PipManager() { }
@@ -432,6 +441,7 @@ public class PipManager implements BasePipManager {
} }
Intent intent = new Intent(mContext, PipMenuActivity.class); Intent intent = new Intent(mContext, PipMenuActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.putExtra(PipMenuActivity.EXTRA_CUSTOM_ACTIONS, mCustomActions);
mContext.startActivity(intent); mContext.startActivity(intent);
} }
@@ -690,6 +700,8 @@ public class PipManager implements BasePipManager {
void onPipActivityClosed(); void onPipActivityClosed();
/** Invoked when the PIP menu gets shown. */ /** Invoked when the PIP menu gets shown. */
void onShowPipMenu(); 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. */ /** Invoked when the PIPed activity is about to return back to the fullscreen. */
void onMoveToFullscreen(); void onMoveToFullscreen();
/** Invoked when we are above to start resizing the Pip. */ /** Invoked when we are above to start resizing the Pip. */

View File

@@ -19,22 +19,27 @@ package com.android.systemui.pip.tv;
import android.animation.Animator; import android.animation.Animator;
import android.animation.AnimatorInflater; import android.animation.AnimatorInflater;
import android.app.Activity; import android.app.Activity;
import android.content.Intent;
import android.content.pm.ParceledListSlice;
import android.os.Bundle; import android.os.Bundle;
import android.view.View; import android.view.View;
import com.android.systemui.R; import com.android.systemui.R;
import java.util.Collections;
/** /**
* Activity to show the PIP menu to control PIP. * Activity to show the PIP menu to control PIP.
*/ */
public class PipMenuActivity extends Activity implements PipManager.Listener { public class PipMenuActivity extends Activity implements PipManager.Listener {
private static final String TAG = "PipMenuActivity"; private static final String TAG = "PipMenuActivity";
static final String EXTRA_CUSTOM_ACTIONS = "custom_actions";
private final PipManager mPipManager = PipManager.getInstance(); private final PipManager mPipManager = PipManager.getInstance();
private Animator mFadeInAnimation; private Animator mFadeInAnimation;
private Animator mFadeOutAnimation; private Animator mFadeOutAnimation;
private View mPipControlsView; private PipControlsView mPipControlsView;
private boolean mRestorePipSizeWhenClose; private boolean mRestorePipSizeWhenClose;
@Override @Override
@@ -51,6 +56,15 @@ public class PipMenuActivity extends Activity implements PipManager.Listener {
mFadeOutAnimation = AnimatorInflater.loadAnimator( mFadeOutAnimation = AnimatorInflater.loadAnimator(
this, R.anim.tv_pip_menu_fade_out_animation); this, R.anim.tv_pip_menu_fade_out_animation);
mFadeOutAnimation.setTarget(mPipControlsView); 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() { private void restorePipAndFinish() {
@@ -95,6 +109,12 @@ public class PipMenuActivity extends Activity implements PipManager.Listener {
finish(); finish();
} }
@Override
public void onPipMenuActionsChanged(ParceledListSlice actions) {
boolean hasCustomActions = actions != null && !actions.getList().isEmpty();
mPipControlsView.setActions(hasCustomActions ? actions.getList() : Collections.EMPTY_LIST);
}
@Override @Override
public void onShowPipMenu() { } public void onShowPipMenu() { }

View File

@@ -23,6 +23,7 @@ import android.content.BroadcastReceiver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.content.pm.ParceledListSlice;
import android.content.res.Resources; import android.content.res.Resources;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.drawable.Icon; import android.graphics.drawable.Icon;
@@ -80,6 +81,11 @@ public class PipNotification {
// no-op. // no-op.
} }
@Override
public void onPipMenuActionsChanged(ParceledListSlice actions) {
// no-op.
}
@Override @Override
public void onMoveToFullscreen() { public void onMoveToFullscreen() {
dismissPipNotification(); dismissPipNotification();