Compare commits

...

36 Commits

Author SHA1 Message Date
yjj38 d2d95ada82 refactor(app): 优化代码结构和可读性
- 格式化代码,调整缩进和空格
- 添加方法注释,解释主要功能
- 修改变量命名,提高可读性
- 删除冗余代码和不必要的注释
2025-07-11 15:29:27 +08:00
yjj38 c3467add0e refactor(device): 重构 ArmCloudApiClient 类
- 格式化代码,提高可读性
- 优化 JSON 构建和解析逻辑
- 统一异常处理方式
- 简化部分代码结构,提高维护性
2025-07-11 10:59:48 +08:00
yjj38 237f07f968 fix(app): 修改设备加载任务执行周期
- 将 LoadDeviceWorker 的执行周期从 30秒改为 30 分钟- 优化了后台任务的执行频率,减少不必要的资源消耗
2025-07-10 18:01:39 +08:00
yjj38 7b93852e89 refactor(app): 修改设备加载工作器的执行周期
- 将 LoadDeviceWorker 的执行周期从30 分钟改为 30 秒
- 删除了 .idea/misc.xml 文件中的 XML 声明
2025-07-10 17:29:40 +08:00
yjj38 14648729aa Merge branch 'Retention' of http://47.108.156.251:3000/yjj/agent-bigo into Retention 2025-07-10 17:13:55 +08:00
yjj38 d7774c2ac6 feat(app): 优化应用初始化和权限请求逻辑
- 重构了 MainActivity 中的 onCreate 方法,优化了应用初始化流程
- 添加了请求存储权限的独立方法 requestStoragePermission
- 实现了只在工作不存在时调度周期性工作的逻辑 schedulePeriodicWorkIfNotExists
- 优化了按钮点击事件的处理,提高了代码可读性和健壮性
- 新增了部署配置文件 deployment.xml,用于腾讯云服务器配置
2025-07-10 17:13:42 +08:00
Administrator ebe5f7df78 packageName空判断 2025-07-10 17:10:53 +08:00
Administrator 076240c559 Merge remote-tracking branch 'origin/Retention' into Retention 2025-07-10 16:34:33 +08:00
yjj38 9acc35fa7b refactor(app): 限制 scriptResultQueue 的容量
- 将 scriptResultQueue 的容量从无限制改为 1
- 此修改旨在防止队列无限增长可能导致的内存问题
2025-07-10 15:02:21 +08:00
yjj38 faa865cb34 build: 添加 ProGuard 规则以保留必要类和方法
- 保留 ArmCloudApiClient 及其公共方法和构造函数
-保留 PropertyItem 内部类及其字段、构造函数和 toJson 方法- 保留枚举类及其 values 和 valueOf 方法
- 保留使用 @SerializedName 注解的字段- 可选地保留注解信息
2025-07-10 14:30:13 +08:00
yjj38 64449de581 chore: 删除 ProGuard 配置文件
删除了 app/proguard-rules.pro 文件,该文件为空且不包含任何有用的配置信息。这个文件的存在可能是由于历史原因或开发过程中的临时需要,但目前项目中已不再需要它。
2025-07-10 14:07:21 +08:00
Administrator 67ac7b8d94 2025-07-10 14:07:11 +08:00
yjj38 175db8f49b refactor(device): 重构设备列表查询接口并优化日志输出- 重写 getDeviceCodes 方法,改为 getInstanceListInfo,优化参数设计
- 移除未使用的 getDeviceInfo 方法
- 在关键步骤添加调试日志输出,便于问题排查
- 更新网络配置,添加新的服务器 IP
2025-07-10 11:41:50 +08:00
yjj38 247db8b28e refactor(MainActivity): 移除获取 AndroidId 的硬编码方法
- 删除了 `getAndroidId` 方法,该方法返回一个硬编码的字符串。
2025-07-09 21:18:16 +08:00
yjj38 4e06a78ac7 refactor(MainActivity): 移除获取 AndroidId 的硬编码方法
- 删除了 `getAndroidId` 方法,该方法返回一个硬编码的字符串。
2025-07-09 21:17:04 +08:00
yjj38 84db43d571 feat(proxy): 新增国家代码切换功能并引入OkHttp依赖
- 新增 `CountryCode` 类,用于管理和切换国家代码(目前支持US和RU)。
- 在 `LoadDeviceWorker` 和 `MainActivity` 中引入 `CountryCode` 以实现国家切换。
- `MainActivity` 中的 `startProxyVpn` 方法现在使用 `CountryCode.switchCountry()` 来获取当前国家代码。
- `ClashUtil` 中引入 `okhttp3.logging.HttpLoggingInterceptor`。
- 在 `app/build.gradle` 中添加 `okhttp` 和 `logging-interceptor` 依赖。
- 修改了 `MainActivity` 中 `startProxyVpn` 失败时的日志记录标签。
2025-07-09 21:15:17 +08:00
yjj38 d35aa11cdd refactor(proxy): 添加按端口切换代理功能并优化日志记录
- 新增 `getProxyPort` 方法,用于从 `ip.port.json` 文件中读取代理端口。
- 新增 `switchProxyWithPort` 方法,用于根据国家和端口号调用服务器接口切换代理。
- `switchProxyGroup` 方法中的网络请求改为同步执行,并增加了对响应体是否为空的判断。
- 优化了 OkHttpClient 实例的创建,使用共享的 `sharedClient` 并添加了连接和读取超时以及日志拦截器。
- `LoadDeviceWorker` 中 `startProxyVpn` 方法的日志标签从 `TAG` 改为 `MainActivity`。
2025-07-09 21:06:41 +08:00
yjj38 2b60f10351 refactor(proxy): 切换代理逻辑调整
- 将 `ClashUtil.switchProxyGroup` 调用替换为 `ClashUtil.switchProxyWithPort(CountryCode.switchCountry())`
- 移除了 `MainActivity` 中的 `executeSingleLogic` 方法,相关逻辑已在 `LoadDeviceWorker` 中处理
- 在 `LoadDeviceWorker` 和 `MainActivity` 的 `startProxyVpn` 方法中,使用 `TAG` 记录日志,并更新了代理切换逻辑。
2025-07-09 21:04:28 +08:00
yjj38 20eaac8e12 Refactor: 更新实例属性时获取更多设备码
将 `client.getDeviceCodes` 方法中获取设备码的数量从 1 调整为 100,以便在更新实例属性时有更多可用的设备码。
2025-07-09 20:50:32 +08:00
yjj38 c1cdbfeffc refactor(device): 重构设备信息修改和ARM云API客户端逻辑
- `ArmCloudApiClient`:移除构造函数中的 `baseUrl`、`accessKey` 和 `secretKey` 参数,将这些值硬编码到类中。
- `ChangeDeviceInfoUtil`:
    - `changeDeviceInfo` 方法:移除 `padCodes` 参数,改为在方法内部通过 `armClient.getDeviceCodes` 获取。
- `LoadDeviceWorker`:
    - `executeSingleLogic` 方法:在调用 `ChangeDeviceInfoUtil.changeDeviceInfo` 时传递 `MainActivity.armClient`。
- `MainActivity`:
    - 新增 `armClient` 静态成员变量,并在 `onCreate` 中初始化 `ArmCloudApiClient` 实例。
    - 修改 `modifyDeviceInfoButton` 的点击事件,在调用 `ChangeDeviceInfoUtil.changeDeviceInfo` 时传递 `armClient`。
    - `executeSingleLogic` 方法:在调用 `ChangeDeviceInfoUtil.changeDeviceInfo` 时传递 `armClient`。
    - 移除了 `getAndroidId(Context context)` 方法中未使用的代码。
    - 简化了 `onCreate` 方法中的按钮初始化逻辑,引入了 `setupButton` 辅助方法。
    - 引入了 `logInfo`, `logError`, `logWarn`, `showToast` 等辅助方法以简化日志记录和UI提示。
    - 移除了未使用的 `instance` 静态成员变量和 `getInstance()` 方法。
    - 移除了 `executeLogic` 方法和相关的 `isRunning`、`taskLock` 成员变量,相关逻辑已移至 `LoadDeviceWorker`。
    - 调整了 `onDestroy` 方法的逻辑。
2025-07-09 20:27:48 +08:00
yjj38 f0339e7251 feat(ArmCloudApiClient): 新增设备列表查询功能并优化属性更新接口
- 新增 `getDeviceCodes` 方法,用于分页查询ARM设备列表并提取 `deviceCode`。该方法支持多种筛选条件,如实例分配状态、物理机状态、服务器编码等。
- 优化 `updateInstanceProperties` 方法:
    - 增加对 `padCodes` 参数重复项的校验。
    - 将 `padCodes` 的类型从 `JSONArray` 更改为 `new JSONArray(Arrays.asList(padCodes))`,以正确处理数组。
    - 完善了错误处理逻辑,对JSON构建、签名计算、接口返回错误码和响应解析失败等情况进行了更详细的日志记录和异常抛出。
    - 在接口请求成功后,增加了对响应体中 `code` 字段的校验,确保接口调用成功。
    - 将 `baseUrl` 设置为常量。
2025-07-09 19:38:31 +08:00
yjj38 6b507223b3 refactor(device): 重构设备信息修改逻辑并集成ArmCloud API
- 将设备信息修改的硬编码字符串替换为常量。
- `changeDeviceInfo` 方法现在接收 `ArmCloudApiClient` 和 `padCodes` 作为参数。
- 优化了Bigo和AF设备信息的处理逻辑,当对应的 `bigoDeviceObject` 或 `afDeviceObject` 为空时,会跳过相关设置并记录警告。
- 对于AF设备信息的系统属性修改,现在使用 `ArmCloudApiClient` 的 `updateInstanceProperties` 方法进行更新,替代了原有的 `ShellUtils.execRootCmd` 调用。
- 新增 `addProperty` 辅助方法,用于向 `List<PropertyItem>` 中添加非空属性。
- 新增 `execRootCmdIfNotEmpty` 辅助方法,用于执行非空的Shell命令并记录结果。
- 移除了直接通过 `ShellUtils.execRootCmd` 修改系统属性的代码,例如 `setprop ro.product.brand` 等。
- 确保在发生错误时抛出原始异常类型,而不是统一包装成 `RuntimeException`。
2025-07-09 18:36:26 +08:00
yjj38 2a7132d7b0 refactor(retention): 重构项目并添加新功能
- 更新包名从 com.example.studyapp 到 com.example.retention
- 添加 ArmCloudApiClient 类实现设备属性更新功能
- 更新所有相关类和文件以适应新的包名
2025-07-09 17:04:18 +08:00
yjj38 7cf941af6e feat(release): 新增签名配置并更新构建类型
- 新增 release 签名配置,使用 agent_retention.jks 密钥库
- 更新 release 构建类型,启用签名配置和代码混淆
- 移除旧的密钥库文件 (agentkey.jks, new-release-key.jks, your-release-key.jks)
- 更新 .idea/misc.xml 文件
2025-07-04 11:17:03 +08:00
yjj38 c999ee6f69 refactor(proxy): 移除代理检查国家功能并优化 VPN 启动逻辑
- 删除了 ClashUtil 类中的 checkCountryIsUS 方法,移除了检查国家是否为美国的功能
- 修改了 LoadDeviceWorker 类中的 startProxyVpn 方法,去除了返回值,简化了逻辑- 优化了 executeSingleLogic 方法的流程,移除了与代理检查国家相关的代码
2025-07-03 17:25:34 +08:00
yjj38 0ac201e93b refactor(main): 重构主函数并添加日志记录功能
- 新增 log 和 error 函数用于日志记录
- 重新封装 HTTP 请求和 API 处理逻辑
- 主函数增加错误处理和任务完成检查
- 添加定时器实现循环执行
2025-07-03 16:32:34 +08:00
yjj38 431267228f JDK升级到21,优化ClashUtil的代理切换和网络检查逻辑
- 项目的JDK版本从17升级到21。
- ClashUtil的`switchProxyGroup`方法改为同步执行,并增加了对HTTP响应状态码的检查。
- 新增`checkCountryIsUS`方法,用于通过ipinfo.io判断当前IP是否在美国,并在VPN启动后调用此方法进行验证。
- `LoadDeviceWorker`中的`startProxyVpn`方法现在会根据`checkCountryIsUS`的结果来决定是否继续执行后续操作。
- AutoJs脚本 (`main.js`) 更新,使用Promise和async/await来处理并行的HTTP请求,并分别调用ipv4.geojs.io的接口获取国家代码和详细地理位置信息。
2025-07-03 13:17:14 +08:00
Administrator 619e39cdc6 打包配置 2025-06-28 14:50:24 +08:00
Administrator d8dd6e740a 添加取消定时任务的方法 2025-06-26 18:18:37 +08:00
Administrator 203fccea87 下载安装 2025-06-26 16:51:31 +08:00
Administrator e619d85361 2025-06-26 14:02:58 +08:00
Administrator 6fa7c334ea 2025-06-25 19:06:03 +08:00
Administrator 951f6697e8 2025-06-25 19:02:51 +08:00
Administrator 4642f6e459 . 2025-06-25 18:09:58 +08:00
Administrator d21136edf5 . 2025-06-25 15:21:03 +08:00
Administrator a6c58716ec . 2025-06-25 13:59:07 +08:00
59 changed files with 2142 additions and 1261 deletions

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="17" />
<bytecodeTargetLevel target="21" />
</component>
</project>

View File

@ -24,7 +24,7 @@
</component>
<component name="DBNavigator.Project.DatabaseConsoleManager">
<connection id="9e7d43d6-9c6a-47fa-b0b1-b14b18fadac4">
<console name="Connection" type="STANDARD" schema="" session="Main" />
<console name="Connection" type="STANDARD" schema="gin_demo" session="Main" />
</connection>
</component>
<component name="DBNavigator.Project.DatabaseEditorStateManager">
@ -36,6 +36,9 @@
<component name="DBNavigator.Project.DatabaseSessionManager">
<connection id="9e7d43d6-9c6a-47fa-b0b1-b14b18fadac4" />
</component>
<component name="DBNavigator.Project.ExecutionManager">
<retain-sticky-names value="false" />
</component>
<component name="DBNavigator.Project.ObjectQuickFilterManager">
<last-used-operator value="EQUAL" />
<filters />

14
.idea/deployment.xml Normal file
View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="PublishConfigData" remoteFilesAllowedToDisappearOnAutoupload="false">
<serverData>
<paths name="腾讯云">
<serverdata>
<mappings>
<mapping local="$PROJECT_DIR$" web="/" />
</mappings>
</serverdata>
</paths>
</serverData>
</component>
</project>

View File

@ -4,10 +4,10 @@
<selectionStates>
<SelectionState runConfigName="app">
<option name="selectionMode" value="DROPDOWN" />
<DropdownSelection timestamp="2025-06-23T04:03:54.535123700Z">
<DropdownSelection timestamp="2025-06-26T06:59:46.673021800Z">
<Target type="DEFAULT_BOOT">
<handle>
<DeviceId pluginId="PhysicalDevice" identifier="serial=LMV500N03f5c1fc" />
<DeviceId pluginId="Default" identifier="serial=8.217.74.194:1137;connection=a396d877" />
</handle>
</Target>
</DropdownSelection>

View File

@ -1,7 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="jbr-17" project-jdk-type="JavaSDK">
<component name="FrameworkDetectionExcludesConfiguration">
<file type="web" url="file://$PROJECT_DIR$" />
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="21" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">

Binary file not shown.

BIN
app/agent_retention.jks Normal file

Binary file not shown.

View File

@ -3,11 +3,20 @@ plugins {
}
android {
namespace 'com.example.studyapp'
namespace 'com.example.retention'
compileSdk 35
signingConfigs {
release {
storeFile file('agent_retention.jks')
storePassword 'agent_retention'
keyAlias 'agent_retention'
keyPassword 'agent_retention'
}
}
defaultConfig {
applicationId "com.example.studyapp"
applicationId "com.example.retention"
minSdk 24
targetSdk 35
versionCode 1
@ -30,7 +39,8 @@ android {
buildTypes {
release {
minifyEnabled false
signingConfig signingConfigs.release
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
debug {
@ -52,6 +62,14 @@ android {
path file("src/main/cpp/CMakeLists.txt")
}
}
applicationVariants.all { variant ->
variant.outputs.all { output ->
def versionName = variant.versionName ?: "1.0"
def versionCode = variant.versionCode ?: 1
output.outputFileName = "app-${variant.applicationId}-v${versionName}-${versionCode}.apk"
}
}
}
dependencies {
@ -85,4 +103,7 @@ dependencies {
testImplementation 'junit:junit:4.13.2'
testImplementation 'org.mockito:mockito-inline:4.8.0'
implementation 'com.squareup.okhttp3:okhttp:4.12.0'
implementation 'com.squareup.okhttp3:logging-interceptor:4.12.0'
}

View File

@ -1,21 +1,27 @@
# 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
# 保留 ArmCloudApiClient 及其所有公共方法和构造函数
-keep class com.example.retention.device.ArmCloudApiClient {
public <init>();
public *;
}
# 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 *;
#}
# 保留 PropertyItem 内部类及其字段构造函数和 toJson 方法
-keep class com.example.retention.device.ArmCloudApiClient$PropertyItem {
private java.lang.String propertiesName;
private java.lang.String propertiesValue;
public <init>(java.lang.String, java.lang.String);
public org.json.JSONObject toJson();
}
# 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
# 保留使用 @SerializedName 注解的字段
-keepclassmembers class * {
@com.google.gson.annotations.SerializedName <fields>;
}
# 保留注解信息(根据需要启用)
# -keepattributes *Annotation*

Binary file not shown.

View File

@ -4,7 +4,7 @@
"type": "APK",
"kind": "Directory"
},
"applicationId": "com.example.studyapp",
"applicationId": "com.example.retention",
"variantName": "release",
"elements": [
{
@ -13,7 +13,7 @@
"attributes": [],
"versionCode": 1,
"versionName": "1.0",
"outputFile": "app-release.apk"
"outputFile": "app-com.example.retention-v1.0-1.apk"
}
],
"elementType": "File",
@ -22,14 +22,14 @@
"minApi": 28,
"maxApi": 30,
"baselineProfiles": [
"baselineProfiles/1/app-release.dm"
"baselineProfiles/1/app-com.example.retention-v1.0-1.dm"
]
},
{
"minApi": 31,
"maxApi": 2147483647,
"baselineProfiles": [
"baselineProfiles/0/app-release.dm"
"baselineProfiles/0/app-com.example.retention-v1.0-1.dm"
]
}
],

View File

@ -1,4 +1,4 @@
package com.example.studyapp;
package com.example.retention;
import android.content.Context;
@ -21,6 +21,6 @@ public class ExampleInstrumentedTest {
public void useAppContext() {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
assertEquals("com.example.studyapp", appContext.getPackageName());
assertEquals("com.example.retention", appContext.getPackageName());
}
}

View File

@ -39,13 +39,13 @@
android:usesCleartextTraffic="true"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/box"
android:label="Script helper"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.StudyApp"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:name="com.example.retention.MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

View File

@ -0,0 +1,343 @@
package com.example.retention;
import static com.example.retention.task.TaskUtil.infoUpload;
import static com.example.retention.utils.Utils.isNetworkAvailable;
import android.app.Activity;
import android.app.AlertDialog;
import android.net.Uri;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
import android.Manifest;
import android.content.pm.PackageManager;
import android.os.Environment;
import androidx.annotation.Nullable;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.appcompat.app.AppCompatActivity;
import androidx.work.ExistingPeriodicWorkPolicy;
import androidx.work.PeriodicWorkRequest;
import androidx.work.WorkManager;
import com.example.retention.R;
import com.example.retention.autoJS.AutoJsUtil;
import com.example.retention.config.CountryCode;
import com.example.retention.device.ArmCloudApiClient;
import com.example.retention.device.ChangeDeviceInfoUtil;
import com.example.retention.proxy.ClashUtil;
import com.example.retention.service.MyAccessibilityService;
import com.example.retention.task.TaskUtil;
import com.example.retention.utils.LogFileUtil;
import com.example.retention.utils.ShellUtils;
import com.example.retention.worker.CheckAccessibilityWorker;
import com.example.retention.worker.LoadDeviceWorker;
import java.lang.ref.WeakReference;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class MainActivity extends AppCompatActivity {
private static final int REQUEST_CODE_STORAGE_PERMISSION = 1;
private static final int ALLOW_ALL_FILES_ACCESS_PERMISSION_CODE = 1001;
public static ExecutorService executorService;
public static ArmCloudApiClient armClient;
// 假设我们从配置文件中提取出了以下 name 项数据仅为部分示例数据
private final String[] proxyNames = {
"mr", "sr", "bq", "ml", "ht", "ga", "mk", "by", "pr", "hr", "hu",
"in", "gt", "at", "kh", "bn", "mg", "kr", "ca", "gh", "ma", "md",
"je", "pa", "ba", "mm", "ir", "gy", "mt", "ae", "es", "ng", "ls",
"ag", "pk", "bd", "kn", "mw", "ve", "hk", "cv", "hn", "tm", "us",
"cz", "ly", "gb", "kz", "it", "bh", "sn", "fi", "co", "sx", "bm",
"fj", "cw", "st", "bw", "fr", "bb", "tg", "ci", "gd", "ne", "bj",
"nz", "rs", "do", "cl", "lb", "nl", "re", "aw", "ug", "sv", "ar",
"jo", "bg", "jp", "rw", "py", "mn", "ec", "uz", "ro", "cu", "gu",
"xk", "sy", "so", "zm", "tz", "ni", "sc", "my", "gf", "na", "zw",
"la", "et", "ao", "ua", "om", "np", "mx", "mz", "dm", "ye", "gi",
"cr", "cm", "ph", "am", "th", "ch", "br", "sd", "ie", "bo", "bs",
"tc", "vg", "pe", "sa", "dk", "tn", "ee", "jm", "lc", "pt", "qa",
"ge", "ps"
};
// 初始化 ExecutorService
private void initializeExecutorService() {
if (executorService == null || executorService.isShutdown()) {
executorService = new ThreadPoolExecutor(
1, // 核心线程数
1, // 最大线程数
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(50), // 阻塞队列
new ThreadPoolExecutor.AbortPolicy() // 拒绝策略
);
}
}
private static final int REQUEST_CODE_PERMISSIONS = 100;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LogFileUtil.initialize(this);
setContentView(R.layout.activity_main);
logInfo("onCreate: Initializing application");
initializeExecutorService();
System.setProperty("java.library.path", getApplicationInfo().nativeLibraryDir);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
requestStoragePermission();
}
} else {
if (!Environment.isExternalStorageManager()) {
Intent intent = new Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION);
intent.setData(Uri.parse("package:" + getPackageName()));
startActivityForResult(intent, ALLOW_ALL_FILES_ACCESS_PERMISSION_CODE);
}
}
if (!isNetworkAvailable(this)) {
showToast("网络不可用");
logError("Network not available, closing app.");
finish();
return;
}
logInfo("onCreate: Setting up work manager");
schedulePeriodicWorkIfNotExists();
logInfo("onCreate: Setting up UI components");
setupButton(R.id.run_script_button, v -> {
try {
AutoJsUtil.runAutojsScript(this);
} catch (Exception e) {
LogFileUtil.logAndWrite(Log.ERROR, "MainActivity", "runAutojsScript: " + e.getMessage(), e);
showToast("执行脚本失败");
}
});
setupButton(R.id.connectVpnButton, v -> startProxyVpn(this));
setupButton(R.id.disconnectVpnButton, v -> ClashUtil.stopProxy(this));
setupButton(R.id.switchVpnButton, v -> ClashUtil.switchProxyGroup("GLOBAL", "us", "http://127.0.0.1:6170"));
armClient = new ArmCloudApiClient();
String currentPackage = getPackageName();
ShellUtils.execRootCmdAndGetResult("pm grant " + currentPackage + " android.permission.INTERACT_ACROSS_USERS");
ShellUtils.execRootCmdAndGetResult("pm grant " + currentPackage + " android.permission.WRITE_SECURE_SETTINGS");
setupButton(R.id.modifyDeviceInfoButton, v -> ChangeDeviceInfoUtil.changeDeviceInfo(currentPackage, this, armClient));
setupButton(R.id.resetDeviceInfoButton, v -> ChangeDeviceInfoUtil.resetChangedDeviceInfo(currentPackage, this));
setupButton(R.id.execute_button, v -> {
((Button) v).setEnabled(false);
startLoadWork();
});
setupButton(R.id.stop_execute_button, v -> {
WorkManager.getInstance(this).cancelAllWorkByTag(WORK_TAG);
Button executeButton = findViewById(R.id.execute_button);
if (executeButton != null) {
executeButton.setEnabled(true);
}
});
}
private void requestStoragePermission() {
ActivityCompat.requestPermissions(
this,
new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
REQUEST_CODE_STORAGE_PERMISSION
);
}
private void schedulePeriodicWorkIfNotExists() {
WorkManager.getInstance(this).getWorkInfosForUniqueWorkLiveData("CheckAccessibilityWorker")
.observe(this, workInfos -> {
if (workInfos == null || workInfos.isEmpty()) {
PeriodicWorkRequest workRequest = new PeriodicWorkRequest.Builder(CheckAccessibilityWorker.class, 15, TimeUnit.MINUTES)
.addTag("CheckAccessibilityWorker")
.build();
WorkManager.getInstance(this).enqueueUniquePeriodicWork(
"CheckAccessibilityWorker",
ExistingPeriodicWorkPolicy.REPLACE,
workRequest
);
}
});
}
private void setupButton(int resId, View.OnClickListener listener) {
Button button = findViewById(resId);
if (button != null) {
button.setOnClickListener(listener);
} else {
logInfo("Button not found: " + resId);
}
}
private static String WORK_TAG = "LOAD_WORK";
/**
* 这段代码的功能是启动一个周期性后台任务 创建一个周期为30分钟的PeriodicWorkRequest执行LoadDeviceWorker类的任务 设置初始延迟为0秒添加任务标签WORK_TAG 使用WorkManager将任务加入队列准备执行
*/
private void startLoadWork() {
PeriodicWorkRequest workRequest = new PeriodicWorkRequest.
Builder(LoadDeviceWorker.class, 30, TimeUnit.MINUTES)
.setInitialDelay(0, TimeUnit.SECONDS)
.addTag(WORK_TAG)
.build();
WorkManager.getInstance(this).enqueue(workRequest);
}
public static final LinkedBlockingQueue<String> scriptResultQueue = new LinkedBlockingQueue<>(1);
private void startProxyVpn(Context context) {
if (!isNetworkAvailable(context)) {
Toast.makeText(context, "Network is not available", Toast.LENGTH_SHORT).show();
LogFileUtil.logAndWrite(Log.ERROR, "MainActivity", "startProxyVpn: Network is not available.", null);
return;
}
if (!(context instanceof Activity)) {
Toast.makeText(context, "Context must be an Activity", Toast.LENGTH_SHORT).show();
LogFileUtil.logAndWrite(Log.ERROR, "MainActivity", "startProxyVpn: Context is not an Activity.", null);
return;
}
try {
ClashUtil.startProxy(context);
ClashUtil.switchProxyWithPort(CountryCode.switchCountry());
// ClashUtil.switchProxyGroup("PROXY", "my-socks5-proxy", "http://127.0.0.1:6170");
} catch (Exception e) {
LogFileUtil.logAndWrite(Log.ERROR, "MainActivity", "startProxyVpn: Failed to start VPN", e);
Toast.makeText(context, "Failed to start VPN: " +
(e.getMessage() != null ? e.getMessage() : "Unknown error"),
Toast.LENGTH_SHORT).show();
}
}
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == REQUEST_CODE_STORAGE_PERMISSION) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
Toast.makeText(this, "Storage Permissions granted", Toast.LENGTH_SHORT).show();
} else {
// 提示权限被拒绝同时允许用户重新授予权限
showPermissionExplanationDialog();
}
}
if (requestCode == REQUEST_CODE_PERMISSIONS) {
boolean allGranted = true;
for (int result : grantResults) {
if (result != PackageManager.PERMISSION_GRANTED) {
allGranted = false;
break;
}
}
if (allGranted) {
// 所有权限已授予
startMyForegroundService();
} else {
Toast.makeText(this, "未授予必要权限,请检查设置", Toast.LENGTH_SHORT).show();
}
}
}
private void startMyForegroundService() {
Intent serviceIntent = new Intent(this, MyAccessibilityService.class);
ContextCompat.startForegroundService(this, serviceIntent);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
switch (requestCode) {
case ALLOW_ALL_FILES_ACCESS_PERMISSION_CODE:
handleStoragePermissionResult(resultCode);
break;
default:
break;
}
}
private void handleStoragePermissionResult(int resultCode) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && Environment.isExternalStorageManager()) {
Toast.makeText(this, "Storage Permissions granted", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(this, "请授予所有文件管理权限", Toast.LENGTH_SHORT).show();
finish();
}
}
private void showPermissionExplanationDialog() {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("Permission Required")
.setMessage("Storage Permission is required for the app to function. Please enable it in Settings.")
.setPositiveButton("Go to Settings", (dialog, which) -> {
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
Uri uri = Uri.fromParts("package", getPackageName(), null);
intent.setData(uri);
startActivity(intent);
})
.setNegativeButton("Cancel", (dialog, which) -> finish())
.show();
}
@Override
protected void onDestroy() {
LogFileUtil.logAndWrite(Log.INFO, "MainActivity", "onDestroy: Cleaning up resources", null);
super.onDestroy();
if (AutoJsUtil.scriptResultReceiver != null) {
unregisterReceiver(AutoJsUtil.scriptResultReceiver);
AutoJsUtil.scriptResultReceiver = null;
}
if (executorService != null) {
executorService.shutdown();
}
}
private void logInfo(String message) {
LogFileUtil.logAndWrite(Log.INFO, "MainActivity", message, null);
}
private void logError(String message) {
LogFileUtil.logAndWrite(Log.ERROR, "MainActivity", message, null);
}
private void logWarn(String message) {
LogFileUtil.logAndWrite(Log.WARN, "MainActivity", message, null);
}
private void showToast(String message) {
Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
}
}

