Merge pull request #6467 from vector-im/feature/adm/share-text-crash

Crash - Sharing text
This commit is contained in:
Adam Brown 2022-07-06 11:24:56 +01:00 committed by GitHub
commit 89348995c2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 109 additions and 80 deletions

1
changelog.d/6451.bugfix Normal file
View file

@ -0,0 +1 @@
Fixes crash when sharing plain text, such as a url

View file

@ -45,7 +45,6 @@ class AttachmentsHelper(val context: Context, val callback: Callback) : Restorab
} }
fun onContentAttachmentsReady(attachments: List<ContentAttachmentData>) fun onContentAttachmentsReady(attachments: List<ContentAttachmentData>)
fun onAttachmentsProcessFailed()
} }
// Capture path allows to handle camera image picking. It must be restored if the activity gets killed. // Capture path allows to handle camera image picking. It must be restored if the activity gets killed.
@ -188,41 +187,4 @@ class AttachmentsHelper(val context: Context, val callback: Callback) : Restorab
.map { it.toContentAttachmentData() } .map { it.toContentAttachmentData() }
) )
} }
/**
* This methods aims to handle share intent.
*
* @return true if it can handle the intent data, false otherwise
*/
fun handleShareIntent(context: Context, intent: Intent): Boolean {
val type = intent.resolveType(context) ?: return false
if (type.startsWith("image")) {
callback.onContentAttachmentsReady(
MultiPicker.get(MultiPicker.IMAGE).getIncomingFiles(context, intent).map {
it.toContentAttachmentData()
}
)
} else if (type.startsWith("video")) {
callback.onContentAttachmentsReady(
MultiPicker.get(MultiPicker.VIDEO).getIncomingFiles(context, intent).map {
it.toContentAttachmentData()
}
)
} else if (type.startsWith("audio")) {
callback.onContentAttachmentsReady(
MultiPicker.get(MultiPicker.AUDIO).getIncomingFiles(context, intent).map {
it.toContentAttachmentData()
}
)
} else if (type.startsWith("application") || type.startsWith("file") || type.startsWith("text") || type.startsWith("*")) {
callback.onContentAttachmentsReady(
MultiPicker.get(MultiPicker.FILE).getIncomingFiles(context, intent).map {
it.toContentAttachmentData()
}
)
} else {
return false
}
return true
}
} }

View file

@ -0,0 +1,82 @@
/*
* Copyright (c) 2022 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.features.attachments
import android.content.Context
import android.content.Intent
import im.vector.lib.multipicker.MultiPicker
import org.matrix.android.sdk.api.session.content.ContentAttachmentData
import javax.inject.Inject
class ShareIntentHandler @Inject constructor() {
/**
* This methods aims to handle incoming share intents.
*
* @return true if it can handle the intent data, false otherwise
*/
fun handleIncomingShareIntent(context: Context, intent: Intent, onFile: (List<ContentAttachmentData>) -> Unit, onPlainText: (String) -> Unit): Boolean {
val type = intent.resolveType(context) ?: return false
return when {
type == "text/plain" -> handlePlainText(intent, onPlainText)
type.startsWith("image") -> {
onFile(
MultiPicker.get(MultiPicker.IMAGE).getIncomingFiles(context, intent).map {
it.toContentAttachmentData()
}
)
true
}
type.startsWith("video") -> {
onFile(
MultiPicker.get(MultiPicker.VIDEO).getIncomingFiles(context, intent).map {
it.toContentAttachmentData()
}
)
true
}
type.startsWith("audio") -> {
onFile(
MultiPicker.get(MultiPicker.AUDIO).getIncomingFiles(context, intent).map {
it.toContentAttachmentData()
}
)
true
}
type.startsWith("application") || type.startsWith("file") || type.startsWith("text") || type.startsWith("*") -> {
onFile(
MultiPicker.get(MultiPicker.FILE).getIncomingFiles(context, intent).map {
it.toContentAttachmentData()
}
)
true
}
else -> false
}
}
private fun handlePlainText(intent: Intent, onPlainText: (String) -> Unit): Boolean {
val content = intent.getCharSequenceExtra(Intent.EXTRA_TEXT)?.toString()
return if (content?.isNotEmpty() == true) {
onPlainText(content)
true
} else {
false
}
}
}

