Merge branch 'release/1.1.1'

This commit is contained in:
Benoit Marty 2021-03-10 22:04:26 +01:00
commit bc81931773
450 changed files with 18176 additions and 16025 deletions

23
.github/dependabot.yml vendored Normal file
View file

@ -0,0 +1,23 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
# Updates for Github Actions used in the repo
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
# Updates for Gradle dependencies used in the app
- package-ecosystem: gradle
directory: "/"
schedule:
interval: "daily"
open-pull-requests-limit: 200
reviewers:
- "bmarty"
### ignore:
### - dependency-name: com.squareup.okhttp3:logging-interceptor
### versions: "> 3.12.10"

View file

@ -31,6 +31,7 @@
<w>signin</w>
<w>signout</w>
<w>signup</w>
<w>snackbar</w>
<w>ssss</w>
<w>sygnal</w>
<w>threepid</w>

View file

@ -1,3 +1,42 @@
Changes in Element 1.1.1 (2021-XX-XX)
===================================================
Features ✨:
-
Improvements 🙌:
- Allow non-HTTPS connections to homeservers on Tor (#2941)
- Fetch homeserver type and version and display in a new setting screen and add info in rageshakes (#2831)
- Improve initial sync performance - split into 2 transactions (#983)
- PIP support for Jitsi call (#2418)
- Add tooltip for room quick actions
- Pre-share session keys when opening a room or start typing (#2771)
- Sending is now queuing by room and not uniquely to the session
- Improve Snackbar duration (#2929)
- Improve sending message state (#2937)
Bugfix 🐛:
- Try to fix crash about UrlPreview (#2640)
- Be robust if Event.type is missing (#2946)
- Snappier message send status
- Fix MainActivity display (#2927)
Translations 🗣:
- All string resources and translations have been moved to the application module. Weblate project for the SDK will be removed.
SDK API changes ⚠️:
-
Build 🧱:
- Update a lot of dependencies, with the help of dependabot.
- Add a script to download and install APK from the CI
Test:
-
Other changes:
- Rework edition of event management
Changes in Element 1.1.0 (2021-02-19)
===================================================
@ -1196,7 +1235,7 @@ Mode details here: https://medium.com/@RiotChat/introducing-the-riotx-beta-for-a
=======================================================
Changes in Element 1.X.X (2021-XX-XX)
Changes in Element 1.1.X (2021-XX-XX)
===================================================
Features ✨:

View file

@ -29,7 +29,7 @@ To create a new screen:
- Then right click on the package, and select `New/New Vector/RiotX Feature`.
- Follow the Wizard, especially replace `Main` by something more relevant to your feature.
- Click on `Finish`.
- Remaining steps are described as TODO in the generated files, or will be pointed out by the compilator, or at runtime :)
- Remaining steps are described as TODO in the generated files, or will be pointed out by the compiler, or at runtime :)
Note that if the templates are modified, the only things to do is to restart Android Studio for the change to take effect.

View file

@ -63,13 +63,13 @@ android {
dependencies {
implementation 'com.github.chrisbanes:PhotoView:2.1.4'
implementation 'io.reactivex.rxjava2:rxkotlin:2.3.0'
implementation 'io.reactivex.rxjava2:rxkotlin:2.4.0'
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.3.2'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation "androidx.recyclerview:recyclerview:1.1.0"
implementation "androidx.recyclerview:recyclerview:1.2.0-beta02"
implementation 'com.google.android.material:material:1.2.1'
implementation 'com.google.android.material:material:1.3.0'
}

View file

@ -1,17 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/design_default_color_primary">
<TextView
android:id="@+id/testPage"
android:layout_centerInParent="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="1"
android:textSize="80sp"
android:textStyle="bold" />
</RelativeLayout>

View file

@ -2,8 +2,8 @@
buildscript {
// Ref: https://kotlinlang.org/releases.html
ext.kotlin_version = '1.4.21'
ext.kotlin_coroutines_version = "1.4.1"
ext.kotlin_version = '1.4.31'
ext.kotlin_coroutines_version = "1.4.2"
repositories {
google()
jcenter()
@ -15,8 +15,9 @@ buildscript {
classpath 'com.android.tools.build:gradle:4.1.2'
classpath 'com.google.gms:google-services:4.3.5'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:2.7.1'
classpath 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.1.1'
classpath 'com.google.android.gms:oss-licenses-plugin:0.10.2'
classpath "com.likethesalad.android:string-reference:1.2.1"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files

View file

@ -0,0 +1,2 @@
Canvis principals d'aquesta versió: millora de VoIP (trucades i videotrucades en xats personals) i correcció d'errors!
Registre de canvis complet: https://github.com/vector-im/element-android/releases/tag/v1.1.0

View file

@ -1,2 +1,2 @@
Hlavní změny v této verzi: Náhled URL, nová klávesice s Emoji, nové možnosti nastavení místností a sníh na vánoce!
Plné znění změn: https://github.com/vector-im/element-android/releases/tag/v1.0.12
Plné znění změn: https://github.com/vector-im/element-android/releases/tag/v1.0.13

View file

@ -0,0 +1,2 @@
Hlavní změny v této verzi: Úpravy práv místností, automatický tmavý/světlý vzhled a řada oprav chyb.
Úplný záznam změn: https://github.com/vector-im/element-android/releases/tag/v1.0.14

View file

@ -0,0 +1,2 @@
Hlavní změny v této verzi: Podpora přihlášení v sociálních sítích.
Úplný záznam změn: https://github.com/vector-im/element-android/releases/tag/v1.0.15

View file

@ -0,0 +1,2 @@
Hlavní změny v této verzi: Podpora přihlášení v sociálních sítích.
Úplný záznam změn: https://github.com/vector-im/element-android/releases/tag/v1.0.15 a https://github.com/vector-im/element-android/releases/tag/v1.0.16

View file

@ -0,0 +1,2 @@
Hlavní změny v této verzi: Opravy chyb!
Úplný záznam změn: https://github.com/vector-im/element-android/releases/tag/v1.0.17

View file

@ -0,0 +1,2 @@
Hlavní změny v této verzi: VoIP (audio a video hovory v DM), vylepšení a opravy chyb!
Úplný záznam změn: https://github.com/vector-im/element-android/releases/tag/v1.1.0

View file

@ -1,30 +1,30 @@
Element je nový typ aplikace pro výměnu zpráv a kolaboraci, která:
Element je nový typ aplikace pro výměnu zpráv a spolupráci, která:
1. Vám dá moc zachovat si soukromí
2. Vás nechá komunikovat s kýmkoli v síti Matrix a dokonce dále integrací s aplikacemi jako Slack
3. Vás ochrání před inzercí, těžbou dat a uzavřenými zahradami
4. Vás zabezpečí šifrováním end-to-end s křížovým podepisováním pro ověření ostatních
1. Vám dá kontrolu nad ochranou vašeho soukromí
2. Umožní vám komunikovat s kýmkoli v síti Matrix a dokonce i mimo ni pomocí integrací s aplikacemi, jako je Slack
3. Ochrání vás před inzercí, dataminingem a uzavřenými zahradami
4. Zabezpečí vás end-to-end šifrováním s křížovým podpisem pro ověření ostatních
Element is completely different from other messaging and collaboration apps because it is decentralised and open source.
Element je zcela odlišný od ostatních aplikací pro zasílání zpráv a spolupráci, protože je decentralizovaný a otevřený.
Element lets you self-host - or choose a host - so that you have privacy, ownership and control of your data and conversations. It gives you access to an open network; so youre not just stuck speaking to other Element users only. And it is very secure.
Element vám umožňuje použít vlastní domovský server - nebo si vybrat hostitele - abyste měli soukromí, vlastnictví a kontrolu nad svými daty a konverzacemi. Poskytuje vám přístup k otevřené síti; takže nejste zaseknuti jen při konverzaci s ostatními uživateli Elementu. A je velmi bezpečný.
Element is able to do all this because it operates on Matrix - the standard for open, decentralised communication.
Element je toho všeho schopen, protože pracuje na Matrixu - standardu otevřené, decentralizované komunikace.
Element puts you in control by letting you choose who hosts your conversations. From the Element app, you can choose to host in different ways:
Element vám dává kontrolu nad tím, že si můžete vybrat, kdo bude hostovat vaše konverzace. Z aplikace Element si můžete vybrat hostování různými způsoby:
1. Get a free account on the matrix.org public server hosted by the Matrix developers, or choose from thousands of public servers hosted by volunteers
2. Self-host your account by running a server on your own hardware
3. Sign up for an account on a custom server by simply subscribing to the Element Matrix Services hosting platform
1. Získejte zdarma účet na veřejném serveru matrix.org hostovaném vývojáři Matrixu, nebo si vyberte z tisíců veřejných serverů hostovaných dobrovolníky
2. Hostujte svůj účet spuštěním serveru na svém vlastním hardwaru
3. Zaregistrujte si účet na vlastním serveru jednoduchým přihlášením k hostitelské platformě Element Matrix Services
<b>Why choose Element?</b>
<b>Proč zvolit Element?</b>
<b>OWN YOUR DATA</b>: You decide where to keep your data and messages. You own it and control it, not some MEGACORP that mines your data or gives access to third parties.
<b>VLASTNĚTE SVÁ DATA</b>: Vy rozhodnete, kde svá data a zprávy ponecháte. Vlastníte je a jsou pod vaší kontrolou, ne nějaký MEGACORP, který těží vaše data nebo poskytuje přístup třetím stranám.
<b>OPEN MESSAGING AND COLLABORATION</b>: You can chat with anyone else in the Matrix network, whether theyre using Element or another Matrix app, and even if they are using a different messaging system of the likes of Slack, IRC or XMPP.
<b>ZPRÁVY A SPOLUPRÁCE</b>: Můžete chatovat s kýmkoli v síti Matrix, ať už používá Element nebo jinou aplikaci, a to i v případě, že používají jiný systém zasílání zpráv, jako je Slack, IRC nebo XMPP.
<b>SUPER-SECURE</b>: Real end-to-end encryption (only those in the conversation can decrypt messages), and cross-signing to verify the devices of conversation participants.
<b>MAXIMÁLNĚ BEZPEČNÉ</b>: Skutečné šifrování typu end-to-end (pouze ti v konverzaci mohou dešifrovat zprávy) a křížové podepisování k ověření zařízení účastníků konverzace.
<b>COMPLETE COMMUNICATION</b>: Messaging, voice and video calls, file sharing, screen sharing and a whole bunch of integrations, bots and widgets. Build rooms, communities, stay in touch and get things done.
<b>KOMPLETNÍ KOMUNIKACE</b>: Zprávy, hlasové hovory a videohovory, sdílení souborů, sdílení obrazovky a celá řada integrací, robotů a widgetů. Budujte místnosti, komunity, zůstaňte v kontaktu a spolupracujte.
<b>EVERYWHERE YOU ARE</b>: Stay in touch wherever you are with fully synchronised message history across all your devices and on the web at https://app.element.io.
<b>KDEKOLIV JSTE</b>: Zůstaňte v kontaktu, ať jste kdekoli, s plně synchronizovanou historií zpráv na všech vašich zařízeních a na webu na adrese https://app.element.io.

View file

@ -1 +1 @@
Zabezpečený decentralizovaný chat & VoIP. Uchovejte svá data v bezpečí.
Zabezpečený decentralizovaný chat a VoIP. Uchovejte svá data v bezpečí.

View file

@ -0,0 +1,2 @@
Hauptänderungen in dieser Version: VoIP-Verbesserung (Audio- und Video-Anrufe in Direktnachrichten) und Fehlerkorrekturen!
Vollständiges Änderungsprotokoll: https://github.com/vector-im/element-android/releases/tag/v1.1.0

View file

@ -0,0 +1,2 @@
Main changes in this version: performance improvement and bug fixes!
Full changelog: https://github.com/vector-im/element-android/releases/tag/v1.1.1

View file

@ -0,0 +1,2 @@
Olulisemad muutused selles versioonis: Heli- ja videokõnede parandused otsevestluses ning üldised veaparandused!
Muudatuste logi täismahus: https://github.com/vector-im/element-android/releases/tag/v1.1.0

View file

@ -1,2 +1,2 @@
Tärkeimmät muutokset tässä versiossa: URL-esikatselu, uusi emoji-näppäimistö, uudet huoneasetukset ja lunta jouluna!
Tärkeimmät muutokset tässä versiossa: URL-esikatselu, uusi emoji-näppäimistö, uudet huoneasetukset ja lunta jouluksi!
Täysi muutosloki: https://github.com/vector-im/element-android/releases/tag/v1.0.12

View file

@ -1,2 +1,2 @@
Tärkeimmät muutokset tässä versiossa: URL-esikatselu, uusi emoji-näppäimistö, uudet huoneasetukset ja lunta jouluna!
Täysi muutosloki: https://github.com/vector-im/element-android/releases/tag/v1.0.12
Tärkeimmät muutokset tässä versiossa: URL-esikatselu, uusi emoji-näppäimistö, uudet huoneasetukset ja lunta jouluksi!
Täysi muutosloki: https://github.com/vector-im/element-android/releases/tag/v1.0.13

View file

@ -0,0 +1,2 @@
Tärkeimmät muutokset tässä versiossa: Huoneoikeuksien muokkaus, automaattinen valoisa/tumma teema ja läjäpäin virheenkorjauksia.
Täysi muutosloki: https://github.com/vector-im/element-android/releases/tag/v1.0.14

View file

@ -0,0 +1,2 @@
Tärkeimmät muutokset tässä versiossa: Social Login -tuki.
Täysi muutosloki: https://github.com/vector-im/element-android/releases/tag/v1.0.15

View file

@ -0,0 +1,2 @@
Tärkeimmät muutokset tässä versiossa: Social Login -tuki.
Täysi muutosloki: https://github.com/vector-im/element-android/releases/tag/v1.0.15 and https://github.com/vector-im/element-android/releases/tag/v1.0.16

View file

@ -0,0 +1,2 @@
Tärkeimmät muutokset tässä versiossa: Virheenkorjauksia!
Täysi muutosloki: https://github.com/vector-im/element-android/releases/tag/v1.0.17

View file

@ -0,0 +1,2 @@
Principais mudanças nessa versão: correções de erros!
Registro de todas as alterações: https://github.com/vector-im/element-android/releases/tag/v1.0.17

View file

@ -0,0 +1,2 @@
Основные изменения в этой версии: Исправлены ошибки!
Полный список изменений: https://github.com/vector-im/element-android/releases/tag/v1.0.17

View file

@ -0,0 +1 @@
ඉලෙමන්ට් (මීට පෙර Riot.im)

View file

@ -0,0 +1,2 @@
Huvudsakliga ändringar i den här versionen: Buggfixar!
Full ändringslogg: https://github.com/vector-im/element-android/releases/tag/v1.0.17

View file

@ -0,0 +1,2 @@
Huvudsakliga ändringar i den här versionen: Förbättringar för VoIP (ljud- och videosamtal i DM) och buggfixar!
Full ändringslogg: https://github.com/vector-im/element-android/releases/tag/v1.1.0

View file

@ -0,0 +1,2 @@
Основні зміни в цій версії: поліпшення VoIP (аудіо та відео дзвінки в DM) та виправлення помилок!
Повний журнал змін: https://github.com/vector-im/element-android/releases/tag/v1.1.0

View file

@ -0,0 +1,2 @@
此版本中的主要變動:錯誤修復!
完整變更紀錄https://github.com/vector-im/element-android/releases/tag/v1.0.17

View file

@ -0,0 +1,2 @@
此版本的主要變更VoIP直接訊息中的音訊與視訊通話改善與錯誤修復
完整變更紀錄https://github.com/vector-im/element-android/releases/tag/v1.1.0

View file

@ -1,7 +1,6 @@
#Fri Jan 29 18:05:42 CET 2021
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionSha256Sum=1433372d903ffba27496f8d5af24265310d2da0d78bf6b4e5138831d4fe066e9
distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.2-all.zip
distributionSha256Sum=9af5c8e7e2cd1a3b0f694a4ac262b9f38c75262e74a9e8b5101af302a6beadd7
distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.3-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View file

@ -36,7 +36,7 @@ android {
dependencies {
implementation project(":matrix-sdk-android")
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'io.reactivex.rxjava2:rxkotlin:2.3.0'
implementation 'io.reactivex.rxjava2:rxkotlin:2.4.0'
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-rx2:$kotlin_coroutines_version"

View file

@ -9,7 +9,7 @@ buildscript {
jcenter()
}
dependencies {
classpath "io.realm:realm-gradle-plugin:10.1.2"
classpath "io.realm:realm-gradle-plugin:10.3.1"
}
}
@ -112,9 +112,9 @@ dependencies {
def lifecycle_version = '2.2.0'
def arch_version = '2.1.0'
def markwon_version = '3.1.0'
def daggerVersion = '2.31'
def work_version = '2.4.0'
def retrofit_version = '2.6.2'
def daggerVersion = '2.33'
def work_version = '2.5.0'
def retrofit_version = '2.9.0'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutines_version"
@ -130,7 +130,7 @@ dependencies {
implementation "com.squareup.retrofit2:retrofit:$retrofit_version"
implementation "com.squareup.retrofit2:converter-moshi:$retrofit_version"
implementation(platform("com.squareup.okhttp3:okhttp-bom:4.8.1"))
implementation(platform("com.squareup.okhttp3:okhttp-bom:4.9.1"))
implementation 'com.squareup.okhttp3:okhttp'
implementation 'com.squareup.okhttp3:logging-interceptor'
implementation 'com.squareup.okhttp3:okhttp-urlconnection'
@ -141,11 +141,11 @@ dependencies {
implementation "ru.noties.markwon:core:$markwon_version"
// Image
implementation 'androidx.exifinterface:exifinterface:1.3.1'
implementation 'androidx.exifinterface:exifinterface:1.3.2'
// Database
implementation 'com.github.Zhuinden:realm-monarchy:0.7.1'
kapt 'dk.ilios:realmfieldnameshelper:1.1.1'
kapt 'dk.ilios:realmfieldnameshelper:2.0.0'
// Work
implementation "androidx.work:work-runtime-ktx:$work_version"
@ -155,7 +155,7 @@ dependencies {
implementation "io.arrow-kt:arrow-instances-core:$arrow_version"
// olm lib is now hosted by jitpack: https://jitpack.io/#org.matrix.gitlab.matrix-org/olm
implementation 'org.matrix.gitlab.matrix-org:olm:3.1.2'
implementation 'org.matrix.gitlab.matrix-org:olm:3.2.2'
// DI
implementation "com.google.dagger:dagger:$daggerVersion"
@ -166,14 +166,14 @@ dependencies {
implementation 'com.facebook.stetho:stetho-okhttp3:1.5.1'
// Phone number https://github.com/google/libphonenumber
implementation 'com.googlecode.libphonenumber:libphonenumber:8.10.23'
implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.19'
testImplementation 'junit:junit:4.13'
testImplementation 'org.robolectric:robolectric:4.3'
testImplementation 'junit:junit:4.13.2'
testImplementation 'org.robolectric:robolectric:4.5.1'
//testImplementation 'org.robolectric:shadows-support-v4:3.0'
// Note: version sticks to 1.9.2 due to https://github.com/mockk/mockk/issues/281
testImplementation 'io.mockk:mockk:1.9.2.kotlin12'
testImplementation 'org.amshove.kluent:kluent-android:1.61'
testImplementation 'io.mockk:mockk:1.10.6'
testImplementation 'org.amshove.kluent:kluent-android:1.65'
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version"
// Plant Timber tree for test
testImplementation 'net.lachlanmckee:timber-junit-rule:1.0.1'
@ -186,7 +186,7 @@ dependencies {
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
androidTestImplementation 'org.amshove.kluent:kluent-android:1.61'
// Note: version sticks to 1.9.2 due to https://github.com/mockk/mockk/issues/281
androidTestImplementation 'io.mockk:mockk-android:1.9.2.kotlin12'
androidTestImplementation 'io.mockk:mockk-android:1.10.6'
androidTestImplementation "androidx.arch.core:core-testing:$arch_version"
androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version"
// Plant Timber tree for test

View file

@ -19,6 +19,15 @@ package org.matrix.android.sdk.common
import android.content.Context
import android.net.Uri
import androidx.lifecycle.Observer
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withTimeout
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertTrue
import org.matrix.android.sdk.api.Matrix
import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.MatrixConfiguration
@ -34,15 +43,6 @@ import org.matrix.android.sdk.api.session.room.timeline.Timeline
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings
import org.matrix.android.sdk.api.session.sync.SyncState
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withTimeout
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertTrue
import java.util.ArrayList
import java.util.UUID
import java.util.concurrent.CountDownLatch
@ -59,7 +59,13 @@ class CommonTestHelper(context: Context) {
fun getTestInterceptor(session: Session): MockOkHttpInterceptor? = TestNetworkModule.interceptorForSession(session.sessionId) as? MockOkHttpInterceptor
init {
Matrix.initialize(context, MatrixConfiguration("TestFlavor"))
Matrix.initialize(
context,
MatrixConfiguration(
applicationFlavor = "TestFlavor",
roomDisplayNameFallbackProvider = TestRoomDisplayNameFallbackProvider()
)
)
matrix = Matrix.getInstance(context)
}
@ -385,8 +391,8 @@ fun List<TimelineEvent>.checkSendOrder(baseTextMessage: String, numberOfMessages
return drop(startIndex)
.take(numberOfMessages)
.foldRightIndexed(true) { index, timelineEvent, acc ->
val body = timelineEvent.root.content.toModel<MessageContent>()?.body
val currentMessageSuffix = numberOfMessages - index
acc && (body == null || body.startsWith(baseTextMessage) && body.endsWith("#$currentMessageSuffix"))
}
val body = timelineEvent.root.content.toModel<MessageContent>()?.body
val currentMessageSuffix = numberOfMessages - index
acc && (body == null || body.startsWith(baseTextMessage) && body.endsWith("#$currentMessageSuffix"))
}
}

View file

@ -0,0 +1,40 @@
/*
* Copyright (c) 2021 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.common
import org.matrix.android.sdk.api.RoomDisplayNameFallbackProvider
class TestRoomDisplayNameFallbackProvider() : RoomDisplayNameFallbackProvider {
override fun getNameForRoomInvite() =
"Room invite"
override fun getNameForEmptyRoom() =
"Empty room"
override fun getNameFor2members(name1: String?, name2: String?) =
"$name1 and $name2"
override fun getNameFor3members(name1: String?, name2: String?, name3: String?) =
"$name1, $name2 and $name3"
override fun getNameFor4members(name1: String?, name2: String?, name3: String?, name4: String?) =
"$name1, $name2, $name3 and $name4"
override fun getNameFor4membersAndMore(name1: String?, name2: String?, name3: String?, remainingCount: Int) =
"$name1, $name2, $name3 and $remainingCount others"
}

View file

@ -0,0 +1,103 @@
/*
* Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.internal.crypto
import android.util.Log
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.matrix.android.sdk.InstrumentedTest
import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.common.CommonTestHelper
import org.matrix.android.sdk.common.CryptoTestHelper
import org.matrix.android.sdk.internal.crypto.model.event.EncryptedEventContent
import org.matrix.android.sdk.internal.crypto.model.event.RoomKeyContent
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
@RunWith(AndroidJUnit4::class)
@FixMethodOrder(MethodSorters.JVM)
class PreShareKeysTest : InstrumentedTest {
private val mTestHelper = CommonTestHelper(context())
private val mCryptoTestHelper = CryptoTestHelper(mTestHelper)
@Test
fun ensure_outbound_session_happy_path() {
val testData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true)
val e2eRoomID = testData.roomId
val aliceSession = testData.firstSession
val bobSession = testData.secondSession!!
// clear any outbound session
aliceSession.cryptoService().discardOutboundSession(e2eRoomID)
val preShareCount = bobSession.cryptoService().getGossipingEvents().count {
it.senderId == aliceSession.myUserId
&& it.getClearType() == EventType.ROOM_KEY
}
assertEquals(0, preShareCount, "Bob should not have receive any key from alice at this point")
Log.d("#Test", "Room Key Received from alice $preShareCount")
// Force presharing of new outbound key
mTestHelper.doSync<Unit> {
aliceSession.cryptoService().prepareToEncrypt(e2eRoomID, it)
}
mTestHelper.waitWithLatch { latch ->
mTestHelper.retryPeriodicallyWithLatch(latch) {
val newGossipCount = bobSession.cryptoService().getGossipingEvents().count {
it.senderId == aliceSession.myUserId
&& it.getClearType() == EventType.ROOM_KEY
}
newGossipCount > preShareCount
}
}
val latest = bobSession.cryptoService().getGossipingEvents().lastOrNull {
it.senderId == aliceSession.myUserId
&& it.getClearType() == EventType.ROOM_KEY
}
val content = latest?.getClearContent().toModel<RoomKeyContent>()
assertNotNull(content, "Bob should have received and decrypted a room key event from alice")
assertEquals(e2eRoomID, content.roomId, "Wrong room")
val megolmSessionId = content.sessionId!!
val sharedIndex = aliceSession.cryptoService().getSharedWithInfo(e2eRoomID, megolmSessionId)
.getObject(bobSession.myUserId, bobSession.sessionParams.deviceId)
assertEquals(0, sharedIndex, "The session received by bob should match what alice sent")
// Just send a real message as test
val sentEvent = mTestHelper.sendTextMessage(aliceSession.getRoom(e2eRoomID)!!, "Allo", 1).first()
assertEquals(megolmSessionId, sentEvent.root.content.toModel<EncryptedEventContent>()?.sessionId, "Unexpected megolm session")
mTestHelper.waitWithLatch { latch ->
mTestHelper.retryPeriodicallyWithLatch(latch) {
bobSession.getRoom(e2eRoomID)?.getTimeLineEvent(sentEvent.eventId)?.root?.getClearType() == EventType.MESSAGE
}
}
mTestHelper.signOutAndClose(aliceSession)
mTestHelper.signOutAndClose(bobSession)
}
}

View file

@ -51,7 +51,7 @@ class WithHeldTests : InstrumentedTest {
// =============================
val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
val bobSession = mTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
val bobSession = mTestHelper.createAccount(TestConstants.USER_BOB, SessionTestParams(true))
// Initialize cross signing on both
mCryptoTestHelper.initializeCrossSigning(aliceSession)

View file

@ -39,7 +39,11 @@ data class MatrixConfiguration(
/**
* True to advertise support for call transfers to other parties on Matrix calls.
*/
val supportsCallTransfer: Boolean = false
val supportsCallTransfer: Boolean = false,
/**
* RoomDisplayNameFallbackProvider to provide default room display name.
*/
val roomDisplayNameFallbackProvider: RoomDisplayNameFallbackProvider
) {
/**

View file

@ -0,0 +1,26 @@
/*
* Copyright (c) 2021 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.api
interface RoomDisplayNameFallbackProvider {
fun getNameForRoomInvite(): String
fun getNameForEmptyRoom(): String
fun getNameFor2members(name1: String?, name2: String?): String
fun getNameFor3members(name1: String?, name2: String?, name3: String?): String
fun getNameFor4members(name1: String?, name2: String?, name3: String?, name4: String?): String
fun getNameFor4membersAndMore(name1: String?, name2: String?, name3: String?, remainingCount: Int): String
}

View file

@ -0,0 +1,29 @@
/*
* Copyright (c) 2021 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.api.crypto
enum class VerificationState {
REQUEST,
WAITING,
CANCELED_BY_ME,
CANCELED_BY_OTHER,
DONE
}
fun VerificationState.isCanceled(): Boolean {
return this == VerificationState.CANCELED_BY_ME || this == VerificationState.CANCELED_BY_OTHER
}

View file

@ -0,0 +1,24 @@
/*
* Copyright (c) 2021 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.api.federation
interface FederationService {
/**
* Get information about the homeserver
*/
suspend fun getFederationVersion(): FederationVersion
}

View file

@ -0,0 +1,31 @@
/*
* Copyright (c) 2021 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.api.federation
/**
* Ref: https://matrix.org/docs/spec/server_server/latest#get-matrix-federation-v1-version
*/
data class FederationVersion(
/**
* Arbitrary name that identify this implementation.
*/
val name: String?,
/**
* Version of this implementation. The version format depends on the implementation.
*/
val version: String?
)

View file

@ -21,6 +21,7 @@ import androidx.lifecycle.LiveData
import okhttp3.OkHttpClient
import org.matrix.android.sdk.api.auth.data.SessionParams
import org.matrix.android.sdk.api.failure.GlobalError
import org.matrix.android.sdk.api.federation.FederationService
import org.matrix.android.sdk.api.pushrules.PushRuleService
import org.matrix.android.sdk.api.session.account.AccountService
import org.matrix.android.sdk.api.session.accountdata.AccountDataService
@ -34,6 +35,7 @@ import org.matrix.android.sdk.api.session.file.FileService
import org.matrix.android.sdk.api.session.group.GroupService
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilitiesService
import org.matrix.android.sdk.api.session.identity.IdentityService
import org.matrix.android.sdk.api.session.initsync.InitialSyncProgressService
import org.matrix.android.sdk.api.session.integrationmanager.IntegrationManagerService
import org.matrix.android.sdk.api.session.media.MediaService
import org.matrix.android.sdk.api.session.permalinks.PermalinkService
@ -213,6 +215,11 @@ interface Session :
*/
fun searchService(): SearchService
/**
* Returns the federation service associated with the session
*/
fun federationService(): FederationService
/**
* Returns the third party service associated with the session
*/

View file

@ -156,4 +156,10 @@ interface CryptoService {
fun getWithHeldMegolmSession(roomId: String, sessionId: String): RoomKeyWithHeldContent?
fun logDbUsageInfo()
/**
* Perform any background tasks that can be done before a message is ready to
* send, in order to speed up sending of the message.
*/
fun prepareToEncrypt(roomId: String, callback: MatrixCallback<Unit>)
}

View file

@ -66,7 +66,7 @@ inline fun <reified T> T.toContent(): Content {
*/
@JsonClass(generateAdapter = true)
data class Event(
@Json(name = "type") val type: String,
@Json(name = "type") val type: String? = null,
@Json(name = "event_id") val eventId: String? = null,
@Json(name = "content") val content: Content? = null,
@Json(name = "prev_content") val prevContent: Content? = null,
@ -98,6 +98,19 @@ data class Event(
@Transient
var ageLocalTs: Long? = null
/**
* Copy all fields, including transient fields
*/
fun copyAll(): Event {
return copy().also {
it.mxDecryptionResult = mxDecryptionResult
it.mCryptoError = mCryptoError
it.mCryptoErrorReason = mCryptoErrorReason
it.sendState = sendState
it.ageLocalTs = ageLocalTs
}
}
/**
* Check if event is a state event.
* @return true if event is state event.
@ -135,7 +148,7 @@ data class Event(
* @return the event type
*/
fun getClearType(): String {
return mxDecryptionResult?.payload?.get("type")?.toString() ?: type
return mxDecryptionResult?.payload?.get("type")?.toString() ?: type ?: EventType.MISSING_TYPE
}
/**

View file

@ -20,6 +20,8 @@ package org.matrix.android.sdk.api.session.events.model
* Constants defining known event types from Matrix specifications.
*/
object EventType {
// Used when the type is missing, which should not happen
const val MISSING_TYPE = "org.matrix.android.sdk.missing_type"
const val PRESENCE = "m.presence"
const val MESSAGE = "m.room.message"

View file

@ -21,6 +21,11 @@ package org.matrix.android.sdk.api.session.homeserver
*/
interface HomeServerCapabilitiesService {
/**
* Force a refresh of the stored data
*/
suspend fun refreshHomeServerCapabilities()
/**
* Get the HomeServer capabilities
*/

View file

@ -0,0 +1,30 @@
/*
* Copyright (c) 2021 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.api.session.initsync
enum class InitSyncStep {
ServerComputing,
Downloading,
ImportingAccount,
ImportingAccountCrypto,
ImportingAccountRoom,
ImportingAccountGroups,
ImportingAccountData,
ImportingAccountJoinedRooms,
ImportingAccountInvitedRooms,
ImportingAccountLeftRooms
}

View file

@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@ -13,9 +13,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.api.session
package org.matrix.android.sdk.api.session.initsync
import androidx.annotation.StringRes
import androidx.lifecycle.LiveData
interface InitialSyncProgressService {
@ -25,7 +24,7 @@ interface InitialSyncProgressService {
sealed class Status {
object Idle : Status()
data class Progressing(
@StringRes val statusText: Int,
val initSyncStep: InitSyncStep,
val percentProgress: Int = 0
) : Status()
}

View file

@ -16,11 +16,9 @@
package org.matrix.android.sdk.api.session.room
import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility
import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoomsParams
import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoomsResponse
import org.matrix.android.sdk.api.util.Cancelable
/**
* This interface defines methods to get and join public rooms. It's implemented at the session level.
@ -30,9 +28,8 @@ interface RoomDirectoryService {
/**
* Get rooms from directory
*/
fun getPublicRooms(server: String?,
publicRoomsParams: PublicRoomsParams,
callback: MatrixCallback<PublicRoomsResponse>): Cancelable
suspend fun getPublicRooms(server: String?,
publicRoomsParams: PublicRoomsParams): PublicRoomsResponse
/**
* Get the visibility of a room in the directory

View file

@ -30,4 +30,10 @@ interface RoomCryptoService {
* Enable encryption of the room
*/
suspend fun enableEncryption(algorithm: String = MXCRYPTO_ALGORITHM_MEGOLM)
/**
* Ensures all members of the room are loaded and outbound session keys are shared.
* If this method is not called, CryptoService will ensure it before sending events.
*/
suspend fun prepareToEncrypt()
}

View file

@ -18,7 +18,7 @@ package org.matrix.android.sdk.api.session.room.model
import org.matrix.android.sdk.api.session.events.model.Content
data class EditAggregatedSummary(
val aggregatedContent: Content? = null,
val latestContent: Content? = null,
// The list of the eventIDs used to build the summary (might be out of sync if chunked received from message chunk)
val sourceEvents: List<String>,
val localEchos: List<String>,

View file

@ -17,7 +17,7 @@ package org.matrix.android.sdk.api.session.room.model
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import org.matrix.android.sdk.internal.session.room.VerificationState
import org.matrix.android.sdk.api.crypto.VerificationState
/**
* Contains an aggregated summary info of the references.

View file

@ -16,7 +16,6 @@
package org.matrix.android.sdk.api.session.room.model.relation
import androidx.lifecycle.LiveData
import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.room.model.EventAnnotationsSummary
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
@ -67,11 +66,11 @@ interface RelationService {
/**
* Edit a text message body. Limited to "m.text" contentType
* @param targetEventId The event to edit
* @param targetEvent The event to edit
* @param newBodyText The edited body
* @param compatibilityBodyText The text that will appear on clients that don't support yet edition
*/
fun editTextMessage(targetEventId: String,
fun editTextMessage(targetEvent: TimelineEvent,
msgType: String,
newBodyText: CharSequence,
newBodyAutoMarkdown: Boolean,
@ -92,8 +91,11 @@ interface RelationService {
/**
* Get the edit history of the given event
* The return list will contain the original event and all the editions of this event, done by the
* same sender, sorted in the reverse order (so the original event is the latest element, and the
* latest edition is the first element of the list)
*/
fun fetchEditHistory(eventId: String, callback: MatrixCallback<List<Event>>)
suspend fun fetchEditHistory(eventId: String): List<Event>
/**
* Reply to an event in the timeline (must be in same room)

View file

@ -17,14 +17,11 @@
package org.matrix.android.sdk.api.session.room.powerlevels
import androidx.annotation.StringRes
import org.matrix.android.sdk.R
sealed class Role(open val value: Int, @StringRes val res: Int) : Comparable<Role> {
object Admin : Role(100, R.string.power_level_admin)
object Moderator : Role(50, R.string.power_level_moderator)
object Default : Role(0, R.string.power_level_default)
data class Custom(override val value: Int) : Role(value, R.string.power_level_custom)
sealed class Role(open val value: Int) : Comparable<Role> {
object Admin : Role(100)
object Moderator : Role(50)
object Default : Role(0)
data class Custom(override val value: Int) : Role(value)
override fun compareTo(other: Role): Int {
return value.compareTo(other.value)

View file

@ -132,4 +132,9 @@ interface SendService {
* Resend all failed messages one by one (and keep order)
*/
fun resendAllFailedMessages()
/**
* Cancel all failed messages
*/
fun cancelAllFailedMessages()
}

View file

@ -38,6 +38,9 @@ import org.matrix.android.sdk.api.util.ContentUtils.extractUsefulTextFromReply
*/
data class TimelineEvent(
val root: Event,
/**
* Uniquely identify an event, computed locally by the sdk
*/
val localId: Long,
val eventId: String,
val displayIndex: Int,
@ -123,8 +126,7 @@ fun TimelineEvent.getLastMessageContent(): MessageContent? {
return if (root.getClearType() == EventType.STICKER) {
root.getClearContent().toModel<MessageStickerContent>()
} else {
annotations?.editSummary?.aggregatedContent?.toModel()
?: root.getClearContent().toModel()
(annotations?.editSummary?.latestContent ?: root.getClearContent()).toModel()
}
}

View file

@ -36,9 +36,23 @@ interface TimelineService {
*/
fun createTimeline(eventId: String?, settings: TimelineSettings): Timeline
/**
* Returns a snapshot of TimelineEvent event with eventId.
* At the opposite of getTimeLineEventLive which will be updated when local echo event is synced, it will return null in this case.
* @param eventId the eventId to get the TimelineEvent
*/
fun getTimeLineEvent(eventId: String): TimelineEvent?
/**
* Creates a LiveData of Optional TimelineEvent event with eventId.
* If the eventId is a local echo eventId, it will make the LiveData be updated with the synced TimelineEvent when coming through the sync.
* In this case, makes sure to use the new synced eventId from the TimelineEvent class if you want to interact, as the local echo is removed from the SDK.
* @param eventId the eventId to listen for TimelineEvent
*/
fun getTimeLineEventLive(eventId: String): LiveData<Optional<TimelineEvent>>
/**
* Returns a snapshot list of TimelineEvent with EventType.MESSAGE and MessageType.MSGTYPE_IMAGE or MessageType.MSGTYPE_VIDEO.
*/
fun getAttachmentMessages(): List<TimelineEvent>
}

View file

@ -22,9 +22,9 @@ interface FilterService {
NoFilter,
/**
* Filter for Riot, will include only known event type
* Filter for Element, will include only known event type
*/
RiotFilter
ElementFilter
}
/**

View file

@ -53,6 +53,7 @@ import org.matrix.android.sdk.api.session.room.model.RoomMemberContent
import org.matrix.android.sdk.internal.crypto.actions.MegolmSessionDataImporter
import org.matrix.android.sdk.internal.crypto.actions.SetDeviceVerificationAction
import org.matrix.android.sdk.internal.crypto.algorithms.IMXEncrypting
import org.matrix.android.sdk.internal.crypto.algorithms.IMXGroupEncryption
import org.matrix.android.sdk.internal.crypto.algorithms.IMXWithHeldExtension
import org.matrix.android.sdk.internal.crypto.algorithms.megolm.MXMegolmEncryptionFactory
import org.matrix.android.sdk.internal.crypto.algorithms.olm.MXOlmEncryptionFactory
@ -97,7 +98,6 @@ import org.matrix.olm.OlmManager
import timber.log.Timber
import java.util.concurrent.atomic.AtomicBoolean
import javax.inject.Inject
import kotlin.jvm.Throws
import kotlin.math.max
/**
@ -667,7 +667,12 @@ internal class DefaultCryptoService @Inject constructor(
override fun discardOutboundSession(roomId: String) {
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
roomEncryptorsStore.get(roomId)?.discardSessionKey()
val roomEncryptor = roomEncryptorsStore.get(roomId)
if (roomEncryptor is IMXGroupEncryption) {
roomEncryptor.discardSessionKey()
} else {
Timber.e("## CRYPTO | discardOutboundSession() for:$roomId: Unable to handle IMXGroupEncryption")
}
}
}
@ -703,7 +708,7 @@ internal class DefaultCryptoService @Inject constructor(
*/
@Throws(MXCryptoError::class)
private fun internalDecryptEvent(event: Event, timeline: String): MXEventDecryptionResult {
return eventDecryptor.decryptEvent(event, timeline)
return eventDecryptor.decryptEvent(event, timeline)
}
/**
@ -1290,6 +1295,43 @@ internal class DefaultCryptoService @Inject constructor(
cryptoStore.logDbUsageInfo()
}
override fun prepareToEncrypt(roomId: String, callback: MatrixCallback<Unit>) {
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
Timber.d("## CRYPTO | prepareToEncrypt() : Check room members up to date")
// Ensure to load all room members
try {
loadRoomMembersTask.execute(LoadRoomMembersTask.Params(roomId))
} catch (failure: Throwable) {
Timber.e("## CRYPTO | prepareToEncrypt() : Failed to load room members")
callback.onFailure(failure)
return@launch
}
val userIds = getRoomUserIds(roomId)
val alg = roomEncryptorsStore.get(roomId)
?: getEncryptionAlgorithm(roomId)
?.let { setEncryptionInRoom(roomId, it, false, userIds) }
?.let { roomEncryptorsStore.get(roomId) }
if (alg == null) {
val reason = String.format(MXCryptoError.UNABLE_TO_ENCRYPT_REASON, MXCryptoError.NO_MORE_ALGORITHM_REASON)
Timber.e("## CRYPTO | prepareToEncrypt() : $reason")
callback.onFailure(IllegalArgumentException("Missing algorithm"))
return@launch
}
runCatching {
(alg as? IMXGroupEncryption)?.preshareKey(userIds)
}.fold(
{ callback.onSuccess(Unit) },
{
Timber.e("## CRYPTO | prepareToEncrypt() failed.")
callback.onFailure(it)
}
)
}
}
/* ==========================================================================================
* For test only
* ========================================================================================== */

View file

@ -105,7 +105,7 @@ internal class EventDecryptor @Inject constructor(
try {
return alg.decryptEvent(event, timeline)
} catch (mxCryptoError: MXCryptoError) {
Timber.d("## CRYPTO | internalDecryptEvent : Failed to decrypt ${event.eventId} reason: $mxCryptoError")
Timber.v("## CRYPTO | internalDecryptEvent : Failed to decrypt ${event.eventId} reason: $mxCryptoError")
if (algorithm == MXCRYPTO_ALGORITHM_OLM) {
if (mxCryptoError is MXCryptoError.Base
&& mxCryptoError.errorType == MXCryptoError.ErrorType.BAD_ENCRYPTED_MESSAGE) {

View file

@ -28,6 +28,7 @@ import org.matrix.android.sdk.api.session.crypto.keyshare.GossipingRequestListen
import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.internal.crypto.algorithms.IMXGroupEncryption
import org.matrix.android.sdk.internal.crypto.crosssigning.toBase64NoPadding
import org.matrix.android.sdk.internal.crypto.keysbackup.util.extractCurveKeyFromRecoveryKey
import org.matrix.android.sdk.internal.crypto.model.rest.GossipingDefaultContent
@ -290,12 +291,16 @@ internal class IncomingGossipingRequestManager @Inject constructor(
.also { cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED) }
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
val isSuccess = roomEncryptor.reshareKey(sessionId, userId, deviceId, senderKey)
if (roomEncryptor is IMXGroupEncryption) {
val isSuccess = roomEncryptor.reshareKey(sessionId, userId, deviceId, senderKey)
if (isSuccess) {
cryptoStore.updateGossipingRequestState(request, GossipingRequestState.ACCEPTED)
if (isSuccess) {
cryptoStore.updateGossipingRequestState(request, GossipingRequestState.ACCEPTED)
} else {
cryptoStore.updateGossipingRequestState(request, GossipingRequestState.UNABLE_TO_PROCESS)
}
} else {
cryptoStore.updateGossipingRequestState(request, GossipingRequestState.UNABLE_TO_PROCESS)
Timber.e("## CRYPTO | handleKeyRequestFromOtherUser() from:$userId: Unable to handle IMXGroupEncryption.reshareKey for $alg")
}
}
cryptoStore.updateGossipingRequestState(request, GossipingRequestState.RE_REQUESTED)

View file

@ -32,34 +32,4 @@ internal interface IMXEncrypting {
* @return the encrypted content
*/
suspend fun encryptEventContent(eventContent: Content, eventType: String, userIds: List<String>): Content
/**
* In Megolm, each recipient maintains a record of the ratchet value which allows
* them to decrypt any messages sent in the session after the corresponding point
* in the conversation. If this value is compromised, an attacker can similarly
* decrypt past messages which were encrypted by a key derived from the
* compromised or subsequent ratchet values. This gives 'partial' forward
* secrecy.
*
* To mitigate this issue, the application should offer the user the option to
* discard historical conversations, by winding forward any stored ratchet values,
* or discarding sessions altogether.
*/
fun discardSessionKey()
/**
* Re-shares a session key with devices if the key has already been
* sent to them.
*
* @param sessionId The id of the outbound session to share.
* @param userId The id of the user who owns the target device.
* @param deviceId The id of the target device.
* @param senderKey The key of the originating device for the session.
*
* @return true in case of success
*/
suspend fun reshareKey(sessionId: String,
userId: String,
deviceId: String,
senderKey: String): Boolean
}

View file

@ -0,0 +1,52 @@
/*
* Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.internal.crypto.algorithms
internal interface IMXGroupEncryption {
/**
* In Megolm, each recipient maintains a record of the ratchet value which allows
* them to decrypt any messages sent in the session after the corresponding point
* in the conversation. If this value is compromised, an attacker can similarly
* decrypt past messages which were encrypted by a key derived from the
* compromised or subsequent ratchet values. This gives 'partial' forward
* secrecy.
*
* To mitigate this issue, the application should offer the user the option to
* discard historical conversations, by winding forward any stored ratchet values,
* or discarding sessions altogether.
*/
fun discardSessionKey()
suspend fun preshareKey(userIds: List<String>)
/**
* Re-shares a session key with devices if the key has already been
* sent to them.
*
* @param sessionId The id of the outbound session to share.
* @param userId The id of the user who owns the target device.
* @param deviceId The id of the target device.
* @param senderKey The key of the originating device for the session.
*
* @return true in case of success
*/
suspend fun reshareKey(sessionId: String,
userId: String,
deviceId: String,
senderKey: String): Boolean
}

View file

@ -18,8 +18,6 @@ package org.matrix.android.sdk.internal.crypto.algorithms.megolm
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.auth.data.Credentials
import org.matrix.android.sdk.api.session.crypto.MXCryptoError
import org.matrix.android.sdk.api.session.events.model.Content
import org.matrix.android.sdk.api.session.events.model.Event
@ -30,6 +28,7 @@ import org.matrix.android.sdk.internal.crypto.MXOlmDevice
import org.matrix.android.sdk.internal.crypto.actions.EnsureOlmSessionsForDevicesAction
import org.matrix.android.sdk.internal.crypto.actions.MessageEncrypter
import org.matrix.android.sdk.internal.crypto.algorithms.IMXEncrypting
import org.matrix.android.sdk.internal.crypto.algorithms.IMXGroupEncryption
import org.matrix.android.sdk.internal.crypto.keysbackup.DefaultKeysBackupService
import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo
import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap
@ -39,8 +38,6 @@ import org.matrix.android.sdk.internal.crypto.model.forEach
import org.matrix.android.sdk.internal.crypto.repository.WarnOnUnknownDeviceRepository
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask
import org.matrix.android.sdk.internal.task.TaskExecutor
import org.matrix.android.sdk.internal.task.configureWith
import org.matrix.android.sdk.internal.util.JsonCanonicalizer
import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers
import org.matrix.android.sdk.internal.util.convertToUTF8
@ -54,14 +51,14 @@ internal class MXMegolmEncryption(
private val cryptoStore: IMXCryptoStore,
private val deviceListManager: DeviceListManager,
private val ensureOlmSessionsForDevicesAction: EnsureOlmSessionsForDevicesAction,
private val credentials: Credentials,
private val userId: String,
private val deviceId: String,
private val sendToDeviceTask: SendToDeviceTask,
private val messageEncrypter: MessageEncrypter,
private val warnOnUnknownDevicesRepository: WarnOnUnknownDeviceRepository,
private val taskExecutor: TaskExecutor,
private val coroutineDispatchers: MatrixCoroutineDispatchers,
private val cryptoCoroutineScope: CoroutineScope
) : IMXEncrypting {
) : IMXEncrypting, IMXGroupEncryption {
// OutboundSessionInfo. Null if we haven't yet started setting one up. Note
// that even if this is non-null, it may not be ready for use (in which
@ -93,6 +90,7 @@ internal class MXMegolmEncryption(
// annoyingly we have to serialize again the saved outbound session to store message index :/
// if not we would see duplicate message index errors
olmDevice.storeOutboundGroupSessionForRoom(roomId, outboundSession.sessionId)
Timber.v("## CRYPTO | encryptEventContent: Finished in ${System.currentTimeMillis() - ts} millis")
}
}
@ -117,6 +115,16 @@ internal class MXMegolmEncryption(
olmDevice.discardOutboundGroupSessionForRoom(roomId)
}
override suspend fun preshareKey(userIds: List<String>) {
val ts = System.currentTimeMillis()
Timber.v("## CRYPTO | preshareKey : getDevicesInRoom")
val devices = getDevicesInRoom(userIds)
val outboundSession = ensureOutboundSession(devices.allowedDevices)
notifyWithheldForSession(devices.withHeldDevices, outboundSession)
Timber.v("## CRYPTO | preshareKey ${System.currentTimeMillis() - ts} millis")
}
/**
* Prepare a new session.
*
@ -252,7 +260,7 @@ internal class MXMegolmEncryption(
continue
}
Timber.i("## CRYPTO | shareUserDevicesKey() : Sharing keys with device $userId:$deviceID")
Timber.i("## CRYPTO | shareUserDevicesKey() : Add to share keys contentMap for $userId:$deviceID")
contentMap.setObject(userId, deviceID, messageEncrypter.encryptMessage(payload, listOf(sessionResult.deviceInfo)))
haveTargets = true
}
@ -263,12 +271,14 @@ 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.
val gossipingEventBuffer = arrayListOf<Event>()
for ((userId, devicesToShareWith) in devicesByUser) {
for ((deviceId) in devicesToShareWith) {
session.sharedWithHelper.markedSessionAsShared(userId, deviceId, chainIndex)
cryptoStore.saveGossipingEvent(Event(
gossipingEventBuffer.add(
Event(
type = EventType.ROOM_KEY,
senderId = credentials.userId,
senderId = this.userId,
content = submap.apply {
this["session_key"] = ""
// we add a fake key for trail
@ -278,6 +288,8 @@ internal class MXMegolmEncryption(
}
}
cryptoStore.saveGossipingEvents(gossipingEventBuffer)
if (haveTargets) {
t0 = System.currentTimeMillis()
Timber.i("## CRYPTO | shareUserDevicesKey() ${session.sessionId} : has target")
@ -294,8 +306,11 @@ internal class MXMegolmEncryption(
}
}
private fun notifyKeyWithHeld(targets: List<UserDevice>, sessionId: String, senderKey: String?, code: WithHeldCode) {
Timber.i("## CRYPTO | notifyKeyWithHeld() :sending withheld key for $targets session:$sessionId ")
private suspend fun notifyKeyWithHeld(targets: List<UserDevice>,
sessionId: String,
senderKey: String?,
code: WithHeldCode) {
Timber.i("## CRYPTO | notifyKeyWithHeld() :sending withheld key for $targets session:$sessionId and code $code")
val withHeldContent = RoomKeyWithHeldContent(
roomId = roomId,
senderKey = senderKey,
@ -311,13 +326,11 @@ internal class MXMegolmEncryption(
}
}
)
sendToDeviceTask.configureWith(params) {
callback = object : MatrixCallback<Unit> {
override fun onFailure(failure: Throwable) {
Timber.e("## CRYPTO | notifyKeyWithHeld() : Failed to notify withheld key for $targets session: $sessionId ")
}
}
}.executeBy(taskExecutor)
try {
sendToDeviceTask.execute(params)
} catch (failure: Throwable) {
Timber.e("## CRYPTO | notifyKeyWithHeld() : Failed to notify withheld key for $targets session: $sessionId ")
}
}
/**
@ -343,7 +356,7 @@ internal class MXMegolmEncryption(
// Include our device ID so that recipients can send us a
// m.new_device message if they don't have our session key.
map["device_id"] = credentials.deviceId!!
map["device_id"] = deviceId
session.useCount++
return map
}

View file

@ -17,7 +17,6 @@
package org.matrix.android.sdk.internal.crypto.algorithms.megolm
import kotlinx.coroutines.CoroutineScope
import org.matrix.android.sdk.api.auth.data.Credentials
import org.matrix.android.sdk.internal.crypto.DeviceListManager
import org.matrix.android.sdk.internal.crypto.MXOlmDevice
import org.matrix.android.sdk.internal.crypto.actions.EnsureOlmSessionsForDevicesAction
@ -26,7 +25,8 @@ import org.matrix.android.sdk.internal.crypto.keysbackup.DefaultKeysBackupServic
import org.matrix.android.sdk.internal.crypto.repository.WarnOnUnknownDeviceRepository
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask
import org.matrix.android.sdk.internal.task.TaskExecutor
import org.matrix.android.sdk.internal.di.DeviceId
import org.matrix.android.sdk.internal.di.UserId
import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers
import javax.inject.Inject
@ -36,29 +36,29 @@ internal class MXMegolmEncryptionFactory @Inject constructor(
private val cryptoStore: IMXCryptoStore,
private val deviceListManager: DeviceListManager,
private val ensureOlmSessionsForDevicesAction: EnsureOlmSessionsForDevicesAction,
private val credentials: Credentials,
@UserId private val userId: String,
@DeviceId private val deviceId: String?,
private val sendToDeviceTask: SendToDeviceTask,
private val messageEncrypter: MessageEncrypter,
private val warnOnUnknownDevicesRepository: WarnOnUnknownDeviceRepository,
private val taskExecutor: TaskExecutor,
private val coroutineDispatchers: MatrixCoroutineDispatchers,
private val cryptoCoroutineScope: CoroutineScope) {
fun create(roomId: String): MXMegolmEncryption {
return MXMegolmEncryption(
roomId,
olmDevice,
defaultKeysBackupService,
cryptoStore,
deviceListManager,
ensureOlmSessionsForDevicesAction,
credentials,
sendToDeviceTask,
messageEncrypter,
warnOnUnknownDevicesRepository,
taskExecutor,
coroutineDispatchers,
cryptoCoroutineScope
roomId = roomId,
olmDevice = olmDevice,
defaultKeysBackupService = defaultKeysBackupService,
cryptoStore = cryptoStore,
deviceListManager = deviceListManager,
ensureOlmSessionsForDevicesAction = ensureOlmSessionsForDevicesAction,
userId = userId,
deviceId = deviceId!!,
sendToDeviceTask = sendToDeviceTask,
messageEncrypter = messageEncrypter,
warnOnUnknownDevicesRepository = warnOnUnknownDevicesRepository,
coroutineDispatchers = coroutineDispatchers,
cryptoCoroutineScope = cryptoCoroutineScope
)
}
}

View file

@ -76,13 +76,4 @@ internal class MXOlmEncryption(
deviceListManager.downloadKeys(users, false)
ensureOlmSessionsForUsersAction.handle(users)
}
override fun discardSessionKey() {
// No need for olm
}
override suspend fun reshareKey(sessionId: String, userId: String, deviceId: String, senderKey: String): Boolean {
// No need for olm
return false
}
}

View file

@ -45,7 +45,7 @@ internal class DefaultEncryptEventTask @Inject constructor(
// don't want to wait for any query
// if (!params.crypto.isRoomEncrypted(params.roomId)) return params.event
val localEvent = params.event
if (localEvent.eventId == null) {
if (localEvent.eventId == null || localEvent.type == null) {
throw IllegalArgumentException()
}

Some files were not shown because too many files have changed in this diff Show more