Compare commits
No commits in common. "727df6f21ff54d4469a3271392b3387c11649b69" and "ef7f148e1354a100598d3ca2765fb175ff06ff49" have entirely different histories.
727df6f21f
...
ef7f148e13
|
@ -4,14 +4,6 @@
|
||||||
<selectionStates>
|
<selectionStates>
|
||||||
<SelectionState runConfigName="app">
|
<SelectionState runConfigName="app">
|
||||||
<option name="selectionMode" value="DROPDOWN" />
|
<option name="selectionMode" value="DROPDOWN" />
|
||||||
<DropdownSelection timestamp="2025-07-07T06:37:35.266033300Z">
|
|
||||||
<Target type="DEFAULT_BOOT">
|
|
||||||
<handle>
|
|
||||||
<DeviceId pluginId="Default" identifier="serial=8.217.74.194:8924;connection=1af33e42" />
|
|
||||||
</handle>
|
|
||||||
</Target>
|
|
||||||
</DropdownSelection>
|
|
||||||
<DialogSelection />
|
|
||||||
</SelectionState>
|
</SelectionState>
|
||||||
</selectionStates>
|
</selectionStates>
|
||||||
</component>
|
</component>
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="GradleMigrationSettings" migrationVersion="1" />
|
|
||||||
<component name="GradleSettings">
|
<component name="GradleSettings">
|
||||||
<option name="linkedExternalProjectsSettings">
|
<option name="linkedExternalProjectsSettings">
|
||||||
<GradleProjectSettings>
|
<GradleProjectSettings>
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK">
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK">
|
||||||
|
|
|
@ -1,47 +1,21 @@
|
||||||
-keep class com.google.gson.** { *; }
|
# Add project specific ProGuard rules here.
|
||||||
-keep class com.google.gson.stream.** { *; }
|
# You can control the set of applied configuration files using the
|
||||||
|
# proguardFiles setting in build.gradle.
|
||||||
|
#
|
||||||
|
# For more details, see
|
||||||
|
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||||
|
|
||||||
# 保留所有注解
|
# If your project uses WebView with JS, uncomment the following
|
||||||
-keepattributes *Annotation*
|
# and specify the fully qualified class name to the JavaScript interface
|
||||||
-keepattributes Signature
|
# class:
|
||||||
|
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||||
|
# public *;
|
||||||
|
#}
|
||||||
|
|
||||||
# 保留枚举类
|
# Uncomment this to preserve the line number information for
|
||||||
-keepclassmembers enum * {
|
# debugging stack traces.
|
||||||
public static **[] values();
|
#-keepattributes SourceFile,LineNumberTable
|
||||||
public static ** valueOf(java.lang.String);
|
|
||||||
}
|
|
||||||
|
|
||||||
# 保留所有模型类(根据你的包结构调整)
|
# If you keep the line number information, uncomment this to
|
||||||
-keep class com.android.grape.pad.** { *; }
|
# hide the original source file name.
|
||||||
-keep class com.android.grape.net.ApiResponse{ *; }
|
#-renamesourcefileattribute SourceFile
|
||||||
-keep class com.android.grape.net.ApiResponseList{ *; }
|
|
||||||
|
|
||||||
# 保留所有使用 @SerializedName 注解的字段
|
|
||||||
-keepclassmembers class * {
|
|
||||||
@com.google.gson.annotations.SerializedName <fields>;
|
|
||||||
}
|
|
||||||
|
|
||||||
# 保留所有模型类的无参构造函数
|
|
||||||
-keepclassmembers class com.android.grape.pad.** {
|
|
||||||
public <init>();
|
|
||||||
}
|
|
||||||
|
|
||||||
# 保留类型适配器
|
|
||||||
-keep class * extends com.google.gson.TypeAdapter {
|
|
||||||
public com.google.gson.TypeAdapter create(com.google.gson.Gson, com.google.gson.reflect.TypeToken);
|
|
||||||
}
|
|
||||||
# 保留 Gson 创建的类
|
|
||||||
-keep class com.google.gson.examples.android.model.** { *; }
|
|
||||||
-keepattributes Signature
|
|
||||||
|
|
||||||
# 保留 TypeToken 类及其子类
|
|
||||||
-keep class com.google.gson.reflect.TypeToken { *; }
|
|
||||||
-keep class * extends com.google.gson.reflect.TypeToken
|
|
||||||
|
|
||||||
-keep class sun.misc.Unsafe { *; }
|
|
||||||
|
|
||||||
# 保留注解信息
|
|
||||||
-keepattributes *Annotation*
|
|
||||||
|
|
||||||
# 保留 Kotlin 元数据(如果使用 Kotlin)
|
|
||||||
-keepclassmembers class **$TypeToken { *; }
|
|
|
@ -9,7 +9,8 @@
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||||
<uses-permission android:name="android.permission.BIND_VPN_SERVICE"
|
<uses-permission android:name="android.permission.BIND_VPN_SERVICE"
|
||||||
tools:ignore="ProtectedPermissions" />
|
tools:ignore="ProtectedPermissions" />
|
||||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
|
<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.SYSTEM_ALERT_WINDOW" />
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<uses-permission android:name="android.permission.VPN_SERVICE" />
|
<uses-permission android:name="android.permission.VPN_SERVICE" />
|
||||||
|
@ -48,11 +49,6 @@
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/Theme.AndroidGrape"
|
android:theme="@style/Theme.AndroidGrape"
|
||||||
tools:targetApi="31">
|
tools:targetApi="31">
|
||||||
<provider
|
|
||||||
android:name=".provider.DeviceInfoProvider"
|
|
||||||
android:authorities="com.android.grape.deviceinfo.provider"
|
|
||||||
android:exported="true">
|
|
||||||
</provider>
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
android:exported="true">
|
android:exported="true">
|
||||||
|
|
|
@ -3,7 +3,6 @@ package com.android.grape
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.util.Log
|
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.activity.enableEdgeToEdge
|
import androidx.activity.enableEdgeToEdge
|
||||||
import androidx.activity.viewModels
|
import androidx.activity.viewModels
|
||||||
|
@ -12,19 +11,11 @@ import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.core.view.ViewCompat
|
import androidx.core.view.ViewCompat
|
||||||
import androidx.core.view.WindowInsetsCompat
|
import androidx.core.view.WindowInsetsCompat
|
||||||
import com.android.grape.databinding.ActivityMainBinding
|
import com.android.grape.databinding.ActivityMainBinding
|
||||||
import com.android.grape.job.MonitorService
|
|
||||||
import com.android.grape.provider.DeviceDataAccessor
|
|
||||||
import com.android.grape.provider.DeviceInfoHelper
|
|
||||||
import com.android.grape.util.ClashUtil
|
import com.android.grape.util.ClashUtil
|
||||||
import com.android.grape.util.MockTools
|
|
||||||
import com.android.grape.util.NotificationPermissionHandler
|
import com.android.grape.util.NotificationPermissionHandler
|
||||||
import com.android.grape.util.ScriptUtil
|
import com.android.grape.util.ScriptUtil
|
||||||
import com.android.grape.util.ShellUtils
|
|
||||||
import com.android.grape.util.StoragePermissionHelper
|
import com.android.grape.util.StoragePermissionHelper
|
||||||
import com.android.grape.util.Util
|
import com.blankj.utilcode.util.LogUtils
|
||||||
import com.android.grape.util.Util.AUTO_JSPACKAGENAME
|
|
||||||
import com.android.grape.util.Util.killRecordProcess
|
|
||||||
import com.google.gson.Gson
|
|
||||||
|
|
||||||
class MainActivity : AppCompatActivity() {
|
class MainActivity : AppCompatActivity() {
|
||||||
private val viewModel by viewModels<MainViewModel>()
|
private val viewModel by viewModels<MainViewModel>()
|
||||||
|
@ -33,7 +24,6 @@ class MainActivity : AppCompatActivity() {
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
enableEdgeToEdge()
|
enableEdgeToEdge()
|
||||||
init()
|
|
||||||
viewBinding = ActivityMainBinding.inflate(layoutInflater)
|
viewBinding = ActivityMainBinding.inflate(layoutInflater)
|
||||||
setContentView(viewBinding.root)
|
setContentView(viewBinding.root)
|
||||||
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
|
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
|
||||||
|
@ -44,23 +34,28 @@ class MainActivity : AppCompatActivity() {
|
||||||
checkPermission()
|
checkPermission()
|
||||||
ScriptUtil.registerScriptResultReceiver()
|
ScriptUtil.registerScriptResultReceiver()
|
||||||
viewBinding.start.setOnClickListener {
|
viewBinding.start.setOnClickListener {
|
||||||
MonitorService.onEvent(MainApplication.instance)
|
try {
|
||||||
// DeviceDataAccessor.saveDeviceInfo(this, DeviceInfoHelper.getDeviceId(this))
|
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"),
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
viewBinding.stop.setOnClickListener {
|
viewBinding.stop.setOnClickListener {
|
||||||
killRecordProcess(this, packageName)
|
ClashUtil.stopProxy(this)
|
||||||
// DeviceDataAccessor.deleteDeviceInfo(this, DeviceInfoHelper.getDeviceId(this))
|
// AutoJsUtil.stopAutojsScript(this)
|
||||||
// val deviceInfo = DeviceDataAccessor.getDeviceInfo(this, DeviceInfoHelper.getDeviceId(this))
|
|
||||||
// Log.d("TAG", "onCreate: ${Gson().toJson(deviceInfo?:"")}")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun init() {
|
|
||||||
MockTools.exec("pm grant com.android.grape android.permission.INTERACT_ACROSS_USERS")
|
|
||||||
MockTools.exec("pm grant com.android.grape android.permission.WRITE_SECURE_SETTINGS")
|
|
||||||
MockTools.exec("pm setenforce 0")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
ClashUtil.unregisterReceiver(this)
|
ClashUtil.unregisterReceiver(this)
|
||||||
|
|
|
@ -3,7 +3,6 @@ package com.android.grape
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.work.PeriodicWorkRequest
|
import androidx.work.PeriodicWorkRequest
|
||||||
import androidx.work.WorkManager
|
import androidx.work.WorkManager
|
||||||
import com.android.grape.job.MonitorService
|
|
||||||
import com.android.grape.service.MyAccessibilityService
|
import com.android.grape.service.MyAccessibilityService
|
||||||
import com.android.grape.work.CheckAccessibilityWorker
|
import com.android.grape.work.CheckAccessibilityWorker
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
@ -22,5 +21,6 @@ class MainViewModel:ViewModel() {
|
||||||
.build()
|
.build()
|
||||||
WorkManager.getInstance(MainApplication.instance).enqueue(workRequest)
|
WorkManager.getInstance(MainApplication.instance).enqueue(workRequest)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
package com.android.grape.data
|
||||||
|
|
||||||
|
class AfInfo {
|
||||||
|
var advertiserId: String? = null
|
||||||
|
var model: String? = null
|
||||||
|
var brand: String? = null
|
||||||
|
var androidId: String? = null
|
||||||
|
var xPixels: Int = 0
|
||||||
|
var yPixels: Int = 0
|
||||||
|
var densityDpi: Int = 0
|
||||||
|
var country: String? = null
|
||||||
|
var batteryLevel: String? = null
|
||||||
|
var stackInfo: String? = null
|
||||||
|
var product: String? = null
|
||||||
|
var network: String? = null
|
||||||
|
var langCode: String? = null
|
||||||
|
var cpuAbi: String? = null
|
||||||
|
var yDp: Long = 0
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
package com.android.grape.data
|
||||||
|
|
||||||
|
class BigoInfo {
|
||||||
|
var cpuClockSpeed: String? = null
|
||||||
|
var gaid: String? = null
|
||||||
|
var userAgent: String? = null
|
||||||
|
var osLang: String? = null
|
||||||
|
var osVer: String? = null
|
||||||
|
var tz: String? = null
|
||||||
|
var systemCountry: String? = null
|
||||||
|
var simCountry: String? = null
|
||||||
|
var romFreeIn: Long = 0
|
||||||
|
var resolution: String? = null
|
||||||
|
var vendor: String? = null
|
||||||
|
var batteryScale: Int = 0
|
||||||
|
var net: String? = null
|
||||||
|
var dpi: Long = 0
|
||||||
|
var romFreeExt: Long = 0
|
||||||
|
var dpiF: String? = null
|
||||||
|
var cpuCoreNum: Long = 0
|
||||||
|
}
|
|
@ -2,8 +2,6 @@ package com.android.grape.data
|
||||||
|
|
||||||
|
|
||||||
import com.google.gson.annotations.SerializedName
|
import com.google.gson.annotations.SerializedName
|
||||||
import org.json.JSONArray
|
|
||||||
import org.json.JSONObject
|
|
||||||
|
|
||||||
data class Device(
|
data class Device(
|
||||||
var advertiserId: String = "",
|
var advertiserId: String = "",
|
||||||
|
@ -112,32 +110,4 @@ data class Device(
|
||||||
var vendingVersionName: String = "",
|
var vendingVersionName: String = "",
|
||||||
@SerializedName("web_ua")
|
@SerializedName("web_ua")
|
||||||
var webUa: String = ""
|
var webUa: String = ""
|
||||||
){
|
)
|
||||||
fun toJson(): String {
|
|
||||||
return JSONObject().apply {
|
|
||||||
put("sensors", JSONArray(sensor))
|
|
||||||
put("arch", arch)
|
|
||||||
put("cpu_abi", cpuAbi)
|
|
||||||
put("cpu_abi2", cpuAbi2)
|
|
||||||
put("size", dim.size)
|
|
||||||
put("xdp", dim.xdp)
|
|
||||||
put("ydp", dim.ydp)
|
|
||||||
put("y_px", dim.yPx)
|
|
||||||
put("x_px", dim.xPx)
|
|
||||||
put("d_dpi", dim.dDpi)
|
|
||||||
put("btch", btch)
|
|
||||||
put("btl", batteryLevel)
|
|
||||||
put("disk", disk)
|
|
||||||
put("sdk", sdk)
|
|
||||||
put("network", network)
|
|
||||||
put("api_ver", vendingVersionCode)
|
|
||||||
put("api_ver_name", vendingVersionName)
|
|
||||||
put("carrier", carrier)
|
|
||||||
put("product", product)
|
|
||||||
put("last_boot_time", lastBootTime)
|
|
||||||
put("install_source_info", installerPackage)
|
|
||||||
put("advertiserId", advertiserId)
|
|
||||||
}.toString()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
package com.android.grape.data
|
||||||
|
|
||||||
|
class DeviceInfo {
|
||||||
|
var lang: String? = null
|
||||||
|
var roProductBrand: String? = null
|
||||||
|
var roProductModel: String? = null
|
||||||
|
var roProductManufacturer: String? = null
|
||||||
|
var roProductDevice: String? = null
|
||||||
|
var roProductName: String? = null
|
||||||
|
var roBuildVersionIncremental: String? = null
|
||||||
|
var roBuildFingerprint: String? = null
|
||||||
|
var roOdmBuildFingerprint: String? = null
|
||||||
|
var roProductBuildFingerprint: String? = null
|
||||||
|
var roSystemBuildFingerprint: String? = null
|
||||||
|
var roSystemExtBuildFingerprint: String? = null
|
||||||
|
var roVendorBuildFingerprint: String? = null
|
||||||
|
var roBuildPlatform: String? = null
|
||||||
|
var persistSysCloudDrmId: String? = null
|
||||||
|
var persistSysCloudBatteryCapacity: Int = 0
|
||||||
|
var persistSysCloudGpuGlVendor: String? = null
|
||||||
|
var persistSysCloudGpuGlRenderer: String? = null
|
||||||
|
var persistSysCloudGpuGlVersion: String? = null
|
||||||
|
var persistSysCloudGpuEglVendor: String? = null
|
||||||
|
var persistSysCloudGpuEglVersion: String? = null
|
||||||
|
}
|
|
@ -9,6 +9,4 @@ data class Sensor(
|
||||||
var sV: String = "",
|
var sV: String = "",
|
||||||
var sVE: List<Double> = listOf(),
|
var sVE: List<Double> = listOf(),
|
||||||
var sVS: List<Double> = listOf()
|
var sVS: List<Double> = listOf()
|
||||||
) {
|
)
|
||||||
companion object
|
|
||||||
}
|
|
|
@ -15,7 +15,7 @@ class AutoJobService : JobIntentService() {
|
||||||
Handler(Looper.getMainLooper()).postDelayed({
|
Handler(Looper.getMainLooper()).postDelayed({
|
||||||
ScriptUtil.execScript(
|
ScriptUtil.execScript(
|
||||||
this@AutoJobService,
|
this@AutoJobService,
|
||||||
"/sdcard/script/main.js"
|
"/sdcard/autojs/main.js"
|
||||||
)
|
)
|
||||||
}, 2000L)
|
}, 2000L)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -23,8 +23,8 @@ class DownloadAppJobService : JobIntentService() {
|
||||||
|
|
||||||
if (succ) {
|
if (succ) {
|
||||||
errTime = 0L
|
errTime = 0L
|
||||||
// InstallService.onEvent(this)
|
StartVpnServerJobService.onEvent(this)
|
||||||
StartVpnPortJobService.onEvent(this)
|
// StartVpnPortJobService.onEvent(this)
|
||||||
} else {
|
} else {
|
||||||
Util.isClickRet = false
|
Util.isClickRet = false
|
||||||
Util.setInstallRet(false)
|
Util.setInstallRet(false)
|
||||||
|
|
|
@ -4,7 +4,6 @@ import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.core.app.JobIntentService
|
import androidx.core.app.JobIntentService
|
||||||
import com.android.grape.net.ChangeCallBack
|
|
||||||
import com.android.grape.util.ChangeDeviceInfoUtil
|
import com.android.grape.util.ChangeDeviceInfoUtil
|
||||||
import com.android.grape.util.MockTools
|
import com.android.grape.util.MockTools
|
||||||
import com.android.grape.util.ServiceUtils
|
import com.android.grape.util.ServiceUtils
|
||||||
|
@ -24,9 +23,9 @@ class OpenAppService : JobIntentService() {
|
||||||
ServiceUtils.setEnableApp("com.android.chrome", true)
|
ServiceUtils.setEnableApp("com.android.chrome", true)
|
||||||
ServiceUtils.setEnableApp("com.UCMobile", true)
|
ServiceUtils.setEnableApp("com.UCMobile", true)
|
||||||
if (Util.isCanAuto && Util.canAutoLc.isNotEmpty()) {
|
if (Util.isCanAuto && Util.canAutoLc.isNotEmpty()) {
|
||||||
MockTools.exec("pm grant ${Util.AUTO_JSPACKAGENAME} android.permission.READ_EXTERNAL_STORAGE") //sdcard权限
|
MockTools.exec("pm grant org.autojs.autojs android.permission.READ_EXTERNAL_STORAGE") //sdcard权限
|
||||||
MockTools.exec("pm grant ${Util.AUTO_JSPACKAGENAME} android.permission.SYSTEM_ALERT_WINDOW") //悬浮窗权限
|
MockTools.exec("pm grant org.autojs.autojs android.permission.SYSTEM_ALERT_WINDOW") //悬浮窗权限
|
||||||
MockTools.exec("settings put secure enabled_accessibility_services ${Util.AUTO_JSPACKAGENAME}/${Util.AUTO_JSPACKAGENAME}.core.accessibility.AccessibilityService")
|
MockTools.exec("settings put secure enabled_accessibility_services org.autojs.autojs/com.stardust.autojs.core.accessibility.AccessibilityService")
|
||||||
Util.doScript(this, Util.AUTO_JSPACKAGENAME) //autojs
|
Util.doScript(this, Util.AUTO_JSPACKAGENAME) //autojs
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
|
@ -38,21 +37,13 @@ class OpenAppService : JobIntentService() {
|
||||||
// Log.d("IOSTQ:", "执行新装任务")
|
// Log.d("IOSTQ:", "执行新装任务")
|
||||||
// Util.setInfo(this)
|
// Util.setInfo(this)
|
||||||
// }
|
// }
|
||||||
// ChangeDeviceInfoUtil.changeDeviceInfo()
|
ChangeDeviceInfoUtil.changeDeviceInfo()
|
||||||
ChangeDeviceInfoUtil.changeDevice(callBack = object : ChangeCallBack {
|
|
||||||
override fun changeSuccess() {
|
|
||||||
Util.openRecordApp(this@OpenAppService)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun changeFailed() {
|
|
||||||
Util.setFinish(this@OpenAppService)
|
|
||||||
}
|
|
||||||
|
|
||||||
})
|
|
||||||
} catch (e: IOException) {
|
} catch (e: IOException) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Util.hookOpenApp(this);
|
// Util.hookOpenApp(this);
|
||||||
|
Util.openRecordApp(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
|
@ -6,17 +6,13 @@ import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.core.app.JobIntentService
|
import androidx.core.app.JobIntentService
|
||||||
import com.android.grape.net.MyGet
|
|
||||||
import com.android.grape.util.ClashUtil
|
import com.android.grape.util.ClashUtil
|
||||||
import com.android.grape.util.ClashUtil.getProxyPort
|
|
||||||
import com.android.grape.util.Util
|
import com.android.grape.util.Util
|
||||||
import java.util.Locale
|
|
||||||
|
|
||||||
class StartVpnPortJobService : JobIntentService() {
|
class StartVpnPortJobService : JobIntentService() {
|
||||||
override fun onHandleWork(intent: Intent) {
|
override fun onHandleWork(intent: Intent) {
|
||||||
Log.i(TAG, "start to handle work")
|
Log.i(TAG, "start to handle work")
|
||||||
ClashUtil.switchProxyGroup("PROXY", "DIRECT", "http://127.0.0.1:6170")
|
if (ClashUtil.checkProxy( this)) {
|
||||||
if (exec()) {
|
|
||||||
Handler(Looper.getMainLooper()).postDelayed({
|
Handler(Looper.getMainLooper()).postDelayed({
|
||||||
StartVpnServerJobService.onEvent(
|
StartVpnServerJobService.onEvent(
|
||||||
this@StartVpnPortJobService
|
this@StartVpnPortJobService
|
||||||
|
@ -29,28 +25,6 @@ class StartVpnPortJobService : JobIntentService() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun exec(): Boolean {
|
|
||||||
try {
|
|
||||||
val port = getProxyPort()
|
|
||||||
var nRetryCount = 0
|
|
||||||
do {
|
|
||||||
val url =
|
|
||||||
"http://39.103.73.250/tt/test/testProxy.jsp?port=$port&country=" + Util.proxyCountry
|
|
||||||
?.uppercase(Locale.getDefault())
|
|
||||||
val result: String = MyGet.get(url)
|
|
||||||
Log.d(TAG, "request url == $url result$result")
|
|
||||||
if (result.contains("ok")) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
} while (nRetryCount++ < 3)
|
|
||||||
return false
|
|
||||||
} catch (err: Exception) {
|
|
||||||
err.printStackTrace()
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val TAG = "IOSTQ:StartVpnPort"
|
private const val TAG = "IOSTQ:StartVpnPort"
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,6 @@ import android.util.Log
|
||||||
import androidx.core.app.JobIntentService
|
import androidx.core.app.JobIntentService
|
||||||
import com.android.grape.MainApplication
|
import com.android.grape.MainApplication
|
||||||
import com.android.grape.util.ClashUtil
|
import com.android.grape.util.ClashUtil
|
||||||
import com.android.grape.util.CountryCode
|
|
||||||
import com.android.grape.util.Util
|
import com.android.grape.util.Util
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -28,12 +27,19 @@ class StartVpnServerJobService : JobIntentService() {
|
||||||
onEvent(
|
onEvent(
|
||||||
this@StartVpnServerJobService
|
this@StartVpnServerJobService
|
||||||
)
|
)
|
||||||
}, 2000L)
|
}, 1000L)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun exec(): Boolean {
|
private fun exec(): Boolean {
|
||||||
return ClashUtil.switchProxyGroup("PROXY", "my-socks5-proxy", "http://127.0.0.1:6170")
|
ClashUtil.startProxy(MainApplication.instance)
|
||||||
|
ClashUtil.switchProxyGroup("GLOBAL", Util.proxyCountry, "http://127.0.0.1:6170")
|
||||||
|
if (!ClashUtil.checkProxy(MainApplication.instance)) {
|
||||||
|
println("IOSTQ:start vpn error")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
println("IOSTQ:start vpn ok")
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
|
@ -1,70 +0,0 @@
|
||||||
package com.android.grape.net
|
|
||||||
|
|
||||||
import android.util.Log
|
|
||||||
import com.android.grape.pad.Pad
|
|
||||||
import com.android.grape.pad.PadTask
|
|
||||||
import com.android.grape.pad.TaskDetail
|
|
||||||
import com.google.gson.Gson
|
|
||||||
import com.google.gson.reflect.TypeToken
|
|
||||||
import org.json.JSONArray
|
|
||||||
import org.json.JSONObject
|
|
||||||
|
|
||||||
object Api {
|
|
||||||
private const val TAG = "Api"
|
|
||||||
const val UPDATE_PAD: String = "/openapi/open/pad/updatePadProperties"
|
|
||||||
const val PAD_DETAIL: String = "/openapi/open/pad/padDetails"
|
|
||||||
const val PAD_TASK: String = "/task-center/open/task/padTaskDetail"
|
|
||||||
|
|
||||||
|
|
||||||
fun padDetail(): ApiResponse<Pad>{
|
|
||||||
val param = Gson().toJson(mapOf(
|
|
||||||
"page" to 1,
|
|
||||||
"rows" to 10,
|
|
||||||
"padCode" to listOf("ACP250702PWJCTLF")
|
|
||||||
))
|
|
||||||
val (response, code) = HttpUtils.postSync(PAD_DETAIL, param)
|
|
||||||
if (code == 200) {
|
|
||||||
Log.d("padDetail", response.toString())
|
|
||||||
var apiResponse: ApiResponse<Pad> = Gson().fromJson(response, object : TypeToken<ApiResponse<Pad>>() {}.type)
|
|
||||||
return apiResponse
|
|
||||||
} else {
|
|
||||||
Log.d("padDetail", "error: $code")
|
|
||||||
return ApiResponse(code, "error", 0, null)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun updatePad(params: String): ApiResponseList<PadTask>{
|
|
||||||
val (response, code) = HttpUtils.postSync(UPDATE_PAD, params)
|
|
||||||
if (code == 200) {
|
|
||||||
Log.d("updatePad", response.toString())
|
|
||||||
var apiResponse: ApiResponseList<PadTask> = Gson().fromJson(response, object : TypeToken<ApiResponseList<PadTask>>() {}.type)
|
|
||||||
return apiResponse
|
|
||||||
} else {
|
|
||||||
Log.d("updatePad", "error: $code")
|
|
||||||
return ApiResponseList(code, "error", 0, emptyList())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun padTaskDetail(taskId: Int): Int{
|
|
||||||
val jsonString = JSONObject().apply {
|
|
||||||
put("taskIds", JSONArray().apply {
|
|
||||||
put(taskId)
|
|
||||||
})
|
|
||||||
}.toString()
|
|
||||||
Log.d(TAG, "padTaskDetail: $jsonString")
|
|
||||||
val (response, code) = HttpUtils.postSync(PAD_TASK, jsonString)
|
|
||||||
if (code == 200) {
|
|
||||||
Log.d("padTaskDetail", response.toString())
|
|
||||||
var apiResponse: ApiResponseList<TaskDetail> = Gson().fromJson(response, object : TypeToken<ApiResponseList<TaskDetail>>() {}.type)
|
|
||||||
val taskList = apiResponse.data
|
|
||||||
if (apiResponse.isSuccess() && taskList!= null && taskList.isNotEmpty()){
|
|
||||||
val task = taskList[0]
|
|
||||||
return task.taskStatus
|
|
||||||
}
|
|
||||||
return -1
|
|
||||||
}else {
|
|
||||||
Log.d("padTaskDetail", "error: $code")
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,23 +0,0 @@
|
||||||
package com.android.grape.net
|
|
||||||
|
|
||||||
data class ApiResponse<T>(
|
|
||||||
val code: Int,
|
|
||||||
val message: String,
|
|
||||||
val ts: Long,
|
|
||||||
var data: T? = null
|
|
||||||
){
|
|
||||||
fun isSuccess(): Boolean {
|
|
||||||
return code == 200
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
data class ApiResponseList<T>(
|
|
||||||
val code: Int,
|
|
||||||
val message: String,
|
|
||||||
val ts: Long,
|
|
||||||
var data: List<T>? = null
|
|
||||||
){
|
|
||||||
fun isSuccess(): Boolean {
|
|
||||||
return code == 200
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,39 +0,0 @@
|
||||||
package com.android.grape.net
|
|
||||||
|
|
||||||
import android.util.Log
|
|
||||||
import java.nio.charset.StandardCharsets
|
|
||||||
import javax.crypto.Mac
|
|
||||||
import javax.crypto.spec.SecretKeySpec
|
|
||||||
|
|
||||||
|
|
||||||
object ArmCloudSignatureV2 {
|
|
||||||
const val ALGORITHM: String = "HmacSHA256"
|
|
||||||
const val SECRET_KEY: String = "gz8f1u0t63byzdu6ozbx8r5qs3e5lipt"
|
|
||||||
const val SECRET_ID: String = "3yc8c8bg1dym0zaiwjh867al"
|
|
||||||
|
|
||||||
@Throws(Exception::class)
|
|
||||||
fun calculateSignature(
|
|
||||||
timestamp: String?,
|
|
||||||
path: String,
|
|
||||||
body: String?,
|
|
||||||
secretKey: String = SECRET_ID
|
|
||||||
): String {
|
|
||||||
val stringToSign = timestamp + path + (body ?: "")
|
|
||||||
Log.d("TAG", "calculateSignature: $stringToSign")
|
|
||||||
val hmacSha256 = Mac.getInstance(ALGORITHM)
|
|
||||||
val secretKeySpec = SecretKeySpec(secretKey.toByteArray(StandardCharsets.UTF_8), ALGORITHM)
|
|
||||||
hmacSha256.init(secretKeySpec)
|
|
||||||
val hash = hmacSha256.doFinal(stringToSign.toByteArray(StandardCharsets.UTF_8))
|
|
||||||
return bytesToHex(hash)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun bytesToHex(bytes: ByteArray): String {
|
|
||||||
val hexString = StringBuilder()
|
|
||||||
for (b in bytes) {
|
|
||||||
val hex = Integer.toHexString(0xff and b.toInt())
|
|
||||||
if (hex.length == 1) hexString.append('0')
|
|
||||||
hexString.append(hex)
|
|
||||||
}
|
|
||||||
return hexString.toString()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,6 +0,0 @@
|
||||||
package com.android.grape.net
|
|
||||||
|
|
||||||
interface ChangeCallBack {
|
|
||||||
fun changeSuccess()
|
|
||||||
fun changeFailed()
|
|
||||||
}
|
|
|
@ -2,28 +2,22 @@ package com.android.grape.net
|
||||||
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import com.blankj.utilcode.util.LogUtils
|
import com.blankj.utilcode.util.LogUtils
|
||||||
import com.google.gson.Gson
|
|
||||||
import okhttp3.*
|
import okhttp3.*
|
||||||
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
||||||
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
||||||
import okhttp3.RequestBody.Companion.toRequestBody
|
import okhttp3.RequestBody.Companion.toRequestBody
|
||||||
import okhttp3.logging.HttpLoggingInterceptor
|
|
||||||
import okhttp3.logging.HttpLoggingInterceptor.Level
|
|
||||||
import okhttp3.logging.HttpLoggingInterceptor.Logger
|
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
object HttpUtils {
|
object HttpUtils {
|
||||||
const val HOST: String = "https://openapi-hk.armcloud.net"
|
|
||||||
// OkHttp客户端配置
|
// OkHttp客户端配置
|
||||||
private val client: OkHttpClient by lazy {
|
private val client: OkHttpClient by lazy {
|
||||||
OkHttpClient.Builder()
|
OkHttpClient.Builder()
|
||||||
.connectTimeout(15, TimeUnit.SECONDS)
|
.connectTimeout(15, TimeUnit.SECONDS)
|
||||||
.readTimeout(15, TimeUnit.SECONDS)
|
.readTimeout(15, TimeUnit.SECONDS)
|
||||||
.writeTimeout(15, TimeUnit.SECONDS)
|
.writeTimeout(15, TimeUnit.SECONDS)
|
||||||
.addInterceptor(HttpLoggingInterceptor().apply {
|
.addInterceptor(LoggingInterceptor()) // 添加日志拦截器
|
||||||
level = Level.BODY
|
|
||||||
})
|
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,6 +30,47 @@ object HttpUtils {
|
||||||
fun onFailure(error: String, code: Int?)
|
fun onFailure(error: String, code: Int?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 日志拦截器
|
||||||
|
private class LoggingInterceptor : Interceptor {
|
||||||
|
override fun intercept(chain: Interceptor.Chain): Response {
|
||||||
|
val request = chain.request()
|
||||||
|
val url = request.url.toString()
|
||||||
|
val method = request.method
|
||||||
|
|
||||||
|
// 记录请求信息
|
||||||
|
LogUtils.d("HTTP_REQUEST", "URL: $url")
|
||||||
|
LogUtils.d("HTTP_REQUEST", "Method: $method")
|
||||||
|
|
||||||
|
if ("POST" == method) {
|
||||||
|
request.body?.let { body ->
|
||||||
|
if (body is FormBody) {
|
||||||
|
val formBody = StringBuilder()
|
||||||
|
for (i in 0 until body.size) {
|
||||||
|
formBody.append("${body.name(i)}:${body.value(i)}; ")
|
||||||
|
}
|
||||||
|
Log.d("HTTP_REQUEST", "Form Data: $formBody")
|
||||||
|
} else if (body is MultipartBody) {
|
||||||
|
Log.d("HTTP_REQUEST", "Multipart Form Data")
|
||||||
|
} else {
|
||||||
|
Log.d("HTTP_REQUEST", "Body: ${body.contentType()}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val response = chain.proceed(request)
|
||||||
|
val responseBody = response.peekBody(Long.MAX_VALUE)
|
||||||
|
val responseString = responseBody.string()
|
||||||
|
|
||||||
|
// 记录响应信息
|
||||||
|
LogUtils.d("HTTP_RESPONSE", "Code: ${response.code}")
|
||||||
|
LogUtils.d("HTTP_RESPONSE", "Response: $responseString")
|
||||||
|
|
||||||
|
return response.newBuilder()
|
||||||
|
.body(responseBody)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GET 请求(异步)
|
* GET 请求(异步)
|
||||||
*
|
*
|
||||||
|
@ -47,9 +82,10 @@ object HttpUtils {
|
||||||
fun getAsync(
|
fun getAsync(
|
||||||
url: String,
|
url: String,
|
||||||
params: Map<String, String>? = null,
|
params: Map<String, String>? = null,
|
||||||
|
headers: Map<String, String>? = null,
|
||||||
callback: HttpCallback
|
callback: HttpCallback
|
||||||
) {
|
) {
|
||||||
val request = buildGetRequest(url, params)
|
val request = buildGetRequest(url, params, headers)
|
||||||
executeRequestAsync(request, callback)
|
executeRequestAsync(request, callback)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,10 +99,31 @@ object HttpUtils {
|
||||||
*/
|
*/
|
||||||
fun postAsync(
|
fun postAsync(
|
||||||
url: String,
|
url: String,
|
||||||
body: String? = null,
|
body: String,
|
||||||
|
headers: Map<String, String>? = null,
|
||||||
callback: HttpCallback
|
callback: HttpCallback
|
||||||
) {
|
) {
|
||||||
val request = buildPostRequest(url, body)
|
val requestBody = body.toRequestBody(JSON_MEDIA_TYPE)
|
||||||
|
val request = buildPostRequest(url, requestBody, headers)
|
||||||
|
executeRequestAsync(request, callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* POST Form Data 请求(异步)
|
||||||
|
*
|
||||||
|
* @param url 请求URL
|
||||||
|
* @param formData 表单数据
|
||||||
|
* @param headers 请求头(可选)
|
||||||
|
* @param callback 回调接口
|
||||||
|
*/
|
||||||
|
fun postFormAsync(
|
||||||
|
url: String,
|
||||||
|
formData: Map<String, String>,
|
||||||
|
headers: Map<String, String>? = null,
|
||||||
|
callback: HttpCallback
|
||||||
|
) {
|
||||||
|
val formBody = buildFormBody(formData)
|
||||||
|
val request = buildPostRequest(url, formBody, headers)
|
||||||
executeRequestAsync(request, callback)
|
executeRequestAsync(request, callback)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -81,8 +138,9 @@ object HttpUtils {
|
||||||
fun getSync(
|
fun getSync(
|
||||||
url: String,
|
url: String,
|
||||||
params: Map<String, String>? = null,
|
params: Map<String, String>? = null,
|
||||||
|
headers: Map<String, String>? = null
|
||||||
): Pair<String?, Int> {
|
): Pair<String?, Int> {
|
||||||
val request = buildGetRequest(url, params)
|
val request = buildGetRequest(url, params, headers)
|
||||||
return executeRequestSync(request)
|
return executeRequestSync(request)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -96,9 +154,11 @@ object HttpUtils {
|
||||||
*/
|
*/
|
||||||
fun postSync(
|
fun postSync(
|
||||||
url: String,
|
url: String,
|
||||||
body: String? = null
|
body: String,
|
||||||
|
headers: Map<String, String>? = null
|
||||||
): Pair<String?, Int> {
|
): Pair<String?, Int> {
|
||||||
val request = buildPostRequest(url, body)
|
val requestBody = body.toRequestBody(JSON_MEDIA_TYPE)
|
||||||
|
val request = buildPostRequest(url, requestBody, headers)
|
||||||
return executeRequestSync(request)
|
return executeRequestSync(request)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -112,72 +172,46 @@ object HttpUtils {
|
||||||
// 内部方法 --------------------------------
|
// 内部方法 --------------------------------
|
||||||
|
|
||||||
private fun buildGetRequest(
|
private fun buildGetRequest(
|
||||||
path: String ,
|
url: String,
|
||||||
params: Map<String, String>?,
|
params: Map<String, String>?,
|
||||||
|
headers: Map<String, String>?
|
||||||
): Request {
|
): Request {
|
||||||
val url = "$HOST$path"
|
|
||||||
val httpUrlBuilder = url.toHttpUrlOrNull()?.newBuilder()
|
val httpUrlBuilder = url.toHttpUrlOrNull()?.newBuilder()
|
||||||
?: throw IllegalArgumentException("Invalid URL: $url")
|
?: throw IllegalArgumentException("Invalid URL: $url")
|
||||||
|
|
||||||
var param = ""
|
// 添加查询参数
|
||||||
params?.forEach { (key, value) ->
|
params?.forEach { (key, value) ->
|
||||||
httpUrlBuilder.addQueryParameter(key, value)
|
httpUrlBuilder.addQueryParameter(key, value)
|
||||||
param += "&${key}=${value}"
|
|
||||||
}
|
|
||||||
if (param.isNotEmpty()){
|
|
||||||
param = param.substring(1)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val requestBuilder = Request.Builder().url(httpUrlBuilder.build())
|
val requestBuilder = Request.Builder().url(httpUrlBuilder.build())
|
||||||
|
|
||||||
val authver = "2.0"
|
// 添加请求头
|
||||||
val timestamp = System.currentTimeMillis()
|
addHeaders(requestBuilder, headers)
|
||||||
val sign =
|
|
||||||
ArmCloudSignatureV2.calculateSignature(timestamp.toString(), path, param)
|
|
||||||
addHeaders(
|
|
||||||
requestBuilder,
|
|
||||||
mapOf(
|
|
||||||
"authver" to authver,
|
|
||||||
"x-ak" to ArmCloudSignatureV2.SECRET_KEY,
|
|
||||||
"x-timestamp" to timestamp.toString(),
|
|
||||||
"x-sign" to sign
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
return requestBuilder.build()
|
return requestBuilder.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildPostRequest(
|
private fun buildPostRequest(
|
||||||
path: String,
|
url: String,
|
||||||
body: String? = null
|
body: RequestBody,
|
||||||
|
headers: Map<String, String>?
|
||||||
): Request {
|
): Request {
|
||||||
val url = "$HOST$path"
|
|
||||||
val bodyString = body?:""
|
|
||||||
val requestBuilder = Request.Builder()
|
val requestBuilder = Request.Builder()
|
||||||
.url(url)
|
.url(url)
|
||||||
.post(bodyString.toRequestBody(JSON_MEDIA_TYPE))
|
.post(body)
|
||||||
val authver = "2.0"
|
|
||||||
val timestamp = System.currentTimeMillis()
|
// 添加请求头
|
||||||
val sign =
|
addHeaders(requestBuilder, headers)
|
||||||
ArmCloudSignatureV2.calculateSignature(timestamp.toString(), path, body)
|
|
||||||
addHeaders(
|
|
||||||
requestBuilder,
|
|
||||||
mapOf(
|
|
||||||
"authver" to authver,
|
|
||||||
"x-ak" to ArmCloudSignatureV2.SECRET_KEY,
|
|
||||||
"x-timestamp" to timestamp.toString(),
|
|
||||||
"x-sign" to sign
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
return requestBuilder.build()
|
return requestBuilder.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun addHeaders(
|
private fun addHeaders(
|
||||||
builder: Request.Builder,
|
builder: Request.Builder,
|
||||||
headers: Map<String, String>
|
headers: Map<String, String>?
|
||||||
) {
|
) {
|
||||||
headers.forEach { (key, value) ->
|
headers?.forEach { (key, value) ->
|
||||||
builder.addHeader(key, value)
|
builder.addHeader(key, value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -218,13 +252,13 @@ object HttpUtils {
|
||||||
private fun executeRequestSync(request: Request): Pair<String?, Int> {
|
private fun executeRequestSync(request: Request): Pair<String?, Int> {
|
||||||
return try {
|
return try {
|
||||||
val response = client.newCall(request).execute()
|
val response = client.newCall(request).execute()
|
||||||
val responseBody = response.peekBody(Long.MAX_VALUE).string()
|
val responseBody = response.body?.string()
|
||||||
val code = response.code
|
val code = response.code
|
||||||
Log.d("TAG", "executeRequestSync: $responseBody")
|
|
||||||
if (response.isSuccessful && responseBody.isNotEmpty()) {
|
if (response.isSuccessful && responseBody != null) {
|
||||||
Pair(responseBody, code)
|
Pair(responseBody, code)
|
||||||
} else {
|
} else {
|
||||||
Pair(null, -1)
|
Pair(null, code)
|
||||||
}
|
}
|
||||||
} catch (e: IOException) {
|
} catch (e: IOException) {
|
||||||
Pair(null, -1) // 网络错误状态码
|
Pair(null, -1) // 网络错误状态码
|
||||||
|
|
|
@ -2,48 +2,16 @@ package com.android.grape.net
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import okhttp3.OkHttpClient
|
|
||||||
import okhttp3.Request
|
|
||||||
import okhttp3.Response
|
|
||||||
import java.io.BufferedInputStream
|
import java.io.BufferedInputStream
|
||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.net.HttpURLConnection
|
import java.net.HttpURLConnection
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
import java.util.concurrent.TimeUnit
|
|
||||||
|
|
||||||
|
|
||||||
object MyGet {
|
object MyGet {
|
||||||
private const val TAG = "MyGet"
|
private const val TAG = "MyGet"
|
||||||
val affHttpClient: OkHttpClient = OkHttpClient().apply {
|
|
||||||
dispatcher.maxRequests = 1000
|
|
||||||
dispatcher.maxRequestsPerHost = 1000
|
|
||||||
}
|
|
||||||
|
|
||||||
fun get(url_: String): String {
|
|
||||||
var response: Response? = null
|
|
||||||
val client: OkHttpClient = affHttpClient.newBuilder()
|
|
||||||
.connectTimeout(5, TimeUnit.SECONDS)
|
|
||||||
.readTimeout(5, TimeUnit.SECONDS)
|
|
||||||
.build()
|
|
||||||
val request = Request.Builder()
|
|
||||||
.url(url_)
|
|
||||||
.addHeader("User-Agent", "PostmanRuntime/7.29.0")
|
|
||||||
.addHeader("Accept", "*/*")
|
|
||||||
.addHeader("Accept-Encoding", "gzip, deflate, br")
|
|
||||||
.addHeader("Connection", "keep-alive")
|
|
||||||
.build()
|
|
||||||
try {
|
|
||||||
response = client.newCall(request).execute()
|
|
||||||
return response.body?.string()?:""
|
|
||||||
} catch (e: java.lang.Exception) {
|
|
||||||
e.printStackTrace()
|
|
||||||
} finally {
|
|
||||||
response?.close()
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getData(url_: String, ua: String?): String? {
|
fun getData(url_: String, ua: String?): String? {
|
||||||
return getData(url_, ua, 0)
|
return getData(url_, ua, 0)
|
||||||
|
@ -63,7 +31,7 @@ object MyGet {
|
||||||
httpUrlConnection = url.openConnection() as HttpURLConnection
|
httpUrlConnection = url.openConnection() as HttpURLConnection
|
||||||
|
|
||||||
httpUrlConnection.allowUserInteraction = true
|
httpUrlConnection.allowUserInteraction = true
|
||||||
httpUrlConnection.doOutput = false
|
httpUrlConnection!!.doOutput = false
|
||||||
httpUrlConnection.doInput = true
|
httpUrlConnection.doInput = true
|
||||||
httpUrlConnection.useCaches = false
|
httpUrlConnection.useCaches = false
|
||||||
httpUrlConnection.setRequestProperty("Connection", "close") //add 20200428
|
httpUrlConnection.setRequestProperty("Connection", "close") //add 20200428
|
||||||
|
@ -172,7 +140,7 @@ object MyGet {
|
||||||
httpUrlConnection = url.openConnection() as HttpURLConnection
|
httpUrlConnection = url.openConnection() as HttpURLConnection
|
||||||
|
|
||||||
httpUrlConnection.allowUserInteraction = true
|
httpUrlConnection.allowUserInteraction = true
|
||||||
httpUrlConnection.doOutput = false
|
httpUrlConnection!!.doOutput = false
|
||||||
httpUrlConnection.doInput = true
|
httpUrlConnection.doInput = true
|
||||||
httpUrlConnection.useCaches = false
|
httpUrlConnection.useCaches = false
|
||||||
httpUrlConnection.instanceFollowRedirects = false
|
httpUrlConnection.instanceFollowRedirects = false
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
package com.android.grape.net
|
||||||
|
|
||||||
|
class NetworkHelper {
|
||||||
|
|
||||||
|
}
|
|
@ -1,14 +0,0 @@
|
||||||
package com.android.grape.pad
|
|
||||||
|
|
||||||
|
|
||||||
import com.google.gson.annotations.SerializedName
|
|
||||||
|
|
||||||
data class DcInfo(
|
|
||||||
var area: String = "",
|
|
||||||
var dcCode: String = "",
|
|
||||||
var dcName: String = "",
|
|
||||||
var ossEndpoint: String = "",
|
|
||||||
var ossEndpointInternal: String = "",
|
|
||||||
var ossFileEndpoint: String = "",
|
|
||||||
var ossScreenshotEndpoint: String = ""
|
|
||||||
)
|
|
|
@ -1,13 +0,0 @@
|
||||||
package com.android.grape.pad
|
|
||||||
|
|
||||||
|
|
||||||
import com.google.gson.annotations.SerializedName
|
|
||||||
|
|
||||||
data class Pad(
|
|
||||||
var page: Int = 0,
|
|
||||||
var pageData: List<PageData> = listOf(),
|
|
||||||
var rows: Int = 0,
|
|
||||||
var size: Int = 0,
|
|
||||||
var total: Int = 0,
|
|
||||||
var totalPage: Int = 0
|
|
||||||
)
|
|
|
@ -1,10 +0,0 @@
|
||||||
package com.android.grape.pad
|
|
||||||
|
|
||||||
|
|
||||||
import com.google.gson.annotations.SerializedName
|
|
||||||
|
|
||||||
data class PadTask(
|
|
||||||
var padCode: String = "",
|
|
||||||
var taskId: Int = 0,
|
|
||||||
var vmStatus: Int = 0
|
|
||||||
)
|
|
|
@ -1,17 +0,0 @@
|
||||||
package com.android.grape.pad
|
|
||||||
|
|
||||||
|
|
||||||
import com.google.gson.annotations.SerializedName
|
|
||||||
|
|
||||||
data class PageData(
|
|
||||||
var dataSize: Long = 0,
|
|
||||||
var dataSizeUsed: Long = 0,
|
|
||||||
var dcInfo: DcInfo = DcInfo(),
|
|
||||||
var deviceLevel: String = "",
|
|
||||||
var deviceStatus: Int = 0,
|
|
||||||
var imageId: String = "",
|
|
||||||
var online: Int = 0,
|
|
||||||
var padCode: String = "",
|
|
||||||
var padStatus: Int = 0,
|
|
||||||
var streamStatus: Int = 0
|
|
||||||
)
|
|
|
@ -1,14 +0,0 @@
|
||||||
package com.android.grape.pad
|
|
||||||
|
|
||||||
|
|
||||||
import com.google.gson.annotations.SerializedName
|
|
||||||
|
|
||||||
data class TaskDetail(
|
|
||||||
var endTime: Long = 0,
|
|
||||||
var errorMsg: String = "",
|
|
||||||
var padCode: String = "",
|
|
||||||
var taskContent: Any? = Any(),
|
|
||||||
var taskId: Int = 0,
|
|
||||||
var taskResult: Any? = Any(),
|
|
||||||
var taskStatus: Int = 0
|
|
||||||
)
|
|
|
@ -1,100 +0,0 @@
|
||||||
package com.android.grape.provider
|
|
||||||
|
|
||||||
import android.content.ContentValues
|
|
||||||
import android.content.Context
|
|
||||||
import android.net.Uri
|
|
||||||
import com.android.grape.data.Device
|
|
||||||
|
|
||||||
// DeviceDataAccessor.kt
|
|
||||||
object DeviceDataAccessor {
|
|
||||||
|
|
||||||
// 保存设备信息到提供方
|
|
||||||
fun saveDeviceInfo(context: Context, device: Device): Uri? {
|
|
||||||
val resolver = context.contentResolver
|
|
||||||
val actualDeviceId = DeviceInfoHelper.getDeviceId()
|
|
||||||
|
|
||||||
val values = ContentValues().apply {
|
|
||||||
put(DeviceInfoContract.DeviceInfoEntry.COLUMN_DEVICE_ID, actualDeviceId)
|
|
||||||
put(DeviceInfoContract.DeviceInfoEntry.COLUMN_DATA, device.toJson())
|
|
||||||
}
|
|
||||||
|
|
||||||
// 尝试更新现有记录
|
|
||||||
val updateCount = resolver.update(
|
|
||||||
getDeviceUri(actualDeviceId),
|
|
||||||
values,
|
|
||||||
null,
|
|
||||||
null
|
|
||||||
)
|
|
||||||
|
|
||||||
// 如果没有更新记录,则插入新记录
|
|
||||||
return if (updateCount == 0) {
|
|
||||||
resolver.insert(DeviceInfoContract.CONTENT_URI, values)
|
|
||||||
} else {
|
|
||||||
getDeviceUri(actualDeviceId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 读取设备信息
|
|
||||||
fun getDeviceInfo(context: Context, deviceId: String? = null): String? {
|
|
||||||
val resolver = context.contentResolver
|
|
||||||
val actualDeviceId = deviceId ?: DeviceInfoHelper.getDeviceId()
|
|
||||||
|
|
||||||
val cursor = resolver.query(
|
|
||||||
getDeviceUri(actualDeviceId),
|
|
||||||
arrayOf(DeviceInfoContract.DeviceInfoEntry.COLUMN_DATA),
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null
|
|
||||||
)
|
|
||||||
|
|
||||||
return cursor?.use {
|
|
||||||
if (it.moveToFirst()) {
|
|
||||||
val json =
|
|
||||||
it.getString(it.run { it.getColumnIndex(DeviceInfoContract.DeviceInfoEntry.COLUMN_DATA) })
|
|
||||||
json
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取所有设备信息
|
|
||||||
fun getAllDeviceInfo(context: Context): List<String> {
|
|
||||||
val resolver = context.contentResolver
|
|
||||||
val deviceList = mutableListOf<String>()
|
|
||||||
|
|
||||||
resolver.query(
|
|
||||||
DeviceInfoContract.CONTENT_URI,
|
|
||||||
arrayOf(DeviceInfoContract.DeviceInfoEntry.COLUMN_DATA),
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
"${DeviceInfoContract.DeviceInfoEntry.COLUMN_TIMESTAMP} DESC"
|
|
||||||
)?.use { cursor ->
|
|
||||||
val dataIndex = cursor.getColumnIndex(DeviceInfoContract.DeviceInfoEntry.COLUMN_DATA)
|
|
||||||
|
|
||||||
while (cursor.moveToNext()) {
|
|
||||||
val json = cursor.getString(dataIndex)
|
|
||||||
deviceList.add(json)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return deviceList
|
|
||||||
}
|
|
||||||
|
|
||||||
// 删除设备信息
|
|
||||||
fun deleteDeviceInfo(context: Context, deviceId: String? = null): Int {
|
|
||||||
val actualDeviceId = deviceId ?: DeviceInfoHelper.getDeviceId()
|
|
||||||
return context.contentResolver.delete(
|
|
||||||
getDeviceUri(actualDeviceId),
|
|
||||||
null,
|
|
||||||
null
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getDeviceUri(deviceId: String): Uri {
|
|
||||||
return Uri.withAppendedPath(
|
|
||||||
DeviceInfoContract.CONTENT_URI,
|
|
||||||
"device/$deviceId"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,27 +0,0 @@
|
||||||
package com.android.grape.provider
|
|
||||||
|
|
||||||
import android.net.Uri
|
|
||||||
import androidx.core.net.toUri
|
|
||||||
|
|
||||||
object DeviceInfoContract {
|
|
||||||
const val AUTHORITY = "com.android.grape.deviceinfo.provider"
|
|
||||||
val CONTENT_URI: Uri = "content://$AUTHORITY/device_info".toUri()
|
|
||||||
|
|
||||||
object DeviceInfoEntry {
|
|
||||||
const val TABLE_NAME = "device_info"
|
|
||||||
const val COLUMN_DEVICE_ID = "device_id"
|
|
||||||
const val COLUMN_DATA = "data"
|
|
||||||
const val COLUMN_TIMESTAMP = "timestamp"
|
|
||||||
|
|
||||||
// JSON 键名
|
|
||||||
const val KEY_CPU_ABI = "cpu_abi"
|
|
||||||
const val KEY_DIM = "dim"
|
|
||||||
const val KEY_BTCH = "btch"
|
|
||||||
const val KEY_ARCH = "arch"
|
|
||||||
const val KEY_BTL = "btl"
|
|
||||||
const val KEY_CPU_ABI2 = "cpu_abi2"
|
|
||||||
const val KEY_DISK = "disk"
|
|
||||||
const val KEY_SDK = "sdk"
|
|
||||||
const val KEY_NETWORK = "network"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,33 +0,0 @@
|
||||||
package com.android.grape.provider
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.database.sqlite.SQLiteDatabase
|
|
||||||
import android.database.sqlite.SQLiteOpenHelper
|
|
||||||
import android.provider.BaseColumns
|
|
||||||
|
|
||||||
class DeviceInfoDbHelper(context: Context) : SQLiteOpenHelper(
|
|
||||||
context, DATABASE_NAME, null, DATABASE_VERSION
|
|
||||||
) {
|
|
||||||
companion object {
|
|
||||||
const val DATABASE_NAME = "device_info.db"
|
|
||||||
const val DATABASE_VERSION = 1
|
|
||||||
|
|
||||||
const val SQL_CREATE_ENTRIES = """
|
|
||||||
CREATE TABLE ${DeviceInfoContract.DeviceInfoEntry.TABLE_NAME} (
|
|
||||||
${BaseColumns._ID} INTEGER PRIMARY KEY,
|
|
||||||
${DeviceInfoContract.DeviceInfoEntry.COLUMN_DEVICE_ID} TEXT UNIQUE,
|
|
||||||
${DeviceInfoContract.DeviceInfoEntry.COLUMN_DATA} TEXT NOT NULL,
|
|
||||||
${DeviceInfoContract.DeviceInfoEntry.COLUMN_TIMESTAMP} INTEGER NOT NULL DEFAULT (strftime('%s','now'))
|
|
||||||
)
|
|
||||||
"""
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreate(db: SQLiteDatabase) {
|
|
||||||
db.execSQL(SQL_CREATE_ENTRIES)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
|
|
||||||
db.execSQL("DROP TABLE IF EXISTS ${DeviceInfoContract.DeviceInfoEntry.TABLE_NAME}")
|
|
||||||
onCreate(db)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,8 +0,0 @@
|
||||||
package com.android.grape.provider
|
|
||||||
|
|
||||||
object DeviceInfoHelper {
|
|
||||||
// 获取设备唯一ID
|
|
||||||
fun getDeviceId(): String {
|
|
||||||
return "d1b3e7f8c9a04b8e9f2c1d5e6f7a8b9c"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,148 +0,0 @@
|
||||||
package com.android.grape.provider
|
|
||||||
|
|
||||||
import android.content.ContentProvider
|
|
||||||
import android.content.ContentUris
|
|
||||||
import android.content.ContentValues
|
|
||||||
import android.content.UriMatcher
|
|
||||||
import android.database.Cursor
|
|
||||||
import android.database.sqlite.SQLiteQueryBuilder
|
|
||||||
import android.net.Uri
|
|
||||||
import android.provider.BaseColumns
|
|
||||||
import java.sql.SQLException
|
|
||||||
|
|
||||||
// DeviceInfoProvider.kt
|
|
||||||
class DeviceInfoProvider : ContentProvider() {
|
|
||||||
private lateinit var dbHelper: DeviceInfoDbHelper
|
|
||||||
private val uriMatcher = UriMatcher(UriMatcher.NO_MATCH).apply {
|
|
||||||
addURI(DeviceInfoContract.AUTHORITY, "device_info", 1)
|
|
||||||
addURI(DeviceInfoContract.AUTHORITY, "device_info/#", 2)
|
|
||||||
addURI(DeviceInfoContract.AUTHORITY, "device_info/device/*", 3)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreate(): Boolean {
|
|
||||||
dbHelper = DeviceInfoDbHelper(context!!)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun query(
|
|
||||||
uri: Uri,
|
|
||||||
projection: Array<String>?,
|
|
||||||
selection: String?,
|
|
||||||
selectionArgs: Array<String>?,
|
|
||||||
sortOrder: String?
|
|
||||||
): Cursor? {
|
|
||||||
val db = dbHelper.readableDatabase
|
|
||||||
val qb = SQLiteQueryBuilder().apply {
|
|
||||||
tables = DeviceInfoContract.DeviceInfoEntry.TABLE_NAME
|
|
||||||
}
|
|
||||||
|
|
||||||
when (uriMatcher.match(uri)) {
|
|
||||||
1 -> {} // 查询所有
|
|
||||||
2 -> qb.appendWhere("${BaseColumns._ID} = ${uri.lastPathSegment}")
|
|
||||||
3 -> qb.appendWhere("${DeviceInfoContract.DeviceInfoEntry.COLUMN_DEVICE_ID} = '${uri.lastPathSegment}'")
|
|
||||||
else -> throw IllegalArgumentException("Unknown URI: $uri")
|
|
||||||
}
|
|
||||||
|
|
||||||
return qb.query(db, projection, selection, selectionArgs, null, null, sortOrder).apply {
|
|
||||||
setNotificationUri(context!!.contentResolver, uri)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun insert(uri: Uri, values: ContentValues?): Uri? {
|
|
||||||
val db = dbHelper.writableDatabase
|
|
||||||
val now = System.currentTimeMillis() / 1000
|
|
||||||
|
|
||||||
// 添加时间戳
|
|
||||||
values?.put(DeviceInfoContract.DeviceInfoEntry.COLUMN_TIMESTAMP, now)
|
|
||||||
|
|
||||||
val rowId = db.insert(DeviceInfoContract.DeviceInfoEntry.TABLE_NAME, null, values)
|
|
||||||
|
|
||||||
if (rowId > 0) {
|
|
||||||
val newUri = ContentUris.withAppendedId(DeviceInfoContract.CONTENT_URI, rowId)
|
|
||||||
context?.contentResolver?.notifyChange(newUri, null)
|
|
||||||
return newUri
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun update(
|
|
||||||
uri: Uri,
|
|
||||||
values: ContentValues?,
|
|
||||||
selection: String?,
|
|
||||||
selectionArgs: Array<String>?
|
|
||||||
): Int {
|
|
||||||
val db = dbHelper.writableDatabase
|
|
||||||
val now = System.currentTimeMillis() / 1000
|
|
||||||
|
|
||||||
// 更新时间戳
|
|
||||||
values?.put(DeviceInfoContract.DeviceInfoEntry.COLUMN_TIMESTAMP, now)
|
|
||||||
|
|
||||||
val count = when (uriMatcher.match(uri)) {
|
|
||||||
1 -> db.update(
|
|
||||||
DeviceInfoContract.DeviceInfoEntry.TABLE_NAME,
|
|
||||||
values, selection, selectionArgs
|
|
||||||
)
|
|
||||||
2 -> {
|
|
||||||
val id = uri.lastPathSegment
|
|
||||||
db.update(
|
|
||||||
DeviceInfoContract.DeviceInfoEntry.TABLE_NAME,
|
|
||||||
values,
|
|
||||||
"${BaseColumns._ID} = ?",
|
|
||||||
arrayOf(id)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
3 -> {
|
|
||||||
val deviceId = uri.lastPathSegment
|
|
||||||
db.update(
|
|
||||||
DeviceInfoContract.DeviceInfoEntry.TABLE_NAME,
|
|
||||||
values,
|
|
||||||
"${DeviceInfoContract.DeviceInfoEntry.COLUMN_DEVICE_ID} = ?",
|
|
||||||
arrayOf(deviceId)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
else -> throw IllegalArgumentException("Unknown URI: $uri")
|
|
||||||
}
|
|
||||||
|
|
||||||
context?.contentResolver?.notifyChange(uri, null)
|
|
||||||
return count
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun delete(uri: Uri, selection: String?, selectionArgs: Array<String>?): Int {
|
|
||||||
val db = dbHelper.writableDatabase
|
|
||||||
|
|
||||||
val count = when (uriMatcher.match(uri)) {
|
|
||||||
1 -> db.delete(
|
|
||||||
DeviceInfoContract.DeviceInfoEntry.TABLE_NAME,
|
|
||||||
selection, selectionArgs
|
|
||||||
)
|
|
||||||
2 -> {
|
|
||||||
val id = uri.lastPathSegment
|
|
||||||
db.delete(
|
|
||||||
DeviceInfoContract.DeviceInfoEntry.TABLE_NAME,
|
|
||||||
"${BaseColumns._ID} = ?",
|
|
||||||
arrayOf(id)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
3 -> {
|
|
||||||
val deviceId = uri.lastPathSegment
|
|
||||||
db.delete(
|
|
||||||
DeviceInfoContract.DeviceInfoEntry.TABLE_NAME,
|
|
||||||
"${DeviceInfoContract.DeviceInfoEntry.COLUMN_DEVICE_ID} = ?",
|
|
||||||
arrayOf(deviceId)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
else -> throw IllegalArgumentException("Unknown URI: $uri")
|
|
||||||
}
|
|
||||||
|
|
||||||
context?.contentResolver?.notifyChange(uri, null)
|
|
||||||
return count
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getType(uri: Uri): String? {
|
|
||||||
return when (uriMatcher.match(uri)) {
|
|
||||||
1 -> "${DeviceInfoContract.AUTHORITY}/device_info"
|
|
||||||
2, 3 -> "vnd.android.cursor.item/vnd.example.deviceinfo"
|
|
||||||
else -> throw IllegalArgumentException("Unknown URI: $uri")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -83,9 +83,7 @@ class ApkSourceBuilder(private val mContext: Context) {
|
||||||
val apkFileDescriptors: MutableList<FileDescriptor> = ArrayList<FileDescriptor>(
|
val apkFileDescriptors: MutableList<FileDescriptor> = ArrayList<FileDescriptor>(
|
||||||
mApkFiles?.size?: 0
|
mApkFiles?.size?: 0
|
||||||
)
|
)
|
||||||
mApkFiles?.let {
|
for (apkFile in mApkFiles!!) apkFileDescriptors.add(NormalFileDescriptor(apkFile))
|
||||||
for (apkFile in it) apkFileDescriptors.add(NormalFileDescriptor(apkFile))
|
|
||||||
}
|
|
||||||
|
|
||||||
apkSource = DefaultApkSource(apkFileDescriptors)
|
apkSource = DefaultApkSource(apkFileDescriptors)
|
||||||
} else if (mZipFile != null) {
|
} else if (mZipFile != null) {
|
||||||
|
|
|
@ -59,16 +59,13 @@ class FlexSaiPackageInstaller private constructor(c: Context) : SaiPackageInstal
|
||||||
installer: SaiPackageInstaller,
|
installer: SaiPackageInstaller,
|
||||||
params: SaiPiSessionParams
|
params: SaiPiSessionParams
|
||||||
): String {
|
): String {
|
||||||
val sessionId = installer.createSession(params)?:""
|
val sessionId = installer.createSession(params)
|
||||||
mSessionIdToInstaller[sessionId] = installer
|
mSessionIdToInstaller[sessionId!!] = installer
|
||||||
return sessionId
|
return sessionId
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun createSession(params: SaiPiSessionParams): String {
|
override fun createSession(params: SaiPiSessionParams): String {
|
||||||
mDefaultInstaller?.let {
|
return createSessionOnInstaller(mDefaultInstaller!!, params)
|
||||||
return createSessionOnInstaller(it, params)
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun enqueueSession(sessionId: String) {
|
override fun enqueueSession(sessionId: String) {
|
||||||
|
|
|
@ -18,7 +18,7 @@ class MyBroadcastReceiver : BroadcastReceiver() {
|
||||||
//接收广播消息
|
//接收广播消息
|
||||||
fruit = intent.getStringExtra("fruit")
|
fruit = intent.getStringExtra("fruit")
|
||||||
//调用接口MyReceiver里面的interFruit方法传入接收的内容
|
//调用接口MyReceiver里面的interFruit方法传入接收的内容
|
||||||
mReceiver?.interFruit(fruit)
|
mReceiver!!.interFruit(fruit)
|
||||||
//使用Toast显示广播消息
|
//使用Toast显示广播消息
|
||||||
Toast.makeText(context, fruit, Toast.LENGTH_SHORT).show()
|
Toast.makeText(context, fruit, Toast.LENGTH_SHORT).show()
|
||||||
}
|
}
|
||||||
|
|
|
@ -116,7 +116,7 @@ class RootlessSaiPiBroadcastReceiver(c: Context) : BroadcastReceiver() {
|
||||||
|
|
||||||
val androidPackageInstallerError: AndroidPackageInstallerError =
|
val androidPackageInstallerError: AndroidPackageInstallerError =
|
||||||
getAndroidPmError(errorCode, error)
|
getAndroidPmError(errorCode, error)
|
||||||
if (androidPackageInstallerError != AndroidPackageInstallerError.UNKNOWN) {
|
if (androidPackageInstallerError !== AndroidPackageInstallerError.UNKNOWN) {
|
||||||
return androidPackageInstallerError.getDescription(mContext)
|
return androidPackageInstallerError.getDescription(mContext)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -53,9 +53,9 @@ abstract class ShellSaiPackageInstaller protected constructor(c: Context?) :
|
||||||
|
|
||||||
val installedPackage: String
|
val installedPackage: String
|
||||||
try {
|
try {
|
||||||
installedPackage = intent.dataString?.replace("package:", "")?:""
|
installedPackage = intent.dataString!!.replace("package:", "")
|
||||||
val installerPackage: String =
|
val installerPackage: String =
|
||||||
context.packageManager.getInstallerPackageName(installedPackage)?:""
|
context.getPackageManager().getInstallerPackageName(installedPackage)?:""
|
||||||
Log.d(tag(), "installerPackage=$installerPackage")
|
Log.d(tag(), "installerPackage=$installerPackage")
|
||||||
if ("com.android.grape" != installerPackage) return
|
if ("com.android.grape" != installerPackage) return
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
@ -130,44 +130,74 @@ abstract class ShellSaiPackageInstaller protected constructor(c: Context?) :
|
||||||
).build()
|
).build()
|
||||||
)
|
)
|
||||||
unlockInstallation()
|
unlockInstallation()
|
||||||
|
// Toast.makeText(getContext(),"Installation failed",Toast.LENGTH_SHORT).show();
|
||||||
Util.setInstallRet(false)
|
Util.setInstallRet(false)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
androidSessionId = createSession()
|
androidSessionId = createSession()
|
||||||
//todo params.apkSource().apkLocalPath?
|
val path = "/sdcard/apks/" + "com.zhiliaoapp.musically"
|
||||||
val path = "/sdcard/apks/${Util.recordPackageName}"
|
|
||||||
val file = File(path)
|
val file = File(path)
|
||||||
|
|
||||||
val files = file.listFiles()
|
val files = file.listFiles()
|
||||||
var currentApkFile = 0
|
var currentApkFile = 0
|
||||||
files?.let {
|
|
||||||
for (f in files) {
|
for (f in files) {
|
||||||
if (f.length() <= 0) {
|
if (f.length() <= 0) {
|
||||||
setSessionState(sessionId, SaiPiSessionState.Builder(sessionId, SaiPiSessionStatus.INSTALLATION_FAILED).appTempName(appTempName).error(MainApplication.instance.getString(R.string.installer_error_unknown_apk_size), null).build())
|
setSessionState(
|
||||||
|
sessionId,
|
||||||
|
SaiPiSessionState.Builder(sessionId, SaiPiSessionStatus.INSTALLATION_FAILED).appTempName(
|
||||||
|
appTempName
|
||||||
|
).error(
|
||||||
|
MainApplication.instance.getString(R.string.installer_error_unknown_apk_size),
|
||||||
|
null
|
||||||
|
).build()
|
||||||
|
)
|
||||||
unlockInstallation()
|
unlockInstallation()
|
||||||
|
// Toast.makeText(getContext(),"Installation failed",Toast.LENGTH_SHORT).show();
|
||||||
Util.setInstallRet(false)
|
Util.setInstallRet(false)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ensureCommandSucceeded(shell.exec(Shell.Command("pm", "install-write", f.length().toString(), androidSessionId.toString(), String.format("%d.apk", currentApkFile++), f.path)))
|
ensureCommandSucceeded(
|
||||||
}
|
shell.exec(
|
||||||
|
Shell.Command(
|
||||||
|
"pm",
|
||||||
|
"install-write",
|
||||||
|
f.length().toString(),
|
||||||
|
androidSessionId.toString(),
|
||||||
|
String.format("%d.apk", currentApkFile++),
|
||||||
|
f.path
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
mAwaitingBroadcast.set(true)
|
mAwaitingBroadcast.set(true)
|
||||||
val installationResult: Shell.Result = shell.exec(Shell.Command("pm", "install-commit", androidSessionId.toString()))
|
val installationResult: Shell.Result =
|
||||||
|
shell.exec(Shell.Command("pm", "install-commit", androidSessionId.toString()))
|
||||||
Log.i(tag(), "installationResult:" + installationResult.isSuccessful)
|
Log.i(tag(), "installationResult:" + installationResult.isSuccessful)
|
||||||
if (!installationResult.isSuccessful) {
|
if (!installationResult.isSuccessful) {
|
||||||
mAwaitingBroadcast.set(false)
|
mAwaitingBroadcast.set(false)
|
||||||
val shortError: String = MainApplication.instance.getString(R.string.installer_error_shell, installerName, """
|
|
||||||
|
val shortError: String = MainApplication.instance.getString(
|
||||||
|
R.string.installer_error_shell,
|
||||||
|
installerName, """
|
||||||
${getSessionInfo(apkSource)}
|
${getSessionInfo(apkSource)}
|
||||||
|
|
||||||
${parseError(installationResult)}
|
${parseError(installationResult)}
|
||||||
""".trimIndent())
|
""".trimIndent()
|
||||||
setSessionState(sessionId, SaiPiSessionState.Builder(sessionId, SaiPiSessionStatus.INSTALLATION_FAILED).appTempName(appTempName).error(
|
)
|
||||||
|
setSessionState(
|
||||||
|
sessionId, SaiPiSessionState.Builder(sessionId, SaiPiSessionStatus.INSTALLATION_FAILED)
|
||||||
|
.appTempName(appTempName)
|
||||||
|
.error(
|
||||||
shortError, """
|
shortError, """
|
||||||
$shortError
|
$shortError
|
||||||
|
|
||||||
${installationResult.toString()}
|
${installationResult.toString()}
|
||||||
""".trimIndent()).build())
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
|
||||||
unlockInstallation()
|
unlockInstallation()
|
||||||
|
|
||||||
Util.setInstallRet(false)
|
Util.setInstallRet(false)
|
||||||
|
@ -176,6 +206,7 @@ abstract class ShellSaiPackageInstaller protected constructor(c: Context?) :
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
//TODO this catches resources close exception causing a crash, same in rootless installer
|
||||||
Log.w(tag(), e)
|
Log.w(tag(), e)
|
||||||
|
|
||||||
if (androidSessionId != null) {
|
if (androidSessionId != null) {
|
||||||
|
@ -260,7 +291,7 @@ abstract class ShellSaiPackageInstaller protected constructor(c: Context?) :
|
||||||
DbgPreferencesHelper.customInstallCreateCommand
|
DbgPreferencesHelper.customInstallCreateCommand
|
||||||
if (customInstallCreateCommand != null) {
|
if (customInstallCreateCommand != null) {
|
||||||
val args = ArrayList(
|
val args = ArrayList(
|
||||||
listOf(
|
Arrays.asList(
|
||||||
*customInstallCreateCommand.split(" ".toRegex()).dropLastWhile { it.isEmpty() }
|
*customInstallCreateCommand.split(" ".toRegex()).dropLastWhile { it.isEmpty() }
|
||||||
.toTypedArray()))
|
.toTypedArray()))
|
||||||
val command = args.removeAt(0)
|
val command = args.removeAt(0)
|
||||||
|
@ -325,7 +356,7 @@ abstract class ShellSaiPackageInstaller protected constructor(c: Context?) :
|
||||||
val sessionIdPattern = Pattern.compile("(\\d+)")
|
val sessionIdPattern = Pattern.compile("(\\d+)")
|
||||||
val sessionIdMatcher = sessionIdPattern.matcher(commandResult)
|
val sessionIdMatcher = sessionIdPattern.matcher(commandResult)
|
||||||
sessionIdMatcher.find()
|
sessionIdMatcher.find()
|
||||||
return sessionIdMatcher.group(1)?.toInt()
|
return sessionIdMatcher.group(1).toInt()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.w(tag(), commandResult, e)
|
Log.w(tag(), commandResult, e)
|
||||||
return null
|
return null
|
||||||
|
@ -334,7 +365,7 @@ abstract class ShellSaiPackageInstaller protected constructor(c: Context?) :
|
||||||
|
|
||||||
private fun parseError(installCommitResult: Shell.Result): String {
|
private fun parseError(installCommitResult: Shell.Result): String {
|
||||||
var matchedError: AndroidPackageInstallerError = AndroidPackageInstallerError.UNKNOWN
|
var matchedError: AndroidPackageInstallerError = AndroidPackageInstallerError.UNKNOWN
|
||||||
for (error in AndroidPackageInstallerError.entries) {
|
for (error in AndroidPackageInstallerError.values()) {
|
||||||
if (installCommitResult.out.contains(error.error)) {
|
if (installCommitResult.out.contains(error.error)) {
|
||||||
matchedError = error
|
matchedError = error
|
||||||
break
|
break
|
||||||
|
|
|
@ -28,7 +28,7 @@ class CopyToFileApkSource(context: Context, wrappedApkSource: ApkSource) :
|
||||||
IOUtils.deleteRecursively(it)
|
IOUtils.deleteRecursively(it)
|
||||||
}
|
}
|
||||||
|
|
||||||
mCurrentApkFile = File(mTempDir, mWrappedApkSource.apkName?:"")
|
mCurrentApkFile = File(mTempDir, mWrappedApkSource.apkName)
|
||||||
|
|
||||||
mWrappedApkSource.openApkInputStream().use { `in` ->
|
mWrappedApkSource.openApkInputStream().use { `in` ->
|
||||||
FileOutputStream(mCurrentApkFile).use { out ->
|
FileOutputStream(mCurrentApkFile).use { out ->
|
||||||
|
|
|
@ -34,7 +34,7 @@ class SignerApkSource(private val mContext: Context, apkSource: ApkSource) : Apk
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
mCurrentSignedApkFile = File(mTempDir, apkName?:"")
|
mCurrentSignedApkFile = File(mTempDir, apkName)
|
||||||
mWrappedApkSource.openApkInputStream()?.let {
|
mWrappedApkSource.openApkInputStream()?.let {
|
||||||
mApkSigner?.sign(
|
mApkSigner?.sign(
|
||||||
it,
|
it,
|
||||||
|
|
|
@ -38,11 +38,11 @@ object FileUtils {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun trimFilename(res: StringBuilder, maxBytes: Int) {
|
private fun trimFilename(res: StringBuilder, maxBytes: Int) {
|
||||||
var bytes = maxBytes
|
var maxBytes = maxBytes
|
||||||
var raw = res.toString().toByteArray(StandardCharsets.UTF_8)
|
var raw = res.toString().toByteArray(StandardCharsets.UTF_8)
|
||||||
if (raw.size > bytes) {
|
if (raw.size > maxBytes) {
|
||||||
bytes -= 3
|
maxBytes -= 3
|
||||||
while (raw.size > bytes) {
|
while (raw.size > maxBytes) {
|
||||||
res.deleteCharAt(res.length / 2)
|
res.deleteCharAt(res.length / 2)
|
||||||
raw = res.toString().toByteArray(StandardCharsets.UTF_8)
|
raw = res.toString().toByteArray(StandardCharsets.UTF_8)
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,15 +25,14 @@ class ZipApkSource(private val mContext: Context, private val mZipFileDescriptor
|
||||||
@Throws(Exception::class)
|
@Throws(Exception::class)
|
||||||
override fun nextApk(): Boolean {
|
override fun nextApk(): Boolean {
|
||||||
if (!mIsOpen) {
|
if (!mIsOpen) {
|
||||||
mZipInputStream = ZipInputStream(mZipFileDescriptor.open()).apply {
|
mZipInputStream = ZipInputStream(mZipFileDescriptor.open())
|
||||||
mWrappedStream = ZipInputStreamWrapper(this)
|
mWrappedStream = ZipInputStreamWrapper(mZipInputStream!!)
|
||||||
}
|
|
||||||
mIsOpen = true
|
mIsOpen = true
|
||||||
}
|
}
|
||||||
|
|
||||||
do {
|
do {
|
||||||
try {
|
try {
|
||||||
entry = mZipInputStream?.nextEntry
|
entry = mZipInputStream!!.nextEntry
|
||||||
} catch (e: ZipException) {
|
} catch (e: ZipException) {
|
||||||
if (e.message == "only DEFLATED entries can have EXT descriptor") {
|
if (e.message == "only DEFLATED entries can have EXT descriptor") {
|
||||||
throw ZipException("only DEFLATED entries can have EXT descriptor")
|
throw ZipException("only DEFLATED entries can have EXT descriptor")
|
||||||
|
|
|
@ -35,8 +35,8 @@ class ZipFileApkSource(context: Context, private val mZipFileDescriptor: FileDes
|
||||||
if (mZipFile == null) copyAndOpenZip()
|
if (mZipFile == null) copyAndOpenZip()
|
||||||
|
|
||||||
entry = null
|
entry = null
|
||||||
while (entry == null && mZipEntries?.hasMoreElements() == true) {
|
while (entry == null && mZipEntries!!.hasMoreElements()) {
|
||||||
mZipEntries?.nextElement()?.let { nextEntry ->
|
val nextEntry = mZipEntries!!.nextElement()
|
||||||
if (!nextEntry.isDirectory && nextEntry.name.lowercase(Locale.getDefault())
|
if (!nextEntry.isDirectory && nextEntry.name.lowercase(Locale.getDefault())
|
||||||
.endsWith(".apk")
|
.endsWith(".apk")
|
||||||
) {
|
) {
|
||||||
|
@ -44,7 +44,6 @@ class ZipFileApkSource(context: Context, private val mZipFileDescriptor: FileDes
|
||||||
mSeenApkFile = true
|
mSeenApkFile = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (entry == null) {
|
if (entry == null) {
|
||||||
require(mSeenApkFile) { mContext.getString(R.string.installer_error_zip_contains_no_apks) }
|
require(mSeenApkFile) { mContext.getString(R.string.installer_error_zip_contains_no_apks) }
|
||||||
|
|
|
@ -138,7 +138,7 @@ class PackageMeta : Parcelable {
|
||||||
|
|
||||||
return Builder(applicationInfo.packageName)
|
return Builder(applicationInfo.packageName)
|
||||||
.setLabel(applicationInfo.loadLabel(pm).toString())
|
.setLabel(applicationInfo.loadLabel(pm).toString())
|
||||||
.setHasSplits(applicationInfo.splitPublicSourceDirs != null && applicationInfo.splitPublicSourceDirs?.isNotEmpty() == true)
|
.setHasSplits(applicationInfo.splitPublicSourceDirs != null && applicationInfo.splitPublicSourceDirs!!.size > 0)
|
||||||
.setIsSystemApp((applicationInfo.flags and ApplicationInfo.FLAG_SYSTEM) != 0)
|
.setIsSystemApp((applicationInfo.flags and ApplicationInfo.FLAG_SYSTEM) != 0)
|
||||||
.setVersionCode(if (Utils.apiIsAtLeast(Build.VERSION_CODES.P)) packageInfo.longVersionCode else packageInfo.versionCode.toLong())
|
.setVersionCode(if (Utils.apiIsAtLeast(Build.VERSION_CODES.P)) packageInfo.longVersionCode else packageInfo.versionCode.toLong())
|
||||||
.setVersionName(packageInfo.versionName)
|
.setVersionName(packageInfo.versionName)
|
||||||
|
|
|
@ -127,11 +127,11 @@ class SaiPiSessionState private constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
fun error(shortError: String?, fullError: String?): Builder {
|
fun error(shortError: String?, fullError: String?): Builder {
|
||||||
var error = fullError
|
var fullError = fullError
|
||||||
mState.mShortError = shortError
|
mState.mShortError = shortError
|
||||||
if (error == null) error = shortError
|
if (fullError == null) fullError = shortError
|
||||||
|
|
||||||
mState.mFullError = error
|
mState.mFullError = fullError
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,6 @@ package com.android.grape.sai.prefers
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.os.Environment
|
import android.os.Environment
|
||||||
import androidx.core.content.edit
|
|
||||||
|
|
||||||
|
|
||||||
class PreferencesHelper private constructor(c: Context) {
|
class PreferencesHelper private constructor(c: Context) {
|
||||||
|
@ -51,7 +50,7 @@ class PreferencesHelper private constructor(c: Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setShouldSignApks(signApks: Boolean) {
|
fun setShouldSignApks(signApks: Boolean) {
|
||||||
prefs.edit { putBoolean(PreferencesKeys.SIGN_APKS, signApks) }
|
prefs.edit().putBoolean(PreferencesKeys.SIGN_APKS, signApks).apply()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun shouldExtractArchives(): Boolean {
|
fun shouldExtractArchives(): Boolean {
|
||||||
|
@ -65,7 +64,7 @@ class PreferencesHelper private constructor(c: Context) {
|
||||||
var installer: Int
|
var installer: Int
|
||||||
get() = prefs.getInt(PreferencesKeys.INSTALLER, PreferencesValues.INSTALLER_ROOTED)
|
get() = prefs.getInt(PreferencesKeys.INSTALLER, PreferencesValues.INSTALLER_ROOTED)
|
||||||
set(installer) {
|
set(installer) {
|
||||||
prefs.edit { putInt(PreferencesKeys.INSTALLER, installer) }
|
prefs.edit().putInt(PreferencesKeys.INSTALLER, installer).apply()
|
||||||
}
|
}
|
||||||
|
|
||||||
var backupFileNameFormat: String?
|
var backupFileNameFormat: String?
|
||||||
|
@ -74,7 +73,7 @@ class PreferencesHelper private constructor(c: Context) {
|
||||||
PreferencesValues.BACKUP_FILE_NAME_FORMAT_DEFAULT
|
PreferencesValues.BACKUP_FILE_NAME_FORMAT_DEFAULT
|
||||||
)
|
)
|
||||||
set(format) {
|
set(format) {
|
||||||
prefs.edit { putString(PreferencesKeys.BACKUP_FILE_NAME_FORMAT, format) }
|
prefs.edit().putString(PreferencesKeys.BACKUP_FILE_NAME_FORMAT, format).apply()
|
||||||
}
|
}
|
||||||
|
|
||||||
var installLocation: Int
|
var installLocation: Int
|
||||||
|
@ -88,9 +87,8 @@ class PreferencesHelper private constructor(c: Context) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
set(installLocation) {
|
set(installLocation) {
|
||||||
prefs.edit {
|
prefs.edit().putString(PreferencesKeys.INSTALL_LOCATION, installLocation.toString())
|
||||||
putString(PreferencesKeys.INSTALL_LOCATION, installLocation.toString())
|
.apply()
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun useOldInstaller(): Boolean {
|
fun useOldInstaller(): Boolean {
|
||||||
|
@ -110,7 +108,7 @@ class PreferencesHelper private constructor(c: Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setSafTipShown() {
|
fun setSafTipShown() {
|
||||||
prefs.edit { putBoolean(PreferencesKeys.SAF_TIP_SHOWN, true) }
|
prefs.edit().putBoolean(PreferencesKeys.SAF_TIP_SHOWN, true).apply()
|
||||||
}
|
}
|
||||||
|
|
||||||
val isInstallerXEnabled: Boolean
|
val isInstallerXEnabled: Boolean
|
||||||
|
@ -122,19 +120,19 @@ class PreferencesHelper private constructor(c: Context) {
|
||||||
var isAnalyticsEnabled: Boolean
|
var isAnalyticsEnabled: Boolean
|
||||||
get() = prefs.getBoolean(PreferencesKeys.ENABLE_ANALYTICS, true)
|
get() = prefs.getBoolean(PreferencesKeys.ENABLE_ANALYTICS, true)
|
||||||
set(enabled) {
|
set(enabled) {
|
||||||
prefs.edit { putBoolean(PreferencesKeys.ENABLE_ANALYTICS, enabled) }
|
prefs.edit().putBoolean(PreferencesKeys.ENABLE_ANALYTICS, enabled).apply()
|
||||||
}
|
}
|
||||||
|
|
||||||
var isInitialIndexingDone: Boolean
|
var isInitialIndexingDone: Boolean
|
||||||
get() = prefs.getBoolean(PreferencesKeys.INITIAL_INDEXING_RUN, false)
|
get() = prefs.getBoolean(PreferencesKeys.INITIAL_INDEXING_RUN, false)
|
||||||
set(done) {
|
set(done) {
|
||||||
prefs.edit { putBoolean(PreferencesKeys.INITIAL_INDEXING_RUN, done) }
|
prefs.edit().putBoolean(PreferencesKeys.INITIAL_INDEXING_RUN, done).apply()
|
||||||
}
|
}
|
||||||
|
|
||||||
var isSingleApkExportEnabled: Boolean
|
var isSingleApkExportEnabled: Boolean
|
||||||
get() = prefs.getBoolean(PreferencesKeys.BACKUP_APK_EXPORT, false)
|
get() = prefs.getBoolean(PreferencesKeys.BACKUP_APK_EXPORT, false)
|
||||||
set(enabled) {
|
set(enabled) {
|
||||||
prefs.edit { putBoolean(PreferencesKeys.BACKUP_APK_EXPORT, enabled) }
|
prefs.edit().putBoolean(PreferencesKeys.BACKUP_APK_EXPORT, enabled).apply()
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
|
@ -40,9 +40,11 @@ class MyAccessibilityService:AccessibilityService(){
|
||||||
val manager: NotificationManager = getSystemService<NotificationManager>(
|
val manager: NotificationManager = getSystemService<NotificationManager>(
|
||||||
NotificationManager::class.java
|
NotificationManager::class.java
|
||||||
)
|
)
|
||||||
|
if (manager != null) {
|
||||||
manager.createNotificationChannel(channel)
|
manager.createNotificationChannel(channel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun startForegroundService() {
|
private fun startForegroundService() {
|
||||||
createNotificationChannel()
|
createNotificationChannel()
|
||||||
|
|
|
@ -1,128 +0,0 @@
|
||||||
package com.android.grape.util
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.os.Environment
|
|
||||||
import androidx.core.content.ContextCompat
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import okhttp3.OkHttpClient
|
|
||||||
import okhttp3.Request
|
|
||||||
import java.io.File
|
|
||||||
import java.io.FileOutputStream
|
|
||||||
import java.io.IOException
|
|
||||||
import java.io.InputStream
|
|
||||||
|
|
||||||
class AndroidFileDownloader(private val context: Context) {
|
|
||||||
|
|
||||||
private val client = OkHttpClient()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 下载文件到指定路径
|
|
||||||
*
|
|
||||||
* @param url 文件下载URL
|
|
||||||
* @param relativePath 相对于外部存储目录的路径(如 "Downloads/MyApp/file.zip")
|
|
||||||
* @param fileName 文件名(可选,如未提供则从URL提取)
|
|
||||||
* @param progressCallback 进度回调
|
|
||||||
* @param completionCallback 完成回调
|
|
||||||
*/
|
|
||||||
fun downloadFile(
|
|
||||||
url: String,
|
|
||||||
relativePath: String,
|
|
||||||
fileName: String? = null,
|
|
||||||
): Boolean {
|
|
||||||
try {
|
|
||||||
// 检查存储权限
|
|
||||||
if (!hasStoragePermission()) {
|
|
||||||
throw IOException("缺少存储权限")
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取目标文件
|
|
||||||
val file = getOutputFile(relativePath, fileName ?: extractFileName(url))
|
|
||||||
|
|
||||||
// 确保目录存在
|
|
||||||
file.parentFile?.let { parentDir ->
|
|
||||||
if (!parentDir.exists()) {
|
|
||||||
val dirsCreated = parentDir.mkdirs()
|
|
||||||
if (!dirsCreated) {
|
|
||||||
throw IOException("无法创建目录: ${parentDir.absolutePath}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val request = Request.Builder()
|
|
||||||
.url(url)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
client.newCall(request).execute().use { response ->
|
|
||||||
if (!response.isSuccessful) {
|
|
||||||
throw IOException("下载失败: ${response.code} ${response.message}")
|
|
||||||
}
|
|
||||||
|
|
||||||
val body = response.body ?: throw IOException("响应体为空")
|
|
||||||
val totalBytes = body.contentLength()
|
|
||||||
|
|
||||||
// 写入文件
|
|
||||||
body.byteStream().use { inputStream ->
|
|
||||||
FileOutputStream(file).use { outputStream ->
|
|
||||||
copyStreamWithProgress(inputStream, outputStream, totalBytes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
e.printStackTrace()
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查存储权限
|
|
||||||
*/
|
|
||||||
private fun hasStoragePermission(): Boolean {
|
|
||||||
return ContextCompat.checkSelfPermission(
|
|
||||||
context,
|
|
||||||
android.Manifest.permission.WRITE_EXTERNAL_STORAGE
|
|
||||||
) == android.content.pm.PackageManager.PERMISSION_GRANTED
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取输出文件路径
|
|
||||||
*/
|
|
||||||
private fun getOutputFile(relativePath: String, fileName: String): File {
|
|
||||||
val baseDir = context.getExternalFilesDir(null)
|
|
||||||
?: Environment.getExternalStorageDirectory()
|
|
||||||
?: throw IOException("无法访问外部存储")
|
|
||||||
|
|
||||||
return File(baseDir, "$relativePath/$fileName")
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 从URL提取文件名
|
|
||||||
*/
|
|
||||||
private fun extractFileName(url: String): String {
|
|
||||||
return url.substringAfterLast('/').takeIf { it.isNotBlank() } ?: "downloaded_file"
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 带进度回调的流复制
|
|
||||||
*/
|
|
||||||
private fun copyStreamWithProgress(
|
|
||||||
inputStream: InputStream,
|
|
||||||
outputStream: FileOutputStream,
|
|
||||||
totalBytes: Long?,
|
|
||||||
progressCallback: ((Long, Long?) -> Unit)? = null
|
|
||||||
) {
|
|
||||||
val buffer = ByteArray(4096)
|
|
||||||
var bytesCopied = 0L
|
|
||||||
var bytesRead: Int
|
|
||||||
|
|
||||||
while (inputStream.read(buffer).also { bytesRead = it } != -1) {
|
|
||||||
outputStream.write(buffer, 0, bytesRead)
|
|
||||||
bytesCopied += bytesRead
|
|
||||||
progressCallback?.invoke(bytesCopied, totalBytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
outputStream.flush()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -5,175 +5,14 @@ import android.content.Context
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import com.android.grape.MainApplication
|
import com.android.grape.MainApplication
|
||||||
import com.android.grape.data.Device
|
import com.android.grape.data.Device
|
||||||
import com.android.grape.net.Api
|
|
||||||
import com.android.grape.net.ChangeCallBack
|
|
||||||
import com.android.grape.provider.DeviceDataAccessor
|
|
||||||
import com.android.grape.util.Util.paramsJson
|
import com.android.grape.util.Util.paramsJson
|
||||||
import com.blankj.utilcode.util.LogUtils
|
import com.blankj.utilcode.util.LogUtils
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.delay
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import org.json.JSONArray
|
|
||||||
import org.json.JSONException
|
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
import java.lang.reflect.InvocationTargetException
|
import java.lang.reflect.InvocationTargetException
|
||||||
|
|
||||||
object ChangeDeviceInfoUtil {
|
object ChangeDeviceInfoUtil {
|
||||||
val scope = CoroutineScope(Dispatchers.IO)
|
|
||||||
fun changeDevice(callBack: ChangeCallBack){
|
|
||||||
try {
|
|
||||||
val deviceObject = paramsJson?.getJSONObject("device")
|
|
||||||
if (deviceObject == null) {
|
|
||||||
LogUtils.d("ERROR", "ChangeDeviceInfoUtil", "device is null")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
val device = GsonUtils.fromJsonObject(deviceObject.toString(), Device::class.java)
|
|
||||||
DeviceDataAccessor.saveDeviceInfo(MainApplication.instance, device)
|
|
||||||
val padCode = ShellUtils.execRootCmdAndGetResult("getprop ro.boot.pad_code")
|
|
||||||
Log.d("TAG", "changeDevice: $padCode")
|
|
||||||
val jsonString = JSONObject().apply {
|
|
||||||
put("padCodes", JSONArray().apply {
|
|
||||||
put(padCode)
|
|
||||||
})
|
|
||||||
put("modemPropertiesList", JSONArray().apply {
|
|
||||||
put(JSONObject().apply {
|
|
||||||
put("propertiesName", "MCCMNC")
|
|
||||||
put("propertiesValue", "${device.mcc},${device.mnc}")
|
|
||||||
})
|
|
||||||
put(JSONObject().apply {
|
|
||||||
put("propertiesName", "OpName")
|
|
||||||
put("propertiesValue", device.operator)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
put("systemPropertiesList", JSONArray().apply {
|
|
||||||
put(JSONObject().apply {
|
|
||||||
put("propertiesName", "ro.product.manufacturer")
|
|
||||||
put("propertiesValue", device.manufactor)
|
|
||||||
})
|
|
||||||
put(JSONObject().apply {
|
|
||||||
put("propertiesName", "ro.product.brand")
|
|
||||||
put("propertiesValue", device.brand)
|
|
||||||
})
|
|
||||||
put(JSONObject().apply {
|
|
||||||
put("propertiesName", "ro.product.model")
|
|
||||||
put("propertiesValue", device.model)
|
|
||||||
})
|
|
||||||
// put(JSONObject().apply {
|
|
||||||
// put("propertiesName", "ro.build.id")
|
|
||||||
// put("propertiesValue", device.buildDisplayId)
|
|
||||||
// })
|
|
||||||
put(JSONObject().apply {
|
|
||||||
put("propertiesName", "ro.build.display.id")
|
|
||||||
put("propertiesValue", device.buildDisplayId)
|
|
||||||
})
|
|
||||||
put(JSONObject().apply {
|
|
||||||
put("propertiesName", "ro.product.name")
|
|
||||||
put("propertiesValue", device.product)
|
|
||||||
})
|
|
||||||
put(JSONObject().apply {
|
|
||||||
put("propertiesName", "ro.product.device")
|
|
||||||
put("propertiesValue", device.device)
|
|
||||||
})
|
|
||||||
put(JSONObject().apply {
|
|
||||||
put("propertiesName", "ro.product.board")
|
|
||||||
put("propertiesValue", device.expand.board)
|
|
||||||
})
|
|
||||||
// put(JSONObject().apply {
|
|
||||||
// put("propertiesName", "ro.build.tags")
|
|
||||||
// put("propertiesValue", device.expand)
|
|
||||||
// })
|
|
||||||
put(JSONObject().apply {
|
|
||||||
put("propertiesName", "ro.build.fingerprint")
|
|
||||||
put("propertiesValue", device.expand.fingerprint)
|
|
||||||
})
|
|
||||||
put(JSONObject().apply {
|
|
||||||
put("propertiesName", "ro.build.date.utc")
|
|
||||||
put("propertiesValue", device.expand.roBuildDateUtc)
|
|
||||||
})
|
|
||||||
put(JSONObject().apply {
|
|
||||||
put("propertiesName", "ro.build.user")
|
|
||||||
put("propertiesValue", device.expand.uname)
|
|
||||||
})
|
|
||||||
// put(JSONObject().apply {
|
|
||||||
// put("propertiesName", "ro.build.host")
|
|
||||||
// put("propertiesValue", device.expand.)
|
|
||||||
// })
|
|
||||||
put(JSONObject().apply {
|
|
||||||
put("propertiesName", "ro.build.description")
|
|
||||||
put("propertiesValue", device.expand.roBuildDescription)
|
|
||||||
})
|
|
||||||
put(JSONObject().apply {
|
|
||||||
put("propertiesName", "ro.build.version.incremental")
|
|
||||||
put("propertiesValue", device.expand.incremental)
|
|
||||||
})
|
|
||||||
// put(JSONObject().apply {
|
|
||||||
// put("propertiesName", "ro.build.version.codename")
|
|
||||||
// put("propertiesValue", device.cod)
|
|
||||||
// })
|
|
||||||
})
|
|
||||||
put("settingPropertiesList", JSONArray().apply {
|
|
||||||
put(JSONObject().apply {
|
|
||||||
put("propertiesName", "ssaid/${Util.recordPackageName}")
|
|
||||||
put("propertiesValue", device.androidId)
|
|
||||||
})
|
|
||||||
put(JSONObject().apply {
|
|
||||||
put("propertiesName", "bt/mac")
|
|
||||||
put("propertiesValue", device.expand.lyMAC)
|
|
||||||
})
|
|
||||||
put(JSONObject().apply {
|
|
||||||
put("propertiesName", "language")
|
|
||||||
put("propertiesValue", device.locale.lang)
|
|
||||||
})
|
|
||||||
put(JSONObject().apply {
|
|
||||||
put("propertiesName", "timezone")
|
|
||||||
put("propertiesValue", device.tzDisplayName)
|
|
||||||
})
|
|
||||||
// put(JSONObject().apply {
|
|
||||||
// put("propertiesName", "systemvolume")
|
|
||||||
// put("propertiesValue", device.expand.)
|
|
||||||
// })
|
|
||||||
})
|
|
||||||
put("oaidPropertiesList", JSONArray().apply {
|
|
||||||
put(JSONObject().apply {
|
|
||||||
put("propertiesName", "AAID")
|
|
||||||
put("propertiesValue", device.advertiserId)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}.toString()
|
|
||||||
val response = Api.updatePad(jsonString)
|
|
||||||
val dataList = response.data
|
|
||||||
if (response.isSuccess() && dataList!= null && dataList.isNotEmpty()){
|
|
||||||
val padTask = dataList[0]
|
|
||||||
scope.launch {
|
|
||||||
Log.d("TAG", "changeDevice: $padTask")
|
|
||||||
var loop = true
|
|
||||||
while (loop) {
|
|
||||||
delay(5000)
|
|
||||||
val result = Api.padTaskDetail(padTask.taskId)
|
|
||||||
if (result == 3) {
|
|
||||||
Log.d("ChangeDeviceInfoUtil", "changeDeviceInfo changeDeviceInfo success")
|
|
||||||
loop = false
|
|
||||||
callBack.changeSuccess()
|
|
||||||
} else if (result == -1) {
|
|
||||||
Log.d("ChangeDeviceInfoUtil", "changeDeviceInfo changeDeviceInfo fail")
|
|
||||||
loop = false
|
|
||||||
callBack.changeFailed()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e: JSONException) {
|
|
||||||
e.printStackTrace()
|
|
||||||
callBack.changeFailed()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun changeDeviceInfo() {
|
fun changeDeviceInfo() {
|
||||||
try {
|
try {
|
||||||
MockTools.exec("pm grant com.android.grape android.permission.INTERACT_ACROSS_USERS")
|
|
||||||
MockTools.exec("pm grant com.android.grape android.permission.WRITE_SECURE_SETTINGS")
|
|
||||||
MockTools.exec("pm setenforce 0")
|
|
||||||
val deviceObject = paramsJson?.getJSONObject("device")
|
val deviceObject = paramsJson?.getJSONObject("device")
|
||||||
if (deviceObject == null) {
|
if (deviceObject == null) {
|
||||||
LogUtils.d("ERROR", "ChangeDeviceInfoUtil", "device is null")
|
LogUtils.d("ERROR", "ChangeDeviceInfoUtil", "device is null")
|
||||||
|
@ -193,7 +32,7 @@ object ChangeDeviceInfoUtil {
|
||||||
vcloudsettingsPut("$currentPkgName.vendor", device.expand.glVendor, context)
|
vcloudsettingsPut("$currentPkgName.vendor", device.expand.glVendor, context)
|
||||||
// callVCloudSettings_put("$currentPkgName.battery_scale", batteryScale.toString(), context)
|
// callVCloudSettings_put("$currentPkgName.battery_scale", batteryScale.toString(), context)
|
||||||
vcloudsettingsPut("$currentPkgName.os_lang", device.lang, context)
|
vcloudsettingsPut("$currentPkgName.os_lang", device.lang, context)
|
||||||
vcloudsettingsPut("$currentPkgName.model", device.model, context)
|
vcloudsettingsPut("$currentPkgName.model", device.model, context);
|
||||||
vcloudsettingsPut("$currentPkgName.net", device.network, context)
|
vcloudsettingsPut("$currentPkgName.net", device.network, context)
|
||||||
vcloudsettingsPut("$currentPkgName.dpi", device.expand.dPI.toString(), context)
|
vcloudsettingsPut("$currentPkgName.dpi", device.expand.dPI.toString(), context)
|
||||||
// callVCloudSettings_put(
|
// callVCloudSettings_put(
|
||||||
|
@ -212,7 +51,7 @@ object ChangeDeviceInfoUtil {
|
||||||
// **os_ver**
|
// **os_ver**
|
||||||
vcloudsettingsPut(currentPkgName + "_os_ver", device.sdkVer, context)
|
vcloudsettingsPut(currentPkgName + "_os_ver", device.sdkVer, context)
|
||||||
// **tz** (时区)
|
// **tz** (时区)
|
||||||
vcloudsettingsPut(currentPkgName + "_tz", TimeZoneUtils.formatTimeZoneOffset(device.tzOffTime), context)
|
vcloudsettingsPut(currentPkgName + "_tz", device.tzOffTime.toString(), context)
|
||||||
|
|
||||||
vcloudsettingsPut("$currentPkgName.advertiserId", device.advertiserId, context)
|
vcloudsettingsPut("$currentPkgName.advertiserId", device.advertiserId, context)
|
||||||
vcloudsettingsPut("$currentPkgName.brand", device.brand, context)
|
vcloudsettingsPut("$currentPkgName.brand", device.brand, context)
|
||||||
|
@ -282,9 +121,6 @@ object ChangeDeviceInfoUtil {
|
||||||
ShellUtils.execRootCmd("setprop persist.sys.cloud.battery.capacity ${device.battery}")
|
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_vendor ${device.expand.glVendor}")
|
||||||
ShellUtils.execRootCmd("setprop persist.sys.cloud.gpu.gl_renderer ${device.expand.glRenderer}")
|
ShellUtils.execRootCmd("setprop persist.sys.cloud.gpu.gl_renderer ${device.expand.glRenderer}")
|
||||||
ShellUtils.execRootCmd("setprop persist.sys.timezone ${TimeZoneUtils.formatTimeZoneOffset(device.tzOffTime)}")
|
|
||||||
ShellUtils.execRootCmd("setprop persist.sys.country ${device.country}")
|
|
||||||
ShellUtils.execRootCmd("setprop persist.sys.language ${device.expand.displayLang}")
|
|
||||||
// 这个值不能随便改 必须是 OpenGL ES %d.%d 这个格式
|
// 这个值不能随便改 必须是 OpenGL ES %d.%d 这个格式
|
||||||
ShellUtils.execRootCmd("setprop persist.sys.cloud.gpu.gl_version ${device.expand.glVersion}")
|
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_vendor $persist_sys_cloud_gpu_egl_vendor")
|
||||||
|
@ -301,7 +137,7 @@ object ChangeDeviceInfoUtil {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun vcloudsettingsPut(key: String, value: String, context: Context) {
|
private fun vcloudsettingsPut(key: String, value: String, context: Context) {
|
||||||
if (key.isEmpty()) {
|
if (key.isEmpty()) {
|
||||||
LogUtils.e(Log.ERROR, "ChangeDeviceInfoUtil", "Key cannot be null or empty", null)
|
LogUtils.e(Log.ERROR, "ChangeDeviceInfoUtil", "Key cannot be null or empty", null)
|
||||||
throw IllegalArgumentException("Key cannot be null or empty")
|
throw IllegalArgumentException("Key cannot be null or empty")
|
||||||
|
|
|
@ -4,35 +4,24 @@ import android.content.BroadcastReceiver
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.IntentFilter
|
import android.content.IntentFilter
|
||||||
import android.os.Environment
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import com.blankj.utilcode.util.LogUtils
|
import com.blankj.utilcode.util.LogUtils
|
||||||
|
import okhttp3.Call
|
||||||
|
import okhttp3.Callback
|
||||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||||
import okhttp3.MediaType
|
import okhttp3.MediaType
|
||||||
import okhttp3.MediaType.Companion.toMediaType
|
import okhttp3.MediaType.Companion.toMediaType
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import okhttp3.RequestBody.Companion.toRequestBody
|
import okhttp3.RequestBody.Companion.toRequestBody
|
||||||
import okhttp3.logging.HttpLoggingInterceptor
|
import okhttp3.Response
|
||||||
import org.json.JSONException
|
import org.json.JSONException
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
import java.io.BufferedReader
|
|
||||||
import java.io.File
|
|
||||||
import java.io.FileReader
|
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.util.concurrent.CountDownLatch
|
import java.util.concurrent.CountDownLatch
|
||||||
import java.util.concurrent.TimeUnit
|
|
||||||
|
|
||||||
object ClashUtil {
|
object ClashUtil {
|
||||||
private val sharedClient: OkHttpClient = OkHttpClient.Builder()
|
|
||||||
.connectTimeout(10, TimeUnit.SECONDS) // 连接超时
|
|
||||||
.readTimeout(30, TimeUnit.SECONDS) // 读取超时
|
|
||||||
.addInterceptor(HttpLoggingInterceptor().apply {
|
|
||||||
level = HttpLoggingInterceptor.Level.BODY
|
|
||||||
})
|
|
||||||
.build()
|
|
||||||
|
|
||||||
fun startProxy(context: Context) {
|
fun startProxy(context: Context) {
|
||||||
val intent = Intent("com.github.kr328.clash.intent.action.SESSION_CREATE")
|
val intent = Intent("com.github.kr328.clash.intent.action.SESSION_CREATE")
|
||||||
intent.putExtra("profile", "default") // 可选择您在 Clash 中配置的 Profile
|
intent.putExtra("profile", "default") // 可选择您在 Clash 中配置的 Profile
|
||||||
|
@ -46,66 +35,6 @@ object ClashUtil {
|
||||||
}.start()
|
}.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getProxyPort(): Int {
|
|
||||||
val scriptDir = File(Environment.getExternalStorageDirectory(), "script")
|
|
||||||
val portFile = File(scriptDir, "ip.port.json")
|
|
||||||
val text = StringBuilder()
|
|
||||||
try {
|
|
||||||
BufferedReader(FileReader(portFile)).use { br ->
|
|
||||||
var line: String?
|
|
||||||
while ((br.readLine().also { line = it }) != null) {
|
|
||||||
text.append(line).append('\n')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e: IOException) {
|
|
||||||
Log.e("TAG", "getProxyPort: ", e)
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
var port = -1
|
|
||||||
try {
|
|
||||||
Log.d("TAG", "getProxyPort: $text")
|
|
||||||
val config = JSONObject(text.toString())
|
|
||||||
port = config.optInt("port", -1)
|
|
||||||
} catch (e: java.lang.Exception) {
|
|
||||||
Log.e("TAG", "getProxyPort: ", e)
|
|
||||||
}
|
|
||||||
return port
|
|
||||||
}
|
|
||||||
|
|
||||||
fun switchProxyWithPort(country: String?) {
|
|
||||||
val port = getProxyPort()
|
|
||||||
// 安全构建 URL
|
|
||||||
val url = "http://39.103.73.250/tt/test/testProxy.jsp".toHttpUrl()
|
|
||||||
.newBuilder()
|
|
||||||
.addQueryParameter("port", port.toString() + "")
|
|
||||||
.addQueryParameter("country", country)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
val request = Request.Builder()
|
|
||||||
.url(url)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
try {
|
|
||||||
sharedClient.newCall(request).execute().use { response ->
|
|
||||||
// 读取并记录响应内容
|
|
||||||
val responseBody = response.body?.string() ?: "Empty response body"
|
|
||||||
if (response.isSuccessful) {
|
|
||||||
Log.d(
|
|
||||||
"ClashUtil",
|
|
||||||
"switchProxyGroup: Success | Status: " + response.code + " | Response: " + responseBody
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
Log.d(
|
|
||||||
"ClashUtil",
|
|
||||||
"switchProxyGroup: Failed | Status: " + response.code + " | Response: " + responseBody
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e: java.lang.Exception) {
|
|
||||||
Log.d("ClashUtil", "switchProxyGroup: Unexpected error", e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var isRunning: Boolean = false
|
var isRunning: Boolean = false
|
||||||
|
|
||||||
val clashStatusReceiver: BroadcastReceiver = object : BroadcastReceiver() {
|
val clashStatusReceiver: BroadcastReceiver = object : BroadcastReceiver() {
|
||||||
|
@ -161,7 +90,26 @@ object ClashUtil {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun switchProxyGroup(groupName: String?, proxyName: String?, controllerUrl: String): Boolean {
|
fun switchProxyGroup(groupName: String?, proxyName: String?, controllerUrl: String) {
|
||||||
|
if (groupName == null || groupName.trim { it <= ' ' }
|
||||||
|
.isEmpty() || proxyName == null || proxyName.trim { it <= ' ' }.isEmpty()) {
|
||||||
|
LogUtils.log(
|
||||||
|
Log.ERROR,
|
||||||
|
"ClashUtil",
|
||||||
|
"switchProxyGroup: Invalid arguments",
|
||||||
|
null
|
||||||
|
)
|
||||||
|
throw IllegalArgumentException("Group name and proxy name must not be empty")
|
||||||
|
}
|
||||||
|
if (!controllerUrl.matches("^https?://.*".toRegex())) {
|
||||||
|
LogUtils.log(
|
||||||
|
Log.ERROR,
|
||||||
|
"ClashUtil",
|
||||||
|
"switchProxyGroup: Invalid controller URL",
|
||||||
|
null
|
||||||
|
)
|
||||||
|
throw IllegalArgumentException("Invalid controller URL")
|
||||||
|
}
|
||||||
|
|
||||||
val client = OkHttpClient()
|
val client = OkHttpClient()
|
||||||
val json = JSONObject()
|
val json = JSONObject()
|
||||||
|
@ -185,27 +133,39 @@ object ClashUtil {
|
||||||
.put(requestBody)
|
.put(requestBody)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
|
client.newCall(request).enqueue(object : Callback {
|
||||||
|
override fun onFailure(call: Call, e: IOException) {
|
||||||
|
LogUtils.log(
|
||||||
|
Log.ERROR,
|
||||||
|
"ClashUtil",
|
||||||
|
"switchProxyGroup: Failed to switch proxy",
|
||||||
|
e
|
||||||
|
)
|
||||||
|
println("Failed to switch proxy: " + e.message)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(IOException::class)
|
||||||
|
override fun onResponse(call: Call, response: Response) {
|
||||||
try {
|
try {
|
||||||
val result = sharedClient.newCall(request).execute().use { response ->
|
if (response.body != null) {
|
||||||
// 读取并记录响应内容
|
LogUtils.log(
|
||||||
val responseBody = if (response.body != null) response.body?.string() else "Empty response body"
|
Log.INFO,
|
||||||
val isSuccess = if (response.isSuccessful) {
|
|
||||||
LogUtils.d(
|
|
||||||
"ClashUtil",
|
"ClashUtil",
|
||||||
"switchProxyGroup: Success | Status: " + response.code + " | Response: " + responseBody)
|
"switchProxyGroup: Switch proxy response",
|
||||||
true
|
null
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
LogUtils.d(
|
LogUtils.log(
|
||||||
|
Log.ERROR,
|
||||||
"ClashUtil",
|
"ClashUtil",
|
||||||
"switchProxyGroup: Failed | Status: " + response.code + " | Response: " + responseBody)
|
"switchProxyGroup: Response body is null",
|
||||||
false
|
null
|
||||||
|
)
|
||||||
}
|
}
|
||||||
return isSuccess
|
} finally {
|
||||||
}
|
response.close()
|
||||||
return result
|
}
|
||||||
} catch (e: Exception) {
|
}
|
||||||
LogUtils.d("ClashUtil", "switchProxyGroup: Unexpected error", e)
|
})
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,18 +0,0 @@
|
||||||
package com.android.grape.util
|
|
||||||
|
|
||||||
import android.util.Log
|
|
||||||
import com.blankj.utilcode.util.LogUtils
|
|
||||||
|
|
||||||
object CountryCode {
|
|
||||||
const val DEVICE_TYPE: Int = 2
|
|
||||||
const val US: String = "US"
|
|
||||||
const val RU: String = "RU"
|
|
||||||
val DEFAULT: String = US
|
|
||||||
var currentCountry: String = DEFAULT
|
|
||||||
|
|
||||||
fun switchCountry(): String {
|
|
||||||
currentCountry = if (currentCountry == US) RU else US
|
|
||||||
LogUtils.d("TAG", "Switched country to: $currentCountry")
|
|
||||||
return currentCountry
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,8 +1,6 @@
|
||||||
package com.android.grape.util
|
package com.android.grape.util
|
||||||
|
|
||||||
import android.os.Environment
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import com.blankj.utilcode.util.LogUtils
|
|
||||||
import java.io.BufferedInputStream
|
import java.io.BufferedInputStream
|
||||||
import java.io.BufferedOutputStream
|
import java.io.BufferedOutputStream
|
||||||
import java.io.BufferedReader
|
import java.io.BufferedReader
|
||||||
|
@ -18,7 +16,6 @@ import java.io.InputStreamReader
|
||||||
import java.io.OutputStream
|
import java.io.OutputStream
|
||||||
import java.io.RandomAccessFile
|
import java.io.RandomAccessFile
|
||||||
import java.io.UnsupportedEncodingException
|
import java.io.UnsupportedEncodingException
|
||||||
import java.nio.charset.StandardCharsets
|
|
||||||
import java.util.Enumeration
|
import java.util.Enumeration
|
||||||
import java.util.zip.ZipEntry
|
import java.util.zip.ZipEntry
|
||||||
import java.util.zip.ZipFile
|
import java.util.zip.ZipFile
|
||||||
|
@ -390,31 +387,4 @@ object FileUtils {
|
||||||
output.close()
|
output.close()
|
||||||
input.close()
|
input.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun writePackageName(packageName: String) {
|
|
||||||
val file = File(
|
|
||||||
Environment.getExternalStorageDirectory(),
|
|
||||||
"script/packagesname.txt"
|
|
||||||
)
|
|
||||||
val parentDir = file.getParentFile()
|
|
||||||
if (parentDir != null && !parentDir.exists()) {
|
|
||||||
val dirsCreated = parentDir.mkdirs()
|
|
||||||
if (!dirsCreated) {
|
|
||||||
Log.e("FileWrite", "Failed to create directories: $parentDir")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
LogUtils.d("TAG", "writePackageName: $packageName", null)
|
|
||||||
try {
|
|
||||||
BufferedOutputStream(
|
|
||||||
FileOutputStream(file)
|
|
||||||
).use { bos ->
|
|
||||||
bos.write(packageName.toByteArray(StandardCharsets.UTF_8))
|
|
||||||
bos.flush() // 确保数据写入磁盘
|
|
||||||
}
|
|
||||||
} catch (e: IOException) {
|
|
||||||
Log.e("FileWrite", "Failed to write package name: $packageName", e)
|
|
||||||
// 6. 可以考虑添加重试机制或通知用户
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -12,26 +12,25 @@ object MockTools {
|
||||||
fun exec(cmd: String): String {
|
fun exec(cmd: String): String {
|
||||||
var retString = ""
|
var retString = ""
|
||||||
try {
|
try {
|
||||||
retString = ShellUtils.execRootCmdAndGetResult(cmd)
|
//创建socket
|
||||||
// //创建socket
|
val myCmd = "SU|$cmd"
|
||||||
// val myCmd = "SU|$cmd"
|
|
||||||
//
|
val mSocket = Socket()
|
||||||
// val mSocket = Socket()
|
val inetSocketAddress = InetSocketAddress("127.0.0.1", 12345)
|
||||||
// val inetSocketAddress = InetSocketAddress("127.0.0.1", 12345)
|
mSocket.connect(inetSocketAddress)
|
||||||
// mSocket.connect(inetSocketAddress)
|
val bufferedWriter = BufferedWriter(OutputStreamWriter(mSocket.getOutputStream()))
|
||||||
// val bufferedWriter = BufferedWriter(OutputStreamWriter(mSocket.getOutputStream()))
|
val bufferedReader = BufferedReader(InputStreamReader(mSocket.getInputStream()))
|
||||||
// val bufferedReader = BufferedReader(InputStreamReader(mSocket.getInputStream()))
|
bufferedWriter.write(myCmd + "\r\n")
|
||||||
// bufferedWriter.write(myCmd + "\r\n")
|
bufferedWriter.flush()
|
||||||
// bufferedWriter.flush()
|
val stringBuilder = StringBuilder()
|
||||||
// val stringBuilder = StringBuilder()
|
var line: String? = null
|
||||||
// var line: String? = null
|
while ((bufferedReader.readLine().also { line = it }) != null) {
|
||||||
// while ((bufferedReader.readLine().also { line = it }) != null) {
|
stringBuilder.append(line + "\n")
|
||||||
// stringBuilder.append(line + "\n")
|
}
|
||||||
// }
|
//retString = bufferedReader.readLine();
|
||||||
// //retString = bufferedReader.readLine();
|
retString = stringBuilder.toString()
|
||||||
// retString = stringBuilder.toString()
|
bufferedReader.close()
|
||||||
// bufferedReader.close()
|
bufferedWriter.close()
|
||||||
// bufferedWriter.close()
|
|
||||||
} catch (eeeee: Exception) {
|
} catch (eeeee: Exception) {
|
||||||
eeeee.printStackTrace()
|
eeeee.printStackTrace()
|
||||||
}
|
}
|
||||||
|
@ -41,26 +40,25 @@ object MockTools {
|
||||||
fun execRead(cmd: String): String {
|
fun execRead(cmd: String): String {
|
||||||
var retString = ""
|
var retString = ""
|
||||||
try {
|
try {
|
||||||
retString = ShellUtils.execRootCmdAndGetResult(cmd)
|
//创建socket
|
||||||
// //创建socket
|
val myCmd = "SU_1|$cmd"
|
||||||
// val myCmd = "SU_1|$cmd"
|
|
||||||
//
|
val mSocket = Socket()
|
||||||
// val mSocket = Socket()
|
val inetSocketAddress = InetSocketAddress("127.0.0.1", 12345)
|
||||||
// val inetSocketAddress = InetSocketAddress("127.0.0.1", 12345)
|
mSocket.connect(inetSocketAddress)
|
||||||
// mSocket.connect(inetSocketAddress)
|
val bufferedWriter = BufferedWriter(OutputStreamWriter(mSocket.getOutputStream()))
|
||||||
// val bufferedWriter = BufferedWriter(OutputStreamWriter(mSocket.getOutputStream()))
|
val bufferedReader = BufferedReader(InputStreamReader(mSocket.getInputStream()))
|
||||||
// val bufferedReader = BufferedReader(InputStreamReader(mSocket.getInputStream()))
|
bufferedWriter.write(myCmd + "\r\n")
|
||||||
// bufferedWriter.write(myCmd + "\r\n")
|
bufferedWriter.flush()
|
||||||
// bufferedWriter.flush()
|
val stringBuilder = StringBuilder()
|
||||||
// val stringBuilder = StringBuilder()
|
var line: String? = null
|
||||||
// var line: String? = null
|
while ((bufferedReader.readLine().also { line = it }) != null) {
|
||||||
// while ((bufferedReader.readLine().also { line = it }) != null) {
|
stringBuilder.append(line + "\n")
|
||||||
// stringBuilder.append(line + "\n")
|
}
|
||||||
// }
|
retString = stringBuilder.toString()
|
||||||
// retString = stringBuilder.toString()
|
//retString = bufferedReader.readLine();
|
||||||
// //retString = bufferedReader.readLine();
|
bufferedReader.close()
|
||||||
// bufferedReader.close()
|
bufferedWriter.close()
|
||||||
// bufferedWriter.close()
|
|
||||||
} catch (eeeee: Exception) {
|
} catch (eeeee: Exception) {
|
||||||
eeeee.printStackTrace()
|
eeeee.printStackTrace()
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,6 @@ import android.content.pm.PackageManager
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.provider.Settings
|
import android.provider.Settings
|
||||||
import android.util.Log
|
|
||||||
import androidx.activity.result.ActivityResultLauncher
|
import androidx.activity.result.ActivityResultLauncher
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
|
@ -107,6 +106,7 @@ class NotificationPermissionHandler(
|
||||||
fun requestNotificationPermission() {
|
fun requestNotificationPermission() {
|
||||||
// 保存当前权限状态,用于比较设置后是否发生变化
|
// 保存当前权限状态,用于比较设置后是否发生变化
|
||||||
lastPermissionState = areNotificationsEnabled()
|
lastPermissionState = areNotificationsEnabled()
|
||||||
|
|
||||||
when {
|
when {
|
||||||
// Android 13+ - 使用运行时权限请求
|
// Android 13+ - 使用运行时权限请求
|
||||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU -> {
|
Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU -> {
|
||||||
|
@ -198,18 +198,19 @@ class NotificationPermissionHandler(
|
||||||
}
|
}
|
||||||
|
|
||||||
val activity = context as? AppCompatActivity ?: return
|
val activity = context as? AppCompatActivity ?: return
|
||||||
|
|
||||||
when {
|
when {
|
||||||
// 已经拥有权限
|
// 已经拥有权限
|
||||||
areNotificationsEnabled() -> {
|
areNotificationsEnabled() -> {
|
||||||
callback(PermissionResult.GRANTED)
|
callback(PermissionResult.GRANTED)
|
||||||
}
|
}
|
||||||
// 需要显示权限解释
|
// 需要显示权限解释
|
||||||
// ActivityCompat.shouldShowRequestPermissionRationale(
|
ActivityCompat.shouldShowRequestPermissionRationale(
|
||||||
// activity,
|
activity,
|
||||||
// Manifest.permission.POST_NOTIFICATIONS
|
Manifest.permission.POST_NOTIFICATIONS
|
||||||
// ) -> {
|
) -> {
|
||||||
// showPermissionRationaleDialog()
|
showPermissionRationaleDialog()
|
||||||
// }
|
}
|
||||||
// 请求权限
|
// 请求权限
|
||||||
else -> {
|
else -> {
|
||||||
permissionRequestPending = true
|
permissionRequestPending = true
|
||||||
|
@ -275,7 +276,6 @@ class NotificationPermissionHandler(
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val TAG = "NotificationPermissionH"
|
|
||||||
/**
|
/**
|
||||||
* 快速检查当前设备的通知权限状态
|
* 快速检查当前设备的通知权限状态
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -103,6 +103,10 @@ public class ShellUtils {
|
||||||
|
|
||||||
public static String execRootCmdAndGetResult(String cmd) {
|
public static String execRootCmdAndGetResult(String cmd) {
|
||||||
Log.d("ShellUtils", "execRootCmdAndGetResult - Started execution for command: " + cmd);
|
Log.d("ShellUtils", "execRootCmdAndGetResult - Started execution for command: " + cmd);
|
||||||
|
if (cmd == null || cmd.trim().isEmpty()) {
|
||||||
|
LogUtils.e(Log.ERROR, "ShellUtils", "Unsafe or empty command. Aborting execution.", null);
|
||||||
|
throw new IllegalArgumentException("Unsafe or empty command.");
|
||||||
|
}
|
||||||
// if (!isCommandSafe(cmd)) { // 检查命令的合法性
|
// if (!isCommandSafe(cmd)) { // 检查命令的合法性
|
||||||
// Log.e("ShellUtils", "Detected unsafe command. Aborting execution.");
|
// Log.e("ShellUtils", "Detected unsafe command. Aborting execution.");
|
||||||
// throw new IllegalArgumentException("Detected unsafe command.");
|
// throw new IllegalArgumentException("Detected unsafe command.");
|
||||||
|
@ -112,13 +116,18 @@ public class ShellUtils {
|
||||||
ExecutorService executor = Executors.newFixedThreadPool(2);
|
ExecutorService executor = Executors.newFixedThreadPool(2);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
Log.d("ShellUtils", "Determining appropriate shell for execution...");
|
||||||
if (hasBin("su")) {
|
if (hasBin("su")) {
|
||||||
|
Log.d("ShellUtils", "'su' binary found, using 'su' shell.");
|
||||||
process = Runtime.getRuntime().exec("su");
|
process = Runtime.getRuntime().exec("su");
|
||||||
} else if (hasBin("xu")) {
|
} else if (hasBin("xu")) {
|
||||||
|
Log.d("ShellUtils", "'xu' binary found, using 'xu' shell.");
|
||||||
process = Runtime.getRuntime().exec("xu");
|
process = Runtime.getRuntime().exec("xu");
|
||||||
} else if (hasBin("vu")) {
|
} else if (hasBin("vu")) {
|
||||||
|
Log.d("ShellUtils", "'vu' binary found, using 'vu' shell.");
|
||||||
process = Runtime.getRuntime().exec("vu");
|
process = Runtime.getRuntime().exec("vu");
|
||||||
} else {
|
} else {
|
||||||
|
Log.d("ShellUtils", "No specific binary found, using 'sh' shell.");
|
||||||
process = Runtime.getRuntime().exec("sh");
|
process = Runtime.getRuntime().exec("sh");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -128,6 +137,7 @@ public class ShellUtils {
|
||||||
BufferedReader br = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8));
|
BufferedReader br = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8));
|
||||||
BufferedReader errorReader = new BufferedReader(new InputStreamReader(errorStream, StandardCharsets.UTF_8))) {
|
BufferedReader errorReader = new BufferedReader(new InputStreamReader(errorStream, StandardCharsets.UTF_8))) {
|
||||||
|
|
||||||
|
Log.d("ShellUtils", "Starting separate thread to process error stream...");
|
||||||
executor.submit(() -> {
|
executor.submit(() -> {
|
||||||
String line;
|
String line;
|
||||||
try {
|
try {
|
||||||
|
@ -139,21 +149,28 @@ public class ShellUtils {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Log.d("ShellUtils", "Writing the command to the shell...");
|
||||||
os.write((cmd + "\n").getBytes());
|
os.write((cmd + "\n").getBytes());
|
||||||
os.write("exit\n".getBytes());
|
os.write("exit\n".getBytes());
|
||||||
os.flush();
|
os.flush();
|
||||||
|
Log.d("ShellUtils", "Command written to shell. Waiting for process to complete.");
|
||||||
|
|
||||||
StringBuilder output = new StringBuilder();
|
StringBuilder output = new StringBuilder();
|
||||||
String line;
|
String line;
|
||||||
while ((line = br.readLine()) != null) {
|
while ((line = br.readLine()) != null) {
|
||||||
|
Log.d("ShellUtils", "Shell Output: " + line);
|
||||||
output.append(line).append("\n");
|
output.append(line).append("\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Log.d("ShellUtils", "Awaiting process termination...");
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
if (!process.waitFor(10, TimeUnit.SECONDS)) {
|
if (!process.waitFor(10, TimeUnit.SECONDS)) {
|
||||||
|
LogUtils.e(Log.ERROR, "ShellUtils", "Process execution timed out. Destroying process.", null);
|
||||||
process.destroyForcibly();
|
process.destroyForcibly();
|
||||||
throw new RuntimeException("Shell command execution timeout.");
|
throw new RuntimeException("Shell command execution timeout.");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
Log.d("ShellUtils", "Using manual time tracking method for process termination (API < 26).");
|
||||||
long startTime = System.currentTimeMillis();
|
long startTime = System.currentTimeMillis();
|
||||||
while (true) {
|
while (true) {
|
||||||
try {
|
try {
|
||||||
|
@ -161,6 +178,7 @@ public class ShellUtils {
|
||||||
break;
|
break;
|
||||||
} catch (IllegalThreadStateException e) {
|
} catch (IllegalThreadStateException e) {
|
||||||
if (System.currentTimeMillis() - startTime > 10000) { // 10 seconds
|
if (System.currentTimeMillis() - startTime > 10000) { // 10 seconds
|
||||||
|
LogUtils.e(Log.ERROR, "ShellUtils", "Process execution timed out (manual tracking). Destroying process.", null);
|
||||||
process.destroy();
|
process.destroy();
|
||||||
throw new RuntimeException("Shell command execution timeout.");
|
throw new RuntimeException("Shell command execution timeout.");
|
||||||
}
|
}
|
||||||
|
@ -168,6 +186,8 @@ public class ShellUtils {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Log.d("ShellUtils", "Process terminated successfully. Returning result.");
|
||||||
return output.toString().trim();
|
return output.toString().trim();
|
||||||
}
|
}
|
||||||
} catch (IOException | InterruptedException e) {
|
} catch (IOException | InterruptedException e) {
|
||||||
|
@ -176,6 +196,7 @@ public class ShellUtils {
|
||||||
return "Error: " + e.getMessage();
|
return "Error: " + e.getMessage();
|
||||||
} finally {
|
} finally {
|
||||||
if (process != null) {
|
if (process != null) {
|
||||||
|
Log.d("ShellUtils", "Finalizing process. Attempting to destroy it.");
|
||||||
process.destroy();
|
process.destroy();
|
||||||
}
|
}
|
||||||
executor.shutdown();
|
executor.shutdown();
|
||||||
|
@ -185,9 +206,10 @@ public class ShellUtils {
|
||||||
|
|
||||||
public static void execRootCmd(String cmd) {
|
public static void execRootCmd(String cmd) {
|
||||||
// 校验命令是否安全
|
// 校验命令是否安全
|
||||||
// if (!isCommandSafe(cmd)) {
|
if (!isCommandSafe(cmd)) {
|
||||||
// return;
|
LogUtils.e(Log.ERROR, "ShellUtils", "Unsafe command, aborting.", null);
|
||||||
// }
|
return;
|
||||||
|
}
|
||||||
List<String> cmds = new ArrayList<>();
|
List<String> cmds = new ArrayList<>();
|
||||||
cmds.add(cmd);
|
cmds.add(cmd);
|
||||||
|
|
||||||
|
|
|
@ -1,75 +0,0 @@
|
||||||
package com.android.grape.util
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import com.android.grape.util.TimeZoneUtils.formatTimeZoneOffset
|
|
||||||
import java.util.*
|
|
||||||
import kotlin.math.abs
|
|
||||||
|
|
||||||
object TimeZoneUtils {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 从偏移时间获取最匹配的时区ID
|
|
||||||
*
|
|
||||||
* @param offsetMillis 时区偏移(毫秒)
|
|
||||||
* @param useDaylight 是否考虑夏令时
|
|
||||||
*/
|
|
||||||
fun getBestMatchTimeZoneId(offsetMillis: Int, useDaylight: Boolean = false): String {
|
|
||||||
val availableIds = TimeZone.getAvailableIDs()
|
|
||||||
|
|
||||||
// 首选:完全匹配的时区
|
|
||||||
availableIds.firstOrNull { id ->
|
|
||||||
val tz = TimeZone.getTimeZone(id)
|
|
||||||
if (useDaylight) {
|
|
||||||
tz.getOffset(System.currentTimeMillis()) == offsetMillis
|
|
||||||
} else {
|
|
||||||
tz.rawOffset == offsetMillis
|
|
||||||
}
|
|
||||||
}?.let { return it }
|
|
||||||
|
|
||||||
// 次选:最接近的时区
|
|
||||||
return availableIds.minByOrNull { id ->
|
|
||||||
val tz = TimeZone.getTimeZone(id)
|
|
||||||
val diff = if (useDaylight) {
|
|
||||||
abs(tz.getOffset(System.currentTimeMillis()) - offsetMillis)
|
|
||||||
} else {
|
|
||||||
abs(tz.rawOffset - offsetMillis)
|
|
||||||
}
|
|
||||||
diff
|
|
||||||
} ?: TimeZone.getDefault().id
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取时区偏移的格式化字符串
|
|
||||||
*/
|
|
||||||
fun formatTimeZoneOffset(offsetMillis: Int): String {
|
|
||||||
val hours = offsetMillis / (1000 * 60 * 60)
|
|
||||||
val minutes = abs(offsetMillis) / (1000 * 60) % 60
|
|
||||||
|
|
||||||
return when {
|
|
||||||
hours > 0 -> String.format("UTC+%d:%02d", hours, minutes)
|
|
||||||
hours < 0 -> String.format("UTC%d:%02d", hours, minutes) // 自动显示负号
|
|
||||||
else -> "UTC"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 从设备获取当前时区偏移
|
|
||||||
*/
|
|
||||||
fun getCurrentTimeZoneOffset(): Int {
|
|
||||||
return TimeZone.getDefault().getOffset(System.currentTimeMillis())
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 将时间转换为指定时区偏移的时间
|
|
||||||
*/
|
|
||||||
fun convertTimeToOffset(time: Date, targetOffsetMillis: Int): Date {
|
|
||||||
val currentOffset = TimeZone.getDefault().getOffset(time.time)
|
|
||||||
val diff = targetOffsetMillis - currentOffset
|
|
||||||
return Date(time.time + diff)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun main() {
|
|
||||||
val offset = formatTimeZoneOffset(19800000)
|
|
||||||
println("当前时区偏移:$offset")
|
|
||||||
}
|
|
|
@ -73,7 +73,7 @@ object Util {
|
||||||
private const val sendRefer = true
|
private const val sendRefer = true
|
||||||
var isNeedRestored: Boolean = false
|
var isNeedRestored: Boolean = false
|
||||||
const val AUTO_PACKAGENAME: String = "com.play4u.luabox"
|
const val AUTO_PACKAGENAME: String = "com.play4u.luabox"
|
||||||
const val AUTO_JSPACKAGENAME: String = "org.autojs.autojs6"
|
const val AUTO_JSPACKAGENAME: String = "org.autojs.autojs"
|
||||||
const val proxy_packagename: String = "com.tunnelworkshop.postern"
|
const val proxy_packagename: String = "com.tunnelworkshop.postern"
|
||||||
const val AUTO_CLASSNAME: String = "com.cyjh.elfin.activity.SplashActivity"
|
const val AUTO_CLASSNAME: String = "com.cyjh.elfin.activity.SplashActivity"
|
||||||
private const val hookPackageName = "com.affsystem.androidhooker"
|
private const val hookPackageName = "com.affsystem.androidhooker"
|
||||||
|
@ -275,7 +275,10 @@ object Util {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isInstallRet(): Boolean {
|
fun isInstallRet(): Boolean {
|
||||||
return installRet?:false
|
if (installRet == null) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return installRet!!
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setAfLog(afLogV: String) {
|
fun setAfLog(afLogV: String) {
|
||||||
|
@ -696,8 +699,8 @@ object Util {
|
||||||
val result: String? = MyPost.postData(" ".toByteArray(charset = Charsets.UTF_8),
|
val result: String? = MyPost.postData(" ".toByteArray(charset = Charsets.UTF_8),
|
||||||
"$url?$params"
|
"$url?$params"
|
||||||
)
|
)
|
||||||
LogUtils.e("IOSTQ:execReloginTask->result:$result")
|
|
||||||
if (result != null && result.isNotEmpty()) {
|
if (result != null && result.length > 0) {
|
||||||
taskJson = JSONObject(result).apply {
|
taskJson = JSONObject(result).apply {
|
||||||
val code = getInt("code")
|
val code = getInt("code")
|
||||||
if (code == 1) {
|
if (code == 1) {
|
||||||
|
@ -749,9 +752,9 @@ object Util {
|
||||||
"$url?$params"
|
"$url?$params"
|
||||||
)
|
)
|
||||||
|
|
||||||
LogUtils.e("request result : $result")
|
printStr("request result : $result")
|
||||||
|
|
||||||
if (result != null && result.isNotEmpty()) {
|
if (result != null && result.length > 0) {
|
||||||
taskJson = JSONObject(result)
|
taskJson = JSONObject(result)
|
||||||
|
|
||||||
val code = taskJson!!.getInt("code")
|
val code = taskJson!!.getInt("code")
|
||||||
|
@ -762,7 +765,7 @@ object Util {
|
||||||
execSetJson(context)
|
execSetJson(context)
|
||||||
clickTime = 1
|
clickTime = 1
|
||||||
} else {
|
} else {
|
||||||
LogUtils.i(TAG, "request result code invalid : $code");
|
LogUtils.i(TAG, "request result code invalid : "+code);
|
||||||
setFinish(context)
|
setFinish(context)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -785,6 +788,7 @@ object Util {
|
||||||
nRandom++
|
nRandom++
|
||||||
|
|
||||||
if (nRandom % 3 == 0) {
|
if (nRandom % 3 == 0) {
|
||||||
|
// execInstallTask(context);
|
||||||
execReloginTask(context)
|
execReloginTask(context)
|
||||||
} else {
|
} else {
|
||||||
execInstallTask(context)
|
execInstallTask(context)
|
||||||
|
@ -945,6 +949,8 @@ object Util {
|
||||||
videoProxy = extJo.getString("videoProxy")
|
videoProxy = extJo.getString("videoProxy")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if (extJo.has("proxy") && !extJo.isNull("proxy")) {
|
if (extJo.has("proxy") && !extJo.isNull("proxy")) {
|
||||||
val proxyJo = extJo.getJSONObject("proxy")
|
val proxyJo = extJo.getJSONObject("proxy")
|
||||||
proxyIp = proxyJo.getString("proxyIp")
|
proxyIp = proxyJo.getString("proxyIp")
|
||||||
|
@ -1042,7 +1048,7 @@ object Util {
|
||||||
val ret: String? = MyPost.postData("".toByteArray(), url)
|
val ret: String? = MyPost.postData("".toByteArray(), url)
|
||||||
Log.i(TAG, "ret:$ret")
|
Log.i(TAG, "ret:$ret")
|
||||||
|
|
||||||
val jo = JSONObject(ret?:"")
|
val jo = JSONObject(ret)
|
||||||
|
|
||||||
if (jo.getInt("code") == 1) {
|
if (jo.getInt("code") == 1) {
|
||||||
regEmailJson = jo.getJSONObject("emailInfo")
|
regEmailJson = jo.getJSONObject("emailInfo")
|
||||||
|
@ -1140,7 +1146,7 @@ object Util {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
unzipAPkSh(script_path, "/sdcard/script/")
|
unzipAPkSh(script_path, "/sdcard/autojs/")
|
||||||
delFileSh(script_path)
|
delFileSh(script_path)
|
||||||
}
|
}
|
||||||
return isDownload
|
return isDownload
|
||||||
|
@ -1551,13 +1557,11 @@ object Util {
|
||||||
|
|
||||||
fun openRecordApp(context: Context) {
|
fun openRecordApp(context: Context) {
|
||||||
if (scriptOpenApp == 0) {
|
if (scriptOpenApp == 0) {
|
||||||
recordPackageName?.let {
|
|
||||||
execTargetApp(
|
execTargetApp(
|
||||||
context,
|
context,
|
||||||
it
|
recordPackageName!!
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (!isCanAuto || canAutoLc.length <= 10) {
|
if (!isCanAuto || canAutoLc.length <= 10) {
|
||||||
Handler(Looper.getMainLooper()).postDelayed(Runnable {
|
Handler(Looper.getMainLooper()).postDelayed(Runnable {
|
||||||
|
@ -1617,12 +1621,10 @@ object Util {
|
||||||
if (!file.exists()) {
|
if (!file.exists()) {
|
||||||
val parent = file.parentFile
|
val parent = file.parentFile
|
||||||
|
|
||||||
if (parent?.exists() == true) {
|
if (parent!!.exists()) {
|
||||||
file.mkdir()
|
file.mkdir()
|
||||||
} else {
|
} else {
|
||||||
if (parent != null) {
|
|
||||||
forceCreteDir(parent)
|
forceCreteDir(parent)
|
||||||
}
|
|
||||||
file.mkdir()
|
file.mkdir()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1632,8 +1634,7 @@ object Util {
|
||||||
if (!file.exists()) {
|
if (!file.exists()) {
|
||||||
val parent = file.parentFile
|
val parent = file.parentFile
|
||||||
|
|
||||||
parent?.exists()?.let {
|
if (!parent!!.exists()) {
|
||||||
if (!it) {
|
|
||||||
MockTools.exec("mkdir $parent")
|
MockTools.exec("mkdir $parent")
|
||||||
forceMakeDir(parent)
|
forceMakeDir(parent)
|
||||||
// file.mkdirs();
|
// file.mkdirs();
|
||||||
|
@ -1642,7 +1643,6 @@ object Util {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
fun writeFile1(context: Context?, dirName: String, fileName: String, contents: String) {
|
fun writeFile1(context: Context?, dirName: String, fileName: String, contents: String) {
|
||||||
val fs = File("$dirName/$fileName")
|
val fs = File("$dirName/$fileName")
|
||||||
|
@ -1767,11 +1767,7 @@ object Util {
|
||||||
}
|
}
|
||||||
|
|
||||||
val create_dir = file.parentFile
|
val create_dir = file.parentFile
|
||||||
if (create_dir != null && !create_dir.exists()){
|
forceMakeDir(create_dir!!)
|
||||||
forceMakeDir(create_dir)
|
|
||||||
}else if (create_dir == null){
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
val fileLength = 0L
|
val fileLength = 0L
|
||||||
|
|
||||||
|
@ -1784,7 +1780,7 @@ object Util {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
conn = url.openConnection() as HttpURLConnection
|
conn = url.openConnection() as HttpURLConnection
|
||||||
`is` = conn.inputStream
|
`is` = conn!!.inputStream
|
||||||
fos = FileOutputStream(file)
|
fos = FileOutputStream(file)
|
||||||
val buf = ByteArray(256)
|
val buf = ByteArray(256)
|
||||||
conn.connect()
|
conn.connect()
|
||||||
|
@ -1813,15 +1809,15 @@ object Util {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
} finally {
|
} finally {
|
||||||
try {
|
try {
|
||||||
conn?.disconnect()
|
conn!!.disconnect()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
fos?.close()
|
fos!!.close()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
`is`?.close()
|
`is`!!.close()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2053,19 +2049,17 @@ object Util {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun installApks4Tmp(apkName: String?, context: Context): Boolean {
|
fun installApks4Tmp(apkName: String?, context: Context?): Boolean {
|
||||||
var result = MockTools.execRead("pm install-create")
|
var result = MockTools.execRead("pm install-create")
|
||||||
Log.d(TAG, "installApks4Tmp: successMsg $result")
|
Log.d(TAG, "installApks4Tmp: successMsg $result")
|
||||||
val session = result.substring(result.indexOf("[") + 1, result.indexOf("]"))
|
val session = result.substring(result.indexOf("[") + 1, result.indexOf("]"))
|
||||||
Log.d(TAG, "installApks4Tmp: session $session")
|
Log.d(TAG, "installApks4Tmp: session $session")
|
||||||
|
|
||||||
val file = File(getRecordDataDirName(context))
|
val file = File("/sdcard/apks/$apkName")
|
||||||
Log.d(TAG, "installApks4Tmp: ${file.absolutePath}")
|
|
||||||
val files = file.listFiles()
|
val files = file.listFiles()
|
||||||
var bool = true
|
var bool = true
|
||||||
var currentApkFile = 1
|
var currentApkFile = 1
|
||||||
files?.let {
|
for (f in files) {
|
||||||
for (f in it) {
|
|
||||||
val extraName = f.name.substring(f.name.lastIndexOf('.') + 1)
|
val extraName = f.name.substring(f.name.lastIndexOf('.') + 1)
|
||||||
Log.d(
|
Log.d(
|
||||||
TAG,
|
TAG,
|
||||||
|
@ -2084,8 +2078,6 @@ object Util {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (bool) {
|
if (bool) {
|
||||||
result = MockTools.execRead("pm install-commit $session")
|
result = MockTools.execRead("pm install-commit $session")
|
||||||
if (!result.contains("Success")) {
|
if (!result.contains("Success")) {
|
||||||
|
@ -2094,11 +2086,9 @@ object Util {
|
||||||
} else {
|
} else {
|
||||||
result = MockTools.execRead("pm install-abandon $session")
|
result = MockTools.execRead("pm install-abandon $session")
|
||||||
}
|
}
|
||||||
files?.let {
|
for (f in files) {
|
||||||
for (f in it) {
|
|
||||||
MockTools.execRead("rm -rf $f")
|
MockTools.execRead("rm -rf $f")
|
||||||
}
|
}
|
||||||
}
|
|
||||||
setInstallRet(bool)
|
setInstallRet(bool)
|
||||||
return bool
|
return bool
|
||||||
}
|
}
|
||||||
|
@ -2433,7 +2423,7 @@ object Util {
|
||||||
}
|
}
|
||||||
tags = filterStr(tags)
|
tags = filterStr(tags)
|
||||||
// Log.e(TAG, "getCid:*"+tags+"*");
|
// Log.e(TAG, "getCid:*"+tags+"*");
|
||||||
return tags?:""
|
return tags!!
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getPropertiesFromAssets(context: Context, fileName: String): Properties {
|
fun getPropertiesFromAssets(context: Context, fileName: String): Properties {
|
||||||
|
@ -2571,9 +2561,9 @@ object Util {
|
||||||
*/
|
*/
|
||||||
//当本应用位于后台时,则将它切换到最前端
|
//当本应用位于后台时,则将它切换到最前端
|
||||||
fun setTopApp(context: Context) {
|
fun setTopApp(context: Context) {
|
||||||
Log.i(TAG, "start to setTopApp");
|
// Log.i(TAG, "start to setTopApp");
|
||||||
if (isRunningForeground(context)) {
|
if (isRunningForeground(context)) {
|
||||||
Log.i(TAG, "app isRunningForeground");
|
// Log.i(TAG, "app isRunningForeground");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
//获取ActivityManager
|
//获取ActivityManager
|
||||||
|
|
|
@ -9,8 +9,6 @@ import android.text.TextUtils
|
||||||
import androidx.work.CoroutineWorker
|
import androidx.work.CoroutineWorker
|
||||||
import androidx.work.WorkerParameters
|
import androidx.work.WorkerParameters
|
||||||
import com.android.grape.service.MyAccessibilityService
|
import com.android.grape.service.MyAccessibilityService
|
||||||
import androidx.core.content.edit
|
|
||||||
import com.android.grape.job.MonitorService
|
|
||||||
|
|
||||||
class CheckAccessibilityWorker(
|
class CheckAccessibilityWorker(
|
||||||
context: Context,
|
context: Context,
|
||||||
|
@ -34,7 +32,7 @@ class CheckAccessibilityWorker(
|
||||||
applicationContext.startActivity(intent)
|
applicationContext.startActivity(intent)
|
||||||
|
|
||||||
// 更新状态
|
// 更新状态
|
||||||
sharedPreferences.edit { putBoolean("accessibility_prompted", true) }
|
sharedPreferences.edit().putBoolean("accessibility_prompted", true).apply()
|
||||||
}
|
}
|
||||||
return Result.retry()
|
return Result.retry()
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue