SystemMediaRouteProvider.java revision b507e525a61ed761eecfc2eaaf19af7e8db5dca5
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.media;
18
19import android.content.BroadcastReceiver;
20import android.content.Context;
21import android.content.Intent;
22import android.content.IntentFilter;
23import android.content.res.Resources;
24import android.media.AudioManager;
25import android.os.Build;
26import android.support.v7.mediarouter.R;
27import android.util.Log;
28import android.view.Display;
29
30import java.lang.reflect.InvocationTargetException;
31import java.lang.reflect.Method;
32import java.util.ArrayList;
33
34/**
35 * Provides routes for built-in system destinations such as the local display
36 * and speaker.  On Jellybean and newer platform releases, queries the framework
37 * MediaRouter for framework-provided routes and registers non-framework-provided
38 * routes as user routes.
39 */
40abstract class SystemMediaRouteProvider extends MediaRouteProvider {
41    private static final String TAG = "SystemMediaRouteProvider";
42
43    public static final String PACKAGE_NAME = "android";
44    public static final String DEFAULT_ROUTE_ID = "DEFAULT_ROUTE";
45
46    protected SystemMediaRouteProvider(Context context) {
47        super(context, new ProviderMetadata(PACKAGE_NAME));
48    }
49
50    public static SystemMediaRouteProvider obtain(Context context, SyncCallback syncCallback) {
51        if (Build.VERSION.SDK_INT >= 18) {
52            return new JellybeanMr2Impl(context, syncCallback);
53        }
54        if (Build.VERSION.SDK_INT >= 17) {
55            return new JellybeanMr1Impl(context, syncCallback);
56        }
57        if (Build.VERSION.SDK_INT >= 16) {
58            return new JellybeanImpl(context, syncCallback);
59        }
60        return new LegacyImpl(context);
61    }
62
63    /**
64     * Called by the media router when a route is added to synchronize state with
65     * the framework media router.
66     */
67    public void onSyncRouteAdded(MediaRouter.RouteInfo route) {
68    }
69
70    /**
71     * Called by the media router when a route is removed to synchronize state with
72     * the framework media router.
73     */
74    public void onSyncRouteRemoved(MediaRouter.RouteInfo route) {
75    }
76
77    /**
78     * Called by the media router when a route is changed to synchronize state with
79     * the framework media router.
80     */
81    public void onSyncRouteChanged(MediaRouter.RouteInfo route) {
82    }
83
84    /**
85     * Called by the media router when a route is selected to synchronize state with
86     * the framework media router.
87     */
88    public void onSyncRouteSelected(MediaRouter.RouteInfo route) {
89    }
90
91    /**
92     * Callbacks into the media router to synchronize state with the framework media router.
93     */
94    public interface SyncCallback {
95        public MediaRouter.RouteInfo getSystemRouteByDescriptorId(String id);
96    }
97
98    /**
99     * Legacy implementation for platform versions prior to Jellybean.
100     */
101    static class LegacyImpl extends SystemMediaRouteProvider {
102        private static final int PLAYBACK_STREAM = AudioManager.STREAM_MUSIC;
103
104        private static final IntentFilter[] CONTROL_FILTERS;
105        static {
106            CONTROL_FILTERS = new IntentFilter[1];
107            CONTROL_FILTERS[0] = new IntentFilter();
108            CONTROL_FILTERS[0].addCategory(MediaControlIntent.CATEGORY_LIVE_AUDIO);
109            CONTROL_FILTERS[0].addCategory(MediaControlIntent.CATEGORY_LIVE_VIDEO);
110        }
111
112        private final AudioManager mAudioManager;
113        private final VolumeChangeReceiver mVolumeChangeReceiver;
114        private int mLastReportedVolume = -1;
115
116        public LegacyImpl(Context context) {
117            super(context);
118            mAudioManager = (AudioManager)context.getSystemService(Context.AUDIO_SERVICE);
119            mVolumeChangeReceiver = new VolumeChangeReceiver();
120
121            context.registerReceiver(mVolumeChangeReceiver,
122                    new IntentFilter(VolumeChangeReceiver.VOLUME_CHANGED_ACTION));
123            publishRoutes();
124        }
125
126        private void publishRoutes() {
127            Resources r = getContext().getResources();
128            RouteDescriptor defaultRoute = new RouteDescriptor(
129                    DEFAULT_ROUTE_ID, r.getString(R.string.system_route_name));
130            defaultRoute.setControlFilters(CONTROL_FILTERS);
131            defaultRoute.setPlaybackStream(PLAYBACK_STREAM);
132            defaultRoute.setPlaybackType(MediaRouter.RouteInfo.PLAYBACK_TYPE_LOCAL);
133            defaultRoute.setVolumeHandling(MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE);
134            defaultRoute.setVolumeMax(mAudioManager.getStreamMaxVolume(PLAYBACK_STREAM));
135            mLastReportedVolume = mAudioManager.getStreamVolume(PLAYBACK_STREAM);
136            defaultRoute.setVolume(mLastReportedVolume);
137
138            ProviderDescriptor providerDescriptor = new ProviderDescriptor();
139            providerDescriptor.setRoutes(new RouteDescriptor[] { defaultRoute });
140            setDescriptor(providerDescriptor);
141        }
142
143        @Override
144        public RouteController onCreateRouteController(String routeId) {
145            if (routeId.equals(DEFAULT_ROUTE_ID)) {
146                return new DefaultRouteController();
147            }
148            return null;
149        }
150
151        final class DefaultRouteController extends RouteController {
152            @Override
153            public void setVolume(int volume) {
154                mAudioManager.setStreamVolume(PLAYBACK_STREAM, volume, 0);
155                publishRoutes();
156            }
157
158            @Override
159            public void updateVolume(int delta) {
160                int volume = mAudioManager.getStreamVolume(PLAYBACK_STREAM);
161                int maxVolume = mAudioManager.getStreamMaxVolume(PLAYBACK_STREAM);
162                int newVolume = Math.min(maxVolume, Math.max(0, volume + delta));
163                if (newVolume != volume) {
164                    mAudioManager.setStreamVolume(PLAYBACK_STREAM, volume, 0);
165                }
166                publishRoutes();
167            }
168        }
169
170        final class VolumeChangeReceiver extends BroadcastReceiver {
171            // These constants come from AudioManager.
172            public static final String VOLUME_CHANGED_ACTION =
173                    "android.media.VOLUME_CHANGED_ACTION";
174            public static final String EXTRA_VOLUME_STREAM_TYPE =
175                    "android.media.EXTRA_VOLUME_STREAM_TYPE";
176            public static final String EXTRA_VOLUME_STREAM_VALUE =
177                    "android.media.EXTRA_VOLUME_STREAM_VALUE";
178
179            @Override
180            public void onReceive(Context context, Intent intent) {
181                if (intent.getAction().equals(VOLUME_CHANGED_ACTION)) {
182                    final int streamType = intent.getIntExtra(EXTRA_VOLUME_STREAM_TYPE, -1);
183                    if (streamType == PLAYBACK_STREAM) {
184                        final int volume = intent.getIntExtra(EXTRA_VOLUME_STREAM_VALUE, -1);
185                        if (volume >= 0 && volume != mLastReportedVolume) {
186                            publishRoutes();
187                        }
188                    }
189                }
190            }
191        }
192    }
193
194    /**
195     * Jellybean implementation.
196     */
197    static class JellybeanImpl extends SystemMediaRouteProvider
198            implements MediaRouterJellybean.Callback, MediaRouterJellybean.VolumeCallback {
199        protected static final int ALL_ROUTE_TYPES =
200                MediaRouterJellybean.ROUTE_TYPE_LIVE_AUDIO
201                | MediaRouterJellybean.ROUTE_TYPE_LIVE_VIDEO
202                | MediaRouterJellybean.ROUTE_TYPE_USER;
203
204        private static final IntentFilter[] LIVE_AUDIO_CONTROL_FILTERS;
205        static {
206            LIVE_AUDIO_CONTROL_FILTERS = new IntentFilter[1];
207            LIVE_AUDIO_CONTROL_FILTERS[0] = new IntentFilter();
208            LIVE_AUDIO_CONTROL_FILTERS[0].addCategory(MediaControlIntent.CATEGORY_LIVE_AUDIO);
209        }
210
211        private static final IntentFilter[] LIVE_VIDEO_CONTROL_FILTERS;
212        static {
213            LIVE_VIDEO_CONTROL_FILTERS = new IntentFilter[1];
214            LIVE_VIDEO_CONTROL_FILTERS[0] = new IntentFilter();
215            LIVE_VIDEO_CONTROL_FILTERS[0].addCategory(MediaControlIntent.CATEGORY_LIVE_VIDEO);
216        }
217
218        private static final IntentFilter[] ALL_CONTROL_FILTERS;
219        static {
220            ALL_CONTROL_FILTERS = new IntentFilter[1];
221            ALL_CONTROL_FILTERS[0] = new IntentFilter();
222            ALL_CONTROL_FILTERS[0].addCategory(MediaControlIntent.CATEGORY_LIVE_AUDIO);
223            ALL_CONTROL_FILTERS[0].addCategory(MediaControlIntent.CATEGORY_LIVE_VIDEO);
224        }
225
226        private final SyncCallback mSyncCallback;
227        private Method mSelectRouteIntMethod;
228        private Method mGetSystemAudioRouteMethod;
229
230        protected final Object mRouterObj;
231        protected final Object mCallbackObj;
232        protected final Object mVolumeCallbackObj;
233        protected final Object mUserRouteCategoryObj;
234
235        // Maintains an association from framework routes to support library routes.
236        // Note that we cannot use the tag field for this because an application may
237        // have published its own user routes to the framework media router and already
238        // used the tag for its own purposes.
239        protected final ArrayList<SystemRouteRecord> mSystemRouteRecords =
240                new ArrayList<SystemRouteRecord>();
241
242        // Maintains an association from support library routes to framework routes.
243        protected final ArrayList<UserRouteRecord> mUserRouteRecords =
244                new ArrayList<UserRouteRecord>();
245
246        public JellybeanImpl(Context context, SyncCallback syncCallback) {
247            super(context);
248            mSyncCallback = syncCallback;
249            mRouterObj = MediaRouterJellybean.getMediaRouter(context);
250            mCallbackObj = createCallbackObj();
251            mVolumeCallbackObj = createVolumeCallbackObj();
252
253            Resources r = context.getResources();
254            mUserRouteCategoryObj = MediaRouterJellybean.createRouteCategory(
255                    mRouterObj, r.getString(R.string.user_route_category_name), false);
256
257            addInitialSystemRoutes();
258            MediaRouterJellybean.addCallback(mRouterObj, ALL_ROUTE_TYPES, mCallbackObj);
259        }
260
261        @Override
262        public void onRouteAdded(Object routeObj) {
263            if (getUserRouteRecord(routeObj) == null) {
264                int index = findSystemRouteRecord(routeObj);
265                if (index < 0) {
266                    addSystemRouteNoPublish(routeObj);
267                    publishRoutes();
268                }
269            }
270        }
271
272        private void addInitialSystemRoutes() {
273            for (Object routeObj : MediaRouterJellybean.getRoutes(mRouterObj)) {
274                addSystemRouteNoPublish(routeObj);
275            }
276            publishRoutes();
277        }
278
279        private void addSystemRouteNoPublish(Object routeObj) {
280            if (getUserRouteRecord(routeObj) == null) {
281                boolean isDefault = (getDefaultRoute() == routeObj);
282                SystemRouteRecord record = new SystemRouteRecord(routeObj, isDefault);
283                updateSystemRouteDescriptor(record);
284                mSystemRouteRecords.add(record);
285            }
286        }
287
288        @Override
289        public void onRouteRemoved(Object routeObj) {
290            if (getUserRouteRecord(routeObj) == null) {
291                int index = findSystemRouteRecord(routeObj);
292                if (index >= 0) {
293                    mSystemRouteRecords.remove(index);
294                    publishRoutes();
295                }
296            }
297        }
298
299        @Override
300        public void onRouteChanged(Object routeObj) {
301            if (getUserRouteRecord(routeObj) == null) {
302                int index = findSystemRouteRecord(routeObj);
303                if (index >= 0) {
304                    SystemRouteRecord record = mSystemRouteRecords.get(index);
305                    updateSystemRouteDescriptor(record);
306                    publishRoutes();
307                }
308            }
309        }
310
311        @Override
312        public void onRouteVolumeChanged(Object routeObj) {
313            if (getUserRouteRecord(routeObj) == null) {
314                int index = findSystemRouteRecord(routeObj);
315                if (index >= 0) {
316                    SystemRouteRecord record = mSystemRouteRecords.get(index);
317                    int newVolume = MediaRouterJellybean.RouteInfo.getVolume(routeObj);
318                    if (newVolume != record.mRouteDescriptor.getVolume()) {
319                        record.mRouteDescriptor = new RouteDescriptor(record.mRouteDescriptor);
320                        record.mRouteDescriptor.setVolume(newVolume);
321                        publishRoutes();
322                    }
323                }
324            }
325        }
326
327        @Override
328        public void onRouteSelected(int type, Object routeObj) {
329            if (routeObj != MediaRouterJellybean.getSelectedRoute(mRouterObj, ALL_ROUTE_TYPES)) {
330                // The currently selected route has already changed so this callback
331                // is stale.  Drop it to prevent getting into sync loops.
332                return;
333            }
334
335            UserRouteRecord userRouteRecord = getUserRouteRecord(routeObj);
336            if (userRouteRecord != null) {
337                userRouteRecord.mRoute.select();
338            } else {
339                // Select the route if it already exists in the compat media router.
340                // If not, we will select it instead when the route is added.
341                int index = findSystemRouteRecord(routeObj);
342                if (index >= 0) {
343                    SystemRouteRecord record = mSystemRouteRecords.get(index);
344                    MediaRouter.RouteInfo route = mSyncCallback.getSystemRouteByDescriptorId(
345                            record.mRouteDescriptorId);
346                    if (route != null) {
347                        route.select();
348                    }
349                }
350            }
351        }
352
353        @Override
354        public void onRouteUnselected(int type, Object routeObj) {
355            // Nothing to do when a route is unselected.
356            // We only need to handle when a route is selected.
357        }
358
359        @Override
360        public void onRouteGrouped(Object routeObj, Object groupObj, int index) {
361            // Route grouping is deprecated and no longer supported.
362        }
363
364        @Override
365        public void onRouteUngrouped(Object routeObj, Object groupObj) {
366            // Route grouping is deprecated and no longer supported.
367        }
368
369        @Override
370        public void onVolumeSetRequest(Object routeObj, int volume) {
371            UserRouteRecord record = getUserRouteRecord(routeObj);
372            if (record != null) {
373                record.mRoute.requestSetVolume(volume);
374            }
375        }
376
377        @Override
378        public void onVolumeUpdateRequest(Object routeObj, int direction) {
379            UserRouteRecord record = getUserRouteRecord(routeObj);
380            if (record != null) {
381                record.mRoute.requestUpdateVolume(direction);
382            }
383        }
384
385        @Override
386        public void onSyncRouteAdded(MediaRouter.RouteInfo route) {
387            if (route.getProviderInstance() != this) {
388                Object routeObj = MediaRouterJellybean.createUserRoute(
389                        mRouterObj, mUserRouteCategoryObj);
390                UserRouteRecord record = new UserRouteRecord(route, routeObj);
391                MediaRouterJellybean.RouteInfo.setTag(routeObj, record);
392                MediaRouterJellybean.UserRouteInfo.setVolumeCallback(routeObj, mVolumeCallbackObj);
393                updateUserRouteProperties(record);
394                mUserRouteRecords.add(record);
395                MediaRouterJellybean.addUserRoute(mRouterObj, routeObj);
396            } else {
397                // If the newly added route is the counterpart of the currently selected
398                // route in the framework media router then ensure it is selected in
399                // the compat media router.
400                Object routeObj = MediaRouterJellybean.getSelectedRoute(
401                        mRouterObj, ALL_ROUTE_TYPES);
402                int index = findSystemRouteRecord(routeObj);
403                if (index >= 0) {
404                    SystemRouteRecord record = mSystemRouteRecords.get(index);
405                    if (record.mRouteDescriptorId.equals(route.getDescriptorId())) {
406                        route.select();
407                    }
408                }
409            }
410        }
411
412        @Override
413        public void onSyncRouteRemoved(MediaRouter.RouteInfo route) {
414            if (route.getProviderInstance() != this) {
415                int index = findUserRouteRecord(route);
416                if (index >= 0) {
417                    UserRouteRecord record = mUserRouteRecords.remove(index);
418                    MediaRouterJellybean.RouteInfo.setTag(record.mRouteObj, null);
419                    MediaRouterJellybean.UserRouteInfo.setVolumeCallback(record.mRouteObj, null);
420                    MediaRouterJellybean.removeUserRoute(mRouterObj, record.mRouteObj);
421                }
422            }
423        }
424
425        @Override
426        public void onSyncRouteChanged(MediaRouter.RouteInfo route) {
427            if (route.getProviderInstance() != this) {
428                int index = findUserRouteRecord(route);
429                if (index >= 0) {
430                    UserRouteRecord record = mUserRouteRecords.get(index);
431                    updateUserRouteProperties(record);
432                }
433            }
434        }
435
436        @Override
437        public void onSyncRouteSelected(MediaRouter.RouteInfo route) {
438            if (!route.isSelected()) {
439                // The currently selected route has already changed so this callback
440                // is stale.  Drop it to prevent getting into sync loops.
441                return;
442            }
443
444            if (route.getProviderInstance() != this) {
445                int index = findUserRouteRecord(route);
446                if (index >= 0) {
447                    UserRouteRecord record = mUserRouteRecords.get(index);
448                    selectRoute(record.mRouteObj);
449                }
450            } else {
451                int index = findSystemRouteRecordByDescriptorId(route.getDescriptorId());
452                if (index >= 0) {
453                    SystemRouteRecord record = mSystemRouteRecords.get(index);
454                    selectRoute(record.mRouteObj);
455                }
456            }
457        }
458
459        protected void publishRoutes() {
460            int count = mSystemRouteRecords.size();
461            RouteDescriptor[] routeDescriptors = new RouteDescriptor[count];
462            for (int i = 0; i < count; i++) {
463                routeDescriptors[i] = mSystemRouteRecords.get(i).mRouteDescriptor;
464            }
465
466            ProviderDescriptor providerDescriptor = new ProviderDescriptor();
467            providerDescriptor.setRoutes(routeDescriptors);
468            setDescriptor(providerDescriptor);
469        }
470
471        protected int findSystemRouteRecord(Object routeObj) {
472            final int count = mSystemRouteRecords.size();
473            for (int i = 0; i < count; i++) {
474                if (mSystemRouteRecords.get(i).mRouteObj == routeObj) {
475                    return i;
476                }
477            }
478            return -1;
479        }
480
481        protected int findSystemRouteRecordByDescriptorId(String id) {
482            final int count = mSystemRouteRecords.size();
483            for (int i = 0; i < count; i++) {
484                if (mSystemRouteRecords.get(i).mRouteDescriptorId.equals(id)) {
485                    return i;
486                }
487            }
488            return -1;
489        }
490
491        protected int findUserRouteRecord(MediaRouter.RouteInfo route) {
492            final int count = mUserRouteRecords.size();
493            for (int i = 0; i < count; i++) {
494                if (mUserRouteRecords.get(i).mRoute == route) {
495                    return i;
496                }
497            }
498            return -1;
499        }
500
501        protected UserRouteRecord getUserRouteRecord(Object routeObj) {
502            Object tag = MediaRouterJellybean.RouteInfo.getTag(routeObj);
503            return tag instanceof UserRouteRecord ? (UserRouteRecord)tag : null;
504        }
505
506        protected void updateSystemRouteDescriptor(SystemRouteRecord record) {
507            // We must always recreate the route descriptor when making any changes
508            // because they are intended to be immutable once published.
509            String name = MediaRouterJellybean.RouteInfo.getName(
510                    record.mRouteObj, getContext()).toString();
511            record.mRouteDescriptor = new RouteDescriptor(
512                    record.mRouteDescriptorId, name);
513
514            int supportedTypes = MediaRouterJellybean.RouteInfo.getSupportedTypes(
515                    record.mRouteObj);
516            if ((supportedTypes & MediaRouterJellybean.ROUTE_TYPE_LIVE_AUDIO) != 0) {
517                if ((supportedTypes & MediaRouterJellybean.ROUTE_TYPE_LIVE_VIDEO) != 0) {
518                    record.mRouteDescriptor.setControlFilters(ALL_CONTROL_FILTERS);
519                } else {
520                    record.mRouteDescriptor.setControlFilters(LIVE_AUDIO_CONTROL_FILTERS);
521                }
522            } else if ((supportedTypes & MediaRouterJellybean.ROUTE_TYPE_LIVE_VIDEO) != 0) {
523                record.mRouteDescriptor.setControlFilters(LIVE_VIDEO_CONTROL_FILTERS);
524            }
525
526            CharSequence status = MediaRouterJellybean.RouteInfo.getStatus(record.mRouteObj);
527            if (status != null) {
528                record.mRouteDescriptor.setStatus(status.toString());
529            }
530            record.mRouteDescriptor.setIconDrawable(
531                    MediaRouterJellybean.RouteInfo.getIconDrawable(record.mRouteObj));
532            record.mRouteDescriptor.setPlaybackType(
533                    MediaRouterJellybean.RouteInfo.getPlaybackType(record.mRouteObj));
534            record.mRouteDescriptor.setPlaybackStream(
535                    MediaRouterJellybean.RouteInfo.getPlaybackStream(record.mRouteObj));
536            record.mRouteDescriptor.setVolume(
537                    MediaRouterJellybean.RouteInfo.getVolume(record.mRouteObj));
538            record.mRouteDescriptor.setVolumeMax(
539                    MediaRouterJellybean.RouteInfo.getVolumeMax(record.mRouteObj));
540            record.mRouteDescriptor.setVolumeHandling(
541                    MediaRouterJellybean.RouteInfo.getVolumeHandling(record.mRouteObj));
542        }
543
544        protected void updateUserRouteProperties(UserRouteRecord record) {
545            MediaRouterJellybean.UserRouteInfo.setName(
546                    record.mRouteObj, record.mRoute.getName());
547            MediaRouterJellybean.UserRouteInfo.setStatus(
548                    record.mRouteObj, normalizeStatus(record.mRoute.getStatus()));
549            MediaRouterJellybean.UserRouteInfo.setIconDrawable(
550                    record.mRouteObj, record.mRoute.getIconDrawable());
551            MediaRouterJellybean.UserRouteInfo.setPlaybackType(
552                    record.mRouteObj, record.mRoute.getPlaybackType());
553            MediaRouterJellybean.UserRouteInfo.setPlaybackStream(
554                    record.mRouteObj, record.mRoute.getPlaybackStream());
555            MediaRouterJellybean.UserRouteInfo.setVolume(
556                    record.mRouteObj, record.mRoute.getVolume());
557            MediaRouterJellybean.UserRouteInfo.setVolumeMax(
558                    record.mRouteObj, record.mRoute.getVolumeMax());
559            MediaRouterJellybean.UserRouteInfo.setVolumeHandling(
560                    record.mRouteObj, record.mRoute.getVolumeHandling());
561        }
562
563        // The framework MediaRouter crashes if we set a null status even though
564        // RouteInfo.getStatus() may return null.  So we need to use a different
565        // value instead.
566        private static String normalizeStatus(String status) {
567            return status != null ? status : "";
568        }
569
570        protected Object createCallbackObj() {
571            return MediaRouterJellybean.createCallback(this);
572        }
573
574        protected Object createVolumeCallbackObj() {
575            return MediaRouterJellybean.createVolumeCallback(this);
576        }
577
578        protected void selectRoute(Object routeObj) {
579            int types = MediaRouterJellybean.RouteInfo.getSupportedTypes(routeObj);
580            if ((types & MediaRouterJellybean.ROUTE_TYPE_USER) == 0) {
581                // Handle non-user routes.
582                // On JB and JB MR1, the selectRoute() API only supports programmatically
583                // selecting user routes.  So instead we rely on the hidden selectRouteInt()
584                // method on these versions of the platform.  This limitation was removed
585                // in JB MR2.  See also the JellybeanMr2Impl implementation of this method.
586                if (mSelectRouteIntMethod == null) {
587                    try {
588                        mSelectRouteIntMethod = mRouterObj.getClass().getMethod(
589                                "selectRouteInt", int.class, MediaRouterJellybean.RouteInfo.clazz);
590                    } catch (NoSuchMethodException ex) {
591                        Log.w(TAG, "Cannot programmatically select non-user route "
592                                + "because the platform is missing the selectRouteInt() "
593                                + "method.  Media routing may not work.", ex);
594                        return;
595                    }
596                }
597                try {
598                    mSelectRouteIntMethod.invoke(mRouterObj, ALL_ROUTE_TYPES, routeObj);
599                } catch (IllegalAccessException ex) {
600                    Log.w(TAG, "Cannot programmatically select non-user route.  "
601                            + "Media routing may not work.", ex);
602                } catch (InvocationTargetException ex) {
603                    Log.w(TAG, "Cannot programmatically select non-user route.  "
604                            + "Media routing may not work.", ex);
605                }
606            } else {
607                // Handle user routes.
608                MediaRouterJellybean.selectRoute(mRouterObj, ALL_ROUTE_TYPES, routeObj);
609            }
610        }
611
612        protected Object getDefaultRoute() {
613            // On JB and JB MR1, the getDefaultRoute() API does not exist.
614            // Instead there is a hidden getSystemAudioRoute() that does the same thing.
615            // See also the JellybeanMr2Impl implementation of this method.
616            if (mGetSystemAudioRouteMethod == null) {
617                try {
618                    mGetSystemAudioRouteMethod = mRouterObj.getClass().getMethod(
619                            "getSystemAudioRoute");
620                } catch (NoSuchMethodException ex) {
621                    // Fall through.
622                }
623            }
624            if (mGetSystemAudioRouteMethod != null) {
625                try {
626                    return mGetSystemAudioRouteMethod.invoke(mRouterObj);
627                } catch (IllegalAccessException ex) {
628                    // Fall through.
629                } catch (InvocationTargetException ex) {
630                    // Fall through.
631                }
632            }
633            // Could not find the method or it does not work.
634            // Return the first route and hope for the best.
635            return MediaRouterJellybean.getRoutes(mRouterObj).get(0);
636        }
637
638        /**
639         * Represents a route that is provided by the framework media router
640         * and published by this route provider to the support library media router.
641         */
642        protected static final class SystemRouteRecord {
643            private static int sNextId;
644
645            public final Object mRouteObj;
646            public final String mRouteDescriptorId;
647            public RouteDescriptor mRouteDescriptor; // assigned immediately after creation
648
649            public SystemRouteRecord(Object routeObj, boolean isDefault) {
650                mRouteObj = routeObj;
651                mRouteDescriptorId = isDefault ? DEFAULT_ROUTE_ID : "ROUTE_" + (sNextId++);
652            }
653        }
654
655        /**
656         * Represents a route that is provided by the support library media router
657         * and published by this route provider to the framework media router.
658         */
659        protected static final class UserRouteRecord {
660            public final MediaRouter.RouteInfo mRoute;
661            public final Object mRouteObj;
662
663            public UserRouteRecord(MediaRouter.RouteInfo route, Object routeObj) {
664                mRoute = route;
665                mRouteObj = routeObj;
666            }
667        }
668    }
669
670    /**
671     * Jellybean MR1 implementation.
672     */
673    private static class JellybeanMr1Impl extends JellybeanImpl
674            implements MediaRouterJellybeanMr1.Callback {
675        public JellybeanMr1Impl(Context context, SyncCallback syncCallback) {
676            super(context, syncCallback);
677        }
678
679        @Override
680        public void onRoutePresentationDisplayChanged(Object routeObj) {
681            int index = findSystemRouteRecord(routeObj);
682            if (index >= 0) {
683                SystemRouteRecord record = mSystemRouteRecords.get(index);
684                Display newPresentationDisplay =
685                        MediaRouterJellybeanMr1.RouteInfo.getPresentationDisplay(routeObj);
686                int newPresentationDisplayId = (newPresentationDisplay != null
687                        ? newPresentationDisplay.getDisplayId() : -1);
688                if (newPresentationDisplayId
689                        != record.mRouteDescriptor.getPresentationDisplayId()) {
690                    record.mRouteDescriptor = new RouteDescriptor(record.mRouteDescriptor);
691                    record.mRouteDescriptor.setPresentationDisplayId(newPresentationDisplayId);
692                    publishRoutes();
693                }
694            }
695        }
696
697        @Override
698        protected void updateSystemRouteDescriptor(SystemRouteRecord record) {
699            super.updateSystemRouteDescriptor(record);
700
701            if (!MediaRouterJellybeanMr1.RouteInfo.isEnabled(record.mRouteObj)) {
702                record.mRouteDescriptor.setEnabled(false);
703            }
704            Display presentationDisplay =
705                    MediaRouterJellybeanMr1.RouteInfo.getPresentationDisplay(record.mRouteObj);
706            if (presentationDisplay != null) {
707                record.mRouteDescriptor.setPresentationDisplayId(
708                        presentationDisplay.getDisplayId());
709            }
710        }
711
712        @Override
713        protected Object createCallbackObj() {
714            return MediaRouterJellybeanMr1.createCallback(this);
715        }
716    }
717
718    /**
719     * Jellybean MR2 implementation.
720     */
721    private static class JellybeanMr2Impl extends JellybeanMr1Impl {
722        public JellybeanMr2Impl(Context context, SyncCallback syncCallback) {
723            super(context, syncCallback);
724        }
725
726        @Override
727        protected void selectRoute(Object routeObj) {
728            MediaRouterJellybean.selectRoute(mRouterObj, ALL_ROUTE_TYPES, routeObj);
729        }
730
731        @Override
732        protected Object getDefaultRoute() {
733            return MediaRouterJellybeanMr2.getDefaultRoute(mRouterObj);
734        }
735    }
736}
737