mirror of
https://github.com/element-hq/element-android
synced 2024-11-24 02:15:35 +03:00
Merge branch 'develop' into feature/fga/timeline_chunks_rework
This commit is contained in:
commit
91215854f4
513 changed files with 7262 additions and 2619 deletions
23
.github/workflows/sync-from-external-sources.yml
vendored
23
.github/workflows/sync-from-external-sources.yml
vendored
|
@ -70,4 +70,27 @@ jobs:
|
||||||
body: |
|
body: |
|
||||||
- Update SAS Strings from matrix-doc.
|
- Update SAS Strings from matrix-doc.
|
||||||
branch: sync-sas-strings
|
branch: sync-sas-strings
|
||||||
|
base: develop
|
||||||
|
|
||||||
|
sync-analytics-plan:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
# Skip in forks
|
||||||
|
if: github.repository == 'vector-im/element-android'
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Run analytics import script
|
||||||
|
run: ./tools/import_analytic_plan.sh
|
||||||
|
- name: Create Pull Request for analytics plan
|
||||||
|
uses: peter-evans/create-pull-request@v3
|
||||||
|
with:
|
||||||
|
commit-message: Sync analytics plan
|
||||||
|
title: Sync analytics plan
|
||||||
|
body: |
|
||||||
|
### Update analytics plan
|
||||||
|
Reviewers:
|
||||||
|
- [ ] Please remove usage of Event or Enum which may have been removed or updated
|
||||||
|
- [ ] please ensure new Events or new Enums are used to send analytics by pushing new commit(s) to this PR.
|
||||||
|
|
||||||
|
*Note*: Change are coming from [this project](https://github.com/matrix-org/matrix-analytics-events)
|
||||||
|
branch: sync-analytics-plan
|
||||||
base: develop
|
base: develop
|
31
.github/workflows/triage-move-labelled.yml
vendored
31
.github/workflows/triage-move-labelled.yml
vendored
|
@ -34,7 +34,7 @@ jobs:
|
||||||
with:
|
with:
|
||||||
headers: '{"GraphQL-Features": "projects_next_graphql"}'
|
headers: '{"GraphQL-Features": "projects_next_graphql"}'
|
||||||
query: |
|
query: |
|
||||||
mutation add_to_project($projectid:String!,$contentid:String!) {
|
mutation add_to_project($projectid:ID!,$contentid:ID!) {
|
||||||
addProjectNextItem(input:{projectId:$projectid contentId:$contentid}) {
|
addProjectNextItem(input:{projectId:$projectid contentId:$contentid}) {
|
||||||
projectNextItem {
|
projectNextItem {
|
||||||
id
|
id
|
||||||
|
@ -59,7 +59,7 @@ jobs:
|
||||||
# with:
|
# with:
|
||||||
# headers: '{"GraphQL-Features": "projects_next_graphql"}'
|
# headers: '{"GraphQL-Features": "projects_next_graphql"}'
|
||||||
# query: |
|
# query: |
|
||||||
# mutation add_to_project($projectid:String!,$contentid:String!) {
|
# mutation add_to_project($projectid:ID!,$contentid:ID!) {
|
||||||
# addProjectNextItem(input:{projectId:$projectid contentId:$contentid}) {
|
# addProjectNextItem(input:{projectId:$projectid contentId:$contentid}) {
|
||||||
# projectNextItem {
|
# projectNextItem {
|
||||||
# id
|
# id
|
||||||
|
@ -82,7 +82,7 @@ jobs:
|
||||||
with:
|
with:
|
||||||
headers: '{"GraphQL-Features": "projects_next_graphql"}'
|
headers: '{"GraphQL-Features": "projects_next_graphql"}'
|
||||||
query: |
|
query: |
|
||||||
mutation add_to_project($projectid:String!,$contentid:String!) {
|
mutation add_to_project($projectid:ID!,$contentid:ID!) {
|
||||||
addProjectNextItem(input:{projectId:$projectid contentId:$contentid}) {
|
addProjectNextItem(input:{projectId:$projectid contentId:$contentid}) {
|
||||||
projectNextItem {
|
projectNextItem {
|
||||||
id
|
id
|
||||||
|
@ -105,7 +105,7 @@ jobs:
|
||||||
with:
|
with:
|
||||||
headers: '{"GraphQL-Features": "projects_next_graphql"}'
|
headers: '{"GraphQL-Features": "projects_next_graphql"}'
|
||||||
query: |
|
query: |
|
||||||
mutation add_to_project($projectid:String!,$contentid:String!) {
|
mutation add_to_project($projectid:ID!,$contentid:ID!) {
|
||||||
addProjectNextItem(input:{projectId:$projectid contentId:$contentid}) {
|
addProjectNextItem(input:{projectId:$projectid contentId:$contentid}) {
|
||||||
projectNextItem {
|
projectNextItem {
|
||||||
id
|
id
|
||||||
|
@ -117,3 +117,26 @@ jobs:
|
||||||
env:
|
env:
|
||||||
PROJECT_ID: "PN_kwDOAM0swc0rRA"
|
PROJECT_ID: "PN_kwDOAM0swc0rRA"
|
||||||
GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||||
|
|
||||||
|
move_message_bubbles_issues:
|
||||||
|
name: A-Message-Bubbles to Message bubbles board
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: >
|
||||||
|
contains(github.event.issue.labels.*.name, 'A-Message-Bubbles')
|
||||||
|
steps:
|
||||||
|
- uses: octokit/graphql-action@v2.x
|
||||||
|
with:
|
||||||
|
headers: '{"GraphQL-Features": "projects_next_graphql"}'
|
||||||
|
query: |
|
||||||
|
mutation add_to_project($projectid:ID!,$contentid:ID!) {
|
||||||
|
addProjectNextItem(input:{projectId:$projectid contentId:$contentid}) {
|
||||||
|
projectNextItem {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
projectid: ${{ env.PROJECT_ID }}
|
||||||
|
contentid: ${{ github.event.issue.node_id }}
|
||||||
|
env:
|
||||||
|
PROJECT_ID: "PN_kwDOAM0swc3m-g"
|
||||||
|
GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||||
|
|
|
@ -24,6 +24,7 @@
|
||||||
<w>pbkdf</w>
|
<w>pbkdf</w>
|
||||||
<w>pids</w>
|
<w>pids</w>
|
||||||
<w>pkcs</w>
|
<w>pkcs</w>
|
||||||
|
<w>posthog</w>
|
||||||
<w>previewable</w>
|
<w>previewable</w>
|
||||||
<w>previewables</w>
|
<w>previewables</w>
|
||||||
<w>pstn</w>
|
<w>pstn</w>
|
||||||
|
|
62
CHANGES.md
62
CHANGES.md
|
@ -1,3 +1,65 @@
|
||||||
|
Changes in Element v1.3.12 (2021-12-20)
|
||||||
|
=======================================
|
||||||
|
|
||||||
|
Bugfixes 🐛
|
||||||
|
----------
|
||||||
|
- Fixing emoji related crashes on android 8.1.1 and below ([#4769](https://github.com/vector-im/element-android/issues/4769))
|
||||||
|
|
||||||
|
|
||||||
|
Changes in Element v1.3.11 (2021-12-17)
|
||||||
|
=======================================
|
||||||
|
|
||||||
|
Bugfixes 🐛
|
||||||
|
----------
|
||||||
|
- Fixing proximity sensor still being active after a call ([#2467](https://github.com/vector-im/element-android/issues/2467))
|
||||||
|
- Fix name and shield are truncated in the room detail screen ([#4700](https://github.com/vector-im/element-android/issues/4700))
|
||||||
|
- Call banner: center text vertically ([#4710](https://github.com/vector-im/element-android/issues/4710))
|
||||||
|
- Fixes unable to render messages by allowing them to render whilst the emoji library is initialising ([#4733](https://github.com/vector-im/element-android/issues/4733))
|
||||||
|
- Fix app crash uppon long press on a reply event ([#4742](https://github.com/vector-im/element-android/issues/4742))
|
||||||
|
- Fixes crash when launching rooms which contain emojis in the emote content on android 12+ ([#4743](https://github.com/vector-im/element-android/issues/4743))
|
||||||
|
|
||||||
|
Other changes
|
||||||
|
-------------
|
||||||
|
- Avoids leaking the activity windows when loading dialogs are displaying ([#4713](https://github.com/vector-im/element-android/issues/4713))
|
||||||
|
|
||||||
|
|
||||||
|
Changes in Element v1.3.10 (2021-12-14)
|
||||||
|
=======================================
|
||||||
|
|
||||||
|
Features ✨
|
||||||
|
----------
|
||||||
|
- Poll Feature - Render in timeline ([#4653](https://github.com/vector-im/element-android/issues/4653))
|
||||||
|
- Updates URL previews to match latest designs ([#4278](https://github.com/vector-im/element-android/issues/4278))
|
||||||
|
- Setup Analytics framework using PostHog. Analytics are disabled by default. Opt-in screen not automatically displayed yet. ([#4559](https://github.com/vector-im/element-android/issues/4559))
|
||||||
|
- Create a legal screen in the setting to group all the different policies. ([#4660](https://github.com/vector-im/element-android/issues/4660))
|
||||||
|
- Add a help section in the settings. ([#4638](https://github.com/vector-im/element-android/issues/4638))
|
||||||
|
- MSC2732: Olm fallback keys ([#3473](https://github.com/vector-im/element-android/issues/3473))
|
||||||
|
|
||||||
|
Bugfixes 🐛
|
||||||
|
----------
|
||||||
|
- Fixes message menu showing when copying message urls ([#4324](https://github.com/vector-im/element-android/issues/4324))
|
||||||
|
- Fix lots of integration tests by introducing TestMatrix class and MatrixWorkerFactory. ([#4546](https://github.com/vector-im/element-android/issues/4546))
|
||||||
|
- Fix empty Dev Tools screen issue. ([#4592](https://github.com/vector-im/element-android/issues/4592))
|
||||||
|
- Fix for outgoing voip call via sip bridge failing after 1 minute. ([#4621](https://github.com/vector-im/element-android/issues/4621))
|
||||||
|
- Update log warning for call selection during voip calls. ([#4636](https://github.com/vector-im/element-android/issues/4636))
|
||||||
|
- Fix possible crash when having identical subspaces in multiple root spaces ([#4693](https://github.com/vector-im/element-android/issues/4693))
|
||||||
|
- Fix a crash in the timeline with some Emojis. Also migrate to androidx.emoji2 ([#4698](https://github.com/vector-im/element-android/issues/4698))
|
||||||
|
- At the very first room search after opening the app sometimes no results are displayed ([#4600](https://github.com/vector-im/element-android/issues/4600))
|
||||||
|
|
||||||
|
Other changes
|
||||||
|
-------------
|
||||||
|
- Upgrade OLM to v3.2.7 and get it from our maven repository. ([#4647](https://github.com/vector-im/element-android/issues/4647))
|
||||||
|
- Add explicit dependency location, regarding the several maven repository. Also update some libraries (flexbox and alerter), and do some cleanup. ([#4670](https://github.com/vector-im/element-android/issues/4670))
|
||||||
|
- Introducing feature flagging to the login and notification settings flows ([#4626](https://github.com/vector-im/element-android/issues/4626))
|
||||||
|
- There is no need to call job.cancel() when we are using viewModelScope() ([#4602](https://github.com/vector-im/element-android/issues/4602))
|
||||||
|
- Debounce some clicks ([#4645](https://github.com/vector-im/element-android/issues/4645))
|
||||||
|
- Improve issue automation workflows ([#4617](https://github.com/vector-im/element-android/issues/4617))
|
||||||
|
- Add automation to move message bubbles issues to message bubbles board. ([#4666](https://github.com/vector-im/element-android/issues/4666))
|
||||||
|
- Fix graphql warning in issue workflow automation ([#4671](https://github.com/vector-im/element-android/issues/4671))
|
||||||
|
- Cleanup the layout files ([#4604](https://github.com/vector-im/element-android/issues/4604))
|
||||||
|
- Cleanup id ref. Use type views instead ([#4650](https://github.com/vector-im/element-android/issues/4650))
|
||||||
|
|
||||||
|
|
||||||
Changes in Element v1.3.9 (2021-12-01)
|
Changes in Element v1.3.9 (2021-12-01)
|
||||||
======================================
|
======================================
|
||||||
|
|
||||||
|
|
66
build.gradle
66
build.gradle
|
@ -1,12 +1,11 @@
|
||||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||||
|
|
||||||
buildscript {
|
buildscript {
|
||||||
|
|
||||||
apply from: 'dependencies.gradle'
|
apply from: 'dependencies.gradle'
|
||||||
|
apply from: 'dependencies_groups.gradle'
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
jcenter()
|
|
||||||
maven {
|
maven {
|
||||||
url "https://plugins.gradle.org/m2/"
|
url "https://plugins.gradle.org/m2/"
|
||||||
}
|
}
|
||||||
|
@ -30,52 +29,57 @@ buildscript {
|
||||||
|
|
||||||
// ktlint Plugin
|
// ktlint Plugin
|
||||||
plugins {
|
plugins {
|
||||||
id "org.jlleitschuh.gradle.ktlint" version "10.2.0"
|
id "org.jlleitschuh.gradle.ktlint" version "10.2.1"
|
||||||
}
|
}
|
||||||
|
|
||||||
allprojects {
|
allprojects {
|
||||||
apply plugin: "org.jlleitschuh.gradle.ktlint"
|
apply plugin: "org.jlleitschuh.gradle.ktlint"
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
// For olm library. This has to be declared first, to ensure that Olm library is not downloaded from another repo
|
// For olm library.
|
||||||
maven { url 'https://gitlab.matrix.org/api/v4/projects/27/packages/maven' }
|
maven {
|
||||||
|
url 'https://gitlab.matrix.org/api/v4/projects/27/packages/maven'
|
||||||
|
content {
|
||||||
|
groups.olm.regex.each { includeGroupByRegex it }
|
||||||
|
groups.olm.group.each { includeGroup it }
|
||||||
|
}
|
||||||
|
}
|
||||||
maven {
|
maven {
|
||||||
url 'https://jitpack.io'
|
url 'https://jitpack.io'
|
||||||
content {
|
content {
|
||||||
// Use this repo only for FilePicker
|
groups.jitpack.regex.each { includeGroupByRegex it }
|
||||||
includeGroupByRegex "com\\.github\\.jaiselrahman"
|
groups.jitpack.group.each { includeGroup it }
|
||||||
// And monarchy
|
|
||||||
includeGroupByRegex "com\\.github\\.Zhuinden"
|
|
||||||
// And ucrop
|
|
||||||
includeGroupByRegex "com\\.github\\.yalantis"
|
|
||||||
// JsonViewer
|
|
||||||
includeGroupByRegex 'com\\.github\\.BillCarsonFr'
|
|
||||||
// PhotoView
|
|
||||||
includeGroupByRegex 'com\\.github\\.chrisbanes'
|
|
||||||
// PFLockScreen-Android
|
|
||||||
includeGroupByRegex 'com\\.github\\.vector-im'
|
|
||||||
// DraggableView
|
|
||||||
includeGroupByRegex 'com\\.github\\.hyuwah'
|
|
||||||
|
|
||||||
// Chat effects
|
|
||||||
includeGroupByRegex 'com\\.github\\.jetradarmobile'
|
|
||||||
includeGroupByRegex 'nl\\.dionsegijn'
|
|
||||||
|
|
||||||
// Voice RecordView
|
|
||||||
includeGroupByRegex 'com\\.github\\.Armen101'
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' }
|
|
||||||
// Jitsi repo
|
// Jitsi repo
|
||||||
maven {
|
maven {
|
||||||
url "https://github.com/vector-im/jitsi_libre_maven/raw/main/android-sdk-3.10.0"
|
url "https://github.com/vector-im/jitsi_libre_maven/raw/main/android-sdk-3.10.0"
|
||||||
// Note: to test Jitsi release you can use a local file like this:
|
// Note: to test Jitsi release you can use a local file like this:
|
||||||
// url "file:///Users/bmarty/workspaces/jitsi_libre_maven/android-sdk-3.10.0"
|
// url "file:///Users/bmarty/workspaces/jitsi_libre_maven/android-sdk-3.10.0"
|
||||||
|
content {
|
||||||
|
groups.jitsi.regex.each { includeGroupByRegex it }
|
||||||
|
groups.jitsi.group.each { includeGroup it }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
google {
|
||||||
|
content {
|
||||||
|
groups.google.regex.each { includeGroupByRegex it }
|
||||||
|
groups.google.group.each { includeGroup it }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mavenCentral {
|
||||||
|
content {
|
||||||
|
groups.mavenCentral.regex.each { includeGroupByRegex it }
|
||||||
|
groups.mavenCentral.group.each { includeGroup it }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//noinspection JcenterRepositoryObsolete
|
||||||
|
jcenter {
|
||||||
|
content {
|
||||||
|
groups.jcenter.regex.each { includeGroupByRegex it }
|
||||||
|
groups.jcenter.group.each { includeGroup it }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
google()
|
|
||||||
mavenCentral()
|
|
||||||
jcenter()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
|
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
|
||||||
|
|
1
changelog.d/3444.bugfix
Normal file
1
changelog.d/3444.bugfix
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Attachment picker UI improvements
|
|
@ -1 +0,0 @@
|
||||||
Updates URL previews to match latest designs
|
|
|
@ -1 +0,0 @@
|
||||||
Fixes message menu showing when copying message urls
|
|
|
@ -1 +0,0 @@
|
||||||
Fix lots of integration tests by introducing TestMatrix class and MatrixWorkerFactory.
|
|
|
@ -1 +0,0 @@
|
||||||
There is no need to call job.cancel() when we are using viewModelScope()
|
|
|
@ -1 +0,0 @@
|
||||||
Cleanup the layout files
|
|
1
changelog.d/4612.misc
Normal file
1
changelog.d/4612.misc
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Workaround to fetch all the pending toDevice events from a Synapse homeserver
|
|
@ -1 +0,0 @@
|
||||||
Improve issue automation workflows
|
|
|
@ -1 +0,0 @@
|
||||||
Fix for outgoing voip call via sip bridge failing after 1 minute.
|
|
|
@ -1 +0,0 @@
|
||||||
Introducing feature flagging to the login and notification settings flows
|
|
|
@ -1 +0,0 @@
|
||||||
Update log warning for call selection during voip calls.
|
|
|
@ -1 +0,0 @@
|
||||||
Debounce some clicks
|
|
|
@ -1 +0,0 @@
|
||||||
Upgrade OLM to v3.2.7 and get it from our maven repository.
|
|
1
changelog.d/4747.misc
Normal file
1
changelog.d/4747.misc
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Cleaning rendering of state events in timeline
|
1
changelog.d/4756.bugfix
Normal file
1
changelog.d/4756.bugfix
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Fixes newer emojis rendering strangely when inserting from the system keyboard
|
1
changelog.d/4767.bugfix
Normal file
1
changelog.d/4767.bugfix
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Fixing unable to change change avatar in some scenarios
|
1
changelog.d/4804.bugfix
Normal file
1
changelog.d/4804.bugfix
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Fixing encrypted non message events showing up as notification messages (eg when a participant joins, mutes or leaves a voice call)
|
201
dependencies_groups.gradle
Normal file
201
dependencies_groups.gradle
Normal file
|
@ -0,0 +1,201 @@
|
||||||
|
ext.groups = [
|
||||||
|
jitpack : [
|
||||||
|
regex: [
|
||||||
|
],
|
||||||
|
group: [
|
||||||
|
'com.github.Armen101',
|
||||||
|
'com.github.BillCarsonFr',
|
||||||
|
'com.github.chrisbanes',
|
||||||
|
'com.github.hyuwah',
|
||||||
|
'com.github.jetradarmobile',
|
||||||
|
'com.github.tapadoo',
|
||||||
|
'com.github.vector-im',
|
||||||
|
'com.github.yalantis',
|
||||||
|
'com.github.Zhuinden',
|
||||||
|
]
|
||||||
|
],
|
||||||
|
olm : [
|
||||||
|
regex: [
|
||||||
|
],
|
||||||
|
group: [
|
||||||
|
'org.matrix.android',
|
||||||
|
]
|
||||||
|
],
|
||||||
|
jitsi : [
|
||||||
|
regex: [
|
||||||
|
],
|
||||||
|
group: [
|
||||||
|
'com.facebook.react',
|
||||||
|
'org.jitsi.react',
|
||||||
|
'org.webkit',
|
||||||
|
]
|
||||||
|
],
|
||||||
|
google : [
|
||||||
|
regex: [
|
||||||
|
'androidx\\..*',
|
||||||
|
'com\\.android\\.tools\\..*',
|
||||||
|
'com\\.google\\.android\\..*',
|
||||||
|
],
|
||||||
|
group: [
|
||||||
|
'com.google.firebase',
|
||||||
|
'com.android',
|
||||||
|
'com.android.tools',
|
||||||
|
]
|
||||||
|
],
|
||||||
|
mavenCentral: [
|
||||||
|
regex: [
|
||||||
|
],
|
||||||
|
group: [
|
||||||
|
'com.adevinta.android',
|
||||||
|
'com.airbnb.android',
|
||||||
|
'com.almworks.sqlite4java',
|
||||||
|
'com.arthenica',
|
||||||
|
'com.atlassian.commonmark',
|
||||||
|
'com.atlassian.pom',
|
||||||
|
'com.beust',
|
||||||
|
'com.davemorrissey.labs',
|
||||||
|
'com.dropbox.core',
|
||||||
|
'com.facebook.fresco',
|
||||||
|
'com.facebook.infer.annotation',
|
||||||
|
'com.facebook.soloader',
|
||||||
|
'com.facebook.stetho',
|
||||||
|
'com.fasterxml',
|
||||||
|
'com.fasterxml.jackson',
|
||||||
|
'com.fasterxml.jackson.core',
|
||||||
|
'com.gabrielittner.threetenbp',
|
||||||
|
'com.getkeepsafe.relinker',
|
||||||
|
'com.github.bumptech.glide',
|
||||||
|
'com.github.filippudak',
|
||||||
|
'com.github.filippudak.progresspieview',
|
||||||
|
'com.github.javaparser',
|
||||||
|
'com.github.piasy',
|
||||||
|
'com.github.shyiko.klob',
|
||||||
|
'com.google',
|
||||||
|
'com.google.auto.service',
|
||||||
|
'com.google.auto.value',
|
||||||
|
'com.google.code.findbugs',
|
||||||
|
'com.google.code.gson',
|
||||||
|
'com.google.dagger',
|
||||||
|
'com.google.devtools.ksp',
|
||||||
|
'com.google.errorprone',
|
||||||
|
'com.google.googlejavaformat',
|
||||||
|
'com.google.guava',
|
||||||
|
'com.google.j2objc',
|
||||||
|
'com.google.jimfs',
|
||||||
|
'com.google.protobuf',
|
||||||
|
'com.google.zxing',
|
||||||
|
'com.googlecode.htmlcompressor',
|
||||||
|
'com.googlecode.json-simple',
|
||||||
|
'com.googlecode.libphonenumber',
|
||||||
|
'com.ibm.icu',
|
||||||
|
'com.jakewharton.android.repackaged',
|
||||||
|
'com.jakewharton.timber',
|
||||||
|
'com.linkedin.dexmaker',
|
||||||
|
'com.nulab-inc',
|
||||||
|
'com.otaliastudios.opengl',
|
||||||
|
'com.parse.bolts',
|
||||||
|
'com.pinterest',
|
||||||
|
'com.pinterest.ktlint',
|
||||||
|
'com.posthog.android',
|
||||||
|
'com.squareup',
|
||||||
|
'com.squareup.duktape',
|
||||||
|
'com.squareup.moshi',
|
||||||
|
'com.squareup.okhttp3',
|
||||||
|
'com.squareup.okio',
|
||||||
|
'com.squareup.retrofit2',
|
||||||
|
'com.sun.activation',
|
||||||
|
'com.sun.istack',
|
||||||
|
'com.sun.xml.bind',
|
||||||
|
'com.sun.xml.bind.mvn',
|
||||||
|
'com.sun.xml.fastinfoset',
|
||||||
|
'com.thoughtworks.qdox',
|
||||||
|
'com.vanniktech',
|
||||||
|
'commons-cli',
|
||||||
|
'commons-codec',
|
||||||
|
'commons-io',
|
||||||
|
'commons-logging',
|
||||||
|
'info.picocli',
|
||||||
|
'io.arrow-kt',
|
||||||
|
'io.github.detekt.sarif4k',
|
||||||
|
'io.github.reactivecircus.flowbinding',
|
||||||
|
'io.jsonwebtoken',
|
||||||
|
'io.kindedj',
|
||||||
|
'io.mockk',
|
||||||
|
'io.noties.markwon',
|
||||||
|
'io.reactivex.rxjava2',
|
||||||
|
'io.realm',
|
||||||
|
'it.unimi.dsi',
|
||||||
|
'jakarta.activation',
|
||||||
|
'jakarta.xml.bind',
|
||||||
|
'javax.annotation',
|
||||||
|
'javax.inject',
|
||||||
|
'jline',
|
||||||
|
'jp.wasabeef',
|
||||||
|
'junit',
|
||||||
|
'me.leolin',
|
||||||
|
'me.saket',
|
||||||
|
'net.bytebuddy',
|
||||||
|
'net.java',
|
||||||
|
'net.java.dev.jna',
|
||||||
|
'net.lachlanmckee',
|
||||||
|
'net.ltgt.gradle.incap',
|
||||||
|
'net.sf.jopt-simple',
|
||||||
|
'net.sf.kxml',
|
||||||
|
'nl.dionsegijn',
|
||||||
|
'org.amshove.kluent',
|
||||||
|
'org.apache',
|
||||||
|
'org.apache.ant',
|
||||||
|
'org.apache.commons',
|
||||||
|
'org.apache.httpcomponents',
|
||||||
|
'org.apache.sanselan',
|
||||||
|
'org.bouncycastle',
|
||||||
|
'org.checkerframework',
|
||||||
|
'org.codehaus',
|
||||||
|
'org.codehaus.groovy',
|
||||||
|
'org.codehaus.mojo',
|
||||||
|
'org.eclipse.ee4j',
|
||||||
|
'org.ec4j.core',
|
||||||
|
'org.glassfish.jaxb',
|
||||||
|
'org.hamcrest',
|
||||||
|
'org.jetbrains',
|
||||||
|
'org.jetbrains.intellij.deps',
|
||||||
|
'org.jetbrains.kotlin',
|
||||||
|
'org.jetbrains.kotlinx',
|
||||||
|
'org.jsoup',
|
||||||
|
'org.junit',
|
||||||
|
'org.junit.jupiter',
|
||||||
|
'org.junit.platform',
|
||||||
|
'org.jvnet.staxex',
|
||||||
|
'org.mockito',
|
||||||
|
'org.mongodb',
|
||||||
|
'org.objenesis',
|
||||||
|
'org.opentest4j',
|
||||||
|
'org.ow2',
|
||||||
|
'org.ow2.asm',
|
||||||
|
'org.ow2.asm',
|
||||||
|
'org.reactivestreams',
|
||||||
|
'org.robolectric',
|
||||||
|
'org.slf4j',
|
||||||
|
'org.sonatype.oss',
|
||||||
|
'org.testng',
|
||||||
|
'org.threeten',
|
||||||
|
'xerces',
|
||||||
|
'xml-apis',
|
||||||
|
]
|
||||||
|
],
|
||||||
|
jcenter : [
|
||||||
|
regex: [
|
||||||
|
],
|
||||||
|
group: [
|
||||||
|
'com.amulyakhare',
|
||||||
|
'com.otaliastudios',
|
||||||
|
'com.yqritc',
|
||||||
|
// https://github.com/cmelchior/realmfieldnameshelper/issues/42
|
||||||
|
'dk.ilios',
|
||||||
|
'im.dlg',
|
||||||
|
'me.dm7.barcodescanner',
|
||||||
|
'me.gujun.android',
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|
16
docs/analytics.md
Normal file
16
docs/analytics.md
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
# Analytics in Element
|
||||||
|
|
||||||
|
## Solution
|
||||||
|
|
||||||
|
Element is using PostHog to send analytics event.
|
||||||
|
We ask for the user to give consent before sending any analytics data.
|
||||||
|
|
||||||
|
## How to add a new Event
|
||||||
|
|
||||||
|
The analytics plan is shared between all Element clients. To add an Event, please open a PR to this project: https://github.com/matrix-org/matrix-analytics-events
|
||||||
|
|
||||||
|
Then, once the PR has been merged, you can run the tool `import_analytic_plan.sh` to import the plan to Element, and then you can use the new Event. Note that this tool is run by Github action once a week.
|
||||||
|
|
||||||
|
## Forks of Element
|
||||||
|
|
||||||
|
Analytics on forks are disabled by default. Please refer to AnalyticsConfig and there implementation to setup analytics on your project.
|
|
@ -1,2 +1,2 @@
|
||||||
Hlavní změny v této verzi: Opravy chyb týkající se především oznámení.
|
Hlavní změny v této verzi: Opravy chyb týkající se především oznámení.
|
||||||
Úplný seznam změn: https://github.com/vector-im/element-android/releases/tag/v1.3.7
|
Úplný seznam změn: https://github.com/vector-im/element-android/releases/tag/v1.3.7-RC2
|
||||||
|
|
2
fastlane/metadata/android/cs-CZ/changelogs/40103080.txt
Normal file
2
fastlane/metadata/android/cs-CZ/changelogs/40103080.txt
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
Hlavní změny v této verzi: Opravy chyb!
|
||||||
|
Úplný seznam změn: https://github.com/vector-im/element-android/releases/tag/v1.3.8
|
|
@ -1,2 +1,2 @@
|
||||||
Hauptänderungen: Fehler bei Benachrichtigungen gefixt
|
Hauptänderungen: Fehler bei Benachrichtigungen gefixt
|
||||||
Ganze Änderungsliste: https://github.com/vector-im/element-android/releases/tag/v1.3.7
|
Ganze Änderungsliste: https://github.com/vector-im/element-android/releases/tag/v1.3.7-RC2
|
||||||
|
|
2
fastlane/metadata/android/de-DE/changelogs/40103080.txt
Normal file
2
fastlane/metadata/android/de-DE/changelogs/40103080.txt
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
Änderungen: Verschiedene Fehler behoben
|
||||||
|
Änderungsliste: https://github.com/vector-im/element-android/releases/tag/v1.3.8
|
2
fastlane/metadata/android/en-US/changelogs/40103100.txt
Normal file
2
fastlane/metadata/android/en-US/changelogs/40103100.txt
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
Main changes in this version: Add support for polls (in labs). New URL preview design.
|
||||||
|
Full changelog: https://github.com/vector-im/element-android/releases/tag/v1.3.10
|
2
fastlane/metadata/android/en-US/changelogs/40103110.txt
Normal file
2
fastlane/metadata/android/en-US/changelogs/40103110.txt
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
Main changes in this version: Bug fixes!
|
||||||
|
Full changelog: https://github.com/vector-im/element-android/releases/tag/v1.3.11
|
2
fastlane/metadata/android/en-US/changelogs/40103120.txt
Normal file
2
fastlane/metadata/android/en-US/changelogs/40103120.txt
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
Main changes in this version: Bug fixes!
|
||||||
|
Full changelog: https://github.com/vector-im/element-android/releases/tag/v1.3.12
|
|
@ -1,2 +1,2 @@
|
||||||
Põhilised muutused selles versioonis: erinevad veaparandused, neist enamus on seotud teavitustega.
|
Põhilised muutused selles versioonis: erinevad veaparandused, neist enamus on seotud teavitustega.
|
||||||
Kogu ingliskeelne muudatuste logi: https://github.com/vector-im/element-android/releases/tag/v1.3.7
|
Kogu ingliskeelne muudatuste logi: https://github.com/vector-im/element-android/releases/tag/v1.3.7-RC2
|
||||||
|
|
2
fastlane/metadata/android/et/changelogs/40103080.txt
Normal file
2
fastlane/metadata/android/et/changelogs/40103080.txt
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
Põhilised muutused selles versioonis: pinu veaparandusi!
|
||||||
|
Kogu ingliskeelne muudatuste logi: https://github.com/vector-im/element-android/releases/tag/v1.3.8
|
|
@ -1,2 +1,2 @@
|
||||||
تغییرات اصلی در این نگارش: رفع اشکالهایی عمدتاً مربوط به آگاهیها.
|
تغییرات اصلی در این نگارش: رفع اشکالهایی عمدتاً مربوط به آگاهیها.
|
||||||
گزارش دگرگونی کامل: https://github.com/vector-im/element-android/releases/tag/v1.3.7
|
گزارش دگرگونی کامل: https://github.com/vector-im/element-android/releases/tag/v1.3.7-RC2
|
||||||
|
|
2
fastlane/metadata/android/fa/changelogs/40103080.txt
Normal file
2
fastlane/metadata/android/fa/changelogs/40103080.txt
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
تغییرات عمده در این نگارش: رفع مشکلها!
|
||||||
|
گزارش دگرکونی کامل: https://github.com/vector-im/element-android/releases/tag/v1.3.8
|
|
@ -1,2 +1,2 @@
|
||||||
Principaux changements pour cette version : corrections de problèmes, principalement sur les notifications
|
Principaux changements pour cette version : corrections de problèmes, principalement sur les notifications
|
||||||
Intégralité des changements : https://github.com/vector-im/element-android/releases/tag/v1.3.7
|
Intégralité des changements : https://github.com/vector-im/element-android/releases/tag/v1.3.7-RC2
|
||||||
|
|
2
fastlane/metadata/android/fr-FR/changelogs/40103080.txt
Normal file
2
fastlane/metadata/android/fr-FR/changelogs/40103080.txt
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
Principaux changements pour cette version : corrections de bugs !
|
||||||
|
Intégralité des changements : https://github.com/vector-im/element-android/releases/tag/v1.3.8
|
|
@ -1,2 +1,2 @@
|
||||||
Fő változás ebben a verzióban: Értesítési hibajavítások
|
Fő változás ebben a verzióban: Értesítési hibajavítások
|
||||||
Teljes változásnapló: https://github.com/vector-im/element-android/releases/tag/v1.3.7
|
Teljes változásnapló: https://github.com/vector-im/element-android/releases/tag/v1.3.7-RC2
|
||||||
|
|
2
fastlane/metadata/android/hu-HU/changelogs/40103080.txt
Normal file
2
fastlane/metadata/android/hu-HU/changelogs/40103080.txt
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
Főbb változtatások ebben a verzióban: Hibajavítások
|
||||||
|
Teljes változásnapló: https://github.com/vector-im/element-android/releases/tag/v1.3.8
|
|
@ -1,2 +1,2 @@
|
||||||
Perubahan utama di versi ini: Perbaikan bug terutama untuk notifikasinya.
|
Perubahan utama di versi ini: Perbaikan bug terutama untuk notifikasinya.
|
||||||
Changelog lengkap: https://github.com/vector-im/element-android/releases/tag/v1.3.7
|
Changelog lengkap: https://github.com/vector-im/element-android/releases/tag/v1.3.7-RC2
|
||||||
|
|
2
fastlane/metadata/android/id/changelogs/40103080.txt
Normal file
2
fastlane/metadata/android/id/changelogs/40103080.txt
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
Perubahan utama di versi ini: Beberapa perbaikan bug!
|
||||||
|
Changelog lengkap: https://github.com/vector-im/element-android/releases/tag/v1.3.8
|
|
@ -26,7 +26,7 @@ Element menempatkan Anda dalam kendali dengan cara yang berbeda:
|
||||||
2. Host sendiri akun Anda dengan menjalankan server pada infrastruktur IT Anda sendiri
|
2. Host sendiri akun Anda dengan menjalankan server pada infrastruktur IT Anda sendiri
|
||||||
3. Daftar untuk akun di server khusus dengan berlangganan platform hosting Layanan Matrix Element
|
3. Daftar untuk akun di server khusus dengan berlangganan platform hosting Layanan Matrix Element
|
||||||
|
|
||||||
<b>Pesan terbuka dan kolaborasi</b>
|
<b>Perpesanan dan kolaborasi terbuka</b>
|
||||||
Anda dapat mengobrol dengan siapa saja di jaringan Matrix, jika mereka menggunakan Element, aplikasi Matrix lain atau bahkan menggunakan aplikasi perpesanan yang berbeda.
|
Anda dapat mengobrol dengan siapa saja di jaringan Matrix, jika mereka menggunakan Element, aplikasi Matrix lain atau bahkan menggunakan aplikasi perpesanan yang berbeda.
|
||||||
|
|
||||||
<b>Sangat aman</b>
|
<b>Sangat aman</b>
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
Modifiche principali in questa versione: correzioni riguardo le notifiche.
|
Modifiche principali in questa versione: correzioni riguardo le notifiche.
|
||||||
Cronologia completa: https://github.com/vector-im/element-android/releases/tag/v1.3.7
|
Cronologia completa: https://github.com/vector-im/element-android/releases/tag/v1.3.7-RC2
|
||||||
|
|
2
fastlane/metadata/android/it-IT/changelogs/40103080.txt
Normal file
2
fastlane/metadata/android/it-IT/changelogs/40103080.txt
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
Modifiche principali in questa versione: correzioni di errori!
|
||||||
|
Cronologia completa: https://github.com/vector-im/element-android/releases/tag/v1.3.8
|
|
@ -1,2 +1,2 @@
|
||||||
Principais mudanças nesta versão: Consertos de bugs principalmente quanto às notificações.
|
Principais mudanças nesta versão: Consertos de bugs principalmente quanto às notificações.
|
||||||
Changelog completo: https://github.com/vector-im/element-android/releases/tag/v1.3.7
|
Changelog completo: https://github.com/vector-im/element-android/releases/tag/v1.3.7-RC2
|
||||||
|
|
2
fastlane/metadata/android/pt-BR/changelogs/40103080.txt
Normal file
2
fastlane/metadata/android/pt-BR/changelogs/40103080.txt
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
Principais mudanças nesta versão: Consertos de bugs!
|
||||||
|
Changelog completo: https://github.com/vector-im/element-android/releases/tag/v1.3.8
|
2
fastlane/metadata/android/sk/changelogs/40103070.txt
Normal file
2
fastlane/metadata/android/sk/changelogs/40103070.txt
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
Hlavné zmeny v tejto verzii: Opravy chýb týkajúce sa najmä oznámení.
|
||||||
|
Úplný zoznam zmien: https://github.com/vector-im/element-android/releases/tag/v1.3.7-RC2
|
2
fastlane/metadata/android/sk/changelogs/40103080.txt
Normal file
2
fastlane/metadata/android/sk/changelogs/40103080.txt
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
Hlavné zmeny v tejto verzii: Opravy chýb!
|
||||||
|
Úplný zoznam zmien: https://github.com/vector-im/element-android/releases/tag/v1.3.8
|
|
@ -1 +1 @@
|
||||||
Zabezpečené konverzácie a VoIP. Ochráňte vaše údaje pred tretími stranami.
|
Skupinový messenger - šifrované správy, skupinové konverzácie a videohovory
|
||||||
|
|
2
fastlane/metadata/android/sq/changelogs/40103070.txt
Normal file
2
fastlane/metadata/android/sq/changelogs/40103070.txt
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
Ndryshimet kryesore në këtë version: Ndreqje të metash të lidhura kryesisht me njoftimet.
|
||||||
|
Regjistër i plotë ndryshimesh: https://github.com/vector-im/element-android/releases/tag/v1.3.7-RC2
|
2
fastlane/metadata/android/sq/changelogs/40103080.txt
Normal file
2
fastlane/metadata/android/sq/changelogs/40103080.txt
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
Ndryshimet kryesore në këtë version: Ndreqje të metash!
|
||||||
|
Regjistër i plotë ndryshimesh: https://github.com/vector-im/element-android/releases/tag/v1.3.8
|
|
@ -1,2 +1,2 @@
|
||||||
Huvudsakliga ändringar i den här versionen: Buggfixar som huvudsakligen rör aviseringar.
|
Huvudsakliga ändringar i den här versionen: Buggfixar som huvudsakligen rör aviseringar.
|
||||||
Full ändringslogg: https://github.com/vector-im/element-android/releases/tag/v1.3.7
|
Full ändringslogg: https://github.com/vector-im/element-android/releases/tag/v1.3.7-RC2
|
||||||
|
|
2
fastlane/metadata/android/sv-SE/changelogs/40103080.txt
Normal file
2
fastlane/metadata/android/sv-SE/changelogs/40103080.txt
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
Huvudsakliga ändringar i den här versionen: Buggfixar!
|
||||||
|
Full ändringslogg: https://github.com/vector-im/element-android/releases/tag/v1.3.8
|
|
@ -1,2 +1,2 @@
|
||||||
Основні зміни в цій версії: виправлення помилок в основному у повідомленнях.
|
Основні зміни в цій версії: виправлення помилок в основному у повідомленнях.
|
||||||
Повний журнал змін: https://github.com/vector-im/element-android/releases/tag/v1.3.7
|
Повний журнал змін: https://github.com/vector-im/element-android/releases/tag/v1.3.7-RC2
|
||||||
|
|
2
fastlane/metadata/android/uk/changelogs/40103080.txt
Normal file
2
fastlane/metadata/android/uk/changelogs/40103080.txt
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
Основні зміни у цій версії: Виправлення помилок!
|
||||||
|
Повний перелік змін: https://github.com/vector-im/element-android/releases/tag/v1.3.8
|
|
@ -1,7 +1,7 @@
|
||||||
Element — це і безпечний месенджер, і застосунок для співпраці команди, який ідеально підходить для групових бесід під час віддаленої роботи. Цей застосунок для спілкування застосовує наскрізне шифрування для забезпечення відеоконференцій, обміну файлами та голосових викликів.
|
Element — це і безпечний месенджер, і застосунок для співпраці команди, який ідеально підходить спілкування групами під час віддаленої роботи. Цей застосунок для спілкування використовує наскрізне шифрування для забезпечення відеоконференцій, обміну файлами та голосових викликів.
|
||||||
|
|
||||||
<b>Можливості Element включають:</b>
|
<b>Можливості Element включають:</b>
|
||||||
- Розширені засоби спілкування в Інтернеті
|
- Розширені засоби онлайн-спілкування
|
||||||
- Повністю зашифровані повідомлення для надання можливості безпечнішого корпоративного спілкування, навіть для віддалених працівників
|
- Повністю зашифровані повідомлення для надання можливості безпечнішого корпоративного спілкування, навіть для віддалених працівників
|
||||||
- Децентралізований чат на основі відкритого коду Matrix
|
- Децентралізований чат на основі відкритого коду Matrix
|
||||||
- Безпечний обмін файлами із зашифрованими даними для керування проєктами
|
- Безпечний обмін файлами із зашифрованими даними для керування проєктами
|
||||||
|
@ -33,10 +33,10 @@ Element надає такі можливості на вибір:
|
||||||
Справжнє наскрізне шифрування (лише учасники бесіди можуть розшифровувати повідомлення) та взаємне підписування пристроїв.
|
Справжнє наскрізне шифрування (лише учасники бесіди можуть розшифровувати повідомлення) та взаємне підписування пристроїв.
|
||||||
|
|
||||||
<b>Повноцінні спілкування та інтеграція</b>
|
<b>Повноцінні спілкування та інтеграція</b>
|
||||||
Обмін повідомленнями, голосові та відеовиклики, обмін файлами, спільний доступ до екрана та ціла купа інтеграцій, ботів та розширень. Створюйте кімнати, спільноти, залишайтеся на зв’язку та виконуйте завдання.
|
Обмін повідомленнями, голосові та відеовиклики, обмін файлами, спільний доступ до екрана та ціла купа інтеграцій, ботів та віджетів. Створюйте кімнати, спільноти, залишайтеся на зв’язку та виконуйте завдання.
|
||||||
|
|
||||||
<b>Продовжуйте, де зупинилися</b>
|
<b>Продовжуйте, де зупинилися</b>
|
||||||
Залишайтеся на зв'язку, де б ви не знаходились, з повністю синхронізованою історією повідомлень на всіх своїх пристроях та в Інтернеті за адресою https://app.element.io
|
Залишайтеся на зв'язку, де б ви не знаходились, з повністю синхронізованою історією повідомлень на всіх своїх пристроях та в Інтернеті за адресою https://app.element.io
|
||||||
|
|
||||||
<b>Відкритий код</b>
|
<b>Відкритий код</b>
|
||||||
Element для Android це проєкт з відкритим кодом, розміщений GitHub. Будь ласка, повідомте про помилки та/або сприяйте його розвитку на https://github.com/vector-im/element-android
|
Element для Android — це проєкт з відкритим кодом, розміщений на GitHub. Повідомляйте про помилки та/або допомагайте його розвитку на https://github.com/vector-im/element-android
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
此版本的主要变化:主要关于通知的错误修复。
|
此版本的主要变化:主要关于通知的错误修复。
|
||||||
完整更新日志:https://github.com/vector-im/element-android/releases/tag/v1.3.7
|
完整更新日志:https://github.com/vector-im/element-android/releases/tag/v1.3.7-RC2
|
||||||
|
|
2
fastlane/metadata/android/zh-CN/changelogs/40103080.txt
Normal file
2
fastlane/metadata/android/zh-CN/changelogs/40103080.txt
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
此版本主要变化:Bug 修复!
|
||||||
|
完整更新日志:https://github.com/vector-im/element-android/releases/tag/v1.3.8
|
|
@ -1,2 +1,2 @@
|
||||||
此版本中的主要變動:主要關於通知的臭蟲修復。
|
此版本中的主要變動:主要關於通知的臭蟲修復。
|
||||||
完整的變更紀錄:https://github.com/vector-im/element-android/releases/tag/v1.3.7
|
完整的變更紀錄:https://github.com/vector-im/element-android/releases/tag/v1.3.7-RC2
|
||||||
|
|
2
fastlane/metadata/android/zh-TW/changelogs/40103080.txt
Normal file
2
fastlane/metadata/android/zh-TW/changelogs/40103080.txt
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
此版本中的主要變動:臭蟲修復!
|
||||||
|
完整的變更紀錄:https://github.com/vector-im/element-android/releases/tag/v1.3.8
|
4
gradle/wrapper/gradle-wrapper.properties
vendored
4
gradle/wrapper/gradle-wrapper.properties
vendored
|
@ -1,6 +1,6 @@
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionSha256Sum=b75392c5625a88bccd58a574552a5a323edca82dab5942d2d41097f809c6bcce
|
distributionSha256Sum=c9490e938b221daf0094982288e4038deed954a3f12fb54cbf270ddf4e37d879
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.1-all.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-all.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
|
6
library/ui-styles/src/main/res/values-sw600dp/tablet.xml
Normal file
6
library/ui-styles/src/main/res/values-sw600dp/tablet.xml
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
|
||||||
|
<dimen name="width_percent">0.6</dimen>
|
||||||
|
|
||||||
|
</resources>
|
6
library/ui-styles/src/main/res/values-sw720dp/tablet.xml
Normal file
6
library/ui-styles/src/main/res/values-sw720dp/tablet.xml
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
|
||||||
|
<dimen name="width_percent">0.5</dimen>
|
||||||
|
|
||||||
|
</resources>
|
|
@ -42,4 +42,9 @@
|
||||||
|
|
||||||
<!-- Preview Url -->
|
<!-- Preview Url -->
|
||||||
<dimen name="preview_url_view_corner_radius">8dp</dimen>
|
<dimen name="preview_url_view_corner_radius">8dp</dimen>
|
||||||
|
|
||||||
|
<!-- Composer -->
|
||||||
|
<dimen name="composer_min_height">56dp</dimen>
|
||||||
|
<dimen name="composer_attachment_size">52dp</dimen>
|
||||||
|
<dimen name="composer_attachment_margin">1dp</dimen>
|
||||||
</resources>
|
</resources>
|
6
library/ui-styles/src/main/res/values/tablet.xml
Normal file
6
library/ui-styles/src/main/res/values/tablet.xml
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
|
||||||
|
<dimen name="width_percent">1</dimen>
|
||||||
|
|
||||||
|
</resources>
|
|
@ -152,6 +152,13 @@ class FlowSession(private val session: Session) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun liveUserAccountData(type: String): Flow<Optional<UserAccountDataEvent>> {
|
||||||
|
return session.accountDataService().getLiveUserAccountDataEvent(type).asFlow()
|
||||||
|
.startWith(session.coroutineDispatchers.io) {
|
||||||
|
session.accountDataService().getUserAccountDataEvent(type).toOptional()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun liveRoomAccountData(types: Set<String>): Flow<List<RoomAccountDataEvent>> {
|
fun liveRoomAccountData(types: Set<String>): Flow<List<RoomAccountDataEvent>> {
|
||||||
return session.accountDataService().getLiveRoomAccountDataEvents(types).asFlow()
|
return session.accountDataService().getLiveRoomAccountDataEvents(types).asFlow()
|
||||||
.startWith(session.coroutineDispatchers.io) {
|
.startWith(session.coroutineDispatchers.io) {
|
||||||
|
|
|
@ -31,7 +31,7 @@ android {
|
||||||
// that the app's state is completely cleared between tests.
|
// that the app's state is completely cleared between tests.
|
||||||
testInstrumentationRunnerArguments clearPackageData: 'true'
|
testInstrumentationRunnerArguments clearPackageData: 'true'
|
||||||
|
|
||||||
buildConfigField "String", "SDK_VERSION", "\"1.3.10\""
|
buildConfigField "String", "SDK_VERSION", "\"1.3.13\""
|
||||||
|
|
||||||
buildConfigField "String", "GIT_SDK_REVISION", "\"${gitRevision()}\""
|
buildConfigField "String", "GIT_SDK_REVISION", "\"${gitRevision()}\""
|
||||||
resValue "string", "git_sdk_revision", "\"${gitRevision()}\""
|
resValue "string", "git_sdk_revision", "\"${gitRevision()}\""
|
||||||
|
@ -158,7 +158,7 @@ dependencies {
|
||||||
implementation libs.apache.commonsImaging
|
implementation libs.apache.commonsImaging
|
||||||
|
|
||||||
// Phone number https://github.com/google/libphonenumber
|
// Phone number https://github.com/google/libphonenumber
|
||||||
implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.39'
|
implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.40'
|
||||||
|
|
||||||
testImplementation libs.tests.junit
|
testImplementation libs.tests.junit
|
||||||
testImplementation 'org.robolectric:robolectric:4.7.3'
|
testImplementation 'org.robolectric:robolectric:4.7.3'
|
||||||
|
|
|
@ -104,6 +104,8 @@ object EventType {
|
||||||
|
|
||||||
// Poll
|
// Poll
|
||||||
const val POLL_START = "org.matrix.msc3381.poll.start"
|
const val POLL_START = "org.matrix.msc3381.poll.start"
|
||||||
|
const val POLL_RESPONSE = "org.matrix.msc3381.poll.response"
|
||||||
|
const val POLL_END = "org.matrix.msc3381.poll.end"
|
||||||
|
|
||||||
// Unwedging
|
// Unwedging
|
||||||
internal const val DUMMY = "m.dummy"
|
internal const val DUMMY = "m.dummy"
|
||||||
|
|
|
@ -24,25 +24,24 @@ import com.squareup.moshi.JsonClass
|
||||||
*/
|
*/
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
data class PollSummaryContent(
|
data class PollSummaryContent(
|
||||||
// Index of my vote
|
var myVote: String? = null,
|
||||||
var myVote: Int? = null,
|
|
||||||
// Array of VoteInfo, list is constructed so that there is only one vote by user
|
// Array of VoteInfo, list is constructed so that there is only one vote by user
|
||||||
// And that optionIndex is valid
|
// And that optionIndex is valid
|
||||||
var votes: List<VoteInfo>? = null
|
var votes: List<VoteInfo>? = null,
|
||||||
) {
|
var votesSummary: Map<String, VoteSummary>? = null,
|
||||||
|
var totalVotes: Int = 0,
|
||||||
|
var winnerVoteCount: Int = 0
|
||||||
|
)
|
||||||
|
|
||||||
fun voteCount(): Int {
|
@JsonClass(generateAdapter = true)
|
||||||
return votes?.size ?: 0
|
data class VoteSummary(
|
||||||
}
|
val total: Int = 0,
|
||||||
|
val percentage: Double = 0.0
|
||||||
fun voteCountForOption(optionIndex: Int): Int {
|
)
|
||||||
return votes?.filter { it.optionIndex == optionIndex }?.count() ?: 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
data class VoteInfo(
|
data class VoteInfo(
|
||||||
val userId: String,
|
val userId: String,
|
||||||
val optionIndex: Int,
|
val option: String,
|
||||||
val voteTimestamp: Long
|
val voteTimestamp: Long
|
||||||
)
|
)
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.matrix.android.sdk.api.session.room.model.message
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class representing the org.matrix.msc3381.poll.end event content
|
||||||
|
*/
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
data class MessageEndPollContent(
|
||||||
|
@Json(name = "m.relates_to") val relatesTo: RelationDefaultContent? = null
|
||||||
|
)
|
|
@ -1,40 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package org.matrix.android.sdk.api.session.room.model.message
|
|
||||||
|
|
||||||
import com.squareup.moshi.Json
|
|
||||||
import com.squareup.moshi.JsonClass
|
|
||||||
import org.matrix.android.sdk.api.session.events.model.Content
|
|
||||||
import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent
|
|
||||||
|
|
||||||
// Possible values for optionType
|
|
||||||
const val OPTION_TYPE_POLL = "org.matrix.poll"
|
|
||||||
const val OPTION_TYPE_BUTTONS = "org.matrix.buttons"
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Polls and bot buttons are m.room.message events with a msgtype of m.options,
|
|
||||||
* Ref: https://github.com/matrix-org/matrix-doc/pull/2192
|
|
||||||
*/
|
|
||||||
@JsonClass(generateAdapter = true)
|
|
||||||
data class MessageOptionsContent(
|
|
||||||
@Json(name = MessageContent.MSG_TYPE_JSON_KEY) override val msgType: String = MessageType.MSGTYPE_OPTIONS,
|
|
||||||
@Json(name = "type") val optionType: String? = null,
|
|
||||||
@Json(name = "body") override val body: String,
|
|
||||||
@Json(name = "label") val label: String?,
|
|
||||||
@Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null,
|
|
||||||
@Json(name = "options") val options: List<OptionItem>? = null,
|
|
||||||
@Json(name = "m.new_content") override val newContent: Content? = null
|
|
||||||
) : MessageContent
|
|
|
@ -18,8 +18,18 @@ package org.matrix.android.sdk.api.session.room.model.message
|
||||||
|
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.Content
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
data class MessagePollContent(
|
data class MessagePollContent(
|
||||||
@Json(name = "org.matrix.msc3381.poll.start") val pollCreationInfo: PollCreationInfo? = null
|
/**
|
||||||
)
|
* Local message type, not from server
|
||||||
|
*/
|
||||||
|
@Transient
|
||||||
|
override val msgType: String = MessageType.MSGTYPE_POLL_START,
|
||||||
|
@Json(name = "body") override val body: String = "",
|
||||||
|
@Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null,
|
||||||
|
@Json(name = "m.new_content") override val newContent: Content? = null,
|
||||||
|
@Json(name = "org.matrix.msc3381.poll.start") val pollCreationInfo: PollCreationInfo? = null
|
||||||
|
) : MessageContent
|
||||||
|
|
|
@ -21,13 +21,15 @@ import com.squareup.moshi.JsonClass
|
||||||
import org.matrix.android.sdk.api.session.events.model.Content
|
import org.matrix.android.sdk.api.session.events.model.Content
|
||||||
import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent
|
import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent
|
||||||
|
|
||||||
/**
|
|
||||||
* Ref: https://github.com/matrix-org/matrix-doc/pull/2192
|
|
||||||
*/
|
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
data class MessagePollResponseContent(
|
data class MessagePollResponseContent(
|
||||||
@Json(name = MessageContent.MSG_TYPE_JSON_KEY) override val msgType: String = MessageType.MSGTYPE_RESPONSE,
|
/**
|
||||||
@Json(name = "body") override val body: String,
|
* Local message type, not from server
|
||||||
|
*/
|
||||||
|
@Transient
|
||||||
|
override val msgType: String = MessageType.MSGTYPE_POLL_RESPONSE,
|
||||||
|
@Json(name = "body") override val body: String = "",
|
||||||
@Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null,
|
@Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null,
|
||||||
@Json(name = "m.new_content") override val newContent: Content? = null
|
@Json(name = "m.new_content") override val newContent: Content? = null,
|
||||||
|
@Json(name = "org.matrix.msc3381.poll.response") val response: PollResponse? = null
|
||||||
) : MessageContent
|
) : MessageContent
|
||||||
|
|
|
@ -25,15 +25,18 @@ object MessageType {
|
||||||
const val MSGTYPE_VIDEO = "m.video"
|
const val MSGTYPE_VIDEO = "m.video"
|
||||||
const val MSGTYPE_LOCATION = "m.location"
|
const val MSGTYPE_LOCATION = "m.location"
|
||||||
const val MSGTYPE_FILE = "m.file"
|
const val MSGTYPE_FILE = "m.file"
|
||||||
const val MSGTYPE_OPTIONS = "org.matrix.options"
|
|
||||||
const val MSGTYPE_RESPONSE = "org.matrix.response"
|
|
||||||
const val MSGTYPE_POLL_CLOSED = "org.matrix.poll_closed"
|
|
||||||
const val MSGTYPE_VERIFICATION_REQUEST = "m.key.verification.request"
|
const val MSGTYPE_VERIFICATION_REQUEST = "m.key.verification.request"
|
||||||
|
|
||||||
// Add, in local, a fake message type in order to StickerMessage can inherit Message class
|
// Add, in local, a fake message type in order to StickerMessage can inherit Message class
|
||||||
// Because sticker isn't a message type but a event type without msgtype field
|
// Because sticker isn't a message type but a event type without msgtype field
|
||||||
const val MSGTYPE_STICKER_LOCAL = "org.matrix.android.sdk.sticker"
|
const val MSGTYPE_STICKER_LOCAL = "org.matrix.android.sdk.sticker"
|
||||||
|
|
||||||
|
// Fake message types for poll events to be able to inherit them from MessageContent
|
||||||
|
// Because poll events are not message events and they don't hanve msgtype field
|
||||||
|
const val MSGTYPE_POLL_START = "org.matrix.android.sdk.poll.start"
|
||||||
|
const val MSGTYPE_POLL_RESPONSE = "org.matrix.android.sdk.poll.response"
|
||||||
|
|
||||||
const val MSGTYPE_CONFETTI = "nic.custom.confetti"
|
const val MSGTYPE_CONFETTI = "nic.custom.confetti"
|
||||||
const val MSGTYPE_SNOWFALL = "io.element.effect.snowfall"
|
const val MSGTYPE_SNOWFALL = "io.element.effect.snowfall"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
* Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||||
*
|
*
|
||||||
* 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.
|
||||||
|
@ -19,11 +19,7 @@ package org.matrix.android.sdk.api.session.room.model.message
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
/**
|
|
||||||
* Ref: https://github.com/matrix-org/matrix-doc/pull/2192
|
|
||||||
*/
|
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
data class OptionItem(
|
data class PollResponse(
|
||||||
@Json(name = "label") val label: String?,
|
@Json(name = "answers") val answers: List<String>? = null
|
||||||
@Json(name = "value") val value: String?
|
|
||||||
)
|
)
|
|
@ -91,11 +91,17 @@ interface SendService {
|
||||||
/**
|
/**
|
||||||
* Method to send a poll response.
|
* Method to send a poll response.
|
||||||
* @param pollEventId the poll currently replied to
|
* @param pollEventId the poll currently replied to
|
||||||
* @param optionIndex The reply index
|
* @param answerId The id of the answer
|
||||||
* @param optionValue The option value (for compatibility)
|
|
||||||
* @return a [Cancelable]
|
* @return a [Cancelable]
|
||||||
*/
|
*/
|
||||||
fun sendOptionsReply(pollEventId: String, optionIndex: Int, optionValue: String): Cancelable
|
fun voteToPoll(pollEventId: String, answerId: String): Cancelable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* End a poll in the room.
|
||||||
|
* @param pollEventId event id of the poll
|
||||||
|
* @return a [Cancelable]
|
||||||
|
*/
|
||||||
|
fun endPoll(pollEventId: String): Cancelable
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Redact (delete) the given event.
|
* Redact (delete) the given event.
|
||||||
|
|
|
@ -32,6 +32,7 @@ object RoomSummaryConstants {
|
||||||
EventType.CALL_ANSWER,
|
EventType.CALL_ANSWER,
|
||||||
EventType.ENCRYPTED,
|
EventType.ENCRYPTED,
|
||||||
EventType.STICKER,
|
EventType.STICKER,
|
||||||
EventType.REACTION
|
EventType.REACTION,
|
||||||
|
EventType.POLL_START
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,6 +27,7 @@ import org.matrix.android.sdk.api.session.events.model.toModel
|
||||||
import org.matrix.android.sdk.api.session.room.model.EventAnnotationsSummary
|
import org.matrix.android.sdk.api.session.room.model.EventAnnotationsSummary
|
||||||
import org.matrix.android.sdk.api.session.room.model.ReadReceipt
|
import org.matrix.android.sdk.api.session.room.model.ReadReceipt
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.MessageContent
|
import org.matrix.android.sdk.api.session.room.model.message.MessageContent
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.MessageStickerContent
|
import org.matrix.android.sdk.api.session.room.model.message.MessageStickerContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.MessageTextContent
|
import org.matrix.android.sdk.api.session.room.model.message.MessageTextContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent
|
import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent
|
||||||
|
@ -130,10 +131,10 @@ fun TimelineEvent.getEditedEventId(): String? {
|
||||||
* Get last MessageContent, after a possible edition
|
* Get last MessageContent, after a possible edition
|
||||||
*/
|
*/
|
||||||
fun TimelineEvent.getLastMessageContent(): MessageContent? {
|
fun TimelineEvent.getLastMessageContent(): MessageContent? {
|
||||||
return if (root.getClearType() == EventType.STICKER) {
|
return when (root.getClearType()) {
|
||||||
root.getClearContent().toModel<MessageStickerContent>()
|
EventType.STICKER -> root.getClearContent().toModel<MessageStickerContent>()
|
||||||
} else {
|
EventType.POLL_START -> root.getClearContent().toModel<MessagePollContent>()
|
||||||
(annotations?.editSummary?.latestContent ?: root.getClearContent()).toModel()
|
else -> (annotations?.editSummary?.latestContent ?: root.getClearContent()).toModel()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -58,6 +58,14 @@ data class SyncResponse(
|
||||||
@Json(name = "device_one_time_keys_count")
|
@Json(name = "device_one_time_keys_count")
|
||||||
val deviceOneTimeKeysCount: DeviceOneTimeKeysCountSyncResponse? = null,
|
val deviceOneTimeKeysCount: DeviceOneTimeKeysCountSyncResponse? = null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The key algorithms for which the server has an unused fallback key for the device.
|
||||||
|
* If the client wants the server to have a fallback key for a given key algorithm,
|
||||||
|
* but that algorithm is not listed in device_unused_fallback_key_types, the client will upload a new key.
|
||||||
|
*/
|
||||||
|
@Json(name = "org.matrix.msc2732.device_unused_fallback_key_types")
|
||||||
|
val deviceUnusedFallbackKeyTypes: List<String>? = null,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List of groups.
|
* List of groups.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -16,6 +16,8 @@
|
||||||
|
|
||||||
package org.matrix.android.sdk.api.session.terms
|
package org.matrix.android.sdk.api.session.terms
|
||||||
|
|
||||||
|
import org.matrix.android.sdk.internal.session.terms.TermsResponse
|
||||||
|
|
||||||
interface TermsService {
|
interface TermsService {
|
||||||
enum class ServiceType {
|
enum class ServiceType {
|
||||||
IntegrationManager,
|
IntegrationManager,
|
||||||
|
@ -28,4 +30,10 @@ interface TermsService {
|
||||||
baseUrl: String,
|
baseUrl: String,
|
||||||
agreedUrls: List<String>,
|
agreedUrls: List<String>,
|
||||||
token: String?)
|
token: String?)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the homeserver terms, from the register API.
|
||||||
|
* Will be updated once https://github.com/matrix-org/matrix-doc/pull/3012 will be implemented.
|
||||||
|
*/
|
||||||
|
suspend fun getHomeserverTerms(baseUrl: String): TermsResponse
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,6 +67,7 @@ import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo
|
||||||
import org.matrix.android.sdk.internal.crypto.model.ImportRoomKeysResult
|
import org.matrix.android.sdk.internal.crypto.model.ImportRoomKeysResult
|
||||||
import org.matrix.android.sdk.internal.crypto.model.MXDeviceInfo
|
import org.matrix.android.sdk.internal.crypto.model.MXDeviceInfo
|
||||||
import org.matrix.android.sdk.internal.crypto.model.MXEncryptEventContentResult
|
import org.matrix.android.sdk.internal.crypto.model.MXEncryptEventContentResult
|
||||||
|
import org.matrix.android.sdk.internal.crypto.model.MXKey.Companion.KEY_SIGNED_CURVE_25519_TYPE
|
||||||
import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap
|
import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap
|
||||||
import org.matrix.android.sdk.internal.crypto.model.event.EncryptedEventContent
|
import org.matrix.android.sdk.internal.crypto.model.event.EncryptedEventContent
|
||||||
import org.matrix.android.sdk.internal.crypto.model.event.RoomKeyContent
|
import org.matrix.android.sdk.internal.crypto.model.event.RoomKeyContent
|
||||||
|
@ -428,9 +429,27 @@ internal class DefaultCryptoService @Inject constructor(
|
||||||
val currentCount = syncResponse.deviceOneTimeKeysCount.signedCurve25519 ?: 0
|
val currentCount = syncResponse.deviceOneTimeKeysCount.signedCurve25519 ?: 0
|
||||||
oneTimeKeysUploader.updateOneTimeKeyCount(currentCount)
|
oneTimeKeysUploader.updateOneTimeKeyCount(currentCount)
|
||||||
}
|
}
|
||||||
if (isStarted()) {
|
// There is a limit of to_device events returned per sync.
|
||||||
|
// If we are in a case of such limited to_device sync we can't try to generate/upload
|
||||||
|
// new otk now, because there might be some pending olm pre-key to_device messages that would fail if we rotate
|
||||||
|
// the old otk too early. In this case we want to wait for the pending to_device before doing anything
|
||||||
|
// As per spec:
|
||||||
|
// If there is a large queue of send-to-device messages, the server should limit the number sent in each /sync response.
|
||||||
|
// 100 messages is recommended as a reasonable limit.
|
||||||
|
// The limit is not part of the spec, so it's probably safer to handle that when there are no more to_device ( so we are sure
|
||||||
|
// that there are no pending to_device
|
||||||
|
val toDevices = syncResponse.toDevice?.events.orEmpty()
|
||||||
|
if (isStarted() && toDevices.isEmpty()) {
|
||||||
// Make sure we process to-device messages before generating new one-time-keys #2782
|
// Make sure we process to-device messages before generating new one-time-keys #2782
|
||||||
deviceListManager.refreshOutdatedDeviceLists()
|
deviceListManager.refreshOutdatedDeviceLists()
|
||||||
|
// The presence of device_unused_fallback_key_types indicates that the server supports fallback keys.
|
||||||
|
// If there's no unused signed_curve25519 fallback key we need a new one.
|
||||||
|
if (syncResponse.deviceUnusedFallbackKeyTypes != null &&
|
||||||
|
// Generate a fallback key only if the server does not already have an unused fallback key.
|
||||||
|
!syncResponse.deviceUnusedFallbackKeyTypes.contains(KEY_SIGNED_CURVE_25519_TYPE)) {
|
||||||
|
oneTimeKeysUploader.needsNewFallback()
|
||||||
|
}
|
||||||
|
|
||||||
oneTimeKeysUploader.maybeUploadOneTimeKeys()
|
oneTimeKeysUploader.maybeUploadOneTimeKeys()
|
||||||
incomingGossipingRequestManager.processReceivedGossipingRequests()
|
incomingGossipingRequestManager.processReceivedGossipingRequests()
|
||||||
}
|
}
|
||||||
|
@ -928,7 +947,7 @@ internal class DefaultCryptoService @Inject constructor(
|
||||||
signatures = objectSigner.signObject(canonicalJson)
|
signatures = objectSigner.signObject(canonicalJson)
|
||||||
)
|
)
|
||||||
|
|
||||||
val uploadDeviceKeysParams = UploadKeysTask.Params(rest, null)
|
val uploadDeviceKeysParams = UploadKeysTask.Params(rest, null, null)
|
||||||
uploadKeysTask.execute(uploadDeviceKeysParams)
|
uploadKeysTask.execute(uploadDeviceKeysParams)
|
||||||
|
|
||||||
cryptoStore.setDeviceKeysUploaded(true)
|
cryptoStore.setDeviceKeysUploaded(true)
|
||||||
|
|
|
@ -136,6 +136,51 @@ internal class MXOlmDevice @Inject constructor(
|
||||||
return store.getOlmAccount().maxOneTimeKeys()
|
return store.getOlmAccount().maxOneTimeKeys()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an unpublished fallback key
|
||||||
|
* A call to markKeysAsPublished will mark it as published and this
|
||||||
|
* call will return null (until a call to generateFallbackKey is made)
|
||||||
|
*/
|
||||||
|
fun getFallbackKey(): MutableMap<String, MutableMap<String, String>>? {
|
||||||
|
try {
|
||||||
|
return store.getOlmAccount().fallbackKey()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Timber.e("## getFallbackKey() : failed")
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a new fallback key if there is not already
|
||||||
|
* an unpublished one.
|
||||||
|
* @return true if a new key was generated
|
||||||
|
*/
|
||||||
|
fun generateFallbackKeyIfNeeded(): Boolean {
|
||||||
|
try {
|
||||||
|
if (!hasUnpublishedFallbackKey()) {
|
||||||
|
store.getOlmAccount().generateFallbackKey()
|
||||||
|
store.saveOlmAccount()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Timber.e("## generateFallbackKey() : failed")
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun hasUnpublishedFallbackKey(): Boolean {
|
||||||
|
return getFallbackKey()?.get(OlmAccount.JSON_KEY_ONE_TIME_KEY).orEmpty().isNotEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun forgetFallbackKey() {
|
||||||
|
try {
|
||||||
|
store.getOlmAccount().forgetFallbackKey()
|
||||||
|
store.saveOlmAccount()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Timber.e("## forgetFallbackKey() : failed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Release the instance
|
* Release the instance
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
package org.matrix.android.sdk.internal.crypto
|
package org.matrix.android.sdk.internal.crypto
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||||
import org.matrix.android.sdk.internal.crypto.model.MXKey
|
import org.matrix.android.sdk.internal.crypto.model.MXKey
|
||||||
import org.matrix.android.sdk.internal.crypto.model.rest.KeysUploadResponse
|
import org.matrix.android.sdk.internal.crypto.model.rest.KeysUploadResponse
|
||||||
|
@ -28,11 +29,16 @@ import javax.inject.Inject
|
||||||
import kotlin.math.floor
|
import kotlin.math.floor
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
|
|
||||||
|
// The spec recommend a 5mn delay, but due to federation
|
||||||
|
// or server downtime we give it a bit more time (1 hour)
|
||||||
|
const val FALLBACK_KEY_FORGET_DELAY = 60 * 60_000L
|
||||||
|
|
||||||
@SessionScope
|
@SessionScope
|
||||||
internal class OneTimeKeysUploader @Inject constructor(
|
internal class OneTimeKeysUploader @Inject constructor(
|
||||||
private val olmDevice: MXOlmDevice,
|
private val olmDevice: MXOlmDevice,
|
||||||
private val objectSigner: ObjectSigner,
|
private val objectSigner: ObjectSigner,
|
||||||
private val uploadKeysTask: UploadKeysTask
|
private val uploadKeysTask: UploadKeysTask,
|
||||||
|
context: Context
|
||||||
) {
|
) {
|
||||||
// tell if there is a OTK check in progress
|
// tell if there is a OTK check in progress
|
||||||
private var oneTimeKeyCheckInProgress = false
|
private var oneTimeKeyCheckInProgress = false
|
||||||
|
@ -41,6 +47,9 @@ internal class OneTimeKeysUploader @Inject constructor(
|
||||||
private var lastOneTimeKeyCheck: Long = 0
|
private var lastOneTimeKeyCheck: Long = 0
|
||||||
private var oneTimeKeyCount: Int? = null
|
private var oneTimeKeyCount: Int? = null
|
||||||
|
|
||||||
|
// Simple storage to remember when was uploaded the last fallback key
|
||||||
|
private val storage = context.getSharedPreferences("OneTimeKeysUploader_${olmDevice.deviceEd25519Key.hashCode()}", Context.MODE_PRIVATE)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stores the current one_time_key count which will be handled later (in a call of
|
* Stores the current one_time_key count which will be handled later (in a call of
|
||||||
* _onSyncCompleted). The count is e.g. coming from a /sync response.
|
* _onSyncCompleted). The count is e.g. coming from a /sync response.
|
||||||
|
@ -51,6 +60,15 @@ internal class OneTimeKeysUploader @Inject constructor(
|
||||||
oneTimeKeyCount = currentCount
|
oneTimeKeyCount = currentCount
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun needsNewFallback() {
|
||||||
|
if (olmDevice.generateFallbackKeyIfNeeded()) {
|
||||||
|
// As we generated a new one, it's already forgetting one
|
||||||
|
// so we can clear the last publish time
|
||||||
|
// (in case the network calls fails after to avoid calling forgetKey)
|
||||||
|
saveLastFallbackKeyPublishTime(0L)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if the OTK must be uploaded.
|
* Check if the OTK must be uploaded.
|
||||||
*/
|
*/
|
||||||
|
@ -65,9 +83,19 @@ internal class OneTimeKeysUploader @Inject constructor(
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
lastOneTimeKeyCheck = System.currentTimeMillis()
|
|
||||||
oneTimeKeyCheckInProgress = true
|
oneTimeKeyCheckInProgress = true
|
||||||
|
|
||||||
|
val oneTimeKeyCountFromSync = oneTimeKeyCount
|
||||||
|
?: fetchOtkCount() // we don't have count from sync so get from server
|
||||||
|
?: return Unit.also {
|
||||||
|
oneTimeKeyCheckInProgress = false
|
||||||
|
Timber.w("maybeUploadOneTimeKeys: Failed to get otk count from server")
|
||||||
|
}
|
||||||
|
|
||||||
|
Timber.d("maybeUploadOneTimeKeys: otk count $oneTimeKeyCountFromSync , unpublished fallback key ${olmDevice.hasUnpublishedFallbackKey()}")
|
||||||
|
|
||||||
|
lastOneTimeKeyCheck = System.currentTimeMillis()
|
||||||
|
|
||||||
// We then check how many keys we can store in the Account object.
|
// We then check how many keys we can store in the Account object.
|
||||||
val maxOneTimeKeys = olmDevice.getMaxNumberOfOneTimeKeys()
|
val maxOneTimeKeys = olmDevice.getMaxNumberOfOneTimeKeys()
|
||||||
|
|
||||||
|
@ -78,37 +106,37 @@ internal class OneTimeKeysUploader @Inject constructor(
|
||||||
// discard the oldest private keys first. This will eventually clean
|
// discard the oldest private keys first. This will eventually clean
|
||||||
// out stale private keys that won't receive a message.
|
// out stale private keys that won't receive a message.
|
||||||
val keyLimit = floor(maxOneTimeKeys / 2.0).toInt()
|
val keyLimit = floor(maxOneTimeKeys / 2.0).toInt()
|
||||||
if (oneTimeKeyCount == null) {
|
|
||||||
// Ask the server how many otk he has
|
// We need to keep a pool of one time public keys on the server so that
|
||||||
oneTimeKeyCount = fetchOtkCount()
|
// other devices can start conversations with us. But we can only store
|
||||||
}
|
// a finite number of private keys in the olm Account object.
|
||||||
val oneTimeKeyCountFromSync = oneTimeKeyCount
|
// To complicate things further then can be a delay between a device
|
||||||
if (oneTimeKeyCountFromSync != null) {
|
// claiming a public one time key from the server and it sending us a
|
||||||
// We need to keep a pool of one time public keys on the server so that
|
// message. We need to keep the corresponding private key locally until
|
||||||
// other devices can start conversations with us. But we can only store
|
// we receive the message.
|
||||||
// a finite number of private keys in the olm Account object.
|
// But that message might never arrive leaving us stuck with duff
|
||||||
// To complicate things further then can be a delay between a device
|
// private keys clogging up our local storage.
|
||||||
// claiming a public one time key from the server and it sending us a
|
// So we need some kind of engineering compromise to balance all of
|
||||||
// message. We need to keep the corresponding private key locally until
|
// these factors.
|
||||||
// we receive the message.
|
tryOrNull("Unable to upload OTK") {
|
||||||
// But that message might never arrive leaving us stuck with duff
|
val uploadedKeys = uploadOTK(oneTimeKeyCountFromSync, keyLimit)
|
||||||
// private keys clogging up our local storage.
|
Timber.v("## uploadKeys() : success, $uploadedKeys key(s) sent")
|
||||||
// So we need some kind of engineering compromise to balance all of
|
|
||||||
// these factors.
|
|
||||||
tryOrNull("Unable to upload OTK") {
|
|
||||||
val uploadedKeys = uploadOTK(oneTimeKeyCountFromSync, keyLimit)
|
|
||||||
Timber.v("## uploadKeys() : success, $uploadedKeys key(s) sent")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Timber.w("maybeUploadOneTimeKeys: waiting to know the number of OTK from the sync")
|
|
||||||
lastOneTimeKeyCheck = 0
|
|
||||||
}
|
}
|
||||||
oneTimeKeyCheckInProgress = false
|
oneTimeKeyCheckInProgress = false
|
||||||
|
|
||||||
|
// Check if we need to forget a fallback key
|
||||||
|
val latestPublishedTime = getLastFallbackKeyPublishTime()
|
||||||
|
if (latestPublishedTime != 0L && System.currentTimeMillis() - latestPublishedTime > FALLBACK_KEY_FORGET_DELAY) {
|
||||||
|
// This should be called once you are reasonably certain that you will not receive any more messages
|
||||||
|
// that use the old fallback key
|
||||||
|
Timber.d("## forgetFallbackKey()")
|
||||||
|
olmDevice.forgetFallbackKey()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun fetchOtkCount(): Int? {
|
private suspend fun fetchOtkCount(): Int? {
|
||||||
return tryOrNull("Unable to get OTK count") {
|
return tryOrNull("Unable to get OTK count") {
|
||||||
val result = uploadKeysTask.execute(UploadKeysTask.Params(null, null))
|
val result = uploadKeysTask.execute(UploadKeysTask.Params(null, null, null))
|
||||||
result.oneTimeKeyCountsForAlgorithm(MXKey.KEY_SIGNED_CURVE_25519_TYPE)
|
result.oneTimeKeyCountsForAlgorithm(MXKey.KEY_SIGNED_CURVE_25519_TYPE)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -121,24 +149,47 @@ internal class OneTimeKeysUploader @Inject constructor(
|
||||||
* @return the number of uploaded keys
|
* @return the number of uploaded keys
|
||||||
*/
|
*/
|
||||||
private suspend fun uploadOTK(keyCount: Int, keyLimit: Int): Int {
|
private suspend fun uploadOTK(keyCount: Int, keyLimit: Int): Int {
|
||||||
if (keyLimit <= keyCount) {
|
if (keyLimit <= keyCount && !olmDevice.hasUnpublishedFallbackKey()) {
|
||||||
// If we don't need to generate any more keys then we are done.
|
// If we don't need to generate any more keys then we are done.
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
val keysThisLoop = min(keyLimit - keyCount, ONE_TIME_KEY_GENERATION_MAX_NUMBER)
|
var keysThisLoop = 0
|
||||||
olmDevice.generateOneTimeKeys(keysThisLoop)
|
if (keyLimit > keyCount) {
|
||||||
|
// Creating keys can be an expensive operation so we limit the
|
||||||
|
// number we generate in one go to avoid blocking the application
|
||||||
|
// for too long.
|
||||||
|
keysThisLoop = min(keyLimit - keyCount, ONE_TIME_KEY_GENERATION_MAX_NUMBER)
|
||||||
|
olmDevice.generateOneTimeKeys(keysThisLoop)
|
||||||
|
}
|
||||||
|
|
||||||
|
// We check before sending if there is an unpublished key in order to saveLastFallbackKeyPublishTime if needed
|
||||||
|
val hadUnpublishedFallbackKey = olmDevice.hasUnpublishedFallbackKey()
|
||||||
val response = uploadOneTimeKeys(olmDevice.getOneTimeKeys())
|
val response = uploadOneTimeKeys(olmDevice.getOneTimeKeys())
|
||||||
olmDevice.markKeysAsPublished()
|
olmDevice.markKeysAsPublished()
|
||||||
|
if (hadUnpublishedFallbackKey) {
|
||||||
|
// It had an unpublished fallback key that was published just now
|
||||||
|
saveLastFallbackKeyPublishTime(System.currentTimeMillis())
|
||||||
|
}
|
||||||
|
|
||||||
if (response.hasOneTimeKeyCountsForAlgorithm(MXKey.KEY_SIGNED_CURVE_25519_TYPE)) {
|
if (response.hasOneTimeKeyCountsForAlgorithm(MXKey.KEY_SIGNED_CURVE_25519_TYPE)) {
|
||||||
// Maybe upload other keys
|
// Maybe upload other keys
|
||||||
return keysThisLoop + uploadOTK(response.oneTimeKeyCountsForAlgorithm(MXKey.KEY_SIGNED_CURVE_25519_TYPE), keyLimit)
|
return keysThisLoop +
|
||||||
|
uploadOTK(response.oneTimeKeyCountsForAlgorithm(MXKey.KEY_SIGNED_CURVE_25519_TYPE), keyLimit) +
|
||||||
|
(if (hadUnpublishedFallbackKey) 1 else 0)
|
||||||
} else {
|
} else {
|
||||||
Timber.e("## uploadOTK() : response for uploading keys does not contain one_time_key_counts.signed_curve25519")
|
Timber.e("## uploadOTK() : response for uploading keys does not contain one_time_key_counts.signed_curve25519")
|
||||||
throw Exception("response for uploading keys does not contain one_time_key_counts.signed_curve25519")
|
throw Exception("response for uploading keys does not contain one_time_key_counts.signed_curve25519")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun saveLastFallbackKeyPublishTime(timeMillis: Long) {
|
||||||
|
storage.edit().putLong("last_fb_key_publish", timeMillis).apply()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getLastFallbackKeyPublishTime(): Long {
|
||||||
|
return storage.getLong("last_fb_key_publish", 0)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Upload curve25519 one time keys.
|
* Upload curve25519 one time keys.
|
||||||
*/
|
*/
|
||||||
|
@ -159,10 +210,26 @@ internal class OneTimeKeysUploader @Inject constructor(
|
||||||
oneTimeJson["signed_curve25519:$key_id"] = k
|
oneTimeJson["signed_curve25519:$key_id"] = k
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val fallbackJson = mutableMapOf<String, Any>()
|
||||||
|
val fallbackCurve25519Map = olmDevice.getFallbackKey()?.get(OlmAccount.JSON_KEY_ONE_TIME_KEY).orEmpty()
|
||||||
|
fallbackCurve25519Map.forEach { (key_id, key) ->
|
||||||
|
val k = mutableMapOf<String, Any>()
|
||||||
|
k["key"] = key
|
||||||
|
k["fallback"] = true
|
||||||
|
val canonicalJson = JsonCanonicalizer.getCanonicalJson(Map::class.java, k)
|
||||||
|
k["signatures"] = objectSigner.signObject(canonicalJson)
|
||||||
|
|
||||||
|
fallbackJson["signed_curve25519:$key_id"] = k
|
||||||
|
}
|
||||||
|
|
||||||
// For now, we set the device id explicitly, as we may not be using the
|
// For now, we set the device id explicitly, as we may not be using the
|
||||||
// same one as used in login.
|
// same one as used in login.
|
||||||
val uploadParams = UploadKeysTask.Params(null, oneTimeJson)
|
val uploadParams = UploadKeysTask.Params(
|
||||||
return uploadKeysTask.execute(uploadParams)
|
deviceKeys = null,
|
||||||
|
oneTimeKeys = oneTimeJson,
|
||||||
|
fallbackKeys = fallbackJson.takeIf { fallbackJson.isNotEmpty() }
|
||||||
|
)
|
||||||
|
return uploadKeysTask.executeRetry(uploadParams, 3)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
@ -173,6 +240,6 @@ internal class OneTimeKeysUploader @Inject constructor(
|
||||||
private const val ONE_TIME_KEY_GENERATION_MAX_NUMBER = 5
|
private const val ONE_TIME_KEY_GENERATION_MAX_NUMBER = 5
|
||||||
|
|
||||||
// frequency with which to check & upload one-time keys
|
// frequency with which to check & upload one-time keys
|
||||||
private const val ONE_TIME_KEY_UPLOAD_PERIOD = (60 * 1000).toLong() // one minute
|
private const val ONE_TIME_KEY_UPLOAD_PERIOD = (60_000).toLong() // one minute
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,5 +40,12 @@ internal data class KeysUploadBody(
|
||||||
* May be absent if no new one-time keys are required.
|
* May be absent if no new one-time keys are required.
|
||||||
*/
|
*/
|
||||||
@Json(name = "one_time_keys")
|
@Json(name = "one_time_keys")
|
||||||
val oneTimeKeys: JsonDict? = null
|
val oneTimeKeys: JsonDict? = null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the user had previously uploaded a fallback key for a given algorithm, it is replaced.
|
||||||
|
* The server will only keep one fallback key per algorithm for each user.
|
||||||
|
*/
|
||||||
|
@Json(name = "org.matrix.msc2732.fallback_keys")
|
||||||
|
val fallbackKeys: JsonDict? = null
|
||||||
)
|
)
|
||||||
|
|
|
@ -32,7 +32,8 @@ internal interface UploadKeysTask : Task<UploadKeysTask.Params, KeysUploadRespon
|
||||||
// the device keys to send.
|
// the device keys to send.
|
||||||
val deviceKeys: DeviceKeys?,
|
val deviceKeys: DeviceKeys?,
|
||||||
// the one-time keys to send.
|
// the one-time keys to send.
|
||||||
val oneTimeKeys: JsonDict?
|
val oneTimeKeys: JsonDict?,
|
||||||
|
val fallbackKeys: JsonDict?
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,7 +45,8 @@ internal class DefaultUploadKeysTask @Inject constructor(
|
||||||
override suspend fun execute(params: UploadKeysTask.Params): KeysUploadResponse {
|
override suspend fun execute(params: UploadKeysTask.Params): KeysUploadResponse {
|
||||||
val body = KeysUploadBody(
|
val body = KeysUploadBody(
|
||||||
deviceKeys = params.deviceKeys,
|
deviceKeys = params.deviceKeys,
|
||||||
oneTimeKeys = params.oneTimeKeys
|
oneTimeKeys = params.oneTimeKeys,
|
||||||
|
fallbackKeys = params.fallbackKeys
|
||||||
)
|
)
|
||||||
|
|
||||||
Timber.i("## Uploading device keys -> $body")
|
Timber.i("## Uploading device keys -> $body")
|
||||||
|
|
|
@ -25,7 +25,6 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageFileContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.MessageImageContent
|
import org.matrix.android.sdk.api.session.room.model.message.MessageImageContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.MessageLocationContent
|
import org.matrix.android.sdk.api.session.room.model.message.MessageLocationContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.MessageNoticeContent
|
import org.matrix.android.sdk.api.session.room.model.message.MessageNoticeContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.MessageOptionsContent
|
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.MessagePollResponseContent
|
import org.matrix.android.sdk.api.session.room.model.message.MessagePollResponseContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.MessageTextContent
|
import org.matrix.android.sdk.api.session.room.model.message.MessageTextContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.MessageType
|
import org.matrix.android.sdk.api.session.room.model.message.MessageType
|
||||||
|
@ -57,8 +56,7 @@ object MoshiProvider {
|
||||||
.registerSubtype(MessageLocationContent::class.java, MessageType.MSGTYPE_LOCATION)
|
.registerSubtype(MessageLocationContent::class.java, MessageType.MSGTYPE_LOCATION)
|
||||||
.registerSubtype(MessageFileContent::class.java, MessageType.MSGTYPE_FILE)
|
.registerSubtype(MessageFileContent::class.java, MessageType.MSGTYPE_FILE)
|
||||||
.registerSubtype(MessageVerificationRequestContent::class.java, MessageType.MSGTYPE_VERIFICATION_REQUEST)
|
.registerSubtype(MessageVerificationRequestContent::class.java, MessageType.MSGTYPE_VERIFICATION_REQUEST)
|
||||||
.registerSubtype(MessageOptionsContent::class.java, MessageType.MSGTYPE_OPTIONS)
|
.registerSubtype(MessagePollResponseContent::class.java, MessageType.MSGTYPE_POLL_RESPONSE)
|
||||||
.registerSubtype(MessagePollResponseContent::class.java, MessageType.MSGTYPE_RESPONSE)
|
|
||||||
)
|
)
|
||||||
.add(SerializeNulls.JSON_ADAPTER_FACTORY)
|
.add(SerializeNulls.JSON_ADAPTER_FACTORY)
|
||||||
.build()
|
.build()
|
||||||
|
|
|
@ -109,18 +109,23 @@ internal class FileUploader @Inject constructor(
|
||||||
filename: String?,
|
filename: String?,
|
||||||
mimeType: String?,
|
mimeType: String?,
|
||||||
progressListener: ProgressRequestBody.Listener? = null): ContentUploadResponse {
|
progressListener: ProgressRequestBody.Listener? = null): ContentUploadResponse {
|
||||||
val inputStream = withContext(Dispatchers.IO) {
|
val workingFile = context.copyUriToTempFile(uri)
|
||||||
context.contentResolver.openInputStream(uri)
|
|
||||||
} ?: throw FileNotFoundException()
|
|
||||||
val workingFile = temporaryFileCreator.create()
|
|
||||||
workingFile.outputStream().use {
|
|
||||||
inputStream.copyTo(it)
|
|
||||||
}
|
|
||||||
return uploadFile(workingFile, filename, mimeType, progressListener).also {
|
return uploadFile(workingFile, filename, mimeType, progressListener).also {
|
||||||
tryOrNull { workingFile.delete() }
|
tryOrNull { workingFile.delete() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private suspend fun Context.copyUriToTempFile(uri: Uri): File {
|
||||||
|
return withContext(Dispatchers.IO) {
|
||||||
|
val inputStream = contentResolver.openInputStream(uri) ?: throw FileNotFoundException()
|
||||||
|
val workingFile = temporaryFileCreator.create()
|
||||||
|
workingFile.outputStream().use {
|
||||||
|
inputStream.copyTo(it)
|
||||||
|
}
|
||||||
|
workingFile
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun upload(uploadBody: RequestBody,
|
private suspend fun upload(uploadBody: RequestBody,
|
||||||
filename: String?,
|
filename: String?,
|
||||||
progressListener: ProgressRequestBody.Listener?): ContentUploadResponse {
|
progressListener: ProgressRequestBody.Listener?): ContentUploadResponse {
|
||||||
|
|
|
@ -56,6 +56,7 @@ internal class DefaultProcessEventForPushTask @Inject constructor(
|
||||||
|
|
||||||
val allEvents = (newJoinEvents + inviteEvents).filter { event ->
|
val allEvents = (newJoinEvents + inviteEvents).filter { event ->
|
||||||
when (event.type) {
|
when (event.type) {
|
||||||
|
EventType.POLL_START,
|
||||||
EventType.MESSAGE,
|
EventType.MESSAGE,
|
||||||
EventType.REDACTION,
|
EventType.REDACTION,
|
||||||
EventType.ENCRYPTED,
|
EventType.ENCRYPTED,
|
||||||
|
|
|
@ -68,7 +68,7 @@ internal class DefaultProfileService @Inject constructor(private val taskExecuto
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun updateAvatar(userId: String, newAvatarUri: Uri, fileName: String) {
|
override suspend fun updateAvatar(userId: String, newAvatarUri: Uri, fileName: String) {
|
||||||
withContext(coroutineDispatchers.main) {
|
withContext(coroutineDispatchers.io) {
|
||||||
val response = fileUploader.uploadFromUri(newAvatarUri, fileName, MimeTypes.Jpeg)
|
val response = fileUploader.uploadFromUri(newAvatarUri, fileName, MimeTypes.Jpeg)
|
||||||
setAvatarUrlTask.execute(SetAvatarUrlTask.Params(userId = userId, newAvatarUrl = response.contentUri))
|
setAvatarUrlTask.execute(SetAvatarUrlTask.Params(userId = userId, newAvatarUrl = response.contentUri))
|
||||||
userStore.updateAvatar(userId, response.contentUri)
|
userStore.updateAvatar(userId, response.contentUri)
|
||||||
|
|
|
@ -17,20 +17,27 @@ package org.matrix.android.sdk.internal.session.room
|
||||||
|
|
||||||
import io.realm.Realm
|
import io.realm.Realm
|
||||||
import org.matrix.android.sdk.api.crypto.VerificationState
|
import org.matrix.android.sdk.api.crypto.VerificationState
|
||||||
|
import org.matrix.android.sdk.api.extensions.orFalse
|
||||||
|
import org.matrix.android.sdk.api.query.QueryStringValue
|
||||||
import org.matrix.android.sdk.api.session.events.model.AggregatedAnnotation
|
import org.matrix.android.sdk.api.session.events.model.AggregatedAnnotation
|
||||||
import org.matrix.android.sdk.api.session.events.model.Event
|
import org.matrix.android.sdk.api.session.events.model.Event
|
||||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||||
import org.matrix.android.sdk.api.session.events.model.LocalEcho
|
import org.matrix.android.sdk.api.session.events.model.LocalEcho
|
||||||
import org.matrix.android.sdk.api.session.events.model.RelationType
|
import org.matrix.android.sdk.api.session.events.model.RelationType
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.getRelationContent
|
||||||
import org.matrix.android.sdk.api.session.events.model.toContent
|
import org.matrix.android.sdk.api.session.events.model.toContent
|
||||||
import org.matrix.android.sdk.api.session.events.model.toModel
|
import org.matrix.android.sdk.api.session.events.model.toModel
|
||||||
import org.matrix.android.sdk.api.session.room.model.PollSummaryContent
|
import org.matrix.android.sdk.api.session.room.model.PollSummaryContent
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.ReferencesAggregatedContent
|
import org.matrix.android.sdk.api.session.room.model.ReferencesAggregatedContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.VoteInfo
|
import org.matrix.android.sdk.api.session.room.model.VoteInfo
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.VoteSummary
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.MessageContent
|
import org.matrix.android.sdk.api.session.room.model.message.MessageContent
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.message.MessageEndPollContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.MessagePollResponseContent
|
import org.matrix.android.sdk.api.session.room.model.message.MessagePollResponseContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.MessageRelationContent
|
import org.matrix.android.sdk.api.session.room.model.message.MessageRelationContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.relation.ReactionContent
|
import org.matrix.android.sdk.api.session.room.model.relation.ReactionContent
|
||||||
|
import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper
|
||||||
import org.matrix.android.sdk.internal.crypto.model.event.EncryptedEventContent
|
import org.matrix.android.sdk.internal.crypto.model.event.EncryptedEventContent
|
||||||
import org.matrix.android.sdk.internal.crypto.verification.toState
|
import org.matrix.android.sdk.internal.crypto.verification.toState
|
||||||
import org.matrix.android.sdk.internal.database.mapper.ContentMapper
|
import org.matrix.android.sdk.internal.database.mapper.ContentMapper
|
||||||
|
@ -50,11 +57,13 @@ import org.matrix.android.sdk.internal.database.query.getOrCreate
|
||||||
import org.matrix.android.sdk.internal.database.query.where
|
import org.matrix.android.sdk.internal.database.query.where
|
||||||
import org.matrix.android.sdk.internal.di.UserId
|
import org.matrix.android.sdk.internal.di.UserId
|
||||||
import org.matrix.android.sdk.internal.session.EventInsertLiveProcessor
|
import org.matrix.android.sdk.internal.session.EventInsertLiveProcessor
|
||||||
|
import org.matrix.android.sdk.internal.session.room.state.StateEventDataSource
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal class EventRelationsAggregationProcessor @Inject constructor(
|
internal class EventRelationsAggregationProcessor @Inject constructor(
|
||||||
@UserId private val userId: String
|
@UserId private val userId: String,
|
||||||
|
private val stateEventDataSource: StateEventDataSource
|
||||||
) : EventInsertLiveProcessor {
|
) : EventInsertLiveProcessor {
|
||||||
|
|
||||||
private val allowedTypes = listOf(
|
private val allowedTypes = listOf(
|
||||||
|
@ -69,7 +78,9 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
|
||||||
// TODO Add ?
|
// TODO Add ?
|
||||||
// EventType.KEY_VERIFICATION_READY,
|
// EventType.KEY_VERIFICATION_READY,
|
||||||
EventType.KEY_VERIFICATION_KEY,
|
EventType.KEY_VERIFICATION_KEY,
|
||||||
EventType.ENCRYPTED
|
EventType.ENCRYPTED,
|
||||||
|
EventType.POLL_RESPONSE,
|
||||||
|
EventType.POLL_END
|
||||||
)
|
)
|
||||||
|
|
||||||
override fun shouldProcess(eventId: String, eventType: String, insertType: EventInsertType): Boolean {
|
override fun shouldProcess(eventId: String, eventType: String, insertType: EventInsertType): Boolean {
|
||||||
|
@ -107,9 +118,6 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
|
||||||
Timber.v("###REPLACE in room $roomId for event ${event.eventId}")
|
Timber.v("###REPLACE in room $roomId for event ${event.eventId}")
|
||||||
// A replace!
|
// A replace!
|
||||||
handleReplace(realm, event, content, roomId, isLocalEcho)
|
handleReplace(realm, event, content, roomId, isLocalEcho)
|
||||||
} else if (content?.relatesTo?.type == RelationType.RESPONSE) {
|
|
||||||
Timber.v("###RESPONSE in room $roomId for event ${event.eventId}")
|
|
||||||
handleResponse(realm, event, content, roomId, isLocalEcho)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -139,9 +147,11 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
|
||||||
Timber.v("###REPLACE in room $roomId for event ${event.eventId}")
|
Timber.v("###REPLACE in room $roomId for event ${event.eventId}")
|
||||||
// A replace!
|
// A replace!
|
||||||
handleReplace(realm, event, it, roomId, isLocalEcho, encryptedEventContent.relatesTo.eventId)
|
handleReplace(realm, event, it, roomId, isLocalEcho, encryptedEventContent.relatesTo.eventId)
|
||||||
} else if (encryptedEventContent.relatesTo.type == RelationType.RESPONSE) {
|
} else if (event.getClearType() == EventType.POLL_RESPONSE) {
|
||||||
Timber.v("###RESPONSE in room $roomId for event ${event.eventId}")
|
event.getClearContent().toModel<MessagePollResponseContent>(catchError = true)?.let { pollResponseContent ->
|
||||||
handleResponse(realm, event, it, roomId, isLocalEcho, encryptedEventContent.relatesTo.eventId)
|
Timber.v("###RESPONSE in room $roomId for event ${event.eventId}")
|
||||||
|
handleResponse(realm, event, pollResponseContent, roomId, isLocalEcho, encryptedEventContent.relatesTo.eventId)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (encryptedEventContent?.relatesTo?.type == RelationType.REFERENCE) {
|
} else if (encryptedEventContent?.relatesTo?.type == RelationType.REFERENCE) {
|
||||||
|
@ -158,6 +168,16 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
|
||||||
handleVerification(realm, event, roomId, isLocalEcho, it)
|
handleVerification(realm, event, roomId, isLocalEcho, it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
EventType.POLL_RESPONSE -> {
|
||||||
|
event.getClearContent().toModel<MessagePollResponseContent>(catchError = true)?.let {
|
||||||
|
handleResponse(realm, event, it, roomId, isLocalEcho, event.getRelationContent()?.eventId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EventType.POLL_END -> {
|
||||||
|
event.content.toModel<MessageEndPollContent>(catchError = true)?.let {
|
||||||
|
handleEndPoll(realm, event, it, roomId, isLocalEcho)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if (encryptedEventContent?.relatesTo?.type == RelationType.ANNOTATION) {
|
} else if (encryptedEventContent?.relatesTo?.type == RelationType.ANNOTATION) {
|
||||||
// Reaction
|
// Reaction
|
||||||
|
@ -188,6 +208,16 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
EventType.POLL_RESPONSE -> {
|
||||||
|
event.content.toModel<MessagePollResponseContent>(catchError = true)?.let {
|
||||||
|
handleResponse(realm, event, it, roomId, isLocalEcho)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EventType.POLL_END -> {
|
||||||
|
event.content.toModel<MessageEndPollContent>(catchError = true)?.let {
|
||||||
|
handleEndPoll(realm, event, it, roomId, isLocalEcho)
|
||||||
|
}
|
||||||
|
}
|
||||||
else -> Timber.v("UnHandled event ${event.eventId}")
|
else -> Timber.v("UnHandled event ${event.eventId}")
|
||||||
}
|
}
|
||||||
} catch (t: Throwable) {
|
} catch (t: Throwable) {
|
||||||
|
@ -276,7 +306,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
|
||||||
|
|
||||||
private fun handleResponse(realm: Realm,
|
private fun handleResponse(realm: Realm,
|
||||||
event: Event,
|
event: Event,
|
||||||
content: MessageContent,
|
content: MessagePollResponseContent,
|
||||||
roomId: String,
|
roomId: String,
|
||||||
isLocalEcho: Boolean,
|
isLocalEcho: Boolean,
|
||||||
relatedEventId: String? = null) {
|
relatedEventId: String? = null) {
|
||||||
|
@ -321,11 +351,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val responseContent = event.content.toModel<MessagePollResponseContent>() ?: return Unit.also {
|
val option = content.response?.answers?.first() ?: return Unit.also {
|
||||||
Timber.d("## POLL Receiving malformed response eventId:$eventId content: ${event.content}")
|
|
||||||
}
|
|
||||||
|
|
||||||
val optionIndex = responseContent.relatesTo?.option ?: return Unit.also {
|
|
||||||
Timber.d("## POLL Ignoring malformed response no option eventId:$eventId content: ${event.content}")
|
Timber.d("## POLL Ignoring malformed response no option eventId:$eventId content: ${event.content}")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -336,22 +362,36 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
|
||||||
val existingVote = votes[existingVoteIndex]
|
val existingVote = votes[existingVoteIndex]
|
||||||
if (existingVote.voteTimestamp < eventTimestamp) {
|
if (existingVote.voteTimestamp < eventTimestamp) {
|
||||||
// Take the new one
|
// Take the new one
|
||||||
votes[existingVoteIndex] = VoteInfo(senderId, optionIndex, eventTimestamp)
|
votes[existingVoteIndex] = VoteInfo(senderId, option, eventTimestamp)
|
||||||
if (userId == senderId) {
|
if (userId == senderId) {
|
||||||
sumModel.myVote = optionIndex
|
sumModel.myVote = option
|
||||||
}
|
}
|
||||||
Timber.v("## POLL adding vote $optionIndex for user $senderId in poll :$targetEventId ")
|
Timber.v("## POLL adding vote $option for user $senderId in poll :$targetEventId ")
|
||||||
} else {
|
} else {
|
||||||
Timber.v("## POLL Ignoring vote (older than known one) eventId:$eventId ")
|
Timber.v("## POLL Ignoring vote (older than known one) eventId:$eventId ")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
votes.add(VoteInfo(senderId, optionIndex, eventTimestamp))
|
votes.add(VoteInfo(senderId, option, eventTimestamp))
|
||||||
if (userId == senderId) {
|
if (userId == senderId) {
|
||||||
sumModel.myVote = optionIndex
|
sumModel.myVote = option
|
||||||
}
|
}
|
||||||
Timber.v("## POLL adding vote $optionIndex for user $senderId in poll :$targetEventId ")
|
Timber.v("## POLL adding vote $option for user $senderId in poll :$targetEventId ")
|
||||||
}
|
}
|
||||||
sumModel.votes = votes
|
sumModel.votes = votes
|
||||||
|
|
||||||
|
// Precompute the percentage of votes for all options
|
||||||
|
val totalVotes = votes.size
|
||||||
|
sumModel.totalVotes = totalVotes
|
||||||
|
sumModel.votesSummary = votes
|
||||||
|
.groupBy({ it.option }, { it.userId })
|
||||||
|
.mapValues {
|
||||||
|
VoteSummary(
|
||||||
|
total = it.value.size,
|
||||||
|
percentage = if (totalVotes == 0 && it.value.isEmpty()) 0.0 else it.value.size.toDouble() / totalVotes
|
||||||
|
)
|
||||||
|
}
|
||||||
|
sumModel.winnerVoteCount = sumModel.votesSummary?.maxOf { it.value.total } ?: 0
|
||||||
|
|
||||||
if (isLocalEcho) {
|
if (isLocalEcho) {
|
||||||
existingPollSummary.sourceLocalEchoEvents.add(eventId)
|
existingPollSummary.sourceLocalEchoEvents.add(eventId)
|
||||||
} else {
|
} else {
|
||||||
|
@ -361,6 +401,51 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
|
||||||
existingPollSummary.aggregatedContent = ContentMapper.map(sumModel.toContent())
|
existingPollSummary.aggregatedContent = ContentMapper.map(sumModel.toContent())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun handleEndPoll(realm: Realm,
|
||||||
|
event: Event,
|
||||||
|
content: MessageEndPollContent,
|
||||||
|
roomId: String,
|
||||||
|
isLocalEcho: Boolean) {
|
||||||
|
val pollEventId = content.relatesTo?.eventId ?: return
|
||||||
|
|
||||||
|
var existing = EventAnnotationsSummaryEntity.where(realm, roomId, pollEventId).findFirst()
|
||||||
|
if (existing == null) {
|
||||||
|
Timber.v("## POLL creating new relation summary for $pollEventId")
|
||||||
|
existing = EventAnnotationsSummaryEntity.create(realm, roomId, pollEventId)
|
||||||
|
}
|
||||||
|
|
||||||
|
// we have it
|
||||||
|
val existingPollSummary = existing.pollResponseSummary
|
||||||
|
?: realm.createObject(PollResponseAggregatedSummaryEntity::class.java).also {
|
||||||
|
existing.pollResponseSummary = it
|
||||||
|
}
|
||||||
|
|
||||||
|
if (existingPollSummary.closedTime != null) {
|
||||||
|
Timber.v("## Received poll.end event for already ended poll $pollEventId")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val powerLevelsHelper = stateEventDataSource.getStateEvent(roomId, EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.NoCondition)
|
||||||
|
?.content?.toModel<PowerLevelsContent>()
|
||||||
|
?.let { PowerLevelsHelper(it) }
|
||||||
|
if (!powerLevelsHelper?.isUserAbleToRedact(event.senderId ?: "").orFalse()) {
|
||||||
|
Timber.v("## Received poll.end event $pollEventId but user ${event.senderId} doesn't have enough power level in room $roomId")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val txId = event.unsignedData?.transactionId
|
||||||
|
// is it a remote echo?
|
||||||
|
if (!isLocalEcho && existingPollSummary.sourceLocalEchoEvents.contains(txId)) {
|
||||||
|
// ok it has already been managed
|
||||||
|
Timber.v("## POLL Receiving remote echo of response eventId:$pollEventId")
|
||||||
|
existingPollSummary.sourceLocalEchoEvents.remove(txId)
|
||||||
|
existingPollSummary.sourceEvents.add(event.eventId)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
existingPollSummary.closedTime = event.originServerTs
|
||||||
|
}
|
||||||
|
|
||||||
private fun handleInitialAggregatedRelations(realm: Realm,
|
private fun handleInitialAggregatedRelations(realm: Realm,
|
||||||
event: Event,
|
event: Event,
|
||||||
roomId: String,
|
roomId: String,
|
||||||
|
|
|
@ -70,7 +70,8 @@ internal class RedactionEventProcessor @Inject constructor() : EventInsertLivePr
|
||||||
} else {
|
} else {
|
||||||
when (typeToPrune) {
|
when (typeToPrune) {
|
||||||
EventType.ENCRYPTED,
|
EventType.ENCRYPTED,
|
||||||
EventType.MESSAGE -> {
|
EventType.MESSAGE,
|
||||||
|
EventType.POLL_START -> {
|
||||||
Timber.d("REDACTION for message ${eventToPrune.eventId}")
|
Timber.d("REDACTION for message ${eventToPrune.eventId}")
|
||||||
val unsignedData = EventMapper.map(eventToPrune).unsignedData
|
val unsignedData = EventMapper.map(eventToPrune).unsignedData
|
||||||
?: UnsignedData(null, null)
|
?: UnsignedData(null, null)
|
||||||
|
|
|
@ -103,8 +103,14 @@ internal class DefaultSendService @AssistedInject constructor(
|
||||||
.let { sendEvent(it) }
|
.let { sendEvent(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun sendOptionsReply(pollEventId: String, optionIndex: Int, optionValue: String): Cancelable {
|
override fun voteToPoll(pollEventId: String, answerId: String): Cancelable {
|
||||||
return localEchoEventFactory.createOptionsReplyEvent(roomId, pollEventId, optionIndex, optionValue)
|
return localEchoEventFactory.createPollReplyEvent(roomId, pollEventId, answerId)
|
||||||
|
.also { createLocalEcho(it) }
|
||||||
|
.let { sendEvent(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun endPoll(pollEventId: String): Cancelable {
|
||||||
|
return localEchoEventFactory.createEndPollEvent(roomId, pollEventId)
|
||||||
.also { createLocalEcho(it) }
|
.also { createLocalEcho(it) }
|
||||||
.let { sendEvent(it) }
|
.let { sendEvent(it) }
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,6 +35,7 @@ import org.matrix.android.sdk.api.session.room.model.message.ImageInfo
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.MessageAudioContent
|
import org.matrix.android.sdk.api.session.room.model.message.MessageAudioContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.MessageContent
|
import org.matrix.android.sdk.api.session.room.model.message.MessageContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.MessageContentWithFormattedBody
|
import org.matrix.android.sdk.api.session.room.model.message.MessageContentWithFormattedBody
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.message.MessageEndPollContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.MessageFileContent
|
import org.matrix.android.sdk.api.session.room.model.message.MessageFileContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.MessageFormat
|
import org.matrix.android.sdk.api.session.room.model.message.MessageFormat
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.MessageImageContent
|
import org.matrix.android.sdk.api.session.room.model.message.MessageImageContent
|
||||||
|
@ -46,6 +47,7 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageVideoContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.PollAnswer
|
import org.matrix.android.sdk.api.session.room.model.message.PollAnswer
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.PollCreationInfo
|
import org.matrix.android.sdk.api.session.room.model.message.PollCreationInfo
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.PollQuestion
|
import org.matrix.android.sdk.api.session.room.model.message.PollQuestion
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.message.PollResponse
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.ThumbnailInfo
|
import org.matrix.android.sdk.api.session.room.model.message.ThumbnailInfo
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.VideoInfo
|
import org.matrix.android.sdk.api.session.room.model.message.VideoInfo
|
||||||
import org.matrix.android.sdk.api.session.room.model.relation.ReactionContent
|
import org.matrix.android.sdk.api.session.room.model.relation.ReactionContent
|
||||||
|
@ -122,19 +124,28 @@ internal class LocalEchoEventFactory @Inject constructor(
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun createOptionsReplyEvent(roomId: String,
|
fun createPollReplyEvent(roomId: String,
|
||||||
pollEventId: String,
|
pollEventId: String,
|
||||||
optionIndex: Int,
|
answerId: String): Event {
|
||||||
optionLabel: String): Event {
|
val content = MessagePollResponseContent(
|
||||||
return createMessageEvent(roomId,
|
body = answerId,
|
||||||
MessagePollResponseContent(
|
relatesTo = RelationDefaultContent(
|
||||||
body = optionLabel,
|
type = RelationType.REFERENCE,
|
||||||
relatesTo = RelationDefaultContent(
|
eventId = pollEventId),
|
||||||
type = RelationType.RESPONSE,
|
response = PollResponse(
|
||||||
option = optionIndex,
|
answers = listOf(answerId)
|
||||||
eventId = pollEventId)
|
)
|
||||||
|
|
||||||
))
|
)
|
||||||
|
val localId = LocalEcho.createLocalEchoId()
|
||||||
|
return Event(
|
||||||
|
roomId = roomId,
|
||||||
|
originServerTs = dummyOriginServerTs(),
|
||||||
|
senderId = userId,
|
||||||
|
eventId = localId,
|
||||||
|
type = EventType.POLL_RESPONSE,
|
||||||
|
content = content.toContent(),
|
||||||
|
unsignedData = UnsignedData(age = null, transactionId = localId))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun createPollEvent(roomId: String,
|
fun createPollEvent(roomId: String,
|
||||||
|
@ -147,7 +158,7 @@ internal class LocalEchoEventFactory @Inject constructor(
|
||||||
),
|
),
|
||||||
answers = options.mapIndexed { index, option ->
|
answers = options.mapIndexed { index, option ->
|
||||||
PollAnswer(
|
PollAnswer(
|
||||||
id = index.toString(),
|
id = "$index-$option",
|
||||||
answer = option
|
answer = option
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -164,6 +175,25 @@ internal class LocalEchoEventFactory @Inject constructor(
|
||||||
unsignedData = UnsignedData(age = null, transactionId = localId))
|
unsignedData = UnsignedData(age = null, transactionId = localId))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun createEndPollEvent(roomId: String,
|
||||||
|
eventId: String): Event {
|
||||||
|
val content = MessageEndPollContent(
|
||||||
|
relatesTo = RelationDefaultContent(
|
||||||
|
type = RelationType.REFERENCE,
|
||||||
|
eventId = eventId
|
||||||
|
)
|
||||||
|
)
|
||||||
|
val localId = LocalEcho.createLocalEchoId()
|
||||||
|
return Event(
|
||||||
|
roomId = roomId,
|
||||||
|
originServerTs = dummyOriginServerTs(),
|
||||||
|
senderId = userId,
|
||||||
|
eventId = localId,
|
||||||
|
type = EventType.POLL_END,
|
||||||
|
content = content.toContent(),
|
||||||
|
unsignedData = UnsignedData(age = null, transactionId = localId))
|
||||||
|
}
|
||||||
|
|
||||||
fun createReplaceTextOfReply(roomId: String,
|
fun createReplaceTextOfReply(roomId: String,
|
||||||
eventReplaced: TimelineEvent,
|
eventReplaced: TimelineEvent,
|
||||||
originalEvent: TimelineEvent,
|
originalEvent: TimelineEvent,
|
||||||
|
@ -413,7 +443,7 @@ internal class LocalEchoEventFactory @Inject constructor(
|
||||||
when (content?.msgType) {
|
when (content?.msgType) {
|
||||||
MessageType.MSGTYPE_EMOTE,
|
MessageType.MSGTYPE_EMOTE,
|
||||||
MessageType.MSGTYPE_TEXT,
|
MessageType.MSGTYPE_TEXT,
|
||||||
MessageType.MSGTYPE_NOTICE -> {
|
MessageType.MSGTYPE_NOTICE -> {
|
||||||
var formattedText: String? = null
|
var formattedText: String? = null
|
||||||
if (content is MessageContentWithFormattedBody) {
|
if (content is MessageContentWithFormattedBody) {
|
||||||
formattedText = content.matrixFormattedBody
|
formattedText = content.matrixFormattedBody
|
||||||
|
@ -424,11 +454,12 @@ internal class LocalEchoEventFactory @Inject constructor(
|
||||||
TextContent(content.body, formattedText)
|
TextContent(content.body, formattedText)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
MessageType.MSGTYPE_FILE -> return TextContent("sent a file.")
|
MessageType.MSGTYPE_FILE -> return TextContent("sent a file.")
|
||||||
MessageType.MSGTYPE_AUDIO -> return TextContent("sent an audio file.")
|
MessageType.MSGTYPE_AUDIO -> return TextContent("sent an audio file.")
|
||||||
MessageType.MSGTYPE_IMAGE -> return TextContent("sent an image.")
|
MessageType.MSGTYPE_IMAGE -> return TextContent("sent an image.")
|
||||||
MessageType.MSGTYPE_VIDEO -> return TextContent("sent a video.")
|
MessageType.MSGTYPE_VIDEO -> return TextContent("sent a video.")
|
||||||
else -> return TextContent(content?.body ?: "")
|
MessageType.MSGTYPE_POLL_START -> return TextContent((content as? MessagePollContent)?.pollCreationInfo?.question?.question ?: "")
|
||||||
|
else -> return TextContent(content?.body ?: "")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -30,6 +30,7 @@ import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
import kotlinx.coroutines.flow.SharedFlow
|
import kotlinx.coroutines.flow.SharedFlow
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import org.matrix.android.sdk.api.extensions.orFalse
|
||||||
import org.matrix.android.sdk.api.failure.Failure
|
import org.matrix.android.sdk.api.failure.Failure
|
||||||
import org.matrix.android.sdk.api.failure.isTokenError
|
import org.matrix.android.sdk.api.failure.isTokenError
|
||||||
import org.matrix.android.sdk.api.logger.LoggerTag
|
import org.matrix.android.sdk.api.logger.LoggerTag
|
||||||
|
@ -71,6 +72,7 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask,
|
||||||
private var isStarted = false
|
private var isStarted = false
|
||||||
private var isTokenValid = true
|
private var isTokenValid = true
|
||||||
private var retryNoNetworkTask: TimerTask? = null
|
private var retryNoNetworkTask: TimerTask? = null
|
||||||
|
private var previousSyncResponseHasToDevice = false
|
||||||
|
|
||||||
private val activeCallListObserver = Observer<MutableList<MxCall>> { activeCalls ->
|
private val activeCallListObserver = Observer<MutableList<MxCall>> { activeCalls ->
|
||||||
if (activeCalls.isEmpty() && backgroundDetectionObserver.isInBackground) {
|
if (activeCalls.isEmpty() && backgroundDetectionObserver.isInBackground) {
|
||||||
|
@ -171,12 +173,15 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask,
|
||||||
if (state !is SyncState.Running) {
|
if (state !is SyncState.Running) {
|
||||||
updateStateTo(SyncState.Running(afterPause = true))
|
updateStateTo(SyncState.Running(afterPause = true))
|
||||||
}
|
}
|
||||||
// No timeout after a pause
|
val timeout = when {
|
||||||
val timeout = state.let { if (it is SyncState.Running && it.afterPause) 0 else DEFAULT_LONG_POOL_TIMEOUT }
|
previousSyncResponseHasToDevice -> 0L /* Force timeout to 0 */
|
||||||
|
state.let { it is SyncState.Running && it.afterPause } -> 0L /* No timeout after a pause */
|
||||||
|
else -> DEFAULT_LONG_POOL_TIMEOUT
|
||||||
|
}
|
||||||
Timber.tag(loggerTag.value).d("Execute sync request with timeout $timeout")
|
Timber.tag(loggerTag.value).d("Execute sync request with timeout $timeout")
|
||||||
val params = SyncTask.Params(timeout, SyncPresence.Online)
|
val params = SyncTask.Params(timeout, SyncPresence.Online)
|
||||||
val sync = syncScope.launch {
|
val sync = syncScope.launch {
|
||||||
doSync(params)
|
previousSyncResponseHasToDevice = doSync(params)
|
||||||
}
|
}
|
||||||
runBlocking {
|
runBlocking {
|
||||||
sync.join()
|
sync.join()
|
||||||
|
@ -203,10 +208,14 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun doSync(params: SyncTask.Params) {
|
/**
|
||||||
try {
|
* Will return true if the sync response contains some toDevice events.
|
||||||
|
*/
|
||||||
|
private suspend fun doSync(params: SyncTask.Params): Boolean {
|
||||||
|
return try {
|
||||||
val syncResponse = syncTask.execute(params)
|
val syncResponse = syncTask.execute(params)
|
||||||
_syncFlow.emit(syncResponse)
|
_syncFlow.emit(syncResponse)
|
||||||
|
syncResponse.toDevice?.events?.isNotEmpty().orFalse()
|
||||||
} catch (failure: Throwable) {
|
} catch (failure: Throwable) {
|
||||||
if (failure is Failure.NetworkConnection) {
|
if (failure is Failure.NetworkConnection) {
|
||||||
canReachServer = false
|
canReachServer = false
|
||||||
|
@ -229,6 +238,7 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask,
|
||||||
delay(RETRY_WAIT_TIME_MS)
|
delay(RETRY_WAIT_TIME_MS)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
false
|
||||||
} finally {
|
} finally {
|
||||||
state.let {
|
state.let {
|
||||||
if (it is SyncState.Running && it.afterPause) {
|
if (it is SyncState.Running && it.afterPause) {
|
||||||
|
|
|
@ -20,6 +20,7 @@ import androidx.work.BackoffPolicy
|
||||||
import androidx.work.ExistingWorkPolicy
|
import androidx.work.ExistingWorkPolicy
|
||||||
import androidx.work.WorkerParameters
|
import androidx.work.WorkerParameters
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
|
import org.matrix.android.sdk.api.extensions.orFalse
|
||||||
import org.matrix.android.sdk.api.failure.isTokenError
|
import org.matrix.android.sdk.api.failure.isTokenError
|
||||||
import org.matrix.android.sdk.internal.SessionManager
|
import org.matrix.android.sdk.internal.SessionManager
|
||||||
import org.matrix.android.sdk.internal.di.WorkManagerProvider
|
import org.matrix.android.sdk.internal.di.WorkManagerProvider
|
||||||
|
@ -34,8 +35,8 @@ import timber.log.Timber
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
private const val DEFAULT_LONG_POOL_TIMEOUT = 6L
|
private const val DEFAULT_LONG_POOL_TIMEOUT_SECONDS = 6L
|
||||||
private const val DEFAULT_DELAY_TIMEOUT = 30_000L
|
private const val DEFAULT_DELAY_MILLIS = 30_000L
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Possible previous worker: None
|
* Possible previous worker: None
|
||||||
|
@ -47,9 +48,12 @@ internal class SyncWorker(context: Context, workerParameters: WorkerParameters,
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
internal data class Params(
|
internal data class Params(
|
||||||
override val sessionId: String,
|
override val sessionId: String,
|
||||||
val timeout: Long = DEFAULT_LONG_POOL_TIMEOUT,
|
// In seconds
|
||||||
val delay: Long = DEFAULT_DELAY_TIMEOUT,
|
val timeout: Long = DEFAULT_LONG_POOL_TIMEOUT_SECONDS,
|
||||||
|
// In milliseconds
|
||||||
|
val delay: Long = DEFAULT_DELAY_MILLIS,
|
||||||
val periodic: Boolean = false,
|
val periodic: Boolean = false,
|
||||||
|
val forceImmediate: Boolean = false,
|
||||||
override val lastFailureMessage: String? = null
|
override val lastFailureMessage: String? = null
|
||||||
) : SessionWorkerParams
|
) : SessionWorkerParams
|
||||||
|
|
||||||
|
@ -65,13 +69,26 @@ internal class SyncWorker(context: Context, workerParameters: WorkerParameters,
|
||||||
Timber.i("Sync work starting")
|
Timber.i("Sync work starting")
|
||||||
|
|
||||||
return runCatching {
|
return runCatching {
|
||||||
doSync(params.timeout)
|
doSync(if (params.forceImmediate) 0 else params.timeout)
|
||||||
}.fold(
|
}.fold(
|
||||||
{
|
{ hasToDeviceEvents ->
|
||||||
Result.success().also {
|
Result.success().also {
|
||||||
if (params.periodic) {
|
if (params.periodic) {
|
||||||
// we want to schedule another one after delay
|
// we want to schedule another one after a delay, or immediately if hasToDeviceEvents
|
||||||
automaticallyBackgroundSync(workManagerProvider, params.sessionId, params.timeout, params.delay)
|
automaticallyBackgroundSync(
|
||||||
|
workManagerProvider = workManagerProvider,
|
||||||
|
sessionId = params.sessionId,
|
||||||
|
serverTimeoutInSeconds = params.timeout,
|
||||||
|
delayInSeconds = params.delay,
|
||||||
|
forceImmediate = hasToDeviceEvents
|
||||||
|
)
|
||||||
|
} else if (hasToDeviceEvents) {
|
||||||
|
// Previous response has toDevice events, request an immediate sync request
|
||||||
|
requireBackgroundSync(
|
||||||
|
workManagerProvider = workManagerProvider,
|
||||||
|
sessionId = params.sessionId,
|
||||||
|
serverTimeoutInSeconds = 0
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -92,16 +109,29 @@ internal class SyncWorker(context: Context, workerParameters: WorkerParameters,
|
||||||
return params.copy(lastFailureMessage = params.lastFailureMessage ?: message)
|
return params.copy(lastFailureMessage = params.lastFailureMessage ?: message)
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun doSync(timeout: Long) {
|
/**
|
||||||
|
* Will return true if the sync response contains some toDevice events.
|
||||||
|
*/
|
||||||
|
private suspend fun doSync(timeout: Long): Boolean {
|
||||||
val taskParams = SyncTask.Params(timeout * 1000, SyncPresence.Offline)
|
val taskParams = SyncTask.Params(timeout * 1000, SyncPresence.Offline)
|
||||||
syncTask.execute(taskParams)
|
val syncResponse = syncTask.execute(taskParams)
|
||||||
|
return syncResponse.toDevice?.events?.isNotEmpty().orFalse()
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val BG_SYNC_WORK_NAME = "BG_SYNCP"
|
private const val BG_SYNC_WORK_NAME = "BG_SYNCP"
|
||||||
|
|
||||||
fun requireBackgroundSync(workManagerProvider: WorkManagerProvider, sessionId: String, serverTimeout: Long = 0) {
|
fun requireBackgroundSync(workManagerProvider: WorkManagerProvider,
|
||||||
val data = WorkerParamsFactory.toData(Params(sessionId, serverTimeout, 0L, false))
|
sessionId: String,
|
||||||
|
serverTimeoutInSeconds: Long = 0) {
|
||||||
|
val data = WorkerParamsFactory.toData(
|
||||||
|
Params(
|
||||||
|
sessionId = sessionId,
|
||||||
|
timeout = serverTimeoutInSeconds,
|
||||||
|
delay = 0L,
|
||||||
|
periodic = false
|
||||||
|
)
|
||||||
|
)
|
||||||
val workRequest = workManagerProvider.matrixOneTimeWorkRequestBuilder<SyncWorker>()
|
val workRequest = workManagerProvider.matrixOneTimeWorkRequestBuilder<SyncWorker>()
|
||||||
.setConstraints(WorkManagerProvider.workConstraints)
|
.setConstraints(WorkManagerProvider.workConstraints)
|
||||||
.setBackoffCriteria(BackoffPolicy.LINEAR, WorkManagerProvider.BACKOFF_DELAY_MILLIS, TimeUnit.MILLISECONDS)
|
.setBackoffCriteria(BackoffPolicy.LINEAR, WorkManagerProvider.BACKOFF_DELAY_MILLIS, TimeUnit.MILLISECONDS)
|
||||||
|
@ -111,13 +141,24 @@ internal class SyncWorker(context: Context, workerParameters: WorkerParameters,
|
||||||
.enqueueUniqueWork(BG_SYNC_WORK_NAME, ExistingWorkPolicy.APPEND_OR_REPLACE, workRequest)
|
.enqueueUniqueWork(BG_SYNC_WORK_NAME, ExistingWorkPolicy.APPEND_OR_REPLACE, workRequest)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun automaticallyBackgroundSync(workManagerProvider: WorkManagerProvider, sessionId: String, serverTimeout: Long = 0, delayInSeconds: Long = 30) {
|
fun automaticallyBackgroundSync(workManagerProvider: WorkManagerProvider,
|
||||||
val data = WorkerParamsFactory.toData(Params(sessionId, serverTimeout, delayInSeconds, true))
|
sessionId: String,
|
||||||
|
serverTimeoutInSeconds: Long = 0,
|
||||||
|
delayInSeconds: Long = 30,
|
||||||
|
forceImmediate: Boolean = false) {
|
||||||
|
val data = WorkerParamsFactory.toData(
|
||||||
|
Params(
|
||||||
|
sessionId = sessionId,
|
||||||
|
timeout = serverTimeoutInSeconds,
|
||||||
|
delay = delayInSeconds,
|
||||||
|
forceImmediate = forceImmediate
|
||||||
|
)
|
||||||
|
)
|
||||||
val workRequest = workManagerProvider.matrixOneTimeWorkRequestBuilder<SyncWorker>()
|
val workRequest = workManagerProvider.matrixOneTimeWorkRequestBuilder<SyncWorker>()
|
||||||
.setConstraints(WorkManagerProvider.workConstraints)
|
.setConstraints(WorkManagerProvider.workConstraints)
|
||||||
.setInputData(data)
|
.setInputData(data)
|
||||||
.setBackoffCriteria(BackoffPolicy.LINEAR, WorkManagerProvider.BACKOFF_DELAY_MILLIS, TimeUnit.MILLISECONDS)
|
.setBackoffCriteria(BackoffPolicy.LINEAR, WorkManagerProvider.BACKOFF_DELAY_MILLIS, TimeUnit.MILLISECONDS)
|
||||||
.setInitialDelay(delayInSeconds, TimeUnit.SECONDS)
|
.setInitialDelay(if (forceImmediate) 0 else delayInSeconds, TimeUnit.SECONDS)
|
||||||
.build()
|
.build()
|
||||||
// Avoid risking multiple chains of syncs by replacing the existing chain
|
// Avoid risking multiple chains of syncs by replacing the existing chain
|
||||||
workManagerProvider.workManager
|
workManagerProvider.workManager
|
||||||
|
|
|
@ -18,10 +18,13 @@ package org.matrix.android.sdk.internal.session.terms
|
||||||
|
|
||||||
import dagger.Lazy
|
import dagger.Lazy
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
|
import org.matrix.android.sdk.api.auth.data.LoginFlowTypes
|
||||||
|
import org.matrix.android.sdk.api.failure.toRegistrationFlowResponse
|
||||||
import org.matrix.android.sdk.api.session.accountdata.UserAccountDataTypes
|
import org.matrix.android.sdk.api.session.accountdata.UserAccountDataTypes
|
||||||
import org.matrix.android.sdk.api.session.events.model.toModel
|
import org.matrix.android.sdk.api.session.events.model.toModel
|
||||||
import org.matrix.android.sdk.api.session.terms.GetTermsResponse
|
import org.matrix.android.sdk.api.session.terms.GetTermsResponse
|
||||||
import org.matrix.android.sdk.api.session.terms.TermsService
|
import org.matrix.android.sdk.api.session.terms.TermsService
|
||||||
|
import org.matrix.android.sdk.api.util.JsonDict
|
||||||
import org.matrix.android.sdk.internal.di.UnauthenticatedWithCertificate
|
import org.matrix.android.sdk.internal.di.UnauthenticatedWithCertificate
|
||||||
import org.matrix.android.sdk.internal.network.NetworkConstants
|
import org.matrix.android.sdk.internal.network.NetworkConstants
|
||||||
import org.matrix.android.sdk.internal.network.RetrofitFactory
|
import org.matrix.android.sdk.internal.network.RetrofitFactory
|
||||||
|
@ -55,6 +58,27 @@ internal class DefaultTermsService @Inject constructor(
|
||||||
return GetTermsResponse(termsResponse, getAlreadyAcceptedTermUrlsFromAccountData())
|
return GetTermsResponse(termsResponse, getAlreadyAcceptedTermUrlsFromAccountData())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We use a trick here to get the homeserver T&C, we use the register API
|
||||||
|
*/
|
||||||
|
override suspend fun getHomeserverTerms(baseUrl: String): TermsResponse {
|
||||||
|
return try {
|
||||||
|
executeRequest(null) {
|
||||||
|
termsAPI.register(baseUrl + NetworkConstants.URI_API_PREFIX_PATH_R0 + "register")
|
||||||
|
}
|
||||||
|
// Return empty result if it succeed, but it should never happen
|
||||||
|
TermsResponse()
|
||||||
|
} catch (throwable: Throwable) {
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
TermsResponse(
|
||||||
|
policies = (throwable.toRegistrationFlowResponse()
|
||||||
|
?.params
|
||||||
|
?.get(LoginFlowTypes.TERMS) as? JsonDict)
|
||||||
|
?.get("policies") as? JsonDict
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun agreeToTerms(serviceType: TermsService.ServiceType,
|
override suspend fun agreeToTerms(serviceType: TermsService.ServiceType,
|
||||||
baseUrl: String,
|
baseUrl: String,
|
||||||
agreedUrls: List<String>,
|
agreedUrls: List<String>,
|
||||||
|
@ -91,7 +115,7 @@ internal class DefaultTermsService @Inject constructor(
|
||||||
private fun buildUrl(baseUrl: String, serviceType: TermsService.ServiceType): String {
|
private fun buildUrl(baseUrl: String, serviceType: TermsService.ServiceType): String {
|
||||||
val servicePath = when (serviceType) {
|
val servicePath = when (serviceType) {
|
||||||
TermsService.ServiceType.IntegrationManager -> NetworkConstants.URI_INTEGRATION_MANAGER_PATH
|
TermsService.ServiceType.IntegrationManager -> NetworkConstants.URI_INTEGRATION_MANAGER_PATH
|
||||||
TermsService.ServiceType.IdentityService -> NetworkConstants.URI_IDENTITY_PATH_V2
|
TermsService.ServiceType.IdentityService -> NetworkConstants.URI_IDENTITY_PATH_V2
|
||||||
}
|
}
|
||||||
return "${baseUrl.ensureTrailingSlash()}$servicePath"
|
return "${baseUrl.ensureTrailingSlash()}$servicePath"
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,8 @@
|
||||||
|
|
||||||
package org.matrix.android.sdk.internal.session.terms
|
package org.matrix.android.sdk.internal.session.terms
|
||||||
|
|
||||||
|
import org.matrix.android.sdk.api.util.JsonDict
|
||||||
|
import org.matrix.android.sdk.api.util.emptyJsonDict
|
||||||
import org.matrix.android.sdk.internal.network.HttpHeaders
|
import org.matrix.android.sdk.internal.network.HttpHeaders
|
||||||
import retrofit2.http.Body
|
import retrofit2.http.Body
|
||||||
import retrofit2.http.GET
|
import retrofit2.http.GET
|
||||||
|
@ -37,4 +39,12 @@ internal interface TermsAPI {
|
||||||
suspend fun agreeToTerms(@Url url: String,
|
suspend fun agreeToTerms(@Url url: String,
|
||||||
@Body params: AcceptTermsBody,
|
@Body params: AcceptTermsBody,
|
||||||
@Header(HttpHeaders.Authorization) token: String)
|
@Header(HttpHeaders.Authorization) token: String)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API to retrieve the terms for a homeserver. The API /terms does not exist yet, so retrieve the terms from the login flow.
|
||||||
|
* We do not care about the result (Credentials)
|
||||||
|
*/
|
||||||
|
@POST
|
||||||
|
suspend fun register(@Url url: String,
|
||||||
|
@Body body: JsonDict = emptyJsonDict)
|
||||||
}
|
}
|
||||||
|
|
|
@ -160,7 +160,7 @@ Formatter\.formatShortFileSize===1
|
||||||
# android\.text\.TextUtils
|
# android\.text\.TextUtils
|
||||||
|
|
||||||
### This is not a rule, but a warning: the number of "enum class" has changed. For Json classes, it is mandatory that they have `@JsonClass(generateAdapter = false)`. If the enum is not used as a Json class, change the value in file forbidden_strings_in_code.txt
|
### This is not a rule, but a warning: the number of "enum class" has changed. For Json classes, it is mandatory that they have `@JsonClass(generateAdapter = false)`. If the enum is not used as a Json class, change the value in file forbidden_strings_in_code.txt
|
||||||
enum class===110
|
enum class===114
|
||||||
|
|
||||||
### Do not import temporary legacy classes
|
### Do not import temporary legacy classes
|
||||||
import org.matrix.android.sdk.internal.legacy.riot===3
|
import org.matrix.android.sdk.internal.legacy.riot===3
|
||||||
|
|
18
tools/import_analytic_plan.sh
Executable file
18
tools/import_analytic_plan.sh
Executable file
|
@ -0,0 +1,18 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
echo "Deleted existing plan..."
|
||||||
|
rm vector/src/main/java/im/vector/app/features/analytics/plan/*.*
|
||||||
|
|
||||||
|
echo "Cloning analytics project..."
|
||||||
|
mkdir analytics_tmp
|
||||||
|
cd analytics_tmp
|
||||||
|
git clone https://github.com/matrix-org/matrix-analytics-events.git
|
||||||
|
|
||||||
|
echo "Copy plan..."
|
||||||
|
cp matrix-analytics-events/types/kotlin2/* ../vector/src/main/java/im/vector/app/features/analytics/plan/
|
||||||
|
|
||||||
|
echo "Cleanup."
|
||||||
|
cd ..
|
||||||
|
rm -rf analytics_tmp
|
||||||
|
|
||||||
|
echo "Done."
|
|
@ -17,7 +17,7 @@ PARAM_KEYSTORE_PATH=$1
|
||||||
PARAM_APK=$2
|
PARAM_APK=$2
|
||||||
|
|
||||||
# Other params
|
# Other params
|
||||||
BUILD_TOOLS_VERSION="31.0.0-rc5"
|
BUILD_TOOLS_VERSION="31.0.0"
|
||||||
MIN_SDK_VERSION=21
|
MIN_SDK_VERSION=21
|
||||||
|
|
||||||
echo "Signing APK with build-tools version ${BUILD_TOOLS_VERSION} for min SDK version ${MIN_SDK_VERSION}..."
|
echo "Signing APK with build-tools version ${BUILD_TOOLS_VERSION} for min SDK version ${MIN_SDK_VERSION}..."
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue