1 package org.robolectric.shadows; 2 3 import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2; 4 import static android.os.Build.VERSION_CODES.LOLLIPOP; 5 6 import android.bluetooth.BluetoothAdapter; 7 import android.bluetooth.BluetoothAdapter.LeScanCallback; 8 import android.bluetooth.BluetoothDevice; 9 import android.bluetooth.BluetoothProfile; 10 import android.bluetooth.BluetoothServerSocket; 11 import android.bluetooth.BluetoothSocket; 12 import android.os.ParcelUuid; 13 import java.util.Collections; 14 import java.util.HashMap; 15 import java.util.HashSet; 16 import java.util.Map; 17 import java.util.Set; 18 import java.util.UUID; 19 import org.robolectric.annotation.Implementation; 20 import org.robolectric.annotation.Implements; 21 22 @SuppressWarnings({"UnusedDeclaration"}) 23 @Implements(BluetoothAdapter.class) 24 public class ShadowBluetoothAdapter { 25 private static final int ADDRESS_LENGTH = 17; 26 27 private Set<BluetoothDevice> bondedDevices = new HashSet<BluetoothDevice>(); 28 private Set<LeScanCallback> leScanCallbacks = new HashSet<LeScanCallback>(); 29 private boolean isDiscovering; 30 private String address; 31 private boolean enabled; 32 private int state; 33 private String name = "DefaultBluetoothDeviceName"; 34 private int scanMode = BluetoothAdapter.SCAN_MODE_NONE; 35 private boolean isMultipleAdvertisementSupported = true; 36 private Map<Integer, Integer> profileConnectionStateData = new HashMap<>(); 37 38 @Implementation getDefaultAdapter()39 protected static BluetoothAdapter getDefaultAdapter() { 40 return (BluetoothAdapter) ShadowApplication.getInstance().getBluetoothAdapter(); 41 } 42 43 @Implementation getBondedDevices()44 protected Set<BluetoothDevice> getBondedDevices() { 45 return Collections.unmodifiableSet(bondedDevices); 46 } 47 setBondedDevices(Set<BluetoothDevice> bluetoothDevices)48 public void setBondedDevices(Set<BluetoothDevice> bluetoothDevices) { 49 bondedDevices = bluetoothDevices; 50 } 51 52 @Implementation listenUsingInsecureRfcommWithServiceRecord( String serviceName, UUID uuid)53 protected BluetoothServerSocket listenUsingInsecureRfcommWithServiceRecord( 54 String serviceName, UUID uuid) { 55 return ShadowBluetoothServerSocket.newInstance( 56 BluetoothSocket.TYPE_RFCOMM, /*auth=*/ false, /*encrypt=*/ false, new ParcelUuid(uuid)); 57 } 58 59 @Implementation startDiscovery()60 protected boolean startDiscovery() { 61 isDiscovering = true; 62 return true; 63 } 64 65 @Implementation cancelDiscovery()66 protected boolean cancelDiscovery() { 67 isDiscovering = false; 68 return true; 69 } 70 71 @Implementation(minSdk = JELLY_BEAN_MR2) startLeScan(LeScanCallback callback)72 protected boolean startLeScan(LeScanCallback callback) { 73 return startLeScan(null, callback); 74 } 75 76 @Implementation(minSdk = JELLY_BEAN_MR2) startLeScan(UUID[] serviceUuids, LeScanCallback callback)77 protected boolean startLeScan(UUID[] serviceUuids, LeScanCallback callback) { 78 // Ignoring the serviceUuids param for now. 79 leScanCallbacks.add(callback); 80 return true; 81 } 82 83 @Implementation(minSdk = JELLY_BEAN_MR2) stopLeScan(LeScanCallback callback)84 protected void stopLeScan(LeScanCallback callback) { 85 leScanCallbacks.remove(callback); 86 } 87 getLeScanCallbacks()88 public Set<LeScanCallback> getLeScanCallbacks() { 89 return Collections.unmodifiableSet(leScanCallbacks); 90 } 91 getSingleLeScanCallback()92 public LeScanCallback getSingleLeScanCallback() { 93 if (leScanCallbacks.size() != 1) { 94 throw new IllegalStateException("There are " + leScanCallbacks.size() + " callbacks"); 95 } 96 return leScanCallbacks.iterator().next(); 97 } 98 99 @Implementation isDiscovering()100 protected boolean isDiscovering() { 101 return isDiscovering; 102 } 103 104 @Implementation isEnabled()105 protected boolean isEnabled() { 106 return enabled; 107 } 108 109 @Implementation enable()110 protected boolean enable() { 111 enabled = true; 112 return true; 113 } 114 115 @Implementation disable()116 protected boolean disable() { 117 enabled = false; 118 return true; 119 } 120 121 @Implementation getAddress()122 protected String getAddress() { 123 return this.address; 124 } 125 126 @Implementation getState()127 protected int getState() { 128 return state; 129 } 130 131 @Implementation getName()132 protected String getName() { 133 return name; 134 } 135 136 @Implementation setName(String name)137 protected boolean setName(String name) { 138 this.name = name; 139 return true; 140 } 141 142 @Implementation setScanMode(int scanMode)143 protected boolean setScanMode(int scanMode) { 144 if (scanMode != BluetoothAdapter.SCAN_MODE_CONNECTABLE 145 && scanMode != BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE 146 && scanMode != BluetoothAdapter.SCAN_MODE_NONE) { 147 return false; 148 } 149 150 this.scanMode = scanMode; 151 return true; 152 } 153 154 @Implementation getScanMode()155 protected int getScanMode() { 156 return scanMode; 157 } 158 159 @Implementation(minSdk = LOLLIPOP) isMultipleAdvertisementSupported()160 protected boolean isMultipleAdvertisementSupported() { 161 return isMultipleAdvertisementSupported; 162 } 163 164 /** 165 * Validate a Bluetooth address, such as "00:43:A8:23:10:F0" Alphabetic characters must be 166 * uppercase to be valid. 167 * 168 * @param address Bluetooth address as string 169 * @return true if the address is valid, false otherwise 170 */ 171 @Implementation checkBluetoothAddress(String address)172 protected static boolean checkBluetoothAddress(String address) { 173 if (address == null || address.length() != ADDRESS_LENGTH) { 174 return false; 175 } 176 for (int i = 0; i < ADDRESS_LENGTH; i++) { 177 char c = address.charAt(i); 178 switch (i % 3) { 179 case 0: 180 case 1: 181 if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'F')) { 182 // hex character, OK 183 break; 184 } 185 return false; 186 case 2: 187 if (c == ':') { 188 break; // OK 189 } 190 return false; 191 } 192 } 193 return true; 194 } 195 196 /** 197 * Returns the connection state for the given Bluetooth {@code profile}, defaulting to {@link 198 * BluetoothProfile.STATE_DISCONNECTED} if the profile's connection state was never set. 199 * 200 * <p>Set a Bluetooth profile's connection state via {@link #setProfileConnectionState(int, int)}. 201 */ 202 @Implementation getProfileConnectionState(int profile)203 protected int getProfileConnectionState(int profile) { 204 Integer state = profileConnectionStateData.get(profile); 205 if (state == null) { 206 return BluetoothProfile.STATE_DISCONNECTED; 207 } 208 return state; 209 } 210 setAddress(String address)211 public void setAddress(String address) { 212 this.address = address; 213 } 214 setState(int state)215 public void setState(int state) { 216 this.state = state; 217 } 218 setEnabled(boolean enabled)219 public void setEnabled(boolean enabled) { 220 this.enabled = enabled; 221 } 222 setIsMultipleAdvertisementSupported(boolean supported)223 public void setIsMultipleAdvertisementSupported(boolean supported) { 224 isMultipleAdvertisementSupported = supported; 225 } 226 227 /** 228 *Sets the connection state {@code state} for the given BLuetoothProfile {@code profile} 229 */ setProfileConnectionState(int profile, int state)230 public void setProfileConnectionState(int profile, int state) { 231 profileConnectionStateData.put(profile, state); 232 } 233 } 234