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