fix: update local file access permission

This commit is contained in:
yostyle 2024-04-18 11:46:49 +02:00
parent a1823b0f62
commit 33d09ecf40
14 changed files with 78 additions and 26 deletions

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

@ -0,0 +1 @@
Fix crash when accessing a local file and permission is revoked.

View file

@ -31,7 +31,7 @@ class AudioPicker : Picker<MultiPickerAudioType>() {
* Returns selected audio files or empty list if user did not select any files.
*/
override fun getSelectedFiles(context: Context, data: Intent?): List<MultiPickerAudioType> {
return getSelectedUriList(data).mapNotNull { selectedUri ->
return getSelectedUriList(context, data).mapNotNull { selectedUri ->
selectedUri.toMultiPickerAudioType(context)
}
}

View file

@ -41,7 +41,7 @@ class FilePicker : Picker<MultiPickerBaseType>() {
* Returns selected files or empty list if user did not select any files.
*/
override fun getSelectedFiles(context: Context, data: Intent?): List<MultiPickerBaseType> {
return getSelectedUriList(data).mapNotNull { selectedUri ->
return getSelectedUriList(context, data).mapNotNull { selectedUri ->
val type = context.contentResolver.getType(selectedUri)
when {

View file

@ -31,7 +31,7 @@ class ImagePicker : Picker<MultiPickerImageType>() {
* Returns selected image files or empty list if user did not select any files.
*/
override fun getSelectedFiles(context: Context, data: Intent?): List<MultiPickerImageType> {
return getSelectedUriList(data).mapNotNull { selectedUri ->
return getSelectedUriList(context, data).mapNotNull { selectedUri ->
selectedUri.toMultiPickerImageType(context)
}
}

View file

@ -33,7 +33,7 @@ class MediaPicker : Picker<MultiPickerBaseMediaType>() {
* Returns selected image/video files or empty list if user did not select any files.
*/
override fun getSelectedFiles(context: Context, data: Intent?): List<MultiPickerBaseMediaType> {
return getSelectedUriList(data).mapNotNull { selectedUri ->
return getSelectedUriList(context, data).mapNotNull { selectedUri ->
val mimeType = context.contentResolver.getType(selectedUri)
if (mimeType.isMimeTypeVideo()) {

View file

@ -16,6 +16,7 @@
package im.vector.lib.multipicker
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
@ -58,7 +59,17 @@ abstract class Picker<T> {
uriList.forEach {
for (resolveInfo in resInfoList) {
val packageName: String = resolveInfo.activityInfo.packageName
context.grantUriPermission(packageName, it, Intent.FLAG_GRANT_READ_URI_PERMISSION)
// Replace implicit intent by an explicit to fix crash on some devices like Xiaomi.
// see https://juejin.cn/post/7031736325422186510
try {
context.grantUriPermission(packageName, it, Intent.FLAG_GRANT_READ_URI_PERMISSION)
} catch (e: Exception) {
continue
}
data.action = null
data.component = ComponentName(packageName, resolveInfo.activityInfo.name)
break
}
}
return getSelectedFiles(context, data)
@ -82,7 +93,7 @@ abstract class Picker<T> {
activityResultLauncher.launch(createIntent().apply { addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) })
}
protected fun getSelectedUriList(data: Intent?): List<Uri> {
protected fun getSelectedUriList(context: Context, data: Intent?): List<Uri> {
val selectedUriList = mutableListOf<Uri>()
val dataUri = data?.data
val clipData = data?.clipData
@ -104,6 +115,6 @@ abstract class Picker<T> {
}
}
}
return selectedUriList
return selectedUriList.onEach { context.grantUriPermission(context.applicationContext.packageName, it, Intent.FLAG_GRANT_READ_URI_PERMISSION) }
}
}

View file

@ -31,7 +31,7 @@ class VideoPicker : Picker<MultiPickerVideoType>() {
* Returns selected video files or empty list if user did not select any files.
*/
override fun getSelectedFiles(context: Context, data: Intent?): List<MultiPickerVideoType> {
return getSelectedUriList(data).mapNotNull { selectedUri ->
return getSelectedUriList(context, data).mapNotNull { selectedUri ->
selectedUri.toMultiPickerVideoType(context)
}
}

View file

@ -17,8 +17,10 @@
package org.matrix.android.sdk.internal.session.content
import android.content.Context
import android.content.Intent
import android.graphics.BitmapFactory
import android.media.MediaMetadataRetriever
import android.os.Build
import androidx.core.net.toUri
import androidx.work.WorkerParameters
import com.squareup.moshi.JsonClass
@ -115,7 +117,15 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
if (allCancelled) {
// there is no point in uploading the image!
return Result.success(inputData)
.also { Timber.e("## Send: Work cancelled by user") }
.also {
Timber.e("## Send: Work cancelled by user")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context.revokeUriPermission(context.packageName, params.attachment.queryUri, Intent.FLAG_GRANT_READ_URI_PERMISSION)
} else {
context.revokeUriPermission(params.attachment.queryUri, Intent.FLAG_GRANT_READ_URI_PERMISSION)
}
}
}
val attachment = params.attachment
@ -396,6 +406,12 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
)
return Result.success(WorkerParamsFactory.toData(sendParams)).also {
Timber.v("## handleSuccess $attachmentUrl, work is stopped $isStopped")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context.revokeUriPermission(context.packageName, params.attachment.queryUri, Intent.FLAG_GRANT_READ_URI_PERMISSION)
} else {
context.revokeUriPermission(params.attachment.queryUri, Intent.FLAG_GRANT_READ_URI_PERMISSION)
}
}
}

View file

@ -52,7 +52,7 @@ sealed class RoomDetailAction : VectorViewModelAction {
data class ResendMessage(val eventId: String) : RoomDetailAction()
data class RemoveFailedEcho(val eventId: String) : RoomDetailAction()
data class CancelSend(val eventId: String, val force: Boolean) : RoomDetailAction()
data class CancelSend(val event: TimelineEvent, val force: Boolean) : RoomDetailAction()
data class VoteToPoll(val eventId: String, val optionKey: String) : RoomDetailAction()

View file

@ -65,6 +65,10 @@ sealed class RoomDetailViewEvents : VectorViewEvents {
val mimeType: String?
) : RoomDetailViewEvents()
data class RevokeFilePermission(
val uri: Uri
) : RoomDetailViewEvents()
data class DisplayAndAcceptCall(val call: WebRtcCall) : RoomDetailViewEvents()
object DisplayPromptForIntegrationManager : RoomDetailViewEvents()

View file

@ -414,6 +414,7 @@ class TimelineFragment :
RoomDetailViewEvents.RoomReplacementStarted -> handleRoomReplacement()
RoomDetailViewEvents.OpenElementCallWidget -> handleOpenElementCallWidget()
RoomDetailViewEvents.DisplayPromptToStopVoiceBroadcast -> displayPromptToStopVoiceBroadcast()
is RoomDetailViewEvents.RevokeFilePermission -> revokeFilePermission(it)
}
}
@ -1571,14 +1572,14 @@ class TimelineFragment :
private fun handleCancelSend(action: EventSharedAction.Cancel) {
if (action.force) {
timelineViewModel.handle(RoomDetailAction.CancelSend(action.eventId, true))
timelineViewModel.handle(RoomDetailAction.CancelSend(action.event, true))
} else {
MaterialAlertDialogBuilder(requireContext())
.setTitle(R.string.dialog_title_confirmation)
.setMessage(getString(R.string.event_status_cancel_sending_dialog_message))
.setNegativeButton(R.string.no, null)
.setPositiveButton(R.string.yes) { _, _ ->
timelineViewModel.handle(RoomDetailAction.CancelSend(action.eventId, false))
timelineViewModel.handle(RoomDetailAction.CancelSend(action.event, false))
}
.show()
}
@ -2051,6 +2052,21 @@ class TimelineFragment :
}
}
private fun revokeFilePermission(revokeFilePermission: RoomDetailViewEvents.RevokeFilePermission) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
requireContext().revokeUriPermission(
requireContext().applicationContext.packageName,
revokeFilePermission.uri,
Intent.FLAG_GRANT_READ_URI_PERMISSION
)
} else {
requireContext().revokeUriPermission(
revokeFilePermission.uri,
Intent.FLAG_GRANT_READ_URI_PERMISSION
)
}
}
override fun onTapToReturnToCall() {
callManager.getCurrentCall()?.let { call ->
VectorCallActivity.newIntent(

View file

@ -18,6 +18,7 @@ package im.vector.app.features.home.room.detail
import android.net.Uri
import androidx.annotation.IdRes
import androidx.core.net.toUri
import androidx.lifecycle.asFlow
import com.airbnb.mvrx.Async
import com.airbnb.mvrx.Fail
@ -84,6 +85,7 @@ import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.matrix.android.sdk.api.MatrixPatterns
import org.matrix.android.sdk.api.MatrixUrls.isMxcUrl
import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.query.QueryStringValue
@ -111,6 +113,8 @@ import org.matrix.android.sdk.api.session.room.model.Membership
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.localecho.RoomLocalEcho
import org.matrix.android.sdk.api.session.room.model.message.MessageContent
import org.matrix.android.sdk.api.session.room.model.message.MessageWithAttachmentContent
import org.matrix.android.sdk.api.session.room.model.message.getFileUrl
import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent
import org.matrix.android.sdk.api.session.room.model.tombstone.RoomTombstoneContent
@ -1074,18 +1078,17 @@ class TimelineViewModel @AssistedInject constructor(
private fun handleCancel(action: RoomDetailAction.CancelSend) {
if (room == null) return
if (action.force) {
room.sendService().cancelSend(action.eventId)
return
}
val targetEventId = action.eventId
room.getTimelineEvent(targetEventId)?.let {
// State must be in one of the sending states
if (!it.root.sendState.isSending()) {
Timber.e("Cannot cancel message, it is not sending")
return
// State must be in one of the sending states
if (action.force || action.event.root.sendState.isSending()) {
room.sendService().cancelSend(action.event.eventId)
val clearContent = action.event.root.getClearContent()
val messageContent = clearContent?.toModel<MessageContent>() as? MessageWithAttachmentContent
messageContent?.getFileUrl()?.takeIf { !it.isMxcUrl() }?.let {
_viewEvents.post(RoomDetailViewEvents.RevokeFilePermission(it.toUri()))
}
room.sendService().cancelSend(targetEventId)
} else {
Timber.e("Cannot cancel message, it is not sending")
}
}

View file

@ -23,6 +23,7 @@ import im.vector.app.core.platform.VectorSharedAction
import im.vector.app.features.home.room.detail.timeline.item.MessageInformationData
import org.matrix.android.sdk.api.session.room.model.message.MessageContent
import org.matrix.android.sdk.api.session.room.model.message.MessageWithAttachmentContent
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
sealed class EventSharedAction(
@StringRes val titleRes: Int,
@ -71,7 +72,7 @@ sealed class EventSharedAction(
data class Redact(val eventId: String, val askForReason: Boolean, val dialogTitleRes: Int, val dialogDescriptionRes: Int) :
EventSharedAction(R.string.message_action_item_redact, R.drawable.ic_delete, true)
data class Cancel(val eventId: String, val force: Boolean) :
data class Cancel(val event: TimelineEvent, val force: Boolean) :
EventSharedAction(R.string.action_cancel, R.drawable.ic_close_round)
data class ViewSource(val content: String) :

View file

@ -313,7 +313,7 @@ class MessageActionsViewModel @AssistedInject constructor(
private fun ArrayList<EventSharedAction>.addActionsForSendingState(timelineEvent: TimelineEvent) {
// TODO is uploading attachment?
if (canCancel(timelineEvent)) {
add(EventSharedAction.Cancel(timelineEvent.eventId, false))
add(EventSharedAction.Cancel(timelineEvent, false))
}
}
@ -321,7 +321,7 @@ class MessageActionsViewModel @AssistedInject constructor(
// If sent but not synced (synapse stuck at bottom bug)
// Still offer action to cancel (will only remove local echo)
timelineEvent.root.eventId?.let {
add(EventSharedAction.Cancel(it, true))
add(EventSharedAction.Cancel(timelineEvent, true))
}
// TODO Can be redacted