将位置设置为“仅在使用中”时允许时,地理林无法添加地理林。

发布于 2025-01-26 08:28:23 字数 16577 浏览 2 评论 0原文

我们已经实施了一个地理围栏系统,我们的CMS后端操作员可以通过FCM将Geofence数据发送到应用程序。在您在下面看到的Firebaseservice类中,每次发送此类通知时,它都会触发handleslentnotification方法,然后尝试创建一个地理位置对象并将其添加到Geodencing Client。

我们要求在入职过程中访问位置访问,如果未授予位置权限,我们只是忽略了无声通知有效负载。当用户选择“允许所有时间”的位置权限选项时,一切似乎都可以正常工作。收到有效载荷,并创建地理位置并将其添加到客户端以进行跟踪。 当用户选择“仅在使用中允许”时,就会发生问题。

在这种情况下,当我们尝试将Geofence添加到GeoFencingClient中时,它总是通过抛出以下堆栈跟踪而失败:

2022-05-05 11:27:48.107 13685-13685/com.threenitas.ewayprototype I/EwayApp: New location received: 37.95690471,23.72790214
2022-05-05 11:27:55.580 13685-14907/com.threenitas.ewayprototype D/MyFirebaseMessagingService: body = "{\"Id\":\"1234654\",\"Latitude\":37.3495,\"Longitude\":23.4646,\"Radius\":100,\"StatusCode\":\"EnableEvent\",\"Title\":\"test\",\"Description\":\"test\"}"
2022-05-05 11:27:55.593 13685-14907/com.threenitas.ewayprototype D/MyFirebaseMessagingService: New Geofence created: Geofence[CIRCLE id:1234654 transitions:3 37.349500, 23.464600 9999999933815813000000000000000000000m, resp=0s, dwell=-1ms, @-1]
2022-05-05 11:27:55.616 13685-13685/com.threenitas.ewayprototype D/GeofencingManager: Failed to add geofence with id 1234654 to client!
2022-05-05 11:27:55.616 13685-13685/com.threenitas.ewayprototype W/System.err: com.google.android.gms.common.api.ApiException: 1004:
2022-05-05 11:27:55.617 13685-13685/com.threenitas.ewayprototype W/System.err: at com.google.android.gms.common.api.internal.TaskUtil.setResultOrApiException(com.google.android.gms:play-services-base@@18.0.1:4)
2022-05-05 11:27:55.617 13685-13685/com.threenitas.ewayprototype W/System.err: at com.google.android.gms.location.zzat.setResult(com.google.android.gms:play-services-location@@18.0.0:2)
2022-05-05 11:27:55.617 13685-13685/com.threenitas.ewayprototype W/System.err: at com.google.android.gms.internal.location.zzaw.zzb(com.google.android.gms:play-services-location@@18.0.0:3)
2022-05-05 11:27:55.617 13685-13685/com.threenitas.ewayprototype W/System.err: at com.google.android.gms.internal.location.zzaj.zza(com.google.android.gms:play-services-location@@18.0.0:9)
2022-05-05 11:27:55.617 13685-13685/com.threenitas.ewayprototype W/System.err: at com.google.android.gms.internal.location.zzb.onTransact(com.google.android.gms:play-services-location@@18.0.0:3)
2022-05-05 11:27:55.617 13685-13685/com.threenitas.ewayprototype W/System.err: at android.os.Binder.execTransactInternal(Binder.java:1195)
2022-05-05 11:27:55.617 13685-13685/com.threenitas.ewayprototype W/System.err: at android.os.Binder.execTransact(Binder.java:1159)
2022-05-05 11:27:58.087 13685-14822/com.threenitas.ewayprototype W/s.ewayprototyp: Suspending all threads took: 6.101ms
2022-05-05 11:28:06.071 13685-14822/com.threenitas.ewayprototype W/s.ewayprototyp: Suspending all threads took: 5.875ms
2022-05-05 11:28:06.089 13685-14822/com.threenitas.ewayprototype W/s.ewayprototyp: Suspending all threads took: 8.474ms
2022-05-05 11:28:06.106 13685-14822/com.threenitas.ewayprototype W/s.ewayprototyp: Suspending all threads took: 6.250ms
2022-05-05 11:28:06.136 13685-14822/com.threenitas.ewayprototype W/s.ewayprototyp: Suspending all threads took: 6.388ms

