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