Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: update local file access permission #8810

Merged
merged 1 commit into from
May 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/3616.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix crash when accessing a local file and permission is revoked.
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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
Expand All @@ -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) }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,7 @@ class TimelineFragment :
RoomDetailViewEvents.RoomReplacementStarted -> handleRoomReplacement()
RoomDetailViewEvents.OpenElementCallWidget -> handleOpenElementCallWidget()
RoomDetailViewEvents.DisplayPromptToStopVoiceBroadcast -> displayPromptToStopVoiceBroadcast()
is RoomDetailViewEvents.RevokeFilePermission -> revokeFilePermission(it)
}
}

Expand Down Expand Up @@ -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()
}
Expand Down Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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")
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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) :
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -313,15 +313,15 @@ 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))
}
}

private fun ArrayList<EventSharedAction>.addActionsForSentNotSyncedState(timelineEvent: TimelineEvent) {
// 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
Expand Down
Loading