Integrating the Tapkey Mobile SDK for Android

Latest Version

The latest version of the Tapkey Mobile SDK for Android is 2.5.16.0

Warning

This documentation refers to version 2.3.x.x and later. This documentation may not be appropriate for older versions. Take a look at the upgrade section for upgrading to the latest version of the Tapkey Mobile SDK.

Sample App

A Tapkey Mobile SDK sample app for Android is available on GitHub.

Adding Dependencies

The Tapkey Mobile SDK is published in a private Maven repository. To tell Gradle where to find the artifacts, add https://maven.tapkey.com as repository and add the latest version of com.tapkey.android:Tapkey.MobileLib as dependency to the project's build.gradle file.

repositories {
    maven { url "https://maven.tapkey.com" }
}

dependencies {
    implementation 'com.tapkey.android:Tapkey.MobileLib:2.5.x.x'
}

android {
    
}


Bootstrapping the SDK

The Tapkey Mobile SDK expects the Application class of the target App to implement the TapkeyAppContext interface, which returns a singleton instance of the AndroidTapkeyServiceFactory. The singleton should be created in the Application.onCreate() method using the TapkeyServiceFactoryBuilder. As the Tapkey Mobile SDK needs to communicate with the Tapkey Trust Service, the android.permission.INTERNET permission has to be added to the App Manifest.

  1. Let the Application implement TapkeyAppContext.
  2. Use TapkeyServiceFactoryBuilder to build an instance of TapkeyServiceFactory in Application.onCreate().
  3. Implement getTapkeyServiceFactory() which returns the TapkeyServiceFactory singleton.

public class App extends Application implements TapkeyAppContext {

    /*
     * The TapkeyServiceFactory holds all required services.
     */
    private TapkeyServiceFactory tapkeyServiceFactory;

    @Override
    public void onCreate() {
        super.onCreate();

        /*
         * Create an instance of TapkeyServiceFactory. Tapkey expects that a single instance of
         * TapkeyServiceFactory exists inside an application that can be retrieved via the
         * Application instance's getTapkeyServiceFactory() method.
         */
        TapkeyServiceFactoryBuilder b = new TapkeyServiceFactoryBuilder(this);
        this.tapkeyServiceFactory = b.build();
    }

    @Override
    public TapkeyServiceFactory getTapkeyServiceFactory() {
        return tapkeyServiceFactory;
    }
}
4. Add <uses-permission android:name="android.permission.INTERNET"/> to AndroidManifest.xml.

<manifest  >    <uses-permission android:name="android.permission.INTERNET"/>
    <application  ></manifest>

Polling for Data

To allow unlocking during the device has bad or no network connection, it is essential to frequently fetch and cache data.

Tapkey provides an Android BroadcastReceiver that automatically caches such data frequently in background. To enable this feature, PollingScheduler.register() has to be called in Application.onCreate().

public class App extends Application implements TapkeyAppContext{

    @Override
    public void onCreate() {
        super.onCreate();

        

        /*
         * Register polling scheduler.
         * The PollingScheduler polls for notifications from the Tapkey Trust Service. Note that
         * the job ID must be unique across the app. The default interval is 8 hours.
         */
        PollingScheduler.register(this, 1, PollingScheduler.DEFAULT_INTERVAL);
     }
}

Using the SDK's PollingScheduler class requires the android.permission.RECEIVE_BOOT_COMPLETED permission. Otherwise, background polling won't work after the device reboots.

<manifest  >    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
    <application  ></manifest>

While the PollingScheduler is periodically looking for updated keys, it is recommended to poll for updates whenever updates are expected. NotificationManager#pollForNotificationsAsync() can be used to manually check for updates. Manually checking for updated keys is recommended in the following situations:

  • After a user has been logged in to the SDK.
  • After a new grant has been issued for a logged-in user.
  • After a grant that affects the current user has been modified or revoked.
  • After grants, affecting locks that one of the logged-in users has access to, have been revoked. The logged-in users might receive updated revocation information.

Logging In Users

The SDK takes a Tapkey access token to log in a user. Retrieving such an access token is part of the implementing application. Usually, either the Token Exchange or Authentication Code with PKCE grant type will be used. For more information about authenticating against Tapkey, take a look at the authentication section.

Once an access token has been obtained, it can be used to log in a user as outlined below:

