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.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) {
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