mirror of
https://github.com/vector-im/element-android.git
synced 2024-11-16 02:05:06 +08:00
commit
9092b97fb8
@ -19,7 +19,6 @@ package im.vector.matrix.android.api.extensions
|
||||
import im.vector.matrix.android.api.comparators.DatedObjectComparators
|
||||
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.DeviceInfo
|
||||
import java.util.Collections
|
||||
|
||||
/* ==========================================================================================
|
||||
* MXDeviceInfo
|
||||
@ -29,6 +28,6 @@ fun MXDeviceInfo.getFingerprintHumanReadable() = fingerprint()
|
||||
?.chunked(4)
|
||||
?.joinToString(separator = " ")
|
||||
|
||||
fun List<DeviceInfo>.sortByLastSeen() {
|
||||
Collections.sort(this, DatedObjectComparators.descComparator)
|
||||
fun MutableList<DeviceInfo>.sortByLastSeen() {
|
||||
sortWith(DatedObjectComparators.descComparator)
|
||||
}
|
||||
|
@ -30,9 +30,9 @@ object MatrixLinkify {
|
||||
*
|
||||
* @param spannable the text in which the matrix items has to be clickable.
|
||||
*/
|
||||
fun addLinks(spannable: Spannable?, callback: MatrixPermalinkSpan.Callback?): Boolean {
|
||||
fun addLinks(spannable: Spannable, callback: MatrixPermalinkSpan.Callback?): Boolean {
|
||||
// sanity checks
|
||||
if (spannable.isNullOrEmpty()) {
|
||||
if (spannable.isEmpty()) {
|
||||
return false
|
||||
}
|
||||
val text = spannable.toString()
|
||||
|
@ -16,7 +16,6 @@
|
||||
|
||||
package im.vector.matrix.android.api.permalinks
|
||||
|
||||
import android.text.TextUtils
|
||||
import im.vector.matrix.android.api.session.events.model.Event
|
||||
|
||||
/**
|
||||
@ -48,7 +47,7 @@ object PermalinkFactory {
|
||||
* @return the permalink, or null in case of error
|
||||
*/
|
||||
fun createPermalink(id: String): String? {
|
||||
return if (TextUtils.isEmpty(id)) {
|
||||
return if (id.isEmpty()) {
|
||||
null
|
||||
} else MATRIX_TO_URL_BASE + escape(id)
|
||||
}
|
||||
@ -71,11 +70,11 @@ object PermalinkFactory {
|
||||
* @param url the universal link, Ex: "https://matrix.to/#/@benoit:matrix.org"
|
||||
* @return the id from the url, ex: "@benoit:matrix.org", or null if the url is not a permalink
|
||||
*/
|
||||
fun getLinkedId(url: String?): String? {
|
||||
val isSupported = url != null && url.startsWith(MATRIX_TO_URL_BASE)
|
||||
fun getLinkedId(url: String): String? {
|
||||
val isSupported = url.startsWith(MATRIX_TO_URL_BASE)
|
||||
|
||||
return if (isSupported) {
|
||||
url!!.substring(MATRIX_TO_URL_BASE.length)
|
||||
url.substring(MATRIX_TO_URL_BASE.length)
|
||||
} else null
|
||||
}
|
||||
|
||||
@ -86,6 +85,6 @@ object PermalinkFactory {
|
||||
* @return the escaped id
|
||||
*/
|
||||
private fun escape(id: String): String {
|
||||
return id.replace("/".toRegex(), "%2F")
|
||||
return id.replace("/", "%2F")
|
||||
}
|
||||
}
|
||||
|
@ -15,13 +15,11 @@
|
||||
*/
|
||||
package im.vector.matrix.android.api.pushrules
|
||||
|
||||
import android.text.TextUtils
|
||||
import im.vector.matrix.android.api.session.events.model.Event
|
||||
import im.vector.matrix.android.api.session.events.model.EventType
|
||||
import im.vector.matrix.android.api.session.events.model.toModel
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageContent
|
||||
import timber.log.Timber
|
||||
import java.util.regex.Pattern
|
||||
|
||||
class ContainsDisplayNameCondition : Condition(Kind.contains_display_name) {
|
||||
|
||||
@ -34,7 +32,7 @@ class ContainsDisplayNameCondition : Condition(Kind.contains_display_name) {
|
||||
}
|
||||
|
||||
fun isSatisfied(event: Event, displayName: String): Boolean {
|
||||
var message = when (event.type) {
|
||||
val message = when (event.type) {
|
||||
EventType.MESSAGE -> {
|
||||
event.content.toModel<MessageContent>()
|
||||
}
|
||||
@ -59,20 +57,18 @@ class ContainsDisplayNameCondition : Condition(Kind.contains_display_name) {
|
||||
*/
|
||||
fun caseInsensitiveFind(subString: String, longString: String): Boolean {
|
||||
// add sanity checks
|
||||
if (TextUtils.isEmpty(subString) || TextUtils.isEmpty(longString)) {
|
||||
if (subString.isEmpty() || longString.isEmpty()) {
|
||||
return false
|
||||
}
|
||||
|
||||
var res = false
|
||||
|
||||
try {
|
||||
val pattern = Pattern.compile("(\\W|^)" + Pattern.quote(subString) + "(\\W|$)", Pattern.CASE_INSENSITIVE)
|
||||
res = pattern.matcher(longString).find()
|
||||
val regex = Regex("(\\W|^)" + Regex.escape(subString) + "(\\W|$)", RegexOption.IGNORE_CASE)
|
||||
return regex.containsMatchIn(longString)
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "## caseInsensitiveFind() : failed")
|
||||
}
|
||||
|
||||
return res
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,7 +16,6 @@
|
||||
|
||||
package im.vector.matrix.android.api.session.events.model
|
||||
|
||||
import android.text.TextUtils
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import im.vector.matrix.android.api.session.crypto.MXCryptoError
|
||||
@ -35,18 +34,16 @@ typealias Content = JsonDict
|
||||
* This methods is a facility method to map a json content to a model.
|
||||
*/
|
||||
inline fun <reified T> Content?.toModel(catchError: Boolean = true): T? {
|
||||
return this?.let {
|
||||
val moshi = MoshiProvider.providesMoshi()
|
||||
val moshiAdapter = moshi.adapter(T::class.java)
|
||||
return try {
|
||||
moshiAdapter.fromJsonValue(it)
|
||||
} catch (e: Exception) {
|
||||
if (catchError) {
|
||||
Timber.e(e, "To model failed : $e")
|
||||
null
|
||||
} else {
|
||||
throw e
|
||||
}
|
||||
val moshi = MoshiProvider.providesMoshi()
|
||||
val moshiAdapter = moshi.adapter(T::class.java)
|
||||
return try {
|
||||
moshiAdapter.fromJsonValue(this)
|
||||
} catch (e: Exception) {
|
||||
if (catchError) {
|
||||
Timber.e(e, "To model failed : $e")
|
||||
null
|
||||
} else {
|
||||
throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -55,12 +52,10 @@ inline fun <reified T> Content?.toModel(catchError: Boolean = true): T? {
|
||||
* This methods is a facility method to map a model to a json Content
|
||||
*/
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
inline fun <reified T> T?.toContent(): Content? {
|
||||
return this?.let {
|
||||
val moshi = MoshiProvider.providesMoshi()
|
||||
val moshiAdapter = moshi.adapter(T::class.java)
|
||||
return moshiAdapter.toJsonValue(it) as Content
|
||||
}
|
||||
inline fun <reified T> T.toContent(): Content {
|
||||
val moshi = MoshiProvider.providesMoshi()
|
||||
val moshiAdapter = moshi.adapter(T::class.java)
|
||||
return moshiAdapter.toJsonValue(this) as Content
|
||||
}
|
||||
|
||||
/**
|
||||
@ -106,7 +101,7 @@ data class Event(
|
||||
* @return true if this event is encrypted.
|
||||
*/
|
||||
fun isEncrypted(): Boolean {
|
||||
return TextUtils.equals(type, EventType.ENCRYPTED)
|
||||
return type == EventType.ENCRYPTED
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -16,11 +16,9 @@
|
||||
|
||||
package im.vector.matrix.android.api.session.room.model
|
||||
|
||||
import android.text.TextUtils
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import im.vector.matrix.android.api.session.events.model.EventType
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Class representing the EventType.EVENT_TYPE_STATE_ROOM_POWER_LEVELS state event content.
|
||||
@ -45,14 +43,8 @@ data class PowerLevels(
|
||||
* @param userId the user id
|
||||
* @return the power level
|
||||
*/
|
||||
fun getUserPowerLevel(userId: String): Int {
|
||||
// sanity check
|
||||
if (!TextUtils.isEmpty(userId)) {
|
||||
val powerLevel = users[userId]
|
||||
return powerLevel ?: usersDefault
|
||||
}
|
||||
|
||||
return usersDefault
|
||||
fun getUserPowerLevel(userId: String): Int {
|
||||
return users.getOrElse(userId) { usersDefault }
|
||||
}
|
||||
|
||||
/**
|
||||
@ -61,10 +53,8 @@ data class PowerLevels(
|
||||
* @param userId the user
|
||||
* @param powerLevel the new power level
|
||||
*/
|
||||
fun setUserPowerLevel(userId: String?, powerLevel: Int) {
|
||||
if (null != userId) {
|
||||
users[userId] = Integer.valueOf(powerLevel)
|
||||
}
|
||||
fun setUserPowerLevel(userId: String, powerLevel: Int) {
|
||||
users[userId] = powerLevel
|
||||
}
|
||||
|
||||
/**
|
||||
@ -74,8 +64,8 @@ data class PowerLevels(
|
||||
* @param userId the user id
|
||||
* @return true if the user can send the event
|
||||
*/
|
||||
fun maySendEventOfType(eventTypeString: String, userId: String): Boolean {
|
||||
return if (!TextUtils.isEmpty(eventTypeString) && !TextUtils.isEmpty(userId)) {
|
||||
fun maySendEventOfType(eventTypeString: String, userId: String): Boolean {
|
||||
return if (eventTypeString.isNotEmpty() && userId.isNotEmpty()) {
|
||||
getUserPowerLevel(userId) >= minimumPowerLevelForSendingEventAsMessage(eventTypeString)
|
||||
} else false
|
||||
}
|
||||
@ -86,8 +76,8 @@ data class PowerLevels(
|
||||
* @param userId the user id
|
||||
* @return true if the user can send a room message
|
||||
*/
|
||||
fun maySendMessage(userId: String): Boolean {
|
||||
return maySendEventOfType(EventType.MESSAGE, userId)
|
||||
fun maySendMessage(userId: String): Boolean {
|
||||
return maySendEventOfType(EventType.MESSAGE, userId)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -97,7 +87,7 @@ data class PowerLevels(
|
||||
* @param eventTypeString the type of event (in Event.EVENT_TYPE_XXX values)
|
||||
* @return the required minimum power level.
|
||||
*/
|
||||
fun minimumPowerLevelForSendingEventAsMessage(eventTypeString: String?): Int {
|
||||
fun minimumPowerLevelForSendingEventAsMessage(eventTypeString: String?): Int {
|
||||
return events[eventTypeString] ?: eventsDefault
|
||||
}
|
||||
|
||||
@ -108,7 +98,7 @@ data class PowerLevels(
|
||||
* @param eventTypeString the type of event (in Event.EVENT_TYPE_STATE_ values).
|
||||
* @return the required minimum power level.
|
||||
*/
|
||||
fun minimumPowerLevelForSendingEventAsStateEvent(eventTypeString: String?): Int {
|
||||
fun minimumPowerLevelForSendingEventAsStateEvent(eventTypeString: String?): Int {
|
||||
return events[eventTypeString] ?: stateDefault
|
||||
}
|
||||
|
||||
@ -118,18 +108,14 @@ data class PowerLevels(
|
||||
* @param key the notification key
|
||||
* @return the level
|
||||
*/
|
||||
fun notificationLevel(key: String?): Int {
|
||||
if (null != key && notifications.containsKey(key)) {
|
||||
val valAsVoid = notifications[key]
|
||||
fun notificationLevel(key: String): Int {
|
||||
val valAsVoid = notifications[key] ?: return 50
|
||||
|
||||
// the first implementation was a string value
|
||||
return if (valAsVoid is String) {
|
||||
Integer.parseInt(valAsVoid)
|
||||
} else {
|
||||
valAsVoid as Int
|
||||
}
|
||||
// the first implementation was a string value
|
||||
return if (valAsVoid is String) {
|
||||
valAsVoid.toInt()
|
||||
} else {
|
||||
valAsVoid as Int
|
||||
}
|
||||
|
||||
return 50
|
||||
}
|
||||
}
|
||||
|
@ -145,15 +145,7 @@ class CreateRoomParams {
|
||||
*/
|
||||
fun setHistoryVisibility(historyVisibility: RoomHistoryVisibility?) {
|
||||
// Remove the existing value if any.
|
||||
if (initialStates != null && !initialStates!!.isEmpty()) {
|
||||
val newInitialStates = ArrayList<Event>()
|
||||
for (event in initialStates!!) {
|
||||
if (event.getClearType() != EventType.STATE_HISTORY_VISIBILITY) {
|
||||
newInitialStates.add(event)
|
||||
}
|
||||
}
|
||||
initialStates = newInitialStates
|
||||
}
|
||||
initialStates?.removeAll { it.getClearType() == EventType.STATE_HISTORY_VISIBILITY }
|
||||
|
||||
if (historyVisibility != null) {
|
||||
val contentMap = HashMap<String, RoomHistoryVisibility>()
|
||||
|
@ -359,29 +359,16 @@ internal class DefaultCryptoService @Inject constructor(
|
||||
*/
|
||||
override fun setDevicesKnown(devices: List<MXDeviceInfo>, callback: MatrixCallback<Unit>?) {
|
||||
// build a devices map
|
||||
val devicesIdListByUserId = HashMap<String, List<String>>()
|
||||
val devicesIdListByUserId = devices.groupBy({ it.userId }, { it.deviceId })
|
||||
|
||||
for (di in devices) {
|
||||
var deviceIdsList: MutableList<String>? = devicesIdListByUserId[di.userId]?.toMutableList()
|
||||
|
||||
if (null == deviceIdsList) {
|
||||
deviceIdsList = ArrayList()
|
||||
devicesIdListByUserId[di.userId] = deviceIdsList
|
||||
}
|
||||
deviceIdsList.add(di.deviceId)
|
||||
}
|
||||
|
||||
val userIds = devicesIdListByUserId.keys
|
||||
|
||||
for (userId in userIds) {
|
||||
for ((userId, deviceIds) in devicesIdListByUserId) {
|
||||
val storedDeviceIDs = cryptoStore.getUserDevices(userId)
|
||||
|
||||
// sanity checks
|
||||
if (null != storedDeviceIDs) {
|
||||
var isUpdated = false
|
||||
val deviceIds = devicesIdListByUserId[userId]
|
||||
|
||||
deviceIds?.forEach { deviceId ->
|
||||
deviceIds.forEach { deviceId ->
|
||||
val device = storedDeviceIDs[deviceId]
|
||||
|
||||
// assume if the device is either verified or blocked
|
||||
@ -549,16 +536,10 @@ internal class DefaultCryptoService @Inject constructor(
|
||||
val t0 = System.currentTimeMillis()
|
||||
Timber.v("## encryptEventContent() starts")
|
||||
runCatching {
|
||||
safeAlgorithm.encryptEventContent(eventContent, eventType, userIds)
|
||||
}
|
||||
.fold(
|
||||
{
|
||||
Timber.v("## encryptEventContent() : succeeds after ${System.currentTimeMillis() - t0} ms")
|
||||
callback.onSuccess(MXEncryptEventContentResult(it, EventType.ENCRYPTED))
|
||||
},
|
||||
{ callback.onFailure(it) }
|
||||
|
||||
)
|
||||
val content = safeAlgorithm.encryptEventContent(eventContent, eventType, userIds)
|
||||
Timber.v("## encryptEventContent() : succeeds after ${System.currentTimeMillis() - t0} ms")
|
||||
MXEncryptEventContentResult(content, EventType.ENCRYPTED)
|
||||
}.foldToCallback(callback)
|
||||
} else {
|
||||
val algorithm = getEncryptionAlgorithm(roomId)
|
||||
val reason = String.format(MXCryptoError.UNABLE_TO_ENCRYPT_REASON,
|
||||
@ -776,7 +757,7 @@ internal class DefaultCryptoService @Inject constructor(
|
||||
GlobalScope.launch(coroutineDispatchers.main) {
|
||||
runCatching {
|
||||
exportRoomKeys(password, MXMegolmExportEncryption.DEFAULT_ITERATION_COUNT)
|
||||
}.fold(callback::onSuccess, callback::onFailure)
|
||||
}.foldToCallback(callback)
|
||||
}
|
||||
}
|
||||
|
||||
@ -812,8 +793,8 @@ internal class DefaultCryptoService @Inject constructor(
|
||||
progressListener: ProgressListener?,
|
||||
callback: MatrixCallback<ImportRoomKeysResult>) {
|
||||
GlobalScope.launch(coroutineDispatchers.main) {
|
||||
withContext(coroutineDispatchers.crypto) {
|
||||
Try {
|
||||
runCatching {
|
||||
withContext(coroutineDispatchers.crypto) {
|
||||
Timber.v("## importRoomKeys starts")
|
||||
|
||||
val t0 = System.currentTimeMillis()
|
||||
@ -860,19 +841,14 @@ internal class DefaultCryptoService @Inject constructor(
|
||||
fun checkUnknownDevices(userIds: List<String>, callback: MatrixCallback<Unit>) {
|
||||
// force the refresh to ensure that the devices list is up-to-date
|
||||
GlobalScope.launch(coroutineDispatchers.crypto) {
|
||||
runCatching { deviceListManager.downloadKeys(userIds, true) }
|
||||
.fold(
|
||||
{
|
||||
val unknownDevices = getUnknownDevices(it)
|
||||
if (unknownDevices.map.isEmpty()) {
|
||||
callback.onSuccess(Unit)
|
||||
} else {
|
||||
// trigger an an unknown devices exception
|
||||
callback.onFailure(Failure.CryptoError(MXCryptoError.UnknownDevice(unknownDevices)))
|
||||
}
|
||||
},
|
||||
{ callback.onFailure(it) }
|
||||
)
|
||||
runCatching {
|
||||
val keys = deviceListManager.downloadKeys(userIds, true)
|
||||
val unknownDevices = getUnknownDevices(keys)
|
||||
if (unknownDevices.map.isNotEmpty()) {
|
||||
// trigger an an unknown devices exception
|
||||
throw Failure.CryptoError(MXCryptoError.UnknownDevice(unknownDevices))
|
||||
}
|
||||
}.foldToCallback(callback)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -17,7 +17,6 @@
|
||||
|
||||
package im.vector.matrix.android.internal.crypto
|
||||
|
||||
import android.text.TextUtils
|
||||
import im.vector.matrix.android.api.MatrixPatterns
|
||||
import im.vector.matrix.android.api.auth.data.Credentials
|
||||
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
|
||||
@ -27,7 +26,6 @@ import im.vector.matrix.android.internal.crypto.tasks.DownloadKeysForUsersTask
|
||||
import im.vector.matrix.android.internal.session.SessionScope
|
||||
import im.vector.matrix.android.internal.session.sync.SyncTokenStore
|
||||
import timber.log.Timber
|
||||
import java.util.*
|
||||
import javax.inject.Inject
|
||||
|
||||
// Legacy name: MXDeviceList
|
||||
@ -39,13 +37,12 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
|
||||
private val downloadKeysForUsersTask: DownloadKeysForUsersTask) {
|
||||
|
||||
// HS not ready for retry
|
||||
private val notReadyToRetryHS = HashSet<String>()
|
||||
private val notReadyToRetryHS = mutableSetOf<String>()
|
||||
|
||||
init {
|
||||
var isUpdated = false
|
||||
val deviceTrackingStatuses = cryptoStore.getDeviceTrackingStatuses().toMutableMap()
|
||||
for (userId in deviceTrackingStatuses.keys) {
|
||||
val status = deviceTrackingStatuses[userId]!!
|
||||
for ((userId, status) in deviceTrackingStatuses) {
|
||||
if (TRACKING_STATUS_DOWNLOAD_IN_PROGRESS == status || TRACKING_STATUS_UNREACHABLE_SERVER == status) {
|
||||
// if a download was in progress when we got shut down, it isn't any more.
|
||||
deviceTrackingStatuses[userId] = TRACKING_STATUS_PENDING_DOWNLOAD
|
||||
@ -66,7 +63,7 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
|
||||
private fun canRetryKeysDownload(userId: String): Boolean {
|
||||
var res = false
|
||||
|
||||
if (!TextUtils.isEmpty(userId) && userId.contains(":")) {
|
||||
if (':' in userId) {
|
||||
try {
|
||||
synchronized(notReadyToRetryHS) {
|
||||
res = !notReadyToRetryHS.contains(userId.substring(userId.lastIndexOf(":") + 1))
|
||||
@ -119,27 +116,23 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
|
||||
* @param changed the user ids list which have new devices
|
||||
* @param left the user ids list which left a room
|
||||
*/
|
||||
fun handleDeviceListsChanges(changed: List<String>?, left: List<String>?) {
|
||||
fun handleDeviceListsChanges(changed: Collection<String>, left: Collection<String>) {
|
||||
var isUpdated = false
|
||||
val deviceTrackingStatuses = cryptoStore.getDeviceTrackingStatuses().toMutableMap()
|
||||
|
||||
if (changed?.isNotEmpty() == true) {
|
||||
for (userId in changed) {
|
||||
if (deviceTrackingStatuses.containsKey(userId)) {
|
||||
Timber.v("## invalidateUserDeviceList() : Marking device list outdated for $userId")
|
||||
deviceTrackingStatuses[userId] = TRACKING_STATUS_PENDING_DOWNLOAD
|
||||
isUpdated = true
|
||||
}
|
||||
for (userId in changed) {
|
||||
if (deviceTrackingStatuses.containsKey(userId)) {
|
||||
Timber.v("## invalidateUserDeviceList() : Marking device list outdated for $userId")
|
||||
deviceTrackingStatuses[userId] = TRACKING_STATUS_PENDING_DOWNLOAD
|
||||
isUpdated = true
|
||||
}
|
||||
}
|
||||
|
||||
if (left?.isNotEmpty() == true) {
|
||||
for (userId in left) {
|
||||
if (deviceTrackingStatuses.containsKey(userId)) {
|
||||
Timber.v("## invalidateUserDeviceList() : No longer tracking device list for $userId")
|
||||
deviceTrackingStatuses[userId] = TRACKING_STATUS_NOT_TRACKED
|
||||
isUpdated = true
|
||||
}
|
||||
for (userId in left) {
|
||||
if (deviceTrackingStatuses.containsKey(userId)) {
|
||||
Timber.v("## invalidateUserDeviceList() : No longer tracking device list for $userId")
|
||||
deviceTrackingStatuses[userId] = TRACKING_STATUS_NOT_TRACKED
|
||||
isUpdated = true
|
||||
}
|
||||
}
|
||||
|
||||
@ -153,7 +146,7 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
|
||||
* + update
|
||||
*/
|
||||
fun invalidateAllDeviceLists() {
|
||||
handleDeviceListsChanges(ArrayList(cryptoStore.getDeviceTrackingStatuses().keys), null)
|
||||
handleDeviceListsChanges(cryptoStore.getDeviceTrackingStatuses().keys, emptyList())
|
||||
}
|
||||
|
||||
/**
|
||||
@ -163,9 +156,7 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
|
||||
*/
|
||||
private fun onKeysDownloadFailed(userIds: List<String>) {
|
||||
val deviceTrackingStatuses = cryptoStore.getDeviceTrackingStatuses().toMutableMap()
|
||||
for (userId in userIds) {
|
||||
deviceTrackingStatuses[userId] = TRACKING_STATUS_PENDING_DOWNLOAD
|
||||
}
|
||||
userIds.associateWithTo(deviceTrackingStatuses) { TRACKING_STATUS_PENDING_DOWNLOAD }
|
||||
cryptoStore.saveDeviceTrackingStatuses(deviceTrackingStatuses)
|
||||
}
|
||||
|
||||
@ -177,21 +168,15 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
|
||||
*/
|
||||
private fun onKeysDownloadSucceed(userIds: List<String>, failures: Map<String, Map<String, Any>>?): MXUsersDevicesMap<MXDeviceInfo> {
|
||||
if (failures != null) {
|
||||
val keys = failures.keys
|
||||
for (k in keys) {
|
||||
val value = failures[k]
|
||||
if (value!!.containsKey("status")) {
|
||||
val statusCodeAsVoid = value["status"]
|
||||
var statusCode = 0
|
||||
if (statusCodeAsVoid is Double) {
|
||||
statusCode = statusCodeAsVoid.toInt()
|
||||
} else if (statusCodeAsVoid is Int) {
|
||||
statusCode = statusCodeAsVoid.toInt()
|
||||
}
|
||||
if (statusCode == 503) {
|
||||
synchronized(notReadyToRetryHS) {
|
||||
notReadyToRetryHS.add(k)
|
||||
}
|
||||
for ((k, value) in failures) {
|
||||
val statusCode = when (val status = value["status"]) {
|
||||
is Double -> status.toInt()
|
||||
is Int -> status.toInt()
|
||||
else -> 0
|
||||
}
|
||||
if (statusCode == 503) {
|
||||
synchronized(notReadyToRetryHS) {
|
||||
notReadyToRetryHS.add(k)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -228,11 +213,9 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
|
||||
/**
|
||||
* Download the device keys for a list of users and stores the keys in the MXStore.
|
||||
* It must be called in getEncryptingThreadHandler() thread.
|
||||
* The callback is called in the UI thread.
|
||||
*
|
||||
* @param userIds The users to fetch.
|
||||
* @param forceDownload Always download the keys even if cached.
|
||||
* @param callback the asynchronous callback
|
||||
*/
|
||||
suspend fun downloadKeys(userIds: List<String>?, forceDownload: Boolean): MXUsersDevicesMap<MXDeviceInfo> {
|
||||
Timber.v("## downloadKeys() : forceDownload $forceDownload : $userIds")
|
||||
@ -270,7 +253,7 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
|
||||
Timber.v("## downloadKeys() : starts")
|
||||
val t0 = System.currentTimeMillis()
|
||||
val result = doKeyDownloadForUsers(downloadUsers)
|
||||
Timber.v("## downloadKeys() : doKeyDownloadForUsers succeeds after " + (System.currentTimeMillis() - t0) + " ms")
|
||||
Timber.v("## downloadKeys() : doKeyDownloadForUsers succeeds after ${System.currentTimeMillis() - t0} ms")
|
||||
result.also {
|
||||
it.addEntriesFromMap(stored)
|
||||
}
|
||||
@ -303,16 +286,14 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
|
||||
val devices = response.deviceKeys?.get(userId)
|
||||
Timber.v("## doKeyDownloadForUsers() : Got keys for $userId : $devices")
|
||||
if (devices != null) {
|
||||
val mutableDevices = HashMap(devices)
|
||||
val deviceIds = ArrayList(mutableDevices.keys)
|
||||
for (deviceId in deviceIds) {
|
||||
val mutableDevices = devices.toMutableMap()
|
||||
for ((deviceId, deviceInfo) in devices) {
|
||||
// Get the potential previously store device keys for this device
|
||||
val previouslyStoredDeviceKeys = cryptoStore.getUserDevice(deviceId, userId)
|
||||
val deviceInfo = mutableDevices[deviceId]
|
||||
|
||||
// in some race conditions (like unit tests)
|
||||
// the self device must be seen as verified
|
||||
if (TextUtils.equals(deviceInfo!!.deviceId, credentials.deviceId) && TextUtils.equals(userId, credentials.userId)) {
|
||||
if (deviceInfo.deviceId == credentials.deviceId && userId == credentials.userId) {
|
||||
deviceInfo.verified = MXDeviceInfo.DEVICE_VERIFICATION_VERIFIED
|
||||
}
|
||||
// Validate received keys
|
||||
@ -365,13 +346,13 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
|
||||
}
|
||||
|
||||
// Check that the user_id and device_id in the received deviceKeys are correct
|
||||
if (!TextUtils.equals(deviceKeys.userId, userId)) {
|
||||
Timber.e("## validateDeviceKeys() : Mismatched user_id " + deviceKeys.userId + " from " + userId + ":" + deviceId)
|
||||
if (deviceKeys.userId != userId) {
|
||||
Timber.e("## validateDeviceKeys() : Mismatched user_id ${deviceKeys.userId} from $userId:$deviceId")
|
||||
return false
|
||||
}
|
||||
|
||||
if (!TextUtils.equals(deviceKeys.deviceId, deviceId)) {
|
||||
Timber.e("## validateDeviceKeys() : Mismatched device_id " + deviceKeys.deviceId + " from " + userId + ":" + deviceId)
|
||||
if (deviceKeys.deviceId != deviceId) {
|
||||
Timber.e("## validateDeviceKeys() : Mismatched device_id ${deviceKeys.deviceId} from $userId:$deviceId")
|
||||
return false
|
||||
}
|
||||
|
||||
@ -379,21 +360,21 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
|
||||
val signKey = deviceKeys.keys?.get(signKeyId)
|
||||
|
||||
if (null == signKey) {
|
||||
Timber.e("## validateDeviceKeys() : Device " + userId + ":" + deviceKeys.deviceId + " has no ed25519 key")
|
||||
Timber.e("## validateDeviceKeys() : Device $userId:${deviceKeys.deviceId} has no ed25519 key")
|
||||
return false
|
||||
}
|
||||
|
||||
val signatureMap = deviceKeys.signatures?.get(userId)
|
||||
|
||||
if (null == signatureMap) {
|
||||
Timber.e("## validateDeviceKeys() : Device " + userId + ":" + deviceKeys.deviceId + " has no map for " + userId)
|
||||
Timber.e("## validateDeviceKeys() : Device $userId:${deviceKeys.deviceId} has no map for $userId")
|
||||
return false
|
||||
}
|
||||
|
||||
val signature = signatureMap[signKeyId]
|
||||
|
||||
if (null == signature) {
|
||||
Timber.e("## validateDeviceKeys() : Device " + userId + ":" + deviceKeys.deviceId + " is not signed")
|
||||
Timber.e("## validateDeviceKeys() : Device $userId:${deviceKeys.deviceId} is not signed")
|
||||
return false
|
||||
}
|
||||
|
||||
@ -414,7 +395,7 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
|
||||
}
|
||||
|
||||
if (null != previouslyStoredDeviceKeys) {
|
||||
if (!TextUtils.equals(previouslyStoredDeviceKeys.fingerprint(), signKey)) {
|
||||
if (previouslyStoredDeviceKeys.fingerprint() != signKey) {
|
||||
// This should only happen if the list has been MITMed; we are
|
||||
// best off sticking with the original keys.
|
||||
//
|
||||
@ -424,7 +405,7 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
|
||||
+ previouslyStoredDeviceKeys.fingerprint() + " -> " + signKey)
|
||||
|
||||
Timber.e("## validateDeviceKeys() : $previouslyStoredDeviceKeys -> $deviceKeys")
|
||||
Timber.e("## validateDeviceKeys() : " + previouslyStoredDeviceKeys.keys + " -> " + deviceKeys.keys)
|
||||
Timber.e("## validateDeviceKeys() : ${previouslyStoredDeviceKeys.keys} -> ${deviceKeys.keys}")
|
||||
|
||||
return false
|
||||
}
|
||||
@ -438,27 +419,18 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
|
||||
* This method must be called on getEncryptingThreadHandler() thread.
|
||||
*/
|
||||
suspend fun refreshOutdatedDeviceLists() {
|
||||
val users = ArrayList<String>()
|
||||
|
||||
val deviceTrackingStatuses = cryptoStore.getDeviceTrackingStatuses().toMutableMap()
|
||||
|
||||
for (userId in deviceTrackingStatuses.keys) {
|
||||
if (TRACKING_STATUS_PENDING_DOWNLOAD == deviceTrackingStatuses[userId]) {
|
||||
users.add(userId)
|
||||
}
|
||||
val users = deviceTrackingStatuses.keys.filterTo(mutableListOf()) { userId ->
|
||||
TRACKING_STATUS_PENDING_DOWNLOAD == deviceTrackingStatuses[userId]
|
||||
}
|
||||
|
||||
if (users.size == 0) {
|
||||
if (users.isEmpty()) {
|
||||
return
|
||||
}
|
||||
|
||||
// update the statuses
|
||||
for (userId in users) {
|
||||
val status = deviceTrackingStatuses[userId]
|
||||
if (null != status && TRACKING_STATUS_PENDING_DOWNLOAD == status) {
|
||||
deviceTrackingStatuses.put(userId, TRACKING_STATUS_DOWNLOAD_IN_PROGRESS)
|
||||
}
|
||||
}
|
||||
users.associateWithTo(deviceTrackingStatuses) { TRACKING_STATUS_DOWNLOAD_IN_PROGRESS }
|
||||
|
||||
cryptoStore.saveDeviceTrackingStatuses(deviceTrackingStatuses)
|
||||
runCatching {
|
||||
|
@ -16,7 +16,6 @@
|
||||
|
||||
package im.vector.matrix.android.internal.crypto
|
||||
|
||||
import android.text.TextUtils
|
||||
import im.vector.matrix.android.api.auth.data.Credentials
|
||||
import im.vector.matrix.android.api.session.crypto.keyshare.RoomKeysRequestListener
|
||||
import im.vector.matrix.android.api.session.events.model.Event
|
||||
@ -25,7 +24,6 @@ import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyShare
|
||||
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
|
||||
import im.vector.matrix.android.internal.session.SessionScope
|
||||
import timber.log.Timber
|
||||
import java.util.*
|
||||
import javax.inject.Inject
|
||||
import kotlin.collections.ArrayList
|
||||
|
||||
@ -58,7 +56,7 @@ internal class IncomingRoomKeyRequestManager @Inject constructor(
|
||||
when (roomKeyShare?.action) {
|
||||
RoomKeyShare.ACTION_SHARE_REQUEST -> receivedRoomKeyRequests.add(IncomingRoomKeyRequest(event))
|
||||
RoomKeyShare.ACTION_SHARE_CANCELLATION -> receivedRoomKeyRequestCancellations.add(IncomingRoomKeyRequestCancellation(event))
|
||||
else -> Timber.e("## onRoomKeyRequestEvent() : unsupported action " + roomKeyShare?.action)
|
||||
else -> Timber.e("## onRoomKeyRequestEvent() : unsupported action ${roomKeyShare?.action}")
|
||||
}
|
||||
}
|
||||
|
||||
@ -68,7 +66,7 @@ internal class IncomingRoomKeyRequestManager @Inject constructor(
|
||||
* It must be called on CryptoThread
|
||||
*/
|
||||
fun processReceivedRoomKeyRequests() {
|
||||
val roomKeyRequestsToProcess = ArrayList(receivedRoomKeyRequests)
|
||||
val roomKeyRequestsToProcess = receivedRoomKeyRequests.toList()
|
||||
receivedRoomKeyRequests.clear()
|
||||
for (request in roomKeyRequestsToProcess) {
|
||||
val userId = request.userId
|
||||
@ -77,7 +75,7 @@ internal class IncomingRoomKeyRequestManager @Inject constructor(
|
||||
val roomId = body!!.roomId
|
||||
val alg = body.algorithm
|
||||
|
||||
Timber.v("m.room_key_request from " + userId + ":" + deviceId + " for " + roomId + " / " + body.sessionId + " id " + request.requestId)
|
||||
Timber.v("m.room_key_request from $userId:$deviceId for $roomId / ${body.sessionId} id ${request.requestId}")
|
||||
if (userId == null || credentials.userId != userId) {
|
||||
// TODO: determine if we sent this device the keys already: in
|
||||
Timber.e("## processReceivedRoomKeyRequests() : Ignoring room key request from other user for now")
|
||||
@ -92,12 +90,12 @@ internal class IncomingRoomKeyRequestManager @Inject constructor(
|
||||
continue
|
||||
}
|
||||
if (!decryptor.hasKeysForKeyRequest(request)) {
|
||||
Timber.e("## processReceivedRoomKeyRequests() : room key request for unknown session " + body.sessionId!!)
|
||||
Timber.e("## processReceivedRoomKeyRequests() : room key request for unknown session ${body.sessionId!!}")
|
||||
cryptoStore.deleteIncomingRoomKeyRequest(request)
|
||||
continue
|
||||
}
|
||||
|
||||
if (TextUtils.equals(deviceId, credentials.deviceId) && TextUtils.equals(credentials.userId, userId)) {
|
||||
if (credentials.deviceId == deviceId && credentials.userId == userId) {
|
||||
Timber.v("## processReceivedRoomKeyRequests() : oneself device - ignored")
|
||||
cryptoStore.deleteIncomingRoomKeyRequest(request)
|
||||
continue
|
||||
@ -132,7 +130,7 @@ internal class IncomingRoomKeyRequestManager @Inject constructor(
|
||||
var receivedRoomKeyRequestCancellations: List<IncomingRoomKeyRequestCancellation>? = null
|
||||
|
||||
synchronized(this.receivedRoomKeyRequestCancellations) {
|
||||
if (!this.receivedRoomKeyRequestCancellations.isEmpty()) {
|
||||
if (this.receivedRoomKeyRequestCancellations.isNotEmpty()) {
|
||||
receivedRoomKeyRequestCancellations = this.receivedRoomKeyRequestCancellations.toList()
|
||||
this.receivedRoomKeyRequestCancellations.clear()
|
||||
}
|
||||
|
@ -16,20 +16,19 @@
|
||||
|
||||
package im.vector.matrix.android.internal.crypto
|
||||
|
||||
import android.text.TextUtils
|
||||
import android.util.Base64
|
||||
import im.vector.matrix.android.internal.extensions.toUnsignedInt
|
||||
import timber.log.Timber
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.nio.charset.Charset
|
||||
import java.security.SecureRandom
|
||||
import java.util.*
|
||||
import javax.crypto.Cipher
|
||||
import javax.crypto.Mac
|
||||
import javax.crypto.spec.IvParameterSpec
|
||||
import javax.crypto.spec.SecretKeySpec
|
||||
import kotlin.experimental.and
|
||||
import kotlin.experimental.xor
|
||||
import kotlin.math.min
|
||||
|
||||
/**
|
||||
* Utility class to import/export the crypto data
|
||||
@ -51,7 +50,7 @@ object MXMegolmExportEncryption {
|
||||
* @return the AES key
|
||||
*/
|
||||
private fun getAesKey(keyBits: ByteArray): ByteArray {
|
||||
return Arrays.copyOfRange(keyBits, 0, 32)
|
||||
return keyBits.copyOfRange(0, 32)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -61,7 +60,7 @@ object MXMegolmExportEncryption {
|
||||
* @return the Hmac key.
|
||||
*/
|
||||
private fun getHmacKey(keyBits: ByteArray): ByteArray {
|
||||
return Arrays.copyOfRange(keyBits, 32, keyBits.size)
|
||||
return keyBits.copyOfRange(32, keyBits.size)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -77,7 +76,7 @@ object MXMegolmExportEncryption {
|
||||
val body = unpackMegolmKeyFile(data)
|
||||
|
||||
// check we have a version byte
|
||||
if (null == body || body.size == 0) {
|
||||
if (null == body || body.isEmpty()) {
|
||||
Timber.e("## decryptMegolmKeyFile() : Invalid file: too short")
|
||||
throw Exception("Invalid file: too short")
|
||||
}
|
||||
@ -93,27 +92,27 @@ object MXMegolmExportEncryption {
|
||||
throw Exception("Invalid file: too short")
|
||||
}
|
||||
|
||||
if (TextUtils.isEmpty(password)) {
|
||||
if (password.isEmpty()) {
|
||||
throw Exception("Empty password is not supported")
|
||||
}
|
||||
|
||||
val salt = Arrays.copyOfRange(body, 1, 1 + 16)
|
||||
val iv = Arrays.copyOfRange(body, 17, 17 + 16)
|
||||
val salt = body.copyOfRange(1, 1 + 16)
|
||||
val iv = body.copyOfRange(17, 17 + 16)
|
||||
val iterations =
|
||||
(body[33].toUnsignedInt() shl 24) or (body[34].toUnsignedInt() shl 16) or (body[35].toUnsignedInt() shl 8) or body[36].toUnsignedInt()
|
||||
val ciphertext = Arrays.copyOfRange(body, 37, 37 + ciphertextLength)
|
||||
val hmac = Arrays.copyOfRange(body, body.size - 32, body.size)
|
||||
val ciphertext = body.copyOfRange(37, 37 + ciphertextLength)
|
||||
val hmac = body.copyOfRange(body.size - 32, body.size)
|
||||
|
||||
val deriveKey = deriveKeys(salt, iterations, password)
|
||||
|
||||
val toVerify = Arrays.copyOfRange(body, 0, body.size - 32)
|
||||
val toVerify = body.copyOfRange(0, body.size - 32)
|
||||
|
||||
val macKey = SecretKeySpec(getHmacKey(deriveKey), "HmacSHA256")
|
||||
val mac = Mac.getInstance("HmacSHA256")
|
||||
mac.init(macKey)
|
||||
val digest = mac.doFinal(toVerify)
|
||||
|
||||
if (!Arrays.equals(hmac, digest)) {
|
||||
if (!hmac.contentEquals(digest)) {
|
||||
Timber.e("## decryptMegolmKeyFile() : Authentication check failed: incorrect password?")
|
||||
throw Exception("Authentication check failed: incorrect password?")
|
||||
}
|
||||
@ -146,7 +145,7 @@ object MXMegolmExportEncryption {
|
||||
@Throws(Exception::class)
|
||||
@JvmOverloads
|
||||
fun encryptMegolmKeyFile(data: String, password: String, kdf_rounds: Int = DEFAULT_ITERATION_COUNT): ByteArray {
|
||||
if (TextUtils.isEmpty(password)) {
|
||||
if (password.isEmpty()) {
|
||||
throw Exception("Empty password is not supported")
|
||||
}
|
||||
|
||||
@ -196,7 +195,7 @@ object MXMegolmExportEncryption {
|
||||
System.arraycopy(cipherArray, 0, resultBuffer, idx, cipherArray.size)
|
||||
idx += cipherArray.size
|
||||
|
||||
val toSign = Arrays.copyOfRange(resultBuffer, 0, idx)
|
||||
val toSign = resultBuffer.copyOfRange(0, idx)
|
||||
|
||||
val macKey = SecretKeySpec(getHmacKey(deriveKey), "HmacSHA256")
|
||||
val mac = Mac.getInstance("HmacSHA256")
|
||||
@ -234,7 +233,7 @@ object MXMegolmExportEncryption {
|
||||
// start the next line after the newline
|
||||
lineStart = lineEnd + 1
|
||||
|
||||
if (TextUtils.equals(line, HEADER_LINE)) {
|
||||
if (line == HEADER_LINE) {
|
||||
break
|
||||
}
|
||||
}
|
||||
@ -244,15 +243,13 @@ object MXMegolmExportEncryption {
|
||||
// look for the end line
|
||||
while (true) {
|
||||
val lineEnd = fileStr.indexOf('\n', lineStart)
|
||||
val line: String
|
||||
|
||||
if (lineEnd < 0) {
|
||||
line = fileStr.substring(lineStart).trim()
|
||||
val line = if (lineEnd < 0) {
|
||||
fileStr.substring(lineStart)
|
||||
} else {
|
||||
line = fileStr.substring(lineStart, lineEnd).trim()
|
||||
}
|
||||
fileStr.substring(lineStart, lineEnd)
|
||||
}.trim()
|
||||
|
||||
if (TextUtils.equals(line, TRAILER_LINE)) {
|
||||
if (line == TRAILER_LINE) {
|
||||
break
|
||||
}
|
||||
|
||||
@ -290,7 +287,7 @@ object MXMegolmExportEncryption {
|
||||
for (i in 1..nLines) {
|
||||
outStream.write("\n".toByteArray())
|
||||
|
||||
val len = Math.min(LINE_LENGTH, data.size - o)
|
||||
val len = min(LINE_LENGTH, data.size - o)
|
||||
outStream.write(Base64.encode(data, o, len, Base64.DEFAULT))
|
||||
o += LINE_LENGTH
|
||||
}
|
||||
@ -318,7 +315,7 @@ object MXMegolmExportEncryption {
|
||||
// it is simpler than the generic algorithm because the expected key length is equal to the mac key length.
|
||||
// noticed as dklen/hlen
|
||||
val prf = Mac.getInstance("HmacSHA512")
|
||||
prf.init(SecretKeySpec(password.toByteArray(charset("UTF-8")), "HmacSHA512"))
|
||||
prf.init(SecretKeySpec(password.toByteArray(Charsets.UTF_8), "HmacSHA512"))
|
||||
|
||||
// 512 bits key length
|
||||
val key = ByteArray(64)
|
||||
@ -326,8 +323,7 @@ object MXMegolmExportEncryption {
|
||||
|
||||
// U1 = PRF(Password, Salt || INT_32_BE(i))
|
||||
prf.update(salt)
|
||||
val int32BE = ByteArray(4)
|
||||
Arrays.fill(int32BE, 0.toByte())
|
||||
val int32BE = ByteArray(4) { 0.toByte() }
|
||||
int32BE[3] = 1.toByte()
|
||||
prf.update(int32BE)
|
||||
prf.doFinal(Uc, 0)
|
||||
@ -346,7 +342,7 @@ object MXMegolmExportEncryption {
|
||||
}
|
||||
}
|
||||
|
||||
Timber.v("## deriveKeys() : " + iterations + " in " + (System.currentTimeMillis() - t0) + " ms")
|
||||
Timber.v("## deriveKeys() : $iterations in ${System.currentTimeMillis() - t0} ms")
|
||||
|
||||
return key
|
||||
}
|
||||
|
@ -17,7 +17,6 @@
|
||||
|
||||
package im.vector.matrix.android.internal.crypto
|
||||
|
||||
import android.text.TextUtils
|
||||
import im.vector.matrix.android.api.session.crypto.MXCryptoError
|
||||
import im.vector.matrix.android.api.util.JSON_DICT_PARAMETERIZED_TYPE
|
||||
import im.vector.matrix.android.api.util.JsonDict
|
||||
@ -33,7 +32,6 @@ import im.vector.matrix.android.internal.util.convertToUTF8
|
||||
import org.matrix.olm.*
|
||||
import timber.log.Timber
|
||||
import java.net.URLEncoder
|
||||
import java.util.*
|
||||
import javax.inject.Inject
|
||||
|
||||
// The libolm wrapper.
|
||||
@ -434,7 +432,7 @@ internal class MXOlmDevice @Inject constructor(
|
||||
* @return the base64-encoded secret key.
|
||||
*/
|
||||
fun getSessionKey(sessionId: String): String? {
|
||||
if (!TextUtils.isEmpty(sessionId)) {
|
||||
if (sessionId.isNotEmpty()) {
|
||||
try {
|
||||
return outboundGroupSessionStore[sessionId]!!.sessionKey()
|
||||
} catch (e: Exception) {
|
||||
@ -451,7 +449,7 @@ internal class MXOlmDevice @Inject constructor(
|
||||
* @return the current chain index.
|
||||
*/
|
||||
fun getMessageIndex(sessionId: String): Int {
|
||||
return if (!TextUtils.isEmpty(sessionId)) {
|
||||
return if (sessionId.isNotEmpty()) {
|
||||
outboundGroupSessionStore[sessionId]!!.messageIndex()
|
||||
} else 0
|
||||
}
|
||||
@ -464,7 +462,7 @@ internal class MXOlmDevice @Inject constructor(
|
||||
* @return ciphertext
|
||||
*/
|
||||
fun encryptGroupMessage(sessionId: String, payloadString: String): String? {
|
||||
if (!TextUtils.isEmpty(sessionId) && !TextUtils.isEmpty(payloadString)) {
|
||||
if (sessionId.isNotEmpty() && payloadString.isNotEmpty()) {
|
||||
try {
|
||||
return outboundGroupSessionStore[sessionId]!!.encryptMessage(payloadString)
|
||||
} catch (e: Exception) {
|
||||
@ -523,7 +521,7 @@ internal class MXOlmDevice @Inject constructor(
|
||||
}
|
||||
|
||||
try {
|
||||
if (!TextUtils.equals(session.olmInboundGroupSession!!.sessionIdentifier(), sessionId)) {
|
||||
if (session.olmInboundGroupSession!!.sessionIdentifier() != sessionId) {
|
||||
Timber.e("## addInboundGroupSession : ERROR: Mismatched group session ID from senderKey: $senderKey")
|
||||
session.olmInboundGroupSession!!.releaseSession()
|
||||
return false
|
||||
@ -573,7 +571,7 @@ internal class MXOlmDevice @Inject constructor(
|
||||
}
|
||||
|
||||
try {
|
||||
if (!TextUtils.equals(session.olmInboundGroupSession?.sessionIdentifier(), sessionId)) {
|
||||
if (session.olmInboundGroupSession?.sessionIdentifier() != sessionId) {
|
||||
Timber.e("## importInboundGroupSession : ERROR: Mismatched group session ID from senderKey: $senderKey")
|
||||
if (session.olmInboundGroupSession != null) session.olmInboundGroupSession!!.releaseSession()
|
||||
continue
|
||||
@ -758,7 +756,7 @@ internal class MXOlmDevice @Inject constructor(
|
||||
if (session != null) {
|
||||
// Check that the room id matches the original one for the session. This stops
|
||||
// the HS pretending a message was targeting a different room.
|
||||
if (!TextUtils.equals(roomId, session.roomId)) {
|
||||
if (roomId != session.roomId) {
|
||||
val errorDescription = String.format(MXCryptoError.INBOUND_SESSION_MISMATCH_ROOM_ID_REASON, roomId, session.roomId)
|
||||
Timber.e("## getInboundGroupSession() : $errorDescription")
|
||||
throw MXCryptoError.Base(MXCryptoError.ErrorType.INBOUND_SESSION_MISMATCH_ROOM_ID, errorDescription)
|
||||
|
@ -16,12 +16,10 @@
|
||||
|
||||
package im.vector.matrix.android.internal.crypto
|
||||
|
||||
import android.text.TextUtils
|
||||
import im.vector.matrix.android.api.auth.data.Credentials
|
||||
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
|
||||
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
|
||||
import im.vector.matrix.android.internal.session.SessionScope
|
||||
import java.util.*
|
||||
import javax.inject.Inject
|
||||
|
||||
@SessionScope
|
||||
@ -42,11 +40,11 @@ internal class MyDeviceInfoHolder @Inject constructor(
|
||||
init {
|
||||
val keys = HashMap<String, String>()
|
||||
|
||||
if (!TextUtils.isEmpty(olmDevice.deviceEd25519Key)) {
|
||||
if (!olmDevice.deviceEd25519Key.isNullOrEmpty()) {
|
||||
keys["ed25519:" + credentials.deviceId] = olmDevice.deviceEd25519Key!!
|
||||
}
|
||||
|
||||
if (!TextUtils.isEmpty(olmDevice.deviceCurve25519Key)) {
|
||||
if (!olmDevice.deviceCurve25519Key.isNullOrEmpty()) {
|
||||
keys["curve25519:" + credentials.deviceId] = olmDevice.deviceCurve25519Key!!
|
||||
}
|
||||
|
||||
@ -58,13 +56,7 @@ internal class MyDeviceInfoHolder @Inject constructor(
|
||||
// Add our own deviceinfo to the store
|
||||
val endToEndDevicesForUser = cryptoStore.getUserDevices(credentials.userId)
|
||||
|
||||
val myDevices: MutableMap<String, MXDeviceInfo>
|
||||
|
||||
if (null != endToEndDevicesForUser) {
|
||||
myDevices = HashMap(endToEndDevicesForUser)
|
||||
} else {
|
||||
myDevices = HashMap()
|
||||
}
|
||||
val myDevices = endToEndDevicesForUser.orEmpty().toMutableMap()
|
||||
|
||||
myDevices[myDevice.deviceId] = myDevice
|
||||
|
||||
|
@ -24,8 +24,9 @@ import im.vector.matrix.android.internal.session.SessionScope
|
||||
import im.vector.matrix.android.internal.util.JsonCanonicalizer
|
||||
import org.matrix.olm.OlmAccount
|
||||
import timber.log.Timber
|
||||
import java.util.*
|
||||
import javax.inject.Inject
|
||||
import kotlin.math.floor
|
||||
import kotlin.math.min
|
||||
|
||||
@SessionScope
|
||||
internal class OneTimeKeysUploader @Inject constructor(
|
||||
@ -77,7 +78,7 @@ internal class OneTimeKeysUploader @Inject constructor(
|
||||
// If we run out of slots when generating new keys then olm will
|
||||
// discard the oldest private keys first. This will eventually clean
|
||||
// out stale private keys that won't receive a message.
|
||||
val keyLimit = Math.floor(maxOneTimeKeys / 2.0).toInt()
|
||||
val keyLimit = floor(maxOneTimeKeys / 2.0).toInt()
|
||||
if (oneTimeKeyCount != null) {
|
||||
uploadOTK(oneTimeKeyCount!!, keyLimit)
|
||||
} else {
|
||||
@ -116,7 +117,7 @@ internal class OneTimeKeysUploader @Inject constructor(
|
||||
// If we don't need to generate any more keys then we are done.
|
||||
return
|
||||
}
|
||||
val keysThisLoop = Math.min(keyLimit - keyCount, ONE_TIME_KEY_GENERATION_MAX_NUMBER)
|
||||
val keysThisLoop = min(keyLimit - keyCount, ONE_TIME_KEY_GENERATION_MAX_NUMBER)
|
||||
olmDevice.generateOneTimeKeys(keysThisLoop)
|
||||
val response = uploadOneTimeKeys()
|
||||
if (response.hasOneTimeKeyCountsForAlgorithm(MXKey.KEY_SIGNED_CURVE_25519_TYPE)) {
|
||||
@ -132,14 +133,14 @@ internal class OneTimeKeysUploader @Inject constructor(
|
||||
*/
|
||||
private suspend fun uploadOneTimeKeys(): KeysUploadResponse {
|
||||
val oneTimeKeys = olmDevice.getOneTimeKeys()
|
||||
val oneTimeJson = HashMap<String, Any>()
|
||||
val oneTimeJson = mutableMapOf<String, Any>()
|
||||
|
||||
val curve25519Map = oneTimeKeys?.get(OlmAccount.JSON_KEY_ONE_TIME_KEY)
|
||||
|
||||
if (null != curve25519Map) {
|
||||
for (key_id in curve25519Map.keys) {
|
||||
val k = HashMap<String, Any>()
|
||||
k["key"] = curve25519Map.getValue(key_id)
|
||||
for ((key_id, value) in curve25519Map) {
|
||||
val k = mutableMapOf<String, Any>()
|
||||
k["key"] = value
|
||||
|
||||
// the key is also signed
|
||||
val canonicalJson = JsonCanonicalizer.getCanonicalJson(Map::class.java, k)
|
||||
|
@ -16,13 +16,11 @@
|
||||
|
||||
package im.vector.matrix.android.internal.crypto
|
||||
|
||||
import android.text.TextUtils
|
||||
import im.vector.matrix.android.internal.crypto.algorithms.IMXDecrypting
|
||||
import im.vector.matrix.android.internal.crypto.algorithms.megolm.MXMegolmDecryptionFactory
|
||||
import im.vector.matrix.android.internal.crypto.algorithms.olm.MXOlmDecryptionFactory
|
||||
import im.vector.matrix.android.internal.session.SessionScope
|
||||
import timber.log.Timber
|
||||
import java.util.*
|
||||
import javax.inject.Inject
|
||||
|
||||
@SessionScope
|
||||
@ -62,10 +60,8 @@ internal class RoomDecryptorProvider @Inject constructor(
|
||||
}
|
||||
if (roomId != null && roomId.isNotEmpty()) {
|
||||
synchronized(roomDecryptors) {
|
||||
if (!roomDecryptors.containsKey(roomId)) {
|
||||
roomDecryptors[roomId] = HashMap()
|
||||
}
|
||||
val alg = roomDecryptors[roomId]?.get(algorithm)
|
||||
val decryptors = roomDecryptors.getOrPut(roomId) { mutableMapOf() }
|
||||
val alg = decryptors[algorithm]
|
||||
if (alg != null) {
|
||||
return alg
|
||||
}
|
||||
@ -89,7 +85,7 @@ internal class RoomDecryptorProvider @Inject constructor(
|
||||
}
|
||||
else -> olmDecryptionFactory.create()
|
||||
}
|
||||
if (roomId != null && !TextUtils.isEmpty(roomId)) {
|
||||
if (!roomId.isNullOrEmpty()) {
|
||||
synchronized(roomDecryptors) {
|
||||
roomDecryptors[roomId]?.put(algorithm, alg)
|
||||
}
|
||||
|
@ -16,7 +16,6 @@
|
||||
|
||||
package im.vector.matrix.android.internal.crypto.actions
|
||||
|
||||
import android.text.TextUtils
|
||||
import im.vector.matrix.android.internal.crypto.MXOlmDevice
|
||||
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
|
||||
import im.vector.matrix.android.internal.crypto.model.MXKey
|
||||
@ -24,7 +23,6 @@ import im.vector.matrix.android.internal.crypto.model.MXOlmSessionResult
|
||||
import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
|
||||
import im.vector.matrix.android.internal.crypto.tasks.ClaimOneTimeKeysForUsersDeviceTask
|
||||
import timber.log.Timber
|
||||
import java.util.*
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class EnsureOlmSessionsForDevicesAction @Inject constructor(private val olmDevice: MXOlmDevice,
|
||||
@ -35,18 +33,14 @@ internal class EnsureOlmSessionsForDevicesAction @Inject constructor(private val
|
||||
|
||||
val results = MXUsersDevicesMap<MXOlmSessionResult>()
|
||||
|
||||
val userIds = devicesByUser.keys
|
||||
|
||||
for (userId in userIds) {
|
||||
val deviceInfos = devicesByUser[userId]
|
||||
|
||||
for (deviceInfo in deviceInfos!!) {
|
||||
for ((userId, deviceInfos) in devicesByUser) {
|
||||
for (deviceInfo in deviceInfos) {
|
||||
val deviceId = deviceInfo.deviceId
|
||||
val key = deviceInfo.identityKey()
|
||||
|
||||
val sessionId = olmDevice.getSessionId(key!!)
|
||||
|
||||
if (TextUtils.isEmpty(sessionId)) {
|
||||
if (sessionId.isNullOrEmpty()) {
|
||||
devicesWithoutSession.add(deviceInfo)
|
||||
}
|
||||
|
||||
@ -79,9 +73,8 @@ internal class EnsureOlmSessionsForDevicesAction @Inject constructor(private val
|
||||
val claimParams = ClaimOneTimeKeysForUsersDeviceTask.Params(usersDevicesToClaim)
|
||||
val oneTimeKeys = oneTimeKeysForUsersDeviceTask.execute(claimParams)
|
||||
Timber.v("## claimOneTimeKeysForUsersDevices() : keysClaimResponse.oneTimeKeys: $oneTimeKeys")
|
||||
for (userId in userIds) {
|
||||
val deviceInfos = devicesByUser[userId]
|
||||
for (deviceInfo in deviceInfos!!) {
|
||||
for ((userId, deviceInfos) in devicesByUser) {
|
||||
for (deviceInfo in deviceInfos) {
|
||||
var oneTimeKey: MXKey? = null
|
||||
val deviceIds = oneTimeKeys.getUserDeviceIds(userId)
|
||||
if (null != deviceIds) {
|
||||
@ -116,24 +109,22 @@ internal class EnsureOlmSessionsForDevicesAction @Inject constructor(private val
|
||||
val signKeyId = "ed25519:$deviceId"
|
||||
val signature = oneTimeKey.signatureForUserId(userId, signKeyId)
|
||||
|
||||
if (!TextUtils.isEmpty(signature) && !TextUtils.isEmpty(deviceInfo.fingerprint())) {
|
||||
if (!signature.isNullOrEmpty() && !deviceInfo.fingerprint().isNullOrEmpty()) {
|
||||
var isVerified = false
|
||||
var errorMessage: String? = null
|
||||
|
||||
if (signature != null) {
|
||||
try {
|
||||
olmDevice.verifySignature(deviceInfo.fingerprint()!!, oneTimeKey.signalableJSONDictionary(), signature)
|
||||
isVerified = true
|
||||
} catch (e: Exception) {
|
||||
errorMessage = e.message
|
||||
}
|
||||
try {
|
||||
olmDevice.verifySignature(deviceInfo.fingerprint()!!, oneTimeKey.signalableJSONDictionary(), signature)
|
||||
isVerified = true
|
||||
} catch (e: Exception) {
|
||||
errorMessage = e.message
|
||||
}
|
||||
|
||||
// Check one-time key signature
|
||||
if (isVerified) {
|
||||
sessionId = olmDevice.createOutboundSession(deviceInfo.identityKey()!!, oneTimeKey.value)
|
||||
|
||||
if (!TextUtils.isEmpty(sessionId)) {
|
||||
if (!sessionId.isNullOrEmpty()) {
|
||||
Timber.v("## verifyKeyAndStartSession() : Started new sessionid " + sessionId
|
||||
+ " for device " + deviceInfo + "(theirOneTimeKey: " + oneTimeKey.value + ")")
|
||||
} else {
|
||||
|
@ -16,14 +16,11 @@
|
||||
|
||||
package im.vector.matrix.android.internal.crypto.actions
|
||||
|
||||
import android.text.TextUtils
|
||||
import im.vector.matrix.android.internal.crypto.MXOlmDevice
|
||||
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
|
||||
import im.vector.matrix.android.internal.crypto.model.MXOlmSessionResult
|
||||
import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
|
||||
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
|
||||
import timber.log.Timber
|
||||
import java.util.*
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class EnsureOlmSessionsForUsersAction @Inject constructor(private val olmDevice: MXOlmDevice,
|
||||
@ -36,27 +33,14 @@ internal class EnsureOlmSessionsForUsersAction @Inject constructor(private val o
|
||||
*/
|
||||
suspend fun handle(users: List<String>): MXUsersDevicesMap<MXOlmSessionResult> {
|
||||
Timber.v("## ensureOlmSessionsForUsers() : ensureOlmSessionsForUsers $users")
|
||||
val devicesByUser = HashMap<String /* userId */, MutableList<MXDeviceInfo>>()
|
||||
|
||||
for (userId in users) {
|
||||
devicesByUser[userId] = ArrayList()
|
||||
|
||||
val devicesByUser = users.associateWith { userId ->
|
||||
val devices = cryptoStore.getUserDevices(userId)?.values ?: emptyList()
|
||||
|
||||
for (device in devices) {
|
||||
val key = device.identityKey()
|
||||
|
||||
if (TextUtils.equals(key, olmDevice.deviceCurve25519Key)) {
|
||||
// Don't bother setting up session to ourself
|
||||
continue
|
||||
}
|
||||
|
||||
if (device.isVerified) {
|
||||
// Don't bother setting up sessions with blocked users
|
||||
continue
|
||||
}
|
||||
|
||||
devicesByUser[userId]!!.add(device)
|
||||
devices.filter {
|
||||
// Don't bother setting up session to ourself
|
||||
it.identityKey() != olmDevice.deviceCurve25519Key &&
|
||||
// Don't bother setting up sessions with blocked users
|
||||
!it.isVerified
|
||||
}
|
||||
}
|
||||
return ensureOlmSessionsForDevicesAction.handle(devicesByUser)
|
||||
|
@ -16,7 +16,6 @@
|
||||
|
||||
package im.vector.matrix.android.internal.crypto.actions
|
||||
|
||||
import android.text.TextUtils
|
||||
import im.vector.matrix.android.api.auth.data.Credentials
|
||||
import im.vector.matrix.android.internal.crypto.MXCRYPTO_ALGORITHM_OLM
|
||||
import im.vector.matrix.android.internal.crypto.MXOlmDevice
|
||||
@ -25,7 +24,6 @@ import im.vector.matrix.android.internal.crypto.model.rest.EncryptedMessage
|
||||
import im.vector.matrix.android.internal.util.JsonCanonicalizer
|
||||
import im.vector.matrix.android.internal.util.convertToUTF8
|
||||
import timber.log.Timber
|
||||
import java.util.*
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class MessageEncrypter @Inject constructor(private val credentials: Credentials,
|
||||
@ -40,18 +38,12 @@ internal class MessageEncrypter @Inject constructor(private val credentials: Cre
|
||||
* @return the content for an m.room.encrypted event.
|
||||
*/
|
||||
fun encryptMessage(payloadFields: Map<String, Any>, deviceInfos: List<MXDeviceInfo>): EncryptedMessage {
|
||||
val deviceInfoParticipantKey = HashMap<String, MXDeviceInfo>()
|
||||
val participantKeys = ArrayList<String>()
|
||||
val deviceInfoParticipantKey = deviceInfos.associateBy { it.identityKey()!! }
|
||||
|
||||
for (di in deviceInfos) {
|
||||
participantKeys.add(di.identityKey()!!)
|
||||
deviceInfoParticipantKey[di.identityKey()!!] = di
|
||||
}
|
||||
|
||||
val payloadJson = HashMap(payloadFields)
|
||||
val payloadJson = payloadFields.toMutableMap()
|
||||
|
||||
payloadJson["sender"] = credentials.userId
|
||||
payloadJson["sender_device"] = credentials.deviceId
|
||||
payloadJson["sender_device"] = credentials.deviceId!!
|
||||
|
||||
// Include the Ed25519 key so that the recipient knows what
|
||||
// device this message came from.
|
||||
@ -67,30 +59,24 @@ internal class MessageEncrypter @Inject constructor(private val credentials: Cre
|
||||
|
||||
val ciphertext = HashMap<String, Any>()
|
||||
|
||||
for (deviceKey in participantKeys) {
|
||||
for ((deviceKey, deviceInfo) in deviceInfoParticipantKey) {
|
||||
val sessionId = olmDevice.getSessionId(deviceKey)
|
||||
|
||||
if (!TextUtils.isEmpty(sessionId)) {
|
||||
if (!sessionId.isNullOrEmpty()) {
|
||||
Timber.v("Using sessionid $sessionId for device $deviceKey")
|
||||
val deviceInfo = deviceInfoParticipantKey[deviceKey]
|
||||
|
||||
payloadJson["recipient"] = deviceInfo!!.userId
|
||||
|
||||
val recipientsKeysMap = HashMap<String, String>()
|
||||
recipientsKeysMap["ed25519"] = deviceInfo.fingerprint()!!
|
||||
payloadJson["recipient_keys"] = recipientsKeysMap
|
||||
payloadJson["recipient"] = deviceInfo.userId
|
||||
payloadJson["recipient_keys"] = mapOf("ed25519" to deviceInfo.fingerprint()!!)
|
||||
|
||||
val payloadString = convertToUTF8(JsonCanonicalizer.getCanonicalJson(Map::class.java, payloadJson))
|
||||
ciphertext[deviceKey] = olmDevice.encryptMessage(deviceKey, sessionId!!, payloadString)!!
|
||||
ciphertext[deviceKey] = olmDevice.encryptMessage(deviceKey, sessionId, payloadString)!!
|
||||
}
|
||||
}
|
||||
|
||||
val res = EncryptedMessage()
|
||||
|
||||
res.algorithm = MXCRYPTO_ALGORITHM_OLM
|
||||
res.senderKey = olmDevice.deviceCurve25519Key
|
||||
res.cipherText = ciphertext
|
||||
|
||||
return res
|
||||
return EncryptedMessage(
|
||||
algorithm = MXCRYPTO_ALGORITHM_OLM,
|
||||
senderKey = olmDevice.deviceCurve25519Key,
|
||||
cipherText = ciphertext
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -17,7 +17,6 @@
|
||||
|
||||
package im.vector.matrix.android.internal.crypto.algorithms.megolm
|
||||
|
||||
import android.text.TextUtils
|
||||
import im.vector.matrix.android.api.session.crypto.MXCryptoError
|
||||
import im.vector.matrix.android.api.session.events.model.Event
|
||||
import im.vector.matrix.android.api.session.events.model.EventType
|
||||
@ -148,7 +147,7 @@ internal class MXMegolmDecryption(private val userId: String,
|
||||
selfMap["deviceId"] = "*"
|
||||
recipients.add(selfMap)
|
||||
|
||||
if (!TextUtils.equals(sender, userId)) {
|
||||
if (sender != userId) {
|
||||
val senderMap = HashMap<String, String>()
|
||||
senderMap["userId"] = sender
|
||||
senderMap["deviceId"] = encryptedEventContent.deviceId!!
|
||||
@ -176,17 +175,12 @@ internal class MXMegolmDecryption(private val userId: String,
|
||||
val encryptedEventContent = event.content.toModel<EncryptedEventContent>() ?: return
|
||||
val pendingEventsKey = "${encryptedEventContent.senderKey}|${encryptedEventContent.sessionId}"
|
||||
|
||||
if (!pendingEvents.containsKey(pendingEventsKey)) {
|
||||
pendingEvents[pendingEventsKey] = HashMap()
|
||||
}
|
||||
val timeline = pendingEvents.getOrPut(pendingEventsKey) { HashMap() }
|
||||
val events = timeline.getOrPut(timelineId) { ArrayList() }
|
||||
|
||||
if (pendingEvents[pendingEventsKey]?.containsKey(timelineId) == false) {
|
||||
pendingEvents[pendingEventsKey]?.put(timelineId, ArrayList())
|
||||
}
|
||||
|
||||
if (pendingEvents[pendingEventsKey]?.get(timelineId)?.contains(event) == false) {
|
||||
Timber.v("## addEventToPendingList() : add Event " + event.eventId + " in room id " + event.roomId)
|
||||
pendingEvents[pendingEventsKey]?.get(timelineId)?.add(event)
|
||||
if (event !in events) {
|
||||
Timber.v("## addEventToPendingList() : add Event ${event.eventId} in room id ${event.roomId}")
|
||||
events.add(event)
|
||||
}
|
||||
}
|
||||
|
||||
@ -203,7 +197,7 @@ internal class MXMegolmDecryption(private val userId: String,
|
||||
var keysClaimed: MutableMap<String, String> = HashMap()
|
||||
val forwardingCurve25519KeyChain: MutableList<String> = ArrayList()
|
||||
|
||||
if (TextUtils.isEmpty(roomKeyContent.roomId) || TextUtils.isEmpty(roomKeyContent.sessionId) || TextUtils.isEmpty(roomKeyContent.sessionKey)) {
|
||||
if (roomKeyContent.roomId.isNullOrEmpty() || roomKeyContent.sessionId.isNullOrEmpty() || roomKeyContent.sessionKey.isNullOrEmpty()) {
|
||||
Timber.e("## onRoomKeyEvent() : Key event is missing fields")
|
||||
return
|
||||
}
|
||||
|
@ -18,7 +18,6 @@
|
||||
|
||||
package im.vector.matrix.android.internal.crypto.algorithms.megolm
|
||||
|
||||
import android.text.TextUtils
|
||||
import im.vector.matrix.android.api.auth.data.Credentials
|
||||
import im.vector.matrix.android.api.session.crypto.MXCryptoError
|
||||
import im.vector.matrix.android.api.session.events.model.Content
|
||||
@ -38,7 +37,6 @@ import im.vector.matrix.android.internal.crypto.tasks.SendToDeviceTask
|
||||
import im.vector.matrix.android.internal.util.JsonCanonicalizer
|
||||
import im.vector.matrix.android.internal.util.convertToUTF8
|
||||
import timber.log.Timber
|
||||
import java.util.*
|
||||
|
||||
internal class MXMegolmEncryption(
|
||||
// The id of the room we will be sending to.
|
||||
@ -85,7 +83,7 @@ internal class MXMegolmEncryption(
|
||||
keysClaimedMap["ed25519"] = olmDevice.deviceEd25519Key!!
|
||||
|
||||
olmDevice.addInboundGroupSession(sessionId!!, olmDevice.getSessionKey(sessionId)!!, roomId, olmDevice.deviceCurve25519Key!!,
|
||||
ArrayList(), keysClaimedMap, false)
|
||||
emptyList(), keysClaimedMap, false)
|
||||
|
||||
keysBackup.maybeBackupKeys()
|
||||
|
||||
@ -115,10 +113,8 @@ internal class MXMegolmEncryption(
|
||||
for (deviceId in deviceIds!!) {
|
||||
val deviceInfo = devicesInRoom.getObject(userId, deviceId)
|
||||
if (deviceInfo != null && null == safeSession.sharedWithDevices.getObject(userId, deviceId)) {
|
||||
if (!shareMap.containsKey(userId)) {
|
||||
shareMap[userId] = ArrayList()
|
||||
}
|
||||
shareMap[userId]!!.add(deviceInfo)
|
||||
val devices = shareMap.getOrPut(userId) { ArrayList() }
|
||||
devices.add(deviceInfo)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -141,21 +137,17 @@ internal class MXMegolmEncryption(
|
||||
}
|
||||
// reduce the map size to avoid request timeout when there are too many devices (Users size * devices per user)
|
||||
val subMap = HashMap<String, List<MXDeviceInfo>>()
|
||||
val userIds = ArrayList<String>()
|
||||
var devicesCount = 0
|
||||
for (userId in devicesByUsers.keys) {
|
||||
devicesByUsers[userId]?.let {
|
||||
userIds.add(userId)
|
||||
subMap[userId] = it
|
||||
devicesCount += it.size
|
||||
}
|
||||
for ((userId, devices) in devicesByUsers) {
|
||||
subMap[userId] = devices
|
||||
devicesCount += devices.size
|
||||
if (devicesCount > 100) {
|
||||
break
|
||||
}
|
||||
}
|
||||
Timber.v("## shareKey() ; userId $userIds")
|
||||
Timber.v("## shareKey() ; userId ${subMap.keys}")
|
||||
shareUserDevicesKey(session, subMap)
|
||||
val remainingDevices = devicesByUsers.filterKeys { userIds.contains(it).not() }
|
||||
val remainingDevices = devicesByUsers - subMap.keys
|
||||
shareKey(session, remainingDevices)
|
||||
}
|
||||
|
||||
@ -209,8 +201,7 @@ internal class MXMegolmEncryption(
|
||||
continue
|
||||
}
|
||||
Timber.v("## shareUserDevicesKey() : Sharing keys with device $userId:$deviceID")
|
||||
//noinspection ArraysAsListWithZeroOrOneArgument,ArraysAsListWithZeroOrOneArgument
|
||||
contentMap.setObject(userId, deviceID, messageEncrypter.encryptMessage(payload, Arrays.asList(sessionResult.deviceInfo)))
|
||||
contentMap.setObject(userId, deviceID, messageEncrypter.encryptMessage(payload, listOf(sessionResult.deviceInfo)))
|
||||
haveTargets = true
|
||||
}
|
||||
}
|
||||
@ -227,9 +218,8 @@ internal class MXMegolmEncryption(
|
||||
// attempted to share with) rather than the contentMap (those we did
|
||||
// share with), because we don't want to try to claim a one-time-key
|
||||
// for dead devices on every message.
|
||||
for (userId in devicesByUser.keys) {
|
||||
val devicesToShareWith = devicesByUser[userId]
|
||||
for ((deviceId) in devicesToShareWith!!) {
|
||||
for ((userId, devicesToShareWith) in devicesByUser) {
|
||||
for ((deviceId) in devicesToShareWith) {
|
||||
session.sharedWithDevices.setObject(userId, deviceId, chainIndex)
|
||||
}
|
||||
}
|
||||
@ -302,7 +292,7 @@ internal class MXMegolmEncryption(
|
||||
continue
|
||||
}
|
||||
|
||||
if (TextUtils.equals(deviceInfo.identityKey(), olmDevice.deviceCurve25519Key)) {
|
||||
if (deviceInfo.identityKey() == olmDevice.deviceCurve25519Key) {
|
||||
// Don't bother sending to ourself
|
||||
continue
|
||||
}
|
||||
|
@ -18,7 +18,6 @@
|
||||
|
||||
package im.vector.matrix.android.internal.crypto.algorithms.olm
|
||||
|
||||
import android.text.TextUtils
|
||||
import im.vector.matrix.android.api.session.events.model.Content
|
||||
import im.vector.matrix.android.api.session.events.model.toContent
|
||||
import im.vector.matrix.android.internal.crypto.DeviceListManager
|
||||
@ -28,7 +27,6 @@ import im.vector.matrix.android.internal.crypto.actions.MessageEncrypter
|
||||
import im.vector.matrix.android.internal.crypto.algorithms.IMXEncrypting
|
||||
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
|
||||
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
|
||||
import java.util.*
|
||||
|
||||
internal class MXOlmEncryption(
|
||||
private var roomId: String,
|
||||
@ -49,7 +47,7 @@ internal class MXOlmEncryption(
|
||||
val devices = cryptoStore.getUserDevices(userId)?.values ?: emptyList()
|
||||
for (device in devices) {
|
||||
val key = device.identityKey()
|
||||
if (TextUtils.equals(key, olmDevice.deviceCurve25519Key)) {
|
||||
if (key == olmDevice.deviceCurve25519Key) {
|
||||
// Don't bother setting up session to ourself
|
||||
continue
|
||||
}
|
||||
@ -61,10 +59,11 @@ internal class MXOlmEncryption(
|
||||
}
|
||||
}
|
||||
|
||||
val messageMap = HashMap<String, Any>()
|
||||
messageMap["room_id"] = roomId
|
||||
messageMap["type"] = eventType
|
||||
messageMap["content"] = eventContent
|
||||
val messageMap = mapOf(
|
||||
"room_id" to roomId,
|
||||
"type" to eventType,
|
||||
"content" to eventContent
|
||||
)
|
||||
|
||||
messageEncrypter.encryptMessage(messageMap, deviceInfos)
|
||||
return messageMap.toContent()!!
|
||||
|
@ -59,6 +59,7 @@ import im.vector.matrix.android.internal.task.TaskThread
|
||||
import im.vector.matrix.android.internal.task.configureWith
|
||||
import im.vector.matrix.android.internal.util.JsonCanonicalizer
|
||||
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
||||
import im.vector.matrix.android.internal.util.awaitCallback
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
@ -69,6 +70,9 @@ import org.matrix.olm.OlmPkMessage
|
||||
import timber.log.Timber
|
||||
import java.security.InvalidParameterException
|
||||
import javax.inject.Inject
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.resumeWithException
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
import kotlin.random.Random
|
||||
|
||||
/**
|
||||
@ -144,8 +148,8 @@ internal class KeysBackup @Inject constructor(
|
||||
progressListener: ProgressListener?,
|
||||
callback: MatrixCallback<MegolmBackupCreationInfo>) {
|
||||
GlobalScope.launch(coroutineDispatchers.main) {
|
||||
withContext(coroutineDispatchers.crypto) {
|
||||
Try {
|
||||
runCatching {
|
||||
withContext(coroutineDispatchers.crypto) {
|
||||
val olmPkDecryption = OlmPkDecryption()
|
||||
val megolmBackupAuthData = MegolmBackupAuthData()
|
||||
|
||||
@ -394,7 +398,7 @@ internal class KeysBackup @Inject constructor(
|
||||
return keysBackupVersionTrust
|
||||
}
|
||||
|
||||
for (keyId in mySigs.keys) {
|
||||
for ((keyId, mySignature) in mySigs) {
|
||||
// XXX: is this how we're supposed to get the device id?
|
||||
var deviceId: String? = null
|
||||
val components = keyId.split(":")
|
||||
@ -412,7 +416,7 @@ internal class KeysBackup @Inject constructor(
|
||||
val fingerprint = device.fingerprint()
|
||||
if (fingerprint != null) {
|
||||
try {
|
||||
olmDevice.verifySignature(fingerprint, authData.signalableJSONDictionary(), mySigs[keyId] as String)
|
||||
olmDevice.verifySignature(fingerprint, authData.signalableJSONDictionary(), mySignature)
|
||||
isSignatureValid = true
|
||||
} catch (e: OlmException) {
|
||||
Timber.v(e, "getKeysBackupTrust: Bad signature from device ${device.deviceId}")
|
||||
@ -615,8 +619,8 @@ internal class KeysBackup @Inject constructor(
|
||||
Timber.v("restoreKeysWithRecoveryKey: From backup version: ${keysVersionResult.version}")
|
||||
|
||||
GlobalScope.launch(coroutineDispatchers.main) {
|
||||
withContext(coroutineDispatchers.crypto) {
|
||||
Try<OlmPkDecryption> {
|
||||
runCatching {
|
||||
val decryption = withContext(coroutineDispatchers.crypto) {
|
||||
// Check if the recovery is valid before going any further
|
||||
if (!isValidRecoveryKeyForKeysBackupVersion(recoveryKey, keysVersionResult)) {
|
||||
Timber.e("restoreKeysWithRecoveryKey: Invalid recovery key for this keys version")
|
||||
@ -624,85 +628,66 @@ internal class KeysBackup @Inject constructor(
|
||||
}
|
||||
|
||||
// Get a PK decryption instance
|
||||
val decryption = pkDecryptionFromRecoveryKey(recoveryKey)
|
||||
if (decryption == null) {
|
||||
// This should not happen anymore
|
||||
Timber.e("restoreKeysWithRecoveryKey: Invalid recovery key. Error")
|
||||
throw InvalidParameterException("Invalid recovery key")
|
||||
}
|
||||
|
||||
decryption
|
||||
pkDecryptionFromRecoveryKey(recoveryKey)
|
||||
}
|
||||
if (decryption == null) {
|
||||
// This should not happen anymore
|
||||
Timber.e("restoreKeysWithRecoveryKey: Invalid recovery key. Error")
|
||||
throw InvalidParameterException("Invalid recovery key")
|
||||
}
|
||||
}.fold(
|
||||
{
|
||||
callback.onFailure(it)
|
||||
},
|
||||
{ decryption ->
|
||||
stepProgressListener?.onStepProgress(StepProgressListener.Step.DownloadingKey)
|
||||
|
||||
// Get backed up keys from the homeserver
|
||||
getKeys(sessionId, roomId, keysVersionResult.version!!, object : MatrixCallback<KeysBackupData> {
|
||||
override fun onSuccess(data: KeysBackupData) {
|
||||
GlobalScope.launch(coroutineDispatchers.main) {
|
||||
val importRoomKeysResult = withContext(coroutineDispatchers.crypto) {
|
||||
val sessionsData = ArrayList<MegolmSessionData>()
|
||||
// Restore that data
|
||||
var sessionsFromHsCount = 0
|
||||
for (roomIdLoop in data.roomIdToRoomKeysBackupData.keys) {
|
||||
for (sessionIdLoop in data.roomIdToRoomKeysBackupData[roomIdLoop]!!.sessionIdToKeyBackupData.keys) {
|
||||
sessionsFromHsCount++
|
||||
stepProgressListener?.onStepProgress(StepProgressListener.Step.DownloadingKey)
|
||||
|
||||
val keyBackupData = data.roomIdToRoomKeysBackupData[roomIdLoop]!!.sessionIdToKeyBackupData[sessionIdLoop]!!
|
||||
// Get backed up keys from the homeserver
|
||||
val data = getKeys(sessionId, roomId, keysVersionResult.version!!)
|
||||
|
||||
val sessionData = decryptKeyBackupData(keyBackupData, sessionIdLoop, roomIdLoop, decryption)
|
||||
withContext(coroutineDispatchers.crypto) {
|
||||
val sessionsData = ArrayList<MegolmSessionData>()
|
||||
// Restore that data
|
||||
var sessionsFromHsCount = 0
|
||||
for ((roomIdLoop, backupData) in data.roomIdToRoomKeysBackupData) {
|
||||
for ((sessionIdLoop, keyBackupData) in backupData.sessionIdToKeyBackupData) {
|
||||
sessionsFromHsCount++
|
||||
|
||||
sessionData?.let {
|
||||
sessionsData.add(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
Timber.v("restoreKeysWithRecoveryKey: Decrypted ${sessionsData.size} keys out" +
|
||||
" of $sessionsFromHsCount from the backup store on the homeserver")
|
||||
val sessionData = decryptKeyBackupData(keyBackupData, sessionIdLoop, roomIdLoop, decryption)
|
||||
|
||||
// Do not trigger a backup for them if they come from the backup version we are using
|
||||
val backUp = keysVersionResult.version != keysBackupVersion?.version
|
||||
if (backUp) {
|
||||
Timber.v("restoreKeysWithRecoveryKey: Those keys will be backed up" +
|
||||
" to backup version: ${keysBackupVersion?.version}")
|
||||
}
|
||||
|
||||
// Import them into the crypto store
|
||||
val progressListener = if (stepProgressListener != null) {
|
||||
object : ProgressListener {
|
||||
override fun onProgress(progress: Int, total: Int) {
|
||||
// Note: no need to post to UI thread, importMegolmSessionsData() will do it
|
||||
stepProgressListener.onStepProgress(StepProgressListener.Step.ImportingKey(progress, total))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
val result = megolmSessionDataImporter.handle(sessionsData, !backUp, uiHandler, progressListener)
|
||||
|
||||
// Do not back up the key if it comes from a backup recovery
|
||||
if (backUp) {
|
||||
maybeBackupKeys()
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
callback.onSuccess(importRoomKeysResult)
|
||||
}
|
||||
sessionData?.let {
|
||||
sessionsData.add(it)
|
||||
}
|
||||
|
||||
override fun onFailure(failure: Throwable) {
|
||||
callback.onFailure(failure)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
)
|
||||
Timber.v("restoreKeysWithRecoveryKey: Decrypted ${sessionsData.size} keys out" +
|
||||
" of $sessionsFromHsCount from the backup store on the homeserver")
|
||||
|
||||
// Do not trigger a backup for them if they come from the backup version we are using
|
||||
val backUp = keysVersionResult.version != keysBackupVersion?.version
|
||||
if (backUp) {
|
||||
Timber.v("restoreKeysWithRecoveryKey: Those keys will be backed up" +
|
||||
" to backup version: ${keysBackupVersion?.version}")
|
||||
}
|
||||
|
||||
// Import them into the crypto store
|
||||
val progressListener = if (stepProgressListener != null) {
|
||||
object : ProgressListener {
|
||||
override fun onProgress(progress: Int, total: Int) {
|
||||
// Note: no need to post to UI thread, importMegolmSessionsData() will do it
|
||||
stepProgressListener.onStepProgress(StepProgressListener.Step.ImportingKey(progress, total))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
val result = megolmSessionDataImporter.handle(sessionsData, !backUp, uiHandler, progressListener)
|
||||
|
||||
// Do not back up the key if it comes from a backup recovery
|
||||
if (backUp) {
|
||||
maybeBackupKeys()
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
}.foldToCallback(callback)
|
||||
}
|
||||
}
|
||||
|
||||
@ -715,7 +700,7 @@ internal class KeysBackup @Inject constructor(
|
||||
Timber.v("[MXKeyBackup] restoreKeyBackup with password: From backup version: ${keysBackupVersion.version}")
|
||||
|
||||
GlobalScope.launch(coroutineDispatchers.main) {
|
||||
withContext(coroutineDispatchers.crypto) {
|
||||
runCatching {
|
||||
val progressListener = if (stepProgressListener != null) {
|
||||
object : ProgressListener {
|
||||
override fun onProgress(progress: Int, total: Int) {
|
||||
@ -728,22 +713,18 @@ internal class KeysBackup @Inject constructor(
|
||||
null
|
||||
}
|
||||
|
||||
Try {
|
||||
val recoveryKey = withContext(coroutineDispatchers.crypto) {
|
||||
recoveryKeyFromPassword(password, keysBackupVersion, progressListener)
|
||||
}
|
||||
}.fold(
|
||||
{
|
||||
callback.onFailure(it)
|
||||
},
|
||||
{ recoveryKey ->
|
||||
if (recoveryKey == null) {
|
||||
Timber.v("backupKeys: Invalid configuration")
|
||||
callback.onFailure(IllegalStateException("Invalid configuration"))
|
||||
} else {
|
||||
restoreKeysWithRecoveryKey(keysBackupVersion, recoveryKey, roomId, sessionId, stepProgressListener, callback)
|
||||
}
|
||||
if (recoveryKey == null) {
|
||||
Timber.v("backupKeys: Invalid configuration")
|
||||
throw IllegalStateException("Invalid configuration")
|
||||
} else {
|
||||
awaitCallback<ImportRoomKeysResult> {
|
||||
restoreKeysWithRecoveryKey(keysBackupVersion, recoveryKey, roomId, sessionId, stepProgressListener, it)
|
||||
}
|
||||
)
|
||||
}
|
||||
}.foldToCallback(callback)
|
||||
}
|
||||
}
|
||||
|
||||
@ -751,60 +732,26 @@ internal class KeysBackup @Inject constructor(
|
||||
* Same method as [RoomKeysRestClient.getRoomKey] except that it accepts nullable
|
||||
* parameters and always returns a KeysBackupData object through the Callback
|
||||
*/
|
||||
private fun getKeys(sessionId: String?,
|
||||
private suspend fun getKeys(sessionId: String?,
|
||||
roomId: String?,
|
||||
version: String,
|
||||
callback: MatrixCallback<KeysBackupData>) {
|
||||
if (roomId != null && sessionId != null) {
|
||||
version: String): KeysBackupData {
|
||||
return if (roomId != null && sessionId != null) {
|
||||
// Get key for the room and for the session
|
||||
getRoomSessionDataTask
|
||||
.configureWith(GetRoomSessionDataTask.Params(roomId, sessionId, version)) {
|
||||
this.callback = object : MatrixCallback<KeyBackupData> {
|
||||
override fun onSuccess(data: KeyBackupData) {
|
||||
// Convert to KeysBackupData
|
||||
val keysBackupData = KeysBackupData()
|
||||
keysBackupData.roomIdToRoomKeysBackupData = HashMap()
|
||||
val roomKeysBackupData = RoomKeysBackupData()
|
||||
roomKeysBackupData.sessionIdToKeyBackupData = HashMap()
|
||||
roomKeysBackupData.sessionIdToKeyBackupData[sessionId] = data
|
||||
keysBackupData.roomIdToRoomKeysBackupData[roomId] = roomKeysBackupData
|
||||
|
||||
callback.onSuccess(keysBackupData)
|
||||
}
|
||||
|
||||
override fun onFailure(failure: Throwable) {
|
||||
callback.onFailure(failure)
|
||||
}
|
||||
}
|
||||
}
|
||||
.executeBy(taskExecutor)
|
||||
val data = getRoomSessionDataTask.execute(GetRoomSessionDataTask.Params(roomId, sessionId, version))
|
||||
// Convert to KeysBackupData
|
||||
KeysBackupData(mutableMapOf(
|
||||
roomId to RoomKeysBackupData(mutableMapOf(
|
||||
sessionId to data
|
||||
))
|
||||
))
|
||||
} else if (roomId != null) {
|
||||
// Get all keys for the room
|
||||
getRoomSessionsDataTask
|
||||
.configureWith(GetRoomSessionsDataTask.Params(roomId, version)) {
|
||||
this.callback = object : MatrixCallback<RoomKeysBackupData> {
|
||||
override fun onSuccess(data: RoomKeysBackupData) {
|
||||
// Convert to KeysBackupData
|
||||
val keysBackupData = KeysBackupData()
|
||||
keysBackupData.roomIdToRoomKeysBackupData = HashMap()
|
||||
keysBackupData.roomIdToRoomKeysBackupData[roomId] = data
|
||||
|
||||
callback.onSuccess(keysBackupData)
|
||||
}
|
||||
|
||||
override fun onFailure(failure: Throwable) {
|
||||
callback.onFailure(failure)
|
||||
}
|
||||
}
|
||||
}
|
||||
.executeBy(taskExecutor)
|
||||
val data = getRoomSessionsDataTask.execute(GetRoomSessionsDataTask.Params(roomId, version))
|
||||
// Convert to KeysBackupData
|
||||
KeysBackupData(mutableMapOf(roomId to data))
|
||||
} else {
|
||||
// Get all keys
|
||||
getSessionsDataTask
|
||||
.configureWith(GetSessionsDataTask.Params(version)) {
|
||||
this.callback = callback
|
||||
}
|
||||
.executeBy(taskExecutor)
|
||||
getSessionsDataTask.execute(GetSessionsDataTask.Params(version))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -17,13 +17,11 @@
|
||||
|
||||
package im.vector.matrix.android.internal.crypto.model
|
||||
|
||||
import android.text.TextUtils
|
||||
import im.vector.matrix.android.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
|
||||
import im.vector.matrix.android.internal.crypto.MegolmSessionData
|
||||
import org.matrix.olm.OlmInboundGroupSession
|
||||
import timber.log.Timber
|
||||
import java.io.Serializable
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* This class adds more context to a OlmInboundGroupSession object.
|
||||
@ -91,7 +89,7 @@ class OlmInboundGroupSessionWrapper : Serializable {
|
||||
try {
|
||||
olmInboundGroupSession = OlmInboundGroupSession.importSession(megolmSessionData.sessionKey!!)
|
||||
|
||||
if (!TextUtils.equals(olmInboundGroupSession!!.sessionIdentifier(), megolmSessionData.sessionId)) {
|
||||
if (olmInboundGroupSession!!.sessionIdentifier() != megolmSessionData.sessionId) {
|
||||
throw Exception("Mismatched group session Id")
|
||||
}
|
||||
|
||||
|
@ -16,7 +16,6 @@
|
||||
|
||||
package im.vector.matrix.android.internal.crypto.store.db
|
||||
|
||||
import android.text.TextUtils
|
||||
import im.vector.matrix.android.api.auth.data.Credentials
|
||||
import im.vector.matrix.android.internal.crypto.IncomingRoomKeyRequest
|
||||
import im.vector.matrix.android.internal.crypto.NewSessionListener
|
||||
@ -101,8 +100,8 @@ internal class RealmCryptoStore(private val realmConfiguration: RealmConfigurati
|
||||
// Check credentials
|
||||
// The device id may not have been provided in credentials.
|
||||
// Check it only if provided, else trust the stored one.
|
||||
if (!TextUtils.equals(currentMetadata.userId, credentials.userId)
|
||||
|| (credentials.deviceId != null && !TextUtils.equals(credentials.deviceId, currentMetadata.deviceId))) {
|
||||
if (currentMetadata.userId != credentials.userId
|
||||
|| (credentials.deviceId != null && credentials.deviceId != currentMetadata.deviceId)) {
|
||||
Timber.w("## open() : Credentials do not match, close this store and delete data")
|
||||
deleteAll = true
|
||||
currentMetadata = null
|
||||
|
@ -44,18 +44,14 @@ internal class DefaultClaimOneTimeKeysForUsersDevice @Inject constructor(private
|
||||
}
|
||||
val map = MXUsersDevicesMap<MXKey>()
|
||||
keysClaimResponse.oneTimeKeys?.let { oneTimeKeys ->
|
||||
for (userId in oneTimeKeys.keys) {
|
||||
val mapByUserId = oneTimeKeys[userId]
|
||||
for ((userId, mapByUserId) in oneTimeKeys) {
|
||||
for ((deviceId, deviceKey) in mapByUserId) {
|
||||
val mxKey = MXKey.from(deviceKey)
|
||||
|
||||
if (mapByUserId != null) {
|
||||
for (deviceId in mapByUserId.keys) {
|
||||
val mxKey = MXKey.from(mapByUserId[deviceId])
|
||||
|
||||
if (mxKey != null) {
|
||||
map.setObject(userId, deviceId, mxKey)
|
||||
} else {
|
||||
Timber.e("## claimOneTimeKeysForUsersDevices : fail to create a MXKey")
|
||||
}
|
||||
if (mxKey != null) {
|
||||
map.setObject(userId, deviceId, mxKey)
|
||||
} else {
|
||||
Timber.e("## claimOneTimeKeysForUsersDevices : fail to create a MXKey")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,13 +16,11 @@
|
||||
|
||||
package im.vector.matrix.android.internal.crypto.tasks
|
||||
|
||||
import android.text.TextUtils
|
||||
import im.vector.matrix.android.internal.crypto.api.CryptoApi
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.KeysQueryBody
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.KeysQueryResponse
|
||||
import im.vector.matrix.android.internal.network.executeRequest
|
||||
import im.vector.matrix.android.internal.task.Task
|
||||
import java.util.*
|
||||
import javax.inject.Inject
|
||||
|
||||
internal interface DownloadKeysForUsersTask : Task<DownloadKeysForUsersTask.Params, KeysQueryResponse> {
|
||||
@ -37,19 +35,13 @@ internal class DefaultDownloadKeysForUsers @Inject constructor(private val crypt
|
||||
: DownloadKeysForUsersTask {
|
||||
|
||||
override suspend fun execute(params: DownloadKeysForUsersTask.Params): KeysQueryResponse {
|
||||
val downloadQuery = HashMap<String, Map<String, Any>>()
|
||||
|
||||
if (null != params.userIds) {
|
||||
for (userId in params.userIds) {
|
||||
downloadQuery[userId] = HashMap()
|
||||
}
|
||||
}
|
||||
val downloadQuery = params.userIds?.associateWith { emptyMap<String, Any>() }.orEmpty()
|
||||
|
||||
val body = KeysQueryBody(
|
||||
deviceKeys = downloadQuery
|
||||
)
|
||||
|
||||
if (!TextUtils.isEmpty(params.token)) {
|
||||
if (!params.token.isNullOrEmpty()) {
|
||||
body.token = params.token
|
||||
}
|
||||
|
||||
|
@ -16,7 +16,6 @@
|
||||
|
||||
package im.vector.matrix.android.internal.crypto.tasks
|
||||
|
||||
import android.text.TextUtils
|
||||
import im.vector.matrix.android.internal.crypto.api.CryptoApi
|
||||
import im.vector.matrix.android.internal.crypto.model.rest.UpdateDeviceInfoBody
|
||||
import im.vector.matrix.android.internal.network.executeRequest
|
||||
@ -37,7 +36,7 @@ internal class DefaultSetDeviceNameTask @Inject constructor(private val cryptoAp
|
||||
|
||||
override suspend fun execute(params: SetDeviceNameTask.Params) {
|
||||
val body = UpdateDeviceInfoBody(
|
||||
displayName = if (TextUtils.isEmpty(params.deviceName)) "" else params.deviceName
|
||||
displayName = params.deviceName
|
||||
)
|
||||
return executeRequest {
|
||||
apiCall = cryptoApi.updateDeviceInfo(params.deviceId, body)
|
||||
|
@ -17,7 +17,6 @@
|
||||
package im.vector.matrix.android.internal.network
|
||||
|
||||
import android.content.Context
|
||||
import android.text.TextUtils
|
||||
import im.vector.matrix.android.BuildConfig
|
||||
import im.vector.matrix.android.internal.di.MatrixScope
|
||||
import timber.log.Timber
|
||||
@ -60,10 +59,10 @@ internal class UserAgentHolder @Inject constructor(private val context: Context)
|
||||
Timber.e(e, "## initUserAgent() : failed")
|
||||
}
|
||||
|
||||
var systemUserAgent = System.getProperty("http.agent")
|
||||
val systemUserAgent = System.getProperty("http.agent")
|
||||
|
||||
// cannot retrieve the application version
|
||||
if (TextUtils.isEmpty(appName) || TextUtils.isEmpty(appVersion)) {
|
||||
if (appName.isEmpty() || appVersion.isEmpty()) {
|
||||
if (null == systemUserAgent) {
|
||||
userAgent = "Java" + System.getProperty("java.version")
|
||||
}
|
||||
|
@ -75,9 +75,7 @@ internal class DefaultContentUploadStateTracker @Inject constructor() : ContentU
|
||||
private fun updateState(key: String, state: ContentUploadStateTracker.State) {
|
||||
states[key] = state
|
||||
mainHandler.post {
|
||||
listeners[key]?.also { listeners ->
|
||||
listeners.forEach { it.onUpdate(state) }
|
||||
}
|
||||
listeners[key]?.forEach { it.onUpdate(state) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -65,13 +65,11 @@ internal class DefaultGetGroupDataTask @Inject constructor(
|
||||
groupSummaryEntity.displayName = if (name.isNullOrEmpty()) groupId else name
|
||||
groupSummaryEntity.shortDescription = groupSummary.profile?.shortDescription ?: ""
|
||||
|
||||
val roomIds = groupRooms.rooms.map { it.roomId }
|
||||
groupSummaryEntity.roomIds.clear()
|
||||
groupSummaryEntity.roomIds.addAll(roomIds)
|
||||
groupRooms.rooms.mapTo(groupSummaryEntity.roomIds) { it.roomId }
|
||||
|
||||
val userIds = groupUsers.users.map { it.userId }
|
||||
groupSummaryEntity.userIds.clear()
|
||||
groupSummaryEntity.userIds.addAll(userIds)
|
||||
groupUsers.users.mapTo(groupSummaryEntity.userIds) { it.userId }
|
||||
|
||||
groupSummaryEntity.membership = when (groupSummary.user?.membership) {
|
||||
Membership.JOIN.value -> Membership.JOIN
|
||||
|
@ -50,19 +50,15 @@ internal class DefaultProcessEventForPushTask @Inject constructor(
|
||||
defaultPushRuleService.dispatchRoomJoined(it)
|
||||
}
|
||||
val newJoinEvents = params.syncResponse.join
|
||||
.map { entries ->
|
||||
entries.value.timeline?.events?.map { it.copy(roomId = entries.key) }
|
||||
.mapNotNull { (key, value) ->
|
||||
value.timeline?.events?.map { it.copy(roomId = key) }
|
||||
}
|
||||
.fold(emptyList<Event>(), { acc, next ->
|
||||
acc + (next ?: emptyList())
|
||||
})
|
||||
.flatten()
|
||||
val inviteEvents = params.syncResponse.invite
|
||||
.map { entries ->
|
||||
entries.value.inviteState?.events?.map { it.copy(roomId = entries.key) }
|
||||
.mapNotNull { (key, value) ->
|
||||
value.inviteState?.events?.map { it.copy(roomId = key) }
|
||||
}
|
||||
.fold(emptyList<Event>(), { acc, next ->
|
||||
acc + (next ?: emptyList())
|
||||
})
|
||||
.flatten()
|
||||
val allEvents = (newJoinEvents + inviteEvents).filter { event ->
|
||||
when (event.type) {
|
||||
EventType.MESSAGE,
|
||||
@ -84,16 +80,12 @@ internal class DefaultProcessEventForPushTask @Inject constructor(
|
||||
}
|
||||
|
||||
val allRedactedEvents = params.syncResponse.join
|
||||
.map { entries ->
|
||||
entries.value.timeline?.events?.filter {
|
||||
it.type == EventType.REDACTION
|
||||
}
|
||||
.orEmpty()
|
||||
.mapNotNull { it.redacts }
|
||||
}
|
||||
.fold(emptyList<String>(), { acc, next ->
|
||||
acc + next
|
||||
})
|
||||
.asSequence()
|
||||
.mapNotNull { (_, value) -> value.timeline?.events }
|
||||
.flatten()
|
||||
.filter { it.type == EventType.REDACTION }
|
||||
.mapNotNull { it.redacts }
|
||||
.toList()
|
||||
|
||||
Timber.v("[PushRules] Found ${allRedactedEvents.size} redacted events")
|
||||
|
||||
@ -107,18 +99,11 @@ internal class DefaultProcessEventForPushTask @Inject constructor(
|
||||
private fun fulfilledBingRule(event: Event, rules: List<PushRule>): PushRule? {
|
||||
// TODO This should be injected
|
||||
val conditionResolver = DefaultConditionResolver(event, roomService, userId)
|
||||
rules.filter { it.enabled }.forEach { rule ->
|
||||
val isFullfilled = rule.conditions?.map {
|
||||
return rules.firstOrNull { rule ->
|
||||
// All conditions must hold true for an event in order to apply the action for the event.
|
||||
rule.enabled && rule.conditions?.all {
|
||||
it.asExecutableCondition()?.isSatisfied(conditionResolver) ?: false
|
||||
}?.fold(true/*A rule with no conditions always matches*/, { acc, next ->
|
||||
// All conditions must hold true for an event in order to apply the action for the event.
|
||||
acc && next
|
||||
}) ?: false
|
||||
|
||||
if (isFullfilled) {
|
||||
return rule
|
||||
}
|
||||
} ?: false
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
@ -132,8 +132,7 @@ internal class RoomMembers(private val realm: Realm,
|
||||
.findAll()
|
||||
.map { it.asDomain() }
|
||||
.associateBy { it.stateKey!! }
|
||||
.mapValues { it.value.content.toModel<RoomMember>()!! }
|
||||
.filterValues { predicate(it) }
|
||||
.filterValues { predicate(it.content.toModel<RoomMember>()!!) }
|
||||
.keys
|
||||
.toList()
|
||||
}
|
||||
|
@ -49,23 +49,22 @@ internal class DefaultFindReactionEventForUndoTask @Inject constructor(private v
|
||||
}
|
||||
|
||||
private fun getReactionToRedact(realm: Realm, reaction: String, eventId: String): EventEntity? {
|
||||
val summary = EventAnnotationsSummaryEntity.where(realm, eventId).findFirst()
|
||||
if (summary != null) {
|
||||
summary.reactionsSummary.where()
|
||||
.equalTo(ReactionAggregatedSummaryEntityFields.KEY, reaction)
|
||||
.findFirst()?.let {
|
||||
// want to find the event orignated by me!
|
||||
it.sourceEvents.forEach {
|
||||
// find source event
|
||||
EventEntity.where(realm, it).findFirst()?.let { eventEntity ->
|
||||
// is it mine?
|
||||
if (eventEntity.sender == userId) {
|
||||
return eventEntity
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
val summary = EventAnnotationsSummaryEntity.where(realm, eventId).findFirst() ?: return null
|
||||
|
||||
val rase = summary.reactionsSummary.where()
|
||||
.equalTo(ReactionAggregatedSummaryEntityFields.KEY, reaction)
|
||||
.findFirst() ?: return null
|
||||
|
||||
// want to find the event orignated by me!
|
||||
return rase.sourceEvents
|
||||
.asSequence()
|
||||
.mapNotNull {
|
||||
// find source event
|
||||
EventEntity.where(realm, it).findFirst()
|
||||
}
|
||||
.firstOrNull { eventEntity ->
|
||||
// is it mine?
|
||||
eventEntity.sender == userId
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -39,11 +39,10 @@ internal class TimelineEventDecryptor(
|
||||
override fun onNewSession(roomId: String?, senderKey: String, sessionId: String) {
|
||||
synchronized(unknownSessionsFailure) {
|
||||
val toDecryptAgain = ArrayList<String>()
|
||||
unknownSessionsFailure[sessionId]?.let { eventIds ->
|
||||
toDecryptAgain.addAll(eventIds)
|
||||
}
|
||||
val eventIds = unknownSessionsFailure[sessionId]
|
||||
if (eventIds != null) toDecryptAgain.addAll(eventIds)
|
||||
if (toDecryptAgain.isNotEmpty()) {
|
||||
unknownSessionsFailure[sessionId]?.clear()
|
||||
eventIds?.clear()
|
||||
toDecryptAgain.forEach {
|
||||
requestDecryption(it)
|
||||
}
|
||||
@ -72,17 +71,17 @@ internal class TimelineEventDecryptor(
|
||||
|
||||
fun requestDecryption(eventId: String) {
|
||||
synchronized(existingRequests) {
|
||||
if (existingRequests.contains(eventId)) {
|
||||
return Unit.also {
|
||||
Timber.d("Skip Decryption request for event $eventId, already requested")
|
||||
}
|
||||
if (eventId in existingRequests) {
|
||||
Timber.d("Skip Decryption request for event $eventId, already requested")
|
||||
return
|
||||
}
|
||||
existingRequests.add(eventId)
|
||||
}
|
||||
synchronized(unknownSessionsFailure) {
|
||||
unknownSessionsFailure.values.forEach {
|
||||
if (it.contains(eventId)) return@synchronized Unit.also {
|
||||
for (it in unknownSessionsFailure.values) {
|
||||
if (eventId in it) {
|
||||
Timber.d("Skip Decryption request for event $eventId, unknown session")
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -116,10 +115,7 @@ internal class TimelineEventDecryptor(
|
||||
event.content?.toModel<EncryptedEventContent>()?.let { content ->
|
||||
content.sessionId?.let { sessionId ->
|
||||
synchronized(unknownSessionsFailure) {
|
||||
val list = unknownSessionsFailure[sessionId]
|
||||
?: ArrayList<String>().also {
|
||||
unknownSessionsFailure[sessionId] = it
|
||||
}
|
||||
val list = unknownSessionsFailure.getOrPut(sessionId) { ArrayList() }
|
||||
list.add(eventId)
|
||||
}
|
||||
}
|
||||
|
@ -461,9 +461,9 @@ internal class SecretStoringUtils @Inject constructor(private val context: Conte
|
||||
inputCipher.init(Cipher.ENCRYPT_MODE, privateKeyEntry.certificate.publicKey)
|
||||
|
||||
val outputStream = ByteArrayOutputStream()
|
||||
val cipherOutputStream = CipherOutputStream(outputStream, inputCipher)
|
||||
cipherOutputStream.write(secret)
|
||||
cipherOutputStream.close()
|
||||
CipherOutputStream(outputStream, inputCipher).use {
|
||||
it.write(secret)
|
||||
}
|
||||
|
||||
return outputStream.toByteArray()
|
||||
}
|
||||
|
@ -16,7 +16,6 @@
|
||||
|
||||
package im.vector.matrix.android.internal.session.sync
|
||||
|
||||
import android.text.TextUtils
|
||||
import im.vector.matrix.android.api.session.crypto.MXCryptoError
|
||||
import im.vector.matrix.android.api.session.events.model.Event
|
||||
import im.vector.matrix.android.api.session.events.model.EventType
|
||||
@ -41,9 +40,9 @@ internal class CryptoSyncHandler @Inject constructor(private val cryptoService:
|
||||
initialSyncProgressService?.reportProgress(((index / total.toFloat()) * 100).toInt())
|
||||
// Decrypt event if necessary
|
||||
decryptEvent(event, null)
|
||||
if (TextUtils.equals(event.getClearType(), EventType.MESSAGE)
|
||||
if (event.getClearType() == EventType.MESSAGE
|
||||
&& event.getClearContent()?.toModel<MessageContent>()?.type == "m.bad.encrypted") {
|
||||
Timber.e("## handleToDeviceEvent() : Warning: Unable to decrypt to-device event : " + event.content)
|
||||
Timber.e("## handleToDeviceEvent() : Warning: Unable to decrypt to-device event : ${event.content}")
|
||||
} else {
|
||||
sasVerificationService.onToDeviceEvent(event)
|
||||
cryptoService.onToDeviceEvent(event)
|
||||
|
@ -31,7 +31,8 @@ import im.vector.matrix.android.internal.task.TaskThread
|
||||
import im.vector.matrix.android.internal.task.configureWith
|
||||
import timber.log.Timber
|
||||
import java.net.SocketTimeoutException
|
||||
import java.util.*
|
||||
import java.util.Timer
|
||||
import java.util.TimerTask
|
||||
|
||||
/**
|
||||
* Can execute periodic sync task.
|
||||
|
@ -31,18 +31,10 @@ internal class DirectChatsHelper @Inject constructor(@SessionDatabase
|
||||
*/
|
||||
fun getLocalUserAccount(filterRoomId: String? = null): MutableMap<String, MutableList<String>> {
|
||||
return Realm.getInstance(realmConfiguration).use { realm ->
|
||||
val currentDirectRooms = RoomSummaryEntity.getDirectRooms(realm)
|
||||
val directChatsMap = mutableMapOf<String, MutableList<String>>()
|
||||
for (directRoom in currentDirectRooms) {
|
||||
if (directRoom.roomId == filterRoomId) continue
|
||||
val directUserId = directRoom.directUserId ?: continue
|
||||
directChatsMap
|
||||
.getOrPut(directUserId, { arrayListOf() })
|
||||
.apply {
|
||||
add(directRoom.roomId)
|
||||
}
|
||||
}
|
||||
directChatsMap
|
||||
RoomSummaryEntity.getDirectRooms(realm)
|
||||
.asSequence()
|
||||
.filter { it.roomId != filterRoomId && it.directUserId != null }
|
||||
.groupByTo(mutableMapOf(), { it.directUserId!! }, { it.roomId })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ package im.vector.matrix.android.internal.task
|
||||
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.util.Cancelable
|
||||
import java.util.*
|
||||
import java.util.UUID
|
||||
|
||||
internal fun <PARAMS, RESULT> Task<PARAMS, RESULT>.configureWith(params: PARAMS,
|
||||
init: (ConfigurableTask.Builder<PARAMS, RESULT>.() -> Unit) = {}
|
||||
|
@ -59,19 +59,11 @@ object CompatUtil {
|
||||
private const val SHARED_KEY_ANDROID_VERSION_WHEN_KEY_HAS_BEEN_GENERATED = "android_version_when_key_has_been_generated"
|
||||
|
||||
private var sSecretKeyAndVersion: SecretKeyAndVersion? = null
|
||||
private var sPrng: SecureRandom? = null
|
||||
|
||||
/**
|
||||
* Returns the unique SecureRandom instance shared for all local storage encryption operations.
|
||||
*/
|
||||
private val prng: SecureRandom
|
||||
get() {
|
||||
if (sPrng == null) {
|
||||
sPrng = SecureRandom()
|
||||
}
|
||||
|
||||
return sPrng!!
|
||||
}
|
||||
private val prng: SecureRandom by lazy(LazyThreadSafetyMode.NONE) { SecureRandom() }
|
||||
|
||||
/**
|
||||
* Create a GZIPOutputStream instance
|
||||
|
@ -24,12 +24,9 @@ import java.security.MessageDigest
|
||||
fun String.md5() = try {
|
||||
val digest = MessageDigest.getInstance("md5")
|
||||
digest.update(toByteArray())
|
||||
val bytes = digest.digest()
|
||||
val sb = StringBuilder()
|
||||
for (i in bytes.indices) {
|
||||
sb.append(String.format("%02X", bytes[i]))
|
||||
}
|
||||
sb.toString().toLowerCase()
|
||||
digest.digest()
|
||||
.joinToString("") { String.format("%02X", it) }
|
||||
.toLowerCase()
|
||||
} catch (exc: Exception) {
|
||||
// Should not happen, but just in case
|
||||
hashCode().toString()
|
||||
|
@ -21,7 +21,6 @@ package im.vector.riotx.gplay.push.fcm
|
||||
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.text.TextUtils
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.ProcessLifecycleOwner
|
||||
import com.google.firebase.messaging.FirebaseMessagingService
|
||||
@ -214,10 +213,10 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() {
|
||||
}
|
||||
} else {
|
||||
if (notifiableEvent is NotifiableMessageEvent) {
|
||||
if (TextUtils.isEmpty(notifiableEvent.senderName)) {
|
||||
if (notifiableEvent.senderName.isEmpty()) {
|
||||
notifiableEvent.senderName = data["sender_display_name"] ?: data["sender"] ?: ""
|
||||
}
|
||||
if (TextUtils.isEmpty(notifiableEvent.roomName)) {
|
||||
if (notifiableEvent.roomName.isEmpty()) {
|
||||
notifiableEvent.roomName = findRoomNameBestEffort(data, session) ?: ""
|
||||
}
|
||||
}
|
||||
|
@ -18,7 +18,6 @@ package im.vector.riotx.core.dialogs
|
||||
|
||||
import android.app.Activity
|
||||
import android.text.Editable
|
||||
import android.text.TextUtils
|
||||
import android.widget.Button
|
||||
import android.widget.ImageView
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
@ -45,15 +44,15 @@ class ExportKeysDialog {
|
||||
val textWatcher = object : SimpleTextWatcher() {
|
||||
override fun afterTextChanged(s: Editable) {
|
||||
when {
|
||||
TextUtils.isEmpty(passPhrase1EditText.text) -> {
|
||||
passPhrase1EditText.text.isNullOrEmpty() -> {
|
||||
exportButton.isEnabled = false
|
||||
passPhrase2Til.error = null
|
||||
}
|
||||
TextUtils.equals(passPhrase1EditText.text, passPhrase2EditText.text) -> {
|
||||
passPhrase1EditText.text == passPhrase2EditText.text -> {
|
||||
exportButton.isEnabled = true
|
||||
passPhrase2Til.error = null
|
||||
}
|
||||
else -> {
|
||||
else -> {
|
||||
exportButton.isEnabled = false
|
||||
passPhrase2Til.error = activity.getString(R.string.passphrase_passphrase_does_not_match)
|
||||
}
|
||||
|
@ -21,9 +21,7 @@ import android.content.ClipDescription
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.text.TextUtils
|
||||
import androidx.core.util.PatternsCompat.WEB_URL
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Inspired from Riot code: RoomMediaMessage.java
|
||||
@ -69,34 +67,28 @@ fun analyseIntent(intent: Intent): List<ExternalIntentData> {
|
||||
|
||||
// chrome adds many items when sharing an web page link
|
||||
// so, test first the type
|
||||
if (TextUtils.equals(intent.type, ClipDescription.MIMETYPE_TEXT_PLAIN)) {
|
||||
if (intent.type == ClipDescription.MIMETYPE_TEXT_PLAIN) {
|
||||
var message: String? = intent.getStringExtra(Intent.EXTRA_TEXT)
|
||||
|
||||
if (null == message) {
|
||||
val sequence = intent.getCharSequenceExtra(Intent.EXTRA_TEXT)
|
||||
if (null != sequence) {
|
||||
message = sequence.toString()
|
||||
}
|
||||
}
|
||||
?: intent.getCharSequenceExtra(Intent.EXTRA_TEXT)?.toString()
|
||||
|
||||
val subject = intent.getStringExtra(Intent.EXTRA_SUBJECT)
|
||||
|
||||
if (!TextUtils.isEmpty(subject)) {
|
||||
if (TextUtils.isEmpty(message)) {
|
||||
if (!subject.isNullOrEmpty()) {
|
||||
if (message.isNullOrEmpty()) {
|
||||
message = subject
|
||||
} else if (WEB_URL.matcher(message!!).matches()) {
|
||||
} else if (WEB_URL.matcher(message).matches()) {
|
||||
message = subject + "\n" + message
|
||||
}
|
||||
}
|
||||
|
||||
if (!TextUtils.isEmpty(message)) {
|
||||
externalIntentDataList.add(ExternalIntentData.IntentDataText(message!!, null, intent.type))
|
||||
if (!message.isNullOrEmpty()) {
|
||||
externalIntentDataList.add(ExternalIntentData.IntentDataText(message, null, intent.type))
|
||||
return externalIntentDataList
|
||||
}
|
||||
}
|
||||
|
||||
var clipData: ClipData? = null
|
||||
var mimetypes: MutableList<String>? = null
|
||||
var mimeTypes: List<String>? = null
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
|
||||
clipData = intent.clipData
|
||||
@ -106,41 +98,26 @@ fun analyseIntent(intent: Intent): List<ExternalIntentData> {
|
||||
if (null != clipData) {
|
||||
if (null != clipData.description) {
|
||||
if (0 != clipData.description.mimeTypeCount) {
|
||||
mimetypes = ArrayList()
|
||||
|
||||
for (i in 0 until clipData.description.mimeTypeCount) {
|
||||
mimetypes.add(clipData.description.getMimeType(i))
|
||||
mimeTypes = with(clipData.description) {
|
||||
List(mimeTypeCount) { getMimeType(it) }
|
||||
}
|
||||
|
||||
// if the filter is "accept anything" the mimetype does not make sense
|
||||
if (1 == mimetypes.size) {
|
||||
if (mimetypes[0].endsWith("/*")) {
|
||||
mimetypes = null
|
||||
if (1 == mimeTypes.size) {
|
||||
if (mimeTypes[0].endsWith("/*")) {
|
||||
mimeTypes = null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val count = clipData.itemCount
|
||||
|
||||
for (i in 0 until count) {
|
||||
for (i in 0 until clipData.itemCount) {
|
||||
val item = clipData.getItemAt(i)
|
||||
var mimetype: String? = null
|
||||
val mimeType = mimeTypes?.getOrElse(i) { mimeTypes[0] }
|
||||
// uris list is not a valid mimetype
|
||||
.takeUnless { it == ClipDescription.MIMETYPE_TEXT_URILIST }
|
||||
|
||||
if (null != mimetypes) {
|
||||
if (i < mimetypes.size) {
|
||||
mimetype = mimetypes[i]
|
||||
} else {
|
||||
mimetype = mimetypes[0]
|
||||
}
|
||||
|
||||
// uris list is not a valid mimetype
|
||||
if (TextUtils.equals(mimetype, ClipDescription.MIMETYPE_TEXT_URILIST)) {
|
||||
mimetype = null
|
||||
}
|
||||
}
|
||||
|
||||
externalIntentDataList.add(ExternalIntentData.IntentDataClipData(item, mimetype))
|
||||
externalIntentDataList.add(ExternalIntentData.IntentDataClipData(item, mimeType))
|
||||
}
|
||||
} else if (null != intent.data) {
|
||||
externalIntentDataList.add(ExternalIntentData.IntentDataUri(intent.data!!))
|
||||
|
@ -17,7 +17,6 @@
|
||||
package im.vector.riotx.core.preference
|
||||
|
||||
import android.content.Context
|
||||
import android.text.TextUtils
|
||||
import android.util.AttributeSet
|
||||
import android.view.View
|
||||
import android.widget.RadioGroup
|
||||
@ -84,7 +83,7 @@ class BingRulePreference : VectorPreference {
|
||||
val ruleStatusIndex: Int
|
||||
get() {
|
||||
if (null != rule) {
|
||||
if (TextUtils.equals(rule!!.ruleId, BingRule.RULE_ID_SUPPRESS_BOTS_NOTIFICATIONS)) {
|
||||
if (rule!!.ruleId == BingRule.RULE_ID_SUPPRESS_BOTS_NOTIFICATIONS) {
|
||||
if (rule!!.shouldNotNotify()) {
|
||||
return if (rule!!.isEnabled) {
|
||||
NOTIFICATION_OFF_INDEX
|
||||
@ -143,7 +142,7 @@ class BingRulePreference : VectorPreference {
|
||||
if (null != this.rule && index != ruleStatusIndex) {
|
||||
rule = BingRule(this.rule!!)
|
||||
|
||||
if (TextUtils.equals(rule.ruleId, BingRule.RULE_ID_SUPPRESS_BOTS_NOTIFICATIONS)) {
|
||||
if (rule.ruleId == BingRule.RULE_ID_SUPPRESS_BOTS_NOTIFICATIONS) {
|
||||
when (index) {
|
||||
NOTIFICATION_OFF_INDEX -> {
|
||||
rule.isEnabled = true
|
||||
@ -164,8 +163,8 @@ class BingRulePreference : VectorPreference {
|
||||
}
|
||||
|
||||
if (NOTIFICATION_OFF_INDEX == index) {
|
||||
if (TextUtils.equals(this.rule!!.kind, BingRule.KIND_UNDERRIDE)
|
||||
|| TextUtils.equals(rule.ruleId, BingRule.RULE_ID_SUPPRESS_BOTS_NOTIFICATIONS)) {
|
||||
if (this.rule!!.kind == BingRule.KIND_UNDERRIDE
|
||||
|| rule.ruleId == BingRule.RULE_ID_SUPPRESS_BOTS_NOTIFICATIONS) {
|
||||
rule.setNotify(false)
|
||||
} else {
|
||||
rule.isEnabled = false
|
||||
@ -173,11 +172,11 @@ class BingRulePreference : VectorPreference {
|
||||
} else {
|
||||
rule.isEnabled = true
|
||||
rule.setNotify(true)
|
||||
rule.setHighlight(!TextUtils.equals(this.rule!!.kind, BingRule.KIND_UNDERRIDE)
|
||||
&& !TextUtils.equals(rule.ruleId, BingRule.RULE_ID_INVITE_ME)
|
||||
rule.setHighlight(this.rule!!.kind != BingRule.KIND_UNDERRIDE
|
||||
&& rule.ruleId != BingRule.RULE_ID_INVITE_ME
|
||||
&& NOTIFICATION_NOISY_INDEX == index)
|
||||
if (NOTIFICATION_NOISY_INDEX == index) {
|
||||
rule.notificationSound = if (TextUtils.equals(rule.ruleId, BingRule.RULE_ID_CALL)) {
|
||||
rule.notificationSound = if (rule.ruleId == BingRule.RULE_ID_CALL) {
|
||||
BingRule.ACTION_VALUE_RING
|
||||
} else {
|
||||
BingRule.ACTION_VALUE_DEFAULT
|
||||
|
@ -18,7 +18,6 @@ package im.vector.riotx.core.resources
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.text.TextUtils
|
||||
import android.webkit.MimeTypeMap
|
||||
import im.vector.riotx.core.utils.getFileExtension
|
||||
import timber.log.Timber
|
||||
@ -73,7 +72,7 @@ fun openResource(context: Context, uri: Uri, providedMimetype: String?): Resourc
|
||||
var mimetype = providedMimetype
|
||||
try {
|
||||
// if the mime type is not provided, try to find it out
|
||||
if (TextUtils.isEmpty(mimetype)) {
|
||||
if (mimetype.isNullOrEmpty()) {
|
||||
mimetype = context.contentResolver.getType(uri)
|
||||
|
||||
// try to find the mimetype from the filename
|
||||
|
@ -20,7 +20,6 @@ import android.content.Context
|
||||
import android.graphics.Color
|
||||
import android.text.SpannableString
|
||||
import android.text.TextPaint
|
||||
import android.text.TextUtils
|
||||
import android.text.method.LinkMovementMethod
|
||||
import android.text.style.ClickableSpan
|
||||
import android.util.AttributeSet
|
||||
@ -168,7 +167,7 @@ class NotificationAreaView @JvmOverloads constructor(
|
||||
} else {
|
||||
imageView.setImageResource(R.drawable.scrolldown)
|
||||
messageView.setTextColor(ThemeUtils.getColor(context, R.attr.vctr_room_notification_text_color))
|
||||
if (!TextUtils.isEmpty(state.message)) {
|
||||
if (!state.message.isNullOrEmpty()) {
|
||||
messageView.text = SpannableString(state.message)
|
||||
}
|
||||
}
|
||||
|
@ -17,7 +17,6 @@
|
||||
package im.vector.riotx.core.utils
|
||||
|
||||
import android.content.Context
|
||||
import android.text.TextUtils
|
||||
import timber.log.Timber
|
||||
import java.io.File
|
||||
|
||||
@ -60,7 +59,7 @@ private fun logAction(file: File): Boolean {
|
||||
if (file.isDirectory) {
|
||||
Timber.v(file.toString())
|
||||
} else {
|
||||
Timber.v(file.toString() + " " + file.length() + " bytes")
|
||||
Timber.v("$file ${file.length()} bytes")
|
||||
}
|
||||
return true
|
||||
}
|
||||
@ -96,26 +95,19 @@ private fun recursiveActionOnFile(file: File, action: ActionOnFile): Boolean {
|
||||
fun getFileExtension(fileUri: String): String? {
|
||||
var reducedStr = fileUri
|
||||
|
||||
if (!TextUtils.isEmpty(reducedStr)) {
|
||||
if (reducedStr.isNotEmpty()) {
|
||||
// Remove fragment
|
||||
val fragment = fileUri.lastIndexOf('#')
|
||||
if (fragment > 0) {
|
||||
reducedStr = fileUri.substring(0, fragment)
|
||||
}
|
||||
reducedStr = reducedStr.substringBeforeLast('#')
|
||||
|
||||
// Remove query
|
||||
val query = reducedStr.lastIndexOf('?')
|
||||
if (query > 0) {
|
||||
reducedStr = reducedStr.substring(0, query)
|
||||
}
|
||||
reducedStr = reducedStr.substringBeforeLast('?')
|
||||
|
||||
// Remove path
|
||||
val filenamePos = reducedStr.lastIndexOf('/')
|
||||
val filename = if (0 <= filenamePos) reducedStr.substring(filenamePos + 1) else reducedStr
|
||||
val filename = reducedStr.substringAfterLast('/')
|
||||
|
||||
// Contrary to method MimeTypeMap.getFileExtensionFromUrl, we do not check the pattern
|
||||
// See https://stackoverflow.com/questions/14320527/android-should-i-use-mimetypemap-getfileextensionfromurl-bugs
|
||||
if (!filename.isEmpty()) {
|
||||
if (filename.isNotEmpty()) {
|
||||
val dotPos = filename.lastIndexOf('.')
|
||||
if (0 <= dotPos) {
|
||||
val ext = filename.substring(dotPos + 1)
|
||||
@ -135,14 +127,10 @@ fun getFileExtension(fileUri: String): String? {
|
||||
* ========================================================================================== */
|
||||
|
||||
fun getSizeOfFiles(context: Context, root: File): Int {
|
||||
Timber.v("Get size of " + root.absolutePath)
|
||||
return if (root.isDirectory) {
|
||||
root.list()
|
||||
.map {
|
||||
getSizeOfFiles(context, File(root, it))
|
||||
}
|
||||
.fold(0, { acc, other -> acc + other })
|
||||
} else {
|
||||
root.length().toInt()
|
||||
}
|
||||
return root.walkTopDown()
|
||||
.onEnter {
|
||||
Timber.v("Get size of ${it.absolutePath}")
|
||||
true
|
||||
}
|
||||
.sumBy { it.length().toInt() }
|
||||
}
|
||||
|
@ -21,7 +21,6 @@ import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import android.text.TextUtils
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.core.app.ActivityCompat
|
||||
@ -29,7 +28,6 @@ import androidx.core.content.ContextCompat
|
||||
import androidx.fragment.app.Fragment
|
||||
import im.vector.riotx.R
|
||||
import timber.log.Timber
|
||||
import java.util.*
|
||||
|
||||
private const val LOG_TAG = "PermissionUtils"
|
||||
|
||||
@ -72,7 +70,7 @@ const val PERMISSION_REQUEST_CODE_DOWNLOAD_FILE = 575
|
||||
*/
|
||||
fun logPermissionStatuses(context: Context) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
val permissions = Arrays.asList(
|
||||
val permissions = listOf(
|
||||
Manifest.permission.CAMERA,
|
||||
Manifest.permission.RECORD_AUDIO,
|
||||
Manifest.permission.WRITE_EXTERNAL_STORAGE,
|
||||
@ -221,25 +219,25 @@ private fun checkPermissions(permissionsToBeGrantedBitMap: Int,
|
||||
permissionListAlreadyDenied.forEach {
|
||||
when (it) {
|
||||
Manifest.permission.CAMERA -> {
|
||||
if (!TextUtils.isEmpty(explanationMessage)) {
|
||||
if (explanationMessage.isNotEmpty()) {
|
||||
explanationMessage += "\n\n"
|
||||
}
|
||||
explanationMessage += activity.getString(R.string.permissions_rationale_msg_camera)
|
||||
}
|
||||
Manifest.permission.RECORD_AUDIO -> {
|
||||
if (!TextUtils.isEmpty(explanationMessage)) {
|
||||
if (explanationMessage.isNotEmpty()) {
|
||||
explanationMessage += "\n\n"
|
||||
}
|
||||
explanationMessage += activity.getString(R.string.permissions_rationale_msg_record_audio)
|
||||
}
|
||||
Manifest.permission.WRITE_EXTERNAL_STORAGE -> {
|
||||
if (!TextUtils.isEmpty(explanationMessage)) {
|
||||
if (explanationMessage.isNotEmpty()) {
|
||||
explanationMessage += "\n\n"
|
||||
}
|
||||
explanationMessage += activity.getString(R.string.permissions_rationale_msg_storage)
|
||||
}
|
||||
Manifest.permission.READ_CONTACTS -> {
|
||||
if (!TextUtils.isEmpty(explanationMessage)) {
|
||||
if (!explanationMessage.isEmpty()) {
|
||||
explanationMessage += "\n\n"
|
||||
}
|
||||
explanationMessage += activity.getString(R.string.permissions_rationale_msg_contacts)
|
||||
@ -255,7 +253,7 @@ private fun checkPermissions(permissionsToBeGrantedBitMap: Int,
|
||||
.setMessage(explanationMessage)
|
||||
.setOnCancelListener { Toast.makeText(activity, R.string.missing_permissions_warning, Toast.LENGTH_SHORT).show() }
|
||||
.setPositiveButton(R.string.ok) { _, _ ->
|
||||
if (!permissionsListToBeGranted.isEmpty()) {
|
||||
if (permissionsListToBeGranted.isNotEmpty()) {
|
||||
fragment?.requestPermissions(permissionsListToBeGranted.toTypedArray(), requestCode)
|
||||
?: run {
|
||||
ActivityCompat.requestPermissions(activity, permissionsListToBeGranted.toTypedArray(), requestCode)
|
||||
|
@ -24,9 +24,9 @@ import java.util.*
|
||||
object TextUtils {
|
||||
|
||||
private val suffixes = TreeMap<Int, String>().also {
|
||||
it.put(1000, "k")
|
||||
it.put(1000000, "M")
|
||||
it.put(1000000000, "G")
|
||||
it[1000] = "k"
|
||||
it[1000000] = "M"
|
||||
it[1000000000] = "G"
|
||||
}
|
||||
|
||||
fun formatCountToShortDecimal(value: Int): String {
|
||||
|
@ -17,7 +17,6 @@ package im.vector.riotx.features.crypto.keysbackup.setup
|
||||
|
||||
import android.os.AsyncTask
|
||||
import android.os.Bundle
|
||||
import android.text.TextUtils
|
||||
import android.view.ViewGroup
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import android.widget.EditText
|
||||
@ -122,7 +121,7 @@ class KeysBackupSetupStep2Fragment : VectorBaseFragment() {
|
||||
})
|
||||
|
||||
viewModel.passphrase.observe(this, Observer<String> { newValue ->
|
||||
if (TextUtils.isEmpty(newValue)) {
|
||||
if (newValue.isEmpty()) {
|
||||
viewModel.passwordStrength.value = null
|
||||
} else {
|
||||
AsyncTask.execute {
|
||||
@ -172,7 +171,7 @@ class KeysBackupSetupStep2Fragment : VectorBaseFragment() {
|
||||
@OnClick(R.id.keys_backup_setup_step2_button)
|
||||
fun doNext() {
|
||||
when {
|
||||
TextUtils.isEmpty(viewModel.passphrase.value) -> {
|
||||
viewModel.passphrase.value.isNullOrEmpty() -> {
|
||||
viewModel.passphraseError.value = context?.getString(R.string.passphrase_empty_error_message)
|
||||
}
|
||||
viewModel.passphrase.value != viewModel.confirmPassphrase.value -> {
|
||||
@ -192,7 +191,7 @@ class KeysBackupSetupStep2Fragment : VectorBaseFragment() {
|
||||
@OnClick(R.id.keys_backup_setup_step2_skip_button)
|
||||
fun skipPassphrase() {
|
||||
when {
|
||||
TextUtils.isEmpty(viewModel.passphrase.value) -> {
|
||||
viewModel.passphrase.value.isNullOrEmpty() -> {
|
||||
// Generate a recovery key for the user
|
||||
viewModel.megolmBackupCreationInfo = null
|
||||
|
||||
|
@ -20,7 +20,6 @@
|
||||
package im.vector.riotx.features.crypto.keysrequest
|
||||
|
||||
import android.content.Context
|
||||
import android.text.TextUtils
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.session.Session
|
||||
import im.vector.matrix.android.api.session.crypto.keyshare.RoomKeysRequestListener
|
||||
@ -39,7 +38,8 @@ import im.vector.riotx.features.popup.PopupAlertManager
|
||||
import timber.log.Timber
|
||||
import java.text.DateFormat
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import java.util.Locale
|
||||
import java.util.Date
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
import kotlin.collections.ArrayList
|
||||
@ -100,7 +100,7 @@ class KeyRequestHandler @Inject constructor(private val context: Context)
|
||||
alertsToRequests[mappingKey] = ArrayList<IncomingRoomKeyRequest>().apply { this.add(request) }
|
||||
|
||||
// Add a notification for every incoming request
|
||||
session?.downloadKeys(Arrays.asList(userId), false, object : MatrixCallback<MXUsersDevicesMap<MXDeviceInfo>> {
|
||||
session?.downloadKeys(listOf(userId), false, object : MatrixCallback<MXUsersDevicesMap<MXDeviceInfo>> {
|
||||
override fun onSuccess(data: MXUsersDevicesMap<MXDeviceInfo>) {
|
||||
val deviceInfo = data.getObject(userId, deviceId)
|
||||
|
||||
@ -147,7 +147,7 @@ class KeyRequestHandler @Inject constructor(private val context: Context)
|
||||
wasNewDevice: Boolean,
|
||||
deviceInfo: MXDeviceInfo?,
|
||||
moreInfo: DeviceInfo? = null) {
|
||||
val deviceName = if (TextUtils.isEmpty(deviceInfo!!.displayName())) deviceInfo.deviceId else deviceInfo.displayName()
|
||||
val deviceName = if (deviceInfo!!.displayName().isNullOrEmpty()) deviceInfo.deviceId else deviceInfo.displayName()
|
||||
val dialogText: String?
|
||||
|
||||
if (moreInfo != null) {
|
||||
@ -244,12 +244,12 @@ class KeyRequestHandler @Inject constructor(private val context: Context)
|
||||
val deviceId = request.deviceId
|
||||
val requestId = request.requestId
|
||||
|
||||
if (TextUtils.isEmpty(userId) || TextUtils.isEmpty(deviceId) || TextUtils.isEmpty(requestId)) {
|
||||
if (userId.isNullOrEmpty() || deviceId.isNullOrEmpty() || requestId.isNullOrEmpty()) {
|
||||
Timber.e("## handleKeyRequestCancellation() : invalid parameters")
|
||||
return
|
||||
}
|
||||
|
||||
val alertMgrUniqueKey = alertManagerId(deviceId!!, userId!!)
|
||||
val alertMgrUniqueKey = alertManagerId(deviceId, userId)
|
||||
alertsToRequests[alertMgrUniqueKey]?.removeAll {
|
||||
it.deviceId == request.deviceId
|
||||
&& it.userId == request.userId
|
||||
|
@ -17,7 +17,6 @@
|
||||
package im.vector.riotx.features.home.room.detail
|
||||
|
||||
import android.net.Uri
|
||||
import android.text.TextUtils
|
||||
import androidx.annotation.IdRes
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
@ -379,7 +378,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
||||
val document = parser.parse(finalText)
|
||||
val renderer = HtmlRenderer.builder().build()
|
||||
val htmlText = renderer.render(document)
|
||||
if (TextUtils.equals(finalText, htmlText)) {
|
||||
if (finalText == htmlText) {
|
||||
room.sendTextMessage(finalText)
|
||||
} else {
|
||||
room.sendFormattedTextMessage(finalText, htmlText)
|
||||
@ -404,19 +403,22 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
|
||||
|
||||
private fun legacyRiotQuoteText(quotedText: String?, myText: String): String {
|
||||
val messageParagraphs = quotedText?.split("\n\n".toRegex())?.dropLastWhile { it.isEmpty() }?.toTypedArray()
|
||||
val quotedTextMsg = StringBuilder()
|
||||
if (messageParagraphs != null) {
|
||||
for (i in messageParagraphs.indices) {
|
||||
if (messageParagraphs[i].trim() != "") {
|
||||
quotedTextMsg.append("> ").append(messageParagraphs[i])
|
||||
}
|
||||
return buildString {
|
||||
if (messageParagraphs != null) {
|
||||
for (i in messageParagraphs.indices) {
|
||||
if (messageParagraphs[i].isNotBlank()) {
|
||||
append("> ")
|
||||
append(messageParagraphs[i])
|
||||
}
|
||||
|
||||
if (i + 1 != messageParagraphs.size) {
|
||||
quotedTextMsg.append("\n\n")
|
||||
if (i != messageParagraphs.lastIndex) {
|
||||
append("\n\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
append("\n\n")
|
||||
append(myText)
|
||||
}
|
||||
return "$quotedTextMsg\n\n$myText"
|
||||
}
|
||||
|
||||
private fun handleChangeTopicSlashCommand(changeTopic: ParsedCommand.ChangeTopic) {
|
||||
|
@ -131,8 +131,8 @@ class RoomListViewModel @AssistedInject constructor(@Assisted initialState: Room
|
||||
|
||||
setState {
|
||||
copy(
|
||||
joiningRoomsIds = joiningRoomsIds.toMutableSet().apply { add(roomId) },
|
||||
rejectingErrorRoomsIds = rejectingErrorRoomsIds.toMutableSet().apply { remove(roomId) }
|
||||
joiningRoomsIds = joiningRoomsIds + roomId,
|
||||
rejectingErrorRoomsIds = rejectingErrorRoomsIds - roomId
|
||||
)
|
||||
}
|
||||
|
||||
@ -148,8 +148,8 @@ class RoomListViewModel @AssistedInject constructor(@Assisted initialState: Room
|
||||
|
||||
setState {
|
||||
copy(
|
||||
joiningRoomsIds = joiningRoomsIds.toMutableSet().apply { remove(roomId) },
|
||||
joiningErrorRoomsIds = joiningErrorRoomsIds.toMutableSet().apply { add(roomId) }
|
||||
joiningRoomsIds = joiningRoomsIds - roomId,
|
||||
joiningErrorRoomsIds = joiningErrorRoomsIds + roomId
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -167,8 +167,8 @@ class RoomListViewModel @AssistedInject constructor(@Assisted initialState: Room
|
||||
|
||||
setState {
|
||||
copy(
|
||||
rejectingRoomsIds = rejectingRoomsIds.toMutableSet().apply { add(roomId) },
|
||||
joiningErrorRoomsIds = joiningErrorRoomsIds.toMutableSet().apply { remove(roomId) }
|
||||
rejectingRoomsIds = rejectingRoomsIds + roomId,
|
||||
joiningErrorRoomsIds = joiningErrorRoomsIds - roomId
|
||||
)
|
||||
}
|
||||
|
||||
@ -186,8 +186,8 @@ class RoomListViewModel @AssistedInject constructor(@Assisted initialState: Room
|
||||
|
||||
setState {
|
||||
copy(
|
||||
rejectingRoomsIds = rejectingRoomsIds.toMutableSet().apply { remove(roomId) },
|
||||
rejectingErrorRoomsIds = rejectingErrorRoomsIds.toMutableSet().apply { add(roomId) }
|
||||
rejectingRoomsIds = rejectingRoomsIds - roomId,
|
||||
rejectingErrorRoomsIds = rejectingErrorRoomsIds + roomId
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -17,7 +17,6 @@
|
||||
package im.vector.riotx.features.homeserver
|
||||
|
||||
import android.content.Context
|
||||
import android.text.TextUtils
|
||||
import androidx.core.content.edit
|
||||
import androidx.preference.PreferenceManager
|
||||
import im.vector.riotx.R
|
||||
@ -41,11 +40,11 @@ object ServerUrlsRepository {
|
||||
fun setDefaultUrlsFromReferrer(context: Context, homeServerUrl: String, identityServerUrl: String) {
|
||||
PreferenceManager.getDefaultSharedPreferences(context)
|
||||
.edit {
|
||||
if (!TextUtils.isEmpty(homeServerUrl)) {
|
||||
if (homeServerUrl.isNotEmpty()) {
|
||||
putString(DEFAULT_REFERRER_HOME_SERVER_URL_PREF, homeServerUrl)
|
||||
}
|
||||
|
||||
if (!TextUtils.isEmpty(identityServerUrl)) {
|
||||
if (identityServerUrl.isNotEmpty()) {
|
||||
putString(DEFAULT_REFERRER_IDENTITY_SERVER_URL_PREF, identityServerUrl)
|
||||
}
|
||||
}
|
||||
|
@ -16,7 +16,6 @@
|
||||
|
||||
package im.vector.riotx.features.rageshake
|
||||
|
||||
import android.text.TextUtils
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.widget.Toast
|
||||
@ -122,7 +121,7 @@ class BugReportActivity : VectorBaseActivity() {
|
||||
object : BugReporter.IMXBugReportListener {
|
||||
override fun onUploadFailed(reason: String?) {
|
||||
try {
|
||||
if (!TextUtils.isEmpty(reason)) {
|
||||
if (!reason.isNullOrEmpty()) {
|
||||
if (forSuggestion) {
|
||||
Toast.makeText(this@BugReportActivity,
|
||||
getString(R.string.send_suggestion_failed, reason), Toast.LENGTH_LONG).show()
|
||||
|
@ -25,7 +25,6 @@ import android.content.Intent
|
||||
import android.graphics.Bitmap
|
||||
import android.os.AsyncTask
|
||||
import android.os.Build
|
||||
import android.text.TextUtils
|
||||
import android.view.View
|
||||
import im.vector.matrix.android.api.Matrix
|
||||
import im.vector.riotx.BuildConfig
|
||||
@ -166,14 +165,11 @@ class BugReporter @Inject constructor(private val activeSessionHolder: ActiveSes
|
||||
|
||||
if (withDevicesLogs) {
|
||||
val files = vectorFileLogger.getLogFiles()
|
||||
|
||||
for (f in files) {
|
||||
files.mapNotNullTo(gzippedFiles) { f ->
|
||||
if (!mIsCancelled) {
|
||||
val gzippedFile = compressFile(f)
|
||||
|
||||
if (null != gzippedFile) {
|
||||
gzippedFiles.add(gzippedFile)
|
||||
}
|
||||
compressFile(f)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -244,7 +240,7 @@ class BugReporter @Inject constructor(private val activeSessionHolder: ActiveSes
|
||||
.addFormDataPart("theme", ThemeUtils.getApplicationTheme(context))
|
||||
|
||||
val buildNumber = context.getString(R.string.build_number)
|
||||
if (!TextUtils.isEmpty(buildNumber) && buildNumber != "0") {
|
||||
if (buildNumber.isNotEmpty() && buildNumber != "0") {
|
||||
builder.addFormDataPart("build_number", buildNumber)
|
||||
}
|
||||
|
||||
@ -266,10 +262,9 @@ class BugReporter @Inject constructor(private val activeSessionHolder: ActiveSes
|
||||
}
|
||||
|
||||
try {
|
||||
val fos = FileOutputStream(logCatScreenshotFile)
|
||||
bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos)
|
||||
fos.flush()
|
||||
fos.close()
|
||||
logCatScreenshotFile.outputStream().use {
|
||||
bitmap.compress(Bitmap.CompressFormat.PNG, 100, it)
|
||||
}
|
||||
|
||||
builder.addFormDataPart("file",
|
||||
logCatScreenshotFile.name, logCatScreenshotFile.asRequestBody("application/octet-stream".toMediaTypeOrNull()))
|
||||
@ -303,16 +298,14 @@ class BugReporter @Inject constructor(private val activeSessionHolder: ActiveSes
|
||||
|
||||
// add a progress listener
|
||||
requestBody.setWriteListener { totalWritten, contentLength ->
|
||||
val percentage: Int
|
||||
|
||||
if (-1L != contentLength) {
|
||||
val percentage = if (-1L != contentLength) {
|
||||
if (totalWritten > contentLength) {
|
||||
percentage = 100
|
||||
100
|
||||
} else {
|
||||
percentage = (totalWritten * 100 / contentLength).toInt()
|
||||
(totalWritten * 100 / contentLength).toInt()
|
||||
}
|
||||
} else {
|
||||
percentage = 0
|
||||
0
|
||||
}
|
||||
|
||||
if (mIsCancelled && null != mBugReportCall) {
|
||||
@ -350,19 +343,18 @@ class BugReporter @Inject constructor(private val activeSessionHolder: ActiveSes
|
||||
} else if (null == response || null == response.body) {
|
||||
serverError = "Failed with error $responseCode"
|
||||
} else {
|
||||
var inputStream: InputStream? = null
|
||||
|
||||
try {
|
||||
inputStream = response.body!!.byteStream()
|
||||
val inputStream = response.body!!.byteStream()
|
||||
|
||||
var ch = inputStream.read()
|
||||
val b = StringBuilder()
|
||||
while (ch != -1) {
|
||||
b.append(ch.toChar())
|
||||
ch = inputStream.read()
|
||||
serverError = inputStream.use {
|
||||
buildString {
|
||||
var ch = it.read()
|
||||
while (ch != -1) {
|
||||
append(ch.toChar())
|
||||
ch = it.read()
|
||||
}
|
||||
}
|
||||
}
|
||||
serverError = b.toString()
|
||||
inputStream.close()
|
||||
|
||||
// check if the error message
|
||||
try {
|
||||
@ -378,12 +370,6 @@ class BugReporter @Inject constructor(private val activeSessionHolder: ActiveSes
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "## sendBugReport() : failed to parse error")
|
||||
} finally {
|
||||
try {
|
||||
inputStream?.close()
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "## sendBugReport() : failed to close the error stream")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -481,15 +467,9 @@ class BugReporter @Inject constructor(private val activeSessionHolder: ActiveSes
|
||||
crashFile.delete()
|
||||
}
|
||||
|
||||
if (!TextUtils.isEmpty(crashDescription)) {
|
||||
if (crashDescription.isNotEmpty()) {
|
||||
try {
|
||||
val fos = FileOutputStream(crashFile)
|
||||
val osw = OutputStreamWriter(fos)
|
||||
osw.write(crashDescription)
|
||||
osw.close()
|
||||
|
||||
fos.flush()
|
||||
fos.close()
|
||||
crashFile.writeText(crashDescription)
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "## saveCrashReport() : fail to write $e")
|
||||
}
|
||||
@ -503,25 +483,17 @@ class BugReporter @Inject constructor(private val activeSessionHolder: ActiveSes
|
||||
* @return the crash description
|
||||
*/
|
||||
private fun getCrashDescription(context: Context): String? {
|
||||
var crashDescription: String? = null
|
||||
val crashFile = getCrashFile(context)
|
||||
|
||||
if (crashFile.exists()) {
|
||||
try {
|
||||
val fis = FileInputStream(crashFile)
|
||||
val isr = InputStreamReader(fis)
|
||||
|
||||
val buffer = CharArray(fis.available())
|
||||
val len = isr.read(buffer, 0, fis.available())
|
||||
crashDescription = String(buffer, 0, len)
|
||||
isr.close()
|
||||
fis.close()
|
||||
return crashFile.readText()
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "## getCrashDescription() : fail to read $e")
|
||||
}
|
||||
}
|
||||
|
||||
return crashDescription
|
||||
return null
|
||||
}
|
||||
|
||||
// ==============================================================================================================
|
||||
@ -589,13 +561,9 @@ class BugReporter @Inject constructor(private val activeSessionHolder: ActiveSes
|
||||
}
|
||||
|
||||
try {
|
||||
val fos = FileOutputStream(logCatErrFile)
|
||||
val osw = OutputStreamWriter(fos)
|
||||
getLogCatError(osw, isErrorLogcat)
|
||||
osw.close()
|
||||
|
||||
fos.flush()
|
||||
fos.close()
|
||||
logCatErrFile.writer().use {
|
||||
getLogCatError(it, isErrorLogcat)
|
||||
}
|
||||
|
||||
return compressFile(logCatErrFile)
|
||||
} catch (error: OutOfMemoryError) {
|
||||
@ -622,26 +590,17 @@ class BugReporter @Inject constructor(private val activeSessionHolder: ActiveSes
|
||||
return
|
||||
}
|
||||
|
||||
var reader: BufferedReader? = null
|
||||
try {
|
||||
val separator = System.getProperty("line.separator")
|
||||
reader = BufferedReader(InputStreamReader(logcatProc.inputStream), BUFFER_SIZE)
|
||||
var line = reader.readLine()
|
||||
while (line != null) {
|
||||
streamWriter.append(line)
|
||||
streamWriter.append(separator)
|
||||
line = reader.readLine()
|
||||
}
|
||||
logcatProc.inputStream
|
||||
.reader()
|
||||
.buffered(BUFFER_SIZE)
|
||||
.forEachLine { line ->
|
||||
streamWriter.append(line)
|
||||
streamWriter.append(separator)
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
Timber.e(e, "getLog fails")
|
||||
} finally {
|
||||
if (reader != null) {
|
||||
try {
|
||||
reader.close()
|
||||
} catch (e: IOException) {
|
||||
Timber.e(e, "getLog fails with")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -658,45 +617,25 @@ class BugReporter @Inject constructor(private val activeSessionHolder: ActiveSes
|
||||
private fun compressFile(fin: File): File? {
|
||||
Timber.v("## compressFile() : compress ${fin.name}")
|
||||
|
||||
val dstFile = File(fin.parent, fin.name + ".gz")
|
||||
val dstFile = fin.resolveSibling(fin.name + ".gz")
|
||||
|
||||
if (dstFile.exists()) {
|
||||
dstFile.delete()
|
||||
}
|
||||
|
||||
var fos: FileOutputStream? = null
|
||||
var gos: GZIPOutputStream? = null
|
||||
var inputStream: InputStream? = null
|
||||
try {
|
||||
fos = FileOutputStream(dstFile)
|
||||
gos = GZIPOutputStream(fos)
|
||||
|
||||
inputStream = FileInputStream(fin)
|
||||
|
||||
val buffer = ByteArray(2048)
|
||||
var n = inputStream.read(buffer)
|
||||
while (n != -1) {
|
||||
gos.write(buffer, 0, n)
|
||||
n = inputStream.read(buffer)
|
||||
GZIPOutputStream(dstFile.outputStream()).use { gos ->
|
||||
fin.inputStream().use {
|
||||
it.copyTo(gos, 2048)
|
||||
}
|
||||
}
|
||||
|
||||
gos.close()
|
||||
inputStream.close()
|
||||
|
||||
Timber.v("## compressFile() : ${fin.length()} compressed to ${dstFile.length()} bytes")
|
||||
return dstFile
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "## compressFile() failed")
|
||||
} catch (oom: OutOfMemoryError) {
|
||||
Timber.e(oom, "## compressFile() failed")
|
||||
} finally {
|
||||
try {
|
||||
fos?.close()
|
||||
gos?.close()
|
||||
inputStream?.close()
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "## compressFile() failed to close inputStream")
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
|
@ -18,7 +18,6 @@ package im.vector.riotx.features.settings
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.Configuration
|
||||
import android.text.TextUtils
|
||||
import androidx.core.content.edit
|
||||
import androidx.preference.PreferenceManager
|
||||
import im.vector.riotx.R
|
||||
@ -68,7 +67,7 @@ object FontScale {
|
||||
val preferences = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
var scalePreferenceValue: String
|
||||
|
||||
if (!preferences.contains(APPLICATION_FONT_SCALE_KEY)) {
|
||||
if (APPLICATION_FONT_SCALE_KEY !in preferences) {
|
||||
val fontScale = context.resources.configuration.fontScale
|
||||
|
||||
scalePreferenceValue = FONT_SCALE_NORMAL
|
||||
@ -96,9 +95,9 @@ object FontScale {
|
||||
val fontScale = getFontScalePrefValue(context)
|
||||
|
||||
if (fontScaleToPrefValue.containsValue(fontScale)) {
|
||||
for (entry in fontScaleToPrefValue) {
|
||||
if (TextUtils.equals(entry.value, fontScale)) {
|
||||
return entry.key
|
||||
for ((key, value) in fontScaleToPrefValue) {
|
||||
if (value == fontScale) {
|
||||
return key
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -125,9 +124,9 @@ object FontScale {
|
||||
* @param fontScaleDescription the font scale description
|
||||
*/
|
||||
fun updateFontScale(context: Context, fontScaleDescription: String) {
|
||||
for (entry in prefValueToNameResId) {
|
||||
if (TextUtils.equals(context.getString(entry.value), fontScaleDescription)) {
|
||||
saveFontScale(context, entry.key)
|
||||
for ((key, value) in prefValueToNameResId) {
|
||||
if (context.getString(value) == fontScaleDescription) {
|
||||
saveFontScale(context, key)
|
||||
}
|
||||
}
|
||||
|
||||
@ -143,7 +142,7 @@ object FontScale {
|
||||
* @param scaleValue the text scale
|
||||
*/
|
||||
fun saveFontScale(context: Context, scaleValue: String) {
|
||||
if (!TextUtils.isEmpty(scaleValue)) {
|
||||
if (scaleValue.isNotEmpty()) {
|
||||
PreferenceManager.getDefaultSharedPreferences(context)
|
||||
.edit {
|
||||
putString(APPLICATION_FONT_SCALE_KEY, scaleValue)
|
||||
|
@ -20,15 +20,13 @@ import android.content.Context
|
||||
import android.content.res.Configuration
|
||||
import android.os.Build
|
||||
import android.preference.PreferenceManager
|
||||
import android.text.TextUtils
|
||||
import android.util.Pair
|
||||
import androidx.core.content.edit
|
||||
import im.vector.riotx.R
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import java.util.*
|
||||
import java.util.Locale
|
||||
|
||||
/**
|
||||
* Object to manage the Locale choice of the user
|
||||
@ -68,7 +66,7 @@ object VectorLocale {
|
||||
|
||||
// detect if the default language is used
|
||||
val defaultStringValue = getString(context, defaultLocale, R.string.resources_country_code)
|
||||
if (TextUtils.equals(defaultStringValue, getString(context, applicationLocale, R.string.resources_country_code))) {
|
||||
if (defaultStringValue == getString(context, applicationLocale, R.string.resources_country_code)) {
|
||||
applicationLocale = defaultLocale
|
||||
}
|
||||
|
||||
@ -89,21 +87,21 @@ object VectorLocale {
|
||||
|
||||
PreferenceManager.getDefaultSharedPreferences(context).edit {
|
||||
val language = locale.language
|
||||
if (TextUtils.isEmpty(language)) {
|
||||
if (language.isEmpty()) {
|
||||
remove(APPLICATION_LOCALE_LANGUAGE_KEY)
|
||||
} else {
|
||||
putString(APPLICATION_LOCALE_LANGUAGE_KEY, language)
|
||||
}
|
||||
|
||||
val country = locale.country
|
||||
if (TextUtils.isEmpty(country)) {
|
||||
if (country.isEmpty()) {
|
||||
remove(APPLICATION_LOCALE_COUNTRY_KEY)
|
||||
} else {
|
||||
putString(APPLICATION_LOCALE_COUNTRY_KEY, country)
|
||||
}
|
||||
|
||||
val variant = locale.variant
|
||||
if (TextUtils.isEmpty(variant)) {
|
||||
if (variant.isEmpty()) {
|
||||
remove(APPLICATION_LOCALE_VARIANT_KEY)
|
||||
} else {
|
||||
putString(APPLICATION_LOCALE_VARIANT_KEY, variant)
|
||||
@ -120,17 +118,17 @@ object VectorLocale {
|
||||
* @return the localized string
|
||||
*/
|
||||
private fun getString(context: Context, locale: Locale, resourceId: Int): String {
|
||||
var result: String
|
||||
val result: String
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
|
||||
val config = Configuration(context.resources.configuration)
|
||||
config.setLocale(locale)
|
||||
try {
|
||||
result = context.createConfigurationContext(config).getText(resourceId).toString()
|
||||
result = try {
|
||||
context.createConfigurationContext(config).getText(resourceId).toString()
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "## getString() failed")
|
||||
// use the default one
|
||||
result = context.getString(resourceId)
|
||||
context.getString(resourceId)
|
||||
}
|
||||
} else {
|
||||
val resources = context.resources
|
||||
@ -177,8 +175,8 @@ object VectorLocale {
|
||||
|
||||
supportedLocales.clear()
|
||||
|
||||
for (knownLocale in knownLocalesSet) {
|
||||
supportedLocales.add(Locale(knownLocale.first, knownLocale.second))
|
||||
knownLocalesSet.mapTo(supportedLocales) { (language, country) ->
|
||||
Locale(language, country)
|
||||
}
|
||||
|
||||
// sort by human display names
|
||||
@ -194,7 +192,7 @@ object VectorLocale {
|
||||
fun localeToLocalisedString(locale: Locale): String {
|
||||
var res = locale.getDisplayLanguage(locale)
|
||||
|
||||
if (!TextUtils.isEmpty(locale.getDisplayCountry(locale))) {
|
||||
if (locale.getDisplayCountry(locale).isNotEmpty()) {
|
||||
res += " (" + locale.getDisplayCountry(locale) + ")"
|
||||
}
|
||||
|
||||
|
@ -18,11 +18,9 @@
|
||||
package im.vector.riotx.features.settings
|
||||
|
||||
import android.content.Context
|
||||
import android.database.Cursor
|
||||
import android.media.RingtoneManager
|
||||
import android.net.Uri
|
||||
import android.provider.MediaStore
|
||||
import android.text.TextUtils
|
||||
import androidx.core.content.edit
|
||||
import androidx.preference.PreferenceManager
|
||||
import im.vector.riotx.R
|
||||
@ -30,7 +28,6 @@ import im.vector.riotx.features.homeserver.ServerUrlsRepository
|
||||
import im.vector.riotx.features.themes.ThemeUtils
|
||||
import timber.log.Timber
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
import javax.inject.Inject
|
||||
|
||||
class VectorPreferences @Inject constructor(private val context: Context) {
|
||||
@ -173,7 +170,7 @@ class VectorPreferences @Inject constructor(private val context: Context) {
|
||||
private const val MEDIA_SAVING_FOREVER = 3
|
||||
|
||||
// some preferences keys must be kept after a logout
|
||||
private val mKeysToKeepAfterLogout = Arrays.asList(
|
||||
private val mKeysToKeepAfterLogout = listOf(
|
||||
SETTINGS_DEFAULT_MEDIA_COMPRESSION_KEY,
|
||||
SETTINGS_DEFAULT_MEDIA_SOURCE_KEY,
|
||||
SETTINGS_PLAY_SHUTTER_SOUND_KEY,
|
||||
@ -394,7 +391,7 @@ class VectorPreferences @Inject constructor(private val context: Context) {
|
||||
val url = defaultPrefs.getString(SETTINGS_NOTIFICATION_RINGTONE_PREFERENCE_KEY, null)
|
||||
|
||||
// the user selects "None"
|
||||
if (TextUtils.equals(url, "")) {
|
||||
if (url == "") {
|
||||
return null
|
||||
}
|
||||
|
||||
@ -425,29 +422,18 @@ class VectorPreferences @Inject constructor(private val context: Context) {
|
||||
fun getNotificationRingToneName(): String? {
|
||||
val toneUri = getNotificationRingTone() ?: return null
|
||||
|
||||
var name: String? = null
|
||||
|
||||
var cursor: Cursor? = null
|
||||
|
||||
try {
|
||||
val proj = arrayOf(MediaStore.Audio.Media.DATA)
|
||||
cursor = context.contentResolver.query(toneUri, proj, null, null, null)
|
||||
val column_index = cursor!!.getColumnIndexOrThrow(MediaStore.Audio.Media.DATA)
|
||||
cursor.moveToFirst()
|
||||
|
||||
val file = File(cursor.getString(column_index))
|
||||
name = file.name
|
||||
|
||||
if (name!!.contains(".")) {
|
||||
name = name.substring(0, name.lastIndexOf("."))
|
||||
return context.contentResolver.query(toneUri, proj, null, null, null)?.use {
|
||||
val columnIndex = it.getColumnIndexOrThrow(MediaStore.Audio.Media.DATA)
|
||||
it.moveToFirst()
|
||||
File(it.getString(columnIndex)).nameWithoutExtension
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "## getNotificationRingToneName() failed")
|
||||
} finally {
|
||||
cursor?.close()
|
||||
}
|
||||
|
||||
return name
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
@ -553,16 +539,13 @@ class VectorPreferences @Inject constructor(private val context: Context) {
|
||||
* @return the min last access time (in seconds)
|
||||
*/
|
||||
fun getMinMediasLastAccessTime(): Long {
|
||||
val selection = getSelectedMediasSavingPeriod()
|
||||
|
||||
when (selection) {
|
||||
MEDIA_SAVING_3_DAYS -> return System.currentTimeMillis() / 1000 - 3 * 24 * 60 * 60
|
||||
MEDIA_SAVING_1_WEEK -> return System.currentTimeMillis() / 1000 - 7 * 24 * 60 * 60
|
||||
MEDIA_SAVING_1_MONTH -> return System.currentTimeMillis() / 1000 - 30 * 24 * 60 * 60
|
||||
MEDIA_SAVING_FOREVER -> return 0
|
||||
return when (getSelectedMediasSavingPeriod()) {
|
||||
MEDIA_SAVING_3_DAYS -> System.currentTimeMillis() / 1000 - 3 * 24 * 60 * 60
|
||||
MEDIA_SAVING_1_WEEK -> System.currentTimeMillis() / 1000 - 7 * 24 * 60 * 60
|
||||
MEDIA_SAVING_1_MONTH -> System.currentTimeMillis() / 1000 - 30 * 24 * 60 * 60
|
||||
MEDIA_SAVING_FOREVER -> 0
|
||||
else -> 0
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
/**
|
||||
@ -571,15 +554,13 @@ class VectorPreferences @Inject constructor(private val context: Context) {
|
||||
* @return the selected period
|
||||
*/
|
||||
fun getSelectedMediasSavingPeriodString(): String {
|
||||
val selection = getSelectedMediasSavingPeriod()
|
||||
|
||||
when (selection) {
|
||||
MEDIA_SAVING_3_DAYS -> return context.getString(R.string.media_saving_period_3_days)
|
||||
MEDIA_SAVING_1_WEEK -> return context.getString(R.string.media_saving_period_1_week)
|
||||
MEDIA_SAVING_1_MONTH -> return context.getString(R.string.media_saving_period_1_month)
|
||||
MEDIA_SAVING_FOREVER -> return context.getString(R.string.media_saving_period_forever)
|
||||
return when (getSelectedMediasSavingPeriod()) {
|
||||
MEDIA_SAVING_3_DAYS -> context.getString(R.string.media_saving_period_3_days)
|
||||
MEDIA_SAVING_1_WEEK -> context.getString(R.string.media_saving_period_1_week)
|
||||
MEDIA_SAVING_1_MONTH -> context.getString(R.string.media_saving_period_1_month)
|
||||
MEDIA_SAVING_FOREVER -> context.getString(R.string.media_saving_period_forever)
|
||||
else -> "?"
|
||||
}
|
||||
return "?"
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -19,7 +19,6 @@ package im.vector.riotx.features.settings
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.text.TextUtils
|
||||
import android.widget.CheckedTextView
|
||||
import android.widget.LinearLayout
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
@ -194,7 +193,7 @@ class VectorSettingsPreferencesFragment : VectorSettingsBaseFragment() {
|
||||
val v = linearLayout.getChildAt(i)
|
||||
|
||||
if (v is CheckedTextView) {
|
||||
v.isChecked = TextUtils.equals(v.text, scaleText)
|
||||
v.isChecked = v.text == scaleText
|
||||
|
||||
v.setOnClickListener {
|
||||
dialog.dismiss()
|
||||
|
@ -21,7 +21,6 @@ import android.app.Activity
|
||||
import android.content.DialogInterface
|
||||
import android.content.Intent
|
||||
import android.graphics.Typeface
|
||||
import android.text.TextUtils
|
||||
import android.view.KeyEvent
|
||||
import android.widget.Button
|
||||
import android.widget.EditText
|
||||
@ -69,7 +68,7 @@ class VectorSettingsSecurityPrivacyFragment : VectorSettingsBaseFragment() {
|
||||
private var mAccountPassword: String = ""
|
||||
|
||||
// devices: device IDs and device names
|
||||
private var mDevicesNameList: List<DeviceInfo> = ArrayList()
|
||||
private val mDevicesNameList: MutableList<DeviceInfo> = mutableListOf()
|
||||
|
||||
private var mMyDeviceInfo: DeviceInfo? = null
|
||||
|
||||
@ -308,7 +307,7 @@ class VectorSettingsSecurityPrivacyFragment : VectorSettingsBaseFragment() {
|
||||
|
||||
passPhraseEditText.addTextChangedListener(object : SimpleTextWatcher() {
|
||||
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
|
||||
importButton.isEnabled = !TextUtils.isEmpty(passPhraseEditText.text)
|
||||
importButton.isEnabled = !passPhraseEditText.text.isNullOrEmpty()
|
||||
}
|
||||
})
|
||||
|
||||
@ -393,20 +392,20 @@ class VectorSettingsSecurityPrivacyFragment : VectorSettingsBaseFragment() {
|
||||
}
|
||||
|
||||
// crypto section: device ID
|
||||
if (!TextUtils.isEmpty(deviceId)) {
|
||||
if (!deviceId.isNullOrEmpty()) {
|
||||
cryptoInfoDeviceIdPreference.summary = deviceId
|
||||
|
||||
cryptoInfoDeviceIdPreference.setOnPreferenceClickListener {
|
||||
activity?.let { copyToClipboard(it, deviceId!!) }
|
||||
activity?.let { copyToClipboard(it, deviceId) }
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
// crypto section: device key (fingerprint)
|
||||
if (!TextUtils.isEmpty(deviceId) && !TextUtils.isEmpty(userId)) {
|
||||
if (!deviceId.isNullOrEmpty() && userId.isNotEmpty()) {
|
||||
val deviceInfo = session.getDeviceInfo(userId, deviceId)
|
||||
|
||||
if (null != deviceInfo && !TextUtils.isEmpty(deviceInfo.fingerprint())) {
|
||||
if (null != deviceInfo && !deviceInfo.fingerprint().isNullOrEmpty()) {
|
||||
cryptoInfoTextPreference.summary = deviceInfo.getFingerprintHumanReadable()
|
||||
|
||||
cryptoInfoTextPreference.setOnPreferenceClickListener {
|
||||
@ -446,7 +445,7 @@ class VectorSettingsSecurityPrivacyFragment : VectorSettingsBaseFragment() {
|
||||
* It can be any mobile device, as any browser.
|
||||
*/
|
||||
private fun refreshDevicesList() {
|
||||
if (session.isCryptoEnabled() && !TextUtils.isEmpty(session.sessionParams.credentials.deviceId)) {
|
||||
if (session.isCryptoEnabled() && !session.sessionParams.credentials.deviceId.isNullOrEmpty()) {
|
||||
// display a spinner while loading the devices list
|
||||
if (0 == mDevicesListSettingsCategory.preferenceCount) {
|
||||
activity?.let {
|
||||
@ -502,7 +501,8 @@ class VectorSettingsSecurityPrivacyFragment : VectorSettingsBaseFragment() {
|
||||
|
||||
if (isNewList) {
|
||||
var prefIndex = 0
|
||||
mDevicesNameList = aDeviceInfoList
|
||||
mDevicesNameList.clear()
|
||||
mDevicesNameList.addAll(aDeviceInfoList)
|
||||
|
||||
// sort before display: most recent first
|
||||
mDevicesNameList.sortByLastSeen()
|
||||
@ -570,7 +570,7 @@ class VectorSettingsSecurityPrivacyFragment : VectorSettingsBaseFragment() {
|
||||
|
||||
// device name
|
||||
textView = layout.findViewById(R.id.device_name)
|
||||
val displayName = if (TextUtils.isEmpty(aDeviceInfo.displayName)) LABEL_UNAVAILABLE_DATA else aDeviceInfo.displayName
|
||||
val displayName = if (aDeviceInfo.displayName.isNullOrEmpty()) LABEL_UNAVAILABLE_DATA else aDeviceInfo.displayName
|
||||
textView.text = displayName
|
||||
|
||||
// last seen info
|
||||
@ -598,7 +598,7 @@ class VectorSettingsSecurityPrivacyFragment : VectorSettingsBaseFragment() {
|
||||
.setPositiveButton(R.string.rename) { _, _ -> displayDeviceRenameDialog(aDeviceInfo) }
|
||||
|
||||
// disable the deletion for our own device
|
||||
if (!TextUtils.equals(session.getMyDevice().deviceId, aDeviceInfo.deviceId)) {
|
||||
if (session.getMyDevice().deviceId != aDeviceInfo.deviceId) {
|
||||
builder.setNegativeButton(R.string.delete) { _, _ -> deleteDevice(aDeviceInfo) }
|
||||
}
|
||||
|
||||
@ -645,13 +645,13 @@ class VectorSettingsSecurityPrivacyFragment : VectorSettingsBaseFragment() {
|
||||
for (i in 0 until count) {
|
||||
val pref = mDevicesListSettingsCategory.getPreference(i)
|
||||
|
||||
if (TextUtils.equals(aDeviceInfoToRename.deviceId, pref.title)) {
|
||||
if (aDeviceInfoToRename.deviceId == pref.title) {
|
||||
pref.summary = newName
|
||||
}
|
||||
}
|
||||
|
||||
// detect if the updated device is the current account one
|
||||
if (TextUtils.equals(cryptoInfoDeviceIdPreference.summary, aDeviceInfoToRename.deviceId)) {
|
||||
if (cryptoInfoDeviceIdPreference.summary == aDeviceInfoToRename.deviceId) {
|
||||
cryptoInfoDeviceNamePreference.summary = newName
|
||||
}
|
||||
|
||||
@ -716,7 +716,7 @@ class VectorSettingsSecurityPrivacyFragment : VectorSettingsBaseFragment() {
|
||||
* Show a dialog to ask for user password, or use a previously entered password.
|
||||
*/
|
||||
private fun maybeShowDeleteDeviceWithPasswordDialog(deviceId: String, authSession: String?) {
|
||||
if (!TextUtils.isEmpty(mAccountPassword)) {
|
||||
if (mAccountPassword.isNotEmpty()) {
|
||||
deleteDeviceWithPassword(deviceId, authSession, mAccountPassword)
|
||||
} else {
|
||||
activity?.let {
|
||||
@ -729,7 +729,7 @@ class VectorSettingsSecurityPrivacyFragment : VectorSettingsBaseFragment() {
|
||||
.setTitle(R.string.devices_delete_dialog_title)
|
||||
.setView(layout)
|
||||
.setPositiveButton(R.string.devices_delete_submit_button_label, DialogInterface.OnClickListener { _, _ ->
|
||||
if (TextUtils.isEmpty(passwordEditText.toString())) {
|
||||
if (passwordEditText.toString().isEmpty()) {
|
||||
it.toast(R.string.error_empty_field_your_password)
|
||||
return@OnClickListener
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user