mirror of https://github.com/tasks/tasks
Add LocationManager proximity alerts
parent
598cbba8c1
commit
99dee06c64
@ -1,12 +0,0 @@
|
||||
package org.tasks.location
|
||||
|
||||
import org.tasks.data.Place
|
||||
import javax.inject.Inject
|
||||
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
class GeofenceApi @Inject constructor() {
|
||||
fun registerAll() {}
|
||||
fun update(place: Place?) {}
|
||||
fun update(place: String?) {}
|
||||
fun update(taskId: Long) {}
|
||||
}
|
||||
@ -0,0 +1,41 @@
|
||||
package org.tasks.location
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.PendingIntent
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.location.LocationManager
|
||||
import android.net.Uri
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import org.tasks.data.MergedGeofence
|
||||
import org.tasks.data.Place
|
||||
import javax.inject.Inject
|
||||
|
||||
class GeofenceClient @Inject constructor(@ApplicationContext private val context: Context) {
|
||||
private val client = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
|
||||
|
||||
@SuppressLint("MissingPermission")
|
||||
fun addGeofences(@Suppress("UNUSED_PARAMETER") geofence: MergedGeofence) {
|
||||
client.addProximityAlert(
|
||||
geofence.latitude,
|
||||
geofence.longitude,
|
||||
geofence.radius.toFloat(),
|
||||
-1,
|
||||
createPendingIntent(geofence.place.id)
|
||||
)
|
||||
}
|
||||
|
||||
fun removeGeofences(@Suppress("UNUSED_PARAMETER") place: Place) {
|
||||
client.removeProximityAlert(createPendingIntent(place.id))
|
||||
}
|
||||
|
||||
private fun createPendingIntent(place: Long) =
|
||||
PendingIntent.getBroadcast(
|
||||
context,
|
||||
0,
|
||||
Intent(context, GeofenceTransitionsIntentService.Broadcast::class.java)
|
||||
.setData(Uri.parse("tasks://geofence/$place")),
|
||||
PendingIntent.FLAG_UPDATE_CURRENT
|
||||
)
|
||||
}
|
||||
|
||||
@ -0,0 +1,44 @@
|
||||
package org.tasks.location
|
||||
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.location.LocationManager
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import org.tasks.Notifier
|
||||
import org.tasks.data.LocationDao
|
||||
import org.tasks.injection.InjectingJobIntentService
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
class GeofenceTransitionsIntentService : InjectingJobIntentService() {
|
||||
@Inject lateinit var locationDao: LocationDao
|
||||
@Inject lateinit var notifier: Notifier
|
||||
|
||||
override suspend fun doWork(intent: Intent) {
|
||||
val arrival = intent.getBooleanExtra(LocationManager.KEY_PROXIMITY_ENTERING, false)
|
||||
Timber.d("geofence[${intent.data}] arrival[$arrival]")
|
||||
val place = intent.data?.lastPathSegment?.toLongOrNull()?.let { locationDao.getPlace(it) }
|
||||
if (place == null) {
|
||||
Timber.e("Failed to find place ${intent.data}")
|
||||
return
|
||||
}
|
||||
val geofences = if (arrival) {
|
||||
locationDao.getArrivalGeofences(place.uid!!)
|
||||
} else {
|
||||
locationDao.getDepartureGeofences(place.uid!!)
|
||||
}
|
||||
notifier.triggerNotifications(place.id, geofences, arrival)
|
||||
}
|
||||
|
||||
class Broadcast : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
enqueueWork(
|
||||
context,
|
||||
GeofenceTransitionsIntentService::class.java,
|
||||
JOB_ID_GEOFENCE_TRANSITION,
|
||||
intent)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<bool name="support_geofences">false</bool>
|
||||
</resources>
|
||||
@ -1,66 +0,0 @@
|
||||
package org.tasks.location
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.PendingIntent
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import com.google.android.gms.location.Geofence
|
||||
import com.google.android.gms.location.GeofencingRequest
|
||||
import com.google.android.gms.location.LocationServices
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import org.tasks.data.LocationDao
|
||||
import org.tasks.data.MergedGeofence
|
||||
import org.tasks.data.Place
|
||||
import org.tasks.preferences.PermissionChecker
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
class GeofenceApi @Inject constructor(
|
||||
@param:ApplicationContext private val context: Context,
|
||||
private val permissionChecker: PermissionChecker,
|
||||
private val locationDao: LocationDao) {
|
||||
|
||||
suspend fun registerAll() = locationDao.getPlacesWithGeofences().forEach { update(it) }
|
||||
|
||||
suspend fun update(taskId: Long) = update(locationDao.getPlaceForTask(taskId))
|
||||
|
||||
suspend fun update(place: String) = update(locationDao.getPlace(place))
|
||||
|
||||
@SuppressLint("MissingPermission")
|
||||
suspend fun update(place: Place?) {
|
||||
if (place == null || !permissionChecker.canAccessBackgroundLocation()) {
|
||||
return
|
||||
}
|
||||
val client = LocationServices.getGeofencingClient(context)
|
||||
val geofence = locationDao.getGeofencesByPlace(place.uid!!)
|
||||
if (geofence != null) {
|
||||
Timber.d("Adding geofence for %s", geofence)
|
||||
client.addGeofences(
|
||||
GeofencingRequest.Builder().addGeofence(toGoogleGeofence(geofence)).build(),
|
||||
PendingIntent.getBroadcast(
|
||||
context,
|
||||
0,
|
||||
Intent(context, GeofenceTransitionsIntentService.Broadcast::class.java),
|
||||
PendingIntent.FLAG_UPDATE_CURRENT))
|
||||
} else {
|
||||
Timber.d("Removing geofence for %s", place)
|
||||
client.removeGeofences(listOf(place.id.toString()))
|
||||
}
|
||||
}
|
||||
|
||||
private fun toGoogleGeofence(geofence: MergedGeofence): Geofence {
|
||||
var transitionTypes = 0
|
||||
if (geofence.arrival) {
|
||||
transitionTypes = transitionTypes or GeofencingRequest.INITIAL_TRIGGER_ENTER
|
||||
}
|
||||
if (geofence.departure) {
|
||||
transitionTypes = transitionTypes or GeofencingRequest.INITIAL_TRIGGER_EXIT
|
||||
}
|
||||
return Geofence.Builder()
|
||||
.setCircularRegion(geofence.latitude, geofence.longitude, geofence.radius.toFloat())
|
||||
.setRequestId(geofence.uid)
|
||||
.setTransitionTypes(transitionTypes)
|
||||
.setExpirationDuration(Geofence.NEVER_EXPIRE)
|
||||
.build()
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,48 @@
|
||||
package org.tasks.location
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.PendingIntent
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import com.google.android.gms.location.Geofence
|
||||
import com.google.android.gms.location.GeofencingRequest
|
||||
import com.google.android.gms.location.LocationServices
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import org.tasks.data.MergedGeofence
|
||||
import org.tasks.data.Place
|
||||
import javax.inject.Inject
|
||||
|
||||
class GeofenceClient @Inject constructor(@ApplicationContext private val context: Context) {
|
||||
private val client = LocationServices.getGeofencingClient(context)
|
||||
|
||||
@SuppressLint("MissingPermission")
|
||||
fun addGeofences(geofence: MergedGeofence) {
|
||||
client.addGeofences(
|
||||
GeofencingRequest.Builder().addGeofence(toGoogleGeofence(geofence)).build(),
|
||||
PendingIntent.getBroadcast(
|
||||
context,
|
||||
0,
|
||||
Intent(context, GeofenceTransitionsIntentService.Broadcast::class.java),
|
||||
PendingIntent.FLAG_UPDATE_CURRENT))
|
||||
}
|
||||
|
||||
fun removeGeofences(place: Place) {
|
||||
client.removeGeofences(listOf(place.id.toString()))
|
||||
}
|
||||
|
||||
private fun toGoogleGeofence(geofence: MergedGeofence): Geofence {
|
||||
var transitionTypes = 0
|
||||
if (geofence.arrival) {
|
||||
transitionTypes = transitionTypes or GeofencingRequest.INITIAL_TRIGGER_ENTER
|
||||
}
|
||||
if (geofence.departure) {
|
||||
transitionTypes = transitionTypes or GeofencingRequest.INITIAL_TRIGGER_EXIT
|
||||
}
|
||||
return Geofence.Builder()
|
||||
.setCircularRegion(geofence.latitude, geofence.longitude, geofence.radius.toFloat())
|
||||
.setRequestId(geofence.uid)
|
||||
.setTransitionTypes(transitionTypes)
|
||||
.setExpirationDuration(Geofence.NEVER_EXPIRE)
|
||||
.build()
|
||||
}
|
||||
}
|
||||
@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<bool name="support_geofences">true</bool>
|
||||
</resources>
|
||||
@ -0,0 +1,34 @@
|
||||
package org.tasks.location
|
||||
|
||||
import org.tasks.data.LocationDao
|
||||
import org.tasks.data.Place
|
||||
import org.tasks.preferences.PermissionChecker
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
class GeofenceApi @Inject constructor(
|
||||
private val permissionChecker: PermissionChecker,
|
||||
private val locationDao: LocationDao,
|
||||
private val client: GeofenceClient
|
||||
) {
|
||||
suspend fun registerAll() = locationDao.getPlacesWithGeofences().forEach { update(it) }
|
||||
|
||||
suspend fun update(taskId: Long) = update(locationDao.getPlaceForTask(taskId))
|
||||
|
||||
suspend fun update(place: String) = update(locationDao.getPlace(place))
|
||||
|
||||
suspend fun update(place: Place?) {
|
||||
if (place == null || !permissionChecker.canAccessBackgroundLocation()) {
|
||||
return
|
||||
}
|
||||
locationDao
|
||||
.getGeofencesByPlace(place.uid!!)?.let {
|
||||
Timber.d("Adding geofence for %s", it)
|
||||
client.addGeofences(it)
|
||||
}
|
||||
?: place.let {
|
||||
Timber.d("Removing geofence for %s", it)
|
||||
client.removeGeofences(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue