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.app.Dialog;
20import android.content.Context;
21import android.os.Bundle;
22import android.support.annotation.NonNull;
23import android.support.v7.media.MediaRouter;
24import android.support.v7.media.MediaRouteSelector;
25import android.support.v7.mediarouter.R;
26import android.text.TextUtils;
27import android.view.LayoutInflater;
28import android.view.View;
29import android.view.ViewGroup;
30import android.view.Window;
31import android.widget.AdapterView;
32import android.widget.ArrayAdapter;
33import android.widget.ListView;
34import android.widget.TextView;
35
36import java.util.ArrayList;
37import java.util.Collections;
38import java.util.Comparator;
39import 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 */
50public 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
60    public MediaRouteChooserDialog(Context context) {
61        this(context, 0);
62    }
63
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
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     */
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     */
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     */
133    public boolean onFilterRoute(@NonNull MediaRouter.RouteInfo route) {
134        return !route.isDefault() && route.isEnabled() && route.matchesSelector(mSelector);
135    }
136
137    @Override
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
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
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     */
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
193        public RouteAdapter(Context context, List<MediaRouter.RouteInfo> routes) {
194            super(context, 0, routes);
195            mInflater = LayoutInflater.from(context);
196        }
197
198        @Override
199        public boolean areAllItemsEnabled() {
200            return false;
201        }
202
203        @Override
204        public boolean isEnabled(int position) {
205            return getItem(position).isEnabled();
206        }
207
208        @Override
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
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
242        public void onRouteAdded(MediaRouter router, MediaRouter.RouteInfo info) {
243            refreshRoutes();
244        }
245
246        @Override
247        public void onRouteRemoved(MediaRouter router, MediaRouter.RouteInfo info) {
248            refreshRoutes();
249        }
250
251        @Override
252        public void onRouteChanged(MediaRouter router, MediaRouter.RouteInfo info) {
253            refreshRoutes();
254        }
255
256        @Override
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
266        public int compare(MediaRouter.RouteInfo lhs, MediaRouter.RouteInfo rhs) {
267            return lhs.getName().compareTo(rhs.getName());
268        }
269    }
270}
271