Usage (with PowerAuth)
Integrating the Wultra Activation Spawn SDK for Android into your application is a straightforward process, designed to be developer-friendly and efficient. However, to ensure seamless functionality, it is crucial to maintain consistent configuration settings across both the target and source applications. This consistency is essential for the SDK to operate correctly and securely.
Naming conventions
Explanation | |
---|---|
Activation | PowerAuth activation inside the PowerAuthSDK instance in which the user enrolled. |
Activation Data | Data retrieved from the server by the Source App that can activate Target App. |
Source App | Application that is starting the activation spawn process with a valid Activation. |
Target App | Application that will be installed (if not already) and activated. |
Integrating into the Source App
Prerequisite of an application that will activate the Target App is properly configured and activated
PowerAuthSDK
instance.
- Define the Target App in your code:
import com.wultra.android.activationspawn.SpawnableApplication
val application = SpawnableApplication(
// package name of the app
"com.mycompany.android.application.demoapp",
// url deeplink scheme
"demoAppScheme",
// needs to be provided by the powerauth backend administrator at your company
"DEMOAPP"
)
- If your application targets Android 11+ (SDK 30+), you need to declare the query “permissions”
for the package name of Target App in the
AndroidManifest.xml
of the Source App. Otherwise, you won’t be able to detect if the app is already installed.
For more information, visit official documentation
Enable queries inside the Android manifest.
You can also enable all queries inside the Android manifest. This approach is not recommended if not needed otherwise.
- Integrate into your
ViewModel
.
-
Sample implementation:
import android.content.Context import androidx.lifecycle.ViewModel import com.wultra.android.activationspawn.ActivationSpawnData import com.wultra.android.activationspawn.IRetrieveActivationDataListener import com.wultra.android.activationspawn.SpawnableApplication import com.wultra.android.activationspawn.Transporter import com.wultra.android.activationspawn.TransporterConfig import com.wultra.android.activationspawn.createActivationSpawnActivator import com.wultra.android.powerauth.networking.error.ApiError import com.wultra.android.powerauth.networking.ssl.SSLValidationStrategy import io.getlime.security.powerauth.sdk.PowerAuthAuthentication import io.getlime.security.powerauth.sdk.PowerAuthSDK class SampleViewModel(powerAuth: PowerAuthSDK, appContext: Context): ViewModel() { // Additional data for transporter (must be the same for both Source and Target App). var additionalData: ByteArray? = null // Additional data for the app (must be the same for both Source and Target App). var sharedInfo: ByteArray? = null // application that can be activated val application = SpawnableApplication("com.mycompany.myapp", "myappdeeplink", "123") // Note that the transportr configuration must be the same // for both Target and Source App. Please consult with Wultra // what configuration suits your needs. private val transporterConfig = TransporterConfig.semiStable(false, 10) // activator that retrieves activation code from the server private val activator = powerAuth.createActivationSpawnActivator(appContext, SSLValidationStrategy.default(), "https://your-domain.com/your-app") // transporter that launches the transport process private val transporter = Transporter(appContext, transporterConfig, additionalData) fun isAppInstalled(): Boolean { return transporter.isInstalled(application) } fun installApp() { // SpawnManager will open store with the application page. return transporter.openStore(application) } fun activationApp() { // You need to authenticate the user with 2-factor scheme. // Note that for this you need to prompt the user for PIN code/password // or use biometry with "auth.useBiometry = true". // For demo purposes, we assume that the user has pin 1234. val auth = PowerAuthAuthentication.possessionWithPassword("1234") activator.retrieveActivationData(application, auth, object : IRetrieveActivationDataListener { override fun onSuccess(data: ActivationSpawnData) { try { val tag = "User123" // tag that will be transported along with the activation data val annotation = "MyDemoApp" // to tell the target app who opened it // Send it to the Target App. This might prompt the user if he wants to // open the application. transporter.transportDataToApp(data, application, tag, annotation, sharedInfo) } catch (t: Throwable) { // process transport exception } } override fun onError(error: ApiError) { // show error to user } }) } }
Integrating into the Target App
- Declare a URL scheme for the application to enable digesting the deeplink.
-
Retrieve the activation data from a deeplink and prepare the PowerAuth activation.
import android.app.Activity import android.os.Bundle import com.wultra.android.activationspawn.* import io.getlime.security.powerauth.networking.response.CreateActivationResult import io.getlime.security.powerauth.networking.response.ICreateActivationListener import io.getlime.security.powerauth.sdk.PowerAuthSDK class TestActivity: Activity() { // dependencies you need to prepare private lateinit var processor: Processor private lateinit var powerAuth: PowerAuthSDK // Note that the transporter configuration must be the same // for both Target and Source App. Please consult with Wultra // what configuration suits your needs. private val transporterConfig = TransporterConfig.semiStable(false, 10) // Additional data for transporter (must be the same for both Source and Target App). private val additionalData: ByteArray? = null // Additional data for the app (must be the same for both Source and Target App). private val sharedInfo: ByteArray? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) processor = Processor(applicationContext, transporterConfig, additionalData) val data = intent.data if (data != null) { try { val annotation = processor.validateDeeplink(data).getOrNull()?.annotation val transportData = processor.processDeeplink(data, sharedInfo) if (annotation == "MyDemoApp") { // do something based on the source app } if (transportData.tag != null) { // do something with the tag data } powerAuth.createActivation(ActivationSpawnData(transportData.activationCode, transportData.otp), "Simon's phone", object : ICreateActivationListener { override fun onActivationCreateSucceed(result: CreateActivationResult) { // continue with the activation } override fun onActivationCreateFailed(t: Throwable) { // process the error } }) } catch (t: Throwable) { // deeplink cannot be processed } } } }
Passing additional data
For your convenience, you can pass additional data from the Source App to the Target App. There are 2 different types of custom string data that you can pass:
annotation
Annotation is a string in the deeplink and is accessible without the need to decrypt the data.
This is good for example if you want to pass some data that will help you with decoding, like the id of the Source App or other public data. Always assume that the annotation data can be logged as plain-text in the system console.
Example usage of annotation:
// In the source app:
transporter.transportDataToApp(data, application, null, "annotationData", sharedInfo)
// In the target app
processor.validateDeeplink(url)
.onSuccess {
// validated
Log.d("aspawn", it.annotation) // prints "annotationData"
}.onFailure {
// invalid activation spawn deeplink
}
tag
A tag is a string in the encrypted data and is accessible only after successful transport data decryption.
This is good for example if you want to pass some data that will help you during the activation process in the Target App, like an ID of the user or other sensitive data.
Example usage of tag:
// In the source app:
transporter.transportDataToApp(data, application, "user1", null, sharedInfo)
// In the target app
val data = processor.processDeeplink(url)
Log.d("aspawn", data.tag) // prints "user1"
Error handling and logging
If you have a problem with syncing your Source and Target App configuration, we recommend turning on debug logging by ActivationSpawnLogger.verboseLevel = ActivationSpawnLogger.VerboseLevel.DEBUG
Exceptions
All methods that can produce an error are throwing various exceptions (as described in the in-code documentation).
All exceptions use message
property where details about the exception and possible solutions are explained (in the description
property). We highly recommend logging these exceptions into your log system as they provide great debug value about what went wrong.
ProcessDeeplinkException
and TransportDataException
exceptions contain reason
property with the enum value of the error description for convenience.
Logging
The library is intensively logging into the console via ActivationSpawnLogger
.
Possible log levels:
DEBUG
- Debug logging that outputs more granular data, use this only during developmentINFO
- prints info logs + logs for warning level produced by the library (default level)WARNING
- prints warnings and errorsERROR
- prints errors onlyOFF
- logging is turned off
You can set the log level by ActivationSpawnLogger.verboseLevel = ActivationSpawnLogger.VerboseLevel.OFF
.
ActivationSpawnLogger
calls internally android.util.Log
class.
Log Listener
The ActivationSpawnLogger
class offers a static logListener
property. If you provide a listener, all logs will also be passed to it (the library always logs into the Android default log).
Log listener comes in handy when you want to log into a file or some online service.