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