Android Integration
This guide covers the Android-specific integration of the Tapkey Mobile SDK for local vehicle access via BLE.
Prerequisites
Section titled “Prerequisites”- 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 dependencies
Section titled “Library dependencies”| 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 |
Gradle setup
Section titled “Gradle setup”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
Android manifest and permissions
Section titled “Android manifest and permissions”<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.
Authentication flow
Section titled “Authentication flow”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
Token exchange configuration
Section titled “Token exchange configuration”String TOKEN_EXCHANGE_CLIENT_ID = "wma-native-mobile-app";String TOKEN_EXCHANGE_GRANT_TYPE = "http://tapkey.net/oauth/token_exchange";
// ScopesString 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"SDK initialization
Section titled “SDK initialization”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(); }}SDK login
Section titled “SDK login”UserManager userManager = tapkeyServiceFactory.getUserManager();
userManager .logInAsync(accessToken, CancellationTokens.None) .continueOnUi(userId -> { // SDK login successful. }) .catchOnUi(error -> { // SDK login failed. return null; });BLE scanning
Section titled “BLE scanning”BleLockScanner bleLockScanner = tapkeyServiceFactory.getBleLockScanner();
// Start scanningforegroundScanRegistration = bleLockScanner.startForegroundScan();
// Stop scanning when doneforegroundScanRegistration.close();Start scanning only when needed and stop when the access flow ends.
Triggering vehicle access
Section titled “Triggering vehicle access”CommandExecutionFacade commandFacade = tapkeyServiceFactory.getCommandExecutionFacade();BleLockCommunicator communicator = tapkeyServiceFactory.getBleLockCommunicator();BleLockScanner scanner = tapkeyServiceFactory.getBleLockScanner();
String boxId = "..."; // from mobile access contextString 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 handling
Section titled “Error handling”| 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 |
Test checklist
Section titled “Test checklist”- 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