这是Google结束的设计决定,例如此错误跟踪器帖子似乎声称或我在实现中缺少一些东西?

app.kt:

class App : Application() {

    lateinit var appContext: Context
    lateinit var preferences: SharedPreferences
    lateinit var editor: SharedPreferences.Editor
    lateinit var geofencingClient: GeofencingClient
    val inactiveGeofenceIds = arrayListOf<String>()
    val geofenceEventInfo = mutableMapOf<String, GeoEventData>()
    val geofencePendingIntent: PendingIntent by lazy {
        val intent = Intent(appContext, GeofenceBroadcastReceiver::class.java)
        // We use FLAG_UPDATE_CURRENT so that we get the same pending intent back when calling
        // addGeofences() and removeGeofences().
        PendingIntent.getBroadcast(appContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
    }
    val locationListener: LocationListener = object : LocationListener {
        override fun onLocationChanged(location: Location) {
            Log.i("EwayApp", "New location received: ${location.latitude},${location.longitude}")
        }

        override fun onLocationChanged(locations: MutableList<Location>) {}

        override fun onStatusChanged(provider: String?, status: Int, extras: Bundle?) {}

        override fun onProviderEnabled(provider: String) {}

        override fun onProviderDisabled(provider: String) {}
    }

    fun isGeofencingClientInitialized() = this::geofencingClient.isInitialized
...........
}

firbasemessagingservice:

class MyFirebaseMessagingService : FirebaseMessagingService() {
    private val TAG = MyFirebaseMessagingService::class.java.simpleName
    private var geofencingManager: GeofencingManager = getKoin().get()

    override fun onNewToken(token: String) {
        super.onNewToken(token)
        NotificationHub.getInstance(App.instance)
            .registerForPushNotifications(this, fetchIntegrationIdForLetsPlace())
    }


    override fun onMessageReceived(remoteMessage: RemoteMessage) {
        // Check if message contains a data payload
        remoteMessage.data.isNotEmpty().let {
            handleNotification(remoteMessage.data)
        }
    }

    private fun handleNotification(jsonData: MutableMap<String, String>) {
        val title = jsonData["Title"]
        val description = jsonData["Description"]
        val body = jsonData["Body"]
        if (title!!.isEmpty()) {
            if (hasLocationPermission()) handleSilentNotification(body)
            else Log.d(TAG, "No location permission has been granted!")
        } else {
            showNotification(
                title,
                description!!
            )
        }
    }

    private fun hasLocationPermission() = ContextCompat.checkSelfPermission(
        App.instance.appContext,
        Manifest.permission.ACCESS_FINE_LOCATION
    ) == PackageManager.PERMISSION_GRANTED

    private fun showNotification(
        title: String,
        description: String
    ) {
//        val intentForNotification = Intent(application, SplashScreen::class.java)
//        intentForNotification.putExtra("notificationInboxBody", inboxBody)
//        intentForNotification.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)

        val hub = NotificationHub.getInstance(App.instance)
        val id = hub.lastRandomId ?: 0
        hub.lastRandomId = id + 1
        //val pendingIntent = PendingIntent.getActivity(application, hub.lastRandomId!! /* Request code */, intentForNotification, PendingIntent.FLAG_UPDATE_CURRENT)
        val defaultSoundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)
        val notificationBuilder = NotificationCompat.Builder(application, "channel1")
            .setSmallIcon(R.drawable.splash_screen_logo)
            .setContentTitle(title)
            .setAutoCancel(true)
            .setSound(defaultSoundUri)
            .setPriority(Notification.PRIORITY_HIGH)
            .setCategory(Notification.CATEGORY_MESSAGE)
            .setDefaults(Notification.DEFAULT_ALL)
            //.setContentIntent(pendingIntent)
            .setContentText(description)
        with(NotificationManagerCompat.from(this)) {
            // notificationId is a unique int for each notification that you must define
            notify(id, notificationBuilder.build())
        }
    }

