Observing live status in DB from location sharing Android service

This commit is contained in:
Maxime NATUREL 2022-06-21 09:43:29 +02:00
parent 3cffedd353
commit 81e14c7c3b
7 changed files with 37 additions and 54 deletions

View File

@ -16,6 +16,7 @@
package org.matrix.android.sdk.api.session.room.location
import androidx.annotation.MainThread
import androidx.lifecycle.LiveData
import org.matrix.android.sdk.api.session.room.model.livelocation.LiveLocationShareAggregatedSummary
import org.matrix.android.sdk.api.util.Cancelable
@ -60,11 +61,13 @@ interface LocationSharingService {
/**
* Returns a LiveData on the list of current running live location shares.
*/
@MainThread
fun getRunningLiveLocationShareSummaries(): LiveData<List<LiveLocationShareAggregatedSummary>>
/**
* Returns a LiveData on the live location share summary with the given eventId.
* @param beaconInfoEventId event id of the initial beacon info state event
*/
@MainThread
fun getLiveLocationShareSummary(beaconInfoEventId: String): LiveData<Optional<LiveLocationShareAggregatedSummary>>
}

View File

@ -18,22 +18,27 @@ package im.vector.app.features.location
import android.content.Intent
import android.os.Binder
import android.os.Handler
import android.os.IBinder
import android.os.Parcelable
import dagger.hilt.android.AndroidEntryPoint
import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.services.VectorService
import im.vector.app.features.location.live.GetLiveLocationShareSummaryUseCase
import im.vector.app.features.notifications.NotificationUtils
import im.vector.app.features.session.coroutineScope
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.distinctUntilChangedBy
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import kotlinx.parcelize.Parcelize
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.getRoom
import org.matrix.android.sdk.api.session.room.location.UpdateLiveLocationShareResult
import timber.log.Timber
import java.util.Timer
import java.util.TimerTask
import javax.inject.Inject
@AndroidEntryPoint
@ -49,6 +54,7 @@ class LocationSharingService : VectorService(), LocationTracker.Callback {
@Inject lateinit var notificationUtils: NotificationUtils
@Inject lateinit var locationTracker: LocationTracker
@Inject lateinit var activeSessionHolder: ActiveSessionHolder
@Inject lateinit var getLiveLocationShareSummaryUseCase: GetLiveLocationShareSummaryUseCase
private val binder = LocalBinder()
@ -56,8 +62,9 @@ class LocationSharingService : VectorService(), LocationTracker.Callback {
* Keep track of a map between beacon event Id starting the live and RoomArgs.
*/
private val roomArgsMap = mutableMapOf<String, RoomArgs>()
private val timers = mutableListOf<Timer>()
var callback: Callback? = null
private val jobs = mutableListOf<Job>()
private val mainHandler by lazy { Handler(mainLooper) }
override fun onCreate() {
super.onCreate()
@ -78,9 +85,6 @@ class LocationSharingService : VectorService(), LocationTracker.Callback {
val notification = notificationUtils.buildLiveLocationSharingNotification()
startForeground(roomArgs.roomId.hashCode(), notification)
// Schedule a timer to stop sharing
scheduleTimer(roomArgs.roomId, roomArgs.durationMillis)
// Send beacon info state event
launchInIO { session ->
sendStartingLiveBeaconInfo(session, roomArgs)
@ -101,6 +105,7 @@ class LocationSharingService : VectorService(), LocationTracker.Callback {
when (result) {
is UpdateLiveLocationShareResult.Success -> {
addRoomArgs(result.beaconEventId, roomArgs)
listenForLiveSummaryChanges(roomArgs.roomId, result.beaconEventId)
locationTracker.requestLastKnownLocation()
}
is UpdateLiveLocationShareResult.Failure -> {
@ -115,22 +120,7 @@ class LocationSharingService : VectorService(), LocationTracker.Callback {
}
}
private fun scheduleTimer(roomId: String, durationMillis: Long) {
Timer()
.apply {
schedule(object : TimerTask() {
override fun run() {
stopSharingLocation(roomId)
timers.remove(this@apply)
}
}, durationMillis)
}
.also {
timers.add(it)
}
}
fun stopSharingLocation(roomId: String) {
private fun stopSharingLocation(roomId: String) {
Timber.i("### LocationSharingService.stopSharingLocation for $roomId")
removeRoomArgs(roomId)
tryToDestroyMe()
@ -177,9 +167,9 @@ class LocationSharingService : VectorService(), LocationTracker.Callback {
}
private fun destroyMe() {
jobs.forEach { it.cancel() }
jobs.clear()
locationTracker.removeCallback(this)
timers.forEach { it.cancel() }
timers.clear()
stopSelf()
}
@ -202,6 +192,21 @@ class LocationSharingService : VectorService(), LocationTracker.Callback {
beaconIds.forEach { roomArgsMap.remove(it) }
}
private fun listenForLiveSummaryChanges(roomId: String, eventId: String) {
activeSessionHolder
.getSafeActiveSession()
?.let { session ->
mainHandler.post {
val job = getLiveLocationShareSummaryUseCase.execute(roomId, eventId)
.distinctUntilChangedBy { it.isActive }
.filter { it.isActive == false }
.onEach { stopSharingLocation(roomId) }
.launchIn(session.coroutineScope)
jobs.add(job)
}
}
}
private fun launchInIO(block: suspend CoroutineScope.(Session) -> Unit) =
activeSessionHolder
.getSafeActiveSession()

View File

@ -55,10 +55,6 @@ class LocationSharingServiceConnection @Inject constructor(
removeCallback(callback)
}
fun stopLiveLocationSharing(roomId: String) {
locationSharingService?.stopSharingLocation(roomId)
}
override fun onServiceConnected(className: ComponentName, binder: IBinder) {
locationSharingService = (binder as LocationSharingService.LocalBinder).getService().also {
it.callback = this

View File

@ -16,6 +16,7 @@
package im.vector.app.features.location.live
import androidx.annotation.MainThread
import androidx.lifecycle.asFlow
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.emptyFlow
@ -23,13 +24,16 @@ import kotlinx.coroutines.flow.mapNotNull
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.getRoom
import org.matrix.android.sdk.api.session.room.model.livelocation.LiveLocationShareAggregatedSummary
import timber.log.Timber
import javax.inject.Inject
class GetLiveLocationShareSummaryUseCase @Inject constructor(
private val session: Session,
) {
@MainThread
fun execute(roomId: String, eventId: String): Flow<LiveLocationShareAggregatedSummary> {
Timber.d("getting flow for roomId=$roomId and eventId=$eventId")
return session.getRoom(roomId)
?.locationSharingService()
?.getLiveLocationShareSummary(eventId)

View File

@ -16,24 +16,17 @@
package im.vector.app.features.location.live
import im.vector.app.features.location.LocationSharingServiceConnection
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.getRoom
import org.matrix.android.sdk.api.session.room.location.UpdateLiveLocationShareResult
import javax.inject.Inject
class StopLiveLocationShareUseCase @Inject constructor(
private val locationSharingServiceConnection: LocationSharingServiceConnection,
private val session: Session
) {
suspend fun execute(roomId: String): UpdateLiveLocationShareResult? {
val result = sendStoppedBeaconInfo(session, roomId)
when (result) {
is UpdateLiveLocationShareResult.Success -> locationSharingServiceConnection.stopLiveLocationSharing(roomId)
else -> Unit
}
return result
return sendStoppedBeaconInfo(session, roomId)
}
private suspend fun sendStoppedBeaconInfo(session: Session, roomId: String): UpdateLiveLocationShareResult? {

View File

@ -16,7 +16,6 @@
package im.vector.app.features.location.live
import im.vector.app.test.fakes.FakeLocationSharingServiceConnection
import im.vector.app.test.fakes.FakeSession
import io.mockk.unmockkAll
import kotlinx.coroutines.test.runTest
@ -30,11 +29,9 @@ private const val AN_EVENT_ID = "event_id"
class StopLiveLocationShareUseCaseTest {
private val fakeLocationSharingServiceConnection = FakeLocationSharingServiceConnection()
private val fakeSession = FakeSession()
private val stopLiveLocationShareUseCase = StopLiveLocationShareUseCase(
locationSharingServiceConnection = fakeLocationSharingServiceConnection.instance,
session = fakeSession
)
@ -45,7 +42,6 @@ class StopLiveLocationShareUseCaseTest {
@Test
fun `given a room id when calling use case then the current live is stopped with success`() = runTest {
fakeLocationSharingServiceConnection.givenStopLiveLocationSharing()
val updateLiveResult = UpdateLiveLocationShareResult.Success(AN_EVENT_ID)
fakeSession.roomService()
.getRoom(A_ROOM_ID)
@ -55,7 +51,6 @@ class StopLiveLocationShareUseCaseTest {
val result = stopLiveLocationShareUseCase.execute(A_ROOM_ID)
result shouldBeEqualTo updateLiveResult
fakeLocationSharingServiceConnection.verifyStopLiveLocationSharing(A_ROOM_ID)
}
@Test
@ -70,6 +65,5 @@ class StopLiveLocationShareUseCaseTest {
val result = stopLiveLocationShareUseCase.execute(A_ROOM_ID)
result shouldBeEqualTo updateLiveResult
fakeLocationSharingServiceConnection.verifyStopLiveLocationSharingNotCalled(A_ROOM_ID)
}
}

View File

@ -34,16 +34,4 @@ class FakeLocationSharingServiceConnection {
fun verifyBind(callback: LocationSharingServiceConnection.Callback) {
verify { instance.bind(callback) }
}
fun givenStopLiveLocationSharing() {
every { instance.stopLiveLocationSharing(any()) } just runs
}
fun verifyStopLiveLocationSharing(roomId: String) {
verify { instance.stopLiveLocationSharing(roomId) }
}
fun verifyStopLiveLocationSharingNotCalled(roomId: String) {
verify(inverse = true) { instance.stopLiveLocationSharing(roomId) }
}
}