blob: b3c0616eb7971c35990298eee06fdf4d7e38f23f [file] [log] [blame]
package org.robolectric.shadows;
import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2;
import static android.os.Build.VERSION_CODES.LOLLIPOP;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothAdapter.LeScanCallback;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothServerSocket;
import android.bluetooth.BluetoothSocket;
import android.content.Context;
import android.os.ParcelUuid;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
@SuppressWarnings({"UnusedDeclaration"})
@Implements(BluetoothAdapter.class)
public class ShadowBluetoothAdapter {
private static final int ADDRESS_LENGTH = 17;
private Set<BluetoothDevice> bondedDevices = new HashSet<BluetoothDevice>();
private Set<LeScanCallback> leScanCallbacks = new HashSet<LeScanCallback>();
private boolean isDiscovering;
private String address;
private boolean enabled;
private int state;
private String name = "DefaultBluetoothDeviceName";
private int scanMode = BluetoothAdapter.SCAN_MODE_NONE;
private boolean isMultipleAdvertisementSupported = true;
private final Map<Integer, Integer> profileConnectionStateData = new HashMap<>();
private final Map<Integer, BluetoothProfile> profileProxies = new HashMap<>();
@Implementation
protected static BluetoothAdapter getDefaultAdapter() {
return (BluetoothAdapter) ShadowApplication.getInstance().getBluetoothAdapter();
}
@Implementation
protected Set<BluetoothDevice> getBondedDevices() {
return Collections.unmodifiableSet(bondedDevices);
}
public void setBondedDevices(Set<BluetoothDevice> bluetoothDevices) {
bondedDevices = bluetoothDevices;
}
@Implementation
protected BluetoothServerSocket listenUsingInsecureRfcommWithServiceRecord(
String serviceName, UUID uuid) {
return ShadowBluetoothServerSocket.newInstance(
BluetoothSocket.TYPE_RFCOMM, /*auth=*/ false, /*encrypt=*/ false, new ParcelUuid(uuid));
}
@Implementation
protected boolean startDiscovery() {
isDiscovering = true;
return true;
}
@Implementation
protected boolean cancelDiscovery() {
isDiscovering = false;
return true;
}
@Implementation(minSdk = JELLY_BEAN_MR2)
protected boolean startLeScan(LeScanCallback callback) {
return startLeScan(null, callback);
}
@Implementation(minSdk = JELLY_BEAN_MR2)
protected boolean startLeScan(UUID[] serviceUuids, LeScanCallback callback) {
// Ignoring the serviceUuids param for now.
leScanCallbacks.add(callback);
return true;
}
@Implementation(minSdk = JELLY_BEAN_MR2)
protected void stopLeScan(LeScanCallback callback) {
leScanCallbacks.remove(callback);
}
public Set<LeScanCallback> getLeScanCallbacks() {
return Collections.unmodifiableSet(leScanCallbacks);
}
public LeScanCallback getSingleLeScanCallback() {
if (leScanCallbacks.size() != 1) {
throw new IllegalStateException("There are " + leScanCallbacks.size() + " callbacks");
}
return leScanCallbacks.iterator().next();
}
@Implementation
protected boolean isDiscovering() {
return isDiscovering;
}
@Implementation
protected boolean isEnabled() {
return enabled;
}
@Implementation
protected boolean enable() {
enabled = true;
return true;
}
@Implementation
protected boolean disable() {
enabled = false;
return true;
}
@Implementation
protected String getAddress() {
return this.address;
}
@Implementation
protected int getState() {
return state;
}
@Implementation
protected String getName() {
return name;
}
@Implementation
protected boolean setName(String name) {
this.name = name;
return true;
}
@Implementation
protected boolean setScanMode(int scanMode) {
if (scanMode != BluetoothAdapter.SCAN_MODE_CONNECTABLE
&& scanMode != BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE
&& scanMode != BluetoothAdapter.SCAN_MODE_NONE) {
return false;
}
this.scanMode = scanMode;
return true;
}
@Implementation
protected int getScanMode() {
return scanMode;
}
@Implementation(minSdk = LOLLIPOP)
protected boolean isMultipleAdvertisementSupported() {
return isMultipleAdvertisementSupported;
}
/**
* Validate a Bluetooth address, such as "00:43:A8:23:10:F0" Alphabetic characters must be
* uppercase to be valid.
*
* @param address Bluetooth address as string
* @return true if the address is valid, false otherwise
*/
@Implementation
protected static boolean checkBluetoothAddress(String address) {
if (address == null || address.length() != ADDRESS_LENGTH) {
return false;
}
for (int i = 0; i < ADDRESS_LENGTH; i++) {
char c = address.charAt(i);
switch (i % 3) {
case 0:
case 1:
if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'F')) {
// hex character, OK
break;
}
return false;
case 2:
if (c == ':') {
break; // OK
}
return false;
}
}
return true;
}
/**
* Returns the connection state for the given Bluetooth {@code profile}, defaulting to {@link
* BluetoothProfile.STATE_DISCONNECTED} if the profile's connection state was never set.
*
* <p>Set a Bluetooth profile's connection state via {@link #setProfileConnectionState(int, int)}.
*/
@Implementation
protected int getProfileConnectionState(int profile) {
Integer state = profileConnectionStateData.get(profile);
if (state == null) {
return BluetoothProfile.STATE_DISCONNECTED;
}
return state;
}
public void setAddress(String address) {
this.address = address;
}
public void setState(int state) {
this.state = state;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public void setIsMultipleAdvertisementSupported(boolean supported) {
isMultipleAdvertisementSupported = supported;
}
/** Sets the connection state {@code state} for the given BLuetoothProfile {@code profile} */
public void setProfileConnectionState(int profile, int state) {
profileConnectionStateData.put(profile, state);
}
/**
* Sets the active BluetoothProfile {@code proxy} for the given {@code profile}. Will affect
* behavior of {@link BluetoothAdapter#getProfileProxy} and {@link
* BluetoothAdapter#closeProfileProxy}.
*
* <p>Call to {@link BluetoothAdapter#closeProfileProxy} can remove the set active proxy.
*/
public void setProfileProxy(int profile, BluetoothProfile proxy) {
profileProxies.put(profile, proxy);
}
/**
* @return True if active proxy has been set by {@link ShadowBluetoothAdapter#setProfileProxy} for
* the given BluetoothProfile {@code profile} AND it has not been "deactivated" by a call to
* {@link BluetoothAdapter#closeProfileProxy}.
*/
public boolean hasActiveProfileProxy(int profile) {
return profileProxies.get(profile) != null;
}
/**
* Overrides behavior of {@link BluetoothAdapter#getProfileProxy} to return pre-set result. If
* active proxy has been set by {@link ShadowBluetoothAdapter#setProfileProxy} for the given
* {@code profile}, getProfileProxy() will immediately call {@code onServiceConnected} of the
* given BluetoothProfile.ServiceListener {@code listener}.
*
* @return True if active proxy has been set by {@link ShadowBluetoothAdapter#setProfileProxy} for
* the given BluetoothProfile {@code profile}
*/
@Implementation
protected boolean getProfileProxy(
Context context, BluetoothProfile.ServiceListener listener, int profile) {
BluetoothProfile proxy = profileProxies.get(profile);
if (proxy == null) {
return false;
} else {
listener.onServiceConnected(profile, proxy);
return true;
}
}
/**
* Overrides behavior of {@link BluetoothAdapter#closeProfileProxy}. If the given BluetoothProfile
* {@code proxy} was previously set for the given {@code profile} by {@link
* ShadowBluetoothAdapter#setProfileProxy}, this proxy will be "deactivated".
*/
@Implementation
protected void closeProfileProxy(int profile, BluetoothProfile proxy) {
if (profileProxies.get(profile).equals(proxy)) {
profileProxies.remove(profile);
}
}
}