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:name=".ui.MainActivity"
android:exported="true" android:exported="true"
android:configChanges="orientation|screenSize|screenLayout|keyboardHidden" android:configChanges="orientation|screenSize|screenLayout|keyboardHidden"
android:banner="@drawable/tv_banner" android:banner="@drawable/tv_banner">
android:launchMode="singleTask">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
</intent-filter>
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
</intent-filter> </intent-filter>
</activity> </activity>

View File

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