Skip to content

Commit

Permalink
Merge pull request #230 from gavine99/any_file_pe_attachments
Browse files Browse the repository at this point in the history
changes to allow attaching *any* type of file
  • Loading branch information
octoshrimpy authored Jan 27, 2025
2 parents 8ff3824 + 314704a commit c5dc470
Show file tree
Hide file tree
Showing 27 changed files with 499 additions and 191 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,11 @@ private void scheduleRetry(Uri uri) {
case PduHeaders.RESPONSE_STATUS_ERROR_PERMANENT_MESSAGE_NOT_FOUND:
errorString = R.string.service_message_not_found;
break;
case PduHeaders.RESPONSE_STATUS_ERROR_CONTENT_NOT_ACCEPTED:
case PduHeaders.RESPONSE_STATUS_ERROR_PERMANENT_CONTENT_NOT_ACCEPTED:
errorString = R.string.service_message_content_not_accepted;
break;

}
if (errorString != 0) {
DownloadManager.init(mContext.getApplicationContext());
Expand Down
1 change: 1 addition & 0 deletions android-smsmms/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ limitations under the License.
<string name="invalid_destination">Invalid destination address.</string>
<string name="service_not_activated">Service not activated on network.</string>
<string name="service_message_not_found">Message expired or not available.</string>
<string name="service_message_content_not_accepted">Message content is not accepted.</string>
<string name="service_network_problem">Couldn\'t send due to network problem.</string>
<string name="message_queued">Currently can\'t send your message. It will be sent when the service becomes available.</string>
<string name="download_later">Can\'t download right now. Try again later.</string>
Expand Down
1 change: 1 addition & 0 deletions data/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,5 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
</manifest>
36 changes: 36 additions & 0 deletions data/src/main/java/com/moez/QKSMS/extensions/UriExtensions.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Copyright (C) 2017 Moez Bhatti <[email protected]>
*
* This file is part of QKSMS.
*
* QKSMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* QKSMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with QKSMS. If not, see <http://www.gnu.org/licenses/>.
*/
package dev.octoshrimpy.quik.extensions

import android.content.Context
import android.net.Uri

fun Uri.resourceExists(context: Context): Boolean {
// check if resource at uri still exists
try {
return context.contentResolver.query(
this, null, null, null, null
)?.use {
it.moveToFirst()
} ?: false
}
catch (e: Exception) { /* nothing */ }

return false
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ import android.content.ContentValues
import android.content.Context
import android.content.Intent
import android.graphics.BitmapFactory
import android.media.MediaScannerConnection
import android.net.Uri
import android.os.Build
import android.os.Environment
Expand Down Expand Up @@ -65,14 +64,7 @@ import io.realm.Case
import io.realm.Realm
import io.realm.RealmResults
import io.realm.Sort
import okio.buffer
import okio.source
import timber.log.Timber
import java.io.File
import java.io.FileNotFoundException
import java.io.FileOutputStream
import java.io.IOException
import java.util.*
import java.util.concurrent.TimeUnit
import javax.inject.Inject
import javax.inject.Singleton
Expand Down Expand Up @@ -376,27 +368,37 @@ class MessageRepositoryImpl @Inject constructor(
parts += MMSPart("text", ContentType.TEXT_PLAIN, bytes)
}

// Attach contacts
// Attach those that can't be compressed (ie. everything but not images)
parts += attachments
.mapNotNull { attachment -> attachment as? Attachment.Contact }
.map { attachment -> attachment.vCard.toByteArray() }
.map { vCard ->
remainingBytes -= vCard.size
MMSPart("contact", ContentType.TEXT_VCARD, vCard)
}
// filter out images
.filter { !it.isImage(context) }
// filter out items that have no data available (ie user may have deleted
// the file from storage)
.filter { it.getResourceBytes(context).isNotEmpty() }
.map {
remainingBytes -= it.getResourceBytes(context).size
MMSPart(
it.getName(context),
it.getType(context),
it.getResourceBytes(context)
)
}

val imageBytesByAttachment = attachments
.mapNotNull { attachment -> attachment as? Attachment.Image }
.associateWith { attachment ->
val uri = attachment.getUri() ?: return@associateWith byteArrayOf()
when (attachment.isGif(context)) {
true -> ImageUtils.getScaledGif(context, uri, maxWidth, maxHeight)
false -> ImageUtils.getScaledImage(context, uri, maxWidth, maxHeight)
}
// filter out non-images
.filter { it.isImage(context) }
// filter out items that have no data available (ie user may have deleted
// the file from storage)
.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)
}
.toMutableMap()
}
.toMutableMap()

