Files
rayneo-vm/app-glasses-vm/src/main/kotlin/pw/binom/rayneovm/glasses/MainActivity.kt
T
subochev 03f1d7dcc7 glasses: launch guest on Shizuku VirtualDisplay + binocular render
- VmShizukuService is a Shizuku UserService only; remove its <service> manifest entry (ServiceStarter instantiates it reflectively, an Android service entry causes ClassCastException)
- VirtualDisplayFrameSource: await binder via ShizukuVdClient.bindForResult(5s), expose virtualDisplayId + shizukuBinder, stop the UserService on release
- VmGlassesController: serialize acquire via Mutex, use double-buffered SurfaceTexture (DisplayManagerService rejects single-buffer), launch the guest on the VD with ShizukuVdBinder.launchActivity
- GlassesScreen: render controller.frameSource (Running) directly; drop the LaunchedEffect acquire
- DebugTestReceiver: register at runtime from MainActivity (background manifest receivers are blocked on Android 12+)
2026-09-25 17:19:49 +03:00

93 lines
3.6 KiB
Kotlin

package pw.binom.rayneovm.glasses
import android.content.Intent
import android.content.IntentFilter
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.background
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
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
ShizukuBootstrap.attach(this)
val controller = VmGlassesController.fromContext(this)
setContent {
CompositionLocalProvider(VmGlassesController.Local provides controller) {
MaterialTheme(colorScheme = darkColorScheme()) {
Box(
modifier = Modifier
.fillMaxSize()
.background(Color.Black)
) {
val shizukuState by ShizukuBootstrap.state.collectAsState()
DriveShizukuState(controller, shizukuState)
GlassesScreen(
controller = controller,
onRequestShizukuPermission = { ShizukuBootstrap.refresh() },
)
}
}
}
}
}
override fun onResume() {
super.onResume()
ShizukuBootstrap.refresh()
}
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 -> {
if (controller.state.value !is GlassesScreenState.Running) {
controller.setState(GlassesScreenState.ShizukuMissing)
}
}
ShizukuBootstrap.AllowState.Granted -> {
// Если мы сейчас в ShizukuMissing — переходим в NoAppSelected.
// Running не трогаем: приложение уже запущено, Shizuku-статус не должен его ронять.
if (controller.state.value is GlassesScreenState.ShizukuMissing) {
controller.setState(GlassesScreenState.NoAppSelected)
}
}
}
}
}