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.setCheatSheetEnabled(true);
123         mButton.setRouteTypes(mRouteTypes);
124         mButton.setExtendedSettingsClickListener(mExtendedSettingsListener);
125         mButton.setLayoutParams(new ViewGroup.LayoutParams(
126                 ViewGroup.LayoutParams.WRAP_CONTENT,
127                 ViewGroup.LayoutParams.MATCH_PARENT));
128         return mButton;
129     }
130 
131     @Override
onPerformDefaultAction()132     public boolean onPerformDefaultAction() {
133         if (mButton != null) {
134             return mButton.showDialogInternal();
135         }
136         return false;
137     }
138 
139     @Override
overridesItemVisibility()140     public boolean overridesItemVisibility() {
141         return true;
142     }
143 
144     @Override
isVisible()145     public boolean isVisible() {
146         return mRouter.isRouteAvailable(mRouteTypes,
147                 MediaRouter.AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE);
148     }
149 
refreshRoute()150     private void refreshRoute() {
151         refreshVisibility();
152     }
153 
154     private static class MediaRouterCallback extends MediaRouter.SimpleCallback {
155         private final WeakReference<MediaRouteActionProvider> mProviderWeak;
156 
MediaRouterCallback(MediaRouteActionProvider provider)157         public MediaRouterCallback(MediaRouteActionProvider provider) {
158             mProviderWeak = new WeakReference<MediaRouteActionProvider>(provider);
159         }
160 
161         @Override
onRouteAdded(MediaRouter router, RouteInfo info)162         public void onRouteAdded(MediaRouter router, RouteInfo info) {
163             refreshRoute(router);
164         }
165 
166         @Override
onRouteRemoved(MediaRouter router, RouteInfo info)167         public void onRouteRemoved(MediaRouter router, RouteInfo info) {
168             refreshRoute(router);
169         }
170 
171         @Override
onRouteChanged(MediaRouter router, RouteInfo info)172         public void onRouteChanged(MediaRouter router, RouteInfo info) {
173             refreshRoute(router);
174         }
175 
refreshRoute(MediaRouter router)176         private void refreshRoute(MediaRouter router) {
177             MediaRouteActionProvider provider = mProviderWeak.get();
178             if (provider != null) {
179                 provider.refreshRoute();
180             } else {
181                 router.removeCallback(this);
182             }
183         }
184     }
185 }
186