UserManager userManager = tapkeyServiceFactory.getUserManager();
userManager.logInAsync("eyJhbGciOiJSUz…O-YbBq8F7086rQi-kEbERp4dA3r0WonpHnmYcXEnA", ct)
    .continueOnUi(userId -> {
        // User logged in, use user's ID here.
    });

Subsequent calls to userManager.getUsers() will yield a list of IDs of all logged-in users. In most applications, only one user will be logged in at a time.

String userId = userManager.getUsers().get(0);
// Fetch information about the user using the ID

Note that a token refresh handler must be registered with the TapkeyServiceFactory prior to logging in any users. This can be done using the TapkeyServiceFactoryBuilder's setTokenRefreshHandler(TokenRefreshHandler tokenRefreshHandler) method.

// In Application.onCreate():
TapkeyServiceFactoryBuilder b = new TapkeyServiceFactoryBuilder();
b.setTokenRefreshHandler(new TokenRefreshHandler() {
    @Override
    public Promise<String> refreshAuthenticationAsync(String userId, CancellationToken ct) {
        PromiseSource res = new PromiseSource();

        asynchronouslyUpdateTheAccessTokenViaCustomAppLogic(
            newAccessToken -> res.setResult(newAccessToken),
            exception -> {
                if (isPersistentError(exception))
                    throw new TkException (AuthenticationHandlerErrorCodes.TokenRefreshFailed);
                else
                    res.setException(exception);
            }
        );

        return res.getPromise();
    }

    @Override
    public void onRefreshFailed(String userId) {
        /*
         * Will be called if re-authentication failed and a manual call to
         * updateAccessToken() is required.
         */
    }
});
this.tapkeyServiceFactory = b.build(this);

Searching for Nearby Locks

Searching for nearby locks is done using the SDK's BleLockScanner.

The BleLockScanner class requires the android.permission.BLUETOOTH and android.permission.BLUETOOTH_ADMIN permissions. On Android devices running API level 23 and greater, android.permission.ACCESS_COARSE_LOCATION is additionally required to list nearby devices.

The permission android.permission.ACCESS_COARSE_LOCATION is a requirement imposed by Android. Scanning for nearby devices via BLE can be used to determine the position of the Android device. For more details, please consult the Android Documentation.

Add the following uses-permission entries to target app's AndroidManifest.xml:

<manifest  >    <uses-permission android:name="android.permission.BLUETOOTH"/>
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
    <uses-permission-sdk-23 android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <application  ></manifest>

An instance of BleLockScanner can be obtained from the TapkeyServiceFactory.

BleLockScanner scanner = tapkeyServiceFactory.getBleLockScanner();

// Returns an observer registration to stop scanning once finished.
bleScanObserverRegistration = scanner.startForegroundScan();

To observe locks entering and leaving the range of this device, the BleLockScanner's getLocksChangedObservable() method can be used.

// Listen for Tapkey locks coming into or leaving range.
nearbyLocksObserverRegistration = bleLockScanner.getLocksChangedObservable()
    .addObserver(locks -> {
        // Handle nearby locks here, e.g. present them to the user.
    });

// Alternatively, getLocks() can be used to get a list of nearby locks in this moment.
nearbyLocks = bleLockScanner.getLocks();

Make sure to stop scanning if the app does not required information about nearby locks anymore:

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

Triggering a Lock

After a nearby lock was found with the BleLockScanner, it can be opened using the trigger lock command, which is available from the CommandExecutionFacade. Any command inside the command execution facade, requires a TLCP connection to a lock. The following example uses BleLockCommunicator to establish a TCLP connection via BLE.

// The BLE lock manager establishes a connection to the lock which is passed to the
// CommandExecutionFacade to execute a trigger lock command.
return bleLockCommunicator.executeCommandAsync(
    bluetoothAddress, physicalLockId, tlcpConnection -> {
    // With the TlcpConnection to the lock established, the CommandExecutionFacade
    // asynchronously executes the trigger lock command.
    return commandExecutionFacade.triggerLockAsync(tlcpConnection, ct);
}, ct)
.continueOnUi(commandResult -> {
    switch (commandResult.getCommandResultCode()) {
        case Ok:
            return true;

        // Other cases must be handled here.
        
    }

    // Let the user know, something went wrong.
    
    return false;
})
.catchOnUi(e -> {
    Log.e(TAG, "Could not execute trigger lock command.", e);

    if (!ct.isCancellationRequested()) {
        // Handle any exceptions and let the user know, something went wrong.
        
    }
    return false;
});