Integrating the Tapkey Mobile SDK for Android

Warning

This documentation refers to version 2.4.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.4.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.

  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;
    }
}

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);
     }
}

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 can be done via the BleLockScanner. 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;
});