mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2024-11-22 09:25:49 +03:00
Merge branch 'develop' into feature/improve_perf
This commit is contained in:
commit
f28e3ca504
206 changed files with 7331 additions and 556 deletions
|
@ -7,6 +7,7 @@
|
||||||
<w>ciphertext</w>
|
<w>ciphertext</w>
|
||||||
<w>coroutine</w>
|
<w>coroutine</w>
|
||||||
<w>decryptor</w>
|
<w>decryptor</w>
|
||||||
|
<w>displayname</w>
|
||||||
<w>emoji</w>
|
<w>emoji</w>
|
||||||
<w>emojis</w>
|
<w>emojis</w>
|
||||||
<w>fdroid</w>
|
<w>fdroid</w>
|
||||||
|
|
45
CHANGES.md
45
CHANGES.md
|
@ -1,4 +1,37 @@
|
||||||
Changes in RiotX 0.22.0 (2020-XX-XX)
|
Changes in RiotX 0.23.0 (2020-XX-XX)
|
||||||
|
===================================================
|
||||||
|
|
||||||
|
Features ✨:
|
||||||
|
- Call with WebRTC support (##611)
|
||||||
|
- Add capability to change the display name (#1529)
|
||||||
|
|
||||||
|
Improvements 🙌:
|
||||||
|
- "Add Matrix app" menu is now always visible (#1495)
|
||||||
|
|
||||||
|
Bugfix 🐛:
|
||||||
|
- Fix dark theme issue on login screen (#1097)
|
||||||
|
- Incomplete predicate in RealmCryptoStore#getOutgoingRoomKeyRequest (#1519)
|
||||||
|
- User could not redact message that they have sent (#1543)
|
||||||
|
- Use vendor prefix for non merged MSC (#1537)
|
||||||
|
|
||||||
|
Translations 🗣:
|
||||||
|
-
|
||||||
|
|
||||||
|
SDK API changes ⚠️:
|
||||||
|
-
|
||||||
|
|
||||||
|
Build 🧱:
|
||||||
|
- Enable code optimization (Proguard)
|
||||||
|
- SDK is now API level 21 minimum, and so RiotX (#405)
|
||||||
|
|
||||||
|
Other changes:
|
||||||
|
- Use `SharedPreferences#edit` extension function consistently (#1545)
|
||||||
|
- Use `retrofit2.Call.awaitResponse` extension provided by Retrofit 2. (#1526)
|
||||||
|
- Fix minor typo in contribution guide (#1512)
|
||||||
|
- Fix self-assignment of callback in `DefaultRoomPushRuleService#setRoomNotificationState` (#1520)
|
||||||
|
- Random housekeeping clean-ups indicated by Lint (#1520, #1541)
|
||||||
|
|
||||||
|
Changes in RiotX 0.22.0 (2020-06-15)
|
||||||
===================================================
|
===================================================
|
||||||
|
|
||||||
Features ✨:
|
Features ✨:
|
||||||
|
@ -23,15 +56,7 @@ Bugfix 🐛:
|
||||||
- Fix status bar icon contrast on API in [21,23[
|
- Fix status bar icon contrast on API in [21,23[
|
||||||
- Wrong /query request (#1444)
|
- Wrong /query request (#1444)
|
||||||
- Make Credentials.homeServer optional because it is deprecated (#1443)
|
- Make Credentials.homeServer optional because it is deprecated (#1443)
|
||||||
|
- Fix issue on dark themes, after alert popup dismiss
|
||||||
Translations 🗣:
|
|
||||||
-
|
|
||||||
|
|
||||||
SDK API changes ⚠️:
|
|
||||||
-
|
|
||||||
|
|
||||||
Build 🧱:
|
|
||||||
-
|
|
||||||
|
|
||||||
Other changes:
|
Other changes:
|
||||||
- Send plain text in the body of events containing formatted body, as per https://matrix.org/docs/spec/client_server/latest#m-room-message-msgtypes
|
- Send plain text in the body of events containing formatted body, as per https://matrix.org/docs/spec/client_server/latest#m-room-message-msgtypes
|
||||||
|
|
|
@ -19,7 +19,11 @@ An Android Studio template has been added to the project to help creating all fi
|
||||||
|
|
||||||
To install the template (to be done only once):
|
To install the template (to be done only once):
|
||||||
- Go to folder `./tools/template`.
|
- Go to folder `./tools/template`.
|
||||||
- Run the script `./configure.sh`.
|
- Mac OSX: Run the script `./configure.sh`.
|
||||||
|
|
||||||
|
Linux: Run `ANDROID_STUDIO=/path/to/android-studio ./configure`
|
||||||
|
- e.g. `ANDROID_STUDIO=/usr/local/android-studio ./configure`
|
||||||
|
|
||||||
- Restart Android Studio.
|
- Restart Android Studio.
|
||||||
|
|
||||||
To create a new screen:
|
To create a new screen:
|
||||||
|
@ -27,7 +31,7 @@ To create a new screen:
|
||||||
- Then right click on the package, and select `New/New Vector/RiotX Feature`.
|
- Then right click on the package, and select `New/New Vector/RiotX Feature`.
|
||||||
- Follow the Wizard, especially replace `Main` by something more relevant to your feature.
|
- Follow the Wizard, especially replace `Main` by something more relevant to your feature.
|
||||||
- Click on `Finish`.
|
- Click on `Finish`.
|
||||||
- Remainning steps are described as TODO in the generated files, or will be pointed out by the compilator, or at runtime :)
|
- Remaining steps are described as TODO in the generated files, or will be pointed out by the compilator, or at runtime :)
|
||||||
|
|
||||||
Note that if the templates are modified, the only things to do is to restart Android Studio for the change to take effect.
|
Note that if the templates are modified, the only things to do is to restart Android Studio for the change to take effect.
|
||||||
|
|
||||||
|
|
|
@ -11,8 +11,8 @@ RiotX is an Android Matrix Client currently in beta but in active development.
|
||||||
|
|
||||||
It is a total rewrite of [Riot-Android](https://github.com/vector-im/riot-android) with a new user experience. RiotX will become the official replacement as soon as all features are implemented.
|
It is a total rewrite of [Riot-Android](https://github.com/vector-im/riot-android) with a new user experience. RiotX will become the official replacement as soon as all features are implemented.
|
||||||
|
|
||||||
[<img src="https://play.google.com/intl/en_us/badges/images/generic/en_badge_web_generic.png" alt="Get it on Google Play" height="60">](https://play.google.com/store/apps/details?id=im.vector.riotx)
|
[<img src="resources/img/google-play-badge.png" alt="Get it on Google Play" height="60">](https://play.google.com/store/apps/details?id=im.vector.riotx)
|
||||||
[<img src="https://f-droid.org/badge/get-it-on.png" alt="Get it on F-Droid" height="60">](https://f-droid.org/app/im.vector.riotx)
|
[<img src="resources/img/f-droid-badge.png" alt="Get it on F-Droid" height="60">](https://f-droid.org/app/im.vector.riotx)
|
||||||
|
|
||||||
Nightly build: [![Buildkite](https://badge.buildkite.com/657d3db27364448d69d54f66c690f7788bc6aa80a7628e37f3.svg?branch=develop)](https://buildkite.com/matrix-dot-org/riotx-android/builds?branch=develop)
|
Nightly build: [![Buildkite](https://badge.buildkite.com/657d3db27364448d69d54f66c690f7788bc6aa80a7628e37f3.svg?branch=develop)](https://buildkite.com/matrix-dot-org/riotx-android/builds?branch=develop)
|
||||||
|
|
||||||
|
|
420
docs/voip_signaling.md
Normal file
420
docs/voip_signaling.md
Normal file
|
@ -0,0 +1,420 @@
|
||||||
|
╔════════════════════════════════════════════════╗
|
||||||
|
║ ║
|
||||||
|
║A] Placing a call offer ║
|
||||||
|
║ ║
|
||||||
|
╚════════════════════════════════════════════════╝
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
┌───────────────┐
|
||||||
|
│ Matrix │
|
||||||
|
├───────────────┴ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
||||||
|
│
|
||||||
|
│
|
||||||
|
│
|
||||||
|
┌─────────────────┐ │ ┌───────────────────────────┐ ┌─────────────────┐
|
||||||
|
│ Caller │ │ Signaling Room │ │ │ Callee │
|
||||||
|
└─────────────────┘ │ ├───────────────────────────┤ └─────────────────┘
|
||||||
|
┌────┐ │ │ │
|
||||||
|
│ 3 │ │ │ ┌────────────────────┐ │
|
||||||
|
┌─────────────────┐──────┴────┴──────────────────────────┼─▶│ m.call.invite │ │ │ ┌─────────────────┐
|
||||||
|
│ │ │ │ │ mx event │ │ │ │
|
||||||
|
│ │ │ └────────────────────┘ │ │ │ │
|
||||||
|
│ │ │ │ │ │ │
|
||||||
|
│ Riot.im │ │ │ │ │ Riot.im │
|
||||||
|
┌──│ App │ │ │ │ │ App │
|
||||||
|
│ │ │ │ │ │ │ │
|
||||||
|
│ │ │ │ │ │ │ │
|
||||||
|
│ │ │ │ │ │ │ │
|
||||||
|
│ └─────────────────┘ │ │ │ └─────────────────┘
|
||||||
|
┌────┤ ▲ │ │ │
|
||||||
|
│ 1 │ ├────┐ │ └───────────────────────────┘
|
||||||
|
└────┤ │ 2 │ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘
|
||||||
|
│ ┌──┴────┴─────────┐ ┌─────────────────┐
|
||||||
|
│ │ │ │ │
|
||||||
|
│ │ │ │ │
|
||||||
|
│ │ WebRtc │ │ WebRtc │
|
||||||
|
└─▶│ │ │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ │ │
|
||||||
|
└─────────────────┘ └─────────────────┘
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
┌────┐
|
||||||
|
│ 1 │ The Caller app get access to system resources (camera, mic), eventually stun/turn servers, define some
|
||||||
|
└────┘ constrains (video quality, format) and pass it to WebRtc in order to create a Peer Call offer
|
||||||
|
|
||||||
|
┌────┐
|
||||||
|
│ 2 │ The WebRtc layer creates a call Offer (sdp) that needs to be sent to callee
|
||||||
|
└────┘
|
||||||
|
|
||||||
|
┌────┐ The app layer, takes the webrtc offer, encapsulate it in a matrix event adds a callId and send it to the other
|
||||||
|
│ 3 │ user via the room
|
||||||
|
└────┘
|
||||||
|
┌──────────────┐
|
||||||
|
│ mx event │
|
||||||
|
├──────────────┴────────┐
|
||||||
|
│ type: m.call.invite │
|
||||||
|
│ + callId │
|
||||||
|
│ │
|
||||||
|
│ ┌──────────────────┐ │
|
||||||
|
│ │ webrtc sdp │ │
|
||||||
|
│ └──────────────────┘ │
|
||||||
|
└───────────────────────┘
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
╔════════════════════════════════════════════════╗
|
||||||
|
║ ║
|
||||||
|
║B] Sending connection establishment info ║
|
||||||
|
║ ║
|
||||||
|
╚════════════════════════════════════════════════╝
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
┌───────────────┐
|
||||||
|
│ Matrix │
|
||||||
|
├───────────────┴ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
||||||
|
│
|
||||||
|
│
|
||||||
|
│
|
||||||
|
┌─────────────────┐ │ ┌───────────────────────────┐ ┌─────────────────┐
|
||||||
|
│ Caller │ │ Signaling Room │ │ │ Callee │
|
||||||
|
└─────────────────┘ │ ├───────────────────────────┤ └─────────────────┘
|
||||||
|
│ ┌────────────────────┐ │ │
|
||||||
|
│ │ │ m.call.invite │ │
|
||||||
|
┌─────────────────┐ │ │ mx event │ │ │ ┌─────────────────┐
|
||||||
|
│ │ ┌────┐ │ │ └────────────────────┘ │ │ │
|
||||||
|
│ │ │ 3 │ │ ┌────────────────────┐ │ │ │ │
|
||||||
|
│ │──────┴────┴───────┼──────────────────┼─▶│ m.call.candidates │ │ │ │
|
||||||
|
│ Riot.im │ │ │ mx event │ │ │ │ Riot.im │
|
||||||
|
│ App │ │ │ └────────────────────┘ │ │ App │
|
||||||
|
│ │ │ │ │ │ │
|
||||||
|
│ │ │ │ │ │ │
|
||||||
|
│ │ │ │ │ │ │
|
||||||
|
└─────────────────┘ │ │ │ └─────────────────┘
|
||||||
|
▲ │ │ │
|
||||||
|
├────┐ │ └───────────────────────────┘
|
||||||
|
│ 2 │ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘
|
||||||
|
┌───────┴────┴────┐ ┌─────────────────┐
|
||||||
|
│ │ │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ WebRtc │ ┌───────────────┐ │ WebRtc │
|
||||||
|
│ │ │ Stun / Turn │ │ │
|
||||||
|
│ │ ├───────────────┴ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ │ │
|
||||||
|
│ │ │ │ │
|
||||||
|
└─────────────────┘ │ └─────────────────┘
|
||||||
|
▲ │
|
||||||
|
│ │
|
||||||
|
└──────────┬────┬───────────▶ │
|
||||||
|
┌───────────────┐ │ 1 │ │
|
||||||
|
│ │ └────┘ │
|
||||||
|
│ Network Stack │ │
|
||||||
|
│ │ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘
|
||||||
|
│ │
|
||||||
|
└───────────────┘
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
┌────┐
|
||||||
|
│ 1 │ The WebRtc layer gathers information on how it can be reach by the other peer directly (Ice candidates)
|
||||||
|
└────┘
|
||||||
|
┌──────────────────────────────────────────────────────────────────┐
|
||||||
|
│candidate:1 1 tcp 1518149375 127.0.0.1 35990 typ host │
|
||||||
|
└──────────────────────────────────────────────────────────────────┘
|
||||||
|
┌──────────────────────────────────────────────────────────────────┐
|
||||||
|
│candidate:2 1 UDP 2130706431 192.168.1.102 1816 typ host │
|
||||||
|
└──────────────────────────────────────────────────────────────────┘
|
||||||
|
|
||||||
|
┌────┐
|
||||||
|
│ 2 │ The WebRTC layer notifies the App layer when it finds new Ice Candidates
|
||||||
|
└────┘
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
┌────┐ The app layer, takes the ice candidates, encapsulate them in one or several matrix event adds the callId and
|
||||||
|
│ 3 │ send it to the other user via the room
|
||||||
|
└────┘
|
||||||
|
┌──────────────┐
|
||||||
|
│ mx event │
|
||||||
|
├──────────────┴────────────────────────┐
|
||||||
|
│ type: m.call.candidates │
|
||||||
|
│ │
|
||||||
|
│ +CallId │
|
||||||
|
│ │
|
||||||
|
│ ┌──────────────────┐ │
|
||||||
|
│ │ice candidate sdp │ │
|
||||||
|
│ └──────────────────┘ │
|
||||||
|
│ ┌──────────────────┐ │
|
||||||
|
│ │ice candidate sdp │ │
|
||||||
|
│ └──────────────────┘ │
|
||||||
|
│ ┌──────────────────┐ │
|
||||||
|
│ │ice candidate sdp │ │
|
||||||
|
│ └──────────────────┘ │
|
||||||
|
└───────────────────────────────────────┘
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
╔════════════════════════════════════════════════╗
|
||||||
|
║ ║
|
||||||
|
║C] Receiving a call offer ║
|
||||||
|
║ ║
|
||||||
|
╚════════════════════════════════════════════════╝
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
┌───────────────┐
|
||||||
|
│ Matrix │
|
||||||
|
├───────────────┴ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
||||||
|
│
|
||||||
|
│ ┌─────────────────┐
|
||||||
|
│ │ Callee │
|
||||||
|
┌─────────────────┐ │ ┌───────────────────────────┐ └─────────────────┘
|
||||||
|
│ Caller │ │ Signaling Room │ │
|
||||||
|
└─────────────────┘ │ ├───────────────────────────┤
|
||||||
|
│ ┌────────────────────┐ │ │ ┌─────────────────┐
|
||||||
|
│ │ │ m.call.invite │───┼────────────────────────────┬────┬───▶│ │
|
||||||
|
┌─────────────────┐ │ │ mx event │ │ │ │ 1 │ │ │
|
||||||
|
│ │ │ │ └────────────────────┘ │ └────┘ │ │
|
||||||
|
│ │ │ ┌────────────────────┐ │ │ │ Riot.im │
|
||||||
|
│ │ │ │ │ m.call.candidates │ │ │ App │
|
||||||
|
│ Riot.im │ │ │ mx event │ │ │ │ │
|
||||||
|
│ App │ │ │ └────────────────────┘ │ │ │
|
||||||
|
│ │ │ ┌────────────────────┐◀──┼─────────────────┼───┬────┬───────────┤ │
|
||||||
|
│ │◀──────────────────┼──────────────────┼──│ m.call.answer │ │ │ 4 │ └──┬──────────────┘
|
||||||
|
│ │ │ │ mx event │ │ │ └────┘ ├────┐ ▲
|
||||||
|
└────┬────────────┘ │ │ └────────────────────┘ │ │ 2 │ ├────┐
|
||||||
|
│ │ │ │ ├────┘ │ 3 │
|
||||||
|
│ │ └───────────────────────────┘ ┌──▼─────────┴────┤
|
||||||
|
┌────▼────────────┐ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘ │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ │ WebRtc │
|
||||||
|
│ WebRtc │ │ ┌──┴─────────────────┐
|
||||||
|
│ │ │ │ caller offer │
|
||||||
|
┌──────────┴─────────┐ │ │ └──┬─────────────────┘
|
||||||
|
│ callee answer │ │ └─────────────────┘
|
||||||
|
└────────────────────┴───────┘
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
┌────┐
|
||||||
|
│ 1 │ Bob receives a call.invite event in a room, then creates a WebRTC peer connection object
|
||||||
|
└────┘
|
||||||
|
|
||||||
|
┌────┐
|
||||||
|
│ 2 │ The encapsulated call offer sdp from the mx event is transmitted to WebRTC
|
||||||
|
└────┘
|
||||||
|
|
||||||
|
┌────┐
|
||||||
|
│ 3 │ WebRTC then creates a call answer for the offer and send it back to app layer
|
||||||
|
└────┘
|
||||||
|
|
||||||
|
|
||||||
|
┌────┐ The app layer, takes the webrtc answer, encapsulate it in a matrix event adds a callId and send it to the
|
||||||
|
│ 3 │ other user via the room
|
||||||
|
└────┘
|
||||||
|
┌──────────────┐
|
||||||
|
│ mx event │
|
||||||
|
├──────────────┴────────┐
|
||||||
|
│ type: m.call.answer │
|
||||||
|
│ + callId │
|
||||||
|
│ │
|
||||||
|
│ ┌──────────────────┐ │
|
||||||
|
│ │ webrtc sdp │ │
|
||||||
|
│ └──────────────────┘ │
|
||||||
|
└───────────────────────┘
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
╔════════════════════════════════════════════════╗
|
||||||
|
║ ║
|
||||||
|
║D] Callee sends connection establishment info ║
|
||||||
|
║ ║
|
||||||
|
╚════════════════════════════════════════════════╝
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
┌───────────────┐
|
||||||
|
│ Matrix │
|
||||||
|
├───────────────┴ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
||||||
|
│
|
||||||
|
│
|
||||||
|
│
|
||||||
|
┌─────────────────┐ │ ┌───────────────────────────┐ ┌─────────────────┐
|
||||||
|
│ Caller │ │ Signaling Room │ │ │ Callee │
|
||||||
|
└─────────────────┘ │ ├───────────────────────────┤ └─────────────────┘
|
||||||
|
│ ┌────────────────────┐ │ │
|
||||||
|
│ │ │ m.call.invite │ │
|
||||||
|
┌─────────────────┐ │ │ mx event │ │ │ ┌─────────────────┐
|
||||||
|
│ │ │ │ └────────────────────┘ │ │ │
|
||||||
|
│ │ │ ┌────────────────────┐ │ │ │ │
|
||||||
|
│ │ │ │ │ m.call.candidates │ │ │ │
|
||||||
|
│ Riot.im │ │ │ mx event │ │ │ │ Riot.im │
|
||||||
|
│ App │ │ │ └────────────────────┘ │ ┌────┐ │ App │
|
||||||
|
│ │ │ ┌────────────────────┐ │ │ │ 3 │ │ │
|
||||||
|
│ │◀──────────────────┼┐ │ │ m.call.answer │ │ ┌───────┴────┴────────│ │
|
||||||
|
│ │ │ │ │ mx event │ │ ││ │ │
|
||||||
|
└─────────────────┘ ││ │ └────────────────────┘ │ │ └─────────────────┘
|
||||||
|
│ │ │ ┌────────────────────┐ │ ││ ▲
|
||||||
|
│ │└─────────────────┼──│ m.call.candidates │ │ │ ├────┐
|
||||||
|
▼ │ │ mx event │◀──┼────────────────┘│ │ 2 │
|
||||||
|
┌─────────────────┐ │ │ └────────────────────┘ │ ┌────┴────┴───────┐
|
||||||
|
│ │ └───────────────────────────┘ │ │ │
|
||||||
|
│ │ │ │ │
|
||||||
|
│ WebRtc │ │ │ WebRtc │
|
||||||
|
│ │ └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ │ ┌───┴────────────────┐
|
||||||
|
│ │ │ │ caller offer │
|
||||||
|
┌────────┴───────────┐ │ │ └───┬────────────────┘
|
||||||
|
│ callee answer ├─────┘ ┌───────────────┐ └─────────────────┘
|
||||||
|
├────────────────────┤ │ Stun / Turn │ ▲
|
||||||
|
│ callee ice │ ├───────────────┴ ─ ─ ─ ─ ─ ─ ─ ─ ┌────┐ │
|
||||||
|
│ candidates │ │ │ 1 │ │
|
||||||
|
└────────────────────┘ │ ├────┴──┴───────┐
|
||||||
|
│ │ │
|
||||||
|
│ │ Network Stack │
|
||||||
|
│◀─────────────────────┤ │
|
||||||
|
│ │ │
|
||||||
|
│ └───────────────┘
|
||||||
|
│
|
||||||
|
─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘
|
||||||
|
|
||||||
|
|
||||||
|
┌────┐
|
||||||
|
│ 1 │ The WebRtc layer gathers information on how it can be reach by the other peer directly (Ice candidates)
|
||||||
|
└────┘
|
||||||
|
┌──────────────────────────────────────────────────────────────────┐
|
||||||
|
│candidate:1 1 tcp 1518149375 127.0.0.1 35990 typ host │
|
||||||
|
└──────────────────────────────────────────────────────────────────┘
|
||||||
|
┌──────────────────────────────────────────────────────────────────┐
|
||||||
|
│candidate:2 1 UDP 2130706431 192.168.1.102 1816 typ host │
|
||||||
|
└──────────────────────────────────────────────────────────────────┘
|
||||||
|
|
||||||
|
┌────┐
|
||||||
|
│ 2 │ The WebRTC layer notifies the App layer when it finds new Ice Candidates
|
||||||
|
└────┘
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
┌────┐ The app layer, takes the ice candidates, encapsulate them in one or several matrix event adds the callId and
|
||||||
|
│ 3 │ send it to the other user via the room
|
||||||
|
└────┘
|
||||||
|
┌──────────────┐
|
||||||
|
│ mx event │
|
||||||
|
├──────────────┴────────────────────────┐
|
||||||
|
│ type: m.call.candidates │
|
||||||
|
│ │
|
||||||
|
│ +CallId │
|
||||||
|
│ │
|
||||||
|
│ ┌──────────────────┐ │
|
||||||
|
│ │ice candidate sdp │ │
|
||||||
|
│ └──────────────────┘ │
|
||||||
|
│ ┌──────────────────┐ │
|
||||||
|
│ │ice candidate sdp │ │
|
||||||
|
│ └──────────────────┘ │
|
||||||
|
│ ┌──────────────────┐ │
|
||||||
|
│ │ice candidate sdp │ │
|
||||||
|
│ └──────────────────┘ │
|
||||||
|
└───────────────────────────────────────┘
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
╔════════════════════════════════════════════════╗
|
||||||
|
║ ║
|
||||||
|
║D] Caller Callee connection ║
|
||||||
|
║ ║
|
||||||
|
╚════════════════════════════════════════════════╝
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
┌───────────────┐
|
||||||
|
┌─────────────────┐ │ Matrix │ ┌─────────────────┐
|
||||||
|
│ Caller │ ├───────────────┴ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ │ Callee │
|
||||||
|
└─────────────────┘ │ └─────────────────┘
|
||||||
|
│
|
||||||
|
│
|
||||||
|
┌─────────────────┐ │ ┌─────────────────┐
|
||||||
|
│ │ │ │ │
|
||||||
|
│ │ │ │ │
|
||||||
|
│ │ │ │ │
|
||||||
|
│ Riot.im │ └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ │ Riot.im │
|
||||||
|
│ App │ │ App │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ │ │
|
||||||
|
└─────────────────┘ └─────────────────┘
|
||||||
|
┌───────────────┐
|
||||||
|
│ Internet │
|
||||||
|
├───────────────┴ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
||||||
|
┌─────────────────┐ │ ┌─────────────────┐
|
||||||
|
│ │ │ │ │
|
||||||
|
│ ├───────────────────────────────────────────────────────────────────────────────────┴─────────────────────┤ │
|
||||||
|
│ WebRtc │█████████████████████████████████████████████████████████████████████████████████████████████████████████│ WebRtc │
|
||||||
|
┌─────────────┴──────┐ ├────────────────────────────────────────┬──────────────────────────┬───────────────┬─────────────────────┤ ┌─────┴──────────────┐
|
||||||
|
│ callee answer │ │ │ │ Video / Audio Stream │ │ │ caller offer │
|
||||||
|
├────────────────────┤ │ └──────────────────────────┘ │ │ ├────────────────────┤
|
||||||
|
│ callee ice ├──────────┘ └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ └───────────┤ caller ice │
|
||||||
|
│ candidates │ │ candidates │
|
||||||
|
└────────────────────┘ └────────────────────┘
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
┌─────────────────────────────────────────────────────┐
|
||||||
|
│ │░
|
||||||
|
│ If connection is impossible (firewall), and a turn │░
|
||||||
|
│server is available, connection could happen through │░
|
||||||
|
│ a relay │░
|
||||||
|
│ │░
|
||||||
|
└─────────────────────────────────────────────────────┘░
|
||||||
|
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
|
||||||
|
|
||||||
|
|
||||||
|
┌───────────────┐
|
||||||
|
│ Internet │
|
||||||
|
└─┬─────────────┴ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
||||||
|
┌─────────────────┐ │ ┌─────────────────┐
|
||||||
|
│ │ │ ┌─────────────────────────┐ │ │
|
||||||
|
│ ├───────────────────────────────────────┐│ │ │ │ │
|
||||||
|
│ WebRtc │███████████████████████████████████████││ │ │ WebRtc │
|
||||||
|
│ ├───────────────────────────────────────┘│ │ │ │ │
|
||||||
|
│ │ ┌────────┴─────────────────┐ │ Relay │┌─────────────────────────────────────┤ │
|
||||||
|
┌───────────────┴────┐ │ │ Video / Audio Stream │ │ ││█████████████████████████████████████│ ┌───────┴────────────┐
|
||||||
|
│ callee answer ├────────────┘ └────────┬─────────────────┘ │ │└─────────────────────────────────────┴─────────┤ caller offer │
|
||||||
|
├────────────────────┤ │ │ │ ├────────────────────┤
|
||||||
|
│ callee ice │ │ │ │ │ caller ice │
|
||||||
|
│ candidates │ └─────────────────────────┘ │ │ candidates │
|
||||||
|
└────────────────────┘ │ └────────────────────┘
|
||||||
|
│
|
||||||
|
│
|
||||||
|
│
|
||||||
|
│
|
||||||
|
│
|
||||||
|
└ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
|
|
@ -6,7 +6,7 @@ android {
|
||||||
compileSdkVersion 29
|
compileSdkVersion 29
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdkVersion 16
|
minSdkVersion 21
|
||||||
targetSdkVersion 29
|
targetSdkVersion 29
|
||||||
versionCode 1
|
versionCode 1
|
||||||
versionName "1.0"
|
versionName "1.0"
|
||||||
|
|
|
@ -23,7 +23,7 @@ android {
|
||||||
testOptions.unitTests.includeAndroidResources = true
|
testOptions.unitTests.includeAndroidResources = true
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdkVersion 16
|
minSdkVersion 21
|
||||||
targetSdkVersion 29
|
targetSdkVersion 29
|
||||||
versionCode 1
|
versionCode 1
|
||||||
versionName "0.0.1"
|
versionName "0.0.1"
|
||||||
|
@ -35,6 +35,10 @@ android {
|
||||||
resValue "string", "git_sdk_revision", "\"${gitRevision()}\""
|
resValue "string", "git_sdk_revision", "\"${gitRevision()}\""
|
||||||
resValue "string", "git_sdk_revision_unix_date", "\"${gitRevisionUnixDate()}\""
|
resValue "string", "git_sdk_revision_unix_date", "\"${gitRevisionUnixDate()}\""
|
||||||
resValue "string", "git_sdk_revision_date", "\"${gitRevisionDate()}\""
|
resValue "string", "git_sdk_revision_date", "\"${gitRevisionDate()}\""
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
consumerProguardFiles 'proguard-rules.pro'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
|
@ -49,9 +53,6 @@ android {
|
||||||
release {
|
release {
|
||||||
buildConfigField "boolean", "LOG_PRIVATE_DATA", "false"
|
buildConfigField "boolean", "LOG_PRIVATE_DATA", "false"
|
||||||
buildConfigField "okhttp3.logging.HttpLoggingInterceptor.Level", "OKHTTP_LOGGING_LEVEL", "okhttp3.logging.HttpLoggingInterceptor.Level.NONE"
|
buildConfigField "okhttp3.logging.HttpLoggingInterceptor.Level", "OKHTTP_LOGGING_LEVEL", "okhttp3.logging.HttpLoggingInterceptor.Level.NONE"
|
||||||
|
|
||||||
minifyEnabled false
|
|
||||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -113,6 +114,7 @@ dependencies {
|
||||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
|
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
|
||||||
|
|
||||||
implementation "androidx.appcompat:appcompat:1.1.0"
|
implementation "androidx.appcompat:appcompat:1.1.0"
|
||||||
|
implementation "androidx.core:core-ktx:1.1.0"
|
||||||
|
|
||||||
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
|
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
|
||||||
implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
|
implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
|
||||||
|
@ -161,6 +163,10 @@ dependencies {
|
||||||
// Phone number https://github.com/google/libphonenumber
|
// Phone number https://github.com/google/libphonenumber
|
||||||
implementation 'com.googlecode.libphonenumber:libphonenumber:8.10.23'
|
implementation 'com.googlecode.libphonenumber:libphonenumber:8.10.23'
|
||||||
|
|
||||||
|
// Web RTC
|
||||||
|
// TODO meant for development purposes only. See http://webrtc.github.io/webrtc-org/native-code/android/
|
||||||
|
implementation 'org.webrtc:google-webrtc:1.0.+'
|
||||||
|
|
||||||
debugImplementation 'com.airbnb.okreplay:okreplay:1.5.0'
|
debugImplementation 'com.airbnb.okreplay:okreplay:1.5.0'
|
||||||
releaseImplementation 'com.airbnb.okreplay:noop:1.5.0'
|
releaseImplementation 'com.airbnb.okreplay:noop:1.5.0'
|
||||||
androidTestImplementation 'com.airbnb.okreplay:espresso:1.5.0'
|
androidTestImplementation 'com.airbnb.okreplay:espresso:1.5.0'
|
||||||
|
|
42
matrix-sdk-android/proguard-rules.pro
vendored
42
matrix-sdk-android/proguard-rules.pro
vendored
|
@ -19,3 +19,45 @@
|
||||||
# If you keep the line number information, uncomment this to
|
# If you keep the line number information, uncomment this to
|
||||||
# hide the original source file name.
|
# hide the original source file name.
|
||||||
#-renamesourcefileattribute SourceFile
|
#-renamesourcefileattribute SourceFile
|
||||||
|
|
||||||
|
|
||||||
|
### EVENT BUS ###
|
||||||
|
|
||||||
|
-keepattributes *Annotation*
|
||||||
|
-keepclassmembers class * {
|
||||||
|
@org.greenrobot.eventbus.Subscribe <methods>;
|
||||||
|
}
|
||||||
|
-keep enum org.greenrobot.eventbus.ThreadMode { *; }
|
||||||
|
|
||||||
|
### MOSHI ###
|
||||||
|
|
||||||
|
# JSR 305 annotations are for embedding nullability information.
|
||||||
|
|
||||||
|
-dontwarn javax.annotation.**
|
||||||
|
|
||||||
|
-keepclasseswithmembers class * {
|
||||||
|
@com.squareup.moshi.* <methods>;
|
||||||
|
}
|
||||||
|
|
||||||
|
-keep @com.squareup.moshi.JsonQualifier interface *
|
||||||
|
|
||||||
|
# Enum field names are used by the integrated EnumJsonAdapter.
|
||||||
|
# values() is synthesized by the Kotlin compiler and is used by EnumJsonAdapter indirectly
|
||||||
|
# Annotate enums with @JsonClass(generateAdapter = false) to use them with Moshi.
|
||||||
|
-keepclassmembers @com.squareup.moshi.JsonClass class * extends java.lang.Enum {
|
||||||
|
<fields>;
|
||||||
|
**[] values();
|
||||||
|
}
|
||||||
|
|
||||||
|
-keep class kotlin.reflect.jvm.internal.impl.builtins.BuiltInsLoaderImpl
|
||||||
|
|
||||||
|
-keepclassmembers class kotlin.Metadata {
|
||||||
|
public <methods>;
|
||||||
|
}
|
||||||
|
|
||||||
|
### OKHTTP for Android Studio ###
|
||||||
|
-keep class okhttp3.Headers { *; }
|
||||||
|
-keep interface okhttp3.Interceptor.* { *; }
|
||||||
|
|
||||||
|
### OLM JNI ###
|
||||||
|
-keep class org.matrix.olm.** { *; }
|
|
@ -241,14 +241,14 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
|
||||||
val eventWireContent = event.content.toContent()
|
val eventWireContent = event.content.toContent()
|
||||||
assertNotNull(eventWireContent)
|
assertNotNull(eventWireContent)
|
||||||
|
|
||||||
assertNull(eventWireContent.get("body"))
|
assertNull(eventWireContent["body"])
|
||||||
assertEquals(MXCRYPTO_ALGORITHM_MEGOLM, eventWireContent.get("algorithm"))
|
assertEquals(MXCRYPTO_ALGORITHM_MEGOLM, eventWireContent["algorithm"])
|
||||||
|
|
||||||
assertNotNull(eventWireContent.get("ciphertext"))
|
assertNotNull(eventWireContent["ciphertext"])
|
||||||
assertNotNull(eventWireContent.get("session_id"))
|
assertNotNull(eventWireContent["session_id"])
|
||||||
assertNotNull(eventWireContent.get("sender_key"))
|
assertNotNull(eventWireContent["sender_key"])
|
||||||
|
|
||||||
assertEquals(senderSession.sessionParams.deviceId, eventWireContent.get("device_id"))
|
assertEquals(senderSession.sessionParams.deviceId, eventWireContent["device_id"])
|
||||||
|
|
||||||
assertNotNull(event.eventId)
|
assertNotNull(event.eventId)
|
||||||
assertEquals(roomId, event.roomId)
|
assertEquals(roomId, event.roomId)
|
||||||
|
@ -257,7 +257,7 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
|
||||||
|
|
||||||
val eventContent = event.toContent()
|
val eventContent = event.toContent()
|
||||||
assertNotNull(eventContent)
|
assertNotNull(eventContent)
|
||||||
assertEquals(clearMessage, eventContent.get("body"))
|
assertEquals(clearMessage, eventContent["body"])
|
||||||
assertEquals(senderSession.myUserId, event.senderId)
|
assertEquals(senderSession.myUserId, event.senderId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -90,7 +90,7 @@ class KeyShareTests : InstrumentedTest {
|
||||||
} catch (failure: Throwable) {
|
} catch (failure: Throwable) {
|
||||||
}
|
}
|
||||||
|
|
||||||
val outgoingRequestBefore = aliceSession2.cryptoService().getOutgoingRoomKeyRequest()
|
val outgoingRequestsBefore = aliceSession2.cryptoService().getOutgoingRoomKeyRequests()
|
||||||
// Try to request
|
// Try to request
|
||||||
aliceSession2.cryptoService().requestRoomKeyForEvent(receivedEvent.root)
|
aliceSession2.cryptoService().requestRoomKeyForEvent(receivedEvent.root)
|
||||||
|
|
||||||
|
@ -100,10 +100,10 @@ class KeyShareTests : InstrumentedTest {
|
||||||
var outGoingRequestId: String? = null
|
var outGoingRequestId: String? = null
|
||||||
|
|
||||||
mTestHelper.retryPeriodicallyWithLatch(waitLatch) {
|
mTestHelper.retryPeriodicallyWithLatch(waitLatch) {
|
||||||
aliceSession2.cryptoService().getOutgoingRoomKeyRequest()
|
aliceSession2.cryptoService().getOutgoingRoomKeyRequests()
|
||||||
.filter { req ->
|
.filter { req ->
|
||||||
// filter out request that was known before
|
// filter out request that was known before
|
||||||
!outgoingRequestBefore.any { req.requestId == it.requestId }
|
!outgoingRequestsBefore.any { req.requestId == it.requestId }
|
||||||
}
|
}
|
||||||
.let {
|
.let {
|
||||||
val outgoing = it.firstOrNull { it.sessionId == eventMegolmSessionId }
|
val outgoing = it.firstOrNull { it.sessionId == eventMegolmSessionId }
|
||||||
|
@ -115,10 +115,10 @@ class KeyShareTests : InstrumentedTest {
|
||||||
|
|
||||||
Log.v("TEST", "=======> Outgoing requet Id is $outGoingRequestId")
|
Log.v("TEST", "=======> Outgoing requet Id is $outGoingRequestId")
|
||||||
|
|
||||||
val outgoingRequestAfter = aliceSession2.cryptoService().getOutgoingRoomKeyRequest()
|
val outgoingRequestAfter = aliceSession2.cryptoService().getOutgoingRoomKeyRequests()
|
||||||
|
|
||||||
// We should have a new request
|
// We should have a new request
|
||||||
Assert.assertTrue(outgoingRequestAfter.size > outgoingRequestBefore.size)
|
Assert.assertTrue(outgoingRequestAfter.size > outgoingRequestsBefore.size)
|
||||||
Assert.assertNotNull(outgoingRequestAfter.first { it.sessionId == eventMegolmSessionId })
|
Assert.assertNotNull(outgoingRequestAfter.first { it.sessionId == eventMegolmSessionId })
|
||||||
|
|
||||||
// The first session should see an incoming request
|
// The first session should see an incoming request
|
||||||
|
@ -126,7 +126,7 @@ class KeyShareTests : InstrumentedTest {
|
||||||
mTestHelper.waitWithLatch { latch ->
|
mTestHelper.waitWithLatch { latch ->
|
||||||
mTestHelper.retryPeriodicallyWithLatch(latch) {
|
mTestHelper.retryPeriodicallyWithLatch(latch) {
|
||||||
// DEBUG LOGS
|
// DEBUG LOGS
|
||||||
aliceSession.cryptoService().getIncomingRoomKeyRequest().let {
|
aliceSession.cryptoService().getIncomingRoomKeyRequests().let {
|
||||||
Log.v("TEST", "Incoming request Session 1 (looking for $outGoingRequestId)")
|
Log.v("TEST", "Incoming request Session 1 (looking for $outGoingRequestId)")
|
||||||
Log.v("TEST", "=========================")
|
Log.v("TEST", "=========================")
|
||||||
it.forEach { keyRequest ->
|
it.forEach { keyRequest ->
|
||||||
|
@ -135,7 +135,7 @@ class KeyShareTests : InstrumentedTest {
|
||||||
Log.v("TEST", "=========================")
|
Log.v("TEST", "=========================")
|
||||||
}
|
}
|
||||||
|
|
||||||
val incoming = aliceSession.cryptoService().getIncomingRoomKeyRequest().firstOrNull { it.requestId == outGoingRequestId }
|
val incoming = aliceSession.cryptoService().getIncomingRoomKeyRequests().firstOrNull { it.requestId == outGoingRequestId }
|
||||||
incoming?.state == GossipingRequestState.REJECTED
|
incoming?.state == GossipingRequestState.REJECTED
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -155,7 +155,7 @@ class KeyShareTests : InstrumentedTest {
|
||||||
|
|
||||||
mTestHelper.waitWithLatch { latch ->
|
mTestHelper.waitWithLatch { latch ->
|
||||||
mTestHelper.retryPeriodicallyWithLatch(latch) {
|
mTestHelper.retryPeriodicallyWithLatch(latch) {
|
||||||
aliceSession.cryptoService().getIncomingRoomKeyRequest().let {
|
aliceSession.cryptoService().getIncomingRoomKeyRequests().let {
|
||||||
Log.v("TEST", "Incoming request Session 1")
|
Log.v("TEST", "Incoming request Session 1")
|
||||||
Log.v("TEST", "=========================")
|
Log.v("TEST", "=========================")
|
||||||
it.forEach {
|
it.forEach {
|
||||||
|
@ -171,7 +171,7 @@ class KeyShareTests : InstrumentedTest {
|
||||||
Thread.sleep(6_000)
|
Thread.sleep(6_000)
|
||||||
mTestHelper.waitWithLatch { latch ->
|
mTestHelper.waitWithLatch { latch ->
|
||||||
mTestHelper.retryPeriodicallyWithLatch(latch) {
|
mTestHelper.retryPeriodicallyWithLatch(latch) {
|
||||||
aliceSession2.cryptoService().getOutgoingRoomKeyRequest().let {
|
aliceSession2.cryptoService().getOutgoingRoomKeyRequests().let {
|
||||||
it.any { it.requestBody?.sessionId == eventMegolmSessionId && it.state == OutgoingGossipingRequestState.CANCELLED }
|
it.any { it.requestBody?.sessionId == eventMegolmSessionId && it.state == OutgoingGossipingRequestState.CANCELLED }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -252,7 +252,7 @@ class KeyShareTests : InstrumentedTest {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
val txId: String = "m.testVerif12"
|
val txId = "m.testVerif12"
|
||||||
aliceVerificationService2.beginKeyVerification(VerificationMethod.SAS, aliceSession1.myUserId, aliceSession1.sessionParams.deviceId
|
aliceVerificationService2.beginKeyVerification(VerificationMethod.SAS, aliceSession1.myUserId, aliceSession1.sessionParams.deviceId
|
||||||
?: "", txId)
|
?: "", txId)
|
||||||
|
|
||||||
|
|
|
@ -144,7 +144,7 @@ class QuadSTests : InstrumentedTest {
|
||||||
|
|
||||||
val secretAccountData = assertAccountData(aliceSession, "secret.of.life")
|
val secretAccountData = assertAccountData(aliceSession, "secret.of.life")
|
||||||
|
|
||||||
val encryptedContent = secretAccountData.content.get("encrypted") as? Map<*, *>
|
val encryptedContent = secretAccountData.content["encrypted"] as? Map<*, *>
|
||||||
assertNotNull("Element should be encrypted", encryptedContent)
|
assertNotNull("Element should be encrypted", encryptedContent)
|
||||||
assertNotNull("Secret should be encrypted with default key", encryptedContent?.get(keyId))
|
assertNotNull("Secret should be encrypted with default key", encryptedContent?.get(keyId))
|
||||||
|
|
||||||
|
|
|
@ -69,7 +69,7 @@ data class HomeServerConnectionConfig(
|
||||||
*/
|
*/
|
||||||
fun withHomeServerUri(hsUri: Uri): Builder {
|
fun withHomeServerUri(hsUri: Uri): Builder {
|
||||||
if (hsUri.scheme != "http" && hsUri.scheme != "https") {
|
if (hsUri.scheme != "http" && hsUri.scheme != "https") {
|
||||||
throw RuntimeException("Invalid home server URI: " + hsUri)
|
throw RuntimeException("Invalid home server URI: $hsUri")
|
||||||
}
|
}
|
||||||
// ensure trailing /
|
// ensure trailing /
|
||||||
val hsString = hsUri.toString().ensureTrailingSlash()
|
val hsString = hsUri.toString().ensureTrailingSlash()
|
||||||
|
|
|
@ -16,10 +16,15 @@
|
||||||
|
|
||||||
package im.vector.matrix.android.api.extensions
|
package im.vector.matrix.android.api.extensions
|
||||||
|
|
||||||
inline fun <A> tryThis(operation: () -> A): A? {
|
import timber.log.Timber
|
||||||
|
|
||||||
|
inline fun <A> tryThis(message: String? = null, operation: () -> A): A? {
|
||||||
return try {
|
return try {
|
||||||
operation()
|
operation()
|
||||||
} catch (any: Throwable) {
|
} catch (any: Throwable) {
|
||||||
|
if (message != null) {
|
||||||
|
Timber.e(any, message)
|
||||||
|
}
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -87,14 +87,13 @@ class EventMatchCondition(
|
||||||
// Very simple glob to regexp converter
|
// Very simple glob to regexp converter
|
||||||
private fun simpleGlobToRegExp(glob: String): String {
|
private fun simpleGlobToRegExp(glob: String): String {
|
||||||
var out = "" // "^"
|
var out = "" // "^"
|
||||||
for (i in 0 until glob.length) {
|
for (element in glob) {
|
||||||
val c = glob[i]
|
when (element) {
|
||||||
when (c) {
|
|
||||||
'*' -> out += ".*"
|
'*' -> out += ".*"
|
||||||
'?' -> out += '.'.toString()
|
'?' -> out += '.'.toString()
|
||||||
'.' -> out += "\\."
|
'.' -> out += "\\."
|
||||||
'\\' -> out += "\\\\"
|
'\\' -> out += "\\\\"
|
||||||
else -> out += c
|
else -> out += element
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
out += "" // '$'.toString()
|
out += "" // '$'.toString()
|
||||||
|
|
|
@ -24,6 +24,7 @@ import im.vector.matrix.android.api.pushrules.PushRuleService
|
||||||
import im.vector.matrix.android.api.session.account.AccountService
|
import im.vector.matrix.android.api.session.account.AccountService
|
||||||
import im.vector.matrix.android.api.session.accountdata.AccountDataService
|
import im.vector.matrix.android.api.session.accountdata.AccountDataService
|
||||||
import im.vector.matrix.android.api.session.cache.CacheService
|
import im.vector.matrix.android.api.session.cache.CacheService
|
||||||
|
import im.vector.matrix.android.api.session.call.CallSignalingService
|
||||||
import im.vector.matrix.android.api.session.content.ContentUploadStateTracker
|
import im.vector.matrix.android.api.session.content.ContentUploadStateTracker
|
||||||
import im.vector.matrix.android.api.session.content.ContentUrlResolver
|
import im.vector.matrix.android.api.session.content.ContentUrlResolver
|
||||||
import im.vector.matrix.android.api.session.crypto.CryptoService
|
import im.vector.matrix.android.api.session.crypto.CryptoService
|
||||||
|
@ -171,6 +172,11 @@ interface Session :
|
||||||
*/
|
*/
|
||||||
fun integrationManagerService(): IntegrationManagerService
|
fun integrationManagerService(): IntegrationManagerService
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the call signaling service associated with the session
|
||||||
|
*/
|
||||||
|
fun callSignalingService(): CallSignalingService
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a listener to the session.
|
* Add a listener to the session.
|
||||||
* @param listener the listener to add.
|
* @param listener the listener to add.
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
/*
|
||||||
|
* 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.matrix.android.api.session.call
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
|
import im.vector.matrix.android.api.util.Cancelable
|
||||||
|
|
||||||
|
interface CallSignalingService {
|
||||||
|
|
||||||
|
fun getTurnServer(callback: MatrixCallback<TurnServerResponse>): Cancelable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an outgoing call
|
||||||
|
*/
|
||||||
|
fun createOutgoingCall(roomId: String, otherUserId: String, isVideoCall: Boolean): MxCall
|
||||||
|
|
||||||
|
fun addCallListener(listener: CallsListener)
|
||||||
|
|
||||||
|
fun removeCallListener(listener: CallsListener)
|
||||||
|
|
||||||
|
fun getCallWithId(callId: String) : MxCall?
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
/*
|
||||||
|
* 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.matrix.android.api.session.call
|
||||||
|
|
||||||
|
import org.webrtc.PeerConnection
|
||||||
|
|
||||||
|
sealed class CallState {
|
||||||
|
|
||||||
|
/** Idle, setting up objects */
|
||||||
|
object Idle : CallState()
|
||||||
|
|
||||||
|
/** Dialing. Outgoing call is signaling the remote peer */
|
||||||
|
object Dialing : CallState()
|
||||||
|
|
||||||
|
/** Local ringing. Incoming call offer received */
|
||||||
|
object LocalRinging : CallState()
|
||||||
|
|
||||||
|
/** Answering. Incoming call is responding to remote peer */
|
||||||
|
object Answering : CallState()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Connected. Incoming/Outgoing call, ice layer connecting or connected
|
||||||
|
* Notice that the PeerState failed is not always final, if you switch network, new ice candidtates
|
||||||
|
* could be exchanged, and the connection could go back to connected
|
||||||
|
* */
|
||||||
|
data class Connected(val iceConnectionState: PeerConnection.PeerConnectionState) : CallState()
|
||||||
|
|
||||||
|
/** Terminated. Incoming/Outgoing call, the call is terminated */
|
||||||
|
object Terminated : CallState()
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
/*
|
||||||
|
* 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.matrix.android.api.session.call
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.session.room.model.call.CallAnswerContent
|
||||||
|
import im.vector.matrix.android.api.session.room.model.call.CallCandidatesContent
|
||||||
|
import im.vector.matrix.android.api.session.room.model.call.CallHangupContent
|
||||||
|
import im.vector.matrix.android.api.session.room.model.call.CallInviteContent
|
||||||
|
|
||||||
|
interface CallsListener {
|
||||||
|
/**
|
||||||
|
* Called when there is an incoming call within the room.
|
||||||
|
*/
|
||||||
|
fun onCallInviteReceived(mxCall: MxCall, callInviteContent: CallInviteContent)
|
||||||
|
|
||||||
|
fun onCallIceCandidateReceived(mxCall: MxCall, iceCandidatesContent: CallCandidatesContent)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An outgoing call is started.
|
||||||
|
*/
|
||||||
|
fun onCallAnswerReceived(callAnswerContent: CallAnswerContent)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a called has been hung up
|
||||||
|
*/
|
||||||
|
fun onCallHangupReceived(callHangupContent: CallHangupContent)
|
||||||
|
|
||||||
|
fun onCallManagedByOtherSession(callId: String)
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
/*
|
||||||
|
* 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.matrix.android.api.session.call
|
||||||
|
|
||||||
|
import org.webrtc.EglBase
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The root [EglBase] instance shared by the entire application for
|
||||||
|
* the sake of reducing the utilization of system resources (such as EGL
|
||||||
|
* contexts)
|
||||||
|
* by performing a runtime check.
|
||||||
|
*/
|
||||||
|
object EglUtils {
|
||||||
|
|
||||||
|
// TODO how do we release that?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lazily creates and returns the one and only [EglBase] which will
|
||||||
|
* serve as the root for all contexts that are needed.
|
||||||
|
*/
|
||||||
|
@get:Synchronized var rootEglBase: EglBase? = null
|
||||||
|
get() {
|
||||||
|
if (field == null) {
|
||||||
|
val configAttributes = EglBase.CONFIG_PLAIN
|
||||||
|
try {
|
||||||
|
field = EglBase.createEgl14(configAttributes)
|
||||||
|
?: EglBase.createEgl10(configAttributes) // Fall back to EglBase10.
|
||||||
|
} catch (ex: Throwable) {
|
||||||
|
Timber.e(ex, "Failed to create EglBase")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return field
|
||||||
|
}
|
||||||
|
private set
|
||||||
|
|
||||||
|
val rootEglBaseContext: EglBase.Context?
|
||||||
|
get() {
|
||||||
|
val eglBase = rootEglBase
|
||||||
|
return eglBase?.eglBaseContext
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,75 @@
|
||||||
|
/*
|
||||||
|
* 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.matrix.android.api.session.call
|
||||||
|
|
||||||
|
import org.webrtc.IceCandidate
|
||||||
|
import org.webrtc.SessionDescription
|
||||||
|
|
||||||
|
interface MxCallDetail {
|
||||||
|
val callId: String
|
||||||
|
val isOutgoing: Boolean
|
||||||
|
val roomId: String
|
||||||
|
val otherUserId: String
|
||||||
|
val isVideoCall: Boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define both an incoming call and on outgoing call
|
||||||
|
*/
|
||||||
|
interface MxCall : MxCallDetail {
|
||||||
|
|
||||||
|
var state: CallState
|
||||||
|
/**
|
||||||
|
* Pick Up the incoming call
|
||||||
|
* It has no effect on outgoing call
|
||||||
|
*/
|
||||||
|
fun accept(sdp: SessionDescription)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reject an incoming call
|
||||||
|
* It's an alias to hangUp
|
||||||
|
*/
|
||||||
|
fun reject() = hangUp()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* End the call
|
||||||
|
*/
|
||||||
|
fun hangUp()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start a call
|
||||||
|
* Send offer SDP to the other participant.
|
||||||
|
*/
|
||||||
|
fun offerSdp(sdp: SessionDescription)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send Ice candidate to the other participant.
|
||||||
|
*/
|
||||||
|
fun sendLocalIceCandidates(candidates: List<IceCandidate>)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send removed ICE candidates to the other participant.
|
||||||
|
*/
|
||||||
|
fun sendLocalIceCandidateRemovals(candidates: List<IceCandidate>)
|
||||||
|
|
||||||
|
fun addListener(listener: StateListener)
|
||||||
|
fun removeListener(listener: StateListener)
|
||||||
|
|
||||||
|
interface StateListener {
|
||||||
|
fun onStateUpdate(call: MxCall)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
/*
|
||||||
|
* 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.matrix.android.api.session.call
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
|
// TODO Should not be exposed
|
||||||
|
/**
|
||||||
|
* Ref: https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-voip-turnserver
|
||||||
|
*/
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
data class TurnServerResponse(
|
||||||
|
/**
|
||||||
|
* Required. The username to use.
|
||||||
|
*/
|
||||||
|
@Json(name = "username") val username: String?,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Required. The password to use.
|
||||||
|
*/
|
||||||
|
@Json(name = "password") val password: String?,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Required. A list of TURN URIs
|
||||||
|
*/
|
||||||
|
@Json(name = "uris") val uris: List<String>?,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Required. The time-to-live in seconds
|
||||||
|
*/
|
||||||
|
@Json(name = "ttl") val ttl: Int?
|
||||||
|
)
|
|
@ -138,7 +138,9 @@ interface CryptoService {
|
||||||
|
|
||||||
fun removeSessionListener(listener: NewSessionListener)
|
fun removeSessionListener(listener: NewSessionListener)
|
||||||
|
|
||||||
fun getOutgoingRoomKeyRequest(): List<OutgoingRoomKeyRequest>
|
fun getOutgoingRoomKeyRequests(): List<OutgoingRoomKeyRequest>
|
||||||
fun getIncomingRoomKeyRequest(): List<IncomingRoomKeyRequest>
|
|
||||||
|
fun getIncomingRoomKeyRequests(): List<IncomingRoomKeyRequest>
|
||||||
|
|
||||||
fun getGossipingEventsTrail(): List<Event>
|
fun getGossipingEventsTrail(): List<Event>
|
||||||
}
|
}
|
||||||
|
|
|
@ -58,7 +58,6 @@ object EventType {
|
||||||
const val STATE_ROOM_ENCRYPTION = "m.room.encryption"
|
const val STATE_ROOM_ENCRYPTION = "m.room.encryption"
|
||||||
|
|
||||||
// Call Events
|
// Call Events
|
||||||
|
|
||||||
const val CALL_INVITE = "m.call.invite"
|
const val CALL_INVITE = "m.call.invite"
|
||||||
const val CALL_CANDIDATES = "m.call.candidates"
|
const val CALL_CANDIDATES = "m.call.candidates"
|
||||||
const val CALL_ANSWER = "m.call.answer"
|
const val CALL_ANSWER = "m.call.answer"
|
||||||
|
|
|
@ -26,5 +26,5 @@ object RelationType {
|
||||||
/** Lets you define an event which references an existing event.*/
|
/** Lets you define an event which references an existing event.*/
|
||||||
const val REFERENCE = "m.reference"
|
const val REFERENCE = "m.reference"
|
||||||
/** Lets you define an event which adds a response to an existing event.*/
|
/** Lets you define an event which adds a response to an existing event.*/
|
||||||
const val RESPONSE = "m.response"
|
const val RESPONSE = "org.matrix.response"
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,12 +35,19 @@ interface ProfileService {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the current dispayname for this user
|
* Return the current display name for this user
|
||||||
* @param userId the userId param to look for
|
* @param userId the userId param to look for
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
fun getDisplayName(userId: String, matrixCallback: MatrixCallback<Optional<String>>): Cancelable
|
fun getDisplayName(userId: String, matrixCallback: MatrixCallback<Optional<String>>): Cancelable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the display name for this user
|
||||||
|
* @param userId the userId to update the display name of
|
||||||
|
* @param newDisplayName the new display name of the user
|
||||||
|
*/
|
||||||
|
fun setDisplayName(userId: String, newDisplayName: String, matrixCallback: MatrixCallback<Unit>): Cancelable
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the current avatarUrl for this user.
|
* Return the current avatarUrl for this user.
|
||||||
* @param userId the userId param to look for
|
* @param userId the userId param to look for
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
package im.vector.matrix.android.api.session.room
|
package im.vector.matrix.android.api.session.room
|
||||||
|
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
|
import im.vector.matrix.android.api.session.room.call.RoomCallService
|
||||||
import im.vector.matrix.android.api.session.room.crypto.RoomCryptoService
|
import im.vector.matrix.android.api.session.room.crypto.RoomCryptoService
|
||||||
import im.vector.matrix.android.api.session.room.members.MembershipService
|
import im.vector.matrix.android.api.session.room.members.MembershipService
|
||||||
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||||
|
@ -47,6 +48,7 @@ interface Room :
|
||||||
StateService,
|
StateService,
|
||||||
UploadsService,
|
UploadsService,
|
||||||
ReportingService,
|
ReportingService,
|
||||||
|
RoomCallService,
|
||||||
RelationService,
|
RelationService,
|
||||||
RoomCryptoService,
|
RoomCryptoService,
|
||||||
RoomPushRuleService {
|
RoomPushRuleService {
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
/*
|
||||||
|
* 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.matrix.android.api.session.room.call
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This interface defines methods to handle calls in a room. It's implemented at the room level.
|
||||||
|
*/
|
||||||
|
interface RoomCallService {
|
||||||
|
/**
|
||||||
|
* Return true if calls (audio or video) can be performed on this Room
|
||||||
|
*/
|
||||||
|
fun canStartCall(): Boolean
|
||||||
|
}
|
|
@ -17,10 +17,12 @@
|
||||||
package im.vector.matrix.android.api.session.room.model
|
package im.vector.matrix.android.api.session.room.model
|
||||||
|
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents the membership of a user on a room
|
* Represents the membership of a user on a room
|
||||||
*/
|
*/
|
||||||
|
@JsonClass(generateAdapter = false)
|
||||||
enum class Membership(val value: String) {
|
enum class Membership(val value: String) {
|
||||||
|
|
||||||
NONE("none"),
|
NONE("none"),
|
||||||
|
|
|
@ -17,7 +17,9 @@
|
||||||
package im.vector.matrix.android.api.session.room.model
|
package im.vector.matrix.android.api.session.room.model
|
||||||
|
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = false)
|
||||||
enum class RoomDirectoryVisibility {
|
enum class RoomDirectoryVisibility {
|
||||||
@Json(name = "private") PRIVATE,
|
@Json(name = "private") PRIVATE,
|
||||||
@Json(name = "public") PUBLIC
|
@Json(name = "public") PUBLIC
|
||||||
|
|
|
@ -29,6 +29,7 @@ data class RoomGuestAccessContent(
|
||||||
@Json(name = "guest_access") val guestAccess: GuestAccess? = null
|
@Json(name = "guest_access") val guestAccess: GuestAccess? = null
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = false)
|
||||||
enum class GuestAccess(val value: String) {
|
enum class GuestAccess(val value: String) {
|
||||||
@Json(name = "can_join")
|
@Json(name = "can_join")
|
||||||
CanJoin("can_join"),
|
CanJoin("can_join"),
|
||||||
|
|
|
@ -17,10 +17,12 @@
|
||||||
package im.vector.matrix.android.api.session.room.model
|
package im.vector.matrix.android.api.session.room.model
|
||||||
|
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ref: https://matrix.org/docs/spec/client_server/latest#room-history-visibility
|
* Ref: https://matrix.org/docs/spec/client_server/latest#room-history-visibility
|
||||||
*/
|
*/
|
||||||
|
@JsonClass(generateAdapter = false)
|
||||||
enum class RoomHistoryVisibility {
|
enum class RoomHistoryVisibility {
|
||||||
/**
|
/**
|
||||||
* All events while this is the m.room.history_visibility value may be shared by any
|
* All events while this is the m.room.history_visibility value may be shared by any
|
||||||
|
|
|
@ -18,10 +18,12 @@
|
||||||
package im.vector.matrix.android.api.session.room.model
|
package im.vector.matrix.android.api.session.room.model
|
||||||
|
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enum for [RoomJoinRulesContent] : https://matrix.org/docs/spec/client_server/r0.4.0#m-room-join-rules
|
* Enum for [RoomJoinRulesContent] : https://matrix.org/docs/spec/client_server/r0.4.0#m-room-join-rules
|
||||||
*/
|
*/
|
||||||
|
@JsonClass(generateAdapter = false)
|
||||||
enum class RoomJoinRules(val value: String) {
|
enum class RoomJoinRules(val value: String) {
|
||||||
|
|
||||||
@Json(name = "public")
|
@Json(name = "public")
|
||||||
|
|
|
@ -61,6 +61,9 @@ data class RoomSummary constructor(
|
||||||
val isFavorite: Boolean
|
val isFavorite: Boolean
|
||||||
get() = tags.any { it.name == RoomTag.ROOM_TAG_FAVOURITE }
|
get() = tags.any { it.name == RoomTag.ROOM_TAG_FAVOURITE }
|
||||||
|
|
||||||
|
val canStartCall: Boolean
|
||||||
|
get() = isDirect && joinedMembersCount == 2
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val NOT_IN_BREADCRUMBS = -1
|
const val NOT_IN_BREADCRUMBS = -1
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,16 +19,34 @@ package im.vector.matrix.android.api.session.room.model.call
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This event is sent by the callee when they wish to answer the call.
|
||||||
|
*/
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
data class CallAnswerContent(
|
data class CallAnswerContent(
|
||||||
|
/**
|
||||||
|
* Required. The ID of the call this event relates to.
|
||||||
|
*/
|
||||||
@Json(name = "call_id") val callId: String,
|
@Json(name = "call_id") val callId: String,
|
||||||
@Json(name = "version") val version: Int,
|
/**
|
||||||
@Json(name = "answer") val answer: Answer
|
* Required. The session description object
|
||||||
|
*/
|
||||||
|
@Json(name = "answer") val answer: Answer,
|
||||||
|
/**
|
||||||
|
* Required. The version of the VoIP specification this messages adheres to. This specification is version 0.
|
||||||
|
*/
|
||||||
|
@Json(name = "version") val version: Int = 0
|
||||||
) {
|
) {
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
data class Answer(
|
data class Answer(
|
||||||
@Json(name = "type") val type: String,
|
/**
|
||||||
|
* Required. The type of session description. Must be 'answer'.
|
||||||
|
*/
|
||||||
|
@Json(name = "type") val type: SdpType = SdpType.ANSWER,
|
||||||
|
/**
|
||||||
|
* Required. The SDP text of the session description.
|
||||||
|
*/
|
||||||
@Json(name = "sdp") val sdp: String
|
@Json(name = "sdp") val sdp: String
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,17 +19,39 @@ package im.vector.matrix.android.api.session.room.model.call
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This event is sent by callers after sending an invite and by the callee after answering.
|
||||||
|
* Its purpose is to give the other party additional ICE candidates to try using to communicate.
|
||||||
|
*/
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
data class CallCandidatesContent(
|
data class CallCandidatesContent(
|
||||||
|
/**
|
||||||
|
* Required. The ID of the call this event relates to.
|
||||||
|
*/
|
||||||
@Json(name = "call_id") val callId: String,
|
@Json(name = "call_id") val callId: String,
|
||||||
@Json(name = "version") val version: Int,
|
/**
|
||||||
@Json(name = "candidates") val candidates: List<Candidate> = emptyList()
|
* Required. Array of objects describing the candidates.
|
||||||
|
*/
|
||||||
|
@Json(name = "candidates") val candidates: List<Candidate> = emptyList(),
|
||||||
|
/**
|
||||||
|
* Required. The version of the VoIP specification this messages adheres to. This specification is version 0.
|
||||||
|
*/
|
||||||
|
@Json(name = "version") val version: Int = 0
|
||||||
) {
|
) {
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
data class Candidate(
|
data class Candidate(
|
||||||
|
/**
|
||||||
|
* Required. The SDP media type this candidate is intended for.
|
||||||
|
*/
|
||||||
@Json(name = "sdpMid") val sdpMid: String,
|
@Json(name = "sdpMid") val sdpMid: String,
|
||||||
@Json(name = "sdpMLineIndex") val sdpMLineIndex: String,
|
/**
|
||||||
|
* Required. The index of the SDP 'm' line this candidate is intended for.
|
||||||
|
*/
|
||||||
|
@Json(name = "sdpMLineIndex") val sdpMLineIndex: Int,
|
||||||
|
/**
|
||||||
|
* Required. The SDP 'a' line of the candidate.
|
||||||
|
*/
|
||||||
@Json(name = "candidate") val candidate: String
|
@Json(name = "candidate") val candidate: String
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,8 +19,32 @@ package im.vector.matrix.android.api.session.room.model.call
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sent by either party to signal their termination of the call. This can be sent either once
|
||||||
|
* the call has been established or before to abort the call.
|
||||||
|
*/
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
data class CallHangupContent(
|
data class CallHangupContent(
|
||||||
|
/**
|
||||||
|
* Required. The ID of the call this event relates to.
|
||||||
|
*/
|
||||||
@Json(name = "call_id") val callId: String,
|
@Json(name = "call_id") val callId: String,
|
||||||
@Json(name = "version") val version: Int
|
/**
|
||||||
)
|
* Required. The version of the VoIP specification this message adheres to. This specification is version 0.
|
||||||
|
*/
|
||||||
|
@Json(name = "version") val version: Int = 0,
|
||||||
|
/**
|
||||||
|
* Optional error reason for the hangup. This should not be provided when the user naturally ends or rejects the call.
|
||||||
|
* When there was an error in the call negotiation, this should be `ice_failed` for when ICE negotiation fails
|
||||||
|
* 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
|
||||||
|
) {
|
||||||
|
enum class Reason {
|
||||||
|
@Json(name = "ice_failed")
|
||||||
|
ICE_FAILED,
|
||||||
|
|
||||||
|
@Json(name = "invite_timeout")
|
||||||
|
INVITE_TIMEOUT
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -19,23 +19,45 @@ package im.vector.matrix.android.api.session.room.model.call
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This event is sent by the caller when they wish to establish a call.
|
||||||
|
*/
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
data class CallInviteContent(
|
data class CallInviteContent(
|
||||||
@Json(name = "call_id") val callId: String,
|
/**
|
||||||
@Json(name = "version") val version: Int,
|
* Required. A unique identifier for the call.
|
||||||
@Json(name = "lifetime") val lifetime: Int,
|
*/
|
||||||
@Json(name = "offer") val offer: Offer
|
@Json(name = "call_id") val callId: String?,
|
||||||
|
/**
|
||||||
|
* Required. The session description object
|
||||||
|
*/
|
||||||
|
@Json(name = "offer") val offer: Offer?,
|
||||||
|
/**
|
||||||
|
* Required. The version of the VoIP specification this message adheres to. This specification is version 0.
|
||||||
|
*/
|
||||||
|
@Json(name = "version") val version: Int? = 0,
|
||||||
|
/**
|
||||||
|
* Required. The time in milliseconds that the invite is valid for.
|
||||||
|
* Once the invite age exceeds this value, clients should discard it.
|
||||||
|
* They should also no longer show the call as awaiting an answer in the UI.
|
||||||
|
*/
|
||||||
|
@Json(name = "lifetime") val lifetime: Int?
|
||||||
) {
|
) {
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
data class Offer(
|
data class Offer(
|
||||||
@Json(name = "type") val type: String,
|
/**
|
||||||
@Json(name = "sdp") val sdp: String
|
* Required. The type of session description. Must be 'offer'.
|
||||||
|
*/
|
||||||
|
@Json(name = "type") val type: SdpType? = SdpType.OFFER,
|
||||||
|
/**
|
||||||
|
* Required. The SDP text of the session description.
|
||||||
|
*/
|
||||||
|
@Json(name = "sdp") val sdp: String?
|
||||||
) {
|
) {
|
||||||
companion object {
|
companion object {
|
||||||
const val SDP_VIDEO = "m=video"
|
const val SDP_VIDEO = "m=video"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isVideo(): Boolean = offer.sdp.contains(Offer.SDP_VIDEO)
|
fun isVideo(): Boolean = offer?.sdp?.contains(Offer.SDP_VIDEO) == true
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
/*
|
||||||
|
* 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.matrix.android.api.session.room.model.call
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
|
||||||
|
enum class SdpType {
|
||||||
|
@Json(name = "offer")
|
||||||
|
OFFER,
|
||||||
|
|
||||||
|
@Json(name = "answer")
|
||||||
|
ANSWER
|
||||||
|
}
|
|
@ -17,7 +17,9 @@
|
||||||
package im.vector.matrix.android.api.session.room.model.create
|
package im.vector.matrix.android.api.session.room.model.create
|
||||||
|
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = false)
|
||||||
enum class CreateRoomPreset {
|
enum class CreateRoomPreset {
|
||||||
@Json(name = "private_chat")
|
@Json(name = "private_chat")
|
||||||
PRESET_PRIVATE_CHAT,
|
PRESET_PRIVATE_CHAT,
|
||||||
|
|
|
@ -90,6 +90,6 @@ interface WidgetPostAPIMediator {
|
||||||
/**
|
/**
|
||||||
* Triggered when a widget is posting
|
* Triggered when a widget is posting
|
||||||
*/
|
*/
|
||||||
fun handleWidgetRequest(eventData: JsonDict): Boolean
|
fun handleWidgetRequest(mediator: WidgetPostAPIMediator, eventData: JsonDict): Boolean
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,7 +34,8 @@ interface WidgetService {
|
||||||
fun getWidgetURLFormatter(): WidgetURLFormatter
|
fun getWidgetURLFormatter(): WidgetURLFormatter
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an instance of [WidgetPostAPIMediator].
|
* Returns a new instance of [WidgetPostAPIMediator].
|
||||||
|
* Be careful to call clearWebView method and setHandler to null to avoid memory leaks.
|
||||||
* This is to be used for "admin" widgets so you can interact through JS.
|
* This is to be used for "admin" widgets so you can interact through JS.
|
||||||
*/
|
*/
|
||||||
fun getWidgetPostAPIMediator(): WidgetPostAPIMediator
|
fun getWidgetPostAPIMediator(): WidgetPostAPIMediator
|
||||||
|
|
|
@ -21,6 +21,9 @@ import com.squareup.moshi.Json
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
import im.vector.matrix.android.api.util.JsonDict
|
import im.vector.matrix.android.api.util.JsonDict
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ref: https://github.com/matrix-org/matrix-doc/issues/1236
|
||||||
|
*/
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
data class WidgetContent(
|
data class WidgetContent(
|
||||||
@Json(name = "creatorUserId") val creatorUserId: String? = null,
|
@Json(name = "creatorUserId") val creatorUserId: String? = null,
|
||||||
|
|
|
@ -16,6 +16,25 @@
|
||||||
|
|
||||||
package im.vector.matrix.android.api.session.widgets.model
|
package im.vector.matrix.android.api.session.widgets.model
|
||||||
|
|
||||||
|
private val DEFINED_TYPES by lazy {
|
||||||
|
listOf(
|
||||||
|
WidgetType.Jitsi,
|
||||||
|
WidgetType.TradingView,
|
||||||
|
WidgetType.Spotify,
|
||||||
|
WidgetType.Video,
|
||||||
|
WidgetType.GoogleDoc,
|
||||||
|
WidgetType.GoogleCalendar,
|
||||||
|
WidgetType.Etherpad,
|
||||||
|
WidgetType.StickerPicker,
|
||||||
|
WidgetType.Grafana,
|
||||||
|
WidgetType.Custom,
|
||||||
|
WidgetType.IntegrationManager
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ref: https://github.com/matrix-org/matrix-doc/issues/1236
|
||||||
|
*/
|
||||||
sealed class WidgetType(open val preferred: String, open val legacy: String = preferred) {
|
sealed class WidgetType(open val preferred: String, open val legacy: String = preferred) {
|
||||||
object Jitsi : WidgetType("m.jitsi", "jitsi")
|
object Jitsi : WidgetType("m.jitsi", "jitsi")
|
||||||
object TradingView : WidgetType("m.tradingview")
|
object TradingView : WidgetType("m.tradingview")
|
||||||
|
@ -30,7 +49,7 @@ sealed class WidgetType(open val preferred: String, open val legacy: String = pr
|
||||||
object IntegrationManager : WidgetType("m.integration_manager")
|
object IntegrationManager : WidgetType("m.integration_manager")
|
||||||
data class Fallback(override val preferred: String) : WidgetType(preferred)
|
data class Fallback(override val preferred: String) : WidgetType(preferred)
|
||||||
|
|
||||||
fun matches(type: String?): Boolean {
|
fun matches(type: String): Boolean {
|
||||||
return type == preferred || type == legacy
|
return type == preferred || type == legacy
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,20 +59,6 @@ sealed class WidgetType(open val preferred: String, open val legacy: String = pr
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
private val DEFINED_TYPES = listOf(
|
|
||||||
Jitsi,
|
|
||||||
TradingView,
|
|
||||||
Spotify,
|
|
||||||
Video,
|
|
||||||
GoogleDoc,
|
|
||||||
GoogleCalendar,
|
|
||||||
Etherpad,
|
|
||||||
StickerPicker,
|
|
||||||
Grafana,
|
|
||||||
Custom,
|
|
||||||
IntegrationManager
|
|
||||||
)
|
|
||||||
|
|
||||||
fun fromString(type: String): WidgetType {
|
fun fromString(type: String): WidgetType {
|
||||||
val matchingType = DEFINED_TYPES.firstOrNull {
|
val matchingType = DEFINED_TYPES.firstOrNull {
|
||||||
it.matches(type)
|
it.matches(type)
|
||||||
|
|
|
@ -67,6 +67,7 @@ import im.vector.matrix.android.internal.crypto.tasks.DefaultEncryptEventTask
|
||||||
import im.vector.matrix.android.internal.crypto.tasks.DefaultGetDeviceInfoTask
|
import im.vector.matrix.android.internal.crypto.tasks.DefaultGetDeviceInfoTask
|
||||||
import im.vector.matrix.android.internal.crypto.tasks.DefaultGetDevicesTask
|
import im.vector.matrix.android.internal.crypto.tasks.DefaultGetDevicesTask
|
||||||
import im.vector.matrix.android.internal.crypto.tasks.DefaultInitializeCrossSigningTask
|
import im.vector.matrix.android.internal.crypto.tasks.DefaultInitializeCrossSigningTask
|
||||||
|
import im.vector.matrix.android.internal.crypto.tasks.DefaultSendEventTask
|
||||||
import im.vector.matrix.android.internal.crypto.tasks.DefaultSendToDeviceTask
|
import im.vector.matrix.android.internal.crypto.tasks.DefaultSendToDeviceTask
|
||||||
import im.vector.matrix.android.internal.crypto.tasks.DefaultSendVerificationMessageTask
|
import im.vector.matrix.android.internal.crypto.tasks.DefaultSendVerificationMessageTask
|
||||||
import im.vector.matrix.android.internal.crypto.tasks.DefaultSetDeviceNameTask
|
import im.vector.matrix.android.internal.crypto.tasks.DefaultSetDeviceNameTask
|
||||||
|
@ -80,6 +81,7 @@ import im.vector.matrix.android.internal.crypto.tasks.EncryptEventTask
|
||||||
import im.vector.matrix.android.internal.crypto.tasks.GetDeviceInfoTask
|
import im.vector.matrix.android.internal.crypto.tasks.GetDeviceInfoTask
|
||||||
import im.vector.matrix.android.internal.crypto.tasks.GetDevicesTask
|
import im.vector.matrix.android.internal.crypto.tasks.GetDevicesTask
|
||||||
import im.vector.matrix.android.internal.crypto.tasks.InitializeCrossSigningTask
|
import im.vector.matrix.android.internal.crypto.tasks.InitializeCrossSigningTask
|
||||||
|
import im.vector.matrix.android.internal.crypto.tasks.SendEventTask
|
||||||
import im.vector.matrix.android.internal.crypto.tasks.SendToDeviceTask
|
import im.vector.matrix.android.internal.crypto.tasks.SendToDeviceTask
|
||||||
import im.vector.matrix.android.internal.crypto.tasks.SendVerificationMessageTask
|
import im.vector.matrix.android.internal.crypto.tasks.SendVerificationMessageTask
|
||||||
import im.vector.matrix.android.internal.crypto.tasks.SetDeviceNameTask
|
import im.vector.matrix.android.internal.crypto.tasks.SetDeviceNameTask
|
||||||
|
@ -251,4 +253,7 @@ internal abstract class CryptoModule {
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindInitializeCrossSigningTask(task: DefaultInitializeCrossSigningTask): InitializeCrossSigningTask
|
abstract fun bindInitializeCrossSigningTask(task: DefaultInitializeCrossSigningTask): InitializeCrossSigningTask
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindSendEventTask(task: DefaultSendEventTask): SendEventTask
|
||||||
}
|
}
|
||||||
|
|
|
@ -1263,11 +1263,11 @@ internal class DefaultCryptoService @Inject constructor(
|
||||||
return "DefaultCryptoService of $userId ($deviceId)"
|
return "DefaultCryptoService of $userId ($deviceId)"
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getOutgoingRoomKeyRequest(): List<OutgoingRoomKeyRequest> {
|
override fun getOutgoingRoomKeyRequests(): List<OutgoingRoomKeyRequest> {
|
||||||
return cryptoStore.getOutgoingRoomKeyRequests()
|
return cryptoStore.getOutgoingRoomKeyRequests()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getIncomingRoomKeyRequest(): List<IncomingRoomKeyRequest> {
|
override fun getIncomingRoomKeyRequests(): List<IncomingRoomKeyRequest> {
|
||||||
return cryptoStore.getIncomingRoomKeyRequests()
|
return cryptoStore.getIncomingRoomKeyRequests()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -897,9 +897,9 @@ internal class RealmCryptoStore @Inject constructor(
|
||||||
it.toOutgoingGossipingRequest() as? OutgoingRoomKeyRequest
|
it.toOutgoingGossipingRequest() as? OutgoingRoomKeyRequest
|
||||||
}.firstOrNull {
|
}.firstOrNull {
|
||||||
it.requestBody?.algorithm == requestBody.algorithm
|
it.requestBody?.algorithm == requestBody.algorithm
|
||||||
it.requestBody?.roomId == requestBody.roomId
|
&& it.requestBody?.roomId == requestBody.roomId
|
||||||
it.requestBody?.senderKey == requestBody.senderKey
|
&& it.requestBody?.senderKey == requestBody.senderKey
|
||||||
it.requestBody?.sessionId == requestBody.sessionId
|
&& it.requestBody?.sessionId == requestBody.sessionId
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,79 @@
|
||||||
|
/*
|
||||||
|
* 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.matrix.android.internal.crypto.tasks
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.session.crypto.CryptoService
|
||||||
|
import im.vector.matrix.android.api.session.events.model.Event
|
||||||
|
import im.vector.matrix.android.api.session.room.send.SendState
|
||||||
|
import im.vector.matrix.android.internal.network.executeRequest
|
||||||
|
import im.vector.matrix.android.internal.session.room.RoomAPI
|
||||||
|
import im.vector.matrix.android.internal.session.room.send.LocalEchoUpdater
|
||||||
|
import im.vector.matrix.android.internal.session.room.send.SendResponse
|
||||||
|
import im.vector.matrix.android.internal.task.Task
|
||||||
|
import org.greenrobot.eventbus.EventBus
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
internal interface SendEventTask : Task<SendEventTask.Params, String> {
|
||||||
|
data class Params(
|
||||||
|
val event: Event,
|
||||||
|
val cryptoService: CryptoService?
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class DefaultSendEventTask @Inject constructor(
|
||||||
|
private val localEchoUpdater: LocalEchoUpdater,
|
||||||
|
private val encryptEventTask: DefaultEncryptEventTask,
|
||||||
|
private val roomAPI: RoomAPI,
|
||||||
|
private val eventBus: EventBus) : SendEventTask {
|
||||||
|
|
||||||
|
override suspend fun execute(params: SendEventTask.Params): String {
|
||||||
|
val event = handleEncryption(params)
|
||||||
|
val localId = event.eventId!!
|
||||||
|
|
||||||
|
try {
|
||||||
|
localEchoUpdater.updateSendState(localId, SendState.SENDING)
|
||||||
|
val executeRequest = executeRequest<SendResponse>(eventBus) {
|
||||||
|
apiCall = roomAPI.send(
|
||||||
|
localId,
|
||||||
|
roomId = event.roomId ?: "",
|
||||||
|
content = event.content,
|
||||||
|
eventType = event.type
|
||||||
|
)
|
||||||
|
}
|
||||||
|
localEchoUpdater.updateSendState(localId, SendState.SENT)
|
||||||
|
return executeRequest.eventId
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
localEchoUpdater.updateSendState(localId, SendState.UNDELIVERED)
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun handleEncryption(params: SendEventTask.Params): Event {
|
||||||
|
if (params.cryptoService?.isRoomEncrypted(params.event.roomId ?: "") == true) {
|
||||||
|
try {
|
||||||
|
return encryptEventTask.execute(EncryptEventTask.Params(
|
||||||
|
params.event.roomId ?: "",
|
||||||
|
params.event,
|
||||||
|
listOf("m.relates_to"),
|
||||||
|
params.cryptoService
|
||||||
|
))
|
||||||
|
} catch (throwable: Throwable) {
|
||||||
|
// We said it's ok to send verification request in clear
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return params.event
|
||||||
|
}
|
||||||
|
}
|
|
@ -28,7 +28,7 @@ import kotlin.math.ceil
|
||||||
*/
|
*/
|
||||||
object HkdfSha256 {
|
object HkdfSha256 {
|
||||||
|
|
||||||
public fun deriveSecret(inputKeyMaterial: ByteArray, salt: ByteArray?, info: ByteArray, outputLength: Int): ByteArray {
|
fun deriveSecret(inputKeyMaterial: ByteArray, salt: ByteArray?, info: ByteArray, outputLength: Int): ByteArray {
|
||||||
return expand(extract(salt, inputKeyMaterial), info, outputLength)
|
return expand(extract(salt, inputKeyMaterial), info, outputLength)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -273,7 +273,7 @@ internal abstract class SASDefaultVerificationTransaction(
|
||||||
if (keyIDNoPrefix == otherCrossSigningMasterKeyPublic) {
|
if (keyIDNoPrefix == otherCrossSigningMasterKeyPublic) {
|
||||||
// Check the signature
|
// Check the signature
|
||||||
val mac = macUsingAgreedMethod(otherCrossSigningMasterKeyPublic, baseInfo + it)
|
val mac = macUsingAgreedMethod(otherCrossSigningMasterKeyPublic, baseInfo + it)
|
||||||
if (mac != theirMacSafe.mac.get(it)) {
|
if (mac != theirMacSafe.mac[it]) {
|
||||||
// WRONG!
|
// WRONG!
|
||||||
Timber.e("## SAS Verification: mac mismatch for MasterKey with id $keyIDNoPrefix")
|
Timber.e("## SAS Verification: mac mismatch for MasterKey with id $keyIDNoPrefix")
|
||||||
cancel(CancelCode.MismatchedKeys)
|
cancel(CancelCode.MismatchedKeys)
|
||||||
|
|
|
@ -85,7 +85,7 @@ internal class SendVerificationMessageWorker(context: Context,
|
||||||
private const val OUTPUT_KEY_FAILED = "failed"
|
private const val OUTPUT_KEY_FAILED = "failed"
|
||||||
|
|
||||||
fun hasFailed(outputData: Data): Boolean {
|
fun hasFailed(outputData: Data): Boolean {
|
||||||
return outputData.getBoolean(SendVerificationMessageWorker.OUTPUT_KEY_FAILED, false)
|
return outputData.getBoolean(OUTPUT_KEY_FAILED, false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@ package im.vector.matrix.android.internal.database
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.util.Base64
|
import android.util.Base64
|
||||||
|
import androidx.core.content.edit
|
||||||
import im.vector.matrix.android.BuildConfig
|
import im.vector.matrix.android.BuildConfig
|
||||||
import im.vector.matrix.android.internal.session.securestorage.SecretStoringUtils
|
import im.vector.matrix.android.internal.session.securestorage.SecretStoringUtils
|
||||||
import io.realm.RealmConfiguration
|
import io.realm.RealmConfiguration
|
||||||
|
@ -67,10 +68,9 @@ internal class RealmKeysUtils @Inject constructor(context: Context,
|
||||||
val key = generateKeyForRealm()
|
val key = generateKeyForRealm()
|
||||||
val encodedKey = Base64.encodeToString(key, Base64.NO_PADDING)
|
val encodedKey = Base64.encodeToString(key, Base64.NO_PADDING)
|
||||||
val toStore = secretStoringUtils.securelyStoreString(encodedKey, alias)
|
val toStore = secretStoringUtils.securelyStoreString(encodedKey, alias)
|
||||||
sharedPreferences
|
sharedPreferences.edit {
|
||||||
.edit()
|
putString("${ENCRYPTED_KEY_PREFIX}_$alias", Base64.encodeToString(toStore!!, Base64.NO_PADDING))
|
||||||
.putString("${ENCRYPTED_KEY_PREFIX}_$alias", Base64.encodeToString(toStore!!, Base64.NO_PADDING))
|
}
|
||||||
.apply()
|
|
||||||
return key
|
return key
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,10 +107,9 @@ internal class RealmKeysUtils @Inject constructor(context: Context,
|
||||||
if (hasKeyForDatabase(alias)) {
|
if (hasKeyForDatabase(alias)) {
|
||||||
secretStoringUtils.safeDeleteKey(alias)
|
secretStoringUtils.safeDeleteKey(alias)
|
||||||
|
|
||||||
sharedPreferences
|
sharedPreferences.edit {
|
||||||
.edit()
|
remove("${ENCRYPTED_KEY_PREFIX}_$alias")
|
||||||
.remove("${ENCRYPTED_KEY_PREFIX}_$alias")
|
}
|
||||||
.apply()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
package im.vector.matrix.android.internal.database
|
package im.vector.matrix.android.internal.database
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import androidx.core.content.edit
|
||||||
import im.vector.matrix.android.internal.database.model.SessionRealmModule
|
import im.vector.matrix.android.internal.database.model.SessionRealmModule
|
||||||
import im.vector.matrix.android.internal.di.SessionFilesDirectory
|
import im.vector.matrix.android.internal.di.SessionFilesDirectory
|
||||||
import im.vector.matrix.android.internal.di.SessionId
|
import im.vector.matrix.android.internal.di.SessionId
|
||||||
|
@ -54,10 +55,9 @@ internal class SessionRealmConfigurationFactory @Inject constructor(
|
||||||
Timber.v("************************************************************")
|
Timber.v("************************************************************")
|
||||||
deleteRealmFiles()
|
deleteRealmFiles()
|
||||||
}
|
}
|
||||||
sharedPreferences
|
sharedPreferences.edit {
|
||||||
.edit()
|
putBoolean("$REALM_SHOULD_CLEAR_FLAG_$sessionId", true)
|
||||||
.putBoolean("$REALM_SHOULD_CLEAR_FLAG_$sessionId", true)
|
}
|
||||||
.apply()
|
|
||||||
|
|
||||||
val realmConfiguration = RealmConfiguration.Builder()
|
val realmConfiguration = RealmConfiguration.Builder()
|
||||||
.compactOnLaunch()
|
.compactOnLaunch()
|
||||||
|
@ -73,10 +73,9 @@ internal class SessionRealmConfigurationFactory @Inject constructor(
|
||||||
// Try creating a realm instance and if it succeeds we can clear the flag
|
// Try creating a realm instance and if it succeeds we can clear the flag
|
||||||
Realm.getInstance(realmConfiguration).use {
|
Realm.getInstance(realmConfiguration).use {
|
||||||
Timber.v("Successfully create realm instance")
|
Timber.v("Successfully create realm instance")
|
||||||
sharedPreferences
|
sharedPreferences.edit {
|
||||||
.edit()
|
putBoolean("$REALM_SHOULD_CLEAR_FLAG_$sessionId", false)
|
||||||
.putBoolean("$REALM_SHOULD_CLEAR_FLAG_$sessionId", false)
|
}
|
||||||
.apply()
|
|
||||||
}
|
}
|
||||||
return realmConfiguration
|
return realmConfiguration
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,7 +25,7 @@ internal object TimelineEventFilter {
|
||||||
*/
|
*/
|
||||||
internal object Content {
|
internal object Content {
|
||||||
internal const val EDIT = """{*"m.relates_to"*"rel_type":*"m.replace"*}"""
|
internal const val EDIT = """{*"m.relates_to"*"rel_type":*"m.replace"*}"""
|
||||||
internal const val RESPONSE = """{*"m.relates_to"*"rel_type":*"m.response"*}"""
|
internal const val RESPONSE = """{*"m.relates_to"*"rel_type":*"org.matrix.response"*}"""
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2019 New Vector Ltd
|
* Copyright 2020 New Vector Ltd
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -22,12 +22,13 @@ import kotlinx.coroutines.CancellationException
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import org.greenrobot.eventbus.EventBus
|
import org.greenrobot.eventbus.EventBus
|
||||||
import retrofit2.Call
|
import retrofit2.Call
|
||||||
|
import retrofit2.awaitResponse
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
|
||||||
internal suspend inline fun <DATA> executeRequest(eventBus: EventBus?,
|
internal suspend inline fun <DATA : Any> executeRequest(eventBus: EventBus?,
|
||||||
block: Request<DATA>.() -> Unit) = Request<DATA>(eventBus).apply(block).execute()
|
block: Request<DATA>.() -> Unit) = Request<DATA>(eventBus).apply(block).execute()
|
||||||
|
|
||||||
internal class Request<DATA>(private val eventBus: EventBus?) {
|
internal class Request<DATA : Any>(private val eventBus: EventBus?) {
|
||||||
|
|
||||||
var isRetryable = false
|
var isRetryable = false
|
||||||
var initialDelay: Long = 100L
|
var initialDelay: Long = 100L
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
*
|
*
|
||||||
* * Copyright 2019 New Vector Ltd
|
* * Copyright 2020 New Vector Ltd
|
||||||
* *
|
* *
|
||||||
* * Licensed under the Apache License, Version 2.0 (the "License");
|
* * Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* * you may not use this file except in compliance with the License.
|
* * you may not use this file except in compliance with the License.
|
||||||
|
@ -26,8 +26,6 @@ import im.vector.matrix.android.internal.di.MoshiProvider
|
||||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||||
import okhttp3.ResponseBody
|
import okhttp3.ResponseBody
|
||||||
import org.greenrobot.eventbus.EventBus
|
import org.greenrobot.eventbus.EventBus
|
||||||
import retrofit2.Call
|
|
||||||
import retrofit2.Callback
|
|
||||||
import retrofit2.Response
|
import retrofit2.Response
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
@ -35,23 +33,6 @@ import java.net.HttpURLConnection
|
||||||
import kotlin.coroutines.resume
|
import kotlin.coroutines.resume
|
||||||
import kotlin.coroutines.resumeWithException
|
import kotlin.coroutines.resumeWithException
|
||||||
|
|
||||||
internal suspend fun <T> Call<T>.awaitResponse(): Response<T> {
|
|
||||||
return suspendCancellableCoroutine { continuation ->
|
|
||||||
continuation.invokeOnCancellation {
|
|
||||||
cancel()
|
|
||||||
}
|
|
||||||
enqueue(object : Callback<T> {
|
|
||||||
override fun onResponse(call: Call<T>, response: Response<T>) {
|
|
||||||
continuation.resume(response)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onFailure(call: Call<T>, t: Throwable) {
|
|
||||||
continuation.resumeWithException(t)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal suspend fun okhttp3.Call.awaitResponse(): okhttp3.Response {
|
internal suspend fun okhttp3.Call.awaitResponse(): okhttp3.Response {
|
||||||
return suspendCancellableCoroutine { continuation ->
|
return suspendCancellableCoroutine { continuation ->
|
||||||
continuation.invokeOnCancellation {
|
continuation.invokeOnCancellation {
|
||||||
|
|
|
@ -33,7 +33,7 @@ data class Fingerprint(
|
||||||
|
|
||||||
@Throws(CertificateException::class)
|
@Throws(CertificateException::class)
|
||||||
fun matchesCert(cert: X509Certificate): Boolean {
|
fun matchesCert(cert: X509Certificate): Boolean {
|
||||||
var o: Fingerprint? = when (hashType) {
|
val o: Fingerprint? = when (hashType) {
|
||||||
HashType.SHA256 -> newSha256Fingerprint(cert)
|
HashType.SHA256 -> newSha256Fingerprint(cert)
|
||||||
HashType.SHA1 -> newSha1Fingerprint(cert)
|
HashType.SHA1 -> newSha1Fingerprint(cert)
|
||||||
}
|
}
|
||||||
|
@ -76,6 +76,7 @@ data class Fingerprint(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = false)
|
||||||
enum class HashType {
|
enum class HashType {
|
||||||
@Json(name = "sha-1") SHA1,
|
@Json(name = "sha-1") SHA1,
|
||||||
@Json(name = "sha-256")SHA256
|
@Json(name = "sha-256")SHA256
|
||||||
|
|
|
@ -28,6 +28,7 @@ import im.vector.matrix.android.api.session.Session
|
||||||
import im.vector.matrix.android.api.session.account.AccountService
|
import im.vector.matrix.android.api.session.account.AccountService
|
||||||
import im.vector.matrix.android.api.session.accountdata.AccountDataService
|
import im.vector.matrix.android.api.session.accountdata.AccountDataService
|
||||||
import im.vector.matrix.android.api.session.cache.CacheService
|
import im.vector.matrix.android.api.session.cache.CacheService
|
||||||
|
import im.vector.matrix.android.api.session.call.CallSignalingService
|
||||||
import im.vector.matrix.android.api.session.content.ContentUploadStateTracker
|
import im.vector.matrix.android.api.session.content.ContentUploadStateTracker
|
||||||
import im.vector.matrix.android.api.session.content.ContentUrlResolver
|
import im.vector.matrix.android.api.session.content.ContentUrlResolver
|
||||||
import im.vector.matrix.android.api.session.crypto.CryptoService
|
import im.vector.matrix.android.api.session.crypto.CryptoService
|
||||||
|
@ -108,7 +109,8 @@ internal class DefaultSession @Inject constructor(
|
||||||
private val coroutineDispatchers: MatrixCoroutineDispatchers,
|
private val coroutineDispatchers: MatrixCoroutineDispatchers,
|
||||||
private val defaultIdentityService: DefaultIdentityService,
|
private val defaultIdentityService: DefaultIdentityService,
|
||||||
private val integrationManagerService: IntegrationManagerService,
|
private val integrationManagerService: IntegrationManagerService,
|
||||||
private val taskExecutor: TaskExecutor)
|
private val taskExecutor: TaskExecutor,
|
||||||
|
private val callSignalingService: Lazy<CallSignalingService>)
|
||||||
: Session,
|
: Session,
|
||||||
RoomService by roomService.get(),
|
RoomService by roomService.get(),
|
||||||
RoomDirectoryService by roomDirectoryService.get(),
|
RoomDirectoryService by roomDirectoryService.get(),
|
||||||
|
@ -244,6 +246,8 @@ internal class DefaultSession @Inject constructor(
|
||||||
|
|
||||||
override fun integrationManagerService() = integrationManagerService
|
override fun integrationManagerService() = integrationManagerService
|
||||||
|
|
||||||
|
override fun callSignalingService(): CallSignalingService = callSignalingService.get()
|
||||||
|
|
||||||
override fun addListener(listener: Session.Listener) {
|
override fun addListener(listener: Session.Listener) {
|
||||||
sessionListeners.addListener(listener)
|
sessionListeners.addListener(listener)
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,6 +30,7 @@ import im.vector.matrix.android.internal.di.SessionAssistedInjectModule
|
||||||
import im.vector.matrix.android.internal.network.NetworkConnectivityChecker
|
import im.vector.matrix.android.internal.network.NetworkConnectivityChecker
|
||||||
import im.vector.matrix.android.internal.session.account.AccountModule
|
import im.vector.matrix.android.internal.session.account.AccountModule
|
||||||
import im.vector.matrix.android.internal.session.cache.CacheModule
|
import im.vector.matrix.android.internal.session.cache.CacheModule
|
||||||
|
import im.vector.matrix.android.internal.session.call.CallModule
|
||||||
import im.vector.matrix.android.internal.session.content.ContentModule
|
import im.vector.matrix.android.internal.session.content.ContentModule
|
||||||
import im.vector.matrix.android.internal.session.content.UploadContentWorker
|
import im.vector.matrix.android.internal.session.content.UploadContentWorker
|
||||||
import im.vector.matrix.android.internal.session.filter.FilterModule
|
import im.vector.matrix.android.internal.session.filter.FilterModule
|
||||||
|
@ -83,7 +84,8 @@ import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
||||||
AccountDataModule::class,
|
AccountDataModule::class,
|
||||||
ProfileModule::class,
|
ProfileModule::class,
|
||||||
SessionAssistedInjectModule::class,
|
SessionAssistedInjectModule::class,
|
||||||
AccountModule::class
|
AccountModule::class,
|
||||||
|
CallModule::class
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
@SessionScope
|
@SessionScope
|
||||||
|
|
|
@ -60,6 +60,7 @@ import im.vector.matrix.android.internal.network.RetrofitFactory
|
||||||
import im.vector.matrix.android.internal.network.httpclient.addAccessTokenInterceptor
|
import im.vector.matrix.android.internal.network.httpclient.addAccessTokenInterceptor
|
||||||
import im.vector.matrix.android.internal.network.token.AccessTokenProvider
|
import im.vector.matrix.android.internal.network.token.AccessTokenProvider
|
||||||
import im.vector.matrix.android.internal.network.token.HomeserverAccessTokenProvider
|
import im.vector.matrix.android.internal.network.token.HomeserverAccessTokenProvider
|
||||||
|
import im.vector.matrix.android.internal.session.call.CallEventObserver
|
||||||
import im.vector.matrix.android.internal.session.group.GroupSummaryUpdater
|
import im.vector.matrix.android.internal.session.group.GroupSummaryUpdater
|
||||||
import im.vector.matrix.android.internal.session.homeserver.DefaultHomeServerCapabilitiesService
|
import im.vector.matrix.android.internal.session.homeserver.DefaultHomeServerCapabilitiesService
|
||||||
import im.vector.matrix.android.internal.session.identity.DefaultIdentityService
|
import im.vector.matrix.android.internal.session.identity.DefaultIdentityService
|
||||||
|
@ -72,7 +73,6 @@ import im.vector.matrix.android.internal.session.securestorage.DefaultSecureStor
|
||||||
import im.vector.matrix.android.internal.session.typing.DefaultTypingUsersTracker
|
import im.vector.matrix.android.internal.session.typing.DefaultTypingUsersTracker
|
||||||
import im.vector.matrix.android.internal.session.user.accountdata.DefaultAccountDataService
|
import im.vector.matrix.android.internal.session.user.accountdata.DefaultAccountDataService
|
||||||
import im.vector.matrix.android.internal.session.widgets.DefaultWidgetURLFormatter
|
import im.vector.matrix.android.internal.session.widgets.DefaultWidgetURLFormatter
|
||||||
import im.vector.matrix.android.internal.session.widgets.WidgetManager
|
|
||||||
import im.vector.matrix.android.internal.util.md5
|
import im.vector.matrix.android.internal.util.md5
|
||||||
import io.realm.RealmConfiguration
|
import io.realm.RealmConfiguration
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
|
@ -263,7 +263,7 @@ internal abstract class SessionModule {
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
@IntoSet
|
@IntoSet
|
||||||
abstract fun bindWidgetManager(observer: WidgetManager): SessionLifecycleObserver
|
abstract fun bindCallEventObserver(observer: CallEventObserver): SessionLifecycleObserver
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
@IntoSet
|
@IntoSet
|
||||||
|
|
|
@ -0,0 +1,66 @@
|
||||||
|
/*
|
||||||
|
* 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.matrix.android.internal.session.call
|
||||||
|
|
||||||
|
import com.zhuinden.monarchy.Monarchy
|
||||||
|
import im.vector.matrix.android.api.session.events.model.EventType
|
||||||
|
import im.vector.matrix.android.internal.database.RealmLiveEntityObserver
|
||||||
|
import im.vector.matrix.android.internal.database.mapper.asDomain
|
||||||
|
import im.vector.matrix.android.internal.database.model.EventEntity
|
||||||
|
import im.vector.matrix.android.internal.database.query.whereTypes
|
||||||
|
import im.vector.matrix.android.internal.di.SessionDatabase
|
||||||
|
import im.vector.matrix.android.internal.di.UserId
|
||||||
|
import io.realm.OrderedCollectionChangeSet
|
||||||
|
import io.realm.RealmConfiguration
|
||||||
|
import io.realm.RealmResults
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import timber.log.Timber
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
internal class CallEventObserver @Inject constructor(
|
||||||
|
@SessionDatabase realmConfiguration: RealmConfiguration,
|
||||||
|
@UserId private val userId: String,
|
||||||
|
private val task: CallEventsObserverTask
|
||||||
|
) : RealmLiveEntityObserver<EventEntity>(realmConfiguration) {
|
||||||
|
|
||||||
|
override val query = Monarchy.Query<EventEntity> {
|
||||||
|
EventEntity.whereTypes(it, listOf(
|
||||||
|
EventType.CALL_ANSWER,
|
||||||
|
EventType.CALL_CANDIDATES,
|
||||||
|
EventType.CALL_INVITE,
|
||||||
|
EventType.CALL_HANGUP,
|
||||||
|
EventType.ENCRYPTED)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onChange(results: RealmResults<EventEntity>, changeSet: OrderedCollectionChangeSet) {
|
||||||
|
Timber.v("EventRelationsAggregationUpdater called with ${changeSet.insertions.size} insertions")
|
||||||
|
|
||||||
|
val insertedDomains = changeSet.insertions
|
||||||
|
.asSequence()
|
||||||
|
.mapNotNull { results[it]?.asDomain() }
|
||||||
|
.toList()
|
||||||
|
|
||||||
|
val params = CallEventsObserverTask.Params(
|
||||||
|
insertedDomains,
|
||||||
|
userId
|
||||||
|
)
|
||||||
|
observerScope.launch {
|
||||||
|
task.execute(params)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,92 @@
|
||||||
|
/*
|
||||||
|
* 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.matrix.android.internal.session.call
|
||||||
|
|
||||||
|
import com.zhuinden.monarchy.Monarchy
|
||||||
|
import im.vector.matrix.android.api.session.crypto.CryptoService
|
||||||
|
import im.vector.matrix.android.api.session.crypto.MXCryptoError
|
||||||
|
import im.vector.matrix.android.api.session.events.model.Event
|
||||||
|
import im.vector.matrix.android.api.session.events.model.EventType
|
||||||
|
import im.vector.matrix.android.internal.crypto.algorithms.olm.OlmDecryptionResult
|
||||||
|
import im.vector.matrix.android.internal.di.SessionDatabase
|
||||||
|
import im.vector.matrix.android.internal.task.Task
|
||||||
|
import im.vector.matrix.android.internal.util.awaitTransaction
|
||||||
|
import io.realm.Realm
|
||||||
|
import timber.log.Timber
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
internal interface CallEventsObserverTask : Task<CallEventsObserverTask.Params, Unit> {
|
||||||
|
|
||||||
|
data class Params(
|
||||||
|
val events: List<Event>,
|
||||||
|
val userId: String
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class DefaultCallEventsObserverTask @Inject constructor(
|
||||||
|
@SessionDatabase private val monarchy: Monarchy,
|
||||||
|
private val cryptoService: CryptoService,
|
||||||
|
private val callService: DefaultCallSignalingService) : CallEventsObserverTask {
|
||||||
|
|
||||||
|
override suspend fun execute(params: CallEventsObserverTask.Params) {
|
||||||
|
val events = params.events
|
||||||
|
val userId = params.userId
|
||||||
|
monarchy.awaitTransaction { realm ->
|
||||||
|
Timber.v(">>> DefaultCallEventsObserverTask[${params.hashCode()}] called with ${events.size} events")
|
||||||
|
update(realm, events, userId)
|
||||||
|
Timber.v("<<< DefaultCallEventsObserverTask[${params.hashCode()}] finished")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun update(realm: Realm, events: List<Event>, userId: String) {
|
||||||
|
val now = System.currentTimeMillis()
|
||||||
|
// TODO might check if an invite is not closed (hangup/answsered) in the same event batch?
|
||||||
|
events.forEach { event ->
|
||||||
|
event.roomId ?: return@forEach Unit.also {
|
||||||
|
Timber.w("Event with no room id ${event.eventId}")
|
||||||
|
}
|
||||||
|
val age = now - (event.ageLocalTs ?: now)
|
||||||
|
if (age > 40_000) {
|
||||||
|
// To old to ring?
|
||||||
|
return@forEach
|
||||||
|
}
|
||||||
|
event.ageLocalTs
|
||||||
|
decryptIfNeeded(event)
|
||||||
|
if (EventType.isCallEvent(event.getClearType())) {
|
||||||
|
callService.onCallEvent(event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Timber.v("$realm : $userId")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun decryptIfNeeded(event: Event) {
|
||||||
|
if (event.isEncrypted() && event.mxDecryptionResult == null) {
|
||||||
|
try {
|
||||||
|
val result = cryptoService.decryptEvent(event, event.roomId ?: "")
|
||||||
|
event.mxDecryptionResult = OlmDecryptionResult(
|
||||||
|
payload = result.clearEvent,
|
||||||
|
senderKey = result.senderCurve25519Key,
|
||||||
|
keysClaimed = result.claimedEd25519Key?.let { k -> mapOf("ed25519" to k) },
|
||||||
|
forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain
|
||||||
|
)
|
||||||
|
} catch (e: MXCryptoError) {
|
||||||
|
Timber.v("Call service: Failed to decrypt event")
|
||||||
|
// TODO -> we should keep track of this and retry, or aggregation will be broken
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
/*
|
||||||
|
* 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.matrix.android.internal.session.call
|
||||||
|
|
||||||
|
import dagger.Binds
|
||||||
|
import dagger.Module
|
||||||
|
import dagger.Provides
|
||||||
|
import im.vector.matrix.android.api.session.call.CallSignalingService
|
||||||
|
import im.vector.matrix.android.internal.session.SessionScope
|
||||||
|
import retrofit2.Retrofit
|
||||||
|
|
||||||
|
@Module
|
||||||
|
internal abstract class CallModule {
|
||||||
|
|
||||||
|
@Module
|
||||||
|
companion object {
|
||||||
|
@Provides
|
||||||
|
@JvmStatic
|
||||||
|
@SessionScope
|
||||||
|
fun providesVoipApi(retrofit: Retrofit): VoipApi {
|
||||||
|
return retrofit.create(VoipApi::class.java)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindCallSignalingService(service: DefaultCallSignalingService): CallSignalingService
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindGetTurnServerTask(task: DefaultGetTurnServerTask): GetTurnServerTask
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindCallEventsObserverTask(task: DefaultCallEventsObserverTask): CallEventsObserverTask
|
||||||
|
}
|
|
@ -0,0 +1,236 @@
|
||||||
|
/*
|
||||||
|
* 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.matrix.android.internal.session.call
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
|
import im.vector.matrix.android.api.extensions.tryThis
|
||||||
|
import im.vector.matrix.android.api.session.call.CallSignalingService
|
||||||
|
import im.vector.matrix.android.api.session.call.CallState
|
||||||
|
import im.vector.matrix.android.api.session.call.CallsListener
|
||||||
|
import im.vector.matrix.android.api.session.call.MxCall
|
||||||
|
import im.vector.matrix.android.api.session.call.TurnServerResponse
|
||||||
|
import im.vector.matrix.android.api.session.events.model.Event
|
||||||
|
import im.vector.matrix.android.api.session.events.model.EventType
|
||||||
|
import im.vector.matrix.android.api.session.events.model.toModel
|
||||||
|
import im.vector.matrix.android.api.session.room.model.call.CallAnswerContent
|
||||||
|
import im.vector.matrix.android.api.session.room.model.call.CallCandidatesContent
|
||||||
|
import im.vector.matrix.android.api.session.room.model.call.CallHangupContent
|
||||||
|
import im.vector.matrix.android.api.session.room.model.call.CallInviteContent
|
||||||
|
import im.vector.matrix.android.api.util.Cancelable
|
||||||
|
import im.vector.matrix.android.api.util.NoOpCancellable
|
||||||
|
import im.vector.matrix.android.internal.di.UserId
|
||||||
|
import im.vector.matrix.android.internal.session.SessionScope
|
||||||
|
import im.vector.matrix.android.internal.session.call.model.MxCallImpl
|
||||||
|
import im.vector.matrix.android.internal.session.room.send.LocalEchoEventFactory
|
||||||
|
import im.vector.matrix.android.internal.session.room.send.RoomEventSender
|
||||||
|
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||||
|
import im.vector.matrix.android.internal.task.configureWith
|
||||||
|
import timber.log.Timber
|
||||||
|
import java.util.UUID
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@SessionScope
|
||||||
|
internal class DefaultCallSignalingService @Inject constructor(
|
||||||
|
@UserId
|
||||||
|
private val userId: String,
|
||||||
|
private val localEchoEventFactory: LocalEchoEventFactory,
|
||||||
|
private val roomEventSender: RoomEventSender,
|
||||||
|
private val taskExecutor: TaskExecutor,
|
||||||
|
private val turnServerTask: GetTurnServerTask
|
||||||
|
) : CallSignalingService {
|
||||||
|
|
||||||
|
private val callListeners = mutableSetOf<CallsListener>()
|
||||||
|
|
||||||
|
private val activeCalls = mutableListOf<MxCall>()
|
||||||
|
|
||||||
|
private var cachedTurnServerResponse: TurnServerResponse? = null
|
||||||
|
|
||||||
|
override fun getTurnServer(callback: MatrixCallback<TurnServerResponse>): Cancelable {
|
||||||
|
if (cachedTurnServerResponse != null) {
|
||||||
|
cachedTurnServerResponse?.let { callback.onSuccess(it) }
|
||||||
|
return NoOpCancellable
|
||||||
|
}
|
||||||
|
return turnServerTask
|
||||||
|
.configureWith(GetTurnServerTask.Params) {
|
||||||
|
this.callback = object : MatrixCallback<TurnServerResponse> {
|
||||||
|
override fun onSuccess(data: TurnServerResponse) {
|
||||||
|
cachedTurnServerResponse = data
|
||||||
|
callback.onSuccess(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFailure(failure: Throwable) {
|
||||||
|
callback.onFailure(failure)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.executeBy(taskExecutor)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun createOutgoingCall(roomId: String, otherUserId: String, isVideoCall: Boolean): MxCall {
|
||||||
|
return MxCallImpl(
|
||||||
|
callId = UUID.randomUUID().toString(),
|
||||||
|
isOutgoing = true,
|
||||||
|
roomId = roomId,
|
||||||
|
userId = userId,
|
||||||
|
otherUserId = otherUserId,
|
||||||
|
isVideoCall = isVideoCall,
|
||||||
|
localEchoEventFactory = localEchoEventFactory,
|
||||||
|
roomEventSender = roomEventSender
|
||||||
|
).also {
|
||||||
|
activeCalls.add(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun addCallListener(listener: CallsListener) {
|
||||||
|
callListeners.add(listener)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun removeCallListener(listener: CallsListener) {
|
||||||
|
callListeners.remove(listener)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getCallWithId(callId: String): MxCall? {
|
||||||
|
Timber.v("## VOIP getCallWithId $callId all calls ${activeCalls.map { it.callId }}")
|
||||||
|
return activeCalls.find { it.callId == callId }
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun onCallEvent(event: Event) {
|
||||||
|
when (event.getClearType()) {
|
||||||
|
EventType.CALL_ANSWER -> {
|
||||||
|
event.getClearContent().toModel<CallAnswerContent>()?.let {
|
||||||
|
if (event.senderId == userId) {
|
||||||
|
// ok it's an answer from me.. is it remote echo or other session
|
||||||
|
val knownCall = getCallWithId(it.callId)
|
||||||
|
if (knownCall == null) {
|
||||||
|
Timber.d("## VOIP onCallEvent ${event.getClearType()} id ${it.callId} send by me")
|
||||||
|
} else if (!knownCall.isOutgoing) {
|
||||||
|
// incoming call
|
||||||
|
// if it was anwsered by this session, the call state would be in Answering(or connected) state
|
||||||
|
if (knownCall.state == CallState.LocalRinging) {
|
||||||
|
// discard current call, it's answered by another of my session
|
||||||
|
onCallManageByOtherSession(it.callId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
onCallAnswer(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EventType.CALL_INVITE -> {
|
||||||
|
if (event.senderId == userId) {
|
||||||
|
// Always ignore local echos of invite
|
||||||
|
return
|
||||||
|
}
|
||||||
|
event.getClearContent().toModel<CallInviteContent>()?.let { content ->
|
||||||
|
val incomingCall = MxCallImpl(
|
||||||
|
callId = content.callId ?: return@let,
|
||||||
|
isOutgoing = false,
|
||||||
|
roomId = event.roomId ?: return@let,
|
||||||
|
userId = userId,
|
||||||
|
otherUserId = event.senderId ?: return@let,
|
||||||
|
isVideoCall = content.isVideo(),
|
||||||
|
localEchoEventFactory = localEchoEventFactory,
|
||||||
|
roomEventSender = roomEventSender
|
||||||
|
)
|
||||||
|
activeCalls.add(incomingCall)
|
||||||
|
onCallInvite(incomingCall, content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EventType.CALL_HANGUP -> {
|
||||||
|
event.getClearContent().toModel<CallHangupContent>()?.let { content ->
|
||||||
|
|
||||||
|
if (event.senderId == userId) {
|
||||||
|
// ok it's an answer from me.. is it remote echo or other session
|
||||||
|
val knownCall = getCallWithId(content.callId)
|
||||||
|
if (knownCall == null) {
|
||||||
|
Timber.d("## VOIP onCallEvent ${event.getClearType()} id ${content.callId} send by me")
|
||||||
|
} else if (!knownCall.isOutgoing) {
|
||||||
|
// incoming call
|
||||||
|
if (knownCall.state == CallState.LocalRinging) {
|
||||||
|
// discard current call, it's answered by another of my session
|
||||||
|
onCallManageByOtherSession(content.callId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
onCallHangup(content)
|
||||||
|
activeCalls.removeAll { it.callId == content.callId }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EventType.CALL_CANDIDATES -> {
|
||||||
|
if (event.senderId == userId) {
|
||||||
|
// Always ignore local echos of invite
|
||||||
|
return
|
||||||
|
}
|
||||||
|
event.getClearContent().toModel<CallCandidatesContent>()?.let { content ->
|
||||||
|
activeCalls.firstOrNull { it.callId == content.callId }?.let {
|
||||||
|
onCallIceCandidate(it, content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onCallHangup(hangup: CallHangupContent) {
|
||||||
|
callListeners.toList().forEach {
|
||||||
|
tryThis {
|
||||||
|
it.onCallHangupReceived(hangup)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onCallAnswer(answer: CallAnswerContent) {
|
||||||
|
callListeners.toList().forEach {
|
||||||
|
tryThis {
|
||||||
|
it.onCallAnswerReceived(answer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onCallManageByOtherSession(callId: String) {
|
||||||
|
callListeners.toList().forEach {
|
||||||
|
tryThis {
|
||||||
|
it.onCallManagedByOtherSession(callId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onCallInvite(incomingCall: MxCall, invite: CallInviteContent) {
|
||||||
|
// Ignore the invitation from current user
|
||||||
|
if (incomingCall.otherUserId == userId) return
|
||||||
|
|
||||||
|
callListeners.toList().forEach {
|
||||||
|
tryThis {
|
||||||
|
it.onCallInviteReceived(incomingCall, invite)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onCallIceCandidate(incomingCall: MxCall, candidates: CallCandidatesContent) {
|
||||||
|
callListeners.toList().forEach {
|
||||||
|
tryThis {
|
||||||
|
it.onCallIceCandidateReceived(incomingCall, candidates)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val CALL_TIMEOUT_MS = 120_000
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
/*
|
||||||
|
* 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.matrix.android.internal.session.call
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.session.call.TurnServerResponse
|
||||||
|
import im.vector.matrix.android.internal.network.executeRequest
|
||||||
|
import im.vector.matrix.android.internal.task.Task
|
||||||
|
import org.greenrobot.eventbus.EventBus
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
internal abstract class GetTurnServerTask : Task<GetTurnServerTask.Params, TurnServerResponse> {
|
||||||
|
object Params
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class DefaultGetTurnServerTask @Inject constructor(private val voipAPI: VoipApi,
|
||||||
|
private val eventBus: EventBus) : GetTurnServerTask() {
|
||||||
|
|
||||||
|
override suspend fun execute(params: Params): TurnServerResponse {
|
||||||
|
return executeRequest(eventBus) {
|
||||||
|
apiCall = voipAPI.getTurnServer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
/*
|
||||||
|
* 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.matrix.android.internal.session.call
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.session.call.TurnServerResponse
|
||||||
|
import im.vector.matrix.android.internal.network.NetworkConstants
|
||||||
|
import retrofit2.Call
|
||||||
|
import retrofit2.http.GET
|
||||||
|
|
||||||
|
internal interface VoipApi {
|
||||||
|
|
||||||
|
@GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "voip/turnServer")
|
||||||
|
fun getTurnServer(): Call<TurnServerResponse>
|
||||||
|
}
|
|
@ -0,0 +1,150 @@
|
||||||
|
/*
|
||||||
|
* 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.matrix.android.internal.session.call.model
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.session.call.CallState
|
||||||
|
import im.vector.matrix.android.api.session.call.MxCall
|
||||||
|
import im.vector.matrix.android.api.session.events.model.Content
|
||||||
|
import im.vector.matrix.android.api.session.events.model.Event
|
||||||
|
import im.vector.matrix.android.api.session.events.model.EventType
|
||||||
|
import im.vector.matrix.android.api.session.events.model.LocalEcho
|
||||||
|
import im.vector.matrix.android.api.session.events.model.UnsignedData
|
||||||
|
import im.vector.matrix.android.api.session.events.model.toContent
|
||||||
|
import im.vector.matrix.android.api.session.room.model.call.CallAnswerContent
|
||||||
|
import im.vector.matrix.android.api.session.room.model.call.CallCandidatesContent
|
||||||
|
import im.vector.matrix.android.api.session.room.model.call.CallHangupContent
|
||||||
|
import im.vector.matrix.android.api.session.room.model.call.CallInviteContent
|
||||||
|
import im.vector.matrix.android.internal.session.call.DefaultCallSignalingService
|
||||||
|
import im.vector.matrix.android.internal.session.room.send.LocalEchoEventFactory
|
||||||
|
import im.vector.matrix.android.internal.session.room.send.RoomEventSender
|
||||||
|
import org.webrtc.IceCandidate
|
||||||
|
import org.webrtc.SessionDescription
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
|
internal class MxCallImpl(
|
||||||
|
override val callId: String,
|
||||||
|
override val isOutgoing: Boolean,
|
||||||
|
override val roomId: String,
|
||||||
|
private val userId: String,
|
||||||
|
override val otherUserId: String,
|
||||||
|
override val isVideoCall: Boolean,
|
||||||
|
private val localEchoEventFactory: LocalEchoEventFactory,
|
||||||
|
private val roomEventSender: RoomEventSender
|
||||||
|
) : MxCall {
|
||||||
|
|
||||||
|
override var state: CallState = CallState.Idle
|
||||||
|
set(value) {
|
||||||
|
field = value
|
||||||
|
dispatchStateChange()
|
||||||
|
}
|
||||||
|
|
||||||
|
private val listeners = mutableListOf<MxCall.StateListener>()
|
||||||
|
|
||||||
|
override fun addListener(listener: MxCall.StateListener) {
|
||||||
|
listeners.add(listener)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun removeListener(listener: MxCall.StateListener) {
|
||||||
|
listeners.remove(listener)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun dispatchStateChange() {
|
||||||
|
listeners.forEach {
|
||||||
|
try {
|
||||||
|
it.onStateUpdate(this)
|
||||||
|
} catch (failure: Throwable) {
|
||||||
|
Timber.d("dispatchStateChange failed for call $callId : ${failure.localizedMessage}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
if (isOutgoing) {
|
||||||
|
state = CallState.Idle
|
||||||
|
} else {
|
||||||
|
// because it's created on reception of an offer
|
||||||
|
state = CallState.LocalRinging
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun offerSdp(sdp: SessionDescription) {
|
||||||
|
if (!isOutgoing) return
|
||||||
|
Timber.v("## VOIP offerSdp $callId")
|
||||||
|
state = CallState.Dialing
|
||||||
|
CallInviteContent(
|
||||||
|
callId = callId,
|
||||||
|
lifetime = DefaultCallSignalingService.CALL_TIMEOUT_MS,
|
||||||
|
offer = CallInviteContent.Offer(sdp = sdp.description)
|
||||||
|
)
|
||||||
|
.let { createEventAndLocalEcho(type = EventType.CALL_INVITE, roomId = roomId, content = it.toContent()) }
|
||||||
|
.also { roomEventSender.sendEvent(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun sendLocalIceCandidates(candidates: List<IceCandidate>) {
|
||||||
|
CallCandidatesContent(
|
||||||
|
callId = callId,
|
||||||
|
candidates = candidates.map {
|
||||||
|
CallCandidatesContent.Candidate(
|
||||||
|
sdpMid = it.sdpMid,
|
||||||
|
sdpMLineIndex = it.sdpMLineIndex,
|
||||||
|
candidate = it.sdp
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.let { createEventAndLocalEcho(type = EventType.CALL_CANDIDATES, roomId = roomId, content = it.toContent()) }
|
||||||
|
.also { roomEventSender.sendEvent(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun sendLocalIceCandidateRemovals(candidates: List<IceCandidate>) {
|
||||||
|
// For now we don't support this flow
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hangUp() {
|
||||||
|
Timber.v("## VOIP hangup $callId")
|
||||||
|
CallHangupContent(
|
||||||
|
callId = callId
|
||||||
|
)
|
||||||
|
.let { createEventAndLocalEcho(type = EventType.CALL_HANGUP, roomId = roomId, content = it.toContent()) }
|
||||||
|
.also { roomEventSender.sendEvent(it) }
|
||||||
|
state = CallState.Terminated
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun accept(sdp: SessionDescription) {
|
||||||
|
Timber.v("## VOIP accept $callId")
|
||||||
|
if (isOutgoing) return
|
||||||
|
state = CallState.Answering
|
||||||
|
CallAnswerContent(
|
||||||
|
callId = callId,
|
||||||
|
answer = CallAnswerContent.Answer(sdp = sdp.description)
|
||||||
|
)
|
||||||
|
.let { createEventAndLocalEcho(type = EventType.CALL_ANSWER, roomId = roomId, content = it.toContent()) }
|
||||||
|
.also { roomEventSender.sendEvent(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createEventAndLocalEcho(localId: String = LocalEcho.createLocalEchoId(), type: String, roomId: String, content: Content): Event {
|
||||||
|
return Event(
|
||||||
|
roomId = roomId,
|
||||||
|
originServerTs = System.currentTimeMillis(),
|
||||||
|
senderId = userId,
|
||||||
|
eventId = localId,
|
||||||
|
type = type,
|
||||||
|
content = content,
|
||||||
|
unsignedData = UnsignedData(age = null, transactionId = localId)
|
||||||
|
)
|
||||||
|
.also { localEchoEventFactory.createLocalEcho(it) }
|
||||||
|
}
|
||||||
|
}
|
|
@ -35,7 +35,8 @@ import javax.inject.Inject
|
||||||
internal class DefaultProfileService @Inject constructor(private val taskExecutor: TaskExecutor,
|
internal class DefaultProfileService @Inject constructor(private val taskExecutor: TaskExecutor,
|
||||||
@SessionDatabase private val monarchy: Monarchy,
|
@SessionDatabase private val monarchy: Monarchy,
|
||||||
private val refreshUserThreePidsTask: RefreshUserThreePidsTask,
|
private val refreshUserThreePidsTask: RefreshUserThreePidsTask,
|
||||||
private val getProfileInfoTask: GetProfileInfoTask) : ProfileService {
|
private val getProfileInfoTask: GetProfileInfoTask,
|
||||||
|
private val setDisplayNameTask: SetDisplayNameTask) : ProfileService {
|
||||||
|
|
||||||
override fun getDisplayName(userId: String, matrixCallback: MatrixCallback<Optional<String>>): Cancelable {
|
override fun getDisplayName(userId: String, matrixCallback: MatrixCallback<Optional<String>>): Cancelable {
|
||||||
val params = GetProfileInfoTask.Params(userId)
|
val params = GetProfileInfoTask.Params(userId)
|
||||||
|
@ -55,6 +56,14 @@ internal class DefaultProfileService @Inject constructor(private val taskExecuto
|
||||||
.executeBy(taskExecutor)
|
.executeBy(taskExecutor)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun setDisplayName(userId: String, newDisplayName: String, matrixCallback: MatrixCallback<Unit>): Cancelable {
|
||||||
|
return setDisplayNameTask
|
||||||
|
.configureWith(SetDisplayNameTask.Params(userId = userId, newDisplayName = newDisplayName)) {
|
||||||
|
callback = matrixCallback
|
||||||
|
}
|
||||||
|
.executeBy(taskExecutor)
|
||||||
|
}
|
||||||
|
|
||||||
override fun getAvatarUrl(userId: String, matrixCallback: MatrixCallback<Optional<String>>): Cancelable {
|
override fun getAvatarUrl(userId: String, matrixCallback: MatrixCallback<Optional<String>>): Cancelable {
|
||||||
val params = GetProfileInfoTask.Params(userId)
|
val params = GetProfileInfoTask.Params(userId)
|
||||||
return getProfileInfoTask
|
return getProfileInfoTask
|
||||||
|
|
|
@ -23,6 +23,7 @@ import retrofit2.Call
|
||||||
import retrofit2.http.Body
|
import retrofit2.http.Body
|
||||||
import retrofit2.http.GET
|
import retrofit2.http.GET
|
||||||
import retrofit2.http.POST
|
import retrofit2.http.POST
|
||||||
|
import retrofit2.http.PUT
|
||||||
import retrofit2.http.Path
|
import retrofit2.http.Path
|
||||||
|
|
||||||
internal interface ProfileAPI {
|
internal interface ProfileAPI {
|
||||||
|
@ -42,6 +43,12 @@ internal interface ProfileAPI {
|
||||||
@GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "account/3pid")
|
@GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "account/3pid")
|
||||||
fun getThreePIDs(): Call<AccountThreePidsResponse>
|
fun getThreePIDs(): Call<AccountThreePidsResponse>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Change user display name
|
||||||
|
*/
|
||||||
|
@PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "profile/{userId}/displayname")
|
||||||
|
fun setDisplayName(@Path("userId") userId: String, @Body body: SetDisplayNameBody): Call<Unit>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Bind a threePid
|
* Bind a threePid
|
||||||
* Ref: https://matrix.org/docs/spec/client_server/latest#post-matrix-client-r0-account-3pid-bind
|
* Ref: https://matrix.org/docs/spec/client_server/latest#post-matrix-client-r0-account-3pid-bind
|
||||||
|
|
|
@ -51,4 +51,7 @@ internal abstract class ProfileModule {
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindUnbindThreePidsTask(task: DefaultUnbindThreePidsTask): UnbindThreePidsTask
|
abstract fun bindUnbindThreePidsTask(task: DefaultUnbindThreePidsTask): UnbindThreePidsTask
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindSetDisplayNameTask(task: DefaultSetDisplayNameTask): SetDisplayNameTask
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
/*
|
||||||
|
* 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.matrix.android.internal.session.profile
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
internal data class SetDisplayNameBody(
|
||||||
|
/**
|
||||||
|
* The new display name for this user.
|
||||||
|
*/
|
||||||
|
@Json(name = "displayname")
|
||||||
|
val displayName: String
|
||||||
|
)
|
|
@ -0,0 +1,43 @@
|
||||||
|
/*
|
||||||
|
* 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.matrix.android.internal.session.profile
|
||||||
|
|
||||||
|
import im.vector.matrix.android.internal.network.executeRequest
|
||||||
|
import im.vector.matrix.android.internal.task.Task
|
||||||
|
import org.greenrobot.eventbus.EventBus
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
internal abstract class SetDisplayNameTask : Task<SetDisplayNameTask.Params, Unit> {
|
||||||
|
data class Params(
|
||||||
|
val userId: String,
|
||||||
|
val newDisplayName: String
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class DefaultSetDisplayNameTask @Inject constructor(
|
||||||
|
private val profileAPI: ProfileAPI,
|
||||||
|
private val eventBus: EventBus) : SetDisplayNameTask() {
|
||||||
|
|
||||||
|
override suspend fun execute(params: Params) {
|
||||||
|
return executeRequest(eventBus) {
|
||||||
|
val body = SetDisplayNameBody(
|
||||||
|
displayName = params.newDisplayName
|
||||||
|
)
|
||||||
|
apiCall = profileAPI.setDisplayName(params.userId, body)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -21,6 +21,7 @@ import im.vector.matrix.android.api.MatrixCallback
|
||||||
import im.vector.matrix.android.api.session.crypto.CryptoService
|
import im.vector.matrix.android.api.session.crypto.CryptoService
|
||||||
import im.vector.matrix.android.api.session.events.model.EventType
|
import im.vector.matrix.android.api.session.events.model.EventType
|
||||||
import im.vector.matrix.android.api.session.room.Room
|
import im.vector.matrix.android.api.session.room.Room
|
||||||
|
import im.vector.matrix.android.api.session.room.call.RoomCallService
|
||||||
import im.vector.matrix.android.api.session.room.members.MembershipService
|
import im.vector.matrix.android.api.session.room.members.MembershipService
|
||||||
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||||
import im.vector.matrix.android.api.session.room.model.relation.RelationService
|
import im.vector.matrix.android.api.session.room.model.relation.RelationService
|
||||||
|
@ -51,6 +52,7 @@ internal class DefaultRoom @Inject constructor(override val roomId: String,
|
||||||
private val stateService: StateService,
|
private val stateService: StateService,
|
||||||
private val uploadsService: UploadsService,
|
private val uploadsService: UploadsService,
|
||||||
private val reportingService: ReportingService,
|
private val reportingService: ReportingService,
|
||||||
|
private val roomCallService: RoomCallService,
|
||||||
private val readService: ReadService,
|
private val readService: ReadService,
|
||||||
private val typingService: TypingService,
|
private val typingService: TypingService,
|
||||||
private val tagsService: TagsService,
|
private val tagsService: TagsService,
|
||||||
|
@ -67,6 +69,7 @@ internal class DefaultRoom @Inject constructor(override val roomId: String,
|
||||||
StateService by stateService,
|
StateService by stateService,
|
||||||
UploadsService by uploadsService,
|
UploadsService by uploadsService,
|
||||||
ReportingService by reportingService,
|
ReportingService by reportingService,
|
||||||
|
RoomCallService by roomCallService,
|
||||||
ReadService by readService,
|
ReadService by readService,
|
||||||
TypingService by typingService,
|
TypingService by typingService,
|
||||||
TagsService by tagsService,
|
TagsService by tagsService,
|
||||||
|
|
|
@ -19,6 +19,7 @@ package im.vector.matrix.android.internal.session.room
|
||||||
import im.vector.matrix.android.api.session.crypto.CryptoService
|
import im.vector.matrix.android.api.session.crypto.CryptoService
|
||||||
import im.vector.matrix.android.api.session.room.Room
|
import im.vector.matrix.android.api.session.room.Room
|
||||||
import im.vector.matrix.android.internal.session.SessionScope
|
import im.vector.matrix.android.internal.session.SessionScope
|
||||||
|
import im.vector.matrix.android.internal.session.room.call.DefaultRoomCallService
|
||||||
import im.vector.matrix.android.internal.session.room.draft.DefaultDraftService
|
import im.vector.matrix.android.internal.session.room.draft.DefaultDraftService
|
||||||
import im.vector.matrix.android.internal.session.room.membership.DefaultMembershipService
|
import im.vector.matrix.android.internal.session.room.membership.DefaultMembershipService
|
||||||
import im.vector.matrix.android.internal.session.room.notification.DefaultRoomPushRuleService
|
import im.vector.matrix.android.internal.session.room.notification.DefaultRoomPushRuleService
|
||||||
|
@ -49,6 +50,7 @@ internal class DefaultRoomFactory @Inject constructor(private val cryptoService:
|
||||||
private val stateServiceFactory: DefaultStateService.Factory,
|
private val stateServiceFactory: DefaultStateService.Factory,
|
||||||
private val uploadsServiceFactory: DefaultUploadsService.Factory,
|
private val uploadsServiceFactory: DefaultUploadsService.Factory,
|
||||||
private val reportingServiceFactory: DefaultReportingService.Factory,
|
private val reportingServiceFactory: DefaultReportingService.Factory,
|
||||||
|
private val roomCallServiceFactory: DefaultRoomCallService.Factory,
|
||||||
private val readServiceFactory: DefaultReadService.Factory,
|
private val readServiceFactory: DefaultReadService.Factory,
|
||||||
private val typingServiceFactory: DefaultTypingService.Factory,
|
private val typingServiceFactory: DefaultTypingService.Factory,
|
||||||
private val tagsServiceFactory: DefaultTagsService.Factory,
|
private val tagsServiceFactory: DefaultTagsService.Factory,
|
||||||
|
@ -69,6 +71,7 @@ internal class DefaultRoomFactory @Inject constructor(private val cryptoService:
|
||||||
stateService = stateServiceFactory.create(roomId),
|
stateService = stateServiceFactory.create(roomId),
|
||||||
uploadsService = uploadsServiceFactory.create(roomId),
|
uploadsService = uploadsServiceFactory.create(roomId),
|
||||||
reportingService = reportingServiceFactory.create(roomId),
|
reportingService = reportingServiceFactory.create(roomId),
|
||||||
|
roomCallService = roomCallServiceFactory.create(roomId),
|
||||||
readService = readServiceFactory.create(roomId),
|
readService = readServiceFactory.create(roomId),
|
||||||
typingService = typingServiceFactory.create(roomId),
|
typingService = typingServiceFactory.create(roomId),
|
||||||
tagsService = tagsServiceFactory.create(roomId),
|
tagsService = tagsServiceFactory.create(roomId),
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
/*
|
||||||
|
* 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.matrix.android.internal.session.room.call
|
||||||
|
|
||||||
|
import com.squareup.inject.assisted.Assisted
|
||||||
|
import com.squareup.inject.assisted.AssistedInject
|
||||||
|
import im.vector.matrix.android.api.extensions.orFalse
|
||||||
|
import im.vector.matrix.android.api.session.room.call.RoomCallService
|
||||||
|
import im.vector.matrix.android.internal.session.room.RoomGetter
|
||||||
|
|
||||||
|
internal class DefaultRoomCallService @AssistedInject constructor(
|
||||||
|
@Assisted private val roomId: String,
|
||||||
|
private val roomGetter: RoomGetter
|
||||||
|
) : RoomCallService {
|
||||||
|
|
||||||
|
@AssistedInject.Factory
|
||||||
|
interface Factory {
|
||||||
|
fun create(roomId: String): RoomCallService
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun canStartCall(): Boolean {
|
||||||
|
return roomGetter.getRoom(roomId)?.roomSummary()?.canStartCall.orFalse()
|
||||||
|
}
|
||||||
|
}
|
|
@ -52,7 +52,7 @@ internal class DefaultRoomPushRuleService @AssistedInject constructor(@Assisted
|
||||||
override fun setRoomNotificationState(roomNotificationState: RoomNotificationState, matrixCallback: MatrixCallback<Unit>): Cancelable {
|
override fun setRoomNotificationState(roomNotificationState: RoomNotificationState, matrixCallback: MatrixCallback<Unit>): Cancelable {
|
||||||
return setRoomNotificationStateTask
|
return setRoomNotificationStateTask
|
||||||
.configureWith(SetRoomNotificationStateTask.Params(roomId, roomNotificationState)) {
|
.configureWith(SetRoomNotificationStateTask.Params(roomId, roomNotificationState)) {
|
||||||
this.callback = callback
|
this.callback = matrixCallback
|
||||||
}
|
}
|
||||||
.executeBy(taskExecutor)
|
.executeBy(taskExecutor)
|
||||||
}
|
}
|
||||||
|
|
|
@ -58,7 +58,8 @@ internal class DefaultSendService @AssistedInject constructor(
|
||||||
private val localEchoEventFactory: LocalEchoEventFactory,
|
private val localEchoEventFactory: LocalEchoEventFactory,
|
||||||
private val cryptoService: CryptoService,
|
private val cryptoService: CryptoService,
|
||||||
private val taskExecutor: TaskExecutor,
|
private val taskExecutor: TaskExecutor,
|
||||||
private val localEchoRepository: LocalEchoRepository
|
private val localEchoRepository: LocalEchoRepository,
|
||||||
|
private val roomEventSender: RoomEventSender
|
||||||
) : SendService {
|
) : SendService {
|
||||||
|
|
||||||
@AssistedInject.Factory
|
@AssistedInject.Factory
|
||||||
|
@ -111,20 +112,6 @@ internal class DefaultSendService @AssistedInject constructor(
|
||||||
.let { sendEvent(it) }
|
.let { sendEvent(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun sendEvent(event: Event): Cancelable {
|
|
||||||
// Encrypted room handling
|
|
||||||
return if (cryptoService.isRoomEncrypted(roomId)) {
|
|
||||||
Timber.v("Send event in encrypted room")
|
|
||||||
val encryptWork = createEncryptEventWork(event, true)
|
|
||||||
// Note that event will be replaced by the result of the previous work
|
|
||||||
val sendWork = createSendEventWork(event, false)
|
|
||||||
timelineSendEventWorkCommon.postSequentialWorks(roomId, encryptWork, sendWork)
|
|
||||||
} else {
|
|
||||||
val sendWork = createSendEventWork(event, true)
|
|
||||||
timelineSendEventWorkCommon.postWork(roomId, sendWork)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun sendMedias(attachments: List<ContentAttachmentData>,
|
override fun sendMedias(attachments: List<ContentAttachmentData>,
|
||||||
compressBeforeSending: Boolean,
|
compressBeforeSending: Boolean,
|
||||||
roomIds: Set<String>): Cancelable {
|
roomIds: Set<String>): Cancelable {
|
||||||
|
@ -269,6 +256,10 @@ internal class DefaultSendService @AssistedInject constructor(
|
||||||
return cancelableBag
|
return cancelableBag
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun sendEvent(event: Event): Cancelable {
|
||||||
|
return roomEventSender.sendEvent(event)
|
||||||
|
}
|
||||||
|
|
||||||
private fun createLocalEcho(event: Event) {
|
private fun createLocalEcho(event: Event) {
|
||||||
localEchoEventFactory.createLocalEcho(event)
|
localEchoEventFactory.createLocalEcho(event)
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,7 +37,7 @@ internal class MarkdownParser @Inject constructor(
|
||||||
fun parse(text: String): TextContent {
|
fun parse(text: String): TextContent {
|
||||||
// If no special char are detected, just return plain text
|
// If no special char are detected, just return plain text
|
||||||
if (text.contains(mdSpecialChars).not()) {
|
if (text.contains(mdSpecialChars).not()) {
|
||||||
return TextContent(text.toString())
|
return TextContent(text)
|
||||||
}
|
}
|
||||||
|
|
||||||
val document = parser.parse(text)
|
val document = parser.parse(text)
|
||||||
|
@ -56,7 +56,7 @@ internal class MarkdownParser @Inject constructor(
|
||||||
val plainText = textContentRenderer.render(document)
|
val plainText = textContentRenderer.render(document)
|
||||||
TextContent(plainText, cleanHtmlText.postTreatment())
|
TextContent(plainText, cleanHtmlText.postTreatment())
|
||||||
} else {
|
} else {
|
||||||
TextContent(text.toString())
|
TextContent(text)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,72 @@
|
||||||
|
/*
|
||||||
|
* 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.matrix.android.internal.session.room.send
|
||||||
|
|
||||||
|
import androidx.work.BackoffPolicy
|
||||||
|
import androidx.work.OneTimeWorkRequest
|
||||||
|
import im.vector.matrix.android.api.session.crypto.CryptoService
|
||||||
|
import im.vector.matrix.android.api.session.events.model.Event
|
||||||
|
import im.vector.matrix.android.api.util.Cancelable
|
||||||
|
import im.vector.matrix.android.internal.di.SessionId
|
||||||
|
import im.vector.matrix.android.internal.di.WorkManagerProvider
|
||||||
|
import im.vector.matrix.android.internal.session.room.timeline.TimelineSendEventWorkCommon
|
||||||
|
import im.vector.matrix.android.internal.worker.WorkerParamsFactory
|
||||||
|
import im.vector.matrix.android.internal.worker.startChain
|
||||||
|
import timber.log.Timber
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
internal class RoomEventSender @Inject constructor(
|
||||||
|
private val workManagerProvider: WorkManagerProvider,
|
||||||
|
private val timelineSendEventWorkCommon: TimelineSendEventWorkCommon,
|
||||||
|
@SessionId private val sessionId: String,
|
||||||
|
private val cryptoService: CryptoService
|
||||||
|
) {
|
||||||
|
fun sendEvent(event: Event): Cancelable {
|
||||||
|
// Encrypted room handling
|
||||||
|
return if (cryptoService.isRoomEncrypted(event.roomId ?: "")) {
|
||||||
|
Timber.v("Send event in encrypted room")
|
||||||
|
val encryptWork = createEncryptEventWork(event, true)
|
||||||
|
// Note that event will be replaced by the result of the previous work
|
||||||
|
val sendWork = createSendEventWork(event, false)
|
||||||
|
timelineSendEventWorkCommon.postSequentialWorks(event.roomId ?: "", encryptWork, sendWork)
|
||||||
|
} else {
|
||||||
|
val sendWork = createSendEventWork(event, true)
|
||||||
|
timelineSendEventWorkCommon.postWork(event.roomId ?: "", sendWork)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createEncryptEventWork(event: Event, startChain: Boolean): OneTimeWorkRequest {
|
||||||
|
// Same parameter
|
||||||
|
val params = EncryptEventWorker.Params(sessionId, event)
|
||||||
|
val sendWorkData = WorkerParamsFactory.toData(params)
|
||||||
|
|
||||||
|
return workManagerProvider.matrixOneTimeWorkRequestBuilder<EncryptEventWorker>()
|
||||||
|
.setConstraints(WorkManagerProvider.workConstraints)
|
||||||
|
.setInputData(sendWorkData)
|
||||||
|
.startChain(startChain)
|
||||||
|
.setBackoffCriteria(BackoffPolicy.LINEAR, WorkManagerProvider.BACKOFF_DELAY, TimeUnit.MILLISECONDS)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createSendEventWork(event: Event, startChain: Boolean): OneTimeWorkRequest {
|
||||||
|
val sendContentWorkerParams = SendEventWorker.Params(sessionId, event)
|
||||||
|
val sendWorkData = WorkerParamsFactory.toData(sendContentWorkerParams)
|
||||||
|
|
||||||
|
return timelineSendEventWorkCommon.createWork<SendEventWorker>(sendWorkData, startChain)
|
||||||
|
}
|
||||||
|
}
|
|
@ -78,7 +78,7 @@ internal class DefaultWidgetPostAPIMediator @Inject constructor(private val mosh
|
||||||
|
|
||||||
private fun onWidgetMessage(eventData: JsonDict) {
|
private fun onWidgetMessage(eventData: JsonDict) {
|
||||||
try {
|
try {
|
||||||
if (handler?.handleWidgetRequest(eventData) == false) {
|
if (handler?.handleWidgetRequest(this, eventData) == false) {
|
||||||
sendError("", eventData)
|
sendError("", eventData)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
|
|
@ -26,10 +26,11 @@ import im.vector.matrix.android.api.session.widgets.WidgetURLFormatter
|
||||||
import im.vector.matrix.android.api.session.widgets.model.Widget
|
import im.vector.matrix.android.api.session.widgets.model.Widget
|
||||||
import im.vector.matrix.android.api.util.Cancelable
|
import im.vector.matrix.android.api.util.Cancelable
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
import javax.inject.Provider
|
||||||
|
|
||||||
internal class DefaultWidgetService @Inject constructor(private val widgetManager: WidgetManager,
|
internal class DefaultWidgetService @Inject constructor(private val widgetManager: WidgetManager,
|
||||||
private val widgetURLFormatter: WidgetURLFormatter,
|
private val widgetURLFormatter: WidgetURLFormatter,
|
||||||
private val widgetPostAPIMediator: WidgetPostAPIMediator)
|
private val widgetPostAPIMediator: Provider<WidgetPostAPIMediator>)
|
||||||
: WidgetService {
|
: WidgetService {
|
||||||
|
|
||||||
override fun getWidgetURLFormatter(): WidgetURLFormatter {
|
override fun getWidgetURLFormatter(): WidgetURLFormatter {
|
||||||
|
@ -37,7 +38,7 @@ internal class DefaultWidgetService @Inject constructor(private val widgetManage
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getWidgetPostAPIMediator(): WidgetPostAPIMediator {
|
override fun getWidgetPostAPIMediator(): WidgetPostAPIMediator {
|
||||||
return widgetPostAPIMediator
|
return widgetPostAPIMediator.get()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getRoomWidgets(
|
override fun getRoomWidgets(
|
||||||
|
|
|
@ -67,8 +67,7 @@ internal class DefaultGetScalarTokenTask @Inject constructor(private val widgets
|
||||||
throw IllegalStateException("Scalar token is null")
|
throw IllegalStateException("Scalar token is null")
|
||||||
}
|
}
|
||||||
scalarTokenStore.setToken(serverUrl, registerWidgetResponse.scalarToken)
|
scalarTokenStore.setToken(serverUrl, registerWidgetResponse.scalarToken)
|
||||||
widgetsAPI.validateToken(registerWidgetResponse.scalarToken, WIDGET_API_VERSION)
|
return validateToken(widgetsAPI, serverUrl, registerWidgetResponse.scalarToken)
|
||||||
return registerWidgetResponse.scalarToken
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun validateToken(widgetsAPI: WidgetsAPI, serverUrl: String, scalarToken: String): String {
|
private suspend fun validateToken(widgetsAPI: WidgetsAPI, serverUrl: String, scalarToken: String): String {
|
||||||
|
|
|
@ -27,6 +27,7 @@ import android.security.keystore.KeyGenParameterSpec
|
||||||
import android.security.keystore.KeyProperties
|
import android.security.keystore.KeyProperties
|
||||||
import android.util.Base64
|
import android.util.Base64
|
||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
|
import androidx.core.content.edit
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
|
@ -152,9 +153,9 @@ object CompatUtil {
|
||||||
.build())
|
.build())
|
||||||
key = generator.generateKey()
|
key = generator.generateKey()
|
||||||
|
|
||||||
sharedPreferences.edit()
|
sharedPreferences.edit {
|
||||||
.putInt(SHARED_KEY_ANDROID_VERSION_WHEN_KEY_HAS_BEEN_GENERATED, Build.VERSION.SDK_INT)
|
putInt(SHARED_KEY_ANDROID_VERSION_WHEN_KEY_HAS_BEEN_GENERATED, Build.VERSION.SDK_INT)
|
||||||
.apply()
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -188,10 +189,10 @@ object CompatUtil {
|
||||||
cipher.init(Cipher.WRAP_MODE, keyPair.public)
|
cipher.init(Cipher.WRAP_MODE, keyPair.public)
|
||||||
val wrappedAesKey = cipher.wrap(key)
|
val wrappedAesKey = cipher.wrap(key)
|
||||||
|
|
||||||
sharedPreferences.edit()
|
sharedPreferences.edit {
|
||||||
.putString(AES_WRAPPED_PROTECTION_KEY_SHARED_PREFERENCE, Base64.encodeToString(wrappedAesKey, 0))
|
putString(AES_WRAPPED_PROTECTION_KEY_SHARED_PREFERENCE, Base64.encodeToString(wrappedAesKey, 0))
|
||||||
.putInt(SHARED_KEY_ANDROID_VERSION_WHEN_KEY_HAS_BEEN_GENERATED, Build.VERSION.SDK_INT)
|
putInt(SHARED_KEY_ANDROID_VERSION_WHEN_KEY_HAS_BEEN_GENERATED, Build.VERSION.SDK_INT)
|
||||||
.apply()
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -33,8 +33,8 @@
|
||||||
<string name="notice_display_name_set_by_you">You set your display name to %1$s</string>
|
<string name="notice_display_name_set_by_you">You set your display name to %1$s</string>
|
||||||
<string name="notice_display_name_changed_from">%1$s changed their display name from %2$s to %3$s</string>
|
<string name="notice_display_name_changed_from">%1$s changed their display name from %2$s to %3$s</string>
|
||||||
<string name="notice_display_name_changed_from_by_you">You changed your display name from %1$s to %2$s</string>
|
<string name="notice_display_name_changed_from_by_you">You changed your display name from %1$s to %2$s</string>
|
||||||
<string name="notice_display_name_removed">%1$s removed their display name (%2$s)</string>
|
<string name="notice_display_name_removed">%1$s removed their display name (it was %2$s)</string>
|
||||||
<string name="notice_display_name_removed_by_you">You removed your display name (%1$s)</string>
|
<string name="notice_display_name_removed_by_you">You removed your display name (it was %1$s)</string>
|
||||||
<string name="notice_room_topic_changed">%1$s changed the topic to: %2$s</string>
|
<string name="notice_room_topic_changed">%1$s changed the topic to: %2$s</string>
|
||||||
<string name="notice_room_topic_changed_by_you">You changed the topic to: %1$s</string>
|
<string name="notice_room_topic_changed_by_you">You changed the topic to: %1$s</string>
|
||||||
<string name="notice_room_name_changed">%1$s changed the room name to: %2$s</string>
|
<string name="notice_room_name_changed">%1$s changed the room name to: %2$s</string>
|
||||||
|
@ -43,6 +43,8 @@
|
||||||
<string name="notice_placed_video_call_by_you">You placed a video call.</string>
|
<string name="notice_placed_video_call_by_you">You placed a video call.</string>
|
||||||
<string name="notice_placed_voice_call">%s placed a voice call.</string>
|
<string name="notice_placed_voice_call">%s placed a voice call.</string>
|
||||||
<string name="notice_placed_voice_call_by_you">You placed a voice call.</string>
|
<string name="notice_placed_voice_call_by_you">You placed a voice call.</string>
|
||||||
|
<string name="notice_call_candidates">%s sent data to setup the call.</string>
|
||||||
|
<string name="notice_call_candidates_by_you">You sent data to setup the call.</string>
|
||||||
<string name="notice_answered_call">%s answered the call.</string>
|
<string name="notice_answered_call">%s answered the call.</string>
|
||||||
<string name="notice_answered_call_by_you">You answered the call.</string>
|
<string name="notice_answered_call_by_you">You answered the call.</string>
|
||||||
<string name="notice_ended_call">%s ended the call.</string>
|
<string name="notice_ended_call">%s ended the call.</string>
|
||||||
|
@ -362,4 +364,8 @@
|
||||||
|
|
||||||
<string name="key_verification_request_fallback_message">%s is requesting to verify your key, but your client does not support in-chat key verification. You will need to use legacy key verification to verify keys.</string>
|
<string name="key_verification_request_fallback_message">%s is requesting to verify your key, but your client does not support in-chat key verification. You will need to use legacy key verification to verify keys.</string>
|
||||||
|
|
||||||
|
<string name="call_notification_answer">Accept</string>
|
||||||
|
<string name="call_notification_reject">Decline</string>
|
||||||
|
<string name="call_notification_hangup">Hang Up</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -62,9 +62,9 @@ class ContactPicker(override val requestCode: Int) : Picker<MultiPickerContactTy
|
||||||
|
|
||||||
val contactId = cursor.getInt(idColumn)
|
val contactId = cursor.getInt(idColumn)
|
||||||
var name = cursor.getString(nameColumn)
|
var name = cursor.getString(nameColumn)
|
||||||
var photoUri = cursor.getString(photoUriColumn)
|
val photoUri = cursor.getString(photoUriColumn)
|
||||||
var phoneNumberList = mutableListOf<String>()
|
val phoneNumberList = mutableListOf<String>()
|
||||||
var emailList = mutableListOf<String>()
|
val emailList = mutableListOf<String>()
|
||||||
|
|
||||||
getRawContactId(context.contentResolver, contactId)?.let { rawContactId ->
|
getRawContactId(context.contentResolver, contactId)?.let { rawContactId ->
|
||||||
val selection = ContactsContract.Data.RAW_CONTACT_ID + " = ?"
|
val selection = ContactsContract.Data.RAW_CONTACT_ID + " = ?"
|
||||||
|
|
BIN
resources/img/f-droid-badge.png
Normal file
BIN
resources/img/f-droid-badge.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 20 KiB |
BIN
resources/img/google-play-badge.png
Normal file
BIN
resources/img/google-play-badge.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 17 KiB |
|
@ -17,8 +17,9 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
echo "Configure RiotX Template..."
|
echo "Configure RiotX Template..."
|
||||||
|
if [ -z ${ANDROID_STUDIO+x} ]; then ANDROID_STUDIO="/Applications/Android Studio.app/Contents"; fi
|
||||||
{
|
{
|
||||||
ln -s $(pwd)/RiotXFeature /Applications/Android\ Studio.app/Contents/plugins/android/lib/templates/other
|
ln -s $(pwd)/RiotXFeature "${ANDROID_STUDIO%/}/plugins/android/lib/templates/other"
|
||||||
} && {
|
} && {
|
||||||
echo "Please restart Android Studio."
|
echo "Please restart Android Studio."
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@ androidExtensions {
|
||||||
}
|
}
|
||||||
|
|
||||||
ext.versionMajor = 0
|
ext.versionMajor = 0
|
||||||
ext.versionMinor = 22
|
ext.versionMinor = 23
|
||||||
ext.versionPatch = 0
|
ext.versionPatch = 0
|
||||||
|
|
||||||
static def getGitTimestamp() {
|
static def getGitTimestamp() {
|
||||||
|
@ -107,9 +107,8 @@ android {
|
||||||
compileSdkVersion 29
|
compileSdkVersion 29
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "im.vector.riotx"
|
applicationId "im.vector.riotx"
|
||||||
// Set to API 19 because motionLayout is min API 18.
|
// Set to API 21: see #405
|
||||||
// In the future we may consider using an alternative of MotionLayout to support API 16. But for security reason, maybe not.
|
minSdkVersion 21
|
||||||
minSdkVersion 19
|
|
||||||
targetSdkVersion 29
|
targetSdkVersion 29
|
||||||
multiDexEnabled true
|
multiDexEnabled true
|
||||||
|
|
||||||
|
@ -192,8 +191,14 @@ android {
|
||||||
resValue "bool", "debug_mode", "false"
|
resValue "bool", "debug_mode", "false"
|
||||||
buildConfigField "boolean", "LOW_PRIVACY_LOG_ENABLE", "false"
|
buildConfigField "boolean", "LOW_PRIVACY_LOG_ENABLE", "false"
|
||||||
|
|
||||||
minifyEnabled false
|
postprocessing {
|
||||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
removeUnusedCode true
|
||||||
|
removeUnusedResources true
|
||||||
|
// We do not activate obfuscation as it makes it hard then to read crash reports, and it's a bit useless on an open source project :)
|
||||||
|
obfuscate false
|
||||||
|
optimizeCode true
|
||||||
|
proguardFiles 'proguard-rules.pro'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -385,6 +390,9 @@ dependencies {
|
||||||
|
|
||||||
implementation 'com.github.BillCarsonFr:JsonViewer:0.5'
|
implementation 'com.github.BillCarsonFr:JsonViewer:0.5'
|
||||||
|
|
||||||
|
// TODO meant for development purposes only
|
||||||
|
implementation 'org.webrtc:google-webrtc:1.0.+'
|
||||||
|
|
||||||
// QR-code
|
// QR-code
|
||||||
// Stick to 3.3.3 because of https://github.com/zxing/zxing/issues/1170
|
// Stick to 3.3.3 because of https://github.com/zxing/zxing/issues/1170
|
||||||
implementation 'com.google.zxing:core:3.3.3'
|
implementation 'com.google.zxing:core:3.3.3'
|
||||||
|
|
2
vector/proguard-rules.pro
vendored
2
vector/proguard-rules.pro
vendored
|
@ -19,3 +19,5 @@
|
||||||
# If you keep the line number information, uncomment this to
|
# If you keep the line number information, uncomment this to
|
||||||
# hide the original source file name.
|
# hide the original source file name.
|
||||||
#-renamesourcefileattribute SourceFile
|
#-renamesourcefileattribute SourceFile
|
||||||
|
|
||||||
|
-keep class im.vector.riotx.features.** { *; }
|
|
@ -21,6 +21,7 @@ import android.app.Activity
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
|
import androidx.core.content.edit
|
||||||
import com.google.android.gms.common.ConnectionResult
|
import com.google.android.gms.common.ConnectionResult
|
||||||
import com.google.android.gms.common.GoogleApiAvailability
|
import com.google.android.gms.common.GoogleApiAvailability
|
||||||
import com.google.firebase.iid.FirebaseInstanceId
|
import com.google.firebase.iid.FirebaseInstanceId
|
||||||
|
@ -57,10 +58,9 @@ object FcmHelper {
|
||||||
*/
|
*/
|
||||||
fun storeFcmToken(context: Context,
|
fun storeFcmToken(context: Context,
|
||||||
token: String?) {
|
token: String?) {
|
||||||
PreferenceManager.getDefaultSharedPreferences(context)
|
PreferenceManager.getDefaultSharedPreferences(context).edit {
|
||||||
.edit()
|
putString(PREFS_KEY_FCM_TOKEN, token)
|
||||||
.putString(PREFS_KEY_FCM_TOKEN, token)
|
}
|
||||||
.apply()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -3,13 +3,30 @@
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
package="im.vector.riotx">
|
package="im.vector.riotx">
|
||||||
|
|
||||||
|
<!-- Needed for VOIP call to detect and switch to headset-->
|
||||||
|
<uses-permission android:name="android.permission.BLUETOOTH" />
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<uses-permission android:name="android.permission.READ_CONTACTS" />
|
<uses-permission android:name="android.permission.READ_CONTACTS" />
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
<uses-permission android:name="android.permission.VIBRATE" />
|
<uses-permission android:name="android.permission.VIBRATE" />
|
||||||
<uses-permission android:name="android.permission.CAMERA" />
|
<uses-permission android:name="android.permission.CAMERA" />
|
||||||
|
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||||
|
|
||||||
|
<!-- Call feature -->
|
||||||
|
<uses-permission android:name="android.permission.MANAGE_OWN_CALLS" />
|
||||||
|
<uses-permission android:name="android.permission.READ_CALL_LOG" />
|
||||||
|
<!-- Needed for voice call to toggle speaker on or off -->
|
||||||
|
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
|
||||||
|
<!-- READ_PHONE_STATE is needed only if your calling app reads numbers from the `PHONE_STATE`
|
||||||
|
intent action. -->
|
||||||
|
|
||||||
|
<!-- Needed to show incoming calls activity in lock screen-->
|
||||||
|
<uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" />
|
||||||
|
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||||
|
<!-- Needed for incoming calls -->
|
||||||
|
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
|
||||||
|
|
||||||
<!-- Adding CAMERA permission prevents Chromebooks to see the application on the PlayStore -->
|
<!-- Adding CAMERA permission prevents Chromebooks to see the application on the PlayStore -->
|
||||||
<!-- Tell that the Camera is not mandatory to install the application -->
|
<!-- Tell that the Camera is not mandatory to install the application -->
|
||||||
<uses-feature
|
<uses-feature
|
||||||
|
@ -172,6 +189,7 @@
|
||||||
<activity
|
<activity
|
||||||
android:name=".features.attachments.preview.AttachmentsPreviewActivity"
|
android:name=".features.attachments.preview.AttachmentsPreviewActivity"
|
||||||
android:theme="@style/AppTheme.AttachmentsPreview" />
|
android:theme="@style/AppTheme.AttachmentsPreview" />
|
||||||
|
<activity android:name=".features.call.VectorCallActivity" />
|
||||||
|
|
||||||
<activity android:name=".features.terms.ReviewTermsActivity" />
|
<activity android:name=".features.terms.ReviewTermsActivity" />
|
||||||
<activity android:name=".features.widgets.WidgetActivity" />
|
<activity android:name=".features.widgets.WidgetActivity" />
|
||||||
|
@ -180,20 +198,47 @@
|
||||||
|
|
||||||
<service
|
<service
|
||||||
android:name=".core.services.CallService"
|
android:name=".core.services.CallService"
|
||||||
android:exported="false" />
|
android:exported="false" >
|
||||||
|
<!-- in order to get headset button events -->
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MEDIA_BUTTON" />
|
||||||
|
</intent-filter>
|
||||||
|
</service>
|
||||||
|
|
||||||
<service
|
<service
|
||||||
android:name=".core.services.VectorSyncService"
|
android:name=".core.services.VectorSyncService"
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
|
|
||||||
|
<service
|
||||||
|
android:name=".features.call.telecom.VectorConnectionService"
|
||||||
|
android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.telecom.ConnectionService" />
|
||||||
|
</intent-filter>
|
||||||
|
</service>
|
||||||
|
|
||||||
<!-- Receivers -->
|
<!-- Receivers -->
|
||||||
|
|
||||||
|
<receiver
|
||||||
|
android:name=".features.call.service.CallHeadsUpActionReceiver"
|
||||||
|
android:exported="false" />
|
||||||
|
|
||||||
<!-- Exported false, should only be accessible from this app!! -->
|
<!-- Exported false, should only be accessible from this app!! -->
|
||||||
<receiver
|
<receiver
|
||||||
android:name=".features.notifications.NotificationBroadcastReceiver"
|
android:name=".features.notifications.NotificationBroadcastReceiver"
|
||||||
android:enabled="true"
|
android:enabled="true"
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
|
|
||||||
|
<!--
|
||||||
|
A media button receiver receives and helps translate hardware media playback buttons,
|
||||||
|
such as those found on wired and wireless headsets, into the appropriate callbacks in your app.
|
||||||
|
-->
|
||||||
|
<receiver android:name="androidx.media.session.MediaButtonReceiver" >
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MEDIA_BUTTON" />
|
||||||
|
</intent-filter>
|
||||||
|
</receiver>
|
||||||
|
|
||||||
<!-- Providers -->
|
<!-- Providers -->
|
||||||
|
|
||||||
<provider
|
<provider
|
||||||
|
|
|
@ -43,6 +43,7 @@ import im.vector.riotx.core.di.HasVectorInjector
|
||||||
import im.vector.riotx.core.di.VectorComponent
|
import im.vector.riotx.core.di.VectorComponent
|
||||||
import im.vector.riotx.core.extensions.configureAndStart
|
import im.vector.riotx.core.extensions.configureAndStart
|
||||||
import im.vector.riotx.core.rx.RxConfig
|
import im.vector.riotx.core.rx.RxConfig
|
||||||
|
import im.vector.riotx.features.call.WebRtcPeerConnectionManager
|
||||||
import im.vector.riotx.features.configuration.VectorConfiguration
|
import im.vector.riotx.features.configuration.VectorConfiguration
|
||||||
import im.vector.riotx.features.lifecycle.VectorActivityLifecycleCallbacks
|
import im.vector.riotx.features.lifecycle.VectorActivityLifecycleCallbacks
|
||||||
import im.vector.riotx.features.notifications.NotificationDrawerManager
|
import im.vector.riotx.features.notifications.NotificationDrawerManager
|
||||||
|
@ -80,6 +81,8 @@ class VectorApplication : Application(), HasVectorInjector, MatrixConfiguration.
|
||||||
@Inject lateinit var appStateHandler: AppStateHandler
|
@Inject lateinit var appStateHandler: AppStateHandler
|
||||||
@Inject lateinit var rxConfig: RxConfig
|
@Inject lateinit var rxConfig: RxConfig
|
||||||
@Inject lateinit var popupAlertManager: PopupAlertManager
|
@Inject lateinit var popupAlertManager: PopupAlertManager
|
||||||
|
@Inject lateinit var webRtcPeerConnectionManager: WebRtcPeerConnectionManager
|
||||||
|
|
||||||
lateinit var vectorComponent: VectorComponent
|
lateinit var vectorComponent: VectorComponent
|
||||||
private var fontThreadHandler: Handler? = null
|
private var fontThreadHandler: Handler? = null
|
||||||
|
|
||||||
|
@ -122,6 +125,7 @@ class VectorApplication : Application(), HasVectorInjector, MatrixConfiguration.
|
||||||
val lastAuthenticatedSession = authenticationService.getLastAuthenticatedSession()!!
|
val lastAuthenticatedSession = authenticationService.getLastAuthenticatedSession()!!
|
||||||
activeSessionHolder.setActiveSession(lastAuthenticatedSession)
|
activeSessionHolder.setActiveSession(lastAuthenticatedSession)
|
||||||
lastAuthenticatedSession.configureAndStart(applicationContext, pushRuleTriggerListener, sessionListener)
|
lastAuthenticatedSession.configureAndStart(applicationContext, pushRuleTriggerListener, sessionListener)
|
||||||
|
lastAuthenticatedSession.callSignalingService().addCallListener(webRtcPeerConnectionManager)
|
||||||
}
|
}
|
||||||
ProcessLifecycleOwner.get().lifecycle.addObserver(object : LifecycleObserver {
|
ProcessLifecycleOwner.get().lifecycle.addObserver(object : LifecycleObserver {
|
||||||
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
|
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
|
||||||
|
|
|
@ -24,6 +24,8 @@ import dagger.Component
|
||||||
import im.vector.riotx.core.error.ErrorFormatter
|
import im.vector.riotx.core.error.ErrorFormatter
|
||||||
import im.vector.riotx.core.preference.UserAvatarPreference
|
import im.vector.riotx.core.preference.UserAvatarPreference
|
||||||
import im.vector.riotx.features.MainActivity
|
import im.vector.riotx.features.MainActivity
|
||||||
|
import im.vector.riotx.features.call.CallControlsBottomSheet
|
||||||
|
import im.vector.riotx.features.call.VectorCallActivity
|
||||||
import im.vector.riotx.features.createdirect.CreateDirectRoomActivity
|
import im.vector.riotx.features.createdirect.CreateDirectRoomActivity
|
||||||
import im.vector.riotx.features.crypto.keysbackup.settings.KeysBackupManageActivity
|
import im.vector.riotx.features.crypto.keysbackup.settings.KeysBackupManageActivity
|
||||||
import im.vector.riotx.features.crypto.quads.SharedSecureStorageActivity
|
import im.vector.riotx.features.crypto.quads.SharedSecureStorageActivity
|
||||||
|
@ -130,6 +132,7 @@ interface ScreenComponent {
|
||||||
fun inject(activity: InviteUsersToRoomActivity)
|
fun inject(activity: InviteUsersToRoomActivity)
|
||||||
fun inject(activity: ReviewTermsActivity)
|
fun inject(activity: ReviewTermsActivity)
|
||||||
fun inject(activity: WidgetActivity)
|
fun inject(activity: WidgetActivity)
|
||||||
|
fun inject(activity: VectorCallActivity)
|
||||||
|
|
||||||
/* ==========================================================================================
|
/* ==========================================================================================
|
||||||
* BottomSheets
|
* BottomSheets
|
||||||
|
@ -146,6 +149,7 @@ interface ScreenComponent {
|
||||||
fun inject(bottomSheet: BootstrapBottomSheet)
|
fun inject(bottomSheet: BootstrapBottomSheet)
|
||||||
fun inject(bottomSheet: RoomWidgetPermissionBottomSheet)
|
fun inject(bottomSheet: RoomWidgetPermissionBottomSheet)
|
||||||
fun inject(bottomSheet: RoomWidgetsBottomSheet)
|
fun inject(bottomSheet: RoomWidgetsBottomSheet)
|
||||||
|
fun inject(bottomSheet: CallControlsBottomSheet)
|
||||||
|
|
||||||
/* ==========================================================================================
|
/* ==========================================================================================
|
||||||
* Others
|
* Others
|
||||||
|
|
|
@ -31,6 +31,7 @@ import im.vector.riotx.core.error.ErrorFormatter
|
||||||
import im.vector.riotx.core.pushers.PushersManager
|
import im.vector.riotx.core.pushers.PushersManager
|
||||||
import im.vector.riotx.core.utils.AssetReader
|
import im.vector.riotx.core.utils.AssetReader
|
||||||
import im.vector.riotx.core.utils.DimensionConverter
|
import im.vector.riotx.core.utils.DimensionConverter
|
||||||
|
import im.vector.riotx.features.call.WebRtcPeerConnectionManager
|
||||||
import im.vector.riotx.features.configuration.VectorConfiguration
|
import im.vector.riotx.features.configuration.VectorConfiguration
|
||||||
import im.vector.riotx.features.crypto.keysrequest.KeyRequestHandler
|
import im.vector.riotx.features.crypto.keysrequest.KeyRequestHandler
|
||||||
import im.vector.riotx.features.crypto.verification.IncomingVerificationRequestHandler
|
import im.vector.riotx.features.crypto.verification.IncomingVerificationRequestHandler
|
||||||
|
@ -134,6 +135,8 @@ interface VectorComponent {
|
||||||
|
|
||||||
fun reAuthHelper(): ReAuthHelper
|
fun reAuthHelper(): ReAuthHelper
|
||||||
|
|
||||||
|
fun webRtcPeerConnectionManager(): WebRtcPeerConnectionManager
|
||||||
|
|
||||||
@Component.Factory
|
@Component.Factory
|
||||||
interface Factory {
|
interface Factory {
|
||||||
fun create(@BindsInstance context: Context): VectorComponent
|
fun create(@BindsInstance context: Context): VectorComponent
|
||||||
|
|
|
@ -22,6 +22,7 @@ import dagger.Binds
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.multibindings.IntoMap
|
import dagger.multibindings.IntoMap
|
||||||
import im.vector.riotx.core.platform.ConfigurationViewModel
|
import im.vector.riotx.core.platform.ConfigurationViewModel
|
||||||
|
import im.vector.riotx.features.call.SharedActiveCallViewModel
|
||||||
import im.vector.riotx.features.crypto.keysbackup.restore.KeysBackupRestoreFromKeyViewModel
|
import im.vector.riotx.features.crypto.keysbackup.restore.KeysBackupRestoreFromKeyViewModel
|
||||||
import im.vector.riotx.features.crypto.keysbackup.restore.KeysBackupRestoreFromPassphraseViewModel
|
import im.vector.riotx.features.crypto.keysbackup.restore.KeysBackupRestoreFromPassphraseViewModel
|
||||||
import im.vector.riotx.features.crypto.keysbackup.restore.KeysBackupRestoreSharedViewModel
|
import im.vector.riotx.features.crypto.keysbackup.restore.KeysBackupRestoreSharedViewModel
|
||||||
|
@ -85,6 +86,11 @@ interface ViewModelModule {
|
||||||
@ViewModelKey(ConfigurationViewModel::class)
|
@ViewModelKey(ConfigurationViewModel::class)
|
||||||
fun bindConfigurationViewModel(viewModel: ConfigurationViewModel): ViewModel
|
fun bindConfigurationViewModel(viewModel: ConfigurationViewModel): ViewModel
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@IntoMap
|
||||||
|
@ViewModelKey(SharedActiveCallViewModel::class)
|
||||||
|
fun bindSharedActiveCallViewModel(viewModel: SharedActiveCallViewModel): ViewModel
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
@IntoMap
|
@IntoMap
|
||||||
@ViewModelKey(UserDirectorySharedActionViewModel::class)
|
@ViewModelKey(UserDirectorySharedActionViewModel::class)
|
||||||
|
|
|
@ -40,10 +40,10 @@ class DefaultErrorFormatter @Inject constructor(
|
||||||
null -> null
|
null -> null
|
||||||
is IdentityServiceError -> identityServerError(throwable)
|
is IdentityServiceError -> identityServerError(throwable)
|
||||||
is Failure.NetworkConnection -> {
|
is Failure.NetworkConnection -> {
|
||||||
when {
|
when (throwable.ioException) {
|
||||||
throwable.ioException is SocketTimeoutException ->
|
is SocketTimeoutException ->
|
||||||
stringProvider.getString(R.string.error_network_timeout)
|
stringProvider.getString(R.string.error_network_timeout)
|
||||||
throwable.ioException is UnknownHostException ->
|
is UnknownHostException ->
|
||||||
// Invalid homeserver?
|
// Invalid homeserver?
|
||||||
// TODO Check network state, airplane mode, etc.
|
// TODO Check network state, airplane mode, etc.
|
||||||
stringProvider.getString(R.string.login_error_unknown_host)
|
stringProvider.getString(R.string.login_error_unknown_host)
|
||||||
|
|
|
@ -17,10 +17,15 @@
|
||||||
package im.vector.riotx.core.extensions
|
package im.vector.riotx.core.extensions
|
||||||
|
|
||||||
import androidx.fragment.app.FragmentTransaction
|
import androidx.fragment.app.FragmentTransaction
|
||||||
|
import im.vector.matrix.android.api.extensions.tryThis
|
||||||
|
|
||||||
inline fun androidx.fragment.app.FragmentManager.commitTransactionNow(func: FragmentTransaction.() -> FragmentTransaction) {
|
inline fun androidx.fragment.app.FragmentManager.commitTransactionNow(func: FragmentTransaction.() -> FragmentTransaction) {
|
||||||
|
// Could throw and make the app crash
|
||||||
|
// e.g sharedActionViewModel.observe()
|
||||||
|
tryThis("Failed to commitTransactionNow") {
|
||||||
beginTransaction().func().commitNow()
|
beginTransaction().func().commitNow()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
inline fun androidx.fragment.app.FragmentManager.commitTransaction(func: FragmentTransaction.() -> FragmentTransaction) {
|
inline fun androidx.fragment.app.FragmentManager.commitTransaction(func: FragmentTransaction.() -> FragmentTransaction) {
|
||||||
beginTransaction().func().commit()
|
beginTransaction().func().commit()
|
||||||
|
|
|
@ -38,10 +38,6 @@ fun Session.configureAndStart(context: Context,
|
||||||
startSyncing(context)
|
startSyncing(context)
|
||||||
refreshPushers()
|
refreshPushers()
|
||||||
pushRuleTriggerListener.startWithSession(this)
|
pushRuleTriggerListener.startWithSession(this)
|
||||||
|
|
||||||
// TODO P1 From HomeActivity
|
|
||||||
// @Inject lateinit var incomingVerificationRequestHandler: IncomingVerificationRequestHandler
|
|
||||||
// @Inject lateinit var keyRequestHandler: KeyRequestHandler
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Session.startSyncing(context: Context) {
|
fun Session.startSyncing(context: Context) {
|
||||||
|
|
|
@ -165,6 +165,7 @@ abstract class VectorBaseActivity : AppCompatActivity(), HasScreenInjector {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
Timber.i("onCreate Activity ${this.javaClass.simpleName}")
|
||||||
val vectorComponent = getVectorComponent()
|
val vectorComponent = getVectorComponent()
|
||||||
screenComponent = DaggerScreenComponent.factory().create(vectorComponent, this)
|
screenComponent = DaggerScreenComponent.factory().create(vectorComponent, this)
|
||||||
val timeForInjection = measureTimeMillis {
|
val timeForInjection = measureTimeMillis {
|
||||||
|
@ -252,6 +253,7 @@ abstract class VectorBaseActivity : AppCompatActivity(), HasScreenInjector {
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
|
Timber.i("onDestroy Activity ${this.javaClass.simpleName}")
|
||||||
unBinder?.unbind()
|
unBinder?.unbind()
|
||||||
unBinder = null
|
unBinder = null
|
||||||
|
|
||||||
|
@ -279,6 +281,7 @@ abstract class VectorBaseActivity : AppCompatActivity(), HasScreenInjector {
|
||||||
|
|
||||||
override fun onPause() {
|
override fun onPause() {
|
||||||
super.onPause()
|
super.onPause()
|
||||||
|
Timber.i("onPause Activity ${this.javaClass.simpleName}")
|
||||||
|
|
||||||
rageShake.stop()
|
rageShake.stop()
|
||||||
|
|
||||||
|
|
|
@ -34,6 +34,7 @@ import com.airbnb.mvrx.MvRxViewId
|
||||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||||
|
import com.jakewharton.rxbinding3.view.clicks
|
||||||
import im.vector.riotx.core.di.DaggerScreenComponent
|
import im.vector.riotx.core.di.DaggerScreenComponent
|
||||||
import im.vector.riotx.core.di.ScreenComponent
|
import im.vector.riotx.core.di.ScreenComponent
|
||||||
import im.vector.riotx.core.utils.DimensionConverter
|
import im.vector.riotx.core.utils.DimensionConverter
|
||||||
|
@ -41,6 +42,7 @@ import io.reactivex.android.schedulers.AndroidSchedulers
|
||||||
import io.reactivex.disposables.CompositeDisposable
|
import io.reactivex.disposables.CompositeDisposable
|
||||||
import io.reactivex.disposables.Disposable
|
import io.reactivex.disposables.Disposable
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add MvRx capabilities to bottomsheetdialog (like BaseMvRxFragment)
|
* Add MvRx capabilities to bottomsheetdialog (like BaseMvRxFragment)
|
||||||
|
@ -169,6 +171,18 @@ abstract class VectorBaseBottomSheetDialogFragment : BottomSheetDialogFragment()
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ==========================================================================================
|
||||||
|
* Views
|
||||||
|
* ========================================================================================== */
|
||||||
|
|
||||||
|
protected fun View.debouncedClicks(onClicked: () -> Unit) {
|
||||||
|
clicks()
|
||||||
|
.throttleFirst(300, TimeUnit.MILLISECONDS)
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe { onClicked() }
|
||||||
|
.disposeOnDestroyView()
|
||||||
|
}
|
||||||
|
|
||||||
/* ==========================================================================================
|
/* ==========================================================================================
|
||||||
* ViewEvents
|
* ViewEvents
|
||||||
* ========================================================================================== */
|
* ========================================================================================== */
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue