Skip to content

Commit

Permalink
feat: android initialization also working
Browse files Browse the repository at this point in the history
Signed-off-by: Timo Glastra <[email protected]>
  • Loading branch information
TimoGlastra committed Jul 3, 2024
1 parent 4df861e commit c8b8d56
Show file tree
Hide file tree
Showing 7 changed files with 92 additions and 81 deletions.
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ An [Expo Module](https://docs.expo.dev/modules/overview/) and [Expo Config Plugi

## Getting Started

Install the plugin and `expo-build-properties` using the following command. We need `expo-build-properties` to set the `minSdkVersion` for Android to at least 26. If you're already on 26 or higher this package is not needed.
Install the plugin and `expo-build-properties` using the following command. We need `expo-build-properties` to set the `minSdkVersion` for Android to at least 26, and enable `useLegacyPackaging` (see [App Bundle](https://www.ausweisapp.bund.de/sdk/android.html#app-bundle) in Ausweis SDK documentation).

```sh
# yarn
Expand All @@ -69,7 +69,8 @@ Then add the plugin to your Expo app config (`app.json`, `app.config.json` or `a
"expo-build-properties",
{
"android": {
"minSdkVersion": 26
"minSdkVersion": 26,
"useLegacyPackaging": true
}
}
]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package id.animo.ausweissdk

import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.withTimeout
import android.app.Activity;
import android.nfc.Tag
import android.nfc.NfcAdapter
Expand All @@ -9,13 +11,18 @@ import android.content.Context
import android.content.Intent
import android.content.ServiceConnection
import android.os.IBinder
import android.os.RemoteException
import com.governikus.ausweisapp2.IAusweisApp2Sdk
import com.governikus.ausweisapp2.IAusweisApp2SdkCallback
import java.lang.Exception
import java.util.concurrent.CancellationException

class AusweisSdkConnectionHandler(private val context: Context) {
private var bound: Boolean = false
class AusweisSdkConnectionHandler() {
private var sdk: IAusweisApp2Sdk? = null
private var receiveMessageCallback: ((String?) -> Unit)? = null
private var initializationComplete = CompletableDeferred<Unit>()

val isInitialized: Boolean
get() = sdk != null && getSessionID() != null

private val nfcReaderCallback = NfcAdapter.ReaderCallback { pTag ->
if (pTag.techList.contains(IsoDep::class.java.name)) {
Expand All @@ -25,20 +32,18 @@ class AusweisSdkConnectionHandler(private val context: Context) {

private val connection = object : ServiceConnection {
override fun onServiceConnected(className: ComponentName, service: IBinder) {
try {
sdk = IAusweisApp2Sdk.Stub.asInterface(service)
connectToSdk()
} catch (e: ClassCastException) {
e.printStackTrace()
} catch (e: RemoteException) {
e.printStackTrace()
val _sdk = IAusweisApp2Sdk.Stub.asInterface(service)
val success = _sdk.connectSdk(callback)
if (!success) {
throw Exception("Error connecting to SDK. Possibly because the SDK is already initialized")
}
bound = true

initializationComplete.complete(Unit)
sdk = _sdk
}

override fun onServiceDisconnected(className: ComponentName) {
sdk = null
bound = false
}
}

Expand All @@ -47,105 +52,90 @@ class AusweisSdkConnectionHandler(private val context: Context) {

override fun sessionIdGenerated(pSessionId: String?, pIsSecureSessionId: Boolean) {
sessionID = pSessionId
// Handle session ID generation
}

override fun receive(pJson: String?) {
// Handle message from SDK
receiveMessageCallback?.let { it(pJson) }
}

override fun sdkDisconnected() {
// handle sdk disconnected
sessionID = null
}
}

fun bindService() {
suspend fun bindService(context: Context) {
if (sdk != null) {
throw Exception("Cannot bind service if already bound. Call unbindService first")
}

initializationComplete.cancel(CancellationException("Binding started"))
initializationComplete = CompletableDeferred()

val pkg = context.packageName
val name = "com.governikus.ausweisapp2.START_SERVICE"
val serviceIntent = Intent(name).apply {
setPackage(pkg)
}

context.bindService(serviceIntent, connection, Context.BIND_AUTO_CREATE)

// Wait for initialization to complete with timeout
withTimeout(10000L) {
initializationComplete.await()
}
}

fun registerCallback(callbackHandler: (String?) -> Unit) {
this.receiveMessageCallback = callbackHandler
}

/**
* Disconnect from SDK according to
* https://www.ausweisapp.bund.de/sdk/android.html#disconnect-from-sdk
*/
fun unbindService() {
if (bound) {
fun unbindService(context: Context) {
if (sdk != null) {
context.unbindService(connection)
bound = false
}
}

fun getSessionID(): String? {
private fun getSessionID(): String? {
return callback.sessionID
}

fun sendCommand(command: String) {
try {
val sessionID = getSessionID()
if (sessionID != null) {
val success = sdk?.send(sessionID, command) ?: false
if (!success) {
// Handle error: disconnected?
}
} else {
// Handle error: sessionID is null
}
} catch (e: RemoteException) {
e.printStackTrace()
val sessionID = getSessionID() ?: throw Exception("No open session")
val sdk = sdk ?: throw Exception("No SDK defined")

val success = sdk.send(sessionID, command)
if (!success) {
throw Exception("Error sending command to SDK")
}
}

fun updateNfcTag(tag: Tag): Boolean {
try {
val sessionID = getSessionID()
if (sessionID != null) {
val success = sdk?.updateNfcTag(sessionID, tag) ?: false
return success
} else {
return false
// Handle error: sessionID is null
}
} catch (e: RemoteException) {
e.printStackTrace()
return false
}
// if no session is active, we will return false, as we can't handle the tag
val sessionID = getSessionID() ?: return false
val sdk = sdk ?: throw Exception("No SDK defined. Unable to handle tag")

val success = sdk.updateNfcTag(sessionID, tag)
return success
}

fun enableDispatcher(activity: Activity) {
val flags =
NfcAdapter.FLAG_READER_NFC_A or NfcAdapter.FLAG_READER_NFC_B or NfcAdapter.FLAG_READER_SKIP_NDEF_CHECK

NfcAdapter.getDefaultAdapter(context)
NfcAdapter.getDefaultAdapter(activity.baseContext)
?.enableReaderMode(activity, nfcReaderCallback, flags, null)
}

fun disableDispatcher(activity: Activity) {
NfcAdapter.getDefaultAdapter(context)?.disableReaderMode(activity)
}

private fun connectToSdk() {
try {
sdk?.let {
if (!it.connectSdk(callback)) {
// Handle error: already connected?
}
}
} catch (e: RemoteException) {
e.printStackTrace()
}
NfcAdapter.getDefaultAdapter(activity.baseContext)?.disableReaderMode(activity)
}

companion object {
@Volatile
private var instance: AusweisSdkConnectionHandler? = null

fun getInstance(context: Context): AusweisSdkConnectionHandler =
instance ?: synchronized(this) {
instance ?: AusweisSdkConnectionHandler(context).also { instance = it }
}
var instance = AusweisSdkConnectionHandler()
}
}
34 changes: 28 additions & 6 deletions android/src/main/java/id/animo/ausweissdk/AusweisSdkModule.kt
Original file line number Diff line number Diff line change
@@ -1,22 +1,44 @@
package id.animo.ausweissdk

import expo.modules.kotlin.functions.Coroutine
import expo.modules.kotlin.modules.Module
import expo.modules.kotlin.modules.ModuleDefinition
import android.util.Log

class AusweisSdkModule : Module() {
private var connectionHandler = AusweisSdkConnectionHandler.instance.apply {
this.registerCallback { receivedValue: String? ->
Log.i("AusweisSdk", "Received message $receivedValue")

sendEvent("onMessage", mapOf(
"value" to receivedValue
))
}
}

override fun definition() = ModuleDefinition {
Name("AusweisSdk")
Events("onMessage")

Function("sendCommand") { command: String ->

if (!connectionHandler.isInitialized) {
throw IllegalStateException("SDK not initialized. Call initialize before sending commands")
}

connectionHandler.sendCommand(command)
}

AsyncFunction("initialize") { value: String ->
// Send an event to JavaScript.
sendEvent("onChange", mapOf(
"value" to value
))
AsyncFunction("initialize") Coroutine { ->
val context = appContext.reactContext?.applicationContext
?: throw IllegalStateException("React application context is not available")

if (connectionHandler.isInitialized) {
return@Coroutine
}

Log.i("AusweisSdk", "Binding Service")
connectionHandler.bindService(context)
return@Coroutine
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,7 @@ class AusweisSdkReactActivityLifecycleListener(private val context: Context) : R
override fun onNewIntent(intent: Intent): Boolean {
val tag: Tag? = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG)
if (tag != null) {
val connectionHandler = AusweisSdkConnectionHandler.getInstance(context)
return connectionHandler.updateNfcTag(tag)
return AusweisSdkConnectionHandler.instance.updateNfcTag(tag)
}

return false
Expand All @@ -27,14 +26,12 @@ class AusweisSdkReactActivityLifecycleListener(private val context: Context) : R
override fun onResume(activity: Activity) {
super.onResume(activity)

val connectionHandler = AusweisSdkConnectionHandler.getInstance(context)
connectionHandler.enableDispatcher(activity)
AusweisSdkConnectionHandler.instance.enableDispatcher(activity)
}

override fun onPause(activity: Activity) {
super.onPause(activity)

val connectionHandler = AusweisSdkConnectionHandler.getInstance(context)
connectionHandler.disableDispatcher(activity)
AusweisSdkConnectionHandler.instance.disableDispatcher(activity)
}
}
3 changes: 2 additions & 1 deletion ausweis-example/app.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
"expo-build-properties",
{
"android": {
"minSdkVersion": 26
"minSdkVersion": 26,
"useLegacyPackaging": true
}
}
]
Expand Down
1 change: 1 addition & 0 deletions ausweis-example/app/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export default function App() {
initializeSdk()
.then(() => setIsSdkInitialized(true))
.catch((e) => {
setIsSdkInitialized(true)
console.log('error setting up', e)
})
}, [])
Expand Down
1 change: 0 additions & 1 deletion src/AusweisSdkModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import type { NativeModule } from 'react-native'
interface AusweisSdk extends NativeModule {
sendCommand: (command: string) => void
initialize(): Promise<boolean>
asyncFunction: () => Promise<string>
}

// It loads the native module object from the JSI or falls back to
Expand Down

0 comments on commit c8b8d56

Please sign in to comment.