Files
rayneo-vm/app-glasses-vm/src/main/kotlin/pw/binom/rayneovm/glasses/ShizukuBootstrap.kt
T
subochev 8a0b7832c0 phone: autonomous Shizuku revive over wireless-adb; glasses screen on/off buttons
AdbClient wraps the bundled libadb.so (useLegacyPackaging -> exec from nativeLibraryDir, HOME/TMPDIR overridden, adb key injected into BuildConfig from ~/.android at build time and never committed). ShizukuProvisioner: on each BLE+WiFi connect it enables ADB-over-WiFi via Mercury, connects with mDNS fallback, pushes the Shizuku starter and runs it; runs once per BLE session, does not relaunch GlassesApp when the WS channel is already up, and exposes runShell() for arbitrary commands. DebugReceiver (DUMP) + am start --ez provision for diagnostics.

Protocol: ShizukuStatus(running/granted) + RequestShizukuStatus; glasses push it on connect and on Shizuku state change, using a passive probe() that never shows a permission dialog. Glasses kill-guard now recreates its UserService when the Shizuku binder dies instead of spamming DeadObjectException. Mercury: brightness disables auto-brightness first (was silently ignored), BLE auto-connect loop retries every 6s (fixes 'searches then stops'). New phone section 'Экран очков' with wake/sleep via adb (input keyevent 224/223).
2026-09-27 03:52:05 +03:00

145 lines
6.3 KiB
Kotlin
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
/** Снимок состояния без побочных эффектов (не показывает диалог разрешения). */
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<String>, g: IntArray) {
* ...
* ShizukuBootstrap.onRequestPermissionResult(rc, g[0])
* }
*/
fun onRequestPermissionResult(requestCode: Int, grantResult: Int) {
// Shizuku.addRequestPermissionResultListener вызывается автоматически внутри Shizuku,
// здесь делать ничего не надо. Метод оставлен как no-op для удобства и обратной совместимости.
}
}