feat: Add boot notification

This commit is contained in:
Myzel394 2023-10-28 17:52:32 +02:00
parent 60fc16f2c4
commit 2e9f1c7ade
No known key found for this signature in database
GPG Key ID: 79CC92F37B3E1A2B
5 changed files with 127 additions and 29 deletions

View File

@ -9,18 +9,30 @@ import androidx.annotation.RequiresApi
object NotificationHelper { object NotificationHelper {
const val RECORDER_CHANNEL_ID = "recorder" const val RECORDER_CHANNEL_ID = "recorder"
const val RECORDER_CHANNEL_NOTIFICATION_ID = 1 const val RECORDER_CHANNEL_NOTIFICATION_ID = 1
const val BOOT_CHANNEL_ID = "boot"
const val BOOT_CHANNEL_NOTIFICATION_ID = 2
@RequiresApi(Build.VERSION_CODES.O) @RequiresApi(Build.VERSION_CODES.O)
fun createChannels(context: Context) { fun createChannels(context: Context) {
val channel = NotificationChannel(
RECORDER_CHANNEL_ID,
context.resources.getString(R.string.notificationChannels_recorder_name),
android.app.NotificationManager.IMPORTANCE_LOW,
)
channel.description = context.resources.getString(R.string.notificationChannels_recorder_description)
val notificationManager = context.getSystemService(NotificationManager::class.java) val notificationManager = context.getSystemService(NotificationManager::class.java)
notificationManager.createNotificationChannel(channel) notificationManager.createNotificationChannel(
NotificationChannel(
RECORDER_CHANNEL_ID,
context.getString(R.string.notificationChannels_recorder_name),
NotificationManager.IMPORTANCE_LOW,
).apply {
description = context.getString(R.string.notificationChannels_recorder_description)
}
)
notificationManager.createNotificationChannel(
NotificationChannel(
BOOT_CHANNEL_ID,
context.getString(R.string.notificationChannels_boot_name),
NotificationManager.IMPORTANCE_LOW,
).apply {
description = context.getString(R.string.notificationChannels_boot_description)
}
)
} }
} }

View File

