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)