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.v4.media.routing;
18
19import android.content.Context;
20import android.graphics.drawable.Drawable;
21import android.os.Build;
22import android.util.Log;
23
24import java.lang.reflect.InvocationTargetException;
25import java.lang.reflect.Method;
26import java.util.ArrayList;
27import java.util.List;
28
29class MediaRouterJellybean {
30    private static final String TAG = "MediaRouterJellybean";
31
32    public static final int ROUTE_TYPE_LIVE_AUDIO = 0x1;
33    public static final int ROUTE_TYPE_LIVE_VIDEO = 0x2;
34    public static final int ROUTE_TYPE_USER = 0x00800000;
35
36    public static final int ALL_ROUTE_TYPES =
37            MediaRouterJellybean.ROUTE_TYPE_LIVE_AUDIO
38            | MediaRouterJellybean.ROUTE_TYPE_LIVE_VIDEO
39            | MediaRouterJellybean.ROUTE_TYPE_USER;
40
41    public static Object getMediaRouter(Context context) {
42        return context.getSystemService(Context.MEDIA_ROUTER_SERVICE);
43    }
44
45    @SuppressWarnings({ "rawtypes", "unchecked" })
46    public static List getRoutes(Object routerObj) {
47        final android.media.MediaRouter router = (android.media.MediaRouter)routerObj;
48        final int count = router.getRouteCount();
49        List out = new ArrayList(count);
50        for (int i = 0; i < count; i++) {
51            out.add(router.getRouteAt(i));
52        }
53        return out;
54    }
55
56    @SuppressWarnings({ "rawtypes", "unchecked" })
57    public static List getCategories(Object routerObj) {
58        final android.media.MediaRouter router = (android.media.MediaRouter)routerObj;
59        final int count = router.getCategoryCount();
60        List out = new ArrayList(count);
61        for (int i = 0; i < count; i++) {
62            out.add(router.getCategoryAt(i));
63        }
64        return out;
65    }
66
67    public static Object getSelectedRoute(Object routerObj, int type) {
68        return ((android.media.MediaRouter)routerObj).getSelectedRoute(type);
69    }
70
71    public static void selectRoute(Object routerObj, int types, Object routeObj) {
72        ((android.media.MediaRouter)routerObj).selectRoute(types,
73                (android.media.MediaRouter.RouteInfo)routeObj);
74    }
75
76    public static void addCallback(Object routerObj, int types, Object callbackObj) {
77        ((android.media.MediaRouter)routerObj).addCallback(types,
78                (android.media.MediaRouter.Callback)callbackObj);
79    }
80
81    public static void removeCallback(Object routerObj, Object callbackObj) {
82        ((android.media.MediaRouter)routerObj).removeCallback(
83                (android.media.MediaRouter.Callback)callbackObj);
84    }
85
86    public static Object createRouteCategory(Object routerObj,
87            String name, boolean isGroupable) {
88        return ((android.media.MediaRouter)routerObj).createRouteCategory(name, isGroupable);
89    }
90
91    public static Object createUserRoute(Object routerObj, Object categoryObj) {
92        return ((android.media.MediaRouter)routerObj).createUserRoute(
93                (android.media.MediaRouter.RouteCategory)categoryObj);
94    }
95
96    public static void addUserRoute(Object routerObj, Object routeObj) {
97        ((android.media.MediaRouter)routerObj).addUserRoute(
98                (android.media.MediaRouter.UserRouteInfo)routeObj);
99    }
100
101    public static void removeUserRoute(Object routerObj, Object routeObj) {
102        ((android.media.MediaRouter)routerObj).removeUserRoute(
103                (android.media.MediaRouter.UserRouteInfo)routeObj);
104    }
105
106    public static Object createCallback(Callback callback) {
107        return new CallbackProxy<Callback>(callback);
108    }
109
110    public static Object createVolumeCallback(VolumeCallback callback) {
111        return new VolumeCallbackProxy<VolumeCallback>(callback);
112    }
113
114    public static final class RouteInfo {
115        public static CharSequence getName(Object routeObj, Context context) {
116            return ((android.media.MediaRouter.RouteInfo)routeObj).getName(context);
117        }
118
119        public static CharSequence getStatus(Object routeObj) {
120            return ((android.media.MediaRouter.RouteInfo)routeObj).getStatus();
121        }
122
123        public static int getSupportedTypes(Object routeObj) {
124            return ((android.media.MediaRouter.RouteInfo)routeObj).getSupportedTypes();
125        }
126
127        public static Object getCategory(Object routeObj) {
128            return ((android.media.MediaRouter.RouteInfo)routeObj).getCategory();
129        }
130
131        public static Drawable getIconDrawable(Object routeObj) {
132            return ((android.media.MediaRouter.RouteInfo)routeObj).getIconDrawable();
133        }
134
135        public static int getPlaybackType(Object routeObj) {
136            return ((android.media.MediaRouter.RouteInfo)routeObj).getPlaybackType();
137        }
138
139        public static int getPlaybackStream(Object routeObj) {
140            return ((android.media.MediaRouter.RouteInfo)routeObj).getPlaybackStream();
141        }
142
143        public static int getVolume(Object routeObj) {
144            return ((android.media.MediaRouter.RouteInfo)routeObj).getVolume();
145        }
146
147        public static int getVolumeMax(Object routeObj) {
148            return ((android.media.MediaRouter.RouteInfo)routeObj).getVolumeMax();
149        }
150
151        public static int getVolumeHandling(Object routeObj) {
152            return ((android.media.MediaRouter.RouteInfo)routeObj).getVolumeHandling();
153        }
154
155        public static Object getTag(Object routeObj) {
156            return ((android.media.MediaRouter.RouteInfo)routeObj).getTag();
157        }
158
159        public static void setTag(Object routeObj, Object tag) {
160            ((android.media.MediaRouter.RouteInfo)routeObj).setTag(tag);
161        }
162
163        public static void requestSetVolume(Object routeObj, int volume) {
164            ((android.media.MediaRouter.RouteInfo)routeObj).requestSetVolume(volume);
165        }
166
167        public static void requestUpdateVolume(Object routeObj, int direction) {
168            ((android.media.MediaRouter.RouteInfo)routeObj).requestUpdateVolume(direction);
169        }
170
171        public static Object getGroup(Object routeObj) {
172            return ((android.media.MediaRouter.RouteInfo)routeObj).getGroup();
173        }
174
175        public static boolean isGroup(Object routeObj) {
176            return routeObj instanceof android.media.MediaRouter.RouteGroup;
177        }
178    }
179
180    public static final class RouteGroup {
181        @SuppressWarnings({ "rawtypes", "unchecked" })
182        public static List getGroupedRoutes(Object groupObj) {
183            final android.media.MediaRouter.RouteGroup group =
184                    (android.media.MediaRouter.RouteGroup)groupObj;
185            final int count = group.getRouteCount();
186            List out = new ArrayList(count);
187            for (int i = 0; i < count; i++) {
188                out.add(group.getRouteAt(i));
189            }
190            return out;
191        }
192    }
193
194    public static final class UserRouteInfo {
195        public static void setName(Object routeObj, CharSequence name) {
196            ((android.media.MediaRouter.UserRouteInfo)routeObj).setName(name);
197        }
198
199        public static void setStatus(Object routeObj, CharSequence status) {
200            ((android.media.MediaRouter.UserRouteInfo)routeObj).setStatus(status);
201        }
202
203        public static void setIconDrawable(Object routeObj, Drawable icon) {
204            ((android.media.MediaRouter.UserRouteInfo)routeObj).setIconDrawable(icon);
205        }
206
207        public static void setPlaybackType(Object routeObj, int type) {
208            ((android.media.MediaRouter.UserRouteInfo)routeObj).setPlaybackType(type);
209        }
210
211        public static void setPlaybackStream(Object routeObj, int stream) {
212            ((android.media.MediaRouter.UserRouteInfo)routeObj).setPlaybackStream(stream);
213        }
214
215        public static void setVolume(Object routeObj, int volume) {
216            ((android.media.MediaRouter.UserRouteInfo)routeObj).setVolume(volume);
217        }
218
219        public static void setVolumeMax(Object routeObj, int volumeMax) {
220            ((android.media.MediaRouter.UserRouteInfo)routeObj).setVolumeMax(volumeMax);
221        }
222
223        public static void setVolumeHandling(Object routeObj, int volumeHandling) {
224            ((android.media.MediaRouter.UserRouteInfo)routeObj).setVolumeHandling(volumeHandling);
225        }
226
227        public static void setVolumeCallback(Object routeObj, Object volumeCallbackObj) {
228            ((android.media.MediaRouter.UserRouteInfo)routeObj).setVolumeCallback(
229                    (android.media.MediaRouter.VolumeCallback)volumeCallbackObj);
230        }
231
232        public static void setRemoteControlClient(Object routeObj, Object rccObj) {
233            ((android.media.MediaRouter.UserRouteInfo)routeObj).setRemoteControlClient(
234                    (android.media.RemoteControlClient)rccObj);
235        }
236    }
237
238    public static final class RouteCategory {
239        public static CharSequence getName(Object categoryObj, Context context) {
240            return ((android.media.MediaRouter.RouteCategory)categoryObj).getName(context);
241        }
242
243        @SuppressWarnings({ "rawtypes", "unchecked" })
244        public static List getRoutes(Object categoryObj) {
245            ArrayList out = new ArrayList();
246            ((android.media.MediaRouter.RouteCategory)categoryObj).getRoutes(out);
247            return out;
248        }
249
250        public static int getSupportedTypes(Object categoryObj) {
251            return ((android.media.MediaRouter.RouteCategory)categoryObj).getSupportedTypes();
252        }
253
254        public static boolean isGroupable(Object categoryObj) {
255            return ((android.media.MediaRouter.RouteCategory)categoryObj).isGroupable();
256        }
257    }
258
259    public static interface Callback {
260        public void onRouteSelected(int type, Object routeObj);
261        public void onRouteUnselected(int type, Object routeObj);
262        public void onRouteAdded(Object routeObj);
263        public void onRouteRemoved(Object routeObj);
264        public void onRouteChanged(Object routeObj);
265        public void onRouteGrouped(Object routeObj, Object groupObj, int index);
266        public void onRouteUngrouped(Object routeObj, Object groupObj);
267        public void onRouteVolumeChanged(Object routeObj);
268    }
269
270    public static interface VolumeCallback {
271        public void onVolumeSetRequest(Object routeObj, int volume);
272        public void onVolumeUpdateRequest(Object routeObj, int direction);
273    }
274
275    /**
276     * Workaround for limitations of selectRoute() on JB and JB MR1.
277     * Do not use on JB MR2 and above.
278     */
279    public static final class SelectRouteWorkaround {
280        private Method mSelectRouteIntMethod;
281
282        public SelectRouteWorkaround() {
283            if (Build.VERSION.SDK_INT < 16 || Build.VERSION.SDK_INT > 17) {
284                throw new UnsupportedOperationException();
285            }
286            try {
287                mSelectRouteIntMethod = android.media.MediaRouter.class.getMethod(
288                        "selectRouteInt", int.class, android.media.MediaRouter.RouteInfo.class);
289            } catch (NoSuchMethodException ex) {
290            }
291        }
292
293        public void selectRoute(Object routerObj, int types, Object routeObj) {
294            android.media.MediaRouter router = (android.media.MediaRouter)routerObj;
295            android.media.MediaRouter.RouteInfo route =
296                    (android.media.MediaRouter.RouteInfo)routeObj;
297
298            int routeTypes = route.getSupportedTypes();
299            if ((routeTypes & ROUTE_TYPE_USER) == 0) {
300                // Handle non-user routes.
301                // On JB and JB MR1, the selectRoute() API only supports programmatically
302                // selecting user routes.  So instead we rely on the hidden selectRouteInt()
303                // method on these versions of the platform.
304                // This limitation was removed in JB MR2.
305                if (mSelectRouteIntMethod != null) {
306                    try {
307                        mSelectRouteIntMethod.invoke(router, types, route);
308                        return; // success!
309                    } catch (IllegalAccessException ex) {
310                        Log.w(TAG, "Cannot programmatically select non-user route.  "
311                                + "Media routing may not work.", ex);
312                    } catch (InvocationTargetException ex) {
313                        Log.w(TAG, "Cannot programmatically select non-user route.  "
314                                + "Media routing may not work.", ex);
315                    }
316                } else {
317                    Log.w(TAG, "Cannot programmatically select non-user route "
318                            + "because the platform is missing the selectRouteInt() "
319                            + "method.  Media routing may not work.");
320                }
321            }
322
323            // Default handling.
324            router.selectRoute(types, route);
325        }
326    }
327
328    /**
329     * Workaround the fact that the getDefaultRoute() method does not exist in JB and JB MR1.
330     * Do not use on JB MR2 and above.
331     */
332    public static final class GetDefaultRouteWorkaround {
333        private Method mGetSystemAudioRouteMethod;
334
335        public GetDefaultRouteWorkaround() {
336            if (Build.VERSION.SDK_INT < 16 || Build.VERSION.SDK_INT > 17) {
337                throw new UnsupportedOperationException();
338            }
339            try {
340                mGetSystemAudioRouteMethod =
341                        android.media.MediaRouter.class.getMethod("getSystemAudioRoute");
342            } catch (NoSuchMethodException ex) {
343            }
344        }
345
346        public Object getDefaultRoute(Object routerObj) {
347            android.media.MediaRouter router = (android.media.MediaRouter)routerObj;
348
349            if (mGetSystemAudioRouteMethod != null) {
350                try {
351                    return mGetSystemAudioRouteMethod.invoke(router);
352                } catch (IllegalAccessException ex) {
353                } catch (InvocationTargetException ex) {
354                }
355            }
356
357            // Could not find the method or it does not work.
358            // Return the first route and hope for the best.
359            return router.getRouteAt(0);
360        }
361    }
362
363    static class CallbackProxy<T extends Callback>
364            extends android.media.MediaRouter.Callback {
365        protected final T mCallback;
366
367        public CallbackProxy(T callback) {
368            mCallback = callback;
369        }
370
371        @Override
372        public void onRouteSelected(android.media.MediaRouter router,
373                int type, android.media.MediaRouter.RouteInfo route) {
374            mCallback.onRouteSelected(type, route);
375        }
376
377        @Override
378        public void onRouteUnselected(android.media.MediaRouter router,
379                int type, android.media.MediaRouter.RouteInfo route) {
380            mCallback.onRouteUnselected(type, route);
381        }
382
383        @Override
384        public void onRouteAdded(android.media.MediaRouter router,
385                android.media.MediaRouter.RouteInfo route) {
386            mCallback.onRouteAdded(route);
387        }
388
389        @Override
390        public void onRouteRemoved(android.media.MediaRouter router,
391                android.media.MediaRouter.RouteInfo route) {
392            mCallback.onRouteRemoved(route);
393        }
394
395        @Override
396        public void onRouteChanged(android.media.MediaRouter router,
397                android.media.MediaRouter.RouteInfo route) {
398            mCallback.onRouteChanged(route);
399        }
400
401        @Override
402        public void onRouteGrouped(android.media.MediaRouter router,
403                android.media.MediaRouter.RouteInfo route,
404                android.media.MediaRouter.RouteGroup group, int index) {
405            mCallback.onRouteGrouped(route, group, index);
406        }
407
408        @Override
409        public void onRouteUngrouped(android.media.MediaRouter router,
410                android.media.MediaRouter.RouteInfo route,
411                android.media.MediaRouter.RouteGroup group) {
412            mCallback.onRouteUngrouped(route, group);
413        }
414
415        @Override
416        public void onRouteVolumeChanged(android.media.MediaRouter router,
417                android.media.MediaRouter.RouteInfo route) {
418            mCallback.onRouteVolumeChanged(route);
419        }
420    }
421
422    static class VolumeCallbackProxy<T extends VolumeCallback>
423            extends android.media.MediaRouter.VolumeCallback {
424        protected final T mCallback;
425
426        public VolumeCallbackProxy(T callback) {
427            mCallback = callback;
428        }
429
430        @Override
431        public void onVolumeSetRequest(android.media.MediaRouter.RouteInfo route,
432                int volume) {
433            mCallback.onVolumeSetRequest(route, volume);
434        }
435
436        @Override
437        public void onVolumeUpdateRequest(android.media.MediaRouter.RouteInfo route,
438                int direction) {
439            mCallback.onVolumeUpdateRequest(route, direction);
440        }
441    }
442}
443