From 2e9f1c7ade4bc33e56bf218c5ad63f93e32c7f4d Mon Sep 17 00:00:00 2001 From: Myzel394 <50424412+Myzel394@users.noreply.github.com> Date: Sat, 28 Oct 2023 17:52:32 +0200 Subject: [PATCH] feat: Add boot notification --- .../app/myzel394/alibi/NotificationHelper.kt | 28 +++-- .../java/app/myzel394/alibi/db/AppSettings.kt | 8 +- .../myzel394/alibi/receivers/BootReceiver.kt | 114 +++++++++++++++--- .../alibi/ui/models/AudioRecorderModel.kt | 2 +- app/src/main/res/values/strings.xml | 4 + 5 files changed, 127 insertions(+), 29 deletions(-) diff --git a/app/src/main/java/app/myzel394/alibi/NotificationHelper.kt b/app/src/main/java/app/myzel394/alibi/NotificationHelper.kt index 679cee7..c34c221 100644 --- a/app/src/main/java/app/myzel394/alibi/NotificationHelper.kt +++ b/app/src/main/java/app/myzel394/alibi/NotificationHelper.kt @@ -9,18 +9,30 @@ import androidx.annotation.RequiresApi object NotificationHelper { const val RECORDER_CHANNEL_ID = "recorder" 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) 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) - 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) + } + ) } } \ No newline at end of file diff --git a/app/src/main/java/app/myzel394/alibi/db/AppSettings.kt b/app/src/main/java/app/myzel394/alibi/db/AppSettings.kt index 565a5fd..8714157 100644 --- a/app/src/main/java/app/myzel394/alibi/db/AppSettings.kt +++ b/app/src/main/java/app/myzel394/alibi/db/AppSettings.kt @@ -19,7 +19,7 @@ data class AppSettings( val showAdvancedSettings: Boolean = false, val theme: Theme = Theme.SYSTEM, val lastRecording: RecordingInformation? = null, - val bootBehavior: BootBehavior? = BootBehavior.START_RECORDING, + val bootBehavior: BootBehavior? = BootBehavior.SHOW_NOTIFICATION, ) { fun setShowAdvancedSettings(showAdvancedSettings: Boolean): AppSettings { return copy(showAdvancedSettings = showAdvancedSettings) @@ -56,7 +56,13 @@ data class AppSettings( } enum class BootBehavior { + // Always start recording, no matter if it was interrupted or not START_RECORDING, + + // Only start recording if it was interrupted + CONTINUE_RECORDING, + + // Show a notification if interrupted SHOW_NOTIFICATION, } diff --git a/app/src/main/java/app/myzel394/alibi/receivers/BootReceiver.kt b/app/src/main/java/app/myzel394/alibi/receivers/BootReceiver.kt index d413458..cb45e93 100644 --- a/app/src/main/java/app/myzel394/alibi/receivers/BootReceiver.kt +++ b/app/src/main/java/app/myzel394/alibi/receivers/BootReceiver.kt @@ -1,42 +1,118 @@ package app.myzel394.alibi.receivers +import android.app.NotificationManager +import android.app.PendingIntent import android.content.BroadcastReceiver import android.content.ComponentName import android.content.Context import android.content.Intent import android.content.ServiceConnection import android.os.IBinder +import androidx.core.app.NotificationCompat 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.RecorderNotificationHelper import app.myzel394.alibi.services.RecorderService import app.myzel394.alibi.ui.enums.Screen 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() { + 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?) { - println("Received new intent: ${intent?.action}") + if (intent?.action != Intent.ACTION_BOOT_COMPLETED || context == null) { + return + } - if (intent?.action == Intent.ACTION_BOOT_COMPLETED) { - println("Starting service") + scope.launch { + context.dataStore.data.collectLatest { settings -> + when (settings.bootBehavior) { + AppSettings.BootBehavior.CONTINUE_RECORDING -> { + if (AudioRecorderExporter.hasRecordingsAvailable(context)) { + startRecording(context, settings) + } + } - val connection = object : ServiceConnection { - override fun onServiceConnected(className: ComponentName, service: IBinder) { - ((service as RecorderService.RecorderBinder).getService() as AudioRecorderService).also { recorder -> - recorder.startRecording() + AppSettings.BootBehavior.START_RECORDING -> startRecording(context, settings) + AppSettings.BootBehavior.SHOW_NOTIFICATION -> showNotification(context) + null -> { + // 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) } } } diff --git a/app/src/main/java/app/myzel394/alibi/ui/models/AudioRecorderModel.kt b/app/src/main/java/app/myzel394/alibi/ui/models/AudioRecorderModel.kt index 7c9724a..da3f1d7 100644 --- a/app/src/main/java/app/myzel394/alibi/ui/models/AudioRecorderModel.kt +++ b/app/src/main/java/app/myzel394/alibi/ui/models/AudioRecorderModel.kt @@ -125,7 +125,7 @@ class AudioRecorderModel : ViewModel() { } } ContextCompat.startForegroundService(context, intent) - context.bindService(intent, connection, Context.BIND_AUTO_CREATE) + context.bindService(intent, connection, 0) } fun stopRecording(context: Context) { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 17065ce..f33ccf0 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -111,4 +111,8 @@ If enabled, Alibi will immediately delete recordings after you have saved the file. Boot Behavior Show a notification + Boot notification + If enabled, you\'ll be informed that your recording was interrupted + Alibi was interrupted + Your device restarted and your recording has been interrupted \ No newline at end of file