MediaRouteControllerDialog.java revision 92badabc6a274e3bf8ea2a0072e8a6371f252722
1/* 2 * Copyright (C) 2013 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package android.support.v7.app; 18 19import android.app.Dialog; 20import android.content.Context; 21import android.content.IntentSender; 22import android.content.IntentSender.SendIntentException; 23import android.graphics.drawable.Drawable; 24import android.os.Bundle; 25import android.os.RemoteException; 26import android.support.v4.media.MediaDescriptionCompat; 27import android.support.v4.media.MediaMetadataCompat; 28import android.support.v4.media.session.MediaControllerCompat; 29import android.support.v4.media.session.MediaSessionCompat; 30import android.support.v4.media.session.PlaybackStateCompat; 31import android.support.v7.media.MediaRouteSelector; 32import android.support.v7.media.MediaRouter; 33import android.support.v7.mediarouter.R; 34import android.text.TextUtils; 35import android.util.Log; 36import android.view.KeyEvent; 37import android.view.View; 38import android.view.Window; 39import android.widget.Button; 40import android.widget.FrameLayout; 41import android.widget.ImageButton; 42import android.widget.ImageView; 43import android.widget.TextView; 44 45/** 46 * This class implements the route controller dialog for {@link MediaRouter}. 47 * <p> 48 * This dialog allows the user to control or disconnect from the currently selected route. 49 * </p> 50 * 51 * @see MediaRouteButton 52 * @see MediaRouteActionProvider 53 */ 54public class MediaRouteControllerDialog extends Dialog { 55 private static final String TAG = "MediaRouteControllerDialog"; 56 57 private final MediaRouter mRouter; 58 private final MediaRouterCallback mCallback; 59 private final MediaRouter.RouteInfo mRoute; 60 61 private boolean mCreated; 62 private Drawable mMediaRouteConnectingDrawable; 63 private Drawable mMediaRouteOnDrawable; 64 private Drawable mCurrentIconDrawable; 65 private Drawable mSettingsDrawable; 66 67 private View mControlView; 68 69 private Button mDisconnectButton; 70 private Button mStopCastingButton; 71 private ImageButton mPlayPauseButton; 72 private ImageButton mSettingsButton; 73 74 private ImageView mArtView; 75 private TextView mTitleView; 76 private TextView mSubtitleView; 77 private TextView mRouteNameView; 78 private View mTitlesWrapper; 79 80 private MediaControllerCompat mMediaController; 81 private MediaControllerCallback mControllerCallback; 82 private PlaybackStateCompat mState; 83 private MediaDescriptionCompat mDescription; 84 85 86 public MediaRouteControllerDialog(Context context) { 87 this(context, 0); 88 } 89 90 public MediaRouteControllerDialog(Context context, int theme) { 91 super(MediaRouterThemeHelper.createThemedContext(context), theme); 92 context = getContext(); 93 94 mControllerCallback = new MediaControllerCallback(); 95 mRouter = MediaRouter.getInstance(context); 96 mCallback = new MediaRouterCallback(); 97 mRoute = mRouter.getSelectedRoute(); 98 } 99 100 /** 101 * Gets the route that this dialog is controlling. 102 */ 103 public MediaRouter.RouteInfo getRoute() { 104 return mRoute; 105 } 106 107 /** 108 * Provides the subclass an opportunity to create a view that will 109 * be included within the body of the dialog to offer additional media controls 110 * for the currently playing content. 111 * 112 * @param savedInstanceState The dialog's saved instance state. 113 * @return The media control view, or null if none. 114 */ 115 public View onCreateMediaControlView(Bundle savedInstanceState) { 116 return null; 117 } 118 119 /** 120 * Gets the media control view that was created by {@link #onCreateMediaControlView(Bundle)}. 121 * 122 * @return The media control view, or null if none. 123 */ 124 public View getMediaControlView() { 125 return mControlView; 126 } 127 128 /** 129 * Set the session to use for metadata and transport controls. The dialog 130 * will listen to changes on this session and update the UI automatically in 131 * response to changes. 132 * 133 * @param sessionToken The token for the session to use. 134 */ 135 public void setMediaSession(MediaSessionCompat.Token sessionToken) { 136 if (mMediaController != null) { 137 mMediaController.unregisterCallback(mControllerCallback); 138 mMediaController = null; 139 } 140 if (sessionToken == null) { 141 return; 142 } 143 try { 144 mMediaController = new MediaControllerCompat(getContext(), sessionToken); 145 } catch (RemoteException e) { 146 Log.e(TAG, "Error creating media controller in setMediaSession.", e); 147 } 148 if (mMediaController != null) { 149 mMediaController.registerCallback(mControllerCallback); 150 } 151 MediaMetadataCompat metadata = mMediaController == null ? null 152 : mMediaController.getMetadata(); 153 mDescription = metadata == null ? null : metadata.getDescription(); 154 mState = mMediaController == null ? null : mMediaController.getPlaybackState(); 155 update(); 156 } 157 158 /** 159 * Gets the description being used by the default UI. 160 * 161 * @return The current description. 162 */ 163 public MediaSessionCompat.Token getMediaSession() { 164 return mMediaController == null ? null : mMediaController.getSessionToken(); 165 } 166 167 @Override 168 protected void onCreate(Bundle savedInstanceState) { 169 super.onCreate(savedInstanceState); 170 171 getWindow().requestFeature(Window.FEATURE_NO_TITLE); 172 173 setContentView(R.layout.mr_media_route_controller_material_dialog_b); 174 175 ClickListener listener = new ClickListener(); 176 177 mDisconnectButton = (Button) findViewById(R.id.disconnect); 178 mDisconnectButton.setOnClickListener(listener); 179 180 mStopCastingButton = (Button) findViewById(R.id.stop); 181 mStopCastingButton.setOnClickListener(listener); 182 183 mSettingsButton = (ImageButton) findViewById(R.id.settings); 184 mSettingsButton.setOnClickListener(listener); 185 186 mArtView = (ImageView) findViewById(R.id.art); 187 mTitleView = (TextView) findViewById(R.id.title); 188 mSubtitleView = (TextView) findViewById(R.id.subtitle); 189 mTitlesWrapper = findViewById(R.id.text_wrapper); 190 mPlayPauseButton = (ImageButton) findViewById(R.id.play_pause); 191 mPlayPauseButton.setOnClickListener(listener); 192 mRouteNameView = (TextView) findViewById(R.id.route_name); 193 194 mCreated = true; 195 if (update()) { 196 mControlView = onCreateMediaControlView(savedInstanceState); 197 FrameLayout controlFrame = 198 (FrameLayout)findViewById(R.id.media_route_control_frame); 199 if (mControlView != null) { 200 controlFrame.findViewById(R.id.default_control_frame).setVisibility(View.GONE); 201 controlFrame.addView(mControlView); 202 } 203 } 204 } 205 206 @Override 207 public void onAttachedToWindow() { 208 super.onAttachedToWindow(); 209 210 mRouter.addCallback(MediaRouteSelector.EMPTY, mCallback, 211 MediaRouter.CALLBACK_FLAG_UNFILTERED_EVENTS); 212 update(); 213 } 214 215 @Override 216 public void onDetachedFromWindow() { 217 mRouter.removeCallback(mCallback); 218 219 super.onDetachedFromWindow(); 220 } 221 222 @Override 223 public boolean onKeyDown(int keyCode, KeyEvent event) { 224 if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN 225 || keyCode == KeyEvent.KEYCODE_VOLUME_UP) { 226 mRoute.requestUpdateVolume(keyCode == KeyEvent.KEYCODE_VOLUME_DOWN ? -1 : 1); 227 return true; 228 } 229 return super.onKeyDown(keyCode, event); 230 } 231 232 @Override 233 public boolean onKeyUp(int keyCode, KeyEvent event) { 234 if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN 235 || keyCode == KeyEvent.KEYCODE_VOLUME_UP) { 236 return true; 237 } 238 return super.onKeyUp(keyCode, event); 239 } 240 241 private boolean update() { 242 if (!mRoute.isSelected() || mRoute.isDefault()) { 243 dismiss(); 244 return false; 245 } 246 if (!mCreated) { 247 return false; 248 } 249 250 mRouteNameView.setText(mRoute.getName()); 251 252 if (mRoute.canDisconnect()) { 253 mDisconnectButton.setVisibility(View.VISIBLE); 254 } else { 255 mDisconnectButton.setVisibility(View.GONE); 256 } 257 258 if (mRoute.getSettingsIntent() != null) { 259 mSettingsButton.setVisibility(View.VISIBLE); 260 } else { 261 mSettingsButton.setVisibility(View.GONE); 262 } 263 264 if (mControlView == null) { 265 if (mDescription != null) { 266 if (mDescription.getIconBitmap() != null) { 267 mArtView.setImageBitmap(mDescription.getIconBitmap()); 268 mArtView.setVisibility(View.VISIBLE); 269 } else if (mDescription.getIconUri() != null) { 270 // TODO replace with background load of icon 271 mArtView.setImageURI(mDescription.getIconUri()); 272 mArtView.setVisibility(View.VISIBLE); 273 } else { 274 mArtView.setImageDrawable(null); 275 mArtView.setVisibility(View.GONE); 276 } 277 278 boolean haveText = false; 279 CharSequence text = mDescription.getTitle(); 280 if (!TextUtils.isEmpty(text)) { 281 mTitleView.setText(text); 282 haveText = true; 283 } else { 284 mTitleView.setText(null); 285 mTitleView.setVisibility(View.GONE); 286 } 287 text = mDescription.getSubtitle(); 288 if (!TextUtils.isEmpty(text)) { 289 mSubtitleView.setText(mDescription.getSubtitle()); 290 haveText = true; 291 } else { 292 mSubtitleView.setText(null); 293 mSubtitleView.setVisibility(View.GONE); 294 } 295 if (!haveText) { 296 mTitlesWrapper.setVisibility(View.GONE); 297 } 298 } else { 299 mArtView.setVisibility(View.GONE); 300 mTitlesWrapper.setVisibility(View.GONE); 301 } 302 if (mState != null) { 303 boolean isPlaying = mState.getState() == PlaybackStateCompat.STATE_BUFFERING 304 || mState.getState() == PlaybackStateCompat.STATE_PLAYING; 305 boolean supportsPlay = (mState.getActions() & (PlaybackStateCompat.ACTION_PLAY 306 | PlaybackStateCompat.ACTION_PLAY_PAUSE)) != 0; 307 boolean supportsPause = (mState.getActions() & (PlaybackStateCompat.ACTION_PAUSE 308 | PlaybackStateCompat.ACTION_PLAY_PAUSE)) != 0; 309 if (isPlaying && supportsPause) { 310 mPlayPauseButton.setVisibility(View.VISIBLE); 311 mPlayPauseButton.setImageResource(MediaRouterThemeHelper.getThemeResource( 312 getContext(), R.attr.mediaRoutePauseDrawable)); 313 mPlayPauseButton.setContentDescription(getContext().getResources() 314 .getText(R.string.mr_media_route_controller_pause)); 315 } else if (!isPlaying && supportsPlay) { 316 mPlayPauseButton.setVisibility(View.VISIBLE); 317 mPlayPauseButton.setImageResource(MediaRouterThemeHelper.getThemeResource( 318 getContext(), R.attr.mediaRoutePlayDrawable)); 319 mPlayPauseButton.setContentDescription(getContext().getResources() 320 .getText(R.string.mr_media_route_controller_play)); 321 } else { 322 mPlayPauseButton.setVisibility(View.GONE); 323 } 324 } else { 325 mPlayPauseButton.setVisibility(View.GONE); 326 } 327 } 328 return true; 329 } 330 331 private Drawable getIconDrawable() { 332 if (mRoute.isConnecting()) { 333 if (mMediaRouteConnectingDrawable == null) { 334 mMediaRouteConnectingDrawable = MediaRouterThemeHelper.getThemeDrawable( 335 getContext(), R.attr.mediaRouteConnectingDrawable); 336 } 337 return mMediaRouteConnectingDrawable; 338 } else { 339 if (mMediaRouteOnDrawable == null) { 340 mMediaRouteOnDrawable = MediaRouterThemeHelper.getThemeDrawable( 341 getContext(), R.attr.mediaRouteOnDrawable); 342 } 343 return mMediaRouteOnDrawable; 344 } 345 } 346 347 private final class MediaRouterCallback extends MediaRouter.Callback { 348 @Override 349 public void onRouteUnselected(MediaRouter router, MediaRouter.RouteInfo route) { 350 update(); 351 } 352 353 @Override 354 public void onRouteChanged(MediaRouter router, MediaRouter.RouteInfo route) { 355 update(); 356 } 357 358 @Override 359 public void onRouteVolumeChanged(MediaRouter router, MediaRouter.RouteInfo route) { 360 if (route == mRoute) { 361 } 362 } 363 } 364 365 private final class MediaControllerCallback extends MediaControllerCompat.Callback { 366 @Override 367 public void onSessionDestroyed() { 368 if (mMediaController != null) { 369 mMediaController.unregisterCallback(mControllerCallback); 370 mMediaController = null; 371 } 372 } 373 374 @Override 375 public void onPlaybackStateChanged(PlaybackStateCompat state) { 376 mState = state; 377 update(); 378 } 379 380 @Override 381 public void onMetadataChanged(MediaMetadataCompat metadata) { 382 mDescription = metadata == null ? null : metadata.getDescription(); 383 update(); 384 } 385 } 386 387 private final class ClickListener implements View.OnClickListener { 388 @Override 389 public void onClick(View v) { 390 int id = v.getId(); 391 if (id == R.id.stop || id == R.id.disconnect) { 392 if (mRoute.isSelected()) { 393 mRouter.unselect(id == R.id.stop ? 394 MediaRouter.UNSELECT_REASON_STOPPED : 395 MediaRouter.UNSELECT_REASON_DISCONNECTED); 396 } 397 dismiss(); 398 } else if (id == R.id.play_pause) { 399 if (mMediaController != null && mState != null) { 400 if (mState.getState() == PlaybackStateCompat.STATE_PLAYING) { 401 mMediaController.getTransportControls().pause(); 402 } else { 403 mMediaController.getTransportControls().play(); 404 } 405 } 406 } else if (id == R.id.settings) { 407 IntentSender is = mRoute.getSettingsIntent(); 408 if (is != null) { 409 try { 410 is.sendIntent(null, 0, null, null, null); 411 dismiss(); 412 } catch (Exception e) { 413 Log.e(TAG, "Error opening route settings.", e); 414 } 415 } 416 } 417 } 418 } 419} 420