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.app.Dialog;
20 import android.content.Context;
21 import android.os.Bundle;
22 import android.support.annotation.NonNull;
23 import android.support.v7.media.MediaRouter;
24 import android.support.v7.media.MediaRouteSelector;
25 import android.support.v7.mediarouter.R;
26 import android.text.TextUtils;
27 import android.view.LayoutInflater;
28 import android.view.View;
29 import android.view.ViewGroup;
30 import android.view.Window;
31 import android.widget.AdapterView;
32 import android.widget.ArrayAdapter;
33 import android.widget.ListView;
34 import android.widget.TextView;
35 
36 import java.util.ArrayList;
37 import java.util.Collections;
38 import java.util.Comparator;
39 import java.util.List;
40 
41 /**
42  * This class implements the route chooser dialog for {@link MediaRouter}.
43  * <p>
44  * This dialog allows the user to choose a route that matches a given selector.
45  * </p>
46  *
47  * @see MediaRouteButton
48  * @see MediaRouteActionProvider
49  */
50 public class MediaRouteChooserDialog extends Dialog {
51     private final MediaRouter mRouter;
52     private final MediaRouterCallback mCallback;
53 
54     private MediaRouteSelector mSelector = MediaRouteSelector.EMPTY;
55     private ArrayList<MediaRouter.RouteInfo> mRoutes;
56     private RouteAdapter mAdapter;
57     private ListView mListView;
58     private boolean mAttachedToWindow;
59 
MediaRouteChooserDialog(Context context)60     public MediaRouteChooserDialog(Context context) {
61         this(context, 0);
62     }
63 
MediaRouteChooserDialog(Context context, int theme)64     public MediaRouteChooserDialog(Context context, int theme) {
65         super(MediaRouterThemeHelper.createThemedContext(context), theme);
66         context = getContext();
67 
68         mRouter = MediaRouter.getInstance(context);
69         mCallback = new MediaRouterCallback();
70     }
71 
72     /**
73      * Gets the media route selector for filtering the routes that the user can select.
74      *
75      * @return The selector, never null.
76      */
77     @NonNull
getRouteSelector()78     public MediaRouteSelector getRouteSelector() {
79         return mSelector;
80     }
81 
82     /**
83      * Sets the media route selector for filtering the routes that the user can select.
84      *
85      * @param selector The selector, must not be null.
86      */
setRouteSelector(@onNull MediaRouteSelector selector)87     public void setRouteSelector(@NonNull MediaRouteSelector selector) {
88         if (selector == null) {
89             throw new IllegalArgumentException("selector must not be null");
90         }
91 
92         if (!mSelector.equals(selector)) {
93             mSelector = selector;
94 
95             if (mAttachedToWindow) {
96                 mRouter.removeCallback(mCallback);
97                 mRouter.addCallback(selector, mCallback,
98                         MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN);
99             }
100 
101             refreshRoutes();
102         }
103     }
104 
105     /**
106      * Called to filter the set of routes that should be included in the list.
107      * <p>
108      * The default implementation iterates over all routes in the provided list and
109      * removes those for which {@link #onFilterRoute} returns false.
110      * </p>
111      *
112      * @param routes The list of routes to filter in-place, never null.
113      */
onFilterRoutes(@onNull List<MediaRouter.RouteInfo> routes)114     public void onFilterRoutes(@NonNull List<MediaRouter.RouteInfo> routes) {
115         for (int i = routes.size(); i-- > 0; ) {
116             if (!onFilterRoute(routes.get(i))) {
117                 routes.remove(i);
118             }
119         }
120     }
121 
122     /**
123      * Returns true if the route should be included in the list.
124      * <p>
125      * The default implementation returns true for enabled non-default routes that
126      * match the selector.  Subclasses can override this method to filter routes
127      * differently.
128      * </p>
129      *
130      * @param route The route to consider, never null.
131      * @return True if the route should be included in the chooser dialog.
132      */
onFilterRoute(@onNull MediaRouter.RouteInfo route)133     public boolean onFilterRoute(@NonNull MediaRouter.RouteInfo route) {
134         return !route.isDefault() && route.isEnabled() && route.matchesSelector(mSelector);
135     }
136 
137     @Override
onCreate(Bundle savedInstanceState)138     protected void onCreate(Bundle savedInstanceState) {
139         super.onCreate(savedInstanceState);
140 
141         getWindow().requestFeature(Window.FEATURE_LEFT_ICON);
142 
143         setContentView(R.layout.mr_media_route_chooser_dialog);
144         setTitle(R.string.mr_media_route_chooser_title);
145 
146         // Must be called after setContentView.
147         getWindow().setFeatureDrawableResource(Window.FEATURE_LEFT_ICON,
148                 MediaRouterThemeHelper.getThemeResource(
149                         getContext(), R.attr.mediaRouteOffDrawable));
150 
151         mRoutes = new ArrayList<MediaRouter.RouteInfo>();
152         mAdapter = new RouteAdapter(getContext(), mRoutes);
153         mListView = (ListView)findViewById(R.id.media_route_list);
154         mListView.setAdapter(mAdapter);
155         mListView.setOnItemClickListener(mAdapter);
156         mListView.setEmptyView(findViewById(android.R.id.empty));
157     }
158 
159     @Override
onAttachedToWindow()160     public void onAttachedToWindow() {
161         super.onAttachedToWindow();
162 
163         mAttachedToWindow = true;
164         mRouter.addCallback(mSelector, mCallback, MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN);
165         refreshRoutes();
166     }
167 
168     @Override
onDetachedFromWindow()169     public void onDetachedFromWindow() {
170         mAttachedToWindow = false;
171         mRouter.removeCallback(mCallback);
172 
173         super.onDetachedFromWindow();
174     }
175 
176     /**
177      * Refreshes the list of routes that are shown in the chooser dialog.
178      */
refreshRoutes()179     public void refreshRoutes() {
180         if (mAttachedToWindow) {
181             mRoutes.clear();
182             mRoutes.addAll(mRouter.getRoutes());
183             onFilterRoutes(mRoutes);
184             Collections.sort(mRoutes, RouteComparator.sInstance);
185             mAdapter.notifyDataSetChanged();
186         }
187     }
188 
189     private final class RouteAdapter extends ArrayAdapter<MediaRouter.RouteInfo>
190             implements ListView.OnItemClickListener {
191         private final LayoutInflater mInflater;
192 
RouteAdapter(Context context, List<MediaRouter.RouteInfo> routes)193         public RouteAdapter(Context context, List<MediaRouter.RouteInfo> routes) {
194             super(context, 0, routes);
195             mInflater = LayoutInflater.from(context);
196         }
197 
198         @Override
areAllItemsEnabled()199         public boolean areAllItemsEnabled() {
200             return false;
201         }
202 
203         @Override
isEnabled(int position)204         public boolean isEnabled(int position) {
205             return getItem(position).isEnabled();
206         }
207 
208         @Override
getView(int position, View convertView, ViewGroup parent)209         public View getView(int position, View convertView, ViewGroup parent) {
210             View view = convertView;
211             if (view == null) {
212                 view = mInflater.inflate(R.layout.mr_media_route_list_item, parent, false);
213             }
214             MediaRouter.RouteInfo route = getItem(position);
215             TextView text1 = (TextView)view.findViewById(android.R.id.text1);
216             TextView text2 = (TextView)view.findViewById(android.R.id.text2);
217             text1.setText(route.getName());
218             String description = route.getDescription();
219             if (TextUtils.isEmpty(description)) {
220                 text2.setVisibility(View.GONE);
221                 text2.setText("");
222             } else {
223                 text2.setVisibility(View.VISIBLE);
224                 text2.setText(description);
225             }
226             view.setEnabled(route.isEnabled());
227             return view;
228         }
229 
230         @Override
onItemClick(AdapterView<?> parent, View view, int position, long id)231         public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
232             MediaRouter.RouteInfo route = getItem(position);
233             if (route.isEnabled()) {
234                 route.select();
235                 dismiss();
236             }
237         }
238     }
239 
240     private final class MediaRouterCallback extends MediaRouter.Callback {
241         @Override
onRouteAdded(MediaRouter router, MediaRouter.RouteInfo info)242         public void onRouteAdded(MediaRouter router, MediaRouter.RouteInfo info) {
243             refreshRoutes();
244         }
245 
246         @Override
onRouteRemoved(MediaRouter router, MediaRouter.RouteInfo info)247         public void onRouteRemoved(MediaRouter router, MediaRouter.RouteInfo info) {
248             refreshRoutes();
249         }
250 
251         @Override
onRouteChanged(MediaRouter router, MediaRouter.RouteInfo info)252         public void onRouteChanged(MediaRouter router, MediaRouter.RouteInfo info) {
253             refreshRoutes();
254         }
255 
256         @Override
onRouteSelected(MediaRouter router, MediaRouter.RouteInfo route)257         public void onRouteSelected(MediaRouter router, MediaRouter.RouteInfo route) {
258             dismiss();
259         }
260     }
261 
262     private static final class RouteComparator implements Comparator<MediaRouter.RouteInfo> {
263         public static final RouteComparator sInstance = new RouteComparator();
264 
265         @Override
compare(MediaRouter.RouteInfo lhs, MediaRouter.RouteInfo rhs)266         public int compare(MediaRouter.RouteInfo lhs, MediaRouter.RouteInfo rhs) {
267             return lhs.getName().compareTo(rhs.getName());
268         }
269     }
270 }
271