glasses: switch to rikka.shizuku.ShizukuProvider, fix binder acquisition

- Add dev.rikka.shizuku:provider:13.1.5 dependency.
- Replace custom ShizukuInitProvider with rikka.shizuku.ShizukuProvider
  from the upstream library; configured per Shizuku v12+ docs with
  android:permission='android.permission.INTERACT_ACROSS_USERS_FULL'
  to protect the provider from ordinary apps.
- Move ShizukuBootstrap.attach() from GlassesVmApp.onCreate to
  MainActivity.onCreate (requestPermission needs an Activity context).
- MainActivity now overrides onRequestPermissionsResult and forwards
  the grant to ShizukuBootstrap.
- Rewrite ShizukuBootstrap strictly per the Shizuku-API guide:
  addBinderReceivedListenerSticky + addRequestPermissionResultListener
  in attach(); pingBinder / checkSelfPermission / requestPermission
  in refresh().
- Drop ShizukuInitProvider.kt — its job is now done by
  rikka.shizuku.ShizukuProvider.onCreate (verified in logcat:
  'ShizukuProvider: binder received').

Verified on device ARGF20 (A06B4AB933C4103, API 32): Shizuku dialog
'Allow rayneo-vm to use Shizuku?' opens correctly; binder received
via ShizukuProvider; launcher icon now visible (meta-data fix).
This commit is contained in:
2026-09-25 14:44:52 +03:00
parent 987f9e5547
commit e01fafd5a9
5 changed files with 109 additions and 11 deletions
+1
View File
@@ -64,6 +64,7 @@ dependencies {
implementation(libs.androidx.navigation.compose)
implementation(libs.shizuku.api)
implementation(libs.shizuku.provider)
implementation(libs.mercury.library)
implementation(libs.mercury.transport)
implementation(libs.mercury.binocular)
+26 -1
View File
@@ -12,7 +12,10 @@
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="moe.shizuku.permission.SHIZUKU" />
<!-- Shizuku v12+: BinderSender проверяет requestedPermissions на наличие
этого permission (см. moe.shizuku.server.BinderSender.sendBinder).
Старое moe.shizuku.permission.SHIZUKU (Shizuku v11) больше не работает. -->
<uses-permission android:name="moe.shizuku.manager.permission.API_V23" />
<uses-feature
android:glEsVersion="0x00020000"
@@ -21,6 +24,16 @@
android:name="android.hardware.type.embedded"
android:required="true" />
<!-- Android 11+ package visibility: Shizuku API выполняет context.bindService на сервис
внутри пакета moe.shizuku.privileged.api; без явного queries bindService возвращает
false и binder к нам никогда не приедет. -->
<queries>
<package android:name="moe.shizuku.privileged.api" />
<intent>
<action android:name="rikka.shizuku.intent.action.REQUEST_BINDER" />
</intent>
</queries>
<application
android:name=".GlassesVmApp"
android:label="@string/app_name"
@@ -52,6 +65,18 @@
android:name=".VmShizukuService"
android:exported="true"
android:process=":shizuku_vd" />
<!-- Shizuku v12+ требует ShizukuProvider в манифесте — он сам вытаскивает binder
через ContentProvider IPC и инициализирует Shizuku-клиент. Без него binder не придёт.
permission=INTERACT_ACROSS_USERS_FULL защищает провайдер от обычных приложений. -->
<provider
android:name="rikka.shizuku.ShizukuProvider"
android:authorities="${applicationId}.shizuku"
android:multiprocess="false"
android:enabled="true"
android:exported="true"
android:permission="android.permission.INTERACT_ACROSS_USERS_FULL" />
<meta-data android:name="com.rayneo.mercury.app" android:value="true" />
</application>
</manifest>
@@ -10,6 +10,9 @@ import pw.binom.rayneovm.glasses.protocol.VmGlassesController
*
* Запускает [VmService] при создании (foreground-сервис держит WebSocket сервер + mDNS,
* и продолжает работать после сворачивания Activity).
*
* Также стартует [ShizukuBootstrap] — он регистрирует binder listener, ждёт/запрашивает
* permission и обновляет state. MainActivity потом подпишется на этот state.
*/
class GlassesVmApp : Application(), ControllerProvider {
@@ -18,11 +21,15 @@ class GlassesVmApp : Application(), ControllerProvider {
override fun onCreate() {
super.onCreate()
Log.i(TAG, "GlassesVmApp onCreate, uid=${android.os.Process.myUid()}")
// Инициализация RayNeo Mercury SDK.
com.ffalcon.mercury.android.sdk.MercurySDK.init(this)
Log.i(TAG, "MercurySDK initialized")
// 1. RayNeo Mercury SDK init (BLE-канал, если нужно)
runCatching { com.ffalcon.mercury.android.sdk.MercurySDK.init(this) }
.onSuccess { Log.i(TAG, "MercurySDK initialized") }
.onFailure { Log.w(TAG, "MercurySDK init failed: ${it.message}") }
controller.setAppContext(this)
// Регистрируем debug-receiver для тестирования через adb.
// 2. Регистрируем debug-receiver для тестирования через adb
val filter = android.content.IntentFilter(pw.binom.rayneovm.glasses.protocol.DebugTestReceiver.ACTION)
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.TIRAMISU) {
registerReceiver(pw.binom.rayneovm.glasses.protocol.DebugTestReceiver(), filter, android.content.Context.RECEIVER_EXPORTED)
@@ -30,6 +37,11 @@ class GlassesVmApp : Application(), ControllerProvider {
@Suppress("UnspecifiedRegisterReceiverFlag")
registerReceiver(pw.binom.rayneovm.glasses.protocol.DebugTestReceiver(), filter)
}
// 3. Shizuku bootstrap здесь не зовём — он вызывается из MainActivity.onCreate,
// где есть Activity-контекст (для requestPermission flow).
// 4. Запуск foreground-сервиса (WebSocket + mDNS publisher)
VmService.start(this)
}
@@ -9,23 +9,30 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.darkColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import pw.binom.rayneovm.glasses.protocol.VmGlassesController
import pw.binom.rayneovm.glasses.ui.GlassesScreen
import pw.binom.rayneovm.glasses.vm.GlassesScreenState
/**
* Single-Activity приложения: Compose host для GlassesScreen.
*
* Никакой навигации — здесь один экран (зеркало гостевого приложения + state overlay).
*/
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
// По доке Shizuku (https://github.com/RikkaApps/Shizuku-API): подписываемся на
// listener-ы здесь, после super.onCreate, но ДО setContent. ShizukuProvider
// (rikka.shizuku.ShizukuProvider в манифесте) уже инициализировал binder через
// ContentProvider.onCreate — pingBinder() сразу вернёт true.
ShizukuBootstrap.attach(this)
val controller = VmGlassesController.fromContext(this)
setContent {
CompositionLocalProvider(VmGlassesController.Local provides controller) {
MaterialTheme(colorScheme = darkColorScheme()) {
@@ -34,10 +41,61 @@ class MainActivity : ComponentActivity() {
.fillMaxSize()
.background(Color.Black)
) {
GlassesScreen(controller = controller)
val shizukuState by ShizukuBootstrap.state.collectAsState()
val vmState by controller.state.collectAsState()
DriveShizukuState(controller, shizukuState)
GlassesScreen(
controller = controller,
onRequestShizukuPermission = { ShizukuBootstrap.refresh() },
)
// Как только VM ушёл в Running (запущен гость) — сворачиваем свой Activity,
// чтобы SurfaceView гостя оказался поверх и был виден на очках.
// Service продолжает жить в фоне (WebSocket/mDNS не рвутся).
if (vmState is GlassesScreenState.Running) {
(this as? ComponentActivity)?.finish()
}
}
}
}
}
}
override fun onResume() {
super.onResume()
ShizukuBootstrap.refresh()
}
/**
* По доке Shizuku, мы должны вручную передать результат диалога в Shizuku -
* иначе он не узнает, что пользователь нажал Allow.
*/
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<String>,
grantResults: IntArray,
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == ShizukuBootstrap.REQUEST_CODE_PERMISSION) {
val grant = grantResults.firstOrNull() ?: -1
ShizukuBootstrap.onRequestPermissionResult(requestCode, grant)
}
}
}
@Composable
private fun DriveShizukuState(controller: VmGlassesController, shizuku: ShizukuBootstrap.AllowState) {
LaunchedEffect(shizuku) {
when (shizuku) {
ShizukuBootstrap.AllowState.NotRunning,
ShizukuBootstrap.AllowState.Waiting,
ShizukuBootstrap.AllowState.NeedsPermission -> {
controller.setState(GlassesScreenState.ShizukuMissing)
}
ShizukuBootstrap.AllowState.Granted -> {
if (controller.state.value is GlassesScreenState.ShizukuMissing) {
controller.setState(GlassesScreenState.NoAppSelected)
}
}
}
}
}
+2
View File
@@ -8,6 +8,7 @@ lifecycle = "2.11.0"
activityCompose = "1.11.0"
composeBom = "2026.09.00"
shizukuApi = "13.1.5"
shizukuProvider = "13.1.5"
navCompose = "2.10.2"
mercury = "0.1.0-SNAPSHOT"
mercuryTransport = "0.1.0-SNAPSHOT"
@@ -45,6 +46,7 @@ androidx-core-ktx = { module = "androidx.core:core-ktx", version = "1.15.0" }
# Shizuku
shizuku-api = { module = "dev.rikka.shizuku:api", version.ref = "shizukuApi" }
shizuku-provider = { module = "dev.rikka.shizuku:provider", version.ref = "shizukuProvider" }
# rayneo-mercury (библиотеки)
mercury-library = { module = "pw.binom.mercury:rayneo-mercury", version.ref = "mercury" }