This commit is contained in:
Administrator 2025-07-04 16:38:32 +08:00
parent 890539cbc0
commit 1e7ceae4ed
24 changed files with 1174 additions and 338 deletions

6
.idea/vcs.xml Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

View File

@ -9,6 +9,8 @@
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.BIND_VPN_SERVICE"
tools:ignore="ProtectedPermissions" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"
android:maxSdkVersion="32"/>
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.VPN_SERVICE" />
@ -41,6 +43,7 @@
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:networkSecurityConfig="@xml/network_security_config"
android:requestLegacyExternalStorage="true"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
@ -117,8 +120,6 @@
android:name="android.accessibilityservice"
android:resource="@xml/accessibility_service_config" />
</service>
<receiver android:name="com.android.grape.receiver.ScriptReceiver" />
</application>
</manifest>

View File

@ -13,18 +13,24 @@ import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.lifecycle.lifecycleScope
import com.android.grape.databinding.ActivityMainBinding
import com.android.grape.job.MonitorService
import com.android.grape.receiver.ScriptReceiver
import com.android.grape.util.ClashUtil
import com.android.grape.util.HttpUtil
import com.android.grape.util.NotificationPermissionHandler
import com.android.grape.util.StoragePermissionHelper
import com.blankj.utilcode.util.LogUtils
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
class MainActivity : AppCompatActivity() {
private val viewModel by viewModels<MainViewModel>()
private lateinit var viewBinding: ActivityMainBinding
private var intentFilter: IntentFilter? = null
private var scriptReceiver: ScriptReceiver? = null
private lateinit var permissionHandler: NotificationPermissionHandler
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
@ -39,15 +45,14 @@ class MainActivity : AppCompatActivity() {
registerReceiver()
viewBinding.start.setOnClickListener {
try {
ClashUtil.startProxy(this) // 在主线程中调用
ClashUtil.switchProxyGroup("GLOBAL", "us", "http://127.0.0.1:6170")
} catch (e: Exception) {
LogUtils.log(
Log.ERROR,
"MainActivity",
"startProxyVpn: Failed to start VPN",
e
ClashUtil.startProxy(this)
ClashUtil.switchProxyGroup(
"GLOBAL",
ClashUtil.getRandomLocale(),
"http://127.0.0.1:6170"
)
} catch (e: Exception) {
LogUtils.e("startProxyVpn: Failed to start VPN", e)
Toast.makeText(
this,
"Failed to start VPN: " + (if (e.message != null) e.message else "Unknown error"),
@ -77,7 +82,11 @@ class MainActivity : AppCompatActivity() {
}
private fun checkPermission() {
viewModel.checkAccessibilityService()
permissionHandler = NotificationPermissionHandler(this) { result ->
handlePermissionResult(result)
}
checkNotificationPermission()
StoragePermissionHelper.requestFullStoragePermission(
activity = this,
onGranted = { performFileOperation() },
@ -85,10 +94,56 @@ class MainActivity : AppCompatActivity() {
)
}
private fun checkNotificationPermission() {
when (val result = NotificationPermissionHandler.checkPermissionState(this)) {
NotificationPermissionHandler.PermissionResult.GRANTED,
NotificationPermissionHandler.PermissionResult.NOT_NEEDED -> {
showNotification()
}
NotificationPermissionHandler.PermissionResult.DENIED,
NotificationPermissionHandler.PermissionResult.NEEDS_SETTINGS -> {
// 请求权限
permissionHandler.requestNotificationPermission()
}
}
}
/**
* 处理权限结果回调
*/
private fun handlePermissionResult(result: NotificationPermissionHandler.PermissionResult) {
when (result) {
NotificationPermissionHandler.PermissionResult.GRANTED -> {
// 权限已授予,显示通知
showNotification()
Toast.makeText(this, "通知权限已开启", Toast.LENGTH_SHORT).show()
}
NotificationPermissionHandler.PermissionResult.DENIED -> {
// 权限被拒绝
Toast.makeText(this, "通知权限被拒绝", Toast.LENGTH_SHORT).show()
}
NotificationPermissionHandler.PermissionResult.NEEDS_SETTINGS -> {
Toast.makeText(this, "通知权限被拒绝", Toast.LENGTH_SHORT).show()
}
NotificationPermissionHandler.PermissionResult.NOT_NEEDED -> {
// 不需要特殊权限(旧设备)
showNotification()
}
}
}
private fun showNotification() {
viewModel.checkAccessibilityService()
}
private fun performFileOperation() {
// 执行文件读写操作
// Toast.makeText(this, "文件访问权限已授予", Toast.LENGTH_SHORT).show()
MonitorService.onEvent(this)
// MonitorService.onEvent(this)
}
private fun showPermissionDeniedDialog() {

View File

@ -0,0 +1,8 @@
package com.android.grape.data
import com.google.gson.annotations.SerializedName
data class As(
var nameValuePairs: NameValuePairs = NameValuePairs()
)

View File

@ -0,0 +1,11 @@
package com.android.grape.data
import com.google.gson.annotations.SerializedName
data class DdlInfo(
var fromfg: Int = 0,
var net0: Int = 0,
var rfrwait: Int = 0,
var status: String = ""
)

View File

@ -0,0 +1,113 @@
package com.android.grape.data
import com.google.gson.annotations.SerializedName
data class Device(
var advertiserId: String = "",
@SerializedName("af_preinstalled")
var afPreinstalled: Boolean = false,
var androidId: String = "",
var arch: String = "",
@SerializedName("as")
var asX: As = As(),
var battery: Int = 0,
var battery1: Int = 0,
var batteryLevel: Int = 0,
var batteryType: Int = 0,
var bn: String = "",
var brand: String = "",
var btch: String = "",
@SerializedName("build_display_id")
var buildDisplayId: String = "",
var carrier: String = "",
@SerializedName("click_ts")
var clickTs: Int = 0,
var clk: String = "",
var country: String = "",
@SerializedName("cpu_abi")
var cpuAbi: String = "",
@SerializedName("cpu_abi2")
var cpuAbi2: String = "",
var ddlInfo: DdlInfo = DdlInfo(),
var debug: Boolean = false,
var device: String = "",
var deviceCity: String = "",
var deviceType: String = "",
var dim: Dim = Dim(),
var dimId: Int = 0,
var dimReset: Int = 0,
var dimType: Int = 0,
var disk: String = "",
var diskRate: Double = 0.0,
var expand: Expand = Expand(),
var fetchAdIdLatency: Int = 0,
@SerializedName("from_fg")
var fromFg: Int = 0,
@SerializedName("google_custom")
var googleCustom: GoogleCustom = GoogleCustom(),
var hook: String = "",
@SerializedName("init_to_fg")
var initToFg: Int = 0,
var install: String = "",
@SerializedName("install_begin_ts")
var installBeginTs: Int = 0,
@SerializedName("installer_package")
var installerPackage: String = "",
var instant: Boolean = false,
var ip: String = "",
var ivc: Boolean = false,
var lang: String = "",
@SerializedName("lang_code")
var langCode: String = "",
@SerializedName("last_boot_time")
var lastBootTime: Long = 0,
var lastBootTimeOff: Int = 0,
var latency: Int = 0,
var locale: Locale = Locale(),
var manufactor: String = "",
var mcc: Int = 0,
var mnc: Int = 0,
var model: String = "",
@SerializedName("native_dir")
var nativeDir: Boolean = false,
var network: String = "",
var noRcLatency: Boolean = false,
@SerializedName("open_referrer")
var openReferrer: String = "",
@SerializedName("open_referrerReset")
var openReferrerReset: Int = 0,
var opener: String = "",
var `operator`: String = "",
var platformextension: String = "",
var pr: Pr = Pr(),
var product: String = "",
var rawDevice: String = "",
var rawProduct: String = "",
@SerializedName("rc.delay")
var rcDelay: Int = 0,
@SerializedName("rc.latency")
var rcLatency: Int = 0,
var referrer: String = "",
@SerializedName("sc_o")
var scO: String = "",
var sdk: String = "",
var sdkVer: String = "",
var sensor: List<Sensor> = listOf(),
var sensorReset: Int = 0,
@SerializedName("sensor_size")
var sensorSize: Int = 0,
@SerializedName("sig_n")
var sigN: String = "",
@SerializedName("sys_ua")
var sysUa: String = "",
var timepassedsincelastlaunch: Int = 0,
var tzDisplayName: String = "",
var tzOffTime: Int = 0,
@SerializedName("val")
var valX: String = "",
var vendingVersionCode: String = "",
var vendingVersionName: String = "",
@SerializedName("web_ua")
var webUa: String = ""
)

View File

@ -0,0 +1,16 @@
package com.android.grape.data
import com.google.gson.annotations.SerializedName
data class Dim(
@SerializedName("d_dpi")
var dDpi: String = "",
var size: String = "",
@SerializedName("x_px")
var xPx: String = "",
var xdp: String = "",
@SerializedName("y_px")
var yPx: String = "",
var ydp: String = ""
)

View File

@ -0,0 +1,87 @@
package com.android.grape.data
import com.google.gson.annotations.SerializedName
data class Expand(
@SerializedName("ABI")
var aBI: String = "",
@SerializedName("API")
var aPI: String = "",
var amGetConfig: String = "",
@SerializedName("AndroidID")
var androidID: String = "",
@SerializedName("AndroidVer")
var androidVer: String = "",
@SerializedName("BSSID")
var bSSID: String = "",
var baseband: String = "",
var batteryLevel: Int = 0,
var board: String = "",
var bootLoader: String = "",
var brand: String = "",
var country: String = "",
@SerializedName("CountryCode")
var countryCode: String = "",
var cpuinfobuf: String = "",
var cpuinfostr: String = "",
@SerializedName("DPI")
var dPI: Int = 0,
var dataStatfs: Long = 0,
var device: String = "",
var display: String = "",
var displayLang: String = "",
var downloadCacheStatfs: Long = 0,
var fingerprint: String = "",
var firstInstallTime: Int = 0,
var gaid: String = "",
var glExtensions: String = "",
var glRenderer: String = "",
var glVendor: String = "",
var glVersion: String = "",
var height: Int = 0,
@SerializedName("ID")
var iD: String = "",
var incremental: String = "",
var lang: String = "",
var lastUpdateTime: Int = 0,
var latitude: Double = 0.0,
var localip: String = "",
var longtitude: Double = 0.0,
var lyMAC: String = "",
@SerializedName("Manufacture")
var manufacture: String = "",
var model: String = "",
var network: String = "",
var phoneBootTime: Int = 0,
var phoneUsedTime: Int = 0,
var pmfea: String = "",
var pmlib: String = "",
var procStat: String = "",
var procVersion: String = "",
var ratioVersion: String = "",
var referrerClickTime: Long = 0,
var referrerFromGP: String = "",
var regionid: String = "",
var roBoardPlatform: String = "",
var roBootimageBuildFingerprint: String = "",
var roBuildDateUtc: Int = 0,
var roBuildDescription: String = "",
var roHardware: String = "",
var roProductCpuAbilist: String = "",
var roProductName: String = "",
var rootStatfs: Int = 0,
var screenBrightness: Int = 0,
var sdStatfs: Long = 0,
var sdcardCreateTime: Int = 0,
var soRomRam: String = "",
var timerawoff: Int = 0,
var tmdisplayname: String = "",
var uname: String = "",
var width: Int = 0,
@SerializedName("WifiMAC")
var wifiMAC: String = "",
@SerializedName("WifiName")
var wifiName: String = "",
var wvua: String = ""
)

View File

@ -0,0 +1,14 @@
package com.android.grape.data
import com.google.gson.annotations.SerializedName
data class GoogleCustom(
@SerializedName("click_server_ts")
var clickServerTs: Int = 0,
@SerializedName("install_begin_server_ts")
var installBeginServerTs: Int = 0,
@SerializedName("install_version")
var installVersion: String = "",
var instant: Boolean = false
)

View File

@ -0,0 +1,10 @@
package com.android.grape.data
import com.google.gson.annotations.SerializedName
data class Locale(
var country: String = "",
var displayLang: String = "",
var lang: String = ""
)

View File

@ -0,0 +1,11 @@
package com.android.grape.data
import com.google.gson.annotations.SerializedName
data class NameValuePairs(
var cav: Int = 0,
@SerializedName("null")
var nullX: Int = 0,
var other: Int = 0
)

View File

@ -0,0 +1,22 @@
package com.android.grape.data
import com.google.gson.annotations.SerializedName
data class NameValuePairsX(
var ac: String = "",
var ah: String = "",
var ai: String = "",
var ak: String = "",
var al: String = "",
var am: String = "",
var an: String = "",
var ap: String = "",
var aq: String = "",
var ar: String = "",
@SerializedName("as")
var asX: String = "",
var at: String = "",
var au: String = "",
var av: String = ""
)

View File

@ -0,0 +1,8 @@
package com.android.grape.data
import com.google.gson.annotations.SerializedName
data class Pr(
var nameValuePairs: NameValuePairsX = NameValuePairsX()
)

View File

@ -0,0 +1,12 @@
package com.android.grape.data
import com.google.gson.annotations.SerializedName
data class Sensor(
var sN: String = "",
var sT: Int = 0,
var sV: String = "",
var sVE: List<Double> = listOf(),
var sVS: List<Double> = listOf()
)

View File

@ -4,6 +4,7 @@ import android.content.Context
import android.content.Intent
import android.util.Log
import androidx.core.app.JobIntentService
import com.android.grape.util.ChangeDeviceInfoUtil
import com.android.grape.util.MockTools
import com.android.grape.util.ServiceUtils
import com.android.grape.util.Util
@ -28,13 +29,15 @@ class OpenAppService : JobIntentService() {
Util.doScript(this, Util.AUTO_JSPACKAGENAME) //autojs
}
try {
if (Util.isNeedRestored) {
Log.d("IOSTQ:", "执行留存任务")
Util.setRrInfo(this)
} else {
Log.d("IOSTQ:", "执行新装任务")
Util.setInfo(this)
}
//todo fix 改机流程
// if (Util.isNeedRestored) {
// Log.d("IOSTQ:", "执行留存任务")
// Util.setRrInfo(this)
// } else {
// Log.d("IOSTQ:", "执行新装任务")
// Util.setInfo(this)
// }
ChangeDeviceInfoUtil.changeDeviceInfo()
} catch (e: IOException) {
e.printStackTrace()
}

View File

@ -3,329 +3,150 @@ package com.android.grape.util
import android.content.ContentResolver
import android.content.Context
import android.util.Log
import com.android.grape.data.AfInfo
import com.android.grape.data.BigoInfo
import com.android.grape.data.DeviceInfo
import com.android.grape.MainApplication
import com.android.grape.data.Device
import com.android.grape.util.Util.paramsJson
import com.blankj.utilcode.util.LogUtils
import org.json.JSONObject
import java.lang.reflect.InvocationTargetException
object ChangeDeviceInfoUtil {
fun changeDeviceInfo(
current_pkg_name: String,
context: Context?,
bigoDeviceObject: JSONObject?,
afDeviceObject: JSONObject?
) {
var bigoDeviceObject = bigoDeviceObject
var afDeviceObject = afDeviceObject
try {
val bigoDevice: BigoInfo
if (bigoDeviceObject != null) {
// BIGO
val cpuClockSpeed = bigoDeviceObject.optString("cpu_clock_speed")
val gaid = bigoDeviceObject.optString("gaid")
val userAgent = bigoDeviceObject.optString("User-Agent")
val osLang = bigoDeviceObject.optString("os_lang")
val osVer = bigoDeviceObject.optString("os_ver")
val tz = bigoDeviceObject.optString("tz")
val systemCountry = bigoDeviceObject.optString("system_country")
val simCountry = bigoDeviceObject.optString("sim_country")
val romFreeIn = bigoDeviceObject.optLong("rom_free_in")
val resolution = bigoDeviceObject.optString("resolution")
val vendor = bigoDeviceObject.optString("vendor")
val batteryScale = bigoDeviceObject.optInt("bat_scale")
// String model = deviceObject.optString("model");
val net = bigoDeviceObject.optString("net")
val dpi = bigoDeviceObject.optLong("dpi")
val romFreeExt = bigoDeviceObject.optLong("rom_free_ext")
val dpiF = bigoDeviceObject.optString("dpi_f")
val cpuCoreNum = bigoDeviceObject.optLong("cpu_core_num")
bigoDevice = BigoInfo()
bigoDevice.cpuClockSpeed = cpuClockSpeed
bigoDevice.gaid = gaid
bigoDevice.userAgent = userAgent
bigoDevice.osLang = osLang
bigoDevice.osVer = osVer
bigoDevice.tz = tz
bigoDevice.systemCountry = systemCountry
bigoDevice.simCountry = simCountry
bigoDevice.romFreeIn = romFreeIn
bigoDevice.resolution = resolution
bigoDevice.vendor = vendor
bigoDevice.batteryScale = batteryScale
bigoDevice.net = net
bigoDevice.dpi = dpi
bigoDevice.romFreeExt = romFreeExt
bigoDevice.dpiF = dpiF
bigoDevice.cpuCoreNum = cpuCoreNum
// TaskUtil.setBigoDevice(bigoDevice)
try {
callVCloudSettings_put(
"$current_pkg_name.system_country",
systemCountry,
context
)
callVCloudSettings_put("$current_pkg_name.sim_country", simCountry, context)
callVCloudSettings_put(
"$current_pkg_name.rom_free_in",
romFreeIn.toString(),
context
)
callVCloudSettings_put("$current_pkg_name.resolution", resolution, context)
callVCloudSettings_put("$current_pkg_name.vendor", vendor, context)
callVCloudSettings_put(
"$current_pkg_name.battery_scale",
batteryScale.toString(),
context
)
callVCloudSettings_put("$current_pkg_name.os_lang", osLang, context)
// callVCloudSettings_put(current_pkg_name + ".model", model, context);
callVCloudSettings_put("$current_pkg_name.net", net, context)
callVCloudSettings_put("$current_pkg_name.dpi", dpi.toString(), context)
callVCloudSettings_put(
"$current_pkg_name.rom_free_ext",
romFreeExt.toString(),
context
)
callVCloudSettings_put("$current_pkg_name.dpi_f", dpiF, context)
callVCloudSettings_put(
"$current_pkg_name.cpu_core_num",
cpuCoreNum.toString(),
context
)
callVCloudSettings_put(
"$current_pkg_name.cpu_clock_speed",
cpuClockSpeed,
context
)
callVCloudSettings_put(current_pkg_name + "_gaid", gaid, context)
// **User-Agent**
callVCloudSettings_put(current_pkg_name + "_user_agent", userAgent, context)
// **os_lang**系统语言
callVCloudSettings_put(current_pkg_name + "_os_lang", osLang, context)
// **os_ver**
callVCloudSettings_put(current_pkg_name + "_os_ver", osVer, context)
// **tz** (时区)
callVCloudSettings_put(current_pkg_name + "_tz", tz, context)
} catch (e: Throwable) {
LogUtils.e(
Log.ERROR,
"ChangeDeviceInfoUtil",
"Error occurred while changing device info",
e
)
throw RuntimeException("Error occurred in changeDeviceInfo", e)
}
bigoDeviceObject = null
val deviceObject = paramsJson?.getJSONObject("device")
if (deviceObject == null) {
LogUtils.d("ERROR", "ChangeDeviceInfoUtil", "device is null")
return
}
val context = MainApplication.instance
val device = GsonUtils.fromJsonObject(deviceObject.toString(), Device::class.java)
val currentPkgName = MainApplication.instance.packageName
vcloudsettingsPut(
"$currentPkgName.system_country",
device.country,
context
)
vcloudsettingsPut("$currentPkgName.sim_country", device.country, context)
// callVCloudSettings_put(
// "$currentPkgName.rom_free_in",
// romFreeIn.toString(),
// context
// )
vcloudsettingsPut(
"$currentPkgName.resolution",
"${device.expand.width}x${device.expand.height}}",
context
)
vcloudsettingsPut("$currentPkgName.vendor", device.expand.glVendor, context)
// callVCloudSettings_put("$currentPkgName.battery_scale", batteryScale.toString(), context)
vcloudsettingsPut("$currentPkgName.os_lang", device.lang, context)
vcloudsettingsPut("$currentPkgName.model", device.model, context);
vcloudsettingsPut("$currentPkgName.net", device.network, context)
vcloudsettingsPut("$currentPkgName.dpi", device.expand.dPI.toString(), context)
// callVCloudSettings_put(
// "$currentPkgName.rom_free_ext",
// romFreeExt.toString(),
// context
// )
// callVCloudSettings_put("$currentPkgName.dpi_f", dpiF, context)
// callVCloudSettings_put("$currentPkgName.cpu_core_num", cpuCoreNum.toString(), context)
// callVCloudSettings_put("$currentPkgName.cpu_clock_speed", cpuClockSpeed, context)
vcloudsettingsPut(currentPkgName + "_gaid", device.expand.gaid, context)
// **User-Agent**
// callVCloudSettings_put(currentPkgName + "_user_agent", userAgent, context)
// **os_lang**系统语言
vcloudsettingsPut(currentPkgName + "_os_lang", device.lang, context)
// **os_ver**
vcloudsettingsPut(currentPkgName + "_os_ver", device.sdkVer, context)
// **tz** (时区)
vcloudsettingsPut(currentPkgName + "_tz", device.tzOffTime.toString(), context)
val deviceInfo: DeviceInfo
val afDevice: AfInfo
if (afDeviceObject != null) {
val advertiserId = afDeviceObject.optString(".advertiserId")
val model = afDeviceObject.optString(".model")
val brand = afDeviceObject.optString(".brand")
val androidId = afDeviceObject.optString(".android_id")
val xPixels = afDeviceObject.optInt(".deviceData.dim.x_px")
val yPixels = afDeviceObject.optInt(".deviceData.dim.y_px")
val densityDpi = afDeviceObject.optInt(".deviceData.dim.d_dpi")
val country = afDeviceObject.optString(".country")
val batteryLevel = afDeviceObject.optString(".batteryLevel")
val stackInfo = Thread.currentThread().stackTrace[2].toString()
val product = afDeviceObject.optString(".product")
val network = afDeviceObject.optString(".network")
val langCode = afDeviceObject.optString(".lang_code")
val cpuAbi = afDeviceObject.optString(".deviceData.cpu_abi")
val yDp = afDeviceObject.optLong(".deviceData.dim.ydp")
vcloudsettingsPut("$currentPkgName.advertiserId", device.advertiserId, context)
vcloudsettingsPut("$currentPkgName.brand", device.brand, context)
vcloudsettingsPut("$currentPkgName.android_id", device.androidId, context)
vcloudsettingsPut("$currentPkgName.lang", device.lang, context)
vcloudsettingsPut("$currentPkgName.country", device.country, context)
vcloudsettingsPut(
"$currentPkgName.batteryLevel",
device.batteryLevel.toString(), context
)
// callVCloudSettings_put(
// currentPkgName + "_screen.optMetrics.stack",
// stackInfo,
// context
// )
vcloudsettingsPut("$currentPkgName.product", device.product, context)
vcloudsettingsPut("$currentPkgName.network", device.network, context)
vcloudsettingsPut("$currentPkgName.cpu_abi", device.cpuAbi, context)
vcloudsettingsPut("$currentPkgName.lang_code", device.langCode, context)
// **广告标识符 (advertiserId)** 及 **启用状态**
val isAdIdEnabled = true // 默认启用广告 ID
vcloudsettingsPut(
"$currentPkgName.advertiserIdEnabled",
isAdIdEnabled.toString(),
context
)
afDevice = AfInfo()
afDevice.advertiserId = advertiserId
afDevice.model = model
afDevice.brand = brand
afDevice.androidId = androidId
afDevice.xPixels = xPixels
afDevice.yPixels = yPixels
afDevice.densityDpi = densityDpi
afDevice.country = country
afDevice.batteryLevel = batteryLevel
afDevice.stackInfo = stackInfo
afDevice.product = product
afDevice.network = network
afDevice.langCode = langCode
afDevice.cpuAbi = cpuAbi
afDevice.yDp = yDp
// TaskUtil.setAfDevice(afDevice)
val displayMetrics = JSONObject()
val lang = afDeviceObject.optString(".lang")
val ro_product_brand = afDeviceObject.optString("ro.product.brand", "")
val ro_product_model = afDeviceObject.optString("ro.product.model", "")
val ro_product_manufacturer =
afDeviceObject.optString("ro.product.manufacturer", "")
val ro_product_device = afDeviceObject.optString("ro.product.device", "")
val ro_product_name = afDeviceObject.optString("ro.product.name", "")
val ro_build_version_incremental =
afDeviceObject.optString("ro.build.version.incremental", "")
val ro_build_fingerprint = afDeviceObject.optString("ro.build.fingerprint", "")
val ro_odm_build_fingerprint =
afDeviceObject.optString("ro.odm.build.fingerprint", "")
val ro_product_build_fingerprint =
afDeviceObject.optString("ro.product.build.fingerprint", "")
val ro_system_build_fingerprint =
afDeviceObject.optString("ro.system.build.fingerprint", "")
val ro_system_ext_build_fingerprint =
afDeviceObject.optString("ro.system_ext.build.fingerprint", "")
val ro_vendor_build_fingerprint =
afDeviceObject.optString("ro.vendor.build.fingerprint", "")
val ro_build_platform = afDeviceObject.optString("ro.board.platform", "")
val persist_sys_cloud_drm_id =
afDeviceObject.optString("persist.sys.cloud.drm.id", "")
val persist_sys_cloud_battery_capacity =
afDeviceObject.optInt("persist.sys.cloud.battery.capacity", -1)
val persist_sys_cloud_gpu_gl_vendor =
afDeviceObject.optString("persist.sys.cloud.gpu.gl_vendor", "")
val persist_sys_cloud_gpu_gl_renderer =
afDeviceObject.optString("persist.sys.cloud.gpu.gl_renderer", "")
val persist_sys_cloud_gpu_gl_version =
afDeviceObject.optString("persist.sys.cloud.gpu.gl_version", "")
val persist_sys_cloud_gpu_egl_vendor =
afDeviceObject.optString("persist.sys.cloud.gpu.egl_vendor", "")
val persist_sys_cloud_gpu_egl_version =
afDeviceObject.optString("persist.sys.cloud.gpu.egl_version", "")
val global_android_id = afDeviceObject.optString(".android_id", "")
val anticheck_pkgs = afDeviceObject.optString(".anticheck_pkgs", "")
val pm_list_features = afDeviceObject.optString(".pm_list_features", "")
val pm_list_libraries = afDeviceObject.optString(".pm_list_libraries", "")
val system_http_agent = afDeviceObject.optString("system.http.agent", "")
val webkit_http_agent = afDeviceObject.optString("webkit.http.agent", "")
val com_fk_tools_pkgInfo = afDeviceObject.optString(".pkg_info", "")
val appsflyerKey = afDeviceObject.optString(".appsflyerKey", "")
val appUserId = afDeviceObject.optString(".appUserId", "")
val disk = afDeviceObject.optString(".disk", "")
val operator = afDeviceObject.optString(".operator", "")
val cell_mcc = afDeviceObject.optString(".cell.mcc", "")
val cell_mnc = afDeviceObject.optString(".cell.mnc", "")
val date1 = afDeviceObject.optString(".date1", "")
val date2 = afDeviceObject.optString(".date2", "")
val bootId = afDeviceObject.optString("BootId", "")
displayMetrics.put("widthPixels", device.expand.width)
deviceInfo = DeviceInfo()
deviceInfo.lang = lang
deviceInfo.roProductBrand = ro_product_brand
deviceInfo.roProductModel = ro_product_model
deviceInfo.roProductManufacturer = ro_product_manufacturer
deviceInfo.roProductDevice = ro_product_device
deviceInfo.roProductName = ro_product_name
deviceInfo.roBuildVersionIncremental = ro_build_version_incremental
deviceInfo.roBuildFingerprint = ro_build_fingerprint
deviceInfo.roOdmBuildFingerprint = ro_odm_build_fingerprint
deviceInfo.roProductBuildFingerprint = ro_product_build_fingerprint
deviceInfo.roSystemBuildFingerprint = ro_system_build_fingerprint
deviceInfo.roSystemExtBuildFingerprint = ro_system_ext_build_fingerprint
deviceInfo.roVendorBuildFingerprint = ro_vendor_build_fingerprint
deviceInfo.roBuildPlatform = ro_build_platform
deviceInfo.persistSysCloudDrmId = persist_sys_cloud_drm_id
deviceInfo.persistSysCloudBatteryCapacity = persist_sys_cloud_battery_capacity
deviceInfo.persistSysCloudGpuGlVendor = persist_sys_cloud_gpu_gl_vendor
deviceInfo.persistSysCloudGpuGlRenderer = persist_sys_cloud_gpu_gl_renderer
deviceInfo.persistSysCloudGpuGlVersion = persist_sys_cloud_gpu_gl_version
deviceInfo.persistSysCloudGpuEglVendor = persist_sys_cloud_gpu_egl_vendor
deviceInfo.persistSysCloudGpuEglVersion = persist_sys_cloud_gpu_egl_version
// TaskUtil.setDeviceInfo(deviceInfo)
try {
callVCloudSettings_put("$current_pkg_name.advertiserId", advertiserId, context)
callVCloudSettings_put("$current_pkg_name.model", model, context)
callVCloudSettings_put("$current_pkg_name.brand", brand, context)
callVCloudSettings_put("$current_pkg_name.android_id", androidId, context)
callVCloudSettings_put("$current_pkg_name.lang", lang, context)
callVCloudSettings_put("$current_pkg_name.country", country, context)
callVCloudSettings_put("$current_pkg_name.batteryLevel", batteryLevel, context)
callVCloudSettings_put(
current_pkg_name + "_screen.optMetrics.stack",
stackInfo,
context
)
callVCloudSettings_put("$current_pkg_name.product", product, context)
callVCloudSettings_put("$current_pkg_name.network", network, context)
callVCloudSettings_put("$current_pkg_name.cpu_abi", cpuAbi, context)
callVCloudSettings_put("$current_pkg_name.lang_code", langCode, context)
// **广告标识符 (advertiserId)** 及 **启用状态**
val isAdIdEnabled = true // 默认启用广告 ID
callVCloudSettings_put(
"$current_pkg_name.advertiserIdEnabled",
isAdIdEnabled.toString(),
context
)
displayMetrics.put("heightPixels", device.expand.height)
displayMetrics.put("densityDpi", device.expand.dPI)
displayMetrics.put("yDp", device.dim.ydp)
vcloudsettingsPut(
"screen.device.displayMetrics",
displayMetrics.toString(),
context
)
val displayMetrics = JSONObject()
displayMetrics.put("widthPixels", xPixels)
displayMetrics.put("heightPixels", yPixels)
displayMetrics.put("densityDpi", densityDpi)
displayMetrics.put("yDp", yDp)
callVCloudSettings_put(
"screen.device.displayMetrics",
displayMetrics.toString(),
context
)
if (!ShellUtils.hasRootAccess()) {
LogUtils.d(
"ERROR",
"ChangeDeviceInfoUtil",
"Root access is required to execute system property changes"
)
}
// 设置机型, 直接设置属性
ShellUtils.execRootCmd("setprop ro.product.brand $ro_product_brand")
ShellUtils.execRootCmd("setprop ro.product.model $ro_product_model")
ShellUtils.execRootCmd("setprop ro.product.manufacturer $ro_product_manufacturer")
ShellUtils.execRootCmd("setprop ro.product.device $ro_product_device")
ShellUtils.execRootCmd("setprop ro.product.name $ro_product_name")
ShellUtils.execRootCmd("setprop ro.build.version.incremental $ro_build_version_incremental")
ShellUtils.execRootCmd("setprop ro.build.fingerprint $ro_build_fingerprint")
ShellUtils.execRootCmd("setprop ro.odm.build.fingerprint $ro_odm_build_fingerprint")
ShellUtils.execRootCmd("setprop ro.product.build.fingerprint $ro_product_build_fingerprint")
ShellUtils.execRootCmd("setprop ro.system.build.fingerprint $ro_system_build_fingerprint")
ShellUtils.execRootCmd("setprop ro.system_ext.build.fingerprint $ro_system_ext_build_fingerprint")
ShellUtils.execRootCmd("setprop ro.vendor.build.fingerprint $ro_vendor_build_fingerprint")
ShellUtils.execRootCmd("setprop ro.board.platform $ro_build_platform")
// Native.setBootId(bootId);
// 修改drm id
ShellUtils.execRootCmd("setprop persist.sys.cloud.drm.id $persist_sys_cloud_drm_id")
// 电量模拟需要大于1000
ShellUtils.execRootCmd("setprop persist.sys.cloud.battery.capacity $persist_sys_cloud_battery_capacity")
ShellUtils.execRootCmd("setprop persist.sys.cloud.gpu.gl_vendor $persist_sys_cloud_gpu_gl_vendor")
ShellUtils.execRootCmd("setprop persist.sys.cloud.gpu.gl_renderer $persist_sys_cloud_gpu_gl_renderer")
// 这个值不能随便改 必须是 OpenGL ES %d.%d 这个格式
ShellUtils.execRootCmd("setprop persist.sys.cloud.gpu.gl_version $persist_sys_cloud_gpu_gl_version")
ShellUtils.execRootCmd("setprop persist.sys.cloud.gpu.egl_vendor $persist_sys_cloud_gpu_egl_vendor")
ShellUtils.execRootCmd("setprop persist.sys.cloud.gpu.egl_version $persist_sys_cloud_gpu_egl_version")
} catch (e: Throwable) {
LogUtils.e(
Log.ERROR,
"ChangeDeviceInfoUtil",
"Error occurred in changeDeviceInfo",
e
)
throw RuntimeException("Error occurred in changeDeviceInfo", e)
}
afDeviceObject = null
if (!ShellUtils.hasRootAccess()) {
LogUtils.d(
"ERROR",
"ChangeDeviceInfoUtil",
"Root access is required to execute system property changes"
)
}
} catch (e: Exception) {
e.printStackTrace()
// 设置机型, 直接设置属性
ShellUtils.execRootCmd("setprop ro.product.brand ${device.brand}")
ShellUtils.execRootCmd("setprop ro.product.model ${device.model}")
ShellUtils.execRootCmd("setprop ro.product.manufacturer ${device.manufactor}")
ShellUtils.execRootCmd("setprop ro.product.device ${device.device}")
ShellUtils.execRootCmd("setprop ro.product.name ${device.expand.roProductName}")
ShellUtils.execRootCmd("setprop ro.build.version.incremental ${device.expand.incremental}")
ShellUtils.execRootCmd("setprop ro.build.fingerprint ${device.expand.roBootimageBuildFingerprint}")
ShellUtils.execRootCmd("setprop ro.odm.build.fingerprint ${device.expand.roBootimageBuildFingerprint}")
ShellUtils.execRootCmd("setprop ro.product.build.fingerprint ${device.expand.roBootimageBuildFingerprint}")
ShellUtils.execRootCmd("setprop ro.system.build.fingerprint ${device.expand.roBootimageBuildFingerprint}")
ShellUtils.execRootCmd("setprop ro.system_ext.build.fingerprint ${device.expand.roBootimageBuildFingerprint}")
ShellUtils.execRootCmd("setprop ro.vendor.build.fingerprint ${device.expand.roBootimageBuildFingerprint}")
ShellUtils.execRootCmd("setprop ro.board.platform ${device.expand.roBoardPlatform}")
// 修改drm id
// ShellUtils.execRootCmd("setprop persist.sys.cloud.drm.id $persist_sys_cloud_drm_id")
// 电量模拟需要大于1000
ShellUtils.execRootCmd("setprop persist.sys.cloud.battery.capacity ${device.battery}")
ShellUtils.execRootCmd("setprop persist.sys.cloud.gpu.gl_vendor ${device.expand.glVendor}")
ShellUtils.execRootCmd("setprop persist.sys.cloud.gpu.gl_renderer ${device.expand.glRenderer}")
// 这个值不能随便改 必须是 OpenGL ES %d.%d 这个格式
ShellUtils.execRootCmd("setprop persist.sys.cloud.gpu.gl_version ${device.expand.glVersion}")
// ShellUtils.execRootCmd("setprop persist.sys.cloud.gpu.egl_vendor $persist_sys_cloud_gpu_egl_vendor")
// ShellUtils.execRootCmd("setprop persist.sys.cloud.gpu.egl_version $persist_sys_cloud_gpu_egl_version")
} catch (e: Throwable) {
LogUtils.e(
Log.ERROR,
"ChangeDeviceInfoUtil",
"Error occurred while changing device info",
e
)
throw RuntimeException("Error occurred in changeDeviceInfo", e)
}
}
private fun callVCloudSettings_put(key: String?, value: String?, context: Context?) {
if (context == null) {
LogUtils.e(Log.ERROR, "ChangeDeviceInfoUtil", "Context cannot be null", null)
throw IllegalArgumentException("Context cannot be null")
}
if (key == null || key.isEmpty()) {
private fun vcloudsettingsPut(key: String?, value: String?, context: Context) {
if (key.isNullOrEmpty()) {
LogUtils.e(Log.ERROR, "ChangeDeviceInfoUtil", "Key cannot be null or empty", null)
throw IllegalArgumentException("Key cannot be null or empty")
}
@ -350,15 +171,11 @@ object ChangeDeviceInfoUtil {
Log.d("Debug", "putString executed successfully.")
} catch (e: ClassNotFoundException) {
LogUtils.e(
Log.WARN,
"ChangeDeviceInfoUtil",
"Class not found: android.provider.VCloudSettings\$Global. This may not be supported on this device.",
e
)
} catch (e: NoSuchMethodException) {
LogUtils.e(
Log.WARN,
"ChangeDeviceInfoUtil",
"Method not found: android.provider.VCloudSettings\$Global.putString. This may not be supported on this",
e
)
@ -366,23 +183,17 @@ object ChangeDeviceInfoUtil {
val cause = e.targetException
if (cause is SecurityException) {
LogUtils.e(
Log.ERROR,
"ChangeDeviceInfoUtil",
"Error occurred in changeDeviceInfo",
cause
)
} else {
LogUtils.e(
Log.ERROR,
"ChangeDeviceInfoUtil",
"Error occurred in changeDeviceInfo",
cause
)
}
} catch (e: Exception) {
LogUtils.e(
Log.ERROR,
"ChangeDeviceInfoUtil",
"Unexpected error during putString invocation",
e
)

View File

@ -77,9 +77,19 @@ object ClashUtil {
}
fun unregisterReceiver(context: Context) {
context.unregisterReceiver(clashStatusReceiver)
try {
context.unregisterReceiver(clashStatusReceiver)
} catch (e: Exception) {
e.printStackTrace()
}
}
fun getRandomLocale(): String {
val locales = listOf("ru", "us")
return locales.random()
}
fun switchProxyGroup(groupName: String?, proxyName: String?, controllerUrl: String) {
if (groupName == null || groupName.trim { it <= ' ' }
.isEmpty() || proxyName == null || proxyName.trim { it <= ' ' }.isEmpty()) {

View File

@ -0,0 +1,72 @@
package com.android.grape.util
import android.os.Build
import androidx.annotation.RequiresApi
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.google.gson.JsonDeserializer
import com.google.gson.JsonObject
import com.google.gson.reflect.TypeToken
import java.lang.reflect.Type
import java.util.*
object GsonUtils {
// 可配置的Gson实例
private val gson: Gson by lazy {
GsonBuilder()
// .setDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'")
.create()
}
// 自定义Gson实例
private val customGson = mutableMapOf<String, Gson>()
/**
* 通用转换函数
* @param json JsonObject
* @param clazz 目标类型
*/
fun <T> fromJsonObject(json: String, clazz: Class<T>): T {
return gson.fromJson(json, clazz)
}
/**
* 通用转换函数带泛型
* @param json JsonObject
* @param typeToken 类型Token
*/
fun <T> fromJsonObject(json: JsonObject, typeToken: TypeToken<T>): T {
return gson.fromJson(json, typeToken.type)
}
/**
* 使用自定义适配器转换
* @param json JsonObject
* @param clazz 目标类型
* @param adapters 适配器列表
*/
@RequiresApi(Build.VERSION_CODES.P)
fun <T> fromJsonObject(
json: JsonObject,
clazz: Class<T>,
vararg adapters: Pair<Type, Any>
): T {
val gsonKey = clazz.name + adapters.joinToString { it.first.typeName }
val customGsonInstance = customGson[gsonKey] ?: buildCustomGson(adapters).also {
customGson[gsonKey] = it
}
return customGsonInstance.fromJson(json, clazz)
}
private fun buildCustomGson(adapters: Array<out Pair<Type, Any>>): Gson {
val builder = GsonBuilder()
adapters.forEach { (type, adapter) ->
when (adapter) {
is JsonDeserializer<*> -> builder.registerTypeAdapter(type, adapter)
else -> throw IllegalArgumentException("Unsupported adapter type")
}
}
return builder.create()
}
}

View File

@ -0,0 +1,49 @@
package com.android.grape.util;
import android.content.Context;
import android.provider.Settings;
import android.util.Log;
import com.blankj.utilcode.util.LogUtils;
import org.json.JSONObject;
import java.nio.charset.StandardCharsets;
public class HttpUtil {
public static void execReloginTask(Context context) {
String url = "http://39.103.73.250/tt/ddj/preRequest!requestRelogin.do";
String ANDROID_ID = Settings.System.getString(context.getContentResolver(), Settings.System.ANDROID_ID);
String params = "platform=Android&tag=" + "119" + "&uuid=" + ANDROID_ID;
System.out.println("IOSTQ:execReloginTask->url:" + url + "?" + params);
try {
String result = new MyPost().PostData(context, " ".getBytes(StandardCharsets.UTF_8), url + "?" + params);
LogUtils.e("request result : " + result);
} catch (Exception e) {
Log.i("TAG", "execTask error:" + e.getMessage());
}
}
public static void execInstallTask(Context context) {
String url = "http://39.103.73.250/tt/ddj/preRequest!requestInstall.do";
String ANDROID_ID = Settings.System.getString(context.getContentResolver(), Settings.System.ANDROID_ID);
String params = "platform=Android&tag=" + "119" + "&uuid=" + ANDROID_ID;
LogUtils.e("IOSTQ:request result : " + url + "?" + params);
try {
String result = new MyPost().PostData(context, " ".getBytes("utf-8"), url + "?" + params);
LogUtils.e("request result : " + result);
} catch (Exception e) {
e.printStackTrace();
LogUtils.i("TAG", "execTask error:" + e.getMessage());
}
}
}

View File

@ -0,0 +1,168 @@
package com.android.grape.util;
import android.content.Context;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
public class MyPost {
private static final String TAG = "MyPost";
// private static final String TAG = TT.dd("4B7HyaIsVcs=");
private static okhttp3.OkHttpClient oklient = new okhttp3.OkHttpClient();
public String PostData(Context context, byte[] byt, String url_) {
byte[] bytes = postDataBytes(context, byt, url_);
if (bytes != null && bytes.length > 0) {
try {
return new String(bytes, "UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
return null;
}
public String PostDataCommon(Context context, byte[] byt, String url_) {
byte[] bytes = postDataBytes(context, byt, url_);
if (bytes != null && bytes.length >= 0) {
if (bytes.length == 0) {
return "";
} else {
try {
return new String(bytes, "UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
}
return null;
}
public String httpGet(String url) throws Exception {
URL obj = new URL(url);
HttpURLConnection con = (HttpURLConnection) obj.openConnection();
//默认值我GET
con.setRequestMethod("GET");
int responseCode = con.getResponseCode();
System.out.println("\nSending 'GET' request to URL : " + url);
System.out.println("Response Code : " + responseCode);
BufferedReader in = new BufferedReader(
new InputStreamReader(con.getInputStream()));
String inputLine;
StringBuffer response = new StringBuffer();
while ((inputLine = in.readLine()) != null) {
response.append(inputLine);
}
in.close();
//打印结果
System.out.println(response.toString());
return response.toString();
}
public byte[] postDataBytes(Context context, byte[] byt, String url_) {
String result = null;
HttpURLConnection httpUrlConnection = null;
InputStream inStrm = null;
ByteArrayOutputStream baos = null;
BufferedInputStream bis = null;
try {
httpUrlConnection = NetWorkUtils.getHttpURLConnection(context, url_);
httpUrlConnection.setAllowUserInteraction(true);
httpUrlConnection.setDoOutput(true);
httpUrlConnection.setDoInput(true);
httpUrlConnection.setUseCaches(false);
httpUrlConnection.setRequestProperty("Connection", "close");//add 20200428
httpUrlConnection.setRequestProperty("Content-type", "application/x-java-serialized-object");
// httpUrlConnection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
httpUrlConnection.setRequestMethod("POST");
httpUrlConnection.setConnectTimeout(20000);
OutputStream outStrm = null;
outStrm = httpUrlConnection.getOutputStream();
outStrm.write(byt);
outStrm.flush();
outStrm.close();
inStrm = httpUrlConnection.getInputStream();
baos = new ByteArrayOutputStream();
bis = new BufferedInputStream(inStrm);
byte[] buf = new byte[1024];
int readSize = -1;
while ((readSize = bis.read(buf)) != -1) {
baos.write(buf, 0, readSize);
}
byte[] data = baos.toByteArray();
return data;
} catch (Exception e) {
e.printStackTrace();
result = null;
} finally {
if (baos != null) {
try {
baos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (bis != null) {
try {
bis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (inStrm != null) {
try {
inStrm.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (httpUrlConnection != null) {
httpUrlConnection.disconnect();
httpUrlConnection = null;
}
System.gc();
}
return null;
}
}

View File

@ -0,0 +1,27 @@
package com.android.grape.util;
import android.content.Context;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
public class NetWorkUtils {
public static HttpURLConnection getHttpURLConnection(Context context,
String _url) throws IOException {
return getHttpURLConnection(context, _url, false);
}
private static int dynamicPort0 = 20380;
public static HttpURLConnection getHttpURLConnection(Context context, String _url, boolean isProxy) throws IOException {
URL url = new URL(_url);
HttpURLConnection httpUrlConnection = null;
httpUrlConnection = (HttpURLConnection)url.openConnection();
return httpUrlConnection;
}
}

View File

@ -0,0 +1,309 @@
package com.android.grape.util
import android.Manifest
import android.app.NotificationManager
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build
import android.provider.Settings
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.RequiresApi
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.app.NotificationManagerCompat
import androidx.core.content.ContextCompat
/**
* 通知权限管理工具
*
* 使用示例
*
* class MainActivity : AppCompatActivity() {
* private lateinit var permissionHandler: NotificationPermissionHandler
*
* override fun onCreate(savedInstanceState: Bundle?) {
* super.onCreate(savedInstanceState)
*
* // 初始化权限处理器
* permissionHandler = NotificationPermissionHandler(this, ::onPermissionResult)
*
* // 检查通知权限
* if (permissionHandler.shouldRequestNotificationPermission()) {
* permissionHandler.requestNotificationPermission()
* }
* }
*
* override fun onResume() {
* super.onResume()
* // 处理从设置返回的情况
* permissionHandler.handleReturnFromSettings()
* }
*
* // 权限回调处理
* private fun onPermissionResult(result: PermissionResult) {
* when(result) {
* PermissionResult.GRANTED -> showNotification()
* PermissionResult.DENIED -> showRationale()
* PermissionResult.NEEDS_SETTINGS -> showSettingsGuide()
* PermissionResult.NOT_NEEDED -> showNotification()
* }
* }
* }
*/
class NotificationPermissionHandler(
private val context: Context,
private val callback: (PermissionResult) -> Unit
) {
// Android 13+ 通知权限请求启动器
private var permissionLauncher: ActivityResultLauncher<String>? = null
// 请求状态跟踪
private var permissionRequestPending = false
private var lastPermissionState = false
// 用于跟踪从设置页面返回的状态
private var returnFromSettings = false
init {
registerPermissionLauncherIfNeeded()
}
/**
* 检查是否需要请求通知权限
*/
fun shouldRequestNotificationPermission(): Boolean {
return !areNotificationsEnabled()
}
/**
* 检查通知权限是否已授予
*/
fun areNotificationsEnabled(): Boolean {
return when {
// Android 13+ (TIRAMISU) 需要运行时权限
Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU -> {
ContextCompat.checkSelfPermission(
context,
Manifest.permission.POST_NOTIFICATIONS
) == PackageManager.PERMISSION_GRANTED
}
// Android 8.0+ (Oreo) 检查通知设置
Build.VERSION.SDK_INT >= Build.VERSION_CODES.O -> {
NotificationManagerCompat.from(context).areNotificationsEnabled()
}
// Android 7.1 及以下不需要特别权限
else -> true
}
}
/**
* 请求通知权限
*/
fun requestNotificationPermission() {
// 保存当前权限状态,用于比较设置后是否发生变化
lastPermissionState = areNotificationsEnabled()
when {
// Android 13+ - 使用运行时权限请求
Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU -> {
requestPermissionApi33()
}
// Android 8.0-12 - 跳转到应用通知设置
Build.VERSION.SDK_INT >= Build.VERSION_CODES.O -> {
openNotificationSettings()
permissionRequestPending = true
}
// Android 7.1 及以下 - 默认启用
else -> {
callback(PermissionResult.NOT_NEEDED)
}
}
}
/**
* 处理从设置页面返回的情况
* 应在Activity的onResume中调用
*/
fun handleReturnFromSettings() {
if (returnFromSettings) {
returnFromSettings = false
checkSettingsChanged()
}
}
/**
* 打开应用的通知设置页面
*/
fun openNotificationSettings() {
val intent = when {
// Android 8.0及以上
Build.VERSION.SDK_INT >= Build.VERSION_CODES.O -> {
Intent().apply {
action = Settings.ACTION_APP_NOTIFICATION_SETTINGS
putExtra(Settings.EXTRA_APP_PACKAGE, context.packageName)
// 处理特定设备需要额外设置的情况
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
putExtra(Settings.EXTRA_CHANNEL_ID, context.applicationInfo.uid)
}
}
}
// Android 7.1及以下
else -> {
Intent().apply {
action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS
addCategory(Intent.CATEGORY_DEFAULT)
data = Uri.parse("package:${context.packageName}")
}
}
}
if (context is AppCompatActivity) {
returnFromSettings = true
context.startActivity(intent)
}
}
/**
* 显示权限解释对话框
*/
fun showPermissionRationaleDialog() {
if (context !is AppCompatActivity) return
val activity = context as AppCompatActivity
AlertDialog.Builder(activity)
.setTitle("需要通知权限")
.setMessage("我们需要通知权限来向你发送重要信息、更新提醒等内容。请允许通知权限以使用完整功能。")
.setPositiveButton("确定") { _, _ ->
requestNotificationPermission()
}
.setNegativeButton("取消", null)
.show()
}
// 私有方法 --------------------------------------------
/**
* Android 13+ 权限请求处理
*/
@RequiresApi(33)
private fun requestPermissionApi33() {
if (permissionLauncher == null) {
throw IllegalStateException("Permission launcher not registered. Call registerPermissionLauncherIfNeeded() first.")
}
val activity = context as? AppCompatActivity ?: return
when {
// 已经拥有权限
areNotificationsEnabled() -> {
callback(PermissionResult.GRANTED)
}
// 需要显示权限解释
ActivityCompat.shouldShowRequestPermissionRationale(
activity,
Manifest.permission.POST_NOTIFICATIONS
) -> {
showPermissionRationaleDialog()
}
// 请求权限
else -> {
permissionRequestPending = true
permissionLauncher?.launch(Manifest.permission.POST_NOTIFICATIONS)
}
}
}
/**
* 注册权限启动器仅Android 13+需要
*/
private fun registerPermissionLauncherIfNeeded() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) return
if (context !is AppCompatActivity) return
val activity = context as AppCompatActivity
permissionLauncher = activity.registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { isGranted ->
if (isGranted) {
callback(PermissionResult.GRANTED)
} else {
// 如果用户勾选了"不再询问"
if (!ActivityCompat.shouldShowRequestPermissionRationale(
activity,
Manifest.permission.POST_NOTIFICATIONS
)) {
callback(PermissionResult.NEEDS_SETTINGS)
} else {
callback(PermissionResult.DENIED)
}
}
permissionRequestPending = false
}
}
/**
* 检查设置页面返回后的权限变化
*/
private fun checkSettingsChanged() {
if (!permissionRequestPending) return
permissionRequestPending = false
val currentState = areNotificationsEnabled()
when {
currentState -> callback(PermissionResult.GRANTED)
currentState != lastPermissionState -> callback(PermissionResult.DENIED)
// 状态未改变
else -> Unit // 无操作
}
}
/**
* 权限请求结果枚举
*/
enum class PermissionResult {
GRANTED, // 权限已授予
DENIED, // 权限被拒绝
NEEDS_SETTINGS, // 需要跳转到设置
NOT_NEEDED // 不需要权限Android 7.1及以下)
}
companion object {
/**
* 快速检查当前设备的通知权限状态
*/
@JvmStatic
fun checkPermissionState(context: Context): PermissionResult {
return when {
// Android 13+ 需要运行时权限
Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU -> {
if (ContextCompat.checkSelfPermission(
context,
Manifest.permission.POST_NOTIFICATIONS
) == PackageManager.PERMISSION_GRANTED) {
PermissionResult.GRANTED
} else {
PermissionResult.DENIED
}
}
// Android 8.0+ 检查通知设置
Build.VERSION.SDK_INT >= Build.VERSION_CODES.O -> {
if (NotificationManagerCompat.from(context).areNotificationsEnabled()) {
PermissionResult.GRANTED
} else {
PermissionResult.NEEDS_SETTINGS
}
}
// Android 7.1 及以下默认启用通知
else -> PermissionResult.NOT_NEEDED
}
}
}
}

View File

@ -696,7 +696,7 @@ object Util {
val valid = false
println("IOSTQ:execReloginTask->url:$url?$params")
try {
val result: String? = MyPost.postData(" ".toByteArray(charset("utf-8")),
val result: String? = MyPost.postData(" ".toByteArray(charset = Charsets.UTF_8),
"$url?$params"
)
@ -748,7 +748,7 @@ object Util {
// boolean valid = false;
printStr("IOSTQ:request result : $url?$params")
try {
val result: String? = MyPost.postData(" ".toByteArray(charset("utf-8")),
val result: String? = MyPost.postData(" ".toByteArray(charset = Charsets.UTF_8),
"$url?$params"
)
@ -2394,7 +2394,7 @@ object Util {
// Log.i(TAG, "setFinish");
MonitorService.setRunning(false)
killRecordProcess(context, "com.tunnelworkshop.postern")
// killRecordProcess(context, "com.tunnelworkshop.postern")
killRecordProcess(context, AUTO_JSPACKAGENAME)
Handler(Looper.getMainLooper()).postDelayed(Runnable {
MonitorService.onEvent(

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="true">
<trust-anchors>
<certificates src="system" />
<certificates src="user" />
</trust-anchors>
</base-config>
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">127.0.0.1</domain>
</domain-config>
</network-security-config>