/*
* Copyright (C) 2008 Esmertec AG. Copyright (C) 2008 The Android Open Source
* Project
*
* 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 net.everythingandroid.smspopup.util;
import java.util.ArrayList;
import net.everythingandroid.smspopup.BuildConfig;
import net.everythingandroid.smspopup.R;
import net.everythingandroid.smspopup.receiver.SmsReceiver;
import net.everythingandroid.smspopup.service.SmsReceiverService;
import android.app.PendingIntent;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.database.sqlite.SQLiteException;
import android.net.Uri;
import android.preference.PreferenceManager;
import android.telephony.SmsManager;
public class SmsMessageSender {
private final Context mContext;
private final int mNumberOfDests;
private final String[] mDests;
private final String mMessageText;
private final String mServiceCenter;
private final long mThreadId;
private long mTimestamp;
private boolean splitMessage;
private boolean requestDeliveryReport;
// Default preference values
private static final boolean DEFAULT_DELIVERY_REPORT_MODE = false;
private static final boolean DEFAULT_SPLIT_MESSAGE = false;
// http://android.git.kernel.org/?p=platform/frameworks/base.git;a=blob;f=core/java/android/provider/Telephony.java
public static final String REPLY_PATH_PRESENT = "reply_path_present";
public static final String SERVICE_CENTER = "service_center";
// public static final String DATE = "date";
/**
* The thread ID of the message
* <P>
* Type: INTEGER
* </P>
*/
public static final String THREAD_ID = "thread_id";
/**
* The address of the other party
* <P>
* Type: TEXT
* </P>
*/
public static final String ADDRESS = "address";
/**
* The person ID of the sender
* <P>
* Type: INTEGER (long)
* </P>
*/
public static final String PERSON_ID = "person";
/**
* The date the message was sent
* <P>
* Type: INTEGER (long)
* </P>
*/
public static final String DATE = "date";
/**
* Has the message been read
* <P>
* Type: INTEGER (boolean)
* </P>
*/
public static final String READ = "read";
/**
* The TP-Status value for the message, or -1 if no status has been received
*/
public static final String STATUS = "status";
public static final int STATUS_NONE = -1;
public static final int STATUS_COMPLETE = 0;
public static final int STATUS_PENDING = 64;
public static final int STATUS_FAILED = 128;
/**
* The subject of the message, if present
* <P>
* Type: TEXT
* </P>
*/
public static final String SUBJECT = "subject";
/**
* The body of the message
* <P>
* Type: TEXT
* </P>
*/
public static final String BODY = "body";
public static final String TYPE = "type";
public static final int MESSAGE_TYPE_ALL = 0;
public static final int MESSAGE_TYPE_INBOX = 1;
public static final int MESSAGE_TYPE_SENT = 2;
public static final int MESSAGE_TYPE_DRAFT = 3;
public static final int MESSAGE_TYPE_OUTBOX = 4;
public static final int MESSAGE_TYPE_FAILED = 5; // for failed outgoing messages
public static final int MESSAGE_TYPE_QUEUED = 6; // for messages to send later
private static final String[] SERVICE_CENTER_PROJECTION =
new String[] { REPLY_PATH_PRESENT, SERVICE_CENTER, };
// private static final String[] DATE_PROJECTION = new String[] {
// DATE
// };
private static final int COLUMN_REPLY_PATH_PRESENT = 0;
private static final int COLUMN_SERVICE_CENTER = 1;
// http://android.git.kernel.org/?p=platform/packages/apps/Mms.git;a=blob;f=src/com/android/mms/transaction/MessageStatusReceiver.java
public static final String MESSAGING_STATUS_RECEIVED_ACTION =
"com.android.mms.transaction.MessageStatusReceiver.MESSAGE_STATUS_RECEIVED";
public static final String MESSAGING_PACKAGE_NAME = "com.android.mms";
public static final String MESSAGING_STATUS_CLASS_NAME =
MESSAGING_PACKAGE_NAME + ".transaction.MessageStatusReceiver";
public static final String MESSAGING_RECEIVER_CLASS_NAME =
MESSAGING_PACKAGE_NAME + ".transaction.SmsReceiver";
public static final String MESSAGING_CONVO_CLASS_NAME = "com.android.mms.ui.ConversationList";
public static final String MESSAGING_COMPOSE_CLASS_NAME =
"com.android.mms.ui.ComposeMessageActivity";
// Bad idea, commenting out for now
/*
* public static final String[][] MMS_APP_LIST = { { // Stock Android messaging app (the bulk of
* phones use the same class/package names) MESSAGING_PACKAGE_NAME, MESSAGING_PACKAGE_NAME +
* ".transaction.SmsReceiver", SmsReceiverService.MESSAGE_SENT_ACTION,
* MESSAGING_STATUS_CLASS_NAME, MESSAGING_STATUS_RECEIVED_ACTION,
*
* }, { // Motoblur phones like Droid X "com.motorola.blur.conversations",
* "com.motorola.blur.conversations.transaction.SmsReceiver",
* "com.motorola.blur.conversations.transaction.MESSAGE_SENT",
* "com.motorola.blur.conversations.transaction.MessageStatusReceiver",
* "com.motorola.blur.conversations.transaction.MessageStatusReceiver.MESSAGE_STATUS_RECEIVED",
* }, };
*/
/**
* Send a message via the system app and system db
*
* @param context
* the context
* @param dests
* the destination addresses
* @param msgText
* the message text
* @param threadId
* the message thread id
*/
public SmsMessageSender(Context context, String[] dests, String msgText, long threadId) {
mContext = context;
mMessageText = msgText;
mNumberOfDests = dests.length;
mDests = new String[mNumberOfDests];
System.arraycopy(dests, 0, mDests, 0, mNumberOfDests);
mTimestamp = System.currentTimeMillis();
mThreadId = threadId;
// mThreadId = threadId > 0 ? threadId
// : Threads.getOrCreateThreadId(context,
// new HashSet<String>(Arrays.asList(dests)));
mServiceCenter = getOutgoingServiceCenter(mThreadId);
SharedPreferences mPrefs = PreferenceManager.getDefaultSharedPreferences(mContext);
// Fetch split message pref
splitMessage =
mPrefs.getBoolean(
mContext.getString(R.string.pref_split_message_key), DEFAULT_SPLIT_MESSAGE);
// Fetch delivery report pref
requestDeliveryReport =
mPrefs.getBoolean(
mContext.getString(R.string.pref_delivery_report_key),
DEFAULT_DELIVERY_REPORT_MODE);
}
public boolean sendMessage() {
if (!(mThreadId > 0)) {
return false;
}
// Don't try to send an empty message.
if ((mMessageText == null) || (mNumberOfDests == 0)) {
return false;
}
SmsManager smsManager = SmsManager.getDefault();
for (int i = 0; i < mNumberOfDests; i++) {
ArrayList<String> messages = smsManager.divideMessage(mMessageText);
int messageCount = messages.size();
ArrayList<PendingIntent> deliveryIntents = new ArrayList<PendingIntent>(messageCount);
ArrayList<PendingIntent> sentIntents = new ArrayList<PendingIntent>(messageCount);
// Apparently some CDMA networks have a 160 character limit on sending messages (no
// multi-part SMS messages), if splitMessage is true we will break apart the messages
// and send separately.
if (splitMessage) {
for (int j = 0; j < messageCount; j++) {
Uri uri = null;
try {
uri =
addMessage(mContext.getContentResolver(), mDests[i], messages
.get(j),
null, mTimestamp, requestDeliveryReport, mThreadId);
} catch (SQLiteException e) {
// TODO: show error here
// SqliteWrapper.checkSQLiteException(mContext, e);
}
PendingIntent deliveryReportIntent = null;
if (requestDeliveryReport) {
deliveryReportIntent =
PendingIntent.getBroadcast(mContext, 0,
new Intent(MESSAGING_STATUS_RECEIVED_ACTION, uri)
.setClassName(MESSAGING_PACKAGE_NAME,
MESSAGING_STATUS_CLASS_NAME), 0);
}
PendingIntent sentIntent =
PendingIntent.getBroadcast(mContext, 0,
new Intent(SmsReceiverService.MESSAGE_SENT_ACTION, uri)
.setClass(mContext, SmsReceiver.class), 0);
smsManager.sendTextMessage(
mDests[i], mServiceCenter, messages.get(j), sentIntent,
deliveryReportIntent);
}
} else { // Otherwise send as multipart message
Uri uri = null;
try {
uri =
addMessage(mContext.getContentResolver(), mDests[i], mMessageText,
null,
mTimestamp, requestDeliveryReport, mThreadId);
} catch (SQLiteException e) {
// TODO: show error here
// SqliteWrapper.checkSQLiteException(mContext, e);
}
for (int j = 0; j < messageCount; j++) {
if (requestDeliveryReport) {
deliveryIntents.add(PendingIntent.getBroadcast(mContext, 0,
new Intent(
MESSAGING_STATUS_RECEIVED_ACTION, uri).setClassName(
MESSAGING_PACKAGE_NAME, MESSAGING_STATUS_CLASS_NAME),
// MessageStatusReceiver.class),
0));
}
sentIntents.add(PendingIntent.getBroadcast(mContext, 0,
new Intent(
SmsReceiverService.MESSAGE_SENT_ACTION, uri).setClass(
mContext, SmsReceiver.class),
// .setClassName(MMS_PACKAGE_NAME, MMS_SENT_CLASS_NAME),
// SmsReceiver.class
0));
}
if (BuildConfig.DEBUG)
Log.v("Sending message in " + messageCount + " parts");
smsManager.sendMultipartTextMessage(
mDests[i], mServiceCenter, messages, sentIntents, deliveryIntents);
}
}
return false;
}
/**
* Get the service center to use for a reply.
*
* The rule from TS 23.040 D.6 is that we send reply messages to the service center of the
* message to which we're replying, but only if we haven't already replied to that message and
* only if <code>TP-Reply-Path</code> was set in that message.
*
* Therefore, return the service center from the most recent message in the conversation, but
* only if it is a message from the other party, and only if <code>TP-Reply-Path</code> is set.
* Otherwise, return null.
*/
private String getOutgoingServiceCenter(long threadId) {
Cursor cursor = null;
try {
cursor =
mContext.getContentResolver()
.query(SmsPopupUtils.SMS_CONTENT_URI,
SERVICE_CENTER_PROJECTION, "thread_id = " + threadId, null,
"date DESC");
// cursor = SqliteWrapper.query(mContext, mContext.getContentResolver(),
// Sms.CONTENT_URI, SERVICE_CENTER_PROJECTION,
// "thread_id = " + threadId, null, "date DESC");
if ((cursor == null) || !cursor.moveToFirst()) {
return null;
}
boolean replyPathPresent = (1 == cursor.getInt(COLUMN_REPLY_PATH_PRESENT));
return replyPathPresent ? cursor.getString(COLUMN_SERVICE_CENTER) : null;
} finally {
if (cursor != null) {
cursor.close();
}
}
}
/**
* Add an SMS to the Out box.
*
* @param resolver
* the content resolver to use
* @param address
* the address of the sender
* @param body
* the body of the message
* @param subject
* the psuedo-subject of the message
* @param date
* the timestamp for the message
* @param deliveryReport
* whether a delivery report was requested for the message
* @return the URI for the new message
*/
public static Uri addMessage(ContentResolver resolver, String address, String body,
String subject, Long date, boolean deliveryReport, long threadId) {
/**
* The content:// style URL for this table
*/
final Uri CONTENT_URI = Uri.parse("content://sms/outbox");
return addMessageToUri(resolver, CONTENT_URI, address, body, subject, date, true,
deliveryReport, threadId);
}
/**
* Add an SMS to the given URI with thread_id specified.
*
* @param resolver
* the content resolver to use
* @param uri
* the URI to add the message to
* @param address
* the address of the sender
* @param body
* the body of the message
* @param subject
* the psuedo-subject of the message
* @param date
* the timestamp for the message
* @param read
* true if the message has been read, false if not
* @param deliveryReport
* true if a delivery report was requested, false if not
* @param threadId
* the thread_id of the message
* @return the URI for the new message
*/
public static Uri addMessageToUri(ContentResolver resolver, Uri uri, String address,
String body,
String subject, Long date, boolean read, boolean deliveryReport, long threadId) {
ContentValues values = new ContentValues(7);
values.put(ADDRESS, address);
if (date != null) {
values.put(DATE, date);
}
values.put(READ, read ? Integer.valueOf(1) : Integer.valueOf(0));
values.put(SUBJECT, subject);
values.put(BODY, body);
if (deliveryReport) {
values.put(STATUS, STATUS_PENDING);
}
if (threadId != -1L) {
values.put(THREAD_ID, threadId);
}
return resolver.insert(uri, values);
}
/**
* Move a message to the given folder.
*
* @param context
* the context to use
* @param uri
* the message to move
* @param folder
* the folder to move to
* @return true if the operation succeeded
*/
public static boolean moveMessageToFolder(Context context, Uri uri, int folder) {
if (uri == null) {
return false;
}
boolean markAsUnread = false;
boolean markAsRead = false;
switch (folder) {
case MESSAGE_TYPE_INBOX:
case MESSAGE_TYPE_DRAFT:
break;
case MESSAGE_TYPE_OUTBOX:
case MESSAGE_TYPE_SENT:
markAsRead = true;
break;
case MESSAGE_TYPE_FAILED:
case MESSAGE_TYPE_QUEUED:
markAsUnread = true;
break;
default:
return false;
}
ContentValues values = new ContentValues(2);
values.put(TYPE, folder);
if (markAsUnread) {
values.put(READ, Integer.valueOf(0));
} else if (markAsRead) {
values.put(READ, Integer.valueOf(1));
}
int result = 0;
try {
result = context.getContentResolver().update(uri, values, null, null);
} catch (Exception e) {
}
return 1 == result;
}
}