blob: 05a3adb750961ba3cb65311379401f9c82b7cc97 [file] [log] [blame]
/*
* Copyright (C) 2013 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 android.support.v7.app;
import static android.widget.SeekBar.OnSeekBarChangeListener;
import android.content.ContentResolver;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.RemoteException;
import android.support.v4.media.MediaDescriptionCompat;
import android.support.v4.media.MediaMetadataCompat;
import android.support.v4.media.session.MediaControllerCompat;
import android.support.v4.media.session.MediaSessionCompat;
import android.support.v4.media.session.PlaybackStateCompat;
import android.support.v7.graphics.Palette;
import android.support.v7.media.MediaRouteSelector;
import android.support.v7.media.MediaRouter;
import android.support.v7.media.SeekBarJellybean;
import android.support.v7.mediarouter.R;
import android.text.TextUtils;
import android.util.Log;
import android.util.TypedValue;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.MeasureSpec;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.RelativeLayout;
import android.widget.SeekBar;
import android.widget.TextView;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.util.List;
/**
* This class implements the route controller dialog for {@link MediaRouter}.
* <p>
* This dialog allows the user to control or disconnect from the currently selected route.
* </p>
*
* @see MediaRouteButton
* @see MediaRouteActionProvider
*/
public class MediaRouteControllerDialog extends AlertDialog {
private static final String TAG = "MediaRouteControllerDialog";
// STOPSHIP: Remove the flag when the group volume control implementation completes.
private static final boolean USE_GROUP = false;
// Time to wait before updating the volume when the user lets go of the seek bar
// to allow the route provider time to propagate the change and publish a new
// route descriptor.
private static final int VOLUME_UPDATE_DELAY_MILLIS = 250;
private static final int BUTTON_NEUTRAL_RES_ID = android.R.id.button3;
private static final int BUTTON_DISCONNECT_RES_ID = android.R.id.button2;
private static final int BUTTON_STOP_RES_ID = android.R.id.button1;
private final MediaRouter mRouter;
private final MediaRouterCallback mCallback;
private final MediaRouter.RouteInfo mRoute;
private boolean mCreated;
private boolean mAttachedToWindow;
private int mDialogContentWidth;
private View mCustomControlView;
private Button mDisconnectButton;
private Button mStopCastingButton;
private ImageButton mPlayPauseButton;
private ImageButton mCloseButton;
private ImageButton mGroupExpandCollapseButton;
private FrameLayout mCustomControlLayout;
private FrameLayout mDefaultControlLayout;
private ImageView mArtView;
private TextView mTitleView;
private TextView mSubtitleView;
private TextView mRouteNameTextView;
private boolean mVolumeControlEnabled = true;
// Layout for media controllers including play/pause button, volume slider,
// and group volume sliders.
private LinearLayout mMediaControlLayout;
// Layout for media controllers including play/pause button and volume slider.
private LinearLayout mMediaMainControlLayout;
private RelativeLayout mPlaybackControl;
private LinearLayout mVolumeControl;
private View mDividerView;
private ListView mVolumeGroupList;
private SeekBar mVolumeSlider;
private boolean mVolumeSliderTouched;
private int mVolumeSliderColor;
private final int mVolumeGroupListItemHeight;
private final int mVolumeGroupListMaxHeight;
private final int mVolumeGroupListPaddingBottom;
private MediaControllerCompat mMediaController;
private MediaControllerCallback mControllerCallback;
private PlaybackStateCompat mState;
private MediaDescriptionCompat mDescription;
private FetchArtTask mFetchArtTask;
private Bitmap mArtIconBitmap;
private Uri mArtIconUri;
private boolean mIsGroupExpanded;
public MediaRouteControllerDialog(Context context) {
this(context, 0);
}
public MediaRouteControllerDialog(Context context, int theme) {
super(MediaRouterThemeHelper.createThemedContext(context), theme);
context = getContext();
mControllerCallback = new MediaControllerCallback();
mRouter = MediaRouter.getInstance(context);
mCallback = new MediaRouterCallback();
mRoute = mRouter.getSelectedRoute();
setMediaSession(mRouter.getMediaSessionToken());
mVolumeGroupListItemHeight = context.getResources().getDimensionPixelSize(
R.dimen.mr_controller_volume_group_list_item_height);
mVolumeGroupListMaxHeight = context.getResources().getDimensionPixelSize(
R.dimen.mr_controller_volume_group_list_max_height);
mVolumeGroupListPaddingBottom = context.getResources().getDimensionPixelSize(
R.dimen.mr_controller_volume_group_list_padding_bottom);
}
/**
* Gets the route that this dialog is controlling.
*/
public MediaRouter.RouteInfo getRoute() {
return mRoute;
}
private MediaRouter.RouteGroup getGroup() {
if (mRoute instanceof MediaRouter.RouteGroup) {
return (MediaRouter.RouteGroup) mRoute;
}
return null;
}
/**
* Provides the subclass an opportunity to create a view that will
* be included within the body of the dialog to offer additional media controls
* for the currently playing content.
*
* @param savedInstanceState The dialog's saved instance state.
* @return The media control view, or null if none.
*/
public View onCreateMediaControlView(Bundle savedInstanceState) {
return null;
}
/**
* Gets the media control view that was created by {@link #onCreateMediaControlView(Bundle)}.
*
* @return The media control view, or null if none.
*/
public View getMediaControlView() {
return mCustomControlView;
}
/**
* Sets whether to enable the volume slider and volume control using the volume keys
* when the route supports it.
* <p>
* The default value is true.
* </p>
*/
public void setVolumeControlEnabled(boolean enable) {
if (mVolumeControlEnabled != enable) {
mVolumeControlEnabled = enable;
if (mCreated) {
updateVolumeControl();
}
}
}
/**
* Returns whether to enable the volume slider and volume control using the volume keys
* when the route supports it.
*/
public boolean isVolumeControlEnabled() {
return mVolumeControlEnabled;
}
/**
* Set the session to use for metadata and transport controls. The dialog
* will listen to changes on this session and update the UI automatically in
* response to changes.
*
* @param sessionToken The token for the session to use.
*/
private void setMediaSession(MediaSessionCompat.Token sessionToken) {
if (mMediaController != null) {
mMediaController.unregisterCallback(mControllerCallback);
mMediaController = null;
}
if (sessionToken == null) {
return;
}
if (!mAttachedToWindow) {
return;
}
try {
mMediaController = new MediaControllerCompat(getContext(), sessionToken);
} catch (RemoteException e) {
Log.e(TAG, "Error creating media controller in setMediaSession.", e);
}
if (mMediaController != null) {
mMediaController.registerCallback(mControllerCallback);
}
MediaMetadataCompat metadata = mMediaController == null ? null
: mMediaController.getMetadata();
mDescription = metadata == null ? null : metadata.getDescription();
mState = mMediaController == null ? null : mMediaController.getPlaybackState();
update();
}
/**
* Gets the description being used by the default UI.
*
* @return The current description.
*/
public MediaSessionCompat.Token getMediaSession() {
return mMediaController == null ? null : mMediaController.getSessionToken();
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.mr_controller_material_dialog_b);
// Remove the neutral button.
findViewById(BUTTON_NEUTRAL_RES_ID).setVisibility(View.GONE);
ClickListener listener = new ClickListener();
mDisconnectButton = (Button) findViewById(BUTTON_DISCONNECT_RES_ID);
mDisconnectButton.setText(R.string.mr_controller_disconnect);
mDisconnectButton.setOnClickListener(listener);
mStopCastingButton = (Button) findViewById(BUTTON_STOP_RES_ID);
mStopCastingButton.setText(R.string.mr_controller_stop);
mStopCastingButton.setOnClickListener(listener);
TypedValue value = new TypedValue();
if (getContext().getTheme().resolveAttribute(R.attr.colorPrimary, value, true)) {
mDisconnectButton.setTextColor(value.data);
mStopCastingButton.setTextColor(value.data);
}
mRouteNameTextView = (TextView) findViewById(R.id.mr_name);
mCloseButton = (ImageButton) findViewById(R.id.mr_close);
mCloseButton.setOnClickListener(listener);
mCustomControlLayout = (FrameLayout) findViewById(R.id.mr_custom_control);
mDefaultControlLayout = (FrameLayout) findViewById(R.id.mr_default_control);
mArtView = (ImageView) findViewById(R.id.mr_art);
mMediaControlLayout = (LinearLayout) findViewById(R.id.mr_media_control);
mMediaMainControlLayout = (LinearLayout) findViewById(R.id.mr_media_main_control);
mDividerView = findViewById(R.id.mr_control_divider);
mPlaybackControl = (RelativeLayout) findViewById(R.id.mr_playback_control);
mTitleView = (TextView) findViewById(R.id.mr_control_title);
mSubtitleView = (TextView) findViewById(R.id.mr_control_subtitle);
mPlayPauseButton = (ImageButton) findViewById(R.id.mr_control_play_pause);
mPlayPauseButton.setOnClickListener(listener);
mVolumeControl = (LinearLayout) findViewById(R.id.mr_volume_control);
mVolumeSlider = (SeekBar) findViewById(R.id.mr_volume_slider);
mVolumeSlider.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
private final Runnable mStopTrackingTouch = new Runnable() {
@Override
public void run() {
if (mVolumeSliderTouched) {
mVolumeSliderTouched = false;
updateVolumeControl();
}
}
};
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
if (mVolumeSliderTouched) {
mVolumeSlider.removeCallbacks(mStopTrackingTouch);
} else {
mVolumeSliderTouched = true;
}
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
// Defer resetting mVolumeSliderTouched to allow the media route provider
// a little time to settle into its new state and publish the final
// volume update.
mVolumeSlider.postDelayed(mStopTrackingTouch, VOLUME_UPDATE_DELAY_MILLIS);
}
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
if (fromUser) {
mRoute.requestSetVolume(progress);
}
}
});
mVolumeSliderColor = MediaRouterThemeHelper.getVolumeSliderColor(getContext());
setVolumeSliderColor(getContext(), mVolumeSlider, mVolumeSliderColor);
TypedArray styledAttributes = getContext().obtainStyledAttributes(new int[] {
R.attr.mediaRouteExpandGroupDrawable,
R.attr.mediaRouteCollapseGroupDrawable
});
final Drawable expandGroupDrawable = styledAttributes.getDrawable(0);
final Drawable collapseGroupDrawable = styledAttributes.getDrawable(1);
styledAttributes.recycle();
mVolumeGroupList = (ListView)findViewById(R.id.mr_volume_group_list);
mGroupExpandCollapseButton = (ImageButton) findViewById(R.id.mr_group_expand_collapse);
mGroupExpandCollapseButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mIsGroupExpanded = !mIsGroupExpanded;
if (mIsGroupExpanded) {
mGroupExpandCollapseButton.setImageDrawable(collapseGroupDrawable);
mVolumeGroupList.setVisibility(View.VISIBLE);
mVolumeGroupList.setAdapter(
new VolumeGroupAdapter(getContext(), getGroup().getRoutes()));
} else {
mGroupExpandCollapseButton.setImageDrawable(expandGroupDrawable);
mVolumeGroupList.setVisibility(View.GONE);
}
updateLayoutHeight();
}
});
mCustomControlView = onCreateMediaControlView(savedInstanceState);
if (mCustomControlView != null) {
mCustomControlLayout.addView(mCustomControlView);
mCustomControlLayout.setVisibility(View.VISIBLE);
mArtView.setVisibility(View.GONE);
}
mCreated = true;
updateLayout();
}
/**
* Sets the width of the dialog. Also called when configuration changes.
*/
void updateLayout() {
int width = MediaRouteDialogHelper.getDialogWidth(getContext());
getWindow().setLayout(width, ViewGroup.LayoutParams.WRAP_CONTENT);
View decorView = getWindow().getDecorView();
mDialogContentWidth = width - decorView.getPaddingLeft() - decorView.getPaddingRight();
// Ensure the mArtView is updated.
mArtIconBitmap = null;
mArtIconUri = null;
update();
}
@Override
public void onAttachedToWindow() {
super.onAttachedToWindow();
mAttachedToWindow = true;
mRouter.addCallback(MediaRouteSelector.EMPTY, mCallback,
MediaRouter.CALLBACK_FLAG_UNFILTERED_EVENTS);
setMediaSession(mRouter.getMediaSessionToken());
}
@Override
public void onDetachedFromWindow() {
mRouter.removeCallback(mCallback);
setMediaSession(null);
mAttachedToWindow = false;
super.onDetachedFromWindow();
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN
|| keyCode == KeyEvent.KEYCODE_VOLUME_UP) {
mRoute.requestUpdateVolume(keyCode == KeyEvent.KEYCODE_VOLUME_DOWN ? -1 : 1);
return true;
}
return super.onKeyDown(keyCode, event);
}
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN
|| keyCode == KeyEvent.KEYCODE_VOLUME_UP) {
return true;
}
return super.onKeyUp(keyCode, event);
}
private void update() {
if (!mRoute.isSelected() || mRoute.isDefault()) {
dismiss();
return;
}
if (!mCreated) {
return;
}
mRouteNameTextView.setText(mRoute.getName());
mDisconnectButton.setVisibility(mRoute.canDisconnect() ? View.VISIBLE : View.GONE);
if (mCustomControlView == null) {
if (mFetchArtTask != null) {
mFetchArtTask.cancel(true);
}
mFetchArtTask = new FetchArtTask();
mFetchArtTask.execute();
}
updateVolumeControl();
updatePlaybackControl();
}
private boolean isPlaybackControlAvailable() {
return mCustomControlView == null && (mDescription != null || mState != null);
}
/**
* Returns the height of main media controller which includes playback control and master
* volume control.
*/
private int getMainControllerHeight(boolean showPlaybackControl) {
int height = 0;
if (showPlaybackControl || mVolumeControl.getVisibility() == View.VISIBLE) {
height += mMediaMainControlLayout.getPaddingTop()
+ mMediaMainControlLayout.getPaddingBottom();
if (showPlaybackControl) {
height += mPlaybackControl.getMeasuredHeight();
}
if (mVolumeControl.getVisibility() == View.VISIBLE) {
height += mVolumeControl.getMeasuredHeight();
}
if (showPlaybackControl && mVolumeControl.getVisibility() == View.VISIBLE) {
height += mDividerView.getMeasuredHeight();
}
}
return height;
}
/**
* Updates the height of views and hide artwork or metadata if space is limited.
*/
private void updateLayoutHeight() {
if (mCustomControlView != null) {
return;
}
View decorView = getWindow().getDecorView();
decorView.measure(
MeasureSpec.makeMeasureSpec(getWindow().getAttributes().width, MeasureSpec.EXACTLY),
MeasureSpec.UNSPECIFIED);
int artViewHeight = 0;
if (mArtView.getDrawable() instanceof BitmapDrawable) {
Bitmap art = ((BitmapDrawable) mArtView.getDrawable()).getBitmap();
if (art != null) {
artViewHeight = getDesiredArtHeight(art.getWidth(), art.getHeight());
mArtView.setScaleType(art.getWidth() >= art.getHeight()
? ImageView.ScaleType.FIT_XY : ImageView.ScaleType.FIT_CENTER);
}
}
int mainControllerHeight = getMainControllerHeight(isPlaybackControlAvailable());
int volumeGroupListCount = mVolumeGroupList.getVisibility() == View.VISIBLE
? mVolumeGroupList.getAdapter().getCount() : 0;
int volumeGroupHeight = mVolumeGroupListItemHeight * volumeGroupListCount
+ mVolumeGroupListPaddingBottom;
volumeGroupHeight = Math.min(volumeGroupHeight, mVolumeGroupListMaxHeight);
int desiredControlLayoutHeight =
Math.max(artViewHeight, volumeGroupHeight) + mainControllerHeight;
Rect visibleRect = new Rect();
decorView.getWindowVisibleDisplayFrame(visibleRect);
// Height of non-control views in decor view.
// This includes title bar, button bar, and dialog's vertical padding which should be
// always shown.
int nonControlViewHeight = decorView.getMeasuredHeight()
- mDefaultControlLayout.getMeasuredHeight();
// Maximum allowed height for controls to fit screen.
int maximumControlViewHeight = visibleRect.height() - nonControlViewHeight;
// Show artwork if it fits the screen and expanded volume group list has fewer than 3 items.
if (artViewHeight > 0 && volumeGroupListCount < 3
&& desiredControlLayoutHeight <= maximumControlViewHeight) {
mArtView.setVisibility(View.VISIBLE);
setLayoutHeight(mArtView, artViewHeight);
} else {
mArtView.setVisibility(View.GONE);
artViewHeight = 0;
desiredControlLayoutHeight = volumeGroupHeight + mainControllerHeight;
}
// Show control if it fits the screen
if (isPlaybackControlAvailable()
&& desiredControlLayoutHeight <= maximumControlViewHeight) {
mPlaybackControl.setVisibility(View.VISIBLE);
} else {
mPlaybackControl.setVisibility(View.GONE);
}
// TODO: Update the top and bottom padding of the control layout according to the display
// height.
mDividerView.setVisibility((mVolumeControl.getVisibility() == View.VISIBLE
&& mPlaybackControl.getVisibility() == View.VISIBLE)
? View.VISIBLE : View.GONE);
mMediaControlLayout.setVisibility((mVolumeControl.getVisibility() == View.GONE
&& mPlaybackControl.getVisibility() == View.GONE)
? View.GONE : View.VISIBLE);
mainControllerHeight = getMainControllerHeight(
mPlaybackControl.getVisibility() == View.VISIBLE);
desiredControlLayoutHeight =
Math.max(artViewHeight, volumeGroupHeight) + mainControllerHeight;
// Limit the volume group list height to fit the screen.
if (desiredControlLayoutHeight > maximumControlViewHeight) {
volumeGroupHeight -= (desiredControlLayoutHeight - maximumControlViewHeight);
desiredControlLayoutHeight = maximumControlViewHeight;
}
setLayoutHeight(mVolumeGroupList, volumeGroupHeight);
setLayoutHeight(mDefaultControlLayout, desiredControlLayoutHeight);
}
private void updateVolumeControl() {
if (!mVolumeSliderTouched) {
if (isVolumeControlAvailable()) {
mVolumeControl.setVisibility(View.VISIBLE);
mVolumeSlider.setMax(mRoute.getVolumeMax());
mVolumeSlider.setProgress(mRoute.getVolume());
if (USE_GROUP) {
if (getGroup() == null) {
mGroupExpandCollapseButton.setVisibility(View.GONE);
} else {
mGroupExpandCollapseButton.setVisibility(View.VISIBLE);
VolumeGroupAdapter adapter =
(VolumeGroupAdapter) mVolumeGroupList.getAdapter();
if (adapter != null) {
adapter.notifyDataSetChanged();
}
}
}
} else {
mVolumeControl.setVisibility(View.GONE);
}
updateLayoutHeight();
}
}
private void updatePlaybackControl() {
if (isPlaybackControlAvailable()) {
mPlaybackControl.setVisibility(View.VISIBLE);
CharSequence title = mDescription == null ? null : mDescription.getTitle();
boolean hasTitle = !TextUtils.isEmpty(title);
CharSequence subtitle = mDescription == null ? null : mDescription.getSubtitle();
boolean hasSubtitle = !TextUtils.isEmpty(subtitle);
if (!hasTitle && !hasSubtitle) {
if (mRoute.getPresentationDisplayId()
!= MediaRouter.RouteInfo.PRESENTATION_DISPLAY_ID_NONE) {
// The user is currently casting screen.
mTitleView.setText(R.string.mr_controller_casting_screen);
} else {
mTitleView.setText((mState == null
|| mState.getState() == PlaybackStateCompat.STATE_NONE)
? R.string.mr_controller_no_media_selected
: R.string.mr_controller_no_info_available);
}
mTitleView.setVisibility(View.VISIBLE);
mSubtitleView.setVisibility(View.GONE);
} else {
mTitleView.setText(title);
mTitleView.setVisibility(hasTitle ? View.VISIBLE : View.GONE);
mSubtitleView.setText(subtitle);
mSubtitleView.setVisibility(hasSubtitle ? View.VISIBLE : View.GONE);
}
if (mState != null) {
boolean isPlaying = mState.getState() == PlaybackStateCompat.STATE_BUFFERING
|| mState.getState() == PlaybackStateCompat.STATE_PLAYING;
boolean supportsPlay = (mState.getActions() & (PlaybackStateCompat.ACTION_PLAY
| PlaybackStateCompat.ACTION_PLAY_PAUSE)) != 0;
boolean supportsPause = (mState.getActions() & (PlaybackStateCompat.ACTION_PAUSE
| PlaybackStateCompat.ACTION_PLAY_PAUSE)) != 0;
if (isPlaying && supportsPause) {
mPlayPauseButton.setVisibility(View.VISIBLE);
mPlayPauseButton.setImageResource(MediaRouterThemeHelper.getThemeResource(
getContext(), R.attr.mediaRoutePauseDrawable));
mPlayPauseButton.setContentDescription(getContext().getResources()
.getText(R.string.mr_controller_pause));
} else if (!isPlaying && supportsPlay) {
mPlayPauseButton.setVisibility(View.VISIBLE);
mPlayPauseButton.setImageResource(MediaRouterThemeHelper.getThemeResource(
getContext(), R.attr.mediaRoutePlayDrawable));
mPlayPauseButton.setContentDescription(getContext().getResources()
.getText(R.string.mr_controller_play));
} else {
mPlayPauseButton.setVisibility(View.GONE);
}
}
} else {
mPlaybackControl.setVisibility(View.GONE);
}
updateLayoutHeight();
}
private boolean isVolumeControlAvailable() {
return mVolumeControlEnabled && mRoute.getVolumeHandling() ==
MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE;
}
private static void setLayoutHeight(View view, int height) {
ViewGroup.LayoutParams lp = view.getLayoutParams();
lp.height = height;
view.setLayoutParams(lp);
}
private static void setVolumeSliderColor(Context context, SeekBar volumeSlider, int color) {
volumeSlider.getProgressDrawable().setColorFilter(color, PorterDuff.Mode.SRC_IN);
if (Build.VERSION.SDK_INT >= 16) {
SeekBarJellybean.getThumb(volumeSlider).setColorFilter(color, PorterDuff.Mode.SRC_IN);
} else {
// In case getThumb() isn't available, use the thumb drawable from AppCompat.
Drawable thumb =
context.getResources().getDrawable(R.drawable.abc_seekbar_thumb_material);
thumb.setColorFilter(color, PorterDuff.Mode.SRC_IN);
volumeSlider.setThumb(thumb);
}
}
/**
* Returns desired art height to fit into controller dialog.
*/
private int getDesiredArtHeight(int originalWidth, int originalHeight) {
if (originalWidth >= originalHeight) {
// For landscape art, fit width to dialog width.
return (int) ((float) mDialogContentWidth * originalHeight / originalWidth + 0.5f);
}
// For portrait art, fit height to 16:9 ratio case's height.
return (int) ((float) mDialogContentWidth * 9 / 16 + 0.5f);
}
private final class MediaRouterCallback extends MediaRouter.Callback {
@Override
public void onRouteUnselected(MediaRouter router, MediaRouter.RouteInfo route) {
update();
}
@Override
public void onRouteChanged(MediaRouter router, MediaRouter.RouteInfo route) {
update();
}
@Override
public void onRouteVolumeChanged(MediaRouter router, MediaRouter.RouteInfo route) {
if (route == mRoute) {
updateVolumeControl();
}
}
}
private final class MediaControllerCallback extends MediaControllerCompat.Callback {
@Override
public void onSessionDestroyed() {
if (mMediaController != null) {
mMediaController.unregisterCallback(mControllerCallback);
mMediaController = null;
}
}
@Override
public void onPlaybackStateChanged(PlaybackStateCompat state) {
mState = state;
update();
}
@Override
public void onMetadataChanged(MediaMetadataCompat metadata) {
mDescription = metadata == null ? null : metadata.getDescription();
update();
}
}
private final class ClickListener implements View.OnClickListener {
@Override
public void onClick(View v) {
int id = v.getId();
if (id == BUTTON_STOP_RES_ID || id == BUTTON_DISCONNECT_RES_ID) {
if (mRoute.isSelected()) {
mRouter.unselect(id == BUTTON_STOP_RES_ID ?
MediaRouter.UNSELECT_REASON_STOPPED :
MediaRouter.UNSELECT_REASON_DISCONNECTED);
}
dismiss();
} else if (id == R.id.mr_control_play_pause) {
if (mMediaController != null && mState != null) {
if (mState.getState() == PlaybackStateCompat.STATE_PLAYING) {
mMediaController.getTransportControls().pause();
} else {
mMediaController.getTransportControls().play();
}
}
} else if (id == R.id.mr_close) {
dismiss();
}
}
}
private class VolumeGroupAdapter extends ArrayAdapter<MediaRouter.RouteInfo> {
final static float DISABLED_ALPHA = .3f;
final OnSeekBarChangeListener mOnSeekBarChangeListener = new OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
if (fromUser) {
int position = (int) seekBar.getTag();
getGroup().getRouteAt(position).requestSetVolume(progress);
}
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
// TODO: Implement
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
// TODO: Implement
}
};
public VolumeGroupAdapter(Context context, List<MediaRouter.RouteInfo> objects) {
super(context, 0, objects);
}
@Override
public View getView(final int position, View convertView, ViewGroup parent) {
View v = convertView;
if (v == null) {
v = LayoutInflater.from(getContext()).inflate(
R.layout.mr_controller_volume_item, parent, false);
setVolumeSliderColor(getContext(), (SeekBar) v.findViewById(R.id.mr_volume_slider),
mVolumeSliderColor);
}
setViewPaddingBottom(v, position == getCount() - 1 ? mVolumeGroupListPaddingBottom : 0);
MediaRouter.RouteInfo route = getItem(position);
if (route != null) {
boolean isEnabled = route.isEnabled();
TextView routeName = (TextView) v.findViewById(R.id.mr_name);
routeName.setEnabled(isEnabled);
routeName.setText(route.getName());
SeekBar volumeSlider = (SeekBar) v.findViewById(R.id.mr_volume_slider);
volumeSlider.setEnabled(isEnabled);
volumeSlider.setTag(position);
if (isEnabled) {
if (route.getVolumeHandling()
== MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE) {
volumeSlider.setMax(route.getVolumeMax());
volumeSlider.setProgress(route.getVolume());
volumeSlider.setOnSeekBarChangeListener(mOnSeekBarChangeListener);
} else {
volumeSlider.setMax(100);
volumeSlider.setProgress(100);
volumeSlider.setEnabled(false);
}
}
if (Build.VERSION.SDK_INT >= 16) {
SeekBarJellybean.getThumb(volumeSlider).mutate().setAlpha(isEnabled ? 255 : 0);
// TODO: Still see an artifact even though the thumb is transparent. Remove it.
}
ImageView volumeItemIcon =
(ImageView) v.findViewById(R.id.mr_volume_item_icon);
volumeItemIcon.setAlpha(isEnabled ? 255 : (int) (255 * DISABLED_ALPHA));
}
return v;
}
}
private static void setViewPaddingBottom(View view, int bottom) {
int left = view.getPaddingLeft();
int top = view.getPaddingTop();
int right = view.getPaddingRight();
view.setPadding(left, top, right, bottom);
}
private class FetchArtTask extends AsyncTask<Void, Void, Bitmap> {
final Bitmap mIconBitmap;
final Uri mIconUri;
int mBackgroundColor;
FetchArtTask() {
mIconBitmap = mDescription == null ? null : mDescription.getIconBitmap();
mIconUri = mDescription == null ? null : mDescription.getIconUri();
}
@Override
protected void onPreExecute() {
if (mArtIconBitmap == mIconBitmap && mArtIconUri == mIconUri) {
// Already handled the current art.
cancel(true);
}
}
@Override
protected Bitmap doInBackground(Void... arg) {
Bitmap art = null;
if (mIconBitmap != null) {
art = mIconBitmap;
} else if (mIconUri != null) {
String scheme = mIconUri.getScheme();
if (!(ContentResolver.SCHEME_ANDROID_RESOURCE.equals(scheme)
|| ContentResolver.SCHEME_CONTENT.equals(scheme)
|| ContentResolver.SCHEME_FILE.equals(scheme))) {
Log.w(TAG, "Icon Uri should point to local resources.");
return null;
}
BufferedInputStream stream = null;
try {
stream = new BufferedInputStream(
getContext().getContentResolver().openInputStream(mIconUri));
// Query art size.
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeStream(stream, null, options);
if (options.outWidth == 0 || options.outHeight == 0) {
return null;
}
// Rewind the stream in order to restart art decoding.
try {
stream.reset();
} catch (IOException e) {
// Failed to rewind the stream, try to reopen it.
stream.close();
stream = new BufferedInputStream(getContext().getContentResolver()
.openInputStream(mIconUri));
}
// Calculate required size to decode the art and possibly resize it.
options.inJustDecodeBounds = false;
int reqHeight = getDesiredArtHeight(options.outWidth, options.outHeight);
int ratio = options.outHeight / reqHeight;
options.inSampleSize = Math.max(1, Integer.highestOneBit(ratio));
if (isCancelled()) {
return null;
}
art = BitmapFactory.decodeStream(stream, null, options);
} catch (IOException e){
Log.w(TAG, "Unable to open: " + mIconUri, e);
} finally {
if (stream != null) {
try {
stream.close();
} catch (IOException e) {
}
}
}
}
if (art != null && art.getWidth() < art.getHeight()) {
// Portrait art requires background color.
Palette palette = new Palette.Builder(art).maximumColorCount(1).generate();
mBackgroundColor = palette.getSwatches().isEmpty()
? 0 : palette.getSwatches().get(0).getRgb();
}
return art;
}
@Override
protected void onCancelled() {
mFetchArtTask = null;
}
@Override
protected void onPostExecute(Bitmap art) {
mFetchArtTask = null;
if (mArtIconBitmap != mIconBitmap || mArtIconUri != mIconUri) {
mArtIconBitmap = mIconBitmap;
mArtIconUri = mIconUri;
mArtView.setImageBitmap(art);
mArtView.setBackgroundColor(mBackgroundColor);
updateLayoutHeight();
}
}
}
}