From 222a2002f96032e84287c53bb03cf386a8af8f4f Mon Sep 17 00:00:00 2001 From: yjj38 Date: Wed, 18 Jun 2025 14:31:11 +0800 Subject: [PATCH] Add `TaskUtil` for device and file operations; update `MainActivity` for Android ID handling and permissions Introduced new `TaskUtil` utility class for device info upload, file compression, and remote operations. Updated `MainActivity` to dynamically fetch and utilize Android ID, added Android 13+ permission handling for `FOREGROUND_SERVICE`. Included Mockito dependencies for unit testing, implemented `TaskUtilTest` for API interaction validation. Adjusted `ChangeDeviceInfoUtil` initialization and fetched properties dynamically. Updated `minSdk` and network security configuration. --- app/build.gradle | 9 +- app/src/main/AndroidManifest.xml | 16 +- .../com/example/studyapp/MainActivity.java | 91 +++-- .../example/studyapp/autoJS/AutoJsUtil.java | 18 +- .../studyapp/device/ChangeDeviceInfoUtil.java | 285 +++++++++------ .../studyapp/device/DeviceConfiguration.json | 63 ++++ .../studyapp/{utils => proxy}/ClashUtil.java | 3 +- .../service/MyAccessibilityService.java | 52 ++- .../com/example/studyapp/task/AfInfo.java | 19 + .../com/example/studyapp/task/BigoInfo.java | 22 ++ .../com/example/studyapp/task/DeviceInfo.java | 26 ++ .../com/example/studyapp/task/TaskUtil.java | 336 ++++++++++++++++++ app/src/main/jniLibs/arm64-v8a/libnative.so | Bin 81472 -> 81640 bytes .../main/res/xml/network_security_config.xml | 1 + .../example/studyapp/task/TaskUtilTest.java | 99 ++++++ 15 files changed, 883 insertions(+), 157 deletions(-) create mode 100644 app/src/main/java/com/example/studyapp/device/DeviceConfiguration.json rename app/src/main/java/com/example/studyapp/{utils => proxy}/ClashUtil.java (98%) create mode 100644 app/src/main/java/com/example/studyapp/task/AfInfo.java create mode 100644 app/src/main/java/com/example/studyapp/task/BigoInfo.java create mode 100644 app/src/main/java/com/example/studyapp/task/DeviceInfo.java create mode 100644 app/src/main/java/com/example/studyapp/task/TaskUtil.java create mode 100644 app/src/test/java/com/example/studyapp/task/TaskUtilTest.java diff --git a/app/build.gradle b/app/build.gradle index 3317bad..4ce4220 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -8,7 +8,7 @@ android { defaultConfig { applicationId "com.example.studyapp" - minSdk 24 + minSdk 23 targetSdk 35 versionCode 1 versionName "1.0" @@ -73,4 +73,11 @@ dependencies { // 如果需要 RxJava 支持(可选) implementation 'com.squareup.retrofit2:adapter-rxjava3:2.9.0' + + // 添加 Mockito 核心依赖 + testImplementation 'org.mockito:mockito-core:5.4.0' + + // 如果需要在 Android Instrumented Tests 中使用 Mockito + androidTestImplementation 'org.mockito:mockito-android:5.4.0' + } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 8ac24cf..df578f1 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -20,6 +20,11 @@ + + + + + - - - - - + android:foregroundServiceType="mediaProjection" + android:exported="true"> diff --git a/app/src/main/java/com/example/studyapp/MainActivity.java b/app/src/main/java/com/example/studyapp/MainActivity.java index 1082467..b7426ef 100644 --- a/app/src/main/java/com/example/studyapp/MainActivity.java +++ b/app/src/main/java/com/example/studyapp/MainActivity.java @@ -2,7 +2,6 @@ package com.example.studyapp; import android.app.Activity; import android.app.AlertDialog; -import android.content.BroadcastReceiver; import android.net.Uri; import android.content.Context; import android.content.Intent; @@ -31,16 +30,15 @@ import androidx.work.WorkManager; import com.example.studyapp.autoJS.AutoJsUtil; import com.example.studyapp.device.ChangeDeviceInfoUtil; -import com.example.studyapp.utils.ClashUtil; +import com.example.studyapp.proxy.ClashUtil; +import com.example.studyapp.service.MyAccessibilityService; +import com.example.studyapp.task.TaskUtil; import com.example.studyapp.worker.CheckAccessibilityWorker; import java.lang.ref.WeakReference; import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; public class MainActivity extends AppCompatActivity { @@ -69,6 +67,8 @@ public class MainActivity extends AppCompatActivity { "ge", "ps" }; + public static String androidId; + // 初始化 ExecutorService private void initializeExecutorService() { if (executorService == null || executorService.isShutdown()) { @@ -82,6 +82,30 @@ public class MainActivity extends AppCompatActivity { } } + /** + * 获取 Android 设备的 ANDROID_ID + * + * @param context 应用上下文 + * @return 设备的 ANDROID_ID,若无法获取,则返回 null + */ + private void getAndroidId(Context context) { + if (context == null) { + throw new IllegalArgumentException("Context cannot be null"); + } + executorService.submit(() -> { + try { + androidId = Settings.Secure.getString( + context.getContentResolver(), + Settings.Secure.ANDROID_ID + ); + } catch (Exception e) { + Log.e("MainActivity", "getAndroidId: Failed to get ANDROID_ID", e); + } + }); + } + + private static final int REQUEST_CODE_PERMISSIONS = 100; + @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -89,6 +113,7 @@ public class MainActivity extends AppCompatActivity { instance = new WeakReference<>(this); initializeExecutorService(); + getAndroidId(this); System.setProperty("java.library.path", this.getApplicationInfo().nativeLibraryDir); if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) { // 针对 Android 10 或更低版本检查普通存储权限 @@ -110,6 +135,16 @@ public class MainActivity extends AppCompatActivity { startActivityForResult(intent, ALLOW_ALL_FILES_ACCESS_PERMISSION_CODE); } } + // 添加对 FOREGROUND_SERVICE 权限的检查和请求 + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { // 判断 Android 13+ + String[] requiredPermissions = { + android.Manifest.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION, + "android.permission.CAPTURE_VIDEO_OUTPUT" + }; + ActivityCompat.requestPermissions(this, requiredPermissions, REQUEST_CODE_PERMISSIONS); + } else { + Toast.makeText(this, "当前设备不支持这些权限", Toast.LENGTH_SHORT).show(); + } if (!isNetworkAvailable(this)) { Toast.makeText(this, "Network is not available", Toast.LENGTH_SHORT).show(); @@ -123,7 +158,7 @@ public class MainActivity extends AppCompatActivity { // 初始化按钮 Button runScriptButton = findViewById(R.id.run_script_button); if (runScriptButton != null) { - runScriptButton.setOnClickListener(v -> AutoJsUtil.runAutojsScript(this,"")); + runScriptButton.setOnClickListener(v -> AutoJsUtil.runAutojsScript(this)); } else { Toast.makeText(this, "Button not found", Toast.LENGTH_SHORT).show(); } @@ -164,7 +199,7 @@ public class MainActivity extends AppCompatActivity { } // 初始化 ChangeDeviceInfoUtil - ChangeDeviceInfoUtil.initialize("US", 2); + ChangeDeviceInfoUtil.initialize("US", 2, this); // 获取输入框和按钮 EditText inputNumber = findViewById(R.id.input_number); Button executeButton = findViewById(R.id.execute_button); @@ -237,13 +272,13 @@ public class MainActivity extends AppCompatActivity { AutoJsUtil.flag = true; // 广播状态更新 } - for (int i = 0; i < number; i++) { + while (true) { synchronized (taskLock) { while (!AutoJsUtil.flag) { taskLock.wait(30000); } - Log.d("MainActivity", "任务执行第:" + i); - executeSingleLogic(i); + executeSingleLogic(); + TaskUtil.execSaveTask(this); } } } catch (InterruptedException e) { @@ -257,14 +292,11 @@ public class MainActivity extends AppCompatActivity { public static final Object broadcastLock = new Object(); // 广播锁 public static final Object taskLock = new Object(); // 任务逻辑锁 - public void executeSingleLogic(int i) { - Log.i("MainActivity", "executeSingleLogic: Start execution for index " + i); - long startTime = System.currentTimeMillis(); // 开始计时 + public void executeSingleLogic() { Log.i("MainActivity", "executeSingleLogic: Proxy not active, starting VPN"); startProxyVpn(this); - Log.i("MainActivity", "executeSingleLogic: Switching proxy group to " + proxyNames[i]); try { ClashUtil.switchProxyGroup("GLOBAL", "us", "http://127.0.0.1:6170"); } catch (Exception e) { @@ -273,15 +305,10 @@ public class MainActivity extends AppCompatActivity { } Log.i("MainActivity", "executeSingleLogic: Changing device info"); - String url = ChangeDeviceInfoUtil.changeDeviceInfo(getPackageName(), this); + ChangeDeviceInfoUtil.changeDeviceInfo(getPackageName(), this); Log.i("MainActivity", "executeSingleLogic: Running AutoJs script"); - AutoJsUtil.runAutojsScript(this, url); - - runOnUiThread(() -> Toast.makeText(this, "第 " + (i + 1) + " 次执行完成", Toast.LENGTH_SHORT).show()); - - long endTime = System.currentTimeMillis(); // 结束计时 - Log.i("MainActivity", "executeSingleLogic: Finished execution for index " + i + " in " + (endTime - startTime) + " ms"); + AutoJsUtil.runAutojsScript(this); } private void startProxyVpn(Context context) { @@ -315,8 +342,30 @@ public class MainActivity extends AppCompatActivity { 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); diff --git a/app/src/main/java/com/example/studyapp/autoJS/AutoJsUtil.java b/app/src/main/java/com/example/studyapp/autoJS/AutoJsUtil.java index 44e48ea..623d671 100644 --- a/app/src/main/java/com/example/studyapp/autoJS/AutoJsUtil.java +++ b/app/src/main/java/com/example/studyapp/autoJS/AutoJsUtil.java @@ -3,6 +3,7 @@ package com.example.studyapp.autoJS; import static androidx.core.content.ContextCompat.startActivity; import static com.example.studyapp.MainActivity.broadcastLock; import static com.example.studyapp.MainActivity.taskLock; +import static com.example.studyapp.task.TaskUtil.infoUpload; import android.Manifest; import android.content.ActivityNotFoundException; @@ -26,6 +27,7 @@ import com.example.studyapp.service.CloudPhoneManageService; import java.io.File; +import java.io.IOException; import retrofit2.Call; import retrofit2.Callback; import retrofit2.Response; @@ -38,7 +40,7 @@ public class AutoJsUtil { public static volatile boolean flag; private static int count; - public static void runAutojsScript(Context context,String url) { + public static void runAutojsScript(Context context) { // 检查脚本文件 Log.i("AutoJsUtil", "-------脚本运行开始:--------"+ count++ ); File scriptFile = new File(Environment.getExternalStorageDirectory(), "script/main.js"); @@ -58,7 +60,6 @@ public class AutoJsUtil { Intent intent = new Intent(); intent.setClassName("org.autojs.autojs6", "org.autojs.autojs.external.open.RunIntentActivity"); intent.putExtra("path", scriptFile.getAbsolutePath()); - intent.putExtra("url", url); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); try { context.startActivity(intent); @@ -85,6 +86,13 @@ public class AutoJsUtil { AutoJsUtil.flag = true; } synchronized (taskLock) { + try { + infoUpload(context, MainActivity.androidId, scriptResult); + } catch (IOException e) { + // 例如:可以显示给用户一条错误消息 + Log.e("AutoJsUtil", "File upload failed: " + e.getMessage()); + } + taskLock.notifyAll(); // 唤醒任务线程 } } @@ -129,10 +137,7 @@ public class AutoJsUtil { } private static final String AUTOJS_SCRIPT_FINISHED_ACTION = "org.autojs.SCRIPT_FINISHED"; - private static final String SCRIPT_RESULT_KEY = "result"; - private static final Object lock = new Object(); - - + private static final String SCRIPT_RESULT_KEY = "package"; public static void stopAutojsScript(Context context) { // 停止运行脚本的 Intent @@ -157,5 +162,4 @@ public class AutoJsUtil { Log.e("AutoJsUtil", "目标活动未找到: org.autojs.autojs.external.open.StopServiceActivity"); } } - } diff --git a/app/src/main/java/com/example/studyapp/device/ChangeDeviceInfoUtil.java b/app/src/main/java/com/example/studyapp/device/ChangeDeviceInfoUtil.java index 4af97db..00615c6 100644 --- a/app/src/main/java/com/example/studyapp/device/ChangeDeviceInfoUtil.java +++ b/app/src/main/java/com/example/studyapp/device/ChangeDeviceInfoUtil.java @@ -2,24 +2,21 @@ package com.example.studyapp.device; import android.content.ContentResolver; import android.content.Context; -import android.net.ConnectivityManager; -import android.net.Network; -import android.net.NetworkCapabilities; import android.net.Uri; -import android.text.TextUtils; import android.util.Log; +import com.example.studyapp.MainActivity; +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.ShellUtils; -import com.google.android.gms.ads.identifier.AdvertisingIdClient; import java.io.IOException; import java.lang.reflect.InvocationTargetException; -import java.util.HashMap; -import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; @@ -28,9 +25,9 @@ import java.lang.reflect.Method; public class ChangeDeviceInfoUtil { - private static JSONObject bigoDeviceObject; + private static JSONObject bigoDeviceObject; - private static JSONObject afDeviceObject; + 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") @@ -51,30 +48,65 @@ public class ChangeDeviceInfoUtil { // 创建一个线程池用于执行网络任务 private static final ExecutorService executorService = Executors.newSingleThreadExecutor(); - public static void initialize(String country, int tag) { + public static void initialize(String country, int tag, MainActivity mainActivity) { executorService.submit(() -> { try { - String bigoJson = HttpUtil.requestGet(buildBigoUrl(country, tag)); - String afJson = HttpUtil.requestGet(buildAfUrl(country, tag )); - - bigoDeviceObject = new JSONObject(bigoJson).optJSONObject("device"); - afDeviceObject = new JSONObject(afJson).optJSONObject("device"); - - if (bigoDeviceObject == null || afDeviceObject == null) { - throw new JSONException("Device object is missing in the response JSON"); + // 发起网络请求并捕获可能的异常 + String bigoJson; + String afJson; + try { + bigoJson = HttpUtil.requestGet(buildBigoUrl(country, tag)); + afJson = HttpUtil.requestGet(buildAfUrl(country, tag)); + } catch (IOException ioException) { + Log.e("Error", "Network request failed", ioException); + return; } - Log.d("Debug", "bigoDeviceObject: " + bigoDeviceObject.toString()); - Log.d("Debug", "afDeviceObject: " + afDeviceObject.toString()); + // 执行查询任务 + String response; + try { + response = TaskUtil.execQueryTask(mainActivity); + } catch (Exception e) { + Log.e("Error", "Task execution failed", e); + return; + } + + // 解析 JSON 数据 + JSONObject bigoDeviceObject; + JSONObject afDeviceObject; + try { + if (response != null) { + JSONObject responseJson = new JSONObject(response); + bigoDeviceObject = responseJson.optJSONObject("bigoDeviceObject"); + afDeviceObject = responseJson.optJSONObject("afDeviceObject"); + } else { + bigoDeviceObject = new JSONObject(bigoJson).optJSONObject("device"); + afDeviceObject = new JSONObject(afJson).optJSONObject("device"); + } + } catch (JSONException e) { + Log.e("Error", "Failed to parse JSON", e); + return; + } + + // 检查解析结果 + if (bigoDeviceObject == null || afDeviceObject == null) { + Log.e("Error", "Device object is missing in response"); + return; + } + + // 输出结果(附加空检查) + Log.d("Debug", "bigoDeviceObject: " + (bigoDeviceObject != null ? bigoDeviceObject.toString() : "null")); + Log.d("Debug", "afDeviceObject: " + (afDeviceObject != null ? afDeviceObject.toString() : "null")); + } catch (Exception e) { - Log.e("Error", "Failed to load or parse the response JSON", e); + Log.e("Error", "Unexpected error occurred", e); } }); } - public static String changeDeviceInfo(String current_pkg_name, Context context) { + public static void changeDeviceInfo(String current_pkg_name, Context context) { if (bigoDeviceObject == null || afDeviceObject == null) { Log.e("ChangeDeviceInfoUtil", "Required device JSON objects are not initialized"); @@ -95,14 +127,31 @@ public class ChangeDeviceInfoUtil { String resolution = bigoDeviceObject.optString("resolution"); String vendor = bigoDeviceObject.optString("vendor"); int batteryScale = bigoDeviceObject.optInt("bat_scale"); - //String model = deviceObject.optString("model"); + // 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"); - //AF + BigoInfo 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; String advertiserId = afDeviceObject.optString(".advertiserId"); String model = afDeviceObject.optString(".model"); String brand = afDeviceObject.optString(".brand"); @@ -118,33 +167,93 @@ public class ChangeDeviceInfoUtil { String langCode = afDeviceObject.optString(".lang_code"); String cpuAbi = afDeviceObject.optString(".deviceData.cpu_abi"); int yDp = afDeviceObject.optInt(".deviceData.dim.ydp"); + TaskUtil.setBigoDevice(bigoDevice); + + AfInfo 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", ""); + DeviceInfo 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); - - String url = "https://app.appsflyer.com/com.gateio.gateio?pid=seikoads_int&af_siteid={aff}&c=Cj4jwOBf&af_sub_siteid={aff_sub6}&af_c_id={offer_id}&af_ad={adx_bundle_id}&af_ad_id={affiliate_id}&af_adset_id={offer_id}&af_channel={adx_id}&af_cost_currency={currency}&af_cost_value={payout}&af_adset={transaction_id}&af_click_lookback=7d&af_ip={ip}&af_lang={aff_sub4}&af_ua={ua}&clickid={transaction_id}&advertising_id={aff_sub3}&idfa={aff_sub3}&af_model={model}&af_os_version={os_version}&is_incentivized=false&af_prt=huiimedia"; - Map params = new HashMap<>(); - params.put("aff", "12345"); - params.put("aff_sub6", "sub6Value"); - params.put("offer_id", "offer123"); - params.put("adx_bundle_id", "adxBundle123"); - params.put("affiliate_id", "affiliateID123"); - params.put("currency", "USD"); - params.put("payout", "5.0"); - params.put("transaction_id",""); - params.put("ip",HttpUtil.getLocalIpAddress()); - params.put("aff_sub4", "English"); - params.put("aff_sub3", "AdvertisingID123"); - - params.put("adx_id", advertiserId); - params.put("ua",userAgent); - params.put("model", model); - params.put("os_version", osVer); + 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", ""); // 自动处理分辨率信息 // int widthPixels = Integer.parseInt(resolution.split("x")[0]); // int heightPixels = Integer.parseInt(resolution.split("x")[1]); - + // // 更新屏幕显示相关参数 // JSONObject displayMetrics = new JSONObject(); // displayMetrics.put("widthPixels", widthPixels); @@ -152,7 +261,7 @@ public class ChangeDeviceInfoUtil { // displayMetrics.put("densityDpi", dpi); // callVCloudSettings_put("screen.device.displayMetrics", displayMetrics.toString(), context); - //BIGO 替换写死的值为 JSON 动态值 + // BIGO 替换写死的值为 JSON 动态值 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); @@ -160,7 +269,7 @@ public class ChangeDeviceInfoUtil { 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 + ".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); @@ -177,7 +286,7 @@ public class ChangeDeviceInfoUtil { // **tz** (时区) callVCloudSettings_put(current_pkg_name + "_tz", tz, context); - //AF 替换写死的值为 JSON 动态值 + // AF 替换写死的值为 JSON 动态值 callVCloudSettings_put(current_pkg_name + ".advertiserId", advertiserId, context); callVCloudSettings_put(current_pkg_name + ".model", model, context); callVCloudSettings_put(current_pkg_name + ".brand", brand, context); @@ -203,74 +312,34 @@ public class ChangeDeviceInfoUtil { if (!ShellUtils.hasRootAccess()) { Log.e("ChangeDeviceInfoUtil", "Root access is required to execute system property changes"); - return null; } - 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", ""); // 设置机型, 直接设置属性 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 ); + 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); + // Native.setBootId(bootId); // 修改drm id - ShellUtils.execRootCmd("setprop persist.sys.cloud.drm.id "+persist_sys_cloud_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); + 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); - - // 填充占位符 - return HttpUtil.fillUrlPlaceholders(url, params); + 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) { Log.e("ChangeDeviceInfoUtil", "Error occurred while changing device info", e); throw new RuntimeException("Error occurred in changeDeviceInfo", e); diff --git a/app/src/main/java/com/example/studyapp/device/DeviceConfiguration.json b/app/src/main/java/com/example/studyapp/device/DeviceConfiguration.json new file mode 100644 index 0000000..4756d93 --- /dev/null +++ b/app/src/main/java/com/example/studyapp/device/DeviceConfiguration.json @@ -0,0 +1,63 @@ +{ + "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" + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/studyapp/utils/ClashUtil.java b/app/src/main/java/com/example/studyapp/proxy/ClashUtil.java similarity index 98% rename from app/src/main/java/com/example/studyapp/utils/ClashUtil.java rename to app/src/main/java/com/example/studyapp/proxy/ClashUtil.java index ebf94d8..0bc9528 100644 --- a/app/src/main/java/com/example/studyapp/utils/ClashUtil.java +++ b/app/src/main/java/com/example/studyapp/proxy/ClashUtil.java @@ -1,4 +1,4 @@ -package com.example.studyapp.utils; +package com.example.studyapp.proxy; import android.content.BroadcastReceiver; import android.content.Context; @@ -10,7 +10,6 @@ import java.io.IOException; import java.util.concurrent.CountDownLatch; import okhttp3.Call; import okhttp3.Callback; -import okhttp3.Credentials; import okhttp3.HttpUrl; import okhttp3.MediaType; import okhttp3.OkHttpClient; diff --git a/app/src/main/java/com/example/studyapp/service/MyAccessibilityService.java b/app/src/main/java/com/example/studyapp/service/MyAccessibilityService.java index 40b975c..36b0edc 100644 --- a/app/src/main/java/com/example/studyapp/service/MyAccessibilityService.java +++ b/app/src/main/java/com/example/studyapp/service/MyAccessibilityService.java @@ -1,16 +1,22 @@ package com.example.studyapp.service; +import android.Manifest; import android.accessibilityservice.AccessibilityService; +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; @@ -35,28 +41,56 @@ public class MyAccessibilityService extends AccessibilityService { private void createNotificationChannel() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - String channelId = "2"; - String channelName = "Foreground Service"; - int importance = NotificationManager.IMPORTANCE_LOW; - NotificationChannel channel = new NotificationChannel(channelId, channelName, importance); - NotificationManager notificationManager = getSystemService(NotificationManager.class); - if (notificationManager != null) { - notificationManager.createNotificationChannel(channel); + NotificationChannel channel = new NotificationChannel( + "2", + "无障碍服务", + NotificationManager.IMPORTANCE_LOW + ); + channel.setDescription("此服务在后台运行,提供无障碍支持。"); + NotificationManager manager = getSystemService(NotificationManager.class); + if (manager != null) { + manager.createNotificationChannel(channel); } } } private void startForegroundService() { createNotificationChannel(); + Intent notificationIntent = new Intent(this, MainActivity.class); int pendingIntentFlags = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ? PendingIntent.FLAG_IMMUTABLE : 0; PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, pendingIntentFlags); + NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this, "2") .setContentTitle("无障碍服务运行中") .setContentText("此服务正在持续运行") .setSmallIcon(R.drawable.ic_launcher_foreground) .setContentIntent(pendingIntent); - android.app.Notification notification = notificationBuilder.build(); - startForeground(1, notification); + + Notification notification = notificationBuilder.build(); + + if (ContextCompat.checkSelfPermission(this, Manifest.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION) + != PackageManager.PERMISSION_GRANTED) { + Toast.makeText(this, "请授予前台服务权限以启动服务", Toast.LENGTH_SHORT).show(); + return; + } + + String[] requiredPermissions = { + Manifest.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION, + "android.permission.CAPTURE_VIDEO_OUTPUT" + }; + + for (String permission : requiredPermissions) { + if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) { + return; + } + } + + // 设置前台服务类型(仅在 API 29+) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + startForeground(1, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION); + } else { + startForeground(1, notification); + } } } diff --git a/app/src/main/java/com/example/studyapp/task/AfInfo.java b/app/src/main/java/com/example/studyapp/task/AfInfo.java new file mode 100644 index 0000000..793da1b --- /dev/null +++ b/app/src/main/java/com/example/studyapp/task/AfInfo.java @@ -0,0 +1,19 @@ +package com.example.studyapp.task; + +public class AfInfo { + public String advertiserId; + public String model; + public String brand; + public String androidId; + public int xPixels; + public int yPixels; + public int densityDpi; + public String country; + public String batteryLevel; + public String stackInfo; + public String product; + public String network; + public String langCode; + public String cpuAbi; + public long yDp; +} diff --git a/app/src/main/java/com/example/studyapp/task/BigoInfo.java b/app/src/main/java/com/example/studyapp/task/BigoInfo.java new file mode 100644 index 0000000..d67d5c6 --- /dev/null +++ b/app/src/main/java/com/example/studyapp/task/BigoInfo.java @@ -0,0 +1,22 @@ +package com.example.studyapp.task; + +// 使用 JSON 库动态生成 JSON 请求体 (使用 Gson 示例) +public class BigoInfo { + public String cpuClockSpeed; + public String gaid; + public String userAgent; + public String osLang; + public String osVer; + public String tz; + public String systemCountry; + public String simCountry; + public long romFreeIn; + public String resolution; + public String vendor; + public int batteryScale; + public String net; + public long dpi; + public long romFreeExt; + public String dpiF; + public long cpuCoreNum; +} diff --git a/app/src/main/java/com/example/studyapp/task/DeviceInfo.java b/app/src/main/java/com/example/studyapp/task/DeviceInfo.java new file mode 100644 index 0000000..d69a9bc --- /dev/null +++ b/app/src/main/java/com/example/studyapp/task/DeviceInfo.java @@ -0,0 +1,26 @@ +package com.example.studyapp.task; + +public class DeviceInfo { + + public String lang; + public String roProductBrand; + public String roProductModel; + public String roProductManufacturer; + public String roProductDevice; + public String roProductName; + public String roBuildVersionIncremental; + public String roBuildFingerprint; + public String roOdmBuildFingerprint; + public String roProductBuildFingerprint; + public String roSystemBuildFingerprint; + public String roSystemExtBuildFingerprint; + public String roVendorBuildFingerprint; + public String roBuildPlatform; + public String persistSysCloudDrmId; + public int persistSysCloudBatteryCapacity; + public String persistSysCloudGpuGlVendor; + public String persistSysCloudGpuGlRenderer; + public String persistSysCloudGpuGlVersion; + public String persistSysCloudGpuEglVendor; + public String persistSysCloudGpuEglVersion; +} \ No newline at end of file diff --git a/app/src/main/java/com/example/studyapp/task/TaskUtil.java b/app/src/main/java/com/example/studyapp/task/TaskUtil.java new file mode 100644 index 0000000..0a1e1ee --- /dev/null +++ b/app/src/main/java/com/example/studyapp/task/TaskUtil.java @@ -0,0 +1,336 @@ +package com.example.studyapp.task; + +import android.content.Context; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.os.Build.VERSION; +import android.os.Build.VERSION_CODES; +import android.provider.Settings; +import com.example.studyapp.MainActivity; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import java.io.FileInputStream; +import java.nio.file.Files; +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; +import okhttp3.OkHttpClient; +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; +import java.io.IOException; + + +/** + * @Time: 2025/6/16 11:11 + * @Creator: 初屿贤 + * @File: TaskUtil + * @Project: study.App + * @Description: + */ +public class TaskUtil { + + private static volatile DeviceInfo deviceInfo; + private static volatile BigoInfo bigoDevice; + private static volatile AfInfo afDevice; + + private static final String BASE_URL = "http://47.238.96.231:8112"; + private static OkHttpClient okHttpClient = new OkHttpClient(); + + private static void postDeviceInfo(String androidId) { + if (okHttpClient == null) { + throw new IllegalStateException("HttpClient is not initialized"); + } + if (BASE_URL == null || BASE_URL.isEmpty()) { + throw new IllegalStateException("BASE_URL is not initialized"); + } + if (bigoDevice == null || afDevice == null || deviceInfo == null) { + throw new IllegalStateException("Device information is missing"); + } + + Payload payload = new Payload(); + payload.bigoDeviceObject = bigoDevice; + payload.afDeviceObject = afDevice; + payload.other = deviceInfo; + + Gson gson = new GsonBuilder().serializeNulls().create(); + String jsonRequestBody = gson.toJson(payload); + + HttpUrl url = HttpUrl.parse(BASE_URL) + .newBuilder() + .addPathSegment("device_info_upload") + .addQueryParameter("id", androidId) + .build(); + + RequestBody body = RequestBody.create(MediaType.get("application/json; charset=utf-8"), jsonRequestBody); + + Request request = new Request.Builder() + .url(url) + .post(body) + .build(); + + okHttpClient.newCall(request).enqueue(new Callback() { + @Override + public void onFailure(@NotNull Call call, @NotNull IOException e) { + System.err.println("Request failed: " + e.getMessage()); + // Optional: Add retry logic + } + + @Override + public void onResponse(@NotNull Call call, @NotNull Response response) { + try (ResponseBody responseBody = response.body()) { + if (!response.isSuccessful()) { + System.err.println("Request failed with status: " + response.code() + ", message: " + response.message()); + return; + } + if (responseBody != null) { + System.out.println(responseBody.string()); + } else { + System.err.println("Response body is null"); + } + } catch (Exception e) { + System.err.println("Error while processing response: " + e.getMessage()); + } + } + }); + } + + public static void infoUpload(Context context, String androidId, String packAge) throws IOException { + + if (context == null) { + throw new IllegalArgumentException("Context or Package name cannot be null or empty"); + } + + if (androidId == null || androidId.isEmpty()) { + System.err.println("ANDROID_ID is null or empty"); + return; + } + PackageInfo packageInfo; + try { + // 根据包名获取APK路径 + packageInfo = context.getPackageManager().getPackageInfo(packAge, 0); + if (packageInfo == null) { + throw new IllegalStateException("未找到包名对应的信息:" + packAge); + } + } catch (PackageManager.NameNotFoundException e) { + throw new RuntimeException("未找到包名对应的应用:" + packAge, e); + } + + String apkSourceDir = packageInfo.applicationInfo.sourceDir; // APK路径 + String outputZipPath = new File( + context.getExternalFilesDir(null), + androidId + "_" + packAge + ".zip" + ).getAbsolutePath(); // 压缩文件输出路径 + + File zipFile = new File(outputZipPath); + if (zipFile.exists() && !zipFile.delete()) { + System.err.println("旧的压缩文件无法删除:" + outputZipPath); + return; + } + + File sourceApk = new File(apkSourceDir); + File copiedApk = new File(context.getCacheDir(), packAge + "_temp.apk"); + if (copiedApk.exists() && !copiedApk.delete()) { + System.err.println("旧的临时apk文件无法删除:" + copiedApk.getAbsolutePath()); + return; + } + Files.copy(sourceApk.toPath(), copiedApk.toPath()); + + // 压缩APK文件 + try ( + FileInputStream fis = new FileInputStream(copiedApk); + FileOutputStream fos = new FileOutputStream(outputZipPath); + ZipOutputStream zipOut = new ZipOutputStream(fos)) { + + ZipEntry zipEntry = new ZipEntry(new File(apkSourceDir).getName()); + zipOut.putNextEntry(zipEntry); + byte[] buffer = new byte[1024]; + int bytesRead; + while ((bytesRead = fis.read(buffer)) >= 0) { + zipOut.write(buffer, 0, bytesRead); + } + System.out.println("APK文件成功压缩至:" + outputZipPath); + } catch (IOException e) { + e.printStackTrace(); + return; + } + + // 上传压缩文件 + File fileToUpload = new File(outputZipPath); + if (!fileToUpload.exists()) { + System.out.println("上传文件不存在:" + outputZipPath); + return; + } + + RequestBody fileBody = RequestBody.create( + MediaType.get("application/zip"), fileToUpload + ); + MultipartBody requestBody = new MultipartBody.Builder() + .setType(MultipartBody.FORM) + .addFormDataPart("file", fileToUpload.getName(), fileBody) + .build(); + + Request request = new Request.Builder() + .url(BASE_URL + "/tar_info_upload") + .post(requestBody) + .build(); + + okHttpClient.newCall(request).enqueue(new Callback() { + @Override + public void onFailure(@NotNull Call call, @NotNull IOException e) { + e.printStackTrace(); + System.out.println("上传失败: " + e.getMessage()); + } + + @Override + public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException { + try { + if (response.isSuccessful()) { + System.out.println("文件上传成功: " + response.body().string()); + } else { + System.out.println("上传失败: " + response.message()); + } + } finally { + response.close(); // 确保关闭 response 以避免资源泄漏 + } + } + }); + } + + private static void validate() { + if (okHttpClient == null) { + throw new IllegalStateException("HttpClient is not initialized"); + } + if (BASE_URL == null || BASE_URL.isEmpty()) { + throw new IllegalStateException("BASE_URL is not initialized"); + } + } + + private static String getDeviceInfoSync(String androidId) { + validate(); // 检查 BASE_URL 和 okHttpClient 的合法性 + + HttpUrl url = HttpUrl.parse(BASE_URL + "/device_info_download") + .newBuilder() + .addQueryParameter("androidId", androidId) + .build(); + + Request request = new Request.Builder() + .url(url) + .get() + .build(); + + try (Response response = okHttpClient.newCall(request).execute()) { + if (!response.isSuccessful()) { // 检查响应状态 + throw new IOException("Unexpected response: " + response); + } + ResponseBody body = response.body(); + if (body != null) { + return body.string(); + } else { + throw new IOException("Response body is null"); + } + } catch (IOException e) { + e.printStackTrace(); + return null; // 或者抛出上层处理 + } + } + + private static void infoDownload(String androidId) { + // 下载压缩包 + HttpUrl url = HttpUrl.parse(BASE_URL + "/tar_info_download") + .newBuilder() + .addQueryParameter("file_name", "test") + .build(); + + Request request = new Request.Builder() + .url(url) + .get() + .build(); + + okHttpClient.newCall(request).enqueue(new Callback() { + @Override + public void onFailure(@NotNull Call call, @NotNull IOException e) { + e.printStackTrace(); + } + + @Override + public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException { + if (response.isSuccessful()) { + byte[] fileBytes = response.body().bytes(); + String savePath = "/storage/emulated/0/Download/test.zip"; + + File file = new File(savePath); + file.getParentFile().mkdirs(); + + try (FileOutputStream fos = new FileOutputStream(file)) { + fos.write(fileBytes); + System.out.println("File saved to " + savePath); + } catch (IOException e) { + e.printStackTrace(); + } + } else { + System.out.println("Download failed: " + response.message()); + } + } + }); + } + + public static String execQueryTask(Context context) { + String androidId = Settings.Secure.getString( + context.getContentResolver(), + Settings.Secure.ANDROID_ID + ); + return getDeviceInfoSync(androidId); + } + + public static void execSaveTask(Context context) { + if (context == null) { + throw new IllegalArgumentException("Context or Package name cannot be null or empty"); + } + + if (MainActivity.androidId == null || MainActivity.androidId.isEmpty()) { + System.err.println("ANDROID_ID is null or empty"); + return; + } + + try { + postDeviceInfo(MainActivity.androidId); + } catch (Exception e) { + System.err.println("Error in execReloginTask: " + e.getMessage()); + e.printStackTrace(); + } + } + + public static void setHttpClient(OkHttpClient client) { + okHttpClient = client; + } + + public static void setDeviceInfo(DeviceInfo info) { + deviceInfo = info; + } + + public static void setBigoDevice(BigoInfo info) { + bigoDevice = info; + } + + public static void setAfDevice(AfInfo info) { + afDevice = info; + } +} + +class Payload { + + BigoInfo bigoDeviceObject; + AfInfo afDeviceObject; + DeviceInfo other; +} + diff --git a/app/src/main/jniLibs/arm64-v8a/libnative.so b/app/src/main/jniLibs/arm64-v8a/libnative.so index 1935dd899748c627672c0a646fc313121b3b3242..23668041618d5ee7898d3e91283aadd686768008 100644 GIT binary patch delta 19784 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.211.204.20 127.0.0.1 8.217.137.25 + 47.238.96.231 diff --git a/app/src/test/java/com/example/studyapp/task/TaskUtilTest.java b/app/src/test/java/com/example/studyapp/task/TaskUtilTest.java new file mode 100644 index 0000000..aaaed11 --- /dev/null +++ b/app/src/test/java/com/example/studyapp/task/TaskUtilTest.java @@ -0,0 +1,99 @@ +package com.example.studyapp.task; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.*; + +import java.io.IOException; +import okhttp3.Call; +import okhttp3.Callback; +import okhttp3.MediaType; +import okhttp3.OkHttpClient; +import okhttp3.Protocol; +import okhttp3.Request; +import okhttp3.Response; +import okhttp3.ResponseBody; +import okio.Buffer; +import org.junit.Test; +import org.mockito.ArgumentCaptor; + +public class TaskUtilTest { + + @Test + public void testPostDeviceInfo() throws Exception { + OkHttpClient mockClient = mock(OkHttpClient.class); + TaskUtil.setHttpClient(mockClient); // 确保 mockClient 被 TaskUtil 使用 + + // Mock Call 和 Response + Call mockCall = mock(Call.class); + Response mockResponse = new Response.Builder() + .request(new Request.Builder().url("http://192.168.1.121:5000/device_info_upload").build()) + .protocol(Protocol.HTTP_1_1) + .code(200) + .message("OK") + .body(ResponseBody.create(MediaType.get("application/json; charset=utf-8"), "Success")) + .build(); + + // 配置 Mock 行为 + when(mockClient.newCall(any(Request.class))).thenReturn(mockCall); + doAnswer(invocation -> { + Callback callback = invocation.getArgument(0); + callback.onResponse(mockCall, mockResponse); // 模拟请求成功的回调 + return null; + }).when(mockCall).enqueue(any(Callback.class)); + + // 调用测试方法 + //TaskUtil.postDeviceInfo(); + + // 验证 newCall 是否被调用 + ArgumentCaptor requestCaptor = ArgumentCaptor.forClass(Request.class); + verify(mockClient).newCall(requestCaptor.capture()); // 确认调用 + Request capturedRequest = requestCaptor.getValue(); + + // 验证请求内容 + assertNotNull(capturedRequest); + assertEquals("POST", capturedRequest.method()); + assertEquals("http://192.168.1.121:5000/device_info_upload", capturedRequest.url().toString()); + assertNotNull(capturedRequest.body()); + + // 验证提交数据 JSON + Buffer buffer = new Buffer(); + capturedRequest.body().writeTo(buffer); + String body = buffer.readUtf8(); + assertTrue(body.contains("\"bigoDeviceObject\"")); + assertTrue(body.contains("\"afDeviceObject\"")); + } + + @Test + public void testPostDeviceInfoDownload_FailedRequest() throws Exception { + OkHttpClient mockClient = mock(OkHttpClient.class); + Call mockCall = mock(Call.class); + + when(mockClient.newCall(any(Request.class))).thenReturn(mockCall); + + doAnswer(invocation -> { + Callback callback = invocation.getArgument(0); + callback.onFailure(mockCall, new IOException("Request failed")); + return null; + }).when(mockCall).enqueue(any(Callback.class)); + + //TaskUtil.postDeviceInfo(); + + ArgumentCaptor requestCaptor = ArgumentCaptor.forClass(Request.class); + verify(mockClient).newCall(requestCaptor.capture()); + Request capturedRequest = requestCaptor.getValue(); + + assertNotNull(capturedRequest); + assertEquals("POST", capturedRequest.method()); + assertEquals("http://192.168.1.121:5000/device_info_upload", capturedRequest.url().toString()); + assertNotNull(capturedRequest.body()); + + // Validate JSON body + Buffer buffer = new Buffer(); + capturedRequest.body().writeTo(buffer); + String body = buffer.readUtf8(); + assertTrue(body.contains("\"bigoDeviceObject\"")); + assertTrue(body.contains("\"afDeviceObject\"")); + } +} \ No newline at end of file