diff --git a/.github/ISSUE_TEMPLATE/release.md b/.github/ISSUE_TEMPLATE/release.md
new file mode 100644
index 0000000000..154e93286c
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/release.md
@@ -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`
+
+
diff --git a/.idea/dictionaries/bmarty.xml b/.idea/dictionaries/bmarty.xml
index 8299f1c4de..e143720aa9 100644
--- a/.idea/dictionaries/bmarty.xml
+++ b/.idea/dictionaries/bmarty.xml
@@ -38,6 +38,7 @@
unpublishunwedgingvctr
+ wellknown
\ No newline at end of file
diff --git a/CHANGES.md b/CHANGES.md
index 9947146a9e..a5ef37fb55 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,3 +1,69 @@
+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)
+======================================
+
+Features ✨
+----------
+ - Reveal password: use facility from com.google.android.material.textfield.TextInputLayout instead of manual handling. ([#3545](https://github.com/vector-im/element-android/issues/3545))
+ - Implements new design for Jump to unread and quick fix visibility issues. ([#3547](https://github.com/vector-im/element-android/issues/3547))
+
+Bugfixes 🐛
+----------
+ - Fix some issues with timeline cache invalidation and visibility. ([#3542](https://github.com/vector-im/element-android/issues/3542))
+ - Fix call invite processed after call is ended because of fastlane mode. ([#3564](https://github.com/vector-im/element-android/issues/3564))
+ - Fix crash after video call. ([#3577](https://github.com/vector-im/element-android/issues/3577))
+ - Fix crash out of memory ([#3583](https://github.com/vector-im/element-android/issues/3583))
+ - CryptoStore migration has to be object to avoid crash ([#3605](https://github.com/vector-im/element-android/issues/3605))
+
+
Changes in Element v1.1.11 (2021-06-22)
=======================================
diff --git a/Gemfile.lock b/Gemfile.lock
index 6045f55496..345b4c1502 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -2,25 +2,25 @@ GEM
remote: https://rubygems.org/
specs:
CFPropertyList (3.0.3)
- addressable (2.7.0)
+ addressable (2.8.0)
public_suffix (>= 2.0.2, < 5.0)
artifactory (3.0.15)
atomos (0.1.3)
aws-eventstream (1.1.1)
- aws-partitions (1.462.0)
- aws-sdk-core (3.114.0)
+ aws-partitions (1.479.0)
+ aws-sdk-core (3.117.0)
aws-eventstream (~> 1, >= 1.0.2)
aws-partitions (~> 1, >= 1.239.0)
aws-sigv4 (~> 1.1)
jmespath (~> 1.0)
- aws-sdk-kms (1.43.0)
+ aws-sdk-kms (1.44.0)
aws-sdk-core (~> 3, >= 3.112.0)
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-kms (~> 1)
aws-sigv4 (~> 1.1)
- aws-sigv4 (1.2.3)
+ aws-sigv4 (1.2.4)
aws-eventstream (~> 1, >= 1.0.2)
babosa (1.0.4)
claide (1.0.3)
@@ -35,13 +35,15 @@ GEM
unf (>= 0.0.5, < 1.0.0)
dotenv (2.7.6)
emoji_regex (3.2.2)
- excon (0.81.0)
- faraday (1.4.2)
+ excon (0.85.0)
+ faraday (1.5.1)
faraday-em_http (~> 1.0)
faraday-em_synchrony (~> 1.0)
faraday-excon (~> 1.1)
+ faraday-httpclient (~> 1.0.1)
faraday-net_http (~> 1.0)
faraday-net_http_persistent (~> 1.1)
+ faraday-patron (~> 1.0)
multipart-post (>= 1.2, < 3)
ruby2_keywords (>= 0.0.4)
faraday-cookie_jar (0.0.7)
@@ -50,12 +52,14 @@ GEM
faraday-em_http (1.0.0)
faraday-em_synchrony (1.0.0)
faraday-excon (1.1.0)
+ faraday-httpclient (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 (~> 1.0)
- fastimage (2.2.3)
- fastlane (2.184.0)
+ fastimage (2.2.4)
+ fastlane (2.187.0)
CFPropertyList (>= 2.3, < 4.0.0)
addressable (>= 2.3, < 3.0.0)
artifactory (~> 3.0)
@@ -94,37 +98,36 @@ GEM
xcpretty (~> 0.3.0)
xcpretty-travis-formatter (>= 0.0.3)
gh_inspector (1.1.3)
- google-apis-androidpublisher_v3 (0.4.0)
- google-apis-core (~> 0.1)
- google-apis-core (0.3.0)
+ google-apis-androidpublisher_v3 (0.8.0)
+ google-apis-core (>= 0.4, < 2.a)
+ google-apis-core (0.4.0)
addressable (~> 2.5, >= 2.5.1)
- googleauth (~> 0.14)
- httpclient (>= 2.8.1, < 3.0)
+ googleauth (>= 0.16.2, < 2.a)
+ httpclient (>= 2.8.1, < 3.a)
mini_mime (~> 1.0)
representable (~> 3.0)
- retriable (>= 2.0, < 4.0)
+ retriable (>= 2.0, < 4.a)
rexml
- signet (~> 0.14)
webrick
- google-apis-iamcredentials_v1 (0.4.0)
- google-apis-core (~> 0.1)
- google-apis-playcustomapp_v1 (0.3.0)
- google-apis-core (~> 0.1)
- google-apis-storage_v1 (0.4.0)
- google-apis-core (~> 0.1)
+ google-apis-iamcredentials_v1 (0.6.0)
+ google-apis-core (>= 0.4, < 2.a)
+ google-apis-playcustomapp_v1 (0.5.0)
+ google-apis-core (>= 0.4, < 2.a)
+ google-apis-storage_v1 (0.6.0)
+ google-apis-core (>= 0.4, < 2.a)
google-cloud-core (1.6.0)
google-cloud-env (~> 1.0)
google-cloud-errors (~> 1.0)
google-cloud-env (1.5.0)
faraday (>= 0.17.3, < 2.0)
google-cloud-errors (1.1.0)
- google-cloud-storage (1.31.1)
+ google-cloud-storage (1.34.1)
addressable (~> 2.5)
digest-crc (~> 0.4)
google-apis-iamcredentials_v1 (~> 0.1)
google-apis-storage_v1 (~> 0.1)
- google-cloud-core (~> 1.2)
- googleauth (~> 0.9)
+ google-cloud-core (~> 1.6)
+ googleauth (>= 0.16.2, < 2.a)
mini_mime (~> 1.0)
googleauth (0.16.2)
faraday (>= 0.17.3, < 2.0)
@@ -134,7 +137,7 @@ GEM
os (>= 0.9, < 2.0)
signet (~> 0.14)
highline (2.0.3)
- http-cookie (1.0.3)
+ http-cookie (1.0.4)
domain_name (~> 0.5)
httpclient (2.8.3)
jmespath (1.4.0)
@@ -150,7 +153,7 @@ GEM
os (1.1.1)
plist (3.6.0)
public_suffix (4.0.6)
- rake (13.0.3)
+ rake (13.0.6)
representable (3.1.1)
declarative (< 0.1.0)
trailblazer-option (>= 0.1.1, < 0.2.0)
@@ -158,8 +161,8 @@ GEM
retriable (3.1.2)
rexml (3.2.5)
rouge (2.0.7)
- ruby2_keywords (0.0.4)
- rubyzip (2.3.0)
+ ruby2_keywords (0.0.5)
+ rubyzip (2.3.2)
security (0.1.3)
signet (0.15.0)
addressable (~> 2.3)
@@ -184,12 +187,13 @@ GEM
unicode-display_width (1.7.0)
webrick (1.7.0)
word_wrap (1.0.0)
- xcodeproj (1.19.0)
+ xcodeproj (1.20.0)
CFPropertyList (>= 2.3.3, < 4.0)
atomos (~> 0.1.3)
claide (>= 1.0.2, < 2.0)
colored2 (~> 3.1)
nanaimo (~> 0.3.0)
+ rexml (~> 3.2.4)
xcpretty (0.3.0)
rouge (~> 2.0.7)
xcpretty-travis-formatter (1.0.1)
diff --git a/attachment-viewer/build.gradle b/attachment-viewer/build.gradle
index 4b95b20029..c393c5f483 100644
--- a/attachment-viewer/build.gradle
+++ b/attachment-viewer/build.gradle
@@ -55,9 +55,9 @@ dependencies {
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
- implementation 'androidx.core:core-ktx:1.5.0'
- implementation 'androidx.appcompat:appcompat:1.3.0'
+ implementation 'androidx.core:core-ktx:1.6.0'
+ implementation 'androidx.appcompat:appcompat:1.3.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'
}
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
index b6c4a17559..bbb6b343cf 100644
--- a/build.gradle
+++ b/build.gradle
@@ -2,7 +2,7 @@
buildscript {
// 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"
repositories {
google()
@@ -12,7 +12,7 @@ buildscript {
}
}
dependencies {
- classpath 'com.android.tools.build:gradle:4.2.1'
+ classpath 'com.android.tools.build:gradle:4.2.2'
classpath 'com.google.gms:google-services:4.3.8'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.3'
diff --git a/changelog.d/3545.feature b/changelog.d/3545.feature
deleted file mode 100644
index f582affbf2..0000000000
--- a/changelog.d/3545.feature
+++ /dev/null
@@ -1 +0,0 @@
-Reveal password: use facility from com.google.android.material.textfield.TextInputLayout instead of manual handling.
\ No newline at end of file
diff --git a/changelog.d/3547.feature b/changelog.d/3547.feature
deleted file mode 100644
index 8921932067..0000000000
--- a/changelog.d/3547.feature
+++ /dev/null
@@ -1 +0,0 @@
-Implements new design for Jump to unread and quick fix visibility issues.
\ No newline at end of file
diff --git a/changelog.d/3564.bugfix b/changelog.d/3564.bugfix
deleted file mode 100644
index 74f71ccded..0000000000
--- a/changelog.d/3564.bugfix
+++ /dev/null
@@ -1 +0,0 @@
-Fix call invite processed after call is ended because of fastlane mode.
\ No newline at end of file
diff --git a/changelog.d/3577.bugfix b/changelog.d/3577.bugfix
deleted file mode 100644
index 6b3917292f..0000000000
--- a/changelog.d/3577.bugfix
+++ /dev/null
@@ -1 +0,0 @@
-Fix crash after video call.
\ No newline at end of file
diff --git a/docs/identity_server.md b/docs/identity_server.md
index 0d75108dd3..e765ae3949 100644
--- a/docs/identity_server.md
+++ b/docs/identity_server.md
@@ -4,7 +4,7 @@ Issue: #607
PR: #1354
## 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
@@ -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
## 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
- vector.im TOS: https://vector.im/identity-server-privacy-notice
diff --git a/docs/notifications.md b/docs/notifications.md
index a00fef8fae..b44984785a 100644
--- a/docs/notifications.md
+++ b/docs/notifications.md
@@ -2,11 +2,11 @@ This document aims to describe how Element android displays notifications to the
# Table of Contents
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)
* [Push VS Notification](#push-vs-notification)
* [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)
* [Background processing limitations](#background-processing-limitations)
2. [Element Notification implementations](#element-notification-implementations)
@@ -22,9 +22,9 @@ First let's start with some prerequisite 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. `
@@ -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.
-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.
```
@@ -122,13 +122,13 @@ Recommended reading:
* 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-).
`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).
@@ -187,7 +187,7 @@ In background, and depending on wether push is available or not, Element will us
## 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.
@@ -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)).
-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.
@@ -217,7 +217,7 @@ Homeserver ----> Sygnal ----> FCM ----> Element
**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
* 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)
diff --git a/fastlane/metadata/android/cs-CZ/changelogs/40101070.txt b/fastlane/metadata/android/cs-CZ/changelogs/40101070.txt
new file mode 100644
index 0000000000..2a6afb21a5
--- /dev/null
+++ b/fastlane/metadata/android/cs-CZ/changelogs/40101070.txt
@@ -0,0 +1,2 @@
+Hlavní změny v této verzi: beta podpora pro Spaces. Komprimace videa před odesláním.
+Úplný záznam změn: https://github.com/vector-im/element-android/releases/tag/v1.1.7
diff --git a/fastlane/metadata/android/cs-CZ/changelogs/40101080.txt b/fastlane/metadata/android/cs-CZ/changelogs/40101080.txt
new file mode 100644
index 0000000000..5ec173ebea
--- /dev/null
+++ b/fastlane/metadata/android/cs-CZ/changelogs/40101080.txt
@@ -0,0 +1,2 @@
+Hlavní změny v této verzi: vylepšení pro Spaces
+Úplný záznam změn: https://github.com/vector-im/element-android/releases/tag/v1.1.8
diff --git a/fastlane/metadata/android/cs-CZ/changelogs/40101090.txt b/fastlane/metadata/android/cs-CZ/changelogs/40101090.txt
new file mode 100644
index 0000000000..782a2fa8c5
--- /dev/null
+++ b/fastlane/metadata/android/cs-CZ/changelogs/40101090.txt
@@ -0,0 +1,2 @@
+Hlavní změny v této verzi: doplněna podpora pro síť gitter.im
+Úplný záznam změn: https://github.com/vector-im/element-android/releases/tag/v1.1.9
diff --git a/fastlane/metadata/android/cs-CZ/changelogs/40101100.txt b/fastlane/metadata/android/cs-CZ/changelogs/40101100.txt
new file mode 100644
index 0000000000..de73b6f382
--- /dev/null
+++ b/fastlane/metadata/android/cs-CZ/changelogs/40101100.txt
@@ -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
diff --git a/fastlane/metadata/android/cs-CZ/changelogs/40101110.txt b/fastlane/metadata/android/cs-CZ/changelogs/40101110.txt
new file mode 100644
index 0000000000..c4bfee0779
--- /dev/null
+++ b/fastlane/metadata/android/cs-CZ/changelogs/40101110.txt
@@ -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
diff --git a/fastlane/metadata/android/de-DE/changelogs/40101100.txt b/fastlane/metadata/android/de-DE/changelogs/40101100.txt
new file mode 100644
index 0000000000..04f944cdcf
--- /dev/null
+++ b/fastlane/metadata/android/de-DE/changelogs/40101100.txt
@@ -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
diff --git a/fastlane/metadata/android/de-DE/changelogs/40101110.txt b/fastlane/metadata/android/de-DE/changelogs/40101110.txt
new file mode 100644
index 0000000000..8e115469e1
--- /dev/null
+++ b/fastlane/metadata/android/de-DE/changelogs/40101110.txt
@@ -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
diff --git a/fastlane/metadata/android/en-US/changelogs/40101120.txt b/fastlane/metadata/android/en-US/changelogs/40101120.txt
new file mode 100644
index 0000000000..6118974777
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/40101120.txt
@@ -0,0 +1,2 @@
+Main changes in this version: theme and style update and fix a crash after video call
+Full changelog: https://github.com/vector-im/element-android/releases/tag/v1.1.12
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/40101130.txt b/fastlane/metadata/android/en-US/changelogs/40101130.txt
new file mode 100644
index 0000000000..9fc3f23b3e
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/40101130.txt
@@ -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
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/40101140.txt b/fastlane/metadata/android/en-US/changelogs/40101140.txt
new file mode 100644
index 0000000000..ee04069968
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/40101140.txt
@@ -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
\ No newline at end of file
diff --git a/fastlane/metadata/android/et/changelogs/40101100.txt b/fastlane/metadata/android/et/changelogs/40101100.txt
new file mode 100644
index 0000000000..e00e6de393
--- /dev/null
+++ b/fastlane/metadata/android/et/changelogs/40101100.txt
@@ -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
diff --git a/fastlane/metadata/android/et/changelogs/40101110.txt b/fastlane/metadata/android/et/changelogs/40101110.txt
new file mode 100644
index 0000000000..a4629c92d0
--- /dev/null
+++ b/fastlane/metadata/android/et/changelogs/40101110.txt
@@ -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
diff --git a/fastlane/metadata/android/fr-FR/changelogs/40101070.txt b/fastlane/metadata/android/fr-FR/changelogs/40101070.txt
new file mode 100644
index 0000000000..66cc5d1671
--- /dev/null
+++ b/fastlane/metadata/android/fr-FR/changelogs/40101070.txt
@@ -0,0 +1,2 @@
+Principaux changements pour cette version : prise en charge des espaces en bêta. Compression des vidéos avant envoi.
+Intégralité des changements : https://github.com/vector-im/element-android/releases/tag/v1.1.7
diff --git a/fastlane/metadata/android/fr-FR/changelogs/40101080.txt b/fastlane/metadata/android/fr-FR/changelogs/40101080.txt
new file mode 100644
index 0000000000..31a4b72363
--- /dev/null
+++ b/fastlane/metadata/android/fr-FR/changelogs/40101080.txt
@@ -0,0 +1,2 @@
+Principaux changements pour cette version : amélioration des espaces.
+Intégralité des changements : https://github.com/vector-im/element-android/releases/tag/v1.1.8
diff --git a/fastlane/metadata/android/fr-FR/changelogs/40101090.txt b/fastlane/metadata/android/fr-FR/changelogs/40101090.txt
new file mode 100644
index 0000000000..99f02f1bb2
--- /dev/null
+++ b/fastlane/metadata/android/fr-FR/changelogs/40101090.txt
@@ -0,0 +1,2 @@
+Principaux changements pour cette version : ajout de la prise en charge de gitter.im
+Intégralité des changements : https://github.com/vector-im/element-android/releases/tag/v1.1.9
diff --git a/fastlane/metadata/android/fr-FR/full_description.txt b/fastlane/metadata/android/fr-FR/full_description.txt
index 066b94868b..78fcdf5617 100644
--- a/fastlane/metadata/android/fr-FR/full_description.txt
+++ b/fastlane/metadata/android/fr-FR/full_description.txt
@@ -1,30 +1,39 @@
-Element est une nouvelle application de messagerie et de collaboration qui :
+Element est à la fois une messagerie sécurisée et une application de collaboration en équipe, idéale pour les conversations de groupe en télétravail. Cette application utilise le chiffrement de bout en bout. Elle permet de mettre en place des téléconférences vidéo, du partage de fichier et des appels vocaux.
-1. Vous permet de préserver votre vie privée
-2. Vous permet de communiquer avec n’importe qui sur réseau Matrix, et plus encore grâce aux intégrations d’autres applications telles que Slack ou Discord
-3. Vous protège de la publicité et de la collecte de données
-4. Vous protège grâce au chiffrement de bout-à-bout et à la signature croisée pour authentifier les autres utilisateurs
+Les fonctionnalités d’Element incluent :
+- Outils de communication en ligne avancés
+- Communication d’entreprise sécurisée par le chiffrement de bout en bout des messages, même pour les travailleurs à distance
+- Messagerie décentralisée basée sur le framework open source Matrix
+- Partage sécurisé de fichiers avec chiffrement des données lors de la gestion de projet
+- Conversations vidéo par voix sur IP et partage d’écran
+- Intégration facile avec vos outils de collaboration, de gestion de projet, services de VoIP et autres applications de messagerie
-Element est complètement différente des autres applications de messagerie et de collaboration puisque l’application est décentralisée et open-source.
+Element est complètement différente des autres applications de messagerie et de collaboration. Elle s’appuie sur Matrix, un réseau ouvert de communication décentralisée. Elle permet l’auto-hébergement pour que ses utilisateurs restent le plus en contrôle possible de leurs données et leurs messages.
-Element vous permet d’héberger vous-même ou de choisir un hôte vous permettant d’assurer votre vie privée, la propriété et le contrôle de vos données et de vos conversations. Cela vous donne accès à un réseau ouvert. Vous n’êtes donc pas condamné à parler à d’autres utilisateurs d’Element seulement. Et c'est très sécurisé.
+Confidentialité et messagerie chiffrée
+Element vous protège des publicités non désirées, du minage de données et des prisons dorées. Elle protègé vos données et vos communications vocales grâce au chiffrement de bout en bout et à la vérification de signature croisée entre appareils.
-Element peut faire tout ça car elle est basée sur Matrix, le protocole standard pour la communication ouverte et décentralisée.
+Element vous donne la main sur votre confidentialité en vous permettant de communiquer de manière sécurisée avec tout le réseau Matrix ou d’autres applications de communication d’entreprise au travers d’intégrations d’applications comme Slack.
-Element vous donne le contrôle en vous laissant choisir qui héberge vos conversations. Depuis l'application Element, vous pouvez choisir votre hôte de différentes manières :
+Element peut être auto-hébergé
+Pour une meilleure souveraineté sur vos données et conversations, Element peut être auto-hébergé ou vous pouvez choisir votre hôte Matrix - la norme open source pour les communications décentralisées. Element garantit votre confidentialité, conformité aux normes de sécurité, tout en proposant une intégration souple.
-1. Créer un compte gratuit sur le serveur public matrix.org hébergé par les développeurs de Matrix, ou choisir parmi les milliers de serveurs public hébergés par des bénévoles
-2. Héberger vous-même votre compte en installant un serveur sur votre propre machine
-3. Créer un compte sur un serveur personnalisé en souscrivant sur la plateforme d'hébergement « Element Matrix Services » (EMS)
+Vos données vous appartiennent
+Vous décidez où stocker vos données et messages. Aucun risque de minage de données où d’accès par des tierce parties.
-Pourquoi choisir Element ?
+Element vous place aux commandes de différente manières :
+1. Inscrivez vous sur le serveur public matrix.org hébergé par les développeurs de Matrix ou choisissez parmi des milliers de serveurs publics hébergés par des bénévoles
+2. Auto-hébergez votre compte sur un serveur de votre proper infrastructure informatique
+3. Inscrivez vous à la plateforme d’hébergement Element Matrix Services
-VOS DONNÉES VOUS APPARTIENNENT : vous décidez où stocker vos données et messages. Ils vous appartiennent et vous les maîtrisez. Aucune multinationale ne viendra extraire vos données pour les envoyer au plus offrant.
+Messagerie et collaboration ouvertes
+Vous pouvez discuter avec tout le réseau Matrix, que vos interlocuteurs utilisent Element, une autre application Matrix, ou même s’ils utilisent une application complètement différente.
-MESSAGERIE ET COLLABORATION OUVERTES : vous pouvez discuter avec tout le réseau Matrix, qu’ils utilisent Element ou une autre application Matrix, même s’ils utilisent une autre plateforme de messagerie telle que Slack, IRC ou XMPP.
+Ultra sécurisé
+Chiffrement de bout en bout (seules les personnes dans la conversation peuvent déchiffrer les messages) et vérification de signature croisée entre appareils.
-ULTRA SÉCURISÉ : chiffrement de bout en bout (seuls les membres d’une conversation peuvent déchiffrer les messages), et signature croisée pour vérifier les appareils de vos interlocuteurs.
+Communication et intégration parfaites
+Messagerie instantannée, appels audio et vidéo, partage de fichier, partage d’écran et bien d’autres intégrations, bots et widgets. Lancez des salons, des communautés, restez en contact et menez vos projets à bien.
-TOUTES VOS COMMUNICATIONS : messagerie, appels audio et vidéo, partage de fichier, partage d’écran et un grand nombre d’intégrations, robots et widgets. Participez à des salons, des communautés, restez en contact et faites avancer vos projets.
-
-PARTOUT AVEC VOUS : votre historique reste synchronisé entre tous vos appareils et sur le web sur https://element.io/app.
+Reprenez où vous vous êtes arrêté
+Restez en contact où que vous soyez grâce à l’historique des messages synchronisé entre tous vos appareils et sur le web sur https://app.element.io
diff --git a/fastlane/metadata/android/hu-HU/changelogs/40101100.txt b/fastlane/metadata/android/hu-HU/changelogs/40101100.txt
new file mode 100644
index 0000000000..34171b2221
--- /dev/null
+++ b/fastlane/metadata/android/hu-HU/changelogs/40101100.txt
@@ -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
diff --git a/fastlane/metadata/android/hu-HU/changelogs/40101110.txt b/fastlane/metadata/android/hu-HU/changelogs/40101110.txt
new file mode 100644
index 0000000000..1550736f36
--- /dev/null
+++ b/fastlane/metadata/android/hu-HU/changelogs/40101110.txt
@@ -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
diff --git a/fastlane/metadata/android/it-IT/changelogs/40101100.txt b/fastlane/metadata/android/it-IT/changelogs/40101100.txt
new file mode 100644
index 0000000000..6f2447e5f6
--- /dev/null
+++ b/fastlane/metadata/android/it-IT/changelogs/40101100.txt
@@ -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
diff --git a/fastlane/metadata/android/it-IT/changelogs/40101110.txt b/fastlane/metadata/android/it-IT/changelogs/40101110.txt
new file mode 100644
index 0000000000..263f46b4f1
--- /dev/null
+++ b/fastlane/metadata/android/it-IT/changelogs/40101110.txt
@@ -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
diff --git a/fastlane/metadata/android/pt-BR/changelogs/40101070.txt b/fastlane/metadata/android/pt-BR/changelogs/40101070.txt
index ab403ea4cc..5667b1609a 100644
--- a/fastlane/metadata/android/pt-BR/changelogs/40101070.txt
+++ b/fastlane/metadata/android/pt-BR/changelogs/40101070.txt
@@ -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
diff --git a/fastlane/metadata/android/pt-BR/changelogs/40101080.txt b/fastlane/metadata/android/pt-BR/changelogs/40101080.txt
index 25047345ef..7922e6d800 100644
--- a/fastlane/metadata/android/pt-BR/changelogs/40101080.txt
+++ b/fastlane/metadata/android/pt-BR/changelogs/40101080.txt
@@ -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
diff --git a/fastlane/metadata/android/pt-BR/changelogs/40101090.txt b/fastlane/metadata/android/pt-BR/changelogs/40101090.txt
index cc5e71cf7f..3246596eab 100644
--- a/fastlane/metadata/android/pt-BR/changelogs/40101090.txt
+++ b/fastlane/metadata/android/pt-BR/changelogs/40101090.txt
@@ -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
diff --git a/fastlane/metadata/android/pt-BR/changelogs/40101100.txt b/fastlane/metadata/android/pt-BR/changelogs/40101100.txt
new file mode 100644
index 0000000000..4efd042313
--- /dev/null
+++ b/fastlane/metadata/android/pt-BR/changelogs/40101100.txt
@@ -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
diff --git a/fastlane/metadata/android/pt-BR/changelogs/40101110.txt b/fastlane/metadata/android/pt-BR/changelogs/40101110.txt
new file mode 100644
index 0000000000..ac98ef9e3b
--- /dev/null
+++ b/fastlane/metadata/android/pt-BR/changelogs/40101110.txt
@@ -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
diff --git a/fastlane/metadata/android/uk/changelogs/40101100.txt b/fastlane/metadata/android/uk/changelogs/40101100.txt
new file mode 100644
index 0000000000..c1fde35579
--- /dev/null
+++ b/fastlane/metadata/android/uk/changelogs/40101100.txt
@@ -0,0 +1,2 @@
+Основні зміни цієї версії: оновлено зовнішній вигляд та нові можливості для просторів
+Вичерпний перелік змін: https://github.com/vector-im/element-android/releases/tag/v1.1.10
diff --git a/fastlane/metadata/android/uk/changelogs/40101110.txt b/fastlane/metadata/android/uk/changelogs/40101110.txt
new file mode 100644
index 0000000000..9aa1195e49
--- /dev/null
+++ b/fastlane/metadata/android/uk/changelogs/40101110.txt
@@ -0,0 +1,2 @@
+Основні зміни цієї версії: оновлено зовнішній вигляд та нові можливості для просторів (bugfix для 1.1.10)
+Вичерпний перелік змін: https://github.com/vector-im/element-android/releases/tag/v1.1.11
diff --git a/fastlane/metadata/android/zh-CN/changelogs/40101100.txt b/fastlane/metadata/android/zh-CN/changelogs/40101100.txt
new file mode 100644
index 0000000000..b748f34290
--- /dev/null
+++ b/fastlane/metadata/android/zh-CN/changelogs/40101100.txt
@@ -0,0 +1,2 @@
+此版本的主要变化:主题和样式更新以及空间的新功能。
+完整更新日志:https://github.com/vector-im/element-android/releases/tag/v1.1.10
diff --git a/fastlane/metadata/android/zh-CN/changelogs/40101110.txt b/fastlane/metadata/android/zh-CN/changelogs/40101110.txt
new file mode 100644
index 0000000000..ef3f46e07d
--- /dev/null
+++ b/fastlane/metadata/android/zh-CN/changelogs/40101110.txt
@@ -0,0 +1,2 @@
+此版本的主要变化:主题和样式更新以及空间的新功能(1.1.10 的错误修复)
+完整更新日志:https://github.com/vector-im/element-android/releases/tag/v1.1.11
diff --git a/fastlane/metadata/android/zh-TW/changelogs/40101100.txt b/fastlane/metadata/android/zh-TW/changelogs/40101100.txt
new file mode 100644
index 0000000000..08e081fd8b
--- /dev/null
+++ b/fastlane/metadata/android/zh-TW/changelogs/40101100.txt
@@ -0,0 +1,2 @@
+此版本中的主要變動:佈景主題與樣式更新,以及空間的新功能。
+完整的變更紀錄:https://github.com/vector-im/element-android/releases/tag/v1.1.10
diff --git a/fastlane/metadata/android/zh-TW/changelogs/40101110.txt b/fastlane/metadata/android/zh-TW/changelogs/40101110.txt
new file mode 100644
index 0000000000..91bbc18fff
--- /dev/null
+++ b/fastlane/metadata/android/zh-TW/changelogs/40101110.txt
@@ -0,0 +1,2 @@
+此版本中的主要變動:佈景主題與樣式更新,以及空間的新功能(1.1.10 的臭蟲修復版本)
+完整的變更紀錄:https://github.com/vector-im/element-android/releases/tag/v1.1.11
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 283500479a..17ba19021b 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionSha256Sum=a9e356a21595348b6f04b024ed0b08ac8aea6b2ac37e6c0ef58e51549cd7b9cb
-distributionUrl=https\://services.gradle.org/distributions/gradle-7.1-all.zip
+distributionSha256Sum=9bb8bc05f562f2d42bdf1ba8db62f6b6fa1c3bf6c392228802cc7cb0578fe7e0
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.1.1-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
diff --git a/library/ui-styles/build.gradle b/library/ui-styles/build.gradle
index 47c4664636..4d34195962 100644
--- a/library/ui-styles/build.gradle
+++ b/library/ui-styles/build.gradle
@@ -52,8 +52,8 @@ android {
}
dependencies {
- implementation 'androidx.appcompat:appcompat:1.3.0'
- implementation 'com.google.android.material:material:1.3.0'
+ implementation 'androidx.appcompat:appcompat:1.3.1'
+ implementation 'com.google.android.material:material:1.4.0'
// Pref theme
implementation 'androidx.preference:preference-ktx:1.1.1'
// PFLockScreen attrs
diff --git a/matrix-sdk-android-rx/build.gradle b/matrix-sdk-android-rx/build.gradle
index 0d4aa7fc84..899432b498 100644
--- a/matrix-sdk-android-rx/build.gradle
+++ b/matrix-sdk-android-rx/build.gradle
@@ -35,7 +35,7 @@ android {
dependencies {
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:rxandroid:2.1.1'
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-rx2:$kotlin_coroutines_version"
diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle
index e8d1c1b018..fbaae97fdc 100644
--- a/matrix-sdk-android/build.gradle
+++ b/matrix-sdk-android/build.gradle
@@ -9,7 +9,7 @@ buildscript {
mavenCentral()
}
dependencies {
- classpath "io.realm:realm-gradle-plugin:10.6.0"
+ classpath "io.realm:realm-gradle-plugin:10.6.1"
}
}
@@ -126,7 +126,7 @@ dependencies {
def lifecycle_version = '2.2.0'
def arch_version = '2.1.0'
def markwon_version = '3.1.0'
- def daggerVersion = '2.37'
+ def daggerVersion = '2.38'
def work_version = '2.5.0'
def retrofit_version = '2.9.0'
@@ -136,8 +136,8 @@ dependencies {
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutines_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version"
- implementation "androidx.appcompat:appcompat:1.3.0"
- implementation "androidx.core:core-ktx:1.5.0"
+ implementation "androidx.appcompat:appcompat:1.3.1"
+ implementation "androidx.core:core-ktx:1.6.0"
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
@@ -185,30 +185,30 @@ dependencies {
implementation 'com.otaliastudios:transcoder:0.10.3'
// 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 'org.robolectric:robolectric:4.5.1'
//testImplementation 'org.robolectric:shadows-support-v4:3.0'
// Note: version sticks to 1.9.2 due to https://github.com/mockk/mockk/issues/281
- testImplementation 'io.mockk:mockk:1.11.0'
- testImplementation 'org.amshove.kluent:kluent-android:1.67'
+ testImplementation 'io.mockk:mockk:1.12.0'
+ testImplementation 'org.amshove.kluent:kluent-android:1.68'
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version"
// Plant Timber tree for test
testImplementation 'net.lachlanmckee:timber-junit-rule:1.0.1'
kaptAndroidTest "com.google.dagger:dagger-compiler:$daggerVersion"
- androidTestImplementation 'androidx.test:core:1.3.0'
- androidTestImplementation 'androidx.test:runner:1.3.0'
- androidTestImplementation 'androidx.test:rules:1.3.0'
- androidTestImplementation 'androidx.test.ext:junit:1.1.2'
- androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
- androidTestImplementation 'org.amshove.kluent:kluent-android:1.65'
- androidTestImplementation 'io.mockk:mockk-android:1.11.0'
+ androidTestImplementation 'androidx.test:core:1.4.0'
+ androidTestImplementation 'androidx.test:runner:1.4.0'
+ androidTestImplementation 'androidx.test:rules:1.4.0'
+ androidTestImplementation 'androidx.test.ext:junit:1.1.3'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
+ androidTestImplementation 'org.amshove.kluent:kluent-android:1.68'
+ androidTestImplementation 'io.mockk:mockk-android:1.12.0'
androidTestImplementation "androidx.arch.core:core-testing:$arch_version"
androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version"
// Plant Timber tree for test
androidTestImplementation 'net.lachlanmckee:timber-junit-rule:1.0.1'
- androidTestUtil 'androidx.test:orchestrator:1.3.0'
+ androidTestUtil 'androidx.test:orchestrator:1.4.0'
}
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CommonTestHelper.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CommonTestHelper.kt
index 287a4233d9..6e07223ac7 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CommonTestHelper.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CommonTestHelper.kt
@@ -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 {
return HomeServerConnectionConfig.Builder()
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTest.kt
index eb8b8b9730..89d297c592 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTest.kt
@@ -816,7 +816,7 @@ class KeysBackupTest : InstrumentedTest {
// - Do an e2e backup to the homeserver
mKeysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup)
- // Get key backup version from the home server
+ // Get key backup version from the homeserver
val keysVersionResult = mTestHelper.doSync {
keysBackup.getCurrentVersion(it)
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixPatterns.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixPatterns.kt
index 7f5f3f54ef..9a5e40ffeb 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixPatterns.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixPatterns.kt
@@ -16,8 +16,12 @@
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
+ * Ref: https://matrix.org/docs/spec/appendices#identifier-grammar
*/
object MatrixPatterns {
@@ -25,7 +29,7 @@ object MatrixPatterns {
private const val DOMAIN_REGEX = ":[A-Z0-9.-]+(:[0-9]{2,5})?"
// 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"
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 (~),
* 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
}
@@ -163,4 +167,18 @@ object MatrixPatterns {
"[^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(":")
+ }
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/HomeServerConnectionConfig.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/HomeServerConnectionConfig.kt
index e1c5171bfc..215f0a0351 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/HomeServerConnectionConfig.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/HomeServerConnectionConfig.kt
@@ -18,11 +18,11 @@ package org.matrix.android.sdk.api.auth.data
import android.net.Uri
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.internal.network.ssl.Fingerprint
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.
@@ -31,7 +31,12 @@ import okhttp3.TlsVersion
*/
@JsonClass(generateAdapter = true)
data class HomeServerConnectionConfig(
+ // This is the homeserver URL entered by the user
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 antiVirusServerUri: Uri? = null,
val allowedFingerprints: List = emptyList(),
@@ -47,7 +52,6 @@ data class HomeServerConnectionConfig(
* This builder should be use to create a [HomeServerConnectionConfig] instance.
*/
class Builder {
-
private lateinit var homeServerUri: Uri
private var identityServerUri: Uri? = null
private var antiVirusServerUri: Uri? = null
@@ -69,14 +73,14 @@ data class HomeServerConnectionConfig(
*/
fun withHomeServerUri(hsUri: Uri): Builder {
if (hsUri.scheme != "http" && hsUri.scheme != "https") {
- throw RuntimeException("Invalid home server URI: $hsUri")
+ throw RuntimeException("Invalid homeserver URI: $hsUri")
}
// ensure trailing /
val hsString = hsUri.toString().ensureTrailingSlash()
homeServerUri = try {
Uri.parse(hsString)
} catch (e: Exception) {
- throw RuntimeException("Invalid home server URI: $hsUri")
+ throw RuntimeException("Invalid homeserver URI: $hsUri")
}
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.
* @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.
* @return this builder
@@ -234,16 +238,16 @@ data class HomeServerConnectionConfig(
*/
fun build(): HomeServerConnectionConfig {
return HomeServerConnectionConfig(
- homeServerUri,
- identityServerUri,
- antiVirusServerUri,
- allowedFingerprints,
- shouldPin,
- tlsVersions,
- tlsCipherSuites,
- shouldAcceptTlsExtensions,
- allowHttpExtension,
- forceUsageTlsVersions
+ homeServerUri = homeServerUri,
+ identityServerUri = identityServerUri,
+ antiVirusServerUri = antiVirusServerUri,
+ allowedFingerprints = allowedFingerprints,
+ shouldPin = shouldPin,
+ tlsVersions = tlsVersions,
+ tlsCipherSuites = tlsCipherSuites,
+ shouldAcceptTlsExtensions = shouldAcceptTlsExtensions,
+ allowHttpExtension = allowHttpExtension,
+ forceUsageTlsVersions = forceUsageTlsVersions
)
}
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/SessionParams.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/SessionParams.kt
index b2a57c7f5c..b490ac877e 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/SessionParams.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/SessionParams.kt
@@ -51,13 +51,18 @@ data class SessionParams(
val deviceId = credentials.deviceId
/**
- * The current homeserver Url. It can be different that the homeserver url entered
- * during login phase, because a redirection may have occurred
+ * The homeserver Url entered by the user during the login phase.
*/
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
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/registration/RegistrationFlowResponse.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/registration/RegistrationFlowResponse.kt
index 2b1c1c09b3..ac740ddab7 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/registration/RegistrationFlowResponse.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/registration/RegistrationFlowResponse.kt
@@ -38,7 +38,7 @@ data class RegistrationFlowResponse(
val completedStages: List? = 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.
*/
@Json(name = "session")
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/wellknown/WellknownResult.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/wellknown/WellknownResult.kt
index c68a9e9699..56257db79c 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/wellknown/WellknownResult.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/wellknown/WellknownResult.kt
@@ -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
*/
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,
* 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.
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/MatrixIdFailure.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/MatrixIdFailure.kt
new file mode 100644
index 0000000000..8f7bca803d
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/MatrixIdFailure.kt
@@ -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()
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/logger/LoggerTag.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/logger/LoggerTag.kt
new file mode 100644
index 0000000000..51f9b50699
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/logger/LoggerTag.kt
@@ -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"
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/raw/RawService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/raw/RawService.kt
index f1722b2189..3366d040f7 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/raw/RawService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/raw/RawService.kt
@@ -29,8 +29,10 @@ interface RawService {
/**
* 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
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/CallState.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/CallState.kt
index 2dbd1c9b01..47a63b4a25 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/CallState.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/CallState.kt
@@ -16,6 +16,8 @@
package org.matrix.android.sdk.api.session.call
+import org.matrix.android.sdk.api.session.room.model.call.EndCallReason
+
sealed class CallState {
/** Idle, setting up objects */
@@ -42,6 +44,6 @@ sealed class CallState {
* */
data class Connected(val iceConnectionState: MxPeerConnectionState) : CallState()
- /** Terminated. Incoming/Outgoing call, the call is terminated */
- object Terminated : CallState()
+ /** Ended. Incoming/Outgoing call, the call is terminated */
+ data class Ended(val reason: EndCallReason? = null) : CallState()
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/MxCall.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/MxCall.kt
index fcc9f7072d..dd23e81cc6 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/MxCall.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/MxCall.kt
@@ -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.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.util.Optional
@@ -69,7 +69,7 @@ interface MxCall : MxCallDetail {
/**
* End the call
*/
- fun hangUp(reason: CallHangupContent.Reason? = null)
+ fun hangUp(reason: EndCallReason? = null)
/**
* Start a call
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/KeysBackupService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/KeysBackupService.kt
index 465d00168f..4464427b90 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/KeysBackupService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/KeysBackupService.kt
@@ -28,7 +28,7 @@ import org.matrix.android.sdk.internal.crypto.store.SavedKeyBackupKeyInfo
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.
* @param callback onSuccess(null) will be called if there is no backup on the server
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/KeysBackupState.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/KeysBackupState.kt
index 7d0f04ebcf..a4cc133398 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/KeysBackupState.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/KeysBackupState.kt
@@ -54,7 +54,7 @@ enum class KeysBackupState {
// Need to check the current backup version on the homeserver
Unknown,
- // Checking if backup is enabled on home server
+ // Checking if backup is enabled on homeserver
CheckingBackUpOnHomeserver,
// Backup has been stopped because a new backup version has been detected on the homeserver
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt
index 6400dd6444..3d82846e7e 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt
@@ -104,7 +104,7 @@ data class Event(
/**
* 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.
*/
@Transient
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/HomeServerCapabilities.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/HomeServerCapabilities.kt
index da99ab8d54..10c2db4535 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/HomeServerCapabilities.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/HomeServerCapabilities.kt
@@ -32,7 +32,13 @@ data class HomeServerCapabilities(
/**
* 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 {
const val MAX_UPLOAD_FILE_SIZE_UNKNOWN = -1L
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/RoomVersionModel.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/RoomVersionModel.kt
new file mode 100644
index 0000000000..7798b4cc63
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/RoomVersionModel.kt
@@ -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
+)
+
+data class RoomVersionInfo(
+ val version: String,
+ val status: RoomVersionStatus
+)
+
+enum class RoomVersionStatus {
+ STABLE,
+ UNSTABLE
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/Room.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/Room.kt
index cb04b05a74..ebe96b6382 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/Room.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/Room.kt
@@ -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.typing.TypingService
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.space.Space
import org.matrix.android.sdk.api.util.Optional
@@ -57,7 +58,8 @@ interface Room :
RelationService,
RoomCryptoService,
RoomPushRuleService,
- RoomAccountDataService {
+ RoomAccountDataService,
+ RoomVersionService {
/**
* The roomId of this room
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomSummaryQueryParams.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomSummaryQueryParams.kt
index 88ec2de768..b440857518 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomSummaryQueryParams.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomSummaryQueryParams.kt
@@ -39,12 +39,6 @@ fun spaceSummaryQueryParams(init: (RoomSummaryQueryParams.Builder.() -> Unit) =
.build()
}
-enum class RoomCategoryFilter {
- ONLY_DM,
- ONLY_ROOMS,
- ALL
-}
-
/**
* 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]
@@ -59,11 +53,10 @@ data class RoomSummaryQueryParams(
val excludeType: List?,
val includeType: List?,
val activeSpaceFilter: ActiveSpaceFilter?,
- var activeGroupId: String? = null
+ val activeGroupId: String? = null
) {
class Builder {
-
var roomId: QueryStringValue = QueryStringValue.IsNotEmpty
var displayName: QueryStringValue = QueryStringValue.IsNotEmpty
var canonicalAlias: QueryStringValue = QueryStringValue.NoCondition
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallHangupContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallHangupContent.kt
index 9d6e1a7eae..31f801dd6f 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallHangupContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallHangupContent.kt
@@ -43,29 +43,5 @@ data class CallHangupContent(
* or `invite_timeout` for when the other party did not answer in time.
* One of: ["ice_failed", "invite_timeout"]
*/
- @Json(name = "reason") val reason: Reason? = null
-) : 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
- }
-}
+ @Json(name = "reason") val reason: EndCallReason? = null
+) : CallSignalingContent
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallRejectContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallRejectContent.kt
index ea412fbe3e..1b9a7186e2 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallRejectContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallRejectContent.kt
@@ -36,5 +36,10 @@ data class CallRejectContent(
/**
* 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
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/EndCallReason.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/EndCallReason.kt
new file mode 100644
index 0000000000..60e038b2f9
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/EndCallReason.kt
@@ -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
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/CreateRoomParams.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/CreateRoomParams.kt
index ca8c66bb3b..c46d7d0fd2 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/CreateRoomParams.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/CreateRoomParams.kt
@@ -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.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
-// TODO Give a way to include other initial states
open class CreateRoomParams {
/**
* 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()
+ /**
+ * 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()
+
/**
* Set to true to disable federation of this room.
* Default: false
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/CreateRoomStateEvent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/CreateRoomStateEvent.kt
new file mode 100644
index 0000000000..fcfdc3e333
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/CreateRoomStateEvent.kt
@@ -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 = ""
+)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageAudioContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageAudioContent.kt
index fcf3d73cbe..b4ba5c0a66 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageAudioContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageAudioContent.kt
@@ -54,5 +54,5 @@ data class MessageAudioContent(
) : MessageWithAttachmentContent {
override val mimeType: String?
- get() = encryptedFileInfo?.mimetype ?: audioInfo?.mimeType
+ get() = audioInfo?.mimeType
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageFileContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageFileContent.kt
index d93f115322..96877b4d9f 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageFileContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageFileContent.kt
@@ -60,8 +60,7 @@ data class MessageFileContent(
) : MessageWithAttachmentContent {
override val mimeType: String?
- get() = encryptedFileInfo?.mimetype
- ?: info?.mimeType
+ get() = info?.mimeType
?: MimeTypeMap.getFileExtensionFromUrl(filename ?: body)?.let { extension ->
MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension)
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageImageContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageImageContent.kt
index 73e27b64e3..73fd1eab56 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageImageContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageImageContent.kt
@@ -20,7 +20,6 @@ import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import org.matrix.android.sdk.api.session.events.model.Content
import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent
-import org.matrix.android.sdk.api.util.MimeTypes
import org.matrix.android.sdk.internal.crypto.model.rest.EncryptedFileInfo
@JsonClass(generateAdapter = true)
@@ -55,5 +54,5 @@ data class MessageImageContent(
@Json(name = "file") override val encryptedFileInfo: EncryptedFileInfo? = null
) : MessageImageInfoContent {
override val mimeType: String?
- get() = encryptedFileInfo?.mimetype ?: info?.mimeType ?: MimeTypes.Images
+ get() = info?.mimeType
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageStickerContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageStickerContent.kt
index 280316d4b5..8e1d4d3d75 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageStickerContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageStickerContent.kt
@@ -55,5 +55,5 @@ data class MessageStickerContent(
@Json(name = "file") override val encryptedFileInfo: EncryptedFileInfo? = null
) : MessageImageInfoContent {
override val mimeType: String?
- get() = encryptedFileInfo?.mimetype ?: info?.mimeType
+ get() = info?.mimeType
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageVideoContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageVideoContent.kt
index b7581c9fbf..3f5d2dab2e 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageVideoContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageVideoContent.kt
@@ -53,5 +53,5 @@ data class MessageVideoContent(
@Json(name = "file") override val encryptedFileInfo: EncryptedFileInfo? = null
) : MessageWithAttachmentContent {
override val mimeType: String?
- get() = encryptedFileInfo?.mimetype ?: videoInfo?.mimeType
+ get() = videoInfo?.mimeType
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/version/RoomVersionService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/version/RoomVersionService.kt
new file mode 100644
index 0000000000..ea67b55174
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/version/RoomVersionService.kt
@@ -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
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/Space.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/Space.kt
index db25762c2f..3bae6126e0 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/Space.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/Space.kt
@@ -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.model.RoomSummary
+import org.matrix.android.sdk.api.session.space.model.SpaceChildContent
interface Space {
@@ -38,6 +39,8 @@ interface Space {
autoJoin: Boolean = false,
suggested: Boolean? = false)
+ fun getChildInfo(roomId: String): SpaceChildContent?
+
suspend fun removeChildren(roomId: String)
@Throws
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/AuthAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/AuthAPI.kt
index 5a9fa9edf6..50d9e5a06c 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/AuthAPI.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/AuthAPI.kt
@@ -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.LoginFlowResponse
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.WebClientConfig
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.AddThreePidRegistrationResponse
@@ -44,16 +44,16 @@ import retrofit2.http.Url
*/
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")
- 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")
- suspend fun getRiotConfig(): RiotConfig
+ suspend fun getWebClientConfig(): WebClientConfig
/**
* Get the version information of the homeserver
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt
index 20ce438d8e..e76dc28734 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt
@@ -19,6 +19,8 @@ package org.matrix.android.sdk.internal.auth
import android.net.Uri
import dagger.Lazy
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.data.Credentials
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.wellknown.WellknownResult
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.util.appendParamToUrl
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.login.DefaultLoginWizard
import org.matrix.android.sdk.internal.auth.login.DirectLoginTask
@@ -122,7 +125,7 @@ internal class DefaultAuthenticationService @Inject constructor(
private fun getHomeServerUrlBase(): String? {
return pendingSessionData
?.homeServerConnectionConfig
- ?.homeServerUri
+ ?.homeServerUriBase
?.toString()
?.trim { it == '/' }
}
@@ -143,9 +146,9 @@ internal class DefaultAuthenticationService @Inject constructor(
return result.fold(
{
// 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(
- homeServerUri = Uri.parse(it.homeServerUrl)
+ homeServerUriBase = Uri.parse(it.homeServerUrl)
)
pendingSessionData = PendingSessionData(alteredHomeServerConnectionConfig)
@@ -154,7 +157,7 @@ internal class DefaultAuthenticationService @Inject constructor(
},
{
if (it is UnrecognizedCertificateException) {
- throw Failure.UnrecognizedCertificateFailure(homeServerConnectionConfig.homeServerUri.toString(), it.fingerprint)
+ throw Failure.UnrecognizedCertificateFailure(homeServerConnectionConfig.homeServerUriBase.toString(), it.fingerprint)
} else {
throw it
}
@@ -165,46 +168,57 @@ internal class DefaultAuthenticationService @Inject constructor(
private suspend fun getLoginFlowInternal(homeServerConnectionConfig: HomeServerConnectionConfig): LoginFlowResult {
val authAPI = buildAuthAPI(homeServerConnectionConfig)
- // First check the homeserver version
- return runCatching {
- executeRequest(null) {
- authAPI.versions()
+ // First check if there is a well-known file
+ return try {
+ getWellknownLoginFlowInternal(homeServerConnectionConfig)
+ } 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 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 {
executeRequest(null) {
- authAPI.getRiotConfigDomain(domain)
+ authAPI.getWebClientConfigDomain(domain)
}
}
- .map { riotConfig ->
- onRiotConfigRetrieved(homeServerConnectionConfig, riotConfig)
+ .map { webClientConfig ->
+ onWebClientConfigRetrieved(homeServerConnectionConfig, webClientConfig)
}
.fold(
{
@@ -214,7 +228,7 @@ internal class DefaultAuthenticationService @Inject constructor(
if (it is Failure.OtherServerError
&& it.httpCode == HttpsURLConnection.HTTP_NOT_FOUND /* 404 */) {
// Try with config.json
- getRiotLoginFlowInternal(homeServerConnectionConfig)
+ getWebClientLoginFlowInternal(homeServerConnectionConfig)
} else {
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)
- // Ok, try to get the config.json file of a RiotWeb client
- return runCatching {
- executeRequest(null) {
- authAPI.getRiotConfig()
- }
+ // Ok, try to get the config.json file of a Web client
+ return executeRequest(null) {
+ authAPI.getWebClientConfig()
}
- .map { riotConfig ->
- onRiotConfigRetrieved(homeServerConnectionConfig, riotConfig)
+ .let { webClientConfig ->
+ 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 {
- val defaultHomeServerUrl = riotConfig.getPreferredHomeServerUrl()
+ private suspend fun onWebClientConfigRetrieved(homeServerConnectionConfig: HomeServerConnectionConfig, webClientConfig: WebClientConfig): LoginFlowResult {
+ val defaultHomeServerUrl = webClientConfig.getPreferredHomeServerUrl()
if (defaultHomeServerUrl?.isNotEmpty() == true) {
// Ok, good sign, we got a default hs url
val newHomeServerConnectionConfig = homeServerConnectionConfig.copy(
- homeServerUri = Uri.parse(defaultHomeServerUrl)
+ homeServerUriBase = Uri.parse(defaultHomeServerUrl)
)
val newAuthAPI = buildAuthAPI(newHomeServerConnectionConfig)
@@ -275,15 +273,13 @@ internal class DefaultAuthenticationService @Inject constructor(
val domain = homeServerConnectionConfig.homeServerUri.host
?: throw Failure.OtherServerError("", HttpsURLConnection.HTTP_NOT_FOUND /* 404 */)
- // Create a fake userId, for the getWellknown task
- val fakeUserId = "@alice:$domain"
- val wellknownResult = getWellknownTask.execute(GetWellknownTask.Params(fakeUserId, homeServerConnectionConfig))
+ val wellknownResult = getWellknownTask.execute(GetWellknownTask.Params(domain, homeServerConnectionConfig))
return when (wellknownResult) {
is WellknownResult.Prompt -> {
val newHomeServerConnectionConfig = homeServerConnectionConfig.copy(
- homeServerUri = Uri.parse(wellknownResult.homeServerUrl),
- identityServerUri = wellknownResult.identityServerUrl?.let { Uri.parse(it) }
+ homeServerUriBase = Uri.parse(wellknownResult.homeServerUrl),
+ identityServerUri = wellknownResult.identityServerUrl?.let { Uri.parse(it) } ?: homeServerConnectionConfig.identityServerUri
)
val newAuthAPI = buildAuthAPI(newHomeServerConnectionConfig)
@@ -379,7 +375,14 @@ internal class DefaultAuthenticationService @Inject constructor(
override suspend fun getWellKnownData(matrixId: String,
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,
@@ -390,7 +393,7 @@ internal class DefaultAuthenticationService @Inject constructor(
}
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)
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/IsValidClientServerApiTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/IsValidClientServerApiTask.kt
index 867cf46b8d..bc3d887000 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/IsValidClientServerApiTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/IsValidClientServerApiTask.kt
@@ -42,7 +42,7 @@ internal class DefaultIsValidClientServerApiTask @Inject constructor(
override suspend fun execute(params: IsValidClientServerApiTask.Params): Boolean {
val client = buildClient(params.homeServerConnectionConfig)
- val homeServerUrl = params.homeServerConnectionConfig.homeServerUri.toString()
+ val homeServerUrl = params.homeServerConnectionConfig.homeServerUriBase.toString()
val authAPI = retrofitFactory.create(client, homeServerUrl)
.create(AuthAPI::class.java)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/SessionCreator.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/SessionCreator.kt
index 7c4a0c38ec..160fd2d556 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/SessionCreator.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/SessionCreator.kt
@@ -38,7 +38,7 @@ internal class DefaultSessionCreator @Inject constructor(
) : 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
*/
override suspend fun createSession(credentials: Credentials, homeServerConnectionConfig: HomeServerConnectionConfig): Session {
@@ -56,7 +56,7 @@ internal class DefaultSessionCreator @Inject constructor(
tryOrNull {
isValidClientServerApiTask.execute(
IsValidClientServerApiTask.Params(
- homeServerConnectionConfig.copy(homeServerUri = it)
+ homeServerConnectionConfig.copy(homeServerUriBase = it)
)
)
.also { Timber.d("Overriding homeserver url: $it") }
@@ -66,7 +66,7 @@ internal class DefaultSessionCreator @Inject constructor(
val sessionParams = SessionParams(
credentials = credentials,
homeServerConnectionConfig = homeServerConnectionConfig.copy(
- homeServerUri = overriddenUrl ?: homeServerConnectionConfig.homeServerUri,
+ homeServerUriBase = overriddenUrl ?: homeServerConnectionConfig.homeServerUriBase,
identityServerUri = credentials.discoveryInformation?.identityServer?.baseURL
// remove trailing "/"
?.trim { it == '/' }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/RiotConfig.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/WebClientConfig.kt
similarity index 84%
rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/RiotConfig.kt
rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/WebClientConfig.kt
index e61358a67b..65c3dc64a6 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/RiotConfig.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/WebClientConfig.kt
@@ -20,7 +20,7 @@ import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@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"
*/
@@ -28,7 +28,7 @@ internal data class RiotConfig(
val defaultHomeServerUrl: String?,
@Json(name = "default_server_config")
- val defaultServerConfig: RiotConfigDefaultServerConfig?
+ val defaultServerConfig: WebClientConfigDefaultServerConfig?
) {
fun getPreferredHomeServerUrl(): String? {
return defaultHomeServerUrl
@@ -38,13 +38,13 @@ internal data class RiotConfig(
}
@JsonClass(generateAdapter = true)
-internal data class RiotConfigDefaultServerConfig(
+internal data class WebClientConfigDefaultServerConfig(
@Json(name = "m.homeserver")
- val homeServer: RiotConfigBaseConfig? = null
+ val homeServer: WebClientConfigBaseConfig? = null
)
@JsonClass(generateAdapter = true)
-internal data class RiotConfigBaseConfig(
+internal data class WebClientConfigBaseConfig(
@Json(name = "base_url")
val baseURL: String? = null
)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/db/AuthRealmMigration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/db/AuthRealmMigration.kt
index bb2667228b..c2104690b3 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/db/AuthRealmMigration.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/db/AuthRealmMigration.kt
@@ -16,17 +16,19 @@
package org.matrix.android.sdk.internal.auth.db
-import org.matrix.android.sdk.api.auth.data.Credentials
-import org.matrix.android.sdk.api.auth.data.sessionId
-import org.matrix.android.sdk.internal.di.MoshiProvider
+import android.net.Uri
import io.realm.DynamicRealm
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
internal object AuthRealmMigration : RealmMigration {
// Current schema version
- const val SCHEMA_VERSION = 3L
+ const val SCHEMA_VERSION = 4L
override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) {
Timber.d("Migrating Auth Realm from $oldVersion to $newVersion")
@@ -34,6 +36,7 @@ internal object AuthRealmMigration : RealmMigration {
if (oldVersion <= 0) migrateTo1(realm)
if (oldVersion <= 1) migrateTo2(realm)
if (oldVersion <= 2) migrateTo3(realm)
+ if (oldVersion <= 3) migrateTo4(realm)
}
private fun migrateTo1(realm: DynamicRealm) {
@@ -81,4 +84,34 @@ internal object AuthRealmMigration : RealmMigration {
}
?.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))
+ }
+ }
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/DirectLoginTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/DirectLoginTask.kt
index 77bbb8096f..3888633723 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/DirectLoginTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/DirectLoginTask.kt
@@ -50,7 +50,7 @@ internal class DefaultDirectLoginTask @Inject constructor(
override suspend fun execute(params: DirectLoginTask.Params): Session {
val client = buildClient(params.homeServerConnectionConfig)
- val homeServerUrl = params.homeServerConnectionConfig.homeServerUri.toString()
+ val homeServerUrl = params.homeServerConnectionConfig.homeServerUriBase.toString()
val authAPI = retrofitFactory.create(client, homeServerUrl)
.create(AuthAPI::class.java)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoModule.kt
index e114f86a99..84d4fef5af 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoModule.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoModule.kt
@@ -112,7 +112,6 @@ internal abstract class CryptoModule {
@SessionScope
fun providesRealmConfiguration(@SessionFilesDirectory directory: File,
@UserMd5 userMd5: String,
- realmCryptoStoreMigration: RealmCryptoStoreMigration,
realmKeysUtils: RealmKeysUtils): RealmConfiguration {
return RealmConfiguration.Builder()
.directory(directory)
@@ -123,7 +122,7 @@ internal abstract class CryptoModule {
.modules(RealmCryptoStoreModule())
.allowWritesOnUiThread(true)
.schemaVersion(RealmCryptoStoreMigration.CRYPTO_STORE_SCHEMA_VERSION)
- .migration(realmCryptoStoreMigration)
+ .migration(RealmCryptoStoreMigration)
.build()
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt
index 63f15aaf6e..79910c6de2 100755
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt
@@ -16,6 +16,7 @@
package org.matrix.android.sdk.internal.crypto
+import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.MatrixPatterns
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)
} catch (throwable: Throwable) {
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
}
Timber.v("## CRYPTO | doKeyDownloadForUsers() : Got keys for " + filteredUsers.size + " users")
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/attachments/MXEncryptedAttachments.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/attachments/MXEncryptedAttachments.kt
index 5a9852b6db..70730326da 100755
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/attachments/MXEncryptedAttachments.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/attachments/MXEncryptedAttachments.kt
@@ -39,7 +39,9 @@ internal object MXEncryptedAttachments {
private const val SECRET_KEY_SPEC_ALGORITHM = "AES"
private const val MESSAGE_DIGEST_ALGORITHM = "SHA-256"
- fun encrypt(clearStream: InputStream, mimetype: String?, outputFile: File, progress: ((current: Int, total: Int) -> Unit)): EncryptedFileInfo {
+ fun encrypt(clearStream: InputStream,
+ outputFile: File,
+ progress: ((current: Int, total: Int) -> Unit)): EncryptedFileInfo {
val t0 = System.currentTimeMillis()
val secureRandom = SecureRandom()
val initVectorBytes = ByteArray(16) { 0.toByte() }
@@ -86,7 +88,6 @@ internal object MXEncryptedAttachments {
return EncryptedFileInfo(
url = null,
- mimetype = mimetype,
key = EncryptedFileKey(
alg = "A256CTR",
ext = true,
@@ -155,10 +156,9 @@ internal object MXEncryptedAttachments {
* Encrypt an attachment stream.
* DO NOT USE for big files, it will load all in memory
* @param attachmentStream the attachment stream. Will be closed after this method call.
- * @param mimetype the mime type
* @return the encryption file info
*/
- fun encryptAttachment(attachmentStream: InputStream, mimetype: String?): EncryptionResult {
+ fun encryptAttachment(attachmentStream: InputStream): EncryptionResult {
val t0 = System.currentTimeMillis()
val secureRandom = SecureRandom()
@@ -207,7 +207,6 @@ internal object MXEncryptedAttachments {
return EncryptionResult(
encryptedFileInfo = EncryptedFileInfo(
url = null,
- mimetype = mimetype,
key = EncryptedFileKey(
alg = "A256CTR",
ext = true,
@@ -232,7 +231,9 @@ internal object MXEncryptedAttachments {
* @param outputStream the outputStream where the decrypted attachment will be write.
* @return true in case of success, false in case of error
*/
- fun decryptAttachment(attachmentStream: InputStream?, elementToDecrypt: ElementToDecrypt?, outputStream: OutputStream): Boolean {
+ fun decryptAttachment(attachmentStream: InputStream?,
+ elementToDecrypt: ElementToDecrypt?,
+ outputStream: OutputStream): Boolean {
// sanity checks
if (null == attachmentStream || elementToDecrypt == null) {
Timber.e("## decryptAttachment() : null stream")
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/MXDeviceInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/MXDeviceInfo.kt
index 00b8bde5d9..68cc41005e 100755
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/MXDeviceInfo.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/MXDeviceInfo.kt
@@ -56,7 +56,7 @@ data class MXDeviceInfo(
val signatures: Map>? = null,
/*
- * Additional data from the home server.
+ * Additional data from the homeserver.
*/
@Json(name = "unsigned")
val unsigned: JsonDict? = null,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/EncryptedFileInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/EncryptedFileInfo.kt
index 0ed6c0dac9..4fc3adb42c 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/EncryptedFileInfo.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/EncryptedFileInfo.kt
@@ -29,12 +29,6 @@ data class EncryptedFileInfo(
@Json(name = "url")
val url: String? = null,
- /**
- * Not documented
- */
- @Json(name = "mimetype")
- val mimetype: String? = null,
-
/**
* Required. A JSON Web Key object.
*/
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt
index 181bd94cc7..3d12e74fcd 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt
@@ -475,7 +475,7 @@ internal interface IMXCryptoStore {
fun getGossipingEvents(): List
fun setDeviceKeysUploaded(uploaded: Boolean)
- fun getDeviceKeysUploaded(): Boolean
+ fun areDeviceKeysUploaded(): Boolean
fun tidyUpDataBase()
fun logDbUsageInfo()
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt
index 9ae93d61eb..9266f8fe7d 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt
@@ -937,7 +937,7 @@ internal class RealmCryptoStore @Inject constructor(
}
}
- override fun getDeviceKeysUploaded(): Boolean {
+ override fun areDeviceKeysUploaded(): Boolean {
return doWithRealm(realmConfiguration) {
it.where().findFirst()?.deviceKeysSentToServer
} ?: false
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStoreMigration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStoreMigration.kt
index bca7914388..8e77f5b823 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStoreMigration.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStoreMigration.kt
@@ -18,6 +18,9 @@ package org.matrix.android.sdk.internal.crypto.store.db
import com.squareup.moshi.Moshi
import com.squareup.moshi.Types
+import io.realm.DynamicRealm
+import io.realm.RealmMigration
+import io.realm.RealmObjectSchema
import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.util.JsonDict
import org.matrix.android.sdk.internal.crypto.model.MXDeviceInfo
@@ -35,29 +38,24 @@ import org.matrix.android.sdk.internal.crypto.store.db.model.KeysBackupDataEntit
import org.matrix.android.sdk.internal.crypto.store.db.model.MyDeviceLastSeenInfoEntityFields
import org.matrix.android.sdk.internal.crypto.store.db.model.OlmInboundGroupSessionEntityFields
import org.matrix.android.sdk.internal.crypto.store.db.model.OlmSessionEntityFields
+import org.matrix.android.sdk.internal.crypto.store.db.model.OutboundGroupSessionInfoEntityFields
import org.matrix.android.sdk.internal.crypto.store.db.model.OutgoingGossipingRequestEntityFields
import org.matrix.android.sdk.internal.crypto.store.db.model.SharedSessionEntityFields
import org.matrix.android.sdk.internal.crypto.store.db.model.TrustLevelEntityFields
import org.matrix.android.sdk.internal.crypto.store.db.model.UserEntityFields
import org.matrix.android.sdk.internal.crypto.store.db.model.WithHeldSessionEntityFields
+import org.matrix.android.sdk.internal.di.MoshiProvider
import org.matrix.android.sdk.internal.di.SerializeNulls
-import io.realm.DynamicRealm
-import io.realm.RealmMigration
-import io.realm.RealmObjectSchema
-import org.matrix.android.sdk.internal.crypto.store.db.model.OutboundGroupSessionInfoEntityFields
import org.matrix.androidsdk.crypto.data.MXOlmInboundGroupSession2
import timber.log.Timber
-import javax.inject.Inject
import org.matrix.androidsdk.crypto.data.MXDeviceInfo as LegacyMXDeviceInfo
-internal class RealmCryptoStoreMigration @Inject constructor(private val crossSigningKeysMapper: CrossSigningKeysMapper) : RealmMigration {
+internal object RealmCryptoStoreMigration : RealmMigration {
- companion object {
- // 0, 1, 2: legacy Riot-Android
- // 3: migrate to RiotX schema
- // 4, 5, 6, 7, 8, 9: migrations from RiotX (which was previously 1, 2, 3, 4, 5, 6)
- const val CRYPTO_STORE_SCHEMA_VERSION = 12L
- }
+ // 0, 1, 2: legacy Riot-Android
+ // 3: migrate to RiotX schema
+ // 4, 5, 6, 7, 8, 9: migrations from RiotX (which was previously 1, 2, 3, 4, 5, 6)
+ const val CRYPTO_STORE_SCHEMA_VERSION = 12L
private fun RealmObjectSchema.addFieldIfNotExists(fieldName: String, fieldType: Class<*>): RealmObjectSchema {
if (!hasField(fieldName)) {
@@ -384,6 +382,8 @@ internal class RealmCryptoStoreMigration @Inject constructor(private val crossSi
private fun migrateTo7(realm: DynamicRealm) {
Timber.d("Step 6 -> 7")
Timber.d("Updating KeyInfoEntity table")
+ val crossSigningKeysMapper = CrossSigningKeysMapper(MoshiProvider.providesMoshi())
+
val keyInfoEntities = realm.where("KeyInfoEntity").findAll()
try {
keyInfoEntities.forEach {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/InitializeCrossSigningTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/InitializeCrossSigningTask.kt
index f8a8354e48..1d40e5defd 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/InitializeCrossSigningTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/InitializeCrossSigningTask.kt
@@ -97,7 +97,7 @@ internal class DefaultInitializeCrossSigningTask @Inject constructor(
Timber.v("## CrossSigning - sskPublicKey:$sskPublicKey")
- // Sign userSigningKey with master
+ // Sign selfSigningKey with master
val signedSSK = CryptoCrossSigningKey.Builder(userId, KeyUsage.SELF_SIGNING)
.key(sskPublicKey)
.build()
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt
index 2b3c3b28ee..28ae4d8bfd 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt
@@ -19,9 +19,10 @@ package org.matrix.android.sdk.internal.database
import io.realm.DynamicRealm
import io.realm.FieldAttribute
import io.realm.RealmMigration
-import org.matrix.android.sdk.api.session.room.model.VersioningState
import org.matrix.android.sdk.api.session.events.model.EventType
+import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesContent
+import org.matrix.android.sdk.api.session.room.model.VersioningState
import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent
import org.matrix.android.sdk.api.session.room.model.tag.RoomTag
import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntityFields
@@ -40,14 +41,12 @@ import org.matrix.android.sdk.internal.database.model.SpaceChildSummaryEntityFie
import org.matrix.android.sdk.internal.database.model.SpaceParentSummaryEntityFields
import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields
import org.matrix.android.sdk.internal.di.MoshiProvider
+import org.matrix.android.sdk.internal.query.process
import timber.log.Timber
-import javax.inject.Inject
-class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
+internal object RealmSessionStoreMigration : RealmMigration {
- companion object {
- const val SESSION_STORE_SCHEMA_VERSION = 14L
- }
+ const val SESSION_STORE_SCHEMA_VERSION = 16L
override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) {
Timber.v("Migrating Realm Session from $oldVersion to $newVersion")
@@ -66,6 +65,8 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
if (oldVersion <= 11) migrateTo12(realm)
if (oldVersion <= 12) migrateTo13(realm)
if (oldVersion <= 13) migrateTo14(realm)
+ if (oldVersion <= 14) migrateTo15(realm)
+ if (oldVersion <= 15) migrateTo16(realm)
}
private fun migrateTo1(realm: DynamicRealm) {
@@ -292,7 +293,7 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
Timber.d("Step 13 -> 14")
val roomAccountDataSchema = realm.schema.create("RoomAccountDataEntity")
.addField(RoomAccountDataEntityFields.CONTENT_STR, String::class.java)
- .addField(RoomAccountDataEntityFields.TYPE, String::class.java, FieldAttribute.INDEXED)
+ .addField(RoomAccountDataEntityFields.TYPE, String::class.java, FieldAttribute.INDEXED)
realm.schema.get("RoomEntity")
?.addRealmListField(RoomEntityFields.ACCOUNT_DATA.`$`, roomAccountDataSchema)
@@ -306,4 +307,27 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
roomAccountDataSchema.isEmbedded = true
}
+
+ private fun migrateTo15(realm: DynamicRealm) {
+ Timber.d("Step 14 -> 15")
+ // fix issue with flattenParentIds on DM that kept growing with duplicate
+ // so we reset it, will be updated next sync
+ realm.where("RoomSummaryEntity")
+ .process(RoomSummaryEntityFields.MEMBERSHIP_STR, Membership.activeMemberships())
+ .equalTo(RoomSummaryEntityFields.IS_DIRECT, true)
+ .findAll()
+ .onEach {
+ 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)
+ }
+ }
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/SessionRealmConfigurationFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/SessionRealmConfigurationFactory.kt
index 244fe3432a..1771c5b202 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/SessionRealmConfigurationFactory.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/SessionRealmConfigurationFactory.kt
@@ -43,7 +43,6 @@ internal class SessionRealmConfigurationFactory @Inject constructor(
@SessionFilesDirectory val directory: File,
@SessionId val sessionId: String,
@UserMd5 val userMd5: String,
- val migration: RealmSessionStoreMigration,
context: Context) {
// Keep legacy preferences name for compatibility reason
@@ -72,7 +71,7 @@ internal class SessionRealmConfigurationFactory @Inject constructor(
.allowWritesOnUiThread(true)
.modules(SessionRealmModule())
.schemaVersion(RealmSessionStoreMigration.SESSION_STORE_SCHEMA_VERSION)
- .migration(migration)
+ .migration(RealmSessionStoreMigration)
.build()
// Try creating a realm instance and if it succeeds we can clear the flag
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/HomeServerCapabilitiesMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/HomeServerCapabilitiesMapper.kt
index b18c67294f..2575cdef26 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/HomeServerCapabilitiesMapper.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/HomeServerCapabilitiesMapper.kt
@@ -16,8 +16,15 @@
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.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.di.MoshiProvider
+import org.matrix.android.sdk.internal.session.homeserver.RoomVersions
+import org.matrix.android.sdk.internal.session.room.version.DefaultRoomVersionService
/**
* HomeServerCapabilitiesEntity -> HomeSeverCapabilities
@@ -29,7 +36,30 @@ internal object HomeServerCapabilitiesMapper {
canChangePassword = entity.canChangePassword,
maxUploadFileSize = entity.maxUploadFileSize,
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
+ }
+ )
+ }
+ )
+ }
+ }
+ }
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/HomeServerCapabilitiesEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/HomeServerCapabilitiesEntity.kt
index 763dcf80a2..980449ddfb 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/HomeServerCapabilitiesEntity.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/HomeServerCapabilitiesEntity.kt
@@ -21,6 +21,7 @@ import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities
internal open class HomeServerCapabilitiesEntity(
var canChangePassword: Boolean = true,
+ var roomVersionsJson: String? = null,
var maxUploadFileSize: Long = HomeServerCapabilities.MAX_UPLOAD_FILE_SIZE_UNKNOWN,
var lastVersionIdentityServerSupported: Boolean = false,
var defaultIdentityServerUrl: String? = null,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/UserAccountDataEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/UserAccountDataEntity.kt
index cfdb84d033..e258c20df5 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/UserAccountDataEntity.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/UserAccountDataEntity.kt
@@ -20,7 +20,7 @@ import io.realm.RealmObject
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.
* Users may only view the account data for their own account.
* The account_data may be either global or scoped to a particular rooms.
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/federation/FederationModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/federation/FederationModule.kt
index 320bf1d445..a4eef80c58 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/federation/FederationModule.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/federation/FederationModule.kt
@@ -37,7 +37,8 @@ internal abstract class FederationModule {
fun providesFederationAPI(@Unauthenticated okHttpClient: Lazy,
sessionParams: SessionParams,
retrofitFactory: RetrofitFactory): FederationAPI {
- return retrofitFactory.create(okHttpClient, sessionParams.homeServerUrl).create(FederationAPI::class.java)
+ return retrofitFactory.create(okHttpClient, sessionParams.homeServerUrlBase)
+ .create(FederationAPI::class.java)
}
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/DefaultLegacySessionImporter.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/DefaultLegacySessionImporter.kt
index 4586cfea1e..ad2aff4c9d 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/DefaultLegacySessionImporter.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/DefaultLegacySessionImporter.kt
@@ -42,7 +42,6 @@ import org.matrix.android.sdk.internal.legacy.riot.HomeServerConnectionConfig as
internal class DefaultLegacySessionImporter @Inject constructor(
private val context: Context,
private val sessionParamsStore: SessionParamsStore,
- private val realmCryptoStoreMigration: RealmCryptoStoreMigration,
private val realmKeysUtils: RealmKeysUtils
) : LegacySessionImporter {
@@ -172,7 +171,7 @@ internal class DefaultLegacySessionImporter @Inject constructor(
.name("crypto_store.realm")
.modules(RealmCryptoStoreModule())
.schemaVersion(RealmCryptoStoreMigration.CRYPTO_STORE_SCHEMA_VERSION)
- .migration(realmCryptoStoreMigration)
+ .migration(RealmCryptoStoreMigration)
.build()
Timber.d("Migration: copy DB to encrypted DB")
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/riot/Credentials.java b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/riot/Credentials.java
index 6844744044..0ca0c7db85 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/riot/Credentials.java
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/riot/Credentials.java
@@ -41,7 +41,7 @@ public class Credentials {
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 JSONObject toJson() throws JSONException {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/riot/HomeServerConnectionConfig.java b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/riot/HomeServerConnectionConfig.java
index 21d069f295..75fc187c45 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/riot/HomeServerConnectionConfig.java
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/riot/HomeServerConnectionConfig.java
@@ -44,7 +44,7 @@ import timber.log.Timber;
*/
public class HomeServerConnectionConfig {
- // the home server URI
+ // the homeserver URI
private Uri mHomeServerUri;
// the jitsi server URI. Can be null
@Nullable
@@ -82,7 +82,7 @@ public class HomeServerConnectionConfig {
}
/**
- * Update the home server URI.
+ * Update the homeserver 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() {
return mHomeServerUri;
@@ -145,7 +145,7 @@ public class HomeServerConnectionConfig {
public void setCredentials(Credentials 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.homeServer != null) {
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
public List 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
public List getAcceptedTlsCipherSuites() {
@@ -426,7 +426,7 @@ public class HomeServerConnectionConfig {
*/
public Builder withHomeServerUri(final Uri homeServerUri) {
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 /
@@ -435,7 +435,7 @@ public class HomeServerConnectionConfig {
String url = homeServerUri.toString();
mHomeServerConnectionConfig.mHomeServerUri = Uri.parse(url.substring(0, url.length() - 1));
} catch (Exception e) {
- throw new RuntimeException("Invalid home server URI: " + homeServerUri);
+ throw new RuntimeException("Invalid homeserver URI: " + homeServerUri);
}
} else {
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.
* @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.
* @return this builder
@@ -666,7 +666,7 @@ public class HomeServerConnectionConfig {
public HomeServerConnectionConfig build() {
// Check mandatory parameters
if (mHomeServerConnectionConfig.mHomeServerUri == null) {
- throw new RuntimeException("Home server URI not set");
+ throw new RuntimeException("Homeserver URI not set");
}
return mHomeServerConnectionConfig;
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/riot/LoginStorage.java b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/riot/LoginStorage.java
index 516007524e..2820b66886 100755
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/riot/LoginStorage.java
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/riot/LoginStorage.java
@@ -38,7 +38,7 @@ import timber.log.Timber;
public class 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 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 getCredentialsList() {
SharedPreferences prefs = mContext.getSharedPreferences(PREFS_LOGIN, Context.MODE_PRIVATE);
@@ -85,7 +85,7 @@ public class LoginStorage {
/**
* 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) {
if (null != config && config.getCredentials() != null) {
@@ -203,4 +203,4 @@ public class LoginStorage {
//Need to commit now because called before forcing an app restart
editor.commit();
}
-}
\ No newline at end of file
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/ssl/CertUtil.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/ssl/CertUtil.kt
index 9d7263f56a..976751446b 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/ssl/CertUtil.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/ssl/CertUtil.kt
@@ -253,7 +253,7 @@ internal object CertUtil {
val list = ArrayList()
list.add(builder.build())
// 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)
}
return list
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/DefaultRawService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/DefaultRawService.kt
index 42b826de16..bca1e498de 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/DefaultRawService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/DefaultRawService.kt
@@ -29,10 +29,9 @@ internal class DefaultRawService @Inject constructor(
return getUrlTask.execute(GetUrlTask.Params(url, cacheStrategy))
}
- override suspend fun getWellknown(userId: String): String {
- val homeServerDomain = userId.substringAfter(":")
+ override suspend fun getWellknown(domain: String): String {
return getUrl(
- "https://$homeServerDomain/.well-known/matrix/client",
+ "https://$domain/.well-known/matrix/client",
CacheStrategy.TtlCache(TimeUnit.HOURS.toMillis(8), false)
)
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt
index a284d976d0..1cbf621d36 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt
@@ -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.session.download.DownloadProgressInterceptor.Companion.DOWNLOAD_PROGRESS_INTERCEPTOR_HEADER
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.writeToFile
import timber.log.Timber
@@ -96,6 +97,9 @@ internal class DefaultFileService @Inject constructor(
}
}
+ var atomicFileDownload: AtomicFileCreator? = null
+ var atomicFileDecrypt: AtomicFileCreator? = null
+
if (existingDownload != null) {
// FIXME If the first downloader cancels then we'll unfortunately be cancelled too.
return existingDownload.await()
@@ -131,8 +135,11 @@ internal class DefaultFileService @Inject constructor(
Timber.v("Response size ${response.body?.contentLength()} - Stream available: ${!source.exhausted()}")
// 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()
+ atomicFileCreator.commit()
} else {
Timber.v("## FileService: cache hit for $url")
}
@@ -145,8 +152,10 @@ internal class DefaultFileService @Inject constructor(
Timber.v("## FileService: decrypt file")
// Ensure the parent folder exists
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 ->
- cachedFiles.decryptedFile.outputStream().buffered().use { outputStream ->
+ atomicFileCreator.partFile.outputStream().buffered().use { outputStream ->
MXEncryptedAttachments.decryptAttachment(
inputStream,
elementToDecrypt,
@@ -154,6 +163,7 @@ internal class DefaultFileService @Inject constructor(
)
}
}
+ atomicFileCreator.commit()
if (!decryptSuccess) {
throw IllegalStateException("Decryption error")
}
@@ -174,6 +184,11 @@ internal class DefaultFileService @Inject constructor(
}
toNotify?.completeWith(result)
+ result.onFailure {
+ atomicFileDownload?.cancel()
+ atomicFileDecrypt?.cancel()
+ }
+
return result.getOrThrow()
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt
index 1cf99bbbd7..c2bd1e24ed 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt
@@ -313,7 +313,7 @@ internal class DefaultSession @Inject constructor(
override fun getUiaSsoFallbackUrl(authenticationSessionId: String): String {
val hsBas = sessionParams.homeServerConnectionConfig
- .homeServerUri
+ .homeServerUriBase
.toString()
.trim { it == '/' }
return buildString {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt
index 964620c316..07ba8eb58f 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt
@@ -260,7 +260,7 @@ internal abstract class SessionModule {
sessionParams: SessionParams,
retrofitFactory: RetrofitFactory): Retrofit {
return retrofitFactory
- .create(okHttpClient, sessionParams.homeServerConnectionConfig.homeServerUri.toString())
+ .create(okHttpClient, sessionParams.homeServerConnectionConfig.homeServerUriBase.toString())
}
@JvmStatic
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallEventProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallEventProcessor.kt
index 473adeb8d2..bdc254fc99 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallEventProcessor.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallEventProcessor.kt
@@ -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.EventType
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.SessionScope
import timber.log.Timber
import javax.inject.Inject
+private val loggerTag = LoggerTag("CallEventProcessor", LoggerTag.VOIP)
+
@SessionScope
internal class CallEventProcessor @Inject constructor(private val callSignalingHandler: CallSignalingHandler)
: EventInsertLiveProcessor {
@@ -71,14 +74,8 @@ internal class CallEventProcessor @Inject constructor(private val callSignalingH
}
private fun dispatchToCallSignalingHandlerIfNeeded(event: Event) {
- val now = System.currentTimeMillis()
event.roomId ?: return Unit.also {
- Timber.w("Event with no room id ${event.eventId}")
- }
- val age = now - (event.ageLocalTs ?: now)
- if (age > 40_000) {
- // Too old to ring?
- return
+ Timber.tag(loggerTag.value).w("Event with no room id ${event.eventId}")
}
callSignalingHandler.onCallEvent(event)
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallSignalingHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallSignalingHandler.kt
index b0901af719..59058bf976 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallSignalingHandler.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallSignalingHandler.kt
@@ -16,6 +16,7 @@
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.CallState
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 javax.inject.Inject
+private val loggerTag = LoggerTag("CallSignalingHandler", LoggerTag.VOIP)
+private const val MAX_AGE_TO_RING = 40_000
+
@SessionScope
internal class CallSignalingHandler @Inject constructor(private val activeCallHandler: ActiveCallHandler,
private val mxCallFactory: MxCallFactory,
@@ -111,12 +115,12 @@ internal class CallSignalingHandler @Inject constructor(private val activeCallHa
return
}
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
}
val selectedPartyId = content.selectedPartyId
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
}
callListenersDispatcher.onCallSelectAnswerReceived(content)
@@ -130,7 +134,7 @@ internal class CallSignalingHandler @Inject constructor(private val activeCallHa
return
}
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
}
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
// a partner yet but we're treating the hangup as a reject as per VoIP v0)
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
}
- if (call.state != CallState.Terminated) {
+ if (call.state !is CallState.Ended) {
activeCallHandler.removeCall(content.callId)
callListenersDispatcher.onCallHangupReceived(content)
}
@@ -180,12 +184,18 @@ internal class CallSignalingHandler @Inject constructor(private val activeCallHa
if (event.roomId == null || event.senderId == null) {
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() ?: return
content.callId ?: return
if (invitedCallIds.contains(content.callId)) {
// 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
}
val incomingCall = mxCallFactory.createIncomingCall(
@@ -214,7 +224,8 @@ internal class CallSignalingHandler @Inject constructor(private val activeCallHa
callListenersDispatcher.onCallManagedByOtherSession(content.callId)
} else {
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
}
mxCallFactory.updateOutgoingCallWithOpponentData(call, event.senderId, content, content.capabilities)
@@ -231,7 +242,7 @@ internal class CallSignalingHandler @Inject constructor(private val activeCallHa
activeCallHandler.getCallWithId(it)
}
if (currentCall == null) {
- Timber.v("Call with id $callId is null")
+ Timber.tag(loggerTag.value).v("Call with id $callId is null")
}
return currentCall
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/DefaultCallSignalingService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/DefaultCallSignalingService.kt
index da1f84cc89..4a949e21a6 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/DefaultCallSignalingService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/DefaultCallSignalingService.kt
@@ -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.TurnServerResponse
import org.matrix.android.sdk.internal.session.SessionScope
-import timber.log.Timber
import javax.inject.Inject
@SessionScope
@@ -51,7 +50,6 @@ internal class DefaultCallSignalingService @Inject constructor(
}
override fun getCallWithId(callId: String): MxCall? {
- Timber.v("## VOIP getCallWithId $callId all calls ${activeCallHandler.getActiveCallsLiveData().value?.map { it.callId }}")
return activeCallHandler.getCallWithId(callId)
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/model/MxCallImpl.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/model/MxCallImpl.kt
index f101685a4b..9fc84e6fe5 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/model/MxCallImpl.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/model/MxCallImpl.kt
@@ -17,6 +17,7 @@
package org.matrix.android.sdk.internal.session.call.model
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.CallState
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.CallSelectAnswerContent
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.util.Optional
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 java.math.BigDecimal
+private val loggerTag = LoggerTag("MxCallImpl", LoggerTag.VOIP)
+
internal class MxCallImpl(
override val callId: String,
override val isOutgoing: Boolean,
@@ -93,7 +97,7 @@ internal class MxCallImpl(
try {
it.onStateUpdate(this)
} 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) {
if (!isOutgoing) return
- Timber.v("## VOIP offerSdp $callId")
+ Timber.tag(loggerTag.value).v("offerSdp $callId")
state = CallState.Dialing
CallInviteContent(
callId = callId,
@@ -124,7 +128,7 @@ internal class MxCallImpl(
}
override fun sendLocalCallCandidates(candidates: List) {
- Timber.v("Send local call canditates $callId: $candidates")
+ Timber.tag(loggerTag.value).v("Send local call canditates $callId: $candidates")
CallCandidatesContent(
callId = callId,
partyId = ourPartyId,
@@ -141,11 +145,11 @@ internal class MxCallImpl(
override fun reject() {
if (opponentVersion < 1) {
- Timber.v("Opponent version is less than 1 ($opponentVersion): sending hangup instead of reject")
- hangUp()
+ Timber.tag(loggerTag.value).v("Opponent version is less than 1 ($opponentVersion): sending hangup instead of reject")
+ hangUp(EndCallReason.USER_HANGUP)
return
}
- Timber.v("## VOIP reject $callId")
+ Timber.tag(loggerTag.value).v("reject $callId")
CallRejectContent(
callId = callId,
partyId = ourPartyId,
@@ -153,24 +157,24 @@ internal class MxCallImpl(
)
.let { createEventAndLocalEcho(type = EventType.CALL_REJECT, roomId = roomId, content = it.toContent()) }
.also { eventSenderProcessor.postEvent(it) }
- state = CallState.Terminated
+ state = CallState.Ended(reason = EndCallReason.USER_HANGUP)
}
- override fun hangUp(reason: CallHangupContent.Reason?) {
- Timber.v("## VOIP hangup $callId")
+ override fun hangUp(reason: EndCallReason?) {
+ Timber.tag(loggerTag.value).v("hangup $callId")
CallHangupContent(
callId = callId,
partyId = ourPartyId,
- reason = reason ?: CallHangupContent.Reason.USER_HANGUP,
+ reason = reason,
version = MxCall.VOIP_PROTO_VERSION.toString()
)
.let { createEventAndLocalEcho(type = EventType.CALL_HANGUP, roomId = roomId, content = it.toContent()) }
.also { eventSenderProcessor.postEvent(it) }
- state = CallState.Terminated
+ state = CallState.Ended(reason)
}
override fun accept(sdpString: String) {
- Timber.v("## VOIP accept $callId")
+ Timber.tag(loggerTag.value).v("accept $callId")
if (isOutgoing) return
state = CallState.Answering
CallAnswerContent(
@@ -185,7 +189,7 @@ internal class MxCallImpl(
}
override fun negotiate(sdpString: String, type: SdpType) {
- Timber.v("## VOIP negotiate $callId")
+ Timber.tag(loggerTag.value).v("negotiate $callId")
CallNegotiateContent(
callId = callId,
partyId = ourPartyId,
@@ -198,7 +202,7 @@ internal class MxCallImpl(
}
override fun selectAnswer() {
- Timber.v("## VOIP select answer $callId")
+ Timber.tag(loggerTag.value).v("select answer $callId")
if (isOutgoing) return
state = CallState.Answering
CallSelectAnswerContent(
@@ -219,7 +223,7 @@ internal class MxCallImpl(
val profileInfo = try {
getProfileInfoTask.execute(profileInfoParams)
} 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
}
CallReplacesContent(
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/DefaultContentUrlResolver.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/DefaultContentUrlResolver.kt
index f765056496..e4efdaa254 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/DefaultContentUrlResolver.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/DefaultContentUrlResolver.kt
@@ -26,7 +26,7 @@ private const val MATRIX_CONTENT_URI_SCHEME = "mxc://"
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"
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/UploadContentWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/UploadContentWorker.kt
index 237411db53..f14c85cf80 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/UploadContentWorker.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/UploadContentWorker.kt
@@ -234,7 +234,7 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
.also { filesToDelete.add(it) }
uploadedFileEncryptedFileInfo =
- MXEncryptedAttachments.encrypt(fileToUpload.inputStream(), attachment.getSafeMimeType(), encryptedFile) { read, total ->
+ MXEncryptedAttachments.encrypt(fileToUpload.inputStream(), encryptedFile) { read, total ->
notifyTracker(params) {
contentUploadStateTracker.setEncrypting(it, read.toLong(), total.toLong())
}
@@ -315,7 +315,7 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
if (params.isEncrypted) {
Timber.v("Encrypt thumbnail")
notifyTracker(params) { contentUploadStateTracker.setEncryptingThumbnail(it) }
- val encryptionResult = MXEncryptedAttachments.encryptAttachment(thumbnailData.bytes.inputStream(), thumbnailData.mimeType)
+ val encryptionResult = MXEncryptedAttachments.encryptAttachment(thumbnailData.bytes.inputStream())
val contentUploadResponse = fileUploader.uploadByteArray(
byteArray = encryptionResult.encryptedByteArray,
filename = null,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/DefaultHomeServerCapabilitiesService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/DefaultHomeServerCapabilitiesService.kt
index 0ed690d972..4c755b54b5 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/DefaultHomeServerCapabilitiesService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/DefaultHomeServerCapabilitiesService.kt
@@ -16,18 +16,12 @@
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.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
internal class DefaultHomeServerCapabilitiesService @Inject constructor(
- @SessionDatabase private val monarchy: Monarchy,
+ private val homeServerCapabilitiesDataSource: HomeServerCapabilitiesDataSource,
private val getHomeServerCapabilitiesTask: GetHomeServerCapabilitiesTask
) : HomeServerCapabilitiesService {
@@ -36,11 +30,7 @@ internal class DefaultHomeServerCapabilitiesService @Inject constructor(
}
override fun getHomeServerCapabilities(): HomeServerCapabilities {
- return Realm.getInstance(monarchy.realmConfiguration).use { realm ->
- HomeServerCapabilitiesEntity.get(realm)?.let {
- HomeServerCapabilitiesMapper.map(it)
- }
- }
+ return homeServerCapabilitiesDataSource.getHomeServerCapabilities()
?: HomeServerCapabilities()
}
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetCapabilitiesResult.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetCapabilitiesResult.kt
index ab029a0fce..c4bc09a233 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetCapabilitiesResult.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetCapabilitiesResult.kt
@@ -19,6 +19,7 @@ package org.matrix.android.sdk.internal.session.homeserver
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
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
@@ -38,9 +39,14 @@ internal data class Capabilities(
* Capability to indicate if the user can change their 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)
@@ -52,6 +58,21 @@ internal data class ChangePassword(
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
internal fun GetCapabilitiesResult.canChangePassword(): Boolean {
return capabilities?.changePassword?.enabled.orTrue()
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetHomeServerCapabilitiesTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetHomeServerCapabilitiesTask.kt
index 740370123f..612b98f863 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetHomeServerCapabilitiesTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetHomeServerCapabilitiesTask.kt
@@ -17,6 +17,7 @@
package org.matrix.android.sdk.internal.session.homeserver
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.wellknown.WellknownResult
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.database.model.HomeServerCapabilitiesEntity
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.UserId
import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
@@ -89,7 +91,10 @@ internal class DefaultGetHomeServerCapabilitiesTask @Inject constructor(
}.getOrNull()
val wellknownResult = runCatching {
- getWellknownTask.execute(GetWellknownTask.Params(userId, homeServerConnectionConfig))
+ getWellknownTask.execute(GetWellknownTask.Params(
+ domain = userId.getDomain(),
+ homeServerConnectionConfig = homeServerConnectionConfig
+ ))
}.getOrNull()
insertInDb(capabilities, mediaConfig, versions, wellknownResult)
@@ -104,6 +109,10 @@ internal class DefaultGetHomeServerCapabilitiesTask @Inject constructor(
if (getCapabilitiesResult != null) {
homeServerCapabilitiesEntity.canChangePassword = getCapabilitiesResult.canChangePassword()
+
+ homeServerCapabilitiesEntity.roomVersionsJson = getCapabilitiesResult.capabilities?.roomVersions?.let {
+ MoshiProvider.providesMoshi().adapter(RoomVersions::class.java).toJson(it)
+ }
}
if (getMediaConfigResult != null) {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/HomeServerCapabilitiesDataSource.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/HomeServerCapabilitiesDataSource.kt
new file mode 100644
index 0000000000..6c913fa41e
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/HomeServerCapabilitiesDataSource.kt
@@ -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)
+ }
+ }
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/DefaultIdentityService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/DefaultIdentityService.kt
index 4f88d8eb95..48870b86b7 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/DefaultIdentityService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/DefaultIdentityService.kt
@@ -191,7 +191,7 @@ internal class DefaultIdentityService @Inject constructor(
} else {
// 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,
- // 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) }
.onFailure { Timber.w(it, "Unable to disconnect identity server") }
@@ -241,7 +241,7 @@ internal class DefaultIdentityService @Inject constructor(
override suspend fun getShareStatus(threePids: List): Map {
// 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()) {
return emptyMap()
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityAuthAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityAuthAPI.kt
index 9d990d4d8f..f77eb296aa 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityAuthAPI.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityAuthAPI.kt
@@ -42,7 +42,7 @@ internal interface IdentityAuthAPI {
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")
suspend fun pingV1()
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityModule.kt
index 7a39a333a5..4d664b76be 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityModule.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityModule.kt
@@ -60,7 +60,6 @@ internal abstract class IdentityModule {
@SessionScope
fun providesIdentityRealmConfiguration(realmKeysUtils: RealmKeysUtils,
@SessionFilesDirectory directory: File,
- migration: RealmIdentityStoreMigration,
@UserMd5 userMd5: String): RealmConfiguration {
return RealmConfiguration.Builder()
.directory(directory)
@@ -69,7 +68,7 @@ internal abstract class IdentityModule {
realmKeysUtils.configureEncryption(this, SessionModule.getKeyAlias(userMd5))
}
.schemaVersion(RealmIdentityStoreMigration.IDENTITY_STORE_SCHEMA_VERSION)
- .migration(migration)
+ .migration(RealmIdentityStoreMigration)
.allowWritesOnUiThread(true)
.modules(IdentityRealmModule())
.build()
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/db/RealmIdentityStoreMigration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/db/RealmIdentityStoreMigration.kt
index 6081dbab12..21c0f8eb9e 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/db/RealmIdentityStoreMigration.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/db/RealmIdentityStoreMigration.kt
@@ -19,13 +19,10 @@ package org.matrix.android.sdk.internal.session.identity.db
import io.realm.DynamicRealm
import io.realm.RealmMigration
import timber.log.Timber
-import javax.inject.Inject
-internal class RealmIdentityStoreMigration @Inject constructor() : RealmMigration {
+internal object RealmIdentityStoreMigration : RealmMigration {
- companion object {
- const val IDENTITY_STORE_SCHEMA_VERSION = 1L
- }
+ const val IDENTITY_STORE_SCHEMA_VERSION = 1L
override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) {
Timber.v("Migrating Realm Identity from $oldVersion to $newVersion")
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/integrationmanager/IntegrationManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/integrationmanager/IntegrationManager.kt
index aa82cf9222..b654b8610d 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/integrationmanager/IntegrationManager.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/integrationmanager/IntegrationManager.kt
@@ -43,8 +43,8 @@ import javax.inject.Inject
/**
* The integration manager allows to
- * - Get the Integration Manager that a user has explicitly set for its account (via account data)
- * - Get the recommended/preferred Integration Manager list as defined by the HomeServer (via wellknown)
+ * - Get the integration manager that a user has explicitly set for its account (via account data)
+ * - Get the recommended/preferred integration manager list as defined by the homeserver (via wellknown)
* - Check if the user has disabled the integration manager feature
* - Allow / Disallow Integration manager (propagated to other riot clients)
*
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/ViaParameterFinder.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/ViaParameterFinder.kt
index 72fbfcced5..82565d8118 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/ViaParameterFinder.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/ViaParameterFinder.kt
@@ -16,6 +16,7 @@
package org.matrix.android.sdk.internal.session.permalinks
+import org.matrix.android.sdk.api.MatrixPatterns.getDomain
import org.matrix.android.sdk.api.session.room.members.roomMemberQueryParams
import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.internal.di.UserId
@@ -47,9 +48,9 @@ internal class ViaParameterFinder @Inject constructor(
}
fun computeViaParams(userId: String, roomId: String, max: Int): List {
- val userHomeserver = userId.substringAfter(":")
+ val userHomeserver = userId.getDomain()
return getUserIdsOfJoinedMembers(roomId)
- .map { it.substringAfter(":") }
+ .map { it.getDomain() }
.groupBy { it }
.mapValues { it.value.size }
.toMutableMap()
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/ProfileAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/ProfileAPI.kt
index 485a4973ca..4b56db9f13 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/ProfileAPI.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/ProfileAPI.kt
@@ -86,7 +86,7 @@ internal interface ProfileAPI {
suspend fun addMsisdn(@Body body: AddMsisdnBody): AddMsisdnResponse
/**
- * Validate Msisdn code (same model than for Identity server API)
+ * Validate Msisdn code (same model than for identity server API)
*/
@POST
suspend fun validateMsisdn(@Url url: String,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoom.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoom.kt
index 0d9c106d41..8afd690f64 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoom.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoom.kt
@@ -37,6 +37,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.typing.TypingService
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.space.Space
import org.matrix.android.sdk.api.util.Optional
@@ -67,9 +68,11 @@ internal class DefaultRoom(override val roomId: String,
private val roomMembersService: MembershipService,
private val roomPushRuleService: RoomPushRuleService,
private val roomAccountDataService: RoomAccountDataService,
+ private val roomVersionService: RoomVersionService,
private val sendStateTask: SendStateTask,
private val viaParameterFinder: ViaParameterFinder,
- private val searchTask: SearchTask) :
+ private val searchTask: SearchTask
+) :
Room,
TimelineService by timelineService,
SendService by sendService,
@@ -85,7 +88,8 @@ internal class DefaultRoom(override val roomId: String,
RelationService by relationService,
MembershipService by roomMembersService,
RoomPushRuleService by roomPushRuleService,
- RoomAccountDataService by roomAccountDataService {
+ RoomAccountDataService by roomAccountDataService,
+ RoomVersionService by roomVersionService {
override fun getRoomSummaryLive(): LiveData> {
return roomSummaryDataSource.getRoomSummaryLive(roomId)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt
index 4f12604039..18ece60629 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt
@@ -369,4 +369,15 @@ internal interface RoomAPI {
@Path("roomId") roomId: String,
@Path("type") type: String,
@Body content: JsonDict)
+
+ /**
+ * Upgrades the given room to a particular room version.
+ * Errors:
+ * 400, The request was invalid. One way this can happen is if the room version requested is not supported by the homeserver
+ * (M_UNSUPPORTED_ROOM_VERSION)
+ * 403: The user is not permitted to upgrade the room.(M_FORBIDDEN)
+ */
+ @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/upgrade")
+ suspend fun upgradeRoom(@Path("roomId") roomId: String,
+ @Body body: RoomUpgradeBody): RoomUpgradeResponse
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomFactory.kt
index 9ddb8f1177..d44eb32529 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomFactory.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomFactory.kt
@@ -37,6 +37,7 @@ import org.matrix.android.sdk.internal.session.room.tags.DefaultTagsService
import org.matrix.android.sdk.internal.session.room.timeline.DefaultTimelineService
import org.matrix.android.sdk.internal.session.room.typing.DefaultTypingService
import org.matrix.android.sdk.internal.session.room.uploads.DefaultUploadsService
+import org.matrix.android.sdk.internal.session.room.version.DefaultRoomVersionService
import org.matrix.android.sdk.internal.session.search.SearchTask
import javax.inject.Inject
@@ -61,6 +62,7 @@ internal class DefaultRoomFactory @Inject constructor(private val cryptoService:
private val relationServiceFactory: DefaultRelationService.Factory,
private val membershipServiceFactory: DefaultMembershipService.Factory,
private val roomPushRuleServiceFactory: DefaultRoomPushRuleService.Factory,
+ private val roomVersionServiceFactory: DefaultRoomVersionService.Factory,
private val roomAccountDataServiceFactory: DefaultRoomAccountDataService.Factory,
private val sendStateTask: SendStateTask,
private val viaParameterFinder: ViaParameterFinder,
@@ -87,6 +89,7 @@ internal class DefaultRoomFactory @Inject constructor(private val cryptoService:
roomMembersService = membershipServiceFactory.create(roomId),
roomPushRuleService = roomPushRuleServiceFactory.create(roomId),
roomAccountDataService = roomAccountDataServiceFactory.create(roomId),
+ roomVersionService = roomVersionServiceFactory.create(roomId),
sendStateTask = sendStateTask,
searchTask = searchTask,
viaParameterFinder = viaParameterFinder
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt
index d88c195056..c04c899e18 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt
@@ -92,6 +92,8 @@ import org.matrix.android.sdk.internal.session.room.typing.DefaultSendTypingTask
import org.matrix.android.sdk.internal.session.room.typing.SendTypingTask
import org.matrix.android.sdk.internal.session.room.uploads.DefaultGetUploadsTask
import org.matrix.android.sdk.internal.session.room.uploads.GetUploadsTask
+import org.matrix.android.sdk.internal.session.room.version.DefaultRoomVersionUpgradeTask
+import org.matrix.android.sdk.internal.session.room.version.RoomVersionUpgradeTask
import org.matrix.android.sdk.internal.session.space.DefaultSpaceService
import retrofit2.Retrofit
@@ -243,4 +245,7 @@ internal abstract class RoomModule {
@Binds
abstract fun bindGetEventTask(task: DefaultGetEventTask): GetEventTask
+
+ @Binds
+ abstract fun bindRoomVersionUpgradeTask(task: DefaultRoomVersionUpgradeTask): RoomVersionUpgradeTask
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomUpgradeBody.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomUpgradeBody.kt
new file mode 100644
index 0000000000..4629f6e409
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomUpgradeBody.kt
@@ -0,0 +1,26 @@
+/*
+ * 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.internal.session.room
+
+import com.squareup.moshi.Json
+import com.squareup.moshi.JsonClass
+
+@JsonClass(generateAdapter = true)
+internal data class RoomUpgradeBody(
+ @Json(name = "new_version")
+ val newVersion: String
+)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomUpgradeResponse.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomUpgradeResponse.kt
new file mode 100644
index 0000000000..1cca2c572b
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomUpgradeResponse.kt
@@ -0,0 +1,26 @@
+/*
+ * 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.internal.session.room
+
+import com.squareup.moshi.Json
+import com.squareup.moshi.JsonClass
+
+@JsonClass(generateAdapter = true)
+internal data class RoomUpgradeResponse(
+ @Json(name = "replacement_room")
+ val replacementRoomId: String
+)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/RoomAliasAvailabilityChecker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/RoomAliasAvailabilityChecker.kt
index 66164c5280..7c137a8102 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/RoomAliasAvailabilityChecker.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/RoomAliasAvailabilityChecker.kt
@@ -16,6 +16,7 @@
package org.matrix.android.sdk.internal.session.room.alias
+import org.matrix.android.sdk.api.MatrixPatterns.getDomain
import org.matrix.android.sdk.api.failure.Failure
import org.matrix.android.sdk.api.session.room.alias.RoomAliasError
import org.matrix.android.sdk.internal.di.UserId
@@ -64,6 +65,6 @@ internal class RoomAliasAvailabilityChecker @Inject constructor(
}
companion object {
- internal fun String.toFullLocalAlias(userId: String) = "#" + this + ":" + userId.substringAfter(":")
+ internal fun String.toFullLocalAlias(userId: String) = "#" + this + ":" + userId.getDomain()
}
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomBodyBuilder.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomBodyBuilder.kt
index 018b865388..2c04759b22 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomBodyBuilder.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomBodyBuilder.kt
@@ -59,7 +59,7 @@ internal class CreateRoomBodyBuilder @Inject constructor(
val invite3pids = params.invite3pids
.takeIf { it.isNotEmpty() }
?.let { invites ->
- // This can throw Exception if Identity server is not configured
+ // This can throw an exception if identity server is not configured
ensureIdentityTokenTask.execute(Unit)
val identityServerUrlWithoutProtocol = identityStore.getIdentityServerUrlWithoutProtocol()
@@ -81,13 +81,14 @@ internal class CreateRoomBodyBuilder @Inject constructor(
params.historyVisibility = params.historyVisibility ?: RoomHistoryVisibility.SHARED
params.guestAccess = params.guestAccess ?: GuestAccess.Forbidden
}
- val initialStates = listOfNotNull(
+ val initialStates = (listOfNotNull(
buildEncryptionWithAlgorithmEvent(params),
buildHistoryVisibilityEvent(params),
buildAvatarEvent(params),
buildGuestAccess(params),
buildJoinRulesRestricted(params)
)
+ + buildCustomInitialStates(params))
.takeIf { it.isNotEmpty() }
return CreateRoomBody(
@@ -95,7 +96,7 @@ internal class CreateRoomBodyBuilder @Inject constructor(
roomAliasName = params.roomAliasName,
name = params.name,
topic = params.topic,
- invitedUserIds = params.invitedUserIds.filter { it != userId },
+ invitedUserIds = params.invitedUserIds.filter { it != userId }.takeIf { it.isNotEmpty() },
invite3pids = invite3pids,
creationContent = params.creationContent.takeIf { it.isNotEmpty() },
initialStates = initialStates,
@@ -103,10 +104,19 @@ internal class CreateRoomBodyBuilder @Inject constructor(
isDirect = params.isDirect,
powerLevelContentOverride = params.powerLevelContentOverride,
roomVersion = params.roomVersion
-
)
}
+ private fun buildCustomInitialStates(params: CreateRoomParams): List {
+ return params.initialStates.map {
+ Event(
+ type = it.type,
+ stateKey = it.stateKey,
+ content = it.content
+ )
+ }
+ }
+
private suspend fun buildAvatarEvent(params: CreateRoomParams): Event? {
return params.avatarUri?.let { avatarUri ->
// First upload the image, ignoring any error
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt
index 449189e6b5..092fec4d72 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt
@@ -142,7 +142,7 @@ internal class DefaultSendService @AssistedInject constructor(
// The image has not yet been sent
val attachmentData = ContentAttachmentData(
size = messageContent.info!!.size,
- mimeType = messageContent.info.mimeType!!,
+ mimeType = messageContent.mimeType,
width = messageContent.info.width.toLong(),
height = messageContent.info.height.toLong(),
name = messageContent.body,
@@ -169,7 +169,7 @@ internal class DefaultSendService @AssistedInject constructor(
is MessageFileContent -> {
val attachmentData = ContentAttachmentData(
size = messageContent.info!!.size,
- mimeType = messageContent.info.mimeType!!,
+ mimeType = messageContent.mimeType,
name = messageContent.getFileName(),
queryUri = Uri.parse(messageContent.url),
type = ContentAttachmentData.Type.FILE
@@ -181,7 +181,7 @@ internal class DefaultSendService @AssistedInject constructor(
val attachmentData = ContentAttachmentData(
size = messageContent.audioInfo?.size ?: 0,
duration = messageContent.audioInfo?.duration?.toLong() ?: 0L,
- mimeType = messageContent.audioInfo?.mimeType,
+ mimeType = messageContent.mimeType,
name = messageContent.body,
queryUri = Uri.parse(messageContent.url),
type = ContentAttachmentData.Type.AUDIO
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessorCoroutine.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessorCoroutine.kt
index 80bfd02b0e..3be01762e7 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessorCoroutine.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessorCoroutine.kt
@@ -46,7 +46,7 @@ private const val MAX_RETRY_COUNT = 3
/**
* This class is responsible for sending events in order in each room. It uses the QueuedTask.queueIdentifier to execute tasks sequentially.
- * Each send is retried 3 times, if there is no network (e.g if cannot ping home server) it will wait and
+ * Each send is retried 3 times, if there is no network (e.g if cannot ping homeserver) it will wait and
* periodically test reachability before resume (does not count as a retry)
*
* If the app is killed before all event were sent, on next wakeup the scheduled events will be re posted
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessorThread.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessorThread.kt
index 9db7cc9039..f32890f3fb 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessorThread.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessorThread.kt
@@ -42,7 +42,7 @@ import kotlin.concurrent.schedule
/**
* A simple ever running thread unique for that session responsible of sending events in order.
- * Each send is retried 3 times, if there is no network (e.g if cannot ping home server) it will wait and
+ * Each send is retried 3 times, if there is no network (e.g if cannot ping homeserver) it will wait and
* periodically test reachability before resume (does not count as a retry)
*
* If the app is killed before all event were sent, on next wakeup the scheduled events will be re posted
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/HomeServerAvailabilityChecker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/HomeServerAvailabilityChecker.kt
index 2d53699917..1d7ce587f9 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/HomeServerAvailabilityChecker.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/HomeServerAvailabilityChecker.kt
@@ -26,8 +26,8 @@ import java.net.Socket
internal class HomeServerAvailabilityChecker(val sessionParams: SessionParams) {
fun check(): Boolean {
- val host = sessionParams.homeServerConnectionConfig.homeServerUri.host ?: return false
- val port = sessionParams.homeServerConnectionConfig.homeServerUri.port.takeIf { it != -1 } ?: 80
+ val host = sessionParams.homeServerConnectionConfig.homeServerUriBase.host ?: return false
+ val port = sessionParams.homeServerConnectionConfig.homeServerUriBase.port.takeIf { it != -1 } ?: 80
val timeout = 30_000
try {
Socket().use { socket ->
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt
index bff1af60ca..0b8c6df806 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt
@@ -247,10 +247,10 @@ internal class RoomSummaryDataSource @Inject constructor(@SessionDatabase privat
queryParams.roomCategoryFilter?.let {
when (it) {
- RoomCategoryFilter.ONLY_DM -> query.equalTo(RoomSummaryEntityFields.IS_DIRECT, true)
- RoomCategoryFilter.ONLY_ROOMS -> query.equalTo(RoomSummaryEntityFields.IS_DIRECT, false)
+ RoomCategoryFilter.ONLY_DM -> query.equalTo(RoomSummaryEntityFields.IS_DIRECT, true)
+ RoomCategoryFilter.ONLY_ROOMS -> query.equalTo(RoomSummaryEntityFields.IS_DIRECT, false)
RoomCategoryFilter.ONLY_WITH_NOTIFICATIONS -> query.greaterThan(RoomSummaryEntityFields.NOTIFICATION_COUNT, 0)
- RoomCategoryFilter.ALL -> {
+ RoomCategoryFilter.ALL -> {
// nop
}
}
@@ -274,15 +274,15 @@ internal class RoomSummaryDataSource @Inject constructor(@SessionDatabase privat
query.equalTo(RoomSummaryEntityFields.ROOM_TYPE, it)
}
when (queryParams.roomCategoryFilter) {
- RoomCategoryFilter.ONLY_DM -> query.equalTo(RoomSummaryEntityFields.IS_DIRECT, true)
- RoomCategoryFilter.ONLY_ROOMS -> query.equalTo(RoomSummaryEntityFields.IS_DIRECT, false)
+ RoomCategoryFilter.ONLY_DM -> query.equalTo(RoomSummaryEntityFields.IS_DIRECT, true)
+ RoomCategoryFilter.ONLY_ROOMS -> query.equalTo(RoomSummaryEntityFields.IS_DIRECT, false)
RoomCategoryFilter.ONLY_WITH_NOTIFICATIONS -> query.greaterThan(RoomSummaryEntityFields.NOTIFICATION_COUNT, 0)
- RoomCategoryFilter.ALL -> Unit // nop
+ RoomCategoryFilter.ALL -> Unit // nop
}
// Timber.w("VAL: activeSpaceId : ${queryParams.activeSpaceId}")
when (queryParams.activeSpaceFilter) {
- is ActiveSpaceFilter.ActiveSpace -> {
+ is ActiveSpaceFilter.ActiveSpace -> {
// It's annoying but for now realm java does not support querying in primitive list :/
// https://github.com/realm/realm-java/issues/5361
if (queryParams.activeSpaceFilter.currentSpaceId == null) {
@@ -300,8 +300,8 @@ internal class RoomSummaryDataSource @Inject constructor(@SessionDatabase privat
}
}
- if (queryParams.activeGroupId != null) {
- query.contains(RoomSummaryEntityFields.GROUP_IDS, queryParams.activeGroupId!!)
+ queryParams.activeGroupId?.let { activeGroupId ->
+ query.contains(RoomSummaryEntityFields.GROUP_IDS, activeGroupId)
}
return query
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt
index 77b69991cf..9d1c0e26a3 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt
@@ -187,7 +187,6 @@ internal class RoomSummaryUpdater @Inject constructor(
measureTimeMillis {
val lookupMap = realm.where(RoomSummaryEntity::class.java)
.process(RoomSummaryEntityFields.MEMBERSHIP_STR, Membership.activeMemberships())
- .equalTo(RoomSummaryEntityFields.IS_DIRECT, false)
// we order by roomID to be consistent when breaking parent/child cycles
.sort(RoomSummaryEntityFields.ROOM_ID)
.findAll().map {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/version/DefaultRoomVersionService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/version/DefaultRoomVersionService.kt
new file mode 100644
index 0000000000..dc12c3209b
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/version/DefaultRoomVersionService.kt
@@ -0,0 +1,84 @@
+/*
+ * 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.internal.session.room.version
+
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import org.matrix.android.sdk.api.query.QueryStringValue
+import org.matrix.android.sdk.api.session.events.model.EventType
+import org.matrix.android.sdk.api.session.events.model.toModel
+import org.matrix.android.sdk.api.session.homeserver.RoomVersionStatus
+import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent
+import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent
+import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper
+import org.matrix.android.sdk.api.session.room.version.RoomVersionService
+import org.matrix.android.sdk.internal.session.homeserver.HomeServerCapabilitiesDataSource
+import org.matrix.android.sdk.internal.session.room.state.StateEventDataSource
+
+internal class DefaultRoomVersionService @AssistedInject constructor(
+ @Assisted private val roomId: String,
+ private val homeServerCapabilitiesDataSource: HomeServerCapabilitiesDataSource,
+ private val stateEventDataSource: StateEventDataSource,
+ private val roomVersionUpgradeTask: RoomVersionUpgradeTask
+) : RoomVersionService {
+
+ @AssistedFactory
+ interface Factory {
+ fun create(roomId: String): DefaultRoomVersionService
+ }
+
+ override fun getRoomVersion(): String {
+ return stateEventDataSource.getStateEvent(roomId, EventType.STATE_ROOM_CREATE, QueryStringValue.IsEmpty)
+ ?.content
+ ?.toModel()
+ ?.roomVersion
+ // as per spec -> Defaults to "1" if the key does not exist.
+ ?: DEFAULT_ROOM_VERSION
+ }
+
+ override suspend fun upgradeToVersion(version: String): String {
+ return roomVersionUpgradeTask.execute(
+ RoomVersionUpgradeTask.Params(
+ roomId = roomId,
+ newVersion = version
+ )
+ )
+ }
+
+ override fun getRecommendedVersion(): String {
+ return homeServerCapabilitiesDataSource.getHomeServerCapabilities()?.roomVersions?.defaultRoomVersion ?: DEFAULT_ROOM_VERSION
+ }
+
+ override fun isUsingUnstableRoomVersion(): Boolean {
+ val versionCaps = homeServerCapabilitiesDataSource.getHomeServerCapabilities()?.roomVersions
+ val currentVersion = getRoomVersion()
+ return versionCaps?.supportedVersion?.firstOrNull { it.version == currentVersion }?.status == RoomVersionStatus.UNSTABLE
+ }
+
+ override fun userMayUpgradeRoom(userId: String): Boolean {
+ val powerLevelsHelper = stateEventDataSource.getStateEvent(roomId, EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.NoCondition)
+ ?.content?.toModel()
+ ?.let { PowerLevelsHelper(it) }
+
+ return powerLevelsHelper?.isUserAllowedToSend(userId, true, EventType.STATE_ROOM_TOMBSTONE) ?: false
+ }
+
+ companion object {
+ const val DEFAULT_ROOM_VERSION = "1"
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/version/RoomVersionUpgradeTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/version/RoomVersionUpgradeTask.kt
new file mode 100644
index 0000000000..457bb3e948
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/version/RoomVersionUpgradeTask.kt
@@ -0,0 +1,66 @@
+/*
+ * 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.internal.session.room.version
+
+import io.realm.RealmConfiguration
+import org.matrix.android.sdk.api.extensions.tryOrNull
+import org.matrix.android.sdk.api.session.room.model.Membership
+import org.matrix.android.sdk.internal.database.awaitNotEmptyResult
+import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity
+import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields
+import org.matrix.android.sdk.internal.di.SessionDatabase
+import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
+import org.matrix.android.sdk.internal.network.executeRequest
+import org.matrix.android.sdk.internal.session.room.RoomAPI
+import org.matrix.android.sdk.internal.session.room.RoomUpgradeBody
+import org.matrix.android.sdk.internal.task.Task
+import java.util.concurrent.TimeUnit
+import javax.inject.Inject
+
+internal interface RoomVersionUpgradeTask : Task {
+ data class Params(
+ val roomId: String,
+ val newVersion: String
+ )
+}
+
+internal class DefaultRoomVersionUpgradeTask @Inject constructor(
+ private val roomAPI: RoomAPI,
+ private val globalErrorReceiver: GlobalErrorReceiver,
+ @SessionDatabase
+ private val realmConfiguration: RealmConfiguration
+) : RoomVersionUpgradeTask {
+
+ override suspend fun execute(params: RoomVersionUpgradeTask.Params): String {
+ val replacementRoomId = executeRequest(globalErrorReceiver) {
+ roomAPI.upgradeRoom(
+ roomId = params.roomId,
+ body = RoomUpgradeBody(params.newVersion)
+ )
+ }.replacementRoomId
+
+ // Wait for room to come back from the sync (but it can maybe be in the DB if the sync response is received before)
+ tryOrNull {
+ awaitNotEmptyResult(realmConfiguration, TimeUnit.MINUTES.toMillis(1L)) { realm ->
+ realm.where(RoomSummaryEntity::class.java)
+ .equalTo(RoomSummaryEntityFields.ROOM_ID, replacementRoomId)
+ .equalTo(RoomSummaryEntityFields.MEMBERSHIP_STR, Membership.JOIN.name)
+ }
+ }
+ return replacementRoomId
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpace.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpace.kt
index 70c52bf4ae..233eef45f8 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpace.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpace.kt
@@ -86,6 +86,12 @@ internal class DefaultSpace(
)
}
+ override fun getChildInfo(roomId: String): SpaceChildContent? {
+ return room.getStateEvents(setOf(EventType.STATE_SPACE_CHILD), QueryStringValue.Equals(roomId))
+ .firstOrNull()
+ ?.content.toModel()
+ }
+
override suspend fun setChildrenOrder(roomId: String, order: String?) {
val existing = room.getStateEvents(setOf(EventType.STATE_SPACE_CHILD), QueryStringValue.Equals(roomId))
.firstOrNull()
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncTask.kt
index 83a2ffc446..c80fbe60c1 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncTask.kt
@@ -94,7 +94,7 @@ internal class DefaultSyncTask @Inject constructor(
userStore.createOrUpdate(userId)
initialSyncProgressService.startRoot(InitSyncStep.ImportingAccount, 100)
}
- // Maybe refresh the home server capabilities data we know
+ // Maybe refresh the homeserver capabilities data we know
getHomeServerCapabilitiesTask.execute(GetHomeServerCapabilitiesTask.Params(forceRefresh = false))
val readTimeOut = (params.timeout + TIMEOUT_MARGIN).coerceAtLeast(TimeOutInterceptor.DEFAULT_LONG_TIMEOUT)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/file/AtomicFileCreator.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/file/AtomicFileCreator.kt
new file mode 100644
index 0000000000..ca10c0ed0f
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/file/AtomicFileCreator.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2021 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.util.file
+
+import timber.log.Timber
+import java.io.File
+
+internal class AtomicFileCreator(private val file: File) {
+ val partFile = File(file.parentFile, "${file.name}.part")
+
+ init {
+ if (file.exists()) {
+ Timber.w("## AtomicFileCreator: target file ${file.path} exists, it should not happen.")
+ }
+ if (partFile.exists()) {
+ Timber.d("## AtomicFileCreator: discard aborted part file ${partFile.path}")
+ // No need to delete the file, we will overwrite it
+ }
+ }
+
+ fun cancel() {
+ partFile.delete()
+ }
+
+ fun commit() {
+ partFile.renameTo(file)
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/wellknown/GetWellknownTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/wellknown/GetWellknownTask.kt
index 7a9beac8c0..f11e87e1e7 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/wellknown/GetWellknownTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/wellknown/GetWellknownTask.kt
@@ -18,7 +18,6 @@ package org.matrix.android.sdk.internal.wellknown
import android.util.MalformedJsonException
import dagger.Lazy
-import org.matrix.android.sdk.api.MatrixPatterns
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
import org.matrix.android.sdk.api.auth.data.WellKnown
import org.matrix.android.sdk.api.auth.wellknown.WellknownResult
@@ -39,7 +38,11 @@ import javax.net.ssl.HttpsURLConnection
internal interface GetWellknownTask : Task {
data class Params(
- val matrixId: String,
+ /**
+ * domain, for instance "matrix.org"
+ * the URL will be https://{domain}/.well-known/matrix/client
+ */
+ val domain: String,
val homeServerConnectionConfig: HomeServerConnectionConfig?
)
}
@@ -54,14 +57,8 @@ internal class DefaultGetWellknownTask @Inject constructor(
) : GetWellknownTask {
override suspend fun execute(params: GetWellknownTask.Params): WellknownResult {
- if (!MatrixPatterns.isUserId(params.matrixId)) {
- return WellknownResult.InvalidMatrixId
- }
-
- val homeServerDomain = params.matrixId.substringAfter(":")
-
val client = buildClient(params.homeServerConnectionConfig)
- return findClientConfig(homeServerDomain, client)
+ return findClientConfig(params.domain, client)
}
private fun buildClient(homeServerConnectionConfig: HomeServerConnectionConfig?): OkHttpClient {
@@ -133,7 +130,7 @@ internal class DefaultGetWellknownTask @Inject constructor(
}
/**
- * Return true if home server is valid, and (if applicable) if identity server is pingable
+ * Return true if homeserver is valid, and (if applicable) if identity server is pingable
*/
private suspend fun validateHomeServer(homeServerBaseUrl: String, wellKnown: WellKnown, client: OkHttpClient): WellknownResult {
val capabilitiesAPI = retrofitFactory.create(client, homeServerBaseUrl)
@@ -189,7 +186,7 @@ internal class DefaultGetWellknownTask @Inject constructor(
}
/**
- * Try to get an identity server URL from a home server URL, using a .wellknown request
+ * Try to get an identity server URL from a homeserver URL, using a .wellknown request
*/
/*
fun getIdentityServer(homeServerUrl: String, callback: ApiCallback) {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/androidsdk/crypto/data/MXDeviceInfo.java b/matrix-sdk-android/src/main/java/org/matrix/androidsdk/crypto/data/MXDeviceInfo.java
index 393c633c6a..1014ceda0e 100755
--- a/matrix-sdk-android/src/main/java/org/matrix/androidsdk/crypto/data/MXDeviceInfo.java
+++ b/matrix-sdk-android/src/main/java/org/matrix/androidsdk/crypto/data/MXDeviceInfo.java
@@ -66,7 +66,7 @@ public class MXDeviceInfo implements Serializable {
public Map> signatures;
/*
- * Additional data from the home server.
+ * Additional data from the homeserver.
*/
public Map unsigned;
@@ -81,4 +81,4 @@ public class MXDeviceInfo implements Serializable {
public MXDeviceInfo() {
mVerified = DEVICE_VERIFICATION_UNKNOWN;
}
-}
\ No newline at end of file
+}
diff --git a/multipicker/build.gradle b/multipicker/build.gradle
index a993c452b0..04ce8a2aec 100644
--- a/multipicker/build.gradle
+++ b/multipicker/build.gradle
@@ -42,8 +42,8 @@ android {
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
- implementation 'androidx.appcompat:appcompat:1.3.0'
- implementation "androidx.fragment:fragment-ktx:1.3.5"
+ implementation 'androidx.appcompat:appcompat:1.3.1'
+ implementation "androidx.fragment:fragment-ktx:1.3.6"
implementation 'androidx.exifinterface:exifinterface:1.3.2'
// Log
diff --git a/tools/check/forbidden_strings_in_code.txt b/tools/check/forbidden_strings_in_code.txt
index e0645e00b3..ba9dec0877 100644
--- a/tools/check/forbidden_strings_in_code.txt
+++ b/tools/check/forbidden_strings_in_code.txt
@@ -162,7 +162,7 @@ Formatter\.formatShortFileSize===1
# android\.text\.TextUtils
### This is not a rule, but a warning: the number of "enum class" has changed. For Json classes, it is mandatory that they have `@JsonClass(generateAdapter = false)`. If the enum is not used as a Json class, change the value in file forbidden_strings_in_code.txt
-enum class===101
+enum class===102
### Do not import temporary legacy classes
import org.matrix.android.sdk.internal.legacy.riot===3
diff --git a/vector/build.gradle b/vector/build.gradle
index 6854c3312f..7ef5b93b8c 100644
--- a/vector/build.gradle
+++ b/vector/build.gradle
@@ -14,7 +14,7 @@ kapt {
// Note: 2 digits max for each value
ext.versionMajor = 1
ext.versionMinor = 1
-ext.versionPatch = 12
+ext.versionPatch = 15
static def getGitTimestamp() {
def cmd = 'git show -s --format=%ct'
@@ -246,6 +246,11 @@ android {
productFlavors {
gplay {
+ apply plugin: 'com.google.gms.google-services'
+ afterEvaluate {
+ tasks.matching { it.name.contains("GoogleServices") && !it.name.contains("Gplay") }*.enabled = false
+ }
+
dimension "store"
isDefault = true
versionName "${versionMajor}.${versionMinor}.${versionPatch}${getGplayVersionSuffix()}"
@@ -300,13 +305,13 @@ android {
dependencies {
def epoxy_version = '4.6.2'
- def fragment_version = '1.3.5'
+ def fragment_version = '1.3.6'
def arrow_version = "0.8.2"
def markwon_version = '4.1.2'
def big_image_viewer_version = '1.8.0'
def glide_version = '4.12.0'
def moshi_version = '1.12.0'
- def daggerVersion = '2.37'
+ def daggerVersion = '2.38'
def autofill_version = "1.1.0"
def work_version = '2.5.0'
def arch_version = '2.1.0'
@@ -315,9 +320,9 @@ dependencies {
def jjwt_version = '0.11.2'
// Tests
- def kluent_version = '1.67'
- def androidxTest_version = '1.3.0'
- def espresso_version = '3.3.0'
+ def kluent_version = '1.68'
+ def androidxTest_version = '1.4.0'
+ def espresso_version = '3.4.0'
implementation project(":matrix-sdk-android")
implementation project(":matrix-sdk-android-rx")
@@ -332,12 +337,12 @@ dependencies {
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version"
implementation "androidx.recyclerview:recyclerview:1.2.1"
- implementation 'androidx.appcompat:appcompat:1.3.0'
+ implementation 'androidx.appcompat:appcompat:1.3.1'
implementation "androidx.fragment:fragment-ktx:$fragment_version"
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation "androidx.sharetarget:sharetarget:1.1.0"
- implementation 'androidx.core:core-ktx:1.5.0'
- implementation "androidx.media:media:1.3.1"
+ implementation 'androidx.core:core-ktx:1.6.0'
+ implementation "androidx.media:media:1.4.0"
implementation "androidx.transition:transition:1.4.1"
implementation "org.threeten:threetenbp:1.4.0:no-tzdb"
@@ -355,7 +360,7 @@ dependencies {
implementation 'com.facebook.stetho:stetho:1.6.0'
// Phone number https://github.com/google/libphonenumber
- implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.26'
+ implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.28'
// rx
implementation 'io.reactivex.rxjava2:rxkotlin:2.4.0'
@@ -386,7 +391,7 @@ dependencies {
// UI
implementation 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1'
- implementation 'com.google.android.material:material:1.3.0'
+ implementation 'com.google.android.material:material:1.4.0'
implementation 'me.gujun.android:span:1.7'
implementation "io.noties.markwon:core:$markwon_version"
implementation "io.noties.markwon:html:$markwon_version"
@@ -493,7 +498,7 @@ dependencies {
androidTestImplementation "androidx.test:core:$androidxTest_version"
androidTestImplementation "androidx.test:runner:$androidxTest_version"
androidTestImplementation "androidx.test:rules:$androidxTest_version"
- androidTestImplementation 'androidx.test.ext:junit:1.1.2'
+ androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation "androidx.test.espresso:espresso-core:$espresso_version"
androidTestImplementation "androidx.test.espresso:espresso-contrib:$espresso_version"
androidTestImplementation "androidx.test.espresso:espresso-intents:$espresso_version"
@@ -506,7 +511,3 @@ dependencies {
exclude group: 'org.jetbrains.kotlin'
}
}
-
-if (getGradle().getStartParameter().getTaskRequests().toString().contains("Gplay")) {
- apply plugin: 'com.google.gms.google-services'
-}
diff --git a/vector/src/androidTest/java/im/vector/app/RegistrationTest.kt b/vector/src/androidTest/java/im/vector/app/RegistrationTest.kt
index 73ca94b148..fcec2a9fb5 100644
--- a/vector/src/androidTest/java/im/vector/app/RegistrationTest.kt
+++ b/vector/src/androidTest/java/im/vector/app/RegistrationTest.kt
@@ -57,7 +57,7 @@ class RegistrationTest {
onView(withId(R.id.loginSplashSubmit))
.perform(click())
- // Check that home server options are shown
+ // Check that homeserver options are shown
onView(withId(R.id.loginServerTitle))
.check(matches(isDisplayed()))
.check(matches(withText(R.string.login_server_title)))
diff --git a/vector/src/androidTest/java/im/vector/app/ui/UiAllScreensSanityTest.kt b/vector/src/androidTest/java/im/vector/app/ui/UiAllScreensSanityTest.kt
index 53e1645f09..26365e0c72 100644
--- a/vector/src/androidTest/java/im/vector/app/ui/UiAllScreensSanityTest.kt
+++ b/vector/src/androidTest/java/im/vector/app/ui/UiAllScreensSanityTest.kt
@@ -486,7 +486,7 @@ class UiAllScreensSanityTest {
clickOn(R.string.add_identity_server)
pressBack()
pressBack()
- // Home server
+ // Homeserver
clickOnPreference(R.string.settings_home_server)
pressBack()
// Identity server
diff --git a/vector/src/debug/AndroidManifest.xml b/vector/src/debug/AndroidManifest.xml
index b97384099f..8ffcec6bc1 100644
--- a/vector/src/debug/AndroidManifest.xml
+++ b/vector/src/debug/AndroidManifest.xml
@@ -4,6 +4,7 @@
+
diff --git a/vector/src/debug/java/im/vector/app/features/debug/DebugMenuActivity.kt b/vector/src/debug/java/im/vector/app/features/debug/DebugMenuActivity.kt
index 539091c7ce..4b5228d199 100644
--- a/vector/src/debug/java/im/vector/app/features/debug/DebugMenuActivity.kt
+++ b/vector/src/debug/java/im/vector/app/features/debug/DebugMenuActivity.kt
@@ -30,9 +30,8 @@ import im.vector.app.core.di.ScreenComponent
import im.vector.app.core.extensions.registerStartForActivityResult
import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.core.utils.PERMISSIONS_FOR_TAKING_PHOTO
-import im.vector.app.core.utils.PERMISSION_REQUEST_CODE_LAUNCH_CAMERA
-import im.vector.app.core.utils.allGranted
import im.vector.app.core.utils.checkPermissions
+import im.vector.app.core.utils.registerForPermissionsResult
import im.vector.app.core.utils.toast
import im.vector.app.databinding.ActivityDebugMenuBinding
import im.vector.app.features.debug.sas.DebugSasEmojiActivity
@@ -48,7 +47,6 @@ import im.vector.lib.ui.styles.debug.DebugVectorButtonStylesLightActivity
import im.vector.lib.ui.styles.debug.DebugVectorTextViewDarkActivity
import im.vector.lib.ui.styles.debug.DebugVectorTextViewLightActivity
import org.matrix.android.sdk.internal.crypto.verification.qrcode.toQrCodeData
-
import timber.log.Timber
import javax.inject.Inject
@@ -115,6 +113,9 @@ class DebugMenuActivity : VectorBaseActivity() {
}
views.debugTestCrash.setOnClickListener { testCrash() }
views.debugScanQrCode.setOnClickListener { scanQRCode() }
+ views.debugPermission.setOnClickListener {
+ startActivity(Intent(this, DebugPermissionActivity::class.java))
+ }
}
private fun renderQrCode(text: String) {
@@ -217,15 +218,13 @@ class DebugMenuActivity : VectorBaseActivity() {
}
private fun scanQRCode() {
- if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, this, PERMISSION_REQUEST_CODE_LAUNCH_CAMERA)) {
+ if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, this, permissionCameraLauncher)) {
doScanQRCode()
}
}
- override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) {
- super.onRequestPermissionsResult(requestCode, permissions, grantResults)
-
- if (requestCode == PERMISSION_REQUEST_CODE_LAUNCH_CAMERA && allGranted(grantResults)) {
+ private val permissionCameraLauncher = registerForPermissionsResult { allGranted, _ ->
+ if (allGranted) {
doScanQRCode()
}
}
diff --git a/vector/src/debug/java/im/vector/app/features/debug/DebugPermissionActivity.kt b/vector/src/debug/java/im/vector/app/features/debug/DebugPermissionActivity.kt
new file mode 100644
index 0000000000..048c64bc3a
--- /dev/null
+++ b/vector/src/debug/java/im/vector/app/features/debug/DebugPermissionActivity.kt
@@ -0,0 +1,135 @@
+/*
+ * Copyright 2021 New Vector Ltd
+ *
+ * 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 im.vector.app.features.debug
+
+import android.Manifest
+import android.content.pm.PackageManager
+import android.os.Build
+import android.widget.Toast
+import androidx.core.app.ActivityCompat
+import androidx.core.content.ContextCompat
+import im.vector.app.R
+import im.vector.app.core.platform.VectorBaseActivity
+import im.vector.app.core.utils.checkPermissions
+import im.vector.app.core.utils.onPermissionDeniedDialog
+import im.vector.app.core.utils.onPermissionDeniedSnackbar
+import im.vector.app.core.utils.registerForPermissionsResult
+import im.vector.app.databinding.ActivityDebugPermissionBinding
+import timber.log.Timber
+
+class DebugPermissionActivity : VectorBaseActivity() {
+
+ override fun getBinding() = ActivityDebugPermissionBinding.inflate(layoutInflater)
+
+ override fun getCoordinatorLayout() = views.coordinatorLayout
+
+ // For debug
+ private val allPermissions = listOf(
+ Manifest.permission.CAMERA,
+ Manifest.permission.RECORD_AUDIO,
+ Manifest.permission.WRITE_EXTERNAL_STORAGE,
+ Manifest.permission.READ_EXTERNAL_STORAGE,
+ Manifest.permission.READ_CONTACTS)
+
+ private var lastPermissions = emptyList()
+
+ override fun initUiAndData() {
+ views.status.setOnClickListener { refresh() }
+
+ views.camera.setOnClickListener {
+ lastPermissions = listOf(Manifest.permission.CAMERA)
+ checkPerm()
+ }
+ views.audio.setOnClickListener {
+ lastPermissions = listOf(Manifest.permission.RECORD_AUDIO)
+ checkPerm()
+ }
+ views.cameraAudio.setOnClickListener {
+ lastPermissions = listOf(Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO)
+ checkPerm()
+ }
+ views.write.setOnClickListener {
+ lastPermissions = listOf(Manifest.permission.WRITE_EXTERNAL_STORAGE)
+ checkPerm()
+ }
+ views.read.setOnClickListener {
+ lastPermissions = listOf(Manifest.permission.READ_EXTERNAL_STORAGE)
+ checkPerm()
+ }
+ views.contact.setOnClickListener {
+ lastPermissions = listOf(Manifest.permission.READ_CONTACTS)
+ checkPerm()
+ }
+ }
+
+ private fun checkPerm() {
+ if (checkPermissions(lastPermissions, this, launcher, R.string.debug_rationale)) {
+ Toast.makeText(this, "Already granted, sync call", Toast.LENGTH_SHORT).show()
+ }
+ }
+
+ private var dialogOrSnackbar = false
+
+ private val launcher = registerForPermissionsResult { allGranted, deniedPermanently ->
+ if (allGranted) {
+ Toast.makeText(this, "All granted", Toast.LENGTH_SHORT).show()
+ } else {
+ if (deniedPermanently) {
+ dialogOrSnackbar = !dialogOrSnackbar
+ if (dialogOrSnackbar) {
+ onPermissionDeniedDialog(R.string.denied_permission_generic)
+ } else {
+ onPermissionDeniedSnackbar(R.string.denied_permission_generic)
+ }
+ } else {
+ Toast.makeText(this, "Denied", Toast.LENGTH_SHORT).show()
+ }
+ }
+ }
+
+ override fun onResume() {
+ super.onResume()
+ refresh()
+ }
+
+ private fun refresh() {
+ views.status.text = getStatus()
+ }
+
+ private fun getStatus(): String {
+ return buildString {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ Timber.v("## debugPermission() : log the permissions status used by the app")
+ allPermissions.forEach { permission ->
+ append("[$permission] : ")
+ if (ContextCompat.checkSelfPermission(this@DebugPermissionActivity, permission) == PackageManager.PERMISSION_GRANTED) {
+ append("PERMISSION_GRANTED")
+ } else {
+ append("PERMISSION_DENIED")
+ }
+ append(" show rational: ")
+ append(ActivityCompat.shouldShowRequestPermissionRationale(this@DebugPermissionActivity, permission))
+ append("\n")
+ }
+ } else {
+ append("Before M!")
+ }
+ append("\n")
+ append("(Click to refresh)")
+ }
+ }
+}
diff --git a/vector/src/debug/res/layout/activity_debug_menu.xml b/vector/src/debug/res/layout/activity_debug_menu.xml
index a83f61266a..fadffecf83 100644
--- a/vector/src/debug/res/layout/activity_debug_menu.xml
+++ b/vector/src/debug/res/layout/activity_debug_menu.xml
@@ -152,6 +152,12 @@
android:layout_height="200dp"
tools:src="@drawable/ic_qr_code_add" />
+
+
diff --git a/vector/src/debug/res/layout/activity_debug_permission.xml b/vector/src/debug/res/layout/activity_debug_permission.xml
new file mode 100644
index 0000000000..6340d8faa7
--- /dev/null
+++ b/vector/src/debug/res/layout/activity_debug_permission.xml
@@ -0,0 +1,75 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/vector/src/debug/res/values/strings.xml b/vector/src/debug/res/values/strings.xml
new file mode 100644
index 0000000000..a7b8e38634
--- /dev/null
+++ b/vector/src/debug/res/values/strings.xml
@@ -0,0 +1,4 @@
+
+
+ Rationale!
+
\ No newline at end of file
diff --git a/vector/src/main/java/im/vector/app/VectorApplication.kt b/vector/src/main/java/im/vector/app/VectorApplication.kt
index 4791c2e499..be8447d409 100644
--- a/vector/src/main/java/im/vector/app/VectorApplication.kt
+++ b/vector/src/main/java/im/vector/app/VectorApplication.kt
@@ -162,7 +162,6 @@ class VectorApplication :
// Do not display the name change popup
doNotShowDisclaimerDialog(this)
}
-
if (authenticationService.hasAuthenticatedSessions() && !activeSessionHolder.hasActiveSession()) {
val lastAuthenticatedSession = authenticationService.getLastAuthenticatedSession()!!
activeSessionHolder.setActiveSession(lastAuthenticatedSession)
diff --git a/vector/src/main/java/im/vector/app/core/di/ScreenComponent.kt b/vector/src/main/java/im/vector/app/core/di/ScreenComponent.kt
index 38edb771bb..2a2c8908b0 100644
--- a/vector/src/main/java/im/vector/app/core/di/ScreenComponent.kt
+++ b/vector/src/main/java/im/vector/app/core/di/ScreenComponent.kt
@@ -40,12 +40,14 @@ import im.vector.app.features.debug.DebugMenuActivity
import im.vector.app.features.devtools.RoomDevToolActivity
import im.vector.app.features.home.HomeActivity
import im.vector.app.features.home.HomeModule
+import im.vector.app.features.home.room.detail.JoinReplacementRoomBottomSheet
import im.vector.app.features.home.room.detail.RoomDetailActivity
import im.vector.app.features.home.room.detail.readreceipts.DisplayReadReceiptsBottomSheet
import im.vector.app.features.home.room.detail.search.SearchActivity
import im.vector.app.features.home.room.detail.timeline.action.MessageActionsBottomSheet
import im.vector.app.features.home.room.detail.timeline.edithistory.ViewEditHistoryBottomSheet
import im.vector.app.features.home.room.detail.timeline.reactions.ViewReactionsBottomSheet
+import im.vector.app.features.home.room.detail.upgrade.MigrateRoomBottomSheet
import im.vector.app.features.home.room.detail.widget.RoomWidgetsBottomSheet
import im.vector.app.features.home.room.filtered.FilteredRoomsActivity
import im.vector.app.features.home.room.list.RoomListModule
@@ -193,6 +195,8 @@ interface ScreenComponent {
fun inject(bottomSheet: SpaceSettingsMenuBottomSheet)
fun inject(bottomSheet: InviteRoomSpaceChooserBottomSheet)
fun inject(bottomSheet: SpaceInviteBottomSheet)
+ fun inject(bottomSheet: JoinReplacementRoomBottomSheet)
+ fun inject(bottomSheet: MigrateRoomBottomSheet)
/* ==========================================================================================
* Others
diff --git a/vector/src/main/java/im/vector/app/core/dialogs/GalleryOrCameraDialogHelper.kt b/vector/src/main/java/im/vector/app/core/dialogs/GalleryOrCameraDialogHelper.kt
index 3b25fd3f89..23c2e13f6f 100644
--- a/vector/src/main/java/im/vector/app/core/dialogs/GalleryOrCameraDialogHelper.kt
+++ b/vector/src/main/java/im/vector/app/core/dialogs/GalleryOrCameraDialogHelper.kt
@@ -29,6 +29,7 @@ import im.vector.app.core.extensions.registerStartForActivityResult
import im.vector.app.core.resources.ColorProvider
import im.vector.app.core.utils.PERMISSIONS_FOR_TAKING_PHOTO
import im.vector.app.core.utils.checkPermissions
+import im.vector.app.core.utils.onPermissionDeniedDialog
import im.vector.app.core.utils.registerForPermissionsResult
import im.vector.app.features.media.createUCropWithDefaultSettings
import im.vector.lib.multipicker.MultiPicker
@@ -55,9 +56,11 @@ class GalleryOrCameraDialogHelper(
private val listener = fragment as? Listener ?: error("Fragment must implement GalleryOrCameraDialogHelper.Listener")
- private val takePhotoPermissionActivityResultLauncher = fragment.registerForPermissionsResult { allGranted ->
+ private val takePhotoPermissionActivityResultLauncher = fragment.registerForPermissionsResult { allGranted, deniedPermanently ->
if (allGranted) {
doOpenCamera()
+ } else if (deniedPermanently) {
+ activity.onPermissionDeniedDialog(R.string.denied_permission_camera)
}
}
@@ -116,7 +119,7 @@ class GalleryOrCameraDialogHelper(
private fun onAvatarTypeSelected(type: Type) {
when (type) {
- Type.Camera ->
+ Type.Camera ->
if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, activity, takePhotoPermissionActivityResultLauncher)) {
doOpenCamera()
}
diff --git a/vector/src/main/java/im/vector/app/core/dialogs/UnrecognizedCertificateDialog.kt b/vector/src/main/java/im/vector/app/core/dialogs/UnrecognizedCertificateDialog.kt
index ca92e6aa75..39458a054a 100644
--- a/vector/src/main/java/im/vector/app/core/dialogs/UnrecognizedCertificateDialog.kt
+++ b/vector/src/main/java/im/vector/app/core/dialogs/UnrecognizedCertificateDialog.kt
@@ -50,7 +50,15 @@ class UnrecognizedCertificateDialog @Inject constructor(
val userId = activeSessionHolder.getSafeActiveSession()?.myUserId
val hsConfig = activeSessionHolder.getSafeActiveSession()?.sessionParams?.homeServerConnectionConfig ?: return
- internalShow(activity, unrecognizedFingerprint, true, callback, userId, hsConfig.homeServerUri.toString(), hsConfig.allowedFingerprints.isNotEmpty())
+ internalShow(
+ activity = activity,
+ unrecognizedFingerprint = unrecognizedFingerprint,
+ existing = true,
+ callback = callback,
+ userId = userId,
+ homeServerUrl = hsConfig.homeServerUriBase.toString(),
+ homeServerConnectionConfigHasFingerprints = hsConfig.allowedFingerprints.isNotEmpty()
+ )
}
/**
@@ -60,7 +68,15 @@ class UnrecognizedCertificateDialog @Inject constructor(
unrecognizedFingerprint: Fingerprint,
homeServerUrl: String,
callback: Callback) {
- internalShow(activity, unrecognizedFingerprint, false, callback, null, homeServerUrl, false)
+ internalShow(
+ activity = activity,
+ unrecognizedFingerprint = unrecognizedFingerprint,
+ existing = false,
+ callback = callback,
+ userId = null,
+ homeServerUrl = homeServerUrl,
+ homeServerConnectionConfigHasFingerprints = false
+ )
}
/**
diff --git a/vector/src/main/java/im/vector/app/core/error/ErrorFormatter.kt b/vector/src/main/java/im/vector/app/core/error/ErrorFormatter.kt
index c0219b26e2..e7602e5cfe 100644
--- a/vector/src/main/java/im/vector/app/core/error/ErrorFormatter.kt
+++ b/vector/src/main/java/im/vector/app/core/error/ErrorFormatter.kt
@@ -21,6 +21,7 @@ import im.vector.app.core.resources.StringProvider
import im.vector.app.features.call.dialpad.DialPadLookup
import org.matrix.android.sdk.api.failure.Failure
import org.matrix.android.sdk.api.failure.MatrixError
+import org.matrix.android.sdk.api.failure.MatrixIdFailure
import org.matrix.android.sdk.api.failure.isInvalidPassword
import org.matrix.android.sdk.api.session.identity.IdentityServiceError
import java.net.HttpURLConnection
@@ -39,9 +40,9 @@ class DefaultErrorFormatter @Inject constructor(
override fun toHumanReadable(throwable: Throwable?): String {
return when (throwable) {
- null -> null
- is IdentityServiceError -> identityServerError(throwable)
- is Failure.NetworkConnection -> {
+ null -> null
+ is IdentityServiceError -> identityServerError(throwable)
+ is Failure.NetworkConnection -> {
when (throwable.ioException) {
is SocketTimeoutException ->
stringProvider.getString(R.string.error_network_timeout)
@@ -54,7 +55,7 @@ class DefaultErrorFormatter @Inject constructor(
stringProvider.getString(R.string.error_no_network)
}
}
- is Failure.ServerError -> {
+ is Failure.ServerError -> {
when {
throwable.error.code == MatrixError.M_CONSENT_NOT_GIVEN -> {
// Special case for terms and conditions
@@ -104,23 +105,25 @@ class DefaultErrorFormatter @Inject constructor(
}
}
}
- is Failure.OtherServerError -> {
+ is Failure.OtherServerError -> {
when (throwable.httpCode) {
- HttpURLConnection.HTTP_NOT_FOUND ->
+ HttpURLConnection.HTTP_NOT_FOUND ->
// homeserver not found
stringProvider.getString(R.string.login_error_no_homeserver_found)
HttpURLConnection.HTTP_UNAUTHORIZED ->
// uia errors?
stringProvider.getString(R.string.error_unauthorized)
- else ->
+ else ->
throwable.localizedMessage
}
}
- is DialPadLookup.Failure.NumberIsYours ->
+ is DialPadLookup.Failure.NumberIsYours ->
stringProvider.getString(R.string.cannot_call_yourself)
- is DialPadLookup.Failure.NoResult ->
+ is DialPadLookup.Failure.NoResult ->
stringProvider.getString(R.string.call_dial_pad_lookup_error)
- else -> throwable.localizedMessage
+ is MatrixIdFailure.InvalidMatrixId ->
+ stringProvider.getString(R.string.login_signin_matrix_id_error_invalid_matrix_id)
+ else -> throwable.localizedMessage
}
?: stringProvider.getString(R.string.unknown_error)
}
diff --git a/vector/src/main/java/im/vector/app/core/extensions/ViewExtensions.kt b/vector/src/main/java/im/vector/app/core/extensions/ViewExtensions.kt
index 759e130e29..92dc76670f 100644
--- a/vector/src/main/java/im/vector/app/core/extensions/ViewExtensions.kt
+++ b/vector/src/main/java/im/vector/app/core/extensions/ViewExtensions.kt
@@ -41,7 +41,7 @@ fun SearchView.withoutLeftMargin() {
}
fun EditText.hidePassword() {
- inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD
+ inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD
}
fun View.getMeasurements(): Pair {
diff --git a/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt b/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt
index 899a99c314..61abbd445b 100644
--- a/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt
+++ b/vector/src/main/java/im/vector/app/core/platform/VectorBaseActivity.kt
@@ -32,7 +32,6 @@ import androidx.annotation.MainThread
import androidx.annotation.MenuRes
import androidx.annotation.StringRes
import androidx.appcompat.app.AppCompatActivity
-import com.google.android.material.appbar.MaterialToolbar
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
@@ -42,6 +41,7 @@ import androidx.fragment.app.FragmentManager
import androidx.lifecycle.ViewModelProvider
import androidx.viewbinding.ViewBinding
import com.bumptech.glide.util.Util
+import com.google.android.material.appbar.MaterialToolbar
import com.google.android.material.snackbar.Snackbar
import com.jakewharton.rxbinding3.view.clicks
import im.vector.app.BuildConfig
@@ -82,14 +82,13 @@ import im.vector.app.receivers.DebugReceiver
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.disposables.Disposable
-
import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.failure.GlobalError
import timber.log.Timber
import java.util.concurrent.TimeUnit
import kotlin.system.measureTimeMillis
-abstract class VectorBaseActivity : AppCompatActivity(), HasScreenInjector {
+abstract class VectorBaseActivity : AppCompatActivity(), HasScreenInjector {
/* ==========================================================================================
* View
* ========================================================================================== */
@@ -596,12 +595,19 @@ abstract class VectorBaseActivity : AppCompatActivity(), HasScr
}
fun showSnackbar(message: String, @StringRes withActionTitle: Int?, action: (() -> Unit)?) {
- getCoordinatorLayout()?.let {
- Snackbar.make(it, message, Snackbar.LENGTH_LONG).apply {
+ val coordinatorLayout = getCoordinatorLayout()
+ if (coordinatorLayout != null) {
+ Snackbar.make(coordinatorLayout, message, Snackbar.LENGTH_LONG).apply {
withActionTitle?.let {
- setAction(withActionTitle, { action?.invoke() })
+ setAction(withActionTitle) { action?.invoke() }
}
}.show()
+ } else {
+ if (vectorPreferences.failFast()) {
+ error("No CoordinatorLayout to display this snackbar!")
+ } else {
+ Timber.w("No CoordinatorLayout to display this snackbar!")
+ }
}
}
diff --git a/vector/src/main/java/im/vector/app/core/platform/VectorBaseBottomSheetDialogFragment.kt b/vector/src/main/java/im/vector/app/core/platform/VectorBaseBottomSheetDialogFragment.kt
index d6d4d07500..b9b5bc8ca5 100644
--- a/vector/src/main/java/im/vector/app/core/platform/VectorBaseBottomSheetDialogFragment.kt
+++ b/vector/src/main/java/im/vector/app/core/platform/VectorBaseBottomSheetDialogFragment.kt
@@ -46,7 +46,7 @@ import java.util.concurrent.TimeUnit
/**
* Add MvRx capabilities to bottomsheetdialog (like BaseMvRxFragment)
*/
-abstract class VectorBaseBottomSheetDialogFragment : BottomSheetDialogFragment(), MvRxView {
+abstract class VectorBaseBottomSheetDialogFragment : BottomSheetDialogFragment(), MvRxView {
private val mvrxViewIdProperty = MvRxViewId()
final override val mvrxViewId: String by mvrxViewIdProperty
@@ -168,6 +168,10 @@ abstract class VectorBaseBottomSheetDialogFragment : BottomShee
@CallSuper
override fun invalidate() {
+ forceExpandState()
+ }
+
+ protected fun forceExpandState() {
if (showExpanded) {
// Force the bottom sheet to be expanded
bottomSheetBehavior?.state = BottomSheetBehavior.STATE_EXPANDED
diff --git a/vector/src/main/java/im/vector/app/core/services/CallService.kt b/vector/src/main/java/im/vector/app/core/services/CallService.kt
index 59eee14d37..d8cf8cf6b8 100644
--- a/vector/src/main/java/im/vector/app/core/services/CallService.kt
+++ b/vector/src/main/java/im/vector/app/core/services/CallService.kt
@@ -37,16 +37,21 @@ import im.vector.app.features.home.AvatarRenderer
import im.vector.app.features.notifications.NotificationUtils
import im.vector.app.features.popup.IncomingCallAlert
import im.vector.app.features.popup.PopupAlertManager
+import org.matrix.android.sdk.api.logger.LoggerTag
+import org.matrix.android.sdk.api.session.room.model.call.EndCallReason
import org.matrix.android.sdk.api.util.MatrixItem
import timber.log.Timber
+private val loggerTag = LoggerTag("CallService", LoggerTag.VOIP)
+
/**
* Foreground service to manage calls
*/
class CallService : VectorService() {
private val connections = mutableMapOf()
- private val knownCalls = mutableSetOf()
+ private val knownCalls = mutableSetOf()
+ private val connectedCallIds = mutableSetOf()
private lateinit var notificationManager: NotificationManagerCompat
private lateinit var notificationUtils: NotificationUtils
@@ -91,7 +96,7 @@ class CallService : VectorService() {
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
- Timber.v("## VOIP onStartCommand $intent")
+ Timber.tag(loggerTag.value).v("onStartCommand $intent")
if (mediaSession == null) {
mediaSession = MediaSessionCompat(applicationContext, CallService::class.java.name).apply {
setCallback(mediaSessionButtonCallback)
@@ -115,19 +120,19 @@ class CallService : VectorService() {
callRingPlayerOutgoing?.start()
displayOutgoingRingingCallNotification(intent)
}
- ACTION_ONGOING_CALL -> {
+ ACTION_ONGOING_CALL -> {
callRingPlayerIncoming?.stop()
callRingPlayerOutgoing?.stop()
displayCallInProgressNotification(intent)
}
- ACTION_CALL_CONNECTING -> {
+ ACTION_CALL_CONNECTING -> {
// lower notification priority
displayCallInProgressNotification(intent)
// stop ringing
callRingPlayerIncoming?.stop()
callRingPlayerOutgoing?.stop()
}
- ACTION_CALL_TERMINATED -> {
+ ACTION_CALL_TERMINATED -> {
handleCallTerminated(intent)
}
else -> {
@@ -148,15 +153,15 @@ class CallService : VectorService() {
*
*/
private fun displayIncomingCallNotification(intent: Intent) {
- Timber.v("## VOIP displayIncomingCallNotification $intent")
+ Timber.tag(loggerTag.value).v("displayIncomingCallNotification $intent")
val callId = intent.getStringExtra(EXTRA_CALL_ID) ?: ""
val call = callManager.getCallById(callId) ?: return Unit.also {
handleUnexpectedState(callId)
}
+ val callInformation = call.toCallInformation()
val isVideoCall = call.mxCall.isVideoCall
val fromBg = intent.getBooleanExtra(EXTRA_IS_IN_BG, false)
- val opponentMatrixItem = getOpponentMatrixItem(call)
- Timber.v("displayIncomingCallNotification : display the dedicated notification")
+ Timber.tag(loggerTag.value).v("displayIncomingCallNotification : display the dedicated notification")
val incomingCallAlert = IncomingCallAlert(callId,
shouldBeDisplayedIn = { activity ->
if (activity is VectorCallActivity) {
@@ -165,7 +170,7 @@ class CallService : VectorService() {
}
).apply {
viewBinder = IncomingCallAlert.ViewBinder(
- matrixItem = opponentMatrixItem,
+ matrixItem = callInformation.opponentMatrixItem,
avatarRenderer = avatarRenderer,
isVideoCall = isVideoCall,
onAccept = { showCallScreen(call, VectorCallActivity.INCOMING_ACCEPT) },
@@ -177,7 +182,7 @@ class CallService : VectorService() {
alertManager.postVectorAlert(incomingCallAlert)
val notification = notificationUtils.buildIncomingCallNotification(
call = call,
- title = opponentMatrixItem?.getBestName() ?: call.mxCall.opponentUserId,
+ title = callInformation.opponentMatrixItem?.getBestName() ?: callInformation.opponentUserId,
fromBg = fromBg
)
if (knownCalls.isEmpty()) {
@@ -185,23 +190,32 @@ class CallService : VectorService() {
} else {
notificationManager.notify(callId.hashCode(), notification)
}
- knownCalls.add(callId)
+ knownCalls.add(callInformation)
}
private fun handleCallTerminated(intent: Intent) {
val callId = intent.getStringExtra(EXTRA_CALL_ID) ?: ""
+ val endCallReason = intent.getSerializableExtra(EXTRA_END_CALL_REASON) as EndCallReason
+ val rejected = intent.getBooleanExtra(EXTRA_END_CALL_REJECTED, false)
alertManager.cancelAlert(callId)
- if (!knownCalls.remove(callId)) {
- Timber.v("Call terminated for unknown call $callId$")
+ val terminatedCall = knownCalls.firstOrNull { it.callId == callId }
+ if (terminatedCall == null) {
+ Timber.tag(loggerTag.value).v("Call terminated for unknown call $callId$")
handleUnexpectedState(callId)
return
}
- val notification = notificationUtils.buildCallEndedNotification()
- notificationManager.notify(callId.hashCode(), notification)
+ knownCalls.remove(terminatedCall)
if (knownCalls.isEmpty()) {
mediaSession?.isActive = false
myStopSelf()
}
+ val wasConnected = connectedCallIds.remove(callId)
+ val notification = notificationUtils.buildCallEndedNotification(terminatedCall.isVideoCall)
+ notificationManager.notify(callId.hashCode(), notification)
+ if (!wasConnected && !terminatedCall.isOutgoing && !rejected && endCallReason != EndCallReason.ANSWERED_ELSEWHERE) {
+ val missedCallNotification = notificationUtils.buildCallMissedNotification(terminatedCall)
+ notificationManager.notify(MISSED_CALL_TAG, terminatedCall.nativeRoomId.hashCode(), missedCallNotification)
+ }
}
private fun showCallScreen(call: WebRtcCall, mode: String) {
@@ -218,51 +232,52 @@ class CallService : VectorService() {
val call = callManager.getCallById(callId) ?: return Unit.also {
handleUnexpectedState(callId)
}
- val opponentMatrixItem = getOpponentMatrixItem(call)
- Timber.v("displayOutgoingCallNotification : display the dedicated notification")
+ val callInformation = call.toCallInformation()
+ Timber.tag(loggerTag.value).v("displayOutgoingCallNotification : display the dedicated notification")
val notification = notificationUtils.buildOutgoingRingingCallNotification(
call = call,
- title = opponentMatrixItem?.getBestName() ?: call.mxCall.opponentUserId
+ title = callInformation.opponentMatrixItem?.getBestName() ?: callInformation.opponentUserId
)
if (knownCalls.isEmpty()) {
startForeground(callId.hashCode(), notification)
} else {
notificationManager.notify(callId.hashCode(), notification)
}
- knownCalls.add(callId)
+ knownCalls.add(callInformation)
}
/**
* Display a call in progress notification.
*/
private fun displayCallInProgressNotification(intent: Intent) {
- Timber.v("## VOIP displayCallInProgressNotification")
+ Timber.tag(loggerTag.value).v("displayCallInProgressNotification")
val callId = intent.getStringExtra(EXTRA_CALL_ID) ?: ""
+ connectedCallIds.add(callId)
val call = callManager.getCallById(callId) ?: return Unit.also {
handleUnexpectedState(callId)
}
- val opponentMatrixItem = getOpponentMatrixItem(call)
alertManager.cancelAlert(callId)
+ val callInformation = call.toCallInformation()
val notification = notificationUtils.buildPendingCallNotification(
call = call,
- title = opponentMatrixItem?.getBestName() ?: call.mxCall.opponentUserId
+ title = callInformation.opponentMatrixItem?.getBestName() ?: callInformation.opponentUserId
)
if (knownCalls.isEmpty()) {
startForeground(callId.hashCode(), notification)
} else {
notificationManager.notify(callId.hashCode(), notification)
}
- knownCalls.add(callId)
+ knownCalls.add(callInformation)
}
private fun handleUnexpectedState(callId: String?) {
- Timber.v("Fallback to clear everything")
+ Timber.tag(loggerTag.value).v("Fallback to clear everything")
callRingPlayerIncoming?.stop()
callRingPlayerOutgoing?.stop()
if (callId != null) {
notificationManager.cancel(callId.hashCode())
}
- val notification = notificationUtils.buildCallEndedNotification()
+ val notification = notificationUtils.buildCallEndedNotification(false)
startForeground(DEFAULT_NOTIFICATION_ID, notification)
if (knownCalls.isEmpty()) {
mediaSession?.isActive = false
@@ -274,14 +289,31 @@ class CallService : VectorService() {
connections[callConnection.callId] = callConnection
}
- private fun getOpponentMatrixItem(call: WebRtcCall): MatrixItem? {
- return vectorComponent().activeSessionHolder().getSafeActiveSession()?.let {
- call.getOpponentAsMatrixItem(it)
- }
+ private fun WebRtcCall.toCallInformation(): CallInformation {
+ return CallInformation(
+ callId = this.callId,
+ nativeRoomId = this.nativeRoomId,
+ opponentUserId = this.mxCall.opponentUserId,
+ opponentMatrixItem = vectorComponent().activeSessionHolder().getSafeActiveSession()?.let {
+ this.getOpponentAsMatrixItem(it)
+ },
+ isVideoCall = this.mxCall.isVideoCall,
+ isOutgoing = this.mxCall.isOutgoing
+ )
}
+ data class CallInformation(
+ val callId: String,
+ val nativeRoomId: String,
+ val opponentUserId: String,
+ val opponentMatrixItem: MatrixItem?,
+ val isVideoCall: Boolean,
+ val isOutgoing: Boolean
+ )
+
companion object {
private const val DEFAULT_NOTIFICATION_ID = 6480
+ private const val MISSED_CALL_TAG = "MISSED_CALL_TAG"
private const val ACTION_INCOMING_RINGING_CALL = "im.vector.app.core.services.CallService.ACTION_INCOMING_RINGING_CALL"
private const val ACTION_OUTGOING_RINGING_CALL = "im.vector.app.core.services.CallService.ACTION_OUTGOING_RINGING_CALL"
@@ -294,6 +326,8 @@ class CallService : VectorService() {
private const val EXTRA_CALL_ID = "EXTRA_CALL_ID"
private const val EXTRA_IS_IN_BG = "EXTRA_IS_IN_BG"
+ private const val EXTRA_END_CALL_REJECTED = "EXTRA_END_CALL_REJECTED"
+ private const val EXTRA_END_CALL_REASON = "EXTRA_END_CALL_REASON"
fun onIncomingCallRinging(context: Context,
callId: String,
@@ -329,11 +363,13 @@ class CallService : VectorService() {
ContextCompat.startForegroundService(context, intent)
}
- fun onCallTerminated(context: Context, callId: String) {
+ fun onCallTerminated(context: Context, callId: String, endCallReason: EndCallReason, rejected: Boolean) {
val intent = Intent(context, CallService::class.java)
.apply {
action = ACTION_CALL_TERMINATED
putExtra(EXTRA_CALL_ID, callId)
+ putExtra(EXTRA_END_CALL_REASON, endCallReason)
+ putExtra(EXTRA_END_CALL_REJECTED, rejected)
}
ContextCompat.startForegroundService(context, intent)
}
diff --git a/vector/src/main/java/im/vector/app/core/ui/list/GenericProgressBarItem.kt b/vector/src/main/java/im/vector/app/core/ui/list/GenericProgressBarItem.kt
new file mode 100644
index 0000000000..e392c0bdf2
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/core/ui/list/GenericProgressBarItem.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2021 New Vector Ltd
+ *
+ * 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 im.vector.app.core.ui.list
+
+import android.widget.ProgressBar
+import com.airbnb.epoxy.EpoxyAttribute
+import com.airbnb.epoxy.EpoxyModelClass
+import im.vector.app.R
+import im.vector.app.core.epoxy.VectorEpoxyHolder
+import im.vector.app.core.epoxy.VectorEpoxyModel
+
+/**
+ * A generic progress bar item.
+ */
+@EpoxyModelClass(layout = R.layout.item_generic_progress)
+abstract class GenericProgressBarItem : VectorEpoxyModel() {
+
+ @EpoxyAttribute
+ var progress: Int = 0
+
+ @EpoxyAttribute
+ var total: Int = 100
+
+ @EpoxyAttribute
+ var indeterminate: Boolean = false
+
+ override fun bind(holder: Holder) {
+ super.bind(holder)
+ holder.progressbar.progress = progress
+ holder.progressbar.max = total
+ holder.progressbar.isIndeterminate = indeterminate
+ }
+
+ class Holder : VectorEpoxyHolder() {
+ val progressbar by bind(R.id.genericProgressBar)
+ }
+}
diff --git a/vector/src/main/java/im/vector/app/core/ui/views/NotificationAreaView.kt b/vector/src/main/java/im/vector/app/core/ui/views/NotificationAreaView.kt
index ad2a4b8e0c..463d94b288 100644
--- a/vector/src/main/java/im/vector/app/core/ui/views/NotificationAreaView.kt
+++ b/vector/src/main/java/im/vector/app/core/ui/views/NotificationAreaView.kt
@@ -21,11 +21,12 @@ import android.graphics.Color
import android.text.method.LinkMovementMethod
import android.util.AttributeSet
import android.view.View
-import android.widget.RelativeLayout
+import android.widget.LinearLayout
import androidx.core.content.ContextCompat
import androidx.core.text.italic
import im.vector.app.R
import im.vector.app.core.error.ResourceLimitErrorFormatter
+import im.vector.app.core.extensions.exhaustive
import im.vector.app.core.utils.DimensionConverter
import im.vector.app.databinding.ViewNotificationAreaBinding
import im.vector.app.features.themes.ThemeUtils
@@ -44,7 +45,7 @@ class NotificationAreaView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
-) : RelativeLayout(context, attrs, defStyleAttr) {
+) : LinearLayout(context, attrs, defStyleAttr) {
var delegate: Delegate? = null
private var state: State = State.Initial
@@ -69,12 +70,13 @@ class NotificationAreaView @JvmOverloads constructor(
cleanUp()
state = newState
when (newState) {
+ State.Initial -> Unit
is State.Default -> renderDefault()
is State.Hidden -> renderHidden()
is State.NoPermissionToPost -> renderNoPermissionToPost()
- is State.Tombstone -> renderTombstone(newState)
+ is State.Tombstone -> renderTombstone()
is State.ResourceLimitExceededError -> renderResourceLimitExceededError(newState)
- }
+ }.exhaustive
}
// PRIVATE METHODS ****************************************************************************************************************************************
@@ -125,15 +127,15 @@ class NotificationAreaView @JvmOverloads constructor(
setBackgroundColor(ContextCompat.getColor(context, backgroundColor))
}
- private fun renderTombstone(state: State.Tombstone) {
+ private fun renderTombstone() {
visibility = View.VISIBLE
- views.roomNotificationIcon.setImageResource(R.drawable.error)
+ views.roomNotificationIcon.setImageResource(R.drawable.ic_warning_badge)
val message = span {
+resources.getString(R.string.room_tombstone_versioned_description)
+"\n"
span(resources.getString(R.string.room_tombstone_continuation_link)) {
textDecorationLine = "underline"
- onClick = { delegate?.onTombstoneEventClicked(state.tombstoneEvent) }
+ onClick = { delegate?.onTombstoneEventClicked() }
}
}
views.roomNotificationMessage.movementMethod = BetterLinkMovementMethod.getInstance()
@@ -177,6 +179,6 @@ class NotificationAreaView @JvmOverloads constructor(
* An interface to delegate some actions to another object
*/
interface Delegate {
- fun onTombstoneEventClicked(tombstoneEvent: Event)
+ fun onTombstoneEventClicked()
}
}
diff --git a/vector/src/main/java/im/vector/app/core/utils/PermissionsTools.kt b/vector/src/main/java/im/vector/app/core/utils/PermissionsTools.kt
index b6566b4ce9..4268a034f5 100644
--- a/vector/src/main/java/im/vector/app/core/utils/PermissionsTools.kt
+++ b/vector/src/main/java/im/vector/app/core/utils/PermissionsTools.kt
@@ -18,117 +18,67 @@ package im.vector.app.core.utils
import android.Manifest
import android.app.Activity
-import android.content.Context
import android.content.pm.PackageManager
-import android.os.Build
-import android.widget.Toast
+import androidx.activity.ComponentActivity
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.StringRes
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
+import androidx.fragment.app.FragmentActivity
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import im.vector.app.R
import im.vector.app.core.platform.VectorBaseActivity
-import timber.log.Timber
-
-// Android M permission request code management
-private const val PERMISSIONS_GRANTED = true
-private const val PERMISSIONS_DENIED = !PERMISSIONS_GRANTED
-
-// Permission bit
-private const val PERMISSION_BYPASSED = 0x0
-const val PERMISSION_CAMERA = 0x1
-private const val PERMISSION_WRITE_EXTERNAL_STORAGE = 0x1 shl 1
-private const val PERMISSION_RECORD_AUDIO = 0x1 shl 2
-private const val PERMISSION_READ_CONTACTS = 0x1 shl 3
-private const val PERMISSION_READ_EXTERNAL_STORAGE = 0x1 shl 4
// Permissions sets
-const val PERMISSIONS_FOR_AUDIO_IP_CALL = PERMISSION_RECORD_AUDIO
-const val PERMISSIONS_FOR_VIDEO_IP_CALL = PERMISSION_CAMERA or PERMISSION_RECORD_AUDIO
-const val PERMISSIONS_FOR_TAKING_PHOTO = PERMISSION_CAMERA
-const val PERMISSIONS_FOR_MEMBERS_SEARCH = PERMISSION_READ_CONTACTS
-const val PERMISSIONS_FOR_MEMBER_DETAILS = PERMISSION_READ_CONTACTS
-const val PERMISSIONS_FOR_ROOM_AVATAR = PERMISSION_CAMERA
-const val PERMISSIONS_FOR_VIDEO_RECORDING = PERMISSION_CAMERA or PERMISSION_RECORD_AUDIO
-const val PERMISSIONS_FOR_WRITING_FILES = PERMISSION_WRITE_EXTERNAL_STORAGE
-const val PERMISSIONS_FOR_READING_FILES = PERMISSION_READ_EXTERNAL_STORAGE
-const val PERMISSIONS_FOR_PICKING_CONTACT = PERMISSION_READ_CONTACTS
+val PERMISSIONS_FOR_AUDIO_IP_CALL = listOf(Manifest.permission.RECORD_AUDIO)
+val PERMISSIONS_FOR_VIDEO_IP_CALL = listOf(Manifest.permission.RECORD_AUDIO, Manifest.permission.CAMERA)
+val PERMISSIONS_FOR_TAKING_PHOTO = listOf(Manifest.permission.CAMERA)
+val PERMISSIONS_FOR_MEMBERS_SEARCH = listOf(Manifest.permission.READ_CONTACTS)
+val PERMISSIONS_FOR_ROOM_AVATAR = listOf(Manifest.permission.CAMERA)
+val PERMISSIONS_FOR_WRITING_FILES = listOf(Manifest.permission.WRITE_EXTERNAL_STORAGE)
+val PERMISSIONS_FOR_PICKING_CONTACT = listOf(Manifest.permission.READ_CONTACTS)
-const val PERMISSIONS_EMPTY = PERMISSION_BYPASSED
+val PERMISSIONS_EMPTY = emptyList()
-// Request code to ask permission to the system (arbitrary values)
-const val PERMISSION_REQUEST_CODE = 567
-const val PERMISSION_REQUEST_CODE_LAUNCH_CAMERA = 568
-const val PERMISSION_REQUEST_CODE_LAUNCH_NATIVE_CAMERA = 569
-const val PERMISSION_REQUEST_CODE_LAUNCH_NATIVE_VIDEO_CAMERA = 570
-const val PERMISSION_REQUEST_CODE_AUDIO_CALL = 571
-const val PERMISSION_REQUEST_CODE_VIDEO_CALL = 572
-const val PERMISSION_REQUEST_CODE_CHANGE_AVATAR = 574
-const val PERMISSION_REQUEST_CODE_DOWNLOAD_FILE = 575
-const val PERMISSION_REQUEST_CODE_PICK_ATTACHMENT = 576
-const val PERMISSION_REQUEST_CODE_INCOMING_URI = 577
-const val PERMISSION_REQUEST_CODE_READ_CONTACTS = 579
+// This is not ideal to store the value like that, but it works
+private var permissionDialogDisplayed = false
/**
- * Log the used permissions statuses.
+ * First boolean is true if all permissions have been granted
+ * Second boolean is true if the permission is denied forever AND the permission request has not been displayed.
+ * So when the user does not grant the permission and check the box do not ask again, this boolean will be false.
+ * Only useful if the first boolean is false
*/
-fun logPermissionStatuses(context: Context) {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
- val permissions = listOf(
- Manifest.permission.CAMERA,
- Manifest.permission.RECORD_AUDIO,
- Manifest.permission.WRITE_EXTERNAL_STORAGE,
- Manifest.permission.READ_EXTERNAL_STORAGE,
- Manifest.permission.READ_CONTACTS)
+fun ComponentActivity.registerForPermissionsResult(lambda: (allGranted: Boolean, deniedPermanently: Boolean) -> Unit)
+ : ActivityResultLauncher> {
+ return registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { result ->
+ onPermissionResult(result, lambda)
+ }
+}
- Timber.v("## logPermissionStatuses() : log the permissions status used by the app")
+fun Fragment.registerForPermissionsResult(lambda: (allGranted: Boolean, deniedPermanently: Boolean) -> Unit): ActivityResultLauncher> {
+ return registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { result ->
+ onPermissionResult(result, lambda)
+ }
+}
- for (permission in permissions) {
- Timber.v(("Status of [$permission] : " +
- if (PackageManager.PERMISSION_GRANTED == ContextCompat.checkSelfPermission(context, permission)) {
- "PERMISSION_GRANTED"
- } else {
- "PERMISSION_DENIED"
- }))
+private fun onPermissionResult(result: Map, lambda: (allGranted: Boolean, deniedPermanently: Boolean) -> Unit) {
+ if (result.keys.all { result[it] == true }) {
+ lambda(true, /* not used */ false)
+ } else {
+ if (permissionDialogDisplayed) {
+ // A permission dialog has been displayed, so even if the user has checked the do not ask again button, we do
+ // not tell the user to open the app settings
+ lambda(false, false)
+ } else {
+ // No dialog has been displayed, so tell the user to go to the system setting
+ lambda(false, true)
}
}
-}
-
-fun Fragment.registerForPermissionsResult(allGranted: (Boolean) -> Unit): ActivityResultLauncher> {
- return registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { result ->
- allGranted.invoke(result.keys.all { result[it] == true })
- }
-}
-
-/**
- * See [.checkPermissions]
- *
- * @param permissionsToBeGrantedBitMap
- * @param activity
- * @return true if the permissions are granted (synchronous flow), false otherwise (asynchronous flow)
- */
-fun checkPermissions(permissionsToBeGrantedBitMap: Int,
- activity: Activity,
- requestCode: Int,
- @StringRes rationaleMessage: Int = 0): Boolean {
- return checkPermissions(permissionsToBeGrantedBitMap, activity, null, requestCode, rationaleMessage)
-}
-
-/**
- * See [.checkPermissions]
- *
- * @param permissionsToBeGrantedBitMap
- * @param activityResultLauncher from the calling fragment that is requesting the permissions
- * @return true if the permissions are granted (synchronous flow), false otherwise (asynchronous flow)
- */
-fun checkPermissions(permissionsToBeGrantedBitMap: Int,
- activity: Activity,
- activityResultLauncher: ActivityResultLauncher>,
- @StringRes rationaleMessage: Int = 0): Boolean {
- return checkPermissions(permissionsToBeGrantedBitMap, activity, activityResultLauncher, 0, rationaleMessage)
+ // Reset
+ permissionDialogDisplayed = false
}
/**
@@ -144,145 +94,65 @@ fun checkPermissions(permissionsToBeGrantedBitMap: Int,
* If a permission was already denied by the user, a popup is displayed to
* explain why vector needs the corresponding permission.
*
- * @param permissionsToBeGrantedBitMap the permissions bit map to be granted
- * @param activity the calling Activity that is requesting the permissions (or fragment parent)
- * @param activityResultLauncher from the calling fragment that is requesting the permissions
+ * @param permissionsToBeGranted the permissions to be granted
+ * @param activity the calling Activity that is requesting the permissions (or fragment parent)
+ * @param activityResultLauncher from the calling fragment/Activity that is requesting the permissions
+ * @param rationaleMessage message to be displayed BEFORE requesting for the permission
* @return true if the permissions are granted (synchronous flow), false otherwise (asynchronous flow)
*/
-private fun checkPermissions(permissionsToBeGrantedBitMap: Int,
- activity: Activity,
- activityResultLauncher: ActivityResultLauncher>?,
- requestCode: Int,
- @StringRes rationaleMessage: Int
-): Boolean {
- var isPermissionGranted = false
+fun checkPermissions(permissionsToBeGranted: List,
+ activity: Activity,
+ activityResultLauncher: ActivityResultLauncher>,
+ @StringRes rationaleMessage: Int = 0): Boolean {
+ // retrieve the permissions to be granted according to the permission list
+ val missingPermissions = permissionsToBeGranted.filter { permission ->
+ ContextCompat.checkSelfPermission(activity.applicationContext, permission) == PackageManager.PERMISSION_DENIED
+ }
- // sanity check
- if (PERMISSIONS_EMPTY == permissionsToBeGrantedBitMap) {
- isPermissionGranted = true
- } else if (PERMISSIONS_FOR_AUDIO_IP_CALL != permissionsToBeGrantedBitMap
- && PERMISSIONS_FOR_VIDEO_IP_CALL != permissionsToBeGrantedBitMap
- && PERMISSIONS_FOR_TAKING_PHOTO != permissionsToBeGrantedBitMap
- && PERMISSIONS_FOR_MEMBERS_SEARCH != permissionsToBeGrantedBitMap
- && PERMISSIONS_FOR_MEMBER_DETAILS != permissionsToBeGrantedBitMap
- && PERMISSIONS_FOR_ROOM_AVATAR != permissionsToBeGrantedBitMap
- && PERMISSIONS_FOR_VIDEO_RECORDING != permissionsToBeGrantedBitMap
- && PERMISSIONS_FOR_WRITING_FILES != permissionsToBeGrantedBitMap
- && PERMISSIONS_FOR_READING_FILES != permissionsToBeGrantedBitMap) {
- Timber.w("## checkPermissions(): permissions to be granted are not supported")
- isPermissionGranted = false
- } else {
- val permissionListAlreadyDenied = ArrayList()
- val permissionsListToBeGranted = ArrayList()
- var isRequestPermissionRequired = false
+ return if (missingPermissions.isNotEmpty()) {
+ permissionDialogDisplayed = !permissionsDeniedPermanently(missingPermissions, activity)
- // retrieve the permissions to be granted according to the request code bit map
- if (PERMISSION_CAMERA == permissionsToBeGrantedBitMap and PERMISSION_CAMERA) {
- val permissionType = Manifest.permission.CAMERA
- isRequestPermissionRequired = isRequestPermissionRequired or
- updatePermissionsToBeGranted(activity, permissionListAlreadyDenied, permissionsListToBeGranted, permissionType)
- }
-
- if (PERMISSION_RECORD_AUDIO == permissionsToBeGrantedBitMap and PERMISSION_RECORD_AUDIO) {
- val permissionType = Manifest.permission.RECORD_AUDIO
- isRequestPermissionRequired = isRequestPermissionRequired or
- updatePermissionsToBeGranted(activity, permissionListAlreadyDenied, permissionsListToBeGranted, permissionType)
- }
-
- if (PERMISSION_WRITE_EXTERNAL_STORAGE == permissionsToBeGrantedBitMap and PERMISSION_WRITE_EXTERNAL_STORAGE) {
- val permissionType = Manifest.permission.WRITE_EXTERNAL_STORAGE
- isRequestPermissionRequired = isRequestPermissionRequired or
- updatePermissionsToBeGranted(activity, permissionListAlreadyDenied, permissionsListToBeGranted, permissionType)
- }
-
- if (PERMISSION_READ_EXTERNAL_STORAGE == permissionsToBeGrantedBitMap and PERMISSION_READ_EXTERNAL_STORAGE) {
- val permissionType = Manifest.permission.READ_EXTERNAL_STORAGE
- isRequestPermissionRequired = isRequestPermissionRequired or
- updatePermissionsToBeGranted(activity, permissionListAlreadyDenied, permissionsListToBeGranted, permissionType)
- }
-
- // the contact book access is requested for any android platforms
- // for android M, we use the system preferences
- // for android < M, we use a dedicated settings
- if (PERMISSION_READ_CONTACTS == permissionsToBeGrantedBitMap and PERMISSION_READ_CONTACTS) {
- val permissionType = Manifest.permission.READ_CONTACTS
-
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
- isRequestPermissionRequired = isRequestPermissionRequired or
- updatePermissionsToBeGranted(activity, permissionListAlreadyDenied, permissionsListToBeGranted, permissionType)
- } else {
- // TODO uncomment
- /*if (!ContactsManager.getInstance().isContactBookAccessRequested) {
- isRequestPermissionRequired = true
- permissionsListToBeGranted.add(permissionType)
- }*/
- }
- }
-
- // if some permissions were already denied: display a dialog to the user before asking again.
- if (permissionListAlreadyDenied.isNotEmpty() && rationaleMessage != 0) {
- // display the dialog with the info text
+ if (rationaleMessage != 0 && permissionDialogDisplayed) {
+ // display the dialog with the info text. Do not do it if no system dialog will
+ // be displayed
MaterialAlertDialogBuilder(activity)
.setTitle(R.string.permissions_rationale_popup_title)
.setMessage(rationaleMessage)
- .setOnCancelListener { Toast.makeText(activity, R.string.missing_permissions_warning, Toast.LENGTH_SHORT).show() }
+ .setCancelable(false)
.setPositiveButton(R.string.ok) { _, _ ->
- if (permissionsListToBeGranted.isNotEmpty()) {
- activityResultLauncher
- ?.launch(permissionsListToBeGranted.toTypedArray())
- ?: run {
- ActivityCompat.requestPermissions(activity, permissionsListToBeGranted.toTypedArray(), requestCode)
- }
- }
+ activityResultLauncher.launch(missingPermissions.toTypedArray())
}
.show()
} else {
// some permissions are not granted, ask permissions
- if (isRequestPermissionRequired) {
- val permissionsArrayToBeGranted = permissionsListToBeGranted.toTypedArray()
-
- // for android < M, we use a custom dialog to request the contacts book access.
- if (permissionsListToBeGranted.contains(Manifest.permission.READ_CONTACTS)
- && Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
- TODO()
- /*
- MaterialAlertDialogBuilder(activity)
- .setIcon(android.R.drawable.ic_dialog_info)
- .setTitle(R.string.permissions_rationale_popup_title)
- .setMessage(R.string.permissions_msg_contacts_warning_other_androids)
- // gives the contacts book access
- .setPositiveButton(R.string.yes) { _, _ ->
- ContactsManager.getInstance().setIsContactBookAccessAllowed(true)
- fragment?.requestPermissions(permissionsArrayToBeGranted, requestCode)
- ?: run {
- ActivityCompat.requestPermissions(activity, permissionsArrayToBeGranted, requestCode)
- }
- }
- // or reject it
- .setNegativeButton(R.string.no) { _, _ ->
- ContactsManager.getInstance().setIsContactBookAccessAllowed(false)
- fragment?.requestPermissions(permissionsArrayToBeGranted, requestCode)
- ?: run {
- ActivityCompat.requestPermissions(activity, permissionsArrayToBeGranted, requestCode)
- }
- }
- .show()
- */
- } else {
- activityResultLauncher
- ?.launch(permissionsArrayToBeGranted)
- ?: run {
- ActivityCompat.requestPermissions(activity, permissionsArrayToBeGranted, requestCode)
- }
- }
- } else {
- // permissions were granted, start now.
- isPermissionGranted = true
- }
+ activityResultLauncher.launch(missingPermissions.toTypedArray())
}
+ false
+ } else {
+ // permissions were granted, start now.
+ true
}
+}
- return isPermissionGranted
+/**
+ * To be call after the permission request
+ *
+ * @param permissionsToBeGranted the permissions to be granted
+ * @param activity the calling Activity that is requesting the permissions (or fragment parent)
+ *
+ * @return true if one of the permission has been denied and the user check the do not ask again checkbox
+ */
+private fun permissionsDeniedPermanently(permissionsToBeGranted: List,
+ activity: Activity): Boolean {
+ return permissionsToBeGranted
+ .filter { permission ->
+ ContextCompat.checkSelfPermission(activity.applicationContext, permission) == PackageManager.PERMISSION_DENIED
+ }
+ .any { permission ->
+ // If shouldShowRequestPermissionRationale() returns true, it means that the user as denied the permission, but not permanently.
+ // If it return false, it mean that the user as denied permanently the permission
+ ActivityCompat.shouldShowRequestPermissionRationale(activity, permission).not()
+ }
}
fun VectorBaseActivity<*>.onPermissionDeniedSnackbar(@StringRes rationaleMessage: Int) {
@@ -291,50 +161,13 @@ fun VectorBaseActivity<*>.onPermissionDeniedSnackbar(@StringRes rationaleMessage
}
}
-/**
- * Helper method used in [.checkPermissions] to populate the list of the
- * permissions to be granted (permissionsListToBeGrantedOut) and the list of the permissions already denied (permissionAlreadyDeniedListOut).
- *
- * @param activity calling activity
- * @param permissionAlreadyDeniedListOut list to be updated with the permissions already denied by the user
- * @param permissionsListToBeGrantedOut list to be updated with the permissions to be granted
- * @param permissionType the permission to be checked
- * @return true if the permission requires to be granted, false otherwise
- */
-private fun updatePermissionsToBeGranted(activity: Activity,
- permissionAlreadyDeniedListOut: MutableList,
- permissionsListToBeGrantedOut: MutableList,
- permissionType: String): Boolean {
- var isRequestPermissionRequested = false
-
- // add permission to be granted
- permissionsListToBeGrantedOut.add(permissionType)
-
- if (PackageManager.PERMISSION_GRANTED != ContextCompat.checkSelfPermission(activity.applicationContext, permissionType)) {
- isRequestPermissionRequested = true
-
- // add permission to the ones that were already asked to the user
- if (ActivityCompat.shouldShowRequestPermissionRationale(activity, permissionType)) {
- permissionAlreadyDeniedListOut.add(permissionType)
- }
- }
- return isRequestPermissionRequested
-}
-
-/**
- * Return true if all permissions are granted, false if not or if permission request has been cancelled
- */
-fun allGranted(grantResults: IntArray): Boolean {
- if (grantResults.isEmpty()) {
- // A cancellation occurred
- return false
- }
-
- var granted = true
-
- grantResults.forEach {
- granted = granted && PackageManager.PERMISSION_GRANTED == it
- }
-
- return granted
+fun FragmentActivity.onPermissionDeniedDialog(@StringRes rationaleMessage: Int) {
+ MaterialAlertDialogBuilder(this)
+ .setTitle(R.string.missing_permissions_title)
+ .setMessage(rationaleMessage)
+ .setPositiveButton(R.string.open_settings) { _, _ ->
+ openAppSettingsPage(this)
+ }
+ .setNegativeButton(R.string.cancel, null)
+ .show()
}
diff --git a/vector/src/main/java/im/vector/app/features/attachments/AttachmentTypeSelectorView.kt b/vector/src/main/java/im/vector/app/features/attachments/AttachmentTypeSelectorView.kt
index cf7270225d..c0d4669108 100644
--- a/vector/src/main/java/im/vector/app/features/attachments/AttachmentTypeSelectorView.kt
+++ b/vector/src/main/java/im/vector/app/features/attachments/AttachmentTypeSelectorView.kt
@@ -206,7 +206,7 @@ class AttachmentTypeSelectorView(context: Context,
/**
* The all possible types to pick with their required permissions.
*/
- enum class Type(val permissionsBit: Int) {
+ enum class Type(val permissions: List) {
CAMERA(PERMISSIONS_FOR_TAKING_PHOTO),
GALLERY(PERMISSIONS_EMPTY),
FILE(PERMISSIONS_EMPTY),
diff --git a/vector/src/main/java/im/vector/app/features/call/CallControlsView.kt b/vector/src/main/java/im/vector/app/features/call/CallControlsView.kt
index 1a54551072..3742de6271 100644
--- a/vector/src/main/java/im/vector/app/features/call/CallControlsView.kt
+++ b/vector/src/main/java/im/vector/app/features/call/CallControlsView.kt
@@ -118,7 +118,7 @@ class CallControlsView @JvmOverloads constructor(
views.connectedControls.isVisible = false
}
}
- is CallState.Terminated,
+ is CallState.Ended,
null -> {
views.ringingControls.isVisible = false
views.connectedControls.isVisible = false
diff --git a/vector/src/main/java/im/vector/app/features/call/VectorCallActivity.kt b/vector/src/main/java/im/vector/app/features/call/VectorCallActivity.kt
index 21939bd42b..a1e3717329 100644
--- a/vector/src/main/java/im/vector/app/features/call/VectorCallActivity.kt
+++ b/vector/src/main/java/im/vector/app/features/call/VectorCallActivity.kt
@@ -40,8 +40,8 @@ import im.vector.app.core.di.ScreenComponent
import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.core.utils.PERMISSIONS_FOR_AUDIO_IP_CALL
import im.vector.app.core.utils.PERMISSIONS_FOR_VIDEO_IP_CALL
-import im.vector.app.core.utils.allGranted
import im.vector.app.core.utils.checkPermissions
+import im.vector.app.core.utils.registerForPermissionsResult
import im.vector.app.databinding.ActivityCallBinding
import im.vector.app.features.call.dialpad.CallDialPadBottomSheet
import im.vector.app.features.call.dialpad.DialPadFragment
@@ -54,6 +54,7 @@ import im.vector.app.features.home.room.detail.RoomDetailArgs
import io.reactivex.android.schedulers.AndroidSchedulers
import kotlinx.parcelize.Parcelize
import org.matrix.android.sdk.api.extensions.orFalse
+import org.matrix.android.sdk.api.logger.LoggerTag
import org.matrix.android.sdk.api.session.call.CallState
import org.matrix.android.sdk.api.session.call.MxPeerConnectionState
import org.matrix.android.sdk.api.session.call.TurnServerResponse
@@ -71,6 +72,8 @@ data class CallArgs(
val isVideoCall: Boolean
) : Parcelable
+private val loggerTag = LoggerTag("VectorCallActivity", LoggerTag.VOIP)
+
class VectorCallActivity : VectorBaseActivity(), CallControlsView.InteractionListener {
override fun getBinding() = ActivityCallBinding.inflate(layoutInflater)
@@ -113,11 +116,11 @@ class VectorCallActivity : VectorBaseActivity(), CallContro
if (intent.hasExtra(MvRx.KEY_ARG)) {
callArgs = intent.getParcelableExtra(MvRx.KEY_ARG)!!
} else {
- Timber.e("## VOIP missing callArgs for VectorCall Activity")
+ Timber.tag(loggerTag.value).e("missing callArgs for VectorCall Activity")
finish()
}
- Timber.v("## VOIP EXTRA_MODE is ${intent.getStringExtra(EXTRA_MODE)}")
+ Timber.tag(loggerTag.value).v("EXTRA_MODE is ${intent.getStringExtra(EXTRA_MODE)}")
if (intent.getStringExtra(EXTRA_MODE) == INCOMING_RINGING) {
turnScreenOnAndKeyguardOff()
}
@@ -139,11 +142,11 @@ class VectorCallActivity : VectorBaseActivity(), CallContro
.disposeOnDestroy()
if (callArgs.isVideoCall) {
- if (checkPermissions(PERMISSIONS_FOR_VIDEO_IP_CALL, this, CAPTURE_PERMISSION_REQUEST_CODE, R.string.permissions_rationale_msg_camera_and_audio)) {
+ if (checkPermissions(PERMISSIONS_FOR_VIDEO_IP_CALL, this, permissionCameraLauncher, R.string.permissions_rationale_msg_camera_and_audio)) {
start()
}
} else {
- if (checkPermissions(PERMISSIONS_FOR_AUDIO_IP_CALL, this, CAPTURE_PERMISSION_REQUEST_CODE, R.string.permissions_rationale_msg_record_audio)) {
+ if (checkPermissions(PERMISSIONS_FOR_AUDIO_IP_CALL, this, permissionCameraLauncher, R.string.permissions_rationale_msg_record_audio)) {
start()
}
}
@@ -160,7 +163,7 @@ class VectorCallActivity : VectorBaseActivity(), CallContro
}
private fun renderState(state: VectorCallViewState) {
- Timber.v("## VOIP renderState call $state")
+ Timber.tag(loggerTag.value).v("renderState call $state")
if (state.callState is Fail) {
finish()
return
@@ -196,7 +199,7 @@ class VectorCallActivity : VectorBaseActivity(), CallContro
views.callConnectingProgress.isVisible = true
configureCallInfo(state)
}
- is CallState.Connected -> {
+ is CallState.Connected -> {
if (callState.iceConnectionState == MxPeerConnectionState.CONNECTED) {
if (state.isLocalOnHold || state.isRemoteOnHold) {
views.smallIsHeldIcon.isVisible = true
@@ -246,10 +249,10 @@ class VectorCallActivity : VectorBaseActivity(), CallContro
views.callConnectingProgress.isVisible = true
}
}
- is CallState.Terminated -> {
+ is CallState.Ended -> {
finish()
}
- null -> {
+ null -> {
}
}
}
@@ -298,9 +301,8 @@ class VectorCallActivity : VectorBaseActivity(), CallContro
}
}
- override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) {
- super.onRequestPermissionsResult(requestCode, permissions, grantResults)
- if (requestCode == CAPTURE_PERMISSION_REQUEST_CODE && allGranted(grantResults)) {
+ private val permissionCameraLauncher = registerForPermissionsResult { allGranted, _ ->
+ if (allGranted) {
start()
} else {
// TODO display something
@@ -310,7 +312,7 @@ class VectorCallActivity : VectorBaseActivity(), CallContro
private fun start() {
rootEglBase = EglUtils.rootEglBase ?: return Unit.also {
- Timber.v("## VOIP rootEglBase is null")
+ Timber.tag(loggerTag.value).v("rootEglBase is null")
finish()
}
@@ -336,7 +338,7 @@ class VectorCallActivity : VectorBaseActivity(), CallContro
}
private fun handleViewEvents(event: VectorCallViewEvents?) {
- Timber.v("## VOIP handleViewEvents $event")
+ Timber.tag(loggerTag.value).v("handleViewEvents $event")
when (event) {
VectorCallViewEvents.DismissNoCall -> {
finish()
@@ -358,7 +360,7 @@ class VectorCallActivity : VectorBaseActivity(), CallContro
}
private fun onErrorTimoutConnect(turn: TurnServerResponse?) {
- Timber.d("## VOIP onErrorTimoutConnect $turn")
+ Timber.tag(loggerTag.value).d("onErrorTimoutConnect $turn")
// TODO ask to use default stun, etc...
MaterialAlertDialogBuilder(this)
.setTitle(R.string.call_failed_no_connection)
@@ -370,8 +372,6 @@ class VectorCallActivity : VectorBaseActivity(), CallContro
}
companion object {
-
- private const val CAPTURE_PERMISSION_REQUEST_CODE = 1
private const val EXTRA_MODE = "EXTRA_MODE"
private const val FRAGMENT_DIAL_PAD_TAG = "FRAGMENT_DIAL_PAD_TAG"
@@ -440,7 +440,7 @@ class VectorCallActivity : VectorBaseActivity(), CallContro
// Needed to let you answer call when phone is locked
private fun turnScreenOnAndKeyguardOff() {
- Timber.v("## VOIP turnScreenOnAndKeyguardOff")
+ Timber.tag(loggerTag.value).v("turnScreenOnAndKeyguardOff")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
setShowWhenLocked(true)
setTurnScreenOn(true)
@@ -461,7 +461,7 @@ class VectorCallActivity : VectorBaseActivity(), CallContro
}
private fun turnScreenOffAndKeyguardOn() {
- Timber.v("## VOIP turnScreenOnAndKeyguardOn")
+ Timber.tag(loggerTag.value).v("turnScreenOnAndKeyguardOn")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
setShowWhenLocked(false)
setTurnScreenOn(false)
diff --git a/vector/src/main/java/im/vector/app/features/call/audio/API21AudioDeviceDetector.kt b/vector/src/main/java/im/vector/app/features/call/audio/API21AudioDeviceDetector.kt
index 32b243aa2b..4f54f703b4 100644
--- a/vector/src/main/java/im/vector/app/features/call/audio/API21AudioDeviceDetector.kt
+++ b/vector/src/main/java/im/vector/app/features/call/audio/API21AudioDeviceDetector.kt
@@ -25,9 +25,12 @@ import android.media.AudioManager
import androidx.core.content.getSystemService
import im.vector.app.core.services.BluetoothHeadsetReceiver
import im.vector.app.core.services.WiredHeadsetStateReceiver
+import org.matrix.android.sdk.api.logger.LoggerTag
import timber.log.Timber
import java.util.HashSet
+private val loggerTag = LoggerTag("API21AudioDeviceDetector", LoggerTag.VOIP)
+
internal class API21AudioDeviceDetector(private val context: Context,
private val audioManager: AudioManager,
private val callAudioManager: CallAudioManager
@@ -62,17 +65,17 @@ internal class API21AudioDeviceDetector(private val context: Context,
}
private fun isBluetoothHeadsetOn(): Boolean {
- Timber.v("## VOIP: AudioManager isBluetoothHeadsetOn")
+ Timber.tag(loggerTag.value).v("AudioManager isBluetoothHeadsetOn")
try {
if (connectedBlueToothHeadset == null) return false.also {
- Timber.v("## VOIP: AudioManager no connected bluetooth headset")
+ Timber.tag(loggerTag.value).v("AudioManager no connected bluetooth headset")
}
if (!audioManager.isBluetoothScoAvailableOffCall) return false.also {
- Timber.v("## VOIP: AudioManager isBluetoothScoAvailableOffCall false")
+ Timber.tag(loggerTag.value).v("AudioManager isBluetoothScoAvailableOffCall false")
}
return true
} catch (failure: Throwable) {
- Timber.e("## VOIP: AudioManager isBluetoothHeadsetOn failure ${failure.localizedMessage}")
+ Timber.e("AudioManager isBluetoothHeadsetOn failure ${failure.localizedMessage}")
return false
}
}
@@ -91,11 +94,11 @@ internal class API21AudioDeviceDetector(private val context: Context,
bluetoothHeadsetStateReceiver = BluetoothHeadsetReceiver.createAndRegister(context, this)
val bm: BluetoothManager? = context.getSystemService()
val adapter = bm?.adapter
- Timber.d("## VOIP Bluetooth adapter $adapter")
+ Timber.tag(loggerTag.value).d("Bluetooth adapter $adapter")
bluetoothAdapter = adapter
adapter?.getProfileProxy(context, object : BluetoothProfile.ServiceListener {
override fun onServiceDisconnected(profile: Int) {
- Timber.d("## VOIP onServiceDisconnected $profile")
+ Timber.tag(loggerTag.value).d("onServiceDisconnected $profile")
if (profile == BluetoothProfile.HEADSET) {
connectedBlueToothHeadset = null
onAudioDeviceChange()
@@ -103,7 +106,7 @@ internal class API21AudioDeviceDetector(private val context: Context,
}
override fun onServiceConnected(profile: Int, proxy: BluetoothProfile?) {
- Timber.d("## VOIP onServiceConnected $profile , proxy:$proxy")
+ Timber.tag(loggerTag.value).d("onServiceConnected $profile , proxy:$proxy")
if (profile == BluetoothProfile.HEADSET) {
connectedBlueToothHeadset = proxy
onAudioDeviceChange()
@@ -122,12 +125,12 @@ internal class API21AudioDeviceDetector(private val context: Context,
}
override fun onHeadsetEvent(event: WiredHeadsetStateReceiver.HeadsetPlugEvent) {
- Timber.v("onHeadsetEvent $event")
+ Timber.tag(loggerTag.value).v("onHeadsetEvent $event")
onAudioDeviceChange()
}
override fun onBTHeadsetEvent(event: BluetoothHeadsetReceiver.BTHeadsetPlugEvent) {
- Timber.v("onBTHeadsetEvent $event")
+ Timber.tag(loggerTag.value).v("onBTHeadsetEvent $event")
onAudioDeviceChange()
}
}
diff --git a/vector/src/main/java/im/vector/app/features/call/conference/JitsiService.kt b/vector/src/main/java/im/vector/app/features/call/conference/JitsiService.kt
index c9fb8fbccd..7b01824c6c 100644
--- a/vector/src/main/java/im/vector/app/features/call/conference/JitsiService.kt
+++ b/vector/src/main/java/im/vector/app/features/call/conference/JitsiService.kt
@@ -56,7 +56,7 @@ class JitsiService @Inject constructor(
// Build data for a jitsi widget
val widgetId: String = WidgetType.Jitsi.preferred + "_" + session.myUserId + "_" + System.currentTimeMillis()
val preferredJitsiDomain = tryOrNull {
- rawService.getElementWellknown(session.myUserId)
+ rawService.getElementWellknown(session.sessionParams)
?.jitsiServer
?.preferredDomain
}
diff --git a/vector/src/main/java/im/vector/app/features/call/transfer/CallTransferViewModel.kt b/vector/src/main/java/im/vector/app/features/call/transfer/CallTransferViewModel.kt
index 0f37ccaa29..0217551260 100644
--- a/vector/src/main/java/im/vector/app/features/call/transfer/CallTransferViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/call/transfer/CallTransferViewModel.kt
@@ -57,7 +57,7 @@ class CallTransferViewModel @AssistedInject constructor(@Assisted initialState:
private val call = callManager.getCallById(initialState.callId)
private val callListener = object : WebRtcCall.Listener {
override fun onStateUpdate(call: MxCall) {
- if (call.state == CallState.Terminated) {
+ if (call.state is CallState.Ended) {
_viewEvents.post(CallTransferViewEvents.Dismiss)
}
}
diff --git a/vector/src/main/java/im/vector/app/features/call/webrtc/PeerConnectionObserver.kt b/vector/src/main/java/im/vector/app/features/call/webrtc/PeerConnectionObserver.kt
index f14bb2f849..99c26c5ebe 100644
--- a/vector/src/main/java/im/vector/app/features/call/webrtc/PeerConnectionObserver.kt
+++ b/vector/src/main/java/im/vector/app/features/call/webrtc/PeerConnectionObserver.kt
@@ -16,6 +16,7 @@
package im.vector.app.features.call.webrtc
+import org.matrix.android.sdk.api.logger.LoggerTag
import org.matrix.android.sdk.api.session.call.CallState
import org.matrix.android.sdk.api.session.call.MxPeerConnectionState
import org.webrtc.DataChannel
@@ -25,10 +26,12 @@ import org.webrtc.PeerConnection
import org.webrtc.RtpReceiver
import timber.log.Timber
+private val loggerTag = LoggerTag("PeerConnectionObserver", LoggerTag.VOIP)
+
class PeerConnectionObserver(private val webRtcCall: WebRtcCall) : PeerConnection.Observer {
override fun onConnectionChange(newState: PeerConnection.PeerConnectionState?) {
- Timber.v("## VOIP StreamObserver onConnectionChange: $newState")
+ Timber.tag(loggerTag.value).v("StreamObserver onConnectionChange: $newState")
when (newState) {
/**
* Every ICE transport used by the connection is either in use (state "connected" or "completed")
@@ -79,20 +82,20 @@ class PeerConnectionObserver(private val webRtcCall: WebRtcCall) : PeerConnectio
}
override fun onIceCandidate(iceCandidate: IceCandidate) {
- Timber.v("## VOIP StreamObserver onIceCandidate: $iceCandidate")
+ Timber.tag(loggerTag.value).v("StreamObserver onIceCandidate: $iceCandidate")
webRtcCall.onIceCandidate(iceCandidate)
}
override fun onDataChannel(dc: DataChannel) {
- Timber.v("## VOIP StreamObserver onDataChannel: ${dc.state()}")
+ Timber.tag(loggerTag.value).v("StreamObserver onDataChannel: ${dc.state()}")
}
override fun onIceConnectionReceivingChange(receiving: Boolean) {
- Timber.v("## VOIP StreamObserver onIceConnectionReceivingChange: $receiving")
+ Timber.tag(loggerTag.value).v("StreamObserver onIceConnectionReceivingChange: $receiving")
}
override fun onIceConnectionChange(newState: PeerConnection.IceConnectionState) {
- Timber.v("## VOIP StreamObserver onIceConnectionChange IceConnectionState:$newState")
+ Timber.tag(loggerTag.value).v("StreamObserver onIceConnectionChange IceConnectionState:$newState")
when (newState) {
/**
* the ICE agent is gathering addresses or is waiting to be given remote candidates through
@@ -145,29 +148,29 @@ class PeerConnectionObserver(private val webRtcCall: WebRtcCall) : PeerConnectio
}
override fun onAddStream(stream: MediaStream) {
- Timber.v("## VOIP StreamObserver onAddStream: $stream")
+ Timber.tag(loggerTag.value).v("StreamObserver onAddStream: $stream")
webRtcCall.onAddStream(stream)
}
override fun onRemoveStream(stream: MediaStream) {
- Timber.v("## VOIP StreamObserver onRemoveStream")
+ Timber.tag(loggerTag.value).v("StreamObserver onRemoveStream")
webRtcCall.onRemoveStream()
}
override fun onIceGatheringChange(newState: PeerConnection.IceGatheringState) {
- Timber.v("## VOIP StreamObserver onIceGatheringChange: $newState")
+ Timber.tag(loggerTag.value).v("StreamObserver onIceGatheringChange: $newState")
}
override fun onSignalingChange(newState: PeerConnection.SignalingState) {
- Timber.v("## VOIP StreamObserver onSignalingChange: $newState")
+ Timber.tag(loggerTag.value).v("StreamObserver onSignalingChange: $newState")
}
override fun onIceCandidatesRemoved(candidates: Array) {
- Timber.v("## VOIP StreamObserver onIceCandidatesRemoved: ${candidates.contentToString()}")
+ Timber.tag(loggerTag.value).v("StreamObserver onIceCandidatesRemoved: ${candidates.contentToString()}")
}
override fun onRenegotiationNeeded() {
- Timber.v("## VOIP StreamObserver onRenegotiationNeeded")
+ Timber.tag(loggerTag.value).v("StreamObserver onRenegotiationNeeded")
webRtcCall.onRenegotiationNeeded(restartIce = false)
}
@@ -178,6 +181,6 @@ class PeerConnectionObserver(private val webRtcCall: WebRtcCall) : PeerConnectio
* gets a new set of tracks because the media element being captured loaded a new source.
*/
override fun onAddTrack(p0: RtpReceiver?, p1: Array?) {
- Timber.v("## VOIP StreamObserver onAddTrack")
+ Timber.tag(loggerTag.value).v("StreamObserver onAddTrack")
}
}
diff --git a/vector/src/main/java/im/vector/app/features/call/webrtc/PeerConnectionObserverAdapter.kt b/vector/src/main/java/im/vector/app/features/call/webrtc/PeerConnectionObserverAdapter.kt
deleted file mode 100644
index 3d31f0e705..0000000000
--- a/vector/src/main/java/im/vector/app/features/call/webrtc/PeerConnectionObserverAdapter.kt
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright (c) 2020 New Vector Ltd
- *
- * 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 im.vector.app.features.call.webrtc
-
-import org.webrtc.DataChannel
-import org.webrtc.IceCandidate
-import org.webrtc.MediaStream
-import org.webrtc.PeerConnection
-import org.webrtc.RtpReceiver
-import timber.log.Timber
-
-abstract class PeerConnectionObserverAdapter : PeerConnection.Observer {
- override fun onIceCandidate(p0: IceCandidate?) {
- Timber.v("## VOIP onIceCandidate $p0")
- }
-
- override fun onDataChannel(p0: DataChannel?) {
- Timber.v("## VOIP onDataChannel $p0")
- }
-
- override fun onIceConnectionReceivingChange(p0: Boolean) {
- Timber.v("## VOIP onIceConnectionReceivingChange $p0")
- }
-
- override fun onIceConnectionChange(p0: PeerConnection.IceConnectionState?) {
- Timber.v("## VOIP onIceConnectionChange $p0")
- }
-
- override fun onIceGatheringChange(p0: PeerConnection.IceGatheringState?) {
- Timber.v("## VOIP onIceConnectionChange $p0")
- }
-
- override fun onAddStream(mediaStream: MediaStream?) {
- Timber.v("## VOIP onAddStream $mediaStream")
- }
-
- override fun onSignalingChange(p0: PeerConnection.SignalingState?) {
- Timber.v("## VOIP onSignalingChange $p0")
- }
-
- override fun onIceCandidatesRemoved(p0: Array?) {
- Timber.v("## VOIP onIceCandidatesRemoved $p0")
- }
-
- override fun onRemoveStream(mediaStream: MediaStream?) {
- Timber.v("## VOIP onRemoveStream $mediaStream")
- }
-
- override fun onRenegotiationNeeded() {
- Timber.v("## VOIP onRenegotiationNeeded")
- }
-
- override fun onAddTrack(p0: RtpReceiver?, p1: Array?) {
- Timber.v("## VOIP onAddTrack $p0 / out: $p1")
- }
-}
diff --git a/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCall.kt b/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCall.kt
index 3259b0915f..91d3ab7ddf 100644
--- a/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCall.kt
+++ b/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCall.kt
@@ -45,6 +45,7 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.extensions.tryOrNull
+import org.matrix.android.sdk.api.logger.LoggerTag
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.call.CallIdGenerator
import org.matrix.android.sdk.api.session.call.CallState
@@ -57,6 +58,9 @@ import org.matrix.android.sdk.api.session.room.model.call.CallCandidatesContent
import org.matrix.android.sdk.api.session.room.model.call.CallHangupContent
import org.matrix.android.sdk.api.session.room.model.call.CallInviteContent
import org.matrix.android.sdk.api.session.room.model.call.CallNegotiateContent
+import org.matrix.android.sdk.api.session.room.model.call.CallRejectContent
+import org.matrix.android.sdk.api.session.room.model.call.CallSelectAnswerContent
+import org.matrix.android.sdk.api.session.room.model.call.EndCallReason
import org.matrix.android.sdk.api.session.room.model.call.SdpType
import org.threeten.bp.Duration
import org.webrtc.AudioSource
@@ -88,6 +92,8 @@ private const val AUDIO_TRACK_ID = "${STREAM_ID}a0"
private const val VIDEO_TRACK_ID = "${STREAM_ID}v0"
private val DEFAULT_AUDIO_CONSTRAINTS = MediaConstraints()
+private val loggerTag = LoggerTag("WebRtcCall", LoggerTag.VOIP)
+
class WebRtcCall(
val mxCall: MxCall,
// This is where the call is placed from an ui perspective.
@@ -99,7 +105,7 @@ class WebRtcCall(
private val sessionProvider: Provider,
private val peerConnectionFactoryProvider: Provider,
private val onCallBecomeActive: (WebRtcCall) -> Unit,
- private val onCallEnded: (String) -> Unit
+ private val onCallEnded: (String, EndCallReason, Boolean) -> Unit
) : MxCall.StateListener {
interface Listener : MxCall.StateListener {
@@ -192,7 +198,7 @@ class WebRtcCall(
.subscribe {
// omit empty :/
if (it.isNotEmpty()) {
- Timber.v("## Sending local ice candidates to call")
+ Timber.tag(loggerTag.value).v("Sending local ice candidates to call")
// it.forEach { peerConnection?.addIceCandidate(it) }
mxCall.sendLocalCallCandidates(it.mapToCallCandidate())
}
@@ -210,7 +216,7 @@ class WebRtcCall(
fun onRenegotiationNeeded(restartIce: Boolean) {
sessionScope?.launch(dispatcher) {
if (mxCall.state != CallState.CreateOffer && mxCall.opponentVersion == 0) {
- Timber.v("Opponent does not support renegotiation: ignoring onRenegotiationNeeded event")
+ Timber.tag(loggerTag.value).v("Opponent does not support renegotiation: ignoring onRenegotiationNeeded event")
return@launch
}
val constraints = MediaConstraints()
@@ -218,7 +224,7 @@ class WebRtcCall(
constraints.mandatory.add(MediaConstraints.KeyValuePair("IceRestart", "true"))
}
val peerConnection = peerConnection ?: return@launch
- Timber.v("## VOIP creating offer...")
+ Timber.tag(loggerTag.value).v("creating offer...")
makingOffer = true
try {
val sessionDescription = peerConnection.awaitCreateOffer(constraints) ?: return@launch
@@ -227,7 +233,7 @@ class WebRtcCall(
// Allow a short time for initial candidates to be gathered
delay(200)
}
- if (mxCall.state == CallState.Terminated) {
+ if (mxCall.state is CallState.Ended) {
return@launch
}
if (mxCall.state == CallState.CreateOffer) {
@@ -238,7 +244,7 @@ class WebRtcCall(
}
} catch (failure: Throwable) {
// Need to handle error properly.
- Timber.v("Failure while creating offer")
+ Timber.tag(loggerTag.value).v("Failure while creating offer")
} finally {
makingOffer = false
}
@@ -267,7 +273,7 @@ class WebRtcCall(
}
}
}
- Timber.v("## VOIP creating peer connection...with iceServers $iceServers ")
+ Timber.tag(loggerTag.value).v("creating peer connection...with iceServers $iceServers ")
val rtcConfig = PeerConnection.RTCConfiguration(iceServers).apply {
sdpSemantics = PeerConnection.SdpSemantics.UNIFIED_PLAN
}
@@ -285,7 +291,7 @@ class WebRtcCall(
createCallId = CallIdGenerator.generate(),
awaitCallId = null
)
- endCall(sendEndSignaling = false)
+ terminate(EndCallReason.REPLACED)
}
}
@@ -307,14 +313,14 @@ class WebRtcCall(
createCallId = newCallId,
awaitCallId = null
)
- endCall(sendEndSignaling = false)
- transferTargetCall.endCall(sendEndSignaling = false)
+ terminate(EndCallReason.REPLACED)
+ transferTargetCall.terminate(EndCallReason.REPLACED)
}
}
fun acceptIncomingCall() {
sessionScope?.launch {
- Timber.v("## VOIP acceptIncomingCall from state ${mxCall.state}")
+ Timber.tag(loggerTag.value).v("acceptIncomingCall from state ${mxCall.state}")
if (mxCall.state == CallState.LocalRinging) {
internalAcceptIncomingCall()
}
@@ -333,7 +339,7 @@ class WebRtcCall(
sender.dtmf()?.insertDtmf(digit, 100, 70)
return@launch
} catch (failure: Throwable) {
- Timber.v("Fail to send Dtmf digit")
+ Timber.tag(loggerTag.value).v("Fail to send Dtmf digit")
}
}
}
@@ -342,7 +348,7 @@ class WebRtcCall(
fun attachViewRenderers(localViewRenderer: SurfaceViewRenderer?, remoteViewRenderer: SurfaceViewRenderer, mode: String?) {
sessionScope?.launch(dispatcher) {
- Timber.v("## VOIP attachViewRenderers localRendeder $localViewRenderer / $remoteViewRenderer")
+ Timber.tag(loggerTag.value).v("attachViewRenderers localRendeder $localViewRenderer / $remoteViewRenderer")
localSurfaceRenderers.addIfNeeded(localViewRenderer)
remoteSurfaceRenderers.addIfNeeded(remoteViewRenderer)
when (mode) {
@@ -389,7 +395,7 @@ class WebRtcCall(
}
private suspend fun detachRenderersInternal(renderers: List?) = withContext(dispatcher) {
- Timber.v("## VOIP detachRenderers")
+ Timber.tag(loggerTag.value).v("detachRenderers")
if (renderers.isNullOrEmpty()) {
// remove all sinks
localSurfaceRenderers.forEach {
@@ -422,12 +428,12 @@ class WebRtcCall(
// 2. Access camera (if video call) + microphone, create local stream
createLocalStream()
attachViewRenderersInternal()
- Timber.v("## VOIP remoteCandidateSource $remoteCandidateSource")
+ Timber.tag(loggerTag.value).v("remoteCandidateSource $remoteCandidateSource")
remoteIceCandidateDisposable = remoteCandidateSource.subscribe({
- Timber.v("## VOIP adding remote ice candidate $it")
+ Timber.tag(loggerTag.value).v("adding remote ice candidate $it")
peerConnection?.addIceCandidate(it)
}, {
- Timber.v("## VOIP failed to add remote ice candidate $it")
+ Timber.tag(loggerTag.value).v("failed to add remote ice candidate $it")
})
// Now we wait for negotiation callback
}
@@ -453,15 +459,15 @@ class WebRtcCall(
SessionDescription(SessionDescription.Type.OFFER, it)
}
if (offerSdp == null) {
- Timber.v("We don't have any offer to process")
+ Timber.tag(loggerTag.value).v("We don't have any offer to process")
return@withContext
}
- Timber.v("Offer sdp for invite: ${offerSdp.description}")
+ Timber.tag(loggerTag.value).v("Offer sdp for invite: ${offerSdp.description}")
try {
peerConnection?.awaitSetRemoteDescription(offerSdp)
} catch (failure: Throwable) {
- Timber.v("Failure putting remote description")
- endCall(true, CallHangupContent.Reason.UNKWOWN_ERROR)
+ Timber.tag(loggerTag.value).v("Failure putting remote description")
+ endCall(reason = EndCallReason.UNKWOWN_ERROR)
return@withContext
}
// 2) Access camera + microphone, create local stream
@@ -472,12 +478,12 @@ class WebRtcCall(
createAnswer()?.also {
mxCall.accept(it.description)
}
- Timber.v("## VOIP remoteCandidateSource $remoteCandidateSource")
+ Timber.tag(loggerTag.value).v("remoteCandidateSource $remoteCandidateSource")
remoteIceCandidateDisposable = remoteCandidateSource.subscribe({
- Timber.v("## VOIP adding remote ice candidate $it")
+ Timber.tag(loggerTag.value).v("adding remote ice candidate $it")
peerConnection?.addIceCandidate(it)
}, {
- Timber.v("## VOIP failed to add remote ice candidate $it")
+ Timber.tag(loggerTag.value).v("failed to add remote ice candidate $it")
})
}
@@ -489,7 +495,7 @@ class WebRtcCall(
private fun createLocalStream() {
val peerConnectionFactory = peerConnectionFactoryProvider.get() ?: return
- Timber.v("Create local stream for call ${mxCall.callId}")
+ Timber.tag(loggerTag.value).v("Create local stream for call ${mxCall.callId}")
configureAudioTrack(peerConnectionFactory)
// add video track if needed
if (mxCall.isVideoCall) {
@@ -502,7 +508,7 @@ class WebRtcCall(
val audioSource = peerConnectionFactory.createAudioSource(DEFAULT_AUDIO_CONSTRAINTS)
val audioTrack = peerConnectionFactory.createAudioTrack(AUDIO_TRACK_ID, audioSource)
audioTrack.setEnabled(true)
- Timber.v("Add audio track $AUDIO_TRACK_ID to call ${mxCall.callId}")
+ Timber.tag(loggerTag.value).v("Add audio track $AUDIO_TRACK_ID to call ${mxCall.callId}")
peerConnection?.addTrack(audioTrack, listOf(STREAM_ID))
localAudioSource = audioSource
localAudioTrack = audioTrack
@@ -544,7 +550,7 @@ class WebRtcCall(
override fun onCameraClosed() {
super.onCameraClosed()
- Timber.v("onCameraClosed")
+ Timber.tag(loggerTag.value).v("onCameraClosed")
// This could happen if you open the camera app in chat
// We then register in order to restart capture as soon as the camera is available again
videoCapturerIsInError = true
@@ -552,16 +558,16 @@ class WebRtcCall(
cameraAvailabilityCallback = object : CameraManager.AvailabilityCallback() {
override fun onCameraUnavailable(cameraId: String) {
super.onCameraUnavailable(cameraId)
- Timber.v("On camera unavailable: $cameraId")
+ Timber.tag(loggerTag.value).v("On camera unavailable: $cameraId")
}
override fun onCameraAccessPrioritiesChanged() {
super.onCameraAccessPrioritiesChanged()
- Timber.v("onCameraAccessPrioritiesChanged")
+ Timber.tag(loggerTag.value).v("onCameraAccessPrioritiesChanged")
}
override fun onCameraAvailable(cameraId: String) {
- Timber.v("On camera available: $cameraId")
+ Timber.tag(loggerTag.value).v("On camera available: $cameraId")
if (cameraId == camera.name) {
videoCapturer?.startCapture(currentCaptureFormat.width, currentCaptureFormat.height, currentCaptureFormat.fps)
cameraManager?.unregisterAvailabilityCallback(this)
@@ -574,7 +580,7 @@ class WebRtcCall(
val videoSource = peerConnectionFactory.createVideoSource(videoCapturer.isScreencast)
val surfaceTextureHelper = SurfaceTextureHelper.create("CaptureThread", rootEglBase!!.eglBaseContext)
- Timber.v("## VOIP Local video source created")
+ Timber.tag(loggerTag.value).v("Local video source created")
videoCapturer.initialize(surfaceTextureHelper, context, videoSource.capturerObserver)
// HD
@@ -582,7 +588,7 @@ class WebRtcCall(
this.videoCapturer = videoCapturer
val videoTrack = peerConnectionFactory.createVideoTrack(VIDEO_TRACK_ID, videoSource)
- Timber.v("Add video track $VIDEO_TRACK_ID to call ${mxCall.callId}")
+ Timber.tag(loggerTag.value).v("Add video track $VIDEO_TRACK_ID to call ${mxCall.callId}")
videoTrack.setEnabled(true)
peerConnection?.addTrack(videoTrack, listOf(STREAM_ID))
localVideoSource = videoSource
@@ -592,7 +598,7 @@ class WebRtcCall(
fun setCaptureFormat(format: CaptureFormat) {
sessionScope?.launch(dispatcher) {
- Timber.v("## VOIP setCaptureFormat $format")
+ Timber.tag(loggerTag.value).v("setCaptureFormat $format")
videoCapturer?.changeCaptureFormat(format.width, format.height, format.fps)
currentCaptureFormat = format
}
@@ -686,14 +692,14 @@ class WebRtcCall(
fun switchCamera() {
sessionScope?.launch(dispatcher) {
- Timber.v("## VOIP switchCamera")
+ Timber.tag(loggerTag.value).v("switchCamera")
if (mxCall.state is CallState.Connected && mxCall.isVideoCall) {
val oppositeCamera = getOppositeCameraIfAny() ?: return@launch
videoCapturer?.switchCamera(
object : CameraVideoCapturer.CameraSwitchHandler {
// Invoked on success. |isFrontCamera| is true if the new camera is front facing.
override fun onCameraSwitchDone(isFrontCamera: Boolean) {
- Timber.v("## VOIP onCameraSwitchDone isFront $isFrontCamera")
+ Timber.tag(loggerTag.value).v("onCameraSwitchDone isFront $isFrontCamera")
cameraInUse = oppositeCamera
localSurfaceRenderers.forEach {
it.get()?.setMirror(isFrontCamera)
@@ -704,7 +710,7 @@ class WebRtcCall(
}
override fun onCameraSwitchError(errorDescription: String?) {
- Timber.v("## VOIP onCameraSwitchError isFront $errorDescription")
+ Timber.tag(loggerTag.value).v("onCameraSwitchError isFront $errorDescription")
}
}, oppositeCamera.name
)
@@ -713,7 +719,7 @@ class WebRtcCall(
}
private suspend fun createAnswer(): SessionDescription? {
- Timber.w("## VOIP createAnswer")
+ Timber.tag(loggerTag.value).w("createAnswer")
val peerConnection = peerConnection ?: return null
val constraints = MediaConstraints().apply {
mandatory.add(MediaConstraints.KeyValuePair("OfferToReceiveAudio", "true"))
@@ -724,7 +730,7 @@ class WebRtcCall(
peerConnection.awaitSetLocalDescription(localDescription)
localDescription
} catch (failure: Throwable) {
- Timber.v("Fail to create answer")
+ Timber.tag(loggerTag.value).v("Fail to create answer")
null
}
}
@@ -765,9 +771,9 @@ class WebRtcCall(
sessionScope?.launch(dispatcher) {
// reportError("Weird-looking stream: " + stream);
if (stream.audioTracks.size > 1 || stream.videoTracks.size > 1) {
- Timber.e("## VOIP StreamObserver weird looking stream: $stream")
+ Timber.tag(loggerTag.value).e("StreamObserver weird looking stream: $stream")
// TODO maybe do something more??
- endCall(true)
+ endCall(EndCallReason.UNKWOWN_ERROR)
return@launch
}
if (stream.audioTracks.size == 1) {
@@ -795,32 +801,34 @@ class WebRtcCall(
}
}
- fun endCall(sendEndSignaling: Boolean = true, reason: CallHangupContent.Reason? = null) {
+ fun endCall(reason: EndCallReason = EndCallReason.USER_HANGUP) {
sessionScope?.launch(dispatcher) {
- if (mxCall.state == CallState.Terminated) {
+ if (mxCall.state is CallState.Ended) {
return@launch
}
- // Close tracks ASAP
- localVideoTrack?.setEnabled(false)
- localVideoTrack?.setEnabled(false)
- cameraAvailabilityCallback?.let { cameraAvailabilityCallback ->
- val cameraManager = context.getSystemService()!!
- cameraManager.unregisterAvailabilityCallback(cameraAvailabilityCallback)
- }
- val wasRinging = mxCall.state is CallState.LocalRinging
- mxCall.state = CallState.Terminated
- release()
- onCallEnded(callId)
- if (sendEndSignaling) {
- if (wasRinging) {
- mxCall.reject()
- } else {
- mxCall.hangUp(reason)
- }
+ val reject = mxCall.state is CallState.LocalRinging
+ terminate(EndCallReason.USER_HANGUP, reject)
+ if (reject) {
+ mxCall.reject()
+ } else {
+ mxCall.hangUp(reason)
}
}
}
+ private suspend fun terminate(reason: EndCallReason? = null, rejected: Boolean = false) = withContext(dispatcher) {
+ // Close tracks ASAP
+ localVideoTrack?.setEnabled(false)
+ localVideoTrack?.setEnabled(false)
+ cameraAvailabilityCallback?.let { cameraAvailabilityCallback ->
+ val cameraManager = context.getSystemService()!!
+ cameraManager.unregisterAvailabilityCallback(cameraAvailabilityCallback)
+ }
+ mxCall.state = CallState.Ended(reason ?: EndCallReason.USER_HANGUP)
+ release()
+ onCallEnded(callId, reason ?: EndCallReason.USER_HANGUP, rejected)
+ }
+
// Call listener
fun onCallIceCandidateReceived(iceCandidatesContent: CallCandidatesContent) {
@@ -829,7 +837,7 @@ class WebRtcCall(
if (it.sdpMid.isNullOrEmpty() || it.candidate.isNullOrEmpty()) {
return@forEach
}
- Timber.v("## VOIP onCallIceCandidateReceived for call ${mxCall.callId} sdp: ${it.candidate}")
+ Timber.tag(loggerTag.value).v("onCallIceCandidateReceived for call ${mxCall.callId} sdp: ${it.candidate}")
val iceCandidate = IceCandidate(it.sdpMid, it.sdpMLineIndex, it.candidate)
remoteCandidateSource.onNext(iceCandidate)
}
@@ -838,12 +846,12 @@ class WebRtcCall(
fun onCallAnswerReceived(callAnswerContent: CallAnswerContent) {
sessionScope?.launch(dispatcher) {
- Timber.v("## VOIP onCallAnswerReceived ${callAnswerContent.callId}")
+ Timber.tag(loggerTag.value).v("onCallAnswerReceived ${callAnswerContent.callId}")
val sdp = SessionDescription(SessionDescription.Type.ANSWER, callAnswerContent.answer.sdp)
try {
peerConnection?.awaitSetRemoteDescription(sdp)
} catch (failure: Throwable) {
- endCall(true, CallHangupContent.Reason.UNKWOWN_ERROR)
+ endCall(EndCallReason.UNKWOWN_ERROR)
return@launch
}
if (mxCall.opponentPartyId?.hasValue().orFalse()) {
@@ -858,7 +866,7 @@ class WebRtcCall(
val type = description?.type
val sdpText = description?.sdp
if (type == null || sdpText == null) {
- Timber.i("Ignoring invalid m.call.negotiate event")
+ Timber.tag(loggerTag.value).i("Ignoring invalid m.call.negotiate event")
return@launch
}
val peerConnection = peerConnection ?: return@launch
@@ -873,7 +881,7 @@ class WebRtcCall(
ignoreOffer = !polite && offerCollision
if (ignoreOffer) {
- Timber.i("Ignoring colliding negotiate event because we're impolite")
+ Timber.tag(loggerTag.value).i("Ignoring colliding negotiate event because we're impolite")
return@launch
}
val prevOnHold = computeIsLocalOnHold()
@@ -886,7 +894,7 @@ class WebRtcCall(
}
}
} catch (failure: Throwable) {
- Timber.e(failure, "Failed to complete negotiation")
+ Timber.tag(loggerTag.value).e(failure, "Failed to complete negotiation")
}
val nowOnHold = computeIsLocalOnHold()
wasLocalOnHold = nowOnHold
@@ -904,12 +912,35 @@ class WebRtcCall(
}
}
+ fun onCallHangupReceived(callHangupContent: CallHangupContent) {
+ sessionScope?.launch(dispatcher) {
+ terminate(callHangupContent.reason)
+ }
+ }
+
+ fun onCallRejectReceived(callRejectContent: CallRejectContent) {
+ sessionScope?.launch(dispatcher) {
+ terminate(callRejectContent.reason, true)
+ }
+ }
+
+ fun onCallSelectedAnswerReceived(callSelectAnswerContent: CallSelectAnswerContent) {
+ sessionScope?.launch(dispatcher) {
+ val selectedPartyId = callSelectAnswerContent.selectedPartyId
+ if (selectedPartyId != mxCall.ourPartyId) {
+ Timber.i("Got select_answer for party ID $selectedPartyId: we are party ID ${mxCall.ourPartyId}.")
+ // The other party has picked somebody else's answer
+ terminate()
+ }
+ }
+ }
+
fun onCallAssertedIdentityReceived(callAssertedIdentityContent: CallAssertedIdentityContent) {
sessionScope?.launch(dispatcher) {
val session = sessionProvider.get() ?: return@launch
val newAssertedIdentity = callAssertedIdentityContent.assertedIdentity ?: return@launch
if (newAssertedIdentity.id == null && newAssertedIdentity.displayName == null) {
- Timber.v("Asserted identity received with no relevant information, skip")
+ Timber.tag(loggerTag.value).v("Asserted identity received with no relevant information, skip")
return@launch
}
remoteAssertedIdentity = newAssertedIdentity
diff --git a/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCallExt.kt b/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCallExt.kt
index c99d097707..ef9ef3ef9a 100644
--- a/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCallExt.kt
+++ b/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCallExt.kt
@@ -21,7 +21,12 @@ import org.matrix.android.sdk.api.util.MatrixItem
import org.matrix.android.sdk.api.util.toMatrixItem
fun WebRtcCall.getOpponentAsMatrixItem(session: Session): MatrixItem? {
- return session.getRoomSummary(nativeRoomId)?.otherMemberIds?.firstOrNull()?.let {
- session.getUser(it)?.toMatrixItem()
+ return session.getRoomSummary(nativeRoomId)?.let { roomSummary ->
+ // Fallback to RoomSummary if there is no other member.
+ if (roomSummary.otherMemberIds.isEmpty()) {
+ roomSummary.toMatrixItem()
+ } else {
+ roomSummary.otherMemberIds.first().let { session.getUser(it)?.toMatrixItem() }
+ }
}
}
diff --git a/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCallManager.kt b/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCallManager.kt
index 25463428e9..73a6c07d6a 100644
--- a/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCallManager.kt
+++ b/vector/src/main/java/im/vector/app/features/call/webrtc/WebRtcCallManager.kt
@@ -29,10 +29,13 @@ import im.vector.app.features.call.lookup.CallProtocolsChecker
import im.vector.app.features.call.lookup.CallUserMapper
import im.vector.app.features.call.utils.EglUtils
import im.vector.app.features.call.vectorCallService
+import im.vector.app.features.session.coroutineScope
import im.vector.app.push.fcm.FcmHelper
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.asCoroutineDispatcher
import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.extensions.tryOrNull
+import org.matrix.android.sdk.api.logger.LoggerTag
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.call.CallListener
import org.matrix.android.sdk.api.session.call.CallState
@@ -45,6 +48,7 @@ import org.matrix.android.sdk.api.session.room.model.call.CallInviteContent
import org.matrix.android.sdk.api.session.room.model.call.CallNegotiateContent
import org.matrix.android.sdk.api.session.room.model.call.CallRejectContent
import org.matrix.android.sdk.api.session.room.model.call.CallSelectAnswerContent
+import org.matrix.android.sdk.api.session.room.model.call.EndCallReason
import org.webrtc.DefaultVideoDecoderFactory
import org.webrtc.DefaultVideoEncoderFactory
import org.webrtc.PeerConnectionFactory
@@ -60,6 +64,8 @@ import javax.inject.Singleton
* Manage peerConnectionFactory & Peer connections outside of activity lifecycle to resist configuration changes
* Use app context
*/
+private val loggerTag = LoggerTag("WebRtcCallManager", LoggerTag.VOIP)
+
@Singleton
class WebRtcCallManager @Inject constructor(
private val context: Context,
@@ -75,6 +81,9 @@ class WebRtcCallManager @Inject constructor(
private val callUserMapper: CallUserMapper?
get() = currentSession?.vectorCallService?.userMapper
+ private val sessionScope: CoroutineScope?
+ get() = currentSession?.coroutineScope
+
interface CurrentCallListener {
fun onCurrentCallChange(call: WebRtcCall?) {}
fun onAudioDevicesChange() {}
@@ -184,7 +193,7 @@ class WebRtcCallManager @Inject constructor(
fun getAdvertisedCalls() = advertisedCalls
fun headSetButtonTapped() {
- Timber.v("## VOIP headSetButtonTapped")
+ Timber.tag(loggerTag.value).v("headSetButtonTapped")
val call = getCurrentCall() ?: return
if (call.mxCall.state is CallState.LocalRinging) {
call.acceptIncomingCall()
@@ -197,12 +206,12 @@ class WebRtcCallManager @Inject constructor(
private fun createPeerConnectionFactoryIfNeeded() {
if (peerConnectionFactory != null) return
- Timber.v("## VOIP createPeerConnectionFactory")
+ Timber.tag(loggerTag.value).v("createPeerConnectionFactory")
val eglBaseContext = rootEglBase?.eglBaseContext ?: return Unit.also {
- Timber.e("## VOIP No EGL BASE")
+ Timber.tag(loggerTag.value).e("No EGL BASE")
}
- Timber.v("## VOIP PeerConnectionFactory.initialize")
+ Timber.tag(loggerTag.value).v("PeerConnectionFactory.initialize")
PeerConnectionFactory.initialize(PeerConnectionFactory
.InitializationOptions.builder(context.applicationContext)
.createInitializationOptions()
@@ -216,7 +225,7 @@ class WebRtcCallManager @Inject constructor(
/* enableH264HighProfile */
true)
val defaultVideoDecoderFactory = DefaultVideoDecoderFactory(eglBaseContext)
- Timber.v("## VOIP PeerConnectionFactory.createPeerConnectionFactory ...")
+ Timber.tag(loggerTag.value).v("PeerConnectionFactory.createPeerConnectionFactory ...")
peerConnectionFactory = PeerConnectionFactory.builder()
.setOptions(options)
.setVideoEncoderFactory(defaultVideoEncoderFactory)
@@ -225,19 +234,19 @@ class WebRtcCallManager @Inject constructor(
}
private fun onCallActive(call: WebRtcCall) {
- Timber.v("## VOIP WebRtcPeerConnectionManager onCall active: ${call.mxCall.callId}")
+ Timber.tag(loggerTag.value).v("WebRtcPeerConnectionManager onCall active: ${call.mxCall.callId}")
val currentCall = getCurrentCall().takeIf { it != call }
currentCall?.updateRemoteOnHold(onHold = true)
audioManager.setMode(if (call.mxCall.isVideoCall) CallAudioManager.Mode.VIDEO_CALL else CallAudioManager.Mode.AUDIO_CALL)
this.currentCall.setAndNotify(call)
}
- private fun onCallEnded(callId: String) {
- Timber.v("## VOIP WebRtcPeerConnectionManager onCall ended: $callId")
+ private fun onCallEnded(callId: String, endCallReason: EndCallReason, rejected: Boolean) {
+ Timber.tag(loggerTag.value).v("onCall ended: $callId")
val webRtcCall = callsByCallId.remove(callId) ?: return Unit.also {
- Timber.v("On call ended for unknown call $callId")
+ Timber.tag(loggerTag.value).v("On call ended for unknown call $callId")
}
- CallService.onCallTerminated(context, callId)
+ CallService.onCallTerminated(context, callId, endCallReason, rejected)
callsByRoomId[webRtcCall.signalingRoomId]?.remove(webRtcCall)
callsByRoomId[webRtcCall.nativeRoomId]?.remove(webRtcCall)
transferees.remove(callId)
@@ -247,7 +256,7 @@ class WebRtcCallManager @Inject constructor(
}
// There is no active calls
if (getCurrentCall() == null) {
- Timber.v("## VOIP Dispose peerConnectionFactory as there is no need to keep one")
+ Timber.tag(loggerTag.value).v("Dispose peerConnectionFactory as there is no need to keep one")
peerConnectionFactory?.dispose()
peerConnectionFactory = null
audioManager.setMode(CallAudioManager.Mode.DEFAULT)
@@ -265,13 +274,13 @@ class WebRtcCallManager @Inject constructor(
suspend fun startOutgoingCall(nativeRoomId: String, otherUserId: String, isVideoCall: Boolean, transferee: WebRtcCall? = null) {
val signalingRoomId = callUserMapper?.getOrCreateVirtualRoomForRoom(nativeRoomId, otherUserId) ?: nativeRoomId
- Timber.v("## VOIP startOutgoingCall in room $signalingRoomId to $otherUserId isVideo $isVideoCall")
+ Timber.tag(loggerTag.value).v("startOutgoingCall in room $signalingRoomId to $otherUserId isVideo $isVideoCall")
if (getCallsByRoomId(nativeRoomId).isNotEmpty()) {
- Timber.w("## VOIP you already have a call in this room")
+ Timber.tag(loggerTag.value).w("you already have a call in this room")
return
}
if (getCurrentCall() != null && getCurrentCall()?.mxCall?.state !is CallState.Connected || getCalls().size >= 2) {
- Timber.w("## VOIP cannot start outgoing call")
+ Timber.tag(loggerTag.value).w("cannot start outgoing call")
// Just ignore, maybe we could answer from other session?
return
}
@@ -294,10 +303,10 @@ class WebRtcCallManager @Inject constructor(
}
override fun onCallIceCandidateReceived(mxCall: MxCall, iceCandidatesContent: CallCandidatesContent) {
- Timber.v("## VOIP onCallIceCandidateReceived for call ${mxCall.callId}")
+ Timber.tag(loggerTag.value).v("onCallIceCandidateReceived for call ${mxCall.callId}")
val call = callsByCallId[iceCandidatesContent.callId]
?: return Unit.also {
- Timber.w("onCallIceCandidateReceived for non active call? ${iceCandidatesContent.callId}")
+ Timber.tag(loggerTag.value).w("onCallIceCandidateReceived for non active call? ${iceCandidatesContent.callId}")
}
call.onCallIceCandidateReceived(iceCandidatesContent)
}
@@ -329,19 +338,19 @@ class WebRtcCallManager @Inject constructor(
return webRtcCall
}
- fun endCallForRoom(roomId: String, originatedByMe: Boolean = true) {
- callsByRoomId[roomId]?.firstOrNull()?.endCall(originatedByMe)
+ fun endCallForRoom(roomId: String) {
+ callsByRoomId[roomId]?.firstOrNull()?.endCall()
}
override fun onCallInviteReceived(mxCall: MxCall, callInviteContent: CallInviteContent) {
- Timber.v("## VOIP onCallInviteReceived callId ${mxCall.callId}")
+ Timber.tag(loggerTag.value).v("onCallInviteReceived callId ${mxCall.callId}")
val nativeRoomId = callUserMapper?.nativeRoomForVirtualRoom(mxCall.roomId) ?: mxCall.roomId
if (getCallsByRoomId(nativeRoomId).isNotEmpty()) {
- Timber.w("## VOIP you already have a call in this room")
+ Timber.tag(loggerTag.value).w("you already have a call in this room")
return
}
if ((getCurrentCall() != null && getCurrentCall()?.mxCall?.state !is CallState.Connected) || getCalls().size >= 2) {
- Timber.w("## VOIP receiving incoming call but cannot handle it")
+ Timber.tag(loggerTag.value).w("receiving incoming call but cannot handle it")
// Just ignore, maybe we could answer from other session?
return
}
@@ -370,7 +379,7 @@ class WebRtcCallManager @Inject constructor(
override fun onCallAnswerReceived(callAnswerContent: CallAnswerContent) {
val call = callsByCallId[callAnswerContent.callId]
?: return Unit.also {
- Timber.w("onCallAnswerReceived for non active call? ${callAnswerContent.callId}")
+ Timber.tag(loggerTag.value).w("onCallAnswerReceived for non active call? ${callAnswerContent.callId}")
}
val mxCall = call.mxCall
// Update service state
@@ -384,43 +393,38 @@ class WebRtcCallManager @Inject constructor(
override fun onCallHangupReceived(callHangupContent: CallHangupContent) {
val call = callsByCallId[callHangupContent.callId]
?: return Unit.also {
- Timber.w("onCallHangupReceived for non active call? ${callHangupContent.callId}")
+ Timber.tag(loggerTag.value).w("onCallHangupReceived for non active call? ${callHangupContent.callId}")
}
- call.endCall(false)
+ call.onCallHangupReceived(callHangupContent)
}
override fun onCallRejectReceived(callRejectContent: CallRejectContent) {
val call = callsByCallId[callRejectContent.callId]
?: return Unit.also {
- Timber.w("onCallRejectReceived for non active call? ${callRejectContent.callId}")
+ Timber.tag(loggerTag.value).w("onCallRejectReceived for non active call? ${callRejectContent.callId}")
}
- call.endCall(false)
+ call.onCallRejectReceived(callRejectContent)
}
override fun onCallSelectAnswerReceived(callSelectAnswerContent: CallSelectAnswerContent) {
val call = callsByCallId[callSelectAnswerContent.callId]
?: return Unit.also {
- Timber.w("onCallSelectAnswerReceived for non active call? ${callSelectAnswerContent.callId}")
+ Timber.tag(loggerTag.value).w("onCallSelectAnswerReceived for non active call? ${callSelectAnswerContent.callId}")
}
- val selectedPartyId = callSelectAnswerContent.selectedPartyId
- if (selectedPartyId != call.mxCall.ourPartyId) {
- Timber.i("Got select_answer for party ID $selectedPartyId: we are party ID ${call.mxCall.ourPartyId}.")
- // The other party has picked somebody else's answer
- call.endCall(false)
- }
+ call.onCallSelectedAnswerReceived(callSelectAnswerContent)
}
override fun onCallNegotiateReceived(callNegotiateContent: CallNegotiateContent) {
val call = callsByCallId[callNegotiateContent.callId]
?: return Unit.also {
- Timber.w("onCallNegotiateReceived for non active call? ${callNegotiateContent.callId}")
+ Timber.tag(loggerTag.value).w("onCallNegotiateReceived for non active call? ${callNegotiateContent.callId}")
}
call.onCallNegotiateReceived(callNegotiateContent)
}
override fun onCallManagedByOtherSession(callId: String) {
- Timber.v("## VOIP onCallManagedByOtherSession: $callId")
- onCallEnded(callId)
+ Timber.tag(loggerTag.value).v("onCallManagedByOtherSession: $callId")
+ onCallEnded(callId, EndCallReason.ANSWERED_ELSEWHERE, false)
}
override fun onCallAssertedIdentityReceived(callAssertedIdentityContent: CallAssertedIdentityContent) {
@@ -429,8 +433,8 @@ class WebRtcCallManager @Inject constructor(
}
val call = callsByCallId[callAssertedIdentityContent.callId]
?: return Unit.also {
- Timber.w("onCallAssertedIdentityReceived for non active call? ${callAssertedIdentityContent.callId}")
+ Timber.tag(loggerTag.value).w("onCallAssertedIdentityReceived for non active call? ${callAssertedIdentityContent.callId}")
}
- call.onCallAssertedIdentityReceived(callAssertedIdentityContent)
+ call.onCallAssertedIdentityReceived(callAssertedIdentityContent)
}
}
diff --git a/vector/src/main/java/im/vector/app/features/command/Command.kt b/vector/src/main/java/im/vector/app/features/command/Command.kt
index 61d39857cc..3719618d31 100644
--- a/vector/src/main/java/im/vector/app/features/command/Command.kt
+++ b/vector/src/main/java/im/vector/app/features/command/Command.kt
@@ -50,7 +50,8 @@ enum class Command(val command: String, val parameters: String, @StringRes val d
CREATE_SPACE("/createspace", "*", R.string.command_description_create_space, true),
ADD_TO_SPACE("/addToSpace", "spaceId", R.string.command_description_create_space, true),
JOIN_SPACE("/joinSpace", "spaceId", R.string.command_description_join_space, true),
- LEAVE_ROOM("/leave", "", R.string.command_description_leave_room, true);
+ LEAVE_ROOM("/leave", "", R.string.command_description_leave_room, true),
+ UPGRADE_ROOM("/upgraderoom", "newVersion", R.string.command_description_upgrade_room, true);
val length
get() = command.length + 1
diff --git a/vector/src/main/java/im/vector/app/features/command/CommandParser.kt b/vector/src/main/java/im/vector/app/features/command/CommandParser.kt
index 3de00f4d0c..adba6e4a18 100644
--- a/vector/src/main/java/im/vector/app/features/command/CommandParser.kt
+++ b/vector/src/main/java/im/vector/app/features/command/CommandParser.kt
@@ -312,24 +312,32 @@ object CommandParser {
)
}
}
- Command.ADD_TO_SPACE.command -> {
+ Command.ADD_TO_SPACE.command -> {
val rawCommand = textMessage.substring(Command.ADD_TO_SPACE.command.length).trim()
ParsedCommand.AddToSpace(
rawCommand
)
}
- Command.JOIN_SPACE.command -> {
+ Command.JOIN_SPACE.command -> {
val spaceIdOrAlias = textMessage.substring(Command.JOIN_SPACE.command.length).trim()
ParsedCommand.JoinSpace(
spaceIdOrAlias
)
}
- Command.LEAVE_ROOM.command -> {
+ Command.LEAVE_ROOM.command -> {
val spaceIdOrAlias = textMessage.substring(Command.LEAVE_ROOM.command.length).trim()
ParsedCommand.LeaveRoom(
spaceIdOrAlias
)
}
+ Command.UPGRADE_ROOM.command -> {
+ val newVersion = textMessage.substring(Command.UPGRADE_ROOM.command.length).trim()
+ if (newVersion.isEmpty()) {
+ ParsedCommand.ErrorSyntax(Command.UPGRADE_ROOM)
+ } else {
+ ParsedCommand.UpgradeRoom(newVersion)
+ }
+ }
else -> {
// Unknown command
ParsedCommand.ErrorUnknownSlashCommand(slashCommand)
diff --git a/vector/src/main/java/im/vector/app/features/command/ParsedCommand.kt b/vector/src/main/java/im/vector/app/features/command/ParsedCommand.kt
index d67caac60a..123f1d3a36 100644
--- a/vector/src/main/java/im/vector/app/features/command/ParsedCommand.kt
+++ b/vector/src/main/java/im/vector/app/features/command/ParsedCommand.kt
@@ -61,4 +61,5 @@ sealed class ParsedCommand {
class AddToSpace(val spaceId: String) : ParsedCommand()
class JoinSpace(val spaceIdOrAlias: String) : ParsedCommand()
class LeaveRoom(val roomId: String) : ParsedCommand()
+ class UpgradeRoom(val newVersion: String) : ParsedCommand()
}
diff --git a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt
index 4aa5f023c4..68123d5e82 100644
--- a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt
+++ b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt
@@ -38,11 +38,9 @@ import im.vector.app.core.platform.SimpleFragmentActivity
import im.vector.app.core.platform.WaitingViewData
import im.vector.app.core.utils.PERMISSIONS_FOR_MEMBERS_SEARCH
import im.vector.app.core.utils.PERMISSIONS_FOR_TAKING_PHOTO
-import im.vector.app.core.utils.PERMISSION_REQUEST_CODE_LAUNCH_CAMERA
-import im.vector.app.core.utils.PERMISSION_REQUEST_CODE_READ_CONTACTS
-import im.vector.app.core.utils.allGranted
import im.vector.app.core.utils.checkPermissions
import im.vector.app.core.utils.onPermissionDeniedSnackbar
+import im.vector.app.core.utils.registerForPermissionsResult
import im.vector.app.features.contactsbook.ContactsBookFragment
import im.vector.app.features.contactsbook.ContactsBookViewModel
import im.vector.app.features.contactsbook.ContactsBookViewState
@@ -52,7 +50,6 @@ import im.vector.app.features.userdirectory.UserListSharedAction
import im.vector.app.features.userdirectory.UserListSharedActionViewModel
import im.vector.app.features.userdirectory.UserListViewModel
import im.vector.app.features.userdirectory.UserListViewState
-
import org.matrix.android.sdk.api.failure.Failure
import org.matrix.android.sdk.api.session.room.failure.CreateRoomFailure
import java.net.HttpURLConnection
@@ -111,35 +108,31 @@ class CreateDirectRoomActivity : SimpleFragmentActivity(), UserListViewModel.Fac
}
private fun openAddByQrCode() {
- if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, this, PERMISSION_REQUEST_CODE_LAUNCH_CAMERA, 0)) {
+ if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, this, permissionCameraLauncher)) {
addFragment(R.id.container, CreateDirectRoomByQrCodeFragment::class.java)
}
}
private fun openPhoneBook() {
// Check permission first
- if (checkPermissions(PERMISSIONS_FOR_MEMBERS_SEARCH,
- this,
- PERMISSION_REQUEST_CODE_READ_CONTACTS,
- 0)) {
+ if (checkPermissions(PERMISSIONS_FOR_MEMBERS_SEARCH, this, permissionReadContactLauncher)) {
addFragmentToBackstack(R.id.container, ContactsBookFragment::class.java)
}
}
- override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) {
- super.onRequestPermissionsResult(requestCode, permissions, grantResults)
- if (allGranted(grantResults)) {
- if (requestCode == PERMISSION_REQUEST_CODE_READ_CONTACTS) {
- doOnPostResume { addFragmentToBackstack(R.id.container, ContactsBookFragment::class.java) }
- } else if (requestCode == PERMISSION_REQUEST_CODE_LAUNCH_CAMERA) {
- addFragment(R.id.container, CreateDirectRoomByQrCodeFragment::class.java)
- }
- } else {
- if (requestCode == PERMISSION_REQUEST_CODE_LAUNCH_CAMERA) {
- onPermissionDeniedSnackbar(R.string.permissions_denied_qr_code)
- } else if (requestCode == PERMISSION_REQUEST_CODE_READ_CONTACTS) {
- onPermissionDeniedSnackbar(R.string.permissions_denied_add_contact)
- }
+ private val permissionReadContactLauncher = registerForPermissionsResult { allGranted, deniedPermanently ->
+ if (allGranted) {
+ doOnPostResume { addFragmentToBackstack(R.id.container, ContactsBookFragment::class.java) }
+ } else if (deniedPermanently) {
+ onPermissionDeniedSnackbar(R.string.permissions_denied_add_contact)
+ }
+ }
+
+ private val permissionCameraLauncher = registerForPermissionsResult { allGranted, deniedPermanently ->
+ if (allGranted) {
+ addFragment(R.id.container, CreateDirectRoomByQrCodeFragment::class.java)
+ } else if (deniedPermanently) {
+ onPermissionDeniedSnackbar(R.string.permissions_denied_qr_code)
}
}
diff --git a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomByQrCodeFragment.kt b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomByQrCodeFragment.kt
index 92a03c5483..8da0147a43 100644
--- a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomByQrCodeFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomByQrCodeFragment.kt
@@ -27,6 +27,7 @@ import im.vector.app.core.extensions.hideKeyboard
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.core.utils.PERMISSIONS_FOR_TAKING_PHOTO
import im.vector.app.core.utils.checkPermissions
+import im.vector.app.core.utils.onPermissionDeniedDialog
import im.vector.app.core.utils.registerForPermissionsResult
import im.vector.app.databinding.FragmentQrCodeScannerBinding
import im.vector.app.features.userdirectory.PendingSelection
@@ -44,9 +45,11 @@ class CreateDirectRoomByQrCodeFragment @Inject constructor() : VectorBaseFragmen
return FragmentQrCodeScannerBinding.inflate(inflater, container, false)
}
- private val openCameraActivityResultLauncher = registerForPermissionsResult { allGranted ->
+ private val openCameraActivityResultLauncher = registerForPermissionsResult { allGranted, deniedPermanently ->
if (allGranted) {
startCamera()
+ } else if (deniedPermanently) {
+ activity?.onPermissionDeniedDialog(R.string.denied_permission_camera)
}
}
diff --git a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomViewModel.kt b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomViewModel.kt
index 2f0b6e5ec9..88e8d68155 100644
--- a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomViewModel.kt
@@ -86,7 +86,7 @@ class CreateDirectRoomViewModel @AssistedInject constructor(@Assisted
setState { copy(createAndInviteState = Loading()) }
viewModelScope.launch(Dispatchers.IO) {
- val adminE2EByDefault = rawService.getElementWellknown(session.myUserId)
+ val adminE2EByDefault = rawService.getElementWellknown(session.sessionParams)
?.isE2EByDefault()
?: true
diff --git a/vector/src/main/java/im/vector/app/features/createdirect/DirectRoomHelper.kt b/vector/src/main/java/im/vector/app/features/createdirect/DirectRoomHelper.kt
index bfa56cfb9e..cf75bdf1b6 100644
--- a/vector/src/main/java/im/vector/app/features/createdirect/DirectRoomHelper.kt
+++ b/vector/src/main/java/im/vector/app/features/createdirect/DirectRoomHelper.kt
@@ -35,7 +35,7 @@ class DirectRoomHelper @Inject constructor(
if (existingRoomId != null) {
roomId = existingRoomId
} else {
- val adminE2EByDefault = rawService.getElementWellknown(session.myUserId)
+ val adminE2EByDefault = rawService.getElementWellknown(session.sessionParams)
?.isE2EByDefault()
?: true
diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/choose/VerificationChooseMethodController.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/choose/VerificationChooseMethodController.kt
index 1202a22042..7025343fc6 100644
--- a/vector/src/main/java/im/vector/app/features/crypto/verification/choose/VerificationChooseMethodController.kt
+++ b/vector/src/main/java/im/vector/app/features/crypto/verification/choose/VerificationChooseMethodController.kt
@@ -45,9 +45,22 @@ class VerificationChooseMethodController @Inject constructor(
val host = this
if (state.otherCanScanQrCode || state.otherCanShowQrCode) {
+ val scanCodeInstructions: String
+ val scanOtherCodeTitle: String
+ val compareEmojiSubtitle: String
+ if (state.isMe) {
+ scanCodeInstructions = stringProvider.getString(R.string.verification_scan_self_notice)
+ scanOtherCodeTitle = stringProvider.getString(R.string.verification_scan_with_this_device)
+ compareEmojiSubtitle = stringProvider.getString(R.string.verification_scan_self_emoji_subtitle)
+ } else {
+ scanCodeInstructions = stringProvider.getString(R.string.verification_scan_notice)
+ scanOtherCodeTitle = stringProvider.getString(R.string.verification_scan_their_code)
+ compareEmojiSubtitle = stringProvider.getString(R.string.verification_scan_emoji_subtitle)
+ }
+
bottomSheetVerificationNoticeItem {
id("notice")
- notice(host.stringProvider.getString(R.string.verification_scan_notice))
+ notice(scanCodeInstructions)
}
if (state.otherCanScanQrCode && !state.qrCodeText.isNullOrBlank()) {
@@ -64,7 +77,7 @@ class VerificationChooseMethodController @Inject constructor(
if (state.otherCanShowQrCode) {
bottomSheetVerificationActionItem {
id("openCamera")
- title(host.stringProvider.getString(R.string.verification_scan_their_code))
+ title(scanOtherCodeTitle)
titleColor(host.colorProvider.getColorFromAttribute(R.attr.colorPrimary))
iconRes(R.drawable.ic_camera)
iconColor(host.colorProvider.getColorFromAttribute(R.attr.colorPrimary))
@@ -80,7 +93,7 @@ class VerificationChooseMethodController @Inject constructor(
id("openEmoji")
title(host.stringProvider.getString(R.string.verification_scan_emoji_title))
titleColor(host.colorProvider.getColorFromAttribute(R.attr.vctr_content_primary))
- subTitle(host.stringProvider.getString(R.string.verification_scan_emoji_subtitle))
+ subTitle(compareEmojiSubtitle)
iconRes(R.drawable.ic_arrow_right)
iconColor(host.colorProvider.getColorFromAttribute(R.attr.vctr_content_primary))
listener { host.listener?.doVerifyBySas() }
diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/choose/VerificationChooseMethodFragment.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/choose/VerificationChooseMethodFragment.kt
index 5d114b26bf..d3f24816a5 100644
--- a/vector/src/main/java/im/vector/app/features/crypto/verification/choose/VerificationChooseMethodFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/crypto/verification/choose/VerificationChooseMethodFragment.kt
@@ -23,12 +23,14 @@ import android.view.ViewGroup
import com.airbnb.mvrx.fragmentViewModel
import com.airbnb.mvrx.parentFragmentViewModel
import com.airbnb.mvrx.withState
+import im.vector.app.R
import im.vector.app.core.extensions.cleanup
import im.vector.app.core.extensions.configureWith
import im.vector.app.core.extensions.registerStartForActivityResult
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.core.utils.PERMISSIONS_FOR_TAKING_PHOTO
import im.vector.app.core.utils.checkPermissions
+import im.vector.app.core.utils.onPermissionDeniedDialog
import im.vector.app.core.utils.registerForPermissionsResult
import im.vector.app.databinding.BottomSheetVerificationChildFragmentBinding
import im.vector.app.features.crypto.verification.VerificationAction
@@ -79,9 +81,11 @@ class VerificationChooseMethodFragment @Inject constructor(
state.pendingRequest.invoke()?.transactionId ?: ""))
}
- private val openCameraActivityResultLauncher = registerForPermissionsResult { allGranted ->
+ private val openCameraActivityResultLauncher = registerForPermissionsResult { allGranted, deniedPermanently ->
if (allGranted) {
doOpenQRCodeScanner()
+ } else if (deniedPermanently) {
+ activity?.onPermissionDeniedDialog(R.string.denied_permission_camera)
}
}
diff --git a/vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt
index dda3df3881..13809bcef3 100644
--- a/vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt
@@ -327,7 +327,7 @@ class HomeDetailFragment @Inject constructor(
private fun setupBottomNavigationView() {
views.bottomNavigationView.menu.findItem(R.id.bottom_action_notification).isVisible = vectorPreferences.labAddNotificationTab()
- views.bottomNavigationView.setOnNavigationItemSelectedListener {
+ views.bottomNavigationView.setOnItemSelectedListener {
val tab = when (it.itemId) {
R.id.bottom_action_people -> HomeTab.RoomList(RoomListDisplayMode.PEOPLE)
R.id.bottom_action_rooms -> HomeTab.RoomList(RoomListDisplayMode.ROOMS)
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/JoinReplacementRoomBottomSheet.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/JoinReplacementRoomBottomSheet.kt
new file mode 100644
index 0000000000..54681366e0
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/JoinReplacementRoomBottomSheet.kt
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2021 New Vector Ltd
+ *
+ * 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 im.vector.app.features.home.room.detail
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import com.airbnb.mvrx.Fail
+import com.airbnb.mvrx.Loading
+import com.airbnb.mvrx.Success
+import com.airbnb.mvrx.Uninitialized
+import com.airbnb.mvrx.parentFragmentViewModel
+import im.vector.app.R
+import im.vector.app.core.di.ScreenComponent
+import im.vector.app.core.epoxy.ClickListener
+import im.vector.app.core.error.ErrorFormatter
+import im.vector.app.core.platform.ButtonStateView
+import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment
+import im.vector.app.databinding.BottomSheetTombstoneJoinBinding
+import javax.inject.Inject
+
+class JoinReplacementRoomBottomSheet :
+ VectorBaseBottomSheetDialogFragment() {
+
+ override fun getBinding(inflater: LayoutInflater, container: ViewGroup?) =
+ BottomSheetTombstoneJoinBinding.inflate(inflater, container, false)
+
+ @Inject
+ lateinit var errorFormatter: ErrorFormatter
+
+ override fun injectWith(injector: ScreenComponent) {
+ injector.inject(this)
+ }
+
+ private val viewModel: RoomDetailViewModel by parentFragmentViewModel()
+
+ override val showExpanded: Boolean
+ get() = true
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ views.roomUpgradeButton.retryClicked = object : ClickListener {
+ override fun invoke(view: View) {
+ viewModel.handle(RoomDetailAction.JoinAndOpenReplacementRoom)
+ }
+ }
+
+ viewModel.selectSubscribe(this, RoomDetailViewState::joinUpgradedRoomAsync) { joinState ->
+ when (joinState) {
+ // it should never be Uninitialized
+ Uninitialized,
+ is Loading -> {
+ views.roomUpgradeButton.render(ButtonStateView.State.Loading)
+ views.descriptionText.setText(R.string.it_may_take_some_time)
+ }
+ is Success -> {
+ views.roomUpgradeButton.render(ButtonStateView.State.Loaded)
+ dismiss()
+ }
+ is Fail -> {
+ // display the error message
+ views.descriptionText.text = errorFormatter.toHumanReadable(joinState.error)
+ views.roomUpgradeButton.render(ButtonStateView.State.Error)
+ }
+ }
+ }
+ }
+}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailAction.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailAction.kt
index c0e73823e4..8dfb0bdcde 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailAction.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailAction.kt
@@ -20,7 +20,6 @@ import android.net.Uri
import android.view.View
import im.vector.app.core.platform.VectorViewModelAction
import org.matrix.android.sdk.api.session.content.ContentAttachmentData
-import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.room.model.message.MessageStickerContent
import org.matrix.android.sdk.api.session.room.model.message.MessageWithAttachmentContent
import org.matrix.android.sdk.api.session.room.timeline.Timeline
@@ -44,7 +43,7 @@ sealed class RoomDetailAction : VectorViewModelAction {
data class NavigateToEvent(val eventId: String, val highlight: Boolean) : RoomDetailAction()
object MarkAllAsRead : RoomDetailAction()
data class DownloadOrOpen(val eventId: String, val senderId: String?, val messageFileContent: MessageWithAttachmentContent) : RoomDetailAction()
- data class HandleTombstoneEvent(val event: Event) : RoomDetailAction()
+ object JoinAndOpenReplacementRoom : RoomDetailAction()
object AcceptInvite : RoomDetailAction()
object RejectInvite : RoomDetailAction()
@@ -108,4 +107,5 @@ sealed class RoomDetailAction : VectorViewModelAction {
// Failed messages
object RemoveAllFailedMessages : RoomDetailAction()
+ data class RoomUpgradeSuccess(val replacementRoomId: String): RoomDetailAction()
}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt
index 3d99dc785f..b88a1a6e3a 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt
@@ -50,6 +50,7 @@ import androidx.core.view.ViewCompat
import androidx.core.view.forEach
import androidx.core.view.isInvisible
import androidx.core.view.isVisible
+import androidx.fragment.app.setFragmentResultListener
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager
@@ -59,11 +60,7 @@ import com.airbnb.epoxy.EpoxyModel
import com.airbnb.epoxy.OnModelBuildFinishedListener
import com.airbnb.epoxy.addGlidePreloader
import com.airbnb.epoxy.glidePreloader
-import com.airbnb.mvrx.Async
-import com.airbnb.mvrx.Fail
-import com.airbnb.mvrx.Loading
import com.airbnb.mvrx.MvRx
-import com.airbnb.mvrx.Success
import com.airbnb.mvrx.args
import com.airbnb.mvrx.fragmentViewModel
import com.airbnb.mvrx.withState
@@ -104,6 +101,7 @@ import im.vector.app.core.utils.copyToClipboard
import im.vector.app.core.utils.createJSonViewerStyleProvider
import im.vector.app.core.utils.createUIHandler
import im.vector.app.core.utils.isValidUrl
+import im.vector.app.core.utils.onPermissionDeniedDialog
import im.vector.app.core.utils.openUrlInExternalBrowser
import im.vector.app.core.utils.registerForPermissionsResult
import im.vector.app.core.utils.saveMedia
@@ -144,6 +142,7 @@ import im.vector.app.features.home.room.detail.timeline.item.MessageTextItem
import im.vector.app.features.home.room.detail.timeline.item.ReadReceiptData
import im.vector.app.features.home.room.detail.timeline.reactions.ViewReactionsBottomSheet
import im.vector.app.features.home.room.detail.timeline.url.PreviewUrlRetriever
+import im.vector.app.features.home.room.detail.upgrade.MigrateRoomBottomSheet
import im.vector.app.features.home.room.detail.widget.RoomWidgetsBottomSheet
import im.vector.app.features.html.EventHtmlRenderer
import im.vector.app.features.html.PillImageSpan
@@ -177,7 +176,6 @@ import org.billcarsonfr.jsonviewer.JSonViewerDialog
import org.commonmark.parser.Parser
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.content.ContentAttachmentData
-import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.api.session.room.model.RoomSummary
@@ -302,6 +300,15 @@ class RoomDetailFragment @Inject constructor(
private lateinit var emojiPopup: EmojiPopup
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setFragmentResultListener(MigrateRoomBottomSheet.REQUEST_KEY) { _, bundle ->
+ bundle.getString(MigrateRoomBottomSheet.BUNDLE_KEY_REPLACEMENT_ROOM)?.let { replacementRoomId ->
+ roomDetailViewModel.handle(RoomDetailAction.RoomUpgradeSuccess(replacementRoomId))
+ }
+ }
+ }
+
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
sharedActionViewModel = activityViewModelProvider.get(MessageSharedActionViewModel::class.java)
@@ -348,10 +355,6 @@ class RoomDetailFragment @Inject constructor(
invalidateOptionsMenu()
})
- roomDetailViewModel.selectSubscribe(this, RoomDetailViewState::tombstoneEventHandling, uniqueOnly("tombstoneEventHandling")) {
- renderTombstoneEventHandling(it)
- }
-
roomDetailViewModel.selectSubscribe(RoomDetailViewState::canShowJumpToReadMarker, RoomDetailViewState::unreadState) { _, _ ->
updateJumpToReadMarkerViewVisibility()
}
@@ -405,6 +408,8 @@ class RoomDetailFragment @Inject constructor(
is RoomDetailViewEvents.StartChatEffect -> handleChatEffect(it.type)
RoomDetailViewEvents.StopChatEffects -> handleStopChatEffects()
is RoomDetailViewEvents.DisplayAndAcceptCall -> acceptIncomingCall(it)
+ RoomDetailViewEvents.RoomReplacementStarted -> handleRoomReplacement()
+ is RoomDetailViewEvents.ShowRoomUpgradeDialog -> handleShowRoomUpgradeDialog(it)
}.exhaustive
}
@@ -423,6 +428,19 @@ class RoomDetailFragment @Inject constructor(
startActivity(intent)
}
+ private fun handleRoomReplacement() {
+ // this will join a new room, it can take time and might fail
+ // so we need to report progress and retry
+ val tag = JoinReplacementRoomBottomSheet::javaClass.name
+ JoinReplacementRoomBottomSheet().show(childFragmentManager, tag)
+ }
+
+ private fun handleShowRoomUpgradeDialog(roomDetailViewEvents: RoomDetailViewEvents.ShowRoomUpgradeDialog) {
+ val tag = MigrateRoomBottomSheet::javaClass.name
+ MigrateRoomBottomSheet.newInstance(roomDetailArgs.roomId, roomDetailViewEvents.newVersion)
+ .show(parentFragmentManager, tag)
+ }
+
private fun handleChatEffect(chatEffect: ChatEffect) {
when (chatEffect) {
ChatEffect.CONFETTI -> {
@@ -472,6 +490,9 @@ class RoomDetailFragment @Inject constructor(
private fun handleOpenRoom(openRoom: RoomDetailViewEvents.OpenRoom) {
navigator.openRoom(requireContext(), openRoom.roomId, null)
+ if (openRoom.closeCurrentRoom) {
+ requireActivity().finish()
+ }
}
private fun requestNativeWidgetPermission(it: RoomDetailViewEvents.RequestNativeWidgetPermission) {
@@ -776,8 +797,8 @@ class RoomDetailFragment @Inject constructor(
private fun setupNotificationView() {
views.notificationAreaView.delegate = object : NotificationAreaView.Delegate {
- override fun onTombstoneEventClicked(tombstoneEvent: Event) {
- roomDetailViewModel.handle(RoomDetailAction.HandleTombstoneEvent(tombstoneEvent))
+ override fun onTombstoneEventClicked() {
+ roomDetailViewModel.handle(RoomDetailAction.JoinAndOpenReplacementRoom)
}
}
}
@@ -964,6 +985,8 @@ class RoomDetailFragment @Inject constructor(
insertUserDisplayNameInTextEditor(roomDetailPendingAction.userId)
is RoomDetailPendingAction.OpenOrCreateDm ->
roomDetailViewModel.handle(RoomDetailAction.OpenOrCreateDm(roomDetailPendingAction.userId))
+ is RoomDetailPendingAction.OpenRoom ->
+ handleOpenRoom(RoomDetailViewEvents.OpenRoom(roomDetailPendingAction.roomId, roomDetailPendingAction.closeCurrentRoom))
}.exhaustive
}
@@ -1040,14 +1063,16 @@ class RoomDetailFragment @Inject constructor(
}
}
- private val startCallActivityResultLauncher = registerForPermissionsResult { allGranted ->
+ private val startCallActivityResultLauncher = registerForPermissionsResult { allGranted, deniedPermanently ->
if (allGranted) {
(roomDetailViewModel.pendingAction as? RoomDetailAction.StartCall)?.let {
roomDetailViewModel.pendingAction = null
roomDetailViewModel.handle(it)
}
} else {
- context?.toast(R.string.permissions_action_not_performed_missing_permissions)
+ if (deniedPermanently) {
+ activity?.onPermissionDeniedDialog(R.string.denied_permission_generic)
+ }
cleanUpAfterPermissionNotGranted()
}
}
@@ -1312,23 +1337,6 @@ class RoomDetailFragment @Inject constructor(
}
}
- private fun renderTombstoneEventHandling(async: Async) {
- when (async) {
- is Loading -> {
- // TODO Better handling progress
- vectorBaseActivity.showWaitingView(getString(R.string.joining_room))
- }
- is Success -> {
- navigator.openRoom(vectorBaseActivity, async())
- vectorBaseActivity.finish()
- }
- is Fail -> {
- vectorBaseActivity.hideWaitingView()
- vectorBaseActivity.toast(errorFormatter.toHumanReadable(async.error))
- }
- }
- }
-
private fun renderSendMessageResult(sendMessageResult: RoomDetailViewEvents.SendMessageResult) {
when (sendMessageResult) {
is RoomDetailViewEvents.SlashCommandHandled -> {
@@ -1733,13 +1741,16 @@ class RoomDetailFragment @Inject constructor(
}
}
- private val saveActionActivityResultLauncher = registerForPermissionsResult { allGranted ->
+ private val saveActionActivityResultLauncher = registerForPermissionsResult { allGranted, deniedPermanently ->
if (allGranted) {
sharedActionViewModel.pendingAction?.let {
handleActions(it)
sharedActionViewModel.pendingAction = null
}
} else {
+ if (deniedPermanently) {
+ activity?.onPermissionDeniedDialog(R.string.denied_permission_generic)
+ }
cleanUpAfterPermissionNotGranted()
}
}
@@ -1972,7 +1983,7 @@ class RoomDetailFragment @Inject constructor(
// AttachmentTypeSelectorView.Callback
- private val typeSelectedActivityResultLauncher = registerForPermissionsResult { allGranted ->
+ private val typeSelectedActivityResultLauncher = registerForPermissionsResult { allGranted, deniedPermanently ->
if (allGranted) {
val pendingType = attachmentsHelper.pendingType
if (pendingType != null) {
@@ -1980,12 +1991,15 @@ class RoomDetailFragment @Inject constructor(
launchAttachmentProcess(pendingType)
}
} else {
+ if (deniedPermanently) {
+ activity?.onPermissionDeniedDialog(R.string.denied_permission_generic)
+ }
cleanUpAfterPermissionNotGranted()
}
}
override fun onTypeSelected(type: AttachmentTypeSelectorView.Type) {
- if (checkPermissions(type.permissionsBit, requireActivity(), typeSelectedActivityResultLauncher)) {
+ if (checkPermissions(type.permissions, requireActivity(), typeSelectedActivityResultLauncher)) {
launchAttachmentProcess(type)
} else {
attachmentsHelper.pendingType = type
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailPendingAction.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailPendingAction.kt
index 598ab9d056..fccab500c5 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailPendingAction.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailPendingAction.kt
@@ -20,4 +20,5 @@ sealed class RoomDetailPendingAction {
data class OpenOrCreateDm(val userId: String) : RoomDetailPendingAction()
data class JumpToReadReceipt(val userId: String) : RoomDetailPendingAction()
data class MentionUser(val userId: String) : RoomDetailPendingAction()
+ data class OpenRoom(val roomId: String, val closeCurrentRoom: Boolean = false) : RoomDetailPendingAction()
}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewEvents.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewEvents.kt
index 4d1e62da7e..d62c5f6003 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewEvents.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewEvents.kt
@@ -41,7 +41,7 @@ sealed class RoomDetailViewEvents : VectorViewEvents {
data class ShowInfoOkDialog(val message: String) : RoomDetailViewEvents()
data class ShowE2EErrorMessage(val withHeldCode: WithHeldCode?) : RoomDetailViewEvents()
- data class OpenRoom(val roomId: String) : RoomDetailViewEvents()
+ data class OpenRoom(val roomId: String, val closeCurrentRoom: Boolean = false) : RoomDetailViewEvents()
data class NavigateToEvent(val eventId: String) : RoomDetailViewEvents()
data class JoinJitsiConference(val widget: Widget, val withVideo: Boolean) : RoomDetailViewEvents()
@@ -94,4 +94,6 @@ sealed class RoomDetailViewEvents : VectorViewEvents {
data class StartChatEffect(val type: ChatEffect) : RoomDetailViewEvents()
object StopChatEffects : RoomDetailViewEvents()
+ object RoomReplacementStarted : RoomDetailViewEvents()
+ data class ShowRoomUpgradeDialog(val newVersion: String, val isPublic: Boolean): RoomDetailViewEvents()
}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt
index 2cb0f99c50..50ffd72444 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt
@@ -281,7 +281,7 @@ class RoomDetailViewModel @AssistedInject constructor(
is RoomDetailAction.EnterReplyMode -> handleReplyAction(action)
is RoomDetailAction.DownloadOrOpen -> handleOpenOrDownloadFile(action)
is RoomDetailAction.NavigateToEvent -> handleNavigateToEvent(action)
- is RoomDetailAction.HandleTombstoneEvent -> handleTombstoneEvent(action)
+ is RoomDetailAction.JoinAndOpenReplacementRoom -> handleJoinAndOpenReplacementRoom()
is RoomDetailAction.ResendMessage -> handleResendEvent(action)
is RoomDetailAction.RemoveFailedEcho -> handleRemove(action)
is RoomDetailAction.MarkAllAsRead -> handleMarkAllAsRead()
@@ -320,6 +320,12 @@ class RoomDetailViewModel @AssistedInject constructor(
is RoomDetailAction.DoNotShowPreviewUrlFor -> handleDoNotShowPreviewUrlFor(action)
RoomDetailAction.RemoveAllFailedMessages -> handleRemoveAllFailedMessages()
RoomDetailAction.ResendAll -> handleResendAll()
+ is RoomDetailAction.RoomUpgradeSuccess -> {
+ setState {
+ copy(joinUpgradedRoomAsync = Success(action.replacementRoomId))
+ }
+ _viewEvents.post(RoomDetailViewEvents.OpenRoom(action.replacementRoomId, closeCurrentRoom = true))
+ }
}.exhaustive
}
@@ -573,24 +579,33 @@ class RoomDetailViewModel @AssistedInject constructor(
}
}
- private fun handleTombstoneEvent(action: RoomDetailAction.HandleTombstoneEvent) {
- val tombstoneContent = action.event.getClearContent().toModel() ?: return
+ private fun handleJoinAndOpenReplacementRoom() = withState { state ->
+ val tombstoneContent = state.tombstoneEvent?.getClearContent()?.toModel() ?: return@withState
val roomId = tombstoneContent.replacementRoomId ?: ""
val isRoomJoined = session.getRoom(roomId)?.roomSummary()?.membership == Membership.JOIN
if (isRoomJoined) {
- setState { copy(tombstoneEventHandling = Success(roomId)) }
+ setState { copy(joinUpgradedRoomAsync = Success(roomId)) }
+ _viewEvents.post(RoomDetailViewEvents.OpenRoom(roomId, closeCurrentRoom = true))
} else {
- val viaServers = MatrixPatterns.extractServerNameFromId(action.event.senderId)
+ val viaServers = MatrixPatterns.extractServerNameFromId(state.tombstoneEvent.senderId)
?.let { listOf(it) }
.orEmpty()
+ // need to provide feedback as joining could take some time
+ _viewEvents.post(RoomDetailViewEvents.RoomReplacementStarted)
+ setState {
+ copy(joinUpgradedRoomAsync = Loading())
+ }
viewModelScope.launch {
val result = runCatchingToAsync {
session.joinRoom(roomId, viaServers = viaServers)
roomId
}
setState {
- copy(tombstoneEventHandling = result)
+ copy(joinUpgradedRoomAsync = result)
+ }
+ if (result is Success) {
+ _viewEvents.post(RoomDetailViewEvents.OpenRoom(roomId, closeCurrentRoom = true))
}
}
}
@@ -816,6 +831,16 @@ class RoomDetailViewModel @AssistedInject constructor(
_viewEvents.post(RoomDetailViewEvents.SlashCommandHandled())
popDraft()
}
+ is ParsedCommand.UpgradeRoom -> {
+ _viewEvents.post(
+ RoomDetailViewEvents.ShowRoomUpgradeDialog(
+ slashCommandResult.newVersion,
+ room.roomSummary()?.isPublic ?: false
+ )
+ )
+ _viewEvents.post(RoomDetailViewEvents.SlashCommandHandled())
+ popDraft()
+ }
}.exhaustive
}
is SendMode.EDIT -> {
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt
index 1ead34fadd..d10456b7c2 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewState.kt
@@ -65,7 +65,7 @@ data class RoomDetailViewState(
val typingMessage: String? = null,
val sendMode: SendMode = SendMode.REGULAR("", false),
val tombstoneEvent: Event? = null,
- val tombstoneEventHandling: Async = Uninitialized,
+ val joinUpgradedRoomAsync: Async = Uninitialized,
val syncState: SyncState = SyncState.Idle,
val highlightedEventId: String? = null,
val unreadState: UnreadState = UnreadState.Unknown,
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt
index a0f87b9749..95d7acb571 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt
@@ -39,13 +39,13 @@ import im.vector.app.features.home.room.detail.UnreadState
import im.vector.app.features.home.room.detail.timeline.factory.MergedHeaderItemFactory
import im.vector.app.features.home.room.detail.timeline.factory.ReadReceiptsItemFactory
import im.vector.app.features.home.room.detail.timeline.factory.TimelineItemFactory
+import im.vector.app.features.home.room.detail.timeline.factory.TimelineItemFactoryParams
import im.vector.app.features.home.room.detail.timeline.helper.ContentDownloadStateTrackerBinder
import im.vector.app.features.home.room.detail.timeline.helper.ContentUploadStateTrackerBinder
import im.vector.app.features.home.room.detail.timeline.helper.TimelineControllerInterceptorHelper
import im.vector.app.features.home.room.detail.timeline.helper.TimelineEventDiffUtilCallback
import im.vector.app.features.home.room.detail.timeline.helper.TimelineEventVisibilityHelper
import im.vector.app.features.home.room.detail.timeline.helper.TimelineEventVisibilityStateChangedListener
-import im.vector.app.features.home.room.detail.timeline.factory.TimelineItemFactoryParams
import im.vector.app.features.home.room.detail.timeline.helper.TimelineMediaSizeProvider
import im.vector.app.features.home.room.detail.timeline.item.AbsMessageItem
import im.vector.app.features.home.room.detail.timeline.item.BasedMergedItem
@@ -163,10 +163,19 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
override fun onChanged(position: Int, count: Int, payload: Any?) {
synchronized(modelCache) {
assertUpdateCallbacksAllowed()
- (position until (position + count)).forEach {
+ (position until position + count).forEach {
// Invalidate cache
modelCache[it] = null
}
+ // Also invalidate the first previous displayable event if
+ // it's sent by the same user so we are sure we have up to date information.
+ val invalidatedSenderId: String? = currentSnapshot.getOrNull(position)?.senderInfo?.userId
+ val prevDisplayableEventIndex = currentSnapshot.subList(0, position).indexOfLast {
+ timelineEventVisibilityHelper.shouldShowEvent(it, eventIdToHighlight)
+ }
+ if (prevDisplayableEventIndex != -1 && currentSnapshot[prevDisplayableEventIndex].senderInfo.userId == invalidatedSenderId) {
+ modelCache[prevDisplayableEventIndex] = null
+ }
requestModelBuild()
}
}
@@ -340,10 +349,14 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
val event = currentSnapshot[position]
val nextEvent = currentSnapshot.nextOrNull(position)
val prevEvent = currentSnapshot.prevOrNull(position)
+ val nextDisplayableEvent = currentSnapshot.subList(position + 1, currentSnapshot.size).firstOrNull {
+ timelineEventVisibilityHelper.shouldShowEvent(it, eventIdToHighlight)
+ }
val params = TimelineItemFactoryParams(
event = event,
prevEvent = prevEvent,
nextEvent = nextEvent,
+ nextDisplayableEvent = nextDisplayableEvent,
highlightedEventId = eventIdToHighlight,
lastSentEventIdWithoutReadReceipts = lastSentEventWithoutReadReceipts,
callback = callback
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt
index b1bac3378e..4767269833 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt
@@ -314,7 +314,7 @@ class MessageItemFactory @Inject constructor(
.leftGuideline(avatarSizeProvider.leftGuideline)
.imageContentRenderer(imageContentRenderer)
.contentUploadStateTrackerBinder(contentUploadStateTrackerBinder)
- .playable(messageContent.info?.mimeType == MimeTypes.Gif)
+ .playable(messageContent.mimeType == MimeTypes.Gif)
.highlighted(highlight)
.mediaData(data)
.apply {
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactoryParams.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactoryParams.kt
index f92cd2800a..0e595ba30e 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactoryParams.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactoryParams.kt
@@ -23,6 +23,7 @@ data class TimelineItemFactoryParams(
val event: TimelineEvent,
val prevEvent: TimelineEvent? = null,
val nextEvent: TimelineEvent? = null,
+ val nextDisplayableEvent: TimelineEvent? = null,
val highlightedEventId: String? = null,
val lastSentEventIdWithoutReadReceipts: String? = null,
val callback: TimelineEventController.Callback? = null
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt
index 124b196f72..221149aced 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt
@@ -50,27 +50,28 @@ import javax.inject.Inject
class MessageInformationDataFactory @Inject constructor(private val session: Session,
private val roomSummariesHolder: RoomSummariesHolder,
private val dateFormatter: VectorDateFormatter,
+ private val visibilityHelper: TimelineEventVisibilityHelper,
private val vectorPreferences: VectorPreferences) {
fun create(params: TimelineItemFactoryParams): MessageInformationData {
val event = params.event
- val nextEvent = params.nextEvent
+ val nextDisplayableEvent = params.nextDisplayableEvent
val eventId = event.eventId
val date = event.root.localDateTime()
- val nextDate = nextEvent?.root?.localDateTime()
+ val nextDate = nextDisplayableEvent?.root?.localDateTime()
val addDaySeparator = date.toLocalDate() != nextDate?.toLocalDate()
val isNextMessageReceivedMoreThanOneHourAgo = nextDate?.isBefore(date.minusMinutes(60))
?: false
val showInformation =
addDaySeparator
- || event.senderInfo.avatarUrl != nextEvent?.senderInfo?.avatarUrl
- || event.senderInfo.disambiguatedDisplayName != nextEvent?.senderInfo?.disambiguatedDisplayName
- || nextEvent.root.getClearType() !in listOf(EventType.MESSAGE, EventType.STICKER, EventType.ENCRYPTED)
+ || event.senderInfo.avatarUrl != nextDisplayableEvent?.senderInfo?.avatarUrl
+ || event.senderInfo.disambiguatedDisplayName != nextDisplayableEvent?.senderInfo?.disambiguatedDisplayName
+ || nextDisplayableEvent.root.getClearType() !in listOf(EventType.MESSAGE, EventType.STICKER, EventType.ENCRYPTED)
|| isNextMessageReceivedMoreThanOneHourAgo
- || isTileTypeMessage(nextEvent)
- || nextEvent.isEdition()
+ || isTileTypeMessage(nextDisplayableEvent)
+ || nextDisplayableEvent.isEdition()
val time = dateFormatter.format(event.root.originServerTs, DateFormatKind.MESSAGE_SIMPLE)
val e2eDecoration = getE2EDecoration(event)
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineControllerInterceptorHelper.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineControllerInterceptorHelper.kt
index 389dd15413..3121f031e2 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineControllerInterceptorHelper.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineControllerInterceptorHelper.kt
@@ -73,7 +73,8 @@ class TimelineControllerInterceptorHelper(private val positionOfReadMarker: KMut
}
epoxyModel.getEventIds().forEach { eventId ->
adapterPositionMapping[eventId] = index
- appendReadMarker = epoxyModel.canAppendReadMarker() && eventId == firstUnreadEventId && atLeastOneVisibleItemsBeforeReadMarker
+ appendReadMarker = appendReadMarker
+ || (epoxyModel.canAppendReadMarker() && eventId == firstUnreadEventId && atLeastOneVisibleItemsBeforeReadMarker)
}
}
if (epoxyModel is DaySeparatorItem) {
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/upgrade/MigrateRoomAction.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/upgrade/MigrateRoomAction.kt
new file mode 100644
index 0000000000..cb65be7e28
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/upgrade/MigrateRoomAction.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2021 New Vector Ltd
+ *
+ * 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 im.vector.app.features.home.room.detail.upgrade
+
+import im.vector.app.core.platform.VectorViewModelAction
+
+sealed class MigrateRoomAction : VectorViewModelAction {
+ data class SetAutoInvite(val autoInvite: Boolean) : MigrateRoomAction()
+ data class SetUpdateKnownParentSpace(val update: Boolean) : MigrateRoomAction()
+ object UpgradeRoom : MigrateRoomAction()
+}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/upgrade/MigrateRoomBottomSheet.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/upgrade/MigrateRoomBottomSheet.kt
new file mode 100644
index 0000000000..6e0aabb0f2
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/upgrade/MigrateRoomBottomSheet.kt
@@ -0,0 +1,152 @@
+/*
+ * Copyright (c) 2021 New Vector Ltd
+ *
+ * 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 im.vector.app.features.home.room.detail.upgrade
+
+import android.os.Bundle
+import android.os.Parcelable
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.core.view.isVisible
+import androidx.fragment.app.setFragmentResult
+import com.airbnb.mvrx.Loading
+import com.airbnb.mvrx.Success
+import com.airbnb.mvrx.fragmentViewModel
+import com.airbnb.mvrx.withState
+import im.vector.app.R
+import im.vector.app.core.di.ScreenComponent
+import im.vector.app.core.error.ErrorFormatter
+import im.vector.app.core.extensions.setTextOrHide
+import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment
+import im.vector.app.databinding.BottomSheetRoomUpgradeBinding
+import kotlinx.parcelize.Parcelize
+import javax.inject.Inject
+
+class MigrateRoomBottomSheet :
+ VectorBaseBottomSheetDialogFragment(),
+ MigrateRoomViewModel.Factory {
+
+ @Parcelize
+ data class Args(
+ val roomId: String,
+ val newVersion: String
+ ) : Parcelable
+
+ @Inject
+ lateinit var viewModelFactory: MigrateRoomViewModel.Factory
+
+ override val showExpanded = true
+
+ @Inject
+ lateinit var errorFormatter: ErrorFormatter
+
+ val viewModel: MigrateRoomViewModel by fragmentViewModel()
+
+ override fun injectWith(injector: ScreenComponent) {
+ injector.inject(this)
+ }
+
+ override fun invalidate() = withState(viewModel) { state ->
+ views.headerText.setText(if (state.isPublic) R.string.upgrade_public_room else R.string.upgrade_private_room)
+ views.upgradeFromTo.text = getString(R.string.upgrade_public_room_from_to, state.currentVersion, state.newVersion)
+
+ views.autoInviteSwitch.isVisible = !state.isPublic && state.otherMemberCount > 0
+
+ views.autoUpdateParent.isVisible = state.knownParents.isNotEmpty()
+
+ when (state.upgradingStatus) {
+ is Loading -> {
+ views.progressBar.isVisible = true
+ views.progressBar.isIndeterminate = state.upgradingProgressIndeterminate
+ views.progressBar.progress = state.upgradingProgress
+ views.progressBar.max = state.upgradingProgressTotal
+ views.inlineError.setTextOrHide(null)
+ views.button.isVisible = false
+ }
+ is Success -> {
+ views.progressBar.isVisible = false
+ when (val result = state.upgradingStatus.invoke()) {
+ is UpgradeRoomViewModelTask.Result.Failure -> {
+ val errorText = when (result) {
+ is UpgradeRoomViewModelTask.Result.UnknownRoom -> {
+ // should not happen
+ getString(R.string.unknown_error)
+ }
+ is UpgradeRoomViewModelTask.Result.NotAllowed -> {
+ getString(R.string.upgrade_room_no_power_to_manage)
+ }
+ is UpgradeRoomViewModelTask.Result.ErrorFailure -> {
+ errorFormatter.toHumanReadable(result.throwable)
+ }
+ else -> null
+ }
+ views.inlineError.setTextOrHide(errorText)
+ views.button.isVisible = true
+ views.button.text = getString(R.string.global_retry)
+ }
+ is UpgradeRoomViewModelTask.Result.Success -> {
+ setFragmentResult(REQUEST_KEY, Bundle().apply {
+ putString(BUNDLE_KEY_REPLACEMENT_ROOM, result.replacementRoomId)
+ })
+ dismiss()
+ }
+ }
+ }
+ else -> {
+ views.button.isVisible = true
+ views.button.text = getString(R.string.upgrade)
+ }
+ }
+
+ super.invalidate()
+ }
+
+ override fun getBinding(inflater: LayoutInflater, container: ViewGroup?) =
+ BottomSheetRoomUpgradeBinding.inflate(inflater, container, false)
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ views.button.debouncedClicks {
+ viewModel.handle(MigrateRoomAction.UpgradeRoom)
+ }
+
+ views.autoInviteSwitch.setOnCheckedChangeListener { _, isChecked ->
+ viewModel.handle(MigrateRoomAction.SetAutoInvite(isChecked))
+ }
+
+ views.autoUpdateParent.setOnCheckedChangeListener { _, isChecked ->
+ viewModel.handle(MigrateRoomAction.SetUpdateKnownParentSpace(isChecked))
+ }
+ }
+
+ override fun create(initialState: MigrateRoomViewState): MigrateRoomViewModel {
+ return viewModelFactory.create(initialState)
+ }
+
+ companion object {
+
+ const val REQUEST_KEY = "MigrateRoomBottomSheetRequest"
+ const val BUNDLE_KEY_REPLACEMENT_ROOM = "BUNDLE_KEY_REPLACEMENT_ROOM"
+
+ fun newInstance(roomId: String, newVersion: String): MigrateRoomBottomSheet {
+ return MigrateRoomBottomSheet().apply {
+ setArguments(Args(roomId, newVersion))
+ }
+ }
+ }
+}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/upgrade/MigrateRoomViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/upgrade/MigrateRoomViewModel.kt
new file mode 100644
index 0000000000..231bb319f0
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/upgrade/MigrateRoomViewModel.kt
@@ -0,0 +1,114 @@
+/*
+ * Copyright (c) 2021 New Vector Ltd
+ *
+ * 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 im.vector.app.features.home.room.detail.upgrade
+
+import com.airbnb.mvrx.ActivityViewModelContext
+import com.airbnb.mvrx.FragmentViewModelContext
+import com.airbnb.mvrx.Loading
+import com.airbnb.mvrx.MvRxViewModelFactory
+import com.airbnb.mvrx.Success
+import com.airbnb.mvrx.ViewModelContext
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import im.vector.app.core.platform.EmptyViewEvents
+import im.vector.app.core.platform.VectorViewModel
+import im.vector.app.features.session.coroutineScope
+import kotlinx.coroutines.launch
+import org.matrix.android.sdk.api.session.Session
+
+class MigrateRoomViewModel @AssistedInject constructor(
+ @Assisted initialState: MigrateRoomViewState,
+ private val session: Session,
+ private val upgradeRoomViewModelTask: UpgradeRoomViewModelTask)
+ : VectorViewModel(initialState) {
+
+ init {
+ val room = session.getRoom(initialState.roomId)
+ val summary = session.getRoomSummary(initialState.roomId)
+ setState {
+ copy(
+ currentVersion = room?.getRoomVersion(),
+ isPublic = summary?.isPublic ?: false,
+ otherMemberCount = summary?.otherMemberIds?.count() ?: 0,
+ knownParents = summary?.flattenParentIds ?: emptyList()
+ )
+ }
+ }
+
+ @AssistedFactory
+ interface Factory {
+ fun create(initialState: MigrateRoomViewState): MigrateRoomViewModel
+ }
+
+ companion object : MvRxViewModelFactory {
+
+ override fun create(viewModelContext: ViewModelContext, state: MigrateRoomViewState): MigrateRoomViewModel? {
+ val factory = when (viewModelContext) {
+ is FragmentViewModelContext -> viewModelContext.fragment as? Factory
+ is ActivityViewModelContext -> viewModelContext.activity as? Factory
+ }
+ return factory?.create(state) ?: error("You should let your activity/fragment implements Factory interface")
+ }
+ }
+
+ override fun handle(action: MigrateRoomAction) {
+ when (action) {
+ is MigrateRoomAction.SetAutoInvite -> {
+ setState {
+ copy(shouldIssueInvites = action.autoInvite)
+ }
+ }
+ is MigrateRoomAction.SetUpdateKnownParentSpace -> {
+ setState {
+ copy(shouldUpdateKnownParents = action.update)
+ }
+ }
+ MigrateRoomAction.UpgradeRoom -> {
+ handleUpgradeRoom()
+ }
+ }
+ }
+
+ private fun handleUpgradeRoom() = withState { state ->
+ val summary = session.getRoomSummary(state.roomId)
+ setState {
+ copy(upgradingStatus = Loading())
+ }
+ session.coroutineScope.launch {
+ val result = upgradeRoomViewModelTask.execute(UpgradeRoomViewModelTask.Params(
+ roomId = state.roomId,
+ newVersion = state.newVersion,
+ userIdsToAutoInvite = summary?.otherMemberIds?.takeIf { state.shouldIssueInvites } ?: emptyList(),
+ parentSpaceToUpdate = summary?.flattenParentIds?.takeIf { state.shouldUpdateKnownParents } ?: emptyList(),
+ progressReporter = { indeterminate, progress, total ->
+ setState {
+ copy(
+ upgradingProgress = progress,
+ upgradingProgressTotal = total,
+ upgradingProgressIndeterminate = indeterminate
+ )
+ }
+ }
+ ))
+
+ setState {
+ copy(upgradingStatus = Success(result))
+ }
+ }
+ }
+}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/upgrade/MigrateRoomViewState.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/upgrade/MigrateRoomViewState.kt
new file mode 100644
index 0000000000..e3936de42f
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/upgrade/MigrateRoomViewState.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2021 New Vector Ltd
+ *
+ * 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 im.vector.app.features.home.room.detail.upgrade
+
+import com.airbnb.mvrx.Async
+import com.airbnb.mvrx.MvRxState
+import com.airbnb.mvrx.Uninitialized
+
+data class MigrateRoomViewState(
+ val roomId: String,
+ val newVersion: String,
+ val currentVersion: String? = null,
+ val isPublic: Boolean = false,
+ val shouldIssueInvites: Boolean = false,
+ val shouldUpdateKnownParents: Boolean = true,
+ val otherMemberCount: Int = 0,
+ val knownParents: List = emptyList(),
+ val upgradingStatus: Async = Uninitialized,
+ val upgradingProgress: Int = 0,
+ val upgradingProgressTotal: Int = 0,
+ val upgradingProgressIndeterminate: Boolean = true
+) : MvRxState {
+ constructor(args: MigrateRoomBottomSheet.Args) : this(
+ roomId = args.roomId,
+ newVersion = args.newVersion
+ )
+}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/upgrade/UpgradeRoomViewModelTask.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/upgrade/UpgradeRoomViewModelTask.kt
new file mode 100644
index 0000000000..32c8e6ee92
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/upgrade/UpgradeRoomViewModelTask.kt
@@ -0,0 +1,99 @@
+/*
+ * Copyright (c) 2021 New Vector Ltd
+ *
+ * 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 im.vector.app.features.home.room.detail.upgrade
+
+import im.vector.app.core.platform.ViewModelTask
+import im.vector.app.core.resources.StringProvider
+import org.matrix.android.sdk.api.extensions.tryOrNull
+import org.matrix.android.sdk.api.session.Session
+import timber.log.Timber
+import javax.inject.Inject
+
+class UpgradeRoomViewModelTask @Inject constructor(
+ val session: Session,
+ val stringProvider: StringProvider
+) : ViewModelTask {
+
+ sealed class Result {
+ data class Success(val replacementRoomId: String) : Result()
+ abstract class Failure(val throwable: Throwable?) : Result()
+ object UnknownRoom : Failure(null)
+ object NotAllowed : Failure(null)
+ class ErrorFailure(throwable: Throwable) : Failure(throwable)
+ }
+
+ data class Params(
+ val roomId: String,
+ val newVersion: String,
+ val userIdsToAutoInvite: List = emptyList(),
+ val parentSpaceToUpdate: List = emptyList(),
+ val progressReporter: ((indeterminate: Boolean, progress: Int, total: Int) -> Unit)? = null
+ )
+
+ override suspend fun execute(params: Params): Result {
+ params.progressReporter?.invoke(true, 0, 0)
+
+ val room = session.getRoom(params.roomId)
+ ?: return Result.UnknownRoom
+ if (!room.userMayUpgradeRoom(session.myUserId)) {
+ return Result.NotAllowed
+ }
+
+ val updatedRoomId = try {
+ room.upgradeToVersion(params.newVersion)
+ } catch (failure: Throwable) {
+ return Result.ErrorFailure(failure)
+ }
+
+ val totalStep = params.userIdsToAutoInvite.size + params.parentSpaceToUpdate.size
+ var currentStep = 0
+ params.userIdsToAutoInvite.forEach {
+ params.progressReporter?.invoke(false, currentStep, totalStep)
+ tryOrNull {
+ session.getRoom(updatedRoomId)?.invite(it)
+ }
+ currentStep++
+ }
+
+ params.parentSpaceToUpdate.forEach { parentId ->
+ params.progressReporter?.invoke(false, currentStep, totalStep)
+ // we try and silently fail
+ try {
+ session.getRoom(parentId)?.asSpace()?.let { parentSpace ->
+ val currentInfo = parentSpace.getChildInfo(params.roomId)
+ if (currentInfo != null) {
+ parentSpace.addChildren(
+ roomId = updatedRoomId,
+ viaServers = currentInfo.via,
+ order = currentInfo.order,
+ autoJoin = currentInfo.autoJoin ?: false,
+ suggested = currentInfo.suggested
+ )
+
+ parentSpace.removeChildren(params.roomId)
+ }
+ }
+ } catch (failure: Throwable) {
+ Timber.d("## Migrate: Failed to update space parent. cause: ${failure.localizedMessage}")
+ } finally {
+ currentStep++
+ }
+ }
+
+ return Result.Success(updatedRoomId)
+ }
+}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/filtered/FilteredRoomFooterItem.kt b/vector/src/main/java/im/vector/app/features/home/room/filtered/FilteredRoomFooterItem.kt
index 9c28faa942..5e45004579 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/filtered/FilteredRoomFooterItem.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/filtered/FilteredRoomFooterItem.kt
@@ -23,13 +23,12 @@ import im.vector.app.R
import im.vector.app.core.epoxy.VectorEpoxyHolder
import im.vector.app.core.epoxy.VectorEpoxyModel
import im.vector.app.core.epoxy.onClick
-import im.vector.app.features.home.room.list.widget.NotifsFabMenuView
@EpoxyModelClass(layout = R.layout.item_room_filter_footer)
abstract class FilteredRoomFooterItem : VectorEpoxyModel() {
@EpoxyAttribute
- var listener: FilteredRoomFooterItemListener? = null
+ var listener: Listener? = null
@EpoxyAttribute
var currentFilter: String = ""
@@ -47,7 +46,9 @@ abstract class FilteredRoomFooterItem : VectorEpoxyModel(R.id.roomFilterFooterOpenRoomDirectory)
}
- interface FilteredRoomFooterItemListener : NotifsFabMenuView.Listener {
+ interface Listener {
fun createRoom(initialName: String)
+ fun createDirectChat()
+ fun openRoomDirectory(initialFilter: String)
}
}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFooterController.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFooterController.kt
index cef8fa2d26..22cd0ae639 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFooterController.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFooterController.kt
@@ -22,6 +22,7 @@ import im.vector.app.core.epoxy.helpFooterItem
import im.vector.app.core.resources.StringProvider
import im.vector.app.core.resources.UserPreferencesProvider
import im.vector.app.features.home.RoomListDisplayMode
+import im.vector.app.features.home.room.filtered.FilteredRoomFooterItem
import im.vector.app.features.home.room.filtered.filteredRoomFooterItem
import javax.inject.Inject
@@ -30,7 +31,7 @@ class RoomListFooterController @Inject constructor(
private val userPreferencesProvider: UserPreferencesProvider
) : TypedEpoxyController() {
- var listener: RoomListListener? = null
+ var listener: FilteredRoomFooterItem.Listener? = null
override fun buildModels(data: RoomListViewState?) {
val host = this
diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt
index 3c08793fa4..fdfe171439 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt
@@ -42,6 +42,7 @@ import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.core.resources.UserPreferencesProvider
import im.vector.app.databinding.FragmentRoomListBinding
import im.vector.app.features.home.RoomListDisplayMode
+import im.vector.app.features.home.room.filtered.FilteredRoomFooterItem
import im.vector.app.features.home.room.list.actions.RoomListActionsArgs
import im.vector.app.features.home.room.list.actions.RoomListQuickActionsBottomSheet
import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedAction
@@ -70,6 +71,7 @@ class RoomListFragment @Inject constructor(
) : VectorBaseFragment(),
RoomListListener,
OnBackPressed,
+ FilteredRoomFooterItem.Listener,
NotifsFabMenuView.Listener {
private var modelBuildListener: OnModelBuildFinishedListener? = null
@@ -178,16 +180,16 @@ class RoomListFragment @Inject constructor(
private fun setupCreateRoomButton() {
when (roomListParams.displayMode) {
RoomListDisplayMode.NOTIFICATIONS -> views.createChatFabMenu.isVisible = true
- RoomListDisplayMode.PEOPLE -> views.createChatRoomButton.isVisible = true
- RoomListDisplayMode.ROOMS -> views.createGroupRoomButton.isVisible = true
+ RoomListDisplayMode.PEOPLE -> views.createChatRoomButton.isVisible = true
+ RoomListDisplayMode.ROOMS -> views.createGroupRoomButton.isVisible = true
else -> Unit // No button in this mode
}
views.createChatRoomButton.debouncedClicks {
- createDirectChat()
+ fabCreateDirectChat()
}
views.createGroupRoomButton.debouncedClicks {
- openRoomDirectory()
+ fabOpenRoomDirectory()
}
// Hide FAB when list is scrolling
@@ -221,14 +223,28 @@ class RoomListFragment @Inject constructor(
roomListViewModel.handle(RoomListAction.FilterWith(filter))
}
- override fun openRoomDirectory(initialFilter: String) {
- navigator.openRoomDirectory(requireActivity(), initialFilter)
+ // FilteredRoomFooterItem.Listener
+ override fun createRoom(initialName: String) {
+ navigator.openCreateRoom(requireActivity(), initialName)
}
override fun createDirectChat() {
navigator.openCreateDirectRoom(requireActivity())
}
+ override fun openRoomDirectory(initialFilter: String) {
+ navigator.openRoomDirectory(requireActivity(), initialFilter)
+ }
+
+ // NotifsFabMenuView.Listener
+ override fun fabCreateDirectChat() {
+ navigator.openCreateDirectRoom(requireActivity())
+ }
+
+ override fun fabOpenRoomDirectory() {
+ navigator.openRoomDirectory(requireActivity(), "")
+ }
+
private fun setupRecyclerView() {
val layoutManager = LinearLayoutManager(context)
stateRestorer = LayoutManagerStateRestorer(layoutManager).register()
@@ -339,8 +355,8 @@ class RoomListFragment @Inject constructor(
if (isAdded) {
when (roomListParams.displayMode) {
RoomListDisplayMode.NOTIFICATIONS -> views.createChatFabMenu.show()
- RoomListDisplayMode.PEOPLE -> views.createChatRoomButton.show()
- RoomListDisplayMode.ROOMS -> views.createGroupRoomButton.show()
+ RoomListDisplayMode.PEOPLE -> views.createChatRoomButton.show()
+ RoomListDisplayMode.ROOMS -> views.createGroupRoomButton.show()
else -> Unit
}
}
@@ -409,14 +425,14 @@ class RoomListFragment @Inject constructor(
image = ContextCompat.getDrawable(requireContext(), R.drawable.ic_noun_party_popper),
message = getString(R.string.room_list_catchup_empty_body))
}
- RoomListDisplayMode.PEOPLE ->
+ RoomListDisplayMode.PEOPLE ->
StateView.State.Empty(
title = getString(R.string.room_list_people_empty_title),
image = ContextCompat.getDrawable(requireContext(), R.drawable.empty_state_dm),
isBigImage = true,
message = getString(R.string.room_list_people_empty_body)
)
- RoomListDisplayMode.ROOMS ->
+ RoomListDisplayMode.ROOMS ->
StateView.State.Empty(
title = getString(R.string.room_list_rooms_empty_title),
image = ContextCompat.getDrawable(requireContext(), R.drawable.empty_state_room),
@@ -480,8 +496,4 @@ class RoomListFragment @Inject constructor(
notificationDrawerManager.clearMemberShipNotificationForRoom(room.roomId)
roomListViewModel.handle(RoomListAction.RejectInvitation(room))
}
-
- override fun createRoom(initialName: String) {
- navigator.openCreateRoom(requireActivity(), initialName)
- }
}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListListener.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListListener.kt
index cf619ce435..b0bb6706dd 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListListener.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListListener.kt
@@ -16,11 +16,10 @@
package im.vector.app.features.home.room.list
-import im.vector.app.features.home.room.filtered.FilteredRoomFooterItem
import org.matrix.android.sdk.api.session.room.model.RoomSummary
import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo
-interface RoomListListener : FilteredRoomFooterItem.FilteredRoomFooterItemListener {
+interface RoomListListener {
fun onRoomClicked(room: RoomSummary)
fun onRoomLongClicked(room: RoomSummary): Boolean
fun onRejectRoomInvitation(room: RoomSummary)
diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilder.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilder.kt
index 5267158000..019a6ceddf 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilder.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilder.kt
@@ -20,4 +20,6 @@ import im.vector.app.features.home.RoomListDisplayMode
interface RoomListSectionBuilder {
fun buildSections(mode: RoomListDisplayMode) : List
+
+ fun dispose()
}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/GroupRoomListSectionBuilder.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderGroup.kt
similarity index 90%
rename from vector/src/main/java/im/vector/app/features/home/room/list/GroupRoomListSectionBuilder.kt
rename to vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderGroup.kt
index 106a02cd3c..f101669af3 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/list/GroupRoomListSectionBuilder.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderGroup.kt
@@ -24,9 +24,8 @@ import im.vector.app.core.resources.StringProvider
import im.vector.app.features.home.RoomListDisplayMode
import im.vector.app.features.invite.AutoAcceptInvites
import im.vector.app.features.invite.showInvites
-import io.reactivex.disposables.Disposable
+import io.reactivex.disposables.CompositeDisposable
import io.reactivex.schedulers.Schedulers
-import kotlinx.coroutines.CoroutineScope
import org.matrix.android.sdk.api.query.RoomCategoryFilter
import org.matrix.android.sdk.api.query.RoomTagQueryFilter
import org.matrix.android.sdk.api.session.Session
@@ -35,16 +34,16 @@ import org.matrix.android.sdk.api.session.room.UpdatableLivePageResult
import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.rx.asObservable
-class GroupRoomListSectionBuilder(
- val session: Session,
- val stringProvider: StringProvider,
- val viewModelScope: CoroutineScope,
- val appStateHandler: AppStateHandler,
+class RoomListSectionBuilderGroup(
+ private val session: Session,
+ private val stringProvider: StringProvider,
+ private val appStateHandler: AppStateHandler,
private val autoAcceptInvites: AutoAcceptInvites,
- val onDisposable: (Disposable) -> Unit,
- val onUdpatable: (UpdatableLivePageResult) -> Unit
+ private val onUpdatable: (UpdatableLivePageResult) -> Unit
) : RoomListSectionBuilder {
+ private val disposables = CompositeDisposable()
+
override fun buildSections(mode: RoomListDisplayMode): List {
val activeGroupAwareQueries = mutableListOf()
val sections = mutableListOf()
@@ -52,7 +51,7 @@ class GroupRoomListSectionBuilder(
when (mode) {
RoomListDisplayMode.PEOPLE -> {
- // 3 sections Invites / Fav / Dms
+ // 4 sections Invites / Fav / Dms / Low Priority
buildPeopleSections(sections, activeGroupAwareQueries, actualGroupId)
}
RoomListDisplayMode.ROOMS -> {
@@ -69,7 +68,7 @@ class GroupRoomListSectionBuilder(
val name = stringProvider.getString(R.string.bottom_action_rooms)
session.getFilteredPagedRoomSummariesLive(qpm)
.let { updatableFilterLivePageResult ->
- onUdpatable(updatableFilterLivePageResult)
+ onUpdatable(updatableFilterLivePageResult)
sections.add(RoomsSection(name, updatableFilterLivePageResult.livePagedList))
}
}
@@ -88,6 +87,7 @@ class GroupRoomListSectionBuilder(
it.activeGroupId = actualGroupId
}
}
+
addSection(
sections,
activeGroupAwareQueries,
@@ -111,8 +111,9 @@ class GroupRoomListSectionBuilder(
}
}
}.also {
- onDisposable.invoke(it)
+ disposables.add(it)
}
+
return sections
}
@@ -218,7 +219,19 @@ class GroupRoomListSectionBuilder(
) {
it.memberships = listOf(Membership.JOIN)
it.roomCategoryFilter = RoomCategoryFilter.ONLY_DM
- it.roomTagQueryFilter = RoomTagQueryFilter(false, null, null)
+ it.roomTagQueryFilter = RoomTagQueryFilter(false, false, null)
+ it.activeGroupId = actualGroupId
+ }
+
+ addSection(
+ sections,
+ activeSpaceAwareQueries,
+ R.string.low_priority_header,
+ false
+ ) {
+ it.memberships = listOf(Membership.JOIN)
+ it.roomCategoryFilter = RoomCategoryFilter.ONLY_DM
+ it.roomTagQueryFilter = RoomTagQueryFilter(false, true, null)
it.activeGroupId = actualGroupId
}
}
@@ -231,7 +244,6 @@ class GroupRoomListSectionBuilder(
withQueryParams(
{ query.invoke(it) },
{ roomQueryParams ->
-
val name = stringProvider.getString(nameRes)
session.getFilteredPagedRoomSummariesLive(roomQueryParams)
.also {
@@ -246,8 +258,9 @@ class GroupRoomListSectionBuilder(
?.notificationCount
?.postValue(session.getNotificationCountForRooms(roomQueryParams))
}.also {
- onDisposable.invoke(it)
+ disposables.add(it)
}
+
sections.add(
RoomsSection(
sectionName = name,
@@ -267,4 +280,8 @@ class GroupRoomListSectionBuilder(
.build()
.let { block(it) }
}
+
+ override fun dispose() {
+ disposables.dispose()
+ }
}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/SpaceRoomListSectionBuilder.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderSpace.kt
similarity index 90%
rename from vector/src/main/java/im/vector/app/features/home/room/list/SpaceRoomListSectionBuilder.kt
rename to vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderSpace.kt
index 5a296ce7ed..13a6fc0d2d 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/list/SpaceRoomListSectionBuilder.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderSpace.kt
@@ -30,7 +30,7 @@ import im.vector.app.features.invite.AutoAcceptInvites
import im.vector.app.features.invite.showInvites
import im.vector.app.space
import io.reactivex.Observable
-import io.reactivex.disposables.Disposable
+import io.reactivex.disposables.CompositeDisposable
import io.reactivex.rxkotlin.Observables
import io.reactivex.schedulers.Schedulers
import kotlinx.coroutines.CoroutineScope
@@ -46,19 +46,20 @@ import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount
import org.matrix.android.sdk.rx.asObservable
-class SpaceRoomListSectionBuilder(
- val session: Session,
- val stringProvider: StringProvider,
- val appStateHandler: AppStateHandler,
- val viewModelScope: CoroutineScope,
- private val suggestedRoomJoiningState: LiveDataE-postkoblingen som ikke er klikket på ennåDette brukernavnet er allerede bruktInneholdt ikke gyldig JSON
diff --git a/vector/src/main/res/values-nl/strings.xml b/vector/src/main/res/values-nl/strings.xml
index b2c1aff6ec..a76c94cc05 100644
--- a/vector/src/main/res/values-nl/strings.xml
+++ b/vector/src/main/res/values-nl/strings.xml
@@ -1477,4 +1477,66 @@
%1$s heeft de kamer aangemaaktJouw uitnodigingJe hebt een afbeelding gestuurd.
+ U heeft %1$s verbannen. Reden: %2$s
+ U heeft de verbanning van %1$s opgeheven. Reden: %2$s
+ U heeft %1$s eruit getrapt. Reden: %2$s
+ U heeft de uitnodiging geweigerd. Reden: %1$s
+ U bent vertrokken. Reden: %1$s
+ %1$s is vertrokken. Reden: %2$s
+ U heeft de ruimte verlaten. Reden: %1$s
+ U heeft zich aangesloten. Reden: %1$s
+ %1$s heeft zich aangesloten. Reden: %2$s
+ U heeft zich aangesloten bij de kamer. Reden: %1$s
+
+ %1$s, %2$s, %3$s en %4$d andere
+ %1$s, %2$s, %3$s en %4$d anderen
+
+ %1$s, %2$s, %3$s en %4$s
+ %1$s, %2$s en %3$s
+ %1$s van %2$s naar %3$s
+ %1$s heeft het machtigingsniveau van %2$s aangepast.
+ U heeft het machtigingsniveau van %1$s aangepast.
+ Speciaal
+ Speciaal (%1$d)
+ Standaardlid
+ Beheerder
+ U heeft de videoconferentie aangepast
+ Videoconferentie aangepast door %1$s
+ U heeft de videoconferentie beëindigd
+ Videoconferentie beëindigd door %1$s
+ U heeft een videoconferentie gestart
+ Videoconferentie gestart door %1$s
+ U heeft de widget %1$s aangepast
+ %1$s heeft de widget %2$s aangepast
+ U heeft de widget %1$s verwijderd
+ %1$s heeft de widget %2$s verwijderd
+ U heeft de widget %1$s toegevoegd
+ %1$s heeft de widget %2$s toegevoegd
+ U heeft de uitnodiging voor %1$s geaccepteerd
+ U heeft de uitnodiging voor %1$s ingetrokken
+ %1$s heeft de uitnodiging voor %2$s ingetrokken
+ U heeft de uitnodiging voor %1$s ingetrokken om zich bij de kamer aan te sluiten
+ U heeft een uitnodiging gestuurd naar %1$s om zich bij de kamer aan te sluiten
+ U heeft de kameravatar verwijderd
+ %1$s heeft de kameravatar verwijderd
+ U heeft het kameronderwerp verwijderd
+ U heeft de kamernaam verwijderd
+ U heeft de ruimte geüpgradet.
+ U verstuurde data om het gesprek op te zetten.
+ %s verstuurde data om het gesprek op te zetten.
+ U heeft een audiogesprek geopend.
+ U heeft een videogesprek geopend.
+ U heeft de ruimtenaam veranderd naar: %1$s
+ U heeft de ruimteavatar aangepast
+ %1$s heeft de ruimteavatar aangepast
+ U heeft het onderwerp gewijzigd naar: %1$s
+ U heeft uw weergavenaam verwijderd (voorheen %1$s)
+ U heeft de uitnodiging van %1$s ingetrokken
+ U heeft %1$s verbannen
+ U heeft de verbanning van %1$s opgeheven
+ U heeft %1$s eruit getrapt
+ U sloot zich aan
+ %1$s sluit aan
+ Je hebt de kamer betreden
+ Je hebt een sticker verzonden.
\ No newline at end of file
diff --git a/vector/src/main/res/values-pt-rBR/strings.xml b/vector/src/main/res/values-pt-rBR/strings.xml
index db7f940e38..d187980d54 100644
--- a/vector/src/main/res/values-pt-rBR/strings.xml
+++ b/vector/src/main/res/values-pt-rBR/strings.xml
@@ -313,8 +313,8 @@
ConvidarFazer signout
- Chamada de Voz
- Chamada de Vídeo
+ Chamar por Voz
+ Chamar por VídeoPesquisa globalMarcar tudo como lidoHistórico
@@ -359,7 +359,7 @@
Enviar crash logsEnviar screenshotReportar bug
- Por favor descreva o bug. O que você fez\? O que você esperava que acontecese\? O que aconteceu na verdade\?
+ Por favor descreva o bug. O que você fez\? O que você esperava que acontecesse\? O que na verdade aconteceu\?Descreva seu problema aquiA fim de diagnosticar problemas, logs deste cliente vão ser enviados com este reporte de bug. Este reporte de bug, incluindo os logs e o screenshot, não será visível publicamente. Se você prefere somente enviar o texto acima, por favor desmarque:Você parece estar agitando o telefone em frustração. Você gostaria de abrir a tela de reporte de bug\?
@@ -429,7 +429,7 @@
Falha para verificar endereço de email: assegure-se que clicou no link no emailSua senha tem sido resettada.
\n
-\nVocê tem sido feito logout de todas as sessões e não vai mais receber notificações push. Para reativar notificações, faça re-login em cada dispositivo.
+\nVocê tem sido feito logout de todas as sessões e não vai mais receber notificações push. Para re-ativar notificações, re-faça login em cada dispositivo.URL deve começar com http[s]://Incapaz de fazer login: Erro de rede
@@ -515,7 +515,7 @@
PrevisualizarRejeitar
- Pular para primeira mensagem não-lida.
+ Pular para não-lida(s)Você tem sido convidada(o) a juntar-se a esta sala por %sEste convite foi enviado para %s, que não está associada(o) a esta conta.
@@ -640,7 +640,7 @@
VersãoTermos & condiçõesNotas de terceiros
- Direito de autor
+ CopyrightPolítica de privacidadeImagem de Perfil
@@ -667,7 +667,7 @@
versão de olmTermos & condiçõesNotas de terceiros
- Direitos de autor
+ CopyrightPolítica de privacidadeLimpar cache
@@ -894,7 +894,7 @@
Lista de GruposChamarBanir usuária(o) vai expulsá-la(o) desta sala e preveni-la(o) de se juntar de novo.
- Todas as mensagens (barulhentas)
+ Todas as mensagens (barulhento)Todas as mensagensMenções somenteMudo
@@ -1015,7 +1015,7 @@
Avatar de reciboAvatar de notaAvatar
- Agite com raiva para reportar bug
+ Agitar com raiva para reportar bugNormalPrivacidade reduzidaO app precisa de permissão para rodar no background
@@ -1077,7 +1077,7 @@
Privacidade de Notificação${app_name} pode rodar no background para gerenciar suas notificações seguramente e privadamente. Isto pode afetar uso de bateria.Conceder permissão
- Escolha um outra opção
+ Escolher uma outra opçãoEnviar dados de analítica${app_name} coleta analítica anônima para nos permitir melhorar o aplicativo.Por favor ative analítica para nos ajudar a melhorar ${app_name}.
@@ -1203,7 +1203,7 @@
Enviar notificações de digitaçãoDeixar outras(os) usuárias(os) saberem que você está digitando.Mostrar recibos de leitura
- Clicar nos recibos de leitura para uma lista detalhada.
+ Clique nos recibos de leitura para uma lista detalhada.Mostrar eventos de juntar-se e sairConvites, expulsões e bans são desafetados.Mostrar eventos de conta
@@ -1358,7 +1358,7 @@
Desbanir usuária(o)Desbanir usuária(o) vai permitir-lhe se juntar à sala de novo.Confirme sua senha
- Você não pode fazer isto de ${app_name} celular
+ Você não pode fazer isto desde ${app_name} mobileAutenticação é requeridaO app não precisa de se conectar ao ServidorCasa no background, isto deveria reduzir uso de bateriaModo Sinc no Background
@@ -1387,7 +1387,7 @@
Gere uma nova Chave de Segurança ou defina uma nova Frase de Segurança para seu backup existente.Isto vai substituir sua Chave ou Frase atual.Descoberta
- Gerencie suas configurações de descoberta.
+ Gerenciar suas configurações de descoberta.Modo de economia de dados aplica um filtro específico para que atualizações de presença e notificações de digitação sejam filtradas fora.Permitir integraçõesGerenciador de Integração
@@ -1600,9 +1600,9 @@
Você recebeu uma requisição de verificação entrante.Visualizar requisiçãoEsperando por parceira(o) confirmar…
- Verificada!
+ Verificada(o)!Você tem confirmado esta sessão com sucesso.
- Mensagens seguras com esta(e) usuária(o) estão encriptadas ponta-a-ponta e não são capazes de ser lidas por terceiros.
+ Mensagens seguras com esta(e) usuária(o) são encriptadas ponta-a-ponta e não são capazes de ser lidas por terceiros.EntendidoNada aparecendo\? Não todos os clientes suportam verificação interativa ainda. Use verificação legado.Usar verificação legado.
@@ -1637,10 +1637,10 @@
Convidada(o) por %sVocê está em dia!Você não tem mais nenhuma mensagem não-lida
- Boas-vindas!
+ Boas-vindas a casa!Fique em dia com suas mensagens não-lidas aquiConversas
- Suas conversas de mensagem direta vai ser exibidas aqui. Toque no + à direita fundo para começar algumas.
+ Suas conversas de mensagem direta vão ser exibidas aqui. Toque no + à direita fundo para começar algumas.SalasSuas salas vão ser exibidas aqui. Toque no + à direita fundo para encontrar umas existentes ou começar algumas propriamente suas.Reações
@@ -1669,7 +1669,7 @@
Nova SalaCRIARNome
- Público
+ PúblicaQualquer pessoa vai ser capaz de se juntar a esta salaDiretório de SalasPublicar esta sala no diretório de salas
@@ -1816,7 +1816,7 @@
\nPor favor permita acesso no próximo pop-up para ser capaz de exportar suas chaves manualmente.Não há nenhuma conexão de rede no momentoIgnorar usuária(o)
- Todas as mensagens (barulhentas)
+ Todas as mensagens (barulhento)Todas as mensagensMenções somenteMutar
@@ -1840,13 +1840,13 @@
Faça chat com pessoas diretamente ou em gruposMantenha conversas privadas com encriptaçãoExtenda & personalize sua experiência
- Comece agora
+ Começar agoraSelecione um servidorAssim como email, contas têm uma casa, embora você pode falar com qualquer pessoaJunte-se a milhões de graça no maior servidor públicoHospedagem premium para organizaçõesSaiba mais
- Outros
+ OutroConfigurações personalizadas & avançadasContinuarConectar-se a %1$s
@@ -1943,7 +1943,7 @@
Este não é um identificador de usuária(o) válido. Formato esperado: \'@usuarix:servidorcasa.org\'Incapaz de encontrar um servidorcasa válido. Por favor cheque seu identificadorVista por
- Você fez signout
+ Você está com signout feitoPode ser devido a várias razões:
\n
\n• Você tem mudado sua senha numa outra sessão.
@@ -1952,7 +1952,7 @@
\n
\n• O/a administrador(a) de seu servidor tem invalidado seu acesso por razão de segurança.Fazer signin de novo
- Você fez signout
+ Você está com signout feitoFazer signinA/o admin de seu servidorcasa (%1$s) fez seu signout de sua conta %2$s (%3$s).Faça signin para recuperar chaves de encriptação armazenadas exclusivamente neste dispositivo. Você precisa delas para ler todas suas mensagens seguras em qualquer dispositivo.
@@ -1995,7 +1995,7 @@
Eles correspondemEles não correspondemVerifique esta(e) usuária(o) ao confirmar que os seguintes emoji únicos aparecem na tela dela(e), na mesma ordem.
- Para máxima segurança, use um outro meio de comunicação confiado ou faça isto em pessoa.
+ Para segurança ótima, use um outro meio de comunicação confiado ou faça isto em pessoa.Procure pelo escudo verde para assegurar que um/uma usuário(a) é confiado. Confie em todos(as) os/as usuários(as) numa sala para assegurar que a sala é segura.Não seguroUm dos seguintes pode estar comprometido:
@@ -2010,7 +2010,7 @@
ArquivoStickerEsperando…
- %s cancelado
+ %s cancelouVocê cancelou%s aceitouVocê aceitou
@@ -2036,7 +2036,7 @@
Mensagens nesta sala não são encriptadas ponta-a-ponta.Mensagens nesta sala são encriptadas ponta-a-ponta.
\n
-\nSuas mensagens são asseguradas com cadeados e somente você e a/o recipente têm as chaves únicas para os destrancar.
+\nSuas mensagens são asseguradas com cadeados e somente você e a/o recipente têm as chaves únicas para as destrancar.SegurançaSaiba maisMais
@@ -2109,7 +2109,7 @@
Completar SegurançaUse uma sessão existente para verificar esta aqui, garantindo-lhe acesso a mensagens encriptadas.Verificar
- Verificada
+ Verificada(o)AvisoFalha para obter sessõesSessões
@@ -2207,7 +2207,7 @@
Configurando Backup de ChaveSuas %2$s & %1$s estão agora definidas.
\n
-\nMantenha-as seguras! Você vai precisar delas para destrancar mensagens encriptadas e informação segura se você perder todas as suas sessões ativas.
+\nMantenha-as seguras! Você vai precisar delas para destrancar mensagens encriptadas e informação segura se você perder todas suas sessões ativas.Imprima-a e armazene-a em algum lugar seguroSalve-a em uma chave USB ou drive de backupCopie-a para seu armazenamento nuvem pessoal
@@ -2361,7 +2361,7 @@
Você não pode acessar esta mensagem porque o/a enviador(a) propositalmente não enviou as chavesEsperando por histórico de encriptaçãoRiot agora é Element!
- Nós estamos animados em anunciar que nós mudamos de nome! Seu app está atualizado e o signin está feito a sua conta.
+ Nós estamos animados em anunciar que nós temos mudado de nome! Seu app está atualizado e você está com signin feito a sua conta.ENTENDISABER MAISSalvar chave de recuperação em
@@ -2475,7 +2475,7 @@
ConfiguraçõesMensagens aqui são encriptadas ponta-a-ponta.
\n
-\nSuas mensagens são asseguradas com cadeados e somente você e a/o recipiente têm as chaves únicas para os destrancar.
+\nSuas mensagens são asseguradas com cadeados e somente você e a/o recipiente têm as chaves únicas para as destrancar.Mensagens aqui não são encriptadas ponta-a-ponta.Este servidorcasa está rodando uma versão antiga. Peça à/ao admin de seu servidorcasa para fazer upgrade. Você pode continuar, mas algumas funcionalidades podem não funcionar corretamente.Mostrar histórico completo em salas encriptadas
@@ -2510,7 +2510,7 @@
Rotar e recortarConfigurações de salaTópico
- Tópico da sala (opcional)
+ Tópico de sala (opcional)Nome de salaEnviar histórico de requisições de compartilhamento de chavesMais nenhum resultado
@@ -2519,7 +2519,7 @@
Mostrar avançadasEsconder avançadasLink Matrix
- %s para deixar pessoas sabendo do que esta sala se trata.
+ %s para deixar pessoas saberem do que esta sala se trata.Por favor proveja um endereço de salaRecenteQR code não scannado!
@@ -2549,7 +2549,7 @@
Este endereço já está em usoEndereço de salaVocê pode ativar isto se a sala vai somente ser usada para colaborar com times internos em seu servidorcasa. Isto não poder ser mudado mais tarde.
- Bloquear qualquer pessoa que não é parte de %s de nunca se juntar a esta sala
+ Bloquear qualquer pessoa que não é parte de %s de jamais se juntar a esta sala%1$d de %2$dCriar uma nova conversa direta ao scannar um QR codeCriar uma nova conversa direta por ID Matrix
@@ -2876,4 +2876,13 @@
Enviar vídeo com o tamanho originalEnviar vídeos com o tamanho original
+ No momento pessoas podem não ser capaz de se juntar a quaisquer salas privadas que você fizer.
+\n
+\nNós vamos melhorar isto como parte da beta, mas só queríamos deixar você saber.
+ Espaços de colegas de trabalho não estão bem prontos mas você ainda pode dar-lhes uma tentativa
+ Continuar Mesmo Assim
+ Desculpe, um erro ocorreu enquanto tentando se juntar: %s
+ Endereço de espaço
+ Ver e gerenciar endereços deste espaço.
+ Endereços de espaço
\ No newline at end of file
diff --git a/vector/src/main/res/values-pt/strings.xml b/vector/src/main/res/values-pt/strings.xml
index 0cf47bd339..b4aa314cba 100644
--- a/vector/src/main/res/values-pt/strings.xml
+++ b/vector/src/main/res/values-pt/strings.xml
@@ -164,7 +164,7 @@
Criar contaIniciar sessãoSair
- URL do "Home Server"
+ URL do homeserverURL do "Identity Server"PesquisarIniciar nova conversa
@@ -209,7 +209,7 @@
Poderá adicionar o seu email ao seu perfil nas definições.Este servidor quer ter a certeza de que você não é um robôNome de utilizador já existe
- Servidor local (home server):
+ Servidor local (homeserver):Servidor de Identidade:Verifiquei o meu endereço de emailPara redefinir a sua palavra-passe, introduza o endereço de e-mail associado à sua conta:
@@ -489,7 +489,7 @@ Para continuar, insira a sua palavra-passe.Palavra-passe:SubmeterConectado como
- Servidor (Home Server)
+ Servidor (homeserver)Servidor de identidadeVerificação pendenteVerifique o seu e-mail e clique no link que contém. Uma vez feito isso, clique em continuar.
diff --git a/vector/src/main/res/values-si/strings.xml b/vector/src/main/res/values-si/strings.xml
index e1939a1ed6..5b7811c1e3 100644
--- a/vector/src/main/res/values-si/strings.xml
+++ b/vector/src/main/res/values-si/strings.xml
@@ -5,4 +5,5 @@
සැකසුම්කාමරයඅඳුරු තේමාව
+ ඔබගේ ආරාධනය
\ No newline at end of file
diff --git a/vector/src/main/res/values-sq/strings.xml b/vector/src/main/res/values-sq/strings.xml
index 73512f103c..553761ce26 100644
--- a/vector/src/main/res/values-sq/strings.xml
+++ b/vector/src/main/res/values-sq/strings.xml
@@ -452,7 +452,7 @@
Hidheni tejAnëtarë listePo njëkohësohet…
- Hidhu te mesazhi i parë i palexuar.
+ Hidhu te të palexuarit.Jeni ftuar të merrni pjesë në këtë dhomë nga %snjë dhomëFjalosje e Re
@@ -2796,4 +2796,13 @@
Jepni emrin e e një shërbyesi të ri që doni të eksploroni.Shtoni shërbyes të riShërbyesi juaj
+ Hëpërhë, personat mund të mos jenë në gjendje të hyjnë në çfarëdo dhome private që krijoni.
+\n
+\nDo ta përmirësojmë këtë punë, si pjesë e versionit beta, thjesht donim t’ua bënim të ditur.
+ Hapësirat për anëtarë ekipi ende s’janë tërësisht gati, por mund t’i provoni
+ Vazhdo, Sido Qoftë
+ Na ndjeni, ndodhi një gabim teksa provohej të hyhej: %s
+ Adresë hapësire
+ Adresa hapësire
+ Shihni dhe administroni adresa në këtë hapësirë.
\ No newline at end of file
diff --git a/vector/src/main/res/values-sv/strings.xml b/vector/src/main/res/values-sv/strings.xml
index ce65fd9a5a..d9b58bb605 100644
--- a/vector/src/main/res/values-sv/strings.xml
+++ b/vector/src/main/res/values-sv/strings.xml
@@ -2806,4 +2806,9 @@
Ange namnet för en ny server du vill utforska.Lägg till en ny serverDin server
+ För tillfället så kan folk kanske inte gå med i privata rum som du skapar.
+\n
+\nVi kommer att förbättra detta som en del av betan, men ville låta dig veta.
+ Lagkamratsutrymmen är inte riktigt färdiga men du kan ändå testa dem
+ Fortsätt ändå
\ No newline at end of file
diff --git a/vector/src/main/res/values-ta/strings.xml b/vector/src/main/res/values-ta/strings.xml
new file mode 100644
index 0000000000..a6b3daec93
--- /dev/null
+++ b/vector/src/main/res/values-ta/strings.xml
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/vector/src/main/res/values-th/strings.xml b/vector/src/main/res/values-th/strings.xml
index a55bb93992..091dc268a0 100644
--- a/vector/src/main/res/values-th/strings.xml
+++ b/vector/src/main/res/values-th/strings.xml
@@ -300,9 +300,9 @@
ทำให้เป็นผู้ดูแลทำให้เป็นผู้ควบคุมเอาออกจากห้องนี้
- 1 สมาชิก
+ สมาชิก 1 คน
- %d สมาชิก
+ สมาชิก %d คนส่งไฟล์ไดเรกทอรีผู้ใช้
diff --git a/vector/src/main/res/values-tr/strings.xml b/vector/src/main/res/values-tr/strings.xml
index 3e80f9f496..1d62631111 100644
--- a/vector/src/main/res/values-tr/strings.xml
+++ b/vector/src/main/res/values-tr/strings.xml
@@ -1820,4 +1820,13 @@
%1$s %2$s widgetını kaldırdı%1$s uçtan uca şifrelemeyi etkinleştirdi (%2$s)bilinmeyen (%s).
+ %s güncellendi.
+ birisi.
+ Gelecek mesajları %1$s için görünür yaptınız.
+ %1$s, gelecek mesajları %2$s için görünür hale getirdi.
+ Oda geçmişini %1$s için görünür yaptınız.
+ %1$s oda geçmişini %2$s \'ye görünür yaptı.
+ Çağrı kurulumu için verileri gönderdiniz.
+ %s kurulumu çağrı verileri gönderildi.
+ %1$s başlıklı görünen ad silindi.
\ No newline at end of file
diff --git a/vector/src/main/res/values-zh-rCN/strings.xml b/vector/src/main/res/values-zh-rCN/strings.xml
index d3cc148943..cee86a356b 100644
--- a/vector/src/main/res/values-zh-rCN/strings.xml
+++ b/vector/src/main/res/values-zh-rCN/strings.xml
@@ -725,7 +725,7 @@
进入聊天室进入一个聊天室输入聊天室 ID 或者聊天室别名
- 跳到第一条未读消息。
+ 跳到未读主页显示固定含错过通知的聊天室置顶含有未读消息的聊天室
@@ -2763,4 +2763,13 @@
输入你想要探索的新服务器的名称。添加一个新的服务器你的服务器
+ 眼下,人们可能无法加入您设置的任何私人房间。
+\n
+\n作为测试版的一部分,我们将对此进行改进,只是想让你知道。
+ 队友空间还没有完全准备好,但你仍然可以尝试一下
+ 不论如何继续
+ 抱歉,尝试加入 %s 时发生了一个错误
+ 空间地址
+ 查看和管理这个空间的地址。
+ 空间地址
\ No newline at end of file
diff --git a/vector/src/main/res/values-zh-rTW/strings.xml b/vector/src/main/res/values-zh-rTW/strings.xml
index 1c67fdbbcd..21ff01b42c 100644
--- a/vector/src/main/res/values-zh-rTW/strings.xml
+++ b/vector/src/main/res/values-zh-rTW/strings.xml
@@ -502,7 +502,7 @@
列出成員打開標頭正在同步……
- 跳到第一個未讀的訊息。
+ 跳到未讀你已經被 %s 邀請加入此聊天室邀請已經被傳送給 %s,但與此帳號沒有進行關連。
\n你也許希望以不同帳號登入,或將電子郵件加入到您的帳號。
@@ -2753,4 +2753,13 @@
輸入您想要探索的新伺服器名稱。加入新的伺服器您的伺服器
+ 目前,人們可能無法加入您開啟的任何私人聊天室。
+\n
+\n作為測試版的一部分,我們會對此進行改善,但想先讓您知道。
+ 隊友空間還沒有完全準備好,但您仍可以試試看
+ 無論如何都要繼續
+ 抱歉,試圖加入時發生錯誤:%s
+ 空間地址
+ 檢視與管理此空間的地址。
+ 空間地址
\ No newline at end of file
diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml
index 6908fe5d79..e3385765a8 100644
--- a/vector/src/main/res/values/strings.xml
+++ b/vector/src/main/res/values/strings.xml
@@ -74,19 +74,19 @@
You upgraded here.%s set the server ACLs for this room.You set the server ACLs for this room.
- • Server matching %s are banned.
- • Server matching %s are allowed.
- • Server matching IP literals are allowed.
- • Server matching IP literals are banned.
+ • Servers matching %s are banned.
+ • Servers matching %s are allowed.
+ • Servers matching IP literals are allowed.
+ • Servers matching IP literals are banned.%s changed the server ACLs for this room.You changed the server ACLs for this room.
- • Server matching %s are now banned.
- • Server matching %s were removed from the ban list.
- • Server matching %s are now allowed.
- • Server matching %s were removed from the allowed list.
- • Server matching IP literals are now allowed.
- • Server matching IP literals are now banned.
+ • Servers matching %s are now banned.
+ • Servers matching %s were removed from the ban list.
+ • Servers matching %s are now allowed.
+ • Servers matching %s were removed from the allowed list.
+ • Servers matching IP literals are now allowed.
+ • Servers matching IP literals are now banned.No change.🎉 All servers are banned from participating! This room can no longer be used.
@@ -386,12 +386,16 @@
ResetStart Chatting
+
+ Some permissions are missing to perform this action, please grant the permissions from the system settings.
+ To perform this action, please grant the Camera permission from the system settings.Ongoing conference call.\nJoin as %1$s or %2$sVoiceVideoCannot start the call, please try later
+ Missing permissions"Due to missing permissions, some features may be missing…"Due to missing permissions, this action is not possible.You need permission to invite to start a conference in this room
@@ -538,7 +542,8 @@
Log inSign outHomeserver URL
- Identity Server URL
+ Homeserver API URL
+ Identity server URLSearchStart New Chat
@@ -628,7 +633,7 @@
This homeserver would like to make sure you are not a robotUsername in useHomeserver:
- Identity Server:
+ Identity server:I have verified my email addressTo reset your password, enter the email address linked to your account:The email address linked to your account must be entered.
@@ -722,6 +727,14 @@
Call connectedCall connecting…Call ended
+
+ Missed audio call
+ %d missed audio calls
+
+
+ Missed video call
+ %d missed video calls
+ Calling…Incoming CallIncoming Video Call
@@ -926,7 +939,7 @@
Resend unsent messagesDelete unsent messagesFile not found
- You do not have permission to post to this room
+ You do not have permission to post to this room.%d new message%d new messages
@@ -1123,8 +1136,8 @@
Add AccountToken Registration
- FCM token successfully registered to HomeServer.
- Failed to register FCM token to HomeServer:\n%1$s
+ FCM token successfully registered to homeserver.
+ Failed to register FCM token to homeserver:\n%1$sTest PushThe application is waiting for the PUSH
@@ -1166,7 +1179,7 @@
NormalReduced privacyThe app needs permission to run in the background
- The apps does not need to connect to the HomeServer in the background, it should reduce battery usage
+ The apps does not need to connect to the homeserver in the background, it should reduce battery usage• Notifications are sent via Firebase Cloud Messaging• Notifications only contain meta data• Message content of the notification is located securely direct from the Matrix homeserver
@@ -1231,7 +1244,7 @@
OtherAdvancedIntegrations
- Use an Integration Manager to manage bots, bridges, widgets and sticker packs.\nIntegration Managers receive configuration data, and can modify widgets, send room invites and set power levels on your behalf.
+ Use an integration manager to manage bots, bridges, widgets and sticker packs.\nIntegration managers receive configuration data, and can modify widgets, send room invites and set power levels on your behalf.CryptographyCryptography Keys ManagementNotification Targets
@@ -1316,9 +1329,9 @@
Logged in asHomeserver
- Identity Server
+ Identity serverAllow integrations
- Integration Manager
+ Integration managerIntegrations are disabled"Enable 'Allow integrations' in Settings to do this."
@@ -1693,7 +1706,7 @@
Use the microphoneRead DRM protected Media
-
+
Unable to create widget.Failed to send request.
@@ -1820,7 +1833,7 @@
Please enter a username.Please enter your password.
- This room has been replaced and is no longer active
+ This room has been replaced and is no longer active.The conversation continues hereThis room is a continuation of another conversationClick here to see older messages
@@ -1904,7 +1917,7 @@
The recovery key has been saved to \'%s\'.\n\nWarning: this file may be deleted if the application is uninstalled.The recovery key has been saved.
- A backup already exist on your HomeServer
+ A backup already exist on your homeserverIt looks like you already have setup key backup from another session. Do you want to replace it with the one you’re creating?ReplaceStop
@@ -2070,7 +2083,7 @@
Unknown Error
- You are not using any Identity Server
+ You are not using any identity serverNo identity server is configured, it is required to reset your password.It looks like you’re trying to connect to another homeserver. Do you want to sign out?
@@ -2268,7 +2281,7 @@
Give consentSend emails and phone numbers
- In order to discover existing contacts you know, do you accept to send your contact data (phone numbers and/or emails) to the configured Identity Server (%1$s)?\n\nFor more privacy, the sent data will be hashed before being sent.
+ In order to discover existing contacts you know, do you accept to send your contact data (phone numbers and/or emails) to the configured identity server (%1$s)?\n\nFor more privacy, the sent data will be hashed before being sent.Enter an identity server URLCould not connect to identity server
@@ -2630,9 +2643,12 @@
YouScan the code with the other user\'s device to securely verify each other
+ Scan the code with your other device or switch and scan with this deviceScan their code
+ Scan with this deviceCan\'t scanIf you\'re not in person, compare emoji instead
+ Verify by comparing emoji insteadVerify by comparing emojis
@@ -2735,6 +2751,11 @@
Server file upload limitYour homeserver accepts attachments (files, media, etc.) with a size up to %s.The limit is unknown.
+
+ Room Versions 👓
+ Default Version
+ stable
+ unstableNo cryptographic information available
@@ -3295,6 +3316,7 @@
Create a SpaceJoin the Space with the given idLeave room with given id (or current room if null)
+ Upgrades a room to a new versionSendingSent
@@ -3406,5 +3428,21 @@
"Teammate spaces aren’t quite ready but you can still give them a try""At the moment people might not be able to join any private rooms you make.\n\nWe’ll be improving this as part of the beta, but just wanted to let you know."
+
+ Join replacement room
+ Please be patient, it may take some time.
+
+ Upgrade
+ Upgrade public room
+ Upgrade private room
+ Upgrading a room is an advanced action and is usually recommended when a room is unstable due to bugs, missing features or security vulnerabilities.\nThis usually only affects how the room is processed on the server.
+ You\'ll upgrade this room from %s to %s.
+ Automatically invite users
+ Automatically update space parent
+ You need permission to upgrade a room
+
+ This room is running room version %s, which this homeserver has marked as unstable.
+ Upgrade to the recommended room version
+
Sorry, an error occurred while trying to join: %s