Skip to content

Getting started! - Android

Prerequisites

Android Studio

Android API Level 21 or higher

flinkey API

  • Customer ID
  • SDK key
  • Subscription key

flinkey Box

flinkey user ID

Step-by-Step Guide

This documents guides you though the basic integration steps of the Tapkey Mobile SDK. At the end of this guide you should be able to trigger (open/close) your flinkey Box. In addition there are more advanced concepts like the management of digital keys and offline support which will be addressed in depth in another document.

1. Library dependencies

There are two libraries to be added as dependencies to your Android project: The Tapkey Mobile SDK and the WITTE Mobile Library. The Tapkey Mobile SDK comprises everything in the scope of a mobile application to operate the flinkey Box. The WITTE Mobile Library offers a set of utility and convenience extensions used in context with the Tapkey Mobile SDK.

1.1. Tapkey Mobile SDK

The Tapkey Mobile SDK is distributed through Tapkey's own maven repository server. Before you can reference the Tapkey Mobile SDK the corresponding maven repository needs to be added to the list of repositories in the gradle.build file.

repositories {
    /**
    *  Maven repository for the Tapkey SDK.
    */
    maven { url "https://maven.tapkey.com" }
}

Add the Tapkey.Mobile.Lib library reference to your gradle.build file.

dependencies {
    api 'com.tapkey.android:Tapkey.MobileLib:2.4.0.0'
    implementation 'net.openid:appauth:0.7.1'
}

1.2. WITTE Mobile Library

The WITTE Mobile library is distributed via JCenter which is one of the default repositories configured in any new Android Studio project.

buildscript {
    repositories {
        google()
        jcenter()
    }
}

Add the witte-mobile-library reference to your gradle.build file.

dependencies {
    implementation 'digital.witte:witte-mobile-library:1.0.0'
}

2. Configuration

Create a configuration instance using your WITTE customer ID, SDK key and subscription key. This instance will be referenced in source code examples of following steps.

import digital.witte.wittemobilelibrary.Configuration;

public class App extends Application {
    final static Configuration Config = new Configuration(
        CustomerId,
        SdkKey,
        SubscriptionKey);

    // ...
}

3. AndroidTapkeyServiceFactory

The AndroidTapkeyServiceFactory is the central object of the Tapkey Mobile SDK. It creates and provides access to additional objects needed for authentication, management of digital keys, communication with the flinkey Box and others.

public class App extends Application {
    private AndroidTapkeyServiceFactory mTapkeyServiceFactory;

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

        TapkeyEnvironmentConfig tapkeyEnvironmentConfig = new TapkeyEnvironmentConfigBuilder(this)
                .setBleServiceUuid(Configuration.BleServiceUuid)
                .setTenantId(Configuration.TenantId)
                .build();

        mTapkeyServiceFactory = new TapkeyServiceFactoryBuilder(this)
                .setConfig(tapkeyEnvironmentConfig)
                .setTokenRefreshHandler(new WitteTokenRefreshHandler(new WitteTokenProvider(this, WitteConfiguration, WitteUserId)))
                .build();

        // ...
    }
}

4. Implement the TapkeyAppContext interface

Implementing the TapkeyAppContext is actually optional but we strongly recommend doing so as it is required for the background synchronisation of digital keys when conducted by the Tapkey Mobile SDK.

public class App extends Application implements TapkeyAppContext {
    private TapkeyServiceFactory mTapkeyServiceFactory;

    // ...

    @Override
    public TapkeyServiceFactory getTapkeyServiceFactory() {
        return mTapkeyServiceFactory;
    }
}

5. Access Token

The access token is retrieved from the Tapkey backend by exchanging an idToken which needs to be retrieved from the WITTE backend for a specific user. 1. Retrieve idToken from the Witte backen 2. Retrieve access token from the Tapkey backend by exchanging the idToken

public class WitteTokenProvider {

    private final static String AuthConfigScheme = "https";
    private final static String AuthConfigAuthority = "login.tapkey.com";

    private final static String TokenExchangeClientId = "wma-native-mobile-app";
    private final static String TokenExchangeGrantType = "http://tapkey.net/oauth/token_exchange";

    private final static String TokenExchangeScopeRegisterMobiles = "register:mobiles";
    private final static String TokenExchangeScopeReadUser = "read:user";
    private final static String TokenExchangeScopeHandleKeys = "handle:keys";

