Merge branch 'develop' into feature/fga/new_voip_design

This commit is contained in:
ganfra 2021-07-23 12:17:05 +02:00
commit 4788630949
263 changed files with 3619 additions and 1337 deletions

84
.github/ISSUE_TEMPLATE/release.md vendored Normal file
View file

@ -0,0 +1,84 @@
---
name: Release
about: Checklist for each release. To be used by the core team only.
title: "[Release] Element Android v"
labels: "\U0001F680 Release"
assignees: bmarty
---
For the example, we are releasing the version 1.1.10. Delete this line and replace 1.1.10 with the version in the issue content.
### Before the release
- [ ] Weblate sync, fix lint issue if any (in a dedicated PR)
- [ ] Check the update of the store descriptions (using Google Translate if necessary) to ensure that the changes are acceptable to be published to the stores.
- [ ] Run the script `./tools/release/pushPlayStoreMetaData.sh`. You can check in the GooglePlay console the Activity log to check the effect.
### Do the release
- [ ] Create release with gitflow, branch name `release/1.1.10`
- [ ] Run `./tools/import_emojis.py` and commit the change if any.
- [ ] Run `./tools/import_sas_strings.py` and commit the change if any. If there is no change since a while, ping Travis
- [ ] Check the crashes from the PlayStore
- [ ] Check the rageshake with the current dev version: https://github.com/matrix-org/element-android-rageshakes/labels/1.1.10-dev
- [ ] Run the integration test, and especially `UiAllScreensSanityTest.allScreensTest()`
- [ ] Create an account on matrix.org
- [ ] Run towncrier: `towncrier --version v1.1.10 --draft` (remove `--draft` do write the file CHANGES.md)
- [ ] Add file for fastlane under ./fastlane/metadata/android/en-US/changelogs
- [ ] Push the branch and start a draft PR (will not be merged), to check that the CI is happy with all the changes.
- [ ] Finish release with gitflow, delete the draft PR
- [ ] Push `main` and the new tag `v1.1.10` to origin
- [ ] Checkout `develop`
- [ ] Increase version in `./vector/build.gradle`
- [ ] Commit and push `develop`
- [ ] Wait for [Buildkite](https://buildkite.com/matrix-dot-org/element-android/builds?branch=main) to build the `main` branch.
- [ ] Run the script `~/scripts/releaseElement.sh`. It will download the APKs from Buildkite check them and sign them.
- [ ] Install the APK on your phone to check that the upgrade went well (no init sync, etc.)
- [ ] Create a new beta release on the GooglePlay console and upload the 4 signed Apks.
- [ ] Check that the version codes are correct
- [ ] Copy the fastlane change to the GooglePlay console in the section en-GB.
- [ ] Push to beta release to 100% of the users
- [ ] Create the release on gitHub [from the tag](https://github.com/vector-im/element-android/tags), copy paste the block from the file CHANGES.md
- [ ] Add the 4 signed APKs to the GitHub release
- [ ] Ping the Android Internal room
- [ ] Add an entry in the internal diary
### Once Live on PlayStore
- [ ] Ping the Android public room and update its topic
### After at least 2 days
- [ ] Check the [rageshakes](https://github.com/matrix-org/element-android-rageshakes/issues)
- [ ] Check the crash reports on the GooglePlay console
- [ ] Check the Android Element room for any reported issues on the new version
- [ ] If all is OK, push to production and notify Markus (Bubu) to release the F-Droid version
- [ ] Ping the Android public room and update its topic with the new available version
### Android SDK2
- [ ] Checkout the `main` branch on Element Android project
#### On the SDK2 project
https://github.com/matrix-org/matrix-android-sdk2
- [ ] Create a release with GitFlow
- [ ] Update the files `./build.gradle` and `./gradle/gradle-wrapper.properties` manually, to use the latest version for the dependency. You can get inspired by the same files on Element Android project.
- [ ] Run the script `./tools/import_from_element.sh`
- [ ] Update the version in `./matrix-sdk-android/build.gradle` and let the script finish to build the library
- [ ] Update the file `CHANGES.md`
- [ ] Finish the release using GitFlow
- [ ] Create the release on GitHub from [the tag](https://github.com/matrix-org/matrix-android-sdk2/tags)
- [ ] Upload the AAR on the GitHub release
### Android SDK2 sample
https://github.com/matrix-org/matrix-android-sdk2-sample
- [ ] Update the dependency to the new version of the SDK2. Jitpack will have to build the AAR, it can take a few minutes. You can check status on https://jitpack.io/#matrix-org/matrix-android-sdk2
- [ ] Build and run the sample, you may have to fix some API break
- [ ] Commit and push directly on `main`
<!-- Note: some scripts are not public because they contain some private keys -->

View file

@ -38,6 +38,7 @@
<w>unpublish</w> <w>unpublish</w>
<w>unwedging</w> <w>unwedging</w>
<w>vctr</w> <w>vctr</w>
<w>wellknown</w>
</words> </words>
</dictionary> </dictionary>
</component> </component>

View file

@ -1,3 +1,52 @@
Changes in Element 1.1.14 (2021-07-23)
======================================
Features ✨
----------
- Add low priority section in DM tab ([#3463](https://github.com/vector-im/element-android/issues/3463))
- Show missed call notification. ([#3710](https://github.com/vector-im/element-android/issues/3710))
Bugfixes 🐛
----------
- Don't use the transaction ID of the verification for the request ([#3589](https://github.com/vector-im/element-android/issues/3589))
- Avoid incomplete downloads in cache ([#3656](https://github.com/vector-im/element-android/issues/3656))
- Fix a crash which can happen when user signs out ([#3720](https://github.com/vector-im/element-android/issues/3720))
- Ensure OTKs are uploaded when the session is created ([#3724](https://github.com/vector-im/element-android/issues/3724))
SDK API changes ⚠️
------------------
- Add initialState support to CreateRoomParams (#3713) ([#3713](https://github.com/vector-im/element-android/issues/3713))
Other changes
-------------
- Apply grammatical fixes to the Server ACL timeline messages. ([#3721](https://github.com/vector-im/element-android/issues/3721))
- Add tags in the log, especially for VoIP, but can be used for other features in the future ([#3723](https://github.com/vector-im/element-android/issues/3723))
Changes in Element v1.1.13 (2021-07-19)
=======================================
Features ✨
----------
- Remove redundant mimetype (vector-im/element-web#2547) ([#3273](https://github.com/vector-im/element-android/issues/3273))
- Room version capabilities and room upgrade support, better error feedback ([#3551](https://github.com/vector-im/element-android/issues/3551))
- Add retry support in room addresses screen ([#3635](https://github.com/vector-im/element-android/issues/3635))
- Better management of permission requests ([#3667](https://github.com/vector-im/element-android/issues/3667))
Bugfixes 🐛
----------
- Standardise spelling and casing of homeserver, identity server, and integration manager. ([#491](https://github.com/vector-im/element-android/issues/491))
- Perform .well-known request first, even if the entered URL is a valid homeserver base url ([#2843](https://github.com/vector-im/element-android/issues/2843))
- Use different copy for self verification. ([#3624](https://github.com/vector-im/element-android/issues/3624))
- Crash when opening room addresses screen with no internet connection ([#3634](https://github.com/vector-im/element-android/issues/3634))
- Fix unread messages marker being hidden in collapsed membership item ([#3655](https://github.com/vector-im/element-android/issues/3655))
- Ensure reaction emoji picker tabs look fine on small displays ([#3661](https://github.com/vector-im/element-android/issues/3661))
SDK API changes ⚠️
------------------
- RawService.getWellknown() now takes a domain instead of a matrixId as parameter ([#3572](https://github.com/vector-im/element-android/issues/3572))
Changes in Element 1.1.12 (2021-07-05) Changes in Element 1.1.12 (2021-07-05)
====================================== ======================================

View file

@ -2,25 +2,25 @@ GEM
remote: https://rubygems.org/ remote: https://rubygems.org/
specs: specs:
CFPropertyList (3.0.3) CFPropertyList (3.0.3)
addressable (2.7.0) addressable (2.8.0)
public_suffix (>= 2.0.2, < 5.0) public_suffix (>= 2.0.2, < 5.0)
artifactory (3.0.15) artifactory (3.0.15)
atomos (0.1.3) atomos (0.1.3)
aws-eventstream (1.1.1) aws-eventstream (1.1.1)
aws-partitions (1.462.0) aws-partitions (1.479.0)
aws-sdk-core (3.114.0) aws-sdk-core (3.117.0)
aws-eventstream (~> 1, >= 1.0.2) aws-eventstream (~> 1, >= 1.0.2)
aws-partitions (~> 1, >= 1.239.0) aws-partitions (~> 1, >= 1.239.0)
aws-sigv4 (~> 1.1) aws-sigv4 (~> 1.1)
jmespath (~> 1.0) jmespath (~> 1.0)
aws-sdk-kms (1.43.0) aws-sdk-kms (1.44.0)
aws-sdk-core (~> 3, >= 3.112.0) aws-sdk-core (~> 3, >= 3.112.0)
aws-sigv4 (~> 1.1) aws-sigv4 (~> 1.1)
aws-sdk-s3 (1.95.1) aws-sdk-s3 (1.96.1)
aws-sdk-core (~> 3, >= 3.112.0) aws-sdk-core (~> 3, >= 3.112.0)
aws-sdk-kms (~> 1) aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.1) aws-sigv4 (~> 1.1)
aws-sigv4 (1.2.3) aws-sigv4 (1.2.4)
aws-eventstream (~> 1, >= 1.0.2) aws-eventstream (~> 1, >= 1.0.2)
babosa (1.0.4) babosa (1.0.4)
claide (1.0.3) claide (1.0.3)
@ -35,13 +35,15 @@ GEM
unf (>= 0.0.5, < 1.0.0) unf (>= 0.0.5, < 1.0.0)
dotenv (2.7.6) dotenv (2.7.6)
emoji_regex (3.2.2) emoji_regex (3.2.2)
excon (0.81.0) excon (0.85.0)
faraday (1.4.2) faraday (1.5.1)
faraday-em_http (~> 1.0) faraday-em_http (~> 1.0)
faraday-em_synchrony (~> 1.0) faraday-em_synchrony (~> 1.0)
faraday-excon (~> 1.1) faraday-excon (~> 1.1)
faraday-httpclient (~> 1.0.1)
faraday-net_http (~> 1.0) faraday-net_http (~> 1.0)
faraday-net_http_persistent (~> 1.1) faraday-net_http_persistent (~> 1.1)
faraday-patron (~> 1.0)
multipart-post (>= 1.2, < 3) multipart-post (>= 1.2, < 3)
ruby2_keywords (>= 0.0.4) ruby2_keywords (>= 0.0.4)
faraday-cookie_jar (0.0.7) faraday-cookie_jar (0.0.7)
@ -50,12 +52,14 @@ GEM
faraday-em_http (1.0.0) faraday-em_http (1.0.0)
faraday-em_synchrony (1.0.0) faraday-em_synchrony (1.0.0)
faraday-excon (1.1.0) faraday-excon (1.1.0)
faraday-httpclient (1.0.1)
faraday-net_http (1.0.1) faraday-net_http (1.0.1)
faraday-net_http_persistent (1.1.0) faraday-net_http_persistent (1.2.0)
faraday-patron (1.0.0)
faraday_middleware (1.0.0) faraday_middleware (1.0.0)
faraday (~> 1.0) faraday (~> 1.0)
fastimage (2.2.3) fastimage (2.2.4)
fastlane (2.184.0) fastlane (2.187.0)
CFPropertyList (>= 2.3, < 4.0.0) CFPropertyList (>= 2.3, < 4.0.0)
addressable (>= 2.3, < 3.0.0) addressable (>= 2.3, < 3.0.0)
artifactory (~> 3.0) artifactory (~> 3.0)
@ -94,37 +98,36 @@ GEM
xcpretty (~> 0.3.0) xcpretty (~> 0.3.0)
xcpretty-travis-formatter (>= 0.0.3) xcpretty-travis-formatter (>= 0.0.3)
gh_inspector (1.1.3) gh_inspector (1.1.3)
google-apis-androidpublisher_v3 (0.4.0) google-apis-androidpublisher_v3 (0.8.0)
google-apis-core (~> 0.1) google-apis-core (>= 0.4, < 2.a)
google-apis-core (0.3.0) google-apis-core (0.4.0)
addressable (~> 2.5, >= 2.5.1) addressable (~> 2.5, >= 2.5.1)
googleauth (~> 0.14) googleauth (>= 0.16.2, < 2.a)
httpclient (>= 2.8.1, < 3.0) httpclient (>= 2.8.1, < 3.a)
mini_mime (~> 1.0) mini_mime (~> 1.0)
representable (~> 3.0) representable (~> 3.0)
retriable (>= 2.0, < 4.0) retriable (>= 2.0, < 4.a)
rexml rexml
signet (~> 0.14)
webrick webrick
google-apis-iamcredentials_v1 (0.4.0) google-apis-iamcredentials_v1 (0.6.0)
google-apis-core (~> 0.1) google-apis-core (>= 0.4, < 2.a)
google-apis-playcustomapp_v1 (0.3.0) google-apis-playcustomapp_v1 (0.5.0)
google-apis-core (~> 0.1) google-apis-core (>= 0.4, < 2.a)
google-apis-storage_v1 (0.4.0) google-apis-storage_v1 (0.6.0)
google-apis-core (~> 0.1) google-apis-core (>= 0.4, < 2.a)
google-cloud-core (1.6.0) google-cloud-core (1.6.0)
google-cloud-env (~> 1.0) google-cloud-env (~> 1.0)
google-cloud-errors (~> 1.0) google-cloud-errors (~> 1.0)
google-cloud-env (1.5.0) google-cloud-env (1.5.0)
faraday (>= 0.17.3, < 2.0) faraday (>= 0.17.3, < 2.0)
google-cloud-errors (1.1.0) google-cloud-errors (1.1.0)
google-cloud-storage (1.31.1) google-cloud-storage (1.34.1)
addressable (~> 2.5) addressable (~> 2.5)
digest-crc (~> 0.4) digest-crc (~> 0.4)
google-apis-iamcredentials_v1 (~> 0.1) google-apis-iamcredentials_v1 (~> 0.1)
google-apis-storage_v1 (~> 0.1) google-apis-storage_v1 (~> 0.1)
google-cloud-core (~> 1.2) google-cloud-core (~> 1.6)
googleauth (~> 0.9) googleauth (>= 0.16.2, < 2.a)
mini_mime (~> 1.0) mini_mime (~> 1.0)
googleauth (0.16.2) googleauth (0.16.2)
faraday (>= 0.17.3, < 2.0) faraday (>= 0.17.3, < 2.0)
@ -134,7 +137,7 @@ GEM
os (>= 0.9, < 2.0) os (>= 0.9, < 2.0)
signet (~> 0.14) signet (~> 0.14)
highline (2.0.3) highline (2.0.3)
http-cookie (1.0.3) http-cookie (1.0.4)
domain_name (~> 0.5) domain_name (~> 0.5)
httpclient (2.8.3) httpclient (2.8.3)
jmespath (1.4.0) jmespath (1.4.0)
@ -150,7 +153,7 @@ GEM
os (1.1.1) os (1.1.1)
plist (3.6.0) plist (3.6.0)
public_suffix (4.0.6) public_suffix (4.0.6)
rake (13.0.3) rake (13.0.6)
representable (3.1.1) representable (3.1.1)
declarative (< 0.1.0) declarative (< 0.1.0)
trailblazer-option (>= 0.1.1, < 0.2.0) trailblazer-option (>= 0.1.1, < 0.2.0)
@ -158,8 +161,8 @@ GEM
retriable (3.1.2) retriable (3.1.2)
rexml (3.2.5) rexml (3.2.5)
rouge (2.0.7) rouge (2.0.7)
ruby2_keywords (0.0.4) ruby2_keywords (0.0.5)
rubyzip (2.3.0) rubyzip (2.3.2)
security (0.1.3) security (0.1.3)
signet (0.15.0) signet (0.15.0)
addressable (~> 2.3) addressable (~> 2.3)
@ -184,12 +187,13 @@ GEM
unicode-display_width (1.7.0) unicode-display_width (1.7.0)
webrick (1.7.0) webrick (1.7.0)
word_wrap (1.0.0) word_wrap (1.0.0)
xcodeproj (1.19.0) xcodeproj (1.20.0)
CFPropertyList (>= 2.3.3, < 4.0) CFPropertyList (>= 2.3.3, < 4.0)
atomos (~> 0.1.3) atomos (~> 0.1.3)
claide (>= 1.0.2, < 2.0) claide (>= 1.0.2, < 2.0)
colored2 (~> 3.1) colored2 (~> 3.1)
nanaimo (~> 0.3.0) nanaimo (~> 0.3.0)
rexml (~> 3.2.4)
xcpretty (0.3.0) xcpretty (0.3.0)
rouge (~> 2.0.7) rouge (~> 2.0.7)
xcpretty-travis-formatter (1.0.1) xcpretty-travis-formatter (1.0.1)

View file

@ -56,8 +56,8 @@ dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.6.0' implementation 'androidx.core:core-ktx:1.6.0'
implementation 'androidx.appcompat:appcompat:1.3.0' implementation 'androidx.appcompat:appcompat:1.3.1'
implementation "androidx.recyclerview:recyclerview:1.2.1" implementation "androidx.recyclerview:recyclerview:1.2.1"
implementation 'com.google.android.material:material:1.3.0' implementation 'com.google.android.material:material:1.4.0'
} }

View file

@ -2,7 +2,7 @@
buildscript { buildscript {
// Ref: https://kotlinlang.org/releases.html // Ref: https://kotlinlang.org/releases.html
ext.kotlin_version = '1.5.20' ext.kotlin_version = '1.5.21'
ext.kotlin_coroutines_version = "1.5.0" ext.kotlin_coroutines_version = "1.5.0"
repositories { repositories {
google() google()

View file

@ -1 +0,0 @@
Remove redundant mimetype (vector-im/element-web#2547)

View file

@ -4,7 +4,7 @@ Issue: #607
PR: #1354 PR: #1354
## Introduction ## Introduction
Identity Servers support contact discovery on Matrix by letting people look up Third Party Identifiers to see if the owner has publicly linked them with their Matrix ID. Identity servers support contact discovery on Matrix by letting people look up Third Party Identifiers to see if the owner has publicly linked them with their Matrix ID.
## Implementation ## Implementation
@ -87,6 +87,6 @@ This screen displays the identity server configuration and the binding of the us
This screen is a form to set a new identity server URL This screen is a form to set a new identity server URL
## Ref: ## Ref:
- https://matrix.org/blog/2019/09/27/privacy-improvements-in-synapse-1-4-and-riot-1-4 is a good summary of the role of an Identity server and the proper way to configure and use it in respect to the privacy and the consent of the user. - https://matrix.org/blog/2019/09/27/privacy-improvements-in-synapse-1-4-and-riot-1-4 is a good summary of the role of an identity server and the proper way to configure and use it in respect to the privacy and the consent of the user.
- API documentation: https://matrix.org/docs/spec/identity_service/latest - API documentation: https://matrix.org/docs/spec/identity_service/latest
- vector.im TOS: https://vector.im/identity-server-privacy-notice - vector.im TOS: https://vector.im/identity-server-privacy-notice

View file

@ -2,11 +2,11 @@ This document aims to describe how Element android displays notifications to the
# Table of Contents # Table of Contents
1. [Prerequisites Knowledge](#prerequisites-knowledge) 1. [Prerequisites Knowledge](#prerequisites-knowledge)
* [How does a matrix client get a message from a Home Server?](#how-does-a-matrix-client-get-a-message-from-a-home-server) * [How does a matrix client get a message from a homeserver?](#how-does-a-matrix-client-get-a-message-from-a-homeserver)
* [How does a mobile app receives push notification?](#how-does-a-mobile-app-receives-push-notification) * [How does a mobile app receives push notification?](#how-does-a-mobile-app-receives-push-notification)
* [Push VS Notification](#push-vs-notification) * [Push VS Notification](#push-vs-notification)
* [Push in the matrix federated world](#push-in-the-matrix-federated-world) * [Push in the matrix federated world](#push-in-the-matrix-federated-world)
* [How does the Home Server knows when to notify a client?](#how-does-the-home-server-knows-when-to-notify-a-client) * [How does the homeserver know when to notify a client?](#how-does-the-homeserver-know-when-to-notify-a-client)
* [Push vs privacy, and mitigation](#push-vs-privacy-and-mitigation) * [Push vs privacy, and mitigation](#push-vs-privacy-and-mitigation)
* [Background processing limitations](#background-processing-limitations) * [Background processing limitations](#background-processing-limitations)
2. [Element Notification implementations](#element-notification-implementations) 2. [Element Notification implementations](#element-notification-implementations)
@ -22,9 +22,9 @@ First let's start with some prerequisite knowledge
# Prerequisites Knowledge # Prerequisites Knowledge
## How does a matrix client get a message from a Home Server? ## How does a matrix client get a message from a homeserver?
In order to get messages from a home server, a matrix client need to perform a ``sync`` operation. In order to get messages from a homeserver, a matrix client need to perform a ``sync`` operation.
`To read events, the intended flow of operation is for clients to first call the /sync API without a since parameter. This returns the most recent message events for each room, as well as the state of the room at the start of the returned timeline. ` `To read events, the intended flow of operation is for clients to first call the /sync API without a since parameter. This returns the most recent message events for each room, as well as the state of the room at the start of the returned timeline. `
@ -90,7 +90,7 @@ That means that Element Android, a matrix client created by New Vector, is using
If you create your own matrix client, you will also need to deploy an instance of a **Push Gateway** with the credentials needed to use FCM for your app. If you create your own matrix client, you will also need to deploy an instance of a **Push Gateway** with the credentials needed to use FCM for your app.
On registration, a matrix client must tell to it's Home Server what Push Gateway to use. On registration, a matrix client must tell its homeserver what Push Gateway to use.
See [Sygnal](https://github.com/matrix-org/sygnal/) for a reference implementation. See [Sygnal](https://github.com/matrix-org/sygnal/) for a reference implementation.
``` ```
@ -122,13 +122,13 @@ Recommended reading:
* https://matrix.org/docs/spec/client_server/r0.4.0.html#id128 * https://matrix.org/docs/spec/client_server/r0.4.0.html#id128
## How does the Home Server knows when to notify a client? ## How does the homeserver know when to notify a client?
This is defined by [**push rules**](https://matrix.org/docs/spec/client_server/r0.4.0.html#push-rules-). This is defined by [**push rules**](https://matrix.org/docs/spec/client_server/r0.4.0.html#push-rules-).
`A push rule is a single rule that states under what conditions an event should be passed onto a push gateway and how the notification should be presented (sound / importance).` `A push rule is a single rule that states under what conditions an event should be passed onto a push gateway and how the notification should be presented (sound / importance).`
A Home Server can be configured with default rules (for Direct messages, group messages, mentions, etc.. ). A homeserver can be configured with default rules (for Direct messages, group messages, mentions, etc.. ).
There are different kind of push rules, it can be per room (each new message on this room should be notified), it can also define a pattern that a message should match (when you are mentioned, or key word based). There are different kind of push rules, it can be per room (each new message on this room should be notified), it can also define a pattern that a message should match (when you are mentioned, or key word based).
@ -187,7 +187,7 @@ In background, and depending on wether push is available or not, Element will us
## Push (FCM) received in background ## Push (FCM) received in background
In order to enable Push, Element must first get a push token from the firebase SDK, then register a pusher with this token on the HomeServer. In order to enable Push, Element must first get a push token from the firebase SDK, then register a pusher with this token on the homeserver.
When a message should be notified to a user, the user's homeserver notifies the registered `push gateway` for Element, that is [sygnal](https://github.com/matrix-org/sygnal) _- The reference implementation for push gateways -_ hosted by matrix.org. When a message should be notified to a user, the user's homeserver notifies the registered `push gateway` for Element, that is [sygnal](https://github.com/matrix-org/sygnal) _- The reference implementation for push gateways -_ hosted by matrix.org.
@ -199,7 +199,7 @@ Homeserver ----> Sygnal (configured for Element) ----> FCM ----> Element
The push gateway is configured to only send `(eventId,roomId)` in the push payload (for better [privacy](#push-vs-privacy-and-mitigation)). The push gateway is configured to only send `(eventId,roomId)` in the push payload (for better [privacy](#push-vs-privacy-and-mitigation)).
Element needs then to synchronise with the user's HomeServer, in order to resolve the event and create a notification. Element needs then to synchronise with the user's homeserver, in order to resolve the event and create a notification.
As per [Google recommendation](https://android-developers.googleblog.com/2018/09/notifying-your-users-with-fcm.html), Element will then use the WorkManager API in order to trigger a background sync. As per [Google recommendation](https://android-developers.googleblog.com/2018/09/notifying-your-users-with-fcm.html), Element will then use the WorkManager API in order to trigger a background sync.
@ -217,7 +217,7 @@ Homeserver ----> Sygnal ----> FCM ----> Element
**Possible outcomes** **Possible outcomes**
Upon reception of the FCM push, Element will perform a sync call to the Home Server, during this process it is possible that: Upon reception of the FCM push, Element will perform a sync call to the homeserver, during this process it is possible that:
* Happy path, the sync is performed, the message resolved and displayed in the notification drawer * Happy path, the sync is performed, the message resolved and displayed in the notification drawer
* The notified message is not in the sync. Can happen if a lot of things did happen since the push (`gappy sync`) * The notified message is not in the sync. Can happen if a lot of things did happen since the push (`gappy sync`)
* The sync generates additional notifications (e.g an encrypted message where the user is mentioned detected locally) * The sync generates additional notifications (e.g an encrypted message where the user is mentioned detected locally)

View file

@ -0,0 +1,2 @@
Hlavní změny v této verzi: aktualizace vzhledu a stylu a nové funkce prostorů.
Úplný záznam změn: https://github.com/vector-im/element-android/releases/tag/v1.1.10

View file

@ -0,0 +1,2 @@
Hlavní změny v této verzi: aktualizace vzhledu a stylu a nové funkce prostorů (bugfix pro 1.1.10)
Úplný záznam změn: https://github.com/vector-im/element-android/releases/tag/v1.1.11

View file

@ -0,0 +1,2 @@
Hauptänderungen: Design-Update und neue Features für Spaces
Vollständige Änderungsliste: https://github.com/vector-im/element-android/releases/tag/v1.1.10

View file

@ -0,0 +1,2 @@
Hauptänderungen: Design-Update und neue Features für Spaces (Bugfix für 1.1.10)
Vollständige Änderungsliste: https://github.com/vector-im/element-android/releases/tag/v1.1.11

View file

@ -0,0 +1,2 @@
Main changes in this version: mainly stability and bugfixes update.
Full changelog: https://github.com/vector-im/element-android/releases/tag/v1.1.13

View file

@ -0,0 +1,2 @@
Main changes in this version: fix an issue about encrypted messages.
Full changelog: https://github.com/vector-im/element-android/releases/tag/v1.1.14

View file

@ -0,0 +1,2 @@
Põhilised muutused selles versioonis: teemade ja välimuse uuendused ning mõned kogukondade uuendused
Kogu ingliskeelne muudatuste logi: https://github.com/vector-im/element-android/releases/tag/v1.1.10

View file

@ -0,0 +1,2 @@
Põhilised muutused selles versioonis: teemade ja välimuse uuendused ning mõned kogukondade uuendused (1.1.10 veaparandus)
Kogu ingliskeelne muudatuste logi: https://github.com/vector-im/element-android/releases/tag/v1.1.11

View file

@ -0,0 +1,2 @@
Főbb változtatások ebben a verzióban: kinézet és stílus frissítések és új funkciók a terekhez
Teljes változásnapló: https://github.com/vector-im/element-android/releases/tag/v1.1.10

View file

@ -0,0 +1,2 @@
Főbb változtatások ebben a verzióban: kinézet és stílus frissítések és új funkciók a terekhez (hibajavítás az 1.1.10-hez)
Teljes változásnapló: https://github.com/vector-im/element-android/releases/tag/v1.1.11

View file

@ -0,0 +1,2 @@
Modifiche principali in questa versione: aggiornati tema e stile e nuove funzioni per gli spazi .
Cronologia completa: https://github.com/vector-im/element-android/releases/tag/v1.1.10

View file

@ -0,0 +1,2 @@
Modifiche principali in questa versione: aggiornati tema e stile e nuove funzioni per gli spazi (bugfix per 1.1.10)
Cronologia completa: https://github.com/vector-im/element-android/releases/tag/v1.1.11

View file

@ -1,2 +1,2 @@
Principais mudanças nesta version: suporte beta para Espaços. Comprimir vídeo antes de enviar. Principais mudanças nesta versão: suporte beta para Espaços. Comprimir vídeo antes de enviar.
Changelog completo: https://github.com/vector-im/element-android/releases/tag/v1.1.7 Changelog completo: https://github.com/vector-im/element-android/releases/tag/v1.1.7

View file

@ -1,2 +1,2 @@
Principais mudanças nesta version: melhoramento para Espaços. Principais mudanças nesta versão: melhoramento para Espaços.
Changelog completo: https://github.com/vector-im/element-android/releases/tag/v1.1.8 Changelog completo: https://github.com/vector-im/element-android/releases/tag/v1.1.8

View file

@ -1,2 +1,2 @@
Principais mudanças nesta version: adicionar supporte a rede gitter.im. Principais mudanças nesta versão: adicionar supporte a rede gitter.im.
Changelog completo: https://github.com/vector-im/element-android/releases/tag/v1.1.9 Changelog completo: https://github.com/vector-im/element-android/releases/tag/v1.1.9

View file

@ -0,0 +1,2 @@
Principais mudanças nesta versão: atualização de tema e estilo e novas funcionalidades para espaços.
Changelog completo: https://github.com/vector-im/element-android/releases/tag/v1.1.10

View file

@ -0,0 +1,2 @@
Principais mudanças nesta versão: atualização de tema e estilo e novas funcionalidades para espaços (bugfix para 1.1.10)
Changelog completo: https://github.com/vector-im/element-android/releases/tag/v1.1.11

View file

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

View file

@ -0,0 +1,2 @@
Основні зміни цієї версії: оновлено зовнішній вигляд та нові можливості для просторів (bugfix для 1.1.10)
Вичерпний перелік змін: https://github.com/vector-im/element-android/releases/tag/v1.1.11

View file

@ -0,0 +1,2 @@
此版本的主要变化:主题和样式更新以及空间的新功能。
完整更新日志https://github.com/vector-im/element-android/releases/tag/v1.1.10

View file

@ -0,0 +1,2 @@
此版本的主要变化主题和样式更新以及空间的新功能1.1.10 的错误修复)
完整更新日志https://github.com/vector-im/element-android/releases/tag/v1.1.11

View file

@ -0,0 +1,2 @@
此版本中的主要變動:佈景主題與樣式更新,以及空間的新功能。
完整的變更紀錄https://github.com/vector-im/element-android/releases/tag/v1.1.10

View file

@ -0,0 +1,2 @@
此版本中的主要變動佈景主題與樣式更新以及空間的新功能1.1.10 的臭蟲修復版本)
完整的變更紀錄https://github.com/vector-im/element-android/releases/tag/v1.1.11

View file

@ -52,8 +52,8 @@ android {
} }
dependencies { dependencies {
implementation 'androidx.appcompat:appcompat:1.3.0' implementation 'androidx.appcompat:appcompat:1.3.1'
implementation 'com.google.android.material:material:1.3.0' implementation 'com.google.android.material:material:1.4.0'
// Pref theme // Pref theme
implementation 'androidx.preference:preference-ktx:1.1.1' implementation 'androidx.preference:preference-ktx:1.1.1'
// PFLockScreen attrs // PFLockScreen attrs

View file

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

View file

@ -112,7 +112,7 @@ dependencies {
def lifecycle_version = '2.2.0' def lifecycle_version = '2.2.0'
def arch_version = '2.1.0' def arch_version = '2.1.0'
def markwon_version = '3.1.0' def markwon_version = '3.1.0'
def daggerVersion = '2.37' def daggerVersion = '2.38'
def work_version = '2.5.0' def work_version = '2.5.0'
def retrofit_version = '2.9.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-core:$kotlin_coroutines_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version"
implementation "androidx.appcompat:appcompat:1.3.0" implementation "androidx.appcompat:appcompat:1.3.1"
implementation "androidx.core:core-ktx:1.6.0" implementation "androidx.core:core-ktx:1.6.0"
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version" implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
@ -169,14 +169,14 @@ dependencies {
implementation 'com.otaliastudios:transcoder:0.10.3' implementation 'com.otaliastudios:transcoder:0.10.3'
// Phone number https://github.com/google/libphonenumber // Phone number https://github.com/google/libphonenumber
implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.26' implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.28'
testImplementation 'junit:junit:4.13.2' testImplementation 'junit:junit:4.13.2'
testImplementation 'org.robolectric:robolectric:4.5.1' testImplementation 'org.robolectric:robolectric:4.5.1'
//testImplementation 'org.robolectric:shadows-support-v4:3.0' //testImplementation 'org.robolectric:shadows-support-v4:3.0'
// Note: version sticks to 1.9.2 due to https://github.com/mockk/mockk/issues/281 // Note: version sticks to 1.9.2 due to https://github.com/mockk/mockk/issues/281
testImplementation 'io.mockk:mockk:1.12.0' testImplementation 'io.mockk:mockk:1.12.0'
testImplementation 'org.amshove.kluent:kluent-android:1.67' testImplementation 'org.amshove.kluent:kluent-android:1.68'
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version" testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version"
// Plant Timber tree for test // Plant Timber tree for test
testImplementation 'net.lachlanmckee:timber-junit-rule:1.0.1' testImplementation 'net.lachlanmckee:timber-junit-rule:1.0.1'
@ -185,9 +185,9 @@ dependencies {
androidTestImplementation 'androidx.test:core:1.4.0' androidTestImplementation 'androidx.test:core:1.4.0'
androidTestImplementation 'androidx.test:runner:1.4.0' androidTestImplementation 'androidx.test:runner:1.4.0'
androidTestImplementation 'androidx.test:rules:1.4.0' androidTestImplementation 'androidx.test:rules:1.4.0'
androidTestImplementation 'androidx.test.ext:junit:1.1.2' androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
androidTestImplementation 'org.amshove.kluent:kluent-android:1.65' androidTestImplementation 'org.amshove.kluent:kluent-android:1.68'
androidTestImplementation 'io.mockk:mockk-android:1.12.0' androidTestImplementation 'io.mockk:mockk-android:1.12.0'
androidTestImplementation "androidx.arch.core:core-testing:$arch_version" androidTestImplementation "androidx.arch.core:core-testing:$arch_version"
androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version" androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version"

View file

@ -78,7 +78,7 @@ class CommonTestHelper(context: Context) {
} }
/** /**
* Create a Home server configuration, with Http connection allowed for test * Create a homeserver configuration, with Http connection allowed for test
*/ */
fun createHomeServerConfig(): HomeServerConnectionConfig { fun createHomeServerConfig(): HomeServerConnectionConfig {
return HomeServerConnectionConfig.Builder() return HomeServerConnectionConfig.Builder()

View file

@ -816,7 +816,7 @@ class KeysBackupTest : InstrumentedTest {
// - Do an e2e backup to the homeserver // - Do an e2e backup to the homeserver
mKeysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup) mKeysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup)
// Get key backup version from the home server // Get key backup version from the homeserver
val keysVersionResult = mTestHelper.doSync<KeysVersionResult?> { val keysVersionResult = mTestHelper.doSync<KeysVersionResult?> {
keysBackup.getCurrentVersion(it) keysBackup.getCurrentVersion(it)
} }

View file

@ -16,8 +16,12 @@
package org.matrix.android.sdk.api package org.matrix.android.sdk.api
import org.matrix.android.sdk.BuildConfig
import timber.log.Timber
/** /**
* This class contains pattern to match the different Matrix ids * This class contains pattern to match the different Matrix ids
* Ref: https://matrix.org/docs/spec/appendices#identifier-grammar
*/ */
object MatrixPatterns { object MatrixPatterns {
@ -25,7 +29,7 @@ object MatrixPatterns {
private const val DOMAIN_REGEX = ":[A-Z0-9.-]+(:[0-9]{2,5})?" private const val DOMAIN_REGEX = ":[A-Z0-9.-]+(:[0-9]{2,5})?"
// regex pattern to find matrix user ids in a string. // regex pattern to find matrix user ids in a string.
// See https://matrix.org/speculator/spec/HEAD/appendices.html#historical-user-ids // See https://matrix.org/docs/spec/appendices#historical-user-ids
private const val MATRIX_USER_IDENTIFIER_REGEX = "@[A-Z0-9\\x21-\\x39\\x3B-\\x7F]+$DOMAIN_REGEX" private const val MATRIX_USER_IDENTIFIER_REGEX = "@[A-Z0-9\\x21-\\x39\\x3B-\\x7F]+$DOMAIN_REGEX"
val PATTERN_CONTAIN_MATRIX_USER_IDENTIFIER = MATRIX_USER_IDENTIFIER_REGEX.toRegex(RegexOption.IGNORE_CASE) val PATTERN_CONTAIN_MATRIX_USER_IDENTIFIER = MATRIX_USER_IDENTIFIER_REGEX.toRegex(RegexOption.IGNORE_CASE)
@ -154,7 +158,7 @@ object MatrixPatterns {
* Orders which are not strings, or do not consist solely of ascii characters in the range \x20 (space) to \x7E (~), * Orders which are not strings, or do not consist solely of ascii characters in the range \x20 (space) to \x7E (~),
* or consist of more than 50 characters, are forbidden and the field should be ignored if received. * or consist of more than 50 characters, are forbidden and the field should be ignored if received.
*/ */
fun isValidOrderString(order: String?) : Boolean { fun isValidOrderString(order: String?): Boolean {
return order != null && order.length < 50 && order matches ORDER_STRING_REGEX return order != null && order.length < 50 && order matches ORDER_STRING_REGEX
} }
@ -163,4 +167,18 @@ object MatrixPatterns {
"[^a-z0-9._%#@=+-]".toRegex().replace(it, "") "[^a-z0-9._%#@=+-]".toRegex().replace(it, "")
} }
} }
/**
* Return the domain form a userId
* Examples:
* - "@alice:domain.org".getDomain() will return "domain.org"
* - "@bob:domain.org:3455".getDomain() will return "domain.org:3455"
*/
fun String.getDomain(): String {
if (BuildConfig.DEBUG && !isUserId(this)) {
// They are some invalid userId localpart in the wild, but the domain part should be there anyway
Timber.w("Not a valid user ID: $this")
}
return substringAfter(":")
}
} }

View file

@ -18,11 +18,11 @@ package org.matrix.android.sdk.api.auth.data
import android.net.Uri import android.net.Uri
import com.squareup.moshi.JsonClass import com.squareup.moshi.JsonClass
import okhttp3.CipherSuite
import okhttp3.TlsVersion
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig.Builder import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig.Builder
import org.matrix.android.sdk.internal.network.ssl.Fingerprint import org.matrix.android.sdk.internal.network.ssl.Fingerprint
import org.matrix.android.sdk.internal.util.ensureTrailingSlash import org.matrix.android.sdk.internal.util.ensureTrailingSlash
import okhttp3.CipherSuite
import okhttp3.TlsVersion
/** /**
* This data class holds how to connect to a specific Homeserver. * This data class holds how to connect to a specific Homeserver.
@ -31,7 +31,12 @@ import okhttp3.TlsVersion
*/ */
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
data class HomeServerConnectionConfig( data class HomeServerConnectionConfig(
// This is the homeserver URL entered by the user
val homeServerUri: Uri, val homeServerUri: Uri,
// This is the homeserver base URL for the client-server API. Default to homeServerUri,
// but can be updated with data from .Well-Known before login, and/or with the data
// included in the login response
val homeServerUriBase: Uri = homeServerUri,
val identityServerUri: Uri? = null, val identityServerUri: Uri? = null,
val antiVirusServerUri: Uri? = null, val antiVirusServerUri: Uri? = null,
val allowedFingerprints: List<Fingerprint> = emptyList(), val allowedFingerprints: List<Fingerprint> = emptyList(),
@ -47,7 +52,6 @@ data class HomeServerConnectionConfig(
* This builder should be use to create a [HomeServerConnectionConfig] instance. * This builder should be use to create a [HomeServerConnectionConfig] instance.
*/ */
class Builder { class Builder {
private lateinit var homeServerUri: Uri private lateinit var homeServerUri: Uri
private var identityServerUri: Uri? = null private var identityServerUri: Uri? = null
private var antiVirusServerUri: Uri? = null private var antiVirusServerUri: Uri? = null
@ -69,14 +73,14 @@ data class HomeServerConnectionConfig(
*/ */
fun withHomeServerUri(hsUri: Uri): Builder { fun withHomeServerUri(hsUri: Uri): Builder {
if (hsUri.scheme != "http" && hsUri.scheme != "https") { if (hsUri.scheme != "http" && hsUri.scheme != "https") {
throw RuntimeException("Invalid home server URI: $hsUri") throw RuntimeException("Invalid homeserver URI: $hsUri")
} }
// ensure trailing / // ensure trailing /
val hsString = hsUri.toString().ensureTrailingSlash() val hsString = hsUri.toString().ensureTrailingSlash()
homeServerUri = try { homeServerUri = try {
Uri.parse(hsString) Uri.parse(hsString)
} catch (e: Exception) { } catch (e: Exception) {
throw RuntimeException("Invalid home server URI: $hsUri") throw RuntimeException("Invalid homeserver URI: $hsUri")
} }
return this return this
} }
@ -134,7 +138,7 @@ data class HomeServerConnectionConfig(
} }
/** /**
* Add an accepted TLS version for TLS connections with the home server. * Add an accepted TLS version for TLS connections with the homeserver.
* *
* @param tlsVersion the tls version to add to the set of TLS versions accepted. * @param tlsVersion the tls version to add to the set of TLS versions accepted.
* @return this builder * @return this builder
@ -156,7 +160,7 @@ data class HomeServerConnectionConfig(
} }
/** /**
* Add a TLS cipher suite to the list of accepted TLS connections with the home server. * Add a TLS cipher suite to the list of accepted TLS connections with the homeserver.
* *
* @param tlsCipherSuite the tls cipher suite to add. * @param tlsCipherSuite the tls cipher suite to add.
* @return this builder * @return this builder
@ -234,16 +238,16 @@ data class HomeServerConnectionConfig(
*/ */
fun build(): HomeServerConnectionConfig { fun build(): HomeServerConnectionConfig {
return HomeServerConnectionConfig( return HomeServerConnectionConfig(
homeServerUri, homeServerUri = homeServerUri,
identityServerUri, identityServerUri = identityServerUri,
antiVirusServerUri, antiVirusServerUri = antiVirusServerUri,
allowedFingerprints, allowedFingerprints = allowedFingerprints,
shouldPin, shouldPin = shouldPin,
tlsVersions, tlsVersions = tlsVersions,
tlsCipherSuites, tlsCipherSuites = tlsCipherSuites,
shouldAcceptTlsExtensions, shouldAcceptTlsExtensions = shouldAcceptTlsExtensions,
allowHttpExtension, allowHttpExtension = allowHttpExtension,
forceUsageTlsVersions forceUsageTlsVersions = forceUsageTlsVersions
) )
} }
} }

View file

@ -51,13 +51,18 @@ data class SessionParams(
val deviceId = credentials.deviceId val deviceId = credentials.deviceId
/** /**
* The current homeserver Url. It can be different that the homeserver url entered * The homeserver Url entered by the user during the login phase.
* during login phase, because a redirection may have occurred
*/ */
val homeServerUrl = homeServerConnectionConfig.homeServerUri.toString() val homeServerUrl = homeServerConnectionConfig.homeServerUri.toString()
/** /**
* The current homeserver host * The current homeserver Url for client-server API. It can be different that the homeserver url entered
* during login phase, because a redirection may have occurred
*/
val homeServerUrlBase = homeServerConnectionConfig.homeServerUriBase.toString()
/**
* The current homeserver host, using what has been entered by the user during login phase
*/ */
val homeServerHost = homeServerConnectionConfig.homeServerUri.host val homeServerHost = homeServerConnectionConfig.homeServerUri.host

View file

@ -38,7 +38,7 @@ data class RegistrationFlowResponse(
val completedStages: List<String>? = null, val completedStages: List<String>? = null,
/** /**
* The session identifier that the client must pass back to the home server, if one is provided, * The session identifier that the client must pass back to the homeserver, if one is provided,
* in subsequent attempts to authenticate in the same API call. * in subsequent attempts to authenticate in the same API call.
*/ */
@Json(name = "session") @Json(name = "session")

View file

@ -22,11 +22,6 @@ import org.matrix.android.sdk.api.auth.data.WellKnown
* Ref: https://matrix.org/docs/spec/client_server/latest#well-known-uri * Ref: https://matrix.org/docs/spec/client_server/latest#well-known-uri
*/ */
sealed class WellknownResult { sealed class WellknownResult {
/**
* The provided matrixId is no valid. Unable to extract a domain name.
*/
object InvalidMatrixId : WellknownResult()
/** /**
* Retrieve the specific piece of information from the user in a way which fits within the existing client user experience, * Retrieve the specific piece of information from the user in a way which fits within the existing client user experience,
* if the client is inclined to do so. Failure can take place instead if no good user experience for this is possible at this point. * if the client is inclined to do so. Failure can take place instead if no good user experience for this is possible at this point.

View file

@ -0,0 +1,21 @@
/*
* 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.failure
sealed class MatrixIdFailure : Failure.FeatureFailure() {
object InvalidMatrixId : MatrixIdFailure()
}

View file

@ -0,0 +1,34 @@
/*
* 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.logger
/**
* Parent class for custom logger tags. Can be used with Timber :
*
* val loggerTag = LoggerTag("MyTag", LoggerTag.VOIP)
* Timber.tag(loggerTag.value).v("My log message")
*/
open class LoggerTag(_value: String, parentTag: LoggerTag? = null) {
object VOIP : LoggerTag("VOIP")
val value: String = if (parentTag == null) {
_value
} else {
"${parentTag.value}/$_value"
}
}

View file

@ -29,8 +29,10 @@ interface RawService {
/** /**
* Specific case for the well-known file. Cache validity is 8 hours * Specific case for the well-known file. Cache validity is 8 hours
* @param domain the domain to get the .well-known file, for instance "matrix.org".
* The URL will be "https://{domain}/.well-known/matrix/client"
*/ */
suspend fun getWellknown(userId: String): String suspend fun getWellknown(domain: String): String
/** /**
* Clear all the cache data * Clear all the cache data

View file

@ -16,6 +16,8 @@
package org.matrix.android.sdk.api.session.call package org.matrix.android.sdk.api.session.call
import org.matrix.android.sdk.api.session.room.model.call.EndCallReason
sealed class CallState { sealed class CallState {
/** Idle, setting up objects */ /** Idle, setting up objects */
@ -42,6 +44,6 @@ sealed class CallState {
* */ * */
data class Connected(val iceConnectionState: MxPeerConnectionState) : CallState() data class Connected(val iceConnectionState: MxPeerConnectionState) : CallState()
/** Terminated. Incoming/Outgoing call, the call is terminated */ /** Ended. Incoming/Outgoing call, the call is terminated */
object Terminated : CallState() data class Ended(val reason: EndCallReason? = null) : CallState()
} }

View file

@ -18,7 +18,7 @@ package org.matrix.android.sdk.api.session.call
import org.matrix.android.sdk.api.session.room.model.call.CallCandidate import org.matrix.android.sdk.api.session.room.model.call.CallCandidate
import org.matrix.android.sdk.api.session.room.model.call.CallCapabilities import org.matrix.android.sdk.api.session.room.model.call.CallCapabilities
import org.matrix.android.sdk.api.session.room.model.call.CallHangupContent import org.matrix.android.sdk.api.session.room.model.call.EndCallReason
import org.matrix.android.sdk.api.session.room.model.call.SdpType import org.matrix.android.sdk.api.session.room.model.call.SdpType
import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.api.util.Optional
@ -69,7 +69,7 @@ interface MxCall : MxCallDetail {
/** /**
* End the call * End the call
*/ */
fun hangUp(reason: CallHangupContent.Reason? = null) fun hangUp(reason: EndCallReason? = null)
/** /**
* Start a call * Start a call

View file

@ -28,7 +28,7 @@ import org.matrix.android.sdk.internal.crypto.store.SavedKeyBackupKeyInfo
interface KeysBackupService { interface KeysBackupService {
/** /**
* Retrieve the current version of the backup from the home server * Retrieve the current version of the backup from the homeserver
* *
* It can be different than keysBackupVersion. * It can be different than keysBackupVersion.
* @param callback onSuccess(null) will be called if there is no backup on the server * @param callback onSuccess(null) will be called if there is no backup on the server

View file

@ -54,7 +54,7 @@ enum class KeysBackupState {
// Need to check the current backup version on the homeserver // Need to check the current backup version on the homeserver
Unknown, Unknown,
// Checking if backup is enabled on home server // Checking if backup is enabled on homeserver
CheckingBackUpOnHomeserver, CheckingBackUpOnHomeserver,
// Backup has been stopped because a new backup version has been detected on the homeserver // Backup has been stopped because a new backup version has been detected on the homeserver

View file

@ -104,7 +104,7 @@ data class Event(
/** /**
* The `age` value transcoded in a timestamp based on the device clock when the SDK received * The `age` value transcoded in a timestamp based on the device clock when the SDK received
* the event from the home server. * the event from the homeserver.
* Unlike `age`, this value is static. * Unlike `age`, this value is static.
*/ */
@Transient @Transient

View file

@ -32,7 +32,13 @@ data class HomeServerCapabilities(
/** /**
* Default identity server url, provided in Wellknown * Default identity server url, provided in Wellknown
*/ */
val defaultIdentityServerUrl: String? = null val defaultIdentityServerUrl: String? = null,
/**
* Room versions supported by the server
* This capability describes the default and available room versions a server supports, and at what level of stability.
* Clients should make use of this capability to determine if users need to be encouraged to upgrade their rooms.
*/
val roomVersions: RoomVersionCapabilities? = null
) { ) {
companion object { companion object {
const val MAX_UPLOAD_FILE_SIZE_UNKNOWN = -1L const val MAX_UPLOAD_FILE_SIZE_UNKNOWN = -1L

View file

@ -0,0 +1,32 @@
/*
* Copyright 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.homeserver
data class RoomVersionCapabilities(
val defaultRoomVersion: String,
val supportedVersion: List<RoomVersionInfo>
)
data class RoomVersionInfo(
val version: String,
val status: RoomVersionStatus
)
enum class RoomVersionStatus {
STABLE,
UNSTABLE
}

View file

@ -34,6 +34,7 @@ import org.matrix.android.sdk.api.session.room.tags.TagsService
import org.matrix.android.sdk.api.session.room.timeline.TimelineService import org.matrix.android.sdk.api.session.room.timeline.TimelineService
import org.matrix.android.sdk.api.session.room.typing.TypingService import org.matrix.android.sdk.api.session.room.typing.TypingService
import org.matrix.android.sdk.api.session.room.uploads.UploadsService import org.matrix.android.sdk.api.session.room.uploads.UploadsService
import org.matrix.android.sdk.api.session.room.version.RoomVersionService
import org.matrix.android.sdk.api.session.search.SearchResult import org.matrix.android.sdk.api.session.search.SearchResult
import org.matrix.android.sdk.api.session.space.Space import org.matrix.android.sdk.api.session.space.Space
import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.api.util.Optional
@ -57,7 +58,8 @@ interface Room :
RelationService, RelationService,
RoomCryptoService, RoomCryptoService,
RoomPushRuleService, RoomPushRuleService,
RoomAccountDataService { RoomAccountDataService,
RoomVersionService {
/** /**
* The roomId of this room * The roomId of this room

View file

@ -39,12 +39,6 @@ fun spaceSummaryQueryParams(init: (RoomSummaryQueryParams.Builder.() -> Unit) =
.build() .build()
} }
enum class RoomCategoryFilter {
ONLY_DM,
ONLY_ROOMS,
ALL
}
/** /**
* This class can be used to filter room summaries to use with: * This class can be used to filter room summaries to use with:
* [org.matrix.android.sdk.api.session.room.Room] and [org.matrix.android.sdk.api.session.room.RoomService] * [org.matrix.android.sdk.api.session.room.Room] and [org.matrix.android.sdk.api.session.room.RoomService]
@ -59,11 +53,10 @@ data class RoomSummaryQueryParams(
val excludeType: List<String?>?, val excludeType: List<String?>?,
val includeType: List<String?>?, val includeType: List<String?>?,
val activeSpaceFilter: ActiveSpaceFilter?, val activeSpaceFilter: ActiveSpaceFilter?,
var activeGroupId: String? = null val activeGroupId: String? = null
) { ) {
class Builder { class Builder {
var roomId: QueryStringValue = QueryStringValue.IsNotEmpty var roomId: QueryStringValue = QueryStringValue.IsNotEmpty
var displayName: QueryStringValue = QueryStringValue.IsNotEmpty var displayName: QueryStringValue = QueryStringValue.IsNotEmpty
var canonicalAlias: QueryStringValue = QueryStringValue.NoCondition var canonicalAlias: QueryStringValue = QueryStringValue.NoCondition

View file

@ -43,29 +43,5 @@ data class CallHangupContent(
* or `invite_timeout` for when the other party did not answer in time. * or `invite_timeout` for when the other party did not answer in time.
* One of: ["ice_failed", "invite_timeout"] * One of: ["ice_failed", "invite_timeout"]
*/ */
@Json(name = "reason") val reason: Reason? = null @Json(name = "reason") val reason: EndCallReason? = null
) : CallSignalingContent { ) : CallSignalingContent
@JsonClass(generateAdapter = false)
enum class Reason {
@Json(name = "ice_failed")
ICE_FAILED,
@Json(name = "ice_timeout")
ICE_TIMEOUT,
@Json(name = "user_hangup")
USER_HANGUP,
@Json(name = "replaced")
REPLACED,
@Json(name = "user_media_failed")
USER_MEDIA_FAILED,
@Json(name = "invite_timeout")
INVITE_TIMEOUT,
@Json(name = "unknown_error")
UNKWOWN_ERROR
}
}

View file

@ -36,5 +36,10 @@ data class CallRejectContent(
/** /**
* Required. The version of the VoIP specification this message adheres to. * Required. The version of the VoIP specification this message adheres to.
*/ */
@Json(name = "version") override val version: String? @Json(name = "version") override val version: String?,
/**
* Optional error reason for the reject.
*/
@Json(name = "reason") val reason: EndCallReason? = null
) : CallSignalingContent ) : CallSignalingContent

View file

@ -0,0 +1,50 @@
/*
* 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.room.model.call
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = false)
enum class EndCallReason {
@Json(name = "ice_failed")
ICE_FAILED,
@Json(name = "ice_timeout")
ICE_TIMEOUT,
@Json(name = "user_hangup")
USER_HANGUP,
@Json(name = "replaced")
REPLACED,
@Json(name = "user_media_failed")
USER_MEDIA_FAILED,
@Json(name = "invite_timeout")
INVITE_TIMEOUT,
@Json(name = "unknown_error")
UNKWOWN_ERROR,
@Json(name = "user_busy")
USER_BUSY,
@Json(name = "answered_elsewhere")
ANSWERED_ELSEWHERE
}

View file

@ -25,7 +25,6 @@ import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility
import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesAllowEntry import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesAllowEntry
import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
// TODO Give a way to include other initial states
open class CreateRoomParams { open class CreateRoomParams {
/** /**
* A public visibility indicates that the room will be shown in the published room list. * A public visibility indicates that the room will be shown in the published room list.
@ -103,6 +102,13 @@ open class CreateRoomParams {
*/ */
val creationContent = mutableMapOf<String, Any>() val creationContent = mutableMapOf<String, Any>()
/**
* A list of state events to set in the new room. This allows the user to override the default state events
* set in the new room. The expected format of the state events are an object with type, state_key and content keys set.
* Takes precedence over events set by preset, but gets overridden by name and topic keys.
*/
val initialStates = mutableListOf<CreateRoomStateEvent>()
/** /**
* Set to true to disable federation of this room. * Set to true to disable federation of this room.
* Default: false * Default: false

View file

@ -0,0 +1,36 @@
/*
* 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.room.model.create
import org.matrix.android.sdk.api.session.events.model.Content
data class CreateRoomStateEvent(
/**
* Required. The type of event to send.
*/
val type: String,
/**
* Required. The content of the event.
*/
val content: Content,
/**
* The state_key of the state event. Defaults to an empty string.
*/
val stateKey: String = ""
)

View file

@ -0,0 +1,45 @@
/*
* Copyright 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.room.version
interface RoomVersionService {
/**
* Return the room version of this room
*/
fun getRoomVersion(): String
/**
* Upgrade to the given room version
* @return the replacement room id
*/
suspend fun upgradeToVersion(version: String): String
/**
* Get the recommended room version for the current homeserver
*/
fun getRecommendedVersion() : String
/**
* Ask if the user has enough power level to upgrade the room
*/
fun userMayUpgradeRoom(userId: String): Boolean
/**
* Return true if the current room version is declared unstable by the homeserver
*/
fun isUsingUnstableRoomVersion(): Boolean
}

View file

@ -18,6 +18,7 @@ package org.matrix.android.sdk.api.session.space
import org.matrix.android.sdk.api.session.room.Room import org.matrix.android.sdk.api.session.room.Room
import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.model.RoomSummary
import org.matrix.android.sdk.api.session.space.model.SpaceChildContent
interface Space { interface Space {
@ -38,6 +39,8 @@ interface Space {
autoJoin: Boolean = false, autoJoin: Boolean = false,
suggested: Boolean? = false) suggested: Boolean? = false)
fun getChildInfo(roomId: String): SpaceChildContent?
suspend fun removeChildren(roomId: String) suspend fun removeChildren(roomId: String)
@Throws @Throws

View file

@ -21,8 +21,8 @@ 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.Availability
import org.matrix.android.sdk.internal.auth.data.LoginFlowResponse import org.matrix.android.sdk.internal.auth.data.LoginFlowResponse
import org.matrix.android.sdk.internal.auth.data.PasswordLoginParams import org.matrix.android.sdk.internal.auth.data.PasswordLoginParams
import org.matrix.android.sdk.internal.auth.data.RiotConfig
import org.matrix.android.sdk.internal.auth.data.TokenLoginParams import org.matrix.android.sdk.internal.auth.data.TokenLoginParams
import org.matrix.android.sdk.internal.auth.data.WebClientConfig
import org.matrix.android.sdk.internal.auth.login.ResetPasswordMailConfirmed import org.matrix.android.sdk.internal.auth.login.ResetPasswordMailConfirmed
import org.matrix.android.sdk.internal.auth.registration.AddThreePidRegistrationParams import org.matrix.android.sdk.internal.auth.registration.AddThreePidRegistrationParams
import org.matrix.android.sdk.internal.auth.registration.AddThreePidRegistrationResponse import org.matrix.android.sdk.internal.auth.registration.AddThreePidRegistrationResponse
@ -44,16 +44,16 @@ import retrofit2.http.Url
*/ */
internal interface AuthAPI { internal interface AuthAPI {
/** /**
* Get a Riot config file, using the name including the domain * Get a Web client config file, using the name including the domain
*/ */
@GET("config.{domain}.json") @GET("config.{domain}.json")
suspend fun getRiotConfigDomain(@Path("domain") domain: String): RiotConfig suspend fun getWebClientConfigDomain(@Path("domain") domain: String): WebClientConfig
/** /**
* Get a Riot config file * Get a Web client default config file
*/ */
@GET("config.json") @GET("config.json")
suspend fun getRiotConfig(): RiotConfig suspend fun getWebClientConfig(): WebClientConfig
/** /**
* Get the version information of the homeserver * Get the version information of the homeserver

View file

@ -19,6 +19,8 @@ package org.matrix.android.sdk.internal.auth
import android.net.Uri import android.net.Uri
import dagger.Lazy import dagger.Lazy
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import org.matrix.android.sdk.api.MatrixPatterns
import org.matrix.android.sdk.api.MatrixPatterns.getDomain
import org.matrix.android.sdk.api.auth.AuthenticationService import org.matrix.android.sdk.api.auth.AuthenticationService
import org.matrix.android.sdk.api.auth.data.Credentials import org.matrix.android.sdk.api.auth.data.Credentials
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
@ -28,10 +30,11 @@ import org.matrix.android.sdk.api.auth.login.LoginWizard
import org.matrix.android.sdk.api.auth.registration.RegistrationWizard import org.matrix.android.sdk.api.auth.registration.RegistrationWizard
import org.matrix.android.sdk.api.auth.wellknown.WellknownResult import org.matrix.android.sdk.api.auth.wellknown.WellknownResult
import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.failure.Failure
import org.matrix.android.sdk.api.failure.MatrixIdFailure
import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.util.appendParamToUrl import org.matrix.android.sdk.api.util.appendParamToUrl
import org.matrix.android.sdk.internal.SessionManager import org.matrix.android.sdk.internal.SessionManager
import org.matrix.android.sdk.internal.auth.data.RiotConfig import org.matrix.android.sdk.internal.auth.data.WebClientConfig
import org.matrix.android.sdk.internal.auth.db.PendingSessionData import org.matrix.android.sdk.internal.auth.db.PendingSessionData
import org.matrix.android.sdk.internal.auth.login.DefaultLoginWizard import org.matrix.android.sdk.internal.auth.login.DefaultLoginWizard
import org.matrix.android.sdk.internal.auth.login.DirectLoginTask import org.matrix.android.sdk.internal.auth.login.DirectLoginTask
@ -122,7 +125,7 @@ internal class DefaultAuthenticationService @Inject constructor(
private fun getHomeServerUrlBase(): String? { private fun getHomeServerUrlBase(): String? {
return pendingSessionData return pendingSessionData
?.homeServerConnectionConfig ?.homeServerConnectionConfig
?.homeServerUri ?.homeServerUriBase
?.toString() ?.toString()
?.trim { it == '/' } ?.trim { it == '/' }
} }
@ -143,9 +146,9 @@ internal class DefaultAuthenticationService @Inject constructor(
return result.fold( return result.fold(
{ {
// The homeserver exists and up to date, keep the config // The homeserver exists and up to date, keep the config
// Homeserver url may have been changed, if it was a Riot url // Homeserver url may have been changed, if it was a Web client url
val alteredHomeServerConnectionConfig = homeServerConnectionConfig.copy( val alteredHomeServerConnectionConfig = homeServerConnectionConfig.copy(
homeServerUri = Uri.parse(it.homeServerUrl) homeServerUriBase = Uri.parse(it.homeServerUrl)
) )
pendingSessionData = PendingSessionData(alteredHomeServerConnectionConfig) pendingSessionData = PendingSessionData(alteredHomeServerConnectionConfig)
@ -154,7 +157,7 @@ internal class DefaultAuthenticationService @Inject constructor(
}, },
{ {
if (it is UnrecognizedCertificateException) { if (it is UnrecognizedCertificateException) {
throw Failure.UnrecognizedCertificateFailure(homeServerConnectionConfig.homeServerUri.toString(), it.fingerprint) throw Failure.UnrecognizedCertificateFailure(homeServerConnectionConfig.homeServerUriBase.toString(), it.fingerprint)
} else { } else {
throw it throw it
} }
@ -165,46 +168,57 @@ internal class DefaultAuthenticationService @Inject constructor(
private suspend fun getLoginFlowInternal(homeServerConnectionConfig: HomeServerConnectionConfig): LoginFlowResult { private suspend fun getLoginFlowInternal(homeServerConnectionConfig: HomeServerConnectionConfig): LoginFlowResult {
val authAPI = buildAuthAPI(homeServerConnectionConfig) val authAPI = buildAuthAPI(homeServerConnectionConfig)
// First check the homeserver version // First check if there is a well-known file
return runCatching { return try {
executeRequest(null) { getWellknownLoginFlowInternal(homeServerConnectionConfig)
authAPI.versions() } catch (failure: Throwable) {
if (failure is Failure.OtherServerError
&& failure.httpCode == HttpsURLConnection.HTTP_NOT_FOUND /* 404 */) {
// 404, no well-known data, try direct access to the API
// First check the homeserver version
return runCatching {
executeRequest(null) {
authAPI.versions()
}
}
.map { versions ->
// Ok, it seems that the homeserver url is valid
getLoginFlowResult(authAPI, versions, homeServerConnectionConfig.homeServerUriBase.toString())
}
.fold(
{
it
},
{
if (it is Failure.OtherServerError
&& it.httpCode == HttpsURLConnection.HTTP_NOT_FOUND /* 404 */) {
// It's maybe a Web client url?
getWebClientDomainLoginFlowInternal(homeServerConnectionConfig)
} else {
throw it
}
}
)
} else {
throw failure
} }
} }
.map { versions ->
// Ok, it seems that the homeserver url is valid
getLoginFlowResult(authAPI, versions, homeServerConnectionConfig.homeServerUri.toString())
}
.fold(
{
it
},
{
if (it is Failure.OtherServerError
&& it.httpCode == HttpsURLConnection.HTTP_NOT_FOUND /* 404 */) {
// It's maybe a Riot url?
getRiotDomainLoginFlowInternal(homeServerConnectionConfig)
} else {
throw it
}
}
)
} }
private suspend fun getRiotDomainLoginFlowInternal(homeServerConnectionConfig: HomeServerConnectionConfig): LoginFlowResult { private suspend fun getWebClientDomainLoginFlowInternal(homeServerConnectionConfig: HomeServerConnectionConfig): LoginFlowResult {
val authAPI = buildAuthAPI(homeServerConnectionConfig) val authAPI = buildAuthAPI(homeServerConnectionConfig)
val domain = homeServerConnectionConfig.homeServerUri.host val domain = homeServerConnectionConfig.homeServerUri.host
?: return getRiotLoginFlowInternal(homeServerConnectionConfig) ?: return getWebClientLoginFlowInternal(homeServerConnectionConfig)
// Ok, try to get the config.domain.json file of a RiotWeb client // Ok, try to get the config.domain.json file of a Web client
return runCatching { return runCatching {
executeRequest(null) { executeRequest(null) {
authAPI.getRiotConfigDomain(domain) authAPI.getWebClientConfigDomain(domain)
} }
} }
.map { riotConfig -> .map { webClientConfig ->
onRiotConfigRetrieved(homeServerConnectionConfig, riotConfig) onWebClientConfigRetrieved(homeServerConnectionConfig, webClientConfig)
} }
.fold( .fold(
{ {
@ -214,7 +228,7 @@ internal class DefaultAuthenticationService @Inject constructor(
if (it is Failure.OtherServerError if (it is Failure.OtherServerError
&& it.httpCode == HttpsURLConnection.HTTP_NOT_FOUND /* 404 */) { && it.httpCode == HttpsURLConnection.HTTP_NOT_FOUND /* 404 */) {
// Try with config.json // Try with config.json
getRiotLoginFlowInternal(homeServerConnectionConfig) getWebClientLoginFlowInternal(homeServerConnectionConfig)
} else { } else {
throw it throw it
} }
@ -222,40 +236,24 @@ internal class DefaultAuthenticationService @Inject constructor(
) )
} }
private suspend fun getRiotLoginFlowInternal(homeServerConnectionConfig: HomeServerConnectionConfig): LoginFlowResult { private suspend fun getWebClientLoginFlowInternal(homeServerConnectionConfig: HomeServerConnectionConfig): LoginFlowResult {
val authAPI = buildAuthAPI(homeServerConnectionConfig) val authAPI = buildAuthAPI(homeServerConnectionConfig)
// Ok, try to get the config.json file of a RiotWeb client // Ok, try to get the config.json file of a Web client
return runCatching { return executeRequest(null) {
executeRequest(null) { authAPI.getWebClientConfig()
authAPI.getRiotConfig()
}
} }
.map { riotConfig -> .let { webClientConfig ->
onRiotConfigRetrieved(homeServerConnectionConfig, riotConfig) onWebClientConfigRetrieved(homeServerConnectionConfig, webClientConfig)
} }
.fold(
{
it
},
{
if (it is Failure.OtherServerError
&& it.httpCode == HttpsURLConnection.HTTP_NOT_FOUND /* 404 */) {
// Try with wellknown
getWellknownLoginFlowInternal(homeServerConnectionConfig)
} else {
throw it
}
}
)
} }
private suspend fun onRiotConfigRetrieved(homeServerConnectionConfig: HomeServerConnectionConfig, riotConfig: RiotConfig): LoginFlowResult { private suspend fun onWebClientConfigRetrieved(homeServerConnectionConfig: HomeServerConnectionConfig, webClientConfig: WebClientConfig): LoginFlowResult {
val defaultHomeServerUrl = riotConfig.getPreferredHomeServerUrl() val defaultHomeServerUrl = webClientConfig.getPreferredHomeServerUrl()
if (defaultHomeServerUrl?.isNotEmpty() == true) { if (defaultHomeServerUrl?.isNotEmpty() == true) {
// Ok, good sign, we got a default hs url // Ok, good sign, we got a default hs url
val newHomeServerConnectionConfig = homeServerConnectionConfig.copy( val newHomeServerConnectionConfig = homeServerConnectionConfig.copy(
homeServerUri = Uri.parse(defaultHomeServerUrl) homeServerUriBase = Uri.parse(defaultHomeServerUrl)
) )
val newAuthAPI = buildAuthAPI(newHomeServerConnectionConfig) val newAuthAPI = buildAuthAPI(newHomeServerConnectionConfig)
@ -275,15 +273,13 @@ internal class DefaultAuthenticationService @Inject constructor(
val domain = homeServerConnectionConfig.homeServerUri.host val domain = homeServerConnectionConfig.homeServerUri.host
?: throw Failure.OtherServerError("", HttpsURLConnection.HTTP_NOT_FOUND /* 404 */) ?: throw Failure.OtherServerError("", HttpsURLConnection.HTTP_NOT_FOUND /* 404 */)
// Create a fake userId, for the getWellknown task val wellknownResult = getWellknownTask.execute(GetWellknownTask.Params(domain, homeServerConnectionConfig))
val fakeUserId = "@alice:$domain"
val wellknownResult = getWellknownTask.execute(GetWellknownTask.Params(fakeUserId, homeServerConnectionConfig))
return when (wellknownResult) { return when (wellknownResult) {
is WellknownResult.Prompt -> { is WellknownResult.Prompt -> {
val newHomeServerConnectionConfig = homeServerConnectionConfig.copy( val newHomeServerConnectionConfig = homeServerConnectionConfig.copy(
homeServerUri = Uri.parse(wellknownResult.homeServerUrl), homeServerUriBase = Uri.parse(wellknownResult.homeServerUrl),
identityServerUri = wellknownResult.identityServerUrl?.let { Uri.parse(it) } identityServerUri = wellknownResult.identityServerUrl?.let { Uri.parse(it) } ?: homeServerConnectionConfig.identityServerUri
) )
val newAuthAPI = buildAuthAPI(newHomeServerConnectionConfig) val newAuthAPI = buildAuthAPI(newHomeServerConnectionConfig)
@ -379,7 +375,14 @@ internal class DefaultAuthenticationService @Inject constructor(
override suspend fun getWellKnownData(matrixId: String, override suspend fun getWellKnownData(matrixId: String,
homeServerConnectionConfig: HomeServerConnectionConfig?): WellknownResult { homeServerConnectionConfig: HomeServerConnectionConfig?): WellknownResult {
return getWellknownTask.execute(GetWellknownTask.Params(matrixId, homeServerConnectionConfig)) if (!MatrixPatterns.isUserId(matrixId)) {
throw MatrixIdFailure.InvalidMatrixId
}
return getWellknownTask.execute(GetWellknownTask.Params(
domain = matrixId.getDomain(),
homeServerConnectionConfig = homeServerConnectionConfig)
)
} }
override suspend fun directAuthentication(homeServerConnectionConfig: HomeServerConnectionConfig, override suspend fun directAuthentication(homeServerConnectionConfig: HomeServerConnectionConfig,
@ -390,7 +393,7 @@ internal class DefaultAuthenticationService @Inject constructor(
} }
private fun buildAuthAPI(homeServerConnectionConfig: HomeServerConnectionConfig): AuthAPI { private fun buildAuthAPI(homeServerConnectionConfig: HomeServerConnectionConfig): AuthAPI {
val retrofit = retrofitFactory.create(buildClient(homeServerConnectionConfig), homeServerConnectionConfig.homeServerUri.toString()) val retrofit = retrofitFactory.create(buildClient(homeServerConnectionConfig), homeServerConnectionConfig.homeServerUriBase.toString())
return retrofit.create(AuthAPI::class.java) return retrofit.create(AuthAPI::class.java)
} }

View file

@ -42,7 +42,7 @@ internal class DefaultIsValidClientServerApiTask @Inject constructor(
override suspend fun execute(params: IsValidClientServerApiTask.Params): Boolean { override suspend fun execute(params: IsValidClientServerApiTask.Params): Boolean {
val client = buildClient(params.homeServerConnectionConfig) val client = buildClient(params.homeServerConnectionConfig)
val homeServerUrl = params.homeServerConnectionConfig.homeServerUri.toString() val homeServerUrl = params.homeServerConnectionConfig.homeServerUriBase.toString()
val authAPI = retrofitFactory.create(client, homeServerUrl) val authAPI = retrofitFactory.create(client, homeServerUrl)
.create(AuthAPI::class.java) .create(AuthAPI::class.java)

View file

@ -38,7 +38,7 @@ internal class DefaultSessionCreator @Inject constructor(
) : SessionCreator { ) : SessionCreator {
/** /**
* Credentials can affect the homeServerConnectionConfig, override home server url and/or * Credentials can affect the homeServerConnectionConfig, override homeserver url and/or
* identity server url if provided in the credentials * identity server url if provided in the credentials
*/ */
override suspend fun createSession(credentials: Credentials, homeServerConnectionConfig: HomeServerConnectionConfig): Session { override suspend fun createSession(credentials: Credentials, homeServerConnectionConfig: HomeServerConnectionConfig): Session {
@ -56,7 +56,7 @@ internal class DefaultSessionCreator @Inject constructor(
tryOrNull { tryOrNull {
isValidClientServerApiTask.execute( isValidClientServerApiTask.execute(
IsValidClientServerApiTask.Params( IsValidClientServerApiTask.Params(
homeServerConnectionConfig.copy(homeServerUri = it) homeServerConnectionConfig.copy(homeServerUriBase = it)
) )
) )
.also { Timber.d("Overriding homeserver url: $it") } .also { Timber.d("Overriding homeserver url: $it") }
@ -66,7 +66,7 @@ internal class DefaultSessionCreator @Inject constructor(
val sessionParams = SessionParams( val sessionParams = SessionParams(
credentials = credentials, credentials = credentials,
homeServerConnectionConfig = homeServerConnectionConfig.copy( homeServerConnectionConfig = homeServerConnectionConfig.copy(
homeServerUri = overriddenUrl ?: homeServerConnectionConfig.homeServerUri, homeServerUriBase = overriddenUrl ?: homeServerConnectionConfig.homeServerUriBase,
identityServerUri = credentials.discoveryInformation?.identityServer?.baseURL identityServerUri = credentials.discoveryInformation?.identityServer?.baseURL
// remove trailing "/" // remove trailing "/"
?.trim { it == '/' } ?.trim { it == '/' }

View file

@ -20,7 +20,7 @@ import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
internal data class RiotConfig( internal data class WebClientConfig(
/** /**
* This is now deprecated, but still used first, rather than value from "default_server_config" * This is now deprecated, but still used first, rather than value from "default_server_config"
*/ */
@ -28,7 +28,7 @@ internal data class RiotConfig(
val defaultHomeServerUrl: String?, val defaultHomeServerUrl: String?,
@Json(name = "default_server_config") @Json(name = "default_server_config")
val defaultServerConfig: RiotConfigDefaultServerConfig? val defaultServerConfig: WebClientConfigDefaultServerConfig?
) { ) {
fun getPreferredHomeServerUrl(): String? { fun getPreferredHomeServerUrl(): String? {
return defaultHomeServerUrl return defaultHomeServerUrl
@ -38,13 +38,13 @@ internal data class RiotConfig(
} }
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
internal data class RiotConfigDefaultServerConfig( internal data class WebClientConfigDefaultServerConfig(
@Json(name = "m.homeserver") @Json(name = "m.homeserver")
val homeServer: RiotConfigBaseConfig? = null val homeServer: WebClientConfigBaseConfig? = null
) )
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
internal data class RiotConfigBaseConfig( internal data class WebClientConfigBaseConfig(
@Json(name = "base_url") @Json(name = "base_url")
val baseURL: String? = null val baseURL: String? = null
) )

View file

@ -16,17 +16,19 @@
package org.matrix.android.sdk.internal.auth.db package org.matrix.android.sdk.internal.auth.db
import org.matrix.android.sdk.api.auth.data.Credentials import android.net.Uri
import org.matrix.android.sdk.api.auth.data.sessionId
import org.matrix.android.sdk.internal.di.MoshiProvider
import io.realm.DynamicRealm import io.realm.DynamicRealm
import io.realm.RealmMigration import io.realm.RealmMigration
import org.matrix.android.sdk.api.auth.data.Credentials
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
import org.matrix.android.sdk.api.auth.data.sessionId
import org.matrix.android.sdk.internal.di.MoshiProvider
import timber.log.Timber import timber.log.Timber
internal object AuthRealmMigration : RealmMigration { internal object AuthRealmMigration : RealmMigration {
// Current schema version // Current schema version
const val SCHEMA_VERSION = 3L const val SCHEMA_VERSION = 4L
override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) { override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) {
Timber.d("Migrating Auth Realm from $oldVersion to $newVersion") Timber.d("Migrating Auth Realm from $oldVersion to $newVersion")
@ -34,6 +36,7 @@ internal object AuthRealmMigration : RealmMigration {
if (oldVersion <= 0) migrateTo1(realm) if (oldVersion <= 0) migrateTo1(realm)
if (oldVersion <= 1) migrateTo2(realm) if (oldVersion <= 1) migrateTo2(realm)
if (oldVersion <= 2) migrateTo3(realm) if (oldVersion <= 2) migrateTo3(realm)
if (oldVersion <= 3) migrateTo4(realm)
} }
private fun migrateTo1(realm: DynamicRealm) { private fun migrateTo1(realm: DynamicRealm) {
@ -81,4 +84,34 @@ internal object AuthRealmMigration : RealmMigration {
} }
?.addPrimaryKey(SessionParamsEntityFields.SESSION_ID) ?.addPrimaryKey(SessionParamsEntityFields.SESSION_ID)
} }
private fun migrateTo4(realm: DynamicRealm) {
Timber.d("Step 3 -> 4")
Timber.d("Update SessionParamsEntity to add HomeServerConnectionConfig.homeServerUriBase value")
val adapter = MoshiProvider.providesMoshi()
.adapter(HomeServerConnectionConfig::class.java)
realm.schema.get("SessionParamsEntity")
?.transform {
val homeserverConnectionConfigJson = it.getString(SessionParamsEntityFields.HOME_SERVER_CONNECTION_CONFIG_JSON)
val homeserverConnectionConfig = adapter
.fromJson(homeserverConnectionConfigJson)
val homeserverUrl = homeserverConnectionConfig?.homeServerUri?.toString()
// Special case for matrix.org. Old session may use "https://matrix.org", newer one may use
// "https://matrix-client.matrix.org". So fix that here
val alteredHomeserverConnectionConfig =
if (homeserverUrl == "https://matrix.org" || homeserverUrl == "https://matrix-client.matrix.org") {
homeserverConnectionConfig.copy(
homeServerUri = Uri.parse("https://matrix.org"),
homeServerUriBase = Uri.parse("https://matrix-client.matrix.org")
)
} else {
homeserverConnectionConfig
}
it.set(SessionParamsEntityFields.HOME_SERVER_CONNECTION_CONFIG_JSON, adapter.toJson(alteredHomeserverConnectionConfig))
}
}
} }

View file

@ -50,7 +50,7 @@ internal class DefaultDirectLoginTask @Inject constructor(
override suspend fun execute(params: DirectLoginTask.Params): Session { override suspend fun execute(params: DirectLoginTask.Params): Session {
val client = buildClient(params.homeServerConnectionConfig) val client = buildClient(params.homeServerConnectionConfig)
val homeServerUrl = params.homeServerConnectionConfig.homeServerUri.toString() val homeServerUrl = params.homeServerConnectionConfig.homeServerUriBase.toString()
val authAPI = retrofitFactory.create(client, homeServerUrl) val authAPI = retrofitFactory.create(client, homeServerUrl)
.create(AuthAPI::class.java) .create(AuthAPI::class.java)

View file

@ -314,6 +314,12 @@ internal class DefaultCryptoService @Inject constructor(
cryptoCoroutineScope.launchToCallback(coroutineDispatchers.crypto, NoOpMatrixCallback()) { cryptoCoroutineScope.launchToCallback(coroutineDispatchers.crypto, NoOpMatrixCallback()) {
// Open the store // Open the store
cryptoStore.open() cryptoStore.open()
if (!cryptoStore.areDeviceKeysUploaded()) {
// Schedule upload of OTK
oneTimeKeysUploader.updateOneTimeKeyCount(0)
}
// this can throw if no network // this can throw if no network
tryOrNull { tryOrNull {
uploadDeviceKeys() uploadDeviceKeys()
@ -905,7 +911,7 @@ internal class DefaultCryptoService @Inject constructor(
* Upload my user's device keys. * Upload my user's device keys.
*/ */
private suspend fun uploadDeviceKeys() { private suspend fun uploadDeviceKeys() {
if (cryptoStore.getDeviceKeysUploaded()) { if (cryptoStore.areDeviceKeysUploaded()) {
Timber.d("Keys already uploaded, nothing to do") Timber.d("Keys already uploaded, nothing to do")
return return
} }

View file

@ -16,6 +16,7 @@
package org.matrix.android.sdk.internal.crypto package org.matrix.android.sdk.internal.crypto
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.MatrixPatterns import org.matrix.android.sdk.api.MatrixPatterns
import org.matrix.android.sdk.api.auth.data.Credentials import org.matrix.android.sdk.api.auth.data.Credentials
@ -336,7 +337,12 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
downloadKeysForUsersTask.execute(params) downloadKeysForUsersTask.execute(params)
} catch (throwable: Throwable) { } catch (throwable: Throwable) {
Timber.e(throwable, "## CRYPTO | doKeyDownloadForUsers(): error") Timber.e(throwable, "## CRYPTO | doKeyDownloadForUsers(): error")
onKeysDownloadFailed(filteredUsers) if (throwable is CancellationException) {
// the crypto module is getting closed, so we cannot access the DB anymore
Timber.w("The crypto module is closed, ignoring this error")
} else {
onKeysDownloadFailed(filteredUsers)
}
throw throwable throw throwable
} }
Timber.v("## CRYPTO | doKeyDownloadForUsers() : Got keys for " + filteredUsers.size + " users") Timber.v("## CRYPTO | doKeyDownloadForUsers() : Got keys for " + filteredUsers.size + " users")

View file

@ -16,6 +16,7 @@
package org.matrix.android.sdk.internal.crypto package org.matrix.android.sdk.internal.crypto
import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.internal.crypto.model.MXKey import org.matrix.android.sdk.internal.crypto.model.MXKey
import org.matrix.android.sdk.internal.crypto.model.rest.KeysUploadResponse import org.matrix.android.sdk.internal.crypto.model.rest.KeysUploadResponse
import org.matrix.android.sdk.internal.crypto.tasks.UploadKeysTask import org.matrix.android.sdk.internal.crypto.tasks.UploadKeysTask
@ -77,6 +78,10 @@ internal class OneTimeKeysUploader @Inject constructor(
// discard the oldest private keys first. This will eventually clean // discard the oldest private keys first. This will eventually clean
// out stale private keys that won't receive a message. // out stale private keys that won't receive a message.
val keyLimit = floor(maxOneTimeKeys / 2.0).toInt() val keyLimit = floor(maxOneTimeKeys / 2.0).toInt()
if (oneTimeKeyCount == null) {
// Ask the server how many otk he has
oneTimeKeyCount = fetchOtkCount()
}
val oneTimeKeyCountFromSync = oneTimeKeyCount val oneTimeKeyCountFromSync = oneTimeKeyCount
if (oneTimeKeyCountFromSync != null) { if (oneTimeKeyCountFromSync != null) {
// We need to keep a pool of one time public keys on the server so that // We need to keep a pool of one time public keys on the server so that
@ -90,17 +95,22 @@ internal class OneTimeKeysUploader @Inject constructor(
// private keys clogging up our local storage. // private keys clogging up our local storage.
// So we need some kind of engineering compromise to balance all of // So we need some kind of engineering compromise to balance all of
// these factors. // these factors.
try { tryOrNull("Unable to upload OTK") {
val uploadedKeys = uploadOTK(oneTimeKeyCountFromSync, keyLimit) val uploadedKeys = uploadOTK(oneTimeKeyCountFromSync, keyLimit)
Timber.v("## uploadKeys() : success, $uploadedKeys key(s) sent") Timber.v("## uploadKeys() : success, $uploadedKeys key(s) sent")
} finally {
oneTimeKeyCheckInProgress = false
} }
} else { } else {
Timber.w("maybeUploadOneTimeKeys: waiting to know the number of OTK from the sync") Timber.w("maybeUploadOneTimeKeys: waiting to know the number of OTK from the sync")
oneTimeKeyCheckInProgress = false
lastOneTimeKeyCheck = 0 lastOneTimeKeyCheck = 0
} }
oneTimeKeyCheckInProgress = false
}
private suspend fun fetchOtkCount(): Int? {
return tryOrNull("Unable to get OTK count") {
val result = uploadKeysTask.execute(UploadKeysTask.Params(null, null))
result.oneTimeKeyCountsForAlgorithm(MXKey.KEY_SIGNED_CURVE_25519_TYPE)
}
} }
/** /**

View file

@ -56,7 +56,7 @@ data class MXDeviceInfo(
val signatures: Map<String, Map<String, String>>? = null, val signatures: Map<String, Map<String, String>>? = null,
/* /*
* Additional data from the home server. * Additional data from the homeserver.
*/ */
@Json(name = "unsigned") @Json(name = "unsigned")
val unsigned: JsonDict? = null, val unsigned: JsonDict? = null,

View file

@ -475,7 +475,7 @@ internal interface IMXCryptoStore {
fun getGossipingEvents(): List<Event> fun getGossipingEvents(): List<Event>
fun setDeviceKeysUploaded(uploaded: Boolean) fun setDeviceKeysUploaded(uploaded: Boolean)
fun getDeviceKeysUploaded(): Boolean fun areDeviceKeysUploaded(): Boolean
fun tidyUpDataBase() fun tidyUpDataBase()
fun logDbUsageInfo() fun logDbUsageInfo()
} }

View file

@ -937,7 +937,7 @@ internal class RealmCryptoStore @Inject constructor(
} }
} }
override fun getDeviceKeysUploaded(): Boolean { override fun areDeviceKeysUploaded(): Boolean {
return doWithRealm(realmConfiguration) { return doWithRealm(realmConfiguration) {
it.where<CryptoMetadataEntity>().findFirst()?.deviceKeysSentToServer it.where<CryptoMetadataEntity>().findFirst()?.deviceKeysSentToServer
} ?: false } ?: false

View file

@ -97,7 +97,7 @@ internal class DefaultInitializeCrossSigningTask @Inject constructor(
Timber.v("## CrossSigning - sskPublicKey:$sskPublicKey") Timber.v("## CrossSigning - sskPublicKey:$sskPublicKey")
// Sign userSigningKey with master // Sign selfSigningKey with master
val signedSSK = CryptoCrossSigningKey.Builder(userId, KeyUsage.SELF_SIGNING) val signedSSK = CryptoCrossSigningKey.Builder(userId, KeyUsage.SELF_SIGNING)
.key(sskPublicKey) .key(sskPublicKey)
.build() .build()

View file

@ -68,7 +68,7 @@ internal class VerificationTransportToDevice(
contentMap.setObject(otherUserId, it, keyReq) contentMap.setObject(otherUserId, it, keyReq)
} }
sendToDeviceTask sendToDeviceTask
.configureWith(SendToDeviceTask.Params(MessageType.MSGTYPE_VERIFICATION_REQUEST, contentMap, localId)) { .configureWith(SendToDeviceTask.Params(MessageType.MSGTYPE_VERIFICATION_REQUEST, contentMap)) {
this.callback = object : MatrixCallback<Unit> { this.callback = object : MatrixCallback<Unit> {
override fun onSuccess(data: Unit) { override fun onSuccess(data: Unit) {
Timber.v("## verification [$tx.transactionId] send toDevice request success") Timber.v("## verification [$tx.transactionId] send toDevice request success")
@ -124,7 +124,7 @@ internal class VerificationTransportToDevice(
contentMap.setObject(tx.otherUserId, tx.otherDeviceId, toSendToDeviceObject) contentMap.setObject(tx.otherUserId, tx.otherDeviceId, toSendToDeviceObject)
sendToDeviceTask sendToDeviceTask
.configureWith(SendToDeviceTask.Params(type, contentMap, tx.transactionId)) { .configureWith(SendToDeviceTask.Params(type, contentMap)) {
this.callback = object : MatrixCallback<Unit> { this.callback = object : MatrixCallback<Unit> {
override fun onSuccess(data: Unit) { override fun onSuccess(data: Unit) {
Timber.v("## SAS verification [$tx.transactionId] toDevice type '$type' success.") Timber.v("## SAS verification [$tx.transactionId] toDevice type '$type' success.")
@ -155,7 +155,7 @@ internal class VerificationTransportToDevice(
val contentMap = MXUsersDevicesMap<Any>() val contentMap = MXUsersDevicesMap<Any>()
contentMap.setObject(otherUserId, otherUserDeviceId, cancelMessage) contentMap.setObject(otherUserId, otherUserDeviceId, cancelMessage)
sendToDeviceTask sendToDeviceTask
.configureWith(SendToDeviceTask.Params(EventType.KEY_VERIFICATION_DONE, contentMap, transactionId)) { .configureWith(SendToDeviceTask.Params(EventType.KEY_VERIFICATION_DONE, contentMap)) {
this.callback = object : MatrixCallback<Unit> { this.callback = object : MatrixCallback<Unit> {
override fun onSuccess(data: Unit) { override fun onSuccess(data: Unit) {
onDone?.invoke() onDone?.invoke()
@ -176,7 +176,7 @@ internal class VerificationTransportToDevice(
val contentMap = MXUsersDevicesMap<Any>() val contentMap = MXUsersDevicesMap<Any>()
contentMap.setObject(otherUserId, otherUserDeviceId, cancelMessage) contentMap.setObject(otherUserId, otherUserDeviceId, cancelMessage)
sendToDeviceTask sendToDeviceTask
.configureWith(SendToDeviceTask.Params(EventType.KEY_VERIFICATION_CANCEL, contentMap, transactionId)) { .configureWith(SendToDeviceTask.Params(EventType.KEY_VERIFICATION_CANCEL, contentMap)) {
this.callback = object : MatrixCallback<Unit> { this.callback = object : MatrixCallback<Unit> {
override fun onSuccess(data: Unit) { override fun onSuccess(data: Unit) {
Timber.v("## SAS verification [$transactionId] canceled for reason ${code.value}") Timber.v("## SAS verification [$transactionId] canceled for reason ${code.value}")

View file

@ -46,7 +46,7 @@ import timber.log.Timber
internal object RealmSessionStoreMigration : RealmMigration { internal object RealmSessionStoreMigration : RealmMigration {
const val SESSION_STORE_SCHEMA_VERSION = 15L const val SESSION_STORE_SCHEMA_VERSION = 16L
override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) { override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) {
Timber.v("Migrating Realm Session from $oldVersion to $newVersion") Timber.v("Migrating Realm Session from $oldVersion to $newVersion")
@ -66,6 +66,7 @@ internal object RealmSessionStoreMigration : RealmMigration {
if (oldVersion <= 12) migrateTo13(realm) if (oldVersion <= 12) migrateTo13(realm)
if (oldVersion <= 13) migrateTo14(realm) if (oldVersion <= 13) migrateTo14(realm)
if (oldVersion <= 14) migrateTo15(realm) if (oldVersion <= 14) migrateTo15(realm)
if (oldVersion <= 15) migrateTo16(realm)
} }
private fun migrateTo1(realm: DynamicRealm) { private fun migrateTo1(realm: DynamicRealm) {
@ -308,6 +309,7 @@ internal object RealmSessionStoreMigration : RealmMigration {
} }
private fun migrateTo15(realm: DynamicRealm) { private fun migrateTo15(realm: DynamicRealm) {
Timber.d("Step 14 -> 15")
// fix issue with flattenParentIds on DM that kept growing with duplicate // fix issue with flattenParentIds on DM that kept growing with duplicate
// so we reset it, will be updated next sync // so we reset it, will be updated next sync
realm.where("RoomSummaryEntity") realm.where("RoomSummaryEntity")
@ -318,4 +320,14 @@ internal object RealmSessionStoreMigration : RealmMigration {
it.setString(RoomSummaryEntityFields.FLATTEN_PARENT_IDS, null) it.setString(RoomSummaryEntityFields.FLATTEN_PARENT_IDS, null)
} }
} }
private fun migrateTo16(realm: DynamicRealm) {
Timber.d("Step 15 -> 16")
realm.schema.get("HomeServerCapabilitiesEntity")
?.addField(HomeServerCapabilitiesEntityFields.ROOM_VERSIONS_JSON, String::class.java)
?.transform { obj ->
// Schedule a refresh of the capabilities
obj.setLong(HomeServerCapabilitiesEntityFields.LAST_UPDATED_TIMESTAMP, 0)
}
}
} }

View file

@ -16,8 +16,15 @@
package org.matrix.android.sdk.internal.database.mapper package org.matrix.android.sdk.internal.database.mapper
import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities
import org.matrix.android.sdk.api.session.homeserver.RoomVersionCapabilities
import org.matrix.android.sdk.api.session.homeserver.RoomVersionInfo
import org.matrix.android.sdk.api.session.homeserver.RoomVersionStatus
import org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEntity import org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEntity
import org.matrix.android.sdk.internal.di.MoshiProvider
import org.matrix.android.sdk.internal.session.homeserver.RoomVersions
import org.matrix.android.sdk.internal.session.room.version.DefaultRoomVersionService
/** /**
* HomeServerCapabilitiesEntity -> HomeSeverCapabilities * HomeServerCapabilitiesEntity -> HomeSeverCapabilities
@ -29,7 +36,30 @@ internal object HomeServerCapabilitiesMapper {
canChangePassword = entity.canChangePassword, canChangePassword = entity.canChangePassword,
maxUploadFileSize = entity.maxUploadFileSize, maxUploadFileSize = entity.maxUploadFileSize,
lastVersionIdentityServerSupported = entity.lastVersionIdentityServerSupported, lastVersionIdentityServerSupported = entity.lastVersionIdentityServerSupported,
defaultIdentityServerUrl = entity.defaultIdentityServerUrl defaultIdentityServerUrl = entity.defaultIdentityServerUrl,
roomVersions = mapRoomVersion(entity.roomVersionsJson)
) )
} }
private fun mapRoomVersion(roomVersionsJson: String?): RoomVersionCapabilities? {
roomVersionsJson ?: return null
return tryOrNull {
MoshiProvider.providesMoshi().adapter(RoomVersions::class.java).fromJson(roomVersionsJson)?.let {
RoomVersionCapabilities(
defaultRoomVersion = it.default ?: DefaultRoomVersionService.DEFAULT_ROOM_VERSION,
supportedVersion = it.available.entries.map { entry ->
RoomVersionInfo(
version = entry.key,
status = if (entry.value == "stable") {
RoomVersionStatus.STABLE
} else {
RoomVersionStatus.UNSTABLE
}
)
}
)
}
}
}
} }

View file

@ -21,6 +21,7 @@ import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities
internal open class HomeServerCapabilitiesEntity( internal open class HomeServerCapabilitiesEntity(
var canChangePassword: Boolean = true, var canChangePassword: Boolean = true,
var roomVersionsJson: String? = null,
var maxUploadFileSize: Long = HomeServerCapabilities.MAX_UPLOAD_FILE_SIZE_UNKNOWN, var maxUploadFileSize: Long = HomeServerCapabilities.MAX_UPLOAD_FILE_SIZE_UNKNOWN,
var lastVersionIdentityServerSupported: Boolean = false, var lastVersionIdentityServerSupported: Boolean = false,
var defaultIdentityServerUrl: String? = null, var defaultIdentityServerUrl: String? = null,

View file

@ -20,7 +20,7 @@ import io.realm.RealmObject
import io.realm.annotations.Index import io.realm.annotations.Index
/** /**
* Clients can store custom config data for their account on their HomeServer. * Clients can store custom config data for their account on their homeserver.
* This account data will be synced between different devices and can persist across installations on a particular device. * This account data will be synced between different devices and can persist across installations on a particular device.
* Users may only view the account data for their own account. * Users may only view the account data for their own account.
* The account_data may be either global or scoped to a particular rooms. * The account_data may be either global or scoped to a particular rooms.

View file

@ -37,7 +37,8 @@ internal abstract class FederationModule {
fun providesFederationAPI(@Unauthenticated okHttpClient: Lazy<OkHttpClient>, fun providesFederationAPI(@Unauthenticated okHttpClient: Lazy<OkHttpClient>,
sessionParams: SessionParams, sessionParams: SessionParams,
retrofitFactory: RetrofitFactory): FederationAPI { retrofitFactory: RetrofitFactory): FederationAPI {
return retrofitFactory.create(okHttpClient, sessionParams.homeServerUrl).create(FederationAPI::class.java) return retrofitFactory.create(okHttpClient, sessionParams.homeServerUrlBase)
.create(FederationAPI::class.java)
} }
} }

View file

@ -41,7 +41,7 @@ public class Credentials {
public String deviceId; public String deviceId;
// Optional data that may contain info to override home server and/or identity server // Optional data that may contain info to override homeserver and/or identity server
public WellKnown wellKnown; public WellKnown wellKnown;
public JSONObject toJson() throws JSONException { public JSONObject toJson() throws JSONException {

View file

@ -44,7 +44,7 @@ import timber.log.Timber;
*/ */
public class HomeServerConnectionConfig { public class HomeServerConnectionConfig {
// the home server URI // the homeserver URI
private Uri mHomeServerUri; private Uri mHomeServerUri;
// the jitsi server URI. Can be null // the jitsi server URI. Can be null
@Nullable @Nullable
@ -82,7 +82,7 @@ public class HomeServerConnectionConfig {
} }
/** /**
* Update the home server URI. * Update the homeserver URI.
* *
* @param uri the new HS uri * @param uri the new HS uri
*/ */
@ -91,7 +91,7 @@ public class HomeServerConnectionConfig {
} }
/** /**
* @return the home server uri * @return the homeserver uri
*/ */
public Uri getHomeserverUri() { public Uri getHomeserverUri() {
return mHomeServerUri; return mHomeServerUri;
@ -145,7 +145,7 @@ public class HomeServerConnectionConfig {
public void setCredentials(Credentials credentials) { public void setCredentials(Credentials credentials) {
mCredentials = credentials; mCredentials = credentials;
// Override home server url and/or identity server url if provided // Override homeserver url and/or identity server url if provided
if (credentials.wellKnown != null) { if (credentials.wellKnown != null) {
if (credentials.wellKnown.homeServer != null) { if (credentials.wellKnown.homeServer != null) {
String homeServerUrl = credentials.wellKnown.homeServer.baseURL; String homeServerUrl = credentials.wellKnown.homeServer.baseURL;
@ -200,7 +200,7 @@ public class HomeServerConnectionConfig {
} }
/** /**
* TLS versions accepted for TLS connections with the home server. * TLS versions accepted for TLS connections with the homeserver.
*/ */
@Nullable @Nullable
public List<TlsVersion> getAcceptedTlsVersions() { public List<TlsVersion> getAcceptedTlsVersions() {
@ -208,7 +208,7 @@ public class HomeServerConnectionConfig {
} }
/** /**
* TLS cipher suites accepted for TLS connections with the home server. * TLS cipher suites accepted for TLS connections with the homeserver.
*/ */
@Nullable @Nullable
public List<CipherSuite> getAcceptedTlsCipherSuites() { public List<CipherSuite> getAcceptedTlsCipherSuites() {
@ -426,7 +426,7 @@ public class HomeServerConnectionConfig {
*/ */
public Builder withHomeServerUri(final Uri homeServerUri) { public Builder withHomeServerUri(final Uri homeServerUri) {
if (homeServerUri == null || (!"http".equals(homeServerUri.getScheme()) && !"https".equals(homeServerUri.getScheme()))) { if (homeServerUri == null || (!"http".equals(homeServerUri.getScheme()) && !"https".equals(homeServerUri.getScheme()))) {
throw new RuntimeException("Invalid home server URI: " + homeServerUri); throw new RuntimeException("Invalid homeserver URI: " + homeServerUri);
} }
// remove trailing / // remove trailing /
@ -435,7 +435,7 @@ public class HomeServerConnectionConfig {
String url = homeServerUri.toString(); String url = homeServerUri.toString();
mHomeServerConnectionConfig.mHomeServerUri = Uri.parse(url.substring(0, url.length() - 1)); mHomeServerConnectionConfig.mHomeServerUri = Uri.parse(url.substring(0, url.length() - 1));
} catch (Exception e) { } catch (Exception e) {
throw new RuntimeException("Invalid home server URI: " + homeServerUri); throw new RuntimeException("Invalid homeserver URI: " + homeServerUri);
} }
} else { } else {
mHomeServerConnectionConfig.mHomeServerUri = homeServerUri; mHomeServerConnectionConfig.mHomeServerUri = homeServerUri;
@ -549,7 +549,7 @@ public class HomeServerConnectionConfig {
} }
/** /**
* Add an accepted TLS version for TLS connections with the home server. * Add an accepted TLS version for TLS connections with the homeserver.
* *
* @param tlsVersion the tls version to add to the set of TLS versions accepted. * @param tlsVersion the tls version to add to the set of TLS versions accepted.
* @return this builder * @return this builder
@ -577,7 +577,7 @@ public class HomeServerConnectionConfig {
} }
/** /**
* Add a TLS cipher suite to the list of accepted TLS connections with the home server. * Add a TLS cipher suite to the list of accepted TLS connections with the homeserver.
* *
* @param tlsCipherSuite the tls cipher suite to add. * @param tlsCipherSuite the tls cipher suite to add.
* @return this builder * @return this builder
@ -666,7 +666,7 @@ public class HomeServerConnectionConfig {
public HomeServerConnectionConfig build() { public HomeServerConnectionConfig build() {
// Check mandatory parameters // Check mandatory parameters
if (mHomeServerConnectionConfig.mHomeServerUri == null) { if (mHomeServerConnectionConfig.mHomeServerUri == null) {
throw new RuntimeException("Home server URI not set"); throw new RuntimeException("Homeserver URI not set");
} }
return mHomeServerConnectionConfig; return mHomeServerConnectionConfig;

View file

@ -38,7 +38,7 @@ import timber.log.Timber;
public class LoginStorage { public class LoginStorage {
private static final String PREFS_LOGIN = "Vector.LoginStorage"; private static final String PREFS_LOGIN = "Vector.LoginStorage";
// multi accounts + home server config // multi accounts + homeserver config
private static final String PREFS_KEY_CONNECTION_CONFIGS = "PREFS_KEY_CONNECTION_CONFIGS"; private static final String PREFS_KEY_CONNECTION_CONFIGS = "PREFS_KEY_CONNECTION_CONFIGS";
private final Context mContext; private final Context mContext;
@ -49,7 +49,7 @@ public class LoginStorage {
} }
/** /**
* @return the list of home server configurations. * @return the list of homeserver configurations.
*/ */
public List<HomeServerConnectionConfig> getCredentialsList() { public List<HomeServerConnectionConfig> getCredentialsList() {
SharedPreferences prefs = mContext.getSharedPreferences(PREFS_LOGIN, Context.MODE_PRIVATE); SharedPreferences prefs = mContext.getSharedPreferences(PREFS_LOGIN, Context.MODE_PRIVATE);
@ -85,7 +85,7 @@ public class LoginStorage {
/** /**
* Add a credentials to the credentials list * Add a credentials to the credentials list
* *
* @param config the home server config to add. * @param config the homeserver config to add.
*/ */
public void addCredentials(HomeServerConnectionConfig config) { public void addCredentials(HomeServerConnectionConfig config) {
if (null != config && config.getCredentials() != null) { if (null != config && config.getCredentials() != null) {
@ -203,4 +203,4 @@ public class LoginStorage {
//Need to commit now because called before forcing an app restart //Need to commit now because called before forcing an app restart
editor.commit(); editor.commit();
} }
} }

View file

@ -253,7 +253,7 @@ internal object CertUtil {
val list = ArrayList<ConnectionSpec>() val list = ArrayList<ConnectionSpec>()
list.add(builder.build()) list.add(builder.build())
// TODO: we should display a warning if user enter an http url // TODO: we should display a warning if user enter an http url
if (hsConfig.allowHttpExtension || hsConfig.homeServerUri.toString().startsWith("http://")) { if (hsConfig.allowHttpExtension || hsConfig.homeServerUriBase.toString().startsWith("http://")) {
list.add(ConnectionSpec.CLEARTEXT) list.add(ConnectionSpec.CLEARTEXT)
} }
return list return list

View file

@ -29,10 +29,9 @@ internal class DefaultRawService @Inject constructor(
return getUrlTask.execute(GetUrlTask.Params(url, cacheStrategy)) return getUrlTask.execute(GetUrlTask.Params(url, cacheStrategy))
} }
override suspend fun getWellknown(userId: String): String { override suspend fun getWellknown(domain: String): String {
val homeServerDomain = userId.substringAfter(":")
return getUrl( return getUrl(
"https://$homeServerDomain/.well-known/matrix/client", "https://$domain/.well-known/matrix/client",
CacheStrategy.TtlCache(TimeUnit.HOURS.toMillis(8), false) CacheStrategy.TtlCache(TimeUnit.HOURS.toMillis(8), false)
) )
} }

View file

@ -33,6 +33,7 @@ import org.matrix.android.sdk.internal.di.SessionDownloadsDirectory
import org.matrix.android.sdk.internal.di.UnauthenticatedWithCertificateWithProgress import org.matrix.android.sdk.internal.di.UnauthenticatedWithCertificateWithProgress
import org.matrix.android.sdk.internal.session.download.DownloadProgressInterceptor.Companion.DOWNLOAD_PROGRESS_INTERCEPTOR_HEADER import org.matrix.android.sdk.internal.session.download.DownloadProgressInterceptor.Companion.DOWNLOAD_PROGRESS_INTERCEPTOR_HEADER
import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers
import org.matrix.android.sdk.internal.util.file.AtomicFileCreator
import org.matrix.android.sdk.internal.util.md5 import org.matrix.android.sdk.internal.util.md5
import org.matrix.android.sdk.internal.util.writeToFile import org.matrix.android.sdk.internal.util.writeToFile
import timber.log.Timber import timber.log.Timber
@ -96,6 +97,9 @@ internal class DefaultFileService @Inject constructor(
} }
} }
var atomicFileDownload: AtomicFileCreator? = null
var atomicFileDecrypt: AtomicFileCreator? = null
if (existingDownload != null) { if (existingDownload != null) {
// FIXME If the first downloader cancels then we'll unfortunately be cancelled too. // FIXME If the first downloader cancels then we'll unfortunately be cancelled too.
return existingDownload.await() return existingDownload.await()
@ -131,8 +135,11 @@ internal class DefaultFileService @Inject constructor(
Timber.v("Response size ${response.body?.contentLength()} - Stream available: ${!source.exhausted()}") Timber.v("Response size ${response.body?.contentLength()} - Stream available: ${!source.exhausted()}")
// Write the file to cache (encrypted version if the file is encrypted) // Write the file to cache (encrypted version if the file is encrypted)
writeToFile(source.inputStream(), cachedFiles.file) // Write to a part file first, so if we abort before done, we don't have a broken cached file
val atomicFileCreator = AtomicFileCreator(cachedFiles.file).also { atomicFileDownload = it }
writeToFile(source.inputStream(), atomicFileCreator.partFile)
response.close() response.close()
atomicFileCreator.commit()
} else { } else {
Timber.v("## FileService: cache hit for $url") Timber.v("## FileService: cache hit for $url")
} }
@ -145,8 +152,10 @@ internal class DefaultFileService @Inject constructor(
Timber.v("## FileService: decrypt file") Timber.v("## FileService: decrypt file")
// Ensure the parent folder exists // Ensure the parent folder exists
cachedFiles.decryptedFile.parentFile?.mkdirs() cachedFiles.decryptedFile.parentFile?.mkdirs()
// Write to a part file first, so if we abort before done, we don't have a broken cached file
val atomicFileCreator = AtomicFileCreator(cachedFiles.decryptedFile).also { atomicFileDecrypt = it }
val decryptSuccess = cachedFiles.file.inputStream().use { inputStream -> val decryptSuccess = cachedFiles.file.inputStream().use { inputStream ->
cachedFiles.decryptedFile.outputStream().buffered().use { outputStream -> atomicFileCreator.partFile.outputStream().buffered().use { outputStream ->
MXEncryptedAttachments.decryptAttachment( MXEncryptedAttachments.decryptAttachment(
inputStream, inputStream,
elementToDecrypt, elementToDecrypt,
@ -154,6 +163,7 @@ internal class DefaultFileService @Inject constructor(
) )
} }
} }
atomicFileCreator.commit()
if (!decryptSuccess) { if (!decryptSuccess) {
throw IllegalStateException("Decryption error") throw IllegalStateException("Decryption error")
} }
@ -174,6 +184,11 @@ internal class DefaultFileService @Inject constructor(
} }
toNotify?.completeWith(result) toNotify?.completeWith(result)
result.onFailure {
atomicFileDownload?.cancel()
atomicFileDecrypt?.cancel()
}
return result.getOrThrow() return result.getOrThrow()
} }

View file

@ -313,7 +313,7 @@ internal class DefaultSession @Inject constructor(
override fun getUiaSsoFallbackUrl(authenticationSessionId: String): String { override fun getUiaSsoFallbackUrl(authenticationSessionId: String): String {
val hsBas = sessionParams.homeServerConnectionConfig val hsBas = sessionParams.homeServerConnectionConfig
.homeServerUri .homeServerUriBase
.toString() .toString()
.trim { it == '/' } .trim { it == '/' }
return buildString { return buildString {

View file

@ -261,7 +261,7 @@ internal abstract class SessionModule {
sessionParams: SessionParams, sessionParams: SessionParams,
retrofitFactory: RetrofitFactory): Retrofit { retrofitFactory: RetrofitFactory): Retrofit {
return retrofitFactory return retrofitFactory
.create(okHttpClient, sessionParams.homeServerConnectionConfig.homeServerUri.toString()) .create(okHttpClient, sessionParams.homeServerConnectionConfig.homeServerUriBase.toString())
} }
@JvmStatic @JvmStatic

View file

@ -20,11 +20,14 @@ import io.realm.Realm
import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.internal.database.model.EventInsertType import org.matrix.android.sdk.internal.database.model.EventInsertType
import org.matrix.android.sdk.api.logger.LoggerTag
import org.matrix.android.sdk.internal.session.EventInsertLiveProcessor import org.matrix.android.sdk.internal.session.EventInsertLiveProcessor
import org.matrix.android.sdk.internal.session.SessionScope import org.matrix.android.sdk.internal.session.SessionScope
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
private val loggerTag = LoggerTag("CallEventProcessor", LoggerTag.VOIP)
@SessionScope @SessionScope
internal class CallEventProcessor @Inject constructor(private val callSignalingHandler: CallSignalingHandler) internal class CallEventProcessor @Inject constructor(private val callSignalingHandler: CallSignalingHandler)
: EventInsertLiveProcessor { : EventInsertLiveProcessor {
@ -71,14 +74,8 @@ internal class CallEventProcessor @Inject constructor(private val callSignalingH
} }
private fun dispatchToCallSignalingHandlerIfNeeded(event: Event) { private fun dispatchToCallSignalingHandlerIfNeeded(event: Event) {
val now = System.currentTimeMillis()
event.roomId ?: return Unit.also { event.roomId ?: return Unit.also {
Timber.w("Event with no room id ${event.eventId}") Timber.tag(loggerTag.value).w("Event with no room id ${event.eventId}")
}
val age = now - (event.ageLocalTs ?: now)
if (age > 40_000) {
// Too old to ring?
return
} }
callSignalingHandler.onCallEvent(event) callSignalingHandler.onCallEvent(event)
} }

View file

@ -16,6 +16,7 @@
package org.matrix.android.sdk.internal.session.call package org.matrix.android.sdk.internal.session.call
import org.matrix.android.sdk.api.logger.LoggerTag
import org.matrix.android.sdk.api.session.call.CallListener import org.matrix.android.sdk.api.session.call.CallListener
import org.matrix.android.sdk.api.session.call.CallState import org.matrix.android.sdk.api.session.call.CallState
import org.matrix.android.sdk.api.session.call.MxCall import org.matrix.android.sdk.api.session.call.MxCall
@ -36,6 +37,9 @@ import org.matrix.android.sdk.internal.session.SessionScope
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
private val loggerTag = LoggerTag("CallSignalingHandler", LoggerTag.VOIP)
private const val MAX_AGE_TO_RING = 40_000
@SessionScope @SessionScope
internal class CallSignalingHandler @Inject constructor(private val activeCallHandler: ActiveCallHandler, internal class CallSignalingHandler @Inject constructor(private val activeCallHandler: ActiveCallHandler,
private val mxCallFactory: MxCallFactory, private val mxCallFactory: MxCallFactory,
@ -111,12 +115,12 @@ internal class CallSignalingHandler @Inject constructor(private val activeCallHa
return return
} }
if (call.isOutgoing) { if (call.isOutgoing) {
Timber.v("Got selectAnswer for an outbound call: ignoring") Timber.tag(loggerTag.value).v("Got selectAnswer for an outbound call: ignoring")
return return
} }
val selectedPartyId = content.selectedPartyId val selectedPartyId = content.selectedPartyId
if (selectedPartyId == null) { if (selectedPartyId == null) {
Timber.w("Got nonsensical select_answer with null selected_party_id: ignoring") Timber.tag(loggerTag.value).w("Got nonsensical select_answer with null selected_party_id: ignoring")
return return
} }
callListenersDispatcher.onCallSelectAnswerReceived(content) callListenersDispatcher.onCallSelectAnswerReceived(content)
@ -130,7 +134,7 @@ internal class CallSignalingHandler @Inject constructor(private val activeCallHa
return return
} }
if (call.opponentPartyId != null && !call.partyIdsMatches(content)) { if (call.opponentPartyId != null && !call.partyIdsMatches(content)) {
Timber.v("Ignoring candidates from party ID ${content.partyId} we have chosen party ID ${call.opponentPartyId}") Timber.tag(loggerTag.value).v("Ignoring candidates from party ID ${content.partyId} we have chosen party ID ${call.opponentPartyId}")
return return
} }
callListenersDispatcher.onCallIceCandidateReceived(call, content) callListenersDispatcher.onCallIceCandidateReceived(call, content)
@ -163,10 +167,10 @@ internal class CallSignalingHandler @Inject constructor(private val activeCallHa
// party ID must match (our chosen partner hanging up the call) or be undefined (we haven't chosen // party ID must match (our chosen partner hanging up the call) or be undefined (we haven't chosen
// a partner yet but we're treating the hangup as a reject as per VoIP v0) // a partner yet but we're treating the hangup as a reject as per VoIP v0)
if (call.opponentPartyId != null && !call.partyIdsMatches(content)) { if (call.opponentPartyId != null && !call.partyIdsMatches(content)) {
Timber.v("Ignoring hangup from party ID ${content.partyId} we have chosen party ID ${call.opponentPartyId}") Timber.tag(loggerTag.value).v("Ignoring hangup from party ID ${content.partyId} we have chosen party ID ${call.opponentPartyId}")
return return
} }
if (call.state != CallState.Terminated) { if (call.state !is CallState.Ended) {
activeCallHandler.removeCall(content.callId) activeCallHandler.removeCall(content.callId)
callListenersDispatcher.onCallHangupReceived(content) callListenersDispatcher.onCallHangupReceived(content)
} }
@ -180,12 +184,18 @@ internal class CallSignalingHandler @Inject constructor(private val activeCallHa
if (event.roomId == null || event.senderId == null) { if (event.roomId == null || event.senderId == null) {
return return
} }
val now = System.currentTimeMillis()
val age = now - (event.ageLocalTs ?: now)
if (age > MAX_AGE_TO_RING) {
Timber.tag(loggerTag.value).w("Call invite is too old to ring.")
return
}
val content = event.getClearContent().toModel<CallInviteContent>() ?: return val content = event.getClearContent().toModel<CallInviteContent>() ?: return
content.callId ?: return content.callId ?: return
if (invitedCallIds.contains(content.callId)) { if (invitedCallIds.contains(content.callId)) {
// Call is already known, maybe due to fast lane. Ignore // Call is already known, maybe due to fast lane. Ignore
Timber.d("Ignoring already known call invite") Timber.tag(loggerTag.value).d("Ignoring already known call invite")
return return
} }
val incomingCall = mxCallFactory.createIncomingCall( val incomingCall = mxCallFactory.createIncomingCall(
@ -214,7 +224,8 @@ internal class CallSignalingHandler @Inject constructor(private val activeCallHa
callListenersDispatcher.onCallManagedByOtherSession(content.callId) callListenersDispatcher.onCallManagedByOtherSession(content.callId)
} else { } else {
if (call.opponentPartyId != null) { if (call.opponentPartyId != null) {
Timber.v("Ignoring answer from party ID ${content.partyId} we already have an answer from ${call.opponentPartyId}") Timber.tag(loggerTag.value)
.v("Ignoring answer from party ID ${content.partyId} we already have an answer from ${call.opponentPartyId}")
return return
} }
mxCallFactory.updateOutgoingCallWithOpponentData(call, event.senderId, content, content.capabilities) mxCallFactory.updateOutgoingCallWithOpponentData(call, event.senderId, content, content.capabilities)
@ -231,7 +242,7 @@ internal class CallSignalingHandler @Inject constructor(private val activeCallHa
activeCallHandler.getCallWithId(it) activeCallHandler.getCallWithId(it)
} }
if (currentCall == null) { if (currentCall == null) {
Timber.v("Call with id $callId is null") Timber.tag(loggerTag.value).v("Call with id $callId is null")
} }
return currentCall return currentCall
} }

View file

@ -21,7 +21,6 @@ import org.matrix.android.sdk.api.session.call.CallSignalingService
import org.matrix.android.sdk.api.session.call.MxCall import org.matrix.android.sdk.api.session.call.MxCall
import org.matrix.android.sdk.api.session.call.TurnServerResponse import org.matrix.android.sdk.api.session.call.TurnServerResponse
import org.matrix.android.sdk.internal.session.SessionScope import org.matrix.android.sdk.internal.session.SessionScope
import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
@SessionScope @SessionScope
@ -51,7 +50,6 @@ internal class DefaultCallSignalingService @Inject constructor(
} }
override fun getCallWithId(callId: String): MxCall? { override fun getCallWithId(callId: String): MxCall? {
Timber.v("## VOIP getCallWithId $callId all calls ${activeCallHandler.getActiveCallsLiveData().value?.map { it.callId }}")
return activeCallHandler.getCallWithId(callId) return activeCallHandler.getCallWithId(callId)
} }

View file

@ -17,6 +17,7 @@
package org.matrix.android.sdk.internal.session.call.model package org.matrix.android.sdk.internal.session.call.model
import org.matrix.android.sdk.api.MatrixConfiguration import org.matrix.android.sdk.api.MatrixConfiguration
import org.matrix.android.sdk.api.logger.LoggerTag
import org.matrix.android.sdk.api.session.call.CallIdGenerator import org.matrix.android.sdk.api.session.call.CallIdGenerator
import org.matrix.android.sdk.api.session.call.CallState import org.matrix.android.sdk.api.session.call.CallState
import org.matrix.android.sdk.api.session.call.MxCall import org.matrix.android.sdk.api.session.call.MxCall
@ -38,6 +39,7 @@ import org.matrix.android.sdk.api.session.room.model.call.CallRejectContent
import org.matrix.android.sdk.api.session.room.model.call.CallReplacesContent import org.matrix.android.sdk.api.session.room.model.call.CallReplacesContent
import org.matrix.android.sdk.api.session.room.model.call.CallSelectAnswerContent import org.matrix.android.sdk.api.session.room.model.call.CallSelectAnswerContent
import org.matrix.android.sdk.api.session.room.model.call.CallSignalingContent import org.matrix.android.sdk.api.session.room.model.call.CallSignalingContent
import org.matrix.android.sdk.api.session.room.model.call.EndCallReason
import org.matrix.android.sdk.api.session.room.model.call.SdpType import org.matrix.android.sdk.api.session.room.model.call.SdpType
import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.api.util.Optional
import org.matrix.android.sdk.internal.session.call.DefaultCallSignalingService import org.matrix.android.sdk.internal.session.call.DefaultCallSignalingService
@ -47,6 +49,8 @@ import org.matrix.android.sdk.internal.session.room.send.queue.EventSenderProces
import timber.log.Timber import timber.log.Timber
import java.math.BigDecimal import java.math.BigDecimal
private val loggerTag = LoggerTag("MxCallImpl", LoggerTag.VOIP)
internal class MxCallImpl( internal class MxCallImpl(
override val callId: String, override val callId: String,
override val isOutgoing: Boolean, override val isOutgoing: Boolean,
@ -93,7 +97,7 @@ internal class MxCallImpl(
try { try {
it.onStateUpdate(this) it.onStateUpdate(this)
} catch (failure: Throwable) { } catch (failure: Throwable) {
Timber.d("dispatchStateChange failed for call $callId : ${failure.localizedMessage}") Timber.tag(loggerTag.value).d("dispatchStateChange failed for call $callId : ${failure.localizedMessage}")
} }
} }
} }
@ -109,7 +113,7 @@ internal class MxCallImpl(
override fun offerSdp(sdpString: String) { override fun offerSdp(sdpString: String) {
if (!isOutgoing) return if (!isOutgoing) return
Timber.v("## VOIP offerSdp $callId") Timber.tag(loggerTag.value).v("offerSdp $callId")
state = CallState.Dialing state = CallState.Dialing
CallInviteContent( CallInviteContent(
callId = callId, callId = callId,
@ -124,7 +128,7 @@ internal class MxCallImpl(
} }
override fun sendLocalCallCandidates(candidates: List<CallCandidate>) { override fun sendLocalCallCandidates(candidates: List<CallCandidate>) {
Timber.v("Send local call canditates $callId: $candidates") Timber.tag(loggerTag.value).v("Send local call canditates $callId: $candidates")
CallCandidatesContent( CallCandidatesContent(
callId = callId, callId = callId,
partyId = ourPartyId, partyId = ourPartyId,
@ -141,11 +145,11 @@ internal class MxCallImpl(
override fun reject() { override fun reject() {
if (opponentVersion < 1) { if (opponentVersion < 1) {
Timber.v("Opponent version is less than 1 ($opponentVersion): sending hangup instead of reject") Timber.tag(loggerTag.value).v("Opponent version is less than 1 ($opponentVersion): sending hangup instead of reject")
hangUp() hangUp(EndCallReason.USER_HANGUP)
return return
} }
Timber.v("## VOIP reject $callId") Timber.tag(loggerTag.value).v("reject $callId")
CallRejectContent( CallRejectContent(
callId = callId, callId = callId,
partyId = ourPartyId, partyId = ourPartyId,
@ -153,24 +157,24 @@ internal class MxCallImpl(
) )
.let { createEventAndLocalEcho(type = EventType.CALL_REJECT, roomId = roomId, content = it.toContent()) } .let { createEventAndLocalEcho(type = EventType.CALL_REJECT, roomId = roomId, content = it.toContent()) }
.also { eventSenderProcessor.postEvent(it) } .also { eventSenderProcessor.postEvent(it) }
state = CallState.Terminated state = CallState.Ended(reason = EndCallReason.USER_HANGUP)
} }
override fun hangUp(reason: CallHangupContent.Reason?) { override fun hangUp(reason: EndCallReason?) {
Timber.v("## VOIP hangup $callId") Timber.tag(loggerTag.value).v("hangup $callId")
CallHangupContent( CallHangupContent(
callId = callId, callId = callId,
partyId = ourPartyId, partyId = ourPartyId,
reason = reason ?: CallHangupContent.Reason.USER_HANGUP, reason = reason,
version = MxCall.VOIP_PROTO_VERSION.toString() version = MxCall.VOIP_PROTO_VERSION.toString()
) )
.let { createEventAndLocalEcho(type = EventType.CALL_HANGUP, roomId = roomId, content = it.toContent()) } .let { createEventAndLocalEcho(type = EventType.CALL_HANGUP, roomId = roomId, content = it.toContent()) }
.also { eventSenderProcessor.postEvent(it) } .also { eventSenderProcessor.postEvent(it) }
state = CallState.Terminated state = CallState.Ended(reason)
} }
override fun accept(sdpString: String) { override fun accept(sdpString: String) {
Timber.v("## VOIP accept $callId") Timber.tag(loggerTag.value).v("accept $callId")
if (isOutgoing) return if (isOutgoing) return
state = CallState.Answering state = CallState.Answering
CallAnswerContent( CallAnswerContent(
@ -185,7 +189,7 @@ internal class MxCallImpl(
} }
override fun negotiate(sdpString: String, type: SdpType) { override fun negotiate(sdpString: String, type: SdpType) {
Timber.v("## VOIP negotiate $callId") Timber.tag(loggerTag.value).v("negotiate $callId")
CallNegotiateContent( CallNegotiateContent(
callId = callId, callId = callId,
partyId = ourPartyId, partyId = ourPartyId,
@ -198,7 +202,7 @@ internal class MxCallImpl(
} }
override fun selectAnswer() { override fun selectAnswer() {
Timber.v("## VOIP select answer $callId") Timber.tag(loggerTag.value).v("select answer $callId")
if (isOutgoing) return if (isOutgoing) return
state = CallState.Answering state = CallState.Answering
CallSelectAnswerContent( CallSelectAnswerContent(
@ -219,7 +223,7 @@ internal class MxCallImpl(
val profileInfo = try { val profileInfo = try {
getProfileInfoTask.execute(profileInfoParams) getProfileInfoTask.execute(profileInfoParams)
} catch (failure: Throwable) { } catch (failure: Throwable) {
Timber.v("Fail fetching profile info of $targetUserId while transferring call") Timber.tag(loggerTag.value).v("Fail fetching profile info of $targetUserId while transferring call")
null null
} }
CallReplacesContent( CallReplacesContent(

View file

@ -26,7 +26,7 @@ private const val MATRIX_CONTENT_URI_SCHEME = "mxc://"
internal class DefaultContentUrlResolver @Inject constructor(homeServerConnectionConfig: HomeServerConnectionConfig) : ContentUrlResolver { internal class DefaultContentUrlResolver @Inject constructor(homeServerConnectionConfig: HomeServerConnectionConfig) : ContentUrlResolver {
private val baseUrl = homeServerConnectionConfig.homeServerUri.toString().ensureTrailingSlash() private val baseUrl = homeServerConnectionConfig.homeServerUriBase.toString().ensureTrailingSlash()
override val uploadUrl = baseUrl + NetworkConstants.URI_API_MEDIA_PREFIX_PATH_R0 + "upload" override val uploadUrl = baseUrl + NetworkConstants.URI_API_MEDIA_PREFIX_PATH_R0 + "upload"

View file

@ -16,18 +16,12 @@
package org.matrix.android.sdk.internal.session.homeserver package org.matrix.android.sdk.internal.session.homeserver
import com.zhuinden.monarchy.Monarchy
import io.realm.Realm
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilitiesService import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilitiesService
import org.matrix.android.sdk.internal.database.mapper.HomeServerCapabilitiesMapper
import org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEntity
import org.matrix.android.sdk.internal.database.query.get
import org.matrix.android.sdk.internal.di.SessionDatabase
import javax.inject.Inject import javax.inject.Inject
internal class DefaultHomeServerCapabilitiesService @Inject constructor( internal class DefaultHomeServerCapabilitiesService @Inject constructor(
@SessionDatabase private val monarchy: Monarchy, private val homeServerCapabilitiesDataSource: HomeServerCapabilitiesDataSource,
private val getHomeServerCapabilitiesTask: GetHomeServerCapabilitiesTask private val getHomeServerCapabilitiesTask: GetHomeServerCapabilitiesTask
) : HomeServerCapabilitiesService { ) : HomeServerCapabilitiesService {
@ -36,11 +30,7 @@ internal class DefaultHomeServerCapabilitiesService @Inject constructor(
} }
override fun getHomeServerCapabilities(): HomeServerCapabilities { override fun getHomeServerCapabilities(): HomeServerCapabilities {
return Realm.getInstance(monarchy.realmConfiguration).use { realm -> return homeServerCapabilitiesDataSource.getHomeServerCapabilities()
HomeServerCapabilitiesEntity.get(realm)?.let {
HomeServerCapabilitiesMapper.map(it)
}
}
?: HomeServerCapabilities() ?: HomeServerCapabilities()
} }
} }

View file

@ -19,6 +19,7 @@ package org.matrix.android.sdk.internal.session.homeserver
import com.squareup.moshi.Json import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass import com.squareup.moshi.JsonClass
import org.matrix.android.sdk.api.extensions.orTrue import org.matrix.android.sdk.api.extensions.orTrue
import org.matrix.android.sdk.api.util.JsonDict
/** /**
* Ref: https://matrix.org/docs/spec/client_server/latest#get-matrix-client-r0-capabilities * Ref: https://matrix.org/docs/spec/client_server/latest#get-matrix-client-r0-capabilities
@ -38,9 +39,14 @@ internal data class Capabilities(
* Capability to indicate if the user can change their password. * Capability to indicate if the user can change their password.
*/ */
@Json(name = "m.change_password") @Json(name = "m.change_password")
val changePassword: ChangePassword? = null val changePassword: ChangePassword? = null,
// No need for m.room_versions for the moment /**
* This capability describes the default and available room versions a server supports, and at what level of stability.
* Clients should make use of this capability to determine if users need to be encouraged to upgrade their rooms.
*/
@Json(name = "m.room_versions")
val roomVersions: RoomVersions? = null
) )
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
@ -52,6 +58,21 @@ internal data class ChangePassword(
val enabled: Boolean? val enabled: Boolean?
) )
@JsonClass(generateAdapter = true)
internal data class RoomVersions(
/**
* Required. The default room version the server is using for new rooms.
*/
@Json(name = "default")
val default: String?,
/**
* Required. A detailed description of the room versions the server supports.
*/
@Json(name = "available")
val available: JsonDict
)
// The spec says: If not present, the client should assume that password changes are possible via the API // The spec says: If not present, the client should assume that password changes are possible via the API
internal fun GetCapabilitiesResult.canChangePassword(): Boolean { internal fun GetCapabilitiesResult.canChangePassword(): Boolean {
return capabilities?.changePassword?.enabled.orTrue() return capabilities?.changePassword?.enabled.orTrue()

View file

@ -17,6 +17,7 @@
package org.matrix.android.sdk.internal.session.homeserver package org.matrix.android.sdk.internal.session.homeserver
import com.zhuinden.monarchy.Monarchy import com.zhuinden.monarchy.Monarchy
import org.matrix.android.sdk.api.MatrixPatterns.getDomain
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
import org.matrix.android.sdk.api.auth.wellknown.WellknownResult import org.matrix.android.sdk.api.auth.wellknown.WellknownResult
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities
@ -24,6 +25,7 @@ import org.matrix.android.sdk.internal.auth.version.Versions
import org.matrix.android.sdk.internal.auth.version.isLoginAndRegistrationSupportedBySdk import org.matrix.android.sdk.internal.auth.version.isLoginAndRegistrationSupportedBySdk
import org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEntity import org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEntity
import org.matrix.android.sdk.internal.database.query.getOrCreate import org.matrix.android.sdk.internal.database.query.getOrCreate
import org.matrix.android.sdk.internal.di.MoshiProvider
import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.di.SessionDatabase
import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.di.UserId
import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
@ -89,7 +91,10 @@ internal class DefaultGetHomeServerCapabilitiesTask @Inject constructor(
}.getOrNull() }.getOrNull()
val wellknownResult = runCatching { val wellknownResult = runCatching {
getWellknownTask.execute(GetWellknownTask.Params(userId, homeServerConnectionConfig)) getWellknownTask.execute(GetWellknownTask.Params(
domain = userId.getDomain(),
homeServerConnectionConfig = homeServerConnectionConfig
))
}.getOrNull() }.getOrNull()
insertInDb(capabilities, mediaConfig, versions, wellknownResult) insertInDb(capabilities, mediaConfig, versions, wellknownResult)
@ -104,6 +109,10 @@ internal class DefaultGetHomeServerCapabilitiesTask @Inject constructor(
if (getCapabilitiesResult != null) { if (getCapabilitiesResult != null) {
homeServerCapabilitiesEntity.canChangePassword = getCapabilitiesResult.canChangePassword() homeServerCapabilitiesEntity.canChangePassword = getCapabilitiesResult.canChangePassword()
homeServerCapabilitiesEntity.roomVersionsJson = getCapabilitiesResult.capabilities?.roomVersions?.let {
MoshiProvider.providesMoshi().adapter(RoomVersions::class.java).toJson(it)
}
} }
if (getMediaConfigResult != null) { if (getMediaConfigResult != null) {

View file

@ -0,0 +1,38 @@
/*
* 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.homeserver
import com.zhuinden.monarchy.Monarchy
import io.realm.Realm
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities
import org.matrix.android.sdk.internal.database.mapper.HomeServerCapabilitiesMapper
import org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEntity
import org.matrix.android.sdk.internal.database.query.get
import org.matrix.android.sdk.internal.di.SessionDatabase
import javax.inject.Inject
internal class HomeServerCapabilitiesDataSource @Inject constructor(
@SessionDatabase private val monarchy: Monarchy
) {
fun getHomeServerCapabilities(): HomeServerCapabilities? {
return Realm.getInstance(monarchy.realmConfiguration).use { realm ->
HomeServerCapabilitiesEntity.get(realm)?.let {
HomeServerCapabilitiesMapper.map(it)
}
}
}
}

View file

@ -191,7 +191,7 @@ internal class DefaultIdentityService @Inject constructor(
} else { } else {
// Disconnect previous one if any, first, because the token will change. // Disconnect previous one if any, first, because the token will change.
// In case of error when configuring the new identity server, this is not a big deal, // In case of error when configuring the new identity server, this is not a big deal,
// we will ask for a new token on the previous Identity server // we will ask for a new token on the previous identity server
runCatching { identityDisconnectTask.execute(Unit) } runCatching { identityDisconnectTask.execute(Unit) }
.onFailure { Timber.w(it, "Unable to disconnect identity server") } .onFailure { Timber.w(it, "Unable to disconnect identity server") }
@ -241,7 +241,7 @@ internal class DefaultIdentityService @Inject constructor(
override suspend fun getShareStatus(threePids: List<ThreePid>): Map<ThreePid, SharedState> { override suspend fun getShareStatus(threePids: List<ThreePid>): Map<ThreePid, SharedState> {
// Note: we do not require user consent here, because it is used for emails and phone numbers that the user has already sent // Note: we do not require user consent here, because it is used for emails and phone numbers that the user has already sent
// to the home server, and not emails and phone numbers from the contact book of the user // to the homeserver, and not emails and phone numbers from the contact book of the user
if (threePids.isEmpty()) { if (threePids.isEmpty()) {
return emptyMap() return emptyMap()

View file

@ -42,7 +42,7 @@ internal interface IdentityAuthAPI {
suspend fun ping() suspend fun ping()
/** /**
* Ping v1 will be used to check outdated Identity server * Ping v1 will be used to check outdated identity server
*/ */
@GET("_matrix/identity/api/v1") @GET("_matrix/identity/api/v1")
suspend fun pingV1() suspend fun pingV1()

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