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

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

package org.robolectric.shadows;

import static android.bluetooth.BluetoothDevice.BOND_NONE;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2;
import static android.os.Build.VERSION_CODES.M;
import static android.os.Build.VERSION_CODES.O;
import static android.os.Build.VERSION_CODES.O_MR1;
import static android.os.Build.VERSION_CODES.Q;
import static android.os.Build.VERSION_CODES.S;
import static org.robolectric.util.reflector.Reflector.reflector;

import android.annotation.IntRange;
import android.app.ActivityThread;
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothSocket;
import android.bluetooth.BluetoothStatusCodes;
import android.bluetooth.IBluetooth;
import android.content.Context;
import android.os.Build.VERSION;
import android.os.Handler;
import android.os.ParcelUuid;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.RealObject;
import org.robolectric.annotation.Resetter;
import org.robolectric.shadow.api.Shadow;
import org.robolectric.util.ReflectionHelpers;
import org.robolectric.util.reflector.Direct;
import org.robolectric.util.reflector.ForType;
import org.robolectric.util.reflector.Static;

/** Shadow for {@link BluetoothDevice}. */
@Implements(value = BluetoothDevice.class, looseSignatures = true)
public class ShadowBluetoothDevice {
  @Deprecated // Prefer {@link android.bluetooth.BluetoothAdapter#getRemoteDevice}
  public static BluetoothDevice newInstance(String address) {
    return ReflectionHelpers.callConstructor(
        BluetoothDevice.class, ReflectionHelpers.ClassParameter.from(String.class, address));
  }

  @Resetter
  public static void reset() {
    bluetoothSocket = null;
  }

  private static BluetoothSocket bluetoothSocket = null;

  @RealObject private BluetoothDevice realBluetoothDevice;
  private String name;
  private ParcelUuid[] uuids;
  private int bondState = BOND_NONE;
  private boolean createdBond = false;
  private boolean fetchUuidsWithSdpResult = false;
  private int fetchUuidsWithSdpCount = 0;
  private int type = BluetoothDevice.DEVICE_TYPE_UNKNOWN;
  private final List bluetoothGatts = new ArrayList<>();
  private Boolean pairingConfirmation = null;
  private byte[] pin = null;
  private String alias;
  private boolean shouldThrowOnGetAliasName = false;
  private BluetoothClass bluetoothClass = null;
  private boolean shouldThrowSecurityExceptions = false;
  private final Map metadataMap = new HashMap<>();
  private int batteryLevel = BluetoothDevice.BATTERY_LEVEL_BLUETOOTH_OFF;
  private boolean isInSilenceMode = false;

  /**
   * Implements getService() in the same way the original method does, but ignores any Exceptions
   * from invoking {@link android.bluetooth.BluetoothAdapter#getBluetoothService}.
   */
  @Implementation
  protected static IBluetooth getService() {
    // Attempt to call the underlying getService method, but ignore any Exceptions. This allows us
    // to easily create BluetoothDevices for testing purposes without having any actual Bluetooth
    // capability.
    try {
      return reflector(BluetoothDeviceReflector.class).getService();
    } catch (Exception e) {
      // No-op.
    }
    return null;
  }

  public void setName(String name) {
    this.name = name;
  }

