Home | History | Annotate | Download | only in app
      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 
     58     public MediaRouteButton(Context context) {
     59         this(context, null);
     60     }
     61 
     62     public MediaRouteButton(Context context, AttributeSet attrs) {
     63         this(context, null, com.android.internal.R.attr.mediaRouteButtonStyle);
     64     }
     65 
     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 
     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
    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 
    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 
    150     private void updateRouteInfo() {
    151         updateRemoteIndicator();
    152         updateRouteCount();
    153     }
    154 
    155     public int getRouteTypes() {
    156         return mRouteTypes;
    157     }
    158 
    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 
    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
    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
    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
    209     protected boolean verifyDrawable(Drawable who) {
    210         return super.verifyDrawable(who) || who == mRemoteIndicator;
    211     }
    212 
    213     @Override
    214     public void jumpDrawablesToCurrentState() {
    215         super.jumpDrawablesToCurrentState();
    216         if (mRemoteIndicator != null) mRemoteIndicator.jumpToCurrentState();
    217     }
    218 
    219     @Override
    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
    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
    238     public void onDetachedFromWindow() {
    239         if (mRouteTypes != 0) {
    240             mRouter.removeCallback(mRouterCallback);
    241         }
    242         mAttachedToWindow = false;
    243         super.onDetachedFromWindow();
    244     }
    245 
    246     @Override
    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
    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 
    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      */
    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 
    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
    359         public void onRouteSelected(MediaRouter router, int type, RouteInfo info) {
    360             updateRemoteIndicator();
    361         }
    362 
    363         @Override
    364         public void onRouteUnselected(MediaRouter router, int type, RouteInfo info) {
    365             updateRemoteIndicator();
    366         }
    367 
    368         @Override
    369         public void onRouteAdded(MediaRouter router, RouteInfo info) {
    370             updateRouteCount();
    371         }
    372 
    373         @Override
    374         public void onRouteRemoved(MediaRouter router, RouteInfo info) {
    375             updateRouteCount();
    376         }
    377 
    378         @Override
    379         public void onRouteGrouped(MediaRouter router, RouteInfo info, RouteGroup group,
    380                 int index) {
    381             updateRouteCount();
    382         }
    383 
    384         @Override
    385         public void onRouteUngrouped(MediaRouter router, RouteInfo info, RouteGroup group) {
    386             updateRouteCount();
    387         }
    388     }
    389 }
    390