package com.example.modifier.repo import android.annotation.SuppressLint import android.content.Context import android.util.Log import androidx.core.content.ContextCompat import com.example.modifier.baseTag 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_GSF import com.example.modifier.constants.PACKAGE_MESSAGING import com.example.modifier.data.BackupItem import com.example.modifier.data.BackupItemDao import com.example.modifier.enums.RcsConfigureState import com.example.modifier.enums.ReqState import com.example.modifier.extension.clear import com.example.modifier.extension.disable import com.example.modifier.extension.enable import com.example.modifier.extension.kill import com.example.modifier.http.api.RcsNumberApi import com.example.modifier.model.SpoofedSimInfo import com.example.modifier.service.ModifierService import com.example.modifier.utils.clearConv import com.example.modifier.utils.getContext import com.example.modifier.utils.shellRun import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import java.io.File import java.util.Date import java.util.Optional import kotlin.coroutines.coroutineContext import kotlin.time.Duration.Companion.minutes import kotlin.time.Duration.Companion.seconds class BackupRepository( private val context: Context, private val dao: BackupItemDao, ) { companion object { private const val TAG = "$baseTag/BackupRepository" } suspend fun backup(type: String, sendCount: Int, stock: Int = 0): BackupItem { AppStateRepo.instance.updateRuntimeFlags(reqState = ReqState.BACKUP) val spoofedSimInfoRepo = SpoofedSimInfoRepo.instance clearConv() delay(3000) // ModifierService.instance!!.toggleRcsSwitch(false) val context = getContext() val dest = File( ContextCompat.getExternalFilesDirs(context, "backup")[0], System.currentTimeMillis().toString() ) dest.mkdirs() val packages = mutableListOf( PACKAGE_MESSAGING, PACKAGE_GMS, PACKAGE_GSF ) packages.forEach { File(dest, it).mkdirs() } val cmds = mutableListOf() for (pkg in packages) { if (!shellRun("ls /data/data/$pkg").second .contains("No such file or directory") ) { cmds.add("tar zcpf $dest/$pkg/data.tar.gz -C /data/data $pkg ") } if (!shellRun("ls /data/user_de/0/$pkg").second .contains("No such file or directory") ) { cmds.add("tar zcpf $dest/$pkg/data_de.tar.gz -C /data/user_de/0 $pkg ") } if (!shellRun("ls /sdcard/Android/data/$pkg").second .contains("No such file or directory") ) { cmds.add("tar zcpf $dest/$pkg/data_ext.tar.gz -C /sdcard/Android/data $pkg ") } } shellRun(*cmds.toTypedArray()) val spoofedSimInfo = spoofedSimInfoRepo.spoofedSimInfo.value val backup = BackupItem( createdAt = Date().time, numberId = spoofedSimInfo.numberId, number = spoofedSimInfo.number, code = spoofedSimInfo.areaCode, country = spoofedSimInfo.country, mcc = spoofedSimInfo.mcc, mnc = spoofedSimInfo.mnc, imei = spoofedSimInfo.imei, imsi = spoofedSimInfo.imsi, iccid = spoofedSimInfo.iccid, path = dest.path, sendCount = sendCount, lastUse = Date().time, type = type, stock = stock, carrierId = Optional.ofNullable(spoofedSimInfo.carrierId).orElse("1"), carrierName = Optional.ofNullable(spoofedSimInfo.carrierName).orElse("T-Mobile"), retry = 0 ) dao.findBackupForNumber(spoofedSimInfo.country, spoofedSimInfo.number)?.let { File(it.path).deleteRecursively() backup.sendCount += it.sendCount backup.stock = it.stock dao.delete(it) } backup.id = dao.insert(backup).toInt() return backup } @SuppressLint("SdCardPath") suspend fun restore(backup: BackupItem): Boolean { val spoofedSimInfoRepo = SpoofedSimInfoRepo.instance val simInfo = SpoofedSimInfo( numberId = backup.numberId, number = backup.number, mcc = backup.mcc, mnc = backup.mnc, country = backup.country, areaCode = backup.code, iccid = backup.iccid, imei = backup.imei, imsi = backup.imsi, available = false, carrierId = backup.carrierId, carrierName = backup.carrierName ) spoofedSimInfoRepo.updateSpoofedSimInfo(spoofedSimInfo = simInfo, suspend = false) val packages = mutableListOf( PACKAGE_MESSAGING, PACKAGE_GMS, PACKAGE_GSF ) shellRun(PACKAGE_MESSAGING.clear(), "sleep 2", PACKAGE_MESSAGING.disable()) for (pkg in packages) { for (item in listOf("data.tar.gz", "data_de.tar.gz", "data_ext.tar.gz")) { val file = File("${backup.path}/$pkg/$item") if (!file.exists()) { continue } val dest = when (item) { "data.tar.gz" -> "/data/data" "data_de.tar.gz" -> "/data/user_de/0" "data_ext.tar.gz" -> "/sdcard/Android/data" else -> "" } shellRun( "find $dest/$pkg -type f -delete", "tar zxf ${file.path} -C $dest --exclude \"**/AndroidPlatformServices.apk\"" ) } } val taskRunner = ModifierService.instance?.taskRunner shellRun( // "sed -i 's///g' /data/data/com.google.android.apps.messaging/shared_prefs/bugle.xml", PACKAGE_GSF.kill(), "sleep 1", PACKAGE_GMS.kill(), "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 1", CMD_MESSAGING_APP, "sleep 5", CMD_RCS_SETTINGS_ACTIVITY, "sleep 5", PACKAGE_MESSAGING.kill(), "sleep 1", CMD_MESSAGING_APP, "sleep 5", ) var otp = false val success = run isSuccess@{ if (backup.stock != 1) { return@isSuccess true } if (taskRunner == null) return@isSuccess false if (!taskRunner.screenController.toggleRcsSwitch(true)) { shellRun( PACKAGE_GMS.kill(), PACKAGE_MESSAGING.kill(), "sleep 5", CMD_MESSAGING_APP ) delay(5000) if (!taskRunner.screenController.toggleRcsSwitch(true)) { return@isSuccess false } } val job = CoroutineScope(coroutineContext).launch { taskRunner.gmsgStateRepository.rcsConfigureState.collect { if (it == RcsConfigureState.RETRY) { shellRun( PACKAGE_GMS.kill(), "sleep 5", PACKAGE_MESSAGING.kill(), CMD_MESSAGING_APP ) } } } val state = taskRunner.gmsgStateRepository.waitForRcsState( arrayOf(RcsConfigureState.CONFIGURED, RcsConfigureState.WAITING_FOR_OTP), 5.minutes ) job.cancel() if (state == RcsConfigureState.CONFIGURED) return@isSuccess true if (state == RcsConfigureState.WAITING_FOR_OTP) otp = true false } if (success) { Log.i(TAG, "restore: success") spoofedSimInfoRepo.updateSpoofedSimInfo( spoofedSimInfo = simInfo.copy(available = true), suspend = false ) if (backup.stock == 1) { backup.stock = 2 dao.update(backup) RcsNumberApi.updateStockFlag(backup.numberId, 2) } return true } else { Log.i(TAG, "restore: failed") if (backup.stock == 1) { backup.retry++ if (backup.retry >= 3 || otp) { RcsNumberApi.updateStockFlag(backup.numberId, 3) if (otp) { dao.delete(backup) } else { backup.stock = 3 dao.update(backup) } } else { dao.update(backup) } } } return false } suspend fun delete(backupItem: BackupItem) { dao.delete(backupItem) } suspend fun findBackupForRestore(number: String): BackupItem? { return dao.findBackupForRestore(number) } suspend fun cleanBackup(start: Long, end: Long) { dao.findBackupsBetween(start, end).forEach { runCatching { File(it.path).deleteRecursively() } dao.delete(it) } } }