| | |
| | | package com.safeluck.floatwindow |
| | | |
| | | import android.content.ComponentName |
| | | import android.content.Context |
| | | import android.content.Intent |
| | | import android.content.ServiceConnection |
| | | import android.os.Bundle |
| | | import android.os.IBinder |
| | | import android.os.RemoteException |
| | | import androidx.activity.ComponentActivity |
| | | import androidx.activity.compose.setContent |
| | | import androidx.activity.enableEdgeToEdge |
| | | import androidx.compose.foundation.layout.fillMaxSize |
| | | import androidx.compose.foundation.layout.padding |
| | | import androidx.compose.material3.Scaffold |
| | | import androidx.compose.material3.Text |
| | | import androidx.compose.runtime.Composable |
| | | import androidx.compose.foundation.layout.* |
| | | import androidx.compose.material3.* |
| | | import androidx.compose.runtime.mutableStateOf |
| | | import androidx.compose.runtime.* |
| | | import androidx.compose.ui.Alignment |
| | | import androidx.compose.ui.Modifier |
| | | import androidx.compose.ui.tooling.preview.Preview |
| | | import androidx.compose.ui.unit.dp |
| | | import com.safeluck.floatwindow.ui.theme.AnyunVideoTheme |
| | | import timber.log.Timber |
| | | |
| | | class MainActivity : ComponentActivity() { |
| | | private var mediaAidlInterface: IMediaAidlInterface? = null |
| | | |
| | | private val callback = object : IMyCallback.Stub() { |
| | | @Throws(RemoteException::class) |
| | | override fun onResult(re: ResponseVO?) { |
| | | re?.let { |
| | | Timber.d("Callback received: type=${it.type}, errCode=${it.errCode}, message=${it.message}") |
| | | } |
| | | } |
| | | } |
| | | |
| | | override fun onCreate(savedInstanceState: Bundle?) { |
| | | super.onCreate(savedInstanceState) |
| | | enableEdgeToEdge() |
| | | |
| | | var isServiceBoundState by mutableStateOf(false) |
| | | |
| | | val serviceConnection = object : ServiceConnection { |
| | | override fun onServiceConnected(name: ComponentName?, service: IBinder?) { |
| | | mediaAidlInterface = IMediaAidlInterface.Stub.asInterface(service) |
| | | isServiceBoundState = true |
| | | Timber.d("FloatingService connected") |
| | | } |
| | | |
| | | override fun onServiceDisconnected(name: ComponentName?) { |
| | | mediaAidlInterface = null |
| | | isServiceBoundState = false |
| | | Timber.d("FloatingService disconnected") |
| | | } |
| | | } |
| | | |
| | | setContent { |
| | | AnyunVideoTheme { |
| | | Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding -> |
| | | Greeting( |
| | | name = "Android", |
| | | modifier = Modifier.padding(innerPadding) |
| | | Surface( |
| | | modifier = Modifier.fillMaxSize(), |
| | | color = MaterialTheme.colorScheme.background |
| | | ) { |
| | | MainScreen( |
| | | isServiceBound = isServiceBoundState, |
| | | onBindService = { |
| | | if (!isServiceBoundState) { |
| | | val intent = Intent(this@MainActivity, FloatingService::class.java) |
| | | bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE) |
| | | Timber.d("Binding FloatingService") |
| | | } |
| | | }, |
| | | onUnbindService = { |
| | | if (isServiceBoundState) { |
| | | try { |
| | | mediaAidlInterface?.unregisterCallback(callback) |
| | | } catch (e: RemoteException) { |
| | | Timber.e(e, "Error unregistering callback") |
| | | } |
| | | unbindService(serviceConnection) |
| | | isServiceBoundState = false |
| | | mediaAidlInterface = null |
| | | Timber.d("Unbinding FloatingService") |
| | | } |
| | | }, |
| | | onStartAndroidRecord = { startAndroidRecord() }, |
| | | onStopAndroidRecord = { stopAndroidRecord() }, |
| | | onStartUsbRecord = { startUsbRecord() }, |
| | | onStopUsbRecord = { stopUsbRecord() }, |
| | | onStartUsbPush = { startUsbPush() }, |
| | | onStopUsbPush = { stopUsbPush() } |
| | | ) |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | private fun startAndroidRecord() { |
| | | if (mediaAidlInterface == null) { |
| | | Timber.w("Service not bound, cannot start Android record") |
| | | return |
| | | } |
| | | |
| | | try { |
| | | val mediaArgu = MediaArgu().apply { |
| | | isPush = false |
| | | isUsedOutCamera = false // Android 内置摄像头 |
| | | codeRate = 0 |
| | | frameRate = 0 |
| | | m_screen = MediaArgu.ScreenSolution(640, 480) // 默认分辨率 |
| | | recordTime = 0 |
| | | tfCardFlag = 0 // 内部存储 |
| | | } |
| | | |
| | | mediaAidlInterface?.registerCallback(callback) |
| | | mediaAidlInterface?.startMedia(mediaArgu) |
| | | Timber.d("Started Android camera record") |
| | | } catch (e: RemoteException) { |
| | | Timber.e(e, "Error starting Android record") |
| | | } |
| | | } |
| | | |
| | | private fun stopAndroidRecord() { |
| | | if (mediaAidlInterface == null) { |
| | | Timber.w("Service not bound, cannot stop Android record") |
| | | return |
| | | } |
| | | |
| | | try { |
| | | mediaAidlInterface?.stopMedia() |
| | | Timber.d("Stopped Android camera record") |
| | | } catch (e: RemoteException) { |
| | | Timber.e(e, "Error stopping Android record") |
| | | } |
| | | } |
| | | |
| | | private fun startUsbRecord() { |
| | | if (mediaAidlInterface == null) { |
| | | Timber.w("Service not bound, cannot start USB record") |
| | | return |
| | | } |
| | | |
| | | try { |
| | | val mediaArgu = MediaArgu().apply { |
| | | isPush = false |
| | | isUsedOutCamera = true // USB 摄像头 |
| | | codeRate = 0 |
| | | frameRate = 0 |
| | | m_screen = MediaArgu.ScreenSolution(640, 480) // 默认分辨率 |
| | | recordTime = 0 |
| | | tfCardFlag = 0 // 内部存储 |
| | | } |
| | | |
| | | mediaAidlInterface?.registerCallback(callback) |
| | | mediaAidlInterface?.startMedia(mediaArgu) |
| | | Timber.d("Started USB camera record") |
| | | } catch (e: RemoteException) { |
| | | Timber.e(e, "Error starting USB record") |
| | | } |
| | | } |
| | | |
| | | private fun stopUsbRecord() { |
| | | if (mediaAidlInterface == null) { |
| | | Timber.w("Service not bound, cannot stop USB record") |
| | | return |
| | | } |
| | | |
| | | try { |
| | | mediaAidlInterface?.stopMedia() |
| | | Timber.d("Stopped USB camera record") |
| | | } catch (e: RemoteException) { |
| | | Timber.e(e, "Error stopping USB record") |
| | | } |
| | | } |
| | | |
| | | private fun startUsbPush() { |
| | | if (mediaAidlInterface == null) { |
| | | Timber.w("Service not bound, cannot start USB push") |
| | | return |
| | | } |
| | | |
| | | try { |
| | | val mediaArgu = MediaArgu().apply { |
| | | isPush = true |
| | | isUsedOutCamera = true // USB 摄像头 |
| | | codeRate = 0 |
| | | frameRate = 0 |
| | | m_screen = MediaArgu.ScreenSolution(640, 480) // 默认分辨率 |
| | | url = "rtmp://your-push-url" // TODO: 需要设置实际的推流地址 |
| | | userName = "" |
| | | pwd = "" |
| | | } |
| | | |
| | | mediaAidlInterface?.registerCallback(callback) |
| | | mediaAidlInterface?.startMedia(mediaArgu) |
| | | Timber.d("Started USB camera push") |
| | | } catch (e: RemoteException) { |
| | | Timber.e(e, "Error starting USB push") |
| | | } |
| | | } |
| | | |
| | | private fun stopUsbPush() { |
| | | if (mediaAidlInterface == null) { |
| | | Timber.w("Service not bound, cannot stop USB push") |
| | | return |
| | | } |
| | | |
| | | try { |
| | | mediaAidlInterface?.stopMedia() |
| | | Timber.d("Stopped USB camera push") |
| | | } catch (e: RemoteException) { |
| | | Timber.e(e, "Error stopping USB push") |
| | | } |
| | | } |
| | | |
| | | override fun onDestroy() { |
| | | super.onDestroy() |
| | | if (mediaAidlInterface != null) { |
| | | try { |
| | | mediaAidlInterface?.unregisterCallback(callback) |
| | | } catch (e: RemoteException) { |
| | | Timber.e(e, "Error unregistering callback") |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | @Composable |
| | | fun Greeting(name: String, modifier: Modifier = Modifier) { |
| | | fun MainScreen( |
| | | isServiceBound: Boolean, |
| | | onBindService: () -> Unit, |
| | | onUnbindService: () -> Unit, |
| | | onStartAndroidRecord: () -> Unit, |
| | | onStopAndroidRecord: () -> Unit, |
| | | onStartUsbRecord: () -> Unit, |
| | | onStopUsbRecord: () -> Unit, |
| | | onStartUsbPush: () -> Unit, |
| | | onStopUsbPush: () -> Unit |
| | | ) { |
| | | Column( |
| | | modifier = Modifier |
| | | .fillMaxSize() |
| | | .padding(16.dp), |
| | | horizontalAlignment = Alignment.CenterHorizontally, |
| | | verticalArrangement = Arrangement.spacedBy(12.dp) |
| | | ) { |
| | | Text( |
| | | text = "Hello $name!", |
| | | modifier = modifier |
| | | text = "FloatingService 控制", |
| | | style = MaterialTheme.typography.headlineMedium, |
| | | modifier = Modifier.padding(bottom = 8.dp) |
| | | ) |
| | | |
| | | Text( |
| | | text = if (isServiceBound) "服务状态: 已绑定" else "服务状态: 未绑定", |
| | | style = MaterialTheme.typography.bodyMedium, |
| | | color = if (isServiceBound) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.error, |
| | | modifier = Modifier.padding(bottom = 16.dp) |
| | | ) |
| | | |
| | | // 按钮 1: 绑定服务 |
| | | Button( |
| | | onClick = onBindService, |
| | | modifier = Modifier.fillMaxWidth(), |
| | | enabled = !isServiceBound |
| | | ) { |
| | | Text("1. 绑定 FloatingService") |
| | | } |
| | | |
| | | @Preview(showBackground = true) |
| | | @Composable |
| | | fun GreetingPreview() { |
| | | AnyunVideoTheme { |
| | | Greeting("Android") |
| | | // 按钮 2: 解绑服务 |
| | | Button( |
| | | onClick = onUnbindService, |
| | | modifier = Modifier.fillMaxWidth(), |
| | | enabled = isServiceBound |
| | | ) { |
| | | Text("2. 解绑 FloatingService") |
| | | } |
| | | |
| | | Divider(modifier = Modifier.padding(vertical = 8.dp)) |
| | | |
| | | // 按钮 3: 开始 Android 相机录像 |
| | | Button( |
| | | onClick = onStartAndroidRecord, |
| | | modifier = Modifier.fillMaxWidth(), |
| | | enabled = isServiceBound |
| | | ) { |
| | | Text("3. 开始 Android 相机录像") |
| | | } |
| | | |
| | | // 按钮 4: 结束 Android 相机录像 |
| | | Button( |
| | | onClick = onStopAndroidRecord, |
| | | modifier = Modifier.fillMaxWidth(), |
| | | enabled = isServiceBound |
| | | ) { |
| | | Text("4. 结束 Android 相机录像") |
| | | } |
| | | |
| | | Divider(modifier = Modifier.padding(vertical = 8.dp)) |
| | | |
| | | // 按钮 5: 开始 USB 相机录像 |
| | | Button( |
| | | onClick = onStartUsbRecord, |
| | | modifier = Modifier.fillMaxWidth(), |
| | | enabled = isServiceBound |
| | | ) { |
| | | Text("5. 开始 USB 相机录像") |
| | | } |
| | | |
| | | // 按钮 6: 结束 USB 相机录像 |
| | | Button( |
| | | onClick = onStopUsbRecord, |
| | | modifier = Modifier.fillMaxWidth(), |
| | | enabled = isServiceBound |
| | | ) { |
| | | Text("6. 结束 USB 相机录像") |
| | | } |
| | | |
| | | Divider(modifier = Modifier.padding(vertical = 8.dp)) |
| | | |
| | | // 按钮 7: 开始 USB 推流 |
| | | Button( |
| | | onClick = onStartUsbPush, |
| | | modifier = Modifier.fillMaxWidth(), |
| | | enabled = isServiceBound |
| | | ) { |
| | | Text("7. 开始 USB 推流") |
| | | } |
| | | |
| | | // 按钮 8: 结束 USB 推流 |
| | | Button( |
| | | onClick = onStopUsbPush, |
| | | modifier = Modifier.fillMaxWidth(), |
| | | enabled = isServiceBound |
| | | ) { |
| | | Text("8. 结束 USB 推流") |
| | | } |
| | | } |
| | | } |