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