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:
@@ -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 для удобства и обратной совместимости.
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user