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:
@@ -64,6 +64,7 @@ dependencies {
|
|||||||
implementation(libs.androidx.navigation.compose)
|
implementation(libs.androidx.navigation.compose)
|
||||||
|
|
||||||
implementation(libs.shizuku.api)
|
implementation(libs.shizuku.api)
|
||||||
|
implementation(libs.shizuku.provider)
|
||||||
implementation(libs.mercury.library)
|
implementation(libs.mercury.library)
|
||||||
implementation(libs.mercury.transport)
|
implementation(libs.mercury.transport)
|
||||||
implementation(libs.mercury.binocular)
|
implementation(libs.mercury.binocular)
|
||||||
|
|||||||
@@ -12,7 +12,10 @@
|
|||||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
<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
|
<uses-feature
|
||||||
android:glEsVersion="0x00020000"
|
android:glEsVersion="0x00020000"
|
||||||
@@ -21,6 +24,16 @@
|
|||||||
android:name="android.hardware.type.embedded"
|
android:name="android.hardware.type.embedded"
|
||||||
android:required="true" />
|
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
|
<application
|
||||||
android:name=".GlassesVmApp"
|
android:name=".GlassesVmApp"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
@@ -52,6 +65,18 @@
|
|||||||
android:name=".VmShizukuService"
|
android:name=".VmShizukuService"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:process=":shizuku_vd" />
|
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" />
|
<meta-data android:name="com.rayneo.mercury.app" android:value="true" />
|
||||||
</application>
|
</application>
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|||||||
@@ -10,6 +10,9 @@ import pw.binom.rayneovm.glasses.protocol.VmGlassesController
|
|||||||
*
|
*
|
||||||
* Запускает [VmService] при создании (foreground-сервис держит WebSocket сервер + mDNS,
|
* Запускает [VmService] при создании (foreground-сервис держит WebSocket сервер + mDNS,
|
||||||
* и продолжает работать после сворачивания Activity).
|
* и продолжает работать после сворачивания Activity).
|
||||||
|
*
|
||||||
|
* Также стартует [ShizukuBootstrap] — он регистрирует binder listener, ждёт/запрашивает
|
||||||
|
* permission и обновляет state. MainActivity потом подпишется на этот state.
|
||||||
*/
|
*/
|
||||||
class GlassesVmApp : Application(), ControllerProvider {
|
class GlassesVmApp : Application(), ControllerProvider {
|
||||||
|
|
||||||
@@ -18,11 +21,15 @@ class GlassesVmApp : Application(), ControllerProvider {
|
|||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
Log.i(TAG, "GlassesVmApp onCreate, uid=${android.os.Process.myUid()}")
|
Log.i(TAG, "GlassesVmApp onCreate, uid=${android.os.Process.myUid()}")
|
||||||
// Инициализация RayNeo Mercury SDK.
|
|
||||||
com.ffalcon.mercury.android.sdk.MercurySDK.init(this)
|
// 1. RayNeo Mercury SDK init (BLE-канал, если нужно)
|
||||||
Log.i(TAG, "MercurySDK initialized")
|
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)
|
controller.setAppContext(this)
|
||||||
// Регистрируем debug-receiver для тестирования через adb.
|
|
||||||
|
// 2. Регистрируем debug-receiver для тестирования через adb
|
||||||
val filter = android.content.IntentFilter(pw.binom.rayneovm.glasses.protocol.DebugTestReceiver.ACTION)
|
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) {
|
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)
|
registerReceiver(pw.binom.rayneovm.glasses.protocol.DebugTestReceiver(), filter, android.content.Context.RECEIVER_EXPORTED)
|
||||||
@@ -30,6 +37,11 @@ class GlassesVmApp : Application(), ControllerProvider {
|
|||||||
@Suppress("UnspecifiedRegisterReceiverFlag")
|
@Suppress("UnspecifiedRegisterReceiverFlag")
|
||||||
registerReceiver(pw.binom.rayneovm.glasses.protocol.DebugTestReceiver(), filter)
|
registerReceiver(pw.binom.rayneovm.glasses.protocol.DebugTestReceiver(), filter)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 3. Shizuku bootstrap здесь не зовём — он вызывается из MainActivity.onCreate,
|
||||||
|
// где есть Activity-контекст (для requestPermission flow).
|
||||||
|
|
||||||
|
// 4. Запуск foreground-сервиса (WebSocket + mDNS publisher)
|
||||||
VmService.start(this)
|
VmService.start(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,23 +9,30 @@ import androidx.compose.foundation.layout.Box
|
|||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.darkColorScheme
|
import androidx.compose.material3.darkColorScheme
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
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.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import pw.binom.rayneovm.glasses.protocol.VmGlassesController
|
import pw.binom.rayneovm.glasses.protocol.VmGlassesController
|
||||||
import pw.binom.rayneovm.glasses.ui.GlassesScreen
|
import pw.binom.rayneovm.glasses.ui.GlassesScreen
|
||||||
|
import pw.binom.rayneovm.glasses.vm.GlassesScreenState
|
||||||
|
|
||||||
/**
|
|
||||||
* Single-Activity приложения: Compose host для GlassesScreen.
|
|
||||||
*
|
|
||||||
* Никакой навигации — здесь один экран (зеркало гостевого приложения + state overlay).
|
|
||||||
*/
|
|
||||||
class MainActivity : ComponentActivity() {
|
class MainActivity : ComponentActivity() {
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
enableEdgeToEdge()
|
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)
|
val controller = VmGlassesController.fromContext(this)
|
||||||
|
|
||||||
setContent {
|
setContent {
|
||||||
CompositionLocalProvider(VmGlassesController.Local provides controller) {
|
CompositionLocalProvider(VmGlassesController.Local provides controller) {
|
||||||
MaterialTheme(colorScheme = darkColorScheme()) {
|
MaterialTheme(colorScheme = darkColorScheme()) {
|
||||||
@@ -34,10 +41,61 @@ class MainActivity : ComponentActivity() {
|
|||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.background(Color.Black)
|
.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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ lifecycle = "2.11.0"
|
|||||||
activityCompose = "1.11.0"
|
activityCompose = "1.11.0"
|
||||||
composeBom = "2026.09.00"
|
composeBom = "2026.09.00"
|
||||||
shizukuApi = "13.1.5"
|
shizukuApi = "13.1.5"
|
||||||
|
shizukuProvider = "13.1.5"
|
||||||
navCompose = "2.10.2"
|
navCompose = "2.10.2"
|
||||||
mercury = "0.1.0-SNAPSHOT"
|
mercury = "0.1.0-SNAPSHOT"
|
||||||
mercuryTransport = "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
|
||||||
shizuku-api = { module = "dev.rikka.shizuku:api", version.ref = "shizukuApi" }
|
shizuku-api = { module = "dev.rikka.shizuku:api", version.ref = "shizukuApi" }
|
||||||
|
shizuku-provider = { module = "dev.rikka.shizuku:provider", version.ref = "shizukuProvider" }
|
||||||
|
|
||||||
# rayneo-mercury (библиотеки)
|
# rayneo-mercury (библиотеки)
|
||||||
mercury-library = { module = "pw.binom.mercury:rayneo-mercury", version.ref = "mercury" }
|
mercury-library = { module = "pw.binom.mercury:rayneo-mercury", version.ref = "mercury" }
|
||||||
|
|||||||
Reference in New Issue
Block a user