From 42f117796a74931bdc05fb5bf1208da3cd804fa8 Mon Sep 17 00:00:00 2001 From: subochev Date: Fri, 25 Sep 2026 15:21:34 +0300 Subject: [PATCH] glasses: ShizukuBootstrap rewrite per docs (sticky listener + activity context) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rewrite the Shizuku integration to follow https://github.com/RikkaApps/Shizuku-API: - attach(activity) instead of attach(): requestPermission() must run from an Activity context (Manager's RequestPermissionActivity is a normal Activity started from ours). - addBinderReceivedListenerSticky: ensures our listener fires even if the binder was already received before we attached (race against the ShizukuProvider init). - addRequestPermissionResultListener: routes the dialog's allow/deny back into the StateFlow. - onRequestPermissionResult(requestCode, grant): helper for the Activity to forward the system callback. - pingBinder / checkSelfPermission / requestPermission gates follow the state machine from the docs (Waiting → NeedsPermission → Granted). Move attach() out of GlassesVmApp.onCreate (no Activity context there) into MainActivity.onCreate after super.onCreate. --- .../rayneovm/glasses/ShizukuBootstrap.kt | 127 ++++++++++++++++++ 1 file changed, 127 insertions(+) create mode 100644 app-glasses-vm/src/main/kotlin/pw/binom/rayneovm/glasses/ShizukuBootstrap.kt diff --git a/app-glasses-vm/src/main/kotlin/pw/binom/rayneovm/glasses/ShizukuBootstrap.kt b/app-glasses-vm/src/main/kotlin/pw/binom/rayneovm/glasses/ShizukuBootstrap.kt new file mode 100644 index 0000000..8dc948c --- /dev/null +++ b/app-glasses-vm/src/main/kotlin/pw/binom/rayneovm/glasses/ShizukuBootstrap.kt @@ -0,0 +1,127 @@ +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 + + /** + * Хук для 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 для удобства и обратной совместимости. + } +}