package pw.binom.rayneovm.glasses import android.app.Activity import android.util.Log import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import rikka.shizuku.Shizuku /** * Управляет жизненным циклом Shizuku для GlassesApp. * * Состояния: * - Waiting — Shizuku ещё не ответил (binder listener ждёт) * - NotRunning — Shizuku не запущен (нет сервера) * - NeedsPermission — Shizuku запущен, но прав ещё нет * - Granted — права есть, можно работать * * Использование (см. https://github.com/RikkaApps/Shizuku-API): * 1. В AndroidManifest.xml добавить `` (уже есть). * 2. В Application.onCreate / Activity.onCreate один раз: * Shizuku.addBinderReceivedListenerSticky(...) * Shizuku.addRequestPermissionResultListener(...) * 3. Проверять ShizukuBootstrap.state. * * Зависимость `dev.rikka.shizuku:provider` нужна (в ней ShizukuProvider). */ object ShizukuBootstrap { enum class AllowState { Waiting, NotRunning, NeedsPermission, Granted, } private const val TAG = "ShizukuBootstrap" const val REQUEST_CODE_PERMISSION = 7801 private val _state = MutableStateFlow(AllowState.Waiting) val state: StateFlow = _state.asStateFlow() @Volatile private var binderReceivedCalled = false /** * Привязывает listeners к Shizuku (по доке). Вызывать из Activity.onCreate(). * * - addBinderReceivedListenerSticky — сразу же выстреливает, если binder уже получен * (без "sticky" мы бы пропустили). * - addRequestPermissionResultListener — вызовется после диалога. * * ShizukuProvider из `dev.rikka.shizuku:provider` инициализирует binder до того, * как наш процесс получит управление в onCreate (через ContentProvider.onCreate), * поэтому pingBinder() сразу вернёт true — если Shizuku-сервер живой и v12+. */ fun attach(activity: Activity) { // 1. Подписка на приход binder-а (sticky ⇒ сразу же вызовется, если уже есть). Shizuku.addBinderReceivedListenerSticky(object : Shizuku.OnBinderReceivedListener { override fun onBinderReceived() { if (binderReceivedCalled) return binderReceivedCalled = true Log.i(TAG, "onBinderReceived") refresh() } }) // 2. Подписка на результат диалога разрешения. Shizuku.addRequestPermissionResultListener { _, grantResult -> Log.i(TAG, "onRequestPermissionResult: grant=$grantResult") refresh() } // 3. Сразу пробуем — может, binder уже есть. if (Shizuku.pingBinder()) { Log.i(TAG, "attach: pingBinder сразу ok") refresh() } else { Log.w(TAG, "attach: pingBinder=false, ждём binder от ShizukuProvider") _state.value = AllowState.Waiting } } /** Перечитывает состояние и (если нужно) запрашивает permission. */ fun refresh() { val running = try { Shizuku.pingBinder() } catch (t: Throwable) { false } if (!running) { Log.w(TAG, "refresh: Shizuku не запущен") _state.value = AllowState.NotRunning return } val perm = try { Shizuku.checkSelfPermission() } catch (t: Throwable) { Log.w(TAG, "checkSelfPermission failed", t) -1 } if (perm == 0 /* PERMISSION_GRANTED */) { Log.i(TAG, "refresh: granted") _state.value = AllowState.Granted } else { Log.i(TAG, "refresh: needs permission (perm=$perm)") if (_state.value != AllowState.NeedsPermission) { _state.value = AllowState.NeedsPermission try { Shizuku.requestPermission(REQUEST_CODE_PERMISSION) } catch (t: Throwable) { Log.w(TAG, "requestPermission failed", t) } } } } fun isGranted(): Boolean = _state.value == AllowState.Granted /** Снимок состояния без побочных эффектов (не показывает диалог разрешения). */ data class Snapshot(val running: Boolean, val granted: Boolean) /** * Пассивная проверка Shizuku: жив ли сервер и выдан ли нам permission. * * Зовётся из сервиса (не из Activity), поэтому не имеет права запрашивать разрешение — * только читает. Нужна, чтобы телефон узнал, поднимать ли Shizuku через ADB. */ fun probe(): Snapshot { val running = try { Shizuku.pingBinder() } catch (_: Throwable) { false } val granted = if (!running) false else try { Shizuku.checkSelfPermission() == 0 /* PERMISSION_GRANTED */ } catch (_: Throwable) { false } return Snapshot(running = running, granted = granted) } /** * Хук для Activity.onRequestPermissionResult: перенаправить в Shizuku. * * override fun onRequestPermissionsResult(rc: Int, p: Array, g: IntArray) { * ... * ShizukuBootstrap.onRequestPermissionResult(rc, g[0]) * } */ fun onRequestPermissionResult(requestCode: Int, grantResult: Int) { // Shizuku.addRequestPermissionResultListener вызывается автоматически внутри Shizuku, // здесь делать ничего не надо. Метод оставлен как no-op для удобства и обратной совместимости. } }