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 android.content.Context;
20 import android.media.MediaRouter;
21 import android.media.MediaRouter.RouteInfo;
22 import android.util.Log;
23 import android.view.ActionProvider;
24 import android.view.MenuItem;
25 import android.view.View;
26 import android.view.ViewGroup;
27 
28 import java.lang.ref.WeakReference;
29 
30 /**
31  * The media route action provider displays a {@link MediaRouteButton media route button}
32  * in the application's {@link ActionBar} to allow the user to select routes and
33  * to control the currently selected route.
34  * <p>
35  * The application must specify the kinds of routes that the user should be allowed
36  * to select by specifying the route types with the {@link #setRouteTypes} 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 public class MediaRouteActionProvider extends ActionProvider {
46     private static final String TAG = "MediaRouteActionProvider";
47 
48     private final Context mContext;
49     private final MediaRouter mRouter;
50     private final MediaRouterCallback mCallback;
51 
52     private int mRouteTypes;
53     private MediaRouteButton mButton;
54     private View.OnClickListener mExtendedSettingsListener;
55 
MediaRouteActionProvider(Context context)56     public MediaRouteActionProvider(Context context) {
57         super(context);
58 
59         mContext = context;
60         mRouter = (MediaRouter) context.getSystemService(Context.MEDIA_ROUTER_SERVICE);
61         mCallback = new MediaRouterCallback(this);
62 
63         // Start with live audio by default.
64         // TODO Update this when new route types are added; segment by API level
65         // when different route types were added.
66         setRouteTypes(MediaRouter.ROUTE_TYPE_LIVE_AUDIO);
67     }
68 
69     /**
70      * Sets the types of routes that will be shown in the media route chooser dialog
71      * launched by this button.
72      *
73      * @param types The route types to match.
74      */
setRouteTypes(int types)75     public void setRouteTypes(int types) {
76         if (mRouteTypes != types) {
77             // FIXME: We currently have no way of knowing whether the action provider
78             // is still needed by the UI.  Unfortunately this means the action provider
79             // may leak callbacks until garbage collection occurs.  This may result in
80             // media route providers doing more work than necessary in the short term
81             // while trying to discover routes that are no longer of interest to the
82             // application.  To solve this problem, the action provider will need some
83             // indication from the framework that it is being destroyed.
84             if (mRouteTypes != 0) {
85                 mRouter.removeCallback(mCallback);
86             }
87             mRouteTypes = types;
88             if (types != 0) {
89                 mRouter.addCallback(types, mCallback,
90                         MediaRouter.CALLBACK_FLAG_PASSIVE_DISCOVERY);
91             }
92             refreshRoute();
93 
94             if (mButton != null) {
95                 mButton.setRouteTypes(mRouteTypes);
96             }
97         }
98     }
99 
setExtendedSettingsClickListener(View.OnClickListener listener)100     public void setExtendedSettingsClickListener(View.OnClickListener listener) {
101         mExtendedSettingsListener = listener;
102         if (mButton != null) {
103             mButton.setExtendedSettingsClickListener(listener);
104         }
105     }
106 
107     @Override
108     @SuppressWarnings("deprecation")
onCreateActionView()109     public View onCreateActionView() {
110         throw new UnsupportedOperationException("Use onCreateActionView(MenuItem) instead.");
111     }
112 
113     @Override
onCreateActionView(MenuItem item)114     public View onCreateActionView(MenuItem item) {
115         if (mButton != null) {
116             Log.e(TAG, "onCreateActionView: this ActionProvider is already associated " +
117                     "with a menu item. Don't reuse MediaRouteActionProvider instances! " +
118                     "Abandoning the old one...");
119         }
120 
121         mButton = new MediaRouteButton(mContext);
122         mButton.setRouteTypes(mRouteTypes);
123         mButton.setExtendedSettingsClickListener(mExtendedSettingsListener);
124         mButton.setLayoutParams(new ViewGroup.LayoutParams(
125                 ViewGroup.LayoutParams.WRAP_CONTENT,
126                 ViewGroup.LayoutParams.MATCH_PARENT));
127         return mButton;
128     }
129 
130     @Override
onPerformDefaultAction()131     public boolean onPerformDefaultAction() {
132         if (mButton != null) {
133             return mButton.showDialogInternal();
134         }
135         return false;
136     }
137 
138     @Override
overridesItemVisibility()139     public boolean overridesItemVisibility() {
140         return true;
141     }
142 
143     @Override
isVisible()144     public boolean isVisible() {
145         return mRouter.isRouteAvailable(mRouteTypes,
146                 MediaRouter.AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE);
147     }
148 
refreshRoute()149     private void refreshRoute() {
150         refreshVisibility();
151     }
152 
153     private static class MediaRouterCallback extends MediaRouter.SimpleCallback {
154         private final WeakReference<MediaRouteActionProvider> mProviderWeak;
155 
MediaRouterCallback(MediaRouteActionProvider provider)156         public MediaRouterCallback(MediaRouteActionProvider provider) {
157             mProviderWeak = new WeakReference<MediaRouteActionProvider>(provider);
158         }
159 
160         @Override
onRouteAdded(MediaRouter router, RouteInfo info)161         public void onRouteAdded(MediaRouter router, RouteInfo info) {
162             refreshRoute(router);
163         }
164 
165         @Override
onRouteRemoved(MediaRouter router, RouteInfo info)166         public void onRouteRemoved(MediaRouter router, RouteInfo info) {
167             refreshRoute(router);
168         }
169 
170         @Override
onRouteChanged(MediaRouter router, RouteInfo info)171         public void onRouteChanged(MediaRouter router, RouteInfo info) {
172             refreshRoute(router);
173         }
174 
refreshRoute(MediaRouter router)175         private void refreshRoute(MediaRouter router) {
176             MediaRouteActionProvider provider = mProviderWeak.get();
177             if (provider != null) {
178                 provider.refreshRoute();
179             } else {
180                 router.removeCallback(this);
181             }
182         }
183     }
184 }
185