Meshtastic-Android/app/src/main/java/com/geeksville/mesh/MainActivity.kt

486 lines
18 KiB
Kotlin
Raw Normal View History

/*
2025-01-02 06:50:26 -03:00
* Copyright (c) 2025 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
2020-01-22 21:46:41 -08:00
package com.geeksville.mesh
2020-01-20 15:53:22 -08:00
import android.app.PendingIntent
import android.app.TaskStackBuilder
2020-01-24 12:49:27 -08:00
import android.bluetooth.BluetoothAdapter
import android.content.Intent
2020-06-18 23:05:33 -04:00
import android.content.pm.PackageInfo
2020-01-21 13:12:01 -08:00
import android.content.pm.PackageManager
2020-06-08 14:04:56 -07:00
import android.hardware.usb.UsbManager
import android.net.Uri
2022-11-04 18:31:18 -03:00
import android.os.Bundle
import android.os.RemoteException
feat: Show ALERT_APP notifications and override DND (#1515) * feat: Show alert notifications and override silent mode This commit adds support for showing alert notifications with high priority and the ability to override silent mode to ensure they are delivered to the user. The changes include: - Adding `showAlertNotification` function which overrides silent mode and configures a custom volume, shows a notification with high priority. - Creating a new notification channel for alerts. - Adding the alert app port number to the list of remembered data types. - Modifying `rememberDataPacket` to check for alert app messages and show alert notification. * Add notification policy access permission and DND override for alerts This commit adds the `ACCESS_NOTIFICATION_POLICY` permission to the manifest and requests this permission from the user. It also adds a check for notification policy access in the MainActivity, and if it's not granted, shows a rationale dialog. Additionally, the commit adds a notification override to the `showAlertNotification` function in `MeshServiceNotifications` to temporarily disable DND for alert notifications and restore the original ringer settings afterwards. * Refactor: Enhance Android Notification and DND Handling - **Notification Channel Improvements:** - Added `notificationLightColor` for better customization. - Set `enableLights` and `enableVibration` in the alert channel. - Use `alert.mp3` sound for alert channel. - **DND Permission Request:** - Introduced a new permission request flow for Do Not Disturb (DND) access. - Show a rationale dialog before requesting permission. - Persist if rationale was shown to avoid re-prompting. - Added a `notificationPolicyAccessLauncher` to handle the permission request result. - **Critical Alert Text** - Added critical alert text in strings. - Used critical alert text if the alert message is empty. - **Other Changes** - Removed unused imports and constants. - Updated snackbar to support action. * Refactor alert notification logic - Change `notificationLightColor` to be lazy initialized. - Update alert notification to use `CATEGORY_ALARM`. - Use `dataPacket.alert` instead of `dataPacket.text` for alert content. - Add `alert` property to `DataPacket` to handle alert messages. * Set notification light color back to blue. * Request notification permissions on grant The app now checks for notification policy access after notification permissions are granted. * make detekt happy * updates dnd dialog text * Refactor notification channel creation and critical alerts - Initialize notification channels on service creation. - Remove `ACCESS_NOTIFICATION_POLICY` permission. - Modify the logic for requesting "Do Not Disturb" override permission to align with channel settings. - Add new string resources for Alerts Channel Settings. - Update wording for critical alert DND override. - Update DND override request flow. - Create notification channels on the service creation using `initChannels`. - Adjust logic to check for "Do Not Disturb" override permission to align with notification channel settings. - Ensure notification channels are created only if they do not already exist. * refactor: Update DnD dialog with instructions for Samsung - Renamed "Alerts Channel Settings" to "Channel Settings". - Added Samsung-specific instructions and a link to Samsung's support page for Do Not Disturb mode in the alerts dialog. - Updated the dialog to display Samsung-specific instructions when on a Samsung device. * Refactor critical alerts instructions - Updated the critical alerts instructions to include a link to Samsung's support page directly within the alert dialog. - Removed the separate "Samsung Instructions" string and incorporated the information into the main instruction text, improving clarity and reducing redundancy. - Made improvements to the UI.
2025-03-05 07:28:52 -06:00
import android.provider.Settings
2020-04-07 11:27:51 -07:00
import android.view.MotionEvent
2020-01-22 13:02:24 -08:00
import android.widget.Toast
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
2022-05-16 23:32:49 -03:00
import androidx.activity.result.contract.ActivityResultContracts
2020-04-08 08:16:06 -07:00
import androidx.activity.viewModels
2022-12-10 11:03:14 -03:00
import androidx.appcompat.app.AppCompatActivity
2021-04-11 12:10:17 +02:00
import androidx.appcompat.app.AppCompatDelegate
2025-05-17 11:39:53 -05:00
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.runtime.SideEffect
2025-05-17 11:39:53 -05:00
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.platform.LocalView
feat: Show ALERT_APP notifications and override DND (#1515) * feat: Show alert notifications and override silent mode This commit adds support for showing alert notifications with high priority and the ability to override silent mode to ensure they are delivered to the user. The changes include: - Adding `showAlertNotification` function which overrides silent mode and configures a custom volume, shows a notification with high priority. - Creating a new notification channel for alerts. - Adding the alert app port number to the list of remembered data types. - Modifying `rememberDataPacket` to check for alert app messages and show alert notification. * Add notification policy access permission and DND override for alerts This commit adds the `ACCESS_NOTIFICATION_POLICY` permission to the manifest and requests this permission from the user. It also adds a check for notification policy access in the MainActivity, and if it's not granted, shows a rationale dialog. Additionally, the commit adds a notification override to the `showAlertNotification` function in `MeshServiceNotifications` to temporarily disable DND for alert notifications and restore the original ringer settings afterwards. * Refactor: Enhance Android Notification and DND Handling - **Notification Channel Improvements:** - Added `notificationLightColor` for better customization. - Set `enableLights` and `enableVibration` in the alert channel. - Use `alert.mp3` sound for alert channel. - **DND Permission Request:** - Introduced a new permission request flow for Do Not Disturb (DND) access. - Show a rationale dialog before requesting permission. - Persist if rationale was shown to avoid re-prompting. - Added a `notificationPolicyAccessLauncher` to handle the permission request result. - **Critical Alert Text** - Added critical alert text in strings. - Used critical alert text if the alert message is empty. - **Other Changes** - Removed unused imports and constants. - Updated snackbar to support action. * Refactor alert notification logic - Change `notificationLightColor` to be lazy initialized. - Update alert notification to use `CATEGORY_ALARM`. - Use `dataPacket.alert` instead of `dataPacket.text` for alert content. - Add `alert` property to `DataPacket` to handle alert messages. * Set notification light color back to blue. * Request notification permissions on grant The app now checks for notification policy access after notification permissions are granted. * make detekt happy * updates dnd dialog text * Refactor notification channel creation and critical alerts - Initialize notification channels on service creation. - Remove `ACCESS_NOTIFICATION_POLICY` permission. - Modify the logic for requesting "Do Not Disturb" override permission to align with channel settings. - Add new string resources for Alerts Channel Settings. - Update wording for critical alert DND override. - Update DND override request flow. - Create notification channels on the service creation using `initChannels`. - Adjust logic to check for "Do Not Disturb" override permission to align with notification channel settings. - Ensure notification channels are created only if they do not already exist. * refactor: Update DnD dialog with instructions for Samsung - Renamed "Alerts Channel Settings" to "Channel Settings". - Added Samsung-specific instructions and a link to Samsung's support page for Do Not Disturb mode in the alerts dialog. - Updated the dialog to display Samsung-specific instructions when on a Samsung device. * Refactor critical alerts instructions - Updated the critical alerts instructions to include a link to Samsung's support page directly within the alert dialog. - Removed the separate "Samsung Instructions" string and incorporated the information into the main instruction text, improving clarity and reducing redundancy. - Made improvements to the UI.
2025-03-05 07:28:52 -06:00
import androidx.core.content.edit
import androidx.core.net.toUri
2022-02-04 00:57:27 -03:00
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.core.view.WindowCompat
import androidx.lifecycle.lifecycleScope
import com.geeksville.mesh.android.BindFailedException
import com.geeksville.mesh.android.GeeksvilleApplication
import com.geeksville.mesh.android.Logging
import com.geeksville.mesh.android.ServiceClient
import com.geeksville.mesh.android.getBluetoothPermissions
import com.geeksville.mesh.android.getNotificationPermissions
import com.geeksville.mesh.android.hasBluetoothPermission
import com.geeksville.mesh.android.hasNotificationPermission
import com.geeksville.mesh.android.permissionMissing
import com.geeksville.mesh.android.shouldShowRequestPermissionRationale
2022-09-04 22:52:40 -03:00
import com.geeksville.mesh.concurrent.handledLaunch
import com.geeksville.mesh.model.BluetoothViewModel
import com.geeksville.mesh.model.UIViewModel
import com.geeksville.mesh.navigation.DEEP_LINK_BASE_URI
import com.geeksville.mesh.service.MeshService
import com.geeksville.mesh.service.ServiceRepository
import com.geeksville.mesh.service.startService
import com.geeksville.mesh.ui.MainMenuAction
import com.geeksville.mesh.ui.MainScreen
import com.geeksville.mesh.ui.common.theme.AppTheme
import com.geeksville.mesh.ui.common.theme.MODE_DYNAMIC
import com.geeksville.mesh.ui.sharing.toSharedContact
import com.geeksville.mesh.ui.intro.AppIntroductionScreen
2022-09-04 22:52:40 -03:00
import com.geeksville.mesh.util.Exceptions
2022-12-10 11:03:14 -03:00
import com.geeksville.mesh.util.LanguageUtils
import com.geeksville.mesh.util.getPackageInfoCompat
import dagger.hilt.android.AndroidEntryPoint
2022-02-03 18:15:06 -08:00
import kotlinx.coroutines.Job
import javax.inject.Inject
2021-03-02 13:22:55 +08:00
@AndroidEntryPoint
2022-12-10 11:03:14 -03:00
class MainActivity : AppCompatActivity(), Logging {
private val bluetoothViewModel: BluetoothViewModel by viewModels()
private val model: UIViewModel by viewModels()
2020-04-08 08:16:06 -07:00
@Inject
internal lateinit var serviceRepository: ServiceRepository
private var showAppIntro by mutableStateOf(false)
private val bluetoothPermissionsLauncher =
registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { result ->
if (result.entries.all { it.value }) {
info("Bluetooth permissions granted")
} else {
warn("Bluetooth permissions denied")
model.showSnackbar(permissionMissing)
2022-09-03 11:07:10 -03:00
}
2022-11-04 18:31:18 -03:00
requestedEnable = false
2022-09-03 11:07:10 -03:00
bluetoothViewModel.permissionsUpdated()
}
private val notificationPermissionsLauncher =
registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { result ->
if (result.entries.all { it.value }) {
info("Notification permissions granted")
feat: Show ALERT_APP notifications and override DND (#1515) * feat: Show alert notifications and override silent mode This commit adds support for showing alert notifications with high priority and the ability to override silent mode to ensure they are delivered to the user. The changes include: - Adding `showAlertNotification` function which overrides silent mode and configures a custom volume, shows a notification with high priority. - Creating a new notification channel for alerts. - Adding the alert app port number to the list of remembered data types. - Modifying `rememberDataPacket` to check for alert app messages and show alert notification. * Add notification policy access permission and DND override for alerts This commit adds the `ACCESS_NOTIFICATION_POLICY` permission to the manifest and requests this permission from the user. It also adds a check for notification policy access in the MainActivity, and if it's not granted, shows a rationale dialog. Additionally, the commit adds a notification override to the `showAlertNotification` function in `MeshServiceNotifications` to temporarily disable DND for alert notifications and restore the original ringer settings afterwards. * Refactor: Enhance Android Notification and DND Handling - **Notification Channel Improvements:** - Added `notificationLightColor` for better customization. - Set `enableLights` and `enableVibration` in the alert channel. - Use `alert.mp3` sound for alert channel. - **DND Permission Request:** - Introduced a new permission request flow for Do Not Disturb (DND) access. - Show a rationale dialog before requesting permission. - Persist if rationale was shown to avoid re-prompting. - Added a `notificationPolicyAccessLauncher` to handle the permission request result. - **Critical Alert Text** - Added critical alert text in strings. - Used critical alert text if the alert message is empty. - **Other Changes** - Removed unused imports and constants. - Updated snackbar to support action. * Refactor alert notification logic - Change `notificationLightColor` to be lazy initialized. - Update alert notification to use `CATEGORY_ALARM`. - Use `dataPacket.alert` instead of `dataPacket.text` for alert content. - Add `alert` property to `DataPacket` to handle alert messages. * Set notification light color back to blue. * Request notification permissions on grant The app now checks for notification policy access after notification permissions are granted. * make detekt happy * updates dnd dialog text * Refactor notification channel creation and critical alerts - Initialize notification channels on service creation. - Remove `ACCESS_NOTIFICATION_POLICY` permission. - Modify the logic for requesting "Do Not Disturb" override permission to align with channel settings. - Add new string resources for Alerts Channel Settings. - Update wording for critical alert DND override. - Update DND override request flow. - Create notification channels on the service creation using `initChannels`. - Adjust logic to check for "Do Not Disturb" override permission to align with notification channel settings. - Ensure notification channels are created only if they do not already exist. * refactor: Update DnD dialog with instructions for Samsung - Renamed "Alerts Channel Settings" to "Channel Settings". - Added Samsung-specific instructions and a link to Samsung's support page for Do Not Disturb mode in the alerts dialog. - Updated the dialog to display Samsung-specific instructions when on a Samsung device. * Refactor critical alerts instructions - Updated the critical alerts instructions to include a link to Samsung's support page directly within the alert dialog. - Removed the separate "Samsung Instructions" string and incorporated the information into the main instruction text, improving clarity and reducing redundancy. - Made improvements to the UI.
2025-03-05 07:28:52 -06:00
checkAlertDnD()
} else {
warn("Notification permissions denied")
model.showSnackbar(getString(R.string.notification_denied))
}
}
2020-02-14 07:47:20 -08:00
override fun onCreate(savedInstanceState: Bundle?) {
enableEdgeToEdge()
2022-02-04 00:57:27 -03:00
installSplashScreen()
2020-02-14 07:47:20 -08:00
super.onCreate(savedInstanceState)
val prefs = UIViewModel.getPreferences(this)
if (savedInstanceState == null) {
val lang = prefs.getString("lang", LanguageUtils.SYSTEM_DEFAULT)
if (lang != LanguageUtils.SYSTEM_MANAGED) LanguageUtils.migrateLanguagePrefs(prefs)
info("in-app language is ${LanguageUtils.getLocale()}")
if (!prefs.getBoolean("app_intro_completed", false)) {
showAppIntro = true
} else {
(application as GeeksvilleApplication).askToRate(this)
}
2022-12-10 11:03:14 -03:00
}
WindowCompat.setDecorFitsSystemWindows(window, false)
setContent {
val theme by model.theme.collectAsState()
val dynamic = theme == MODE_DYNAMIC
val dark = when (theme) {
AppCompatDelegate.MODE_NIGHT_YES -> true
AppCompatDelegate.MODE_NIGHT_NO -> false
AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM -> isSystemInDarkTheme()
else -> isSystemInDarkTheme()
}
AppTheme(
dynamicColor = dynamic,
darkTheme = dark,
) {
val view = LocalView.current
if (!view.isInEditMode) {
SideEffect {
AppCompatDelegate.setDefaultNightMode(theme)
}
}
if (showAppIntro) {
AppIntroductionScreen(onDone = {
prefs.edit { putBoolean("app_intro_completed", true) }
showAppIntro = false
(application as GeeksvilleApplication).askToRate(this@MainActivity)
})
} else {
MainScreen(
uIViewModel = model,
bluetoothViewModel = bluetoothViewModel,
onAction = ::onMainMenuAction,
)
}
}
}
handleIntent(intent)
}
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
handleIntent(intent)
}
private fun handleIntent(intent: Intent) {
val appLinkAction = intent.action
val appLinkData: Uri? = intent.data
when (appLinkAction) {
Intent.ACTION_VIEW -> {
appLinkData?.let {
debug("App link data: $it")
if (it.path?.startsWith("/e/") == true ||
it.path?.startsWith("/E/") == true
) {
debug("App link data is a channel set")
model.requestChannelUrl(it)
} else if (it.path?.startsWith("/v/") == true ||
it.path?.startsWith("/V/") == true
) {
val sharedContact = it.toSharedContact()
debug("App link data is a shared contact: ${sharedContact.user.longName}")
model.setSharedContactRequested(sharedContact)
} else {
debug("App link data is not a channel set")
}
}
}
UsbManager.ACTION_USB_DEVICE_ATTACHED -> {
debug("USB device attached")
showSettingsPage()
}
2020-06-08 14:04:56 -07:00
Intent.ACTION_MAIN -> {
}
Intent.ACTION_SEND -> {
val text = intent.getStringExtra(Intent.EXTRA_TEXT)
if (text != null) {
createShareIntent(text).send()
}
}
else -> {
warn("Unexpected action $appLinkAction")
}
2020-06-08 14:04:56 -07:00
}
2020-02-14 07:47:20 -08:00
}
2020-01-21 09:37:39 -08:00
private fun createShareIntent(message: String): PendingIntent {
val deepLink = "$DEEP_LINK_BASE_URI/share?message=$message"
val startActivityIntent = Intent(
Intent.ACTION_VIEW, deepLink.toUri(),
this, MainActivity::class.java
).apply {
flags = Intent.FLAG_ACTIVITY_SINGLE_TOP
}
val resultPendingIntent: PendingIntent? = TaskStackBuilder.create(this).run {
addNextIntentWithParentStack(startActivityIntent)
getPendingIntent(0, PendingIntent.FLAG_IMMUTABLE)
}
return resultPendingIntent!!
}
private fun createSettingsIntent(): PendingIntent {
val deepLink = "$DEEP_LINK_BASE_URI/connections"
val startActivityIntent = Intent(
Intent.ACTION_VIEW, deepLink.toUri(),
this, MainActivity::class.java
).apply {
flags = Intent.FLAG_ACTIVITY_SINGLE_TOP
}
val resultPendingIntent: PendingIntent? = TaskStackBuilder.create(this).run {
addNextIntentWithParentStack(startActivityIntent)
getPendingIntent(0, PendingIntent.FLAG_IMMUTABLE)
}
return resultPendingIntent!!
}
2022-05-16 23:32:49 -03:00
private var requestedEnable = false
private val bleRequestEnable = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) {
requestedEnable = false
}
2022-08-30 22:20:44 -03:00
private val createDocumentLauncher = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
2020-04-07 10:40:01 -07:00
) {
if (it.resultCode == RESULT_OK) {
2022-08-30 22:20:44 -03:00
it.data?.data?.let { file_uri -> model.saveMessagesCSV(file_uri) }
2020-01-20 15:53:22 -08:00
}
2020-02-14 07:47:20 -08:00
}
2020-02-09 05:52:17 -08:00
2022-04-22 17:22:06 -03:00
private fun onMeshConnectionChanged(newConnection: MeshService.ConnectionState) {
if (newConnection == MeshService.ConnectionState.CONNECTED) {
feat: Show ALERT_APP notifications and override DND (#1515) * feat: Show alert notifications and override silent mode This commit adds support for showing alert notifications with high priority and the ability to override silent mode to ensure they are delivered to the user. The changes include: - Adding `showAlertNotification` function which overrides silent mode and configures a custom volume, shows a notification with high priority. - Creating a new notification channel for alerts. - Adding the alert app port number to the list of remembered data types. - Modifying `rememberDataPacket` to check for alert app messages and show alert notification. * Add notification policy access permission and DND override for alerts This commit adds the `ACCESS_NOTIFICATION_POLICY` permission to the manifest and requests this permission from the user. It also adds a check for notification policy access in the MainActivity, and if it's not granted, shows a rationale dialog. Additionally, the commit adds a notification override to the `showAlertNotification` function in `MeshServiceNotifications` to temporarily disable DND for alert notifications and restore the original ringer settings afterwards. * Refactor: Enhance Android Notification and DND Handling - **Notification Channel Improvements:** - Added `notificationLightColor` for better customization. - Set `enableLights` and `enableVibration` in the alert channel. - Use `alert.mp3` sound for alert channel. - **DND Permission Request:** - Introduced a new permission request flow for Do Not Disturb (DND) access. - Show a rationale dialog before requesting permission. - Persist if rationale was shown to avoid re-prompting. - Added a `notificationPolicyAccessLauncher` to handle the permission request result. - **Critical Alert Text** - Added critical alert text in strings. - Used critical alert text if the alert message is empty. - **Other Changes** - Removed unused imports and constants. - Updated snackbar to support action. * Refactor alert notification logic - Change `notificationLightColor` to be lazy initialized. - Update alert notification to use `CATEGORY_ALARM`. - Use `dataPacket.alert` instead of `dataPacket.text` for alert content. - Add `alert` property to `DataPacket` to handle alert messages. * Set notification light color back to blue. * Request notification permissions on grant The app now checks for notification policy access after notification permissions are granted. * make detekt happy * updates dnd dialog text * Refactor notification channel creation and critical alerts - Initialize notification channels on service creation. - Remove `ACCESS_NOTIFICATION_POLICY` permission. - Modify the logic for requesting "Do Not Disturb" override permission to align with channel settings. - Add new string resources for Alerts Channel Settings. - Update wording for critical alert DND override. - Update DND override request flow. - Create notification channels on the service creation using `initChannels`. - Adjust logic to check for "Do Not Disturb" override permission to align with notification channel settings. - Ensure notification channels are created only if they do not already exist. * refactor: Update DnD dialog with instructions for Samsung - Renamed "Alerts Channel Settings" to "Channel Settings". - Added Samsung-specific instructions and a link to Samsung's support page for Do Not Disturb mode in the alerts dialog. - Updated the dialog to display Samsung-specific instructions when on a Samsung device. * Refactor critical alerts instructions - Updated the critical alerts instructions to include a link to Samsung's support page directly within the alert dialog. - Removed the separate "Samsung Instructions" string and incorporated the information into the main instruction text, improving clarity and reducing redundancy. - Made improvements to the UI.
2025-03-05 07:28:52 -06:00
checkNotificationPermissions()
}
}
private fun checkNotificationPermissions() {
if (!hasNotificationPermission()) {
val notificationPermissions = getNotificationPermissions()
2025-05-17 11:39:53 -05:00
if (shouldShowRequestPermissionRationale(notificationPermissions)) {
val title = getString(R.string.notification_required)
val message = getString(R.string.why_notification_required)
model.showAlert(
title = title,
message = message,
onConfirm = {
notificationPermissionsLauncher.launch(notificationPermissions)
},
)
} else {
feat: Show ALERT_APP notifications and override DND (#1515) * feat: Show alert notifications and override silent mode This commit adds support for showing alert notifications with high priority and the ability to override silent mode to ensure they are delivered to the user. The changes include: - Adding `showAlertNotification` function which overrides silent mode and configures a custom volume, shows a notification with high priority. - Creating a new notification channel for alerts. - Adding the alert app port number to the list of remembered data types. - Modifying `rememberDataPacket` to check for alert app messages and show alert notification. * Add notification policy access permission and DND override for alerts This commit adds the `ACCESS_NOTIFICATION_POLICY` permission to the manifest and requests this permission from the user. It also adds a check for notification policy access in the MainActivity, and if it's not granted, shows a rationale dialog. Additionally, the commit adds a notification override to the `showAlertNotification` function in `MeshServiceNotifications` to temporarily disable DND for alert notifications and restore the original ringer settings afterwards. * Refactor: Enhance Android Notification and DND Handling - **Notification Channel Improvements:** - Added `notificationLightColor` for better customization. - Set `enableLights` and `enableVibration` in the alert channel. - Use `alert.mp3` sound for alert channel. - **DND Permission Request:** - Introduced a new permission request flow for Do Not Disturb (DND) access. - Show a rationale dialog before requesting permission. - Persist if rationale was shown to avoid re-prompting. - Added a `notificationPolicyAccessLauncher` to handle the permission request result. - **Critical Alert Text** - Added critical alert text in strings. - Used critical alert text if the alert message is empty. - **Other Changes** - Removed unused imports and constants. - Updated snackbar to support action. * Refactor alert notification logic - Change `notificationLightColor` to be lazy initialized. - Update alert notification to use `CATEGORY_ALARM`. - Use `dataPacket.alert` instead of `dataPacket.text` for alert content. - Add `alert` property to `DataPacket` to handle alert messages. * Set notification light color back to blue. * Request notification permissions on grant The app now checks for notification policy access after notification permissions are granted. * make detekt happy * updates dnd dialog text * Refactor notification channel creation and critical alerts - Initialize notification channels on service creation. - Remove `ACCESS_NOTIFICATION_POLICY` permission. - Modify the logic for requesting "Do Not Disturb" override permission to align with channel settings. - Add new string resources for Alerts Channel Settings. - Update wording for critical alert DND override. - Update DND override request flow. - Create notification channels on the service creation using `initChannels`. - Adjust logic to check for "Do Not Disturb" override permission to align with notification channel settings. - Ensure notification channels are created only if they do not already exist. * refactor: Update DnD dialog with instructions for Samsung - Renamed "Alerts Channel Settings" to "Channel Settings". - Added Samsung-specific instructions and a link to Samsung's support page for Do Not Disturb mode in the alerts dialog. - Updated the dialog to display Samsung-specific instructions when on a Samsung device. * Refactor critical alerts instructions - Updated the critical alerts instructions to include a link to Samsung's support page directly within the alert dialog. - Removed the separate "Samsung Instructions" string and incorporated the information into the main instruction text, improving clarity and reducing redundancy. - Made improvements to the UI.
2025-03-05 07:28:52 -06:00
notificationPermissionsLauncher.launch(notificationPermissions)
}
}
}
@Suppress("MagicNumber")
feat: Show ALERT_APP notifications and override DND (#1515) * feat: Show alert notifications and override silent mode This commit adds support for showing alert notifications with high priority and the ability to override silent mode to ensure they are delivered to the user. The changes include: - Adding `showAlertNotification` function which overrides silent mode and configures a custom volume, shows a notification with high priority. - Creating a new notification channel for alerts. - Adding the alert app port number to the list of remembered data types. - Modifying `rememberDataPacket` to check for alert app messages and show alert notification. * Add notification policy access permission and DND override for alerts This commit adds the `ACCESS_NOTIFICATION_POLICY` permission to the manifest and requests this permission from the user. It also adds a check for notification policy access in the MainActivity, and if it's not granted, shows a rationale dialog. Additionally, the commit adds a notification override to the `showAlertNotification` function in `MeshServiceNotifications` to temporarily disable DND for alert notifications and restore the original ringer settings afterwards. * Refactor: Enhance Android Notification and DND Handling - **Notification Channel Improvements:** - Added `notificationLightColor` for better customization. - Set `enableLights` and `enableVibration` in the alert channel. - Use `alert.mp3` sound for alert channel. - **DND Permission Request:** - Introduced a new permission request flow for Do Not Disturb (DND) access. - Show a rationale dialog before requesting permission. - Persist if rationale was shown to avoid re-prompting. - Added a `notificationPolicyAccessLauncher` to handle the permission request result. - **Critical Alert Text** - Added critical alert text in strings. - Used critical alert text if the alert message is empty. - **Other Changes** - Removed unused imports and constants. - Updated snackbar to support action. * Refactor alert notification logic - Change `notificationLightColor` to be lazy initialized. - Update alert notification to use `CATEGORY_ALARM`. - Use `dataPacket.alert` instead of `dataPacket.text` for alert content. - Add `alert` property to `DataPacket` to handle alert messages. * Set notification light color back to blue. * Request notification permissions on grant The app now checks for notification policy access after notification permissions are granted. * make detekt happy * updates dnd dialog text * Refactor notification channel creation and critical alerts - Initialize notification channels on service creation. - Remove `ACCESS_NOTIFICATION_POLICY` permission. - Modify the logic for requesting "Do Not Disturb" override permission to align with channel settings. - Add new string resources for Alerts Channel Settings. - Update wording for critical alert DND override. - Update DND override request flow. - Create notification channels on the service creation using `initChannels`. - Adjust logic to check for "Do Not Disturb" override permission to align with notification channel settings. - Ensure notification channels are created only if they do not already exist. * refactor: Update DnD dialog with instructions for Samsung - Renamed "Alerts Channel Settings" to "Channel Settings". - Added Samsung-specific instructions and a link to Samsung's support page for Do Not Disturb mode in the alerts dialog. - Updated the dialog to display Samsung-specific instructions when on a Samsung device. * Refactor critical alerts instructions - Updated the critical alerts instructions to include a link to Samsung's support page directly within the alert dialog. - Removed the separate "Samsung Instructions" string and incorporated the information into the main instruction text, improving clarity and reducing redundancy. - Made improvements to the UI.
2025-03-05 07:28:52 -06:00
private fun checkAlertDnD() {
val prefs = UIViewModel.getPreferences(this)
val rationaleShown = prefs.getBoolean("dnd_rationale_shown", false)
if (!rationaleShown && hasNotificationPermission()) {
fun showAlertAppNotificationSettings() {
val intent = Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS)
intent.putExtra(Settings.EXTRA_APP_PACKAGE, packageName)
intent.putExtra(Settings.EXTRA_CHANNEL_ID, "my_alerts")
startActivity(intent)
}
model.showAlert(
title = getString(R.string.alerts_dnd_request_title),
html = getString(R.string.alerts_dnd_request_text),
onConfirm = {
showAlertAppNotificationSettings()
},
).also {
prefs.edit { putBoolean("dnd_rationale_shown", true) }
}
}
2020-02-14 07:47:20 -08:00
}
2020-02-09 05:52:17 -08:00
2020-03-05 09:50:33 -08:00
override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
return try {
super.dispatchTouchEvent(ev)
} catch (ex: Throwable) {
2020-03-05 09:50:33 -08:00
Exceptions.report(
ex,
"dispatchTouchEvent"
) // hide this Compose error from the user but report to the mothership
false
}
}
private var serviceSetupJob: Job? = null
private val mesh = object : ServiceClient<IMeshService>(IMeshService.Stub::asInterface) {
2022-01-31 21:55:24 -03:00
override fun onConnected(service: IMeshService) {
serviceSetupJob?.cancel()
serviceSetupJob = lifecycleScope.handledLaunch {
serviceRepository.setMeshService(service)
try {
val connectionState =
MeshService.ConnectionState.valueOf(service.connectionState())
onMeshConnectionChanged(connectionState)
} catch (ex: RemoteException) {
errormsg("Device error during init ${ex.message}")
2021-03-04 09:08:29 +08:00
}
2022-04-28 11:54:04 -03:00
debug("connected to mesh service, connectionState=${model.connectionState.value}")
}
2020-02-25 08:10:23 -08:00
}
2020-02-14 07:47:20 -08:00
2020-02-25 08:10:23 -08:00
override fun onDisconnected() {
serviceSetupJob?.cancel()
serviceRepository.setMeshService(null)
2020-01-23 08:09:50 -08:00
}
2020-02-14 07:47:20 -08:00
}
2020-01-23 08:09:50 -08:00
private fun bindMeshService() {
2020-02-14 07:47:20 -08:00
debug("Binding to mesh service!")
2020-07-02 09:53:52 -07:00
try {
MeshService.startService(this)
2020-07-02 09:53:52 -07:00
} catch (ex: Exception) {
errormsg("Failed to start service from activity - but ignoring because bind will work ${ex.message}")
}
mesh.connect(
this,
MeshService.createIntent(),
BIND_AUTO_CREATE + BIND_ABOVE_CLIENT
)
2020-02-14 07:47:20 -08:00
}
2020-01-23 08:09:50 -08:00
2020-02-18 09:09:49 -08:00
override fun onStart() {
super.onStart()
bluetoothViewModel.enabled.observe(this) { enabled ->
if (!enabled && !requestedEnable && model.selectedBluetooth) {
2022-11-04 18:31:18 -03:00
requestedEnable = true
if (hasBluetoothPermission()) {
2022-05-16 23:32:49 -03:00
val enableBtIntent = Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE)
bleRequestEnable.launch(enableBtIntent)
2022-11-04 18:31:18 -03:00
} else {
val bluetoothPermissions = getBluetoothPermissions()
2025-05-17 11:39:53 -05:00
val title = getString(R.string.required_permissions)
val message = permissionMissing
model.showAlert(
title = title,
message = message,
onConfirm = {
bluetoothPermissionsLauncher.launch(bluetoothPermissions)
},
)
}
}
}
2021-03-17 15:53:08 +08:00
try {
bindMeshService()
2021-03-23 13:21:51 +08:00
} catch (ex: BindFailedException) {
errormsg("Bind of MeshService failed${ex.message}")
2021-03-17 15:53:08 +08:00
}
}
private fun showSettingsPage() {
createSettingsIntent().send()
2020-02-14 07:47:20 -08:00
}
2020-01-20 15:53:22 -08:00
private fun onMainMenuAction(action: MainMenuAction) {
when (action) {
MainMenuAction.ABOUT -> {
2020-06-18 23:05:33 -04:00
getVersionInfo()
2020-09-23 22:47:45 -04:00
}
MainMenuAction.EXPORT_MESSAGES -> {
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "application/csv"
putExtra(Intent.EXTRA_TITLE, "rangetest.csv")
}
2022-08-30 22:20:44 -03:00
createDocumentLauncher.launch(intent)
}
MainMenuAction.THEME -> {
2021-04-11 12:10:17 +02:00
chooseThemeDialog()
}
MainMenuAction.LANGUAGE -> {
2022-03-30 23:14:02 +02:00
chooseLangDialog()
}
MainMenuAction.SHOW_INTRO -> {
showAppIntro = true // Show intro again if selected from menu
2022-08-24 12:16:57 -04:00
}
else -> {}
2020-01-20 15:53:22 -08:00
}
}
2020-06-18 23:05:33 -04:00
private fun getVersionInfo() {
try {
val packageInfo: PackageInfo = packageManager.getPackageInfoCompat(packageName, 0)
2020-06-18 23:05:33 -04:00
val versionName = packageInfo.versionName
2022-02-03 02:16:31 -03:00
Toast.makeText(this, versionName, Toast.LENGTH_LONG).show()
2020-06-18 23:05:33 -04:00
} catch (e: PackageManager.NameNotFoundException) {
errormsg("Can not find the version: ${e.message}")
}
}
2021-04-11 12:10:17 +02:00
private fun chooseThemeDialog() {
val styles = mapOf(
2025-05-17 11:39:53 -05:00
getString(R.string.dynamic) to MODE_DYNAMIC,
getString(R.string.theme_light) to AppCompatDelegate.MODE_NIGHT_NO,
getString(R.string.theme_dark) to AppCompatDelegate.MODE_NIGHT_YES,
getString(R.string.theme_system) to AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
)
2021-04-11 12:10:17 +02:00
val prefs = UIViewModel.getPreferences(this)
val theme = prefs.getInt("theme", AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
debug("Theme from prefs: $theme")
2025-05-17 11:39:53 -05:00
model.showAlert(
title = getString(R.string.choose_theme),
message = "",
choices = styles.mapValues { (_, value) ->
{
model.setTheme(value)
}
},
)
2021-04-11 12:10:17 +02:00
}
2022-03-30 23:14:02 +02:00
private fun chooseLangDialog() {
2022-12-10 11:03:14 -03:00
val languageTags = LanguageUtils.getLanguageTags(this)
val lang = LanguageUtils.getLocale()
2022-03-30 23:14:02 +02:00
debug("Lang from prefs: $lang")
2025-05-17 11:39:53 -05:00
val langMap = languageTags.mapValues { (_, value) ->
{
LanguageUtils.setLocale(value)
}
}
2025-05-17 11:39:53 -05:00
model.showAlert(
title = getString(R.string.preferences_language),
message = "",
choices = langMap,
)
}
2020-02-14 07:47:20 -08:00
}