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.

  1. 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"
  )
  1. 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 in the manifest

Enable queries inside the Android manifest.

Enable all queries in the manifest

You can also enable all queries inside the Android manifest. This approach is not recommended if not needed otherwise.

  1. 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

  1. Declare a URL scheme for the application to enable digesting the deeplink.

Deeplink scheme settings

  1. 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 development
  • INFO - prints info logs + logs for warning level produced by the library (default level)
  • WARNING - prints warnings and errors
  • ERROR - prints errors only
  • OFF - 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.

Last updated on Jun 06, 2024 (09:34) Edit on Github Send Feedback
Search

develop

Activation Spawn SDK for Android