MediaRouteControllerDialog.java revision 692a547730bbc95ad277d5214ef3d786ce1e499f
1edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Project/*
2edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Project * Copyright 2018 The Android Open Source Project
3edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Project *
4edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Project * Licensed under the Apache License, Version 2.0 (the "License");
5edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Project * you may not use this file except in compliance with the License.
6edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Project * You may obtain a copy of the License at
7edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Project *
8edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Project *      http://www.apache.org/licenses/LICENSE-2.0
9edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Project *
10edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Project * Unless required by applicable law or agreed to in writing, software
11edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Project * distributed under the License is distributed on an "AS IS" BASIS,
12edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Project * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Project * See the License for the specific language governing permissions and
14edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Project * limitations under the License.
15edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Project */
16edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Project
17edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Projectpackage com.android.support.mediarouter.app;
18edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Project
19edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Projectimport static android.support.v4.media.session.PlaybackStateCompat.ACTION_PAUSE;
20edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Projectimport static android.support.v4.media.session.PlaybackStateCompat.ACTION_PLAY;
21edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Projectimport static android.support.v4.media.session.PlaybackStateCompat.ACTION_PLAY_PAUSE;
22edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Projectimport static android.support.v4.media.session.PlaybackStateCompat.ACTION_STOP;
23c5b2c0bf8007562536b822eb060fc54a01f8e08bMathias Agopian
24c5b2c0bf8007562536b822eb060fc54a01f8e08bMathias Agopianimport android.app.PendingIntent;
25c5b2c0bf8007562536b822eb060fc54a01f8e08bMathias Agopianimport android.content.ContentResolver;
26edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Projectimport android.content.Context;
27d0566bc26fcf6ca396118701fa11900b627f2c09Mathias Agopianimport android.content.res.Resources;
282adaf04fab35cf47c824d74d901b54094e01ccd3Andy McFaddenimport android.graphics.Bitmap;
292b5dd4062dd951b2cafc4b80bd3a1813b013d71aMathias Agopianimport android.graphics.BitmapFactory;
302b5dd4062dd951b2cafc4b80bd3a1813b013d71aMathias Agopianimport android.graphics.Rect;
31a099a24c93bfa599fc5c36a647e946c26f68514fKalle Raitaimport android.graphics.drawable.BitmapDrawable;
324cdc58f6840d15b4952149d2b345ec1f97d505bcRobert Carrimport android.net.Uri;
33edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Projectimport android.os.AsyncTask;
3428f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wrightimport android.os.Bundle;
3528f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wrightimport android.os.RemoteException;
3690ac799241f077a7b7e6c1875fd933864c8dd2a7Mathias Agopianimport android.os.SystemClock;
3767d8bd66aaf04805cb8f2616ba964141b865e3b9Lajos Molnarimport android.support.v4.media.MediaDescriptionCompat;
38c4f471e75a8ec64ec34e3f2944a5a756215d0becDan Stozaimport android.support.v4.media.MediaMetadataCompat;
39582270d69db94286a248bd829f1ae6f910d45124Jamie Gennisimport android.support.v4.media.session.MediaControllerCompat;
40134f0422866e8985188ed10dfbdcb8e6c34b87f7Jamie Gennisimport android.support.v4.media.session.MediaSessionCompat;
419cce325fae8adcf7560a28eef394489f09bad74dMathias Agopianimport android.support.v4.media.session.PlaybackStateCompat;
42edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Projectimport android.support.v4.util.ObjectsCompat;
43edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Projectimport android.support.v4.view.accessibility.AccessibilityEventCompat;
44edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Projectimport android.support.v7.app.AlertDialog;
45edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Projectimport android.support.v7.graphics.Palette;
46edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Project
47edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Projectimport android.text.TextUtils;
48edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Projectimport android.util.Log;
49e2347b7953f70232541bdc1b392230d6231aaeb9Chih-Hung Hsiehimport android.view.KeyEvent;
50edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Projectimport android.view.LayoutInflater;
51edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Projectimport android.view.View;
52edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Projectimport android.view.View.MeasureSpec;
53edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Projectimport android.view.ViewGroup;
54d723bd7669b4fc88dc282d8bf8ba5ecb2849d22fDan Stozaimport android.view.ViewTreeObserver;
55d723bd7669b4fc88dc282d8bf8ba5ecb2849d22fDan Stozaimport android.view.accessibility.AccessibilityEvent;
567e27f05739c8a2655cf0f7faea35614ce0a50278Mathias Agopianimport android.view.accessibility.AccessibilityManager;
57edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Projectimport android.view.animation.AccelerateDecelerateInterpolator;
58edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Projectimport android.view.animation.AlphaAnimation;
59edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Projectimport android.view.animation.Animation;
60edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Projectimport android.view.animation.AnimationSet;
617e27f05739c8a2655cf0f7faea35614ce0a50278Mathias Agopianimport android.view.animation.AnimationUtils;
62edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Projectimport android.view.animation.Interpolator;
63edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Projectimport android.view.animation.Transformation;
641db73f66624e7d151710483dd58e03eed672f064Robert Carrimport android.view.animation.TranslateAnimation;
651db73f66624e7d151710483dd58e03eed672f064Robert Carrimport android.widget.ArrayAdapter;
661db73f66624e7d151710483dd58e03eed672f064Robert Carrimport android.widget.Button;
671db73f66624e7d151710483dd58e03eed672f064Robert Carrimport android.widget.FrameLayout;
681db73f66624e7d151710483dd58e03eed672f064Robert Carrimport android.widget.ImageButton;
691db73f66624e7d151710483dd58e03eed672f064Robert Carrimport android.widget.ImageView;
701db73f66624e7d151710483dd58e03eed672f064Robert Carrimport android.widget.LinearLayout;
711db73f66624e7d151710483dd58e03eed672f064Robert Carrimport android.widget.RelativeLayout;
721db73f66624e7d151710483dd58e03eed672f064Robert Carrimport android.widget.SeekBar;
731db73f66624e7d151710483dd58e03eed672f064Robert Carrimport android.widget.TextView;
748b33f032327f8de0dcc0e6d0d43ed80f834b51f6Mathias Agopian
758b33f032327f8de0dcc0e6d0d43ed80f834b51f6Mathias Agopianimport com.android.media.update.R;
768b33f032327f8de0dcc0e6d0d43ed80f834b51f6Mathias Agopianimport com.android.support.mediarouter.media.MediaRouteSelector;
778b33f032327f8de0dcc0e6d0d43ed80f834b51f6Mathias Agopianimport com.android.support.mediarouter.media.MediaRouter;
78edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Projectimport com.android.support.mediarouter.app.OverlayListView.OverlayObject;
79edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Project
80edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Projectimport java.io.BufferedInputStream;
81d723bd7669b4fc88dc282d8bf8ba5ecb2849d22fDan Stozaimport java.io.IOException;
82d723bd7669b4fc88dc282d8bf8ba5ecb2849d22fDan Stozaimport java.io.InputStream;
83d723bd7669b4fc88dc282d8bf8ba5ecb2849d22fDan Stozaimport java.net.URL;
84d723bd7669b4fc88dc282d8bf8ba5ecb2849d22fDan Stozaimport java.net.URLConnection;
858b33f032327f8de0dcc0e6d0d43ed80f834b51f6Mathias Agopianimport java.util.ArrayList;
86d723bd7669b4fc88dc282d8bf8ba5ecb2849d22fDan Stozaimport java.util.HashMap;
87d723bd7669b4fc88dc282d8bf8ba5ecb2849d22fDan Stozaimport java.util.HashSet;
88d723bd7669b4fc88dc282d8bf8ba5ecb2849d22fDan Stozaimport java.util.List;
89d723bd7669b4fc88dc282d8bf8ba5ecb2849d22fDan Stozaimport java.util.Map;
90698c0873cf2e07bdc7fd1e72169aee2a19fa40d7Mathias Agopianimport java.util.Set;
91d723bd7669b4fc88dc282d8bf8ba5ecb2849d22fDan Stozaimport java.util.concurrent.TimeUnit;
92d723bd7669b4fc88dc282d8bf8ba5ecb2849d22fDan Stoza
93b8d69a55f1c187a35ac41e69de63251f5501b6f4Jamie Gennis/**
94edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Project * This class implements the route controller dialog for {@link MediaRouter}.
95edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Project * <p>
96edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Project * This dialog allows the user to control or disconnect from the currently selected route.
97edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Project * </p>
98edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Project *
99edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Project * @see MediaRouteButton
100edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Project * @see MediaRouteActionProvider
101edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Project */
102edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Projectpublic class MediaRouteControllerDialog extends AlertDialog {
10340482ff650751819d4104c10a30974838168438cChavi Weingarten    // Tags should be less than 24 characters long (see docs for android.util.Log.isLoggable())
10440482ff650751819d4104c10a30974838168438cChavi Weingarten    static final String TAG = "MediaRouteCtrlDialog";
10540482ff650751819d4104c10a30974838168438cChavi Weingarten    static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
10640482ff650751819d4104c10a30974838168438cChavi Weingarten
1072a9fc493dfdba67108e4335bb1fe931bc1e2a025Mathias Agopian    // Time to wait before updating the volume when the user lets go of the seek bar
1082a9fc493dfdba67108e4335bb1fe931bc1e2a025Mathias Agopian    // to allow the route provider time to propagate the change and publish a new
1092a9fc493dfdba67108e4335bb1fe931bc1e2a025Mathias Agopian    // route descriptor.
110c18790018be5d7ea7061ccbc81f3044e74adc823Dan Stoza    static final int VOLUME_UPDATE_DELAY_MILLIS = 500;
111d723bd7669b4fc88dc282d8bf8ba5ecb2849d22fDan Stoza    static final int CONNECTION_TIMEOUT_MILLIS = (int) TimeUnit.SECONDS.toMillis(30L);
112d723bd7669b4fc88dc282d8bf8ba5ecb2849d22fDan Stoza
113ae0608381b2b4699218febd6d45ad9d307544d55Robert Carr    private static final int BUTTON_NEUTRAL_RES_ID = android.R.id.button3;
114ae0608381b2b4699218febd6d45ad9d307544d55Robert Carr    static final int BUTTON_DISCONNECT_RES_ID = android.R.id.button2;
115c701401f8cec2e5309f8b57e2b97baced5093274Dan Stoza    static final int BUTTON_STOP_RES_ID = android.R.id.button1;
116c3ebe66b49cfba035e1fd0e160a13db38eb81b0eRiley Andrews
11740482ff650751819d4104c10a30974838168438cChavi Weingarten    final MediaRouter mRouter;
11840482ff650751819d4104c10a30974838168438cChavi Weingarten    private final MediaRouterCallback mCallback;
11940482ff650751819d4104c10a30974838168438cChavi Weingarten    final MediaRouter.RouteInfo mRoute;
12040482ff650751819d4104c10a30974838168438cChavi Weingarten
12140482ff650751819d4104c10a30974838168438cChavi Weingarten    Context mContext;
12240482ff650751819d4104c10a30974838168438cChavi Weingarten    private boolean mCreated;
12340482ff650751819d4104c10a30974838168438cChavi Weingarten    private boolean mAttachedToWindow;
12440482ff650751819d4104c10a30974838168438cChavi Weingarten
12540482ff650751819d4104c10a30974838168438cChavi Weingarten    private int mDialogContentWidth;
12640482ff650751819d4104c10a30974838168438cChavi Weingarten
12740482ff650751819d4104c10a30974838168438cChavi Weingarten    private View mCustomControlView;
12840482ff650751819d4104c10a30974838168438cChavi Weingarten
12940482ff650751819d4104c10a30974838168438cChavi Weingarten    private Button mDisconnectButton;
13040482ff650751819d4104c10a30974838168438cChavi Weingarten    private Button mStopCastingButton;
1312a9fc493dfdba67108e4335bb1fe931bc1e2a025Mathias Agopian    private ImageButton mPlaybackControlButton;
1322a9fc493dfdba67108e4335bb1fe931bc1e2a025Mathias Agopian    private ImageButton mCloseButton;
133a76b271f0e14325fa0ebb98e1cac0a15adfea1cbchaviw    private MediaRouteExpandCollapseButton mGroupExpandCollapseButton;
13440482ff650751819d4104c10a30974838168438cChavi Weingarten
135578038fc49f83c4c8c4accdce49df404ecd6ad02Robert Carr    private FrameLayout mExpandableAreaLayout;
136a76b271f0e14325fa0ebb98e1cac0a15adfea1cbchaviw    private LinearLayout mDialogAreaLayout;
137a76b271f0e14325fa0ebb98e1cac0a15adfea1cbchaviw    FrameLayout mDefaultControlLayout;
138a76b271f0e14325fa0ebb98e1cac0a15adfea1cbchaviw    private FrameLayout mCustomControlLayout;
1397206d49b2963ce2e926a5f25fe94aca9c06471e6chaviw    private ImageView mArtView;
1407206d49b2963ce2e926a5f25fe94aca9c06471e6chaviw    private TextView mTitleView;
141578038fc49f83c4c8c4accdce49df404ecd6ad02Robert Carr    private TextView mSubtitleView;
14240482ff650751819d4104c10a30974838168438cChavi Weingarten    private TextView mRouteNameTextView;
14340482ff650751819d4104c10a30974838168438cChavi Weingarten
14440482ff650751819d4104c10a30974838168438cChavi Weingarten    private boolean mVolumeControlEnabled = true;
14540482ff650751819d4104c10a30974838168438cChavi Weingarten    // Layout for media controllers including play/pause button and the main volume slider.
14640482ff650751819d4104c10a30974838168438cChavi Weingarten    private LinearLayout mMediaMainControlLayout;
14740482ff650751819d4104c10a30974838168438cChavi Weingarten    private RelativeLayout mPlaybackControlLayout;
14840482ff650751819d4104c10a30974838168438cChavi Weingarten    private LinearLayout mVolumeControlLayout;
14940482ff650751819d4104c10a30974838168438cChavi Weingarten    private View mDividerView;
15040482ff650751819d4104c10a30974838168438cChavi Weingarten
15140482ff650751819d4104c10a30974838168438cChavi Weingarten    OverlayListView mVolumeGroupList;
15240482ff650751819d4104c10a30974838168438cChavi Weingarten    VolumeGroupAdapter mVolumeGroupAdapter;
15340482ff650751819d4104c10a30974838168438cChavi Weingarten    private List<MediaRouter.RouteInfo> mGroupMemberRoutes;
15440482ff650751819d4104c10a30974838168438cChavi Weingarten    Set<MediaRouter.RouteInfo> mGroupMemberRoutesAdded;
15540482ff650751819d4104c10a30974838168438cChavi Weingarten    private Set<MediaRouter.RouteInfo> mGroupMemberRoutesRemoved;
15640482ff650751819d4104c10a30974838168438cChavi Weingarten    Set<MediaRouter.RouteInfo> mGroupMemberRoutesAnimatingWithBitmap;
157a76b271f0e14325fa0ebb98e1cac0a15adfea1cbchaviw    SeekBar mVolumeSlider;
158a76b271f0e14325fa0ebb98e1cac0a15adfea1cbchaviw    VolumeChangeListener mVolumeChangeListener;
159582270d69db94286a248bd829f1ae6f910d45124Jamie Gennis    MediaRouter.RouteInfo mRouteInVolumeSliderTouched;
1602adaf04fab35cf47c824d74d901b54094e01ccd3Andy McFadden    private int mVolumeGroupListItemIconSize;
161134f0422866e8985188ed10dfbdcb8e6c34b87f7Jamie Gennis    private int mVolumeGroupListItemHeight;
162134f0422866e8985188ed10dfbdcb8e6c34b87f7Jamie Gennis    private int mVolumeGroupListMaxHeight;
163134f0422866e8985188ed10dfbdcb8e6c34b87f7Jamie Gennis    private final int mVolumeGroupListPaddingTop;
164134f0422866e8985188ed10dfbdcb8e6c34b87f7Jamie Gennis    Map<MediaRouter.RouteInfo, SeekBar> mVolumeSliderMap;
165134f0422866e8985188ed10dfbdcb8e6c34b87f7Jamie Gennis
166134f0422866e8985188ed10dfbdcb8e6c34b87f7Jamie Gennis    MediaControllerCompat mMediaController;
167e6f43ddce78d6846af12550ff9193c5c6fe5844bSteve Block    MediaControllerCallback mControllerCallback;
168134f0422866e8985188ed10dfbdcb8e6c34b87f7Jamie Gennis    PlaybackStateCompat mState;
169134f0422866e8985188ed10dfbdcb8e6c34b87f7Jamie Gennis    MediaDescriptionCompat mDescription;
170134f0422866e8985188ed10dfbdcb8e6c34b87f7Jamie Gennis
1712ea926bda2fa30f3ba8d0ed2d2395a8ada952e6eMarco Nelissen    FetchArtTask mFetchArtTask;
172134f0422866e8985188ed10dfbdcb8e6c34b87f7Jamie Gennis    Bitmap mArtIconBitmap;
173e6f43ddce78d6846af12550ff9193c5c6fe5844bSteve Block    Uri mArtIconUri;
174582270d69db94286a248bd829f1ae6f910d45124Jamie Gennis    boolean mArtIconIsLoaded;
175134f0422866e8985188ed10dfbdcb8e6c34b87f7Jamie Gennis    Bitmap mArtIconLoadedBitmap;
176134f0422866e8985188ed10dfbdcb8e6c34b87f7Jamie Gennis    int mArtIconBackgroundColor;
177134f0422866e8985188ed10dfbdcb8e6c34b87f7Jamie Gennis
178134f0422866e8985188ed10dfbdcb8e6c34b87f7Jamie Gennis    boolean mHasPendingUpdate;
179134f0422866e8985188ed10dfbdcb8e6c34b87f7Jamie Gennis    boolean mPendingUpdateAnimationNeeded;
180e6f43ddce78d6846af12550ff9193c5c6fe5844bSteve Block
181582270d69db94286a248bd829f1ae6f910d45124Jamie Gennis    boolean mIsGroupExpanded;
182134f0422866e8985188ed10dfbdcb8e6c34b87f7Jamie Gennis    boolean mIsGroupListAnimating;
183134f0422866e8985188ed10dfbdcb8e6c34b87f7Jamie Gennis    boolean mIsGroupListAnimationPending;
184134f0422866e8985188ed10dfbdcb8e6c34b87f7Jamie Gennis    int mGroupListAnimationDurationMs;
185134f0422866e8985188ed10dfbdcb8e6c34b87f7Jamie Gennis    private int mGroupListFadeInDurationMs;
186134f0422866e8985188ed10dfbdcb8e6c34b87f7Jamie Gennis    private int mGroupListFadeOutDurationMs;
187e6f43ddce78d6846af12550ff9193c5c6fe5844bSteve Block
188582270d69db94286a248bd829f1ae6f910d45124Jamie Gennis    private Interpolator mInterpolator;
189134f0422866e8985188ed10dfbdcb8e6c34b87f7Jamie Gennis    private Interpolator mLinearOutSlowInInterpolator;
190134f0422866e8985188ed10dfbdcb8e6c34b87f7Jamie Gennis    private Interpolator mFastOutSlowInInterpolator;
191134f0422866e8985188ed10dfbdcb8e6c34b87f7Jamie Gennis    private Interpolator mAccelerateDecelerateInterpolator;
192134f0422866e8985188ed10dfbdcb8e6c34b87f7Jamie Gennis
193d0566bc26fcf6ca396118701fa11900b627f2c09Mathias Agopian    final AccessibilityManager mAccessibilityManager;
1946b376713907086c9642e7b7e66e51ddfa531b003Brian Anderson
1956b376713907086c9642e7b7e66e51ddfa531b003Brian Anderson    Runnable mGroupListFadeInAnimation = new Runnable() {
1966b376713907086c9642e7b7e66e51ddfa531b003Brian Anderson        @Override
1976b376713907086c9642e7b7e66e51ddfa531b003Brian Anderson        public void run() {
1986b376713907086c9642e7b7e66e51ddfa531b003Brian Anderson            startGroupListFadeInAnimation();
1996b376713907086c9642e7b7e66e51ddfa531b003Brian Anderson        }
2006b376713907086c9642e7b7e66e51ddfa531b003Brian Anderson    };
2016b376713907086c9642e7b7e66e51ddfa531b003Brian Anderson
2026b376713907086c9642e7b7e66e51ddfa531b003Brian Anderson    public MediaRouteControllerDialog(Context context) {
2036b376713907086c9642e7b7e66e51ddfa531b003Brian Anderson        this(context, 0);
2046b376713907086c9642e7b7e66e51ddfa531b003Brian Anderson    }
2056b376713907086c9642e7b7e66e51ddfa531b003Brian Anderson
2066b376713907086c9642e7b7e66e51ddfa531b003Brian Anderson    public MediaRouteControllerDialog(Context context, int theme) {
2076b376713907086c9642e7b7e66e51ddfa531b003Brian Anderson        super(context = MediaRouterThemeHelper.createThemedDialogContext(context, theme, true),
2086b376713907086c9642e7b7e66e51ddfa531b003Brian Anderson                MediaRouterThemeHelper.createThemedDialogStyle(context));
2096b376713907086c9642e7b7e66e51ddfa531b003Brian Anderson        mContext = getContext();
2106b376713907086c9642e7b7e66e51ddfa531b003Brian Anderson
2116b376713907086c9642e7b7e66e51ddfa531b003Brian Anderson        mControllerCallback = new MediaControllerCallback();
2126b376713907086c9642e7b7e66e51ddfa531b003Brian Anderson        mRouter = MediaRouter.getInstance(mContext);
2136b376713907086c9642e7b7e66e51ddfa531b003Brian Anderson        mCallback = new MediaRouterCallback();
2146b376713907086c9642e7b7e66e51ddfa531b003Brian Anderson        mRoute = mRouter.getSelectedRoute();
2156b376713907086c9642e7b7e66e51ddfa531b003Brian Anderson        setMediaSession(mRouter.getMediaSessionToken());
2166b376713907086c9642e7b7e66e51ddfa531b003Brian Anderson        mVolumeGroupListPaddingTop = mContext.getResources().getDimensionPixelSize(
2176b376713907086c9642e7b7e66e51ddfa531b003Brian Anderson                R.dimen.mr_controller_volume_group_list_padding_top);
2186b376713907086c9642e7b7e66e51ddfa531b003Brian Anderson        mAccessibilityManager =
2196b376713907086c9642e7b7e66e51ddfa531b003Brian Anderson                (AccessibilityManager) mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
2206b376713907086c9642e7b7e66e51ddfa531b003Brian Anderson        if (android.os.Build.VERSION.SDK_INT >= 21) {
2216b376713907086c9642e7b7e66e51ddfa531b003Brian Anderson            mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
2226b376713907086c9642e7b7e66e51ddfa531b003Brian Anderson                    R.interpolator.mr_linear_out_slow_in);
2236b376713907086c9642e7b7e66e51ddfa531b003Brian Anderson            mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
2246b376713907086c9642e7b7e66e51ddfa531b003Brian Anderson                    R.interpolator.mr_fast_out_slow_in);
2256b376713907086c9642e7b7e66e51ddfa531b003Brian Anderson        }
2266b376713907086c9642e7b7e66e51ddfa531b003Brian Anderson        mAccelerateDecelerateInterpolator = new AccelerateDecelerateInterpolator();
2276b376713907086c9642e7b7e66e51ddfa531b003Brian Anderson    }
2286b376713907086c9642e7b7e66e51ddfa531b003Brian Anderson
2296b376713907086c9642e7b7e66e51ddfa531b003Brian Anderson    /**
2306b376713907086c9642e7b7e66e51ddfa531b003Brian Anderson     * Gets the route that this dialog is controlling.
2316b376713907086c9642e7b7e66e51ddfa531b003Brian Anderson     */
2326b376713907086c9642e7b7e66e51ddfa531b003Brian Anderson    public MediaRouter.RouteInfo getRoute() {
2336b376713907086c9642e7b7e66e51ddfa531b003Brian Anderson        return mRoute;
2346b376713907086c9642e7b7e66e51ddfa531b003Brian Anderson    }
2356b376713907086c9642e7b7e66e51ddfa531b003Brian Anderson
2366b376713907086c9642e7b7e66e51ddfa531b003Brian Anderson    private MediaRouter.RouteGroup getGroup() {
2376b376713907086c9642e7b7e66e51ddfa531b003Brian Anderson        if (mRoute instanceof MediaRouter.RouteGroup) {
238b1e2f8deb38353e4bcc9d3ef06bc15bd5e417425Jorim Jaggi            return (MediaRouter.RouteGroup) mRoute;
239d0566bc26fcf6ca396118701fa11900b627f2c09Mathias Agopian        }
240d0566bc26fcf6ca396118701fa11900b627f2c09Mathias Agopian        return null;
241d0566bc26fcf6ca396118701fa11900b627f2c09Mathias Agopian    }
242d0566bc26fcf6ca396118701fa11900b627f2c09Mathias Agopian
243d0566bc26fcf6ca396118701fa11900b627f2c09Mathias Agopian    /**
244d0566bc26fcf6ca396118701fa11900b627f2c09Mathias Agopian     * Provides the subclass an opportunity to create a view that will replace the default media
245d0566bc26fcf6ca396118701fa11900b627f2c09Mathias Agopian     * controls for the currently playing content.
246d0566bc26fcf6ca396118701fa11900b627f2c09Mathias Agopian     *
247b1e2f8deb38353e4bcc9d3ef06bc15bd5e417425Jorim Jaggi     * @param savedInstanceState The dialog's saved instance state.
248d0566bc26fcf6ca396118701fa11900b627f2c09Mathias Agopian     * @return The media control view, or null if none.
249d0566bc26fcf6ca396118701fa11900b627f2c09Mathias Agopian     */
250d0566bc26fcf6ca396118701fa11900b627f2c09Mathias Agopian    public View onCreateMediaControlView(Bundle savedInstanceState) {
251d0566bc26fcf6ca396118701fa11900b627f2c09Mathias Agopian        return null;
252e6f43ddce78d6846af12550ff9193c5c6fe5844bSteve Block    }
253d0566bc26fcf6ca396118701fa11900b627f2c09Mathias Agopian
254d0566bc26fcf6ca396118701fa11900b627f2c09Mathias Agopian    /**
255d0566bc26fcf6ca396118701fa11900b627f2c09Mathias Agopian     * Gets the media control view that was created by {@link #onCreateMediaControlView(Bundle)}.
256d0566bc26fcf6ca396118701fa11900b627f2c09Mathias Agopian     *
257d0566bc26fcf6ca396118701fa11900b627f2c09Mathias Agopian     * @return The media control view, or null if none.
258d0566bc26fcf6ca396118701fa11900b627f2c09Mathias Agopian     */
2598e533069e5721e55cb9768e140e16546c3a4a8b6Colin Cross    public View getMediaControlView() {
260dd3cb84cfbe8068790c6233b5829fae9c4a0ee93Jamie Gennis        return mCustomControlView;
261e57f292595bec48f65c8088b00ff6beea01217e9Mathias Agopian    }
262e57f292595bec48f65c8088b00ff6beea01217e9Mathias Agopian
263e57f292595bec48f65c8088b00ff6beea01217e9Mathias Agopian    /**
2648dfa92fef9759a881e96ee58d59875d35023aab9Andy McFadden     * Sets whether to enable the volume slider and volume control using the volume keys
265dd3cb84cfbe8068790c6233b5829fae9c4a0ee93Jamie Gennis     * when the route supports it.
266e57f292595bec48f65c8088b00ff6beea01217e9Mathias Agopian     * <p>
267e57f292595bec48f65c8088b00ff6beea01217e9Mathias Agopian     * The default value is true.
268e57f292595bec48f65c8088b00ff6beea01217e9Mathias Agopian     * </p>
269e57f292595bec48f65c8088b00ff6beea01217e9Mathias Agopian     */
2706c913be9ca95fd6b556d056e165a4ba6dc69795bJesse Hall    public void setVolumeControlEnabled(boolean enable) {
2716c913be9ca95fd6b556d056e165a4ba6dc69795bJesse Hall        if (mVolumeControlEnabled != enable) {
2726c913be9ca95fd6b556d056e165a4ba6dc69795bJesse Hall            mVolumeControlEnabled = enable;
2736c913be9ca95fd6b556d056e165a4ba6dc69795bJesse Hall            if (mCreated) {
2746c913be9ca95fd6b556d056e165a4ba6dc69795bJesse Hall                update(false);
2756c913be9ca95fd6b556d056e165a4ba6dc69795bJesse Hall            }
2766c913be9ca95fd6b556d056e165a4ba6dc69795bJesse Hall        }
2776c913be9ca95fd6b556d056e165a4ba6dc69795bJesse Hall    }
278e57f292595bec48f65c8088b00ff6beea01217e9Mathias Agopian
279e57f292595bec48f65c8088b00ff6beea01217e9Mathias Agopian    /**
280e57f292595bec48f65c8088b00ff6beea01217e9Mathias Agopian     * Returns whether to enable the volume slider and volume control using the volume keys
281e57f292595bec48f65c8088b00ff6beea01217e9Mathias Agopian     * when the route supports it.
282e57f292595bec48f65c8088b00ff6beea01217e9Mathias Agopian     */
283e57f292595bec48f65c8088b00ff6beea01217e9Mathias Agopian    public boolean isVolumeControlEnabled() {
284e57f292595bec48f65c8088b00ff6beea01217e9Mathias Agopian        return mVolumeControlEnabled;
285e57f292595bec48f65c8088b00ff6beea01217e9Mathias Agopian    }
286e57f292595bec48f65c8088b00ff6beea01217e9Mathias Agopian
2872c9b11f0291210c9b9513a1a0cce6afebd361b3bPrashant Malani    /**
2888e533069e5721e55cb9768e140e16546c3a4a8b6Colin Cross     * Set the session to use for metadata and transport controls. The dialog
2898e533069e5721e55cb9768e140e16546c3a4a8b6Colin Cross     * will listen to changes on this session and update the UI automatically in
2908e533069e5721e55cb9768e140e16546c3a4a8b6Colin Cross     * response to changes.
291c01a79d77b829e7de86ef137694e8ad708470ca1Andy McFadden     *
2922c9b11f0291210c9b9513a1a0cce6afebd361b3bPrashant Malani     * @param sessionToken The token for the session to use.
2932c9b11f0291210c9b9513a1a0cce6afebd361b3bPrashant Malani     */
2948e533069e5721e55cb9768e140e16546c3a4a8b6Colin Cross    private void setMediaSession(MediaSessionCompat.Token sessionToken) {
2953094df359d1e6e2ae8ca4e935cc093f563804c96Mathias Agopian        if (mMediaController != null) {
2967f7da32569f8e0b3d383a40b95f8ac1d55afd801Dan Stoza            mMediaController.unregisterCallback(mControllerCallback);
2977f7da32569f8e0b3d383a40b95f8ac1d55afd801Dan Stoza            mMediaController = null;
298c666cae2d5995097ec49a87e375e2afdd92802b7Mathias Agopian        }
299c666cae2d5995097ec49a87e375e2afdd92802b7Mathias Agopian        if (sessionToken == null) {
300c666cae2d5995097ec49a87e375e2afdd92802b7Mathias Agopian            return;
3019d4e3d2f42e93e2d12bacabe97d307d30c3c20ddJeff Brown        }
3027f7da32569f8e0b3d383a40b95f8ac1d55afd801Dan Stoza        if (!mAttachedToWindow) {
3037f7da32569f8e0b3d383a40b95f8ac1d55afd801Dan Stoza            return;
3047f7da32569f8e0b3d383a40b95f8ac1d55afd801Dan Stoza        }
305d723bd7669b4fc88dc282d8bf8ba5ecb2849d22fDan Stoza        try {
3067f7da32569f8e0b3d383a40b95f8ac1d55afd801Dan Stoza            mMediaController = new MediaControllerCompat(mContext, sessionToken);
3077f7da32569f8e0b3d383a40b95f8ac1d55afd801Dan Stoza        } catch (RemoteException e) {
3087f7da32569f8e0b3d383a40b95f8ac1d55afd801Dan Stoza            Log.e(TAG, "Error creating media controller in setMediaSession.", e);
3097f7da32569f8e0b3d383a40b95f8ac1d55afd801Dan Stoza        }
3107f7da32569f8e0b3d383a40b95f8ac1d55afd801Dan Stoza        if (mMediaController != null) {
3117f7da32569f8e0b3d383a40b95f8ac1d55afd801Dan Stoza            mMediaController.registerCallback(mControllerCallback);
3127f7da32569f8e0b3d383a40b95f8ac1d55afd801Dan Stoza        }
3137f7da32569f8e0b3d383a40b95f8ac1d55afd801Dan Stoza        MediaMetadataCompat metadata = mMediaController == null ? null
3147f7da32569f8e0b3d383a40b95f8ac1d55afd801Dan Stoza                : mMediaController.getMetadata();
3157f7da32569f8e0b3d383a40b95f8ac1d55afd801Dan Stoza        mDescription = metadata == null ? null : metadata.getDescription();
3167f7da32569f8e0b3d383a40b95f8ac1d55afd801Dan Stoza        mState = mMediaController == null ? null : mMediaController.getPlaybackState();
31767d8bd66aaf04805cb8f2616ba964141b865e3b9Lajos Molnar        updateArtIconIfNeeded();
31867d8bd66aaf04805cb8f2616ba964141b865e3b9Lajos Molnar        update(false);
31967d8bd66aaf04805cb8f2616ba964141b865e3b9Lajos Molnar    }
32067d8bd66aaf04805cb8f2616ba964141b865e3b9Lajos Molnar
32167d8bd66aaf04805cb8f2616ba964141b865e3b9Lajos Molnar    /**
32267d8bd66aaf04805cb8f2616ba964141b865e3b9Lajos Molnar     * Gets the session to use for metadata and transport controls.
32367d8bd66aaf04805cb8f2616ba964141b865e3b9Lajos Molnar     *
32467d8bd66aaf04805cb8f2616ba964141b865e3b9Lajos Molnar     * @return The token for the session to use or null if none.
32567d8bd66aaf04805cb8f2616ba964141b865e3b9Lajos Molnar     */
32667d8bd66aaf04805cb8f2616ba964141b865e3b9Lajos Molnar    public MediaSessionCompat.Token getMediaSession() {
32767d8bd66aaf04805cb8f2616ba964141b865e3b9Lajos Molnar        return mMediaController == null ? null : mMediaController.getSessionToken();
32867d8bd66aaf04805cb8f2616ba964141b865e3b9Lajos Molnar    }
32967d8bd66aaf04805cb8f2616ba964141b865e3b9Lajos Molnar
33067d8bd66aaf04805cb8f2616ba964141b865e3b9Lajos Molnar    @Override
33167d8bd66aaf04805cb8f2616ba964141b865e3b9Lajos Molnar    protected void onCreate(Bundle savedInstanceState) {
33267d8bd66aaf04805cb8f2616ba964141b865e3b9Lajos Molnar        super.onCreate(savedInstanceState);
3337f7da32569f8e0b3d383a40b95f8ac1d55afd801Dan Stoza
3347f7da32569f8e0b3d383a40b95f8ac1d55afd801Dan Stoza        getWindow().setBackgroundDrawableResource(android.R.color.transparent);
3357f7da32569f8e0b3d383a40b95f8ac1d55afd801Dan Stoza        setContentView(R.layout.mr_controller_material_dialog_b);
3367f7da32569f8e0b3d383a40b95f8ac1d55afd801Dan Stoza
3377f7da32569f8e0b3d383a40b95f8ac1d55afd801Dan Stoza        // Remove the neutral button.
3387f7da32569f8e0b3d383a40b95f8ac1d55afd801Dan Stoza        findViewById(BUTTON_NEUTRAL_RES_ID).setVisibility(View.GONE);
3397f7da32569f8e0b3d383a40b95f8ac1d55afd801Dan Stoza
3407f7da32569f8e0b3d383a40b95f8ac1d55afd801Dan Stoza        ClickListener listener = new ClickListener();
3417f7da32569f8e0b3d383a40b95f8ac1d55afd801Dan Stoza
3427f7da32569f8e0b3d383a40b95f8ac1d55afd801Dan Stoza        mExpandableAreaLayout = findViewById(R.id.mr_expandable_area);
3437f7da32569f8e0b3d383a40b95f8ac1d55afd801Dan Stoza        mExpandableAreaLayout.setOnClickListener(new View.OnClickListener() {
3447f7da32569f8e0b3d383a40b95f8ac1d55afd801Dan Stoza            @Override
3457f7da32569f8e0b3d383a40b95f8ac1d55afd801Dan Stoza            public void onClick(View v) {
3467f7da32569f8e0b3d383a40b95f8ac1d55afd801Dan Stoza                dismiss();
3477f7da32569f8e0b3d383a40b95f8ac1d55afd801Dan Stoza            }
3487f7da32569f8e0b3d383a40b95f8ac1d55afd801Dan Stoza        });
349c666cae2d5995097ec49a87e375e2afdd92802b7Mathias Agopian        mDialogAreaLayout = findViewById(R.id.mr_dialog_area);
350c666cae2d5995097ec49a87e375e2afdd92802b7Mathias Agopian        mDialogAreaLayout.setOnClickListener(new View.OnClickListener() {
351d85084b2b65828442eafaff9b811e9b6c9ca9fadSvetoslav            @Override
35228f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright            public void onClick(View v) {
353a52f0295622a42849f5ef81c44589b816b2ccacbPeiyong Lin                // Eat unhandled touch events.
35428f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright            }
35528f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright        });
35628f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright        int color = MediaRouterThemeHelper.getButtonTextColor(mContext);
35728f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright        mDisconnectButton = findViewById(BUTTON_DISCONNECT_RES_ID);
35828f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright        mDisconnectButton.setText(R.string.mr_controller_disconnect);
35928f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright        mDisconnectButton.setTextColor(color);
36028f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright        mDisconnectButton.setOnClickListener(listener);
36128f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright
36228f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright        mStopCastingButton = findViewById(BUTTON_STOP_RES_ID);
36328f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright        mStopCastingButton.setText(R.string.mr_controller_stop_casting);
36428f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright        mStopCastingButton.setTextColor(color);
36528f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright        mStopCastingButton.setOnClickListener(listener);
36628f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright
36728f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright        mRouteNameTextView = findViewById(R.id.mr_name);
36828f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright        mCloseButton = findViewById(R.id.mr_close);
36928f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright        mCloseButton.setOnClickListener(listener);
37028f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright        mCustomControlLayout = findViewById(R.id.mr_custom_control);
37128f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright        mDefaultControlLayout = findViewById(R.id.mr_default_control);
37228f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright
37328f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright        // Start the session activity when a content item (album art, title or subtitle) is clicked.
37428f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright        View.OnClickListener onClickListener = new View.OnClickListener() {
37528f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright            @Override
376a52f0295622a42849f5ef81c44589b816b2ccacbPeiyong Lin            public void onClick(View v) {
37728f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright                if (mMediaController != null) {
37828f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright                    PendingIntent pi = mMediaController.getSessionActivity();
37928f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright                    if (pi != null) {
38028f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright                        try {
38128f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright                            pi.send();
382a52f0295622a42849f5ef81c44589b816b2ccacbPeiyong Lin                            dismiss();
38328f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright                        } catch (PendingIntent.CanceledException e) {
38428f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright                            Log.e(TAG, pi + " was not sent, it had been canceled.");
38528f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright                        }
38628f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright                    }
387a52f0295622a42849f5ef81c44589b816b2ccacbPeiyong Lin                }
38828f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright            }
38928f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright        };
39028f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright        mArtView = findViewById(R.id.mr_art);
39128f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright        mArtView.setOnClickListener(onClickListener);
392a52f0295622a42849f5ef81c44589b816b2ccacbPeiyong Lin        findViewById(R.id.mr_control_title_container).setOnClickListener(onClickListener);
39328f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright
39428f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright        mMediaMainControlLayout = findViewById(R.id.mr_media_main_control);
39528f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright        mDividerView = findViewById(R.id.mr_control_divider);
39628f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright
397a52f0295622a42849f5ef81c44589b816b2ccacbPeiyong Lin        mPlaybackControlLayout = findViewById(R.id.mr_playback_control);
39828f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright        mTitleView = findViewById(R.id.mr_control_title);
399a52f0295622a42849f5ef81c44589b816b2ccacbPeiyong Lin        mSubtitleView = findViewById(R.id.mr_control_subtitle);
40028f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright        mPlaybackControlButton = findViewById(R.id.mr_control_playback_ctrl);
40128f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright        mPlaybackControlButton.setOnClickListener(listener);
40228f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright
403a52f0295622a42849f5ef81c44589b816b2ccacbPeiyong Lin        mVolumeControlLayout = findViewById(R.id.mr_volume_control);
40428f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright        mVolumeControlLayout.setVisibility(View.GONE);
40528f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright        mVolumeSlider = findViewById(R.id.mr_volume_slider);
40628f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright        mVolumeSlider.setTag(mRoute);
40728f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright        mVolumeChangeListener = new VolumeChangeListener();
40828f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright        mVolumeSlider.setOnSeekBarChangeListener(mVolumeChangeListener);
40928f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright
41028f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright        mVolumeGroupList = findViewById(R.id.mr_volume_group_list);
41128f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright        mGroupMemberRoutes = new ArrayList<MediaRouter.RouteInfo>();
41228f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright        mVolumeGroupAdapter = new VolumeGroupAdapter(mVolumeGroupList.getContext(),
41328f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright                mGroupMemberRoutes);
41428f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright        mVolumeGroupList.setAdapter(mVolumeGroupAdapter);
415a52f0295622a42849f5ef81c44589b816b2ccacbPeiyong Lin        mGroupMemberRoutesAnimatingWithBitmap = new HashSet<>();
41628f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright
41728f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright        MediaRouterThemeHelper.setMediaControlsBackgroundColor(mContext,
41828f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright                mMediaMainControlLayout, mVolumeGroupList, getGroup() != null);
41928f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright        MediaRouterThemeHelper.setVolumeSliderColor(mContext,
42028f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright                (MediaRouteVolumeSlider) mVolumeSlider, mMediaMainControlLayout);
42128f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright        mVolumeSliderMap = new HashMap<>();
42228f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright        mVolumeSliderMap.put(mRoute, mVolumeSlider);
42328f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright
42428f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright        mGroupExpandCollapseButton =
42528f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright                findViewById(R.id.mr_group_expand_collapse);
42628f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright        mGroupExpandCollapseButton.setOnClickListener(new View.OnClickListener() {
42728f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright            @Override
428d85084b2b65828442eafaff9b811e9b6c9ca9fadSvetoslav            public void onClick(View v) {
429d85084b2b65828442eafaff9b811e9b6c9ca9fadSvetoslav                mIsGroupExpanded = !mIsGroupExpanded;
430d85084b2b65828442eafaff9b811e9b6c9ca9fadSvetoslav                if (mIsGroupExpanded) {
431d85084b2b65828442eafaff9b811e9b6c9ca9fadSvetoslav                    mVolumeGroupList.setVisibility(View.VISIBLE);
432d85084b2b65828442eafaff9b811e9b6c9ca9fadSvetoslav                }
433d85084b2b65828442eafaff9b811e9b6c9ca9fadSvetoslav                loadInterpolator();
434d85084b2b65828442eafaff9b811e9b6c9ca9fadSvetoslav                updateLayoutHeight(true);
435d85084b2b65828442eafaff9b811e9b6c9ca9fadSvetoslav            }
436d85084b2b65828442eafaff9b811e9b6c9ca9fadSvetoslav        });
437d85084b2b65828442eafaff9b811e9b6c9ca9fadSvetoslav        loadInterpolator();
438d85084b2b65828442eafaff9b811e9b6c9ca9fadSvetoslav        mGroupListAnimationDurationMs = mContext.getResources().getInteger(
439d85084b2b65828442eafaff9b811e9b6c9ca9fadSvetoslav                R.integer.mr_controller_volume_group_list_animation_duration_ms);
440d85084b2b65828442eafaff9b811e9b6c9ca9fadSvetoslav        mGroupListFadeInDurationMs = mContext.getResources().getInteger(
441d85084b2b65828442eafaff9b811e9b6c9ca9fadSvetoslav                R.integer.mr_controller_volume_group_list_fade_in_duration_ms);
442c4f471e75a8ec64ec34e3f2944a5a756215d0becDan Stoza        mGroupListFadeOutDurationMs = mContext.getResources().getInteger(
443c4f471e75a8ec64ec34e3f2944a5a756215d0becDan Stoza                R.integer.mr_controller_volume_group_list_fade_out_duration_ms);
444c4f471e75a8ec64ec34e3f2944a5a756215d0becDan Stoza
445c4f471e75a8ec64ec34e3f2944a5a756215d0becDan Stoza        mCustomControlView = onCreateMediaControlView(savedInstanceState);
446c4f471e75a8ec64ec34e3f2944a5a756215d0becDan Stoza        if (mCustomControlView != null) {
447c4f471e75a8ec64ec34e3f2944a5a756215d0becDan Stoza            mCustomControlLayout.addView(mCustomControlView);
448c4f471e75a8ec64ec34e3f2944a5a756215d0becDan Stoza            mCustomControlLayout.setVisibility(View.VISIBLE);
449c4f471e75a8ec64ec34e3f2944a5a756215d0becDan Stoza        }
450c4f471e75a8ec64ec34e3f2944a5a756215d0becDan Stoza        mCreated = true;
451c4f471e75a8ec64ec34e3f2944a5a756215d0becDan Stoza        updateLayout();
452c4f471e75a8ec64ec34e3f2944a5a756215d0becDan Stoza    }
453c4f471e75a8ec64ec34e3f2944a5a756215d0becDan Stoza
454c4f471e75a8ec64ec34e3f2944a5a756215d0becDan Stoza    /**
455c4f471e75a8ec64ec34e3f2944a5a756215d0becDan Stoza     * Sets the width of the dialog. Also called when configuration changes.
456c4f471e75a8ec64ec34e3f2944a5a756215d0becDan Stoza     */
457c4f471e75a8ec64ec34e3f2944a5a756215d0becDan Stoza    void updateLayout() {
458c4f471e75a8ec64ec34e3f2944a5a756215d0becDan Stoza        int width = MediaRouteDialogHelper.getDialogWidth(mContext);
459c4f471e75a8ec64ec34e3f2944a5a756215d0becDan Stoza        getWindow().setLayout(width, ViewGroup.LayoutParams.WRAP_CONTENT);
460e1f5e6fd7ad07778fa4381059852a94567d77dfcMathias Agopian
461c4f471e75a8ec64ec34e3f2944a5a756215d0becDan Stoza        View decorView = getWindow().getDecorView();
462c4f471e75a8ec64ec34e3f2944a5a756215d0becDan Stoza        mDialogContentWidth = width - decorView.getPaddingLeft() - decorView.getPaddingRight();
463c4f471e75a8ec64ec34e3f2944a5a756215d0becDan Stoza
464c1ba5c4649554e744844b07cfe402b42fbe12ff3Sahil Dhanju        Resources res = mContext.getResources();
465c1ba5c4649554e744844b07cfe402b42fbe12ff3Sahil Dhanju        mVolumeGroupListItemIconSize = res.getDimensionPixelSize(
466c1ba5c4649554e744844b07cfe402b42fbe12ff3Sahil Dhanju                R.dimen.mr_controller_volume_group_list_item_icon_size);
467c1ba5c4649554e744844b07cfe402b42fbe12ff3Sahil Dhanju        mVolumeGroupListItemHeight = res.getDimensionPixelSize(
468c1ba5c4649554e744844b07cfe402b42fbe12ff3Sahil Dhanju                R.dimen.mr_controller_volume_group_list_item_height);
469c1ba5c4649554e744844b07cfe402b42fbe12ff3Sahil Dhanju        mVolumeGroupListMaxHeight = res.getDimensionPixelSize(
470c1ba5c4649554e744844b07cfe402b42fbe12ff3Sahil Dhanju                R.dimen.mr_controller_volume_group_list_max_height);
471c1ba5c4649554e744844b07cfe402b42fbe12ff3Sahil Dhanju
472c1ba5c4649554e744844b07cfe402b42fbe12ff3Sahil Dhanju        // Fetch art icons again for layout changes to resize it accordingly
473c1ba5c4649554e744844b07cfe402b42fbe12ff3Sahil Dhanju        mArtIconBitmap = null;
474c1ba5c4649554e744844b07cfe402b42fbe12ff3Sahil Dhanju        mArtIconUri = null;
475c1ba5c4649554e744844b07cfe402b42fbe12ff3Sahil Dhanju        updateArtIconIfNeeded();
476c1ba5c4649554e744844b07cfe402b42fbe12ff3Sahil Dhanju        update(false);
477c1ba5c4649554e744844b07cfe402b42fbe12ff3Sahil Dhanju    }
478c1ba5c4649554e744844b07cfe402b42fbe12ff3Sahil Dhanju
479c1ba5c4649554e744844b07cfe402b42fbe12ff3Sahil Dhanju    @Override
480c1ba5c4649554e744844b07cfe402b42fbe12ff3Sahil Dhanju    public void onAttachedToWindow() {
481c1ba5c4649554e744844b07cfe402b42fbe12ff3Sahil Dhanju        super.onAttachedToWindow();
482c1ba5c4649554e744844b07cfe402b42fbe12ff3Sahil Dhanju        mAttachedToWindow = true;
483c1ba5c4649554e744844b07cfe402b42fbe12ff3Sahil Dhanju
484c1ba5c4649554e744844b07cfe402b42fbe12ff3Sahil Dhanju        mRouter.addCallback(MediaRouteSelector.EMPTY, mCallback,
485c1ba5c4649554e744844b07cfe402b42fbe12ff3Sahil Dhanju                MediaRouter.CALLBACK_FLAG_UNFILTERED_EVENTS);
486c1ba5c4649554e744844b07cfe402b42fbe12ff3Sahil Dhanju        setMediaSession(mRouter.getMediaSessionToken());
487c1ba5c4649554e744844b07cfe402b42fbe12ff3Sahil Dhanju    }
488c1ba5c4649554e744844b07cfe402b42fbe12ff3Sahil Dhanju
489c1ba5c4649554e744844b07cfe402b42fbe12ff3Sahil Dhanju    @Override
490c1ba5c4649554e744844b07cfe402b42fbe12ff3Sahil Dhanju    public void onDetachedFromWindow() {
491c1ba5c4649554e744844b07cfe402b42fbe12ff3Sahil Dhanju        mRouter.removeCallback(mCallback);
492c1ba5c4649554e744844b07cfe402b42fbe12ff3Sahil Dhanju        setMediaSession(null);
493c1ba5c4649554e744844b07cfe402b42fbe12ff3Sahil Dhanju        mAttachedToWindow = false;
494c1ba5c4649554e744844b07cfe402b42fbe12ff3Sahil Dhanju        super.onDetachedFromWindow();
495c1ba5c4649554e744844b07cfe402b42fbe12ff3Sahil Dhanju    }
496c1ba5c4649554e744844b07cfe402b42fbe12ff3Sahil Dhanju
497c1ba5c4649554e744844b07cfe402b42fbe12ff3Sahil Dhanju    @Override
498c1ba5c4649554e744844b07cfe402b42fbe12ff3Sahil Dhanju    public boolean onKeyDown(int keyCode, KeyEvent event) {
499c1ba5c4649554e744844b07cfe402b42fbe12ff3Sahil Dhanju        if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN
500c1ba5c4649554e744844b07cfe402b42fbe12ff3Sahil Dhanju                || keyCode == KeyEvent.KEYCODE_VOLUME_UP) {
501c1ba5c4649554e744844b07cfe402b42fbe12ff3Sahil Dhanju            mRoute.requestUpdateVolume(keyCode == KeyEvent.KEYCODE_VOLUME_DOWN ? -1 : 1);
502c1ba5c4649554e744844b07cfe402b42fbe12ff3Sahil Dhanju            return true;
503c1ba5c4649554e744844b07cfe402b42fbe12ff3Sahil Dhanju        }
504c1ba5c4649554e744844b07cfe402b42fbe12ff3Sahil Dhanju        return super.onKeyDown(keyCode, event);
505c1ba5c4649554e744844b07cfe402b42fbe12ff3Sahil Dhanju    }
506a099a24c93bfa599fc5c36a647e946c26f68514fKalle Raita
507a099a24c93bfa599fc5c36a647e946c26f68514fKalle Raita    @Override
508a099a24c93bfa599fc5c36a647e946c26f68514fKalle Raita    public boolean onKeyUp(int keyCode, KeyEvent event) {
509a099a24c93bfa599fc5c36a647e946c26f68514fKalle Raita        if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN
510a099a24c93bfa599fc5c36a647e946c26f68514fKalle Raita                || keyCode == KeyEvent.KEYCODE_VOLUME_UP) {
511a099a24c93bfa599fc5c36a647e946c26f68514fKalle Raita            return true;
512a099a24c93bfa599fc5c36a647e946c26f68514fKalle Raita        }
513a099a24c93bfa599fc5c36a647e946c26f68514fKalle Raita        return super.onKeyUp(keyCode, event);
514a099a24c93bfa599fc5c36a647e946c26f68514fKalle Raita    }
515a099a24c93bfa599fc5c36a647e946c26f68514fKalle Raita
516a099a24c93bfa599fc5c36a647e946c26f68514fKalle Raita    void update(boolean animate) {
517a099a24c93bfa599fc5c36a647e946c26f68514fKalle Raita        // Defer dialog updates if a user is adjusting a volume in the list
518a099a24c93bfa599fc5c36a647e946c26f68514fKalle Raita        if (mRouteInVolumeSliderTouched != null) {
519a099a24c93bfa599fc5c36a647e946c26f68514fKalle Raita            mHasPendingUpdate = true;
520a099a24c93bfa599fc5c36a647e946c26f68514fKalle Raita            mPendingUpdateAnimationNeeded |= animate;
521a099a24c93bfa599fc5c36a647e946c26f68514fKalle Raita            return;
522a099a24c93bfa599fc5c36a647e946c26f68514fKalle Raita        }
523a099a24c93bfa599fc5c36a647e946c26f68514fKalle Raita        mHasPendingUpdate = false;
524a099a24c93bfa599fc5c36a647e946c26f68514fKalle Raita        mPendingUpdateAnimationNeeded = false;
525a099a24c93bfa599fc5c36a647e946c26f68514fKalle Raita        if (!mRoute.isSelected() || mRoute.isDefaultOrBluetooth()) {
526a099a24c93bfa599fc5c36a647e946c26f68514fKalle Raita            dismiss();
527a099a24c93bfa599fc5c36a647e946c26f68514fKalle Raita            return;
528a099a24c93bfa599fc5c36a647e946c26f68514fKalle Raita        }
529a099a24c93bfa599fc5c36a647e946c26f68514fKalle Raita        if (!mCreated) {
530a099a24c93bfa599fc5c36a647e946c26f68514fKalle Raita            return;
531a099a24c93bfa599fc5c36a647e946c26f68514fKalle Raita        }
532a099a24c93bfa599fc5c36a647e946c26f68514fKalle Raita
533a099a24c93bfa599fc5c36a647e946c26f68514fKalle Raita        mRouteNameTextView.setText(mRoute.getName());
534a099a24c93bfa599fc5c36a647e946c26f68514fKalle Raita        mDisconnectButton.setVisibility(mRoute.canDisconnect() ? View.VISIBLE : View.GONE);
535a099a24c93bfa599fc5c36a647e946c26f68514fKalle Raita        if (mCustomControlView == null && mArtIconIsLoaded) {
536edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Project            if (isBitmapRecycled(mArtIconLoadedBitmap)) {
537edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Project                Log.w(TAG, "Can't set artwork image with recycled bitmap: " + mArtIconLoadedBitmap);
538d723bd7669b4fc88dc282d8bf8ba5ecb2849d22fDan Stoza            } else {
539d723bd7669b4fc88dc282d8bf8ba5ecb2849d22fDan Stoza                mArtView.setImageBitmap(mArtIconLoadedBitmap);
540d723bd7669b4fc88dc282d8bf8ba5ecb2849d22fDan Stoza                mArtView.setBackgroundColor(mArtIconBackgroundColor);
541d723bd7669b4fc88dc282d8bf8ba5ecb2849d22fDan Stoza            }
542edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Project            clearLoadedBitmap();
543edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Project        }
544edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Project        updateVolumeControlLayout();
545edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Project        updatePlaybackControlLayout();
546edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Project        updateLayoutHeight(animate);
547edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Project    }
548edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Project
549edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Project    private boolean isBitmapRecycled(Bitmap bitmap) {
550edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Project        return bitmap != null && bitmap.isRecycled();
55183c0446f27b9542d6c2e724817b2b2d8d1f55085Mathias Agopian    }
5522ea926bda2fa30f3ba8d0ed2d2395a8ada952e6eMarco Nelissen
553edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Project    private boolean canShowPlaybackControlLayout() {
5546c913be9ca95fd6b556d056e165a4ba6dc69795bJesse Hall        return mCustomControlView == null && (mDescription != null || mState != null);
5556c913be9ca95fd6b556d056e165a4ba6dc69795bJesse Hall    }
5561db73f66624e7d151710483dd58e03eed672f064Robert Carr
5571db73f66624e7d151710483dd58e03eed672f064Robert Carr    /**
5581db73f66624e7d151710483dd58e03eed672f064Robert Carr     * Returns the height of main media controller which includes playback control and master
5591db73f66624e7d151710483dd58e03eed672f064Robert Carr     * volume control.
5601db73f66624e7d151710483dd58e03eed672f064Robert Carr     */
5611db73f66624e7d151710483dd58e03eed672f064Robert Carr    private int getMainControllerHeight(boolean showPlaybackControl) {
5621db73f66624e7d151710483dd58e03eed672f064Robert Carr        int height = 0;
5631db73f66624e7d151710483dd58e03eed672f064Robert Carr        if (showPlaybackControl || mVolumeControlLayout.getVisibility() == View.VISIBLE) {
564698c0873cf2e07bdc7fd1e72169aee2a19fa40d7Mathias Agopian            height += mMediaMainControlLayout.getPaddingTop()
56583c0446f27b9542d6c2e724817b2b2d8d1f55085Mathias Agopian                    + mMediaMainControlLayout.getPaddingBottom();
566d723bd7669b4fc88dc282d8bf8ba5ecb2849d22fDan Stoza            if (showPlaybackControl) {
567d723bd7669b4fc88dc282d8bf8ba5ecb2849d22fDan Stoza                height +=  mPlaybackControlLayout.getMeasuredHeight();
5688afa1c4ab86d724feb7716e153b7835385534590Michael Lentine            }
5698afa1c4ab86d724feb7716e153b7835385534590Michael Lentine            if (mVolumeControlLayout.getVisibility() == View.VISIBLE) {
5708afa1c4ab86d724feb7716e153b7835385534590Michael Lentine                height += mVolumeControlLayout.getMeasuredHeight();
571698c0873cf2e07bdc7fd1e72169aee2a19fa40d7Mathias Agopian            }
572698c0873cf2e07bdc7fd1e72169aee2a19fa40d7Mathias Agopian            if (showPlaybackControl && mVolumeControlLayout.getVisibility() == View.VISIBLE) {
573698c0873cf2e07bdc7fd1e72169aee2a19fa40d7Mathias Agopian                height += mDividerView.getMeasuredHeight();
574d723bd7669b4fc88dc282d8bf8ba5ecb2849d22fDan Stoza            }
5758afa1c4ab86d724feb7716e153b7835385534590Michael Lentine        }
5768afa1c4ab86d724feb7716e153b7835385534590Michael Lentine        return height;
5778afa1c4ab86d724feb7716e153b7835385534590Michael Lentine    }
578698c0873cf2e07bdc7fd1e72169aee2a19fa40d7Mathias Agopian
579698c0873cf2e07bdc7fd1e72169aee2a19fa40d7Mathias Agopian    private void updateMediaControlVisibility(boolean canShowPlaybackControlLayout) {
580d723bd7669b4fc88dc282d8bf8ba5ecb2849d22fDan Stoza        // TODO: Update the top and bottom padding of the control layout according to the display
581d723bd7669b4fc88dc282d8bf8ba5ecb2849d22fDan Stoza        // height.
5828afa1c4ab86d724feb7716e153b7835385534590Michael Lentine        mDividerView.setVisibility((mVolumeControlLayout.getVisibility() == View.VISIBLE
5838afa1c4ab86d724feb7716e153b7835385534590Michael Lentine                && canShowPlaybackControlLayout) ? View.VISIBLE : View.GONE);
5848afa1c4ab86d724feb7716e153b7835385534590Michael Lentine        mMediaMainControlLayout.setVisibility((mVolumeControlLayout.getVisibility() == View.GONE
5858b33f032327f8de0dcc0e6d0d43ed80f834b51f6Mathias Agopian                && !canShowPlaybackControlLayout) ? View.GONE : View.VISIBLE);
5868b33f032327f8de0dcc0e6d0d43ed80f834b51f6Mathias Agopian    }
5878b33f032327f8de0dcc0e6d0d43ed80f834b51f6Mathias Agopian
588d723bd7669b4fc88dc282d8bf8ba5ecb2849d22fDan Stoza    void updateLayoutHeight(final boolean animate) {
5898afa1c4ab86d724feb7716e153b7835385534590Michael Lentine        // We need to defer the update until the first layout has occurred, as we don't yet know the
5908afa1c4ab86d724feb7716e153b7835385534590Michael Lentine        // overall visible display size in which the window this view is attached to has been
5918afa1c4ab86d724feb7716e153b7835385534590Michael Lentine        // positioned in.
5928b33f032327f8de0dcc0e6d0d43ed80f834b51f6Mathias Agopian        mDefaultControlLayout.requestLayout();
5938b33f032327f8de0dcc0e6d0d43ed80f834b51f6Mathias Agopian        ViewTreeObserver observer = mDefaultControlLayout.getViewTreeObserver();
594d723bd7669b4fc88dc282d8bf8ba5ecb2849d22fDan Stoza        observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
595d723bd7669b4fc88dc282d8bf8ba5ecb2849d22fDan Stoza            @Override
596d723bd7669b4fc88dc282d8bf8ba5ecb2849d22fDan Stoza            public void onGlobalLayout() {
5976c913be9ca95fd6b556d056e165a4ba6dc69795bJesse Hall                mDefaultControlLayout.getViewTreeObserver().removeGlobalOnLayoutListener(this);
5986c913be9ca95fd6b556d056e165a4ba6dc69795bJesse Hall                if (mIsGroupListAnimating) {
599edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Project                    mIsGroupListAnimationPending = true;
60083c0446f27b9542d6c2e724817b2b2d8d1f55085Mathias Agopian                } else {
601edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Project                    updateLayoutHeightInternal(animate);
6026c913be9ca95fd6b556d056e165a4ba6dc69795bJesse Hall                }
6036c913be9ca95fd6b556d056e165a4ba6dc69795bJesse Hall            }
6042a9fc493dfdba67108e4335bb1fe931bc1e2a025Mathias Agopian        });
6052a9fc493dfdba67108e4335bb1fe931bc1e2a025Mathias Agopian    }
6062a9fc493dfdba67108e4335bb1fe931bc1e2a025Mathias Agopian
60740482ff650751819d4104c10a30974838168438cChavi Weingarten    /**
60860d6922a011fe18c111b8d30fb6ef1f655b6b15ePablo Ceballos     * Updates the height of views and hide artwork or metadata if space is limited.
609c18790018be5d7ea7061ccbc81f3044e74adc823Dan Stoza     */
610d723bd7669b4fc88dc282d8bf8ba5ecb2849d22fDan Stoza    void updateLayoutHeightInternal(boolean animate) {
611d723bd7669b4fc88dc282d8bf8ba5ecb2849d22fDan Stoza        // Measure the size of widgets and get the height of main components.
612ae0608381b2b4699218febd6d45ad9d307544d55Robert Carr        int oldHeight = getLayoutHeight(mMediaMainControlLayout);
613ae0608381b2b4699218febd6d45ad9d307544d55Robert Carr        setLayoutHeight(mMediaMainControlLayout, ViewGroup.LayoutParams.MATCH_PARENT);
614c701401f8cec2e5309f8b57e2b97baced5093274Dan Stoza        updateMediaControlVisibility(canShowPlaybackControlLayout());
615d723bd7669b4fc88dc282d8bf8ba5ecb2849d22fDan Stoza        View decorView = getWindow().getDecorView();
616c701401f8cec2e5309f8b57e2b97baced5093274Dan Stoza        decorView.measure(
61740482ff650751819d4104c10a30974838168438cChavi Weingarten                MeasureSpec.makeMeasureSpec(getWindow().getAttributes().width, MeasureSpec.EXACTLY),
61840482ff650751819d4104c10a30974838168438cChavi Weingarten                MeasureSpec.UNSPECIFIED);
61940482ff650751819d4104c10a30974838168438cChavi Weingarten        setLayoutHeight(mMediaMainControlLayout, oldHeight);
6202a9fc493dfdba67108e4335bb1fe931bc1e2a025Mathias Agopian        int artViewHeight = 0;
62140482ff650751819d4104c10a30974838168438cChavi Weingarten        if (mCustomControlView == null && mArtView.getDrawable() instanceof BitmapDrawable) {
62240482ff650751819d4104c10a30974838168438cChavi Weingarten            Bitmap art = ((BitmapDrawable) mArtView.getDrawable()).getBitmap();
62340482ff650751819d4104c10a30974838168438cChavi Weingarten            if (art != null) {
6246c913be9ca95fd6b556d056e165a4ba6dc69795bJesse Hall                artViewHeight = getDesiredArtHeight(art.getWidth(), art.getHeight());
6256c913be9ca95fd6b556d056e165a4ba6dc69795bJesse Hall                mArtView.setScaleType(art.getWidth() >= art.getHeight()
626a76b271f0e14325fa0ebb98e1cac0a15adfea1cbchaviw                        ? ImageView.ScaleType.FIT_XY : ImageView.ScaleType.FIT_CENTER);
627a76b271f0e14325fa0ebb98e1cac0a15adfea1cbchaviw            }
628a76b271f0e14325fa0ebb98e1cac0a15adfea1cbchaviw        }
62940482ff650751819d4104c10a30974838168438cChavi Weingarten        int mainControllerHeight = getMainControllerHeight(canShowPlaybackControlLayout());
6307206d49b2963ce2e926a5f25fe94aca9c06471e6chaviw        int volumeGroupListCount = mGroupMemberRoutes.size();
6317206d49b2963ce2e926a5f25fe94aca9c06471e6chaviw        // Scale down volume group list items in landscape mode.
6327206d49b2963ce2e926a5f25fe94aca9c06471e6chaviw        int expandedGroupListHeight = getGroup() == null ? 0 :
633578038fc49f83c4c8c4accdce49df404ecd6ad02Robert Carr                mVolumeGroupListItemHeight * getGroup().getRoutes().size();
634a76b271f0e14325fa0ebb98e1cac0a15adfea1cbchaviw        if (volumeGroupListCount > 0) {
635578038fc49f83c4c8c4accdce49df404ecd6ad02Robert Carr            expandedGroupListHeight += mVolumeGroupListPaddingTop;
636578038fc49f83c4c8c4accdce49df404ecd6ad02Robert Carr        }
637a76b271f0e14325fa0ebb98e1cac0a15adfea1cbchaviw        expandedGroupListHeight = Math.min(expandedGroupListHeight, mVolumeGroupListMaxHeight);
63840482ff650751819d4104c10a30974838168438cChavi Weingarten        int visibleGroupListHeight = mIsGroupExpanded ? expandedGroupListHeight : 0;
63940482ff650751819d4104c10a30974838168438cChavi Weingarten
64040482ff650751819d4104c10a30974838168438cChavi Weingarten        int desiredControlLayoutHeight =
641a76b271f0e14325fa0ebb98e1cac0a15adfea1cbchaviw                Math.max(artViewHeight, visibleGroupListHeight) + mainControllerHeight;
642a76b271f0e14325fa0ebb98e1cac0a15adfea1cbchaviw        Rect visibleRect = new Rect();
643134f0422866e8985188ed10dfbdcb8e6c34b87f7Jamie Gennis        decorView.getWindowVisibleDisplayFrame(visibleRect);
644134f0422866e8985188ed10dfbdcb8e6c34b87f7Jamie Gennis        // Height of non-control views in decor view.
6452adaf04fab35cf47c824d74d901b54094e01ccd3Andy McFadden        // This includes title bar, button bar, and dialog's vertical padding which should be
6462adaf04fab35cf47c824d74d901b54094e01ccd3Andy McFadden        // always shown.
6472adaf04fab35cf47c824d74d901b54094e01ccd3Andy McFadden        int nonControlViewHeight = mDialogAreaLayout.getMeasuredHeight()
648134f0422866e8985188ed10dfbdcb8e6c34b87f7Jamie Gennis                - mDefaultControlLayout.getMeasuredHeight();
6496c913be9ca95fd6b556d056e165a4ba6dc69795bJesse Hall        // Maximum allowed height for controls to fit screen.
6506c913be9ca95fd6b556d056e165a4ba6dc69795bJesse Hall        int maximumControlViewHeight = visibleRect.height() - nonControlViewHeight;
6516b376713907086c9642e7b7e66e51ddfa531b003Brian Anderson
6526b376713907086c9642e7b7e66e51ddfa531b003Brian Anderson        // Show artwork if it fits the screen.
6536b376713907086c9642e7b7e66e51ddfa531b003Brian Anderson        if (mCustomControlView == null && artViewHeight > 0
6546b376713907086c9642e7b7e66e51ddfa531b003Brian Anderson                && desiredControlLayoutHeight <= maximumControlViewHeight) {
6556b376713907086c9642e7b7e66e51ddfa531b003Brian Anderson            mArtView.setVisibility(View.VISIBLE);
6566b376713907086c9642e7b7e66e51ddfa531b003Brian Anderson            setLayoutHeight(mArtView, artViewHeight);
6576b376713907086c9642e7b7e66e51ddfa531b003Brian Anderson        } else {
6586b376713907086c9642e7b7e66e51ddfa531b003Brian Anderson            if (getLayoutHeight(mVolumeGroupList) + mMediaMainControlLayout.getMeasuredHeight()
6596b376713907086c9642e7b7e66e51ddfa531b003Brian Anderson                    >= mDefaultControlLayout.getMeasuredHeight()) {
6606b376713907086c9642e7b7e66e51ddfa531b003Brian Anderson                mArtView.setVisibility(View.GONE);
6616b376713907086c9642e7b7e66e51ddfa531b003Brian Anderson            }
6626b376713907086c9642e7b7e66e51ddfa531b003Brian Anderson            artViewHeight = 0;
6636b376713907086c9642e7b7e66e51ddfa531b003Brian Anderson            desiredControlLayoutHeight = visibleGroupListHeight + mainControllerHeight;
6646b376713907086c9642e7b7e66e51ddfa531b003Brian Anderson        }
6656b376713907086c9642e7b7e66e51ddfa531b003Brian Anderson        // Show the playback control if it fits the screen.
6666b376713907086c9642e7b7e66e51ddfa531b003Brian Anderson        if (canShowPlaybackControlLayout()
6676b376713907086c9642e7b7e66e51ddfa531b003Brian Anderson                && desiredControlLayoutHeight <= maximumControlViewHeight) {
6686b376713907086c9642e7b7e66e51ddfa531b003Brian Anderson            mPlaybackControlLayout.setVisibility(View.VISIBLE);
6696b376713907086c9642e7b7e66e51ddfa531b003Brian Anderson        } else {
670d0566bc26fcf6ca396118701fa11900b627f2c09Mathias Agopian            mPlaybackControlLayout.setVisibility(View.GONE);
671d0566bc26fcf6ca396118701fa11900b627f2c09Mathias Agopian        }
672b1e2f8deb38353e4bcc9d3ef06bc15bd5e417425Jorim Jaggi        updateMediaControlVisibility(mPlaybackControlLayout.getVisibility() == View.VISIBLE);
673b1e2f8deb38353e4bcc9d3ef06bc15bd5e417425Jorim Jaggi        mainControllerHeight = getMainControllerHeight(
6742ea926bda2fa30f3ba8d0ed2d2395a8ada952e6eMarco Nelissen                mPlaybackControlLayout.getVisibility() == View.VISIBLE);
675d0566bc26fcf6ca396118701fa11900b627f2c09Mathias Agopian        desiredControlLayoutHeight =
6766c913be9ca95fd6b556d056e165a4ba6dc69795bJesse Hall                Math.max(artViewHeight, visibleGroupListHeight) + mainControllerHeight;
677e57f292595bec48f65c8088b00ff6beea01217e9Mathias Agopian
678e57f292595bec48f65c8088b00ff6beea01217e9Mathias Agopian        // Limit the volume group list height to fit the screen.
6798dfa92fef9759a881e96ee58d59875d35023aab9Andy McFadden        if (desiredControlLayoutHeight > maximumControlViewHeight) {
680dd3cb84cfbe8068790c6233b5829fae9c4a0ee93Jamie Gennis            visibleGroupListHeight -= (desiredControlLayoutHeight - maximumControlViewHeight);
681dd3cb84cfbe8068790c6233b5829fae9c4a0ee93Jamie Gennis            desiredControlLayoutHeight = maximumControlViewHeight;
682e57f292595bec48f65c8088b00ff6beea01217e9Mathias Agopian        }
683e57f292595bec48f65c8088b00ff6beea01217e9Mathias Agopian        // Update the layouts with the computed heights.
6846c913be9ca95fd6b556d056e165a4ba6dc69795bJesse Hall        mMediaMainControlLayout.clearAnimation();
6856c913be9ca95fd6b556d056e165a4ba6dc69795bJesse Hall        mVolumeGroupList.clearAnimation();
6866c913be9ca95fd6b556d056e165a4ba6dc69795bJesse Hall        mDefaultControlLayout.clearAnimation();
6876c913be9ca95fd6b556d056e165a4ba6dc69795bJesse Hall        if (animate) {
6886c913be9ca95fd6b556d056e165a4ba6dc69795bJesse Hall            animateLayoutHeight(mMediaMainControlLayout, mainControllerHeight);
6896c913be9ca95fd6b556d056e165a4ba6dc69795bJesse Hall            animateLayoutHeight(mVolumeGroupList, visibleGroupListHeight);
6906c913be9ca95fd6b556d056e165a4ba6dc69795bJesse Hall            animateLayoutHeight(mDefaultControlLayout, desiredControlLayoutHeight);
691e57f292595bec48f65c8088b00ff6beea01217e9Mathias Agopian        } else {
692e57f292595bec48f65c8088b00ff6beea01217e9Mathias Agopian            setLayoutHeight(mMediaMainControlLayout, mainControllerHeight);
693e57f292595bec48f65c8088b00ff6beea01217e9Mathias Agopian            setLayoutHeight(mVolumeGroupList, visibleGroupListHeight);
694e57f292595bec48f65c8088b00ff6beea01217e9Mathias Agopian            setLayoutHeight(mDefaultControlLayout, desiredControlLayoutHeight);
695e57f292595bec48f65c8088b00ff6beea01217e9Mathias Agopian        }
696e57f292595bec48f65c8088b00ff6beea01217e9Mathias Agopian        // Maximize the window size with a transparent layout in advance for smooth animation.
6976c913be9ca95fd6b556d056e165a4ba6dc69795bJesse Hall        setLayoutHeight(mExpandableAreaLayout, visibleRect.height());
6987f7da32569f8e0b3d383a40b95f8ac1d55afd801Dan Stoza        rebuildVolumeGroupList(animate);
6997f7da32569f8e0b3d383a40b95f8ac1d55afd801Dan Stoza    }
7007f7da32569f8e0b3d383a40b95f8ac1d55afd801Dan Stoza
7017f7da32569f8e0b3d383a40b95f8ac1d55afd801Dan Stoza    void updateVolumeGroupItemHeight(View item) {
7027f7da32569f8e0b3d383a40b95f8ac1d55afd801Dan Stoza        LinearLayout container = (LinearLayout) item.findViewById(R.id.volume_item_container);
7037f7da32569f8e0b3d383a40b95f8ac1d55afd801Dan Stoza        setLayoutHeight(container, mVolumeGroupListItemHeight);
7047f7da32569f8e0b3d383a40b95f8ac1d55afd801Dan Stoza        View icon = item.findViewById(R.id.mr_volume_item_icon);
705d723bd7669b4fc88dc282d8bf8ba5ecb2849d22fDan Stoza        ViewGroup.LayoutParams lp = icon.getLayoutParams();
7067f7da32569f8e0b3d383a40b95f8ac1d55afd801Dan Stoza        lp.width = mVolumeGroupListItemIconSize;
7077f7da32569f8e0b3d383a40b95f8ac1d55afd801Dan Stoza        lp.height = mVolumeGroupListItemIconSize;
7087f7da32569f8e0b3d383a40b95f8ac1d55afd801Dan Stoza        icon.setLayoutParams(lp);
7097f7da32569f8e0b3d383a40b95f8ac1d55afd801Dan Stoza    }
7107f7da32569f8e0b3d383a40b95f8ac1d55afd801Dan Stoza
7117f7da32569f8e0b3d383a40b95f8ac1d55afd801Dan Stoza    private void animateLayoutHeight(final View view, int targetHeight) {
7127f7da32569f8e0b3d383a40b95f8ac1d55afd801Dan Stoza        final int startValue = getLayoutHeight(view);
71367d8bd66aaf04805cb8f2616ba964141b865e3b9Lajos Molnar        final int endValue = targetHeight;
71467d8bd66aaf04805cb8f2616ba964141b865e3b9Lajos Molnar        Animation anim = new Animation() {
71567d8bd66aaf04805cb8f2616ba964141b865e3b9Lajos Molnar            @Override
71667d8bd66aaf04805cb8f2616ba964141b865e3b9Lajos Molnar            protected void applyTransformation(float interpolatedTime, Transformation t) {
71767d8bd66aaf04805cb8f2616ba964141b865e3b9Lajos Molnar                int height = startValue - (int) ((startValue - endValue) * interpolatedTime);
71867d8bd66aaf04805cb8f2616ba964141b865e3b9Lajos Molnar                setLayoutHeight(view, height);
71967d8bd66aaf04805cb8f2616ba964141b865e3b9Lajos Molnar            }
72067d8bd66aaf04805cb8f2616ba964141b865e3b9Lajos Molnar        };
72167d8bd66aaf04805cb8f2616ba964141b865e3b9Lajos Molnar        anim.setDuration(mGroupListAnimationDurationMs);
72267d8bd66aaf04805cb8f2616ba964141b865e3b9Lajos Molnar        if (android.os.Build.VERSION.SDK_INT >= 21) {
72367d8bd66aaf04805cb8f2616ba964141b865e3b9Lajos Molnar            anim.setInterpolator(mInterpolator);
72467d8bd66aaf04805cb8f2616ba964141b865e3b9Lajos Molnar        }
7257f7da32569f8e0b3d383a40b95f8ac1d55afd801Dan Stoza        view.startAnimation(anim);
7267f7da32569f8e0b3d383a40b95f8ac1d55afd801Dan Stoza    }
7277f7da32569f8e0b3d383a40b95f8ac1d55afd801Dan Stoza
7287f7da32569f8e0b3d383a40b95f8ac1d55afd801Dan Stoza    void loadInterpolator() {
7297f7da32569f8e0b3d383a40b95f8ac1d55afd801Dan Stoza        if (android.os.Build.VERSION.SDK_INT >= 21) {
7307f7da32569f8e0b3d383a40b95f8ac1d55afd801Dan Stoza            mInterpolator = mIsGroupExpanded ? mLinearOutSlowInInterpolator
7317f7da32569f8e0b3d383a40b95f8ac1d55afd801Dan Stoza                    : mFastOutSlowInInterpolator;
7327f7da32569f8e0b3d383a40b95f8ac1d55afd801Dan Stoza        } else {
733c666cae2d5995097ec49a87e375e2afdd92802b7Mathias Agopian            mInterpolator = mAccelerateDecelerateInterpolator;
7349d4e3d2f42e93e2d12bacabe97d307d30c3c20ddJeff Brown        }
7357f7da32569f8e0b3d383a40b95f8ac1d55afd801Dan Stoza    }
7367f7da32569f8e0b3d383a40b95f8ac1d55afd801Dan Stoza
737c666cae2d5995097ec49a87e375e2afdd92802b7Mathias Agopian    private void updateVolumeControlLayout() {
7386c913be9ca95fd6b556d056e165a4ba6dc69795bJesse Hall        if (isVolumeControlAvailable(mRoute)) {
7396c913be9ca95fd6b556d056e165a4ba6dc69795bJesse Hall            if (mVolumeControlLayout.getVisibility() == View.GONE) {
74028f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright                mVolumeControlLayout.setVisibility(View.VISIBLE);
74128f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright                mVolumeSlider.setMax(mRoute.getVolumeMax());
742a52f0295622a42849f5ef81c44589b816b2ccacbPeiyong Lin                mVolumeSlider.setProgress(mRoute.getVolume());
74328f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright                mGroupExpandCollapseButton.setVisibility(getGroup() == null ? View.GONE
74428f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright                        : View.VISIBLE);
74528f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright            }
74628f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright        } else {
74728f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright            mVolumeControlLayout.setVisibility(View.GONE);
74828f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright        }
74928f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright    }
75028f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright
75128f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright    private void rebuildVolumeGroupList(boolean animate) {
75228f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright        List<MediaRouter.RouteInfo> routes = getGroup() == null ? null : getGroup().getRoutes();
75328f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright        if (routes == null) {
754a52f0295622a42849f5ef81c44589b816b2ccacbPeiyong Lin            mGroupMemberRoutes.clear();
75528f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright            mVolumeGroupAdapter.notifyDataSetChanged();
75628f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright        } else if (MediaRouteDialogHelper.listUnorderedEquals(mGroupMemberRoutes, routes)) {
75728f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright            mVolumeGroupAdapter.notifyDataSetChanged();
75828f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright        } else {
75928f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright            HashMap<MediaRouter.RouteInfo, Rect> previousRouteBoundMap = animate
76028f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright                    ? MediaRouteDialogHelper.getItemBoundMap(mVolumeGroupList, mVolumeGroupAdapter)
76128f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright                    : null;
76228f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright            HashMap<MediaRouter.RouteInfo, BitmapDrawable> previousRouteBitmapMap = animate
76328f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright                    ? MediaRouteDialogHelper.getItemBitmapMap(mContext, mVolumeGroupList,
76428f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright                            mVolumeGroupAdapter) : null;
76528f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright            mGroupMemberRoutesAdded =
76628f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright                    MediaRouteDialogHelper.getItemsAdded(mGroupMemberRoutes, routes);
767a52f0295622a42849f5ef81c44589b816b2ccacbPeiyong Lin            mGroupMemberRoutesRemoved = MediaRouteDialogHelper.getItemsRemoved(mGroupMemberRoutes,
76828f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright                    routes);
76928f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright            mGroupMemberRoutes.addAll(0, mGroupMemberRoutesAdded);
77028f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright            mGroupMemberRoutes.removeAll(mGroupMemberRoutesRemoved);
77128f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright            mVolumeGroupAdapter.notifyDataSetChanged();
77228f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright            if (animate && mIsGroupExpanded
77328f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright                    && mGroupMemberRoutesAdded.size() + mGroupMemberRoutesRemoved.size() > 0) {
77428f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright                animateGroupListItems(previousRouteBoundMap, previousRouteBitmapMap);
77528f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright            } else {
77628f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright                mGroupMemberRoutesAdded = null;
77728f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright                mGroupMemberRoutesRemoved = null;
77828f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright            }
77928f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright        }
78028f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright    }
78128f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright
78228f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright    private void animateGroupListItems(final Map<MediaRouter.RouteInfo, Rect> previousRouteBoundMap,
78328f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright            final Map<MediaRouter.RouteInfo, BitmapDrawable> previousRouteBitmapMap) {
78428f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright        mVolumeGroupList.setEnabled(false);
78528f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright        mVolumeGroupList.requestLayout();
786a52f0295622a42849f5ef81c44589b816b2ccacbPeiyong Lin        mIsGroupListAnimating = true;
78728f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright        ViewTreeObserver observer = mVolumeGroupList.getViewTreeObserver();
78828f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright        observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
78928f24d0ab481bd9c6fd5618414fee694e837c5c6Michael Wright            @Override
790d85084b2b65828442eafaff9b811e9b6c9ca9fadSvetoslav            public void onGlobalLayout() {
791d85084b2b65828442eafaff9b811e9b6c9ca9fadSvetoslav                mVolumeGroupList.getViewTreeObserver().removeGlobalOnLayoutListener(this);
792d85084b2b65828442eafaff9b811e9b6c9ca9fadSvetoslav                animateGroupListItemsInternal(previousRouteBoundMap, previousRouteBitmapMap);
793d85084b2b65828442eafaff9b811e9b6c9ca9fadSvetoslav            }
794d85084b2b65828442eafaff9b811e9b6c9ca9fadSvetoslav        });
795d85084b2b65828442eafaff9b811e9b6c9ca9fadSvetoslav    }
796d85084b2b65828442eafaff9b811e9b6c9ca9fadSvetoslav
797d85084b2b65828442eafaff9b811e9b6c9ca9fadSvetoslav    void animateGroupListItemsInternal(
798d85084b2b65828442eafaff9b811e9b6c9ca9fadSvetoslav            Map<MediaRouter.RouteInfo, Rect> previousRouteBoundMap,
799d85084b2b65828442eafaff9b811e9b6c9ca9fadSvetoslav            Map<MediaRouter.RouteInfo, BitmapDrawable> previousRouteBitmapMap) {
800d85084b2b65828442eafaff9b811e9b6c9ca9fadSvetoslav        if (mGroupMemberRoutesAdded == null || mGroupMemberRoutesRemoved == null) {
801d85084b2b65828442eafaff9b811e9b6c9ca9fadSvetoslav            return;
802d85084b2b65828442eafaff9b811e9b6c9ca9fadSvetoslav        }
803d85084b2b65828442eafaff9b811e9b6c9ca9fadSvetoslav        int groupSizeDelta = mGroupMemberRoutesAdded.size() - mGroupMemberRoutesRemoved.size();
8042c9b11f0291210c9b9513a1a0cce6afebd361b3bPrashant Malani        boolean listenerRegistered = false;
8052c9b11f0291210c9b9513a1a0cce6afebd361b3bPrashant Malani        Animation.AnimationListener listener = new Animation.AnimationListener() {
8062c9b11f0291210c9b9513a1a0cce6afebd361b3bPrashant Malani            @Override
8072c9b11f0291210c9b9513a1a0cce6afebd361b3bPrashant Malani            public void onAnimationStart(Animation animation) {
8082c9b11f0291210c9b9513a1a0cce6afebd361b3bPrashant Malani                mVolumeGroupList.startAnimationAll();
8092c9b11f0291210c9b9513a1a0cce6afebd361b3bPrashant Malani                mVolumeGroupList.postDelayed(mGroupListFadeInAnimation,
8102c9b11f0291210c9b9513a1a0cce6afebd361b3bPrashant Malani                        mGroupListAnimationDurationMs);
811c4f471e75a8ec64ec34e3f2944a5a756215d0becDan Stoza            }
812c4f471e75a8ec64ec34e3f2944a5a756215d0becDan Stoza
813c4f471e75a8ec64ec34e3f2944a5a756215d0becDan Stoza            @Override
814c4f471e75a8ec64ec34e3f2944a5a756215d0becDan Stoza            public void onAnimationEnd(Animation animation) { }
815c4f471e75a8ec64ec34e3f2944a5a756215d0becDan Stoza
816c4f471e75a8ec64ec34e3f2944a5a756215d0becDan Stoza            @Override
817c4f471e75a8ec64ec34e3f2944a5a756215d0becDan Stoza            public void onAnimationRepeat(Animation animation) { }
818c4f471e75a8ec64ec34e3f2944a5a756215d0becDan Stoza        };
819c4f471e75a8ec64ec34e3f2944a5a756215d0becDan Stoza
820c4f471e75a8ec64ec34e3f2944a5a756215d0becDan Stoza        // Animate visible items from previous positions to current positions except routes added
821c4f471e75a8ec64ec34e3f2944a5a756215d0becDan Stoza        // just before. Added routes will remain hidden until translate animation finishes.
822c4f471e75a8ec64ec34e3f2944a5a756215d0becDan Stoza        int first = mVolumeGroupList.getFirstVisiblePosition();
823c4f471e75a8ec64ec34e3f2944a5a756215d0becDan Stoza        for (int i = 0; i < mVolumeGroupList.getChildCount(); ++i) {
824e1f5e6fd7ad07778fa4381059852a94567d77dfcMathias Agopian            View view = mVolumeGroupList.getChildAt(i);
825c4f471e75a8ec64ec34e3f2944a5a756215d0becDan Stoza            int position = first + i;
826c4f471e75a8ec64ec34e3f2944a5a756215d0becDan Stoza            MediaRouter.RouteInfo route = mVolumeGroupAdapter.getItem(position);
827c4f471e75a8ec64ec34e3f2944a5a756215d0becDan Stoza            Rect previousBounds = previousRouteBoundMap.get(route);
828c1ba5c4649554e744844b07cfe402b42fbe12ff3Sahil Dhanju            int currentTop = view.getTop();
829c1ba5c4649554e744844b07cfe402b42fbe12ff3Sahil Dhanju            int previousTop = previousBounds != null ? previousBounds.top
830c1ba5c4649554e744844b07cfe402b42fbe12ff3Sahil Dhanju                    : (currentTop + mVolumeGroupListItemHeight * groupSizeDelta);
831c1ba5c4649554e744844b07cfe402b42fbe12ff3Sahil Dhanju            AnimationSet animSet = new AnimationSet(true);
832c1ba5c4649554e744844b07cfe402b42fbe12ff3Sahil Dhanju            if (mGroupMemberRoutesAdded != null && mGroupMemberRoutesAdded.contains(route)) {
833c1ba5c4649554e744844b07cfe402b42fbe12ff3Sahil Dhanju                previousTop = currentTop;
834c1ba5c4649554e744844b07cfe402b42fbe12ff3Sahil Dhanju                Animation alphaAnim = new AlphaAnimation(0.0f, 0.0f);
835c1ba5c4649554e744844b07cfe402b42fbe12ff3Sahil Dhanju                alphaAnim.setDuration(mGroupListFadeInDurationMs);
836c1ba5c4649554e744844b07cfe402b42fbe12ff3Sahil Dhanju                animSet.addAnimation(alphaAnim);
837c1ba5c4649554e744844b07cfe402b42fbe12ff3Sahil Dhanju            }
838c1ba5c4649554e744844b07cfe402b42fbe12ff3Sahil Dhanju            Animation translationAnim = new TranslateAnimation(0, 0, previousTop - currentTop, 0);
839c1ba5c4649554e744844b07cfe402b42fbe12ff3Sahil Dhanju            translationAnim.setDuration(mGroupListAnimationDurationMs);
840c1ba5c4649554e744844b07cfe402b42fbe12ff3Sahil Dhanju            animSet.addAnimation(translationAnim);
841c1ba5c4649554e744844b07cfe402b42fbe12ff3Sahil Dhanju            animSet.setFillAfter(true);
842c1ba5c4649554e744844b07cfe402b42fbe12ff3Sahil Dhanju            animSet.setFillEnabled(true);
843c1ba5c4649554e744844b07cfe402b42fbe12ff3Sahil Dhanju            animSet.setInterpolator(mInterpolator);
844c1ba5c4649554e744844b07cfe402b42fbe12ff3Sahil Dhanju            if (!listenerRegistered) {
845c1ba5c4649554e744844b07cfe402b42fbe12ff3Sahil Dhanju                listenerRegistered = true;
846c1ba5c4649554e744844b07cfe402b42fbe12ff3Sahil Dhanju                animSet.setAnimationListener(listener);
847c1ba5c4649554e744844b07cfe402b42fbe12ff3Sahil Dhanju            }
848a099a24c93bfa599fc5c36a647e946c26f68514fKalle Raita            view.clearAnimation();
849a099a24c93bfa599fc5c36a647e946c26f68514fKalle Raita            view.startAnimation(animSet);
850a099a24c93bfa599fc5c36a647e946c26f68514fKalle Raita            previousRouteBoundMap.remove(route);
851a099a24c93bfa599fc5c36a647e946c26f68514fKalle Raita            previousRouteBitmapMap.remove(route);
852a099a24c93bfa599fc5c36a647e946c26f68514fKalle Raita        }
853a099a24c93bfa599fc5c36a647e946c26f68514fKalle Raita
854a099a24c93bfa599fc5c36a647e946c26f68514fKalle Raita        // If a member route doesn't exist any longer, it can be either removed or moved out of the
855a099a24c93bfa599fc5c36a647e946c26f68514fKalle Raita        // ListView layout boundary. In this case, use the previously captured bitmaps for
856a099a24c93bfa599fc5c36a647e946c26f68514fKalle Raita        // animation.
857a099a24c93bfa599fc5c36a647e946c26f68514fKalle Raita        for (Map.Entry<MediaRouter.RouteInfo, BitmapDrawable> item
858a099a24c93bfa599fc5c36a647e946c26f68514fKalle Raita                : previousRouteBitmapMap.entrySet()) {
8596c913be9ca95fd6b556d056e165a4ba6dc69795bJesse Hall            final MediaRouter.RouteInfo route = item.getKey();
86083c0446f27b9542d6c2e724817b2b2d8d1f55085Mathias Agopian            final BitmapDrawable bitmap = item.getValue();
8616c913be9ca95fd6b556d056e165a4ba6dc69795bJesse Hall            final Rect bounds = previousRouteBoundMap.get(route);
862edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Project            OverlayObject object = null;
863edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Project            if (mGroupMemberRoutesRemoved.contains(route)) {
864edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Project                object = new OverlayObject(bitmap, bounds).setAlphaAnimation(1.0f, 0.0f)
865edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Project                        .setDuration(mGroupListFadeOutDurationMs)
866edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Project                        .setInterpolator(mInterpolator);
867edbf3b6af777b721cd2a1ef461947e51e88241e1The Android Open Source Project            } else {
868                int deltaY = groupSizeDelta * mVolumeGroupListItemHeight;
869                object = new OverlayObject(bitmap, bounds).setTranslateYAnimation(deltaY)
870                        .setDuration(mGroupListAnimationDurationMs)
871                        .setInterpolator(mInterpolator)
872                        .setAnimationEndListener(new OverlayObject.OnAnimationEndListener() {
873                            @Override
874                            public void onAnimationEnd() {
875                                mGroupMemberRoutesAnimatingWithBitmap.remove(route);
876                                mVolumeGroupAdapter.notifyDataSetChanged();
877                            }
878                        });
879                mGroupMemberRoutesAnimatingWithBitmap.add(route);
880            }
881            mVolumeGroupList.addOverlayObject(object);
882        }
883    }
884
885    void startGroupListFadeInAnimation() {
886        clearGroupListAnimation(true);
887        mVolumeGroupList.requestLayout();
888        ViewTreeObserver observer = mVolumeGroupList.getViewTreeObserver();
889        observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
890            @Override
891            public void onGlobalLayout() {
892                mVolumeGroupList.getViewTreeObserver().removeGlobalOnLayoutListener(this);
893                startGroupListFadeInAnimationInternal();
894            }
895        });
896    }
897
898    void startGroupListFadeInAnimationInternal() {
899        if (mGroupMemberRoutesAdded != null && mGroupMemberRoutesAdded.size() != 0) {
900            fadeInAddedRoutes();
901        } else {
902            finishAnimation(true);
903        }
904    }
905
906    void finishAnimation(boolean animate) {
907        mGroupMemberRoutesAdded = null;
908        mGroupMemberRoutesRemoved = null;
909        mIsGroupListAnimating = false;
910        if (mIsGroupListAnimationPending) {
911            mIsGroupListAnimationPending = false;
912            updateLayoutHeight(animate);
913        }
914        mVolumeGroupList.setEnabled(true);
915    }
916
917    private void fadeInAddedRoutes() {
918        Animation.AnimationListener listener = new Animation.AnimationListener() {
919            @Override
920            public void onAnimationStart(Animation animation) { }
921
922            @Override
923            public void onAnimationEnd(Animation animation) {
924                finishAnimation(true);
925            }
926
927            @Override
928            public void onAnimationRepeat(Animation animation) { }
929        };
930        boolean listenerRegistered = false;
931        int first = mVolumeGroupList.getFirstVisiblePosition();
932        for (int i = 0; i < mVolumeGroupList.getChildCount(); ++i) {
933            View view = mVolumeGroupList.getChildAt(i);
934            int position = first + i;
935            MediaRouter.RouteInfo route = mVolumeGroupAdapter.getItem(position);
936            if (mGroupMemberRoutesAdded.contains(route)) {
937                Animation alphaAnim = new AlphaAnimation(0.0f, 1.0f);
938                alphaAnim.setDuration(mGroupListFadeInDurationMs);
939                alphaAnim.setFillEnabled(true);
940                alphaAnim.setFillAfter(true);
941                if (!listenerRegistered) {
942                    listenerRegistered = true;
943                    alphaAnim.setAnimationListener(listener);
944                }
945                view.clearAnimation();
946                view.startAnimation(alphaAnim);
947            }
948        }
949    }
950
951    void clearGroupListAnimation(boolean exceptAddedRoutes) {
952        int first = mVolumeGroupList.getFirstVisiblePosition();
953        for (int i = 0; i < mVolumeGroupList.getChildCount(); ++i) {
954            View view = mVolumeGroupList.getChildAt(i);
955            int position = first + i;
956            MediaRouter.RouteInfo route = mVolumeGroupAdapter.getItem(position);
957            if (exceptAddedRoutes && mGroupMemberRoutesAdded != null
958                    && mGroupMemberRoutesAdded.contains(route)) {
959                continue;
960            }
961            LinearLayout container = (LinearLayout) view.findViewById(R.id.volume_item_container);
962            container.setVisibility(View.VISIBLE);
963            AnimationSet animSet = new AnimationSet(true);
964            Animation alphaAnim = new AlphaAnimation(1.0f, 1.0f);
965            alphaAnim.setDuration(0);
966            animSet.addAnimation(alphaAnim);
967            Animation translationAnim = new TranslateAnimation(0, 0, 0, 0);
968            translationAnim.setDuration(0);
969            animSet.setFillAfter(true);
970            animSet.setFillEnabled(true);
971            view.clearAnimation();
972            view.startAnimation(animSet);
973        }
974        mVolumeGroupList.stopAnimationAll();
975        if (!exceptAddedRoutes) {
976            finishAnimation(false);
977        }
978    }
979
980    private void updatePlaybackControlLayout() {
981        if (canShowPlaybackControlLayout()) {
982            CharSequence title = mDescription == null ? null : mDescription.getTitle();
983            boolean hasTitle = !TextUtils.isEmpty(title);
984
985            CharSequence subtitle = mDescription == null ? null : mDescription.getSubtitle();
986            boolean hasSubtitle = !TextUtils.isEmpty(subtitle);
987
988            boolean showTitle = false;
989            boolean showSubtitle = false;
990            if (mRoute.getPresentationDisplayId()
991                    != MediaRouter.RouteInfo.PRESENTATION_DISPLAY_ID_NONE) {
992                // The user is currently casting screen.
993                mTitleView.setText(R.string.mr_controller_casting_screen);
994                showTitle = true;
995            } else if (mState == null || mState.getState() == PlaybackStateCompat.STATE_NONE) {
996                // Show "No media selected" as we don't yet know the playback state.
997                mTitleView.setText(R.string.mr_controller_no_media_selected);
998                showTitle = true;
999            } else if (!hasTitle && !hasSubtitle) {
1000                mTitleView.setText(R.string.mr_controller_no_info_available);
1001                showTitle = true;
1002            } else {
1003                if (hasTitle) {
1004                    mTitleView.setText(title);
1005                    showTitle = true;
1006                }
1007                if (hasSubtitle) {
1008                    mSubtitleView.setText(subtitle);
1009                    showSubtitle = true;
1010                }
1011            }
1012            mTitleView.setVisibility(showTitle ? View.VISIBLE : View.GONE);
1013            mSubtitleView.setVisibility(showSubtitle ? View.VISIBLE : View.GONE);
1014
1015            if (mState != null) {
1016                boolean isPlaying = mState.getState() == PlaybackStateCompat.STATE_BUFFERING
1017                        || mState.getState() == PlaybackStateCompat.STATE_PLAYING;
1018                Context playbackControlButtonContext = mPlaybackControlButton.getContext();
1019                boolean visible = true;
1020                int iconDrawableAttr = 0;
1021                int iconDescResId = 0;
1022                if (isPlaying && isPauseActionSupported()) {
1023                    iconDrawableAttr = R.attr.mediaRoutePauseDrawable;
1024                    iconDescResId = R.string.mr_controller_pause;
1025                } else if (isPlaying && isStopActionSupported()) {
1026                    iconDrawableAttr = R.attr.mediaRouteStopDrawable;
1027                    iconDescResId = R.string.mr_controller_stop;
1028                } else if (!isPlaying && isPlayActionSupported()) {
1029                    iconDrawableAttr = R.attr.mediaRoutePlayDrawable;
1030                    iconDescResId = R.string.mr_controller_play;
1031                } else {
1032                    visible = false;
1033                }
1034                mPlaybackControlButton.setVisibility(visible ? View.VISIBLE : View.GONE);
1035                if (visible) {
1036                    mPlaybackControlButton.setImageResource(
1037                            MediaRouterThemeHelper.getThemeResource(
1038                                    playbackControlButtonContext, iconDrawableAttr));
1039                    mPlaybackControlButton.setContentDescription(
1040                            playbackControlButtonContext.getResources()
1041                                    .getText(iconDescResId));
1042                }
1043            }
1044        }
1045    }
1046
1047    private boolean isPlayActionSupported() {
1048        return (mState.getActions() & (ACTION_PLAY | ACTION_PLAY_PAUSE)) != 0;
1049    }
1050
1051    private boolean isPauseActionSupported() {
1052        return (mState.getActions() & (ACTION_PAUSE | ACTION_PLAY_PAUSE)) != 0;
1053    }
1054
1055    private boolean isStopActionSupported() {
1056        return (mState.getActions() & ACTION_STOP) != 0;
1057    }
1058
1059    boolean isVolumeControlAvailable(MediaRouter.RouteInfo route) {
1060        return mVolumeControlEnabled && route.getVolumeHandling()
1061                == MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE;
1062    }
1063
1064    private static int getLayoutHeight(View view) {
1065        return view.getLayoutParams().height;
1066    }
1067
1068    static void setLayoutHeight(View view, int height) {
1069        ViewGroup.LayoutParams lp = view.getLayoutParams();
1070        lp.height = height;
1071        view.setLayoutParams(lp);
1072    }
1073
1074    private static boolean uriEquals(Uri uri1, Uri uri2) {
1075        if (uri1 != null && uri1.equals(uri2)) {
1076            return true;
1077        } else if (uri1 == null && uri2 == null) {
1078            return true;
1079        }
1080        return false;
1081    }
1082
1083    /**
1084     * Returns desired art height to fit into controller dialog.
1085     */
1086    int getDesiredArtHeight(int originalWidth, int originalHeight) {
1087        if (originalWidth >= originalHeight) {
1088            // For landscape art, fit width to dialog width.
1089            return (int) ((float) mDialogContentWidth * originalHeight / originalWidth + 0.5f);
1090        }
1091        // For portrait art, fit height to 16:9 ratio case's height.
1092        return (int) ((float) mDialogContentWidth * 9 / 16 + 0.5f);
1093    }
1094
1095    void updateArtIconIfNeeded() {
1096        if (mCustomControlView != null || !isIconChanged()) {
1097            return;
1098        }
1099        if (mFetchArtTask != null) {
1100            mFetchArtTask.cancel(true);
1101        }
1102        mFetchArtTask = new FetchArtTask();
1103        mFetchArtTask.execute();
1104    }
1105
1106    /**
1107     * Clear the bitmap loaded by FetchArtTask. Will be called after the loaded bitmaps are applied
1108     * to artwork, or no longer valid.
1109     */
1110    void clearLoadedBitmap() {
1111        mArtIconIsLoaded = false;
1112        mArtIconLoadedBitmap = null;
1113        mArtIconBackgroundColor = 0;
1114    }
1115
1116    /**
1117     * Returns whether a new art image is different from an original art image. Compares
1118     * Bitmap objects first, and then compares URIs only if bitmap is unchanged with
1119     * a null value.
1120     */
1121    private boolean isIconChanged() {
1122        Bitmap newBitmap = mDescription == null ? null : mDescription.getIconBitmap();
1123        Uri newUri = mDescription == null ? null : mDescription.getIconUri();
1124        Bitmap oldBitmap = mFetchArtTask == null ? mArtIconBitmap : mFetchArtTask.getIconBitmap();
1125        Uri oldUri = mFetchArtTask == null ? mArtIconUri : mFetchArtTask.getIconUri();
1126        if (oldBitmap != newBitmap) {
1127            return true;
1128        } else if (oldBitmap == null && !uriEquals(oldUri, newUri)) {
1129            return true;
1130        }
1131        return false;
1132    }
1133
1134    private final class MediaRouterCallback extends MediaRouter.Callback {
1135        MediaRouterCallback() {
1136        }
1137
1138        @Override
1139        public void onRouteUnselected(MediaRouter router, MediaRouter.RouteInfo route) {
1140            update(false);
1141        }
1142
1143        @Override
1144        public void onRouteChanged(MediaRouter router, MediaRouter.RouteInfo route) {
1145            update(true);
1146        }
1147
1148        @Override
1149        public void onRouteVolumeChanged(MediaRouter router, MediaRouter.RouteInfo route) {
1150            SeekBar volumeSlider = mVolumeSliderMap.get(route);
1151            int volume = route.getVolume();
1152            if (DEBUG) {
1153                Log.d(TAG, "onRouteVolumeChanged(), route.getVolume:" + volume);
1154            }
1155            if (volumeSlider != null && mRouteInVolumeSliderTouched != route) {
1156                volumeSlider.setProgress(volume);
1157            }
1158        }
1159    }
1160
1161    private final class MediaControllerCallback extends MediaControllerCompat.Callback {
1162        MediaControllerCallback() {
1163        }
1164
1165        @Override
1166        public void onSessionDestroyed() {
1167            if (mMediaController != null) {
1168                mMediaController.unregisterCallback(mControllerCallback);
1169                mMediaController = null;
1170            }
1171        }
1172
1173        @Override
1174        public void onPlaybackStateChanged(PlaybackStateCompat state) {
1175            mState = state;
1176            update(false);
1177        }
1178
1179        @Override
1180        public void onMetadataChanged(MediaMetadataCompat metadata) {
1181            mDescription = metadata == null ? null : metadata.getDescription();
1182            updateArtIconIfNeeded();
1183            update(false);
1184        }
1185    }
1186
1187    private final class ClickListener implements View.OnClickListener {
1188        ClickListener() {
1189        }
1190
1191        @Override
1192        public void onClick(View v) {
1193            int id = v.getId();
1194            if (id == BUTTON_STOP_RES_ID || id == BUTTON_DISCONNECT_RES_ID) {
1195                if (mRoute.isSelected()) {
1196                    mRouter.unselect(id == BUTTON_STOP_RES_ID ?
1197                            MediaRouter.UNSELECT_REASON_STOPPED :
1198                            MediaRouter.UNSELECT_REASON_DISCONNECTED);
1199                }
1200                dismiss();
1201            } else if (id == R.id.mr_control_playback_ctrl) {
1202                if (mMediaController != null && mState != null) {
1203                    boolean isPlaying = mState.getState() == PlaybackStateCompat.STATE_PLAYING;
1204                    int actionDescResId = 0;
1205                    if (isPlaying && isPauseActionSupported()) {
1206                        mMediaController.getTransportControls().pause();
1207                        actionDescResId = R.string.mr_controller_pause;
1208                    } else if (isPlaying && isStopActionSupported()) {
1209                        mMediaController.getTransportControls().stop();
1210                        actionDescResId = R.string.mr_controller_stop;
1211                    } else if (!isPlaying && isPlayActionSupported()){
1212                        mMediaController.getTransportControls().play();
1213                        actionDescResId = R.string.mr_controller_play;
1214                    }
1215                    // Announce the action for accessibility.
1216                    if (mAccessibilityManager != null && mAccessibilityManager.isEnabled()
1217                            && actionDescResId != 0) {
1218                        AccessibilityEvent event = AccessibilityEvent.obtain(
1219                                AccessibilityEventCompat.TYPE_ANNOUNCEMENT);
1220                        event.setPackageName(mContext.getPackageName());
1221                        event.setClassName(getClass().getName());
1222                        event.getText().add(mContext.getString(actionDescResId));
1223                        mAccessibilityManager.sendAccessibilityEvent(event);
1224                    }
1225                }
1226            } else if (id == R.id.mr_close) {
1227                dismiss();
1228            }
1229        }
1230    }
1231
1232    private class VolumeChangeListener implements SeekBar.OnSeekBarChangeListener {
1233        private final Runnable mStopTrackingTouch = new Runnable() {
1234            @Override
1235            public void run() {
1236                if (mRouteInVolumeSliderTouched != null) {
1237                    mRouteInVolumeSliderTouched = null;
1238                    if (mHasPendingUpdate) {
1239                        update(mPendingUpdateAnimationNeeded);
1240                    }
1241                }
1242            }
1243        };
1244
1245        VolumeChangeListener() {
1246        }
1247
1248        @Override
1249        public void onStartTrackingTouch(SeekBar seekBar) {
1250            if (mRouteInVolumeSliderTouched != null) {
1251                mVolumeSlider.removeCallbacks(mStopTrackingTouch);
1252            }
1253            mRouteInVolumeSliderTouched = (MediaRouter.RouteInfo) seekBar.getTag();
1254        }
1255
1256        @Override
1257        public void onStopTrackingTouch(SeekBar seekBar) {
1258            // Defer resetting mVolumeSliderTouched to allow the media route provider
1259            // a little time to settle into its new state and publish the final
1260            // volume update.
1261            mVolumeSlider.postDelayed(mStopTrackingTouch, VOLUME_UPDATE_DELAY_MILLIS);
1262        }
1263
1264        @Override
1265        public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
1266            if (fromUser) {
1267                MediaRouter.RouteInfo route = (MediaRouter.RouteInfo) seekBar.getTag();
1268                if (DEBUG) {
1269                    Log.d(TAG, "onProgressChanged(): calling "
1270                            + "MediaRouter.RouteInfo.requestSetVolume(" + progress + ")");
1271                }
1272                route.requestSetVolume(progress);
1273            }
1274        }
1275    }
1276
1277    private class VolumeGroupAdapter extends ArrayAdapter<MediaRouter.RouteInfo> {
1278        final float mDisabledAlpha;
1279
1280        public VolumeGroupAdapter(Context context, List<MediaRouter.RouteInfo> objects) {
1281            super(context, 0, objects);
1282            mDisabledAlpha = MediaRouterThemeHelper.getDisabledAlpha(context);
1283        }
1284
1285        @Override
1286        public boolean isEnabled(int position) {
1287            return false;
1288        }
1289
1290        @Override
1291        public View getView(final int position, View convertView, ViewGroup parent) {
1292            View v = convertView;
1293            if (v == null) {
1294                v = LayoutInflater.from(parent.getContext()).inflate(
1295                        R.layout.mr_controller_volume_item, parent, false);
1296            } else {
1297                updateVolumeGroupItemHeight(v);
1298            }
1299
1300            MediaRouter.RouteInfo route = getItem(position);
1301            if (route != null) {
1302                boolean isEnabled = route.isEnabled();
1303
1304                TextView routeName = (TextView) v.findViewById(R.id.mr_name);
1305                routeName.setEnabled(isEnabled);
1306                routeName.setText(route.getName());
1307
1308                MediaRouteVolumeSlider volumeSlider =
1309                        (MediaRouteVolumeSlider) v.findViewById(R.id.mr_volume_slider);
1310                MediaRouterThemeHelper.setVolumeSliderColor(
1311                        parent.getContext(), volumeSlider, mVolumeGroupList);
1312                volumeSlider.setTag(route);
1313                mVolumeSliderMap.put(route, volumeSlider);
1314                volumeSlider.setHideThumb(!isEnabled);
1315                volumeSlider.setEnabled(isEnabled);
1316                if (isEnabled) {
1317                    if (isVolumeControlAvailable(route)) {
1318                        volumeSlider.setMax(route.getVolumeMax());
1319                        volumeSlider.setProgress(route.getVolume());
1320                        volumeSlider.setOnSeekBarChangeListener(mVolumeChangeListener);
1321                    } else {
1322                        volumeSlider.setMax(100);
1323                        volumeSlider.setProgress(100);
1324                        volumeSlider.setEnabled(false);
1325                    }
1326                }
1327
1328                ImageView volumeItemIcon =
1329                        (ImageView) v.findViewById(R.id.mr_volume_item_icon);
1330                volumeItemIcon.setAlpha(isEnabled ? 0xFF : (int) (0xFF * mDisabledAlpha));
1331
1332                // If overlay bitmap exists, real view should remain hidden until
1333                // the animation ends.
1334                LinearLayout container = (LinearLayout) v.findViewById(R.id.volume_item_container);
1335                container.setVisibility(mGroupMemberRoutesAnimatingWithBitmap.contains(route)
1336                        ? View.INVISIBLE : View.VISIBLE);
1337
1338                // Routes which are being added will be invisible until animation ends.
1339                if (mGroupMemberRoutesAdded != null && mGroupMemberRoutesAdded.contains(route)) {
1340                    Animation alphaAnim = new AlphaAnimation(0.0f, 0.0f);
1341                    alphaAnim.setDuration(0);
1342                    alphaAnim.setFillEnabled(true);
1343                    alphaAnim.setFillAfter(true);
1344                    v.clearAnimation();
1345                    v.startAnimation(alphaAnim);
1346                }
1347            }
1348            return v;
1349        }
1350    }
1351
1352    private class FetchArtTask extends AsyncTask<Void, Void, Bitmap> {
1353        // Show animation only when fetching takes a long time.
1354        private static final long SHOW_ANIM_TIME_THRESHOLD_MILLIS = 120L;
1355
1356        private final Bitmap mIconBitmap;
1357        private final Uri mIconUri;
1358        private int mBackgroundColor;
1359        private long mStartTimeMillis;
1360
1361        FetchArtTask() {
1362            Bitmap bitmap = mDescription == null ? null : mDescription.getIconBitmap();
1363            if (isBitmapRecycled(bitmap)) {
1364                Log.w(TAG, "Can't fetch the given art bitmap because it's already recycled.");
1365                bitmap = null;
1366            }
1367            mIconBitmap = bitmap;
1368            mIconUri = mDescription == null ? null : mDescription.getIconUri();
1369        }
1370
1371        public Bitmap getIconBitmap() {
1372            return mIconBitmap;
1373        }
1374
1375        public Uri getIconUri() {
1376            return mIconUri;
1377        }
1378
1379        @Override
1380        protected void onPreExecute() {
1381            mStartTimeMillis = SystemClock.uptimeMillis();
1382            clearLoadedBitmap();
1383        }
1384
1385        @Override
1386        protected Bitmap doInBackground(Void... arg) {
1387            Bitmap art = null;
1388            if (mIconBitmap != null) {
1389                art = mIconBitmap;
1390            } else if (mIconUri != null) {
1391                InputStream stream = null;
1392                try {
1393                    if ((stream = openInputStreamByScheme(mIconUri)) == null) {
1394                        Log.w(TAG, "Unable to open: " + mIconUri);
1395                        return null;
1396                    }
1397                    // Query art size.
1398                    BitmapFactory.Options options = new BitmapFactory.Options();
1399                    options.inJustDecodeBounds = true;
1400                    BitmapFactory.decodeStream(stream, null, options);
1401                    if (options.outWidth == 0 || options.outHeight == 0) {
1402                        return null;
1403                    }
1404                    // Rewind the stream in order to restart art decoding.
1405                    try {
1406                        stream.reset();
1407                    } catch (IOException e) {
1408                        // Failed to rewind the stream, try to reopen it.
1409                        stream.close();
1410                        if ((stream = openInputStreamByScheme(mIconUri)) == null) {
1411                            Log.w(TAG, "Unable to open: " + mIconUri);
1412                            return null;
1413                        }
1414                    }
1415                    // Calculate required size to decode the art and possibly resize it.
1416                    options.inJustDecodeBounds = false;
1417                    int reqHeight = getDesiredArtHeight(options.outWidth, options.outHeight);
1418                    int ratio = options.outHeight / reqHeight;
1419                    options.inSampleSize = Math.max(1, Integer.highestOneBit(ratio));
1420                    if (isCancelled()) {
1421                        return null;
1422                    }
1423                    art = BitmapFactory.decodeStream(stream, null, options);
1424                } catch (IOException e){
1425                    Log.w(TAG, "Unable to open: " + mIconUri, e);
1426                } finally {
1427                    if (stream != null) {
1428                        try {
1429                            stream.close();
1430                        } catch (IOException e) {
1431                        }
1432                    }
1433                }
1434            }
1435            if (isBitmapRecycled(art)) {
1436                Log.w(TAG, "Can't use recycled bitmap: " + art);
1437                return null;
1438            }
1439            if (art != null && art.getWidth() < art.getHeight()) {
1440                // Portrait art requires dominant color as background color.
1441                Palette palette = new Palette.Builder(art).maximumColorCount(1).generate();
1442                mBackgroundColor = palette.getSwatches().isEmpty()
1443                        ? 0 : palette.getSwatches().get(0).getRgb();
1444            }
1445            return art;
1446        }
1447
1448        @Override
1449        protected void onPostExecute(Bitmap art) {
1450            mFetchArtTask = null;
1451            if (!ObjectsCompat.equals(mArtIconBitmap, mIconBitmap)
1452                    || !ObjectsCompat.equals(mArtIconUri, mIconUri)) {
1453                mArtIconBitmap = mIconBitmap;
1454                mArtIconLoadedBitmap = art;
1455                mArtIconUri = mIconUri;
1456                mArtIconBackgroundColor = mBackgroundColor;
1457                mArtIconIsLoaded = true;
1458                long elapsedTimeMillis = SystemClock.uptimeMillis() - mStartTimeMillis;
1459                // Loaded bitmap will be applied on the next update
1460                update(elapsedTimeMillis > SHOW_ANIM_TIME_THRESHOLD_MILLIS);
1461            }
1462        }
1463
1464        private InputStream openInputStreamByScheme(Uri uri) throws IOException {
1465            String scheme = uri.getScheme().toLowerCase();
1466            InputStream stream = null;
1467            if (ContentResolver.SCHEME_ANDROID_RESOURCE.equals(scheme)
1468                    || ContentResolver.SCHEME_CONTENT.equals(scheme)
1469                    || ContentResolver.SCHEME_FILE.equals(scheme)) {
1470                stream = mContext.getContentResolver().openInputStream(uri);
1471            } else {
1472                URL url = new URL(uri.toString());
1473                URLConnection conn = url.openConnection();
1474                conn.setConnectTimeout(CONNECTION_TIMEOUT_MILLIS);
1475                conn.setReadTimeout(CONNECTION_TIMEOUT_MILLIS);
1476                stream = conn.getInputStream();
1477            }
1478            return (stream == null) ? null : new BufferedInputStream(stream);
1479        }
1480    }
1481}
1482