Skip to content

Commit

Permalink
Better handling of location permissions (#360)
Browse files Browse the repository at this point in the history
* working-ish prototype on the example app

* first pass implementaiton in the SDK and it compiles

* working in the sdk but still need to clean up

* exposed manual setting of listening on activity resume

* change to use activityresultcontract and handle approximate locations

* changes to the example and fix to calls

* clean up implementation

* support reject background perms

* rename

* add a comment

* clean up implementation

* dont break interface

* rename for ios parity

* clean up

* handle user revoke at bg stage

* rename

* rename

* rename

* add field

* version bump

* remove example changes

* update example

* fix plural

* revert example changes

* remove space
  • Loading branch information
KennyHuRadar authored May 31, 2024
1 parent 86fe41c commit a5faae5
Show file tree
Hide file tree
Showing 8 changed files with 355 additions and 3 deletions.
2 changes: 2 additions & 0 deletions MIGRATION.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
# Migration guides
## 3.11.x to 3.12.x
-`RadarReceiver` interface has been changed to include `onLocationPermissionStatusUpdated` method.

## 3.9.x to 3.10.x
- The `fun searchGeofences( radius: Int, tags: Array<String>?, metadata: JSONObject?, limit: Int?, callback: RadarSearchGeofencesCallback)` method is now `fun searchGeofences( radius: Int?, tags: Array<String>?, metadata: JSONObject?, limit: Int?, includeGeometry, Boolean?, callback: RadarSearchGeofencesCallback)` and `fun searchGeofences( near:Location, radius: Int, tags: Array<String>?, metadata: JSONObject?, limit: Int?, callback: RadarSearchGeofencesCallback)` is now `fun searchGeofences( near:Location, radius: Int?, tags: Array<String>?, metadata: JSONObject?, limit: Int?, includeGeometry, Boolean?, callback: RadarSearchGeofencesCallback)`. `radius` is now optional and `includeGeometry` needs to be set if you wish your returned geofence objects to include the full geometry of polygon geofences.
Expand Down
4 changes: 2 additions & 2 deletions example/src/main/java/io/radar/example/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,18 @@ import android.Manifest
import android.content.Context
import android.location.Location
import android.os.Build
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import android.widget.Button
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import io.radar.sdk.Radar
import io.radar.sdk.RadarTrackingOptions
import io.radar.sdk.RadarTripOptions
import io.radar.sdk.RadarVerifiedReceiver
import org.json.JSONObject
import java.util.*
import java.util.EnumSet

class MainActivity : AppCompatActivity() {

Expand Down
10 changes: 10 additions & 0 deletions example/src/main/java/io/radar/example/MyRadarReceiver.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import io.radar.sdk.Radar
import io.radar.sdk.RadarReceiver
import io.radar.sdk.model.RadarEvent
import io.radar.sdk.model.RadarUser
import io.radar.sdk.model.RadarLocationPermissionStatus
import kotlin.random.Random

class MyRadarReceiver : RadarReceiver() {
Expand Down Expand Up @@ -69,4 +70,13 @@ class MyRadarReceiver : RadarReceiver() {
notify(context, message)
}

override fun onLocationPermissionStatusUpdated(
context: Context,
status: RadarLocationPermissionStatus
) {
val statusString = RadarLocationPermissionStatus.stringForLocationPermissionState(status.status)
val body = "Location permission status updated: ${statusString}"
notify(context, body)
}

}
4 changes: 3 additions & 1 deletion sdk/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ apply plugin: "org.jetbrains.dokka"
apply plugin: 'io.radar.mvnpublish'

ext {
radarVersion = '3.11.1'
radarVersion = '3.12.0'
}

String buildNumber = ".${System.currentTimeMillis()}"
Expand Down Expand Up @@ -67,6 +67,8 @@ dependencies {
implementation "androidx.appcompat:appcompat:1.4.0"
implementation "androidx.core:core-ktx:1.7.0"
implementation "com.google.android.gms:play-services-location:21.0.1"
implementation "androidx.activity:activity:1.2.0"
implementation "androidx.fragment:fragment:1.3.0"
compileOnly "com.huawei.hms:location:6.4.0.300"
compileOnly "com.google.android.play:integrity:1.2.0"
testImplementation "androidx.test.ext:junit:1.1.5"
Expand Down
49 changes: 49 additions & 0 deletions sdk/src/main/java/io/radar/sdk/Radar.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.radar.sdk

import android.annotation.SuppressLint
import android.app.Activity
import android.app.Application
import android.content.Context
import android.location.Location
Expand Down Expand Up @@ -424,6 +425,7 @@ object Radar {
internal var initialized = false
internal var isFlushingReplays = false
private lateinit var context: Context
private var activity: Activity? = null
internal lateinit var handler: Handler
private var receiver: RadarReceiver? = null
private var verifiedReceiver: RadarVerifiedReceiver? = null
Expand All @@ -435,6 +437,7 @@ object Radar {
private lateinit var replayBuffer: RadarReplayBuffer
internal lateinit var batteryManager: RadarBatteryManager
private lateinit var verificationManager: RadarVerificationManager
private lateinit var locationPermissionManager: RadarLocationPermissionManager

/**
* Initializes the Radar SDK. Call this method from the main thread in `Application.onCreate()` before calling any other Radar methods.
Expand Down Expand Up @@ -469,6 +472,10 @@ object Radar {
this.context = context.applicationContext
this.handler = Handler(this.context.mainLooper)

if (context is Activity) {
this.activity = context
}

if (receiver != null) {
this.receiver = receiver
}
Expand Down Expand Up @@ -528,6 +535,9 @@ object Radar {
}
application?.registerActivityLifecycleCallbacks(RadarActivityLifecycleCallbacks(fraud))

locationPermissionManager = RadarLocationPermissionManager(this.context, this.activity)
application?.registerActivityLifecycleCallbacks(locationPermissionManager)


val featureSettings = RadarSettings.getFeatureSettings(this.context)
if (featureSettings.usePersistence) {
Expand Down Expand Up @@ -3080,6 +3090,39 @@ object Radar {
})
}
}
/**
* Requests foreground location permissions.
*/
@JvmStatic
fun requestForegroundLocationPermission() {
locationPermissionManager.requestForegroundLocationPermission()
}

/**
* Requests background location permissions.
*/
@JvmStatic
fun requestBackgroundLocationPermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
locationPermissionManager.requestBackgroundLocationPermission()
}
}

/**
* @return A RadarPermissionStatus object with the current location permissions status.
*/
@JvmStatic
fun getLocationPermissionStatus():RadarLocationPermissionStatus {
return locationPermissionManager.getLocationPermissionStatus()
}

/**
* Directs the user to the app settings to enable location permissions.
*/
@JvmStatic
fun openAppSettings() {
locationPermissionManager.openAppSettings()
}

/**
* Sets the log level for debug logs.
Expand Down Expand Up @@ -3421,6 +3464,12 @@ object Radar {
logger.i("📍️ Radar token updated | token = $token")
}

internal fun sendLocationPermissionStatus(status: RadarLocationPermissionStatus) {
receiver?.onLocationPermissionStatusUpdated(context, status)

logger.i("📍️ Radar location permission updated | status = $status")
}

internal fun setLogPersistenceFeatureFlag(enabled: Boolean) {
this.logBuffer.setPersistentLogFeatureFlag(enabled)
}
Expand Down
116 changes: 116 additions & 0 deletions sdk/src/main/java/io/radar/sdk/RadarLocationPermissionManager.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package io.radar.sdk

import android.Manifest
import android.app.Activity
import android.app.Application
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.provider.Settings
import androidx.activity.ComponentActivity
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.RequiresApi
import io.radar.sdk.model.RadarLocationPermissionStatus

class RadarLocationPermissionManager(private val context: Context, private val activity: Activity?): Application.ActivityLifecycleCallbacks {

private var danglingBackgroundPermissionRequest = false

private lateinit var requestForegroundLocationPermissionLauncher: ActivityResultLauncher<String>

private lateinit var requestBackgroundLocationPermissionLauncher: ActivityResultLauncher<String>

init {
if (activity is ComponentActivity) {
requestForegroundLocationPermissionLauncher = activity.registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { isGranted: Boolean ->
if (!isGranted) {
RadarLocationPermissionStatus.savePreviouslyDeniedForeground(context,true)
}
Radar.sendLocationPermissionStatus(RadarLocationPermissionStatus.initWithStatus(context, activity))
// TODO: sync the user's permissions status here
}

requestBackgroundLocationPermissionLauncher = activity.registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { isGranted: Boolean ->
if (!isGranted) {
RadarLocationPermissionStatus.savePreviouslyDeniedBackground(context, true)
}
Radar.sendLocationPermissionStatus(RadarLocationPermissionStatus.initWithStatus(context, activity))
// TODO: sync the user's permissions status here
}
}
}

@RequiresApi(Build.VERSION_CODES.Q)
fun requestBackgroundLocationPermission() {
if (activity is ComponentActivity) {
requestBackgroundLocationPermissionLauncher.launch(Manifest.permission.ACCESS_BACKGROUND_LOCATION)
// TODO: sync the user's location permissions action with the their permissions status here
}
}

fun requestForegroundLocationPermission() {
if (activity is ComponentActivity) {
Radar.sendLocationPermissionStatus(RadarLocationPermissionStatus.initWithStatus(context,activity,true))
requestForegroundLocationPermissionLauncher.launch(Manifest.permission.ACCESS_FINE_LOCATION)
// TODO: sync the user's location permissions action with the their permissions status here
}
}

fun openAppSettings() {
danglingBackgroundPermissionRequest = true
val intent = Intent().apply {
action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS
val uri = Uri.fromParts("package", context.packageName, null)
data = uri
flags = Intent.FLAG_ACTIVITY_NEW_TASK
}
context.startActivity(intent)
}

fun getLocationPermissionStatus(): RadarLocationPermissionStatus {
if (activity is ComponentActivity) {
return RadarLocationPermissionStatus.initWithStatus(context, activity)
}
return RadarLocationPermissionStatus()
}

override fun onActivityPaused(activity: Activity) {
// do nothing
}

override fun onActivityStopped(p0: Activity) {
// do nothing
}

override fun onActivitySaveInstanceState(p0: Activity, p1: Bundle) {
// do nothing
}

override fun onActivityDestroyed(p0: Activity) {
// do nothing
}

override fun onActivityCreated(p0: Activity, p1: Bundle?) {
// do nothing
}

override fun onActivityStarted(p0: Activity) {
// do nothing
}

override fun onActivityResumed(activity: Activity) {
if (danglingBackgroundPermissionRequest) {
Radar.sendLocationPermissionStatus(RadarLocationPermissionStatus.initWithStatus(context,activity))
// TODO: sync the user's location permissions action with the their permissions status here
}
danglingBackgroundPermissionRequest = false
}

}
9 changes: 9 additions & 0 deletions sdk/src/main/java/io/radar/sdk/RadarReceiver.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import android.content.Context
import android.location.Location
import io.radar.sdk.Radar.RadarStatus
import io.radar.sdk.model.RadarEvent
import io.radar.sdk.model.RadarLocationPermissionStatus
import io.radar.sdk.model.RadarUser

/**
Expand Down Expand Up @@ -56,4 +57,12 @@ abstract class RadarReceiver {
*/
abstract fun onLog(context: Context, message: String)

/**
* Tells the reciever that the location permissions status was updated.
*
* @param[context] The context.
* @param[status] The location permissions status.
*/
abstract fun onLocationPermissionStatusUpdated(context: Context, status: RadarLocationPermissionStatus)

}
Loading

0 comments on commit a5faae5

Please sign in to comment.