diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 65c64e538..d90b3f13e 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -35,6 +35,7 @@
android:screenOrientation="sensor"
android:supportsRtl="true"
android:theme="@style/Theme.App.Starting"
+ tools:replace="android:allowBackup,android:dataExtractionRules,android:fullBackupContent"
tools:targetApi="tiramisu">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
()
private val downloadDialogManager by inject()
private val calendarSyncScheduler by inject()
+ private val appsFlyerDeepLinkHandler by inject()
private val branchLogger = Logger(BRANCH_TAG)
@@ -114,6 +116,7 @@ class AppActivity : AppCompatActivity(), InsetHolder, WindowSizeHolder {
setupInitialFragment(savedInstanceState)
observeLogoutEvent()
observeDownloadFailedDialog()
+ observeAppsFlyerDeepLink()
calendarSyncScheduler.scheduleDailySync()
}
@@ -208,6 +211,14 @@ class AppActivity : AppCompatActivity(), InsetHolder, WindowSizeHolder {
}
}
+ private fun observeAppsFlyerDeepLink() {
+ lifecycleScope.launch {
+ appsFlyerDeepLinkHandler.deepLink.collect { deepLink ->
+ viewModel.makeExternalRoute(supportFragmentManager, deepLink)
+ }
+ }
+ }
+
override fun onStart() {
super.onStart()
diff --git a/app/src/main/java/org/openedx/app/OpenEdXApp.kt b/app/src/main/java/org/openedx/app/OpenEdXApp.kt
index 6524cde5d..ad0599e2f 100644
--- a/app/src/main/java/org/openedx/app/OpenEdXApp.kt
+++ b/app/src/main/java/org/openedx/app/OpenEdXApp.kt
@@ -9,6 +9,7 @@ import io.branch.referral.Branch
import org.koin.android.ext.android.inject
import org.koin.android.ext.koin.androidContext
import org.koin.core.context.startKoin
+import org.openedx.app.deeplink.AppsFlyerDeepLinkHandler
import org.openedx.app.deeplink.BranchBrazeDeeplinkHandler
import org.openedx.app.di.appModule
import org.openedx.app.di.networkingModule
@@ -20,6 +21,7 @@ class OpenEdXApp : Application() {
private val config by inject()
private val pluginManager by inject()
+ private val appsFlyerDeepLinkHandler by inject()
override fun onCreate() {
super.onCreate()
@@ -44,6 +46,10 @@ class OpenEdXApp : Application() {
Branch.getAutoInstance(this)
}
+ if (config.getAppsFlyerConfig().enabled) {
+ appsFlyerDeepLinkHandler.init(this, config.getAppsFlyerConfig().devKey)
+ }
+
if (config.getBrazeConfig().isEnabled && config.getFirebaseConfig().enabled) {
val isCloudMessagingEnabled = config.getFirebaseConfig().isCloudMessagingEnabled &&
config.getBrazeConfig().isPushNotificationsEnabled
diff --git a/app/src/main/java/org/openedx/app/deeplink/AppsFlyerDeepLinkHandler.kt b/app/src/main/java/org/openedx/app/deeplink/AppsFlyerDeepLinkHandler.kt
new file mode 100644
index 000000000..0a369ee6d
--- /dev/null
+++ b/app/src/main/java/org/openedx/app/deeplink/AppsFlyerDeepLinkHandler.kt
@@ -0,0 +1,60 @@
+package org.openedx.app.deeplink
+
+import android.content.Context
+import com.appsflyer.AppsFlyerLib
+import com.appsflyer.deeplink.DeepLinkListener
+import com.appsflyer.deeplink.DeepLinkResult
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.SharedFlow
+import kotlinx.coroutines.flow.asSharedFlow
+import org.json.JSONObject
+import org.openedx.core.utils.Logger
+
+class AppsFlyerDeepLinkHandler {
+
+ private val logger = Logger(TAG)
+
+ private val _deepLink = MutableSharedFlow(replay = 1, extraBufferCapacity = 1)
+ val deepLink: SharedFlow = _deepLink.asSharedFlow()
+
+ fun init(context: Context, devKey: String) {
+ val appsFlyerLib = AppsFlyerLib.getInstance()
+ appsFlyerLib.subscribeForDeepLink(object : DeepLinkListener {
+ override fun onDeepLinking(deepLinkResult: DeepLinkResult) {
+ when (deepLinkResult.status) {
+ DeepLinkResult.Status.FOUND -> {
+ logger.i { "AppsFlyer deep link found." }
+ _deepLink.tryEmit(DeepLink(buildParams(deepLinkResult)))
+ }
+
+ DeepLinkResult.Status.NOT_FOUND -> {
+ logger.i { "AppsFlyer deep link not found." }
+ }
+
+ DeepLinkResult.Status.ERROR -> {
+ logger.e { "AppsFlyer deep link resolution failed. Caused by - ${deepLinkResult.error}" }
+ }
+ }
+ }
+ })
+ appsFlyerLib.init(devKey, null, context)
+ appsFlyerLib.start(context)
+ }
+
+ // Defaults every OneLink click to the registration screen unless the OneLink
+ // template explicitly sets a `screen_name` deep link parameter.
+ private fun buildParams(deepLinkResult: DeepLinkResult): Map {
+ val clickEvent: JSONObject? = deepLinkResult.deepLink?.clickEvent
+ val params = mutableMapOf()
+ clickEvent?.keys()?.forEach { key -> params[key] = clickEvent.optString(key) }
+
+ if (!params.containsKey(DeepLink.Keys.SCREEN_NAME.value)) {
+ params[DeepLink.Keys.SCREEN_NAME.value] = DeepLinkType.REGISTER.type
+ }
+ return params
+ }
+
+ companion object {
+ private const val TAG = "AppsFlyer"
+ }
+}
diff --git a/app/src/main/java/org/openedx/app/deeplink/DeepLink.kt b/app/src/main/java/org/openedx/app/deeplink/DeepLink.kt
index ac494df06..0cff34d3c 100644
--- a/app/src/main/java/org/openedx/app/deeplink/DeepLink.kt
+++ b/app/src/main/java/org/openedx/app/deeplink/DeepLink.kt
@@ -49,6 +49,7 @@ enum class DeepLinkType(val type: String) {
REMOVE_BETA_TESTER("remove_beta_tester"),
FORUM_RESPONSE("forum_response"),
FORUM_COMMENT("forum_comment"),
+ REGISTER("register"),
NONE("");
companion object {
diff --git a/app/src/main/java/org/openedx/app/deeplink/DeepLinkRouter.kt b/app/src/main/java/org/openedx/app/deeplink/DeepLinkRouter.kt
index 2192a6b89..befa6fe83 100644
--- a/app/src/main/java/org/openedx/app/deeplink/DeepLinkRouter.kt
+++ b/app/src/main/java/org/openedx/app/deeplink/DeepLinkRouter.kt
@@ -8,6 +8,7 @@ import org.openedx.app.AppRouter
import org.openedx.app.MainFragment
import org.openedx.app.R
import org.openedx.auth.presentation.signin.SignInFragment
+import org.openedx.auth.presentation.signup.SignUpFragment
import org.openedx.core.FragmentViewType
import org.openedx.core.config.Config
import org.openedx.core.data.storage.CorePreferences
@@ -41,6 +42,7 @@ class DeepLinkRouter(
DeepLinkType.DISCOVERY -> navigateToDiscoveryScreen(fm)
DeepLinkType.DISCOVERY_COURSE_DETAIL -> navigateToCourseDetail(fm, deepLink)
DeepLinkType.DISCOVERY_PROGRAM_DETAIL -> navigateToProgramDetail(fm, deepLink)
+ DeepLinkType.REGISTER -> navigateToRegistration(fm)
else -> handleLoggedOutOrUserNavigation(fm, deepLink)
}
}
@@ -192,6 +194,20 @@ class DeepLinkRouter(
}
}
+ private fun navigateToRegistration(fm: FragmentManager) {
+ when {
+ isUserLoggedIn -> navigateToDashboard(fm)
+ !config.isRegistrationEnabled() -> navigateToSignIn(fm)
+ appRouter.getVisibleFragment(fm = fm) !is SignUpFragment -> {
+ appRouter.navigateToSignUp(
+ fm = fm,
+ courseId = null,
+ infoType = null
+ )
+ }
+ }
+ }
+
private fun navigateToCourseDashboard(
fm: FragmentManager,
deepLink: DeepLink,
diff --git a/app/src/main/java/org/openedx/app/di/AppModule.kt b/app/src/main/java/org/openedx/app/di/AppModule.kt
index cdb240387..36d4a5cbf 100644
--- a/app/src/main/java/org/openedx/app/di/AppModule.kt
+++ b/app/src/main/java/org/openedx/app/di/AppModule.kt
@@ -16,6 +16,7 @@ import org.openedx.app.AppRouter
import org.openedx.app.BuildConfig
import org.openedx.app.PluginManager
import org.openedx.app.data.storage.PreferencesManager
+import org.openedx.app.deeplink.AppsFlyerDeepLinkHandler
import org.openedx.app.deeplink.DeepLinkRouter
import org.openedx.app.room.AppDatabase
import org.openedx.app.room.DATABASE_NAME
@@ -129,6 +130,7 @@ val appModule = module {
single { get() }
single { get() }
single { DeepLinkRouter(get(), get(), get(), get(), get(), get()) }
+ single { AppsFlyerDeepLinkHandler() }
single { get() }
single { get() }
diff --git a/build.gradle b/build.gradle
index 674a1057f..ef822abef 100644
--- a/build.gradle
+++ b/build.gradle
@@ -29,6 +29,7 @@ buildscript {
// Third-party library versions
branch_sdk_version = '5.20.0'
+ appsflyer_sdk_version = '6.17.4'
play_services_ads_identifier_version = '18.2.0'
install_referrer_version = '2.2'
snakeyaml_version = '2.4'
diff --git a/core/build.gradle b/core/build.gradle
index 19be1f57a..9391645c7 100644
--- a/core/build.gradle
+++ b/core/build.gradle
@@ -114,6 +114,9 @@ dependencies {
api "com.google.android.gms:play-services-ads-identifier:$play_services_ads_identifier_version"
api "com.android.installreferrer:installreferrer:$install_referrer_version"
+ // AppsFlyer SDK Integration
+ api "com.appsflyer:af-android-sdk:$appsflyer_sdk_version"
+
// Zip
api "net.lingala.zip4j:zip4j:$zip_version"
diff --git a/core/src/main/java/org/openedx/core/config/AppsFlyerConfig.kt b/core/src/main/java/org/openedx/core/config/AppsFlyerConfig.kt
new file mode 100644
index 000000000..cc3304d96
--- /dev/null
+++ b/core/src/main/java/org/openedx/core/config/AppsFlyerConfig.kt
@@ -0,0 +1,11 @@
+package org.openedx.core.config
+
+import com.google.gson.annotations.SerializedName
+
+data class AppsFlyerConfig(
+ @SerializedName("ENABLED")
+ val enabled: Boolean = false,
+
+ @SerializedName("DEV_KEY")
+ val devKey: String = "",
+)
diff --git a/core/src/main/java/org/openedx/core/config/Config.kt b/core/src/main/java/org/openedx/core/config/Config.kt
index d26741699..1fe858151 100644
--- a/core/src/main/java/org/openedx/core/config/Config.kt
+++ b/core/src/main/java/org/openedx/core/config/Config.kt
@@ -100,6 +100,10 @@ class Config(context: Context) {
return getObjectOrNewInstance(BRANCH, BranchConfig::class.java)
}
+ fun getAppsFlyerConfig(): AppsFlyerConfig {
+ return getObjectOrNewInstance(APPSFLYER, AppsFlyerConfig::class.java)
+ }
+
fun isWhatsNewEnabled(): Boolean {
return getBoolean(WHATS_NEW_ENABLED, false)
}
@@ -189,6 +193,7 @@ class Config(context: Context) {
private const val DASHBOARD = "DASHBOARD"
private const val EXPERIMENTAL_FEATURES = "EXPERIMENTAL_FEATURES"
private const val BRANCH = "BRANCH"
+ private const val APPSFLYER = "APPSFLYER"
private const val UI_COMPONENTS = "UI_COMPONENTS"
private const val PLATFORM_NAME = "PLATFORM_NAME"
}
diff --git a/default_config/dev/config.yaml b/default_config/dev/config.yaml
index 952e041de..1ac8e76b3 100644
--- a/default_config/dev/config.yaml
+++ b/default_config/dev/config.yaml
@@ -64,6 +64,10 @@ BRANCH:
HOST: ''
ALTERNATE_HOST: ''
+APPSFLYER:
+ ENABLED: false
+ DEV_KEY: ''
+
EXPERIMENTAL_FEATURES:
APP_LEVEL_DOWNLOADS:
ENABLED: false
diff --git a/default_config/prod/config.yaml b/default_config/prod/config.yaml
index a7f265a45..75bee4416 100644
--- a/default_config/prod/config.yaml
+++ b/default_config/prod/config.yaml
@@ -64,6 +64,10 @@ BRANCH:
HOST: ''
ALTERNATE_HOST: ''
+APPSFLYER:
+ ENABLED: false
+ DEV_KEY: ''
+
EXPERIMENTAL_FEATURES:
APP_LEVEL_DOWNLOADS:
ENABLED: false
diff --git a/default_config/stage/config.yaml b/default_config/stage/config.yaml
index a7f265a45..75bee4416 100644
--- a/default_config/stage/config.yaml
+++ b/default_config/stage/config.yaml
@@ -64,6 +64,10 @@ BRANCH:
HOST: ''
ALTERNATE_HOST: ''
+APPSFLYER:
+ ENABLED: false
+ DEV_KEY: ''
+
EXPERIMENTAL_FEATURES:
APP_LEVEL_DOWNLOADS:
ENABLED: false