Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/6123.wip
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[Live location sharing] Update entity in DB when a live is timed out
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconLocati
* Aggregation info concerning a live location share.
*/
data class LiveLocationShareAggregatedSummary(
/**
* Indicate whether the live is currently running.
*/
val isActive: Boolean?,
val endOfLiveTimestampMillis: Long?,
val lastLocationDataContent: MessageBeaconLocationDataContent?,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ internal open class LiveLocationShareAggregatedSummaryEntity(

var roomId: String = "",

/**
* Indicate whether the live is currently running.
*/
var isActive: Boolean? = null,

var endOfLiveTimestampMillis: Long? = null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,11 @@ internal fun LiveLocationShareAggregatedSummaryEntity.Companion.getOrCreate(
return LiveLocationShareAggregatedSummaryEntity.where(realm, roomId, eventId).findFirst()
?: LiveLocationShareAggregatedSummaryEntity.create(realm, roomId, eventId)
}

internal fun LiveLocationShareAggregatedSummaryEntity.Companion.get(
realm: Realm,
roomId: String,
eventId: String,
): LiveLocationShareAggregatedSummaryEntity? {
return LiveLocationShareAggregatedSummaryEntity.where(realm, roomId, eventId).findFirst()
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not for this PR, but now the line 55 could be replaced by a call to this new method.

Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ import org.matrix.android.sdk.internal.session.profile.ProfileModule
import org.matrix.android.sdk.internal.session.pushers.AddPusherWorker
import org.matrix.android.sdk.internal.session.pushers.PushersModule
import org.matrix.android.sdk.internal.session.room.RoomModule
import org.matrix.android.sdk.internal.session.room.aggregation.livelocation.DeactivateLiveLocationShareWorker
import org.matrix.android.sdk.internal.session.room.send.MultipleEventSendingDispatcherWorker
import org.matrix.android.sdk.internal.session.room.send.RedactEventWorker
import org.matrix.android.sdk.internal.session.room.send.SendEventWorker
Expand Down Expand Up @@ -131,6 +132,8 @@ internal interface SessionComponent {

fun inject(worker: UpdateTrustWorker)

fun inject(worker: DeactivateLiveLocationShareWorker)

@Component.Factory
interface Factory {
fun create(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/*
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.matrix.android.sdk.internal.session.room.aggregation.livelocation

import android.content.Context
import androidx.work.WorkerParameters
import com.squareup.moshi.JsonClass
import io.realm.RealmConfiguration
import org.matrix.android.sdk.api.util.md5
import org.matrix.android.sdk.internal.SessionManager
import org.matrix.android.sdk.internal.database.awaitTransaction
import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntity
import org.matrix.android.sdk.internal.database.query.get
import org.matrix.android.sdk.internal.di.SessionDatabase
import org.matrix.android.sdk.internal.session.SessionComponent
import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker
import org.matrix.android.sdk.internal.worker.SessionWorkerParams
import timber.log.Timber
import javax.inject.Inject

/**
* Worker dedicated to update live location summary data so that it is considered as deactivated.
* For the context: it is needed since a live location share should be deactivated after a certain timeout.
*/
internal class DeactivateLiveLocationShareWorker(context: Context, params: WorkerParameters, sessionManager: SessionManager) :
SessionSafeCoroutineWorker<DeactivateLiveLocationShareWorker.Params>(
context,
params,
sessionManager,
Params::class.java
) {

@JsonClass(generateAdapter = true)
internal data class Params(
override val sessionId: String,
override val lastFailureMessage: String? = null,
val eventId: String,
val roomId: String
) : SessionWorkerParams

@SessionDatabase
@Inject lateinit var realmConfiguration: RealmConfiguration

override fun injectWith(injector: SessionComponent) {
injector.inject(this)
}

override suspend fun doSafeWork(params: Params): Result {
return runCatching {
deactivateLiveLocationShare(params)
}.fold(
onSuccess = {
Result.success()
},
onFailure = {
Timber.e("failed to deactivate live, eventId: ${params.eventId}, roomId: ${params.roomId}")
Result.failure()
}
)
}

private suspend fun deactivateLiveLocationShare(params: Params) {
awaitTransaction(realmConfiguration) { realm ->
val aggregatedSummary = LiveLocationShareAggregatedSummaryEntity.get(
realm = realm,
roomId = params.roomId,
eventId = params.eventId
)
aggregatedSummary?.isActive = false
}
}

override fun buildErrorParams(params: Params, message: String): Params {
return params.copy(lastFailureMessage = params.lastFailureMessage ?: message)
}

companion object {
fun getWorkName(eventId: String, roomId: String): String {
val hash = "$eventId$roomId".md5()
return "DeactivateLiveLocationWork-$hash"
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package org.matrix.android.sdk.internal.session.room.aggregation.livelocation

import androidx.work.ExistingWorkPolicy
import io.realm.Realm
import org.matrix.android.sdk.api.extensions.orTrue
import org.matrix.android.sdk.api.session.events.model.Event
Expand All @@ -26,17 +27,27 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconLocati
import org.matrix.android.sdk.internal.database.mapper.ContentMapper
import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntity
import org.matrix.android.sdk.internal.database.query.getOrCreate
import org.matrix.android.sdk.internal.di.SessionId
import org.matrix.android.sdk.internal.di.WorkManagerProvider
import org.matrix.android.sdk.internal.util.time.Clock
import org.matrix.android.sdk.internal.worker.WorkerParamsFactory
import timber.log.Timber
import java.util.concurrent.TimeUnit
import javax.inject.Inject

internal class LiveLocationAggregationProcessor @Inject constructor() {
internal class LiveLocationAggregationProcessor @Inject constructor(
@SessionId private val sessionId: String,
private val workManagerProvider: WorkManagerProvider,
private val clock: Clock,
) {

fun handleBeaconInfo(realm: Realm, event: Event, content: MessageBeaconInfoContent, roomId: String, isLocalEcho: Boolean) {
if (event.senderId.isNullOrEmpty() || isLocalEcho) {
return
}

val targetEventId = if (content.isLive.orTrue()) {
val isLive = content.isLive.orTrue()
val targetEventId = if (isLive) {
event.eventId
} else {
// when live is set to false, we use the id of the event that should have been replaced
Expand All @@ -56,8 +67,39 @@ internal class LiveLocationAggregationProcessor @Inject constructor() {

Timber.d("updating summary of id=$targetEventId with isLive=${content.isLive}")

aggregatedSummary.endOfLiveTimestampMillis = content.getBestTimestampMillis()?.let { it + (content.timeout ?: 0) }
aggregatedSummary.isActive = content.isLive
val endOfLiveTimestampMillis = content.getBestTimestampMillis()?.let { it + (content.timeout ?: 0) }
aggregatedSummary.endOfLiveTimestampMillis = endOfLiveTimestampMillis
aggregatedSummary.isActive = isLive

if (isLive) {
scheduleDeactivationAfterTimeout(targetEventId, roomId, endOfLiveTimestampMillis)
} else {
cancelDeactivationAfterTimeout(targetEventId, roomId)
}
}

private fun scheduleDeactivationAfterTimeout(eventId: String, roomId: String, endOfLiveTimestampMillis: Long?) {
endOfLiveTimestampMillis ?: return

val workParams = DeactivateLiveLocationShareWorker.Params(sessionId = sessionId, eventId = eventId, roomId = roomId)
val workData = WorkerParamsFactory.toData(workParams)
val workName = DeactivateLiveLocationShareWorker.getWorkName(eventId = eventId, roomId = roomId)
val workDelayMillis = (endOfLiveTimestampMillis - clock.epochMillis()).coerceAtLeast(0)
val workRequest = workManagerProvider.matrixOneTimeWorkRequestBuilder<DeactivateLiveLocationShareWorker>()
.setInitialDelay(workDelayMillis, TimeUnit.MILLISECONDS)
.setInputData(workData)
.build()

workManagerProvider.workManager.enqueueUniqueWork(
workName,
ExistingWorkPolicy.REPLACE,
workRequest
)
}

private fun cancelDeactivationAfterTimeout(eventId: String, roomId: String) {
val workName = DeactivateLiveLocationShareWorker.getWorkName(eventId = eventId, roomId = roomId)
workManagerProvider.workManager.cancelUniqueWork(workName)
}

fun handleBeaconLocationData(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import org.matrix.android.sdk.internal.di.MatrixScope
import org.matrix.android.sdk.internal.session.content.UploadContentWorker
import org.matrix.android.sdk.internal.session.group.GetGroupDataWorker
import org.matrix.android.sdk.internal.session.pushers.AddPusherWorker
import org.matrix.android.sdk.internal.session.room.aggregation.livelocation.DeactivateLiveLocationShareWorker
import org.matrix.android.sdk.internal.session.room.send.MultipleEventSendingDispatcherWorker
import org.matrix.android.sdk.internal.session.room.send.RedactEventWorker
import org.matrix.android.sdk.internal.session.room.send.SendEventWorker
Expand Down Expand Up @@ -64,9 +65,11 @@ internal class MatrixWorkerFactory @Inject constructor(private val sessionManage
SyncWorker(appContext, workerParameters, sessionManager)
UpdateTrustWorker::class.java.name ->
UpdateTrustWorker(appContext, workerParameters, sessionManager)
UploadContentWorker::class.java.name ->
UploadContentWorker::class.java.name ->
UploadContentWorker(appContext, workerParameters, sessionManager)
else -> {
DeactivateLiveLocationShareWorker::class.java.name ->
DeactivateLiveLocationShareWorker(appContext, workerParameters, sessionManager)
else -> {
Timber.w("No worker defined on MatrixWorkerFactory for $workerClassName will delegate to default.")
// Return null to delegate to the default WorkerFactory.
null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ import im.vector.app.features.home.room.detail.timeline.item.MessageLiveLocation
import im.vector.app.features.location.INITIAL_MAP_ZOOM_IN_TIMELINE
import im.vector.app.features.location.UrlMapProvider
import im.vector.app.features.location.toLocationData
import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
import org.threeten.bp.LocalDateTime
Expand Down Expand Up @@ -129,7 +128,7 @@ class LiveLocationShareMessageItemFactory @Inject constructor(
private fun getViewState(liveLocationShareSummaryData: LiveLocationShareSummaryData?): LiveLocationShareViewState {
return when {
liveLocationShareSummaryData?.isActive == null -> LiveLocationShareViewState.Unkwown
liveLocationShareSummaryData.isActive.not() || isLiveTimedOut(liveLocationShareSummaryData) -> LiveLocationShareViewState.Inactive
liveLocationShareSummaryData.isActive.not() -> LiveLocationShareViewState.Inactive
liveLocationShareSummaryData.isActive && liveLocationShareSummaryData.lastGeoUri.isNullOrEmpty() -> LiveLocationShareViewState.Loading
else ->
LiveLocationShareViewState.Running(
Expand All @@ -139,16 +138,6 @@ class LiveLocationShareMessageItemFactory @Inject constructor(
}.also { viewState -> Timber.d("computed viewState: $viewState") }
}

private fun isLiveTimedOut(liveLocationShareSummaryData: LiveLocationShareSummaryData): Boolean {
return getEndOfLiveDateTime(liveLocationShareSummaryData)
?.let { endOfLive ->
// this will only cover users with different timezones but not users with manually time set
val now = LocalDateTime.now()
now.isAfter(endOfLive)
}
.orFalse()
}

private fun getEndOfLiveDateTime(liveLocationShareSummaryData: LiveLocationShareSummaryData): LocalDateTime? {
return liveLocationShareSummaryData.endOfLiveTimestampMillis?.let { DateProvider.toLocalDateTime(timestamp = it) }
}
Expand Down