diff --git a/app/src/generic/java/org/tasks/location/GeofenceApi.kt b/app/src/generic/java/org/tasks/location/GeofenceApi.kt
deleted file mode 100644
index f5bc00df2..000000000
--- a/app/src/generic/java/org/tasks/location/GeofenceApi.kt
+++ /dev/null
@@ -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) {}
-}
\ No newline at end of file
diff --git a/app/src/generic/java/org/tasks/location/GeofenceClient.kt b/app/src/generic/java/org/tasks/location/GeofenceClient.kt
new file mode 100644
index 000000000..6245be679
--- /dev/null
+++ b/app/src/generic/java/org/tasks/location/GeofenceClient.kt
@@ -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
+ )
+}
+
diff --git a/app/src/generic/java/org/tasks/location/GeofenceTransitionsIntentService.kt b/app/src/generic/java/org/tasks/location/GeofenceTransitionsIntentService.kt
new file mode 100644
index 000000000..585ddcbc8
--- /dev/null
+++ b/app/src/generic/java/org/tasks/location/GeofenceTransitionsIntentService.kt
@@ -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)
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/generic/res/values/bools.xml b/app/src/generic/res/values/bools.xml
deleted file mode 100644
index 30b01d340..000000000
--- a/app/src/generic/res/values/bools.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
- false
-
\ No newline at end of file
diff --git a/app/src/googleplay/AndroidManifest.xml b/app/src/googleplay/AndroidManifest.xml
index 3d15d9006..f7a385266 100644
--- a/app/src/googleplay/AndroidManifest.xml
+++ b/app/src/googleplay/AndroidManifest.xml
@@ -13,12 +13,6 @@
android:name="com.google.android.gms.version"
android:value="@integer/google_play_services_version"/>
-
-
-
diff --git a/app/src/googleplay/java/org/tasks/location/GeofenceApi.kt b/app/src/googleplay/java/org/tasks/location/GeofenceApi.kt
deleted file mode 100644
index bbc46c5cf..000000000
--- a/app/src/googleplay/java/org/tasks/location/GeofenceApi.kt
+++ /dev/null
@@ -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()
- }
-}
\ No newline at end of file
diff --git a/app/src/googleplay/java/org/tasks/location/GeofenceClient.kt b/app/src/googleplay/java/org/tasks/location/GeofenceClient.kt
new file mode 100644
index 000000000..cafb7610e
--- /dev/null
+++ b/app/src/googleplay/java/org/tasks/location/GeofenceClient.kt
@@ -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()
+ }
+}
\ No newline at end of file
diff --git a/app/src/googleplay/res/values/bools.xml b/app/src/googleplay/res/values/bools.xml
deleted file mode 100644
index ee92dd0ba..000000000
--- a/app/src/googleplay/res/values/bools.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
- true
-
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index c10e24160..1f3dae08b 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -493,6 +493,12 @@
android:name=".locale.receiver.TaskerIntentService"
android:permission="android.permission.BIND_JOB_SERVICE"/>
+
+
+
+ suspend fun getArrivalGeofences(place: String, now: Long = now()): List
@Query("SELECT geofences.* FROM geofences"
+ " INNER JOIN tasks ON tasks._id = geofences.task"
+ " WHERE place = :place AND departure = 1 AND tasks.completed = 0"
+ " AND tasks.deleted = 0 AND tasks.snoozeTime < :now AND tasks.hideUntil < :now")
- suspend fun getDepartureGeofences(place: String, now: Long): List
+ suspend fun getDepartureGeofences(place: String, now: Long = now()): List
@Query("SELECT * FROM geofences"
+ " INNER JOIN places ON geofences.place = places.uid"
diff --git a/app/src/main/java/org/tasks/location/GeofenceApi.kt b/app/src/main/java/org/tasks/location/GeofenceApi.kt
new file mode 100644
index 000000000..77a72a378
--- /dev/null
+++ b/app/src/main/java/org/tasks/location/GeofenceApi.kt
@@ -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)
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/tasks/preferences/Device.java b/app/src/main/java/org/tasks/preferences/Device.java
index 0b81a150c..4e60d8143 100644
--- a/app/src/main/java/org/tasks/preferences/Device.java
+++ b/app/src/main/java/org/tasks/preferences/Device.java
@@ -13,7 +13,6 @@ import dagger.hilt.android.qualifiers.ApplicationContext;
import java.util.List;
import javax.inject.Inject;
import org.tasks.BuildConfig;
-import org.tasks.R;
import org.tasks.locale.Locale;
import timber.log.Timber;
@@ -36,10 +35,6 @@ public class Device {
return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_MICROPHONE);
}
- public boolean supportsGeofences() {
- return context.getResources().getBoolean(R.bool.support_geofences);
- }
-
public boolean voiceInputAvailable() {
PackageManager pm = context.getPackageManager();
List activities =
diff --git a/app/src/main/java/org/tasks/preferences/fragments/TaskDefaults.kt b/app/src/main/java/org/tasks/preferences/fragments/TaskDefaults.kt
index 774acd85e..c7dd07e91 100644
--- a/app/src/main/java/org/tasks/preferences/fragments/TaskDefaults.kt
+++ b/app/src/main/java/org/tasks/preferences/fragments/TaskDefaults.kt
@@ -112,8 +112,6 @@ class TaskDefaults : InjectingPreferenceFragment() {
updateRecurrence()
updateDefaultLocation()
updateTags()
-
- requires(device.supportsGeofences(), R.string.p_default_location_reminder_key, R.string.p_default_location_radius)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
diff --git a/app/src/main/java/org/tasks/ui/LocationControlSet.kt b/app/src/main/java/org/tasks/ui/LocationControlSet.kt
index 2d5c2c515..0b841e360 100644
--- a/app/src/main/java/org/tasks/ui/LocationControlSet.kt
+++ b/app/src/main/java/org/tasks/ui/LocationControlSet.kt
@@ -65,7 +65,7 @@ class LocationControlSet : TaskEditControlFragment() {
geofenceOptions.visibility = View.GONE
locationAddress.visibility = View.GONE
} else {
- geofenceOptions.visibility = if (device.supportsGeofences()) View.VISIBLE else View.GONE
+ geofenceOptions.visibility = View.VISIBLE
geofenceOptions.setImageResource(
if (permissionChecker.canAccessBackgroundLocation()
&& (location.isArrival || location.isDeparture)) R.drawable.ic_outline_notifications_24px else R.drawable.ic_outline_notifications_off_24px)