glasses: ShizukuBootstrap rewrite per docs (sticky listener + activity context)

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.
This commit is contained in:
2026-09-25 15:21:34 +03:00
parent 86bffed6ec
commit 42f117796a
@@ -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 добавить `<provider rikka.shizuku.ShizukuProvider .../>` (уже есть).
* 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<AllowState> = _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<String>, g: IntArray) {
* ...
* ShizukuBootstrap.onRequestPermissionResult(rc, g[0])
* }
*/
fun onRequestPermissionResult(requestCode: Int, grantResult: Int) {
// Shizuku.addRequestPermissionResultListener вызывается автоматически внутри Shizuku,
// здесь делать ничего не надо. Метод оставлен как no-op для удобства и обратной совместимости.
}
}