Skip to content

Scan for Tapkey-Enabled locks using Bluetooth Low Energy

This guide will show you how to scan for Tapkey-Enabled Bluetooth Devices using the Tapkey Mobile SDK for Android. Tapkey SDK relies on Bluetooth Low Energy (BLE) to sense and connect with nearby Tapkey-Enabled lock devices, allowing for optimal battery consumption.

Some of the core concepts of the Tapkey Mobile SDK for Android are:

  • BleLockScanner used to detect and communicate Tapkey locks nearby.

In order to enable the communication between the device and Tapkey-Enabled locks, you are required to:

  • Set proper permissions from the application to be able to access BLE.
  • Activate scanning via Tapkey API or by a custom implementation (suggested by the tapkey team).
  • Use either Observable methods or Get information from nearby devices as required.
  • Stop/Close scanning when no longer required.

Setup Requisites

It is required to set proper permissions to be able to access the BLE functionality from the device, so that Tapkey SDK can be used to access Tapkey-Enabled locks.

Adding Bluetooth Permissions to the Android Manifest

Depending on the Android version the app is running on, different permissions are required to scan for and communicate with BLE devices. The following table lists the permissions needed for each Android version:

Android Version Permissions Required
Android 12 (API Level 31) and later android.permission.BLUETOOTH_CONNECT, android.permission.BLUETOOTH_SCAN
Android 10 (API Level 29) - Android 11 (API Level 30) android.permission.BLUETOOTH_ADMIN, android.permission.BLUETOOTH, android.permission.ACCESS_FINE_LOCATION
Android 6 (API Level 23) - Android 9 (API Level 28) android.permission.BLUETOOTH_ADMIN, android.permission.BLUETOOTH, android.permission.ACCESS_COARSE_LOCATION

Add the following uses-permission entries to the AndroidManifest.xml:

<manifest  >
        <uses-permission android:name="android.permission.BLUETOOTH"
        android:maxSdkVersion="30"/>

    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"
        android:maxSdkVersion="30"/>

    <uses-permission-sdk-23 android:name="android.permission.ACCESS_COARSE_LOCATION"
        android:maxSdkVersion="30"/>

    <uses-permission-sdk-23 android:name="android.permission.ACCESS_FINE_LOCATION"
        android:maxSdkVersion="30" />

    <uses-permission android:name="android.permission.BLUETOOTH_CONNECT"
        tools:targetApi="31"/>

    <uses-permission android:name="android.permission.BLUETOOTH_SCAN"
        android:usesPermissionFlags="neverForLocation"
        tools:targetApi="31" />
    </manifest>

The flag android:usesPermissionFlags="neverForLocation" may only be specified if the app doesn't use Bluetooth to determine the device's location. The Tapkey Mobile SDK for Android does not use Bluetooth to determine the device's location.

Read more

Requesting Permissions

Before starting a BLE scan, the required permissions must be requested. If the required permissions haven't been granted, scanning does not fail but returns an empty list of nearby devices. If you are sure that you have nearby devices but they do not show up, please check if your app has been granted all the required permissions.

This can be done, for example, with ContextCompat.checkSelfPermission(...). For more details about requesting Android Permissions, refer to the Android Documentation.

Scanning

Custom BLE Scanner gives fine control from implementation perspective. This option allows consumers to make fine grained decisions like when a lock is considered nearby or out of range, add the concept of further BLE devices that need to be discovered and listed at the same time, or when the default implementation does not meet your requirements.

The Tapkey Mobile SDK for Android provides utilities to filter and detect Tapkey-enabled Bluetooth devices.

Refer to the Android Documentation for guidance on implementing a custom BLE scanner.

Filter for Tapkey-Enabled locks

You can apply ScanFilters to start a BLE scan efficiently. The Tapkey Mobile SDK for Android provides filters for Tapkey-enabled devices, which can be extended or omitted depending on each use case.

// Retrieve the TapkeyBleAdvertisingFormat from the TapkeyServiceFactory instance
TapkeyBleAdvertisingFormat advertisingFormat = tapkeyServiceFactory.getTapkeyBleAdvertisingFormat();

