MediaRouteControllerDialog.java revision 94be6100218126ce6a08bf1f56209578500b361f
1f519df88d6a69e517e08b844ffadd722b9ceb9abTim Kilbourn/* 2f519df88d6a69e517e08b844ffadd722b9ceb9abTim Kilbourn * Copyright (C) 2013 The Android Open Source Project 3f519df88d6a69e517e08b844ffadd722b9ceb9abTim Kilbourn * 4f519df88d6a69e517e08b844ffadd722b9ceb9abTim Kilbourn * Licensed under the Apache License, Version 2.0 (the "License"); 5f519df88d6a69e517e08b844ffadd722b9ceb9abTim Kilbourn * you may not use this file except in compliance with the License. 6f519df88d6a69e517e08b844ffadd722b9ceb9abTim Kilbourn * You may obtain a copy of the License at 7f519df88d6a69e517e08b844ffadd722b9ceb9abTim Kilbourn * 8f519df88d6a69e517e08b844ffadd722b9ceb9abTim Kilbourn * http://www.apache.org/licenses/LICENSE-2.0 9f519df88d6a69e517e08b844ffadd722b9ceb9abTim Kilbourn * 10f519df88d6a69e517e08b844ffadd722b9ceb9abTim Kilbourn * Unless required by applicable law or agreed to in writing, software 11f519df88d6a69e517e08b844ffadd722b9ceb9abTim Kilbourn * distributed under the License is distributed on an "AS IS" BASIS, 12f519df88d6a69e517e08b844ffadd722b9ceb9abTim Kilbourn * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13f519df88d6a69e517e08b844ffadd722b9ceb9abTim Kilbourn * See the License for the specific language governing permissions and 14f519df88d6a69e517e08b844ffadd722b9ceb9abTim Kilbourn * limitations under the License. 15f519df88d6a69e517e08b844ffadd722b9ceb9abTim Kilbourn */ 16f519df88d6a69e517e08b844ffadd722b9ceb9abTim Kilbourn 17f519df88d6a69e517e08b844ffadd722b9ceb9abTim Kilbournpackage android.support.v7.app; 18f519df88d6a69e517e08b844ffadd722b9ceb9abTim Kilbourn 19f519df88d6a69e517e08b844ffadd722b9ceb9abTim Kilbournimport android.app.Dialog; 20f519df88d6a69e517e08b844ffadd722b9ceb9abTim Kilbournimport android.content.Context; 21f519df88d6a69e517e08b844ffadd722b9ceb9abTim Kilbournimport android.content.IntentSender; 22f519df88d6a69e517e08b844ffadd722b9ceb9abTim Kilbournimport android.content.IntentSender.SendIntentException; 23f519df88d6a69e517e08b844ffadd722b9ceb9abTim Kilbournimport android.graphics.drawable.Drawable; 24f519df88d6a69e517e08b844ffadd722b9ceb9abTim Kilbournimport android.os.Bundle; 25f519df88d6a69e517e08b844ffadd722b9ceb9abTim Kilbournimport android.os.RemoteException; 26f519df88d6a69e517e08b844ffadd722b9ceb9abTim Kilbournimport android.support.v4.media.MediaDescriptionCompat; 27f519df88d6a69e517e08b844ffadd722b9ceb9abTim Kilbournimport android.support.v4.media.MediaMetadataCompat; 28f519df88d6a69e517e08b844ffadd722b9ceb9abTim Kilbournimport android.support.v4.media.session.MediaControllerCompat; 29f519df88d6a69e517e08b844ffadd722b9ceb9abTim Kilbournimport android.support.v4.media.session.MediaSessionCompat; 30f519df88d6a69e517e08b844ffadd722b9ceb9abTim Kilbournimport android.support.v4.media.session.PlaybackStateCompat; 31f519df88d6a69e517e08b844ffadd722b9ceb9abTim Kilbournimport android.support.v7.media.MediaRouteSelector; 32f519df88d6a69e517e08b844ffadd722b9ceb9abTim Kilbournimport 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(R.drawable.ic_media_pause); 312 mPlayPauseButton.setContentDescription(getContext().getResources() 313 .getText(R.string.mr_media_route_controller_pause)); 314 } else if (!isPlaying && supportsPlay) { 315 mPlayPauseButton.setVisibility(View.VISIBLE); 316 mPlayPauseButton.setImageResource(R.drawable.ic_media_play); 317 mPlayPauseButton.setContentDescription(getContext().getResources() 318 .getText(R.string.mr_media_route_controller_play)); 319 } else { 320 mPlayPauseButton.setVisibility(View.GONE); 321 } 322 } else { 323 mPlayPauseButton.setVisibility(View.GONE); 324 } 325 } 326 return true; 327 } 328 329 private Drawable getIconDrawable() { 330 if (mRoute.isConnecting()) { 331 if (mMediaRouteConnectingDrawable == null) { 332 mMediaRouteConnectingDrawable = MediaRouterThemeHelper.getThemeDrawable( 333 getContext(), R.attr.mediaRouteConnectingDrawable); 334 } 335 return mMediaRouteConnectingDrawable; 336 } else { 337 if (mMediaRouteOnDrawable == null) { 338 mMediaRouteOnDrawable = MediaRouterThemeHelper.getThemeDrawable( 339 getContext(), R.attr.mediaRouteOnDrawable); 340 } 341 return mMediaRouteOnDrawable; 342 } 343 } 344 345 private final class MediaRouterCallback extends MediaRouter.Callback { 346 @Override 347 public void onRouteUnselected(MediaRouter router, MediaRouter.RouteInfo route) { 348 update(); 349 } 350 351 @Override 352 public void onRouteChanged(MediaRouter router, MediaRouter.RouteInfo route) { 353 update(); 354 } 355 356 @Override 357 public void onRouteVolumeChanged(MediaRouter router, MediaRouter.RouteInfo route) { 358 if (route == mRoute) { 359 } 360 } 361 } 362 363 private final class MediaControllerCallback extends MediaControllerCompat.Callback { 364 @Override 365 public void onSessionDestroyed() { 366 if (mMediaController != null) { 367 mMediaController.unregisterCallback(mControllerCallback); 368 mMediaController = null; 369 } 370 } 371 372 @Override 373 public void onPlaybackStateChanged(PlaybackStateCompat state) { 374 mState = state; 375 update(); 376 } 377 378 @Override 379 public void onMetadataChanged(MediaMetadataCompat metadata) { 380 mDescription = metadata == null ? null : metadata.getDescription(); 381 update(); 382 } 383 } 384 385 private final class ClickListener implements View.OnClickListener { 386 @Override 387 public void onClick(View v) { 388 int id = v.getId(); 389 if (id == R.id.stop || id == R.id.disconnect) { 390 if (mRoute.isSelected()) { 391 mRouter.unselect(id == R.id.stop ? 392 MediaRouter.UNSELECT_REASON_STOPPED : 393 MediaRouter.UNSELECT_REASON_DISCONNECTED); 394 } 395 dismiss(); 396 } else if (id == R.id.play_pause) { 397 if (mMediaController != null && mState != null) { 398 if (mState.getState() == PlaybackStateCompat.STATE_PLAYING) { 399 mMediaController.getTransportControls().pause(); 400 } else { 401 mMediaController.getTransportControls().play(); 402 } 403 } 404 } else if (id == R.id.settings) { 405 IntentSender is = mRoute.getSettingsIntent(); 406 if (is != null) { 407 try { 408 is.sendIntent(null, 0, null, null, null); 409 dismiss(); 410 } catch (Exception e) { 411 Log.e(TAG, "Error opening route settings.", e); 412 } 413 } 414 } 415 } 416 } 417} 418