Compare commits
10 Commits
ef7f148e13
...
727df6f21f
Author | SHA1 | Date |
---|---|---|
|
727df6f21f | |
|
e2800ffe8e | |
|
0239820321 | |
|
0e252b3751 | |
|
5bcecde83d | |
|
5772b91b9a | |
|
ce0933ee94 | |
|
fc62906c0e | |
|
b504984268 | |
|
2ed86fb2b7 |
|
@ -4,6 +4,14 @@
|
|||
<selectionStates>
|
||||
<SelectionState runConfigName="app">
|
||||
<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>
|
||||
</selectionStates>
|
||||
</component>
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="GradleMigrationSettings" migrationVersion="1" />
|
||||
<component name="GradleSettings">
|
||||
<option name="linkedExternalProjectsSettings">
|
||||
<GradleProjectSettings>
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK">
|
||||
|
|
|
@ -1,21 +1,47 @@
|
|||
# Add project specific ProGuard rules here.
|
||||
# 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
|
||||
-keep class com.google.gson.** { *; }
|
||||
-keep class com.google.gson.stream.** { *; }
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
# 保留所有注解
|
||||
-keepattributes *Annotation*
|
||||
-keepattributes Signature
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
# 保留枚举类
|
||||
-keepclassmembers enum * {
|
||||
public static **[] values();
|
||||
public static ** valueOf(java.lang.String);
|
||||
}
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
# 保留所有模型类(根据你的包结构调整)
|
||||
-keep class com.android.grape.pad.** { *; }
|
||||
-keep class com.android.grape.net.ApiResponse{ *; }
|
||||
-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,8 +9,7 @@
|
|||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.BIND_VPN_SERVICE"
|
||||
tools:ignore="ProtectedPermissions" />
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"
|
||||
android:maxSdkVersion="32"/>
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
|
||||
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.VPN_SERVICE" />
|
||||
|
@ -49,6 +48,11 @@
|
|||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.AndroidGrape"
|
||||
tools:targetApi="31">
|
||||
<provider
|
||||
android:name=".provider.DeviceInfoProvider"
|
||||
android:authorities="com.android.grape.deviceinfo.provider"
|
||||
android:exported="true">
|
||||
</provider>
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true">
|
||||
|
|
|
@ -3,6 +3,7 @@ package com.android.grape
|
|||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.activity.viewModels
|
||||
|
@ -11,11 +12,19 @@ import androidx.appcompat.app.AppCompatActivity
|
|||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
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.MockTools
|
||||
import com.android.grape.util.NotificationPermissionHandler
|
||||
import com.android.grape.util.ScriptUtil
|
||||
import com.android.grape.util.ShellUtils
|
||||
import com.android.grape.util.StoragePermissionHelper
|
||||
import com.blankj.utilcode.util.LogUtils
|
||||
import com.android.grape.util.Util
|
||||
import com.android.grape.util.Util.AUTO_JSPACKAGENAME
|
||||
import com.android.grape.util.Util.killRecordProcess
|
||||
import com.google.gson.Gson
|
||||
|
||||
class MainActivity : AppCompatActivity() {
|
||||
private val viewModel by viewModels<MainViewModel>()
|
||||
|
@ -24,6 +33,7 @@ class MainActivity : AppCompatActivity() {
|
|||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
enableEdgeToEdge()
|
||||
init()
|
||||
viewBinding = ActivityMainBinding.inflate(layoutInflater)
|
||||
setContentView(viewBinding.root)
|
||||
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
|
||||
|
@ -34,28 +44,23 @@ class MainActivity : AppCompatActivity() {
|
|||
checkPermission()
|
||||
ScriptUtil.registerScriptResultReceiver()
|
||||
viewBinding.start.setOnClickListener {
|
||||
try {
|
||||
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()
|
||||
}
|
||||
MonitorService.onEvent(MainApplication.instance)
|
||||
// DeviceDataAccessor.saveDeviceInfo(this, DeviceInfoHelper.getDeviceId(this))
|
||||
}
|
||||
viewBinding.stop.setOnClickListener {
|
||||
ClashUtil.stopProxy(this)
|
||||
// AutoJsUtil.stopAutojsScript(this)
|
||||
killRecordProcess(this, packageName)
|
||||
// DeviceDataAccessor.deleteDeviceInfo(this, DeviceInfoHelper.getDeviceId(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() {
|
||||
super.onDestroy()
|
||||
ClashUtil.unregisterReceiver(this)
|
||||
|
|
|
@ -3,6 +3,7 @@ package com.android.grape
|
|||
import androidx.lifecycle.ViewModel
|
||||
import androidx.work.PeriodicWorkRequest
|
||||
import androidx.work.WorkManager
|
||||
import com.android.grape.job.MonitorService
|
||||
import com.android.grape.service.MyAccessibilityService
|
||||
import com.android.grape.work.CheckAccessibilityWorker
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
@ -21,6 +22,5 @@ class MainViewModel:ViewModel() {
|
|||
.build()
|
||||
WorkManager.getInstance(MainApplication.instance).enqueue(workRequest)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
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
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
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,6 +2,8 @@ package com.android.grape.data
|
|||
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONObject
|
||||
|
||||
data class Device(
|
||||
var advertiserId: String = "",
|
||||
|
@ -110,4 +112,32 @@ data class Device(
|
|||
var vendingVersionName: String = "",
|
||||
@SerializedName("web_ua")
|
||||
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()
|
||||
}
|
||||
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
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,4 +9,6 @@ data class Sensor(
|
|||
var sV: String = "",
|
||||
var sVE: List<Double> = listOf(),
|
||||
var sVS: List<Double> = listOf()
|
||||
)
|
||||
) {
|
||||
companion object
|
||||
}
|
|
@ -15,7 +15,7 @@ class AutoJobService : JobIntentService() {
|
|||
Handler(Looper.getMainLooper()).postDelayed({
|
||||
ScriptUtil.execScript(
|
||||
this@AutoJobService,
|
||||
"/sdcard/autojs/main.js"
|
||||
"/sdcard/script/main.js"
|
||||
)
|
||||
}, 2000L)
|
||||
} else {
|
||||
|
|
|
@ -23,8 +23,8 @@ class DownloadAppJobService : JobIntentService() {
|
|||
|
||||
if (succ) {
|
||||
errTime = 0L
|
||||
StartVpnServerJobService.onEvent(this)
|
||||
// StartVpnPortJobService.onEvent(this)
|
||||
// InstallService.onEvent(this)
|
||||
StartVpnPortJobService.onEvent(this)
|
||||
} else {
|
||||
Util.isClickRet = false
|
||||
Util.setInstallRet(false)
|
||||
|
|
|
@ -4,6 +4,7 @@ import android.content.Context
|
|||
import android.content.Intent
|
||||
import android.util.Log
|
||||
import androidx.core.app.JobIntentService
|
||||
import com.android.grape.net.ChangeCallBack
|
||||
import com.android.grape.util.ChangeDeviceInfoUtil
|
||||
import com.android.grape.util.MockTools
|
||||
import com.android.grape.util.ServiceUtils
|
||||
|
@ -23,9 +24,9 @@ class OpenAppService : JobIntentService() {
|
|||
ServiceUtils.setEnableApp("com.android.chrome", true)
|
||||
ServiceUtils.setEnableApp("com.UCMobile", true)
|
||||
if (Util.isCanAuto && Util.canAutoLc.isNotEmpty()) {
|
||||
MockTools.exec("pm grant org.autojs.autojs android.permission.READ_EXTERNAL_STORAGE") //sdcard权限
|
||||
MockTools.exec("pm grant org.autojs.autojs android.permission.SYSTEM_ALERT_WINDOW") //悬浮窗权限
|
||||
MockTools.exec("settings put secure enabled_accessibility_services org.autojs.autojs/com.stardust.autojs.core.accessibility.AccessibilityService")
|
||||
MockTools.exec("pm grant ${Util.AUTO_JSPACKAGENAME} android.permission.READ_EXTERNAL_STORAGE") //sdcard权限
|
||||
MockTools.exec("pm grant ${Util.AUTO_JSPACKAGENAME} android.permission.SYSTEM_ALERT_WINDOW") //悬浮窗权限
|
||||
MockTools.exec("settings put secure enabled_accessibility_services ${Util.AUTO_JSPACKAGENAME}/${Util.AUTO_JSPACKAGENAME}.core.accessibility.AccessibilityService")
|
||||
Util.doScript(this, Util.AUTO_JSPACKAGENAME) //autojs
|
||||
}
|
||||
try {
|
||||
|
@ -37,13 +38,21 @@ class OpenAppService : JobIntentService() {
|
|||
// Log.d("IOSTQ:", "执行新装任务")
|
||||
// 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) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
|
||||
// Util.hookOpenApp(this);
|
||||
Util.openRecordApp(this)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
|
|
@ -6,13 +6,17 @@ import android.os.Handler
|
|||
import android.os.Looper
|
||||
import android.util.Log
|
||||
import androidx.core.app.JobIntentService
|
||||
import com.android.grape.net.MyGet
|
||||
import com.android.grape.util.ClashUtil
|
||||
import com.android.grape.util.ClashUtil.getProxyPort
|
||||
import com.android.grape.util.Util
|
||||
import java.util.Locale
|
||||
|
||||
class StartVpnPortJobService : JobIntentService() {
|
||||
override fun onHandleWork(intent: Intent) {
|
||||
Log.i(TAG, "start to handle work")
|
||||
if (ClashUtil.checkProxy( this)) {
|
||||
ClashUtil.switchProxyGroup("PROXY", "DIRECT", "http://127.0.0.1:6170")
|
||||
if (exec()) {
|
||||
Handler(Looper.getMainLooper()).postDelayed({
|
||||
StartVpnServerJobService.onEvent(
|
||||
this@StartVpnPortJobService
|
||||
|
@ -25,6 +29,28 @@ 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 {
|
||||
private const val TAG = "IOSTQ:StartVpnPort"
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ import android.util.Log
|
|||
import androidx.core.app.JobIntentService
|
||||
import com.android.grape.MainApplication
|
||||
import com.android.grape.util.ClashUtil
|
||||
import com.android.grape.util.CountryCode
|
||||
import com.android.grape.util.Util
|
||||
|
||||
/**
|
||||
|
@ -27,19 +28,12 @@ class StartVpnServerJobService : JobIntentService() {
|
|||
onEvent(
|
||||
this@StartVpnServerJobService
|
||||
)
|
||||
}, 1000L)
|
||||
}, 2000L)
|
||||
}
|
||||
}
|
||||
|
||||
private fun exec(): Boolean {
|
||||
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
|
||||
return ClashUtil.switchProxyGroup("PROXY", "my-socks5-proxy", "http://127.0.0.1:6170")
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
|
|
@ -0,0 +1,70 @@
|
|||
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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
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
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
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()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
package com.android.grape.net
|
||||
|
||||
interface ChangeCallBack {
|
||||
fun changeSuccess()
|
||||
fun changeFailed()
|
||||
}
|
|
@ -2,22 +2,28 @@ package com.android.grape.net
|
|||
|
||||
import android.util.Log
|
||||
import com.blankj.utilcode.util.LogUtils
|
||||
import com.google.gson.Gson
|
||||
import okhttp3.*
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
||||
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
||||
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.util.concurrent.TimeUnit
|
||||
|
||||
object HttpUtils {
|
||||
|
||||
const val HOST: String = "https://openapi-hk.armcloud.net"
|
||||
// OkHttp客户端配置
|
||||
private val client: OkHttpClient by lazy {
|
||||
OkHttpClient.Builder()
|
||||
.connectTimeout(15, TimeUnit.SECONDS)
|
||||
.readTimeout(15, TimeUnit.SECONDS)
|
||||
.writeTimeout(15, TimeUnit.SECONDS)
|
||||
.addInterceptor(LoggingInterceptor()) // 添加日志拦截器
|
||||
.addInterceptor(HttpLoggingInterceptor().apply {
|
||||
level = Level.BODY
|
||||
})
|
||||
.build()
|
||||
}
|
||||
|
||||
|
@ -30,47 +36,6 @@ object HttpUtils {
|
|||
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 请求(异步)
|
||||
*
|
||||
|
@ -82,10 +47,9 @@ object HttpUtils {
|
|||
fun getAsync(
|
||||
url: String,
|
||||
params: Map<String, String>? = null,
|
||||
headers: Map<String, String>? = null,
|
||||
callback: HttpCallback
|
||||
) {
|
||||
val request = buildGetRequest(url, params, headers)
|
||||
val request = buildGetRequest(url, params)
|
||||
executeRequestAsync(request, callback)
|
||||
}
|
||||
|
||||
|
@ -99,31 +63,10 @@ object HttpUtils {
|
|||
*/
|
||||
fun postAsync(
|
||||
url: String,
|
||||
body: String,
|
||||
headers: Map<String, String>? = null,
|
||||
body: String? = null,
|
||||
callback: HttpCallback
|
||||
) {
|
||||
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)
|
||||
val request = buildPostRequest(url, body)
|
||||
executeRequestAsync(request, callback)
|
||||
}
|
||||
|
||||
|
@ -138,9 +81,8 @@ object HttpUtils {
|
|||
fun getSync(
|
||||
url: String,
|
||||
params: Map<String, String>? = null,
|
||||
headers: Map<String, String>? = null
|
||||
): Pair<String?, Int> {
|
||||
val request = buildGetRequest(url, params, headers)
|
||||
val request = buildGetRequest(url, params)
|
||||
return executeRequestSync(request)
|
||||
}
|
||||
|
||||
|
@ -154,11 +96,9 @@ object HttpUtils {
|
|||
*/
|
||||
fun postSync(
|
||||
url: String,
|
||||
body: String,
|
||||
headers: Map<String, String>? = null
|
||||
body: String? = null
|
||||
): Pair<String?, Int> {
|
||||
val requestBody = body.toRequestBody(JSON_MEDIA_TYPE)
|
||||
val request = buildPostRequest(url, requestBody, headers)
|
||||
val request = buildPostRequest(url, body)
|
||||
return executeRequestSync(request)
|
||||
}
|
||||
|
||||
|
@ -172,46 +112,72 @@ object HttpUtils {
|
|||
// 内部方法 --------------------------------
|
||||
|
||||
private fun buildGetRequest(
|
||||
url: String,
|
||||
path: String ,
|
||||
params: Map<String, String>?,
|
||||
headers: Map<String, String>?
|
||||
): Request {
|
||||
val url = "$HOST$path"
|
||||
val httpUrlBuilder = url.toHttpUrlOrNull()?.newBuilder()
|
||||
?: throw IllegalArgumentException("Invalid URL: $url")
|
||||
|
||||
// 添加查询参数
|
||||
var param = ""
|
||||
params?.forEach { (key, value) ->
|
||||
httpUrlBuilder.addQueryParameter(key, value)
|
||||
param += "&${key}=${value}"
|
||||
}
|
||||
if (param.isNotEmpty()){
|
||||
param = param.substring(1)
|
||||
}
|
||||
|
||||
val requestBuilder = Request.Builder().url(httpUrlBuilder.build())
|
||||
|
||||
// 添加请求头
|
||||
addHeaders(requestBuilder, headers)
|
||||
val authver = "2.0"
|
||||
val timestamp = System.currentTimeMillis()
|
||||
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()
|
||||
}
|
||||
|
||||
private fun buildPostRequest(
|
||||
url: String,
|
||||
body: RequestBody,
|
||||
headers: Map<String, String>?
|
||||
path: String,
|
||||
body: String? = null
|
||||
): Request {
|
||||
val url = "$HOST$path"
|
||||
val bodyString = body?:""
|
||||
val requestBuilder = Request.Builder()
|
||||
.url(url)
|
||||
.post(body)
|
||||
|
||||
// 添加请求头
|
||||
addHeaders(requestBuilder, headers)
|
||||
.post(bodyString.toRequestBody(JSON_MEDIA_TYPE))
|
||||
val authver = "2.0"
|
||||
val timestamp = System.currentTimeMillis()
|
||||
val sign =
|
||||
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()
|
||||
}
|
||||
|
||||
private fun addHeaders(
|
||||
builder: Request.Builder,
|
||||
headers: Map<String, String>?
|
||||
headers: Map<String, String>
|
||||
) {
|
||||
headers?.forEach { (key, value) ->
|
||||
headers.forEach { (key, value) ->
|
||||
builder.addHeader(key, value)
|
||||
}
|
||||
}
|
||||
|
@ -252,13 +218,13 @@ object HttpUtils {
|
|||
private fun executeRequestSync(request: Request): Pair<String?, Int> {
|
||||
return try {
|
||||
val response = client.newCall(request).execute()
|
||||
val responseBody = response.body?.string()
|
||||
val responseBody = response.peekBody(Long.MAX_VALUE).string()
|
||||
val code = response.code
|
||||
|
||||
if (response.isSuccessful && responseBody != null) {
|
||||
Log.d("TAG", "executeRequestSync: $responseBody")
|
||||
if (response.isSuccessful && responseBody.isNotEmpty()) {
|
||||
Pair(responseBody, code)
|
||||
} else {
|
||||
Pair(null, code)
|
||||
Pair(null, -1)
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
Pair(null, -1) // 网络错误状态码
|
||||
|
|
|
@ -2,16 +2,48 @@ package com.android.grape.net
|
|||
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import java.io.BufferedInputStream
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.net.HttpURLConnection
|
||||
import java.net.URL
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
|
||||
object 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? {
|
||||
return getData(url_, ua, 0)
|
||||
|
@ -31,7 +63,7 @@ object MyGet {
|
|||
httpUrlConnection = url.openConnection() as HttpURLConnection
|
||||
|
||||
httpUrlConnection.allowUserInteraction = true
|
||||
httpUrlConnection!!.doOutput = false
|
||||
httpUrlConnection.doOutput = false
|
||||
httpUrlConnection.doInput = true
|
||||
httpUrlConnection.useCaches = false
|
||||
httpUrlConnection.setRequestProperty("Connection", "close") //add 20200428
|
||||
|
@ -140,7 +172,7 @@ object MyGet {
|
|||
httpUrlConnection = url.openConnection() as HttpURLConnection
|
||||
|
||||
httpUrlConnection.allowUserInteraction = true
|
||||
httpUrlConnection!!.doOutput = false
|
||||
httpUrlConnection.doOutput = false
|
||||
httpUrlConnection.doInput = true
|
||||
httpUrlConnection.useCaches = false
|
||||
httpUrlConnection.instanceFollowRedirects = false
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
package com.android.grape.net
|
||||
|
||||
class NetworkHelper {
|
||||
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
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 = ""
|
||||
)
|
|
@ -0,0 +1,13 @@
|
|||
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
|
||||
)
|
|
@ -0,0 +1,10 @@
|
|||
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
|
||||
)
|
|
@ -0,0 +1,17 @@
|
|||
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
|
||||
)
|
|
@ -0,0 +1,14 @@
|
|||
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
|
||||
)
|
|
@ -0,0 +1,100 @@
|
|||
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"
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
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"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
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)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
package com.android.grape.provider
|
||||
|
||||
object DeviceInfoHelper {
|
||||
// 获取设备唯一ID
|
||||
fun getDeviceId(): String {
|
||||
return "d1b3e7f8c9a04b8e9f2c1d5e6f7a8b9c"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,148 @@
|
|||
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,7 +83,9 @@ class ApkSourceBuilder(private val mContext: Context) {
|
|||
val apkFileDescriptors: MutableList<FileDescriptor> = ArrayList<FileDescriptor>(
|
||||
mApkFiles?.size?: 0
|
||||
)
|
||||
for (apkFile in mApkFiles!!) apkFileDescriptors.add(NormalFileDescriptor(apkFile))
|
||||
mApkFiles?.let {
|
||||
for (apkFile in it) apkFileDescriptors.add(NormalFileDescriptor(apkFile))
|
||||
}
|
||||
|
||||
apkSource = DefaultApkSource(apkFileDescriptors)
|
||||
} else if (mZipFile != null) {
|
||||
|
|
|
@ -59,13 +59,16 @@ class FlexSaiPackageInstaller private constructor(c: Context) : SaiPackageInstal
|
|||
installer: SaiPackageInstaller,
|
||||
params: SaiPiSessionParams
|
||||
): String {
|
||||
val sessionId = installer.createSession(params)
|
||||
mSessionIdToInstaller[sessionId!!] = installer
|
||||
val sessionId = installer.createSession(params)?:""
|
||||
mSessionIdToInstaller[sessionId] = installer
|
||||
return sessionId
|
||||
}
|
||||
|
||||
override fun createSession(params: SaiPiSessionParams): String {
|
||||
return createSessionOnInstaller(mDefaultInstaller!!, params)
|
||||
mDefaultInstaller?.let {
|
||||
return createSessionOnInstaller(it, params)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
override fun enqueueSession(sessionId: String) {
|
||||
|
|
|
@ -18,7 +18,7 @@ class MyBroadcastReceiver : BroadcastReceiver() {
|
|||
//接收广播消息
|
||||
fruit = intent.getStringExtra("fruit")
|
||||
//调用接口MyReceiver里面的interFruit方法传入接收的内容
|
||||
mReceiver!!.interFruit(fruit)
|
||||
mReceiver?.interFruit(fruit)
|
||||
//使用Toast显示广播消息
|
||||
Toast.makeText(context, fruit, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
|
|
@ -116,7 +116,7 @@ class RootlessSaiPiBroadcastReceiver(c: Context) : BroadcastReceiver() {
|
|||
|
||||
val androidPackageInstallerError: AndroidPackageInstallerError =
|
||||
getAndroidPmError(errorCode, error)
|
||||
if (androidPackageInstallerError !== AndroidPackageInstallerError.UNKNOWN) {
|
||||
if (androidPackageInstallerError != AndroidPackageInstallerError.UNKNOWN) {
|
||||
return androidPackageInstallerError.getDescription(mContext)
|
||||
}
|
||||
|
||||
|
|
|
@ -53,9 +53,9 @@ abstract class ShellSaiPackageInstaller protected constructor(c: Context?) :
|
|||
|
||||
val installedPackage: String
|
||||
try {
|
||||
installedPackage = intent.dataString!!.replace("package:", "")
|
||||
installedPackage = intent.dataString?.replace("package:", "")?:""
|
||||
val installerPackage: String =
|
||||
context.getPackageManager().getInstallerPackageName(installedPackage)?:""
|
||||
context.packageManager.getInstallerPackageName(installedPackage)?:""
|
||||
Log.d(tag(), "installerPackage=$installerPackage")
|
||||
if ("com.android.grape" != installerPackage) return
|
||||
} catch (e: Exception) {
|
||||
|
@ -130,74 +130,44 @@ abstract class ShellSaiPackageInstaller protected constructor(c: Context?) :
|
|||
).build()
|
||||
)
|
||||
unlockInstallation()
|
||||
// Toast.makeText(getContext(),"Installation failed",Toast.LENGTH_SHORT).show();
|
||||
Util.setInstallRet(false)
|
||||
return
|
||||
}
|
||||
androidSessionId = createSession()
|
||||
val path = "/sdcard/apks/" + "com.zhiliaoapp.musically"
|
||||
//todo params.apkSource().apkLocalPath?
|
||||
val path = "/sdcard/apks/${Util.recordPackageName}"
|
||||
val file = File(path)
|
||||
|
||||
val files = file.listFiles()
|
||||
var currentApkFile = 0
|
||||
files?.let {
|
||||
for (f in files) {
|
||||
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()
|
||||
// Toast.makeText(getContext(),"Installation failed",Toast.LENGTH_SHORT).show();
|
||||
Util.setInstallRet(false)
|
||||
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)
|
||||
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)
|
||||
if (!installationResult.isSuccessful) {
|
||||
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)}
|
||||
|
||||
${parseError(installationResult)}
|
||||
""".trimIndent()
|
||||
)
|
||||
setSessionState(
|
||||
sessionId, SaiPiSessionState.Builder(sessionId, SaiPiSessionStatus.INSTALLATION_FAILED)
|
||||
.appTempName(appTempName)
|
||||
.error(
|
||||
""".trimIndent())
|
||||
setSessionState(sessionId, SaiPiSessionState.Builder(sessionId, SaiPiSessionStatus.INSTALLATION_FAILED).appTempName(appTempName).error(
|
||||
shortError, """
|
||||
$shortError
|
||||
|
||||
${installationResult.toString()}
|
||||
""".trimIndent()
|
||||
)
|
||||
.build()
|
||||
)
|
||||
|
||||
""".trimIndent()).build())
|
||||
unlockInstallation()
|
||||
|
||||
Util.setInstallRet(false)
|
||||
|
@ -206,7 +176,6 @@ abstract class ShellSaiPackageInstaller protected constructor(c: Context?) :
|
|||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
//TODO this catches resources close exception causing a crash, same in rootless installer
|
||||
Log.w(tag(), e)
|
||||
|
||||
if (androidSessionId != null) {
|
||||
|
@ -291,7 +260,7 @@ abstract class ShellSaiPackageInstaller protected constructor(c: Context?) :
|
|||
DbgPreferencesHelper.customInstallCreateCommand
|
||||
if (customInstallCreateCommand != null) {
|
||||
val args = ArrayList(
|
||||
Arrays.asList(
|
||||
listOf(
|
||||
*customInstallCreateCommand.split(" ".toRegex()).dropLastWhile { it.isEmpty() }
|
||||
.toTypedArray()))
|
||||
val command = args.removeAt(0)
|
||||
|
@ -356,7 +325,7 @@ abstract class ShellSaiPackageInstaller protected constructor(c: Context?) :
|
|||
val sessionIdPattern = Pattern.compile("(\\d+)")
|
||||
val sessionIdMatcher = sessionIdPattern.matcher(commandResult)
|
||||
sessionIdMatcher.find()
|
||||
return sessionIdMatcher.group(1).toInt()
|
||||
return sessionIdMatcher.group(1)?.toInt()
|
||||
} catch (e: Exception) {
|
||||
Log.w(tag(), commandResult, e)
|
||||
return null
|
||||
|
@ -365,7 +334,7 @@ abstract class ShellSaiPackageInstaller protected constructor(c: Context?) :
|
|||
|
||||
private fun parseError(installCommitResult: Shell.Result): String {
|
||||
var matchedError: AndroidPackageInstallerError = AndroidPackageInstallerError.UNKNOWN
|
||||
for (error in AndroidPackageInstallerError.values()) {
|
||||
for (error in AndroidPackageInstallerError.entries) {
|
||||
if (installCommitResult.out.contains(error.error)) {
|
||||
matchedError = error
|
||||
break
|
||||
|
|
|
@ -28,7 +28,7 @@ class CopyToFileApkSource(context: Context, wrappedApkSource: ApkSource) :
|
|||
IOUtils.deleteRecursively(it)
|
||||
}
|
||||
|
||||
mCurrentApkFile = File(mTempDir, mWrappedApkSource.apkName)
|
||||
mCurrentApkFile = File(mTempDir, mWrappedApkSource.apkName?:"")
|
||||
|
||||
mWrappedApkSource.openApkInputStream().use { `in` ->
|
||||
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 {
|
||||
mApkSigner?.sign(
|
||||
it,
|
||||
|
|
|
@ -38,11 +38,11 @@ object FileUtils {
|
|||
}
|
||||
|
||||
private fun trimFilename(res: StringBuilder, maxBytes: Int) {
|
||||
var maxBytes = maxBytes
|
||||
var bytes = maxBytes
|
||||
var raw = res.toString().toByteArray(StandardCharsets.UTF_8)
|
||||
if (raw.size > maxBytes) {
|
||||
maxBytes -= 3
|
||||
while (raw.size > maxBytes) {
|
||||
if (raw.size > bytes) {
|
||||
bytes -= 3
|
||||
while (raw.size > bytes) {
|
||||
res.deleteCharAt(res.length / 2)
|
||||
raw = res.toString().toByteArray(StandardCharsets.UTF_8)
|
||||
}
|
||||
|
|
|
@ -25,14 +25,15 @@ class ZipApkSource(private val mContext: Context, private val mZipFileDescriptor
|
|||
@Throws(Exception::class)
|
||||
override fun nextApk(): Boolean {
|
||||
if (!mIsOpen) {
|
||||
mZipInputStream = ZipInputStream(mZipFileDescriptor.open())
|
||||
mWrappedStream = ZipInputStreamWrapper(mZipInputStream!!)
|
||||
mZipInputStream = ZipInputStream(mZipFileDescriptor.open()).apply {
|
||||
mWrappedStream = ZipInputStreamWrapper(this)
|
||||
}
|
||||
mIsOpen = true
|
||||
}
|
||||
|
||||
do {
|
||||
try {
|
||||
entry = mZipInputStream!!.nextEntry
|
||||
entry = mZipInputStream?.nextEntry
|
||||
} catch (e: ZipException) {
|
||||
if (e.message == "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()
|
||||
|
||||
entry = null
|
||||
while (entry == null && mZipEntries!!.hasMoreElements()) {
|
||||
val nextEntry = mZipEntries!!.nextElement()
|
||||
while (entry == null && mZipEntries?.hasMoreElements() == true) {
|
||||
mZipEntries?.nextElement()?.let { nextEntry ->
|
||||
if (!nextEntry.isDirectory && nextEntry.name.lowercase(Locale.getDefault())
|
||||
.endsWith(".apk")
|
||||
) {
|
||||
|
@ -44,6 +44,7 @@ class ZipFileApkSource(context: Context, private val mZipFileDescriptor: FileDes
|
|||
mSeenApkFile = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (entry == null) {
|
||||
require(mSeenApkFile) { mContext.getString(R.string.installer_error_zip_contains_no_apks) }
|
||||
|
|
|
@ -138,7 +138,7 @@ class PackageMeta : Parcelable {
|
|||
|
||||
return Builder(applicationInfo.packageName)
|
||||
.setLabel(applicationInfo.loadLabel(pm).toString())
|
||||
.setHasSplits(applicationInfo.splitPublicSourceDirs != null && applicationInfo.splitPublicSourceDirs!!.size > 0)
|
||||
.setHasSplits(applicationInfo.splitPublicSourceDirs != null && applicationInfo.splitPublicSourceDirs?.isNotEmpty() == true)
|
||||
.setIsSystemApp((applicationInfo.flags and ApplicationInfo.FLAG_SYSTEM) != 0)
|
||||
.setVersionCode(if (Utils.apiIsAtLeast(Build.VERSION_CODES.P)) packageInfo.longVersionCode else packageInfo.versionCode.toLong())
|
||||
.setVersionName(packageInfo.versionName)
|
||||
|
|
|
@ -127,11 +127,11 @@ class SaiPiSessionState private constructor(
|
|||
}
|
||||
|
||||
fun error(shortError: String?, fullError: String?): Builder {
|
||||
var fullError = fullError
|
||||
var error = fullError
|
||||
mState.mShortError = shortError
|
||||
if (fullError == null) fullError = shortError
|
||||
if (error == null) error = shortError
|
||||
|
||||
mState.mFullError = fullError
|
||||
mState.mFullError = error
|
||||
return this
|
||||
}
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ package com.android.grape.sai.prefers
|
|||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.os.Environment
|
||||
import androidx.core.content.edit
|
||||
|
||||
|
||||
class PreferencesHelper private constructor(c: Context) {
|
||||
|
@ -50,7 +51,7 @@ class PreferencesHelper private constructor(c: Context) {
|
|||
}
|
||||
|
||||
fun setShouldSignApks(signApks: Boolean) {
|
||||
prefs.edit().putBoolean(PreferencesKeys.SIGN_APKS, signApks).apply()
|
||||
prefs.edit { putBoolean(PreferencesKeys.SIGN_APKS, signApks) }
|
||||
}
|
||||
|
||||
fun shouldExtractArchives(): Boolean {
|
||||
|
@ -64,7 +65,7 @@ class PreferencesHelper private constructor(c: Context) {
|
|||
var installer: Int
|
||||
get() = prefs.getInt(PreferencesKeys.INSTALLER, PreferencesValues.INSTALLER_ROOTED)
|
||||
set(installer) {
|
||||
prefs.edit().putInt(PreferencesKeys.INSTALLER, installer).apply()
|
||||
prefs.edit { putInt(PreferencesKeys.INSTALLER, installer) }
|
||||
}
|
||||
|
||||
var backupFileNameFormat: String?
|
||||
|
@ -73,7 +74,7 @@ class PreferencesHelper private constructor(c: Context) {
|
|||
PreferencesValues.BACKUP_FILE_NAME_FORMAT_DEFAULT
|
||||
)
|
||||
set(format) {
|
||||
prefs.edit().putString(PreferencesKeys.BACKUP_FILE_NAME_FORMAT, format).apply()
|
||||
prefs.edit { putString(PreferencesKeys.BACKUP_FILE_NAME_FORMAT, format) }
|
||||
}
|
||||
|
||||
var installLocation: Int
|
||||
|
@ -87,8 +88,9 @@ class PreferencesHelper private constructor(c: Context) {
|
|||
}
|
||||
}
|
||||
set(installLocation) {
|
||||
prefs.edit().putString(PreferencesKeys.INSTALL_LOCATION, installLocation.toString())
|
||||
.apply()
|
||||
prefs.edit {
|
||||
putString(PreferencesKeys.INSTALL_LOCATION, installLocation.toString())
|
||||
}
|
||||
}
|
||||
|
||||
fun useOldInstaller(): Boolean {
|
||||
|
@ -108,7 +110,7 @@ class PreferencesHelper private constructor(c: Context) {
|
|||
}
|
||||
|
||||
fun setSafTipShown() {
|
||||
prefs.edit().putBoolean(PreferencesKeys.SAF_TIP_SHOWN, true).apply()
|
||||
prefs.edit { putBoolean(PreferencesKeys.SAF_TIP_SHOWN, true) }
|
||||
}
|
||||
|
||||
val isInstallerXEnabled: Boolean
|
||||
|
@ -120,19 +122,19 @@ class PreferencesHelper private constructor(c: Context) {
|
|||
var isAnalyticsEnabled: Boolean
|
||||
get() = prefs.getBoolean(PreferencesKeys.ENABLE_ANALYTICS, true)
|
||||
set(enabled) {
|
||||
prefs.edit().putBoolean(PreferencesKeys.ENABLE_ANALYTICS, enabled).apply()
|
||||
prefs.edit { putBoolean(PreferencesKeys.ENABLE_ANALYTICS, enabled) }
|
||||
}
|
||||
|
||||
var isInitialIndexingDone: Boolean
|
||||
get() = prefs.getBoolean(PreferencesKeys.INITIAL_INDEXING_RUN, false)
|
||||
set(done) {
|
||||
prefs.edit().putBoolean(PreferencesKeys.INITIAL_INDEXING_RUN, done).apply()
|
||||
prefs.edit { putBoolean(PreferencesKeys.INITIAL_INDEXING_RUN, done) }
|
||||
}
|
||||
|
||||
var isSingleApkExportEnabled: Boolean
|
||||
get() = prefs.getBoolean(PreferencesKeys.BACKUP_APK_EXPORT, false)
|
||||
set(enabled) {
|
||||
prefs.edit().putBoolean(PreferencesKeys.BACKUP_APK_EXPORT, enabled).apply()
|
||||
prefs.edit { putBoolean(PreferencesKeys.BACKUP_APK_EXPORT, enabled) }
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
|
|
@ -40,11 +40,9 @@ class MyAccessibilityService:AccessibilityService(){
|
|||
val manager: NotificationManager = getSystemService<NotificationManager>(
|
||||
NotificationManager::class.java
|
||||
)
|
||||
if (manager != null) {
|
||||
manager.createNotificationChannel(channel)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun startForegroundService() {
|
||||
createNotificationChannel()
|
||||
|
|
|
@ -0,0 +1,128 @@
|
|||
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,14 +5,175 @@ import android.content.Context
|
|||
import android.util.Log
|
||||
import com.android.grape.MainApplication
|
||||
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.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 java.lang.reflect.InvocationTargetException
|
||||
|
||||
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() {
|
||||
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")
|
||||
if (deviceObject == null) {
|
||||
LogUtils.d("ERROR", "ChangeDeviceInfoUtil", "device is null")
|
||||
|
@ -32,7 +193,7 @@ object ChangeDeviceInfoUtil {
|
|||
vcloudsettingsPut("$currentPkgName.vendor", device.expand.glVendor, context)
|
||||
// callVCloudSettings_put("$currentPkgName.battery_scale", batteryScale.toString(), context)
|
||||
vcloudsettingsPut("$currentPkgName.os_lang", device.lang, context)
|
||||
vcloudsettingsPut("$currentPkgName.model", device.model, context);
|
||||
vcloudsettingsPut("$currentPkgName.model", device.model, context)
|
||||
vcloudsettingsPut("$currentPkgName.net", device.network, context)
|
||||
vcloudsettingsPut("$currentPkgName.dpi", device.expand.dPI.toString(), context)
|
||||
// callVCloudSettings_put(
|
||||
|
@ -51,7 +212,7 @@ object ChangeDeviceInfoUtil {
|
|||
// **os_ver**
|
||||
vcloudsettingsPut(currentPkgName + "_os_ver", device.sdkVer, context)
|
||||
// **tz** (时区)
|
||||
vcloudsettingsPut(currentPkgName + "_tz", device.tzOffTime.toString(), context)
|
||||
vcloudsettingsPut(currentPkgName + "_tz", TimeZoneUtils.formatTimeZoneOffset(device.tzOffTime), context)
|
||||
|
||||
vcloudsettingsPut("$currentPkgName.advertiserId", device.advertiserId, context)
|
||||
vcloudsettingsPut("$currentPkgName.brand", device.brand, context)
|
||||
|
@ -121,6 +282,9 @@ object ChangeDeviceInfoUtil {
|
|||
ShellUtils.execRootCmd("setprop persist.sys.cloud.battery.capacity ${device.battery}")
|
||||
ShellUtils.execRootCmd("setprop persist.sys.cloud.gpu.gl_vendor ${device.expand.glVendor}")
|
||||
ShellUtils.execRootCmd("setprop persist.sys.cloud.gpu.gl_renderer ${device.expand.glRenderer}")
|
||||
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 这个格式
|
||||
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")
|
||||
|
@ -137,7 +301,7 @@ object ChangeDeviceInfoUtil {
|
|||
|
||||
}
|
||||
|
||||
private fun vcloudsettingsPut(key: String, value: String, context: Context) {
|
||||
fun vcloudsettingsPut(key: String, value: String, context: Context) {
|
||||
if (key.isEmpty()) {
|
||||
LogUtils.e(Log.ERROR, "ChangeDeviceInfoUtil", "Key cannot be null or empty", null)
|
||||
throw IllegalArgumentException("Key cannot be null or empty")
|
||||
|
|
|
@ -4,24 +4,35 @@ import android.content.BroadcastReceiver
|
|||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.os.Environment
|
||||
import android.util.Log
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.blankj.utilcode.util.LogUtils
|
||||
import okhttp3.Call
|
||||
import okhttp3.Callback
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import okhttp3.MediaType
|
||||
import okhttp3.MediaType.Companion.toMediaType
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import okhttp3.RequestBody.Companion.toRequestBody
|
||||
import okhttp3.Response
|
||||
import okhttp3.logging.HttpLoggingInterceptor
|
||||
import org.json.JSONException
|
||||
import org.json.JSONObject
|
||||
import java.io.BufferedReader
|
||||
import java.io.File
|
||||
import java.io.FileReader
|
||||
import java.io.IOException
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
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) {
|
||||
val intent = Intent("com.github.kr328.clash.intent.action.SESSION_CREATE")
|
||||
intent.putExtra("profile", "default") // 可选择您在 Clash 中配置的 Profile
|
||||
|
@ -35,6 +46,66 @@ object ClashUtil {
|
|||
}.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
|
||||
|
||||
val clashStatusReceiver: BroadcastReceiver = object : BroadcastReceiver() {
|
||||
|
@ -90,26 +161,7 @@ object ClashUtil {
|
|||
}
|
||||
|
||||
|
||||
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")
|
||||
}
|
||||
fun switchProxyGroup(groupName: String?, proxyName: String?, controllerUrl: String): Boolean {
|
||||
|
||||
val client = OkHttpClient()
|
||||
val json = JSONObject()
|
||||
|
@ -133,39 +185,27 @@ object ClashUtil {
|
|||
.put(requestBody)
|
||||
.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 {
|
||||
if (response.body != null) {
|
||||
LogUtils.log(
|
||||
Log.INFO,
|
||||
val result = sharedClient.newCall(request).execute().use { response ->
|
||||
// 读取并记录响应内容
|
||||
val responseBody = if (response.body != null) response.body?.string() else "Empty response body"
|
||||
val isSuccess = if (response.isSuccessful) {
|
||||
LogUtils.d(
|
||||
"ClashUtil",
|
||||
"switchProxyGroup: Switch proxy response",
|
||||
null
|
||||
)
|
||||
"switchProxyGroup: Success | Status: " + response.code + " | Response: " + responseBody)
|
||||
true
|
||||
} else {
|
||||
LogUtils.log(
|
||||
Log.ERROR,
|
||||
LogUtils.d(
|
||||
"ClashUtil",
|
||||
"switchProxyGroup: Response body is null",
|
||||
null
|
||||
)
|
||||
"switchProxyGroup: Failed | Status: " + response.code + " | Response: " + responseBody)
|
||||
false
|
||||
}
|
||||
} finally {
|
||||
response.close()
|
||||
return isSuccess
|
||||
}
|
||||
return result
|
||||
} catch (e: Exception) {
|
||||
LogUtils.d("ClashUtil", "switchProxyGroup: Unexpected error", e)
|
||||
}
|
||||
})
|
||||
return false
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
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,6 +1,8 @@
|
|||
package com.android.grape.util
|
||||
|
||||
import android.os.Environment
|
||||
import android.util.Log
|
||||
import com.blankj.utilcode.util.LogUtils
|
||||
import java.io.BufferedInputStream
|
||||
import java.io.BufferedOutputStream
|
||||
import java.io.BufferedReader
|
||||
|
@ -16,6 +18,7 @@ import java.io.InputStreamReader
|
|||
import java.io.OutputStream
|
||||
import java.io.RandomAccessFile
|
||||
import java.io.UnsupportedEncodingException
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.util.Enumeration
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipFile
|
||||
|
@ -387,4 +390,31 @@ object FileUtils {
|
|||
output.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,25 +12,26 @@ object MockTools {
|
|||
fun exec(cmd: String): String {
|
||||
var retString = ""
|
||||
try {
|
||||
//创建socket
|
||||
val myCmd = "SU|$cmd"
|
||||
|
||||
val mSocket = Socket()
|
||||
val inetSocketAddress = InetSocketAddress("127.0.0.1", 12345)
|
||||
mSocket.connect(inetSocketAddress)
|
||||
val bufferedWriter = BufferedWriter(OutputStreamWriter(mSocket.getOutputStream()))
|
||||
val bufferedReader = BufferedReader(InputStreamReader(mSocket.getInputStream()))
|
||||
bufferedWriter.write(myCmd + "\r\n")
|
||||
bufferedWriter.flush()
|
||||
val stringBuilder = StringBuilder()
|
||||
var line: String? = null
|
||||
while ((bufferedReader.readLine().also { line = it }) != null) {
|
||||
stringBuilder.append(line + "\n")
|
||||
}
|
||||
//retString = bufferedReader.readLine();
|
||||
retString = stringBuilder.toString()
|
||||
bufferedReader.close()
|
||||
bufferedWriter.close()
|
||||
retString = ShellUtils.execRootCmdAndGetResult(cmd)
|
||||
// //创建socket
|
||||
// val myCmd = "SU|$cmd"
|
||||
//
|
||||
// val mSocket = Socket()
|
||||
// val inetSocketAddress = InetSocketAddress("127.0.0.1", 12345)
|
||||
// mSocket.connect(inetSocketAddress)
|
||||
// val bufferedWriter = BufferedWriter(OutputStreamWriter(mSocket.getOutputStream()))
|
||||
// val bufferedReader = BufferedReader(InputStreamReader(mSocket.getInputStream()))
|
||||
// bufferedWriter.write(myCmd + "\r\n")
|
||||
// bufferedWriter.flush()
|
||||
// val stringBuilder = StringBuilder()
|
||||
// var line: String? = null
|
||||
// while ((bufferedReader.readLine().also { line = it }) != null) {
|
||||
// stringBuilder.append(line + "\n")
|
||||
// }
|
||||
// //retString = bufferedReader.readLine();
|
||||
// retString = stringBuilder.toString()
|
||||
// bufferedReader.close()
|
||||
// bufferedWriter.close()
|
||||
} catch (eeeee: Exception) {
|
||||
eeeee.printStackTrace()
|
||||
}
|
||||
|
@ -40,25 +41,26 @@ object MockTools {
|
|||
fun execRead(cmd: String): String {
|
||||
var retString = ""
|
||||
try {
|
||||
//创建socket
|
||||
val myCmd = "SU_1|$cmd"
|
||||
|
||||
val mSocket = Socket()
|
||||
val inetSocketAddress = InetSocketAddress("127.0.0.1", 12345)
|
||||
mSocket.connect(inetSocketAddress)
|
||||
val bufferedWriter = BufferedWriter(OutputStreamWriter(mSocket.getOutputStream()))
|
||||
val bufferedReader = BufferedReader(InputStreamReader(mSocket.getInputStream()))
|
||||
bufferedWriter.write(myCmd + "\r\n")
|
||||
bufferedWriter.flush()
|
||||
val stringBuilder = StringBuilder()
|
||||
var line: String? = null
|
||||
while ((bufferedReader.readLine().also { line = it }) != null) {
|
||||
stringBuilder.append(line + "\n")
|
||||
}
|
||||
retString = stringBuilder.toString()
|
||||
//retString = bufferedReader.readLine();
|
||||
bufferedReader.close()
|
||||
bufferedWriter.close()
|
||||
retString = ShellUtils.execRootCmdAndGetResult(cmd)
|
||||
// //创建socket
|
||||
// val myCmd = "SU_1|$cmd"
|
||||
//
|
||||
// val mSocket = Socket()
|
||||
// val inetSocketAddress = InetSocketAddress("127.0.0.1", 12345)
|
||||
// mSocket.connect(inetSocketAddress)
|
||||
// val bufferedWriter = BufferedWriter(OutputStreamWriter(mSocket.getOutputStream()))
|
||||
// val bufferedReader = BufferedReader(InputStreamReader(mSocket.getInputStream()))
|
||||
// bufferedWriter.write(myCmd + "\r\n")
|
||||
// bufferedWriter.flush()
|
||||
// val stringBuilder = StringBuilder()
|
||||
// var line: String? = null
|
||||
// while ((bufferedReader.readLine().also { line = it }) != null) {
|
||||
// stringBuilder.append(line + "\n")
|
||||
// }
|
||||
// retString = stringBuilder.toString()
|
||||
// //retString = bufferedReader.readLine();
|
||||
// bufferedReader.close()
|
||||
// bufferedWriter.close()
|
||||
} catch (eeeee: Exception) {
|
||||
eeeee.printStackTrace()
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import android.content.pm.PackageManager
|
|||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.provider.Settings
|
||||
import android.util.Log
|
||||
import androidx.activity.result.ActivityResultLauncher
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.annotation.RequiresApi
|
||||
|
@ -106,7 +107,6 @@ class NotificationPermissionHandler(
|
|||
fun requestNotificationPermission() {
|
||||
// 保存当前权限状态,用于比较设置后是否发生变化
|
||||
lastPermissionState = areNotificationsEnabled()
|
||||
|
||||
when {
|
||||
// Android 13+ - 使用运行时权限请求
|
||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU -> {
|
||||
|
@ -198,19 +198,18 @@ class NotificationPermissionHandler(
|
|||
}
|
||||
|
||||
val activity = context as? AppCompatActivity ?: return
|
||||
|
||||
when {
|
||||
// 已经拥有权限
|
||||
areNotificationsEnabled() -> {
|
||||
callback(PermissionResult.GRANTED)
|
||||
}
|
||||
// 需要显示权限解释
|
||||
ActivityCompat.shouldShowRequestPermissionRationale(
|
||||
activity,
|
||||
Manifest.permission.POST_NOTIFICATIONS
|
||||
) -> {
|
||||
showPermissionRationaleDialog()
|
||||
}
|
||||
// ActivityCompat.shouldShowRequestPermissionRationale(
|
||||
// activity,
|
||||
// Manifest.permission.POST_NOTIFICATIONS
|
||||
// ) -> {
|
||||
// showPermissionRationaleDialog()
|
||||
// }
|
||||
// 请求权限
|
||||
else -> {
|
||||
permissionRequestPending = true
|
||||
|
@ -276,6 +275,7 @@ class NotificationPermissionHandler(
|
|||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "NotificationPermissionH"
|
||||
/**
|
||||
* 快速检查当前设备的通知权限状态
|
||||
*/
|
||||
|
|
|
@ -103,31 +103,22 @@ public class ShellUtils {
|
|||
|
||||
public static String execRootCmdAndGetResult(String 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)) { // 检查命令的合法性
|
||||
// Log.e("ShellUtils", "Detected unsafe command. Aborting execution.");
|
||||
// throw new IllegalArgumentException("Detected unsafe command.");
|
||||
// }
|
||||
// if (!isCommandSafe(cmd)) { // 检查命令的合法性
|
||||
// Log.e("ShellUtils", "Detected unsafe command. Aborting execution.");
|
||||
// throw new IllegalArgumentException("Detected unsafe command.");
|
||||
// }
|
||||
|
||||
Process process = null;
|
||||
ExecutorService executor = Executors.newFixedThreadPool(2);
|
||||
|
||||
try {
|
||||
Log.d("ShellUtils", "Determining appropriate shell for execution...");
|
||||
if (hasBin("su")) {
|
||||
Log.d("ShellUtils", "'su' binary found, using 'su' shell.");
|
||||
process = Runtime.getRuntime().exec("su");
|
||||
} else if (hasBin("xu")) {
|
||||
Log.d("ShellUtils", "'xu' binary found, using 'xu' shell.");
|
||||
process = Runtime.getRuntime().exec("xu");
|
||||
} else if (hasBin("vu")) {
|
||||
Log.d("ShellUtils", "'vu' binary found, using 'vu' shell.");
|
||||
process = Runtime.getRuntime().exec("vu");
|
||||
} else {
|
||||
Log.d("ShellUtils", "No specific binary found, using 'sh' shell.");
|
||||
process = Runtime.getRuntime().exec("sh");
|
||||
}
|
||||
|
||||
|
@ -137,7 +128,6 @@ public class ShellUtils {
|
|||
BufferedReader br = new BufferedReader(new InputStreamReader(is, 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(() -> {
|
||||
String line;
|
||||
try {
|
||||
|
@ -149,28 +139,21 @@ public class ShellUtils {
|
|||
}
|
||||
});
|
||||
|
||||
Log.d("ShellUtils", "Writing the command to the shell...");
|
||||
os.write((cmd + "\n").getBytes());
|
||||
os.write("exit\n".getBytes());
|
||||
os.flush();
|
||||
Log.d("ShellUtils", "Command written to shell. Waiting for process to complete.");
|
||||
|
||||
StringBuilder output = new StringBuilder();
|
||||
String line;
|
||||
while ((line = br.readLine()) != null) {
|
||||
Log.d("ShellUtils", "Shell Output: " + line);
|
||||
output.append(line).append("\n");
|
||||
}
|
||||
|
||||
Log.d("ShellUtils", "Awaiting process termination...");
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
if (!process.waitFor(10, TimeUnit.SECONDS)) {
|
||||
LogUtils.e(Log.ERROR, "ShellUtils", "Process execution timed out. Destroying process.", null);
|
||||
process.destroyForcibly();
|
||||
throw new RuntimeException("Shell command execution timeout.");
|
||||
}
|
||||
} else {
|
||||
Log.d("ShellUtils", "Using manual time tracking method for process termination (API < 26).");
|
||||
long startTime = System.currentTimeMillis();
|
||||
while (true) {
|
||||
try {
|
||||
|
@ -178,7 +161,6 @@ public class ShellUtils {
|
|||
break;
|
||||
} catch (IllegalThreadStateException e) {
|
||||
if (System.currentTimeMillis() - startTime > 10000) { // 10 seconds
|
||||
LogUtils.e(Log.ERROR, "ShellUtils", "Process execution timed out (manual tracking). Destroying process.", null);
|
||||
process.destroy();
|
||||
throw new RuntimeException("Shell command execution timeout.");
|
||||
}
|
||||
|
@ -186,8 +168,6 @@ public class ShellUtils {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
Log.d("ShellUtils", "Process terminated successfully. Returning result.");
|
||||
return output.toString().trim();
|
||||
}
|
||||
} catch (IOException | InterruptedException e) {
|
||||
|
@ -196,7 +176,6 @@ public class ShellUtils {
|
|||
return "Error: " + e.getMessage();
|
||||
} finally {
|
||||
if (process != null) {
|
||||
Log.d("ShellUtils", "Finalizing process. Attempting to destroy it.");
|
||||
process.destroy();
|
||||
}
|
||||
executor.shutdown();
|
||||
|
@ -206,10 +185,9 @@ public class ShellUtils {
|
|||
|
||||
public static void execRootCmd(String cmd) {
|
||||
// 校验命令是否安全
|
||||
if (!isCommandSafe(cmd)) {
|
||||
LogUtils.e(Log.ERROR, "ShellUtils", "Unsafe command, aborting.", null);
|
||||
return;
|
||||
}
|
||||
// if (!isCommandSafe(cmd)) {
|
||||
// return;
|
||||
// }
|
||||
List<String> cmds = new ArrayList<>();
|
||||
cmds.add(cmd);
|
||||
|
||||
|
|
|
@ -0,0 +1,75 @@
|
|||
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
|
||||
var isNeedRestored: Boolean = false
|
||||
const val AUTO_PACKAGENAME: String = "com.play4u.luabox"
|
||||
const val AUTO_JSPACKAGENAME: String = "org.autojs.autojs"
|
||||
const val AUTO_JSPACKAGENAME: String = "org.autojs.autojs6"
|
||||
const val proxy_packagename: String = "com.tunnelworkshop.postern"
|
||||
const val AUTO_CLASSNAME: String = "com.cyjh.elfin.activity.SplashActivity"
|
||||
private const val hookPackageName = "com.affsystem.androidhooker"
|
||||
|
@ -275,10 +275,7 @@ object Util {
|
|||
}
|
||||
|
||||
fun isInstallRet(): Boolean {
|
||||
if (installRet == null) {
|
||||
return false
|
||||
}
|
||||
return installRet!!
|
||||
return installRet?:false
|
||||
}
|
||||
|
||||
fun setAfLog(afLogV: String) {
|
||||
|
@ -699,8 +696,8 @@ object Util {
|
|||
val result: String? = MyPost.postData(" ".toByteArray(charset = Charsets.UTF_8),
|
||||
"$url?$params"
|
||||
)
|
||||
|
||||
if (result != null && result.length > 0) {
|
||||
LogUtils.e("IOSTQ:execReloginTask->result:$result")
|
||||
if (result != null && result.isNotEmpty()) {
|
||||
taskJson = JSONObject(result).apply {
|
||||
val code = getInt("code")
|
||||
if (code == 1) {
|
||||
|
@ -752,9 +749,9 @@ object Util {
|
|||
"$url?$params"
|
||||
)
|
||||
|
||||
printStr("request result : $result")
|
||||
LogUtils.e("request result : $result")
|
||||
|
||||
if (result != null && result.length > 0) {
|
||||
if (result != null && result.isNotEmpty()) {
|
||||
taskJson = JSONObject(result)
|
||||
|
||||
val code = taskJson!!.getInt("code")
|
||||
|
@ -765,7 +762,7 @@ object Util {
|
|||
execSetJson(context)
|
||||
clickTime = 1
|
||||
} else {
|
||||
LogUtils.i(TAG, "request result code invalid : "+code);
|
||||
LogUtils.i(TAG, "request result code invalid : $code");
|
||||
setFinish(context)
|
||||
}
|
||||
} else {
|
||||
|
@ -788,7 +785,6 @@ object Util {
|
|||
nRandom++
|
||||
|
||||
if (nRandom % 3 == 0) {
|
||||
// execInstallTask(context);
|
||||
execReloginTask(context)
|
||||
} else {
|
||||
execInstallTask(context)
|
||||
|
@ -949,8 +945,6 @@ object Util {
|
|||
videoProxy = extJo.getString("videoProxy")
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (extJo.has("proxy") && !extJo.isNull("proxy")) {
|
||||
val proxyJo = extJo.getJSONObject("proxy")
|
||||
proxyIp = proxyJo.getString("proxyIp")
|
||||
|
@ -1048,7 +1042,7 @@ object Util {
|
|||
val ret: String? = MyPost.postData("".toByteArray(), url)
|
||||
Log.i(TAG, "ret:$ret")
|
||||
|
||||
val jo = JSONObject(ret)
|
||||
val jo = JSONObject(ret?:"")
|
||||
|
||||
if (jo.getInt("code") == 1) {
|
||||
regEmailJson = jo.getJSONObject("emailInfo")
|
||||
|
@ -1146,7 +1140,7 @@ object Util {
|
|||
return false
|
||||
}
|
||||
|
||||
unzipAPkSh(script_path, "/sdcard/autojs/")
|
||||
unzipAPkSh(script_path, "/sdcard/script/")
|
||||
delFileSh(script_path)
|
||||
}
|
||||
return isDownload
|
||||
|
@ -1557,11 +1551,13 @@ object Util {
|
|||
|
||||
fun openRecordApp(context: Context) {
|
||||
if (scriptOpenApp == 0) {
|
||||
recordPackageName?.let {
|
||||
execTargetApp(
|
||||
context,
|
||||
recordPackageName!!
|
||||
it
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (!isCanAuto || canAutoLc.length <= 10) {
|
||||
Handler(Looper.getMainLooper()).postDelayed(Runnable {
|
||||
|
@ -1621,10 +1617,12 @@ object Util {
|
|||
if (!file.exists()) {
|
||||
val parent = file.parentFile
|
||||
|
||||
if (parent!!.exists()) {
|
||||
if (parent?.exists() == true) {
|
||||
file.mkdir()
|
||||
} else {
|
||||
if (parent != null) {
|
||||
forceCreteDir(parent)
|
||||
}
|
||||
file.mkdir()
|
||||
}
|
||||
}
|
||||
|
@ -1634,7 +1632,8 @@ object Util {
|
|||
if (!file.exists()) {
|
||||
val parent = file.parentFile
|
||||
|
||||
if (!parent!!.exists()) {
|
||||
parent?.exists()?.let {
|
||||
if (!it) {
|
||||
MockTools.exec("mkdir $parent")
|
||||
forceMakeDir(parent)
|
||||
// file.mkdirs();
|
||||
|
@ -1643,6 +1642,7 @@ object Util {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun writeFile1(context: Context?, dirName: String, fileName: String, contents: String) {
|
||||
val fs = File("$dirName/$fileName")
|
||||
|
@ -1767,7 +1767,11 @@ object Util {
|
|||
}
|
||||
|
||||
val create_dir = file.parentFile
|
||||
forceMakeDir(create_dir!!)
|
||||
if (create_dir != null && !create_dir.exists()){
|
||||
forceMakeDir(create_dir)
|
||||
}else if (create_dir == null){
|
||||
return false
|
||||
}
|
||||
|
||||
val fileLength = 0L
|
||||
|
||||
|
@ -1780,7 +1784,7 @@ object Util {
|
|||
|
||||
try {
|
||||
conn = url.openConnection() as HttpURLConnection
|
||||
`is` = conn!!.inputStream
|
||||
`is` = conn.inputStream
|
||||
fos = FileOutputStream(file)
|
||||
val buf = ByteArray(256)
|
||||
conn.connect()
|
||||
|
@ -1809,15 +1813,15 @@ object Util {
|
|||
e.printStackTrace()
|
||||
} finally {
|
||||
try {
|
||||
conn!!.disconnect()
|
||||
conn?.disconnect()
|
||||
} catch (e: Exception) {
|
||||
}
|
||||
try {
|
||||
fos!!.close()
|
||||
fos?.close()
|
||||
} catch (e: Exception) {
|
||||
}
|
||||
try {
|
||||
`is`!!.close()
|
||||
`is`?.close()
|
||||
} catch (e: Exception) {
|
||||
}
|
||||
}
|
||||
|
@ -2049,17 +2053,19 @@ object Util {
|
|||
}
|
||||
|
||||
|
||||
fun installApks4Tmp(apkName: String?, context: Context?): Boolean {
|
||||
fun installApks4Tmp(apkName: String?, context: Context): Boolean {
|
||||
var result = MockTools.execRead("pm install-create")
|
||||
Log.d(TAG, "installApks4Tmp: successMsg $result")
|
||||
val session = result.substring(result.indexOf("[") + 1, result.indexOf("]"))
|
||||
Log.d(TAG, "installApks4Tmp: session $session")
|
||||
|
||||
val file = File("/sdcard/apks/$apkName")
|
||||
val file = File(getRecordDataDirName(context))
|
||||
Log.d(TAG, "installApks4Tmp: ${file.absolutePath}")
|
||||
val files = file.listFiles()
|
||||
var bool = true
|
||||
var currentApkFile = 1
|
||||
for (f in files) {
|
||||
files?.let {
|
||||
for (f in it) {
|
||||
val extraName = f.name.substring(f.name.lastIndexOf('.') + 1)
|
||||
Log.d(
|
||||
TAG,
|
||||
|
@ -2078,6 +2084,8 @@ object Util {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (bool) {
|
||||
result = MockTools.execRead("pm install-commit $session")
|
||||
if (!result.contains("Success")) {
|
||||
|
@ -2086,9 +2094,11 @@ object Util {
|
|||
} else {
|
||||
result = MockTools.execRead("pm install-abandon $session")
|
||||
}
|
||||
for (f in files) {
|
||||
files?.let {
|
||||
for (f in it) {
|
||||
MockTools.execRead("rm -rf $f")
|
||||
}
|
||||
}
|
||||
setInstallRet(bool)
|
||||
return bool
|
||||
}
|
||||
|
@ -2423,7 +2433,7 @@ object Util {
|
|||
}
|
||||
tags = filterStr(tags)
|
||||
// Log.e(TAG, "getCid:*"+tags+"*");
|
||||
return tags!!
|
||||
return tags?:""
|
||||
}
|
||||
|
||||
fun getPropertiesFromAssets(context: Context, fileName: String): Properties {
|
||||
|
@ -2561,9 +2571,9 @@ object Util {
|
|||
*/
|
||||
//当本应用位于后台时,则将它切换到最前端
|
||||
fun setTopApp(context: Context) {
|
||||
// Log.i(TAG, "start to setTopApp");
|
||||
Log.i(TAG, "start to setTopApp");
|
||||
if (isRunningForeground(context)) {
|
||||
// Log.i(TAG, "app isRunningForeground");
|
||||
Log.i(TAG, "app isRunningForeground");
|
||||
return;
|
||||
}
|
||||
//获取ActivityManager
|
||||
|
|
|
@ -9,6 +9,8 @@ import android.text.TextUtils
|
|||
import androidx.work.CoroutineWorker
|
||||
import androidx.work.WorkerParameters
|
||||
import com.android.grape.service.MyAccessibilityService
|
||||
import androidx.core.content.edit
|
||||
import com.android.grape.job.MonitorService
|
||||
|
||||
class CheckAccessibilityWorker(
|
||||
context: Context,
|
||||
|
@ -32,7 +34,7 @@ class CheckAccessibilityWorker(
|
|||
applicationContext.startActivity(intent)
|
||||
|
||||
// 更新状态
|
||||
sharedPreferences.edit().putBoolean("accessibility_prompted", true).apply()
|
||||
sharedPreferences.edit { putBoolean("accessibility_prompted", true) }
|
||||
}
|
||||
return Result.retry()
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue