x1ongzhu 1 год назад
Родитель
Сommit
4013867767
20 измененных файлов с 439 добавлено и 245 удалено
  1. 136 0
      app/schemas/com.example.modifier.data.AppDatabase/4.json
  2. 5 0
      app/src/main/java/com/example/modifier/MyApplication.kt
  3. 2 2
      app/src/main/java/com/example/modifier/adapter/BackupAdapter.kt
  4. 13 1
      app/src/main/java/com/example/modifier/data/AppDatabase.kt
  5. 3 7
      app/src/main/java/com/example/modifier/data/BackupItem.kt
  6. 1 1
      app/src/main/java/com/example/modifier/data/BackupItemDao.kt
  7. 5 3
      app/src/main/java/com/example/modifier/http/KtorClient.kt
  8. 1 1
      app/src/main/java/com/example/modifier/http/api/RcsNumberApi.kt
  9. 1 1
      app/src/main/java/com/example/modifier/http/response/RcsNumberResponse.kt
  10. 1 0
      app/src/main/java/com/example/modifier/model/SpoofedSimInfo.kt
  11. 31 5
      app/src/main/java/com/example/modifier/repo/AppPrefsRepo.kt
  12. 31 12
      app/src/main/java/com/example/modifier/repo/AppStateRepo.kt
  13. 34 9
      app/src/main/java/com/example/modifier/repo/BackupRepository.kt
  14. 49 36
      app/src/main/java/com/example/modifier/repo/SpoofedSimInfoRepo.kt
  15. 26 33
      app/src/main/java/com/example/modifier/service/ModifierService.kt
  16. 63 74
      app/src/main/java/com/example/modifier/service/TaskRunner.kt
  17. 4 10
      app/src/main/java/com/example/modifier/ui/backup/BackupFragment.kt
  18. 28 36
      app/src/main/java/com/example/modifier/ui/settings/SettingsFragment.kt
  19. 4 13
      app/src/main/java/com/example/modifier/ui/utils/UtilsFragment.kt
  20. 1 1
      app/src/main/java/com/example/modifier/utils/System.kt

+ 136 - 0
app/schemas/com.example.modifier.data.AppDatabase/4.json

@@ -0,0 +1,136 @@
+{
+  "formatVersion": 1,
+  "database": {
+    "version": 4,
+    "identityHash": "8d26e8302c394aa76a666c722f56088e",
+    "entities": [
+      {
+        "tableName": "BackupItem",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `createdAt` INTEGER NOT NULL, `numberId` INTEGER NOT NULL, `number` TEXT NOT NULL, `country` TEXT NOT NULL, `code` TEXT NOT NULL, `mcc` TEXT NOT NULL, `mnc` TEXT NOT NULL, `imei` TEXT NOT NULL, `imsi` TEXT NOT NULL, `iccid` TEXT NOT NULL, `sendCount` INTEGER NOT NULL, `path` TEXT NOT NULL, `lastUse` INTEGER NOT NULL, `type` TEXT NOT NULL, `stock` INTEGER NOT NULL, `carrierId` TEXT NOT NULL, `carrierName` TEXT NOT NULL)",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "createdAt",
+            "columnName": "createdAt",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "numberId",
+            "columnName": "numberId",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "number",
+            "columnName": "number",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "country",
+            "columnName": "country",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "code",
+            "columnName": "code",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "mcc",
+            "columnName": "mcc",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "mnc",
+            "columnName": "mnc",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "imei",
+            "columnName": "imei",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "imsi",
+            "columnName": "imsi",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "iccid",
+            "columnName": "iccid",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "sendCount",
+            "columnName": "sendCount",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "path",
+            "columnName": "path",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "lastUse",
+            "columnName": "lastUse",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "type",
+            "columnName": "type",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "stock",
+            "columnName": "stock",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "carrierId",
+            "columnName": "carrierId",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "carrierName",
+            "columnName": "carrierName",
+            "affinity": "TEXT",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "autoGenerate": true,
+          "columnNames": [
+            "id"
+          ]
+        },
+        "indices": [],
+        "foreignKeys": []
+      }
+    ],
+    "views": [],
+    "setupQueries": [
+      "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+      "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '8d26e8302c394aa76a666c722f56088e')"
+    ]
+  }
+}

+ 5 - 0
app/src/main/java/com/example/modifier/MyApplication.kt

@@ -1,14 +1,17 @@
 package com.example.modifier
 
 import android.app.Application
+import android.util.Log
 import com.example.modifier.data.AppContainer
 import com.example.modifier.data.AppDataContainer
+import com.example.modifier.repo.AppPrefsRepo
 import dagger.hilt.android.HiltAndroidApp
 
 const val baseTag = "Modifier"
 
 @HiltAndroidApp
 class MyApplication : Application() {
+    private val tag = "$baseTag/MyApplication"
 
     lateinit var container: AppContainer
 
@@ -16,5 +19,7 @@ class MyApplication : Application() {
         super.onCreate()
         //        DynamicColors.applyToActivitiesIfAvailable(this);
         container = AppDataContainer(this)
+
+        Log.i(tag, "server=${AppPrefsRepo.instance.appPrefs.value.server}")
     }
 }

+ 2 - 2
app/src/main/java/com/example/modifier/adapter/BackupAdapter.kt

