Compare commits

..

3 Commits

Author SHA1 Message Date
Benoit Marty a89f0ddd1d Merge branch 'release/0.4.0' 2019-08-30 15:04:43 +02:00
Benoit Marty 9cd69d1e33 Merge branch 'release/0.3.0' 2019-08-08 16:45:03 +02:00
Benoit Marty df6080b1da Merge branch 'release/0.2.0' 2019-07-18 17:47:39 +02:00
48 changed files with 401 additions and 328 deletions

View File

@ -1,26 +1,3 @@
Changes in RiotX 0.5.0 (2019-XX-XX)
===================================================

Features:
-

Improvements:
- Reduce default release build log level, and lab option to enable more logs.

Other changes:
-

Bugfix:
- Fix crash due to missing informationData (#535)
- Progress in initial sync dialog is decreasing for a step and should not (#532)

Translations:
-

Build:
- Fix issue with version name (#533)
- Fix rendering issue of accepted third party invitation event

Changes in RiotX 0.4.0 (2019-XX-XX)
===================================================


View File

@ -21,7 +21,7 @@ import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
import im.vector.matrix.android.internal.crypto.store.db.RealmCryptoStore
import im.vector.matrix.android.internal.crypto.store.db.RealmCryptoStoreModule
import io.realm.RealmConfiguration
import kotlin.random.Random
import java.util.*

internal class CryptoStoreHelper {

@ -35,7 +35,7 @@ internal class CryptoStoreHelper {
}

fun createCredential() = Credentials(
userId = "userId_" + Random.nextInt(),
userId = "userId_" + Random().nextInt(),
homeServer = "http://matrix.org",
accessToken = "access_token",
refreshToken = null,

View File

@ -17,6 +17,7 @@
package im.vector.matrix.android.api.comparators

import im.vector.matrix.android.api.interfaces.DatedObject
import java.util.*

object DatedObjectComparators {


View File

@ -19,7 +19,7 @@ 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
import java.util.*

/* ==========================================================================================
* MXDeviceInfo

View File

@ -18,17 +18,18 @@ package im.vector.matrix.android.api.pushrules
import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.room.RoomService
import timber.log.Timber
import java.util.regex.Pattern

private val regex = Regex("^(==|<=|>=|<|>)?(\\d*)$")
private val regex = Pattern.compile("^(==|<=|>=|<|>)?(\\d*)$")

class RoomMemberCountCondition(val iz: String) : Condition(Kind.room_member_count) {
class RoomMemberCountCondition(val `is`: String) : Condition(Kind.room_member_count) {

override fun isSatisfied(conditionResolver: ConditionResolver): Boolean {
return conditionResolver.resolveRoomMemberCountCondition(this)
}

override fun technicalDescription(): String {
return "Room member count is $iz"
return "Room member count is $`is`"
}

fun isSatisfied(event: Event, session: RoomService?): Boolean {
@ -55,9 +56,12 @@ class RoomMemberCountCondition(val iz: String) : Condition(Kind.room_member_coun
*/
private fun parseIsField(): Pair<String?, Int>? {
try {
val match = regex.find(iz) ?: return null
val (prefix, count) = match.destructured
return prefix to count.toInt()
val match = regex.matcher(`is`)
if (match.find()) {
val prefix = match.group(1)
val count = match.group(2).toInt()
return prefix to count
}
} catch (t: Throwable) {
Timber.d(t)
}

View File

@ -20,10 +20,10 @@ import androidx.lifecycle.LiveData

interface InitialSyncProgressService {

fun getInitialSyncProgressStatus() : LiveData<Status?>
fun getLiveStatus() : LiveData<Status?>

data class Status(
@StringRes val statusText: Int,
@StringRes val statusText: Int?,
val percentProgress: Int = 0
)
}

View File

@ -17,7 +17,7 @@ package im.vector.matrix.android.api.session.pushers

import androidx.lifecycle.LiveData
import im.vector.matrix.android.api.MatrixCallback
import java.util.UUID
import java.util.*


interface PushersService {

View File

@ -29,6 +29,7 @@ import im.vector.matrix.android.api.session.room.model.PowerLevels
import im.vector.matrix.android.api.session.room.model.RoomDirectoryVisibility
import im.vector.matrix.android.api.session.room.model.RoomHistoryVisibility
import im.vector.matrix.android.internal.auth.data.ThreePidMedium
import java.util.*

/**
* Parameter to create a room, with facilities functions to configure it
@ -132,7 +133,7 @@ class CreateRoomParams {
)

if (null == initialStates) {
initialStates = mutableListOf(algoEvent)
initialStates = Arrays.asList<Event>(algoEvent)
} else {
initialStates!!.add(algoEvent)
}
@ -165,7 +166,7 @@ class CreateRoomParams {
content = contentMap.toContent())

if (null == initialStates) {
initialStates = mutableListOf(historyVisibilityEvent)
initialStates = Arrays.asList<Event>(historyVisibilityEvent)
} else {
initialStates!!.add(historyVisibilityEvent)
}

View File

@ -27,7 +27,7 @@ import java.math.BigInteger
import java.security.KeyPairGenerator
import java.security.KeyStore
import java.security.SecureRandom
import java.util.Calendar
import java.util.*
import javax.crypto.*
import javax.crypto.spec.GCMParameterSpec
import javax.crypto.spec.IvParameterSpec
@ -479,7 +479,12 @@ object SecretStoringUtils {
val output = Cipher.getInstance(RSA_MODE)
output.init(Cipher.DECRYPT_MODE, privateKeyEntry.privateKey)

return CipherInputStream(encrypted, output).use { it.readBytes() }
val bos = ByteArrayOutputStream()
CipherInputStream(encrypted, output).use {
it.copyTo(bos)
}

return bos.toByteArray()
}

private fun formatMExtract(bis: InputStream): Pair<ByteArray, ByteArray> {
@ -490,7 +495,14 @@ object SecretStoringUtils {
val iv = ByteArray(ivSize)
bis.read(iv, 0, ivSize)

val encrypted = bis.readBytes()

val bos = ByteArrayOutputStream()
var next = bis.read()
while (next != -1) {
bos.write(next)
next = bis.read()
}
val encrypted = bos.toByteArray()
return Pair(iv, encrypted)
}

@ -518,7 +530,14 @@ object SecretStoringUtils {
val iv = ByteArray(ivSize)
bis.read(iv)

val encrypted = bis.readBytes()
val bos = ByteArrayOutputStream()

var next = bis.read()
while (next != -1) {
bos.write(next)
next = bis.read()
}
val encrypted = bos.toByteArray()
return Triple(encryptedKey, iv, encrypted)
}

@ -560,7 +579,14 @@ object SecretStoringUtils {
val iv = ByteArray(ivSize)
bis.read(iv)

val encrypted = bis.readBytes()
val bos = ByteArrayOutputStream()

var next = bis.read()
while (next != -1) {
bos.write(next)
next = bis.read()
}
val encrypted = bos.toByteArray()
return Triple(salt, iv, encrypted)
}
}

View File

@ -93,7 +93,7 @@ import kotlin.math.max
* Specially, it tracks all room membership changes events in order to do keys updates.
*/
@SessionScope
internal class DefaultCryptoService @Inject constructor(
internal class CryptoManager @Inject constructor(
// Olm Manager
private val olmManager: OlmManager,
// The credentials,
@ -1067,6 +1067,6 @@ internal class DefaultCryptoService @Inject constructor(
* ========================================================================================== */

override fun toString(): String {
return "DefaultCryptoService of " + credentials.userId + " (" + credentials.deviceId + ")"
return "CryptoManager of " + credentials.userId + " (" + credentials.deviceId + ")"
}
}

View File

@ -105,7 +105,7 @@ internal abstract class CryptoModule {
}

@Binds
abstract fun bindCryptoService(cryptoService: DefaultCryptoService): CryptoService
abstract fun bindCryptoService(cryptoManager: CryptoManager): CryptoService

@Binds
abstract fun bindDeleteDeviceTask(deleteDeviceTask: DefaultDeleteDeviceTask): DeleteDeviceTask

View File

@ -17,6 +17,7 @@
package im.vector.matrix.android.internal.crypto

import im.vector.matrix.android.api.auth.data.Credentials
import java.util.*
import javax.inject.Inject

internal class ObjectSigner @Inject constructor(private val credentials: Credentials,

View File

@ -31,6 +31,7 @@ import im.vector.matrix.android.internal.crypto.model.event.OlmPayloadContent
import im.vector.matrix.android.internal.di.MoshiProvider
import im.vector.matrix.android.internal.util.convertFromUTF8
import timber.log.Timber
import java.util.*

internal class MXOlmDecryption(
// The olm device interface
@ -157,14 +158,33 @@ internal class MXOlmDecryption(
* @return payload, if decrypted successfully.
*/
private fun decryptMessage(message: JsonDict, theirDeviceIdentityKey: String): String? {
val sessionIds = olmDevice.getSessionIds(theirDeviceIdentityKey) ?: emptySet()
val sessionIdsSet = olmDevice.getSessionIds(theirDeviceIdentityKey)

val messageBody = message["body"] as? String ?: return null
val messageType = when (val typeAsVoid = message["type"]) {
is Double -> typeAsVoid.toInt()
is Int -> typeAsVoid
is Long -> typeAsVoid.toInt()
else -> return null
val sessionIds: List<String>

if (null == sessionIdsSet) {
sessionIds = ArrayList()
} else {
sessionIds = ArrayList(sessionIdsSet)
}

val messageBody = message["body"] as? String
var messageType: Int? = null

val typeAsVoid = message["type"]

if (null != typeAsVoid) {
if (typeAsVoid is Double) {
messageType = typeAsVoid.toInt()
} else if (typeAsVoid is Int) {
messageType = typeAsVoid
} else if (typeAsVoid is Long) {
messageType = typeAsVoid.toInt()
}
}

if (null == messageBody || null == messageType) {
return null
}

// Try each session in turn

View File

@ -26,6 +26,7 @@ import java.io.ByteArrayOutputStream
import java.io.InputStream
import java.security.MessageDigest
import java.security.SecureRandom
import java.util.*
import javax.crypto.Cipher
import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.SecretKeySpec
@ -58,7 +59,8 @@ object MXEncryptedAttachments {
// Half of the IV is random, the lower order bits are zeroed
// such that the counter never wraps.
// See https://github.com/matrix-org/matrix-ios-kit/blob/3dc0d8e46b4deb6669ed44f72ad79be56471354c/MatrixKit/Models/Room/MXEncryptedAttachments.m#L75
val initVectorBytes = ByteArray(16) { 0.toByte() }
val initVectorBytes = ByteArray(16)
Arrays.fill(initVectorBytes, 0.toByte())

val ivRandomPart = ByteArray(8)
secureRandom.nextBytes(ivRandomPart)
@ -113,7 +115,7 @@ object MXEncryptedAttachments {
encryptedByteArray = outStream.toByteArray()
)

Timber.v("Encrypt in ${System.currentTimeMillis() - t0} ms")
Timber.v("Encrypt in " + (System.currentTimeMillis() - t0) + " ms")
return Try.just(result)
} catch (oom: OutOfMemoryError) {
Timber.e(oom, "## encryptAttachment failed")
@ -204,13 +206,13 @@ object MXEncryptedAttachments {
val decryptedStream = ByteArrayInputStream(outStream.toByteArray())
outStream.close()

Timber.v("Decrypt in ${System.currentTimeMillis() - t0} ms")
Timber.v("Decrypt in " + (System.currentTimeMillis() - t0) + " ms")

return decryptedStream
} catch (oom: OutOfMemoryError) {
Timber.e(oom, "## decryptAttachment() : failed ${oom.message}")
Timber.e(oom, "## decryptAttachment() : failed " + oom.message)
} catch (e: Exception) {
Timber.e(e, "## decryptAttachment() : failed ${e.message}")
Timber.e(e, "## decryptAttachment() : failed " + e.message)
}

try {
@ -226,20 +228,34 @@ object MXEncryptedAttachments {
* Base64 URL conversion methods
*/

private fun base64UrlToBase64(base64Url: String): String {
return base64Url.replace('-', '+')
.replace('_', '/')
private fun base64UrlToBase64(base64Url: String?): String? {
var result = base64Url
if (null != result) {
result = result.replace("-".toRegex(), "+")
result = result.replace("_".toRegex(), "/")
}

return result
}

private fun base64ToBase64Url(base64: String): String {
return base64.replace("\n".toRegex(), "")
.replace("\\+".toRegex(), "-")
.replace('/', '_')
.replace("=", "")
private fun base64ToBase64Url(base64: String?): String? {
var result = base64
if (null != result) {
result = result.replace("\n".toRegex(), "")
result = result.replace("\\+".toRegex(), "-")
result = result.replace("/".toRegex(), "_")
result = result.replace("=".toRegex(), "")
}
return result
}

private fun base64ToUnpaddedBase64(base64: String): String {
return base64.replace("\n".toRegex(), "")
.replace("=", "")
private fun base64ToUnpaddedBase64(base64: String?): String? {
var result = base64
if (null != result) {
result = result.replace("\n".toRegex(), "")
result = result.replace("=".toRegex(), "")
}

return result
}
}

View File

@ -66,8 +66,9 @@ import org.matrix.olm.OlmPkEncryption
import org.matrix.olm.OlmPkMessage
import timber.log.Timber
import java.security.InvalidParameterException
import java.util.*
import javax.inject.Inject
import kotlin.random.Random
import kotlin.collections.HashMap

/**
* A KeysBackup class instance manage incremental backup of e2e keys (megolm keys)
@ -113,6 +114,8 @@ internal class KeysBackup @Inject constructor(
// The backup key being used.
private var backupOlmPkEncryption: OlmPkEncryption? = null

private val random = Random()

private var backupAllGroupSessionsCallback: MatrixCallback<Unit>? = null

private var keysBackupStateListener: KeysBackupStateListener? = null
@ -845,7 +848,7 @@ internal class KeysBackup @Inject constructor(
// Wait between 0 and 10 seconds, to avoid backup requests from
// different clients hitting the server all at the same time when a
// new key is sent
val delayInMs = Random.nextLong(KEY_BACKUP_WAITING_TIME_TO_SEND_KEY_BACKUP_MILLIS)
val delayInMs = random.nextInt(KEY_BACKUP_WAITING_TIME_TO_SEND_KEY_BACKUP_MILLIS).toLong()

uiHandler.postDelayed({ backupKeys() }, delayInMs)
}
@ -1304,7 +1307,7 @@ internal class KeysBackup @Inject constructor(

// Make the request
storeSessionDataTask
.configureWith(StoreSessionsDataTask.Params(keysBackupVersion!!.version!!, keysBackupData)) {
.configureWith(StoreSessionsDataTask.Params(keysBackupVersion!!.version!!, keysBackupData)){
this.callback = sendingRequestCallback
}
.executeBy(taskExecutor)
@ -1402,7 +1405,7 @@ internal class KeysBackup @Inject constructor(

companion object {
// Maximum delay in ms in {@link maybeBackupKeys}
private const val KEY_BACKUP_WAITING_TIME_TO_SEND_KEY_BACKUP_MILLIS = 10_000L
private const val KEY_BACKUP_WAITING_TIME_TO_SEND_KEY_BACKUP_MILLIS = 10000

// Maximum number of keys to send at a time to the homeserver.
private const val KEY_BACKUP_SEND_KEYS_MAX_COUNT = 100

View File

@ -22,7 +22,7 @@ package im.vector.matrix.android.internal.crypto.keysbackup
import androidx.annotation.WorkerThread
import im.vector.matrix.android.api.listeners.ProgressListener
import timber.log.Timber
import java.util.UUID
import java.util.*
import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec
import kotlin.experimental.xor

View File

@ -20,6 +20,7 @@ import android.os.Handler
import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupState
import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupStateListener
import timber.log.Timber
import java.util.*

internal class KeysBackupStateManager(private val uiHandler: Handler) {


View File

@ -23,6 +23,7 @@ import com.squareup.moshi.JsonClass
import im.vector.matrix.android.api.util.JsonDict
import im.vector.matrix.android.internal.crypto.model.rest.DeviceKeys
import java.io.Serializable
import java.util.*

@JsonClass(generateAdapter = true)
data class MXDeviceInfo(

View File

@ -18,6 +18,7 @@ package im.vector.matrix.android.internal.crypto.model

import im.vector.matrix.android.api.util.JsonDict
import timber.log.Timber
import java.util.*

data class MXKey(
/**
@ -45,7 +46,11 @@ data class MXKey(
* @return the signed data map
*/
fun signalableJSONDictionary(): Map<String, Any> {
return mapOf("key" to value)
val map = HashMap<String, Any>()

map["key"] = value

return map
}

/**

View File

@ -16,6 +16,7 @@

package im.vector.matrix.android.internal.crypto.model

import java.util.*

class MXUsersDevicesMap<E> {

@ -26,7 +27,7 @@ class MXUsersDevicesMap<E> {
* @return the user Ids
*/
val userIds: List<String>
get() = map.keys.toList()
get() = ArrayList(map.keys)

val isEmpty: Boolean
get() = map.isEmpty()
@ -39,7 +40,7 @@ class MXUsersDevicesMap<E> {
* @return the device ids list
*/
fun getUserDeviceIds(userId: String?): List<String>? {
return if (!userId.isNullOrBlank() && map.containsKey(userId)) {
return if (userId?.isNotBlank() == true && map.containsKey(userId)) {
map[userId]!!.keys.toList()
} else null
}
@ -52,7 +53,7 @@ class MXUsersDevicesMap<E> {
* @return the object
*/
fun getObject(userId: String?, deviceId: String?): E? {
return if (!userId.isNullOrBlank() && !deviceId.isNullOrBlank()) {
return if (userId?.isNotBlank() == true && deviceId?.isNotBlank() == true && map.containsKey(userId)) {
map[userId]?.get(deviceId)
} else null
}
@ -66,8 +67,11 @@ class MXUsersDevicesMap<E> {
*/
fun setObject(userId: String?, deviceId: String?, o: E?) {
if (null != o && userId?.isNotBlank() == true && deviceId?.isNotBlank() == true) {
val devices = map.getOrPut(userId) { HashMap() }
devices[deviceId] = o
if (map[userId] == null) {
map[userId] = HashMap()
}

map[userId]?.put(deviceId, o)
}
}

@ -78,7 +82,7 @@ class MXUsersDevicesMap<E> {
* @param userId the user id
*/
fun setObjects(userId: String?, objectsPerDevices: Map<String, E>?) {
if (!userId.isNullOrBlank()) {
if (userId?.isNotBlank() == true) {
if (null == objectsPerDevices) {
map.remove(userId)
} else {
@ -93,7 +97,7 @@ class MXUsersDevicesMap<E> {
* @param userId the user id.
*/
fun removeUserObjects(userId: String?) {
if (!userId.isNullOrBlank()) {
if (userId?.isNotBlank() == true) {
map.remove(userId)
}
}

View File

@ -21,8 +21,8 @@ import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
import im.vector.matrix.android.internal.crypto.model.rest.SendToDeviceBody
import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.task.Task
import java.util.*
import javax.inject.Inject
import kotlin.random.Random

internal interface SendToDeviceTask : Task<SendToDeviceTask.Params, Unit> {
data class Params(
@ -45,7 +45,7 @@ internal class DefaultSendToDeviceTask @Inject constructor(private val cryptoApi
return executeRequest {
apiCall = cryptoApi.sendToDevice(
params.eventType,
params.transactionId ?: Random.nextInt(Integer.MAX_VALUE).toString(),
params.transactionId ?: Random().nextInt(Integer.MAX_VALUE).toString(),
sendToDeviceBody
)
}

View File

@ -44,7 +44,7 @@ import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import timber.log.Timber
import java.util.UUID
import java.util.*
import javax.inject.Inject
import kotlin.collections.HashMap

@ -161,7 +161,7 @@ internal class DefaultSasVerificationService @Inject constructor(private val cre
cancelTransaction(
startReq.transactionID!!,
otherUserId!!,
startReq.fromDevice ?: event.getSenderKey()!!,
startReq?.fromDevice ?: event.getSenderKey()!!,
CancelCode.UnknownMethod
)
}
@ -388,13 +388,14 @@ internal class DefaultSasVerificationService @Inject constructor(private val cre
* This string must be unique for the pair of users performing verification for the duration that the transaction is valid
*/
private fun createUniqueIDForTransaction(userId: String, deviceID: String): String {
return buildString {
append(credentials.userId).append("|")
append(credentials.deviceId).append("|")
append(userId).append("|")
append(deviceID).append("|")
append(UUID.randomUUID().toString())
}
val buff = StringBuffer()
buff
.append(credentials.userId).append("|")
.append(credentials.deviceId).append("|")
.append(userId).append("|")
.append(deviceID).append("|")
.append(UUID.randomUUID().toString())
return buff.toString()
}



View File

@ -22,7 +22,7 @@ import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.matrix.android.api.session.room.model.tag.RoomTag
import im.vector.matrix.android.internal.crypto.algorithms.olm.OlmDecryptionResult
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
import java.util.UUID
import java.util.*
import javax.inject.Inject

internal class RoomSummaryMapper @Inject constructor(

View File

@ -24,6 +24,7 @@ import java.security.KeyStore
import java.security.MessageDigest
import java.security.cert.CertificateException
import java.security.cert.X509Certificate
import java.util.*
import javax.net.ssl.*
import kotlin.experimental.and


View File

@ -25,6 +25,7 @@ import java.net.UnknownHostException
import java.security.KeyManagementException
import java.security.NoSuchAlgorithmException
import java.security.SecureRandom
import java.util.*
import javax.net.ssl.SSLContext
import javax.net.ssl.SSLSocket
import javax.net.ssl.SSLSocketFactory
@ -100,16 +101,25 @@ constructor(trustPinned: Array<TrustManager>, acceptedTlsVersions: List<TlsVersi
}

private fun enableTLSOnSocket(socket: Socket?): Socket? {
if (socket is SSLSocket) {
val supportedProtocols = socket.supportedProtocols.toSet()
val filteredEnabledProtocols = enabledProtocols.filter { it in supportedProtocols }
if (socket != null && socket is SSLSocket) {
val sslSocket = socket as SSLSocket?

if (filteredEnabledProtocols.isNotEmpty()) {
val supportedProtocols = Arrays.asList(*sslSocket!!.supportedProtocols)
val filteredEnabledProtocols = ArrayList<String>()

for (protocol in enabledProtocols) {
if (supportedProtocols.contains(protocol)) {
filteredEnabledProtocols.add(protocol)
}
}

if (!filteredEnabledProtocols.isEmpty()) {
try {
socket.enabledProtocols = filteredEnabledProtocols.toTypedArray()
sslSocket.enabledProtocols = filteredEnabledProtocols.toTypedArray()
} catch (e: Exception) {
Timber.e(e)
}

}
}
return socket

View File

@ -15,7 +15,6 @@
*/
package im.vector.matrix.android.internal.session

import androidx.annotation.StringRes
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import im.vector.matrix.android.api.session.InitialSyncProgressService
@ -26,33 +25,31 @@ import javax.inject.Inject
@SessionScope
class DefaultInitialSyncProgressService @Inject constructor() : InitialSyncProgressService {

private var status = MutableLiveData<InitialSyncProgressService.Status>()
var status = MutableLiveData<InitialSyncProgressService.Status>()

private var rootTask: TaskInfo? = null
var rootTask: TaskInfo? = null

override fun getInitialSyncProgressStatus(): LiveData<InitialSyncProgressService.Status?> {
override fun getLiveStatus(): LiveData<InitialSyncProgressService.Status?> {
return status
}

fun startTask(@StringRes nameRes: Int, totalProgress: Int, parentWeight: Float = 1f) {
// Create a rootTask, or add a child to the leaf

fun startTask(nameRes: Int, totalProgress: Int, parentWeight: Float = 1f) {
if (rootTask == null) {
rootTask = TaskInfo(nameRes, totalProgress)
} else {
val currentLeaf = rootTask!!.leaf()

val newTask = TaskInfo(nameRes,
totalProgress,
currentLeaf,
parentWeight)

val newTask = TaskInfo(nameRes, totalProgress)
newTask.parent = currentLeaf
newTask.offset = currentLeaf.currentProgress
currentLeaf.child = newTask
newTask.parentWeight = parentWeight
}
reportProgress(0)
}

fun reportProgress(progress: Int) {
rootTask?.leaf()?.setProgress(progress)
rootTask?.leaf()?.incrementProgress(progress)
}

fun endTask(nameRes: Int) {
@ -61,7 +58,7 @@ class DefaultInitialSyncProgressService @Inject constructor() : InitialSyncProgr
//close it
val parent = endedTask.parent
parent?.child = null
parent?.setProgress(endedTask.offset + (endedTask.totalProgress * endedTask.parentWeight).toInt())
parent?.incrementProgress(endedTask.offset + (endedTask.totalProgress * endedTask.parentWeight).toInt())
}
if (endedTask?.parent == null) {
status.postValue(null)
@ -74,17 +71,14 @@ class DefaultInitialSyncProgressService @Inject constructor() : InitialSyncProgr
}


private inner class TaskInfo(@StringRes var nameRes: Int,
var totalProgress: Int,
var parent: TaskInfo? = null,
var parentWeight: Float = 1f,
var offset: Int = parent?.currentProgress ?: 0) {
inner class TaskInfo(var nameRes: Int,
var totalProgress: Int) {
var parent: TaskInfo? = null
var child: TaskInfo? = null
var parentWeight: Float = 1f
var currentProgress: Int = 0
var offset: Int = 0

/**
* Get the further child
*/
fun leaf(): TaskInfo {
var last = this
while (last.child != null) {
@ -93,27 +87,26 @@ class DefaultInitialSyncProgressService @Inject constructor() : InitialSyncProgr
return last
}

/**
* Set progress of the parent if any (which will post value), or post the value
*/
fun setProgress(progress: Int) {
fun incrementProgress(progress: Int) {
currentProgress = progress
// val newProgress = Math.min(currentProgress + progress, totalProgress)
parent?.let {
val parentProgress = (currentProgress * parentWeight).toInt()
it.setProgress(offset + parentProgress)
} ?: run {
Timber.e("--- ${leaf().nameRes}: $currentProgress")
it.incrementProgress(offset + parentProgress)
}
if (parent == null) {
Timber.e("--- ${leaf().nameRes}: ${currentProgress}")
status.postValue(
InitialSyncProgressService.Status(leaf().nameRes, currentProgress)
)
}
}
}

}

inline fun <T> reportSubtask(reporter: DefaultInitialSyncProgressService?,
@StringRes nameRes: Int,
nameRes: Int,
totalProgress: Int,
parentWeight: Float = 1f,
block: () -> T): T {
@ -128,11 +121,11 @@ inline fun <K, V, R> Map<out K, V>.mapWithProgress(reporter: DefaultInitialSyncP
taskId: Int,
weight: Float,
transform: (Map.Entry<K, V>) -> R): List<R> {
val total = count().toFloat()
val total = count()
var current = 0
reporter?.startTask(taskId, 100, weight)
return map {
reporter?.reportProgress((current / total * 100).toInt())
return this.map {
reporter?.reportProgress((current / total.toFloat() * 100).toInt())
current++
transform.invoke(it)
}.also {

View File

@ -40,7 +40,7 @@ import im.vector.matrix.android.api.session.sync.FilterService
import im.vector.matrix.android.api.session.sync.SyncState
import im.vector.matrix.android.api.session.user.UserService
import im.vector.matrix.android.api.util.MatrixCallbackDelegate
import im.vector.matrix.android.internal.crypto.DefaultCryptoService
import im.vector.matrix.android.internal.crypto.CryptoManager
import im.vector.matrix.android.internal.database.LiveEntityObserver
import im.vector.matrix.android.internal.session.sync.job.SyncThread
import im.vector.matrix.android.internal.session.sync.job.SyncWorker
@ -63,7 +63,7 @@ internal class DefaultSession @Inject constructor(override val sessionParams: Se
private val signOutService: Lazy<SignOutService>,
private val pushRuleService: Lazy<PushRuleService>,
private val pushersService: Lazy<PushersService>,
private val cryptoService: Lazy<DefaultCryptoService>,
private val cryptoService: Lazy<CryptoManager>,
private val fileService: Lazy<FileService>,
private val syncThreadProvider: Provider<SyncThread>,
private val contentUrlResolver: ContentUrlResolver,

View File

@ -30,8 +30,9 @@ internal class DefaultContentUploadStateTracker @Inject constructor() : ContentU
private val listeners = mutableMapOf<String, MutableList<ContentUploadStateTracker.UpdateListener>>()

override fun track(key: String, updateListener: ContentUploadStateTracker.UpdateListener) {
val listeners = listeners.getOrPut(key) { ArrayList() }
val listeners = listeners[key] ?: ArrayList()
listeners.add(updateListener)
this.listeners[key] = listeners
val currentState = states[key] ?: ContentUploadStateTracker.State.Idle
mainHandler.post { updateListener.onUpdate(currentState) }
}

View File

@ -32,7 +32,7 @@ import im.vector.matrix.android.internal.task.configureWith
import im.vector.matrix.android.internal.worker.WorkManagerUtil
import im.vector.matrix.android.internal.worker.WorkManagerUtil.matrixOneTimeWorkRequestBuilder
import im.vector.matrix.android.internal.worker.WorkerParamsFactory
import java.util.UUID
import java.util.*
import java.util.concurrent.TimeUnit
import javax.inject.Inject


View File

@ -38,7 +38,7 @@ import im.vector.matrix.android.internal.session.room.RoomSummaryUpdater
import im.vector.matrix.android.internal.util.StringProvider
import org.commonmark.parser.Parser
import org.commonmark.renderer.html.HtmlRenderer
import java.util.UUID
import java.util.*
import javax.inject.Inject

/**
@ -304,22 +304,17 @@ internal class LocalEchoEventFactory @Inject constructor(private val credentials
}

private fun buildReplyFallback(body: TextContent, originalSenderId: String?, newBodyText: String): String {
return buildString {
append("> <")
append(originalSenderId)
append(">")

val lines = body.text.split("\n")
lines.forEachIndexed { index, s ->
if (index == 0) {
append(" $s")
} else {
append("\n> $s")
}
val lines = body.text.split("\n")
val replyFallback = StringBuffer("> <$originalSenderId>")
lines.forEachIndexed { index, s ->
if (index == 0) {
replyFallback.append(" $s")
} else {
replyFallback.append("\n> $s")
}
append("\n\n")
append(newBodyText)
}
replyFallback.append("\n\n").append(newBodyText)
return replyFallback.toString()
}

/**

View File

@ -22,7 +22,7 @@ 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 im.vector.matrix.android.internal.crypto.DefaultCryptoService
import im.vector.matrix.android.internal.crypto.CryptoManager
import im.vector.matrix.android.internal.crypto.MXEventDecryptionResult
import im.vector.matrix.android.internal.crypto.algorithms.olm.OlmDecryptionResult
import im.vector.matrix.android.internal.crypto.verification.DefaultSasVerificationService
@ -33,7 +33,7 @@ import timber.log.Timber
import javax.inject.Inject


internal class CryptoSyncHandler @Inject constructor(private val cryptoService: DefaultCryptoService,
internal class CryptoSyncHandler @Inject constructor(private val cryptoManager: CryptoManager,
private val sasVerificationService: DefaultSasVerificationService) {

fun handleToDevice(toDevice: ToDeviceSyncResponse, initialSyncProgressService: DefaultInitialSyncProgressService? = null) {
@ -47,13 +47,13 @@ internal class CryptoSyncHandler @Inject constructor(private val cryptoService:
Timber.e("## handleToDeviceEvent() : Warning: Unable to decrypt to-device event : " + event.content)
} else {
sasVerificationService.onToDeviceEvent(event)
cryptoService.onToDeviceEvent(event)
cryptoManager.onToDeviceEvent(event)
}
}
}

fun onSyncCompleted(syncResponse: SyncResponse) {
cryptoService.onSyncCompleted(syncResponse)
cryptoManager.onSyncCompleted(syncResponse)
}


@ -68,7 +68,7 @@ internal class CryptoSyncHandler @Inject constructor(private val cryptoService:
if (event.getClearType() == EventType.ENCRYPTED) {
var result: MXEventDecryptionResult? = null
try {
result = cryptoService.decryptEvent(event, timelineId ?: "")
result = cryptoManager.decryptEvent(event, timelineId ?: "")
} catch (exception: MXCryptoError) {
event.mCryptoError = (exception as? MXCryptoError.Base)?.errorType //setCryptoError(exception.cryptoError)
}

View File

@ -23,7 +23,7 @@ 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.Membership
import im.vector.matrix.android.api.session.room.model.tag.RoomTagContent
import im.vector.matrix.android.internal.crypto.DefaultCryptoService
import im.vector.matrix.android.internal.crypto.CryptoManager
import im.vector.matrix.android.internal.database.helper.*
import im.vector.matrix.android.internal.database.model.ChunkEntity
import im.vector.matrix.android.internal.database.model.EventEntityFields
@ -50,7 +50,7 @@ internal class RoomSyncHandler @Inject constructor(private val monarchy: Monarch
private val readReceiptHandler: ReadReceiptHandler,
private val roomSummaryUpdater: RoomSummaryUpdater,
private val roomTagHandler: RoomTagHandler,
private val cryptoService: DefaultCryptoService,
private val cryptoManager: CryptoManager,
private val tokenStore: SyncTokenStore,
private val pushRuleService: DefaultPushRuleService,
private val processForPushTask: ProcessEventForPushTask,
@ -97,12 +97,12 @@ internal class RoomSyncHandler @Inject constructor(private val monarchy: Monarch
handleJoinedRoom(realm, it.key, it.value, isInitialSync)
}
is HandlingStrategy.INVITED ->
handlingStrategy.data.mapWithProgress(reporter, R.string.initial_sync_start_importing_account_invited_rooms, 0.1f) {
handlingStrategy.data.mapWithProgress(reporter, R.string.initial_sync_start_importing_account_invited_rooms, 0.4f) {
handleInvitedRoom(realm, it.key, it.value)
}

is HandlingStrategy.LEFT -> {
handlingStrategy.data.mapWithProgress(reporter, R.string.initial_sync_start_importing_account_left_rooms, 0.3f) {
handlingStrategy.data.mapWithProgress(reporter, R.string.initial_sync_start_importing_account_left_rooms, 0.2f) {
handleLeftRoom(realm, it.key, it.value)
}
}
@ -125,7 +125,8 @@ internal class RoomSyncHandler @Inject constructor(private val monarchy: Monarch
handleRoomAccountDataEvents(realm, roomId, roomSync.accountData)
}

val roomEntity = RoomEntity.where(realm, roomId).findFirst() ?: realm.createObject(roomId)
val roomEntity = RoomEntity.where(realm, roomId).findFirst()
?: realm.createObject(roomId)

if (roomEntity.membership == Membership.INVITE) {
roomEntity.chunks.deleteAllFromRealm()
@ -134,12 +135,13 @@ internal class RoomSyncHandler @Inject constructor(private val monarchy: Monarch

// State event
if (roomSync.state != null && roomSync.state.events.isNotEmpty()) {
val minStateIndex = roomEntity.untimelinedStateEvents.where().min(EventEntityFields.STATE_INDEX)?.toInt() ?: Int.MIN_VALUE
val minStateIndex = roomEntity.untimelinedStateEvents.where().min(EventEntityFields.STATE_INDEX)?.toInt()
?: Int.MIN_VALUE
val untimelinedStateIndex = minStateIndex + 1
roomSync.state.events.forEach { event ->
roomEntity.addStateEvent(event, filterDuplicates = true, stateIndex = untimelinedStateIndex)
// Give info to crypto module
cryptoService.onStateEvent(roomId, event)
cryptoManager.onStateEvent(roomId, event)
UserEntityFactory.createOrNull(event)?.also {
realm.insertOrUpdate(it)
}
@ -165,7 +167,8 @@ internal class RoomSyncHandler @Inject constructor(private val monarchy: Monarch
roomSync:
InvitedRoomSync): RoomEntity {
Timber.v("Handle invited sync for room $roomId")
val roomEntity = RoomEntity.where(realm, roomId).findFirst() ?: realm.createObject(roomId)
val roomEntity = RoomEntity.where(realm, roomId).findFirst()
?: realm.createObject(roomId)
roomEntity.membership = Membership.INVITE
if (roomSync.inviteState != null && roomSync.inviteState.events.isNotEmpty()) {
val chunkEntity = handleTimelineEvents(realm, roomEntity, roomSync.inviteState.events)
@ -178,7 +181,8 @@ internal class RoomSyncHandler @Inject constructor(private val monarchy: Monarch
private fun handleLeftRoom(realm: Realm,
roomId: String,
roomSync: RoomSync): RoomEntity {
val roomEntity = RoomEntity.where(realm, roomId).findFirst() ?: realm.createObject(roomId)
val roomEntity = RoomEntity.where(realm, roomId).findFirst()
?: realm.createObject(roomId)

roomEntity.membership = Membership.LEAVE
roomEntity.chunks.deleteAllFromRealm()
@ -210,7 +214,7 @@ internal class RoomSyncHandler @Inject constructor(private val monarchy: Monarch
event.eventId?.also { eventIds.add(it) }
chunkEntity.add(roomEntity.roomId, event, PaginationDirection.FORWARDS, stateIndexOffset)
// Give info to crypto module
cryptoService.onLiveEvent(roomEntity.roomId, event)
cryptoManager.onLiveEvent(roomEntity.roomId, event)
// Try to remove local echo
event.unsignedData?.transactionId?.also {
val sendingEventEntity = roomEntity.sendingTimelineEvents.find(it)

View File

@ -21,6 +21,7 @@ import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
import im.vector.matrix.android.internal.database.model.RoomTagEntity
import im.vector.matrix.android.internal.database.query.where
import io.realm.Realm
import java.util.*
import javax.inject.Inject

internal class RoomTagHandler @Inject constructor() {
@ -29,8 +30,16 @@ internal class RoomTagHandler @Inject constructor() {
if (content == null) {
return
}
val tags = content.tags.entries.map { (tagName, params) ->
RoomTagEntity(tagName, params["order"] as? Double)
val tags = ArrayList<RoomTagEntity>()
for (tagName in content.tags.keys) {
val params = content.tags[tagName]
val order = params?.get("order")
val tag = if (order is Double) {
RoomTagEntity(tagName, order)
} else {
RoomTagEntity(tagName, null)
}
tags.add(tag)
}
val roomSummaryEntity = RoomSummaryEntity.where(realm, roomId).findFirst()
?: RoomSummaryEntity(roomId)

View File

@ -18,7 +18,7 @@ package im.vector.matrix.android.internal.session.sync

import arrow.core.Try
import im.vector.matrix.android.R
import im.vector.matrix.android.internal.crypto.DefaultCryptoService
import im.vector.matrix.android.internal.crypto.CryptoManager
import im.vector.matrix.android.internal.session.DefaultInitialSyncProgressService
import im.vector.matrix.android.internal.session.reportSubtask
import im.vector.matrix.android.internal.session.sync.model.SyncResponse
@ -30,7 +30,7 @@ internal class SyncResponseHandler @Inject constructor(private val roomSyncHandl
private val userAccountDataSyncHandler: UserAccountDataSyncHandler,
private val groupSyncHandler: GroupSyncHandler,
private val cryptoSyncHandler: CryptoSyncHandler,
private val cryptoService: DefaultCryptoService,
private val cryptoManager: CryptoManager,
private val initialSyncProgressService: DefaultInitialSyncProgressService) {

fun handleResponse(syncResponse: SyncResponse, fromToken: String?, isCatchingUp: Boolean): Try<SyncResponse> {
@ -40,12 +40,12 @@ internal class SyncResponseHandler @Inject constructor(private val roomSyncHandl
val reporter = initialSyncProgressService.takeIf { isInitialSync }

measureTimeMillis {
if (!cryptoService.isStarted()) {
Timber.v("Should start cryptoService")
cryptoService.start(isInitialSync)
if (!cryptoManager.isStarted()) {
Timber.v("Should start cryptoManager")
cryptoManager.start(isInitialSync)
}
}.also {
Timber.v("Finish handling start cryptoService in $it ms")
Timber.v("Finish handling start cryptoManager in $it ms")
}
val measure = measureTimeMillis {
// Handle the to device events before the room ones

View File

@ -19,7 +19,7 @@ package im.vector.matrix.android.internal.util
import android.content.Context
import androidx.work.WorkManager
import im.vector.matrix.android.api.util.Cancelable
import java.util.UUID
import java.util.*

internal class CancelableWork(private val context: Context,
private val workId: UUID) : Cancelable {

View File

@ -34,7 +34,7 @@ import java.security.*
import java.security.cert.CertificateException
import java.security.spec.AlgorithmParameterSpec
import java.security.spec.RSAKeyGenParameterSpec
import java.util.Calendar
import java.util.*
import java.util.zip.GZIPOutputStream
import javax.crypto.*
import javax.crypto.spec.GCMParameterSpec

View File

@ -22,7 +22,7 @@ import org.json.JSONArray
import org.json.JSONException
import org.json.JSONObject
import timber.log.Timber
import java.util.TreeSet
import java.util.*

/**
* Build canonical Json
@ -60,33 +60,43 @@ object JsonCanonicalizer {
when (any) {
is JSONArray -> {
// Canonicalize each element of the array
return (0 until any.length()).joinToString(separator = ",", prefix = "[", postfix = "]") {
canonicalizeRecursive(any.get(it))
val result = StringBuilder("[")

for (i in 0 until any.length()) {
result.append(canonicalizeRecursive(any.get(i)))
if (i < any.length() - 1) {
result.append(",")
}
}

result.append("]")

return result.toString()
}
is JSONObject -> {
// Sort the attributes by name, and the canonicalize each element of the JSONObject
val result = StringBuilder("{")

val attributes = TreeSet<String>()
for (entry in any.keys()) {
attributes.add(entry)
}

return buildString {
append("{")
for ((index, value) in attributes.withIndex()) {
append("\"")
append(value)
append("\"")
append(":")
append(canonicalizeRecursive(any[value]))
for (attribute in attributes.withIndex()) {
result.append("\"")
.append(attribute.value)
.append("\"")
.append(":")
.append(canonicalizeRecursive(any[attribute.value]))

if (index < attributes.size - 1) {
append(",")
}
if (attribute.index < attributes.size - 1) {
result.append(",")
}
append("}")
}

result.append("}")

return result.toString()
}
is String -> return JSONObject.quote(any)
else -> return any.toString()

View File

@ -15,7 +15,7 @@ androidExtensions {
}

ext.versionMajor = 0
ext.versionMinor = 5
ext.versionMinor = 4
ext.versionPatch = 0

static def getGitTimestamp() {
@ -51,7 +51,7 @@ static def gitRevisionDate() {
}

static def gitBranchName() {
def cmd = "git rev-parse --abbrev-ref HEAD"
def cmd = "git name-rev --name-only HEAD"
return cmd.execute().text.trim()
}


View File

@ -86,12 +86,9 @@ class VectorApplication : Application(), HasVectorInjector, MatrixConfiguration.
vectorComponent = DaggerVectorComponent.factory().create(this)
vectorComponent.inject(this)
vectorUncaughtExceptionHandler.activate(this)

if (BuildConfig.DEBUG) {
Timber.plant(Timber.DebugTree())
}
Timber.plant(vectorComponent.vectorFileLogger())

// Log
VectorFileLogger.init(this)
Timber.plant(Timber.DebugTree(), VectorFileLogger)
if (BuildConfig.DEBUG) {
Stetho.initializeWithDefaults(this)
}

View File

@ -41,7 +41,6 @@ import im.vector.riotx.features.notifications.NotificationBroadcastReceiver
import im.vector.riotx.features.notifications.NotificationDrawerManager
import im.vector.riotx.features.notifications.PushRuleTriggerListener
import im.vector.riotx.features.rageshake.BugReporter
import im.vector.riotx.features.rageshake.VectorFileLogger
import im.vector.riotx.features.rageshake.VectorUncaughtExceptionHandler
import im.vector.riotx.features.settings.VectorPreferences
import javax.inject.Singleton
@ -102,8 +101,6 @@ interface VectorComponent {

fun vectorPreferences(): VectorPreferences

fun vectorFileLogger(): VectorFileLogger

@Component.Factory
interface Factory {
fun create(@BindsInstance context: Context): VectorComponent

View File

@ -26,6 +26,7 @@ import androidx.appcompat.widget.Toolbar
import androidx.core.view.GravityCompat
import androidx.core.view.isVisible
import androidx.drawerlayout.widget.DrawerLayout
import androidx.fragment.app.FragmentManager
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders
import com.airbnb.mvrx.viewModel
@ -35,10 +36,12 @@ import im.vector.riotx.core.di.ScreenComponent
import im.vector.riotx.core.extensions.hideKeyboard
import im.vector.riotx.core.extensions.observeEvent
import im.vector.riotx.core.extensions.replaceFragment
import im.vector.riotx.core.platform.OnBackPressed
import im.vector.riotx.core.platform.ToolbarConfigurable
import im.vector.riotx.core.platform.VectorBaseActivity
import im.vector.riotx.core.pushers.PushersManager
import im.vector.riotx.features.disclaimer.showDisclaimerDialog
import im.vector.riotx.features.navigation.Navigator
import im.vector.riotx.features.notifications.NotificationDrawerManager
import im.vector.riotx.features.rageshake.VectorUncaughtExceptionHandler
import im.vector.riotx.features.workers.signout.SignOutViewModel
@ -116,22 +119,22 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable {
intent.removeExtra(EXTRA_CLEAR_EXISTING_NOTIFICATION)
}

activeSessionHolder.getSafeActiveSession()?.getInitialSyncProgressStatus()?.observe(this, Observer { status ->
if (status == null) {
activeSessionHolder.getSafeActiveSession()?.getLiveStatus()?.observe(this, Observer { sprogress ->
Timber.e("${sprogress?.statusText?.let { getString(it) }} ${sprogress?.percentProgress}")
if (sprogress == null) {
waiting_view.isVisible = false
} else {
Timber.e("${getString(status.statusText)} ${status.percentProgress}")
waiting_view.setOnClickListener {
//block interactions
}
waiting_view_status_horizontal_progress.apply {
isIndeterminate = false
max = 100
progress = status.percentProgress
progress = sprogress.percentProgress
isVisible = true
}
waiting_view_status_text.apply {
text = getString(status.statusText)
text = sprogress.statusText?.let { getString(it) }
isVisible = true
}
waiting_view.isVisible = true
@ -210,6 +213,8 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable {
}




companion object {
private const val EXTRA_CLEAR_EXISTING_NOTIFICATION = "EXTRA_CLEAR_EXISTING_NOTIFICATION"


View File

@ -28,7 +28,15 @@ import im.vector.matrix.android.api.permalinks.MatrixLinkify
import im.vector.matrix.android.api.permalinks.MatrixPermalinkSpan
import im.vector.matrix.android.api.session.events.model.RelationType
import im.vector.matrix.android.api.session.events.model.toModel
import im.vector.matrix.android.api.session.room.model.message.*
import im.vector.matrix.android.api.session.room.model.message.MessageAudioContent
import im.vector.matrix.android.api.session.room.model.message.MessageContent
import im.vector.matrix.android.api.session.room.model.message.MessageEmoteContent
import im.vector.matrix.android.api.session.room.model.message.MessageFileContent
import im.vector.matrix.android.api.session.room.model.message.MessageImageContent
import im.vector.matrix.android.api.session.room.model.message.MessageNoticeContent
import im.vector.matrix.android.api.session.room.model.message.MessageTextContent
import im.vector.matrix.android.api.session.room.model.message.MessageVideoContent
import im.vector.matrix.android.api.session.room.model.message.getFileUrl
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.matrix.android.api.session.room.timeline.getLastMessageContent
import im.vector.matrix.android.internal.crypto.attachments.toElementToDecrypt
@ -39,12 +47,26 @@ import im.vector.riotx.core.epoxy.VectorEpoxyModel
import im.vector.riotx.core.linkify.VectorLinkify
import im.vector.riotx.core.resources.ColorProvider
import im.vector.riotx.core.resources.StringProvider
import im.vector.riotx.core.resources.UserPreferencesProvider
import im.vector.riotx.core.utils.DebouncedClickListener
import im.vector.riotx.features.home.AvatarRenderer
import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController
import im.vector.riotx.features.home.room.detail.timeline.helper.ContentUploadStateTrackerBinder
import im.vector.riotx.features.home.room.detail.timeline.helper.TimelineMediaSizeProvider
import im.vector.riotx.features.home.room.detail.timeline.item.*
import im.vector.riotx.features.home.room.detail.timeline.helper.senderAvatar
import im.vector.riotx.features.home.room.detail.timeline.item.BlankItem_
import im.vector.riotx.features.home.room.detail.timeline.item.DefaultItem
import im.vector.riotx.features.home.room.detail.timeline.item.DefaultItem_
import im.vector.riotx.features.home.room.detail.timeline.item.MessageFileItem
import im.vector.riotx.features.home.room.detail.timeline.item.MessageFileItem_
import im.vector.riotx.features.home.room.detail.timeline.item.MessageImageVideoItem
import im.vector.riotx.features.home.room.detail.timeline.item.MessageImageVideoItem_
import im.vector.riotx.features.home.room.detail.timeline.item.MessageInformationData
import im.vector.riotx.features.home.room.detail.timeline.item.MessageTextItem
import im.vector.riotx.features.home.room.detail.timeline.item.MessageTextItem_
import im.vector.riotx.features.home.room.detail.timeline.item.NoticeItem_
import im.vector.riotx.features.home.room.detail.timeline.item.RedactedMessageItem
import im.vector.riotx.features.home.room.detail.timeline.item.RedactedMessageItem_
import im.vector.riotx.features.home.room.detail.timeline.util.MessageInformationDataFactory
import im.vector.riotx.features.html.EventHtmlRenderer
import im.vector.riotx.features.media.ImageContentRenderer
@ -81,28 +103,32 @@ class MessageItemFactory @Inject constructor(

val messageContent: MessageContent =
event.getLastMessageContent()
?: //Malformed content, we should echo something on screen
return buildNotHandledMessageItem(stringProvider.getString(R.string.malformed_message),
informationData, highlight, callback)
?: //Malformed content, we should echo something on screen
return DefaultItem_().text(stringProvider.getString(R.string.malformed_message))

if (messageContent.relatesTo?.type == RelationType.REPLACE
|| event.isEncrypted() && event.root.content.toModel<EncryptedEventContent>()?.relatesTo?.type == RelationType.REPLACE
|| event.isEncrypted() && event.root.content.toModel<EncryptedEventContent>()?.relatesTo?.type == RelationType.REPLACE
) {
// This is an edit event, we should display it when debugging as a notice event
// This is an edit event, we should it when debugging as a notice event
return noticeItemFactory.create(event, highlight, callback)
}
// val all = event.root.toContent()
// val ev = all.toModel<Event>()
return when (messageContent) {
is MessageEmoteContent -> buildEmoteMessageItem(messageContent, informationData, highlight, callback)
is MessageTextContent -> buildTextMessageItem(messageContent, informationData, highlight, callback)
is MessageEmoteContent -> buildEmoteMessageItem(messageContent,
informationData,
highlight,
callback)
is MessageTextContent -> buildTextMessageItem(messageContent,
informationData,
highlight,
callback)
is MessageImageContent -> buildImageMessageItem(messageContent, informationData, highlight, callback)
is MessageNoticeContent -> buildNoticeMessageItem(messageContent, informationData, highlight, callback)
is MessageVideoContent -> buildVideoMessageItem(messageContent, informationData, highlight, callback)
is MessageFileContent -> buildFileMessageItem(messageContent, informationData, highlight, callback)
is MessageAudioContent -> buildAudioMessageItem(messageContent, informationData, highlight, callback)
else -> buildNotHandledMessageItem("${messageContent.type} message events are not yet handled",
informationData, highlight, callback)
else -> buildNotHandledMessageItem(messageContent, highlight)
}
}

@ -131,7 +157,7 @@ class MessageItemFactory @Inject constructor(
}))
.longClickListener { view ->
return@longClickListener callback?.onEventLongClicked(informationData, messageContent, view)
?: false
?: false
}
}

@ -156,7 +182,7 @@ class MessageItemFactory @Inject constructor(
}))
.longClickListener { view ->
return@longClickListener callback?.onEventLongClicked(informationData, messageContent, view)
?: false
?: false
}
.clickListener(
DebouncedClickListener(View.OnClickListener { _ ->
@ -164,17 +190,11 @@ class MessageItemFactory @Inject constructor(
}))
}

private fun buildNotHandledMessageItem(text: String,
informationData: MessageInformationData,
highlight: Boolean,
callback: TimelineEventController.Callback?): DefaultItem? {
private fun buildNotHandledMessageItem(messageContent: MessageContent, highlight: Boolean): DefaultItem? {
val text = "${messageContent.type} message events are not yet handled"
return DefaultItem_()
.text(text)
.avatarRenderer(avatarRenderer)
.highlighted(highlight)
.informationData(informationData)
.baseCallback(callback)
.readReceiptsCallback(callback)
}

private fun buildImageMessageItem(messageContent: MessageImageContent,
@ -217,7 +237,7 @@ class MessageItemFactory @Inject constructor(
}))
.longClickListener { view ->
return@longClickListener callback?.onEventLongClicked(informationData, messageContent, view)
?: false
?: false
}
}

@ -230,7 +250,7 @@ class MessageItemFactory @Inject constructor(
val thumbnailData = ImageContentRenderer.Data(
filename = messageContent.body,
url = messageContent.videoInfo?.thumbnailFile?.url
?: messageContent.videoInfo?.thumbnailUrl,
?: messageContent.videoInfo?.thumbnailUrl,
elementToDecrypt = messageContent.videoInfo?.thumbnailFile?.toElementToDecrypt(),
height = messageContent.videoInfo?.height,
maxHeight = maxHeight,
@ -266,7 +286,7 @@ class MessageItemFactory @Inject constructor(
.clickListener { view -> callback?.onVideoMessageClicked(messageContent, videoData, view) }
.longClickListener { view ->
return@longClickListener callback?.onEventLongClicked(informationData, messageContent, view)
?: false
?: false
}
}

@ -306,7 +326,7 @@ class MessageItemFactory @Inject constructor(
}))
.longClickListener { view ->
return@longClickListener callback?.onEventLongClicked(informationData, messageContent, view)
?: false
?: false
}
}

@ -336,9 +356,9 @@ class MessageItemFactory @Inject constructor(
//nop
}
},
editStart,
editEnd,
Spanned.SPAN_INCLUSIVE_EXCLUSIVE)
editStart,
editEnd,
Spanned.SPAN_INCLUSIVE_EXCLUSIVE)
return spannable
}

@ -376,7 +396,7 @@ class MessageItemFactory @Inject constructor(
}))
.longClickListener { view ->
return@longClickListener callback?.onEventLongClicked(informationData, messageContent, view)
?: false
?: false
}
}

@ -413,7 +433,7 @@ class MessageItemFactory @Inject constructor(
}))
.longClickListener { view ->
return@longClickListener callback?.onEventLongClicked(informationData, messageContent, view)
?: false
?: false
}
}

@ -433,7 +453,7 @@ class MessageItemFactory @Inject constructor(
}))
.longClickListener { view ->
return@longClickListener callback?.onEventLongClicked(informationData, null, view)
?: false
?: false
}
}


View File

@ -16,6 +16,7 @@

package im.vector.riotx.features.home.room.detail.timeline.format

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
@ -23,14 +24,12 @@ import im.vector.matrix.android.api.session.room.model.*
import im.vector.matrix.android.api.session.room.model.call.CallInviteContent
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.riotx.R
import im.vector.riotx.core.di.ActiveSessionHolder
import im.vector.riotx.core.resources.StringProvider
import im.vector.riotx.features.home.room.detail.timeline.helper.senderName
import timber.log.Timber
import javax.inject.Inject

class NoticeEventFormatter @Inject constructor(private val sessionHolder: ActiveSessionHolder,
private val stringProvider: StringProvider) {
class NoticeEventFormatter @Inject constructor(private val stringProvider: StringProvider) {

fun format(timelineEvent: TimelineEvent): CharSequence? {
return when (val type = timelineEvent.root.getClearType()) {
@ -75,10 +74,10 @@ class NoticeEventFormatter @Inject constructor(private val sessionHolder: Active

private fun formatRoomNameEvent(event: Event, senderName: String?): CharSequence? {
val content = event.getClearContent().toModel<RoomNameContent>() ?: return null
return if (content.name.isNullOrBlank()) {
stringProvider.getString(R.string.notice_room_name_removed, senderName)
} else {
return if (!TextUtils.isEmpty(content.name)) {
stringProvider.getString(R.string.notice_room_name_changed, senderName, content.name)
} else {
stringProvider.getString(R.string.notice_room_name_removed, senderName)
}
}

@ -96,7 +95,8 @@ class NoticeEventFormatter @Inject constructor(private val sessionHolder: Active
}

private fun formatRoomHistoryVisibilityEvent(event: Event, senderName: String?): CharSequence? {
val historyVisibility = event.getClearContent().toModel<RoomHistoryVisibilityContent>()?.historyVisibility ?: return null
val historyVisibility = event.getClearContent().toModel<RoomHistoryVisibilityContent>()?.historyVisibility
?: return null

val formattedVisibility = when (historyVisibility) {
RoomHistoryVisibility.SHARED -> stringProvider.getString(R.string.notice_room_visibility_shared)
@ -138,7 +138,7 @@ class NoticeEventFormatter @Inject constructor(private val sessionHolder: Active
private fun buildProfileNotice(event: Event, senderName: String?, eventContent: RoomMember?, prevEventContent: RoomMember?): String? {
val displayText = StringBuilder()
// Check display name has been changed
if (eventContent?.displayName != prevEventContent?.displayName) {
if (!TextUtils.equals(eventContent?.displayName, prevEventContent?.displayName)) {
val displayNameText = when {
prevEventContent?.displayName.isNullOrEmpty() ->
stringProvider.getString(R.string.notice_display_name_set, event.senderId, eventContent?.displayName)
@ -146,12 +146,12 @@ class NoticeEventFormatter @Inject constructor(private val sessionHolder: Active
stringProvider.getString(R.string.notice_display_name_removed, event.senderId, prevEventContent?.displayName)
else ->
stringProvider.getString(R.string.notice_display_name_changed_from,
event.senderId, prevEventContent?.displayName, eventContent?.displayName)
event.senderId, prevEventContent?.displayName, eventContent?.displayName)
}
displayText.append(displayNameText)
}
// Check whether the avatar has been changed
if (eventContent?.avatarUrl != prevEventContent?.avatarUrl) {
if (!TextUtils.equals(eventContent?.avatarUrl, prevEventContent?.avatarUrl)) {
val displayAvatarText = if (displayText.isNotEmpty()) {
displayText.append(" ")
stringProvider.getString(R.string.notice_avatar_changed_too)
@ -168,18 +168,17 @@ class NoticeEventFormatter @Inject constructor(private val sessionHolder: Active
val targetDisplayName = eventContent?.displayName ?: prevEventContent?.displayName ?: ""
return when {
Membership.INVITE == eventContent?.membership -> {
val selfUserId = sessionHolder.getSafeActiveSession()?.myUserId
// TODO get userId
val selfUserId = ""
when {
eventContent.thirdPartyInvite != null -> {
val userWhoHasAccepted = eventContent.thirdPartyInvite?.signed?.mxid ?: event.stateKey
eventContent.thirdPartyInvite != null ->
stringProvider.getString(R.string.notice_room_third_party_registered_invite,
userWhoHasAccepted, eventContent.thirdPartyInvite?.displayName)
}
event.stateKey == selfUserId ->
targetDisplayName, eventContent.thirdPartyInvite?.displayName)
TextUtils.equals(event.stateKey, selfUserId) ->
stringProvider.getString(R.string.notice_room_invite_you, senderDisplayName)
event.stateKey.isNullOrEmpty() ->
event.stateKey.isNullOrEmpty() ->
stringProvider.getString(R.string.notice_room_invite_no_invitee, senderDisplayName)
else ->
else ->
stringProvider.getString(R.string.notice_room_invite, senderDisplayName, targetDisplayName)
}
}
@ -187,7 +186,7 @@ class NoticeEventFormatter @Inject constructor(private val sessionHolder: Active
stringProvider.getString(R.string.notice_room_join, senderDisplayName)
Membership.LEAVE == eventContent?.membership ->
// 2 cases here: this member may have left voluntarily or they may have been "left" by someone else ie. kicked
return if (event.senderId == event.stateKey) {
return if (TextUtils.equals(event.senderId, event.stateKey)) {
if (prevEventContent?.membership == Membership.INVITE) {
stringProvider.getString(R.string.notice_room_reject, senderDisplayName)
} else {

View File

@ -52,8 +52,7 @@ import javax.inject.Singleton
*/
@Singleton
class BugReporter @Inject constructor(private val activeSessionHolder: ActiveSessionHolder,
private val versionProvider: VersionProvider,
private val vectorFileLogger : VectorFileLogger) {
private val versionProvider: VersionProvider) {
var inMultiWindowMode = false

companion object {
@ -163,7 +162,7 @@ class BugReporter @Inject constructor(private val activeSessionHolder: ActiveSes
val gzippedFiles = ArrayList<File>()

if (withDevicesLogs) {
val files = vectorFileLogger.getLogFiles()
val files = VectorFileLogger.getLogFiles()

for (f in files) {
if (!mIsCancelled) {
@ -349,20 +348,20 @@ 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
var `is`: InputStream? = null

try {
inputStream = response.body()!!.byteStream()
`is` = response.body()!!.byteStream()

if (null != inputStream) {
var ch = inputStream.read()
if (null != `is`) {
var ch = `is`.read()
val b = StringBuilder()
while (ch != -1) {
b.append(ch.toChar())
ch = inputStream.read()
ch = `is`.read()
}
serverError = b.toString()
inputStream.close()
`is`.close()

// check if the error message
try {
@ -381,7 +380,7 @@ class BugReporter @Inject constructor(private val activeSessionHolder: ActiveSes
Timber.e(e, "## sendBugReport() : failed to parse error " + e.message)
} finally {
try {
inputStream?.close()
`is`?.close()
} catch (e: Exception) {
Timber.e(e, "## sendBugReport() : failed to close the error stream " + e.message)
}

View File

@ -17,74 +17,43 @@
package im.vector.riotx.features.rageshake

import android.content.Context
import android.util.Log
import im.vector.riotx.features.settings.VectorPreferences
import android.text.TextUtils
import timber.log.Timber
import java.io.File
import java.io.IOException
import java.io.PrintWriter
import java.io.StringWriter
import java.text.SimpleDateFormat
import java.util.*
import java.util.logging.*
import java.util.logging.Formatter
import javax.inject.Inject
import javax.inject.Singleton
import kotlin.collections.ArrayList

private const val LOG_SIZE_BYTES = 20 * 1024 * 1024 // 20MB
object VectorFileLogger : Timber.DebugTree() {

private const val LOG_ROTATION_COUNT = 3


@Singleton
class VectorFileLogger @Inject constructor(val context: Context, private val vectorPreferences: VectorPreferences) : Timber.DebugTree() {
private const val LOG_SIZE_BYTES = 50 * 1024 * 1024 // 50MB

// relatively large rotation count because closing > opening the app rotates the log (!)
private const val LOG_ROTATION_COUNT = 15

private val sLogger = Logger.getLogger("im.vector.riotx")
private var sFileHandler: FileHandler? = null
private var sCacheDirectory: File? = null
private var sFileName = "riotxlogs"
private lateinit var sFileHandler: FileHandler
private lateinit var sCacheDirectory: File
private var sFileName = "riotx"

private val prioPrefixes = mapOf(
Log.VERBOSE to "V/ ",
Log.DEBUG to "D/ ",
Log.INFO to "I/ ",
Log.WARN to "W/ ",
Log.ERROR to "E/ ",
Log.ASSERT to "WTF/ "
)

init {
fun init(context: Context) {
val logsDirectoryFile = context.cacheDir.absolutePath + "/logs"

setLogDirectory(File(logsDirectoryFile))
try {
if (sCacheDirectory != null) {
sFileHandler = FileHandler(sCacheDirectory!!.absolutePath + "/" + sFileName + ".%g.txt", LOG_SIZE_BYTES, LOG_ROTATION_COUNT)
sFileHandler?.formatter = LogFormatter()
sLogger.useParentHandlers = false
sLogger.level = Level.ALL
sLogger.addHandler(sFileHandler)
}
} catch (e: Throwable) {
Timber.e(e, "Failed to initialize FileLogger")
}
init("RiotXLog")
}

override fun log(priority: Int, tag: String?, message: String, t: Throwable?) {
if (sFileHandler == null) return
if (skipLog(priority)) return
if (t != null) {
logToFile(t)
}
logToFile(prioPrefixes[priority] ?: "$priority ", tag ?: "Tag", message)
}

private fun skipLog(priority: Int): Boolean {
return if (vectorPreferences.labAllowedExtendedLogging()) {
false
} else {
priority < Log.ERROR
}
logToFile("$priority ", tag ?: "Tag", message)
}

/**
@ -99,6 +68,24 @@ class VectorFileLogger @Inject constructor(val context: Context, private val vec
sCacheDirectory = cacheDir
}

/**
* Initialises the logger. Should be called AFTER [Log.setLogDirectory].
*
* @param fileName the base file name
*/
private fun init(fileName: String) {
try {
if (!TextUtils.isEmpty(fileName)) {
sFileName = fileName
}
sFileHandler = FileHandler(sCacheDirectory.absolutePath + "/" + sFileName + ".%g.txt", LOG_SIZE_BYTES, LOG_ROTATION_COUNT)
sFileHandler.formatter = LogFormatter()
sLogger.useParentHandlers = false
sLogger.level = Level.ALL
sLogger.addHandler(sFileHandler)
} catch (e: IOException) {
}
}

/**
* Adds our own log files to the provided list of files.
@ -112,8 +99,8 @@ class VectorFileLogger @Inject constructor(val context: Context, private val vec
try {
// reported by GA
if (null != sFileHandler) {
sFileHandler!!.flush()
val absPath = sCacheDirectory?.absolutePath ?: return emptyList()
sFileHandler.flush()
val absPath = sCacheDirectory.absolutePath

for (i in 0..LOG_ROTATION_COUNT) {
val filepath = "$absPath/$sFileName.$i.txt"
@ -124,7 +111,7 @@ class VectorFileLogger @Inject constructor(val context: Context, private val vec
}
}
} catch (e: Exception) {
Timber.e(e, "## addLogFiles() failed : %s", e.message)
Timber.e(e, "## addLogFiles() failed : " + e.message)
}

return files

View File

@ -149,8 +149,6 @@ class VectorPreferences @Inject constructor(private val context: Context) {
private const val SETTINGS_USE_NATIVE_CAMERA_PREFERENCE_KEY = "SETTINGS_USE_NATIVE_CAMERA_PREFERENCE_KEY"
private const val SETTINGS_ENABLE_SEND_VOICE_FEATURE_PREFERENCE_KEY = "SETTINGS_ENABLE_SEND_VOICE_FEATURE_PREFERENCE_KEY"

const val SETTINGS_LABS_ALLOW_EXTENDED_LOGS = "SETTINGS_LABS_ALLOW_EXTENDED_LOGS"

private const val SETTINGS_LABS_SHOW_HIDDEN_EVENTS_PREFERENCE_KEY = "SETTINGS_LABS_SHOW_HIDDEN_EVENTS_PREFERENCE_KEY"
private const val SETTINGS_LABS_ENABLE_SWIPE_TO_REPLY = "SETTINGS_LABS_ENABLE_SWIPE_TO_REPLY"

@ -259,10 +257,6 @@ class VectorPreferences @Inject constructor(private val context: Context) {
return defaultPrefs.getBoolean(SETTINGS_LABS_ENABLE_SWIPE_TO_REPLY, true)
}

fun labAllowedExtendedLogging(): Boolean {
return defaultPrefs.getBoolean(SETTINGS_LABS_ALLOW_EXTENDED_LOGS, false)
}

/**
* Tells if we have already asked the user to disable battery optimisations on android >= M devices.
*

View File

@ -2,7 +2,5 @@
<resources>

<!-- Strings not defined in Riot -->
<string name="labs_allow_extended_logging">Enable verbose logs.</string>
<string name="labs_allow_extended_logging_summary">Verbose logs will help developers by providing more logs when you send a RageShake. Even when enabled, the application does not log message contents or any other private data.</string>

</resources>

View File

@ -45,13 +45,6 @@
android:key="SETTINGS_LABS_ENABLE_SWIPE_TO_REPLY"
android:title="@string/labs_swipe_to_reply_in_timeline" />


<im.vector.riotx.core.preference.VectorSwitchPreference
android:defaultValue="false"
android:key="SETTINGS_LABS_ALLOW_EXTENDED_LOGS"
android:summary="@string/labs_allow_extended_logging_summary"
android:title="@string/labs_allow_extended_logging" />

<!--</im.vector.riotx.core.preference.VectorPreferenceCategory>-->

</androidx.preference.PreferenceScreen>