val imageByteCount = imageBytesByAttachment.values.sumBy { byteArray -> byteArray.size }
val imageByteCount = imageBytesByAttachment.values.sumOf { it.size }
if (imageByteCount > remainingBytes) {
imageBytesByAttachment.forEach { (attachment, originalBytes) ->
val uri = attachment.getUri() ?: return@forEach
Expand Down Expand Up @@ -426,9 +428,9 @@ class MessageRepositoryImpl @Inject constructor(
val newHeight = (newWidth / aspectRatio).toInt()

attempts++
scaledBytes = when (attachment.isGif(context)) {
true -> ImageUtils.getScaledGif(context, uri, newWidth, newHeight, 80)
false -> ImageUtils.getScaledImage(context, uri, newWidth, newHeight, 80)
scaledBytes = when (attachment.getType(context) == "image/gif") {
true -> ImageUtils.getScaledGif(context, attachment.getUri(), maxWidth, maxHeight)
false -> ImageUtils.getScaledImage(context, attachment.getUri(), maxWidth, maxHeight)
}

Timber.d("Compression attempt $attempts: ${scaledBytes.size / 1024}/${maxBytes.toInt() / 1024}Kb ($width*$height -> $newWidth*$newHeight)")
Expand All @@ -440,7 +442,7 @@ class MessageRepositoryImpl @Inject constructor(
}

imageBytesByAttachment.forEach { (attachment, bytes) ->
parts += when (attachment.isGif(context)) {
parts += when (attachment.getType(context) == "image/gif") {
true -> MMSPart("image", ContentType.IMAGE_GIF, bytes)
false -> MMSPart("image", ContentType.IMAGE_JPEG, bytes)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ class SendScheduledMessage @Inject constructor(
}
.map { message ->
val threadId = TelephonyCompat.getOrCreateThreadId(context, message.recipients)
val attachments = message.attachments.mapNotNull(Uri::parse).map { Attachment.Image(it) }
val attachments = message.attachments.mapNotNull(Uri::parse).map { Attachment(it) }
SendMessage.Params(message.subId, threadId, message.recipients, message.body, attachments)
}
.flatMap(sendMessage::buildObservable)
Expand Down
105 changes: 89 additions & 16 deletions domain/src/main/java/com/moez/QKSMS/model/Attachment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -21,33 +21,106 @@ package dev.octoshrimpy.quik.model
import android.content.Context
import android.net.Uri
import android.os.Build
import android.provider.OpenableColumns
import androidx.core.view.inputmethod.InputContentInfoCompat

sealed class Attachment {

data class Image(
private val uri: Uri? = null,
private val inputContent: InputContentInfoCompat? = null
) : Attachment() {
class Attachment(
private val uri: Uri? = null,
private val 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
} else {
uri
fun getUri(): Uri {
return (
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1)
inputContent?.contentUri ?: uri ?: Uri.EMPTY
else uri ?: Uri.EMPTY
)
}

fun getType(context: Context): String {
return context.contentResolver.getType(getUri()) ?: "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"
}
}
catch (e: Exception) { /* nothing*/ }

return "unknown"
}

fun isGif(context: Context): Boolean {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1 && inputContent != null) {
inputContent.description.hasMimeType("image/gif")
} else {
uri?.let(context.contentResolver::getType) == "image/gif"
fun isVCard(context: Context): Boolean {
return (getType(context) == "text/x-vcard")
}

fun hasDisplayableImage(context: Context): Boolean {
val mimeType = getType(context)
return (mimeType.startsWith("image/") || mimeType.startsWith("video/"))
}

fun isAudio(context: Context): Boolean {
val mimeType = getType(context)
return (mimeType.startsWith("audio/"))
}

fun isImage(context: Context): Boolean {
val mimeType = getType(context)
return (mimeType.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
it.moveToFirst()
return it.getLong(sizeIndex)
}
}
catch (e: Exception) { /* nothing */ }

return -1
}

data class Contact(val vCard: String) : Attachment()
fun getResourceBytes(context: Context): ByteArray {
if (resourceBytes != null)
return resourceBytes!!

resourceBytes = ByteArray(0)

try {
context.contentResolver.openInputStream(getUri())?.use {
resourceBytes = it.readBytes()
}
} catch (e: Exception) {
}

return resourceBytes!!
}

}

Expand Down
6 changes: 4 additions & 2 deletions domain/src/main/java/com/moez/QKSMS/model/MmsPart.kt
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,13 @@ open class MmsPart : RealmObject() {
fun getUri() = "content://mms/part/$id".toUri()

fun getSummary(): String? = when {
type == "application/smil" -> null
type == "text/plain" -> text
type == "text/x-vCard" -> "Contact card"
type == "text/x-vcard" -> "Contact card"
type.startsWith("image") -> "Photo"
type.startsWith("video") -> "Video"
else -> null
type.startsWith("audio") -> "Audio"
else -> type.substring(type.indexOf('/') + 1)
}

}
14 changes: 2 additions & 12 deletions presentation/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -88,22 +88,12 @@
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="image/*" />
<data android:mimeType="*/*" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND_MULTIPLE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="image/*" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/x-vcard" />
<data android:mimeType="*/*" />
</intent-filter>

<meta-data
Expand Down
Loading

0 comments on commit c5dc470

Please sign in to comment.