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

842 lines
32 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.Activity
2020-01-24 12:49:27 -08:00
import android.bluetooth.BluetoothAdapter
import android.content.Context
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
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.os.Build
2022-11-04 18:31:18 -03:00
import android.os.Bundle
import android.os.Handler
import android.os.Looper
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
import android.text.Html
2021-03-02 13:22:55 +08:00
import android.text.method.LinkMovementMethod
2020-04-07 11:27:51 -07:00
import android.view.Menu
import android.view.MenuItem
import android.view.MotionEvent
2021-03-02 13:22:55 +08:00
import android.widget.TextView
2020-01-22 13:02:24 -08:00
import android.widget.Toast
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
import androidx.annotation.StringRes
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
2020-06-18 23:05:33 -04:00
import androidx.appcompat.widget.Toolbar
import androidx.compose.runtime.getValue
2020-01-21 13:12:01 -08:00
import androidx.core.content.ContextCompat
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
2022-02-04 00:57:27 -03:00
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
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.view.setPadding
2020-04-07 09:36:12 -07:00
import androidx.fragment.app.Fragment
2020-09-23 22:47:45 -04:00
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentTransaction
import androidx.lifecycle.asLiveData
import androidx.lifecycle.compose.collectAsStateWithLifecycle
2020-04-07 09:36:12 -07:00
import androidx.viewpager2.adapter.FragmentStateAdapter
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.dpToPx
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.rationaleDialog
import com.geeksville.mesh.android.shouldShowRequestPermissionRationale
2022-09-04 22:52:40 -03:00
import com.geeksville.mesh.concurrent.handledLaunch
import com.geeksville.mesh.databinding.ActivityMainBinding
import com.geeksville.mesh.model.BluetoothViewModel
2021-03-02 15:12:57 +08:00
import com.geeksville.mesh.model.DeviceVersion
import com.geeksville.mesh.model.UIViewModel
import com.geeksville.mesh.navigation.navigateToNavGraph
import com.geeksville.mesh.service.MeshService
import com.geeksville.mesh.service.MeshServiceNotifications
import com.geeksville.mesh.service.ServiceRepository
import com.geeksville.mesh.service.startService
import com.geeksville.mesh.ui.ChannelFragment
import com.geeksville.mesh.ui.ContactsFragment
import com.geeksville.mesh.ui.DebugFragment
import com.geeksville.mesh.ui.QuickChatSettingsFragment
import com.geeksville.mesh.ui.SettingsFragment
import com.geeksville.mesh.ui.UsersFragment
import com.geeksville.mesh.ui.components.ScannedQrCodeDialog
import com.geeksville.mesh.ui.map.MapFragment
import com.geeksville.mesh.ui.message.navigateToMessages
import com.geeksville.mesh.ui.navigateToShareMessage
import com.geeksville.mesh.ui.theme.AppTheme
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 com.google.android.material.dialog.MaterialAlertDialogBuilder
2022-01-24 14:56:17 -03:00
import com.google.android.material.snackbar.Snackbar
import com.google.android.material.tabs.TabLayout
2020-04-07 09:36:12 -07:00
import com.google.android.material.tabs.TabLayoutMediator
import dagger.hilt.android.AndroidEntryPoint
2022-02-03 18:15:06 -08:00
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.cancel
import java.text.DateFormat
2022-10-16 19:16:33 -03:00
import java.util.Date
import javax.inject.Inject
2021-03-02 13:22:55 +08:00
/*
UI design
material setup instructions: https://material.io/develop/android/docs/getting-started/
dark theme (or use system eventually) https://material.io/develop/android/theming/dark/
NavDrawer is a standard draw which can be dragged in from the left or the menu icon inside the app
title.
Fragments:
2020-02-16 18:54:29 -08:00
SettingsFragment shows "Settings"
username
shortname
bluetooth pairing list
(eventually misc device settings that are not channel related)
2020-02-16 18:54:29 -08:00
Channel fragment "Channel"
qr code, copy link button
ch number
misc other settings
(eventually a way of choosing between past channels)
2020-02-16 18:54:29 -08:00
ChatFragment "Messages"
a text box to enter new texts
a scrolling list of rows. each row is a text and a sender info layout
2020-02-16 18:54:29 -08:00
NodeListFragment "Users"
a node info row for every node
ViewModels:
BTScanModel starts/stops bt scan and provides list of devices (manages entire scan lifecycle)
MeshModel contains: (manages entire service relationship)
current received texts
current radio macaddr
current node infos (updated dynamically)
eventually use bottom navigation bar to switch between, Members, Chat, Channel, Settings. https://material.io/develop/android/components/bottom-navigation-view/
use numbers of # chat messages and # of members in the badges.
(per this recommendation to not use top tabs: https://ux.stackexchange.com/questions/102439/android-ux-when-to-use-bottom-navigation-and-when-to-use-tabs )
eventually:
make a custom theme: https://github.com/material-components/material-components-android/tree/master/material-theme-builder
*/
@AndroidEntryPoint
2022-12-10 11:03:14 -03:00
class MainActivity : AppCompatActivity(), Logging {
2020-01-21 09:37:39 -08:00
private lateinit var binding: ActivityMainBinding
// Used to schedule a coroutine in the GUI thread
private val mainScope = CoroutineScope(Dispatchers.Main + Job())
2020-02-09 07:28:24 -08:00
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 val bluetoothPermissionsLauncher =
registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { result ->
if (result.entries.all { it.value }) {
info("Bluetooth permissions granted")
} else {
warn("Bluetooth permissions denied")
2022-11-04 18:31:18 -03:00
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")
showSnackbar(getString(R.string.notification_denied), Snackbar.LENGTH_SHORT)
}
}
data class TabInfo(@StringRes val textResId: Int, val icon: Int, val content: Fragment)
private val tabInfos = arrayOf(
TabInfo(
R.string.main_tab_messages,
R.drawable.ic_twotone_message_24,
ContactsFragment()
),
TabInfo(
R.string.main_tab_users,
R.drawable.ic_twotone_people_24,
UsersFragment()
),
TabInfo(
R.string.main_tab_map,
R.drawable.ic_twotone_map_24,
MapFragment()
),
TabInfo(
R.string.main_tab_channel,
R.drawable.ic_twotone_contactless_24,
ChannelFragment()
),
TabInfo(
R.string.main_tab_settings,
R.drawable.ic_twotone_settings_applications_24,
SettingsFragment()
)
)
private val tabsAdapter = object : FragmentStateAdapter(supportFragmentManager, lifecycle) {
override fun getItemCount(): Int = tabInfos.size
override fun createFragment(position: Int): Fragment = tabInfos[position].content
2020-04-07 09:36:12 -07:00
}
2020-02-14 07:47:20 -08:00
override fun onCreate(savedInstanceState: Bundle?) {
2022-02-04 00:57:27 -03:00
installSplashScreen()
2020-02-14 07:47:20 -08:00
super.onCreate(savedInstanceState)
if (savedInstanceState == null) {
val prefs = UIViewModel.getPreferences(this)
// First run: migrate in-app language prefs to appcompat
val lang = prefs.getString("lang", LanguageUtils.SYSTEM_DEFAULT)
if (lang != LanguageUtils.SYSTEM_MANAGED) LanguageUtils.migrateLanguagePrefs(prefs)
info("in-app language is ${LanguageUtils.getLocale()}")
// Set theme
AppCompatDelegate.setDefaultNightMode(
prefs.getInt("theme", AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
)
// First run: show AppIntroduction
if (!prefs.getBoolean("app_intro_completed", false)) {
startActivity(Intent(this, AppIntroduction::class.java))
}
// Ask user to rate in play store
(application as GeeksvilleApplication).askToRate(this)
2022-12-10 11:03:14 -03:00
}
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
2020-04-07 09:36:12 -07:00
2020-06-18 23:05:33 -04:00
initToolbar()
binding.pager.adapter = tabsAdapter
binding.pager.isUserInputEnabled =
false // Gestures for screen switching doesn't work so good with the map view
2020-04-09 11:03:17 -07:00
// pager.offscreenPageLimit = 0 // Don't keep any offscreen pages around, because we want to make sure our bluetooth scanning stops
2022-01-02 15:29:27 -03:00
TabLayoutMediator(binding.tabLayout, binding.pager, false, false) { tab, position ->
tab.icon = ContextCompat.getDrawable(this, tabInfos[position].icon)
tab.contentDescription = ContextCompat.getString(this, tabInfos[position].textResId)
2020-04-07 09:36:12 -07:00
}.attach()
2020-04-08 11:57:31 -07:00
binding.tabLayout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
override fun onTabSelected(tab: TabLayout.Tab?) {
val mainTab = tab?.position ?: 0
model.setCurrentTab(mainTab)
}
override fun onTabUnselected(tab: TabLayout.Tab?) { }
override fun onTabReselected(tab: TabLayout.Tab?) { }
})
binding.composeView.setContent {
val connState by model.connectionState.collectAsStateWithLifecycle()
val channels by model.channels.collectAsStateWithLifecycle()
val requestChannelSet by model.requestChannelSet.collectAsStateWithLifecycle()
AppTheme {
if (connState.isConnected()) {
if (requestChannelSet != null) {
ScannedQrCodeDialog(
channels = channels,
incoming = requestChannelSet!!,
onDismiss = model::clearRequestChannelUrl,
onConfirm = model::setChannels,
)
}
}
}
}
// Handle any intent
handleIntent(intent)
}
2020-06-18 23:05:33 -04:00
private fun initToolbar() {
2022-10-16 19:16:33 -03:00
val toolbar = binding.toolbar as Toolbar
2020-06-18 23:05:33 -04:00
setSupportActionBar(toolbar)
supportActionBar?.setDisplayShowTitleEnabled(false)
}
2020-06-10 21:50:34 -04:00
private fun updateConnectionStatusImage(connected: MeshService.ConnectionState) {
if (model.actionBarMenu == null) return
2020-06-18 23:05:33 -04:00
2020-06-10 21:50:34 -04:00
val (image, tooltip) = when (connected) {
MeshService.ConnectionState.CONNECTED -> R.drawable.cloud_on to R.string.connected
MeshService.ConnectionState.DEVICE_SLEEP -> R.drawable.ic_twotone_cloud_upload_24 to R.string.device_sleeping
MeshService.ConnectionState.DISCONNECTED -> R.drawable.cloud_off to R.string.disconnected
2020-06-10 21:50:34 -04:00
}
2020-07-17 17:06:29 -04:00
val item = model.actionBarMenu?.findItem(R.id.connectStatusImage)
2020-06-18 23:05:33 -04:00
if (item != null) {
item.setIcon(image)
item.setTitle(tooltip)
}
2020-06-10 21:50:34 -04:00
}
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
handleIntent(intent)
}
// Handle any intents that were passed into us
private fun handleIntent(intent: Intent) {
val appLinkAction = intent.action
val appLinkData: Uri? = intent.data
when (appLinkAction) {
Intent.ACTION_VIEW -> {
debug("Asked to open a channel URL - ask user if they want to switch to that channel. If so send the config to the radio")
appLinkData?.let(model::requestChannelUrl)
// We now wait for the device to connect, once connected, we ask the user if they want to switch to the new channel
}
MeshServiceNotifications.OPEN_MESSAGE_ACTION -> {
val contactKey =
intent.getStringExtra(MeshServiceNotifications.OPEN_MESSAGE_EXTRA_CONTACT_KEY)
showMessages(contactKey)
}
UsbManager.ACTION_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) {
shareMessages(text)
}
}
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
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
) {
2022-08-30 22:20:44 -03:00
if (it.resultCode == Activity.RESULT_OK) {
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-08-30 22:20:44 -03:00
override fun onDestroy() {
mainScope.cancel("Activity going away")
super.onDestroy()
2020-02-14 07:47:20 -08:00
}
2021-03-02 15:12:57 +08:00
/** Show an alert that may contain HTML */
private fun showAlert(titleText: Int, messageText: Int) {
2021-03-02 15:12:57 +08:00
// make links clickable per https://stackoverflow.com/a/62642807
// val messageStr = getText(messageText)
val builder = MaterialAlertDialogBuilder(this)
.setCancelable(false)
2021-03-02 15:12:57 +08:00
.setTitle(titleText)
.setMessage(messageText)
2021-03-02 16:27:34 +08:00
.setPositiveButton(R.string.okay) { _, _ ->
2021-03-02 15:12:57 +08:00
info("User acknowledged")
}
val dialog = builder.show()
// Make the textview clickable. Must be called after show()
val view = (dialog.findViewById(android.R.id.message) as TextView?)!!
// Linkify.addLinks(view, Linkify.ALL) // not needed with this method
view.movementMethod = LinkMovementMethod.getInstance()
showSettingsPage() // Default to the settings page in this case
}
// Called when we gain/lose a connection to our mesh radio
2022-04-22 17:22:06 -03:00
private fun onMeshConnectionChanged(newConnection: MeshService.ConnectionState) {
if (newConnection == MeshService.ConnectionState.CONNECTED) {
serviceRepository.meshService?.let { service ->
try {
2021-03-23 13:21:51 +08:00
val info: MyNodeInfo? = service.myNodeInfo // this can be null
2021-03-23 13:21:51 +08:00
if (info != null) {
val isOld = info.minAppVersion > BuildConfig.VERSION_CODE
if (isOld) {
2021-03-23 13:21:51 +08:00
showAlert(R.string.app_too_old, R.string.must_update)
} else {
// If we are already doing an update don't put up a dialog or try to get device info
val isUpdating = service.updateStatus >= 0
if (!isUpdating) {
val curVer = DeviceVersion(info.firmwareVersion ?: "0.0.0")
2021-03-02 13:22:55 +08:00
if (curVer < MeshService.minDeviceVersion) {
showAlert(R.string.firmware_too_old, R.string.firmware_old)
}
2021-03-23 13:21:51 +08:00
}
2021-03-02 15:12:57 +08:00
}
2021-03-02 13:22:55 +08:00
}
} catch (ex: RemoteException) {
warn("Abandoning connect $ex, because we probably just lost device connection")
}
2022-01-03 21:59:30 -03:00
// if provideLocation enabled: Start providing location (from phone GPS) to mesh
if (model.provideLocation.value == true) {
2022-05-20 09:13:59 -03:00
service.startProvideLocation()
}
2020-04-09 12:22:41 -07:00
}
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()
rationaleDialog(
shouldShowRequestPermissionRationale(notificationPermissions),
R.string.notification_required,
getString(R.string.why_notification_required),
) {
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() {
if (
Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
) {
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)
}
val message = Html.fromHtml(
getString(R.string.alerts_dnd_request_text),
Html.FROM_HTML_MODE_COMPACT
)
val messageTextView = TextView(this).also {
it.text = message
it.movementMethod = LinkMovementMethod.getInstance()
it.setPadding(dpToPx(16f))
}
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
MaterialAlertDialogBuilder(this)
.setTitle(R.string.alerts_dnd_request_title)
.setView(messageTextView)
.setNeutralButton(R.string.cancel) { dialog, _ ->
prefs.edit { putBoolean("dnd_rationale_shown", true) }
dialog.dismiss()
}
.setPositiveButton(R.string.channel_settings) { dialog, _ ->
showAlertAppNotificationSettings()
prefs.edit { putBoolean("dnd_rationale_shown", true) }
dialog.dismiss()
}
.setCancelable(false).show()
}
}
2020-02-14 07:47:20 -08:00
}
2020-02-09 05:52:17 -08:00
2022-02-03 02:16:31 -03:00
private fun showSnackbar(msgId: Int) {
2022-02-12 18:54:10 -03:00
try {
2022-10-16 19:16:33 -03:00
Snackbar.make(binding.root, msgId, Snackbar.LENGTH_LONG).show()
2022-02-12 18:54:10 -03:00
} catch (ex: IllegalStateException) {
2022-04-08 18:37:22 -03:00
errormsg("Snackbar couldn't find view for msgId $msgId")
2022-02-12 18:54:10 -03:00
}
}
private fun showSnackbar(msg: String, duration: Int = Snackbar.LENGTH_INDEFINITE) {
2022-02-12 18:54:10 -03:00
try {
Snackbar.make(binding.root, msg, duration)
2022-02-12 18:54:10 -03:00
.apply { view.findViewById<TextView>(R.id.snackbar_text).isSingleLine = false }
.setAction(R.string.okay) {
// dismiss
}
.show()
} catch (ex: IllegalStateException) {
2022-04-08 18:37:22 -03:00
errormsg("Snackbar couldn't find view for msgString $msg")
2022-02-12 18:54:10 -03: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 connectionJob: Job? = null
2022-01-03 21:59:30 -03:00
private val mesh = object :
2022-01-31 21:55:24 -03:00
ServiceClient<IMeshService>({
IMeshService.Stub.asInterface(it)
2020-04-07 10:40:01 -07:00
}) {
2022-01-31 21:55:24 -03:00
override fun onConnected(service: IMeshService) {
connectionJob = mainScope.handledLaunch {
serviceRepository.setMeshService(service)
try {
val connectionState =
MeshService.ConnectionState.valueOf(service.connectionState())
// We won't receive a notify for the initial state of connection, so we force an update here
onMeshConnectionChanged(connectionState)
} catch (ex: RemoteException) {
errormsg("Device error during init ${ex.message}")
} finally {
2021-03-04 09:08:29 +08:00
connectionJob = null
}
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() {
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!")
// we bind using the well known name, to make sure 3rd party apps could also
if (serviceRepository.meshService != null) {
/* This problem can occur if we unbind, but there is already an onConnected job waiting to run. That job runs and then makes meshService != null again
I think I've fixed this by cancelling connectionJob. We'll see!
*/
Exceptions.reportError("meshService was supposed to be null, ignoring (but reporting a bug)")
}
2020-07-02 09:53:52 -07:00
try {
MeshService.startService(this) // Start the service so it stays running even after we unbind
} catch (ex: Exception) {
// Old samsung phones have a race condition andthis might rarely fail. Which is probably find because the bind will be sufficient most of the time
errormsg("Failed to start service from activity - but ignoring because bind will work ${ex.message}")
}
// ALSO bind so we can use the api
mesh.connect(
this,
MeshService.createIntent(),
Context.BIND_AUTO_CREATE + Context.BIND_ABOVE_CLIENT
)
2020-02-14 07:47:20 -08:00
}
2020-01-23 08:09:50 -08:00
private fun unbindMeshService() {
2020-02-14 07:47:20 -08:00
// If we have received the service, and hence registered with
// it, then now is the time to unregister.
// if we never connected, do nothing
2020-04-19 16:24:47 -07:00
debug("Unbinding from mesh service!")
connectionJob?.let { job ->
connectionJob = null
warn("We had a pending onConnection job, so we are cancelling it")
job.cancel("unbinding")
}
2020-04-19 16:24:47 -07:00
mesh.close()
serviceRepository.setMeshService(null)
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 onStop() {
2020-02-14 07:47:20 -08:00
unbindMeshService()
2020-02-18 09:09:49 -08:00
super.onStop()
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()
2020-01-23 08:09:50 -08:00
model.connectionState.asLiveData().observe(this) { state ->
onMeshConnectionChanged(state)
updateConnectionStatusImage(state)
}
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()
rationaleDialog(shouldShowRequestPermissionRationale(bluetoothPermissions)) {
bluetoothPermissionsLauncher.launch(bluetoothPermissions)
}
}
}
}
// Call showSnackbar() whenever [snackbarText] updates with a non-null value
model.snackbarText.observe(this) { text ->
if (text is Int) showSnackbar(text)
if (text is String) showSnackbar(text)
if (text != null) model.clearSnackbarText()
}
model.currentTab.observe(this) {
binding.tabLayout.getTabAt(it)?.select()
}
model.tracerouteResponse.observe(this) { response ->
MaterialAlertDialogBuilder(this)
.setCancelable(false)
.setTitle(R.string.traceroute)
.setMessage(response ?: return@observe)
.setPositiveButton(R.string.okay) { _, _ -> }
.show()
model.clearTracerouteResponse()
}
2021-03-17 15:53:08 +08:00
try {
bindMeshService()
2021-03-23 13:21:51 +08:00
} catch (ex: BindFailedException) {
2021-03-17 15:53:08 +08:00
// App is probably shutting down, ignore
errormsg("Bind of MeshService failed")
}
2021-03-23 13:21:51 +08:00
val bonded = model.bondedAddress != null
if (!bonded) showSettingsPage()
}
private fun showSettingsPage() {
binding.pager.currentItem = 5
2020-02-14 07:47:20 -08:00
}
2020-01-23 08:09:50 -08:00
private fun showMessages(contactKey: String?) {
model.setCurrentTab(0)
if (contactKey != null) {
supportFragmentManager.navigateToMessages(contactKey)
}
}
private fun shareMessages(message: String?) {
model.setCurrentTab(0)
if (message != null) {
supportFragmentManager.navigateToShareMessage(message)
}
}
2020-02-14 07:47:20 -08:00
override fun onCreateOptionsMenu(menu: Menu): Boolean {
// Inflate the menu; this adds items to the action bar if it is present.
menuInflater.inflate(R.menu.menu_main, menu)
2020-07-17 17:06:29 -04:00
model.actionBarMenu = menu
updateConnectionStatusImage(model.connectionState.value)
2020-07-17 17:06:29 -04:00
2020-02-14 07:47:20 -08:00
return true
}
2020-01-20 15:53:22 -08:00
private val handler: Handler by lazy {
2022-04-03 11:25:50 -03:00
Handler(Looper.getMainLooper())
}
override fun onPrepareOptionsMenu(menu: Menu): Boolean {
2021-03-02 13:22:55 +08:00
menu.findItem(R.id.stress_test).isVisible =
BuildConfig.DEBUG // only show stress test for debug builds (for now)
2023-05-13 18:18:49 -03:00
menu.findItem(R.id.radio_config).isEnabled = !model.isManaged
return super.onPrepareOptionsMenu(menu)
}
2020-02-14 07:47:20 -08:00
override fun onOptionsItemSelected(item: MenuItem): Boolean {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
return when (item.itemId) {
2020-06-18 23:05:33 -04:00
R.id.about -> {
getVersionInfo()
return true
}
R.id.connectStatusImage -> {
Toast.makeText(applicationContext, item.title, Toast.LENGTH_SHORT).show()
2020-06-18 23:05:33 -04:00
return true
}
2020-09-23 22:47:45 -04:00
R.id.debug -> {
val fragmentManager: FragmentManager = supportFragmentManager
val fragmentTransaction: FragmentTransaction = fragmentManager.beginTransaction()
val nameFragment = DebugFragment()
fragmentTransaction.add(R.id.mainActivityLayout, nameFragment)
fragmentTransaction.addToBackStack(null)
fragmentTransaction.commit()
return true
}
R.id.stress_test -> {
fun postPing() {
2021-02-10 21:36:45 -08:00
// Send ping message and arrange delayed recursion.
debug("Sending ping")
val str = "Ping " + DateFormat.getTimeInstance(DateFormat.MEDIUM)
2021-02-10 21:36:45 -08:00
.format(Date(System.currentTimeMillis()))
2022-09-15 22:24:04 -03:00
model.sendMessage(str)
2022-01-31 21:55:24 -03:00
handler.postDelayed({ postPing() }, 30000)
}
item.isChecked = !item.isChecked // toggle ping test
if (item.isChecked) {
postPing()
} else {
handler.removeCallbacksAndMessages(null)
}
return true
}
R.id.radio_config -> {
supportFragmentManager.navigateToNavGraph()
2022-11-22 22:01:37 -03:00
return true
}
R.id.save_messages_csv -> {
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)
return true
}
2021-04-11 12:10:17 +02:00
R.id.theme -> {
chooseThemeDialog()
return true
}
2022-03-30 23:14:02 +02:00
R.id.preferences_language -> {
chooseLangDialog()
return true
}
R.id.show_intro -> {
startActivity(Intent(this, AppIntroduction::class.java))
return true
2022-08-24 12:16:57 -04:00
}
2022-08-10 17:26:15 +01:00
R.id.preferences_quick_chat -> {
2022-08-09 15:26:52 +01:00
val fragmentManager: FragmentManager = supportFragmentManager
val fragmentTransaction: FragmentTransaction = fragmentManager.beginTransaction()
val nameFragment = QuickChatSettingsFragment()
fragmentTransaction.add(R.id.mainActivityLayout, nameFragment)
fragmentTransaction.addToBackStack(null)
fragmentTransaction.commit()
return true
}
2020-02-14 07:47:20 -08:00
else -> super.onOptionsItemSelected(item)
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}")
}
}
// Theme functions
2021-04-11 12:10:17 +02:00
private fun chooseThemeDialog() {
// Prepare dialog and its items
2022-02-05 19:43:56 -03:00
val builder = MaterialAlertDialogBuilder(this)
2022-12-28 17:37:25 -03:00
builder.setTitle(getString(R.string.choose_theme))
2021-04-11 12:10:17 +02:00
val styles = mapOf(
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
// Load preferences and its value
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")
2021-04-11 12:10:17 +02:00
builder.setSingleChoiceItems(
styles.keys.toTypedArray(),
styles.values.indexOf(theme)
) { dialog, position ->
val selectedTheme = styles.values.elementAt(position)
debug("Set theme pref to $selectedTheme")
prefs.edit().putInt("theme", selectedTheme).apply()
AppCompatDelegate.setDefaultNightMode(selectedTheme)
dialog.dismiss()
2021-04-11 12:10:17 +02:00
}
val dialog = builder.create()
dialog.show()
}
2022-03-30 23:14:02 +02:00
private fun chooseLangDialog() {
// Prepare dialog and its items
2022-03-30 23:14:02 +02:00
val builder = MaterialAlertDialogBuilder(this)
builder.setTitle(getString(R.string.preferences_language))
2022-12-10 11:03:14 -03:00
val languageTags = LanguageUtils.getLanguageTags(this)
2022-03-30 23:14:02 +02:00
// Load preferences and its value
2022-12-10 11:03:14 -03:00
val lang = LanguageUtils.getLocale()
2022-03-30 23:14:02 +02:00
debug("Lang from prefs: $lang")
2022-08-24 12:16:57 -04:00
builder.setSingleChoiceItems(
languageTags.keys.toTypedArray(),
languageTags.values.indexOf(lang)
) { dialog, position ->
val selectedLang = languageTags.values.elementAt(position)
debug("Set lang pref to $selectedLang")
2022-12-10 11:03:14 -03:00
LanguageUtils.setLocale(selectedLang)
dialog.dismiss()
}
val dialog = builder.create()
dialog.show()
}
2020-02-14 07:47:20 -08:00
}