    private fun handleSilentNotification(body: String?) {
        Log.d(TAG, "body = $body")
        val geoEventData = jsonToGeoEvent(body)
        if (geoEventData == null) {
            Log.d(TAG, "Geo event data conversion failed!")
            return;
        }

        if (!App.instance.isGeofencingClientInitialized()) { // app is killed , store the payload for later use
            Log.d(TAG, "persist called")
            persistGeoData(geoEventData)
            return
        }

        if (geoEventData.statusCode == GeoEventStatus.DisableEvent.name) {
            geofencingManager.removeGeofence(geoEventData.id)
            return
        }
        val geofence = geofencingManager.createGeofence(geoEventData)
        geofencingManager.storeGeofenceInfo(geofence, geoEventData)
        Log.d(TAG, "New Geofence created: $geofence")
        geofencingManager.addGeofenceToClient(geofence)
    }

    private fun persistGeoData(geoEventData: GeoEventData) {
        val gson = Gson()
        val serializedData = gson.toJson(geoEventData, GeoEventData::class.java)
        App.instance.inactiveGeofenceIds.add(geoEventData.id)
        App.instance.preferences.edit().putString(geoEventData.id, serializedData).apply()
    }

    private fun jsonToGeoEvent(body: String?): GeoEventData? {
        if (body == null) return null
        val gson = Gson()
        val json = body.replace("\\", "")
        val jo = JSONObject(json.substring(1, json.length - 1))
        return gson.fromJson(jo.toString(), GeoEventData::class.java)
    }
}

geofencingManagerimpl:

class GeofencingManagerImpl : GeofencingManager {
    private val tag: String
        get() = GeofencingManager::class.java.simpleName

    @SuppressLint("MissingPermission")
    override fun addGeofenceToClient(geofence: Geofence) {
        App.instance.geofencingClient.addGeofences(
            getGeofencingRequest(geofence),
            App.instance.geofencePendingIntent
        ).run {
            addOnSuccessListener {
                Log.d(tag, "Geofence with id ${geofence.requestId} added to client!")
            }
            addOnFailureListener {
                Log.d(tag, "Failed to add geofence with id ${geofence.requestId} to client!")
                it.printStackTrace()
            }
        }
    }

    override fun createGeofence(geoEventData: GeoEventData) =
        Geofence.Builder()
            .setRequestId(geoEventData.id)
            .setCircularRegion(
                geoEventData.latitude,
                geoEventData.longitude,
                geoEventData.radius
            )
            .setExpirationDuration(Geofence.NEVER_EXPIRE)
            .setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER or Geofence.GEOFENCE_TRANSITION_EXIT)
            .build()

    override fun storeGeofenceInfo(
        geofence: Geofence,
        geoEventData: GeoEventData
    ) {
        App.instance.geofenceEventInfo[geofence.requestId] = geoEventData
    }

    override fun getGeofencingRequest(geofence: Geofence): GeofencingRequest {
        return GeofencingRequest.Builder()
            .setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER)
            .addGeofence(geofence)
            .build()
    }

    override fun removeGeofence(id: String) {
        App.instance.geofenceEventInfo.remove(id)
        App.instance.geofencingClient.removeGeofences(listOf(id)).run {
            addOnSuccessListener {
                Log.d(tag, "Geofence with id $id removed from client!")
            }
            addOnFailureListener {
                Log.d(tag, "Failed to remove geofence with id $id from client!")
                it.printStackTrace()
            }
        }
    }
}

