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