// Get Android ScanFilters
List<ScanFilter> scanFilters = ScanFilters.getAndroidScanFilters(advertisingFormat.getScanFilters())

// Apply the Scanfilters when starting the BLE scan
bluetoothLeScanner.startScan(scanFilters, scanSettings, scanCallback);

Detecting Tapkey-Enabled locks

Once the BLE Scanner discovers a BLE device, the ScanCallback receives a ScanResult. You can use the TapkeyBleAdvertisingParser to identify whether it's a Tapkey-enabled device and its physicalLockId.

// Get the TapkeyBleAdvertisingParser
TapkeyBleAdvertisingFormat advertisingFormat = tapkeyServiceFactory.getTapkeyBleAdvertisingFormat();
TapkeyBleAdvertisingParser advertisingParser = advertisingFormat.getAdvertisingParser();

// Parse the advertising data of the BLE device
TapkeyAdvertisingData data = advertisingParser.parseAdvertisingData(scanRecord.getBytes())

// If the parsed data is not null, the discovered device is a Tapkey-enabled device
if(data != null) {
    String physicalLockId = data.getPhysicalLockId();
}

A sample ScanCallback might look like this:

ScanCallback scanCallback = new ScanCallback() {

    ...

    @Override
    public void onScanResult(int callbackType, ScanResult result) {

        super.onScanResult(callbackType, result);

        if(result == null){
            if(DEBUG) Log.d(TAG, "scan result was null");
            return;
        }

        BluetoothDevice device = result.getDevice();

        switch (callbackType) {
            case ScanSettings.CALLBACK_TYPE_FIRST_MATCH:
            case ScanSettings.CALLBACK_TYPE_ALL_MATCHES:
                ScanRecord scanRecord = result.getScanRecord();

                if(device == null || scanRecord == null){
                    return;
                }

                // Try to parse the advertising data
                // If the retunred data is null, it was not a Tapkey enavled BLE device
                TapkeyAdvertisingData data = advertisingParser.parseAdvertisingData(scanRecord.getBytes())

                if(data != null) {
                    // The TapkeyAdvertisingData contains the physicalLock id,
                    // which can be used to map against the keys
                    String physicalLockId = data.getPhysicalLockId();
                }

                break;

            case ScanSettings.CALLBACK_TYPE_MATCH_LOST:
                break;
        }
    }
};

bluetoothLeScanner.startScan(scanFilters, scanSettings, scanCallback);

Communicate with discovered Lock

After discovering a Tapkey-enabled device, you can communicate with the lock using the Bluetooth address of the BluetoothDevice.

Don't store Bluetooth address

It's important to note that the Bluetooth address may be obfuscated by Android for privacy reasons, so it's recommended not to store it for later use or on a different smartphone.

BluetoothDevice device; // The discovered BluetoothDevice
String physicalLockId; // The discovered physicalLockId

// Get the Bluetooth address
String bluetoothAddress = device.getAddress();

// Execute a command on the lock using the BleLockCommunicator
return bleLockCommunicator.executeCommandAsync(bluetoothAddress, physicalLockId, COMMAND_FUNC, CANCELLATIONTOKEN)
    ...

BleLockScanner

The Tapkey Mobile SDK for Android offers a default implementation of a BluetoothScanner designed to detect Tapkey-Enabled locks. This implementation can be used for a quick start, although it is advisable to utilize a Custom Implementation for better control and flexibility.

BleLockScanner can detect Tapkey-Enabled locks nearby via Bluetooth Low Energy (BLE) by detecting if a Tapkey-Enabled lock is nearby, enters or leaves device's range. Locks that have not been detected within 10 seconds will be assumed as out of range.

Avoid cancelling interactions when Device is out of range

When a connection with a lock is open (e.g. during unlocking), the lock stops advertising. This might cause the BleLockScanner to mark the lock as out of range. Therefore, don't cancel an ongoing command execution when the lock is marked as out of range.

