Merge pull request #622 from Dominaezzz/kotlinify

Some more clean up
This commit is contained in:
Benoit Marty 2019-10-22 12:17:25 +02:00 committed by GitHub
commit 9092b97fb8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
62 changed files with 557 additions and 958 deletions

View File

@ -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)
}

View File

@ -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()

View File

@ -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")
}
}

View File

@ -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
}
}
}

View File

@ -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
}
/**

View File

@ -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
}
}

View File

@ -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>()

View File

@ -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)
}
}

View File

@ -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 {

View File

@ -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()
}

View File

@ -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
}

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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)
}

View File

@ -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 {

View File

@ -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)

View File

@ -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
)
}
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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()!!

View File

@ -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))
}
}

View File

@ -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")
}

View File

@ -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

View File

@ -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")
}
}
}

View File

@ -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
}

View File

@ -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)

View File

@ -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")
}

View File

@ -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) }
}
}
}

View File

@ -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

View File

@ -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
}
}

View File

@ -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()
}

View File

@ -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
}
}
}

View File

@ -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)
}
}

View File

@ -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()
}

View File

@ -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)

View File

@ -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.

View File

@ -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 })
}
}
}

View File

@ -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) = {}

View File

@ -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

View File

@ -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()

View File

@ -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) ?: ""
}
}

View File

@ -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)
}

View File

@ -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!!))

View File

@ -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

View File

@ -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

View File

@ -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)
}
}

View File

@ -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() }
}

View File

@ -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)

View File

@ -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 {

View File

@ -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

View File

@ -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

View File

@ -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) {

View File

@ -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
)
}
}

View File

@ -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)
}
}

View File

@ -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()

View File

@ -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

View File

@ -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)

View File

@ -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) + ")"
}

View File

@ -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 "?"
}
/**

View File

@ -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()

View File

@ -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
}