Files
openless/openless-all/app/android/kotlin/OpenLessShizukuUserServiceClient.kt

128 lines
4.2 KiB
Kotlin

package com.openless.app
import android.content.ComponentName
import android.content.Context
import android.content.ServiceConnection
import android.os.IBinder
import android.util.Log
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicReference
import java.util.concurrent.locks.ReentrantLock
import rikka.shizuku.Shizuku
/**
* Binds the Shizuku UserService for synchronous privileged operations. Recovery calls are
* serialized; unbind removes the UserService after each operation.
*/
internal object OpenLessShizukuUserServiceClient {
private const val TAG = "OpenLessShizukuClient"
private const val BIND_TIMEOUT_MS = 8_000L
private const val SERVICE_VERSION = 3
private const val USER_SERVICE_PROCESS_SUFFIX = "shizuku"
private const val PASTE_SERVICE_PROCESS_SUFFIX = "paste"
private val recoveryLock = ReentrantLock()
@Volatile private var recoveryInProgress = false
fun <T> withService(context: Context, block: (IOpenLessShizukuUserService) -> T): T? {
return bindUserService(
context = context,
daemon = false,
processNameSuffix = USER_SERVICE_PROCESS_SUFFIX,
tag = "openless_shizuku",
block = block,
)
}
/**
* Paste injection uses a daemon UserService so MTK/Xiaomi ROMs do not spawn
* `com.openless.app:shizuku`, where LoadedApk.makeApplicationInner NPEs.
*/
fun <T> withPasteService(context: Context, block: (IOpenLessShizukuUserService) -> T): T? {
return bindUserService(
context = context,
daemon = true,
processNameSuffix = PASTE_SERVICE_PROCESS_SUFFIX,
tag = "openless_paste",
block = block,
)
}
private fun <T> bindUserService(
context: Context,
daemon: Boolean,
processNameSuffix: String,
tag: String,
block: (IOpenLessShizukuUserService) -> T,
): T? {
if (!Shizuku.pingBinder()) {
return null
}
val component =
ComponentName(context.packageName, OpenLessShizukuUserService::class.java.name)
val args =
Shizuku.UserServiceArgs(component)
.daemon(daemon)
.processNameSuffix(processNameSuffix)
.version(SERVICE_VERSION)
.tag(tag)
val latch = CountDownLatch(1)
val binderRef = AtomicReference<IOpenLessShizukuUserService?>(null)
val connection =
object : ServiceConnection {
override fun onServiceConnected(name: ComponentName?, binder: IBinder?) {
binderRef.set(IOpenLessShizukuUserService.Stub.asInterface(binder))
latch.countDown()
}
override fun onServiceDisconnected(name: ComponentName?) {
binderRef.set(null)
}
}
return try {
Shizuku.bindUserService(args, connection)
if (!latch.await(BIND_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
Log.w(TAG, "UserService bind timed out tag=$tag daemon=$daemon")
return null
}
val service = binderRef.get() ?: return null
block(service)
} catch (error: Throwable) {
Log.w(TAG, "UserService bind failed tag=$tag daemon=$daemon", error)
null
} finally {
try {
Shizuku.unbindUserService(args, connection, true)
} catch (error: Throwable) {
Log.w(TAG, "UserService unbind failed tag=$tag", error)
}
}
}
fun <T> withRecoveryLock(block: () -> T): T? {
if (!recoveryLock.tryLock()) {
return null
}
return try {
if (recoveryInProgress) {
null
} else {
recoveryInProgress = true
try {
block()
} finally {
recoveryInProgress = false
}
}
} finally {
recoveryLock.unlock()
}
}
fun isRecoveryInProgress(): Boolean = recoveryInProgress
}