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