Skip to content

Commit

Permalink
REM-881 - Fix crash on location service start
Browse files Browse the repository at this point in the history
  • Loading branch information
naz013 committed Dec 17, 2024
1 parent b8669e1 commit aa1da4b
Show file tree
Hide file tree
Showing 5 changed files with 198 additions and 95 deletions.
4 changes: 3 additions & 1 deletion app/src/main/java/com/elementary/tasks/ReminderApp.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import com.elementary.tasks.core.data.adapter.adapterModule
import com.elementary.tasks.core.data.repository.repositoryModule
import com.elementary.tasks.core.os.osModule
import com.elementary.tasks.core.services.action.actionModule
import com.elementary.tasks.core.services.servicesModule
import com.elementary.tasks.core.utils.Notifier
import com.elementary.tasks.core.utils.completableModule
import com.elementary.tasks.core.utils.converterModule
Expand Down Expand Up @@ -87,7 +88,8 @@ class ReminderApp : MultiDexApplication(), KoinComponent {
voiceModule,
googleTaskModule,
workModule,
noteModule
noteModule,
servicesModule
)
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,12 @@ class ReminderRepository(private val reminderDao: ReminderDao) {
UiReminderType(it.type).isGpsType()
}
}

suspend fun getActiveGpsTypes(): List<Reminder> {
return reminderDao.getAllTypes(active = true, removed = false, types = Reminder.gpsTypes())
}

suspend fun save(reminder: Reminder) {
reminderDao.insert(reminder)
}
}
Original file line number Diff line number Diff line change
@@ -1,39 +1,30 @@
package com.elementary.tasks.core.services

import android.app.ForegroundServiceStartNotAllowedException
import android.app.Service
import android.content.Intent
import android.location.Location
import android.os.Build
import android.os.IBinder
import android.text.TextUtils
import androidx.core.app.NotificationCompat
import com.elementary.tasks.R
import com.elementary.tasks.core.data.dao.ReminderDao
import com.elementary.tasks.core.data.models.Reminder
import com.elementary.tasks.core.location.LocationTracker
import com.elementary.tasks.core.services.action.reminder.ReminderActionProcessor
import com.elementary.tasks.core.services.usecase.CheckLocationReminderUseCase
import com.elementary.tasks.core.utils.Module
import com.elementary.tasks.core.utils.Notifier
import com.elementary.tasks.core.utils.datetime.DateTimeManager
import com.elementary.tasks.core.utils.launchDefault
import com.elementary.tasks.core.utils.params.Prefs
import com.elementary.tasks.core.utils.ui.DefaultDistanceFormatter
import com.elementary.tasks.core.utils.withUIContext
import com.github.naz013.logging.Logger
import org.koin.android.ext.android.inject
import org.koin.core.parameter.parametersOf
import timber.log.Timber
import kotlin.math.roundToInt

