744 lines
29 KiB
Kotlin
744 lines
29 KiB
Kotlin
/*
|
|
* Copyright 2013 Jacob Klinker
|
|
*
|
|
* 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 xyz.johnny.norntalk.messages
|
|
|
|
import android.app.Activity
|
|
import android.app.PendingIntent
|
|
import android.content.*
|
|
import android.net.Uri
|
|
import android.os.Bundle
|
|
import android.os.Looper
|
|
import android.telephony.SmsManager
|
|
import android.telephony.SmsMessage
|
|
import android.text.TextUtils
|
|
import android.view.View
|
|
import android.widget.Toast
|
|
import com.android.mms.MmsConfig
|
|
import com.android.mms.dom.smil.parser.SmilXmlSerializer
|
|
import com.android.mms.util.DownloadManager
|
|
import com.android.mms.util.RateController
|
|
import com.google.android.mms.ContentType
|
|
import com.google.android.mms.InvalidHeaderValueException
|
|
import com.google.android.mms.MMSPart
|
|
import com.google.android.mms.MmsException
|
|
import com.google.android.mms.pdu_alt.*
|
|
import com.google.android.mms.smil.SmilHelper
|
|
import com.klinker.android.logger.Log
|
|
import com.klinker.android.send_message.*
|
|
import java.io.*
|
|
import java.util.*
|
|
|
|
/**
|
|
* Class to process transaction requests for sending
|
|
*
|
|
* @author Jake Klinker
|
|
*
|
|
* Sets context and settings
|
|
*
|
|
* @param context is the context of the activity or service
|
|
*/
|
|
class NornTransaction constructor(private val context: Context) {
|
|
|
|
private var explicitSentSmsReceiver: Intent? = null
|
|
private var explicitSentMmsReceiver: Intent? = null
|
|
private var explicitDeliveredSmsReceiver: Intent? = null
|
|
|
|
private var saveMessage = true
|
|
|
|
var SMS_SENT = ".SMS_SENT"
|
|
var SMS_DELIVERED = ".SMS_DELIVERED"
|
|
|
|
init {
|
|
SMS_SENT = context.packageName + SMS_SENT
|
|
SMS_DELIVERED = context.packageName + SMS_DELIVERED
|
|
|
|
if (NOTIFY_SMS_FAILURE == ".NOTIFY_SMS_FAILURE") {
|
|
NOTIFY_SMS_FAILURE = context.packageName + NOTIFY_SMS_FAILURE
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Evoyer un message à partir d'une instance de [NornMessage]
|
|
*
|
|
* @param message Message à envoyer
|
|
* @param insert Si Vrai le message doit être inséré dans la base de données, Faux sinon
|
|
* cela permet de cacher les messages d'échange de clés à l'utilisateur
|
|
* @param raw Si Vrai le message sera envoyé en clair, sinon le message sera chiffré en fonction
|
|
* des propriétés de la conversation
|
|
*/
|
|
fun sendNewMessage(message: NornMessage, insert: Boolean, raw: Boolean) {
|
|
if (message.medias.isEmpty()) {
|
|
this.sendSmsMessage(message, insert, raw)
|
|
} else {
|
|
try {
|
|
Looper.prepare()
|
|
} catch (e: Exception) {}
|
|
|
|
RateController.init(context)
|
|
DownloadManager.init(context)
|
|
this.sendMmsMessage(message)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Optional: define a [BroadcastReceiver] that will get started when Android notifies us that the SMS has
|
|
* been marked as "sent". If you do not define a receiver here, it will look for the .SMS_SENT receiver
|
|
* that was defined in the AndroidManifest, as discussed in the README.md.
|
|
*
|
|
* @param intent the receiver that you want to start when the message gets marked as sent.
|
|
*/
|
|
fun setExplicitBroadcastForSentSms(intent: Intent): NornTransaction {
|
|
explicitSentSmsReceiver = intent
|
|
return this
|
|
}
|
|
|
|
/**
|
|
* Optional: define a [BroadcastReceiver] that will get started when Android notifies us that the MMS has
|
|
* been marked as "sent". If you do not define a receiver here, it will look for the .MMS_SENT receiver
|
|
* that was defined in the AndroidManifest, as discussed in the README.md.
|
|
*
|
|
* @param intent the receiver that you want to start when the message gets marked as sent.
|
|
*/
|
|
fun setExplicitBroadcastForSentMms(intent: Intent): NornTransaction {
|
|
explicitSentMmsReceiver = intent
|
|
return this
|
|
}
|
|
|
|
/**
|
|
* Optional: define a [BroadcastReceiver] that will get started when Android notifies us that the SMS has
|
|
* been marked as "delivered". If you do not define a receiver here, it will look for the .SMS_DELIVERED
|
|
* receiver that was defined in the AndroidManifest, as discussed in the README.md.
|
|
*
|
|
*
|
|
* Providing a receiver here does not guarantee that it will ever get started. If the [Settings]
|
|
* object does not have delivery reports turned on, this receiver will never get called.
|
|
*
|
|
* @param intent the receiver that you want to start when the message gets marked as sent.
|
|
*/
|
|
fun setExplicitBroadcastForDeliveredSms(intent: Intent): NornTransaction {
|
|
explicitDeliveredSmsReceiver = intent
|
|
return this
|
|
}
|
|
|
|
private fun sendSmsMessage(message: NornMessage, insert: Boolean, raw: Boolean) {
|
|
// envoyer le message chiffré si la conversation est sécurisée
|
|
val body = message.messageBody(raw)
|
|
|
|
// save the message for each of the addresses
|
|
for (i in message.addresses.indices) {
|
|
var sentPI : PendingIntent? = null
|
|
var deliveredPI : PendingIntent? = null
|
|
|
|
if (insert) {
|
|
message.insertMessage(context).get()
|
|
Log.v("send_transaction", "message id: " + message.id)
|
|
|
|
// set up sent and delivered pending intents to be used with message request
|
|
val sentIntent: Intent?
|
|
if (explicitSentSmsReceiver == null) {
|
|
sentIntent = Intent(SMS_SENT)
|
|
BroadcastUtils.addClassName(context, sentIntent, SMS_SENT)
|
|
} else {
|
|
sentIntent = explicitSentSmsReceiver
|
|
}
|
|
|
|
sentIntent!!.putExtra("message_id", message.id)
|
|
sentPI = PendingIntent.getBroadcast(context, message.id.toInt(), sentIntent,
|
|
PendingIntent.FLAG_UPDATE_CURRENT)
|
|
|
|
val deliveredIntent: Intent?
|
|
if (explicitDeliveredSmsReceiver == null) {
|
|
deliveredIntent = Intent(SMS_DELIVERED)
|
|
BroadcastUtils.addClassName(context, deliveredIntent, SMS_DELIVERED)
|
|
} else {
|
|
deliveredIntent = explicitDeliveredSmsReceiver
|
|
}
|
|
|
|
deliveredIntent!!.putExtra("message_id", message.id)
|
|
deliveredPI = PendingIntent.getBroadcast(context, message.id.toInt(),
|
|
deliveredIntent, PendingIntent.FLAG_UPDATE_CURRENT)
|
|
}
|
|
|
|
val sPI = ArrayList<PendingIntent?>()
|
|
val dPI = ArrayList<PendingIntent?>()
|
|
|
|
val smsManager = SmsManagerFactory.createSmsManager(settings)
|
|
Log.v("send_transaction", "found sms manager")
|
|
|
|
if (settings.split) {
|
|
Log.v("send_transaction", "splitting message")
|
|
// figure out the length of supported message
|
|
val splitData = SmsMessage.calculateLength(body, false)
|
|
|
|
// we take the current length + the remaining length to get the total number of characters
|
|
// that message set can support, and then divide by the number of message that will require
|
|
// to get the length supported by a single message
|
|
var length = (body.length + splitData[2]) / splitData[0]
|
|
Log.v("send_transaction", "length: $length")
|
|
|
|
var counter = false
|
|
if (settings.splitCounter && body.length > length) {
|
|
counter = true
|
|
length -= 6
|
|
}
|
|
|
|
// get the split messages
|
|
val textToSend = splitByLength(body, length, counter)
|
|
|
|
// send each message part to each recipient attached to message
|
|
for (j in textToSend.indices) {
|
|
val parts = smsManager.divideMessage(textToSend[j])
|
|
|
|
for (k in parts.indices) {
|
|
sPI.add(sentPI)
|
|
dPI.add(if (settings.deliveryReports) deliveredPI else null)
|
|
}
|
|
|
|
Log.v("send_transaction", "sending split message")
|
|
smsManager.sendMultipartTextMessage(message.addresses[i], null, parts, sPI, dPI)
|
|
}
|
|
} else {
|
|
Log.v("send_transaction", "sending without splitting")
|
|
// send the message normally without forcing anything to be split
|
|
val parts = smsManager.divideMessage(body)
|
|
|
|
for (j in parts.indices) {
|
|
sPI.add(sentPI)
|
|
dPI.add(if (settings.deliveryReports) deliveredPI else null)
|
|
}
|
|
|
|
try {
|
|
Log.v("send_transaction", "sent message")
|
|
smsManager.sendMultipartTextMessage(message.addresses[i], null, parts, sPI, dPI)
|
|
} catch (e: Exception) {
|
|
// whoops...
|
|
Log.v("send_transaction", "error sending message")
|
|
Log.e(TAG, "exception thrown", e)
|
|
|
|
try {
|
|
(context as Activity).window.decorView.findViewById<View>(android.R.id.content).post({ Toast.makeText(context, "Message could not be sent", Toast.LENGTH_LONG).show() })
|
|
} catch (f: Exception) {}
|
|
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
private fun sendMmsMessage(message: NornMessage) {
|
|
// create the medias to send
|
|
val data = ArrayList<MMSPart>()
|
|
|
|
// add any extra medias according to their mimeType set in the message
|
|
// eg. videos, audio, contact cards, location maybe?
|
|
for (m in message.medias) {
|
|
val part = MMSPart()
|
|
part.Name = m.name
|
|
part.MimeType = m.mimeType
|
|
part.Data = m.getContent(context, message)
|
|
data.add(part)
|
|
}
|
|
|
|
val text = message.messageBody(false)
|
|
if (message.text != "") {
|
|
// add text to the end of the part and send
|
|
val part = MMSPart()
|
|
part.Name = "text"
|
|
part.MimeType = "text/plain"
|
|
part.Data = text.toByteArray()
|
|
data.add(part)
|
|
}
|
|
|
|
message.insertMessage(context).get()
|
|
|
|
Log.v(TAG, "using system method for sending")
|
|
sendMmsThroughSystem(context, message.subject, data, message.addresses, explicitSentMmsReceiver)
|
|
}
|
|
|
|
class MessageInfo {
|
|
var token: Long = 0
|
|
var location: Uri? = null
|
|
var bytes: ByteArray? = null
|
|
}
|
|
|
|
// splits text and adds split counter when applicable
|
|
private fun splitByLength(s: String, chunkSize: Int, counter: Boolean): Array<String?> {
|
|
val arraySize = Math.ceil(s.length.toDouble() / chunkSize).toInt()
|
|
|
|
val returnArray = arrayOfNulls<String>(arraySize)
|
|
|
|
var index = 0
|
|
run {
|
|
var i = 0
|
|
while (i < s.length) {
|
|
if (s.length - i < chunkSize) {
|
|
returnArray[index++] = s.substring(i)
|
|
} else {
|
|
returnArray[index++] = s.substring(i, i + chunkSize)
|
|
}
|
|
i = i + chunkSize
|
|
}
|
|
}
|
|
|
|
if (counter && returnArray.size > 1) {
|
|
for (i in returnArray.indices) {
|
|
returnArray[i] = "(" + (i + 1) + "/" + returnArray.size + ") " + returnArray[i]
|
|
}
|
|
}
|
|
|
|
return returnArray
|
|
}
|
|
|
|
/**
|
|
* A method for checking whether or not a certain message will be sent as mms depending on its contents and the settings
|
|
*
|
|
* @param message is the message that you are checking against
|
|
* @return true if the message will be mms, otherwise false
|
|
*/
|
|
fun checkMMS(message: Message): Boolean {
|
|
return message.images.size != 0 ||
|
|
message.parts.size != 0 ||
|
|
settings.sendLongAsMms && Utils.getNumPages(settings, message.text) > settings.sendLongAsMmsAfter ||
|
|
message.addresses.size > 1 && settings.group ||
|
|
message.subject != null
|
|
}
|
|
|
|
companion object {
|
|
|
|
private val TAG = "NornTransaction"
|
|
val settings by lazy { Settings() }
|
|
|
|
var NOTIFY_SMS_FAILURE = ".NOTIFY_SMS_FAILURE"
|
|
val MMS_ERROR = "com.klinker.android.send_message.MMS_ERROR"
|
|
val REFRESH = "com.klinker.android.send_message.REFRESH"
|
|
val MMS_PROGRESS = "com.klinker.android.send_message.MMS_PROGRESS"
|
|
val NOTIFY_OF_DELIVERY = "com.klinker.android.send_message.NOTIFY_DELIVERY"
|
|
val NOTIFY_OF_MMS = "com.klinker.android.messaging.NEW_MMS_DOWNLOADED"
|
|
|
|
val NO_THREAD_ID: Long = 0
|
|
|
|
@Throws(MmsException::class)
|
|
fun getBytes(context: Context, saveMessage: Boolean, recipients: Array<String>,
|
|
parts: Array<MMSPart>?, subject: String?): MessageInfo {
|
|
val sendRequest = SendReq()
|
|
|
|
// create send request addresses
|
|
for (i in recipients.indices) {
|
|
val phoneNumbers = EncodedStringValue.extract(recipients[i])
|
|
|
|
if (phoneNumbers != null && phoneNumbers.size > 0) {
|
|
sendRequest.addTo(phoneNumbers[0])
|
|
}
|
|
}
|
|
|
|
if (subject != null) {
|
|
sendRequest.subject = EncodedStringValue(subject)
|
|
}
|
|
|
|
sendRequest.date = Calendar.getInstance().timeInMillis / 1000L
|
|
|
|
try {
|
|
sendRequest.from = EncodedStringValue(Utils.getMyPhoneNumber(context))
|
|
} catch (e: Exception) {
|
|
Log.e(TAG, "error getting from address", e)
|
|
}
|
|
|
|
val pduBody = PduBody()
|
|
|
|
// assign parts to the pdu body which contains sending data
|
|
var size: Long = 0
|
|
if (parts != null) {
|
|
for (i in parts.indices) {
|
|
val part = parts[i]
|
|
if (part != null) {
|
|
try {
|
|
val partPdu = PduPart()
|
|
partPdu.name = part.Name.toByteArray()
|
|
partPdu.contentType = part.MimeType.toByteArray()
|
|
|
|
if (part.MimeType.startsWith("text")) {
|
|
partPdu.charset = CharacterSets.UTF_8
|
|
}
|
|
// Set Content-Location.
|
|
partPdu.contentLocation = part.Name.toByteArray()
|
|
val index = part.Name.lastIndexOf(".")
|
|
val contentId = if (index == -1)
|
|
part.Name
|
|
else
|
|
part.Name.substring(0, index)
|
|
partPdu.contentId = contentId.toByteArray()
|
|
partPdu.data = part.Data
|
|
|
|
pduBody.addPart(partPdu)
|
|
size += (2 * part.Name.toByteArray().size + part.MimeType.toByteArray().size + part.Data.size + contentId.toByteArray().size).toLong()
|
|
} catch (e: Exception) {
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
val out = ByteArrayOutputStream()
|
|
SmilXmlSerializer.serialize(SmilHelper.createSmilDocument(pduBody), out)
|
|
val smilPart = PduPart()
|
|
smilPart.contentId = "smil".toByteArray()
|
|
smilPart.contentLocation = "smil.xml".toByteArray()
|
|
smilPart.contentType = ContentType.APP_SMIL.toByteArray()
|
|
smilPart.data = out.toByteArray()
|
|
pduBody.addPart(0, smilPart)
|
|
|
|
sendRequest.body = pduBody
|
|
|
|
Log.v(TAG, "setting message size to $size bytes")
|
|
sendRequest.messageSize = size
|
|
|
|
// add everything else that could be set
|
|
sendRequest.priority = PduHeaders.PRIORITY_NORMAL
|
|
sendRequest.deliveryReport = PduHeaders.VALUE_NO
|
|
sendRequest.expiry = (1000 * 60 * 60 * 24 * 7).toLong()
|
|
sendRequest.messageClass = PduHeaders.MESSAGE_CLASS_PERSONAL_STR.toByteArray()
|
|
sendRequest.readReport = PduHeaders.VALUE_NO
|
|
|
|
// create byte array which will actually be sent
|
|
val composer = PduComposer(context, sendRequest)
|
|
val bytesToSend: ByteArray
|
|
|
|
try {
|
|
bytesToSend = composer.make()
|
|
} catch (e: OutOfMemoryError) {
|
|
throw MmsException("Out of memory!")
|
|
}
|
|
|
|
val info = MessageInfo()
|
|
info.bytes = bytesToSend
|
|
|
|
if (saveMessage) {
|
|
try {
|
|
val persister = PduPersister.getPduPersister(context)
|
|
info.location = persister.persist(sendRequest, Uri.parse("content://mms/outbox"), true, settings.group, null)
|
|
} catch (e: Exception) {
|
|
Log.v("sending_mms_library", "error saving mms message")
|
|
Log.e(TAG, "exception thrown", e)
|
|
|
|
// use the old way if something goes wrong with the persister
|
|
insert(context, recipients, parts, subject)
|
|
}
|
|
|
|
}
|
|
|
|
try {
|
|
val query = context.contentResolver.query(info.location!!, arrayOf("thread_id"), null, null, null)
|
|
if (query != null && query.moveToFirst()) {
|
|
info.token = query.getLong(query.getColumnIndex("thread_id"))
|
|
query.close()
|
|
} else {
|
|
// just default sending token for what I had before
|
|
info.token = 4444L
|
|
}
|
|
} catch (e: Exception) {
|
|
Log.e(TAG, "exception thrown", e)
|
|
info.token = 4444L
|
|
}
|
|
|
|
return info
|
|
}
|
|
|
|
val DEFAULT_EXPIRY_TIME = (7 * 24 * 60 * 60).toLong()
|
|
val DEFAULT_PRIORITY = PduHeaders.PRIORITY_NORMAL
|
|
|
|
private fun sendMmsThroughSystem(context: Context, subject: String, parts: List<MMSPart>,
|
|
addresses: Array<String>, explicitSentMmsReceiver: Intent?) {
|
|
try {
|
|
val fileName = "send." + Math.abs(Random().nextLong()).toString() + ".dat"
|
|
val mSendFile = File(context.cacheDir, fileName)
|
|
|
|
val sendReq = buildPdu(context, addresses, subject, parts)
|
|
val persister = PduPersister.getPduPersister(context)
|
|
val messageUri = persister.persist(sendReq, Uri.parse("content://mms/outbox"),
|
|
true, settings.group, null)
|
|
|
|
val intent: Intent
|
|
if (explicitSentMmsReceiver == null) {
|
|
intent = Intent(MmsSentReceiver.MMS_SENT)
|
|
BroadcastUtils.addClassName(context, intent, MmsSentReceiver.MMS_SENT)
|
|
} else {
|
|
intent = explicitSentMmsReceiver
|
|
}
|
|
|
|
intent.putExtra(MmsSentReceiver.EXTRA_CONTENT_URI, messageUri.toString())
|
|
intent.putExtra(MmsSentReceiver.EXTRA_FILE_PATH, mSendFile.path)
|
|
val pendingIntent = PendingIntent.getBroadcast(
|
|
context, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT)
|
|
|
|
val writerUri = Uri.Builder()
|
|
.authority(context.packageName + ".MmsFileProvider")
|
|
.path(fileName)
|
|
.scheme(ContentResolver.SCHEME_CONTENT)
|
|
.build()
|
|
var writer: FileOutputStream? = null
|
|
var contentUri: Uri? = null
|
|
try {
|
|
writer = FileOutputStream(mSendFile)
|
|
writer.write(PduComposer(context, sendReq).make())
|
|
contentUri = writerUri
|
|
} catch (e: IOException) {
|
|
Log.e(TAG, "Error writing send file", e)
|
|
} finally {
|
|
if (writer != null) {
|
|
try {
|
|
writer.close()
|
|
} catch (e: IOException) {
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
val configOverrides = Bundle()
|
|
configOverrides.putBoolean(SmsManager.MMS_CONFIG_GROUP_MMS_ENABLED, settings.group)
|
|
val httpParams = MmsConfig.getHttpParams()
|
|
if (!TextUtils.isEmpty(httpParams)) {
|
|
configOverrides.putString(SmsManager.MMS_CONFIG_HTTP_PARAMS, httpParams)
|
|
}
|
|
configOverrides.putInt(SmsManager.MMS_CONFIG_MAX_MESSAGE_SIZE, MmsConfig.getMaxMessageSize())
|
|
|
|
if (contentUri != null) {
|
|
SmsManagerFactory.createSmsManager(settings).sendMultimediaMessage(context,
|
|
contentUri, null, configOverrides, pendingIntent)
|
|
} else {
|
|
Log.e(TAG, "Error writing sending Mms")
|
|
try {
|
|
pendingIntent.send(SmsManager.MMS_ERROR_IO_ERROR)
|
|
} catch (ex: PendingIntent.CanceledException) {
|
|
Log.e(TAG, "Mms pending intent cancelled?", ex)
|
|
}
|
|
|
|
}
|
|
} catch (e: Exception) {
|
|
Log.e(TAG, "error using system sending method", e)
|
|
}
|
|
|
|
}
|
|
|
|
private fun buildPdu(context: Context, recipients: Array<String>, subject: String,
|
|
parts: List<MMSPart>): SendReq {
|
|
val req = SendReq()
|
|
// From, per spec
|
|
val lineNumber = Utils.getMyPhoneNumber(context)
|
|
if (!TextUtils.isEmpty(lineNumber)) {
|
|
req.from = EncodedStringValue(lineNumber)
|
|
}
|
|
// To
|
|
for (recipient in recipients) {
|
|
req.addTo(EncodedStringValue(recipient))
|
|
}
|
|
// Subject
|
|
if (!TextUtils.isEmpty(subject)) {
|
|
req.subject = EncodedStringValue(subject)
|
|
}
|
|
// Date
|
|
req.date = System.currentTimeMillis() / 1000
|
|
// Body
|
|
val body = PduBody()
|
|
// Add text part. Always add a smil part for compatibility, without it there
|
|
// may be issues on some carriers/client apps
|
|
var size = 0
|
|
for (i in parts.indices) {
|
|
val part = parts[i]
|
|
size += addTextPart(body, part, i)
|
|
}
|
|
|
|
// add a SMIL document for compatibility
|
|
val out = ByteArrayOutputStream()
|
|
SmilXmlSerializer.serialize(SmilHelper.createSmilDocument(body), out)
|
|
val smilPart = PduPart()
|
|
smilPart.contentId = "smil".toByteArray()
|
|
smilPart.contentLocation = "smil.xml".toByteArray()
|
|
smilPart.contentType = ContentType.APP_SMIL.toByteArray()
|
|
smilPart.data = out.toByteArray()
|
|
body.addPart(0, smilPart)
|
|
|
|
req.body = body
|
|
// Message size
|
|
req.messageSize = size.toLong()
|
|
// Message class
|
|
req.messageClass = PduHeaders.MESSAGE_CLASS_PERSONAL_STR.toByteArray()
|
|
// Expiry
|
|
req.expiry = DEFAULT_EXPIRY_TIME
|
|
try {
|
|
// Priority
|
|
req.priority = DEFAULT_PRIORITY
|
|
// Delivery report
|
|
req.deliveryReport = PduHeaders.VALUE_NO
|
|
// Read report
|
|
req.readReport = PduHeaders.VALUE_NO
|
|
} catch (e: InvalidHeaderValueException) {
|
|
}
|
|
|
|
return req
|
|
}
|
|
|
|
private fun addTextPart(pb: PduBody, p: MMSPart, id: Int): Int {
|
|
val filename = p.Name
|
|
val part = PduPart()
|
|
// Set Charset if it's a text medias.
|
|
if (p.MimeType.startsWith("text")) {
|
|
part.charset = CharacterSets.UTF_8
|
|
}
|
|
// Set Content-Type.
|
|
part.contentType = p.MimeType.toByteArray()
|
|
// Set Content-Location.
|
|
part.contentLocation = filename.toByteArray()
|
|
val index = filename.lastIndexOf(".")
|
|
val contentId = if (index == -1)
|
|
filename
|
|
else
|
|
filename.substring(0, index)
|
|
part.contentId = contentId.toByteArray()
|
|
part.data = p.Data
|
|
pb.addPart(part)
|
|
|
|
return part.data.size
|
|
}
|
|
|
|
private fun insert(context: Context, to: Array<String>, parts: Array<MMSPart>?, subject: String?): Uri? {
|
|
try {
|
|
val destUri = Uri.parse("content://mms")
|
|
|
|
val recipients = HashSet<String>()
|
|
recipients.addAll(Arrays.asList(*to))
|
|
val thread_id = Utils.getOrCreateThreadId(context, recipients)
|
|
|
|
// Create a dummy sms
|
|
val dummyValues = ContentValues()
|
|
dummyValues.put("thread_id", thread_id)
|
|
dummyValues.put("body", " ")
|
|
val dummySms = context.contentResolver.insert(Uri.parse("content://sms/sent"), dummyValues)
|
|
|
|
// Create a new message entry
|
|
val now = System.currentTimeMillis()
|
|
val mmsValues = ContentValues()
|
|
mmsValues.put("thread_id", thread_id)
|
|
mmsValues.put("date", now / 1000L)
|
|
mmsValues.put("msg_box", 4)
|
|
//mmsValues.put("m_id", System.currentTimeMillis());
|
|
mmsValues.put("read", true)
|
|
mmsValues.put("sub", subject ?: "")
|
|
mmsValues.put("sub_cs", 106)
|
|
mmsValues.put("ct_t", "application/vnd.wap.multipart.related")
|
|
|
|
var imageBytes: Long = 0
|
|
|
|
for (part in parts!!) {
|
|
imageBytes += part.Data.size.toLong()
|
|
}
|
|
|
|
mmsValues.put("exp", imageBytes)
|
|
|
|
mmsValues.put("m_cls", "personal")
|
|
mmsValues.put("m_type", 128) // 132 (RETRIEVE CONF) 130 (NOTIF IND) 128 (SEND REQ)
|
|
mmsValues.put("v", 19)
|
|
mmsValues.put("pri", 129)
|
|
mmsValues.put("tr_id", "T" + java.lang.Long.toHexString(now))
|
|
mmsValues.put("resp_st", 128)
|
|
|
|
// Insert message
|
|
val res = context.contentResolver.insert(destUri, mmsValues)
|
|
val messageId = res!!.lastPathSegment.trim { it <= ' ' }
|
|
|
|
// Create part
|
|
for (part in parts) {
|
|
if (part.MimeType.startsWith("image")) {
|
|
createPartImage(context, messageId, part.Data, part.MimeType)
|
|
} else if (part.MimeType.startsWith("text")) {
|
|
createPartText(context, messageId, String(part.Data, Charsets.UTF_8))
|
|
}
|
|
}
|
|
|
|
// Create addresses
|
|
for (addr in to) {
|
|
createAddr(context, messageId, addr)
|
|
}
|
|
|
|
//res = Uri.parse(destUri + "/" + messageId);
|
|
|
|
// Delete dummy sms
|
|
context.contentResolver.delete(dummySms!!, null, null)
|
|
|
|
return res
|
|
} catch (e: Exception) {
|
|
Log.v("sending_mms_library", "still an error saving... :(")
|
|
Log.e(TAG, "exception thrown", e)
|
|
}
|
|
|
|
return null
|
|
}
|
|
|
|
// create the image part to be stored in database
|
|
@Throws(Exception::class)
|
|
private fun createPartImage(context: Context, id: String, imageBytes: ByteArray, mimeType: String): Uri {
|
|
val mmsPartValue = ContentValues()
|
|
mmsPartValue.put("mid", id)
|
|
mmsPartValue.put("ct", mimeType)
|
|
mmsPartValue.put("cid", "<" + System.currentTimeMillis() + ">")
|
|
val partUri = Uri.parse("content://mms/$id/part")
|
|
val res = context.contentResolver.insert(partUri, mmsPartValue)
|
|
|
|
// Add data to part
|
|
val os = context.contentResolver.openOutputStream(res!!)
|
|
val `is` = ByteArrayInputStream(imageBytes)
|
|
val buffer = ByteArray(256)
|
|
|
|
var len = `is`.read(buffer)
|
|
while (len != -1) {
|
|
os!!.write(buffer, 0, len)
|
|
len = `is`.read(buffer)
|
|
}
|
|
|
|
os!!.close()
|
|
`is`.close()
|
|
|
|
return res
|
|
}
|
|
|
|
// create the text part to be stored in database
|
|
@Throws(Exception::class)
|
|
private fun createPartText(context: Context, id: String, text: String): Uri? {
|
|
val mmsPartValue = ContentValues()
|
|
mmsPartValue.put("mid", id)
|
|
mmsPartValue.put("ct", "text/plain")
|
|
mmsPartValue.put("cid", "<" + System.currentTimeMillis() + ">")
|
|
mmsPartValue.put("text", text)
|
|
val partUri = Uri.parse("content://mms/$id/part")
|
|
|
|
return context.contentResolver.insert(partUri, mmsPartValue)
|
|
}
|
|
|
|
// add address to the request
|
|
@Throws(Exception::class)
|
|
private fun createAddr(context: Context, id: String, addr: String): Uri? {
|
|
val addrValues = ContentValues()
|
|
addrValues.put("address", addr)
|
|
addrValues.put("charset", "106")
|
|
addrValues.put("type", 151) // TO
|
|
val addrUri = Uri.parse("content://mms/$id/addr")
|
|
|
|
return context.contentResolver.insert(addrUri, addrValues)
|
|
}
|
|
}
|
|
|
|
}
|