Skip to content

Commit

Permalink
Convert HeadlessJsTaskService to Kotlin (facebook#48800)
Browse files Browse the repository at this point in the history
Summary:
Pull Request resolved: facebook#48800

This just converts yet another class from Java to Kotlin

Changelog:
[Internal] [Changed] -

Differential Revision: D68417564
  • Loading branch information
cortinico authored and facebook-github-bot committed Jan 20, 2025
1 parent 967ef32 commit 6aac22b
Show file tree
Hide file tree
Showing 2 changed files with 188 additions and 208 deletions.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

package com.facebook.react

import android.annotation.SuppressLint
import android.app.Service
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.os.IBinder
import android.os.PowerManager
import android.os.PowerManager.WakeLock
import com.facebook.infer.annotation.Assertions
import com.facebook.react.bridge.ReactContext
import com.facebook.react.bridge.UiThreadUtil
import com.facebook.react.internal.featureflags.ReactNativeFeatureFlags.enableBridgelessArchitecture
import com.facebook.react.jstasks.HeadlessJsTaskConfig
import com.facebook.react.jstasks.HeadlessJsTaskContext.Companion.getInstance
import com.facebook.react.jstasks.HeadlessJsTaskEventListener
import java.util.concurrent.CopyOnWriteArraySet

/**
* Base class for running JS without a UI. Generally, you only need to override [ ][.getTaskConfig],
* which is called for every [.onStartCommand]. The result, if not `null`, is used to run a JS task.
*
* If you need more fine-grained control over how tasks are run, you can override
* [ ][.onStartCommand] and call [.startTask] depending on your custom logic.
*
* If you're starting a `HeadlessJsTaskService` from a `BroadcastReceiver` (e.g. handling push
* notifications), make sure to call [acquireWakeLockNow] before returning from
* [BroadcastReceiver.onReceive], to make sure the device doesn't go to sleep before the service is
* started.
*/
public abstract class HeadlessJsTaskService : Service(), HeadlessJsTaskEventListener {
private val activeTasks: MutableSet<Int> = CopyOnWriteArraySet()

override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
val taskConfig = getTaskConfig(intent)
return if (taskConfig != null) {
startTask(taskConfig)
START_REDELIVER_INTENT
} else {
START_NOT_STICKY
}
}

/**
* Called from [.onStartCommand] to create a [HeadlessJsTaskConfig] for this intent.
*
* @param intent the [Intent] received in [onStartCommand].
* @return a [HeadlessJsTaskConfig] to be used with [startTask], or `null` to ignore this command.
*/
protected fun getTaskConfig(intent: Intent?): HeadlessJsTaskConfig? = null

override fun onBind(intent: Intent): IBinder? = null

/**
* Start a task. This method handles starting a new React instance if required.
*
* Has to be called on the UI thread.
*
* @param taskConfig describes what task to start and the parameters to pass to it
*/
protected fun startTask(taskConfig: HeadlessJsTaskConfig) {
UiThreadUtil.assertOnUiThread()
acquireWakeLockNow(this)

val context = reactContext
if (context == null) {
createReactContextAndScheduleTask(taskConfig)
} else {
invokeStartTask(context, taskConfig)
}
}

private fun invokeStartTask(reactContext: ReactContext, taskConfig: HeadlessJsTaskConfig) {
val headlessJsTaskContext = getInstance(reactContext)
headlessJsTaskContext.addTaskEventListener(this)
UiThreadUtil.runOnUiThread {
val taskId = headlessJsTaskContext.startTask(taskConfig)
activeTasks.add(taskId)
}
}

override fun onDestroy() {
super.onDestroy()

val context = reactContext
if (context != null) {
val headlessJsTaskContext = getInstance(context)
headlessJsTaskContext.removeTaskEventListener(this)
}
wakeLock?.release()
}

override fun onHeadlessJsTaskStart(taskId: Int): Unit = Unit

override fun onHeadlessJsTaskFinish(taskId: Int) {
activeTasks.remove(taskId)
if (activeTasks.isEmpty()) {
stopSelf()
}
}

/**
* Get the [ReactNativeHost] used by this app. By default, assumes [getApplication] is an instance
* of [ReactApplication] and calls [ReactApplication.reactNativeHost].
*
* Override this method if your application class does not implement `ReactApplication` or you
* simply have a different mechanism for storing a `ReactNativeHost`, e.g. as a static field
* somewhere.
*/
protected val reactNativeHost: ReactNativeHost
get() = (application as ReactApplication).reactNativeHost

/**
* Get the [ReactHost] used by this app. By default, assumes [.getApplication] is an instance of
* [ReactApplication] and calls [ReactApplication.getReactHost]. This method assumes it is called
* in new architecture and returns null if not.
*/
protected val reactHost: ReactHost?
get() = (application as ReactApplication).reactHost

protected val reactContext: ReactContext?
get() {
if (enableBridgelessArchitecture()) {
val reactHost = reactHost
checkNotNull(reactHost) { "ReactHost is not initialized in New Architecture" }
return reactHost.currentReactContext
} else {
val reactInstanceManager = reactNativeHost.reactInstanceManager
return reactInstanceManager.currentReactContext
}
}

private fun createReactContextAndScheduleTask(taskConfig: HeadlessJsTaskConfig) {
if (enableBridgelessArchitecture()) {
val reactHost = checkNotNull(reactHost)
reactHost.addReactInstanceEventListener(
object : ReactInstanceEventListener {
override fun onReactContextInitialized(context: ReactContext) {
invokeStartTask(context, taskConfig)
reactHost.removeReactInstanceEventListener(this)
}
})
reactHost.start()
} else {
val reactInstanceManager = reactNativeHost.reactInstanceManager
reactInstanceManager.addReactInstanceEventListener(
object : ReactInstanceEventListener {
override fun onReactContextInitialized(context: ReactContext) {
invokeStartTask(context, taskConfig)
reactInstanceManager.removeReactInstanceEventListener(this)
}
})
reactInstanceManager.createReactContextInBackground()
}
}

public companion object {
private var wakeLock: WakeLock? = null

/**
* Acquire a wake lock to ensure the device doesn't go to sleep while processing background
* tasks.
*/
@SuppressLint("WakelockTimeout")
public fun acquireWakeLockNow(context: Context) {
if (wakeLock == null || wakeLock?.isHeld == false) {
val powerManager =
Assertions.assertNotNull(context.getSystemService(POWER_SERVICE) as PowerManager)
wakeLock =
powerManager
.newWakeLock(
PowerManager.PARTIAL_WAKE_LOCK, HeadlessJsTaskService::class.java.canonicalName)
.also { lock ->
lock.setReferenceCounted(false)
lock.acquire()
}
}
}
}
}

0 comments on commit 6aac22b

Please sign in to comment.