Broadcastreceiver:

class GeofenceBroadcastReceiver : BroadcastReceiver() {
    private val TAG = GeofenceBroadcastReceiver::class.java.simpleName

    override fun onReceive(context: Context?, intent: Intent?) {
        val geofencingEvent = GeofencingEvent.fromIntent(intent!!)
        if (geofencingEvent.hasError()) {
            val errorMessage = GeofenceStatusCodes
                .getStatusCodeString(geofencingEvent.errorCode)
            Log.e(TAG, errorMessage)
            return
        }
        val geofenceTransition = geofencingEvent.geofenceTransition

        if (geofenceTransition == Geofence.GEOFENCE_TRANSITION_ENTER ||
            geofenceTransition == Geofence.GEOFENCE_TRANSITION_EXIT
        ) {
            val triggeringGeofences = geofencingEvent.triggeringGeofences

            if (geofenceTransition == Geofence.GEOFENCE_TRANSITION_ENTER) {
                // user entered or is already inside a geofence
                for (geofence in triggeringGeofences) {
                    val eventData = App.instance.geofenceEventInfo[geofence.requestId] ?: continue
                    if (eventData.statusCode == GeoEventStatus.EnableEvent.name) showNotification(
                        eventData.title, eventData.description
                    )
                }
                Log.d(TAG, "User entered geofence!")
            } else {
                Log.d(TAG, "User exited geofence!")
            }
        } else {
            Log.e(
                TAG, "Invalid transition type"
            )
        }
    }

    private fun showNotification(title: String, description: String) {
        //        val intentForNotification = Intent(application, SplashScreen::class.java)
//        intentForNotification.putExtra("notificationInboxBody", inboxBody)
//        intentForNotification.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)

        val hub = NotificationHub.getInstance(App.instance)
        val id = hub.lastRandomId ?: 0
        hub.lastRandomId = id + 1
        //val pendingIntent = PendingIntent.getActivity(application, hub.lastRandomId!! /* Request code */, intentForNotification, PendingIntent.FLAG_UPDATE_CURRENT)
        val defaultSoundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)
        val notificationBuilder = NotificationCompat.Builder(App.instance.appContext, "channel1")
            .setSmallIcon(R.drawable.splash_screen_logo)
            .setContentTitle(title)
            .setAutoCancel(true)
            .setSound(defaultSoundUri)
            .setPriority(Notification.PRIORITY_HIGH)
            .setCategory(Notification.CATEGORY_MESSAGE)
            .setDefaults(Notification.DEFAULT_ALL)
            //.setContentIntent(pendingIntent)
            .setContentText(description)
        with(NotificationManagerCompat.from(App.instance.appContext)) {
            // notificationId is a unique int for each notification that you must define
            notify(id, notificationBuilder.build())
        }
    }
}

subtest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.threenitas.ewayprototype">

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>

    <application
        android:name=".App"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.EWayPrototype"
        android:usesCleartextTraffic="true">

        <meta-data
            android:name="com.google.android.geo.API_KEY"
            android:value="${MAPS_API_KEY}" />

        <receiver android:name=".geofencing.GeofenceBroadcastReceiver"/>
        <service android:foregroundServiceType="location" android:enabled="true" android:name=".geofencing.GeofenceForegroundService"/>
        <service
            android:name=".MyFirebaseMessagingService"
            android:exported="false">
            <intent-filter>
                <action android:name="com.google.firebase.MESSAGING_EVENT" />
            </intent-filter>
        </service>
        <activity
            android:name=".MainActivity"
            android:screenOrientation="portrait"
            android:label="@string/app_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />

                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.BROWSABLE" />
            </intent-filter>
        </activity>
    </application>

</manifest>

