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.graphics.drawable.Drawable; 22import android.os.Bundle; 23import android.support.v7.media.MediaRouteSelector; 24import android.support.v7.media.MediaRouter; 25import android.support.v7.mediarouter.R; 26import android.view.KeyEvent; 27import android.view.View; 28import android.view.Window; 29import android.widget.Button; 30import android.widget.FrameLayout; 31import android.widget.LinearLayout; 32import android.widget.SeekBar; 33 34/** 35 * This class implements the route controller dialog for {@link MediaRouter}. 36 * <p> 37 * This dialog allows the user to control or disconnect from the currently selected route. 38 * </p> 39 * 40 * @see MediaRouteButton 41 * @see MediaRouteActionProvider 42 */ 43public class MediaRouteControllerDialog extends Dialog { 44 private static final String TAG = "MediaRouteControllerDialog"; 45 46 private final MediaRouter mRouter; 47 private final MediaRouterCallback mCallback; 48 private final MediaRouter.RouteInfo mRoute; 49 50 private boolean mCreated; 51 private Drawable mMediaRouteConnectingDrawable; 52 private Drawable mMediaRouteOnDrawable; 53 private Drawable mCurrentIconDrawable; 54 55 private boolean mVolumeControlEnabled = true; 56 private LinearLayout mVolumeLayout; 57 private SeekBar mVolumeSlider; 58 private boolean mVolumeSliderTouched; 59 60 private View mControlView; 61 62 private Button mDisconnectButton; 63 64 public MediaRouteControllerDialog(Context context) { 65 this(context, 0); 66 } 67 68 public MediaRouteControllerDialog(Context context, int theme) { 69 super(MediaRouterThemeHelper.createThemedContext(context, true), theme); 70 context = getContext(); 71 72 mRouter = MediaRouter.getInstance(context); 73 mCallback = new MediaRouterCallback(); 74 mRoute = mRouter.getSelectedRoute(); 75 } 76 77 /** 78 * Gets the route that this dialog is controlling. 79 */ 80 public MediaRouter.RouteInfo getRoute() { 81 return mRoute; 82 } 83 84 /** 85 * Provides the subclass an opportunity to create a view that will 86 * be included within the body of the dialog to offer additional media controls 87 * for the currently playing content. 88 * 89 * @param savedInstanceState The dialog's saved instance state. 90 * @return The media control view, or null if none. 91 */ 92 public View onCreateMediaControlView(Bundle savedInstanceState) { 93 return null; 94 } 95 96 /** 97 * Gets the media control view that was created by {@link #onCreateMediaControlView(Bundle)}. 98 * 99 * @return The media control view, or null if none. 100 */ 101 public View getMediaControlView() { 102 return mControlView; 103 } 104 105 /** 106 * Sets whether to enable the volume slider and volume control using the volume keys 107 * when the route supports it. 108 * <p> 109 * The default value is true. 110 * </p> 111 */ 112 public void setVolumeControlEnabled(boolean enable) { 113 if (mVolumeControlEnabled != enable) { 114 mVolumeControlEnabled = enable; 115 if (mCreated) { 116 updateVolume(); 117 } 118 } 119 } 120 121 /** 122 * Returns whether to enable the volume slider and volume control using the volume keys 123 * when the route supports it. 124 */ 125 public boolean isVolumeControlEnabled() { 126 return mVolumeControlEnabled; 127 } 128 129 @Override 130 protected void onCreate(Bundle savedInstanceState) { 131 super.onCreate(savedInstanceState); 132 133 getWindow().requestFeature(Window.FEATURE_LEFT_ICON); 134 135 setContentView(R.layout.mr_media_route_controller_dialog); 136 137 mVolumeLayout = (LinearLayout)findViewById(R.id.media_route_volume_layout); 138 mVolumeSlider = (SeekBar)findViewById(R.id.media_route_volume_slider); 139 mVolumeSlider.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { 140 @Override 141 public void onStartTrackingTouch(SeekBar seekBar) { 142 mVolumeSliderTouched = true; 143 } 144 145 @Override 146 public void onStopTrackingTouch(SeekBar seekBar) { 147 mVolumeSliderTouched = false; 148 updateVolume(); 149 } 150 151 @Override 152 public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { 153 if (fromUser) { 154 mRoute.requestSetVolume(progress); 155 } 156 } 157 }); 158 159 mDisconnectButton = (Button)findViewById(R.id.media_route_disconnect_button); 160 mDisconnectButton.setOnClickListener(new View.OnClickListener() { 161 @Override 162 public void onClick(View v) { 163 if (mRoute.isSelected()) { 164 mRouter.getDefaultRoute().select(); 165 } 166 dismiss(); 167 } 168 }); 169 170 mCreated = true; 171 if (update()) { 172 mControlView = onCreateMediaControlView(savedInstanceState); 173 FrameLayout controlFrame = 174 (FrameLayout)findViewById(R.id.media_route_control_frame); 175 if (mControlView != null) { 176 controlFrame.addView(mControlView); 177 controlFrame.setVisibility(View.VISIBLE); 178 } else { 179 controlFrame.setVisibility(View.GONE); 180 } 181 } 182 } 183 184 @Override 185 public void onAttachedToWindow() { 186 super.onAttachedToWindow(); 187 188 mRouter.addCallback(MediaRouteSelector.EMPTY, mCallback, 189 MediaRouter.CALLBACK_FLAG_UNFILTERED_EVENTS); 190 update(); 191 } 192 193 @Override 194 public void onDetachedFromWindow() { 195 mRouter.removeCallback(mCallback); 196 197 super.onDetachedFromWindow(); 198 } 199 200 @Override 201 public boolean onKeyDown(int keyCode, KeyEvent event) { 202 if (isVolumeControlAvailable()) { 203 if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) { 204 mRoute.requestUpdateVolume(-1); 205 return true; 206 } else if (keyCode == KeyEvent.KEYCODE_VOLUME_UP) { 207 mRoute.requestUpdateVolume(1); 208 return true; 209 } 210 } 211 return super.onKeyDown(keyCode, event); 212 } 213 214 @Override 215 public boolean onKeyUp(int keyCode, KeyEvent event) { 216 if (isVolumeControlAvailable()) { 217 if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN 218 || keyCode == KeyEvent.KEYCODE_VOLUME_UP) { 219 return true; 220 } 221 } 222 return super.onKeyUp(keyCode, event); 223 } 224 225 private boolean update() { 226 if (!mRoute.isSelected() || mRoute.isDefault()) { 227 dismiss(); 228 return false; 229 } 230 231 setTitle(mRoute.getName()); 232 updateVolume(); 233 234 Drawable icon = getIconDrawable(); 235 if (icon != mCurrentIconDrawable) { 236 mCurrentIconDrawable = icon; 237 238 // There seems to be a bug in the framework where feature drawables 239 // will not start animating unless they experience a transition from 240 // invisible to visible. So we force the drawable to be invisible here. 241 // The window will make the drawable visible when attached. 242 icon.setVisible(false, true); 243 getWindow().setFeatureDrawable(Window.FEATURE_LEFT_ICON, icon); 244 } 245 return true; 246 } 247 248 private Drawable getIconDrawable() { 249 if (mRoute.isConnecting()) { 250 if (mMediaRouteConnectingDrawable == null) { 251 mMediaRouteConnectingDrawable = MediaRouterThemeHelper.getThemeDrawable( 252 getContext(), R.attr.mediaRouteConnectingDrawable); 253 } 254 return mMediaRouteConnectingDrawable; 255 } else { 256 if (mMediaRouteOnDrawable == null) { 257 mMediaRouteOnDrawable = MediaRouterThemeHelper.getThemeDrawable( 258 getContext(), R.attr.mediaRouteOnDrawable); 259 } 260 return mMediaRouteOnDrawable; 261 } 262 } 263 264 private void updateVolume() { 265 if (!mVolumeSliderTouched) { 266 if (isVolumeControlAvailable()) { 267 mVolumeLayout.setVisibility(View.VISIBLE); 268 mVolumeSlider.setMax(mRoute.getVolumeMax()); 269 mVolumeSlider.setProgress(mRoute.getVolume()); 270 } else { 271 mVolumeLayout.setVisibility(View.GONE); 272 } 273 } 274 } 275 276 private boolean isVolumeControlAvailable() { 277 return mVolumeControlEnabled && mRoute.getVolumeHandling() == 278 MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE; 279 } 280 281 private final class MediaRouterCallback extends MediaRouter.Callback { 282 @Override 283 public void onRouteUnselected(MediaRouter router, MediaRouter.RouteInfo route) { 284 update(); 285 } 286 287 @Override 288 public void onRouteChanged(MediaRouter router, MediaRouter.RouteInfo route) { 289 update(); 290 } 291 292 @Override 293 public void onRouteVolumeChanged(MediaRouter router, MediaRouter.RouteInfo route) { 294 if (route == mRoute) { 295 updateVolume(); 296 } 297 } 298 } 299} 300