Merge branch 'develop' into feature/fga/voip_v1_start
This commit is contained in:
commit
14288b545b
|
@ -24,6 +24,8 @@
|
||||||
<w>pbkdf</w>
|
<w>pbkdf</w>
|
||||||
<w>pids</w>
|
<w>pids</w>
|
||||||
<w>pkcs</w>
|
<w>pkcs</w>
|
||||||
|
<w>previewable</w>
|
||||||
|
<w>previewables</w>
|
||||||
<w>riotx</w>
|
<w>riotx</w>
|
||||||
<w>signin</w>
|
<w>signin</w>
|
||||||
<w>signout</w>
|
<w>signout</w>
|
||||||
|
|
61
CHANGES.md
61
CHANGES.md
|
@ -1,17 +1,14 @@
|
||||||
Changes in Element 1.0.12 (2020-XX-XX)
|
Changes in Element 1.0.14 (2020-XX-XX)
|
||||||
===================================================
|
===================================================
|
||||||
|
|
||||||
Features ✨:
|
Features ✨:
|
||||||
- Add room aliases management, and room directory visibility management in a dedicated screen (#1579, #2428)
|
- Enable url previews for notices (#2562)
|
||||||
- Room setting: update join rules and guest access (#2442)
|
|
||||||
|
|
||||||
Improvements 🙌:
|
Improvements 🙌:
|
||||||
- Add Setting Item to Change PIN (#2462)
|
-
|
||||||
- Improve room history visibility setting UX (#1579)
|
|
||||||
|
|
||||||
Bugfix 🐛:
|
Bugfix 🐛:
|
||||||
- Double bottomsheet effect after verify with passphrase
|
- Url previews sometimes attached to wrong message (#2561)
|
||||||
- EditText cursor jumps to the start while typing fast (#2469)
|
|
||||||
|
|
||||||
Translations 🗣:
|
Translations 🗣:
|
||||||
-
|
-
|
||||||
|
@ -20,14 +17,60 @@ SDK API changes ⚠️:
|
||||||
-
|
-
|
||||||
|
|
||||||
Build 🧱:
|
Build 🧱:
|
||||||
- Upgrade some dependencies and Kotlin version
|
-
|
||||||
- Use fragment-ktx and preference-ktx dependencies (fix lint issue KtxExtensionAvailable)
|
|
||||||
|
|
||||||
Test:
|
Test:
|
||||||
-
|
-
|
||||||
|
|
||||||
|
Other changes:
|
||||||
|
- Migrate to ViewBindings (#1072)
|
||||||
|
|
||||||
|
Changes in Element 1.0.13 (2020-12-18)
|
||||||
|
===================================================
|
||||||
|
|
||||||
|
Bugfix 🐛:
|
||||||
|
- Fix MSC2858 implementation details (#2540)
|
||||||
|
|
||||||
|
Changes in Element 1.0.12 (2020-12-15)
|
||||||
|
===================================================
|
||||||
|
|
||||||
|
Features ✨:
|
||||||
|
- Add room aliases management, and room directory visibility management in a dedicated screen (#1579, #2428)
|
||||||
|
- Room setting: update join rules and guest access (#2442)
|
||||||
|
- Url preview (#481)
|
||||||
|
- Store encrypted file in cache and cleanup decrypted file at each app start (#2512)
|
||||||
|
- Emoji Keyboard (#2520)
|
||||||
|
- Social login (#2452)
|
||||||
|
- Support for chat effects in timeline (confetti, snow) (#2535)
|
||||||
|
|
||||||
|
Improvements 🙌:
|
||||||
|
- Add Setting Item to Change PIN (#2462)
|
||||||
|
- Improve room history visibility setting UX (#1579)
|
||||||
|
- Matrix.to deeplink custom scheme support
|
||||||
|
- Homeserver history (#1933)
|
||||||
|
|
||||||
|
Bugfix 🐛:
|
||||||
|
- Fix cancellation of sending event (#2438)
|
||||||
|
- Double bottomsheet effect after verify with passphrase
|
||||||
|
- EditText cursor jumps to the start while typing fast (#2469)
|
||||||
|
- UTD for events before invitation if member state events are hidden (#2486)
|
||||||
|
- No known servers error is given when joining rooms on new Gitter bridge (#2516)
|
||||||
|
- Show preview when sending attachment from the keyboard (#2440)
|
||||||
|
- Do not compress GIFs (#1616, #1254)
|
||||||
|
|
||||||
|
SDK API changes ⚠️:
|
||||||
|
- StateService now exposes suspendable function instead of using MatrixCallback.
|
||||||
|
- RawCacheStrategy has been moved and renamed to CacheStrategy
|
||||||
|
- FileService: remove useless FileService.DownloadMode
|
||||||
|
|
||||||
|
Build 🧱:
|
||||||
|
- Upgrade some dependencies and Kotlin version
|
||||||
|
- Use fragment-ktx and preference-ktx dependencies (fix lint issue KtxExtensionAvailable)
|
||||||
|
- Upgrade Realm dependency to 10.1.2
|
||||||
|
|
||||||
Other changes:
|
Other changes:
|
||||||
- Remove "Status.im" theme #2424
|
- Remove "Status.im" theme #2424
|
||||||
|
- Log HTTP requests and responses in production (level BASIC, i.e. without any private data)
|
||||||
|
|
||||||
Changes in Element 1.0.11 (2020-11-27)
|
Changes in Element 1.0.11 (2020-11-27)
|
||||||
===================================================
|
===================================================
|
||||||
|
|
|
@ -16,7 +16,6 @@
|
||||||
|
|
||||||
apply plugin: 'com.android.library'
|
apply plugin: 'com.android.library'
|
||||||
apply plugin: 'kotlin-android'
|
apply plugin: 'kotlin-android'
|
||||||
apply plugin: 'kotlin-android-extensions'
|
|
||||||
|
|
||||||
buildscript {
|
buildscript {
|
||||||
repositories {
|
repositories {
|
||||||
|
@ -55,6 +54,10 @@ android {
|
||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
jvmTarget = '1.8'
|
jvmTarget = '1.8'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
buildFeatures {
|
||||||
|
viewBinding true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
|
|
@ -17,19 +17,17 @@
|
||||||
package im.vector.lib.attachmentviewer
|
package im.vector.lib.attachmentviewer
|
||||||
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.ImageView
|
import im.vector.lib.attachmentviewer.databinding.ItemAnimatedImageAttachmentBinding
|
||||||
import android.widget.ProgressBar
|
|
||||||
|
|
||||||
class AnimatedImageViewHolder constructor(itemView: View) :
|
class AnimatedImageViewHolder constructor(itemView: View) :
|
||||||
BaseViewHolder(itemView) {
|
BaseViewHolder(itemView) {
|
||||||
|
|
||||||
val touchImageView: ImageView = itemView.findViewById(R.id.imageView)
|
val views = ItemAnimatedImageAttachmentBinding.bind(itemView)
|
||||||
val imageLoaderProgress: ProgressBar = itemView.findViewById(R.id.imageLoaderProgress)
|
|
||||||
|
|
||||||
internal val target = DefaultImageLoaderTarget(this, this.touchImageView)
|
internal val target = DefaultImageLoaderTarget(this, views.imageView)
|
||||||
|
|
||||||
override fun onRecycled() {
|
override fun onRecycled() {
|
||||||
super.onRecycled()
|
super.onRecycled()
|
||||||
touchImageView.setImageDrawable(null)
|
views.imageView.setImageDrawable(null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,7 +33,8 @@ import androidx.core.view.isVisible
|
||||||
import androidx.core.view.updatePadding
|
import androidx.core.view.updatePadding
|
||||||
import androidx.transition.TransitionManager
|
import androidx.transition.TransitionManager
|
||||||
import androidx.viewpager2.widget.ViewPager2
|
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 java.lang.ref.WeakReference
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
|
|
||||||
|
@ -50,12 +51,14 @@ abstract class AttachmentViewerActivity : AppCompatActivity(), AttachmentEventLi
|
||||||
private var overlayView: View? = null
|
private var overlayView: View? = null
|
||||||
set(value) {
|
set(value) {
|
||||||
if (value == overlayView) return
|
if (value == overlayView) return
|
||||||
overlayView?.let { rootContainer.removeView(it) }
|
overlayView?.let { views.rootContainer.removeView(it) }
|
||||||
rootContainer.addView(value)
|
views.rootContainer.addView(value)
|
||||||
value?.updatePadding(top = topInset, bottom = bottomInset)
|
value?.updatePadding(top = topInset, bottom = bottomInset)
|
||||||
field = value
|
field = value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private lateinit var views: ActivityAttachmentViewerBinding
|
||||||
|
|
||||||
private lateinit var swipeDismissHandler: SwipeToDismissHandler
|
private lateinit var swipeDismissHandler: SwipeToDismissHandler
|
||||||
private lateinit var directionDetector: SwipeDirectionDetector
|
private lateinit var directionDetector: SwipeDirectionDetector
|
||||||
private lateinit var scaleDetector: ScaleGestureDetector
|
private lateinit var scaleDetector: ScaleGestureDetector
|
||||||
|
@ -95,17 +98,17 @@ abstract class AttachmentViewerActivity : AppCompatActivity(), AttachmentEventLi
|
||||||
window.setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS, WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
|
window.setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS, WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
|
||||||
window.setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION, WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION)
|
window.setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION, WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION)
|
||||||
|
|
||||||
setContentView(R.layout.activity_attachment_viewer)
|
views = ActivityAttachmentViewerBinding.inflate(layoutInflater)
|
||||||
attachmentPager.orientation = ViewPager2.ORIENTATION_HORIZONTAL
|
setContentView(views.root)
|
||||||
|
views.attachmentPager.orientation = ViewPager2.ORIENTATION_HORIZONTAL
|
||||||
attachmentsAdapter = AttachmentsAdapter()
|
attachmentsAdapter = AttachmentsAdapter()
|
||||||
attachmentPager.adapter = attachmentsAdapter
|
views.attachmentPager.adapter = attachmentsAdapter
|
||||||
imageTransitionView = transitionImageView
|
imageTransitionView = views.transitionImageView
|
||||||
transitionImageContainer = findViewById(R.id.transitionImageContainer)
|
pager2 = views.attachmentPager
|
||||||
pager2 = attachmentPager
|
|
||||||
directionDetector = createSwipeDirectionDetector()
|
directionDetector = createSwipeDirectionDetector()
|
||||||
gestureDetector = createGestureDetector()
|
gestureDetector = createGestureDetector()
|
||||||
|
|
||||||
attachmentPager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
|
views.attachmentPager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
|
||||||
override fun onPageScrollStateChanged(state: Int) {
|
override fun onPageScrollStateChanged(state: Int) {
|
||||||
isImagePagerIdle = state == ViewPager2.SCROLL_STATE_IDLE
|
isImagePagerIdle = state == ViewPager2.SCROLL_STATE_IDLE
|
||||||
}
|
}
|
||||||
|
@ -116,12 +119,12 @@ abstract class AttachmentViewerActivity : AppCompatActivity(), AttachmentEventLi
|
||||||
})
|
})
|
||||||
|
|
||||||
swipeDismissHandler = createSwipeToDismissHandler()
|
swipeDismissHandler = createSwipeToDismissHandler()
|
||||||
rootContainer.setOnTouchListener(swipeDismissHandler)
|
views.rootContainer.setOnTouchListener(swipeDismissHandler)
|
||||||
rootContainer.viewTreeObserver.addOnGlobalLayoutListener { swipeDismissHandler.translationLimit = dismissContainer.height / 4 }
|
views.rootContainer.viewTreeObserver.addOnGlobalLayoutListener { swipeDismissHandler.translationLimit = views.dismissContainer.height / 4 }
|
||||||
|
|
||||||
scaleDetector = createScaleGestureDetector()
|
scaleDetector = createScaleGestureDetector()
|
||||||
|
|
||||||
ViewCompat.setOnApplyWindowInsetsListener(rootContainer) { _, insets ->
|
ViewCompat.setOnApplyWindowInsetsListener(views.rootContainer) { _, insets ->
|
||||||
overlayView?.updatePadding(top = insets.systemWindowInsetTop, bottom = insets.systemWindowInsetBottom)
|
overlayView?.updatePadding(top = insets.systemWindowInsetTop, bottom = insets.systemWindowInsetBottom)
|
||||||
topInset = insets.systemWindowInsetTop
|
topInset = insets.systemWindowInsetTop
|
||||||
bottomInset = insets.systemWindowInsetBottom
|
bottomInset = insets.systemWindowInsetBottom
|
||||||
|
@ -170,7 +173,7 @@ abstract class AttachmentViewerActivity : AppCompatActivity(), AttachmentEventLi
|
||||||
if (swipeDirection == null && (scaleDetector.isInProgress || ev.pointerCount > 1 || wasScaled)) {
|
if (swipeDirection == null && (scaleDetector.isInProgress || ev.pointerCount > 1 || wasScaled)) {
|
||||||
wasScaled = true
|
wasScaled = true
|
||||||
// Log.v("ATTACHEMENTS", "dispatch to pager")
|
// Log.v("ATTACHEMENTS", "dispatch to pager")
|
||||||
return attachmentPager.dispatchTouchEvent(ev)
|
return views.attachmentPager.dispatchTouchEvent(ev)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Log.v("ATTACHEMENTS", "is current item scaled ${isScaled()}")
|
// Log.v("ATTACHEMENTS", "is current item scaled ${isScaled()}")
|
||||||
|
@ -196,16 +199,16 @@ abstract class AttachmentViewerActivity : AppCompatActivity(), AttachmentEventLi
|
||||||
private fun handleEventActionDown(event: MotionEvent) {
|
private fun handleEventActionDown(event: MotionEvent) {
|
||||||
swipeDirection = null
|
swipeDirection = null
|
||||||
wasScaled = false
|
wasScaled = false
|
||||||
attachmentPager.dispatchTouchEvent(event)
|
views.attachmentPager.dispatchTouchEvent(event)
|
||||||
|
|
||||||
swipeDismissHandler.onTouch(rootContainer, event)
|
swipeDismissHandler.onTouch(views.rootContainer, event)
|
||||||
isOverlayWasClicked = dispatchOverlayTouch(event)
|
isOverlayWasClicked = dispatchOverlayTouch(event)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleEventActionUp(event: MotionEvent) {
|
private fun handleEventActionUp(event: MotionEvent) {
|
||||||
// wasDoubleTapped = false
|
// wasDoubleTapped = false
|
||||||
swipeDismissHandler.onTouch(rootContainer, event)
|
swipeDismissHandler.onTouch(views.rootContainer, event)
|
||||||
attachmentPager.dispatchTouchEvent(event)
|
views.attachmentPager.dispatchTouchEvent(event)
|
||||||
isOverlayWasClicked = dispatchOverlayTouch(event)
|
isOverlayWasClicked = dispatchOverlayTouch(event)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -220,12 +223,12 @@ abstract class AttachmentViewerActivity : AppCompatActivity(), AttachmentEventLi
|
||||||
private fun toggleOverlayViewVisibility() {
|
private fun toggleOverlayViewVisibility() {
|
||||||
if (systemUiVisibility) {
|
if (systemUiVisibility) {
|
||||||
// we hide
|
// we hide
|
||||||
TransitionManager.beginDelayedTransition(rootContainer)
|
TransitionManager.beginDelayedTransition(views.rootContainer)
|
||||||
hideSystemUI()
|
hideSystemUI()
|
||||||
overlayView?.isVisible = false
|
overlayView?.isVisible = false
|
||||||
} else {
|
} else {
|
||||||
// we show
|
// we show
|
||||||
TransitionManager.beginDelayedTransition(rootContainer)
|
TransitionManager.beginDelayedTransition(views.rootContainer)
|
||||||
showSystemUI()
|
showSystemUI()
|
||||||
overlayView?.isVisible = true
|
overlayView?.isVisible = true
|
||||||
}
|
}
|
||||||
|
@ -238,11 +241,11 @@ abstract class AttachmentViewerActivity : AppCompatActivity(), AttachmentEventLi
|
||||||
return when (swipeDirection) {
|
return when (swipeDirection) {
|
||||||
SwipeDirection.Up, SwipeDirection.Down -> {
|
SwipeDirection.Up, SwipeDirection.Down -> {
|
||||||
if (isSwipeToDismissAllowed && !wasScaled && isImagePagerIdle) {
|
if (isSwipeToDismissAllowed && !wasScaled && isImagePagerIdle) {
|
||||||
swipeDismissHandler.onTouch(rootContainer, event)
|
swipeDismissHandler.onTouch(views.rootContainer, event)
|
||||||
} else true
|
} else true
|
||||||
}
|
}
|
||||||
SwipeDirection.Left, SwipeDirection.Right -> {
|
SwipeDirection.Left, SwipeDirection.Right -> {
|
||||||
attachmentPager.dispatchTouchEvent(event)
|
views.attachmentPager.dispatchTouchEvent(event)
|
||||||
}
|
}
|
||||||
else -> true
|
else -> true
|
||||||
}
|
}
|
||||||
|
@ -250,8 +253,8 @@ abstract class AttachmentViewerActivity : AppCompatActivity(), AttachmentEventLi
|
||||||
|
|
||||||
private fun handleSwipeViewMove(translationY: Float, translationLimit: Int) {
|
private fun handleSwipeViewMove(translationY: Float, translationLimit: Int) {
|
||||||
val alpha = calculateTranslationAlpha(translationY, translationLimit)
|
val alpha = calculateTranslationAlpha(translationY, translationLimit)
|
||||||
backgroundView.alpha = alpha
|
views.backgroundView.alpha = alpha
|
||||||
dismissContainer.alpha = alpha
|
views.dismissContainer.alpha = alpha
|
||||||
overlayView?.alpha = alpha
|
overlayView?.alpha = alpha
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -265,7 +268,7 @@ abstract class AttachmentViewerActivity : AppCompatActivity(), AttachmentEventLi
|
||||||
|
|
||||||
private fun createSwipeToDismissHandler()
|
private fun createSwipeToDismissHandler()
|
||||||
: SwipeToDismissHandler = SwipeToDismissHandler(
|
: SwipeToDismissHandler = SwipeToDismissHandler(
|
||||||
swipeView = dismissContainer,
|
swipeView = views.dismissContainer,
|
||||||
shouldAnimateDismiss = { shouldAnimateDismiss() },
|
shouldAnimateDismiss = { shouldAnimateDismiss() },
|
||||||
onDismiss = { animateClose() },
|
onDismiss = { animateClose() },
|
||||||
onSwipeViewMove = ::handleSwipeViewMove)
|
onSwipeViewMove = ::handleSwipeViewMove)
|
||||||
|
|
|
@ -98,7 +98,7 @@ class AttachmentsAdapter : RecyclerView.Adapter<BaseViewHolder>() {
|
||||||
fun isScaled(position: Int): Boolean {
|
fun isScaled(position: Int): Boolean {
|
||||||
val holder = recyclerView?.findViewHolderForAdapterPosition(position)
|
val holder = recyclerView?.findViewHolderForAdapterPosition(position)
|
||||||
if (holder is ZoomableImageViewHolder) {
|
if (holder is ZoomableImageViewHolder) {
|
||||||
return holder.touchImageView.attacher.scale > 1f
|
return holder.views.touchImageView.attacher.scale > 1f
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,29 +44,29 @@ internal class DefaultImageLoaderTarget(val holder: AnimatedImageViewHolder, pri
|
||||||
|
|
||||||
override fun onResourceLoading(uid: String, placeholder: Drawable?) {
|
override fun onResourceLoading(uid: String, placeholder: Drawable?) {
|
||||||
if (holder.boundResourceUid != uid) return
|
if (holder.boundResourceUid != uid) return
|
||||||
holder.imageLoaderProgress.isVisible = true
|
holder.views.imageLoaderProgress.isVisible = true
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onLoadFailed(uid: String, errorDrawable: Drawable?) {
|
override fun onLoadFailed(uid: String, errorDrawable: Drawable?) {
|
||||||
if (holder.boundResourceUid != uid) return
|
if (holder.boundResourceUid != uid) return
|
||||||
holder.imageLoaderProgress.isVisible = false
|
holder.views.imageLoaderProgress.isVisible = false
|
||||||
holder.touchImageView.setImageDrawable(errorDrawable)
|
holder.views.imageView.setImageDrawable(errorDrawable)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onResourceCleared(uid: String, placeholder: Drawable?) {
|
override fun onResourceCleared(uid: String, placeholder: Drawable?) {
|
||||||
if (holder.boundResourceUid != uid) return
|
if (holder.boundResourceUid != uid) return
|
||||||
holder.touchImageView.setImageDrawable(placeholder)
|
holder.views.imageView.setImageDrawable(placeholder)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onResourceReady(uid: String, resource: Drawable) {
|
override fun onResourceReady(uid: String, resource: Drawable) {
|
||||||
if (holder.boundResourceUid != uid) return
|
if (holder.boundResourceUid != uid) return
|
||||||
holder.imageLoaderProgress.isVisible = false
|
holder.views.imageLoaderProgress.isVisible = false
|
||||||
// Glide mess up the view size :/
|
// Glide mess up the view size :/
|
||||||
holder.touchImageView.updateLayoutParams {
|
holder.views.imageView.updateLayoutParams {
|
||||||
width = LinearLayout.LayoutParams.MATCH_PARENT
|
width = LinearLayout.LayoutParams.MATCH_PARENT
|
||||||
height = LinearLayout.LayoutParams.MATCH_PARENT
|
height = LinearLayout.LayoutParams.MATCH_PARENT
|
||||||
}
|
}
|
||||||
holder.touchImageView.setImageDrawable(resource)
|
holder.views.imageView.setImageDrawable(resource)
|
||||||
if (resource is Animatable) {
|
if (resource is Animatable) {
|
||||||
resource.start()
|
resource.start()
|
||||||
}
|
}
|
||||||
|
@ -77,30 +77,30 @@ internal class DefaultImageLoaderTarget(val holder: AnimatedImageViewHolder, pri
|
||||||
|
|
||||||
override fun onResourceLoading(uid: String, placeholder: Drawable?) {
|
override fun onResourceLoading(uid: String, placeholder: Drawable?) {
|
||||||
if (holder.boundResourceUid != uid) return
|
if (holder.boundResourceUid != uid) return
|
||||||
holder.imageLoaderProgress.isVisible = true
|
holder.views.imageLoaderProgress.isVisible = true
|
||||||
holder.touchImageView.setImageDrawable(placeholder)
|
holder.views.touchImageView.setImageDrawable(placeholder)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onLoadFailed(uid: String, errorDrawable: Drawable?) {
|
override fun onLoadFailed(uid: String, errorDrawable: Drawable?) {
|
||||||
if (holder.boundResourceUid != uid) return
|
if (holder.boundResourceUid != uid) return
|
||||||
holder.imageLoaderProgress.isVisible = false
|
holder.views.imageLoaderProgress.isVisible = false
|
||||||
holder.touchImageView.setImageDrawable(errorDrawable)
|
holder.views.touchImageView.setImageDrawable(errorDrawable)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onResourceCleared(uid: String, placeholder: Drawable?) {
|
override fun onResourceCleared(uid: String, placeholder: Drawable?) {
|
||||||
if (holder.boundResourceUid != uid) return
|
if (holder.boundResourceUid != uid) return
|
||||||
holder.touchImageView.setImageDrawable(placeholder)
|
holder.views.touchImageView.setImageDrawable(placeholder)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onResourceReady(uid: String, resource: Drawable) {
|
override fun onResourceReady(uid: String, resource: Drawable) {
|
||||||
if (holder.boundResourceUid != uid) return
|
if (holder.boundResourceUid != uid) return
|
||||||
holder.imageLoaderProgress.isVisible = false
|
holder.views.imageLoaderProgress.isVisible = false
|
||||||
// Glide mess up the view size :/
|
// Glide mess up the view size :/
|
||||||
holder.touchImageView.updateLayoutParams {
|
holder.views.touchImageView.updateLayoutParams {
|
||||||
width = LinearLayout.LayoutParams.MATCH_PARENT
|
width = LinearLayout.LayoutParams.MATCH_PARENT
|
||||||
height = 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?) {
|
override fun onThumbnailResourceCleared(uid: String, placeholder: Drawable?) {
|
||||||
if (holder.boundResourceUid != uid) return
|
if (holder.boundResourceUid != uid) return
|
||||||
holder.thumbnailImage.setImageDrawable(placeholder)
|
holder.views.videoThumbnailImage.setImageDrawable(placeholder)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onThumbnailResourceReady(uid: String, resource: Drawable) {
|
override fun onThumbnailResourceReady(uid: String, resource: Drawable) {
|
||||||
if (holder.boundResourceUid != uid) return
|
if (holder.boundResourceUid != uid) return
|
||||||
holder.thumbnailImage.setImageDrawable(resource)
|
holder.views.videoThumbnailImage.setImageDrawable(resource)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onVideoFileLoading(uid: String) {
|
override fun onVideoFileLoading(uid: String) {
|
||||||
if (holder.boundResourceUid != uid) return
|
if (holder.boundResourceUid != uid) return
|
||||||
holder.thumbnailImage.isVisible = true
|
holder.views.videoThumbnailImage.isVisible = true
|
||||||
holder.loaderProgressBar.isVisible = true
|
holder.views.videoLoaderProgress.isVisible = true
|
||||||
holder.videoView.isVisible = false
|
holder.views.videoView.isVisible = false
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onVideoFileLoadFailed(uid: String) {
|
override fun onVideoFileLoadFailed(uid: String) {
|
||||||
|
@ -82,8 +82,8 @@ internal class DefaultVideoLoaderTarget(val holder: VideoViewHolder, private val
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun arrangeForVideoReady() {
|
private fun arrangeForVideoReady() {
|
||||||
holder.thumbnailImage.isVisible = false
|
holder.views.videoThumbnailImage.isVisible = false
|
||||||
holder.loaderProgressBar.isVisible = false
|
holder.views.videoLoaderProgress.isVisible = false
|
||||||
holder.videoView.isVisible = true
|
holder.views.videoView.isVisible = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,11 +18,8 @@ package im.vector.lib.attachmentviewer
|
||||||
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.View
|
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 androidx.core.view.isVisible
|
||||||
|
import im.vector.lib.attachmentviewer.databinding.ItemVideoAttachmentBinding
|
||||||
import io.reactivex.Observable
|
import io.reactivex.Observable
|
||||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||||
import io.reactivex.disposables.Disposable
|
import io.reactivex.disposables.Disposable
|
||||||
|
@ -44,13 +41,9 @@ class VideoViewHolder constructor(itemView: View) :
|
||||||
|
|
||||||
var eventListener: WeakReference<AttachmentEventListener>? = null
|
var eventListener: WeakReference<AttachmentEventListener>? = null
|
||||||
|
|
||||||
val thumbnailImage: ImageView = itemView.findViewById(R.id.videoThumbnailImage)
|
val views = ItemVideoAttachmentBinding.bind(itemView)
|
||||||
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)
|
|
||||||
|
|
||||||
internal val target = DefaultVideoLoaderTarget(this, thumbnailImage)
|
internal val target = DefaultVideoLoaderTarget(this, views.videoThumbnailImage)
|
||||||
|
|
||||||
override fun onRecycled() {
|
override fun onRecycled() {
|
||||||
super.onRecycled()
|
super.onRecycled()
|
||||||
|
@ -77,12 +70,12 @@ class VideoViewHolder constructor(itemView: View) :
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun entersBackground() {
|
override fun entersBackground() {
|
||||||
if (videoView.isPlaying) {
|
if (views.videoView.isPlaying) {
|
||||||
progress = videoView.currentPosition
|
progress = views.videoView.currentPosition
|
||||||
progressDisposable?.dispose()
|
progressDisposable?.dispose()
|
||||||
progressDisposable = null
|
progressDisposable = null
|
||||||
videoView.stopPlayback()
|
views.videoView.stopPlayback()
|
||||||
videoView.pause()
|
views.videoView.pause()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -92,9 +85,9 @@ class VideoViewHolder constructor(itemView: View) :
|
||||||
|
|
||||||
override fun onSelected(selected: Boolean) {
|
override fun onSelected(selected: Boolean) {
|
||||||
if (!selected) {
|
if (!selected) {
|
||||||
if (videoView.isPlaying) {
|
if (views.videoView.isPlaying) {
|
||||||
progress = videoView.currentPosition
|
progress = views.videoView.currentPosition
|
||||||
videoView.stopPlayback()
|
views.videoView.stopPlayback()
|
||||||
} else {
|
} else {
|
||||||
progress = 0
|
progress = 0
|
||||||
}
|
}
|
||||||
|
@ -109,34 +102,34 @@ class VideoViewHolder constructor(itemView: View) :
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun startPlaying() {
|
private fun startPlaying() {
|
||||||
thumbnailImage.isVisible = false
|
views.videoThumbnailImage.isVisible = false
|
||||||
loaderProgressBar.isVisible = false
|
views.videoLoaderProgress.isVisible = false
|
||||||
videoView.isVisible = true
|
views.videoView.isVisible = true
|
||||||
|
|
||||||
videoView.setOnPreparedListener {
|
views.videoView.setOnPreparedListener {
|
||||||
progressDisposable?.dispose()
|
progressDisposable?.dispose()
|
||||||
progressDisposable = Observable.interval(100, TimeUnit.MILLISECONDS)
|
progressDisposable = Observable.interval(100, TimeUnit.MILLISECONDS)
|
||||||
.timeInterval()
|
.timeInterval()
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe {
|
.subscribe {
|
||||||
val duration = videoView.duration
|
val duration = views.videoView.duration
|
||||||
val progress = videoView.currentPosition
|
val progress = views.videoView.currentPosition
|
||||||
val isPlaying = videoView.isPlaying
|
val isPlaying = views.videoView.isPlaying
|
||||||
// Log.v("FOO", "isPlaying $isPlaying $progress/$duration")
|
// Log.v("FOO", "isPlaying $isPlaying $progress/$duration")
|
||||||
eventListener?.get()?.onEvent(AttachmentEvents.VideoEvent(isPlaying, progress, duration))
|
eventListener?.get()?.onEvent(AttachmentEvents.VideoEvent(isPlaying, progress, duration))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
videoView.setVideoPath(mVideoPath)
|
views.videoView.setVideoPath(mVideoPath)
|
||||||
} catch (failure: Throwable) {
|
} catch (failure: Throwable) {
|
||||||
// Couldn't open
|
// Couldn't open
|
||||||
Log.v(VideoViewHolder::class.java.name, "Failed to start video")
|
Log.v(VideoViewHolder::class.java.name, "Failed to start video")
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!wasPaused) {
|
if (!wasPaused) {
|
||||||
videoView.start()
|
views.videoView.start()
|
||||||
if (progress > 0) {
|
if (progress > 0) {
|
||||||
videoView.seekTo(progress)
|
views.videoView.seekTo(progress)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -146,17 +139,17 @@ class VideoViewHolder constructor(itemView: View) :
|
||||||
when (commands) {
|
when (commands) {
|
||||||
AttachmentCommands.StartVideo -> {
|
AttachmentCommands.StartVideo -> {
|
||||||
wasPaused = false
|
wasPaused = false
|
||||||
videoView.start()
|
views.videoView.start()
|
||||||
}
|
}
|
||||||
AttachmentCommands.PauseVideo -> {
|
AttachmentCommands.PauseVideo -> {
|
||||||
wasPaused = true
|
wasPaused = true
|
||||||
videoView.pause()
|
views.videoView.pause()
|
||||||
}
|
}
|
||||||
is AttachmentCommands.SeekTo -> {
|
is AttachmentCommands.SeekTo -> {
|
||||||
val duration = videoView.duration
|
val duration = views.videoView.duration
|
||||||
if (duration > 0) {
|
if (duration > 0) {
|
||||||
val seekDuration = duration * (commands.percentProgress / 100f)
|
val seekDuration = duration * (commands.percentProgress / 100f)
|
||||||
videoView.seekTo(seekDuration.toInt())
|
views.videoView.seekTo(seekDuration.toInt())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,31 +17,29 @@
|
||||||
package im.vector.lib.attachmentviewer
|
package im.vector.lib.attachmentviewer
|
||||||
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.ProgressBar
|
import im.vector.lib.attachmentviewer.databinding.ItemImageAttachmentBinding
|
||||||
import com.github.chrisbanes.photoview.PhotoView
|
|
||||||
|
|
||||||
class ZoomableImageViewHolder constructor(itemView: View) :
|
class ZoomableImageViewHolder constructor(itemView: View) :
|
||||||
BaseViewHolder(itemView) {
|
BaseViewHolder(itemView) {
|
||||||
|
|
||||||
val touchImageView: PhotoView = itemView.findViewById(R.id.touchImageView)
|
val views = ItemImageAttachmentBinding.bind(itemView)
|
||||||
val imageLoaderProgress: ProgressBar = itemView.findViewById(R.id.imageLoaderProgress)
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
touchImageView.setAllowParentInterceptOnEdge(false)
|
views.touchImageView.setAllowParentInterceptOnEdge(false)
|
||||||
touchImageView.setOnScaleChangeListener { scaleFactor, _, _ ->
|
views.touchImageView.setOnScaleChangeListener { scaleFactor, _, _ ->
|
||||||
// Log.v("ATTACHEMENTS", "scaleFactor $scaleFactor")
|
// Log.v("ATTACHEMENTS", "scaleFactor $scaleFactor")
|
||||||
// It's a bit annoying but when you pitch down the scaling
|
// It's a bit annoying but when you pitch down the scaling
|
||||||
// is not exactly one :/
|
// is not exactly one :/
|
||||||
touchImageView.setAllowParentInterceptOnEdge(scaleFactor <= 1.0008f)
|
views.touchImageView.setAllowParentInterceptOnEdge(scaleFactor <= 1.0008f)
|
||||||
}
|
}
|
||||||
touchImageView.setScale(1.0f, true)
|
views.touchImageView.setScale(1.0f, true)
|
||||||
touchImageView.setAllowParentInterceptOnEdge(true)
|
views.touchImageView.setAllowParentInterceptOnEdge(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
internal val target = DefaultImageLoaderTarget.ZoomableImageTarget(this, touchImageView)
|
internal val target = DefaultImageLoaderTarget.ZoomableImageTarget(this, views.touchImageView)
|
||||||
|
|
||||||
override fun onRecycled() {
|
override fun onRecycled() {
|
||||||
super.onRecycled()
|
super.onRecycled()
|
||||||
touchImageView.setImageDrawable(null)
|
views.touchImageView.setImageDrawable(null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,6 +43,10 @@ allprojects {
|
||||||
includeGroupByRegex 'com\\.github\\.chrisbanes'
|
includeGroupByRegex 'com\\.github\\.chrisbanes'
|
||||||
// PFLockScreen-Android
|
// PFLockScreen-Android
|
||||||
includeGroupByRegex 'com\\.github\\.vector-im'
|
includeGroupByRegex 'com\\.github\\.vector-im'
|
||||||
|
|
||||||
|
//Chat effects
|
||||||
|
includeGroupByRegex 'com\\.github\\.jetradarmobile'
|
||||||
|
includeGroupByRegex 'nl\\.dionsegijn'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
maven {
|
maven {
|
||||||
|
|
|
@ -165,7 +165,7 @@ In this case, the user can click on "Sign in with SSO" and the native web browse
|
||||||
|
|
||||||
> https://homeserver.with.sso/_matrix/client/r0/login/sso/redirect?redirectUrl=element%3A%2F%element
|
> https://homeserver.with.sso/_matrix/client/r0/login/sso/redirect?redirectUrl=element%3A%2F%element
|
||||||
|
|
||||||
The parameter `redirectUrl` is set to `element://element`.
|
The parameter `redirectUrl` is set to `element://connect`.
|
||||||
|
|
||||||
ChromeCustomTabs are an intermediate way to display a WebPage, between a WebView and using the external browser. More info can be found [here](https://developer.chrome.com/multidevice/android/customtabs)
|
ChromeCustomTabs are an intermediate way to display a WebPage, between a WebView and using the external browser. More info can be found [here](https://developer.chrome.com/multidevice/android/customtabs)
|
||||||
|
|
||||||
|
@ -175,7 +175,7 @@ During the process, user may be asked to validate an email by clicking on a link
|
||||||
|
|
||||||
Once the process is finished, the web page will call the `redirectUrl` with an extra parameter `loginToken`
|
Once the process is finished, the web page will call the `redirectUrl` with an extra parameter `loginToken`
|
||||||
|
|
||||||
> element://element?loginToken=MDAxOWxvY2F0aW9uIG1vemlsbGEub3JnCjAwMTNpZGVudGlmaWVy
|
> element://connect?loginToken=MDAxOWxvY2F0aW9uIG1vemlsbGEub3JnCjAwMTNpZGVudGlmaWVy
|
||||||
|
|
||||||
This navigation is intercepted by Element by the `LoginActivity`, which will then ask the homeserver to convert this `loginToken` to an access token
|
This navigation is intercepted by Element by the `LoginActivity`, which will then ask the homeserver to convert this `loginToken` to an access token
|
||||||
|
|
||||||
|
|
|
@ -0,0 +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
|
|
@ -0,0 +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
|
|
@ -0,0 +1,2 @@
|
||||||
|
Tämä versio sisältää virheenkorjauksia ja muita parannuksia. Viestien lähettäminen on nyt paljon nopeampaa.
|
||||||
|
Täysi muutosloki: https://github.com/vector-im/element-android/releases/tag/v1.0.10
|
|
@ -0,0 +1,30 @@
|
||||||
|
Element on uudenlainen viestinsovellus, joka:
|
||||||
|
|
||||||
|
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 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:
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
<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>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>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>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 keskustelua ja VoIP-puheluita. Pidä tietosi turvassa.
|
Turvallista, hajautettua, keskusteluja ja VoIP-puheluita. Pidä tietosi turvassa.
|
||||||
|
|
|
@ -1 +1,2 @@
|
||||||
// DA FARE
|
Questa nuova versione contiene soprattutto correzioni di errori e miglioramenti. L'invio di messaggi ora è molto più veloce.
|
||||||
|
Cronologia completa: https://github.com/vector-im/element-android/releases/tag/v1.0.10
|
||||||
|
|
|
@ -1 +1,2 @@
|
||||||
// A FAZER
|
Esta nova versão contém principalmente correções de erros e melhorias. Enviar mensagens agora é muito mais rápido.
|
||||||
|
Registro de todas as alterações: https://github.com/vector-im/element-android/releases/tag/v1.0.10
|
||||||
|
|
|
@ -1 +1,2 @@
|
||||||
// ATT GÖRA
|
Den här nya versionen innehåller mest buggfixar och förbättringar. Det går nu mycket snabbare att skicka meddelanden.
|
||||||
|
Full ändringslogg: https://github.com/vector-im/element-android/releases/tag/v1.0.10
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
Ця версія містить переважно виправлення помилок та деякі покращення. Відправлення повідомлень стало тепер ще швидшим.
|
||||||
|
Повний перелік змін: https://github.com/vector-im/element-android/releases/tag/v1.0.10
|
|
@ -1 +1,2 @@
|
||||||
// 待辦事項
|
這個新版本主要包含錯誤修復與改善。傳送訊息更快了。
|
||||||
|
完整的變更紀錄請見:https://github.com/vector-im/element-android/releases/tag/v1.0.10
|
||||||
|
|
|
@ -18,7 +18,7 @@ org.gradle.jvmargs=-Xmx2048m
|
||||||
org.gradle.vfs.watch=true
|
org.gradle.vfs.watch=true
|
||||||
|
|
||||||
vector.debugPrivateData=false
|
vector.debugPrivateData=false
|
||||||
vector.httpLogLevel=NONE
|
vector.httpLogLevel=BASIC
|
||||||
|
|
||||||
# Note: to debug, you can put and uncomment the following lines in the file ~/.gradle/gradle.properties to override the value above
|
# Note: to debug, you can put and uncomment the following lines in the file ~/.gradle/gradle.properties to override the value above
|
||||||
#vector.debugPrivateData=true
|
#vector.debugPrivateData=true
|
||||||
|
|
|
@ -38,6 +38,7 @@ dependencies {
|
||||||
implementation 'androidx.appcompat:appcompat:1.2.0'
|
implementation 'androidx.appcompat:appcompat:1.2.0'
|
||||||
implementation 'io.reactivex.rxjava2:rxkotlin:2.3.0'
|
implementation 'io.reactivex.rxjava2:rxkotlin:2.3.0'
|
||||||
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
|
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
|
||||||
|
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-rx2:$kotlin_coroutines_version"
|
||||||
|
|
||||||
// Paging
|
// Paging
|
||||||
implementation "androidx.paging:paging-runtime-ktx:2.1.2"
|
implementation "androidx.paging:paging-runtime-ktx:2.1.2"
|
||||||
|
|
|
@ -17,14 +17,20 @@
|
||||||
package org.matrix.android.sdk.rx
|
package org.matrix.android.sdk.rx
|
||||||
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import io.reactivex.Completable
|
||||||
|
import io.reactivex.Observable
|
||||||
|
import io.reactivex.Single
|
||||||
|
import kotlinx.coroutines.rx2.rxCompletable
|
||||||
import org.matrix.android.sdk.api.query.QueryStringValue
|
import org.matrix.android.sdk.api.query.QueryStringValue
|
||||||
import org.matrix.android.sdk.api.session.events.model.Event
|
import org.matrix.android.sdk.api.session.events.model.Event
|
||||||
import org.matrix.android.sdk.api.session.identity.ThreePid
|
import org.matrix.android.sdk.api.session.identity.ThreePid
|
||||||
import org.matrix.android.sdk.api.session.room.Room
|
import org.matrix.android.sdk.api.session.room.Room
|
||||||
import org.matrix.android.sdk.api.session.room.members.RoomMemberQueryParams
|
import org.matrix.android.sdk.api.session.room.members.RoomMemberQueryParams
|
||||||
import org.matrix.android.sdk.api.session.room.model.EventAnnotationsSummary
|
import org.matrix.android.sdk.api.session.room.model.EventAnnotationsSummary
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.GuestAccess
|
||||||
import org.matrix.android.sdk.api.session.room.model.ReadReceipt
|
import org.matrix.android.sdk.api.session.room.model.ReadReceipt
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility
|
import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.RoomJoinRules
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary
|
import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||||
import org.matrix.android.sdk.api.session.room.notification.RoomNotificationState
|
import org.matrix.android.sdk.api.session.room.notification.RoomNotificationState
|
||||||
|
@ -32,11 +38,6 @@ import org.matrix.android.sdk.api.session.room.send.UserDraft
|
||||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||||
import org.matrix.android.sdk.api.util.Optional
|
import org.matrix.android.sdk.api.util.Optional
|
||||||
import org.matrix.android.sdk.api.util.toOptional
|
import org.matrix.android.sdk.api.util.toOptional
|
||||||
import io.reactivex.Completable
|
|
||||||
import io.reactivex.Observable
|
|
||||||
import io.reactivex.Single
|
|
||||||
import org.matrix.android.sdk.api.session.room.model.GuestAccess
|
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomJoinRules
|
|
||||||
|
|
||||||
class RxRoom(private val room: Room) {
|
class RxRoom(private val room: Room) {
|
||||||
|
|
||||||
|
@ -121,28 +122,28 @@ class RxRoom(private val room: Room) {
|
||||||
room.invite3pid(threePid, it)
|
room.invite3pid(threePid, it)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateTopic(topic: String): Completable = completableBuilder<Unit> {
|
fun updateTopic(topic: String): Completable = rxCompletable {
|
||||||
room.updateTopic(topic, it)
|
room.updateTopic(topic)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateName(name: String): Completable = completableBuilder<Unit> {
|
fun updateName(name: String): Completable = rxCompletable {
|
||||||
room.updateName(name, it)
|
room.updateName(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateHistoryReadability(readability: RoomHistoryVisibility): Completable = completableBuilder<Unit> {
|
fun updateHistoryReadability(readability: RoomHistoryVisibility): Completable = rxCompletable {
|
||||||
room.updateHistoryReadability(readability, it)
|
room.updateHistoryReadability(readability)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateJoinRule(joinRules: RoomJoinRules?, guestAccess: GuestAccess?): Completable = completableBuilder<Unit> {
|
fun updateJoinRule(joinRules: RoomJoinRules?, guestAccess: GuestAccess?): Completable = rxCompletable {
|
||||||
room.updateJoinRule(joinRules, guestAccess, it)
|
room.updateJoinRule(joinRules, guestAccess)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateAvatar(avatarUri: Uri, fileName: String): Completable = completableBuilder<Unit> {
|
fun updateAvatar(avatarUri: Uri, fileName: String): Completable = rxCompletable {
|
||||||
room.updateAvatar(avatarUri, fileName, it)
|
room.updateAvatar(avatarUri, fileName)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun deleteAvatar(): Completable = completableBuilder<Unit> {
|
fun deleteAvatar(): Completable = rxCompletable {
|
||||||
room.deleteAvatar(it)
|
room.deleteAvatar()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -47,6 +47,7 @@ import org.matrix.android.sdk.api.util.toOptional
|
||||||
import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo
|
import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo
|
||||||
import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo
|
import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo
|
||||||
import org.matrix.android.sdk.internal.crypto.store.PrivateKeysInfo
|
import org.matrix.android.sdk.internal.crypto.store.PrivateKeysInfo
|
||||||
|
import org.matrix.android.sdk.internal.session.room.alias.RoomAliasDescription
|
||||||
|
|
||||||
class RxSession(private val session: Session) {
|
class RxSession(private val session: Session) {
|
||||||
|
|
||||||
|
@ -139,7 +140,7 @@ class RxSession(private val session: Session) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getRoomIdByAlias(roomAlias: String,
|
fun getRoomIdByAlias(roomAlias: String,
|
||||||
searchOnServer: Boolean): Single<Optional<String>> = singleBuilder {
|
searchOnServer: Boolean): Single<Optional<RoomAliasDescription>> = singleBuilder {
|
||||||
session.getRoomIdByAlias(roomAlias, searchOnServer, it)
|
session.getRoomIdByAlias(roomAlias, searchOnServer, it)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
apply plugin: 'com.android.library'
|
apply plugin: 'com.android.library'
|
||||||
apply plugin: 'kotlin-android'
|
apply plugin: 'kotlin-android'
|
||||||
apply plugin: 'kotlin-kapt'
|
apply plugin: 'kotlin-kapt'
|
||||||
apply plugin: 'kotlin-android-extensions'
|
apply plugin: 'kotlin-parcelize'
|
||||||
apply plugin: 'realm-android'
|
apply plugin: 'realm-android'
|
||||||
|
|
||||||
buildscript {
|
buildscript {
|
||||||
|
@ -9,14 +9,10 @@ buildscript {
|
||||||
jcenter()
|
jcenter()
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath "io.realm:realm-gradle-plugin:10.0.0"
|
classpath "io.realm:realm-gradle-plugin:10.1.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
androidExtensions {
|
|
||||||
experimental = true
|
|
||||||
}
|
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 29
|
compileSdkVersion 29
|
||||||
testOptions.unitTests.includeAndroidResources = true
|
testOptions.unitTests.includeAndroidResources = true
|
||||||
|
@ -63,7 +59,7 @@ android {
|
||||||
|
|
||||||
release {
|
release {
|
||||||
buildConfigField "boolean", "LOG_PRIVATE_DATA", "false"
|
buildConfigField "boolean", "LOG_PRIVATE_DATA", "false"
|
||||||
buildConfigField "okhttp3.logging.HttpLoggingInterceptor.Level", "OKHTTP_LOGGING_LEVEL", "okhttp3.logging.HttpLoggingInterceptor.Level.NONE"
|
buildConfigField "okhttp3.logging.HttpLoggingInterceptor.Level", "OKHTTP_LOGGING_LEVEL", "okhttp3.logging.HttpLoggingInterceptor.Level.BASIC"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,7 @@ import androidx.work.WorkManager
|
||||||
import com.zhuinden.monarchy.Monarchy
|
import com.zhuinden.monarchy.Monarchy
|
||||||
import org.matrix.android.sdk.BuildConfig
|
import org.matrix.android.sdk.BuildConfig
|
||||||
import org.matrix.android.sdk.api.auth.AuthenticationService
|
import org.matrix.android.sdk.api.auth.AuthenticationService
|
||||||
|
import org.matrix.android.sdk.api.auth.HomeServerHistoryService
|
||||||
import org.matrix.android.sdk.api.legacy.LegacySessionImporter
|
import org.matrix.android.sdk.api.legacy.LegacySessionImporter
|
||||||
import org.matrix.android.sdk.api.raw.RawService
|
import org.matrix.android.sdk.api.raw.RawService
|
||||||
import org.matrix.android.sdk.common.DaggerTestMatrixComponent
|
import org.matrix.android.sdk.common.DaggerTestMatrixComponent
|
||||||
|
@ -49,6 +50,7 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo
|
||||||
@Inject internal lateinit var backgroundDetectionObserver: BackgroundDetectionObserver
|
@Inject internal lateinit var backgroundDetectionObserver: BackgroundDetectionObserver
|
||||||
@Inject internal lateinit var olmManager: OlmManager
|
@Inject internal lateinit var olmManager: OlmManager
|
||||||
@Inject internal lateinit var sessionManager: SessionManager
|
@Inject internal lateinit var sessionManager: SessionManager
|
||||||
|
@Inject internal lateinit var homeServerHistoryService: HomeServerHistoryService
|
||||||
|
|
||||||
private val uiHandler = Handler(Looper.getMainLooper())
|
private val uiHandler = Handler(Looper.getMainLooper())
|
||||||
|
|
||||||
|
@ -71,6 +73,8 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo
|
||||||
|
|
||||||
fun rawService() = rawService
|
fun rawService() = rawService
|
||||||
|
|
||||||
|
fun homeServerHistoryService() = homeServerHistoryService
|
||||||
|
|
||||||
fun legacySessionImporter(): LegacySessionImporter {
|
fun legacySessionImporter(): LegacySessionImporter {
|
||||||
return legacySessionImporter
|
return legacySessionImporter
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,13 +17,13 @@
|
||||||
package org.matrix.android.sdk.internal.crypto.encryption
|
package org.matrix.android.sdk.internal.crypto.encryption
|
||||||
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
import org.amshove.kluent.shouldBe
|
import org.amshove.kluent.shouldBe
|
||||||
import org.junit.FixMethodOrder
|
import org.junit.FixMethodOrder
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
import org.junit.runners.MethodSorters
|
import org.junit.runners.MethodSorters
|
||||||
import org.matrix.android.sdk.InstrumentedTest
|
import org.matrix.android.sdk.InstrumentedTest
|
||||||
import org.matrix.android.sdk.api.NoOpMatrixCallback
|
|
||||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||||
import org.matrix.android.sdk.api.session.events.model.toContent
|
import org.matrix.android.sdk.api.session.events.model.toContent
|
||||||
import org.matrix.android.sdk.api.session.room.Room
|
import org.matrix.android.sdk.api.session.room.Room
|
||||||
|
@ -57,15 +57,16 @@ class EncryptionTest : InstrumentedTest {
|
||||||
@Test
|
@Test
|
||||||
fun test_EncryptionStateEvent() {
|
fun test_EncryptionStateEvent() {
|
||||||
performTest(roomShouldBeEncrypted = true) { room ->
|
performTest(roomShouldBeEncrypted = true) { room ->
|
||||||
|
runBlocking {
|
||||||
// Send an encryption Event as a State Event
|
// Send an encryption Event as a State Event
|
||||||
room.sendStateEvent(
|
room.sendStateEvent(
|
||||||
eventType = EventType.STATE_ROOM_ENCRYPTION,
|
eventType = EventType.STATE_ROOM_ENCRYPTION,
|
||||||
stateKey = null,
|
stateKey = null,
|
||||||
body = EncryptionEventContent(algorithm = MXCRYPTO_ALGORITHM_MEGOLM).toContent(),
|
body = EncryptionEventContent(algorithm = MXCRYPTO_ALGORITHM_MEGOLM).toContent()
|
||||||
callback = NoOpMatrixCallback()
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun performTest(roomShouldBeEncrypted: Boolean, action: (Room) -> Unit) {
|
private fun performTest(roomShouldBeEncrypted: Boolean, action: (Room) -> Unit) {
|
||||||
val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceInARoom(encryptedRoom = false)
|
val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceInARoom(encryptedRoom = false)
|
||||||
|
|
|
@ -264,7 +264,7 @@ class KeysBackupTest : InstrumentedTest {
|
||||||
assertNotNull(decryption)
|
assertNotNull(decryption)
|
||||||
// - Check decryptKeyBackupData() returns stg
|
// - Check decryptKeyBackupData() returns stg
|
||||||
val sessionData = keysBackup
|
val sessionData = keysBackup
|
||||||
.decryptKeyBackupData(keyBackupData!!,
|
.decryptKeyBackupData(keyBackupData,
|
||||||
session.olmInboundGroupSession!!.sessionIdentifier(),
|
session.olmInboundGroupSession!!.sessionIdentifier(),
|
||||||
cryptoTestData.roomId,
|
cryptoTestData.roomId,
|
||||||
decryption!!)
|
decryption!!)
|
||||||
|
|
|
@ -111,7 +111,7 @@ class KeysBackupTestHelper(
|
||||||
Assert.assertTrue(keysBackup.isEnabled)
|
Assert.assertTrue(keysBackup.isEnabled)
|
||||||
|
|
||||||
stateObserver.stopAndCheckStates(null)
|
stateObserver.stopAndCheckStates(null)
|
||||||
return PrepareKeysBackupDataResult(megolmBackupCreationInfo, keysVersion.version!!)
|
return PrepareKeysBackupDataResult(megolmBackupCreationInfo, keysVersion.version)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -0,0 +1,108 @@
|
||||||
|
/*
|
||||||
|
* 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.session.media
|
||||||
|
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
|
import org.amshove.kluent.shouldBeEqualTo
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.matrix.android.sdk.InstrumentedTest
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.Event
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.toContent
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.message.MessageTextContent
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.message.MessageType
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4::class)
|
||||||
|
internal class UrlsExtractorTest : InstrumentedTest {
|
||||||
|
|
||||||
|
private val urlsExtractor = UrlsExtractor()
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun wrongEventTypeTest() {
|
||||||
|
createEvent(body = "https://matrix.org")
|
||||||
|
.copy(type = EventType.STATE_ROOM_GUEST_ACCESS)
|
||||||
|
.let { urlsExtractor.extract(it) }
|
||||||
|
.size shouldBeEqualTo 0
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun oneUrlTest() {
|
||||||
|
createEvent(body = "https://matrix.org")
|
||||||
|
.let { urlsExtractor.extract(it) }
|
||||||
|
.let { result ->
|
||||||
|
result.size shouldBeEqualTo 1
|
||||||
|
result[0] shouldBeEqualTo "https://matrix.org"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun withoutProtocolTest() {
|
||||||
|
createEvent(body = "www.matrix.org")
|
||||||
|
.let { urlsExtractor.extract(it) }
|
||||||
|
.size shouldBeEqualTo 0
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun oneUrlWithParamTest() {
|
||||||
|
createEvent(body = "https://matrix.org?foo=bar")
|
||||||
|
.let { urlsExtractor.extract(it) }
|
||||||
|
.let { result ->
|
||||||
|
result.size shouldBeEqualTo 1
|
||||||
|
result[0] shouldBeEqualTo "https://matrix.org?foo=bar"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun oneUrlWithParamsTest() {
|
||||||
|
createEvent(body = "https://matrix.org?foo=bar&bar=foo")
|
||||||
|
.let { urlsExtractor.extract(it) }
|
||||||
|
.let { result ->
|
||||||
|
result.size shouldBeEqualTo 1
|
||||||
|
result[0] shouldBeEqualTo "https://matrix.org?foo=bar&bar=foo"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun oneUrlInlinedTest() {
|
||||||
|
createEvent(body = "Hello https://matrix.org, how are you?")
|
||||||
|
.let { urlsExtractor.extract(it) }
|
||||||
|
.let { result ->
|
||||||
|
result.size shouldBeEqualTo 1
|
||||||
|
result[0] shouldBeEqualTo "https://matrix.org"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun twoUrlsTest() {
|
||||||
|
createEvent(body = "https://matrix.org https://example.org")
|
||||||
|
.let { urlsExtractor.extract(it) }
|
||||||
|
.let { result ->
|
||||||
|
result.size shouldBeEqualTo 2
|
||||||
|
result[0] shouldBeEqualTo "https://matrix.org"
|
||||||
|
result[1] shouldBeEqualTo "https://example.org"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createEvent(body: String): Event = Event(
|
||||||
|
type = EventType.MESSAGE,
|
||||||
|
content = MessageTextContent(
|
||||||
|
msgType = MessageType.MSGTYPE_TEXT,
|
||||||
|
body = body
|
||||||
|
).toContent()
|
||||||
|
)
|
||||||
|
}
|
|
@ -17,7 +17,6 @@
|
||||||
package org.matrix.android.sdk.internal.network.interceptors
|
package org.matrix.android.sdk.internal.network.interceptors
|
||||||
|
|
||||||
import androidx.annotation.NonNull
|
import androidx.annotation.NonNull
|
||||||
import org.matrix.android.sdk.BuildConfig
|
|
||||||
import okhttp3.logging.HttpLoggingInterceptor
|
import okhttp3.logging.HttpLoggingInterceptor
|
||||||
import org.json.JSONArray
|
import org.json.JSONArray
|
||||||
import org.json.JSONException
|
import org.json.JSONException
|
||||||
|
@ -38,8 +37,6 @@ class FormattedJsonHttpLogger : HttpLoggingInterceptor.Logger {
|
||||||
*/
|
*/
|
||||||
@Synchronized
|
@Synchronized
|
||||||
override fun log(@NonNull message: String) {
|
override fun log(@NonNull message: String) {
|
||||||
// In RELEASE there is no log, but for sure, test again BuildConfig.DEBUG
|
|
||||||
if (BuildConfig.DEBUG) {
|
|
||||||
Timber.v(message)
|
Timber.v(message)
|
||||||
|
|
||||||
if (message.startsWith("{")) {
|
if (message.startsWith("{")) {
|
||||||
|
@ -63,7 +60,6 @@ class FormattedJsonHttpLogger : HttpLoggingInterceptor.Logger {
|
||||||
}
|
}
|
||||||
// Else not a json string to log
|
// Else not a json string to log
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private fun logJson(formattedJson: String) {
|
private fun logJson(formattedJson: String) {
|
||||||
formattedJson
|
formattedJson
|
||||||
|
|
|
@ -23,6 +23,7 @@ import androidx.work.WorkManager
|
||||||
import com.zhuinden.monarchy.Monarchy
|
import com.zhuinden.monarchy.Monarchy
|
||||||
import org.matrix.android.sdk.BuildConfig
|
import org.matrix.android.sdk.BuildConfig
|
||||||
import org.matrix.android.sdk.api.auth.AuthenticationService
|
import org.matrix.android.sdk.api.auth.AuthenticationService
|
||||||
|
import org.matrix.android.sdk.api.auth.HomeServerHistoryService
|
||||||
import org.matrix.android.sdk.api.legacy.LegacySessionImporter
|
import org.matrix.android.sdk.api.legacy.LegacySessionImporter
|
||||||
import org.matrix.android.sdk.api.raw.RawService
|
import org.matrix.android.sdk.api.raw.RawService
|
||||||
import org.matrix.android.sdk.internal.SessionManager
|
import org.matrix.android.sdk.internal.SessionManager
|
||||||
|
@ -47,6 +48,7 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo
|
||||||
@Inject internal lateinit var backgroundDetectionObserver: BackgroundDetectionObserver
|
@Inject internal lateinit var backgroundDetectionObserver: BackgroundDetectionObserver
|
||||||
@Inject internal lateinit var olmManager: OlmManager
|
@Inject internal lateinit var olmManager: OlmManager
|
||||||
@Inject internal lateinit var sessionManager: SessionManager
|
@Inject internal lateinit var sessionManager: SessionManager
|
||||||
|
@Inject internal lateinit var homeServerHistoryService: HomeServerHistoryService
|
||||||
|
|
||||||
init {
|
init {
|
||||||
Monarchy.init(context)
|
Monarchy.init(context)
|
||||||
|
@ -65,6 +67,8 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo
|
||||||
|
|
||||||
fun rawService() = rawService
|
fun rawService() = rawService
|
||||||
|
|
||||||
|
fun homeServerHistoryService() = homeServerHistoryService
|
||||||
|
|
||||||
fun legacySessionImporter(): LegacySessionImporter {
|
fun legacySessionImporter(): LegacySessionImporter {
|
||||||
return legacySessionImporter
|
return legacySessionImporter
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,7 @@ package org.matrix.android.sdk.api.auth
|
||||||
/**
|
/**
|
||||||
* Path to use when the client does not supported any or all login flows
|
* 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
|
* Ref: https://matrix.org/docs/spec/client_server/latest#login-fallback
|
||||||
* */
|
*/
|
||||||
const val LOGIN_FALLBACK_PATH = "/_matrix/static/client/login/"
|
const val LOGIN_FALLBACK_PATH = "/_matrix/static/client/login/"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -33,5 +33,6 @@ const val REGISTER_FALLBACK_PATH = "/_matrix/static/client/register/"
|
||||||
* Ref: https://matrix.org/docs/spec/client_server/latest#sso-client-login
|
* 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 SSO_REDIRECT_PATH = "/_matrix/client/r0/login/sso/redirect"
|
||||||
|
const val MSC2858_SSO_REDIRECT_PATH = "/_matrix/client/unstable/org.matrix.msc2858/login/sso/redirect"
|
||||||
|
|
||||||
const val SSO_REDIRECT_URL_PARAM = "redirectUrl"
|
const val SSO_REDIRECT_URL_PARAM = "redirectUrl"
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
/*
|
||||||
|
* 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.auth
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A simple service to remember homeservers you already connected to.
|
||||||
|
*/
|
||||||
|
interface HomeServerHistoryService {
|
||||||
|
|
||||||
|
fun getKnownServersUrls(): List<String>
|
||||||
|
|
||||||
|
fun addHomeServerToHistory(url: String)
|
||||||
|
|
||||||
|
fun clearHistory()
|
||||||
|
}
|
|
@ -19,6 +19,7 @@ package org.matrix.android.sdk.api.auth.data
|
||||||
sealed class LoginFlowResult {
|
sealed class LoginFlowResult {
|
||||||
data class Success(
|
data class Success(
|
||||||
val supportedLoginTypes: List<String>,
|
val supportedLoginTypes: List<String>,
|
||||||
|
val ssoIdentityProviders: List<SsoIdentityProvider>?,
|
||||||
val isLoginAndRegistrationSupported: Boolean,
|
val isLoginAndRegistrationSupported: Boolean,
|
||||||
val homeServerUrl: String,
|
val homeServerUrl: String,
|
||||||
val isOutdatedHomeserver: Boolean
|
val isOutdatedHomeserver: Boolean
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
/*
|
||||||
|
* 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.auth.data
|
||||||
|
|
||||||
|
import android.os.Parcelable
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
@Parcelize
|
||||||
|
data class SsoIdentityProvider(
|
||||||
|
/**
|
||||||
|
* The id field would be opaque with the accepted characters matching unreserved URI characters as defined in RFC3986
|
||||||
|
* - this was chosen to avoid having to encode special characters in the URL. Max length 128.
|
||||||
|
*/
|
||||||
|
@Json(name = "id") val id: String,
|
||||||
|
/**
|
||||||
|
* The name field should be the human readable string intended for printing by the client.
|
||||||
|
*/
|
||||||
|
@Json(name = "name") val name: String?,
|
||||||
|
/**
|
||||||
|
* The icon field is the only optional field and should point to an icon representing the IdP.
|
||||||
|
* If present then it must be an HTTPS URL to an image resource.
|
||||||
|
* This should be hosted by the homeserver service provider to not leak the client's IP address unnecessarily.
|
||||||
|
*/
|
||||||
|
@Json(name = "icon") val iconUrl: String?
|
||||||
|
) : Parcelable {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
// Not really defined by the spec, but we may define some ids here
|
||||||
|
const val ID_GOOGLE = "google"
|
||||||
|
const val ID_GITHUB = "github"
|
||||||
|
const val ID_APPLE = "apple"
|
||||||
|
const val ID_FACEBOOK = "facebook"
|
||||||
|
const val ID_TWITTER = "twitter"
|
||||||
|
}
|
||||||
|
}
|
|
@ -14,16 +14,16 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.matrix.android.sdk.api.raw
|
package org.matrix.android.sdk.api.cache
|
||||||
|
|
||||||
sealed class RawCacheStrategy {
|
sealed class CacheStrategy {
|
||||||
// Data is always fetched from the server
|
// Data is always fetched from the server
|
||||||
object NoCache : RawCacheStrategy()
|
object NoCache : CacheStrategy()
|
||||||
|
|
||||||
// Once data is retrieved, it is stored for the provided amount of time.
|
// Once data is retrieved, it is stored for the provided amount of time.
|
||||||
// In case of error, and if strict is set to false, the cache can be returned if available
|
// In case of error, and if strict is set to false, the cache can be returned if available
|
||||||
data class TtlCache(val validityDurationInMillis: Long, val strict: Boolean) : RawCacheStrategy()
|
data class TtlCache(val validityDurationInMillis: Long, val strict: Boolean) : CacheStrategy()
|
||||||
|
|
||||||
// Once retrieved, the data is stored in cache and will be always get from the cache
|
// Once retrieved, the data is stored in cache and will be always get from the cache
|
||||||
object InfiniteCache : RawCacheStrategy()
|
object InfiniteCache : CacheStrategy()
|
||||||
}
|
}
|
|
@ -16,6 +16,8 @@
|
||||||
|
|
||||||
package org.matrix.android.sdk.api.raw
|
package org.matrix.android.sdk.api.raw
|
||||||
|
|
||||||
|
import org.matrix.android.sdk.api.cache.CacheStrategy
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Useful methods to fetch raw data from the server. The access token will not be used to fetched the data
|
* Useful methods to fetch raw data from the server. The access token will not be used to fetched the data
|
||||||
*/
|
*/
|
||||||
|
@ -23,7 +25,7 @@ interface RawService {
|
||||||
/**
|
/**
|
||||||
* Get a URL, either from cache or from the remote server, depending on the cache strategy
|
* Get a URL, either from cache or from the remote server, depending on the cache strategy
|
||||||
*/
|
*/
|
||||||
suspend fun getUrl(url: String, rawCacheStrategy: RawCacheStrategy): String
|
suspend fun getUrl(url: String, cacheStrategy: CacheStrategy): String
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Specific case for the well-known file. Cache validity is 8 hours
|
* Specific case for the well-known file. Cache validity is 8 hours
|
||||||
|
|
|
@ -35,6 +35,7 @@ import org.matrix.android.sdk.api.session.group.GroupService
|
||||||
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilitiesService
|
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilitiesService
|
||||||
import org.matrix.android.sdk.api.session.identity.IdentityService
|
import org.matrix.android.sdk.api.session.identity.IdentityService
|
||||||
import org.matrix.android.sdk.api.session.integrationmanager.IntegrationManagerService
|
import org.matrix.android.sdk.api.session.integrationmanager.IntegrationManagerService
|
||||||
|
import org.matrix.android.sdk.api.session.media.MediaService
|
||||||
import org.matrix.android.sdk.api.session.permalinks.PermalinkService
|
import org.matrix.android.sdk.api.session.permalinks.PermalinkService
|
||||||
import org.matrix.android.sdk.api.session.profile.ProfileService
|
import org.matrix.android.sdk.api.session.profile.ProfileService
|
||||||
import org.matrix.android.sdk.api.session.pushers.PushersService
|
import org.matrix.android.sdk.api.session.pushers.PushersService
|
||||||
|
@ -181,6 +182,11 @@ interface Session :
|
||||||
*/
|
*/
|
||||||
fun widgetService(): WidgetService
|
fun widgetService(): WidgetService
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the media service associated with the session
|
||||||
|
*/
|
||||||
|
fun mediaService(): MediaService
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the integration manager service associated with the session
|
* Returns the integration manager service associated with the session
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -20,7 +20,8 @@ import android.net.Uri
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import androidx.exifinterface.media.ExifInterface
|
import androidx.exifinterface.media.ExifInterface
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
import kotlinx.android.parcel.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
|
import org.matrix.android.sdk.api.util.MimeTypes.normalizeMimeType
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
|
@ -45,5 +46,5 @@ data class ContentAttachmentData(
|
||||||
VIDEO
|
VIDEO
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getSafeMimeType() = if (mimeType == "image/jpg") "image/jpeg" else mimeType
|
fun getSafeMimeType() = mimeType?.normalizeMimeType()
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,8 +18,12 @@ package org.matrix.android.sdk.api.session.file
|
||||||
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import org.matrix.android.sdk.api.MatrixCallback
|
import org.matrix.android.sdk.api.MatrixCallback
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.message.MessageWithAttachmentContent
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.message.getFileName
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.message.getFileUrl
|
||||||
import org.matrix.android.sdk.api.util.Cancelable
|
import org.matrix.android.sdk.api.util.Cancelable
|
||||||
import org.matrix.android.sdk.internal.crypto.attachments.ElementToDecrypt
|
import org.matrix.android.sdk.internal.crypto.attachments.ElementToDecrypt
|
||||||
|
import org.matrix.android.sdk.internal.crypto.attachments.toElementToDecrypt
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -27,23 +31,6 @@ import java.io.File
|
||||||
*/
|
*/
|
||||||
interface FileService {
|
interface FileService {
|
||||||
|
|
||||||
enum class DownloadMode {
|
|
||||||
/**
|
|
||||||
* Download file in external storage
|
|
||||||
*/
|
|
||||||
TO_EXPORT,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Download file in cache
|
|
||||||
*/
|
|
||||||
FOR_INTERNAL_USE,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Download file in file provider path
|
|
||||||
*/
|
|
||||||
FOR_EXTERNAL_SHARE
|
|
||||||
}
|
|
||||||
|
|
||||||
enum class FileState {
|
enum class FileState {
|
||||||
IN_CACHE,
|
IN_CACHE,
|
||||||
DOWNLOADING,
|
DOWNLOADING,
|
||||||
|
@ -54,34 +41,79 @@ interface FileService {
|
||||||
* Download a file.
|
* Download a file.
|
||||||
* Result will be a decrypted file, stored in the cache folder. url parameter will be used to create unique filename to avoid name collision.
|
* Result will be a decrypted file, stored in the cache folder. url parameter will be used to create unique filename to avoid name collision.
|
||||||
*/
|
*/
|
||||||
fun downloadFile(
|
fun downloadFile(fileName: String,
|
||||||
downloadMode: DownloadMode,
|
|
||||||
id: String,
|
|
||||||
fileName: String,
|
|
||||||
mimeType: String?,
|
mimeType: String?,
|
||||||
url: String?,
|
url: String?,
|
||||||
elementToDecrypt: ElementToDecrypt?,
|
elementToDecrypt: ElementToDecrypt?,
|
||||||
callback: MatrixCallback<File>): Cancelable
|
callback: MatrixCallback<File>): Cancelable
|
||||||
|
|
||||||
fun isFileInCache(mxcUrl: String, mimeType: String?): Boolean
|
fun downloadFile(messageContent: MessageWithAttachmentContent,
|
||||||
|
callback: MatrixCallback<File>): Cancelable =
|
||||||
|
downloadFile(
|
||||||
|
fileName = messageContent.getFileName(),
|
||||||
|
mimeType = messageContent.mimeType,
|
||||||
|
url = messageContent.getFileUrl(),
|
||||||
|
elementToDecrypt = messageContent.encryptedFileInfo?.toElementToDecrypt(),
|
||||||
|
callback = callback
|
||||||
|
)
|
||||||
|
|
||||||
|
fun isFileInCache(mxcUrl: String?,
|
||||||
|
fileName: String,
|
||||||
|
mimeType: String?,
|
||||||
|
elementToDecrypt: ElementToDecrypt?
|
||||||
|
): Boolean
|
||||||
|
|
||||||
|
fun isFileInCache(messageContent: MessageWithAttachmentContent) =
|
||||||
|
isFileInCache(
|
||||||
|
mxcUrl = messageContent.getFileUrl(),
|
||||||
|
fileName = messageContent.getFileName(),
|
||||||
|
mimeType = messageContent.mimeType,
|
||||||
|
elementToDecrypt = messageContent.encryptedFileInfo?.toElementToDecrypt())
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Use this URI and pass it to intent using flag Intent.FLAG_GRANT_READ_URI_PERMISSION
|
* Use this URI and pass it to intent using flag Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||||
* (if not other app won't be able to access it)
|
* (if not other app won't be able to access it)
|
||||||
*/
|
*/
|
||||||
fun getTemporarySharableURI(mxcUrl: String, mimeType: String?): Uri?
|
fun getTemporarySharableURI(mxcUrl: String?,
|
||||||
|
fileName: String,
|
||||||
|
mimeType: String?,
|
||||||
|
elementToDecrypt: ElementToDecrypt?): Uri?
|
||||||
|
|
||||||
|
fun getTemporarySharableURI(messageContent: MessageWithAttachmentContent): Uri? =
|
||||||
|
getTemporarySharableURI(
|
||||||
|
mxcUrl = messageContent.getFileUrl(),
|
||||||
|
fileName = messageContent.getFileName(),
|
||||||
|
mimeType = messageContent.mimeType,
|
||||||
|
elementToDecrypt = messageContent.encryptedFileInfo?.toElementToDecrypt()
|
||||||
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get information on the given file.
|
* Get information on the given file.
|
||||||
* Mimetype should be the same one as passed to downloadFile (limitation for now)
|
* Mimetype should be the same one as passed to downloadFile (limitation for now)
|
||||||
*/
|
*/
|
||||||
fun fileState(mxcUrl: String, mimeType: String?): FileState
|
fun fileState(mxcUrl: String?,
|
||||||
|
fileName: String,
|
||||||
|
mimeType: String?,
|
||||||
|
elementToDecrypt: ElementToDecrypt?): FileState
|
||||||
|
|
||||||
|
fun fileState(messageContent: MessageWithAttachmentContent): FileState =
|
||||||
|
fileState(
|
||||||
|
mxcUrl = messageContent.getFileUrl(),
|
||||||
|
fileName = messageContent.getFileName(),
|
||||||
|
mimeType = messageContent.mimeType,
|
||||||
|
elementToDecrypt = messageContent.encryptedFileInfo?.toElementToDecrypt()
|
||||||
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clears all the files downloaded by the service
|
* Clears all the files downloaded by the service, including decrypted files
|
||||||
*/
|
*/
|
||||||
fun clearCache()
|
fun clearCache()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears all the decrypted files by the service
|
||||||
|
*/
|
||||||
|
fun clearDecryptedCache()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get size of cached files
|
* Get size of cached files
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -0,0 +1,50 @@
|
||||||
|
/*
|
||||||
|
* 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.media
|
||||||
|
|
||||||
|
import org.matrix.android.sdk.api.cache.CacheStrategy
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.Event
|
||||||
|
import org.matrix.android.sdk.api.util.JsonDict
|
||||||
|
|
||||||
|
interface MediaService {
|
||||||
|
/**
|
||||||
|
* Extract URLs from an Event.
|
||||||
|
* @return the list of URLs contains in the body of the Event. It does not mean that URLs in this list have UrlPreview data
|
||||||
|
*/
|
||||||
|
fun extractUrls(event: Event): List<String>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Raw Url Preview data from the homeserver. There is no cache management for this request
|
||||||
|
* @param url The url to get the preview data from
|
||||||
|
* @param timestamp The optional timestamp
|
||||||
|
*/
|
||||||
|
suspend fun getRawPreviewUrl(url: String, timestamp: Long?): JsonDict
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Url Preview data from the homeserver, or from cache, depending on the cache strategy
|
||||||
|
* @param url The url to get the preview data from
|
||||||
|
* @param timestamp The optional timestamp. Note that this parameter is not taken into account
|
||||||
|
* if the data is already in cache and the cache strategy allow to use it
|
||||||
|
* @param cacheStrategy the cache strategy, see the type for more details
|
||||||
|
*/
|
||||||
|
suspend fun getPreviewUrl(url: String, timestamp: Long?, cacheStrategy: CacheStrategy): PreviewUrlData
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear the cache of all retrieved UrlPreview data
|
||||||
|
*/
|
||||||
|
suspend fun clearCache()
|
||||||
|
}
|
|
@ -0,0 +1,51 @@
|
||||||
|
/*
|
||||||
|
* 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.media
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Facility data class to get the common field of a PreviewUrl response form the server
|
||||||
|
*
|
||||||
|
* Example of return data for the url `https://matrix.org`:
|
||||||
|
* <pre>
|
||||||
|
* {
|
||||||
|
* "matrix:image:size": 112805,
|
||||||
|
* "og:description": "Matrix is an open standard for interoperable, decentralised, real-time communication",
|
||||||
|
* "og:image": "mxc://matrix.org/2020-12-03_uFqjagCCTJbaaJxb",
|
||||||
|
* "og:image:alt": "Matrix is an open standard for interoperable, decentralised, real-time communication",
|
||||||
|
* "og:image:height": 467,
|
||||||
|
* "og:image:type": "image/jpeg",
|
||||||
|
* "og:image:width": 911,
|
||||||
|
* "og:locale": "en_US",
|
||||||
|
* "og:site_name": "Matrix.org",
|
||||||
|
* "og:title": "Matrix.org",
|
||||||
|
* "og:type": "website",
|
||||||
|
* "og:url": "https://matrix.org"
|
||||||
|
* }
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
data class PreviewUrlData(
|
||||||
|
// Value of field "og:url". If not provided, this is the value passed in parameter
|
||||||
|
val url: String,
|
||||||
|
// Value of field "og:site_name"
|
||||||
|
val siteName: String?,
|
||||||
|
// Value of field "og:title"
|
||||||
|
val title: String?,
|
||||||
|
// Value of field "og:description"
|
||||||
|
val description: String?,
|
||||||
|
// Value of field "og:image"
|
||||||
|
val mxcUrl: String?
|
||||||
|
)
|
|
@ -25,6 +25,7 @@ interface PermalinkService {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val MATRIX_TO_URL_BASE = "https://matrix.to/#/"
|
const val MATRIX_TO_URL_BASE = "https://matrix.to/#/"
|
||||||
|
const val MATRIX_TO_CUSTOM_SCHEME_URL_BASE = "element://"
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -18,12 +18,15 @@ package org.matrix.android.sdk.api.session.room
|
||||||
|
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import org.matrix.android.sdk.api.MatrixCallback
|
import org.matrix.android.sdk.api.MatrixCallback
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.Event
|
||||||
import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
|
import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary
|
import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||||
import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
|
import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
|
||||||
|
import org.matrix.android.sdk.api.session.room.peeking.PeekResult
|
||||||
import org.matrix.android.sdk.api.util.Cancelable
|
import org.matrix.android.sdk.api.util.Cancelable
|
||||||
import org.matrix.android.sdk.api.util.Optional
|
import org.matrix.android.sdk.api.util.Optional
|
||||||
|
import org.matrix.android.sdk.internal.session.room.alias.RoomAliasDescription
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This interface defines methods to get rooms. It's implemented at the session level.
|
* This interface defines methods to get rooms. It's implemented at the session level.
|
||||||
|
@ -120,7 +123,7 @@ interface RoomService {
|
||||||
*/
|
*/
|
||||||
fun getRoomIdByAlias(roomAlias: String,
|
fun getRoomIdByAlias(roomAlias: String,
|
||||||
searchOnServer: Boolean,
|
searchOnServer: Boolean,
|
||||||
callback: MatrixCallback<Optional<String>>): Cancelable
|
callback: MatrixCallback<Optional<RoomAliasDescription>>): Cancelable
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete a room alias
|
* Delete a room alias
|
||||||
|
@ -163,4 +166,16 @@ interface RoomService {
|
||||||
* @return a LiveData of the optional found room member
|
* @return a LiveData of the optional found room member
|
||||||
*/
|
*/
|
||||||
fun getRoomMemberLive(userId: String, roomId: String): LiveData<Optional<RoomMemberSummary>>
|
fun getRoomMemberLive(userId: String, roomId: String): LiveData<Optional<RoomMemberSummary>>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get some state events about a room
|
||||||
|
*/
|
||||||
|
fun getRoomState(roomId: String, callback: MatrixCallback<List<Event>>)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use this if you want to get information from a room that you are not yet in (or invited)
|
||||||
|
* It might be possible to get some information on this room if it is public or if guest access is allowed
|
||||||
|
* This call will try to gather some information on this room, but it could fail and get nothing more
|
||||||
|
*/
|
||||||
|
fun peekRoom(roomIdOrAlias: String, callback: MatrixCallback<PeekResult>)
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@ import com.squareup.moshi.Json
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
import org.matrix.android.sdk.api.session.events.model.Content
|
import org.matrix.android.sdk.api.session.events.model.Content
|
||||||
import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent
|
import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent
|
||||||
|
import org.matrix.android.sdk.api.util.MimeTypes
|
||||||
import org.matrix.android.sdk.internal.crypto.model.rest.EncryptedFileInfo
|
import org.matrix.android.sdk.internal.crypto.model.rest.EncryptedFileInfo
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
|
@ -54,5 +55,5 @@ data class MessageImageContent(
|
||||||
@Json(name = "file") override val encryptedFileInfo: EncryptedFileInfo? = null
|
@Json(name = "file") override val encryptedFileInfo: EncryptedFileInfo? = null
|
||||||
) : MessageImageInfoContent {
|
) : MessageImageInfoContent {
|
||||||
override val mimeType: String?
|
override val mimeType: String?
|
||||||
get() = encryptedFileInfo?.mimetype ?: info?.mimeType ?: "image/*"
|
get() = encryptedFileInfo?.mimetype ?: info?.mimeType ?: MimeTypes.Images
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,4 +33,7 @@ object MessageType {
|
||||||
// Add, in local, a fake message type in order to StickerMessage can inherit Message class
|
// Add, in local, a fake message type in order to StickerMessage can inherit Message class
|
||||||
// Because sticker isn't a message type but a event type without msgtype field
|
// Because sticker isn't a message type but a event type without msgtype field
|
||||||
const val MSGTYPE_STICKER_LOCAL = "org.matrix.android.sdk.sticker"
|
const val MSGTYPE_STICKER_LOCAL = "org.matrix.android.sdk.sticker"
|
||||||
|
|
||||||
|
const val MSGTYPE_CONFETTI = "nic.custom.confetti"
|
||||||
|
const val MSGTYPE_SNOW = "nic.custom.snow"
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.session.room.peeking
|
||||||
|
|
||||||
|
sealed class PeekResult {
|
||||||
|
data class Success(
|
||||||
|
val roomId: String,
|
||||||
|
val alias: String?,
|
||||||
|
val name: String?,
|
||||||
|
val topic: String?,
|
||||||
|
val avatarUrl: String?,
|
||||||
|
val numJoinedMembers: Int?,
|
||||||
|
val viaServers: List<String>
|
||||||
|
) : PeekResult()
|
||||||
|
|
||||||
|
data class PeekingNotAllowed(
|
||||||
|
val roomId: String,
|
||||||
|
val alias: String?,
|
||||||
|
val viaServers: List<String>
|
||||||
|
) : PeekResult()
|
||||||
|
|
||||||
|
object UnknownAlias : PeekResult()
|
||||||
|
}
|
|
@ -18,13 +18,11 @@ package org.matrix.android.sdk.api.session.room.state
|
||||||
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import org.matrix.android.sdk.api.MatrixCallback
|
|
||||||
import org.matrix.android.sdk.api.query.QueryStringValue
|
import org.matrix.android.sdk.api.query.QueryStringValue
|
||||||
import org.matrix.android.sdk.api.session.events.model.Event
|
import org.matrix.android.sdk.api.session.events.model.Event
|
||||||
import org.matrix.android.sdk.api.session.room.model.GuestAccess
|
import org.matrix.android.sdk.api.session.room.model.GuestAccess
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility
|
import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomJoinRules
|
import org.matrix.android.sdk.api.session.room.model.RoomJoinRules
|
||||||
import org.matrix.android.sdk.api.util.Cancelable
|
|
||||||
import org.matrix.android.sdk.api.util.JsonDict
|
import org.matrix.android.sdk.api.util.JsonDict
|
||||||
import org.matrix.android.sdk.api.util.Optional
|
import org.matrix.android.sdk.api.util.Optional
|
||||||
|
|
||||||
|
@ -33,41 +31,41 @@ interface StateService {
|
||||||
/**
|
/**
|
||||||
* Update the topic of the room
|
* Update the topic of the room
|
||||||
*/
|
*/
|
||||||
fun updateTopic(topic: String, callback: MatrixCallback<Unit>): Cancelable
|
suspend fun updateTopic(topic: String)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update the name of the room
|
* Update the name of the room
|
||||||
*/
|
*/
|
||||||
fun updateName(name: String, callback: MatrixCallback<Unit>): Cancelable
|
suspend fun updateName(name: String)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update the canonical alias of the room
|
* Update the canonical alias of the room
|
||||||
* @param alias the canonical alias, or null to reset the canonical alias of this room
|
* @param alias the canonical alias, or null to reset the canonical alias of this room
|
||||||
* @param altAliases the alternative aliases for this room. It should include the canonical alias if any.
|
* @param altAliases the alternative aliases for this room. It should include the canonical alias if any.
|
||||||
*/
|
*/
|
||||||
fun updateCanonicalAlias(alias: String?, altAliases: List<String>, callback: MatrixCallback<Unit>): Cancelable
|
suspend fun updateCanonicalAlias(alias: String?, altAliases: List<String>)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update the history readability of the room
|
* Update the history readability of the room
|
||||||
*/
|
*/
|
||||||
fun updateHistoryReadability(readability: RoomHistoryVisibility, callback: MatrixCallback<Unit>): Cancelable
|
suspend fun updateHistoryReadability(readability: RoomHistoryVisibility)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update the join rule and/or the guest access
|
* Update the join rule and/or the guest access
|
||||||
*/
|
*/
|
||||||
fun updateJoinRule(joinRules: RoomJoinRules?, guestAccess: GuestAccess?, callback: MatrixCallback<Unit>): Cancelable
|
suspend fun updateJoinRule(joinRules: RoomJoinRules?, guestAccess: GuestAccess?)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update the avatar of the room
|
* Update the avatar of the room
|
||||||
*/
|
*/
|
||||||
fun updateAvatar(avatarUri: Uri, fileName: String, callback: MatrixCallback<Unit>): Cancelable
|
suspend fun updateAvatar(avatarUri: Uri, fileName: String)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete the avatar of the room
|
* Delete the avatar of the room
|
||||||
*/
|
*/
|
||||||
fun deleteAvatar(callback: MatrixCallback<Unit>): Cancelable
|
suspend fun deleteAvatar()
|
||||||
|
|
||||||
fun sendStateEvent(eventType: String, stateKey: String?, body: JsonDict, callback: MatrixCallback<Unit>): Cancelable
|
suspend fun sendStateEvent(eventType: String, stateKey: String?, body: JsonDict)
|
||||||
|
|
||||||
fun getStateEvent(eventType: String, stateKey: QueryStringValue = QueryStringValue.NoCondition): Event?
|
fun getStateEvent(eventType: String, stateKey: QueryStringValue = QueryStringValue.NoCondition): Event?
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
/*
|
||||||
|
* 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.timeline
|
||||||
|
|
||||||
|
data class EventTypeFilter(
|
||||||
|
/**
|
||||||
|
* Allowed event type.
|
||||||
|
*/
|
||||||
|
val eventType: String,
|
||||||
|
/**
|
||||||
|
* Allowed state key. Set null if you want to allow all events,
|
||||||
|
* otherwise allowed events will be filtered according to the given stateKey.
|
||||||
|
*/
|
||||||
|
val stateKey: String?
|
||||||
|
)
|
|
@ -36,5 +36,5 @@ data class TimelineEventFilters(
|
||||||
/**
|
/**
|
||||||
* If [filterTypes] is true, the list of types allowed by the list.
|
* If [filterTypes] is true, the list of types allowed by the list.
|
||||||
*/
|
*/
|
||||||
val allowedTypes: List<String> = emptyList()
|
val allowedTypes: List<EventTypeFilter> = emptyList()
|
||||||
)
|
)
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
/*
|
||||||
|
* 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 org.matrix.android.sdk.api.extensions.orFalse
|
||||||
|
|
||||||
|
// The Android SDK does not provide constant for mime type, add some of them here
|
||||||
|
object MimeTypes {
|
||||||
|
const val Any: String = "*/*"
|
||||||
|
const val OctetStream = "application/octet-stream"
|
||||||
|
|
||||||
|
const val Images = "image/*"
|
||||||
|
|
||||||
|
const val Png = "image/png"
|
||||||
|
const val BadJpg = "image/jpg"
|
||||||
|
const val Jpeg = "image/jpeg"
|
||||||
|
const val Gif = "image/gif"
|
||||||
|
|
||||||
|
fun String?.normalizeMimeType() = if (this == BadJpg) Jpeg else this
|
||||||
|
|
||||||
|
fun String?.isMimeTypeImage() = this?.startsWith("image/").orFalse()
|
||||||
|
fun String?.isMimeTypeVideo() = this?.startsWith("video/").orFalse()
|
||||||
|
fun String?.isMimeTypeAudio() = this?.startsWith("audio/").orFalse()
|
||||||
|
}
|
|
@ -33,6 +33,7 @@ import org.matrix.android.sdk.internal.di.AuthDatabase
|
||||||
import org.matrix.android.sdk.internal.legacy.DefaultLegacySessionImporter
|
import org.matrix.android.sdk.internal.legacy.DefaultLegacySessionImporter
|
||||||
import org.matrix.android.sdk.internal.wellknown.WellknownModule
|
import org.matrix.android.sdk.internal.wellknown.WellknownModule
|
||||||
import io.realm.RealmConfiguration
|
import io.realm.RealmConfiguration
|
||||||
|
import org.matrix.android.sdk.api.auth.HomeServerHistoryService
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
@Module(includes = [WellknownModule::class])
|
@Module(includes = [WellknownModule::class])
|
||||||
|
@ -80,4 +81,7 @@ internal abstract class AuthModule {
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindDirectLoginTask(task: DefaultDirectLoginTask): DirectLoginTask
|
abstract fun bindDirectLoginTask(task: DefaultDirectLoginTask): DirectLoginTask
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindHomeServerHistoryService(service: DefaultHomeServerHistoryService): HomeServerHistoryService
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,7 @@ import org.matrix.android.sdk.api.auth.AuthenticationService
|
||||||
import org.matrix.android.sdk.api.auth.data.Credentials
|
import org.matrix.android.sdk.api.auth.data.Credentials
|
||||||
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
|
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
|
||||||
import org.matrix.android.sdk.api.auth.data.LoginFlowResult
|
import org.matrix.android.sdk.api.auth.data.LoginFlowResult
|
||||||
|
import org.matrix.android.sdk.api.auth.data.LoginFlowTypes
|
||||||
import org.matrix.android.sdk.api.auth.login.LoginWizard
|
import org.matrix.android.sdk.api.auth.login.LoginWizard
|
||||||
import org.matrix.android.sdk.api.auth.registration.RegistrationWizard
|
import org.matrix.android.sdk.api.auth.registration.RegistrationWizard
|
||||||
import org.matrix.android.sdk.api.auth.wellknown.WellknownResult
|
import org.matrix.android.sdk.api.auth.wellknown.WellknownResult
|
||||||
|
@ -278,6 +279,7 @@ internal class DefaultAuthenticationService @Inject constructor(
|
||||||
}
|
}
|
||||||
return LoginFlowResult.Success(
|
return LoginFlowResult.Success(
|
||||||
loginFlowResponse.flows.orEmpty().mapNotNull { it.type },
|
loginFlowResponse.flows.orEmpty().mapNotNull { it.type },
|
||||||
|
loginFlowResponse.flows.orEmpty().firstOrNull { it.type == LoginFlowTypes.SSO }?.ssoIdentityProvider,
|
||||||
versions.isLoginAndRegistrationSupportedBySdk(),
|
versions.isLoginAndRegistrationSupportedBySdk(),
|
||||||
homeServerUrl,
|
homeServerUrl,
|
||||||
!versions.isSupportedBySdk()
|
!versions.isSupportedBySdk()
|
||||||
|
|
|
@ -0,0 +1,50 @@
|
||||||
|
/*
|
||||||
|
* 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.auth
|
||||||
|
|
||||||
|
import com.zhuinden.monarchy.Monarchy
|
||||||
|
import io.realm.kotlin.where
|
||||||
|
import org.matrix.android.sdk.api.auth.HomeServerHistoryService
|
||||||
|
import org.matrix.android.sdk.internal.database.model.KnownServerUrlEntity
|
||||||
|
import org.matrix.android.sdk.internal.di.GlobalDatabase
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class DefaultHomeServerHistoryService @Inject constructor(
|
||||||
|
@GlobalDatabase private val monarchy: Monarchy
|
||||||
|
) : HomeServerHistoryService {
|
||||||
|
|
||||||
|
override fun getKnownServersUrls(): List<String> {
|
||||||
|
return monarchy.fetchAllMappedSync(
|
||||||
|
{ realm ->
|
||||||
|
realm.where<KnownServerUrlEntity>()
|
||||||
|
},
|
||||||
|
{ it.url }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun addHomeServerToHistory(url: String) {
|
||||||
|
monarchy.writeAsync { realm ->
|
||||||
|
KnownServerUrlEntity(url).let {
|
||||||
|
realm.insertOrUpdate(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun clearHistory() {
|
||||||
|
monarchy.runTransactionSync { it.where<KnownServerUrlEntity>().findAll().deleteAllFromRealm() }
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.auth.data
|
||||||
|
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
|
import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
internal data class LoginFlowResponse(
|
internal data class LoginFlowResponse(
|
||||||
|
@ -34,5 +35,13 @@ internal data class LoginFlow(
|
||||||
* The login type. This is supplied as the type when logging in.
|
* The login type. This is supplied as the type when logging in.
|
||||||
*/
|
*/
|
||||||
@Json(name = "type")
|
@Json(name = "type")
|
||||||
val type: String?
|
val type: String?,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Augments m.login.sso flow discovery definition to include metadata on the supported IDPs
|
||||||
|
* the client can show a button for each of the supported providers
|
||||||
|
* See MSC #2858
|
||||||
|
*/
|
||||||
|
@Json(name = "org.matrix.msc2858.identity_providers")
|
||||||
|
val ssoIdentityProvider: List<SsoIdentityProvider>?
|
||||||
)
|
)
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
package org.matrix.android.sdk.internal.auth.registration
|
package org.matrix.android.sdk.internal.auth.registration
|
||||||
|
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import kotlinx.android.parcel.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class represent a localized privacy policy for registration Flow.
|
* This class represent a localized privacy policy for registration Flow.
|
||||||
|
|
|
@ -51,6 +51,18 @@ data class RegistrationFlowResponse(
|
||||||
* The information that the client will need to know in order to use a given type of authentication.
|
* The information that the client will need to know in order to use a given type of authentication.
|
||||||
* For each login stage type presented, that type may be present as a key in this dictionary.
|
* For each login stage type presented, that type may be present as a key in this dictionary.
|
||||||
* For example, the public key of reCAPTCHA stage could be given here.
|
* For example, the public key of reCAPTCHA stage could be given here.
|
||||||
|
* other example
|
||||||
|
* "params": {
|
||||||
|
* "m.login.sso": {
|
||||||
|
* "identity_providers": [
|
||||||
|
* {
|
||||||
|
* "id": "google",
|
||||||
|
* "name": "Google",
|
||||||
|
* "icon": "https://..."
|
||||||
|
* }
|
||||||
|
* ]
|
||||||
|
* }
|
||||||
|
* }
|
||||||
*/
|
*/
|
||||||
@Json(name = "params")
|
@Json(name = "params")
|
||||||
val params: JsonDict? = null
|
val params: JsonDict? = null
|
||||||
|
|
|
@ -18,7 +18,7 @@ package org.matrix.android.sdk.internal.crypto.attachments
|
||||||
|
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import org.matrix.android.sdk.internal.crypto.model.rest.EncryptedFileInfo
|
import org.matrix.android.sdk.internal.crypto.model.rest.EncryptedFileInfo
|
||||||
import kotlinx.android.parcel.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
fun EncryptedFileInfo.toElementToDecrypt(): ElementToDecrypt? {
|
fun EncryptedFileInfo.toElementToDecrypt(): ElementToDecrypt? {
|
||||||
// Check the validity of some fields
|
// Check the validity of some fields
|
||||||
|
|
|
@ -20,6 +20,7 @@ import io.realm.DynamicRealm
|
||||||
import io.realm.RealmMigration
|
import io.realm.RealmMigration
|
||||||
import org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEntityFields
|
import org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEntityFields
|
||||||
import org.matrix.android.sdk.internal.database.model.PendingThreePidEntityFields
|
import org.matrix.android.sdk.internal.database.model.PendingThreePidEntityFields
|
||||||
|
import org.matrix.android.sdk.internal.database.model.PreviewUrlCacheEntityFields
|
||||||
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields
|
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
@ -27,7 +28,7 @@ import javax.inject.Inject
|
||||||
class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
|
class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val SESSION_STORE_SCHEMA_VERSION = 5L
|
const val SESSION_STORE_SCHEMA_VERSION = 6L
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) {
|
override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) {
|
||||||
|
@ -38,6 +39,7 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
|
||||||
if (oldVersion <= 2) migrateTo3(realm)
|
if (oldVersion <= 2) migrateTo3(realm)
|
||||||
if (oldVersion <= 3) migrateTo4(realm)
|
if (oldVersion <= 3) migrateTo4(realm)
|
||||||
if (oldVersion <= 4) migrateTo5(realm)
|
if (oldVersion <= 4) migrateTo5(realm)
|
||||||
|
if (oldVersion <= 5) migrateTo6(realm)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun migrateTo1(realm: DynamicRealm) {
|
private fun migrateTo1(realm: DynamicRealm) {
|
||||||
|
@ -89,4 +91,18 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
|
||||||
?.removeField("adminE2EByDefault")
|
?.removeField("adminE2EByDefault")
|
||||||
?.removeField("preferredJitsiDomain")
|
?.removeField("preferredJitsiDomain")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun migrateTo6(realm: DynamicRealm) {
|
||||||
|
Timber.d("Step 5 -> 6")
|
||||||
|
realm.schema.create("PreviewUrlCacheEntity")
|
||||||
|
.addField(PreviewUrlCacheEntityFields.URL, String::class.java)
|
||||||
|
.setRequired(PreviewUrlCacheEntityFields.URL, true)
|
||||||
|
.addPrimaryKey(PreviewUrlCacheEntityFields.URL)
|
||||||
|
.addField(PreviewUrlCacheEntityFields.URL_FROM_SERVER, String::class.java)
|
||||||
|
.addField(PreviewUrlCacheEntityFields.SITE_NAME, String::class.java)
|
||||||
|
.addField(PreviewUrlCacheEntityFields.TITLE, String::class.java)
|
||||||
|
.addField(PreviewUrlCacheEntityFields.DESCRIPTION, String::class.java)
|
||||||
|
.addField(PreviewUrlCacheEntityFields.MXC_URL, String::class.java)
|
||||||
|
.addField(PreviewUrlCacheEntityFields.LAST_UPDATED_TIMESTAMP, Long::class.java)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
/*
|
||||||
|
* 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.database.model
|
||||||
|
|
||||||
|
import io.realm.RealmObject
|
||||||
|
import io.realm.annotations.PrimaryKey
|
||||||
|
|
||||||
|
internal open class KnownServerUrlEntity(
|
||||||
|
@PrimaryKey
|
||||||
|
var url: String = ""
|
||||||
|
) : RealmObject() {
|
||||||
|
companion object
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
/*
|
||||||
|
* 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.database.model
|
||||||
|
|
||||||
|
import io.realm.RealmObject
|
||||||
|
import io.realm.annotations.PrimaryKey
|
||||||
|
|
||||||
|
internal open class PreviewUrlCacheEntity(
|
||||||
|
@PrimaryKey
|
||||||
|
var url: String = "",
|
||||||
|
|
||||||
|
var urlFromServer: String? = null,
|
||||||
|
var siteName: String? = null,
|
||||||
|
var title: String? = null,
|
||||||
|
var description: String? = null,
|
||||||
|
var mxcUrl: String? = null,
|
||||||
|
|
||||||
|
var lastUpdatedTimestamp: Long = 0L
|
||||||
|
) : RealmObject() {
|
||||||
|
|
||||||
|
companion object
|
||||||
|
}
|
|
@ -48,6 +48,7 @@ import io.realm.annotations.RealmModule
|
||||||
PushRulesEntity::class,
|
PushRulesEntity::class,
|
||||||
PushRuleEntity::class,
|
PushRuleEntity::class,
|
||||||
PushConditionEntity::class,
|
PushConditionEntity::class,
|
||||||
|
PreviewUrlCacheEntity::class,
|
||||||
PusherEntity::class,
|
PusherEntity::class,
|
||||||
PusherDataEntity::class,
|
PusherDataEntity::class,
|
||||||
ReadReceiptsSummaryEntity::class,
|
ReadReceiptsSummaryEntity::class,
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
/*
|
||||||
|
* 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.database.query
|
||||||
|
|
||||||
|
import io.realm.Realm
|
||||||
|
import io.realm.kotlin.createObject
|
||||||
|
import io.realm.kotlin.where
|
||||||
|
import org.matrix.android.sdk.internal.database.model.PreviewUrlCacheEntity
|
||||||
|
import org.matrix.android.sdk.internal.database.model.PreviewUrlCacheEntityFields
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current PreviewUrlCacheEntity, return null if it does not exist
|
||||||
|
*/
|
||||||
|
internal fun PreviewUrlCacheEntity.Companion.get(realm: Realm, url: String): PreviewUrlCacheEntity? {
|
||||||
|
return realm.where<PreviewUrlCacheEntity>()
|
||||||
|
.equalTo(PreviewUrlCacheEntityFields.URL, url)
|
||||||
|
.findFirst()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current PreviewUrlCacheEntity, create one if it does not exist
|
||||||
|
*/
|
||||||
|
internal fun PreviewUrlCacheEntity.Companion.getOrCreate(realm: Realm, url: String): PreviewUrlCacheEntity {
|
||||||
|
return get(realm, url) ?: realm.createObject(url)
|
||||||
|
}
|
|
@ -71,8 +71,23 @@ internal fun TimelineEventEntity.Companion.latestEvent(realm: Realm,
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun RealmQuery<TimelineEventEntity>.filterEvents(filters: TimelineEventFilters): RealmQuery<TimelineEventEntity> {
|
internal fun RealmQuery<TimelineEventEntity>.filterEvents(filters: TimelineEventFilters): RealmQuery<TimelineEventEntity> {
|
||||||
if (filters.filterTypes) {
|
if (filters.filterTypes && filters.allowedTypes.isNotEmpty()) {
|
||||||
`in`(TimelineEventEntityFields.ROOT.TYPE, filters.allowedTypes.toTypedArray())
|
beginGroup()
|
||||||
|
filters.allowedTypes.forEachIndexed { index, filter ->
|
||||||
|
if (filter.stateKey == null) {
|
||||||
|
equalTo(TimelineEventEntityFields.ROOT.TYPE, filter.eventType)
|
||||||
|
} else {
|
||||||
|
beginGroup()
|
||||||
|
equalTo(TimelineEventEntityFields.ROOT.TYPE, filter.eventType)
|
||||||
|
and()
|
||||||
|
equalTo(TimelineEventEntityFields.ROOT.STATE_KEY, filter.stateKey)
|
||||||
|
endGroup()
|
||||||
|
}
|
||||||
|
if (index != filters.allowedTypes.size - 1) {
|
||||||
|
or()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
endGroup()
|
||||||
}
|
}
|
||||||
if (filters.filterUseless) {
|
if (filters.filterUseless) {
|
||||||
not()
|
not()
|
||||||
|
|
|
@ -25,6 +25,7 @@ import okhttp3.OkHttpClient
|
||||||
import org.matrix.android.sdk.api.Matrix
|
import org.matrix.android.sdk.api.Matrix
|
||||||
import org.matrix.android.sdk.api.MatrixConfiguration
|
import org.matrix.android.sdk.api.MatrixConfiguration
|
||||||
import org.matrix.android.sdk.api.auth.AuthenticationService
|
import org.matrix.android.sdk.api.auth.AuthenticationService
|
||||||
|
import org.matrix.android.sdk.api.auth.HomeServerHistoryService
|
||||||
import org.matrix.android.sdk.api.raw.RawService
|
import org.matrix.android.sdk.api.raw.RawService
|
||||||
import org.matrix.android.sdk.internal.SessionManager
|
import org.matrix.android.sdk.internal.SessionManager
|
||||||
import org.matrix.android.sdk.internal.auth.AuthModule
|
import org.matrix.android.sdk.internal.auth.AuthModule
|
||||||
|
@ -62,6 +63,8 @@ internal interface MatrixComponent {
|
||||||
|
|
||||||
fun rawService(): RawService
|
fun rawService(): RawService
|
||||||
|
|
||||||
|
fun homeServerHistoryService(): HomeServerHistoryService
|
||||||
|
|
||||||
fun context(): Context
|
fun context(): Context
|
||||||
|
|
||||||
fun matrixConfiguration(): MatrixConfiguration
|
fun matrixConfiguration(): MatrixConfiguration
|
||||||
|
@ -71,9 +74,6 @@ internal interface MatrixComponent {
|
||||||
@CacheDirectory
|
@CacheDirectory
|
||||||
fun cacheDir(): File
|
fun cacheDir(): File
|
||||||
|
|
||||||
@ExternalFilesDirectory
|
|
||||||
fun externalFilesDir(): File?
|
|
||||||
|
|
||||||
fun olmManager(): OlmManager
|
fun olmManager(): OlmManager
|
||||||
|
|
||||||
fun taskExecutor(): TaskExecutor
|
fun taskExecutor(): TaskExecutor
|
||||||
|
|
|
@ -57,13 +57,6 @@ internal object MatrixModule {
|
||||||
return context.cacheDir
|
return context.cacheDir
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
|
||||||
@Provides
|
|
||||||
@ExternalFilesDirectory
|
|
||||||
fun providesExternalFilesDir(context: Context): File? {
|
|
||||||
return context.getExternalFilesDir(null)
|
|
||||||
}
|
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@Provides
|
@Provides
|
||||||
@MatrixScope
|
@MatrixScope
|
||||||
|
|
|
@ -16,14 +16,15 @@
|
||||||
|
|
||||||
package org.matrix.android.sdk.internal.network
|
package org.matrix.android.sdk.internal.network
|
||||||
|
|
||||||
import org.matrix.android.sdk.api.failure.Failure
|
|
||||||
import org.matrix.android.sdk.api.failure.shouldBeRetried
|
|
||||||
import org.matrix.android.sdk.internal.network.ssl.CertUtil
|
|
||||||
import kotlinx.coroutines.CancellationException
|
import kotlinx.coroutines.CancellationException
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import org.greenrobot.eventbus.EventBus
|
import org.greenrobot.eventbus.EventBus
|
||||||
|
import org.matrix.android.sdk.api.failure.Failure
|
||||||
|
import org.matrix.android.sdk.api.failure.shouldBeRetried
|
||||||
|
import org.matrix.android.sdk.internal.network.ssl.CertUtil
|
||||||
import retrofit2.Call
|
import retrofit2.Call
|
||||||
import retrofit2.awaitResponse
|
import retrofit2.awaitResponse
|
||||||
|
import timber.log.Timber
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
|
||||||
internal suspend inline fun <DATA : Any> executeRequest(eventBus: EventBus?,
|
internal suspend inline fun <DATA : Any> executeRequest(eventBus: EventBus?,
|
||||||
|
@ -49,6 +50,9 @@ internal class Request<DATA : Any>(private val eventBus: EventBus?) {
|
||||||
throw response.toFailure(eventBus)
|
throw response.toFailure(eventBus)
|
||||||
}
|
}
|
||||||
} catch (exception: Throwable) {
|
} catch (exception: Throwable) {
|
||||||
|
// Log some details about the request which has failed
|
||||||
|
Timber.e("Exception when executing request ${apiCall.request().method} ${apiCall.request().url.toString().substringBefore("?")}")
|
||||||
|
|
||||||
// Check if this is a certificateException
|
// Check if this is a certificateException
|
||||||
CertUtil.getCertificateException(exception)
|
CertUtil.getCertificateException(exception)
|
||||||
// TODO Support certificate error once logged
|
// TODO Support certificate error once logged
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
package org.matrix.android.sdk.internal.raw
|
package org.matrix.android.sdk.internal.raw
|
||||||
|
|
||||||
import org.matrix.android.sdk.api.raw.RawCacheStrategy
|
import org.matrix.android.sdk.api.cache.CacheStrategy
|
||||||
import org.matrix.android.sdk.api.raw.RawService
|
import org.matrix.android.sdk.api.raw.RawService
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
@ -25,15 +25,15 @@ internal class DefaultRawService @Inject constructor(
|
||||||
private val getUrlTask: GetUrlTask,
|
private val getUrlTask: GetUrlTask,
|
||||||
private val cleanRawCacheTask: CleanRawCacheTask
|
private val cleanRawCacheTask: CleanRawCacheTask
|
||||||
) : RawService {
|
) : RawService {
|
||||||
override suspend fun getUrl(url: String, rawCacheStrategy: RawCacheStrategy): String {
|
override suspend fun getUrl(url: String, cacheStrategy: CacheStrategy): String {
|
||||||
return getUrlTask.execute(GetUrlTask.Params(url, rawCacheStrategy))
|
return getUrlTask.execute(GetUrlTask.Params(url, cacheStrategy))
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getWellknown(userId: String): String {
|
override suspend fun getWellknown(userId: String): String {
|
||||||
val homeServerDomain = userId.substringAfter(":")
|
val homeServerDomain = userId.substringAfter(":")
|
||||||
return getUrl(
|
return getUrl(
|
||||||
"https://$homeServerDomain/.well-known/matrix/client",
|
"https://$homeServerDomain/.well-known/matrix/client",
|
||||||
RawCacheStrategy.TtlCache(TimeUnit.HOURS.toMillis(8), false)
|
CacheStrategy.TtlCache(TimeUnit.HOURS.toMillis(8), false)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,7 @@ package org.matrix.android.sdk.internal.raw
|
||||||
|
|
||||||
import com.zhuinden.monarchy.Monarchy
|
import com.zhuinden.monarchy.Monarchy
|
||||||
import okhttp3.ResponseBody
|
import okhttp3.ResponseBody
|
||||||
import org.matrix.android.sdk.api.raw.RawCacheStrategy
|
import org.matrix.android.sdk.api.cache.CacheStrategy
|
||||||
import org.matrix.android.sdk.internal.database.model.RawCacheEntity
|
import org.matrix.android.sdk.internal.database.model.RawCacheEntity
|
||||||
import org.matrix.android.sdk.internal.database.query.get
|
import org.matrix.android.sdk.internal.database.query.get
|
||||||
import org.matrix.android.sdk.internal.database.query.getOrCreate
|
import org.matrix.android.sdk.internal.database.query.getOrCreate
|
||||||
|
@ -32,7 +32,7 @@ import javax.inject.Inject
|
||||||
internal interface GetUrlTask : Task<GetUrlTask.Params, String> {
|
internal interface GetUrlTask : Task<GetUrlTask.Params, String> {
|
||||||
data class Params(
|
data class Params(
|
||||||
val url: String,
|
val url: String,
|
||||||
val rawCacheStrategy: RawCacheStrategy
|
val cacheStrategy: CacheStrategy
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,14 +42,14 @@ internal class DefaultGetUrlTask @Inject constructor(
|
||||||
) : GetUrlTask {
|
) : GetUrlTask {
|
||||||
|
|
||||||
override suspend fun execute(params: GetUrlTask.Params): String {
|
override suspend fun execute(params: GetUrlTask.Params): String {
|
||||||
return when (params.rawCacheStrategy) {
|
return when (params.cacheStrategy) {
|
||||||
RawCacheStrategy.NoCache -> doRequest(params.url)
|
CacheStrategy.NoCache -> doRequest(params.url)
|
||||||
is RawCacheStrategy.TtlCache -> doRequestWithCache(
|
is CacheStrategy.TtlCache -> doRequestWithCache(
|
||||||
params.url,
|
params.url,
|
||||||
params.rawCacheStrategy.validityDurationInMillis,
|
params.cacheStrategy.validityDurationInMillis,
|
||||||
params.rawCacheStrategy.strict
|
params.cacheStrategy.strict
|
||||||
)
|
)
|
||||||
RawCacheStrategy.InfiniteCache -> doRequestWithCache(
|
CacheStrategy.InfiniteCache -> doRequestWithCache(
|
||||||
params.url,
|
params.url,
|
||||||
Long.MAX_VALUE,
|
Long.MAX_VALUE,
|
||||||
true
|
true
|
|
@ -0,0 +1,41 @@
|
||||||
|
/*
|
||||||
|
* 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.raw
|
||||||
|
|
||||||
|
import io.realm.DynamicRealm
|
||||||
|
import io.realm.RealmMigration
|
||||||
|
import org.matrix.android.sdk.internal.database.model.KnownServerUrlEntityFields
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
|
internal object GlobalRealmMigration : RealmMigration {
|
||||||
|
|
||||||
|
// Current schema version
|
||||||
|
const val SCHEMA_VERSION = 1L
|
||||||
|
|
||||||
|
override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) {
|
||||||
|
Timber.d("Migrating Auth Realm from $oldVersion to $newVersion")
|
||||||
|
|
||||||
|
if (oldVersion <= 0) migrateTo1(realm)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun migrateTo1(realm: DynamicRealm) {
|
||||||
|
realm.schema.create("KnownServerUrlEntity")
|
||||||
|
.addField(KnownServerUrlEntityFields.URL, String::class.java)
|
||||||
|
.addPrimaryKey(KnownServerUrlEntityFields.URL)
|
||||||
|
.setRequired(KnownServerUrlEntityFields.URL, true)
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,6 +17,7 @@
|
||||||
package org.matrix.android.sdk.internal.raw
|
package org.matrix.android.sdk.internal.raw
|
||||||
|
|
||||||
import io.realm.annotations.RealmModule
|
import io.realm.annotations.RealmModule
|
||||||
|
import org.matrix.android.sdk.internal.database.model.KnownServerUrlEntity
|
||||||
import org.matrix.android.sdk.internal.database.model.RawCacheEntity
|
import org.matrix.android.sdk.internal.database.model.RawCacheEntity
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -24,6 +25,7 @@ import org.matrix.android.sdk.internal.database.model.RawCacheEntity
|
||||||
*/
|
*/
|
||||||
@RealmModule(library = true,
|
@RealmModule(library = true,
|
||||||
classes = [
|
classes = [
|
||||||
RawCacheEntity::class
|
RawCacheEntity::class,
|
||||||
|
KnownServerUrlEntity::class
|
||||||
])
|
])
|
||||||
internal class GlobalRealmModule
|
internal class GlobalRealmModule
|
||||||
|
|
|
@ -57,6 +57,9 @@ internal abstract class RawModule {
|
||||||
realmKeysUtils.configureEncryption(this, DB_ALIAS)
|
realmKeysUtils.configureEncryption(this, DB_ALIAS)
|
||||||
}
|
}
|
||||||
.name("matrix-sdk-global.realm")
|
.name("matrix-sdk-global.realm")
|
||||||
|
.schemaVersion(GlobalRealmMigration.SCHEMA_VERSION)
|
||||||
|
.migration(GlobalRealmMigration)
|
||||||
|
.allowWritesOnUiThread(true)
|
||||||
.modules(GlobalRealmModule())
|
.modules(GlobalRealmModule())
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,10 @@ import android.net.Uri
|
||||||
import android.webkit.MimeTypeMap
|
import android.webkit.MimeTypeMap
|
||||||
import androidx.core.content.FileProvider
|
import androidx.core.content.FileProvider
|
||||||
import arrow.core.Try
|
import arrow.core.Try
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
|
import okhttp3.Request
|
||||||
import org.matrix.android.sdk.api.MatrixCallback
|
import org.matrix.android.sdk.api.MatrixCallback
|
||||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||||
import org.matrix.android.sdk.api.session.content.ContentUrlResolver
|
import org.matrix.android.sdk.api.session.content.ContentUrlResolver
|
||||||
|
@ -29,35 +33,21 @@ import org.matrix.android.sdk.api.util.Cancelable
|
||||||
import org.matrix.android.sdk.api.util.NoOpCancellable
|
import org.matrix.android.sdk.api.util.NoOpCancellable
|
||||||
import org.matrix.android.sdk.internal.crypto.attachments.ElementToDecrypt
|
import org.matrix.android.sdk.internal.crypto.attachments.ElementToDecrypt
|
||||||
import org.matrix.android.sdk.internal.crypto.attachments.MXEncryptedAttachments
|
import org.matrix.android.sdk.internal.crypto.attachments.MXEncryptedAttachments
|
||||||
import org.matrix.android.sdk.internal.di.CacheDirectory
|
|
||||||
import org.matrix.android.sdk.internal.di.ExternalFilesDirectory
|
|
||||||
import org.matrix.android.sdk.internal.di.SessionDownloadsDirectory
|
import org.matrix.android.sdk.internal.di.SessionDownloadsDirectory
|
||||||
import org.matrix.android.sdk.internal.di.UnauthenticatedWithCertificateWithProgress
|
import org.matrix.android.sdk.internal.di.UnauthenticatedWithCertificateWithProgress
|
||||||
import org.matrix.android.sdk.internal.session.download.DownloadProgressInterceptor.Companion.DOWNLOAD_PROGRESS_INTERCEPTOR_HEADER
|
import org.matrix.android.sdk.internal.session.download.DownloadProgressInterceptor.Companion.DOWNLOAD_PROGRESS_INTERCEPTOR_HEADER
|
||||||
import org.matrix.android.sdk.internal.task.TaskExecutor
|
import org.matrix.android.sdk.internal.task.TaskExecutor
|
||||||
import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers
|
import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers
|
||||||
|
import org.matrix.android.sdk.internal.util.md5
|
||||||
import org.matrix.android.sdk.internal.util.toCancelable
|
import org.matrix.android.sdk.internal.util.toCancelable
|
||||||
import org.matrix.android.sdk.internal.util.writeToFile
|
import org.matrix.android.sdk.internal.util.writeToFile
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import okhttp3.OkHttpClient
|
|
||||||
import okhttp3.Request
|
|
||||||
import okio.buffer
|
|
||||||
import okio.sink
|
|
||||||
import okio.source
|
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.io.InputStream
|
|
||||||
import java.net.URLEncoder
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal class DefaultFileService @Inject constructor(
|
internal class DefaultFileService @Inject constructor(
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
@CacheDirectory
|
|
||||||
private val cacheDirectory: File,
|
|
||||||
@ExternalFilesDirectory
|
|
||||||
private val externalFilesDirectory: File?,
|
|
||||||
@SessionDownloadsDirectory
|
@SessionDownloadsDirectory
|
||||||
private val sessionCacheDirectory: File,
|
private val sessionCacheDirectory: File,
|
||||||
private val contentUrlResolver: ContentUrlResolver,
|
private val contentUrlResolver: ContentUrlResolver,
|
||||||
|
@ -67,9 +57,17 @@ internal class DefaultFileService @Inject constructor(
|
||||||
private val taskExecutor: TaskExecutor
|
private val taskExecutor: TaskExecutor
|
||||||
) : FileService {
|
) : FileService {
|
||||||
|
|
||||||
private fun String.safeFileName() = URLEncoder.encode(this, Charsets.US_ASCII.displayName())
|
// Legacy folder, will be deleted
|
||||||
|
private val legacyFolder = File(sessionCacheDirectory, "MF")
|
||||||
|
// Folder to store downloaded files (not decrypted)
|
||||||
|
private val downloadFolder = File(sessionCacheDirectory, "F")
|
||||||
|
// Folder to store decrypted files
|
||||||
|
private val decryptedFolder = File(downloadFolder, "D")
|
||||||
|
|
||||||
private val downloadFolder = File(sessionCacheDirectory, "MF")
|
init {
|
||||||
|
// Clear the legacy downloaded files
|
||||||
|
legacyFolder.deleteRecursively()
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retain ongoing downloads to avoid re-downloading and already downloading file
|
* Retain ongoing downloads to avoid re-downloading and already downloading file
|
||||||
|
@ -81,28 +79,26 @@ internal class DefaultFileService @Inject constructor(
|
||||||
* Download file in the cache folder, and eventually decrypt it
|
* Download file in the cache folder, and eventually decrypt it
|
||||||
* TODO looks like files are copied 3 times
|
* TODO looks like files are copied 3 times
|
||||||
*/
|
*/
|
||||||
override fun downloadFile(downloadMode: FileService.DownloadMode,
|
override fun downloadFile(fileName: String,
|
||||||
id: String,
|
|
||||||
fileName: String,
|
|
||||||
mimeType: String?,
|
mimeType: String?,
|
||||||
url: String?,
|
url: String?,
|
||||||
elementToDecrypt: ElementToDecrypt?,
|
elementToDecrypt: ElementToDecrypt?,
|
||||||
callback: MatrixCallback<File>): Cancelable {
|
callback: MatrixCallback<File>): Cancelable {
|
||||||
val unwrappedUrl = url ?: return NoOpCancellable.also {
|
url ?: return NoOpCancellable.also {
|
||||||
callback.onFailure(IllegalArgumentException("url is null"))
|
callback.onFailure(IllegalArgumentException("url is null"))
|
||||||
}
|
}
|
||||||
|
|
||||||
Timber.v("## FileService downloadFile $unwrappedUrl")
|
Timber.v("## FileService downloadFile $url")
|
||||||
|
|
||||||
synchronized(ongoing) {
|
synchronized(ongoing) {
|
||||||
val existing = ongoing[unwrappedUrl]
|
val existing = ongoing[url]
|
||||||
if (existing != null) {
|
if (existing != null) {
|
||||||
Timber.v("## FileService downloadFile is already downloading.. ")
|
Timber.v("## FileService downloadFile is already downloading.. ")
|
||||||
existing.add(callback)
|
existing.add(callback)
|
||||||
return NoOpCancellable
|
return NoOpCancellable
|
||||||
} else {
|
} else {
|
||||||
// mark as tracked
|
// mark as tracked
|
||||||
ongoing[unwrappedUrl] = ArrayList()
|
ongoing[url] = ArrayList()
|
||||||
// and proceed to download
|
// and proceed to download
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -110,15 +106,15 @@ internal class DefaultFileService @Inject constructor(
|
||||||
return taskExecutor.executorScope.launch(coroutineDispatchers.main) {
|
return taskExecutor.executorScope.launch(coroutineDispatchers.main) {
|
||||||
withContext(coroutineDispatchers.io) {
|
withContext(coroutineDispatchers.io) {
|
||||||
Try {
|
Try {
|
||||||
if (!downloadFolder.exists()) {
|
if (!decryptedFolder.exists()) {
|
||||||
downloadFolder.mkdirs()
|
decryptedFolder.mkdirs()
|
||||||
}
|
}
|
||||||
// ensure we use unique file name by using URL (mapped to suitable file name)
|
// ensure we use unique file name by using URL (mapped to suitable file name)
|
||||||
// Also we need to add extension for the FileProvider, if not it lot's of app that it's
|
// Also we need to add extension for the FileProvider, if not it lot's of app that it's
|
||||||
// shared with will not function well (even if mime type is passed in the intent)
|
// shared with will not function well (even if mime type is passed in the intent)
|
||||||
File(downloadFolder, fileForUrl(unwrappedUrl, mimeType))
|
getFiles(url, fileName, mimeType, elementToDecrypt != null)
|
||||||
}.flatMap { destFile ->
|
}.flatMap { cachedFiles ->
|
||||||
if (!destFile.exists()) {
|
if (!cachedFiles.file.exists()) {
|
||||||
val resolvedUrl = contentUrlResolver.resolveFullSize(url) ?: return@flatMap Try.Failure(IllegalArgumentException("url is null"))
|
val resolvedUrl = contentUrlResolver.resolveFullSize(url) ?: return@flatMap Try.Failure(IllegalArgumentException("url is null"))
|
||||||
|
|
||||||
val request = Request.Builder()
|
val request = Request.Builder()
|
||||||
|
@ -141,79 +137,153 @@ internal class DefaultFileService @Inject constructor(
|
||||||
|
|
||||||
Timber.v("Response size ${response.body?.contentLength()} - Stream available: ${!source.exhausted()}")
|
Timber.v("Response size ${response.body?.contentLength()} - Stream available: ${!source.exhausted()}")
|
||||||
|
|
||||||
if (elementToDecrypt != null) {
|
// Write the file to cache (encrypted version if the file is encrypted)
|
||||||
Timber.v("## FileService: decrypt file")
|
writeToFile(source.inputStream(), cachedFiles.file)
|
||||||
val decryptSuccess = destFile.outputStream().buffered().use {
|
|
||||||
MXEncryptedAttachments.decryptAttachment(
|
|
||||||
source.inputStream(),
|
|
||||||
elementToDecrypt,
|
|
||||||
it
|
|
||||||
)
|
|
||||||
}
|
|
||||||
response.close()
|
response.close()
|
||||||
if (!decryptSuccess) {
|
|
||||||
return@flatMap Try.Failure(IllegalStateException("Decryption error"))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
writeToFile(source.inputStream(), destFile)
|
|
||||||
response.close()
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
Timber.v("## FileService: cache hit for $url")
|
Timber.v("## FileService: cache hit for $url")
|
||||||
}
|
}
|
||||||
|
|
||||||
Try.just(copyFile(destFile, downloadMode))
|
Try.just(cachedFiles)
|
||||||
}
|
}
|
||||||
}.fold({
|
}.flatMap { cachedFiles ->
|
||||||
callback.onFailure(it)
|
// Decrypt if necessary
|
||||||
|
if (cachedFiles.decryptedFile != null) {
|
||||||
|
if (!cachedFiles.decryptedFile.exists()) {
|
||||||
|
Timber.v("## FileService: decrypt file")
|
||||||
|
// Ensure the parent folder exists
|
||||||
|
cachedFiles.decryptedFile.parentFile?.mkdirs()
|
||||||
|
val decryptSuccess = cachedFiles.file.inputStream().use { inputStream ->
|
||||||
|
cachedFiles.decryptedFile.outputStream().buffered().use { outputStream ->
|
||||||
|
MXEncryptedAttachments.decryptAttachment(
|
||||||
|
inputStream,
|
||||||
|
elementToDecrypt,
|
||||||
|
outputStream
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!decryptSuccess) {
|
||||||
|
return@flatMap Try.Failure(IllegalStateException("Decryption error"))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Timber.v("## FileService: cache hit for decrypted file")
|
||||||
|
}
|
||||||
|
Try.just(cachedFiles.decryptedFile)
|
||||||
|
} else {
|
||||||
|
// Clear file
|
||||||
|
Try.just(cachedFiles.file)
|
||||||
|
}
|
||||||
|
}.fold(
|
||||||
|
{ throwable ->
|
||||||
|
callback.onFailure(throwable)
|
||||||
// notify concurrent requests
|
// notify concurrent requests
|
||||||
val toNotify = synchronized(ongoing) {
|
val toNotify = synchronized(ongoing) {
|
||||||
ongoing[unwrappedUrl]?.also {
|
ongoing[url]?.also {
|
||||||
ongoing.remove(unwrappedUrl)
|
ongoing.remove(url)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
toNotify?.forEach { otherCallbacks ->
|
toNotify?.forEach { otherCallbacks ->
|
||||||
tryOrNull { otherCallbacks.onFailure(it) }
|
tryOrNull { otherCallbacks.onFailure(throwable) }
|
||||||
}
|
}
|
||||||
}, { file ->
|
},
|
||||||
|
{ file ->
|
||||||
callback.onSuccess(file)
|
callback.onSuccess(file)
|
||||||
// notify concurrent requests
|
// notify concurrent requests
|
||||||
val toNotify = synchronized(ongoing) {
|
val toNotify = synchronized(ongoing) {
|
||||||
ongoing[unwrappedUrl]?.also {
|
ongoing[url]?.also {
|
||||||
ongoing.remove(unwrappedUrl)
|
ongoing.remove(url)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Timber.v("## FileService additional to notify ${toNotify?.size ?: 0} ")
|
Timber.v("## FileService additional to notify ${toNotify?.size ?: 0} ")
|
||||||
toNotify?.forEach { otherCallbacks ->
|
toNotify?.forEach { otherCallbacks ->
|
||||||
tryOrNull { otherCallbacks.onSuccess(file) }
|
tryOrNull { otherCallbacks.onSuccess(file) }
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
)
|
||||||
}.toCancelable()
|
}.toCancelable()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun storeDataFor(url: String, mimeType: String?, inputStream: InputStream) {
|
fun storeDataFor(mxcUrl: String,
|
||||||
val file = File(downloadFolder, fileForUrl(url, mimeType))
|
filename: String?,
|
||||||
val source = inputStream.source().buffer()
|
mimeType: String?,
|
||||||
file.sink().buffer().let { sink ->
|
originalFile: File,
|
||||||
source.use { input ->
|
encryptedFile: File?) {
|
||||||
sink.use { output ->
|
val files = getFiles(mxcUrl, filename, mimeType, encryptedFile != null)
|
||||||
output.writeAll(input)
|
if (encryptedFile != null) {
|
||||||
|
// We switch the two files here, original file it the decrypted file
|
||||||
|
files.decryptedFile?.let { originalFile.copyTo(it) }
|
||||||
|
encryptedFile.copyTo(files.file)
|
||||||
|
} else {
|
||||||
|
// Just copy the original file
|
||||||
|
originalFile.copyTo(files.file)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun safeFileName(fileName: String?, mimeType: String?): String {
|
||||||
|
return buildString {
|
||||||
|
// filename has to be safe for the Android System
|
||||||
|
val result = fileName
|
||||||
|
?.replace("[^a-z A-Z0-9\\\\.\\-]".toRegex(), "_")
|
||||||
|
?.takeIf { it.isNotEmpty() }
|
||||||
|
?: DEFAULT_FILENAME
|
||||||
|
append(result)
|
||||||
|
// Check that the extension is correct regarding the mimeType
|
||||||
|
val extensionFromMime = mimeType?.let { MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType) }
|
||||||
|
if (extensionFromMime != null) {
|
||||||
|
// Compare
|
||||||
|
val fileExtension = result.substringAfterLast(delimiter = ".", missingDelimiterValue = "")
|
||||||
|
if (fileExtension.isEmpty() || fileExtension != extensionFromMime) {
|
||||||
|
// Missing extension, or diff in extension, add the one provided by the mimetype
|
||||||
|
append(".")
|
||||||
|
append(extensionFromMime)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun fileForUrl(url: String, mimeType: String?): String {
|
override fun isFileInCache(mxcUrl: String?,
|
||||||
val extension = mimeType?.let { MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType) }
|
fileName: String,
|
||||||
return if (extension != null) "${url.safeFileName()}.$extension" else url.safeFileName()
|
mimeType: String?,
|
||||||
|
elementToDecrypt: ElementToDecrypt?): Boolean {
|
||||||
|
return fileState(mxcUrl, fileName, mimeType, elementToDecrypt) == FileService.FileState.IN_CACHE
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun isFileInCache(mxcUrl: String, mimeType: String?): Boolean {
|
internal data class CachedFiles(
|
||||||
return File(downloadFolder, fileForUrl(mxcUrl, mimeType)).exists()
|
// This is the downloaded file. Can be clear or encrypted
|
||||||
|
val file: File,
|
||||||
|
// This is the decrypted file. Null if the original file is not encrypted
|
||||||
|
val decryptedFile: File?
|
||||||
|
) {
|
||||||
|
fun getClearFile(): File = decryptedFile ?: file
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun fileState(mxcUrl: String, mimeType: String?): FileService.FileState {
|
private fun getFiles(mxcUrl: String,
|
||||||
if (isFileInCache(mxcUrl, mimeType)) return FileService.FileState.IN_CACHE
|
fileName: String?,
|
||||||
|
mimeType: String?,
|
||||||
|
isEncrypted: Boolean): CachedFiles {
|
||||||
|
val hashFolder = mxcUrl.md5()
|
||||||
|
val safeFileName = safeFileName(fileName, mimeType)
|
||||||
|
return if (isEncrypted) {
|
||||||
|
// Encrypted file
|
||||||
|
CachedFiles(
|
||||||
|
File(downloadFolder, "$hashFolder/$ENCRYPTED_FILENAME"),
|
||||||
|
File(decryptedFolder, "$hashFolder/$safeFileName")
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
// Clear file
|
||||||
|
CachedFiles(
|
||||||
|
File(downloadFolder, "$hashFolder/$safeFileName"),
|
||||||
|
null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun fileState(mxcUrl: String?,
|
||||||
|
fileName: String,
|
||||||
|
mimeType: String?,
|
||||||
|
elementToDecrypt: ElementToDecrypt?): FileService.FileState {
|
||||||
|
mxcUrl ?: return FileService.FileState.UNKNOWN
|
||||||
|
if (getFiles(mxcUrl, fileName, mimeType, elementToDecrypt != null).file.exists()) return FileService.FileState.IN_CACHE
|
||||||
val isDownloading = synchronized(ongoing) {
|
val isDownloading = synchronized(ongoing) {
|
||||||
ongoing[mxcUrl] != null
|
ongoing[mxcUrl] != null
|
||||||
}
|
}
|
||||||
|
@ -224,26 +294,18 @@ internal class DefaultFileService @Inject constructor(
|
||||||
* Use this URI and pass it to intent using flag Intent.FLAG_GRANT_READ_URI_PERMISSION
|
* Use this URI and pass it to intent using flag Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||||
* (if not other app won't be able to access it)
|
* (if not other app won't be able to access it)
|
||||||
*/
|
*/
|
||||||
override fun getTemporarySharableURI(mxcUrl: String, mimeType: String?): Uri? {
|
override fun getTemporarySharableURI(mxcUrl: String?,
|
||||||
|
fileName: String,
|
||||||
|
mimeType: String?,
|
||||||
|
elementToDecrypt: ElementToDecrypt?): Uri? {
|
||||||
|
mxcUrl ?: return null
|
||||||
// this string could be extracted no?
|
// this string could be extracted no?
|
||||||
val authority = "${context.packageName}.mx-sdk.fileprovider"
|
val authority = "${context.packageName}.mx-sdk.fileprovider"
|
||||||
val targetFile = File(downloadFolder, fileForUrl(mxcUrl, mimeType))
|
val targetFile = getFiles(mxcUrl, fileName, mimeType, elementToDecrypt != null).getClearFile()
|
||||||
if (!targetFile.exists()) return null
|
if (!targetFile.exists()) return null
|
||||||
return FileProvider.getUriForFile(context, authority, targetFile)
|
return FileProvider.getUriForFile(context, authority, targetFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun copyFile(file: File, downloadMode: FileService.DownloadMode): File {
|
|
||||||
// TODO some of this seems outdated, will need to be re-worked
|
|
||||||
return when (downloadMode) {
|
|
||||||
FileService.DownloadMode.TO_EXPORT ->
|
|
||||||
file.copyTo(File(externalFilesDirectory, file.name), true)
|
|
||||||
FileService.DownloadMode.FOR_EXTERNAL_SHARE ->
|
|
||||||
file.copyTo(File(File(cacheDirectory, "ext_share"), file.name), true)
|
|
||||||
FileService.DownloadMode.FOR_INTERNAL_USE ->
|
|
||||||
file
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getCacheSize(): Int {
|
override fun getCacheSize(): Int {
|
||||||
return downloadFolder.walkTopDown()
|
return downloadFolder.walkTopDown()
|
||||||
.onEnter {
|
.onEnter {
|
||||||
|
@ -256,4 +318,14 @@ internal class DefaultFileService @Inject constructor(
|
||||||
override fun clearCache() {
|
override fun clearCache() {
|
||||||
downloadFolder.deleteRecursively()
|
downloadFolder.deleteRecursively()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun clearDecryptedCache() {
|
||||||
|
decryptedFolder.deleteRecursively()
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val ENCRYPTED_FILENAME = "encrypted.bin"
|
||||||
|
// The extension would be added from the mimetype
|
||||||
|
private const val DEFAULT_FILENAME = "file"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,6 +43,7 @@ import org.matrix.android.sdk.api.session.file.FileService
|
||||||
import org.matrix.android.sdk.api.session.group.GroupService
|
import org.matrix.android.sdk.api.session.group.GroupService
|
||||||
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilitiesService
|
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilitiesService
|
||||||
import org.matrix.android.sdk.api.session.integrationmanager.IntegrationManagerService
|
import org.matrix.android.sdk.api.session.integrationmanager.IntegrationManagerService
|
||||||
|
import org.matrix.android.sdk.api.session.media.MediaService
|
||||||
import org.matrix.android.sdk.api.session.permalinks.PermalinkService
|
import org.matrix.android.sdk.api.session.permalinks.PermalinkService
|
||||||
import org.matrix.android.sdk.api.session.profile.ProfileService
|
import org.matrix.android.sdk.api.session.profile.ProfileService
|
||||||
import org.matrix.android.sdk.api.session.pushers.PushersService
|
import org.matrix.android.sdk.api.session.pushers.PushersService
|
||||||
|
@ -102,6 +103,7 @@ internal class DefaultSession @Inject constructor(
|
||||||
private val permalinkService: Lazy<PermalinkService>,
|
private val permalinkService: Lazy<PermalinkService>,
|
||||||
private val secureStorageService: Lazy<SecureStorageService>,
|
private val secureStorageService: Lazy<SecureStorageService>,
|
||||||
private val profileService: Lazy<ProfileService>,
|
private val profileService: Lazy<ProfileService>,
|
||||||
|
private val mediaService: Lazy<MediaService>,
|
||||||
private val widgetService: Lazy<WidgetService>,
|
private val widgetService: Lazy<WidgetService>,
|
||||||
private val syncThreadProvider: Provider<SyncThread>,
|
private val syncThreadProvider: Provider<SyncThread>,
|
||||||
private val contentUrlResolver: ContentUrlResolver,
|
private val contentUrlResolver: ContentUrlResolver,
|
||||||
|
@ -263,6 +265,8 @@ internal class DefaultSession @Inject constructor(
|
||||||
|
|
||||||
override fun widgetService(): WidgetService = widgetService.get()
|
override fun widgetService(): WidgetService = widgetService.get()
|
||||||
|
|
||||||
|
override fun mediaService(): MediaService = mediaService.get()
|
||||||
|
|
||||||
override fun integrationManagerService() = integrationManagerService
|
override fun integrationManagerService() = integrationManagerService
|
||||||
|
|
||||||
override fun callSignalingService(): CallSignalingService = callSignalingService.get()
|
override fun callSignalingService(): CallSignalingService = callSignalingService.get()
|
||||||
|
|
|
@ -40,6 +40,7 @@ import org.matrix.android.sdk.internal.session.group.GroupModule
|
||||||
import org.matrix.android.sdk.internal.session.homeserver.HomeServerCapabilitiesModule
|
import org.matrix.android.sdk.internal.session.homeserver.HomeServerCapabilitiesModule
|
||||||
import org.matrix.android.sdk.internal.session.identity.IdentityModule
|
import org.matrix.android.sdk.internal.session.identity.IdentityModule
|
||||||
import org.matrix.android.sdk.internal.session.integrationmanager.IntegrationManagerModule
|
import org.matrix.android.sdk.internal.session.integrationmanager.IntegrationManagerModule
|
||||||
|
import org.matrix.android.sdk.internal.session.media.MediaModule
|
||||||
import org.matrix.android.sdk.internal.session.openid.OpenIdModule
|
import org.matrix.android.sdk.internal.session.openid.OpenIdModule
|
||||||
import org.matrix.android.sdk.internal.session.profile.ProfileModule
|
import org.matrix.android.sdk.internal.session.profile.ProfileModule
|
||||||
import org.matrix.android.sdk.internal.session.pushers.AddHttpPusherWorker
|
import org.matrix.android.sdk.internal.session.pushers.AddHttpPusherWorker
|
||||||
|
@ -75,6 +76,7 @@ import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers
|
||||||
GroupModule::class,
|
GroupModule::class,
|
||||||
ContentModule::class,
|
ContentModule::class,
|
||||||
CacheModule::class,
|
CacheModule::class,
|
||||||
|
MediaModule::class,
|
||||||
CryptoModule::class,
|
CryptoModule::class,
|
||||||
PushersModule::class,
|
PushersModule::class,
|
||||||
OpenIdModule::class,
|
OpenIdModule::class,
|
||||||
|
|
|
@ -50,6 +50,7 @@ import org.matrix.android.sdk.internal.database.EventInsertLiveObserver
|
||||||
import org.matrix.android.sdk.internal.database.RealmSessionProvider
|
import org.matrix.android.sdk.internal.database.RealmSessionProvider
|
||||||
import org.matrix.android.sdk.internal.database.SessionRealmConfigurationFactory
|
import org.matrix.android.sdk.internal.database.SessionRealmConfigurationFactory
|
||||||
import org.matrix.android.sdk.internal.di.Authenticated
|
import org.matrix.android.sdk.internal.di.Authenticated
|
||||||
|
import org.matrix.android.sdk.internal.di.CacheDirectory
|
||||||
import org.matrix.android.sdk.internal.di.DeviceId
|
import org.matrix.android.sdk.internal.di.DeviceId
|
||||||
import org.matrix.android.sdk.internal.di.SessionDatabase
|
import org.matrix.android.sdk.internal.di.SessionDatabase
|
||||||
import org.matrix.android.sdk.internal.di.SessionDownloadsDirectory
|
import org.matrix.android.sdk.internal.di.SessionDownloadsDirectory
|
||||||
|
@ -169,9 +170,9 @@ internal abstract class SessionModule {
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@Provides
|
@Provides
|
||||||
@SessionDownloadsDirectory
|
@SessionDownloadsDirectory
|
||||||
fun providesCacheDir(@SessionId sessionId: String,
|
fun providesDownloadsCacheDir(@SessionId sessionId: String,
|
||||||
context: Context): File {
|
@CacheDirectory cacheFile: File): File {
|
||||||
return File(context.cacheDir, "downloads/$sessionId")
|
return File(cacheFile, "downloads/$sessionId")
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
|
|
|
@ -20,6 +20,9 @@ import com.squareup.moshi.Json
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
data class ContentUploadResponse(
|
internal data class ContentUploadResponse(
|
||||||
|
/**
|
||||||
|
* Required. The MXC URI to the uploaded content.
|
||||||
|
*/
|
||||||
@Json(name = "content_uri") val contentUri: String
|
@Json(name = "content_uri") val contentUri: String
|
||||||
)
|
)
|
||||||
|
|
|
@ -20,6 +20,7 @@ import android.content.Context
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import android.media.MediaMetadataRetriever
|
import android.media.MediaMetadataRetriever
|
||||||
import org.matrix.android.sdk.api.session.content.ContentAttachmentData
|
import org.matrix.android.sdk.api.session.content.ContentAttachmentData
|
||||||
|
import org.matrix.android.sdk.api.util.MimeTypes
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
|
|
||||||
|
@ -58,7 +59,7 @@ internal object ThumbnailExtractor {
|
||||||
height = thumbnailHeight,
|
height = thumbnailHeight,
|
||||||
size = thumbnailSize.toLong(),
|
size = thumbnailSize.toLong(),
|
||||||
bytes = outputStream.toByteArray(),
|
bytes = outputStream.toByteArray(),
|
||||||
mimeType = "image/jpeg"
|
mimeType = MimeTypes.Jpeg
|
||||||
)
|
)
|
||||||
thumbnail.recycle()
|
thumbnail.recycle()
|
||||||
outputStream.reset()
|
outputStream.reset()
|
||||||
|
|
|
@ -29,6 +29,7 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.MessageFileContent
|
import org.matrix.android.sdk.api.session.room.model.message.MessageFileContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.MessageImageContent
|
import org.matrix.android.sdk.api.session.room.model.message.MessageImageContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.MessageVideoContent
|
import org.matrix.android.sdk.api.session.room.model.message.MessageVideoContent
|
||||||
|
import org.matrix.android.sdk.api.util.MimeTypes
|
||||||
import org.matrix.android.sdk.internal.crypto.attachments.MXEncryptedAttachments
|
import org.matrix.android.sdk.internal.crypto.attachments.MXEncryptedAttachments
|
||||||
import org.matrix.android.sdk.internal.crypto.model.rest.EncryptedFileInfo
|
import org.matrix.android.sdk.internal.crypto.model.rest.EncryptedFileInfo
|
||||||
import org.matrix.android.sdk.internal.database.mapper.ContentMapper
|
import org.matrix.android.sdk.internal.database.mapper.ContentMapper
|
||||||
|
@ -151,7 +152,10 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
|
||||||
params.attachment.size
|
params.attachment.size
|
||||||
)
|
)
|
||||||
|
|
||||||
if (attachment.type == ContentAttachmentData.Type.IMAGE && params.compressBeforeSending) {
|
if (attachment.type == ContentAttachmentData.Type.IMAGE
|
||||||
|
// Do not compress gif
|
||||||
|
&& attachment.mimeType != MimeTypes.Gif
|
||||||
|
&& params.compressBeforeSending) {
|
||||||
fileToUpload = imageCompressor.compress(context, workingFile, MAX_IMAGE_SIZE, MAX_IMAGE_SIZE)
|
fileToUpload = imageCompressor.compress(context, workingFile, MAX_IMAGE_SIZE, MAX_IMAGE_SIZE)
|
||||||
.also { compressedFile ->
|
.also { compressedFile ->
|
||||||
// Get new Bitmap size
|
// Get new Bitmap size
|
||||||
|
@ -174,14 +178,15 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val encryptedFile: File?
|
||||||
val contentUploadResponse = if (params.isEncrypted) {
|
val contentUploadResponse = if (params.isEncrypted) {
|
||||||
Timber.v("## FileService: Encrypt file")
|
Timber.v("## FileService: Encrypt file")
|
||||||
|
|
||||||
val tmpEncrypted = File.createTempFile(UUID.randomUUID().toString(), null, context.cacheDir)
|
encryptedFile = File.createTempFile(UUID.randomUUID().toString(), null, context.cacheDir)
|
||||||
.also { filesToDelete.add(it) }
|
.also { filesToDelete.add(it) }
|
||||||
|
|
||||||
uploadedFileEncryptedFileInfo =
|
uploadedFileEncryptedFileInfo =
|
||||||
MXEncryptedAttachments.encrypt(fileToUpload.inputStream(), attachment.getSafeMimeType(), tmpEncrypted) { read, total ->
|
MXEncryptedAttachments.encrypt(fileToUpload.inputStream(), attachment.getSafeMimeType(), encryptedFile) { read, total ->
|
||||||
notifyTracker(params) {
|
notifyTracker(params) {
|
||||||
contentUploadStateTracker.setEncrypting(it, read.toLong(), total.toLong())
|
contentUploadStateTracker.setEncrypting(it, read.toLong(), total.toLong())
|
||||||
}
|
}
|
||||||
|
@ -190,18 +195,23 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
|
||||||
Timber.v("## FileService: Uploading file")
|
Timber.v("## FileService: Uploading file")
|
||||||
|
|
||||||
fileUploader
|
fileUploader
|
||||||
.uploadFile(tmpEncrypted, attachment.name, "application/octet-stream", progressListener)
|
.uploadFile(encryptedFile, attachment.name, MimeTypes.OctetStream, progressListener)
|
||||||
} else {
|
} else {
|
||||||
Timber.v("## FileService: Clear file")
|
Timber.v("## FileService: Clear file")
|
||||||
|
encryptedFile = null
|
||||||
fileUploader
|
fileUploader
|
||||||
.uploadFile(fileToUpload, attachment.name, attachment.getSafeMimeType(), progressListener)
|
.uploadFile(fileToUpload, attachment.name, attachment.getSafeMimeType(), progressListener)
|
||||||
}
|
}
|
||||||
|
|
||||||
Timber.v("## FileService: Update cache storage for ${contentUploadResponse.contentUri}")
|
Timber.v("## FileService: Update cache storage for ${contentUploadResponse.contentUri}")
|
||||||
try {
|
try {
|
||||||
context.contentResolver.openInputStream(attachment.queryUri)?.let {
|
fileService.storeDataFor(
|
||||||
fileService.storeDataFor(contentUploadResponse.contentUri, params.attachment.getSafeMimeType(), it)
|
mxcUrl = contentUploadResponse.contentUri,
|
||||||
}
|
filename = params.attachment.name,
|
||||||
|
mimeType = params.attachment.getSafeMimeType(),
|
||||||
|
originalFile = workingFile,
|
||||||
|
encryptedFile = encryptedFile
|
||||||
|
)
|
||||||
Timber.v("## FileService: cache storage updated")
|
Timber.v("## FileService: cache storage updated")
|
||||||
} catch (failure: Throwable) {
|
} catch (failure: Throwable) {
|
||||||
Timber.e(failure, "## FileService: Failed to update file cache")
|
Timber.e(failure, "## FileService: Failed to update file cache")
|
||||||
|
@ -252,7 +262,7 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
|
||||||
val encryptionResult = MXEncryptedAttachments.encryptAttachment(thumbnailData.bytes.inputStream(), thumbnailData.mimeType)
|
val encryptionResult = MXEncryptedAttachments.encryptAttachment(thumbnailData.bytes.inputStream(), thumbnailData.mimeType)
|
||||||
val contentUploadResponse = fileUploader.uploadByteArray(encryptionResult.encryptedByteArray,
|
val contentUploadResponse = fileUploader.uploadByteArray(encryptionResult.encryptedByteArray,
|
||||||
"thumb_${params.attachment.name}",
|
"thumb_${params.attachment.name}",
|
||||||
"application/octet-stream",
|
MimeTypes.OctetStream,
|
||||||
thumbnailProgressListener)
|
thumbnailProgressListener)
|
||||||
UploadThumbnailResult(
|
UploadThumbnailResult(
|
||||||
contentUploadResponse.contentUri,
|
contentUploadResponse.contentUri,
|
||||||
|
|
|
@ -22,19 +22,12 @@ import retrofit2.Call
|
||||||
import retrofit2.http.GET
|
import retrofit2.http.GET
|
||||||
|
|
||||||
internal interface CapabilitiesAPI {
|
internal interface CapabilitiesAPI {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Request the homeserver capabilities
|
* Request the homeserver capabilities
|
||||||
*/
|
*/
|
||||||
@GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "capabilities")
|
@GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "capabilities")
|
||||||
fun getCapabilities(): Call<GetCapabilitiesResult>
|
fun getCapabilities(): Call<GetCapabilitiesResult>
|
||||||
|
|
||||||
/**
|
|
||||||
* Request the upload capabilities
|
|
||||||
*/
|
|
||||||
@GET(NetworkConstants.URI_API_MEDIA_PREFIX_PATH_R0 + "config")
|
|
||||||
fun getUploadCapabilities(): Call<GetUploadCapabilitiesResult>
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Request the versions
|
* Request the versions
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -29,6 +29,8 @@ import org.matrix.android.sdk.internal.di.SessionDatabase
|
||||||
import org.matrix.android.sdk.internal.di.UserId
|
import org.matrix.android.sdk.internal.di.UserId
|
||||||
import org.matrix.android.sdk.internal.network.executeRequest
|
import org.matrix.android.sdk.internal.network.executeRequest
|
||||||
import org.matrix.android.sdk.internal.session.integrationmanager.IntegrationManagerConfigExtractor
|
import org.matrix.android.sdk.internal.session.integrationmanager.IntegrationManagerConfigExtractor
|
||||||
|
import org.matrix.android.sdk.internal.session.media.GetMediaConfigResult
|
||||||
|
import org.matrix.android.sdk.internal.session.media.MediaAPI
|
||||||
import org.matrix.android.sdk.internal.task.Task
|
import org.matrix.android.sdk.internal.task.Task
|
||||||
import org.matrix.android.sdk.internal.util.awaitTransaction
|
import org.matrix.android.sdk.internal.util.awaitTransaction
|
||||||
import org.matrix.android.sdk.internal.wellknown.GetWellknownTask
|
import org.matrix.android.sdk.internal.wellknown.GetWellknownTask
|
||||||
|
@ -40,6 +42,7 @@ internal interface GetHomeServerCapabilitiesTask : Task<Unit, Unit>
|
||||||
|
|
||||||
internal class DefaultGetHomeServerCapabilitiesTask @Inject constructor(
|
internal class DefaultGetHomeServerCapabilitiesTask @Inject constructor(
|
||||||
private val capabilitiesAPI: CapabilitiesAPI,
|
private val capabilitiesAPI: CapabilitiesAPI,
|
||||||
|
private val mediaAPI: MediaAPI,
|
||||||
@SessionDatabase private val monarchy: Monarchy,
|
@SessionDatabase private val monarchy: Monarchy,
|
||||||
private val eventBus: EventBus,
|
private val eventBus: EventBus,
|
||||||
private val getWellknownTask: GetWellknownTask,
|
private val getWellknownTask: GetWellknownTask,
|
||||||
|
@ -67,9 +70,9 @@ internal class DefaultGetHomeServerCapabilitiesTask @Inject constructor(
|
||||||
}
|
}
|
||||||
}.getOrNull()
|
}.getOrNull()
|
||||||
|
|
||||||
val uploadCapabilities = runCatching {
|
val mediaConfig = runCatching {
|
||||||
executeRequest<GetUploadCapabilitiesResult>(eventBus) {
|
executeRequest<GetMediaConfigResult>(eventBus) {
|
||||||
apiCall = capabilitiesAPI.getUploadCapabilities()
|
apiCall = mediaAPI.getMediaConfig()
|
||||||
}
|
}
|
||||||
}.getOrNull()
|
}.getOrNull()
|
||||||
|
|
||||||
|
@ -83,11 +86,11 @@ internal class DefaultGetHomeServerCapabilitiesTask @Inject constructor(
|
||||||
getWellknownTask.execute(GetWellknownTask.Params(userId, homeServerConnectionConfig))
|
getWellknownTask.execute(GetWellknownTask.Params(userId, homeServerConnectionConfig))
|
||||||
}.getOrNull()
|
}.getOrNull()
|
||||||
|
|
||||||
insertInDb(capabilities, uploadCapabilities, versions, wellknownResult)
|
insertInDb(capabilities, mediaConfig, versions, wellknownResult)
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun insertInDb(getCapabilitiesResult: GetCapabilitiesResult?,
|
private suspend fun insertInDb(getCapabilitiesResult: GetCapabilitiesResult?,
|
||||||
getUploadCapabilitiesResult: GetUploadCapabilitiesResult?,
|
getMediaConfigResult: GetMediaConfigResult?,
|
||||||
getVersionResult: Versions?,
|
getVersionResult: Versions?,
|
||||||
getWellknownResult: WellknownResult?) {
|
getWellknownResult: WellknownResult?) {
|
||||||
monarchy.awaitTransaction { realm ->
|
monarchy.awaitTransaction { realm ->
|
||||||
|
@ -97,8 +100,8 @@ internal class DefaultGetHomeServerCapabilitiesTask @Inject constructor(
|
||||||
homeServerCapabilitiesEntity.canChangePassword = getCapabilitiesResult.canChangePassword()
|
homeServerCapabilitiesEntity.canChangePassword = getCapabilitiesResult.canChangePassword()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (getUploadCapabilitiesResult != null) {
|
if (getMediaConfigResult != null) {
|
||||||
homeServerCapabilitiesEntity.maxUploadFileSize = getUploadCapabilitiesResult.maxUploadSize
|
homeServerCapabilitiesEntity.maxUploadFileSize = getMediaConfigResult.maxUploadSize
|
||||||
?: HomeServerCapabilities.MAX_UPLOAD_FILE_SIZE_UNKNOWN
|
?: HomeServerCapabilities.MAX_UPLOAD_FILE_SIZE_UNKNOWN
|
||||||
}
|
}
|
||||||
|
|
|
@ -93,7 +93,7 @@ internal class DefaultIdentityBulkLookupTask @Inject constructor(
|
||||||
} catch (failure: Throwable) {
|
} catch (failure: Throwable) {
|
||||||
// Catch invalid hash pepper and retry
|
// Catch invalid hash pepper and retry
|
||||||
if (canRetry && failure is Failure.ServerError && failure.error.code == MatrixError.M_INVALID_PEPPER) {
|
if (canRetry && failure is Failure.ServerError && failure.error.code == MatrixError.M_INVALID_PEPPER) {
|
||||||
// This is not documented, by the error can contain the new pepper!
|
// This is not documented, but the error can contain the new pepper!
|
||||||
if (!failure.error.newLookupPepper.isNullOrEmpty()) {
|
if (!failure.error.newLookupPepper.isNullOrEmpty()) {
|
||||||
// Store it and use it right now
|
// Store it and use it right now
|
||||||
hashDetailResponse.copy(pepper = failure.error.newLookupPepper)
|
hashDetailResponse.copy(pepper = failure.error.newLookupPepper)
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
/*
|
||||||
|
* 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.session.media
|
||||||
|
|
||||||
|
import com.zhuinden.monarchy.Monarchy
|
||||||
|
import io.realm.kotlin.where
|
||||||
|
import org.matrix.android.sdk.internal.database.model.PreviewUrlCacheEntity
|
||||||
|
import org.matrix.android.sdk.internal.di.SessionDatabase
|
||||||
|
import org.matrix.android.sdk.internal.task.Task
|
||||||
|
import org.matrix.android.sdk.internal.util.awaitTransaction
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
internal interface ClearPreviewUrlCacheTask : Task<Unit, Unit>
|
||||||
|
|
||||||
|
internal class DefaultClearPreviewUrlCacheTask @Inject constructor(
|
||||||
|
@SessionDatabase private val monarchy: Monarchy
|
||||||
|
) : ClearPreviewUrlCacheTask {
|
||||||
|
|
||||||
|
override suspend fun execute(params: Unit) {
|
||||||
|
monarchy.awaitTransaction { realm ->
|
||||||
|
realm.where<PreviewUrlCacheEntity>()
|
||||||
|
.findAll()
|
||||||
|
.deleteAllFromRealm()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
/*
|
||||||
|
* 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.session.media
|
||||||
|
|
||||||
|
import androidx.collection.LruCache
|
||||||
|
import org.matrix.android.sdk.api.cache.CacheStrategy
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.Event
|
||||||
|
import org.matrix.android.sdk.api.session.media.MediaService
|
||||||
|
import org.matrix.android.sdk.api.session.media.PreviewUrlData
|
||||||
|
import org.matrix.android.sdk.api.util.JsonDict
|
||||||
|
import org.matrix.android.sdk.internal.util.getOrPut
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
internal class DefaultMediaService @Inject constructor(
|
||||||
|
private val clearPreviewUrlCacheTask: ClearPreviewUrlCacheTask,
|
||||||
|
private val getPreviewUrlTask: GetPreviewUrlTask,
|
||||||
|
private val getRawPreviewUrlTask: GetRawPreviewUrlTask,
|
||||||
|
private val urlsExtractor: UrlsExtractor
|
||||||
|
) : MediaService {
|
||||||
|
// Cache of extracted URLs
|
||||||
|
private val extractedUrlsCache = LruCache<String, List<String>>(1_000)
|
||||||
|
|
||||||
|
override fun extractUrls(event: Event): List<String> {
|
||||||
|
return extractedUrlsCache.getOrPut(event.cacheKey()) { urlsExtractor.extract(event) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Event.cacheKey() = "${eventId ?: ""}-${roomId ?: ""}"
|
||||||
|
|
||||||
|
override suspend fun getRawPreviewUrl(url: String, timestamp: Long?): JsonDict {
|
||||||
|
return getRawPreviewUrlTask.execute(GetRawPreviewUrlTask.Params(url, timestamp))
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getPreviewUrl(url: String, timestamp: Long?, cacheStrategy: CacheStrategy): PreviewUrlData {
|
||||||
|
return getPreviewUrlTask.execute(GetPreviewUrlTask.Params(url, timestamp, cacheStrategy))
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun clearCache() {
|
||||||
|
extractedUrlsCache.evictAll()
|
||||||
|
clearPreviewUrlCacheTask.execute(Unit)
|
||||||
|
}
|
||||||
|
}
|
|
@ -14,13 +14,13 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.matrix.android.sdk.internal.session.homeserver
|
package org.matrix.android.sdk.internal.session.media
|
||||||
|
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
@JsonClass(generateAdapter = true)
|
||||||
internal data class GetUploadCapabilitiesResult(
|
internal data class GetMediaConfigResult(
|
||||||
/**
|
/**
|
||||||
* The maximum size an upload can be in bytes. Clients SHOULD use this as a guide when uploading content.
|
* The maximum size an upload can be in bytes. Clients SHOULD use this as a guide when uploading content.
|
||||||
* If not listed or null, the size limit should be treated as unknown.
|
* If not listed or null, the size limit should be treated as unknown.
|
|
@ -0,0 +1,122 @@
|
||||||
|
/*
|
||||||
|
* 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.session.media
|
||||||
|
|
||||||
|
import com.zhuinden.monarchy.Monarchy
|
||||||
|
import org.greenrobot.eventbus.EventBus
|
||||||
|
import org.matrix.android.sdk.api.cache.CacheStrategy
|
||||||
|
import org.matrix.android.sdk.api.session.media.PreviewUrlData
|
||||||
|
import org.matrix.android.sdk.api.util.JsonDict
|
||||||
|
import org.matrix.android.sdk.internal.database.model.PreviewUrlCacheEntity
|
||||||
|
import org.matrix.android.sdk.internal.database.query.get
|
||||||
|
import org.matrix.android.sdk.internal.database.query.getOrCreate
|
||||||
|
import org.matrix.android.sdk.internal.di.SessionDatabase
|
||||||
|
import org.matrix.android.sdk.internal.network.executeRequest
|
||||||
|
import org.matrix.android.sdk.internal.task.Task
|
||||||
|
import org.matrix.android.sdk.internal.util.awaitTransaction
|
||||||
|
import java.util.Date
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
internal interface GetPreviewUrlTask : Task<GetPreviewUrlTask.Params, PreviewUrlData> {
|
||||||
|
data class Params(
|
||||||
|
val url: String,
|
||||||
|
val timestamp: Long?,
|
||||||
|
val cacheStrategy: CacheStrategy
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class DefaultGetPreviewUrlTask @Inject constructor(
|
||||||
|
private val mediaAPI: MediaAPI,
|
||||||
|
private val eventBus: EventBus,
|
||||||
|
@SessionDatabase private val monarchy: Monarchy
|
||||||
|
) : GetPreviewUrlTask {
|
||||||
|
|
||||||
|
override suspend fun execute(params: GetPreviewUrlTask.Params): PreviewUrlData {
|
||||||
|
return when (params.cacheStrategy) {
|
||||||
|
CacheStrategy.NoCache -> doRequest(params.url, params.timestamp)
|
||||||
|
is CacheStrategy.TtlCache -> doRequestWithCache(
|
||||||
|
params.url,
|
||||||
|
params.timestamp,
|
||||||
|
params.cacheStrategy.validityDurationInMillis,
|
||||||
|
params.cacheStrategy.strict
|
||||||
|
)
|
||||||
|
CacheStrategy.InfiniteCache -> doRequestWithCache(
|
||||||
|
params.url,
|
||||||
|
params.timestamp,
|
||||||
|
Long.MAX_VALUE,
|
||||||
|
true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun doRequest(url: String, timestamp: Long?): PreviewUrlData {
|
||||||
|
return executeRequest<JsonDict>(eventBus) {
|
||||||
|
apiCall = mediaAPI.getPreviewUrlData(url, timestamp)
|
||||||
|
}
|
||||||
|
.toPreviewUrlData(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun JsonDict.toPreviewUrlData(url: String): PreviewUrlData {
|
||||||
|
return PreviewUrlData(
|
||||||
|
url = (get("og:url") as? String) ?: url,
|
||||||
|
siteName = get("og:site_name") as? String,
|
||||||
|
title = get("og:title") as? String,
|
||||||
|
description = get("og:description") as? String,
|
||||||
|
mxcUrl = get("og:image") as? String
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun doRequestWithCache(url: String, timestamp: Long?, validityDurationInMillis: Long, strict: Boolean): PreviewUrlData {
|
||||||
|
// Get data from cache
|
||||||
|
var dataFromCache: PreviewUrlData? = null
|
||||||
|
var isCacheValid = false
|
||||||
|
monarchy.doWithRealm { realm ->
|
||||||
|
val entity = PreviewUrlCacheEntity.get(realm, url)
|
||||||
|
dataFromCache = entity?.toDomain()
|
||||||
|
isCacheValid = entity != null && Date().time < entity.lastUpdatedTimestamp + validityDurationInMillis
|
||||||
|
}
|
||||||
|
|
||||||
|
val finalDataFromCache = dataFromCache
|
||||||
|
if (finalDataFromCache != null && isCacheValid) {
|
||||||
|
return finalDataFromCache
|
||||||
|
}
|
||||||
|
|
||||||
|
// No cache or outdated cache
|
||||||
|
val data = try {
|
||||||
|
doRequest(url, timestamp)
|
||||||
|
} catch (throwable: Throwable) {
|
||||||
|
// In case of error, we can return value from cache even if outdated
|
||||||
|
return finalDataFromCache
|
||||||
|
?.takeIf { !strict }
|
||||||
|
?: throw throwable
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store cache
|
||||||
|
monarchy.awaitTransaction { realm ->
|
||||||
|
val previewUrlCacheEntity = PreviewUrlCacheEntity.getOrCreate(realm, url)
|
||||||
|
previewUrlCacheEntity.urlFromServer = data.url
|
||||||
|
previewUrlCacheEntity.siteName = data.siteName
|
||||||
|
previewUrlCacheEntity.title = data.title
|
||||||
|
previewUrlCacheEntity.description = data.description
|
||||||
|
previewUrlCacheEntity.mxcUrl = data.mxcUrl
|
||||||
|
|
||||||
|
previewUrlCacheEntity.lastUpdatedTimestamp = Date().time
|
||||||
|
}
|
||||||
|
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
/*
|
||||||
|
* 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.session.media
|
||||||
|
|
||||||
|
import org.greenrobot.eventbus.EventBus
|
||||||
|
import org.matrix.android.sdk.api.util.JsonDict
|
||||||
|
import org.matrix.android.sdk.internal.network.executeRequest
|
||||||
|
import org.matrix.android.sdk.internal.task.Task
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
internal interface GetRawPreviewUrlTask : Task<GetRawPreviewUrlTask.Params, JsonDict> {
|
||||||
|
data class Params(
|
||||||
|
val url: String,
|
||||||
|
val timestamp: Long?
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class DefaultGetRawPreviewUrlTask @Inject constructor(
|
||||||
|
private val mediaAPI: MediaAPI,
|
||||||
|
private val eventBus: EventBus
|
||||||
|
) : GetRawPreviewUrlTask {
|
||||||
|
|
||||||
|
override suspend fun execute(params: GetRawPreviewUrlTask.Params): JsonDict {
|
||||||
|
return executeRequest(eventBus) {
|
||||||
|
apiCall = mediaAPI.getPreviewUrlData(params.url, params.timestamp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
/*
|
||||||
|
* 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.session.media
|
||||||
|
|
||||||
|
import org.matrix.android.sdk.api.util.JsonDict
|
||||||
|
import org.matrix.android.sdk.internal.network.NetworkConstants
|
||||||
|
import retrofit2.Call
|
||||||
|
import retrofit2.http.GET
|
||||||
|
import retrofit2.http.Query
|
||||||
|
|
||||||
|
internal interface MediaAPI {
|
||||||
|
/**
|
||||||
|
* Retrieve the configuration of the content repository
|
||||||
|
* Ref: https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-media-r0-config
|
||||||
|
*/
|
||||||
|
@GET(NetworkConstants.URI_API_MEDIA_PREFIX_PATH_R0 + "config")
|
||||||
|
fun getMediaConfig(): Call<GetMediaConfigResult>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get information about a URL for the client. Typically this is called when a client
|
||||||
|
* sees a URL in a message and wants to render a preview for the user.
|
||||||
|
* Ref: https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-media-r0-preview-url
|
||||||
|
* @param url Required. The URL to get a preview of.
|
||||||
|
* @param ts The preferred point in time to return a preview for. The server may return a newer version
|
||||||
|
* if it does not have the requested version available.
|
||||||
|
*/
|
||||||
|
@GET(NetworkConstants.URI_API_MEDIA_PREFIX_PATH_R0 + "preview_url")
|
||||||
|
fun getPreviewUrlData(@Query("url") url: String, @Query("ts") ts: Long?): Call<JsonDict>
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
/*
|
||||||
|
* 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.session.media
|
||||||
|
|
||||||
|
import dagger.Binds
|
||||||
|
import dagger.Module
|
||||||
|
import dagger.Provides
|
||||||
|
import org.matrix.android.sdk.api.session.media.MediaService
|
||||||
|
import org.matrix.android.sdk.internal.session.SessionScope
|
||||||
|
import retrofit2.Retrofit
|
||||||
|
|
||||||
|
@Module
|
||||||
|
internal abstract class MediaModule {
|
||||||
|
|
||||||
|
@Module
|
||||||
|
companion object {
|
||||||
|
@Provides
|
||||||
|
@JvmStatic
|
||||||
|
@SessionScope
|
||||||
|
fun providesMediaAPI(retrofit: Retrofit): MediaAPI {
|
||||||
|
return retrofit.create(MediaAPI::class.java)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindMediaService(service: DefaultMediaService): MediaService
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindGetRawPreviewUrlTask(task: DefaultGetRawPreviewUrlTask): GetRawPreviewUrlTask
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindGetPreviewUrlTask(task: DefaultGetPreviewUrlTask): GetPreviewUrlTask
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindClearMediaCacheTask(task: DefaultClearPreviewUrlCacheTask): ClearPreviewUrlCacheTask
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
/*
|
||||||
|
* 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.session.media
|
||||||
|
|
||||||
|
import org.matrix.android.sdk.api.session.media.PreviewUrlData
|
||||||
|
import org.matrix.android.sdk.internal.database.model.PreviewUrlCacheEntity
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PreviewUrlCacheEntity -> PreviewUrlData
|
||||||
|
*/
|
||||||
|
internal fun PreviewUrlCacheEntity.toDomain() = PreviewUrlData(
|
||||||
|
url = urlFromServer ?: url,
|
||||||
|
siteName = siteName,
|
||||||
|
title = title,
|
||||||
|
description = description,
|
||||||
|
mxcUrl = mxcUrl
|
||||||
|
)
|
|
@ -0,0 +1,48 @@
|
||||||
|
/*
|
||||||
|
* 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.session.media
|
||||||
|
|
||||||
|
import android.util.Patterns
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.Event
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.toModel
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.message.MessageContent
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.message.MessageType
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
internal class UrlsExtractor @Inject constructor() {
|
||||||
|
// Sadly Patterns.WEB_URL_WITH_PROTOCOL is not public so filter the protocol later
|
||||||
|
private val urlRegex = Patterns.WEB_URL.toRegex()
|
||||||
|
|
||||||
|
fun extract(event: Event): List<String> {
|
||||||
|
return event.takeIf { it.getClearType() == EventType.MESSAGE }
|
||||||
|
?.getClearContent()
|
||||||
|
?.toModel<MessageContent>()
|
||||||
|
?.takeIf {
|
||||||
|
it.msgType == MessageType.MSGTYPE_TEXT
|
||||||
|
|| it.msgType == MessageType.MSGTYPE_NOTICE
|
||||||
|
|| it.msgType == MessageType.MSGTYPE_EMOTE
|
||||||
|
}
|
||||||
|
?.body
|
||||||
|
?.let { urlRegex.findAll(it) }
|
||||||
|
?.map { it.value }
|
||||||
|
?.filter { it.startsWith("https://") || it.startsWith("http://") }
|
||||||
|
?.distinct()
|
||||||
|
?.toList()
|
||||||
|
.orEmpty()
|
||||||
|
}
|
||||||
|
}
|
|
@ -26,6 +26,7 @@ import org.matrix.android.sdk.api.session.identity.ThreePid
|
||||||
import org.matrix.android.sdk.api.session.profile.ProfileService
|
import org.matrix.android.sdk.api.session.profile.ProfileService
|
||||||
import org.matrix.android.sdk.api.util.Cancelable
|
import org.matrix.android.sdk.api.util.Cancelable
|
||||||
import org.matrix.android.sdk.api.util.JsonDict
|
import org.matrix.android.sdk.api.util.JsonDict
|
||||||
|
import org.matrix.android.sdk.api.util.MimeTypes
|
||||||
import org.matrix.android.sdk.api.util.Optional
|
import org.matrix.android.sdk.api.util.Optional
|
||||||
import org.matrix.android.sdk.internal.database.model.PendingThreePidEntity
|
import org.matrix.android.sdk.internal.database.model.PendingThreePidEntity
|
||||||
import org.matrix.android.sdk.internal.database.model.UserThreePidEntity
|
import org.matrix.android.sdk.internal.database.model.UserThreePidEntity
|
||||||
|
@ -80,7 +81,7 @@ internal class DefaultProfileService @Inject constructor(private val taskExecuto
|
||||||
|
|
||||||
override fun updateAvatar(userId: String, newAvatarUri: Uri, fileName: String, matrixCallback: MatrixCallback<Unit>): Cancelable {
|
override fun updateAvatar(userId: String, newAvatarUri: Uri, fileName: String, matrixCallback: MatrixCallback<Unit>): Cancelable {
|
||||||
return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, matrixCallback) {
|
return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, matrixCallback) {
|
||||||
val response = fileUploader.uploadFromUri(newAvatarUri, fileName, "image/jpeg")
|
val response = fileUploader.uploadFromUri(newAvatarUri, fileName, MimeTypes.Jpeg)
|
||||||
setAvatarUrlTask.execute(SetAvatarUrlTask.Params(userId = userId, newAvatarUrl = response.contentUri))
|
setAvatarUrlTask.execute(SetAvatarUrlTask.Params(userId = userId, newAvatarUrl = response.contentUri))
|
||||||
userStore.updateAvatar(userId, response.contentUri)
|
userStore.updateAvatar(userId, response.contentUri)
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@ import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.Transformations
|
import androidx.lifecycle.Transformations
|
||||||
import com.zhuinden.monarchy.Monarchy
|
import com.zhuinden.monarchy.Monarchy
|
||||||
import org.matrix.android.sdk.api.MatrixCallback
|
import org.matrix.android.sdk.api.MatrixCallback
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.Event
|
||||||
import org.matrix.android.sdk.api.session.room.Room
|
import org.matrix.android.sdk.api.session.room.Room
|
||||||
import org.matrix.android.sdk.api.session.room.RoomService
|
import org.matrix.android.sdk.api.session.room.RoomService
|
||||||
import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams
|
import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams
|
||||||
|
@ -27,6 +28,7 @@ import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary
|
import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||||
import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
|
import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
|
||||||
|
import org.matrix.android.sdk.api.session.room.peeking.PeekResult
|
||||||
import org.matrix.android.sdk.api.util.Cancelable
|
import org.matrix.android.sdk.api.util.Cancelable
|
||||||
import org.matrix.android.sdk.api.util.Optional
|
import org.matrix.android.sdk.api.util.Optional
|
||||||
import org.matrix.android.sdk.api.util.toOptional
|
import org.matrix.android.sdk.api.util.toOptional
|
||||||
|
@ -35,10 +37,13 @@ import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntityFie
|
||||||
import org.matrix.android.sdk.internal.di.SessionDatabase
|
import org.matrix.android.sdk.internal.di.SessionDatabase
|
||||||
import org.matrix.android.sdk.internal.session.room.alias.DeleteRoomAliasTask
|
import org.matrix.android.sdk.internal.session.room.alias.DeleteRoomAliasTask
|
||||||
import org.matrix.android.sdk.internal.session.room.alias.GetRoomIdByAliasTask
|
import org.matrix.android.sdk.internal.session.room.alias.GetRoomIdByAliasTask
|
||||||
|
import org.matrix.android.sdk.internal.session.room.alias.RoomAliasDescription
|
||||||
import org.matrix.android.sdk.internal.session.room.create.CreateRoomTask
|
import org.matrix.android.sdk.internal.session.room.create.CreateRoomTask
|
||||||
import org.matrix.android.sdk.internal.session.room.membership.RoomChangeMembershipStateDataSource
|
import org.matrix.android.sdk.internal.session.room.membership.RoomChangeMembershipStateDataSource
|
||||||
import org.matrix.android.sdk.internal.session.room.membership.RoomMemberHelper
|
import org.matrix.android.sdk.internal.session.room.membership.RoomMemberHelper
|
||||||
import org.matrix.android.sdk.internal.session.room.membership.joining.JoinRoomTask
|
import org.matrix.android.sdk.internal.session.room.membership.joining.JoinRoomTask
|
||||||
|
import org.matrix.android.sdk.internal.session.room.peeking.PeekRoomTask
|
||||||
|
import org.matrix.android.sdk.internal.session.room.peeking.ResolveRoomStateTask
|
||||||
import org.matrix.android.sdk.internal.session.room.read.MarkAllRoomsReadTask
|
import org.matrix.android.sdk.internal.session.room.read.MarkAllRoomsReadTask
|
||||||
import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryDataSource
|
import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryDataSource
|
||||||
import org.matrix.android.sdk.internal.session.user.accountdata.UpdateBreadcrumbsTask
|
import org.matrix.android.sdk.internal.session.user.accountdata.UpdateBreadcrumbsTask
|
||||||
|
@ -55,6 +60,8 @@ internal class DefaultRoomService @Inject constructor(
|
||||||
private val updateBreadcrumbsTask: UpdateBreadcrumbsTask,
|
private val updateBreadcrumbsTask: UpdateBreadcrumbsTask,
|
||||||
private val roomIdByAliasTask: GetRoomIdByAliasTask,
|
private val roomIdByAliasTask: GetRoomIdByAliasTask,
|
||||||
private val deleteRoomAliasTask: DeleteRoomAliasTask,
|
private val deleteRoomAliasTask: DeleteRoomAliasTask,
|
||||||
|
private val resolveRoomStateTask: ResolveRoomStateTask,
|
||||||
|
private val peekRoomTask: PeekRoomTask,
|
||||||
private val roomGetter: RoomGetter,
|
private val roomGetter: RoomGetter,
|
||||||
private val roomSummaryDataSource: RoomSummaryDataSource,
|
private val roomSummaryDataSource: RoomSummaryDataSource,
|
||||||
private val roomChangeMembershipStateDataSource: RoomChangeMembershipStateDataSource,
|
private val roomChangeMembershipStateDataSource: RoomChangeMembershipStateDataSource,
|
||||||
|
@ -119,7 +126,7 @@ internal class DefaultRoomService @Inject constructor(
|
||||||
.executeBy(taskExecutor)
|
.executeBy(taskExecutor)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getRoomIdByAlias(roomAlias: String, searchOnServer: Boolean, callback: MatrixCallback<Optional<String>>): Cancelable {
|
override fun getRoomIdByAlias(roomAlias: String, searchOnServer: Boolean, callback: MatrixCallback<Optional<RoomAliasDescription>>): Cancelable {
|
||||||
return roomIdByAliasTask
|
return roomIdByAliasTask
|
||||||
.configureWith(GetRoomIdByAliasTask.Params(roomAlias, searchOnServer)) {
|
.configureWith(GetRoomIdByAliasTask.Params(roomAlias, searchOnServer)) {
|
||||||
this.callback = callback
|
this.callback = callback
|
||||||
|
@ -154,4 +161,20 @@ internal class DefaultRoomService @Inject constructor(
|
||||||
results.firstOrNull().toOptional()
|
results.firstOrNull().toOptional()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getRoomState(roomId: String, callback: MatrixCallback<List<Event>>) {
|
||||||
|
resolveRoomStateTask
|
||||||
|
.configureWith(ResolveRoomStateTask.Params(roomId)) {
|
||||||
|
this.callback = callback
|
||||||
|
}
|
||||||
|
.executeBy(taskExecutor)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun peekRoom(roomIdOrAlias: String, callback: MatrixCallback<PeekResult>) {
|
||||||
|
peekRoomTask
|
||||||
|
.configureWith(PeekRoomTask.Params(roomIdOrAlias)) {
|
||||||
|
this.callback = callback
|
||||||
|
}
|
||||||
|
.executeBy(taskExecutor)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -183,7 +183,7 @@ internal interface RoomAPI {
|
||||||
@Body body: ThreePidInviteBody): Call<Unit>
|
@Body body: ThreePidInviteBody): Call<Unit>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send a generic state events
|
* Send a generic state event
|
||||||
*
|
*
|
||||||
* @param roomId the room id.
|
* @param roomId the room id.
|
||||||
* @param stateEventType the state event type
|
* @param stateEventType the state event type
|
||||||
|
@ -195,7 +195,7 @@ internal interface RoomAPI {
|
||||||
@Body params: JsonDict): Call<Unit>
|
@Body params: JsonDict): Call<Unit>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send a generic state events
|
* Send a generic state event
|
||||||
*
|
*
|
||||||
* @param roomId the room id.
|
* @param roomId the room id.
|
||||||
* @param stateEventType the state event type
|
* @param stateEventType the state event type
|
||||||
|
@ -208,6 +208,13 @@ internal interface RoomAPI {
|
||||||
@Path("state_key") stateKey: String,
|
@Path("state_key") stateKey: String,
|
||||||
@Body params: JsonDict): Call<Unit>
|
@Body params: JsonDict): Call<Unit>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get state events of a room
|
||||||
|
* Ref: https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-rooms-roomid-state
|
||||||
|
*/
|
||||||
|
@GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/state")
|
||||||
|
fun getRoomState(@Path("roomId") roomId: String) : Call<List<Event>>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send a relation event to a room.
|
* Send a relation event to a room.
|
||||||
*
|
*
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue