mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2025-02-01 11:46:57 +01:00
Merge branch 'merge-v1.3.8' into sc
Change-Id: If000613462f3cd3b73c8de24f456dccc16fcde38 Conflicts: vector/src/main/java/im/vector/app/features/home/UnreadMessagesSharedViewModel.kt vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt
This commit is contained in:
commit
c03c6867f7
67
CHANGES.md
67
CHANGES.md
@ -1,3 +1,70 @@
|
||||
Changes in Element v1.3.8 (2021-11-17)
|
||||
======================================
|
||||
|
||||
Features ✨
|
||||
----------
|
||||
- Make notification text spoiler aware ([#3477](https://github.com/vector-im/element-android/issues/3477))
|
||||
- Poll Feature - Create Poll Screen (Disabled for now) ([#4367](https://github.com/vector-im/element-android/issues/4367))
|
||||
- Adds support for images inside message notifications ([#4402](https://github.com/vector-im/element-android/issues/4402))
|
||||
|
||||
Bugfixes 🐛
|
||||
----------
|
||||
- Render markdown in room list ([#452](https://github.com/vector-im/element-android/issues/452))
|
||||
- Fix incorrect cropping of conversation icons ([#4424](https://github.com/vector-im/element-android/issues/4424))
|
||||
- Fix potential NullPointerException crashes in Room and User account data sources ([#4428](https://github.com/vector-im/element-android/issues/4428))
|
||||
- Unable to establish Olm outbound session from fallback key ([#4446](https://github.com/vector-im/element-android/issues/4446))
|
||||
- Fixes intermittent crash on sign out due to the session being incorrectly recreated whilst being closed ([#4480](https://github.com/vector-im/element-android/issues/4480))
|
||||
|
||||
SDK API changes ⚠️
|
||||
------------------
|
||||
- Add content scanner API from MSC1453
|
||||
API documentation : https://github.com/matrix-org/matrix-content-scanner#api ([#4392](https://github.com/vector-im/element-android/issues/4392))
|
||||
- Breaking SDK API change to PushRuleListener, the separated callbacks have been merged into one with a data class which includes all the previously separated push information ([#4401](https://github.com/vector-im/element-android/issues/4401))
|
||||
|
||||
Other changes
|
||||
-------------
|
||||
- Finish migration from RxJava to Flow ([#4219](https://github.com/vector-im/element-android/issues/4219))
|
||||
- Remove redundant text in feature request issue form ([#4257](https://github.com/vector-im/element-android/issues/4257))
|
||||
- Add and improve issue triage workflows ([#4435](https://github.com/vector-im/element-android/issues/4435))
|
||||
- Update issue template to bring in line with element-web ([#4452](https://github.com/vector-im/element-android/issues/4452))
|
||||
|
||||
|
||||
Changes in Element v1.3.7 (2021-11-04)
|
||||
======================================
|
||||
|
||||
Features ✨
|
||||
----------
|
||||
- Adding the room name to the invitation notification (if the room summary is available) ([#582](https://github.com/vector-im/element-android/issues/582))
|
||||
- Updating single sign on providers ordering to match priority/popularity ([#4277](https://github.com/vector-im/element-android/issues/4277))
|
||||
|
||||
Bugfixes 🐛
|
||||
----------
|
||||
- Stops showing a dedicated redacted event notification, the message notifications will update accordingly ([#1491](https://github.com/vector-im/element-android/issues/1491))
|
||||
- Fixes marking individual notifications as read causing other notifications to be dismissed ([#3395](https://github.com/vector-im/element-android/issues/3395))
|
||||
- Fixing missing send button in light mode dev tools - send * event ([#3674](https://github.com/vector-im/element-android/issues/3674))
|
||||
- Fixing room search needing exact casing for non latin-1 character named rooms ([#3968](https://github.com/vector-im/element-android/issues/3968))
|
||||
- Fixing call ringtones only playing once when the ringtone doesn't contain looping metadata (android 9.0 and above) ([#4047](https://github.com/vector-im/element-android/issues/4047))
|
||||
- Tentatively fixing the doubled notifications by updating the group summary at specific points in the notification rendering cycle ([#4152](https://github.com/vector-im/element-android/issues/4152))
|
||||
- Do not show shortcuts if a PIN code is set ([#4170](https://github.com/vector-im/element-android/issues/4170))
|
||||
- Fixes being unable to join rooms by name ([#4255](https://github.com/vector-im/element-android/issues/4255))
|
||||
- Fixing missing F-Droid notifications when in background due to background syncs not triggering ([#4298](https://github.com/vector-im/element-android/issues/4298))
|
||||
- Fix video compression before upload ([#4353](https://github.com/vector-im/element-android/issues/4353))
|
||||
- Fixing QR code crashes caused by a known issue in the zxing library for older versions of android by downgrading to 3.3.3 ([#4361](https://github.com/vector-im/element-android/issues/4361))
|
||||
- Fixing timeline crash when rotating with the emoji window open ([#4365](https://github.com/vector-im/element-android/issues/4365))
|
||||
- Fix handling of links coming from web instance reported as malformed by mistake ([#4369](https://github.com/vector-im/element-android/issues/4369))
|
||||
|
||||
SDK API changes ⚠️
|
||||
------------------
|
||||
- Add API `LoginWizard.loginCustom(data: JsonDict): Session` to be able to login to a homeserver using arbitrary request content ([#4266](https://github.com/vector-im/element-android/issues/4266))
|
||||
- Add optional deviceId to the login API ([#4334](https://github.com/vector-im/element-android/issues/4334))
|
||||
|
||||
Other changes
|
||||
-------------
|
||||
- Migrate app DI framework to Hilt ([#3888](https://github.com/vector-im/element-android/issues/3888))
|
||||
- Limit supported TLS versions and cipher suites ([#4192](https://github.com/vector-im/element-android/issues/4192))
|
||||
- Fixed capitalisation of text on initial sync screen ([#4292](https://github.com/vector-im/element-android/issues/4292))
|
||||
|
||||
|
||||
Changes in Element v1.3.6 (2021-10-26)
|
||||
======================================
|
||||
|
||||
|
@ -17,6 +17,7 @@
|
||||
|
||||
package im.vector.lib.attachmentviewer
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.graphics.Color
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
@ -141,7 +142,12 @@ abstract class AttachmentViewerActivity : AppCompatActivity(), AttachmentEventLi
|
||||
// New API instead of SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN and SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
|
||||
window.setDecorFitsSystemWindows(false)
|
||||
// New API instead of SYSTEM_UI_FLAG_IMMERSIVE
|
||||
window.decorView.windowInsetsController?.systemBarsBehavior = WindowInsetsController.BEHAVIOR_SHOW_BARS_BY_SWIPE
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
window.decorView.windowInsetsController?.systemBarsBehavior = WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
|
||||
} else {
|
||||
@SuppressLint("WrongConstant")
|
||||
window.decorView.windowInsetsController?.systemBarsBehavior = WindowInsetsController.BEHAVIOR_SHOW_BARS_BY_SWIPE
|
||||
}
|
||||
// New API instead of FLAG_TRANSLUCENT_STATUS
|
||||
window.statusBarColor = ContextCompat.getColor(this, R.color.half_transparent_status_bar)
|
||||
// new API instead of FLAG_TRANSLUCENT_NAVIGATION
|
||||
@ -347,7 +353,12 @@ abstract class AttachmentViewerActivity : AppCompatActivity(), AttachmentEventLi
|
||||
// new API instead of SYSTEM_UI_FLAG_HIDE_NAVIGATION
|
||||
window.decorView.windowInsetsController?.hide(WindowInsets.Type.navigationBars())
|
||||
// New API instead of SYSTEM_UI_FLAG_IMMERSIVE
|
||||
window.decorView.windowInsetsController?.systemBarsBehavior = WindowInsetsController.BEHAVIOR_SHOW_BARS_BY_SWIPE
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
window.decorView.windowInsetsController?.systemBarsBehavior = WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
|
||||
} else {
|
||||
@SuppressLint("WrongConstant")
|
||||
window.decorView.windowInsetsController?.systemBarsBehavior = WindowInsetsController.BEHAVIOR_SHOW_BARS_BY_SWIPE
|
||||
}
|
||||
// New API instead of FLAG_TRANSLUCENT_STATUS
|
||||
window.statusBarColor = ContextCompat.getColor(this, R.color.half_transparent_status_bar)
|
||||
// New API instead of FLAG_TRANSLUCENT_NAVIGATION
|
||||
|
@ -17,6 +17,7 @@ buildscript {
|
||||
// https://developer.android.com/studio/releases/gradle-plugin
|
||||
classpath libs.gradle.gradlePlugin
|
||||
classpath libs.gradle.kotlinPlugin
|
||||
classpath libs.gradle.hiltPlugin
|
||||
classpath 'com.google.gms:google-services:4.3.10'
|
||||
classpath 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.3'
|
||||
classpath 'com.google.android.gms:oss-licenses-plugin:0.10.4'
|
||||
|
@ -1,8 +1,8 @@
|
||||
ext.versions = [
|
||||
|
||||
'minSdk' : 21,
|
||||
'compileSdk' : 30,
|
||||
'targetSdk' : 30,
|
||||
'compileSdk' : 31,
|
||||
'targetSdk' : 31,
|
||||
'sourceCompat' : JavaVersion.VERSION_1_8,
|
||||
'targetCompat' : JavaVersion.VERSION_1_8,
|
||||
]
|
||||
@ -11,13 +11,13 @@ def gradle = "7.0.3"
|
||||
// Ref: https://kotlinlang.org/releases.html
|
||||
def kotlin = "1.5.31"
|
||||
def kotlinCoroutines = "1.5.2"
|
||||
def dagger = "2.39.1"
|
||||
def dagger = "2.40.1"
|
||||
def retrofit = "2.9.0"
|
||||
def arrow = "0.8.2"
|
||||
def markwon = "4.6.2"
|
||||
def moshi = "1.12.0"
|
||||
def lifecycle = "2.2.0"
|
||||
def rxBinding = "3.1.0"
|
||||
def lifecycle = "2.4.0"
|
||||
def flowBinding = "1.2.0"
|
||||
def epoxy = "4.6.2"
|
||||
def mavericks = "2.4.0"
|
||||
def glide = "4.12.0"
|
||||
@ -26,7 +26,7 @@ def jjwt = "0.11.2"
|
||||
def vanniktechEmoji = "0.8.0"
|
||||
|
||||
// Testing
|
||||
def mockk = "1.12.0"
|
||||
def mockk = "1.12.1"
|
||||
def espresso = "3.4.0"
|
||||
def androidxTest = "1.4.0"
|
||||
|
||||
@ -34,28 +34,31 @@ def androidxTest = "1.4.0"
|
||||
ext.libs = [
|
||||
gradle : [
|
||||
'gradlePlugin' : "com.android.tools.build:gradle:$gradle",
|
||||
'kotlinPlugin' : "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin"
|
||||
'kotlinPlugin' : "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin",
|
||||
'hiltPlugin' : "com.google.dagger:hilt-android-gradle-plugin:$dagger"
|
||||
|
||||
],
|
||||
jetbrains : [
|
||||
'kotlinReflect' : "org.jetbrains.kotlin:kotlin-reflect:$kotlin",
|
||||
'coroutinesCore' : "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinCoroutines",
|
||||
'coroutinesAndroid' : "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlinCoroutines",
|
||||
'coroutinesRx2' : "org.jetbrains.kotlinx:kotlinx-coroutines-rx2:$kotlinCoroutines"
|
||||
'coroutinesRx2' : "org.jetbrains.kotlinx:kotlinx-coroutines-rx2:$kotlinCoroutines",
|
||||
'coroutinesTest' : "org.jetbrains.kotlinx:kotlinx-coroutines-test:$kotlinCoroutines"
|
||||
],
|
||||
androidx : [
|
||||
'appCompat' : "androidx.appcompat:appcompat:1.3.1",
|
||||
'core' : "androidx.core:core-ktx:1.6.0",
|
||||
'core' : "androidx.core:core-ktx:1.7.0",
|
||||
'recyclerview' : "androidx.recyclerview:recyclerview:1.2.1",
|
||||
'exifinterface' : "androidx.exifinterface:exifinterface:1.3.3",
|
||||
'fragmentKtx' : "androidx.fragment:fragment-ktx:1.3.6",
|
||||
'constraintLayout' : "androidx.constraintlayout:constraintlayout:2.1.1",
|
||||
'work' : "androidx.work:work-runtime-ktx:2.6.0",
|
||||
'work' : "androidx.work:work-runtime-ktx:2.7.0",
|
||||
'autoFill' : "androidx.autofill:autofill:1.1.0",
|
||||
'preferenceKtx' : "androidx.preference:preference-ktx:1.1.1",
|
||||
'junit' : "androidx.test.ext:junit:1.1.3",
|
||||
'lifecycleExtensions' : "androidx.lifecycle:lifecycle-extensions:$lifecycle",
|
||||
'lifecycleJava8' : "androidx.lifecycle:lifecycle-common-java8:$lifecycle",
|
||||
'lifecycleLivedata' : "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1",
|
||||
'lifecycleCommon' : "androidx.lifecycle:lifecycle-common:$lifecycle",
|
||||
'lifecycleLivedata' : "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle",
|
||||
'lifecycleProcess' : "androidx.lifecycle:lifecycle-process:$lifecycle",
|
||||
'datastore' : "androidx.datastore:datastore:1.0.0",
|
||||
'datastorepreferences' : "androidx.datastore:datastore-preferences:1.0.0",
|
||||
'pagingRuntimeKtx' : "androidx.paging:paging-runtime-ktx:2.1.2",
|
||||
@ -73,7 +76,9 @@ ext.libs = [
|
||||
],
|
||||
dagger : [
|
||||
'dagger' : "com.google.dagger:dagger:$dagger",
|
||||
'daggerCompiler' : "com.google.dagger:dagger-compiler:$dagger"
|
||||
'daggerCompiler' : "com.google.dagger:dagger-compiler:$dagger",
|
||||
'hilt' : "com.google.dagger:hilt-android:$dagger",
|
||||
'hiltCompiler' : "com.google.dagger:hilt-compiler:$dagger"
|
||||
],
|
||||
squareup : [
|
||||
'moshi' : "com.squareup.moshi:moshi-adapters:$moshi",
|
||||
@ -100,7 +105,6 @@ ext.libs = [
|
||||
'epoxyProcessor' : "com.airbnb.android:epoxy-processor:$epoxy",
|
||||
'epoxyPaging' : "com.airbnb.android:epoxy-paging:$epoxy",
|
||||
'mavericks' : "com.airbnb.android:mavericks:$mavericks",
|
||||
'mavericksRx' : "com.airbnb.android:mavericks-rxjava2:$mavericks",
|
||||
'mavericksTesting' : "com.airbnb.android:mavericks-testing:$mavericks"
|
||||
],
|
||||
mockk : [
|
||||
@ -113,13 +117,13 @@ ext.libs = [
|
||||
'bigImageViewer' : "com.github.piasy:BigImageViewer:$bigImageViewer",
|
||||
'glideImageLoader' : "com.github.piasy:GlideImageLoader:$bigImageViewer",
|
||||
'progressPieIndicator' : "com.github.piasy:ProgressPieIndicator:$bigImageViewer",
|
||||
'glideImageViewFactory' : "com.github.piasy:GlideImageViewFactory:$bigImageViewer"
|
||||
'glideImageViewFactory' : "com.github.piasy:GlideImageViewFactory:$bigImageViewer",
|
||||
'flowBinding' : "io.github.reactivecircus.flowbinding:flowbinding-android:$flowBinding",
|
||||
'flowBindingAppcompat' : "io.github.reactivecircus.flowbinding:flowbinding-appcompat:$flowBinding",
|
||||
'flowBindingMaterial' : "io.github.reactivecircus.flowbinding:flowbinding-material:$flowBinding"
|
||||
],
|
||||
jakewharton : [
|
||||
'timber' : "com.jakewharton.timber:timber:5.0.1",
|
||||
'rxbinding' : "com.jakewharton.rxbinding3:rxbinding:$rxBinding",
|
||||
'rxbindingAppcompat' : "com.jakewharton.rxbinding3:rxbinding-appcompat:$rxBinding",
|
||||
'rxbindingMaterial' : "com.jakewharton.rxbinding3:rxbinding-material:$rxBinding"
|
||||
'timber' : "com.jakewharton.timber:timber:5.0.1"
|
||||
],
|
||||
jsonwebtoken: [
|
||||
'jjwtApi' : "io.jsonwebtoken:jjwt-api:$jjwt",
|
||||
|
33
docs/hilt_migration.md
Normal file
33
docs/hilt_migration.md
Normal file
@ -0,0 +1,33 @@
|
||||
Useful links:
|
||||
- https://dagger.dev/hilt/migration-guide
|
||||
- https://dagger.dev/hilt/quick-start
|
||||
|
||||
Hilt is built on top of Dagger 2 and simplify usage by removing needs to create components manually.
|
||||
|
||||
When you create a new feature, you should have the following:
|
||||
|
||||
Annotate your Activity with @AndroidEntryPoint
|
||||
If you have a BottomSheetFragment => Annotate it with @AndroidEntryPoint
|
||||
Otherwise => Add your Fragment to the FragmentModule
|
||||
Add your ViewModel.Factory to the MavericksViewModelModule
|
||||
Makes sure your ViewModel as the following code:
|
||||
|
||||
```
|
||||
@AssistedFactory
|
||||
interface Factory: MavericksAssistedViewModelFactory<MyViewModel, MyViewState> {
|
||||
override fun create(initialState: MyViewState): MyViewModel
|
||||
}
|
||||
|
||||
companion object : MavericksViewModelFactory<MyViewModel, MyViewState> by hiltMavericksViewModelFactory()
|
||||
```
|
||||
|
||||
## Some remarks
|
||||
|
||||
@MavericksViewModelScope dependencies can't be injected inside Fragments/Activities
|
||||
You can only inject @Singleton, @MavericksViewModelScope or unscoped dependencies inside Maverick ViewModels
|
||||
You can access some specific dependencies from Singleton component by using
|
||||
```
|
||||
context.singletonEntryPoint()
|
||||
```
|
||||
Be aware that only the app has been migrated to Hilt and not the SDK.
|
||||
|
41
docs/rx_flow_migration.md
Normal file
41
docs/rx_flow_migration.md
Normal file
@ -0,0 +1,41 @@
|
||||
Useful links:
|
||||
- https://github.com/ReactiveCircus/FlowBinding
|
||||
- https://ivanisidrowu.github.io/kotlin/2020/08/09/Kotlin-Flow-Migration-And-Testing.html
|
||||
|
||||
|
||||
Rx is now completely removed from Element dependencies.
|
||||
Some examples of the changes:
|
||||
|
||||
```
|
||||
sharedActionViewModel
|
||||
.observe()
|
||||
.subscribe { handleQuickActions(it) }
|
||||
.disposeOnDestroyView()
|
||||
```
|
||||
|
||||
became
|
||||
|
||||
```
|
||||
sharedActionViewModel
|
||||
.stream()
|
||||
.onEach { handleQuickActions(it) }
|
||||
.launchIn(viewLifecycleOwner.lifecycleScope)
|
||||
|
||||
```
|
||||
|
||||
Inside fragment use
|
||||
```
|
||||
launchIn(viewLifecycleOwner.lifecycleScope)
|
||||
```
|
||||
Inside activity use
|
||||
```
|
||||
launchIn(lifecycleScope)
|
||||
```
|
||||
Inside viewModel use
|
||||
```
|
||||
launchIn(viewModelScope)
|
||||
```
|
||||
|
||||
Also be aware that when using these scopes the coroutine is launched on Dispatchers.Main by default.
|
||||
|
||||
|
4
gradle/wrapper/gradle-wrapper.properties
vendored
4
gradle/wrapper/gradle-wrapper.properties
vendored
@ -1,6 +1,6 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionSha256Sum=a8da5b02437a60819cad23e10fc7e9cf32bcb57029d9cb277e26eeff76ce014b
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-all.zip
|
||||
distributionSha256Sum=00b273629df4ce46e68df232161d5a7c4e495b9a029ce6e0420f071e21316867
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3-all.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:color="?vctr_content_tertiary" android:state_enabled="false" />
|
||||
<item android:color="?vctr_content_tertiary" android:state_focused="false" />
|
||||
<item android:color="?colorPrimary" />
|
||||
</selector>
|
@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:color="?colorPrimary" android:state_focused="true"/>
|
||||
<item android:color="?colorPrimary" android:state_hovered="true"/>
|
||||
<item android:color="?colorPrimary" android:state_enabled="false"/>
|
||||
<item android:color="?vctr_content_quinary"/>
|
||||
</selector>
|
@ -129,9 +129,9 @@
|
||||
<color name="vctr_chat_effect_snow_background_light">@color/black_alpha</color>
|
||||
<color name="vctr_chat_effect_snow_background_dark">@android:color/transparent</color>
|
||||
|
||||
<attr name="vctr_voice_message_toast_background" format="color" />
|
||||
<color name="vctr_voice_message_toast_background_light">@color/palette_black_900</color>
|
||||
<color name="vctr_voice_message_toast_background_dark">@color/palette_gray_400</color>
|
||||
<attr name="vctr_toast_background" format="color" />
|
||||
<color name="vctr_toast_background_light">@color/palette_black_900</color>
|
||||
<color name="vctr_toast_background_dark">@color/palette_gray_400</color>
|
||||
|
||||
<!-- Presence Indicator colors -->
|
||||
<attr name="vctr_presence_indicator_offline" format="color" />
|
||||
|
@ -9,6 +9,11 @@
|
||||
<item name="lineHeight">24sp</item>
|
||||
</style>
|
||||
|
||||
<style name="Widget.Vector.Button.CallToAction" parent="Widget.Vector.Button">
|
||||
<item name="android:backgroundTint">@color/button_background_tint_selector</item>
|
||||
<item name="android:textColor">@android:color/white</item>
|
||||
</style>
|
||||
|
||||
<style name="Widget.Vector.Button.Destructive">
|
||||
<item name="android:minWidth">94dp</item>
|
||||
<item name="materialThemeOverlay">@style/VectorMaterialThemeOverlayDestructive</item>
|
||||
|
@ -1,14 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<!-- Default style for TextInputLayout -->
|
||||
<style name="Widget.Vector.TextInputLayout" parent="Widget.MaterialComponents.TextInputLayout.OutlinedBox" />
|
||||
|
||||
<style name="Widget.Vector.TextInputLayout.Password">
|
||||
<item name="endIconMode">password_toggle</item>
|
||||
<item name="endIconTint">?vctr_content_secondary</item>
|
||||
</style>
|
||||
|
||||
<style name="Widget.Vector.EditText.Composer" parent="Widget.AppCompat.EditText">
|
||||
<item name="android:background">@android:color/transparent</item>
|
||||
<item name="android:inputType">textCapSentences|textMultiLine</item>
|
||||
|
@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<!-- Default style for TextInputLayout -->
|
||||
<style name="Widget.Vector.TextInputLayout" parent="Widget.MaterialComponents.TextInputLayout.OutlinedBox" />
|
||||
|
||||
<style name="Widget.Vector.TextInputLayout.Password">
|
||||
<item name="endIconMode">password_toggle</item>
|
||||
<item name="endIconTint">?vctr_content_secondary</item>
|
||||
</style>
|
||||
|
||||
<style name="Widget.Vector.TextInputLayout.Form">
|
||||
<item name="boxStrokeColor">@color/form_edit_text_stroke_color_selector</item>
|
||||
<item name="android:textColorHint">@color/form_edit_text_hint_color_selector</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
15
library/ui-styles/src/main/res/values/styles_toast.xml
Normal file
15
library/ui-styles/src/main/res/values/styles_toast.xml
Normal file
@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<style name="Widget.Vector.TextView.Caption.Toast">
|
||||
<item name="android:paddingTop">8dp</item>
|
||||
<item name="android:paddingBottom">8dp</item>
|
||||
<item name="android:paddingStart">12dp</item>
|
||||
<item name="android:paddingEnd">12dp</item>
|
||||
<item name="android:background">@drawable/bg_round_corner_8dp</item>
|
||||
<item name="android:backgroundTint">?vctr_toast_background</item>
|
||||
<item name="android:textColor">@color/palette_white</item>
|
||||
<item name="android:gravity">center</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
@ -12,15 +12,4 @@
|
||||
<item name="direction">rightToLeft</item>
|
||||
</style>
|
||||
|
||||
<style name="Widget.Vector.TextView.Caption.Toast">
|
||||
<item name="android:paddingTop">8dp</item>
|
||||
<item name="android:paddingBottom">8dp</item>
|
||||
<item name="android:paddingStart">12dp</item>
|
||||
<item name="android:paddingEnd">12dp</item>
|
||||
<item name="android:background">@drawable/bg_round_corner_8dp</item>
|
||||
<item name="android:backgroundTint">?vctr_voice_message_toast_background</item>
|
||||
<item name="android:textColor">@color/palette_white</item>
|
||||
<item name="android:gravity">center</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
@ -176,8 +176,7 @@
|
||||
<!-- Keywords -->
|
||||
<item name="vctr_keyword_style">@style/Widget.Vector.Keyword</item>
|
||||
|
||||
<!-- Voice Message -->
|
||||
<item name="vctr_voice_message_toast_background">@color/vctr_voice_message_toast_background_dark</item>
|
||||
<item name="vctr_toast_background">@color/vctr_toast_background_dark</item>
|
||||
</style>
|
||||
|
||||
<style name="Theme.Vector.Dark" parent="Base.Theme.Vector.Dark" />
|
||||
|
@ -179,8 +179,7 @@
|
||||
<!-- Keywords -->
|
||||
<item name="vctr_keyword_style">@style/Widget.Vector.Keyword</item>
|
||||
|
||||
<!-- Voice Message -->
|
||||
<item name="vctr_voice_message_toast_background">@color/vctr_voice_message_toast_background_light</item>
|
||||
<item name="vctr_toast_background">@color/vctr_toast_background_light</item>
|
||||
</style>
|
||||
|
||||
<style name="Theme.Vector.Light" parent="Base.Theme.Vector.Light" />
|
||||
|
@ -121,7 +121,7 @@
|
||||
<item name="vctr_jump_to_unread_style">@style/Widget.Vector.JumpToUnread.SC.Dark</item>
|
||||
|
||||
<!-- Voice Message -->
|
||||
<item name="vctr_voice_message_toast_background">?colorBackgroundFloating</item>
|
||||
<item name="vctr_toast_background">?colorBackgroundFloating</item>
|
||||
</style>
|
||||
|
||||
<style name="AppTheme.Base.SC.Dark">
|
||||
|
@ -117,7 +117,7 @@
|
||||
<item name="vctr_jump_to_unread_style">@style/Widget.Vector.JumpToUnread.SC.Light</item>
|
||||
|
||||
<!-- Voice Message -->
|
||||
<item name="vctr_voice_message_toast_background">@color/background_floating_sc</item> <!-- needs dark background -->
|
||||
<item name="vctr_toast_background">@color/background_floating_sc</item> <!-- needs dark background -->
|
||||
</style>
|
||||
|
||||
<!-- Default AppTheme for usage where settings don't apply -->
|
||||
|
@ -27,6 +27,7 @@ import org.matrix.android.sdk.api.session.group.GroupSummaryQueryParams
|
||||
import org.matrix.android.sdk.api.session.group.model.GroupSummary
|
||||
import org.matrix.android.sdk.api.session.identity.ThreePid
|
||||
import org.matrix.android.sdk.api.session.pushers.Pusher
|
||||
import org.matrix.android.sdk.api.session.room.RoomSortOrder
|
||||
import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams
|
||||
import org.matrix.android.sdk.api.session.room.accountdata.RoomAccountDataEvent
|
||||
import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
|
||||
@ -44,10 +45,10 @@ import org.matrix.android.sdk.internal.crypto.store.PrivateKeysInfo
|
||||
|
||||
class FlowSession(private val session: Session) {
|
||||
|
||||
fun liveRoomSummaries(queryParams: RoomSummaryQueryParams): Flow<List<RoomSummary>> {
|
||||
return session.getRoomSummariesLive(queryParams).asFlow()
|
||||
fun liveRoomSummaries(queryParams: RoomSummaryQueryParams, sortOrder: RoomSortOrder = RoomSortOrder.NONE): Flow<List<RoomSummary>> {
|
||||
return session.getRoomSummariesLive(queryParams, sortOrder).asFlow()
|
||||
.startWith(session.coroutineDispatchers.io) {
|
||||
session.getRoomSummaries(queryParams)
|
||||
session.getRoomSummaries(queryParams, sortOrder)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -9,7 +9,7 @@ buildscript {
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath "io.realm:realm-gradle-plugin:10.8.0"
|
||||
classpath "io.realm:realm-gradle-plugin:10.8.1"
|
||||
}
|
||||
}
|
||||
|
||||
@ -31,7 +31,7 @@ android {
|
||||
// that the app's state is completely cleared between tests.
|
||||
testInstrumentationRunnerArguments clearPackageData: 'true'
|
||||
|
||||
buildConfigField "String", "SDK_VERSION", "\"1.3.6\""
|
||||
buildConfigField "String", "SDK_VERSION", "\"1.3.8\""
|
||||
|
||||
buildConfigField "String", "GIT_SDK_REVISION", "\"${gitRevision()}\""
|
||||
resValue "string", "git_sdk_revision", "\"${gitRevision()}\""
|
||||
@ -44,6 +44,7 @@ android {
|
||||
}
|
||||
|
||||
testOptions {
|
||||
// Comment to run on Android 12
|
||||
execution 'ANDROIDX_TEST_ORCHESTRATOR'
|
||||
}
|
||||
|
||||
@ -106,8 +107,9 @@ dependencies {
|
||||
implementation libs.androidx.appCompat
|
||||
implementation libs.androidx.core
|
||||
|
||||
implementation libs.androidx.lifecycleExtensions
|
||||
implementation libs.androidx.lifecycleJava8
|
||||
// Lifecycle
|
||||
implementation libs.androidx.lifecycleCommon
|
||||
implementation libs.androidx.lifecycleProcess
|
||||
|
||||
// Network
|
||||
implementation libs.squareup.retrofit
|
||||
@ -156,10 +158,10 @@ dependencies {
|
||||
implementation libs.apache.commonsImaging
|
||||
|
||||
// Phone number https://github.com/google/libphonenumber
|
||||
implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.35'
|
||||
implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.37'
|
||||
|
||||
testImplementation libs.tests.junit
|
||||
testImplementation 'org.robolectric:robolectric:4.6.1'
|
||||
testImplementation 'org.robolectric:robolectric:4.7'
|
||||
//testImplementation 'org.robolectric:shadows-support-v4:3.0'
|
||||
// Note: version sticks to 1.9.2 due to https://github.com/mockk/mockk/issues/281
|
||||
testImplementation libs.mockk.mockk
|
||||
|
@ -16,6 +16,7 @@
|
||||
|
||||
package org.matrix.android.sdk.api
|
||||
|
||||
import okhttp3.ConnectionSpec
|
||||
import org.matrix.android.sdk.api.crypto.MXCryptoConfig
|
||||
import java.net.Proxy
|
||||
|
||||
@ -44,6 +45,10 @@ data class MatrixConfiguration(
|
||||
* You can create one using for instance Proxy(proxyType, InetSocketAddress.createUnresolved(hostname, port).
|
||||
*/
|
||||
val proxy: Proxy? = null,
|
||||
/**
|
||||
* TLS versions and cipher suites limitation for unauthenticated requests
|
||||
*/
|
||||
val connectionSpec: ConnectionSpec = ConnectionSpec.RESTRICTED_TLS,
|
||||
/**
|
||||
* True to advertise support for call transfers to other parties on Matrix calls.
|
||||
*/
|
||||
|
@ -20,7 +20,18 @@ package org.matrix.android.sdk.api
|
||||
* This class contains pattern to match Matrix Url, aka mxc urls
|
||||
*/
|
||||
object MatrixUrls {
|
||||
/**
|
||||
* "mxc" scheme, including "://". So "mxc://"
|
||||
*/
|
||||
const val MATRIX_CONTENT_URI_SCHEME = "mxc://"
|
||||
|
||||
/**
|
||||
* Return true if the String starts with "mxc://"
|
||||
*/
|
||||
fun String.isMxcUrl() = startsWith(MATRIX_CONTENT_URI_SCHEME)
|
||||
|
||||
/**
|
||||
* Remove the "mxc://" prefix. No op if the String is not a Mxc URL
|
||||
*/
|
||||
fun String.removeMxcPrefix() = removePrefix(MATRIX_CONTENT_URI_SCHEME)
|
||||
}
|
||||
|
@ -105,9 +105,15 @@ interface AuthenticationService {
|
||||
/**
|
||||
* Authenticate with a matrixId and a password
|
||||
* Usually call this after a successful call to getWellKnownData()
|
||||
* @param homeServerConnectionConfig the information about the homeserver and other configuration
|
||||
* @param matrixId the matrixId of the user
|
||||
* @param password the password of the account
|
||||
* @param initialDeviceName the initial device name
|
||||
* @param deviceId the device id, optional. If not provided or null, the server will generate one.
|
||||
*/
|
||||
suspend fun directAuthentication(homeServerConnectionConfig: HomeServerConnectionConfig,
|
||||
matrixId: String,
|
||||
password: String,
|
||||
initialDeviceName: String): Session
|
||||
initialDeviceName: String,
|
||||
deviceId: String? = null): Session
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ package org.matrix.android.sdk.api.auth.data
|
||||
import android.net.Uri
|
||||
import com.squareup.moshi.JsonClass
|
||||
import okhttp3.CipherSuite
|
||||
import okhttp3.ConnectionSpec
|
||||
import okhttp3.TlsVersion
|
||||
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig.Builder
|
||||
import org.matrix.android.sdk.internal.network.ssl.Fingerprint
|
||||
@ -191,32 +192,25 @@ data class HomeServerConnectionConfig(
|
||||
/**
|
||||
* Convenient method to limit the TLS versions and cipher suites for this Builder
|
||||
* Ref:
|
||||
* - https://www.ssi.gouv.fr/uploads/2017/02/security-recommendations-for-tls_v1.1.pdf
|
||||
* - https://www.ssi.gouv.fr/uploads/2017/07/anssi-guide-recommandations_de_securite_relatives_a_tls-v1.2.pdf
|
||||
* - https://developer.android.com/reference/javax/net/ssl/SSLEngine
|
||||
*
|
||||
* @param tlsLimitations true to use Tls limitations
|
||||
* @param enableCompatibilityMode set to true for Android < 20
|
||||
* @return this builder
|
||||
*/
|
||||
@Deprecated("TLS versions and cipher suites are limited by default")
|
||||
fun withTlsLimitations(tlsLimitations: Boolean, enableCompatibilityMode: Boolean): Builder {
|
||||
if (tlsLimitations) {
|
||||
withShouldAcceptTlsExtensions(false)
|
||||
|
||||
// Tls versions
|
||||
addAcceptedTlsVersion(TlsVersion.TLS_1_2)
|
||||
addAcceptedTlsVersion(TlsVersion.TLS_1_3)
|
||||
// TlS versions
|
||||
ConnectionSpec.RESTRICTED_TLS.tlsVersions?.let { this.tlsVersions.addAll(it) }
|
||||
|
||||
forceUsageOfTlsVersions(enableCompatibilityMode)
|
||||
|
||||
// Cipher suites
|
||||
addAcceptedTlsCipherSuite(CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256)
|
||||
addAcceptedTlsCipherSuite(CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256)
|
||||
addAcceptedTlsCipherSuite(CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256)
|
||||
addAcceptedTlsCipherSuite(CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256)
|
||||
addAcceptedTlsCipherSuite(CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384)
|
||||
addAcceptedTlsCipherSuite(CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384)
|
||||
addAcceptedTlsCipherSuite(CipherSuite.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256)
|
||||
addAcceptedTlsCipherSuite(CipherSuite.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256)
|
||||
ConnectionSpec.RESTRICTED_TLS.cipherSuites?.let { this.tlsCipherSuites.addAll(it) }
|
||||
|
||||
if (enableCompatibilityMode) {
|
||||
// Adopt some preceding cipher suites for Android < 20 to be able to negotiate
|
||||
|
@ -17,6 +17,7 @@
|
||||
package org.matrix.android.sdk.api.auth.login
|
||||
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import org.matrix.android.sdk.api.util.JsonDict
|
||||
|
||||
/**
|
||||
* Set of methods to be able to login to an existing account on a homeserver.
|
||||
@ -34,12 +35,14 @@ interface LoginWizard {
|
||||
*
|
||||
* @param login the login field. Can be a user name, or a msisdn (email or phone number) associated to the account
|
||||
* @param password the password of the account
|
||||
* @param deviceName the initial device name
|
||||
* @param initialDeviceName the initial device name
|
||||
* @param deviceId the device id, optional. If not provided or null, the server will generate one.
|
||||
* @return a [Session] if the login is successful
|
||||
*/
|
||||
suspend fun login(login: String,
|
||||
password: String,
|
||||
deviceName: String): Session
|
||||
initialDeviceName: String,
|
||||
deviceId: String? = null): Session
|
||||
|
||||
/**
|
||||
* Exchange a login token to an access token.
|
||||
@ -49,6 +52,12 @@ interface LoginWizard {
|
||||
*/
|
||||
suspend fun loginWithToken(loginToken: String): Session
|
||||
|
||||
/**
|
||||
* Login to the homeserver by sending a custom JsonDict.
|
||||
* The data should contain at least one entry "type" with a String value.
|
||||
*/
|
||||
suspend fun loginCustom(data: JsonDict): Session
|
||||
|
||||
/**
|
||||
* Ask the homeserver to reset the user password. The password will not be reset until
|
||||
* [resetPasswordMailConfirmed] is successfully called.
|
||||
|
@ -18,6 +18,8 @@ package org.matrix.android.sdk.api.failure
|
||||
|
||||
import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse
|
||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||
import org.matrix.android.sdk.api.session.contentscanner.ContentScannerError
|
||||
import org.matrix.android.sdk.api.session.contentscanner.ScanFailure
|
||||
import org.matrix.android.sdk.internal.di.MoshiProvider
|
||||
import java.io.IOException
|
||||
import javax.net.ssl.HttpsURLConnection
|
||||
@ -100,3 +102,19 @@ fun Throwable.isRegistrationAvailabilityError(): Boolean {
|
||||
error.code == MatrixError.M_INVALID_USERNAME ||
|
||||
error.code == MatrixError.M_EXCLUSIVE)
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to convert to a ScanFailure. Return null in the cases it's not possible
|
||||
*/
|
||||
fun Throwable.toScanFailure(): ScanFailure? {
|
||||
return if (this is Failure.OtherServerError) {
|
||||
tryOrNull {
|
||||
MoshiProvider.providesMoshi()
|
||||
.adapter(ContentScannerError::class.java)
|
||||
.fromJson(errorBody)
|
||||
}
|
||||
?.let { ScanFailure(it, httpCode, this) }
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
* 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.
|
||||
@ -13,19 +13,14 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.matrix.android.sdk.api.pushrules
|
||||
|
||||
package im.vector.app.core.utils
|
||||
import org.matrix.android.sdk.api.pushrules.rest.PushRule
|
||||
import org.matrix.android.sdk.api.session.events.model.Event
|
||||
|
||||
import io.reactivex.Completable
|
||||
import io.reactivex.Single
|
||||
import io.reactivex.disposables.Disposable
|
||||
import io.reactivex.internal.functions.Functions
|
||||
import timber.log.Timber
|
||||
|
||||
fun <T> Single<T>.subscribeLogError(): Disposable {
|
||||
return subscribe(Functions.emptyConsumer(), { Timber.e(it) })
|
||||
}
|
||||
|
||||
fun Completable.subscribeLogError(): Disposable {
|
||||
return subscribe({}, { Timber.e(it) })
|
||||
}
|
||||
data class PushEvents(
|
||||
val matchedEvents: List<Pair<Event, PushRule>>,
|
||||
val roomsJoined: Collection<String>,
|
||||
val roomsLeft: Collection<String>,
|
||||
val redactedEventIds: List<String>
|
||||
)
|
@ -51,11 +51,7 @@ interface PushRuleService {
|
||||
// fun fulfilledBingRule(event: Event, rules: List<PushRule>): PushRule?
|
||||
|
||||
interface PushRuleListener {
|
||||
fun onMatchRule(event: Event, actions: List<Action>)
|
||||
fun onRoomJoined(roomId: String)
|
||||
fun onRoomLeft(roomId: String)
|
||||
fun onEventRedacted(redactedEventId: String)
|
||||
fun batchFinish()
|
||||
fun onEvents(pushEvents: PushEvents)
|
||||
}
|
||||
|
||||
fun getKeywords(): LiveData<Set<String>>
|
||||
|
@ -19,17 +19,41 @@ package org.matrix.android.sdk.api.query
|
||||
/**
|
||||
* Basic query language. All these cases are mutually exclusive.
|
||||
*/
|
||||
sealed class QueryStringValue {
|
||||
object NoCondition : QueryStringValue()
|
||||
object IsNull : QueryStringValue()
|
||||
object IsNotNull : QueryStringValue()
|
||||
object IsEmpty : QueryStringValue()
|
||||
object IsNotEmpty : QueryStringValue()
|
||||
data class Equals(val string: String, val case: Case = Case.SENSITIVE) : QueryStringValue()
|
||||
data class Contains(val string: String, val case: Case = Case.SENSITIVE) : QueryStringValue()
|
||||
sealed interface QueryStringValue {
|
||||
sealed interface ContentQueryStringValue : QueryStringValue {
|
||||
val string: String
|
||||
val case: Case
|
||||
}
|
||||
|
||||
object NoCondition : QueryStringValue
|
||||
object IsNull : QueryStringValue
|
||||
object IsNotNull : QueryStringValue
|
||||
object IsEmpty : QueryStringValue
|
||||
object IsNotEmpty : QueryStringValue
|
||||
|
||||
data class Equals(override val string: String, override val case: Case = Case.SENSITIVE) : ContentQueryStringValue
|
||||
data class Contains(override val string: String, override val case: Case = Case.SENSITIVE) : ContentQueryStringValue
|
||||
|
||||
enum class Case {
|
||||
/**
|
||||
* Match query sensitive to case
|
||||
*/
|
||||
SENSITIVE,
|
||||
INSENSITIVE
|
||||
|
||||
/**
|
||||
* Match query insensitive to case, this only works for Latin-1 character sets
|
||||
*/
|
||||
INSENSITIVE,
|
||||
|
||||
/**
|
||||
* Match query with input normalized (case insensitive)
|
||||
* Works around Realms inability to sort or filter by case for non Latin-1 character sets
|
||||
* Expects the target field to contain normalized data
|
||||
*
|
||||
* @see org.matrix.android.sdk.internal.util.Normalizer.normalize
|
||||
*/
|
||||
NORMALIZED
|
||||
}
|
||||
}
|
||||
|
||||
internal fun QueryStringValue.isNormalized() = this is QueryStringValue.ContentQueryStringValue && case == QueryStringValue.Case.NORMALIZED
|
||||
|
@ -31,6 +31,7 @@ import org.matrix.android.sdk.api.session.cache.CacheService
|
||||
import org.matrix.android.sdk.api.session.call.CallSignalingService
|
||||
import org.matrix.android.sdk.api.session.content.ContentUploadStateTracker
|
||||
import org.matrix.android.sdk.api.session.content.ContentUrlResolver
|
||||
import org.matrix.android.sdk.api.session.contentscanner.ContentScannerService
|
||||
import org.matrix.android.sdk.api.session.crypto.CryptoService
|
||||
import org.matrix.android.sdk.api.session.events.EventService
|
||||
import org.matrix.android.sdk.api.session.file.ContentDownloadStateTracker
|
||||
@ -120,9 +121,11 @@ interface Session :
|
||||
fun requireBackgroundSync()
|
||||
|
||||
/**
|
||||
* Launches infinite periodic background syncs
|
||||
* This does not work in doze mode :/
|
||||
* If battery optimization is on it can work in app standby but that's all :/
|
||||
* Launches infinite self rescheduling background syncs via the WorkManager
|
||||
*
|
||||
* While dozing, syncs will only occur during maintenance windows
|
||||
* For reliability it's recommended to also start a long running foreground service
|
||||
* along with disabling battery optimizations
|
||||
*/
|
||||
fun startAutomaticBackgroundSync(timeOutInSeconds: Long, repeatDelayInSeconds: Long)
|
||||
|
||||
@ -190,6 +193,11 @@ interface Session :
|
||||
*/
|
||||
fun cryptoService(): CryptoService
|
||||
|
||||
/**
|
||||
* Returns the ContentScannerService associated with the session
|
||||
*/
|
||||
fun contentScannerService(): ContentScannerService
|
||||
|
||||
/**
|
||||
* Returns the identity service associated with the session
|
||||
*/
|
||||
|
@ -16,6 +16,8 @@
|
||||
|
||||
package org.matrix.android.sdk.api.session.content
|
||||
|
||||
import org.matrix.android.sdk.internal.crypto.attachments.ElementToDecrypt
|
||||
|
||||
/**
|
||||
* This interface defines methods for accessing content from the current session.
|
||||
*/
|
||||
@ -39,6 +41,15 @@ interface ContentUrlResolver {
|
||||
*/
|
||||
fun resolveFullSize(contentUrl: String?): String?
|
||||
|
||||
/**
|
||||
* Get the ResolvedMethod to download a URL
|
||||
*
|
||||
* @param contentUrl the Matrix media content URI (in the form of "mxc://...").
|
||||
* @param elementToDecrypt Encryption data may be required if you use a content scanner
|
||||
* @return the Method to access resource, or null if invalid
|
||||
*/
|
||||
fun resolveForDownload(contentUrl: String?, elementToDecrypt: ElementToDecrypt? = null): ResolvedMethod?
|
||||
|
||||
/**
|
||||
* Get the actual URL for accessing the thumbnail image of a given Matrix media content URI.
|
||||
*
|
||||
@ -49,4 +60,9 @@ interface ContentUrlResolver {
|
||||
* @return the URL to access the described resource, or null if the url is invalid.
|
||||
*/
|
||||
fun resolveThumbnail(contentUrl: String?, width: Int, height: Int, method: ThumbnailMethod): String?
|
||||
|
||||
sealed class ResolvedMethod {
|
||||
data class GET(val url: String) : ResolvedMethod()
|
||||
data class POST(val url: String, val jsonBody: String) : ResolvedMethod()
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,49 @@
|
||||
/*
|
||||
* 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.contentscanner
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class ContentScannerError(
|
||||
@Json(name = "info") val info: String? = null,
|
||||
@Json(name = "reason") val reason: String? = null
|
||||
) {
|
||||
companion object {
|
||||
// 502 The server failed to request media from the media repo.
|
||||
const val REASON_MCS_MEDIA_REQUEST_FAILED = "MCS_MEDIA_REQUEST_FAILED"
|
||||
|
||||
/* 400 The server failed to decrypt the encrypted media downloaded from the media repo.*/
|
||||
const val REASON_MCS_MEDIA_FAILED_TO_DECRYPT = "MCS_MEDIA_FAILED_TO_DECRYPT"
|
||||
|
||||
/* 403 The server scanned the downloaded media but the antivirus script returned a non-zero exit code.*/
|
||||
const val REASON_MCS_MEDIA_NOT_CLEAN = "MCS_MEDIA_NOT_CLEAN"
|
||||
|
||||
/* 403 The provided encrypted_body could not be decrypted. The client should request the public key of the server and then retry (once).*/
|
||||
const val REASON_MCS_BAD_DECRYPTION = "MCS_BAD_DECRYPTION"
|
||||
|
||||
/* 400 The request body contains malformed JSON.*/
|
||||
const val REASON_MCS_MALFORMED_JSON = "MCS_MALFORMED_JSON"
|
||||
}
|
||||
}
|
||||
|
||||
class ScanFailure(val error: ContentScannerError, val httpCode: Int, cause: Throwable? = null) : Throwable(cause = cause)
|
||||
|
||||
// For Glide, which deals with Exception and not with Throwable
|
||||
fun ScanFailure.toException() = Exception(this)
|
||||
fun Throwable.toScanFailure() = this.cause as? ScanFailure
|
@ -0,0 +1,40 @@
|
||||
/*
|
||||
* 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.contentscanner
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import org.matrix.android.sdk.api.util.Optional
|
||||
import org.matrix.android.sdk.internal.crypto.attachments.ElementToDecrypt
|
||||
|
||||
interface ContentScannerService {
|
||||
|
||||
val serverPublicKey: String?
|
||||
|
||||
fun getContentScannerServer(): String?
|
||||
fun setScannerUrl(url: String?)
|
||||
fun enableScanner(enabled: Boolean)
|
||||
fun isScannerEnabled(): Boolean
|
||||
fun getLiveStatusForFile(mxcUrl: String, fetchIfNeeded: Boolean = true, fileInfo: ElementToDecrypt? = null): LiveData<Optional<ScanStatusInfo>>
|
||||
fun getCachedScanResultForFile(mxcUrl: String): ScanStatusInfo?
|
||||
|
||||
/**
|
||||
* Get the current public curve25519 key that the AV server is advertising.
|
||||
* @param callback on success callback containing the server public key
|
||||
*/
|
||||
suspend fun getServerPublicKey(forceDownload: Boolean = false): String?
|
||||
suspend fun getScanResultForAttachment(mxcUrl: String, fileInfo: ElementToDecrypt? = null): ScanStatusInfo
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
/*
|
||||
* 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.contentscanner
|
||||
|
||||
enum class ScanState {
|
||||
TRUSTED,
|
||||
INFECTED,
|
||||
UNKNOWN,
|
||||
IN_PROGRESS
|
||||
}
|
||||
|
||||
data class ScanStatusInfo(
|
||||
val state: ScanState,
|
||||
val scanDateTimestamp: Long?,
|
||||
val humanReadableMessage: String?
|
||||
)
|
@ -22,6 +22,8 @@ import org.json.JSONObject
|
||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||
import org.matrix.android.sdk.api.failure.MatrixError
|
||||
import org.matrix.android.sdk.api.session.crypto.MXCryptoError
|
||||
import org.matrix.android.sdk.api.session.room.model.Membership
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomMemberContent
|
||||
import org.matrix.android.sdk.api.session.room.model.message.MessageContent
|
||||
import org.matrix.android.sdk.api.session.room.model.message.MessageType
|
||||
import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent
|
||||
@ -310,3 +312,6 @@ fun Event.isEdition(): Boolean {
|
||||
fun Event.getPresenceContent(): PresenceContent? {
|
||||
return content.toModel<PresenceContent>()
|
||||
}
|
||||
|
||||
fun Event.isInvitation(): Boolean = type == EventType.STATE_ROOM_MEMBER &&
|
||||
content?.toModel<RoomMemberContent>()?.membership == Membership.INVITE
|
||||
|
@ -102,6 +102,9 @@ object EventType {
|
||||
// Relation Events
|
||||
const val REACTION = "m.reaction"
|
||||
|
||||
// Poll
|
||||
const val POLL_START = "org.matrix.msc3381.poll.start"
|
||||
|
||||
// Unwedging
|
||||
internal const val DUMMY = "m.dummy"
|
||||
|
||||
|
@ -94,13 +94,15 @@ interface RoomService {
|
||||
* Get a snapshot list of room summaries.
|
||||
* @return the immutable list of [RoomSummary]
|
||||
*/
|
||||
fun getRoomSummaries(queryParams: RoomSummaryQueryParams): List<RoomSummary>
|
||||
fun getRoomSummaries(queryParams: RoomSummaryQueryParams,
|
||||
sortOrder: RoomSortOrder = RoomSortOrder.NONE): List<RoomSummary>
|
||||
|
||||
/**
|
||||
* Get a live list of room summaries. This list is refreshed as soon as the data changes.
|
||||
* @return the [LiveData] of List[RoomSummary]
|
||||
*/
|
||||
fun getRoomSummariesLive(queryParams: RoomSummaryQueryParams): LiveData<List<RoomSummary>>
|
||||
fun getRoomSummariesLive(queryParams: RoomSummaryQueryParams,
|
||||
sortOrder: RoomSortOrder = RoomSortOrder.ACTIVITY): LiveData<List<RoomSummary>>
|
||||
|
||||
/**
|
||||
* Get a snapshot list of Breadcrumbs
|
||||
|
@ -0,0 +1,25 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class MessagePollContent(
|
||||
@Json(name = "org.matrix.msc3381.poll.start") val pollCreationInfo: PollCreationInfo? = null
|
||||
)
|
@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.android.sdk.api.session.room.model.message
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class PollAnswer(
|
||||
@Json(name = "id") val id: String? = null,
|
||||
@Json(name = "org.matrix.msc1767.text") val answer: String? = null
|
||||
)
|
@ -0,0 +1,28 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class PollCreationInfo(
|
||||
@Json(name = "question") val question: PollQuestion? = null,
|
||||
@Json(name = "kind") val kind: String? = "org.matrix.msc3381.poll.disclosed",
|
||||
@Json(name = "max_selections") val maxSelections: Int = 1,
|
||||
@Json(name = "answers") val answers: List<PollAnswer>? = null
|
||||
)
|
@ -0,0 +1,25 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class PollQuestion(
|
||||
@Json(name = "org.matrix.msc1767.text") val question: String? = null
|
||||
)
|
@ -20,7 +20,6 @@ import org.matrix.android.sdk.api.session.content.ContentAttachmentData
|
||||
import org.matrix.android.sdk.api.session.events.model.Content
|
||||
import org.matrix.android.sdk.api.session.events.model.Event
|
||||
import org.matrix.android.sdk.api.session.room.model.message.MessageType
|
||||
import org.matrix.android.sdk.api.session.room.model.message.OptionItem
|
||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||
import org.matrix.android.sdk.api.util.Cancelable
|
||||
|
||||
@ -84,10 +83,10 @@ interface SendService {
|
||||
/**
|
||||
* Send a poll to the room.
|
||||
* @param question the question
|
||||
* @param options list of (label, value)
|
||||
* @param options list of options
|
||||
* @return a [Cancelable]
|
||||
*/
|
||||
fun sendPoll(question: String, options: List<OptionItem>): Cancelable
|
||||
fun sendPoll(question: String, options: List<String>): Cancelable
|
||||
|
||||
/**
|
||||
* Method to send a poll response.
|
||||
|
@ -28,8 +28,10 @@ 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.message.MessageContent
|
||||
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.relation.RelationDefaultContent
|
||||
import org.matrix.android.sdk.api.session.room.sender.SenderInfo
|
||||
import org.matrix.android.sdk.api.util.ContentUtils
|
||||
import org.matrix.android.sdk.api.util.ContentUtils.extractUsefulTextFromReply
|
||||
|
||||
/**
|
||||
@ -131,20 +133,6 @@ fun TimelineEvent.getLastMessageContent(): MessageContent? {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get last Message body, after a possible edition
|
||||
*/
|
||||
fun TimelineEvent.getLastMessageBody(): String? {
|
||||
val lastMessageContent = getLastMessageContent()
|
||||
|
||||
if (lastMessageContent != null) {
|
||||
return lastMessageContent.newContent?.toModel<MessageContent>()?.body
|
||||
?: lastMessageContent.body
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if it's a reply
|
||||
*/
|
||||
@ -156,11 +144,25 @@ fun TimelineEvent.isEdition(): Boolean {
|
||||
return root.isEdition()
|
||||
}
|
||||
|
||||
fun TimelineEvent.getTextEditableContent(): String? {
|
||||
val lastContent = getLastMessageContent()
|
||||
/**
|
||||
* Get the latest message body, after a possible edition, stripping the reply prefix if necessary
|
||||
*/
|
||||
fun TimelineEvent.getTextEditableContent(): String {
|
||||
val lastContentBody = getLastMessageContent()?.body ?: return ""
|
||||
return if (isReply()) {
|
||||
return extractUsefulTextFromReply(lastContent?.body ?: "")
|
||||
extractUsefulTextFromReply(lastContentBody)
|
||||
} else {
|
||||
lastContent?.body ?: ""
|
||||
lastContentBody
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the latest displayable content.
|
||||
* Will take care to hide spoiler text
|
||||
*/
|
||||
fun MessageContent.getTextDisplayableContent(): String {
|
||||
return newContent?.toModel<MessageTextContent>()?.matrixFormattedBody?.let { ContentUtils.formatSpoilerTextFromHtml(it) }
|
||||
?: newContent?.toModel<MessageContent>()?.body
|
||||
?: (this as MessageTextContent?)?.matrixFormattedBody?.let { ContentUtils.formatSpoilerTextFromHtml(it) }
|
||||
?: body
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ package org.matrix.android.sdk.api.session.space
|
||||
import android.net.Uri
|
||||
import androidx.lifecycle.LiveData
|
||||
import org.matrix.android.sdk.api.session.events.model.Event
|
||||
import org.matrix.android.sdk.api.session.room.RoomSortOrder
|
||||
import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||
import org.matrix.android.sdk.internal.session.space.peeking.SpacePeekResult
|
||||
@ -74,9 +75,11 @@ interface SpaceService {
|
||||
* Get a live list of space summaries. This list is refreshed as soon as the data changes.
|
||||
* @return the [LiveData] of List[SpaceSummary]
|
||||
*/
|
||||
fun getSpaceSummariesLive(queryParams: SpaceSummaryQueryParams): LiveData<List<RoomSummary>>
|
||||
fun getSpaceSummariesLive(queryParams: SpaceSummaryQueryParams,
|
||||
sortOrder: RoomSortOrder = RoomSortOrder.NONE): LiveData<List<RoomSummary>>
|
||||
|
||||
fun getSpaceSummaries(spaceSummaryQueryParams: SpaceSummaryQueryParams): List<RoomSummary>
|
||||
fun getSpaceSummaries(spaceSummaryQueryParams: SpaceSummaryQueryParams,
|
||||
sortOrder: RoomSortOrder = RoomSortOrder.NONE): List<RoomSummary>
|
||||
|
||||
suspend fun joinSpace(spaceIdOrAlias: String,
|
||||
reason: String? = null,
|
||||
|
@ -15,6 +15,8 @@
|
||||
*/
|
||||
package org.matrix.android.sdk.api.util
|
||||
|
||||
import org.matrix.android.sdk.internal.util.unescapeHtml
|
||||
|
||||
object ContentUtils {
|
||||
fun extractUsefulTextFromReply(repliedBody: String): String {
|
||||
val lines = repliedBody.lines()
|
||||
@ -44,4 +46,15 @@ object ContentUtils {
|
||||
}
|
||||
return repliedBody
|
||||
}
|
||||
|
||||
@Suppress("RegExpRedundantEscape")
|
||||
fun formatSpoilerTextFromHtml(formattedBody: String): String {
|
||||
// var reason = "",
|
||||
// can capture the spoiler reason for better formatting? ex. { reason = it.value; ">"}
|
||||
return formattedBody.replace("(?<=<span data-mx-spoiler)=\\\".+?\\\">".toRegex(), ">")
|
||||
.replace("(?<=<span data-mx-spoiler>).+?(?=</span>)".toRegex()) { SPOILER_CHAR.repeat(it.value.length) }
|
||||
.unescapeHtml()
|
||||
}
|
||||
|
||||
private const val SPOILER_CHAR = "█"
|
||||
}
|
||||
|
@ -51,6 +51,11 @@ internal class SessionManager @Inject constructor(private val matrixComponent: M
|
||||
}
|
||||
}
|
||||
|
||||
fun stopSession(sessionId: String) {
|
||||
val sessionComponent = sessionComponents[sessionId] ?: throw RuntimeException("You don't have a session for id $sessionId")
|
||||
sessionComponent.session().stopSync()
|
||||
}
|
||||
|
||||
fun getOrCreateSessionComponent(sessionParams: SessionParams): SessionComponent {
|
||||
return sessionComponents.getOrPut(sessionParams.credentials.sessionId()) {
|
||||
DaggerSessionComponent
|
||||
|
@ -121,6 +121,10 @@ internal interface AuthAPI {
|
||||
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "login")
|
||||
suspend fun login(@Body loginParams: TokenLoginParams): Credentials
|
||||
|
||||
@Headers("CONNECT_TIMEOUT:60000", "READ_TIMEOUT:60000", "WRITE_TIMEOUT:60000")
|
||||
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "login")
|
||||
suspend fun login(@Body loginParams: JsonDict): Credentials
|
||||
|
||||
/**
|
||||
* Ask the homeserver to reset the password associated with the provided email.
|
||||
*/
|
||||
|
@ -388,8 +388,15 @@ internal class DefaultAuthenticationService @Inject constructor(
|
||||
override suspend fun directAuthentication(homeServerConnectionConfig: HomeServerConnectionConfig,
|
||||
matrixId: String,
|
||||
password: String,
|
||||
initialDeviceName: String): Session {
|
||||
return directLoginTask.execute(DirectLoginTask.Params(homeServerConnectionConfig, matrixId, password, initialDeviceName))
|
||||
initialDeviceName: String,
|
||||
deviceId: String?): Session {
|
||||
return directLoginTask.execute(DirectLoginTask.Params(
|
||||
homeServerConnectionConfig = homeServerConnectionConfig,
|
||||
userId = matrixId,
|
||||
password = password,
|
||||
deviceName = initialDeviceName,
|
||||
deviceId = deviceId
|
||||
))
|
||||
}
|
||||
|
||||
private fun buildAuthAPI(homeServerConnectionConfig: HomeServerConnectionConfig): AuthAPI {
|
||||
|
@ -49,51 +49,54 @@ internal data class PasswordLoginParams(
|
||||
|
||||
fun userIdentifier(user: String,
|
||||
password: String,
|
||||
deviceDisplayName: String? = null,
|
||||
deviceId: String? = null): PasswordLoginParams {
|
||||
deviceDisplayName: String?,
|
||||
deviceId: String?): PasswordLoginParams {
|
||||
return PasswordLoginParams(
|
||||
mapOf(
|
||||
identifier = mapOf(
|
||||
IDENTIFIER_KEY_TYPE to IDENTIFIER_KEY_TYPE_USER,
|
||||
IDENTIFIER_KEY_USER to user
|
||||
),
|
||||
password,
|
||||
LoginFlowTypes.PASSWORD,
|
||||
deviceDisplayName,
|
||||
deviceId)
|
||||
password = password,
|
||||
type = LoginFlowTypes.PASSWORD,
|
||||
deviceDisplayName = deviceDisplayName,
|
||||
deviceId = deviceId
|
||||
)
|
||||
}
|
||||
|
||||
fun thirdPartyIdentifier(medium: String,
|
||||
address: String,
|
||||
password: String,
|
||||
deviceDisplayName: String? = null,
|
||||
deviceId: String? = null): PasswordLoginParams {
|
||||
deviceDisplayName: String?,
|
||||
deviceId: String?): PasswordLoginParams {
|
||||
return PasswordLoginParams(
|
||||
mapOf(
|
||||
identifier = mapOf(
|
||||
IDENTIFIER_KEY_TYPE to IDENTIFIER_KEY_TYPE_THIRD_PARTY,
|
||||
IDENTIFIER_KEY_MEDIUM to medium,
|
||||
IDENTIFIER_KEY_ADDRESS to address
|
||||
),
|
||||
password,
|
||||
LoginFlowTypes.PASSWORD,
|
||||
deviceDisplayName,
|
||||
deviceId)
|
||||
password = password,
|
||||
type = LoginFlowTypes.PASSWORD,
|
||||
deviceDisplayName = deviceDisplayName,
|
||||
deviceId = deviceId
|
||||
)
|
||||
}
|
||||
|
||||
fun phoneIdentifier(country: String,
|
||||
phone: String,
|
||||
password: String,
|
||||
deviceDisplayName: String? = null,
|
||||
deviceId: String? = null): PasswordLoginParams {
|
||||
deviceDisplayName: String?,
|
||||
deviceId: String?): PasswordLoginParams {
|
||||
return PasswordLoginParams(
|
||||
mapOf(
|
||||
identifier = mapOf(
|
||||
IDENTIFIER_KEY_TYPE to IDENTIFIER_KEY_TYPE_PHONE,
|
||||
IDENTIFIER_KEY_COUNTRY to country,
|
||||
IDENTIFIER_KEY_PHONE to phone
|
||||
),
|
||||
password,
|
||||
LoginFlowTypes.PASSWORD,
|
||||
deviceDisplayName,
|
||||
deviceId)
|
||||
password = password,
|
||||
type = LoginFlowTypes.PASSWORD,
|
||||
deviceDisplayName = deviceDisplayName,
|
||||
deviceId = deviceId
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -21,6 +21,7 @@ import org.matrix.android.sdk.api.auth.login.LoginProfileInfo
|
||||
import org.matrix.android.sdk.api.auth.login.LoginWizard
|
||||
import org.matrix.android.sdk.api.auth.registration.RegisterThreePid
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import org.matrix.android.sdk.api.util.JsonDict
|
||||
import org.matrix.android.sdk.internal.auth.AuthAPI
|
||||
import org.matrix.android.sdk.internal.auth.PendingSessionStore
|
||||
import org.matrix.android.sdk.internal.auth.SessionCreator
|
||||
@ -32,6 +33,7 @@ import org.matrix.android.sdk.internal.auth.registration.AddThreePidRegistration
|
||||
import org.matrix.android.sdk.internal.auth.registration.RegisterAddThreePidTask
|
||||
import org.matrix.android.sdk.internal.network.executeRequest
|
||||
import org.matrix.android.sdk.internal.session.content.DefaultContentUrlResolver
|
||||
import org.matrix.android.sdk.internal.session.contentscanner.DisabledContentScannerService
|
||||
|
||||
internal class DefaultLoginWizard(
|
||||
private val authAPI: AuthAPI,
|
||||
@ -43,7 +45,7 @@ internal class DefaultLoginWizard(
|
||||
|
||||
private val getProfileTask: GetProfileTask = DefaultGetProfileTask(
|
||||
authAPI,
|
||||
DefaultContentUrlResolver(pendingSessionData.homeServerConnectionConfig)
|
||||
DefaultContentUrlResolver(pendingSessionData.homeServerConnectionConfig, DisabledContentScannerService())
|
||||
)
|
||||
|
||||
override suspend fun getProfileInfo(matrixId: String): LoginProfileInfo {
|
||||
@ -52,11 +54,23 @@ internal class DefaultLoginWizard(
|
||||
|
||||
override suspend fun login(login: String,
|
||||
password: String,
|
||||
deviceName: String): Session {
|
||||
initialDeviceName: String,
|
||||
deviceId: String?): Session {
|
||||
val loginParams = if (Patterns.EMAIL_ADDRESS.matcher(login).matches()) {
|
||||
PasswordLoginParams.thirdPartyIdentifier(ThreePidMedium.EMAIL, login, password, deviceName)
|
||||
PasswordLoginParams.thirdPartyIdentifier(
|
||||
medium = ThreePidMedium.EMAIL,
|
||||
address = login,
|
||||
password = password,
|
||||
deviceDisplayName = initialDeviceName,
|
||||
deviceId = deviceId
|
||||
)
|
||||
} else {
|
||||
PasswordLoginParams.userIdentifier(login, password, deviceName)
|
||||
PasswordLoginParams.userIdentifier(
|
||||
user = login,
|
||||
password = password,
|
||||
deviceDisplayName = initialDeviceName,
|
||||
deviceId = deviceId
|
||||
)
|
||||
}
|
||||
val credentials = executeRequest(null) {
|
||||
authAPI.login(loginParams)
|
||||
@ -79,6 +93,14 @@ internal class DefaultLoginWizard(
|
||||
return sessionCreator.createSession(credentials, pendingSessionData.homeServerConnectionConfig)
|
||||
}
|
||||
|
||||
override suspend fun loginCustom(data: JsonDict): Session {
|
||||
val credentials = executeRequest(null) {
|
||||
authAPI.login(data)
|
||||
}
|
||||
|
||||
return sessionCreator.createSession(credentials, pendingSessionData.homeServerConnectionConfig)
|
||||
}
|
||||
|
||||
override suspend fun resetPassword(email: String, newPassword: String) {
|
||||
val param = RegisterAddThreePidTask.Params(
|
||||
RegisterThreePid.Email(email),
|
||||
|
@ -37,7 +37,8 @@ internal interface DirectLoginTask : Task<DirectLoginTask.Params, Session> {
|
||||
val homeServerConnectionConfig: HomeServerConnectionConfig,
|
||||
val userId: String,
|
||||
val password: String,
|
||||
val deviceName: String
|
||||
val deviceName: String,
|
||||
val deviceId: String?
|
||||
)
|
||||
}
|
||||
|
||||
@ -55,7 +56,12 @@ internal class DefaultDirectLoginTask @Inject constructor(
|
||||
val authAPI = retrofitFactory.create(client, homeServerUrl)
|
||||
.create(AuthAPI::class.java)
|
||||
|
||||
val loginParams = PasswordLoginParams.userIdentifier(params.userId, params.password, params.deviceName)
|
||||
val loginParams = PasswordLoginParams.userIdentifier(
|
||||
user = params.userId,
|
||||
password = params.password,
|
||||
deviceDisplayName = params.deviceName,
|
||||
deviceId = params.deviceId
|
||||
)
|
||||
|
||||
val credentials = try {
|
||||
executeRequest(null) {
|
||||
|
@ -38,14 +38,22 @@ data class MXKey(
|
||||
/**
|
||||
* signature user Id to [deviceid][signature]
|
||||
*/
|
||||
private val signatures: Map<String, Map<String, String>>
|
||||
private val signatures: Map<String, Map<String, String>>,
|
||||
|
||||
/**
|
||||
* We have to store the original json because it can contain other fields
|
||||
* that we don't support yet but they would be needed to check signatures
|
||||
*/
|
||||
private val rawMap: JsonDict
|
||||
) {
|
||||
|
||||
/**
|
||||
* @return the signed data map
|
||||
*/
|
||||
fun signalableJSONDictionary(): Map<String, Any> {
|
||||
return mapOf("key" to value)
|
||||
return rawMap.filter {
|
||||
it.key != "signatures" && it.key != "unsigned"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -82,6 +90,7 @@ data class MXKey(
|
||||
* <pre>
|
||||
* "signed_curve25519:AAAAFw": {
|
||||
* "key": "IjwIcskng7YjYcn0tS8TUOT2OHHtBSfMpcfIczCgXj4",
|
||||
* "fallback" : true|false
|
||||
* "signatures": {
|
||||
* "@userId:matrix.org": {
|
||||
* "ed25519:GMJRREOASV": "EUjp6pXzK9u3SDFR\/qLbzpOi3bEREeI6qMnKzXu992HsfuDDZftfJfiUXv9b\/Hqq1og4qM\/vCQJGTHAWMmgkCg"
|
||||
@ -107,7 +116,8 @@ data class MXKey(
|
||||
type = components[0],
|
||||
keyId = components[1],
|
||||
value = params["key"] as String,
|
||||
signatures = params["signatures"] as Map<String, Map<String, String>>
|
||||
signatures = params["signatures"] as Map<String, Map<String, String>>,
|
||||
rawMap = params
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -34,7 +34,7 @@ internal interface SendEventTask : Task<SendEventTask.Params, String> {
|
||||
|
||||
internal class DefaultSendEventTask @Inject constructor(
|
||||
private val localEchoRepository: LocalEchoRepository,
|
||||
private val encryptEventTask: DefaultEncryptEventTask,
|
||||
private val encryptEventTask: EncryptEventTask,
|
||||
private val loadRoomMembersTask: LoadRoomMembersTask,
|
||||
private val roomAPI: RoomAPI,
|
||||
private val globalErrorReceiver: GlobalErrorReceiver) : SendEventTask {
|
||||
|
@ -34,7 +34,7 @@ internal interface SendVerificationMessageTask : Task<SendVerificationMessageTas
|
||||
|
||||
internal class DefaultSendVerificationMessageTask @Inject constructor(
|
||||
private val localEchoRepository: LocalEchoRepository,
|
||||
private val encryptEventTask: DefaultEncryptEventTask,
|
||||
private val encryptEventTask: EncryptEventTask,
|
||||
private val roomAPI: RoomAPI,
|
||||
private val cryptoSessionInfoProvider: CryptoSessionInfoProvider,
|
||||
private val globalErrorReceiver: GlobalErrorReceiver) : SendVerificationMessageTask {
|
||||
|
@ -45,17 +45,30 @@ import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields
|
||||
import org.matrix.android.sdk.internal.database.model.presence.UserPresenceEntityFields
|
||||
import org.matrix.android.sdk.internal.di.MoshiProvider
|
||||
import org.matrix.android.sdk.internal.query.process
|
||||
import org.matrix.android.sdk.internal.util.Normalizer
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
internal object RealmSessionStoreMigration : RealmMigration {
|
||||
internal class RealmSessionStoreMigration @Inject constructor(
|
||||
private val normalizer: Normalizer
|
||||
) : RealmMigration {
|
||||
|
||||
// SC-specific DB changes on top of Element
|
||||
// 1: added markedUnread field
|
||||
const val SESSION_STORE_SCHEMA_SC_VERSION = 4L
|
||||
const val SESSION_STORE_SCHEMA_SC_VERSION_OFFSET = (1L shl 12)
|
||||
companion object {
|
||||
// SC-specific DB changes on top of Element
|
||||
// 1: added markedUnread field
|
||||
const val SESSION_STORE_SCHEMA_SC_VERSION = 4L
|
||||
const val SESSION_STORE_SCHEMA_SC_VERSION_OFFSET = (1L shl 12)
|
||||
|
||||
const val SESSION_STORE_SCHEMA_VERSION = 18L +
|
||||
SESSION_STORE_SCHEMA_SC_VERSION * SESSION_STORE_SCHEMA_SC_VERSION_OFFSET
|
||||
const val SESSION_STORE_SCHEMA_VERSION = 19L +
|
||||
SESSION_STORE_SCHEMA_SC_VERSION * SESSION_STORE_SCHEMA_SC_VERSION_OFFSET
|
||||
}
|
||||
|
||||
/**
|
||||
* Forces all RealmSessionStoreMigration instances to be equal
|
||||
* Avoids Realm throwing when multiple instances of the migration are set
|
||||
*/
|
||||
override fun equals(other: Any?) = other is RealmSessionStoreMigration
|
||||
override fun hashCode() = 1000
|
||||
|
||||
|
||||
override fun migrate(realm: DynamicRealm, combinedOldVersion: Long, newVersion: Long) {
|
||||
@ -82,6 +95,7 @@ internal object RealmSessionStoreMigration : RealmMigration {
|
||||
if (oldVersion <= 15) migrateTo16(realm)
|
||||
if (oldVersion <= 16) migrateTo17(realm)
|
||||
if (oldVersion <= 17) migrateTo18(realm)
|
||||
if (oldVersion <= 18) migrateTo19(realm)
|
||||
|
||||
if (oldScVersion <= 0) migrateToSc1(realm)
|
||||
if (oldScVersion <= 1) migrateToSc2(realm)
|
||||
@ -410,4 +424,16 @@ internal object RealmSessionStoreMigration : RealmMigration {
|
||||
realm.schema.get("RoomMemberSummaryEntity")
|
||||
?.addRealmObjectField(RoomMemberSummaryEntityFields.USER_PRESENCE_ENTITY.`$`, userPresenceEntity)
|
||||
}
|
||||
|
||||
private fun migrateTo19(realm: DynamicRealm) {
|
||||
Timber.d("Step 18 -> 19")
|
||||
realm.schema.get("RoomSummaryEntity")
|
||||
?.addField(RoomSummaryEntityFields.NORMALIZED_DISPLAY_NAME, String::class.java)
|
||||
?.transform {
|
||||
it.getString(RoomSummaryEntityFields.DISPLAY_NAME)?.let { displayName ->
|
||||
val normalised = normalizer.normalize(displayName)
|
||||
it.set(RoomSummaryEntityFields.NORMALIZED_DISPLAY_NAME, normalised)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -40,6 +40,7 @@ private const val REALM_NAME = "disk_store.realm"
|
||||
*/
|
||||
internal class SessionRealmConfigurationFactory @Inject constructor(
|
||||
private val realmKeysUtils: RealmKeysUtils,
|
||||
private val realmSessionStoreMigration: RealmSessionStoreMigration,
|
||||
@SessionFilesDirectory val directory: File,
|
||||
@SessionId val sessionId: String,
|
||||
@UserMd5 val userMd5: String,
|
||||
@ -71,7 +72,7 @@ internal class SessionRealmConfigurationFactory @Inject constructor(
|
||||
.allowWritesOnUiThread(true)
|
||||
.modules(SessionRealmModule())
|
||||
.schemaVersion(RealmSessionStoreMigration.SESSION_STORE_SCHEMA_VERSION)
|
||||
.migration(RealmSessionStoreMigration)
|
||||
.migration(realmSessionStoreMigration)
|
||||
.build()
|
||||
|
||||
// Try creating a realm instance and if it succeeds we can clear the flag
|
||||
|
@ -21,13 +21,13 @@ import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||
import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo
|
||||
import org.matrix.android.sdk.api.session.room.model.SpaceParentInfo
|
||||
import org.matrix.android.sdk.api.session.room.model.tag.RoomTag
|
||||
import org.matrix.android.sdk.api.session.typing.TypingUsersTracker
|
||||
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity
|
||||
import org.matrix.android.sdk.internal.database.model.presence.toUserPresence
|
||||
import org.matrix.android.sdk.internal.session.typing.DefaultTypingUsersTracker
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class RoomSummaryMapper @Inject constructor(private val timelineEventMapper: TimelineEventMapper,
|
||||
private val typingUsersTracker: DefaultTypingUsersTracker) {
|
||||
private val typingUsersTracker: TypingUsersTracker) {
|
||||
|
||||
fun map(roomSummaryEntity: RoomSummaryEntity): RoomSummary {
|
||||
val tags = roomSummaryEntity.tags().map {
|
||||
@ -48,7 +48,7 @@ internal class RoomSummaryMapper @Inject constructor(private val timelineEventMa
|
||||
|
||||
return RoomSummary(
|
||||
roomId = roomSummaryEntity.roomId,
|
||||
displayName = roomSummaryEntity.displayName ?: "",
|
||||
displayName = roomSummaryEntity.displayName() ?: "",
|
||||
name = roomSummaryEntity.name ?: "",
|
||||
topic = roomSummaryEntity.topic ?: "",
|
||||
avatarUrl = roomSummaryEntity.avatarUrl ?: "",
|
||||
|
@ -31,6 +31,7 @@ import org.matrix.android.sdk.api.session.room.model.VersioningState
|
||||
import org.matrix.android.sdk.api.session.room.model.tag.RoomTag
|
||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||
import org.matrix.android.sdk.internal.database.model.presence.UserPresenceEntity
|
||||
import org.matrix.android.sdk.internal.session.room.membership.RoomName
|
||||
import timber.log.Timber
|
||||
|
||||
internal open class RoomSummaryEntity(
|
||||
@ -40,10 +41,24 @@ internal open class RoomSummaryEntity(
|
||||
var children: RealmList<SpaceChildSummaryEntity> = RealmList()
|
||||
) : RealmObject() {
|
||||
|
||||
var displayName: String? = ""
|
||||
set(value) {
|
||||
if (value != field) field = value
|
||||
private var displayName: String? = ""
|
||||
|
||||
fun displayName() = displayName
|
||||
|
||||
fun setDisplayName(roomName: RoomName) {
|
||||
if (roomName.name != displayName) {
|
||||
displayName = roomName.name
|
||||
normalizedDisplayName = roomName.normalizedName
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Workaround for Realm only supporting Latin-1 character sets when sorting
|
||||
* or filtering by case
|
||||
* See https://github.com/realm/realm-core/issues/777
|
||||
*/
|
||||
private var normalizedDisplayName: String? = ""
|
||||
|
||||
var avatarUrl: String? = ""
|
||||
set(value) {
|
||||
if (value != field) field = value
|
||||
@ -355,6 +370,7 @@ internal open class RoomSummaryEntity(
|
||||
roomEncryptionTrustLevelStr = value?.name
|
||||
}
|
||||
}
|
||||
|
||||
companion object
|
||||
|
||||
|
||||
|
@ -37,3 +37,7 @@ internal annotation class CryptoDatabase
|
||||
@Qualifier
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
internal annotation class IdentityDatabase
|
||||
|
||||
@Qualifier
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
internal annotation class ContentScannerDatabase
|
||||
|
@ -20,6 +20,7 @@ import com.facebook.stetho.okhttp3.StethoInterceptor
|
||||
import com.squareup.moshi.Moshi
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import okhttp3.ConnectionSpec
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.logging.HttpLoggingInterceptor
|
||||
import org.matrix.android.sdk.BuildConfig
|
||||
@ -29,6 +30,7 @@ import org.matrix.android.sdk.internal.network.TimeOutInterceptor
|
||||
import org.matrix.android.sdk.internal.network.UserAgentInterceptor
|
||||
import org.matrix.android.sdk.internal.network.interceptors.CurlLoggingInterceptor
|
||||
import org.matrix.android.sdk.internal.network.interceptors.FormattedJsonHttpLogger
|
||||
import java.util.Collections
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
@Module
|
||||
@ -66,6 +68,8 @@ internal object NetworkModule {
|
||||
httpLoggingInterceptor: HttpLoggingInterceptor,
|
||||
curlLoggingInterceptor: CurlLoggingInterceptor,
|
||||
apiInterceptor: ApiInterceptor): OkHttpClient {
|
||||
val spec = ConnectionSpec.Builder(matrixConfiguration.connectionSpec).build()
|
||||
|
||||
return OkHttpClient.Builder()
|
||||
.connectTimeout(30, TimeUnit.SECONDS)
|
||||
.readTimeout(60, TimeUnit.SECONDS)
|
||||
@ -87,6 +91,7 @@ internal object NetworkModule {
|
||||
proxy(it)
|
||||
}
|
||||
}
|
||||
.connectionSpecs(Collections.singletonList(spec))
|
||||
.build()
|
||||
}
|
||||
|
||||
|
@ -38,6 +38,9 @@ internal object NetworkConstants {
|
||||
// Integration
|
||||
const val URI_INTEGRATION_MANAGER_PATH = "_matrix/integrations/v1/"
|
||||
|
||||
// Content scanner
|
||||
const val URI_API_PREFIX_PATH_MEDIA_PROXY_UNSTABLE = "_matrix/media_proxy/unstable/"
|
||||
|
||||
// Federation
|
||||
const val URI_FEDERATION_PATH = "_matrix/federation/v1/"
|
||||
}
|
||||
|
@ -177,15 +177,13 @@ internal object CertUtil {
|
||||
|
||||
val trustPinned = arrayOf<TrustManager>(PinnedTrustManagerProvider.provide(hsConfig.allowedFingerprints, defaultTrustManager))
|
||||
|
||||
val sslSocketFactory: SSLSocketFactory
|
||||
|
||||
if (hsConfig.forceUsageTlsVersions && hsConfig.tlsVersions != null) {
|
||||
val sslSocketFactory = if (hsConfig.forceUsageTlsVersions && !hsConfig.tlsVersions.isNullOrEmpty()) {
|
||||
// Force usage of accepted Tls Versions for Android < 20
|
||||
sslSocketFactory = TLSSocketFactory(trustPinned, hsConfig.tlsVersions)
|
||||
TLSSocketFactory(trustPinned, hsConfig.tlsVersions)
|
||||
} else {
|
||||
val sslContext = SSLContext.getInstance("TLS")
|
||||
sslContext.init(null, trustPinned, java.security.SecureRandom())
|
||||
sslSocketFactory = sslContext.socketFactory
|
||||
sslContext.socketFactory
|
||||
}
|
||||
|
||||
return PinnedSSLSocketFactory(sslSocketFactory, defaultTrustManager!!)
|
||||
@ -237,14 +235,14 @@ internal object CertUtil {
|
||||
* @return a list of accepted TLS specifications.
|
||||
*/
|
||||
fun newConnectionSpecs(hsConfig: HomeServerConnectionConfig): List<ConnectionSpec> {
|
||||
val builder = ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
|
||||
val builder = ConnectionSpec.Builder(ConnectionSpec.RESTRICTED_TLS)
|
||||
val tlsVersions = hsConfig.tlsVersions
|
||||
if (null != tlsVersions && tlsVersions.isNotEmpty()) {
|
||||
if (!tlsVersions.isNullOrEmpty()) {
|
||||
builder.tlsVersions(*tlsVersions.toTypedArray())
|
||||
}
|
||||
|
||||
val tlsCipherSuites = hsConfig.tlsCipherSuites
|
||||
if (null != tlsCipherSuites && tlsCipherSuites.isNotEmpty()) {
|
||||
if (!tlsCipherSuites.isNullOrEmpty()) {
|
||||
builder.cipherSuites(*tlsCipherSuites.toTypedArray())
|
||||
}
|
||||
|
||||
|
@ -20,24 +20,41 @@ import io.realm.Case
|
||||
import io.realm.RealmObject
|
||||
import io.realm.RealmQuery
|
||||
import org.matrix.android.sdk.api.query.QueryStringValue
|
||||
import timber.log.Timber
|
||||
import org.matrix.android.sdk.api.query.QueryStringValue.ContentQueryStringValue
|
||||
import org.matrix.android.sdk.internal.util.Normalizer
|
||||
import javax.inject.Inject
|
||||
|
||||
fun <T : RealmObject> RealmQuery<T>.process(field: String, queryStringValue: QueryStringValue): RealmQuery<T> {
|
||||
when (queryStringValue) {
|
||||
is QueryStringValue.NoCondition -> Timber.v("No condition to process")
|
||||
is QueryStringValue.IsNotNull -> isNotNull(field)
|
||||
is QueryStringValue.IsNull -> isNull(field)
|
||||
is QueryStringValue.IsEmpty -> isEmpty(field)
|
||||
is QueryStringValue.IsNotEmpty -> isNotEmpty(field)
|
||||
is QueryStringValue.Equals -> equalTo(field, queryStringValue.string, queryStringValue.case.toRealmCase())
|
||||
is QueryStringValue.Contains -> contains(field, queryStringValue.string, queryStringValue.case.toRealmCase())
|
||||
class QueryStringValueProcessor @Inject constructor(
|
||||
private val normalizer: Normalizer
|
||||
) {
|
||||
|
||||
fun <T : RealmObject> RealmQuery<T>.process(field: String, queryStringValue: QueryStringValue): RealmQuery<T> {
|
||||
return when (queryStringValue) {
|
||||
is QueryStringValue.NoCondition -> this
|
||||
is QueryStringValue.IsNotNull -> isNotNull(field)
|
||||
is QueryStringValue.IsNull -> isNull(field)
|
||||
is QueryStringValue.IsEmpty -> isEmpty(field)
|
||||
is QueryStringValue.IsNotEmpty -> isNotEmpty(field)
|
||||
is ContentQueryStringValue -> when (queryStringValue) {
|
||||
is QueryStringValue.Equals -> equalTo(field, queryStringValue.toRealmValue(), queryStringValue.case.toRealmCase())
|
||||
is QueryStringValue.Contains -> contains(field, queryStringValue.toRealmValue(), queryStringValue.case.toRealmCase())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun ContentQueryStringValue.toRealmValue(): String {
|
||||
return when (case) {
|
||||
QueryStringValue.Case.NORMALIZED -> normalizer.normalize(string)
|
||||
QueryStringValue.Case.SENSITIVE,
|
||||
QueryStringValue.Case.INSENSITIVE -> string
|
||||
}
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
private fun QueryStringValue.Case.toRealmCase(): Case {
|
||||
return when (this) {
|
||||
QueryStringValue.Case.INSENSITIVE -> Case.INSENSITIVE
|
||||
QueryStringValue.Case.SENSITIVE -> Case.SENSITIVE
|
||||
QueryStringValue.Case.SENSITIVE,
|
||||
QueryStringValue.Case.NORMALIZED -> Case.SENSITIVE
|
||||
}
|
||||
}
|
||||
|
@ -23,8 +23,10 @@ import androidx.core.content.FileProvider
|
||||
import kotlinx.coroutines.CompletableDeferred
|
||||
import kotlinx.coroutines.completeWith
|
||||
import kotlinx.coroutines.withContext
|
||||
import okhttp3.MediaType.Companion.toMediaType
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import okhttp3.RequestBody.Companion.toRequestBody
|
||||
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
|
||||
import org.matrix.android.sdk.api.failure.Failure
|
||||
import org.matrix.android.sdk.api.session.content.ContentUrlResolver
|
||||
@ -118,12 +120,24 @@ internal class DefaultFileService @Inject constructor(
|
||||
val cachedFiles = getFiles(url, fileName, mimeType, elementToDecrypt != null)
|
||||
|
||||
if (!cachedFiles.file.exists()) {
|
||||
val resolvedUrl = contentUrlResolver.resolveFullSize(url) ?: throw IllegalArgumentException("url is null")
|
||||
val resolvedUrl = contentUrlResolver.resolveForDownload(url, elementToDecrypt) ?: throw IllegalArgumentException("url is null")
|
||||
|
||||
val request = Request.Builder()
|
||||
.url(resolvedUrl)
|
||||
.header(DOWNLOAD_PROGRESS_INTERCEPTOR_HEADER, url)
|
||||
.build()
|
||||
val request = when (resolvedUrl) {
|
||||
is ContentUrlResolver.ResolvedMethod.GET -> {
|
||||
Request.Builder()
|
||||
.url(resolvedUrl.url)
|
||||
.header(DOWNLOAD_PROGRESS_INTERCEPTOR_HEADER, url)
|
||||
.build()
|
||||
}
|
||||
|
||||
is ContentUrlResolver.ResolvedMethod.POST -> {
|
||||
Request.Builder()
|
||||
.url(resolvedUrl.url)
|
||||
.header(DOWNLOAD_PROGRESS_INTERCEPTOR_HEADER, url)
|
||||
.post(resolvedUrl.jsonBody.toRequestBody("application/json".toMediaType()))
|
||||
.build()
|
||||
}
|
||||
}
|
||||
|
||||
val response = try {
|
||||
okHttpClient.newCall(request).execute()
|
||||
|
@ -35,12 +35,14 @@ import org.matrix.android.sdk.api.session.cache.CacheService
|
||||
import org.matrix.android.sdk.api.session.call.CallSignalingService
|
||||
import org.matrix.android.sdk.api.session.content.ContentUploadStateTracker
|
||||
import org.matrix.android.sdk.api.session.content.ContentUrlResolver
|
||||
import org.matrix.android.sdk.api.session.contentscanner.ContentScannerService
|
||||
import org.matrix.android.sdk.api.session.crypto.CryptoService
|
||||
import org.matrix.android.sdk.api.session.events.EventService
|
||||
import org.matrix.android.sdk.api.session.file.ContentDownloadStateTracker
|
||||
import org.matrix.android.sdk.api.session.file.FileService
|
||||
import org.matrix.android.sdk.api.session.group.GroupService
|
||||
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilitiesService
|
||||
import org.matrix.android.sdk.api.session.identity.IdentityService
|
||||
import org.matrix.android.sdk.api.session.initsync.SyncStatusService
|
||||
import org.matrix.android.sdk.api.session.integrationmanager.IntegrationManagerService
|
||||
import org.matrix.android.sdk.api.session.media.MediaService
|
||||
@ -72,7 +74,6 @@ import org.matrix.android.sdk.internal.di.SessionId
|
||||
import org.matrix.android.sdk.internal.di.UnauthenticatedWithCertificate
|
||||
import org.matrix.android.sdk.internal.di.WorkManagerProvider
|
||||
import org.matrix.android.sdk.internal.network.GlobalErrorHandler
|
||||
import org.matrix.android.sdk.internal.session.identity.DefaultIdentityService
|
||||
import org.matrix.android.sdk.internal.session.sync.SyncTokenStore
|
||||
import org.matrix.android.sdk.internal.session.sync.job.SyncThread
|
||||
import org.matrix.android.sdk.internal.session.sync.job.SyncWorker
|
||||
@ -124,7 +125,8 @@ internal class DefaultSession @Inject constructor(
|
||||
private val _sharedSecretStorageService: Lazy<SharedSecretStorageService>,
|
||||
private val accountService: Lazy<AccountService>,
|
||||
private val eventService: Lazy<EventService>,
|
||||
private val defaultIdentityService: DefaultIdentityService,
|
||||
private val contentScannerService: Lazy<ContentScannerService>,
|
||||
private val identityService: IdentityService,
|
||||
private val integrationManagerService: IntegrationManagerService,
|
||||
private val thirdPartyService: Lazy<ThirdPartyService>,
|
||||
private val callSignalingService: Lazy<CallSignalingService>,
|
||||
@ -174,8 +176,8 @@ internal class DefaultSession @Inject constructor(
|
||||
lifecycleObservers.forEach {
|
||||
it.onSessionStarted(this)
|
||||
}
|
||||
sessionListeners.dispatch { _, listener ->
|
||||
listener.onSessionStarted(this)
|
||||
dispatchTo(sessionListeners) { session, listener ->
|
||||
listener.onSessionStarted(session)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -217,8 +219,8 @@ internal class DefaultSession @Inject constructor(
|
||||
// timelineEventDecryptor.destroy()
|
||||
uiHandler.post {
|
||||
lifecycleObservers.forEach { it.onSessionStopped(this) }
|
||||
sessionListeners.dispatch { _, listener ->
|
||||
listener.onSessionStopped(this)
|
||||
dispatchTo(sessionListeners) { session, listener ->
|
||||
listener.onSessionStopped(session)
|
||||
}
|
||||
}
|
||||
cryptoService.get().close()
|
||||
@ -249,8 +251,8 @@ internal class DefaultSession @Inject constructor(
|
||||
lifecycleObservers.forEach {
|
||||
it.onClearCache(this)
|
||||
}
|
||||
sessionListeners.dispatch { _, listener ->
|
||||
listener.onClearCache(this)
|
||||
dispatchTo(sessionListeners) { session, listener ->
|
||||
listener.onClearCache(session)
|
||||
}
|
||||
}
|
||||
withContext(NonCancellable) {
|
||||
@ -260,8 +262,8 @@ internal class DefaultSession @Inject constructor(
|
||||
}
|
||||
|
||||
override fun onGlobalError(globalError: GlobalError) {
|
||||
sessionListeners.dispatch { _, listener ->
|
||||
listener.onGlobalError(this, globalError)
|
||||
dispatchTo(sessionListeners) { session, listener ->
|
||||
listener.onGlobalError(session, globalError)
|
||||
}
|
||||
}
|
||||
|
||||
@ -275,7 +277,9 @@ internal class DefaultSession @Inject constructor(
|
||||
|
||||
override fun cryptoService(): CryptoService = cryptoService.get()
|
||||
|
||||
override fun identityService() = defaultIdentityService
|
||||
override fun contentScannerService(): ContentScannerService = contentScannerService.get()
|
||||
|
||||
override fun identityService() = identityService
|
||||
|
||||
override fun fileService(): FileService = defaultFileService.get()
|
||||
|
||||
|
@ -36,6 +36,7 @@ import org.matrix.android.sdk.internal.session.cache.CacheModule
|
||||
import org.matrix.android.sdk.internal.session.call.CallModule
|
||||
import org.matrix.android.sdk.internal.session.content.ContentModule
|
||||
import org.matrix.android.sdk.internal.session.content.UploadContentWorker
|
||||
import org.matrix.android.sdk.internal.session.contentscanner.ContentScannerModule
|
||||
import org.matrix.android.sdk.internal.session.filter.FilterModule
|
||||
import org.matrix.android.sdk.internal.session.group.GetGroupDataWorker
|
||||
import org.matrix.android.sdk.internal.session.group.GroupModule
|
||||
@ -94,6 +95,7 @@ import org.matrix.android.sdk.internal.util.system.SystemModule
|
||||
AccountModule::class,
|
||||
FederationModule::class,
|
||||
CallModule::class,
|
||||
ContentScannerModule::class,
|
||||
SearchModule::class,
|
||||
ThirdPartyModule::class,
|
||||
SpaceModule::class,
|
||||
|
@ -18,15 +18,11 @@ package org.matrix.android.sdk.internal.session
|
||||
|
||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
import org.matrix.android.sdk.internal.SessionManager
|
||||
import org.matrix.android.sdk.internal.di.SessionId
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
@SessionScope
|
||||
internal class SessionListeners @Inject constructor(
|
||||
@SessionId private val sessionId: String,
|
||||
private val sessionManager: SessionManager) {
|
||||
internal class SessionListeners @Inject constructor() {
|
||||
|
||||
private val listeners = mutableSetOf<Session.Listener>()
|
||||
|
||||
@ -42,18 +38,19 @@ internal class SessionListeners @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
fun dispatch(block: (Session, Session.Listener) -> Unit) {
|
||||
fun dispatch(session: Session, block: (Session, Session.Listener) -> Unit) {
|
||||
synchronized(listeners) {
|
||||
val session = getSession() ?: return Unit.also {
|
||||
Timber.w("You don't have any attached session")
|
||||
}
|
||||
listeners.forEach {
|
||||
tryOrNull { block(session, it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getSession(): Session? {
|
||||
return sessionManager.getSessionComponent(sessionId)?.session()
|
||||
}
|
||||
}
|
||||
|
||||
internal fun Session?.dispatchTo(sessionListeners: SessionListeners, block: (Session, Session.Listener) -> Unit) {
|
||||
if (this == null) {
|
||||
Timber.w("You don't have any attached session")
|
||||
return
|
||||
}
|
||||
sessionListeners.dispatch(this, block)
|
||||
}
|
||||
|
@ -163,6 +163,7 @@ internal abstract class SessionModule {
|
||||
@JvmStatic
|
||||
@Provides
|
||||
@SessionFilesDirectory
|
||||
@SessionScope
|
||||
fun providesFilesDir(@UserMd5 userMd5: String,
|
||||
@SessionId sessionId: String,
|
||||
context: Context): File {
|
||||
|
@ -44,7 +44,7 @@ internal class DefaultDeactivateAccountTask @Inject constructor(
|
||||
|
||||
override suspend fun execute(params: DeactivateAccountTask.Params) {
|
||||
val deactivateAccountParams = DeactivateAccountParams.create(params.userAuthParam, params.eraseAllData)
|
||||
|
||||
cleanupSession.stopActiveTasks()
|
||||
val canCleanup = try {
|
||||
executeRequest(globalErrorReceiver) {
|
||||
accountAPI.deactivate(deactivateAccountParams)
|
||||
@ -71,7 +71,7 @@ internal class DefaultDeactivateAccountTask @Inject constructor(
|
||||
runCatching { identityDisconnectTask.execute(Unit) }
|
||||
.onFailure { Timber.w(it, "Unable to disconnect identity server") }
|
||||
|
||||
cleanupSession.handle()
|
||||
cleanupSession.cleanup()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -50,20 +50,26 @@ internal class CleanupSession @Inject constructor(
|
||||
@CryptoDatabase private val realmCryptoConfiguration: RealmConfiguration,
|
||||
@UserMd5 private val userMd5: String
|
||||
) {
|
||||
suspend fun handle() {
|
||||
|
||||
fun stopActiveTasks() {
|
||||
Timber.d("Cleanup: cancel pending works...")
|
||||
workManagerProvider.cancelAllWorks()
|
||||
|
||||
Timber.d("Cleanup: stop session...")
|
||||
sessionManager.stopSession(sessionId)
|
||||
}
|
||||
|
||||
suspend fun cleanup() {
|
||||
val sessionRealmCount = Realm.getGlobalInstanceCount(realmSessionConfiguration)
|
||||
val cryptoRealmCount = Realm.getGlobalInstanceCount(realmCryptoConfiguration)
|
||||
Timber.d("Realm instance ($sessionRealmCount - $cryptoRealmCount)")
|
||||
|
||||
Timber.d("Cleanup: delete session params...")
|
||||
sessionParamsStore.delete(sessionId)
|
||||
|
||||
Timber.d("Cleanup: cancel pending works...")
|
||||
workManagerProvider.cancelAllWorks()
|
||||
|
||||
Timber.d("Cleanup: release session...")
|
||||
sessionManager.releaseSession(sessionId)
|
||||
|
||||
Timber.d("Cleanup: delete session params...")
|
||||
sessionParamsStore.delete(sessionId)
|
||||
|
||||
Timber.d("Cleanup: clear session data...")
|
||||
clearSessionDataTask.execute(Unit)
|
||||
|
||||
|
@ -16,20 +16,45 @@
|
||||
|
||||
package org.matrix.android.sdk.internal.session.content
|
||||
|
||||
import org.matrix.android.sdk.api.MatrixUrls
|
||||
import org.matrix.android.sdk.api.MatrixUrls.isMxcUrl
|
||||
import org.matrix.android.sdk.api.MatrixUrls.removeMxcPrefix
|
||||
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
|
||||
import org.matrix.android.sdk.api.session.content.ContentUrlResolver
|
||||
import org.matrix.android.sdk.api.session.contentscanner.ContentScannerService
|
||||
import org.matrix.android.sdk.internal.crypto.attachments.ElementToDecrypt
|
||||
import org.matrix.android.sdk.internal.network.NetworkConstants
|
||||
import org.matrix.android.sdk.internal.session.contentscanner.ScanEncryptorUtils
|
||||
import org.matrix.android.sdk.internal.session.contentscanner.model.toJson
|
||||
import org.matrix.android.sdk.internal.util.ensureTrailingSlash
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class DefaultContentUrlResolver @Inject constructor(homeServerConnectionConfig: HomeServerConnectionConfig) : ContentUrlResolver {
|
||||
internal class DefaultContentUrlResolver @Inject constructor(
|
||||
homeServerConnectionConfig: HomeServerConnectionConfig,
|
||||
private val scannerService: ContentScannerService
|
||||
) : ContentUrlResolver {
|
||||
|
||||
private val baseUrl = homeServerConnectionConfig.homeServerUriBase.toString().ensureTrailingSlash()
|
||||
|
||||
override val uploadUrl = baseUrl + NetworkConstants.URI_API_MEDIA_PREFIX_PATH_R0 + "upload"
|
||||
|
||||
override fun resolveForDownload(contentUrl: String?, elementToDecrypt: ElementToDecrypt?): ContentUrlResolver.ResolvedMethod? {
|
||||
return if (scannerService.isScannerEnabled() && elementToDecrypt != null) {
|
||||
val baseUrl = scannerService.getContentScannerServer()
|
||||
val sep = if (baseUrl?.endsWith("/") == true) "" else "/"
|
||||
|
||||
val url = baseUrl + sep + NetworkConstants.URI_API_PREFIX_PATH_MEDIA_PROXY_UNSTABLE + "download_encrypted"
|
||||
|
||||
ContentUrlResolver.ResolvedMethod.POST(
|
||||
url = url,
|
||||
jsonBody = ScanEncryptorUtils
|
||||
.getDownloadBodyAndEncryptIfNeeded(scannerService.serverPublicKey, contentUrl ?: "", elementToDecrypt)
|
||||
.toJson()
|
||||
)
|
||||
} else {
|
||||
resolveFullSize(contentUrl)?.let { ContentUrlResolver.ResolvedMethod.GET(it) }
|
||||
}
|
||||
}
|
||||
|
||||
override fun resolveFullSize(contentUrl: String?): String? {
|
||||
return contentUrl
|
||||
// do not allow non-mxc content URLs
|
||||
@ -37,7 +62,7 @@ internal class DefaultContentUrlResolver @Inject constructor(homeServerConnectio
|
||||
?.let {
|
||||
resolve(
|
||||
contentUrl = it,
|
||||
prefix = NetworkConstants.URI_API_MEDIA_PREFIX_PATH_R0 + "download/"
|
||||
toThumbnail = false
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -49,16 +74,27 @@ internal class DefaultContentUrlResolver @Inject constructor(homeServerConnectio
|
||||
?.let {
|
||||
resolve(
|
||||
contentUrl = it,
|
||||
prefix = NetworkConstants.URI_API_MEDIA_PREFIX_PATH_R0 + "thumbnail/",
|
||||
toThumbnail = true,
|
||||
params = "?width=$width&height=$height&method=${method.value}"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun resolve(contentUrl: String,
|
||||
prefix: String,
|
||||
params: String = ""): String? {
|
||||
var serverAndMediaId = contentUrl.removePrefix(MatrixUrls.MATRIX_CONTENT_URI_SCHEME)
|
||||
toThumbnail: Boolean,
|
||||
params: String = ""): String {
|
||||
var serverAndMediaId = contentUrl.removeMxcPrefix()
|
||||
|
||||
val apiPath = if (scannerService.isScannerEnabled()) {
|
||||
NetworkConstants.URI_API_PREFIX_PATH_MEDIA_PROXY_UNSTABLE
|
||||
} else {
|
||||
NetworkConstants.URI_API_MEDIA_PREFIX_PATH_R0
|
||||
}
|
||||
val prefix = if (toThumbnail) {
|
||||
apiPath + "thumbnail/"
|
||||
} else {
|
||||
apiPath + "download/"
|
||||
}
|
||||
val fragmentOffset = serverAndMediaId.indexOf("#")
|
||||
var fragment = ""
|
||||
if (fragmentOffset >= 0) {
|
||||
@ -66,6 +102,11 @@ internal class DefaultContentUrlResolver @Inject constructor(homeServerConnectio
|
||||
serverAndMediaId = serverAndMediaId.substring(0, fragmentOffset)
|
||||
}
|
||||
|
||||
return baseUrl + prefix + serverAndMediaId + params + fragment
|
||||
val resolvedUrl = if (scannerService.isScannerEnabled()) {
|
||||
scannerService.getContentScannerServer()!!.ensureTrailingSlash()
|
||||
} else {
|
||||
baseUrl
|
||||
}
|
||||
return resolvedUrl + prefix + serverAndMediaId + params + fragment
|
||||
}
|
||||
}
|
||||
|
@ -35,12 +35,12 @@ import org.matrix.android.sdk.api.failure.Failure
|
||||
import org.matrix.android.sdk.api.failure.MatrixError
|
||||
import org.matrix.android.sdk.api.session.content.ContentUrlResolver
|
||||
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities
|
||||
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilitiesService
|
||||
import org.matrix.android.sdk.internal.di.Authenticated
|
||||
import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
|
||||
import org.matrix.android.sdk.internal.network.ProgressRequestBody
|
||||
import org.matrix.android.sdk.internal.network.awaitResponse
|
||||
import org.matrix.android.sdk.internal.network.toFailure
|
||||
import org.matrix.android.sdk.internal.session.homeserver.DefaultHomeServerCapabilitiesService
|
||||
import org.matrix.android.sdk.internal.util.TemporaryFileCreator
|
||||
import java.io.File
|
||||
import java.io.FileNotFoundException
|
||||
@ -50,7 +50,7 @@ import javax.inject.Inject
|
||||
internal class FileUploader @Inject constructor(
|
||||
@Authenticated private val okHttpClient: OkHttpClient,
|
||||
private val globalErrorReceiver: GlobalErrorReceiver,
|
||||
private val homeServerCapabilitiesService: DefaultHomeServerCapabilitiesService,
|
||||
private val homeServerCapabilitiesService: HomeServerCapabilitiesService,
|
||||
private val context: Context,
|
||||
private val temporaryFileCreator: TemporaryFileCreator,
|
||||
contentUrlResolver: ContentUrlResolver,
|
||||
|
@ -217,8 +217,11 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
|
||||
.also { filesToDelete.add(it) }
|
||||
}
|
||||
VideoCompressionResult.CompressionNotNeeded,
|
||||
VideoCompressionResult.CompressionCancelled,
|
||||
VideoCompressionResult.CompressionCancelled -> {
|
||||
workingFile
|
||||
}
|
||||
is VideoCompressionResult.CompressionFailed -> {
|
||||
Timber.e(videoCompressionResult.failure, "Video compression failed")
|
||||
workingFile
|
||||
}
|
||||
}
|
||||
|
@ -44,7 +44,7 @@ internal class VideoCompressor @Inject constructor(
|
||||
var result: Int = -1
|
||||
var failure: Throwable? = null
|
||||
Transcoder.into(destinationFile.path)
|
||||
.addDataSource(object: FilePathDataSource(videoFile.path) {
|
||||
.addDataSource(object : FilePathDataSource(videoFile.path) {
|
||||
// https://github.com/natario1/Transcoder/issues/154
|
||||
@Suppress("SENSELESS_COMPARISON") // Source is annotated as @NonNull, but can actually be null...
|
||||
override fun isInitialized(): Boolean {
|
||||
|
@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.android.sdk.internal.session.contentscanner
|
||||
|
||||
import okhttp3.ResponseBody
|
||||
import org.matrix.android.sdk.internal.network.NetworkConstants
|
||||
import org.matrix.android.sdk.internal.session.contentscanner.model.DownloadBody
|
||||
import org.matrix.android.sdk.internal.session.contentscanner.model.ScanResponse
|
||||
import org.matrix.android.sdk.internal.session.contentscanner.model.ServerPublicKeyResponse
|
||||
import retrofit2.http.Body
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.POST
|
||||
import retrofit2.http.Path
|
||||
|
||||
/**
|
||||
* https://github.com/matrix-org/matrix-content-scanner
|
||||
*/
|
||||
internal interface ContentScannerApi {
|
||||
|
||||
@POST(NetworkConstants.URI_API_PREFIX_PATH_MEDIA_PROXY_UNSTABLE + "download_encrypted")
|
||||
suspend fun downloadEncrypted(@Body info: DownloadBody): ResponseBody
|
||||
|
||||
@POST(NetworkConstants.URI_API_PREFIX_PATH_MEDIA_PROXY_UNSTABLE + "scan_encrypted")
|
||||
suspend fun scanFile(@Body info: DownloadBody): ScanResponse
|
||||
|
||||
@GET(NetworkConstants.URI_API_PREFIX_PATH_MEDIA_PROXY_UNSTABLE + "public_key")
|
||||
suspend fun getServerPublicKey(): ServerPublicKeyResponse
|
||||
|
||||
@GET(NetworkConstants.URI_API_PREFIX_PATH_MEDIA_PROXY_UNSTABLE + "scan/{domain}/{mediaId}")
|
||||
suspend fun scanMedia(@Path(value = "domain") domain: String, @Path(value = "mediaId") mediaId: String): ScanResponse
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.android.sdk.internal.session.contentscanner
|
||||
|
||||
import org.matrix.android.sdk.internal.session.SessionScope
|
||||
import javax.inject.Inject
|
||||
|
||||
@SessionScope
|
||||
internal class ContentScannerApiProvider @Inject constructor() {
|
||||
var contentScannerApi: ContentScannerApi? = null
|
||||
}
|
@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.android.sdk.internal.session.contentscanner
|
||||
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import io.realm.RealmConfiguration
|
||||
import org.matrix.android.sdk.api.session.contentscanner.ContentScannerService
|
||||
import org.matrix.android.sdk.internal.database.RealmKeysUtils
|
||||
import org.matrix.android.sdk.internal.di.ContentScannerDatabase
|
||||
import org.matrix.android.sdk.internal.di.SessionFilesDirectory
|
||||
import org.matrix.android.sdk.internal.di.UserMd5
|
||||
import org.matrix.android.sdk.internal.session.SessionModule
|
||||
import org.matrix.android.sdk.internal.session.SessionScope
|
||||
import org.matrix.android.sdk.internal.session.contentscanner.data.ContentScannerStore
|
||||
import org.matrix.android.sdk.internal.session.contentscanner.db.ContentScannerRealmModule
|
||||
import org.matrix.android.sdk.internal.session.contentscanner.db.RealmContentScannerStore
|
||||
import org.matrix.android.sdk.internal.session.contentscanner.tasks.DefaultDownloadEncryptedTask
|
||||
import org.matrix.android.sdk.internal.session.contentscanner.tasks.DefaultGetServerPublicKeyTask
|
||||
import org.matrix.android.sdk.internal.session.contentscanner.tasks.DefaultScanEncryptedTask
|
||||
import org.matrix.android.sdk.internal.session.contentscanner.tasks.DefaultScanMediaTask
|
||||
import org.matrix.android.sdk.internal.session.contentscanner.tasks.DownloadEncryptedTask
|
||||
import org.matrix.android.sdk.internal.session.contentscanner.tasks.GetServerPublicKeyTask
|
||||
import org.matrix.android.sdk.internal.session.contentscanner.tasks.ScanEncryptedTask
|
||||
import org.matrix.android.sdk.internal.session.contentscanner.tasks.ScanMediaTask
|
||||
import java.io.File
|
||||
|
||||
@Module
|
||||
internal abstract class ContentScannerModule {
|
||||
@Module
|
||||
companion object {
|
||||
|
||||
@JvmStatic
|
||||
@Provides
|
||||
@ContentScannerDatabase
|
||||
@SessionScope
|
||||
fun providesContentScannerRealmConfiguration(realmKeysUtils: RealmKeysUtils,
|
||||
@SessionFilesDirectory directory: File,
|
||||
@UserMd5 userMd5: String): RealmConfiguration {
|
||||
return RealmConfiguration.Builder()
|
||||
.directory(directory)
|
||||
.name("matrix-sdk-content-scanning.realm")
|
||||
.apply {
|
||||
realmKeysUtils.configureEncryption(this, SessionModule.getKeyAlias(userMd5))
|
||||
}
|
||||
.allowWritesOnUiThread(true)
|
||||
.modules(ContentScannerRealmModule())
|
||||
.build()
|
||||
}
|
||||
}
|
||||
|
||||
@Binds
|
||||
abstract fun bindContentScannerService(service: DisabledContentScannerService): ContentScannerService
|
||||
|
||||
@Binds
|
||||
abstract fun bindContentScannerStore(store: RealmContentScannerStore): ContentScannerStore
|
||||
|
||||
@Binds
|
||||
abstract fun bindDownloadEncryptedTask(task: DefaultDownloadEncryptedTask): DownloadEncryptedTask
|
||||
|
||||
@Binds
|
||||
abstract fun bindGetServerPublicKeyTask(task: DefaultGetServerPublicKeyTask): GetServerPublicKeyTask
|
||||
|
||||
@Binds
|
||||
abstract fun bindScanMediaTask(task: DefaultScanMediaTask): ScanMediaTask
|
||||
|
||||
@Binds
|
||||
abstract fun bindScanEncryptedTask(task: DefaultScanEncryptedTask): ScanEncryptedTask
|
||||
}
|
@ -0,0 +1,131 @@
|
||||
/*
|
||||
* Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.android.sdk.internal.session.contentscanner
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import dagger.Lazy
|
||||
import kotlinx.coroutines.launch
|
||||
import okhttp3.OkHttpClient
|
||||
import org.matrix.android.sdk.api.session.contentscanner.ContentScannerService
|
||||
import org.matrix.android.sdk.api.session.contentscanner.ScanState
|
||||
import org.matrix.android.sdk.api.session.contentscanner.ScanStatusInfo
|
||||
import org.matrix.android.sdk.api.util.Optional
|
||||
import org.matrix.android.sdk.internal.crypto.attachments.ElementToDecrypt
|
||||
import org.matrix.android.sdk.internal.di.Unauthenticated
|
||||
import org.matrix.android.sdk.internal.network.RetrofitFactory
|
||||
import org.matrix.android.sdk.internal.session.SessionScope
|
||||
import org.matrix.android.sdk.internal.session.contentscanner.data.ContentScannerStore
|
||||
import org.matrix.android.sdk.internal.session.contentscanner.tasks.GetServerPublicKeyTask
|
||||
import org.matrix.android.sdk.internal.session.contentscanner.tasks.ScanEncryptedTask
|
||||
import org.matrix.android.sdk.internal.session.contentscanner.tasks.ScanMediaTask
|
||||
import org.matrix.android.sdk.internal.task.TaskExecutor
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
@SessionScope
|
||||
internal class DefaultContentScannerService @Inject constructor(
|
||||
private val retrofitFactory: RetrofitFactory,
|
||||
@Unauthenticated
|
||||
private val okHttpClient: Lazy<OkHttpClient>,
|
||||
private val contentScannerApiProvider: ContentScannerApiProvider,
|
||||
private val contentScannerStore: ContentScannerStore,
|
||||
private val getServerPublicKeyTask: GetServerPublicKeyTask,
|
||||
private val scanEncryptedTask: ScanEncryptedTask,
|
||||
private val scanMediaTask: ScanMediaTask,
|
||||
private val taskExecutor: TaskExecutor
|
||||
) : ContentScannerService {
|
||||
|
||||
// Cache public key in memory
|
||||
override var serverPublicKey: String? = null
|
||||
private set
|
||||
|
||||
override fun getContentScannerServer(): String? {
|
||||
return contentScannerStore.getScannerUrl()
|
||||
}
|
||||
|
||||
override suspend fun getServerPublicKey(forceDownload: Boolean): String? {
|
||||
val api = contentScannerApiProvider.contentScannerApi ?: throw IllegalArgumentException("No content scanner define")
|
||||
|
||||
if (!forceDownload && serverPublicKey != null) {
|
||||
return serverPublicKey
|
||||
}
|
||||
|
||||
return getServerPublicKeyTask.execute(GetServerPublicKeyTask.Params(api)).also {
|
||||
serverPublicKey = it
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getScanResultForAttachment(mxcUrl: String, fileInfo: ElementToDecrypt?): ScanStatusInfo {
|
||||
val result = if (fileInfo != null) {
|
||||
scanEncryptedTask.execute(ScanEncryptedTask.Params(
|
||||
mxcUrl = mxcUrl,
|
||||
publicServerKey = getServerPublicKey(false),
|
||||
encryptedInfo = fileInfo
|
||||
))
|
||||
} else {
|
||||
scanMediaTask.execute(ScanMediaTask.Params(mxcUrl))
|
||||
}
|
||||
|
||||
return ScanStatusInfo(
|
||||
state = if (result.clean) ScanState.TRUSTED else ScanState.INFECTED,
|
||||
humanReadableMessage = result.info,
|
||||
scanDateTimestamp = System.currentTimeMillis()
|
||||
)
|
||||
}
|
||||
|
||||
override fun setScannerUrl(url: String?) = contentScannerStore.setScannerUrl(url).also {
|
||||
if (url == null) {
|
||||
contentScannerApiProvider.contentScannerApi = null
|
||||
serverPublicKey = null
|
||||
} else {
|
||||
val api = retrofitFactory
|
||||
.create(okHttpClient, url)
|
||||
.create(ContentScannerApi::class.java)
|
||||
contentScannerApiProvider.contentScannerApi = api
|
||||
|
||||
taskExecutor.executorScope.launch {
|
||||
try {
|
||||
getServerPublicKey(true)
|
||||
} catch (failure: Throwable) {
|
||||
Timber.e("Failed to get public server api")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun enableScanner(enabled: Boolean) = contentScannerStore.enableScanner(enabled)
|
||||
|
||||
override fun isScannerEnabled(): Boolean = contentScannerStore.isScanEnabled()
|
||||
|
||||
override fun getCachedScanResultForFile(mxcUrl: String): ScanStatusInfo? {
|
||||
return contentScannerStore.getScanResult(mxcUrl)
|
||||
}
|
||||
|
||||
override fun getLiveStatusForFile(mxcUrl: String, fetchIfNeeded: Boolean, fileInfo: ElementToDecrypt?): LiveData<Optional<ScanStatusInfo>> {
|
||||
val data = contentScannerStore.getLiveScanResult(mxcUrl)
|
||||
if (fetchIfNeeded && !contentScannerStore.isScanResultKnownOrInProgress(mxcUrl, getContentScannerServer())) {
|
||||
taskExecutor.executorScope.launch {
|
||||
try {
|
||||
getScanResultForAttachment(mxcUrl, fileInfo)
|
||||
} catch (failure: Throwable) {
|
||||
Timber.e("Failed to get file status : ${failure.localizedMessage}")
|
||||
}
|
||||
}
|
||||
}
|
||||
return data
|
||||
}
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.android.sdk.internal.session.contentscanner
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import org.matrix.android.sdk.api.session.contentscanner.ContentScannerService
|
||||
import org.matrix.android.sdk.api.session.contentscanner.ScanStatusInfo
|
||||
import org.matrix.android.sdk.api.util.Optional
|
||||
import org.matrix.android.sdk.internal.crypto.attachments.ElementToDecrypt
|
||||
import org.matrix.android.sdk.internal.session.SessionScope
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Created to by-pass ProfileTask execution in LoginWizard.
|
||||
*/
|
||||
@SessionScope
|
||||
internal class DisabledContentScannerService @Inject constructor() : ContentScannerService {
|
||||
|
||||
override val serverPublicKey: String?
|
||||
get() = null
|
||||
|
||||
override fun getContentScannerServer(): String? {
|
||||
return null
|
||||
}
|
||||
|
||||
override suspend fun getServerPublicKey(forceDownload: Boolean): String? {
|
||||
return null
|
||||
}
|
||||
|
||||
override suspend fun getScanResultForAttachment(mxcUrl: String, fileInfo: ElementToDecrypt?): ScanStatusInfo {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun setScannerUrl(url: String?) {
|
||||
}
|
||||
|
||||
override fun enableScanner(enabled: Boolean) {
|
||||
}
|
||||
|
||||
override fun isScannerEnabled(): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun getLiveStatusForFile(mxcUrl: String, fetchIfNeeded: Boolean, fileInfo: ElementToDecrypt?): LiveData<Optional<ScanStatusInfo>> {
|
||||
return MutableLiveData()
|
||||
}
|
||||
|
||||
override fun getCachedScanResultForFile(mxcUrl: String): ScanStatusInfo? {
|
||||
return null
|
||||
}
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.android.sdk.internal.session.contentscanner
|
||||
|
||||
import org.matrix.android.sdk.internal.crypto.attachments.ElementToDecrypt
|
||||
import org.matrix.android.sdk.internal.crypto.model.rest.EncryptedFileInfo
|
||||
import org.matrix.android.sdk.internal.crypto.model.rest.EncryptedFileKey
|
||||
import org.matrix.android.sdk.internal.crypto.tools.withOlmEncryption
|
||||
import org.matrix.android.sdk.internal.session.contentscanner.model.DownloadBody
|
||||
import org.matrix.android.sdk.internal.session.contentscanner.model.EncryptedBody
|
||||
import org.matrix.android.sdk.internal.session.contentscanner.model.toCanonicalJson
|
||||
|
||||
internal object ScanEncryptorUtils {
|
||||
|
||||
fun getDownloadBodyAndEncryptIfNeeded(publicServerKey: String?, mxcUrl: String, elementToDecrypt: ElementToDecrypt): DownloadBody {
|
||||
// TODO, upstream refactoring changed the object model here...
|
||||
// it's bad we have to recreate and use hardcoded values
|
||||
val encryptedInfo = EncryptedFileInfo(
|
||||
url = mxcUrl,
|
||||
iv = elementToDecrypt.iv,
|
||||
hashes = mapOf("sha256" to elementToDecrypt.sha256),
|
||||
key = EncryptedFileKey(
|
||||
k = elementToDecrypt.k,
|
||||
alg = "A256CTR",
|
||||
keyOps = listOf("encrypt", "decrypt"),
|
||||
kty = "oct",
|
||||
ext = true
|
||||
),
|
||||
v = "v2"
|
||||
)
|
||||
return if (publicServerKey != null) {
|
||||
// We should encrypt
|
||||
withOlmEncryption { olm ->
|
||||
olm.setRecipientKey(publicServerKey)
|
||||
|
||||
val olmResult = olm.encrypt(DownloadBody(encryptedInfo).toCanonicalJson())
|
||||
DownloadBody(
|
||||
encryptedBody = EncryptedBody(
|
||||
cipherText = olmResult.mCipherText,
|
||||
ephemeral = olmResult.mEphemeralKey,
|
||||
mac = olmResult.mMac
|
||||
)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
DownloadBody(encryptedInfo)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.android.sdk.internal.session.contentscanner.data
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import org.matrix.android.sdk.api.session.contentscanner.ScanState
|
||||
import org.matrix.android.sdk.api.session.contentscanner.ScanStatusInfo
|
||||
import org.matrix.android.sdk.api.util.Optional
|
||||
|
||||
internal interface ContentScannerStore {
|
||||
|
||||
fun getScannerUrl(): String?
|
||||
|
||||
fun setScannerUrl(url: String?)
|
||||
|
||||
fun enableScanner(enabled: Boolean)
|
||||
|
||||
fun isScanEnabled(): Boolean
|
||||
|
||||
fun getScanResult(mxcUrl: String): ScanStatusInfo?
|
||||
fun getLiveScanResult(mxcUrl: String): LiveData<Optional<ScanStatusInfo>>
|
||||
fun isScanResultKnownOrInProgress(mxcUrl: String, scannerUrl: String?): Boolean
|
||||
|
||||
fun updateStateForContent(mxcUrl: String, state: ScanState, scannerUrl: String?)
|
||||
fun updateScanResultForContent(mxcUrl: String, scannerUrl: String?, state: ScanState, humanReadable: String)
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.android.sdk.internal.session.contentscanner.db
|
||||
|
||||
import io.realm.RealmObject
|
||||
import io.realm.annotations.Index
|
||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||
import org.matrix.android.sdk.api.session.contentscanner.ScanState
|
||||
import org.matrix.android.sdk.api.session.contentscanner.ScanStatusInfo
|
||||
|
||||
internal open class ContentScanResultEntity(
|
||||
@Index
|
||||
var mediaUrl: String? = null,
|
||||
var scanStatusString: String? = null,
|
||||
var humanReadableMessage: String? = null,
|
||||
var scanDateTimestamp: Long? = null,
|
||||
var scannerUrl: String? = null
|
||||
) : RealmObject() {
|
||||
|
||||
var scanResult: ScanState
|
||||
get() {
|
||||
return scanStatusString
|
||||
?.let {
|
||||
tryOrNull { ScanState.valueOf(it) }
|
||||
}
|
||||
?: ScanState.UNKNOWN
|
||||
}
|
||||
set(result) {
|
||||
scanStatusString = result.name
|
||||
}
|
||||
|
||||
fun toModel(): ScanStatusInfo {
|
||||
return ScanStatusInfo(
|
||||
state = this.scanResult,
|
||||
humanReadableMessage = humanReadableMessage,
|
||||
scanDateTimestamp = scanDateTimestamp
|
||||
)
|
||||
}
|
||||
|
||||
companion object
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.android.sdk.internal.session.contentscanner.db
|
||||
|
||||
import io.realm.Realm
|
||||
import io.realm.kotlin.createObject
|
||||
import io.realm.kotlin.where
|
||||
|
||||
internal fun ContentScanResultEntity.Companion.get(realm: Realm, attachmentUrl: String, contentScannerUrl: String?): ContentScanResultEntity? {
|
||||
return realm.where<ContentScanResultEntity>()
|
||||
.equalTo(ContentScanResultEntityFields.MEDIA_URL, attachmentUrl)
|
||||
.apply {
|
||||
contentScannerUrl?.let {
|
||||
equalTo(ContentScanResultEntityFields.SCANNER_URL, it)
|
||||
}
|
||||
}
|
||||
.findFirst()
|
||||
}
|
||||
|
||||
internal fun ContentScanResultEntity.Companion.getOrCreate(realm: Realm, attachmentUrl: String, contentScannerUrl: String?): ContentScanResultEntity {
|
||||
return ContentScanResultEntity.get(realm, attachmentUrl, contentScannerUrl)
|
||||
?: realm.createObject<ContentScanResultEntity>().also {
|
||||
it.mediaUrl = attachmentUrl
|
||||
it.scanDateTimestamp = System.currentTimeMillis()
|
||||
it.scannerUrl = contentScannerUrl
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
* 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.
|
||||
@ -14,14 +14,14 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.app.features.home.room.list
|
||||
package org.matrix.android.sdk.internal.session.contentscanner.db
|
||||
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import io.realm.RealmObject
|
||||
|
||||
@Module
|
||||
abstract class RoomListModule {
|
||||
internal open class ContentScannerInfoEntity(
|
||||
var serverUrl: String? = null,
|
||||
var enabled: Boolean? = null
|
||||
) : RealmObject() {
|
||||
|
||||
@Binds
|
||||
abstract fun providesRoomListViewModelFactory(factory: RoomListViewModelFactory): RoomListViewModel.Factory
|
||||
companion object
|
||||
}
|
@ -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.internal.session.contentscanner.db
|
||||
|
||||
import io.realm.annotations.RealmModule
|
||||
|
||||
/**
|
||||
* Realm module for content scanner classes
|
||||
*/
|
||||
@RealmModule(library = true,
|
||||
classes = [
|
||||
ContentScannerInfoEntity::class,
|
||||
ContentScanResultEntity::class
|
||||
])
|
||||
internal class ContentScannerRealmModule
|
@ -0,0 +1,143 @@
|
||||
/*
|
||||
* Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.android.sdk.internal.session.contentscanner.db
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.Transformations
|
||||
import com.zhuinden.monarchy.Monarchy
|
||||
import io.realm.Realm
|
||||
import io.realm.RealmConfiguration
|
||||
import io.realm.kotlin.createObject
|
||||
import io.realm.kotlin.where
|
||||
import org.matrix.android.sdk.api.extensions.orFalse
|
||||
import org.matrix.android.sdk.api.session.contentscanner.ScanState
|
||||
import org.matrix.android.sdk.api.session.contentscanner.ScanStatusInfo
|
||||
import org.matrix.android.sdk.api.util.Optional
|
||||
import org.matrix.android.sdk.api.util.toOptional
|
||||
import org.matrix.android.sdk.internal.di.ContentScannerDatabase
|
||||
import org.matrix.android.sdk.internal.session.SessionScope
|
||||
import org.matrix.android.sdk.internal.session.contentscanner.data.ContentScannerStore
|
||||
import org.matrix.android.sdk.internal.util.isValidUrl
|
||||
import javax.inject.Inject
|
||||
|
||||
@SessionScope
|
||||
internal class RealmContentScannerStore @Inject constructor(
|
||||
@ContentScannerDatabase
|
||||
private val realmConfiguration: RealmConfiguration
|
||||
) : ContentScannerStore {
|
||||
|
||||
private val monarchy = Monarchy.Builder()
|
||||
.setRealmConfiguration(realmConfiguration)
|
||||
.build()
|
||||
|
||||
override fun getScannerUrl(): String? {
|
||||
return monarchy.fetchAllMappedSync(
|
||||
{ realm ->
|
||||
realm.where<ContentScannerInfoEntity>()
|
||||
}, {
|
||||
it.serverUrl
|
||||
}
|
||||
).firstOrNull()
|
||||
}
|
||||
|
||||
override fun setScannerUrl(url: String?) {
|
||||
monarchy.runTransactionSync { realm ->
|
||||
val info = realm.where<ContentScannerInfoEntity>().findFirst()
|
||||
?: realm.createObject()
|
||||
info.serverUrl = url
|
||||
}
|
||||
}
|
||||
|
||||
override fun enableScanner(enabled: Boolean) {
|
||||
monarchy.runTransactionSync { realm ->
|
||||
val info = realm.where<ContentScannerInfoEntity>().findFirst()
|
||||
?: realm.createObject()
|
||||
info.enabled = enabled
|
||||
}
|
||||
}
|
||||
|
||||
override fun isScanEnabled(): Boolean {
|
||||
return monarchy.fetchAllMappedSync(
|
||||
{ realm ->
|
||||
realm.where<ContentScannerInfoEntity>()
|
||||
}, {
|
||||
it.enabled.orFalse() && it.serverUrl?.isValidUrl().orFalse()
|
||||
}
|
||||
).firstOrNull().orFalse()
|
||||
}
|
||||
|
||||
override fun updateStateForContent(mxcUrl: String, state: ScanState, scannerUrl: String?) {
|
||||
monarchy.runTransactionSync {
|
||||
ContentScanResultEntity.getOrCreate(it, mxcUrl, scannerUrl).scanResult = state
|
||||
}
|
||||
}
|
||||
|
||||
override fun updateScanResultForContent(mxcUrl: String, scannerUrl: String?, state: ScanState, humanReadable: String) {
|
||||
monarchy.runTransactionSync {
|
||||
ContentScanResultEntity.getOrCreate(it, mxcUrl, scannerUrl).apply {
|
||||
scanResult = state
|
||||
scanDateTimestamp = System.currentTimeMillis()
|
||||
humanReadableMessage = humanReadable
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun isScanResultKnownOrInProgress(mxcUrl: String, scannerUrl: String?): Boolean {
|
||||
var isKnown = false
|
||||
monarchy.runTransactionSync {
|
||||
val info = ContentScanResultEntity.get(it, mxcUrl, scannerUrl)?.scanResult
|
||||
isKnown = when (info) {
|
||||
ScanState.IN_PROGRESS,
|
||||
ScanState.TRUSTED,
|
||||
ScanState.INFECTED -> true
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
return isKnown
|
||||
}
|
||||
|
||||
override fun getScanResult(mxcUrl: String): ScanStatusInfo? {
|
||||
return monarchy.fetchAllMappedSync({ realm ->
|
||||
realm.where<ContentScanResultEntity>()
|
||||
.equalTo(ContentScanResultEntityFields.MEDIA_URL, mxcUrl)
|
||||
.apply {
|
||||
getScannerUrl()?.let {
|
||||
equalTo(ContentScanResultEntityFields.SCANNER_URL, it)
|
||||
}
|
||||
}
|
||||
}, {
|
||||
it.toModel()
|
||||
})
|
||||
.firstOrNull()
|
||||
}
|
||||
|
||||
override fun getLiveScanResult(mxcUrl: String): LiveData<Optional<ScanStatusInfo>> {
|
||||
val liveData = monarchy.findAllMappedWithChanges(
|
||||
{ realm: Realm ->
|
||||
realm.where<ContentScanResultEntity>()
|
||||
.equalTo(ContentScanResultEntityFields.MEDIA_URL, mxcUrl)
|
||||
.equalTo(ContentScanResultEntityFields.SCANNER_URL, getScannerUrl())
|
||||
},
|
||||
{ entity ->
|
||||
entity.toModel()
|
||||
}
|
||||
)
|
||||
return Transformations.map(liveData) {
|
||||
it.firstOrNull().toOptional()
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.android.sdk.internal.session.contentscanner.model
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import org.matrix.android.sdk.internal.crypto.model.rest.EncryptedFileInfo
|
||||
import org.matrix.android.sdk.internal.di.MoshiProvider
|
||||
import org.matrix.android.sdk.internal.util.JsonCanonicalizer
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class DownloadBody(
|
||||
@Json(name = "file") val file: EncryptedFileInfo? = null,
|
||||
@Json(name = "encrypted_body") val encryptedBody: EncryptedBody? = null
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class EncryptedBody(
|
||||
@Json(name = "ciphertext") val cipherText: String,
|
||||
@Json(name = "mac") val mac: String,
|
||||
@Json(name = "ephemeral") val ephemeral: String
|
||||
)
|
||||
|
||||
internal fun DownloadBody.toJson(): String = MoshiProvider.providesMoshi().adapter(DownloadBody::class.java).toJson(this)
|
||||
|
||||
internal fun DownloadBody.toCanonicalJson() = JsonCanonicalizer.getCanonicalJson(DownloadBody::class.java, this)
|
@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.android.sdk.internal.session.contentscanner.model
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
/**
|
||||
* {
|
||||
* "clean": true,
|
||||
* "info": "File clean at 6/7/2018, 6:02:40 PM"
|
||||
* }
|
||||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class ScanResponse(
|
||||
@Json(name = "clean") val clean: Boolean,
|
||||
/** Human-readable information about the result. */
|
||||
@Json(name = "info") val info: String?
|
||||
)
|
@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.android.sdk.internal.session.contentscanner.model
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
internal data class ServerPublicKeyResponse(
|
||||
@Json(name = "public_key")
|
||||
val publicKey: String?
|
||||
)
|
@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.android.sdk.internal.session.contentscanner.tasks
|
||||
|
||||
import okhttp3.ResponseBody
|
||||
import org.matrix.android.sdk.internal.crypto.attachments.ElementToDecrypt
|
||||
import org.matrix.android.sdk.internal.network.executeRequest
|
||||
import org.matrix.android.sdk.internal.session.contentscanner.ContentScannerApiProvider
|
||||
import org.matrix.android.sdk.internal.session.contentscanner.ScanEncryptorUtils
|
||||
import org.matrix.android.sdk.internal.task.Task
|
||||
import javax.inject.Inject
|
||||
|
||||
internal interface DownloadEncryptedTask : Task<DownloadEncryptedTask.Params, ResponseBody> {
|
||||
data class Params(
|
||||
val publicServerKey: String?,
|
||||
val encryptedInfo: ElementToDecrypt,
|
||||
val mxcUrl: String
|
||||
)
|
||||
}
|
||||
|
||||
internal class DefaultDownloadEncryptedTask @Inject constructor(
|
||||
private val contentScannerApiProvider: ContentScannerApiProvider
|
||||
) : DownloadEncryptedTask {
|
||||
|
||||
override suspend fun execute(params: DownloadEncryptedTask.Params): ResponseBody {
|
||||
val dlBody = ScanEncryptorUtils.getDownloadBodyAndEncryptIfNeeded(
|
||||
params.publicServerKey,
|
||||
params.mxcUrl,
|
||||
params.encryptedInfo
|
||||
)
|
||||
|
||||
val api = contentScannerApiProvider.contentScannerApi ?: throw IllegalArgumentException()
|
||||
return executeRequest(null) {
|
||||
api.downloadEncrypted(dlBody)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.android.sdk.internal.session.contentscanner.tasks
|
||||
|
||||
import org.matrix.android.sdk.internal.network.executeRequest
|
||||
import org.matrix.android.sdk.internal.session.contentscanner.ContentScannerApi
|
||||
import org.matrix.android.sdk.internal.session.contentscanner.model.ServerPublicKeyResponse
|
||||
import org.matrix.android.sdk.internal.task.Task
|
||||
import javax.inject.Inject
|
||||
|
||||
internal interface GetServerPublicKeyTask : Task<GetServerPublicKeyTask.Params, String?> {
|
||||
data class Params(
|
||||
val contentScannerApi: ContentScannerApi
|
||||
)
|
||||
}
|
||||
|
||||
internal class DefaultGetServerPublicKeyTask @Inject constructor() : GetServerPublicKeyTask {
|
||||
|
||||
override suspend fun execute(params: GetServerPublicKeyTask.Params): String? {
|
||||
return executeRequest<ServerPublicKeyResponse>(null) {
|
||||
params.contentScannerApi.getServerPublicKey()
|
||||
}.publicKey
|
||||
}
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.android.sdk.internal.session.contentscanner.tasks
|
||||
|
||||
import org.matrix.android.sdk.api.failure.toScanFailure
|
||||
import org.matrix.android.sdk.api.session.contentscanner.ScanState
|
||||
import org.matrix.android.sdk.internal.crypto.attachments.ElementToDecrypt
|
||||
import org.matrix.android.sdk.internal.network.executeRequest
|
||||
import org.matrix.android.sdk.internal.session.contentscanner.ContentScannerApiProvider
|
||||
import org.matrix.android.sdk.internal.session.contentscanner.ScanEncryptorUtils
|
||||
import org.matrix.android.sdk.internal.session.contentscanner.data.ContentScannerStore
|
||||
import org.matrix.android.sdk.internal.session.contentscanner.model.ScanResponse
|
||||
import org.matrix.android.sdk.internal.task.Task
|
||||
import javax.inject.Inject
|
||||
|
||||
internal interface ScanEncryptedTask : Task<ScanEncryptedTask.Params, ScanResponse> {
|
||||
data class Params(
|
||||
val mxcUrl: String,
|
||||
val publicServerKey: String?,
|
||||
val encryptedInfo: ElementToDecrypt
|
||||
)
|
||||
}
|
||||
|
||||
internal class DefaultScanEncryptedTask @Inject constructor(
|
||||
private val contentScannerApiProvider: ContentScannerApiProvider,
|
||||
private val contentScannerStore: ContentScannerStore
|
||||
) : ScanEncryptedTask {
|
||||
|
||||
override suspend fun execute(params: ScanEncryptedTask.Params): ScanResponse {
|
||||
val mxcUrl = params.mxcUrl
|
||||
val dlBody = ScanEncryptorUtils.getDownloadBodyAndEncryptIfNeeded(params.publicServerKey, params.mxcUrl, params.encryptedInfo)
|
||||
|
||||
val scannerUrl = contentScannerStore.getScannerUrl()
|
||||
contentScannerStore.updateStateForContent(params.mxcUrl, ScanState.IN_PROGRESS, scannerUrl)
|
||||
|
||||
try {
|
||||
val api = contentScannerApiProvider.contentScannerApi ?: throw IllegalArgumentException()
|
||||
val executeRequest = executeRequest<ScanResponse>(null) {
|
||||
api.scanFile(dlBody)
|
||||
}
|
||||
contentScannerStore.updateScanResultForContent(
|
||||
mxcUrl,
|
||||
scannerUrl,
|
||||
ScanState.TRUSTED.takeIf { executeRequest.clean } ?: ScanState.INFECTED,
|
||||
executeRequest.info ?: ""
|
||||
)
|
||||
return executeRequest
|
||||
} catch (failure: Throwable) {
|
||||
contentScannerStore.updateStateForContent(params.mxcUrl, ScanState.UNKNOWN, scannerUrl)
|
||||
throw failure.toScanFailure() ?: failure
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.android.sdk.internal.session.contentscanner.tasks
|
||||
|
||||
import org.matrix.android.sdk.api.MatrixUrls.isMxcUrl
|
||||
import org.matrix.android.sdk.api.MatrixUrls.removeMxcPrefix
|
||||
import org.matrix.android.sdk.api.failure.toScanFailure
|
||||
import org.matrix.android.sdk.api.session.contentscanner.ScanState
|
||||
import org.matrix.android.sdk.internal.network.executeRequest
|
||||
import org.matrix.android.sdk.internal.session.contentscanner.ContentScannerApiProvider
|
||||
import org.matrix.android.sdk.internal.session.contentscanner.data.ContentScannerStore
|
||||
import org.matrix.android.sdk.internal.session.contentscanner.model.ScanResponse
|
||||
import org.matrix.android.sdk.internal.task.Task
|
||||
import javax.inject.Inject
|
||||
|
||||
internal interface ScanMediaTask : Task<ScanMediaTask.Params, ScanResponse> {
|
||||
data class Params(
|
||||
val mxcUrl: String
|
||||
)
|
||||
}
|
||||
|
||||
internal class DefaultScanMediaTask @Inject constructor(
|
||||
private val contentScannerApiProvider: ContentScannerApiProvider,
|
||||
private val contentScannerStore: ContentScannerStore
|
||||
) : ScanMediaTask {
|
||||
|
||||
override suspend fun execute(params: ScanMediaTask.Params): ScanResponse {
|
||||
// "mxc://server.org/QNDpzLopkoQYNikJfoZCQuCXJ"
|
||||
if (!params.mxcUrl.isMxcUrl()) {
|
||||
throw IllegalAccessException("Invalid mxc url")
|
||||
}
|
||||
val scannerUrl = contentScannerStore.getScannerUrl()
|
||||
contentScannerStore.updateStateForContent(params.mxcUrl, ScanState.IN_PROGRESS, scannerUrl)
|
||||
|
||||
var serverAndMediaId = params.mxcUrl.removeMxcPrefix()
|
||||
val fragmentOffset = serverAndMediaId.indexOf("#")
|
||||
if (fragmentOffset >= 0) {
|
||||
serverAndMediaId = serverAndMediaId.substring(0, fragmentOffset)
|
||||
}
|
||||
|
||||
val split = serverAndMediaId.split("/")
|
||||
if (split.size != 2) {
|
||||
throw IllegalAccessException("Invalid mxc url")
|
||||
}
|
||||
|
||||
try {
|
||||
val scanResponse = executeRequest<ScanResponse>(null) {
|
||||
val api = contentScannerApiProvider.contentScannerApi ?: throw IllegalArgumentException()
|
||||
api.scanMedia(split[0], split[1])
|
||||
}
|
||||
contentScannerStore.updateScanResultForContent(
|
||||
params.mxcUrl,
|
||||
scannerUrl,
|
||||
ScanState.TRUSTED.takeIf { scanResponse.clean } ?: ScanState.INFECTED,
|
||||
scanResponse.info ?: ""
|
||||
)
|
||||
return scanResponse
|
||||
} catch (failure: Throwable) {
|
||||
contentScannerStore.updateStateForContent(params.mxcUrl, ScanState.UNKNOWN, scannerUrl)
|
||||
throw failure.toScanFailure() ?: failure
|
||||
}
|
||||
}
|
||||
}
|
@ -30,12 +30,16 @@ import org.matrix.android.sdk.internal.database.model.GroupSummaryEntity
|
||||
import org.matrix.android.sdk.internal.database.model.GroupSummaryEntityFields
|
||||
import org.matrix.android.sdk.internal.database.query.where
|
||||
import org.matrix.android.sdk.internal.di.SessionDatabase
|
||||
import org.matrix.android.sdk.internal.query.QueryStringValueProcessor
|
||||
import org.matrix.android.sdk.internal.query.process
|
||||
import org.matrix.android.sdk.internal.util.fetchCopyMap
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class DefaultGroupService @Inject constructor(@SessionDatabase private val monarchy: Monarchy,
|
||||
private val groupFactory: GroupFactory) : GroupService {
|
||||
internal class DefaultGroupService @Inject constructor(
|
||||
@SessionDatabase private val monarchy: Monarchy,
|
||||
private val groupFactory: GroupFactory,
|
||||
private val queryStringValueProcessor: QueryStringValueProcessor,
|
||||
) : GroupService {
|
||||
|
||||
override fun getGroup(groupId: String): Group? {
|
||||
return Realm.getInstance(monarchy.realmConfiguration).use { realm ->
|
||||
@ -67,8 +71,10 @@ internal class DefaultGroupService @Inject constructor(@SessionDatabase private
|
||||
}
|
||||
|
||||
private fun groupSummariesQuery(realm: Realm, queryParams: GroupSummaryQueryParams): RealmQuery<GroupSummaryEntity> {
|
||||
return GroupSummaryEntity.where(realm)
|
||||
.process(GroupSummaryEntityFields.DISPLAY_NAME, queryParams.displayName)
|
||||
.process(GroupSummaryEntityFields.MEMBERSHIP_STR, queryParams.memberships)
|
||||
return with(queryStringValueProcessor) {
|
||||
GroupSummaryEntity.where(realm)
|
||||
.process(GroupSummaryEntityFields.DISPLAY_NAME, queryParams.displayName)
|
||||
.process(GroupSummaryEntityFields.MEMBERSHIP_STR, queryParams.memberships)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -80,7 +80,7 @@ internal class DefaultIdentityService @Inject constructor(
|
||||
private val identityApiProvider: IdentityApiProvider,
|
||||
private val accountDataDataSource: UserAccountDataDataSource,
|
||||
private val homeServerCapabilitiesService: HomeServerCapabilitiesService,
|
||||
private val sign3pidInvitationTask: DefaultSign3pidInvitationTask,
|
||||
private val sign3pidInvitationTask: Sign3pidInvitationTask,
|
||||
private val sessionParams: SessionParams
|
||||
) : IdentityService, SessionLifecycleObserver {
|
||||
|
||||
|
@ -21,6 +21,7 @@ import dagger.Module
|
||||
import dagger.Provides
|
||||
import io.realm.RealmConfiguration
|
||||
import okhttp3.OkHttpClient
|
||||
import org.matrix.android.sdk.api.session.identity.IdentityService
|
||||
import org.matrix.android.sdk.internal.database.RealmKeysUtils
|
||||
import org.matrix.android.sdk.internal.di.AuthenticatedIdentity
|
||||
import org.matrix.android.sdk.internal.di.IdentityDatabase
|
||||
@ -75,6 +76,9 @@ internal abstract class IdentityModule {
|
||||
}
|
||||
}
|
||||
|
||||
@Binds
|
||||
abstract fun bindIdentityService(service: DefaultIdentityService): IdentityService
|
||||
|
||||
@Binds
|
||||
@AuthenticatedIdentity
|
||||
abstract fun bindAccessTokenProvider(provider: IdentityAccessTokenProvider): AccessTokenProvider
|
||||
|
@ -19,6 +19,7 @@ import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.Transformations
|
||||
import com.zhuinden.monarchy.Monarchy
|
||||
import org.matrix.android.sdk.api.pushrules.Action
|
||||
import org.matrix.android.sdk.api.pushrules.PushEvents
|
||||
import org.matrix.android.sdk.api.pushrules.PushRuleService
|
||||
import org.matrix.android.sdk.api.pushrules.RuleKind
|
||||
import org.matrix.android.sdk.api.pushrules.RuleScope
|
||||
@ -142,79 +143,6 @@ internal class DefaultPushRuleService @Inject constructor(
|
||||
return pushRuleFinder.fulfilledBingRule(event, rules)?.getActions().orEmpty()
|
||||
}
|
||||
|
||||
// fun processEvents(events: List<Event>) {
|
||||
// var hasDoneSomething = false
|
||||
// events.forEach { event ->
|
||||
// fulfilledBingRule(event)?.let {
|
||||
// hasDoneSomething = true
|
||||
// dispatchBing(event, it)
|
||||
// }
|
||||
// }
|
||||
// if (hasDoneSomething)
|
||||
// dispatchFinish()
|
||||
// }
|
||||
|
||||
fun dispatchBing(event: Event, rule: PushRule) {
|
||||
synchronized(listeners) {
|
||||
val actionsList = rule.getActions()
|
||||
listeners.forEach {
|
||||
try {
|
||||
it.onMatchRule(event, actionsList)
|
||||
} catch (e: Throwable) {
|
||||
Timber.e(e, "Error while dispatching bing")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun dispatchRoomJoined(roomId: String) {
|
||||
synchronized(listeners) {
|
||||
listeners.forEach {
|
||||
try {
|
||||
it.onRoomJoined(roomId)
|
||||
} catch (e: Throwable) {
|
||||
Timber.e(e, "Error while dispatching room joined")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun dispatchRoomLeft(roomId: String) {
|
||||
synchronized(listeners) {
|
||||
listeners.forEach {
|
||||
try {
|
||||
it.onRoomLeft(roomId)
|
||||
} catch (e: Throwable) {
|
||||
Timber.e(e, "Error while dispatching room left")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun dispatchRedactedEventId(redactedEventId: String) {
|
||||
synchronized(listeners) {
|
||||
listeners.forEach {
|
||||
try {
|
||||
it.onEventRedacted(redactedEventId)
|
||||
} catch (e: Throwable) {
|
||||
Timber.e(e, "Error while dispatching redacted event")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun dispatchFinish() {
|
||||
synchronized(listeners) {
|
||||
listeners.forEach {
|
||||
try {
|
||||
it.batchFinish()
|
||||
} catch (e: Throwable) {
|
||||
Timber.e(e, "Error while dispatching finish")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getKeywords(): LiveData<Set<String>> {
|
||||
// Keywords are all content rules that don't start with '.'
|
||||
val liveData = monarchy.findAllMappedWithChanges(
|
||||
@ -229,4 +157,16 @@ internal class DefaultPushRuleService @Inject constructor(
|
||||
results.firstOrNull().orEmpty().toSet()
|
||||
}
|
||||
}
|
||||
|
||||
fun dispatchEvents(pushEvents: PushEvents) {
|
||||
synchronized(listeners) {
|
||||
listeners.forEach {
|
||||
try {
|
||||
it.onEvents(pushEvents)
|
||||
} catch (e: Throwable) {
|
||||
Timber.e(e, "Error while dispatching push events")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,8 +16,10 @@
|
||||
|
||||
package org.matrix.android.sdk.internal.session.notification
|
||||
|
||||
import org.matrix.android.sdk.api.pushrules.PushEvents
|
||||
import org.matrix.android.sdk.api.pushrules.rest.PushRule
|
||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||
import org.matrix.android.sdk.api.session.events.model.isInvitation
|
||||
import org.matrix.android.sdk.api.session.sync.model.RoomsSyncResponse
|
||||
import org.matrix.android.sdk.internal.di.UserId
|
||||
import org.matrix.android.sdk.internal.task.Task
|
||||
@ -38,24 +40,20 @@ internal class DefaultProcessEventForPushTask @Inject constructor(
|
||||
) : ProcessEventForPushTask {
|
||||
|
||||
override suspend fun execute(params: ProcessEventForPushTask.Params) {
|
||||
// Handle left rooms
|
||||
params.syncResponse.leave.keys.forEach {
|
||||
defaultPushRuleService.dispatchRoomLeft(it)
|
||||
}
|
||||
// Handle joined rooms
|
||||
params.syncResponse.join.keys.forEach {
|
||||
defaultPushRuleService.dispatchRoomJoined(it)
|
||||
}
|
||||
val newJoinEvents = params.syncResponse.join
|
||||
.mapNotNull { (key, value) ->
|
||||
value.timeline?.events?.map { it.copy(roomId = key) }
|
||||
value.timeline?.events?.mapNotNull {
|
||||
it.takeIf { !it.isInvitation() }?.copy(roomId = key)
|
||||
}
|
||||
}
|
||||
.flatten()
|
||||
|
||||
val inviteEvents = params.syncResponse.invite
|
||||
.mapNotNull { (key, value) ->
|
||||
value.inviteState?.events?.map { it.copy(roomId = key) }
|
||||
}
|
||||
.flatten()
|
||||
|
||||
val allEvents = (newJoinEvents + inviteEvents).filter { event ->
|
||||
when (event.type) {
|
||||
EventType.MESSAGE,
|
||||
@ -69,10 +67,10 @@ internal class DefaultProcessEventForPushTask @Inject constructor(
|
||||
}
|
||||
Timber.v("[PushRules] Found ${allEvents.size} out of ${(newJoinEvents + inviteEvents).size}" +
|
||||
" to check for push rules with ${params.rules.size} rules")
|
||||
allEvents.forEach { event ->
|
||||
val matchedEvents = allEvents.mapNotNull { event ->
|
||||
pushRuleFinder.fulfilledBingRule(event, params.rules)?.let {
|
||||
Timber.v("[PushRules] Rule $it match for event ${event.eventId}")
|
||||
defaultPushRuleService.dispatchBing(event, it)
|
||||
event to it
|
||||
}
|
||||
}
|
||||
|
||||
@ -86,10 +84,13 @@ internal class DefaultProcessEventForPushTask @Inject constructor(
|
||||
|
||||
Timber.v("[PushRules] Found ${allRedactedEvents.size} redacted events")
|
||||
|
||||
allRedactedEvents.forEach { redactedEventId ->
|
||||
defaultPushRuleService.dispatchRedactedEventId(redactedEventId)
|
||||
}
|
||||
|
||||
defaultPushRuleService.dispatchFinish()
|
||||
defaultPushRuleService.dispatchEvents(
|
||||
PushEvents(
|
||||
matchedEvents = matchedEvents,
|
||||
roomsJoined = params.syncResponse.join.keys,
|
||||
roomsLeft = params.syncResponse.leave.keys,
|
||||
redactedEventIds = allRedactedEvents
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -85,12 +85,14 @@ internal class DefaultRoomService @Inject constructor(
|
||||
return roomSummaryDataSource.getRoomSummary(roomIdOrAlias)
|
||||
}
|
||||
|
||||
override fun getRoomSummaries(queryParams: RoomSummaryQueryParams): List<RoomSummary> {
|
||||
return roomSummaryDataSource.getRoomSummaries(queryParams)
|
||||
override fun getRoomSummaries(queryParams: RoomSummaryQueryParams,
|
||||
sortOrder: RoomSortOrder): List<RoomSummary> {
|
||||
return roomSummaryDataSource.getRoomSummaries(queryParams, sortOrder)
|
||||
}
|
||||
|
||||
override fun getRoomSummariesLive(queryParams: RoomSummaryQueryParams): LiveData<List<RoomSummary>> {
|
||||
return roomSummaryDataSource.getRoomSummariesLive(queryParams)
|
||||
override fun getRoomSummariesLive(queryParams: RoomSummaryQueryParams,
|
||||
sortOrder: RoomSortOrder): LiveData<List<RoomSummary>> {
|
||||
return roomSummaryDataSource.getRoomSummariesLive(queryParams, sortOrder)
|
||||
}
|
||||
|
||||
override fun getPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams,
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user