  /**
   * Sets the alias name of the device.
   *
   * 

Alias is the locally modified name of a remote device. * *

Alias Name is not part of the supported SDK, and accessed via reflection. * * @param alias alias name. */ @Implementation public Object setAlias(Object alias) { this.alias = (String) alias; if (RuntimeEnvironment.getApiLevel() >= S) { return BluetoothStatusCodes.SUCCESS; } else { return true; } } /** * Sets if a runtime exception is thrown when the alias name of the device is accessed. * *

Intended to replicate what may happen if the unsupported SDK is changed. * *

Alias is the locally modified name of a remote device. * *

Alias Name is not part of the supported SDK, and accessed via reflection. * * @param shouldThrow if getAliasName() should throw when called. */ public void setThrowOnGetAliasName(boolean shouldThrow) { shouldThrowOnGetAliasName = shouldThrow; } /** * Sets if a runtime exception is thrown when bluetooth methods with BLUETOOTH_CONNECT permission * pre-requisites are accessed. * *

Intended to replicate what may happen if user has not enabled nearby device permissions. * * @param shouldThrow if methods should throw SecurityExceptions without enabled permissions when * called. */ public void setShouldThrowSecurityExceptions(boolean shouldThrow) { shouldThrowSecurityExceptions = shouldThrow; } @Implementation protected String getName() { checkForBluetoothConnectPermission(); return name; } @Implementation protected String getAlias() { checkForBluetoothConnectPermission(); return alias; } @Implementation(maxSdk = Q) protected String getAliasName() throws ReflectiveOperationException { // Mimicking if the officially supported function is changed. if (shouldThrowOnGetAliasName) { throw new ReflectiveOperationException("Exception on getAliasName"); } // Matches actual implementation. String name = getAlias(); return name != null ? name : getName(); } /** Sets the return value for {@link BluetoothDevice#getType}. */ public void setType(int type) { this.type = type; } /** * Overrides behavior of {@link BluetoothDevice#getType} to return pre-set result. * * @return Value set by calling {@link ShadowBluetoothDevice#setType}. If setType has not * previously been called, will return BluetoothDevice.DEVICE_TYPE_UNKNOWN. */ @Implementation(minSdk = JELLY_BEAN_MR2) protected int getType() { checkForBluetoothConnectPermission(); return type; } /** Sets the return value for {@link BluetoothDevice#getUuids}. */ public void setUuids(ParcelUuid[] uuids) { this.uuids = uuids; } /** * Overrides behavior of {@link BluetoothDevice#getUuids} to return pre-set result. * * @return Value set by calling {@link ShadowBluetoothDevice#setUuids}. If setUuids has not * previously been called, will return null. */ @Implementation protected ParcelUuid[] getUuids() { checkForBluetoothConnectPermission(); return uuids; } /** Sets value of bond state for {@link BluetoothDevice#getBondState}. */ public void setBondState(int bondState) { this.bondState = bondState; } /** * Overrides behavior of {@link BluetoothDevice#getBondState} to return pre-set result. * * @returns Value set by calling {@link ShadowBluetoothDevice#setBondState}. If setBondState has * not previously been called, will return {@link BluetoothDevice#BOND_NONE} to indicate the * device is not bonded. */ @Implementation protected int getBondState() { checkForBluetoothConnectPermission(); return bondState; } /** Sets whether this device has been bonded with. */ public void setCreatedBond(boolean createdBond) { this.createdBond = createdBond; } /** Returns whether this device has been bonded with. */ @Implementation protected boolean createBond() { checkForBluetoothConnectPermission(); return createdBond; } @Implementation(minSdk = Q) protected BluetoothSocket createInsecureL2capChannel(int psm) throws IOException { checkForBluetoothConnectPermission(); return reflector(BluetoothDeviceReflector.class, realBluetoothDevice) .createInsecureL2capChannel(psm); } @Implementation(minSdk = Q) protected BluetoothSocket createL2capChannel(int psm) throws IOException { checkForBluetoothConnectPermission(); return reflector(BluetoothDeviceReflector.class, realBluetoothDevice).createL2capChannel(psm); } @Implementation protected boolean removeBond() { checkForBluetoothConnectPermission(); boolean result = createdBond; createdBond = false; return result; } @Implementation protected boolean setPin(byte[] pin) { checkForBluetoothConnectPermission(); this.pin = pin; return true; } /** * Get the PIN previously set with a call to {@link BluetoothDevice#setPin(byte[])}, or null if no * PIN has been set. */ public byte[] getPin() { return pin; } @Implementation public boolean setPairingConfirmation(boolean confirm) { checkForBluetoothConnectPermission(); this.pairingConfirmation = confirm; return true; } /** * Get the confirmation value previously set with a call to {@link * BluetoothDevice#setPairingConfirmation(boolean)}, or null if no value is set. */ public Boolean getPairingConfirmation() { return pairingConfirmation; } @Implementation protected BluetoothSocket createRfcommSocketToServiceRecord(UUID uuid) throws IOException { checkForBluetoothConnectPermission(); synchronized (ShadowBluetoothDevice.class) { if (bluetoothSocket == null) { bluetoothSocket = Shadow.newInstanceOf(BluetoothSocket.class); } } return bluetoothSocket; } /** Sets value of the return result for {@link BluetoothDevice#fetchUuidsWithSdp}. */ public void setFetchUuidsWithSdpResult(boolean fetchUuidsWithSdpResult) { this.fetchUuidsWithSdpResult = fetchUuidsWithSdpResult; } /** * Overrides behavior of {@link BluetoothDevice#fetchUuidsWithSdp}. This method updates the * counter which counts the number of invocations of this method. * * @returns Value set by calling {@link ShadowBluetoothDevice#setFetchUuidsWithSdpResult}. If not * previously set, will return false by default. */ @Implementation protected boolean fetchUuidsWithSdp() { checkForBluetoothConnectPermission(); fetchUuidsWithSdpCount++; return fetchUuidsWithSdpResult; } /** Returns the number of times fetchUuidsWithSdp has been called. */ public int getFetchUuidsWithSdpCount() { return fetchUuidsWithSdpCount; } @Implementation(minSdk = JELLY_BEAN_MR2) protected BluetoothGatt connectGatt( Context context, boolean autoConnect, BluetoothGattCallback callback) { checkForBluetoothConnectPermission(); return connectGatt(callback); } @Implementation(minSdk = M) protected BluetoothGatt connectGatt( Context context, boolean autoConnect, BluetoothGattCallback callback, int transport) { checkForBluetoothConnectPermission(); return connectGatt(callback); } @Implementation(minSdk = O) protected BluetoothGatt connectGatt( Context context, boolean autoConnect, BluetoothGattCallback callback, int transport, int phy, Handler handler) { checkForBluetoothConnectPermission(); return connectGatt(callback); } private BluetoothGatt connectGatt(BluetoothGattCallback callback) { BluetoothGatt bluetoothGatt = ShadowBluetoothGatt.newInstance(realBluetoothDevice); bluetoothGatts.add(bluetoothGatt); ShadowBluetoothGatt shadowBluetoothGatt = Shadow.extract(bluetoothGatt); shadowBluetoothGatt.setGattCallback(callback); return bluetoothGatt; } /** * Returns all {@link BluetoothGatt} objects created by calling {@link * ShadowBluetoothDevice#connectGatt}. */ public List getBluetoothGatts() { return bluetoothGatts; } /** * Causes {@link BluetoothGattCallback#onConnectionStateChange to be called for every GATT client. * @param status Status of the GATT operation * @param newState The new state of the GATT profile */ public void simulateGattConnectionChange(int status, int newState) { for (BluetoothGatt bluetoothGatt : bluetoothGatts) { ShadowBluetoothGatt shadowBluetoothGatt = Shadow.extract(bluetoothGatt); BluetoothGattCallback gattCallback = shadowBluetoothGatt.getGattCallback(); gattCallback.onConnectionStateChange(bluetoothGatt, status, newState); } } /** * Overrides behavior of {@link BluetoothDevice#getBluetoothClass} to return pre-set result. * * @return Value set by calling {@link ShadowBluetoothDevice#setBluetoothClass}. If setType has * not previously been called, will return null. */ @Implementation public BluetoothClass getBluetoothClass() { checkForBluetoothConnectPermission(); return bluetoothClass; } /** Sets the return value for {@link BluetoothDevice#getBluetoothClass}. */ public void setBluetoothClass(BluetoothClass bluetoothClass) { this.bluetoothClass = bluetoothClass; } @Implementation(minSdk = Q) protected boolean setMetadata(int key, byte[] value) { checkForBluetoothConnectPermission(); metadataMap.put(key, value); return true; } @Implementation(minSdk = Q) protected byte[] getMetadata(int key) { checkForBluetoothConnectPermission(); return metadataMap.get(key); } public void setBatteryLevel(@IntRange(from = -100, to = 100) int batteryLevel) { this.batteryLevel = batteryLevel; } @Implementation(minSdk = O_MR1) protected int getBatteryLevel() { checkForBluetoothConnectPermission(); return batteryLevel; } @Implementation(minSdk = Q) public boolean setSilenceMode(boolean isInSilenceMode) { checkForBluetoothConnectPermission(); this.isInSilenceMode = isInSilenceMode; return true; } @Implementation(minSdk = Q) protected boolean isInSilenceMode() { checkForBluetoothConnectPermission(); return isInSilenceMode; } @ForType(BluetoothDevice.class) interface BluetoothDeviceReflector { @Static @Direct IBluetooth getService(); @Direct BluetoothSocket createInsecureL2capChannel(int psm); @Direct BluetoothSocket createL2capChannel(int psm); } static ShadowInstrumentation getShadowInstrumentation() { ActivityThread activityThread = (ActivityThread) RuntimeEnvironment.getActivityThread(); return Shadow.extract(activityThread.getInstrumentation()); } private void checkForBluetoothConnectPermission() { if (shouldThrowSecurityExceptions && VERSION.SDK_INT >= S && !checkPermission(android.Manifest.permission.BLUETOOTH_CONNECT)) { throw new SecurityException("Bluetooth connect permission required."); } } static boolean checkPermission(String permission) { return getShadowInstrumentation() .checkPermission(permission, android.os.Process.myPid(), android.os.Process.myUid()) == PERMISSION_GRANTED; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy