• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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