AeroToss: авто-обновление + foreground service + кнопка отмены

1. Авто-обновление:
- Discovery пересканируется каждые 5 секунд автоматически
- Устройства снова появляются после потери связи

2. Foreground service (Android):
- AeroTossService: уведомление + wake lock
- Разрешения: FOREGROUND_SERVICE, POST_NOTIFICATIONS, WAKE_LOCK
- Приложение работает в фоне, передача не останавливается

3. Кнопка отмены:
- HomeScreen: кнопка 'Отменить отправку' при передаче
- SendScreen: кнопка 'Отменить отправку' с отменой передачи

4. Разрешения:
- Запрос POST_NOTIFICATIONS при первом запуске (Android 13+)
- Запрос ACCESS_FINE_LOCATION
This commit is contained in:
2026-08-20 00:04:10 +03:00
parent a1b600a614
commit b6a0e75bab
6 changed files with 167 additions and 36 deletions
@@ -0,0 +1,64 @@
package com.aerotoss
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.Service
import android.content.Intent
import android.content.pm.ServiceInfo
import android.os.Build
import android.os.IBinder
import android.os.PowerManager
class AeroTossService : Service() {
private var wakeLock: PowerManager.WakeLock? = null
override fun onBind(intent: Intent?): IBinder? = null
override fun onCreate() {
super.onCreate()
createNotificationChannel()
val notification = Notification.Builder(this, CHANNEL_ID)
.setContentTitle("AeroToss")
.setContentText("Поиск устройств...")
.setSmallIcon(android.R.drawable.ic_menu_share)
.build()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
startForeground(NOTIFICATION_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC)
} else {
@Suppress("DEPRECATION")
startForeground(NOTIFICATION_ID, notification)
}
@Suppress("DEPRECATION")
val pm = getSystemService(POWER_SERVICE) as PowerManager
@Suppress("DEPRECATION")
wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "AeroToss::DiscoveryLock")
wakeLock?.acquire(60 * 60 * 1000L)
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
return START_STICKY
}
override fun onDestroy() {
wakeLock?.release()
super.onDestroy()
}
private fun createNotificationChannel() {
val channel = NotificationChannel(
CHANNEL_ID,
"AeroToss Discovery",
NotificationManager.IMPORTANCE_LOW
)
val nm = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
nm.createNotificationChannel(channel)
}
companion object {
const val CHANNEL_ID = "aerotoss_discovery"
const val NOTIFICATION_ID = 1
}
}
@@ -1,9 +1,14 @@
package com.aerotoss
import android.Manifest
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.content.ContextCompat
import androidx.lifecycle.lifecycleScope
import com.aerotoss.core.AeroTossManager
import com.aerotoss.discovery.CompositeAndroidDiscovery
@@ -25,14 +30,46 @@ class MainActivity : ComponentActivity() {
handleAndroidPickResult(uri, applicationContext)
}
private val permissionLauncher = registerForActivityResult(
ActivityResultContracts.RequestMultiplePermissions()
) { permissions ->
if (permissions.values.all { it }) {
startApp()
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (manager != null) return
androidFilePickerLauncher = { mimeTypes, _ ->
filePickerLauncher.launch(mimeTypes)
}
val neededPermissions = mutableListOf(
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.CHANGE_WIFI_MULTICAST_STATE,
)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
neededPermissions.add(Manifest.permission.POST_NOTIFICATIONS)
}
val notGranted = neededPermissions.filter {
ContextCompat.checkSelfPermission(this, it) != PackageManager.PERMISSION_GRANTED
}
if (notGranted.isNotEmpty()) {
permissionLauncher.launch(notGranted.toTypedArray())
} else {
startApp()
}
}
private fun startApp() {
if (manager != null) return
startForegroundService(Intent(this, AeroTossService::class.java))
val transferManager = AndroidFileTransferManager(applicationContext)
val wifiDirectManager = WifiDirectManager(applicationContext)
val discoveryManager = CompositeAndroidDiscovery(wifiDirectManager)
@@ -51,5 +88,6 @@ class MainActivity : ComponentActivity() {
super.onDestroy()
manager?.dispose()
manager = null
stopService(Intent(this, AeroTossService::class.java))
}
}