• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2012 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.app;
18 
19 import com.android.internal.R;
20 import com.android.internal.app.MediaRouteChooserDialogFragment;
21 
22 import android.content.Context;
23 import android.content.ContextWrapper;
24 import android.content.res.TypedArray;
25 import android.graphics.Canvas;
26 import android.graphics.drawable.Drawable;
27 import android.media.MediaRouter;
28 import android.media.MediaRouter.RouteGroup;
29 import android.media.MediaRouter.RouteInfo;
30 import android.util.AttributeSet;
31 import android.util.Log;
32 import android.view.SoundEffectConstants;
33 import android.view.View;
34 
35 public class MediaRouteButton extends View {
36     private static final String TAG = "MediaRouteButton";
37 
38     private MediaRouter mRouter;
39     private final MediaRouteCallback mRouterCallback = new MediaRouteCallback();
40     private int mRouteTypes;
41 
42     private boolean mAttachedToWindow;
43 
44     private Drawable mRemoteIndicator;
45     private boolean mRemoteActive;
46     private boolean mToggleMode;
47 
48     private int mMinWidth;
49     private int mMinHeight;
50 
51     private OnClickListener mExtendedSettingsClickListener;
52     private MediaRouteChooserDialogFragment mDialogFragment;
53 
54     private static final int[] ACTIVATED_STATE_SET = {
55         R.attr.state_activated
56     };
57 
MediaRouteButton(Context context)58     public MediaRouteButton(Context context) {
59         this(context, null);
60     }
61 
MediaRouteButton(Context context, AttributeSet attrs)62     public MediaRouteButton(Context context, AttributeSet attrs) {
63         this(context, null, com.android.internal.R.attr.mediaRouteButtonStyle);
64     }
65 
MediaRouteButton(Context context, AttributeSet attrs, int defStyleAttr)66     public MediaRouteButton(Context context, AttributeSet attrs, int defStyleAttr) {
67         super(context, attrs, defStyleAttr);
68 
69         mRouter = (MediaRouter)context.getSystemService(Context.MEDIA_ROUTER_SERVICE);
70 
71         TypedArray a = context.obtainStyledAttributes(attrs,
72                 com.android.internal.R.styleable.MediaRouteButton, defStyleAttr, 0);
73         setRemoteIndicatorDrawable(a.getDrawable(
74                 com.android.internal.R.styleable.MediaRouteButton_externalRouteEnabledDrawable));
75         mMinWidth = a.getDimensionPixelSize(
76                 com.android.internal.R.styleable.MediaRouteButton_minWidth, 0);
77         mMinHeight = a.getDimensionPixelSize(
78                 com.android.internal.R.styleable.MediaRouteButton_minHeight, 0);
79         final int routeTypes = a.getInteger(
80                 com.android.internal.R.styleable.MediaRouteButton_mediaRouteTypes,
81                 MediaRouter.ROUTE_TYPE_LIVE_AUDIO);
82         a.recycle();
83 
84         setClickable(true);
85 
86         setRouteTypes(routeTypes);
87     }
88 
setRemoteIndicatorDrawable(Drawable d)89     private void setRemoteIndicatorDrawable(Drawable d) {
90         if (mRemoteIndicator != null) {
91             mRemoteIndicator.setCallback(null);
92             unscheduleDrawable(mRemoteIndicator);
93         }
94         mRemoteIndicator = d;
95         if (d != null) {
96             d.setCallback(this);
97             d.setState(getDrawableState());
98             d.setVisible(getVisibility() == VISIBLE, false);
99         }
100 
101         refreshDrawableState();
102     }
103 
104     @Override
performClick()105     public boolean performClick() {
106         // Send the appropriate accessibility events and call listeners
107         boolean handled = super.performClick();
108         if (!handled) {
109             playSoundEffect(SoundEffectConstants.CLICK);
110         }
111 
112         if (mToggleMode) {
113             if (mRemoteActive) {
114                 mRouter.selectRouteInt(mRouteTypes, mRouter.getSystemAudioRoute());
115             } else {
116                 final int N = mRouter.getRouteCount();
117                 for (int i = 0; i < N; i++) {
118                     final RouteInfo route = mRouter.getRouteAt(i);
119                     if ((route.getSupportedTypes() & mRouteTypes) != 0 &&
120                             route != mRouter.getSystemAudioRoute()) {
121                         mRouter.selectRouteInt(mRouteTypes, route);
122                     }
123                 }
124             }
125         } else {
126             showDialog();
127         }
128 
129         return handled;
130     }
131 
setRouteTypes(int types)132     public void setRouteTypes(int types) {
133         if (types == mRouteTypes) {
134             // Already registered; nothing to do.
135             return;
136         }
137 
138         if (mAttachedToWindow && mRouteTypes != 0) {
139             mRouter.removeCallback(mRouterCallback);
140         }
141 
142         mRouteTypes = types;
143 
144         if (mAttachedToWindow) {
145             updateRouteInfo();
146             mRouter.addCallback(types, mRouterCallback);
147         }
148     }
149 
updateRouteInfo()150     private void updateRouteInfo() {
151         updateRemoteIndicator();
152         updateRouteCount();
153     }
154 
getRouteTypes()155     public int getRouteTypes() {
156         return mRouteTypes;
157     }
158 
updateRemoteIndicator()159     void updateRemoteIndicator() {
160         final boolean isRemote =
161                 mRouter.getSelectedRoute(mRouteTypes) != mRouter.getSystemAudioRoute();
162         if (mRemoteActive != isRemote) {
163             mRemoteActive = isRemote;
164             refreshDrawableState();
165         }
166     }
167 
updateRouteCount()168     void updateRouteCount() {
169         final int N = mRouter.getRouteCount();
170         int count = 0;
171         for (int i = 0; i < N; i++) {
172             final RouteInfo route = mRouter.getRouteAt(i);
173             if ((route.getSupportedTypes() & mRouteTypes) != 0) {
174                 if (route instanceof RouteGroup) {
175                     count += ((RouteGroup) route).getRouteCount();
176                 } else {
177                     count++;
178                 }
179             }
180         }
181 
182         setEnabled(count != 0);
183 
184         // Only allow toggling if we have more than just user routes
185         mToggleMode = count == 2 && (mRouteTypes & MediaRouter.ROUTE_TYPE_LIVE_AUDIO) != 0;
186     }
187 
188     @Override
onCreateDrawableState(int extraSpace)189     protected int[] onCreateDrawableState(int extraSpace) {
190         final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
191         if (mRemoteActive) {
192             mergeDrawableStates(drawableState, ACTIVATED_STATE_SET);
193         }
194         return drawableState;
195     }
196 
197     @Override
drawableStateChanged()198     protected void drawableStateChanged() {
199         super.drawableStateChanged();
200 
201         if (mRemoteIndicator != null) {
202             int[] myDrawableState = getDrawableState();
203             mRemoteIndicator.setState(myDrawableState);
204             invalidate();
205         }
206     }
207 
208     @Override
verifyDrawable(Drawable who)209     protected boolean verifyDrawable(Drawable who) {
210         return super.verifyDrawable(who) || who == mRemoteIndicator;
211     }
212 
213     @Override
jumpDrawablesToCurrentState()214     public void jumpDrawablesToCurrentState() {
215         super.jumpDrawablesToCurrentState();
216         if (mRemoteIndicator != null) mRemoteIndicator.jumpToCurrentState();
217     }
218 
219     @Override
setVisibility(int visibility)220     public void setVisibility(int visibility) {
221         super.setVisibility(visibility);
222         if (mRemoteIndicator != null) {
223             mRemoteIndicator.setVisible(getVisibility() == VISIBLE, false);
224         }
225     }
226 
227     @Override
onAttachedToWindow()228     public void onAttachedToWindow() {
229         super.onAttachedToWindow();
230         mAttachedToWindow = true;
231         if (mRouteTypes != 0) {
232             mRouter.addCallback(mRouteTypes, mRouterCallback);
233             updateRouteInfo();
234         }
235     }
236 
237     @Override
onDetachedFromWindow()238     public void onDetachedFromWindow() {
239         if (mRouteTypes != 0) {
240             mRouter.removeCallback(mRouterCallback);
241         }
242         mAttachedToWindow = false;
243         super.onDetachedFromWindow();
244     }
245 
246     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)247     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
248         final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
249         final int heightSize = MeasureSpec.getSize(heightMeasureSpec);
250         final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
251         final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
252 
253         final int minWidth = Math.max(mMinWidth,
254                 mRemoteIndicator != null ? mRemoteIndicator.getIntrinsicWidth() : 0);
255         final int minHeight = Math.max(mMinHeight,
256                 mRemoteIndicator != null ? mRemoteIndicator.getIntrinsicHeight() : 0);
257 
258         int width;
259         switch (widthMode) {
260             case MeasureSpec.EXACTLY:
261                 width = widthSize;
262                 break;
263             case MeasureSpec.AT_MOST:
264                 width = Math.min(widthSize, minWidth + getPaddingLeft() + getPaddingRight());
265                 break;
266             default:
267             case MeasureSpec.UNSPECIFIED:
268                 width = minWidth + getPaddingLeft() + getPaddingRight();
269                 break;
270         }
271 
272         int height;
273         switch (heightMode) {
274             case MeasureSpec.EXACTLY:
275                 height = heightSize;
276                 break;
277             case MeasureSpec.AT_MOST:
278                 height = Math.min(heightSize, minHeight + getPaddingTop() + getPaddingBottom());
279                 break;
280             default:
281             case MeasureSpec.UNSPECIFIED:
282                 height = minHeight + getPaddingTop() + getPaddingBottom();
283                 break;
284         }
285 
286         setMeasuredDimension(width, height);
287     }
288 
289     @Override
onDraw(Canvas canvas)290     protected void onDraw(Canvas canvas) {
291         super.onDraw(canvas);
292 
293         if (mRemoteIndicator == null) return;
294 
295         final int left = getPaddingLeft();
296         final int right = getWidth() - getPaddingRight();
297         final int top = getPaddingTop();
298         final int bottom = getHeight() - getPaddingBottom();
299 
300         final int drawWidth = mRemoteIndicator.getIntrinsicWidth();
301         final int drawHeight = mRemoteIndicator.getIntrinsicHeight();
302         final int drawLeft = left + (right - left - drawWidth) / 2;
303         final int drawTop = top + (bottom - top - drawHeight) / 2;
304 
305         mRemoteIndicator.setBounds(drawLeft, drawTop, drawLeft + drawWidth, drawTop + drawHeight);
306         mRemoteIndicator.draw(canvas);
307     }
308 
setExtendedSettingsClickListener(OnClickListener listener)309     public void setExtendedSettingsClickListener(OnClickListener listener) {
310         mExtendedSettingsClickListener = listener;
311         if (mDialogFragment != null) {
312             mDialogFragment.setExtendedSettingsClickListener(listener);
313         }
314     }
315 
316     /**
317      * Asynchronously show the route chooser dialog.
318      * This will attach a {@link DialogFragment} to the containing Activity.
319      */
showDialog()320     public void showDialog() {
321         final FragmentManager fm = getActivity().getFragmentManager();
322         if (mDialogFragment == null) {
323             // See if one is already attached to this activity.
324             mDialogFragment = (MediaRouteChooserDialogFragment) fm.findFragmentByTag(
325                     MediaRouteChooserDialogFragment.FRAGMENT_TAG);
326         }
327         if (mDialogFragment != null) {
328             Log.w(TAG, "showDialog(): Already showing!");
329             return;
330         }
331 
332         mDialogFragment = new MediaRouteChooserDialogFragment();
333         mDialogFragment.setExtendedSettingsClickListener(mExtendedSettingsClickListener);
334         mDialogFragment.setLauncherListener(new MediaRouteChooserDialogFragment.LauncherListener() {
335             @Override
336             public void onDetached(MediaRouteChooserDialogFragment detachedFragment) {
337                 mDialogFragment = null;
338             }
339         });
340         mDialogFragment.setRouteTypes(mRouteTypes);
341         mDialogFragment.show(fm, MediaRouteChooserDialogFragment.FRAGMENT_TAG);
342     }
343 
getActivity()344     private Activity getActivity() {
345         // Gross way of unwrapping the Activity so we can get the FragmentManager
346         Context context = getContext();
347         while (context instanceof ContextWrapper && !(context instanceof Activity)) {
348             context = ((ContextWrapper) context).getBaseContext();
349         }
350         if (!(context instanceof Activity)) {
351             throw new IllegalStateException("The MediaRouteButton's Context is not an Activity.");
352         }
353 
354         return (Activity) context;
355     }
356 
357     private class MediaRouteCallback extends MediaRouter.SimpleCallback {
358         @Override
onRouteSelected(MediaRouter router, int type, RouteInfo info)359         public void onRouteSelected(MediaRouter router, int type, RouteInfo info) {
360             updateRemoteIndicator();
361         }
362 
363         @Override
onRouteUnselected(MediaRouter router, int type, RouteInfo info)364         public void onRouteUnselected(MediaRouter router, int type, RouteInfo info) {
365             updateRemoteIndicator();
366         }
367 
368         @Override
onRouteAdded(MediaRouter router, RouteInfo info)369         public void onRouteAdded(MediaRouter router, RouteInfo info) {
370             updateRouteCount();
371         }
372 
373         @Override
onRouteRemoved(MediaRouter router, RouteInfo info)374         public void onRouteRemoved(MediaRouter router, RouteInfo info) {
375             updateRouteCount();
376         }
377 
378         @Override
onRouteGrouped(MediaRouter router, RouteInfo info, RouteGroup group, int index)379         public void onRouteGrouped(MediaRouter router, RouteInfo info, RouteGroup group,
380                 int index) {
381             updateRouteCount();
382         }
383 
384         @Override
onRouteUngrouped(MediaRouter router, RouteInfo info, RouteGroup group)385         public void onRouteUngrouped(MediaRouter router, RouteInfo info, RouteGroup group) {
386             updateRouteCount();
387         }
388     }
389 }
390