refactor(app): drop PIF resolvers + dump purge
Remove PatchLevelManager (auto-resolved the security-patch date from an installed PlayIntegrityFix module into security_patch.txt, with a FileObserver hot-reload) and BulletinPoller (scheduled bulletin refresh), and their App.kt init/start calls. Add purgeDebugDiagnostics(): release builds sweep stale teesim-*.bin dumps from /data/local/tmp at boot so a prior debug install can't leave a detection artifact. Stabilize the InterceptorUtils diagnostic dump path to a single file instead of one per call.
This commit is contained in:
@@ -6,12 +6,11 @@ import android.content.Context
|
|||||||
import android.content.ContextWrapper
|
import android.content.ContextWrapper
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
|
import java.io.File
|
||||||
import java.security.Security
|
import java.security.Security
|
||||||
import org.bouncycastle.jce.provider.BouncyCastleProvider
|
import org.bouncycastle.jce.provider.BouncyCastleProvider
|
||||||
import org.matrix.TEESimulator.config.BootStateManager
|
import org.matrix.TEESimulator.config.BootStateManager
|
||||||
import org.matrix.TEESimulator.config.BulletinPoller
|
|
||||||
import org.matrix.TEESimulator.config.ConfigurationManager
|
import org.matrix.TEESimulator.config.ConfigurationManager
|
||||||
import org.matrix.TEESimulator.config.PatchLevelManager
|
|
||||||
import org.matrix.TEESimulator.interception.keystore.AbstractKeystoreInterceptor
|
import org.matrix.TEESimulator.interception.keystore.AbstractKeystoreInterceptor
|
||||||
import org.matrix.TEESimulator.interception.keystore.Keystore2Interceptor
|
import org.matrix.TEESimulator.interception.keystore.Keystore2Interceptor
|
||||||
import org.matrix.TEESimulator.interception.keystore.KeystoreInterceptor
|
import org.matrix.TEESimulator.interception.keystore.KeystoreInterceptor
|
||||||
@@ -41,12 +40,12 @@ object App {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
purgeDebugDiagnostics()
|
||||||
prepareEnvironment()
|
prepareEnvironment()
|
||||||
|
|
||||||
// Spoof boot-state and patch-level props before any hook attaches,
|
// Spoof boot-state props before any hook attaches, so keystore2's
|
||||||
// so keystore2's cached snapshot reflects the spoofed values.
|
// cached snapshot reflects the spoofed values.
|
||||||
BootStateManager.apply()
|
BootStateManager.apply()
|
||||||
PatchLevelManager.initialize()
|
|
||||||
|
|
||||||
// Load the package configuration.
|
// Load the package configuration.
|
||||||
ConfigurationManager.initialize()
|
ConfigurationManager.initialize()
|
||||||
@@ -65,12 +64,6 @@ object App {
|
|||||||
|
|
||||||
NativeCertGen.initialize("/data/adb/modules/tricky_store/libcertgen.so")
|
NativeCertGen.initialize("/data/adb/modules/tricky_store/libcertgen.so")
|
||||||
|
|
||||||
try {
|
|
||||||
BulletinPoller.start()
|
|
||||||
} catch (e: Throwable) {
|
|
||||||
SystemLogger.error("Failed to start BulletinPoller", e)
|
|
||||||
}
|
|
||||||
|
|
||||||
// This starts the message queue processing. It blocks here indefinitely
|
// This starts the message queue processing. It blocks here indefinitely
|
||||||
// processing messages until Looper.myLooper().quit() is called.
|
// processing messages until Looper.myLooper().quit() is called.
|
||||||
Looper.loop()
|
Looper.loop()
|
||||||
@@ -80,6 +73,25 @@ object App {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Release builds never emit diagnostics. Sweep any `.bin` dumps a prior
|
||||||
|
* debug install left in the world-readable temp dir so they can't act as a
|
||||||
|
* detection artifact for apps that probe /data/local/tmp.
|
||||||
|
*/
|
||||||
|
private fun purgeDebugDiagnostics() {
|
||||||
|
if (SystemLogger.isDebugBuild) return
|
||||||
|
val stale =
|
||||||
|
File("/data/local/tmp").listFiles { _, name ->
|
||||||
|
name.startsWith("teesim-") && name.endsWith(".bin")
|
||||||
|
} ?: return
|
||||||
|
stale.forEach { runCatching { it.delete() } }
|
||||||
|
if (stale.isNotEmpty()) {
|
||||||
|
// warning() bypasses the rate limiter, so this once-per-boot audit
|
||||||
|
// line survives the noisy startup window.
|
||||||
|
SystemLogger.warning("Purged ${stale.size} stale debug diagnostic(s) from /data/local/tmp")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** Initializes the necessary Android framework internals to satisfy KeyStore requirements. */
|
/** Initializes the necessary Android framework internals to satisfy KeyStore requirements. */
|
||||||
private fun prepareEnvironment() {
|
private fun prepareEnvironment() {
|
||||||
// 1. Prepare Main Looper
|
// 1. Prepare Main Looper
|
||||||
|
|||||||
@@ -1,193 +0,0 @@
|
|||||||
package org.matrix.TEESimulator.config
|
|
||||||
|
|
||||||
import android.os.Handler
|
|
||||||
import android.os.HandlerThread
|
|
||||||
import java.io.File
|
|
||||||
import java.net.URL
|
|
||||||
import java.nio.file.Files
|
|
||||||
import java.nio.file.StandardCopyOption
|
|
||||||
import javax.net.ssl.HttpsURLConnection
|
|
||||||
import org.json.JSONArray
|
|
||||||
import org.json.JSONObject
|
|
||||||
import org.matrix.TEESimulator.BuildConfig
|
|
||||||
import org.matrix.TEESimulator.logging.SystemLogger
|
|
||||||
|
|
||||||
object BulletinPoller {
|
|
||||||
private const val BULLETIN_URL = "https://source.android.com/docs/security/bulletin/pixel"
|
|
||||||
private const val PATCH_FILE = "/data/adb/tricky_store/security_patch.txt"
|
|
||||||
private const val HISTORY_FILE = "/data/adb/tricky_store/last_bulletin_fetch.json"
|
|
||||||
private const val HISTORY_STAGING = "/data/adb/tricky_store/last_bulletin_fetch.json.next"
|
|
||||||
private const val HISTORY_CAP = 10
|
|
||||||
private const val CONNECT_TIMEOUT_MS = 10_000
|
|
||||||
private const val READ_TIMEOUT_MS = 15_000
|
|
||||||
private const val STEADY_INTERVAL_MS = 24L * 60 * 60 * 1000
|
|
||||||
|
|
||||||
private val BOOTSTRAP_INTERVALS = longArrayOf(5_000, 30_000, 120_000, 600_000, 1_800_000)
|
|
||||||
private val DATE_REGEX = Regex("<td>(\\d{4}-\\d{2}-\\d{2})</td>")
|
|
||||||
private val PATCH_DATE_PATTERN = Regex("^\\d{4}-\\d{2}-\\d{2}$")
|
|
||||||
|
|
||||||
private lateinit var handler: Handler
|
|
||||||
@Volatile private var bootstrapStep = 0
|
|
||||||
@Volatile private var steadyArmed = false
|
|
||||||
|
|
||||||
fun start() {
|
|
||||||
val thread = HandlerThread("BulletinPoller").apply { start() }
|
|
||||||
handler = Handler(thread.looper)
|
|
||||||
handler.postDelayed(::pollOnce, BOOTSTRAP_INTERVALS[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun pollOnce() {
|
|
||||||
try {
|
|
||||||
val result = fetchAndParse()
|
|
||||||
appendHistory(result)
|
|
||||||
scheduleNext(result.status == "success")
|
|
||||||
} catch (t: Throwable) {
|
|
||||||
SystemLogger.error("BulletinPoller: pollOnce failed", t)
|
|
||||||
scheduleNext(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun scheduleNext(success: Boolean) {
|
|
||||||
if (success || steadyArmed) {
|
|
||||||
steadyArmed = true
|
|
||||||
handler.postDelayed(::pollOnce, STEADY_INTERVAL_MS)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
bootstrapStep++
|
|
||||||
if (bootstrapStep >= BOOTSTRAP_INTERVALS.size) {
|
|
||||||
steadyArmed = true
|
|
||||||
handler.postDelayed(::pollOnce, STEADY_INTERVAL_MS)
|
|
||||||
} else {
|
|
||||||
handler.postDelayed(::pollOnce, BOOTSTRAP_INTERVALS[bootstrapStep])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private data class FetchResult(
|
|
||||||
val ts: Long,
|
|
||||||
val status: String,
|
|
||||||
val httpCode: Int?,
|
|
||||||
val parsedDate: String?,
|
|
||||||
val applied: Boolean,
|
|
||||||
val error: String?,
|
|
||||||
)
|
|
||||||
|
|
||||||
private fun fetchAndParse(): FetchResult {
|
|
||||||
val ts = System.currentTimeMillis()
|
|
||||||
var conn: HttpsURLConnection? = null
|
|
||||||
return try {
|
|
||||||
conn =
|
|
||||||
(URL(BULLETIN_URL).openConnection() as HttpsURLConnection).apply {
|
|
||||||
connectTimeout = CONNECT_TIMEOUT_MS
|
|
||||||
readTimeout = READ_TIMEOUT_MS
|
|
||||||
setRequestProperty(
|
|
||||||
"User-Agent",
|
|
||||||
"TEESimulator/${BuildConfig.VERSION_NAME}",
|
|
||||||
)
|
|
||||||
requestMethod = "GET"
|
|
||||||
}
|
|
||||||
val code = conn.responseCode
|
|
||||||
if (code != 200) {
|
|
||||||
return FetchResult(ts, "network_error", code, null, false, "HTTP $code")
|
|
||||||
}
|
|
||||||
val html = conn.inputStream.bufferedReader().use { it.readText() }
|
|
||||||
val date = DATE_REGEX.find(html)?.groupValues?.get(1)
|
|
||||||
if (date == null) {
|
|
||||||
return FetchResult(
|
|
||||||
ts,
|
|
||||||
"parse_error",
|
|
||||||
code,
|
|
||||||
null,
|
|
||||||
false,
|
|
||||||
"no <td>YYYY-MM-DD</td> match",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
val current = currentPatch()
|
|
||||||
if (current == null || date <= current) {
|
|
||||||
return FetchResult(ts, "success", code, date, false, null)
|
|
||||||
}
|
|
||||||
if (PatchLevelManager.updateTo(date)) {
|
|
||||||
FetchResult(ts, "success", code, date, true, null)
|
|
||||||
} else {
|
|
||||||
FetchResult(
|
|
||||||
ts,
|
|
||||||
"validation_rejected",
|
|
||||||
code,
|
|
||||||
date,
|
|
||||||
false,
|
|
||||||
"PatchLevelManager.updateTo rejected $date",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
FetchResult(ts, "network_error", null, null, false, e.toString())
|
|
||||||
} finally {
|
|
||||||
conn?.disconnect()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun currentPatch(): String? {
|
|
||||||
val f = File(PATCH_FILE)
|
|
||||||
if (!f.exists()) return null
|
|
||||||
val raw = try {
|
|
||||||
f.readLines()
|
|
||||||
.firstOrNull { it.startsWith("system=") }
|
|
||||||
?.substringAfter("system=")
|
|
||||||
?.trim()
|
|
||||||
?.takeIf { it != "prop" && it.isNotEmpty() }
|
|
||||||
} catch (_: Exception) {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
if (raw == null) return null
|
|
||||||
if (PATCH_DATE_PATTERN.matches(raw)) return raw
|
|
||||||
SystemLogger.warning(
|
|
||||||
"BulletinPoller: ignoring malformed system='$raw' in $PATCH_FILE"
|
|
||||||
)
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun appendHistory(result: FetchResult) {
|
|
||||||
try {
|
|
||||||
val target = File(HISTORY_FILE)
|
|
||||||
val staging = File(HISTORY_STAGING)
|
|
||||||
val existing = if (target.exists()) runCatching { target.readText() }.getOrNull() else null
|
|
||||||
val history =
|
|
||||||
existing
|
|
||||||
?.let { runCatching { JSONObject(it).optJSONArray("history") }.getOrNull() }
|
|
||||||
?: JSONArray()
|
|
||||||
val entry =
|
|
||||||
JSONObject().apply {
|
|
||||||
put("ts", result.ts)
|
|
||||||
put("status", result.status)
|
|
||||||
put("http_code", result.httpCode ?: JSONObject.NULL)
|
|
||||||
put("parsed_date", result.parsedDate ?: JSONObject.NULL)
|
|
||||||
put("applied", result.applied)
|
|
||||||
put("error", result.error ?: JSONObject.NULL)
|
|
||||||
}
|
|
||||||
history.put(entry)
|
|
||||||
while (history.length() > HISTORY_CAP) history.remove(0)
|
|
||||||
|
|
||||||
val latestKnown =
|
|
||||||
(0 until history.length())
|
|
||||||
.mapNotNull {
|
|
||||||
history.optJSONObject(it)?.optString("parsed_date", "")?.takeIf { d ->
|
|
||||||
d.isNotBlank()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.lastOrNull()
|
|
||||||
|
|
||||||
val root =
|
|
||||||
JSONObject().apply {
|
|
||||||
put("latest_known_date", latestKnown ?: JSONObject.NULL)
|
|
||||||
put("history", history)
|
|
||||||
}
|
|
||||||
staging.writeText(root.toString(2))
|
|
||||||
Files.move(
|
|
||||||
staging.toPath(),
|
|
||||||
target.toPath(),
|
|
||||||
StandardCopyOption.ATOMIC_MOVE,
|
|
||||||
StandardCopyOption.REPLACE_EXISTING,
|
|
||||||
)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
SystemLogger.error("BulletinPoller: failed to persist history", e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,192 +0,0 @@
|
|||||||
package org.matrix.TEESimulator.config
|
|
||||||
|
|
||||||
import android.os.Build
|
|
||||||
import android.os.FileObserver
|
|
||||||
import android.os.SystemProperties
|
|
||||||
import java.io.File
|
|
||||||
import java.nio.file.Files
|
|
||||||
import java.nio.file.StandardCopyOption
|
|
||||||
import java.time.LocalDate
|
|
||||||
import org.json.JSONObject
|
|
||||||
import org.matrix.TEESimulator.logging.SystemLogger
|
|
||||||
import org.matrix.TEESimulator.util.AndroidDeviceUtils
|
|
||||||
|
|
||||||
object PatchLevelManager {
|
|
||||||
private const val PATCH_FILE = "/data/adb/tricky_store/security_patch.txt"
|
|
||||||
private const val STAGING_FILE = "/data/adb/tricky_store/security_patch.txt.next"
|
|
||||||
private const val PIF_DIR = "/data/adb/modules/playintegrityfix"
|
|
||||||
private const val FLOOR_YYYYMMDD = 20200101
|
|
||||||
private const val MAX_PAST_OFFSET = 10000
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Pixel security bulletins publish monthly; pre-announced dates occasionally
|
|
||||||
* slip by 2-4 weeks. 60 days covers that window without admitting a
|
|
||||||
* far-future date from a hostile or mis-parsed bulletin response.
|
|
||||||
*/
|
|
||||||
private const val MAX_FUTURE_DAYS = 60L
|
|
||||||
|
|
||||||
private val PIF_FILENAMES =
|
|
||||||
setOf("pif.json", "pif.prop", "custom.pif.json", "custom.pif.prop")
|
|
||||||
|
|
||||||
private val DATE_PATTERN = Regex("^\\d{4}-\\d{2}-\\d{2}$")
|
|
||||||
private val PROP_PATTERN = Regex("^SECURITY_PATCH=(.+)$", RegexOption.MULTILINE)
|
|
||||||
private val SECTION_HEADER = Regex("^\\[[a-zA-Z0-9_.-]+]$")
|
|
||||||
private val GLOBAL_KEYS = setOf("system", "boot", "vendor", "all")
|
|
||||||
|
|
||||||
private val PIF_SOURCES =
|
|
||||||
listOf(
|
|
||||||
"/data/adb/modules/playintegrityfix/pif.json",
|
|
||||||
"/data/adb/pif.json",
|
|
||||||
"/data/adb/modules/playintegrityfix/pif.prop",
|
|
||||||
"/data/adb/pif.prop",
|
|
||||||
"/data/adb/modules/playintegrityfix/custom.pif.json",
|
|
||||||
"/data/adb/modules/playintegrityfix/custom.pif.prop",
|
|
||||||
)
|
|
||||||
|
|
||||||
fun initialize() {
|
|
||||||
refreshFromSources()
|
|
||||||
startPifObserver()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun refreshFromSources() {
|
|
||||||
val date =
|
|
||||||
resolvePifPatch()
|
|
||||||
?: SystemProperties.get(
|
|
||||||
"ro.build.version.security_patch",
|
|
||||||
Build.VERSION.SECURITY_PATCH,
|
|
||||||
)
|
|
||||||
SystemLogger.info("PatchLevelManager: resolved patch date = $date")
|
|
||||||
applyToProps(date)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun startPifObserver() {
|
|
||||||
if (!File(PIF_DIR).exists()) {
|
|
||||||
SystemLogger.debug("PatchLevelManager: PIF dir absent, hot-reload disabled")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
PifObserver.startWatching()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Synchronized
|
|
||||||
private fun applyToProps(date: String) {
|
|
||||||
if (!DATE_PATTERN.matches(date)) {
|
|
||||||
SystemLogger.warning(
|
|
||||||
"PatchLevelManager: skip resetprop for invalid date: $date"
|
|
||||||
)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
AndroidDeviceUtils.setProperty("ro.build.version.security_patch", date)
|
|
||||||
AndroidDeviceUtils.setProperty("ro.vendor.build.security_patch", date)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun updateTo(date: String): Boolean {
|
|
||||||
if (!DATE_PATTERN.matches(date)) {
|
|
||||||
SystemLogger.warning("PatchLevelManager: invalid date format: $date")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
val dateInt = date.replace("-", "").toInt()
|
|
||||||
if (dateInt < FLOOR_YYYYMMDD) {
|
|
||||||
SystemLogger.warning("PatchLevelManager: $date below floor $FLOOR_YYYYMMDD")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
val now = LocalDate.now()
|
|
||||||
val today = now.year * 10000 + now.monthValue * 100 + now.dayOfMonth
|
|
||||||
if (today >= dateInt + MAX_PAST_OFFSET) {
|
|
||||||
SystemLogger.warning(
|
|
||||||
"PatchLevelManager: $date more than 1y older than today ($today)"
|
|
||||||
)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
val maxFuture =
|
|
||||||
now.plusDays(MAX_FUTURE_DAYS).let {
|
|
||||||
it.year * 10000 + it.monthValue * 100 + it.dayOfMonth
|
|
||||||
}
|
|
||||||
if (dateInt > maxFuture) {
|
|
||||||
SystemLogger.warning(
|
|
||||||
"PatchLevelManager: $date more than $MAX_FUTURE_DAYS days in future ($maxFuture)"
|
|
||||||
)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
atomicWrite(date)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
SystemLogger.error("PatchLevelManager: atomicWrite failed for $date", e)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
applyToProps(date)
|
|
||||||
SystemLogger.info("PatchLevelManager: applied patch date $date")
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun resolvePifPatch(): String? {
|
|
||||||
val source =
|
|
||||||
PIF_SOURCES.map(::File).lastOrNull { it.exists() && it.length() > 0 }
|
|
||||||
?: return null
|
|
||||||
return try {
|
|
||||||
val text = source.readText()
|
|
||||||
val parsed =
|
|
||||||
if (source.name.endsWith(".json")) {
|
|
||||||
JSONObject(text).optString("SECURITY_PATCH", "")
|
|
||||||
} else {
|
|
||||||
PROP_PATTERN.find(text)?.groupValues?.get(1)?.trim().orEmpty()
|
|
||||||
}
|
|
||||||
parsed.takeIf { it.isNotBlank() }
|
|
||||||
} catch (e: Exception) {
|
|
||||||
SystemLogger.warning(
|
|
||||||
"PatchLevelManager: failed to parse ${source.path}: ${e.message}"
|
|
||||||
)
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun atomicWrite(date: String) {
|
|
||||||
val target = File(PATCH_FILE)
|
|
||||||
val staging = File(STAGING_FILE)
|
|
||||||
staging.writeText(mergedContents(target, date))
|
|
||||||
Files.move(
|
|
||||||
staging.toPath(),
|
|
||||||
target.toPath(),
|
|
||||||
StandardCopyOption.ATOMIC_MOVE,
|
|
||||||
StandardCopyOption.REPLACE_EXISTING,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun mergedContents(target: File, date: String): String {
|
|
||||||
val globalBlock = "system=$date\nboot=$date\nvendor=$date\n"
|
|
||||||
if (!target.exists()) return globalBlock
|
|
||||||
val tail = stripGlobalAssignments(target.readLines())
|
|
||||||
if (tail.isEmpty()) return globalBlock
|
|
||||||
return globalBlock + tail.joinToString("\n", prefix = "\n", postfix = "\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun stripGlobalAssignments(lines: List<String>): List<String> {
|
|
||||||
val kept = mutableListOf<String>()
|
|
||||||
var inGlobal = true
|
|
||||||
for (line in lines) {
|
|
||||||
val trimmed = line.trim()
|
|
||||||
if (SECTION_HEADER.matches(trimmed)) {
|
|
||||||
inGlobal = false
|
|
||||||
kept += line
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if (inGlobal && isGlobalKeyAssignment(trimmed)) continue
|
|
||||||
kept += line
|
|
||||||
}
|
|
||||||
return kept
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun isGlobalKeyAssignment(trimmed: String): Boolean {
|
|
||||||
if (trimmed.isEmpty() || trimmed.startsWith("#") || '=' !in trimmed) return false
|
|
||||||
val key = trimmed.substringBefore('=').trim().lowercase()
|
|
||||||
return key in GLOBAL_KEYS
|
|
||||||
}
|
|
||||||
|
|
||||||
private object PifObserver :
|
|
||||||
FileObserver(File(PIF_DIR), CLOSE_WRITE or MOVED_TO or DELETE) {
|
|
||||||
override fun onEvent(event: Int, path: String?) {
|
|
||||||
if (path == null || path !in PIF_FILENAMES) return
|
|
||||||
SystemLogger.info("PatchLevelManager: PIF change ($path), refreshing")
|
|
||||||
refreshFromSources()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -127,7 +127,7 @@ object InterceptorUtils {
|
|||||||
val savedPos = parcel.dataPosition()
|
val savedPos = parcel.dataPosition()
|
||||||
val wire = parcel.marshall()
|
val wire = parcel.marshall()
|
||||||
parcel.setDataPosition(savedPos)
|
parcel.setDataPosition(savedPos)
|
||||||
val path = "/data/local/tmp/teesim-$diagnosticTag-${System.nanoTime()}.bin"
|
val path = "/data/local/tmp/teesim-$diagnosticTag.bin"
|
||||||
runCatching { java.io.File(path).writeBytes(wire) }
|
runCatching { java.io.File(path).writeBytes(wire) }
|
||||||
SystemLogger.debug("[$diagnosticTag] reply len=${wire.size} path=$path")
|
SystemLogger.debug("[$diagnosticTag] reply len=${wire.size} path=$path")
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user