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 для удобства и обратной совместимости.
+ }
+}