Merge tag 'v1.1.8' into sc

Change-Id: Ied7cd01e47a76e9d8f546ae1c2d6f10b083c4480

Conflicts:
	matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt
	vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt
	vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageTextItem.kt
	vector/src/main/java/im/vector/app/features/roomdirectory/createroom/CreateRoomController.kt
	vector/src/main/java/im/vector/app/features/spaces/SpaceSummaryController.kt
This commit is contained in:
SpiritCroc 2021-05-26 09:04:00 +02:00
commit 6c06462ec9
431 changed files with 12675 additions and 3441 deletions

View file

@ -5,6 +5,6 @@
- [ ] Changes has been tested on an Android device or Android emulator with API 21
- [ ] UI change has been tested on both light and dark themes
- [ ] Pull request is based on the develop branch
- [ ] Pull request updates [CHANGES.md](https://github.com/vector-im/element-android/blob/develop/CHANGES.md)
- [ ] Pull request includes a new file under ./newsfragment. See https://github.com/vector-im/element-android/blob/develop/CONTRIBUTING.md#changelog
- [ ] Pull request includes screenshots or videos if containing UI changes
- [ ] Pull request includes a [sign off](https://github.com/matrix-org/synapse/blob/master/CONTRIBUTING.md#sign-off)

59
.github/workflows/build.yml vendored Normal file
View file

@ -0,0 +1,59 @@
name: APK Build
on:
pull_request: { }
push:
branches: [ main, develop ]
jobs:
debug:
name: Build debug APKs (${{ matrix.target }})
runs-on: ubuntu-latest
if: github.ref != 'refs/heads/main'
strategy:
fail-fast: false
matrix:
target: [ Gplay, Fdroid ]
steps:
- uses: actions/checkout@v2
- uses: actions/cache@v2
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-
- name: Assemble ${{ matrix.target }} debug apk
run: ./gradlew assemble${{ matrix.target }}Debug --stacktrace
- name: Upload ${{ matrix.target }} debug APKs
uses: actions/upload-artifact@v2
with:
name: vector-${{ matrix.target }}-debug
path: |
vector/build/outputs/apk/*/debug/*.apk
release:
name: Build unsigned GPlay APKs
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v2
- uses: actions/cache@v2
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-
- name: Assemble GPlay unsigned apk
run: ./gradlew clean assembleGplayRelease --stacktrace
- name: Upload Gplay unsigned APKs
uses: actions/upload-artifact@v2
with:
name: vector-gplay-release-unsigned
path: |
vector/build/outputs/apk/*/release/*.apk
# TODO: add exodus checks

49
.github/workflows/integration.yml vendored Normal file
View file

@ -0,0 +1,49 @@
name: Integration Test
on:
pull_request: { }
push:
branches: [ main, develop ]
jobs:
integration-tests:
name: Integration Tests (Synapse)
runs-on: ubuntu-latest
strategy:
matrix:
api-level: [21, 30]
steps:
- uses: actions/checkout@v2
- name: Set up Python 3.8
uses: actions/setup-python@v2
with:
python-version: 3.8
- name: Cache pip
uses: actions/cache@v2
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip
restore-keys: |
${{ runner.os }}-pip-
${{ runner.os }}-
- uses: actions/cache@v2
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-
- name: Start synapse server
run: |
python3 -m venv .synapse
source .synapse/bin/activate
pip install synapse matrix-synapse
curl -sL https://raw.githubusercontent.com/matrix-org/synapse/develop/demo/start.sh --no-rate-limit \
| sed s/127.0.0.1/0.0.0.0/g | bash
- name: Run integration tests on API ${{ matrix.api-level }}
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: ${{ matrix.api-level }}
# script: ./gradlew -PallWarningsAsErrors=false vector:connectedAndroidTest matrix-sdk-android:connectedAndroidTest
script: ./gradlew -PallWarningsAsErrors=false connectedCheck

74
.github/workflows/quality.yml vendored Normal file
View file

@ -0,0 +1,74 @@
name: Code Quality Checks
on:
pull_request: { }
push:
branches: [ main, develop ]
jobs:
check:
name: Project Check Suite
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Run code quality check suite
run: ./tools/check/check_code_quality.sh
klint:
name: Kotlin Linter
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Run klint
run: |
curl -sSLO https://github.com/pinterest/ktlint/releases/download/0.36.0/ktlint && chmod a+x ktlint
./ktlint --android --experimental -v
android-lint:
name: Android Linter
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/cache@v2
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-
- name: Lint analysis of the SDK
run: ./gradlew clean :matrix-sdk-android:lintRelease --stacktrace
- name: Upload reports
uses: actions/upload-artifact@v2
with:
name: linting-report-android-sdk
path: matrix-sdk-android/build/reports/*.*
apk-lint:
name: Lint APK (${{ matrix.target }})
runs-on: ubuntu-latest
if: github.ref != 'refs/heads/main'
strategy:
fail-fast: false
matrix:
target: [ Gplay, Fdroid ]
steps:
- uses: actions/checkout@v2
- uses: actions/cache@v2
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-
- name: Lint ${{ matrix.target }} release
run: ./gradlew clean lint${{ matrix.target }}Release --stacktrace
- name: Upload ${{ matrix.target }} linting report
uses: actions/upload-artifact@v2
if: always()
with:
name: release-debug-linting-report-${{ matrix.target }}
path: |
vector/build/reports/*.*

23
.github/workflows/tests.yml vendored Normal file
View file

@ -0,0 +1,23 @@
name: Test
on:
pull_request: {}
push:
branches: [main, develop]
jobs:
unit-tests:
name: Run Unit Tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/cache@v2
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-
- name: Run unit tests
run: ./gradlew clean test --stacktrace -PallWarningsAsErrors=false

View file

@ -1,4 +1,4 @@
# FTR: Configuration on https://travis-ci.org/vector-im/riotX-android/settings
# FTR: Configuration on https://travis-ci.org/github/vector-im/element-android/settings
#
# - Build only if .travis.yml is present -> On
# - Limit concurrent jobs -> Off
@ -8,53 +8,11 @@
# - Auto cancel branch builds -> On
# - Auto cancel pull request builds -> On
language: android
jdk: oraclejdk8
sudo: false
notifications:
email: false
android:
components:
# Uncomment the lines below if you want to
# use the latest revision of Android SDK Tools
- tools
- platform-tools
# The BuildTools version used by your project
- build-tools-29.0.3
# The SDK version used to compile your project
- android-29
before_cache:
- rm -f $HOME/.gradle/caches/modules-2/modules-2.lock
- rm -fr $HOME/.gradle/caches/*/plugin-resolution/
cache:
directories:
- $HOME/.gradle/caches/
- $HOME/.gradle/wrapper/
- $HOME/.android/build-cache
# Build with the development SDK
before_script:
# Not necessary for the moment
# - /bin/sh ./set_debug_env.sh
# Just build the project for now
# Just run a simple script here
script:
# Build app (assembleGplayRelease assembleFdroidRelease)
# Build Android test (assembleAndroidTest) (disabled for now)
# Code quality (lintGplayRelease lintFdroidRelease)
# Split into two steps because if a task contain Fdroid, PlayService will be disabled
# Done by Buildkite now: - ./gradlew clean assembleGplayRelease lintGplayRelease --stacktrace
# Done by Buildkite now: - ./gradlew clean assembleFdroidRelease lintFdroidRelease --stacktrace
# Run unitary test (Disable for now, see https://travis-ci.org/vector-im/riot-android/builds/502504370)
# - ./gradlew testGplayReleaseUnitTest --stacktrace
# Other code quality check
# Done by Buildkite now: - ./tools/check/check_code_quality.sh
- ./tools/travis/check_pr.sh
# Check that indonesians file are identical. Due to Android issue, the resource folder must be value-in/, and Weblate export data into value-id/.
# Done by Buildkite now: - diff ./vector/src/main/res/values-id/strings.xml ./vector/src/main/res/values-in/strings.xml

View file

@ -1,3 +1,30 @@
Changes in Element 1.1.8 (2021-05-25)
===================================================
Improvements 🙌:
- Support Jitsi authentication (#3379)
Bugfix 🐛:
- Space Invite by link not always displayed for public space (#3345)
- Wrong copy in share space bottom sheet (#3346)
- Fix a problem with database migration on nightly builds (#3335)
- Implement a workaround to render <del> and <u> in the timeline (#1817)
- Make sure the SDK can retrieve the secret storage if the system is upgraded (#3304)
- Spaces | Explore room list: the RoomId is displayed instead of name (#3371)
- Spaces | Personal spaces add DM - Web Parity (#3271)
- Spaces | Improve 'Leave Space' UX/UI (#3359)
- Don't create private spaces with encryption enabled (#3363)
- #+ button on lower right when looking at an empty space goes to an empty 'Explore rooms' (#3327)
Build 🧱:
- Compile with Kotlin 1.5.10.
- Upgrade some dependencies: gradle wrapper, third party lib, etc.
- Sign APK with build tools 30.0.3
Other changes:
- Add documentation on LoginWizard and RegistrationWizard (#3303)
- Setup towncrier tool (#3293)
Changes in Element 1.1.7 (2021-05-12)
===================================================
@ -67,7 +94,7 @@ Changes in Element 1.1.4 (2021-04-09)
Improvements 🙌:
- Split network request `/keys/query` into smaller requests (250 users max) (#2925)
- Crypto improvement | Bulk send NO_OLM withheld code
- Crypto improvement | Bulk send NO_OLM withheld code
- Display the room shield in all room setting screens
- Improve message with Emoji only detection (#3017)
- Picture preview when replying. Also add the image preview in the message detail bottomsheet (#2916)
@ -626,7 +653,7 @@ Improvements 🙌:
- Sending events is now retried only 3 times, so we avoid blocking the sending queue too long.
- Display warning when fail to send events in room list
- Improve UI of edit role action in member profile
- Moderation | New screen to display list of banned users in room settings, with unban action
- Moderation | New screen to display list of banned users in room settings, with unban action
Bugfix 🐛:
- Fix theme issue on Room directory screen (#1613)
@ -1346,36 +1373,3 @@ Changes in RiotX 0.1.0 (2019-07-11)
First release!
Mode details here: https://medium.com/@RiotChat/introducing-the-riotx-beta-for-android-b17952e8f771
=======================================================
+ TEMPLATE WHEN PREPARING A NEW RELEASE +
=======================================================
Changes in Element 1.1.X (2021-XX-XX)
===================================================
Features ✨:
-
Improvements 🙌:
-
Bugfix 🐛:
-
Translations 🗣:
-
SDK API changes ⚠️:
-
Build 🧱:
-
Test:
-
Other changes:
-

View file

@ -51,9 +51,21 @@ If an issue does not exist yet, it may be relevant to open a new issue and let u
This project is full Kotlin. Please do not write Java classes.
### CHANGES.md
### Changelog
Please add a line to the top of the file `CHANGES.md` describing your change.
Please create at least one file under ./newsfragment containing details about your change. Towncrier will be used when preparing the release.
Towncrier says to use the PR number for the filename, but the issue number is also fine.
Supported filename extensions are:
- ``.feature``: Signifying a new feature in Element Android or in the Matrix SDK.
- ``.bugfix``: Signifying a bug fix.
- ``.doc``: Signifying a documentation improvement.
- ``.removal``: Signifying a deprecation or removal of public API. Can be used to notifying about API change in the Matrix SDK
- ``.misc``: A ticket has been closed, but it is not of interest to users. Note that in this case, the content of the file will not be output, but just the issue/PR number.
See https://github.com/twisted/towncrier#news-fragments if you need more details.
### Code quality

View file

@ -47,14 +47,14 @@ android {
}
dependencies {
implementation 'com.github.chrisbanes:PhotoView:2.1.4'
implementation 'com.github.chrisbanes:PhotoView: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.appcompat:appcompat:1.3.0'
implementation "androidx.recyclerview:recyclerview:1.2.0"
implementation 'com.google.android.material:material:1.3.0'

View file

@ -33,6 +33,7 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.core.view.GestureDetectorCompat
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.isVisible
import androidx.core.view.updatePadding
import androidx.transition.TransitionManager
@ -124,9 +125,11 @@ abstract class AttachmentViewerActivity : AppCompatActivity(), AttachmentEventLi
scaleDetector = createScaleGestureDetector()
ViewCompat.setOnApplyWindowInsetsListener(views.rootContainer) { _, insets ->
overlayView?.updatePadding(top = insets.systemWindowInsetTop, bottom = insets.systemWindowInsetBottom)
topInset = insets.systemWindowInsetTop
bottomInset = insets.systemWindowInsetBottom
val systemBarsInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars())
overlayView?.updatePadding(top = systemBarsInsets.top, bottom = systemBarsInsets.bottom)
topInset = systemBarsInsets.top
bottomInset = systemBarsInsets.bottom
insets
}
}

View file

@ -2,8 +2,8 @@
buildscript {
// Ref: https://kotlinlang.org/releases.html
ext.kotlin_version = '1.4.32'
ext.kotlin_coroutines_version = "1.4.2"
ext.kotlin_version = '1.5.10'
ext.kotlin_coroutines_version = "1.5.0"
repositories {
google()
jcenter()
@ -12,10 +12,10 @@ buildscript {
}
}
dependencies {
classpath 'com.android.tools.build:gradle:4.1.3'
classpath 'com.google.gms:google-services:4.3.5'
classpath 'com.android.tools.build:gradle:4.2.1'
classpath 'com.google.gms:google-services:4.3.8'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.1.1'
classpath 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.2.0'
classpath 'com.google.android.gms:oss-licenses-plugin:0.10.4'
classpath "com.likethesalad.android:string-reference:1.2.2"

View file

@ -1,39 +1,39 @@
Element ist mehr als ein sicherer Messenger. Es ist ein produktives Kolaborationsapp für das Team und eignet sich ideal für den Gruppenchat beim Arbeiten von zuhause aus. Mit eingebauter Ende-zu-Ende-Verschlüsselung ermöglicht Element umfangreiche und sichere Videokonferenzen, das Teilen von Dokumenten/Dateien und Videoanrufe.
Element ist einerseits ein sicherer Messenger, andererseits ideal geeignet für die produktive Zusammenarbeit mit dem Team im Homeoffice. Mit eingebauter Ende-zu-Ende-Verschlüsselung ermöglicht Element umfangreiche und sichere Videokonferenzen, das Teilen von Dateien sowie Sprachanrufe.
<b>Element enthält folgende Funktionen:</b>
- Fortgeschrittene Werkzeuge für die Online-Kommunikation
- Vollverschlüsselte Nachrichten um eine sichere Kommunikation innerhalb und außerhalb des Unternehmens zu ermöglichen
- Dezentralisierte Chats basierend auf das quelloffene Matrix-Framework
- Sichere und kontrollierte Dateienfreigabe durch verschlüsselte Daten beim verwalten von Projekten
- Videochats über VoIP und Bildschirmübertragung
- Einfache Einbindungen mit Ihren favorisierten Online-Kolaborationswerkzeugen, Projektverwaltungswerkzeugen, VoIP-Diensten und andere Kommunikationsapps für Ihren Team
<b>Element bietet folgende Funktionen:</b>
- Fortschrittliche Werkzeuge für die Online-Kommunikation
- Vollständig verschlüsselte Nachrichten, um eine sichere Kommunikation innerhalb und außerhalb von Unternehmen zu ermöglichen
- Dezentralisierte Chats, basierend auf dem quelloffenen Matrix-Framework
- Sichere und kontrollierte Dateifreigabe durch verschlüsselte Daten beim Verwalten von Projekten
- Videochats mit VoIP und Bildschirmübertragung
- Einfache Einbindung in Ihre bevorzugten Online-Kollaborations- und Projektverwaltungswerkzeuge, VoIP-Dienste und andere Kommunikationsapps für Ihr Team
Element unterscheidet sich deutlich von anderen Kommunikations- und Kollaborationsapps. Es läuft auf Matrix, ein offenes Netzwerk für eine sichere und dezentralisierte Kommunikation. Es erlaubt den Nutzern ihre eigenen Matrix-Dienste zu betreiben und gibt ihnen damit die vollständige Kontrolle und Besitz über ihre eigenen Daten und Nachrichten.
Element unterscheidet sich grundlegend von anderen Kommunikations- und Kollaborationsapps. Es läuft auf Matrix, einem offenen Netzwerk für sichere und dezentralisierte Kommunikation. Es erlaubt Nutzern ihre eigene Infrastruktur zu betreiben und gibt ihnen damit vollständige Kontrolle und Besitz über ihre eigenen Daten und Nachrichten.
<b>Privatsphäre/Datenschutz und verschlüsselte Kommunikation</b>
Element schützt Ihnen vor unerwünschte Werbung, das Datenschürfen und geschlossene unentkommbare Dienste. Auch schützt es all Ihre Daten, Video und Sprachkommunikation unter vier Augen durch Ende-zu-Ende-Verschlüsselung und das Quersignieren von Geräten zur Verifizierung.
Element schützt Sie vor unerwünschter Werbung, dem Datenschürfen und abgeschlossenen Plattformen. Auch schützt es all Ihre Daten, Ihre Video- und Sprachkommunikation unter vier Augen, durch Ende-zu-Ende-Verschlüsselung und durch das Quersignieren von Geräten zur Verifizierung.
Element gibt Ihnen die Kontrolle über Ihre Privatsphäre, während es Ihnen ermöglicht mit jeden auf dem Matrix-Netzwerk oder andere geschäftliche Kollaborationswerkzeuge durch das Einbinden von Apps wie Slack sicher zu kommunizieren.
Element gibt Ihnen die Kontrolle über Ihre Privatsphäre und ermöglicht es Ihnen zugleich, mit jedem im Matrix-Netzwerk sicher zu kommunizieren - oder auch auf anderen geschäftlichen Kollaborationswerkzeugen, zum Beispiel durch das Einbinden von Apps wie Slack.
<b>Element kann man selber betreiben</b>
Um mehr Kontrolle über Ihre sensiblen Daten und Konversationen zu ermöglichen, kann man Element selbst betreiben oder Sie wählen irgendeinen Matrix-basierten Dienst - der standard für quelloffene, dezentralisierte Kommunikation. Element gibt Ihnen Privatsphäre, Sicherheitskonformität und die Flexibilität zum Integrieren.
Um mehr Kontrolle über Ihre sensiblen Daten und Konversationen zu ermöglichen, kann man Element selbst betreiben, oder Sie wählen irgendeinen Matrix-basierten Dienst - der Standard für quelloffene, dezentralisierte Kommunikation. Element gibt Ihnen Privatsphäre, Sicherheitskonformität und Flexibilität für Integrationen.
<b>Besitzen Sie Ihre Daten</b>
Sie entscheiden wo Sie Ihre Daten und Nachrichten aufbewahren, ohne das Risiko des Datenschürfens oder des Zugriffes Dritter.
Sie entscheiden, wo Sie Ihre Daten und Nachrichten aufbewahren - ohne das Datenschürfen oder den Zugriff Dritter zu riskieren.
Element gibt Ihnen die Kontrolle durch verschiedene Wege:
1. Kostenlos auf dem öffentlichen matrix.org Server registrieren, der von den Matrix-Entwicklern gehostet wird, oder wähle aus Tausenden von öffentlichen Servern, die von Freiwilligen gehostet werden
2. Einen Konto auf einem eigenen Server in der eigenen IT-Infrastruktur betreiben
3. Einen Konto auf einem benutzerdefinierten Server erstellen, zum Beispiel durch ein Abonnement bei Element Matrix Services (kurz EMS)
Element gibt Ihnen auf verschiedene Arten die Kontrolle:
1. Kostenlos auf dem öffentlichen matrix.org-Server registrieren, der von den Matrix-Entwicklern gehostet wird, oder wählen Sie aus Tausenden von öffentlichen Servern, die von Freiwilligen betrieben werden
2. Ein Konto auf einem eigenen Server in der eigenen IT-Infrastruktur betreiben
3. Einen Konto auf einem maßgeschneiderten Server erstellen, zum Beispiel durch ein Abonnement der Element Matrix Services (kurz EMS)
<b>Offene Kommunikation und Zusammenarbeit</b>
Sie können mit jeden auf dem Matrix-Netzwerk chatten, egal ob sie Element, eine Matrix-App oder sogar eine andere Kommunikationsapp nutzen.
Sie können mit jedem im Matrix-Netzwerk chatten, egal ob ihr Kontakt Element, eine andere Matrix-App oder sogar eine völlig andere Anwendung nutzt.
<b>Super sicher</b>
Reale Ende-zu-Ende-Verschlüsselung (nur die Personen in der Konversation können die Nachricht entschüsseln) und Quersignierung von Geräten zur Verifizierung.
Echte Ende-zu-Ende-Verschlüsselung (nur die Personen in der Unterhaltung können die Nachrichten entschüsseln), sowie die Quersignierung von Geräten zur Verifizierung.
<b>Vollständige Kommunikation und Integration</b>
Kurznachrichten, Sprach- und Videoanrufe, kontrollierte Dateifreigaben, Bildschirmübertragungen und eine ganze Reihe an Integrationen, Bots and Widgets. Schaffe Räume, Gemeinschaften, bleibe auf dem Laufenden und erledige Sachen.
Kurznachrichten, Sprach- und Videoanrufe, Dateifreigaben, Bildschirmübertragungen und eine ganze Reihe an Integrationen, Bots and Widgets. Schaffen Sie Räume, Communities, bleiben Sie auf dem Laufenden und erledigen Sie Sachen.
<b>Das Stehengelassene später wieder aufgreifen</b>
Bleibe auf dem Laufenden, egal wo Sie sind, mit vollständig synchronisierter Nachrichtenverlauf quer über all Ihrer Geräte und im Netz auf https://app.element.io
<b>Da Weitermachen, wo Sie aufgehört haben</b>
Bleiben Sie in Kontakt, egal wo Sie sind, mit vollständig synchronisiertem Nachrichtenverlauf quer über all Ihre Geräte und im Netz auf https://app.element.io

View file

@ -0,0 +1,2 @@
Main changes in this version: improvement for Spaces.
Full changelog: https://github.com/vector-im/element-android/releases/tag/v1.1.8

View file

@ -1,30 +1,39 @@
Element on uudenlainen viestinsovellus, joka:
Element on turvallinen pikaviesti- ja tiimityösovellus joka sopii mainiosti ryhmäkeskusteluihin etätöissä. Sovellus käyttää päästä päähän -salausta ja tarjoaa videoneuvottelun, tiedostojen jakamisen ja äänipuhelut.
1. Antaa sinun päättää yksityisyydestäsi
2. Antaa sinun kommunikoida kenen tahansa kanssa Matrix-verkossa ja jopa sen ulkopuolella siltaamalla sovelluksiin, kuten Slack
3. Suojaa sinua mainonnalta, tietojen keräämiseltä ja suljetuilta alustoilta
4. Suojaa sinut päästä päähän -salauksella sekä ristiin varmentamisella muiden todentamiseksi
<b>Elementin ominaisuuksia:</b>
- Edistyneet viestintätyökalut
- Turvallisempaa yritysviestintää täysin salatuilla viesteillä, myös etätyöntekijöille
- Hajautettu keskustelu, joka perustuu avoimen lähdekoodin Matrix-teknologiaan
- Turvallinen tiedostojen jakaminen datan salauksella
- Videoneuvottelut VoIP:lla ja näytön jakamisella
- Helppo integraatio tiimityövälineisiin, projektinhallintatyökaluihin, VoIP-palveluihin ja muihin ryhmäviestimiin
Element eroaa täysin muista viestintäsovelluksista, koska se on hajautettu ja avointa lähdekoodia.
Element on täysin erilainen kuin muut viestintä- ja yhteistyösovellukset. Se toimii Matrixilla, avoimella turvallisen ja hajautetun viestinnän verkostolla. Palvelimen ylläpitäminen itse on mahdollista, jos haluaa tietonsa ja viestinsä täysin hallintaansa.
Element antaa sinun isännöidä itse - tai valita palveluntarjoajan - jotta sinulla on yksityisyys ja voit hallita tietojasi sekä keskustelujasi. Se antaa sinulle pääsyn avoimeen verkkoon, joten et jää juttelemaan vain toisten Elementin käyttäjien kanssa. Se on myös hyvin turvallinen.
<b>Yksityisyys ja salattu viestintä</b>
Element protects you from unwanted ads, data mining and walled gardens. It also secures all your data, one-to-one video and voice communication through end-to-end encryption and cross-signed device verification.
Element pystyy tekemään kaiken tämän, koska se toimii Matrixilla - avoimella, hajautetun viestinnän standardilla.
Element gives you control over your privacy while allowing you to communicate securely with anyone on the Matrix network, or other business collaboration tools by integrating with apps such as Slack.
Element antaa sinulle päätösvallan antamalla sinun valita, kuka isännöi keskustelujasi. Element-sovelluksessa voit valita isännän eri tavoin:
<b>Voit ylläpitää omaa palvelinta</b>
To allow more control of your sensitive data and conversations, Element can be self-hosted or you can choose any Matrix-based host - the standard for open source, decentralized communication. Element gives you privacy, security compliance and integration flexibility.
1. Hanki ilmainen tili Matrix-kehittäjien ylläpitämällä matrix.org-palvelimella tai valitse tuhansista vapaaehtoisten ylläpitämistä julkisista palvelimista.
2. Isännöi tiliäsi itse ylläpitämällä palvelinta omalla laitteellasi
3. Luo tili sinua varten tehdyllä palvelimella tilaamalla Element Matrix Services -palvelu
<b>Hallitse omia tietojasi</b>
You decide where to keep your data and messages. Without the risk of data mining or access from third parties.
<b>Miksi valita Element?</b>
Element puts you in control in different ways:
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 IT infrastructure
3. Sign up for an account on a custom server by simply subscribing to the Element Matrix Services hosting platform
<b>OMAT TIEDOT</b>: Sinä päätät, missä tietosi ja viestisi säilytetään. Sinä määräät, ei jokin jättiyhtiö, joka tutkii tietojasi tai antaa niitä kolmansille osapuolille.
<b>Avointa viestintää ja yhteistyötä</b>
Voit keskustella kenen tahansa kanssa Matrix-verkossa, riippumatta siitä käyttääkö tämä Elementiä, jotain muuta Matrix-sovellusta tai jopa muuta viestintäsovellusta.
<b>AVOINTA VIESTINTÄÄ JA YHTEISTYÖTÄ</b>: Voit keskustella kaikkien muiden Matrix-verkon käyttäjien kanssa, riippumatta siitä käyttävätkö he Elementiä tai muuta Matrix-sovellusta, ja vaikka he käyttäisivät eri viestijärjestelmiä, kuten Slack, IRC tai XMPP.
<b>Todella turvallinen</b>
Real end-to-end encryption (only those in the conversation can decrypt messages), and cross-signed device verification.
<b>ERITTÄIN TURVALLINEN</b>: Vahva päästä päähän -salaus (vain keskustelussa olevat voivat purkaa viestien salauksen), ja ristiin varmentaminen keskustelun osallistujien laitteiden tarkistamiseksi.
<b>Kattavaa viestintää ja integraatioita</b>
Viestit, ääni- ja videopuhelut, tiedostojen jakaminen, näytön jakaminen ja koko joukko integraatioita, botteja ja sovelmia. Luo huoneita ja yhteisöjä, pidä yhteyttä ja hoida asiasi.
<b>KATTAVAA VIESTINTÄÄ</b>: Viestit, ääni- ja videopuhelut, tiedostojen jakaminen, näytön jakaminen ja koko joukko integraatioita, botteja ja sovelmia. Rakenna huoneita ja yhteisöjä, pidä yhteyttä ja hoida asiasi.
<b>MISSÄ TAHANSA OLETKIN</b>: Pidä yhteyttä missä tahansa, täysin synkronoidun viestihistorian kautta kaikilla laitteillasi ja verkossa osoitteessa https://app.element.io.
<b>Jatka siitä mihin jäit</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

View file

@ -1 +1 @@
Turvallista, hajautettua keskustelua ja VoIP-puheluita. Pidä tietosi turvassa.
Ryhmäviestin - salattua viestintää, ryhmäkeskusteluja ja videopuheluita

View file

@ -1 +1 @@
Element (aiemmin Riot.im)
Element - Turvallinen viestin

View file

@ -1 +1 @@
Messagerie de groupes - messages chiffrés, groupés et appels vidéos
Messagerie de groupe - messages chiffrés, groupes et appels vidéos

View file

@ -1,2 +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
Principais alterações nesta versão: Correções de bugs!
Changelog completo: https://github.com/vector-im/element-android/releases/tag/v1.0.17

View file

@ -1,2 +1,2 @@
Principais mudanças nesta versão: Melhoria de VoIP (chamadas de áudio e vídeo em conversas) e correção de erros!
Registro de alterações completo: https://github.com/vector-im/element-android/releases/tag/v1.1.0
Principais alterações nesta versão: melhoramento de VoIP (chamadas de áudio e vídeo em DM) e correções de bugs!
Changelog completo: https://github.com/vector-im/element-android/releases/tag/v1.1.0

View file

@ -1,2 +1,2 @@
Principais mudanças nesta versão: melhoria de desempenho e correção de erros!
Registro de alterações completo: https://github.com/vector-im/element-android/releases/tag/v1.1.1
Principais alterações nesta versão: melhoramento de performance e correções de bugs!
Changelog completo: https://github.com/vector-im/element-android/releases/tag/v1.1.1

View file

@ -0,0 +1,2 @@
Principais alterações nesta versão: melhoramento de performance e correções de bugs!
Changelog completo: https://github.com/vector-im/element-android/releases/tag/v1.1.2

View file

@ -0,0 +1,2 @@
Principais alterações nesta versão: melhoramento de performance e correções de bugs!
Changelog completo: https://github.com/vector-im/element-android/releases/tag/v1.1.3

View file

@ -0,0 +1,2 @@
Principais alterações nesta versão: melhora de performance e correções de bugs!
Changelog completo: https://github.com/vector-im/element-android/releases/tag/v1.1.4

View file

@ -0,0 +1,2 @@
Principais alterações nesta versão: correções quentes para 1.1.4
Changelog completo: https://github.com/vector-im/element-android/releases/tag/v1.1.5

View file

@ -0,0 +1,2 @@
Principais alterações nesta versão: correções quentes para 1.1.5
Changelog completo: https://github.com/vector-im/element-android/releases/tag/v1.1.6

View file

@ -1,30 +1,39 @@
Element é um novo tipo de aplicativo de mensagens e colaboração que:
Element é tanto um mensageiro seguro como um app de colaboração de time de produtividade que é ideal para chats de grupo enquanto se trabalha remotamente. Este app de chat usa encriptação ponta-a-ponta para prover conferência de vídeo, compartilhamento de arquivo e chamadas de voz poderasos.
1. Coloca você no controle de sua privacidade;
2. Permite que você se comunique com qualquer pessoa na rede Matrix e, através de integrações, outros aplicativos como o Slack;
3. Protege você de anúncios, mineração de dados e de ecossistemas fechados;
4. Faz uso da criptografia de ponta a ponta, com autoverificação para confirmar outras pessoas.
<b>As funções de Element incluem:</b>
- Ferramentas de comunicação online avançadas
- Mensagens completamente encriptadas para permitir comunicação de corporação mais segura, até para pessoas trabalhando remotamente
- Chat descentralizado baseado na framework open source Matrix
- Compartilhamento de arquivo seguramente com dados encriptados enquanto se gerencia projetos
- Chats de vídeo com Voz sobre IP e compartilhamento de tela
- Integração fácil com suas ferramentas de colaboração online favoritas, ferramentas de gerenciamento de projetos, serviços de VoIP e outros apps de mensageria de time
Element é completamente diferente de outros aplicativos de mensagem e colaboração porque é descentralizado e código aberto.
Element é completamente diferente de outros apps de mensageria e colaboração. Ele opera em Matrix, uma rede aberta para mensageria segura e comunicação descentralizada. Ele permite auto-hospedagem para dar a usuárias(os) máxima propriedade e controle de seus dados e suas mensagens.
É possível hospedar um servidor ou escolher um servidor hospedeiro, para que você tenha privacidade, controle e que você seja o dono de seus dados e conversas. Com o Element, você possui acesso a uma rede aberta; então você não está preso falando somente com outros usuários no Element. O Element também é muito seguro.
<b>Privacidade e mensageria encriptada</b>
Element protege você de ads não-desejados, minagem de dados e jardins murados. Ele também assegura todos os seus dados, vídeo um-a-um e comunicação de voz através de encriptação ponta-a-ponta e verificação de dispositivo assinada cruzado.
Element é capaz de fazer tudo isso porque ele opera no Matrix, o padrão para comunicação aberta e descentralizada.
Element dá a você controle sobre sua privacidade enquanto permite a você se comunicar seguramente com qualquer pessoa na rede Matrix, ou outras ferramentas de colaboração ao se integrar com apps tais como Slack.
Element coloca você no controle ao permitir quem hospeda suas conversas. Neste aplicativo, você pode escolher hospedar de maneiras diferentes:
<b>Element pode ser auto-hospedado</b>
Para permitir mais controle de seus dados e conversas sensíveis, Element pode ser auto-hospedado ou você pode escolher qualquer host baseado em Matrix - o standard para comunicação open source e descentralizada. Element dá a você privacidade, conformidade de segurança e flexibilidade de integração.
1. Crie uma conta gratuita no servidor público matrix.org;
2. Hospede sua conta no seu servidor;
3. Entre em uma conta em um servidor customizado ao simplesmente se inscrever na plataforma de hospedagem Serviços Matrix Element.
<b>Tenha posse de seus dados</b>
Você decidade onde mannter seus dados e mensagens. Sem o risco de minagem de dados ou acesso de terceiros.
<b>Por que escolher o Element?</b>
Element põe você em controle de diferentes maneiras:
1. Tenha uma conta grátis no servidor público matrix.org hospedado pelos desenvolvedores Matrix, ou escolha de milhares de servidores públicos hospedados por pessoas se voluntariando
2. Auto-hospede sua conta ao rodar um servidor em sua própria infraestrutura de TI
3. Registre-se para uma conta num servidor personalizado ao simplesmente assinar a plataforma de hospedagem Element Matrix Services
<b>SEJA O DONO DE SEUS DADOS</b>: você decide onde manter seus dados e mensagens. Você é o dono deles e também os controla, não alguma mega corporação que minera os seus dados ou compartilha eles com terceiros.
<b>Mensageria e colaboração abertos</b>
Você pode fazer chat com qualquer pessoa na rede Matrix, caso ela esteja usando Element, um outro app de Matrix ou mesmo se ela estiver usando um app de mensagem diferente.
<b>COLABORAÇÃO E MENSAGENS ABERTAS</b>: você pode falar com qualquer outra pessoa na rede Matrix, não importa se ela está usando o Element ou algum outro aplicativo Matrix, ou até mesmo se ela está utilizando um sistema de mensagens diferente como o Slack, IRC ou XMPP.
<b>Super seguro</b>
Encriptação ponta-a-ponta real (somente aquelas/es na conversa podem decriptar mensagens), e verificação de dispositivo assinada cuzado.
<b>SUPER-SEGURO</b>: criptografia de ponta a ponta verdadeira (apenas aqueles na conversa podem descriptografar mensagens), e assinatura cruzada para verificar os dispositivos de participantes de conversas.
<b>Comunicação e integração completas</b>
Messageria, chamas de voz e vídeo, compartilhamento de arquivo, compartilhamento de tela e um monte de integrações, bots e widgets. Construa salas, comunidades, fique em contato e tenha as coisas feitas.
<b>COMUNICAÇÃO COMPLETA</b>: mensagens, chamadas de voz, chamadas de vídeo, compartilhamento de arquivos, compartilhamento de tela e um monte de integrações, robôs e widgets. Construa salas, comunidades, mantenha contato e faça seus projetos.
<b>NÃO IMPORTA ONDE VOCÊ ESTEJA</b>: mantenha contato não importa onde você esteja com o histórico sincronizado de mensagens em todos os seus dispositivos e no navegador em https://app.element.io.
<b>Continue de onde você parou</b>
Fique em contato onde quer que você esteja com histórico de mensagem completamente sincronizado por todos os seus dispositivos e na web em https://app.element.io

View file

@ -1 +1 @@
Conversas e chamadas seguras e descentralizadas. Mantenha seus dados protegidos.
Mensageiro de grupo - mensagens encriptadas, chat de grupo e chamadas de vídeo

View file

@ -1 +1 @@
Element (o novo Riot.im)
Element - Mensageiro Seguro

View file

@ -0,0 +1 @@
Mesagerie de grup - mesaje criptate, comunicare de grup și apeluri video

View file

@ -0,0 +1 @@
Element - Mesagerie securizată

View file

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

View file

@ -0,0 +1,2 @@
Основні зміни в цій версії: виправлення для 1.1.4
Повний журнал змін: https://github.com/vector-im/element-android/releases/tag/v1.1.5

View file

@ -0,0 +1,2 @@
Основні зміни в цій версії: виправлення для 1.1.5
Повний журнал змін: https://github.com/vector-im/element-android/releases/tag/v1.1.6

View file

@ -1 +1 @@
Захищене децентралізоване листування та дзвінки. Тримайте ваші дані в безпеці.
Груповий месенджер — зашифровані повідомлення, групові бесіди та відеовиклики

View file

@ -1 +1 @@
Element (раніше Riot.im)
Element — Безпечний месенджер

View file

@ -1 +1 @@
Ứng dụng chat và gọi phân tán bảo mật. Bảo vệ dữ liệu của bạn khỏi bên thứ ba.
Nhắn tin nhóm - tin nhắn được mã hoá, cuộc trò chuyện nhóm và cuộc gọi video

View file

@ -1 +1 @@
Element (trước là Riot.im)
Element - Nhắn tin bảo mật

View file

@ -1 +1 @@
安全、去中心化的聊天与 VoIP 通话。保护您的数据不被第三方窃取。
群组消息应用-加密的消息传递、群组聊天和视频通话

View file

@ -1 +1 @@
Element(曾为 Riot.im
Element - 安全消息应用

View file

@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionSha256Sum=81003f83b0056d20eedf48cddd4f52a9813163d4ba185bcf8abd34b8eeea4cbd
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-all.zip
distributionSha256Sum=13bf8d3cf8eeeb5770d19741a59bde9bd966dd78d17f1bbad787a05ef19d1c2d
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View file

@ -35,7 +35,7 @@ android {
dependencies {
implementation project(":matrix-sdk-android")
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'androidx.appcompat:appcompat:1.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 {
mavenCentral()
}
dependencies {
classpath "io.realm:realm-gradle-plugin:10.4.0"
classpath "io.realm:realm-gradle-plugin:10.5.0"
}
}
@ -112,7 +112,7 @@ dependencies {
def lifecycle_version = '2.2.0'
def arch_version = '2.1.0'
def markwon_version = '3.1.0'
def daggerVersion = '2.35'
def daggerVersion = '2.35.1'
def work_version = '2.5.0'
def retrofit_version = '2.9.0'
@ -120,7 +120,7 @@ dependencies {
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutines_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version"
implementation "androidx.appcompat:appcompat:1.2.0"
implementation "androidx.appcompat:appcompat:1.3.0"
implementation "androidx.core:core-ktx:1.3.2"
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
@ -169,7 +169,7 @@ dependencies {
implementation 'com.otaliastudios:transcoder:0.10.3'
// Phone number https://github.com/google/libphonenumber
implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.22'
implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.23'
testImplementation 'junit:junit:4.13.2'
testImplementation 'org.robolectric:robolectric:4.5.1'

View file

@ -26,6 +26,7 @@ import org.matrix.android.sdk.internal.di.MatrixModule
import org.matrix.android.sdk.internal.di.MatrixScope
import org.matrix.android.sdk.internal.di.NetworkModule
import org.matrix.android.sdk.internal.raw.RawModule
import org.matrix.android.sdk.internal.util.system.SystemModule
@Component(modules = [
TestModule::class,
@ -33,6 +34,7 @@ import org.matrix.android.sdk.internal.raw.RawModule
NetworkModule::class,
AuthModule::class,
RawModule::class,
SystemModule::class,
TestNetworkModule::class
])
@MatrixScope

View file

@ -226,12 +226,12 @@ class QrCodeTest : InstrumentedTest {
private fun checkHeader(byteArray: ByteArray) {
// MATRIX
byteArray[0] shouldBeEqualTo 'M'.toByte()
byteArray[1] shouldBeEqualTo 'A'.toByte()
byteArray[2] shouldBeEqualTo 'T'.toByte()
byteArray[3] shouldBeEqualTo 'R'.toByte()
byteArray[4] shouldBeEqualTo 'I'.toByte()
byteArray[5] shouldBeEqualTo 'X'.toByte()
byteArray[0] shouldBeEqualTo 'M'.code.toByte()
byteArray[1] shouldBeEqualTo 'A'.code.toByte()
byteArray[2] shouldBeEqualTo 'T'.code.toByte()
byteArray[3] shouldBeEqualTo 'R'.code.toByte()
byteArray[4] shouldBeEqualTo 'I'.code.toByte()
byteArray[5] shouldBeEqualTo 'X'.code.toByte()
// Version
byteArray[6] shouldBeEqualTo 2

View file

@ -0,0 +1,184 @@
/*
* 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.internal.session.securestorage
import android.os.Build
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.amshove.kluent.shouldBeEqualTo
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.internal.crypto.crosssigning.fromBase64
import org.matrix.android.sdk.internal.crypto.crosssigning.toBase64NoPadding
import java.io.ByteArrayOutputStream
import java.util.UUID
@RunWith(AndroidJUnit4::class)
@FixMethodOrder(MethodSorters.JVM)
class SecretStoringUtilsTest : InstrumentedTest {
private val buildVersionSdkIntProvider = TestBuildVersionSdkIntProvider()
private val secretStoringUtils = SecretStoringUtils(context(), buildVersionSdkIntProvider)
companion object {
const val TEST_STR = "This is something I want to store safely!"
}
@Test
fun testStringNominalCaseApi21() {
val alias = generateAlias()
buildVersionSdkIntProvider.value = Build.VERSION_CODES.LOLLIPOP
// Encrypt
val encrypted = secretStoringUtils.securelyStoreString(TEST_STR, alias)
// Decrypt
val decrypted = secretStoringUtils.loadSecureSecret(encrypted, alias)
decrypted shouldBeEqualTo TEST_STR
secretStoringUtils.safeDeleteKey(alias)
}
@Test
fun testStringNominalCaseApi23() {
val alias = generateAlias()
buildVersionSdkIntProvider.value = Build.VERSION_CODES.M
// Encrypt
val encrypted = secretStoringUtils.securelyStoreString(TEST_STR, alias)
// Decrypt
val decrypted = secretStoringUtils.loadSecureSecret(encrypted, alias)
decrypted shouldBeEqualTo TEST_STR
secretStoringUtils.safeDeleteKey(alias)
}
@Test
fun testStringNominalCaseApi30() {
val alias = generateAlias()
buildVersionSdkIntProvider.value = Build.VERSION_CODES.R
// Encrypt
val encrypted = secretStoringUtils.securelyStoreString(TEST_STR, alias)
// Decrypt
val decrypted = secretStoringUtils.loadSecureSecret(encrypted, alias)
decrypted shouldBeEqualTo TEST_STR
secretStoringUtils.safeDeleteKey(alias)
}
@Test
fun testStringMigration21_23() {
val alias = generateAlias()
buildVersionSdkIntProvider.value = Build.VERSION_CODES.LOLLIPOP
// Encrypt
val encrypted = secretStoringUtils.securelyStoreString(TEST_STR, alias)
// Simulate a system upgrade
buildVersionSdkIntProvider.value = Build.VERSION_CODES.M
// Decrypt
val decrypted = secretStoringUtils.loadSecureSecret(encrypted, alias)
decrypted shouldBeEqualTo TEST_STR
secretStoringUtils.safeDeleteKey(alias)
}
@Test
fun testObjectNominalCaseApi21() {
val alias = generateAlias()
buildVersionSdkIntProvider.value = Build.VERSION_CODES.LOLLIPOP
// Encrypt
val encrypted = ByteArrayOutputStream().also { outputStream ->
outputStream.use {
secretStoringUtils.securelyStoreObject(TEST_STR, alias, it)
}
}
.toByteArray()
.toBase64NoPadding()
// Decrypt
val decrypted = encrypted.fromBase64().inputStream().use {
secretStoringUtils.loadSecureSecret<String>(it, alias)
}
decrypted shouldBeEqualTo TEST_STR
secretStoringUtils.safeDeleteKey(alias)
}
@Test
fun testObjectNominalCaseApi23() {
val alias = generateAlias()
buildVersionSdkIntProvider.value = Build.VERSION_CODES.M
// Encrypt
val encrypted = ByteArrayOutputStream().also { outputStream ->
outputStream.use {
secretStoringUtils.securelyStoreObject(TEST_STR, alias, it)
}
}
.toByteArray()
.toBase64NoPadding()
// Decrypt
val decrypted = encrypted.fromBase64().inputStream().use {
secretStoringUtils.loadSecureSecret<String>(it, alias)
}
decrypted shouldBeEqualTo TEST_STR
secretStoringUtils.safeDeleteKey(alias)
}
@Test
fun testObjectNominalCaseApi30() {
val alias = generateAlias()
buildVersionSdkIntProvider.value = Build.VERSION_CODES.R
// Encrypt
val encrypted = ByteArrayOutputStream().also { outputStream ->
outputStream.use {
secretStoringUtils.securelyStoreObject(TEST_STR, alias, it)
}
}
.toByteArray()
.toBase64NoPadding()
// Decrypt
val decrypted = encrypted.fromBase64().inputStream().use {
secretStoringUtils.loadSecureSecret<String>(it, alias)
}
decrypted shouldBeEqualTo TEST_STR
secretStoringUtils.safeDeleteKey(alias)
}
@Test
fun testObjectMigration21_23() {
val alias = generateAlias()
buildVersionSdkIntProvider.value = Build.VERSION_CODES.LOLLIPOP
// Encrypt
val encrypted = ByteArrayOutputStream().also { outputStream ->
outputStream.use {
secretStoringUtils.securelyStoreObject(TEST_STR, alias, it)
}
}
.toByteArray()
.toBase64NoPadding()
// Simulate a system upgrade
buildVersionSdkIntProvider.value = Build.VERSION_CODES.M
// Decrypt
val decrypted = encrypted.fromBase64().inputStream().use {
secretStoringUtils.loadSecureSecret<String>(it, alias)
}
decrypted shouldBeEqualTo TEST_STR
secretStoringUtils.safeDeleteKey(alias)
}
private fun generateAlias() = UUID.randomUUID().toString()
}

View file

@ -0,0 +1,25 @@
/*
* 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.internal.session.securestorage
import org.matrix.android.sdk.internal.util.system.BuildVersionSdkIntProvider
class TestBuildVersionSdkIntProvider : BuildVersionSdkIntProvider {
var value: Int = 0
override fun get() = value
}

View file

@ -51,11 +51,15 @@ interface AuthenticationService {
/**
* Return a LoginWizard, to login to the homeserver. The login flow has to be retrieved first.
*
* See [LoginWizard] for more details
*/
fun getLoginWizard(): LoginWizard
/**
* Return a RegistrationWizard, to create an matrix account on the homeserver. The login flow has to be retrieved first.
*
* See [RegistrationWizard] for more details.
*/
fun getRegistrationWizard(): RegistrationWizard

View file

@ -48,7 +48,7 @@ data class SsoIdentityProvider(
*/
@Json(name = "brand") val brand: String?
) : Parcelable {
) : Parcelable, Comparable<SsoIdentityProvider> {
companion object {
const val BRAND_GOOGLE = "org.matrix.google"
@ -58,4 +58,25 @@ data class SsoIdentityProvider(
const val BRAND_TWITTER = "org.matrix.twitter"
const val BRAND_GITLAB = "org.matrix.gitlab"
}
override fun compareTo(other: SsoIdentityProvider): Int {
return other.toPriority().compareTo(toPriority())
}
private fun toPriority(): Int {
return when (brand) {
// We are on Android, so user is more likely to have a Google account
BRAND_GOOGLE -> 5
// Facebook is also an important SSO provider
BRAND_FACEBOOK -> 4
// Twitter is more for professionals
BRAND_TWITTER -> 3
// Here it's very for techie people
BRAND_GITHUB,
BRAND_GITLAB -> 2
// And finally, if the account has been created with an iPhone...
BRAND_APPLE -> 1
else -> 0
}
}
}

View file

@ -0,0 +1,23 @@
/*
* 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.auth.login
data class LoginProfileInfo(
val matrixId: String,
val displayName: String?,
val fullAvatarUrl: String?
)

View file

@ -17,34 +17,51 @@
package org.matrix.android.sdk.api.auth.login
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.util.Cancelable
/**
* Set of methods to be able to login to an existing account on a homeserver.
*
* More documentation can be found in the file https://github.com/vector-im/element-android/blob/main/docs/signin.md
*/
interface LoginWizard {
/**
* Get some information about a matrixId: displayName and avatar url
*/
suspend fun getProfileInfo(matrixId: String): LoginProfileInfo
/**
* @param login the login field
* @param password the password field
* Login to the homeserver.
*
* @param login the login field. Can be a user name, or a msisdn (email or phone number) associated to the account
* @param password the password of the account
* @param deviceName the initial device name
* @param callback the matrix callback on which you'll receive the result of authentication.
* @return a [Cancelable]
* @return a [Session] if the login is successful
*/
suspend fun login(login: String,
password: String,
deviceName: String): Session
/**
* Exchange a login token to an access token
* Exchange a login token to an access token.
*
* @param loginToken login token, obtain when login has happen in a WebView, using SSO
* @return a [Session] if the login is successful
*/
suspend fun loginWithToken(loginToken: String): Session
/**
* Reset user password
* Ask the homeserver to reset the user password. The password will not be reset until
* [resetPasswordMailConfirmed] is successfully called.
*
* @param email an email previously associated to the account the user wants the password to be reset.
* @param newPassword the desired new password
*/
suspend fun resetPassword(email: String,
newPassword: String)
/**
* Confirm the new password, once the user has checked their email
* When this method succeed, tha account password will be effectively modified.
*/
suspend fun resetPasswordMailConfirmed()
}

View file

@ -16,32 +16,98 @@
package org.matrix.android.sdk.api.auth.registration
/**
* Set of methods to be able to create an account on a homeserver.
*
* Common scenario to register an account successfully:
* - Call [getRegistrationFlow] to check that you application supports all the mandatory registration stages
* - Call [createAccount] to start the account creation
* - Fulfill all mandatory stages using the methods [performReCaptcha] [acceptTerms] [dummy], etc.
*
* More documentation can be found in the file https://github.com/vector-im/element-android/blob/main/docs/signup.md
* and https://matrix.org/docs/spec/client_server/latest#account-registration-and-management
*/
interface RegistrationWizard {
/**
* Call this method to get the possible registration flow of the current homeserver.
* It can be useful to ensure that your application implementation supports all the stages
* required to create an account. If it is not the case, you will have to use the web fallback
* to let the user create an account with your application.
* See [org.matrix.android.sdk.api.auth.AuthenticationService.getFallbackUrl]
*/
suspend fun getRegistrationFlow(): RegistrationResult
/**
* Can be call to check is the desired userName is available for registration on the current homeserver.
* It may also fails if the desired userName is not correctly formatted or does not follow any restriction on
* the homeserver. Ex: userName with only digits may be rejected.
* @param userName the desired username. Ex: "alice"
*/
suspend fun registrationAvailable(userName: String): RegistrationAvailability
/**
* This is the first method to call in order to create an account and start the registration process.
*
* @param userName the desired username. Ex: "alice"
* @param password the desired password
* @param initialDeviceDisplayName the device display name
*/
suspend fun createAccount(userName: String?,
password: String?,
initialDeviceDisplayName: String?): RegistrationResult
/**
* Perform the "m.login.recaptcha" stage.
*
* @param response the response from ReCaptcha
*/
suspend fun performReCaptcha(response: String): RegistrationResult
/**
* Perform the "m.login.terms" stage.
*/
suspend fun acceptTerms(): RegistrationResult
/**
* Perform the "m.login.dummy" stage.
*/
suspend fun dummy(): RegistrationResult
/**
* Perform the "m.login.email.identity" or "m.login.msisdn" stage.
*
* @param threePid the threePid to add to the account. If this is an email, the homeserver will send an email
* to validate it. For a msisdn a SMS will be sent.
*/
suspend fun addThreePid(threePid: RegisterThreePid): RegistrationResult
/**
* Ask the homeserver to send again the current threePid (email or msisdn).
*/
suspend fun sendAgainThreePid(): RegistrationResult
/**
* Send the code received by SMS to validate a msisdn.
* If the code is correct, the registration request will be executed to validate the msisdn.
*/
suspend fun handleValidateThreePid(code: String): RegistrationResult
/**
* Useful to poll the homeserver when waiting for the email to be validated by the user.
* Once the email is validated, this method will return successfully.
* @param delayMillis the SDK can wait before sending the request
*/
suspend fun checkIfEmailHasBeenValidated(delayMillis: Long): RegistrationResult
suspend fun registrationAvailable(userName: String): RegistrationAvailability
/**
* This is the current ThreePid, waiting for validation. The SDK will store it in database, so it can be
* restored even if the app has been killed during the registration
*/
val currentThreePid: String?
// True when login and password has been sent with success to the homeserver
/**
* True when login and password have been sent with success to the homeserver, i.e. [createAccount] has been
* called successfully.
*/
val isRegistrationStarted: Boolean
}

View file

@ -39,6 +39,7 @@ 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.openid.OpenIdService
import org.matrix.android.sdk.api.session.permalinks.PermalinkService
import org.matrix.android.sdk.api.session.profile.ProfileService
import org.matrix.android.sdk.api.session.pushers.PushersService
@ -233,6 +234,11 @@ interface Session :
*/
fun spaceService(): SpaceService
/**
* Returns the open id service associated with the session
*/
fun openIdService(): OpenIdService
/**
* Add a listener to the session.
* @param listener the listener to add.

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.session.openid
interface OpenIdService {
/**
* Gets an OpenID token object that the requester may supply to another service to verify their identity in Matrix.
* The generated token is only valid for exchanging for user information from the federation API for OpenID.
*/
suspend fun getOpenIdToken(): OpenIdToken
}

View file

@ -0,0 +1,48 @@
/*
* 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.openid
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class OpenIdToken(
/**
* Required. An access token the consumer may use to verify the identity of the person who generated the token.
* This is given to the federation API GET /openid/userinfo to verify the user's identity.
*/
@Json(name = "access_token")
val accessToken: String,
/**
* Required. The string "Bearer".
*/
@Json(name = "token_type")
val tokenType: String,
/**
* Required. The homeserver domain the consumer should use when attempting to verify the user's identity.
*/
@Json(name = "matrix_server_name")
val matrixServerName: String,
/**
* Required. The number of seconds before this token expires and a new one must be generated.
*/
@Json(name = "expires_in")
val expiresIn: Int
)

View file

@ -83,7 +83,7 @@ interface Room :
* @param beforeLimit how many events before the result are returned.
* @param afterLimit how many events after the result are returned.
* @param includeProfile requests that the server returns the historic profile information for the users that sent the events that were returned.
* @param callback Callback to get the search result
* @return The search result
*/
suspend fun search(searchTerm: String,
nextBatch: String?,

View file

@ -30,5 +30,7 @@ data class SpaceChildInfo(
val autoJoin: Boolean,
val viaServers: List<String>,
val parentRoomId: String?,
val suggested: Boolean?
val suggested: Boolean?,
val canonicalAlias: String?,
val aliases: List<String>?
)

View file

@ -20,6 +20,7 @@ import android.annotation.SuppressLint
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import org.matrix.android.sdk.api.util.JsonDict
import org.matrix.android.sdk.internal.util.safeCapitalize
/**
* Ref: https://github.com/matrix-org/matrix-doc/issues/1236
@ -39,6 +40,6 @@ data class WidgetContent(
@SuppressLint("DefaultLocale")
fun getHumanName(): String {
return (name ?: type ?: "").capitalize()
return (name ?: type ?: "").safeCapitalize()
}
}

View file

@ -117,22 +117,22 @@ sealed class MatrixItem(
var first = dn[startIndex]
// LEFT-TO-RIGHT MARK
if (dn.length >= 2 && 0x200e == first.toInt()) {
if (dn.length >= 2 && 0x200e == first.code) {
startIndex++
first = dn[startIndex]
}
// check if its the start of a surrogate pair
if (first.toInt() in 0xD800..0xDBFF && dn.length > startIndex + 1) {
if (first.code in 0xD800..0xDBFF && dn.length > startIndex + 1) {
val second = dn[startIndex + 1]
if (second.toInt() in 0xDC00..0xDFFF) {
if (second.code in 0xDC00..0xDFFF) {
length++
}
}
dn.substring(startIndex, startIndex + length)
}
.toUpperCase(Locale.ROOT)
.uppercase(Locale.ROOT)
}
companion object {
@ -159,4 +159,4 @@ fun RoomMemberSummary.toMatrixItem() = MatrixItem.UserItem(userId, displayName,
fun SenderInfo.toMatrixItem() = MatrixItem.UserItem(userId, disambiguatedDisplayName, avatarUrl)
fun SpaceChildInfo.toMatrixItem() = MatrixItem.RoomItem(childRoomId, name, avatarUrl)
fun SpaceChildInfo.toMatrixItem() = MatrixItem.RoomItem(childRoomId, name ?: canonicalAlias ?: "", avatarUrl)

View file

@ -17,6 +17,7 @@
package org.matrix.android.sdk.internal.auth
import org.matrix.android.sdk.api.auth.data.Credentials
import org.matrix.android.sdk.api.util.JsonDict
import org.matrix.android.sdk.internal.auth.data.Availability
import org.matrix.android.sdk.internal.auth.data.LoginFlowResponse
import org.matrix.android.sdk.internal.auth.data.PasswordLoginParams
@ -73,6 +74,15 @@ internal interface AuthAPI {
@GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "register/available")
suspend fun registerAvailable(@Query("username") username: String): Availability
/**
* Get the combined profile information for this user.
* This API may be used to fetch the user's own profile information or other users; either locally or on remote homeservers.
* This API may return keys which are not limited to displayname or avatar_url.
* @param userId the user id to fetch profile info
*/
@GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "profile/{userId}")
suspend fun getProfile(@Path("userId") userId: String): JsonDict
/**
* Add 3Pid during registration
* Ref: https://gist.github.com/jryans/839a09bf0c5a70e2f36ed990d50ed928

View file

@ -17,6 +17,7 @@
package org.matrix.android.sdk.internal.auth.login
import android.util.Patterns
import org.matrix.android.sdk.api.auth.login.LoginProfileInfo
import org.matrix.android.sdk.api.auth.login.LoginWizard
import org.matrix.android.sdk.api.auth.registration.RegisterThreePid
import org.matrix.android.sdk.api.session.Session
@ -30,6 +31,7 @@ import org.matrix.android.sdk.internal.auth.db.PendingSessionData
import org.matrix.android.sdk.internal.auth.registration.AddThreePidRegistrationParams
import org.matrix.android.sdk.internal.auth.registration.RegisterAddThreePidTask
import org.matrix.android.sdk.internal.network.executeRequest
import org.matrix.android.sdk.internal.session.content.DefaultContentUrlResolver
internal class DefaultLoginWizard(
private val authAPI: AuthAPI,
@ -39,6 +41,15 @@ internal class DefaultLoginWizard(
private var pendingSessionData: PendingSessionData = pendingSessionStore.getPendingSessionData() ?: error("Pending session data should exist here")
private val getProfileTask: GetProfileTask = DefaultGetProfileTask(
authAPI,
DefaultContentUrlResolver(pendingSessionData.homeServerConnectionConfig)
)
override suspend fun getProfileInfo(matrixId: String): LoginProfileInfo {
return getProfileTask.execute(GetProfileTask.Params(matrixId))
}
override suspend fun login(login: String,
password: String,
deviceName: String): Session {

View file

@ -0,0 +1,48 @@
/*
* 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.internal.auth.login
import org.matrix.android.sdk.api.auth.login.LoginProfileInfo
import org.matrix.android.sdk.api.session.content.ContentUrlResolver
import org.matrix.android.sdk.api.session.profile.ProfileService
import org.matrix.android.sdk.internal.auth.AuthAPI
import org.matrix.android.sdk.internal.network.executeRequest
import org.matrix.android.sdk.internal.task.Task
internal interface GetProfileTask : Task<GetProfileTask.Params, LoginProfileInfo> {
data class Params(
val userId: String
)
}
internal class DefaultGetProfileTask(
private val authAPI: AuthAPI,
private val contentUrlResolver: ContentUrlResolver
) : GetProfileTask {
override suspend fun execute(params: GetProfileTask.Params): LoginProfileInfo {
val info = executeRequest(null) {
authAPI.getProfile(params.userId)
}
return LoginProfileInfo(
matrixId = params.userId,
displayName = info[ProfileService.DISPLAY_NAME_KEY] as? String,
fullAvatarUrl = contentUrlResolver.resolveFullSize(info[ProfileService.AVATAR_URL_KEY] as? String)
)
}
}

View file

@ -545,14 +545,14 @@ internal class DefaultCryptoService @Inject constructor(
val existingAlgorithm = cryptoStore.getRoomAlgorithm(roomId)
if (!existingAlgorithm.isNullOrEmpty() && existingAlgorithm != algorithm) {
Timber.e("## CRYPTO | setEncryptionInRoom() : Ignoring m.room.encryption event which requests a change of config in $roomId")
Timber.e("## CRYPTO | setEncryptionInRoom() : Ignoring m.room.encryption event which requests a change of config in $roomId")
return false
}
val encryptingClass = MXCryptoAlgorithms.hasEncryptorClassForAlgorithm(algorithm)
if (!encryptingClass) {
Timber.e("## CRYPTO | setEncryptionInRoom() : Unable to encrypt room $roomId with $algorithm")
Timber.e("## CRYPTO | setEncryptionInRoom() : Unable to encrypt room $roomId with $algorithm")
return false
}
@ -649,17 +649,17 @@ internal class DefaultCryptoService @Inject constructor(
val safeAlgorithm = alg
if (safeAlgorithm != null) {
val t0 = System.currentTimeMillis()
Timber.v("## CRYPTO | encryptEventContent() starts")
Timber.v("## CRYPTO | encryptEventContent() starts")
runCatching {
val content = safeAlgorithm.encryptEventContent(eventContent, eventType, userIds)
Timber.v("## CRYPTO | encryptEventContent() : succeeds after ${System.currentTimeMillis() - t0} ms")
Timber.v("## CRYPTO | encryptEventContent() : succeeds after ${System.currentTimeMillis() - t0} ms")
MXEncryptEventContentResult(content, EventType.ENCRYPTED)
}.foldToCallback(callback)
} else {
val algorithm = getEncryptionAlgorithm(roomId)
val reason = String.format(MXCryptoError.UNABLE_TO_ENCRYPT_REASON,
algorithm ?: MXCryptoError.NO_MORE_ALGORITHM_REASON)
Timber.e("## CRYPTO | encryptEventContent() : $reason")
Timber.e("## CRYPTO | encryptEventContent() : $reason")
callback.onFailure(Failure.CryptoError(MXCryptoError.Base(MXCryptoError.ErrorType.UNABLE_TO_ENCRYPT, reason)))
}
}
@ -769,7 +769,7 @@ internal class DefaultCryptoService @Inject constructor(
}
val alg = roomDecryptorProvider.getOrCreateRoomDecryptor(roomKeyContent.roomId, roomKeyContent.algorithm)
if (alg == null) {
Timber.e("## CRYPTO | GOSSIP onRoomKeyEvent() : Unable to handle keys for ${roomKeyContent.algorithm}")
Timber.e("## CRYPTO | GOSSIP onRoomKeyEvent() : Unable to handle keys for ${roomKeyContent.algorithm}")
return
}
alg.onRoomKeyEvent(event, keysBackupService)
@ -777,7 +777,7 @@ internal class DefaultCryptoService @Inject constructor(
private fun onKeyWithHeldReceived(event: Event) {
val withHeldContent = event.getClearContent().toModel<RoomKeyWithHeldContent>() ?: return Unit.also {
Timber.i("## CRYPTO | Malformed onKeyWithHeldReceived() : missing fields")
Timber.i("## CRYPTO | Malformed onKeyWithHeldReceived() : missing fields")
}
Timber.i("## CRYPTO | onKeyWithHeldReceived() received from:${event.senderId}, content <$withHeldContent>")
val alg = roomDecryptorProvider.getOrCreateRoomDecryptor(withHeldContent.roomId, withHeldContent.algorithm)
@ -790,16 +790,16 @@ internal class DefaultCryptoService @Inject constructor(
}
private fun onSecretSendReceived(event: Event) {
Timber.i("## CRYPTO | GOSSIP onSecretSend() from ${event.senderId} : onSecretSendReceived ${event.content?.get("sender_key")}")
Timber.i("## CRYPTO | GOSSIP onSecretSend() from ${event.senderId} : onSecretSendReceived ${event.content?.get("sender_key")}")
if (!event.isEncrypted()) {
// secret send messages must be encrypted
Timber.e("## CRYPTO | GOSSIP onSecretSend() :Received unencrypted secret send event")
Timber.e("## CRYPTO | GOSSIP onSecretSend() :Received unencrypted secret send event")
return
}
// Was that sent by us?
if (event.senderId != userId) {
Timber.e("## CRYPTO | GOSSIP onSecretSend() : Ignore secret from other user ${event.senderId}")
Timber.e("## CRYPTO | GOSSIP onSecretSend() : Ignore secret from other user ${event.senderId}")
return
}
@ -809,13 +809,13 @@ internal class DefaultCryptoService @Inject constructor(
.getOutgoingSecretKeyRequests().firstOrNull { it.requestId == secretContent.requestId }
if (existingRequest == null) {
Timber.i("## CRYPTO | GOSSIP onSecretSend() : Ignore secret that was not requested: ${secretContent.requestId}")
Timber.i("## CRYPTO | GOSSIP onSecretSend() : Ignore secret that was not requested: ${secretContent.requestId}")
return
}
if (!handleSDKLevelGossip(existingRequest.secretName, secretContent.secretValue)) {
// TODO Ask to application layer?
Timber.v("## CRYPTO | onSecretSend() : secret not handled by SDK")
Timber.v("## CRYPTO | onSecretSend() : secret not handled by SDK")
}
}
@ -972,13 +972,13 @@ internal class DefaultCryptoService @Inject constructor(
cryptoCoroutineScope.launch(coroutineDispatchers.main) {
runCatching {
withContext(coroutineDispatchers.crypto) {
Timber.v("## CRYPTO | importRoomKeys starts")
Timber.v("## CRYPTO | importRoomKeys starts")
val t0 = System.currentTimeMillis()
val roomKeys = MXMegolmExportEncryption.decryptMegolmKeyFile(roomKeysAsArray, password)
val t1 = System.currentTimeMillis()
Timber.v("## CRYPTO | importRoomKeys : decryptMegolmKeyFile done in ${t1 - t0} ms")
Timber.v("## CRYPTO | importRoomKeys : decryptMegolmKeyFile done in ${t1 - t0} ms")
val importedSessions = MoshiProvider.providesMoshi()
.adapter<List<MegolmSessionData>>(Types.newParameterizedType(List::class.java, MegolmSessionData::class.java))
@ -986,7 +986,7 @@ internal class DefaultCryptoService @Inject constructor(
val t2 = System.currentTimeMillis()
Timber.v("## CRYPTO | importRoomKeys : JSON parsing ${t2 - t1} ms")
Timber.v("## CRYPTO | importRoomKeys : JSON parsing ${t2 - t1} ms")
if (importedSessions == null) {
throw Exception("Error")
@ -1125,7 +1125,7 @@ internal class DefaultCryptoService @Inject constructor(
*/
override fun reRequestRoomKeyForEvent(event: Event) {
val wireContent = event.content.toModel<EncryptedEventContent>() ?: return Unit.also {
Timber.e("## CRYPTO | reRequestRoomKeyForEvent Failed to re-request key, null content")
Timber.e("## CRYPTO | reRequestRoomKeyForEvent Failed to re-request key, null content")
}
val requestBody = RoomKeyRequestBody(
@ -1140,18 +1140,18 @@ internal class DefaultCryptoService @Inject constructor(
override fun requestRoomKeyForEvent(event: Event) {
val wireContent = event.content.toModel<EncryptedEventContent>() ?: return Unit.also {
Timber.e("## CRYPTO | requestRoomKeyForEvent Failed to request key, null content eventId: ${event.eventId}")
Timber.e("## CRYPTO | requestRoomKeyForEvent Failed to request key, null content eventId: ${event.eventId}")
}
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
// if (!isStarted()) {
// Timber.v("## CRYPTO | requestRoomKeyForEvent() : wait after e2e init")
// Timber.v("## CRYPTO | requestRoomKeyForEvent() : wait after e2e init")
// internalStart(false)
// }
roomDecryptorProvider
.getOrCreateRoomDecryptor(event.roomId, wireContent.algorithm)
?.requestKeysForEvent(event, false) ?: run {
Timber.v("## CRYPTO | requestRoomKeyForEvent() : No room decryptor for roomId:${event.roomId} algorithm:${wireContent.algorithm}")
Timber.v("## CRYPTO | requestRoomKeyForEvent() : No room decryptor for roomId:${event.roomId} algorithm:${wireContent.algorithm}")
}
}
}
@ -1180,11 +1180,11 @@ internal class DefaultCryptoService @Inject constructor(
// val lastForcedDate = lastNewSessionForcedDates.getObject(senderId, deviceKey) ?: 0
// val now = System.currentTimeMillis()
// if (now - lastForcedDate < CRYPTO_MIN_FORCE_SESSION_PERIOD_MILLIS) {
// Timber.d("## CRYPTO | markOlmSessionForUnwedging: New session already forced with device at $lastForcedDate. Not forcing another")
// Timber.d("## CRYPTO | markOlmSessionForUnwedging: New session already forced with device at $lastForcedDate. Not forcing another")
// return
// }
//
// Timber.d("## CRYPTO | markOlmSessionForUnwedging from $senderId:${deviceInfo.deviceId}")
// Timber.d("## CRYPTO | markOlmSessionForUnwedging from $senderId:${deviceInfo.deviceId}")
// lastNewSessionForcedDates.setObject(senderId, deviceKey, now)
//
// cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
@ -1201,7 +1201,7 @@ internal class DefaultCryptoService @Inject constructor(
// val encodedPayload = messageEncrypter.encryptMessage(payloadJson, listOf(deviceInfo))
// val sendToDeviceMap = MXUsersDevicesMap<Any>()
// sendToDeviceMap.setObject(senderId, deviceInfo.deviceId, encodedPayload)
// Timber.v("## CRYPTO | markOlmSessionForUnwedging() : sending to $senderId:${deviceInfo.deviceId}")
// Timber.v("## CRYPTO | markOlmSessionForUnwedging() : sending to $senderId:${deviceInfo.deviceId}")
// val sendToDeviceParams = SendToDeviceTask.Params(EventType.ENCRYPTED, sendToDeviceMap)
// sendToDeviceTask.execute(sendToDeviceParams)
// }
@ -1290,12 +1290,12 @@ internal class DefaultCryptoService @Inject constructor(
override fun prepareToEncrypt(roomId: String, callback: MatrixCallback<Unit>) {
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
Timber.d("## CRYPTO | prepareToEncrypt() : Check room members up to date")
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")
Timber.e("## CRYPTO | prepareToEncrypt() : Failed to load room members")
callback.onFailure(failure)
return@launch
}
@ -1308,7 +1308,7 @@ internal class DefaultCryptoService @Inject constructor(
if (alg == null) {
val reason = String.format(MXCryptoError.UNABLE_TO_ENCRYPT_REASON, MXCryptoError.NO_MORE_ALGORITHM_REASON)
Timber.e("## CRYPTO | prepareToEncrypt() : $reason")
Timber.e("## CRYPTO | prepareToEncrypt() : $reason")
callback.onFailure(IllegalArgumentException("Missing algorithm"))
return@launch
}
@ -1318,7 +1318,7 @@ internal class DefaultCryptoService @Inject constructor(
}.fold(
{ callback.onSuccess(Unit) },
{
Timber.e("## CRYPTO | prepareToEncrypt() failed.")
Timber.e("## CRYPTO | prepareToEncrypt() failed.")
callback.onFailure(it)
}
)

View file

@ -111,7 +111,7 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
res = !notReadyToRetryHS.contains(userId.substringAfter(':'))
}
} catch (e: Exception) {
Timber.e(e, "## CRYPTO | canRetryKeysDownload() failed")
Timber.e(e, "## CRYPTO | canRetryKeysDownload() failed")
}
}
@ -150,7 +150,7 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
for (userId in userIds) {
if (!deviceTrackingStatuses.containsKey(userId) || TRACKING_STATUS_NOT_TRACKED == deviceTrackingStatuses[userId]) {
Timber.v("## CRYPTO | startTrackingDeviceList() : Now tracking device list for $userId")
Timber.v("## CRYPTO | startTrackingDeviceList() : Now tracking device list for $userId")
deviceTrackingStatuses[userId] = TRACKING_STATUS_PENDING_DOWNLOAD
isUpdated = true
}
@ -178,7 +178,7 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
for (userId in changed) {
if (deviceTrackingStatuses.containsKey(userId)) {
Timber.v("## CRYPTO | handleDeviceListsChanges() : Marking device list outdated for $userId")
Timber.v("## CRYPTO | handleDeviceListsChanges() : Marking device list outdated for $userId")
deviceTrackingStatuses[userId] = TRACKING_STATUS_PENDING_DOWNLOAD
isUpdated = true
}
@ -186,7 +186,7 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
for (userId in left) {
if (deviceTrackingStatuses.containsKey(userId)) {
Timber.v("## CRYPTO | handleDeviceListsChanges() : No longer tracking device list for $userId")
Timber.v("## CRYPTO | handleDeviceListsChanges() : No longer tracking device list for $userId")
deviceTrackingStatuses[userId] = TRACKING_STATUS_NOT_TRACKED
isUpdated = true
}
@ -276,7 +276,7 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
* @param forceDownload Always download the keys even if cached.
*/
suspend fun downloadKeys(userIds: List<String>?, forceDownload: Boolean): MXUsersDevicesMap<CryptoDeviceInfo> {
Timber.v("## CRYPTO | downloadKeys() : forceDownload $forceDownload : $userIds")
Timber.v("## CRYPTO | downloadKeys() : forceDownload $forceDownload : $userIds")
// Map from userId -> deviceId -> MXDeviceInfo
val stored = MXUsersDevicesMap<CryptoDeviceInfo>()
@ -305,13 +305,13 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
}
}
return if (downloadUsers.isEmpty()) {
Timber.v("## CRYPTO | downloadKeys() : no new user device")
Timber.v("## CRYPTO | downloadKeys() : no new user device")
stored
} else {
Timber.v("## CRYPTO | downloadKeys() : starts")
Timber.v("## CRYPTO | downloadKeys() : starts")
val t0 = System.currentTimeMillis()
val result = doKeyDownloadForUsers(downloadUsers)
Timber.v("## CRYPTO | downloadKeys() : doKeyDownloadForUsers succeeds after ${System.currentTimeMillis() - t0} ms")
Timber.v("## CRYPTO | downloadKeys() : doKeyDownloadForUsers succeeds after ${System.currentTimeMillis() - t0} ms")
result.also {
it.addEntriesFromMap(stored)
}
@ -324,7 +324,7 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
* @param downloadUsers the user ids list
*/
private suspend fun doKeyDownloadForUsers(downloadUsers: List<String>): MXUsersDevicesMap<CryptoDeviceInfo> {
Timber.v("## CRYPTO | doKeyDownloadForUsers() : doKeyDownloadForUsers ${downloadUsers.logLimit()}")
Timber.v("## CRYPTO | doKeyDownloadForUsers() : doKeyDownloadForUsers ${downloadUsers.logLimit()}")
// get the user ids which did not already trigger a keys download
val filteredUsers = downloadUsers.filter { MatrixPatterns.isUserId(it) }
if (filteredUsers.isEmpty()) {
@ -335,16 +335,16 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
val response = try {
downloadKeysForUsersTask.execute(params)
} catch (throwable: Throwable) {
Timber.e(throwable, "## CRYPTO | doKeyDownloadForUsers(): error")
Timber.e(throwable, "## CRYPTO | doKeyDownloadForUsers(): error")
onKeysDownloadFailed(filteredUsers)
throw throwable
}
Timber.v("## CRYPTO | doKeyDownloadForUsers() : Got keys for " + filteredUsers.size + " users")
Timber.v("## CRYPTO | doKeyDownloadForUsers() : Got keys for " + filteredUsers.size + " users")
for (userId in filteredUsers) {
// al devices =
val models = response.deviceKeys?.get(userId)?.mapValues { entry -> CryptoInfoMapper.map(entry.value) }
Timber.v("## CRYPTO | doKeyDownloadForUsers() : Got keys for $userId : $models")
Timber.v("## CRYPTO | doKeyDownloadForUsers() : Got keys for $userId : $models")
if (!models.isNullOrEmpty()) {
val workingCopy = models.toMutableMap()
for ((deviceId, deviceInfo) in models) {
@ -377,13 +377,13 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
}
val masterKey = response.masterKeys?.get(userId)?.toCryptoModel().also {
Timber.v("## CRYPTO | CrossSigning : Got keys for $userId : MSK ${it?.unpaddedBase64PublicKey}")
Timber.v("## CRYPTO | CrossSigning : Got keys for $userId : MSK ${it?.unpaddedBase64PublicKey}")
}
val selfSigningKey = response.selfSigningKeys?.get(userId)?.toCryptoModel()?.also {
Timber.v("## CRYPTO | CrossSigning : Got keys for $userId : SSK ${it.unpaddedBase64PublicKey}")
Timber.v("## CRYPTO | CrossSigning : Got keys for $userId : SSK ${it.unpaddedBase64PublicKey}")
}
val userSigningKey = response.userSigningKeys?.get(userId)?.toCryptoModel()?.also {
Timber.v("## CRYPTO | CrossSigning : Got keys for $userId : USK ${it.unpaddedBase64PublicKey}")
Timber.v("## CRYPTO | CrossSigning : Got keys for $userId : USK ${it.unpaddedBase64PublicKey}")
}
cryptoStore.storeUserCrossSigningKeys(
userId,
@ -411,28 +411,28 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
*/
private fun validateDeviceKeys(deviceKeys: CryptoDeviceInfo?, userId: String, deviceId: String, previouslyStoredDeviceKeys: CryptoDeviceInfo?): Boolean {
if (null == deviceKeys) {
Timber.e("## CRYPTO | validateDeviceKeys() : deviceKeys is null from $userId:$deviceId")
Timber.e("## CRYPTO | validateDeviceKeys() : deviceKeys is null from $userId:$deviceId")
return false
}
if (null == deviceKeys.keys) {
Timber.e("## CRYPTO | validateDeviceKeys() : deviceKeys.keys is null from $userId:$deviceId")
Timber.e("## CRYPTO | validateDeviceKeys() : deviceKeys.keys is null from $userId:$deviceId")
return false
}
if (null == deviceKeys.signatures) {
Timber.e("## CRYPTO | validateDeviceKeys() : deviceKeys.signatures is null from $userId:$deviceId")
Timber.e("## CRYPTO | validateDeviceKeys() : deviceKeys.signatures is null from $userId:$deviceId")
return false
}
// Check that the user_id and device_id in the received deviceKeys are correct
if (deviceKeys.userId != userId) {
Timber.e("## CRYPTO | validateDeviceKeys() : Mismatched user_id ${deviceKeys.userId} from $userId:$deviceId")
Timber.e("## CRYPTO | validateDeviceKeys() : Mismatched user_id ${deviceKeys.userId} from $userId:$deviceId")
return false
}
if (deviceKeys.deviceId != deviceId) {
Timber.e("## CRYPTO | validateDeviceKeys() : Mismatched device_id ${deviceKeys.deviceId} from $userId:$deviceId")
Timber.e("## CRYPTO | validateDeviceKeys() : Mismatched device_id ${deviceKeys.deviceId} from $userId:$deviceId")
return false
}
@ -440,21 +440,21 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
val signKey = deviceKeys.keys[signKeyId]
if (null == signKey) {
Timber.e("## CRYPTO | validateDeviceKeys() : Device $userId:${deviceKeys.deviceId} has no ed25519 key")
Timber.e("## CRYPTO | validateDeviceKeys() : Device $userId:${deviceKeys.deviceId} has no ed25519 key")
return false
}
val signatureMap = deviceKeys.signatures[userId]
if (null == signatureMap) {
Timber.e("## CRYPTO | validateDeviceKeys() : Device $userId:${deviceKeys.deviceId} has no map for $userId")
Timber.e("## CRYPTO | validateDeviceKeys() : Device $userId:${deviceKeys.deviceId} has no map for $userId")
return false
}
val signature = signatureMap[signKeyId]
if (null == signature) {
Timber.e("## CRYPTO | validateDeviceKeys() : Device $userId:${deviceKeys.deviceId} is not signed")
Timber.e("## CRYPTO | validateDeviceKeys() : Device $userId:${deviceKeys.deviceId} is not signed")
return false
}
@ -469,7 +469,7 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
}
if (!isVerified) {
Timber.e("## CRYPTO | validateDeviceKeys() : Unable to verify signature on device " + userId + ":"
Timber.e("## CRYPTO | validateDeviceKeys() : Unable to verify signature on device " + userId + ":"
+ deviceKeys.deviceId + " with error " + errorMessage)
return false
}
@ -480,12 +480,12 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
// best off sticking with the original keys.
//
// Should we warn the user about it somehow?
Timber.e("## CRYPTO | validateDeviceKeys() : WARNING:Ed25519 key for device " + userId + ":"
Timber.e("## CRYPTO | validateDeviceKeys() : WARNING:Ed25519 key for device " + userId + ":"
+ deviceKeys.deviceId + " has changed : "
+ previouslyStoredDeviceKeys.fingerprint() + " -> " + signKey)
Timber.e("## CRYPTO | validateDeviceKeys() : $previouslyStoredDeviceKeys -> $deviceKeys")
Timber.e("## CRYPTO | validateDeviceKeys() : ${previouslyStoredDeviceKeys.keys} -> ${deviceKeys.keys}")
Timber.e("## CRYPTO | validateDeviceKeys() : $previouslyStoredDeviceKeys -> $deviceKeys")
Timber.e("## CRYPTO | validateDeviceKeys() : ${previouslyStoredDeviceKeys.keys} -> ${deviceKeys.keys}")
return false
}
@ -499,7 +499,7 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
* This method must be called on getEncryptingThreadHandler() thread.
*/
suspend fun refreshOutdatedDeviceLists() {
Timber.v("## CRYPTO | refreshOutdatedDeviceLists()")
Timber.v("## CRYPTO | refreshOutdatedDeviceLists()")
val deviceTrackingStatuses = cryptoStore.getDeviceTrackingStatuses().toMutableMap()
val users = deviceTrackingStatuses.keys.filterTo(mutableListOf()) { userId ->
@ -518,10 +518,10 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
doKeyDownloadForUsers(users)
}.fold(
{
Timber.v("## CRYPTO | refreshOutdatedDeviceLists() : done")
Timber.v("## CRYPTO | refreshOutdatedDeviceLists() : done")
},
{
Timber.e(it, "## CRYPTO | refreshOutdatedDeviceLists() : ERROR updating device keys for users $users")
Timber.e(it, "## CRYPTO | refreshOutdatedDeviceLists() : ERROR updating device keys for users $users")
}
)
}

View file

@ -92,20 +92,20 @@ internal class EventDecryptor @Inject constructor(
private fun internalDecryptEvent(event: Event, timeline: String): MXEventDecryptionResult {
val eventContent = event.content
if (eventContent == null) {
Timber.e("## CRYPTO | decryptEvent : empty event content")
Timber.e("## CRYPTO | decryptEvent : empty event content")
throw MXCryptoError.Base(MXCryptoError.ErrorType.BAD_ENCRYPTED_MESSAGE, MXCryptoError.BAD_ENCRYPTED_MESSAGE_REASON)
} else {
val algorithm = eventContent["algorithm"]?.toString()
val alg = roomDecryptorProvider.getOrCreateRoomDecryptor(event.roomId, algorithm)
if (alg == null) {
val reason = String.format(MXCryptoError.UNABLE_TO_DECRYPT_REASON, event.eventId, algorithm)
Timber.e("## CRYPTO | decryptEvent() : $reason")
Timber.e("## CRYPTO | decryptEvent() : $reason")
throw MXCryptoError.Base(MXCryptoError.ErrorType.UNABLE_TO_DECRYPT, reason)
} else {
try {
return alg.decryptEvent(event, timeline)
} catch (mxCryptoError: MXCryptoError) {
Timber.v("## 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) {
@ -119,7 +119,7 @@ internal class EventDecryptor @Inject constructor(
markOlmSessionForUnwedging(event.senderId ?: "", it)
}
?: run {
Timber.i("## CRYPTO | internalDecryptEvent() : Failed to find sender crypto device for unwedging")
Timber.i("## CRYPTO | internalDecryptEvent() : Failed to find sender crypto device for unwedging")
}
}
}
@ -137,18 +137,18 @@ internal class EventDecryptor @Inject constructor(
val lastForcedDate = lastNewSessionForcedDates.getObject(senderId, deviceKey) ?: 0
val now = System.currentTimeMillis()
if (now - lastForcedDate < DefaultCryptoService.CRYPTO_MIN_FORCE_SESSION_PERIOD_MILLIS) {
Timber.w("## CRYPTO | markOlmSessionForUnwedging: New session already forced with device at $lastForcedDate. Not forcing another")
Timber.w("## CRYPTO | markOlmSessionForUnwedging: New session already forced with device at $lastForcedDate. Not forcing another")
return
}
Timber.i("## CRYPTO | markOlmSessionForUnwedging from $senderId:${deviceInfo.deviceId}")
Timber.i("## CRYPTO | markOlmSessionForUnwedging from $senderId:${deviceInfo.deviceId}")
lastNewSessionForcedDates.setObject(senderId, deviceKey, now)
// offload this from crypto thread (?)
cryptoCoroutineScope.launch(coroutineDispatchers.computation) {
val ensured = ensureOlmSessionsForDevicesAction.handle(mapOf(senderId to listOf(deviceInfo)), force = true)
Timber.i("## CRYPTO | markOlmSessionForUnwedging() : ensureOlmSessionsForDevicesAction isEmpty:${ensured.isEmpty}")
Timber.i("## CRYPTO | markOlmSessionForUnwedging() : ensureOlmSessionsForDevicesAction isEmpty:${ensured.isEmpty}")
// Now send a blank message on that session so the other side knows about it.
// (The keyshare request is sent in the clear so that won't do)
@ -161,13 +161,13 @@ internal class EventDecryptor @Inject constructor(
val encodedPayload = messageEncrypter.encryptMessage(payloadJson, listOf(deviceInfo))
val sendToDeviceMap = MXUsersDevicesMap<Any>()
sendToDeviceMap.setObject(senderId, deviceInfo.deviceId, encodedPayload)
Timber.i("## CRYPTO | markOlmSessionForUnwedging() : sending dummy to $senderId:${deviceInfo.deviceId}")
Timber.i("## CRYPTO | markOlmSessionForUnwedging() : sending dummy to $senderId:${deviceInfo.deviceId}")
withContext(coroutineDispatchers.io) {
val sendToDeviceParams = SendToDeviceTask.Params(EventType.ENCRYPTED, sendToDeviceMap)
try {
sendToDeviceTask.execute(sendToDeviceParams)
} catch (failure: Throwable) {
Timber.e(failure, "## CRYPTO | markOlmSessionForUnwedging() : failed to send dummy to $senderId:${deviceInfo.deviceId}")
Timber.e(failure, "## CRYPTO | markOlmSessionForUnwedging() : failed to send dummy to $senderId:${deviceInfo.deviceId}")
}
}
}

View file

@ -74,7 +74,7 @@ internal class MXMegolmDecryption(private val userId: String,
@Throws(MXCryptoError::class)
private fun decryptEvent(event: Event, timeline: String, requestKeysOnFail: Boolean): MXEventDecryptionResult {
Timber.v("## CRYPTO | decryptEvent ${event.eventId} , requestKeysOnFail:$requestKeysOnFail")
Timber.v("## CRYPTO | decryptEvent ${event.eventId}, requestKeysOnFail:$requestKeysOnFail")
if (event.roomId.isNullOrBlank()) {
throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_FIELDS, MXCryptoError.MISSING_FIELDS_REASON)
}
@ -360,7 +360,7 @@ internal class MXMegolmDecryption(private val userId: String,
},
{
// TODO
Timber.e(it, "## CRYPTO | shareKeysWithDevice: failed to get session for request $body")
Timber.e(it, "## CRYPTO | shareKeysWithDevice: failed to get session for request $body")
}
)

View file

@ -80,9 +80,9 @@ internal class MXMegolmEncryption(
eventType: String,
userIds: List<String>): Content {
val ts = System.currentTimeMillis()
Timber.v("## CRYPTO | encryptEventContent : getDevicesInRoom")
Timber.v("## CRYPTO | encryptEventContent : getDevicesInRoom")
val devices = getDevicesInRoom(userIds)
Timber.v("## CRYPTO | encryptEventContent ${System.currentTimeMillis() - ts}: getDevicesInRoom ${devices.allowedDevices.map}")
Timber.v("## CRYPTO | encryptEventContent ${System.currentTimeMillis() - ts}: getDevicesInRoom ${devices.allowedDevices.map}")
val outboundSession = ensureOutboundSession(devices.allowedDevices)
return encryptContent(outboundSession, eventType, eventContent)
@ -91,7 +91,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")
Timber.v("## CRYPTO | encryptEventContent: Finished in ${System.currentTimeMillis() - ts} millis")
}
}
@ -118,13 +118,13 @@ internal class MXMegolmEncryption(
override suspend fun preshareKey(userIds: List<String>) {
val ts = System.currentTimeMillis()
Timber.v("## CRYPTO | preshareKey : getDevicesInRoom")
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")
Timber.v("## CRYPTO | preshareKey ${System.currentTimeMillis() - ts} millis")
}
/**
@ -133,7 +133,7 @@ internal class MXMegolmEncryption(
* @return the session description
*/
private fun prepareNewSessionInRoom(): MXOutboundSessionInfo {
Timber.v("## CRYPTO | prepareNewSessionInRoom() ")
Timber.v("## CRYPTO | prepareNewSessionInRoom() ")
val sessionId = olmDevice.createOutboundGroupSessionForRoom(roomId)
val keysClaimedMap = HashMap<String, String>()
@ -153,7 +153,7 @@ internal class MXMegolmEncryption(
* @param devicesInRoom the devices list
*/
private suspend fun ensureOutboundSession(devicesInRoom: MXUsersDevicesMap<CryptoDeviceInfo>): MXOutboundSessionInfo {
Timber.v("## CRYPTO | ensureOutboundSession start")
Timber.v("## CRYPTO | ensureOutboundSession start")
var session = outboundSession
if (session == null
// Need to make a brand new session?
@ -190,7 +190,7 @@ internal class MXMegolmEncryption(
devicesByUsers: Map<String, List<CryptoDeviceInfo>>) {
// nothing to send, the task is done
if (devicesByUsers.isEmpty()) {
Timber.v("## CRYPTO | shareKey() : nothing more to do")
Timber.v("## CRYPTO | shareKey() : nothing more to do")
return
}
// reduce the map size to avoid request timeout when there are too many devices (Users size * devices per user)
@ -203,7 +203,7 @@ internal class MXMegolmEncryption(
break
}
}
Timber.v("## CRYPTO | shareKey() ; sessionId<${session.sessionId}> userId ${subMap.keys}")
Timber.v("## CRYPTO | shareKey() ; sessionId<${session.sessionId}> userId ${subMap.keys}")
shareUserDevicesKey(session, subMap)
val remainingDevices = devicesByUsers - subMap.keys
shareKey(session, remainingDevices)
@ -232,11 +232,11 @@ internal class MXMegolmEncryption(
payload["content"] = submap
var t0 = System.currentTimeMillis()
Timber.v("## CRYPTO | shareUserDevicesKey() : starts")
Timber.v("## CRYPTO | shareUserDevicesKey() : starts")
val results = ensureOlmSessionsForDevicesAction.handle(devicesByUser)
Timber.v(
"""## CRYPTO | shareUserDevicesKey(): ensureOlmSessionsForDevices succeeds after ${System.currentTimeMillis() - t0} ms"""
"""## CRYPTO | shareUserDevicesKey(): ensureOlmSessionsForDevices succeeds after ${System.currentTimeMillis() - t0} ms"""
.trimMargin()
)
val contentMap = MXUsersDevicesMap<Any>()
@ -257,7 +257,7 @@ internal class MXMegolmEncryption(
noOlmToNotify.add(UserDevice(userId, deviceID))
continue
}
Timber.i("## CRYPTO | shareUserDevicesKey() : Add to share keys contentMap for $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
}
@ -289,17 +289,17 @@ internal class MXMegolmEncryption(
if (haveTargets) {
t0 = System.currentTimeMillis()
Timber.i("## CRYPTO | shareUserDevicesKey() ${session.sessionId} : has target")
Timber.i("## CRYPTO | shareUserDevicesKey() ${session.sessionId} : has target")
val sendToDeviceParams = SendToDeviceTask.Params(EventType.ENCRYPTED, contentMap)
try {
sendToDeviceTask.execute(sendToDeviceParams)
Timber.i("## CRYPTO | shareUserDevicesKey() : sendToDevice succeeds after ${System.currentTimeMillis() - t0} ms")
Timber.i("## CRYPTO | shareUserDevicesKey() : sendToDevice succeeds after ${System.currentTimeMillis() - t0} ms")
} catch (failure: Throwable) {
// What to do here...
Timber.e("## CRYPTO | shareUserDevicesKey() : Failed to share session <${session.sessionId}> with $devicesByUser ")
Timber.e("## CRYPTO | shareUserDevicesKey() : Failed to share session <${session.sessionId}> with $devicesByUser ")
}
} else {
Timber.i("## CRYPTO | shareUserDevicesKey() : no need to sharekey")
Timber.i("## CRYPTO | shareUserDevicesKey() : no need to sharekey")
}
if (noOlmToNotify.isNotEmpty()) {
@ -317,7 +317,7 @@ internal class MXMegolmEncryption(
sessionId: String,
senderKey: String?,
code: WithHeldCode) {
Timber.i("## CRYPTO | notifyKeyWithHeld() :sending withheld key for $targets session:$sessionId and code $code")
Timber.i("## CRYPTO | notifyKeyWithHeld() :sending withheld key for $targets session:$sessionId and code $code")
val withHeldContent = RoomKeyWithHeldContent(
roomId = roomId,
senderKey = senderKey,
@ -336,7 +336,7 @@ internal class MXMegolmEncryption(
try {
sendToDeviceTask.execute(params)
} catch (failure: Throwable) {
Timber.e("## CRYPTO | notifyKeyWithHeld() : Failed to notify withheld key for $targets session: $sessionId ")
Timber.e("## CRYPTO | notifyKeyWithHeld() : Failed to notify withheld key for $targets session: $sessionId ")
}
}
@ -473,7 +473,7 @@ internal class MXMegolmEncryption(
val encodedPayload = messageEncrypter.encryptMessage(payloadJson, listOf(deviceInfo))
val sendToDeviceMap = MXUsersDevicesMap<Any>()
sendToDeviceMap.setObject(userId, deviceId, encodedPayload)
Timber.i("## CRYPTO | reshareKey() : sending session $sessionId to $userId:$deviceId")
Timber.i("## CRYPTO | reshareKey() : sending session $sessionId to $userId:$deviceId")
val sendToDeviceParams = SendToDeviceTask.Params(EventType.ENCRYPTED, sendToDeviceMap)
return try {
sendToDeviceTask.execute(sendToDeviceParams)

View file

@ -57,7 +57,7 @@ object HkdfSha256 {
/*
The output OKM is calculated as follows:
Notation | -> When the message is composed of several elements we use concatenation (denoted |) in the second argument;
Notation | -> When the message is composed of several elements we use concatenation (denoted |) in the second argument;
N = ceil(L/HashLen)

View file

@ -345,7 +345,7 @@ internal abstract class SASDefaultVerificationTransaction(
}
protected fun hashUsingAgreedHashMethod(toHash: String): String? {
if ("sha256" == accepted?.hash?.toLowerCase(Locale.ROOT)) {
if ("sha256" == accepted?.hash?.lowercase(Locale.ROOT)) {
val olmUtil = OlmUtility()
val hashBytes = olmUtil.sha256(toHash)
olmUtil.releaseUtility()
@ -355,7 +355,7 @@ internal abstract class SASDefaultVerificationTransaction(
}
private fun macUsingAgreedMethod(message: String, info: String): String? {
return when (accepted?.messageAuthenticationCode?.toLowerCase(Locale.ROOT)) {
return when (accepted?.messageAuthenticationCode?.lowercase(Locale.ROOT)) {
SAS_MAC_SHA256_LONGKDF -> getSAS().calculateMacLongKdf(message, info)
SAS_MAC_SHA256 -> getSAS().calculateMac(message, info)
else -> null

View file

@ -48,7 +48,7 @@ fun QrCodeData.toEncodedString(): String {
// TransactionId
transactionId.forEach {
result += it.toByte()
result += it.code.toByte()
}
// Keys

View file

@ -71,7 +71,7 @@ internal class RealmKeysUtils @Inject constructor(context: Context,
val encodedKey = Base64.encodeToString(key, Base64.NO_PADDING)
val toStore = secretStoringUtils.securelyStoreString(encodedKey, alias)
sharedPreferences.edit {
putString("${ENCRYPTED_KEY_PREFIX}_$alias", Base64.encodeToString(toStore!!, Base64.NO_PADDING))
putString("${ENCRYPTED_KEY_PREFIX}_$alias", Base64.encodeToString(toStore, Base64.NO_PADDING))
}
return key
}
@ -84,7 +84,7 @@ internal class RealmKeysUtils @Inject constructor(context: Context,
val encryptedB64 = sharedPreferences.getString("${ENCRYPTED_KEY_PREFIX}_$alias", null)
val encryptedKey = Base64.decode(encryptedB64, Base64.NO_PADDING)
val b64 = secretStoringUtils.loadSecureSecret(encryptedKey, alias)
return Base64.decode(b64!!, Base64.NO_PADDING)
return Base64.decode(b64, Base64.NO_PADDING)
}
fun configureEncryption(realmConfigurationBuilder: RealmConfiguration.Builder, alias: String) {

View file

@ -49,7 +49,7 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
const val SESSION_STORE_SCHEMA_SC_VERSION = 2L
const val SESSION_STORE_SCHEMA_SC_VERSION_OFFSET = (1L shl 12)
const val SESSION_STORE_SCHEMA_VERSION = 12L +
const val SESSION_STORE_SCHEMA_VERSION = 13L +
SESSION_STORE_SCHEMA_SC_VERSION * SESSION_STORE_SCHEMA_SC_VERSION_OFFSET
}
@ -71,6 +71,7 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
if (oldVersion <= 9) migrateTo10(realm)
if (oldVersion <= 10) migrateTo11(realm)
if (oldVersion <= 11) migrateTo12(realm)
if (oldVersion <= 12) migrateTo13(realm)
if (oldScVersion <= 0) migrateToSc1(realm)
if (oldScVersion <= 1) migrateToSc2(realm)
@ -302,4 +303,14 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
?.addField(SpaceChildSummaryEntityFields.SUGGESTED, Boolean::class.java)
?.setNullable(SpaceChildSummaryEntityFields.SUGGESTED, true)
}
private fun migrateTo13(realm: DynamicRealm) {
Timber.d("Step 12 -> 13")
// Fix issue with the nightly build. Eventually play again the migration which has been included in migrateTo12()
realm.schema.get("SpaceChildSummaryEntity")
?.takeIf { !it.hasField(SpaceChildSummaryEntityFields.SUGGESTED) }
?.addField(SpaceChildSummaryEntityFields.SUGGESTED, Boolean::class.java)
?.setNullable(SpaceChildSummaryEntityFields.SUGGESTED, true)
}
}

View file

@ -102,7 +102,9 @@ internal class RoomSummaryMapper @Inject constructor(private val timelineEventMa
autoJoin = it.autoJoin ?: false,
viaServers = it.viaServers.toList(),
parentRoomId = roomSummaryEntity.roomId,
suggested = it.suggested
suggested = it.suggested,
canonicalAlias = it.childSummaryEntity?.canonicalAlias,
aliases = it.childSummaryEntity?.aliases?.toList()
)
},
flattenParentIds = roomSummaryEntity.flattenParentIds?.split("|") ?: emptyList()

View file

@ -36,6 +36,7 @@ import org.matrix.android.sdk.internal.session.TestInterceptor
import org.matrix.android.sdk.internal.task.TaskExecutor
import org.matrix.android.sdk.internal.util.BackgroundDetectionObserver
import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers
import org.matrix.android.sdk.internal.util.system.SystemModule
import org.matrix.olm.OlmManager
import java.io.File
@ -44,6 +45,7 @@ import java.io.File
NetworkModule::class,
AuthModule::class,
RawModule::class,
SystemModule::class,
NoOpTestModule::class
])
@MatrixScope

View file

@ -291,7 +291,7 @@ internal class DefaultFileService @Inject constructor(
Timber.v("Get size of ${it.absolutePath}")
true
}
.sumBy { it.length().toInt() }
.sumOf { it.length().toInt() }
}
override fun clearCache() {

View file

@ -43,6 +43,7 @@ import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilitiesServi
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.openid.OpenIdService
import org.matrix.android.sdk.api.session.permalinks.PermalinkService
import org.matrix.android.sdk.api.session.profile.ProfileService
import org.matrix.android.sdk.api.session.pushers.PushersService
@ -125,6 +126,7 @@ internal class DefaultSession @Inject constructor(
private val thirdPartyService: Lazy<ThirdPartyService>,
private val callSignalingService: Lazy<CallSignalingService>,
private val spaceService: Lazy<SpaceService>,
private val openIdService: Lazy<OpenIdService>,
@UnauthenticatedWithCertificate
private val unauthenticatedWithCertificateOkHttpClient: Lazy<OkHttpClient>
) : Session,
@ -289,6 +291,8 @@ internal class DefaultSession @Inject constructor(
override fun spaceService(): SpaceService = spaceService.get()
override fun openIdService(): OpenIdService = openIdService.get()
override fun getOkHttpClient(): OkHttpClient {
return unauthenticatedWithCertificateOkHttpClient.get()
}

View file

@ -64,6 +64,7 @@ import org.matrix.android.sdk.internal.session.user.accountdata.AccountDataModul
import org.matrix.android.sdk.internal.session.widgets.WidgetModule
import org.matrix.android.sdk.internal.task.TaskExecutor
import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers
import org.matrix.android.sdk.internal.util.system.SystemModule
@Component(dependencies = [MatrixComponent::class],
modules = [
@ -80,6 +81,7 @@ import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers
CacheModule::class,
MediaModule::class,
CryptoModule::class,
SystemModule::class,
PushersModule::class,
OpenIdModule::class,
WidgetModule::class,

View file

@ -38,6 +38,7 @@ import org.matrix.android.sdk.api.session.accountdata.AccountDataService
import org.matrix.android.sdk.api.session.events.EventService
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilitiesService
import org.matrix.android.sdk.api.session.initsync.InitialSyncProgressService
import org.matrix.android.sdk.api.session.openid.OpenIdService
import org.matrix.android.sdk.api.session.permalinks.PermalinkService
import org.matrix.android.sdk.api.session.securestorage.SecureStorageService
import org.matrix.android.sdk.api.session.securestorage.SharedSecretStorageService
@ -82,6 +83,7 @@ import org.matrix.android.sdk.internal.session.homeserver.DefaultHomeServerCapab
import org.matrix.android.sdk.internal.session.identity.DefaultIdentityService
import org.matrix.android.sdk.internal.session.initsync.DefaultInitialSyncProgressService
import org.matrix.android.sdk.internal.session.integrationmanager.IntegrationManager
import org.matrix.android.sdk.internal.session.openid.DefaultOpenIdService
import org.matrix.android.sdk.internal.session.permalinks.DefaultPermalinkService
import org.matrix.android.sdk.internal.session.room.EventRelationsAggregationProcessor
import org.matrix.android.sdk.internal.session.room.create.RoomCreateEventProcessor
@ -373,6 +375,9 @@ internal abstract class SessionModule {
@Binds
abstract fun bindPermalinkService(service: DefaultPermalinkService): PermalinkService
@Binds
abstract fun bindOpenIdTokenService(service: DefaultOpenIdService): OpenIdService
@Binds
abstract fun bindTypingUsersTracker(tracker: DefaultTypingUsersTracker): TypingUsersTracker

View file

@ -47,14 +47,15 @@ import java.io.FileNotFoundException
import java.io.IOException
import javax.inject.Inject
internal class FileUploader @Inject constructor(@Authenticated
private val okHttpClient: OkHttpClient,
private val globalErrorReceiver: GlobalErrorReceiver,
private val homeServerCapabilitiesService: DefaultHomeServerCapabilitiesService,
private val context: Context,
private val temporaryFileCreator: TemporaryFileCreator,
contentUrlResolver: ContentUrlResolver,
moshi: Moshi) {
internal class FileUploader @Inject constructor(
@Authenticated private val okHttpClient: OkHttpClient,
private val globalErrorReceiver: GlobalErrorReceiver,
private val homeServerCapabilitiesService: DefaultHomeServerCapabilitiesService,
private val context: Context,
private val temporaryFileCreator: TemporaryFileCreator,
contentUrlResolver: ContentUrlResolver,
moshi: Moshi
) {
private val uploadUrl = contentUrlResolver.uploadUrl
private val responseAdapter = moshi.adapter(ContentUploadResponse::class.java)
@ -120,11 +121,17 @@ internal class FileUploader @Inject constructor(@Authenticated
}
}
private suspend fun upload(uploadBody: RequestBody, filename: String?, progressListener: ProgressRequestBody.Listener?): ContentUploadResponse {
private suspend fun upload(uploadBody: RequestBody,
filename: String?,
progressListener: ProgressRequestBody.Listener?): ContentUploadResponse {
val urlBuilder = uploadUrl.toHttpUrlOrNull()?.newBuilder() ?: throw RuntimeException()
val httpUrl = urlBuilder
.addQueryParameter("filename", filename)
.apply {
if (filename != null) {
addQueryParameter("filename", filename)
}
}
.build()
val requestBody = if (progressListener != null) ProgressRequestBody(uploadBody, progressListener) else uploadBody

View file

@ -229,7 +229,6 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
val encryptedFile: File?
val contentUploadResponse = if (params.isEncrypted) {
Timber.v("## Encrypt file")
encryptedFile = temporaryFileCreator.create()
.also { filesToDelete.add(it) }
@ -239,16 +238,22 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
contentUploadStateTracker.setEncrypting(it, read.toLong(), total.toLong())
}
}
Timber.v("## Uploading file")
fileUploader
.uploadFile(encryptedFile, attachment.name, MimeTypes.OctetStream, progressListener)
fileUploader.uploadFile(
file = encryptedFile,
filename = null,
mimeType = MimeTypes.OctetStream,
progressListener = progressListener
)
} else {
Timber.v("## Clear file")
Timber.v("## Uploading clear file")
encryptedFile = null
fileUploader
.uploadFile(fileToUpload, attachment.name, attachment.getSafeMimeType(), progressListener)
fileUploader.uploadFile(
file = fileToUpload,
filename = attachment.name,
mimeType = attachment.getSafeMimeType(),
progressListener = progressListener
)
}
Timber.v("## Update cache storage for ${contentUploadResponse.contentUri}")
@ -312,7 +317,7 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
val encryptionResult = MXEncryptedAttachments.encryptAttachment(thumbnailData.bytes.inputStream(), thumbnailData.mimeType)
val contentUploadResponse = fileUploader.uploadByteArray(
byteArray = encryptionResult.encryptedByteArray,
filename = "thumb_${params.attachment.name}",
filename = null,
mimeType = MimeTypes.OctetStream,
progressListener = thumbnailProgressListener
)

View file

@ -16,9 +16,9 @@
package org.matrix.android.sdk.internal.session.identity
import org.matrix.android.sdk.api.session.openid.OpenIdToken
import org.matrix.android.sdk.internal.network.NetworkConstants
import org.matrix.android.sdk.internal.session.identity.model.IdentityRegisterResponse
import org.matrix.android.sdk.internal.session.openid.RequestOpenIdTokenResponse
import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.POST
@ -52,5 +52,5 @@ internal interface IdentityAuthAPI {
* The request body is the same as the values returned by /openid/request_token in the Client-Server API.
*/
@POST(NetworkConstants.URI_IDENTITY_PATH_V2 + "account/register")
suspend fun register(@Body openIdToken: RequestOpenIdTokenResponse): IdentityRegisterResponse
suspend fun register(@Body openIdToken: OpenIdToken): IdentityRegisterResponse
}

View file

@ -117,7 +117,7 @@ internal class DefaultIdentityBulkLookupTask @Inject constructor(
return withOlmUtility { olmUtility ->
threePids.map { threePid ->
base64ToBase64Url(
olmUtility.sha256(threePid.value.toLowerCase(Locale.ROOT)
olmUtility.sha256(threePid.value.lowercase(Locale.ROOT)
+ " " + threePid.toMedium() + " " + pepper)
)
}

View file

@ -16,16 +16,16 @@
package org.matrix.android.sdk.internal.session.identity
import org.matrix.android.sdk.api.session.openid.OpenIdToken
import org.matrix.android.sdk.internal.network.executeRequest
import org.matrix.android.sdk.internal.session.identity.model.IdentityRegisterResponse
import org.matrix.android.sdk.internal.session.openid.RequestOpenIdTokenResponse
import org.matrix.android.sdk.internal.task.Task
import javax.inject.Inject
internal interface IdentityRegisterTask : Task<IdentityRegisterTask.Params, IdentityRegisterResponse> {
data class Params(
val identityAuthAPI: IdentityAuthAPI,
val openIdTokenResponse: RequestOpenIdTokenResponse
val openIdToken: OpenIdToken
)
}
@ -33,7 +33,7 @@ internal class DefaultIdentityRegisterTask @Inject constructor() : IdentityRegis
override suspend fun execute(params: IdentityRegisterTask.Params): IdentityRegisterResponse {
return executeRequest(null) {
params.identityAuthAPI.register(params.openIdTokenResponse)
params.identityAuthAPI.register(params.openIdToken)
}
}
}

View file

@ -0,0 +1,28 @@
/*
* 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.internal.session.openid
import org.matrix.android.sdk.api.session.openid.OpenIdService
import org.matrix.android.sdk.api.session.openid.OpenIdToken
import javax.inject.Inject
internal class DefaultOpenIdService @Inject constructor(private val getOpenIdTokenTask: GetOpenIdTokenTask): OpenIdService {
override suspend fun getOpenIdToken(): OpenIdToken {
return getOpenIdTokenTask.execute(Unit)
}
}

View file

@ -16,20 +16,21 @@
package org.matrix.android.sdk.internal.session.openid
import org.matrix.android.sdk.api.session.openid.OpenIdToken
import org.matrix.android.sdk.internal.di.UserId
import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
import org.matrix.android.sdk.internal.network.executeRequest
import org.matrix.android.sdk.internal.task.Task
import javax.inject.Inject
internal interface GetOpenIdTokenTask : Task<Unit, RequestOpenIdTokenResponse>
internal interface GetOpenIdTokenTask : Task<Unit, OpenIdToken>
internal class DefaultGetOpenIdTokenTask @Inject constructor(
@UserId private val userId: String,
private val openIdAPI: OpenIdAPI,
private val globalErrorReceiver: GlobalErrorReceiver) : GetOpenIdTokenTask {
override suspend fun execute(params: Unit): RequestOpenIdTokenResponse {
override suspend fun execute(params: Unit): OpenIdToken {
return executeRequest(globalErrorReceiver) {
openIdAPI.openIdToken(userId)
}

View file

@ -16,6 +16,7 @@
package org.matrix.android.sdk.internal.session.openid
import org.matrix.android.sdk.api.session.openid.OpenIdToken
import org.matrix.android.sdk.api.util.JsonDict
import org.matrix.android.sdk.internal.network.NetworkConstants
import retrofit2.http.Body
@ -34,5 +35,5 @@ internal interface OpenIdAPI {
*/
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "user/{userId}/openid/request_token")
suspend fun openIdToken(@Path("userId") userId: String,
@Body body: JsonDict = emptyMap()): RequestOpenIdTokenResponse
@Body body: JsonDict = emptyMap()): OpenIdToken
}

View file

@ -358,10 +358,9 @@ internal class RoomSummaryUpdater @Inject constructor(
if (it != null) addAll(it)
}
}.distinct()
if (flattenRelated.isEmpty()) {
dmRoom.flattenParentIds = null
} else {
dmRoom.flattenParentIds = "|${flattenRelated.joinToString("|")}|"
if (flattenRelated.isNotEmpty()) {
// we keep real m.child/m.parent relations and add the one for common memberships
dmRoom.flattenParentIds += "|${flattenRelated.joinToString("|")}|"
}
// Timber.v("## SPACES: flatten of ${dmRoom.otherMemberIds.joinToString(",")} is ${dmRoom.flattenParentIds}")
}

View file

@ -18,12 +18,14 @@
package org.matrix.android.sdk.internal.session.securestorage
import android.annotation.SuppressLint
import android.content.Context
import android.os.Build
import android.security.KeyPairGeneratorSpec
import android.security.keystore.KeyGenParameterSpec
import android.security.keystore.KeyProperties
import androidx.annotation.RequiresApi
import org.matrix.android.sdk.internal.util.system.BuildVersionSdkIntProvider
import timber.log.Timber
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
@ -32,6 +34,7 @@ import java.io.InputStream
import java.io.ObjectInputStream
import java.io.ObjectOutputStream
import java.io.OutputStream
import java.lang.IllegalArgumentException
import java.math.BigInteger
import java.security.KeyPairGenerator
import java.security.KeyStore
@ -58,23 +61,19 @@ import javax.security.auth.x500.X500Principal
* is not available.
*
* <b>Android [K-M[</b>
* For android >=KITKAT and <M, we use the keystore to generate and store a private/public key pair. Then for each secret, a
* For android >=L and <M, we use the keystore to generate and store a private/public key pair. Then for each secret, a
* random secret key in generated to perform encryption.
* This secret key is encrypted with the public RSA key and stored with the encrypted secret.
* In order to decrypt the encrypted secret key will be retrieved then decrypted with the RSA private key.
*
* <b>Older androids</b>
* For older androids as a fallback we generate an AES key from the alias using PBKDF2 with random salt.
* The salt and iv are stored with encrypted data.
*
* Sample usage:
* <code>
* val secret = "The answer is 42"
* val KEncrypted = SecretStoringUtils.securelyStoreString(secret, "myAlias", context)
* val KEncrypted = SecretStoringUtils.securelyStoreString(secret, "myAlias")
* //This can be stored anywhere e.g. encoded in b64 and stored in preference for example
*
* //to get back the secret, just call
* val kDecrypted = SecretStoringUtils.loadSecureSecret(KEncrypted!!, "myAlias", context)
* val kDecrypted = SecretStoringUtils.loadSecureSecret(KEncrypted, "myAlias")
* </code>
*
* You can also just use this utility to store a secret key, and use any encryption algorithm that you want.
@ -82,7 +81,10 @@ import javax.security.auth.x500.X500Principal
* Important: Keys stored in the keystore can be wiped out (depends of the OS version, like for example if you
* add a pin or change the schema); So you might and with a useless pile of bytes.
*/
internal class SecretStoringUtils @Inject constructor(private val context: Context) {
internal class SecretStoringUtils @Inject constructor(
private val context: Context,
private val buildVersionSdkIntProvider: BuildVersionSdkIntProvider
) {
companion object {
private const val ANDROID_KEY_STORE = "AndroidKeyStore"
@ -91,7 +93,6 @@ internal class SecretStoringUtils @Inject constructor(private val context: Conte
private const val FORMAT_API_M: Byte = 0
private const val FORMAT_1: Byte = 1
private const val FORMAT_2: Byte = 2
}
private val keyStore: KeyStore by lazy {
@ -113,43 +114,52 @@ internal class SecretStoringUtils @Inject constructor(private val context: Conte
/**
* Encrypt the given secret using the android Keystore.
* On android >= M, will directly use the keystore to generate a symmetric key
* On android >= KitKat and <M, as symmetric key gen is not available, will use an symmetric key generated
* On android >= Lollipop and <M, as symmetric key gen is not available, will use an symmetric key generated
* in the keystore to encrypted a random symmetric key. The encrypted symmetric key is returned
* in the bytearray (in can be stored anywhere, it is encrypted)
* On older version a key in generated from alias with random salt.
*
* The secret is encrypted using the following method: AES/GCM/NoPadding
*/
@SuppressLint("NewApi")
@Throws(Exception::class)
fun securelyStoreString(secret: String, keyAlias: String): ByteArray? {
fun securelyStoreString(secret: String, keyAlias: String): ByteArray {
return when {
Build.VERSION.SDK_INT >= Build.VERSION_CODES.M -> encryptStringM(secret, keyAlias)
else -> encryptString(secret, keyAlias)
buildVersionSdkIntProvider.get() >= Build.VERSION_CODES.M -> encryptStringM(secret, keyAlias)
else -> encryptString(secret, keyAlias)
}
}
/**
* Decrypt a secret that was encrypted by #securelyStoreString()
*/
@SuppressLint("NewApi")
@Throws(Exception::class)
fun loadSecureSecret(encrypted: ByteArray, keyAlias: String): String? {
return when {
Build.VERSION.SDK_INT >= Build.VERSION_CODES.M -> decryptStringM(encrypted, keyAlias)
else -> decryptString(encrypted, keyAlias)
fun loadSecureSecret(encrypted: ByteArray, keyAlias: String): String {
encrypted.inputStream().use { inputStream ->
// First get the format
return when (val format = inputStream.read().toByte()) {
FORMAT_API_M -> decryptStringM(inputStream, keyAlias)
FORMAT_1 -> decryptString(inputStream, keyAlias)
else -> throw IllegalArgumentException("Unknown format $format")
}
}
}
@SuppressLint("NewApi")
fun securelyStoreObject(any: Any, keyAlias: String, output: OutputStream) {
when {
Build.VERSION.SDK_INT >= Build.VERSION_CODES.M -> saveSecureObjectM(keyAlias, output, any)
else -> saveSecureObject(keyAlias, output, any)
buildVersionSdkIntProvider.get() >= Build.VERSION_CODES.M -> saveSecureObjectM(keyAlias, output, any)
else -> saveSecureObject(keyAlias, output, any)
}
}
@SuppressLint("NewApi")
fun <T> loadSecureSecret(inputStream: InputStream, keyAlias: String): T? {
return when {
Build.VERSION.SDK_INT >= Build.VERSION_CODES.M -> loadSecureObjectM(keyAlias, inputStream)
else -> loadSecureObject(keyAlias, inputStream)
// First get the format
return when (val format = inputStream.read().toByte()) {
FORMAT_API_M -> loadSecureObjectM(keyAlias, inputStream)
FORMAT_1 -> loadSecureObject(keyAlias, inputStream)
else -> throw IllegalArgumentException("Unknown format $format")
}
}
@ -180,7 +190,7 @@ internal class SecretStoringUtils @Inject constructor(private val context: Conte
- Store the encrypted AES
Generate a key pair for encryption
*/
fun getOrGenerateKeyPairForAlias(alias: String): KeyStore.PrivateKeyEntry {
private fun getOrGenerateKeyPairForAlias(alias: String): KeyStore.PrivateKeyEntry {
val privateKeyEntry = (keyStore.getEntry(alias, null) as? KeyStore.PrivateKeyEntry)
if (privateKeyEntry != null) return privateKeyEntry
@ -193,7 +203,7 @@ internal class SecretStoringUtils @Inject constructor(private val context: Conte
.setAlias(alias)
.setSubject(X500Principal("CN=$alias"))
.setSerialNumber(BigInteger.TEN)
// .setEncryptionRequired() requires that the phone as a pin/schema
// .setEncryptionRequired() requires that the phone has a pin/schema
.setStartDate(start.time)
.setEndDate(end.time)
.build()
@ -205,7 +215,7 @@ internal class SecretStoringUtils @Inject constructor(private val context: Conte
}
@RequiresApi(Build.VERSION_CODES.M)
fun encryptStringM(text: String, keyAlias: String): ByteArray? {
private fun encryptStringM(text: String, keyAlias: String): ByteArray {
val secretKey = getOrGenerateSymmetricKeyForAliasM(keyAlias)
val cipher = Cipher.getInstance(AES_MODE)
@ -217,8 +227,8 @@ internal class SecretStoringUtils @Inject constructor(private val context: Conte
}
@RequiresApi(Build.VERSION_CODES.M)
private fun decryptStringM(encryptedChunk: ByteArray, keyAlias: String): String {
val (iv, encryptedText) = formatMExtract(encryptedChunk.inputStream())
private fun decryptStringM(inputStream: InputStream, keyAlias: String): String {
val (iv, encryptedText) = formatMExtract(inputStream)
val secretKey = getOrGenerateSymmetricKeyForAliasM(keyAlias)
@ -229,7 +239,7 @@ internal class SecretStoringUtils @Inject constructor(private val context: Conte
return String(cipher.doFinal(encryptedText), Charsets.UTF_8)
}
private fun encryptString(text: String, keyAlias: String): ByteArray? {
private fun encryptString(text: String, keyAlias: String): ByteArray {
// we generate a random symmetric key
val key = ByteArray(16)
secureRandom.nextBytes(key)
@ -246,8 +256,8 @@ internal class SecretStoringUtils @Inject constructor(private val context: Conte
return format1Make(encryptedKey, iv, encryptedBytes)
}
private fun decryptString(data: ByteArray, keyAlias: String): String? {
val (encryptedKey, iv, encrypted) = format1Extract(ByteArrayInputStream(data))
private fun decryptString(inputStream: InputStream, keyAlias: String): String {
val (encryptedKey, iv, encrypted) = format1Extract(inputStream)
// we need to decrypt the key
val sKeyBytes = rsaDecrypt(keyAlias, ByteArrayInputStream(encryptedKey))
@ -307,30 +317,11 @@ internal class SecretStoringUtils @Inject constructor(private val context: Conte
output.write(bos1.toByteArray())
}
// @RequiresApi(Build.VERSION_CODES.M)
// @Throws(IOException::class)
// fun saveSecureObjectM(keyAlias: String, file: File, writeObject: Any) {
// FileOutputStream(file).use {
// saveSecureObjectM(keyAlias, it, writeObject)
// }
// }
//
// @RequiresApi(Build.VERSION_CODES.M)
// @Throws(IOException::class)
// fun <T> loadSecureObjectM(keyAlias: String, file: File): T? {
// FileInputStream(file).use {
// return loadSecureObjectM<T>(keyAlias, it)
// }
// }
@RequiresApi(Build.VERSION_CODES.M)
@Throws(IOException::class)
private fun <T> loadSecureObjectM(keyAlias: String, inputStream: InputStream): T? {
val secretKey = getOrGenerateSymmetricKeyForAliasM(keyAlias)
val format = inputStream.read()
assert(format.toByte() == FORMAT_API_M)
val ivSize = inputStream.read()
val iv = ByteArray(ivSize)
inputStream.read(iv, 0, ivSize)
@ -393,9 +384,6 @@ internal class SecretStoringUtils @Inject constructor(private val context: Conte
}
private fun formatMExtract(bis: InputStream): Pair<ByteArray, ByteArray> {
val format = bis.read().toByte()
assert(format == FORMAT_API_M)
val ivSize = bis.read()
val iv = ByteArray(ivSize)
bis.read(iv, 0, ivSize)
@ -414,9 +402,6 @@ internal class SecretStoringUtils @Inject constructor(private val context: Conte
}
private fun format1Extract(bis: InputStream): Triple<ByteArray, ByteArray, ByteArray> {
val format = bis.read()
assert(format.toByte() == FORMAT_1)
val keySizeBig = bis.read()
val keySizeLow = bis.read()
val encryptedKeySize = keySizeBig.shl(8) + keySizeLow
@ -443,32 +428,4 @@ internal class SecretStoringUtils @Inject constructor(private val context: Conte
return bos.toByteArray()
}
private fun format2Make(salt: ByteArray, iv: ByteArray, encryptedBytes: ByteArray): ByteArray {
val bos = ByteArrayOutputStream(3 + salt.size + iv.size + encryptedBytes.size)
bos.write(FORMAT_2.toInt())
bos.write(salt.size)
bos.write(salt)
bos.write(iv.size)
bos.write(iv)
bos.write(encryptedBytes)
return bos.toByteArray()
}
private fun format2Extract(bis: InputStream): Triple<ByteArray, ByteArray, ByteArray> {
val format = bis.read()
assert(format.toByte() == FORMAT_2)
val saltSize = bis.read()
val salt = ByteArray(saltSize)
bis.read(salt)
val ivSize = bis.read()
val iv = ByteArray(ivSize)
bis.read(iv)
val encrypted = bis.readBytes()
return Triple(salt, iv, encrypted)
}
}

View file

@ -81,7 +81,6 @@ internal class DefaultSpaceService @Inject constructor(
} else {
this.preset = CreateRoomPreset.PRESET_PRIVATE_CHAT
visibility = RoomDirectoryVisibility.PRIVATE
enableEncryption()
}
})
}
@ -146,7 +145,9 @@ internal class DefaultSpaceService @Inject constructor(
viaServers = childStateEvContent.via.orEmpty(),
activeMemberCount = childSummary.numJoinedMembers,
parentRoomId = childStateEv.roomId,
suggested = childStateEvContent.suggested
suggested = childStateEvContent.suggested,
canonicalAlias = childSummary.canonicalAlias,
aliases = childSummary.aliases
)
}
}.orEmpty()

View file

@ -83,7 +83,7 @@ internal class DefaultJoinSpaceTask @Inject constructor(
Timber.v("## Space: > Sync done ...")
// after that i should have the children (? do I need to paginate to get state)
val summary = roomSummaryDataSource.getSpaceSummary(params.roomIdOrAlias)
Timber.v("## Space: Found space summary Name:[${summary?.name}] children: ${summary?.spaceChildren?.size}")
Timber.v("## Space: Found space summary Name:[${summary?.name}] children: ${summary?.spaceChildren?.size}")
summary?.spaceChildren?.forEach {
// val childRoomSummary = it.roomSummary ?: return@forEach
Timber.v("## Space: Processing child :[${it.childRoomId}] autoJoin:${it.autoJoin}")

View file

@ -64,7 +64,7 @@ internal class CryptoSyncHandler @Inject constructor(private val cryptoService:
* @return true if the event has been decrypted
*/
private fun decryptToDeviceEvent(event: Event, timelineId: String?): Boolean {
Timber.v("## CRYPTO | decryptToDeviceEvent")
Timber.v("## CRYPTO | decryptToDeviceEvent")
if (event.getClearType() == EventType.ENCRYPTED) {
var result: MXEventDecryptionResult? = null
try {
@ -76,7 +76,7 @@ internal class CryptoSyncHandler @Inject constructor(private val cryptoService:
val deviceId = cryptoService.getCryptoDeviceInfo(event.senderId!!).firstOrNull {
it.identityKey() == senderKey
}?.deviceId ?: senderKey
Timber.e("## CRYPTO | Failed to decrypt to device event from ${event.senderId}|$deviceId reason:<${event.mCryptoError ?: exception}>")
Timber.e("## CRYPTO | Failed to decrypt to device event from ${event.senderId}|$deviceId reason:<${event.mCryptoError ?: exception}>")
}
if (null != result) {
@ -89,7 +89,7 @@ internal class CryptoSyncHandler @Inject constructor(private val cryptoService:
return true
} else {
// should not happen
Timber.e("## CRYPTO | ERROR NULL DECRYPTION RESULT from ${event.senderId}")
Timber.e("## CRYPTO | ERROR NULL DECRYPTION RESULT from ${event.senderId}")
}
}

View file

@ -15,7 +15,7 @@
*/
package org.matrix.android.sdk.internal.session.widgets
import org.matrix.android.sdk.internal.session.openid.RequestOpenIdTokenResponse
import org.matrix.android.sdk.api.session.openid.OpenIdToken
import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.POST
@ -29,7 +29,7 @@ internal interface WidgetsAPI {
* @param body the body content (Ref: https://github.com/matrix-org/matrix-doc/pull/1961)
*/
@POST("register")
suspend fun register(@Body body: RequestOpenIdTokenResponse,
suspend fun register(@Body body: OpenIdToken,
@Query("v") version: String?): RegisterWidgetResponse
@GET("account")

View file

@ -27,7 +27,7 @@ fun String.md5() = try {
digest.update(toByteArray())
digest.digest()
.joinToString("") { String.format("%02X", it) }
.toLowerCase(Locale.ROOT)
.lowercase(Locale.ROOT)
} catch (exc: Exception) {
// Should not happen, but just in case
hashCode().toString()

View file

@ -17,6 +17,7 @@
package org.matrix.android.sdk.internal.util
import timber.log.Timber
import java.util.Locale
/**
* Convert a string to an UTF8 String
@ -24,7 +25,7 @@ import timber.log.Timber
* @param s the string to convert
* @return the utf-8 string
*/
fun convertToUTF8(s: String): String {
internal fun convertToUTF8(s: String): String {
return try {
val bytes = s.toByteArray(Charsets.UTF_8)
String(bytes)
@ -40,7 +41,7 @@ fun convertToUTF8(s: String): String {
* @param s the string to convert
* @return the utf-16 string
*/
fun convertFromUTF8(s: String): String {
internal fun convertFromUTF8(s: String): String {
return try {
val bytes = s.toByteArray()
String(bytes, Charsets.UTF_8)
@ -56,7 +57,7 @@ fun convertFromUTF8(s: String): String {
* @param subString the string to search for
* @return whether a match was found
*/
fun String.caseInsensitiveFind(subString: String): Boolean {
internal fun String.caseInsensitiveFind(subString: String): Boolean {
// add sanity checks
if (subString.isEmpty() || isEmpty()) {
return false
@ -78,3 +79,14 @@ internal val spaceChars = "[\u00A0\u2000-\u200B\u2800\u3000]".toRegex()
* Strip all the UTF-8 chars which are actually spaces
*/
internal fun String.replaceSpaceChars() = replace(spaceChars, "")
// String.capitalize is now deprecated
internal fun String.safeCapitalize(): String {
return replaceFirstChar { char ->
if (char.isLowerCase()) {
char.titlecase(Locale.getDefault())
} else {
char.toString()
}
}
}

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.internal.util.system
internal interface BuildVersionSdkIntProvider {
/**
* Return the current version of the Android SDK
*/
fun get(): Int
}

View file

@ -0,0 +1,25 @@
/*
* 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.internal.util.system
import android.os.Build
import javax.inject.Inject
internal class DefaultBuildVersionSdkIntProvider @Inject constructor()
: BuildVersionSdkIntProvider {
override fun get() = Build.VERSION.SDK_INT
}

View file

@ -0,0 +1,27 @@
/*
* 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.internal.util.system
import dagger.Binds
import dagger.Module
@Module
internal abstract class SystemModule {
@Binds
abstract fun bindBuildVersionSdkIntProvider(provider: DefaultBuildVersionSdkIntProvider): BuildVersionSdkIntProvider
}

View file

@ -42,7 +42,7 @@ android {
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'androidx.appcompat:appcompat:1.3.0'
implementation "androidx.fragment:fragment-ktx:1.3.3"
implementation 'androidx.exifinterface:exifinterface:1.3.2'

View file

@ -161,7 +161,7 @@ Formatter\.formatShortFileSize===1
# android\.text\.TextUtils
### This is not a rule, but a warning: the number of "enum class" has changed. For Json classes, it is mandatory that they have `@JsonClass(generateAdapter = false)`. If the enum is not used as a Json class, change the value in file forbidden_strings_in_code.txt
enum class===100
enum class===101
### Do not import temporary legacy classes
import org.matrix.android.sdk.internal.legacy.riot===3

View file

@ -17,7 +17,7 @@ PARAM_KEYSTORE_PATH=$1
PARAM_APK=$2
# Other params
BUILD_TOOLS_VERSION="29.0.3"
BUILD_TOOLS_VERSION="30.0.3"
MIN_SDK_VERSION=21
echo "Signing APK with build-tools version ${BUILD_TOOLS_VERSION} for min SDK version ${MIN_SDK_VERSION}..."

View file

@ -23,7 +23,7 @@ PARAM_KS_PASS=$3
PARAM_KEY_PASS=$4
# Other params
BUILD_TOOLS_VERSION="29.0.3"
BUILD_TOOLS_VERSION="30.0.3"
MIN_SDK_VERSION=21
echo "Signing APK with build-tools version ${BUILD_TOOLS_VERSION} for min SDK version ${MIN_SDK_VERSION}..."

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