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