Merge tag 'v1.0.14' into sc
Change-Id: I0d3df2f8c5f4e611f3268c4c4d2784f7eac89bb5 Conflicts: gradle/wrapper/gradle-wrapper.properties matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/UrlsExtractor.kt vector/src/main/java/im/vector/app/core/epoxy/ExpandableTextItem.kt vector/src/main/java/im/vector/app/core/ui/views/BottomSheetActionButton.kt vector/src/main/java/im/vector/app/features/home/HomeActivity.kt vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt vector/src/main/java/im/vector/app/features/home/room/detail/timeline/url/PreviewUrlView.kt vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileActivity.kt vector/src/main/java/im/vector/app/features/roomprofile/RoomProfileController.kt vector/src/main/java/im/vector/app/features/settings/VectorSettingsNotificationsTroubleshootFragment.kt vector/src/main/java/im/vector/app/features/settings/VectorSettingsPreferencesFragment.kt vector/src/main/java/im/vector/app/features/themes/ThemeUtils.kt vector/src/main/res/layout/composer_layout_constraint_set_compact.xml vector/src/main/res/layout/composer_layout_constraint_set_expanded.xml vector/src/main/res/layout/fragment_keys_backup_setup_step3.xml vector/src/main/res/xml/vector_settings_preferences.xml
This commit is contained in:
commit
53c498b728
34
CHANGES.md
34
CHANGES.md
|
@ -1,3 +1,37 @@
|
|||
Changes in Element 1.0.14 (2020-XX-XX)
|
||||
===================================================
|
||||
|
||||
Features ✨:
|
||||
- Enable url previews for notices (#2562)
|
||||
- Edit room permissions (#2471)
|
||||
|
||||
Improvements 🙌:
|
||||
- Add System theme option and set as default (#904, #2387)
|
||||
- Store megolm outbound session to improve send time of first message after app launch.
|
||||
- Warn user when they are leaving a not public room (#1460)
|
||||
- Option to disable emoji keyboard (#2563)
|
||||
|
||||
Bugfix 🐛:
|
||||
- Unspecced msgType field in m.sticker (#2580)
|
||||
- Wait for all room members to be known before sending a message to a e2e room (#2518)
|
||||
- Url previews sometimes attached to wrong message (#2561)
|
||||
- Room Topic not displayed correctly after visiting a link (#2551)
|
||||
- Hiding membership events works the exact opposite (#2603)
|
||||
- Tapping drawer having more than 1 room in notifications gives "malformed link" error (#2605)
|
||||
- Sent image not displayed when opened immediately after sending (#409)
|
||||
- Initial sync is not retried correctly when there is some network error. (#2632)
|
||||
- Fix switch theme issue, and white field issue (#2599, #2528)
|
||||
- Fix request too large Uri error when joining a room
|
||||
|
||||
Translations 🗣:
|
||||
- New language supported: Hebrew
|
||||
|
||||
Build 🧱:
|
||||
- Remove dependency to org.greenrobot.eventbus library
|
||||
|
||||
Other changes:
|
||||
- Migrate to ViewBindings (#1072)
|
||||
|
||||
Changes in Element 1.0.13 (2020-12-18)
|
||||
===================================================
|
||||
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
|
||||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply plugin: 'kotlin-android-extensions'
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
|
@ -55,6 +54,10 @@ android {
|
|||
kotlinOptions {
|
||||
jvmTarget = '1.8'
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
viewBinding true
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
|
|
@ -17,19 +17,17 @@
|
|||
package im.vector.lib.attachmentviewer
|
||||
|
||||
import android.view.View
|
||||
import android.widget.ImageView
|
||||
import android.widget.ProgressBar
|
||||
import im.vector.lib.attachmentviewer.databinding.ItemAnimatedImageAttachmentBinding
|
||||
|
||||
class AnimatedImageViewHolder constructor(itemView: View) :
|
||||
BaseViewHolder(itemView) {
|
||||
|
||||
val touchImageView: ImageView = itemView.findViewById(R.id.imageView)
|
||||
val imageLoaderProgress: ProgressBar = itemView.findViewById(R.id.imageLoaderProgress)
|
||||
val views = ItemAnimatedImageAttachmentBinding.bind(itemView)
|
||||
|
||||
internal val target = DefaultImageLoaderTarget(this, this.touchImageView)
|
||||
internal val target = DefaultImageLoaderTarget(this, views.imageView)
|
||||
|
||||
override fun onRecycled() {
|
||||
super.onRecycled()
|
||||
touchImageView.setImageDrawable(null)
|
||||
views.imageView.setImageDrawable(null)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,43 +33,51 @@ import androidx.core.view.isVisible
|
|||
import androidx.core.view.updatePadding
|
||||
import androidx.transition.TransitionManager
|
||||
import androidx.viewpager2.widget.ViewPager2
|
||||
import kotlinx.android.synthetic.main.activity_attachment_viewer.*
|
||||
import im.vector.lib.attachmentviewer.databinding.ActivityAttachmentViewerBinding
|
||||
|
||||
import java.lang.ref.WeakReference
|
||||
import kotlin.math.abs
|
||||
|
||||
abstract class AttachmentViewerActivity : AppCompatActivity(), AttachmentEventListener {
|
||||
|
||||
lateinit var pager2: ViewPager2
|
||||
lateinit var imageTransitionView: ImageView
|
||||
lateinit var transitionImageContainer: ViewGroup
|
||||
protected val pager2: ViewPager2
|
||||
get() = views.attachmentPager
|
||||
protected val imageTransitionView: ImageView
|
||||
get() = views.transitionImageView
|
||||
protected val transitionImageContainer: ViewGroup
|
||||
get() = views.transitionImageContainer
|
||||
|
||||
var topInset = 0
|
||||
var bottomInset = 0
|
||||
var systemUiVisibility = true
|
||||
private var topInset = 0
|
||||
private var bottomInset = 0
|
||||
private var systemUiVisibility = true
|
||||
|
||||
private var overlayView: View? = null
|
||||
set(value) {
|
||||
if (value == overlayView) return
|
||||
overlayView?.let { rootContainer.removeView(it) }
|
||||
rootContainer.addView(value)
|
||||
overlayView?.let { views.rootContainer.removeView(it) }
|
||||
views.rootContainer.addView(value)
|
||||
value?.updatePadding(top = topInset, bottom = bottomInset)
|
||||
field = value
|
||||
}
|
||||
|
||||
private lateinit var views: ActivityAttachmentViewerBinding
|
||||
|
||||
private lateinit var swipeDismissHandler: SwipeToDismissHandler
|
||||
private lateinit var directionDetector: SwipeDirectionDetector
|
||||
private lateinit var scaleDetector: ScaleGestureDetector
|
||||
private lateinit var gestureDetector: GestureDetectorCompat
|
||||
|
||||
var currentPosition = 0
|
||||
private set
|
||||
|
||||
private var swipeDirection: SwipeDirection? = null
|
||||
|
||||
private fun isScaled() = attachmentsAdapter.isScaled(currentPosition)
|
||||
|
||||
private val attachmentsAdapter = AttachmentsAdapter()
|
||||
|
||||
private var wasScaled: Boolean = false
|
||||
private var isSwipeToDismissAllowed: Boolean = true
|
||||
private lateinit var attachmentsAdapter: AttachmentsAdapter
|
||||
private var isOverlayWasClicked = false
|
||||
|
||||
// private val shouldDismissToBottom: Boolean
|
||||
|
@ -95,17 +103,14 @@ abstract class AttachmentViewerActivity : AppCompatActivity(), AttachmentEventLi
|
|||
window.setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS, WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
|
||||
window.setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION, WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION)
|
||||
|
||||
setContentView(R.layout.activity_attachment_viewer)
|
||||
attachmentPager.orientation = ViewPager2.ORIENTATION_HORIZONTAL
|
||||
attachmentsAdapter = AttachmentsAdapter()
|
||||
attachmentPager.adapter = attachmentsAdapter
|
||||
imageTransitionView = transitionImageView
|
||||
transitionImageContainer = findViewById(R.id.transitionImageContainer)
|
||||
pager2 = attachmentPager
|
||||
views = ActivityAttachmentViewerBinding.inflate(layoutInflater)
|
||||
setContentView(views.root)
|
||||
views.attachmentPager.orientation = ViewPager2.ORIENTATION_HORIZONTAL
|
||||
views.attachmentPager.adapter = attachmentsAdapter
|
||||
directionDetector = createSwipeDirectionDetector()
|
||||
gestureDetector = createGestureDetector()
|
||||
|
||||
attachmentPager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
|
||||
views.attachmentPager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
|
||||
override fun onPageScrollStateChanged(state: Int) {
|
||||
isImagePagerIdle = state == ViewPager2.SCROLL_STATE_IDLE
|
||||
}
|
||||
|
@ -116,12 +121,12 @@ abstract class AttachmentViewerActivity : AppCompatActivity(), AttachmentEventLi
|
|||
})
|
||||
|
||||
swipeDismissHandler = createSwipeToDismissHandler()
|
||||
rootContainer.setOnTouchListener(swipeDismissHandler)
|
||||
rootContainer.viewTreeObserver.addOnGlobalLayoutListener { swipeDismissHandler.translationLimit = dismissContainer.height / 4 }
|
||||
views.rootContainer.setOnTouchListener(swipeDismissHandler)
|
||||
views.rootContainer.viewTreeObserver.addOnGlobalLayoutListener { swipeDismissHandler.translationLimit = views.dismissContainer.height / 4 }
|
||||
|
||||
scaleDetector = createScaleGestureDetector()
|
||||
|
||||
ViewCompat.setOnApplyWindowInsetsListener(rootContainer) { _, insets ->
|
||||
ViewCompat.setOnApplyWindowInsetsListener(views.rootContainer) { _, insets ->
|
||||
overlayView?.updatePadding(top = insets.systemWindowInsetTop, bottom = insets.systemWindowInsetBottom)
|
||||
topInset = insets.systemWindowInsetTop
|
||||
bottomInset = insets.systemWindowInsetBottom
|
||||
|
@ -170,7 +175,7 @@ abstract class AttachmentViewerActivity : AppCompatActivity(), AttachmentEventLi
|
|||
if (swipeDirection == null && (scaleDetector.isInProgress || ev.pointerCount > 1 || wasScaled)) {
|
||||
wasScaled = true
|
||||
// Log.v("ATTACHEMENTS", "dispatch to pager")
|
||||
return attachmentPager.dispatchTouchEvent(ev)
|
||||
return views.attachmentPager.dispatchTouchEvent(ev)
|
||||
}
|
||||
|
||||
// Log.v("ATTACHEMENTS", "is current item scaled ${isScaled()}")
|
||||
|
@ -196,16 +201,16 @@ abstract class AttachmentViewerActivity : AppCompatActivity(), AttachmentEventLi
|
|||
private fun handleEventActionDown(event: MotionEvent) {
|
||||
swipeDirection = null
|
||||
wasScaled = false
|
||||
attachmentPager.dispatchTouchEvent(event)
|
||||
views.attachmentPager.dispatchTouchEvent(event)
|
||||
|
||||
swipeDismissHandler.onTouch(rootContainer, event)
|
||||
swipeDismissHandler.onTouch(views.rootContainer, event)
|
||||
isOverlayWasClicked = dispatchOverlayTouch(event)
|
||||
}
|
||||
|
||||
private fun handleEventActionUp(event: MotionEvent) {
|
||||
// wasDoubleTapped = false
|
||||
swipeDismissHandler.onTouch(rootContainer, event)
|
||||
attachmentPager.dispatchTouchEvent(event)
|
||||
swipeDismissHandler.onTouch(views.rootContainer, event)
|
||||
views.attachmentPager.dispatchTouchEvent(event)
|
||||
isOverlayWasClicked = dispatchOverlayTouch(event)
|
||||
}
|
||||
|
||||
|
@ -220,12 +225,12 @@ abstract class AttachmentViewerActivity : AppCompatActivity(), AttachmentEventLi
|
|||
private fun toggleOverlayViewVisibility() {
|
||||
if (systemUiVisibility) {
|
||||
// we hide
|
||||
TransitionManager.beginDelayedTransition(rootContainer)
|
||||
TransitionManager.beginDelayedTransition(views.rootContainer)
|
||||
hideSystemUI()
|
||||
overlayView?.isVisible = false
|
||||
} else {
|
||||
// we show
|
||||
TransitionManager.beginDelayedTransition(rootContainer)
|
||||
TransitionManager.beginDelayedTransition(views.rootContainer)
|
||||
showSystemUI()
|
||||
overlayView?.isVisible = true
|
||||
}
|
||||
|
@ -238,11 +243,11 @@ abstract class AttachmentViewerActivity : AppCompatActivity(), AttachmentEventLi
|
|||
return when (swipeDirection) {
|
||||
SwipeDirection.Up, SwipeDirection.Down -> {
|
||||
if (isSwipeToDismissAllowed && !wasScaled && isImagePagerIdle) {
|
||||
swipeDismissHandler.onTouch(rootContainer, event)
|
||||
swipeDismissHandler.onTouch(views.rootContainer, event)
|
||||
} else true
|
||||
}
|
||||
SwipeDirection.Left, SwipeDirection.Right -> {
|
||||
attachmentPager.dispatchTouchEvent(event)
|
||||
views.attachmentPager.dispatchTouchEvent(event)
|
||||
}
|
||||
else -> true
|
||||
}
|
||||
|
@ -250,8 +255,8 @@ abstract class AttachmentViewerActivity : AppCompatActivity(), AttachmentEventLi
|
|||
|
||||
private fun handleSwipeViewMove(translationY: Float, translationLimit: Int) {
|
||||
val alpha = calculateTranslationAlpha(translationY, translationLimit)
|
||||
backgroundView.alpha = alpha
|
||||
dismissContainer.alpha = alpha
|
||||
views.backgroundView.alpha = alpha
|
||||
views.dismissContainer.alpha = alpha
|
||||
overlayView?.alpha = alpha
|
||||
}
|
||||
|
||||
|
@ -265,7 +270,7 @@ abstract class AttachmentViewerActivity : AppCompatActivity(), AttachmentEventLi
|
|||
|
||||
private fun createSwipeToDismissHandler()
|
||||
: SwipeToDismissHandler = SwipeToDismissHandler(
|
||||
swipeView = dismissContainer,
|
||||
swipeView = views.dismissContainer,
|
||||
shouldAnimateDismiss = { shouldAnimateDismiss() },
|
||||
onDismiss = { animateClose() },
|
||||
onSwipeViewMove = ::handleSwipeViewMove)
|
||||
|
|
|
@ -98,7 +98,7 @@ class AttachmentsAdapter : RecyclerView.Adapter<BaseViewHolder>() {
|
|||
fun isScaled(position: Int): Boolean {
|
||||
val holder = recyclerView?.findViewHolderForAdapterPosition(position)
|
||||
if (holder is ZoomableImageViewHolder) {
|
||||
return holder.touchImageView.attacher.scale > 1f
|
||||
return holder.views.touchImageView.attacher.scale > 1f
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
|
|
@ -44,29 +44,29 @@ internal class DefaultImageLoaderTarget(val holder: AnimatedImageViewHolder, pri
|
|||
|
||||
override fun onResourceLoading(uid: String, placeholder: Drawable?) {
|
||||
if (holder.boundResourceUid != uid) return
|
||||
holder.imageLoaderProgress.isVisible = true
|
||||
holder.views.imageLoaderProgress.isVisible = true
|
||||
}
|
||||
|
||||
override fun onLoadFailed(uid: String, errorDrawable: Drawable?) {
|
||||
if (holder.boundResourceUid != uid) return
|
||||
holder.imageLoaderProgress.isVisible = false
|
||||
holder.touchImageView.setImageDrawable(errorDrawable)
|
||||
holder.views.imageLoaderProgress.isVisible = false
|
||||
holder.views.imageView.setImageDrawable(errorDrawable)
|
||||
}
|
||||
|
||||
override fun onResourceCleared(uid: String, placeholder: Drawable?) {
|
||||
if (holder.boundResourceUid != uid) return
|
||||
holder.touchImageView.setImageDrawable(placeholder)
|
||||
holder.views.imageView.setImageDrawable(placeholder)
|
||||
}
|
||||
|
||||
override fun onResourceReady(uid: String, resource: Drawable) {
|
||||
if (holder.boundResourceUid != uid) return
|
||||
holder.imageLoaderProgress.isVisible = false
|
||||
holder.views.imageLoaderProgress.isVisible = false
|
||||
// Glide mess up the view size :/
|
||||
holder.touchImageView.updateLayoutParams {
|
||||
holder.views.imageView.updateLayoutParams {
|
||||
width = LinearLayout.LayoutParams.MATCH_PARENT
|
||||
height = LinearLayout.LayoutParams.MATCH_PARENT
|
||||
}
|
||||
holder.touchImageView.setImageDrawable(resource)
|
||||
holder.views.imageView.setImageDrawable(resource)
|
||||
if (resource is Animatable) {
|
||||
resource.start()
|
||||
}
|
||||
|
@ -77,30 +77,30 @@ internal class DefaultImageLoaderTarget(val holder: AnimatedImageViewHolder, pri
|
|||
|
||||
override fun onResourceLoading(uid: String, placeholder: Drawable?) {
|
||||
if (holder.boundResourceUid != uid) return
|
||||
holder.imageLoaderProgress.isVisible = true
|
||||
holder.touchImageView.setImageDrawable(placeholder)
|
||||
holder.views.imageLoaderProgress.isVisible = true
|
||||
holder.views.touchImageView.setImageDrawable(placeholder)
|
||||
}
|
||||
|
||||
override fun onLoadFailed(uid: String, errorDrawable: Drawable?) {
|
||||
if (holder.boundResourceUid != uid) return
|
||||
holder.imageLoaderProgress.isVisible = false
|
||||
holder.touchImageView.setImageDrawable(errorDrawable)
|
||||
holder.views.imageLoaderProgress.isVisible = false
|
||||
holder.views.touchImageView.setImageDrawable(errorDrawable)
|
||||
}
|
||||
|
||||
override fun onResourceCleared(uid: String, placeholder: Drawable?) {
|
||||
if (holder.boundResourceUid != uid) return
|
||||
holder.touchImageView.setImageDrawable(placeholder)
|
||||
holder.views.touchImageView.setImageDrawable(placeholder)
|
||||
}
|
||||
|
||||
override fun onResourceReady(uid: String, resource: Drawable) {
|
||||
if (holder.boundResourceUid != uid) return
|
||||
holder.imageLoaderProgress.isVisible = false
|
||||
holder.views.imageLoaderProgress.isVisible = false
|
||||
// Glide mess up the view size :/
|
||||
holder.touchImageView.updateLayoutParams {
|
||||
holder.views.touchImageView.updateLayoutParams {
|
||||
width = LinearLayout.LayoutParams.MATCH_PARENT
|
||||
height = LinearLayout.LayoutParams.MATCH_PARENT
|
||||
}
|
||||
holder.touchImageView.setImageDrawable(resource)
|
||||
holder.views.touchImageView.setImageDrawable(resource)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -49,19 +49,19 @@ internal class DefaultVideoLoaderTarget(val holder: VideoViewHolder, private val
|
|||
|
||||
override fun onThumbnailResourceCleared(uid: String, placeholder: Drawable?) {
|
||||
if (holder.boundResourceUid != uid) return
|
||||
holder.thumbnailImage.setImageDrawable(placeholder)
|
||||
holder.views.videoThumbnailImage.setImageDrawable(placeholder)
|
||||
}
|
||||
|
||||
override fun onThumbnailResourceReady(uid: String, resource: Drawable) {
|
||||
if (holder.boundResourceUid != uid) return
|
||||
holder.thumbnailImage.setImageDrawable(resource)
|
||||
holder.views.videoThumbnailImage.setImageDrawable(resource)
|
||||
}
|
||||
|
||||
override fun onVideoFileLoading(uid: String) {
|
||||
if (holder.boundResourceUid != uid) return
|
||||
holder.thumbnailImage.isVisible = true
|
||||
holder.loaderProgressBar.isVisible = true
|
||||
holder.videoView.isVisible = false
|
||||
holder.views.videoThumbnailImage.isVisible = true
|
||||
holder.views.videoLoaderProgress.isVisible = true
|
||||
holder.views.videoView.isVisible = false
|
||||
}
|
||||
|
||||
override fun onVideoFileLoadFailed(uid: String) {
|
||||
|
@ -82,8 +82,8 @@ internal class DefaultVideoLoaderTarget(val holder: VideoViewHolder, private val
|
|||
}
|
||||
|
||||
private fun arrangeForVideoReady() {
|
||||
holder.thumbnailImage.isVisible = false
|
||||
holder.loaderProgressBar.isVisible = false
|
||||
holder.videoView.isVisible = true
|
||||
holder.views.videoThumbnailImage.isVisible = false
|
||||
holder.views.videoLoaderProgress.isVisible = false
|
||||
holder.views.videoView.isVisible = true
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,11 +18,8 @@ package im.vector.lib.attachmentviewer
|
|||
|
||||
import android.util.Log
|
||||
import android.view.View
|
||||
import android.widget.ImageView
|
||||
import android.widget.ProgressBar
|
||||
import android.widget.TextView
|
||||
import android.widget.VideoView
|
||||
import androidx.core.view.isVisible
|
||||
import im.vector.lib.attachmentviewer.databinding.ItemVideoAttachmentBinding
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.disposables.Disposable
|
||||
|
@ -44,13 +41,9 @@ class VideoViewHolder constructor(itemView: View) :
|
|||
|
||||
var eventListener: WeakReference<AttachmentEventListener>? = null
|
||||
|
||||
val thumbnailImage: ImageView = itemView.findViewById(R.id.videoThumbnailImage)
|
||||
val videoView: VideoView = itemView.findViewById(R.id.videoView)
|
||||
val loaderProgressBar: ProgressBar = itemView.findViewById(R.id.videoLoaderProgress)
|
||||
val videoControlIcon: ImageView = itemView.findViewById(R.id.videoControlIcon)
|
||||
val errorTextView: TextView = itemView.findViewById(R.id.videoMediaViewerErrorView)
|
||||
val views = ItemVideoAttachmentBinding.bind(itemView)
|
||||
|
||||
internal val target = DefaultVideoLoaderTarget(this, thumbnailImage)
|
||||
internal val target = DefaultVideoLoaderTarget(this, views.videoThumbnailImage)
|
||||
|
||||
override fun onRecycled() {
|
||||
super.onRecycled()
|
||||
|
@ -77,12 +70,12 @@ class VideoViewHolder constructor(itemView: View) :
|
|||
}
|
||||
|
||||
override fun entersBackground() {
|
||||
if (videoView.isPlaying) {
|
||||
progress = videoView.currentPosition
|
||||
if (views.videoView.isPlaying) {
|
||||
progress = views.videoView.currentPosition
|
||||
progressDisposable?.dispose()
|
||||
progressDisposable = null
|
||||
videoView.stopPlayback()
|
||||
videoView.pause()
|
||||
views.videoView.stopPlayback()
|
||||
views.videoView.pause()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -92,9 +85,9 @@ class VideoViewHolder constructor(itemView: View) :
|
|||
|
||||
override fun onSelected(selected: Boolean) {
|
||||
if (!selected) {
|
||||
if (videoView.isPlaying) {
|
||||
progress = videoView.currentPosition
|
||||
videoView.stopPlayback()
|
||||
if (views.videoView.isPlaying) {
|
||||
progress = views.videoView.currentPosition
|
||||
views.videoView.stopPlayback()
|
||||
} else {
|
||||
progress = 0
|
||||
}
|
||||
|
@ -109,34 +102,34 @@ class VideoViewHolder constructor(itemView: View) :
|
|||
}
|
||||
|
||||
private fun startPlaying() {
|
||||
thumbnailImage.isVisible = false
|
||||
loaderProgressBar.isVisible = false
|
||||
videoView.isVisible = true
|
||||
views.videoThumbnailImage.isVisible = false
|
||||
views.videoLoaderProgress.isVisible = false
|
||||
views.videoView.isVisible = true
|
||||
|
||||
videoView.setOnPreparedListener {
|
||||
views.videoView.setOnPreparedListener {
|
||||
progressDisposable?.dispose()
|
||||
progressDisposable = Observable.interval(100, TimeUnit.MILLISECONDS)
|
||||
.timeInterval()
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe {
|
||||
val duration = videoView.duration
|
||||
val progress = videoView.currentPosition
|
||||
val isPlaying = videoView.isPlaying
|
||||
val duration = views.videoView.duration
|
||||
val progress = views.videoView.currentPosition
|
||||
val isPlaying = views.videoView.isPlaying
|
||||
// Log.v("FOO", "isPlaying $isPlaying $progress/$duration")
|
||||
eventListener?.get()?.onEvent(AttachmentEvents.VideoEvent(isPlaying, progress, duration))
|
||||
}
|
||||
}
|
||||
try {
|
||||
videoView.setVideoPath(mVideoPath)
|
||||
views.videoView.setVideoPath(mVideoPath)
|
||||
} catch (failure: Throwable) {
|
||||
// Couldn't open
|
||||
Log.v(VideoViewHolder::class.java.name, "Failed to start video")
|
||||
}
|
||||
|
||||
if (!wasPaused) {
|
||||
videoView.start()
|
||||
views.videoView.start()
|
||||
if (progress > 0) {
|
||||
videoView.seekTo(progress)
|
||||
views.videoView.seekTo(progress)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -146,17 +139,17 @@ class VideoViewHolder constructor(itemView: View) :
|
|||
when (commands) {
|
||||
AttachmentCommands.StartVideo -> {
|
||||
wasPaused = false
|
||||
videoView.start()
|
||||
views.videoView.start()
|
||||
}
|
||||
AttachmentCommands.PauseVideo -> {
|
||||
wasPaused = true
|
||||
videoView.pause()
|
||||
views.videoView.pause()
|
||||
}
|
||||
is AttachmentCommands.SeekTo -> {
|
||||
val duration = videoView.duration
|
||||
val duration = views.videoView.duration
|
||||
if (duration > 0) {
|
||||
val seekDuration = duration * (commands.percentProgress / 100f)
|
||||
videoView.seekTo(seekDuration.toInt())
|
||||
views.videoView.seekTo(seekDuration.toInt())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,31 +17,29 @@
|
|||
package im.vector.lib.attachmentviewer
|
||||
|
||||
import android.view.View
|
||||
import android.widget.ProgressBar
|
||||
import com.github.chrisbanes.photoview.PhotoView
|
||||
import im.vector.lib.attachmentviewer.databinding.ItemImageAttachmentBinding
|
||||
|
||||
class ZoomableImageViewHolder constructor(itemView: View) :
|
||||
BaseViewHolder(itemView) {
|
||||
|
||||
val touchImageView: PhotoView = itemView.findViewById(R.id.touchImageView)
|
||||
val imageLoaderProgress: ProgressBar = itemView.findViewById(R.id.imageLoaderProgress)
|
||||
val views = ItemImageAttachmentBinding.bind(itemView)
|
||||
|
||||
init {
|
||||
touchImageView.setAllowParentInterceptOnEdge(false)
|
||||
touchImageView.setOnScaleChangeListener { scaleFactor, _, _ ->
|
||||
views.touchImageView.setAllowParentInterceptOnEdge(false)
|
||||
views.touchImageView.setOnScaleChangeListener { scaleFactor, _, _ ->
|
||||
// Log.v("ATTACHEMENTS", "scaleFactor $scaleFactor")
|
||||
// It's a bit annoying but when you pitch down the scaling
|
||||
// is not exactly one :/
|
||||
touchImageView.setAllowParentInterceptOnEdge(scaleFactor <= 1.0008f)
|
||||
views.touchImageView.setAllowParentInterceptOnEdge(scaleFactor <= 1.0008f)
|
||||
}
|
||||
touchImageView.setScale(1.0f, true)
|
||||
touchImageView.setAllowParentInterceptOnEdge(true)
|
||||
views.touchImageView.setScale(1.0f, true)
|
||||
views.touchImageView.setAllowParentInterceptOnEdge(true)
|
||||
}
|
||||
|
||||
internal val target = DefaultImageLoaderTarget.ZoomableImageTarget(this, touchImageView)
|
||||
internal val target = DefaultImageLoaderTarget.ZoomableImageTarget(this, views.touchImageView)
|
||||
|
||||
override fun onRecycled() {
|
||||
super.onRecycled()
|
||||
touchImageView.setImageDrawable(null)
|
||||
views.touchImageView.setImageDrawable(null)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,7 +27,6 @@ $ source env/bin/activate
|
|||
Every time you want to launch these test homeservers, type:
|
||||
|
||||
```shell script
|
||||
$ virtualenv -p python3 env
|
||||
$ source env/bin/activate
|
||||
(env) $ demo/start.sh --no-rate-limit
|
||||
```
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
// TODO
|
||||
Diese neue Version enthält hauptsächlich Fehlerkorrekturen und Verbesserungen. Nachrichten verschicken geht jetzt viel schneller.
|
||||
Vollständige Versionshinweise: https://github.com/vector-im/element-android/releases/tag/v1.0.10
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
Diese neue Version enthält hauptsächlich Verbesserungen der Benutzer*innenoberfläche und der Handhabung. Du kannst jetzt ganz schnell Freund*innen einladen und DMs erstellen, indem du schlicht einen QR-Code scannst.
|
||||
Vollständige Versionshinweise: https://github.com/vector-im/element-android/releases/tag/v1.0.11
|
|
@ -1,2 +1,2 @@
|
|||
Main changes in this version: URL Preview, new Emoji keyboard, new room settings capabilities, and snow for Christmas!
|
||||
Full changelog: https://github.com/vector-im/element-android/releases/tag/v1.0.12
|
||||
Full changelog: https://github.com/vector-im/element-android/releases/tag/v1.0.13
|
|
@ -0,0 +1,2 @@
|
|||
Main changes in this version: Edit room permissions, automatic light/dark theme, and a bunch of bug fixes.
|
||||
Full changelog: https://github.com/vector-im/element-android/releases/tag/v1.0.14
|
|
@ -0,0 +1,2 @@
|
|||
Selles uues versioonis leidub põhiliselt veaparandusi ja pisikohendusi. Sõnumite saatmine on nüüd märkatavalt kiirem.
|
||||
Kogu ingliskeelne muudatuste logi: https://github.com/vector-im/element-android/releases/tag/v1.0.10
|
|
@ -0,0 +1,2 @@
|
|||
Uues versioonis leidub põhiliselt kasutajaliidese ning kasutajakogemuse parandusi. Nüüd saad sõpradele kutseid saata ning otsevestlusi alustada QR-koodi lugemise abil.
|
||||
Kõik muudatused: https://github.com/vector-im/element-android/releases/tag/v1.0.11
|
|
@ -1 +1,2 @@
|
|||
// برای انجام
|
||||
این نگارش جدید به طور عمده شامل رفع اشکالها و بهبودها است. ارسال پیام اکنون بسیار سریعتر است.
|
||||
گزارش دگرگونی کامل: https://github.com/vector-im/element-android/releases/tag/v1.0.10
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
این نگارش جدید به طور عمده شامل رابط کاربری و بهبود تجربه کاربر است. اکنون میتوانید با پویش کدهای QR دوستانتان را دعوت کرده و بسیار سریع پیام مستقیم ایجاد کنید.
|
||||
گزارش دگرگونی کامل: https://github.com/vector-im/element-android/releases/tag/v1.0.11
|
|
@ -1 +1 @@
|
|||
گپ و تماس نامتمرکز امن. دادههایتان را از شرکتها امن نگه دارید.
|
||||
گپ و تماس نامتمرکز امن. دادههایتان را از اشخاص سوم امن نگه دارید.
|
||||
|
|
|
@ -1 +1 @@
|
|||
المنت (ریوت سابق)
|
||||
Element (پیشتر Riot.im)
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
Tämä versio sisältää pääosin käyttöliittymä- ja käyttökokemusparannuksia. Voit nyt kutsua kavereita ja luoda yksityisviestejä nopeasti QR-koodeja lukemalla.
|
||||
Täysi muutosloki: https://github.com/vector-im/element-android/releases/tag/v1.0.11
|
|
@ -1,30 +1,30 @@
|
|||
Element on uudenlainen viestinsovellus, joka:
|
||||
|
||||
1. Antaa sinun päättää yksityisyydestäsi.
|
||||
1. Antaa sinun päättää yksityisyydestäsi
|
||||
2. Antaa sinun kommunikoida kenen tahansa kanssa Matrix-verkossa ja jopa sen ulkopuolella siltaamalla sovelluksiin, kuten Slack
|
||||
3. Suojaa sinua mainonnalta, tietojen keräämiseltä ja suljetuilta alustoilta
|
||||
4. Suojaa sinut päästä päähän -salauksella sekä ristiin varmentamisella muiden todentamiseksi
|
||||
|
||||
Element eroaa täysin muista viestintäsovelluksista, koska se on hajautettu ja avointa lähdekoodia.
|
||||
|
||||
Element antaa sinun isännöidä itse - valita isännän - jotta sinulla on yksityisyys ja voit hallita tietojasi sekä keskustelujasi. Se antaa sinulle pääsyn avoimeen verkkoon; joten et ole jumissa Elementin käyttäjissä.
|
||||
Element antaa sinun isännöidä itse - tai valita palveluntarjoajan - jotta sinulla on yksityisyys ja voit hallita tietojasi sekä keskustelujasi. Se antaa sinulle pääsyn avoimeen verkkoon, joten et jää juttelemaan vain toisten Elementin käyttäjien kanssa. Se on myös hyvin turvallinen.
|
||||
|
||||
Element pystyy tekemään kaiken tämän, koska se toimii Matrixilla - avoimella, hajautetun viestinnän standardilla.
|
||||
|
||||
Element antaa sinulle hallinnan antamalla sinun valita, kuka isännöi keskustelujasi. Element-sovelluksessa voit valita isännän eri tavoin:
|
||||
Element antaa sinulle päätösvallan antamalla sinun valita, kuka isännöi keskustelujasi. Element-sovelluksessa voit valita isännän eri tavoin:
|
||||
|
||||
1. Hanki ilmainen tili Matrix-kehittäjien ylläpitämällä matrix.org-palvelimella tai valitse tuhansista vapaaehtoisten ylläpitämistä julkisista palvelimista.
|
||||
2. Isännöi tiliäsi itse suorittamalla palvelinta omalla laitteellasi
|
||||
3. Luo tili mukautetulla palvelimella yksinkertaisesti tilaamalla Element Matrix Services -palvelu
|
||||
2. Isännöi tiliäsi itse ylläpitämällä palvelinta omalla laitteellasi
|
||||
3. Luo tili sinua varten tehdyllä palvelimella tilaamalla Element Matrix Services -palvelu
|
||||
|
||||
<b>Miksi valita Element?</b>
|
||||
|
||||
<b>OMAT TIEDOT</b>: Sinä päätät, missä tietosi ja viestisi säilytetään. Hallitset sitä itse, eikä jokin MEGAYHTIÖ, joka tutkii tietojasi tai antaa niitä kolmansille osapuolille.
|
||||
<b>OMAT TIEDOT</b>: Sinä päätät, missä tietosi ja viestisi säilytetään. Sinä määräät, ei jokin jättiyhtiö, joka tutkii tietojasi tai antaa niitä kolmansille osapuolille.
|
||||
|
||||
<b>AVOIN KOMMUNIKOINYI JA YHTEISTYÖ</b>: Voit keskustella kaikkien muiden Matrix-verkon käyttäjien kanssa, riippumatta siitä käyttävätkö he Elementiä tai muuta Matrix-sovellusta, ja vaikka he käyttäisivät eri viestijärjestelmiä, kuten Slack, IRC tai XMPP.
|
||||
<b>AVOINTA VIESTINTÄÄ JA YHTEISTYÖTÄ</b>: Voit keskustella kaikkien muiden Matrix-verkon käyttäjien kanssa, riippumatta siitä käyttävätkö he Elementiä tai muuta Matrix-sovellusta, ja vaikka he käyttäisivät eri viestijärjestelmiä, kuten Slack, IRC tai XMPP.
|
||||
|
||||
<b>ERITTÄIN TURVALLINEN</b>: Vahva päästä päähän -salaus (vain keskustelussa olevat voivat purkaa viestien salauksen), ja ristiin varmentaminen keskustelun osallistujien laitteiden tarkistamiseksi.
|
||||
|
||||
<b>TÄYDELLISTÄ VIESTINTÄÄ</b>: Viestit, ääni- ja videopuhelut, tiedostojen jakaminen, näytön jakaminen ja koko joukko integraatioita, botteja ja widgettejä. Rakenna huoneita, yhteisöjä, pidä yhteyttä ja tee asioita.
|
||||
<b>KATTAVAA VIESTINTÄÄ</b>: Viestit, ääni- ja videopuhelut, tiedostojen jakaminen, näytön jakaminen ja koko joukko integraatioita, botteja ja sovelmia. Rakenna huoneita ja yhteisöjä, pidä yhteyttä ja hoida asiasi.
|
||||
|
||||
<b>MISSÄ TAHANSA OLETKIN</b>: Pidä yhteyttä missä tahansa, täysin synkronoidun viestihistorian kautta kaikilla laitteillasi ja verkossa osoitteessa https://app.element.io.
|
||||
|
|
|
@ -1 +1 @@
|
|||
Turvallista, hajautettua, keskusteluja ja VoIP-puheluita. Pidä tietosi turvassa.
|
||||
Turvallista, hajautettua keskustelua ja VoIP-puheluita. Pidä tietosi turvassa.
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
Cette nouvelle version contient principalement des corrections de bogues et des améliorations. Envoyer un message est maintenant plus rapide.
|
||||
Liste complète des changements : https://github.com/vector-im/element-android/releases/tag/v1.0.10
|
|
@ -0,0 +1,2 @@
|
|||
Ez az új verzió főképp hibajavításokat, és teljesítménybeli fejlesztéseket tartalmaz. Most már sokkal gyorsabb az üzenetek elküldése.
|
||||
A változtatások teljes listája itt található: https://github.com/vector-im/element-android/releases/tag/v1.0.10
|
|
@ -0,0 +1,2 @@
|
|||
Ez az új verzió főleg a felhasználói felülettel és a felhasználói élménnyel kapcsolatos javításokat tartalmaz. Mostantól már sokkal gyorsabban hívhatsz meg új ismerősöket a QR kód beolvasás által.
|
||||
A változtatások teljes listája itt található: https://github.com/vector-im/element-android/releases/tag/v1.0.11
|
|
@ -17,7 +17,7 @@ Az Element a te kezedbe adja az irányítást azáltal, hogy eldöntheted, ki t
|
|||
2. A saját számítógépeden is futtathatsz szervert
|
||||
3. Előfizethetsz egy saját szerverre az Element Matrix Szolgáltatások platformon
|
||||
|
||||
<b>Miért válaszd az Element-et?</b>
|
||||
<b>Miért jó az Element-et választani?</b>
|
||||
|
||||
<b>ADATAID MEGVÉDÉSE</b>: Eldöntheted, hol tárold az adataid és üzeneteid. A te tulajdonodban van, nem valami megacégnél, ami bányássza az adataid, vagy továbbadja másoknak.
|
||||
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
Questa nuova versione contiene principalmente miglioramenti di interfaccia ed esperienza utente. Ora puoi invitare amici e iniziare messaggi diretti rapidamente tramite codici QR.
|
||||
Cronologia completa: https://github.com/vector-im/element-android/releases/tag/v1.0.11
|
|
@ -0,0 +1,2 @@
|
|||
Denne nye versjonen inneholder hovedsakelig feilrettinger og forbedringer. Å sende en melding er nå mye raskere.
|
||||
Full endringslogg: https://github.com/vector-im/element-android/releases/tag/v1.0.10
|
|
@ -0,0 +1,2 @@
|
|||
Denne nye versjonen inneholder hovedsakelig forbedringer av brukergrensesnittet og brukeropplevelsen. Nå kan du invitere venner og opprette DM veldig raskt ved å skanne QR-koder.
|
||||
Full endringslogg: https://github.com/vector-im/element-android/releases/tag/v1.0.11
|
|
@ -0,0 +1 @@
|
|||
Sikker desentralisert chat & VoIP. Beskytt dataene dine fra tredjeparter.
|
|
@ -0,0 +1 @@
|
|||
Element (tidligere Riot.im)
|
|
@ -0,0 +1,2 @@
|
|||
Esta nova versão contém principalmente melhorias na interface do usuário e na experiência do usuário. Agora você pode convidar amigos e criar conversas rapidamente, digitalizando códigos QR.
|
||||
Registro completo de alterações: https://github.com/vector-im/element-android/releases/tag/v1.0.11
|
|
@ -0,0 +1,2 @@
|
|||
Эта новая версия в основном содержит исправления ошибок и улучшения. Отправка сообщения стала намного быстрее.
|
||||
Полный список изменений: https://github.com/vector-im/element-android/releases/tag/v1.0.10
|
|
@ -0,0 +1,2 @@
|
|||
Эта новая версия в основном содержит улучшения пользовательского интерфейса и взаимодействия с пользователем. Теперь вы можете приглашать друзей и очень быстро создавать чаты, сканируя QR-коды.
|
||||
Полный список изменений: https://github.com/vector-im/element-android/releases/tag/v1.0.11
|
|
@ -0,0 +1,2 @@
|
|||
Táto verzia obsahuje predovšetkým opravy chýb. Odosielanie správ je odteraz omnoho rýchlejšie.
|
||||
Kompletný zoznam zmien: https://github.com/vector-im/element-android/releases/tag/v1.0.10
|
|
@ -0,0 +1,2 @@
|
|||
Táto verzia obsahuje najmä vylepšenia používateľského rozhrania. Pozývať priateľov alebo vytvárať priame konverzácie môžete veľmi rýchlo naskenovaním QR kódov.
|
||||
Kompletný zoznam zmien: https://github.com/vector-im/element-android/releases/tag/v1.0.11
|
|
@ -0,0 +1,2 @@
|
|||
Den här nya versionen innehåller mest förbättringar för användargränssnittet och användarupplevelsen. Du kan nu bjuda in vänner och skapa direktmeddelanden väldigt snabbt genom att skanna QR-koder.
|
||||
Full ändringslogg: https://github.com/vector-im/element-android/releases/tag/v1.0.11
|
|
@ -0,0 +1,2 @@
|
|||
Ця нова версія містить переважно поліпшення інтерфейсу та зручності користування. Тепер ви можете запросити друзів і створити прямі повідомлення дуже швидко, скануючи QR-коди.
|
||||
Повний перелік змін: https://github.com/vector-im/element-android/releases/tag/v1.0.11
|
|
@ -0,0 +1,2 @@
|
|||
Phiên bản mới này chủ yếu bao gồm sửa lỗi và một số cải thiện. Gửi tin nhắn trở nên nhanh chóng hơn trước.
|
||||
Danh sách đầy đủ các thay đổi: https://github.com/vector-im/element-android/releases/tag/v1.0.10
|
|
@ -0,0 +1,2 @@
|
|||
Phiên bản mới này chủ yếu bao gồm các cải thiện về giao diện và trải nghiệm người dùng. Bây giờ bạn có thể mời bạn bè và bắt đầu nói chuyện nhanh chóng bằng cách quét mã QR.
|
||||
Danh sách đầy đủ các thay đổi: https://github.com/vector-im/element-android/releases/tag/v1.0.11
|
|
@ -0,0 +1 @@
|
|||
Ứng dụng chat và gọi phân tán bảo mật. Bảo vệ dữ liệu của bạn khỏi bên thứ ba.
|
|
@ -0,0 +1 @@
|
|||
Element (trước là Riot.im)
|
|
@ -0,0 +1,2 @@
|
|||
這個新版本主要包含使用者介面與使用者體驗改善。現在您可以邀請朋友,並透過掃描 QR code 來快速建立直接訊息了。
|
||||
完整變更紀錄:https://github.com/vector-im/element-android/releases/tag/v1.0.11
|
|
@ -0,0 +1,2 @@
|
|||
此版本中的主要變更:URL 預覽、新的表情符號鍵盤、新的聊天室設定功能以及聖誕節降雪!
|
||||
完整變更紀錄:https://github.com/vector-im/element-android/releases/tag/v1.0.12
|
|
@ -0,0 +1,2 @@
|
|||
此版本中的主要變更:URL 預覽、新的表情符號鍵盤、新的聊天室設定功能以及聖誕節降雪!
|
||||
完整變更紀錄:https://github.com/vector-im/element-android/releases/tag/v1.0.12
|
|
@ -1,6 +1,6 @@
|
|||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-all.zip
|
||||
distributionSha256Sum=22449f5231796abd892c98b2a07c9ceebe4688d192cd2d6763f8e3bf8acbedeb
|
||||
distributionSha256Sum=a7ca23b3ccf265680f2bfd35f1f00b1424f4466292c7337c85d46c9641b3f053
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.8-all.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply plugin: 'kotlin-kapt'
|
||||
apply plugin: 'kotlin-android-extensions'
|
||||
apply plugin: 'kotlin-parcelize'
|
||||
apply plugin: 'realm-android'
|
||||
|
||||
buildscript {
|
||||
|
@ -13,10 +13,6 @@ buildscript {
|
|||
}
|
||||
}
|
||||
|
||||
androidExtensions {
|
||||
experimental = true
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion 29
|
||||
testOptions.unitTests.includeAndroidResources = true
|
||||
|
@ -171,9 +167,6 @@ dependencies {
|
|||
implementation 'com.jakewharton.timber:timber:4.7.1'
|
||||
implementation 'com.facebook.stetho:stetho-okhttp3:1.5.1'
|
||||
|
||||
// Bus
|
||||
implementation 'org.greenrobot:eventbus:3.1.1'
|
||||
|
||||
// Phone number https://github.com/google/libphonenumber
|
||||
implementation 'com.googlecode.libphonenumber:libphonenumber:8.10.23'
|
||||
|
||||
|
|
|
@ -20,14 +20,8 @@
|
|||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
|
||||
|
||||
### EVENT BUS ###
|
||||
|
||||
# BMA: Not sure I can delete this one without side effect
|
||||
-keepattributes *Annotation*
|
||||
-keepclassmembers class * {
|
||||
@org.greenrobot.eventbus.Subscribe <methods>;
|
||||
}
|
||||
-keep enum org.greenrobot.eventbus.ThreadMode { *; }
|
||||
|
||||
### MOSHI ###
|
||||
|
||||
|
|
|
@ -86,7 +86,7 @@ class CommonTestHelper(context: Context) {
|
|||
*
|
||||
* @param session the session to sync
|
||||
*/
|
||||
fun syncSession(session: Session) {
|
||||
fun syncSession(session: Session, timeout: Long = TestConstants.timeOutMillis) {
|
||||
val lock = CountDownLatch(1)
|
||||
|
||||
val job = GlobalScope.launch(Dispatchers.Main) {
|
||||
|
@ -109,7 +109,7 @@ class CommonTestHelper(context: Context) {
|
|||
}
|
||||
GlobalScope.launch(Dispatchers.Main) { syncLiveData.observeForever(syncObserver) }
|
||||
|
||||
await(lock)
|
||||
await(lock, timeout)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -119,7 +119,7 @@ class CommonTestHelper(context: Context) {
|
|||
* @param message the message to send
|
||||
* @param nbOfMessages the number of time the message will be sent
|
||||
*/
|
||||
fun sendTextMessage(room: Room, message: String, nbOfMessages: Int): List<TimelineEvent> {
|
||||
fun sendTextMessage(room: Room, message: String, nbOfMessages: Int, timeout: Long = TestConstants.timeOutMillis): List<TimelineEvent> {
|
||||
val timeline = room.createTimeline(null, TimelineSettings(10))
|
||||
val sentEvents = ArrayList<TimelineEvent>(nbOfMessages)
|
||||
val latch = CountDownLatch(1)
|
||||
|
@ -151,7 +151,7 @@ class CommonTestHelper(context: Context) {
|
|||
room.sendTextMessage(message + " #" + (i + 1))
|
||||
}
|
||||
// Wait 3 second more per message
|
||||
await(latch, timeout = TestConstants.timeOutMillis + 3_000L * nbOfMessages)
|
||||
await(latch, timeout = timeout + 3_000L * nbOfMessages)
|
||||
timeline.dispose()
|
||||
|
||||
// Check that all events has been created
|
||||
|
@ -215,14 +215,14 @@ class CommonTestHelper(context: Context) {
|
|||
.getLoginFlow(hs, it)
|
||||
}
|
||||
|
||||
doSync<RegistrationResult> {
|
||||
doSync<RegistrationResult>(timeout = 60_000) {
|
||||
matrix.authenticationService
|
||||
.getRegistrationWizard()
|
||||
.createAccount(userName, password, null, it)
|
||||
}
|
||||
|
||||
// Perform dummy step
|
||||
val registrationResult = doSync<RegistrationResult> {
|
||||
val registrationResult = doSync<RegistrationResult>(timeout = 60_000) {
|
||||
matrix.authenticationService
|
||||
.getRegistrationWizard()
|
||||
.dummy(it)
|
||||
|
@ -231,7 +231,7 @@ class CommonTestHelper(context: Context) {
|
|||
assertTrue(registrationResult is RegistrationResult.Success)
|
||||
val session = (registrationResult as RegistrationResult.Success).session
|
||||
if (sessionTestParams.withInitialSync) {
|
||||
syncSession(session)
|
||||
syncSession(session, 60_000)
|
||||
}
|
||||
|
||||
return session
|
||||
|
|
|
@ -18,14 +18,21 @@ package org.matrix.android.sdk.common
|
|||
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
|
||||
data class CryptoTestData(val firstSession: Session,
|
||||
val roomId: String,
|
||||
val secondSession: Session? = null,
|
||||
val thirdSession: Session? = null) {
|
||||
data class CryptoTestData(val roomId: String,
|
||||
val sessions: List<Session>) {
|
||||
|
||||
val firstSession: Session
|
||||
get() = sessions.first()
|
||||
|
||||
val secondSession: Session?
|
||||
get() = sessions.getOrNull(1)
|
||||
|
||||
val thirdSession: Session?
|
||||
get() = sessions.getOrNull(2)
|
||||
|
||||
fun cleanUp(testHelper: CommonTestHelper) {
|
||||
testHelper.signOutAndClose(firstSession)
|
||||
secondSession?.let { testHelper.signOutAndClose(it) }
|
||||
thirdSession?.let { testHelper.signOutAndClose(it) }
|
||||
sessions.forEach {
|
||||
testHelper.signOutAndClose(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -73,7 +73,7 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
|
|||
}
|
||||
}
|
||||
|
||||
return CryptoTestData(aliceSession, roomId)
|
||||
return CryptoTestData(roomId, listOf(aliceSession))
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -139,7 +139,7 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
|
|||
// assertNotNull(roomFromBobPOV.powerLevels)
|
||||
// assertTrue(roomFromBobPOV.powerLevels.maySendMessage(bobSession.myUserId))
|
||||
|
||||
return CryptoTestData(aliceSession, aliceRoomId, bobSession)
|
||||
return CryptoTestData(aliceRoomId, listOf(aliceSession, bobSession))
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -157,7 +157,7 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
|
|||
// wait the initial sync
|
||||
SystemClock.sleep(1000)
|
||||
|
||||
return CryptoTestData(aliceSession, aliceRoomId, cryptoTestData.secondSession, samSession)
|
||||
return CryptoTestData(aliceRoomId, listOf(aliceSession, cryptoTestData.secondSession!!, samSession))
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -381,4 +381,30 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun doE2ETestWithManyMembers(numberOfMembers: Int): CryptoTestData {
|
||||
val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, defaultSessionParams)
|
||||
aliceSession.cryptoService().setWarnOnUnknownDevices(false)
|
||||
|
||||
val roomId = mTestHelper.doSync<String> {
|
||||
aliceSession.createRoom(CreateRoomParams().apply { name = "MyRoom" }, it)
|
||||
}
|
||||
val room = aliceSession.getRoom(roomId)!!
|
||||
|
||||
mTestHelper.runBlockingTest {
|
||||
room.enableEncryption()
|
||||
}
|
||||
|
||||
val sessions = mutableListOf(aliceSession)
|
||||
for (index in 1 until numberOfMembers) {
|
||||
val session = mTestHelper.createAccount("User_$index", defaultSessionParams)
|
||||
mTestHelper.doSync<Unit>(timeout = 600_000) { room.invite(session.myUserId, null, it) }
|
||||
println("TEST -> " + session.myUserId + " invited")
|
||||
mTestHelper.doSync<Unit> { session.joinRoom(room.roomId, null, emptyList(), it) }
|
||||
println("TEST -> " + session.myUserId + " joined")
|
||||
sessions.add(session)
|
||||
}
|
||||
|
||||
return CryptoTestData(roomId, sessions)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -50,6 +50,8 @@ import org.junit.FixMethodOrder
|
|||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.junit.runners.MethodSorters
|
||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||
import org.matrix.android.sdk.api.session.room.model.message.MessageContent
|
||||
import java.util.concurrent.CountDownLatch
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
|
@ -296,4 +298,77 @@ class KeyShareTests : InstrumentedTest {
|
|||
mTestHelper.signOutAndClose(aliceSession1)
|
||||
mTestHelper.signOutAndClose(aliceSession2)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test_ImproperKeyShareBug() {
|
||||
val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
|
||||
|
||||
mTestHelper.doSync<Unit> {
|
||||
aliceSession.cryptoService().crossSigningService()
|
||||
.initializeCrossSigning(UserPasswordAuth(
|
||||
user = aliceSession.myUserId,
|
||||
password = TestConstants.PASSWORD
|
||||
), it)
|
||||
}
|
||||
|
||||
// Create an encrypted room and send a couple of messages
|
||||
val roomId = mTestHelper.doSync<String> {
|
||||
aliceSession.createRoom(
|
||||
CreateRoomParams().apply {
|
||||
visibility = RoomDirectoryVisibility.PRIVATE
|
||||
enableEncryption()
|
||||
},
|
||||
it
|
||||
)
|
||||
}
|
||||
val roomAlicePov = aliceSession.getRoom(roomId)
|
||||
assertNotNull(roomAlicePov)
|
||||
Thread.sleep(1_000)
|
||||
assertTrue(roomAlicePov?.isEncrypted() == true)
|
||||
val secondEventId = mTestHelper.sendTextMessage(roomAlicePov!!, "Message", 3)[1].eventId
|
||||
|
||||
// Create bob session
|
||||
|
||||
val bobSession = mTestHelper.createAccount(TestConstants.USER_BOB, SessionTestParams(true))
|
||||
mTestHelper.doSync<Unit> {
|
||||
bobSession.cryptoService().crossSigningService()
|
||||
.initializeCrossSigning(UserPasswordAuth(
|
||||
user = bobSession.myUserId,
|
||||
password = TestConstants.PASSWORD
|
||||
), it)
|
||||
}
|
||||
|
||||
// Let alice invite bob
|
||||
mTestHelper.doSync<Unit> {
|
||||
roomAlicePov.invite(bobSession.myUserId, null, it)
|
||||
}
|
||||
|
||||
mTestHelper.doSync<Unit> {
|
||||
bobSession.joinRoom(roomAlicePov.roomId, null, emptyList(), it)
|
||||
}
|
||||
|
||||
// we want to discard alice outbound session
|
||||
aliceSession.cryptoService().discardOutboundSession(roomAlicePov.roomId)
|
||||
|
||||
// and now resend a new message to reset index to 0
|
||||
mTestHelper.sendTextMessage(roomAlicePov, "After", 1)
|
||||
|
||||
val roomRoomBobPov = aliceSession.getRoom(roomId)
|
||||
val beforeJoin = roomRoomBobPov!!.getTimeLineEvent(secondEventId)
|
||||
|
||||
var dRes = tryOrNull { bobSession.cryptoService().decryptEvent(beforeJoin!!.root, "") }
|
||||
|
||||
assert(dRes == null)
|
||||
|
||||
// Try to re-ask the keys
|
||||
|
||||
bobSession.cryptoService().reRequestRoomKeyForEvent(beforeJoin!!.root)
|
||||
|
||||
Thread.sleep(3_000)
|
||||
|
||||
// With the bug the first session would have improperly reshare that key :/
|
||||
dRes = tryOrNull { bobSession.cryptoService().decryptEvent(beforeJoin.root, "") }
|
||||
Log.d("#TEST", "KS: sgould not decrypt that ${beforeJoin.root.getClearContent().toModel<MessageContent>()?.body}")
|
||||
assert(dRes?.clearEvent == null)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,92 @@
|
|||
/*
|
||||
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.android.sdk.session.room.timeline
|
||||
|
||||
import org.junit.FixMethodOrder
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.junit.runners.JUnit4
|
||||
import org.junit.runners.MethodSorters
|
||||
import org.matrix.android.sdk.InstrumentedTest
|
||||
import org.matrix.android.sdk.api.extensions.orFalse
|
||||
import org.matrix.android.sdk.api.session.events.model.toModel
|
||||
import org.matrix.android.sdk.api.session.room.model.message.MessageContent
|
||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings
|
||||
import org.matrix.android.sdk.common.CommonTestHelper
|
||||
import org.matrix.android.sdk.common.CryptoTestHelper
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import kotlin.test.fail
|
||||
|
||||
@RunWith(JUnit4::class)
|
||||
@FixMethodOrder(MethodSorters.JVM)
|
||||
class TimelineWithManyMembersTest : InstrumentedTest {
|
||||
|
||||
companion object {
|
||||
private const val NUMBER_OF_MEMBERS = 6
|
||||
}
|
||||
|
||||
private val commonTestHelper = CommonTestHelper(context())
|
||||
private val cryptoTestHelper = CryptoTestHelper(commonTestHelper)
|
||||
|
||||
/**
|
||||
* Ensures when someone sends a message to a crowded room, everyone can decrypt the message.
|
||||
*/
|
||||
@Test
|
||||
fun everyone_should_decrypt_message_in_a_crowded_room() {
|
||||
val cryptoTestData = cryptoTestHelper.doE2ETestWithManyMembers(NUMBER_OF_MEMBERS)
|
||||
|
||||
val sessionForFirstMember = cryptoTestData.firstSession
|
||||
val roomForFirstMember = sessionForFirstMember.getRoom(cryptoTestData.roomId)!!
|
||||
|
||||
val firstMessage = "First messages from Alice"
|
||||
commonTestHelper.sendTextMessage(
|
||||
roomForFirstMember,
|
||||
firstMessage,
|
||||
1,
|
||||
600_000
|
||||
)
|
||||
|
||||
for (index in 1 until cryptoTestData.sessions.size) {
|
||||
val session = cryptoTestData.sessions[index]
|
||||
val roomForCurrentMember = session.getRoom(cryptoTestData.roomId)!!
|
||||
val timelineForCurrentMember = roomForCurrentMember.createTimeline(null, TimelineSettings(30))
|
||||
timelineForCurrentMember.start()
|
||||
|
||||
session.startSync(true)
|
||||
|
||||
run {
|
||||
val lock = CountDownLatch(1)
|
||||
val eventsListener = commonTestHelper.createEventListener(lock) { snapshot ->
|
||||
snapshot
|
||||
.find { it.isEncrypted() }
|
||||
?.let {
|
||||
val body = it.root.getClearContent()?.toModel<MessageContent>()?.body
|
||||
if (body?.startsWith(firstMessage).orFalse()) {
|
||||
println("User " + session.myUserId + " decrypted as " + body)
|
||||
return@createEventListener true
|
||||
} else {
|
||||
fail("User " + session.myUserId + " decrypted as " + body + " CryptoError: " + it.root.mCryptoError)
|
||||
}
|
||||
} ?: return@createEventListener false
|
||||
}
|
||||
timelineForCurrentMember.addListener(eventsListener)
|
||||
commonTestHelper.await(lock, 600_000)
|
||||
}
|
||||
session.stopSync()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -41,6 +41,16 @@ interface AuthenticationService {
|
|||
*/
|
||||
fun getLoginFlowOfSession(sessionId: String, callback: MatrixCallback<LoginFlowResult>): Cancelable
|
||||
|
||||
/**
|
||||
* Get a SSO url
|
||||
*/
|
||||
fun getSsoUrl(redirectUrl: String, deviceId: String?, providerId: String?): String?
|
||||
|
||||
/**
|
||||
* Get the sign in or sign up fallback URL
|
||||
*/
|
||||
fun getFallbackUrl(forSignIn: Boolean, deviceId: String?): String?
|
||||
|
||||
/**
|
||||
* Return a LoginWizard, to login to the homeserver. The login flow has to be retrieved first.
|
||||
*/
|
||||
|
|
|
@ -19,7 +19,7 @@ package org.matrix.android.sdk.api.auth.data
|
|||
import android.os.Parcelable
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import kotlinx.android.parcel.Parcelize
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
@Parcelize
|
||||
|
|
|
@ -37,6 +37,6 @@ class SenderNotificationPermissionCondition(
|
|||
|
||||
fun isSatisfied(event: Event, powerLevels: PowerLevelsContent): Boolean {
|
||||
val powerLevelsHelper = PowerLevelsHelper(powerLevels)
|
||||
return event.senderId != null && powerLevelsHelper.getUserPowerLevelValue(event.senderId) >= powerLevelsHelper.notificationLevel(key)
|
||||
return event.senderId != null && powerLevelsHelper.getUserPowerLevelValue(event.senderId) >= powerLevels.notificationLevel(key)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@ import android.net.Uri
|
|||
import android.os.Parcelable
|
||||
import androidx.exifinterface.media.ExifInterface
|
||||
import com.squareup.moshi.JsonClass
|
||||
import kotlinx.android.parcel.Parcelize
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import org.matrix.android.sdk.api.util.MimeTypes.normalizeMimeType
|
||||
|
||||
@Parcelize
|
||||
|
|
|
@ -25,7 +25,6 @@ interface PermalinkService {
|
|||
|
||||
companion object {
|
||||
const val MATRIX_TO_URL_BASE = "https://matrix.to/#/"
|
||||
const val MATRIX_TO_CUSTOM_SCHEME_URL_BASE = "element://"
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -25,28 +25,85 @@ import org.matrix.android.sdk.api.session.room.powerlevels.Role
|
|||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class PowerLevelsContent(
|
||||
/**
|
||||
* The level required to ban a user. Defaults to 50 if unspecified.
|
||||
*/
|
||||
@Json(name = "ban") val ban: Int = Role.Moderator.value,
|
||||
/**
|
||||
* The level required to kick a user. Defaults to 50 if unspecified.
|
||||
*/
|
||||
@Json(name = "kick") val kick: Int = Role.Moderator.value,
|
||||
/**
|
||||
* The level required to invite a user. Defaults to 50 if unspecified.
|
||||
*/
|
||||
@Json(name = "invite") val invite: Int = Role.Moderator.value,
|
||||
/**
|
||||
* The level required to redact an event. Defaults to 50 if unspecified.
|
||||
*/
|
||||
@Json(name = "redact") val redact: Int = Role.Moderator.value,
|
||||
/**
|
||||
* The default level required to send message events. Can be overridden by the events key. Defaults to 0 if unspecified.
|
||||
*/
|
||||
@Json(name = "events_default") val eventsDefault: Int = Role.Default.value,
|
||||
@Json(name = "events") val events: MutableMap<String, Int> = HashMap(),
|
||||
/**
|
||||
* The level required to send specific event types. This is a mapping from event type to power level required.
|
||||
*/
|
||||
@Json(name = "events") val events: Map<String, Int> = emptyMap(),
|
||||
/**
|
||||
* The default power level for every user in the room, unless their user_id is mentioned in the users key. Defaults to 0 if unspecified.
|
||||
*/
|
||||
@Json(name = "users_default") val usersDefault: Int = Role.Default.value,
|
||||
@Json(name = "users") val users: MutableMap<String, Int> = HashMap(),
|
||||
/**
|
||||
* The power levels for specific users. This is a mapping from user_id to power level for that user.
|
||||
*/
|
||||
@Json(name = "users") val users: Map<String, Int> = emptyMap(),
|
||||
/**
|
||||
* The default level required to send state events. Can be overridden by the events key. Defaults to 50 if unspecified.
|
||||
*/
|
||||
@Json(name = "state_default") val stateDefault: Int = Role.Moderator.value,
|
||||
@Json(name = "notifications") val notifications: Map<String, Any> = HashMap()
|
||||
/**
|
||||
* The power level requirements for specific notification types. This is a mapping from key to power level for that notifications key.
|
||||
*/
|
||||
@Json(name = "notifications") val notifications: Map<String, Any> = emptyMap()
|
||||
) {
|
||||
/**
|
||||
* Alter this content with a new power level for the specified user
|
||||
* Return a copy of this content with a new power level for the specified user
|
||||
*
|
||||
* @param userId the userId to alter the power level of
|
||||
* @param powerLevel the new power level, or null to set the default value.
|
||||
*/
|
||||
fun setUserPowerLevel(userId: String, powerLevel: Int?) {
|
||||
if (powerLevel == null || powerLevel == usersDefault) {
|
||||
users.remove(userId)
|
||||
} else {
|
||||
users[userId] = powerLevel
|
||||
fun setUserPowerLevel(userId: String, powerLevel: Int?): PowerLevelsContent {
|
||||
return copy(
|
||||
users = users.toMutableMap().apply {
|
||||
if (powerLevel == null || powerLevel == usersDefault) {
|
||||
remove(userId)
|
||||
} else {
|
||||
put(userId, powerLevel)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the notification level for a dedicated key.
|
||||
*
|
||||
* @param key the notification key
|
||||
* @return the level, default to Moderator if the key is not found
|
||||
*/
|
||||
fun notificationLevel(key: String): Int {
|
||||
return when (val value = notifications[key]) {
|
||||
// the first implementation was a string value
|
||||
is String -> value.toInt()
|
||||
is Double -> value.toInt()
|
||||
is Int -> value
|
||||
else -> Role.Moderator.value
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Key to use for content.notifications and get the level required to trigger an @room notification. Defaults to 50 if unspecified.
|
||||
*/
|
||||
const val NOTIFICATIONS_ROOM_KEY = "room"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,6 +27,7 @@ data class MessageStickerContent(
|
|||
/**
|
||||
* Set in local, not from server
|
||||
*/
|
||||
@Transient
|
||||
override val msgType: String = MessageType.MSGTYPE_STICKER_LOCAL,
|
||||
|
||||
/**
|
||||
|
|
|
@ -108,19 +108,4 @@ class PowerLevelsHelper(private val powerLevelsContent: PowerLevelsContent) {
|
|||
val powerLevel = getUserPowerLevelValue(userId)
|
||||
return powerLevel >= powerLevelsContent.redact
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the notification level for a dedicated key.
|
||||
*
|
||||
* @param key the notification key
|
||||
* @return the level
|
||||
*/
|
||||
fun notificationLevel(key: String): Int {
|
||||
return when (val value = powerLevelsContent.notifications[key]) {
|
||||
// the first implementation was a string value
|
||||
is String -> value.toInt()
|
||||
is Int -> value
|
||||
else -> Role.Moderator.value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.android.sdk.api.session.room.state
|
||||
|
||||
import org.matrix.android.sdk.api.query.QueryStringValue
|
||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||
import org.matrix.android.sdk.api.session.events.model.toModel
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomJoinRules
|
||||
import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesContent
|
||||
|
||||
/**
|
||||
* Return true if a room can be joined by anyone (RoomJoinRules.PUBLIC)
|
||||
*/
|
||||
fun StateService.isPublic(): Boolean {
|
||||
return getStateEvent(EventType.STATE_ROOM_JOIN_RULES, QueryStringValue.NoCondition)
|
||||
?.content
|
||||
?.toModel<RoomJoinRulesContent>()
|
||||
?.joinRules == RoomJoinRules.PUBLIC
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.android.sdk.api.util
|
||||
|
||||
import java.net.URLEncoder
|
||||
|
||||
/**
|
||||
* Append param and value to a Url, using "?" or "&". Value parameter will be encoded
|
||||
* Return this for chaining purpose
|
||||
*/
|
||||
fun StringBuilder.appendParamToUrl(param: String, value: String): StringBuilder {
|
||||
if (contains("?")) {
|
||||
append("&")
|
||||
} else {
|
||||
append("?")
|
||||
}
|
||||
|
||||
append(param)
|
||||
append("=")
|
||||
append(URLEncoder.encode(value, "utf-8"))
|
||||
|
||||
return this
|
||||
}
|
|
@ -14,25 +14,25 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.android.sdk.api.auth
|
||||
package org.matrix.android.sdk.internal.auth
|
||||
|
||||
/**
|
||||
* Path to use when the client does not supported any or all login flows
|
||||
* Ref: https://matrix.org/docs/spec/client_server/latest#login-fallback
|
||||
*/
|
||||
const val LOGIN_FALLBACK_PATH = "/_matrix/static/client/login/"
|
||||
internal const val LOGIN_FALLBACK_PATH = "/_matrix/static/client/login/"
|
||||
|
||||
/**
|
||||
* Path to use when the client does not supported any or all registration flows
|
||||
* Not documented
|
||||
*/
|
||||
const val REGISTER_FALLBACK_PATH = "/_matrix/static/client/register/"
|
||||
internal const val REGISTER_FALLBACK_PATH = "/_matrix/static/client/register/"
|
||||
|
||||
/**
|
||||
* Path to use when the client want to connect using SSO
|
||||
* Ref: https://matrix.org/docs/spec/client_server/latest#sso-client-login
|
||||
*/
|
||||
const val SSO_REDIRECT_PATH = "/_matrix/client/r0/login/sso/redirect"
|
||||
const val MSC2858_SSO_REDIRECT_PATH = "/_matrix/client/unstable/org.matrix.msc2858/login/sso/redirect"
|
||||
internal const val SSO_REDIRECT_PATH = "/_matrix/client/r0/login/sso/redirect"
|
||||
internal const val MSC2858_SSO_REDIRECT_PATH = "/_matrix/client/unstable/org.matrix.msc2858/login/sso/redirect"
|
||||
|
||||
const val SSO_REDIRECT_URL_PARAM = "redirectUrl"
|
||||
internal const val SSO_REDIRECT_URL_PARAM = "redirectUrl"
|
|
@ -34,6 +34,7 @@ import org.matrix.android.sdk.api.failure.Failure
|
|||
import org.matrix.android.sdk.api.session.Session
|
||||
import org.matrix.android.sdk.api.util.Cancelable
|
||||
import org.matrix.android.sdk.api.util.NoOpCancellable
|
||||
import org.matrix.android.sdk.api.util.appendParamToUrl
|
||||
import org.matrix.android.sdk.internal.SessionManager
|
||||
import org.matrix.android.sdk.internal.auth.data.LoginFlowResponse
|
||||
import org.matrix.android.sdk.internal.auth.data.RiotConfig
|
||||
|
@ -99,6 +100,52 @@ internal class DefaultAuthenticationService @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
override fun getSsoUrl(redirectUrl: String, deviceId: String?, providerId: String?): String? {
|
||||
val homeServerUrlBase = getHomeServerUrlBase() ?: return null
|
||||
|
||||
return buildString {
|
||||
append(homeServerUrlBase)
|
||||
if (providerId != null) {
|
||||
append(MSC2858_SSO_REDIRECT_PATH)
|
||||
append("/$providerId")
|
||||
} else {
|
||||
append(SSO_REDIRECT_PATH)
|
||||
}
|
||||
// Set the redirect url
|
||||
appendParamToUrl(SSO_REDIRECT_URL_PARAM, redirectUrl)
|
||||
deviceId?.takeIf { it.isNotBlank() }?.let {
|
||||
// But https://github.com/matrix-org/synapse/issues/5755
|
||||
appendParamToUrl("device_id", it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getFallbackUrl(forSignIn: Boolean, deviceId: String?): String? {
|
||||
val homeServerUrlBase = getHomeServerUrlBase() ?: return null
|
||||
|
||||
return buildString {
|
||||
append(homeServerUrlBase)
|
||||
if (forSignIn) {
|
||||
append(LOGIN_FALLBACK_PATH)
|
||||
deviceId?.takeIf { it.isNotBlank() }?.let {
|
||||
// But https://github.com/matrix-org/synapse/issues/5755
|
||||
appendParamToUrl("device_id", it)
|
||||
}
|
||||
} else {
|
||||
// For sign up
|
||||
append(REGISTER_FALLBACK_PATH)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getHomeServerUrlBase(): String? {
|
||||
return pendingSessionData
|
||||
?.homeServerConnectionConfig
|
||||
?.homeServerUri
|
||||
?.toString()
|
||||
?.trim { it == '/' }
|
||||
}
|
||||
|
||||
override fun getLoginFlow(homeServerConnectionConfig: HomeServerConnectionConfig, callback: MatrixCallback<LoginFlowResult>): Cancelable {
|
||||
pendingSessionData = null
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
package org.matrix.android.sdk.internal.auth.registration
|
||||
|
||||
import android.os.Parcelable
|
||||
import kotlinx.android.parcel.Parcelize
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
/**
|
||||
* This class represent a localized privacy policy for registration Flow.
|
||||
|
|
|
@ -19,7 +19,6 @@ package org.matrix.android.sdk.internal.crypto
|
|||
import android.content.Context
|
||||
import androidx.work.WorkerParameters
|
||||
import com.squareup.moshi.JsonClass
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.matrix.android.sdk.api.auth.data.Credentials
|
||||
import org.matrix.android.sdk.api.failure.shouldBeRetried
|
||||
import org.matrix.android.sdk.api.session.events.model.Event
|
||||
|
@ -60,7 +59,6 @@ internal class CancelGossipRequestWorker(context: Context,
|
|||
|
||||
@Inject lateinit var sendToDeviceTask: SendToDeviceTask
|
||||
@Inject lateinit var cryptoStore: IMXCryptoStore
|
||||
@Inject lateinit var eventBus: EventBus
|
||||
@Inject lateinit var credentials: Credentials
|
||||
|
||||
override fun injectWith(injector: SessionComponent) {
|
||||
|
|
|
@ -32,6 +32,7 @@ import org.matrix.android.sdk.internal.crypto.crosssigning.toBase64NoPadding
|
|||
import org.matrix.android.sdk.internal.crypto.keysbackup.util.extractCurveKeyFromRecoveryKey
|
||||
import org.matrix.android.sdk.internal.crypto.model.rest.GossipingDefaultContent
|
||||
import org.matrix.android.sdk.internal.crypto.model.rest.GossipingToDeviceObject
|
||||
import org.matrix.android.sdk.internal.crypto.model.rest.RoomKeyRequestBody
|
||||
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
|
||||
import org.matrix.android.sdk.internal.di.SessionId
|
||||
import org.matrix.android.sdk.internal.session.SessionScope
|
||||
|
@ -206,34 +207,7 @@ internal class IncomingGossipingRequestManager @Inject constructor(
|
|||
|
||||
Timber.v("## CRYPTO | GOSSIP processIncomingRoomKeyRequest from $userId:$deviceId for $roomId / ${body.sessionId} id ${request.requestId}")
|
||||
if (credentials.userId != userId) {
|
||||
Timber.w("## CRYPTO | GOSSIP processReceivedGossipingRequests() : room key request from other user")
|
||||
val senderKey = body.senderKey ?: return Unit
|
||||
.also { Timber.w("missing senderKey") }
|
||||
.also { cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED) }
|
||||
val sessionId = body.sessionId ?: return Unit
|
||||
.also { Timber.w("missing sessionId") }
|
||||
.also { cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED) }
|
||||
|
||||
if (alg != MXCRYPTO_ALGORITHM_MEGOLM) {
|
||||
return Unit
|
||||
.also { Timber.w("Only megolm is accepted here") }
|
||||
.also { cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED) }
|
||||
}
|
||||
|
||||
val roomEncryptor = roomEncryptorsStore.get(roomId) ?: return Unit
|
||||
.also { Timber.w("no room Encryptor") }
|
||||
.also { cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED) }
|
||||
|
||||
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
|
||||
val isSuccess = roomEncryptor.reshareKey(sessionId, userId, deviceId, senderKey)
|
||||
|
||||
if (isSuccess) {
|
||||
cryptoStore.updateGossipingRequestState(request, GossipingRequestState.ACCEPTED)
|
||||
} else {
|
||||
cryptoStore.updateGossipingRequestState(request, GossipingRequestState.UNABLE_TO_PROCESS)
|
||||
}
|
||||
}
|
||||
cryptoStore.updateGossipingRequestState(request, GossipingRequestState.RE_REQUESTED)
|
||||
handleKeyRequestFromOtherUser(body, request, alg, roomId, userId, deviceId)
|
||||
return
|
||||
}
|
||||
// TODO: should we queue up requests we don't yet have keys for, in case they turn up later?
|
||||
|
@ -291,6 +265,42 @@ internal class IncomingGossipingRequestManager @Inject constructor(
|
|||
onRoomKeyRequest(request)
|
||||
}
|
||||
|
||||
private fun handleKeyRequestFromOtherUser(body: RoomKeyRequestBody,
|
||||
request: IncomingRoomKeyRequest,
|
||||
alg: String,
|
||||
roomId: String,
|
||||
userId: String,
|
||||
deviceId: String) {
|
||||
Timber.w("## CRYPTO | GOSSIP processReceivedGossipingRequests() : room key request from other user")
|
||||
val senderKey = body.senderKey ?: return Unit
|
||||
.also { Timber.w("missing senderKey") }
|
||||
.also { cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED) }
|
||||
val sessionId = body.sessionId ?: return Unit
|
||||
.also { Timber.w("missing sessionId") }
|
||||
.also { cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED) }
|
||||
|
||||
if (alg != MXCRYPTO_ALGORITHM_MEGOLM) {
|
||||
return Unit
|
||||
.also { Timber.w("Only megolm is accepted here") }
|
||||
.also { cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED) }
|
||||
}
|
||||
|
||||
val roomEncryptor = roomEncryptorsStore.get(roomId) ?: return Unit
|
||||
.also { Timber.w("no room Encryptor") }
|
||||
.also { cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED) }
|
||||
|
||||
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
|
||||
val isSuccess = roomEncryptor.reshareKey(sessionId, userId, deviceId, senderKey)
|
||||
|
||||
if (isSuccess) {
|
||||
cryptoStore.updateGossipingRequestState(request, GossipingRequestState.ACCEPTED)
|
||||
} else {
|
||||
cryptoStore.updateGossipingRequestState(request, GossipingRequestState.UNABLE_TO_PROCESS)
|
||||
}
|
||||
}
|
||||
cryptoStore.updateGossipingRequestState(request, GossipingRequestState.RE_REQUESTED)
|
||||
}
|
||||
|
||||
private fun processIncomingSecretShareRequest(request: IncomingSecretShareRequest) {
|
||||
val secretName = request.secretName ?: return Unit.also {
|
||||
cryptoStore.updateGossipingRequestState(request, GossipingRequestState.REJECTED)
|
||||
|
|
|
@ -19,6 +19,8 @@ package org.matrix.android.sdk.internal.crypto
|
|||
import org.matrix.android.sdk.api.session.crypto.MXCryptoError
|
||||
import org.matrix.android.sdk.api.util.JSON_DICT_PARAMETERIZED_TYPE
|
||||
import org.matrix.android.sdk.api.util.JsonDict
|
||||
import org.matrix.android.sdk.internal.crypto.algorithms.megolm.MXOutboundSessionInfo
|
||||
import org.matrix.android.sdk.internal.crypto.algorithms.megolm.SharedWithHelper
|
||||
import org.matrix.android.sdk.internal.crypto.algorithms.olm.OlmDecryptionResult
|
||||
import org.matrix.android.sdk.internal.crypto.model.OlmInboundGroupSessionWrapper2
|
||||
import org.matrix.android.sdk.internal.crypto.model.OlmSessionWrapper
|
||||
|
@ -46,7 +48,7 @@ internal class MXOlmDevice @Inject constructor(
|
|||
*/
|
||||
private val store: IMXCryptoStore,
|
||||
private val inboundGroupSessionStore: InboundGroupSessionStore
|
||||
) {
|
||||
) {
|
||||
|
||||
/**
|
||||
* @return the Curve25519 key for the account.
|
||||
|
@ -63,11 +65,15 @@ internal class MXOlmDevice @Inject constructor(
|
|||
// The OLM lib utility instance.
|
||||
private var olmUtility: OlmUtility? = null
|
||||
|
||||
private data class GroupSessionCacheItem(
|
||||
val groupId: String,
|
||||
val groupSession: OlmOutboundGroupSession
|
||||
)
|
||||
|
||||
// The outbound group session.
|
||||
// They are not stored in 'store' to avoid to remember to which devices we sent the session key.
|
||||
// Plus, in cryptography, it is good to refresh sessions from time to time.
|
||||
// The key is the session id, the value the outbound group session.
|
||||
private val outboundGroupSessionStore: MutableMap<String, OlmOutboundGroupSession> = HashMap()
|
||||
// Caches active outbound session to avoid to sync with DB before read
|
||||
// The key is the session id, the value the <roomID,outbound group session>.
|
||||
private val outboundGroupSessionCache: MutableMap<String, GroupSessionCacheItem> = HashMap()
|
||||
|
||||
// Store a set of decrypted message indexes for each group session.
|
||||
// This partially mitigates a replay attack where a MITM resends a group
|
||||
|
@ -135,6 +141,10 @@ internal class MXOlmDevice @Inject constructor(
|
|||
*/
|
||||
fun release() {
|
||||
olmUtility?.releaseUtility()
|
||||
outboundGroupSessionCache.values.forEach {
|
||||
it.groupSession.releaseSession()
|
||||
}
|
||||
outboundGroupSessionCache.clear()
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -406,11 +416,12 @@ internal class MXOlmDevice @Inject constructor(
|
|||
*
|
||||
* @return the session id for the outbound session.
|
||||
*/
|
||||
fun createOutboundGroupSession(): String? {
|
||||
fun createOutboundGroupSessionForRoom(roomId: String): String? {
|
||||
var session: OlmOutboundGroupSession? = null
|
||||
try {
|
||||
session = OlmOutboundGroupSession()
|
||||
outboundGroupSessionStore[session.sessionIdentifier()] = session
|
||||
outboundGroupSessionCache[session.sessionIdentifier()] = GroupSessionCacheItem(roomId, session)
|
||||
store.storeCurrentOutboundGroupSessionForRoom(roomId, session)
|
||||
return session.sessionIdentifier()
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "createOutboundGroupSession")
|
||||
|
@ -421,6 +432,39 @@ internal class MXOlmDevice @Inject constructor(
|
|||
return null
|
||||
}
|
||||
|
||||
fun storeOutboundGroupSessionForRoom(roomId: String, sessionId: String) {
|
||||
outboundGroupSessionCache[sessionId]?.let {
|
||||
store.storeCurrentOutboundGroupSessionForRoom(roomId, it.groupSession)
|
||||
}
|
||||
}
|
||||
|
||||
fun restoreOutboundGroupSessionForRoom(roomId: String): MXOutboundSessionInfo? {
|
||||
val restoredOutboundGroupSession = store.getCurrentOutboundGroupSessionForRoom(roomId)
|
||||
if (restoredOutboundGroupSession != null) {
|
||||
val sessionId = restoredOutboundGroupSession.outboundGroupSession.sessionIdentifier()
|
||||
// cache it
|
||||
outboundGroupSessionCache[sessionId] = GroupSessionCacheItem(roomId, restoredOutboundGroupSession.outboundGroupSession)
|
||||
|
||||
return MXOutboundSessionInfo(
|
||||
sessionId = sessionId,
|
||||
sharedWithHelper = SharedWithHelper(roomId, sessionId, store),
|
||||
restoredOutboundGroupSession.creationTime
|
||||
)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
fun discardOutboundGroupSessionForRoom(roomId: String) {
|
||||
val toDiscard = outboundGroupSessionCache.filter {
|
||||
it.value.groupId == roomId
|
||||
}
|
||||
toDiscard.forEach { (sessionId, cacheItem) ->
|
||||
cacheItem.groupSession.releaseSession()
|
||||
outboundGroupSessionCache.remove(sessionId)
|
||||
}
|
||||
store.storeCurrentOutboundGroupSessionForRoom(roomId, null)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current session key of an outbound group session.
|
||||
*
|
||||
|
@ -430,7 +474,7 @@ internal class MXOlmDevice @Inject constructor(
|
|||
fun getSessionKey(sessionId: String): String? {
|
||||
if (sessionId.isNotEmpty()) {
|
||||
try {
|
||||
return outboundGroupSessionStore[sessionId]!!.sessionKey()
|
||||
return outboundGroupSessionCache[sessionId]!!.groupSession.sessionKey()
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "## getSessionKey() : failed")
|
||||
}
|
||||
|
@ -446,7 +490,7 @@ internal class MXOlmDevice @Inject constructor(
|
|||
*/
|
||||
fun getMessageIndex(sessionId: String): Int {
|
||||
return if (sessionId.isNotEmpty()) {
|
||||
outboundGroupSessionStore[sessionId]!!.messageIndex()
|
||||
outboundGroupSessionCache[sessionId]!!.groupSession.messageIndex()
|
||||
} else 0
|
||||
}
|
||||
|
||||
|
@ -460,7 +504,7 @@ internal class MXOlmDevice @Inject constructor(
|
|||
fun encryptGroupMessage(sessionId: String, payloadString: String): String? {
|
||||
if (sessionId.isNotEmpty() && payloadString.isNotEmpty()) {
|
||||
try {
|
||||
return outboundGroupSessionStore[sessionId]!!.encryptMessage(payloadString)
|
||||
return outboundGroupSessionCache[sessionId]!!.groupSession.encryptMessage(payloadString)
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "## encryptGroupMessage() : failed")
|
||||
}
|
||||
|
@ -747,7 +791,7 @@ internal class MXOlmDevice @Inject constructor(
|
|||
throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_SENDER_KEY, MXCryptoError.ERROR_MISSING_PROPERTY_REASON)
|
||||
}
|
||||
|
||||
val session = inboundGroupSessionStore.getInboundGroupSession(sessionId, senderKey)
|
||||
val session = inboundGroupSessionStore.getInboundGroupSession(sessionId, senderKey)
|
||||
|
||||
if (session != null) {
|
||||
// Check that the room id matches the original one for the session. This stops
|
||||
|
|
|
@ -19,7 +19,6 @@ package org.matrix.android.sdk.internal.crypto
|
|||
import android.content.Context
|
||||
import androidx.work.WorkerParameters
|
||||
import com.squareup.moshi.JsonClass
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.matrix.android.sdk.api.auth.data.Credentials
|
||||
import org.matrix.android.sdk.api.failure.shouldBeRetried
|
||||
import org.matrix.android.sdk.api.session.events.model.Event
|
||||
|
@ -52,7 +51,6 @@ internal class SendGossipRequestWorker(context: Context,
|
|||
|
||||
@Inject lateinit var sendToDeviceTask: SendToDeviceTask
|
||||
@Inject lateinit var cryptoStore: IMXCryptoStore
|
||||
@Inject lateinit var eventBus: EventBus
|
||||
@Inject lateinit var credentials: Credentials
|
||||
|
||||
override fun injectWith(injector: SessionComponent) {
|
||||
|
|
|
@ -19,7 +19,6 @@ package org.matrix.android.sdk.internal.crypto
|
|||
import android.content.Context
|
||||
import androidx.work.WorkerParameters
|
||||
import com.squareup.moshi.JsonClass
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.matrix.android.sdk.api.auth.data.Credentials
|
||||
import org.matrix.android.sdk.api.failure.shouldBeRetried
|
||||
import org.matrix.android.sdk.api.session.events.model.Event
|
||||
|
@ -54,7 +53,6 @@ internal class SendGossipWorker(context: Context,
|
|||
|
||||
@Inject lateinit var sendToDeviceTask: SendToDeviceTask
|
||||
@Inject lateinit var cryptoStore: IMXCryptoStore
|
||||
@Inject lateinit var eventBus: EventBus
|
||||
@Inject lateinit var credentials: Credentials
|
||||
@Inject lateinit var messageEncrypter: MessageEncrypter
|
||||
@Inject lateinit var ensureOlmSessionsForDevicesAction: EnsureOlmSessionsForDevicesAction
|
||||
|
|
|
@ -68,6 +68,10 @@ internal class MXMegolmEncryption(
|
|||
// case outboundSession.shareOperation will be non-null.)
|
||||
private var outboundSession: MXOutboundSessionInfo? = null
|
||||
|
||||
init {
|
||||
// restore existing outbound session if any
|
||||
outboundSession = olmDevice.restoreOutboundGroupSessionForRoom(roomId)
|
||||
}
|
||||
// Default rotation periods
|
||||
// TODO: Make it configurable via parameters
|
||||
// Session rotation periods
|
||||
|
@ -86,6 +90,9 @@ internal class MXMegolmEncryption(
|
|||
return encryptContent(outboundSession, eventType, eventContent)
|
||||
.also {
|
||||
notifyWithheldForSession(devices.withHeldDevices, outboundSession)
|
||||
// annoyingly we have to serialize again the saved outbound session to store message index :/
|
||||
// if not we would see duplicate message index errors
|
||||
olmDevice.storeOutboundGroupSessionForRoom(roomId, outboundSession.sessionId)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -107,6 +114,7 @@ internal class MXMegolmEncryption(
|
|||
|
||||
override fun discardSessionKey() {
|
||||
outboundSession = null
|
||||
olmDevice.discardOutboundGroupSessionForRoom(roomId)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -116,7 +124,7 @@ internal class MXMegolmEncryption(
|
|||
*/
|
||||
private fun prepareNewSessionInRoom(): MXOutboundSessionInfo {
|
||||
Timber.v("## CRYPTO | prepareNewSessionInRoom() ")
|
||||
val sessionId = olmDevice.createOutboundGroupSession()
|
||||
val sessionId = olmDevice.createOutboundGroupSessionForRoom(roomId)
|
||||
|
||||
val keysClaimedMap = HashMap<String, String>()
|
||||
keysClaimedMap["ed25519"] = olmDevice.deviceEd25519Key!!
|
||||
|
@ -152,7 +160,7 @@ internal class MXMegolmEncryption(
|
|||
val deviceIds = devicesInRoom.getUserDeviceIds(userId)
|
||||
for (deviceId in deviceIds!!) {
|
||||
val deviceInfo = devicesInRoom.getObject(userId, deviceId)
|
||||
if (deviceInfo != null && !cryptoStore.wasSessionSharedWithUser(roomId, safeSession.sessionId, userId, deviceId).found) {
|
||||
if (deviceInfo != null && !cryptoStore.getSharedSessionInfo(roomId, safeSession.sessionId, userId, deviceId).found) {
|
||||
val devices = shareMap.getOrPut(userId) { ArrayList() }
|
||||
devices.add(deviceInfo)
|
||||
}
|
||||
|
@ -401,11 +409,18 @@ internal class MXMegolmEncryption(
|
|||
.also { Timber.w("## Crypto reshareKey: Device not found") }
|
||||
|
||||
// Get the chain index of the key we previously sent this device
|
||||
val chainIndex = outboundSession?.sharedWithHelper?.wasSharedWith(userId, deviceId) ?: return false
|
||||
val wasSessionSharedWithUser = cryptoStore.getSharedSessionInfo(roomId, sessionId, userId, deviceId)
|
||||
if (!wasSessionSharedWithUser.found) {
|
||||
// This session was never shared with this user
|
||||
// Send a room key with held
|
||||
notifyKeyWithHeld(listOf(UserDevice(userId, deviceId)), sessionId, senderKey, WithHeldCode.UNAUTHORISED)
|
||||
Timber.w("## Crypto reshareKey: ERROR : Never shared megolm with this device")
|
||||
return false
|
||||
}
|
||||
// if found chain index should not be null
|
||||
val chainIndex = wasSessionSharedWithUser.chainIndex ?: return false
|
||||
.also {
|
||||
// Send a room key with held
|
||||
notifyKeyWithHeld(listOf(UserDevice(userId, deviceId)), sessionId, senderKey, WithHeldCode.UNAUTHORISED)
|
||||
Timber.w("## Crypto reshareKey: ERROR : Never share megolm with this device")
|
||||
Timber.w("## Crypto reshareKey: Null chain index")
|
||||
}
|
||||
|
||||
val devicesByUser = mapOf(userId to listOf(deviceInfo))
|
||||
|
|
|
@ -23,9 +23,9 @@ import timber.log.Timber
|
|||
internal class MXOutboundSessionInfo(
|
||||
// The id of the session
|
||||
val sessionId: String,
|
||||
val sharedWithHelper: SharedWithHelper) {
|
||||
// When the session was created
|
||||
private val creationTime = System.currentTimeMillis()
|
||||
val sharedWithHelper: SharedWithHelper,
|
||||
// When the session was created
|
||||
private val creationTime: Long = System.currentTimeMillis()) {
|
||||
|
||||
// Number of times this session has been used
|
||||
var useCount: Int = 0
|
||||
|
|
|
@ -28,10 +28,6 @@ internal class SharedWithHelper(
|
|||
return cryptoStore.getSharedWithInfo(roomId, sessionId)
|
||||
}
|
||||
|
||||
fun wasSharedWith(userId: String, deviceId: String): Int? {
|
||||
return cryptoStore.wasSessionSharedWithUser(roomId, sessionId, userId, deviceId).chainIndex
|
||||
}
|
||||
|
||||
fun markedSessionAsShared(userId: String, deviceId: String, chainIndex: Int) {
|
||||
cryptoStore.markedSessionAsShared(roomId, sessionId, userId, deviceId, chainIndex)
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ package org.matrix.android.sdk.internal.crypto.attachments
|
|||
|
||||
import android.os.Parcelable
|
||||
import org.matrix.android.sdk.internal.crypto.model.rest.EncryptedFileInfo
|
||||
import kotlinx.android.parcel.Parcelize
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
fun EncryptedFileInfo.toElementToDecrypt(): ElementToDecrypt? {
|
||||
// Check the validity of some fields
|
||||
|
|
|
@ -19,20 +19,20 @@ package org.matrix.android.sdk.internal.crypto.keysbackup.tasks
|
|||
import org.matrix.android.sdk.internal.crypto.keysbackup.api.RoomKeysApi
|
||||
import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.CreateKeysBackupVersionBody
|
||||
import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysVersion
|
||||
import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
|
||||
import org.matrix.android.sdk.internal.network.executeRequest
|
||||
import org.matrix.android.sdk.internal.task.Task
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import javax.inject.Inject
|
||||
|
||||
internal interface CreateKeysBackupVersionTask : Task<CreateKeysBackupVersionBody, KeysVersion>
|
||||
|
||||
internal class DefaultCreateKeysBackupVersionTask @Inject constructor(
|
||||
private val roomKeysApi: RoomKeysApi,
|
||||
private val eventBus: EventBus
|
||||
private val globalErrorReceiver: GlobalErrorReceiver
|
||||
) : CreateKeysBackupVersionTask {
|
||||
|
||||
override suspend fun execute(params: CreateKeysBackupVersionBody): KeysVersion {
|
||||
return executeRequest(eventBus) {
|
||||
return executeRequest(globalErrorReceiver) {
|
||||
apiCall = roomKeysApi.createKeysBackupVersion(params)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,9 +17,9 @@
|
|||
package org.matrix.android.sdk.internal.crypto.keysbackup.tasks
|
||||
|
||||
import org.matrix.android.sdk.internal.crypto.keysbackup.api.RoomKeysApi
|
||||
import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
|
||||
import org.matrix.android.sdk.internal.network.executeRequest
|
||||
import org.matrix.android.sdk.internal.task.Task
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import javax.inject.Inject
|
||||
|
||||
internal interface DeleteBackupTask : Task<DeleteBackupTask.Params, Unit> {
|
||||
|
@ -30,11 +30,11 @@ internal interface DeleteBackupTask : Task<DeleteBackupTask.Params, Unit> {
|
|||
|
||||
internal class DefaultDeleteBackupTask @Inject constructor(
|
||||
private val roomKeysApi: RoomKeysApi,
|
||||
private val eventBus: EventBus
|
||||
private val globalErrorReceiver: GlobalErrorReceiver
|
||||
) : DeleteBackupTask {
|
||||
|
||||
override suspend fun execute(params: DeleteBackupTask.Params) {
|
||||
return executeRequest(eventBus) {
|
||||
return executeRequest(globalErrorReceiver) {
|
||||
apiCall = roomKeysApi.deleteBackup(params.version)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,9 +17,9 @@
|
|||
package org.matrix.android.sdk.internal.crypto.keysbackup.tasks
|
||||
|
||||
import org.matrix.android.sdk.internal.crypto.keysbackup.api.RoomKeysApi
|
||||
import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
|
||||
import org.matrix.android.sdk.internal.network.executeRequest
|
||||
import org.matrix.android.sdk.internal.task.Task
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import javax.inject.Inject
|
||||
|
||||
internal interface DeleteRoomSessionDataTask : Task<DeleteRoomSessionDataTask.Params, Unit> {
|
||||
|
@ -32,11 +32,11 @@ internal interface DeleteRoomSessionDataTask : Task<DeleteRoomSessionDataTask.Pa
|
|||
|
||||
internal class DefaultDeleteRoomSessionDataTask @Inject constructor(
|
||||
private val roomKeysApi: RoomKeysApi,
|
||||
private val eventBus: EventBus
|
||||
private val globalErrorReceiver: GlobalErrorReceiver
|
||||
) : DeleteRoomSessionDataTask {
|
||||
|
||||
override suspend fun execute(params: DeleteRoomSessionDataTask.Params) {
|
||||
return executeRequest(eventBus) {
|
||||
return executeRequest(globalErrorReceiver) {
|
||||
apiCall = roomKeysApi.deleteRoomSessionData(
|
||||
params.roomId,
|
||||
params.sessionId,
|
||||
|
|
|
@ -17,9 +17,9 @@
|
|||
package org.matrix.android.sdk.internal.crypto.keysbackup.tasks
|
||||
|
||||
import org.matrix.android.sdk.internal.crypto.keysbackup.api.RoomKeysApi
|
||||
import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
|
||||
import org.matrix.android.sdk.internal.network.executeRequest
|
||||
import org.matrix.android.sdk.internal.task.Task
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import javax.inject.Inject
|
||||
|
||||
internal interface DeleteRoomSessionsDataTask : Task<DeleteRoomSessionsDataTask.Params, Unit> {
|
||||
|
@ -31,11 +31,11 @@ internal interface DeleteRoomSessionsDataTask : Task<DeleteRoomSessionsDataTask.
|
|||
|
||||
internal class DefaultDeleteRoomSessionsDataTask @Inject constructor(
|
||||
private val roomKeysApi: RoomKeysApi,
|
||||
private val eventBus: EventBus
|
||||
private val globalErrorReceiver: GlobalErrorReceiver
|
||||
) : DeleteRoomSessionsDataTask {
|
||||
|
||||
override suspend fun execute(params: DeleteRoomSessionsDataTask.Params) {
|
||||
return executeRequest(eventBus) {
|
||||
return executeRequest(globalErrorReceiver) {
|
||||
apiCall = roomKeysApi.deleteRoomSessionsData(
|
||||
params.roomId,
|
||||
params.version)
|
||||
|
|
|
@ -17,9 +17,9 @@
|
|||
package org.matrix.android.sdk.internal.crypto.keysbackup.tasks
|
||||
|
||||
import org.matrix.android.sdk.internal.crypto.keysbackup.api.RoomKeysApi
|
||||
import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
|
||||
import org.matrix.android.sdk.internal.network.executeRequest
|
||||
import org.matrix.android.sdk.internal.task.Task
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import javax.inject.Inject
|
||||
|
||||
internal interface DeleteSessionsDataTask : Task<DeleteSessionsDataTask.Params, Unit> {
|
||||
|
@ -30,11 +30,11 @@ internal interface DeleteSessionsDataTask : Task<DeleteSessionsDataTask.Params,
|
|||
|
||||
internal class DefaultDeleteSessionsDataTask @Inject constructor(
|
||||
private val roomKeysApi: RoomKeysApi,
|
||||
private val eventBus: EventBus
|
||||
private val globalErrorReceiver: GlobalErrorReceiver
|
||||
) : DeleteSessionsDataTask {
|
||||
|
||||
override suspend fun execute(params: DeleteSessionsDataTask.Params) {
|
||||
return executeRequest(eventBus) {
|
||||
return executeRequest(globalErrorReceiver) {
|
||||
apiCall = roomKeysApi.deleteSessionsData(params.version)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,20 +18,20 @@ package org.matrix.android.sdk.internal.crypto.keysbackup.tasks
|
|||
|
||||
import org.matrix.android.sdk.internal.crypto.keysbackup.api.RoomKeysApi
|
||||
import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysVersionResult
|
||||
import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
|
||||
import org.matrix.android.sdk.internal.network.executeRequest
|
||||
import org.matrix.android.sdk.internal.task.Task
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import javax.inject.Inject
|
||||
|
||||
internal interface GetKeysBackupLastVersionTask : Task<Unit, KeysVersionResult>
|
||||
|
||||
internal class DefaultGetKeysBackupLastVersionTask @Inject constructor(
|
||||
private val roomKeysApi: RoomKeysApi,
|
||||
private val eventBus: EventBus
|
||||
private val globalErrorReceiver: GlobalErrorReceiver
|
||||
) : GetKeysBackupLastVersionTask {
|
||||
|
||||
override suspend fun execute(params: Unit): KeysVersionResult {
|
||||
return executeRequest(eventBus) {
|
||||
return executeRequest(globalErrorReceiver) {
|
||||
apiCall = roomKeysApi.getKeysBackupLastVersion()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,20 +18,20 @@ package org.matrix.android.sdk.internal.crypto.keysbackup.tasks
|
|||
|
||||
import org.matrix.android.sdk.internal.crypto.keysbackup.api.RoomKeysApi
|
||||
import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysVersionResult
|
||||
import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
|
||||
import org.matrix.android.sdk.internal.network.executeRequest
|
||||
import org.matrix.android.sdk.internal.task.Task
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import javax.inject.Inject
|
||||
|
||||
internal interface GetKeysBackupVersionTask : Task<String, KeysVersionResult>
|
||||
|
||||
internal class DefaultGetKeysBackupVersionTask @Inject constructor(
|
||||
private val roomKeysApi: RoomKeysApi,
|
||||
private val eventBus: EventBus
|
||||
private val globalErrorReceiver: GlobalErrorReceiver
|
||||
) : GetKeysBackupVersionTask {
|
||||
|
||||
override suspend fun execute(params: String): KeysVersionResult {
|
||||
return executeRequest(eventBus) {
|
||||
return executeRequest(globalErrorReceiver) {
|
||||
apiCall = roomKeysApi.getKeysBackupVersion(params)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,9 +18,9 @@ package org.matrix.android.sdk.internal.crypto.keysbackup.tasks
|
|||
|
||||
import org.matrix.android.sdk.internal.crypto.keysbackup.api.RoomKeysApi
|
||||
import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeyBackupData
|
||||
import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
|
||||
import org.matrix.android.sdk.internal.network.executeRequest
|
||||
import org.matrix.android.sdk.internal.task.Task
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import javax.inject.Inject
|
||||
|
||||
internal interface GetRoomSessionDataTask : Task<GetRoomSessionDataTask.Params, KeyBackupData> {
|
||||
|
@ -33,11 +33,11 @@ internal interface GetRoomSessionDataTask : Task<GetRoomSessionDataTask.Params,
|
|||
|
||||
internal class DefaultGetRoomSessionDataTask @Inject constructor(
|
||||
private val roomKeysApi: RoomKeysApi,
|
||||
private val eventBus: EventBus
|
||||
private val globalErrorReceiver: GlobalErrorReceiver
|
||||
) : GetRoomSessionDataTask {
|
||||
|
||||
override suspend fun execute(params: GetRoomSessionDataTask.Params): KeyBackupData {
|
||||
return executeRequest(eventBus) {
|
||||
return executeRequest(globalErrorReceiver) {
|
||||
apiCall = roomKeysApi.getRoomSessionData(
|
||||
params.roomId,
|
||||
params.sessionId,
|
||||
|
|
|
@ -18,9 +18,9 @@ package org.matrix.android.sdk.internal.crypto.keysbackup.tasks
|
|||
|
||||
import org.matrix.android.sdk.internal.crypto.keysbackup.api.RoomKeysApi
|
||||
import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.RoomKeysBackupData
|
||||
import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
|
||||
import org.matrix.android.sdk.internal.network.executeRequest
|
||||
import org.matrix.android.sdk.internal.task.Task
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import javax.inject.Inject
|
||||
|
||||
internal interface GetRoomSessionsDataTask : Task<GetRoomSessionsDataTask.Params, RoomKeysBackupData> {
|
||||
|
@ -32,11 +32,11 @@ internal interface GetRoomSessionsDataTask : Task<GetRoomSessionsDataTask.Params
|
|||
|
||||
internal class DefaultGetRoomSessionsDataTask @Inject constructor(
|
||||
private val roomKeysApi: RoomKeysApi,
|
||||
private val eventBus: EventBus
|
||||
private val globalErrorReceiver: GlobalErrorReceiver
|
||||
) : GetRoomSessionsDataTask {
|
||||
|
||||
override suspend fun execute(params: GetRoomSessionsDataTask.Params): RoomKeysBackupData {
|
||||
return executeRequest(eventBus) {
|
||||
return executeRequest(globalErrorReceiver) {
|
||||
apiCall = roomKeysApi.getRoomSessionsData(
|
||||
params.roomId,
|
||||
params.version)
|
||||
|
|
|
@ -18,9 +18,9 @@ package org.matrix.android.sdk.internal.crypto.keysbackup.tasks
|
|||
|
||||
import org.matrix.android.sdk.internal.crypto.keysbackup.api.RoomKeysApi
|
||||
import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysBackupData
|
||||
import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
|
||||
import org.matrix.android.sdk.internal.network.executeRequest
|
||||
import org.matrix.android.sdk.internal.task.Task
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import javax.inject.Inject
|
||||
|
||||
internal interface GetSessionsDataTask : Task<GetSessionsDataTask.Params, KeysBackupData> {
|
||||
|
@ -31,11 +31,11 @@ internal interface GetSessionsDataTask : Task<GetSessionsDataTask.Params, KeysBa
|
|||
|
||||
internal class DefaultGetSessionsDataTask @Inject constructor(
|
||||
private val roomKeysApi: RoomKeysApi,
|
||||
private val eventBus: EventBus
|
||||
private val globalErrorReceiver: GlobalErrorReceiver
|
||||
) : GetSessionsDataTask {
|
||||
|
||||
override suspend fun execute(params: GetSessionsDataTask.Params): KeysBackupData {
|
||||
return executeRequest(eventBus) {
|
||||
return executeRequest(globalErrorReceiver) {
|
||||
apiCall = roomKeysApi.getSessionsData(params.version)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,9 +19,9 @@ package org.matrix.android.sdk.internal.crypto.keysbackup.tasks
|
|||
import org.matrix.android.sdk.internal.crypto.keysbackup.api.RoomKeysApi
|
||||
import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.BackupKeysResult
|
||||
import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeyBackupData
|
||||
import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
|
||||
import org.matrix.android.sdk.internal.network.executeRequest
|
||||
import org.matrix.android.sdk.internal.task.Task
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import javax.inject.Inject
|
||||
|
||||
internal interface StoreRoomSessionDataTask : Task<StoreRoomSessionDataTask.Params, BackupKeysResult> {
|
||||
|
@ -35,11 +35,11 @@ internal interface StoreRoomSessionDataTask : Task<StoreRoomSessionDataTask.Para
|
|||
|
||||
internal class DefaultStoreRoomSessionDataTask @Inject constructor(
|
||||
private val roomKeysApi: RoomKeysApi,
|
||||
private val eventBus: EventBus
|
||||
private val globalErrorReceiver: GlobalErrorReceiver
|
||||
) : StoreRoomSessionDataTask {
|
||||
|
||||
override suspend fun execute(params: StoreRoomSessionDataTask.Params): BackupKeysResult {
|
||||
return executeRequest(eventBus) {
|
||||
return executeRequest(globalErrorReceiver) {
|
||||
apiCall = roomKeysApi.storeRoomSessionData(
|
||||
params.roomId,
|
||||
params.sessionId,
|
||||
|
|
|
@ -19,9 +19,9 @@ package org.matrix.android.sdk.internal.crypto.keysbackup.tasks
|
|||
import org.matrix.android.sdk.internal.crypto.keysbackup.api.RoomKeysApi
|
||||
import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.BackupKeysResult
|
||||
import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.RoomKeysBackupData
|
||||
import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
|
||||
import org.matrix.android.sdk.internal.network.executeRequest
|
||||
import org.matrix.android.sdk.internal.task.Task
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import javax.inject.Inject
|
||||
|
||||
internal interface StoreRoomSessionsDataTask : Task<StoreRoomSessionsDataTask.Params, BackupKeysResult> {
|
||||
|
@ -34,11 +34,11 @@ internal interface StoreRoomSessionsDataTask : Task<StoreRoomSessionsDataTask.Pa
|
|||
|
||||
internal class DefaultStoreRoomSessionsDataTask @Inject constructor(
|
||||
private val roomKeysApi: RoomKeysApi,
|
||||
private val eventBus: EventBus
|
||||
private val globalErrorReceiver: GlobalErrorReceiver
|
||||
) : StoreRoomSessionsDataTask {
|
||||
|
||||
override suspend fun execute(params: StoreRoomSessionsDataTask.Params): BackupKeysResult {
|
||||
return executeRequest(eventBus) {
|
||||
return executeRequest(globalErrorReceiver) {
|
||||
apiCall = roomKeysApi.storeRoomSessionsData(
|
||||
params.roomId,
|
||||
params.version,
|
||||
|
|
|
@ -19,9 +19,9 @@ package org.matrix.android.sdk.internal.crypto.keysbackup.tasks
|
|||
import org.matrix.android.sdk.internal.crypto.keysbackup.api.RoomKeysApi
|
||||
import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.BackupKeysResult
|
||||
import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysBackupData
|
||||
import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
|
||||
import org.matrix.android.sdk.internal.network.executeRequest
|
||||
import org.matrix.android.sdk.internal.task.Task
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import javax.inject.Inject
|
||||
|
||||
internal interface StoreSessionsDataTask : Task<StoreSessionsDataTask.Params, BackupKeysResult> {
|
||||
|
@ -33,11 +33,11 @@ internal interface StoreSessionsDataTask : Task<StoreSessionsDataTask.Params, Ba
|
|||
|
||||
internal class DefaultStoreSessionsDataTask @Inject constructor(
|
||||
private val roomKeysApi: RoomKeysApi,
|
||||
private val eventBus: EventBus
|
||||
private val globalErrorReceiver: GlobalErrorReceiver
|
||||
) : StoreSessionsDataTask {
|
||||
|
||||
override suspend fun execute(params: StoreSessionsDataTask.Params): BackupKeysResult {
|
||||
return executeRequest(eventBus) {
|
||||
return executeRequest(globalErrorReceiver) {
|
||||
apiCall = roomKeysApi.storeSessionsData(
|
||||
params.version,
|
||||
params.keysBackupData)
|
||||
|
|
|
@ -18,9 +18,9 @@ package org.matrix.android.sdk.internal.crypto.keysbackup.tasks
|
|||
|
||||
import org.matrix.android.sdk.internal.crypto.keysbackup.api.RoomKeysApi
|
||||
import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.UpdateKeysBackupVersionBody
|
||||
import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
|
||||
import org.matrix.android.sdk.internal.network.executeRequest
|
||||
import org.matrix.android.sdk.internal.task.Task
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import javax.inject.Inject
|
||||
|
||||
internal interface UpdateKeysBackupVersionTask : Task<UpdateKeysBackupVersionTask.Params, Unit> {
|
||||
|
@ -32,11 +32,11 @@ internal interface UpdateKeysBackupVersionTask : Task<UpdateKeysBackupVersionTas
|
|||
|
||||
internal class DefaultUpdateKeysBackupVersionTask @Inject constructor(
|
||||
private val roomKeysApi: RoomKeysApi,
|
||||
private val eventBus: EventBus
|
||||
private val globalErrorReceiver: GlobalErrorReceiver
|
||||
) : UpdateKeysBackupVersionTask {
|
||||
|
||||
override suspend fun execute(params: UpdateKeysBackupVersionTask.Params) {
|
||||
return executeRequest(eventBus) {
|
||||
return executeRequest(globalErrorReceiver) {
|
||||
apiCall = roomKeysApi.updateKeysBackupVersion(params.version, params.keysBackupVersionBody)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.android.sdk.internal.crypto.model
|
||||
|
||||
import org.matrix.olm.OlmOutboundGroupSession
|
||||
|
||||
data class OutboundGroupSessionWrapper(
|
||||
val outboundGroupSession: OlmOutboundGroupSession,
|
||||
val creationTime: Long
|
||||
)
|
|
@ -33,11 +33,13 @@ import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo
|
|||
import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap
|
||||
import org.matrix.android.sdk.internal.crypto.model.OlmInboundGroupSessionWrapper2
|
||||
import org.matrix.android.sdk.internal.crypto.model.OlmSessionWrapper
|
||||
import org.matrix.android.sdk.internal.crypto.model.OutboundGroupSessionWrapper
|
||||
import org.matrix.android.sdk.internal.crypto.model.event.RoomKeyWithHeldContent
|
||||
import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo
|
||||
import org.matrix.android.sdk.internal.crypto.model.rest.RoomKeyRequestBody
|
||||
import org.matrix.android.sdk.internal.crypto.store.db.model.KeysBackupDataEntity
|
||||
import org.matrix.olm.OlmAccount
|
||||
import org.matrix.olm.OlmOutboundGroupSession
|
||||
|
||||
/**
|
||||
* the crypto data store
|
||||
|
@ -293,6 +295,16 @@ internal interface IMXCryptoStore {
|
|||
*/
|
||||
fun getInboundGroupSession(sessionId: String, senderKey: String): OlmInboundGroupSessionWrapper2?
|
||||
|
||||
/**
|
||||
* Get the current outbound group session for this encrypted room
|
||||
*/
|
||||
fun getCurrentOutboundGroupSessionForRoom(roomId: String): OutboundGroupSessionWrapper?
|
||||
|
||||
/**
|
||||
* Get the current outbound group session for this encrypted room
|
||||
*/
|
||||
fun storeCurrentOutboundGroupSessionForRoom(roomId: String, outboundGroupSession: OlmOutboundGroupSession?)
|
||||
|
||||
/**
|
||||
* Remove an inbound group session
|
||||
*
|
||||
|
@ -439,7 +451,15 @@ internal interface IMXCryptoStore {
|
|||
fun getWithHeldMegolmSession(roomId: String, sessionId: String): RoomKeyWithHeldContent?
|
||||
|
||||
fun markedSessionAsShared(roomId: String?, sessionId: String, userId: String, deviceId: String, chainIndex: Int)
|
||||
fun wasSessionSharedWithUser(roomId: String?, sessionId: String, userId: String, deviceId: String): SharedSessionResult
|
||||
|
||||
/**
|
||||
* Query for information on this session sharing history.
|
||||
* @return SharedSessionResult
|
||||
* if found is true then this session was initialy shared with that user|device,
|
||||
* in this case chainIndex is not nullindicates the ratchet position.
|
||||
* In found is false, chainIndex is null
|
||||
*/
|
||||
fun getSharedSessionInfo(roomId: String?, sessionId: String, userId: String, deviceId: String): SharedSessionResult
|
||||
data class SharedSessionResult(val found: Boolean, val chainIndex: Int?)
|
||||
|
||||
fun getSharedWithInfo(roomId: String?, sessionId: String): MXUsersDevicesMap<Int>
|
||||
|
|
|
@ -47,6 +47,7 @@ import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo
|
|||
import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap
|
||||
import org.matrix.android.sdk.internal.crypto.model.OlmInboundGroupSessionWrapper2
|
||||
import org.matrix.android.sdk.internal.crypto.model.OlmSessionWrapper
|
||||
import org.matrix.android.sdk.internal.crypto.model.OutboundGroupSessionWrapper
|
||||
import org.matrix.android.sdk.internal.crypto.model.event.RoomKeyWithHeldContent
|
||||
import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo
|
||||
import org.matrix.android.sdk.internal.crypto.model.rest.RoomKeyRequestBody
|
||||
|
@ -73,6 +74,7 @@ import org.matrix.android.sdk.internal.crypto.store.db.model.OlmInboundGroupSess
|
|||
import org.matrix.android.sdk.internal.crypto.store.db.model.OlmInboundGroupSessionEntityFields
|
||||
import org.matrix.android.sdk.internal.crypto.store.db.model.OlmSessionEntity
|
||||
import org.matrix.android.sdk.internal.crypto.store.db.model.OlmSessionEntityFields
|
||||
import org.matrix.android.sdk.internal.crypto.store.db.model.OutboundGroupSessionInfoEntity
|
||||
import org.matrix.android.sdk.internal.crypto.store.db.model.OutgoingGossipingRequestEntity
|
||||
import org.matrix.android.sdk.internal.crypto.store.db.model.OutgoingGossipingRequestEntityFields
|
||||
import org.matrix.android.sdk.internal.crypto.store.db.model.SharedSessionEntity
|
||||
|
@ -95,6 +97,7 @@ import org.matrix.android.sdk.internal.di.UserId
|
|||
import org.matrix.android.sdk.internal.session.SessionScope
|
||||
import org.matrix.olm.OlmAccount
|
||||
import org.matrix.olm.OlmException
|
||||
import org.matrix.olm.OlmOutboundGroupSession
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
import kotlin.collections.set
|
||||
|
@ -756,6 +759,42 @@ internal class RealmCryptoStore @Inject constructor(
|
|||
return inboundGroupSessionToRelease[key]
|
||||
}
|
||||
|
||||
override fun getCurrentOutboundGroupSessionForRoom(roomId: String): OutboundGroupSessionWrapper? {
|
||||
return doWithRealm(realmConfiguration) { realm ->
|
||||
realm.where<CryptoRoomEntity>()
|
||||
.equalTo(CryptoRoomEntityFields.ROOM_ID, roomId)
|
||||
.findFirst()?.outboundSessionInfo?.let { entity ->
|
||||
entity.getOutboundGroupSession()?.let {
|
||||
OutboundGroupSessionWrapper(
|
||||
it,
|
||||
entity.creationTime ?: 0
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun storeCurrentOutboundGroupSessionForRoom(roomId: String, outboundGroupSession: OlmOutboundGroupSession?) {
|
||||
// we can do this async, as it's just for restoring on next launch
|
||||
// the olmdevice is caching the active instance
|
||||
// this is called for each sent message (so not high frequency), thus we can use basic realm async without
|
||||
// risk of reaching max async operation limit?
|
||||
doRealmTransactionAsync(realmConfiguration) { realm ->
|
||||
CryptoRoomEntity.getById(realm, roomId)?.let { entity ->
|
||||
// we should delete existing outbound session info if any
|
||||
entity.outboundSessionInfo?.deleteFromRealm()
|
||||
|
||||
if (outboundGroupSession != null) {
|
||||
val info = realm.createObject(OutboundGroupSessionInfoEntity::class.java).apply {
|
||||
creationTime = System.currentTimeMillis()
|
||||
putOutboundGroupSession(outboundGroupSession)
|
||||
}
|
||||
entity.outboundSessionInfo = info
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Note: the result will be only use to export all the keys and not to use the OlmInboundGroupSessionWrapper2,
|
||||
* so there is no need to use or update `inboundGroupSessionToRelease` for native memory management
|
||||
|
@ -1645,7 +1684,7 @@ internal class RealmCryptoStore @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
override fun wasSessionSharedWithUser(roomId: String?, sessionId: String, userId: String, deviceId: String): IMXCryptoStore.SharedSessionResult {
|
||||
override fun getSharedSessionInfo(roomId: String?, sessionId: String, userId: String, deviceId: String): IMXCryptoStore.SharedSessionResult {
|
||||
return doWithRealm(realmConfiguration) { realm ->
|
||||
SharedSessionEntity.get(realm, roomId, sessionId, userId, deviceId)?.let {
|
||||
IMXCryptoStore.SharedSessionResult(true, it.chainIndex)
|
||||
|
|
|
@ -44,6 +44,7 @@ import org.matrix.android.sdk.internal.di.SerializeNulls
|
|||
import io.realm.DynamicRealm
|
||||
import io.realm.RealmMigration
|
||||
import io.realm.RealmObjectSchema
|
||||
import org.matrix.android.sdk.internal.crypto.store.db.model.OutboundGroupSessionInfoEntityFields
|
||||
import org.matrix.androidsdk.crypto.data.MXOlmInboundGroupSession2
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
@ -55,7 +56,7 @@ internal class RealmCryptoStoreMigration @Inject constructor(private val crossSi
|
|||
// 0, 1, 2: legacy Riot-Android
|
||||
// 3: migrate to RiotX schema
|
||||
// 4, 5, 6, 7, 8, 9: migrations from RiotX (which was previously 1, 2, 3, 4, 5, 6)
|
||||
const val CRYPTO_STORE_SCHEMA_VERSION = 11L
|
||||
const val CRYPTO_STORE_SCHEMA_VERSION = 12L
|
||||
}
|
||||
|
||||
private fun RealmObjectSchema.addFieldIfNotExists(fieldName: String, fieldType: Class<*>): RealmObjectSchema {
|
||||
|
@ -93,6 +94,7 @@ internal class RealmCryptoStoreMigration @Inject constructor(private val crossSi
|
|||
if (oldVersion <= 8) migrateTo9(realm)
|
||||
if (oldVersion <= 9) migrateTo10(realm)
|
||||
if (oldVersion <= 10) migrateTo11(realm)
|
||||
if (oldVersion <= 11) migrateTo12(realm)
|
||||
}
|
||||
|
||||
private fun migrateTo1Legacy(realm: DynamicRealm) {
|
||||
|
@ -489,4 +491,16 @@ internal class RealmCryptoStoreMigration @Inject constructor(private val crossSi
|
|||
realm.schema.get("CryptoMetadataEntity")
|
||||
?.addField(CryptoMetadataEntityFields.DEVICE_KEYS_SENT_TO_SERVER, Boolean::class.java)
|
||||
}
|
||||
|
||||
// Version 12L added outbound group session persistence
|
||||
private fun migrateTo12(realm: DynamicRealm) {
|
||||
Timber.d("Step 11 -> 12")
|
||||
val outboundEntitySchema = realm.schema.create("OutboundGroupSessionInfoEntity")
|
||||
.addField(OutboundGroupSessionInfoEntityFields.SERIALIZED_OUTBOUND_SESSION_DATA, String::class.java)
|
||||
.addField(OutboundGroupSessionInfoEntityFields.CREATION_TIME, Long::class.java)
|
||||
.setNullable(OutboundGroupSessionInfoEntityFields.CREATION_TIME, true)
|
||||
|
||||
realm.schema.get("CryptoRoomEntity")
|
||||
?.addRealmObjectField(CryptoRoomEntityFields.OUTBOUND_SESSION_INFO.`$`, outboundEntitySchema)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,6 +33,7 @@ import org.matrix.android.sdk.internal.crypto.store.db.model.TrustLevelEntity
|
|||
import org.matrix.android.sdk.internal.crypto.store.db.model.UserEntity
|
||||
import org.matrix.android.sdk.internal.crypto.store.db.model.WithHeldSessionEntity
|
||||
import io.realm.annotations.RealmModule
|
||||
import org.matrix.android.sdk.internal.crypto.store.db.model.OutboundGroupSessionInfoEntity
|
||||
|
||||
/**
|
||||
* Realm module for Crypto store classes
|
||||
|
@ -54,6 +55,7 @@ import io.realm.annotations.RealmModule
|
|||
OutgoingGossipingRequestEntity::class,
|
||||
MyDeviceLastSeenInfoEntity::class,
|
||||
WithHeldSessionEntity::class,
|
||||
SharedSessionEntity::class
|
||||
SharedSessionEntity::class,
|
||||
OutboundGroupSessionInfoEntity::class
|
||||
])
|
||||
internal class RealmCryptoStoreModule
|
||||
|
|
|
@ -23,7 +23,12 @@ internal open class CryptoRoomEntity(
|
|||
@PrimaryKey var roomId: String? = null,
|
||||
var algorithm: String? = null,
|
||||
var shouldEncryptForInvitedMembers: Boolean? = null,
|
||||
var blacklistUnverifiedDevices: Boolean = false)
|
||||
var blacklistUnverifiedDevices: Boolean = false,
|
||||
// Store the current outbound session for this room,
|
||||
// to avoid re-create and re-share at each startup (if rotation not needed..)
|
||||
// This is specific to megolm but not sure how to model it better
|
||||
var outboundSessionInfo: OutboundGroupSessionInfoEntity? = null
|
||||
)
|
||||
: RealmObject() {
|
||||
|
||||
companion object
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.matrix.android.sdk.internal.crypto.store.db.model
|
||||
|
||||
import io.realm.RealmObject
|
||||
import org.matrix.android.sdk.internal.crypto.store.db.deserializeFromRealm
|
||||
import org.matrix.android.sdk.internal.crypto.store.db.serializeForRealm
|
||||
import org.matrix.olm.OlmOutboundGroupSession
|
||||
import timber.log.Timber
|
||||
|
||||
internal open class OutboundGroupSessionInfoEntity(
|
||||
var serializedOutboundSessionData: String? = null,
|
||||
var creationTime: Long? = null
|
||||
) : RealmObject() {
|
||||
|
||||
fun getOutboundGroupSession(): OlmOutboundGroupSession? {
|
||||
return try {
|
||||
deserializeFromRealm(serializedOutboundSessionData)
|
||||
} catch (failure: Throwable) {
|
||||
Timber.e(failure, "## getOutboundGroupSession() Deserialization failure")
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
fun putOutboundGroupSession(olmOutboundGroupSession: OlmOutboundGroupSession?) {
|
||||
serializedOutboundSessionData = serializeForRealm(olmOutboundGroupSession)
|
||||
}
|
||||
|
||||
companion object
|
||||
}
|
|
@ -21,9 +21,9 @@ import org.matrix.android.sdk.internal.crypto.model.MXKey
|
|||
import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap
|
||||
import org.matrix.android.sdk.internal.crypto.model.rest.KeysClaimBody
|
||||
import org.matrix.android.sdk.internal.crypto.model.rest.KeysClaimResponse
|
||||
import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
|
||||
import org.matrix.android.sdk.internal.network.executeRequest
|
||||
import org.matrix.android.sdk.internal.task.Task
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
|
@ -36,13 +36,13 @@ internal interface ClaimOneTimeKeysForUsersDeviceTask : Task<ClaimOneTimeKeysFor
|
|||
|
||||
internal class DefaultClaimOneTimeKeysForUsersDevice @Inject constructor(
|
||||
private val cryptoApi: CryptoApi,
|
||||
private val eventBus: EventBus
|
||||
private val globalErrorReceiver: GlobalErrorReceiver
|
||||
) : ClaimOneTimeKeysForUsersDeviceTask {
|
||||
|
||||
override suspend fun execute(params: ClaimOneTimeKeysForUsersDeviceTask.Params): MXUsersDevicesMap<MXKey> {
|
||||
val body = KeysClaimBody(oneTimeKeys = params.usersDevicesKeyTypesMap.map)
|
||||
|
||||
val keysClaimResponse = executeRequest<KeysClaimResponse>(eventBus) {
|
||||
val keysClaimResponse = executeRequest<KeysClaimResponse>(globalErrorReceiver) {
|
||||
apiCall = cryptoApi.claimOneTimeKeysForUsersDevices(body)
|
||||
}
|
||||
val map = MXUsersDevicesMap<MXKey>()
|
||||
|
|
|
@ -20,9 +20,9 @@ import org.matrix.android.sdk.api.failure.Failure
|
|||
import org.matrix.android.sdk.api.failure.toRegistrationFlowResponse
|
||||
import org.matrix.android.sdk.internal.crypto.api.CryptoApi
|
||||
import org.matrix.android.sdk.internal.crypto.model.rest.DeleteDeviceParams
|
||||
import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
|
||||
import org.matrix.android.sdk.internal.network.executeRequest
|
||||
import org.matrix.android.sdk.internal.task.Task
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import javax.inject.Inject
|
||||
|
||||
internal interface DeleteDeviceTask : Task<DeleteDeviceTask.Params, Unit> {
|
||||
|
@ -33,12 +33,12 @@ internal interface DeleteDeviceTask : Task<DeleteDeviceTask.Params, Unit> {
|
|||
|
||||
internal class DefaultDeleteDeviceTask @Inject constructor(
|
||||
private val cryptoApi: CryptoApi,
|
||||
private val eventBus: EventBus
|
||||
private val globalErrorReceiver: GlobalErrorReceiver
|
||||
) : DeleteDeviceTask {
|
||||
|
||||
override suspend fun execute(params: DeleteDeviceTask.Params) {
|
||||
try {
|
||||
executeRequest<Unit>(eventBus) {
|
||||
executeRequest<Unit>(globalErrorReceiver) {
|
||||
apiCall = cryptoApi.deleteDevice(params.deviceId, DeleteDeviceParams())
|
||||
}
|
||||
} catch (throwable: Throwable) {
|
||||
|
|
|
@ -21,9 +21,9 @@ import org.matrix.android.sdk.internal.crypto.api.CryptoApi
|
|||
import org.matrix.android.sdk.internal.crypto.model.rest.DeleteDeviceParams
|
||||
import org.matrix.android.sdk.internal.crypto.model.rest.UserPasswordAuth
|
||||
import org.matrix.android.sdk.internal.di.UserId
|
||||
import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
|
||||
import org.matrix.android.sdk.internal.network.executeRequest
|
||||
import org.matrix.android.sdk.internal.task.Task
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import javax.inject.Inject
|
||||
|
||||
internal interface DeleteDeviceWithUserPasswordTask : Task<DeleteDeviceWithUserPasswordTask.Params, Unit> {
|
||||
|
@ -37,11 +37,11 @@ internal interface DeleteDeviceWithUserPasswordTask : Task<DeleteDeviceWithUserP
|
|||
internal class DefaultDeleteDeviceWithUserPasswordTask @Inject constructor(
|
||||
private val cryptoApi: CryptoApi,
|
||||
@UserId private val userId: String,
|
||||
private val eventBus: EventBus
|
||||
private val globalErrorReceiver: GlobalErrorReceiver
|
||||
) : DeleteDeviceWithUserPasswordTask {
|
||||
|
||||
override suspend fun execute(params: DeleteDeviceWithUserPasswordTask.Params) {
|
||||
return executeRequest(eventBus) {
|
||||
return executeRequest(globalErrorReceiver) {
|
||||
apiCall = cryptoApi.deleteDevice(params.deviceId,
|
||||
DeleteDeviceParams(
|
||||
userPasswordAuth = UserPasswordAuth(
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue