All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.robolectric.shadows.ShadowBluetoothAdapter Maven / Gradle / Ivy

There is a newer version: 4.14.1
Show newest version
package org.robolectric.shadows;

import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2;
import static android.os.Build.VERSION_CODES.LOLLIPOP;
import static org.robolectric.shadow.api.Shadow.directlyOn;

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.io.IOException;
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 javax.annotation.Nullable;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.RealObject;

@SuppressWarnings({"UnusedDeclaration"})
@Implements(BluetoothAdapter.class)
public class ShadowBluetoothAdapter {
  @RealObject private BluetoothAdapter realAdapter;

  private static final int ADDRESS_LENGTH = 17;

  private Set bondedDevices = new HashSet();
  private Set leScanCallbacks = new HashSet();
  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 boolean isOverridingProxyBehavior;
  private final Map profileConnectionStateData = new HashMap<>();
  private final Map profileProxies = new HashMap<>();

  @Implementation
  protected static BluetoothAdapter getDefaultAdapter() {
    return (BluetoothAdapter) ShadowApplication.getInstance().getBluetoothAdapter();
  }

  @Implementation
  protected Set getBondedDevices() {
    return Collections.unmodifiableSet(bondedDevices);
  }

  public void setBondedDevices(Set 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 BluetoothServerSocket listenUsingRfcommWithServiceRecord(String serviceName, UUID uuid)
      throws IOException {
    return ShadowBluetoothServerSocket.newInstance(
        BluetoothSocket.TYPE_RFCOMM, /*auth=*/ false, /*encrypt=*/ true, 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 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.
   *
   * 

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 always * affect behavior of {@link BluetoothAdapter#getProfileProxy} and {@link * BluetoothAdapter#closeProfileProxy}. Call to {@link BluetoothAdapter#closeProfileProxy} can * remove the set active proxy. * * @param proxy can be 'null' to simulate the situation where {@link * BluetoothAdapter#getProfileProxy} would return 'false'. This can happen on older Android * versions for Bluetooth profiles introduced in later Android versions. */ public void setProfileProxy(int profile, @Nullable BluetoothProfile proxy) { isOverridingProxyBehavior = true; if (proxy != null) { profileProxies.put(profile, proxy); } } /** * @return 'true' if active (non-null) proxy has been set by {@link * ShadowBluetoothAdapter#setProfileProxy} for the given {@code profile} AND it has not been * "deactivated" by a call to {@link BluetoothAdapter#closeProfileProxy}. Only meaningful if * {@link ShadowBluetoothAdapter#setProfileProxy} has been previously called. */ public boolean hasActiveProfileProxy(int profile) { return profileProxies.get(profile) != null; } /** * Overrides behavior of {@link getProfileProxy} if {@link ShadowBluetoothAdapter#setProfileProxy} * has been previously called. * * If active (non-null) proxy has been set by {@link setProfileProxy} for the given {@code * profile}, {@link getProfileProxy} will immediately call {@code onServiceConnected} of the given * BluetoothProfile.ServiceListener {@code listener}. * * @return 'true' if a proxy object has been set by {@link setProfileProxy} for the given * BluetoothProfile {@code profile} */ @Implementation protected boolean getProfileProxy( Context context, BluetoothProfile.ServiceListener listener, int profile) { if (!isOverridingProxyBehavior) { return directlyOn(realAdapter, BluetoothAdapter.class) .getProfileProxy(context, listener, profile); } BluetoothProfile proxy = profileProxies.get(profile); if (proxy == null) { return false; } else { listener.onServiceConnected(profile, proxy); return true; } } /** * Overrides behavior of {@link closeProfileProxy} if {@link * ShadowBluetoothAdapter#setProfileProxy} has been previously called. * * If the given non-null 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 (!isOverridingProxyBehavior) { directlyOn(realAdapter, BluetoothAdapter.class).closeProfileProxy(profile, proxy); return; } if (proxy != null && proxy.equals(profileProxies.get(profile))) { profileProxies.remove(profile); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy