diff --git a/.idea/.gitignore b/.idea/.gitignore
deleted file mode 100644
index 26d3352..0000000
--- a/.idea/.gitignore
+++ /dev/null
@@ -1,3 +0,0 @@
-# Default ignored files
-/shelf/
-/workspace.xml
diff --git a/.idea/appInsightsSettings.xml b/.idea/appInsightsSettings.xml
deleted file mode 100644
index 371f2e2..0000000
--- a/.idea/appInsightsSettings.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
deleted file mode 100644
index 57b2461..0000000
--- a/.idea/codeStyles/Project.xml
+++ /dev/null
@@ -1,120 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- xmlns:android
-
- ^$
-
-
-
-
-
-
-
-
- xmlns:.*
-
- ^$
-
-
- BY_NAME
-
-
-
-
-
-
- .*:id
-
- http://schemas.android.com/apk/res/android
-
-
-
-
-
-
-
-
- .*:name
-
- http://schemas.android.com/apk/res/android
-
-
-
-
-
-
-
-
- name
-
- ^$
-
-
-
-
-
-
-
-
- style
-
- ^$
-
-
-
-
-
-
-
-
- .*
-
- ^$
-
-
- BY_NAME
-
-
-
-
-
-
- .*
-
- http://schemas.android.com/apk/res/android
-
-
- ANDROID_ATTRIBUTE_ORDER
-
-
-
-
-
-
- .*
-
- .*
-
-
- BY_NAME
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml
deleted file mode 100644
index b9d18bf..0000000
--- a/.idea/codeStyles/codeStyleConfig.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/dbnavigator.xml b/.idea/dbnavigator.xml
index b838193..64f9316 100644
--- a/.idea/dbnavigator.xml
+++ b/.idea/dbnavigator.xml
@@ -4,146 +4,14 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
@@ -558,8 +426,4 @@
-
-
-
-
\ No newline at end of file
diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml
index 2ec1778..b268ef3 100644
--- a/.idea/deploymentTargetSelector.xml
+++ b/.idea/deploymentTargetSelector.xml
@@ -4,14 +4,6 @@
-
-
-
-
-
-
-
-
diff --git a/.idea/git_toolbox_prj.xml b/.idea/git_toolbox_prj.xml
deleted file mode 100644
index 02b915b..0000000
--- a/.idea/git_toolbox_prj.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/google-java-format.xml b/.idea/google-java-format.xml
deleted file mode 100644
index 2aa056d..0000000
--- a/.idea/google-java-format.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
index 2a72309..97f0a8e 100644
--- a/.idea/gradle.xml
+++ b/.idea/gradle.xml
@@ -1,20 +1,17 @@
-
-
-
+
-
diff --git a/.idea/httpRequests/2025-06-21T105829.200.json b/.idea/httpRequests/2025-06-21T105829.200.json
deleted file mode 100644
index 0b03c72..0000000
--- a/.idea/httpRequests/2025-06-21T105829.200.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "b3d893cf9de3a85a": [
- "b3d893cf9de3a85a_bin.mt.plus.zip",
- "dd597ef6-70c8-4765-94c3-a3262817b94c_b3d893cf9de3a85a_bin.mt.plus.zip"
- ]
-}
diff --git a/.idea/httpRequests/2025-06-21T115835.200.json b/.idea/httpRequests/2025-06-21T115835.200.json
deleted file mode 100644
index eb83fe1..0000000
--- a/.idea/httpRequests/2025-06-21T115835.200.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "b3d893cf9de3a85a": [
- "b3d893cf9de3a85a_bin.mt.plus.zip"
- ]
-}
diff --git a/.idea/httpRequests/2025-06-21T122848.200.json b/.idea/httpRequests/2025-06-21T122848.200.json
deleted file mode 100644
index 3d9dec4..0000000
--- a/.idea/httpRequests/2025-06-21T122848.200.json
+++ /dev/null
@@ -1,3 +0,0 @@
-{
- "bin.mt.plus": "b3d893cf9de3a85a_bin.mt.plus.zip"
-}
diff --git a/.idea/httpRequests/2025-06-21T124257.200.json b/.idea/httpRequests/2025-06-21T124257.200.json
deleted file mode 100644
index 0967ef4..0000000
--- a/.idea/httpRequests/2025-06-21T124257.200.json
+++ /dev/null
@@ -1 +0,0 @@
-{}
diff --git a/.idea/httpRequests/2025-06-21T132008.200.json b/.idea/httpRequests/2025-06-21T132008.200.json
deleted file mode 100644
index 28fe5f1..0000000
--- a/.idea/httpRequests/2025-06-21T132008.200.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "bin.mt.plus": "b3d893cf9de3a85a_bin.mt.plus.zip",
- "com.android.chrome": "b3d893cf9de3a85a_com.android.chrome.zip"
-}
diff --git a/.idea/httpRequests/b3d893cf9de3a85a_bin.mt.plus.zip b/.idea/httpRequests/b3d893cf9de3a85a_bin.mt.plus.zip
deleted file mode 100644
index 15cb0ec..0000000
Binary files a/.idea/httpRequests/b3d893cf9de3a85a_bin.mt.plus.zip and /dev/null differ
diff --git a/.idea/httpRequests/http-client.cookies b/.idea/httpRequests/http-client.cookies
deleted file mode 100644
index edfe326..0000000
--- a/.idea/httpRequests/http-client.cookies
+++ /dev/null
@@ -1 +0,0 @@
-# domain path name value date
diff --git a/.idea/httpRequests/http-requests-log.http b/.idea/httpRequests/http-requests-log.http
deleted file mode 100644
index 6e133cf..0000000
--- a/.idea/httpRequests/http-requests-log.http
+++ /dev/null
@@ -1,108 +0,0 @@
-POST http://47.238.96.231:8112/device_info_upload?id=FyZqWrStUvOpKlMn&taskId=d2fc8ffb-63a7-4c03-ae10-2eeb9ce8c989&deviceIp={"timezone":"America\/Los_Angeles","ip":"74.82.228.84","organization":"AS25764 NWF-IFIBER-ASN-2","asn":25764,"area_code":"0","organization_name":"NWF-IFIBER-ASN-2","country_code":"US","country_code3":"USA","continent_code":"NA","accuracy":50,"region":"Washington","latitude":"47.1285","longitude":"-119.2883","city":"Moses Lake","country":"United States"}
-
-Content-Type: application/json; charset=utf-8
-Content-Length: 114
-User-Agent: IntelliJ HTTP Client/Android Studio Narwhal | 2025.1.1
-Accept-Encoding: br, deflate, gzip, x-gzip
-Accept: */*
-
-{
- "bigoDeviceObject": BIGO_DEVICE_OBJECT,
- "afDeviceObject": AF_DEVICE_OBJECT,
- "other": DEVICE_INFO
- }
-
-<> 2025-07-07T182436.200.json
-
-###
-
-POST http://47.238.96.231:8112/device_info_upload?id=FyZqWrStUvOpKlMn&taskId=d2fc8ffb-63a7-4c03-ae10-2eeb9ce8c989&deviceIp={"timezone":"America\/Los_Angeles","ip":"74.82.228.84","organization":"AS25764 NWF-IFIBER-ASN-2","asn":25764,"area_code":"0","organization_name":"NWF-IFIBER-ASN-2","country_code":"US","country_code3":"USA","continent_code":"NA","accuracy":50,"region":"Washington","latitude":"47.1285","longitude":"-119.2883","city":"Moses Lake","country":"United States"}
-
-Content-Type: application/json; charset=utf-8
-Content-Length: 114
-User-Agent: IntelliJ HTTP Client/Android Studio Narwhal | 2025.1.1
-Accept-Encoding: br, deflate, gzip, x-gzip
-Accept: */*
-
-{
- "bigoDeviceObject": BIGO_DEVICE_OBJECT,
- "afDeviceObject": AF_DEVICE_OBJECT,
- "other": DEVICE_INFO
- }
-
-<> 2025-07-07T181724.200.json
-
-###
-
-POST http://47.238.96.231:8112/device_info_upload?id=FyZqWrStUvOpKlMn&taskId=d2fc8ffb-63a7-4c03-ae10-2eeb9ce8c989&deviceIp={"timezone":"America\/Los_Angeles","ip":"74.82.228.84","organization":"AS25764 NWF-IFIBER-ASN-2","asn":25764,"area_code":"0","organization_name":"NWF-IFIBER-ASN-2","country_code":"US","country_code3":"USA","continent_code":"NA","accuracy":50,"region":"Washington","latitude":"47.1285","longitude":"-119.2883","city":"Moses Lake","country":"United States"}
-
-Content-Type: application/json; charset=utf-8
-Content-Length: 114
-User-Agent: IntelliJ HTTP Client/Android Studio Narwhal | 2025.1.1
-Accept-Encoding: br, deflate, gzip, x-gzip
-Accept: */*
-
-{
- "bigoDeviceObject": BIGO_DEVICE_OBJECT,
- "afDeviceObject": AF_DEVICE_OBJECT,
- "other": DEVICE_INFO
- }
-
-<> 2025-07-07T181305.200.json
-
-###
-
-GET http://47.238.96.231:8112/get_package_info?androidId=b3d893cf9de3a85a
-User-Agent: IntelliJ HTTP Client/IntelliJ IDEA 2025.1.2
-Accept-Encoding: br, deflate, gzip, x-gzip
-Accept: */*
-
-<> 2025-06-21T132008.200.json
-
-###
-
-GET http://47.238.96.231:8112/download_code_file?file_name=b3d893cf9de3a85a_bin.mt.plus.zip
-User-Agent: IntelliJ HTTP Client/IntelliJ IDEA 2025.1.2
-Accept-Encoding: br, deflate, gzip, x-gzip
-Accept: */*
-
-<> b3d893cf9de3a85a_bin.mt.plus.zip
-
-###
-
-GET http://47.238.96.231:8112/download_code_file?file_name="b3d893cf9de3a85a_bin.mt.plus.zip"
-User-Agent: IntelliJ HTTP Client/IntelliJ IDEA 2025.1.2
-Accept-Encoding: br, deflate, gzip, x-gzip
-Accept: */*
-
-<> 2025-06-21T124257.200.json
-
-###
-
-GET http://47.238.96.231:8112/get_package_info?androidId=b3d893cf9de3a85a
-User-Agent: IntelliJ HTTP Client/IntelliJ IDEA 2025.1.2
-Accept-Encoding: br, deflate, gzip, x-gzip
-Accept: */*
-
-<> 2025-06-21T122848.200.json
-
-###
-
-GET http://47.238.96.231:8112/get_package_info?androidId=b3d893cf9de3a85a
-User-Agent: IntelliJ HTTP Client/IntelliJ IDEA 2025.1.2
-Accept-Encoding: br, deflate, gzip, x-gzip
-Accept: */*
-
-<> 2025-06-21T115835.200.json
-
-###
-
-GET http://47.238.96.231:8112/get_package_info?androidId=b3d893cf9de3a85a
-User-Agent: IntelliJ HTTP Client/IntelliJ IDEA 2025.1.2
-Accept-Encoding: br, deflate, gzip, x-gzip
-Accept: */*
-
-<> 2025-06-21T105829.200.json
-
-###
-
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
deleted file mode 100644
index cf247ce..0000000
--- a/.idea/inspectionProfiles/Project_Default.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
index 8562ed5..74dd639 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -1,16 +1,10 @@
+
-
+
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
index 2762f2f..7c3c4bc 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -1,5 +1,6 @@
plugins {
alias(libs.plugins.android.application)
+ alias(libs.plugins.kotlin.android)
}
android {
@@ -62,6 +63,9 @@ android {
path file("src/main/cpp/CMakeLists.txt")
}
}
+ kotlinOptions {
+ jvmTarget = '11'
+ }
applicationVariants.all { variant ->
variant.outputs.all { output ->
@@ -78,19 +82,20 @@ dependencies {
implementation libs.activity
implementation libs.constraintlayout
implementation libs.play.services.ads.identifier
+ implementation libs.core.ktx
testImplementation libs.junit
androidTestImplementation libs.ext.junit
androidTestImplementation libs.espresso.core
implementation 'androidx.work:work-runtime:2.9.0'
// Retrofit 核心库
- implementation 'com.squareup.retrofit2:retrofit:2.9.0'
-
- // 如果需要用 Gson 作为 JSON 序列化/反序列化工具,还需添加以下依赖
- implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
-
- // 如果需要 RxJava 支持(可选)
- implementation 'com.squareup.retrofit2:adapter-rxjava3:2.9.0'
+// implementation 'com.squareup.retrofit2:retrofit:2.9.0'
+//
+// // 如果需要用 Gson 作为 JSON 序列化/反序列化工具,还需添加以下依赖
+// implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
+//
+// // 如果需要 RxJava 支持(可选)
+// implementation 'com.squareup.retrofit2:adapter-rxjava3:2.9.0'
implementation 'org.nanohttpd:nanohttpd:2.3.1'
@@ -102,5 +107,9 @@ dependencies {
testImplementation 'junit:junit:4.13.2'
testImplementation 'org.mockito:mockito-inline:4.8.0'
+ implementation(platform("com.squareup.okhttp3:okhttp-bom:4.12.0"))
+ implementation("com.squareup.okhttp3:okhttp")
+ implementation("com.squareup.okhttp3:logging-interceptor")
+ implementation ("com.google.code.gson:gson:2.10.1")
}
diff --git a/app/src/main/java/com/example/studyapp/MainActivity.java b/app/src/main/java/com/example/studyapp/MainActivity.java
index c41c271..6a3354d 100644
--- a/app/src/main/java/com/example/studyapp/MainActivity.java
+++ b/app/src/main/java/com/example/studyapp/MainActivity.java
@@ -39,8 +39,11 @@ 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.update.ChangeCallBack;
+import com.example.studyapp.update.UpdateUtil;
import com.example.studyapp.utils.IpUtil;
import com.example.studyapp.utils.LogFileUtil;
+import com.example.studyapp.utils.ShellUtils;
import com.example.studyapp.worker.CheckAccessibilityWorker;
import java.io.IOException;
@@ -277,20 +280,35 @@ public class MainActivity extends AppCompatActivity {
private void processMainTask(String androidId, String taskId) {
try {
+ ShellUtils.execRootCmdAndGetResult("pm grant com.example.studyapp android.permission.INTERACT_ACROSS_USERS");
+ ShellUtils.execRootCmdAndGetResult("pm grant com.example.studyapp android.permission.WRITE_SECURE_SETTINGS");
+ ShellUtils.execRootCmdAndGetResult("pm setenforce 0");
initializeDeviceAndRunScript();
processScriptResults(androidId, taskId);
} catch (Exception e) {
LogFileUtil.logAndWrite(Log.ERROR, TAG, "Unexpected task error", e);
}
+
}
private void initializeDeviceAndRunScript() {
ChangeDeviceInfoUtil.getAddDeviceInfo(currentCountry.toUpperCase(), DEVICE_TYPE,
(bigoDevice, afDevice) -> {
startProxyVpn(this);
- ChangeDeviceInfoUtil.changeDeviceInfo(getPackageName(), this,
- bigoDevice, afDevice);
- AutoJsUtil.runAutojsScript(this);
+ UpdateUtil.INSTANCE.changeDevice(getPackageName(), bigoDevice, afDevice, new ChangeCallBack() {
+ @Override
+ public void changeSuccess() {
+ AutoJsUtil.runAutojsScript(MainActivity.this);
+ }
+
+ @Override
+ public void changeFailed() {
+ LogFileUtil.logAndWrite(Log.ERROR, TAG, "Change device failed", null);
+ }
+ });
+// ChangeDeviceInfoUtil.changeDeviceInfo(getPackageName(), this,
+// bigoDevice, afDevice);
+// AutoJsUtil.runAutojsScript(this);
});
AutoJsUtil.registerScriptResultReceiver(this);
}
@@ -332,15 +350,25 @@ public class MainActivity extends AppCompatActivity {
private void updateDeviceAndExecuteTask(String androidId, String taskId,
String result, JSONObject bigoDevice, JSONObject afDevice) {
startProxyVpn(this);
- ChangeDeviceInfoUtil.changeDeviceInfo(getPackageName(), this, bigoDevice, afDevice);
- AutoJsUtil.runAutojsScript(this);
+ UpdateUtil.INSTANCE.changeDevice(result, bigoDevice, afDevice,new ChangeCallBack() {
+ @Override
+ public void changeSuccess() {
+ AutoJsUtil.runAutojsScript(MainActivity.this);
+
+ TaskUtil.execSaveTask(MainActivity.this, androidId, taskId, result, IpUtil.fetchGeoInfo());
+ try {
+ infoUpload(MainActivity.this, androidId, result);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ @Override
+ public void changeFailed() {
+ LogFileUtil.logAndWrite(Log.ERROR, TAG, "Change device failed", null);
+ }
+ });
+// ChangeDeviceInfoUtil.changeDeviceInfo(result, this, bigoDevice, afDevice);
- TaskUtil.execSaveTask(this, androidId, taskId, result, IpUtil.fetchGeoInfo());
- try {
- infoUpload(this, androidId, result);
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
}
diff --git a/app/src/main/java/com/example/studyapp/pad/DcInfo.kt b/app/src/main/java/com/example/studyapp/pad/DcInfo.kt
new file mode 100644
index 0000000..83c9c52
--- /dev/null
+++ b/app/src/main/java/com/example/studyapp/pad/DcInfo.kt
@@ -0,0 +1,14 @@
+package com.android.grape.pad
+
+
+import com.google.gson.annotations.SerializedName
+
+data class DcInfo(
+ var area: String = "",
+ var dcCode: String = "",
+ var dcName: String = "",
+ var ossEndpoint: String = "",
+ var ossEndpointInternal: String = "",
+ var ossFileEndpoint: String = "",
+ var ossScreenshotEndpoint: String = ""
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/example/studyapp/pad/Pad.kt b/app/src/main/java/com/example/studyapp/pad/Pad.kt
new file mode 100644
index 0000000..d89c811
--- /dev/null
+++ b/app/src/main/java/com/example/studyapp/pad/Pad.kt
@@ -0,0 +1,13 @@
+package com.android.grape.pad
+
+
+import com.google.gson.annotations.SerializedName
+
+data class Pad(
+ var page: Int = 0,
+ var pageData: List = listOf(),
+ var rows: Int = 0,
+ var size: Int = 0,
+ var total: Int = 0,
+ var totalPage: Int = 0
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/example/studyapp/pad/PadTask.kt b/app/src/main/java/com/example/studyapp/pad/PadTask.kt
new file mode 100644
index 0000000..2c20f61
--- /dev/null
+++ b/app/src/main/java/com/example/studyapp/pad/PadTask.kt
@@ -0,0 +1,10 @@
+package com.android.grape.pad
+
+
+import com.google.gson.annotations.SerializedName
+
+data class PadTask(
+ var padCode: String = "",
+ var taskId: Int = 0,
+ var vmStatus: Int = 0
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/example/studyapp/pad/PageData.kt b/app/src/main/java/com/example/studyapp/pad/PageData.kt
new file mode 100644
index 0000000..21d1fac
--- /dev/null
+++ b/app/src/main/java/com/example/studyapp/pad/PageData.kt
@@ -0,0 +1,17 @@
+package com.android.grape.pad
+
+
+import com.google.gson.annotations.SerializedName
+
+data class PageData(
+ var dataSize: Long = 0,
+ var dataSizeUsed: Long = 0,
+ var dcInfo: DcInfo = DcInfo(),
+ var deviceLevel: String = "",
+ var deviceStatus: Int = 0,
+ var imageId: String = "",
+ var online: Int = 0,
+ var padCode: String = "",
+ var padStatus: Int = 0,
+ var streamStatus: Int = 0
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/example/studyapp/pad/TaskDetail.kt b/app/src/main/java/com/example/studyapp/pad/TaskDetail.kt
new file mode 100644
index 0000000..7a62ce6
--- /dev/null
+++ b/app/src/main/java/com/example/studyapp/pad/TaskDetail.kt
@@ -0,0 +1,14 @@
+package com.android.grape.pad
+
+
+import com.google.gson.annotations.SerializedName
+
+data class TaskDetail(
+ var endTime: Long = 0,
+ var errorMsg: String = "",
+ var padCode: String = "",
+ var taskContent: Any? = Any(),
+ var taskId: Int = 0,
+ var taskResult: Any? = Any(),
+ var taskStatus: Int = 0
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/example/studyapp/update/Api.kt b/app/src/main/java/com/example/studyapp/update/Api.kt
new file mode 100644
index 0000000..ecaa92c
--- /dev/null
+++ b/app/src/main/java/com/example/studyapp/update/Api.kt
@@ -0,0 +1,76 @@
+package com.example.studyapp.update
+
+import android.util.Log
+import com.android.grape.pad.Pad
+import com.android.grape.pad.PadTask
+import com.android.grape.pad.TaskDetail
+import com.google.gson.Gson
+import com.google.gson.reflect.TypeToken
+import org.json.JSONArray
+import org.json.JSONObject
+import kotlin.apply
+import kotlin.collections.isNotEmpty
+import kotlin.to
+import kotlin.toString
+
+object Api {
+ private const val TAG = "Api"
+ const val UPDATE_PAD: String = "/openapi/open/pad/updatePadProperties"
+ const val PAD_DETAIL: String = "/openapi/open/pad/padDetails"
+ const val PAD_TASK: String = "/task-center/open/task/padTaskDetail"
+
+
+ fun padDetail(): ApiResponse{
+ val param = Gson().toJson(
+ mapOf(
+ "page" to 1,
+ "rows" to 10,
+ "padCode" to listOf("ACP250702PWJCTLF")
+ )
+ )
+ val (response, code) = HttpUtils.postSync(PAD_DETAIL, param)
+ if (code == 200) {
+ Log.d("padDetail", response.toString())
+ var apiResponse: ApiResponse = Gson().fromJson(response, object : TypeToken>() {}.type)
+ return apiResponse
+ } else {
+ Log.d("padDetail", "error: $code")
+ return ApiResponse(code, "error", 0, null)
+ }
+ }
+
+ fun updatePad(params: String): ApiResponseList{
+ val (response, code) = HttpUtils.postSync(UPDATE_PAD, params)
+ if (code == 200) {
+ Log.d("updatePad", response.toString())
+ var apiResponse: ApiResponseList = Gson().fromJson(response, object : TypeToken>() {}.type)
+ return apiResponse
+ } else {
+ Log.d("updatePad", "error: $code")
+ return ApiResponseList(code, "error", 0, emptyList())
+ }
+ }
+
+ fun padTaskDetail(taskId: Int): Int{
+ val jsonString = JSONObject().apply {
+ put("taskIds", JSONArray().apply {
+ put(taskId)
+ })
+ }.toString()
+ Log.d(TAG, "padTaskDetail: $jsonString")
+ val (response, code) = HttpUtils.postSync(PAD_TASK, jsonString)
+ if (code == 200) {
+ Log.d("padTaskDetail", response.toString())
+ var apiResponse: ApiResponseList = Gson().fromJson(response, object : TypeToken>() {}.type)
+ val taskList = apiResponse.data
+ if (apiResponse.isSuccess() && taskList!= null && taskList.isNotEmpty()){
+ val task = taskList[0]
+ return task.taskStatus
+ }
+ return -1
+ }else {
+ Log.d("padTaskDetail", "error: $code")
+ return -1
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/studyapp/update/ApiResponse.kt b/app/src/main/java/com/example/studyapp/update/ApiResponse.kt
new file mode 100644
index 0000000..de94d43
--- /dev/null
+++ b/app/src/main/java/com/example/studyapp/update/ApiResponse.kt
@@ -0,0 +1,23 @@
+package com.example.studyapp.update
+
+data class ApiResponse(
+ val code: Int,
+ val message: String,
+ val ts: Long,
+ var data: T? = null
+){
+ fun isSuccess(): Boolean {
+ return code == 200
+ }
+}
+
+data class ApiResponseList(
+ val code: Int,
+ val message: String,
+ val ts: Long,
+ var data: List? = null
+){
+ fun isSuccess(): Boolean {
+ return code == 200
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/studyapp/update/ArmCloudSignatureV2.java b/app/src/main/java/com/example/studyapp/update/ArmCloudSignatureV2.java
new file mode 100644
index 0000000..9f82bcc
--- /dev/null
+++ b/app/src/main/java/com/example/studyapp/update/ArmCloudSignatureV2.java
@@ -0,0 +1,30 @@
+package com.example.studyapp.update;
+
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+import java.nio.charset.StandardCharsets;
+
+public class ArmCloudSignatureV2 {
+ public static final String ALGORITHM = "HmacSHA256";
+ public static final String SECRET_KEY = "gz8f1u0t63byzdu6ozbx8r5qs3e5lipt";
+ public static final String SECRET_ID = "3yc8c8bg1dym0zaiwjh867al";
+
+ public static String calculateSignature(String timestamp, String path, String body) throws Exception {
+ String stringToSign = timestamp + path + (body != null ? body : "");
+ Mac hmacSha256 = Mac.getInstance(ALGORITHM);
+ SecretKeySpec secretKeySpec = new SecretKeySpec(SECRET_ID.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();
+ }
+}
diff --git a/app/src/main/java/com/example/studyapp/update/ChangeCallBack.kt b/app/src/main/java/com/example/studyapp/update/ChangeCallBack.kt
new file mode 100644
index 0000000..d0593b3
--- /dev/null
+++ b/app/src/main/java/com/example/studyapp/update/ChangeCallBack.kt
@@ -0,0 +1,6 @@
+package com.example.studyapp.update
+
+interface ChangeCallBack {
+ fun changeSuccess()
+ fun changeFailed()
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/studyapp/update/HttpUtil.kt b/app/src/main/java/com/example/studyapp/update/HttpUtil.kt
new file mode 100644
index 0000000..9c64bb2
--- /dev/null
+++ b/app/src/main/java/com/example/studyapp/update/HttpUtil.kt
@@ -0,0 +1,226 @@
+package com.example.studyapp.update
+
+import android.util.Log
+import com.google.gson.Gson
+import okhttp3.*
+import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
+import okhttp3.MediaType.Companion.toMediaTypeOrNull
+import okhttp3.RequestBody.Companion.toRequestBody
+import java.io.IOException
+import java.util.concurrent.TimeUnit
+
+object HttpUtils {
+ const val HOST: String = "https://openapi-hk.armcloud.net"
+ // OkHttp客户端配置
+ private val client: OkHttpClient by lazy {
+ OkHttpClient.Builder()
+ .connectTimeout(15, TimeUnit.SECONDS)
+ .readTimeout(15, TimeUnit.SECONDS)
+ .writeTimeout(15, TimeUnit.SECONDS)
+ .build()
+ }
+
+ // JSON媒体类型
+ private val JSON_MEDIA_TYPE = "application/json; charset=utf-8".toMediaTypeOrNull()
+
+ // 回调接口
+ interface HttpCallback {
+ fun onSuccess(response: String, code: Int)
+ fun onFailure(error: String, code: Int?)
+ }
+
+ /**
+ * GET 请求(异步)
+ *
+ * @param url 请求URL
+ * @param params 查询参数(可选)
+ * @param headers 请求头(可选)
+ * @param callback 回调接口
+ */
+ fun getAsync(
+ url: String,
+ params: Map? = null,
+ callback: HttpCallback
+ ) {
+ val request = buildGetRequest(url, params)
+ executeRequestAsync(request, callback)
+ }
+
+ /**
+ * POST 请求(异步)
+ *
+ * @param url 请求URL
+ * @param body 请求体(JSON格式)
+ * @param headers 请求头(可选)
+ * @param callback 回调接口
+ */
+ fun postAsync(
+ url: String,
+ body: String? = null,
+ callback: HttpCallback
+ ) {
+ val request = buildPostRequest(url, body)
+ executeRequestAsync(request, callback)
+ }
+
+ /**
+ * GET 请求(同步)
+ *
+ * @param url 请求URL
+ * @param params 查询参数(可选)
+ * @param headers 请求头(可选)
+ * @return Pair<响应内容, 状态码>
+ */
+ fun getSync(
+ url: String,
+ params: Map? = null,
+ ): Pair {
+ val request = buildGetRequest(url, params)
+ return executeRequestSync(request)
+ }
+
+ /**
+ * POST 请求(同步)
+ *
+ * @param url 请求URL
+ * @param body 请求体(JSON格式)
+ * @param headers 请求头(可选)
+ * @return Pair<响应内容, 状态码>
+ */
+ fun postSync(
+ url: String,
+ body: String? = null
+ ): Pair {
+ val request = buildPostRequest(url, body)
+ return executeRequestSync(request)
+ }
+
+ /**
+ * 取消所有请求
+ */
+ fun cancelAllRequests() {
+ client.dispatcher.cancelAll()
+ }
+
+ // 内部方法 --------------------------------
+
+ private fun buildGetRequest(
+ path: String ,
+ params: Map?,
+ ): Request {
+ val url = "$HOST$path"
+ val httpUrlBuilder = url.toHttpUrlOrNull()?.newBuilder()
+ ?: throw IllegalArgumentException("Invalid URL: $url")
+
+ var param = ""
+ params?.forEach { (key, value) ->
+ httpUrlBuilder.addQueryParameter(key, value)
+ param += "&${key}=${value}"
+ }
+ if (param.isNotEmpty()){
+ param = param.substring(1)
+ }
+
+ val requestBuilder = Request.Builder().url(httpUrlBuilder.build())
+
+ val authver = "2.0"
+ val timestamp = System.currentTimeMillis()
+ val sign =
+ ArmCloudSignatureV2.calculateSignature(timestamp.toString(), path, param)
+ addHeaders(
+ requestBuilder,
+ mapOf(
+ "authver" to authver,
+ "x-ak" to ArmCloudSignatureV2.SECRET_KEY,
+ "x-timestamp" to timestamp.toString(),
+ "x-sign" to sign
+ )
+ )
+
+ return requestBuilder.build()
+ }
+
+ private fun buildPostRequest(
+ path: String,
+ body: String? = null
+ ): Request {
+ val url = "$HOST$path"
+ val bodyString = body?:""
+ val requestBuilder = Request.Builder()
+ .url(url)
+ .post(bodyString.toRequestBody(JSON_MEDIA_TYPE))
+ val authver = "2.0"
+ val timestamp = System.currentTimeMillis()
+ val sign =
+ ArmCloudSignatureV2.calculateSignature(timestamp.toString(), path, body)
+ addHeaders(
+ requestBuilder,
+ mapOf(
+ "authver" to authver,
+ "x-ak" to ArmCloudSignatureV2.SECRET_KEY,
+ "x-timestamp" to timestamp.toString(),
+ "x-sign" to sign
+ )
+ )
+
+ return requestBuilder.build()
+ }
+
+ private fun addHeaders(
+ builder: Request.Builder,
+ headers: Map
+ ) {
+ headers.forEach { (key, value) ->
+ builder.addHeader(key, value)
+ }
+ }
+
+ private fun buildFormBody(formData: Map): RequestBody {
+ val formBodyBuilder = FormBody.Builder()
+ formData.forEach { (key, value) ->
+ formBodyBuilder.add(key, value)
+ }
+ return formBodyBuilder.build()
+ }
+
+ private fun executeRequestAsync(
+ request: Request,
+ callback: HttpCallback
+ ) {
+ client.newCall(request).enqueue(object : Callback {
+ override fun onResponse(call: Call, response: Response) {
+ val responseBody = response.body?.string()
+ val code = response.code
+
+ if (response.isSuccessful && responseBody != null) {
+ callback.onSuccess(responseBody, code)
+ } else {
+ callback.onFailure(
+ responseBody ?: "Empty response",
+ code
+ )
+ }
+ }
+
+ override fun onFailure(call: Call, e: IOException) {
+ callback.onFailure(e.message ?: "Unknown network error", null)
+ }
+ })
+ }
+
+ private fun executeRequestSync(request: Request): Pair {
+ return try {
+ val response = client.newCall(request).execute()
+ val responseBody = response.peekBody(Long.MAX_VALUE).string()
+ val code = response.code
+ Log.d("TAG", "executeRequestSync: $responseBody")
+ if (response.isSuccessful && responseBody.isNotEmpty()) {
+ Pair(responseBody, code)
+ } else {
+ Pair(null, -1)
+ }
+ } catch (e: IOException) {
+ Pair(null, -1) // 网络错误状态码
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/studyapp/update/UpdateUtil.kt b/app/src/main/java/com/example/studyapp/update/UpdateUtil.kt
new file mode 100644
index 0000000..02bc38e
--- /dev/null
+++ b/app/src/main/java/com/example/studyapp/update/UpdateUtil.kt
@@ -0,0 +1,240 @@
+package com.example.studyapp.update
+
+import android.util.Log
+import com.example.studyapp.utils.ShellUtils
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
+import org.json.JSONArray
+import org.json.JSONException
+import org.json.JSONObject
+
+object UpdateUtil {
+ val scope = CoroutineScope(Dispatchers.IO)
+ fun changeDevice(recordPackageName: String, bigoDeviceObject:JSONObject?, afDeviceObject:JSONObject?, callback: ChangeCallBack){
+ try {
+ val padCode = ShellUtils.execRootCmdAndGetResult("getprop ro.boot.pad_code")
+ Log.d("TAG", "changeDevice: $padCode")
+ val jsonObject = JSONObject().apply {
+ put("padCodes", JSONArray().apply {
+ put(padCode)
+ })
+ }
+ var timeZone: String = ""
+ bigoDeviceObject?.apply {
+ val cpuClockSpeed: String = bigoDeviceObject.optString("cpu_clock_speed")
+ val gaid: String = bigoDeviceObject.optString("gaid")
+ val userAgent: String = bigoDeviceObject.optString("User-Agent")
+ val osLang: String = bigoDeviceObject.optString("os_lang")
+ val osVer: String = bigoDeviceObject.optString("os_ver")
+ timeZone = bigoDeviceObject.optString("tz")
+ val systemCountry: String = bigoDeviceObject.optString("system_country")
+ val simCountry: String = bigoDeviceObject.optString("sim_country")
+ val romFreeIn: Long = bigoDeviceObject.optLong("rom_free_in")
+ val resolution: String = bigoDeviceObject.optString("resolution")
+ val vendor: String = bigoDeviceObject.optString("vendor")
+ val batteryScale: Int = bigoDeviceObject.optInt("bat_scale")
+
+ val net: String = bigoDeviceObject.optString("net")
+ val dpi: Int = bigoDeviceObject.optInt("dpi")
+ val romFreeExt: Long = bigoDeviceObject.optLong("rom_free_ext")
+ val dpiF: String = bigoDeviceObject.optString("dpi_f")
+ val cpuCoreNum: Int = bigoDeviceObject.optInt("cpu_core_num")
+ }
+ afDeviceObject?.apply {
+ val advertiserId = afDeviceObject.optString(".advertiserId")
+ val model = afDeviceObject.optString(".model")
+ val brand = afDeviceObject.optString(".brand")
+ val androidId = afDeviceObject.optString(".android_id")
+ val xPixels = afDeviceObject.optInt(".deviceData.dim.x_px")
+ val yPixels = afDeviceObject.optInt(".deviceData.dim.y_px")
+ val densityDpi = afDeviceObject.optInt(".deviceData.dim.d_dpi")
+ val country = afDeviceObject.optString(".country")
+ val batteryLevel = afDeviceObject.optString(".batteryLevel")
+ val stackInfo = Thread.currentThread().getStackTrace()[2].toString()
+ val product = afDeviceObject.optString(".product")
+ val network = afDeviceObject.optString(".network")
+ val langCode = afDeviceObject.optString(".lang_code")
+ val cpuAbi = afDeviceObject.optString(".deviceData.cpu_abi")
+ val yDp = afDeviceObject.optInt(".deviceData.dim.ydp")
+ val lang = afDeviceObject.optString(".lang")
+ val ro_product_brand = afDeviceObject.optString("ro.product.brand", "")
+ val ro_product_model = afDeviceObject.optString("ro.product.model", "")
+ val ro_product_manufacturer =
+ afDeviceObject.optString("ro.product.manufacturer", "")
+ val ro_product_device = afDeviceObject.optString("ro.product.device", "")
+ val ro_product_name = afDeviceObject.optString("ro.product.name", "")
+ val ro_build_version_incremental =
+ afDeviceObject.optString("ro.build.version.incremental", "")
+ val ro_build_fingerprint = afDeviceObject.optString("ro.build.fingerprint", "")
+ val ro_odm_build_fingerprint =
+ afDeviceObject.optString("ro.odm.build.fingerprint", "")
+ val ro_product_build_fingerprint =
+ afDeviceObject.optString("ro.product.build.fingerprint", "")
+ val ro_system_build_fingerprint =
+ afDeviceObject.optString("ro.system.build.fingerprint", "")
+ val ro_system_ext_build_fingerprint =
+ afDeviceObject.optString("ro.system_ext.build.fingerprint", "")
+ val ro_vendor_build_fingerprint =
+ afDeviceObject.optString("ro.vendor.build.fingerprint", "")
+ val ro_build_platform = afDeviceObject.optString("ro.board.platform", "")
+ val persist_sys_cloud_drm_id =
+ afDeviceObject.optString("persist.sys.cloud.drm.id", "")
+ val persist_sys_cloud_battery_capacity =
+ afDeviceObject.optInt("persist.sys.cloud.battery.capacity", -1)
+ val persist_sys_cloud_gpu_gl_vendor =
+ afDeviceObject.optString("persist.sys.cloud.gpu.gl_vendor", "")
+ val persist_sys_cloud_gpu_gl_renderer =
+ afDeviceObject.optString("persist.sys.cloud.gpu.gl_renderer", "")
+ val persist_sys_cloud_gpu_gl_version =
+ afDeviceObject.optString("persist.sys.cloud.gpu.gl_version", "")
+ val persist_sys_cloud_gpu_egl_vendor =
+ afDeviceObject.optString("persist.sys.cloud.gpu.egl_vendor", "")
+ val persist_sys_cloud_gpu_egl_version =
+ afDeviceObject.optString("persist.sys.cloud.gpu.egl_version", "")
+ val global_android_id = afDeviceObject.optString(".android_id", "")
+ val anticheck_pkgs = afDeviceObject.optString(".anticheck_pkgs", "")
+ val pm_list_features = afDeviceObject.optString(".pm_list_features", "")
+ val pm_list_libraries = afDeviceObject.optString(".pm_list_libraries", "")
+ val system_http_agent = afDeviceObject.optString("system.http.agent", "")
+ val webkit_http_agent = afDeviceObject.optString("webkit.http.agent", "")
+ val com_fk_tools_pkgInfo = afDeviceObject.optString(".pkg_info", "")
+ val appsflyerKey = afDeviceObject.optString(".appsflyerKey", "")
+ val appUserId = afDeviceObject.optString(".appUserId", "")
+ val disk = afDeviceObject.optString(".disk", "")
+ val operator = afDeviceObject.optString(".operator", "")
+ val cell_mcc = afDeviceObject.optString(".cell.mcc", "")
+ val cell_mnc = afDeviceObject.optString(".cell.mnc", "")
+ val date1 = afDeviceObject.optString(".date1", "")
+ val date2 = afDeviceObject.optString(".date2", "")
+ val bootId = afDeviceObject.optString("BootId", "")
+
+ jsonObject.apply {
+ put("modemPropertiesList", JSONArray().apply {
+ put(JSONObject().apply {
+ put("propertiesName", "MCCMNC")
+ put("propertiesValue", "${cell_mcc},${cell_mnc}")
+ })
+ })
+ put("systemPropertiesList", JSONArray().apply {
+ put(JSONObject().apply {
+ put("propertiesName", "ro.product.manufacturer")
+ put("propertiesValue", ro_product_manufacturer)
+ })
+ put(JSONObject().apply {
+ put("propertiesName", "ro.product.brand")
+ put("propertiesValue", ro_product_brand)
+ })
+ put(JSONObject().apply {
+ put("propertiesName", "ro.product.model")
+ put("propertiesValue", ro_product_model)
+ })
+// put(JSONObject().apply {
+// put("propertiesName", "ro.build.display.id")
+// put("propertiesValue", )
+// })
+ put(JSONObject().apply {
+ put("propertiesName", "ro.product.name")
+ put("propertiesValue", ro_product_name)
+ })
+ put(JSONObject().apply {
+ put("propertiesName", "ro.product.device")
+ put("propertiesValue", ro_product_device)
+ })
+// put(JSONObject().apply {
+// put("propertiesName", "ro.product.board")
+// put("propertiesValue", bo)
+// })
+// put(JSONObject().apply {
+// put("propertiesName", "ro.build.tags")
+// put("propertiesValue", )
+// })
+ put(JSONObject().apply {
+ put("propertiesName", "ro.build.fingerprint")
+ put("propertiesValue", ro_build_fingerprint)
+ })
+// put(JSONObject().apply {
+// put("propertiesName", "ro.build.date.utc")
+// put("propertiesValue", da)
+// })
+// put(JSONObject().apply {
+// put("propertiesName", "ro.build.user")
+// put("propertiesValue", user)
+// })
+// put(JSONObject().apply {
+// put("propertiesName", "ro.build.host")
+// put("propertiesValue", device.expand.)
+// })
+// put(JSONObject().apply {
+// put("propertiesName", "ro.build.description")
+// put("propertiesValue", des)
+// })
+ put(JSONObject().apply {
+ put("propertiesName", "ro.build.version.incremental")
+ put("propertiesValue", ro_build_version_incremental)
+ })
+// put(JSONObject().apply {
+// put("propertiesName", "ro.build.version.codename")
+// put("propertiesValue", device.cod)
+// })
+ })
+ put("settingPropertiesList", JSONArray().apply {
+ put(JSONObject().apply {
+ put("propertiesName", "ssaid/${recordPackageName}")
+ put("propertiesValue", androidId)
+ })
+// put(JSONObject().apply {
+// put("propertiesName", "bt/mac")
+// put("propertiesValue", mac)
+// })
+ put(JSONObject().apply {
+ put("propertiesName", "language")
+ put("propertiesValue", langCode)
+ })
+ put(JSONObject().apply {
+ put("propertiesName", "timezone")
+ put("propertiesValue", timeZone)
+ })
+// put(JSONObject().apply {
+// put("propertiesName", "systemvolume")
+// put("propertiesValue", device.expand.)
+// })
+ })
+ put("oaidPropertiesList", JSONArray().apply {
+ put(JSONObject().apply {
+ put("propertiesName", "AAID")
+ put("propertiesValue", advertiserId)
+ })
+ })
+ }
+ }
+
+ val jsonString = jsonObject.toString()
+ val response = Api.updatePad(jsonString)
+ val dataList = response.data
+ if (response.isSuccess() && dataList!= null && dataList.isNotEmpty()){
+ val padTask = dataList[0]
+ scope.launch {
+ Log.d("TAG", "changeDevice: $padTask")
+ var loop = true
+ while (loop) {
+ delay(5000)
+ val result = Api.padTaskDetail(padTask.taskId)
+ if (result == 3) {
+ Log.d("ChangeDeviceInfoUtil", "changeDeviceInfo changeDeviceInfo success")
+ loop = false
+ callback.changeSuccess()
+ } else if (result == -1) {
+ Log.d("ChangeDeviceInfoUtil", "changeDeviceInfo changeDeviceInfo fail")
+ loop = false
+ callback.changeFailed()
+ }
+ }
+ }
+ }
+ } catch (e: JSONException) {
+ e.printStackTrace()
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/jniLibs/arm64-v8a/libnative.so b/app/src/main/jniLibs/arm64-v8a/libnative.so
index 967a418..290c546 100644
Binary files a/app/src/main/jniLibs/arm64-v8a/libnative.so and b/app/src/main/jniLibs/arm64-v8a/libnative.so differ
diff --git a/build.gradle b/build.gradle
index ac77d27..c4ae509 100644
--- a/build.gradle
+++ b/build.gradle
@@ -7,4 +7,5 @@ buildscript {
}
plugins {
alias(libs.plugins.android.application) apply false
+ alias(libs.plugins.kotlin.android) apply false
}
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 82a3b7e..8ce6ab6 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -8,6 +8,8 @@ material = "1.12.0"
activity = "1.10.1"
constraintlayout = "2.2.1"
playServicesAdsIdentifier = "18.2.0"
+kotlin = "2.0.21"
+coreKtx = "1.16.0"
[libraries]
junit = { group = "junit", name = "junit", version.ref = "junit" }
@@ -18,6 +20,8 @@ material = { group = "com.google.android.material", name = "material", version.r
activity = { group = "androidx.activity", name = "activity", version.ref = "activity" }
constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" }
play-services-ads-identifier = { group = "com.google.android.gms", name = "play-services-ads-identifier", version.ref = "playServicesAdsIdentifier" }
+core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }
+kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }