Skip to content

Commit c3c0f2c

Browse files
committed
[mage-1435] (#83)
1) Add logic to request POST_NOTIFICATIONS permission to fix defect with notifications not being shown for Android 13+. 2) Fix defect with notifications not being shown on Android versions below 13. 3) Create first-time launch permissions handling to request both location and notifications permissions. Modify logic to not repeatedly request location permission on each app launch.
1 parent ecd2813 commit c3c0f2c

File tree

12 files changed

+266
-154
lines changed

12 files changed

+266
-154
lines changed

mage/src/main/java/mil/nga/giat/mage/LandingActivity.kt

Lines changed: 85 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,21 @@
11
package mil.nga.giat.mage
22

3+
import android.Manifest
34
import android.content.Intent
45
import android.content.pm.PackageManager
56
import android.content.res.Configuration
67
import android.net.Uri
8+
import android.os.Build
79
import android.os.Bundle
810
import android.view.Menu
911
import android.view.MenuItem
1012
import android.view.View
1113
import android.widget.ImageView
1214
import android.widget.TextView
1315
import androidx.activity.result.ActivityResult
14-
import androidx.activity.result.ActivityResultLauncher
1516
import androidx.activity.result.contract.ActivityResultContracts
1617
import androidx.appcompat.app.AppCompatActivity
18+
import androidx.core.app.NotificationManagerCompat
1719
import androidx.core.view.GravityCompat
1820
import androidx.fragment.app.Fragment
1921
import androidx.lifecycle.ViewModelProvider
@@ -41,8 +43,6 @@ import mil.nga.giat.mage.glide.GlideApp
4143
import mil.nga.giat.mage.glide.model.Avatar.Companion.forUser
4244
import mil.nga.giat.mage.help.HelpActivity
4345
import mil.nga.giat.mage.location.LocationAccess
44-
import mil.nga.giat.mage.location.LocationContractResult
45-
import mil.nga.giat.mage.location.LocationPermission
4646
import mil.nga.giat.mage.login.LoginActivity
4747
import mil.nga.giat.mage.map.MapFragment
4848
import mil.nga.giat.mage.map.cache.CacheProvider
@@ -53,6 +53,7 @@ import mil.nga.giat.mage.profile.ProfileActivity
5353
import org.apache.commons.lang3.StringUtils
5454
import java.io.File
5555
import javax.inject.Inject
56+
import androidx.core.content.edit
5657

5758

5859
/**
@@ -82,13 +83,84 @@ class LandingActivity : AppCompatActivity(), NavigationView.OnNavigationItemSele
8283
}
8384
}
8485

85-
private var reportLocationIntent: ActivityResultLauncher<*> = registerForActivityResult(
86-
LocationPermission()
87-
) { (coarseGranted, preciseGranted): LocationContractResult ->
88-
if (preciseGranted || coarseGranted) {
89-
application.startLocationService()
90-
} else {
91-
application.stopLocationService()
86+
//request location and notifications permissions when the app is launched from a fresh install
87+
private fun requestPermissionsOnFirstLaunch() {
88+
if (!PreferenceManager.getDefaultSharedPreferences(this).getBoolean(getString(R.string.havePermissionsBeenPrompted), false)) {
89+
val activityResultLauncher =
90+
registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions ->
91+
var coarseOrPreciseLocationGranted = false
92+
var notificationsGranted = false
93+
94+
permissions.entries.forEach {
95+
val permissionName = it.key
96+
val isGranted = it.value
97+
98+
if (isGranted) {
99+
if (permissionName.contentEquals(Manifest.permission.ACCESS_COARSE_LOCATION)) {
100+
coarseOrPreciseLocationGranted = true;
101+
} else if (permissionName.contentEquals(Manifest.permission.ACCESS_FINE_LOCATION)) {
102+
coarseOrPreciseLocationGranted = true;
103+
} else if (permissionName.contentEquals(Manifest.permission.POST_NOTIFICATIONS)) {
104+
notificationsGranted = true;
105+
}
106+
}
107+
}
108+
109+
if (coarseOrPreciseLocationGranted) {
110+
PreferenceManager.getDefaultSharedPreferences(this).edit {
111+
putBoolean(resources.getString(R.string.reportLocationKey), true)
112+
}
113+
}
114+
115+
//update notification preferences for Android versions 13 or above
116+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
117+
if (notificationsGranted) {
118+
//permission granted, update the notifications state to enabled
119+
PreferenceManager.getDefaultSharedPreferences(this).edit {
120+
putBoolean(resources.getString(R.string.notificationsEnabledKey), true)
121+
}
122+
} else {
123+
//permission denied, update the notifications state to disabled
124+
PreferenceManager.getDefaultSharedPreferences(this).edit {
125+
putBoolean(resources.getString(R.string.notificationsEnabledKey), false)
126+
}
127+
}
128+
} else {
129+
//check if notifications are enabled for Android versions below 13
130+
val notificationManager = NotificationManagerCompat.from(this)
131+
if (notificationManager.areNotificationsEnabled()) {
132+
//notifications enabled, update the notifications state to enabled
133+
PreferenceManager.getDefaultSharedPreferences(this).edit {
134+
putBoolean(resources.getString(R.string.notificationsEnabledKey), true)
135+
}
136+
} else {
137+
//notifications not enabled, update the notifications state to disabled
138+
PreferenceManager.getDefaultSharedPreferences(this).edit {
139+
putBoolean(resources.getString(R.string.notificationsEnabledKey), false)
140+
}
141+
}
142+
}
143+
}
144+
145+
//only attempt to request POST_NOTIFICATIONS permission for Android versions 13 or above
146+
val permissionsToRequest = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
147+
arrayOf(
148+
Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION,
149+
Manifest.permission.POST_NOTIFICATIONS
150+
)
151+
} else {
152+
arrayOf(
153+
Manifest.permission.ACCESS_FINE_LOCATION,
154+
Manifest.permission.ACCESS_COARSE_LOCATION
155+
)
156+
}
157+
158+
activityResultLauncher.launch(permissionsToRequest)
159+
160+
//update flag so that these permissions are requested once and not on every app launch
161+
PreferenceManager.getDefaultSharedPreferences(this).edit() {
162+
putBoolean(getString(R.string.havePermissionsBeenPrompted), true)
163+
}
92164
}
93165
}
94166

@@ -120,7 +192,9 @@ class LandingActivity : AppCompatActivity(), NavigationView.OnNavigationItemSele
120192
setRecentEvents(event)
121193
}
122194
setSupportActionBar(binding!!.toolbar)
123-
reportLocationIntent.launch(null)
195+
196+
requestPermissionsOnFirstLaunch()
197+
124198
binding!!.toolbar.setNavigationIcon(R.drawable.ic_menu_white_24dp)
125199
binding!!.toolbar.setNavigationOnClickListener {
126200
binding!!.drawerLayout.openDrawer(

mage/src/main/java/mil/nga/giat/mage/data/repository/observation/ObservationRepository.kt

Lines changed: 13 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
11
package mil.nga.giat.mage.data.repository.observation
22

33
import android.Manifest
4+
import android.annotation.SuppressLint
45
import android.app.PendingIntent
56
import android.app.PendingIntent.FLAG_IMMUTABLE
67
import android.content.Context
78
import android.content.Intent
89
import android.content.SharedPreferences
910
import android.content.pm.PackageManager
11+
import android.os.Build
1012
import android.util.Log
11-
import androidx.core.app.ActivityCompat
1213
import androidx.core.app.NotificationCompat
1314
import androidx.core.app.NotificationManagerCompat
15+
import androidx.core.content.ContextCompat
1416
import androidx.preference.PreferenceManager
1517
import com.google.gson.JsonObject
1618
import com.google.gson.JsonParser
@@ -46,6 +48,7 @@ import mil.nga.giat.mage.data.datasource.user.UserLocalDataSource
4648
import mil.nga.giat.mage.database.model.observation.ObservationImportant
4749
import mil.nga.giat.mage.sdk.event.IObservationEventListener
4850
import mil.nga.giat.mage.sdk.utils.ISO8601DateFormatFactory
51+
import mil.nga.giat.mage.utils.NotificationUtils
4952
import okhttp3.ResponseBody
5053
import retrofit2.Response
5154
import java.io.IOException
@@ -404,18 +407,20 @@ class ObservationRepository @Inject constructor(
404407
}
405408

406409
if (notify) {
407-
createNotifications(fetched)
410+
createNotificationsIfEligible(fetched)
408411
}
409412
}
410413

411-
private fun createNotifications(observations: Collection<Observation>) {
414+
@SuppressLint("MissingPermission")
415+
private fun createNotificationsIfEligible(observations: Collection<Observation>) {
412416
val preferences = PreferenceManager.getDefaultSharedPreferences(context)
413-
val notificationsEnabled = preferences.getBoolean(context.getString(R.string.notificationsEnabledKey), context.resources.getBoolean(R.bool.notificationsEnabledDefaultValue))
414-
if (observations.isEmpty() || !notificationsEnabled) {
417+
val canSendNotifications = NotificationUtils.canSendNotifications(preferences, context)
418+
419+
//do not create a notification if observations list is empty, the user has disabled notifications within Mage, or the required permission is not granted
420+
if (observations.isEmpty() || !canSendNotifications) {
415421
return
416422
}
417423

418-
val notificationManager = NotificationManagerCompat.from(context)
419424
val groupNotification = NotificationCompat.Builder(context, MageApplication.MAGE_OBSERVATION_NOTIFICATION_CHANNEL_ID)
420425
.setGroupSummary(true)
421426
.setContentTitle("New MAGE Observations")
@@ -424,22 +429,8 @@ class ObservationRepository @Inject constructor(
424429
.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_SUMMARY)
425430
.setGroup(MageApplication.MAGE_OBSERVATION_NOTIFICATION_GROUP)
426431

427-
if (ActivityCompat.checkSelfPermission(
428-
context,
429-
Manifest.permission.POST_NOTIFICATIONS
430-
) != PackageManager.PERMISSION_GRANTED
431-
) {
432-
// TODO: Consider calling
433-
// ActivityCompat#requestPermissions
434-
// here to request the missing permissions, and then overriding
435-
// public void onRequestPermissionsResult(int requestCode, String[] permissions,
436-
// int[] grantResults)
437-
// to handle the case where the user grants the permission. See the documentation
438-
// for ActivityCompat#requestPermissions for more details.
439-
return
440-
} else {
441-
notificationManager.notify(MageApplication.MAGE_OBSERVATION_NOTIFICATION_PREFIX, groupNotification.build())
442-
}
432+
val notificationManager = NotificationManagerCompat.from(context)
433+
notificationManager.notify(MageApplication.MAGE_OBSERVATION_NOTIFICATION_PREFIX, groupNotification.build())
443434

444435
observations.forEach { observation ->
445436
val intent = Intent(context, LandingActivity::class.java)

mage/src/main/java/mil/nga/giat/mage/location/LocationPermission.kt

Lines changed: 0 additions & 51 deletions
This file was deleted.

mage/src/main/java/mil/nga/giat/mage/location/LocationReportingService.kt

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -96,10 +96,13 @@ open class LocationReportingService : LifecycleService(), Observer<Location>, Sh
9696

9797
override fun onDestroy() {
9898
super.onDestroy()
99-
100-
locationProvider.removeObserver(this)
101-
locationChannel.close()
102-
preferences.unregisterOnSharedPreferenceChangeListener(this)
99+
try {
100+
locationProvider.removeObserver(this)
101+
locationChannel.close()
102+
preferences.unregisterOnSharedPreferenceChangeListener(this)
103+
} catch (e: Exception) {
104+
Log.d(LOG_NAME, "Error shutting down service: " + e.message)
105+
}
103106
}
104107

105108
override fun onChanged(value: Location) {

mage/src/main/java/mil/nga/giat/mage/observation/ObservationNotificationListener.java

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import mil.nga.giat.mage.R;
1616
import mil.nga.giat.mage.database.model.observation.Observation;
1717
import mil.nga.giat.mage.sdk.event.IObservationEventListener;
18+
import mil.nga.giat.mage.utils.NotificationUtils;
1819

1920
/**
2021
* This class is responsible for responding to Observation events and dispatching notifications to
@@ -39,26 +40,20 @@ public ObservationNotificationListener(Context context) {
3940

4041
@Override
4142
public void onObservationCreated(Collection<Observation> observations, Boolean sendNotifications) {
43+
//determine if notifications are enabled within Mage and the necessary permissions are in place
44+
Boolean canSendNotifications = NotificationUtils.INSTANCE.canSendNotifications(preferences, context);
4245

43-
if(sendNotifications != null && sendNotifications) {
44-
// are we configured to fire notifications?
45-
boolean notificationsEnabled = preferences.getBoolean(context.getString(R.string.notificationsEnabledKey), context.getResources().getBoolean(R.bool.notificationsEnabledDefaultValue));
46-
47-
// are any of the observations remote? We don't want to fire on locally created
48-
// observations.
46+
if(sendNotifications && canSendNotifications) {
47+
// are any of the observations remote? We don't want to fire on locally created observations.
4948
boolean remoteObservations = Boolean.FALSE;
50-
if (notificationsEnabled) {
51-
for (Observation obs : observations) {
52-
if (obs.getRemoteId() != null) {
53-
remoteObservations = true;
54-
break;
55-
}
49+
for (Observation obs : observations) {
50+
if (obs.getRemoteId() != null) {
51+
remoteObservations = true;
52+
break;
5653
}
5754
}
5855

59-
// Should a notification be presented to the user?
60-
if (notificationsEnabled && remoteObservations && !observations.isEmpty()) {
61-
56+
if (remoteObservations && !observations.isEmpty()) {
6257
// Build intent for notification content
6358
Intent viewIntent = new Intent(context, LandingActivity.class);
6459
//viewIntent.putExtra(EXTRA_EVENT_ID, eventId);

mage/src/main/java/mil/nga/giat/mage/observation/edit/ObservationEditActivity.kt

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import android.content.Intent
55
import android.net.Uri
66
import android.os.Bundle
77
import android.os.Environment
8-
import android.provider.Settings
98
import android.util.Log
109
import android.webkit.MimeTypeMap
1110
import android.widget.Toast
@@ -48,6 +47,7 @@ import mil.nga.giat.mage.sdk.Compatibility.Companion.isServerVersion5
4847
import mil.nga.giat.mage.database.model.observation.Attachment
4948
import mil.nga.giat.mage.data.datasource.event.EventLocalDataSource
5049
import mil.nga.giat.mage.sdk.utils.MediaUtility
50+
import mil.nga.giat.mage.utils.DialogUtils
5151
import mil.nga.sf.Point
5252
import java.io.File
5353
import java.io.IOException
@@ -219,19 +219,6 @@ open class ObservationEditActivity : AppCompatActivity() {
219219
currentMediaPath = savedInstanceState.getString(CURRENT_MEDIA_PATH)
220220
}
221221

222-
private fun showDisabledPermissionsDialog(title: String, message: String) {
223-
AlertDialog.Builder(this)
224-
.setTitle(title)
225-
.setMessage(message)
226-
.setPositiveButton(R.string.settings) { _, _ ->
227-
val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
228-
intent.data = Uri.fromParts("package", applicationContext.packageName, null)
229-
startActivity(intent)
230-
}
231-
.setNegativeButton(android.R.string.cancel, null)
232-
.show()
233-
}
234-
235222
private fun save() {
236223
if (viewModel.saveObservation()) {
237224
finish()
@@ -255,9 +242,7 @@ open class ObservationEditActivity : AppCompatActivity() {
255242
is PermissionRequest.Audio -> launchAudioIntent()
256243
}
257244
} else if (!ActivityCompat.shouldShowRequestPermissionRationale(this, request.permission)) {
258-
showDisabledPermissionsDialog(
259-
resources.getString(request.deniedTitleResourceId),
260-
resources.getString(request.deniedMessageResourceId))
245+
DialogUtils.showDialogForDisabledPermission(this, resources.getString(request.deniedTitleResourceId), resources.getString(request.deniedMessageResourceId))
261246
}
262247
}
263248

0 commit comments

Comments
 (0)