MediaRouteControllerDialog.java revision 2b274bd3e06bd33c9b2a0851256afcda94d96395
1feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk/*
2feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk * Copyright (C) 2013 The Android Open Source Project
3feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk *
4feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk * Licensed under the Apache License, Version 2.0 (the "License");
5feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk * you may not use this file except in compliance with the License.
6feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk * You may obtain a copy of the License at
7feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk *
8feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk *      http://www.apache.org/licenses/LICENSE-2.0
9feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk *
10feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk * Unless required by applicable law or agreed to in writing, software
11feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk * distributed under the License is distributed on an "AS IS" BASIS,
12feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk * See the License for the specific language governing permissions and
14feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk * limitations under the License.
15feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk */
16feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk
17feb50af361e4305a25758966b6b5df2738c00259Ruben Brunkpackage android.support.v7.app;
185096defdaa4716ce81047a855d6e5ce3f8263600Igor Murashkin
19feb50af361e4305a25758966b6b5df2738c00259Ruben Brunkimport static android.widget.SeekBar.OnSeekBarChangeListener;
20feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk
21feb50af361e4305a25758966b6b5df2738c00259Ruben Brunkimport android.content.ContentResolver;
22feb50af361e4305a25758966b6b5df2738c00259Ruben Brunkimport android.content.Context;
23feb50af361e4305a25758966b6b5df2738c00259Ruben Brunkimport android.content.res.Configuration;
24feb50af361e4305a25758966b6b5df2738c00259Ruben Brunkimport android.content.res.Resources;
25feb50af361e4305a25758966b6b5df2738c00259Ruben Brunkimport android.content.res.TypedArray;
26feb50af361e4305a25758966b6b5df2738c00259Ruben Brunkimport android.graphics.Bitmap;
27feb50af361e4305a25758966b6b5df2738c00259Ruben Brunkimport android.graphics.BitmapFactory;
28feb50af361e4305a25758966b6b5df2738c00259Ruben Brunkimport android.graphics.drawable.BitmapDrawable;
29feb50af361e4305a25758966b6b5df2738c00259Ruben Brunkimport android.graphics.drawable.Drawable;
305096defdaa4716ce81047a855d6e5ce3f8263600Igor Murashkinimport android.net.Uri;
31feb50af361e4305a25758966b6b5df2738c00259Ruben Brunkimport android.os.AsyncTask;
32feb50af361e4305a25758966b6b5df2738c00259Ruben Brunkimport android.os.Build;
33feb50af361e4305a25758966b6b5df2738c00259Ruben Brunkimport android.os.Bundle;
34feb50af361e4305a25758966b6b5df2738c00259Ruben Brunkimport android.os.RemoteException;
35feb50af361e4305a25758966b6b5df2738c00259Ruben Brunkimport android.support.v4.media.MediaDescriptionCompat;
36feb50af361e4305a25758966b6b5df2738c00259Ruben Brunkimport android.support.v4.media.MediaMetadataCompat;
37ef14da32804b06bac872c9e0e14ce0e52120a0bdRuben Brunkimport android.support.v4.media.session.MediaControllerCompat;
38feb50af361e4305a25758966b6b5df2738c00259Ruben Brunkimport android.support.v4.media.session.MediaSessionCompat;
39feb50af361e4305a25758966b6b5df2738c00259Ruben Brunkimport android.support.v4.media.session.PlaybackStateCompat;
40feb50af361e4305a25758966b6b5df2738c00259Ruben Brunkimport android.support.v7.graphics.Palette;
4191b9aabc9fa0c058ecc4a8b3f486540c28fe1cc0Ruben Brunkimport android.support.v7.media.MediaRouteSelector;
4291b9aabc9fa0c058ecc4a8b3f486540c28fe1cc0Ruben Brunkimport android.support.v7.media.MediaRouter;
43feb50af361e4305a25758966b6b5df2738c00259Ruben Brunkimport android.support.v7.media.SeekBarJellybean;
44feb50af361e4305a25758966b6b5df2738c00259Ruben Brunkimport android.support.v7.mediarouter.R;
45feb50af361e4305a25758966b6b5df2738c00259Ruben Brunkimport android.text.TextUtils;
46feb50af361e4305a25758966b6b5df2738c00259Ruben Brunkimport android.util.DisplayMetrics;
47feb50af361e4305a25758966b6b5df2738c00259Ruben Brunkimport android.util.Log;
48feb50af361e4305a25758966b6b5df2738c00259Ruben Brunkimport android.view.KeyEvent;
49feb50af361e4305a25758966b6b5df2738c00259Ruben Brunkimport android.view.LayoutInflater;
50feb50af361e4305a25758966b6b5df2738c00259Ruben Brunkimport android.view.View;
51feb50af361e4305a25758966b6b5df2738c00259Ruben Brunkimport android.view.ViewGroup;
52feb50af361e4305a25758966b6b5df2738c00259Ruben Brunkimport android.view.ViewTreeObserver;
53feb50af361e4305a25758966b6b5df2738c00259Ruben Brunkimport android.widget.ArrayAdapter;
54feb50af361e4305a25758966b6b5df2738c00259Ruben Brunkimport android.widget.Button;
55feb50af361e4305a25758966b6b5df2738c00259Ruben Brunkimport android.widget.FrameLayout;
56feb50af361e4305a25758966b6b5df2738c00259Ruben Brunkimport android.widget.ImageButton;
57feb50af361e4305a25758966b6b5df2738c00259Ruben Brunkimport android.widget.ImageView;
58feb50af361e4305a25758966b6b5df2738c00259Ruben Brunkimport android.widget.LinearLayout;
59feb50af361e4305a25758966b6b5df2738c00259Ruben Brunkimport android.widget.ListView;
60feb50af361e4305a25758966b6b5df2738c00259Ruben Brunkimport android.widget.RelativeLayout;
61feb50af361e4305a25758966b6b5df2738c00259Ruben Brunkimport android.widget.SeekBar;
62feb50af361e4305a25758966b6b5df2738c00259Ruben Brunkimport android.widget.TextView;
63feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk
64feb50af361e4305a25758966b6b5df2738c00259Ruben Brunkimport java.io.BufferedInputStream;
65feb50af361e4305a25758966b6b5df2738c00259Ruben Brunkimport java.io.IOException;
66feb50af361e4305a25758966b6b5df2738c00259Ruben Brunkimport java.util.List;
67feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk
68feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk/**
69feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk * This class implements the route controller dialog for {@link MediaRouter}.
70feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk * <p>
71feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk * This dialog allows the user to control or disconnect from the currently selected route.
72feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk * </p>
73feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk *
74feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk * @see MediaRouteButton
75feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk * @see MediaRouteActionProvider
76feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk */
77feb50af361e4305a25758966b6b5df2738c00259Ruben Brunkpublic class MediaRouteControllerDialog extends AlertDialog {
78feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk    private static final String TAG = "MediaRouteControllerDialog";
79feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk
80feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk    // STOPSHIP: Remove the flag when the group volume control implementation completes.
81feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk    private static final boolean USE_GROUP = false;
82feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk
83feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk    // Time to wait before updating the volume when the user lets go of the seek bar
84feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk    // to allow the route provider time to propagate the change and publish a new
85feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk    // route descriptor.
86feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk    private static final int VOLUME_UPDATE_DELAY_MILLIS = 250;
87feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk
88feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk    private final MediaRouter mRouter;
89feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk    private final MediaRouterCallback mCallback;
90feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk    private final MediaRouter.RouteInfo mRoute;
91feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk
92feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk    private boolean mCreated;
93feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk    private boolean mAttachedToWindow;
94feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk    private int mOrientation;
95feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk    private int mDialogWidthPortrait;
96feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk    private int mDialogWidthLandscape;
97feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk    private int mDialogPaddingHorizontal;
98feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk
99feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk    private View mCustomControlView;
100feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk
101feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk    private Button mDisconnectButton;
102feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk    private Button mStopCastingButton;
103feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk    private ImageButton mPlayPauseButton;
104feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk    private ImageButton mCloseButton;
105feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk    private ImageButton mGroupExpandCollapseButton;
106feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk
107feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk    private FrameLayout mCustomControlLayout;
108feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk    private FrameLayout mDefaultControlLayout;
109feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk    private boolean mNeedToAdjustControlFrameLayout = true;
110feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk    private ImageView mArtView;
111feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk    private TextView mTitleView;
112feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk    private TextView mSubtitleView;
113feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk    private TextView mRouteNameTextView;
114feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk
115feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk    private boolean mVolumeControlEnabled = true;
116feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk    private LinearLayout mControlLayout;
117feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk    private RelativeLayout mPlaybackControl;
118feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk    private LinearLayout mVolumeControl;
119feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk    private View mDividerView;
120feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk
121feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk    private ListView mVolumeGroupList;
122feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk    private SeekBar mVolumeSlider;
123feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk    private boolean mVolumeSliderTouched;
1245096defdaa4716ce81047a855d6e5ce3f8263600Igor Murashkin
1255096defdaa4716ce81047a855d6e5ce3f8263600Igor Murashkin    private MediaControllerCompat mMediaController;
1265096defdaa4716ce81047a855d6e5ce3f8263600Igor Murashkin    private MediaControllerCallback mControllerCallback;
127feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk    private PlaybackStateCompat mState;
128feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk    private MediaDescriptionCompat mDescription;
129feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk
130feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk    private FetchArtTask mFetchArtTask;
131feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk    private boolean mIsGroupExpanded;
132feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk
133feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk    public MediaRouteControllerDialog(Context context) {
134feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk        this(context, 0);
135feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk    }
136feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk
137feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk    public MediaRouteControllerDialog(Context context, int theme) {
138feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk        super(MediaRouterThemeHelper.createThemedContext(context), theme);
139feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk        context = getContext();
140feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk
141feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk        mControllerCallback = new MediaControllerCallback();
142feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk        mRouter = MediaRouter.getInstance(context);
143feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk        mCallback = new MediaRouterCallback();
144feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk        mRoute = mRouter.getSelectedRoute();
145feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk        setMediaSession(mRouter.getMediaSessionToken());
146feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk    }
147feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk
148feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk    /**
149feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk     * Gets the route that this dialog is controlling.
150feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk     */
151feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk    public MediaRouter.RouteInfo getRoute() {
152feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk        return mRoute;
153feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk    }
154feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk
1555096defdaa4716ce81047a855d6e5ce3f8263600Igor Murashkin    private MediaRouter.RouteGroup getGroup() {
156feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk        if (mRoute instanceof MediaRouter.RouteGroup) {
157feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk            return (MediaRouter.RouteGroup) mRoute;
158feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk        }
1595096defdaa4716ce81047a855d6e5ce3f8263600Igor Murashkin        return null;
1605096defdaa4716ce81047a855d6e5ce3f8263600Igor Murashkin    }
1615096defdaa4716ce81047a855d6e5ce3f8263600Igor Murashkin
1625096defdaa4716ce81047a855d6e5ce3f8263600Igor Murashkin    /**
1635096defdaa4716ce81047a855d6e5ce3f8263600Igor Murashkin     * Provides the subclass an opportunity to create a view that will
1645096defdaa4716ce81047a855d6e5ce3f8263600Igor Murashkin     * be included within the body of the dialog to offer additional media controls
1655096defdaa4716ce81047a855d6e5ce3f8263600Igor Murashkin     * for the currently playing content.
1665096defdaa4716ce81047a855d6e5ce3f8263600Igor Murashkin     *
1675096defdaa4716ce81047a855d6e5ce3f8263600Igor Murashkin     * @param savedInstanceState The dialog's saved instance state.
1685096defdaa4716ce81047a855d6e5ce3f8263600Igor Murashkin     * @return The media control view, or null if none.
1695096defdaa4716ce81047a855d6e5ce3f8263600Igor Murashkin     */
1705096defdaa4716ce81047a855d6e5ce3f8263600Igor Murashkin    public View onCreateMediaControlView(Bundle savedInstanceState) {
1715096defdaa4716ce81047a855d6e5ce3f8263600Igor Murashkin        return null;
1725096defdaa4716ce81047a855d6e5ce3f8263600Igor Murashkin    }
1735096defdaa4716ce81047a855d6e5ce3f8263600Igor Murashkin
1745096defdaa4716ce81047a855d6e5ce3f8263600Igor Murashkin    /**
1755096defdaa4716ce81047a855d6e5ce3f8263600Igor Murashkin     * Gets the media control view that was created by {@link #onCreateMediaControlView(Bundle)}.
1765096defdaa4716ce81047a855d6e5ce3f8263600Igor Murashkin     *
1775096defdaa4716ce81047a855d6e5ce3f8263600Igor Murashkin     * @return The media control view, or null if none.
178feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk     */
17991b9aabc9fa0c058ecc4a8b3f486540c28fe1cc0Ruben Brunk    public View getMediaControlView() {
18091b9aabc9fa0c058ecc4a8b3f486540c28fe1cc0Ruben Brunk        return mCustomControlView;
18191b9aabc9fa0c058ecc4a8b3f486540c28fe1cc0Ruben Brunk    }
18291b9aabc9fa0c058ecc4a8b3f486540c28fe1cc0Ruben Brunk
18391b9aabc9fa0c058ecc4a8b3f486540c28fe1cc0Ruben Brunk    /**
184feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk     * Sets whether to enable the volume slider and volume control using the volume keys
185feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk     * when the route supports it.
186feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk     * <p>
187feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk     * The default value is true.
1885096defdaa4716ce81047a855d6e5ce3f8263600Igor Murashkin     * </p>
1895096defdaa4716ce81047a855d6e5ce3f8263600Igor Murashkin     */
190feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk    public void setVolumeControlEnabled(boolean enable) {
191feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk        if (mVolumeControlEnabled != enable) {
192feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk            mVolumeControlEnabled = enable;
193feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk            if (mCreated) {
194feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk                updateVolumeControl();
195feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk            }
196feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk        }
197feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk    }
198feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk
199feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk    /**
200feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk     * Returns whether to enable the volume slider and volume control using the volume keys
201feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk     * when the route supports it.
202feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk     */
203feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk    public boolean isVolumeControlEnabled() {
204feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk        return mVolumeControlEnabled;
205feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk    }
206feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk
207feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk    /**
208feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk     * Set the session to use for metadata and transport controls. The dialog
209feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk     * will listen to changes on this session and update the UI automatically in
210feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk     * response to changes.
211feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk     *
212feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk     * @param sessionToken The token for the session to use.
213feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk     */
214feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk    private void setMediaSession(MediaSessionCompat.Token sessionToken) {
21591b9aabc9fa0c058ecc4a8b3f486540c28fe1cc0Ruben Brunk        if (mMediaController != null) {
21691b9aabc9fa0c058ecc4a8b3f486540c28fe1cc0Ruben Brunk            mMediaController.unregisterCallback(mControllerCallback);
21791b9aabc9fa0c058ecc4a8b3f486540c28fe1cc0Ruben Brunk            mMediaController = null;
21891b9aabc9fa0c058ecc4a8b3f486540c28fe1cc0Ruben Brunk        }
21991b9aabc9fa0c058ecc4a8b3f486540c28fe1cc0Ruben Brunk        if (sessionToken == null) {
22091b9aabc9fa0c058ecc4a8b3f486540c28fe1cc0Ruben Brunk            return;
22191b9aabc9fa0c058ecc4a8b3f486540c28fe1cc0Ruben Brunk        }
22291b9aabc9fa0c058ecc4a8b3f486540c28fe1cc0Ruben Brunk        if (!mAttachedToWindow) {
22391b9aabc9fa0c058ecc4a8b3f486540c28fe1cc0Ruben Brunk            return;
22491b9aabc9fa0c058ecc4a8b3f486540c28fe1cc0Ruben Brunk        }
22591b9aabc9fa0c058ecc4a8b3f486540c28fe1cc0Ruben Brunk        try {
22691b9aabc9fa0c058ecc4a8b3f486540c28fe1cc0Ruben Brunk            mMediaController = new MediaControllerCompat(getContext(), sessionToken);
22791b9aabc9fa0c058ecc4a8b3f486540c28fe1cc0Ruben Brunk        } catch (RemoteException e) {
22891b9aabc9fa0c058ecc4a8b3f486540c28fe1cc0Ruben Brunk            Log.e(TAG, "Error creating media controller in setMediaSession.", e);
22991b9aabc9fa0c058ecc4a8b3f486540c28fe1cc0Ruben Brunk        }
23091b9aabc9fa0c058ecc4a8b3f486540c28fe1cc0Ruben Brunk        if (mMediaController != null) {
23191b9aabc9fa0c058ecc4a8b3f486540c28fe1cc0Ruben Brunk            mMediaController.registerCallback(mControllerCallback);
23291b9aabc9fa0c058ecc4a8b3f486540c28fe1cc0Ruben Brunk        }
23391b9aabc9fa0c058ecc4a8b3f486540c28fe1cc0Ruben Brunk        MediaMetadataCompat metadata = mMediaController == null ? null
23491b9aabc9fa0c058ecc4a8b3f486540c28fe1cc0Ruben Brunk                : mMediaController.getMetadata();
23591b9aabc9fa0c058ecc4a8b3f486540c28fe1cc0Ruben Brunk        mDescription = metadata == null ? null : metadata.getDescription();
23691b9aabc9fa0c058ecc4a8b3f486540c28fe1cc0Ruben Brunk        mState = mMediaController == null ? null : mMediaController.getPlaybackState();
23791b9aabc9fa0c058ecc4a8b3f486540c28fe1cc0Ruben Brunk        update();
23891b9aabc9fa0c058ecc4a8b3f486540c28fe1cc0Ruben Brunk    }
23991b9aabc9fa0c058ecc4a8b3f486540c28fe1cc0Ruben Brunk
24091b9aabc9fa0c058ecc4a8b3f486540c28fe1cc0Ruben Brunk    /**
24191b9aabc9fa0c058ecc4a8b3f486540c28fe1cc0Ruben Brunk     * Gets the description being used by the default UI.
24291b9aabc9fa0c058ecc4a8b3f486540c28fe1cc0Ruben Brunk     *
24391b9aabc9fa0c058ecc4a8b3f486540c28fe1cc0Ruben Brunk     * @return The current description.
24491b9aabc9fa0c058ecc4a8b3f486540c28fe1cc0Ruben Brunk     */
24591b9aabc9fa0c058ecc4a8b3f486540c28fe1cc0Ruben Brunk    public MediaSessionCompat.Token getMediaSession() {
24691b9aabc9fa0c058ecc4a8b3f486540c28fe1cc0Ruben Brunk        return mMediaController == null ? null : mMediaController.getSessionToken();
24791b9aabc9fa0c058ecc4a8b3f486540c28fe1cc0Ruben Brunk    }
24891b9aabc9fa0c058ecc4a8b3f486540c28fe1cc0Ruben Brunk
24991b9aabc9fa0c058ecc4a8b3f486540c28fe1cc0Ruben Brunk    @Override
250feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk    protected void onCreate(Bundle savedInstanceState) {
251feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk        super.onCreate(savedInstanceState);
252feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk
253feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk        setContentView(R.layout.mr_controller_material_dialog_b);
254feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk        View decorView = getWindow().getDecorView();
255feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk        mDialogPaddingHorizontal = decorView.getPaddingLeft() + decorView.getPaddingRight();
256feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk        Resources res = getContext().getResources();
257feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk        mDialogWidthPortrait = res.getDimensionPixelSize(
258feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk                R.dimen.mr_dialog_content_width_portrait) + mDialogPaddingHorizontal;
259feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk        mDialogWidthLandscape = res.getDimensionPixelSize(
260feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk                R.dimen.mr_dialog_content_width_landscape) + mDialogPaddingHorizontal;
261feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk
262feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk        ClickListener listener = new ClickListener();
263feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk
264feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk        mDisconnectButton = (Button) findViewById(R.id.mr_button_disconnect);
265feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk        mDisconnectButton.setOnClickListener(listener);
266feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk
267feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk        mStopCastingButton = (Button) findViewById(R.id.mr_button_stop);
268feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk        mStopCastingButton.setOnClickListener(listener);
269feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk
270feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk        mRouteNameTextView = (TextView) findViewById(R.id.mr_name);
271feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk        mCloseButton = (ImageButton) findViewById(R.id.mr_close);
272feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk        mCloseButton.setOnClickListener(listener);
273feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk        mCustomControlLayout = (FrameLayout) findViewById(R.id.mr_custom_control);
274feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk        mDefaultControlLayout = (FrameLayout) findViewById(R.id.mr_default_control);
275feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk        ViewTreeObserver observer = mDefaultControlLayout.getViewTreeObserver();
276feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk        observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
277feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk            @Override
278feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk            public void onGlobalLayout() {
279feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk                updateControlFrameLayout();
280feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk            }
281feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk        });
282feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk        mArtView = (ImageView) findViewById(R.id.mr_art);
283feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk
2845096defdaa4716ce81047a855d6e5ce3f8263600Igor Murashkin        mControlLayout = (LinearLayout) findViewById(R.id.mr_control);
2855096defdaa4716ce81047a855d6e5ce3f8263600Igor Murashkin        mDividerView = findViewById(R.id.mr_control_divider);
2865096defdaa4716ce81047a855d6e5ce3f8263600Igor Murashkin
2875096defdaa4716ce81047a855d6e5ce3f8263600Igor Murashkin        mPlaybackControl = (RelativeLayout) findViewById(R.id.mr_playback_control);
288feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk        mTitleView = (TextView) findViewById(R.id.mr_control_title);
2895096defdaa4716ce81047a855d6e5ce3f8263600Igor Murashkin        mSubtitleView = (TextView) findViewById(R.id.mr_control_subtitle);
290feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk        mPlayPauseButton = (ImageButton) findViewById(R.id.mr_control_play_pause);
291feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk        mPlayPauseButton.setOnClickListener(listener);
292feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk
293feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk        mVolumeControl = (LinearLayout) findViewById(R.id.mr_volume_control);
294feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk        mVolumeSlider = (SeekBar) findViewById(R.id.mr_volume_slider);
295feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk        mVolumeSlider.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
296feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk            private final Runnable mStopTrackingTouch = new Runnable() {
297feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk                @Override
298feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk                public void run() {
299feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk                    if (mVolumeSliderTouched) {
300feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk                        mVolumeSliderTouched = false;
301feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk                        updateVolumeControl();
302feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk                    }
303feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk                }
304feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk            };
305feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk
306feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk            @Override
307feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk            public void onStartTrackingTouch(SeekBar seekBar) {
308feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk                if (mVolumeSliderTouched) {
309feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk                    mVolumeSlider.removeCallbacks(mStopTrackingTouch);
310feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk                } else {
311feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk                    mVolumeSliderTouched = true;
312feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk                }
313feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk            }
314feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk
315feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk            @Override
316feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk            public void onStopTrackingTouch(SeekBar seekBar) {
317feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk                // Defer resetting mVolumeSliderTouched to allow the media route provider
318feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk                // a little time to settle into its new state and publish the final
319feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk                // volume update.
320feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk                mVolumeSlider.postDelayed(mStopTrackingTouch, VOLUME_UPDATE_DELAY_MILLIS);
321feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk            }
322feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk
323feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk            @Override
324feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
325feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk                if (fromUser) {
326feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk                    mRoute.requestSetVolume(progress);
327feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk                }
328feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk            }
329feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk        });
330feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk
331feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk        TypedArray styledAttributes = getContext().obtainStyledAttributes(new int[] {
332feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk                R.attr.mediaRouteExpandGroupDrawable,
333feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk                R.attr.mediaRouteCollapseGroupDrawable
334feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk        });
335feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk        final Drawable expandGroupDrawable = styledAttributes.getDrawable(0);
336feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk        final Drawable collapseGroupDrawable = styledAttributes.getDrawable(1);
337feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk        styledAttributes.recycle();
338feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk
339feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk        mVolumeGroupList = (ListView)findViewById(R.id.mr_volume_group_list);
340ef14da32804b06bac872c9e0e14ce0e52120a0bdRuben Brunk        mGroupExpandCollapseButton = (ImageButton) findViewById(R.id.mr_group_expand_collapse);
341feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk        mGroupExpandCollapseButton.setOnClickListener(new View.OnClickListener() {
342feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk            @Override
343feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk            public void onClick(View v) {
344feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk                mIsGroupExpanded = !mIsGroupExpanded;
345ef14da32804b06bac872c9e0e14ce0e52120a0bdRuben Brunk                if (mIsGroupExpanded) {
346ef14da32804b06bac872c9e0e14ce0e52120a0bdRuben Brunk                    mGroupExpandCollapseButton.setImageDrawable(collapseGroupDrawable);
347ef14da32804b06bac872c9e0e14ce0e52120a0bdRuben Brunk                    mVolumeGroupList.setVisibility(View.VISIBLE);
348feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk                    mVolumeGroupList.setAdapter(
349feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk                            new VolumeGroupAdapter(getContext(), getGroup().getRoutes()));
350feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk                } else {
351feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk                    mGroupExpandCollapseButton.setImageDrawable(expandGroupDrawable);
352ef14da32804b06bac872c9e0e14ce0e52120a0bdRuben Brunk                    mVolumeGroupList.setVisibility(View.GONE);
353feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk                }
354feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk                mNeedToAdjustControlFrameLayout = true;
355feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk            }
356feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk        });
357feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk
358ef14da32804b06bac872c9e0e14ce0e52120a0bdRuben Brunk        mCreated = true;
359feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk        mCustomControlView = onCreateMediaControlView(savedInstanceState);
360feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk        if (mCustomControlView != null) {
361feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk            mCustomControlLayout.addView(mCustomControlView);
362feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk            mCustomControlLayout.setVisibility(View.VISIBLE);
363ef14da32804b06bac872c9e0e14ce0e52120a0bdRuben Brunk            mArtView.setVisibility(View.GONE);
364ef14da32804b06bac872c9e0e14ce0e52120a0bdRuben Brunk        }
365ef14da32804b06bac872c9e0e14ce0e52120a0bdRuben Brunk        update();
366feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk    }
367feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk
368feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk    /**
369ef14da32804b06bac872c9e0e14ce0e52120a0bdRuben Brunk     * Called by {@link MediaRouteControllerDialogFragment} when the device configuration
370ef14da32804b06bac872c9e0e14ce0e52120a0bdRuben Brunk     * is changed.
371ef14da32804b06bac872c9e0e14ce0e52120a0bdRuben Brunk     */
372feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk    void onConfigurationChanged(Configuration newConfig) {
373feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk        onOrientationChanged(newConfig.orientation);
374ef14da32804b06bac872c9e0e14ce0e52120a0bdRuben Brunk    }
375feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk
376feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk    private void onOrientationChanged(int orientation) {
377ef14da32804b06bac872c9e0e14ce0e52120a0bdRuben Brunk        if (!mAttachedToWindow || mOrientation == orientation) {
378feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk            return;
379feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk        }
380feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk        mOrientation = orientation;
381feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk        int dialogWidth = mOrientation == Configuration.ORIENTATION_LANDSCAPE
382feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk                ? mDialogWidthLandscape : mDialogWidthPortrait;
383ef14da32804b06bac872c9e0e14ce0e52120a0bdRuben Brunk        int buttonWidth = (dialogWidth - mDialogPaddingHorizontal) / 2;
384feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk        getWindow().setLayout(dialogWidth, ViewGroup.LayoutParams.WRAP_CONTENT);
385feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk
386feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk        // Manually set the width of buttons to workaround unexpected text alignment changes.
387ef14da32804b06bac872c9e0e14ce0e52120a0bdRuben Brunk        ViewGroup.LayoutParams lp = mStopCastingButton.getLayoutParams();
388ef14da32804b06bac872c9e0e14ce0e52120a0bdRuben Brunk        lp.width = buttonWidth;
389feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk        mStopCastingButton.setLayoutParams(lp);
390ef14da32804b06bac872c9e0e14ce0e52120a0bdRuben Brunk
391feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk        lp = mDisconnectButton.getLayoutParams();
392feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk        lp.width = buttonWidth;
393ef14da32804b06bac872c9e0e14ce0e52120a0bdRuben Brunk        mDisconnectButton.setLayoutParams(lp);
394feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk
395feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk        updateArtView();
396feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk    }
397feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk
398feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk    @Override
399feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk    public void onAttachedToWindow() {
400ef14da32804b06bac872c9e0e14ce0e52120a0bdRuben Brunk        super.onAttachedToWindow();
401feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk        mAttachedToWindow = true;
402feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk
403feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk        mRouter.addCallback(MediaRouteSelector.EMPTY, mCallback,
404feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk                MediaRouter.CALLBACK_FLAG_UNFILTERED_EVENTS);
405ef14da32804b06bac872c9e0e14ce0e52120a0bdRuben Brunk        setMediaSession(mRouter.getMediaSessionToken());
406feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk        onOrientationChanged(getContext().getResources().getConfiguration().orientation);
407feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk    }
408feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk
409feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk    @Override
410feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk    public void onDetachedFromWindow() {
411feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk        mRouter.removeCallback(mCallback);
412feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk        setMediaSession(null);
413ef14da32804b06bac872c9e0e14ce0e52120a0bdRuben Brunk        mAttachedToWindow = false;
414feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk        super.onDetachedFromWindow();
415feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk    }
416feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk
417feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk    @Override
418feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk    public boolean onKeyDown(int keyCode, KeyEvent event) {
419feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk        if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN
420feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk                || keyCode == KeyEvent.KEYCODE_VOLUME_UP) {
421ef14da32804b06bac872c9e0e14ce0e52120a0bdRuben Brunk            mRoute.requestUpdateVolume(keyCode == KeyEvent.KEYCODE_VOLUME_DOWN ? -1 : 1);
422ef14da32804b06bac872c9e0e14ce0e52120a0bdRuben Brunk            return true;
423feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk        }
424ef14da32804b06bac872c9e0e14ce0e52120a0bdRuben Brunk        return super.onKeyDown(keyCode, event);
425feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk    }
426feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk
427ef14da32804b06bac872c9e0e14ce0e52120a0bdRuben Brunk    @Override
428feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk    public boolean onKeyUp(int keyCode, KeyEvent event) {
429feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk        if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN
430feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk                || keyCode == KeyEvent.KEYCODE_VOLUME_UP) {
431feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk            return true;
432feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk        }
433ef14da32804b06bac872c9e0e14ce0e52120a0bdRuben Brunk        return super.onKeyUp(keyCode, event);
434feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk    }
435feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk
436feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk    private void update() {
437ef14da32804b06bac872c9e0e14ce0e52120a0bdRuben Brunk        if (!mRoute.isSelected() || mRoute.isDefault()) {
438ef14da32804b06bac872c9e0e14ce0e52120a0bdRuben Brunk            dismiss();
439feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk            return;
440ef14da32804b06bac872c9e0e14ce0e52120a0bdRuben Brunk        }
441feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk        if (!mCreated) {
442feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk            return;
443ef14da32804b06bac872c9e0e14ce0e52120a0bdRuben Brunk        }
444feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk
445feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk        mRouteNameTextView.setText(mRoute.getName());
446feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk        mDisconnectButton.setVisibility(mRoute.canDisconnect() ? View.VISIBLE : View.INVISIBLE);
447feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk
448feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk        if (mCustomControlView == null) {
449ef14da32804b06bac872c9e0e14ce0e52120a0bdRuben Brunk            if (mFetchArtTask != null) {
450feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk                mFetchArtTask.cancel(true);
451feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk            }
452feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk            mFetchArtTask = new FetchArtTask();
453ef14da32804b06bac872c9e0e14ce0e52120a0bdRuben Brunk            mFetchArtTask.execute();
454ef14da32804b06bac872c9e0e14ce0e52120a0bdRuben Brunk        }
455feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk        updateVolumeControl();
456ef14da32804b06bac872c9e0e14ce0e52120a0bdRuben Brunk        updatePlaybackControl();
457feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk    }
458feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk
459feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk    private void updateVolumeControl() {
460feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk        if (!mVolumeSliderTouched) {
461feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk            if (isVolumeControlAvailable()) {
462feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk                mVolumeControl.setVisibility(View.VISIBLE);
463feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk                mVolumeSlider.setMax(mRoute.getVolumeMax());
464feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk                mVolumeSlider.setProgress(mRoute.getVolume());
465feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk                if (USE_GROUP) {
466ef14da32804b06bac872c9e0e14ce0e52120a0bdRuben Brunk                    if (getGroup() == null) {
467feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk                        mGroupExpandCollapseButton.setVisibility(View.GONE);
468feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk                    } else {
469ef14da32804b06bac872c9e0e14ce0e52120a0bdRuben Brunk                        mGroupExpandCollapseButton.setVisibility(View.VISIBLE);
470feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk                        VolumeGroupAdapter adapter =
471feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk                                (VolumeGroupAdapter) mVolumeGroupList.getAdapter();
472ef14da32804b06bac872c9e0e14ce0e52120a0bdRuben Brunk                        if (adapter != null) {
473feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk                            adapter.notifyDataSetChanged();
474feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk                        }
475ef14da32804b06bac872c9e0e14ce0e52120a0bdRuben Brunk                    }
476feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk                }
477feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk            } else {
478ef14da32804b06bac872c9e0e14ce0e52120a0bdRuben Brunk                mVolumeControl.setVisibility(View.GONE);
479feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk            }
480feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk            updateControlLayoutHeight();
481feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk        }
482feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk    }
483feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk
484feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk    private void updatePlaybackControl() {
485feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk        if (mCustomControlView == null && (mDescription != null || mState != null)) {
486feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk            mPlaybackControl.setVisibility(View.VISIBLE);
487feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk            CharSequence title = mDescription == null ? null : mDescription.getTitle();
488feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk            boolean hasTitle = !TextUtils.isEmpty(title);
489feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk
490feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk            CharSequence subtitle = mDescription == null ? null : mDescription.getSubtitle();
491feb50af361e4305a25758966b6b5df2738c00259Ruben Brunk            boolean hasSubtitle = !TextUtils.isEmpty(subtitle);
492
493            if (!hasTitle && !hasSubtitle) {
494                if (mRoute.getPresentationDisplayId()
495                        != MediaRouter.RouteInfo.PRESENTATION_DISPLAY_ID_NONE) {
496                    // The user is currently casting screen.
497                    mTitleView.setText(R.string.mr_controller_casting_screen);
498                } else {
499                    mTitleView.setText((mState == null
500                            || mState.getState() == PlaybackStateCompat.STATE_NONE)
501                                    ? R.string.mr_controller_no_media_selected
502                                    : R.string.mr_controller_no_info_available);
503                }
504                mTitleView.setEnabled(false);
505                mTitleView.setVisibility(View.VISIBLE);
506                mSubtitleView.setVisibility(View.GONE);
507            } else {
508                mTitleView.setText(title);
509                mTitleView.setEnabled(hasTitle);
510                mTitleView.setVisibility(hasTitle ? View.VISIBLE : View.GONE);
511                mSubtitleView.setText(subtitle);
512                mSubtitleView.setVisibility(hasSubtitle ? View.VISIBLE : View.GONE);
513            }
514            if (mState != null) {
515                boolean isPlaying = mState.getState() == PlaybackStateCompat.STATE_BUFFERING
516                        || mState.getState() == PlaybackStateCompat.STATE_PLAYING;
517                boolean supportsPlay = (mState.getActions() & (PlaybackStateCompat.ACTION_PLAY
518                        | PlaybackStateCompat.ACTION_PLAY_PAUSE)) != 0;
519                boolean supportsPause = (mState.getActions() & (PlaybackStateCompat.ACTION_PAUSE
520                        | PlaybackStateCompat.ACTION_PLAY_PAUSE)) != 0;
521                if (isPlaying && supportsPause) {
522                    mPlayPauseButton.setVisibility(View.VISIBLE);
523                    mPlayPauseButton.setImageResource(MediaRouterThemeHelper.getThemeResource(
524                            getContext(), R.attr.mediaRoutePauseDrawable));
525                    mPlayPauseButton.setContentDescription(getContext().getResources()
526                            .getText(R.string.mr_controller_pause));
527                } else if (!isPlaying && supportsPlay) {
528                    mPlayPauseButton.setVisibility(View.VISIBLE);
529                    mPlayPauseButton.setImageResource(MediaRouterThemeHelper.getThemeResource(
530                            getContext(), R.attr.mediaRoutePlayDrawable));
531                    mPlayPauseButton.setContentDescription(getContext().getResources()
532                            .getText(R.string.mr_controller_play));
533                } else {
534                    mPlayPauseButton.setVisibility(View.GONE);
535                }
536            }
537        } else {
538            mPlaybackControl.setVisibility(View.GONE);
539        }
540        updateControlLayoutHeight();
541    }
542
543    private void updateControlLayoutHeight() {
544        // TODO: Update the top and bottom padding of the control layout according to the display
545        // height.
546        mDividerView.setVisibility((mVolumeControl.getVisibility() == View.VISIBLE
547                && mPlaybackControl.getVisibility() == View.VISIBLE)
548                ? View.VISIBLE : View.GONE);
549        mControlLayout.setVisibility((mVolumeControl.getVisibility() == View.GONE
550                && mPlaybackControl.getVisibility() == View.GONE)
551                ? View.GONE : View.VISIBLE);
552    }
553
554    private void updateControlFrameLayout() {
555        if (!mNeedToAdjustControlFrameLayout) {
556            return;
557        }
558        int height;
559        if (mArtView.getVisibility() == View.GONE) {
560            height = LinearLayout.LayoutParams.WRAP_CONTENT;
561        } else if (!mIsGroupExpanded) {
562            height = mArtView.getHeight() + mControlLayout.getHeight();
563        } else {
564            if (mVolumeGroupList.getAdapter().getCount() <= 2) {
565                // Push the controls up and cover the artwork.
566                height = mArtView.getHeight() + mControlLayout.getHeight()
567                        - mVolumeGroupList.getHeight();
568            } else {
569                // If there are 3 or more, the controls completely cover the artwork.
570                height = mControlLayout.getHeight();
571            }
572        }
573        mDefaultControlLayout.setLayoutParams(new LinearLayout.LayoutParams(
574                LinearLayout.LayoutParams.FILL_PARENT, height));
575        mNeedToAdjustControlFrameLayout = false;
576    }
577
578    private boolean isVolumeControlAvailable() {
579        return mVolumeControlEnabled && mRoute.getVolumeHandling() ==
580                MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE;
581    }
582
583    private void updateArtView() {
584        mNeedToAdjustControlFrameLayout = true;
585        if (!(mArtView.getDrawable() instanceof BitmapDrawable)) {
586            mArtView.setVisibility(View.GONE);
587            return;
588        }
589        Bitmap art = ((BitmapDrawable) mArtView.getDrawable()).getBitmap();
590        if (art == null) {
591            mArtView.setVisibility(View.GONE);
592            return;
593        }
594        int desiredArtHeight = getDesiredArtHeight(art.getWidth(), art.getHeight());
595        DisplayMetrics displayMetrics = getContext().getResources().getDisplayMetrics();
596        int dialogWidth = displayMetrics.widthPixels < displayMetrics.heightPixels
597                ? mDialogWidthPortrait : mDialogWidthLandscape;
598        View decorView = getWindow().getDecorView();
599        decorView.measure(dialogWidth, View.MeasureSpec.UNSPECIFIED);
600        // Show art if and only if it fits in the screen.
601        if (mArtView.getVisibility() == View.GONE) {
602            if (decorView.getMeasuredHeight() + desiredArtHeight <= displayMetrics.heightPixels) {
603                mArtView.setVisibility(View.VISIBLE);
604                mArtView.setMaxHeight(desiredArtHeight);
605            }
606        } else {
607            if (decorView.getMeasuredHeight() - mArtView.getMeasuredHeight() + desiredArtHeight
608                    <= displayMetrics.heightPixels) {
609                mArtView.setMaxHeight(desiredArtHeight);
610            } else {
611                mArtView.setVisibility(View.GONE);
612            }
613        }
614    }
615
616    /**
617     * Returns desired art height to fit into controller dialog.
618     */
619    private int getDesiredArtHeight(int originalWidth, int originalHeight) {
620        int dialogWidth = getWindow().getAttributes().width - mDialogPaddingHorizontal;
621        if (originalWidth >= originalHeight) {
622            // For landscape art, fit width to dialog width.
623            return (int) ((float) dialogWidth * originalHeight / originalWidth + 0.5f);
624        }
625        // For portrait art, fit height to 16:9 ratio case's height.
626        return (int) ((float) dialogWidth * 9 / 16 + 0.5f);
627    }
628
629    private final class MediaRouterCallback extends MediaRouter.Callback {
630        @Override
631        public void onRouteUnselected(MediaRouter router, MediaRouter.RouteInfo route) {
632            update();
633        }
634
635        @Override
636        public void onRouteChanged(MediaRouter router, MediaRouter.RouteInfo route) {
637            update();
638        }
639
640        @Override
641        public void onRouteVolumeChanged(MediaRouter router, MediaRouter.RouteInfo route) {
642            if (route == mRoute) {
643                updateVolumeControl();
644            }
645        }
646    }
647
648    private final class MediaControllerCallback extends MediaControllerCompat.Callback {
649        @Override
650        public void onSessionDestroyed() {
651            if (mMediaController != null) {
652                mMediaController.unregisterCallback(mControllerCallback);
653                mMediaController = null;
654            }
655        }
656
657        @Override
658        public void onPlaybackStateChanged(PlaybackStateCompat state) {
659            mState = state;
660            update();
661        }
662
663        @Override
664        public void onMetadataChanged(MediaMetadataCompat metadata) {
665            mDescription = metadata == null ? null : metadata.getDescription();
666            update();
667        }
668    }
669
670    private final class ClickListener implements View.OnClickListener {
671        @Override
672        public void onClick(View v) {
673            int id = v.getId();
674            if (id == R.id.mr_button_stop || id == R.id.mr_button_disconnect) {
675                if (mRoute.isSelected()) {
676                    mRouter.unselect(id == R.id.mr_button_stop ?
677                            MediaRouter.UNSELECT_REASON_STOPPED :
678                            MediaRouter.UNSELECT_REASON_DISCONNECTED);
679                }
680                dismiss();
681            } else if (id == R.id.mr_control_play_pause) {
682                if (mMediaController != null && mState != null) {
683                    if (mState.getState() == PlaybackStateCompat.STATE_PLAYING) {
684                        mMediaController.getTransportControls().pause();
685                    } else {
686                        mMediaController.getTransportControls().play();
687                    }
688                }
689            } else if (id == R.id.mr_close) {
690                dismiss();
691            }
692        }
693    }
694
695    private class VolumeGroupAdapter extends ArrayAdapter<MediaRouter.RouteInfo> {
696        final static float DISABLED_ALPHA = .3f;
697
698        final OnSeekBarChangeListener mOnSeekBarChangeListener = new OnSeekBarChangeListener() {
699            @Override
700            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
701                if (fromUser) {
702                    int position = (int) seekBar.getTag();
703                    getGroup().getRouteAt(position).requestSetVolume(progress);
704                }
705            }
706
707            @Override
708            public void onStartTrackingTouch(SeekBar seekBar) {
709                // TODO: Implement
710            }
711
712            @Override
713            public void onStopTrackingTouch(SeekBar seekBar) {
714                // TODO: Implement
715            }
716        };
717
718        public VolumeGroupAdapter(Context context, List<MediaRouter.RouteInfo> objects) {
719            super(context, 0, objects);
720        }
721
722        @Override
723        public View getView(final int position, View convertView, ViewGroup parent) {
724            View v = convertView;
725            if (v == null) {
726                v = LayoutInflater.from(getContext()).inflate(
727                        R.layout.mr_controller_volume_item, null);
728            }
729            MediaRouter.RouteInfo route = getItem(position);
730            if (route != null) {
731                boolean isEnabled = route.isEnabled();
732
733                TextView routeName = (TextView) v.findViewById(R.id.mr_name);
734                routeName.setEnabled(isEnabled);
735                routeName.setText(route.getName());
736
737                SeekBar volumeSlider = (SeekBar) v.findViewById(R.id.mr_volume_slider);
738                volumeSlider.setEnabled(isEnabled);
739                volumeSlider.setTag(position);
740                if (isEnabled) {
741                    if (route.getVolumeHandling()
742                            == MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE) {
743                        volumeSlider.setMax(route.getVolumeMax());
744                        volumeSlider.setProgress(route.getVolume());
745                        volumeSlider.setOnSeekBarChangeListener(mOnSeekBarChangeListener);
746                    } else {
747                        volumeSlider.setMax(100);
748                        volumeSlider.setProgress(100);
749                        volumeSlider.setEnabled(false);
750                    }
751                }
752                if (Build.VERSION.SDK_INT >= 16) {
753                    SeekBarJellybean.getThumb(volumeSlider).mutate().setAlpha(isEnabled ? 255 : 0);
754                    // TODO: Still see an artifact even though the thumb is transparent. Remove it.
755                }
756
757                ImageView volumeItemIcon =
758                        (ImageView) v.findViewById(R.id.mr_volume_item_icon);
759                volumeItemIcon.setAlpha(isEnabled ? 255 : (int) (255 * DISABLED_ALPHA));
760            }
761            return v;
762        }
763    }
764
765    private class FetchArtTask extends AsyncTask<Void, Void, Bitmap> {
766        private int mBackgroundColor;
767
768        @Override
769        protected Bitmap doInBackground(Void... arg) {
770            Bitmap art = null;
771            if (mDescription == null) {
772                return null;
773            }
774            if (mDescription.getIconBitmap() != null) {
775                art = mDescription.getIconBitmap();
776            } else if (mDescription.getIconUri() != null) {
777                Uri iconUri = mDescription.getIconUri();
778                String scheme = iconUri.getScheme();
779                if (!(ContentResolver.SCHEME_ANDROID_RESOURCE.equals(scheme)
780                        || ContentResolver.SCHEME_CONTENT.equals(scheme)
781                        || ContentResolver.SCHEME_FILE.equals(scheme))) {
782                    Log.w(TAG, "Icon Uri should point to local resources.");
783                    return null;
784                }
785                BufferedInputStream stream = null;
786                try {
787                    stream = new BufferedInputStream(
788                            getContext().getContentResolver().openInputStream(iconUri));
789
790                    // Query art size.
791                    BitmapFactory.Options options = new BitmapFactory.Options();
792                    options.inJustDecodeBounds = true;
793                    BitmapFactory.decodeStream(stream, null, options);
794                    if (options.outWidth == 0 || options.outHeight == 0) {
795                        return null;
796                    }
797                    // Rewind the stream in order to restart art decoding.
798                    try {
799                        stream.reset();
800                    } catch (IOException e) {
801                        // Failed to rewind the stream, try to reopen it.
802                        stream.close();
803                        stream = new BufferedInputStream(getContext().getContentResolver()
804                                .openInputStream(iconUri));
805                    }
806                    // Calculate required size to decode the art and possibly resize it.
807                    options.inJustDecodeBounds = false;
808                    int reqHeight = getDesiredArtHeight(options.outWidth, options.outHeight);
809                    int ratio = options.outHeight / reqHeight;
810                    options.inSampleSize = Math.max(1, Integer.highestOneBit(ratio));
811                    if (isCancelled()) {
812                        return null;
813                    }
814                    art = BitmapFactory.decodeStream(stream, null, options);
815                } catch (IOException e){
816                    Log.w(TAG, "Unable to open content: " + iconUri, e);
817                } finally {
818                    if (stream != null) {
819                        try {
820                            stream.close();
821                        } catch (IOException e) {
822                        }
823                    }
824                }
825            }
826            if (art != null && art.getWidth() < art.getHeight()) {
827                // Portrait art requires background color.
828                Palette palette = new Palette.Builder(art).maximumColorCount(1).generate();
829                mBackgroundColor = (palette.getSwatches() == null)
830                        ? 0 : palette.getSwatches().get(0).getRgb();
831            }
832            return art;
833        }
834
835        @Override
836        protected void onCancelled() {
837            mFetchArtTask = null;
838        }
839
840        @Override
841        protected void onPostExecute(Bitmap art) {
842            mFetchArtTask = null;
843            mArtView.setImageBitmap(art);
844            mArtView.setBackgroundColor(mBackgroundColor);
845            updateArtView();
846        }
847    }
848}
849