1 package org.robolectric.shadows; 2 3 import static android.os.Build.VERSION_CODES.JELLY_BEAN; 4 import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1; 5 6 import android.media.AudioRoutesInfo; 7 import android.media.MediaRouter; 8 import android.media.MediaRouter.RouteInfo; 9 import android.os.Parcel; 10 import android.text.TextUtils; 11 import javax.annotation.Nullable; 12 import org.robolectric.RuntimeEnvironment; 13 import org.robolectric.annotation.Implements; 14 import org.robolectric.annotation.RealObject; 15 import org.robolectric.annotation.Resetter; 16 import org.robolectric.util.ReflectionHelpers; 17 import org.robolectric.util.ReflectionHelpers.ClassParameter; 18 19 /** Shadow class for {@link android.media.MediaRouter}. */ 20 @Implements(MediaRouter.class) 21 public class ShadowMediaRouter { 22 public static final String BLUETOOTH_DEVICE_NAME = "TestBluetoothDevice"; 23 24 private @RealObject MediaRouter realObject; 25 26 /** 27 * Adds the Bluetooth A2DP route and ensures it's the selected route, simulating connecting a 28 * Bluetooth device. 29 */ 30 public void addBluetoothRoute() { 31 updateBluetoothAudioRoute(BLUETOOTH_DEVICE_NAME); 32 33 if (RuntimeEnvironment.getApiLevel() <= JELLY_BEAN_MR1) { 34 ReflectionHelpers.callInstanceMethod( 35 MediaRouter.class, 36 realObject, 37 "selectRouteInt", 38 ClassParameter.from(int.class, MediaRouter.ROUTE_TYPE_LIVE_AUDIO), 39 ClassParameter.from(RouteInfo.class, getBluetoothA2dpRoute())); 40 } else { 41 realObject.selectRoute(MediaRouter.ROUTE_TYPE_LIVE_AUDIO, getBluetoothA2dpRoute()); 42 } 43 } 44 45 /** Removes the Bluetooth A2DP route, simulating disconnecting the Bluetooth device. */ 46 public void removeBluetoothRoute() { 47 // Android's AudioService passes a null Bluetooth device name to MediaRouter to signal that the 48 // A2DP route should be removed. 49 updateBluetoothAudioRoute(null); 50 } 51 52 /** Returns whether the Bluetooth A2DP route is the currently selected route. */ 53 public boolean isBluetoothRouteSelected(int type) { 54 return realObject.getSelectedRoute(type).equals(getBluetoothA2dpRoute()); 55 } 56 57 private static RouteInfo getBluetoothA2dpRoute() { 58 return ReflectionHelpers.getField( 59 ReflectionHelpers.getStaticField(MediaRouter.class, "sStatic"), "mBluetoothA2dpRoute"); 60 } 61 62 /** 63 * Updates the MediaRouter's Bluetooth audio route. 64 * 65 * @param bluetoothDeviceName the name of the Bluetooth device or null to indicate that the 66 * already-existing Bluetooth A2DP device should be removed 67 */ 68 private void updateBluetoothAudioRoute(@Nullable String bluetoothDeviceName) { 69 callUpdateAudioRoutes(newAudioRouteInfo(bluetoothDeviceName)); 70 } 71 72 /** 73 * Creates a new {@link AudioRoutesInfo} to be used for updating the Bluetooth audio route. 74 * 75 * @param bluetoothDeviceName the name of the Bluetooth device or null to indicate that the 76 * already-existing Bluetooth A2DP device should be removed 77 */ 78 private static AudioRoutesInfo newAudioRouteInfo(@Nullable String bluetoothDeviceName) { 79 Parcel p = Parcel.obtain(); 80 TextUtils.writeToParcel(bluetoothDeviceName, p, /* parcelableFlags= */ 0); 81 p.setDataPosition(0); 82 return AudioRoutesInfo.CREATOR.createFromParcel(p); 83 } 84 85 private void callUpdateAudioRoutes(AudioRoutesInfo routesInfo) { 86 ReflectionHelpers.callInstanceMethod( 87 ReflectionHelpers.getStaticField(MediaRouter.class, "sStatic"), 88 RuntimeEnvironment.getApiLevel() <= JELLY_BEAN 89 ? "updateRoutes" 90 : "updateAudioRoutes", 91 ClassParameter.from(AudioRoutesInfo.class, routesInfo)); 92 } 93 94 @Resetter 95 public static void reset() { 96 ReflectionHelpers.setStaticField(MediaRouter.class, "sStatic", null); 97 } 98 } 99