diff --git a/data/src/main/java/com/moez/QKSMS/repository/MessageRepositoryImpl.kt b/data/src/main/java/com/moez/QKSMS/repository/MessageRepositoryImpl.kt index 5771cff3..db4cc4f4 100644 --- a/data/src/main/java/com/moez/QKSMS/repository/MessageRepositoryImpl.kt +++ b/data/src/main/java/com/moez/QKSMS/repository/MessageRepositoryImpl.kt @@ -392,8 +392,8 @@ class MessageRepositoryImpl @Inject constructor( .filter { it.getResourceBytes(context).isNotEmpty() } .associateWith { when (it.getType(context) == "image/gif") { - true -> ImageUtils.getScaledGif(context, it.getUri(), maxWidth, maxHeight) - false -> ImageUtils.getScaledImage(context, it.getUri(), maxWidth, maxHeight) + true -> ImageUtils.getScaledGif(context, it.uri, maxWidth, maxHeight) + false -> ImageUtils.getScaledImage(context, it.uri, maxWidth, maxHeight) } } .toMutableMap() @@ -401,7 +401,7 @@ class MessageRepositoryImpl @Inject constructor( val imageByteCount = imageBytesByAttachment.values.sumOf { it.size } if (imageByteCount > remainingBytes) { imageBytesByAttachment.forEach { (attachment, originalBytes) -> - val uri = attachment.getUri() ?: return@forEach + val uri = attachment.uri ?: return@forEach val maxBytes = originalBytes.size / imageByteCount.toFloat() * remainingBytes // Get the image dimensions @@ -429,8 +429,8 @@ class MessageRepositoryImpl @Inject constructor( attempts++ scaledBytes = when (attachment.getType(context) == "image/gif") { - true -> ImageUtils.getScaledGif(context, attachment.getUri(), newWidth, newHeight) - false -> ImageUtils.getScaledImage(context, attachment.getUri(), newWidth, newHeight) + true -> ImageUtils.getScaledGif(context, attachment.uri, newWidth, newHeight) + false -> ImageUtils.getScaledImage(context, attachment.uri, newWidth, newHeight) } Timber.d("Compression attempt $attempts: ${scaledBytes.size / 1024}/${maxBytes.toInt() / 1024}Kb ($width*$height -> $newWidth*$newHeight)") diff --git a/domain/src/main/java/com/moez/QKSMS/interactor/SendScheduledMessage.kt b/domain/src/main/java/com/moez/QKSMS/interactor/SendScheduledMessage.kt index 0047fdd8..7ea2ec33 100644 --- a/domain/src/main/java/com/moez/QKSMS/interactor/SendScheduledMessage.kt +++ b/domain/src/main/java/com/moez/QKSMS/interactor/SendScheduledMessage.kt @@ -47,11 +47,11 @@ class SendScheduledMessage @Inject constructor( } .map { message -> val threadId = TelephonyCompat.getOrCreateThreadId(context, message.recipients) - val attachments = message.attachments.mapNotNull(Uri::parse).map { Attachment(it) } + val attachments = message.attachments.mapNotNull(Uri::parse).map { Attachment(context, it) } SendMessage.Params(message.subId, threadId, message.recipients, message.body, attachments) } .flatMap(sendMessage::buildObservable) .doOnNext { scheduledMessageRepo.deleteScheduledMessage(params) } } -} \ No newline at end of file +} diff --git a/domain/src/main/java/com/moez/QKSMS/model/Attachment.kt b/domain/src/main/java/com/moez/QKSMS/model/Attachment.kt index 5b1cdaaa..8957a969 100644 --- a/domain/src/main/java/com/moez/QKSMS/model/Attachment.kt +++ b/domain/src/main/java/com/moez/QKSMS/model/Attachment.kt @@ -18,49 +18,60 @@ */ package dev.octoshrimpy.quik.model +import android.annotation.SuppressLint import android.content.Context import android.net.Uri import android.os.Build +import android.provider.ContactsContract import android.provider.OpenableColumns import androidx.core.view.inputmethod.InputContentInfoCompat -class Attachment( - private val uri: Uri? = null, - private val inputContent: InputContentInfoCompat? = null +@SuppressLint("Range") +class Attachment ( + context: Context, + var uri: Uri = Uri.EMPTY, + inputContent: InputContentInfoCompat? = null ) { private var resourceBytes: ByteArray? = null - fun getUri(): Uri { - return ( - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) - inputContent?.contentUri ?: uri ?: Uri.EMPTY - else uri ?: Uri.EMPTY - ) + init { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) + uri = inputContent?.contentUri ?: uri + + // if constructed with a uri to a contact, convert uri to associated vcard uri + if (context.contentResolver.getType(uri)?.lowercase() == + ContactsContract.Contacts.CONTENT_ITEM_TYPE) { + uri = try { + Uri.withAppendedPath( + ContactsContract.Contacts.CONTENT_VCARD_URI, + context.contentResolver.query( + uri, null, null, null, null + )?.use { + it.moveToFirst() + it.getString(it.getColumnIndex(ContactsContract.Contacts.LOOKUP_KEY)) ?: "" + } + ) + } catch (e: Exception) { + Uri.EMPTY + } + } } fun getType(context: Context): String { - return context.contentResolver.getType(getUri()) ?: "application/octect-stream" + return context.contentResolver.getType(uri) ?: "application/octect-stream" } fun getName(context: Context): String { try { context.contentResolver.query( - getUri(), - arrayOf(OpenableColumns.DISPLAY_NAME), - null, - null, - null - ) - ?.use { cursor -> - val nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME) - if (nameIndex == -1) - return "unknown" - cursor.moveToFirst() - return cursor.getString(nameIndex) ?: "unknown" + uri, arrayOf(OpenableColumns.DISPLAY_NAME), null, null, null + )?.use { + it.moveToFirst() + return it.getString(it.getColumnIndex(OpenableColumns.DISPLAY_NAME)) ?: "unknown" } } - catch (e: Exception) { /* nothing*/ } + catch (e: Exception) { /* nothing */ } return "unknown" } @@ -75,30 +86,20 @@ class Attachment( } fun isAudio(context: Context): Boolean { - val mimeType = getType(context) - return (mimeType.startsWith("audio/")) + return (getType(context).startsWith("audio/")) } fun isImage(context: Context): Boolean { - val mimeType = getType(context) - return (mimeType.startsWith("image/")) + return (getType(context).startsWith("image/")) } fun getSize(context: Context): Long { try { context.contentResolver.query( - getUri(), - arrayOf(OpenableColumns.SIZE), - null, - null, - null - ) - ?.use { - val sizeIndex = it.getColumnIndex(OpenableColumns.SIZE) - if (sizeIndex == -1) - return -1 + uri, arrayOf(OpenableColumns.SIZE), null, null, null + )?.use { it.moveToFirst() - return it.getLong(sizeIndex) + return it.getLong(it.getColumnIndex(OpenableColumns.SIZE)) } } catch (e: Exception) { /* nothing */ } @@ -110,16 +111,14 @@ class Attachment( if (resourceBytes != null) return resourceBytes!! - resourceBytes = ByteArray(0) - try { - context.contentResolver.openInputStream(getUri())?.use { + context.contentResolver.openInputStream(uri)?.use { resourceBytes = it.readBytes() } } catch (e: Exception) { } - return resourceBytes!! + return resourceBytes ?: ByteArray(0) } } diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/AttachmentAdapter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/AttachmentAdapter.kt index 304567fd..72759ea3 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/AttachmentAdapter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/AttachmentAdapter.kt @@ -93,14 +93,14 @@ class AttachmentAdapter @Inject constructor( holder.fileName.visibility = View.GONE // if attachment uri is missing - if (!attachment.getUri().resourceExists(context)) { + if (!attachment.uri.resourceExists(context)) { holder.thumbnail.setImageResource(android.R.drawable.ic_delete) holder.fileName.text = context.getString(R.string.attachment_missing) holder.fileName.visibility = View.VISIBLE return } - val uri = attachment.getUri() + val uri = attachment.uri val mimeType = attachment.getType(context) // if attachment mime type is image/* or video/*, use image/frame @@ -160,4 +160,4 @@ class AttachmentAdapter @Inject constructor( else -> VIEW_TYPE_FILE } -} \ No newline at end of file +} diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeActivity.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeActivity.kt index 7179d255..06de9b73 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeActivity.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeActivity.kt @@ -446,7 +446,7 @@ class ComposeActivity : QkThemedActivity(), ComposeView { override fun requestContact() { val intent = Intent(Intent.ACTION_PICK) - .setType(ContactsContract.CommonDataKinds.Phone.CONTENT_TYPE) + .setType(ContactsContract.Contacts.CONTENT_TYPE) startActivityForResult(Intent.createChooser(intent, null), ComposeView.AttachContactRequestCode) } diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeActivityModule.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeActivityModule.kt index 1a599bd8..9063b86b 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeActivityModule.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeActivityModule.kt @@ -79,7 +79,7 @@ class ComposeActivityModule { activity.intent.getParcelableExtra(Intent.EXTRA_STREAM)?.run(uris::add) activity.intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM)?.run(uris::addAll) - return Attachments(uris.mapNotNull { Attachment(it) }) + return Attachments(uris.mapNotNull { Attachment(activity, it) }) } @Provides diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt index 6763f183..789e886b 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/compose/ComposeViewModel.kt @@ -19,9 +19,7 @@ package dev.octoshrimpy.quik.feature.compose import android.content.Context -import android.net.Uri import android.os.Vibrator -import android.provider.ContactsContract import android.telephony.SmsMessage import androidx.core.content.getSystemService import com.moez.QKSMS.contentproviders.MmsPartProvider @@ -652,8 +650,8 @@ class ComposeViewModel @Inject constructor( // a file, photo or otherwise, was picked by the user Observable.merge( - view.attachAnyFileSelectedIntent.map { uri -> Attachment(uri) }, - view.inputContentIntent.map { inputContent -> Attachment(inputContent = inputContent) } + view.attachAnyFileSelectedIntent.map { uri -> Attachment(context, uri) }, + view.inputContentIntent.map { inputContent -> Attachment(context, inputContent = inputContent) } ) .withLatestFrom(attachments) { attachment, attachments -> attachments + attachment } .doOnNext(attachments::onNext) @@ -677,7 +675,7 @@ class ComposeViewModel @Inject constructor( // Contact was selected for attachment view.contactSelectedIntent - .map { uri -> Attachment(getFullVCardUri(uri)) } + .map { uri -> Attachment(context, uri) } .withLatestFrom(attachments) { attachment, attachments -> attachments + attachment } .subscribeOn(Schedulers.io()) .autoDisposable(view.scope()) @@ -801,9 +799,7 @@ class ComposeViewModel @Inject constructor( // Scheduling a message state.scheduled != 0L -> { newState { copy(scheduled = 0) } - val uris = attachments - .map { it.getUri() } - .map { it.toString() } + val uris = attachments.map { it.uri.toString() } val params = AddScheduledMessage .Params(state.scheduled, subId, addresses, sendAsGroup, body, uris) addScheduledMessage.execute(params) @@ -881,17 +877,4 @@ class ComposeViewModel @Inject constructor( } - private fun getFullVCardUri(contactData: Uri): Uri { - val lookupKey = context.contentResolver.query(contactData, null, null, null, null)?.use { - it.moveToFirst() - val index = it.getColumnIndex(ContactsContract.Contacts.LOOKUP_KEY) - if (index >= 0) - it.getString(index) - else - "" - } - - return Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_VCARD_URI, lookupKey) - } - -} \ No newline at end of file +}