View File

@ -0,0 +1,4 @@
package com.example.retention
class ScriptRepository {
}

View File

@ -1,7 +1,6 @@
package com.example.studyapp.autoJS;
package com.example.retention.autoJS;
import static com.example.studyapp.MainActivity.taskLock;
import static com.example.studyapp.task.TaskUtil.downloadCodeFile;
import static com.example.retention.task.TaskUtil.downloadCodeFile;
import android.content.BroadcastReceiver;
import android.content.Context;
@ -15,9 +14,10 @@ import android.widget.Toast;
import androidx.core.content.ContextCompat;
import com.example.studyapp.MainActivity;
import com.example.studyapp.utils.LogFileUtil;
import com.example.studyapp.utils.ShellUtils;
import com.example.retention.MainActivity;
import com.example.retention.utils.LogFileUtil;
import com.example.retention.utils.ShellUtils;
import java.io.File;
public class AutoJsUtil {
@ -32,8 +32,7 @@ public class AutoJsUtil {
LogFileUtil.logAndWrite(android.util.Log.INFO, "AutoJsUtil", "-------脚本运行开始:--------" + count++,null);
File scriptDir = new File(Environment.getExternalStorageDirectory(), "script");//todo
scriptDir.delete();
// File scriptFile = new File(scriptDir, "main.js");
File scriptFile = downloadCodeFile("main.js", scriptDir);
File scriptFile = downloadCodeFile("mainold.js", scriptDir);//todo
if (scriptFile == null || !scriptFile.exists()) {
runOnUiThread(() -> Toast.makeText(context, "下载脚本文件失败", Toast.LENGTH_SHORT).show());
LogFileUtil.logAndWrite(android.util.Log.ERROR, "AutoJsUtil", "下载脚本文件失败",null);
@ -128,7 +127,7 @@ public class AutoJsUtil {
}
}
private static final String AUTOJS_SCRIPT_FINISHED_ACTION = "org.autojs.SCRIPT_FINISHED";
private static final String AUTOJS_SCRIPT_FINISHED_ACTION = "org.autojs.SCRIPT_FINISHED_CACHE";
private static final String SCRIPT_RESULT_KEY = "result";
public static void stopAutojsScript(Context context) {

View File

@ -1,8 +1,8 @@
package com.example.studyapp.config;
package com.example.retention.config;
import android.content.Context;
import com.example.studyapp.utils.LogFileUtil;
import com.example.retention.utils.LogFileUtil;
import org.json.JSONException;
import org.json.JSONObject;

View File

@ -0,0 +1,30 @@
package com.example.retention.config;
import android.util.Log;
import com.example.retention.utils.LogFileUtil;
/**
* @Time: 2025-08-09 21:08
* @Creator: 初屿贤
* @File: ewfw
* @Project: study.App
* @Description:
*/
public class CountryCode {
public static final int DEVICE_TYPE = 2;
static final String US = "US";
static final String RU = "RU";
// 默认使用美国
static final String DEFAULT = US;
// 当前使用的国家代码
public static String currentCountry = DEFAULT;
public static String switchCountry() {
currentCountry = currentCountry.equals(US) ?
RU : US;
LogFileUtil.logAndWrite(Log.INFO, "TAG",
"Switched country to: " + currentCountry, null);
return currentCountry;
}
}

View File

@ -0,0 +1,411 @@
package com.example.retention.device;
import android.util.Log;
import com.example.retention.utils.LogFileUtil;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.OkHttpClient.Builder;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.ResponseBody;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
/**
* @Time: 2025-06-08 17:06
* @Creator: 初屿贤
* @File: ArmCloudApiClient
* @Project: study.App
* @Description:
*/
public class ArmCloudApiClient {
private final OkHttpClient client;
private final String baseUrl = "https://openapi-hk.armcloud.net";
private final String accessKey = "gz8f1u0t63byzdu6ozbx8r5qs3e5lipt";
private final String secretKey = "3yc8c8bg1dym0zaiwjh867al";
public ArmCloudApiClient() {
this.client = new Builder()
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(10, TimeUnit.SECONDS)
.build();
}
private static final String ALGORITHM = "HmacSHA256";
public static String calculateSignature(String timestamp, String path, String body, String secretKey) throws Exception {
String stringToSign = timestamp + path + (body != null ? body : "");
Mac hmacSha256 = Mac.getInstance(ALGORITHM);
SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getBytes(StandardCharsets.UTF_8), ALGORITHM);
hmacSha256.init(secretKeySpec);
byte[] hash = hmacSha256.doFinal(stringToSign.getBytes(StandardCharsets.UTF_8));
return bytesToHex(hash);
}
private static String bytesToHex(byte[] bytes) {
StringBuilder hexString = new StringBuilder();
for (byte b : bytes) {
String hex = Integer.toHexString(0xff & b);
if (hex.length() == 1) {
hexString.append('0');
}
hexString.append(hex);
}
return hexString.toString();
}
/**
* 修改实例安卓改机属性 静态设置安卓改机属性需要重启实例才能够生效一般用于修改设备信息 该接口与修改实例属性接口的区别在于生效时机该接口生效时间为每次开机初始化 设置实例属性后属性数据会持久化存储重启或重置实例无需再调用该接口
*
* @param padCode 实例 ID非空
* @param props 属性映射非空
* @param restart 是否自动重启
* @return 接口返回结果字符串
* @throws IOException 请求失败或网络错误
*/
public String updateAndroidModProperties(String padCode, Map<String, String> props, boolean restart) throws IOException {
// 参数校验
if (padCode == null || padCode.isEmpty()) {
throw new IllegalArgumentException("padCode 不能为空");
}
if (props == null) {
throw new IllegalArgumentException("props 不能为 null");
}
// 构造请求体
JSONObject json = new JSONObject();
try {
json.put("padCode", padCode);
json.put("props", new JSONObject(props));
json.put("restart", restart);
} catch (JSONException e) {
LogFileUtil.logAndWrite(Log.ERROR, "ArmCloudApiClient", "updateAndroidModProperties: JSON error", e);
}
String jsonBody = json.toString();
// 生成时间戳
String timestamp = String.valueOf(System.currentTimeMillis());
String API_PATH = "/openapi/open/pad/updatePadAndroidProp";
String signature = "";
try {
signature = calculateSignature(timestamp, API_PATH, jsonBody, secretKey);
} catch (Exception e) {
LogFileUtil.logAndWrite(Log.ERROR, "ArmCloudApiClient", "updateAndroidModProperties: Signature error", e);
}
RequestBody body = RequestBody.create(
MediaType.parse("application/json; charset=utf-8"),
jsonBody
);
Request request = new Request.Builder()
.url(baseUrl + API_PATH)
.addHeader("authver", "2.0")
.addHeader("x-ak", accessKey)
.addHeader("x-timestamp", timestamp)
.addHeader("x-sign", signature)
.post(body)
.build();
try (Response response = client.newCall(request).execute()) {
if (!response.isSuccessful()) {
throw new IOException("请求失败: " + response);
}
ResponseBody responseBody = response.body();
if (responseBody == null) {
throw new IOException("响应体为空");
}
return responseBody.string();
}
}
/**
* 修改实例属性 动态修改实例的属性信息包括系统属性和设置 实例需要处于开机状态该接口为即时生效
* <p>
* 示例 String[] padCodes = new String[]{"AC21020010001"}; List<PropertyItem> systemProps = Arrays.asList( new PropertyItem("ro.build.id", "QQ3A.200805.001")
* ); List<PropertyItem> oaidProps = Arrays.asList( new PropertyItem("oaid", "001") );
* <p>
* String response = client.updateInstanceProperties( padCodes, null, // modemPersistProps null, // modemProps null, // systemPersistProps systemProps,
* null, // settingProps oaidProps );
*/
public String updateInstanceProperties(
String[] padCodes,
List<PropertyItem> modemPersistProps,
List<PropertyItem> modemProps,
List<PropertyItem> systemPersistProps,
List<PropertyItem> systemProps,
List<PropertyItem> settingProps,
List<PropertyItem> oaidProps
) throws IOException {
if (padCodes == null || padCodes.length == 0) {
throw new IllegalArgumentException("padCodes 不能为空");
}
// 检查 padCodes 是否有重复项
Set<String> padCodeSet = new HashSet<>();
for (String code : padCodes) {
if (!padCodeSet.add(code)) {
throw new IllegalArgumentException("padCodes 包含重复项: " + code);
}
}
JSONObject json = new JSONObject();
try {
json.put("padCodes", new JSONArray(Arrays.asList(padCodes)));
putPropertyItems(json, "modemPersistPropertiesList", modemPersistProps);
putPropertyItems(json, "modemPropertiesList", modemProps);
putPropertyItems(json, "systemPersistPropertiesList", systemPersistProps);
putPropertyItems(json, "systemPropertiesList", systemProps);
putPropertyItems(json, "settingPropertiesList", settingProps);
putPropertyItems(json, "oaidPropertiesList", oaidProps);
} catch (JSONException e) {
LogFileUtil.logAndWrite(Log.ERROR, "ArmCloudApiClient", "updateInstanceProperties: JSON 构建失败", e);
throw new IOException("JSON 构建失败", e);
}
String jsonBody = json.toString();
String timestamp = String.valueOf(System.currentTimeMillis());
String API_PATH = "/openapi/open/pad/updatePadProperties";
if (secretKey == null || secretKey.isEmpty()) {
throw new IllegalArgumentException("secretKey 不能为空");
}
String signature;
try {
signature = calculateSignature(timestamp, API_PATH, jsonBody, secretKey);
} catch (Exception e) {
LogFileUtil.logAndWrite(Log.ERROR, "ArmCloudApiClient", "updateInstanceProperties: 签名计算失败", e);
throw new IOException("签名计算失败", e);
}
RequestBody body = RequestBody.create(
MediaType.get("application/json; charset=utf-8"),
jsonBody
);
Request request = new Request.Builder()
.url(baseUrl + API_PATH)
.addHeader("authver", "2.0")
.addHeader("x-ak", accessKey)
.addHeader("x-timestamp", timestamp)
.addHeader("x-sign", signature)
.post(body)
.build();
try (Response response = client.newCall(request).execute()) {
if (!response.isSuccessful()) {
throw new IOException("请求失败: " + response);
}
ResponseBody responseBody = response.body();
if (responseBody == null) {
throw new IOException("响应体为空");
}
String responseBodyString = responseBody.string();
JSONObject responseJson = new JSONObject(responseBodyString);
// 校验返回码
if (responseJson.has("code")) {
int code = responseJson.getInt("code");
if (code != 200) {
String errorMsg = responseJson.optString("msg", "未知错误");
LogFileUtil.logAndWrite(Log.ERROR, "ArmCloudApiClient", "updateInstanceProperties: 接口返回错误码 " + code + ", 错误信息: " + errorMsg, null);
}
} else {
LogFileUtil.logAndWrite(Log.ERROR, "ArmCloudApiClient", "updateInstanceProperties: 响应中缺少 'code' 字段", null);
}
return responseBodyString;
} catch (JSONException e) {
LogFileUtil.logAndWrite(Log.ERROR, "ArmCloudApiClient", "updateInstanceProperties: 响应解析失败", e);
throw new IOException("响应解析失败", e);
}
}
private void putPropertyItems(JSONObject json, String key, List<PropertyItem> items) throws JSONException {
if (items != null && !items.isEmpty()) {
JSONArray array = new JSONArray();
for (PropertyItem item : items) {
array.put(item.toJson());
}
json.put(key, array);
}
}
public static class PropertyItem {
private String propertiesName;
private String propertiesValue;
public PropertyItem(String name, String value) {
this.propertiesName = name;
this.propertiesValue = value;
}
public JSONObject toJson() throws JSONException {
JSONObject json = new JSONObject();
json.put("propertiesName", propertiesName);
json.put("propertiesValue", propertiesValue);
return json;
}
}
/**
* 分页查询实例列表信息
*
* @param page 页码
* @param rows 每页条数
* @param armServerCode 服务器编号
* @param deviceCode 板卡编号
* @param padCodes 实例编号数组
* @param groupIds 实例分组ID数组
* @param idc 机房Id
* @return 匹配的 padCode 数组
* @throws IOException 请求失败或网络错误
*/
public String[] getInstanceListInfo(
int page,
int rows,
String armServerCode,
String deviceCode,
String[] padCodes,
Integer[] groupIds,
String idc) throws IOException {
JSONObject json = new JSONObject();
try {
json.put("page", page);
json.put("rows", rows);
if (armServerCode != null) {
json.put("armServerCode", armServerCode);
}
if (deviceCode != null) {
json.put("deviceCode", deviceCode);
}
if (padCodes != null && padCodes.length > 0) {
json.put("padCodes", new JSONArray(Arrays.asList(padCodes)));
}
if (groupIds != null && groupIds.length > 0) {
json.put("groupIds", new JSONArray(Arrays.asList(groupIds)));
}
if (idc != null) {
json.put("idc", idc);
}
} catch (JSONException e) {
LogFileUtil.logAndWrite(Log.ERROR, "ArmCloudApiClient", "getInstanceListInfo: JSON 构建失败", e);
throw new IOException("JSON 构建失败", e);
}
String jsonBody = json.toString();
String timestamp = String.valueOf(System.currentTimeMillis());
String API_PATH = "/openapi/open/pad/infos";
if (secretKey == null || secretKey.isEmpty()) {
throw new IllegalArgumentException("secretKey 不能为空");
}
String signature;
try {
signature = calculateSignature(timestamp, API_PATH, jsonBody, secretKey);
} catch (Exception e) {
LogFileUtil.logAndWrite(Log.ERROR, "ArmCloudApiClient", "getInstanceListInfo: 签名计算失败", e);
throw new IOException("签名计算失败", e);
}
RequestBody body = RequestBody.create(
MediaType.get("application/json; charset=utf-8"),
jsonBody
);
Request request = new Request.Builder()
.url(baseUrl + API_PATH)
.addHeader("authver", "2.0")
.addHeader("x-ak", accessKey)
.addHeader("x-timestamp", timestamp)
.addHeader("x-sign", signature)
.post(body)
.build();
try (Response response = client.newCall(request).execute()) {
if (!response.isSuccessful()) {
throw new IOException("请求失败: " + response);
}
ResponseBody responseBody = response.body();
if (responseBody == null) {
throw new IOException("响应体为空");
}
String responseBodyString = responseBody.string();
JSONObject responseJson = new JSONObject(responseBodyString);
// 校验返回码
if (responseJson.has("code")) {
int code = responseJson.getInt("code");
if (code != 200) {
String errorMsg = responseJson.optString("msg", "未知错误");
LogFileUtil.logAndWrite(Log.ERROR, "ArmCloudApiClient", "getInstanceListInfo: 接口返回错误码 " + code + ", 错误信息: " + errorMsg, null);
throw new IOException("接口返回错误码: " + code + ", 错误信息: " + errorMsg);
}
} else {
LogFileUtil.logAndWrite(Log.ERROR, "ArmCloudApiClient", "getInstanceListInfo: 响应中缺少 'code' 字段", null);
throw new IOException("响应中缺少 'code' 字段");
}
// 提取 padCode 列表
JSONArray pageDataArray = responseJson.optJSONObject("data")
.optJSONArray("pageData");
if (pageDataArray == null || pageDataArray.length() == 0) {
LogFileUtil.logAndWrite(Log.WARN, "ArmCloudApiClient", "getInstanceListInfo: 查询结果为空", null);
return new String[0];
}
int length = pageDataArray.length();
String[] padCodesResult = new String[length];
for (int i = 0; i < length; i++) {
JSONObject item = pageDataArray.getJSONObject(i);
if (!item.has("padCode")) {
LogFileUtil.logAndWrite(Log.WARN, "ArmCloudApiClient", "getInstanceListInfo: 返回对象缺少 'padCode' 字段", null);
throw new IOException("返回对象缺少 'padCode' 字段");
}
padCodesResult[i] = item.getString("padCode");
}
return padCodesResult;
} catch (JSONException e) {
LogFileUtil.logAndWrite(Log.ERROR, "ArmCloudApiClient", "getInstanceListInfo: 响应解析失败", e);
throw new IOException("响应解析失败", e);
}
}
}

View File

@ -0,0 +1,533 @@
package com.example.retention.device;
import static com.example.retention.autoJS.AutoJsUtil.isAppInstalled;
import static com.example.retention.utils.LogFileUtil.logAndWrite;
import android.content.ContentResolver;
import android.content.Context;
import android.net.Uri;
import android.text.TextUtils;
import android.util.Log;
import com.example.retention.device.ArmCloudApiClient.PropertyItem;
import com.example.retention.task.AfInfo;
import com.example.retention.task.BigoInfo;
import com.example.retention.task.DeviceInfo;
import com.example.retention.task.TaskUtil;
import com.example.retention.utils.ApkInstaller;
import com.example.retention.utils.HttpUtil;
import com.example.retention.utils.LogFileUtil;
import com.example.retention.utils.ShellUtils;
import com.example.retention.utils.ZipUtils;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.json.JSONException;
import org.json.JSONObject;
import java.lang.reflect.Method;
public class ChangeDeviceInfoUtil {
private static JSONObject bigoDeviceObject;
private static JSONObject afDeviceObject;
public static String packageName = "";
public static String zipName = "";
public static String buildBigoUrl(String country, int tag) {
return Uri.parse("http://8.217.137.25/tt/zj/dispatcher!bigo.do")
.buildUpon()
.appendQueryParameter("country", country)
.appendQueryParameter("tag", String.valueOf(tag))
.toString();
}
public static String buildAfUrl(String country, int tag) {
return Uri.parse("http://8.217.137.25/tt/zj/dispatcher!af.do")
.buildUpon()
.appendQueryParameter("country", country)
.appendQueryParameter("tag", String.valueOf(tag))
.toString();
}
// 创建一个线程池用于执行网络任务
private static final ExecutorService executorService = Executors.newSingleThreadExecutor();
public static void initialize(String country, int tag, Context context, String androidId) {
LogFileUtil.logAndWrite(android.util.Log.DEBUG, LOG_TAG, "Initializing device info...", null);
executorService.submit(() -> {
try {
LogFileUtil.logAndWrite(android.util.Log.DEBUG, LOG_TAG, "Starting network requests...", null);
String bigoJson = fetchJsonSafely(buildBigoUrl(country, tag), "bigoJson");
String afJson = fetchJsonSafely(buildAfUrl(country, tag), "afJson");
LogFileUtil.logAndWrite(android.util.Log.DEBUG, LOG_TAG, "Received bigoJson: " + bigoJson, null);
LogFileUtil.logAndWrite(android.util.Log.DEBUG, LOG_TAG, "Received afJson: " + afJson, null);
fallBackToNetworkData(bigoJson, afJson);
logDeviceObjects();
processPackageInfo(TaskUtil.getPackageInfo(androidId), context);
} catch (IOException | JSONException e) {
LogFileUtil.logAndWrite(android.util.Log.ERROR, LOG_TAG, "Error occurred during initialization", e);
} catch (Exception e) {
LogFileUtil.logAndWrite(android.util.Log.ERROR, LOG_TAG, "Error occurred during initialization", e);
}
});
}
public static boolean getDeviceInfoSync(String taskId, String androidId) {
String response = "";
try {
response = executeQuerySafely(androidId, taskId);
} catch (Exception e) {
e.printStackTrace();
}
if (response == null || response.isBlank()) {
LogFileUtil.logAndWrite(android.util.Log.ERROR, LOG_TAG, "Error occurred during query", null);
return false;
}
try {
synchronized (ChangeDeviceInfoUtil.class) { // 防止并发访问
parseAndSetDeviceObjects(response);
}
return true;
} catch (Exception e) {
LogFileUtil.logAndWrite(android.util.Log.ERROR, LOG_TAG, "Error parsing JSON", e);
return false;
}
}
private static String fetchJsonSafely(String url, String logKey) throws IOException {
String json = null;
try {
json = HttpUtil.requestGet(url);
if (json != null && !json.isEmpty()) {
LogFileUtil.logAndWrite(android.util.Log.DEBUG, LOG_TAG, "Received " + logKey + ": " + json, null);
return json;
} else {
LogFileUtil.logAndWrite(android.util.Log.WARN, LOG_TAG, "Empty or null response for: " + logKey + ", retrying...", null);
}
} catch (IOException e) {
LogFileUtil.logAndWrite(android.util.Log.WARN, LOG_TAG, "Error fetching " + logKey + ": " + e.getMessage() + ", retrying...", e);
}
// Retry once if the initial attempt failed
json = HttpUtil.requestGet(url);
if (json != null && !json.isEmpty()) {
LogFileUtil.logAndWrite(android.util.Log.DEBUG, LOG_TAG, "Retry success for " + logKey + ": " + json, null);
return json;
} else {
LogFileUtil.logAndWrite(android.util.Log.ERROR, LOG_TAG, "Retry failed for " + logKey + ", response is still null or empty.", null);
throw new IOException("Failed to fetch valid JSON for " + logKey);
}
}
private static boolean isValidResponse(String response) {
return response != null && !response.isBlank() && !response.equals("{}\n")
&& !response.equals("{\"afDeviceObject\": null, \"bigoDeviceObject\": null, \"other\": null}")
&& response.trim().startsWith("{");
}
private static void parseAndSetDeviceObjects(String response) throws JSONException {
String cleanJson = response.trim();
if (cleanJson.startsWith("\"") && cleanJson.endsWith("\"")) {
cleanJson = cleanJson.substring(1, cleanJson.length() - 1).replace("\\\"", "\"");
}
JSONObject responseJson = new JSONObject(cleanJson);
bigoDeviceObject = responseJson.optJSONObject("bigoDeviceObject");
afDeviceObject = responseJson.optJSONObject("afDeviceObject");
packageName = responseJson.optString("package_name");
zipName = responseJson.optString("file_name");
}
private static void fallBackToNetworkData(String bigoJson, String afJson) throws JSONException {
bigoDeviceObject = new JSONObject(bigoJson).optJSONObject("device");
afDeviceObject = new JSONObject(afJson).optJSONObject("device");
}
private static void logDeviceObjects() {
LogFileUtil.logAndWrite(android.util.Log.INFO, LOG_TAG, "Final bigoDeviceObject: " + bigoDeviceObject, null);
LogFileUtil.logAndWrite(android.util.Log.INFO, LOG_TAG, "Final DeviceInfo: " + afDeviceObject, null);
}
public static void processPackageInfo(Map<String, String> packageInfo, Context context) {
if (packageInfo != null) {
for (Map.Entry<String, String> entry : packageInfo.entrySet()) {
String packageName = entry.getKey();
if (!isAppInstalled(packageName)) {
processPackage(packageName, entry.getValue(), context);
} else {
LogFileUtil.logAndWrite(android.util.Log.WARN, LOG_TAG, "Package not installed: " + packageName, null);
}
}
}
}
public static boolean processPackageInfoWithDeviceInfo(String packageName, String zipName, Context context, String androidId, String taskId) {
if (!isAppInstalled(packageName)) {
return processPackage(packageName, zipName, context);
// TaskUtil.postDeviceInfo(androidId, taskId, packageName);
} else {
LogFileUtil.logAndWrite(android.util.Log.WARN, LOG_TAG, "Package not installed: " + packageName, null);
return false;
}
}
private static boolean processPackage(String packageName, String zipName, Context context) {
try {
File filesDir = new File(context.getExternalFilesDir(null).getAbsolutePath());
File file = TaskUtil.downloadCodeFile(zipName, filesDir);
if (file != null && file.exists()) {
File destFile = new File(context.getCacheDir(), packageName);
if (destFile.exists()) {
TaskUtil.delFileSh(destFile.getAbsolutePath());
}
ZipUtils.unzip(file.getAbsolutePath(), destFile.getAbsolutePath());
if (destFile.exists()) {
installApk(destFile.getAbsolutePath());
}
TaskUtil.delFileSh(destFile.getAbsolutePath());
TaskUtil.delFileSh(file.getAbsolutePath());
LogFileUtil.logAndWrite(Log.DEBUG, LOG_TAG, "Processed package: " + packageName, null);
return true;
} else {
LogFileUtil.logAndWrite(android.util.Log.WARN, LOG_TAG, "File download failed for package: " + packageName, null);
return false;
}
} catch (Exception e) {
LogFileUtil.logAndWrite(android.util.Log.ERROR, LOG_TAG, "Error processing package: " + packageName, e);
return false;
}
}
public static boolean installApk(String apkFilePath) {
// 检查文件路径
if (apkFilePath == null || apkFilePath.trim().isEmpty()) {
LogFileUtil.logAndWrite(Log.ERROR, "ShellUtils", "Invalid APK file path", null);
return false;
}
// 确保文件存在
File apkFile = new File(apkFilePath);
if (!apkFile.exists()) {
LogFileUtil.logAndWrite(Log.ERROR, "ShellUtils", "APK file not found: " + apkFilePath, null);
return false;
}
boolean result = ApkInstaller.batchInstallWithRoot(apkFilePath);
if (result) {
Log.d("ShellUtils", "APK installed successfully!");
return true;
} else {
LogFileUtil.logAndWrite(Log.ERROR, "ShellUtils", "Failed to install APK. Result: " + result, null);
return false;
}
}
private static final String LOG_TAG = "TaskUtil";
private static final String INIT_LOG_TEMPLATE = "initialize method called with parameters: Country: %s, Tag: %d, Android ID: %s";
private static final String CONTEXT_LOG_TEMPLATE = "Context instance: %s";
// 辅助方法执行网络请求
private static String fetchJson(String url) throws IOException {
return HttpUtil.requestGet(url);
}
// 辅助方法执行任务
private static String executeQuerySafely(String androidId, String taskId) {
return TaskUtil.execQueryTask(androidId, taskId);
}
public static void changeDeviceInfo(String current_pkg_name, Context context, ArmCloudApiClient client) {
final String B_PREFIX = current_pkg_name + ".";
final String U_PREFIX = current_pkg_name + "_";
if (bigoDeviceObject != null) {
// BIGO
BigoInfo bigoDevice = new BigoInfo();
bigoDevice.cpuClockSpeed = bigoDeviceObject.optString("cpu_clock_speed");
bigoDevice.gaid = bigoDeviceObject.optString("gaid");
bigoDevice.userAgent = bigoDeviceObject.optString("User-Agent");
bigoDevice.osLang = bigoDeviceObject.optString("os_lang");
bigoDevice.osVer = bigoDeviceObject.optString("os_ver");
bigoDevice.tz = bigoDeviceObject.optString("tz");
bigoDevice.systemCountry = bigoDeviceObject.optString("system_country");
bigoDevice.simCountry = bigoDeviceObject.optString("sim_country");
bigoDevice.romFreeIn = bigoDeviceObject.optLong("rom_free_in");
bigoDevice.resolution = bigoDeviceObject.optString("resolution");
bigoDevice.vendor = bigoDeviceObject.optString("vendor");
bigoDevice.batteryScale = bigoDeviceObject.optInt("bat_scale");
bigoDevice.net = bigoDeviceObject.optString("net");
bigoDevice.dpi = bigoDeviceObject.optInt("dpi");
bigoDevice.romFreeExt = bigoDeviceObject.optLong("rom_free_ext");
bigoDevice.dpiF = bigoDeviceObject.optString("dpi_f");
bigoDevice.cpuCoreNum = bigoDeviceObject.optInt("cpu_core_num");
TaskUtil.setBigoDevice(bigoDevice);
try {
Map<String, Object> bigoMap = new HashMap<>();
bigoMap.put(B_PREFIX + "system_country", bigoDevice.systemCountry);
bigoMap.put(B_PREFIX + "sim_country", bigoDevice.simCountry);
bigoMap.put(B_PREFIX + "rom_free_in", String.valueOf(bigoDevice.romFreeIn));
bigoMap.put(B_PREFIX + "resolution", bigoDevice.resolution);
bigoMap.put(B_PREFIX + "vendor", bigoDevice.vendor);
bigoMap.put(B_PREFIX + "battery_scale", String.valueOf(bigoDevice.batteryScale));
bigoMap.put(B_PREFIX + "os_lang", bigoDevice.osLang);
bigoMap.put(B_PREFIX + "net", bigoDevice.net);
bigoMap.put(B_PREFIX + "dpi", String.valueOf(bigoDevice.dpi));
bigoMap.put(B_PREFIX + "rom_free_ext", String.valueOf(bigoDevice.romFreeExt));
bigoMap.put(B_PREFIX + "dpi_f", bigoDevice.dpiF);
bigoMap.put(B_PREFIX + "cpu_core_num", String.valueOf(bigoDevice.cpuCoreNum));
bigoMap.put(B_PREFIX + "cpu_clock_speed", bigoDevice.cpuClockSpeed);
bigoMap.put(current_pkg_name + "_gaid", bigoDevice.gaid);
bigoMap.put(current_pkg_name + "_user_agent", bigoDevice.userAgent);
bigoMap.put(current_pkg_name + "_os_lang", bigoDevice.osLang);
bigoMap.put(current_pkg_name + "_os_ver", bigoDevice.osVer);
bigoMap.put(current_pkg_name + "_tz", bigoDevice.tz);
for (Map.Entry<String, Object> entry : bigoMap.entrySet()) {
callVCloudSettings_put(entry.getKey(), entry.getValue().toString(), context);
}
} catch (Throwable e) {
logAndWrite(android.util.Log.ERROR, "ChangeDeviceInfoUtil", "Error occurred while changing device info", e);
throw e; // 保留原始异常类型
}
} else {
Log.w("ChangeDeviceInfoUtil", "bigoDeviceObject is null, skipping BIGO settings.");
}
if (afDeviceObject != null) {
AfInfo afDevice = new AfInfo();
afDevice.advertiserId = afDeviceObject.optString(".advertiserId");
afDevice.model = afDeviceObject.optString(".model");
afDevice.brand = afDeviceObject.optString(".brand");
afDevice.androidId = afDeviceObject.optString(".android_id");
afDevice.xPixels = afDeviceObject.optInt(".deviceData.dim.x_px");
afDevice.yPixels = afDeviceObject.optInt(".deviceData.dim.y_px");
afDevice.densityDpi = afDeviceObject.optInt(".deviceData.dim.d_dpi");
afDevice.country = afDeviceObject.optString(".country");
afDevice.batteryLevel = afDeviceObject.optString(".batteryLevel");
afDevice.stackInfo = Thread.currentThread().getStackTrace()[2].toString();
afDevice.product = afDeviceObject.optString(".product");
afDevice.network = afDeviceObject.optString(".network");
afDevice.langCode = afDeviceObject.optString(".lang_code");
afDevice.cpuAbi = afDeviceObject.optString(".deviceData.cpu_abi");
afDevice.yDp = afDeviceObject.optInt(".deviceData.dim.ydp");
TaskUtil.setAfDevice(afDevice);
DeviceInfo deviceInfo = new DeviceInfo();
deviceInfo.lang = afDeviceObject.optString(".lang");
deviceInfo.roProductBrand = afDeviceObject.optString("ro.product.brand", "");
deviceInfo.roProductModel = afDeviceObject.optString("ro.product.model", "");
deviceInfo.roProductManufacturer = afDeviceObject.optString("ro.product.manufacturer", "");
deviceInfo.roProductDevice = afDeviceObject.optString("ro.product.device", "");
deviceInfo.roProductName = afDeviceObject.optString("ro.product.name", "");
deviceInfo.roBuildVersionIncremental = afDeviceObject.optString("ro.build.version.incremental", "");
deviceInfo.roBuildFingerprint = afDeviceObject.optString("ro.build.fingerprint", "");
deviceInfo.roOdmBuildFingerprint = afDeviceObject.optString("ro.odm.build.fingerprint", "");
deviceInfo.roProductBuildFingerprint = afDeviceObject.optString("ro.product.build.fingerprint", "");
deviceInfo.roSystemBuildFingerprint = afDeviceObject.optString("ro.system.build.fingerprint", "");
deviceInfo.roSystemExtBuildFingerprint = afDeviceObject.optString("ro.system_ext.build.fingerprint", "");
deviceInfo.roVendorBuildFingerprint = afDeviceObject.optString("ro.vendor.build.fingerprint", "");
deviceInfo.roBuildPlatform = afDeviceObject.optString("ro.board.platform", "");
deviceInfo.persistSysCloudDrmId = afDeviceObject.optString("persist.sys.cloud.drm.id", "");
deviceInfo.persistSysCloudBatteryCapacity = afDeviceObject.optInt("persist.sys.cloud.battery.capacity", -1);
deviceInfo.persistSysCloudGpuGlVendor = afDeviceObject.optString("persist.sys.cloud.gpu.gl_vendor", "");
deviceInfo.persistSysCloudGpuGlRenderer = afDeviceObject.optString("persist.sys.cloud.gpu.gl_renderer", "");
deviceInfo.persistSysCloudGpuGlVersion = afDeviceObject.optString("persist.sys.cloud.gpu.gl_version", "");
deviceInfo.persistSysCloudGpuEglVendor = afDeviceObject.optString("persist.sys.cloud.gpu.egl_vendor", "");
deviceInfo.persistSysCloudGpuEglVersion = afDeviceObject.optString("persist.sys.cloud.gpu.egl_version", "");
TaskUtil.setDeviceInfo(deviceInfo);
try {
Map<String, Object> afMap = new HashMap<>();
afMap.put(B_PREFIX + "advertiserId", afDevice.advertiserId);
afMap.put(B_PREFIX + "model", afDevice.model);
afMap.put(B_PREFIX + "brand", afDevice.brand);
afMap.put(B_PREFIX + "android_id", afDevice.androidId);
afMap.put(B_PREFIX + "lang", afDevice.langCode);
afMap.put(B_PREFIX + "country", afDevice.country);
afMap.put(B_PREFIX + "batteryLevel", afDevice.batteryLevel);
afMap.put(B_PREFIX + "screen.optMetrics.stack", afDevice.stackInfo);
afMap.put(B_PREFIX + "product", afDevice.product);
afMap.put(B_PREFIX + "network", afDevice.network);
afMap.put(B_PREFIX + "cpu_abi", afDevice.cpuAbi);
afMap.put(B_PREFIX + "lang_code", afDevice.langCode);
for (Map.Entry<String, Object> entry : afMap.entrySet()) {
callVCloudSettings_put(entry.getKey(), entry.getValue().toString(), context);
}
callVCloudSettings_put(current_pkg_name + ".advertiserIdEnabled", "true", context);
JSONObject displayMetrics = new JSONObject();
displayMetrics.put("widthPixels", afDevice.xPixels);
displayMetrics.put("heightPixels", afDevice.yPixels);
displayMetrics.put("densityDpi", afDevice.densityDpi);
displayMetrics.put("yDp", afDevice.yDp);
callVCloudSettings_put("screen.device.displayMetrics", displayMetrics.toString(), context);
// 使用 ArmCloud API 设置系统属性
List<PropertyItem> systemProps = new ArrayList<>();
addProperty(systemProps, "ro.product.brand", deviceInfo.roProductBrand);
addProperty(systemProps, "ro.product.model", deviceInfo.roProductModel);
addProperty(systemProps, "ro.product.manufacturer", deviceInfo.roProductManufacturer);
addProperty(systemProps, "ro.product.device", deviceInfo.roProductDevice);
addProperty(systemProps, "ro.product.name", deviceInfo.roProductName);
addProperty(systemProps, "ro.build.version.incremental", deviceInfo.roBuildVersionIncremental);
addProperty(systemProps, "ro.build.fingerprint", deviceInfo.roBuildFingerprint);
addProperty(systemProps, "ro.odm.build.fingerprint", deviceInfo.roOdmBuildFingerprint);
addProperty(systemProps, "ro.product.build.fingerprint", deviceInfo.roProductBuildFingerprint);
addProperty(systemProps, "ro.system.build.fingerprint", deviceInfo.roSystemBuildFingerprint);
addProperty(systemProps, "ro.system_ext.build.fingerprint", deviceInfo.roSystemExtBuildFingerprint);
addProperty(systemProps, "ro.vendor.build.fingerprint", deviceInfo.roVendorBuildFingerprint);
addProperty(systemProps, "ro.board.platform", deviceInfo.roBuildPlatform);
// 调用接口更新实例属性
try {
String[] padCodes = client.getInstanceListInfo(1, 100, null, null, null, null, null);
LogFileUtil.logAndWrite(Log.DEBUG, "ChangeDeviceInfoUtil", "padCodes: " + Arrays.toString(padCodes), null);
String response = client.updateInstanceProperties(
padCodes,
null, // modemPersistProps
null, // modemProps
null, // systemPersistProps
systemProps,
null, // settingProps
null // oaidProps
);
Log.d("ChangeDeviceInfoUtil", "updateInstanceProperties response: " + response);
} catch (IOException e) {
Log.e("ChangeDeviceInfoUtil", "Failed to update instance properties", e);
}
} catch (Throwable e) {
logAndWrite(Log.ERROR, "ChangeDeviceInfoUtil", "Error occurred in changeDeviceInfo", e);
}
} else {
Log.w("ChangeDeviceInfoUtil", "afDeviceObject is null, skipping AF settings.");
}
}
private static void addProperty(List<PropertyItem> list, String name, String value) {
if (value != null && !value.isEmpty()) {
list.add(new PropertyItem(name, value));
}
}
private static void execRootCmdIfNotEmpty(String cmd, String value) {
if (value != null && !value.isEmpty()) {
String result = ShellUtils.execRootCmdAndGetResult(cmd + value);
if (result == null) {
Log.e("ChangeDeviceInfoUtil", "Failed to execute shell command: " + cmd + value + ", result was empty or null.");
} else {
Log.d("ChangeDeviceInfoUtil", "Successfully executed command: " + cmd + value + ", output: " + result);
}
}
}
private static void callVCloudSettings_put(String key, String value, Context context) {
if (context == null) {
logAndWrite(Log.ERROR, "ChangeDeviceInfoUtil", "Context cannot be null", null);
throw new IllegalArgumentException("Context cannot be null");
}
if (key == null || key.isEmpty()) {
logAndWrite(Log.ERROR, "ChangeDeviceInfoUtil", "Key cannot be null or empty", null);
throw new IllegalArgumentException("Key cannot be null or empty");
}
if (value == null) {
logAndWrite(Log.ERROR, "ChangeDeviceInfoUtil", "Value cannot be null", null);
throw new IllegalArgumentException("Value cannot be null");
}
try {
// 获取类对象
Class<?> clazz = Class.forName("android.provider.VCloudSettings$Global");
Method putStringMethod = clazz.getDeclaredMethod("putString", ContentResolver.class, String.class, String.class);
putStringMethod.setAccessible(true);
// 调用方法
putStringMethod.invoke(null, context.getContentResolver(), key, value);
Log.d("Debug", "putString executed successfully.");
} catch (ClassNotFoundException e) {
logAndWrite(Log.WARN, "ChangeDeviceInfoUtil", "Class not found: android.provider.VCloudSettings$Global. This may not be supported on this device.", e);
} catch (NoSuchMethodException e) {
logAndWrite(Log.WARN, "ChangeDeviceInfoUtil", "Method not found: android.provider.VCloudSettings$Global.putString. This may not be supported on this", e);
} catch (InvocationTargetException e) {
Throwable cause = e.getTargetException();
if (cause instanceof SecurityException) {
logAndWrite(Log.ERROR, "ChangeDeviceInfoUtil", "Error occurred in changeDeviceInfo", cause);
} else {
logAndWrite(Log.ERROR, "ChangeDeviceInfoUtil", "Error occurred in changeDeviceInfo", cause);
}
} catch (Exception e) {
logAndWrite(Log.ERROR, "ChangeDeviceInfoUtil", "Unexpected error during putString invocation", e);
}
}
public static void resetChangedDeviceInfo(String current_pkg_name, Context context) {
try {
Native.setBootId("00000000000000000000000000000000");
} catch (Exception e) {
logAndWrite(Log.ERROR, "ChangeDeviceInfoUtil", "Error occurred in reset", e);
}
if (!ShellUtils.hasRootAccess()) {
LogFileUtil.logAndWrite(Log.ERROR, "ChangeDeviceInfoUtil", "Root access is required to execute system property changes", null);
return;
}
ShellUtils.execRootCmd("cmd settings2 delete global global_android_id");
ShellUtils.execRootCmd("cmd settings2 delete global pm_list_features");
ShellUtils.execRootCmd("cmd settings2 delete global pm_list_libraries");
ShellUtils.execRootCmd("cmd settings2 delete global anticheck_pkgs");
ShellUtils.execRootCmd("cmd settings2 delete global " + current_pkg_name + "_android_id");
ShellUtils.execRootCmd("cmd settings2 delete global " + current_pkg_name + "_adb_enabled");
ShellUtils.execRootCmd("cmd settings2 delete global " + current_pkg_name + "_development_settings_enabled");
ShellUtils.execRootCmd("setprop persist.sys.cloud.drm.id \"\"");
ShellUtils.execRootCmd("setprop persist.sys.cloud.gpu.gl_vendor \"\"");
ShellUtils.execRootCmd("setprop persist.sys.cloud.gpu.gl_renderer \"\"");
// 这个值不能随便改 必须是 OpenGL ES %d.%d 这个格式
ShellUtils.execRootCmd("setprop persist.sys.cloud.gpu.gl_version \"\"");
ShellUtils.execRootCmd("setprop persist.sys.cloud.gpu.egl_vendor \"\"");
ShellUtils.execRootCmd("setprop persist.sys.cloud.gpu.egl_version \"\"");
ShellUtils.execRootCmd("setprop ro.product.brand Vortex");
ShellUtils.execRootCmd("setprop ro.product.model HD65_Select");
ShellUtils.execRootCmd("setprop ro.product.manufacturer Vortex");
ShellUtils.execRootCmd("setprop ro.product.device HD65_Select");
ShellUtils.execRootCmd("setprop ro.product.name HD65_Select");
ShellUtils.execRootCmd("setprop ro.build.version.incremental 20240306");
ShellUtils.execRootCmd("setprop ro.build.fingerprint \"Vortex/HD65_Select/HD65_Select:13/TP1A.220624.014/20240306:user/release-keys\"");
ShellUtils.execRootCmd("setprop ro.board.platform sm8150p");
}
}

View File

@ -1,4 +1,4 @@
package com.example.studyapp.device;
package com.example.retention.device;
public class Native {
static {

View File

@ -1,16 +1,19 @@
package com.example.studyapp.proxy;
package com.example.retention.proxy;
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.example.studyapp.utils.LogFileUtil;
import com.example.retention.utils.LogFileUtil;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.concurrent.CountDownLatch;
import okhttp3.Call;
import okhttp3.Callback;
import java.util.concurrent.TimeUnit;
import okhttp3.HttpUrl;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
@ -19,6 +22,7 @@ import okhttp3.RequestBody;
import okhttp3.Response;
import org.json.JSONException;
import org.json.JSONObject;
import okhttp3.logging.HttpLoggingInterceptor;
/**
* @Time: 2025/6/9 11:13
@ -28,6 +32,18 @@ import org.json.JSONObject;
* @Description:
*/
public class ClashUtil {
private static final HttpLoggingInterceptor httpLoggingInterceptor= new HttpLoggingInterceptor();
private static final OkHttpClient sharedClient ;
static {
httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
sharedClient = new OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS) // 连接超时
.readTimeout(30, TimeUnit.SECONDS) // 读取超时
.addInterceptor(httpLoggingInterceptor)
.build();
}
public static void startProxy(Context context) {
Intent intent = new Intent("com.github.kr328.clash.intent.action.SESSION_CREATE");
@ -84,6 +100,60 @@ public class ClashUtil {
context.unregisterReceiver(clashStatusReceiver);
}
public static int getProxyPort(){
File scriptDir = new File(Environment.getExternalStorageDirectory(), "script");
File portFile = new File(scriptDir, "ip.port.json");
StringBuilder text = new StringBuilder();
try (BufferedReader br = new BufferedReader(new FileReader(portFile))) {
String line;
while ((line = br.readLine()) != null) {
text.append(line).append('\n');
}
} catch (IOException e) {
Log.e("TAG", "getProxyPort: ", e);
return -1;
}
int port = -1;
try{
Log.d("TAG", "getProxyPort: "+text);
JSONObject config = new JSONObject(text.toString());
port = config.optInt("port", -1);
}catch (Exception e){
Log.e("TAG", "getProxyPort: ", e);
}
return port;
}
public static void switchProxyWithPort(String country) {
int port = getProxyPort();
// 安全构建 URL
HttpUrl url = HttpUrl.parse("http://39.103.73.250/tt/test/testProxy.jsp")
.newBuilder()
.addQueryParameter("port", port+"")
.addQueryParameter("country", country)
.build();
Request request = new Request.Builder()
.url(url)
.build();
// 使用 try-with-resources 确保 Response 自动关闭
try (Response response = sharedClient.newCall(request).execute()) {
// 读取并记录响应内容
String responseBody = response.body() != null ? response.body().string() : "Empty response body";
if (response.isSuccessful()) {
LogFileUtil.logAndWrite(Log.INFO, "ClashUtil",
"switchProxyGroup: Success | Status: " + response.code() + " | Response: " + responseBody, null);
} else {
LogFileUtil.logAndWrite(Log.ERROR, "ClashUtil",
"switchProxyGroup: Failed | Status: " + response.code() + " | Response: " + responseBody, null);
}
} catch (Exception e) {
LogFileUtil.logAndWrite(Log.ERROR, "ClashUtil", "switchProxyGroup: Unexpected error", e);
}
}
public static void switchProxyGroup(String groupName, String proxyName, String controllerUrl) {
if (groupName == null || groupName.trim().isEmpty() || proxyName == null || proxyName.trim().isEmpty()) {
LogFileUtil.logAndWrite(Log.ERROR, "ClashUtil", "switchProxyGroup: Invalid arguments", null);
@ -116,26 +186,20 @@ public class ClashUtil {
.put(requestBody)
.build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
try {
Response response = client.newCall(request).execute();
if (response.isSuccessful() && response.body() != null) {
LogFileUtil.logAndWrite(Log.INFO, "ClashUtil", "switchProxyGroup: Switch proxy response", null);
} else {
LogFileUtil.logAndWrite(Log.ERROR, "ClashUtil", "switchProxyGroup: Response is not successful or body is null", null);
}
response.close();
} catch (IOException e) {
LogFileUtil.logAndWrite(Log.ERROR, "ClashUtil", "switchProxyGroup: Failed to switch proxy", e);
System.out.println("Failed to switch proxy: " + e.getMessage());
}
@Override
public void onResponse(Call call, Response response) throws IOException {
try {
if (response.body() != null) {
LogFileUtil.logAndWrite(Log.INFO, "ClashUtil", "switchProxyGroup: Switch proxy response", null);
} else {
LogFileUtil.logAndWrite(Log.ERROR, "ClashUtil", "switchProxyGroup: Response body is null", null);
}
} finally {
response.close();
}
}
});
}
}

View File

@ -1,4 +1,4 @@
package com.example.studyapp.request;
package com.example.retention.request;
// 这是发送到服务端的请求体JSON 格式
public class ScriptResultRequest {

View File

@ -1,4 +1,4 @@
package com.example.studyapp.service;
package com.example.retention.service;
import android.Manifest;
import android.accessibilityservice.AccessibilityService;
@ -6,19 +6,17 @@ import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
import android.os.Build;
import android.os.IBinder;
import android.view.accessibility.AccessibilityEvent;
import android.widget.Toast;
import androidx.core.app.NotificationCompat;
import androidx.core.content.ContextCompat;
import com.example.studyapp.MainActivity;
import com.example.studyapp.R;
import com.example.retention.MainActivity;
import com.example.retention.R;
public class MyAccessibilityService extends AccessibilityService {

View File

@ -1,4 +1,4 @@
package com.example.studyapp.task;
package com.example.retention.task;
public class AfInfo {
public String advertiserId;

View File

@ -1,4 +1,4 @@
package com.example.studyapp.task;
package com.example.retention.task;
// 使用 JSON 库动态生成 JSON 请求体 (使用 Gson 示例)
public class BigoInfo {

View File

@ -1,4 +1,4 @@
package com.example.studyapp.task;
package com.example.retention.task;
public class DeviceInfo {

View File

@ -1,15 +1,9 @@
package com.example.studyapp.task;
import static androidx.core.content.PackageManagerCompat.LOG_TAG;
package com.example.retention.task;
import android.content.Context;
import android.os.Environment;
import android.text.TextUtils;
import android.util.Log;
import com.example.studyapp.utils.LogFileUtil;
import com.example.studyapp.utils.ShellUtils;
import com.google.android.gms.common.util.CollectionUtils;
import com.google.android.gms.common.util.MapUtils;
import com.example.retention.utils.LogFileUtil;
import com.example.retention.utils.ShellUtils;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
@ -18,14 +12,10 @@ import java.io.FileInputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.HttpUrl;
import okhttp3.MediaType;
import okhttp3.MultipartBody;
@ -34,7 +24,6 @@ import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.ResponseBody;
import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.io.FileOutputStream;
@ -150,6 +139,7 @@ public class TaskUtil {
Log.d("TaskUtil", "Built HTTP request for device info download");
try (Response response = okHttpClient.newCall(request).execute()) {
LogFileUtil.logAndWrite(android.util.Log.DEBUG, "TaskUtil", "Response : " + response, null);
// 检查响应是否成功
if (!response.isSuccessful()) {
String errorMessage = "Unexpected response: Code=" + response.code() +
@ -622,28 +612,7 @@ public class TaskUtil {
if (response.isSuccessful() && response.body() != null) {
Log.d("TaskUtil", "Response is successful. Preparing to save file."); // 记录成功响应
// 检查目录是否存在
if (!filesLocationDir.exists()) {
boolean dirCreated = filesLocationDir.mkdirs();
Log.d("TaskUtil", "Directory created: " + filesLocationDir.getAbsolutePath() + " - " + dirCreated);
}
File saveFile = new File(filesLocationDir, fileName);
Log.d("TaskUtil", "Target file path: " + saveFile.getAbsolutePath());
try (InputStream is = response.body().byteStream();
OutputStream os = new BufferedOutputStream(new FileOutputStream(saveFile))) {
Log.d("TaskUtil", "Starting to write file...");
byte[] buffer = new byte[8192];
int bytesRead;
while ((bytesRead = is.read(buffer)) != -1) {
os.write(buffer, 0, bytesRead);
}
Log.i("TaskUtil", "File saved successfully to: " + saveFile.getAbsolutePath());
}
return saveFile;
return saveDownloadedFile(response, filesLocationDir, fileName);
} else {
Log.w("TaskUtil", "Download failed. HTTP code: " + response.code() + ", Message: " + response.message());
@ -666,6 +635,55 @@ public class TaskUtil {
}
}
private static File saveDownloadedFile(Response response, File filesLocationDir, String fileName) throws IOException {
// 1. 更严格的目录检查和处理
if (!filesLocationDir.exists()) {
if (!filesLocationDir.mkdirs()) {
throw new IOException("Failed to create directory: " + filesLocationDir.getAbsolutePath());
}
Log.d("TaskUtil", "Directory created: " + filesLocationDir.getAbsolutePath());
}
// 2. 检查目录是否可写
if (!filesLocationDir.canWrite()) {
throw new IOException("Directory not writable: " + filesLocationDir.getAbsolutePath());
}
// 3. 处理文件名冲突
File saveFile = new File(filesLocationDir, fileName);
if (saveFile.exists()) {
saveFile.delete();
}
// 4. 更完善的写入流程
File tempFile = new File(filesLocationDir, fileName + ".tmp");
try (InputStream is = response.body().byteStream();
OutputStream os = new BufferedOutputStream(new FileOutputStream(tempFile))) {
byte[] buffer = new byte[8192];
int bytesRead;
while ((bytesRead = is.read(buffer)) != -1) {
os.write(buffer, 0, bytesRead);
}
// 5. 原子性重命名确保文件完整
if (!tempFile.renameTo(saveFile)) {
throw new IOException("Failed to rename temp file to: " + saveFile.getAbsolutePath());
}
Log.i("TaskUtil", "File saved successfully to: " + saveFile.getAbsolutePath());
return saveFile;
} catch (IOException e) {
// 6. 清理不完整文件
if (tempFile.exists()) {
boolean deleted = tempFile.delete();
Log.w("TaskUtil", "Deleted incomplete file: " + deleted);
}
throw e;
}
}
private static void validate() {
if (okHttpClient == null) {
throw new IllegalStateException("HttpClient is not initialized");
@ -783,4 +801,3 @@ class Payload {
AfInfo afDeviceObject;
DeviceInfo other;
}

View File

@ -0,0 +1,58 @@
package com.example.retention.utils;
import static com.example.retention.utils.ZipUtils.getAllApkFiles;
import android.util.Log;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
public class ApkInstaller {
public static boolean batchInstallWithRoot(String dirPath) {
// 获取APK文件
List<File> apkFiles = getAllApkFiles(dirPath);
return installSplitApks(apkFiles);
}
private static boolean installSplitApks( List<File> apkFiles) {
// 确保base.apk在第一位
File baseApk = null;
List<File> otherApks = new ArrayList<>();
for (File apk : apkFiles) {
if (apk.getName().contains("base.apk")) {
baseApk = apk;
} else {
otherApks.add(apk);
}
}
if (baseApk == null) {
Log.d("TAG", "installSplitApks: 没有 base apk");
return false;
}
// 构建安装命令
StringBuilder cmd = new StringBuilder("pm install \"")
.append(baseApk.getAbsolutePath()).append("\"");
for (File apk : otherApks) {
cmd.append(" \"").append(apk.getAbsolutePath()).append("\"");
}
Log.d("TAG", "installSplitApks: "+cmd);
// 执行命令
String result = ShellUtils.execRootCmdAndGetResult(cmd.toString());
if (result != null && result.contains("Success")) {
Log.d("TAG", "installSplitApks: install success");
return true;
} else {
Log.d("TAG", "installSplitApks: install failed");
return false;
}
}
}

View File

@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.studyapp.utils;
package com.example.retention.utils;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;

View File

@ -1,4 +1,4 @@
package com.example.studyapp.utils;
package com.example.retention.utils;
import android.util.Log;

View File

@ -1,4 +1,4 @@
package com.example.studyapp.utils;
package com.example.retention.utils;
import android.text.TextUtils;
@ -7,7 +7,6 @@ import java.net.UnknownHostException;
import org.json.JSONObject;
import java.util.concurrent.CompletableFuture;
import java.util.regex.Pattern;
public class IpUtil {
public static boolean isValidIPAddress(String ipAddress) {

View File

@ -1,4 +1,4 @@
package com.example.studyapp.utils;
package com.example.retention.utils;
import android.content.Context;
import android.os.Build;

View File

@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.studyapp.utils;
package com.example.retention.utils;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Member;

View File

@ -1,8 +1,6 @@
package com.example.studyapp.utils;
package com.example.retention.utils;
import android.util.Log;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;

View File

@ -1,6 +1,4 @@
package com.example.studyapp.utils;
import static java.security.AccessController.getContext;
package com.example.retention.utils;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;

View File

@ -0,0 +1,78 @@
package com.example.retention.utils;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.net.ConnectivityManager;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.os.Environment;
import android.util.Log;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.List;
public class Utils {
public static boolean isNetworkAvailable(Context context) {
ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
if (connectivityManager != null) {
Network network = connectivityManager.getActiveNetwork();
if (network != null) {
NetworkCapabilities capabilities = connectivityManager.getNetworkCapabilities(network);
return capabilities != null && capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
&& capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED);
}
}
return false;
}
public static boolean isAppInstalled(Context context, String packageName) {
try {
PackageManager pm = context.getPackageManager();
pm.getPackageInfo(packageName, PackageManager.GET_ACTIVITIES);
return true;
} catch (PackageManager.NameNotFoundException e) {
return false;
}
}
public static boolean isPackageInstalled(Context context, String packageName) {
PackageManager pm = context.getPackageManager();
List<ApplicationInfo> packages = pm.getInstalledApplications(0);
for (ApplicationInfo packageInfo : packages) {
Log.d("TAG", "isPackageInstalled: "+packageInfo.packageName);
if (packageInfo.packageName.equals(packageName)) {
return true;
}
}
return false;
}
public static void writePackageName(String packageName){
File file = new File(Environment.getExternalStorageDirectory(),
"script/packagesname.txt");
File parentDir = file.getParentFile();
if (parentDir != null && !parentDir.exists()) {
boolean dirsCreated = parentDir.mkdirs();
if (!dirsCreated) {
Log.e("FileWrite", "Failed to create directories: " + parentDir);
return;
}
}
LogFileUtil.logAndWrite(Log.INFO,"TAG", "writePackageName: "+packageName, null);
try (BufferedOutputStream bos = new BufferedOutputStream(
new FileOutputStream(file))) {
bos.write(packageName.getBytes(StandardCharsets.UTF_8));
bos.flush(); // 确保数据写入磁盘
} catch (IOException e) {
Log.e("FileWrite", "Failed to write package name: " + packageName, e);
// 6. 可以考虑添加重试机制或通知用户
}
}
}

View File

@ -0,0 +1,87 @@
package com.example.retention.utils;
import android.util.Log;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
public class ZipUtils {
/**
* 解压ZIP文件到指定目录
* @param zipFile ZIP文件路径
* @param destDir 目标目录路径
* @throws IOException 如果解压失败
*/
public static void unzip(String zipFile, String destDir) throws IOException {
File dir = new File(destDir);
// 创建目标目录如果不存在
if (!dir.exists()) {
if (!dir.mkdirs()) {
throw new IOException("Failed to create directory: " + destDir);
}
}
try (ZipInputStream zis = new ZipInputStream(new BufferedInputStream(new FileInputStream(zipFile)))) {
ZipEntry ze;
byte[] buffer = new byte[8192];
int count;
while ((ze = zis.getNextEntry()) != null) {
String fileName = ze.getName();
File file = new File(destDir, fileName);
// 创建必要的父目录
File parent = file.getParentFile();
if (parent != null && !parent.exists()) {
if (!parent.mkdirs()) {
throw new IOException("Failed to create parent directory: " + parent);
}
}
if (ze.isDirectory()) {
if (!file.mkdirs()) {
throw new IOException("Failed to create directory: " + file);
}
} else {
try (FileOutputStream fos = new FileOutputStream(file);
BufferedOutputStream bos = new BufferedOutputStream(fos)) {
while ((count = zis.read(buffer)) != -1) {
bos.write(buffer, 0, count);
}
}
}
zis.closeEntry();
}
}
}
public static List<File> getAllApkFiles(String dirPath) {
List<File> apkFiles = new ArrayList<>();
File dir = new File(dirPath);
if (!dir.exists() || !dir.isDirectory()) {
Log.e("APK", "目录不存在或不是目录: " + dirPath);
return apkFiles;
}
File[] files = dir.listFiles();
if (files == null) return apkFiles;
for (File file : files) {
if (file.isFile() && file.getName().toLowerCase().endsWith(".apk")) {
apkFiles.add(file);
}
}
return apkFiles;
}
}

View File

@ -1,4 +1,4 @@
package com.example.studyapp.worker;
package com.example.retention.worker;
import android.accessibilityservice.AccessibilityService;
import android.content.ComponentName;
@ -10,7 +10,7 @@ import android.text.TextUtils;
import androidx.annotation.NonNull;
import androidx.work.CoroutineWorker;
import androidx.work.WorkerParameters;
import com.example.studyapp.service.MyAccessibilityService;
import com.example.retention.service.MyAccessibilityService;
import kotlin.coroutines.Continuation;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

View File

@ -0,0 +1,101 @@
package com.example.retention.worker;
import static com.example.retention.utils.Utils.isNetworkAvailable;
import android.content.Context;
import android.text.TextUtils;
import android.util.Log;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.work.CoroutineWorker;
import androidx.work.WorkerParameters;
import com.example.retention.MainActivity;
import com.example.retention.autoJS.AutoJsUtil;
import com.example.retention.config.CountryCode;
import com.example.retention.device.ChangeDeviceInfoUtil;
import com.example.retention.proxy.ClashUtil;
import com.example.retention.utils.LogFileUtil;
import com.example.retention.utils.Utils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.UUID;
import kotlin.coroutines.Continuation;
public class LoadDeviceWorker extends CoroutineWorker {
private String androidId = "FyZqWrStUvOpKlMn";
private Context context;
public LoadDeviceWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) {
super(context, workerParams);
this.context = context;
}
/**
* 这段代码是LoadDeviceWorker类中的doWork方法用于执行后台任务其功能如下 生成唯一的taskId调用ChangeDeviceInfoUtil.getDeviceInfoSync获取设备信息 获取packageName和zipName并记录日志
* 如果获取设备信息成功且packageName和zipName非空则调用processPackageInfoWithDeviceInfo处理包信息 若处理成功执行executeSingleLogic方法 否则记录获取设备信息失败的日志 最后返回任务执行结果为成功
*/
@Override
public @Nullable Object doWork(@NotNull Continuation<? super Result> continuation) {
String taskId = UUID.randomUUID().toString();
boolean result = ChangeDeviceInfoUtil.getDeviceInfoSync(taskId, androidId);
String packageName = ChangeDeviceInfoUtil.packageName;
String zipName = ChangeDeviceInfoUtil.zipName;
LogFileUtil.logAndWrite(Log.INFO, "TAG", "doWork: " + result + " " + packageName + " " + zipName, null);
if (result && !TextUtils.isEmpty(packageName) && !TextUtils.isEmpty(zipName)) {
boolean isSuccess = ChangeDeviceInfoUtil.processPackageInfoWithDeviceInfo(packageName, zipName, getApplicationContext(), androidId, taskId);
if (isSuccess) {
executeSingleLogic(context, packageName);
}
} else {
LogFileUtil.logAndWrite(Log.INFO, "TAG", "doWork: get Device info false", null);
}
return Result.success();
}
/**
* 这段代码是LoadDeviceWorker类中的executeSingleLogic方法用于执行特定逻辑其功能如下 检查传入的packageName是否为空或空字符串如果是则记录日志并退出方法 记录代理未激活并调用startProxyVpn方法启动VPN
* 记录更改设备信息并调用ChangeDeviceInfoUtil.changeDeviceInfo方法 记录运行AutoJs脚本并调用Utils.writePackageName和AutoJsUtil.runAutojsScript方法
*/
public void executeSingleLogic(Context context, String packageName) {
if (packageName == null || packageName.isEmpty()) {
LogFileUtil.logAndWrite(Log.INFO, "MainActivity", "executeSingleLogic: Package name is empty", null);
return;
}
LogFileUtil.logAndWrite(Log.INFO, "MainActivity", "executeSingleLogic: Proxy not active, starting VPN", null);
startProxyVpn(context);
LogFileUtil.logAndWrite(Log.INFO, "MainActivity", "executeSingleLogic: Changing device info", null);
ChangeDeviceInfoUtil.changeDeviceInfo(packageName, context, MainActivity.armClient);
LogFileUtil.logAndWrite(Log.INFO, "MainActivity", "executeSingleLogic: Running AutoJs script", null);
Utils.writePackageName(packageName);
AutoJsUtil.runAutojsScript(context);
}
private void startProxyVpn(Context context) {
if (!isNetworkAvailable(context)) {
Toast.makeText(context, "Network is not available", Toast.LENGTH_SHORT).show();
LogFileUtil.logAndWrite(Log.ERROR, "MainActivity", "startProxyVpn: Network is not available.", null);
}
// if (!(context instanceof Activity)) {
// Toast.makeText(context, "Context must be an Activity", Toast.LENGTH_SHORT).show();
// LogFileUtil.logAndWrite(Log.ERROR, "MainActivity", "startProxyVpn: Context is not an Activity.",null);
// return;
// }
try {
ClashUtil.startProxy(context);
ClashUtil.switchProxyWithPort(CountryCode.switchCountry());
// ClashUtil.switchProxyGroup("PROXY", "my-socks5-proxy", "http://127.0.0.1:6170");
} catch (Exception e) {
LogFileUtil.logAndWrite(Log.ERROR, "MainActivity", "startProxyVpn: Failed to start VPN", e);
Toast.makeText(context, "Failed to start VPN: " +
(e.getMessage() != null ? e.getMessage() : "Unknown error"),
Toast.LENGTH_SHORT).show();
}
}
}

View File

@ -1,418 +0,0 @@
package com.example.studyapp;
import static com.example.studyapp.task.TaskUtil.infoUpload;
import android.app.Activity;
import android.app.AlertDialog;
import android.net.Uri;
import android.content.Context;
import android.content.Intent;
import android.net.ConnectivityManager;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.os.Build;
import android.os.Bundle;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;
import android.widget.Button;
import android.widget.Toast;
import android.Manifest;
import android.content.pm.PackageManager;
import android.os.Environment;
import androidx.annotation.Nullable;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.appcompat.app.AppCompatActivity;
import androidx.work.PeriodicWorkRequest;
import androidx.work.WorkManager;
import com.example.studyapp.autoJS.AutoJsUtil;
import com.example.studyapp.device.ChangeDeviceInfoUtil;
import com.example.studyapp.proxy.ClashUtil;
import com.example.studyapp.service.MyAccessibilityService;
import com.example.studyapp.task.TaskUtil;
import com.example.studyapp.utils.LogFileUtil;
import com.example.studyapp.utils.ShellUtils;
import com.example.studyapp.worker.CheckAccessibilityWorker;
import java.io.File;
import java.lang.ref.WeakReference;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class MainActivity extends AppCompatActivity {
private static WeakReference<MainActivity> instance;
private static final int REQUEST_CODE_STORAGE_PERMISSION = 1;
private static final int ALLOW_ALL_FILES_ACCESS_PERMISSION_CODE = 1001;
public static ExecutorService executorService;
// 假设我们从配置文件中提取出了以下 name 项数据仅为部分示例数据
private final String[] proxyNames = {
"mr", "sr", "bq", "ml", "ht", "ga", "mk", "by", "pr", "hr", "hu",
"in", "gt", "at", "kh", "bn", "mg", "kr", "ca", "gh", "ma", "md",
"je", "pa", "ba", "mm", "ir", "gy", "mt", "ae", "es", "ng", "ls",
"ag", "pk", "bd", "kn", "mw", "ve", "hk", "cv", "hn", "tm", "us",
"cz", "ly", "gb", "kz", "it", "bh", "sn", "fi", "co", "sx", "bm",
"fj", "cw", "st", "bw", "fr", "bb", "tg", "ci", "gd", "ne", "bj",
"nz", "rs", "do", "cl", "lb", "nl", "re", "aw", "ug", "sv", "ar",
"jo", "bg", "jp", "rw", "py", "mn", "ec", "uz", "ro", "cu", "gu",
"xk", "sy", "so", "zm", "tz", "ni", "sc", "my", "gf", "na", "zw",
"la", "et", "ao", "ua", "om", "np", "mx", "mz", "dm", "ye", "gi",
"cr", "cm", "ph", "am", "th", "ch", "br", "sd", "ie", "bo", "bs",
"tc", "vg", "pe", "sa", "dk", "tn", "ee", "jm", "lc", "pt", "qa",
"ge", "ps"
};
public static volatile String scriptResult;
// 初始化 ExecutorService
private void initializeExecutorService() {
if (executorService == null || executorService.isShutdown()) {
executorService = new ThreadPoolExecutor(
1, // 核心线程数
1, // 最大线程数
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(50), // 阻塞队列
new ThreadPoolExecutor.AbortPolicy() // 拒绝策略
);
}
}
/**
* 获取 Android 设备的 ANDROID_ID
*
* @param context 应用上下文
* @return 设备的 ANDROID_ID若无法获取则返回 null
*/
private String getAndroidId(Context context) {
if (context == null) {
LogFileUtil.logAndWrite(Log.ERROR, "MainActivity", "getAndroidId: Context cannot be null",null);
throw new IllegalArgumentException("Context cannot be null");
}
try {
return Settings.Secure.getString(
context.getContentResolver(),
Settings.Secure.ANDROID_ID
);
} catch (Exception e) {
LogFileUtil.logAndWrite(Log.ERROR, "MainActivity", "getAndroidId: Failed to get ANDROID_ID",e);
return null;
}
}
private static final int REQUEST_CODE_PERMISSIONS = 100;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LogFileUtil.initialize(this);
setContentView(R.layout.activity_main);
instance = new WeakReference<>(this);
LogFileUtil.logAndWrite(Log.INFO, "MainActivity", "onCreate: Initializing application",null);
initializeExecutorService();
System.setProperty("java.library.path", this.getApplicationInfo().nativeLibraryDir);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(
this,
new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
REQUEST_CODE_STORAGE_PERMISSION
);
}
} else {
if (!Environment.isExternalStorageManager()) {
Intent intent = new Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION);
intent.setData(Uri.parse("package:" + getPackageName()));
startActivityForResult(intent, ALLOW_ALL_FILES_ACCESS_PERMISSION_CODE);
}
}
if (!isNetworkAvailable(this)) {
Toast.makeText(this, "Network is not available", Toast.LENGTH_SHORT).show();
LogFileUtil.logAndWrite(Log.ERROR, "MainActivity", "Network not available, closing app.",null);
finish();
}
LogFileUtil.logAndWrite(Log.INFO, "MainActivity", "onCreate: Setting up work manager",null);
PeriodicWorkRequest workRequest = new PeriodicWorkRequest.Builder(CheckAccessibilityWorker.class, 15, TimeUnit.MINUTES)
.build();
WorkManager.getInstance(this).enqueue(workRequest);
LogFileUtil.logAndWrite(Log.INFO, "MainActivity", "onCreate: Setting up UI components",null);
Button runScriptButton = findViewById(R.id.run_script_button);
if (runScriptButton != null) {
runScriptButton.setOnClickListener(v -> AutoJsUtil.runAutojsScript(this));
} else {
LogFileUtil.logAndWrite(Log.WARN, "MainActivity", "Run Script Button not found",null);
Toast.makeText(this, "Button not found", Toast.LENGTH_SHORT).show();
}
Button connectButton = findViewById(R.id.connectVpnButton);
if (connectButton != null) {
connectButton.setOnClickListener(v -> {
String chmodResult = ShellUtils.execRootCmdAndGetResult("pm uninstall com.rovio.baba");
});
// connectButton.setOnClickListener(v -> startProxyVpn(this));
} else {
Toast.makeText(this, "Connect button not found", Toast.LENGTH_SHORT).show();
}
Button disconnectButton = findViewById(R.id.disconnectVpnButton);
if (disconnectButton != null) {
disconnectButton.setOnClickListener(v -> ClashUtil.stopProxy(this));
} else {
Toast.makeText(this, "Disconnect button not found", Toast.LENGTH_SHORT).show();
}
Button switchVpnButton = findViewById(R.id.switchVpnButton);
if (switchVpnButton != null) {
switchVpnButton.setOnClickListener(v -> ClashUtil.switchProxyGroup("GLOBAL", "us", "http://127.0.0.1:6170"));
} else {
Toast.makeText(this, "Disconnect button not found", Toast.LENGTH_SHORT).show();
}
Button modifyDeviceInfoButton = findViewById(R.id.modifyDeviceInfoButton);
if (modifyDeviceInfoButton != null) {
modifyDeviceInfoButton.setOnClickListener(v -> ChangeDeviceInfoUtil.changeDeviceInfo(getPackageName(), this));
} else {
Toast.makeText(this, "modifyDeviceInfo button not found", Toast.LENGTH_SHORT).show();
}
Button resetDeviceInfoButton = findViewById(R.id.resetDeviceInfoButton);
if (resetDeviceInfoButton != null) {
resetDeviceInfoButton.setOnClickListener(v -> ChangeDeviceInfoUtil.resetChangedDeviceInfo(getPackageName(), this));
} else {
Toast.makeText(this, "resetDeviceInfo button not found", Toast.LENGTH_SHORT).show();
}
// 初始化 ChangeDeviceInfoUtil
String androidId = getAndroidId(this);
String taskId = UUID.randomUUID().toString();
ChangeDeviceInfoUtil.initialize("US", 2, this, androidId);
// 获取输入框和按钮
Button executeButton = findViewById(R.id.execute_button);
Button stopExecuteButton = findViewById(R.id.stop_execute_button);
// 设置按钮的点击事件
if (executeButton != null) {
executeButton.setOnClickListener(v -> {
executeButton.setEnabled(false);
Toast.makeText(this, "任务正在执行", Toast.LENGTH_SHORT).show();
executeLogic(androidId,taskId);
});
}
if (stopExecuteButton != null) {
stopExecuteButton.setOnClickListener(v -> {
if (executorService != null && !executorService.isShutdown()) {
executorService.shutdownNow();
ClashUtil.stopProxy(this);
AutoJsUtil.stopAutojsScript(this);
executeButton.setEnabled(true);
}
});
} else {
Toast.makeText(this, "Stop button not found", Toast.LENGTH_SHORT).show();
}
}
private void executeLogic(String androidId, String taskId) {
LogFileUtil.logAndWrite(Log.INFO, "MainActivity", "executeLogic: Start execution",null);
if (!isNetworkAvailable(this)) {
LogFileUtil.logAndWrite(Log.ERROR, "MainActivity", "executeLogic: Network is not available!",null);
Toast.makeText(this, "网络不可用,请检查网络连接", Toast.LENGTH_SHORT).show();
return;
}
LogFileUtil.logAndWrite(Log.INFO, "MainActivity", "executeLogic: Submitting job to executor",null);
initializeExecutorService();
executorService.submit(() -> {
try {
AutoJsUtil.registerScriptResultReceiver(this);
AutoJsUtil.flag = true;
while (isRunning) {
// synchronized (taskLock) {
// while (!AutoJsUtil.flag && isRunning) {
// taskLock.wait(30000);
// }
//
//
// AutoJsUtil.flag = false;
// }
if (!isRunning) break;
// 从队列中获取最新的 scriptResult
LogFileUtil.logAndWrite(Log.INFO, "MainActivity", "executeSingleLogic: Running AutoJs script",null);
ChangeDeviceInfoUtil.getDeviceInfo(taskId, androidId);
executeSingleLogic();
String currentScriptResult = scriptResultQueue.take();
TaskUtil.execSaveTask(this, androidId, taskId, currentScriptResult);
LogFileUtil.logAndWrite(android.util.Log.DEBUG, "MainActivity", "----发送result------;" + currentScriptResult, null);
if (currentScriptResult != null && !TextUtils.isEmpty(currentScriptResult)) {
infoUpload(this, androidId, currentScriptResult);
}
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
LogFileUtil.logAndWrite(Log.ERROR, "MainActivity", "executeLogic: Thread interrupted while waiting", e);
} catch (Exception e) {
LogFileUtil.logAndWrite(Log.ERROR, "MainActivity", "executeLogic: Unexpected task error.", e);
}
});
}
public static final LinkedBlockingQueue<String> scriptResultQueue = new LinkedBlockingQueue<>();
private volatile boolean isRunning = true; // 主线程运行状态
public static final Object taskLock = new Object(); // 任务逻辑锁
public void executeSingleLogic() {
LogFileUtil.logAndWrite(Log.INFO, "MainActivity", "executeSingleLogic: Proxy not active, starting VPN",null);
startProxyVpn(this);
LogFileUtil.logAndWrite(Log.INFO, "MainActivity", "executeSingleLogic: Changing device info",null);
ChangeDeviceInfoUtil.changeDeviceInfo(getPackageName(), this);
LogFileUtil.logAndWrite(Log.INFO, "MainActivity", "executeSingleLogic: Running AutoJs script",null);
AutoJsUtil.runAutojsScript(this);
}
private void startProxyVpn(Context context) {
if (!isNetworkAvailable(context)) {
Toast.makeText(context, "Network is not available", Toast.LENGTH_SHORT).show();
LogFileUtil.logAndWrite(Log.ERROR, "MainActivity", "startProxyVpn: Network is not available.",null);
return;
}
if (!(context instanceof Activity)) {
Toast.makeText(context, "Context must be an Activity", Toast.LENGTH_SHORT).show();
LogFileUtil.logAndWrite(Log.ERROR, "MainActivity", "startProxyVpn: Context is not an Activity.",null);
return;
}
try {
ClashUtil.startProxy(context); // 在主线程中调用
ClashUtil.switchProxyGroup("GLOBAL", "us", "http://127.0.0.1:6170");
} catch (Exception e) {
LogFileUtil.logAndWrite(Log.ERROR, "MainActivity", "startProxyVpn: Failed to start VPN",e);
Toast.makeText(context, "Failed to start VPN: " + (e.getMessage() != null ? e.getMessage() : "Unknown error"), Toast.LENGTH_SHORT).show();
}
}
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == REQUEST_CODE_STORAGE_PERMISSION) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
Toast.makeText(this, "Storage Permissions granted", Toast.LENGTH_SHORT).show();
} else {
// 提示权限被拒绝同时允许用户重新授予权限
showPermissionExplanationDialog();
}
}
if (requestCode == REQUEST_CODE_PERMISSIONS) {
boolean allGranted = true;
for (int result : grantResults) {
if (result != PackageManager.PERMISSION_GRANTED) {
allGranted = false;
break;
}
}
if (allGranted) {
// 所有权限已授予
startMyForegroundService();
} else {
Toast.makeText(this, "未授予必要权限,请检查设置", Toast.LENGTH_SHORT).show();
}
}
}
private void startMyForegroundService() {
Intent serviceIntent = new Intent(this, MyAccessibilityService.class);
ContextCompat.startForegroundService(this, serviceIntent);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
switch (requestCode) {
case ALLOW_ALL_FILES_ACCESS_PERMISSION_CODE:
handleStoragePermissionResult(resultCode);
break;
default:
break;
}
}
private void handleStoragePermissionResult(int resultCode) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && Environment.isExternalStorageManager()) {
Toast.makeText(this, "Storage Permissions granted", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(this, "请授予所有文件管理权限", Toast.LENGTH_SHORT).show();
finish();
}
}
private void showPermissionExplanationDialog() {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("Permission Required")
.setMessage("Storage Permission is required for the app to function. Please enable it in Settings.")
.setPositiveButton("Go to Settings", (dialog, which) -> {
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
Uri uri = Uri.fromParts("package", getPackageName(), null);
intent.setData(uri);
startActivity(intent);
})
.setNegativeButton("Cancel", (dialog, which) -> finish())
.show();
}
@Override
protected void onDestroy() {
LogFileUtil.logAndWrite(Log.INFO, "MainActivity", "onDestroy: Cleaning up resources",null);
super.onDestroy();
instance.clear();
if (AutoJsUtil.scriptResultReceiver != null) {
unregisterReceiver(AutoJsUtil.scriptResultReceiver);
AutoJsUtil.scriptResultReceiver = null;
}
if (executorService != null) {
executorService.shutdown();
}
isRunning = false;
synchronized (taskLock) {
taskLock.notifyAll();
}
}
public static MainActivity getInstance() {
return instance.get(); // 返回实例
}
private boolean isNetworkAvailable(Context context) {
ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
if (connectivityManager != null) {
Network network = connectivityManager.getActiveNetwork();
if (network != null) {
NetworkCapabilities capabilities = connectivityManager.getNetworkCapabilities(network);
return capabilities != null && capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
&& capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED);
}
}
return false;
}
}

View File

@ -1,4 +0,0 @@
package com.example.studyapp
class ScriptRepository {
}

View File

@ -1,556 +0,0 @@
package com.example.studyapp.device;
import static com.example.studyapp.autoJS.AutoJsUtil.isAppInstalled;
import static com.example.studyapp.utils.LogFileUtil.logAndWrite;
import android.content.ContentResolver;
import android.content.Context;
import android.net.Uri;
import android.util.Log;
import com.example.studyapp.task.AfInfo;
import com.example.studyapp.task.BigoInfo;
import com.example.studyapp.task.DeviceInfo;
import com.example.studyapp.task.TaskUtil;
import com.example.studyapp.utils.HttpUtil;
import com.example.studyapp.utils.LogFileUtil;
import com.example.studyapp.utils.ShellUtils;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.json.JSONException;
import org.json.JSONObject;
import java.lang.reflect.Method;
public class ChangeDeviceInfoUtil {
private static JSONObject bigoDeviceObject;
private static JSONObject afDeviceObject;
public static String buildBigoUrl(String country, int tag) {
return Uri.parse("http://8.217.137.25/tt/zj/dispatcher!bigo.do")
.buildUpon()
.appendQueryParameter("country", country)
.appendQueryParameter("tag", String.valueOf(tag))
.toString();
}
public static String buildAfUrl(String country, int tag) {
return Uri.parse("http://8.217.137.25/tt/zj/dispatcher!af.do")
.buildUpon()
.appendQueryParameter("country", country)
.appendQueryParameter("tag", String.valueOf(tag))
.toString();
}
// 创建一个线程池用于执行网络任务
private static final ExecutorService executorService = Executors.newSingleThreadExecutor();
public static void initialize(String country, int tag, Context context, String androidId) {
LogFileUtil.logAndWrite(android.util.Log.DEBUG, LOG_TAG, "Initializing device info...", null);
executorService.submit(() -> {
try {
LogFileUtil.logAndWrite(android.util.Log.DEBUG, LOG_TAG, "Starting network requests...", null);
String bigoJson = fetchJsonSafely(buildBigoUrl(country, tag), "bigoJson");
String afJson = fetchJsonSafely(buildAfUrl(country, tag), "afJson");
fallBackToNetworkData(bigoJson, afJson);
logDeviceObjects();
processPackageInfo(TaskUtil.getPackageInfo(androidId), context);
} catch (IOException | JSONException e) {
LogFileUtil.logAndWrite(android.util.Log.ERROR, LOG_TAG, "Error occurred during initialization", e);
} catch (Exception e) {
LogFileUtil.logAndWrite(android.util.Log.ERROR, LOG_TAG, "Error occurred during initialization", e);
}
});
}
public static void getDeviceInfo(String taskId, String androidId) {
if (taskId == null || androidId == null || taskId.isBlank() || androidId.isBlank()) {
LogFileUtil.logAndWrite(android.util.Log.ERROR, LOG_TAG, "Invalid task",null);
return;
}
executorService.submit(() -> {
String response = "";
try{
response = executeQuerySafely(androidId, taskId);
} catch (Exception e) {
e.printStackTrace();
}
if (response == null || response.isBlank()) {
LogFileUtil.logAndWrite(android.util.Log.ERROR, LOG_TAG, "Error occurred during query", null);
return;
}
if (isValidResponse(response)) {
try {
synchronized (ChangeDeviceInfoUtil.class) { // 防止并发访问
parseAndSetDeviceObjects(response);
}
} catch (JSONException e) {
LogFileUtil.logAndWrite(android.util.Log.ERROR, LOG_TAG, "Error parsing JSON", e);
}
} else {
LogFileUtil.logAndWrite(android.util.Log.ERROR, LOG_TAG, "Error occurred during query",null);
}
});
}
private static String fetchJsonSafely(String url, String logKey) throws IOException {
String json = null;
try {
json = HttpUtil.requestGet(url);
if (json != null && !json.isEmpty()) {
LogFileUtil.logAndWrite(android.util.Log.DEBUG, LOG_TAG, "Received " + logKey + ": " + json, null);
return json;
} else {
LogFileUtil.logAndWrite(android.util.Log.WARN, LOG_TAG, "Empty or null response for: " + logKey + ", retrying...", null);
}
} catch (IOException e) {
LogFileUtil.logAndWrite(android.util.Log.WARN, LOG_TAG, "Error fetching " + logKey + ": " + e.getMessage() + ", retrying...", e);
}
// Retry once if the initial attempt failed
json = HttpUtil.requestGet(url);
if (json != null && !json.isEmpty()) {
LogFileUtil.logAndWrite(android.util.Log.DEBUG, LOG_TAG, "Retry success for " + logKey + ": " + json, null);
return json;
} else {
LogFileUtil.logAndWrite(android.util.Log.ERROR, LOG_TAG, "Retry failed for " + logKey + ", response is still null or empty.", null);
throw new IOException("Failed to fetch valid JSON for " + logKey);
}
}
private static boolean isValidResponse(String response) {
return response != null && !response.isBlank() && !response.equals("{}\n")
&& !response.equals("{\"afDeviceObject\": null, \"bigoDeviceObject\": null, \"other\": null}")
&& response.trim().startsWith("{");
}
private static void parseAndSetDeviceObjects(String response) throws JSONException {
String cleanJson = response.trim();
if (cleanJson.startsWith("\"") && cleanJson.endsWith("\"")) {
cleanJson = cleanJson.substring(1, cleanJson.length() - 1).replace("\\\"", "\"");
}
JSONObject responseJson = new JSONObject(cleanJson);
bigoDeviceObject = responseJson.optJSONObject("bigoDeviceObject");
afDeviceObject = responseJson.optJSONObject("afDeviceObject");
}
private static void fallBackToNetworkData(String bigoJson, String afJson) throws JSONException {
bigoDeviceObject = new JSONObject(bigoJson).optJSONObject("device");
afDeviceObject = new JSONObject(afJson).optJSONObject("device");
}
private static void logDeviceObjects() {
LogFileUtil.logAndWrite(android.util.Log.INFO, LOG_TAG, "Final bigoDeviceObject: " + bigoDeviceObject, null);
LogFileUtil.logAndWrite(android.util.Log.INFO, LOG_TAG, "Final DeviceInfo: " + afDeviceObject, null);
}
private static void processPackageInfo(Map<String, String> packageInfo, Context context) {
if (packageInfo != null) {
for (Map.Entry<String, String> entry : packageInfo.entrySet()) {
String packageName = entry.getKey();
if (!isAppInstalled(packageName)) {
processPackage(packageName, entry.getValue(), context);
} else {
LogFileUtil.logAndWrite(android.util.Log.WARN, LOG_TAG, "Package not installed: " + packageName, null);
}
}
}
}
private static void processPackage(String packageName, String zipName, Context context) {
try {
File filesDir = new File(context.getExternalFilesDir(null).getAbsolutePath());
File file = TaskUtil.downloadCodeFile(zipName, filesDir);
if (file != null && file.exists()) {
File destFile = new File(context.getCacheDir(), packageName+"_download"+".apk");
if (destFile.exists()) {
TaskUtil.delFileSh(destFile.getAbsolutePath());
}
TaskUtil.unZip(destFile, file);
if (destFile.exists()) {
installApk(destFile.getAbsolutePath());
}
TaskUtil.delFileSh(destFile.getAbsolutePath());
TaskUtil.delFileSh(file.getAbsolutePath());
LogFileUtil.logAndWrite(Log.DEBUG, LOG_TAG, "Processed package: " + packageName, null);
} else {
LogFileUtil.logAndWrite(android.util.Log.WARN, LOG_TAG, "File download failed for package: " + packageName, null);
}
} catch (Exception e) {
LogFileUtil.logAndWrite(android.util.Log.ERROR, LOG_TAG, "Error processing package: " + packageName, e);
}
}
public static boolean installApk(String apkFilePath) {
// 检查文件路径
if (apkFilePath == null || apkFilePath.trim().isEmpty()) {
LogFileUtil.logAndWrite(Log.ERROR, "ShellUtils", "Invalid APK file path", null);
return false;
}
// 确保文件存在
File apkFile = new File(apkFilePath);
if (!apkFile.exists()) {
LogFileUtil.logAndWrite(Log.ERROR, "ShellUtils", "APK file not found: " + apkFilePath, null);
return false;
}
// 构造安装命令
String command = "pm install " + apkFilePath;
// 执行命令并获取结果
String result = ShellUtils.execRootCmdAndGetResult(command);
if (result != null && result.contains("Success")) {
Log.d("ShellUtils", "APK installed successfully!");
return true;
} else {
LogFileUtil.logAndWrite(Log.ERROR, "ShellUtils", "Failed to install APK. Result: " + result, null);
return false;
}
}
private static final String LOG_TAG = "TaskUtil";
private static final String INIT_LOG_TEMPLATE = "initialize method called with parameters: Country: %s, Tag: %d, Android ID: %s";
private static final String CONTEXT_LOG_TEMPLATE = "Context instance: %s";
// 辅助方法执行网络请求
private static String fetchJson(String url) throws IOException {
return HttpUtil.requestGet(url);
}
// 辅助方法执行任务
private static String executeQuerySafely(String androidId, String taskId) {
return TaskUtil.execQueryTask(androidId,taskId);
}
public static void changeDeviceInfo(String current_pkg_name, Context context) {
BigoInfo bigoDevice;
if (bigoDeviceObject != null) {
// BIGO
String cpuClockSpeed = bigoDeviceObject.optString("cpu_clock_speed");
String gaid = bigoDeviceObject.optString("gaid");
String userAgent = bigoDeviceObject.optString("User-Agent");
String osLang = bigoDeviceObject.optString("os_lang");
String osVer = bigoDeviceObject.optString("os_ver");
String tz = bigoDeviceObject.optString("tz");
String systemCountry = bigoDeviceObject.optString("system_country");
String simCountry = bigoDeviceObject.optString("sim_country");
long romFreeIn = bigoDeviceObject.optLong("rom_free_in");
String resolution = bigoDeviceObject.optString("resolution");
String vendor = bigoDeviceObject.optString("vendor");
int batteryScale = bigoDeviceObject.optInt("bat_scale");
// String model = deviceObject.optString("model");
String net = bigoDeviceObject.optString("net");
int dpi = bigoDeviceObject.optInt("dpi");
long romFreeExt = bigoDeviceObject.optLong("rom_free_ext");
String dpiF = bigoDeviceObject.optString("dpi_f");
int cpuCoreNum = bigoDeviceObject.optInt("cpu_core_num");
bigoDevice = new BigoInfo();
bigoDevice.cpuClockSpeed = cpuClockSpeed;
bigoDevice.gaid = gaid;
bigoDevice.userAgent = userAgent;
bigoDevice.osLang = osLang;
bigoDevice.osVer = osVer;
bigoDevice.tz = tz;
bigoDevice.systemCountry = systemCountry;
bigoDevice.simCountry = simCountry;
bigoDevice.romFreeIn = romFreeIn;
bigoDevice.resolution = resolution;
bigoDevice.vendor = vendor;
bigoDevice.batteryScale = batteryScale;
bigoDevice.net = net;
bigoDevice.dpi = dpi;
bigoDevice.romFreeExt = romFreeExt;
bigoDevice.dpiF = dpiF;
bigoDevice.cpuCoreNum = cpuCoreNum;
TaskUtil.setBigoDevice(bigoDevice);
try {
callVCloudSettings_put(current_pkg_name + ".system_country", systemCountry, context);
callVCloudSettings_put(current_pkg_name + ".sim_country", simCountry, context);
callVCloudSettings_put(current_pkg_name + ".rom_free_in", String.valueOf(romFreeIn), context);
callVCloudSettings_put(current_pkg_name + ".resolution", resolution, context);
callVCloudSettings_put(current_pkg_name + ".vendor", vendor, context);
callVCloudSettings_put(current_pkg_name + ".battery_scale", String.valueOf(batteryScale), context);
callVCloudSettings_put(current_pkg_name + ".os_lang", osLang, context);
// callVCloudSettings_put(current_pkg_name + ".model", model, context);
callVCloudSettings_put(current_pkg_name + ".net", net, context);
callVCloudSettings_put(current_pkg_name + ".dpi", String.valueOf(dpi), context);
callVCloudSettings_put(current_pkg_name + ".rom_free_ext", String.valueOf(romFreeExt), context);
callVCloudSettings_put(current_pkg_name + ".dpi_f", dpiF, context);
callVCloudSettings_put(current_pkg_name + ".cpu_core_num", String.valueOf(cpuCoreNum), context);
callVCloudSettings_put(current_pkg_name + ".cpu_clock_speed", cpuClockSpeed, context);
callVCloudSettings_put(current_pkg_name + "_gaid", gaid, context);
// **User-Agent**
callVCloudSettings_put(current_pkg_name + "_user_agent", userAgent, context);
// **os_lang**系统语言
callVCloudSettings_put(current_pkg_name + "_os_lang", osLang, context);
// **os_ver**
callVCloudSettings_put(current_pkg_name + "_os_ver", osVer, context);
// **tz** (时区)
callVCloudSettings_put(current_pkg_name + "_tz", tz, context);
} catch (Throwable e) {
logAndWrite(android.util.Log.ERROR, "ChangeDeviceInfoUtil", "Error occurred while changing device info", e);
throw new RuntimeException("Error occurred in changeDeviceInfo", e);
}
}
DeviceInfo deviceInfo;
AfInfo afDevice;
if (afDeviceObject != null) {
String advertiserId = afDeviceObject.optString(".advertiserId");
String model = afDeviceObject.optString(".model");
String brand = afDeviceObject.optString(".brand");
String androidId = afDeviceObject.optString(".android_id");
int xPixels = afDeviceObject.optInt(".deviceData.dim.x_px");
int yPixels = afDeviceObject.optInt(".deviceData.dim.y_px");
int densityDpi = afDeviceObject.optInt(".deviceData.dim.d_dpi");
String country = afDeviceObject.optString(".country");
String batteryLevel = afDeviceObject.optString(".batteryLevel");
String stackInfo = Thread.currentThread().getStackTrace()[2].toString();
String product = afDeviceObject.optString(".product");
String network = afDeviceObject.optString(".network");
String langCode = afDeviceObject.optString(".lang_code");
String cpuAbi = afDeviceObject.optString(".deviceData.cpu_abi");
int yDp = afDeviceObject.optInt(".deviceData.dim.ydp");
afDevice = new AfInfo();
afDevice.advertiserId = advertiserId;
afDevice.model = model;
afDevice.brand = brand;
afDevice.androidId = androidId;
afDevice.xPixels = xPixels;
afDevice.yPixels = yPixels;
afDevice.densityDpi = densityDpi;
afDevice.country = country;
afDevice.batteryLevel = batteryLevel;
afDevice.stackInfo = stackInfo;
afDevice.product = product;
afDevice.network = network;
afDevice.langCode = langCode;
afDevice.cpuAbi = cpuAbi;
afDevice.yDp = yDp;
TaskUtil.setAfDevice(afDevice);
String lang = afDeviceObject.optString(".lang");
String ro_product_brand = afDeviceObject.optString("ro.product.brand", "");
String ro_product_model = afDeviceObject.optString("ro.product.model", "");
String ro_product_manufacturer = afDeviceObject.optString("ro.product.manufacturer", "");
String ro_product_device = afDeviceObject.optString("ro.product.device", "");
String ro_product_name = afDeviceObject.optString("ro.product.name", "");
String ro_build_version_incremental = afDeviceObject.optString("ro.build.version.incremental", "");
String ro_build_fingerprint = afDeviceObject.optString("ro.build.fingerprint", "");
String ro_odm_build_fingerprint = afDeviceObject.optString("ro.odm.build.fingerprint", "");
String ro_product_build_fingerprint = afDeviceObject.optString("ro.product.build.fingerprint", "");
String ro_system_build_fingerprint = afDeviceObject.optString("ro.system.build.fingerprint", "");
String ro_system_ext_build_fingerprint = afDeviceObject.optString("ro.system_ext.build.fingerprint", "");
String ro_vendor_build_fingerprint = afDeviceObject.optString("ro.vendor.build.fingerprint", "");
String ro_build_platform = afDeviceObject.optString("ro.board.platform", "");
String persist_sys_cloud_drm_id = afDeviceObject.optString("persist.sys.cloud.drm.id", "");
int persist_sys_cloud_battery_capacity = afDeviceObject.optInt("persist.sys.cloud.battery.capacity", -1);
String persist_sys_cloud_gpu_gl_vendor = afDeviceObject.optString("persist.sys.cloud.gpu.gl_vendor", "");
String persist_sys_cloud_gpu_gl_renderer = afDeviceObject.optString("persist.sys.cloud.gpu.gl_renderer", "");
String persist_sys_cloud_gpu_gl_version = afDeviceObject.optString("persist.sys.cloud.gpu.gl_version", "");
String persist_sys_cloud_gpu_egl_vendor = afDeviceObject.optString("persist.sys.cloud.gpu.egl_vendor", "");
String persist_sys_cloud_gpu_egl_version = afDeviceObject.optString("persist.sys.cloud.gpu.egl_version", "");
String global_android_id = afDeviceObject.optString(".android_id", "");
String anticheck_pkgs = afDeviceObject.optString(".anticheck_pkgs", "");
String pm_list_features = afDeviceObject.optString(".pm_list_features", "");
String pm_list_libraries = afDeviceObject.optString(".pm_list_libraries", "");
String system_http_agent = afDeviceObject.optString("system.http.agent", "");
String webkit_http_agent = afDeviceObject.optString("webkit.http.agent", "");
String com_fk_tools_pkgInfo = afDeviceObject.optString(".pkg_info", "");
String appsflyerKey = afDeviceObject.optString(".appsflyerKey", "");
String appUserId = afDeviceObject.optString(".appUserId", "");
String disk = afDeviceObject.optString(".disk", "");
String operator = afDeviceObject.optString(".operator", "");
String cell_mcc = afDeviceObject.optString(".cell.mcc", "");
String cell_mnc = afDeviceObject.optString(".cell.mnc", "");
String date1 = afDeviceObject.optString(".date1", "");
String date2 = afDeviceObject.optString(".date2", "");
String bootId = afDeviceObject.optString("BootId", "");
deviceInfo = new DeviceInfo();
deviceInfo.lang = lang;
deviceInfo.roProductBrand = ro_product_brand;
deviceInfo.roProductModel = ro_product_model;
deviceInfo.roProductManufacturer = ro_product_manufacturer;
deviceInfo.roProductDevice = ro_product_device;
deviceInfo.roProductName = ro_product_name;
deviceInfo.roBuildVersionIncremental = ro_build_version_incremental;
deviceInfo.roBuildFingerprint = ro_build_fingerprint;
deviceInfo.roOdmBuildFingerprint = ro_odm_build_fingerprint;
deviceInfo.roProductBuildFingerprint = ro_product_build_fingerprint;
deviceInfo.roSystemBuildFingerprint = ro_system_build_fingerprint;
deviceInfo.roSystemExtBuildFingerprint = ro_system_ext_build_fingerprint;
deviceInfo.roVendorBuildFingerprint = ro_vendor_build_fingerprint;
deviceInfo.roBuildPlatform = ro_build_platform;
deviceInfo.persistSysCloudDrmId = persist_sys_cloud_drm_id;
deviceInfo.persistSysCloudBatteryCapacity = persist_sys_cloud_battery_capacity;
deviceInfo.persistSysCloudGpuGlVendor = persist_sys_cloud_gpu_gl_vendor;
deviceInfo.persistSysCloudGpuGlRenderer = persist_sys_cloud_gpu_gl_renderer;
deviceInfo.persistSysCloudGpuGlVersion = persist_sys_cloud_gpu_gl_version;
deviceInfo.persistSysCloudGpuEglVendor = persist_sys_cloud_gpu_egl_vendor;
deviceInfo.persistSysCloudGpuEglVersion = persist_sys_cloud_gpu_egl_version;
TaskUtil.setDeviceInfo(deviceInfo);
try {
callVCloudSettings_put(current_pkg_name + ".advertiserId", advertiserId, context);
callVCloudSettings_put(current_pkg_name + ".model", model, context);
callVCloudSettings_put(current_pkg_name + ".brand", brand, context);
callVCloudSettings_put(current_pkg_name + ".android_id", androidId, context);
callVCloudSettings_put(current_pkg_name + ".lang", lang, context);
callVCloudSettings_put(current_pkg_name + ".country", country, context);
callVCloudSettings_put(current_pkg_name + ".batteryLevel", batteryLevel, context);
callVCloudSettings_put(current_pkg_name + "_screen.optMetrics.stack", stackInfo, context);
callVCloudSettings_put(current_pkg_name + ".product", product, context);
callVCloudSettings_put(current_pkg_name + ".network", network, context);
callVCloudSettings_put(current_pkg_name + ".cpu_abi", cpuAbi, context);
callVCloudSettings_put(current_pkg_name + ".lang_code", langCode, context);
// **广告标识符 (advertiserId)** **启用状态**
boolean isAdIdEnabled = true; // 默认启用广告 ID
callVCloudSettings_put(current_pkg_name + ".advertiserIdEnabled", String.valueOf(isAdIdEnabled), context);
JSONObject displayMetrics = new JSONObject();
displayMetrics.put("widthPixels", xPixels);
displayMetrics.put("heightPixels", yPixels);
displayMetrics.put("densityDpi", densityDpi);
displayMetrics.put("yDp", yDp);
callVCloudSettings_put("screen.device.displayMetrics", displayMetrics.toString(), context);
if (!ShellUtils.hasRootAccess()) {
LogFileUtil.writeLogToFile("ERROR", "ChangeDeviceInfoUtil", "Root access is required to execute system property changes");
}
// 设置机型, 直接设置属性
ShellUtils.execRootCmd("setprop ro.product.brand " + ro_product_brand);
ShellUtils.execRootCmd("setprop ro.product.model " + ro_product_model);
ShellUtils.execRootCmd("setprop ro.product.manufacturer " + ro_product_manufacturer);
ShellUtils.execRootCmd("setprop ro.product.device " + ro_product_device);
ShellUtils.execRootCmd("setprop ro.product.name " + ro_product_name);
ShellUtils.execRootCmd("setprop ro.build.version.incremental " + ro_build_version_incremental);
ShellUtils.execRootCmd("setprop ro.build.fingerprint " + ro_build_fingerprint);
ShellUtils.execRootCmd("setprop ro.odm.build.fingerprint " + ro_odm_build_fingerprint);
ShellUtils.execRootCmd("setprop ro.product.build.fingerprint " + ro_product_build_fingerprint);
ShellUtils.execRootCmd("setprop ro.system.build.fingerprint " + ro_system_build_fingerprint);
ShellUtils.execRootCmd("setprop ro.system_ext.build.fingerprint " + ro_system_ext_build_fingerprint);
ShellUtils.execRootCmd("setprop ro.vendor.build.fingerprint " + ro_vendor_build_fingerprint);
ShellUtils.execRootCmd("setprop ro.board.platform " + ro_build_platform);
// Native.setBootId(bootId);
// 修改drm id
ShellUtils.execRootCmd("setprop persist.sys.cloud.drm.id " + persist_sys_cloud_drm_id);
// 电量模拟需要大于1000
ShellUtils.execRootCmd("setprop persist.sys.cloud.battery.capacity " + persist_sys_cloud_battery_capacity);
ShellUtils.execRootCmd("setprop persist.sys.cloud.gpu.gl_vendor " + persist_sys_cloud_gpu_gl_vendor);
ShellUtils.execRootCmd("setprop persist.sys.cloud.gpu.gl_renderer " + persist_sys_cloud_gpu_gl_renderer);
// 这个值不能随便改 必须是 OpenGL ES %d.%d 这个格式
ShellUtils.execRootCmd("setprop persist.sys.cloud.gpu.gl_version " + persist_sys_cloud_gpu_gl_version);
ShellUtils.execRootCmd("setprop persist.sys.cloud.gpu.egl_vendor " + persist_sys_cloud_gpu_egl_vendor);
ShellUtils.execRootCmd("setprop persist.sys.cloud.gpu.egl_version " + persist_sys_cloud_gpu_egl_version);
} catch (Throwable e) {
logAndWrite(Log.ERROR, "ChangeDeviceInfoUtil", "Error occurred in changeDeviceInfo", e);
throw new RuntimeException("Error occurred in changeDeviceInfo", e);
}
}
}
private static void callVCloudSettings_put(String key, String value, Context context) {
if (context == null) {
logAndWrite(Log.ERROR, "ChangeDeviceInfoUtil", "Context cannot be null", null);
throw new IllegalArgumentException("Context cannot be null");
}
if (key == null || key.isEmpty()) {
logAndWrite(Log.ERROR, "ChangeDeviceInfoUtil", "Key cannot be null or empty", null);
throw new IllegalArgumentException("Key cannot be null or empty");
}
if (value == null) {
logAndWrite(Log.ERROR, "ChangeDeviceInfoUtil", "Value cannot be null", null);
throw new IllegalArgumentException("Value cannot be null");
}
try {
// 获取类对象
Class<?> clazz = Class.forName("android.provider.VCloudSettings$Global");
Method putStringMethod = clazz.getDeclaredMethod("putString", ContentResolver.class, String.class, String.class);
putStringMethod.setAccessible(true);
// 调用方法
putStringMethod.invoke(null, context.getContentResolver(), key, value);
Log.d("Debug", "putString executed successfully.");
} catch (ClassNotFoundException e) {
logAndWrite(Log.WARN, "ChangeDeviceInfoUtil", "Class not found: android.provider.VCloudSettings$Global. This may not be supported on this device.", e);
} catch (NoSuchMethodException e) {
logAndWrite(Log.WARN, "ChangeDeviceInfoUtil", "Method not found: android.provider.VCloudSettings$Global.putString. This may not be supported on this", e);
} catch (InvocationTargetException e) {
Throwable cause = e.getTargetException();
if (cause instanceof SecurityException) {
logAndWrite(Log.ERROR, "ChangeDeviceInfoUtil", "Error occurred in changeDeviceInfo", cause);
} else {
logAndWrite(Log.ERROR, "ChangeDeviceInfoUtil", "Error occurred in changeDeviceInfo", cause);
}
} catch (Exception e) {
logAndWrite(Log.ERROR, "ChangeDeviceInfoUtil", "Unexpected error during putString invocation", e);
}
}
public static void resetChangedDeviceInfo(String current_pkg_name, Context context) {
try {
Native.setBootId("00000000000000000000000000000000");
} catch (Exception e) {
logAndWrite(Log.ERROR, "ChangeDeviceInfoUtil", "Error occurred in reset", e);
}
if (!ShellUtils.hasRootAccess()) {
LogFileUtil.logAndWrite(Log.ERROR, "ChangeDeviceInfoUtil", "Root access is required to execute system property changes", null);
return;
}
ShellUtils.execRootCmd("cmd settings2 delete global global_android_id");
ShellUtils.execRootCmd("cmd settings2 delete global pm_list_features");
ShellUtils.execRootCmd("cmd settings2 delete global pm_list_libraries");
ShellUtils.execRootCmd("cmd settings2 delete global anticheck_pkgs");
ShellUtils.execRootCmd("cmd settings2 delete global " + current_pkg_name + "_android_id");
ShellUtils.execRootCmd("cmd settings2 delete global " + current_pkg_name + "_adb_enabled");
ShellUtils.execRootCmd("cmd settings2 delete global " + current_pkg_name + "_development_settings_enabled");
ShellUtils.execRootCmd("setprop persist.sys.cloud.drm.id \"\"");
ShellUtils.execRootCmd("setprop persist.sys.cloud.gpu.gl_vendor \"\"");
ShellUtils.execRootCmd("setprop persist.sys.cloud.gpu.gl_renderer \"\"");
// 这个值不能随便改 必须是 OpenGL ES %d.%d 这个格式
ShellUtils.execRootCmd("setprop persist.sys.cloud.gpu.gl_version \"\"");
ShellUtils.execRootCmd("setprop persist.sys.cloud.gpu.egl_vendor \"\"");
ShellUtils.execRootCmd("setprop persist.sys.cloud.gpu.egl_version \"\"");
ShellUtils.execRootCmd("setprop ro.product.brand Vortex");
ShellUtils.execRootCmd("setprop ro.product.model HD65_Select");
ShellUtils.execRootCmd("setprop ro.product.manufacturer Vortex");
ShellUtils.execRootCmd("setprop ro.product.device HD65_Select");
ShellUtils.execRootCmd("setprop ro.product.name HD65_Select");
ShellUtils.execRootCmd("setprop ro.build.version.incremental 20240306");
ShellUtils.execRootCmd("setprop ro.build.fingerprint \"Vortex/HD65_Select/HD65_Select:13/TP1A.220624.014/20240306:user/release-keys\"");
ShellUtils.execRootCmd("setprop ro.board.platform sm8150p");
}
}

View File

@ -1,63 +0,0 @@
{
"cpuClockSpeed": "cpu_clock_speed_value",
"gaid": "gaid_value",
"userAgent": "User-Agent_value",
"osLang": "os_lang_value",
"osVer": "os_ver_value",
"tz": "tz_value",
"systemCountry": "system_country_value",
"simCountry": "sim_country_value",
"romFreeIn": "rom_free_in_value",
"resolution": "resolution_value",
"vendor": "vendor_value",
"batteryScale": "bat_scale_value",
"net": "net_value",
"dpi": "dpi_value",
"romFreeExt": "rom_free_ext_value",
"dpiF": "dpi_f_value",
"cpuCoreNum": "cpu_core_num_value",
"afDeviceObject": {
"advertiserId": "advertiserId_value",
"model": "model_value",
"brand": "brand_value",
"androidId": "android_id_value",
"xPixels": "x_px_value",
"yPixels": "y_px_value",
"densityDpi": "density_dpi_value",
"country": "country_value",
"batteryLevel": "batteryLevel_value",
"stackInfo": "stack_info_value",
"product": "product_value",
"network": "network_value",
"langCode": "lang_code_value",
"cpuAbi": "cpu_abi_value",
"yDp": "ydp_value",
"lang": "lang_value",
"roProductDetails": {
"brand": "ro_product_brand_value",
"model": "ro_product_model_value",
"manufacturer": "ro_product_manufacturer_value",
"device": "ro_product_device_value",
"name": "ro_product_name_value"
},
"roBuildDetails": {
"versionIncremental": "ro_build_version_incremental_value",
"fingerprint": "ro_build_fingerprint_value",
"productBuildFingerprint": "ro_product_build_fingerprint_value",
"systemBuildFingerprint": "ro_system_build_fingerprint_value",
"systemExtBuildFingerprint": "ro_system_ext_build_fingerprint_value",
"vendorBuildFingerprint": "ro_vendor_build_fingerprint_value"
},
"cloudProperties": {
"gpuProperties": {
"glVendor": "persist_sys_cloud_gpu_gl_vendor_value",
"glRenderer": "persist_sys_cloud_gpu_gl_renderer_value",
"glVersion": "persist_sys_cloud_gpu_gl_version_value",
"eglVendor": "persist_sys_cloud_gpu_egl_vendor_value",
"eglVersion": "persist_sys_cloud_gpu_egl_version_value"
},
"drmId": "persist_sys_cloud_drm_id_value",
"batteryCapacity": "persist_sys_cloud_battery_capacity_value"
}
}
}

View File

@ -4,79 +4,94 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp"
tools:context=".MainActivity">
tools:context="com.example.retention.MainActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:divider="@android:color/darker_gray"
android:showDividers="middle"
android:dividerPadding="8dp">
android:dividerPadding="8dp"
android:orientation="vertical"
android:showDividers="middle">
<!-- VPN 分组标题 -->
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="VPN 操作"
android:padding="8dp"
android:gravity="center"
android:textAppearance="@style/TextAppearance.AppCompat.Medium" />
android:padding="8dp"
android:text="VPN 操作"
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
android:visibility="gone" />
<!-- VPN 按钮 -->
<Button
android:id="@+id/run_script_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="运行 脚本" />
android:text="运行 脚本"
android:visibility="gone" />
<Button
android:id="@+id/connectVpnButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="开启 VPN" />
android:text="开启 VPN"
android:visibility="gone" />
<Button
android:id="@+id/disconnectVpnButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="断开 VPN" />
android:text="断开 VPN"
android:visibility="gone" />
<Button
android:id="@+id/switchVpnButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="切换 VPN" />
android:text="切换 VPN"
android:visibility="gone" />
<!-- 设备信息分组标题 -->
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="设备信息操作"
android:padding="8dp"
android:gravity="center"
android:textAppearance="@style/TextAppearance.AppCompat.Medium" />
android:padding="8dp"
android:text="设备信息操作"
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
android:visibility="gone" />
<!-- 设备信息按钮 -->
<Button
android:id="@+id/modifyDeviceInfoButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="修改设备信息" />
android:text="修改设备信息"
android:visibility="gone" />
<Button
android:id="@+id/resetDeviceInfoButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="重置设备信息" />
android:text="重置设备信息"
android:visibility="gone" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Bigo操作"
android:padding="8dp"
android:gravity="center"
android:padding="8dp"
android:text="Bigo操作"
android:textAppearance="@style/TextAppearance.AppCompat.Medium" />
<Button
android:id="@+id/execute_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="一键执行" />
<Button
android:id="@+id/stop_execute_button"
android:layout_width="wrap_content"

View File

@ -1,4 +1,4 @@
<resources>
<string name="app_name">study_app</string>
<string name="app_name">Retention</string>
<string name="accessibility_service_description">This is an accessibility service.</string>
</resources>

View File

@ -13,5 +13,6 @@
<domain includeSubdomains="true">8.217.137.25</domain>
<domain includeSubdomains="true">47.238.96.231</domain>
<domain includeSubdomains="true">192.168.30.80</domain>
<domain includeSubdomains="true">39.103.73.250</domain> <!-- 你的特定 IP 地址 -->
</domain-config>
</network-security-config>

View File

@ -1,4 +1,4 @@
package com.example.studyapp;
package com.example.retention;
import org.junit.Test;

View File

@ -1,4 +1,4 @@
package com.example.studyapp.task;
package com.example.retention.task;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
@ -131,14 +131,14 @@ public class TaskUtilTest {
// 运行上传方法
String taskId = "asddasdasd";
TaskUtil.postDeviceInfo("b3d893cf9de3a85a", taskId, "com.example.studyapp");
TaskUtil.postDeviceInfo("b3d893cf9de3a85a", taskId, "com.example.retention");
}
@Test
public void testGetDeviceInfoSync_Success() throws Exception {
// 运行上传方法
TaskUtil.getDeviceInfoSync("b3d893cf9de3a85a");
// TaskUtil.getDeviceInfoSync("b3d893cf9de3a85a");
}
// @Test

2
cmd
View File

@ -1,4 +1,4 @@
V2243A:/ # ls -l /data/user/0/com.example.studyapp/files/
V2243A:/ # ls -l /data/user/0/com.example.retention/files/
total 37516
-rw-rw-rw- 1 u0_a135 u0_a135 2398 2025-05-27 10:43 config.json
-rw------- 1 u0_a135 u0_a135 24 2025-05-27 10:42 profileInstalled

30
err.log
View File

@ -2,26 +2,26 @@
2025-06-23 17:01:42.320 297-462 ClipboardService system_server E Denying clipboard access to org.autojs.autojs6, application is not in focus nor is it a system service for user 0
2025-06-23 17:01:42.569 297-3144 ClipboardService system_server E Denying clipboard access to org.autojs.autojs6, application is not in focus nor is it a system service for user 0
2025-06-23 17:01:43.415 1532-1532 ActivityInfoProvider org.autojs.autojs6 D setLatestComponent: com.android.settings/com.android.settings.SubSettings com.android.settings/com.android.settings.SubSettings
2025-06-23 17:01:44.188 2002-2092 MemInfoService com.android.expansiontools D memScan: [MemEntity(id=null, pid=35830, user=u0_a104, name=Clash for Android, pkg=com.github.kr328.clash, res=139, time=2, total=6066, ts=1750669303948, pr=20, ni=0, virt=1228, shr=84, s=S, cpu=0.0, mem=4.2, args=com.github.kr328.clash), MemEntity(id=null, pid=63204, user=u0_a104, name=Clash for Android, pkg=com.github.kr328.clash, res=123, time=92, total=6066, ts=1750669303948, pr=20, ni=0, virt=1126, shr=38, s=S, cpu=0.0, mem=3.7, args=com.github.kr328.clash:core), MemEntity(id=null, pid=63228, user=u0_a104, name=Clash for Android, pkg=com.github.kr328.clash, res=25, time=259, total=6066, ts=1750669303948, pr=20, ni=0, virt=704, shr=10, s=S, cpu=0.0, mem=0.7, args=libclash.so), MemEntity(id=null, pid=51143, user=u0_a234, name=Script helper, pkg=com.example.studyapp, res=209, time=9, total=6066, ts=1750669303948, pr=20, ni=0, virt=14336, shr=111, s=S, cpu=36.0, mem=6.3, args=com.example.studyapp), MemEntity(id=null, pid=1532, user=u0_a100, name=AutoJs6, pkg=org.autojs.autojs6, res=307, time=374, total=6066, ts=1750669303948, pr=20, ni=0, virt=33792, shr=179, s=S, cpu=0.0, mem=9.3, args=org.autojs.autojs6), MemEntity(id=null, pid=56880, user=u0_a100, name=AutoJs6, pkg=org.autojs.autojs6, res=0, time=0, total=6066, ts=1750669303948, pr=20, ni=0, virt=10240, shr=0, s=S, cpu=0.0, mem=0.0, args=sh)]
2025-06-23 17:01:44.188 2002-2092 MemInfoService com.android.expansiontools D memScan: [MemEntity(id=null, pid=35830, user=u0_a104, name=Clash for Android, pkg=com.github.kr328.clash, res=139, time=2, total=6066, ts=1750669303948, pr=20, ni=0, virt=1228, shr=84, s=S, cpu=0.0, mem=4.2, args=com.github.kr328.clash), MemEntity(id=null, pid=63204, user=u0_a104, name=Clash for Android, pkg=com.github.kr328.clash, res=123, time=92, total=6066, ts=1750669303948, pr=20, ni=0, virt=1126, shr=38, s=S, cpu=0.0, mem=3.7, args=com.github.kr328.clash:core), MemEntity(id=null, pid=63228, user=u0_a104, name=Clash for Android, pkg=com.github.kr328.clash, res=25, time=259, total=6066, ts=1750669303948, pr=20, ni=0, virt=704, shr=10, s=S, cpu=0.0, mem=0.7, args=libclash.so), MemEntity(id=null, pid=51143, user=u0_a234, name=Script helper, pkg=com.example.retention, res=209, time=9, total=6066, ts=1750669303948, pr=20, ni=0, virt=14336, shr=111, s=S, cpu=36.0, mem=6.3, args=com.example.retention), MemEntity(id=null, pid=1532, user=u0_a100, name=AutoJs6, pkg=org.autojs.autojs6, res=307, time=374, total=6066, ts=1750669303948, pr=20, ni=0, virt=33792, shr=179, s=S, cpu=0.0, mem=9.3, args=org.autojs.autojs6), MemEntity(id=null, pid=56880, user=u0_a100, name=AutoJs6, pkg=org.autojs.autojs6, res=0, time=0, total=6066, ts=1750669303948, pr=20, ni=0, virt=10240, shr=0, s=S, cpu=0.0, mem=0.0, args=sh)]
2025-06-23 17:01:45.397 1532-1532 ActivityInfoProvider org.autojs.autojs6 D setLatestComponent: com.android.settings/androidx.appcompat.app.AlertDialog com.android.settings/androidx.appcompat.app.AlertDialog
2025-06-23 17:01:47.565 1532-1532 ActivityInfoProvider org.autojs.autojs6 D setLatestComponent: com.android.settings/androidx.appcompat.app.AlertDialog com.android.settings/androidx.appcompat.app.AlertDialog
2025-06-23 17:01:51.409 1532-1532 ActivityInfoProvider org.autojs.autojs6 D setLatestComponent: com.android.settings/com.android.settings.SubSettings com.android.settings/com.android.settings.SubSettings
2025-06-23 17:01:54.631 1532-1532 ActivityInfoProvider org.autojs.autojs6 D setLatestComponent: com.android.settings/com.android.settings.Settings$AccessibilitySettingsActivity com.android.settings/com.android.settings.Settings$AccessibilitySettingsActivity
2025-06-23 17:01:54.661 1532-1532 ActivityInfoProvider org.autojs.autojs6 D setLatestComponent: com.android.settings/com.android.settings.Settings$AccessibilitySettingsActivity com.android.settings/com.android.settings.Settings$AccessibilitySettingsActivity
2025-06-23 17:01:54.733 2002-2092 MemInfoService com.android.expansiontools D memScan: [MemEntity(id=null, pid=35830, user=u0_a104, name=Clash for Android, pkg=com.github.kr328.clash, res=139, time=2, total=6066, ts=1750669314510, pr=20, ni=0, virt=1228, shr=84, s=S, cpu=0.0, mem=4.2, args=com.github.kr328.clash), MemEntity(id=null, pid=63204, user=u0_a104, name=Clash for Android, pkg=com.github.kr328.clash, res=123, time=92, total=6066, ts=1750669314510, pr=20, ni=0, virt=1126, shr=38, s=S, cpu=0.0, mem=3.7, args=com.github.kr328.clash:core), MemEntity(id=null, pid=63228, user=u0_a104, name=Clash for Android, pkg=com.github.kr328.clash, res=25, time=259, total=6066, ts=1750669314510, pr=20, ni=0, virt=704, shr=10, s=S, cpu=0.0, mem=0.7, args=libclash.so), MemEntity(id=null, pid=51143, user=u0_a234, name=Script helper, pkg=com.example.studyapp, res=203, time=13, total=6066, ts=1750669314510, pr=20, ni=0, virt=14336, shr=111, s=S, cpu=3.8, mem=6.1, args=com.example.studyapp), MemEntity(id=null, pid=1532, user=u0_a100, name=AutoJs6, pkg=org.autojs.autojs6, res=307, time=374, total=6066, ts=1750669314510, pr=20, ni=0, virt=33792, shr=179, s=S, cpu=0.0, mem=9.3, args=org.autojs.autojs6), MemEntity(id=null, pid=56880, user=u0_a100, name=AutoJs6, pkg=org.autojs.autojs6, res=0, time=0, total=6066, ts=1750669314510, pr=20, ni=0, virt=10240, shr=0, s=S, cpu=0.0, mem=0.0, args=sh)]
2025-06-23 17:01:54.733 2002-2092 MemInfoService com.android.expansiontools D memScan: [MemEntity(id=null, pid=35830, user=u0_a104, name=Clash for Android, pkg=com.github.kr328.clash, res=139, time=2, total=6066, ts=1750669314510, pr=20, ni=0, virt=1228, shr=84, s=S, cpu=0.0, mem=4.2, args=com.github.kr328.clash), MemEntity(id=null, pid=63204, user=u0_a104, name=Clash for Android, pkg=com.github.kr328.clash, res=123, time=92, total=6066, ts=1750669314510, pr=20, ni=0, virt=1126, shr=38, s=S, cpu=0.0, mem=3.7, args=com.github.kr328.clash:core), MemEntity(id=null, pid=63228, user=u0_a104, name=Clash for Android, pkg=com.github.kr328.clash, res=25, time=259, total=6066, ts=1750669314510, pr=20, ni=0, virt=704, shr=10, s=S, cpu=0.0, mem=0.7, args=libclash.so), MemEntity(id=null, pid=51143, user=u0_a234, name=Script helper, pkg=com.example.retention, res=203, time=13, total=6066, ts=1750669314510, pr=20, ni=0, virt=14336, shr=111, s=S, cpu=3.8, mem=6.1, args=com.example.retention), MemEntity(id=null, pid=1532, user=u0_a100, name=AutoJs6, pkg=org.autojs.autojs6, res=307, time=374, total=6066, ts=1750669314510, pr=20, ni=0, virt=33792, shr=179, s=S, cpu=0.0, mem=9.3, args=org.autojs.autojs6), MemEntity(id=null, pid=56880, user=u0_a100, name=AutoJs6, pkg=org.autojs.autojs6, res=0, time=0, total=6066, ts=1750669314510, pr=20, ni=0, virt=10240, shr=0, s=S, cpu=0.0, mem=0.0, args=sh)]
2025-06-23 17:01:56.366 1532-1532 ActivityInfoProvider org.autojs.autojs6 D setLatestComponent: com.android.settings/com.android.settings.Settings$AppManageExternalStorageActivity com.android.settings/com.android.settings.Settings$AppManageExternalStorageActivity
2025-06-23 17:01:56.413 1532-1532 ActivityInfoProvider org.autojs.autojs6 D setLatestComponent: com.android.settings/com.android.settings.Settings$AppManageExternalStorageActivity com.android.settings/com.android.settings.Settings$AppManageExternalStorageActivity
2025-06-23 17:01:59.870 1532-1532 NotificationObserver org.autojs.autojs6 D onNotification: [Storage Permissions granted]; EventType: TYPE_NOTIFICATION_STATE_CHANGED; EventTime: 870589469; PackageName: com.example.studyapp; MovementGranularity: 0; Action: 0; ContentChangeTypes: []; WindowChangeTypes: [] [ ClassName: android.widget.Toast; Text: [Storage Permissions granted]; ContentDescription: null; ItemCount: -1; CurrentItemIndex: -1; Enabled: false; Password: false; Checked: false; FullScreen: false; Scrollable: false; BeforeText: null; FromIndex: -1; ToIndex: -1; ScrollX: 0; ScrollY: 0; MaxScrollX: 0; MaxScrollY: 0; ScrollDeltaX: -1; ScrollDeltaY: -1; AddedCount: -1; RemovedCount: -1; ParcelableData: null ]; recordCount: 0
2025-06-23 17:01:59.924 1532-1532 ActivityInfoProvider org.autojs.autojs6 D setLatestComponent: com.example.studyapp/com.example.studyapp.MainActivity com.example.studyapp/com.example.studyapp.MainActivity
2025-06-23 17:02:05.235 2002-2092 MemInfoService com.android.expansiontools D memScan: [MemEntity(id=null, pid=35830, user=u0_a104, name=Clash for Android, pkg=com.github.kr328.clash, res=139, time=2, total=6058, ts=1750669325040, pr=20, ni=0, virt=1228, shr=84, s=S, cpu=0.0, mem=4.2, args=com.github.kr328.clash), MemEntity(id=null, pid=63204, user=u0_a104, name=Clash for Android, pkg=com.github.kr328.clash, res=123, time=92, total=6058, ts=1750669325040, pr=20, ni=0, virt=1126, shr=38, s=S, cpu=0.0, mem=3.7, args=com.github.kr328.clash:core), MemEntity(id=null, pid=63228, user=u0_a104, name=Clash for Android, pkg=com.github.kr328.clash, res=25, time=259, total=6058, ts=1750669325040, pr=20, ni=0, virt=704, shr=10, s=S, cpu=0.0, mem=0.7, args=libclash.so), MemEntity(id=null, pid=51143, user=u0_a234, name=Script helper, pkg=com.example.studyapp, res=206, time=19, total=6058, ts=1750669325040, pr=10, ni=-10, virt=14336, shr=114, s=S, cpu=104.0, mem=6.2, args=com.example.studyapp), MemEntity(id=null, pid=1532, user=u0_a100, name=AutoJs6, pkg=org.autojs.autojs6, res=308, time=374, total=6058, ts=1750669325040, pr=20, ni=0, virt=33792, shr=179, s=S, cpu=0.0, mem=9.3, args=org.autojs.autojs6), MemEntity(id=null, pid=56880, user=u0_a100, name=AutoJs6, pkg=org.autojs.autojs6, res=0, time=0, total=6058, ts=1750669325040, pr=20, ni=0, virt=10240, shr=0, s=S, cpu=0.0, mem=0.0, args=sh)]
2025-06-23 17:01:59.870 1532-1532 NotificationObserver org.autojs.autojs6 D onNotification: [Storage Permissions granted]; EventType: TYPE_NOTIFICATION_STATE_CHANGED; EventTime: 870589469; PackageName: com.example.retention; MovementGranularity: 0; Action: 0; ContentChangeTypes: []; WindowChangeTypes: [] [ ClassName: android.widget.Toast; Text: [Storage Permissions granted]; ContentDescription: null; ItemCount: -1; CurrentItemIndex: -1; Enabled: false; Password: false; Checked: false; FullScreen: false; Scrollable: false; BeforeText: null; FromIndex: -1; ToIndex: -1; ScrollX: 0; ScrollY: 0; MaxScrollX: 0; MaxScrollY: 0; ScrollDeltaX: -1; ScrollDeltaY: -1; AddedCount: -1; RemovedCount: -1; ParcelableData: null ]; recordCount: 0
2025-06-23 17:01:59.924 1532-1532 ActivityInfoProvider org.autojs.autojs6 D setLatestComponent: com.example.retention/com.example.retention.MainActivity com.example.retention/com.example.retention.MainActivity
2025-06-23 17:02:05.235 2002-2092 MemInfoService com.android.expansiontools D memScan: [MemEntity(id=null, pid=35830, user=u0_a104, name=Clash for Android, pkg=com.github.kr328.clash, res=139, time=2, total=6058, ts=1750669325040, pr=20, ni=0, virt=1228, shr=84, s=S, cpu=0.0, mem=4.2, args=com.github.kr328.clash), MemEntity(id=null, pid=63204, user=u0_a104, name=Clash for Android, pkg=com.github.kr328.clash, res=123, time=92, total=6058, ts=1750669325040, pr=20, ni=0, virt=1126, shr=38, s=S, cpu=0.0, mem=3.7, args=com.github.kr328.clash:core), MemEntity(id=null, pid=63228, user=u0_a104, name=Clash for Android, pkg=com.github.kr328.clash, res=25, time=259, total=6058, ts=1750669325040, pr=20, ni=0, virt=704, shr=10, s=S, cpu=0.0, mem=0.7, args=libclash.so), MemEntity(id=null, pid=51143, user=u0_a234, name=Script helper, pkg=com.example.retention, res=206, time=19, total=6058, ts=1750669325040, pr=10, ni=-10, virt=14336, shr=114, s=S, cpu=104.0, mem=6.2, args=com.example.retention), MemEntity(id=null, pid=1532, user=u0_a100, name=AutoJs6, pkg=org.autojs.autojs6, res=308, time=374, total=6058, ts=1750669325040, pr=20, ni=0, virt=33792, shr=179, s=S, cpu=0.0, mem=9.3, args=org.autojs.autojs6), MemEntity(id=null, pid=56880, user=u0_a100, name=AutoJs6, pkg=org.autojs.autojs6, res=0, time=0, total=6058, ts=1750669325040, pr=20, ni=0, virt=10240, shr=0, s=S, cpu=0.0, mem=0.0, args=sh)]
2025-06-23 17:02:08.114 297-16062 ClipboardService system_server E Denying clipboard access to org.autojs.autojs6, application is not in focus nor is it a system service for user 0
2025-06-23 17:02:08.119 297-462 ClipboardService system_server E Denying clipboard access to org.autojs.autojs6, application is not in focus nor is it a system service for user 0
2025-06-23 17:02:08.365 297-11816 ClipboardService system_server E Denying clipboard access to org.autojs.autojs6, application is not in focus nor is it a system service for user 0
2025-06-23 17:02:09.928 1532-1532 NotificationObserver org.autojs.autojs6 D onNotification: [任务正在执行]; EventType: TYPE_NOTIFICATION_STATE_CHANGED; EventTime: 870599525; PackageName: com.example.studyapp; MovementGranularity: 0; Action: 0; ContentChangeTypes: []; WindowChangeTypes: [] [ ClassName: android.widget.Toast; Text: [任务正在执行]; ContentDescription: null; ItemCount: -1; CurrentItemIndex: -1; Enabled: false; Password: false; Checked: false; FullScreen: false; Scrollable: false; BeforeText: null; FromIndex: -1; ToIndex: -1; ScrollX: 0; ScrollY: 0; MaxScrollX: 0; MaxScrollY: 0; ScrollDeltaX: -1; ScrollDeltaY: -1; AddedCount: -1; RemovedCount: -1; ParcelableData: null ]; recordCount: 0
2025-06-23 17:02:12.071 51143-51376 isAppInstalled com.example.studyapp D Checking if app is installed: org.autojs.autojs6
2025-06-23 17:02:12.075 51143-51376 ShellUtils com.example.studyapp D execRootCmdAndGetResult - Started execution for command: pm list packages | grep org.autojs.autojs6
2025-06-23 17:02:12.120 51143-51376 ShellUtils com.example.studyapp D Shell Output: package:org.autojs.autojs6
2025-06-23 17:02:12.223 51143-51376 isAppInstalled com.example.studyapp D App is installed: org.autojs.autojs6
2025-06-23 17:02:09.928 1532-1532 NotificationObserver org.autojs.autojs6 D onNotification: [任务正在执行]; EventType: TYPE_NOTIFICATION_STATE_CHANGED; EventTime: 870599525; PackageName: com.example.retention; MovementGranularity: 0; Action: 0; ContentChangeTypes: []; WindowChangeTypes: [] [ ClassName: android.widget.Toast; Text: [任务正在执行]; ContentDescription: null; ItemCount: -1; CurrentItemIndex: -1; Enabled: false; Password: false; Checked: false; FullScreen: false; Scrollable: false; BeforeText: null; FromIndex: -1; ToIndex: -1; ScrollX: 0; ScrollY: 0; MaxScrollX: 0; MaxScrollY: 0; ScrollDeltaX: -1; ScrollDeltaY: -1; AddedCount: -1; RemovedCount: -1; ParcelableData: null ]; recordCount: 0
2025-06-23 17:02:12.071 51143-51376 isAppInstalled com.example.retention D Checking if app is installed: org.autojs.autojs6
2025-06-23 17:02:12.075 51143-51376 ShellUtils com.example.retention D execRootCmdAndGetResult - Started execution for command: pm list packages | grep org.autojs.autojs6
2025-06-23 17:02:12.120 51143-51376 ShellUtils com.example.retention D Shell Output: package:org.autojs.autojs6
2025-06-23 17:02:12.223 51143-51376 isAppInstalled com.example.retention D App is installed: org.autojs.autojs6
2025-06-23 17:02:12.225 297-3145 ActivityTaskManager system_server I START u0 {flg=0x10000000 cmp=org.autojs.autojs6/org.autojs.autojs.external.open.RunIntentActivity (has extras)} from uid 10234
2025-06-23 17:02:12.240 1532-1532 App org.autojs.autojs6 D setCurrentActivity: org.autojs.autojs.external.open.RunIntentActivity@aad3ba2
2025-06-23 17:02:12.243 1532-1532 ScriptEngineService org.autojs.autojs6 D JavaScriptSource: true
@ -31,7 +31,7 @@
2025-06-23 17:02:12.250 1532-51468 ContextFactory org.autojs.autojs6 D onContextCreated: count = 1
2025-06-23 17:02:12.294 1532-51468 ScriptEngineService org.autojs.autojs6 D onStart
2025-06-23 17:02:12.299 1532-51468 GlobalConsole org.autojs.autojs6 D 09:02:12.299/V: Running [$sdcard/script/main.js].
2025-06-23 17:02:12.307 1532-1532 ActivityInfoProvider org.autojs.autojs6 D setLatestComponent: com.example.studyapp/com.example.studyapp.MainActivity com.example.studyapp/com.example.studyapp.MainActivity
2025-06-23 17:02:12.307 1532-1532 ActivityInfoProvider org.autojs.autojs6 D setLatestComponent: com.example.retention/com.example.retention.MainActivity com.example.retention/com.example.retention.MainActivity
2025-06-23 17:02:12.307 1532-51469 AndroidContextFactory org.autojs.autojs6 D onContextCreated: count = 2
2025-06-23 17:02:12.308 1532-51470 AndroidContextFactory org.autojs.autojs6 D onContextCreated: count = 3
2025-06-23 17:02:12.308 1532-51470 ContextFactory org.autojs.autojs6 D onContextCreated: count = 3
@ -182,14 +182,14 @@
2025-06-23 17:02:14.093 1532-51468 AndroidContextFactory org.autojs.autojs6 D onContextReleased: count = 0
2025-06-23 17:02:14.093 1532-51468 ContextFactory org.autojs.autojs6 D onContextReleased: count = 0
2025-06-23 17:02:14.093 1532-5003 BufferQueueProducer org.autojs.autojs6 E [ImageReader-1080x1920f1m3-1532-8](id:5fc00000021,api:1,p:52,c:1532) dequeueBuffer: BufferQueue has been abandoned
2025-06-23 17:02:14.099 1532-1532 ActivityInfoProvider org.autojs.autojs6 D setLatestComponent: com.example.studyapp/com.example.studyapp.MainActivity com.example.studyapp/com.example.studyapp.MainActivity
2025-06-23 17:02:14.099 1532-1532 ActivityInfoProvider org.autojs.autojs6 D setLatestComponent: com.example.retention/com.example.retention.MainActivity com.example.retention/com.example.retention.MainActivity
2025-06-23 17:02:14.102 1532-5003 BpBinder org.autojs.autojs6 I onLastStrongRef automatically unlinking death recipients: <uncached descriptor>
2025-06-23 17:02:14.103 297-6815 CoreBackPreview system_server D Window{e9771a6 u0 org.autojs.autojs6/org.autojs.autojs.core.activity.StartForResultActivity}: Setting back callback null
2025-06-23 17:02:14.104 297-6815 InputManager-JNI system_server W Input channel object 'e9771a6 org.autojs.autojs6/org.autojs.autojs.core.activity.StartForResultActivity (client)' was disposed without first being removed with the input manager!
2025-06-23 17:02:14.251 297-297 NotificationService system_server I Cannot find enqueued record for key: 0|org.autojs.autojs6|207|null|10100
2025-06-23 17:02:15.923 297-3145 NotificationService system_server W Toast already killed. pkg=org.autojs.autojs6 token=android.os.BinderProxy@fb6cbea
2025-06-23 17:02:15.934 1532-1532 NotificationObserver org.autojs.autojs6 D onNotification: [首次 Threds errorFailed to invoke method app.viewFile. Cannot view /sdcard/script/launcher-release.apk as it doesn't exist (/storage/emulated/0/script/main.js#647), AutoJs6]; EventType: TYPE_NOTIFICATION_STATE_CHANGED; EventTime: 870605533; PackageName: org.autojs.autojs6; MovementGranularity: 0; Action: 0; ContentChangeTypes: []; WindowChangeTypes: [] [ ClassName: android.widget.Toast; Text: [首次 Threds errorFailed to invoke method app.viewFile. Cannot view /sdcard/script/launcher-release.apk as it doesn't exist (/storage/emulated/0/script/main.js#647), AutoJs6]; ContentDescription: null; ItemCount: -1; CurrentItemIndex: -1; Enabled: false; Password: false; Checked: false; FullScreen: false; Scrollable: false; BeforeText: null; FromIndex: -1; ToIndex: -1; ScrollX: 0; ScrollY: 0; MaxScrollX: 0; MaxScrollY: 0; ScrollDeltaX: -1; ScrollDeltaY: -1; AddedCount: -1; RemovedCount: -1; ParcelableData: null ]; recordCount: 0
2025-06-23 17:02:16.164 2002-2092 MemInfoService com.android.expansiontools D memScan: [MemEntity(id=null, pid=35830, user=u0_a104, name=Clash for Android, pkg=com.github.kr328.clash, res=139, time=2, total=5956, ts=1750669335563, pr=20, ni=0, virt=1228, shr=84, s=S, cpu=0.0, mem=4.2, args=com.github.kr328.clash), MemEntity(id=null, pid=63204, user=u0_a104, name=Clash for Android, pkg=com.github.kr328.clash, res=123, time=92, total=5956, ts=1750669335563, pr=20, ni=0, virt=1126, shr=38, s=S, cpu=0.0, mem=3.7, args=com.github.kr328.clash:core), MemEntity(id=null, pid=63228, user=u0_a104, name=Clash for Android, pkg=com.github.kr328.clash, res=25, time=259, total=5956, ts=1750669335563, pr=20, ni=0, virt=704, shr=10, s=S, cpu=0.0, mem=0.7, args=libclash.so), MemEntity(id=null, pid=51143, user=u0_a234, name=Script helper, pkg=com.example.studyapp, res=212, time=22, total=5956, ts=1750669335563, pr=10, ni=-10, virt=14336, shr=115, s=S, cpu=0.0, mem=6.4, args=com.example.studyapp), MemEntity(id=null, pid=1532, user=u0_a100, name=AutoJs6, pkg=org.autojs.autojs6, res=314, time=374, total=5956, ts=1750669335563, pr=20, ni=0, virt=33792, shr=179, s=S, cpu=0.0, mem=9.5, args=org.autojs.autojs6), MemEntity(id=null, pid=56880, user=u0_a100, name=AutoJs6, pkg=org.autojs.autojs6, res=0, time=0, total=5956, ts=1750669335563, pr=20, ni=0, virt=10240, shr=0, s=S, cpu=0.0, mem=0.0, args=sh)]
2025-06-23 17:02:16.164 2002-2092 MemInfoService com.android.expansiontools D memScan: [MemEntity(id=null, pid=35830, user=u0_a104, name=Clash for Android, pkg=com.github.kr328.clash, res=139, time=2, total=5956, ts=1750669335563, pr=20, ni=0, virt=1228, shr=84, s=S, cpu=0.0, mem=4.2, args=com.github.kr328.clash), MemEntity(id=null, pid=63204, user=u0_a104, name=Clash for Android, pkg=com.github.kr328.clash, res=123, time=92, total=5956, ts=1750669335563, pr=20, ni=0, virt=1126, shr=38, s=S, cpu=0.0, mem=3.7, args=com.github.kr328.clash:core), MemEntity(id=null, pid=63228, user=u0_a104, name=Clash for Android, pkg=com.github.kr328.clash, res=25, time=259, total=5956, ts=1750669335563, pr=20, ni=0, virt=704, shr=10, s=S, cpu=0.0, mem=0.7, args=libclash.so), MemEntity(id=null, pid=51143, user=u0_a234, name=Script helper, pkg=com.example.retention, res=212, time=22, total=5956, ts=1750669335563, pr=10, ni=-10, virt=14336, shr=115, s=S, cpu=0.0, mem=6.4, args=com.example.retention), MemEntity(id=null, pid=1532, user=u0_a100, name=AutoJs6, pkg=org.autojs.autojs6, res=314, time=374, total=5956, ts=1750669335563, pr=20, ni=0, virt=33792, shr=179, s=S, cpu=0.0, mem=9.5, args=org.autojs.autojs6), MemEntity(id=null, pid=56880, user=u0_a100, name=AutoJs6, pkg=org.autojs.autojs6, res=0, time=0, total=5956, ts=1750669335563, pr=20, ni=0, virt=10240, shr=0, s=S, cpu=0.0, mem=0.0, args=sh)]
2025-06-23 17:02:18.505 297-3143 NotificationService system_server W Toast already killed. pkg=org.autojs.autojs6 token=android.os.BinderProxy@c258390
2025-06-23 17:02:18.517 1532-1532 NotificationObserver org.autojs.autojs6 D onNotification: [首次 脚本线程 脚本catch结束, AutoJs6]; EventType: TYPE_NOTIFICATION_STATE_CHANGED; EventTime: 870608116; PackageName: org.autojs.autojs6; MovementGranularity: 0; Action: 0; ContentChangeTypes: []; WindowChangeTypes: [] [ ClassName: android.widget.Toast; Text: [首次 脚本线程 脚本catch结束, AutoJs6]; ContentDescription: null; ItemCount: -1; CurrentItemIndex: -1; Enabled: false; Password: false; Checked: false; FullScreen: false; Scrollable: false; BeforeText: null; FromIndex: -1; ToIndex: -1; ScrollX: 0; ScrollY: 0; MaxScrollX: 0; MaxScrollY: 0; ScrollDeltaX: -1; ScrollDeltaY: -1; AddedCount: -1; RemovedCount: -1; ParcelableData: null ]; recordCount: 0
2025-06-23 17:02:21.098 297-3143 NotificationService system_server W Toast already killed. pkg=org.autojs.autojs6 token=android.os.BinderProxy@afcbf66
@ -197,9 +197,9 @@
2025-06-23 17:02:23.095 1532-51478 TrafficStats org.autojs.autojs6 I tagSocketFd(92, 1234, -1) failed with errno-9
2025-06-23 17:02:24.589 1532-51478 TrafficStats org.autojs.autojs6 D tagSocket(92) with statsTag=0x4d2, statsUid=-1
2025-06-23 17:02:24.589 1532-51478 TrafficStats org.autojs.autojs6 I tagSocketFd(92, 1234, -1) failed with errno-9
2025-06-23 17:02:26.771 2002-2092 MemInfoService com.android.expansiontools D memScan: [MemEntity(id=null, pid=35830, user=u0_a104, name=Clash for Android, pkg=com.github.kr328.clash, res=139, time=2, total=6027, ts=1750669346510, pr=20, ni=0, virt=1228, shr=84, s=S, cpu=0.0, mem=4.2, args=com.github.kr328.clash), MemEntity(id=null, pid=63204, user=u0_a104, name=Clash for Android, pkg=com.github.kr328.clash, res=123, time=92, total=6027, ts=1750669346510, pr=20, ni=0, virt=1126, shr=38, s=S, cpu=0.0, mem=3.7, args=com.github.kr328.clash:core), MemEntity(id=null, pid=63228, user=u0_a104, name=Clash for Android, pkg=com.github.kr328.clash, res=25, time=259, total=6027, ts=1750669346510, pr=20, ni=0, virt=704, shr=10, s=S, cpu=0.0, mem=0.7, args=libclash.so), MemEntity(id=null, pid=51143, user=u0_a234, name=Script helper, pkg=com.example.studyapp, res=212, time=22, total=6027, ts=1750669346510, pr=10, ni=-10, virt=14336, shr=115, s=S, cpu=0.0, mem=6.4, args=com.example.studyapp), MemEntity(id=null, pid=1532, user=u0_a100, name=AutoJs6, pkg=org.autojs.autojs6, res=315, time=374, total=6027, ts=1750669346510, pr=20, ni=0, virt=33792, shr=179, s=S, cpu=0.0, mem=9.6, args=org.autojs.autojs6), MemEntity(id=null, pid=56880, user=u0_a100, name=AutoJs6, pkg=org.autojs.autojs6, res=0, time=0, total=6027, ts=1750669346510, pr=20, ni=0, virt=10240, shr=0, s=S, cpu=0.0, mem=0.0, args=sh)]
2025-06-23 17:02:26.771 2002-2092 MemInfoService com.android.expansiontools D memScan: [MemEntity(id=null, pid=35830, user=u0_a104, name=Clash for Android, pkg=com.github.kr328.clash, res=139, time=2, total=6027, ts=1750669346510, pr=20, ni=0, virt=1228, shr=84, s=S, cpu=0.0, mem=4.2, args=com.github.kr328.clash), MemEntity(id=null, pid=63204, user=u0_a104, name=Clash for Android, pkg=com.github.kr328.clash, res=123, time=92, total=6027, ts=1750669346510, pr=20, ni=0, virt=1126, shr=38, s=S, cpu=0.0, mem=3.7, args=com.github.kr328.clash:core), MemEntity(id=null, pid=63228, user=u0_a104, name=Clash for Android, pkg=com.github.kr328.clash, res=25, time=259, total=6027, ts=1750669346510, pr=20, ni=0, virt=704, shr=10, s=S, cpu=0.0, mem=0.7, args=libclash.so), MemEntity(id=null, pid=51143, user=u0_a234, name=Script helper, pkg=com.example.retention, res=212, time=22, total=6027, ts=1750669346510, pr=10, ni=-10, virt=14336, shr=115, s=S, cpu=0.0, mem=6.4, args=com.example.retention), MemEntity(id=null, pid=1532, user=u0_a100, name=AutoJs6, pkg=org.autojs.autojs6, res=315, time=374, total=6027, ts=1750669346510, pr=20, ni=0, virt=33792, shr=179, s=S, cpu=0.0, mem=9.6, args=org.autojs.autojs6), MemEntity(id=null, pid=56880, user=u0_a100, name=AutoJs6, pkg=org.autojs.autojs6, res=0, time=0, total=6027, ts=1750669346510, pr=20, ni=0, virt=10240, shr=0, s=S, cpu=0.0, mem=0.0, args=sh)]
2025-06-23 17:02:35.613 1532-51478 TrafficStats org.autojs.autojs6 D tagSocket(92) with statsTag=0x4d2, statsUid=-1
2025-06-23 17:02:35.613 1532-51478 TrafficStats org.autojs.autojs6 I tagSocketFd(92, 1234, -1) failed with errno-9
2025-06-23 17:02:36.534 1532-51478 TrafficStats org.autojs.autojs6 D tagSocket(92) with statsTag=0x4d2, statsUid=-1
2025-06-23 17:02:36.535 1532-51478 TrafficStats org.autojs.autojs6 I tagSocketFd(92, 1234, -1) failed with errno-9
2025-06-23 17:02:37.413 2002-2092 MemInfoService com.android.expansiontools D memScan: [MemEntity(id=null, pid=35830, user=u0_a104, name=Clash for Android, pkg=com.github.kr328.clash, res=139, time=2, total=6034, ts=1750669357102, pr=20, ni=0, virt=1228, shr=84, s=S, cpu=0.0, mem=4.2, args=com.github.kr328.clash), MemEntity(id=null, pid=63204, user=u0_a104, name=Clash for Android, pkg=com.github.kr328.clash, res=123, time=92, total=6034, ts=1750669357102, pr=20, ni=0, virt=1126, shr=38, s=S, cpu=0.0, mem=3.7, args=com.github.kr328.clash:core), MemEntity(id=null, pid=63228, user=u0_a104, name=Clash for Android, pkg=com.github.kr328.clash, res=25, time=259, total=6034, ts=1750669357102, pr=20, ni=0, virt=704, shr=10, s=S, cpu=0.0, mem=0.7, args=libclash.so), MemEntity(id=null, pid=51143, user=u0_a234, name=Script helper, pkg=com.example.studyapp, res=212, time=22, total=6034, ts=1750669357102, pr=10, ni=-10, virt=14336, shr=115, s=S, cpu=0.0, mem=6.4, args=com.example.studyapp), MemEntity(id=null, pid=1532, user=u0_a100, name=AutoJs6, pkg=org.autojs.autojs6, res=315, time=374, total=6034, ts=1750669357102, pr=20, ni=0, virt=33792, shr=179, s=S, cpu=0.0, mem=9.6, args=org.autojs.autojs6), MemEntity(id=null, pid=56880, user=u0_a100, name=AutoJs6, pkg=org.autojs.autojs6, res=0, time=0, total=6034, ts=1750669357102, pr=20, ni=0, virt=10240, shr=0, s=S, cpu=0.0, mem=0.0, args=sh)]
2025-06-23 17:02:37.413 2002-2092 MemInfoService com.android.expansiontools D memScan: [MemEntity(id=null, pid=35830, user=u0_a104, name=Clash for Android, pkg=com.github.kr328.clash, res=139, time=2, total=6034, ts=1750669357102, pr=20, ni=0, virt=1228, shr=84, s=S, cpu=0.0, mem=4.2, args=com.github.kr328.clash), MemEntity(id=null, pid=63204, user=u0_a104, name=Clash for Android, pkg=com.github.kr328.clash, res=123, time=92, total=6034, ts=1750669357102, pr=20, ni=0, virt=1126, shr=38, s=S, cpu=0.0, mem=3.7, args=com.github.kr328.clash:core), MemEntity(id=null, pid=63228, user=u0_a104, name=Clash for Android, pkg=com.github.kr328.clash, res=25, time=259, total=6034, ts=1750669357102, pr=20, ni=0, virt=704, shr=10, s=S, cpu=0.0, mem=0.7, args=libclash.so), MemEntity(id=null, pid=51143, user=u0_a234, name=Script helper, pkg=com.example.retention, res=212, time=22, total=6034, ts=1750669357102, pr=10, ni=-10, virt=14336, shr=115, s=S, cpu=0.0, mem=6.4, args=com.example.retention), MemEntity(id=null, pid=1532, user=u0_a100, name=AutoJs6, pkg=org.autojs.autojs6, res=315, time=374, total=6034, ts=1750669357102, pr=20, ni=0, virt=33792, shr=179, s=S, cpu=0.0, mem=9.6, args=org.autojs.autojs6), MemEntity(id=null, pid=56880, user=u0_a100, name=AutoJs6, pkg=org.autojs.autojs6, res=0, time=0, total=6034, ts=1750669357102, pr=20, ni=0, virt=10240, shr=0, s=S, cpu=0.0, mem=0.0, args=sh)]

158
main.js
View File

@ -1,13 +1,155 @@
var message = "任务已启动";
// ========== 日志写入文件的封装 ==========
function log(...args) {
const message = `[LOG] ${args.map(a =>
typeof a === 'object' ? JSON.stringify(a) : a
).join(' ')}`;
console.log(message);
files.append("/sdcard/output_log.txt", new Date().toISOString() + " " + message + "\n");
}
function sendMessage(message) {
app.sendBroadcast({
action: "org.autojs.SCRIPT_FINISHED", // 自定义广播 Action
extras: {
result: message,
package: "org.autojs.autojs6"// 将结果附加到广播中
function error(...args) {
const message = `[ERROR] ${args.map(a =>
typeof a === 'object' ? JSON.stringify(a) : a
).join(' ')}`;
console.error(message);
files.append("/sdcard/output_log.txt", new Date().toISOString() + " " + message + "\n");
}
// ========== 网络请求封装 ==========
function httpGetWithCallbacks(url, successCallback, errorCallback) {
http.get(url, {}, (res, err) => {
if (err) {
if (errorCallback) errorCallback(err);
} else {
if (successCallback) successCallback(res);
}
});
}
sendMessage(message + "点击开始")
function processIpApiWithCallbacks(url, processSuccessCallback, errorMsgPrefix, apiDoneCallback) {
httpGetWithCallbacks(url,
(res) => {
let operationSuccessful = false;
if (!res) {
toast(errorMsgPrefix + "请求没有返回有效响应");
log(errorMsgPrefix + "请求没有返回有效响应");
if (apiDoneCallback) apiDoneCallback(new Error("No valid response from request (res is null)"));
return;
}
if (res.statusCode != 200) {
let errorBody = "";
try {
if (res.body && typeof res.body.string === 'function') {
errorBody = res.body.string(); // 读取并关闭
}
} catch (e) {
error(errorMsgPrefix + "读取错误响应体失败: ", e);
}
toast(errorMsgPrefix + "获取失败,状态码: " + res.statusCode);
log(errorMsgPrefix + "获取失败,状态码: " + res.statusCode + ", Body: " + errorBody);
if (apiDoneCallback) apiDoneCallback(new Error(errorMsgPrefix + "获取失败,状态码: " + res.statusCode));
return;
}
try {
let data = res.body.json(); // 读取并关闭
processSuccessCallback(data);
operationSuccessful = true;
} catch (jsonError) {
toast(errorMsgPrefix + "解析JSON失败");
error(errorMsgPrefix + "解析JSON失败: ", jsonError);
if (apiDoneCallback) apiDoneCallback(jsonError);
return;
}
if (apiDoneCallback) apiDoneCallback(null);
},
(requestError) => {
error(errorMsgPrefix + "处理时发生错误: ", requestError);
if (!toast.isShow()) {
toast(errorMsgPrefix + "请求或处理失败");
}
if (apiDoneCallback) apiDoneCallback(requestError);
}
);
}
// ========== 主函数 ==========
function main() {
log("Main function started.");
let tasksCompleted = 0;
const totalTasks = 2;
let errors = [];
function checkAllTasksDone() {
tasksCompleted++;
if (tasksCompleted === totalTasks) {
log("All API calls have finished processing.");
if (errors.length > 0) {
error("One or more API calls failed:");
errors.forEach(err => error(err));
toast("部分API请求失败请检查日志");
} else {
log("All API calls were successful.");
toast("所有API请求成功");
}
log("Script operations concluded. Adding a small delay for UI.");
sleep(500);
}
}
function handleApiError(error) {
if (error) {
errors.push(error);
}
checkAllTasksDone();
}
// 调用第一个 API
processIpApiWithCallbacks(
"https://ipv4.geojs.io/v1/ip/country.json",
function(data2) {
toast("国家代码: " + data2.country);
log("地理位置信息:");
log("国家代码 (2位): " + data2.country);
log("国家代码 (3位): " + data2.country_3);
log("IP 地址: " + data2.ip);
log("国家名称: " + data2.name);
},
"地理位置",
handleApiError
);
// 调用第二个 API
processIpApiWithCallbacks(
"https://ipv4.geojs.io/v1/ip/geo.json",
function(data3) {
log("新增接口返回的地理数据:");
log("经度: " + data3.longitude);
log("纬度: " + data3.latitude);
log("城市: " + data3.city);
log("地区: " + data3.region);
log("国家: " + data3.country);
log("组织名称: " + data3.organization_name);
log("IP 地址: " + data3.ip);
log("国家代码 (2位): " + data3.country_code);
log("国家代码 (3位): " + data3.country_code3);
log("时区: " + data3.timezone);
log("ASN: " + data3.asn);
log("洲: " + data3.continent_code);
},
"详细地理位置",
handleApiError
);
}
// ========== 启动主函数并设置定时器 ==========
(function startLoop() {
const intervalMillis = 60 * 1000; // 每隔 60 秒执行一次
main(); // 首次启动
setInterval(main, intervalMillis); // 循环执行
})();

Binary file not shown.

Binary file not shown.