View file

@ -73,6 +73,7 @@ import im.vector.app.core.animations.play
import im.vector.app.core.dialogs.ConfirmationDialogBuilder import im.vector.app.core.dialogs.ConfirmationDialogBuilder
import im.vector.app.core.dialogs.GalleryOrCameraDialogHelper import im.vector.app.core.dialogs.GalleryOrCameraDialogHelper
import im.vector.app.core.epoxy.LayoutManagerStateRestorer import im.vector.app.core.epoxy.LayoutManagerStateRestorer
import im.vector.app.core.error.fatalError
import im.vector.app.core.extensions.cleanup import im.vector.app.core.extensions.cleanup
import im.vector.app.core.extensions.containsRtLOverride import im.vector.app.core.extensions.containsRtLOverride
import im.vector.app.core.extensions.ensureEndsLeftToRight import im.vector.app.core.extensions.ensureEndsLeftToRight
@ -130,6 +131,7 @@ import im.vector.app.features.analytics.plan.MobileScreen
import im.vector.app.features.attachments.AttachmentTypeSelectorView import im.vector.app.features.attachments.AttachmentTypeSelectorView
import im.vector.app.features.attachments.AttachmentsHelper import im.vector.app.features.attachments.AttachmentsHelper
import im.vector.app.features.attachments.ContactAttachment import im.vector.app.features.attachments.ContactAttachment
import im.vector.app.features.attachments.ShareIntentHandler
import im.vector.app.features.attachments.preview.AttachmentsPreviewActivity import im.vector.app.features.attachments.preview.AttachmentsPreviewActivity
import im.vector.app.features.attachments.preview.AttachmentsPreviewArgs import im.vector.app.features.attachments.preview.AttachmentsPreviewArgs
import im.vector.app.features.attachments.toGroupedContentAttachmentData import im.vector.app.features.attachments.toGroupedContentAttachmentData
@ -272,6 +274,7 @@ class TimelineFragment @Inject constructor(
private val pillsPostProcessorFactory: PillsPostProcessor.Factory, private val pillsPostProcessorFactory: PillsPostProcessor.Factory,
private val callManager: WebRtcCallManager, private val callManager: WebRtcCallManager,
private val audioMessagePlaybackTracker: AudioMessagePlaybackTracker, private val audioMessagePlaybackTracker: AudioMessagePlaybackTracker,
private val shareIntentHandler: ShareIntentHandler,
private val clock: Clock private val clock: Clock
) : ) :
VectorBaseFragment<FragmentTimelineBinding>(), VectorBaseFragment<FragmentTimelineBinding>(),
@ -1616,7 +1619,9 @@ class TimelineFragment @Inject constructor(
private fun sendUri(uri: Uri): Boolean { private fun sendUri(uri: Uri): Boolean {
val shareIntent = Intent(Intent.ACTION_SEND, uri) val shareIntent = Intent(Intent.ACTION_SEND, uri)
val isHandled = attachmentsHelper.handleShareIntent(requireContext(), shareIntent) val isHandled = shareIntentHandler.handleIncomingShareIntent(requireContext(), shareIntent, ::onContentAttachmentsReady, onPlainText = {
fatalError("Should not happen as we're generating a File based share Intent", vectorPreferences.failFast())
})
if (!isHandled) { if (!isHandled) {
Toast.makeText(requireContext(), R.string.error_handling_incoming_share, Toast.LENGTH_SHORT).show() Toast.makeText(requireContext(), R.string.error_handling_incoming_share, Toast.LENGTH_SHORT).show()
} }
@ -2623,10 +2628,6 @@ class TimelineFragment @Inject constructor(
} }
} }
override fun onAttachmentsProcessFailed() {
Toast.makeText(requireContext(), R.string.error_attachment, Toast.LENGTH_SHORT).show()
}
override fun onContactAttachmentReady(contactAttachment: ContactAttachment) { override fun onContactAttachmentReady(contactAttachment: ContactAttachment) {
super.onContactAttachmentReady(contactAttachment) super.onContactAttachmentReady(contactAttachment)
val formattedContact = contactAttachment.toHumanReadable() val formattedContact = contactAttachment.toHumanReadable()

View file

@ -17,7 +17,6 @@
package im.vector.app.features.share package im.vector.app.features.share
import android.app.Activity import android.app.Activity
import android.content.ClipDescription
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
@ -38,10 +37,9 @@ import im.vector.app.core.extensions.registerStartForActivityResult
import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.databinding.FragmentIncomingShareBinding import im.vector.app.databinding.FragmentIncomingShareBinding
import im.vector.app.features.analytics.plan.ViewRoom import im.vector.app.features.analytics.plan.ViewRoom
import im.vector.app.features.attachments.AttachmentsHelper import im.vector.app.features.attachments.ShareIntentHandler
import im.vector.app.features.attachments.preview.AttachmentsPreviewActivity import im.vector.app.features.attachments.preview.AttachmentsPreviewActivity
import im.vector.app.features.attachments.preview.AttachmentsPreviewArgs import im.vector.app.features.attachments.preview.AttachmentsPreviewArgs
import org.matrix.android.sdk.api.session.content.ContentAttachmentData
import org.matrix.android.sdk.api.session.getRoomSummary import org.matrix.android.sdk.api.session.getRoomSummary
import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.model.RoomSummary
import javax.inject.Inject import javax.inject.Inject
@ -52,13 +50,12 @@ import javax.inject.Inject
*/ */
class IncomingShareFragment @Inject constructor( class IncomingShareFragment @Inject constructor(
private val incomingShareController: IncomingShareController, private val incomingShareController: IncomingShareController,
private val sessionHolder: ActiveSessionHolder private val sessionHolder: ActiveSessionHolder,
private val shareIntentHandler: ShareIntentHandler,
) : ) :
VectorBaseFragment<FragmentIncomingShareBinding>(), VectorBaseFragment<FragmentIncomingShareBinding>(),
AttachmentsHelper.Callback,
IncomingShareController.Callback { IncomingShareController.Callback {
private lateinit var attachmentsHelper: AttachmentsHelper
private val viewModel: IncomingShareViewModel by fragmentViewModel() private val viewModel: IncomingShareViewModel by fragmentViewModel()
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentIncomingShareBinding { override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentIncomingShareBinding {
@ -75,7 +72,6 @@ class IncomingShareFragment @Inject constructor(
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
setupRecyclerView() setupRecyclerView()
setupToolbar(views.incomingShareToolbar) setupToolbar(views.incomingShareToolbar)
attachmentsHelper = AttachmentsHelper(requireContext(), this).register()
viewModel.observeViewEvents { viewModel.observeViewEvents {
when (it) { when (it) {
@ -88,20 +84,15 @@ class IncomingShareFragment @Inject constructor(
val intent = vectorBaseActivity.intent val intent = vectorBaseActivity.intent
val isShareManaged = when (intent?.action) { val isShareManaged = when (intent?.action) {
Intent.ACTION_SEND -> { Intent.ACTION_SEND -> {
var isShareManaged = attachmentsHelper.handleShareIntent(requireContext(), intent) val isShareManaged = handleIncomingShareIntent(intent)
if (!isShareManaged) {
isShareManaged = handleTextShare(intent)
}
// Direct share // Direct share
if (intent.hasExtra(Intent.EXTRA_SHORTCUT_ID)) { if (intent.hasExtra(Intent.EXTRA_SHORTCUT_ID)) {
val roomId = intent.getStringExtra(Intent.EXTRA_SHORTCUT_ID)!! val roomId = intent.getStringExtra(Intent.EXTRA_SHORTCUT_ID)!!
sessionHolder.getSafeActiveSession()?.getRoomSummary(roomId)?.let { viewModel.handle(IncomingShareAction.ShareToRoom(it)) } sessionHolder.getSafeActiveSession()?.getRoomSummary(roomId)?.let { viewModel.handle(IncomingShareAction.ShareToRoom(it)) }
} }
isShareManaged isShareManaged
} }
Intent.ACTION_SEND_MULTIPLE -> attachmentsHelper.handleShareIntent(requireContext(), intent) Intent.ACTION_SEND_MULTIPLE -> handleIncomingShareIntent(intent)
else -> false else -> false
} }
@ -124,6 +115,19 @@ class IncomingShareFragment @Inject constructor(
} }
} }
private fun handleIncomingShareIntent(intent: Intent) = shareIntentHandler.handleIncomingShareIntent(
requireContext(),
intent,
onFile = {
val sharedData = SharedData.Attachments(it)
viewModel.handle(IncomingShareAction.UpdateSharedData(sharedData))
},
onPlainText = {
val sharedData = SharedData.Text(it)
viewModel.handle(IncomingShareAction.UpdateSharedData(sharedData))
}
)
private fun handleMultipleRoomsShareDone(viewEvent: IncomingShareViewEvents.MultipleRoomsShareDone) { private fun handleMultipleRoomsShareDone(viewEvent: IncomingShareViewEvents.MultipleRoomsShareDone) {
requireActivity().let { requireActivity().let {
navigator.openRoom( navigator.openRoom(
@ -173,34 +177,11 @@ class IncomingShareFragment @Inject constructor(
incomingShareController.callback = this incomingShareController.callback = this
} }
override fun onContentAttachmentsReady(attachments: List<ContentAttachmentData>) {
val sharedData = SharedData.Attachments(attachments)
viewModel.handle(IncomingShareAction.UpdateSharedData(sharedData))
}
override fun onAttachmentsProcessFailed() {
cannotManageShare(R.string.error_handling_incoming_share)
}
private fun cannotManageShare(@StringRes messageResId: Int) { private fun cannotManageShare(@StringRes messageResId: Int) {
Toast.makeText(requireContext(), messageResId, Toast.LENGTH_LONG).show() Toast.makeText(requireContext(), messageResId, Toast.LENGTH_LONG).show()
requireActivity().finish() requireActivity().finish()
} }
private fun handleTextShare(intent: Intent): Boolean {
if (intent.type == ClipDescription.MIMETYPE_TEXT_PLAIN) {
val sharedText = intent.getCharSequenceExtra(Intent.EXTRA_TEXT)?.toString()
return if (sharedText.isNullOrEmpty()) {
false
} else {
val sharedData = SharedData.Text(sharedText)
viewModel.handle(IncomingShareAction.UpdateSharedData(sharedData))
true
}
}
return false
}
private fun showConfirmationDialog(roomSummary: RoomSummary, sharedData: SharedData) { private fun showConfirmationDialog(roomSummary: RoomSummary, sharedData: SharedData) {
MaterialAlertDialogBuilder(requireActivity()) MaterialAlertDialogBuilder(requireActivity())
.setTitle(R.string.send_attachment) .setTitle(R.string.send_attachment)

View file

@ -1814,6 +1814,8 @@
<string name="error_file_too_big_simple">"The file is too large to upload."</string> <string name="error_file_too_big_simple">"The file is too large to upload."</string>
<!-- TODO Remove key -->
<!--suppress UnusedResources -->
<string name="error_attachment">"An error occurred while retrieving the attachment."</string> <string name="error_attachment">"An error occurred while retrieving the attachment."</string>
<string name="attachment_type_dialog_title">"Add image from"</string> <string name="attachment_type_dialog_title">"Add image from"</string>
<string name="attachment_type_file">"File"</string> <string name="attachment_type_file">"File"</string>