class GeolocationService : Service() {

private var isNotificationEnabled: Boolean = false
private var stockRadius: Int = 0

private val prefs by inject<Prefs>()
private val reminderDao by inject<ReminderDao>()
private val notifier by inject<Notifier>()
private val dateTimeManager by inject<DateTimeManager>()
private val reminderActionProcessor by inject<ReminderActionProcessor>()
private val distanceFormatter = DefaultDistanceFormatter(this, prefs.useMetric)
private val checkLocationReminderUseCase by inject<CheckLocationReminderUseCase>()

private val locationTracker by inject<LocationTracker> { parametersOf(locationListener) }
private var locationListener: LocationTracker.Listener = object : LocationTracker.Listener {
Expand Down Expand Up @@ -64,103 +55,38 @@ class GeolocationService : Service() {
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
Timber.d("onStartCommand: ")
showDefaultNotification()
isNotificationEnabled = prefs.isDistanceNotificationEnabled
stockRadius = prefs.radius
locationTracker.startUpdates()
return START_STICKY
}

private fun checkReminders(locationA: Location) {
private fun checkReminders(location: Location) {
launchDefault {
for (reminder in reminderDao.getAll(active = true, removed = false)) {
if (Reminder.isGpsType(reminder.type)) {
checkDistance(locationA, reminder)
}
}
}
}

private suspend fun checkDistance(locationA: Location, reminder: Reminder) {
if (!TextUtils.isEmpty(reminder.eventTime)) {
if (dateTimeManager.isCurrent(reminder.eventTime)) {
selectBranch(locationA, reminder)
}
} else {
selectBranch(locationA, reminder)
}
}

private suspend fun selectBranch(locationA: Location, reminder: Reminder) {
if (reminder.isNotificationShown) return
when {
Reminder.isBase(reminder.type, Reminder.BY_OUT) -> checkOut(locationA, reminder)
else -> checkSimple(locationA, reminder)
}
}

private suspend fun checkSimple(locationA: Location, reminder: Reminder) {
val place = reminder.places[0]
val locationB = Location("point B")
locationB.latitude = place.latitude
locationB.longitude = place.longitude
val distance = locationA.distanceTo(locationB)
val roundedDistance = distance.roundToInt()
if (roundedDistance <= getRadius(place.radius)) {
showReminder(reminder)
} else {
showNotification(roundedDistance, reminder)
}
}

private fun getRadius(r: Int): Int {
var radius = r
if (radius == -1) radius = stockRadius
return radius
}

private suspend fun checkOut(locationA: Location, reminder: Reminder) {
val place = reminder.places[0]
val locationB = Location("point B")
locationB.latitude = place.latitude
locationB.longitude = place.longitude
val distance = locationA.distanceTo(locationB)
val roundedDistance = distance.roundToInt()
if (reminder.isLocked) {
if (roundedDistance > getRadius(place.radius)) {
showReminder(reminder)
} else {
if (isNotificationEnabled) {
showNotification(roundedDistance, reminder)
}
}
} else {
if (roundedDistance < getRadius(place.radius)) {
reminder.isLocked = true
reminderDao.insert(reminder)
}
val result = checkLocationReminderUseCase(location)
result.showDistanceNotifications.forEach { showNotification(it) }
result.showReminderNotifications.forEach { showReminder(it) }
}
}

private fun reminderAction(id: String) {
reminderActionProcessor.process(id)
}

private suspend fun showReminder(reminder: Reminder) {
if (reminder.isNotificationShown) return
reminder.isNotificationShown = true
reminderDao.insert(reminder)
withUIContext { reminderAction(reminder.uuId) }
private suspend fun showReminder(
showReminderNotification: CheckLocationReminderUseCase.ShowReminderNotification
) {
withUIContext { reminderAction(showReminderNotification.uuId) }
}

private fun showNotification(roundedDistance: Int, reminder: Reminder) {
if (!isNotificationEnabled) return
private fun showNotification(
notification: CheckLocationReminderUseCase.ShowDistanceNotification
) {
val builder = NotificationCompat.Builder(applicationContext, Notifier.CHANNEL_SILENT)
builder.setContentTitle(reminder.summary)
builder.setContentText(distanceFormatter.format(roundedDistance))
builder.setContentTitle(notification.title)
builder.setContentText(notification.text)
builder.priority = NotificationCompat.PRIORITY_MIN
builder.setSmallIcon(R.drawable.ic_builder_map_my_location)
builder.setCategory(NotificationCompat.CATEGORY_NAVIGATION)
notifier.notify(reminder.uniqueId, builder.build())
notifier.notify(notification.uniqueId, builder.build())
}

private fun showDefaultNotification() {
Expand All @@ -172,7 +98,18 @@ class GeolocationService : Service() {
}
builder.setContentTitle(getString(R.string.location_tracking_service_running))
builder.setSmallIcon(R.drawable.ic_builder_map_my_location)
startForeground(NOTIFICATION_ID, builder.build())
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
try {
startForeground(NOTIFICATION_ID, builder.build())
Logger.i("GeolocationService", "Start foreground: success")
} catch (e: ForegroundServiceStartNotAllowedException) {
Logger.i("GeolocationService", "Start foreground: not allowed")
stopSelf()
}
} else {
startForeground(NOTIFICATION_ID, builder.build())
Logger.i("GeolocationService", "Start foreground: success")
}
}

companion object {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.elementary.tasks.core.services

import com.elementary.tasks.core.services.usecase.CheckLocationReminderUseCase
import org.koin.dsl.module

val servicesModule = module {
factory { CheckLocationReminderUseCase(get(), get(), get(), get()) }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
package com.elementary.tasks.core.services.usecase

import android.content.Context
import android.location.Location
import com.elementary.tasks.core.data.models.Reminder
import com.elementary.tasks.core.data.repository.ReminderRepository
import com.elementary.tasks.core.utils.datetime.DateTimeManager
import com.elementary.tasks.core.utils.params.Prefs
import com.elementary.tasks.core.utils.ui.DefaultDistanceFormatter
import kotlin.math.roundToInt

class CheckLocationReminderUseCase(
context: Context,
private val reminderRepository: ReminderRepository,
private val dateTimeManager: DateTimeManager,
prefs: Prefs
) {

private val distanceFormatter: DefaultDistanceFormatter = DefaultDistanceFormatter(
context = context,
useMetric = prefs.useMetric
)
private val stockRadius: Int = prefs.radius
private val isNotificationEnabled: Boolean = prefs.isDistanceNotificationEnabled

suspend operator fun invoke(location: Location): Result {
val showDistanceNotifications = mutableListOf<ShowDistanceNotification>()
val showReminderNotifications = mutableListOf<ShowReminderNotification>()

for (reminder in reminderRepository.getActiveGpsTypes()) {
if (reminder.isNotificationShown) continue
if (shouldCheckDistance(reminder)) {
when {
destinationReached(location, reminder) -> {
reminder.isNotificationShown = true
reminderRepository.save(reminder)
showReminderNotifications.add(ShowReminderNotification(reminder.uuId))
}

shouldShowDistanceNotification(reminder) -> {
showDistanceNotifications.add(
ShowDistanceNotification(
uniqueId = reminder.uniqueId,
title = reminder.summary,
text = getDistanceText(location, reminder)
)
)
}

shouldLockReminder(location, reminder) -> {
reminder.isLocked = true
reminderRepository.save(reminder)
}
}
}
}

return Result(showDistanceNotifications, showReminderNotifications)
}

private fun getDistanceText(location: Location, reminder: Reminder): String {
return distanceFormatter.format(getDistance(location, reminder))
}

private fun shouldLockReminder(location: Location, reminder: Reminder): Boolean {
return if (reminder.isLeavingType()) {
val distance = getDistance(location, reminder)
val place = reminder.places[0]
!reminder.isLocked && distance < getRadius(place.radius)
} else {
false
}
}

private fun shouldShowDistanceNotification(reminder: Reminder): Boolean {
if (!isNotificationEnabled) return false
return if (reminder.isLeavingType()) {
reminder.isLocked
} else {
true
}
}

private fun destinationReached(location: Location, reminder: Reminder): Boolean {
val distance = getDistance(location, reminder)
val place = reminder.places[0]
return if (reminder.isLeavingType()) {
reminder.isLocked && distance > getRadius(place.radius)
} else {
distance <= getRadius(place.radius)
}
}

private fun getRadius(r: Int): Int {
var radius = r
if (radius == -1) radius = stockRadius
return radius
}

private fun getDistance(location: Location, reminder: Reminder): Int {
return if (reminder.isLeavingType()) {
val place = reminder.places[0]
val loc = Location("point B").apply {
latitude = place.latitude
longitude = place.longitude
}

val distance = location.distanceTo(loc)
distance.roundToInt()
} else {
val place = reminder.places[0]
val loc = Location("point B").apply {
latitude = place.latitude
longitude = place.longitude
}

val distance = location.distanceTo(loc)
distance.roundToInt()
}
}

private fun shouldCheckDistance(reminder: Reminder): Boolean {
return if (reminder.eventTime.isEmpty()) {
true
} else {
dateTimeManager.isCurrent(reminder.eventTime)
}
}

private fun Reminder.isLeavingType(): Boolean {
return Reminder.isBase(type, Reminder.BY_OUT)
}

data class Result(
val showDistanceNotifications: List<ShowDistanceNotification>,
val showReminderNotifications: List<ShowReminderNotification>
)

data class ShowReminderNotification(
val uuId: String
)

data class ShowDistanceNotification(
val uniqueId: Int,
val title: String,
val text: String
)
}

0 comments on commit aa1da4b

Please sign in to comment.