@ -19,7 +19,7 @@ data class AppSettings(
val showAdvancedSettings: Boolean = false, val showAdvancedSettings: Boolean = false,
val theme: Theme = Theme.SYSTEM, val theme: Theme = Theme.SYSTEM,
val lastRecording: RecordingInformation? = null, val lastRecording: RecordingInformation? = null,
val bootBehavior: BootBehavior? = BootBehavior.START_RECORDING, val bootBehavior: BootBehavior? = BootBehavior.SHOW_NOTIFICATION,
) { ) {
fun setShowAdvancedSettings(showAdvancedSettings: Boolean): AppSettings { fun setShowAdvancedSettings(showAdvancedSettings: Boolean): AppSettings {
return copy(showAdvancedSettings = showAdvancedSettings) return copy(showAdvancedSettings = showAdvancedSettings)
@ -56,7 +56,13 @@ data class AppSettings(
} }
enum class BootBehavior { enum class BootBehavior {
// Always start recording, no matter if it was interrupted or not
START_RECORDING, START_RECORDING,
// Only start recording if it was interrupted
CONTINUE_RECORDING,
// Show a notification if interrupted
SHOW_NOTIFICATION, SHOW_NOTIFICATION,
} }

View File

@ -1,42 +1,118 @@
package app.myzel394.alibi.receivers package app.myzel394.alibi.receivers
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.BroadcastReceiver import android.content.BroadcastReceiver
import android.content.ComponentName import android.content.ComponentName
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.ServiceConnection import android.content.ServiceConnection
import android.os.IBinder import android.os.IBinder
import androidx.core.app.NotificationCompat
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import app.myzel394.alibi.MainActivity
import app.myzel394.alibi.NotificationHelper
import app.myzel394.alibi.R
import app.myzel394.alibi.dataStore
import app.myzel394.alibi.db.AppSettings
import app.myzel394.alibi.helpers.AudioRecorderExporter
import app.myzel394.alibi.services.AudioRecorderService import app.myzel394.alibi.services.AudioRecorderService
import app.myzel394.alibi.services.RecorderNotificationHelper
import app.myzel394.alibi.services.RecorderService import app.myzel394.alibi.services.RecorderService
import app.myzel394.alibi.ui.enums.Screen import app.myzel394.alibi.ui.enums.Screen
import app.myzel394.alibi.ui.models.AudioRecorderModel import app.myzel394.alibi.ui.models.AudioRecorderModel
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import kotlinx.serialization.json.Json
class BootReceiver : BroadcastReceiver() { class BootReceiver : BroadcastReceiver() {
private var job = SupervisorJob()
private var scope = CoroutineScope(Dispatchers.IO + job)
private fun startRecording(context: Context, settings: AppSettings) {
val connection = object : ServiceConnection {
override fun onServiceConnected(className: ComponentName, service: IBinder) {
((service as RecorderService.RecorderBinder).getService() as AudioRecorderService).also { recorder ->
recorder.startRecording()
}
}
override fun onServiceDisconnected(arg0: ComponentName) {
}
}
val intent = Intent(context, AudioRecorderService::class.java).apply {
action = "init"
if (settings.notificationSettings != null) {
putExtra(
"notificationDetails",
Json.encodeToString(
RecorderNotificationHelper.NotificationDetails.serializer(),
RecorderNotificationHelper.NotificationDetails.fromNotificationSettings(
context,
settings.notificationSettings
)
),
)
}
}
ContextCompat.startForegroundService(context, intent)
context.bindService(intent, connection, 0)
}
private fun showNotification(context: Context) {
if (!AudioRecorderExporter.hasRecordingsAvailable(context)) {
// Nothing interrupted, so no notification needs to be shown
return
}
val notification = NotificationCompat.Builder(context, NotificationHelper.BOOT_CHANNEL_ID)
.setPriority(NotificationCompat.PRIORITY_LOW)
.setCategory(NotificationCompat.CATEGORY_REMINDER)
.setSmallIcon(R.drawable.launcher_monochrome_noopacity)
.setContentIntent(
PendingIntent.getActivity(
context,
0,
Intent(context, MainActivity::class.java),
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE,
)
)
.setOnlyAlertOnce(true)
.setContentTitle(context.getString(R.string.notification_boot_title))
.setContentText(context.getString(R.string.notification_boot_message))
.build()
val notificationManager =
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.notify(NotificationHelper.BOOT_CHANNEL_NOTIFICATION_ID, notification)
}
override fun onReceive(context: Context?, intent: Intent?) { override fun onReceive(context: Context?, intent: Intent?) {
println("Received new intent: ${intent?.action}") if (intent?.action != Intent.ACTION_BOOT_COMPLETED || context == null) {
return
}
if (intent?.action == Intent.ACTION_BOOT_COMPLETED) { scope.launch {
println("Starting service") context.dataStore.data.collectLatest { settings ->
when (settings.bootBehavior) {
AppSettings.BootBehavior.CONTINUE_RECORDING -> {
if (AudioRecorderExporter.hasRecordingsAvailable(context)) {
startRecording(context, settings)
}
}
val connection = object : ServiceConnection { AppSettings.BootBehavior.START_RECORDING -> startRecording(context, settings)
override fun onServiceConnected(className: ComponentName, service: IBinder) { AppSettings.BootBehavior.SHOW_NOTIFICATION -> showNotification(context)
((service as RecorderService.RecorderBinder).getService() as AudioRecorderService).also { recorder -> null -> {
recorder.startRecording() // Nothing to do
} }
} }
override fun onServiceDisconnected(arg0: ComponentName) {
}
}
val intent = Intent(context, AudioRecorderService::class.java).apply {
action = "initStart"
}
println("Starting service checking context")
if (context != null) {
println("Starting service with context")
ContextCompat.startForegroundService(context, intent)
} }
} }
} }

View File

@ -125,7 +125,7 @@ class AudioRecorderModel : ViewModel() {
} }
} }
ContextCompat.startForegroundService(context, intent) ContextCompat.startForegroundService(context, intent)
context.bindService(intent, connection, Context.BIND_AUTO_CREATE) context.bindService(intent, connection, 0)
} }
fun stopRecording(context: Context) { fun stopRecording(context: Context) {

View File

@ -111,4 +111,8 @@
<string name="ui_settings_option_deleteRecordingsImmediately_description">If enabled, Alibi will immediately delete recordings after you have saved the file.</string> <string name="ui_settings_option_deleteRecordingsImmediately_description">If enabled, Alibi will immediately delete recordings after you have saved the file.</string>
<string name="ui_settings_bootBehavior_title">Boot Behavior</string> <string name="ui_settings_bootBehavior_title">Boot Behavior</string>
<string name="ui_settings_bootBehavior_values_SHOW_NOTIFICATION_title">Show a notification</string> <string name="ui_settings_bootBehavior_values_SHOW_NOTIFICATION_title">Show a notification</string>
<string name="notificationChannels_boot_name">Boot notification</string>
<string name="notificationChannels_boot_description">If enabled, you\'ll be informed that your recording was interrupted</string>
<string name="notification_boot_title">Alibi was interrupted</string>
<string name="notification_boot_message">Your device restarted and your recording has been interrupted</string>
</resources> </resources>