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 package org.matrix.android.sdk.api.session.room.location
import androidx.annotation.MainThread
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import org.matrix.android.sdk.api.session.room.model.livelocation.LiveLocationShareAggregatedSummary import org.matrix.android.sdk.api.session.room.model.livelocation.LiveLocationShareAggregatedSummary
import org.matrix.android.sdk.api.util.Cancelable 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. * Returns a LiveData on the list of current running live location shares.
*/ */
@MainThread
fun getRunningLiveLocationShareSummaries(): LiveData<List<LiveLocationShareAggregatedSummary>> fun getRunningLiveLocationShareSummaries(): LiveData<List<LiveLocationShareAggregatedSummary>>
/** /**
* Returns a LiveData on the live location share summary with the given eventId. * Returns a LiveData on the live location share summary with the given eventId.
* @param beaconInfoEventId event id of the initial beacon info state event * @param beaconInfoEventId event id of the initial beacon info state event
*/ */
@MainThread
fun getLiveLocationShareSummary(beaconInfoEventId: String): LiveData<Optional<LiveLocationShareAggregatedSummary>> 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.content.Intent
import android.os.Binder import android.os.Binder
import android.os.Handler
import android.os.IBinder import android.os.IBinder
import android.os.Parcelable import android.os.Parcelable
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.services.VectorService 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.notifications.NotificationUtils
import im.vector.app.features.session.coroutineScope import im.vector.app.features.session.coroutineScope
import kotlinx.coroutines.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.coroutines.launch
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.getRoom
import org.matrix.android.sdk.api.session.room.location.UpdateLiveLocationShareResult import org.matrix.android.sdk.api.session.room.location.UpdateLiveLocationShareResult
import timber.log.Timber import timber.log.Timber
import java.util.Timer
import java.util.TimerTask
import javax.inject.Inject import javax.inject.Inject
@AndroidEntryPoint @AndroidEntryPoint
@ -49,6 +54,7 @@ class LocationSharingService : VectorService(), LocationTracker.Callback {
@Inject lateinit var notificationUtils: NotificationUtils @Inject lateinit var notificationUtils: NotificationUtils
@Inject lateinit var locationTracker: LocationTracker @Inject lateinit var locationTracker: LocationTracker
@Inject lateinit var activeSessionHolder: ActiveSessionHolder @Inject lateinit var activeSessionHolder: ActiveSessionHolder
@Inject lateinit var getLiveLocationShareSummaryUseCase: GetLiveLocationShareSummaryUseCase
private val binder = LocalBinder() 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. * Keep track of a map between beacon event Id starting the live and RoomArgs.
*/ */
private val roomArgsMap = mutableMapOf<String, RoomArgs>() private val roomArgsMap = mutableMapOf<String, RoomArgs>()
private val timers = mutableListOf<Timer>()
var callback: Callback? = null var callback: Callback? = null
private val jobs = mutableListOf<Job>()
private val mainHandler by lazy { Handler(mainLooper) }
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
@ -78,9 +85,6 @@ class LocationSharingService : VectorService(), LocationTracker.Callback {
val notification = notificationUtils.buildLiveLocationSharingNotification() val notification = notificationUtils.buildLiveLocationSharingNotification()
startForeground(roomArgs.roomId.hashCode(), notification) startForeground(roomArgs.roomId.hashCode(), notification)
// Schedule a timer to stop sharing
scheduleTimer(roomArgs.roomId, roomArgs.durationMillis)
// Send beacon info state event // Send beacon info state event
launchInIO { session -> launchInIO { session ->
sendStartingLiveBeaconInfo(session, roomArgs) sendStartingLiveBeaconInfo(session, roomArgs)
@ -101,6 +105,7 @@ class LocationSharingService : VectorService(), LocationTracker.Callback {
when (result) { when (result) {
is UpdateLiveLocationShareResult.Success -> { is UpdateLiveLocationShareResult.Success -> {
addRoomArgs(result.beaconEventId, roomArgs) addRoomArgs(result.beaconEventId, roomArgs)
listenForLiveSummaryChanges(roomArgs.roomId, result.beaconEventId)
locationTracker.requestLastKnownLocation() locationTracker.requestLastKnownLocation()
} }
is UpdateLiveLocationShareResult.Failure -> { is UpdateLiveLocationShareResult.Failure -> {
@ -115,22 +120,7 @@ class LocationSharingService : VectorService(), LocationTracker.Callback {
} }
} }
private fun scheduleTimer(roomId: String, durationMillis: Long) { private fun stopSharingLocation(roomId: String) {
Timer()
.apply {
schedule(object : TimerTask() {
override fun run() {
stopSharingLocation(roomId)
timers.remove(this@apply)
}
}, durationMillis)
}
.also {
timers.add(it)
}
}
fun stopSharingLocation(roomId: String) {
Timber.i("### LocationSharingService.stopSharingLocation for $roomId") Timber.i("### LocationSharingService.stopSharingLocation for $roomId")
removeRoomArgs(roomId) removeRoomArgs(roomId)
tryToDestroyMe() tryToDestroyMe()
@ -177,9 +167,9 @@ class LocationSharingService : VectorService(), LocationTracker.Callback {
} }
private fun destroyMe() { private fun destroyMe() {
jobs.forEach { it.cancel() }
jobs.clear()
locationTracker.removeCallback(this) locationTracker.removeCallback(this)
timers.forEach { it.cancel() }
timers.clear()
stopSelf() stopSelf()
} }
@ -202,6 +192,21 @@ class LocationSharingService : VectorService(), LocationTracker.Callback {
beaconIds.forEach { roomArgsMap.remove(it) } 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) = private fun launchInIO(block: suspend CoroutineScope.(Session) -> Unit) =
activeSessionHolder activeSessionHolder
.getSafeActiveSession() .getSafeActiveSession()

View File

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

View File

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

View File

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

View File

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

View File

@ -34,16 +34,4 @@ class FakeLocationSharingServiceConnection {
fun verifyBind(callback: LocationSharingServiceConnection.Callback) { fun verifyBind(callback: LocationSharingServiceConnection.Callback) {
verify { instance.bind(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) }
}
} }