We have implemented a Geofencing system where our CMS backend operators can send geofence data to the app through FCM. Inside the FirebaseService class that you see below, every time such a notification is sent it triggers the handleSilentNotification method which then attempts to create a Geofence object and add it to the Geofencing client.

We are requesting location access during the onboarding process and if the location permission was not granted, we just ignore the silent notification payload. When the user selects the "Allow all the time" location permission option, everything seems to work fine. Payloads are received, and geofences are created and added to the client for tracking.
The issue occurs when the user selects "Allow only while in use".

In this case, when we attempt to add the geofence to the GeofencingClient, it always fails by throwing the following stack trace:

2022-05-05 11:27:48.107 13685-13685/com.threenitas.ewayprototype I/EwayApp: New location received: 37.95690471,23.72790214
2022-05-05 11:27:55.580 13685-14907/com.threenitas.ewayprototype D/MyFirebaseMessagingService: body = "{\"Id\":\"1234654\",\"Latitude\":37.3495,\"Longitude\":23.4646,\"Radius\":100,\"StatusCode\":\"EnableEvent\",\"Title\":\"test\",\"Description\":\"test\"}"
2022-05-05 11:27:55.593 13685-14907/com.threenitas.ewayprototype D/MyFirebaseMessagingService: New Geofence created: Geofence[CIRCLE id:1234654 transitions:3 37.349500, 23.464600 9999999933815813000000000000000000000m, resp=0s, dwell=-1ms, @-1]
2022-05-05 11:27:55.616 13685-13685/com.threenitas.ewayprototype D/GeofencingManager: Failed to add geofence with id 1234654 to client!
2022-05-05 11:27:55.616 13685-13685/com.threenitas.ewayprototype W/System.err: com.google.android.gms.common.api.ApiException: 1004:
2022-05-05 11:27:55.617 13685-13685/com.threenitas.ewayprototype W/System.err: at com.google.android.gms.common.api.internal.TaskUtil.setResultOrApiException(com.google.android.gms:play-services-base@@18.0.1:4)
2022-05-05 11:27:55.617 13685-13685/com.threenitas.ewayprototype W/System.err: at com.google.android.gms.location.zzat.setResult(com.google.android.gms:play-services-location@@18.0.0:2)
2022-05-05 11:27:55.617 13685-13685/com.threenitas.ewayprototype W/System.err: at com.google.android.gms.internal.location.zzaw.zzb(com.google.android.gms:play-services-location@@18.0.0:3)
2022-05-05 11:27:55.617 13685-13685/com.threenitas.ewayprototype W/System.err: at com.google.android.gms.internal.location.zzaj.zza(com.google.android.gms:play-services-location@@18.0.0:9)
2022-05-05 11:27:55.617 13685-13685/com.threenitas.ewayprototype W/System.err: at com.google.android.gms.internal.location.zzb.onTransact(com.google.android.gms:play-services-location@@18.0.0:3)
2022-05-05 11:27:55.617 13685-13685/com.threenitas.ewayprototype W/System.err: at android.os.Binder.execTransactInternal(Binder.java:1195)
2022-05-05 11:27:55.617 13685-13685/com.threenitas.ewayprototype W/System.err: at android.os.Binder.execTransact(Binder.java:1159)
2022-05-05 11:27:58.087 13685-14822/com.threenitas.ewayprototype W/s.ewayprototyp: Suspending all threads took: 6.101ms
2022-05-05 11:28:06.071 13685-14822/com.threenitas.ewayprototype W/s.ewayprototyp: Suspending all threads took: 5.875ms
2022-05-05 11:28:06.089 13685-14822/com.threenitas.ewayprototype W/s.ewayprototyp: Suspending all threads took: 8.474ms
2022-05-05 11:28:06.106 13685-14822/com.threenitas.ewayprototype W/s.ewayprototyp: Suspending all threads took: 6.250ms
2022-05-05 11:28:06.136 13685-14822/com.threenitas.ewayprototype W/s.ewayprototyp: Suspending all threads took: 6.388ms

