diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 142d919a7a18..33a12b929e98 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -22,6 +22,11 @@
android:theme="@style/AppTheme"
tools:replace="android:supportsRtl">
+
+
+ return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties['<%- project.name -%>_' + name]
+ }
+
+ repositories {
+ google()
+ mavenCentral()
+ }
+
+ dependencies {
+ // noinspection DifferentKotlinGradleVersion
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${getExtOrDefault('kotlinVersion')}"
+ }
+}
+
+def reactNativeArchitectures() {
+ def value = rootProject.getProperties().get("reactNativeArchitectures")
+ return value ? value.split(",") : ["armeabi-v7a", "x86", "x86_64", "arm64-v8a"]
+}
+
+def isNewArchitectureEnabled() {
+ return rootProject.hasProperty("newArchEnabled") && rootProject.getProperty("newArchEnabled") == "true"
+}
+
+apply plugin: "com.android.library"
+apply plugin: "kotlin-android"
+
+if (isNewArchitectureEnabled()) {
+ apply plugin: "com.facebook.react"
+}
+
+def getExtOrIntegerDefault(name) {
+ return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["ReactNativeBackgroundTask_" + name]).toInteger()
+}
+
+android {
+ namespace "com.expensify.reactnativebackgroundtask"
+
+ compileSdkVersion getExtOrIntegerDefault("compileSdkVersion")
+
+ defaultConfig {
+ minSdkVersion getExtOrIntegerDefault("minSdkVersion")
+ targetSdkVersion getExtOrIntegerDefault("targetSdkVersion")
+ buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()
+
+ }
+
+ buildFeatures {
+ buildConfig true
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ }
+ }
+
+ lintOptions {
+ disable "GradleCompatible"
+ }
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+
+ sourceSets {
+ main {
+ if (isNewArchitectureEnabled()) {
+ java.srcDirs += [
+ "src/newarch",
+ // Codegen specs
+ "generated/java",
+ "generated/jni"
+ ]
+ } else {
+ java.srcDirs += ["src/oldarch"]
+ }
+ }
+ }
+}
+
+repositories {
+ mavenCentral()
+ google()
+}
+
+def kotlin_version = getExtOrDefault("kotlinVersion")
+
+dependencies {
+ //noinspection GradleDynamicVersion
+ implementation "com.facebook.react:react-android"
+ implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
+}
+
+if (isNewArchitectureEnabled()) {
+ react {
+ jsRootDir = file("../src/")
+ libraryName = "ReactNativeBackgroundTask"
+ codegenJavaPackageName = "com.expensify.reactnativebackgroundtask"
+ }
+}
diff --git a/modules/background-task/android/gradle.properties b/modules/background-task/android/gradle.properties
new file mode 100644
index 000000000000..4eec12b62c7e
--- /dev/null
+++ b/modules/background-task/android/gradle.properties
@@ -0,0 +1,5 @@
+ReactNativeBackgroundTask_kotlinVersion=1.9.24
+ReactNativeBackgroundTask_minSdkVersion=23
+ReactNativeBackgroundTask_targetSdkVersion=34
+ReactNativeBackgroundTask_compileSdkVersion=34
+ReactNativeBackgroundTask_ndkversion=26.1.10909125
diff --git a/modules/background-task/android/src/main/AndroidManifest.xml b/modules/background-task/android/src/main/AndroidManifest.xml
new file mode 100644
index 000000000000..cf2838eb09ee
--- /dev/null
+++ b/modules/background-task/android/src/main/AndroidManifest.xml
@@ -0,0 +1,3 @@
+
+
+
diff --git a/modules/background-task/android/src/main/java/com/expensify/reactnativebackgroundtask/BackgroundJobService.kt b/modules/background-task/android/src/main/java/com/expensify/reactnativebackgroundtask/BackgroundJobService.kt
new file mode 100644
index 000000000000..4184727677a0
--- /dev/null
+++ b/modules/background-task/android/src/main/java/com/expensify/reactnativebackgroundtask/BackgroundJobService.kt
@@ -0,0 +1,25 @@
+package com.expensify.reactnativebackgroundtask
+
+import android.app.job.JobParameters
+import android.app.job.JobService
+import android.content.Intent
+import android.util.Log
+import com.facebook.react.ReactApplication
+
+class BackgroundJobService : JobService() {
+ override fun onStartJob(params: JobParameters?): Boolean {
+ val taskName = params?.extras?.getString("taskName")
+ val intent = Intent("com.expensify.reactnativebackgroundtask.TASK_ACTION").apply {
+ putExtra("taskName", taskName)
+ }
+ sendBroadcast(intent)
+
+ // Job is done, return false if no more work is needed
+ return false
+ }
+
+ override fun onStopJob(params: JobParameters?): Boolean {
+ // Return true to reschedule the job
+ return true
+ }
+}
diff --git a/modules/background-task/android/src/main/java/com/expensify/reactnativebackgroundtask/ReactNativeBackgroundTaskModule.kt b/modules/background-task/android/src/main/java/com/expensify/reactnativebackgroundtask/ReactNativeBackgroundTaskModule.kt
new file mode 100644
index 000000000000..395f822645b3
--- /dev/null
+++ b/modules/background-task/android/src/main/java/com/expensify/reactnativebackgroundtask/ReactNativeBackgroundTaskModule.kt
@@ -0,0 +1,69 @@
+package com.expensify.reactnativebackgroundtask
+
+import com.facebook.react.bridge.ReactApplicationContext
+import com.facebook.react.bridge.ReactMethod
+import com.facebook.react.bridge.Promise
+import com.facebook.react.bridge.Callback
+import android.app.job.JobScheduler
+import android.app.job.JobInfo
+import android.content.BroadcastReceiver
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.os.PersistableBundle
+import android.util.Log
+
+class ReactNativeBackgroundTaskModule internal constructor(context: ReactApplicationContext) :
+ ReactNativeBackgroundTaskSpec(context) {
+
+ private val taskReceiver = object : BroadcastReceiver() {
+ override fun onReceive(context: Context?, intent: Intent?) {
+ val taskName = intent?.getStringExtra("taskName")
+ Log.d("ReactNativeBackgroundTaskModule", "Received task: $taskName")
+ emitOnBackgroundTaskExecution(taskName)
+ }
+ }
+
+ init {
+ val filter = IntentFilter("com.expensify.reactnativebackgroundtask.TASK_ACTION")
+ reactApplicationContext.registerReceiver(taskReceiver, filter)
+ }
+
+ override fun getName(): String {
+ return NAME
+ }
+
+ @ReactMethod
+ override fun defineTask(taskName: String, taskExecutor: Callback, promise: Promise) {
+ try {
+ val jobScheduler = reactApplicationContext.getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler
+ val componentName = ComponentName(reactApplicationContext, BackgroundJobService::class.java)
+
+ val extras = PersistableBundle().apply {
+ putString("taskName", taskName)
+ }
+
+ val jobInfo = JobInfo.Builder(taskName.hashCode() and 0xFFFFFF, componentName)
+ .setPeriodic(15 * 60 * 1000L) // 15 minutes in milliseconds
+ .setPersisted(true) // Job persists after reboot
+ .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
+ .setExtras(extras)
+ .build()
+
+ val resultCode = jobScheduler.schedule(jobInfo)
+ if (resultCode == JobScheduler.RESULT_SUCCESS) {
+
+ promise.resolve(null);
+ } else {
+ promise.reject("ERROR", "Failed to schedule job")
+ }
+ } catch (e: Exception) {
+ promise.reject("ERROR", e.message)
+ }
+ }
+
+ companion object {
+ const val NAME = "ReactNativeBackgroundTask"
+ }
+}
diff --git a/modules/background-task/android/src/main/java/com/expensify/reactnativebackgroundtask/ReactNativeBackgroundTaskPackage.kt b/modules/background-task/android/src/main/java/com/expensify/reactnativebackgroundtask/ReactNativeBackgroundTaskPackage.kt
new file mode 100644
index 000000000000..dd0547f7ef35
--- /dev/null
+++ b/modules/background-task/android/src/main/java/com/expensify/reactnativebackgroundtask/ReactNativeBackgroundTaskPackage.kt
@@ -0,0 +1,35 @@
+package com.expensify.reactnativebackgroundtask
+
+import com.facebook.react.TurboReactPackage
+import com.facebook.react.bridge.ReactApplicationContext
+import com.facebook.react.bridge.NativeModule
+import com.facebook.react.module.model.ReactModuleInfoProvider
+import com.facebook.react.module.model.ReactModuleInfo
+import java.util.HashMap
+
+class ReactNativeBackgroundTaskPackage : TurboReactPackage() {
+ override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? {
+ return if (name == ReactNativeBackgroundTaskModule.NAME) {
+ ReactNativeBackgroundTaskModule(reactContext)
+ } else {
+ null
+ }
+ }
+
+ override fun getReactModuleInfoProvider(): ReactModuleInfoProvider {
+ return ReactModuleInfoProvider {
+ val moduleInfos: MutableMap = HashMap()
+ val isTurboModule: Boolean = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED
+ moduleInfos[ReactNativeBackgroundTaskModule.NAME] = ReactModuleInfo(
+ ReactNativeBackgroundTaskModule.NAME,
+ ReactNativeBackgroundTaskModule.NAME,
+ false, // canOverrideExistingModule
+ false, // needsEagerInit
+ true, // hasConstants
+ false, // isCxxModule
+ isTurboModule // isTurboModule
+ )
+ moduleInfos
+ }
+ }
+}
diff --git a/modules/background-task/android/src/newarch/ReactNativeBackgroundTaskSpec.kt b/modules/background-task/android/src/newarch/ReactNativeBackgroundTaskSpec.kt
new file mode 100644
index 000000000000..2ee39c49c66d
--- /dev/null
+++ b/modules/background-task/android/src/newarch/ReactNativeBackgroundTaskSpec.kt
@@ -0,0 +1,7 @@
+package com.expensify.reactnativebackgroundtask
+
+import com.facebook.react.bridge.ReactApplicationContext
+
+abstract class ReactNativeBackgroundTaskSpec internal constructor(context: ReactApplicationContext) :
+ NativeReactNativeBackgroundTaskSpec(context) {
+}
diff --git a/modules/background-task/android/src/oldarch/ReactNativeBackgroundTaskSpec.kt b/modules/background-task/android/src/oldarch/ReactNativeBackgroundTaskSpec.kt
new file mode 100644
index 000000000000..138a1cc1d4af
--- /dev/null
+++ b/modules/background-task/android/src/oldarch/ReactNativeBackgroundTaskSpec.kt
@@ -0,0 +1,11 @@
+package com.expensify.reactnativebackgroundtask
+
+import com.facebook.react.bridge.ReactApplicationContext
+import com.facebook.react.bridge.ReactContextBaseJavaModule
+import com.facebook.react.bridge.Promise
+
+abstract class ReactNativeBackgroundTaskSpec internal constructor(context: ReactApplicationContext) :
+ ReactContextBaseJavaModule(context) {
+
+ abstract fun defineTask(taskName: String, taskExecutor: Callback, promise: Promise)
+}
diff --git a/modules/background-task/react-native.config.js b/modules/background-task/react-native.config.js
index d532440e69b0..5bad2c0ec408 100644
--- a/modules/background-task/react-native.config.js
+++ b/modules/background-task/react-native.config.js
@@ -4,7 +4,9 @@
module.exports = {
dependency: {
platforms: {
- android: null,
+ android: {
+ cmakeListsPath: 'build/generated/source/codegen/jni/CMakeLists.txt',
+ },
},
},
};
diff --git a/src/setup/backgroundTask/index.ios.ts b/src/setup/backgroundTask/index.native.ts
similarity index 100%
rename from src/setup/backgroundTask/index.ios.ts
rename to src/setup/backgroundTask/index.native.ts
diff --git a/src/setup/backgroundTask/index.ts b/src/setup/backgroundTask/index.ts
index e69de29bb2d1..bb307a04703f 100644
--- a/src/setup/backgroundTask/index.ts
+++ b/src/setup/backgroundTask/index.ts
@@ -0,0 +1,2 @@
+// This file is intentionally empty as Background Tasks are currently only implemented
+// for native mobile platforms. See `index.native.ts` for the native implementation.