The TapkeyServiceFactory allows you to obtain the necessary instance of BleLockScanner for scanning Tapkey locks via BLE.

BleLockScanner scanner = tapkeyServiceFactory.getBleLockScanner();

There are multiple utilities implemented that allow consumers to:

  • Start / Stop foreground scanner which is required to interact with neadby locks.
  • Observe changes in nearby locks presence.
  • Communicate with nearby locks.

Starting BLE scanning

To start the foreground scan, call startForegroundScan() on the BleLockScanner instance. It returns an observer registration that can be used to stop scanning once finished.

Ensure enough permissions have been granted

Before starting the scan, ensure that the necessary permissions have been granted.

bleScanObserverRegistration = scanner.startForegroundScan();

Stop BLE Scanning (close)

Make sure to stop scanning if the app does not require information about nearby locks anymore, such as when the user logs out or user is not required to see nearby locks by user experience.

// Stop scanning for nearby locks.
if (bleScanObserverRegistration != null) {
    bleScanObserverRegistration.close();
    bleScanObserverRegistration = null;
}

Observe changes in device range (getLocksChangedObservable)

Observe changes of devices nearby by using the getLocksChangedObservable() method. This method returns an Observable that emits a list of locks whenever there is a change in the nearby locks.

nearbyLocksObserverRegistration = scanner.getLocksChangedObservable()
    .addObserver(locks -> {
        // Handle nearby locks here, e.g. display.
    });

Get locks nearby (getLocks)

The method getLocks() allows you to get a list of nearby locks at the current moment.

// In addition, getLocks() can be used to get a list of nearby locks at this moment.
List<BleLock> nearbyLocks = scanner.getLocks();

Check lock presence nearby (isLockNearby)

The method isLockNearby() checks if a specific lock is in the device's range.

Requirements

Scanning must still be ongoing via startForegroundScan(), otherwise, isLockNearby() will always return false.

boolean isNearby = scanner.isLockNearby(boundLock.physicalLockId)

One common use scenario for this method is checking for all keys the user holds, the corresponding nearby locks, and display them in the user interface.

Communicate with discovered Lock

When a Tapkey-Enabled device was successfully discovered, the Bluetooth Address can be used to send commands on this lock.

Don't store Bluetooth address

Bluetooth address may be obfuscated by Android for privacy reasons, so it's recommended not to store it for later use or on a different smartphone.

BleLock tapkeyBLELock; // The discovered ble lock
String bluetoothAddress = tapkeyBLELock.getBluetoothAddress();
String physicalLockId = tapkeyBLELock.getPhysicalLockId();

// Execute the command on the Bluetooth address with the help of the `BleLockCommunicator`
return bleLockCommunicator.executeCommandAsync(bluetoothAddress, physicalLockId, COMMAND_FUNC, CANCELLATIONTOKEN)
    ...

Custom Environments

This feature allows third parties to decouple physical locks from the Tapkey environment. As a result, locks with custom environments are only visible within the same environment and remain inaccessible to apps using default or different environments.

Agreement required

Custom environments require that the lock has been manufactured for this specific environment upfront. Therefore, it requires a fitting use case and a special agreement.

The TapkeyBleAdvertisingFormatBuilder can be used to configure and build the TapkeyBleAdvertisingFormat for the environment. The specific values for the configuration will be assigned and provided by Tapkey

TapkeyBleAdvertisingFormat bleAdvertisingFormat = new TapkeyBleAdvertisingFormatBuilder()
    .addV1Format(ENVIRONMENT_SPECIFIC_SERVICE_UID)
    .addV2Format(ENVIRONMENT_SPECIFIC_DOMAIN_ID)
    .build();

Once the TapkeyBleAdvertisingFormat is built, it needs to be registered to the TapkeyServiceFactoryBuilder during the bootstrap:

TapkeyServiceFactory tapkeyServiceFactory = new TapkeyServiceFactoryBuilder(this)
    ...
    .setBluetoothAdvertisingFormat(bleAdvertisingFormat)
    .build();