fix: 自动申请权限 + 图标消失 + 授权后自动切换

权限流程:
- 启动时自动弹出系统授权对话框(不再需要用户手动点击)
- 通过 SideEffect 桥接 Compose state,授权后自动切换到主界面
- 授权等待中显示加载指示器

图标修复:
- 合并两个 intent-filter 为一个(LAUNCHER + LEANBACK_LAUNCHER)
- 移除 singleTask launchMode 避免启动器图标异常

MainActivity 简化:
- 移除 recreate() 循环触发
- 移除多余的回调闭包
This commit is contained in:
2026-05-31 10:28:48 +08:00
parent 461dc799ef
commit 619709dd1d
2 changed files with 55 additions and 110 deletions

View File

@@ -47,16 +47,12 @@
android:name=".ui.MainActivity"
android:exported="true"
android:configChanges="orientation|screenSize|screenLayout|keyboardHidden"
android:banner="@drawable/tv_banner"
android:launchMode="singleTask">
android:banner="@drawable/tv_banner">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
</intent-filter>
</activity>

View File

@@ -8,7 +8,6 @@ import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
import android.os.IBinder
import android.util.Log
import android.view.SurfaceView
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
@@ -30,50 +29,41 @@ import com.qwenpaw.miracast.ui.theme.*
class MainActivity : ComponentActivity() {
companion object {
private const val TAG = "MainActivity"
}
private var service: AirDisplayService? = null
private var serviceBound = false
private var surfaceView: SurfaceView? = null
// Compose state 引用,供 Activity 层写入
private var onPermissionGranted: (() -> Unit)? = null
private val serviceConnection = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName?, binder: IBinder?) {
service = (binder as AirDisplayService.LocalBinder).getService()
serviceBound = true
surfaceView?.let { service?.setSurface(it) }
Log.i(TAG, "服务已绑定")
}
override fun onServiceDisconnected(name: ComponentName?) {
serviceBound = false
service = null
serviceBound = false; service = null
}
}
private var surfaceView: SurfaceView? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val hasPerm = checkPermissions()
val alreadyGranted = checkPermissions()
setContent {
var permGranted by remember { mutableStateOf(hasPerm) }
var permGranted by remember { mutableStateOf(alreadyGranted) }
var serviceStarted by remember { mutableStateOf(false) }
val darkColors = darkColorScheme(
primary = Primary,
onPrimary = OnPrimary,
secondary = Secondary,
background = Background,
surface = Surface,
surfaceVariant = SurfaceVariant,
onSurface = OnSurface,
onSurfaceVariant = OnSurfaceVariant,
error = Error
)
// 暴露 setter 给 Activity 层
SideEffect { onPermissionGranted = { permGranted = true } }
MaterialTheme(colorScheme = darkColors) {
MaterialTheme(colorScheme = darkColorScheme(
primary = Primary, onPrimary = OnPrimary, secondary = Secondary,
background = Background, surface = Surface, surfaceVariant = SurfaceVariant,
onSurface = OnSurface, onSurfaceVariant = OnSurfaceVariant, error = Error
)) {
Box(modifier = Modifier.fillMaxSize()) {
AndroidView(
factory = { ctx -> SurfaceView(ctx).also { surfaceView = it } },
@@ -83,36 +73,29 @@ class MainActivity : ComponentActivity() {
if (permGranted) {
AppContent(
serviceStarted = serviceStarted,
onServiceStarted = {
bindToService()
startService()
serviceStarted = true
}
onStart = { bindAndStart(); serviceStarted = true }
)
} else {
PermissionScreen(
onRequestPermissions = {
requestPermission { permGranted = true }
}
)
PermissionScreen()
}
}
}
}
// 首次启动自动弹出系统授权框
if (!alreadyGranted) {
permLauncher.launch(getRequiredPermission())
}
}
@Composable
private fun AppContent(serviceStarted: Boolean, onServiceStarted: () -> Unit) {
LaunchedEffect(Unit) {
if (!serviceStarted) {
onServiceStarted()
}
}
private fun AppContent(serviceStarted: Boolean, onStart: () -> Unit) {
LaunchedEffect(Unit) { if (!serviceStarted) onStart() }
var currentScreen by remember { mutableStateOf("main") }
val defaultState = AirDisplayManager.UiState()
val state by service?.manager?.uiState?.collectAsStateWithLifecycle(defaultState)
?: remember { mutableStateOf(defaultState) }
val def = AirDisplayManager.UiState()
val state by service?.manager?.uiState?.collectAsStateWithLifecycle(def)
?: remember { mutableStateOf(def) }
when (currentScreen) {
"settings" -> {
@@ -125,11 +108,8 @@ class MainActivity : ComponentActivity() {
else -> MainScreen(
uiState = state,
onStartStop = {
if (state.serviceState is AirDisplayManager.ServiceState.Idle) {
startService()
} else {
stopService()
}
if (state.serviceState is AirDisplayManager.ServiceState.Idle) startSvc()
else stopSvc()
},
onNavigateToSettings = { currentScreen = "settings" }
)
@@ -138,96 +118,65 @@ class MainActivity : ComponentActivity() {
// ── 权限 ──
private fun checkPermissions(): Boolean {
val perm = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
private fun getRequiredPermission() =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU)
Manifest.permission.NEARBY_WIFI_DEVICES
} else {
else
Manifest.permission.ACCESS_FINE_LOCATION
}
return ContextCompat.checkSelfPermission(this, perm) == PackageManager.PERMISSION_GRANTED
}
private fun requestPermission(onGranted: () -> Unit) {
val perm = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
Manifest.permission.NEARBY_WIFI_DEVICES
} else {
Manifest.permission.ACCESS_FINE_LOCATION
}
permLauncher.launch(perm)
// 存储回调,等下在 launcher 结果里调用
pendingOnGranted = onGranted
}
private var pendingOnGranted: (() -> Unit)? = null
private fun checkPermissions() =
ContextCompat.checkSelfPermission(this, getRequiredPermission()) == PackageManager.PERMISSION_GRANTED
private val permLauncher = registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { granted ->
if (granted || Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
pendingOnGranted?.invoke()
onPermissionGranted?.invoke()
}
}
// ── 服务 ──
private fun bindToService() {
val intent = Intent(this, AirDisplayService::class.java)
bindService(intent, serviceConnection, BIND_AUTO_CREATE)
private fun bindAndStart() {
bindService(Intent(this, AirDisplayService::class.java), serviceConnection, BIND_AUTO_CREATE)
startSvc()
}
private fun startService() {
val intent = Intent(this, AirDisplayService::class.java).apply {
action = AirDisplayService.ACTION_START
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
startForegroundService(intent)
} else {
startService(intent)
}
private fun startSvc() {
val i = Intent(this, AirDisplayService::class.java).apply { action = AirDisplayService.ACTION_START }
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) startForegroundService(i) else startService(i)
}
private fun stopService() {
val intent = Intent(this, AirDisplayService::class.java).apply {
action = AirDisplayService.ACTION_STOP
}
startService(intent)
private fun stopSvc() {
startService(Intent(this, AirDisplayService::class.java).apply { action = AirDisplayService.ACTION_STOP })
}
override fun onDestroy() {
if (serviceBound) {
unbindService(serviceConnection)
serviceBound = false
}
if (serviceBound) { unbindService(serviceConnection); serviceBound = false }
super.onDestroy()
}
}
// ── 权限界面 ──
// ── 权限提示 ──
@Composable
private fun PermissionScreen(onRequestPermissions: () -> Unit) {
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
private fun PermissionScreen() {
Box(modifier = Modifier.fillMaxSize().padding(32.dp), contentAlignment = Alignment.Center) {
Card(colors = CardDefaults.cardColors(containerColor = Surface)) {
Column(
modifier = Modifier.padding(32.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
"需要 Wi-Fi Direct 权限",
fontSize = 20.sp,
fontWeight = FontWeight.Bold,
color = OnSurface
)
Text("需要授权", fontSize = 20.sp, fontWeight = FontWeight.Bold, color = OnSurface)
Spacer(Modifier.height(12.dp))
Text(
"AirDisplay 需要邻近设备/位置权限才能进行 Wi-Fi Direct 无线投屏。",
color = OnSurfaceVariant,
fontSize = 14.sp
"系统已弹出授权对话框,请选择「允许」以启用 Wi-Fi Direct 投屏功能",
color = OnSurfaceVariant, fontSize = 14.sp
)
Spacer(Modifier.height(24.dp))
Button(onClick = onRequestPermissions) {
Text("授予权限")
}
Spacer(Modifier.height(16.dp))
CircularProgressIndicator(modifier = Modifier.size(24.dp), strokeWidth = 2.dp)
Spacer(Modifier.height(8.dp))
Text("等待授权…", color = OnSurfaceVariant, fontSize = 12.sp)
}
}
}