blob: b0e037396c837293f68226266cb752b700e62d86 [file] [log] [blame]
Jeff Brown0abd3a62013-11-09 17:48:23 -08001/*
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.Dialog;
22import android.app.MediaRouteActionProvider;
23import android.app.MediaRouteButton;
24import android.content.Context;
25import android.graphics.drawable.Drawable;
26import android.media.MediaRouter;
27import android.media.MediaRouter.RouteGroup;
28import android.media.MediaRouter.RouteInfo;
29import android.os.Bundle;
30import android.view.KeyEvent;
31import android.view.View;
32import android.view.Window;
33import android.widget.Button;
34import android.widget.FrameLayout;
35import android.widget.LinearLayout;
36import 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 */
49public 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) {
Alan Viverette8eea3ea2014-02-03 18:40:20 -0800259 mMediaRouteConnectingDrawable = getContext().getDrawable(
Jeff Brown0abd3a62013-11-09 17:48:23 -0800260 R.drawable.ic_media_route_connecting_holo_dark);
261 }
262 return mMediaRouteConnectingDrawable;
263 } else {
264 if (mMediaRouteOnDrawable == null) {
Alan Viverette8eea3ea2014-02-03 18:40:20 -0800265 mMediaRouteOnDrawable = getContext().getDrawable(
Jeff Brown0abd3a62013-11-09 17:48:23 -0800266 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}