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