mirror of
https://github.com/element-hq/element-android
synced 2024-12-20 08:22:00 +03:00
Merge branch 'release/1.0.7'
This commit is contained in:
commit
d0d4a19033
197 changed files with 5309 additions and 1066 deletions
22
CHANGES.md
22
CHANGES.md
|
@ -1,3 +1,25 @@
|
|||
Changes in Element 1.0.7 (2020-09-17)
|
||||
===================================================
|
||||
|
||||
Improvements 🙌:
|
||||
- Handle date formatting properly (show time am/pm if needed, display year when needed)
|
||||
- Improve F-Droid Notification (#2055)
|
||||
|
||||
Bugfix 🐛:
|
||||
- Clear the notification when the event is read elsewhere (#1822)
|
||||
- Speakerphone is not used for ringback tone (#1644, #1645)
|
||||
- Back camera preview is not mirrored anymore (#1776)
|
||||
- Various report of people that cannot play video (#2107)
|
||||
- Rooms incorrectly marked as unread (#588)
|
||||
- Allow users to show/hide room member state events (#1231)
|
||||
- Fix stuck on loader when launching home
|
||||
|
||||
SDK API changes ⚠️:
|
||||
- Create a new RawService to get plain data from the server.
|
||||
|
||||
Other changes:
|
||||
- Performance: share Realm instance used on UI thread and improve SharedPreferences reading time.
|
||||
|
||||
Changes in Element 1.0.6 (2020-09-08)
|
||||
===================================================
|
||||
|
||||
|
|
|
@ -24,6 +24,7 @@ import com.zhuinden.monarchy.Monarchy
|
|||
import org.matrix.android.sdk.BuildConfig
|
||||
import org.matrix.android.sdk.api.auth.AuthenticationService
|
||||
import org.matrix.android.sdk.api.legacy.LegacySessionImporter
|
||||
import org.matrix.android.sdk.api.raw.RawService
|
||||
import org.matrix.android.sdk.common.DaggerTestMatrixComponent
|
||||
import org.matrix.android.sdk.internal.SessionManager
|
||||
import org.matrix.android.sdk.internal.network.UserAgentHolder
|
||||
|
@ -41,6 +42,7 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo
|
|||
|
||||
@Inject internal lateinit var legacySessionImporter: LegacySessionImporter
|
||||
@Inject internal lateinit var authenticationService: AuthenticationService
|
||||
@Inject internal lateinit var rawService: RawService
|
||||
@Inject internal lateinit var userAgentHolder: UserAgentHolder
|
||||
@Inject internal lateinit var backgroundDetectionObserver: BackgroundDetectionObserver
|
||||
@Inject internal lateinit var olmManager: OlmManager
|
||||
|
@ -61,6 +63,8 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo
|
|||
return authenticationService
|
||||
}
|
||||
|
||||
fun rawService() = rawService
|
||||
|
||||
fun legacySessionImporter(): LegacySessionImporter {
|
||||
return legacySessionImporter
|
||||
}
|
||||
|
|
|
@ -25,8 +25,16 @@ import org.matrix.android.sdk.internal.di.MatrixComponent
|
|||
import org.matrix.android.sdk.internal.di.MatrixModule
|
||||
import org.matrix.android.sdk.internal.di.MatrixScope
|
||||
import org.matrix.android.sdk.internal.di.NetworkModule
|
||||
import org.matrix.android.sdk.internal.raw.RawModule
|
||||
|
||||
@Component(modules = [TestModule::class, MatrixModule::class, NetworkModule::class, AuthModule::class, TestNetworkModule::class])
|
||||
@Component(modules = [
|
||||
TestModule::class,
|
||||
MatrixModule::class,
|
||||
NetworkModule::class,
|
||||
AuthModule::class,
|
||||
RawModule::class,
|
||||
TestNetworkModule::class
|
||||
])
|
||||
@MatrixScope
|
||||
internal interface TestMatrixComponent : MatrixComponent {
|
||||
|
||||
|
|
|
@ -25,6 +25,7 @@ import com.zhuinden.monarchy.Monarchy
|
|||
import org.matrix.android.sdk.BuildConfig
|
||||
import org.matrix.android.sdk.api.auth.AuthenticationService
|
||||
import org.matrix.android.sdk.api.legacy.LegacySessionImporter
|
||||
import org.matrix.android.sdk.api.raw.RawService
|
||||
import org.matrix.android.sdk.internal.SessionManager
|
||||
import org.matrix.android.sdk.internal.di.DaggerMatrixComponent
|
||||
import org.matrix.android.sdk.internal.network.UserAgentHolder
|
||||
|
@ -42,6 +43,7 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo
|
|||
|
||||
@Inject internal lateinit var legacySessionImporter: LegacySessionImporter
|
||||
@Inject internal lateinit var authenticationService: AuthenticationService
|
||||
@Inject internal lateinit var rawService: RawService
|
||||
@Inject internal lateinit var userAgentHolder: UserAgentHolder
|
||||
@Inject internal lateinit var backgroundDetectionObserver: BackgroundDetectionObserver
|
||||
@Inject internal lateinit var olmManager: OlmManager
|
||||
|
@ -62,6 +64,8 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo
|
|||
return authenticationService
|
||||
}
|
||||
|
||||
fun rawService() = rawService
|
||||
|
||||
fun legacySessionImporter(): LegacySessionImporter {
|
||||
return legacySessionImporter
|
||||
}
|
||||
|
|
|
@ -42,9 +42,6 @@ import org.matrix.android.sdk.api.util.JsonDict
|
|||
* }
|
||||
* ]
|
||||
* }
|
||||
* "im.vector.riot.jitsi": {
|
||||
* "preferredDomain": "https://jitsi.riot.im/"
|
||||
* }
|
||||
* }
|
||||
* </pre>
|
||||
*/
|
||||
|
@ -57,24 +54,5 @@ data class WellKnown(
|
|||
val identityServer: WellKnownBaseConfig? = null,
|
||||
|
||||
@Json(name = "m.integrations")
|
||||
val integrations: JsonDict? = null,
|
||||
|
||||
@Json(name = "im.vector.riot.e2ee")
|
||||
val e2eAdminSetting: E2EWellKnownConfig? = null,
|
||||
|
||||
@Json(name = "im.vector.riot.jitsi")
|
||||
val jitsiServer: WellKnownPreferredConfig? = null
|
||||
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class E2EWellKnownConfig(
|
||||
@Json(name = "default")
|
||||
val e2eDefault: Boolean = true
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class WellKnownPreferredConfig(
|
||||
@Json(name = "preferredDomain")
|
||||
val preferredDomain: String? = null
|
||||
val integrations: JsonDict? = null
|
||||
)
|
||||
|
|
|
@ -132,6 +132,8 @@ data class MatrixError(
|
|||
const val M_CANNOT_LEAVE_SERVER_NOTICE_ROOM = "M_CANNOT_LEAVE_SERVER_NOTICE_ROOM"
|
||||
/** (Not documented yet) */
|
||||
const val M_WRONG_ROOM_KEYS_VERSION = "M_WRONG_ROOM_KEYS_VERSION"
|
||||
/** (Not documented yet) */
|
||||
const val M_WEAK_PASSWORD = "M_WEAK_PASSWORD"
|
||||
|
||||
const val M_TERMS_NOT_SIGNED = "M_TERMS_NOT_SIGNED"
|
||||
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
* Copyright 2020 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.api.raw
|
||||
|
||||
sealed class RawCacheStrategy {
|
||||
// Data is always fetched from the server
|
||||
object NoCache: RawCacheStrategy()
|
||||
|
||||
// Once data is retrieved, it is stored for the provided amount of time.
|
||||
// In case of error, and if strict is set to false, the cache can be returned if available
|
||||
data class TtlCache(val validityDurationInMillis: Long, val strict: Boolean): RawCacheStrategy()
|
||||
|
||||
// Once retrieved, the data is stored in cache and will be always get from the cache
|
||||
object InfiniteCache: RawCacheStrategy()
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* Copyright 2020 New Vector Ltd
|
||||
* Copyright 2020 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.api.raw
|
||||
|
||||
import org.matrix.android.sdk.api.MatrixCallback
|
||||
import org.matrix.android.sdk.api.util.Cancelable
|
||||
|
||||
/**
|
||||
* Useful methods to fetch raw data from the server. The access token will not be used to fetched the data
|
||||
*/
|
||||
interface RawService {
|
||||
/**
|
||||
* Get a URL, either from cache or from the remote server, depending on the cache strategy
|
||||
*/
|
||||
fun getUrl(url: String,
|
||||
rawCacheStrategy: RawCacheStrategy,
|
||||
matrixCallback: MatrixCallback<String>): Cancelable
|
||||
|
||||
/**
|
||||
* Specific case for the well-known file. Cache validity is 8 hours
|
||||
*/
|
||||
fun getWellknown(userId: String, matrixCallback: MatrixCallback<String>): Cancelable
|
||||
|
||||
/**
|
||||
* Clear all the cache data
|
||||
*/
|
||||
fun clearCache(matrixCallback: MatrixCallback<Unit>): Cancelable
|
||||
}
|
|
@ -110,7 +110,7 @@ interface Session :
|
|||
* This does not work in doze mode :/
|
||||
* If battery optimization is on it can work in app standby but that's all :/
|
||||
*/
|
||||
fun startAutomaticBackgroundSync(repeatDelay: Long = 30_000L)
|
||||
fun startAutomaticBackgroundSync(timeOutInSeconds: Long, repeatDelayInSeconds: Long)
|
||||
|
||||
fun stopAnyBackgroundSync()
|
||||
|
||||
|
|
|
@ -33,16 +33,7 @@ data class HomeServerCapabilities(
|
|||
/**
|
||||
* Default identity server url, provided in Wellknown
|
||||
*/
|
||||
val defaultIdentityServerUrl: String? = null,
|
||||
/**
|
||||
* Option to allow homeserver admins to set the default E2EE behaviour back to disabled for DMs / private rooms
|
||||
* (as it was before) for various environments where this is desired.
|
||||
*/
|
||||
val adminE2EByDefault: Boolean = true,
|
||||
/**
|
||||
* Preferred Jitsi domain, provided in Wellknown
|
||||
*/
|
||||
val preferredJitsiDomain: String? = null
|
||||
val defaultIdentityServerUrl: String? = null
|
||||
) {
|
||||
companion object {
|
||||
const val MAX_UPLOAD_FILE_SIZE_UNKNOWN = -1L
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* 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.api.session.room.summary
|
||||
|
||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||
|
||||
object RoomSummaryConstants {
|
||||
|
||||
val PREVIEWABLE_TYPES = listOf(
|
||||
// TODO filter message type (KEY_VERIFICATION_READY, etc.)
|
||||
EventType.MESSAGE,
|
||||
EventType.CALL_INVITE,
|
||||
EventType.CALL_HANGUP,
|
||||
EventType.CALL_ANSWER,
|
||||
EventType.ENCRYPTED,
|
||||
EventType.STICKER,
|
||||
EventType.REACTION
|
||||
)
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* 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.api.session.room.timeline
|
||||
|
||||
data class TimelineEventFilters(
|
||||
/**
|
||||
* A flag to filter edit events
|
||||
*/
|
||||
val filterEdits: Boolean = false,
|
||||
/**
|
||||
* A flag to filter redacted events
|
||||
*/
|
||||
val filterRedacted: Boolean = false,
|
||||
/**
|
||||
* A flag to filter useless events, such as membership events without any change
|
||||
*/
|
||||
val filterUseless: Boolean = false,
|
||||
/**
|
||||
* A flag to filter by types. It should be used with [allowedTypes] field
|
||||
*/
|
||||
val filterTypes: Boolean = false,
|
||||
/**
|
||||
* If [filterTypes] is true, the list of types allowed by the list.
|
||||
*/
|
||||
val allowedTypes: List<String> = emptyList()
|
||||
)
|
|
@ -26,25 +26,9 @@ data class TimelineSettings(
|
|||
*/
|
||||
val initialSize: Int,
|
||||
/**
|
||||
* A flag to filter edit events
|
||||
* Filters for timeline event
|
||||
*/
|
||||
val filterEdits: Boolean = false,
|
||||
/**
|
||||
* A flag to filter redacted events
|
||||
*/
|
||||
val filterRedacted: Boolean = false,
|
||||
/**
|
||||
* A flag to filter useless events, such as membership events without any change
|
||||
*/
|
||||
val filterUseless: Boolean = false,
|
||||
/**
|
||||
* A flag to filter by types. It should be used with [allowedTypes] field
|
||||
*/
|
||||
val filterTypes: Boolean = false,
|
||||
/**
|
||||
* If [filterTypes] is true, the list of types allowed by the list.
|
||||
*/
|
||||
val allowedTypes: List<String> = emptyList(),
|
||||
val filters: TimelineEventFilters = TimelineEventFilters(),
|
||||
/**
|
||||
* If true, will build read receipts for each event.
|
||||
*/
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* 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.database
|
||||
|
||||
import io.realm.Realm
|
||||
import java.io.Closeable
|
||||
|
||||
internal class RealmInstanceWrapper(private val realm: Realm, private val closeRealmOnClose: Boolean) : Closeable {
|
||||
|
||||
override fun close() {
|
||||
if (closeRealmOnClose) {
|
||||
realm.close()
|
||||
}
|
||||
}
|
||||
|
||||
fun <R> withRealm(block: (Realm) -> R): R {
|
||||
return use {
|
||||
block(it.realm)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* 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.database
|
||||
|
||||
import android.os.Looper
|
||||
import androidx.annotation.MainThread
|
||||
import com.zhuinden.monarchy.Monarchy
|
||||
import io.realm.Realm
|
||||
import org.matrix.android.sdk.internal.di.SessionDatabase
|
||||
import org.matrix.android.sdk.internal.session.SessionLifecycleObserver
|
||||
import org.matrix.android.sdk.internal.session.SessionScope
|
||||
import javax.inject.Inject
|
||||
import kotlin.concurrent.getOrSet
|
||||
|
||||
/**
|
||||
* This class keeps an instance of realm open in the main thread so you can grab it whenever you want to get a realm
|
||||
* instance. This does check each time if you are on the main thread or not and returns the appropriate realm instance.
|
||||
*/
|
||||
@SessionScope
|
||||
internal class RealmSessionProvider @Inject constructor(@SessionDatabase private val monarchy: Monarchy)
|
||||
: SessionLifecycleObserver {
|
||||
|
||||
private val realmThreadLocal = ThreadLocal<Realm>()
|
||||
|
||||
/**
|
||||
* Allow you to execute a block with an opened realm. It automatically closes it if necessary (ie. when not in main thread)
|
||||
*/
|
||||
fun <R> withRealm(block: (Realm) -> R): R {
|
||||
return getRealmWrapper().withRealm(block)
|
||||
}
|
||||
|
||||
@MainThread
|
||||
override fun onStart() {
|
||||
realmThreadLocal.getOrSet {
|
||||
Realm.getInstance(monarchy.realmConfiguration)
|
||||
}
|
||||
}
|
||||
|
||||
@MainThread
|
||||
override fun onStop() {
|
||||
realmThreadLocal.get()?.close()
|
||||
realmThreadLocal.remove()
|
||||
}
|
||||
|
||||
private fun getRealmWrapper(): RealmInstanceWrapper {
|
||||
val isOnMainThread = isOnMainThread()
|
||||
val realm = if (isOnMainThread) {
|
||||
realmThreadLocal.getOrSet {
|
||||
Realm.getInstance(monarchy.realmConfiguration)
|
||||
}
|
||||
} else {
|
||||
Realm.getInstance(monarchy.realmConfiguration)
|
||||
}
|
||||
return RealmInstanceWrapper(realm, closeRealmOnClose = !isOnMainThread)
|
||||
}
|
||||
|
||||
private fun isOnMainThread() = Looper.myLooper() == Looper.getMainLooper()
|
||||
}
|
|
@ -28,7 +28,7 @@ import javax.inject.Inject
|
|||
class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
|
||||
|
||||
companion object {
|
||||
const val SESSION_STORE_SCHEMA_VERSION = 4L
|
||||
const val SESSION_STORE_SCHEMA_VERSION = 5L
|
||||
}
|
||||
|
||||
override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) {
|
||||
|
@ -38,6 +38,7 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
|
|||
if (oldVersion <= 1) migrateTo2(realm)
|
||||
if (oldVersion <= 2) migrateTo3(realm)
|
||||
if (oldVersion <= 3) migrateTo4(realm)
|
||||
if (oldVersion <= 4) migrateTo5(realm)
|
||||
}
|
||||
|
||||
private fun migrateTo1(realm: DynamicRealm) {
|
||||
|
@ -54,16 +55,16 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
|
|||
private fun migrateTo2(realm: DynamicRealm) {
|
||||
Timber.d("Step 1 -> 2")
|
||||
realm.schema.get("HomeServerCapabilitiesEntity")
|
||||
?.addField(HomeServerCapabilitiesEntityFields.ADMIN_E2_E_BY_DEFAULT, Boolean::class.java)
|
||||
?.addField("adminE2EByDefault", Boolean::class.java)
|
||||
?.transform { obj ->
|
||||
obj.setBoolean(HomeServerCapabilitiesEntityFields.ADMIN_E2_E_BY_DEFAULT, true)
|
||||
obj.setBoolean("adminE2EByDefault", true)
|
||||
}
|
||||
}
|
||||
|
||||
private fun migrateTo3(realm: DynamicRealm) {
|
||||
Timber.d("Step 2 -> 3")
|
||||
realm.schema.get("HomeServerCapabilitiesEntity")
|
||||
?.addField(HomeServerCapabilitiesEntityFields.PREFERRED_JITSI_DOMAIN, String::class.java)
|
||||
?.addField("preferredJitsiDomain", String::class.java)
|
||||
?.transform { obj ->
|
||||
// Schedule a refresh of the capabilities
|
||||
obj.setLong(HomeServerCapabilitiesEntityFields.LAST_UPDATED_TIMESTAMP, 0)
|
||||
|
@ -82,4 +83,11 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
|
|||
.setRequired(PendingThreePidEntityFields.SID, true)
|
||||
.addField(PendingThreePidEntityFields.SUBMIT_URL, String::class.java)
|
||||
}
|
||||
|
||||
private fun migrateTo5(realm: DynamicRealm) {
|
||||
Timber.d("Step 4 -> 5")
|
||||
realm.schema.get("HomeServerCapabilitiesEntity")
|
||||
?.removeField("adminE2EByDefault")
|
||||
?.removeField("preferredJitsiDomain")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,9 +30,7 @@ internal object HomeServerCapabilitiesMapper {
|
|||
canChangePassword = entity.canChangePassword,
|
||||
maxUploadFileSize = entity.maxUploadFileSize,
|
||||
lastVersionIdentityServerSupported = entity.lastVersionIdentityServerSupported,
|
||||
defaultIdentityServerUrl = entity.defaultIdentityServerUrl,
|
||||
adminE2EByDefault = entity.adminE2EByDefault,
|
||||
preferredJitsiDomain = entity.preferredJitsiDomain
|
||||
defaultIdentityServerUrl = entity.defaultIdentityServerUrl
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,26 +18,24 @@
|
|||
package org.matrix.android.sdk.internal.database.mapper
|
||||
|
||||
import org.matrix.android.sdk.api.session.room.model.ReadReceipt
|
||||
import org.matrix.android.sdk.internal.database.RealmSessionProvider
|
||||
import org.matrix.android.sdk.internal.database.model.ReadReceiptsSummaryEntity
|
||||
import org.matrix.android.sdk.internal.database.model.UserEntity
|
||||
import org.matrix.android.sdk.internal.database.query.where
|
||||
import org.matrix.android.sdk.internal.di.SessionDatabase
|
||||
import io.realm.Realm
|
||||
import io.realm.RealmConfiguration
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class ReadReceiptsSummaryMapper @Inject constructor(@SessionDatabase private val realmConfiguration: RealmConfiguration) {
|
||||
internal class ReadReceiptsSummaryMapper @Inject constructor(private val realmSessionProvider: RealmSessionProvider) {
|
||||
|
||||
fun map(readReceiptsSummaryEntity: ReadReceiptsSummaryEntity?): List<ReadReceipt> {
|
||||
if (readReceiptsSummaryEntity == null) {
|
||||
return emptyList()
|
||||
}
|
||||
return Realm.getInstance(realmConfiguration).use { realm ->
|
||||
return realmSessionProvider.withRealm { realm ->
|
||||
val readReceipts = readReceiptsSummaryEntity.readReceipts
|
||||
readReceipts
|
||||
.mapNotNull {
|
||||
val user = UserEntity.where(realm, it.userId).findFirst()
|
||||
?: return@mapNotNull null
|
||||
?: return@mapNotNull null
|
||||
ReadReceipt(user.asDomain(), it.originServerTs.toLong())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,17 +17,15 @@
|
|||
|
||||
package org.matrix.android.sdk.internal.database.model
|
||||
|
||||
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities
|
||||
import io.realm.RealmObject
|
||||
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities
|
||||
|
||||
internal open class HomeServerCapabilitiesEntity(
|
||||
var canChangePassword: Boolean = true,
|
||||
var maxUploadFileSize: Long = HomeServerCapabilities.MAX_UPLOAD_FILE_SIZE_UNKNOWN,
|
||||
var lastVersionIdentityServerSupported: Boolean = false,
|
||||
var defaultIdentityServerUrl: String? = null,
|
||||
var adminE2EByDefault: Boolean = true,
|
||||
var lastUpdatedTimestamp: Long = 0L,
|
||||
var preferredJitsiDomain: String? = null
|
||||
var lastUpdatedTimestamp: Long = 0L
|
||||
) : RealmObject() {
|
||||
|
||||
companion object
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
* Copyright 2020 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.database.model
|
||||
|
||||
import io.realm.RealmObject
|
||||
import io.realm.annotations.PrimaryKey
|
||||
|
||||
internal open class RawCacheEntity(
|
||||
@PrimaryKey
|
||||
var url: String = "",
|
||||
var data: String = "",
|
||||
var lastUpdatedTimestamp: Long = 0L
|
||||
) : RealmObject() {
|
||||
|
||||
companion object
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
* Copyright 2020 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.database.query
|
||||
|
||||
import io.realm.Realm
|
||||
import io.realm.kotlin.createObject
|
||||
import io.realm.kotlin.where
|
||||
import org.matrix.android.sdk.internal.database.model.RawCacheEntity
|
||||
import org.matrix.android.sdk.internal.database.model.RawCacheEntityFields
|
||||
|
||||
/**
|
||||
* Get the current RawCacheEntity, return null if it does not exist
|
||||
*/
|
||||
internal fun RawCacheEntity.Companion.get(realm: Realm, url: String): RawCacheEntity? {
|
||||
return realm.where<RawCacheEntity>()
|
||||
.equalTo(RawCacheEntityFields.URL, url)
|
||||
.findFirst()
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current RawCacheEntity, create one if it does not exist
|
||||
*/
|
||||
internal fun RawCacheEntity.Companion.getOrCreate(realm: Realm, url: String): RawCacheEntity {
|
||||
return get(realm, url) ?: realm.createObject(url)
|
||||
}
|
|
@ -17,17 +17,18 @@
|
|||
|
||||
package org.matrix.android.sdk.internal.database.query
|
||||
|
||||
import org.matrix.android.sdk.api.session.room.send.SendState
|
||||
import org.matrix.android.sdk.internal.database.model.ChunkEntity
|
||||
import org.matrix.android.sdk.internal.database.model.RoomEntity
|
||||
import org.matrix.android.sdk.internal.database.model.TimelineEventEntity
|
||||
import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields
|
||||
import io.realm.Realm
|
||||
import io.realm.RealmList
|
||||
import io.realm.RealmQuery
|
||||
import io.realm.RealmResults
|
||||
import io.realm.Sort
|
||||
import io.realm.kotlin.where
|
||||
import org.matrix.android.sdk.api.session.room.send.SendState
|
||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineEventFilters
|
||||
import org.matrix.android.sdk.internal.database.model.ChunkEntity
|
||||
import org.matrix.android.sdk.internal.database.model.RoomEntity
|
||||
import org.matrix.android.sdk.internal.database.model.TimelineEventEntity
|
||||
import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields
|
||||
|
||||
internal fun TimelineEventEntity.Companion.where(realm: Realm, roomId: String, eventId: String): RealmQuery<TimelineEventEntity> {
|
||||
return realm.where<TimelineEventEntity>()
|
||||
|
@ -56,16 +57,10 @@ internal fun TimelineEventEntity.Companion.findWithSenderMembershipEvent(realm:
|
|||
internal fun TimelineEventEntity.Companion.latestEvent(realm: Realm,
|
||||
roomId: String,
|
||||
includesSending: Boolean,
|
||||
filterContentRelation: Boolean = false,
|
||||
filterTypes: List<String> = emptyList()): TimelineEventEntity? {
|
||||
filters: TimelineEventFilters = TimelineEventFilters()): TimelineEventEntity? {
|
||||
val roomEntity = RoomEntity.where(realm, roomId).findFirst() ?: return null
|
||||
val sendingTimelineEvents = roomEntity.sendingTimelineEvents.where().filterTypes(filterTypes)
|
||||
val liveEvents = ChunkEntity.findLastForwardChunkOfRoom(realm, roomId)?.timelineEvents?.where()?.filterTypes(filterTypes)
|
||||
if (filterContentRelation) {
|
||||
liveEvents
|
||||
?.not()?.like(TimelineEventEntityFields.ROOT.CONTENT, TimelineEventFilter.Content.EDIT)
|
||||
?.not()?.like(TimelineEventEntityFields.ROOT.CONTENT, TimelineEventFilter.Content.RESPONSE)
|
||||
}
|
||||
val sendingTimelineEvents = roomEntity.sendingTimelineEvents.where().filterEvents(filters)
|
||||
val liveEvents = ChunkEntity.findLastForwardChunkOfRoom(realm, roomId)?.timelineEvents?.where()?.filterEvents(filters)
|
||||
val query = if (includesSending && sendingTimelineEvents.findAll().isNotEmpty()) {
|
||||
sendingTimelineEvents
|
||||
} else {
|
||||
|
@ -76,6 +71,24 @@ internal fun TimelineEventEntity.Companion.latestEvent(realm: Realm,
|
|||
?.findFirst()
|
||||
}
|
||||
|
||||
internal fun RealmQuery<TimelineEventEntity>.filterEvents(filters: TimelineEventFilters): RealmQuery<TimelineEventEntity> {
|
||||
if (filters.filterTypes) {
|
||||
`in`(TimelineEventEntityFields.ROOT.TYPE, filters.allowedTypes.toTypedArray())
|
||||
}
|
||||
if (filters.filterUseless) {
|
||||
not()
|
||||
.equalTo(TimelineEventEntityFields.ROOT.IS_USELESS, true)
|
||||
}
|
||||
if (filters.filterEdits) {
|
||||
not().like(TimelineEventEntityFields.ROOT.CONTENT, TimelineEventFilter.Content.EDIT)
|
||||
not().like(TimelineEventEntityFields.ROOT.CONTENT, TimelineEventFilter.Content.RESPONSE)
|
||||
}
|
||||
if (filters.filterRedacted) {
|
||||
not().like(TimelineEventEntityFields.ROOT.UNSIGNED_DATA, TimelineEventFilter.Unsigned.REDACTED)
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
internal fun RealmQuery<TimelineEventEntity>.filterTypes(filterTypes: List<String>): RealmQuery<TimelineEventEntity> {
|
||||
return if (filterTypes.isEmpty()) {
|
||||
this
|
||||
|
|
|
@ -23,6 +23,10 @@ import javax.inject.Qualifier
|
|||
@Retention(AnnotationRetention.RUNTIME)
|
||||
internal annotation class AuthDatabase
|
||||
|
||||
@Qualifier
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
internal annotation class GlobalDatabase
|
||||
|
||||
@Qualifier
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
internal annotation class SessionDatabase
|
||||
|
|
|
@ -22,22 +22,30 @@ import android.content.res.Resources
|
|||
import com.squareup.moshi.Moshi
|
||||
import dagger.BindsInstance
|
||||
import dagger.Component
|
||||
import okhttp3.OkHttpClient
|
||||
import org.matrix.android.sdk.api.Matrix
|
||||
import org.matrix.android.sdk.api.MatrixConfiguration
|
||||
import org.matrix.android.sdk.api.auth.AuthenticationService
|
||||
import org.matrix.android.sdk.api.raw.RawService
|
||||
import org.matrix.android.sdk.internal.SessionManager
|
||||
import org.matrix.android.sdk.internal.auth.AuthModule
|
||||
import org.matrix.android.sdk.internal.auth.SessionParamsStore
|
||||
import org.matrix.android.sdk.internal.raw.RawModule
|
||||
import org.matrix.android.sdk.internal.session.MockHttpInterceptor
|
||||
import org.matrix.android.sdk.internal.session.TestInterceptor
|
||||
import org.matrix.android.sdk.internal.task.TaskExecutor
|
||||
import org.matrix.android.sdk.internal.util.BackgroundDetectionObserver
|
||||
import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers
|
||||
import okhttp3.OkHttpClient
|
||||
import org.matrix.olm.OlmManager
|
||||
import java.io.File
|
||||
|
||||
@Component(modules = [MatrixModule::class, NetworkModule::class, AuthModule::class, NoOpTestModule::class])
|
||||
@Component(modules = [
|
||||
MatrixModule::class,
|
||||
NetworkModule::class,
|
||||
AuthModule::class,
|
||||
RawModule::class,
|
||||
NoOpTestModule::class
|
||||
])
|
||||
@MatrixScope
|
||||
internal interface MatrixComponent {
|
||||
|
||||
|
@ -53,6 +61,8 @@ internal interface MatrixComponent {
|
|||
|
||||
fun authenticationService(): AuthenticationService
|
||||
|
||||
fun rawService(): RawService
|
||||
|
||||
fun context(): Context
|
||||
|
||||
fun matrixConfiguration(): MatrixConfiguration
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
* Copyright 2020 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.raw
|
||||
|
||||
import com.zhuinden.monarchy.Monarchy
|
||||
import io.realm.kotlin.where
|
||||
import org.matrix.android.sdk.internal.database.model.RawCacheEntity
|
||||
import org.matrix.android.sdk.internal.di.GlobalDatabase
|
||||
import org.matrix.android.sdk.internal.task.Task
|
||||
import org.matrix.android.sdk.internal.util.awaitTransaction
|
||||
import javax.inject.Inject
|
||||
|
||||
internal interface CleanRawCacheTask : Task<Unit, Unit>
|
||||
|
||||
internal class DefaultCleanRawCacheTask @Inject constructor(
|
||||
@GlobalDatabase private val monarchy: Monarchy
|
||||
) : CleanRawCacheTask {
|
||||
|
||||
override suspend fun execute(params: Unit) {
|
||||
monarchy.awaitTransaction { realm ->
|
||||
realm.where<RawCacheEntity>()
|
||||
.findAll()
|
||||
.deleteAllFromRealm()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,101 @@
|
|||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
* Copyright 2020 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.raw
|
||||
|
||||
import com.zhuinden.monarchy.Monarchy
|
||||
import okhttp3.ResponseBody
|
||||
import org.matrix.android.sdk.api.raw.RawCacheStrategy
|
||||
import org.matrix.android.sdk.internal.database.model.RawCacheEntity
|
||||
import org.matrix.android.sdk.internal.database.query.get
|
||||
import org.matrix.android.sdk.internal.database.query.getOrCreate
|
||||
import org.matrix.android.sdk.internal.di.GlobalDatabase
|
||||
import org.matrix.android.sdk.internal.network.executeRequest
|
||||
import org.matrix.android.sdk.internal.task.Task
|
||||
import org.matrix.android.sdk.internal.util.awaitTransaction
|
||||
import java.util.Date
|
||||
import javax.inject.Inject
|
||||
|
||||
internal interface GetUrlTask : Task<GetUrlTask.Params, String> {
|
||||
data class Params(
|
||||
val url: String,
|
||||
val rawCacheStrategy: RawCacheStrategy
|
||||
)
|
||||
}
|
||||
|
||||
internal class DefaultGetUrlTask @Inject constructor(
|
||||
private val rawAPI: RawAPI,
|
||||
@GlobalDatabase private val monarchy: Monarchy
|
||||
) : GetUrlTask {
|
||||
|
||||
override suspend fun execute(params: GetUrlTask.Params): String {
|
||||
return when (params.rawCacheStrategy) {
|
||||
RawCacheStrategy.NoCache -> doRequest(params.url)
|
||||
is RawCacheStrategy.TtlCache -> doRequestWithCache(
|
||||
params.url,
|
||||
params.rawCacheStrategy.validityDurationInMillis,
|
||||
params.rawCacheStrategy.strict
|
||||
)
|
||||
RawCacheStrategy.InfiniteCache -> doRequestWithCache(
|
||||
params.url,
|
||||
Long.MAX_VALUE,
|
||||
true
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun doRequest(url: String): String {
|
||||
return executeRequest<ResponseBody>(null) {
|
||||
apiCall = rawAPI.getUrl(url)
|
||||
}
|
||||
.string()
|
||||
}
|
||||
|
||||
private suspend fun doRequestWithCache(url: String, validityDurationInMillis: Long, strict: Boolean): String {
|
||||
// Get data from cache
|
||||
var dataFromCache: String? = null
|
||||
var isCacheValid = false
|
||||
monarchy.doWithRealm { realm ->
|
||||
val entity = RawCacheEntity.get(realm, url)
|
||||
dataFromCache = entity?.data
|
||||
isCacheValid = entity != null && Date().time < entity.lastUpdatedTimestamp + validityDurationInMillis
|
||||
}
|
||||
|
||||
if (dataFromCache != null && isCacheValid) {
|
||||
return dataFromCache as String
|
||||
}
|
||||
|
||||
// No cache or outdated cache
|
||||
val data = try {
|
||||
doRequest(url)
|
||||
} catch (throwable: Throwable) {
|
||||
// In case of error, we can return value from cache even if outdated
|
||||
return dataFromCache
|
||||
?.takeIf { !strict }
|
||||
?: throw throwable
|
||||
}
|
||||
|
||||
// Store cache
|
||||
monarchy.awaitTransaction { realm ->
|
||||
val rawCacheEntity = RawCacheEntity.getOrCreate(realm, url)
|
||||
rawCacheEntity.data = data
|
||||
rawCacheEntity.lastUpdatedTimestamp = Date().time
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* 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.raw
|
||||
|
||||
import org.matrix.android.sdk.api.MatrixCallback
|
||||
import org.matrix.android.sdk.api.raw.RawCacheStrategy
|
||||
import org.matrix.android.sdk.api.raw.RawService
|
||||
import org.matrix.android.sdk.api.util.Cancelable
|
||||
import org.matrix.android.sdk.internal.task.TaskExecutor
|
||||
import org.matrix.android.sdk.internal.task.configureWith
|
||||
import java.util.concurrent.TimeUnit
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class DefaultRawService @Inject constructor(
|
||||
private val taskExecutor: TaskExecutor,
|
||||
private val getUrlTask: GetUrlTask,
|
||||
private val cleanRawCacheTask: CleanRawCacheTask
|
||||
) : RawService {
|
||||
override fun getUrl(url: String,
|
||||
rawCacheStrategy: RawCacheStrategy,
|
||||
matrixCallback: MatrixCallback<String>): Cancelable {
|
||||
return getUrlTask
|
||||
.configureWith(GetUrlTask.Params(url, rawCacheStrategy)) {
|
||||
callback = matrixCallback
|
||||
}
|
||||
.executeBy(taskExecutor)
|
||||
}
|
||||
|
||||
override fun getWellknown(userId: String,
|
||||
matrixCallback: MatrixCallback<String>): Cancelable {
|
||||
val homeServerDomain = userId.substringAfter(":")
|
||||
return getUrl(
|
||||
"https://$homeServerDomain/.well-known/matrix/client",
|
||||
RawCacheStrategy.TtlCache(TimeUnit.HOURS.toMillis(8), false),
|
||||
matrixCallback
|
||||
)
|
||||
}
|
||||
|
||||
override fun clearCache(matrixCallback: MatrixCallback<Unit>): Cancelable {
|
||||
return cleanRawCacheTask
|
||||
.configureWith(Unit) {
|
||||
callback = matrixCallback
|
||||
}
|
||||
.executeBy(taskExecutor)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
* Copyright 2020 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.raw
|
||||
|
||||
import io.realm.annotations.RealmModule
|
||||
import org.matrix.android.sdk.internal.database.model.RawCacheEntity
|
||||
|
||||
/**
|
||||
* Realm module for global classes
|
||||
*/
|
||||
@RealmModule(library = true,
|
||||
classes = [
|
||||
RawCacheEntity::class
|
||||
])
|
||||
internal class GlobalRealmModule
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Copyright 2020 New Vector Ltd
|
||||
* Copyright 2020 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.raw
|
||||
|
||||
import okhttp3.ResponseBody
|
||||
import retrofit2.Call
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Url
|
||||
|
||||
internal interface RawAPI {
|
||||
@GET
|
||||
fun getUrl(@Url url: String): Call<ResponseBody>
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
/*
|
||||
* Copyright 2020 New Vector Ltd
|
||||
* Copyright 2020 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.raw
|
||||
|
||||
import com.zhuinden.monarchy.Monarchy
|
||||
import dagger.Binds
|
||||
import dagger.Lazy
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import io.realm.RealmConfiguration
|
||||
import okhttp3.OkHttpClient
|
||||
import org.matrix.android.sdk.api.raw.RawService
|
||||
import org.matrix.android.sdk.internal.database.RealmKeysUtils
|
||||
import org.matrix.android.sdk.internal.di.GlobalDatabase
|
||||
import org.matrix.android.sdk.internal.di.MatrixScope
|
||||
import org.matrix.android.sdk.internal.di.Unauthenticated
|
||||
import org.matrix.android.sdk.internal.network.RetrofitFactory
|
||||
|
||||
@Module
|
||||
internal abstract class RawModule {
|
||||
|
||||
@Module
|
||||
companion object {
|
||||
private const val DB_ALIAS = "matrix-sdk-global"
|
||||
|
||||
@JvmStatic
|
||||
@Provides
|
||||
@GlobalDatabase
|
||||
fun providesMonarchy(@GlobalDatabase realmConfiguration: RealmConfiguration): Monarchy {
|
||||
return Monarchy.Builder()
|
||||
.setRealmConfiguration(realmConfiguration)
|
||||
.build()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
@Provides
|
||||
@GlobalDatabase
|
||||
@MatrixScope
|
||||
fun providesRealmConfiguration(realmKeysUtils: RealmKeysUtils): RealmConfiguration {
|
||||
return RealmConfiguration.Builder()
|
||||
.apply {
|
||||
realmKeysUtils.configureEncryption(this, DB_ALIAS)
|
||||
}
|
||||
.name("matrix-sdk-global.realm")
|
||||
.modules(GlobalRealmModule())
|
||||
.build()
|
||||
}
|
||||
|
||||
@Provides
|
||||
@JvmStatic
|
||||
fun providesRawAPI(@Unauthenticated okHttpClient: Lazy<OkHttpClient>,
|
||||
retrofitFactory: RetrofitFactory): RawAPI {
|
||||
return retrofitFactory.create(okHttpClient, "https://example.org").create(RawAPI::class.java)
|
||||
}
|
||||
}
|
||||
|
||||
@Binds
|
||||
abstract fun bindRawService(service: DefaultRawService): RawService
|
||||
|
||||
@Binds
|
||||
abstract fun bindGetUrlTask(task: DefaultGetUrlTask): GetUrlTask
|
||||
|
||||
@Binds
|
||||
abstract fun bindCleanRawCacheTask(task: DefaultCleanRawCacheTask): CleanRawCacheTask
|
||||
}
|
|
@ -144,11 +144,13 @@ internal class DefaultFileService @Inject constructor(
|
|||
|
||||
if (elementToDecrypt != null) {
|
||||
Timber.v("## FileService: decrypt file")
|
||||
val decryptSuccess = MXEncryptedAttachments.decryptAttachment(
|
||||
source.inputStream(),
|
||||
elementToDecrypt,
|
||||
destFile.outputStream().buffered()
|
||||
)
|
||||
val decryptSuccess = destFile.outputStream().buffered().use {
|
||||
MXEncryptedAttachments.decryptAttachment(
|
||||
source.inputStream(),
|
||||
elementToDecrypt,
|
||||
it
|
||||
)
|
||||
}
|
||||
response.close()
|
||||
if (!decryptSuccess) {
|
||||
return@flatMap Try.Failure(IllegalStateException("Decryption error"))
|
||||
|
|
|
@ -166,8 +166,8 @@ internal class DefaultSession @Inject constructor(
|
|||
SyncWorker.requireBackgroundSync(workManagerProvider, sessionId)
|
||||
}
|
||||
|
||||
override fun startAutomaticBackgroundSync(repeatDelay: Long) {
|
||||
SyncWorker.automaticallyBackgroundSync(workManagerProvider, sessionId, 0, repeatDelay)
|
||||
override fun startAutomaticBackgroundSync(timeOutInSeconds: Long, repeatDelayInSeconds: Long) {
|
||||
SyncWorker.automaticallyBackgroundSync(workManagerProvider, sessionId, timeOutInSeconds, repeatDelayInSeconds)
|
||||
}
|
||||
|
||||
override fun stopAnyBackgroundSync() {
|
||||
|
|
|
@ -47,6 +47,7 @@ import org.matrix.android.sdk.internal.crypto.secrets.DefaultSharedSecretStorage
|
|||
import org.matrix.android.sdk.internal.crypto.verification.VerificationMessageProcessor
|
||||
import org.matrix.android.sdk.internal.database.DatabaseCleaner
|
||||
import org.matrix.android.sdk.internal.database.EventInsertLiveObserver
|
||||
import org.matrix.android.sdk.internal.database.RealmSessionProvider
|
||||
import org.matrix.android.sdk.internal.database.SessionRealmConfigurationFactory
|
||||
import org.matrix.android.sdk.internal.di.Authenticated
|
||||
import org.matrix.android.sdk.internal.di.DeviceId
|
||||
|
@ -325,23 +326,27 @@ internal abstract class SessionModule {
|
|||
|
||||
@Binds
|
||||
@IntoSet
|
||||
abstract fun bindIntegrationManager(observer: IntegrationManager): SessionLifecycleObserver
|
||||
abstract fun bindIntegrationManager(manager: IntegrationManager): SessionLifecycleObserver
|
||||
|
||||
@Binds
|
||||
@IntoSet
|
||||
abstract fun bindWidgetUrlFormatter(observer: DefaultWidgetURLFormatter): SessionLifecycleObserver
|
||||
abstract fun bindWidgetUrlFormatter(formatter: DefaultWidgetURLFormatter): SessionLifecycleObserver
|
||||
|
||||
@Binds
|
||||
@IntoSet
|
||||
abstract fun bindShieldTrustUpdated(observer: ShieldTrustUpdater): SessionLifecycleObserver
|
||||
abstract fun bindShieldTrustUpdated(updater: ShieldTrustUpdater): SessionLifecycleObserver
|
||||
|
||||
@Binds
|
||||
@IntoSet
|
||||
abstract fun bindIdentityService(observer: DefaultIdentityService): SessionLifecycleObserver
|
||||
abstract fun bindIdentityService(service: DefaultIdentityService): SessionLifecycleObserver
|
||||
|
||||
@Binds
|
||||
@IntoSet
|
||||
abstract fun bindDatabaseCleaner(observer: DatabaseCleaner): SessionLifecycleObserver
|
||||
abstract fun bindDatabaseCleaner(cleaner: DatabaseCleaner): SessionLifecycleObserver
|
||||
|
||||
@Binds
|
||||
@IntoSet
|
||||
abstract fun bindRealmSessionProvider(provider: RealmSessionProvider): SessionLifecycleObserver
|
||||
|
||||
@Binds
|
||||
abstract fun bindInitialSyncProgressService(service: DefaultInitialSyncProgressService): InitialSyncProgressService
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
package org.matrix.android.sdk.internal.session.homeserver
|
||||
|
||||
import com.zhuinden.monarchy.Monarchy
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
|
||||
import org.matrix.android.sdk.api.auth.wellknown.WellknownResult
|
||||
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities
|
||||
|
@ -32,7 +33,6 @@ import org.matrix.android.sdk.internal.session.integrationmanager.IntegrationMan
|
|||
import org.matrix.android.sdk.internal.task.Task
|
||||
import org.matrix.android.sdk.internal.util.awaitTransaction
|
||||
import org.matrix.android.sdk.internal.wellknown.GetWellknownTask
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import timber.log.Timber
|
||||
import java.util.Date
|
||||
import javax.inject.Inject
|
||||
|
@ -109,16 +109,12 @@ internal class DefaultGetHomeServerCapabilitiesTask @Inject constructor(
|
|||
|
||||
if (getWellknownResult != null && getWellknownResult is WellknownResult.Prompt) {
|
||||
homeServerCapabilitiesEntity.defaultIdentityServerUrl = getWellknownResult.identityServerUrl
|
||||
homeServerCapabilitiesEntity.adminE2EByDefault = getWellknownResult.wellKnown.e2eAdminSetting?.e2eDefault ?: true
|
||||
homeServerCapabilitiesEntity.preferredJitsiDomain = getWellknownResult.wellKnown.jitsiServer?.preferredDomain
|
||||
// We are also checking for integration manager configurations
|
||||
val config = configExtractor.extract(getWellknownResult.wellKnown)
|
||||
if (config != null) {
|
||||
Timber.v("Extracted integration config : $config")
|
||||
realm.insertOrUpdate(config)
|
||||
}
|
||||
} else {
|
||||
homeServerCapabilitiesEntity.adminE2EByDefault = true
|
||||
}
|
||||
homeServerCapabilitiesEntity.lastUpdatedTimestamp = Date().time
|
||||
}
|
||||
|
|
|
@ -17,17 +17,16 @@
|
|||
|
||||
package org.matrix.android.sdk.internal.session.room
|
||||
|
||||
import com.zhuinden.monarchy.Monarchy
|
||||
import io.realm.Realm
|
||||
import org.matrix.android.sdk.api.session.room.Room
|
||||
import org.matrix.android.sdk.api.session.room.model.Membership
|
||||
import org.matrix.android.sdk.internal.database.RealmSessionProvider
|
||||
import org.matrix.android.sdk.internal.database.model.RoomEntity
|
||||
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity
|
||||
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields
|
||||
import org.matrix.android.sdk.internal.database.query.where
|
||||
import org.matrix.android.sdk.internal.di.SessionDatabase
|
||||
import org.matrix.android.sdk.internal.session.SessionScope
|
||||
import org.matrix.android.sdk.internal.session.room.membership.RoomMemberHelper
|
||||
import io.realm.Realm
|
||||
import javax.inject.Inject
|
||||
|
||||
internal interface RoomGetter {
|
||||
|
@ -38,18 +37,18 @@ internal interface RoomGetter {
|
|||
|
||||
@SessionScope
|
||||
internal class DefaultRoomGetter @Inject constructor(
|
||||
@SessionDatabase private val monarchy: Monarchy,
|
||||
private val realmSessionProvider: RealmSessionProvider,
|
||||
private val roomFactory: RoomFactory
|
||||
) : RoomGetter {
|
||||
|
||||
override fun getRoom(roomId: String): Room? {
|
||||
return Realm.getInstance(monarchy.realmConfiguration).use { realm ->
|
||||
return realmSessionProvider.withRealm { realm ->
|
||||
createRoom(realm, roomId)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getDirectRoomWith(otherUserId: String): Room? {
|
||||
return Realm.getInstance(monarchy.realmConfiguration).use { realm ->
|
||||
return realmSessionProvider.withRealm { realm ->
|
||||
RoomSummaryEntity.where(realm)
|
||||
.equalTo(RoomSummaryEntityFields.IS_DIRECT, true)
|
||||
.equalTo(RoomSummaryEntityFields.MEMBERSHIP_STR, Membership.JOIN.name)
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
package org.matrix.android.sdk.internal.session.room.send
|
||||
|
||||
import com.zhuinden.monarchy.Monarchy
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.matrix.android.sdk.api.session.events.model.Content
|
||||
import org.matrix.android.sdk.api.session.events.model.Event
|
||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||
|
@ -27,6 +28,7 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageType
|
|||
import org.matrix.android.sdk.api.session.room.send.SendState
|
||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||
import org.matrix.android.sdk.internal.crypto.MXEventDecryptionResult
|
||||
import org.matrix.android.sdk.internal.database.RealmSessionProvider
|
||||
import org.matrix.android.sdk.internal.database.helper.nextId
|
||||
import org.matrix.android.sdk.internal.database.mapper.ContentMapper
|
||||
import org.matrix.android.sdk.internal.database.mapper.TimelineEventMapper
|
||||
|
@ -43,12 +45,11 @@ import org.matrix.android.sdk.internal.session.room.membership.RoomMemberHelper
|
|||
import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryUpdater
|
||||
import org.matrix.android.sdk.internal.session.room.timeline.DefaultTimeline
|
||||
import org.matrix.android.sdk.internal.util.awaitTransaction
|
||||
import io.realm.Realm
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class LocalEchoRepository @Inject constructor(@SessionDatabase private val monarchy: Monarchy,
|
||||
private val realmSessionProvider: RealmSessionProvider,
|
||||
private val roomSummaryUpdater: RoomSummaryUpdater,
|
||||
private val eventBus: EventBus,
|
||||
private val timelineEventMapper: TimelineEventMapper) {
|
||||
|
@ -59,7 +60,7 @@ internal class LocalEchoRepository @Inject constructor(@SessionDatabase private
|
|||
if (event.eventId == null) {
|
||||
throw IllegalStateException("You should have set an eventId for your event")
|
||||
}
|
||||
val timelineEventEntity = Realm.getInstance(monarchy.realmConfiguration).use { realm ->
|
||||
val timelineEventEntity = realmSessionProvider.withRealm { realm ->
|
||||
val eventEntity = event.toEntity(roomId, SendState.UNSENT, System.currentTimeMillis())
|
||||
val roomMemberHelper = RoomMemberHelper(realm, roomId)
|
||||
val myUser = roomMemberHelper.getLastRoomMember(senderId)
|
||||
|
@ -150,7 +151,7 @@ internal class LocalEchoRepository @Inject constructor(@SessionDatabase private
|
|||
}
|
||||
|
||||
fun getAllEventsWithStates(roomId: String, states : List<SendState>): List<TimelineEvent> {
|
||||
return Realm.getInstance(monarchy.realmConfiguration).use { realm ->
|
||||
return realmSessionProvider.withRealm { realm ->
|
||||
TimelineEventEntity
|
||||
.findAllInRoomWithSendStates(realm, roomId, states)
|
||||
.sortedByDescending { it.displayIndex }
|
||||
|
|
|
@ -20,24 +20,26 @@ package org.matrix.android.sdk.internal.session.room.state
|
|||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.Transformations
|
||||
import com.zhuinden.monarchy.Monarchy
|
||||
import io.realm.Realm
|
||||
import io.realm.RealmQuery
|
||||
import io.realm.kotlin.where
|
||||
import org.matrix.android.sdk.api.query.QueryStringValue
|
||||
import org.matrix.android.sdk.api.session.events.model.Event
|
||||
import org.matrix.android.sdk.api.util.Optional
|
||||
import org.matrix.android.sdk.api.util.toOptional
|
||||
import org.matrix.android.sdk.internal.database.RealmSessionProvider
|
||||
import org.matrix.android.sdk.internal.database.mapper.asDomain
|
||||
import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntity
|
||||
import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntityFields
|
||||
import org.matrix.android.sdk.internal.di.SessionDatabase
|
||||
import org.matrix.android.sdk.internal.query.process
|
||||
import io.realm.Realm
|
||||
import io.realm.RealmQuery
|
||||
import io.realm.kotlin.where
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class StateEventDataSource @Inject constructor(@SessionDatabase private val monarchy: Monarchy) {
|
||||
internal class StateEventDataSource @Inject constructor(@SessionDatabase private val monarchy: Monarchy,
|
||||
private val realmSessionProvider: RealmSessionProvider) {
|
||||
|
||||
fun getStateEvent(roomId: String, eventType: String, stateKey: QueryStringValue): Event? {
|
||||
return Realm.getInstance(monarchy.realmConfiguration).use { realm ->
|
||||
return realmSessionProvider.withRealm { realm ->
|
||||
buildStateEventQuery(realm, roomId, setOf(eventType), stateKey).findFirst()?.root?.asDomain()
|
||||
}
|
||||
}
|
||||
|
@ -53,7 +55,7 @@ internal class StateEventDataSource @Inject constructor(@SessionDatabase private
|
|||
}
|
||||
|
||||
fun getStateEvents(roomId: String, eventTypes: Set<String>, stateKey: QueryStringValue): List<Event> {
|
||||
return Realm.getInstance(monarchy.realmConfiguration).use { realm ->
|
||||
return realmSessionProvider.withRealm { realm ->
|
||||
buildStateEventQuery(realm, roomId, eventTypes, stateKey)
|
||||
.findAll()
|
||||
.mapNotNull {
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* 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.summary
|
||||
|
||||
import io.realm.Realm
|
||||
import org.matrix.android.sdk.api.session.room.summary.RoomSummaryConstants
|
||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineEventFilters
|
||||
import org.matrix.android.sdk.internal.database.model.TimelineEventEntity
|
||||
import org.matrix.android.sdk.internal.database.query.latestEvent
|
||||
|
||||
internal object RoomSummaryEventsHelper {
|
||||
|
||||
private val previewFilters = TimelineEventFilters(
|
||||
filterTypes = true,
|
||||
allowedTypes = RoomSummaryConstants.PREVIEWABLE_TYPES,
|
||||
filterUseless = true,
|
||||
filterRedacted = false,
|
||||
filterEdits = true
|
||||
)
|
||||
|
||||
fun getLatestPreviewableEvent(realm: Realm, roomId: String): TimelineEventEntity? {
|
||||
return TimelineEventEntity.latestEvent(
|
||||
realm = realm,
|
||||
roomId = roomId,
|
||||
includesSending = true,
|
||||
filters = previewFilters
|
||||
)
|
||||
}
|
||||
}
|
|
@ -18,6 +18,8 @@
|
|||
package org.matrix.android.sdk.internal.session.room.summary
|
||||
|
||||
import dagger.Lazy
|
||||
import io.realm.Realm
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel
|
||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||
import org.matrix.android.sdk.api.session.events.model.toModel
|
||||
|
@ -40,7 +42,6 @@ import org.matrix.android.sdk.internal.database.query.findAllInRoomWithSendState
|
|||
import org.matrix.android.sdk.internal.database.query.getOrCreate
|
||||
import org.matrix.android.sdk.internal.database.query.getOrNull
|
||||
import org.matrix.android.sdk.internal.database.query.isEventRead
|
||||
import org.matrix.android.sdk.internal.database.query.latestEvent
|
||||
import org.matrix.android.sdk.internal.database.query.whereType
|
||||
import org.matrix.android.sdk.internal.di.UserId
|
||||
import org.matrix.android.sdk.internal.session.room.RoomAvatarResolver
|
||||
|
@ -49,8 +50,6 @@ import org.matrix.android.sdk.internal.session.room.membership.RoomMemberHelper
|
|||
import org.matrix.android.sdk.internal.session.room.timeline.TimelineEventDecryptor
|
||||
import org.matrix.android.sdk.internal.session.sync.model.RoomSyncSummary
|
||||
import org.matrix.android.sdk.internal.session.sync.model.RoomSyncUnreadNotifications
|
||||
import io.realm.Realm
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
|
@ -61,28 +60,6 @@ internal class RoomSummaryUpdater @Inject constructor(
|
|||
private val timelineEventDecryptor: Lazy<TimelineEventDecryptor>,
|
||||
private val eventBus: EventBus) {
|
||||
|
||||
companion object {
|
||||
// TODO: maybe allow user of SDK to give that list
|
||||
val PREVIEWABLE_TYPES = listOf(
|
||||
// TODO filter message type (KEY_VERIFICATION_READY, etc.)
|
||||
EventType.MESSAGE,
|
||||
EventType.STATE_ROOM_NAME,
|
||||
EventType.STATE_ROOM_TOPIC,
|
||||
EventType.STATE_ROOM_AVATAR,
|
||||
EventType.STATE_ROOM_MEMBER,
|
||||
EventType.STATE_ROOM_HISTORY_VISIBILITY,
|
||||
EventType.CALL_INVITE,
|
||||
EventType.CALL_HANGUP,
|
||||
EventType.CALL_ANSWER,
|
||||
EventType.ENCRYPTED,
|
||||
EventType.STATE_ROOM_ENCRYPTION,
|
||||
EventType.STATE_ROOM_THIRD_PARTY_INVITE,
|
||||
EventType.STICKER,
|
||||
EventType.REACTION,
|
||||
EventType.STATE_ROOM_CREATE
|
||||
)
|
||||
}
|
||||
|
||||
fun update(realm: Realm,
|
||||
roomId: String,
|
||||
membership: Membership? = null,
|
||||
|
@ -110,9 +87,6 @@ internal class RoomSummaryUpdater @Inject constructor(
|
|||
roomSummaryEntity.membership = membership
|
||||
}
|
||||
|
||||
val latestPreviewableEvent = TimelineEventEntity.latestEvent(realm, roomId, includesSending = true,
|
||||
filterTypes = PREVIEWABLE_TYPES, filterContentRelation = true)
|
||||
|
||||
val lastNameEvent = CurrentStateEventEntity.getOrNull(realm, roomId, type = EventType.STATE_ROOM_NAME, stateKey = "")?.root
|
||||
val lastTopicEvent = CurrentStateEventEntity.getOrNull(realm, roomId, type = EventType.STATE_ROOM_TOPIC, stateKey = "")?.root
|
||||
val lastCanonicalAliasEvent = CurrentStateEventEntity.getOrNull(realm, roomId, type = EventType.STATE_ROOM_CANONICAL_ALIAS, stateKey = "")?.root
|
||||
|
@ -123,6 +97,8 @@ internal class RoomSummaryUpdater @Inject constructor(
|
|||
.contains(EventEntityFields.CONTENT, "\"algorithm\":\"$MXCRYPTO_ALGORITHM_MEGOLM\"")
|
||||
.findFirst()
|
||||
|
||||
val latestPreviewableEvent = RoomSummaryEventsHelper.getLatestPreviewableEvent(realm, roomId)
|
||||
|
||||
roomSummaryEntity.hasUnreadMessages = roomSummaryEntity.notificationCount > 0
|
||||
// avoid this call if we are sure there are unread events
|
||||
|| !isEventRead(realm.configuration, userId, roomId, latestPreviewableEvent?.eventId)
|
||||
|
@ -178,8 +154,7 @@ internal class RoomSummaryUpdater @Inject constructor(
|
|||
fun updateSendingInformation(realm: Realm, roomId: String) {
|
||||
val roomSummaryEntity = RoomSummaryEntity.getOrCreate(realm, roomId)
|
||||
roomSummaryEntity.updateHasFailedSending()
|
||||
roomSummaryEntity.latestPreviewableEvent = TimelineEventEntity.latestEvent(realm, roomId, includesSending = true,
|
||||
filterTypes = PREVIEWABLE_TYPES, filterContentRelation = true)
|
||||
roomSummaryEntity.latestPreviewableEvent = RoomSummaryEventsHelper.getLatestPreviewableEvent(realm, roomId)
|
||||
}
|
||||
|
||||
fun updateShieldTrust(realm: Realm,
|
||||
|
|
|
@ -39,13 +39,14 @@ import org.matrix.android.sdk.api.session.room.timeline.Timeline
|
|||
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings
|
||||
import org.matrix.android.sdk.api.util.CancelableBag
|
||||
import org.matrix.android.sdk.internal.database.RealmSessionProvider
|
||||
import org.matrix.android.sdk.internal.database.mapper.TimelineEventMapper
|
||||
import org.matrix.android.sdk.internal.database.model.ChunkEntity
|
||||
import org.matrix.android.sdk.internal.database.model.ChunkEntityFields
|
||||
import org.matrix.android.sdk.internal.database.model.RoomEntity
|
||||
import org.matrix.android.sdk.internal.database.model.TimelineEventEntity
|
||||
import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields
|
||||
import org.matrix.android.sdk.internal.database.query.TimelineEventFilter
|
||||
import org.matrix.android.sdk.internal.database.query.filterEvents
|
||||
import org.matrix.android.sdk.internal.database.query.findAllInRoomWithSendStates
|
||||
import org.matrix.android.sdk.internal.database.query.where
|
||||
import org.matrix.android.sdk.internal.database.query.whereRoomId
|
||||
|
@ -76,7 +77,8 @@ internal class DefaultTimeline(
|
|||
private val settings: TimelineSettings,
|
||||
private val hiddenReadReceipts: TimelineHiddenReadReceipts,
|
||||
private val eventBus: EventBus,
|
||||
private val eventDecryptor: TimelineEventDecryptor
|
||||
private val eventDecryptor: TimelineEventDecryptor,
|
||||
private val realmSessionProvider: RealmSessionProvider
|
||||
) : Timeline, TimelineHiddenReadReceipts.Delegate {
|
||||
|
||||
data class OnNewTimelineEvents(val roomId: String, val eventIds: List<String>)
|
||||
|
@ -136,13 +138,13 @@ internal class DefaultTimeline(
|
|||
}
|
||||
|
||||
override fun pendingEventCount(): Int {
|
||||
return Realm.getInstance(realmConfiguration).use {
|
||||
return realmSessionProvider.withRealm {
|
||||
RoomEntity.where(it, roomId).findFirst()?.sendingTimelineEvents?.count() ?: 0
|
||||
}
|
||||
}
|
||||
|
||||
override fun failedToDeliverEventCount(): Int {
|
||||
return Realm.getInstance(realmConfiguration).use {
|
||||
return realmSessionProvider.withRealm {
|
||||
TimelineEventEntity.findAllInRoomWithSendStates(it, roomId, SendState.HAS_FAILED_STATES).count()
|
||||
}
|
||||
}
|
||||
|
@ -182,7 +184,7 @@ internal class DefaultTimeline(
|
|||
}
|
||||
|
||||
private fun TimelineSettings.shouldHandleHiddenReadReceipts(): Boolean {
|
||||
return buildReadReceipts && (filterEdits || filterTypes)
|
||||
return buildReadReceipts && (filters.filterEdits || filters.filterTypes)
|
||||
}
|
||||
|
||||
override fun dispose() {
|
||||
|
@ -239,7 +241,7 @@ internal class DefaultTimeline(
|
|||
return eventId
|
||||
}
|
||||
// Otherwise, we should check if the event is in the db, but is hidden because of filters
|
||||
return Realm.getInstance(realmConfiguration).use { localRealm ->
|
||||
return realmSessionProvider.withRealm { localRealm ->
|
||||
val nonFilteredEvents = buildEventQuery(localRealm)
|
||||
.sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.DESCENDING)
|
||||
.findAll()
|
||||
|
@ -757,29 +759,15 @@ internal class DefaultTimeline(
|
|||
}
|
||||
|
||||
private fun RealmQuery<TimelineEventEntity>.filterEventsWithSettings(): RealmQuery<TimelineEventEntity> {
|
||||
if (settings.filterTypes) {
|
||||
`in`(TimelineEventEntityFields.ROOT.TYPE, settings.allowedTypes.toTypedArray())
|
||||
}
|
||||
if (settings.filterUseless) {
|
||||
not()
|
||||
.equalTo(TimelineEventEntityFields.ROOT.IS_USELESS, true)
|
||||
}
|
||||
if (settings.filterEdits) {
|
||||
not().like(TimelineEventEntityFields.ROOT.CONTENT, TimelineEventFilter.Content.EDIT)
|
||||
not().like(TimelineEventEntityFields.ROOT.CONTENT, TimelineEventFilter.Content.RESPONSE)
|
||||
}
|
||||
if (settings.filterRedacted) {
|
||||
not().like(TimelineEventEntityFields.ROOT.UNSIGNED_DATA, TimelineEventFilter.Unsigned.REDACTED)
|
||||
}
|
||||
return this
|
||||
return filterEvents(settings.filters)
|
||||
}
|
||||
|
||||
private fun List<TimelineEvent>.filterEventsWithSettings(): List<TimelineEvent> {
|
||||
return filter {
|
||||
val filterType = !settings.filterTypes || settings.allowedTypes.contains(it.root.type)
|
||||
val filterType = !settings.filters.filterTypes || settings.filters.allowedTypes.contains(it.root.type)
|
||||
if (!filterType) return@filter false
|
||||
|
||||
val filterEdits = if (settings.filterEdits && it.root.type == EventType.MESSAGE) {
|
||||
val filterEdits = if (settings.filters.filterEdits && it.root.type == EventType.MESSAGE) {
|
||||
val messageContent = it.root.content.toModel<MessageContent>()
|
||||
messageContent?.relatesTo?.type != RelationType.REPLACE && messageContent?.relatesTo?.type != RelationType.RESPONSE
|
||||
} else {
|
||||
|
@ -787,7 +775,7 @@ internal class DefaultTimeline(
|
|||
}
|
||||
if (!filterEdits) return@filter false
|
||||
|
||||
val filterRedacted = !settings.filterRedacted || it.root.isRedacted()
|
||||
val filterRedacted = !settings.filters.filterRedacted || it.root.isRedacted()
|
||||
|
||||
filterRedacted
|
||||
}
|
||||
|
|
|
@ -22,6 +22,9 @@ import androidx.lifecycle.Transformations
|
|||
import com.squareup.inject.assisted.Assisted
|
||||
import com.squareup.inject.assisted.AssistedInject
|
||||
import com.zhuinden.monarchy.Monarchy
|
||||
import io.realm.Sort
|
||||
import io.realm.kotlin.where
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.matrix.android.sdk.api.session.events.model.isImageMessage
|
||||
import org.matrix.android.sdk.api.session.events.model.isVideoMessage
|
||||
import org.matrix.android.sdk.api.session.room.timeline.Timeline
|
||||
|
@ -30,7 +33,7 @@ import org.matrix.android.sdk.api.session.room.timeline.TimelineService
|
|||
import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings
|
||||
import org.matrix.android.sdk.api.util.Optional
|
||||
import org.matrix.android.sdk.api.util.toOptional
|
||||
import org.matrix.android.sdk.internal.crypto.store.db.doWithRealm
|
||||
import org.matrix.android.sdk.internal.database.RealmSessionProvider
|
||||
import org.matrix.android.sdk.internal.database.mapper.ReadReceiptsSummaryMapper
|
||||
import org.matrix.android.sdk.internal.database.mapper.TimelineEventMapper
|
||||
import org.matrix.android.sdk.internal.database.model.TimelineEventEntity
|
||||
|
@ -38,13 +41,10 @@ import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields
|
|||
import org.matrix.android.sdk.internal.database.query.where
|
||||
import org.matrix.android.sdk.internal.di.SessionDatabase
|
||||
import org.matrix.android.sdk.internal.task.TaskExecutor
|
||||
import org.matrix.android.sdk.internal.util.fetchCopyMap
|
||||
import io.realm.Sort
|
||||
import io.realm.kotlin.where
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
|
||||
internal class DefaultTimelineService @AssistedInject constructor(@Assisted private val roomId: String,
|
||||
@SessionDatabase private val monarchy: Monarchy,
|
||||
private val realmSessionProvider: RealmSessionProvider,
|
||||
private val eventBus: EventBus,
|
||||
private val taskExecutor: TaskExecutor,
|
||||
private val contextOfEventTask: GetContextOfEventTask,
|
||||
|
@ -73,17 +73,17 @@ internal class DefaultTimelineService @AssistedInject constructor(@Assisted priv
|
|||
hiddenReadReceipts = TimelineHiddenReadReceipts(readReceiptsSummaryMapper, roomId, settings),
|
||||
eventBus = eventBus,
|
||||
eventDecryptor = eventDecryptor,
|
||||
fetchTokenAndPaginateTask = fetchTokenAndPaginateTask
|
||||
fetchTokenAndPaginateTask = fetchTokenAndPaginateTask,
|
||||
realmSessionProvider = realmSessionProvider
|
||||
)
|
||||
}
|
||||
|
||||
override fun getTimeLineEvent(eventId: String): TimelineEvent? {
|
||||
return monarchy
|
||||
.fetchCopyMap({
|
||||
TimelineEventEntity.where(it, roomId = roomId, eventId = eventId).findFirst()
|
||||
}, { entity, _ ->
|
||||
timelineEventMapper.map(entity)
|
||||
})
|
||||
return realmSessionProvider.withRealm { realm ->
|
||||
TimelineEventEntity.where(realm, roomId = roomId, eventId = eventId).findFirst()?.let {
|
||||
timelineEventMapper.map(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getTimeLineEventLive(eventId: String): LiveData<Optional<TimelineEvent>> {
|
||||
|
@ -98,7 +98,7 @@ internal class DefaultTimelineService @AssistedInject constructor(@Assisted priv
|
|||
|
||||
override fun getAttachmentMessages(): List<TimelineEvent> {
|
||||
// TODO pretty bad query.. maybe we should denormalize clear type in base?
|
||||
return doWithRealm(monarchy.realmConfiguration) { realm ->
|
||||
return realmSessionProvider.withRealm { realm ->
|
||||
realm.where<TimelineEventEntity>()
|
||||
.equalTo(TimelineEventEntityFields.ROOM_ID, roomId)
|
||||
.sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.ASCENDING)
|
||||
|
|
|
@ -18,6 +18,10 @@
|
|||
package org.matrix.android.sdk.internal.session.room.timeline
|
||||
|
||||
import android.util.SparseArray
|
||||
import io.realm.OrderedRealmCollectionChangeListener
|
||||
import io.realm.Realm
|
||||
import io.realm.RealmQuery
|
||||
import io.realm.RealmResults
|
||||
import org.matrix.android.sdk.api.session.room.model.ReadReceipt
|
||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings
|
||||
import org.matrix.android.sdk.internal.database.mapper.ReadReceiptsSummaryMapper
|
||||
|
@ -27,10 +31,6 @@ import org.matrix.android.sdk.internal.database.model.TimelineEventEntity
|
|||
import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields
|
||||
import org.matrix.android.sdk.internal.database.query.TimelineEventFilter
|
||||
import org.matrix.android.sdk.internal.database.query.whereInRoom
|
||||
import io.realm.OrderedRealmCollectionChangeListener
|
||||
import io.realm.Realm
|
||||
import io.realm.RealmQuery
|
||||
import io.realm.RealmResults
|
||||
|
||||
/**
|
||||
* This class is responsible for handling the read receipts for hidden events (check [TimelineSettings] to see filtering).
|
||||
|
@ -151,23 +151,24 @@ internal class TimelineHiddenReadReceipts constructor(private val readReceiptsSu
|
|||
private fun RealmQuery<ReadReceiptsSummaryEntity>.filterReceiptsWithSettings(): RealmQuery<ReadReceiptsSummaryEntity> {
|
||||
beginGroup()
|
||||
var needOr = false
|
||||
if (settings.filterTypes) {
|
||||
not().`in`("${ReadReceiptsSummaryEntityFields.TIMELINE_EVENT}.${TimelineEventEntityFields.ROOT.TYPE}", settings.allowedTypes.toTypedArray())
|
||||
if (settings.filters.filterTypes) {
|
||||
val allowedTypes = settings.filters.allowedTypes.toTypedArray()
|
||||
not().`in`("${ReadReceiptsSummaryEntityFields.TIMELINE_EVENT}.${TimelineEventEntityFields.ROOT.TYPE}", allowedTypes)
|
||||
needOr = true
|
||||
}
|
||||
if (settings.filterUseless) {
|
||||
if (settings.filters.filterUseless) {
|
||||
if (needOr) or()
|
||||
equalTo("${ReadReceiptsSummaryEntityFields.TIMELINE_EVENT}.${TimelineEventEntityFields.ROOT.IS_USELESS}", true)
|
||||
needOr = true
|
||||
}
|
||||
if (settings.filterEdits) {
|
||||
if (settings.filters.filterEdits) {
|
||||
if (needOr) or()
|
||||
like("${ReadReceiptsSummaryEntityFields.TIMELINE_EVENT}.${TimelineEventEntityFields.ROOT.CONTENT}", TimelineEventFilter.Content.EDIT)
|
||||
or()
|
||||
like("${ReadReceiptsSummaryEntityFields.TIMELINE_EVENT}.${TimelineEventEntityFields.ROOT.CONTENT}", TimelineEventFilter.Content.RESPONSE)
|
||||
needOr = true
|
||||
}
|
||||
if (settings.filterRedacted) {
|
||||
if (settings.filters.filterRedacted) {
|
||||
if (needOr) or()
|
||||
like("${ReadReceiptsSummaryEntityFields.TIMELINE_EVENT}.${TimelineEventEntityFields.ROOT.UNSIGNED_DATA}", TimelineEventFilter.Unsigned.REDACTED)
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
package org.matrix.android.sdk.internal.session.room.timeline
|
||||
|
||||
import com.zhuinden.monarchy.Monarchy
|
||||
import io.realm.Realm
|
||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||
import org.matrix.android.sdk.api.session.events.model.toModel
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomMemberContent
|
||||
|
@ -32,19 +33,16 @@ import org.matrix.android.sdk.internal.database.model.ChunkEntity
|
|||
import org.matrix.android.sdk.internal.database.model.EventInsertType
|
||||
import org.matrix.android.sdk.internal.database.model.RoomEntity
|
||||
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity
|
||||
import org.matrix.android.sdk.internal.database.model.TimelineEventEntity
|
||||
import org.matrix.android.sdk.internal.database.query.copyToRealmOrIgnore
|
||||
import org.matrix.android.sdk.internal.database.query.create
|
||||
import org.matrix.android.sdk.internal.database.query.find
|
||||
import org.matrix.android.sdk.internal.database.query.findAllIncludingEvents
|
||||
import org.matrix.android.sdk.internal.database.query.findLastForwardChunkOfRoom
|
||||
import org.matrix.android.sdk.internal.database.query.getOrCreate
|
||||
import org.matrix.android.sdk.internal.database.query.latestEvent
|
||||
import org.matrix.android.sdk.internal.database.query.where
|
||||
import org.matrix.android.sdk.internal.di.SessionDatabase
|
||||
import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryUpdater
|
||||
import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryEventsHelper
|
||||
import org.matrix.android.sdk.internal.util.awaitTransaction
|
||||
import io.realm.Realm
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
|
@ -177,12 +175,7 @@ internal class TokenChunkEventPersistor @Inject constructor(@SessionDatabase pri
|
|||
currentChunk.isLastForward = true
|
||||
currentLastForwardChunk?.deleteOnCascade()
|
||||
RoomSummaryEntity.where(realm, roomId).findFirst()?.apply {
|
||||
latestPreviewableEvent = TimelineEventEntity.latestEvent(
|
||||
realm,
|
||||
roomId,
|
||||
includesSending = true,
|
||||
filterTypes = RoomSummaryUpdater.PREVIEWABLE_TYPES
|
||||
)
|
||||
latestPreviewableEvent = RoomSummaryEventsHelper.getLatestPreviewableEvent(realm, roomId)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -249,13 +242,7 @@ internal class TokenChunkEventPersistor @Inject constructor(@SessionDatabase pri
|
|||
val shouldUpdateSummary = roomSummaryEntity.latestPreviewableEvent == null
|
||||
|| (chunksToDelete.isNotEmpty() && currentChunk.isLastForward && direction == PaginationDirection.FORWARDS)
|
||||
if (shouldUpdateSummary) {
|
||||
val latestPreviewableEvent = TimelineEventEntity.latestEvent(
|
||||
realm,
|
||||
roomId,
|
||||
includesSending = true,
|
||||
filterTypes = RoomSummaryUpdater.PREVIEWABLE_TYPES
|
||||
)
|
||||
roomSummaryEntity.latestPreviewableEvent = latestPreviewableEvent
|
||||
roomSummaryEntity.latestPreviewableEvent = RoomSummaryEventsHelper.getLatestPreviewableEvent(realm, roomId)
|
||||
}
|
||||
if (currentChunk.isValid) {
|
||||
RoomEntity.where(realm, roomId).findFirst()?.addOrUpdate(currentChunk)
|
||||
|
|
|
@ -32,7 +32,7 @@ import javax.inject.Inject
|
|||
|
||||
internal interface SyncTask : Task<SyncTask.Params, Unit> {
|
||||
|
||||
data class Params(var timeout: Long = 30_000L)
|
||||
data class Params(var timeout: Long = 6_000L)
|
||||
}
|
||||
|
||||
internal class DefaultSyncTask @Inject constructor(
|
||||
|
|
|
@ -19,7 +19,14 @@ package org.matrix.android.sdk.internal.session.sync.job
|
|||
import android.app.Service
|
||||
import android.content.Intent
|
||||
import android.os.IBinder
|
||||
import android.os.PowerManager
|
||||
import androidx.core.content.getSystemService
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.cancelChildren
|
||||
import kotlinx.coroutines.launch
|
||||
import org.matrix.android.sdk.api.Matrix
|
||||
import org.matrix.android.sdk.api.failure.Failure
|
||||
import org.matrix.android.sdk.api.failure.isTokenError
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import org.matrix.android.sdk.api.session.sync.SyncState
|
||||
|
@ -28,10 +35,6 @@ import org.matrix.android.sdk.internal.session.sync.SyncTask
|
|||
import org.matrix.android.sdk.internal.task.TaskExecutor
|
||||
import org.matrix.android.sdk.internal.util.BackgroundDetectionObserver
|
||||
import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.cancelChildren
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
|
||||
|
@ -46,6 +49,11 @@ abstract class SyncService : Service() {
|
|||
private var sessionId: String? = null
|
||||
private var mIsSelfDestroyed: Boolean = false
|
||||
|
||||
private var syncTimeoutSeconds: Int = 6
|
||||
private var syncDelaySeconds: Int = 60
|
||||
private var periodic: Boolean = false
|
||||
private var preventReschedule: Boolean = false
|
||||
|
||||
private var isInitialSync: Boolean = false
|
||||
private lateinit var session: Session
|
||||
private lateinit var syncTask: SyncTask
|
||||
|
@ -59,27 +67,60 @@ abstract class SyncService : Service() {
|
|||
private val serviceScope = CoroutineScope(SupervisorJob())
|
||||
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
Timber.i("onStartCommand $intent")
|
||||
val isInit = initialize(intent)
|
||||
if (isInit) {
|
||||
onStart(isInitialSync)
|
||||
doSyncIfNotAlreadyRunning()
|
||||
} else {
|
||||
// We should start and stop as we have to ensure to call Service.startForeground()
|
||||
onStart(isInitialSync)
|
||||
stopMe()
|
||||
Timber.i("## Sync: onStartCommand [$this] $intent with action: ${intent?.action}")
|
||||
|
||||
// We should start we have to ensure we fulfill contract to show notification
|
||||
// for foreground service (as per design for this service)
|
||||
// TODO can we check if it's really in foreground
|
||||
onStart(isInitialSync)
|
||||
when (intent?.action) {
|
||||
ACTION_STOP -> {
|
||||
Timber.i("## Sync: stop command received")
|
||||
// If it was periodic we ensure that it will not reschedule itself
|
||||
preventReschedule = true
|
||||
// we don't want to cancel initial syncs, let it finish
|
||||
if (!isInitialSync) {
|
||||
stopMe()
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
val isInit = initialize(intent)
|
||||
if (isInit) {
|
||||
periodic = intent?.getBooleanExtra(EXTRA_PERIODIC, false) ?: false
|
||||
val onNetworkBack = intent?.getBooleanExtra(EXTRA_NETWORK_BACK_RESTART, false) ?: false
|
||||
Timber.d("## Sync: command received, periodic: $periodic networkBack: $onNetworkBack")
|
||||
if (onNetworkBack && !backgroundDetectionObserver.isInBackground) {
|
||||
// the restart after network occurs while the app is in foreground
|
||||
// so just stop. It will be restarted when entering background
|
||||
preventReschedule = true
|
||||
stopMe()
|
||||
} else {
|
||||
// default is syncing
|
||||
doSyncIfNotAlreadyRunning()
|
||||
}
|
||||
} else {
|
||||
Timber.d("## Sync: Failed to initialize service")
|
||||
stopMe()
|
||||
}
|
||||
}
|
||||
}
|
||||
// No intent just start the service, an alarm will should call with intent
|
||||
return START_STICKY
|
||||
|
||||
// It's ok to be not sticky because we will explicitly start it again on the next alarm?
|
||||
return START_NOT_STICKY
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
Timber.i("## onDestroy() : $this")
|
||||
Timber.i("## Sync: onDestroy() [$this] periodic:$periodic preventReschedule:$preventReschedule")
|
||||
if (!mIsSelfDestroyed) {
|
||||
Timber.w("## Destroy by the system : $this")
|
||||
Timber.d("## Sync: Destroy by the system : $this")
|
||||
}
|
||||
serviceScope.coroutineContext.cancelChildren()
|
||||
isRunning.set(false)
|
||||
// Cancelling the context will trigger the catch close the doSync try
|
||||
serviceScope.coroutineContext.cancelChildren()
|
||||
if (!preventReschedule && periodic && sessionId != null && backgroundDetectionObserver.isInBackground) {
|
||||
Timber.d("## Sync: Reschedule service in $syncDelaySeconds sec")
|
||||
onRescheduleAsked(sessionId ?: "", false, syncTimeoutSeconds, syncDelaySeconds)
|
||||
}
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
|
@ -90,9 +131,15 @@ abstract class SyncService : Service() {
|
|||
|
||||
private fun doSyncIfNotAlreadyRunning() {
|
||||
if (isRunning.get()) {
|
||||
Timber.i("Received a start while was already syncing... ignore")
|
||||
Timber.i("## Sync: Received a start while was already syncing... ignore")
|
||||
} else {
|
||||
isRunning.set(true)
|
||||
// Acquire a lock to give enough time for the sync :/
|
||||
getSystemService<PowerManager>()?.run {
|
||||
newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "riotx:fdroidSynclock").apply {
|
||||
acquire((syncTimeoutSeconds * 1000L + 10_000L))
|
||||
}
|
||||
}
|
||||
serviceScope.launch(coroutineDispatchers.io) {
|
||||
doSync()
|
||||
}
|
||||
|
@ -100,9 +147,10 @@ abstract class SyncService : Service() {
|
|||
}
|
||||
|
||||
private suspend fun doSync() {
|
||||
Timber.v("Execute sync request with timeout 0")
|
||||
val params = SyncTask.Params(TIME_OUT)
|
||||
Timber.v("## Sync: Execute sync request with timeout $syncTimeoutSeconds seconds")
|
||||
val params = SyncTask.Params(syncTimeoutSeconds * 1000L)
|
||||
try {
|
||||
// never do that in foreground, let the syncThread work
|
||||
syncTask.execute(params)
|
||||
// Start sync if we were doing an initial sync and the syncThread is not launched yet
|
||||
if (isInitialSync && session.getSyncState() == SyncState.Idle) {
|
||||
|
@ -111,28 +159,34 @@ abstract class SyncService : Service() {
|
|||
}
|
||||
stopMe()
|
||||
} catch (throwable: Throwable) {
|
||||
Timber.e(throwable)
|
||||
Timber.e(throwable, "## Sync: sync service did fail ${isRunning.get()}")
|
||||
if (throwable.isTokenError()) {
|
||||
stopMe()
|
||||
} else {
|
||||
Timber.v("Should be rescheduled to avoid wasting resources")
|
||||
sessionId?.also {
|
||||
onRescheduleAsked(it, isInitialSync, delay = 10_000L)
|
||||
}
|
||||
stopMe()
|
||||
// no need to retry
|
||||
preventReschedule = true
|
||||
}
|
||||
if (throwable is Failure.NetworkConnection) {
|
||||
// Network is off, no need to reschedule endless alarms :/
|
||||
preventReschedule = true
|
||||
// Instead start a work to restart background sync when network is back
|
||||
onNetworkError(sessionId ?: "", isInitialSync, syncTimeoutSeconds, syncDelaySeconds)
|
||||
}
|
||||
// JobCancellation could be caught here when onDestroy cancels the coroutine context
|
||||
if (isRunning.get()) stopMe()
|
||||
}
|
||||
}
|
||||
|
||||
private fun initialize(intent: Intent?): Boolean {
|
||||
if (intent == null) {
|
||||
Timber.d("## Sync: initialize intent is null")
|
||||
return false
|
||||
}
|
||||
val matrix = Matrix.getInstance(applicationContext)
|
||||
val safeSessionId = intent.getStringExtra(EXTRA_SESSION_ID) ?: return false
|
||||
syncTimeoutSeconds = intent.getIntExtra(EXTRA_TIMEOUT_SECONDS, 6)
|
||||
syncDelaySeconds = intent.getIntExtra(EXTRA_DELAY_SECONDS, 60)
|
||||
try {
|
||||
val sessionComponent = matrix.sessionManager.getSessionComponent(safeSessionId)
|
||||
?: throw IllegalStateException("You should have a session to make it work")
|
||||
?: throw IllegalStateException("## Sync: You should have a session to make it work")
|
||||
session = sessionComponent.session()
|
||||
sessionId = safeSessionId
|
||||
syncTask = sessionComponent.syncTask()
|
||||
|
@ -143,14 +197,16 @@ abstract class SyncService : Service() {
|
|||
backgroundDetectionObserver = matrix.backgroundDetectionObserver
|
||||
return true
|
||||
} catch (exception: Exception) {
|
||||
Timber.e(exception, "An exception occurred during initialisation")
|
||||
Timber.e(exception, "## Sync: An exception occurred during initialisation")
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
abstract fun onStart(isInitialSync: Boolean)
|
||||
|
||||
abstract fun onRescheduleAsked(sessionId: String, isInitialSync: Boolean, delay: Long)
|
||||
abstract fun onRescheduleAsked(sessionId: String, isInitialSync: Boolean, timeout: Int, delay: Int)
|
||||
|
||||
abstract fun onNetworkError(sessionId: String, isInitialSync: Boolean, timeout: Int, delay: Int)
|
||||
|
||||
override fun onBind(intent: Intent?): IBinder? {
|
||||
return null
|
||||
|
@ -158,6 +214,11 @@ abstract class SyncService : Service() {
|
|||
|
||||
companion object {
|
||||
const val EXTRA_SESSION_ID = "EXTRA_SESSION_ID"
|
||||
private const val TIME_OUT = 0L
|
||||
const val EXTRA_TIMEOUT_SECONDS = "EXTRA_TIMEOUT_SECONDS"
|
||||
const val EXTRA_DELAY_SECONDS = "EXTRA_DELAY_SECONDS"
|
||||
const val EXTRA_PERIODIC = "EXTRA_PERIODIC"
|
||||
const val EXTRA_NETWORK_BACK_RESTART = "EXTRA_NETWORK_BACK_RESTART"
|
||||
|
||||
const val ACTION_STOP = "ACTION_STOP"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,7 +34,8 @@ import timber.log.Timber
|
|||
import java.util.concurrent.TimeUnit
|
||||
import javax.inject.Inject
|
||||
|
||||
private const val DEFAULT_LONG_POOL_TIMEOUT = 0L
|
||||
private const val DEFAULT_LONG_POOL_TIMEOUT = 6L
|
||||
private const val DEFAULT_DELAY_TIMEOUT = 30_000L
|
||||
|
||||
/**
|
||||
* Possible previous worker: None
|
||||
|
@ -48,13 +49,15 @@ internal class SyncWorker(context: Context,
|
|||
internal data class Params(
|
||||
override val sessionId: String,
|
||||
val timeout: Long = DEFAULT_LONG_POOL_TIMEOUT,
|
||||
val automaticallyRetry: Boolean = false,
|
||||
val delay: Long = DEFAULT_DELAY_TIMEOUT,
|
||||
val periodic: Boolean = false,
|
||||
override val lastFailureMessage: String? = null
|
||||
) : SessionWorkerParams
|
||||
|
||||
@Inject lateinit var syncTask: SyncTask
|
||||
@Inject lateinit var taskExecutor: TaskExecutor
|
||||
@Inject lateinit var networkConnectivityChecker: NetworkConnectivityChecker
|
||||
@Inject lateinit var workManagerProvider: WorkManagerProvider
|
||||
|
||||
override suspend fun doWork(): Result {
|
||||
Timber.i("Sync work starting")
|
||||
|
@ -67,11 +70,21 @@ internal class SyncWorker(context: Context,
|
|||
return runCatching {
|
||||
doSync(params.timeout)
|
||||
}.fold(
|
||||
{ Result.success() },
|
||||
{
|
||||
Result.success().also {
|
||||
if (params.periodic) {
|
||||
// we want to schedule another one after delay
|
||||
automaticallyBackgroundSync(workManagerProvider, params.sessionId, params.timeout, params.delay)
|
||||
}
|
||||
}
|
||||
},
|
||||
{ failure ->
|
||||
if (failure.isTokenError() || !params.automaticallyRetry) {
|
||||
if (failure.isTokenError()) {
|
||||
Result.failure()
|
||||
} else {
|
||||
// If the worker was stopped (when going back in foreground), a JobCancellation exception is sent
|
||||
// but in this case the result is ignored, as the work is considered stopped,
|
||||
// so don't worry of the retry here for this case
|
||||
Result.retry()
|
||||
}
|
||||
}
|
||||
|
@ -79,7 +92,7 @@ internal class SyncWorker(context: Context,
|
|||
}
|
||||
|
||||
private suspend fun doSync(timeout: Long) {
|
||||
val taskParams = SyncTask.Params(timeout)
|
||||
val taskParams = SyncTask.Params(timeout * 1000)
|
||||
syncTask.execute(taskParams)
|
||||
}
|
||||
|
||||
|
@ -87,25 +100,27 @@ internal class SyncWorker(context: Context,
|
|||
private const val BG_SYNC_WORK_NAME = "BG_SYNCP"
|
||||
|
||||
fun requireBackgroundSync(workManagerProvider: WorkManagerProvider, sessionId: String, serverTimeout: Long = 0) {
|
||||
val data = WorkerParamsFactory.toData(Params(sessionId, serverTimeout, false))
|
||||
val data = WorkerParamsFactory.toData(Params(sessionId, serverTimeout, 0L, false))
|
||||
val workRequest = workManagerProvider.matrixOneTimeWorkRequestBuilder<SyncWorker>()
|
||||
.setConstraints(WorkManagerProvider.workConstraints)
|
||||
.setBackoffCriteria(BackoffPolicy.LINEAR, 1_000, TimeUnit.MILLISECONDS)
|
||||
.setInputData(data)
|
||||
.build()
|
||||
workManagerProvider.workManager
|
||||
.enqueueUniqueWork(BG_SYNC_WORK_NAME, ExistingWorkPolicy.REPLACE, workRequest)
|
||||
.enqueueUniqueWork(BG_SYNC_WORK_NAME, ExistingWorkPolicy.APPEND_OR_REPLACE, workRequest)
|
||||
}
|
||||
|
||||
fun automaticallyBackgroundSync(workManagerProvider: WorkManagerProvider, sessionId: String, serverTimeout: Long = 0, delay: Long = 30_000) {
|
||||
val data = WorkerParamsFactory.toData(Params(sessionId, serverTimeout, true))
|
||||
fun automaticallyBackgroundSync(workManagerProvider: WorkManagerProvider, sessionId: String, serverTimeout: Long = 0, delayInSeconds: Long = 30) {
|
||||
val data = WorkerParamsFactory.toData(Params(sessionId, serverTimeout, delayInSeconds, true))
|
||||
val workRequest = workManagerProvider.matrixOneTimeWorkRequestBuilder<SyncWorker>()
|
||||
.setConstraints(WorkManagerProvider.workConstraints)
|
||||
.setInputData(data)
|
||||
.setBackoffCriteria(BackoffPolicy.LINEAR, delay, TimeUnit.MILLISECONDS)
|
||||
.setBackoffCriteria(BackoffPolicy.LINEAR, 1_000, TimeUnit.MILLISECONDS)
|
||||
.setInitialDelay(delayInSeconds, TimeUnit.SECONDS)
|
||||
.build()
|
||||
|
||||
workManagerProvider.workManager
|
||||
.enqueueUniqueWork(BG_SYNC_WORK_NAME, ExistingWorkPolicy.REPLACE, workRequest)
|
||||
.enqueueUniqueWork(BG_SYNC_WORK_NAME, ExistingWorkPolicy.APPEND_OR_REPLACE, workRequest)
|
||||
}
|
||||
|
||||
fun stopAnyBackgroundSync(workManagerProvider: WorkManagerProvider) {
|
||||
|
|
|
@ -23,9 +23,11 @@ import androidx.paging.DataSource
|
|||
import androidx.paging.LivePagedListBuilder
|
||||
import androidx.paging.PagedList
|
||||
import com.zhuinden.monarchy.Monarchy
|
||||
import io.realm.Case
|
||||
import org.matrix.android.sdk.api.session.user.model.User
|
||||
import org.matrix.android.sdk.api.util.Optional
|
||||
import org.matrix.android.sdk.api.util.toOptional
|
||||
import org.matrix.android.sdk.internal.database.RealmSessionProvider
|
||||
import org.matrix.android.sdk.internal.database.mapper.asDomain
|
||||
import org.matrix.android.sdk.internal.database.model.IgnoredUserEntity
|
||||
import org.matrix.android.sdk.internal.database.model.IgnoredUserEntityFields
|
||||
|
@ -33,11 +35,10 @@ import org.matrix.android.sdk.internal.database.model.UserEntity
|
|||
import org.matrix.android.sdk.internal.database.model.UserEntityFields
|
||||
import org.matrix.android.sdk.internal.database.query.where
|
||||
import org.matrix.android.sdk.internal.di.SessionDatabase
|
||||
import org.matrix.android.sdk.internal.util.fetchCopied
|
||||
import io.realm.Case
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class UserDataSource @Inject constructor(@SessionDatabase private val monarchy: Monarchy) {
|
||||
internal class UserDataSource @Inject constructor(@SessionDatabase private val monarchy: Monarchy,
|
||||
private val realmSessionProvider: RealmSessionProvider) {
|
||||
|
||||
private val realmDataSourceFactory: Monarchy.RealmDataSourceFactory<UserEntity> by lazy {
|
||||
monarchy.createDataSourceFactory { realm ->
|
||||
|
@ -58,10 +59,10 @@ internal class UserDataSource @Inject constructor(@SessionDatabase private val m
|
|||
}
|
||||
|
||||
fun getUser(userId: String): User? {
|
||||
val userEntity = monarchy.fetchCopied { UserEntity.where(it, userId).findFirst() }
|
||||
?: return null
|
||||
|
||||
return userEntity.asDomain()
|
||||
return realmSessionProvider.withRealm {
|
||||
val userEntity = UserEntity.where(it, userId).findFirst()
|
||||
userEntity?.asDomain()
|
||||
}
|
||||
}
|
||||
|
||||
fun getUserLive(userId: String): LiveData<Optional<User>> {
|
||||
|
|
|
@ -20,18 +20,20 @@ package org.matrix.android.sdk.internal.session.user.accountdata
|
|||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.Transformations
|
||||
import com.zhuinden.monarchy.Monarchy
|
||||
import io.realm.Realm
|
||||
import io.realm.RealmQuery
|
||||
import org.matrix.android.sdk.api.session.accountdata.UserAccountDataEvent
|
||||
import org.matrix.android.sdk.api.util.Optional
|
||||
import org.matrix.android.sdk.api.util.toOptional
|
||||
import org.matrix.android.sdk.internal.database.RealmSessionProvider
|
||||
import org.matrix.android.sdk.internal.database.mapper.AccountDataMapper
|
||||
import org.matrix.android.sdk.internal.database.model.UserAccountDataEntity
|
||||
import org.matrix.android.sdk.internal.database.model.UserAccountDataEntityFields
|
||||
import org.matrix.android.sdk.internal.di.SessionDatabase
|
||||
import org.matrix.android.sdk.api.session.accountdata.UserAccountDataEvent
|
||||
import io.realm.Realm
|
||||
import io.realm.RealmQuery
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class AccountDataDataSource @Inject constructor(@SessionDatabase private val monarchy: Monarchy,
|
||||
private val realmSessionProvider: RealmSessionProvider,
|
||||
private val accountDataMapper: AccountDataMapper) {
|
||||
|
||||
fun getAccountDataEvent(type: String): UserAccountDataEvent? {
|
||||
|
@ -45,10 +47,9 @@ internal class AccountDataDataSource @Inject constructor(@SessionDatabase privat
|
|||
}
|
||||
|
||||
fun getAccountDataEvents(types: Set<String>): List<UserAccountDataEvent> {
|
||||
return monarchy.fetchAllMappedSync(
|
||||
{ accountDataEventsQuery(it, types) },
|
||||
accountDataMapper::map
|
||||
)
|
||||
return realmSessionProvider.withRealm {
|
||||
accountDataEventsQuery(it, types).findAll().map(accountDataMapper::map)
|
||||
}
|
||||
}
|
||||
|
||||
fun getLiveAccountDataEvents(types: Set<String>): LiveData<List<UserAccountDataEvent>> {
|
||||
|
|
|
@ -23,17 +23,15 @@ import org.matrix.android.sdk.api.session.room.sender.SenderInfo
|
|||
import org.matrix.android.sdk.api.session.widgets.model.Widget
|
||||
import org.matrix.android.sdk.api.session.widgets.model.WidgetContent
|
||||
import org.matrix.android.sdk.api.session.widgets.model.WidgetType
|
||||
import org.matrix.android.sdk.internal.di.SessionDatabase
|
||||
import org.matrix.android.sdk.internal.database.RealmSessionProvider
|
||||
import org.matrix.android.sdk.internal.di.UserId
|
||||
import org.matrix.android.sdk.internal.session.room.membership.RoomMemberHelper
|
||||
import org.matrix.android.sdk.internal.session.user.UserDataSource
|
||||
import io.realm.Realm
|
||||
import io.realm.RealmConfiguration
|
||||
import java.net.URLEncoder
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class WidgetFactory @Inject constructor(@SessionDatabase private val realmConfiguration: RealmConfiguration,
|
||||
private val userDataSource: UserDataSource,
|
||||
internal class WidgetFactory @Inject constructor(private val userDataSource: UserDataSource,
|
||||
private val realmSessionProvider: RealmSessionProvider,
|
||||
@UserId private val userId: String) {
|
||||
|
||||
fun create(widgetEvent: Event): Widget? {
|
||||
|
@ -44,7 +42,7 @@ internal class WidgetFactory @Inject constructor(@SessionDatabase private val re
|
|||
val senderInfo = if (widgetEvent.senderId == null || widgetEvent.roomId == null) {
|
||||
null
|
||||
} else {
|
||||
Realm.getInstance(realmConfiguration).use {
|
||||
realmSessionProvider.withRealm {
|
||||
val roomMemberHelper = RoomMemberHelper(it, widgetEvent.roomId)
|
||||
val roomMemberSummaryEntity = roomMemberHelper.getLastRoomMember(widgetEvent.senderId)
|
||||
SenderInfo(
|
||||
|
|
|
@ -6,21 +6,21 @@
|
|||
<string name="notice_room_invite_no_invitee">دعوة من %s</string>
|
||||
<string name="notice_room_invite">دعى %1$s %2$s</string>
|
||||
<string name="notice_room_invite_you">دعاك %1$s</string>
|
||||
<string name="notice_room_join">انضمّ %1$s</string>
|
||||
<string name="notice_room_leave">غادر %1$s</string>
|
||||
<string name="notice_room_join">انضمّ %1$s إلى الغرفة</string>
|
||||
<string name="notice_room_leave">غادر %1$s الغرفة</string>
|
||||
<string name="notice_room_reject">رفض %1$s الدعوة</string>
|
||||
<string name="notice_room_kick">طرد %1$s %2$s</string>
|
||||
<string name="notice_room_unban">رفع %1$s الحظر عن %2$s</string>
|
||||
<string name="notice_room_unban">رفع %1$s المنع عن %2$s</string>
|
||||
<string name="notice_room_ban">منع %1$s %2$s</string>
|
||||
<string name="notice_avatar_url_changed">غيّر %1$s صورته</string>
|
||||
<string name="notice_display_name_set">ضبط %1$s اسم العرض على %2$s</string>
|
||||
<string name="notice_display_name_changed_from">غيّر %1$s اسم الحساب المعروض من %2$s إلى %3$s</string>
|
||||
<string name="notice_display_name_removed">أزال %1$s اسم الحساب المعروض (%2$s)</string>
|
||||
<string name="notice_display_name_changed_from">غيّر %1$s اسم العرض من %2$s إلى %3$s</string>
|
||||
<string name="notice_display_name_removed">أزال %1$s اسم العرض (كان %2$s)</string>
|
||||
<string name="notice_room_topic_changed">غيّر %1$s الموضوع إلى: %2$s</string>
|
||||
<string name="notice_room_name_changed">غيّر %1$s اسم الغرفة إلى: %2$s</string>
|
||||
<string name="notice_answered_call">ردّ %s على المكالمة.</string>
|
||||
<string name="notice_ended_call">أنهى %s المكالمة.</string>
|
||||
<string name="notice_made_future_room_visibility">جعل %1$s تأريخ الغرفة مستقبلًا ظاهرا على %2$s</string>
|
||||
<string name="notice_made_future_room_visibility">جعل %1$s تأريخ الغرفة مستقبلًا ظاهرًا على %2$s</string>
|
||||
<string name="notice_room_visibility_invited">كل أعضاء الغرفة من لحظة دعوتهم.</string>
|
||||
<string name="notice_room_visibility_joined">كل أعضاء الغرفة من لحظة انضمامهم.</string>
|
||||
<string name="notice_room_visibility_shared">كل أعضاء الغرفة.</string>
|
||||
|
@ -46,7 +46,7 @@
|
|||
<string name="network_error">خطأ في الشبكة</string>
|
||||
<string name="matrix_error">خطأ في «ماترِكس»</string>
|
||||
|
||||
<string name="room_error_join_failed_empty_room">ليس ممكنا الانضمام ثانيةً إلى غرفة فارغة.</string>
|
||||
<string name="room_error_join_failed_empty_room">لا يمكنك حاليًا الانضمام ثانيةً إلى غرفة فارغة.</string>
|
||||
|
||||
<string name="encrypted_message">رسالة معمّاة</string>
|
||||
|
||||
|
@ -54,13 +54,13 @@
|
|||
<string name="medium_phone_number">رقم الهاتف</string>
|
||||
|
||||
<string name="summary_message">%1$s: %2$s</string>
|
||||
<string name="notice_room_withdraw">انسحب %1$s من الدعوة %2$s</string>
|
||||
<string name="notice_room_withdraw">انسحب %1$s من دعوة %2$s</string>
|
||||
<string name="notice_placed_video_call">أجرى %s مكالمة مرئية.</string>
|
||||
<string name="notice_placed_voice_call">أجرى %s مكالمة صوتية.</string>
|
||||
<string name="notice_room_third_party_registered_invite">قبل %1$s دعوة %2$s</string>
|
||||
<string name="notice_room_third_party_registered_invite">قَبِل %1$s دعوة %2$s</string>
|
||||
|
||||
<string name="could_not_redact">تعذر التهذيب</string>
|
||||
<string name="summary_user_sent_sticker">أرسل %1$s ملصقا.</string>
|
||||
<string name="summary_user_sent_sticker">أرسل %1$s ملصقًا.</string>
|
||||
|
||||
<string name="notice_avatar_changed_too">(تغيّرت الصورة أيضا)</string>
|
||||
|
||||
|
@ -71,12 +71,77 @@
|
|||
<string name="room_displayname_room_invite">دعوة إلى غرفة</string>
|
||||
|
||||
<plurals name="room_displayname_three_and_more_members">
|
||||
<item quantity="zero">صفر</item>
|
||||
<item quantity="one">واحد</item>
|
||||
<item quantity="two">اثنان</item>
|
||||
<item quantity="few">قليل</item>
|
||||
<item quantity="many">كثير</item>
|
||||
<item quantity="other">اخرى</item>
|
||||
<item quantity="zero"></item>
|
||||
<item quantity="one"></item>
|
||||
<item quantity="two"></item>
|
||||
<item quantity="few"></item>
|
||||
<item quantity="many"></item>
|
||||
<item quantity="other"></item>
|
||||
</plurals>
|
||||
|
||||
<string name="summary_you_sent_image">أرسلت صورة.</string>
|
||||
<string name="summary_you_sent_sticker">أرسلت ملصقًا.</string>
|
||||
|
||||
<string name="notice_room_invite_no_invitee_by_you">دعوة منك أنت</string>
|
||||
<string name="notice_room_created">أنشأ %1$s الغرفة</string>
|
||||
<string name="notice_room_created_by_you">أنشأت الغرفة</string>
|
||||
<string name="notice_room_invite_by_you">دعوت %1$s</string>
|
||||
<string name="notice_room_join_by_you">انضممت إلى الغرفة</string>
|
||||
<string name="notice_room_leave_by_you">غادرت الغرفة</string>
|
||||
<string name="notice_room_reject_by_you">رفضت الدعوة</string>
|
||||
<string name="notice_room_kick_by_you">طردت %1$s</string>
|
||||
<string name="notice_room_unban_by_you">رفعت المنع عن %1$s</string>
|
||||
<string name="notice_room_ban_by_you">منعت %1$s</string>
|
||||
<string name="notice_room_withdraw_by_you">انسحبت من دعوة %1$s</string>
|
||||
<string name="notice_avatar_url_changed_by_you">غيّرت صورتك</string>
|
||||
<string name="notice_display_name_set_by_you">ضبطت اسم العرض على %1$s</string>
|
||||
<string name="notice_display_name_changed_from_by_you">غيّرت اسم العرض من %1$s إلى %2$s</string>
|
||||
<string name="notice_display_name_removed_by_you">أزلت اسم العرض (كان %1$s)</string>
|
||||
<string name="notice_room_topic_changed_by_you">غيّرت الموضوع إلى: %1$s</string>
|
||||
<string name="notice_room_avatar_changed">غيّر %1$s صورة الغرفة</string>
|
||||
<string name="notice_room_avatar_changed_by_you">غيّرت صورة الغرفة</string>
|
||||
<string name="notice_room_name_changed_by_you">غيّرت اسم الغرفة إلى: %1$s</string>
|
||||
<string name="notice_placed_video_call_by_you">أجريت مكالمة مرئية.</string>
|
||||
<string name="notice_placed_voice_call_by_you">أجريت مكالمة صوتية.</string>
|
||||
<string name="notice_call_candidates">أرسل %s البيانات لإعداد المكالمة.</string>
|
||||
<string name="notice_call_candidates_by_you">أرسلت البيانات لإعداد المكالمة.</string>
|
||||
<string name="notice_answered_call_by_you">رددت على المكالمة.</string>
|
||||
<string name="notice_ended_call_by_you">أنهيت المكالمة.</string>
|
||||
<string name="notice_made_future_room_visibility_by_you">جعلت تأريخ الغرفة مستقبلًا ظاهرًا على %1$s</string>
|
||||
<string name="notice_end_to_end_by_you">فعّلت تعمية الطرفين (%1$s)</string>
|
||||
<string name="notice_room_update">رقّى %s هذه الغرفة.</string>
|
||||
<string name="notice_room_update_by_you">رقّيت هذه الغرفة.</string>
|
||||
|
||||
<string name="notice_requested_voip_conference_by_you">طلبت اجتماع VoIP</string>
|
||||
<string name="notice_room_name_removed_by_you">أزلت اسم الغرفة</string>
|
||||
<string name="notice_room_topic_removed_by_you">أزلت موضوع الغرفة</string>
|
||||
<string name="notice_room_avatar_removed">أزال %1$s صورة الغرفة</string>
|
||||
<string name="notice_room_avatar_removed_by_you">أزلت صورة الغرفة</string>
|
||||
<string name="notice_event_redacted">أُزيلت الرسالة</string>
|
||||
<string name="notice_event_redacted_by">أزال %1$s الرسالة</string>
|
||||
<string name="notice_event_redacted_with_reason">أُزيلت الرسالة [السبب: %1$s]</string>
|
||||
<string name="notice_event_redacted_by_with_reason">أزال %1$s الرسالة [السبب: %2$s]</string>
|
||||
<string name="notice_room_third_party_invite_by_you">أرسلت دعوة إلى %1$s للانضمام إلى الغرفة</string>
|
||||
<string name="notice_room_third_party_revoked_invite">سحب %1$s دعوة %2$s للانضمام إلى الغرفة</string>
|
||||
<string name="notice_room_third_party_revoked_invite_by_you">سحبت دعوة %1$s للانضمام إلى الغرفة</string>
|
||||
<string name="notice_room_third_party_registered_invite_by_you">قَبِلت دعوة %1$s</string>
|
||||
|
||||
<string name="notice_widget_added">أضاف %1$s الودجة %2$s</string>
|
||||
<string name="notice_widget_added_by_you">أضفت الودجة %1$s</string>
|
||||
<string name="notice_widget_removed">أزال %1$s الودجة %2$s</string>
|
||||
<string name="notice_widget_removed_by_you">أزلت الودجة %1$s</string>
|
||||
<string name="notice_widget_modified">عدّل %1$s الودجة %2$s</string>
|
||||
<string name="notice_widget_modified_by_you">عدّلت الودجة %1$s</string>
|
||||
|
||||
<string name="power_level_admin">مدير</string>
|
||||
<string name="power_level_default">المبدئي</string>
|
||||
<string name="power_level_custom">مخصّص (%1$d)</string>
|
||||
<string name="power_level_custom_no_value">مخصّص</string>
|
||||
|
||||
<string name="notice_power_level_changed_by_you">غيّرت مستوى قوّة %1$s.</string>
|
||||
<string name="notice_power_level_changed">غيّر %1$s مستوى قوّة %2$s.</string>
|
||||
<string name="notice_power_level_diff">%1$s من %2$s إلى %3$s</string>
|
||||
|
||||
<string name="initial_sync_start_importing_account">المزامنة الأولية:
|
||||
\nيستورد الحساب…</string>
|
||||
</resources>
|
||||
|
|
|
@ -147,4 +147,98 @@
|
|||
|
||||
<string name="key_verification_request_fallback_message">%s solicita verificar su clave, pero su cliente no soporta la verificación de la clave en chat. Necesitará usar la verificación de claves clásica para poder verificar las claves.</string>
|
||||
|
||||
<string name="summary_you_sent_image">Enviaste una imagen.</string>
|
||||
<string name="summary_you_sent_sticker">Enviaste un sticker.</string>
|
||||
|
||||
<string name="notice_room_invite_no_invitee_by_you">Tu invitación</string>
|
||||
<string name="notice_room_created">%1$s creó la habitación</string>
|
||||
<string name="notice_room_created_by_you">Tu creaste la habitación</string>
|
||||
<string name="notice_room_invite_by_you">Invitaste a %1$s</string>
|
||||
<string name="notice_room_join_by_you">Te uniste a la Sala</string>
|
||||
<string name="notice_room_leave_by_you">Dejaste la Sala</string>
|
||||
<string name="notice_room_reject_by_you">Rechazaste la invitación</string>
|
||||
<string name="notice_room_kick_by_you">Tu pateaste a %1$s</string>
|
||||
<string name="notice_room_unban_by_you">Tu desbanaste a %1$s</string>
|
||||
<string name="notice_room_ban_by_you">Usted prohibió a %1$s</string>
|
||||
<string name="notice_room_withdraw_by_you">Retiró la invitación de %1$s\'s</string>
|
||||
<string name="notice_avatar_url_changed_by_you">Cambiaste tu avatar</string>
|
||||
<string name="notice_display_name_set_by_you">Establece su nombre de visualización en %1$s</string>
|
||||
<string name="notice_display_name_changed_from_by_you">Cambiaste tu nombre para mostrar de %1$s a %2$s</string>
|
||||
<string name="notice_display_name_removed_by_you">Quitaste tu nombre para mostrar (era %1$s)</string>
|
||||
<string name="notice_room_topic_changed_by_you">Cambiaste el tema a: %1$s</string>
|
||||
<string name="notice_room_avatar_changed">%1$s cambió el avatar de la sala</string>
|
||||
<string name="notice_room_avatar_changed_by_you">Cambiaste el avatar de la habitación</string>
|
||||
<string name="notice_room_name_changed_by_you">Cambiaste el nombre de la habitación a: %1$s</string>
|
||||
<string name="notice_placed_video_call_by_you">Hiciste una videollamada.</string>
|
||||
<string name="notice_placed_voice_call_by_you">Hiciste una llamada de voz.</string>
|
||||
<string name="notice_call_candidates">%s envió datos para configurar la llamada.</string>
|
||||
<string name="notice_call_candidates_by_you">Enviaste datos para configurar la llamada.</string>
|
||||
<string name="notice_answered_call_by_you">Respondiste la llamada.</string>
|
||||
<string name="notice_ended_call_by_you">Terminaste la llamada.</string>
|
||||
<string name="notice_made_future_room_visibility_by_you">Hiciste visible el futuro historial de la %1$s</string>
|
||||
<string name="notice_end_to_end_by_you">Activó el cifrado de un extremo a otro (%1$s)</string>
|
||||
<string name="notice_room_update_by_you">Has mejorado esta habitación.</string>
|
||||
|
||||
<string name="notice_requested_voip_conference_by_you">Solicitaste una conferencia de VoIP</string>
|
||||
<string name="notice_room_name_removed_by_you">Quitaste el nombre de la sala</string>
|
||||
<string name="notice_room_topic_removed_by_you">Quitaste el tema de la sala</string>
|
||||
<string name="notice_room_avatar_removed">%1$s eliminó el avatar de la habitación</string>
|
||||
<string name="notice_room_avatar_removed_by_you">Quitaste el avatar de la habitación</string>
|
||||
<string name="notice_profile_change_redacted_by_you">Actualizaste tu perfil %1$s</string>
|
||||
<string name="notice_room_third_party_invite_by_you">Enviaste una invitación a %1$s para unirse a la sala</string>
|
||||
<string name="notice_room_third_party_revoked_invite_by_you">Revocaste la invitación para que %1$s se una a la sala</string>
|
||||
<string name="notice_room_third_party_registered_invite_by_you">Aceptaste la invitación para %1$s</string>
|
||||
|
||||
<string name="notice_widget_added">%1$s agrego el widget %2$s</string>
|
||||
<string name="notice_widget_added_by_you">Agregaste el widget %1$s</string>
|
||||
<string name="notice_widget_removed">%1$s eliminó el widget %2$s</string>
|
||||
<string name="notice_widget_removed_by_you">Quitaste el widget %1$s</string>
|
||||
<string name="notice_widget_modified">%1$s modifico el widget %2$s</string>
|
||||
<string name="notice_widget_modified_by_you">Modificaste el widget %1$s</string>
|
||||
|
||||
<string name="power_level_admin">Administrador</string>
|
||||
<string name="power_level_moderator">Moderador</string>
|
||||
<string name="power_level_default">Por defecto</string>
|
||||
<string name="power_level_custom">Personalizado (%1$d)</string>
|
||||
<string name="power_level_custom_no_value">Personalizado</string>
|
||||
|
||||
<string name="notice_power_level_changed_by_you">Cambiaste el nivel de potencia de %1$s.</string>
|
||||
<string name="notice_power_level_changed">%1$s cambió el nivel de potencia de %2$s.</string>
|
||||
<string name="notice_power_level_diff">%1$s de %2$s a %3$s</string>
|
||||
|
||||
<string name="notice_room_invite_no_invitee_with_reason_by_you">Tu invitación. Razón: %1$s</string>
|
||||
<string name="notice_room_invite_with_reason_by_you">"nvitaste a %1$s. Razón: %2$s"</string>
|
||||
<string name="notice_room_join_with_reason_by_you">Te uniste a la habitación. Razón: %1$s</string>
|
||||
<string name="notice_room_leave_with_reason_by_you">Dejaste la habitación. Razón: %1$s</string>
|
||||
<string name="notice_room_reject_with_reason_by_you">Rechazaste la invitación. Razón: %1$s</string>
|
||||
<string name="notice_room_kick_with_reason_by_you">Pateaste a %1$s. Motivo: %2$s</string>
|
||||
<string name="notice_room_unban_with_reason_by_you">Has desactivado a %1$s. Motivo: %2$s</string>
|
||||
<string name="notice_room_ban_with_reason_by_you">Prohibiste a %1$s. Motivo: %2$s</string>
|
||||
<string name="notice_room_third_party_invite_with_reason_by_you">Enviaste una invitación a %1$s para unirse a la sala. Motivo: %2$s</string>
|
||||
<string name="notice_room_third_party_revoked_invite_with_reason_by_you">Revocaste la invitación para que %1$s se una a la sala. Motivo: %2$s</string>
|
||||
<string name="notice_room_third_party_registered_invite_with_reason_by_you">Aceptaste la invitación para %1$s. Motivo: %2$s</string>
|
||||
<string name="notice_room_withdraw_with_reason_by_you">Retiró la invitación de %1$s\'s. Motivo: %2$s</string>
|
||||
|
||||
<plurals name="notice_room_aliases_added_by_you">
|
||||
<item quantity="one">Agregaste %1$s como dirección para esta sala.</item>
|
||||
<item quantity="other">Agregaste %1$s como direcciones para esta sala.</item>
|
||||
</plurals>
|
||||
|
||||
<plurals name="notice_room_aliases_removed_by_you">
|
||||
<item quantity="one">Quitaste %1$s como dirección para esta sala.</item>
|
||||
<item quantity="other">Quitaste %2$s como direcciones para esta sala.</item>
|
||||
</plurals>
|
||||
|
||||
<string name="notice_room_aliases_added_and_removed">"%1$s agregó %2$s y eliminó %3$s como direcciones para esta sala."</string>
|
||||
<string name="notice_room_aliases_added_and_removed_by_you">Agregaste %1$s y quitaste %2$s como direcciones para esta sala.</string>
|
||||
|
||||
<string name="notice_room_canonical_alias_set_by_you">Estableciste la dirección principal de esta sala en %1$s.</string>
|
||||
<string name="notice_room_canonical_alias_unset_by_you">Quitaste la dirección principal de esta sala.</string>
|
||||
|
||||
<string name="notice_room_guest_access_can_join_by_you">Ha permitido que los invitados se unan a la sala.</string>
|
||||
<string name="notice_room_guest_access_forbidden_by_you">Ha impedido que los invitados se unan a la sala.</string>
|
||||
|
||||
<string name="notice_end_to_end_ok_by_you">Activó el cifrado de extremo a extremo.</string>
|
||||
<string name="notice_end_to_end_unknown_algorithm_by_you">Activó el cifrado de un extremo a otro (algoritmo %1$s no reconocido).</string>
|
||||
|
||||
</resources>
|
||||
|
|
|
@ -151,7 +151,7 @@ android\.app\.AlertDialog
|
|||
new Gson\(\)
|
||||
|
||||
### Use matrixOneTimeWorkRequestBuilder
|
||||
import androidx.work.OneTimeWorkRequestBuilder===1
|
||||
import androidx.work.OneTimeWorkRequestBuilder===2
|
||||
|
||||
### Use TextUtils.formatFileSize
|
||||
Formatter\.formatFileSize===1
|
||||
|
@ -164,7 +164,7 @@ Formatter\.formatShortFileSize===1
|
|||
# android\.text\.TextUtils
|
||||
|
||||
### This is not a rule, but a warning: the number of "enum class" has changed. For Json classes, it is mandatory that they have `@JsonClass(generateAdapter = false)`. If it is ok, change the value in file forbidden_strings_in_code.txt
|
||||
enum class===76
|
||||
enum class===78
|
||||
|
||||
### Do not import temporary legacy classes
|
||||
import org.matrix.android.sdk.internal.legacy.riot===3
|
||||
|
@ -172,3 +172,6 @@ import org.matrix.androidsdk.crypto.data===2
|
|||
|
||||
### Use `Context#getSystemService` extension function provided by `core-ktx`
|
||||
getSystemService\(Context
|
||||
|
||||
### Use DefaultSharedPreferences.getInstance() instead for better performance
|
||||
PreferenceManager\.getDefaultSharedPreferences==2
|
||||
|
|
|
@ -17,7 +17,7 @@ androidExtensions {
|
|||
// Note: 2 digits max for each value
|
||||
ext.versionMajor = 1
|
||||
ext.versionMinor = 0
|
||||
ext.versionPatch = 6
|
||||
ext.versionPatch = 7
|
||||
|
||||
static def getGitTimestamp() {
|
||||
def cmd = 'git show -s --format=%ct'
|
||||
|
@ -190,6 +190,8 @@ android {
|
|||
|
||||
resValue "bool", "debug_mode", "true"
|
||||
buildConfigField "boolean", "LOW_PRIVACY_LOG_ENABLE", "false"
|
||||
// Set to true if you want to enable strict mode in debug
|
||||
buildConfigField "boolean", "ENABLE_STRICT_MODE_LOGS", "false"
|
||||
|
||||
signingConfig signingConfigs.debug
|
||||
}
|
||||
|
@ -199,6 +201,7 @@ android {
|
|||
|
||||
resValue "bool", "debug_mode", "false"
|
||||
buildConfigField "boolean", "LOW_PRIVACY_LOG_ENABLE", "false"
|
||||
buildConfigField "boolean", "ENABLE_STRICT_MODE_LOGS", "false"
|
||||
|
||||
postprocessing {
|
||||
removeUnusedCode true
|
||||
|
|
|
@ -22,7 +22,7 @@ import android.content.Intent
|
|||
import android.content.IntentFilter
|
||||
import android.content.SharedPreferences
|
||||
import androidx.core.content.edit
|
||||
import androidx.preference.PreferenceManager
|
||||
import im.vector.app.core.di.DefaultSharedPreferences
|
||||
import im.vector.app.core.utils.lsFiles
|
||||
import timber.log.Timber
|
||||
|
||||
|
@ -44,7 +44,7 @@ class DebugReceiver : BroadcastReceiver() {
|
|||
}
|
||||
|
||||
private fun dumpPreferences(context: Context) {
|
||||
logPrefs("DefaultSharedPreferences", PreferenceManager.getDefaultSharedPreferences(context))
|
||||
logPrefs("DefaultSharedPreferences", DefaultSharedPreferences.getInstance(context))
|
||||
}
|
||||
|
||||
private fun logPrefs(name: String, sharedPreferences: SharedPreferences?) {
|
||||
|
@ -58,7 +58,7 @@ class DebugReceiver : BroadcastReceiver() {
|
|||
}
|
||||
|
||||
private fun alterScalarToken(context: Context) {
|
||||
PreferenceManager.getDefaultSharedPreferences(context).edit {
|
||||
DefaultSharedPreferences.getInstance(context).edit {
|
||||
// putString("SCALAR_TOKEN_PREFERENCE_KEY" + Matrix.getInstance(context).defaultSession.myUserId, "bad_token")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,11 @@
|
|||
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
<!--
|
||||
Required for long polling account synchronisation in background.
|
||||
If not present ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS intent action won't work
|
||||
-->
|
||||
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
|
||||
|
||||
<application>
|
||||
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* 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 im.vector.app.fdroid
|
||||
|
||||
import android.content.Context
|
||||
import im.vector.app.core.di.ActiveSessionHolder
|
||||
import im.vector.app.fdroid.receiver.AlarmSyncBroadcastReceiver
|
||||
import im.vector.app.features.settings.BackgroundSyncMode
|
||||
import im.vector.app.features.settings.VectorPreferences
|
||||
import timber.log.Timber
|
||||
|
||||
object BackgroundSyncStarter {
|
||||
fun start(context: Context, vectorPreferences: VectorPreferences, activeSessionHolder: ActiveSessionHolder) {
|
||||
if (vectorPreferences.areNotificationEnabledForDevice()) {
|
||||
val activeSession = activeSessionHolder.getSafeActiveSession() ?: return
|
||||
|
||||
when (vectorPreferences.getFdroidSyncBackgroundMode()) {
|
||||
BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_FOR_BATTERY -> {
|
||||
// we rely on periodic worker
|
||||
Timber.i("## Sync: Work scheduled to periodically sync in ${vectorPreferences.backgroundSyncDelay()}s")
|
||||
activeSession.startAutomaticBackgroundSync(
|
||||
vectorPreferences.backgroundSyncTimeOut().toLong(),
|
||||
vectorPreferences.backgroundSyncDelay().toLong()
|
||||
)
|
||||
}
|
||||
BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_FOR_REALTIME -> {
|
||||
// We need to use alarm in this mode
|
||||
AlarmSyncBroadcastReceiver.scheduleAlarm(context, activeSession.sessionId, vectorPreferences.backgroundSyncDelay())
|
||||
Timber.i("## Sync: Alarm scheduled to start syncing")
|
||||
}
|
||||
BackgroundSyncMode.FDROID_BACKGROUND_SYNC_MODE_DISABLED -> {
|
||||
// we do nothing
|
||||
Timber.i("## Sync: background sync is disabled")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -15,29 +15,30 @@
|
|||
*/
|
||||
package im.vector.app.fdroid.features.settings.troubleshoot
|
||||
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.resources.StringProvider
|
||||
import im.vector.app.core.utils.isIgnoringBatteryOptimizations
|
||||
import im.vector.app.core.utils.requestDisablingBatteryOptimization
|
||||
import im.vector.app.features.settings.troubleshoot.NotificationTroubleshootTestManager
|
||||
import im.vector.app.features.settings.troubleshoot.TroubleshootTest
|
||||
import javax.inject.Inject
|
||||
|
||||
// Not used anymore
|
||||
class TestBatteryOptimization(val fragment: Fragment) : TroubleshootTest(R.string.settings_troubleshoot_test_battery_title) {
|
||||
class TestBatteryOptimization @Inject constructor(
|
||||
private val context: AppCompatActivity,
|
||||
private val stringProvider: StringProvider
|
||||
) : TroubleshootTest(R.string.settings_troubleshoot_test_battery_title) {
|
||||
|
||||
override fun perform() {
|
||||
val context = fragment.context
|
||||
if (context != null && isIgnoringBatteryOptimizations(context)) {
|
||||
description = fragment.getString(R.string.settings_troubleshoot_test_battery_success)
|
||||
if (isIgnoringBatteryOptimizations(context)) {
|
||||
description = stringProvider.getString(R.string.settings_troubleshoot_test_battery_success)
|
||||
status = TestStatus.SUCCESS
|
||||
quickFix = null
|
||||
} else {
|
||||
description = fragment.getString(R.string.settings_troubleshoot_test_battery_failed)
|
||||
description = stringProvider.getString(R.string.settings_troubleshoot_test_battery_failed)
|
||||
quickFix = object : TroubleshootQuickFix(R.string.settings_troubleshoot_test_battery_quickfix) {
|
||||
override fun doFix() {
|
||||
fragment.activity?.let {
|
||||
requestDisablingBatteryOptimization(it, fragment, NotificationTroubleshootTestManager.REQ_CODE_FIX)
|
||||
}
|
||||
requestDisablingBatteryOptimization(context, null, NotificationTroubleshootTestManager.REQ_CODE_FIX)
|
||||
}
|
||||
}
|
||||
status = TestStatus.FAILED
|
||||
|
|
|
@ -22,16 +22,18 @@ import android.content.BroadcastReceiver
|
|||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.os.PowerManager
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.content.getSystemService
|
||||
import im.vector.app.core.di.HasVectorInjector
|
||||
import im.vector.app.core.services.VectorSyncService
|
||||
import androidx.core.content.getSystemService
|
||||
import im.vector.app.features.settings.VectorPreferences
|
||||
import org.matrix.android.sdk.internal.session.sync.job.SyncService
|
||||
import timber.log.Timber
|
||||
|
||||
class AlarmSyncBroadcastReceiver : BroadcastReceiver() {
|
||||
|
||||
lateinit var vectorPreferences: VectorPreferences
|
||||
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
val appContext = context.applicationContext
|
||||
if (appContext is HasVectorInjector) {
|
||||
|
@ -40,41 +42,35 @@ class AlarmSyncBroadcastReceiver : BroadcastReceiver() {
|
|||
Timber.v("No active session don't launch sync service.")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Acquire a lock to give enough time for the sync :/
|
||||
context.getSystemService<PowerManager>()!!.run {
|
||||
newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "riotx:fdroidSynclock").apply {
|
||||
acquire((10_000).toLong())
|
||||
}
|
||||
vectorPreferences = appContext.injector().vectorPreferences()
|
||||
}
|
||||
|
||||
val sessionId = intent.getStringExtra(SyncService.EXTRA_SESSION_ID) ?: return
|
||||
// This method is called when the BroadcastReceiver is receiving an Intent broadcast.
|
||||
Timber.d("RestartBroadcastReceiver received intent")
|
||||
VectorSyncService.newIntent(context, sessionId).let {
|
||||
VectorSyncService.newPeriodicIntent(context, sessionId, vectorPreferences.backgroundSyncTimeOut(), vectorPreferences.backgroundSyncDelay()).let {
|
||||
try {
|
||||
ContextCompat.startForegroundService(context, it)
|
||||
} catch (ex: Throwable) {
|
||||
// TODO
|
||||
Timber.i("## Sync: Failed to start service, Alarm scheduled to restart service")
|
||||
scheduleAlarm(context, sessionId, vectorPreferences.backgroundSyncDelay())
|
||||
Timber.e(ex)
|
||||
}
|
||||
}
|
||||
|
||||
scheduleAlarm(context, sessionId, 30_000L)
|
||||
Timber.i("Alarm scheduled to restart service")
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val REQUEST_CODE = 0
|
||||
|
||||
fun scheduleAlarm(context: Context, sessionId: String, delay: Long) {
|
||||
fun scheduleAlarm(context: Context, sessionId: String, delayInSeconds: Int) {
|
||||
// Reschedule
|
||||
Timber.v("## Sync: Scheduling alarm for background sync in $delayInSeconds seconds")
|
||||
val intent = Intent(context, AlarmSyncBroadcastReceiver::class.java).apply {
|
||||
putExtra(SyncService.EXTRA_SESSION_ID, sessionId)
|
||||
putExtra(SyncService.EXTRA_PERIODIC, true)
|
||||
}
|
||||
val pIntent = PendingIntent.getBroadcast(context, REQUEST_CODE, intent, PendingIntent.FLAG_UPDATE_CURRENT)
|
||||
val firstMillis = System.currentTimeMillis() + delay
|
||||
val firstMillis = System.currentTimeMillis() + delayInSeconds * 1000L
|
||||
val alarmMgr = context.getSystemService<AlarmManager>()!!
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
alarmMgr.setAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, firstMillis, pIntent)
|
||||
|
@ -84,11 +80,20 @@ class AlarmSyncBroadcastReceiver : BroadcastReceiver() {
|
|||
}
|
||||
|
||||
fun cancelAlarm(context: Context) {
|
||||
Timber.v("Cancel alarm")
|
||||
Timber.v("## Sync: Cancel alarm for background sync")
|
||||
val intent = Intent(context, AlarmSyncBroadcastReceiver::class.java)
|
||||
val pIntent = PendingIntent.getBroadcast(context, REQUEST_CODE, intent, PendingIntent.FLAG_UPDATE_CURRENT)
|
||||
val alarmMgr = context.getSystemService<AlarmManager>()!!
|
||||
alarmMgr.cancel(pIntent)
|
||||
|
||||
// Stop current service to restart
|
||||
VectorSyncService.stopIntent(context).let {
|
||||
try {
|
||||
ContextCompat.startForegroundService(context, it)
|
||||
} catch (ex: Throwable) {
|
||||
Timber.i("## Sync: Cancel sync")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,6 +21,8 @@ import android.content.BroadcastReceiver
|
|||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import im.vector.app.core.di.HasVectorInjector
|
||||
import im.vector.app.core.extensions.vectorComponent
|
||||
import im.vector.app.fdroid.BackgroundSyncStarter
|
||||
import timber.log.Timber
|
||||
|
||||
class OnApplicationUpgradeOrRebootReceiver : BroadcastReceiver() {
|
||||
|
@ -29,10 +31,11 @@ class OnApplicationUpgradeOrRebootReceiver : BroadcastReceiver() {
|
|||
Timber.v("## onReceive() ${intent.action}")
|
||||
val appContext = context.applicationContext
|
||||
if (appContext is HasVectorInjector) {
|
||||
val activeSession = appContext.injector().activeSessionHolder().getSafeActiveSession()
|
||||
if (activeSession != null) {
|
||||
AlarmSyncBroadcastReceiver.scheduleAlarm(context, activeSession.sessionId, 10)
|
||||
}
|
||||
BackgroundSyncStarter.start(
|
||||
context,
|
||||
appContext.vectorComponent().vectorPreferences(),
|
||||
appContext.injector().activeSessionHolder()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,9 +22,9 @@ import android.app.Activity
|
|||
import android.content.Context
|
||||
import im.vector.app.core.di.ActiveSessionHolder
|
||||
import im.vector.app.core.pushers.PushersManager
|
||||
import im.vector.app.fdroid.BackgroundSyncStarter
|
||||
import im.vector.app.fdroid.receiver.AlarmSyncBroadcastReceiver
|
||||
import im.vector.app.features.settings.VectorPreferences
|
||||
import timber.log.Timber
|
||||
|
||||
/**
|
||||
* This class has an alter ego in the gplay variant.
|
||||
|
@ -61,16 +61,13 @@ object FcmHelper {
|
|||
// No op
|
||||
}
|
||||
|
||||
fun onEnterForeground(context: Context) {
|
||||
fun onEnterForeground(context: Context, activeSessionHolder: ActiveSessionHolder) {
|
||||
// try to stop all regardless of background mode
|
||||
activeSessionHolder.getSafeActiveSession()?.stopAnyBackgroundSync()
|
||||
AlarmSyncBroadcastReceiver.cancelAlarm(context)
|
||||
}
|
||||
|
||||
fun onEnterBackground(context: Context, vectorPreferences: VectorPreferences, activeSessionHolder: ActiveSessionHolder) {
|
||||
// We need to use alarm in this mode
|
||||
if (vectorPreferences.areNotificationEnabledForDevice() && activeSessionHolder.hasActiveSession()) {
|
||||
val currentSession = activeSessionHolder.getActiveSession()
|
||||
AlarmSyncBroadcastReceiver.scheduleAlarm(context, currentSession.sessionId, 4_000L)
|
||||
Timber.i("Alarm scheduled to restart service")
|
||||
}
|
||||
BackgroundSyncStarter.start(context, vectorPreferences, activeSessionHolder)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ package im.vector.app.push.fcm
|
|||
import androidx.fragment.app.Fragment
|
||||
import im.vector.app.fdroid.features.settings.troubleshoot.TestAutoStartBoot
|
||||
import im.vector.app.fdroid.features.settings.troubleshoot.TestBackgroundRestrictions
|
||||
import im.vector.app.fdroid.features.settings.troubleshoot.TestBatteryOptimization
|
||||
import im.vector.app.features.settings.troubleshoot.NotificationTroubleshootTestManager
|
||||
import im.vector.app.features.settings.troubleshoot.TestAccountSettings
|
||||
import im.vector.app.features.settings.troubleshoot.TestDeviceSettings
|
||||
|
@ -30,7 +31,8 @@ class NotificationTroubleshootTestManagerFactory @Inject constructor(private val
|
|||
private val testDeviceSettings: TestDeviceSettings,
|
||||
private val testPushRulesSettings: TestPushRulesSettings,
|
||||
private val testAutoStartBoot: TestAutoStartBoot,
|
||||
private val testBackgroundRestrictions: TestBackgroundRestrictions) {
|
||||
private val testBackgroundRestrictions: TestBackgroundRestrictions,
|
||||
private val testBatteryOptimization: TestBatteryOptimization) {
|
||||
|
||||
fun create(fragment: Fragment): NotificationTroubleshootTestManager {
|
||||
val mgr = NotificationTroubleshootTestManager(fragment)
|
||||
|
@ -40,6 +42,7 @@ class NotificationTroubleshootTestManagerFactory @Inject constructor(private val
|
|||
mgr.addTest(testPushRulesSettings)
|
||||
mgr.addTest(testAutoStartBoot)
|
||||
mgr.addTest(testBackgroundRestrictions)
|
||||
mgr.addTest(testBatteryOptimization)
|
||||
return mgr
|
||||
}
|
||||
}
|
||||
|
|
|
@ -130,14 +130,9 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() {
|
|||
if (BuildConfig.LOW_PRIVACY_LOG_ENABLE) {
|
||||
Timber.i("## onMessageReceivedInternal() : $data")
|
||||
}
|
||||
val eventId = data["event_id"]
|
||||
val roomId = data["room_id"]
|
||||
if (eventId == null || roomId == null) {
|
||||
Timber.e("## onMessageReceivedInternal() missing eventId and/or roomId")
|
||||
return
|
||||
}
|
||||
|
||||
// update the badge counter
|
||||
val unreadCount = data.get("unread")?.let { Integer.parseInt(it) } ?: 0
|
||||
val unreadCount = data["unread"]?.let { Integer.parseInt(it) } ?: 0
|
||||
BadgeProxy.updateBadgeCount(applicationContext, unreadCount)
|
||||
|
||||
val session = activeSessionHolder.getSafeActiveSession()
|
||||
|
@ -145,6 +140,9 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() {
|
|||
if (session == null) {
|
||||
Timber.w("## Can't sync from push, no current session")
|
||||
} else {
|
||||
val eventId = data["event_id"]
|
||||
val roomId = data["room_id"]
|
||||
|
||||
if (isEventAlreadyKnown(eventId, roomId)) {
|
||||
Timber.i("Ignoring push, event already known")
|
||||
} else {
|
||||
|
|
|
@ -19,7 +19,6 @@ package im.vector.app.push.fcm
|
|||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import androidx.preference.PreferenceManager
|
||||
import android.widget.Toast
|
||||
import androidx.core.content.edit
|
||||
import com.google.android.gms.common.ConnectionResult
|
||||
|
@ -27,6 +26,7 @@ import com.google.android.gms.common.GoogleApiAvailability
|
|||
import com.google.firebase.iid.FirebaseInstanceId
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.di.ActiveSessionHolder
|
||||
import im.vector.app.core.di.DefaultSharedPreferences
|
||||
import im.vector.app.core.pushers.PushersManager
|
||||
import im.vector.app.features.settings.VectorPreferences
|
||||
import timber.log.Timber
|
||||
|
@ -46,7 +46,7 @@ object FcmHelper {
|
|||
* @return the FCM token or null if not received from FCM
|
||||
*/
|
||||
fun getFcmToken(context: Context): String? {
|
||||
return PreferenceManager.getDefaultSharedPreferences(context).getString(PREFS_KEY_FCM_TOKEN, null)
|
||||
return DefaultSharedPreferences.getInstance(context).getString(PREFS_KEY_FCM_TOKEN, null)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -58,7 +58,7 @@ object FcmHelper {
|
|||
*/
|
||||
fun storeFcmToken(context: Context,
|
||||
token: String?) {
|
||||
PreferenceManager.getDefaultSharedPreferences(context).edit {
|
||||
DefaultSharedPreferences.getInstance(context).edit {
|
||||
putString(PREFS_KEY_FCM_TOKEN, token)
|
||||
}
|
||||
}
|
||||
|
@ -102,7 +102,7 @@ object FcmHelper {
|
|||
}
|
||||
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
fun onEnterForeground(context: Context) {
|
||||
fun onEnterForeground(context: Context, activeSessionHolder: ActiveSessionHolder) {
|
||||
// No op
|
||||
}
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@ import android.content.Context
|
|||
import android.content.res.Configuration
|
||||
import android.os.Handler
|
||||
import android.os.HandlerThread
|
||||
import android.os.StrictMode
|
||||
import androidx.core.provider.FontRequest
|
||||
import androidx.core.provider.FontsContractCompat
|
||||
import androidx.lifecycle.Lifecycle
|
||||
|
@ -92,6 +93,7 @@ class VectorApplication :
|
|||
private var fontThreadHandler: Handler? = null
|
||||
|
||||
override fun onCreate() {
|
||||
enableStrictModeIfNeeded()
|
||||
super.onCreate()
|
||||
appContext = this
|
||||
vectorComponent = DaggerVectorComponent.factory().create(this)
|
||||
|
@ -144,7 +146,7 @@ class VectorApplication :
|
|||
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
|
||||
fun entersForeground() {
|
||||
Timber.i("App entered foreground")
|
||||
FcmHelper.onEnterForeground(appContext)
|
||||
FcmHelper.onEnterForeground(appContext, activeSessionHolder)
|
||||
activeSessionHolder.getSafeActiveSession()?.also {
|
||||
it.stopAnyBackgroundSync()
|
||||
}
|
||||
|
@ -163,6 +165,15 @@ class VectorApplication :
|
|||
// initKnownEmojiHashSet(appContext)
|
||||
}
|
||||
|
||||
private fun enableStrictModeIfNeeded() {
|
||||
if (BuildConfig.ENABLE_STRICT_MODE_LOGS) {
|
||||
StrictMode.setThreadPolicy(StrictMode.ThreadPolicy.Builder()
|
||||
.detectAll()
|
||||
.penaltyLog()
|
||||
.build())
|
||||
}
|
||||
}
|
||||
|
||||
override fun providesMatrixConfiguration() = MatrixConfiguration(BuildConfig.FLAVOR_DESCRIPTION)
|
||||
|
||||
override fun getWorkManagerConfiguration(): WorkConfiguration {
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* 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 im.vector.app.core.date
|
||||
|
||||
import android.text.format.DateFormat
|
||||
import im.vector.app.core.resources.LocaleProvider
|
||||
import org.threeten.bp.format.DateTimeFormatter
|
||||
import javax.inject.Inject
|
||||
|
||||
class AbbrevDateFormatterProvider @Inject constructor(private val localeProvider: LocaleProvider) : DateFormatterProvider {
|
||||
|
||||
override val dateWithMonthFormatter: DateTimeFormatter by lazy {
|
||||
val pattern = DateFormat.getBestDateTimePattern(localeProvider.current(), "d MMM")
|
||||
DateTimeFormatter.ofPattern(pattern, localeProvider.current())
|
||||
}
|
||||
|
||||
override val dateWithYearFormatter: DateTimeFormatter by lazy {
|
||||
val pattern = DateFormat.getBestDateTimePattern(localeProvider.current(), "dd.MM.yyyy")
|
||||
DateTimeFormatter.ofPattern(pattern, localeProvider.current())
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* 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 im.vector.app.core.date
|
||||
|
||||
/* This will represent all kind of available date formats for the app.
|
||||
We will use the date Sep 7 2020 at 9:30am as an example.
|
||||
The formatting is depending of the current date.
|
||||
*/
|
||||
enum class DateFormatKind {
|
||||
// Will show date relative and time (today or yesterday or Sep 7 or 09/07/2020 at 9:30am)
|
||||
DEFAULT_DATE_AND_TIME,
|
||||
|
||||
// Will show hour or date relative (9:30am or yesterday or Sep 7 or 09/07/2020)
|
||||
ROOM_LIST,
|
||||
|
||||
// Will show full date (Sep 7 2020)
|
||||
TIMELINE_DAY_DIVIDER,
|
||||
|
||||
// Will show full date and time (Mon, Sep 7 2020, 9:30am)
|
||||
MESSAGE_DETAIL,
|
||||
|
||||
// Will only show time (9:30am)
|
||||
MESSAGE_SIMPLE,
|
||||
|
||||
// Will only show time (9:30am)
|
||||
EDIT_HISTORY_ROW,
|
||||
|
||||
// Will only show date relative (today or yesterday or Sep 7 or 09/07/2020)
|
||||
EDIT_HISTORY_HEADER
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* 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 im.vector.app.core.date
|
||||
|
||||
import org.threeten.bp.format.DateTimeFormatter
|
||||
|
||||
interface DateFormatterProvider {
|
||||
|
||||
val dateWithMonthFormatter: DateTimeFormatter
|
||||
|
||||
val dateWithYearFormatter: DateTimeFormatter
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* 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 im.vector.app.core.date
|
||||
|
||||
import javax.inject.Inject
|
||||
|
||||
class DateFormatterProviders @Inject constructor(private val defaultDateFormatterProvider: DefaultDateFormatterProvider,
|
||||
private val abbrevDateFormatterProvider: AbbrevDateFormatterProvider) {
|
||||
|
||||
fun provide(abbrev: Boolean): DateFormatterProvider {
|
||||
return if (abbrev) {
|
||||
abbrevDateFormatterProvider
|
||||
} else {
|
||||
defaultDateFormatterProvider
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* 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 im.vector.app.core.date
|
||||
|
||||
import android.content.Context
|
||||
import android.text.format.DateFormat
|
||||
import im.vector.app.core.resources.LocaleProvider
|
||||
import org.threeten.bp.format.DateTimeFormatter
|
||||
import javax.inject.Inject
|
||||
|
||||
class DefaultDateFormatterProvider @Inject constructor(private val context: Context,
|
||||
private val localeProvider: LocaleProvider)
|
||||
: DateFormatterProvider {
|
||||
|
||||
override val dateWithMonthFormatter: DateTimeFormatter by lazy {
|
||||
val pattern = DateFormat.getBestDateTimePattern(localeProvider.current(), "d MMMMM")
|
||||
DateTimeFormatter.ofPattern(pattern)
|
||||
}
|
||||
|
||||
override val dateWithYearFormatter: DateTimeFormatter by lazy {
|
||||
val pattern = DateFormat.getBestDateTimePattern(localeProvider.current(), "d MMM y")
|
||||
DateTimeFormatter.ofPattern(pattern)
|
||||
}
|
||||
}
|
|
@ -19,64 +19,147 @@ package im.vector.app.core.date
|
|||
import android.content.Context
|
||||
import android.text.format.DateFormat
|
||||
import android.text.format.DateUtils
|
||||
import im.vector.app.core.resources.DateProvider
|
||||
import im.vector.app.core.resources.LocaleProvider
|
||||
import im.vector.app.core.resources.toTimestamp
|
||||
import org.threeten.bp.LocalDateTime
|
||||
import org.threeten.bp.Period
|
||||
import org.threeten.bp.format.DateTimeFormatter
|
||||
import java.util.Calendar
|
||||
import java.util.Date
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Returns the timestamp for the start of the day of the provided time.
|
||||
* For example, for the time "Jul 21, 11:11" the start of the day: "Jul 21, 00:00" is returned.
|
||||
*/
|
||||
fun startOfDay(time: Long): Long {
|
||||
val calendar = Calendar.getInstance()
|
||||
calendar.time = Date(time)
|
||||
calendar.set(Calendar.HOUR_OF_DAY, 0)
|
||||
calendar.set(Calendar.MINUTE, 0)
|
||||
calendar.set(Calendar.SECOND, 0)
|
||||
calendar.set(Calendar.MILLISECOND, 0)
|
||||
return calendar.time.time
|
||||
}
|
||||
import kotlin.math.absoluteValue
|
||||
|
||||
class VectorDateFormatter @Inject constructor(private val context: Context,
|
||||
private val localeProvider: LocaleProvider) {
|
||||
private val localeProvider: LocaleProvider,
|
||||
private val dateFormatterProviders: DateFormatterProviders) {
|
||||
|
||||
private val messageHourFormatter by lazy {
|
||||
DateTimeFormatter.ofPattern("H:mm", localeProvider.current())
|
||||
private val hourFormatter by lazy {
|
||||
if (DateFormat.is24HourFormat(context)) {
|
||||
DateTimeFormatter.ofPattern("HH:mm", localeProvider.current())
|
||||
} else {
|
||||
DateTimeFormatter.ofPattern("h:mm a", localeProvider.current())
|
||||
}
|
||||
}
|
||||
|
||||
private val messageDayFormatter by lazy {
|
||||
DateTimeFormatter.ofPattern(DateFormat.getBestDateTimePattern(localeProvider.current(), "EEE d MMM"))
|
||||
}
|
||||
|
||||
fun formatMessageHour(localDateTime: LocalDateTime): String {
|
||||
return messageHourFormatter.format(localDateTime)
|
||||
}
|
||||
|
||||
fun formatMessageDay(localDateTime: LocalDateTime): String {
|
||||
return messageDayFormatter.format(localDateTime)
|
||||
private val fullDateFormatter by lazy {
|
||||
val pattern = if (DateFormat.is24HourFormat(context)) {
|
||||
DateFormat.getBestDateTimePattern(localeProvider.current(), "EEE, d MMM yyyy HH:mm")
|
||||
} else {
|
||||
DateFormat.getBestDateTimePattern(localeProvider.current(), "EEE, d MMM yyyy h:mm a")
|
||||
}
|
||||
DateTimeFormatter.ofPattern(pattern, localeProvider.current())
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a localized relative date time for the last 2 days, e.g, "Today, HH:MM", "Yesterday, HH:MM" or
|
||||
* "2 days ago, HH:MM".
|
||||
* For earlier timestamps the absolute date time is returned, e.g. "Month Day, HH:MM".
|
||||
* This method is used to format some date in the app.
|
||||
* It will be able to show only time, only date or both with some logic.
|
||||
* @param ts the timestamp to format or null.
|
||||
* @param dateFormatKind the kind of format to use
|
||||
*
|
||||
* @param time the absolute timestamp [ms] that should be formatted relative to now
|
||||
* @return the formatted date as string.
|
||||
*/
|
||||
fun formatRelativeDateTime(time: Long?): String {
|
||||
if (time == null) {
|
||||
fun format(ts: Long?, dateFormatKind: DateFormatKind): String {
|
||||
if (ts == null) return "-"
|
||||
val localDateTime = DateProvider.toLocalDateTime(ts)
|
||||
return when (dateFormatKind) {
|
||||
DateFormatKind.DEFAULT_DATE_AND_TIME -> formatDateAndTime(ts)
|
||||
DateFormatKind.ROOM_LIST -> formatTimeOrDate(
|
||||
date = localDateTime,
|
||||
showTimeIfSameDay = true,
|
||||
abbrev = true,
|
||||
useRelative = true
|
||||
)
|
||||
DateFormatKind.TIMELINE_DAY_DIVIDER -> formatTimeOrDate(
|
||||
date = localDateTime,
|
||||
alwaysShowYear = true
|
||||
)
|
||||
DateFormatKind.MESSAGE_DETAIL -> formatFullDate(localDateTime)
|
||||
DateFormatKind.MESSAGE_SIMPLE -> formatHour(localDateTime)
|
||||
DateFormatKind.EDIT_HISTORY_ROW -> formatHour(localDateTime)
|
||||
DateFormatKind.EDIT_HISTORY_HEADER -> formatTimeOrDate(
|
||||
date = localDateTime,
|
||||
abbrev = true,
|
||||
useRelative = true
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun formatFullDate(localDateTime: LocalDateTime): String {
|
||||
return fullDateFormatter.format(localDateTime)
|
||||
}
|
||||
|
||||
private fun formatHour(localDateTime: LocalDateTime): String {
|
||||
return hourFormatter.format(localDateTime)
|
||||
}
|
||||
|
||||
private fun formatDateWithMonth(localDateTime: LocalDateTime, abbrev: Boolean = false): String {
|
||||
return dateFormatterProviders.provide(abbrev).dateWithMonthFormatter.format(localDateTime)
|
||||
}
|
||||
|
||||
private fun formatDateWithYear(localDateTime: LocalDateTime, abbrev: Boolean = false): String {
|
||||
return dateFormatterProviders.provide(abbrev).dateWithYearFormatter.format(localDateTime)
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will only show time or date following the parameters.
|
||||
*/
|
||||
private fun formatTimeOrDate(
|
||||
date: LocalDateTime?,
|
||||
showTimeIfSameDay: Boolean = false,
|
||||
useRelative: Boolean = false,
|
||||
alwaysShowYear: Boolean = false,
|
||||
abbrev: Boolean = false
|
||||
): String {
|
||||
if (date == null) {
|
||||
return ""
|
||||
}
|
||||
val now = System.currentTimeMillis()
|
||||
return DateUtils.getRelativeDateTimeString(
|
||||
context,
|
||||
time,
|
||||
val currentDate = DateProvider.currentLocalDateTime()
|
||||
val isSameDay = date.toLocalDate() == currentDate.toLocalDate()
|
||||
return if (showTimeIfSameDay && isSameDay) {
|
||||
formatHour(date)
|
||||
} else {
|
||||
formatDate(date, currentDate, alwaysShowYear, abbrev, useRelative)
|
||||
}
|
||||
}
|
||||
|
||||
private fun formatDate(
|
||||
date: LocalDateTime,
|
||||
currentDate: LocalDateTime,
|
||||
alwaysShowYear: Boolean,
|
||||
abbrev: Boolean,
|
||||
useRelative: Boolean
|
||||
): String {
|
||||
val period = Period.between(date.toLocalDate(), currentDate.toLocalDate())
|
||||
return if (period.years.absoluteValue >= 1 || alwaysShowYear) {
|
||||
formatDateWithYear(date, abbrev)
|
||||
} else if (useRelative && period.days.absoluteValue < 2 && period.months.absoluteValue < 1) {
|
||||
getRelativeDay(date.toTimestamp())
|
||||
} else {
|
||||
formatDateWithMonth(date, abbrev)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will show date and time with a preposition
|
||||
*/
|
||||
private fun formatDateAndTime(ts: Long): String {
|
||||
val date = DateProvider.toLocalDateTime(ts)
|
||||
val currentDate = DateProvider.currentLocalDateTime()
|
||||
// This fake date is created to be able to use getRelativeTimeSpanString so we can get a "at"
|
||||
// preposition and the right am/pm management.
|
||||
val fakeDate = LocalDateTime.of(currentDate.toLocalDate(), date.toLocalTime())
|
||||
val formattedTime = DateUtils.getRelativeTimeSpanString(context, fakeDate.toTimestamp(), true).toString()
|
||||
val formattedDate = formatDate(date, currentDate, alwaysShowYear = false, abbrev = true, useRelative = true)
|
||||
return "$formattedDate $formattedTime"
|
||||
}
|
||||
|
||||
/**
|
||||
* We are using this method for the keywords Today/Yesterday
|
||||
*/
|
||||
private fun getRelativeDay(ts: Long): String {
|
||||
return DateUtils.getRelativeTimeSpanString(
|
||||
ts,
|
||||
System.currentTimeMillis(),
|
||||
DateUtils.DAY_IN_MILLIS,
|
||||
now - startOfDay(now - 2 * DateUtils.DAY_IN_MILLIS),
|
||||
DateUtils.FORMAT_SHOW_WEEKDAY or DateUtils.FORMAT_SHOW_TIME
|
||||
).toString()
|
||||
DateUtils.FORMAT_SHOW_WEEKDAY).toString()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* 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 im.vector.app.core.di
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import androidx.preference.PreferenceManager
|
||||
|
||||
object DefaultSharedPreferences {
|
||||
|
||||
@Volatile private var INSTANCE: SharedPreferences? = null
|
||||
|
||||
fun getInstance(context: Context): SharedPreferences =
|
||||
INSTANCE ?: synchronized(this) {
|
||||
INSTANCE ?: PreferenceManager.getDefaultSharedPreferences(context.applicationContext).also { INSTANCE = it }
|
||||
}
|
||||
}
|
|
@ -57,6 +57,7 @@ import im.vector.app.features.settings.VectorPreferences
|
|||
import im.vector.app.features.ui.UiStateRepository
|
||||
import org.matrix.android.sdk.api.Matrix
|
||||
import org.matrix.android.sdk.api.auth.AuthenticationService
|
||||
import org.matrix.android.sdk.api.raw.RawService
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import javax.inject.Singleton
|
||||
|
||||
|
@ -118,6 +119,8 @@ interface VectorComponent {
|
|||
|
||||
fun authenticationService(): AuthenticationService
|
||||
|
||||
fun rawService(): RawService
|
||||
|
||||
fun bugReporter(): BugReporter
|
||||
|
||||
fun vectorUncaughtExceptionHandler(): VectorUncaughtExceptionHandler
|
||||
|
|
|
@ -34,6 +34,7 @@ import im.vector.app.features.ui.UiStateRepository
|
|||
import org.matrix.android.sdk.api.Matrix
|
||||
import org.matrix.android.sdk.api.auth.AuthenticationService
|
||||
import org.matrix.android.sdk.api.legacy.LegacySessionImporter
|
||||
import org.matrix.android.sdk.api.raw.RawService
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
|
||||
@Module
|
||||
|
@ -78,6 +79,12 @@ abstract class VectorModule {
|
|||
fun providesAuthenticationService(matrix: Matrix): AuthenticationService {
|
||||
return matrix.authenticationService()
|
||||
}
|
||||
|
||||
@Provides
|
||||
@JvmStatic
|
||||
fun providesRawService(matrix: Matrix): RawService {
|
||||
return matrix.rawService()
|
||||
}
|
||||
}
|
||||
|
||||
@Binds
|
||||
|
|
|
@ -22,36 +22,63 @@ import androidx.fragment.app.Fragment
|
|||
import androidx.fragment.app.FragmentTransaction
|
||||
import im.vector.app.core.platform.VectorBaseActivity
|
||||
|
||||
fun VectorBaseActivity.addFragment(frameId: Int, fragment: Fragment) {
|
||||
supportFragmentManager.commitTransaction { add(frameId, fragment) }
|
||||
fun VectorBaseActivity.addFragment(
|
||||
frameId: Int,
|
||||
fragment: Fragment,
|
||||
allowStateLoss: Boolean = false
|
||||
) {
|
||||
supportFragmentManager.commitTransaction(allowStateLoss) { add(frameId, fragment) }
|
||||
}
|
||||
|
||||
fun <T : Fragment> VectorBaseActivity.addFragment(frameId: Int, fragmentClass: Class<T>, params: Parcelable? = null, tag: String? = null) {
|
||||
supportFragmentManager.commitTransaction {
|
||||
fun <T : Fragment> VectorBaseActivity.addFragment(
|
||||
frameId: Int,
|
||||
fragmentClass: Class<T>,
|
||||
params: Parcelable? = null,
|
||||
tag: String? = null,
|
||||
allowStateLoss: Boolean = false
|
||||
) {
|
||||
supportFragmentManager.commitTransaction(allowStateLoss) {
|
||||
add(frameId, fragmentClass, params.toMvRxBundle(), tag)
|
||||
}
|
||||
}
|
||||
|
||||
fun VectorBaseActivity.replaceFragment(frameId: Int, fragment: Fragment, tag: String? = null) {
|
||||
supportFragmentManager.commitTransaction { replace(frameId, fragment, tag) }
|
||||
fun VectorBaseActivity.replaceFragment(
|
||||
frameId: Int,
|
||||
fragment: Fragment,
|
||||
tag: String? = null,
|
||||
allowStateLoss: Boolean = false
|
||||
) {
|
||||
supportFragmentManager.commitTransaction(allowStateLoss) { replace(frameId, fragment, tag) }
|
||||
}
|
||||
|
||||
fun <T : Fragment> VectorBaseActivity.replaceFragment(frameId: Int, fragmentClass: Class<T>, params: Parcelable? = null, tag: String? = null) {
|
||||
supportFragmentManager.commitTransaction {
|
||||
fun <T : Fragment> VectorBaseActivity.replaceFragment(
|
||||
frameId: Int,
|
||||
fragmentClass: Class<T>,
|
||||
params: Parcelable? = null,
|
||||
tag: String? = null,
|
||||
allowStateLoss: Boolean = false
|
||||
) {
|
||||
supportFragmentManager.commitTransaction(allowStateLoss) {
|
||||
replace(frameId, fragmentClass, params.toMvRxBundle(), tag)
|
||||
}
|
||||
}
|
||||
|
||||
fun VectorBaseActivity.addFragmentToBackstack(frameId: Int, fragment: Fragment, tag: String? = null) {
|
||||
supportFragmentManager.commitTransaction { replace(frameId, fragment).addToBackStack(tag) }
|
||||
fun VectorBaseActivity.addFragmentToBackstack(
|
||||
frameId: Int,
|
||||
fragment: Fragment,
|
||||
tag: String? = null,
|
||||
allowStateLoss: Boolean = false
|
||||
) {
|
||||
supportFragmentManager.commitTransaction(allowStateLoss) { replace(frameId, fragment).addToBackStack(tag) }
|
||||
}
|
||||
|
||||
fun <T : Fragment> VectorBaseActivity.addFragmentToBackstack(frameId: Int,
|
||||
fragmentClass: Class<T>,
|
||||
params: Parcelable? = null,
|
||||
tag: String? = null,
|
||||
allowStateLoss: Boolean = false,
|
||||
option: ((FragmentTransaction) -> Unit)? = null) {
|
||||
supportFragmentManager.commitTransaction {
|
||||
supportFragmentManager.commitTransaction(allowStateLoss) {
|
||||
option?.invoke(this)
|
||||
replace(frameId, fragmentClass, params.toMvRxBundle(), tag).addToBackStack(tag)
|
||||
}
|
||||
|
|
|
@ -26,62 +26,126 @@ import java.text.SimpleDateFormat
|
|||
import java.util.Date
|
||||
import java.util.Locale
|
||||
|
||||
fun VectorBaseFragment.addFragment(frameId: Int, fragment: Fragment) {
|
||||
parentFragmentManager.commitTransaction { add(frameId, fragment) }
|
||||
fun VectorBaseFragment.addFragment(
|
||||
frameId: Int,
|
||||
fragment: Fragment,
|
||||
allowStateLoss: Boolean = false
|
||||
) {
|
||||
parentFragmentManager.commitTransaction(allowStateLoss) { add(frameId, fragment) }
|
||||
}
|
||||
|
||||
fun <T : Fragment> VectorBaseFragment.addFragment(frameId: Int, fragmentClass: Class<T>, params: Parcelable? = null, tag: String? = null) {
|
||||
parentFragmentManager.commitTransaction {
|
||||
fun <T : Fragment> VectorBaseFragment.addFragment(
|
||||
frameId: Int,
|
||||
fragmentClass: Class<T>,
|
||||
params: Parcelable? = null,
|
||||
tag: String? = null,
|
||||
allowStateLoss: Boolean = false
|
||||
) {
|
||||
parentFragmentManager.commitTransaction(allowStateLoss) {
|
||||
add(frameId, fragmentClass, params.toMvRxBundle(), tag)
|
||||
}
|
||||
}
|
||||
|
||||
fun VectorBaseFragment.replaceFragment(frameId: Int, fragment: Fragment) {
|
||||
parentFragmentManager.commitTransaction { replace(frameId, fragment) }
|
||||
fun VectorBaseFragment.replaceFragment(
|
||||
frameId: Int,
|
||||
fragment: Fragment,
|
||||
allowStateLoss: Boolean = false
|
||||
) {
|
||||
parentFragmentManager.commitTransaction(allowStateLoss) { replace(frameId, fragment) }
|
||||
}
|
||||
|
||||
fun <T : Fragment> VectorBaseFragment.replaceFragment(frameId: Int, fragmentClass: Class<T>, params: Parcelable? = null, tag: String? = null) {
|
||||
parentFragmentManager.commitTransaction {
|
||||
fun <T : Fragment> VectorBaseFragment.replaceFragment(
|
||||
frameId: Int,
|
||||
fragmentClass: Class<T>,
|
||||
params: Parcelable? = null,
|
||||
tag: String? = null,
|
||||
allowStateLoss: Boolean = false
|
||||
) {
|
||||
parentFragmentManager.commitTransaction(allowStateLoss) {
|
||||
replace(frameId, fragmentClass, params.toMvRxBundle(), tag)
|
||||
}
|
||||
}
|
||||
|
||||
fun VectorBaseFragment.addFragmentToBackstack(frameId: Int, fragment: Fragment, tag: String? = null) {
|
||||
parentFragmentManager.commitTransaction { replace(frameId, fragment, tag).addToBackStack(tag) }
|
||||
fun VectorBaseFragment.addFragmentToBackstack(
|
||||
frameId: Int,
|
||||
fragment: Fragment,
|
||||
tag: String? = null,
|
||||
allowStateLoss: Boolean = false
|
||||
) {
|
||||
parentFragmentManager.commitTransaction(allowStateLoss) { replace(frameId, fragment, tag).addToBackStack(tag) }
|
||||
}
|
||||
|
||||
fun <T : Fragment> VectorBaseFragment.addFragmentToBackstack(frameId: Int, fragmentClass: Class<T>, params: Parcelable? = null, tag: String? = null) {
|
||||
parentFragmentManager.commitTransaction {
|
||||
fun <T : Fragment> VectorBaseFragment.addFragmentToBackstack(
|
||||
frameId: Int,
|
||||
fragmentClass: Class<T>,
|
||||
params: Parcelable? = null,
|
||||
tag: String? = null,
|
||||
allowStateLoss: Boolean = false
|
||||
) {
|
||||
parentFragmentManager.commitTransaction(allowStateLoss) {
|
||||
replace(frameId, fragmentClass, params.toMvRxBundle(), tag).addToBackStack(tag)
|
||||
}
|
||||
}
|
||||
|
||||
fun VectorBaseFragment.addChildFragment(frameId: Int, fragment: Fragment, tag: String? = null) {
|
||||
childFragmentManager.commitTransaction { add(frameId, fragment, tag) }
|
||||
fun VectorBaseFragment.addChildFragment(
|
||||
frameId: Int,
|
||||
fragment: Fragment,
|
||||
tag: String? = null,
|
||||
allowStateLoss: Boolean = false
|
||||
) {
|
||||
childFragmentManager.commitTransaction(allowStateLoss) { add(frameId, fragment, tag) }
|
||||
}
|
||||
|
||||
fun <T : Fragment> VectorBaseFragment.addChildFragment(frameId: Int, fragmentClass: Class<T>, params: Parcelable? = null, tag: String? = null) {
|
||||
childFragmentManager.commitTransaction {
|
||||
fun <T : Fragment> VectorBaseFragment.addChildFragment(
|
||||
frameId: Int,
|
||||
fragmentClass: Class<T>,
|
||||
params: Parcelable? = null,
|
||||
tag: String? = null,
|
||||
allowStateLoss: Boolean = false
|
||||
) {
|
||||
childFragmentManager.commitTransaction(allowStateLoss) {
|
||||
add(frameId, fragmentClass, params.toMvRxBundle(), tag)
|
||||
}
|
||||
}
|
||||
|
||||
fun VectorBaseFragment.replaceChildFragment(frameId: Int, fragment: Fragment, tag: String? = null) {
|
||||
childFragmentManager.commitTransaction { replace(frameId, fragment, tag) }
|
||||
fun VectorBaseFragment.replaceChildFragment(
|
||||
frameId: Int,
|
||||
fragment: Fragment,
|
||||
tag: String? = null,
|
||||
allowStateLoss: Boolean = false
|
||||
) {
|
||||
childFragmentManager.commitTransaction(allowStateLoss) { replace(frameId, fragment, tag) }
|
||||
}
|
||||
|
||||
fun <T : Fragment> VectorBaseFragment.replaceChildFragment(frameId: Int, fragmentClass: Class<T>, params: Parcelable? = null, tag: String? = null) {
|
||||
childFragmentManager.commitTransaction {
|
||||
fun <T : Fragment> VectorBaseFragment.replaceChildFragment(
|
||||
frameId: Int,
|
||||
fragmentClass: Class<T>,
|
||||
params: Parcelable? = null,
|
||||
tag: String? = null,
|
||||
allowStateLoss: Boolean = false
|
||||
) {
|
||||
childFragmentManager.commitTransaction(allowStateLoss) {
|
||||
replace(frameId, fragmentClass, params.toMvRxBundle(), tag)
|
||||
}
|
||||
}
|
||||
|
||||
fun VectorBaseFragment.addChildFragmentToBackstack(frameId: Int, fragment: Fragment, tag: String? = null) {
|
||||
childFragmentManager.commitTransaction { replace(frameId, fragment).addToBackStack(tag) }
|
||||
fun VectorBaseFragment.addChildFragmentToBackstack(
|
||||
frameId: Int,
|
||||
fragment: Fragment,
|
||||
tag: String? = null,
|
||||
allowStateLoss: Boolean = false
|
||||
) {
|
||||
childFragmentManager.commitTransaction(allowStateLoss) { replace(frameId, fragment).addToBackStack(tag) }
|
||||
}
|
||||
|
||||
fun <T : Fragment> VectorBaseFragment.addChildFragmentToBackstack(frameId: Int, fragmentClass: Class<T>, params: Parcelable? = null, tag: String? = null) {
|
||||
childFragmentManager.commitTransaction {
|
||||
fun <T : Fragment> VectorBaseFragment.addChildFragmentToBackstack(
|
||||
frameId: Int,
|
||||
fragmentClass: Class<T>,
|
||||
params: Parcelable? = null,
|
||||
tag: String? = null,
|
||||
allowStateLoss: Boolean = false
|
||||
) {
|
||||
childFragmentManager.commitTransaction(allowStateLoss) {
|
||||
replace(frameId, fragmentClass, params.toMvRxBundle(), tag).addToBackStack(tag)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,6 +27,11 @@ inline fun androidx.fragment.app.FragmentManager.commitTransactionNow(func: Frag
|
|||
}
|
||||
}
|
||||
|
||||
inline fun androidx.fragment.app.FragmentManager.commitTransaction(func: FragmentTransaction.() -> FragmentTransaction) {
|
||||
beginTransaction().func().commit()
|
||||
inline fun androidx.fragment.app.FragmentManager.commitTransaction(allowStateLoss: Boolean = false, func: FragmentTransaction.() -> FragmentTransaction) {
|
||||
val transaction = beginTransaction().func()
|
||||
if (allowStateLoss) {
|
||||
transaction.commitAllowingStateLoss()
|
||||
} else {
|
||||
transaction.commit()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,7 +37,8 @@ fun Session.configureAndStart(context: Context) {
|
|||
fun Session.startSyncing(context: Context) {
|
||||
val applicationContext = context.applicationContext
|
||||
if (!hasAlreadySynced()) {
|
||||
VectorSyncService.newIntent(applicationContext, sessionId).also {
|
||||
// initial sync is done as a service so it can continue below app lifecycle
|
||||
VectorSyncService.newOneShotIntent(applicationContext, sessionId, 0).also {
|
||||
try {
|
||||
ContextCompat.startForegroundService(applicationContext, it)
|
||||
} catch (ex: Throwable) {
|
||||
|
|
|
@ -100,7 +100,7 @@ class VectorGlideDataFetcher(private val activeSessionHolder: ActiveSessionHolde
|
|||
|
||||
override fun loadData(priority: Priority, callback: DataFetcher.DataCallback<in InputStream>) {
|
||||
Timber.v("Load data: $data")
|
||||
if (data.isLocalFile() && data.url != null) {
|
||||
if (data.isLocalFile && data.url != null) {
|
||||
val initialFile = File(data.url)
|
||||
callback.onDataReady(initialFile.inputStream())
|
||||
return
|
||||
|
|
|
@ -23,6 +23,10 @@ import org.threeten.bp.ZoneId
|
|||
object DateProvider {
|
||||
|
||||
private val zoneId = ZoneId.systemDefault()
|
||||
private val zoneOffset by lazy {
|
||||
val now = currentLocalDateTime()
|
||||
zoneId.rules.getOffset(now)
|
||||
}
|
||||
|
||||
fun toLocalDateTime(timestamp: Long?): LocalDateTime {
|
||||
val instant = Instant.ofEpochMilli(timestamp ?: 0)
|
||||
|
@ -33,4 +37,10 @@ object DateProvider {
|
|||
val instant = Instant.now()
|
||||
return LocalDateTime.ofInstant(instant, zoneId)
|
||||
}
|
||||
|
||||
fun toTimestamp(localDateTime: LocalDateTime): Long {
|
||||
return localDateTime.toInstant(zoneOffset).toEpochMilli()
|
||||
}
|
||||
}
|
||||
|
||||
fun LocalDateTime.toTimestamp(): Long = DateProvider.toTimestamp(this)
|
||||
|
|
|
@ -40,4 +40,8 @@ class UserPreferencesProvider @Inject constructor(private val vectorPreferences:
|
|||
fun neverShowLongClickOnRoomHelpAgain() {
|
||||
vectorPreferences.neverShowLongClickOnRoomHelpAgain()
|
||||
}
|
||||
|
||||
fun shouldShowRoomMemberStateEvents(): Boolean {
|
||||
return vectorPreferences.showRoomMemberStateEvents()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,19 +21,56 @@ import android.app.PendingIntent
|
|||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import androidx.core.content.ContextCompat.getSystemService
|
||||
import androidx.core.content.getSystemService
|
||||
import androidx.work.Constraints
|
||||
import androidx.work.Data
|
||||
import androidx.work.NetworkType
|
||||
import androidx.work.OneTimeWorkRequestBuilder
|
||||
import androidx.work.WorkManager
|
||||
import androidx.work.WorkRequest
|
||||
import androidx.work.Worker
|
||||
import androidx.work.WorkerParameters
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.extensions.vectorComponent
|
||||
import im.vector.app.features.notifications.NotificationUtils
|
||||
import org.matrix.android.sdk.internal.session.sync.job.SyncService
|
||||
import timber.log.Timber
|
||||
|
||||
class VectorSyncService : SyncService() {
|
||||
|
||||
companion object {
|
||||
|
||||
fun newIntent(context: Context, sessionId: String): Intent {
|
||||
fun newOneShotIntent(context: Context, sessionId: String, timeoutSeconds: Int): Intent {
|
||||
return Intent(context, VectorSyncService::class.java).also {
|
||||
it.putExtra(EXTRA_SESSION_ID, sessionId)
|
||||
it.putExtra(EXTRA_TIMEOUT_SECONDS, timeoutSeconds)
|
||||
it.putExtra(EXTRA_PERIODIC, false)
|
||||
}
|
||||
}
|
||||
|
||||
fun newPeriodicIntent(context: Context, sessionId: String, timeoutSeconds: Int, delayInSeconds: Int): Intent {
|
||||
return Intent(context, VectorSyncService::class.java).also {
|
||||
it.putExtra(EXTRA_SESSION_ID, sessionId)
|
||||
it.putExtra(EXTRA_TIMEOUT_SECONDS, timeoutSeconds)
|
||||
it.putExtra(EXTRA_PERIODIC, true)
|
||||
it.putExtra(EXTRA_DELAY_SECONDS, delayInSeconds)
|
||||
}
|
||||
}
|
||||
|
||||
fun newPeriodicNetworkBackIntent(context: Context, sessionId: String, timeoutSeconds: Int, delayInSeconds: Int): Intent {
|
||||
return Intent(context, VectorSyncService::class.java).also {
|
||||
it.putExtra(EXTRA_SESSION_ID, sessionId)
|
||||
it.putExtra(EXTRA_TIMEOUT_SECONDS, timeoutSeconds)
|
||||
it.putExtra(EXTRA_PERIODIC, true)
|
||||
it.putExtra(EXTRA_DELAY_SECONDS, delayInSeconds)
|
||||
it.putExtra(EXTRA_NETWORK_BACK_RESTART, true)
|
||||
}
|
||||
}
|
||||
|
||||
fun stopIntent(context: Context): Intent {
|
||||
return Intent(context, VectorSyncService::class.java).also {
|
||||
it.action = ACTION_STOP
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -55,8 +92,30 @@ class VectorSyncService : SyncService() {
|
|||
startForeground(NotificationUtils.NOTIFICATION_ID_FOREGROUND_SERVICE, notification)
|
||||
}
|
||||
|
||||
override fun onRescheduleAsked(sessionId: String, isInitialSync: Boolean, delay: Long) {
|
||||
reschedule(sessionId, delay)
|
||||
override fun onRescheduleAsked(sessionId: String, isInitialSync: Boolean, timeout: Int, delay: Int) {
|
||||
reschedule(sessionId, timeout, delay)
|
||||
}
|
||||
|
||||
override fun onNetworkError(sessionId: String, isInitialSync: Boolean, timeout: Int, delay: Int) {
|
||||
Timber.d("## Sync: A network error occured during sync")
|
||||
val uploadWorkRequest: WorkRequest =
|
||||
OneTimeWorkRequestBuilder<RestartWhenNetworkOn>()
|
||||
.setInputData(Data.Builder()
|
||||
.putString("sessionId", sessionId)
|
||||
.putInt("timeout", timeout)
|
||||
.putInt("delay", delay)
|
||||
.build()
|
||||
)
|
||||
.setConstraints(Constraints.Builder()
|
||||
.setRequiredNetworkType(NetworkType.CONNECTED)
|
||||
.build()
|
||||
)
|
||||
.build()
|
||||
|
||||
Timber.d("## Sync: Schedule a work to restart service when network will be on")
|
||||
WorkManager
|
||||
.getInstance(applicationContext)
|
||||
.enqueue(uploadWorkRequest)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
|
@ -69,13 +128,13 @@ class VectorSyncService : SyncService() {
|
|||
notificationManager.cancel(NotificationUtils.NOTIFICATION_ID_FOREGROUND_SERVICE)
|
||||
}
|
||||
|
||||
private fun reschedule(sessionId: String, delay: Long) {
|
||||
private fun reschedule(sessionId: String, timeout: Int, delay: Int) {
|
||||
val pendingIntent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
PendingIntent.getForegroundService(this, 0, newIntent(this, sessionId), 0)
|
||||
PendingIntent.getForegroundService(this, 0, newPeriodicIntent(this, sessionId, timeout, delay), 0)
|
||||
} else {
|
||||
PendingIntent.getService(this, 0, newIntent(this, sessionId), 0)
|
||||
PendingIntent.getService(this, 0, newPeriodicIntent(this, sessionId, timeout, delay), 0)
|
||||
}
|
||||
val firstMillis = System.currentTimeMillis() + delay
|
||||
val firstMillis = System.currentTimeMillis() + delay * 1000L
|
||||
val alarmMgr = getSystemService<AlarmManager>()!!
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
alarmMgr.setAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, firstMillis, pendingIntent)
|
||||
|
@ -83,4 +142,28 @@ class VectorSyncService : SyncService() {
|
|||
alarmMgr.set(AlarmManager.RTC_WAKEUP, firstMillis, pendingIntent)
|
||||
}
|
||||
}
|
||||
|
||||
class RestartWhenNetworkOn(appContext: Context, workerParams: WorkerParameters) :
|
||||
Worker(appContext, workerParams) {
|
||||
override fun doWork(): Result {
|
||||
val sessionId = inputData.getString("sessionId") ?: return Result.failure()
|
||||
val timeout = inputData.getInt("timeout", 6)
|
||||
val delay = inputData.getInt("delay", 60)
|
||||
|
||||
val pendingIntent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
PendingIntent.getForegroundService(applicationContext, 0, newPeriodicNetworkBackIntent(applicationContext, sessionId, timeout, delay), 0)
|
||||
} else {
|
||||
PendingIntent.getService(applicationContext, 0, newPeriodicNetworkBackIntent(applicationContext, sessionId, timeout, delay), 0)
|
||||
}
|
||||
val firstMillis = System.currentTimeMillis() + delay * 1000L
|
||||
val alarmMgr = getSystemService<AlarmManager>(applicationContext, AlarmManager::class.java)!!
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
alarmMgr.setAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, firstMillis, pendingIntent)
|
||||
} else {
|
||||
alarmMgr.set(AlarmManager.RTC_WAKEUP, firstMillis, pendingIntent)
|
||||
}
|
||||
// Indicate whether the work finished successfully with the Result
|
||||
return Result.success()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,11 +23,11 @@ import android.widget.TextView
|
|||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import androidx.core.content.edit
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.preference.PreferenceManager
|
||||
import butterknife.BindView
|
||||
import butterknife.ButterKnife
|
||||
import butterknife.OnClick
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.di.DefaultSharedPreferences
|
||||
import timber.log.Timber
|
||||
|
||||
/**
|
||||
|
@ -57,7 +57,7 @@ class KeysBackupBanner @JvmOverloads constructor(
|
|||
|
||||
init {
|
||||
setupView()
|
||||
PreferenceManager.getDefaultSharedPreferences(context).edit {
|
||||
DefaultSharedPreferences.getInstance(context).edit {
|
||||
putBoolean(BANNER_SETUP_DO_NOT_SHOW_AGAIN, false)
|
||||
putString(BANNER_RECOVER_DO_NOT_SHOW_FOR_VERSION, "")
|
||||
}
|
||||
|
@ -105,17 +105,17 @@ class KeysBackupBanner @JvmOverloads constructor(
|
|||
state.let {
|
||||
when (it) {
|
||||
is State.Setup -> {
|
||||
PreferenceManager.getDefaultSharedPreferences(context).edit {
|
||||
DefaultSharedPreferences.getInstance(context).edit {
|
||||
putBoolean(BANNER_SETUP_DO_NOT_SHOW_AGAIN, true)
|
||||
}
|
||||
}
|
||||
is State.Recover -> {
|
||||
PreferenceManager.getDefaultSharedPreferences(context).edit {
|
||||
DefaultSharedPreferences.getInstance(context).edit {
|
||||
putString(BANNER_RECOVER_DO_NOT_SHOW_FOR_VERSION, it.version)
|
||||
}
|
||||
}
|
||||
is State.Update -> {
|
||||
PreferenceManager.getDefaultSharedPreferences(context).edit {
|
||||
DefaultSharedPreferences.getInstance(context).edit {
|
||||
putString(BANNER_UPDATE_DO_NOT_SHOW_FOR_VERSION, it.version)
|
||||
}
|
||||
}
|
||||
|
@ -150,7 +150,7 @@ class KeysBackupBanner @JvmOverloads constructor(
|
|||
|
||||
private fun renderSetup(nbOfKeys: Int) {
|
||||
if (nbOfKeys == 0
|
||||
|| PreferenceManager.getDefaultSharedPreferences(context).getBoolean(BANNER_SETUP_DO_NOT_SHOW_AGAIN, false)) {
|
||||
|| DefaultSharedPreferences.getInstance(context).getBoolean(BANNER_SETUP_DO_NOT_SHOW_AGAIN, false)) {
|
||||
// Do not display the setup banner if there is no keys to backup, or if the user has already closed it
|
||||
isVisible = false
|
||||
} else {
|
||||
|
@ -164,7 +164,7 @@ class KeysBackupBanner @JvmOverloads constructor(
|
|||
}
|
||||
|
||||
private fun renderRecover(version: String) {
|
||||
if (version == PreferenceManager.getDefaultSharedPreferences(context).getString(BANNER_RECOVER_DO_NOT_SHOW_FOR_VERSION, null)) {
|
||||
if (version == DefaultSharedPreferences.getInstance(context).getString(BANNER_RECOVER_DO_NOT_SHOW_FOR_VERSION, null)) {
|
||||
isVisible = false
|
||||
} else {
|
||||
isVisible = true
|
||||
|
@ -177,7 +177,7 @@ class KeysBackupBanner @JvmOverloads constructor(
|
|||
}
|
||||
|
||||
private fun renderUpdate(version: String) {
|
||||
if (version == PreferenceManager.getDefaultSharedPreferences(context).getString(BANNER_UPDATE_DO_NOT_SHOW_FOR_VERSION, null)) {
|
||||
if (version == DefaultSharedPreferences.getInstance(context).getString(BANNER_UPDATE_DO_NOT_SHOW_FOR_VERSION, null)) {
|
||||
isVisible = false
|
||||
} else {
|
||||
isVisible = true
|
||||
|
@ -258,7 +258,7 @@ class KeysBackupBanner @JvmOverloads constructor(
|
|||
* Inform the banner that a Recover has been done for this version, so do not show the Recover banner for this version
|
||||
*/
|
||||
fun onRecoverDoneForVersion(context: Context, version: String) {
|
||||
PreferenceManager.getDefaultSharedPreferences(context).edit {
|
||||
DefaultSharedPreferences.getInstance(context).edit {
|
||||
putString(BANNER_RECOVER_DO_NOT_SHOW_FOR_VERSION, version)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ import android.media.Ringtone
|
|||
import android.media.RingtoneManager
|
||||
import android.net.Uri
|
||||
import androidx.core.content.edit
|
||||
import androidx.preference.PreferenceManager
|
||||
import im.vector.app.core.di.DefaultSharedPreferences
|
||||
import im.vector.app.features.settings.VectorPreferences
|
||||
|
||||
/**
|
||||
|
@ -40,7 +40,7 @@ import im.vector.app.features.settings.VectorPreferences
|
|||
* @see Ringtone
|
||||
*/
|
||||
fun getCallRingtoneUri(context: Context): Uri? {
|
||||
val callRingtone: String? = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
val callRingtone: String? = DefaultSharedPreferences.getInstance(context)
|
||||
.getString(VectorPreferences.SETTINGS_CALL_RINGTONE_URI_PREFERENCE_KEY, null)
|
||||
|
||||
callRingtone?.let {
|
||||
|
@ -94,7 +94,7 @@ fun getCallRingtoneName(context: Context): String? {
|
|||
* @see Ringtone
|
||||
*/
|
||||
fun setCallRingtoneUri(context: Context, ringtoneUri: Uri) {
|
||||
PreferenceManager.getDefaultSharedPreferences(context)
|
||||
DefaultSharedPreferences.getInstance(context)
|
||||
.edit {
|
||||
putString(VectorPreferences.SETTINGS_CALL_RINGTONE_URI_PREFERENCE_KEY, ringtoneUri.toString())
|
||||
}
|
||||
|
@ -104,14 +104,14 @@ fun setCallRingtoneUri(context: Context, ringtoneUri: Uri) {
|
|||
* Set using Riot default ringtone
|
||||
*/
|
||||
fun useRiotDefaultRingtone(context: Context): Boolean {
|
||||
return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(VectorPreferences.SETTINGS_CALL_RINGTONE_USE_RIOT_PREFERENCE_KEY, true)
|
||||
return DefaultSharedPreferences.getInstance(context).getBoolean(VectorPreferences.SETTINGS_CALL_RINGTONE_USE_RIOT_PREFERENCE_KEY, true)
|
||||
}
|
||||
|
||||
/**
|
||||
* Ask if default Riot ringtone has to be used
|
||||
*/
|
||||
fun setUseRiotDefaultRingtone(context: Context, useRiotDefault: Boolean) {
|
||||
PreferenceManager.getDefaultSharedPreferences(context)
|
||||
DefaultSharedPreferences.getInstance(context)
|
||||
.edit {
|
||||
putBoolean(VectorPreferences.SETTINGS_CALL_RINGTONE_USE_RIOT_PREFERENCE_KEY, useRiotDefault)
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ import android.content.pm.PackageManager
|
|||
import android.media.AudioManager
|
||||
import androidx.core.content.getSystemService
|
||||
import im.vector.app.core.services.WiredHeadsetStateReceiver
|
||||
import org.matrix.android.sdk.api.session.call.CallState
|
||||
import org.matrix.android.sdk.api.session.call.MxCall
|
||||
import timber.log.Timber
|
||||
import java.util.concurrent.Executors
|
||||
|
@ -116,10 +117,19 @@ class CallAudioManager(
|
|||
// Always disable microphone mute during a WebRTC call.
|
||||
setMicrophoneMute(false)
|
||||
|
||||
adjustCurrentSoundDevice(mxCall)
|
||||
}
|
||||
|
||||
private fun adjustCurrentSoundDevice(mxCall: MxCall) {
|
||||
val audioManager = audioManager ?: return
|
||||
executor.execute {
|
||||
// If there are no headset, start video output in speaker
|
||||
// (you can't watch the video and have the phone close to your ear)
|
||||
if (mxCall.isVideoCall && !isHeadsetOn()) {
|
||||
if (mxCall.state == CallState.LocalRinging && !isHeadsetOn()) {
|
||||
// Always use speaker if incoming call is in ringing state and a headset is not connected
|
||||
Timber.v("##VOIP: AudioManager default to SPEAKER (it is ringing)")
|
||||
setCurrentSoundDevice(SoundDevice.SPEAKER)
|
||||
} else if (mxCall.isVideoCall && !isHeadsetOn()) {
|
||||
// If there are no headset, start video output in speaker
|
||||
// (you can't watch the video and have the phone close to your ear)
|
||||
Timber.v("##VOIP: AudioManager default to speaker ")
|
||||
setCurrentSoundDevice(SoundDevice.SPEAKER)
|
||||
} else {
|
||||
|
@ -138,6 +148,11 @@ class CallAudioManager(
|
|||
}
|
||||
}
|
||||
|
||||
fun onCallConnected(mxCall: MxCall) {
|
||||
Timber.v("##VOIP: AudioManager call answered, adjusting current sound device")
|
||||
adjustCurrentSoundDevice(mxCall)
|
||||
}
|
||||
|
||||
fun getAvailableSoundDevices(): List<SoundDevice> {
|
||||
return ArrayList<SoundDevice>().apply {
|
||||
if (isBluetoothHeadsetOn()) add(SoundDevice.WIRELESS_HEADSET)
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* 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 im.vector.app.features.call
|
||||
|
||||
import im.vector.app.core.platform.VectorViewModelAction
|
||||
|
||||
sealed class VectorCallViewActions : VectorViewModelAction {
|
||||
object EndCall : VectorCallViewActions()
|
||||
object AcceptCall : VectorCallViewActions()
|
||||
object DeclineCall : VectorCallViewActions()
|
||||
object ToggleMute : VectorCallViewActions()
|
||||
object ToggleVideo : VectorCallViewActions()
|
||||
data class ChangeAudioDevice(val device: CallAudioManager.SoundDevice) : VectorCallViewActions()
|
||||
object SwitchSoundDevice : VectorCallViewActions()
|
||||
object HeadSetButtonPressed : VectorCallViewActions()
|
||||
object ToggleCamera : VectorCallViewActions()
|
||||
object ToggleHDSD : VectorCallViewActions()
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* 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 im.vector.app.features.call
|
||||
|
||||
import im.vector.app.core.platform.VectorViewEvents
|
||||
import org.matrix.android.sdk.api.session.call.TurnServerResponse
|
||||
|
||||
sealed class VectorCallViewEvents : VectorViewEvents {
|
||||
|
||||
object DismissNoCall : VectorCallViewEvents()
|
||||
data class ConnectionTimeout(val turn: TurnServerResponse?) : VectorCallViewEvents()
|
||||
data class ShowSoundDeviceChooser(
|
||||
val available: List<CallAudioManager.SoundDevice>,
|
||||
val current: CallAudioManager.SoundDevice
|
||||
) : VectorCallViewEvents()
|
||||
// data class CallAnswered(val content: CallAnswerContent) : VectorCallViewEvents()
|
||||
// data class CallHangup(val content: CallHangupContent) : VectorCallViewEvents()
|
||||
// object CallAccepted : VectorCallViewEvents()
|
||||
}
|
|
@ -16,10 +16,8 @@
|
|||
|
||||
package im.vector.app.features.call
|
||||
|
||||
import com.airbnb.mvrx.Async
|
||||
import com.airbnb.mvrx.Fail
|
||||
import com.airbnb.mvrx.Loading
|
||||
import com.airbnb.mvrx.MvRxState
|
||||
import com.airbnb.mvrx.MvRxViewModelFactory
|
||||
import com.airbnb.mvrx.Success
|
||||
import com.airbnb.mvrx.Uninitialized
|
||||
|
@ -27,9 +25,7 @@ import com.airbnb.mvrx.ViewModelContext
|
|||
import com.squareup.inject.assisted.Assisted
|
||||
import com.squareup.inject.assisted.AssistedInject
|
||||
import im.vector.app.core.extensions.exhaustive
|
||||
import im.vector.app.core.platform.VectorViewEvents
|
||||
import im.vector.app.core.platform.VectorViewModel
|
||||
import im.vector.app.core.platform.VectorViewModelAction
|
||||
import org.matrix.android.sdk.api.MatrixCallback
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import org.matrix.android.sdk.api.session.call.CallState
|
||||
|
@ -41,48 +37,6 @@ import org.webrtc.PeerConnection
|
|||
import java.util.Timer
|
||||
import java.util.TimerTask
|
||||
|
||||
data class VectorCallViewState(
|
||||
val callId: String? = null,
|
||||
val roomId: String = "",
|
||||
val isVideoCall: Boolean,
|
||||
val isAudioMuted: Boolean = false,
|
||||
val isVideoEnabled: Boolean = true,
|
||||
val isVideoCaptureInError: Boolean = false,
|
||||
val isHD: Boolean = false,
|
||||
val isFrontCamera: Boolean = true,
|
||||
val canSwitchCamera: Boolean = true,
|
||||
val soundDevice: CallAudioManager.SoundDevice = CallAudioManager.SoundDevice.PHONE,
|
||||
val availableSoundDevices: List<CallAudioManager.SoundDevice> = emptyList(),
|
||||
val otherUserMatrixItem: Async<MatrixItem> = Uninitialized,
|
||||
val callState: Async<CallState> = Uninitialized
|
||||
) : MvRxState
|
||||
|
||||
sealed class VectorCallViewActions : VectorViewModelAction {
|
||||
object EndCall : VectorCallViewActions()
|
||||
object AcceptCall : VectorCallViewActions()
|
||||
object DeclineCall : VectorCallViewActions()
|
||||
object ToggleMute : VectorCallViewActions()
|
||||
object ToggleVideo : VectorCallViewActions()
|
||||
data class ChangeAudioDevice(val device: CallAudioManager.SoundDevice) : VectorCallViewActions()
|
||||
object SwitchSoundDevice : VectorCallViewActions()
|
||||
object HeadSetButtonPressed : VectorCallViewActions()
|
||||
object ToggleCamera : VectorCallViewActions()
|
||||
object ToggleHDSD : VectorCallViewActions()
|
||||
}
|
||||
|
||||
sealed class VectorCallViewEvents : VectorViewEvents {
|
||||
|
||||
object DismissNoCall : VectorCallViewEvents()
|
||||
data class ConnectionTimeout(val turn: TurnServerResponse?) : VectorCallViewEvents()
|
||||
data class ShowSoundDeviceChooser(
|
||||
val available: List<CallAudioManager.SoundDevice>,
|
||||
val current: CallAudioManager.SoundDevice
|
||||
) : VectorCallViewEvents()
|
||||
// data class CallAnswered(val content: CallAnswerContent) : VectorCallViewEvents()
|
||||
// data class CallHangup(val content: CallHangupContent) : VectorCallViewEvents()
|
||||
// object CallAccepted : VectorCallViewEvents()
|
||||
}
|
||||
|
||||
class VectorCallViewModel @AssistedInject constructor(
|
||||
@Assisted initialState: VectorCallViewState,
|
||||
@Assisted val args: CallArgs,
|
||||
|
@ -91,23 +45,23 @@ class VectorCallViewModel @AssistedInject constructor(
|
|||
val proximityManager: CallProximityManager
|
||||
) : VectorViewModel<VectorCallViewState, VectorCallViewActions, VectorCallViewEvents>(initialState) {
|
||||
|
||||
var call: MxCall? = null
|
||||
private var call: MxCall? = null
|
||||
|
||||
var connectionTimoutTimer: Timer? = null
|
||||
var hasBeenConnectedOnce = false
|
||||
private var connectionTimeoutTimer: Timer? = null
|
||||
private var hasBeenConnectedOnce = false
|
||||
|
||||
private val callStateListener = object : MxCall.StateListener {
|
||||
override fun onStateUpdate(call: MxCall) {
|
||||
val callState = call.state
|
||||
if (callState is CallState.Connected && callState.iceConnectionState == PeerConnection.PeerConnectionState.CONNECTED) {
|
||||
hasBeenConnectedOnce = true
|
||||
connectionTimoutTimer?.cancel()
|
||||
connectionTimoutTimer = null
|
||||
connectionTimeoutTimer?.cancel()
|
||||
connectionTimeoutTimer = null
|
||||
} else {
|
||||
// do we reset as long as it's moving?
|
||||
connectionTimoutTimer?.cancel()
|
||||
connectionTimeoutTimer?.cancel()
|
||||
if (hasBeenConnectedOnce) {
|
||||
connectionTimoutTimer = Timer().apply {
|
||||
connectionTimeoutTimer = Timer().apply {
|
||||
schedule(object : TimerTask() {
|
||||
override fun run() {
|
||||
session.callSignalingService().getTurnServer(object : MatrixCallback<TurnServerResponse> {
|
||||
|
@ -136,17 +90,17 @@ class VectorCallViewModel @AssistedInject constructor(
|
|||
override fun onCurrentCallChange(call: MxCall?) {
|
||||
}
|
||||
|
||||
override fun onCaptureStateChanged(mgr: WebRtcPeerConnectionManager) {
|
||||
override fun onCaptureStateChanged() {
|
||||
setState {
|
||||
copy(
|
||||
isVideoCaptureInError = mgr.capturerIsInError,
|
||||
isHD = mgr.currentCaptureFormat() is CaptureFormat.HD
|
||||
isVideoCaptureInError = webRtcPeerConnectionManager.capturerIsInError,
|
||||
isHD = webRtcPeerConnectionManager.currentCaptureFormat() is CaptureFormat.HD
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onAudioDevicesChange(mgr: WebRtcPeerConnectionManager) {
|
||||
val currentSoundDevice = mgr.audioManager.getCurrentSoundDevice()
|
||||
override fun onAudioDevicesChange() {
|
||||
val currentSoundDevice = webRtcPeerConnectionManager.callAudioManager.getCurrentSoundDevice()
|
||||
if (currentSoundDevice == CallAudioManager.SoundDevice.PHONE) {
|
||||
proximityManager.start()
|
||||
} else {
|
||||
|
@ -155,17 +109,17 @@ class VectorCallViewModel @AssistedInject constructor(
|
|||
|
||||
setState {
|
||||
copy(
|
||||
availableSoundDevices = mgr.audioManager.getAvailableSoundDevices(),
|
||||
availableSoundDevices = webRtcPeerConnectionManager.callAudioManager.getAvailableSoundDevices(),
|
||||
soundDevice = currentSoundDevice
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCameraChange(mgr: WebRtcPeerConnectionManager) {
|
||||
override fun onCameraChange() {
|
||||
setState {
|
||||
copy(
|
||||
canSwitchCamera = mgr.canSwitchCamera(),
|
||||
isFrontCamera = mgr.currentCameraType() == CameraType.FRONT
|
||||
canSwitchCamera = webRtcPeerConnectionManager.canSwitchCamera(),
|
||||
isFrontCamera = webRtcPeerConnectionManager.currentCameraType() == CameraType.FRONT
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -182,7 +136,7 @@ class VectorCallViewModel @AssistedInject constructor(
|
|||
|
||||
mxCall.addListener(callStateListener)
|
||||
|
||||
val currentSoundDevice = webRtcPeerConnectionManager.audioManager.getCurrentSoundDevice()
|
||||
val currentSoundDevice = webRtcPeerConnectionManager.callAudioManager.getCurrentSoundDevice()
|
||||
if (currentSoundDevice == CallAudioManager.SoundDevice.PHONE) {
|
||||
proximityManager.start()
|
||||
}
|
||||
|
@ -193,7 +147,7 @@ class VectorCallViewModel @AssistedInject constructor(
|
|||
callState = Success(mxCall.state),
|
||||
otherUserMatrixItem = item?.let { Success(it) } ?: Uninitialized,
|
||||
soundDevice = currentSoundDevice,
|
||||
availableSoundDevices = webRtcPeerConnectionManager.audioManager.getAvailableSoundDevices(),
|
||||
availableSoundDevices = webRtcPeerConnectionManager.callAudioManager.getAvailableSoundDevices(),
|
||||
isFrontCamera = webRtcPeerConnectionManager.currentCameraType() == CameraType.FRONT,
|
||||
canSwitchCamera = webRtcPeerConnectionManager.canSwitchCamera(),
|
||||
isHD = mxCall.isVideoCall && webRtcPeerConnectionManager.currentCaptureFormat() is CaptureFormat.HD
|
||||
|
@ -250,10 +204,10 @@ class VectorCallViewModel @AssistedInject constructor(
|
|||
Unit
|
||||
}
|
||||
is VectorCallViewActions.ChangeAudioDevice -> {
|
||||
webRtcPeerConnectionManager.audioManager.setCurrentSoundDevice(action.device)
|
||||
webRtcPeerConnectionManager.callAudioManager.setCurrentSoundDevice(action.device)
|
||||
setState {
|
||||
copy(
|
||||
soundDevice = webRtcPeerConnectionManager.audioManager.getCurrentSoundDevice()
|
||||
soundDevice = webRtcPeerConnectionManager.callAudioManager.getCurrentSoundDevice()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* 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 im.vector.app.features.call
|
||||
|
||||
import com.airbnb.mvrx.Async
|
||||
import com.airbnb.mvrx.MvRxState
|
||||
import com.airbnb.mvrx.Uninitialized
|
||||
import org.matrix.android.sdk.api.session.call.CallState
|
||||
import org.matrix.android.sdk.api.util.MatrixItem
|
||||
|
||||
data class VectorCallViewState(
|
||||
val callId: String? = null,
|
||||
val roomId: String = "",
|
||||
val isVideoCall: Boolean,
|
||||
val isAudioMuted: Boolean = false,
|
||||
val isVideoEnabled: Boolean = true,
|
||||
val isVideoCaptureInError: Boolean = false,
|
||||
val isHD: Boolean = false,
|
||||
val isFrontCamera: Boolean = true,
|
||||
val canSwitchCamera: Boolean = true,
|
||||
val soundDevice: CallAudioManager.SoundDevice = CallAudioManager.SoundDevice.PHONE,
|
||||
val availableSoundDevices: List<CallAudioManager.SoundDevice> = emptyList(),
|
||||
val otherUserMatrixItem: Async<MatrixItem> = Uninitialized,
|
||||
val callState: Async<CallState> = Uninitialized
|
||||
) : MvRxState
|
|
@ -23,6 +23,9 @@ import im.vector.app.ActiveSessionDataSource
|
|||
import im.vector.app.core.services.BluetoothHeadsetReceiver
|
||||
import im.vector.app.core.services.CallService
|
||||
import im.vector.app.core.services.WiredHeadsetStateReceiver
|
||||
import io.reactivex.disposables.Disposable
|
||||
import io.reactivex.subjects.PublishSubject
|
||||
import io.reactivex.subjects.ReplaySubject
|
||||
import org.matrix.android.sdk.api.MatrixCallback
|
||||
import org.matrix.android.sdk.api.extensions.tryThis
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
|
@ -35,9 +38,6 @@ import org.matrix.android.sdk.api.session.room.model.call.CallAnswerContent
|
|||
import org.matrix.android.sdk.api.session.room.model.call.CallCandidatesContent
|
||||
import org.matrix.android.sdk.api.session.room.model.call.CallHangupContent
|
||||
import org.matrix.android.sdk.api.session.room.model.call.CallInviteContent
|
||||
import io.reactivex.disposables.Disposable
|
||||
import io.reactivex.subjects.PublishSubject
|
||||
import io.reactivex.subjects.ReplaySubject
|
||||
import org.webrtc.AudioSource
|
||||
import org.webrtc.AudioTrack
|
||||
import org.webrtc.Camera1Enumerator
|
||||
|
@ -79,9 +79,9 @@ class WebRtcPeerConnectionManager @Inject constructor(
|
|||
|
||||
interface CurrentCallListener {
|
||||
fun onCurrentCallChange(call: MxCall?)
|
||||
fun onCaptureStateChanged(mgr: WebRtcPeerConnectionManager) {}
|
||||
fun onAudioDevicesChange(mgr: WebRtcPeerConnectionManager) {}
|
||||
fun onCameraChange(mgr: WebRtcPeerConnectionManager) {}
|
||||
fun onCaptureStateChanged() {}
|
||||
fun onAudioDevicesChange() {}
|
||||
fun onCameraChange() {}
|
||||
}
|
||||
|
||||
private val currentCallsListeners = emptyList<CurrentCallListener>().toMutableList()
|
||||
|
@ -93,9 +93,9 @@ class WebRtcPeerConnectionManager @Inject constructor(
|
|||
currentCallsListeners.remove(listener)
|
||||
}
|
||||
|
||||
val audioManager = CallAudioManager(context.applicationContext) {
|
||||
val callAudioManager = CallAudioManager(context.applicationContext) {
|
||||
currentCallsListeners.forEach {
|
||||
tryThis { it.onAudioDevicesChange(this) }
|
||||
tryThis { it.onAudioDevicesChange() }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -174,7 +174,7 @@ class WebRtcPeerConnectionManager @Inject constructor(
|
|||
set(value) {
|
||||
field = value
|
||||
currentCallsListeners.forEach {
|
||||
tryThis { it.onCaptureStateChanged(this) }
|
||||
tryThis { it.onCaptureStateChanged() }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -503,7 +503,7 @@ class WebRtcPeerConnectionManager @Inject constructor(
|
|||
// render local video in pip view
|
||||
localSurfaceRenderer.forEach {
|
||||
it.get()?.let { pipSurface ->
|
||||
pipSurface.setMirror(true)
|
||||
pipSurface.setMirror(this.cameraInUse?.type == CameraType.FRONT)
|
||||
// no need to check if already added, addSink is checking that
|
||||
currentCall?.localVideoTrack?.addSink(pipSurface)
|
||||
}
|
||||
|
@ -577,7 +577,7 @@ class WebRtcPeerConnectionManager @Inject constructor(
|
|||
fun close() {
|
||||
Timber.v("## VOIP WebRtcPeerConnectionManager close() >")
|
||||
CallService.onNoActiveCall(context)
|
||||
audioManager.stop()
|
||||
callAudioManager.stop()
|
||||
val callToEnd = currentCall
|
||||
currentCall = null
|
||||
// This must be done in this thread
|
||||
|
@ -631,7 +631,7 @@ class WebRtcPeerConnectionManager @Inject constructor(
|
|||
val createdCall = currentSession?.callSignalingService()?.createOutgoingCall(signalingRoomId, otherUserId, isVideoCall) ?: return
|
||||
val callContext = CallContext(createdCall)
|
||||
|
||||
audioManager.startForCall(createdCall)
|
||||
callAudioManager.startForCall(createdCall)
|
||||
currentCall = callContext
|
||||
|
||||
val name = currentSession?.getUser(createdCall.otherUserId)?.getBestName()
|
||||
|
@ -684,7 +684,7 @@ class WebRtcPeerConnectionManager @Inject constructor(
|
|||
|
||||
val callContext = CallContext(mxCall)
|
||||
currentCall = callContext
|
||||
audioManager.startForCall(mxCall)
|
||||
callAudioManager.startForCall(mxCall)
|
||||
executor.execute {
|
||||
callContext.remoteCandidateSource = ReplaySubject.create()
|
||||
}
|
||||
|
@ -740,8 +740,12 @@ class WebRtcPeerConnectionManager @Inject constructor(
|
|||
override fun onCameraSwitchDone(isFrontCamera: Boolean) {
|
||||
Timber.v("## VOIP onCameraSwitchDone isFront $isFrontCamera")
|
||||
cameraInUse = availableCamera.first { if (isFrontCamera) it.type == CameraType.FRONT else it.type == CameraType.BACK }
|
||||
localSurfaceRenderer.forEach {
|
||||
it.get()?.setMirror(isFrontCamera)
|
||||
}
|
||||
|
||||
currentCallsListeners.forEach {
|
||||
tryThis { it.onCameraChange(this@WebRtcPeerConnectionManager) }
|
||||
tryThis { it.onCameraChange() }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -767,7 +771,7 @@ class WebRtcPeerConnectionManager @Inject constructor(
|
|||
// videoCapturer?.stopCapture()
|
||||
videoCapturer?.changeCaptureFormat(format.width, format.height, format.fps)
|
||||
currentCaptureMode = format
|
||||
currentCallsListeners.forEach { tryThis { it.onCaptureStateChanged(this) } }
|
||||
currentCallsListeners.forEach { tryThis { it.onCaptureStateChanged() } }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -798,12 +802,12 @@ class WebRtcPeerConnectionManager @Inject constructor(
|
|||
Timber.v("## VOIP onWiredDeviceEvent $event")
|
||||
currentCall ?: return
|
||||
// sometimes we received un-wanted unplugged...
|
||||
audioManager.wiredStateChange(event)
|
||||
callAudioManager.wiredStateChange(event)
|
||||
}
|
||||
|
||||
fun onWirelessDeviceEvent(event: BluetoothHeadsetReceiver.BTHeadsetPlugEvent) {
|
||||
Timber.v("## VOIP onWirelessDeviceEvent $event")
|
||||
audioManager.bluetoothStateChange(event.plugged)
|
||||
callAudioManager.bluetoothStateChange(event.plugged)
|
||||
}
|
||||
|
||||
override fun onCallAnswerReceived(callAnswerContent: CallAnswerContent) {
|
||||
|
@ -858,6 +862,7 @@ class WebRtcPeerConnectionManager @Inject constructor(
|
|||
*/
|
||||
PeerConnection.PeerConnectionState.CONNECTED -> {
|
||||
callContext.mxCall.state = CallState.Connected(newState)
|
||||
callAudioManager.onCallConnected(callContext.mxCall)
|
||||
}
|
||||
/**
|
||||
* One or more of the ICE transports on the connection is in the "failed" state.
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
package im.vector.app.features.createdirect
|
||||
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.airbnb.mvrx.ActivityViewModelContext
|
||||
import com.airbnb.mvrx.MvRxViewModelFactory
|
||||
import com.airbnb.mvrx.ViewModelContext
|
||||
|
@ -23,13 +24,19 @@ import com.squareup.inject.assisted.Assisted
|
|||
import com.squareup.inject.assisted.AssistedInject
|
||||
import im.vector.app.core.extensions.exhaustive
|
||||
import im.vector.app.core.platform.VectorViewModel
|
||||
import im.vector.app.features.raw.wellknown.getElementWellknown
|
||||
import im.vector.app.features.raw.wellknown.isE2EByDefault
|
||||
import im.vector.app.features.userdirectory.PendingInvitee
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import org.matrix.android.sdk.api.raw.RawService
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
|
||||
import org.matrix.android.sdk.rx.rx
|
||||
|
||||
class CreateDirectRoomViewModel @AssistedInject constructor(@Assisted
|
||||
initialState: CreateDirectRoomViewState,
|
||||
private val rawService: RawService,
|
||||
private val session: Session)
|
||||
: VectorViewModel<CreateDirectRoomViewState, CreateDirectRoomAction, CreateDirectRoomViewEvents>(initialState) {
|
||||
|
||||
|
@ -54,22 +61,28 @@ class CreateDirectRoomViewModel @AssistedInject constructor(@Assisted
|
|||
}
|
||||
|
||||
private fun createRoomAndInviteSelectedUsers(invitees: Set<PendingInvitee>) {
|
||||
val roomParams = CreateRoomParams()
|
||||
.apply {
|
||||
invitees.forEach {
|
||||
when (it) {
|
||||
is PendingInvitee.UserPendingInvitee -> invitedUserIds.add(it.user.userId)
|
||||
is PendingInvitee.ThreePidPendingInvitee -> invite3pids.add(it.threePid)
|
||||
}.exhaustive
|
||||
}
|
||||
setDirectMessage()
|
||||
enableEncryptionIfInvitedUsersSupportIt = session.getHomeServerCapabilities().adminE2EByDefault
|
||||
}
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
val adminE2EByDefault = rawService.getElementWellknown(session.myUserId)
|
||||
?.isE2EByDefault()
|
||||
?: true
|
||||
|
||||
session.rx()
|
||||
.createRoom(roomParams)
|
||||
.execute {
|
||||
copy(createAndInviteState = it)
|
||||
}
|
||||
val roomParams = CreateRoomParams()
|
||||
.apply {
|
||||
invitees.forEach {
|
||||
when (it) {
|
||||
is PendingInvitee.UserPendingInvitee -> invitedUserIds.add(it.user.userId)
|
||||
is PendingInvitee.ThreePidPendingInvitee -> invite3pids.add(it.threePid)
|
||||
}.exhaustive
|
||||
}
|
||||
setDirectMessage()
|
||||
enableEncryptionIfInvitedUsersSupportIt = adminE2EByDefault
|
||||
}
|
||||
|
||||
session.rx()
|
||||
.createRoom(roomParams)
|
||||
.execute {
|
||||
copy(createAndInviteState = it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -87,13 +87,13 @@ class KeysBackupRestoreActivity : SimpleFragmentActivity() {
|
|||
viewModel.navigateEvent.observeEvent(this) { uxStateEvent ->
|
||||
when (uxStateEvent) {
|
||||
KeysBackupRestoreSharedViewModel.NAVIGATE_TO_RECOVER_WITH_KEY -> {
|
||||
addFragmentToBackstack(R.id.container, KeysBackupRestoreFromKeyFragment::class.java)
|
||||
addFragmentToBackstack(R.id.container, KeysBackupRestoreFromKeyFragment::class.java, allowStateLoss = true)
|
||||
}
|
||||
KeysBackupRestoreSharedViewModel.NAVIGATE_TO_SUCCESS -> {
|
||||
viewModel.keyVersionResult.value?.version?.let {
|
||||
KeysBackupBanner.onRecoverDoneForVersion(this, it)
|
||||
}
|
||||
replaceFragment(R.id.container, KeysBackupRestoreSuccessFragment::class.java)
|
||||
replaceFragment(R.id.container, KeysBackupRestoreSuccessFragment::class.java, allowStateLoss = true)
|
||||
}
|
||||
KeysBackupRestoreSharedViewModel.NAVIGATE_TO_4S -> {
|
||||
launch4SActivity()
|
||||
|
|
|
@ -21,6 +21,8 @@ package im.vector.app.features.crypto.keysrequest
|
|||
|
||||
import android.content.Context
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.date.DateFormatKind
|
||||
import im.vector.app.core.date.VectorDateFormatter
|
||||
import im.vector.app.features.popup.DefaultVectorAlert
|
||||
import im.vector.app.features.popup.PopupAlertManager
|
||||
import org.matrix.android.sdk.api.MatrixCallback
|
||||
|
@ -38,10 +40,6 @@ import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo
|
|||
import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap
|
||||
import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo
|
||||
import timber.log.Timber
|
||||
import java.text.DateFormat
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
|
@ -54,8 +52,11 @@ import javax.inject.Singleton
|
|||
*/
|
||||
|
||||
@Singleton
|
||||
class KeyRequestHandler @Inject constructor(private val context: Context, private val popupAlertManager: PopupAlertManager)
|
||||
: GossipingRequestListener,
|
||||
class KeyRequestHandler @Inject constructor(
|
||||
private val context: Context,
|
||||
private val popupAlertManager: PopupAlertManager,
|
||||
private val dateFormatter: VectorDateFormatter
|
||||
) : GossipingRequestListener,
|
||||
VerificationService.Listener {
|
||||
|
||||
private val alertsToRequests = HashMap<String, ArrayList<IncomingRoomKeyRequest>>()
|
||||
|
@ -156,16 +157,7 @@ class KeyRequestHandler @Inject constructor(private val context: Context, privat
|
|||
moreInfo.lastSeenIp
|
||||
}
|
||||
|
||||
val lastSeenTime = moreInfo.lastSeenTs?.let { ts ->
|
||||
val dateFormatTime = SimpleDateFormat("HH:mm:ss", Locale.getDefault())
|
||||
val date = Date(ts)
|
||||
|
||||
val time = dateFormatTime.format(date)
|
||||
val dateFormat = DateFormat.getDateInstance(DateFormat.SHORT, Locale.getDefault())
|
||||
|
||||
dateFormat.format(date) + ", " + time
|
||||
} ?: "-"
|
||||
|
||||
val lastSeenTime = dateFormatter.format(moreInfo.lastSeenTs, DateFormatKind.DEFAULT_DATE_AND_TIME)
|
||||
val lastSeenInfo = context.getString(R.string.devices_details_last_seen_format, lastSeenIp, lastSeenTime)
|
||||
dialogText = if (wasNewDevice) {
|
||||
context.getString(R.string.you_added_a_new_device_with_info, deviceName, lastSeenInfo)
|
||||
|
|
|
@ -20,8 +20,8 @@ import android.app.Activity
|
|||
import android.content.Context
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.core.content.edit
|
||||
import androidx.preference.PreferenceManager
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.di.DefaultSharedPreferences
|
||||
import im.vector.app.core.utils.openUrlInChromeCustomTab
|
||||
import im.vector.app.features.settings.VectorSettingsUrls
|
||||
|
||||
|
@ -31,7 +31,7 @@ private const val CURRENT_DISCLAIMER_VALUE = 2
|
|||
private const val SHARED_PREF_KEY = "LAST_DISCLAIMER_VERSION_VALUE"
|
||||
|
||||
fun showDisclaimerDialog(activity: Activity) {
|
||||
val sharedPrefs = PreferenceManager.getDefaultSharedPreferences(activity)
|
||||
val sharedPrefs = DefaultSharedPreferences.getInstance(activity)
|
||||
|
||||
if (sharedPrefs.getInt(SHARED_PREF_KEY, 0) < CURRENT_DISCLAIMER_VALUE) {
|
||||
sharedPrefs.edit {
|
||||
|
@ -52,7 +52,7 @@ fun showDisclaimerDialog(activity: Activity) {
|
|||
}
|
||||
|
||||
fun doNotShowDisclaimerDialog(context: Context) {
|
||||
val sharedPrefs = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
val sharedPrefs = DefaultSharedPreferences.getInstance(context)
|
||||
|
||||
sharedPrefs.edit {
|
||||
putInt(SHARED_PREF_KEY, CURRENT_DISCLAIMER_VALUE)
|
||||
|
|
|
@ -121,7 +121,7 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable, UnknownDeviceDet
|
|||
is HomeActivitySharedAction.CloseDrawer -> drawerLayout.closeDrawer(GravityCompat.START)
|
||||
is HomeActivitySharedAction.OpenGroup -> {
|
||||
drawerLayout.closeDrawer(GravityCompat.START)
|
||||
replaceFragment(R.id.homeDetailFragmentContainer, HomeDetailFragment::class.java)
|
||||
replaceFragment(R.id.homeDetailFragmentContainer, HomeDetailFragment::class.java, allowStateLoss = true)
|
||||
}
|
||||
}.exhaustive
|
||||
}
|
||||
|
|
|
@ -31,7 +31,6 @@ import im.vector.app.R
|
|||
import im.vector.app.core.extensions.exhaustive
|
||||
import im.vector.app.core.platform.VectorViewModel
|
||||
import im.vector.app.core.resources.StringProvider
|
||||
import im.vector.app.core.resources.UserPreferencesProvider
|
||||
import im.vector.app.core.utils.subscribeLogError
|
||||
import im.vector.app.features.call.WebRtcPeerConnectionManager
|
||||
import im.vector.app.features.command.CommandParser
|
||||
|
@ -40,9 +39,10 @@ import im.vector.app.features.crypto.verification.SupportedVerificationMethodsPr
|
|||
import im.vector.app.features.home.room.detail.composer.rainbow.RainbowGenerator
|
||||
import im.vector.app.features.home.room.detail.sticker.StickerPickerActionHandler
|
||||
import im.vector.app.features.home.room.detail.timeline.helper.RoomSummaryHolder
|
||||
import im.vector.app.features.home.room.detail.timeline.helper.TimelineDisplayableEvents
|
||||
import im.vector.app.features.home.room.detail.timeline.helper.TimelineSettingsFactory
|
||||
import im.vector.app.features.home.room.typing.TypingHelper
|
||||
import im.vector.app.features.powerlevel.PowerLevelsObservableFactory
|
||||
import im.vector.app.features.raw.wellknown.getElementWellknown
|
||||
import im.vector.app.features.settings.VectorLocale
|
||||
import im.vector.app.features.settings.VectorPreferences
|
||||
import io.reactivex.Observable
|
||||
|
@ -59,11 +59,12 @@ import org.matrix.android.sdk.api.MatrixPatterns
|
|||
import org.matrix.android.sdk.api.NoOpMatrixCallback
|
||||
import org.matrix.android.sdk.api.extensions.tryThis
|
||||
import org.matrix.android.sdk.api.query.QueryStringValue
|
||||
import org.matrix.android.sdk.api.raw.RawService
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import org.matrix.android.sdk.api.session.crypto.MXCryptoError
|
||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||
import org.matrix.android.sdk.api.session.events.model.isAttachmentMessage
|
||||
import org.matrix.android.sdk.api.session.events.model.LocalEcho
|
||||
import org.matrix.android.sdk.api.session.events.model.isAttachmentMessage
|
||||
import org.matrix.android.sdk.api.session.events.model.isTextMessage
|
||||
import org.matrix.android.sdk.api.session.events.model.toContent
|
||||
import org.matrix.android.sdk.api.session.events.model.toModel
|
||||
|
@ -86,7 +87,6 @@ import org.matrix.android.sdk.api.session.room.read.ReadService
|
|||
import org.matrix.android.sdk.api.session.room.send.UserDraft
|
||||
import org.matrix.android.sdk.api.session.room.timeline.Timeline
|
||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings
|
||||
import org.matrix.android.sdk.api.session.room.timeline.getTextEditableContent
|
||||
import org.matrix.android.sdk.api.session.widgets.model.Widget
|
||||
import org.matrix.android.sdk.api.session.widgets.model.WidgetType
|
||||
|
@ -105,39 +105,24 @@ import java.util.concurrent.atomic.AtomicBoolean
|
|||
|
||||
class RoomDetailViewModel @AssistedInject constructor(
|
||||
@Assisted private val initialState: RoomDetailViewState,
|
||||
userPreferencesProvider: UserPreferencesProvider,
|
||||
private val vectorPreferences: VectorPreferences,
|
||||
private val stringProvider: StringProvider,
|
||||
private val rainbowGenerator: RainbowGenerator,
|
||||
private val session: Session,
|
||||
private val rawService: RawService,
|
||||
private val supportedVerificationMethodsProvider: SupportedVerificationMethodsProvider,
|
||||
private val stickerPickerActionHandler: StickerPickerActionHandler,
|
||||
private val roomSummaryHolder: RoomSummaryHolder,
|
||||
private val typingHelper: TypingHelper,
|
||||
private val webRtcPeerConnectionManager: WebRtcPeerConnectionManager
|
||||
private val webRtcPeerConnectionManager: WebRtcPeerConnectionManager,
|
||||
timelineSettingsFactory: TimelineSettingsFactory
|
||||
) : VectorViewModel<RoomDetailViewState, RoomDetailAction, RoomDetailViewEvents>(initialState), Timeline.Listener {
|
||||
|
||||
private val room = session.getRoom(initialState.roomId)!!
|
||||
private val eventId = initialState.eventId
|
||||
private val invisibleEventsObservable = BehaviorRelay.create<RoomDetailAction.TimelineEventTurnsInvisible>()
|
||||
private val visibleEventsObservable = BehaviorRelay.create<RoomDetailAction.TimelineEventTurnsVisible>()
|
||||
private val timelineSettings = if (userPreferencesProvider.shouldShowHiddenEvents()) {
|
||||
TimelineSettings(30,
|
||||
filterEdits = false,
|
||||
filterRedacted = userPreferencesProvider.shouldShowRedactedMessages().not(),
|
||||
filterUseless = false,
|
||||
filterTypes = false,
|
||||
buildReadReceipts = userPreferencesProvider.shouldShowReadReceipts())
|
||||
} else {
|
||||
TimelineSettings(30,
|
||||
filterEdits = true,
|
||||
filterRedacted = userPreferencesProvider.shouldShowRedactedMessages().not(),
|
||||
filterUseless = true,
|
||||
filterTypes = true,
|
||||
allowedTypes = TimelineDisplayableEvents.DISPLAYABLE_TYPES,
|
||||
buildReadReceipts = userPreferencesProvider.shouldShowReadReceipts())
|
||||
}
|
||||
|
||||
private val timelineSettings = timelineSettingsFactory.create()
|
||||
private var timelineEvents = PublishRelay.create<List<TimelineEvent>>()
|
||||
val timeline = room.createTimeline(eventId, timelineSettings)
|
||||
|
||||
|
@ -349,7 +334,12 @@ class RoomDetailViewModel @AssistedInject constructor(
|
|||
val roomId: String = room.roomId
|
||||
val confId = roomId.substring(1, roomId.indexOf(":") - 1) + widgetSessionId.toLowerCase(VectorLocale.applicationLocale)
|
||||
|
||||
val jitsiDomain = session.getHomeServerCapabilities().preferredJitsiDomain ?: stringProvider.getString(R.string.preferred_jitsi_domain)
|
||||
val preferredJitsiDomain = tryThis {
|
||||
rawService.getElementWellknown(session.myUserId)
|
||||
?.jitsiServer
|
||||
?.preferredDomain
|
||||
}
|
||||
val jitsiDomain = preferredJitsiDomain ?: stringProvider.getString(R.string.preferred_jitsi_domain)
|
||||
|
||||
// We use the default element wrapper for this widget
|
||||
// https://github.com/vector-im/element-web/blob/develop/docs/jitsi-dev.md
|
||||
|
@ -896,13 +886,15 @@ class RoomDetailViewModel @AssistedInject constructor(
|
|||
}
|
||||
|
||||
private fun handleEventVisible(action: RoomDetailAction.TimelineEventTurnsVisible) {
|
||||
if (action.event.root.sendState.isSent()) { // ignore pending/local events
|
||||
visibleEventsObservable.accept(action)
|
||||
}
|
||||
// We need to update this with the related m.replace also (to move read receipt)
|
||||
action.event.annotations?.editSummary?.sourceEvents?.forEach {
|
||||
room.getTimeLineEvent(it)?.let { event ->
|
||||
visibleEventsObservable.accept(RoomDetailAction.TimelineEventTurnsVisible(event))
|
||||
viewModelScope.launch(Dispatchers.Default) {
|
||||
if (action.event.root.sendState.isSent()) { // ignore pending/local events
|
||||
visibleEventsObservable.accept(action)
|
||||
}
|
||||
// We need to update this with the related m.replace also (to move read receipt)
|
||||
action.event.annotations?.editSummary?.sourceEvents?.forEach {
|
||||
room.getTimeLineEvent(it)?.let { event ->
|
||||
visibleEventsObservable.accept(RoomDetailAction.TimelineEventTurnsVisible(event))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
package im.vector.app.features.home.room.detail.readreceipts
|
||||
|
||||
import com.airbnb.epoxy.TypedEpoxyController
|
||||
import im.vector.app.core.date.DateFormatKind
|
||||
import im.vector.app.core.date.VectorDateFormatter
|
||||
import im.vector.app.features.home.AvatarRenderer
|
||||
import im.vector.app.features.home.room.detail.timeline.item.ReadReceiptData
|
||||
|
@ -36,7 +37,7 @@ class DisplayReadReceiptsController @Inject constructor(private val dateFormatte
|
|||
|
||||
override fun buildModels(readReceipts: List<ReadReceiptData>) {
|
||||
readReceipts.forEach {
|
||||
val timestamp = dateFormatter.formatRelativeDateTime(it.timestamp)
|
||||
val timestamp = dateFormatter.format(it.timestamp, DateFormatKind.DEFAULT_DATE_AND_TIME)
|
||||
DisplayReadReceiptItem_()
|
||||
.id(it.userId)
|
||||
.matrixItem(it.toMatrixItem())
|
||||
|
|
|
@ -25,6 +25,7 @@ import androidx.recyclerview.widget.RecyclerView
|
|||
import com.airbnb.epoxy.EpoxyController
|
||||
import com.airbnb.epoxy.EpoxyModel
|
||||
import com.airbnb.epoxy.VisibilityState
|
||||
import im.vector.app.core.date.DateFormatKind
|
||||
import im.vector.app.core.date.VectorDateFormatter
|
||||
import im.vector.app.core.epoxy.LoadingItem_
|
||||
import im.vector.app.core.extensions.localDateTime
|
||||
|
@ -53,7 +54,6 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageImageInfoCon
|
|||
import org.matrix.android.sdk.api.session.room.model.message.MessageVideoContent
|
||||
import org.matrix.android.sdk.api.session.room.timeline.Timeline
|
||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||
import org.threeten.bp.LocalDateTime
|
||||
import javax.inject.Inject
|
||||
|
||||
class TimelineEventController @Inject constructor(private val dateFormatter: VectorDateFormatter,
|
||||
|
@ -333,13 +333,13 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
|
|||
) {
|
||||
requestModelBuild()
|
||||
}
|
||||
val daySeparatorItem = buildDaySeparatorItem(addDaySeparator, date)
|
||||
val daySeparatorItem = buildDaySeparatorItem(addDaySeparator, event.root.originServerTs)
|
||||
return CacheItemData(event.localId, event.root.eventId, eventModel, mergedHeaderModel, daySeparatorItem)
|
||||
}
|
||||
|
||||
private fun buildDaySeparatorItem(addDaySeparator: Boolean, date: LocalDateTime): DaySeparatorItem? {
|
||||
private fun buildDaySeparatorItem(addDaySeparator: Boolean, originServerTs: Long?): DaySeparatorItem? {
|
||||
return if (addDaySeparator) {
|
||||
val formattedDay = dateFormatter.formatMessageDay(date)
|
||||
val formattedDay = dateFormatter.format(originServerTs, DateFormatKind.TIMELINE_DAY_DIVIDER)
|
||||
DaySeparatorItem_().formattedDay(formattedDay).id(formattedDay)
|
||||
} else {
|
||||
null
|
||||
|
|
|
@ -36,7 +36,7 @@ sealed class EventSharedAction(@StringRes val titleRes: Int,
|
|||
EventSharedAction(R.string.message_add_reaction, R.drawable.ic_add_reaction)
|
||||
|
||||
data class Copy(val content: String) :
|
||||
EventSharedAction(R.string.copy, R.drawable.ic_copy)
|
||||
EventSharedAction(R.string.action_copy, R.drawable.ic_copy)
|
||||
|
||||
data class Edit(val eventId: String) :
|
||||
EventSharedAction(R.string.edit, R.drawable.ic_edit)
|
||||
|
|
|
@ -22,9 +22,6 @@ import com.airbnb.mvrx.Uninitialized
|
|||
import im.vector.app.core.extensions.canReact
|
||||
import im.vector.app.features.home.room.detail.timeline.item.MessageInformationData
|
||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
|
||||
/**
|
||||
* Quick reactions state
|
||||
|
@ -56,11 +53,7 @@ data class MessageActionState(
|
|||
|
||||
constructor(args: TimelineEventFragmentArgs) : this(roomId = args.roomId, eventId = args.eventId, informationData = args.informationData)
|
||||
|
||||
private val dateFormat = SimpleDateFormat("EEE, d MMM yyyy HH:mm", Locale.getDefault())
|
||||
|
||||
fun senderName(): String = informationData.memberName?.toString() ?: ""
|
||||
|
||||
fun time(): String? = timelineEvent()?.root?.originServerTs?.let { dateFormat.format(Date(it)) } ?: ""
|
||||
|
||||
fun canReact() = timelineEvent()?.canReact() == true && actionPermissions.canReact
|
||||
}
|
||||
|
|
|
@ -20,6 +20,8 @@ import com.airbnb.epoxy.TypedEpoxyController
|
|||
import com.airbnb.mvrx.Success
|
||||
import im.vector.app.EmojiCompatFontProvider
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.date.DateFormatKind
|
||||
import im.vector.app.core.date.VectorDateFormatter
|
||||
import im.vector.app.core.epoxy.bottomsheet.BottomSheetQuickReactionsItem
|
||||
import im.vector.app.core.epoxy.bottomsheet.bottomSheetActionItem
|
||||
import im.vector.app.core.epoxy.bottomsheet.bottomSheetMessagePreviewItem
|
||||
|
@ -40,13 +42,16 @@ import javax.inject.Inject
|
|||
class MessageActionsEpoxyController @Inject constructor(
|
||||
private val stringProvider: StringProvider,
|
||||
private val avatarRenderer: AvatarRenderer,
|
||||
private val fontProvider: EmojiCompatFontProvider
|
||||
private val fontProvider: EmojiCompatFontProvider,
|
||||
private val dateFormatter: VectorDateFormatter
|
||||
) : TypedEpoxyController<MessageActionState>() {
|
||||
|
||||
var listener: MessageActionsEpoxyControllerListener? = null
|
||||
|
||||
override fun buildModels(state: MessageActionState) {
|
||||
// Message preview
|
||||
val date = state.timelineEvent()?.root?.originServerTs
|
||||
val formattedDate = dateFormatter.format(date, DateFormatKind.MESSAGE_DETAIL)
|
||||
bottomSheetMessagePreviewItem {
|
||||
id("preview")
|
||||
avatarRenderer(avatarRenderer)
|
||||
|
@ -54,7 +59,7 @@ class MessageActionsEpoxyController @Inject constructor(
|
|||
movementMethod(createLinkMovementMethod(listener))
|
||||
userClicked { listener?.didSelectMenuAction(EventSharedAction.OpenUserProfile(state.informationData.senderId)) }
|
||||
body(state.messageBody.linkify(listener))
|
||||
time(state.time())
|
||||
time(formattedDate)
|
||||
}
|
||||
|
||||
// Send state
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue