/* * Copyright (C) 2013 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.support.v7.app; import android.content.Context; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.view.ActionProvider; import android.support.v7.media.MediaRouter; import android.support.v7.media.MediaRouteSelector; import android.util.Log; import android.view.View; import android.view.ViewGroup; import java.lang.ref.WeakReference; /** * The media route action provider displays a {@link MediaRouteButton media route button} * in the application's {@link ActionBar} to allow the user to select routes and * to control the currently selected route. *

* The application must specify the kinds of routes that the user should be allowed * to select by specifying a {@link MediaRouteSelector selector} with the * {@link #setRouteSelector} method. *

* Refer to {@link MediaRouteButton} for a description of the button that will * appear in the action bar menu. Note that instead of disabling the button * when no routes are available, the action provider will instead make the * menu item invisible. In this way, the button will only be visible when it * is possible for the user to discover and select a matching route. *

* *

Prerequisites

*

* To use the media route action provider, the activity must be a subclass of * {@link ActionBarActivity} from the android.support.v7.appcompat * support library. Refer to support library documentation for details. *

* *

Example

*

*

* The application should define a menu resource to include the provider in the * action bar options menu. Note that the support library action bar uses attributes * that are defined in the application's resource namespace rather than the framework's * resource namespace to configure each item. *

 * <menu xmlns:android="http://schemas.android.com/apk/res/android"
 *         xmlns:app="http://schemas.android.com/apk/res-auto">
 *     <item android:id="@+id/media_route_menu_item"
 *         android:title="@string/media_route_menu_title"
 *         app:showAsAction="always"
 *         app:actionProviderClass="android.support.v7.app.MediaRouteActionProvider"/>
 * </menu>
 * 

* Then configure the menu and set the route selector for the chooser. *

 * public class MyActivity extends ActionBarActivity {
 *     private MediaRouter mRouter;
 *     private MediaRouter.Callback mCallback;
 *     private MediaRouteSelector mSelector;
 *
 *     protected void onCreate(Bundle savedInstanceState) {
 *         super.onCreate(savedInstanceState);
 *
 *         mRouter = Mediarouter.getInstance(this);
 *         mSelector = new MediaRouteSelector.Builder()
 *                 .addControlCategory(MediaControlIntent.CATEGORY_LIVE_AUDIO)
 *                 .addControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)
 *                 .build();
 *         mCallback = new MyCallback();
 *     }
 *
 *     // Add the callback on start to tell the media router what kinds of routes
 *     // the application is interested in so that it can try to discover suitable ones.
 *     public void onStart() {
 *         super.onStart();
 *
 *         mediaRouter.addCallback(mSelector, mCallback,
 *                 MediaRouter.CALLBACK_FLAG_REQUEST_DISCOVERY);
 *
 *         MediaRouter.RouteInfo route = mediaRouter.updateSelectedRoute(mSelector);
 *         // do something with the route...
 *     }
 *
 *     // Remove the selector on stop to tell the media router that it no longer
 *     // needs to invest effort trying to discover routes of these kinds for now.
 *     public void onStop() {
 *         super.onStop();
 *
 *         mediaRouter.removeCallback(mCallback);
 *     }
 *
 *     public boolean onCreateOptionsMenu(Menu menu) {
 *         super.onCreateOptionsMenu(menu);
 *
 *         getMenuInflater().inflate(R.menu.sample_media_router_menu, menu);
 *
 *         MenuItem mediaRouteMenuItem = menu.findItem(R.id.media_route_menu_item);
 *         MediaRouteActionProvider mediaRouteActionProvider =
 *                 (MediaRouteActionProvider)MenuItemCompat.getActionProvider(mediaRouteMenuItem);
 *         mediaRouteActionProvider.setRouteSelector(mSelector);
 *         return true;
 *     }
 *
 *     private final class MyCallback extends MediaRouter.Callback {
 *         // Implement callback methods as needed.
 *     }
 * }
 * 
* * @see #setRouteSelector */ public class MediaRouteActionProvider extends ActionProvider { private static final String TAG = "MediaRouteActionProvider"; private final MediaRouter mRouter; private final MediaRouterCallback mCallback; private MediaRouteSelector mSelector = MediaRouteSelector.EMPTY; private MediaRouteDialogFactory mDialogFactory = MediaRouteDialogFactory.getDefault(); private MediaRouteButton mButton; /** * Creates the action provider. * * @param context The context. */ public MediaRouteActionProvider(Context context) { super(context); mRouter = MediaRouter.getInstance(context); mCallback = new MediaRouterCallback(this); } /** * Gets the media route selector for filtering the routes that the user can * select using the media route chooser dialog. * * @return The selector, never null. */ @NonNull public MediaRouteSelector getRouteSelector() { return mSelector; } /** * Sets the media route selector for filtering the routes that the user can * select using the media route chooser dialog. * * @param selector The selector, must not be null. */ public void setRouteSelector(@NonNull MediaRouteSelector selector) { if (selector == null) { throw new IllegalArgumentException("selector must not be null"); } if (!mSelector.equals(selector)) { // FIXME: We currently have no way of knowing whether the action provider // is still needed by the UI. Unfortunately this means the action provider // may leak callbacks until garbage collection occurs. This may result in // media route providers doing more work than necessary in the short term // while trying to discover routes that are no longer of interest to the // application. To solve this problem, the action provider will need some // indication from the framework that it is being destroyed. if (!mSelector.isEmpty()) { mRouter.removeCallback(mCallback); } if (!selector.isEmpty()) { mRouter.addCallback(selector, mCallback); } mSelector = selector; refreshRoute(); if (mButton != null) { mButton.setRouteSelector(selector); } } } /** * Gets the media route dialog factory to use when showing the route chooser * or controller dialog. * * @return The dialog factory, never null. */ @NonNull public MediaRouteDialogFactory getDialogFactory() { return mDialogFactory; } /** * Sets the media route dialog factory to use when showing the route chooser * or controller dialog. * * @param factory The dialog factory, must not be null. */ public void setDialogFactory(@NonNull MediaRouteDialogFactory factory) { if (factory == null) { throw new IllegalArgumentException("factory must not be null"); } if (mDialogFactory != factory) { mDialogFactory = factory; if (mButton != null) { mButton.setDialogFactory(factory); } } } /** * Gets the associated media route button, or null if it has not yet been created. */ @Nullable public MediaRouteButton getMediaRouteButton() { return mButton; } /** * Called when the media route button is being created. *

* Subclasses may override this method to customize the button. *

*/ public MediaRouteButton onCreateMediaRouteButton() { return new MediaRouteButton(getContext()); } @Override @SuppressWarnings("deprecation") public View onCreateActionView() { if (mButton != null) { Log.e(TAG, "onCreateActionView: this ActionProvider is already associated " + "with a menu item. Don't reuse MediaRouteActionProvider instances! " + "Abandoning the old menu item..."); } mButton = onCreateMediaRouteButton(); mButton.setCheatSheetEnabled(true); mButton.setRouteSelector(mSelector); mButton.setDialogFactory(mDialogFactory); mButton.setLayoutParams(new ViewGroup.LayoutParams( ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.FILL_PARENT)); return mButton; } @Override public boolean onPerformDefaultAction() { if (mButton != null) { return mButton.showDialog(); } return false; } @Override public boolean overridesItemVisibility() { return true; } @Override public boolean isVisible() { return mRouter.isRouteAvailable(mSelector, MediaRouter.AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE); } private void refreshRoute() { refreshVisibility(); } private static final class MediaRouterCallback extends MediaRouter.Callback { private final WeakReference mProviderWeak; public MediaRouterCallback(MediaRouteActionProvider provider) { mProviderWeak = new WeakReference(provider); } @Override public void onRouteAdded(MediaRouter router, MediaRouter.RouteInfo info) { refreshRoute(router); } @Override public void onRouteRemoved(MediaRouter router, MediaRouter.RouteInfo info) { refreshRoute(router); } @Override public void onRouteChanged(MediaRouter router, MediaRouter.RouteInfo info) { refreshRoute(router); } @Override public void onProviderAdded(MediaRouter router, MediaRouter.ProviderInfo provider) { refreshRoute(router); } @Override public void onProviderRemoved(MediaRouter router, MediaRouter.ProviderInfo provider) { refreshRoute(router); } @Override public void onProviderChanged(MediaRouter router, MediaRouter.ProviderInfo provider) { refreshRoute(router); } private void refreshRoute(MediaRouter router) { MediaRouteActionProvider provider = mProviderWeak.get(); if (provider != null) { provider.refreshRoute(); } else { router.removeCallback(this); } } } }