Is this a design decision on google's end like this bug tracker post seems to claim or am I missing something in my implementation?

App.kt:

class App : Application() {

    lateinit var appContext: Context
    lateinit var preferences: SharedPreferences
    lateinit var editor: SharedPreferences.Editor
    lateinit var geofencingClient: GeofencingClient
    val inactiveGeofenceIds = arrayListOf<String>()
    val geofenceEventInfo = mutableMapOf<String, GeoEventData>()
    val geofencePendingIntent: PendingIntent by lazy {
        val intent = Intent(appContext, GeofenceBroadcastReceiver::class.java)
        // We use FLAG_UPDATE_CURRENT so that we get the same pending intent back when calling
        // addGeofences() and removeGeofences().
        PendingIntent.getBroadcast(appContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
    }
    val locationListener: LocationListener = object : LocationListener {
        override fun onLocationChanged(location: Location) {
            Log.i("EwayApp", "New location received: ${location.latitude},${location.longitude}")
        }

        override fun onLocationChanged(locations: MutableList<Location>) {}

        override fun onStatusChanged(provider: String?, status: Int, extras: Bundle?) {}

        override fun onProviderEnabled(provider: String) {}

        override fun onProviderDisabled(provider: String) {}
    }

    fun isGeofencingClientInitialized() = this::geofencingClient.isInitialized
...........
}

FirebaseMessagingService:

class MyFirebaseMessagingService : FirebaseMessagingService() {
    private val TAG = MyFirebaseMessagingService::class.java.simpleName
    private var geofencingManager: GeofencingManager = getKoin().get()

    override fun onNewToken(token: String) {
        super.onNewToken(token)
        NotificationHub.getInstance(App.instance)
            .registerForPushNotifications(this, fetchIntegrationIdForLetsPlace())
    }


    override fun onMessageReceived(remoteMessage: RemoteMessage) {
        // Check if message contains a data payload
        remoteMessage.data.isNotEmpty().let {
            handleNotification(remoteMessage.data)
        }
    }

    private fun handleNotification(jsonData: MutableMap<String, String>) {
        val title = jsonData["Title"]
        val description = jsonData["Description"]
        val body = jsonData["Body"]
        if (title!!.isEmpty()) {
            if (hasLocationPermission()) handleSilentNotification(body)
            else Log.d(TAG, "No location permission has been granted!")
        } else {
            showNotification(
                title,
                description!!
            )
        }
    }

    private fun hasLocationPermission() = ContextCompat.checkSelfPermission(
        App.instance.appContext,
        Manifest.permission.ACCESS_FINE_LOCATION
    ) == PackageManager.PERMISSION_GRANTED

    private fun showNotification(
        title: String,
        description: String
    ) {
//        val intentForNotification = Intent(application, SplashScreen::class.java)
//        intentForNotification.putExtra("notificationInboxBody", inboxBody)
//        intentForNotification.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)

        val hub = NotificationHub.getInstance(App.instance)
        val id = hub.lastRandomId ?: 0
        hub.lastRandomId = id + 1
        //val pendingIntent = PendingIntent.getActivity(application, hub.lastRandomId!! /* Request code */, intentForNotification, PendingIntent.FLAG_UPDATE_CURRENT)
        val defaultSoundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)
        val notificationBuilder = NotificationCompat.Builder(application, "channel1")
            .setSmallIcon(R.drawable.splash_screen_logo)
            .setContentTitle(title)
            .setAutoCancel(true)
            .setSound(defaultSoundUri)
            .setPriority(Notification.PRIORITY_HIGH)
            .setCategory(Notification.CATEGORY_MESSAGE)
            .setDefaults(Notification.DEFAULT_ALL)
            //.setContentIntent(pendingIntent)
            .setContentText(description)
        with(NotificationManagerCompat.from(this)) {
            // notificationId is a unique int for each notification that you must define
            notify(id, notificationBuilder.build())
        }
    }

