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