1 /* 2 * Copyright 2018 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 com.android.support.mediarouter.media; 18 19 import android.content.Context; 20 import android.graphics.drawable.Drawable; 21 import android.media.AudioManager; 22 import android.os.Build; 23 import android.util.Log; 24 25 import java.lang.reflect.InvocationTargetException; 26 import java.lang.reflect.Method; 27 import java.util.ArrayList; 28 import java.util.List; 29 30 // @@RequiresApi(16) 31 final class MediaRouterJellybean { 32 private static final String TAG = "MediaRouterJellybean"; 33 34 // android.media.AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP = 0x80; 35 // android.media.AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES = 0x100; 36 // android.media.AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER = 0x200; 37 public static final int DEVICE_OUT_BLUETOOTH = 0x80 | 0x100 | 0x200; 38 39 public static final int ROUTE_TYPE_LIVE_AUDIO = 0x1; 40 public static final int ROUTE_TYPE_LIVE_VIDEO = 0x2; 41 public static final int ROUTE_TYPE_USER = 0x00800000; 42 43 public static final int ALL_ROUTE_TYPES = 44 MediaRouterJellybean.ROUTE_TYPE_LIVE_AUDIO 45 | MediaRouterJellybean.ROUTE_TYPE_LIVE_VIDEO 46 | MediaRouterJellybean.ROUTE_TYPE_USER; 47 48 public static Object getMediaRouter(Context context) { 49 return context.getSystemService(Context.MEDIA_ROUTER_SERVICE); 50 } 51 52 @SuppressWarnings({ "rawtypes", "unchecked" }) 53 public static List getRoutes(Object routerObj) { 54 final android.media.MediaRouter router = (android.media.MediaRouter)routerObj; 55 final int count = router.getRouteCount(); 56 List out = new ArrayList(count); 57 for (int i = 0; i < count; i++) { 58 out.add(router.getRouteAt(i)); 59 } 60 return out; 61 } 62 63 @SuppressWarnings({ "rawtypes", "unchecked" }) 64 public static List getCategories(Object routerObj) { 65 final android.media.MediaRouter router = (android.media.MediaRouter)routerObj; 66 final int count = router.getCategoryCount(); 67 List out = new ArrayList(count); 68 for (int i = 0; i < count; i++) { 69 out.add(router.getCategoryAt(i)); 70 } 71 return out; 72 } 73 74 public static Object getSelectedRoute(Object routerObj, int type) { 75 return ((android.media.MediaRouter)routerObj).getSelectedRoute(type); 76 } 77 78 public static void selectRoute(Object routerObj, int types, Object routeObj) { 79 ((android.media.MediaRouter)routerObj).selectRoute(types, 80 (android.media.MediaRouter.RouteInfo)routeObj); 81 } 82 83 public static void addCallback(Object routerObj, int types, Object callbackObj) { 84 ((android.media.MediaRouter)routerObj).addCallback(types, 85 (android.media.MediaRouter.Callback)callbackObj); 86 } 87 88 public static void removeCallback(Object routerObj, Object callbackObj) { 89 ((android.media.MediaRouter)routerObj).removeCallback( 90 (android.media.MediaRouter.Callback)callbackObj); 91 } 92 93 public static Object createRouteCategory(Object routerObj, 94 String name, boolean isGroupable) { 95 return ((android.media.MediaRouter)routerObj).createRouteCategory(name, isGroupable); 96 } 97 98 public static Object createUserRoute(Object routerObj, Object categoryObj) { 99 return ((android.media.MediaRouter)routerObj).createUserRoute( 100 (android.media.MediaRouter.RouteCategory)categoryObj); 101 } 102 103 public static void addUserRoute(Object routerObj, Object routeObj) { 104 ((android.media.MediaRouter)routerObj).addUserRoute( 105 (android.media.MediaRouter.UserRouteInfo)routeObj); 106 } 107 108 public static void removeUserRoute(Object routerObj, Object routeObj) { 109 ((android.media.MediaRouter)routerObj).removeUserRoute( 110 (android.media.MediaRouter.UserRouteInfo)routeObj); 111 } 112 113 public static Object createCallback(Callback callback) { 114 return new CallbackProxy<Callback>(callback); 115 } 116 117 public static Object createVolumeCallback(VolumeCallback callback) { 118 return new VolumeCallbackProxy<VolumeCallback>(callback); 119 } 120 121 static boolean checkRoutedToBluetooth(Context context) { 122 try { 123 AudioManager audioManager = (AudioManager) context.getSystemService( 124 Context.AUDIO_SERVICE); 125 Method method = audioManager.getClass().getDeclaredMethod( 126 "getDevicesForStream", int.class); 127 int device = (Integer) method.invoke(audioManager, AudioManager.STREAM_MUSIC); 128 return (device & DEVICE_OUT_BLUETOOTH) != 0; 129 } catch (Exception e) { 130 return false; 131 } 132 } 133 134 public static final class RouteInfo { 135 public static CharSequence getName(Object routeObj, Context context) { 136 return ((android.media.MediaRouter.RouteInfo)routeObj).getName(context); 137 } 138 139 public static CharSequence getStatus(Object routeObj) { 140 return ((android.media.MediaRouter.RouteInfo)routeObj).getStatus(); 141 } 142 143 public static int getSupportedTypes(Object routeObj) { 144 return ((android.media.MediaRouter.RouteInfo)routeObj).getSupportedTypes(); 145 } 146 147 public static Object getCategory(Object routeObj) { 148 return ((android.media.MediaRouter.RouteInfo)routeObj).getCategory(); 149 } 150 151 public static Drawable getIconDrawable(Object routeObj) { 152 return ((android.media.MediaRouter.RouteInfo)routeObj).getIconDrawable(); 153 } 154 155 public static int getPlaybackType(Object routeObj) { 156 return ((android.media.MediaRouter.RouteInfo)routeObj).getPlaybackType(); 157 } 158 159 public static int getPlaybackStream(Object routeObj) { 160 return ((android.media.MediaRouter.RouteInfo)routeObj).getPlaybackStream(); 161 } 162 163 public static int getVolume(Object routeObj) { 164 return ((android.media.MediaRouter.RouteInfo)routeObj).getVolume(); 165 } 166 167 public static int getVolumeMax(Object routeObj) { 168 return ((android.media.MediaRouter.RouteInfo)routeObj).getVolumeMax(); 169 } 170 171 public static int getVolumeHandling(Object routeObj) { 172 return ((android.media.MediaRouter.RouteInfo)routeObj).getVolumeHandling(); 173 } 174 175 public static Object getTag(Object routeObj) { 176 return ((android.media.MediaRouter.RouteInfo)routeObj).getTag(); 177 } 178 179 public static void setTag(Object routeObj, Object tag) { 180 ((android.media.MediaRouter.RouteInfo)routeObj).setTag(tag); 181 } 182 183 public static void requestSetVolume(Object routeObj, int volume) { 184 ((android.media.MediaRouter.RouteInfo)routeObj).requestSetVolume(volume); 185 } 186 187 public static void requestUpdateVolume(Object routeObj, int direction) { 188 ((android.media.MediaRouter.RouteInfo)routeObj).requestUpdateVolume(direction); 189 } 190 191 public static Object getGroup(Object routeObj) { 192 return ((android.media.MediaRouter.RouteInfo)routeObj).getGroup(); 193 } 194 195 public static boolean isGroup(Object routeObj) { 196 return routeObj instanceof android.media.MediaRouter.RouteGroup; 197 } 198 } 199 200 public static final class RouteGroup { 201 @SuppressWarnings({ "rawtypes", "unchecked" }) 202 public static List getGroupedRoutes(Object groupObj) { 203 final android.media.MediaRouter.RouteGroup group = 204 (android.media.MediaRouter.RouteGroup)groupObj; 205 final int count = group.getRouteCount(); 206 List out = new ArrayList(count); 207 for (int i = 0; i < count; i++) { 208 out.add(group.getRouteAt(i)); 209 } 210 return out; 211 } 212 } 213 214 public static final class UserRouteInfo { 215 public static void setName(Object routeObj, CharSequence name) { 216 ((android.media.MediaRouter.UserRouteInfo)routeObj).setName(name); 217 } 218 219 public static void setStatus(Object routeObj, CharSequence status) { 220 ((android.media.MediaRouter.UserRouteInfo)routeObj).setStatus(status); 221 } 222 223 public static void setIconDrawable(Object routeObj, Drawable icon) { 224 ((android.media.MediaRouter.UserRouteInfo)routeObj).setIconDrawable(icon); 225 } 226 227 public static void setPlaybackType(Object routeObj, int type) { 228 ((android.media.MediaRouter.UserRouteInfo)routeObj).setPlaybackType(type); 229 } 230 231 public static void setPlaybackStream(Object routeObj, int stream) { 232 ((android.media.MediaRouter.UserRouteInfo)routeObj).setPlaybackStream(stream); 233 } 234 235 public static void setVolume(Object routeObj, int volume) { 236 ((android.media.MediaRouter.UserRouteInfo)routeObj).setVolume(volume); 237 } 238 239 public static void setVolumeMax(Object routeObj, int volumeMax) { 240 ((android.media.MediaRouter.UserRouteInfo)routeObj).setVolumeMax(volumeMax); 241 } 242 243 public static void setVolumeHandling(Object routeObj, int volumeHandling) { 244 ((android.media.MediaRouter.UserRouteInfo)routeObj).setVolumeHandling(volumeHandling); 245 } 246 247 public static void setVolumeCallback(Object routeObj, Object volumeCallbackObj) { 248 ((android.media.MediaRouter.UserRouteInfo)routeObj).setVolumeCallback( 249 (android.media.MediaRouter.VolumeCallback)volumeCallbackObj); 250 } 251 252 public static void setRemoteControlClient(Object routeObj, Object rccObj) { 253 ((android.media.MediaRouter.UserRouteInfo)routeObj).setRemoteControlClient( 254 (android.media.RemoteControlClient)rccObj); 255 } 256 } 257 258 public static final class RouteCategory { 259 public static CharSequence getName(Object categoryObj, Context context) { 260 return ((android.media.MediaRouter.RouteCategory)categoryObj).getName(context); 261 } 262 263 @SuppressWarnings({ "rawtypes", "unchecked" }) 264 public static List getRoutes(Object categoryObj) { 265 ArrayList out = new ArrayList(); 266 ((android.media.MediaRouter.RouteCategory)categoryObj).getRoutes(out); 267 return out; 268 } 269 270 public static int getSupportedTypes(Object categoryObj) { 271 return ((android.media.MediaRouter.RouteCategory)categoryObj).getSupportedTypes(); 272 } 273 274 public static boolean isGroupable(Object categoryObj) { 275 return ((android.media.MediaRouter.RouteCategory)categoryObj).isGroupable(); 276 } 277 } 278 279 public static interface Callback { 280 public void onRouteSelected(int type, Object routeObj); 281 public void onRouteUnselected(int type, Object routeObj); 282 public void onRouteAdded(Object routeObj); 283 public void onRouteRemoved(Object routeObj); 284 public void onRouteChanged(Object routeObj); 285 public void onRouteGrouped(Object routeObj, Object groupObj, int index); 286 public void onRouteUngrouped(Object routeObj, Object groupObj); 287 public void onRouteVolumeChanged(Object routeObj); 288 } 289 290 public static interface VolumeCallback { 291 public void onVolumeSetRequest(Object routeObj, int volume); 292 public void onVolumeUpdateRequest(Object routeObj, int direction); 293 } 294 295 /** 296 * Workaround for limitations of selectRoute() on JB and JB MR1. 297 * Do not use on JB MR2 and above. 298 */ 299 public static final class SelectRouteWorkaround { 300 private Method mSelectRouteIntMethod; 301 302 public SelectRouteWorkaround() { 303 if (Build.VERSION.SDK_INT < 16 || Build.VERSION.SDK_INT > 17) { 304 throw new UnsupportedOperationException(); 305 } 306 try { 307 mSelectRouteIntMethod = android.media.MediaRouter.class.getMethod( 308 "selectRouteInt", int.class, android.media.MediaRouter.RouteInfo.class); 309 } catch (NoSuchMethodException ex) { 310 } 311 } 312 313 public void selectRoute(Object routerObj, int types, Object routeObj) { 314 android.media.MediaRouter router = (android.media.MediaRouter)routerObj; 315 android.media.MediaRouter.RouteInfo route = 316 (android.media.MediaRouter.RouteInfo)routeObj; 317 318 int routeTypes = route.getSupportedTypes(); 319 if ((routeTypes & ROUTE_TYPE_USER) == 0) { 320 // Handle non-user routes. 321 // On JB and JB MR1, the selectRoute() API only supports programmatically 322 // selecting user routes. So instead we rely on the hidden selectRouteInt() 323 // method on these versions of the platform. 324 // This limitation was removed in JB MR2. 325 if (mSelectRouteIntMethod != null) { 326 try { 327 mSelectRouteIntMethod.invoke(router, types, route); 328 return; // success! 329 } catch (IllegalAccessException ex) { 330 Log.w(TAG, "Cannot programmatically select non-user route. " 331 + "Media routing may not work.", ex); 332 } catch (InvocationTargetException ex) { 333 Log.w(TAG, "Cannot programmatically select non-user route. " 334 + "Media routing may not work.", ex); 335 } 336 } else { 337 Log.w(TAG, "Cannot programmatically select non-user route " 338 + "because the platform is missing the selectRouteInt() " 339 + "method. Media routing may not work."); 340 } 341 } 342 343 // Default handling. 344 router.selectRoute(types, route); 345 } 346 } 347 348 /** 349 * Workaround the fact that the getDefaultRoute() method does not exist in JB and JB MR1. 350 * Do not use on JB MR2 and above. 351 */ 352 public static final class GetDefaultRouteWorkaround { 353 private Method mGetSystemAudioRouteMethod; 354 355 public GetDefaultRouteWorkaround() { 356 if (Build.VERSION.SDK_INT < 16 || Build.VERSION.SDK_INT > 17) { 357 throw new UnsupportedOperationException(); 358 } 359 try { 360 mGetSystemAudioRouteMethod = 361 android.media.MediaRouter.class.getMethod("getSystemAudioRoute"); 362 } catch (NoSuchMethodException ex) { 363 } 364 } 365 366 public Object getDefaultRoute(Object routerObj) { 367 android.media.MediaRouter router = (android.media.MediaRouter)routerObj; 368 369 if (mGetSystemAudioRouteMethod != null) { 370 try { 371 return mGetSystemAudioRouteMethod.invoke(router); 372 } catch (IllegalAccessException ex) { 373 } catch (InvocationTargetException ex) { 374 } 375 } 376 377 // Could not find the method or it does not work. 378 // Return the first route and hope for the best. 379 return router.getRouteAt(0); 380 } 381 } 382 383 static class CallbackProxy<T extends Callback> 384 extends android.media.MediaRouter.Callback { 385 protected final T mCallback; 386 387 public CallbackProxy(T callback) { 388 mCallback = callback; 389 } 390 391 @Override 392 public void onRouteSelected(android.media.MediaRouter router, 393 int type, android.media.MediaRouter.RouteInfo route) { 394 mCallback.onRouteSelected(type, route); 395 } 396 397 @Override 398 public void onRouteUnselected(android.media.MediaRouter router, 399 int type, android.media.MediaRouter.RouteInfo route) { 400 mCallback.onRouteUnselected(type, route); 401 } 402 403 @Override 404 public void onRouteAdded(android.media.MediaRouter router, 405 android.media.MediaRouter.RouteInfo route) { 406 mCallback.onRouteAdded(route); 407 } 408 409 @Override 410 public void onRouteRemoved(android.media.MediaRouter router, 411 android.media.MediaRouter.RouteInfo route) { 412 mCallback.onRouteRemoved(route); 413 } 414 415 @Override 416 public void onRouteChanged(android.media.MediaRouter router, 417 android.media.MediaRouter.RouteInfo route) { 418 mCallback.onRouteChanged(route); 419 } 420 421 @Override 422 public void onRouteGrouped(android.media.MediaRouter router, 423 android.media.MediaRouter.RouteInfo route, 424 android.media.MediaRouter.RouteGroup group, int index) { 425 mCallback.onRouteGrouped(route, group, index); 426 } 427 428 @Override 429 public void onRouteUngrouped(android.media.MediaRouter router, 430 android.media.MediaRouter.RouteInfo route, 431 android.media.MediaRouter.RouteGroup group) { 432 mCallback.onRouteUngrouped(route, group); 433 } 434 435 @Override 436 public void onRouteVolumeChanged(android.media.MediaRouter router, 437 android.media.MediaRouter.RouteInfo route) { 438 mCallback.onRouteVolumeChanged(route); 439 } 440 } 441 442 static class VolumeCallbackProxy<T extends VolumeCallback> 443 extends android.media.MediaRouter.VolumeCallback { 444 protected final T mCallback; 445 446 public VolumeCallbackProxy(T callback) { 447 mCallback = callback; 448 } 449 450 @Override 451 public void onVolumeSetRequest(android.media.MediaRouter.RouteInfo route, 452 int volume) { 453 mCallback.onVolumeSetRequest(route, volume); 454 } 455 456 @Override 457 public void onVolumeUpdateRequest(android.media.MediaRouter.RouteInfo route, 458 int direction) { 459 mCallback.onVolumeUpdateRequest(route, direction); 460 } 461 } 462 } 463