    private fun handleSilentNotification(body: String?) {
        Log.d(TAG, "body = $body")
        val geoEventData = jsonToGeoEvent(body)
        if (geoEventData == null) {
            Log.d(TAG, "Geo event data conversion failed!")
            return;
        }

        if (!App.instance.isGeofencingClientInitialized()) { // app is killed , store the payload for later use
            Log.d(TAG, "persist called")
            persistGeoData(geoEventData)
            return
        }

        if (geoEventData.statusCode == GeoEventStatus.DisableEvent.name) {
            geofencingManager.removeGeofence(geoEventData.id)
            return
        }
        val geofence = geofencingManager.createGeofence(geoEventData)
        geofencingManager.storeGeofenceInfo(geofence, geoEventData)
        Log.d(TAG, "New Geofence created: $geofence")
        geofencingManager.addGeofenceToClient(geofence)
    }

    private fun persistGeoData(geoEventData: GeoEventData) {
        val gson = Gson()
        val serializedData = gson.toJson(geoEventData, GeoEventData::class.java)
        App.instance.inactiveGeofenceIds.add(geoEventData.id)
        App.instance.preferences.edit().putString(geoEventData.id, serializedData).apply()
    }

    private fun jsonToGeoEvent(body: String?): GeoEventData? {
        if (body == null) return null
        val gson = Gson()
        val json = body.replace("\\", "")
        val jo = JSONObject(json.substring(1, json.length - 1))
        return gson.fromJson(jo.toString(), GeoEventData::class.java)
    }
}

GeofencingManagerImpl:

class GeofencingManagerImpl : GeofencingManager {
    private val tag: String
        get() = GeofencingManager::class.java.simpleName

    @SuppressLint("MissingPermission")
    override fun addGeofenceToClient(geofence: Geofence) {
        App.instance.geofencingClient.addGeofences(
            getGeofencingRequest(geofence),
            App.instance.geofencePendingIntent
        ).run {
            addOnSuccessListener {
                Log.d(tag, "Geofence with id ${geofence.requestId} added to client!")
            }
            addOnFailureListener {
                Log.d(tag, "Failed to add geofence with id ${geofence.requestId} to client!")
                it.printStackTrace()
            }
        }
    }

    override fun createGeofence(geoEventData: GeoEventData) =
        Geofence.Builder()
            .setRequestId(geoEventData.id)
            .setCircularRegion(
                geoEventData.latitude,
                geoEventData.longitude,
                geoEventData.radius
            )
            .setExpirationDuration(Geofence.NEVER_EXPIRE)
            .setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER or Geofence.GEOFENCE_TRANSITION_EXIT)
            .build()

    override fun storeGeofenceInfo(
        geofence: Geofence,
        geoEventData: GeoEventData
    ) {
        App.instance.geofenceEventInfo[geofence.requestId] = geoEventData
    }

    override fun getGeofencingRequest(geofence: Geofence): GeofencingRequest {
        return GeofencingRequest.Builder()
            .setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER)
            .addGeofence(geofence)
            .build()
    }

    override fun removeGeofence(id: String) {
        App.instance.geofenceEventInfo.remove(id)
        App.instance.geofencingClient.removeGeofences(listOf(id)).run {
            addOnSuccessListener {
                Log.d(tag, "Geofence with id $id removed from client!")
            }
            addOnFailureListener {
                Log.d(tag, "Failed to remove geofence with id $id from client!")
                it.printStackTrace()
            }
        }
    }
}

BroadcastReceiver:

class GeofenceBroadcastReceiver : BroadcastReceiver() {
    private val TAG = GeofenceBroadcastReceiver::class.java.simpleName

