/*
* 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 com.klinker.android.send_message;
import android.app.Activity;
import android.app.PendingIntent;
import android.content.*;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.Uri;
import android.net.wifi.WifiManager;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.telephony.PhoneNumberUtils;
import android.telephony.SmsManager;
import android.telephony.SmsMessage;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import com.android.mms.MmsConfig;
import com.android.mms.service_alt.MmsNetworkManager;
import com.android.mms.service_alt.MmsRequestManager;
import com.android.mms.service_alt.SendRequest;
import com.klinker.android.logger.Log;
import android.widget.Toast;
import com.android.mms.dom.smil.parser.SmilXmlSerializer;
import com.android.mms.transaction.HttpUtils;
import com.android.mms.transaction.MmsMessageSender;
import com.android.mms.transaction.ProgressCallbackEntity;
import com.android.mms.util.DownloadManager;
import com.android.mms.util.RateController;
import com.google.android.mms.*;
import com.google.android.mms.pdu_alt.*;
import com.google.android.mms.smil.SmilHelper;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.*;
import java.util.concurrent.ExecutionException;
/**
* Class to process transaction requests for sending
*
* @author Jake Klinker
*/
public class Transaction {
private static final String TAG = "Transaction";
public static Settings settings;
private Context context;
private boolean saveMessage = true;
public String SMS_SENT = ".SMS_SENT";
public String SMS_DELIVERED = ".SMS_DELIVERED";
public static String NOTIFY_SMS_FAILURE = ".NOTIFY_SMS_FAILURE";
public static final String MMS_ERROR = "com.klinker.android.send_message.MMS_ERROR";
public static final String REFRESH = "com.klinker.android.send_message.REFRESH";
public static final String MMS_PROGRESS = "com.klinker.android.send_message.MMS_PROGRESS";
public static final String NOTIFY_OF_DELIVERY = "com.klinker.android.send_message.NOTIFY_DELIVERY";
public static final String NOTIFY_OF_MMS = "com.klinker.android.messaging.NEW_MMS_DOWNLOADED";
public static final long NO_THREAD_ID = 0;
/**
* Sets context and initializes settings to default values
*
* @param context is the context of the activity or service
*/
public Transaction(Context context) {
this(context, new Settings());
}
/**
* Sets context and settings
*
* @param context is the context of the activity or service
* @param settings is the settings object to process send requests through
*/
public Transaction(Context context, Settings settings) {
this.settings = settings;
this.context = context;
SMS_SENT = context.getPackageName() + SMS_SENT;
SMS_DELIVERED = context.getPackageName() + SMS_DELIVERED;
if (NOTIFY_SMS_FAILURE.equals(".NOTIFY_SMS_FAILURE")) {
NOTIFY_SMS_FAILURE = context.getPackageName() + NOTIFY_SMS_FAILURE;
}
}
/**
* Called to send a new message depending on settings and provided Message object
* If you want to send message as mms, call this from the UI thread
*
* @param message is the message that you want to send
* @param threadId is the thread id of who to send the message to (can also be set to Transaction.NO_THREAD_ID)
*/
public void sendNewMessage(Message message, long threadId) {
this.saveMessage = message.getSave();
// if message:
// 1) Has images attached
// or
// 1) is enabled to send long messages as mms
// 2) number of pages for that sms exceeds value stored in settings for when to send the mms by
// 3) prefer voice is disabled
// or
// 1) more than one address is attached
// 2) group messaging is enabled
//
// then, send as MMS, else send as Voice or SMS
if (checkMMS(message)) {
try { Looper.prepare(); } catch (Exception e) { }
RateController.init(context);
DownloadManager.init(context);
sendMmsMessage(message.getText(), message.getAddresses(), message.getImages(), message.getImageNames(), message.getParts(), message.getSubject());
} else {
sendSmsMessage(message.getText(), message.getAddresses(), threadId, message.getDelay());
}
}
private void sendSmsMessage(String text, String[] addresses, long threadId, int delay) {
Log.v("send_transaction", "message text: " + text);
Uri messageUri = null;
int messageId = 0;
if (saveMessage) {
Log.v("send_transaction", "saving message");
// add signature to original text to be saved in database (does not strip unicode for saving though)
if (!settings.getSignature().equals("")) {
text += "\n" + settings.getSignature();
}
// save the message for each of the addresses
for (int i = 0; i < addresses.length; i++) {
Calendar cal = Calendar.getInstance();
ContentValues values = new ContentValues();
values.put("address", addresses[i]);
values.put("body", settings.getStripUnicode() ? StripAccents.stripAccents(text) : text);
values.put("date", cal.getTimeInMillis() + "");
values.put("read", 1);
values.put("type", 4);
// attempt to create correct thread id if one is not supplied
if (threadId == NO_THREAD_ID || addresses.length > 1) {
threadId = Utils.getOrCreateThreadId(context, addresses[i]);
}
Log.v("send_transaction", "saving message with thread id: " + threadId);
values.put("thread_id", threadId);
messageUri = context.getContentResolver().insert(Uri.parse("content://sms/"), values);
Log.v("send_transaction", "inserted to uri: " + messageUri);
Cursor query = context.getContentResolver().query(messageUri, new String[] {"_id"}, null, null, null);
if (query != null && query.moveToFirst()) {
messageId = query.getInt(0);
query.close();
}
Log.v("send_transaction", "message id: " + messageId);
// set up sent and delivered pending intents to be used with message request
Intent sentIntent = new Intent(SMS_SENT);
sentIntent.putExtra("message_uri", messageUri == null ? "" : messageUri.toString());
BroadcastUtils.addClassName(context, sentIntent, SMS_SENT);
PendingIntent sentPI = PendingIntent.getBroadcast(
context, messageId, sentIntent, PendingIntent.FLAG_UPDATE_CURRENT);
Intent deliveredIntent = new Intent(SMS_DELIVERED);
deliveredIntent.putExtra("message_uri", messageUri == null ? "" : messageUri.toString());
BroadcastUtils.addClassName(context, deliveredIntent, SMS_DELIVERED);
PendingIntent deliveredPI = PendingIntent.getBroadcast(
context, messageId, deliveredIntent, PendingIntent.FLAG_UPDATE_CURRENT);
ArrayList<PendingIntent> sPI = new ArrayList<PendingIntent>();
ArrayList<PendingIntent> dPI = new ArrayList<PendingIntent>();
String body = text;
// edit the body of the text if unicode needs to be stripped
if (settings.getStripUnicode()) {
body = StripAccents.stripAccents(body);
}
if (!settings.getPreText().equals("")) {
body = settings.getPreText() + " " + body;
}
SmsManager smsManager = SmsManagerFactory.createSmsManager(settings);
Log.v("send_transaction", "found sms manager");
if (settings.getSplit()) {
Log.v("send_transaction", "splitting message");
// figure out the length of supported message
int[] 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
int length = (body.length() + splitData[2]) / splitData[0];
Log.v("send_transaction", "length: " + length);
boolean counter = false;
if (settings.getSplitCounter() && body.length() > length) {
counter = true;
length -= 6;
}
// get the split messages
String[] textToSend = splitByLength(body, length, counter);
// send each message part to each recipient attached to message
for (int j = 0; j < textToSend.length; j++) {
ArrayList<String> parts = smsManager.divideMessage(textToSend[j]);
for (int k = 0; k < parts.size(); k++) {
sPI.add(saveMessage ? sentPI : null);
dPI.add(settings.getDeliveryReports() && saveMessage ? deliveredPI : null);
}
Log.v("send_transaction", "sending split message");
sendDelayedSms(smsManager, addresses[i], parts, sPI, dPI, delay, messageUri);
}
} else {
Log.v("send_transaction", "sending without splitting");
// send the message normally without forcing anything to be split
ArrayList<String> parts = smsManager.divideMessage(body);
for (int j = 0; j < parts.size(); j++) {
sPI.add(saveMessage ? sentPI : null);
dPI.add(settings.getDeliveryReports() && saveMessage ? deliveredPI : null);
}
if (Utils.isDefaultSmsApp(context)) {
try {
Log.v("send_transaction", "sent message");
sendDelayedSms(smsManager, addresses[i], parts, sPI, dPI, delay, messageUri);
} catch (Exception e) {
// whoops...
Log.v("send_transaction", "error sending message");
Log.e(TAG, "exception thrown", e);
try {
((Activity) context).getWindow().getDecorView().findViewById(android.R.id.content).post(new Runnable() {
@Override
public void run() {
Toast.makeText(context, "Message could not be sent", Toast.LENGTH_LONG).show();
}
});
} catch (Exception f) { }
}
} else {
// not default app, so just fire it off right away for the hell of it
smsManager.sendMultipartTextMessage(addresses[i], null, parts, sPI, dPI);
}
}
}
}
}
private void sendDelayedSms(final SmsManager smsManager, final String address,
final ArrayList<String> parts, final ArrayList<PendingIntent> sPI,
final ArrayList<PendingIntent> dPI, final int delay, final Uri messageUri) {
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(delay);
} catch (Exception e) { }
if (checkIfMessageExistsAfterDelay(messageUri)) {
Log.v("send_transaction", "message sent after delay");
try {
smsManager.sendMultipartTextMessage(address, null, parts, sPI, dPI);
} catch (Exception e) {
Log.e(TAG, "exception thrown", e);
}
} else {
Log.v("send_transaction", "message not sent after delay, no longer exists");
}
}
}).start();
}
private boolean checkIfMessageExistsAfterDelay(Uri messageUti) {
Cursor query = context.getContentResolver().query(messageUti, new String[] {"_id"}, null, null, null);
if (query != null && query.moveToFirst()) {
query.close();
return true;
} else {
return false;
}
}
private void sendMmsMessage(String text, String[] addresses, Bitmap[] image, String[] imageNames, List<Message.Part> parts, String subject) {
// merge the string[] of addresses into a single string so they can be inserted into the database easier
String address = "";
for (int i = 0; i < addresses.length; i++) {
address += addresses[i] + " ";
}
address = address.trim();
// create the parts to send
ArrayList<MMSPart> data = new ArrayList<MMSPart>();
for (int i = 0; i < image.length; i++) {
// turn bitmap into byte array to be stored
byte[] imageBytes = Message.bitmapToByteArray(image[i]);
MMSPart part = new MMSPart();
part.MimeType = "image/jpeg";
part.Name = (imageNames != null) ? imageNames[i] : ("image_" + System.currentTimeMillis());
part.Data = imageBytes;
data.add(part);
}
// add any extra media according to their mimeType set in the message
// eg. videos, audio, contact cards, location maybe?
if (parts != null) {
for (Message.Part p : parts) {
MMSPart part = new MMSPart();
if (p.getName() != null) {
part.Name = p.getName();
} else {
part.Name = p.getContentType().split("/")[0];
}
part.MimeType = p.getContentType();
part.Data = p.getMedia();
data.add(part);
}
}
if (text != null && !text.equals("")) {
// add text to the end of the part and send
MMSPart part = new MMSPart();
part.Name = "text";
part.MimeType = "text/plain";
part.Data = text.getBytes();
data.add(part);
}
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
MessageInfo info = null;
try {
info = getBytes(context, saveMessage, address.split(" "),
data.toArray(new MMSPart[data.size()]), subject);
MmsMessageSender sender = new MmsMessageSender(context, info.location, info.bytes.length);
sender.sendMessage(info.token);
IntentFilter filter = new IntentFilter();
filter.addAction(ProgressCallbackEntity.PROGRESS_STATUS_ACTION);
BroadcastReceiver receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
int progress = intent.getIntExtra("progress", -3);
Log.v("sending_mms_library", "progress: " + progress);
// send progress broadcast to update ui if desired...
Intent progressIntent = new Intent(MMS_PROGRESS);
progressIntent.putExtra("progress", progress);
BroadcastUtils.sendExplicitBroadcast(context, progressIntent, MMS_PROGRESS);
if (progress == ProgressCallbackEntity.PROGRESS_COMPLETE) {
BroadcastUtils.sendExplicitBroadcast(context, new Intent(), REFRESH);
try {
context.unregisterReceiver(this);
} catch (Exception e) {
// TODO fix me
// receiver is not registered force close error... hmm.
}
} else if (progress == ProgressCallbackEntity.PROGRESS_ABORT) {
// This seems to get called only after the progress has reached 100 and
// then something else goes wrong, so here we will try and send again
// and see if it works
Log.v("sending_mms_library", "sending aborted for some reason...");
}
}
};
context.registerReceiver(receiver, filter);
} catch (Throwable e) {
Log.e(TAG, "exception thrown", e);
}
} else {
Log.v(TAG, "using lollipop method for sending sms");
if (settings.getUseSystemSending()) {
Log.v(TAG, "using system method for sending");
sendMmsThroughSystem(context, subject, data, addresses);
} else {
try {
MessageInfo info = getBytes(context, saveMessage, address.split(" "),
data.toArray(new MMSPart[data.size()]), subject);
MmsRequestManager requestManager = new MmsRequestManager(context, info.bytes);
SendRequest request = new SendRequest(requestManager, Utils.getDefaultSubscriptionId(),
info.location, null, null, null, null);
MmsNetworkManager manager = new MmsNetworkManager(context, Utils.getDefaultSubscriptionId());
request.execute(context, manager);
} catch (Exception e) {
Log.e(TAG, "error sending mms", e);
}
}
}
}
public static MessageInfo getBytes(Context context, boolean saveMessage, String[] recipients,
MMSPart[] parts, String subject)
throws MmsException {
final SendReq sendRequest = new SendReq();
// create send request addresses
for (int i = 0; i < recipients.length; i++) {
final EncodedStringValue[] phoneNumbers = EncodedStringValue.extract(recipients[i]);
if (phoneNumbers != null && phoneNumbers.length > 0) {
sendRequest.addTo(phoneNumbers[0]);
}
}
if (subject != null) {
sendRequest.setSubject(new EncodedStringValue(subject));
}
sendRequest.setDate(Calendar.getInstance().getTimeInMillis() / 1000L);
try {
sendRequest.setFrom(new EncodedStringValue(Utils.getMyPhoneNumber(context)));
} catch (Exception e) {
Log.e(TAG, "error getting from address", e);
}
final PduBody pduBody = new PduBody();
// assign parts to the pdu body which contains sending data
long size = 0;
if (parts != null) {
for (int i = 0; i < parts.length; i++) {
MMSPart part = parts[i];
if (part != null) {
try {
PduPart partPdu = new PduPart();
partPdu.setName(part.Name.getBytes());
partPdu.setContentType(part.MimeType.getBytes());
if (part.MimeType.startsWith("text")) {
partPdu.setCharset(CharacterSets.UTF_8);
}
// Set Content-Location.
partPdu.setContentLocation(part.Name.getBytes());
int index = part.Name.lastIndexOf(".");
String contentId = (index == -1) ? part.Name
: part.Name.substring(0, index);
partPdu.setContentId(contentId.getBytes());
partPdu.setData(part.Data);
pduBody.addPart(partPdu);
size += ((2 * part.Name.getBytes().length) + part.MimeType.getBytes().length + part.Data.length + contentId.getBytes().length);
} catch (Exception e) {
}
}
}
}
ByteArrayOutputStream out = new ByteArrayOutputStream();
SmilXmlSerializer.serialize(SmilHelper.createSmilDocument(pduBody), out);
PduPart smilPart = new PduPart();
smilPart.setContentId("smil".getBytes());
smilPart.setContentLocation("smil.xml".getBytes());
smilPart.setContentType(ContentType.APP_SMIL.getBytes());
smilPart.setData(out.toByteArray());
pduBody.addPart(0, smilPart);
sendRequest.setBody(pduBody);
Log.v(TAG, "setting message size to " + size + " bytes");
sendRequest.setMessageSize(size);
// add everything else that could be set
sendRequest.setPriority(PduHeaders.PRIORITY_NORMAL);
sendRequest.setDeliveryReport(PduHeaders.VALUE_NO);
sendRequest.setExpiry(1000 * 60 * 60 * 24 * 7);
sendRequest.setMessageClass(PduHeaders.MESSAGE_CLASS_PERSONAL_STR.getBytes());
sendRequest.setReadReport(PduHeaders.VALUE_NO);
// create byte array which will actually be sent
final PduComposer composer = new PduComposer(context, sendRequest);
final byte[] bytesToSend;
try {
bytesToSend = composer.make();
} catch (OutOfMemoryError e) {
throw new MmsException("Out of memory!");
}
MessageInfo info = new MessageInfo();
info.bytes = bytesToSend;
if (saveMessage) {
try {
PduPersister persister = PduPersister.getPduPersister(context);
info.location = persister.persist(sendRequest, Uri.parse("content://mms/outbox"), true, settings.getGroup(), null);
} catch (Exception e) {
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 {
Cursor query = context.getContentResolver().query(info.location, new String[] {"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 (Exception e) {
Log.e(TAG, "exception thrown", e);
info.token = 4444L;
}
return info;
}
public static final long DEFAULT_EXPIRY_TIME = 7 * 24 * 60 * 60;
public static final int DEFAULT_PRIORITY = PduHeaders.PRIORITY_NORMAL;
private static void sendMmsThroughSystem(Context context, String subject, List<MMSPart> parts,
String[] addresses) {
try {
final String fileName = "send." + String.valueOf(Math.abs(new Random().nextLong())) + ".dat";
File mSendFile = new File(context.getCacheDir(), fileName);
SendReq sendReq = buildPdu(context, addresses, subject, parts);
PduPersister persister = PduPersister.getPduPersister(context);
Uri messageUri = persister.persist(sendReq, Uri.parse("content://mms/outbox"),
true, settings.getGroup(), null);
Intent intent = new Intent(MmsSentReceiver.MMS_SENT);
intent.putExtra(MmsSentReceiver.EXTRA_CONTENT_URI, messageUri.toString());
intent.putExtra(MmsSentReceiver.EXTRA_FILE_PATH, mSendFile.getPath());
BroadcastUtils.addClassName(context, intent, MmsSentReceiver.MMS_SENT);
final PendingIntent pendingIntent = PendingIntent.getBroadcast(
context, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
Uri writerUri = (new Uri.Builder())
.authority(context.getPackageName() + ".MmsFileProvider")
.path(fileName)
.scheme(ContentResolver.SCHEME_CONTENT)
.build();
FileOutputStream writer = null;
Uri contentUri = null;
try {
writer = new FileOutputStream(mSendFile);
writer.write(new PduComposer(context, sendReq).make());
contentUri = writerUri;
} catch (final IOException e) {
Log.e(TAG, "Error writing send file", e);
} finally {
if (writer != null) {
try {
writer.close();
} catch (IOException e) {
}
}
}
Bundle configOverrides = new Bundle();
configOverrides.putBoolean(SmsManager.MMS_CONFIG_GROUP_MMS_ENABLED, settings.getGroup());
String 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 (PendingIntent.CanceledException ex) {
Log.e(TAG, "Mms pending intent cancelled?", ex);
}
}
} catch (Exception e) {
Log.e(TAG, "error using system sending method", e);
}
}
private static SendReq buildPdu(Context context, String[] recipients, String subject,
List<MMSPart> parts) {
final SendReq req = new SendReq();
// From, per spec
final String lineNumber = Utils.getMyPhoneNumber(context);
if (!TextUtils.isEmpty(lineNumber)) {
req.setFrom(new EncodedStringValue(lineNumber));
}
// To
for (String recipient : recipients) {
req.addTo(new EncodedStringValue(recipient));
}
// Subject
if (!TextUtils.isEmpty(subject)) {
req.setSubject(new EncodedStringValue(subject));
}
// Date
req.setDate(System.currentTimeMillis() / 1000);
// Body
PduBody body = new PduBody();
// Add text part. Always add a smil part for compatibility, without it there
// may be issues on some carriers/client apps
int size = 0;
for (int i = 0; i < parts.size(); i++) {
MMSPart part = parts.get(i);
size += addTextPart(body, part, i);
}
// add a SMIL document for compatibility
ByteArrayOutputStream out = new ByteArrayOutputStream();
SmilXmlSerializer.serialize(SmilHelper.createSmilDocument(body), out);
PduPart smilPart = new PduPart();
smilPart.setContentId("smil".getBytes());
smilPart.setContentLocation("smil.xml".getBytes());
smilPart.setContentType(ContentType.APP_SMIL.getBytes());
smilPart.setData(out.toByteArray());
body.addPart(0, smilPart);
req.setBody(body);
// Message size
req.setMessageSize(size);
// Message class
req.setMessageClass(PduHeaders.MESSAGE_CLASS_PERSONAL_STR.getBytes());
// Expiry
req.setExpiry(DEFAULT_EXPIRY_TIME);
try {
// Priority
req.setPriority(DEFAULT_PRIORITY);
// Delivery report
req.setDeliveryReport(PduHeaders.VALUE_NO);
// Read report
req.setReadReport(PduHeaders.VALUE_NO);
} catch (InvalidHeaderValueException e) {}
return req;
}
private static int addTextPart(PduBody pb, MMSPart p, int id) {
String filename = p.Name;
final PduPart part = new PduPart();
// Set Charset if it's a text media.
if (p.MimeType.startsWith("text")) {
part.setCharset(CharacterSets.UTF_8);
}
// Set Content-Type.
part.setContentType(p.MimeType.getBytes());
// Set Content-Location.
part.setContentLocation(filename.getBytes());
int index = filename.lastIndexOf(".");
String contentId = (index == -1) ? filename
: filename.substring(0, index);
part.setContentId(contentId.getBytes());
part.setData(p.Data);
pb.addPart(part);
return part.getData().length;
}
public static class MessageInfo {
public long token;
public Uri location;
public byte[] bytes;
}
// splits text and adds split counter when applicable
private String[] splitByLength(String s, int chunkSize, boolean counter) {
int arraySize = (int) Math.ceil((double) s.length() / chunkSize);
String[] returnArray = new String[arraySize];
int index = 0;
for (int i = 0; i < s.length(); i = i + chunkSize) {
if (s.length() - i < chunkSize) {
returnArray[index++] = s.substring(i);
} else {
returnArray[index++] = s.substring(i, i + chunkSize);
}
}
if (counter && returnArray.length > 1) {
for (int i = 0; i < returnArray.length; i++) {
returnArray[i] = "(" + (i + 1) + "/" + returnArray.length + ") " + returnArray[i];
}
}
return returnArray;
}
private static Uri insert(Context context, String[] to, MMSPart[] parts, String subject) {
try {
Uri destUri = Uri.parse("content://mms");
Set<String> recipients = new HashSet<String>();
recipients.addAll(Arrays.asList(to));
long thread_id = Utils.getOrCreateThreadId(context, recipients);
// Create a dummy sms
ContentValues dummyValues = new ContentValues();
dummyValues.put("thread_id", thread_id);
dummyValues.put("body", " ");
Uri dummySms = context.getContentResolver().insert(Uri.parse("content://sms/sent"), dummyValues);
// Create a new message entry
long now = System.currentTimeMillis();
ContentValues mmsValues = new 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 != null ? subject : "");
mmsValues.put("sub_cs", 106);
mmsValues.put("ct_t", "application/vnd.wap.multipart.related");
long imageBytes = 0;
for (MMSPart part : parts) {
imageBytes += part.Data.length;
}
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" + Long.toHexString(now));
mmsValues.put("resp_st", 128);
// Insert message
Uri res = context.getContentResolver().insert(destUri, mmsValues);
String messageId = res.getLastPathSegment().trim();
// Create part
for (MMSPart part : parts) {
if (part.MimeType.startsWith("image")) {
createPartImage(context, messageId, part.Data, part.MimeType);
} else if (part.MimeType.startsWith("text")) {
createPartText(context, messageId, new String(part.Data, "UTF-8"));
}
}
// Create addresses
for (String addr : to) {
createAddr(context, messageId, addr);
}
//res = Uri.parse(destUri + "/" + messageId);
// Delete dummy sms
context.getContentResolver().delete(dummySms, null, null);
return res;
} catch (Exception e) {
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
private static Uri createPartImage(Context context, String id, byte[] imageBytes, String mimeType) throws Exception {
ContentValues mmsPartValue = new ContentValues();
mmsPartValue.put("mid", id);
mmsPartValue.put("ct", mimeType);
mmsPartValue.put("cid", "<" + System.currentTimeMillis() + ">");
Uri partUri = Uri.parse("content://mms/" + id + "/part");
Uri res = context.getContentResolver().insert(partUri, mmsPartValue);
// Add data to part
OutputStream os = context.getContentResolver().openOutputStream(res);
ByteArrayInputStream is = new ByteArrayInputStream(imageBytes);
byte[] buffer = new byte[256];
for (int len = 0; (len = is.read(buffer)) != -1; ) {
os.write(buffer, 0, len);
}
os.close();
is.close();
return res;
}
// create the text part to be stored in database
private static Uri createPartText(Context context, String id, String text) throws Exception {
ContentValues mmsPartValue = new ContentValues();
mmsPartValue.put("mid", id);
mmsPartValue.put("ct", "text/plain");
mmsPartValue.put("cid", "<" + System.currentTimeMillis() + ">");
mmsPartValue.put("text", text);
Uri partUri = Uri.parse("content://mms/" + id + "/part");
Uri res = context.getContentResolver().insert(partUri, mmsPartValue);
return res;
}
// add address to the request
private static Uri createAddr(Context context, String id, String addr) throws Exception {
ContentValues addrValues = new ContentValues();
addrValues.put("address", addr);
addrValues.put("charset", "106");
addrValues.put("type", 151); // TO
Uri addrUri = Uri.parse("content://mms/" + id + "/addr");
Uri res = context.getContentResolver().insert(addrUri, addrValues);
return res;
}
/**
* 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
*/
public boolean checkMMS(Message message) {
return message.getImages().length != 0 ||
(message.getParts().size() != 0) ||
(settings.getSendLongAsMms() && Utils.getNumPages(settings, message.getText()) > settings.getSendLongAsMmsAfter()) ||
(message.getAddresses().length > 1 && settings.getGroup()) ||
message.getSubject() != null;
}
}