Home | History | Annotate | Download | only in app
      1 /*
      2  * Copyright (C) 2013 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.support.v7.app;
     18 
     19 import android.content.Context;
     20 import android.support.annotation.NonNull;
     21 import android.support.annotation.Nullable;
     22 import android.support.v4.view.ActionProvider;
     23 import android.support.v7.media.MediaRouter;
     24 import android.support.v7.media.MediaRouteSelector;
     25 import android.util.Log;
     26 import android.view.View;
     27 import android.view.ViewGroup;
     28 
     29 import java.lang.ref.WeakReference;
     30 
     31 /**
     32  * The media route action provider displays a {@link MediaRouteButton media route button}
     33  * in the application's {@link ActionBar} to allow the user to select routes and
     34  * to control the currently selected route.
     35  * <p>
     36  * The application must specify the kinds of routes that the user should be allowed
     37  * to select by specifying a {@link MediaRouteSelector selector} with the
     38  * {@link #setRouteSelector} method.
     39  * </p><p>
     40  * Refer to {@link MediaRouteButton} for a description of the button that will
     41  * appear in the action bar menu.  Note that instead of disabling the button
     42  * when no routes are available, the action provider will instead make the
     43  * menu item invisible.  In this way, the button will only be visible when it
     44  * is possible for the user to discover and select a matching route.
     45  * </p>
     46  *
     47  * <h3>Prerequisites</h3>
     48  * <p>
     49  * To use the media route action provider, the activity must be a subclass of
     50  * {@link AppCompatActivity} from the <code>android.support.v7.appcompat</code>
     51  * support library.  Refer to support library documentation for details.
     52  * </p>
     53  *
     54  * <h3>Example</h3>
     55  * <p>
     56  * </p><p>
     57  * The application should define a menu resource to include the provider in the
     58  * action bar options menu.  Note that the support library action bar uses attributes
     59  * that are defined in the application's resource namespace rather than the framework's
     60  * resource namespace to configure each item.
     61  * </p><pre>
     62  * &lt;menu xmlns:android="http://schemas.android.com/apk/res/android"
     63  *         xmlns:app="http://schemas.android.com/apk/res-auto">
     64  *     &lt;item android:id="@+id/media_route_menu_item"
     65  *         android:title="@string/media_route_menu_title"
     66  *         app:showAsAction="always"
     67  *         app:actionProviderClass="android.support.v7.app.MediaRouteActionProvider"/>
     68  * &lt;/menu>
     69  * </pre><p>
     70  * Then configure the menu and set the route selector for the chooser.
     71  * </p><pre>
     72  * public class MyActivity extends ActionBarActivity {
     73  *     private MediaRouter mRouter;
     74  *     private MediaRouter.Callback mCallback;
     75  *     private MediaRouteSelector mSelector;
     76  *
     77  *     protected void onCreate(Bundle savedInstanceState) {
     78  *         super.onCreate(savedInstanceState);
     79  *
     80  *         mRouter = Mediarouter.getInstance(this);
     81  *         mSelector = new MediaRouteSelector.Builder()
     82  *                 .addControlCategory(MediaControlIntent.CATEGORY_LIVE_AUDIO)
     83  *                 .addControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)
     84  *                 .build();
     85  *         mCallback = new MyCallback();
     86  *     }
     87  *
     88  *     // Add the callback on start to tell the media router what kinds of routes
     89  *     // the application is interested in so that it can try to discover suitable ones.
     90  *     public void onStart() {
     91  *         super.onStart();
     92  *
     93  *         mediaRouter.addCallback(mSelector, mCallback,
     94  *                 MediaRouter.CALLBACK_FLAG_REQUEST_DISCOVERY);
     95  *
     96  *         MediaRouter.RouteInfo route = mediaRouter.updateSelectedRoute(mSelector);
     97  *         // do something with the route...
     98  *     }
     99  *
    100  *     // Remove the selector on stop to tell the media router that it no longer
    101  *     // needs to invest effort trying to discover routes of these kinds for now.
    102  *     public void onStop() {
    103  *         super.onStop();
    104  *
    105  *         mediaRouter.removeCallback(mCallback);
    106  *     }
    107  *
    108  *     public boolean onCreateOptionsMenu(Menu menu) {
    109  *         super.onCreateOptionsMenu(menu);
    110  *
    111  *         getMenuInflater().inflate(R.menu.sample_media_router_menu, menu);
    112  *
    113  *         MenuItem mediaRouteMenuItem = menu.findItem(R.id.media_route_menu_item);
    114  *         MediaRouteActionProvider mediaRouteActionProvider =
    115  *                 (MediaRouteActionProvider)MenuItemCompat.getActionProvider(mediaRouteMenuItem);
    116  *         mediaRouteActionProvider.setRouteSelector(mSelector);
    117  *         return true;
    118  *     }
    119  *
    120  *     private final class MyCallback extends MediaRouter.Callback {
    121  *         // Implement callback methods as needed.
    122  *     }
    123  * }
    124  * </pre>
    125  *
    126  * @see #setRouteSelector
    127  */
    128 public class MediaRouteActionProvider extends ActionProvider {
    129     private static final String TAG = "MediaRouteActionProvider";
    130 
    131     private final MediaRouter mRouter;
    132     private final MediaRouterCallback mCallback;
    133 
    134     private MediaRouteSelector mSelector = MediaRouteSelector.EMPTY;
    135     private MediaRouteDialogFactory mDialogFactory = MediaRouteDialogFactory.getDefault();
    136     private MediaRouteButton mButton;
    137 
    138     /**
    139      * Creates the action provider.
    140      *
    141      * @param context The context.
    142      */
    143     public MediaRouteActionProvider(Context context) {
    144         super(context);
    145 
    146         mRouter = MediaRouter.getInstance(context);
    147         mCallback = new MediaRouterCallback(this);
    148     }
    149 
    150     /**
    151      * Gets the media route selector for filtering the routes that the user can
    152      * select using the media route chooser dialog.
    153      *
    154      * @return The selector, never null.
    155      */
    156     @NonNull
    157     public MediaRouteSelector getRouteSelector() {
    158         return mSelector;
    159     }
    160 
    161     /**
    162      * Sets the media route selector for filtering the routes that the user can
    163      * select using the media route chooser dialog.
    164      *
    165      * @param selector The selector, must not be null.
    166      */
    167     public void setRouteSelector(@NonNull MediaRouteSelector selector) {
    168         if (selector == null) {
    169             throw new IllegalArgumentException("selector must not be null");
    170         }
    171 
    172         if (!mSelector.equals(selector)) {
    173             // FIXME: We currently have no way of knowing whether the action provider
    174             // is still needed by the UI.  Unfortunately this means the action provider
    175             // may leak callbacks until garbage collection occurs.  This may result in
    176             // media route providers doing more work than necessary in the short term
    177             // while trying to discover routes that are no longer of interest to the
    178             // application.  To solve this problem, the action provider will need some
    179             // indication from the framework that it is being destroyed.
    180             if (!mSelector.isEmpty()) {
    181                 mRouter.removeCallback(mCallback);
    182             }
    183             if (!selector.isEmpty()) {
    184                 mRouter.addCallback(selector, mCallback);
    185             }
    186             mSelector = selector;
    187             refreshRoute();
    188 
    189             if (mButton != null) {
    190                 mButton.setRouteSelector(selector);
    191             }
    192         }
    193     }
    194 
    195     /**
    196      * Gets the media route dialog factory to use when showing the route chooser
    197      * or controller dialog.
    198      *
    199      * @return The dialog factory, never null.
    200      */
    201     @NonNull
    202     public MediaRouteDialogFactory getDialogFactory() {
    203         return mDialogFactory;
    204     }
    205 
    206     /**
    207      * Sets the media route dialog factory to use when showing the route chooser
    208      * or controller dialog.
    209      *
    210      * @param factory The dialog factory, must not be null.
    211      */
    212     public void setDialogFactory(@NonNull MediaRouteDialogFactory factory) {
    213         if (factory == null) {
    214             throw new IllegalArgumentException("factory must not be null");
    215         }
    216 
    217         if (mDialogFactory != factory) {
    218             mDialogFactory = factory;
    219 
    220             if (mButton != null) {
    221                 mButton.setDialogFactory(factory);
    222             }
    223         }
    224     }
    225 
    226     /**
    227      * Gets the associated media route button, or null if it has not yet been created.
    228      */
    229     @Nullable
    230     public MediaRouteButton getMediaRouteButton() {
    231         return mButton;
    232     }
    233 
    234     /**
    235      * Called when the media route button is being created.
    236      * <p>
    237      * Subclasses may override this method to customize the button.
    238      * </p>
    239      */
    240     public MediaRouteButton onCreateMediaRouteButton() {
    241         return new MediaRouteButton(getContext());
    242     }
    243 
    244     @Override
    245     @SuppressWarnings("deprecation")
    246     public View onCreateActionView() {
    247         if (mButton != null) {
    248             Log.e(TAG, "onCreateActionView: this ActionProvider is already associated " +
    249                     "with a menu item. Don't reuse MediaRouteActionProvider instances! " +
    250                     "Abandoning the old menu item...");
    251         }
    252 
    253         mButton = onCreateMediaRouteButton();
    254         mButton.setCheatSheetEnabled(true);
    255         mButton.setRouteSelector(mSelector);
    256         mButton.setDialogFactory(mDialogFactory);
    257         mButton.setLayoutParams(new ViewGroup.LayoutParams(
    258                 ViewGroup.LayoutParams.WRAP_CONTENT,
    259                 ViewGroup.LayoutParams.FILL_PARENT));
    260         return mButton;
    261     }
    262 
    263     @Override
    264     public boolean onPerformDefaultAction() {
    265         if (mButton != null) {
    266             return mButton.showDialog();
    267         }
    268         return false;
    269     }
    270 
    271     @Override
    272     public boolean overridesItemVisibility() {
    273         return true;
    274     }
    275 
    276     @Override
    277     public boolean isVisible() {
    278         return mRouter.isRouteAvailable(mSelector,
    279                 MediaRouter.AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE);
    280     }
    281 
    282     private void refreshRoute() {
    283         refreshVisibility();
    284     }
    285 
    286     private static final class MediaRouterCallback extends MediaRouter.Callback {
    287         private final WeakReference<MediaRouteActionProvider> mProviderWeak;
    288 
    289         public MediaRouterCallback(MediaRouteActionProvider provider) {
    290             mProviderWeak = new WeakReference<MediaRouteActionProvider>(provider);
    291         }
    292 
    293         @Override
    294         public void onRouteAdded(MediaRouter router, MediaRouter.RouteInfo info) {
    295             refreshRoute(router);
    296         }
    297 
    298         @Override
    299         public void onRouteRemoved(MediaRouter router, MediaRouter.RouteInfo info) {
    300             refreshRoute(router);
    301         }
    302 
    303         @Override
    304         public void onRouteChanged(MediaRouter router, MediaRouter.RouteInfo info) {
    305             refreshRoute(router);
    306         }
    307 
    308         @Override
    309         public void onProviderAdded(MediaRouter router, MediaRouter.ProviderInfo provider) {
    310             refreshRoute(router);
    311         }
    312 
    313         @Override
    314         public void onProviderRemoved(MediaRouter router, MediaRouter.ProviderInfo provider) {
    315             refreshRoute(router);
    316         }
    317 
    318         @Override
    319         public void onProviderChanged(MediaRouter router, MediaRouter.ProviderInfo provider) {
    320             refreshRoute(router);
    321         }
    322 
    323         private void refreshRoute(MediaRouter router) {
    324             MediaRouteActionProvider provider = mProviderWeak.get();
    325             if (provider != null) {
    326                 provider.refreshRoute();
    327             } else {
    328                 router.removeCallback(this);
    329             }
    330         }
    331     }
    332 }
    333