@@ -52,8 +52,8 @@ class BackupAdapter(
         holder.binding.tvTime.text =
             SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(Date(backup.createdAt))
         holder.binding.tvInfo.text = String.format(
-            "MCC: %s, MNC: %s, Country: %s, SendCount: %d, Fresh: %b",
-            backup.mcc, backup.mnc, backup.country.uppercase(), backup.sendCount, backup.fresh
+            "MCC: %s, MNC: %s, Country: %s, SendCount: %d, Stock: %b",
+            backup.mcc, backup.mnc, backup.country.uppercase(), backup.sendCount, backup.stock
         )
         holder.binding.btnRestore.setOnClickListener { v: View? ->
             MaterialAlertDialogBuilder(

+ 13 - 1
app/src/main/java/com/example/modifier/data/AppDatabase.kt

@@ -28,9 +28,20 @@ val MIGRATION_2_3 = object : Migration(2, 3) {
     }
 }
 
+val MIGRATION_3_4 = object : Migration(3, 4) {
+    override fun migrate(db: SupportSQLiteDatabase) {
+        db.execSQL(
+            "ALTER TABLE BackupItem ADD COLUMN numberId INTEGER NOT NULL DEFAULT 0"
+        )
+        db.execSQL(
+            "ALTER TABLE BackupItem RENAME COLUMN fresh TO stock"
+        )
+    }
+}
+
 @Database(
     entities = [BackupItem::class],
-    version = 3,
+    version = 4,
     exportSchema = true,
 )
 @TypeConverters(Converters::class)
@@ -48,6 +59,7 @@ abstract class AppDatabase : RoomDatabase() {
                 Room.databaseBuilder(context, AppDatabase::class.java, "app")
                     .addMigrations(MIGRATION_1_2)
                     .addMigrations(MIGRATION_2_3)
+                    .addMigrations(MIGRATION_3_4)
                     .build()
                     .also { Instance = it }
             }

+ 3 - 7
app/src/main/java/com/example/modifier/data/BackupItem.kt

@@ -1,17 +1,14 @@
 package com.example.modifier.data
 
-import androidx.room.ColumnInfo
 import androidx.room.Entity
-import androidx.room.Ignore
 import androidx.room.PrimaryKey
-import java.time.LocalDateTime
-import java.util.Date
 
 @Entity
 data class BackupItem(
     @PrimaryKey(autoGenerate = true)
     var id: Int? = null,
     val createdAt: Long,
+    val numberId: Int,
     val number: String,
     val country: String,
     val code: String,
@@ -24,8 +21,7 @@ data class BackupItem(
     val path: String,
     val lastUse: Long,
     val type: String,
-    @ColumnInfo(defaultValue = "0")
-    var fresh: Boolean = false,
+    var stock: Boolean,
     val carrierId: String,
-    val carrierName: String,
+    val carrierName: String
 )

+ 1 - 1
app/src/main/java/com/example/modifier/data/BackupItemDao.kt

@@ -36,6 +36,6 @@ interface BackupItemDao {
     @Query("SELECT * FROM backupitem WHERE country = :country AND number = :number limit 1")
     suspend fun findBackupForNumber(country: String, number: String): BackupItem?
 
-    @Query("SELECT * FROM backupitem WHERE number != :number AND (fresh = 1 OR lastUse < :time) order by createdAt limit 1")
+    @Query("SELECT * FROM backupitem WHERE number != :number AND (stock = 1 OR lastUse < :time) order by createdAt limit 1")
     suspend fun findBackupForRestore(number: String, time: Long): BackupItem?
 }

+ 5 - 3
app/src/main/java/com/example/modifier/http/KtorClient.kt

@@ -1,6 +1,7 @@
 package com.example.modifier.http
 
 import com.example.modifier.http.response.ErrorResponse
+import com.example.modifier.repo.AppPrefsRepo
 import io.ktor.client.HttpClient
 import io.ktor.client.call.body
 import io.ktor.client.engine.okhttp.OkHttp
@@ -31,10 +32,11 @@ private fun Throwable.isTimeoutException(): Boolean {
 }
 
 @OptIn(ExperimentalSerializationApi::class)
-fun ktorClient(baseUrl: String) = HttpClient(OkHttp) {
+var ktorClient = HttpClient(OkHttp) {
     defaultRequest {
-        url(baseUrl.endsWith("/").let {
-            if (it) baseUrl else "$baseUrl/"
+        val server = AppPrefsRepo.instance.appPrefs.value.server
+        url(server.endsWith("/").let {
+            if (it) server else "$server/"
         })
     }
     install(HttpSend) {

+ 1 - 1
app/src/main/java/com/example/modifier/http/api/RcsNumberApi.kt

@@ -6,7 +6,7 @@ import io.ktor.resources.Resource
 class RcsNumberApi() {
 
     @Resource("{id}")
-    class Id(val parent: RcsNumberApi = RcsNumberApi(), val id: Long) {
+    class Id(val parent: RcsNumberApi = RcsNumberApi(), val id: Int) {
         @Resource("delete")
         class Delete(val parent: Id)
 

+ 1 - 1
app/src/main/java/com/example/modifier/http/response/RcsNumberResponse.kt

@@ -7,7 +7,7 @@ import java.time.LocalDateTime
 
 @Serializable
 data class RcsNumberResponse(
-    val id: Long,
+    val id: Int,
     @Serializable(with = LocalDateTimeSerializer::class)
     val createdAt: LocalDateTime,
     @Serializable(with = LocalDateTimeSerializer::class)

+ 1 - 0
app/src/main/java/com/example/modifier/model/SpoofedSimInfo.kt

@@ -4,6 +4,7 @@ import kotlinx.serialization.Serializable
 
 @Serializable
 data class SpoofedSimInfo(
+    var numberId: Int,
     var number: String,
     var mcc: String,
     var mnc: String,

+ 31 - 5
app/src/main/java/com/example/modifier/repo/AppPreferencesRepository.kt → app/src/main/java/com/example/modifier/repo/AppPrefsRepo.kt

@@ -9,15 +9,22 @@ import androidx.datastore.preferences.core.edit
 import androidx.datastore.preferences.core.stringPreferencesKey
 import androidx.datastore.preferences.preferencesDataStore
 import com.example.modifier.BuildConfig
+import com.example.modifier.Utils
 import com.example.modifier.data.AppPreferences
+import com.example.modifier.utils.uniqueId
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.stateIn
-import kotlin.coroutines.coroutineContext
+import kotlinx.coroutines.launch
 
 val Context.appPreferencesDataStore by preferencesDataStore(name = "${BuildConfig.APPLICATION_ID}.appPreferences")
 
-class AppPreferencesRepository(private val context: Context) {
+class AppPrefsRepo private constructor(private val context: Context) {
+
+    companion object {
+        val instance by lazy { AppPrefsRepo(Utils.getContext()) }
+    }
 
     private object PreferencesKeys {
         val SERVER = stringPreferencesKey("server")
@@ -28,8 +35,19 @@ class AppPreferencesRepository(private val context: Context) {
         val PREVENT_RESET = booleanPreferencesKey("prevent_reset")
     }
 
+    val appPrefs = MutableStateFlow(
+        AppPreferences(
+            server = "http://47.98.225.28",
+            id = uniqueId,
+            name = Build.DEVICE,
+            preventClean = false,
+            preventRequest = false,
+            preventReset = false
+        )
+    )
+
     @SuppressLint("HardwareIds")
-    val appPreferencesFlow = context.appPreferencesDataStore.data.map {
+    private val appPreferencesFlow = context.appPreferencesDataStore.data.map {
         val server = it[PreferencesKeys.SERVER] ?: "http://47.98.225.28"
         var id = it[PreferencesKeys.ID] ?: ""
         if (id.isEmpty()) {
@@ -57,7 +75,15 @@ class AppPreferencesRepository(private val context: Context) {
         )
     }
 
-    suspend fun stateFlow() = appPreferencesFlow.stateIn(CoroutineScope(coroutineContext))
+    init {
+        CoroutineScope(Dispatchers.IO).launch {
+            appPreferencesFlow.collect {
+                appPrefs.emit(it)
+            }
+        }
+    }
+
+//    suspend fun stateFlow() = appPreferencesFlow.stateIn(CoroutineScope(coroutineContext))
 
     suspend fun updateId(id: String) {
         context.appPreferencesDataStore.edit { preferences ->

+ 31 - 12
app/src/main/java/com/example/modifier/repo/AppStateRepository.kt → app/src/main/java/com/example/modifier/repo/AppStateRepo.kt

@@ -7,23 +7,24 @@ import androidx.datastore.preferences.core.edit
 import androidx.datastore.preferences.core.intPreferencesKey
 import androidx.datastore.preferences.preferencesDataStore
 import com.example.modifier.BuildConfig
+import com.example.modifier.Utils
 import com.example.modifier.data.AppRuntimeFlags
 import com.example.modifier.data.AppState
 import com.example.modifier.enums.RequestNumberState
 import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.MainScope
+import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.stateIn
 import kotlinx.coroutines.flow.update
 import kotlinx.coroutines.launch
-import kotlin.coroutines.coroutineContext
 
 val Context.appStateDataStore by preferencesDataStore(name = "${BuildConfig.APPLICATION_ID}.appState")
 
-class AppStateRepository(
-    val context: Context
-) {
+class AppStateRepo private constructor(private val context: Context) {
+
+    companion object {
+        val instance by lazy { AppStateRepo(Utils.getContext()) }
+    }
 
     private object PreferencesKeys {
         val SEND = booleanPreferencesKey("send")
@@ -34,13 +35,32 @@ class AppStateRepository(
 
     private val appRuntimeFlags = MutableStateFlow(AppRuntimeFlags())
 
+    val appState = MutableStateFlow(
+        AppState(
+            send = false,
+            executedNum = 0,
+            successNum = 0,
+            requestedNum = 0,
+            running = false,
+            requesting = false,
+            preparing = false,
+            checkingConnection = false,
+            busy = false,
+            suspended = false,
+            requestNumberState = RequestNumberState.IDLE
+        )
+    )
+
     private val appStateFlow =
         context.appStateDataStore.data.combine(appRuntimeFlags) { preferences, runtimeFlags ->
             val sPrefs =
                 context.getSharedPreferences(BuildConfig.APPLICATION_ID, Context.MODE_PRIVATE)
             val busy =
                 runtimeFlags.running || runtimeFlags.requesting || runtimeFlags.preparing || runtimeFlags.checkingConnection
-            Log.i("AppStateRepository.appStateFlow", "running: ${runtimeFlags.running}, \nrequesting: ${runtimeFlags.requesting}, \npreparing: ${runtimeFlags.preparing}, \ncheckingConnection: ${runtimeFlags.checkingConnection}, \nbusy: $busy")
+            Log.i(
+                "AppStateRepository.appStateFlow",
+                "running: ${runtimeFlags.running}, \nrequesting: ${runtimeFlags.requesting}, \npreparing: ${runtimeFlags.preparing}, \ncheckingConnection: ${runtimeFlags.checkingConnection}, \nbusy: $busy"
+            )
             AppState(
                 send = preferences[PreferencesKeys.SEND] ?: false,
                 executedNum = preferences[PreferencesKeys.EXECUTED_NUM] ?: 0,
@@ -59,14 +79,13 @@ class AppStateRepository(
         }
 
     init {
-
-        MainScope().launch {
-
+        CoroutineScope(Dispatchers.IO).launch {
+            appStateFlow.collect {
+                appState.emit(it)
+            }
         }
     }
 
-    suspend fun stateFlow() = appStateFlow.stateIn(CoroutineScope(coroutineContext))
-
     suspend fun incrementExecutedNum(success: Boolean) {
         context.appStateDataStore.edit { preferences ->
             val executedNum = (preferences[PreferencesKeys.EXECUTED_NUM] ?: 0) + 1

+ 34 - 9
app/src/main/java/com/example/modifier/repo/BackupRepository.kt

@@ -16,26 +16,22 @@ import com.example.modifier.extension.enable
 import com.example.modifier.model.SpoofedSimInfo
 import com.example.modifier.utils.clearConv
 import com.example.modifier.utils.shellRun
-import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.delay
-import kotlinx.coroutines.withContext
-import org.apache.commons.io.IOUtils
 import java.io.File
-import java.nio.file.Files
 import java.util.Date
 import java.util.Optional
 
 class BackupRepository(
     private val context: Context,
     private val dao: BackupItemDao,
-    private val spoofedSimInfoRepository: SpoofedSimInfoRepository
+    private val spoofedSimInfoRepo: SpoofedSimInfoRepo
 ) {
     private val tag = "$baseTag/BackupRepository"
     suspend fun backup(
         spoofedSimInfo: SpoofedSimInfo,
         type: String,
         sendCount: Int,
-        fresh: Boolean = false
+        stock: Boolean = false
     ): BackupItem {
         clearConv()
         delay(3000)
@@ -80,6 +76,7 @@ class BackupRepository(
 
         val backup = BackupItem(
             createdAt = Date().time,
+            numberId = spoofedSimInfo.numberId,
             number = spoofedSimInfo.number,
             code = spoofedSimInfo.areaCode,
             country = spoofedSimInfo.country,
@@ -92,7 +89,7 @@ class BackupRepository(
             sendCount = sendCount,
             lastUse = Date().time,
             type = type,
-            fresh = fresh,
+            stock = stock,
             carrierId = Optional.ofNullable(spoofedSimInfo.carrierId).orElse("1"),
             carrierName = Optional.ofNullable(spoofedSimInfo.carrierName).orElse("T-Mobile")
         )
@@ -101,7 +98,7 @@ class BackupRepository(
             File(it.path).deleteRecursively()
             dao.delete(it)
             backup.sendCount += it.sendCount
-            backup.fresh = false
+            backup.stock = false
         }
 
         backup.id = dao.insert(backup).toInt()
@@ -109,8 +106,9 @@ class BackupRepository(
     }
 
     suspend fun restore(backup: BackupItem): Boolean {
-        spoofedSimInfoRepository.updateSpoofedSimInfo(
+        spoofedSimInfoRepo.updateSpoofedSimInfo(
             SpoofedSimInfo(
+                numberId = backup.numberId,
                 number = backup.number,
                 mcc = backup.mcc,
                 mnc = backup.mnc,
@@ -154,6 +152,33 @@ class BackupRepository(
             PACKAGE_GSF.clear(),
             "sleep 1",
             PACKAGE_GMS.clear(),
+            "settings put secure location_mode 0",
+            "pm revoke com.google.android.gms android.permission.GET_ACCOUNTS",
+            "pm revoke com.google.android.gms android.permission.SYSTEM_ALERT_WINDOW",
+            "pm revoke com.google.android.gms android.permission.POST_NOTIFICATIONS",
+            "pm revoke com.google.android.gms android.permission.READ_CONTACTS",
+            "pm revoke com.google.android.gms android.permission.CAMERA",
+            "pm revoke com.google.android.gms android.permission.RECEIVE_MMS",
+            "pm revoke com.google.android.gms android.permission.GET_APP_OPS_STATS",
+            "pm revoke com.google.android.gms android.permission.PROCESS_OUTGOING_CALLS",
+            "pm revoke com.google.android.gms android.permission.BLUETOOTH_CONNECT",
+            "pm revoke com.google.android.gms android.permission.BLUETOOTH_SCAN",
+            "pm revoke com.google.android.gms android.permission.BLUETOOTH_ADVERTISE",
+            "pm revoke com.google.android.gms android.permission.NEARBY_WIFI_DEVICES",
+            "pm revoke com.google.android.gms android.permission.UWB_RANGING",
+            "pm revoke com.google.android.gms android.permission.READ_CALL_LOG",
+            "pm revoke com.google.android.gms android.permission.WRITE_CONTACTS",
+            "pm revoke com.google.android.gms android.permission.CALL_PHONE",
+            "pm revoke com.google.android.gms android.permission.RECORD_AUDIO",
+            "pm revoke com.google.android.gms android.permission.READ_LOGS",
+            "pm revoke com.google.android.gms android.permission.READ_MEDIA_AUDIO",
+            "pm revoke com.google.android.gms android.permission.READ_MEDIA_IMAGES",
+            "pm revoke com.google.android.gms android.permission.READ_MEDIA_VIDEO",
+            "pm revoke com.google.android.gms android.permission.ACCESS_MEDIA_LOCATION",
+            "pm revoke com.google.android.gms android.permission.ACCESS_BROADCAST_RESPONSE_STATS",
+            "pm revoke com.google.android.gms android.permission.WRITE_CALL_LOG",
+            "pm revoke com.google.android.gms android.permission.BODY_SENSORS",
+            "pm revoke com.google.android.gms android.permission.DUMP",
             "sleep 30",
             PACKAGE_MESSAGING.enable(),
             "sleep 3",

+ 49 - 36
app/src/main/java/com/example/modifier/repo/SpoofedSimInfoRepository.kt → app/src/main/java/com/example/modifier/repo/SpoofedSimInfoRepo.kt

@@ -5,9 +5,9 @@ import android.annotation.SuppressLint
 import android.content.Context
 import android.telephony.SubscriptionManager
 import android.util.Log
-import androidx.core.content.ContextCompat
 import androidx.datastore.preferences.core.booleanPreferencesKey
 import androidx.datastore.preferences.core.edit
+import androidx.datastore.preferences.core.intPreferencesKey
 import androidx.datastore.preferences.core.stringPreferencesKey
 import androidx.datastore.preferences.preferencesDataStore
 import com.example.modifier.BuildConfig
@@ -16,7 +16,6 @@ import com.example.modifier.baseTag
 import com.example.modifier.constants.PACKAGE_GMS
 import com.example.modifier.constants.PACKAGE_MESSAGING
 import com.example.modifier.model.SpoofedSimInfo
-import com.example.modifier.serializer.Json
 import com.example.modifier.utils.genICCID
 import com.example.modifier.utils.genIMEI
 import com.example.modifier.utils.genIMSI
@@ -26,18 +25,24 @@ import com.example.modifier.utils.resumePackage
 import com.example.modifier.utils.shellRun
 import com.example.modifier.utils.suspendPackage
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.stateIn
-import org.apache.commons.io.FileUtils
-import java.io.File
+import kotlinx.coroutines.launch
 import kotlin.coroutines.coroutineContext
 
 val Context.simInfoDataStore by preferencesDataStore(name = "${BuildConfig.APPLICATION_ID}.simInfo")
 
-class SpoofedSimInfoRepository(private val context: Context) {
+class SpoofedSimInfoRepo private constructor(private val context: Context) {
     private val tag = "$baseTag/SpoofedSimInfoRepository"
 
+    companion object {
+        val instance by lazy { SpoofedSimInfoRepo(Utils.getContext()) }
+    }
+
     private object PreferencesKeys {
+        val NUMBER_ID = intPreferencesKey("number_id")
         val NUMBER = stringPreferencesKey("number")
         val MCC = stringPreferencesKey("mcc")
         val MNC = stringPreferencesKey("mnc")
@@ -51,36 +56,21 @@ class SpoofedSimInfoRepository(private val context: Context) {
         val CARRIER_NAME = stringPreferencesKey("carrier_name")
     }
 
-    val simInfoFlow = context.simInfoDataStore.data.map {
-        val file = File(ContextCompat.getDataDir(context), "config.json")
-        val old = file.exists().let {
-            if (it) {
-                try {
-                    return@let Json.decodeFromString<SpoofedSimInfo>(
-                        FileUtils.readFileToString(
-                            file,
-                            "UTF-8"
-                        )
-                    )
-                } catch (e: Exception) {
-                    e.printStackTrace()
-                }
-            }
-            null
-        }
-
-        val number = it[PreferencesKeys.NUMBER] ?: old?.number ?: ""
-        val mcc = it[PreferencesKeys.MCC] ?: old?.mcc ?: ""
-        val mnc = it[PreferencesKeys.MNC] ?: old?.mnc ?: ""
-        val iccid = it[PreferencesKeys.ICCID] ?: old?.iccid ?: ""
-        val imsi = it[PreferencesKeys.IMSI] ?: old?.imsi ?: ""
-        val imei = it[PreferencesKeys.IMEI] ?: old?.imei ?: ""
-        val country = it[PreferencesKeys.COUNTRY] ?: old?.country ?: ""
-        val areaCode = it[PreferencesKeys.AREA_CODE] ?: old?.areaCode ?: ""
-        val available = it[PreferencesKeys.AVAILABLE] ?: old?.available ?: false
-        val carrierId = it[PreferencesKeys.CARRIER_ID] ?: old?.carrierId ?: ""
-        val carrierName = it[PreferencesKeys.CARRIER_NAME] ?: old?.carrierName ?: ""
+    private val simInfoFlow = context.simInfoDataStore.data.map {
+        val numberId = it[PreferencesKeys.NUMBER_ID] ?: 0
+        val number = it[PreferencesKeys.NUMBER] ?: ""
+        val mcc = it[PreferencesKeys.MCC] ?: ""
+        val mnc = it[PreferencesKeys.MNC] ?: ""
+        val iccid = it[PreferencesKeys.ICCID] ?: ""
+        val imsi = it[PreferencesKeys.IMSI] ?: ""
+        val imei = it[PreferencesKeys.IMEI] ?: ""
+        val country = it[PreferencesKeys.COUNTRY] ?: ""
+        val areaCode = it[PreferencesKeys.AREA_CODE] ?: ""
+        val available = it[PreferencesKeys.AVAILABLE] ?: false
+        val carrierId = it[PreferencesKeys.CARRIER_ID] ?: ""
+        val carrierName = it[PreferencesKeys.CARRIER_NAME] ?: ""
         SpoofedSimInfo(
+            numberId = numberId,
             number = number,
             mcc = mcc,
             mnc = mnc,
@@ -95,14 +85,35 @@ class SpoofedSimInfoRepository(private val context: Context) {
         )
     }
 
-    suspend fun stateFlow() = simInfoFlow.stateIn(CoroutineScope(coroutineContext))
+    val spoofedSimInfo = MutableStateFlow(
+        SpoofedSimInfo(
+            numberId = 0,
+            number = "",
+            mcc = "",
+            mnc = "",
+            iccid = "",
+            imsi = "",
+            imei = "",
+            country = "",
+            areaCode = "",
+            available = false,
+            carrierId = "",
+            carrierName = ""
+        )
+    )
 
-    suspend fun value() {
+    init {
+        CoroutineScope(Dispatchers.IO).launch {
+            simInfoFlow.collect {
+                spoofedSimInfo.emit(it)
+            }
+        }
     }
 
     @SuppressLint("MissingPermission")
     suspend fun updateSpoofedSimInfo(spoofedSimInfo: SpoofedSimInfo, suspend: Boolean? = true) {
         context.simInfoDataStore.edit {
+            it[PreferencesKeys.NUMBER_ID] = spoofedSimInfo.numberId
             it[PreferencesKeys.NUMBER] = spoofedSimInfo.number
             it[PreferencesKeys.MCC] = spoofedSimInfo.mcc
             it[PreferencesKeys.MNC] = spoofedSimInfo.mnc
@@ -178,6 +189,7 @@ class SpoofedSimInfoRepository(private val context: Context) {
                 ?.let {
                     updateSpoofedSimInfo(
                         SpoofedSimInfo(
+                            numberId = 0,
                             number = it,
                             mcc = "310",
                             mnc = "150",
@@ -204,6 +216,7 @@ class SpoofedSimInfoRepository(private val context: Context) {
                 ?.let {
                     updateSpoofedSimInfo(
                         SpoofedSimInfo(
+                            numberId = 0,
                             number = it,
                             mcc = "310",
                             mnc = "240",

+ 26 - 33
app/src/main/java/com/example/modifier/service/ModifierService.kt

@@ -26,16 +26,15 @@ import com.example.modifier.R
 import com.example.modifier.baseTag
 import com.example.modifier.TraverseResult
 import com.example.modifier.data.AppDatabase
-import com.example.modifier.data.AppPreferences
 import com.example.modifier.data.AppState
 import com.example.modifier.databinding.FloatingWindowBinding
 import com.example.modifier.enums.RequestNumberState
 import com.example.modifier.model.SpoofedSimInfo
-import com.example.modifier.repo.AppPreferencesRepository
-import com.example.modifier.repo.AppStateRepository
+import com.example.modifier.repo.AppPrefsRepo
+import com.example.modifier.repo.AppStateRepo
 import com.example.modifier.repo.BackupRepository
 import com.example.modifier.repo.GoogleMessageStateRepository
-import com.example.modifier.repo.SpoofedSimInfoRepository
+import com.example.modifier.repo.SpoofedSimInfoRepo
 import com.example.modifier.utils.clearConv
 import com.example.modifier.utils.hasRootAccess
 import com.example.modifier.utils.isRebooted
@@ -74,20 +73,17 @@ class ModifierService : AccessibilityService() {
     private lateinit var binding: FloatingWindowBinding
 
     private val backupItemDao by lazy { AppDatabase.getDatabase(this).itemDao() }
-    private val appPreferencesRepository by lazy { AppPreferencesRepository(this) }
-    private lateinit var appPreferences: StateFlow<AppPreferences>
-    private val appStateRepository: AppStateRepository by lazy { AppStateRepository(this) }
-    private lateinit var appState: StateFlow<AppState>
+    private val appPrefsRepo = AppPrefsRepo.instance
+    private val appStateRepo = AppStateRepo.instance
     private val googleMessageStateRepository by lazy { GoogleMessageStateRepository(this) }
     private val screenInspector by lazy { ScreenInspector(this) }
     private val screenController by lazy { ScreenController(this, screenInspector) }
-    private val spoofedSimInfoRepository by lazy { SpoofedSimInfoRepository(this) }
-    private lateinit var spoofedSimInfo: StateFlow<SpoofedSimInfo>
+    private val spoofedSimInfoRepo = SpoofedSimInfoRepo.instance
     private val backupRepository by lazy {
         BackupRepository(
             this,
             backupItemDao,
-            spoofedSimInfoRepository
+            spoofedSimInfoRepo
         )
     }
     private lateinit var socketClient: SocketClient
@@ -176,21 +172,18 @@ class ModifierService : AccessibilityService() {
             false
         }
         CoroutineScope(Dispatchers.IO).launch {
-            appPreferences = appPreferencesRepository.stateFlow()
-            appState = appStateRepository.stateFlow()
-            spoofedSimInfo = spoofedSimInfoRepository.stateFlow()
             taskRunner = TaskRunner(
                 this@ModifierService,
                 screenInspector,
                 screenController,
-                appStateRepository,
-                appPreferencesRepository,
-                spoofedSimInfoRepository,
+                appStateRepo,
+                appPrefsRepo,
+                spoofedSimInfoRepo,
                 googleMessageStateRepository,
                 backupRepository,
             )
             launch {
-                appState.collect {
+                appStateRepo.appState.collect {
                     withContext(Dispatchers.Main) {
                         binding.swSend.isChecked = it.send
                         binding.btnReq.isEnabled = !it.requesting
@@ -251,7 +244,7 @@ class ModifierService : AccessibilityService() {
             }
 
             launch {
-                appPreferences.collect {
+                appPrefsRepo.appPrefs.collect {
                     withContext(Dispatchers.Main) {
                         binding.swSend.text = it.name
                     }
@@ -259,22 +252,22 @@ class ModifierService : AccessibilityService() {
                         socketClient.disconnect()
                     }
                     socketClient = SocketClient(
-                        appPreferences.value.id,
-                        appPreferences.value.server,
-                        appPreferences.value.name,
+                        appPrefsRepo.appPrefs.value.id,
+                        appPrefsRepo.appPrefs.value.server,
+                        appPrefsRepo.appPrefs.value.name,
                         taskRunner
                     )
                 }
             }
             launch {
-                spoofedSimInfo.collect {
+                spoofedSimInfoRepo.spoofedSimInfo.collect {
                     withContext(Dispatchers.Main) {
                         binding.tvCountry.text = it.country
                     }
                 }
             }
 
-            appStateRepository.updateRuntimeFlags(preparing = true)
+            appStateRepo.updateRuntimeFlags(preparing = true)
             val hasRoot = run checkRoot@{
                 repeat(30) {
                     if (hasRootAccess()) {
@@ -300,7 +293,7 @@ class ModifierService : AccessibilityService() {
             if (Build.MODEL.startsWith("SM-F707") || Build.MODEL.startsWith("SM-F711")) {
                 killPhoneProcess(force = false)
             }
-            appStateRepository.updateRuntimeFlags(preparing = false)
+            appStateRepo.updateRuntimeFlags(preparing = false)
 
             val timer = Timer()
             timer.schedule(object : TimerTask() {
@@ -325,7 +318,7 @@ class ModifierService : AccessibilityService() {
         binding.swSend.setOnTouchListener(touchListener)
         binding.swSend.setOnCheckedChangeListener { buttonView: CompoundButton?, isChecked: Boolean ->
             CoroutineScope(Dispatchers.IO).launch {
-                appStateRepository.updateSend(isChecked)
+                appStateRepo.updateSend(isChecked)
             }
         }
 
@@ -381,7 +374,7 @@ class ModifierService : AccessibilityService() {
 
                     R.id.clear_conv -> {
                         clearConv()
-                        appStateRepository.resetExecutedNum()
+                        appStateRepo.resetExecutedNum()
                     }
 
                     R.id.store_numbers -> {
@@ -398,9 +391,9 @@ class ModifierService : AccessibilityService() {
 
                     R.id.reset_counter -> {
                         CoroutineScope(Dispatchers.IO).launch {
-                            appStateRepository.resetExecutedNum()
-                            appStateRepository.resetSuccessNum()
-                            appStateRepository.resetRequestedNum()
+                            appStateRepo.resetExecutedNum()
+                            appStateRepo.resetSuccessNum()
+                            appStateRepo.resetRequestedNum()
                         }
                     }
                 }
@@ -421,9 +414,9 @@ class ModifierService : AccessibilityService() {
     private fun reportDeviceStatues() {
         if (this::socketClient.isInitialized) {
             socketClient.reportDeviceStatues(
-                appState.value.send,
-                appState.value.busy,
-                spoofedSimInfo.value.country
+                appStateRepo.appState.value.send,
+                appStateRepo.appState.value.busy,
+                spoofedSimInfoRepo.spoofedSimInfo.value.country
             )
         }
     }

+ 63 - 74
app/src/main/java/com/example/modifier/service/TaskRunner.kt

@@ -13,7 +13,6 @@ import com.example.modifier.constants.CMD_MESSAGING_APP
 import com.example.modifier.constants.CMD_RCS_SETTINGS_ACTIVITY
 import com.example.modifier.constants.PACKAGE_GMS
 import com.example.modifier.constants.PACKAGE_MESSAGING
-import com.example.modifier.data.AppPreferences
 import com.example.modifier.data.AppState
 import com.example.modifier.enums.RcsConfigureState
 import com.example.modifier.enums.RcsConnectionStatus
@@ -34,11 +33,11 @@ import com.example.modifier.model.TaskAction
 import com.example.modifier.model.TaskConfig
 import com.example.modifier.model.TaskExecutionResult
 import com.example.modifier.model.UpdateDeviceAction
-import com.example.modifier.repo.AppPreferencesRepository
-import com.example.modifier.repo.AppStateRepository
+import com.example.modifier.repo.AppPrefsRepo
+import com.example.modifier.repo.AppStateRepo
 import com.example.modifier.repo.BackupRepository
 import com.example.modifier.repo.GoogleMessageStateRepository
-import com.example.modifier.repo.SpoofedSimInfoRepository
+import com.example.modifier.repo.SpoofedSimInfoRepo
 import com.example.modifier.utils.changeClashProfile
 import com.example.modifier.utils.clearConv
 import com.example.modifier.utils.genICCID
@@ -86,9 +85,9 @@ class TaskRunner(
     private val context: Context,
     private val screenInspector: ScreenInspector,
     private val screenController: ScreenController,
-    private val appStateRepository: AppStateRepository,
-    private val appPreferencesRepository: AppPreferencesRepository,
-    private val spoofedSimInfoRepository: SpoofedSimInfoRepository,
+    private val appStateRepo: AppStateRepo,
+    private val appPrefsRepo: AppPrefsRepo,
+    private val spoofedSimInfoRepo: SpoofedSimInfoRepo,
     private val googleMessageStateRepository: GoogleMessageStateRepository,
     private val backupRepository: BackupRepository
 ) {
@@ -96,17 +95,6 @@ class TaskRunner(
     private var lastSend = 0L
     private var currentTaskId = 0
     private var requestMode = 1
-    private lateinit var appState: StateFlow<AppState>
-    private lateinit var appPreferences: StateFlow<AppPreferences>
-    private lateinit var spoofedSimInfo: StateFlow<SpoofedSimInfo>
-
-    init {
-        CoroutineScope(Dispatchers.IO).launch {
-            appState = appStateRepository.stateFlow()
-            appPreferences = appPreferencesRepository.stateFlow()
-            spoofedSimInfo = spoofedSimInfoRepository.stateFlow()
-        }
-    }
 
     suspend fun send(
         to: String,
@@ -162,10 +150,10 @@ class TaskRunner(
             } else {
                 Log.i(tag, "RCS not detected")
             }
-            appStateRepository.incrementExecutedNum(success)
+            appStateRepo.incrementExecutedNum(success)
             Log.i(
                 tag,
-                "executedNum: ${appState.value.executedNum}, successNum: ${appState.value.successNum}"
+                "executedNum: ${appStateRepo.appState.value.executedNum}, successNum: ${appStateRepo.appState.value.successNum}"
             )
             delay(1000)
             return success
@@ -180,7 +168,7 @@ class TaskRunner(
         onSuccess: (TaskExecutionResult) -> Unit,
         onError: (Exception) -> Unit
     ) {
-        if (appState.value.busy) {
+        if (appStateRepo.appState.value.busy) {
             onError(Exception("device busy"))
             return
         }
@@ -190,18 +178,18 @@ class TaskRunner(
             currentTaskId = taskAction.data.taskId
             requestMode = if (taskAction.data.config.useBackup) 2 else 1
 
-            if (taskAction.data.config.checkConnection && appState.value.executedNum == 0) {
-                appStateRepository.updateRuntimeFlags(checkingConnection = true)
+            if (taskAction.data.config.checkConnection && appStateRepo.appState.value.executedNum == 0) {
+                appStateRepo.updateRuntimeFlags(checkingConnection = true)
                 if (!checkRcsA10y()) {
                     onError(Exception("RCS not available"))
                     requestNumber()
-                    appStateRepository.updateRuntimeFlags(checkingConnection = false)
+                    appStateRepo.updateRuntimeFlags(checkingConnection = false)
                     return
                 }
-                appStateRepository.updateRuntimeFlags(checkingConnection = false)
+                appStateRepo.updateRuntimeFlags(checkingConnection = false)
             }
 
-            appStateRepository.updateRuntimeFlags(running = true, checkingConnection = false)
+            appStateRepo.updateRuntimeFlags(running = true, checkingConnection = false)
             val success = ArrayList<Int>()
             val fail = ArrayList<Int>()
             for (i in 0 until taskAction.data.tasks.size) {
@@ -224,31 +212,31 @@ class TaskRunner(
             }
             shellRun(CMD_BACK)
             onSuccess(TaskExecutionResult(success, fail))
-            if (taskConfig.requestNumberInterval in 1..appState.value.successNum) {
+            if (taskConfig.requestNumberInterval in 1..appStateRepo.appState.value.successNum) {
                 delay(3000)
                 requestNumber()
-            } else if (taskConfig.cleanCount in 1..appState.value.executedNum && !appPreferences.value.preventClean) {
+            } else if (taskConfig.cleanCount in 1..appStateRepo.appState.value.executedNum && !appPrefsRepo.appPrefs.value.preventClean) {
                 delay(3000)
                 clearConv();
                 shellRun(CMD_MESSAGING_APP)
                 delay(3000)
-                appStateRepository.resetExecutedNum()
+                appStateRepo.resetExecutedNum()
             } else {
                 delay(2000)
             }
-            appStateRepository.updateRuntimeFlags(running = false)
+            appStateRepo.updateRuntimeFlags(running = false)
             if (taskAction.data.config.checkConnection && success.isEmpty()) {
-                appStateRepository.updateRuntimeFlags(checkingConnection = true)
+                appStateRepo.updateRuntimeFlags(checkingConnection = true)
                 if (!checkRcsA10y()) {
                     onError(Exception("RCS not available"))
                     requestNumber()
                 }
-                appStateRepository.updateRuntimeFlags(checkingConnection = false)
+                appStateRepo.updateRuntimeFlags(checkingConnection = false)
             }
         } catch (e: Exception) {
             Log.e(tag, "runTaskError: ${e.message}", e)
             onError(e)
-            appStateRepository.updateRuntimeFlags(running = false)
+            appStateRepo.updateRuntimeFlags(running = false)
         }
     }
 
@@ -257,9 +245,9 @@ class TaskRunner(
             withTimeout(1.hours) {
                 while (true) {
                     delay(100)
-                    appStateRepository.updateRuntimeFlags(requestNumberState = RequestNumberState.RESET)
+                    appStateRepo.updateRuntimeFlags(requestNumberState = RequestNumberState.RESET)
                     googleMessageStateRepository.updateRcsState(RcsConfigureState.NOT_CONFIGURED)
-                    spoofedSimInfoRepository.mock()
+                    spoofedSimInfoRepo.mock()
                     resetAll()
                     var switchAppear = googleMessageStateRepository.waitForRcsState(
                         arrayOf(RcsConfigureState.WAITING_FOR_TOS),
@@ -307,7 +295,7 @@ class TaskRunner(
                         ).let { it == RcsConfigureState.READY }
                     }
                     Log.i(tag, "waitForRcsState: $resetSuccess")
-                    appStateRepository.resetRequestedNum()
+                    appStateRepo.resetRequestedNum()
                     if (resetSuccess) {
                         delay(3000)
                         break
@@ -318,9 +306,9 @@ class TaskRunner(
             withTimeout(1.hours) {
                 while (true) {
                     delay(100)
-                    appStateRepository.updateRuntimeFlags(requestNumberState = RequestNumberState.RESET)
+                    appStateRepo.updateRuntimeFlags(requestNumberState = RequestNumberState.RESET)
                     googleMessageStateRepository.updateRcsState(RcsConfigureState.NOT_CONFIGURED)
-                    spoofedSimInfoRepository.mock()
+                    spoofedSimInfoRepo.mock()
                     resetAll()
                     var switchAppear = googleMessageStateRepository.waitForRcsState(
                         arrayOf(RcsConfigureState.WAITING_FOR_DEFAULT_ON),
@@ -365,7 +353,7 @@ class TaskRunner(
                         ).let { it == RcsConfigureState.READY }
                     }
                     Log.i(tag, "waitForRcsState: $resetSuccess")
-                    appStateRepository.resetRequestedNum()
+                    appStateRepo.resetRequestedNum()
                     if (resetSuccess) {
                         delay(3000)
                         break
@@ -380,37 +368,37 @@ class TaskRunner(
         noBackup: Boolean = false,
         fresh: Boolean = false
     ) {
-        appStateRepository.updateRuntimeFlags(suspended = false)
-        if (appPreferences.value.preventRequest) {
+        appStateRepo.updateRuntimeFlags(suspended = false)
+        if (appPrefsRepo.appPrefs.value.preventRequest) {
             return
         }
-        if (appState.value.requesting) {
+        if (appStateRepo.appState.value.requesting) {
             return
         }
 
-        appStateRepository.updateRuntimeFlags(requesting = true)
+        appStateRepo.updateRuntimeFlags(requesting = true)
         clearConv()
-        if (spoofedSimInfo.value.available) {
-            appStateRepository.updateRuntimeFlags(requestNumberState = RequestNumberState.BACKUP)
+        if (spoofedSimInfoRepo.spoofedSimInfo.value.available) {
+            appStateRepo.updateRuntimeFlags(requestNumberState = RequestNumberState.BACKUP)
             backupRepository.backup(
-                spoofedSimInfo = spoofedSimInfo.value,
+                spoofedSimInfo = spoofedSimInfoRepo.spoofedSimInfo.value,
                 type = "auto",
-                sendCount = appState.value.executedNum,
-                fresh = fresh
+                sendCount = appStateRepo.appState.value.successNum,
+                stock = fresh
             )
         }
 
-        appStateRepository.incrementRequestedNum()
+        appStateRepo.incrementRequestedNum()
         var requestSuccess = false
         var retry = 0
         var needRest = reset
         withTimeoutOrNull(2.hours) {
             while (true) {
                 delay(200)
-                needRest = needRest || retry > 2 || appState.value.requestedNum > 5
+                needRest = needRest || retry > 2 || appStateRepo.appState.value.requestedNum > 5
                 try {
                     val device =
-                        ktorClient(appPreferences.value.server).get(DeviceApi.Id(id = appPreferences.value.id))
+                        ktorClient.get(DeviceApi.Id(id = appPrefsRepo.appPrefs.value.id))
                             .body<DeviceResponse>()
                     if (isClashInstalled(context)) {
 
@@ -439,7 +427,7 @@ class TaskRunner(
 
                     if (requestMode == 2 && !noBackup) {
                         val backup = backupRepository.findBackupForRestore(
-                            spoofedSimInfo.value.number,
+                            spoofedSimInfoRepo.spoofedSimInfo.value.number,
                             System.currentTimeMillis() - 2.days.inWholeMilliseconds
                         )
                         if (backup != null) {
@@ -447,9 +435,9 @@ class TaskRunner(
                                 requestSuccess = true
                                 break
                             } else {
-                                appStateRepository.updateRuntimeFlags(requestNumberState = RequestNumberState.BACKUP)
+                                appStateRepo.updateRuntimeFlags(requestNumberState = RequestNumberState.BACKUP)
                                 backupRepository.backup(
-                                    spoofedSimInfo = spoofedSimInfo.value,
+                                    spoofedSimInfo = spoofedSimInfoRepo.spoofedSimInfo.value,
                                     type = "auto",
                                     sendCount = 0
                                 )
@@ -458,23 +446,23 @@ class TaskRunner(
                         }
                     }
 
-                    if (needRest && !appPreferences.value.preventReset) {
+                    if (needRest && !appPrefsRepo.appPrefs.value.preventReset) {
                         reset()
                         retry = 0
                         needRest = false
                     }
 
                     googleMessageStateRepository.updateRcsState(RcsConfigureState.NOT_CONFIGURED)
-                    appStateRepository.updateRuntimeFlags(requestNumberState = RequestNumberState.REQUEST)
+                    appStateRepo.updateRuntimeFlags(requestNumberState = RequestNumberState.REQUEST)
 
                     val req = RcsNumberRequest(
-                        deviceId = appPreferences.value.id,
+                        deviceId = appPrefsRepo.appPrefs.value.id,
                         taskId = currentTaskId
                     )
                     if (!TextUtils.isEmpty(device.pinCountry)) {
                         req.country = device.pinCountry
                     }
-                    val response = ktorClient(appPreferences.value.server).put(
+                    val response = ktorClient.put(
                         RcsNumberApi()
                     ) {
                         contentType(ContentType.Application.Json)
@@ -487,10 +475,11 @@ class TaskRunner(
                     var rcsNumber = response.body<RcsNumberResponse>()
                     Log.i(tag, "requestNumber response: $rcsNumber")
 
-                    appStateRepository.updateRuntimeFlags(requestNumberState = RequestNumberState.OTP_1)
+                    appStateRepo.updateRuntimeFlags(requestNumberState = RequestNumberState.OTP_1)
 
-                    spoofedSimInfoRepository.updateSpoofedSimInfo(
+                    spoofedSimInfoRepo.updateSpoofedSimInfo(
                         SpoofedSimInfo(
+                            numberId = rcsNumber.id,
                             number = rcsNumber.number,
                             mcc = rcsNumber.mcc,
                             mnc = rcsNumber.mnc,
@@ -540,7 +529,7 @@ class TaskRunner(
 
                     launch {
                         try {
-                            ktorClient(appPreferences.value.server).post(
+                            ktorClient.post(
                                 RcsNumberApi.Id.OtpState(
                                     RcsNumberApi.Id(
                                         RcsNumberApi(),
@@ -558,12 +547,12 @@ class TaskRunner(
                         continue
                     }
 
-                    appStateRepository.updateRuntimeFlags(requestNumberState = RequestNumberState.OTP_2)
+                    appStateRepo.updateRuntimeFlags(requestNumberState = RequestNumberState.OTP_2)
                     withTimeoutOrNull(60.seconds) {
                         while (true) {
                             try {
                                 rcsNumber =
-                                    ktorClient(appPreferences.value.server).get(RcsNumberApi.Id(id = rcsNumber.id))
+                                    ktorClient.get(RcsNumberApi.Id(id = rcsNumber.id))
                                         .body<RcsNumberResponse>()
                                 Log.i(tag, "wait for otp response: $rcsNumber")
                                 if (rcsNumber.status == RcsNumberResponse.STATUS_SUCCESS || rcsNumber.status == RcsNumberResponse.STATUS_EXPIRED) {
@@ -585,7 +574,7 @@ class TaskRunner(
                         Regex("Your Messenger verification code is G-(\\d{6})")
                             .find(rcsNumber.message!!)
                     if (match != null) {
-                        appStateRepository.updateRuntimeFlags(requestNumberState = RequestNumberState.CONFIG)
+                        appStateRepo.updateRuntimeFlags(requestNumberState = RequestNumberState.CONFIG)
 
                         val otp = match.groupValues[1]
                         Log.i(tag, "OTP: $otp")
@@ -627,7 +616,7 @@ class TaskRunner(
                         } else {
                             launch {
                                 try {
-                                    ktorClient(appPreferences.value.server).post(
+                                    ktorClient.post(
                                         RcsNumberApi.Id.Configured(
                                             RcsNumberApi.Id(
                                                 RcsNumberApi(),
@@ -653,23 +642,23 @@ class TaskRunner(
             }
         }
         if (requestSuccess) {
-            spoofedSimInfoRepository.updateSpoofedSimInfo(
-                spoofedSimInfo = spoofedSimInfo.value.copy(
+            spoofedSimInfoRepo.updateSpoofedSimInfo(
+                spoofedSimInfo = spoofedSimInfoRepo.spoofedSimInfo.value.copy(
                     available = true
                 ),
                 suspend = false
             )
-            appStateRepository.resetSuccessNum()
-            appStateRepository.resetExecutedNum()
+            appStateRepo.resetSuccessNum()
+            appStateRepo.resetExecutedNum()
             Log.i(tag, "requestNumber success")
             delay(5000)
             shellRun(PACKAGE_MESSAGING.kill(), "sleep 1", CMD_MESSAGING_APP)
             delay(2000)
         } else {
             Log.e(tag, "requestNumber failed")
-            appStateRepository.updateSend(false)
+            appStateRepo.updateSend(false)
         }
-        appStateRepository.updateRuntimeFlags(
+        appStateRepo.updateRuntimeFlags(
             requesting = false,
             requestNumberState = RequestNumberState.IDLE,
             suspended = !requestSuccess
@@ -705,7 +694,7 @@ class TaskRunner(
                 if (rcsConnected) {
                     val checkRcsA10yNumbers = mutableListOf<String>()
                     try {
-                        val config = ktorClient(appPreferences.value.server)
+                        val config = ktorClient
                             .get(SysConfigApi.Id(SysConfigApi(), "check_availability_numbers"))
                             .body<SysConfigResponse>()
                         Log.i(tag, "sysConfig response: $config")
@@ -803,10 +792,10 @@ class TaskRunner(
     ) {
         try {
             if (updateDeviceAction.data.id != null) {
-                appPreferencesRepository.updateId(updateDeviceAction.data.id)
+                appPrefsRepo.updateId(updateDeviceAction.data.id)
             }
             if (updateDeviceAction.data.name != null) {
-                appPreferencesRepository.updateName(updateDeviceAction.data.name)
+                appPrefsRepo.updateName(updateDeviceAction.data.name)
             }
             onSuccess()
         } catch (e: Exception) {

+ 4 - 10
app/src/main/java/com/example/modifier/ui/backup/BackupFragment.kt

@@ -21,7 +21,7 @@ import com.example.modifier.data.BackupItem
 import com.example.modifier.databinding.FragmentBackupBinding
 import com.example.modifier.model.SpoofedSimInfo
 import com.example.modifier.repo.BackupRepository
-import com.example.modifier.repo.SpoofedSimInfoRepository
+import com.example.modifier.repo.SpoofedSimInfoRepo
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.FlowPreview
 import kotlinx.coroutines.flow.StateFlow
@@ -34,15 +34,12 @@ class BackupFragment : Fragment() {
     private lateinit var binding: FragmentBackupBinding
     private val list = mutableListOf<BackupItem>()
     private lateinit var adapter: BackupAdapter
-    private val spoofedSimInfoRepository by lazy {
-        SpoofedSimInfoRepository(requireContext())
-    }
-    private lateinit var spoofedSimInfo: StateFlow<SpoofedSimInfo>
+    private val spoofedSimInfoRepo = SpoofedSimInfoRepo.instance
     private val backupItemDao by lazy {
         AppDatabase.getDatabase(requireContext()).itemDao()
     }
     private val backupRepository by lazy {
-        BackupRepository(requireContext(), backupItemDao, spoofedSimInfoRepository)
+        BackupRepository(requireContext(), backupItemDao, spoofedSimInfoRepo)
     }
     private var sort: Boolean
         get() {
@@ -76,7 +73,7 @@ class BackupFragment : Fragment() {
             binding.btnAdd.setOnClickListener {
                 lifecycleScope.launch {
                     withContext(Dispatchers.IO) {
-                        val backup = backupRepository.backup(spoofedSimInfo.value, "manual", 0)
+                        val backup = backupRepository.backup(spoofedSimInfoRepo.spoofedSimInfo.value, "manual", 0)
                         list.add(0, backup)
                     }
                     adapter.notifyItemInserted(0)
@@ -95,9 +92,6 @@ class BackupFragment : Fragment() {
                 insets
             }
         }
-        lifecycleScope.launch {
-            spoofedSimInfo = spoofedSimInfoRepository.stateFlow()
-        }
         lifecycleScope.launch {
             withContext(Dispatchers.IO) {
                 refresh()

+ 28 - 36
app/src/main/java/com/example/modifier/ui/settings/SettingsFragment.kt

@@ -25,18 +25,16 @@ import com.example.modifier.R
 import com.example.modifier.baseTag
 import com.example.modifier.Utils
 import com.example.modifier.constants.servers
-import com.example.modifier.data.AppPreferences
-import com.example.modifier.repo.AppPreferencesRepository
+import com.example.modifier.repo.AppPrefsRepo
 import com.example.modifier.databinding.FragmentSettingsBinding
 import com.example.modifier.http.ktorClient
 import com.example.modifier.http.api.RcsNumberApi
 import com.example.modifier.http.request.RcsNumberRequest
 import com.example.modifier.http.response.RcsNumberResponse
 import com.example.modifier.model.SpoofedSimInfo
-import com.example.modifier.repo.SpoofedSimInfoRepository
+import com.example.modifier.repo.SpoofedSimInfoRepo
 import com.example.modifier.utils.genICCID
 import com.example.modifier.utils.genIMEI
-import com.example.modifier.utils.uniqueId
 import com.google.android.material.dialog.MaterialAlertDialogBuilder
 import io.ktor.client.call.body
 import io.ktor.client.plugins.resources.put
@@ -62,14 +60,8 @@ class SettingsFragment : Fragment() {
     private val tag = "$baseTag/SettingsFragment"
     private lateinit var binding: FragmentSettingsBinding
     private lateinit var requestPermissionLauncher: ActivityResultLauncher<String>
-    private val appPreferencesRepository: AppPreferencesRepository by lazy {
-        AppPreferencesRepository(requireContext())
-    }
-    private lateinit var appPreferences: StateFlow<AppPreferences>
-    private val spoofedSimInfoRepository by lazy {
-        SpoofedSimInfoRepository(requireContext())
-    }
-    private lateinit var spoofedSimInfo: StateFlow<SpoofedSimInfo>
+    private val appPrefsRepo = AppPrefsRepo.instance
+    private val spoofedSimInfoRepo = SpoofedSimInfoRepo.instance
 
     init {
         Log.i("SettingsFragment", "SettingsFragment")
@@ -146,8 +138,8 @@ class SettingsFragment : Fragment() {
                     return@setOnClickListener
                 }
                 lifecycleScope.launch {
-                    appPreferencesRepository.updateServer(server)
-                    appPreferencesRepository.updateName(binding.etDeviceLabel.text.toString())
+                    appPrefsRepo.updateServer(server)
+                    appPrefsRepo.updateName(binding.etDeviceLabel.text.toString())
                     Utils.makeLoadingButton(context, binding.btnServer)
                     binding.btnServer.isEnabled = false
                     delay(500)
@@ -168,11 +160,11 @@ class SettingsFragment : Fragment() {
                     withContext(Dispatchers.IO) req@{
                         val response: HttpResponse
                         try {
-                            response = ktorClient(appPreferences.value.server).put(
+                            response = ktorClient.put(
                                 RcsNumberApi()
                             ) {
                                 contentType(ContentType.Application.Json)
-                                setBody(RcsNumberRequest(deviceId = appPreferences.value.id))
+                                setBody(RcsNumberRequest(deviceId = appPrefsRepo.appPrefs.value.id))
                             }
                         } catch (e: Exception) {
                             withContext(Dispatchers.Main) {
@@ -200,19 +192,20 @@ class SettingsFragment : Fragment() {
                         val imsi =
                             mcc + mnc + RandomStringUtils.randomNumeric(15 - mcc.length - mnc.length)
                         val imei = genIMEI()
-                        spoofedSimInfoRepository.updateSpoofedSimInfo(
+                        spoofedSimInfoRepo.updateSpoofedSimInfo(
                             SpoofedSimInfo(
-                                number,
-                                mcc,
-                                mnc,
-                                iccid,
-                                imsi,
-                                imei,
-                                country,
-                                areaCode,
-                                false,
-                                res.carrierId,
-                                res.carrierName
+                                numberId = res.id,
+                                number = number,
+                                mcc = mcc,
+                                mnc = mnc,
+                                iccid = iccid,
+                                imsi = imsi,
+                                imei = imei,
+                                country = country,
+                                areaCode = areaCode,
+                                available = false,
+                                carrierId = res.carrierId,
+                                carrierName = res.carrierName
                             )
                         )
                     }
@@ -224,17 +217,17 @@ class SettingsFragment : Fragment() {
 
             binding.switchClean.setOnCheckedChangeListener { buttonView, isChecked ->
                 CoroutineScope(Dispatchers.IO).launch {
-                    appPreferencesRepository.updatePreventClean(isChecked)
+                    appPrefsRepo.updatePreventClean(isChecked)
                 }
             }
             binding.switchRequest.setOnCheckedChangeListener { buttonView, isChecked ->
                 CoroutineScope(Dispatchers.IO).launch {
-                    appPreferencesRepository.updatePreventRequest(isChecked)
+                    appPrefsRepo.updatePreventRequest(isChecked)
                 }
             }
             binding.swReset.setOnCheckedChangeListener { buttonView, isChecked ->
                 CoroutineScope(Dispatchers.IO).launch {
-                    appPreferencesRepository.updatePreventReset(isChecked)
+                    appPrefsRepo.updatePreventReset(isChecked)
                 }
             }
 
@@ -312,10 +305,8 @@ class SettingsFragment : Fragment() {
         }
 
         viewLifecycleOwner.lifecycleScope.launch {
-            appPreferences = appPreferencesRepository.stateFlow()
-            spoofedSimInfo = spoofedSimInfoRepository.stateFlow()
             launch {
-                appPreferencesRepository.stateFlow().collect {
+                appPrefsRepo.appPrefs.collect {
                     Log.i(tag, "appPreferencesRepository.collect")
                     binding.etServer.setText(it.server)
                     binding.etDeviceLabel.setText(it.name)
@@ -325,7 +316,7 @@ class SettingsFragment : Fragment() {
                 }
             }
             launch {
-                spoofedSimInfo.collect {
+                spoofedSimInfoRepo.spoofedSimInfo.collect {
                     Log.i(tag, "spoofedSimInfo.collect")
                     binding.etNumber.setText(it.number)
                     binding.etMcc.setText(it.mcc)
@@ -348,8 +339,9 @@ class SettingsFragment : Fragment() {
         binding.btnSave.isEnabled = false
         lifecycleScope.launch {
             withContext(Dispatchers.IO) {
-                spoofedSimInfoRepository.updateSpoofedSimInfo(
+                spoofedSimInfoRepo.updateSpoofedSimInfo(
                     SpoofedSimInfo(
+                        0,
                         binding.etNumber.text.toString(),
                         binding.etMcc.text.toString(),
                         binding.etMnc.text.toString(),

+ 4 - 13
app/src/main/java/com/example/modifier/ui/utils/UtilsFragment.kt

@@ -13,19 +13,16 @@ import com.example.modifier.Frida
 import com.example.modifier.R
 import com.example.modifier.Utils
 import com.example.modifier.baseTag
-import com.example.modifier.constants.CMD_BACK_APP
 import com.example.modifier.constants.PACKAGE_GMS
 import com.example.modifier.constants.PACKAGE_GSF
 import com.example.modifier.constants.PACKAGE_MESSAGING
 import com.example.modifier.data.AppPreferences
-import com.example.modifier.repo.AppPreferencesRepository
 import com.example.modifier.databinding.DialogUpdateBinding
 import com.example.modifier.databinding.FragmentUtilsBinding
 import com.example.modifier.extension.kill
 import com.example.modifier.http.ktorClient
 import com.example.modifier.http.api.SysConfigApi
 import com.example.modifier.http.response.SysConfigResponse
-import com.example.modifier.service.ModifierService
 import com.example.modifier.utils.clear
 import com.example.modifier.utils.clearConv
 import com.example.modifier.utils.killPhoneProcess
@@ -55,16 +52,10 @@ import java.io.File
 class UtilsFragment : Fragment() {
     private val tag = "$baseTag/UtilsFragment"
     private lateinit var binding: FragmentUtilsBinding
-    private val appPreferencesRepository: AppPreferencesRepository by lazy {
-        AppPreferencesRepository(requireContext())
-    }
     private lateinit var appPreferences: StateFlow<AppPreferences>
 
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
-        CoroutineScope(Dispatchers.IO).launch {
-            appPreferences = appPreferencesRepository.stateFlow()
-        }
     }
 
     override fun onCreateView(
@@ -228,7 +219,7 @@ class UtilsFragment : Fragment() {
         binding.btnUpdateModifier.setOnClickListener {
             lifecycleScope.launch {
                 try {
-                    val config = ktorClient(appPreferences.value.server).get(
+                    val config = ktorClient.get(
                         SysConfigApi.Id(
                             SysConfigApi(),
                             "modifier_apk"
@@ -245,7 +236,7 @@ class UtilsFragment : Fragment() {
         binding.btnUpdateMessage.setOnClickListener {
             lifecycleScope.launch {
                 try {
-                    val config = ktorClient(appPreferences.value.server).get(
+                    val config = ktorClient.get(
                         SysConfigApi.Id(
                             SysConfigApi(),
                             "message_apk"
@@ -262,7 +253,7 @@ class UtilsFragment : Fragment() {
         binding.btnUpdateGms.setOnClickListener {
             lifecycleScope.launch {
                 try {
-                    val config = ktorClient(appPreferences.value.server).get(
+                    val config = ktorClient.get(
                         SysConfigApi.Id(
                             SysConfigApi(),
                             "gms_apk"
@@ -365,7 +356,7 @@ class UtilsFragment : Fragment() {
             try {
                 val file = File.createTempFile("files", ".apk")
 
-                ktorClient(appPreferences.value.server).prepareGet(url)
+                ktorClient.prepareGet(url)
                     .execute { httpResponse ->
                         val channel: ByteReadChannel = httpResponse.body()
                         while (!channel.isClosedForRead) {

+ 1 - 1
app/src/main/java/com/example/modifier/utils/System.kt

@@ -163,7 +163,7 @@ suspend fun optimize() {
 suspend fun syncTime() {
     try {
         Log.i("Modifier", "syncTime: start")
-        val response = ktorClient("").head("http://www.baidu.com")
+        val response = ktorClient.head("http://www.baidu.com")
         val dateHeader = response.headers["Date"]
         val date = ZonedDateTime.parse(
             dateHeader,