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