    private final static String TokenExchangeParamProviderKey = "provider";
    private final static String TokenExchangeParamSubjectTokenTypeKey = "subject_token_type";
    private final static String TokenExchangeParamSubjectTokenKey = "subject_token";
    private final static String TokenExchangeParamAudienceKey = "audience";
    private final static String TokenExchangeParamRequestedTokenTypeKey = "requested_token_type";

    private final static String TokenExchangeParamProviderValue = "wma.oauth";
    private final static String TokenExchangeParamSubjectTokenTypeValue = "jwt";
    private final static String TokenExchangeParamAudienceValue = "tapkey_api";
    private final static String TokenExchangeParamRequestedTokenTypeValue = "access_token";

    private final Context _context;
    private final Configuration _witteConfiguration;
    private final int _witteUserId;

    public WitteTokenProvider(Context context, Configuration witteConfiguration, int witteUserId) {
        _context = context;
        _witteConfiguration = witteConfiguration;
        _witteUserId = witteUserId;
    }

    public Promise<String> AccessToken() {
        PromiseSource<String> promiseSource = new PromiseSource<>();

        Async.executeAsync(() -> {
            // retrieve WITTE idToken
            IdTokenRequest request = new IdTokenRequest();
            String idToken = request.execute(_witteConfiguration, _witteUserId);
            return idToken;

        }).continueOnUi(idToken -> {
            // retrieve Tapkey access token
            Uri.Builder builder = new Uri.Builder();
            Uri authorizationServer = builder
                    .scheme(AuthConfigScheme)
                    .encodedAuthority(AuthConfigAuthority)
                    .build();

            AuthorizationServiceConfiguration.fetchFromIssuer(authorizationServer, (serviceConfiguration, ex) -> {
                if(null != ex) {
                    promiseSource.setException(ex);
                }
                else {
                    TokenRequest.Builder tokenRequestBuilder =
                            new TokenRequest.Builder(serviceConfiguration, TokenExchangeClientId)
                                    .setCodeVerifier(null)
                                    .setGrantType(TokenExchangeGrantType)
                                    .setScopes(new HashSet<String>() {{
                                        add(TokenExchangeScopeRegisterMobiles);
                                        add(TokenExchangeScopeReadUser);
                                        add(TokenExchangeScopeHandleKeys);
                                    }})
                                    .setAdditionalParameters(new HashMap<String, String>() {{
                                        put(TokenExchangeParamProviderKey, TokenExchangeParamProviderValue);
                                        put(TokenExchangeParamSubjectTokenTypeKey, TokenExchangeParamSubjectTokenTypeValue);
                                        put(TokenExchangeParamSubjectTokenKey, idToken);
                                        put(TokenExchangeParamAudienceKey, TokenExchangeParamAudienceValue);
                                        put(TokenExchangeParamRequestedTokenTypeKey, TokenExchangeParamRequestedTokenTypeValue);
                                    }});

                    TokenRequest tokenRequest = tokenRequestBuilder.build();

                    AuthorizationService authService = new AuthorizationService(_context);
                    authService.performTokenRequest(tokenRequest, (response, ex1) -> {
                        if(null != ex1) {
                            promiseSource.setException(ex1);
                        }
                        else {
                            promiseSource.setResult(response.accessToken);
                        }
                    });
                }
            });

            return null;
        }).conclude();

        return promiseSource.getPromise();
    }
}

6. Login

Once you have retrieved an access token you are ready to authenticate with the Tapkey Mobile SDK. Authentication is done using the UserManager instance which is available though the TapkeyServiceFactory.

AndroidTapkeyServiceFactory serviceFactory = 
    ((App) getActivity().getApplication()).getTapkeyServiceFactory();

// authenticate a user with the Tapkey Mobile SDK
UserManager userManager = serviceFactory.getUserManager();
userManager.logInAsync(accessToken, CancellationTokens.None)
    .continueOnUi(userId -> {
        // login successful
    })
    .catchOnUi(e -> {
        // login failed
        return null;
    });

6. Install a token refresh handler for re-authentication

Once a user is authenticated with the Tapkey Mobile SDK the Tapkey Mobile SDK will need to reauthenticate from time to time. Therefore we need a custom identity provider that retrieves an idToken and hands this token over to the Tapkey Mobile SDK as requested.

public class WitteTokenRefreshHandler implements TokenRefreshHandler {

    private final WitteTokenProvider _witteTokenProvider;

    public WitteTokenRefreshHandler(WitteTokenProvider witteTokenProvider) {
        _witteTokenProvider = witteTokenProvider;
    }

