[go: nahoru, domu]

blob: 4bd5f3656c7c133ed7f01fc011480b8ff59822cc [file] [log] [blame]
Aurimas Liutikasac5fe7c2018-03-06 14:40:53 -08001/*
2 * Copyright (C) 2013 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package androidx.mediarouter.media;
18
19import android.content.BroadcastReceiver;
20import android.content.ComponentName;
21import android.content.Context;
22import android.content.Intent;
23import android.content.IntentFilter;
24import android.content.res.Resources;
25import android.media.AudioManager;
26import android.os.Build;
27import androidx.annotation.RequiresApi;
28import androidx.mediarouter.R;
29import android.view.Display;
30
31import java.util.ArrayList;
32import java.util.List;
33import 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 */
41abstract 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
47 protected SystemMediaRouteProvider(Context context) {
48 super(context, new ProviderMetadata(new ComponentName(PACKAGE_NAME,
49 SystemMediaRouteProvider.class.getName())));
50 }
51
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 */
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 */
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 */
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 */
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 {
100 void onSystemRouteSelectedByDescriptorId(String id);
101 }
102
103 protected Object getDefaultRoute() {
104 return null;
105 }
106
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
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
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
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
172 public void onSetVolume(int volume) {
173 mAudioManager.setStreamVolume(PLAYBACK_STREAM, volume, 0);
174 publishRoutes();
175 }
176
177 @Override
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
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
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
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
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
314 public void onRouteAdded(Object routeObj) {
315 if (addSystemRouteNoPublish(routeObj)) {
316 publishRoutes();
317 }
318 }
319
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
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
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
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
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
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
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 mSyncCallback.onSystemRouteSelectedByDescriptorId(record.mRouteDescriptorId);
421 }
422 }
423 }
424
425 @Override
426 public void onRouteUnselected(int type, Object routeObj) {
427 // Nothing to do when a route is unselected.
428 // We only need to handle when a route is selected.
429 }
430
431 @Override
432 public void onRouteGrouped(Object routeObj, Object groupObj, int index) {
433 // Route grouping is deprecated and no longer supported.
434 }
435
436 @Override
437 public void onRouteUngrouped(Object routeObj, Object groupObj) {
438 // Route grouping is deprecated and no longer supported.
439 }
440
441 @Override
442 public void onVolumeSetRequest(Object routeObj, int volume) {
443 UserRouteRecord record = getUserRouteRecord(routeObj);
444 if (record != null) {
445 record.mRoute.requestSetVolume(volume);
446 }
447 }
448
449 @Override
450 public void onVolumeUpdateRequest(Object routeObj, int direction) {
451 UserRouteRecord record = getUserRouteRecord(routeObj);
452 if (record != null) {
453 record.mRoute.requestUpdateVolume(direction);
454 }
455 }
456
457 @Override
458 public void onSyncRouteAdded(MediaRouter.RouteInfo route) {
459 if (route.getProviderInstance() != this) {
460 Object routeObj = MediaRouterJellybean.createUserRoute(
461 mRouterObj, mUserRouteCategoryObj);
462 UserRouteRecord record = new UserRouteRecord(route, routeObj);
463 MediaRouterJellybean.RouteInfo.setTag(routeObj, record);
464 MediaRouterJellybean.UserRouteInfo.setVolumeCallback(routeObj, mVolumeCallbackObj);
465 updateUserRouteProperties(record);
466 mUserRouteRecords.add(record);
467 MediaRouterJellybean.addUserRoute(mRouterObj, routeObj);
468 } else {
469 // If the newly added route is the counterpart of the currently selected
470 // route in the framework media router then ensure it is selected in
471 // the compat media router.
472 Object routeObj = MediaRouterJellybean.getSelectedRoute(
473 mRouterObj, MediaRouterJellybean.ALL_ROUTE_TYPES);
474 int index = findSystemRouteRecord(routeObj);
475 if (index >= 0) {
476 SystemRouteRecord record = mSystemRouteRecords.get(index);
477 if (record.mRouteDescriptorId.equals(route.getDescriptorId())) {
478 route.select();
479 }
480 }
481 }
482 }
483
484 @Override
485 public void onSyncRouteRemoved(MediaRouter.RouteInfo route) {
486 if (route.getProviderInstance() != this) {
487 int index = findUserRouteRecord(route);
488 if (index >= 0) {
489 UserRouteRecord record = mUserRouteRecords.remove(index);
490 MediaRouterJellybean.RouteInfo.setTag(record.mRouteObj, null);
491 MediaRouterJellybean.UserRouteInfo.setVolumeCallback(record.mRouteObj, null);
492 MediaRouterJellybean.removeUserRoute(mRouterObj, record.mRouteObj);
493 }
494 }
495 }
496
497 @Override
498 public void onSyncRouteChanged(MediaRouter.RouteInfo route) {
499 if (route.getProviderInstance() != this) {
500 int index = findUserRouteRecord(route);
501 if (index >= 0) {
502 UserRouteRecord record = mUserRouteRecords.get(index);
503 updateUserRouteProperties(record);
504 }
505 }
506 }
507
508 @Override
509 public void onSyncRouteSelected(MediaRouter.RouteInfo route) {
510 if (!route.isSelected()) {
511 // The currently selected route has already changed so this callback
512 // is stale. Drop it to prevent getting into sync loops.
513 return;
514 }
515
516 if (route.getProviderInstance() != this) {
517 int index = findUserRouteRecord(route);
518 if (index >= 0) {
519 UserRouteRecord record = mUserRouteRecords.get(index);
520 selectRoute(record.mRouteObj);
521 }
522 } else {
523 int index = findSystemRouteRecordByDescriptorId(route.getDescriptorId());
524 if (index >= 0) {
525 SystemRouteRecord record = mSystemRouteRecords.get(index);
526 selectRoute(record.mRouteObj);
527 }
528 }
529 }
530
531 protected void publishRoutes() {
532 MediaRouteProviderDescriptor.Builder builder =
533 new MediaRouteProviderDescriptor.Builder();
534 int count = mSystemRouteRecords.size();
535 for (int i = 0; i < count; i++) {
536 builder.addRoute(mSystemRouteRecords.get(i).mRouteDescriptor);
537 }
538
539 setDescriptor(builder.build());
540 }
541
542 protected int findSystemRouteRecord(Object routeObj) {
543 final int count = mSystemRouteRecords.size();
544 for (int i = 0; i < count; i++) {
545 if (mSystemRouteRecords.get(i).mRouteObj == routeObj) {
546 return i;
547 }
548 }
549 return -1;
550 }
551
552 protected int findSystemRouteRecordByDescriptorId(String id) {
553 final int count = mSystemRouteRecords.size();
554 for (int i = 0; i < count; i++) {
555 if (mSystemRouteRecords.get(i).mRouteDescriptorId.equals(id)) {
556 return i;
557 }
558 }
559 return -1;
560 }
561
562 protected int findUserRouteRecord(MediaRouter.RouteInfo route) {
563 final int count = mUserRouteRecords.size();
564 for (int i = 0; i < count; i++) {
565 if (mUserRouteRecords.get(i).mRoute == route) {
566 return i;
567 }
568 }
569 return -1;
570 }
571
572 protected UserRouteRecord getUserRouteRecord(Object routeObj) {
573 Object tag = MediaRouterJellybean.RouteInfo.getTag(routeObj);
574 return tag instanceof UserRouteRecord ? (UserRouteRecord)tag : null;
575 }
576
577 protected void updateSystemRouteDescriptor(SystemRouteRecord record) {
578 // We must always recreate the route descriptor when making any changes
579 // because they are intended to be immutable once published.
580 MediaRouteDescriptor.Builder builder = new MediaRouteDescriptor.Builder(
581 record.mRouteDescriptorId, getRouteName(record.mRouteObj));
582 onBuildSystemRouteDescriptor(record, builder);
583 record.mRouteDescriptor = builder.build();
584 }
585
586 protected String getRouteName(Object routeObj) {
587 // Routes should not have null names but it may happen for badly configured
588 // user routes. We tolerate this by using an empty name string here but
589 // such unnamed routes will be discarded by the media router upstream
590 // (with a log message so we can track down the problem).
591 CharSequence name = MediaRouterJellybean.RouteInfo.getName(routeObj, getContext());
592 return name != null ? name.toString() : "";
593 }
594
595 protected void onBuildSystemRouteDescriptor(SystemRouteRecord record,
596 MediaRouteDescriptor.Builder builder) {
597 int supportedTypes = MediaRouterJellybean.RouteInfo.getSupportedTypes(
598 record.mRouteObj);
599 if ((supportedTypes & MediaRouterJellybean.ROUTE_TYPE_LIVE_AUDIO) != 0) {
600 builder.addControlFilters(LIVE_AUDIO_CONTROL_FILTERS);
601 }
602 if ((supportedTypes & MediaRouterJellybean.ROUTE_TYPE_LIVE_VIDEO) != 0) {
603 builder.addControlFilters(LIVE_VIDEO_CONTROL_FILTERS);
604 }
605
606 builder.setPlaybackType(
607 MediaRouterJellybean.RouteInfo.getPlaybackType(record.mRouteObj));
608 builder.setPlaybackStream(
609 MediaRouterJellybean.RouteInfo.getPlaybackStream(record.mRouteObj));
610 builder.setVolume(
611 MediaRouterJellybean.RouteInfo.getVolume(record.mRouteObj));
612 builder.setVolumeMax(
613 MediaRouterJellybean.RouteInfo.getVolumeMax(record.mRouteObj));
614 builder.setVolumeHandling(
615 MediaRouterJellybean.RouteInfo.getVolumeHandling(record.mRouteObj));
616 }
617
618 protected void updateUserRouteProperties(UserRouteRecord record) {
619 MediaRouterJellybean.UserRouteInfo.setName(
620 record.mRouteObj, record.mRoute.getName());
621 MediaRouterJellybean.UserRouteInfo.setPlaybackType(
622 record.mRouteObj, record.mRoute.getPlaybackType());
623 MediaRouterJellybean.UserRouteInfo.setPlaybackStream(
624 record.mRouteObj, record.mRoute.getPlaybackStream());
625 MediaRouterJellybean.UserRouteInfo.setVolume(
626 record.mRouteObj, record.mRoute.getVolume());
627 MediaRouterJellybean.UserRouteInfo.setVolumeMax(
628 record.mRouteObj, record.mRoute.getVolumeMax());
629 MediaRouterJellybean.UserRouteInfo.setVolumeHandling(
630 record.mRouteObj, record.mRoute.getVolumeHandling());
631 }
632
633 protected void updateCallback() {
634 if (mCallbackRegistered) {
635 mCallbackRegistered = false;
636 MediaRouterJellybean.removeCallback(mRouterObj, mCallbackObj);
637 }
638
639 if (mRouteTypes != 0) {
640 mCallbackRegistered = true;
641 MediaRouterJellybean.addCallback(mRouterObj, mRouteTypes, mCallbackObj);
642 }
643 }
644
645 protected Object createCallbackObj() {
646 return MediaRouterJellybean.createCallback(this);
647 }
648
649 protected Object createVolumeCallbackObj() {
650 return MediaRouterJellybean.createVolumeCallback(this);
651 }
652
653 protected void selectRoute(Object routeObj) {
654 if (mSelectRouteWorkaround == null) {
655 mSelectRouteWorkaround = new MediaRouterJellybean.SelectRouteWorkaround();
656 }
657 mSelectRouteWorkaround.selectRoute(mRouterObj,
658 MediaRouterJellybean.ALL_ROUTE_TYPES, routeObj);
659 }
660
661 @Override
662 protected Object getDefaultRoute() {
663 if (mGetDefaultRouteWorkaround == null) {
664 mGetDefaultRouteWorkaround = new MediaRouterJellybean.GetDefaultRouteWorkaround();
665 }
666 return mGetDefaultRouteWorkaround.getDefaultRoute(mRouterObj);
667 }
668
669 @Override
670 protected Object getSystemRoute(MediaRouter.RouteInfo route) {
671 if (route == null) {
672 return null;
673 }
674 int index = findSystemRouteRecordByDescriptorId(route.getDescriptorId());
675 if (index >= 0) {
676 return mSystemRouteRecords.get(index).mRouteObj;
677 }
678 return null;
679 }
680
681 /**
682 * Represents a route that is provided by the framework media router
683 * and published by this route provider to the support library media router.
684 */
685 protected static final class SystemRouteRecord {
686 public final Object mRouteObj;
687 public final String mRouteDescriptorId;
688 public MediaRouteDescriptor mRouteDescriptor; // assigned immediately after creation
689
690 public SystemRouteRecord(Object routeObj, String id) {
691 mRouteObj = routeObj;
692 mRouteDescriptorId = id;
693 }
694 }
695
696 /**
697 * Represents a route that is provided by the support library media router
698 * and published by this route provider to the framework media router.
699 */
700 protected static final class UserRouteRecord {
701 public final MediaRouter.RouteInfo mRoute;
702 public final Object mRouteObj;
703
704 public UserRouteRecord(MediaRouter.RouteInfo route, Object routeObj) {
705 mRoute = route;
706 mRouteObj = routeObj;
707 }
708 }
709
710 protected static final class SystemRouteController extends RouteController {
711 private final Object mRouteObj;
712
713 public SystemRouteController(Object routeObj) {
714 mRouteObj = routeObj;
715 }
716
717 @Override
718 public void onSetVolume(int volume) {
719 MediaRouterJellybean.RouteInfo.requestSetVolume(mRouteObj, volume);
720 }
721
722 @Override
723 public void onUpdateVolume(int delta) {
724 MediaRouterJellybean.RouteInfo.requestUpdateVolume(mRouteObj, delta);
725 }
726 }
727 }
728
729 /**
730 * Jellybean MR1 implementation.
731 */
732 @RequiresApi(17)
733 private static class JellybeanMr1Impl extends JellybeanImpl
734 implements MediaRouterJellybeanMr1.Callback {
735 private MediaRouterJellybeanMr1.ActiveScanWorkaround mActiveScanWorkaround;
736 private MediaRouterJellybeanMr1.IsConnectingWorkaround mIsConnectingWorkaround;
737
738 public JellybeanMr1Impl(Context context, SyncCallback syncCallback) {
739 super(context, syncCallback);
740 }
741
742 @Override
743 public void onRoutePresentationDisplayChanged(Object routeObj) {
744 int index = findSystemRouteRecord(routeObj);
745 if (index >= 0) {
746 SystemRouteRecord record = mSystemRouteRecords.get(index);
747 Display newPresentationDisplay =
748 MediaRouterJellybeanMr1.RouteInfo.getPresentationDisplay(routeObj);
749 int newPresentationDisplayId = (newPresentationDisplay != null
750 ? newPresentationDisplay.getDisplayId() : -1);
751 if (newPresentationDisplayId
752 != record.mRouteDescriptor.getPresentationDisplayId()) {
753 record.mRouteDescriptor =
754 new MediaRouteDescriptor.Builder(record.mRouteDescriptor)
755 .setPresentationDisplayId(newPresentationDisplayId)
756 .build();
757 publishRoutes();
758 }
759 }
760 }
761
762 @Override
763 protected void onBuildSystemRouteDescriptor(SystemRouteRecord record,
764 MediaRouteDescriptor.Builder builder) {
765 super.onBuildSystemRouteDescriptor(record, builder);
766
767 if (!MediaRouterJellybeanMr1.RouteInfo.isEnabled(record.mRouteObj)) {
768 builder.setEnabled(false);
769 }
770
771 if (isConnecting(record)) {
772 builder.setConnecting(true);
773 }
774
775 Display presentationDisplay =
776 MediaRouterJellybeanMr1.RouteInfo.getPresentationDisplay(record.mRouteObj);
777 if (presentationDisplay != null) {
778 builder.setPresentationDisplayId(presentationDisplay.getDisplayId());
779 }
780 }
781
782 @Override
783 protected void updateCallback() {
784 super.updateCallback();
785
786 if (mActiveScanWorkaround == null) {
787 mActiveScanWorkaround = new MediaRouterJellybeanMr1.ActiveScanWorkaround(
788 getContext(), getHandler());
789 }
790 mActiveScanWorkaround.setActiveScanRouteTypes(mActiveScan ? mRouteTypes : 0);
791 }
792
793 @Override
794 protected Object createCallbackObj() {
795 return MediaRouterJellybeanMr1.createCallback(this);
796 }
797
798 protected boolean isConnecting(SystemRouteRecord record) {
799 if (mIsConnectingWorkaround == null) {
800 mIsConnectingWorkaround = new MediaRouterJellybeanMr1.IsConnectingWorkaround();
801 }
802 return mIsConnectingWorkaround.isConnecting(record.mRouteObj);
803 }
804 }
805
806 /**
807 * Jellybean MR2 implementation.
808 */
809 @RequiresApi(18)
810 private static class JellybeanMr2Impl extends JellybeanMr1Impl {
811 public JellybeanMr2Impl(Context context, SyncCallback syncCallback) {
812 super(context, syncCallback);
813 }
814
815 @Override
816 protected void onBuildSystemRouteDescriptor(SystemRouteRecord record,
817 MediaRouteDescriptor.Builder builder) {
818 super.onBuildSystemRouteDescriptor(record, builder);
819
820 CharSequence description =
821 MediaRouterJellybeanMr2.RouteInfo.getDescription(record.mRouteObj);
822 if (description != null) {
823 builder.setDescription(description.toString());
824 }
825 }
826
827 @Override
828 protected void selectRoute(Object routeObj) {
829 MediaRouterJellybean.selectRoute(mRouterObj,
830 MediaRouterJellybean.ALL_ROUTE_TYPES, routeObj);
831 }
832
833 @Override
834 protected Object getDefaultRoute() {
835 return MediaRouterJellybeanMr2.getDefaultRoute(mRouterObj);
836 }
837
838 @Override
839 protected void updateUserRouteProperties(UserRouteRecord record) {
840 super.updateUserRouteProperties(record);
841
842 MediaRouterJellybeanMr2.UserRouteInfo.setDescription(
843 record.mRouteObj, record.mRoute.getDescription());
844 }
845
846 @Override
847 protected void updateCallback() {
848 if (mCallbackRegistered) {
849 MediaRouterJellybean.removeCallback(mRouterObj, mCallbackObj);
850 }
851
852 mCallbackRegistered = true;
853 MediaRouterJellybeanMr2.addCallback(mRouterObj, mRouteTypes, mCallbackObj,
854 MediaRouter.CALLBACK_FLAG_UNFILTERED_EVENTS
855 | (mActiveScan ? MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN : 0));
856 }
857
858 @Override
859 protected boolean isConnecting(SystemRouteRecord record) {
860 return MediaRouterJellybeanMr2.RouteInfo.isConnecting(record.mRouteObj);
861 }
862 }
863
864 /**
865 * Api24 implementation.
866 */
867 @RequiresApi(24)
868 private static class Api24Impl extends JellybeanMr2Impl {
869 public Api24Impl(Context context, SyncCallback syncCallback) {
870 super(context, syncCallback);
871 }
872
873 @Override
874 protected void onBuildSystemRouteDescriptor(SystemRouteRecord record,
875 MediaRouteDescriptor.Builder builder) {
876 super.onBuildSystemRouteDescriptor(record, builder);
877
878 builder.setDeviceType(MediaRouterApi24.RouteInfo.getDeviceType(record.mRouteObj));
879 }
880 }
881}