Skip to content

Android Integration

This guide covers the Android-specific integration of the Tapkey Mobile SDK for local vehicle access via BLE.

  • Partner backend integration is available (assignments + webhooks)
  • SDK Key available in the flinkey Portal (UAT)
  • Mobile access context can be requested from your backend
  • Real Android test devices available (do not rely only on emulators for BLE)
Library Purpose
Tapkey Mobile SDK Mobile key handling, SDK login, BLE access flow
WITTE Mobile Library Box ID conversion, Box Command data, box feedback parsing
AppAuth for Android OAuth / token exchange
repositories {
maven { url "https://maven.tapkey.com" }
maven {
url "https://maven.pkg.github.com/WITTE-Digital/witte-mobile-library-for-android"
credentials {
username = project.findProperty("gpr.user") ?: System.getenv("GITHUB_PACKAGES_USERNAME")
password = project.findProperty("gpr.key") ?: System.getenv("GITHUB_PACKAGES_TOKEN")
}
}
mavenCentral()
}
dependencies {
implementation "com.tapkey.android:Tapkey.MobileLib:$tapkeyMobileSdkVersion"
implementation "digital.witte:witte-mobile-library:$witteMobileLibraryVersion"
implementation "net.openid:appauth:$appAuthVersion"
}

Sample resources: witte-mobile-sample-android | witte-mobile-library-for-android

<uses-permission android:name="android.permission.INTERNET" />
<!-- Legacy Bluetooth (Android ≤ 11) -->
<uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" android:maxSdkVersion="30" />
<!-- Android 12+ -->
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<!-- Location required for BLE on older versions -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" android:maxSdkVersion="30" />
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true" />

Request runtime permissions before scanning. Handle denied permissions gracefully — missing BLE permissions may cause scanning to return no results without an obvious error.

sequenceDiagram
    autonumber
    actor customer as End customer
    participant app as Android app
    participant backend as Partner backend
    participant api as flinkey API
    participant tapkey as Tapkey Trust Service
    participant sdk as Tapkey Mobile SDK

    customer->>app: Sign in
    app->>backend: Request mobile access context
    backend->>api: Get IdToken
    api-->>backend: Return IdToken
    backend-->>app: Return IdToken
    app->>tapkey: Token exchange via AppAuth
    tapkey-->>app: Tapkey access token
    app->>sdk: SDK login
String TOKEN_EXCHANGE_CLIENT_ID = "wma-native-mobile-app";
String TOKEN_EXCHANGE_GRANT_TYPE = "http://tapkey.net/oauth/token_exchange";
// Scopes
String SCOPE_REGISTER_MOBILES = "register:mobiles";
String SCOPE_READ_USER = "read:user";
String SCOPE_HANDLE_KEYS = "handle:keys";
// Additional parameters
"provider" -> "wma.oauth"
"subject_token_type" -> "jwt"
"subject_token" -> idToken
"audience" -> "tapkey_api"
"requested_token_type" -> "access_token"

Create one application-level TapkeyServiceFactory:

public class App extends Application {
private TapkeyServiceFactory tapkeyServiceFactory;
@Override
public void onCreate() {
super.onCreate();
TapkeyEnvironmentConfig config =
new TapkeyEnvironmentConfigBuilder()
.setTenantId(Configuration.TenantId)
.build();
TapkeyBleAdvertisingFormat bleFormat =
new TapkeyBleAdvertisingFormatBuilder()
.addV1Format(Configuration.BleAdvertisingFormatV1)
.addV2Format(Configuration.BleAdvertisingFormatV2)
.build();
tapkeyServiceFactory = new TapkeyServiceFactoryBuilder(this)
.setConfig(config)
.setBluetoothAdvertisingFormat(bleFormat)
.setTokenRefreshHandler(new TokenRefreshHandler() {
@Override
public Promise<String> refreshAuthenticationAsync(
String tapkeyUserId, CancellationToken ct) {
// Request fresh token through partner backend.
return null;
}
@Override
public void onRefreshFailed(String tapkeyUserId) {
// Handle refresh failure.
}
})
.build();
}
}
UserManager userManager = tapkeyServiceFactory.getUserManager();
userManager
.logInAsync(accessToken, CancellationTokens.None)
.continueOnUi(userId -> {
// SDK login successful.
})
.catchOnUi(error -> {
// SDK login failed.
return null;
});
BleLockScanner bleLockScanner = tapkeyServiceFactory.getBleLockScanner();
// Start scanning
foregroundScanRegistration = bleLockScanner.startForegroundScan();
// Stop scanning when done
foregroundScanRegistration.close();

Start scanning only when needed and stop when the access flow ends.

CommandExecutionFacade commandFacade = tapkeyServiceFactory.getCommandExecutionFacade();
BleLockCommunicator communicator = tapkeyServiceFactory.getBleLockCommunicator();
BleLockScanner scanner = tapkeyServiceFactory.getBleLockScanner();
String boxId = "..."; // from mobile access context
String physicalLockId = BoxIdConverter.toPhysicalLockId(boxId);
String bluetoothAddress = scanner.getLock(physicalLockId).getBluetoothAddress();
CancellationToken timeout = CancellationTokens.fromTimeout(60_000);
communicator.executeCommandAsync(
bluetoothAddress, physicalLockId,
tlcpConnection -> {
TriggerLockCommand cmd = new DefaultTriggerLockCommandBuilder().build();
return commandFacade.executeStandardCommandAsync(tlcpConnection, cmd, timeout);
},
timeout
)
.continueOnUi(result -> { /* evaluate result */ })
.catchOnUi(error -> { /* handle error */ return null; });
Error User-facing Technical
Missing access context Ask to retry Check backend assignment flow
Token exchange failed Ask to retry Log sanitized error
Bluetooth disabled Ask to enable BT Do not start scanning
Permission missing Ask to grant Trigger runtime permission
Box not found Move closer to vehicle Stop scan after timeout
Command failed Show access failed Store sanitized diagnostics
  • App authenticates customer against partner backend
  • App requests and receives mobile access context
  • AppAuth token exchange succeeds
  • Tapkey SDK login succeeds
  • Token refresh handler is implemented
  • Required permissions declared and runtime-handled
  • BLE scanning starts and stops correctly
  • App discovers the flinkey BLE Box
  • Access command succeeds
  • Box feedback is handled
  • Bluetooth disabled / box not found states handled
  • No credentials, tokens or digital keys in logs
  • Real-device testing completed