package com.moez.QKSMS.transaction;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.net.Uri;
import android.preference.PreferenceManager;
import android.provider.Telephony;
import android.provider.Telephony.Sms;
import android.telephony.SmsManager;
import android.text.TextUtils;
import android.util.Log;
import android.util.Patterns;
import com.google.android.mms.MmsException;
import com.google.android.mms.pdu_alt.CharacterSets;
import com.google.android.mms.pdu_alt.EncodedStringValue;
import com.google.android.mms.pdu_alt.MultimediaMessagePdu;
import com.google.android.mms.pdu_alt.PduPersister;
import com.moez.QKSMS.MmsConfig;
import com.moez.QKSMS.R;
import com.moez.QKSMS.data.Conversation;
import com.moez.QKSMS.data.Message;
import com.moez.QKSMS.mmssms.Settings;
import com.moez.QKSMS.model.SlideModel;
import com.moez.QKSMS.model.SlideshowModel;
import com.moez.QKSMS.ui.messagelist.MessageColumns;
import com.moez.QKSMS.ui.messagelist.MessageItem;
import com.moez.QKSMS.ui.settings.SettingsFragment;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class SmsHelper {
public static final Uri SMS_CONTENT_PROVIDER = Uri.parse("content://sms/");
public static final Uri MMS_CONTENT_PROVIDER = Uri.parse("content://mms/");
public static final Uri MMS_SMS_CONTENT_PROVIDER = Uri.parse("content://mms-sms/conversations/");
public static final Uri SENT_MESSAGE_CONTENT_PROVIDER = Uri.parse("content://sms/sent");
public static final Uri DRAFTS_CONTENT_PROVIDER = Uri.parse("content://sms/draft");
public static final Uri PENDING_MESSAGE_CONTENT_PROVIDER = Uri.parse("content://sms/outbox");
public static final Uri RECEIVED_MESSAGE_CONTENT_PROVIDER = Uri.parse("content://sms/inbox");
public static final Uri CONVERSATIONS_CONTENT_PROVIDER = Uri.parse("content://mms-sms/conversations?simple=true");
public static final Uri ADDRESSES_CONTENT_PROVIDER = Uri.parse("content://mms-sms/canonical-addresses");
public static final String MAX_MMS_ATTACHMENT_SIZE_UNLIMITED = "unlimited";
public static final String MAX_MMS_ATTACHMENT_SIZE_300KB = "300kb";
public static final String MAX_MMS_ATTACHMENT_SIZE_600KB = "600kb";
public static final String MAX_MMS_ATTACHMENT_SIZE_1MB = "1mb";
public static final String sortDateDesc = "date DESC";
public static final String sortDateAsc = "date ASC";
public static final byte UNREAD = 0;
public static final byte READ = 1;
// Attachment types
public static final int TEXT = 0;
public static final int IMAGE = 1;
public static final int VIDEO = 2;
public static final int AUDIO = 3;
public static final int SLIDESHOW = 4;
// Columns for SMS content providers
public static final String COLUMN_ID = "_id";
public static final String COLUMN_THREAD_ID = "thread_id";
public static final String COLUMN_ADDRESS = "address";
public static final String COLUMN_RECIPIENT = "recipient_ids";
public static final String COLUMN_PERSON = "person";
public static final String COLUMN_SNIPPET = "snippet";
public static final String COLUMN_DATE = "date";
public static final String COLUMN_DATE_NORMALIZED = "normalized_date";
public static final String COLUMN_DATE_SENT = "date_sent";
public static final String COLUMN_STATUS = "status";
public static final String COLUMN_ERROR = "error";
public static final String COLUMN_READ = "read";
public static final String COLUMN_TYPE = "type";
public static final String COLUMN_MMS = "ct_t";
public static final String COLUMN_TEXT = "text";
public static final String COLUMN_SUB = "sub";
public static final String COLUMN_MSG_BOX = "msg_box";
public static final String COLUMN_SUBJECT = "subject";
public static final String COLUMN_BODY = "body";
public static final String COLUMN_SEEN = "seen";
public static final String READ_SELECTION = COLUMN_READ + " = " + READ;
public static final String UNREAD_SELECTION = COLUMN_READ + " = " + UNREAD;
public static final String UNSEEN_SELECTION = COLUMN_SEEN + " = " + UNREAD;
public static final String FAILED_SELECTION = COLUMN_TYPE + " = " + Message.FAILED;
public static final int ADDRESSES_ADDRESS = 1;
private static final String TAG = "SMSHelper";
private static SmsManager sms;
private static Settings sendSettings;
public SmsHelper() {
}
/**
* The quality parameter which is used to compress JPEG images.
*/
public static final int IMAGE_COMPRESSION_QUALITY = 95;
/**
* The minimum quality parameter which is used to compress JPEG images.
*/
public static final int MINIMUM_IMAGE_COMPRESSION_QUALITY = 50;
/**
* Message type: all messages.
*/
public static final int MESSAGE_TYPE_ALL = 0;
/**
* Message type: inbox.
*/
public static final int MESSAGE_TYPE_INBOX = 1;
/**
* Message type: sent messages.
*/
public static final int MESSAGE_TYPE_SENT = 2;
/**
* Message type: drafts.
*/
public static final int MESSAGE_TYPE_DRAFT = 3;
/**
* Message type: outbox.
*/
public static final int MESSAGE_TYPE_OUTBOX = 4;
/**
* Message type: failed outgoing message.
*/
public static final int MESSAGE_TYPE_FAILED = 5;
/**
* Message type: queued to send later.
*/
public static final int MESSAGE_TYPE_QUEUED = 6;
/**
* MMS address parsing data structures
*/
// allowable phone number separators
private static final char[] NUMERIC_CHARS_SUGAR = {
'-', '.', ',', '(', ')', ' ', '/', '\\', '*', '#', '+'
};
private static String[] sNoSubjectStrings;
public static void markSmsSeen(Context context) {
Cursor cursor = context.getContentResolver().query(RECEIVED_MESSAGE_CONTENT_PROVIDER,
new String[]{SmsHelper.COLUMN_ID}, SmsHelper.UNSEEN_SELECTION + " AND " + SmsHelper.UNREAD_SELECTION, null, null);
if (cursor == null) {
Log.i(TAG, "No unseen messages");
return;
}
MessageColumns.ColumnsMap map = new MessageColumns.ColumnsMap(cursor);
if (cursor.moveToFirst()) {
ContentValues cv = new ContentValues();
cv.put("seen", true);
do {
context.getContentResolver().update(Uri.parse("content://sms/" + cursor.getLong(map.mColumnMsgId)), cv, null, null);
} while (cursor.moveToNext());
}
cursor.close();
}
public static void markMmsSeen(Context context) {
Cursor cursor = context.getContentResolver().query(MMS_CONTENT_PROVIDER,
new String[]{SmsHelper.COLUMN_ID}, SmsHelper.UNSEEN_SELECTION + " AND " + SmsHelper.UNREAD_SELECTION, null, null);
if (cursor == null) {
Log.i(TAG, "No unseen messages");
return;
}
MessageColumns.ColumnsMap map = new MessageColumns.ColumnsMap(cursor);
if (cursor.moveToFirst()) {
ContentValues cv = new ContentValues();
cv.put("seen", true);
do {
context.getContentResolver().update(Uri.parse("content://mms/" + cursor.getLong(map.mColumnMsgId)), cv, null, null);
} while (cursor.moveToNext());
}
cursor.close();
}
public static Settings getSendSettings(Context context) {
if (sendSettings == null) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
sendSettings = new Settings();
sendSettings.setMmsc(prefs.getString(SettingsFragment.MMSC_URL, ""));
sendSettings.setProxy(prefs.getString(SettingsFragment.MMS_PROXY, ""));
sendSettings.setPort(prefs.getString(SettingsFragment.MMS_PORT, ""));
sendSettings.setAgent(prefs.getString("mms_agent", ""));
sendSettings.setUserProfileUrl(prefs.getString("mms_user_agent_profile_url", ""));
sendSettings.setUaProfTagName(prefs.getString("mms_user_agent_tag_name", ""));
sendSettings.setGroup(prefs.getBoolean(SettingsFragment.COMPOSE_GROUP, true));
setMaxAttachmentSizeSetting(context, prefs.getString(SettingsFragment.MAX_MMS_ATTACHMENT_SIZE, "300kb"));
sendSettings.setDeliveryReports(prefs.getBoolean(SettingsFragment.DELIVERY_REPORTS, false));
sendSettings.setSplit(prefs.getBoolean(SettingsFragment.SPLIT_SMS, false));
sendSettings.setSplitCounter(prefs.getBoolean(SettingsFragment.SPLIT_COUNTER, true));
sendSettings.setStripUnicode(prefs.getBoolean(SettingsFragment.STRIP_UNICODE, false));
sendSettings.setSignature(prefs.getString("pref_key_signature", ""));
sendSettings.setSendLongAsMms(prefs.getBoolean(SettingsFragment.LONG_AS_MMS, false));
sendSettings.setSendLongAsMmsAfter(Integer.parseInt(prefs.getString(SettingsFragment.LONG_AS_MMS_AFTER, "3")));
sendSettings.setAccount(null);
sendSettings.setRnrSe(null);
}
return sendSettings;
}
/**
* Sets the max MMS attachment size in the MMS settings field.
*
* @param context
* @param maxAttachmentSize The String value in the ListPreference for max attachment sizes
*/
public static void setMaxAttachmentSizeSetting(Context context, String maxAttachmentSize) {
// Initialize sendSettings if it hasn't already been initialized
sendSettings = getSendSettings(context);
if (MAX_MMS_ATTACHMENT_SIZE_300KB.equals(maxAttachmentSize)) {
sendSettings.setMaxAttachmentSize(Settings.MAX_ATTACHMENT_SIZE_300KB);
} else if (MAX_MMS_ATTACHMENT_SIZE_600KB.equals(maxAttachmentSize)) {
sendSettings.setMaxAttachmentSize(Settings.MAX_ATTACHMENT_SIZE_600KB);
} else if (MAX_MMS_ATTACHMENT_SIZE_1MB.equals(maxAttachmentSize)) {
sendSettings.setMaxAttachmentSize(Settings.MAX_ATTACHMENT_SIZE_1MB);
} else if (MAX_MMS_ATTACHMENT_SIZE_UNLIMITED.equals(maxAttachmentSize)) {
sendSettings.setMaxAttachmentSize(Settings.MAX_ATTACHMENT_SIZE_UNLIMITED);
}
}
/**
* Add incoming SMS to inbox
*
* @param context
* @param address Address of sender
* @param body Body of incoming SMS message
* @param time Time that incoming SMS message was sent at
*/
public static Uri addMessageToInbox(Context context, String address, String body, long time) {
ContentResolver contentResolver = context.getContentResolver();
ContentValues cv = new ContentValues();
cv.put("address", address);
cv.put("body", body);
cv.put("date_sent", time);
return contentResolver.insert(RECEIVED_MESSAGE_CONTENT_PROVIDER, cv);
}
/**
* Returns true iff the folder (message type) identifies an
* outgoing message.
*
* @hide
*/
public static boolean isOutgoingFolder(int messageType) {
return (messageType == MESSAGE_TYPE_FAILED)
|| (messageType == MESSAGE_TYPE_OUTBOX)
|| (messageType == MESSAGE_TYPE_SENT)
|| (messageType == MESSAGE_TYPE_QUEUED);
}
public static int getUnseenSMSCount(Context context, long threadId) {
Cursor cursor = null;
int count = 0;
String selection = UNSEEN_SELECTION + " AND " + UNREAD_SELECTION + (threadId == 0 ? "" : " AND " + COLUMN_THREAD_ID + " = " + threadId);
try {
cursor = context.getContentResolver().query(RECEIVED_MESSAGE_CONTENT_PROVIDER, new String[]{COLUMN_ID}, selection, null, null);
cursor.moveToFirst();
count = cursor.getCount();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (cursor != null) {
cursor.close();
}
}
return count;
}
/**
* Returns a string containing the last 10 messages for a given conversation
* This is used to be displayed on the notification page on a wearable, which
* only accepts a single String to be displayed
*/
public static String getHistoryForWearable(Context context, String name, long threadId) {
final String me = context.getString(R.string.me);
Cursor cursor = null;
StringBuilder builder = new StringBuilder();
MessageColumns.ColumnsMap map = new MessageColumns.ColumnsMap();
try {
cursor = context.getContentResolver().query(
Uri.withAppendedPath(Message.MMS_SMS_CONTENT_PROVIDER, "" + threadId),
MessageColumns.PROJECTION, null, null, "normalized_date DESC LIMIT 10");
cursor.moveToLast();
do {
if (cursor.getString(map.mColumnMsgType).equals("sms")) {
int boxId = cursor.getInt(map.mColumnSmsType);
boolean in = boxId == Telephony.TextBasedSmsColumns.MESSAGE_TYPE_INBOX ||
boxId == Telephony.TextBasedSmsColumns.MESSAGE_TYPE_ALL;
builder.append(in ? name : me)
.append("\n")
.append(cursor.getString(map.mColumnSmsBody))
.append("\n\n");
}
} while (cursor.moveToPrevious());
} catch (Exception e) {
e.printStackTrace();
} finally {
if (cursor != null) {
cursor.close();
}
}
return builder.toString();
}
/**
* List of messages grouped by thread id, used for showing notifications
*/
public static HashMap<Long, ArrayList<MessageItem>> getUnreadUnseenConversations(Context context) {
HashMap<Long, ArrayList<MessageItem>> result = new HashMap<>();
String selection = SmsHelper.UNSEEN_SELECTION + " AND " + SmsHelper.UNREAD_SELECTION;
// Create a cursor for the conversation list
Cursor conversationCursor = context.getContentResolver().query(
SmsHelper.CONVERSATIONS_CONTENT_PROVIDER, Conversation.ALL_THREADS_PROJECTION,
SmsHelper.UNREAD_SELECTION, null, SmsHelper.sortDateAsc);
if (conversationCursor != null && conversationCursor.moveToFirst()) {
do {
ArrayList<MessageItem> messages = new ArrayList<>();
long threadId = conversationCursor.getLong(Conversation.ID);
Uri threadUri = Uri.withAppendedPath(Message.MMS_SMS_CONTENT_PROVIDER, Long.toString(threadId));
Cursor messageCursor = context.getContentResolver().query(threadUri, MessageColumns.PROJECTION, selection, null, SmsHelper.sortDateAsc);
if (messageCursor != null && messageCursor.moveToFirst()) {
do {
MessageColumns.ColumnsMap columnsMap = new MessageColumns.ColumnsMap(messageCursor);
MessageItem message = null;
try {
message = new MessageItem(context, messageCursor.getString(columnsMap.mColumnMsgType), messageCursor, columnsMap, null, true);
} catch (MmsException e) {
e.printStackTrace();
}
messages.add(message);
} while (messageCursor.moveToNext());
messageCursor.close();
result.put(threadId, messages);
}
} while (conversationCursor.moveToNext());
conversationCursor.close();
}
return result;
}
/**
* @return A list of unread messages to be deleted by QKReply
*/
public static ArrayList<Message> getUnreadMessagesLegacy(Context context, Uri threadUri) {
ArrayList<Message> result = new ArrayList<>();
if (threadUri != null) {
Cursor messageCursor = context.getContentResolver().query(threadUri, MessageColumns.PROJECTION, UNREAD_SELECTION, null, SmsHelper.sortDateAsc);
MessageColumns.ColumnsMap columnsMap = new MessageColumns.ColumnsMap(messageCursor);
if (messageCursor.moveToFirst()) {
do {
try {
Message message = new Message(context, messageCursor.getLong(columnsMap.mColumnMsgId));
result.add(message);
} catch (Exception e) {
e.printStackTrace();
}
} while (messageCursor.moveToNext());
}
messageCursor.close();
}
return result;
}
public static String getUnreadMessageText(Context context, Uri threadUri) {
StringBuilder builder = new StringBuilder();
ArrayList<Message> messages = SmsHelper.getUnreadMessagesLegacy(context, threadUri);
for (int i = 0; i < messages.size(); i++) {
Message message = messages.get(i);
builder.append(message.getBody());
if (i < messages.size() - 1) {
builder.append("\n\n");
}
}
return builder.toString();
}
public static long getThreadId(Context context, String address) {
Cursor cursor = null;
long threadId = 0;
try {
cursor = context.getContentResolver().query(SENT_MESSAGE_CONTENT_PROVIDER, new String[]{COLUMN_THREAD_ID}, COLUMN_ADDRESS + "=" + address, null, sortDateDesc);
cursor.moveToFirst();
threadId = cursor.getLong(cursor.getColumnIndexOrThrow(COLUMN_THREAD_ID));
} catch (Exception e) {
e.printStackTrace();
} finally {
if (cursor != null) {
cursor.close();
}
}
return threadId;
}
public static int getUnreadMessageCount(Context context) {
int result = 0;
// Create a cursor for the conversation list
Cursor conversationCursor = context.getContentResolver().query(
SmsHelper.CONVERSATIONS_CONTENT_PROVIDER, Conversation.ALL_THREADS_PROJECTION,
SmsHelper.UNREAD_SELECTION, null, SmsHelper.sortDateAsc);
if (conversationCursor.moveToFirst()) {
do {
Uri threadUri = Uri.withAppendedPath(Message.MMS_SMS_CONTENT_PROVIDER, conversationCursor.getString(Conversation.ID));
Cursor messageCursor = context.getContentResolver().query(threadUri, MessageColumns.PROJECTION, SmsHelper.UNREAD_SELECTION, null, SmsHelper.sortDateDesc);
if (messageCursor != null) {
result += messageCursor.getCount();
messageCursor.close();
}
} while (conversationCursor.moveToNext());
}
conversationCursor.close();
return result;
}
public static List<Message> getFailedMessages(Context context) {
Cursor cursor = null;
List<Message> messages = new ArrayList<>();
try {
cursor = context.getContentResolver().query(SMS_CONTENT_PROVIDER, new String[]{COLUMN_ID}, FAILED_SELECTION, null, sortDateDesc);
cursor.moveToFirst();
for (int i = 0; i < cursor.getCount(); i++) {
messages.add(new Message(context, cursor.getLong(cursor.getColumnIndexOrThrow(COLUMN_ID))));
cursor.moveToNext();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (cursor != null) {
cursor.close();
}
}
return messages;
}
public static List<Message> deleteFailedMessages(Context context, long threadId) {
Log.d(TAG, "Deleting failed messages");
Cursor cursor = null;
List<Message> messages = new ArrayList<>();
try {
cursor = context.getContentResolver().query(SMS_CONTENT_PROVIDER, new String[]{COLUMN_ID}, FAILED_SELECTION, null, sortDateDesc);
cursor.moveToFirst();
for (int i = 0; i < cursor.getCount(); i++) {
messages.add(new Message(context, cursor.getLong(cursor.getColumnIndexOrThrow(COLUMN_ID))));
cursor.moveToNext();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (cursor != null) {
cursor.close();
}
}
for (Message m : messages) {
if (m.getThreadId() == threadId) {
Log.d(TAG, "Deleting failed message to " + m.getName() + "\n Body: " + m.getBody());
m.delete();
}
}
return messages;
}
/**
* Add an SMS to the given URI.
*
* @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 pseudo-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
* @return the URI for the new message
* @hide
*/
public static Uri addMessageToUri(ContentResolver resolver,
Uri uri, String address, String body, String subject,
Long date, boolean read, boolean deliveryReport) {
return addMessageToUri(resolver, uri, address, body, subject,
date, read, deliveryReport, -1L);
}
/**
* Add an SMS to the given URI with the specified thread ID.
*
* @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 pseudo-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
* @hide
*/
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(Sms.ADDRESS, address);
if (date != null) {
values.put(Sms.DATE, date);
}
values.put(Sms.READ, read ? Integer.valueOf(1) : Integer.valueOf(0));
values.put(Sms.SUBJECT, subject);
values.put(Sms.BODY, body);
if (deliveryReport) {
values.put(Sms.STATUS, Sms.STATUS_PENDING);
}
if (threadId != -1L) {
values.put(Sms.THREAD_ID, threadId);
}
return resolver.insert(uri, values);
}
public static String extractEncStrFromCursor(Cursor cursor,
int columnRawBytes, int columnCharset) {
String rawBytes = cursor.getString(columnRawBytes);
int charset = cursor.getInt(columnCharset);
if (TextUtils.isEmpty(rawBytes)) {
return "";
} else if (charset == CharacterSets.ANY_CHARSET) {
return rawBytes;
} else {
return new EncodedStringValue(charset, PduPersister.getBytes(rawBytes)).getString();
}
}
/**
* cleanseMmsSubject will take a subject that's says, "<Subject: no subject>", and return
* a null string. Otherwise it will return the original subject string.
*
* @param context a regular context so the function can grab string resources
* @param subject the raw subject
* @return
*/
public static String cleanseMmsSubject(Context context, String subject) {
if (TextUtils.isEmpty(subject)) {
return subject;
}
if (sNoSubjectStrings == null) {
sNoSubjectStrings = context.getResources().getStringArray(R.array.empty_subject_strings);
}
final int len = sNoSubjectStrings.length;
for (int i = 0; i < len; i++) {
if (subject.equalsIgnoreCase(sNoSubjectStrings[i])) {
return null;
}
}
return subject;
}
/**
* Is the specified address an email address?
*
* @param address the input address to test
* @return true if address is an email address; false otherwise.
* @hide
*/
public static boolean isEmailAddress(String address) {
if (TextUtils.isEmpty(address)) {
return false;
}
String s = extractAddrSpec(address);
Matcher match = Patterns.EMAIL_ADDRESS.matcher(s);
return match.matches();
}
/**
* Regex pattern for names and email addresses.
* <ul>
* <li><em>mailbox</em> = {@code name-addr}</li>
* <li><em>name-addr</em> = {@code [display-name] angle-addr}</li>
* <li><em>angle-addr</em> = {@code [CFWS] "<" addr-spec ">" [CFWS]}</li>
* </ul>
*
* @hide
*/
public static final Pattern NAME_ADDR_EMAIL_PATTERN =
Pattern.compile("\\s*(\"[^\"]*\"|[^<>\"]+)\\s*<([^<>]+)>\\s*");
/**
* Helper method to extract email address from address string.
*
* @hide
*/
public static String extractAddrSpec(String address) {
Matcher match = NAME_ADDR_EMAIL_PATTERN.matcher(address);
if (match.matches()) {
return match.group(2);
}
return address;
}
private static HashMap numericSugarMap = new HashMap(NUMERIC_CHARS_SUGAR.length);
/**
* Given a phone number, return the string without syntactic sugar, meaning parens,
* spaces, slashes, dots, dashes, etc. If the input string contains non-numeric
* non-punctuation characters, return null.
*/
private static String parsePhoneNumberForMms(String address) {
StringBuilder builder = new StringBuilder();
int len = address.length();
for (int i = 0; i < len; i++) {
char c = address.charAt(i);
// accept the first '+' in the address
if (c == '+' && builder.length() == 0) {
builder.append(c);
continue;
}
if (Character.isDigit(c)) {
builder.append(c);
continue;
}
if (numericSugarMap.get(c) == null) {
return null;
}
}
return builder.toString();
}
/**
* parse the input address to be a valid MMS address.
* - if the address is an email address, leave it as is.
* - if the address can be parsed into a valid MMS phone number, return the parsed number.
* - if the address is a compliant alias address, leave it as is.
*/
public static String parseMmsAddress(String address) {
// if it's a valid Email address, use that.
if (isEmailAddress(address)) {
return address;
}
// if we are able to parse the address to a MMS compliant phone number, take that.
String retVal = parsePhoneNumberForMms(address);
if (retVal != null && retVal.length() != 0) {
return retVal;
}
// if it's an alias compliant address, use that.
if (isAlias(address)) {
return address;
}
// it's not a valid MMS address, return null
return null;
}
// An alias (or commonly called "nickname") is:
// Nickname must begin with a letter.
// Only letters a-z, numbers 0-9, or . are allowed in Nickname field.
public static boolean isAlias(String string) {
if (!MmsConfig.isAliasEnabled()) {
return false;
}
int len = string == null ? 0 : string.length();
if (len < MmsConfig.getAliasMinChars() || len > MmsConfig.getAliasMaxChars()) {
return false;
}
if (!Character.isLetter(string.charAt(0))) { // Nickname begins with a letter
return false;
}
for (int i = 1; i < len; i++) {
char c = string.charAt(i);
if (!(Character.isLetterOrDigit(c) || c == '.')) {
return false;
}
}
return true;
}
public static int getAttachmentType(SlideshowModel model, MultimediaMessagePdu mmp) {
if (model == null || mmp == null) {
return MessageItem.ATTACHMENT_TYPE_NOT_LOADED;
}
int numberOfSlides = model.size();
if (numberOfSlides > 1) {
return SLIDESHOW;
} else if (numberOfSlides == 1) {
// Only one slide in the slide-show.
SlideModel slide = model.get(0);
if (slide.hasVideo()) {
return VIDEO;
}
if (slide.hasAudio() && slide.hasImage()) {
return SLIDESHOW;
}
if (slide.hasAudio()) {
return AUDIO;
}
if (slide.hasImage()) {
return IMAGE;
}
if (slide.hasText()) {
return TEXT;
}
// Handle the multimedia message only has subject
String subject = mmp.getSubject() != null ? mmp.getSubject().getString() : null;
if (!TextUtils.isEmpty(subject)) {
return TEXT;
}
}
return MessageItem.ATTACHMENT_TYPE_NOT_LOADED;
}
/**
* Returns the position of the message in the cursor.
*
* @param cursor
* @param messageType
* @param messageId
* @param map
* @return
*/
public static int getPositionForMessageId(Cursor cursor, String messageType, long messageId, MessageColumns.ColumnsMap map) {
// Modified binary search on the cursor to find the position of the message in the cursor.
// It's modified because, although the SMS and MMS are generally ordered in terms of their
// ID, they have different IDs. So, we might have a list of IDs like:
//
// [ 4444, 4447, 4449, 4448, 312, 315, 4451 ]
//
// where the 44xx IDs are for SMS messages, and the 31x IDs are for MMS messages. The
// solution is to do a linear scan if we reach a point in the list where the ID doesn't
// match what we're looking for.
// Lower and upper bounds for doing the search
int min = 0;
int max = cursor.getCount() - 1;
while (min <= max) {
int mid = min / 2 + max / 2 + (min & max & 1);
cursor.moveToPosition(mid);
long candidateId = cursor.getLong(map.mColumnMsgId);
String candidateType = cursor.getString(map.mColumnMsgType);
if (messageType.equals(candidateType)) {
if (messageId < candidateId) {
max = mid - 1;
} else if (messageId > candidateId) {
min = mid + 1;
} else {
return mid;
}
} else {
// This message is the wrong type, so we have to do a linear search until we find a
// message that is the right type so we can orient ourselves.
// First, look forward. Stop when we move past max, or reach the end of the cursor.
boolean success = false;
while (cursor.getPosition() <= max && cursor.moveToNext()) {
candidateType = cursor.getString(map.mColumnMsgType);
if (candidateType.equals(messageType)) {
success = true;
break;
}
}
if (!success) {
// We didn't find any messages of the right type by looking forward, so try
// looking backwards.
cursor.moveToPosition(mid);
while (cursor.getPosition() >= min && cursor.moveToPrevious()) {
candidateType = cursor.getString(map.mColumnMsgType);
if (candidateType.equals(messageType)) {
success = true;
break;
}
}
}
if (!success) {
// There is no message with that ID of the correct type!
return -1;
}
// In this case, we've found a message of the correct type! Now to do the binary
// search stuff.
candidateId = cursor.getLong(map.mColumnMsgId);
int pos = cursor.getPosition();
if (messageId < candidateId) {
// The new upper bound is the minimum of where we started and where we ended
// up, subtract one.
max = (mid < pos ? mid : pos) - 1;
} else if (messageId > candidateId) {
// Same as above but in reverse.
min = (mid > pos ? mid : pos) + 1;
} else {
return pos;
}
}
}
// This is the case where we've minimized our bounds until they're the same, and we haven't
// found anything yet---this means that the item doesn't exist, so return -1.
return -1;
}
}