package com.example.modifier.utils import android.accessibilityservice.AccessibilityServiceInfo import android.annotation.SuppressLint import android.app.AlarmManager import android.app.PendingIntent import android.content.Context import android.content.Intent import android.content.pm.PackageManager import android.os.Build import android.provider.Settings import android.text.TextUtils import android.util.Log import android.view.accessibility.AccessibilityManager import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat import com.example.modifier.BuildConfig import com.example.modifier.MainActivity import com.example.modifier.Utils import com.example.modifier.baseTag import com.example.modifier.constants.PACKAGE_GMS import com.example.modifier.extension.kill import com.example.modifier.http.api.SysConfigApi import com.example.modifier.http.ktorClient import com.example.modifier.http.response.SysConfigResponse import com.example.modifier.service.ModifierService import io.ktor.client.call.body import io.ktor.client.plugins.resources.get import io.ktor.client.request.head import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.withContext import org.apache.commons.io.FileUtils import org.json.JSONObject import java.io.File import java.net.NetworkInterface import java.time.ZoneId import java.time.ZonedDateTime import java.time.format.DateTimeFormatter import java.util.Locale import java.util.UUID import kotlin.system.exitProcess const val systemTag = "$baseTag/System" const val ROOTED_PHONE = false val uniqueId: String @SuppressLint("HardwareIds") get() { var uniqueID = "" if (uniqueID.isBlank()) { try { val context = getContext()!! uniqueID = Settings.Secure.getString(context.contentResolver, Settings.Secure.ANDROID_ID) } catch (e: java.lang.Exception) { e.printStackTrace() } } if (uniqueID.isBlank()) { uniqueID = UUID.randomUUID().toString() } return uniqueID } @SuppressLint("PrivateApi") fun getContext(): Context { val activityThreadClass = Class.forName("android.app.ActivityThread") val currentActivityThreadMethod = activityThreadClass.getMethod("currentActivityThread") currentActivityThreadMethod.isAccessible = true val currentActivityThread = currentActivityThreadMethod.invoke(null) val getApplicationMethod = activityThreadClass.getMethod("getApplication") getApplicationMethod.isAccessible = true return getApplicationMethod.invoke(currentActivityThread) as Context } suspend fun hasRootAccess(): Boolean { var rootAccess = false try { val (output, _) = shellRun("echo \"imrooted\"") if (output.contains("imrooted")) { rootAccess = true } } catch (e: Exception) { e.printStackTrace() } return rootAccess } fun hasPermission(permission: String): Boolean { return ActivityCompat.checkSelfPermission( getContext()!!, permission ) == PackageManager.PERMISSION_GRANTED } fun isAccessibilityEnabled(): Boolean { val context = getContext()!! val am = context.getSystemService(Context.ACCESSIBILITY_SERVICE) as AccessibilityManager val enabledServices = am.getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_ALL_MASK) for (enabledService in enabledServices) { Log.i( systemTag, "Enabled service: " + enabledService.resolveInfo.serviceInfo.packageName + "/" + enabledService.resolveInfo.serviceInfo.name ) val enabledServiceInfo = enabledService.resolveInfo.serviceInfo if (enabledServiceInfo.packageName == context.packageName && enabledServiceInfo.name == ModifierService.NAME) return true } return false } suspend fun enableAccessibility(): Boolean { if (isAccessibilityEnabled()) return true val context = getContext()!! val am = context.getSystemService(Context.ACCESSIBILITY_SERVICE) as AccessibilityManager val enabledServices = am.getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_ALL_MASK) val names: MutableList = ArrayList() for (enabledService in enabledServices) { names.add(enabledService.resolveInfo.serviceInfo.packageName + "/" + enabledService.resolveInfo.serviceInfo.name) } names.add(context.packageName + "/" + ModifierService.NAME) try { shellRun( "settings put secure enabled_accessibility_services " + TextUtils.join(":", names), "settings put secure accessibility_enabled 1" ) return true } catch (e: java.lang.Exception) { e.printStackTrace() } return false } suspend fun enableOverlay() { try { shellRun("appops set " + BuildConfig.APPLICATION_ID + " SYSTEM_ALERT_WINDOW allow") } catch (e: java.lang.Exception) { e.printStackTrace() } } suspend fun optimize() { val context = getContext() val packageManager = context.packageManager val info = packageManager.getApplicationInfo("com.google.android.gms", 0) shellRun( "dumpsys deviceidle whitelist +com.google.android.apps.messaging", "dumpsys deviceidle whitelist +${BuildConfig.APPLICATION_ID}", "cmd netpolicy add restrict-background-blacklist ${info.uid}", "pm grant ${BuildConfig.APPLICATION_ID} android.permission.POST_NOTIFICATIONS", ) // if (Build.MODEL.startsWith("SM-F707") || Build.MODEL.startsWith("SM-F711")) { shellRun( "settings put global window_animation_scale 1", "settings put global transition_animation_scale 1", "settings put global animator_duration_scale 1" ) // } } suspend fun syncTime() { try { Log.i(systemTag, "syncTime: start") val response = ktorClient.head("http://www.baidu.com") val dateHeader = response.headers["Date"] val date = ZonedDateTime.parse( dateHeader, DateTimeFormatter.ofPattern("EEE, dd MMM yyyy HH:mm:ss z", Locale.ENGLISH) ) // convert to Asia/Shanghai val dateInZone = date.withZoneSameInstant(ZoneId.of("Asia/Shanghai")) Log.i( systemTag, "CurrentTime from Baidu: ${dateInZone.format(DateTimeFormatter.ISO_DATE_TIME)}" ) shellRun( "settings put system time_12_24 24", "settings put global auto_time 0", "settings put global auto_time_zone 0", "setprop persist.sys.timezone Asia/Shanghai", "date \"${dateInZone.format(DateTimeFormatter.ofPattern("MMddHHmmyyyy.ss"))}\"" ) } catch (e: Exception) { Log.e(systemTag, "Error SyncTime", e) } } suspend fun isRebooted(): Boolean { if (shellRun("getprop rebooted").first.contains("yes")) { return false } shellRun("setprop rebooted yes") return true } suspend fun setBatteryLevel(level: Int) { shellRun("dumpsys battery set level $level") } suspend fun killPhoneProcess(force: Boolean = false): Boolean { try { if (!force) { if (shellRun("getprop phonekilled").first.contains("yes")) { return true } } run kill@{ repeat(3) { val pid = shellRun("pidof com.android.phone").first.trim() if (!Regex("[0-9]+").matches(pid)) { Log.e(systemTag, "killPhoneProcess: pid not found") return true } Log.i(systemTag, "killPhoneProcess: pid=$pid") shellRun("kill -9 $pid") delay(1000) val pidNew = shellRun("pidof com.android.phone").first.trim() if (pidNew == pid) { Log.e( systemTag, "killPhoneProcess: failed to kill phone process" ) } else { Log.i(systemTag, "killPhoneProcess: success, new pid: $pidNew") shellRun("kill -9 $pid", "setprop phonekilled yes") return true } } } } catch (e: Exception) { Log.e(systemTag, "Error Kill Phone", e) } return false } suspend fun currentActivity(): String? { val out = shellRun("dumpsys activity activities | grep topResumedActivity").first val activity = Regex("topResumedActivity=ActivityRecord\\{.*/\\.(\\S*)\\}") .find(out)?.groups?.get(1)?.value return activity } fun sqlite3path(): String { val context = getContext() val dataDir = ContextCompat.getDataDir(context) 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) binDir.listFiles()?.forEach { it.setExecutable(true) } } Log.i("Modifier", "arch: " + Build.SUPPORTED_ABIS.joinToString(", ")) val file = File(dataDir, "bin/sqlite3.arm") file.setExecutable(true) return file.path } fun restartSelf() { val context = getContext() val mStartActivity = Intent(context, MainActivity::class.java) val mPendingIntentId = 123456 val mPendingIntent = PendingIntent.getActivity( context, mPendingIntentId, mStartActivity, PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_IMMUTABLE ) val mgr = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager mgr.set(AlarmManager.RTC, System.currentTimeMillis() + 100, mPendingIntent) exitProcess(0) } fun getIPAddress(): List { return try { NetworkInterface.getNetworkInterfaces().toList() .flatMap { intf -> intf.inetAddresses.toList() .map { it.hostAddress } } .filter { it.matches(Regex("\\d+\\.\\d+\\.\\d+\\.\\d+")) && !it.startsWith("127.") } } catch (e: Exception) { Log.e(systemTag, "Error getIPAddress", e) emptyList() } } suspend fun checkPif() { if (!(Build.MODEL.startsWith("SM-F707") || Build.MODEL.startsWith("SM-F711"))) { return } try { val out = shellRun("cat /data/adb/modules/playintegrityfix/module.prop").first var pifFile = "" if (out.contains("name=Play Integrity Fix")) { pifFile = "pif.json" } else if (out.contains("name=Play Integrity Fork")) { pifFile = "custom.pif.json" } else { Log.e(systemTag, "PIF module not found") } val config = ktorClient.get(SysConfigApi.Id(SysConfigApi(), "pif")) .body() if (config.value.isBlank()) { Log.i(systemTag, "cannot get pif.json from server") return } val newPif = JSONObject(config.value.split("\n") .joinToString("\n") { line -> line.replace(Regex("^\\s*//.*"), "") }) val newFingerPrint = newPif.optString("FINGERPRINT", "") if (newFingerPrint.isEmpty()) { Log.i(systemTag, "cannot get fingerprint from pif.json") return } val currentFingerprint = shellRun("cat /data/adb/modules/playintegrityfix/$pifFile").let { val json = JSONObject(it.first.split("\n") .joinToString("\n") { line -> line.replace(Regex("^\\s*//.*"), "") }) json.optString("FINGERPRINT", "") } if (newFingerPrint != currentFingerprint) { val tmpFile: File withContext(Dispatchers.IO) { tmpFile = File.createTempFile("pif", ".json") FileUtils.writeStringToFile(tmpFile, newPif.toString(), "UTF-8") shellRun( "cp ${tmpFile.path} /data/adb/modules/playintegrityfix/$pifFile", PACKAGE_GMS.kill(), ) tmpFile.delete() Log.i(systemTag, "pif.json updated, fingerprint: $newFingerPrint") delay(5000) } } else { Log.i(systemTag, "pif.json is up to date") } } catch (e: Exception) { Log.e(systemTag, "Error checkPif", e) } }