    @Override
    public Promise<String> refreshAuthenticationAsync(String s, CancellationToken cancellationToken) {
        return _witteTokenProvider.AccessToken();
    }

    @Override
    public void onRefreshFailed(String s) {
    }
}
public class App extends Application implements TapkeyAppContext {

    // ...

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

        // ...

        _tapkeyServiceFactory = new TapkeyServiceFactoryBuilder(this)
                .setConfig(tapkeyEnvironmentConfig)
                .setTokenRefreshHandler(new WitteTokenRefreshHandler(new WitteTokenProvider(this, WitteConfiguration, WitteUserId)))
                .build();

        // ...
    }
}

8. Scanning

A flinkey Box is an BLE device. It is not neccessary to pair the flinkey Box with the mobile device but the Tapkey Mobile SDK needs to scan and eventually find flinkey Boxes nearby before any connection can be made in order to operate the flinkey Box.

AndroidTapkeyServiceFactory serviceFactory = 
    ((App) getActivity().getApplication()).getTapkeyServiceFactory();

BleLockScanner bleLockScanner = serviceFactory.getBleLockScanner();

// start scanning for flinkey Boxes
if(null == _foregroundScanRegistration){
    _foregroundScanRegistration = _bleLockScanner.startForegroundScan();
}

// . . .

// stop scanning for flinkey Boxes
if(null != _foregroundScanRegistration){
    _foregroundScanRegistration.close();
    _foregroundScanRegistration = null;
}

9. Triggering

Triggering a flinkey Box means to toggle its state from opened to closed or vice versa. There are no methods to query the current state of the box (opened or closed) or command the box to open or close. Instead there is the triggerLockAsync method which toggles the state of the flinkey Box and delivers a CommandResult object as result of the method call. The CommandResult object can be evaluated to determine the current state of the flinkey Box.

// the flinkey Box ID as printed on the label of the box
String boxId = "A1-B2-C3-D4";

// convert the Box ID to a physical lock ID for use with Tapkey Mobile SDK 
String physicalLockId = BoxIdConverter.toPhysicalLockId(boxId);

AndroidTapkeyServiceFactory serviceFactory = 
    ((App) getActivity().getApplication()).getTapkeyServiceFactory();

CommandExecutionFacade commandExecutionFacade = serviceFactory.getCommandExecutionFacade();
BleLockCommunicator bleBleLockCommunicator = serviceFactory.getBleLockCommunicator();
BleLockScanner bleLockScanner = serviceFactory.getBleLockScanner();

// trigger a flinkey Box
final int timeoutInMs = 60 * 1000;
CancellationToken timeout = CancellationTokens.fromTimeout(timeoutInMs);

String bluetoothAddress = bleLockScanner.getLock(physicalLockId).getBluetoothAddress();
_bleBleLockCommunicator.executeCommandAsync(bluetoothAddress, physicalLockId, tlcpConnection -> _commandExecutionFacade.triggerLockAsync(tlcpConnection, timeout), timeout)
    .continueOnUi(commandResult -> {
        if (commandResult.getCommandResultCode() == CommandResult.CommandResultCode.Ok) {
            Toast.makeText(getContext(), "triggerLock successful", Toast.LENGTH_SHORT).show();

            // get the 10 byte box feedback from the command result
            Object object = commandResult.getResponseData();
            if (object instanceof byte[]) {

                byte[] responseData = (byte[]) object;
                try {
                    BoxFeedback boxFeedback = BoxFeedback.create(responseData);
                    int boxState = boxFeedback.getBoxState();
                    if (BoxState.UNLOCKED == boxState) {

                        Log.d(TAG, "Box has been opened");
                    }
                    else if (BoxState.LOCKED == boxState) {

                        Log.d(TAG, "Box has been closed");
                    }
                    else if (BoxState.DRAWER_OPEN == boxState) {

                        Log.d(TAG, "The drawer of the Box is open.");
                    }
                }
                catch (IllegalArgumentException iaEx) {

                    Log.e(TAG, iaEx.getMessage());
                }
            }
            return true;
        }
        else {
            Toast.makeText(getContext(), "triggerLock error", Toast.LENGTH_SHORT).show();
            return false;
        }
    })
    .catchOnUi(e -> {
        Toast.makeText(getContext(), "triggerLock exception", Toast.LENGTH_SHORT).show();
        return false;
    });

10. Finish

At this point you should be able to actually trigger your flinkey Box in order to open and close it.