SubwayTooter-Android-App/app/src/main/java/jp/juggler/subwaytooter/push/PushWorker.kt

141 lines
5.3 KiB
Kotlin

package jp.juggler.subwaytooter.push
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import androidx.work.*
import jp.juggler.subwaytooter.ActMain
import jp.juggler.subwaytooter.notification.NotificationChannels
import jp.juggler.util.coroutine.AppDispatchers
import jp.juggler.util.data.notEmpty
import jp.juggler.util.log.LogCategory
import kotlinx.coroutines.withContext
import java.util.concurrent.atomic.AtomicLong
class PushWorker(appContext: Context, workerParams: WorkerParameters) :
CoroutineWorker(appContext, workerParams) {
companion object {
private val log = LogCategory("PushWorker")
private val ncPushWorker = NotificationChannels.PushWorker
const val KEY_ACTION = "action"
const val KEY_ENDPOINT = "endpoint"
const val KEY_MESSAGE_ID = "messageId"
const val KEY_KEEP_ALIVE_MODE = "keepAliveMode"
const val ACTION_UP_ENDPOINT = "upEndpoint"
const val ACTION_MESSAGE = "message"
const val ACTION_REGISTER_ENDPOINT = "endpointRegister"
val timeStartUpEndpoint = AtomicLong(0L)
val timeEndUpEndpoint = AtomicLong(0L)
val timeStartRegisterEndpoint = AtomicLong(0L)
val timeEndRegisterEndpoint = AtomicLong(0L)
fun enqueueUpEndpoint(context: Context, endpoint: String) {
workDataOf(
KEY_ACTION to ACTION_UP_ENDPOINT,
KEY_ENDPOINT to endpoint,
).launchPushWorker(context)
}
fun enqueueRegisterEndpoint(context: Context, keepAliveMode: Boolean = false) {
workDataOf(
KEY_ACTION to ACTION_REGISTER_ENDPOINT,
KEY_KEEP_ALIVE_MODE to keepAliveMode,
).launchPushWorker(context)
}
fun enqueuePushMessage(context: Context, messageId: Long) {
workDataOf(
KEY_ACTION to ACTION_MESSAGE,
KEY_MESSAGE_ID to messageId,
).launchPushWorker(context)
}
fun Data.launchPushWorker(context: Context) {
// EXPEDITED だと制約の種類が限られる
// すぐ起動してほしいので制約は少なめにする
val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.UNMETERED)
.build()
val request = OneTimeWorkRequestBuilder<PushWorker>().apply {
setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
setConstraints(constraints)
setInputData(this@launchPushWorker)
}
WorkManager.getInstance(context).enqueue(request.build())
log.i("enqueued!")
}
}
override suspend fun doWork(): Result = try {
createForegroundInfo()?.let { setForegroundAsync(it) }
withContext(AppDispatchers.IO) {
val pushRepo = applicationContext.pushRepo
when (val action = inputData.getString(KEY_ACTION)) {
ACTION_UP_ENDPOINT -> {
timeStartUpEndpoint.set(System.currentTimeMillis())
try {
val endpoint = inputData.getString(KEY_ENDPOINT)
?.notEmpty() ?: error("missing endpoint.")
pushRepo.newUpEndpoint(endpoint)
} finally {
timeEndUpEndpoint.set(System.currentTimeMillis())
}
}
ACTION_REGISTER_ENDPOINT -> {
timeStartRegisterEndpoint.set(System.currentTimeMillis())
try {
val keepAliveMode = inputData.getBoolean(KEY_KEEP_ALIVE_MODE, false)
pushRepo.registerEndpoint(keepAliveMode)
} finally {
timeEndRegisterEndpoint.set(System.currentTimeMillis())
}
}
ACTION_MESSAGE -> {
val messageId = inputData.getLong(KEY_MESSAGE_ID, 0L)
.takeIf { it != 0L } ?: error("missing message id.")
pushRepo.updateMessage(messageId)
}
else -> error("invalid action $action")
}
}
Result.success()
} catch (ex: Throwable) {
log.e(ex, "doWork failed.")
Result.failure()
}
/**
* 時々OSに呼ばれる
* Android 11 moto g31 で発生
*/
override suspend fun getForegroundInfo(): ForegroundInfo {
return createForegroundInfo(force = true)!!
}
private fun createForegroundInfo(force: Boolean = false): ForegroundInfo? {
val context = applicationContext
// 通知タップ時のPendingIntent
val iTap = Intent(context, ActMain::class.java).apply {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}
val piTap = PendingIntent.getActivity(
context,
ncPushWorker.pircTap,
iTap,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
return ncPushWorker.createForegroundInfo(
context,
piTap = piTap,
force = force,
)
}
}