SystemMediaRouteProvider.java revision 19e77645aa6ce65b466d570375b36e2428f170b3
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.view.Display;
28
29import java.util.ArrayList;
30import java.util.List;
31import java.util.Locale;
32
33/**
34 * Provides routes for built-in system destinations such as the local display
35 * and speaker.  On Jellybean and newer platform releases, queries the framework
36 * MediaRouter for framework-provided routes and registers non-framework-provided
37 * routes as user routes.
38 */
39abstract class SystemMediaRouteProvider extends MediaRouteProvider {
40    private static final String TAG = "SystemMediaRouteProvider";
41
42    public static final String PACKAGE_NAME = "android";
43    public static final String DEFAULT_ROUTE_ID = "DEFAULT_ROUTE";
44
45    protected SystemMediaRouteProvider(Context context) {
46        super(context, new ProviderMetadata(PACKAGE_NAME));
47    }
48
49    public static SystemMediaRouteProvider obtain(Context context, SyncCallback syncCallback) {
50        if (Build.VERSION.SDK_INT >= 18) {
51            return new JellybeanMr2Impl(context, syncCallback);
52        }
53        if (Build.VERSION.SDK_INT >= 17) {
54            return new JellybeanMr1Impl(context, syncCallback);
55        }
56        if (Build.VERSION.SDK_INT >= 16) {
57            return new JellybeanImpl(context, syncCallback);
58        }
59        return new LegacyImpl(context);
60    }
61
62    /**
63     * Called by the media router when a route is added to synchronize state with
64     * the framework media router.
65     */
66    public void onSyncRouteAdded(MediaRouter.RouteInfo route) {
67    }
68
69    /**
70     * Called by the media router when a route is removed to synchronize state with
71     * the framework media router.
72     */
73    public void onSyncRouteRemoved(MediaRouter.RouteInfo route) {
74    }
75
76    /**
77     * Called by the media router when a route is changed to synchronize state with
78     * the framework media router.
79     */
80    public void onSyncRouteChanged(MediaRouter.RouteInfo route) {
81    }
82
83    /**
84     * Called by the media router when a route is selected to synchronize state with
85     * the framework media router.
86     */
87    public void onSyncRouteSelected(MediaRouter.RouteInfo route) {
88    }
89
90    /**
91     * Callbacks into the media router to synchronize state with the framework media router.
92     */
93    public interface SyncCallback {
94        public MediaRouter.RouteInfo getSystemRouteByDescriptorId(String id);
95    }
96
97    /**
98     * Legacy implementation for platform versions prior to Jellybean.
99     */
100    static class LegacyImpl extends SystemMediaRouteProvider {
101        private static final int PLAYBACK_STREAM = AudioManager.STREAM_MUSIC;
102
103        private static final ArrayList<IntentFilter> CONTROL_FILTERS;
104        static {
105            IntentFilter f = new IntentFilter();
106            f.addCategory(MediaControlIntent.CATEGORY_LIVE_AUDIO);
107            f.addCategory(MediaControlIntent.CATEGORY_LIVE_VIDEO);
108
109            CONTROL_FILTERS = new ArrayList<IntentFilter>();
110            CONTROL_FILTERS.add(f);
111        }
112
113        private final AudioManager mAudioManager;
114        private final VolumeChangeReceiver mVolumeChangeReceiver;
115        private int mLastReportedVolume = -1;
116
117        public LegacyImpl(Context context) {
118            super(context);
119            mAudioManager = (AudioManager)context.getSystemService(Context.AUDIO_SERVICE);
120            mVolumeChangeReceiver = new VolumeChangeReceiver();
121
122            context.registerReceiver(mVolumeChangeReceiver,
123                    new IntentFilter(VolumeChangeReceiver.VOLUME_CHANGED_ACTION));
124            publishRoutes();
125        }
126
127        private void publishRoutes() {
128            Resources r = getContext().getResources();
129            int maxVolume = mAudioManager.getStreamMaxVolume(PLAYBACK_STREAM);
130            mLastReportedVolume = mAudioManager.getStreamVolume(PLAYBACK_STREAM);
131            MediaRouteDescriptor defaultRoute = new MediaRouteDescriptor.Builder(
132                    DEFAULT_ROUTE_ID, r.getString(R.string.mr_system_route_name))
133                    .addControlFilters(CONTROL_FILTERS)
134                    .setPlaybackStream(PLAYBACK_STREAM)
135                    .setPlaybackType(MediaRouter.RouteInfo.PLAYBACK_TYPE_LOCAL)
136                    .setVolumeHandling(MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE)
137                    .setVolumeMax(maxVolume)
138                    .setVolume(mLastReportedVolume)
139                    .build();
140
141            MediaRouteProviderDescriptor providerDescriptor =
142                    new MediaRouteProviderDescriptor.Builder()
143                    .addRoute(defaultRoute)
144                    .build();
145            setDescriptor(providerDescriptor);
146        }
147
148        @Override
149        public RouteController onCreateRouteController(String routeId) {
150            if (routeId.equals(DEFAULT_ROUTE_ID)) {
151                return new DefaultRouteController();
152            }
153            return null;
154        }
155
156        final class DefaultRouteController extends RouteController {
157            @Override
158            public void onSetVolume(int volume) {
159                mAudioManager.setStreamVolume(PLAYBACK_STREAM, volume, 0);
160                publishRoutes();
161            }
162
163            @Override
164            public void onUpdateVolume(int delta) {
165                int volume = mAudioManager.getStreamVolume(PLAYBACK_STREAM);
166                int maxVolume = mAudioManager.getStreamMaxVolume(PLAYBACK_STREAM);
167                int newVolume = Math.min(maxVolume, Math.max(0, volume + delta));
168                if (newVolume != volume) {
169                    mAudioManager.setStreamVolume(PLAYBACK_STREAM, volume, 0);
170                }
171                publishRoutes();
172            }
173        }
174
175        final class VolumeChangeReceiver extends BroadcastReceiver {
176            // These constants come from AudioManager.
177            public static final String VOLUME_CHANGED_ACTION =
178                    "android.media.VOLUME_CHANGED_ACTION";
179            public static final String EXTRA_VOLUME_STREAM_TYPE =
180                    "android.media.EXTRA_VOLUME_STREAM_TYPE";
181            public static final String EXTRA_VOLUME_STREAM_VALUE =
182                    "android.media.EXTRA_VOLUME_STREAM_VALUE";
183
184            @Override
185            public void onReceive(Context context, Intent intent) {
186                if (intent.getAction().equals(VOLUME_CHANGED_ACTION)) {
187                    final int streamType = intent.getIntExtra(EXTRA_VOLUME_STREAM_TYPE, -1);
188                    if (streamType == PLAYBACK_STREAM) {
189                        final int volume = intent.getIntExtra(EXTRA_VOLUME_STREAM_VALUE, -1);
190                        if (volume >= 0 && volume != mLastReportedVolume) {
191                            publishRoutes();
192                        }
193                    }
194                }
195            }
196        }
197    }
198
199    /**
200     * Jellybean implementation.
201     */
202    static class JellybeanImpl extends SystemMediaRouteProvider
203            implements MediaRouterJellybean.Callback, MediaRouterJellybean.VolumeCallback {
204        private static final ArrayList<IntentFilter> LIVE_AUDIO_CONTROL_FILTERS;
205        static {
206            IntentFilter f = new IntentFilter();
207            f.addCategory(MediaControlIntent.CATEGORY_LIVE_AUDIO);
208
209            LIVE_AUDIO_CONTROL_FILTERS = new ArrayList<IntentFilter>();
210            LIVE_AUDIO_CONTROL_FILTERS.add(f);
211        }
212
213        private static final ArrayList<IntentFilter> LIVE_VIDEO_CONTROL_FILTERS;
214        static {
215            IntentFilter f = new IntentFilter();
216            f.addCategory(MediaControlIntent.CATEGORY_LIVE_VIDEO);
217
218            LIVE_VIDEO_CONTROL_FILTERS = new ArrayList<IntentFilter>();
219            LIVE_VIDEO_CONTROL_FILTERS.add(f);
220        }
221
222        private final SyncCallback mSyncCallback;
223
224        protected final Object mRouterObj;
225        protected final Object mCallbackObj;
226        protected final Object mVolumeCallbackObj;
227        protected final Object mUserRouteCategoryObj;
228        protected int mRouteTypes;
229        protected boolean mActiveScan;
230        protected boolean mCallbackRegistered;
231
232        // Maintains an association from framework routes to support library routes.
233        // Note that we cannot use the tag field for this because an application may
234        // have published its own user routes to the framework media router and already
235        // used the tag for its own purposes.
236        protected final ArrayList<SystemRouteRecord> mSystemRouteRecords =
237                new ArrayList<SystemRouteRecord>();
238
239        // Maintains an association from support library routes to framework routes.
240        protected final ArrayList<UserRouteRecord> mUserRouteRecords =
241                new ArrayList<UserRouteRecord>();
242
243        private MediaRouterJellybean.SelectRouteWorkaround mSelectRouteWorkaround;
244        private MediaRouterJellybean.GetDefaultRouteWorkaround mGetDefaultRouteWorkaround;
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.mr_user_route_category_name), false);
256
257            updateSystemRoutes();
258        }
259
260        @Override
261        public RouteController onCreateRouteController(String routeId) {
262            int index = findSystemRouteRecordByDescriptorId(routeId);
263            if (index >= 0) {
264                SystemRouteRecord record = mSystemRouteRecords.get(index);
265                return new SystemRouteController(record.mRouteObj);
266            }
267            return null;
268        }
269
270        @Override
271        public void onDiscoveryRequestChanged(MediaRouteDiscoveryRequest request) {
272            int newRouteTypes = 0;
273            boolean newActiveScan = false;
274            if (request != null) {
275                final MediaRouteSelector selector = request.getSelector();
276                final List<String> categories = selector.getControlCategories();
277                final int count = categories.size();
278                for (int i = 0; i < count; i++) {
279                    String category = categories.get(i);
280                    if (category.equals(MediaControlIntent.CATEGORY_LIVE_AUDIO)) {
281                        newRouteTypes |= MediaRouterJellybean.ROUTE_TYPE_LIVE_AUDIO;
282                    } else if (category.equals(MediaControlIntent.CATEGORY_LIVE_VIDEO)) {
283                        newRouteTypes |= MediaRouterJellybean.ROUTE_TYPE_LIVE_VIDEO;
284                    } else {
285                        newRouteTypes |= MediaRouterJellybean.ROUTE_TYPE_USER;
286                    }
287                }
288                newActiveScan = request.isActiveScan();
289            }
290
291            if (mRouteTypes != newRouteTypes || mActiveScan != newActiveScan) {
292                mRouteTypes = newRouteTypes;
293                mActiveScan = newActiveScan;
294                updateCallback();
295                updateSystemRoutes();
296            }
297        }
298
299        @Override
300        public void onRouteAdded(Object routeObj) {
301            if (addSystemRouteNoPublish(routeObj)) {
302                publishRoutes();
303            }
304        }
305
306        private void updateSystemRoutes() {
307            boolean changed = false;
308            for (Object routeObj : MediaRouterJellybean.getRoutes(mRouterObj)) {
309                changed |= addSystemRouteNoPublish(routeObj);
310            }
311            if (changed) {
312                publishRoutes();
313            }
314        }
315
316        private boolean addSystemRouteNoPublish(Object routeObj) {
317            if (getUserRouteRecord(routeObj) == null
318                    && findSystemRouteRecord(routeObj) < 0) {
319                String id = assignRouteId(routeObj);
320                SystemRouteRecord record = new SystemRouteRecord(routeObj, id);
321                updateSystemRouteDescriptor(record);
322                mSystemRouteRecords.add(record);
323                return true;
324            }
325            return false;
326        }
327
328        private String assignRouteId(Object routeObj) {
329            // TODO: The framework media router should supply a unique route id that
330            // we can use here.  For now we use a hash of the route name and take care
331            // to dedupe it.
332            boolean isDefault = (getDefaultRoute() == routeObj);
333            String id = isDefault ? DEFAULT_ROUTE_ID :
334                    String.format(Locale.US, "ROUTE_%08x", getRouteName(routeObj).hashCode());
335            if (findSystemRouteRecordByDescriptorId(id) < 0) {
336                return id;
337            }
338            for (int i = 2; ; i++) {
339                String newId = String.format(Locale.US, "%s_%d", id, i);
340                if (findSystemRouteRecordByDescriptorId(newId) < 0) {
341                    return newId;
342                }
343            }
344        }
345
346        @Override
347        public void onRouteRemoved(Object routeObj) {
348            if (getUserRouteRecord(routeObj) == null) {
349                int index = findSystemRouteRecord(routeObj);
350                if (index >= 0) {
351                    mSystemRouteRecords.remove(index);
352                    publishRoutes();
353                }
354            }
355        }
356
357        @Override
358        public void onRouteChanged(Object routeObj) {
359            if (getUserRouteRecord(routeObj) == null) {
360                int index = findSystemRouteRecord(routeObj);
361                if (index >= 0) {
362                    SystemRouteRecord record = mSystemRouteRecords.get(index);
363                    updateSystemRouteDescriptor(record);
364                    publishRoutes();
365                }
366            }
367        }
368
369        @Override
370        public void onRouteVolumeChanged(Object routeObj) {
371            if (getUserRouteRecord(routeObj) == null) {
372                int index = findSystemRouteRecord(routeObj);
373                if (index >= 0) {
374                    SystemRouteRecord record = mSystemRouteRecords.get(index);
375                    int newVolume = MediaRouterJellybean.RouteInfo.getVolume(routeObj);
376                    if (newVolume != record.mRouteDescriptor.getVolume()) {
377                        record.mRouteDescriptor =
378                                new MediaRouteDescriptor.Builder(record.mRouteDescriptor)
379                                .setVolume(newVolume)
380                                .build();
381                        publishRoutes();
382                    }
383                }
384            }
385        }
386
387        @Override
388        public void onRouteSelected(int type, Object routeObj) {
389            if (routeObj != MediaRouterJellybean.getSelectedRoute(mRouterObj,
390                    MediaRouterJellybean.ALL_ROUTE_TYPES)) {
391                // The currently selected route has already changed so this callback
392                // is stale.  Drop it to prevent getting into sync loops.
393                return;
394            }
395
396            UserRouteRecord userRouteRecord = getUserRouteRecord(routeObj);
397            if (userRouteRecord != null) {
398                userRouteRecord.mRoute.select();
399            } else {
400                // Select the route if it already exists in the compat media router.
401                // If not, we will select it instead when the route is added.
402                int index = findSystemRouteRecord(routeObj);
403                if (index >= 0) {
404                    SystemRouteRecord record = mSystemRouteRecords.get(index);
405                    MediaRouter.RouteInfo route = mSyncCallback.getSystemRouteByDescriptorId(
406                            record.mRouteDescriptorId);
407                    if (route != null) {
408                        route.select();
409                    }
410                }
411            }
412        }
413
414        @Override
415        public void onRouteUnselected(int type, Object routeObj) {
416            // Nothing to do when a route is unselected.
417            // We only need to handle when a route is selected.
418        }
419
420        @Override
421        public void onRouteGrouped(Object routeObj, Object groupObj, int index) {
422            // Route grouping is deprecated and no longer supported.
423        }
424
425        @Override
426        public void onRouteUngrouped(Object routeObj, Object groupObj) {
427            // Route grouping is deprecated and no longer supported.
428        }
429
430        @Override
431        public void onVolumeSetRequest(Object routeObj, int volume) {
432            UserRouteRecord record = getUserRouteRecord(routeObj);
433            if (record != null) {
434                record.mRoute.requestSetVolume(volume);
435            }
436        }
437
438        @Override
439        public void onVolumeUpdateRequest(Object routeObj, int direction) {
440            UserRouteRecord record = getUserRouteRecord(routeObj);
441            if (record != null) {
442                record.mRoute.requestUpdateVolume(direction);
443            }
444        }
445
446        @Override
447        public void onSyncRouteAdded(MediaRouter.RouteInfo route) {
448            if (route.getProviderInstance() != this) {
449                Object routeObj = MediaRouterJellybean.createUserRoute(
450                        mRouterObj, mUserRouteCategoryObj);
451                UserRouteRecord record = new UserRouteRecord(route, routeObj);
452                MediaRouterJellybean.RouteInfo.setTag(routeObj, record);
453                MediaRouterJellybean.UserRouteInfo.setVolumeCallback(routeObj, mVolumeCallbackObj);
454                updateUserRouteProperties(record);
455                mUserRouteRecords.add(record);
456                MediaRouterJellybean.addUserRoute(mRouterObj, routeObj);
457            } else {
458                // If the newly added route is the counterpart of the currently selected
459                // route in the framework media router then ensure it is selected in
460                // the compat media router.
461                Object routeObj = MediaRouterJellybean.getSelectedRoute(
462                        mRouterObj, MediaRouterJellybean.ALL_ROUTE_TYPES);
463                int index = findSystemRouteRecord(routeObj);
464                if (index >= 0) {
465                    SystemRouteRecord record = mSystemRouteRecords.get(index);
466                    if (record.mRouteDescriptorId.equals(route.getDescriptorId())) {
467                        route.select();
468                    }
469                }
470            }
471        }
472
473        @Override
474        public void onSyncRouteRemoved(MediaRouter.RouteInfo route) {
475            if (route.getProviderInstance() != this) {
476                int index = findUserRouteRecord(route);
477                if (index >= 0) {
478                    UserRouteRecord record = mUserRouteRecords.remove(index);
479                    MediaRouterJellybean.RouteInfo.setTag(record.mRouteObj, null);
480                    MediaRouterJellybean.UserRouteInfo.setVolumeCallback(record.mRouteObj, null);
481                    MediaRouterJellybean.removeUserRoute(mRouterObj, record.mRouteObj);
482                }
483            }
484        }
485
486        @Override
487        public void onSyncRouteChanged(MediaRouter.RouteInfo route) {
488            if (route.getProviderInstance() != this) {
489                int index = findUserRouteRecord(route);
490                if (index >= 0) {
491                    UserRouteRecord record = mUserRouteRecords.get(index);
492                    updateUserRouteProperties(record);
493                }
494            }
495        }
496
497        @Override
498        public void onSyncRouteSelected(MediaRouter.RouteInfo route) {
499            if (!route.isSelected()) {
500                // The currently selected route has already changed so this callback
501                // is stale.  Drop it to prevent getting into sync loops.
502                return;
503            }
504
505            if (route.getProviderInstance() != this) {
506                int index = findUserRouteRecord(route);
507                if (index >= 0) {
508                    UserRouteRecord record = mUserRouteRecords.get(index);
509                    selectRoute(record.mRouteObj);
510                }
511            } else {
512                int index = findSystemRouteRecordByDescriptorId(route.getDescriptorId());
513                if (index >= 0) {
514                    SystemRouteRecord record = mSystemRouteRecords.get(index);
515                    selectRoute(record.mRouteObj);
516                }
517            }
518        }
519
520        protected void publishRoutes() {
521            MediaRouteProviderDescriptor.Builder builder =
522                    new MediaRouteProviderDescriptor.Builder();
523            int count = mSystemRouteRecords.size();
524            for (int i = 0; i < count; i++) {
525                builder.addRoute(mSystemRouteRecords.get(i).mRouteDescriptor);
526            }
527
528            setDescriptor(builder.build());
529        }
530
531        protected int findSystemRouteRecord(Object routeObj) {
532            final int count = mSystemRouteRecords.size();
533            for (int i = 0; i < count; i++) {
534                if (mSystemRouteRecords.get(i).mRouteObj == routeObj) {
535                    return i;
536                }
537            }
538            return -1;
539        }
540
541        protected int findSystemRouteRecordByDescriptorId(String id) {
542            final int count = mSystemRouteRecords.size();
543            for (int i = 0; i < count; i++) {
544                if (mSystemRouteRecords.get(i).mRouteDescriptorId.equals(id)) {
545                    return i;
546                }
547            }
548            return -1;
549        }
550
551        protected int findUserRouteRecord(MediaRouter.RouteInfo route) {
552            final int count = mUserRouteRecords.size();
553            for (int i = 0; i < count; i++) {
554                if (mUserRouteRecords.get(i).mRoute == route) {
555                    return i;
556                }
557            }
558            return -1;
559        }
560
561        protected UserRouteRecord getUserRouteRecord(Object routeObj) {
562            Object tag = MediaRouterJellybean.RouteInfo.getTag(routeObj);
563            return tag instanceof UserRouteRecord ? (UserRouteRecord)tag : null;
564        }
565
566        protected void updateSystemRouteDescriptor(SystemRouteRecord record) {
567            // We must always recreate the route descriptor when making any changes
568            // because they are intended to be immutable once published.
569            MediaRouteDescriptor.Builder builder = new MediaRouteDescriptor.Builder(
570                    record.mRouteDescriptorId, getRouteName(record.mRouteObj));
571            onBuildSystemRouteDescriptor(record, builder);
572            record.mRouteDescriptor = builder.build();
573        }
574
575        protected String getRouteName(Object routeObj) {
576            // Routes should not have null names but it may happen for badly configured
577            // user routes.  We tolerate this by using an empty name string here but
578            // such unnamed routes will be discarded by the media router upstream
579            // (with a log message so we can track down the problem).
580            CharSequence name = MediaRouterJellybean.RouteInfo.getName(routeObj, getContext());
581            return name != null ? name.toString() : "";
582        }
583
584        protected void onBuildSystemRouteDescriptor(SystemRouteRecord record,
585                MediaRouteDescriptor.Builder builder) {
586            int supportedTypes = MediaRouterJellybean.RouteInfo.getSupportedTypes(
587                    record.mRouteObj);
588            if ((supportedTypes & MediaRouterJellybean.ROUTE_TYPE_LIVE_AUDIO) != 0) {
589                builder.addControlFilters(LIVE_AUDIO_CONTROL_FILTERS);
590            }
591            if ((supportedTypes & MediaRouterJellybean.ROUTE_TYPE_LIVE_VIDEO) != 0) {
592                builder.addControlFilters(LIVE_VIDEO_CONTROL_FILTERS);
593            }
594
595            builder.setPlaybackType(
596                    MediaRouterJellybean.RouteInfo.getPlaybackType(record.mRouteObj));
597            builder.setPlaybackStream(
598                    MediaRouterJellybean.RouteInfo.getPlaybackStream(record.mRouteObj));
599            builder.setVolume(
600                    MediaRouterJellybean.RouteInfo.getVolume(record.mRouteObj));
601            builder.setVolumeMax(
602                    MediaRouterJellybean.RouteInfo.getVolumeMax(record.mRouteObj));
603            builder.setVolumeHandling(
604                    MediaRouterJellybean.RouteInfo.getVolumeHandling(record.mRouteObj));
605        }
606
607        protected void updateUserRouteProperties(UserRouteRecord record) {
608            MediaRouterJellybean.UserRouteInfo.setName(
609                    record.mRouteObj, record.mRoute.getName());
610            MediaRouterJellybean.UserRouteInfo.setPlaybackType(
611                    record.mRouteObj, record.mRoute.getPlaybackType());
612            MediaRouterJellybean.UserRouteInfo.setPlaybackStream(
613                    record.mRouteObj, record.mRoute.getPlaybackStream());
614            MediaRouterJellybean.UserRouteInfo.setVolume(
615                    record.mRouteObj, record.mRoute.getVolume());
616            MediaRouterJellybean.UserRouteInfo.setVolumeMax(
617                    record.mRouteObj, record.mRoute.getVolumeMax());
618            MediaRouterJellybean.UserRouteInfo.setVolumeHandling(
619                    record.mRouteObj, record.mRoute.getVolumeHandling());
620        }
621
622        protected void updateCallback() {
623            if (mCallbackRegistered) {
624                mCallbackRegistered = false;
625                MediaRouterJellybean.removeCallback(mRouterObj, mCallbackObj);
626            }
627
628            if (mRouteTypes != 0) {
629                mCallbackRegistered = true;
630                MediaRouterJellybean.addCallback(mRouterObj, mRouteTypes, mCallbackObj);
631            }
632        }
633
634        protected Object createCallbackObj() {
635            return MediaRouterJellybean.createCallback(this);
636        }
637
638        protected Object createVolumeCallbackObj() {
639            return MediaRouterJellybean.createVolumeCallback(this);
640        }
641
642        protected void selectRoute(Object routeObj) {
643            if (mSelectRouteWorkaround == null) {
644                mSelectRouteWorkaround = new MediaRouterJellybean.SelectRouteWorkaround();
645            }
646            mSelectRouteWorkaround.selectRoute(mRouterObj,
647                    MediaRouterJellybean.ALL_ROUTE_TYPES, routeObj);
648        }
649
650        protected Object getDefaultRoute() {
651            if (mGetDefaultRouteWorkaround == null) {
652                mGetDefaultRouteWorkaround = new MediaRouterJellybean.GetDefaultRouteWorkaround();
653            }
654            return mGetDefaultRouteWorkaround.getDefaultRoute(mRouterObj);
655        }
656
657        /**
658         * Represents a route that is provided by the framework media router
659         * and published by this route provider to the support library media router.
660         */
661        protected static final class SystemRouteRecord {
662            public final Object mRouteObj;
663            public final String mRouteDescriptorId;
664            public MediaRouteDescriptor mRouteDescriptor; // assigned immediately after creation
665
666            public SystemRouteRecord(Object routeObj, String id) {
667                mRouteObj = routeObj;
668                mRouteDescriptorId = id;
669            }
670        }
671
672        /**
673         * Represents a route that is provided by the support library media router
674         * and published by this route provider to the framework media router.
675         */
676        protected static final class UserRouteRecord {
677            public final MediaRouter.RouteInfo mRoute;
678            public final Object mRouteObj;
679
680            public UserRouteRecord(MediaRouter.RouteInfo route, Object routeObj) {
681                mRoute = route;
682                mRouteObj = routeObj;
683            }
684        }
685
686        protected final class SystemRouteController extends RouteController {
687            private final Object mRouteObj;
688
689            public SystemRouteController(Object routeObj) {
690                mRouteObj = routeObj;
691            }
692
693            @Override
694            public void onSetVolume(int volume) {
695                MediaRouterJellybean.RouteInfo.requestSetVolume(mRouteObj, volume);
696            }
697
698            @Override
699            public void onUpdateVolume(int delta) {
700                MediaRouterJellybean.RouteInfo.requestUpdateVolume(mRouteObj, delta);
701            }
702        }
703    }
704
705    /**
706     * Jellybean MR1 implementation.
707     */
708    private static class JellybeanMr1Impl extends JellybeanImpl
709            implements MediaRouterJellybeanMr1.Callback {
710        private MediaRouterJellybeanMr1.ActiveScanWorkaround mActiveScanWorkaround;
711        private MediaRouterJellybeanMr1.IsConnectingWorkaround mIsConnectingWorkaround;
712
713        public JellybeanMr1Impl(Context context, SyncCallback syncCallback) {
714            super(context, syncCallback);
715        }
716
717        @Override
718        public void onRoutePresentationDisplayChanged(Object routeObj) {
719            int index = findSystemRouteRecord(routeObj);
720            if (index >= 0) {
721                SystemRouteRecord record = mSystemRouteRecords.get(index);
722                Display newPresentationDisplay =
723                        MediaRouterJellybeanMr1.RouteInfo.getPresentationDisplay(routeObj);
724                int newPresentationDisplayId = (newPresentationDisplay != null
725                        ? newPresentationDisplay.getDisplayId() : -1);
726                if (newPresentationDisplayId
727                        != record.mRouteDescriptor.getPresentationDisplayId()) {
728                    record.mRouteDescriptor =
729                            new MediaRouteDescriptor.Builder(record.mRouteDescriptor)
730                            .setPresentationDisplayId(newPresentationDisplayId)
731                            .build();
732                    publishRoutes();
733                }
734            }
735        }
736
737        @Override
738        protected void onBuildSystemRouteDescriptor(SystemRouteRecord record,
739                MediaRouteDescriptor.Builder builder) {
740            super.onBuildSystemRouteDescriptor(record, builder);
741
742            if (!MediaRouterJellybeanMr1.RouteInfo.isEnabled(record.mRouteObj)) {
743                builder.setEnabled(false);
744            }
745
746            if (isConnecting(record)) {
747                builder.setConnecting(true);
748            }
749
750            Display presentationDisplay =
751                    MediaRouterJellybeanMr1.RouteInfo.getPresentationDisplay(record.mRouteObj);
752            if (presentationDisplay != null) {
753                builder.setPresentationDisplayId(presentationDisplay.getDisplayId());
754            }
755        }
756
757        @Override
758        protected void updateCallback() {
759            super.updateCallback();
760
761            if (mActiveScanWorkaround == null) {
762                mActiveScanWorkaround = new MediaRouterJellybeanMr1.ActiveScanWorkaround(
763                        getContext(), getHandler());
764            }
765            mActiveScanWorkaround.setActiveScanRouteTypes(mActiveScan ? mRouteTypes : 0);
766        }
767
768        @Override
769        protected Object createCallbackObj() {
770            return MediaRouterJellybeanMr1.createCallback(this);
771        }
772
773        protected boolean isConnecting(SystemRouteRecord record) {
774            if (mIsConnectingWorkaround == null) {
775                mIsConnectingWorkaround = new MediaRouterJellybeanMr1.IsConnectingWorkaround();
776            }
777            return mIsConnectingWorkaround.isConnecting(record.mRouteObj);
778        }
779    }
780
781    /**
782     * Jellybean MR2 implementation.
783     */
784    private static class JellybeanMr2Impl extends JellybeanMr1Impl {
785        public JellybeanMr2Impl(Context context, SyncCallback syncCallback) {
786            super(context, syncCallback);
787        }
788
789        @Override
790        protected void onBuildSystemRouteDescriptor(SystemRouteRecord record,
791                MediaRouteDescriptor.Builder builder) {
792            super.onBuildSystemRouteDescriptor(record, builder);
793
794            CharSequence description =
795                    MediaRouterJellybeanMr2.RouteInfo.getDescription(record.mRouteObj);
796            if (description != null) {
797                builder.setDescription(description.toString());
798            }
799        }
800
801        @Override
802        protected void selectRoute(Object routeObj) {
803            MediaRouterJellybean.selectRoute(mRouterObj,
804                    MediaRouterJellybean.ALL_ROUTE_TYPES, routeObj);
805        }
806
807        @Override
808        protected Object getDefaultRoute() {
809            return MediaRouterJellybeanMr2.getDefaultRoute(mRouterObj);
810        }
811
812        @Override
813        protected void updateUserRouteProperties(UserRouteRecord record) {
814            super.updateUserRouteProperties(record);
815
816            MediaRouterJellybeanMr2.UserRouteInfo.setDescription(
817                    record.mRouteObj, record.mRoute.getDescription());
818        }
819
820        @Override
821        protected void updateCallback() {
822            if (mCallbackRegistered) {
823                MediaRouterJellybean.removeCallback(mRouterObj, mCallbackObj);
824            }
825
826            mCallbackRegistered = true;
827            MediaRouterJellybeanMr2.addCallback(mRouterObj, mRouteTypes, mCallbackObj,
828                    MediaRouter.CALLBACK_FLAG_UNFILTERED_EVENTS
829                    | (mActiveScan ? MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN : 0));
830        }
831
832        @Override
833        protected boolean isConnecting(SystemRouteRecord record) {
834            return MediaRouterJellybeanMr2.RouteInfo.isConnecting(record.mRouteObj);
835        }
836    }
837}
838