From 15249ddf70b892e0c58d4b547d4e8fcebc6f0b73 Mon Sep 17 00:00:00 2001 From: Arcao Date: Tue, 9 Apr 2024 06:08:26 +0200 Subject: [PATCH] Fix app crash for LiveMap functionality --- .idea/misc.xml | 1 - app/src/main/AndroidManifest.xml | 5 +++- .../base/util/PermissionUtil.kt | 8 ++++++ .../dashboard/DashboardAction.kt | 23 +++++++++-------- .../dashboard/DashboardActivity.kt | 24 ++++++++++++++++++ .../dashboard/DashboardViewModel.kt | 6 +++++ ...tificationPermissionErrorDialogFragment.kt | 14 +++++++++++ .../live_map/LiveMapService.kt | 25 ++++++++++++++++--- .../util/LiveMapNotificationManager.kt | 6 +++++ app/src/main/res/values/strings.xml | 1 + 10 files changed, 96 insertions(+), 17 deletions(-) create mode 100644 app/src/main/java/com/arcao/geocaching4locus/dashboard/fragment/NoPostNotificationPermissionErrorDialogFragment.kt diff --git a/.idea/misc.xml b/.idea/misc.xml index 6e134aec..8b9c7634 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,4 +1,3 @@ - diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index d8f1fce9..d83f3814 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -10,6 +10,7 @@ + - + = Build.VERSION_CODES.TIRAMISU) { + PermissionUtil.hasPermission(this, Manifest.permission.POST_NOTIFICATIONS) + } else { + true + } diff --git a/app/src/main/java/com/arcao/geocaching4locus/dashboard/DashboardAction.kt b/app/src/main/java/com/arcao/geocaching4locus/dashboard/DashboardAction.kt index d4fe5fbf..842f0115 100644 --- a/app/src/main/java/com/arcao/geocaching4locus/dashboard/DashboardAction.kt +++ b/app/src/main/java/com/arcao/geocaching4locus/dashboard/DashboardAction.kt @@ -1,14 +1,15 @@ package com.arcao.geocaching4locus.dashboard -sealed class DashboardAction { - object SearchNearest : DashboardAction() - object ImportGcCode : DashboardAction() - object ImportBookmarks : DashboardAction() - object DownloadLiveMapGeocaches : DashboardAction() - object UsersGuide : DashboardAction() - object Preferences : DashboardAction() - object NavigationBack : DashboardAction() - object LocusMapNotInstalled : DashboardAction() - object SignIn : DashboardAction() - object WarnPowerSaveActive : DashboardAction() +sealed interface DashboardAction { + object SearchNearest : DashboardAction + object ImportGcCode : DashboardAction + object ImportBookmarks : DashboardAction + object DownloadLiveMapGeocaches : DashboardAction + object UsersGuide : DashboardAction + object Preferences : DashboardAction + object NavigationBack : DashboardAction + object LocusMapNotInstalled : DashboardAction + object SignIn : DashboardAction + object WarnPowerSaveActive : DashboardAction + object RequestPostNotificationPermission : DashboardAction } \ No newline at end of file diff --git a/app/src/main/java/com/arcao/geocaching4locus/dashboard/DashboardActivity.kt b/app/src/main/java/com/arcao/geocaching4locus/dashboard/DashboardActivity.kt index 90b1aae9..9a52e0d7 100644 --- a/app/src/main/java/com/arcao/geocaching4locus/dashboard/DashboardActivity.kt +++ b/app/src/main/java/com/arcao/geocaching4locus/dashboard/DashboardActivity.kt @@ -1,9 +1,12 @@ package com.arcao.geocaching4locus.dashboard +import android.Manifest +import android.os.Build import android.os.Bundle import android.view.Menu import android.view.MenuItem import androidx.activity.result.ActivityResultCallback +import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.widget.Toolbar import androidx.databinding.DataBindingUtil import com.arcao.geocaching4locus.R @@ -14,6 +17,7 @@ import com.arcao.geocaching4locus.base.util.isCalledFromLocusMap import com.arcao.geocaching4locus.base.util.showLocusMissingError import com.arcao.geocaching4locus.base.util.showWebPage import com.arcao.geocaching4locus.base.util.withObserve +import com.arcao.geocaching4locus.dashboard.fragment.NoPostNotificationPermissionErrorDialogFragment import com.arcao.geocaching4locus.databinding.ActivityDashboardBinding import com.arcao.geocaching4locus.download_rectangle.DownloadRectangleActivity import com.arcao.geocaching4locus.import_bookmarks.ImportBookmarkActivity @@ -49,6 +53,17 @@ class DashboardActivity : AbstractActionBarActivity(), if (success) viewModel.onClickLiveMap() } + private val requestPostNotificationPermission = registerForActivityResult( + ActivityResultContracts.RequestPermission() + ) { result -> + if (result) { + viewModel.onClickLiveMap() + } else { + NoPostNotificationPermissionErrorDialogFragment.newInstance() + .show(supportFragmentManager) + } + } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -68,6 +83,7 @@ class DashboardActivity : AbstractActionBarActivity(), is DashboardAction.SearchNearest -> searchNearestActivity.launch( if (isCalledFromLocusMap()) intent else null ) + is DashboardAction.ImportGcCode -> importGeocacheCodeActivity.launch(null) is DashboardAction.DownloadLiveMapGeocaches -> downloadRectangleActivity.launch(null) is DashboardAction.ImportBookmarks -> importBookmarkActivity.launch(null) @@ -77,7 +93,13 @@ class DashboardActivity : AbstractActionBarActivity(), is DashboardAction.SignIn -> loginActivity.launch(null) is DashboardAction.WarnPowerSaveActive -> PowerSaveWarningDialogFragment.newInstance() .show(supportFragmentManager) + is DashboardAction.NavigationBack -> finish() + DashboardAction.RequestPostNotificationPermission -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + requestPostNotificationPermission.launch( + Manifest.permission.POST_NOTIFICATIONS + ) + } } } @@ -92,10 +114,12 @@ class DashboardActivity : AbstractActionBarActivity(), viewModel.onClickPreferences() true } + android.R.id.home -> { viewModel.onClickNavigationBack() true } + else -> super.onOptionsItemSelected(item) } } diff --git a/app/src/main/java/com/arcao/geocaching4locus/dashboard/DashboardViewModel.kt b/app/src/main/java/com/arcao/geocaching4locus/dashboard/DashboardViewModel.kt index 2455febc..21ed8bcd 100644 --- a/app/src/main/java/com/arcao/geocaching4locus/dashboard/DashboardViewModel.kt +++ b/app/src/main/java/com/arcao/geocaching4locus/dashboard/DashboardViewModel.kt @@ -8,6 +8,7 @@ import com.arcao.geocaching4locus.base.BaseViewModel import com.arcao.geocaching4locus.base.coroutine.CoroutinesDispatcherProvider import com.arcao.geocaching4locus.base.util.AnalyticsManager import com.arcao.geocaching4locus.base.util.Command +import com.arcao.geocaching4locus.base.util.hasPostNotificationPermission import com.arcao.geocaching4locus.base.util.hidePowerManagementWarning import com.arcao.geocaching4locus.base.util.invoke import com.arcao.geocaching4locus.data.account.AccountManager @@ -60,6 +61,11 @@ class DashboardViewModel( return@mainLaunch } + if (!context.hasPostNotificationPermission) { + action(DashboardAction.RequestPostNotificationPermission) + return@mainLaunch + } + if (!context.hidePowerManagementWarning) { action(DashboardAction.WarnPowerSaveActive) return@mainLaunch diff --git a/app/src/main/java/com/arcao/geocaching4locus/dashboard/fragment/NoPostNotificationPermissionErrorDialogFragment.kt b/app/src/main/java/com/arcao/geocaching4locus/dashboard/fragment/NoPostNotificationPermissionErrorDialogFragment.kt new file mode 100644 index 00000000..69112fce --- /dev/null +++ b/app/src/main/java/com/arcao/geocaching4locus/dashboard/fragment/NoPostNotificationPermissionErrorDialogFragment.kt @@ -0,0 +1,14 @@ +package com.arcao.geocaching4locus.dashboard.fragment + +import com.arcao.geocaching4locus.R +import com.arcao.geocaching4locus.base.fragment.AbstractErrorDialogFragment + +class NoPostNotificationPermissionErrorDialogFragment : AbstractErrorDialogFragment() { + companion object { + fun newInstance() = NoPostNotificationPermissionErrorDialogFragment().apply { + prepareDialog( + message = R.string.error_no_post_notification_permission + ) + } + } +} diff --git a/app/src/main/java/com/arcao/geocaching4locus/live_map/LiveMapService.kt b/app/src/main/java/com/arcao/geocaching4locus/live_map/LiveMapService.kt index 4651cb7c..a4109108 100644 --- a/app/src/main/java/com/arcao/geocaching4locus/live_map/LiveMapService.kt +++ b/app/src/main/java/com/arcao/geocaching4locus/live_map/LiveMapService.kt @@ -3,6 +3,8 @@ package com.arcao.geocaching4locus.live_map import android.content.ComponentName import android.content.Context import android.content.Intent +import android.content.pm.ServiceInfo +import android.os.Build import androidx.lifecycle.LifecycleService import com.arcao.geocaching4locus.base.ProgressState import com.arcao.geocaching4locus.base.constants.AppConstants @@ -11,6 +13,7 @@ import com.arcao.geocaching4locus.base.util.exhaustive import com.arcao.geocaching4locus.base.util.withObserve import com.arcao.geocaching4locus.live_map.util.LiveMapNotificationManager import org.koin.android.ext.android.inject +import timber.log.Timber class LiveMapService : LifecycleService() { private val notificationManager by inject() @@ -27,10 +30,23 @@ class LiveMapService : LifecycleService() { override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { // in case the service is already running, this must be called after each startForegroundService - startForeground( - AppConstants.NOTIFICATION_ID_LIVEMAP, - notificationManager.createNotification().build() - ) + try { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + startForeground( + AppConstants.NOTIFICATION_ID_LIVEMAP, + notificationManager.createNotification().build(), + ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC + ) + } else { + startForeground( + AppConstants.NOTIFICATION_ID_LIVEMAP, + notificationManager.createNotification().build() + ) + } + } catch (e: Exception) { + // if service is restarted after app kill, startForeground may crash on Android 14 + Timber.e(e) + } if (intent != null) { if (ACTION_START == intent.action) { @@ -60,6 +76,7 @@ class LiveMapService : LifecycleService() { is ProgressState.ShowProgress -> { notificationManager.setDownloadingProgress(state.progress, state.maxProgress) } + is ProgressState.HideProgress -> { notificationManager.setDownloadingProgress(Int.MAX_VALUE, Int.MAX_VALUE) } diff --git a/app/src/main/java/com/arcao/geocaching4locus/live_map/util/LiveMapNotificationManager.kt b/app/src/main/java/com/arcao/geocaching4locus/live_map/util/LiveMapNotificationManager.kt index 1a0aaabc..b888d312 100644 --- a/app/src/main/java/com/arcao/geocaching4locus/live_map/util/LiveMapNotificationManager.kt +++ b/app/src/main/java/com/arcao/geocaching4locus/live_map/util/LiveMapNotificationManager.kt @@ -22,6 +22,7 @@ import com.arcao.geocaching4locus.base.constants.PrefConstants import com.arcao.geocaching4locus.base.coroutine.CoroutinesDispatcherProvider import com.arcao.geocaching4locus.base.usecase.RemoveLocusMapPointsUseCase import com.arcao.geocaching4locus.base.util.getText +import com.arcao.geocaching4locus.base.util.hasPostNotificationPermission import com.arcao.geocaching4locus.error.ErrorActivity import com.arcao.geocaching4locus.live_map.LiveMapService import com.arcao.geocaching4locus.live_map.model.LastLiveMapCoordinates @@ -71,6 +72,11 @@ class LiveMapNotificationManager( showError(R.string.error_live_map_periodic_updates) } + willBeEnabled && !context.hasPostNotificationPermission -> { + willBeEnabled = false + showError(R.string.error_no_post_notification_permission) + } + willBeEnabled -> showLiveMapToast(R.string.toast_live_map_enabled) else -> { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index b94b9ed4..06a12c33 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -344,4 +344,5 @@ Made possible through the support of Geocaching Premium Memberships, the API program gives third-party developers the opportunity to work with Geocaching HQ on a full suite of integrated products and services for the community. API developer applications are designed to work with the core services of geocaching.com and provide additional features to the geocaching community. Continue + The app does not have permission to create notifications.