x1ongzhu 1 год назад
Родитель
Сommit
ace05829f8

+ 16 - 4
app/schemas/com.example.modifier.data.AppDatabase/1.json

@@ -2,11 +2,11 @@
   "formatVersion": 1,
   "database": {
     "version": 1,
-    "identityHash": "c4b2fc1aa20ac555446cdde24166c0a0",
+    "identityHash": "0bb12407277293c16c7545f55493e823",
     "entities": [
       {
         "tableName": "BackupItem",
-        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `createdAt` TEXT 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)",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `createdAt` 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)",
         "fields": [
           {
             "fieldPath": "id",
@@ -17,7 +17,7 @@
           {
             "fieldPath": "createdAt",
             "columnName": "createdAt",
-            "affinity": "TEXT",
+            "affinity": "INTEGER",
             "notNull": true
           },
           {
@@ -79,6 +79,18 @@
             "columnName": "path",
             "affinity": "TEXT",
             "notNull": true
+          },
+          {
+            "fieldPath": "lastUse",
+            "columnName": "lastUse",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "type",
+            "columnName": "type",
+            "affinity": "TEXT",
+            "notNull": true
           }
         ],
         "primaryKey": {
@@ -94,7 +106,7 @@
     "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, 'c4b2fc1aa20ac555446cdde24166c0a0')"
+      "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '0bb12407277293c16c7545f55493e823')"
     ]
   }
 }

+ 107 - 0
app/schemas/com.example.modifier.data.AppDatabase/2.json

@@ -0,0 +1,107 @@
+{
+  "formatVersion": 1,
+  "database": {
+    "version": 2,
+    "identityHash": "66680df28f89cc498c93ddbc40b7526f",
+    "entities": [
+      {
+        "tableName": "BackupItem",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `createdAt` TEXT 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` TEXT NOT NULL DEFAULT 'createdAt')",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "createdAt",
+            "columnName": "createdAt",
+            "affinity": "TEXT",
+            "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": "TEXT",
+            "notNull": true,
+            "defaultValue": "'createdAt'"
+          }
+        ],
+        "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, '66680df28f89cc498c93ddbc40b7526f')"
+    ]
+  }
+}

+ 41 - 8
app/src/main/java/com/example/modifier/Global.kt

@@ -6,6 +6,8 @@ import android.content.Intent
 import android.os.Build
 import android.util.Log
 import androidx.core.content.ContextCompat
+import com.example.modifier.data.BackupItem
+import com.example.modifier.data.BackupItemDao
 import com.example.modifier.http.KtorClient
 import com.example.modifier.model.Backup
 import com.example.modifier.model.TelephonyConfig
@@ -29,6 +31,7 @@ import java.time.ZoneId
 import java.time.ZonedDateTime
 import java.time.format.DateTimeFormatter
 import java.util.Base64
+import java.util.Date
 import java.util.Locale
 
 object Global {
@@ -275,9 +278,12 @@ object Global {
         }
         try {
             val dataDir = ContextCompat.getDataDir(context)
-            Utils.copyAssetFolder(context.assets, "bin", File(dataDir, "bin").path)
-            Utils.copyAssetFolder(context.assets, "providerDB", File(dataDir, "providerDB").path)
-            //                        Utils.runAsRoot("su -c \"\"");
+            val binDir = File(dataDir, "bin")
+            val dbDir = File(dataDir, "providerDB")
+            if (!binDir.exists()) {
+                Utils.copyAssetFolder(context.assets, "bin", binDir.path)
+                Utils.copyAssetFolder(context.assets, "providerDB", dbDir.path)
+            }
             Log.i("Modifier", "arch: " + Build.SUPPORTED_ABIS.joinToString(", "))
 
             var arch: String? = null
@@ -293,7 +299,6 @@ object Global {
                 }
                 if (StringUtils.isNoneBlank(arch)) {
                     suspend(sms = true)
-
                     val binPath = File(dataDir, "bin/sqlite3.$arch").path
                     val providerDBPath = File(dataDir, "providerDB/mmssms.db").path
                     Log.i("Modifier", "sqlite3 binPath: $binPath")
@@ -522,7 +527,8 @@ object Global {
 
     @SuppressLint("SdCardPath")
     @JvmStatic
-    suspend fun backup():String {
+    suspend fun backup(backupItemDao: BackupItemDao, type: String, sendCount: Int): BackupItem {
+        clearConv()
         val context = Utils.getContext()
         val dest = File(
             ContextCompat.getExternalFilesDirs(context, "backup")[0],
@@ -569,12 +575,39 @@ object Global {
         cmds.addAll(packages.reversed().map { "pm unsuspend $it" })
         shellRun(*cmds.toTypedArray())
 
-        return dest.path
+        val backup = BackupItem(
+            createdAt = Date().time,
+            number = telephonyConfig.number,
+            code = telephonyConfig.areaCode,
+            country = telephonyConfig.country,
+            mcc = telephonyConfig.mcc,
+            mnc = telephonyConfig.mnc,
+            imei = telephonyConfig.imei,
+            imsi = telephonyConfig.imsi,
+            iccid = telephonyConfig.iccid,
+            path = dest.path,
+            sendCount = sendCount,
+            lastUse = Date().time,
+            type = type
+        )
+        backup.id = backupItemDao.insert(backup).toInt()
+        return backup
     }
 
     @JvmStatic
-    suspend fun restore(backup: Backup) {
-        save(backup.config, false)
+    suspend fun restore(backup: BackupItem) {
+        save(
+            TelephonyConfig(
+                number = backup.number,
+                mcc = backup.mcc,
+                mnc = backup.mnc,
+                country = backup.country,
+                areaCode = backup.code,
+                iccid = backup.iccid,
+                imei = backup.imei,
+                imsi = backup.imsi,
+            ), false
+        )
         val packages = mutableListOf(
             "com.google.android.apps.messaging",
             "com.google.android.gms",

+ 42 - 0
app/src/main/java/com/example/modifier/GlobalState.kt

@@ -0,0 +1,42 @@
+package com.example.modifier
+
+class GlobalState {
+    companion object {
+        var number: String = ""
+        var country: String = ""
+        var areaCode: String = ""
+        var mcc: String = ""
+        var mnc: String = ""
+        var imei: String = ""
+        var iccid: String = ""
+        var imsi: String = ""
+
+        var appCount: Int = 0
+        var appSuccess: Int = 0
+
+        var numberCount: Int = 0
+        var numberSuccess: Int = 0
+
+        var sessionCount: Int = 0
+        var sessionSuccess: Int = 0
+
+        fun increment(success: Boolean = false) {
+            appCount++
+            if (success) {
+                appSuccess++
+            }
+            numberCount++
+            if (success) {
+                numberSuccess++
+            }
+            sessionCount++
+            if (success) {
+                sessionSuccess++
+            }
+        }
+
+
+    }
+
+
+}

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

@@ -10,14 +10,11 @@ import android.view.ViewGroup
 import android.widget.Toast
 import androidx.recyclerview.widget.RecyclerView
 import com.example.modifier.Global
-import com.example.modifier.Global.save
-import com.example.modifier.Global.suspend
-import com.example.modifier.Global.unsuspend
 import com.example.modifier.Utils
 import com.example.modifier.adapter.BackupAdapter.BackupViewHolder
 import com.example.modifier.data.BackupItem
+import com.example.modifier.data.BackupItemDao
 import com.example.modifier.databinding.ItemBackupBinding
-import com.example.modifier.model.Backup
 import com.google.android.material.dialog.MaterialAlertDialogBuilder
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
@@ -25,9 +22,17 @@ import kotlinx.coroutines.launch
 import kotlinx.coroutines.withContext
 import java.io.File
 import java.text.SimpleDateFormat
+import java.util.Date
 
-class BackupAdapter(private val context: Context, private val backups: List<BackupItem>) :
+
+class BackupAdapter(
+    private val context: Context,
+    private val backups: MutableList<BackupItem>,
+    private val backupItemDao: BackupItemDao
+) :
     RecyclerView.Adapter<BackupViewHolder>() {
+
+
     override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BackupViewHolder {
         val binding = ItemBackupBinding.inflate(
             LayoutInflater.from(
@@ -40,8 +45,9 @@ class BackupAdapter(private val context: Context, private val backups: List<Back
     @SuppressLint("DefaultLocale")
     override fun onBindViewHolder(holder: BackupViewHolder, position: Int) {
         val backup = backups[position]
-        holder.binding.tvNumber.text = "+" + backup.code + " " + backup.number
-        holder.binding.tvTime.text = backup.createdAt.toString()
+        holder.binding.tvNumber.text = "+${backup.code} ${backup.number}"
+        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",
             backup.mcc, backup.mnc, backup.country
@@ -57,7 +63,7 @@ class BackupAdapter(private val context: Context, private val backups: List<Back
                     Utils.makeLoadingButton(context, holder.binding.btnRestore)
                     CoroutineScope(Dispatchers.IO).launch {
                         try {
-//                            Global.restore(backup)
+                            Global.restore(backup)
                         } catch (e: Exception) {
                             e.printStackTrace()
                         }
@@ -78,19 +84,26 @@ class BackupAdapter(private val context: Context, private val backups: List<Back
                 .setTitle("Delete backup")
                 .setMessage("Are you sure you want to delete this backup?")
                 .setPositiveButton("Yes") { dialog: DialogInterface?, which: Int ->
-                    val file = File(backup.path)
-                    if (file.exists()) {
-                        try {
-                            Utils.runAsRoot("rm -rf " + backup.path)
-//                            backups.remove(backup)
+                    CoroutineScope(Dispatchers.IO).launch {
+                        val file = File(backup.path)
+                        if (file.exists()) {
+                            try {
+                                File(backup.path).deleteRecursively()
+
+                            } catch (e: Exception) {
+                                e.printStackTrace()
+                                Toast.makeText(context, e.message, Toast.LENGTH_SHORT).show()
+                            }
+                        } else {
+                            Log.e("BackupAdapter", "File not found")
+                        }
+                        backups.remove(backup)
+                        withContext(Dispatchers.Main) {
                             notifyItemRemoved(position)
-                        } catch (e: Exception) {
-                            e.printStackTrace()
-                            Toast.makeText(context, e.message, Toast.LENGTH_SHORT).show()
                         }
-                    } else {
-                        Log.e("BackupAdapter", "File not found")
+                        backupItemDao.delete(backup)
                     }
+
                 }
                 .setNegativeButton("No", null)
                 .show()

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

@@ -22,7 +22,7 @@ import android.content.Context
  * App container for Dependency injection.
  */
 interface AppContainer {
-    val itemsRepository: BackupItemsRepository
+    val backupItemDao: BackupItemDao
 }
 
 /**
@@ -32,7 +32,7 @@ class AppDataContainer(private val context: Context) : AppContainer {
     /**
      * Implementation for [BackupItemsRepository]
      */
-    override val itemsRepository: BackupItemsRepository by lazy {
-        OfflineItemsRepository(AppDatabase.getDatabase(context).itemDao())
+    override val backupItemDao: BackupItemDao by lazy {
+        AppDatabase.getDatabase(context).itemDao()
     }
 }

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

@@ -10,7 +10,7 @@ import androidx.room.TypeConverters
 @Database(
     entities = [BackupItem::class],
     version = 1,
-    exportSchema = true
+    exportSchema = true,
 )
 @TypeConverters(Converters::class)
 abstract class AppDatabase : RoomDatabase() {

+ 5 - 35
app/src/main/java/com/example/modifier/data/BackupItem.kt

@@ -1,5 +1,6 @@
 package com.example.modifier.data
 
+import androidx.room.ColumnInfo
 import androidx.room.Entity
 import androidx.room.Ignore
 import androidx.room.PrimaryKey
@@ -10,7 +11,7 @@ import java.util.Date
 data class BackupItem(
     @PrimaryKey(autoGenerate = true)
     var id: Int? = null,
-    val createdAt: LocalDateTime,
+    val createdAt: Long,
     val number: String,
     val country: String,
     val code: String,
@@ -21,37 +22,6 @@ data class BackupItem(
     val iccid: String,
     val sendCount: Int,
     val path: String,
-    @Ignore
-    var deleting: Boolean = false,
-    @Ignore
-    var restoring: Boolean = false
-
-
-) {
-    constructor(
-        createdAt: LocalDateTime,
-        number: String,
-        country: String,
-        code: String,
-        mcc: String,
-        mnc: String,
-        imei: String,
-        imsi: String,
-        iccid: String,
-        sendCount: Int,
-        path: String
-    ) : this(
-        null,
-        createdAt,
-        number,
-        country,
-        code,
-        mcc,
-        mnc,
-        imei,
-        imsi,
-        iccid,
-        sendCount,
-        path
-    )
-}
+    val lastUse: Long,
+    val type: String
+)

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

@@ -22,6 +22,6 @@ interface BackupItemDao {
     @Query("SELECT * from backupitem WHERE id = :id")
     suspend fun getItem(id: Int): BackupItem?
 
-    @Query("SELECT * FROM backupitem")
+    @Query("SELECT * FROM backupitem ORDER BY id DESC")
     suspend fun getAll(): List<BackupItem>
 }

+ 0 - 30
app/src/main/java/com/example/modifier/data/BackupItemsRepository.kt

@@ -1,30 +0,0 @@
-package com.example.modifier.data
-
-import kotlinx.coroutines.flow.Flow
-
-interface BackupItemsRepository {
-    /**
-     * Retrieve all the items from the the given data source.
-     */
-    suspend fun getAll(): List<BackupItem>
-
-    /**
-     * Retrieve an item from the given data source that matches with the [id].
-     */
-    suspend fun getItem(id: Int): BackupItem?
-
-    /**
-     * Insert item in the data source
-     */
-    suspend fun insertItem(item: BackupItem): Long
-
-    /**
-     * Delete item from the data source
-     */
-    suspend fun deleteItem(item: BackupItem)
-
-    /**
-     * Update item in the data source
-     */
-    suspend fun updateItem(item: BackupItem)
-}

+ 0 - 15
app/src/main/java/com/example/modifier/data/OfflineItemsRepository.kt

@@ -1,15 +0,0 @@
-package com.example.modifier.data
-
-import kotlinx.coroutines.flow.Flow
-
-class OfflineItemsRepository(private val itemDao: BackupItemDao) : BackupItemsRepository {
-    override suspend fun getAll(): List<BackupItem> = itemDao.getAll()
-
-    override suspend fun getItem(id: Int): BackupItem? = itemDao.getItem(id)
-
-    override suspend fun insertItem(item: BackupItem) = itemDao.insert(item)
-
-    override suspend fun deleteItem(item: BackupItem) = itemDao.delete(item)
-
-    override suspend fun updateItem(item: BackupItem) = itemDao.update(item)
-}

+ 104 - 87
app/src/main/java/com/example/modifier/service/ModifierService.kt

@@ -13,7 +13,6 @@ import android.os.Handler
 import android.os.Looper
 import android.util.DisplayMetrics
 import android.util.Log
-import android.util.TypedValue
 import android.view.Gravity
 import android.view.LayoutInflater
 import android.view.MotionEvent
@@ -25,7 +24,6 @@ import android.view.accessibility.AccessibilityNodeInfo
 import android.widget.CompoundButton
 import android.widget.FrameLayout
 import androidx.core.content.ContextCompat
-import androidx.core.view.ViewCompat
 import androidx.datastore.core.DataStore
 import androidx.datastore.preferences.core.Preferences
 import androidx.datastore.preferences.preferencesDataStore
@@ -41,13 +39,11 @@ import com.example.modifier.CMD_RCS_SETTINGS_ACTIVITY
 import com.example.modifier.Global
 import com.example.modifier.Global.load
 import com.example.modifier.Global.resetAll
-import com.example.modifier.MyApplication
 import com.example.modifier.R
 import com.example.modifier.TraverseResult
 import com.example.modifier.Utils
 import com.example.modifier.data.AppDatabase
-import com.example.modifier.data.BackupItemsRepository
-import com.example.modifier.data.OfflineItemsRepository
+import com.example.modifier.data.BackupItemDao
 import com.example.modifier.databinding.FloatingWindowBinding
 import com.example.modifier.enums.RcsConfigureState
 import com.example.modifier.enums.RcsConnectionStatus
@@ -61,6 +57,7 @@ import com.example.modifier.model.SocketCallback
 import com.example.modifier.model.TaskAction
 import com.example.modifier.model.TaskExecutionResult
 import com.example.modifier.model.TelephonyConfig
+import com.example.modifier.ui.shellRun
 import com.google.android.material.color.DynamicColors
 import io.ktor.client.call.body
 import io.ktor.client.plugins.resources.get
@@ -76,7 +73,6 @@ import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.isActive
 import kotlinx.coroutines.launch
-import kotlinx.coroutines.runBlocking
 import kotlinx.coroutines.suspendCancellableCoroutine
 import kotlinx.coroutines.withContext
 import kotlinx.coroutines.withTimeoutOrNull
@@ -87,9 +83,6 @@ import org.apache.commons.lang3.RandomStringUtils
 import org.json.JSONException
 import org.json.JSONObject
 import java.time.LocalDateTime
-import java.time.ZoneId
-import java.time.ZoneOffset
-import java.time.ZonedDateTime
 import java.time.temporal.ChronoUnit
 import java.util.Optional
 import java.util.Timer
@@ -249,8 +242,8 @@ class ModifierService : AccessibilityService(), Emitter.Listener {
         }
     }
 
-    private val backupItemsRepository: BackupItemsRepository by lazy {
-        (application as MyApplication).container.itemsRepository
+    private val backupItemDao: BackupItemDao by lazy {
+        AppDatabase.getDatabase(this).itemDao()
     }
 
     fun connect() {
@@ -292,7 +285,6 @@ class ModifierService : AccessibilityService(), Emitter.Listener {
     }
 
 
-
     override fun onCreate() {
         super.onCreate()
         Log.i(TAG, "Starting ModifierService")
@@ -701,23 +693,8 @@ class ModifierService : AccessibilityService(), Emitter.Listener {
             }
         }
         binding.btnInspect.setOnClickListener {
-            CoroutineScope(Dispatchers.IO).launch {
-                val res = TraverseResult()
-                traverseNode(rootInActiveWindow, res)
-                if (res.rcsSwitch != null) {
-                    Log.i(TAG, "RCS switch isChecked: ${res.rcsSwitch!!.isChecked}")
-                    Log.i(TAG, "Clicking RCS switch")
-                    val rect = Rect()
-                    res.rcsSwitch!!.getBoundsInScreen(rect)
-                    Utils.runAsRoot(
-                        "input tap ${rect.centerX()} ${rect.centerY()}",
-                        "sleep 1",
-                        CMD_BACK, "sleep 1",
-                        CMD_RCS_SETTINGS_ACTIVITY, "sleep 1",
-                    )
-                    traverseNode(rootInActiveWindow, res)
-                    Log.i(TAG, "RCS switch isChecked: ${res.rcsSwitch!!.isChecked}")
-                }
+           CoroutineScope(Dispatchers.IO).launch {
+               toggleRcsSwitch(false)
             }
         }
         binding.btnCheck.setOnClickListener {
@@ -725,7 +702,11 @@ class ModifierService : AccessibilityService(), Emitter.Listener {
                 checkRcsAvailability()
             }
         }
-
+        binding.btnStoreNumbers.setOnClickListener {
+            CoroutineScope(Dispatchers.IO).launch {
+                storeNumbers()
+            }
+        }
         busy.observeForever {
             reportDeviceStatues()
         }
@@ -778,6 +759,51 @@ class ModifierService : AccessibilityService(), Emitter.Listener {
         return state
     }
 
+    private suspend fun toggleRcsSwitch(on: Boolean, retry: Int = 3): Boolean {
+        val res = TraverseResult()
+
+        val success = run {
+            shellRun(CMD_RCS_SETTINGS_ACTIVITY, "sleep 1")
+            repeat(retry) {
+                res.rcsSwitch = null
+                traverseNode(rootInActiveWindow, res)
+                if (res.rcsSwitch == null) {
+                    shellRun(CMD_BACK, "sleep 2", CMD_RCS_SETTINGS_ACTIVITY, "sleep 1")
+                } else {
+                    if (res.rcsSwitch!!.isChecked == on) {
+                        return true
+                    }
+                    val rect = Rect()
+                    res.rcsSwitch!!.getBoundsInScreen(rect)
+                    if (on) {
+                        shellRun(
+                            "input tap ${rect.centerX()} ${rect.centerY()}",
+                            "sleep 1", CMD_BACK, "sleep 1",
+                            CMD_RCS_SETTINGS_ACTIVITY, "sleep 1",
+                        )
+                    } else {
+                        shellRun(
+                            "input tap ${rect.centerX()} ${rect.centerY()}", "sleep 1",
+                        )
+                        rootInActiveWindow.findAccessibilityNodeInfosByViewId("android:id/button1")
+                            .firstOrNull()?.performAction(AccessibilityNodeInfo.ACTION_CLICK)
+                        shellRun(
+                            "sleep 1", CMD_BACK, "sleep 1",
+                            CMD_RCS_SETTINGS_ACTIVITY, "sleep 1",
+                        )
+                    }
+                    traverseNode(rootInActiveWindow, res)
+                    if (res.rcsSwitch!!.isChecked == on) {
+                        return true
+                    }
+                }
+            }
+            false
+        }
+        shellRun(CMD_BACK, "sleep 1")
+        return success
+    }
+
     private suspend fun requestNumber() {
         val color = ContextCompat.getColorStateList(binding.root.context, R.color.btn_color)
         binding.btnReq.backgroundTintList = color
@@ -791,8 +817,10 @@ class ModifierService : AccessibilityService(), Emitter.Listener {
         }
         requestNumberCount++
         requesting.postValue(true)
+
         var requestSuccess = false
         withTimeoutOrNull(1.hours) {
+            var needRest = false
             while (true) {
                 delay(200)
                 try {
@@ -835,10 +863,8 @@ class ModifierService : AccessibilityService(), Emitter.Listener {
                         )
                     )
 
-                    if (requestNumberCount > 5 && !getSharedPreferences(
-                            "settings",
-                            Context.MODE_PRIVATE
-                        )
+                    if (needRest &&
+                        !getSharedPreferences("settings", Context.MODE_PRIVATE)
                             .getBoolean("do_not_reset", false)
                     ) {
                         val resetSuccess = withTimeoutOrNull(5.minutes) {
@@ -859,37 +885,7 @@ class ModifierService : AccessibilityService(), Emitter.Listener {
                                     Log.e(TAG, "RCS not entered default on state, retrying...")
                                     continue
                                 }
-                                Utils.runAsRoot(
-                                    CMD_RCS_SETTINGS_ACTIVITY,
-                                    "sleep 1"
-                                )
-                                val res = TraverseResult()
-                                traverseNode(rootInActiveWindow, res)
-                                if (res.rcsSwitch == null) {
-                                    Log.e(TAG, "RCS switch not found, retrying...")
-                                    continue
-                                }
-                                val switchOn = run turnOnSwitch@{
-                                    repeat(2) {
-                                        if (res.rcsSwitch!!.isChecked) {
-                                            Log.i(TAG, "RCS switch turned on")
-                                            return@turnOnSwitch true
-                                        }
-                                        val rect = Rect()
-                                        res.rcsSwitch!!.getBoundsInScreen(rect)
-                                        Utils.runAsRoot(
-                                            "input tap ${rect.centerX()} ${rect.centerY()}",
-                                            "sleep 1", CMD_BACK, "sleep 1",
-                                            CMD_RCS_SETTINGS_ACTIVITY, "sleep 1",
-                                        )
-                                        traverseNode(rootInActiveWindow, res)
-                                        if (res.rcsSwitch!!.isChecked) {
-                                            Log.i(TAG, "RCS switch turned on")
-                                            return@turnOnSwitch true
-                                        }
-                                    }
-                                    false
-                                }
+                                val switchOn = toggleRcsSwitch(true)
                                 if (!switchOn) {
                                     Log.e(TAG, "RCS switch not turned on, retrying...")
                                     continue
@@ -907,7 +903,9 @@ class ModifierService : AccessibilityService(), Emitter.Listener {
                             }
                             true
                         } ?: false
-                        if (!resetSuccess) {
+                        if (resetSuccess) {
+                            needRest = false
+                        } else {
                             Log.e(TAG, "RCS reset failed, retrying...")
                             continue
                         }
@@ -938,6 +936,9 @@ class ModifierService : AccessibilityService(), Emitter.Listener {
                         ) != RcsConfigureState.WAITING_FOR_OTP
                     ) {
                         Log.e(TAG, "RCS not entered waiting for OTP state, retrying...")
+
+
+
                         continue
                     }
                     if (rcsNumber.expiryTime.isBefore(LocalDateTime.now())) {
@@ -1033,30 +1034,32 @@ class ModifierService : AccessibilityService(), Emitter.Listener {
         }
     }
 
+    private suspend fun checkRcsConnectivity(): Boolean = run checkRcsConnection@{
+        repeat(3) {
+            Log.i(TAG, "Checking RCS status...")
+            shellRun(
+                CMD_CONVERSATION_LIST_ACTIVITY,
+                CMD_RCS_SETTINGS_ACTIVITY,
+                "sleep 1",
+            )
+            val res = TraverseResult()
+            traverseNode(rootInActiveWindow, res)
+            if (res.rcsConnectionStatus == RcsConnectionStatus.CONNECTED) {
+                Log.i(TAG, "RCS is connected")
+                shellRun(CMD_BACK)
+                return@checkRcsConnection true
+            } else {
+                Log.i(TAG, "RCS not connected, retrying...")
+            }
+            shellRun(CMD_BACK, "sleep ${it * 2}")
+        }
+        false
+    }
+
     suspend fun checkRcsAvailability(): Boolean {
         checkingConnection.postValue(true)
         val availability = run checkAvailability@{
-            val rcsConnected = run checkRcsConnection@{
-                repeat(3) {
-                    Log.i(TAG, "Checking RCS status...")
-                    Utils.runAsRoot(
-                        CMD_CONVERSATION_LIST_ACTIVITY,
-                        CMD_RCS_SETTINGS_ACTIVITY,
-                        "sleep 1",
-                    )
-                    val res = TraverseResult()
-                    traverseNode(rootInActiveWindow, res)
-                    if (res.rcsConnectionStatus == RcsConnectionStatus.CONNECTED) {
-                        Log.i(TAG, "RCS is connected")
-                        Utils.runAsRoot(CMD_BACK)
-                        return@checkRcsConnection true
-                    } else {
-                        Log.i(TAG, "RCS not connected, retrying...")
-                    }
-                    Utils.runAsRoot(CMD_BACK, "sleep ${it * 2}")
-                }
-                false
-            }
+            val rcsConnected = checkRcsConnectivity()
             if (!rcsConnected) {
                 return@checkAvailability false
             }
@@ -1110,4 +1113,18 @@ class ModifierService : AccessibilityService(), Emitter.Listener {
         checkingConnection.postValue(false)
         return availability
     }
+
+    private suspend fun storeNumbers() {
+        withContext(Dispatchers.Main) {
+            binding.btnStoreNumbers.isEnabled = false
+        }
+        repeat(20) {
+            requestNumber()
+            delay(3000)
+            Global.backup(backupItemDao, "auto", 0)
+        }
+        withContext(Dispatchers.Main) {
+            binding.btnStoreNumbers.isEnabled = true
+        }
+    }
 }

+ 1 - 1
app/src/main/java/com/example/modifier/ui/AppViewModelProvider.kt

@@ -34,7 +34,7 @@ object AppViewModelProvider {
         initializer {
             BackupViewModel(
                 this.createSavedStateHandle(),
-                myApplication().container.itemsRepository
+                myApplication().container.backupItemDao
             )
         }
         // Initializer for ItemEntryViewModel

+ 50 - 14
app/src/main/java/com/example/modifier/ui/backup/BackupFragment.kt

@@ -5,22 +5,35 @@ import android.os.Bundle
 import android.view.LayoutInflater
 import android.view.View
 import android.view.ViewGroup
+import androidx.appcompat.content.res.AppCompatResources
 import androidx.fragment.app.Fragment
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.lifecycleScope
 import androidx.lifecycle.repeatOnLifecycle
 import androidx.recyclerview.widget.LinearLayoutManager
+import com.example.modifier.Global
 import com.example.modifier.MyApplication
+import com.example.modifier.R
+import com.example.modifier.Utils
 import com.example.modifier.adapter.BackupAdapter
+import com.example.modifier.data.AppContainer
+import com.example.modifier.data.AppDatabase
 import com.example.modifier.data.BackupItem
-import com.example.modifier.data.BackupItemsRepository
+import com.example.modifier.data.BackupItemDao
 import com.example.modifier.databinding.FragmentBackupBinding
+import com.example.modifier.service.ModifierService
+import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+import java.time.LocalDateTime
+import java.util.Date
 
 class BackupFragment : Fragment() {
     private lateinit var binding: FragmentBackupBinding
-    private val backupItemsRepository: BackupItemsRepository by lazy {
-        (requireActivity().application as MyApplication).container.itemsRepository
+    private val list = mutableListOf<BackupItem>()
+    private lateinit var adapter: BackupAdapter
+    private val backupItemDao: BackupItemDao by lazy {
+        AppDatabase.getDatabase(requireContext()).itemDao()
     }
 
     override fun onCreateView(
@@ -31,30 +44,53 @@ class BackupFragment : Fragment() {
             return binding.root
         }
         binding = FragmentBackupBinding.inflate(inflater, container, false)
-
-        val list = mutableListOf<BackupItem>()
-        val adapter = BackupAdapter(requireContext(), list)
+        adapter = BackupAdapter(requireContext(), list, backupItemDao)
         binding.rvBackup.adapter = adapter
         binding.rvBackup.layoutManager = LinearLayoutManager(requireContext())
+        binding.fabBackup.setOnClickListener {
+            Utils.makeLoadingButton(requireContext(), binding.fabBackup)
+            lifecycleScope.launch {
+                withContext(Dispatchers.IO) {
+                    val backup = Global.backup(backupItemDao, "manual", 0)
+                    list.add(0, backup)
+                }
+
+                adapter.notifyItemInserted(0)
+                binding.fabBackup.isEnabled = true
+                binding.fabBackup.icon =
+                    AppCompatResources.getDrawable(requireContext(), R.drawable.ic_add)
+            }
+        }
+        binding.refresh.setOnRefreshListener {
+            lifecycleScope.launch {
+                withContext(Dispatchers.IO) {
+                    refresh()
+                }
+            }
+        }
 
         binding.lifecycleOwner = this
         viewLifecycleOwner.lifecycleScope.launch {
             // repeatOnLifecycle launches the block in a new coroutine every time the
             // lifecycle is in the STARTED state (or above) and cancels it when it's STOPPED.
             viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
-                // Trigger the flow and start listening for values.
-                // This happens when lifecycle is STARTED and stops
-                // collecting when the lifecycle is STOPPED
-                val all = backupItemsRepository.getAll()
-                list.clear()
-                list.addAll(all)
-                adapter.notifyDataSetChanged()
+                withContext(Dispatchers.IO) {
+                    refresh()
+                }
             }
         }
 
         return binding.root
     }
 
-
+    private suspend fun refresh() {
+        val all = backupItemDao.getAll()
+        list.clear()
+        list.addAll(all)
+        withContext(Dispatchers.Main) {
+            adapter.notifyDataSetChanged()
+        }
+        binding.refresh.isRefreshing = false
+    }
 
 }

+ 5 - 29
app/src/main/java/com/example/modifier/ui/backup/BackupViewModel.kt

@@ -5,17 +5,18 @@ import androidx.lifecycle.ViewModel
 import androidx.lifecycle.viewModelScope
 import com.example.modifier.Global
 import com.example.modifier.data.BackupItem
-import com.example.modifier.data.BackupItemsRepository
+import com.example.modifier.data.BackupItemDao
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.update
 import kotlinx.coroutines.launch
 import java.time.LocalDateTime
+import java.util.Date
 
 class BackupViewModel(
     savedStateHandle: SavedStateHandle,
-    private val itemsRepository: BackupItemsRepository
+    private val itemDao: BackupItemDao
 ) : ViewModel() {
 
     val _uiState = MutableStateFlow(BackupUiState())
@@ -23,38 +24,13 @@ class BackupViewModel(
 
     fun addBackup() {
         viewModelScope.launch {
-            _uiState.update {
-                it.copy(backingUp = true)
-            }
-            val path = Global.backup()
-            val item = BackupItem(
-                createdAt = LocalDateTime.now(),
-                number = Global.telephonyConfig.number,
-                country = Global.telephonyConfig.country,
-                code = Global.telephonyConfig.areaCode,
-                mcc = Global.telephonyConfig.mcc,
-                mnc = Global.telephonyConfig.mnc,
-                imei = Global.telephonyConfig.imei,
-                imsi = Global.telephonyConfig.imsi,
-                iccid = Global.telephonyConfig.iccid,
-                sendCount = 0,
-                path = path
-            )
-            item.id = itemsRepository.insertItem(item).toInt()
-            _uiState.update {
-                it.itemList.add(item)
-                it.copy(backingUp = false)
-            }
+
         }
     }
 
     fun loadBackups() {
         viewModelScope.launch {
-            val all = itemsRepository.getAll()
-            _uiState.update {
-                it.itemList.apply { clear(); addAll(all) }
-                it.copy(itemList = it.itemList)
-            }
+
         }
     }
 }

+ 10 - 0
app/src/main/res/drawable/ic_archive.xml

@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="960"
+    android:viewportHeight="960"
+    android:tint="?attr/colorControlNormal">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M480,720L640,560L584,504L520,568L520,400L440,400L440,568L376,504L320,560L480,720ZM200,320L200,760Q200,760 200,760Q200,760 200,760L760,760Q760,760 760,760Q760,760 760,760L760,320L200,320ZM200,840Q167,840 143.5,816.5Q120,793 120,760L120,261Q120,247 124.5,234Q129,221 138,210L188,149Q199,135 215.5,127.5Q232,120 250,120L710,120Q728,120 744.5,127.5Q761,135 772,149L822,210Q831,221 835.5,234Q840,247 840,261L840,760Q840,793 816.5,816.5Q793,840 760,840L200,840ZM216,240L744,240L710,200Q710,200 710,200Q710,200 710,200L250,200Q250,200 250,200Q250,200 250,200L216,240ZM480,540L480,540L480,540Q480,540 480,540Q480,540 480,540L480,540Q480,540 480,540Q480,540 480,540Z"/>
+</vector>

+ 11 - 0
app/src/main/res/layout/floating_window.xml

@@ -114,6 +114,17 @@
                         app:icon="@drawable/ic_ecg_heart"
                         app:iconGravity="textStart"
                         app:iconPadding="0dp" />
+
+                    <com.google.android.material.button.MaterialButton
+                        android:id="@+id/btn_store_numbers"
+                        style="?attr/materialIconButtonFilledTonalStyle"
+                        android:layout_width="50dp"
+                        android:layout_height="34dp"
+                        android:layout_marginLeft="8dp"
+                        android:padding="0dp"
+                        app:icon="@drawable/ic_archive"
+                        app:iconGravity="textStart"
+                        app:iconPadding="0dp" />
                 </LinearLayout>
             </androidx.constraintlayout.widget.ConstraintLayout>
         </com.google.android.material.card.MaterialCardView>

+ 0 - 1
app/src/main/res/layout/fragment_backup.xml

@@ -31,7 +31,6 @@
             android:layout_height="wrap_content"
             android:layout_gravity="bottom|end"
             android:layout_margin="16dp"
-            android:text="Backup"
             app:icon="@drawable/ic_add"
             app:layout_constraintBottom_toBottomOf="parent"
             app:layout_constraintEnd_toEndOf="parent" />