    override fun onReceive(context: Context?, intent: Intent?) {
        val geofencingEvent = GeofencingEvent.fromIntent(intent!!)
        if (geofencingEvent.hasError()) {
            val errorMessage = GeofenceStatusCodes
                .getStatusCodeString(geofencingEvent.errorCode)
            Log.e(TAG, errorMessage)
            return
        }
        val geofenceTransition = geofencingEvent.geofenceTransition

        if (geofenceTransition == Geofence.GEOFENCE_TRANSITION_ENTER ||
            geofenceTransition == Geofence.GEOFENCE_TRANSITION_EXIT
        ) {
            val triggeringGeofences = geofencingEvent.triggeringGeofences

            if (geofenceTransition == Geofence.GEOFENCE_TRANSITION_ENTER) {
                // user entered or is already inside a geofence
                for (geofence in triggeringGeofences) {
                    val eventData = App.instance.geofenceEventInfo[geofence.requestId] ?: continue
                    if (eventData.statusCode == GeoEventStatus.EnableEvent.name) showNotification(
                        eventData.title, eventData.description
                    )
                }
                Log.d(TAG, "User entered geofence!")
            } else {
                Log.d(TAG, "User exited geofence!")
            }
        } else {
            Log.e(
                TAG, "Invalid transition type"
            )
        }
    }

    private fun showNotification(title: String, description: String) {
        //        val intentForNotification = Intent(application, SplashScreen::class.java)
//        intentForNotification.putExtra("notificationInboxBody", inboxBody)
//        intentForNotification.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)

        val hub = NotificationHub.getInstance(App.instance)
        val id = hub.lastRandomId ?: 0
        hub.lastRandomId = id + 1
        //val pendingIntent = PendingIntent.getActivity(application, hub.lastRandomId!! /* Request code */, intentForNotification, PendingIntent.FLAG_UPDATE_CURRENT)
        val defaultSoundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)
        val notificationBuilder = NotificationCompat.Builder(App.instance.appContext, "channel1")
            .setSmallIcon(R.drawable.splash_screen_logo)
            .setContentTitle(title)
            .setAutoCancel(true)
            .setSound(defaultSoundUri)
            .setPriority(Notification.PRIORITY_HIGH)
            .setCategory(Notification.CATEGORY_MESSAGE)
            .setDefaults(Notification.DEFAULT_ALL)
            //.setContentIntent(pendingIntent)
            .setContentText(description)
        with(NotificationManagerCompat.from(App.instance.appContext)) {
            // notificationId is a unique int for each notification that you must define
            notify(id, notificationBuilder.build())
        }
    }
}

Manifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.threenitas.ewayprototype">

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>

    <application
        android:name=".App"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.EWayPrototype"
        android:usesCleartextTraffic="true">

        <meta-data
            android:name="com.google.android.geo.API_KEY"
            android:value="${MAPS_API_KEY}" />

        <receiver android:name=".geofencing.GeofenceBroadcastReceiver"/>
        <service android:foregroundServiceType="location" android:enabled="true" android:name=".geofencing.GeofenceForegroundService"/>
        <service
            android:name=".MyFirebaseMessagingService"
            android:exported="false">
            <intent-filter>
                <action android:name="com.google.firebase.MESSAGING_EVENT" />
            </intent-filter>
        </service>
        <activity
            android:name=".MainActivity"
            android:screenOrientation="portrait"
            android:label="@string/app_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />

                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.BROWSABLE" />
            </intent-filter>
        </activity>
    </application>

</manifest>

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

寄离 2025-02-02 08:28:23

Android Geopencing 文档>

要使用地理申请,您的应用必须请求以下内容:

  • access_fine_location

  • Access_background_location如果您的应用程序目标Android 10(API级别29)或更高

,则第二次权限为“允许所有时间”权限。

The Android geofencing documentation clearly says:

To use geofencing, your app must request the following:

  • ACCESS_FINE_LOCATION

  • ACCESS_BACKGROUND_LOCATION if your app targets Android 10 (API level 29) or higher

That second permission is the "Allow all the time" permission.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文