package com.example.modifier.service import android.content.Context import android.os.Build import android.os.Environment import android.util.Log import android.view.accessibility.AccessibilityNodeInfo import androidx.core.content.ContextCompat import coil3.ImageLoader import coil3.request.crossfade import com.example.modifier.baseTag import com.example.modifier.TraverseResult import com.example.modifier.Utils import com.example.modifier.constants.CMD_BACK import com.example.modifier.constants.CMD_CONVERSATION_LIST_ACTIVITY 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.enums.RcsConfigureState import com.example.modifier.enums.RcsConnectionStatus import com.example.modifier.enums.ReqState import com.example.modifier.exception.RequestNumberException import com.example.modifier.exception.RequestNumberException.Companion.ErrorCode import com.example.modifier.extension.kill import com.example.modifier.http.api.DeviceApi import com.example.modifier.http.api.RcsNumberApi import com.example.modifier.http.api.SysConfigApi import com.example.modifier.http.downloadImage import com.example.modifier.http.ktorClient import com.example.modifier.http.response.SysConfigResponse import com.example.modifier.model.InstallApkAction import com.example.modifier.model.RunScriptAction import com.example.modifier.model.SpoofedSimInfo 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.AppPrefsRepo import com.example.modifier.repo.AppStateRepo import com.example.modifier.repo.BackupRepository import com.example.modifier.repo.GmsgStateRepo import com.example.modifier.repo.SpoofedSimInfoRepo import com.example.modifier.utils.clearConv import com.example.modifier.utils.genICCID import com.example.modifier.utils.genIMEI import com.example.modifier.utils.genIMSI import com.example.modifier.utils.genMacAddress import com.example.modifier.utils.genSerialNo import com.example.modifier.utils.getContext import com.example.modifier.utils.isOldVersion import com.example.modifier.utils.resetAll import com.example.modifier.utils.shellRun import com.example.modifier.utils.smsIntent import com.example.modifier.utils.injectOTP import io.ktor.client.HttpClient import io.ktor.client.call.body import io.ktor.client.engine.okhttp.OkHttp import io.ktor.client.plugins.HttpResponseValidator import io.ktor.client.plugins.ServerResponseException import io.ktor.client.plugins.resources.get import io.ktor.client.request.prepareGet import io.ktor.utils.io.ByteReadChannel import io.ktor.utils.io.core.isEmpty import io.ktor.utils.io.core.readBytes import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.delay import kotlinx.coroutines.isActive import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import kotlinx.coroutines.withTimeout import kotlinx.coroutines.withTimeoutOrNull import org.apache.commons.lang3.RandomStringUtils import java.io.File import java.time.LocalDateTime import java.time.temporal.ChronoUnit import kotlin.coroutines.coroutineContext import kotlin.time.Duration.Companion.hours import kotlin.time.Duration.Companion.minutes import kotlin.time.Duration.Companion.seconds class TaskRunner( private val context: Context, private val screenInspector: ScreenInspector, val screenController: ScreenController, private val appStateRepo: AppStateRepo, private val appPrefsRepo: AppPrefsRepo, private val spoofedSimInfoRepo: SpoofedSimInfoRepo, val gmsgStateRepo: GmsgStateRepo, private val backupRepository: BackupRepository ) { companion object { private const val TAG = "$baseTag/TaskRunner" } private var lastSend = 0L private var currentTaskId = 0 private var requestMode = 1 private var storeNumberJob: Job? = null private val imageLoader = ImageLoader.Builder(context) .crossfade(true) .build() suspend fun send( to: String, body: String?, img: String?, taskConfig: TaskConfig ): Boolean { Log.i(TAG, "Sending SMS to $to: $body") var imgFile: File? = null if (img?.isNotBlank() == true) { imgFile = downloadImage(img) } context.startActivity(smsIntent(to, body, imgFile)) try { Log.i(TAG, "Command executed successfully, waiting for app to open...") delay(1000) var success = false var traverseResult = TraverseResult() withTimeoutOrNull(taskConfig.rcsWait) { repeat(999) { traverseResult = TraverseResult() screenInspector.traverseNode(traverseResult) if (traverseResult.isRcsCapable && traverseResult.sendBtn != null) { return@withTimeoutOrNull } delay(200) } } if (traverseResult.isRcsCapable && traverseResult.sendBtn != null && taskConfig.e2ee > 0) { withTimeoutOrNull(taskConfig.e2eeTimeout) { repeat(999) { traverseResult = TraverseResult() screenInspector.traverseNode(traverseResult) if (traverseResult.encrypted) { return@withTimeoutOrNull } delay(200) } } } if (traverseResult.isRcsCapable) { if (traverseResult.sendBtn == null) { Log.i(TAG, "Send button not found") } else if (taskConfig.e2ee == 2 && !traverseResult.encrypted) { Log.i(TAG, "E2EE not detected") } else { Log.i(TAG, "Clicking send button") val dt = System.currentTimeMillis() - lastSend if (taskConfig.rcsInterval > 0 && dt < taskConfig.rcsInterval) { Log.i(TAG, "Waiting for RCS interval") delay(taskConfig.rcsInterval - dt) } traverseResult.sendBtn!!.performAction(AccessibilityNodeInfo.ACTION_CLICK) lastSend = System.currentTimeMillis() success = true } } else { Log.i(TAG, "RCS not detected") } appStateRepo.incrementExecutedNum( success = success, num = if (img?.isNotBlank() == true && body?.isNotBlank() == true) 2 else 1 ) Log.i( TAG, "executedNum: ${appStateRepo.appState.value.executedNum}, successNum: ${appStateRepo.appState.value.successNum}" ) delay(2000) return success } catch (e: Exception) { e.printStackTrace() } return false } suspend fun runTask( taskAction: TaskAction, onSuccess: (List) -> Unit, onError: (Exception) -> Unit ) { if (appStateRepo.appState.value.busy) { onError(Exception("device busy")) return } try { val taskConfig = taskAction.data.config currentTaskId = taskAction.data.taskId requestMode = if (taskAction.data.config.useBackup) 2 else 1 if (!spoofedSimInfoRepo.spoofedSimInfo.value.available || (taskAction.data.config.checkConnection && appStateRepo.appState.value.executedNum == 0) ) { appStateRepo.updateRuntimeFlags(checkingConnection = true) if (!spoofedSimInfoRepo.spoofedSimInfo.value.available || !checkRcsA10y()) { onError(Exception("RCS not available")) requestNumberOnTask() appStateRepo.updateRuntimeFlags(checkingConnection = false) return } appStateRepo.updateRuntimeFlags(checkingConnection = false) } appStateRepo.updateRuntimeFlags(running = true, checkingConnection = false) val results = mutableListOf() for (i in 0 until taskAction.data.tasks.size) { val taskItem = taskAction.data.tasks[i] val result = TaskExecutionResult( id = taskItem.id, numberId = spoofedSimInfoRepo.spoofedSimInfo.value.numberId ) results.add(result) try { gmsgStateRepo.addToWatchList(result) result.sent = send(taskItem.number, taskItem.message, taskItem.img, taskConfig) } catch (e: Exception) { Log.e(TAG, "runTaskError: ${e.message}", e) } } shellRun(CMD_BACK) delay(5000) onSuccess(results) if (taskConfig.requestNumberInterval in 1..appStateRepo.appState.value.successNum) { delay(3000) requestNumberOnTask() } else if (taskConfig.cleanCount in 1..appStateRepo.appState.value.executedNum && !appPrefsRepo.appPrefs.value.preventClean) { delay(3000) clearConv(); shellRun(CMD_MESSAGING_APP) delay(3000) appStateRepo.resetExecutedNum() } else { delay(2000) } appStateRepo.updateRuntimeFlags(running = false) if (taskAction.data.config.checkConnection && results.none { it.sent }) { appStateRepo.updateRuntimeFlags(checkingConnection = true) if (!checkRcsA10y()) { onError(Exception("RCS not available")) requestNumberOnTask() } appStateRepo.updateRuntimeFlags(checkingConnection = false) } } catch (e: Exception) { Log.e(TAG, "runTaskError: ${e.message}", e) onError(e) appStateRepo.updateRuntimeFlags(running = false) } } suspend fun reset() { val context = getContext() val dir = ContextCompat.getExternalCacheDirs(context)[0] // val pifPath = File(dir, "pif1.json").path // Utils.copyAsset(context.assets, "pif1.json", pifPath) // shellRun("cp $pifPath /data/adb/pif.json") if (isOldVersion(context)) { withTimeout(1.hours) { while (true) { delay(100) appStateRepo.updateRuntimeFlags(reqState = ReqState.RESET) gmsgStateRepo.updateRcsState(RcsConfigureState.NOT_CONFIGURED) spoofedSimInfoRepo.mock() resetAll() var switchAppear = gmsgStateRepo.waitForRcsState( arrayOf(RcsConfigureState.WAITING_FOR_TOS), 2.minutes )?.let { it == RcsConfigureState.WAITING_FOR_TOS } if (switchAppear != true) { shellRun( PACKAGE_GMS.kill(), PACKAGE_MESSAGING.kill(), "sleep 1", CMD_MESSAGING_APP ) switchAppear = gmsgStateRepo.waitForRcsState( arrayOf(RcsConfigureState.WAITING_FOR_TOS), 3.minutes )?.let { it == RcsConfigureState.WAITING_FOR_TOS } if (switchAppear != true) { shellRun( PACKAGE_GMS.kill(), PACKAGE_MESSAGING.kill(), "sleep 1", CMD_MESSAGING_APP ) switchAppear = gmsgStateRepo.waitForRcsState( arrayOf(RcsConfigureState.WAITING_FOR_TOS), 2.minutes )?.let { it == RcsConfigureState.WAITING_FOR_TOS } if (switchAppear != true) { Log.e(TAG, "RCS not entered default on state, retrying...") continue } } } if (!screenController.toggleRcsSwitch(false)) { Log.e(TAG, "RCS switch not turned off, retrying...") continue } if (!screenController.toggleRcsSwitch(true)) { Log.e(TAG, "RCS switch not turned on, retrying...") continue } var resetSuccess = gmsgStateRepo.waitForRcsState( arrayOf( RcsConfigureState.READY ), 30.seconds ).let { it == RcsConfigureState.READY } if (!resetSuccess) { screenController.toggleRcsSwitch(false) delay(1000) screenController.toggleRcsSwitch(true) resetSuccess = gmsgStateRepo.waitForRcsState( arrayOf( RcsConfigureState.READY ), 1.minutes ).let { it == RcsConfigureState.READY } } Log.i(TAG, "waitForRcsState: $resetSuccess") appStateRepo.resetRequestedNum() if (resetSuccess) { delay(3000) break } } } } else { withTimeout(1.hours) { while (true) { delay(100) appStateRepo.updateRuntimeFlags(reqState = ReqState.RESET) gmsgStateRepo.updateRcsState(RcsConfigureState.NOT_CONFIGURED) spoofedSimInfoRepo.mock() resetAll() var switchAppear = gmsgStateRepo.waitForRcsState( arrayOf(RcsConfigureState.WAITING_FOR_DEFAULT_ON), 1.minutes )?.let { it == RcsConfigureState.WAITING_FOR_DEFAULT_ON } if (switchAppear != true) { shellRun( PACKAGE_GMS.kill(), PACKAGE_MESSAGING.kill(), "sleep 1", CMD_MESSAGING_APP ) switchAppear = gmsgStateRepo.waitForRcsState( arrayOf(RcsConfigureState.WAITING_FOR_DEFAULT_ON), 2.minutes )?.let { it == RcsConfigureState.WAITING_FOR_DEFAULT_ON } if (switchAppear != true) { Log.e(TAG, "RCS not entered default on state, retrying...") continue } } val switchOn = screenController.toggleRcsSwitch(true) if (!switchOn) { Log.e(TAG, "RCS switch not turned on, retrying...") continue } var resetSuccess = gmsgStateRepo.waitForRcsState( arrayOf( RcsConfigureState.READY ), 30.seconds ).let { it == RcsConfigureState.READY } if (!resetSuccess) { screenController.toggleRcsSwitch(false) delay(1000) screenController.toggleRcsSwitch(true) resetSuccess = gmsgStateRepo.waitForRcsState( arrayOf( RcsConfigureState.READY ), 1.minutes ).let { it == RcsConfigureState.READY } } Log.i(TAG, "waitForRcsState: $resetSuccess") appStateRepo.resetRequestedNum() if (resetSuccess) { delay(1000) break } } } } appStateRepo.updateRuntimeFlags(reqState = ReqState.NONE) // shellRun("rm /data/adb/pif.json") } private suspend fun requestNumberAtomic(store: Boolean? = false) { appStateRepo.updateRuntimeFlags(reqState = ReqState.REQUEST) gmsgStateRepo.updateRcsState(RcsConfigureState.NOT_CONFIGURED) val device = DeviceApi.getDevice(appPrefsRepo.appPrefs.value.id) val rcsNumber = RcsNumberApi.getRcsNumber( deviceId = appPrefsRepo.appPrefs.value.id, taskId = currentTaskId, pinCountry = device.pinCountry, store = store ) Log.i(TAG, "requestNumber response: $rcsNumber") appStateRepo.updateRuntimeFlags(reqState = ReqState.OTP_1) spoofedSimInfoRepo.updateSpoofedSimInfo( SpoofedSimInfo( numberId = rcsNumber.id, number = rcsNumber.number, mcc = rcsNumber.mcc, mnc = rcsNumber.mnc, iccid = genICCID(rcsNumber.mnc, rcsNumber.areaCode), imsi = genIMSI(rcsNumber.mcc + rcsNumber.mnc), imei = spoofedSimInfoRepo.spoofedSimInfo.value.imei, country = rcsNumber.country, areaCode = rcsNumber.areaCode, available = false, carrierId = rcsNumber.carrierId, carrierName = rcsNumber.carrierName, serialNo = spoofedSimInfoRepo.spoofedSimInfo.value.serialNo, mac = genMacAddress(), bssid = genMacAddress() ) ) shellRun(CMD_MESSAGING_APP) if (rcsNumber.expiryTime.isBefore(LocalDateTime.now())) { throw RequestNumberException(ErrorCode.CODE_NUMBER_EXPIRED) } val tosAgreeJob = CoroutineScope(coroutineContext).launch { run tos@{ repeat(60 * 1000 / 200) { delay(500) val traverseResult = TraverseResult() screenInspector.traverseNode(traverseResult) if (traverseResult.tosAgreeBtn != null) { traverseResult.tosAgreeBtn!!.performAction(AccessibilityNodeInfo.ACTION_CLICK) return@tos } } } } var sendOtpTimeout = ChronoUnit.SECONDS.between(LocalDateTime.now(), rcsNumber.expiryTime).seconds if (sendOtpTimeout < 5.seconds) { throw RequestNumberException(ErrorCode.CODE_TIMEOUT_TOO_SHORT) } if (sendOtpTimeout > 2.minutes) { sendOtpTimeout = 2.minutes } if (gmsgStateRepo.waitForRcsState( arrayOf(RcsConfigureState.WAITING_FOR_OTP), sendOtpTimeout ) != RcsConfigureState.WAITING_FOR_OTP ) { tosAgreeJob.cancel() if (!screenController.toggleRcsSwitch(true)) { throw RequestNumberException(ErrorCode.CODE_RCS_TOGGLED_OFF) } if (RcsConfigureState.REPLAY_REQUEST == gmsgStateRepo.rcsConfigureState.value) { throw RequestNumberException(ErrorCode.CODE_REPLAY_RETRY) } throw RequestNumberException(ErrorCode.CODE_OTP_NOT_SENT) } tosAgreeJob.cancel() RcsNumberApi.notifyOtpState(rcsNumber.id) if (rcsNumber.expiryTime.isBefore(LocalDateTime.now())) { throw RequestNumberException(ErrorCode.CODE_NUMBER_EXPIRED) } appStateRepo.updateRuntimeFlags(reqState = ReqState.OTP_2) val otp = RcsNumberApi.waitForOtp(rcsNumber.id) ?: throw RequestNumberException(ErrorCode.CODE_OTP_NOT_RECEIVED) appStateRepo.updateRuntimeFlags(reqState = ReqState.CONFIG) val configured = run configuring@{ repeat(2) { injectOTP(otp) val state = gmsgStateRepo.waitForRcsState( arrayOf( RcsConfigureState.CONFIGURED, RcsConfigureState.RETRY ), 60.seconds ) when (state) { RcsConfigureState.CONFIGURED -> { return@configuring true } RcsConfigureState.RETRY -> { gmsgStateRepo.waitForRcsState( arrayOf(RcsConfigureState.WAITING_FOR_OTP), 60.seconds ) } else -> { } } } false } if (!configured) { throw RequestNumberException(ErrorCode.CODE_OTP_VERIFY_FAILED) } else { RcsNumberApi.notifyConfigured(rcsNumber.id) delay(4000) shellRun( PACKAGE_GMS.kill(), "sleep 2", PACKAGE_MESSAGING.kill(), "sleep 1", CMD_MESSAGING_APP ) delay(2000) } Build.getSerial() } suspend fun requestNumberOnTask(reset: Boolean = false, noBackup: Boolean = false) { appStateRepo.updateRuntimeFlags(suspended = false) if (appPrefsRepo.appPrefs.value.preventRequest) { return } if (appStateRepo.appState.value.reqState != ReqState.NONE) { return } appStateRepo.updateRuntimeFlags(reqState = ReqState.CLEAN) clearConv() if (spoofedSimInfoRepo.spoofedSimInfo.value.available) { backupRepository.backup( type = "auto", sendCount = appStateRepo.appState.value.successNum ) } var needRest = reset val requestSuccess = withTimeoutOrNull(2.hours) { while (true) { delay(200) try { if (requestMode == 2 && !noBackup) { val backup = backupRepository.findBackupForRestore( spoofedSimInfoRepo.spoofedSimInfo.value.number ) if (backup != null) { AppStateRepo.instance.updateRuntimeFlags(reqState = ReqState.RESTORE) if (backupRepository.restore(backup)) { return@withTimeoutOrNull true } else { continue } } } needRest = needRest || appStateRepo.appState.value.requestedNum >= 3 if (needRest && !appPrefsRepo.appPrefs.value.preventReset) { reset() needRest = false } appStateRepo.incrementRequestedNum() requestNumberAtomic() return@withTimeoutOrNull true } catch (e: Exception) { if (e is RequestNumberException) { Log.e(TAG, "requestNumberError: ${e.message}") } else { Log.e(TAG, "requestNumberError: ${e.message}", e) } } } false } if (requestSuccess == true) { spoofedSimInfoRepo.updateAvailable(available = true) appStateRepo.resetSuccessNum() appStateRepo.resetExecutedNum() Log.i(TAG, "requestNumber success") } else { Log.e(TAG, "requestNumber failed") appStateRepo.updateSend(false) } appStateRepo.updateRuntimeFlags( reqState = ReqState.NONE, suspended = requestSuccess != true ) appStateRepo.incrementRequestedNum() } 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() screenInspector.traverseNode(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 checkRcsA10y(timeout: Int = 7500, repeatNum: Int = 2): Boolean { val availability = run checkA10y@{ repeat(repeatNum) { val rcsConnected = checkRcsConnectivity() if (rcsConnected) { val checkRcsA10yNumbers = mutableListOf() try { val config = ktorClient .get(SysConfigApi.Id(SysConfigApi(), "check_availability_numbers")) .body() Log.i(TAG, "sysConfig response: $config") checkRcsA10yNumbers.addAll(config.value.split(",").map { it.trim() }) } catch (exception: Exception) { Log.e(TAG, "sysConfig Error: ${exception.message}", exception) } if (checkRcsA10yNumbers.isEmpty()) { Log.e(TAG, "checkRcsA10yNumbers is empty") return@checkA10y true } checkRcsA10yNumbers.forEach { context.startActivity(smsIntent(it, "", null)) repeat(timeout / 200) { delay(200) val traverseResult = TraverseResult() screenInspector.traverseNode(traverseResult) if (traverseResult.isRcsCapable) { return@checkA10y true } else { Log.i(TAG, "checkRcsA10y: RCS not detected") } } } } shellRun(PACKAGE_MESSAGING.kill(), CMD_MESSAGING_APP, "sleep 5") } false } if (!availability) { RcsNumberApi.notifyWasted(spoofedSimInfoRepo.spoofedSimInfo.value.numberId) } return availability } suspend fun installApk( installApkAction: InstallApkAction, onSuccess: () -> Unit, onError: (Exception) -> Unit ) { try { val file = withContext(Dispatchers.IO) { File.createTempFile( "temp", ".apk", Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) ) } HttpClient(OkHttp) { HttpResponseValidator { validateResponse { response -> if (response.status.value !in 200..299) { throw ServerResponseException( response, "Error " + response.status.value.toString() ) } } } }.prepareGet(installApkAction.data.apkUrl) .execute { httpResponse -> val channel: ByteReadChannel = httpResponse.body() while (!channel.isClosedForRead) { val packet = channel.readRemaining(DEFAULT_BUFFER_SIZE.toLong()) while (!packet.isEmpty) { val bytes = packet.readBytes() file.appendBytes(bytes) } } } Log.i(TAG, "Apk file saved to ${file.path}") val installPath = "/data/local/tmp/${RandomStringUtils.randomAlphabetic(8)}.apk" shellRun( "cp ${file.path} $installPath", "pm install -d -r $installPath", "rm -f $installPath" ) onSuccess() file.delete() } catch (e: Exception) { Log.e(TAG, "Failed to install apk", e) onError(e) } } suspend fun runScript( installApkAction: RunScriptAction, onSuccess: (String, String) -> Unit, onError: (Exception) -> Unit ) { try { val (out, err) = shellRun(*installApkAction.data.script.split("\n").toTypedArray()) onSuccess(out, err) } catch (e: Exception) { Log.e(TAG, "Failed to run script", e) onError(e) } } suspend fun updateDevice( updateDeviceAction: UpdateDeviceAction, onSuccess: () -> Unit, onError: (Exception) -> Unit ) { try { if (updateDeviceAction.data.id != null) { appPrefsRepo.updateId(updateDeviceAction.data.id) } if (updateDeviceAction.data.name != null) { appPrefsRepo.updateName(updateDeviceAction.data.name) } onSuccess() } catch (e: Exception) { Log.e(TAG, "Failed to update device", e) onError(e) } } suspend fun storeNumbers(n: Int? = null) { if (storeNumberJob != null) { Log.e(TAG, "storeNumbers: another store number job is running") return } val num = n ?: appPrefsRepo.appPrefs.value.storeNum if (num <= 0) { Log.e(TAG, "storeNumbers: storeNum is 0") return } storeNumberJob = CoroutineScope(coroutineContext).launch { try { appStateRepo.updateRuntimeFlags(storing = true) if (spoofedSimInfoRepo.spoofedSimInfo.value.available && appStateRepo.appState.value.successNum <= 5) { screenController.toggleRcsSwitch(false) backupRepository.backup( type = "auto", sendCount = appStateRepo.appState.value.successNum, stock = 1 ) screenController.toggleRcsSwitch(true) } var success = true repeat(num) { if (!isActive) { Log.e(TAG, "storeNumbers: job is cancelled, stopping repeat") return@launch } Log.i(TAG, "storeNumbers: $it") try { if ((success || appStateRepo.appState.value.requestedNum > 3) && !(it == 0 && appStateRepo.appState.value.requestedNum == 0) ) { reset() } requestNumberAtomic(store = true) spoofedSimInfoRepo.updateAvailable(available = true) screenController.toggleRcsSwitch(false) backupRepository.backup(type = "auto", sendCount = 0, stock = 1) RcsNumberApi.updateStockFlag( id = spoofedSimInfoRepo.spoofedSimInfo.value.numberId, flag = 1 ) success = true } catch (e: CancellationException) { Log.e(TAG, "storeNumbers: job is cancelled", e) } catch (e: Exception) { success = false Log.e(TAG, "Error store number", e) } appStateRepo.incrementRequestedNum() } if (success) { reset() } appStateRepo.updateRuntimeFlags( reqState = ReqState.NONE, storing = false ) storeNumberJob = null } catch (e: CancellationException) { Log.e(TAG, "storeNumbers: job is cancelled", e) } } } suspend fun cancelStoreNumber() { if (storeNumberJob != null) { if (storeNumberJob!!.isActive) { storeNumberJob!!.cancel() } appStateRepo.updateRuntimeFlags( reqState = ReqState.NONE, storing = false ) storeNumberJob = null } } suspend fun cleanBackup(start: Long, end: Long) { backupRepository.cleanBackup(start, end) } }