2
0
mirror of https://github.com/KDE/kdeconnect-android synced 2025-08-28 12:47:43 +00:00

[SMSApp] Support plain-text MMS

## Summary

Not having support for MMS caused some minor problems, like in https://bugs.kde.org/show_bug.cgi?id=398889 . This patch adds basic MMS support for plain-text MMS, including multi-target messages.

Android companion to https://invent.kde.org/kde/kdeconnect-kde/merge_requests/97

Currently there are several rough areas:
  - Multi-target messages do not have the full list of recipients (I am planning to work on this in another patch, because this one is already quite large enough)
  - Parsing MMS is significantly slower than parsing SMS. This makes sense, since we need to make significantly many more content:// calls for MMS. The only solution I can think of here is to add the ability to request a range of messages, which I need to do anyway, but which should not be part of this patch.
  - The desktop app is totally busted with regard to multi-target MMS, but that will also be fixed in another MR

BUG: 398889

## Test Plan

### Before:
Open SMS app on desktop, scroll through conversations, notice:
  - Any single-target message which had the most-recent message as an MMS does not appear
  - Any multi-target MMS conversations do not appear

### After:
Open SMS app on desktop, notice:
  - Conversations which have an MMS as their most-recent message appear
  - MMS which consisted of only text are rendered correctly
  - Multi-target conversations are shown (though pretty busted, as said before. Do not attempt to reply to one!)
This commit is contained in:
Simon Redman 2019-06-10 05:48:28 +00:00
parent ec43336153
commit 51e957d822
2 changed files with 412 additions and 70 deletions

View File

@ -21,9 +21,11 @@
package org.kde.kdeconnect.Helpers;
import android.annotation.SuppressLint;
import android.content.ContentUris;
import android.content.Context;
import android.database.ContentObserver;
import android.database.Cursor;
import android.database.sqlite.SQLiteException;
import android.net.Uri;
import android.os.Build;
import android.os.Looper;
@ -33,15 +35,23 @@ import android.util.Log;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
@SuppressLint("InlinedApi")
@ -64,7 +74,6 @@ public class SMSHelper {
*/
@RequiresApi(Build.VERSION_CODES.KITKAT)
private static Uri getSMSURIGood() {
// TODO: Why not use Telephony.MmsSms.CONTENT_URI?
return Telephony.Sms.CONTENT_URI;
}
@ -76,6 +85,21 @@ public class SMSHelper {
}
}
private static Uri getMMSUri() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
return Telephony.Mms.CONTENT_URI;
} else {
// Same as with getSMSUriBad, this is unsafe if the manufacturer did their own thing
// before this was part of the API
return Uri.parse("content://mms/");
}
}
private static Uri getMMSPartUri() {
// Android says we should have Telephony.Mms.Part.CONTENT_URI. Alas, we do not.
return Uri.parse("content://mms/part/");
}
/**
* Get the base address for all message conversations
*/
@ -89,6 +113,26 @@ public class SMSHelper {
}
}
@RequiresApi(api = Build.VERSION_CODES.FROYO)
private static Uri getCompleteConversationsUri() {
// This glorious - but completely undocumented - content URI gives us all messages, both MMS and SMS,
// in all conversations
// See https://stackoverflow.com/a/36439630/3723163
return Uri.parse("content://mms-sms/complete-conversations");
}
/**
* Column used to discriminate between SMS and MMS messages
* Unfortunately, this column is not defined for Telephony.MmsSms.CONTENT_CONVERSATIONS_URI
* (aka. content://mms-sms/conversations)
* which gives us the first message in every conversation, but it *is* defined for
* content://mms-sms/conversations/<threadID> which gives us the complete conversation matching
* that threadID, so at least it's partially useful to us.
*/
private static String getTransportTypeDiscriminatorColumn() {
return Telephony.MmsSms.TYPE_DISCRIMINATOR_COLUMN;
}
/**
* Get all the messages in a requested thread
*
@ -96,50 +140,108 @@ public class SMSHelper {
* @param threadID Thread to look up
* @return List of all messages in the thread
*/
public static List<Message> getMessagesInThread(Context context, ThreadID threadID) {
final String selection = ThreadID.lookupColumn + " == ?";
final String[] selectionArgs = new String[] { threadID.toString() };
public static @NonNull List<Message> getMessagesInThread(
@NonNull Context context,
@NonNull ThreadID threadID
) {
Uri uri = Uri.withAppendedPath(getConversationUri(), threadID.toString());
return getMessagesWithFilter(context, selection, selectionArgs);
return getMessages(uri, context, null, null, null, null);
}
/**
* Get all messages which have a timestamp after the requested timestamp
* Get the newest sent or received message
*
* This might have some potential for race conditions if many messages are received in a short
* timespan, but my target use-case is humans sending and receiving messages, so I don't think
* it will be an issue
*
* @param timestamp epoch in millis matching the timestamp to return
* @return null if no matching message is found, otherwise return a Message
*/
public static List<Message> getMessagesSinceTimestamp(Context context, long timestamp) {
final String selection = Message.DATE + " > ?";
final String[] selectionArgs = new String[] {Long.toString(timestamp)};
public static @Nullable Message getNewestMessage(
@NonNull Context context
) {
List<Message> messages = getMessagesWithFilter(context, null, null, 1L);
return getMessagesWithFilter(context, selection, selectionArgs);
if (messages.size() > 1) {
Log.w("SMSHelper", "getNewestMessage asked for one message but got " + messages.size());
}
if (messages.size() < 1) {
return null;
} else {
return messages.get(0);
}
}
/**
* Gets Messages for caller functions, such as: getMessagesWithFilter() and getConversations()
* Gets messages which match the selection
*
* @param Uri Uri indicating the messages database to read
* @param uri Uri indicating the messages database to read
* @param context android.content.Context running the request.
* @param selection Parameterizable filter to use with the ContentResolver query. May be null.
* @param selectionArgs Parameters for selection. May be null.
* @return Returns HashMap<ThreadID, List<Message>>, which is transformed in caller functions into other classes.
* @param sortOrder Sort ordering passed to Android's content resolver. May be null for unspecified
* @param numberToGet Number of things to get from the result. Pass null to get all
* @return Returns List<Message> of all messages in the return set, either in the order of sortOrder or in an unspecified order
*/
private static HashMap<ThreadID, List<Message>> getMessages(Uri Uri,
Context context,
String selection,
String[] selectionArgs) {
HashMap<ThreadID, List<Message>> toReturn = new HashMap<>();
try (Cursor myCursor = context.getContentResolver().query(
Uri,
Message.smsColumns,
private static @NonNull List<Message> getMessages(
@NonNull Uri uri,
@NonNull Context context,
@Nullable String selection,
@Nullable String[] selectionArgs,
@Nullable String sortOrder,
@Nullable Long numberToGet
) {
List<Message> toReturn = new ArrayList<>();
Set<String> allColumns = new HashSet<>();
allColumns.addAll(Arrays.asList(Message.smsColumns));
allColumns.addAll(Arrays.asList(Message.mmsColumns));
if (uri != getConversationUri()) {
// See https://issuetracker.google.com/issues/134592631
allColumns.add(getTransportTypeDiscriminatorColumn());
}
String[] fetchColumns = {};
fetchColumns = allColumns.toArray(fetchColumns);
try (Cursor myCursor = context.getContentResolver().query(
uri,
fetchColumns,
selection,
selectionArgs,
null)
sortOrder)
) {
if (myCursor != null && myCursor.moveToFirst()) {
int threadColumn = myCursor.getColumnIndexOrThrow(ThreadID.lookupColumn);
do {
int transportTypeColumn = myCursor.getColumnIndex(getTransportTypeDiscriminatorColumn());
TransportType transportType;
if (transportTypeColumn < 0) {
// The column didn't actually exist. See https://issuetracker.google.com/issues/134592631
// Try to determine using other information
int messageBoxColumn = myCursor.getColumnIndex(Telephony.Mms.MESSAGE_BOX);
// MessageBoxColumn is defined for MMS only
boolean messageBoxExists = !myCursor.isNull(messageBoxColumn);
if (messageBoxExists) {
transportType = TransportType.MMS;
} else {
// There is room here for me to have made an assumption and we'll guess wrong
// The penalty is the user will potentially get some garbled data, so that's not too bad.
transportType = TransportType.SMS;
}
} else {
String transportTypeString = myCursor.getString(transportTypeColumn);
if ("mms".equals(transportTypeString)) {
transportType = TransportType.MMS;
} else if ("sms".equals(transportTypeString)) {
transportType = TransportType.SMS;
} else {
Log.w("SMSHelper", "Skipping message with unknown TransportType: " + transportTypeString);
continue;
}
}
HashMap<String, String> messageInfo = new HashMap<>();
for (int columnIdx = 0; columnIdx < myCursor.getColumnCount(); columnIdx++) {
String colName = myCursor.getColumnName(columnIdx);
@ -147,37 +249,41 @@ public class SMSHelper {
messageInfo.put(colName, body);
}
Message message = new Message(messageInfo);
ThreadID threadID = new ThreadID(message.threadID);
if (!toReturn.containsKey(threadID)) {
toReturn.put(threadID, new ArrayList<>());
if (transportType == TransportType.SMS) {
parseSMS(context, messageInfo);
} else if (transportType == TransportType.MMS) {
parseMMS(context, messageInfo);
}
toReturn.get(threadID).add(message);
} while (myCursor.moveToNext());
} else {
// No conversations or SMSes available?
Message message = new Message(messageInfo);
toReturn.add(message);
} while ((numberToGet == null || toReturn.size() != numberToGet) && myCursor.moveToNext());
}
} catch (SQLiteException e) {
throw new MessageAccessException(fetchColumns, uri, e);
}
return toReturn;
}
/**
* Get all messages matching the passed filter. See documentation for Android's ContentResolver
*
* @param context android.content.Context running the request
* @param selection Parameterizable filter to use with the ContentResolver query. May be null.
* @param selectionArgs Parameters for selection. May be null.
* @return List of messages matching the filter
* @param numberToGet Number of things to return. Pass null to get all
* @return List of messages matching the filter, from newest to oldest
*/
private static List<Message> getMessagesWithFilter(Context context, String selection, String[] selectionArgs) {
HashMap<ThreadID, List<Message>> result = getMessages(SMSHelper.getSMSUri(), context, selection, selectionArgs);
List<Message> toReturn = new ArrayList<>();
private static List<Message> getMessagesWithFilter(
@NonNull Context context,
@Nullable String selection,
@Nullable String[] selectionArgs,
@Nullable Long numberToGet
) {
String sortOrder = Message.DATE + " DESC";
for(Map.Entry<ThreadID, List<Message>> entry : result.entrySet()) {
toReturn.addAll(entry.getValue());
}
return toReturn;
return getMessages(getCompleteConversationsUri(), context, selection, selectionArgs, sortOrder, numberToGet);
}
/**
@ -187,27 +293,226 @@ public class SMSHelper {
* @param context android.content.Context running the request
* @return Mapping of thread_id to the first message in each thread
*/
public static Map<ThreadID, Message> getConversations(Context context) {
HashMap<ThreadID, List<Message>> result = getMessages(SMSHelper.getConversationUri(), context, null, null);
HashMap<ThreadID, Message> toReturn = new HashMap<>();
public static Map<ThreadID, Message> getConversations(
@NonNull Context context
) {
Uri uri = SMSHelper.getConversationUri();
for(Map.Entry<ThreadID, List<Message>> entry : result.entrySet()) {
ThreadID returnThreadID = entry.getKey();
List<Message> messages = entry.getValue();
List<Message> unthreadedMessages = getMessages(uri, context, null, null, null, null);
toReturn.put(returnThreadID, messages.get(0));
Map<ThreadID, Message> toReturn = new HashMap<>();
for (Message message : unthreadedMessages) {
ThreadID tID = message.threadID;
if (toReturn.containsKey(tID)) {
Log.w("SMSHelper", "getConversations got two messages for the same ThreadID: " + tID);
}
toReturn.put(tID, message);
}
return toReturn;
}
private static void addEventFlag(
@NonNull Map<String, String> messageInfo,
@NonNull int eventFlag
) {
int oldEvent = Integer.parseInt(messageInfo.getOrDefault(Message.EVENT, "0"));
messageInfo.put(Message.EVENT, Integer.toString(oldEvent | eventFlag));
}
/**
* Do any parsing of an SMS message which still needs to be done
*/
private static void parseSMS(
@NonNull Context context,
@NonNull Map<String, String> messageInfo
) {
addEventFlag(messageInfo, Message.EVENT_TEXT_MESSAGE);
}
/**
* Parse all parts of the MMS message into the messageInfo format
* Original implementation from https://stackoverflow.com/a/6446831/3723163
*/
private static void parseMMS(
@NonNull Context context,
@NonNull Map<String, String> messageInfo
) {
addEventFlag(messageInfo, Message.EVENT_UNKNOWN);
String[] columns = {
Telephony.Mms.Part._ID, // The content ID of this part
Telephony.Mms.Part._DATA, // The location in the filesystem of the data
Telephony.Mms.Part.CONTENT_TYPE, // The mime type of the data
Telephony.Mms.Part.TEXT, // The plain text body of this MMS
Telephony.Mms.Part.CHARSET, // Charset of the plain text body
};
String mmsID = messageInfo.get(Message.U_ID);
String selection = Telephony.Mms.Part.MSG_ID + " = ?";
String[] selectionArgs = {mmsID};
// Get text body and attachments of the message
try (Cursor cursor = context.getContentResolver().query(
getMMSPartUri(),
columns,
selection,
selectionArgs,
null
)) {
if (cursor != null && cursor.moveToFirst()) {
int partIDColumn = cursor.getColumnIndexOrThrow(Telephony.Mms.Part._ID);
int contentTypeColumn = cursor.getColumnIndexOrThrow(Telephony.Mms.Part.CONTENT_TYPE);
int dataColumn = cursor.getColumnIndexOrThrow(Telephony.Mms.Part._DATA);
int textColumn = cursor.getColumnIndexOrThrow(Telephony.Mms.Part.TEXT);
// TODO: Parse charset (As usual, it is skimpily documented) (Possibly refer to MMS spec)
do {
Long partID = cursor.getLong(partIDColumn);
String contentType = cursor.getString(contentTypeColumn);
String data = cursor.getString(dataColumn);
if ("text/plain".equals(contentType)) {
String body;
if (data != null) {
// data != null means the data is on disk. Go get it.
body = getMmsText(context, partID);
} else {
body = cursor.getString(textColumn);
}
messageInfo.put(Message.BODY, body);
addEventFlag(messageInfo, Message.EVENT_TEXT_MESSAGE);
} //TODO: Parse more content types (photos and other attachments) here
} while (cursor.moveToNext());
}
}
// Determine whether the message was in- our out- bound
long messageBox = Long.parseLong(messageInfo.get(Telephony.Mms.MESSAGE_BOX));
if (messageBox == Telephony.Mms.MESSAGE_BOX_INBOX) {
messageInfo.put(Message.TYPE, Integer.toString(Telephony.Sms.MESSAGE_TYPE_INBOX));
} else if (messageBox == Telephony.Mms.MESSAGE_BOX_SENT) {
messageInfo.put(Message.TYPE, Integer.toString(Telephony.Sms.MESSAGE_TYPE_SENT));
} else {
// As an undocumented feature, it looks like the values of Mms.MESSAGE_BOX_*
// are the same as Sms.MESSAGE_TYPE_* of the same type. So by default let's just use
// the value we've got.
// This includes things like drafts, which are a far-distant plan to support
messageInfo.put(Message.TYPE, messageInfo.get(Telephony.Mms.MESSAGE_BOX));
}
// Get address(es) of the message
List<String> addresses = getMmsAddresses(context, Long.parseLong(mmsID));
// It looks like addresses[0] is always the sender of the message and
// following addresses are recipient(s)
// This usually means the addresses list is at least 2 long, but there are cases (special
// telco service messages) where it is not (only 1 long in that case, just the "sender")
// The address field which will get written to the message.
// Remember that this is always the address of the other side of the conversation
String address = "";
if (addresses.size() > 2) {
// TODO: Collect addresses for multi-target MMS
// Probably we will need to figure out the user's address at this point and strip it out of the list
addEventFlag(messageInfo, Message.EVENT_MULTI_TARGET);
} else {
if (messageBox == Telephony.Mms.MESSAGE_BOX_INBOX) {
address = addresses.get(0);
} else if (messageBox == Telephony.Mms.MESSAGE_BOX_SENT) {
address = addresses.get(1);
} else {
Log.w("SMSHelper", "Unknown message type " + messageBox + " while parsing addresses.");
// Not much smart to do here. Just leave as default.
}
}
messageInfo.put(Message.ADDRESS, address);
// Canonicalize the date field
// SMS uses epoch milliseconds, MMS uses epoch seconds. Standardize on milliseconds.
long rawDate = Long.parseLong(messageInfo.get(Message.DATE));
messageInfo.put(Message.DATE, Long.toString(rawDate * 1000));
}
/**
* Get the address(es) of an MMS message
* Original implementation from https://stackoverflow.com/a/6446831/3723163
*/
private static @NonNull List<String> getMmsAddresses(
@NonNull Context context,
@NonNull Long messageID
) {
Uri uri = ContentUris.appendId(getMMSUri().buildUpon(), messageID).appendPath("addr").build();
String[] columns = {
Telephony.Mms.Addr.MSG_ID, // ID of the message for which we are fetching addresses
Telephony.Mms.Addr.ADDRESS, // Address of this part
Telephony.Mms.Addr.CHARSET, // Charset of the returned address (where relevant) //TODO: Handle
};
String selection = Telephony.Mms.Addr.MSG_ID + " = ?";
String[] selectionArgs = {messageID.toString()};
List<String> addresses = new ArrayList<>();
try (Cursor addrCursor = context.getContentResolver().query(
uri,
columns,
selection,
selectionArgs,
null
)) {
if (addrCursor != null && addrCursor.moveToFirst()) {
int addressIndex = addrCursor.getColumnIndex(Telephony.Mms.Addr.ADDRESS);
do {
String address = addrCursor.getString(addressIndex);
addresses.add(address);
} while (addrCursor.moveToNext());
}
}
return addresses;
}
/**
* Get a text part of an MMS message
* Original implementation from https://stackoverflow.com/a/6446831/3723163
*/
private static String getMmsText(
@NonNull Context context,
@NonNull Long id
) {
Uri partURI = ContentUris.withAppendedId(getMMSPartUri(), id);
StringBuilder body = new StringBuilder();
try (InputStream is = context.getContentResolver().openInputStream(partURI)) {
if (is != null) {
InputStreamReader isr = new InputStreamReader(is, "UTF-8");
BufferedReader reader = new BufferedReader(isr);
String temp = reader.readLine();
while (temp != null) {
body.append(temp);
temp = reader.readLine();
}
}
} catch (IOException e) {
throw new SMSHelper.MessageAccessException(partURI, e);
}
return body.toString();
}
/**
* Register a ContentObserver for the Messages database
*
* @param observer ContentObserver to alert on Message changes
*/
public static void registerObserver(ContentObserver observer, Context context) {
public static void registerObserver(
@NonNull ContentObserver observer,
@NonNull Context context
) {
context.getContentResolver().registerContentObserver(
SMSHelper.getSMSUri(),
SMSHelper.getConversationUri(),
true,
observer
);
@ -240,6 +545,29 @@ public class SMSHelper {
}
}
/**
* Indicate that some error has occurred while reading a message.
* More useful for logging than catching and handling
*/
public static class MessageAccessException extends RuntimeException {
MessageAccessException(Uri uri, Throwable cause) {
super("Error getting messages from " + uri.toString(), cause);
}
MessageAccessException(String[] availableColumns, Uri uri, Throwable cause) {
super("Error getting messages from " + uri.toString() + " . Available columns were: " + Arrays.toString(availableColumns), cause);
}
}
/**
* Represent all known transport types
*/
public enum TransportType {
SMS,
MMS,
// Maybe in the future there will be more TransportType, but for now these are all I know about
}
/**
* Represent a message and all of its interesting data columns
*/
@ -250,8 +578,10 @@ public class SMSHelper {
public final long date;
final int type;
final int read;
final long threadID; // ThreadID is *int* for SMS messages but *long* for MMS
final int uID;
final ThreadID threadID; // ThreadID is *int* for SMS messages but *long* for MMS
final long uID;
final int event;
final int subscriptionID;
/**
* Named constants which are used to construct a Message
@ -263,15 +593,19 @@ public class SMSHelper {
static final String TYPE = Telephony.Sms.TYPE; // Compare with Telephony.TextBasedSmsColumns.MESSAGE_TYPE_*
static final String READ = Telephony.Sms.READ; // Whether we have received a read report for this message (int)
static final String THREAD_ID = ThreadID.lookupColumn; // Magic number which binds (message) threads
static final String U_ID = Telephony.Sms._ID; // Something which uniquely identifies this message
static final String U_ID = Telephony.Sms._ID; // Something which uniquely identifies this message
static final String EVENT = "event";
static final String SUBSCRIPTION_ID = Telephony.Sms.SUBSCRIPTION_ID; // An ID which appears to identify a SIM card
/**
* Event flags
* A message should have a bitwise-or of event flags before delivering the packet
* Any events not supported by the receiving device should be ignored
*/
public static final int TEXT_MESSAGE = 0x1; // This message has a "body" field which contains
// pure, human-readable text
public static final int EVENT_UNKNOWN = 0x0; // The message was of some type we did not understand
public static final int EVENT_TEXT_MESSAGE = 0x1; // This message has a "body" field which contains
// pure, human-readable text
public static final int EVENT_MULTI_TARGET = 0x2; // Indicates that this message has multiple recipients
/**
* Define the columns which are to be extracted from the Android SMS database
@ -284,6 +618,16 @@ public class SMSHelper {
Message.READ,
Message.THREAD_ID,
Message.U_ID,
Message.SUBSCRIPTION_ID,
};
static final String[] mmsColumns = new String[]{
Message.U_ID,
Message.THREAD_ID,
Message.DATE,
Message.READ,
Telephony.Mms.TEXT_ONLY,
Telephony.Mms.MESSAGE_BOX, // Compare with Telephony.BaseMmsColumns.MESSAGE_BOX_*
};
Message(final HashMap<String, String> messageInfo) {
@ -293,15 +637,17 @@ public class SMSHelper {
if (messageInfo.get(Message.TYPE) == null)
{
// To be honest, I have no idea why this happens. The docs say the TYPE field is mandatory.
// Just stick some junk in here and hope we can figure it out later.
// Quick investigation suggests that these are multi-target MMSes
Log.w("SMSHelper", "Encountered undefined message type");
type = -1;
// Proceed anyway, maybe this is not an important problem.
} else {
type = Integer.parseInt(messageInfo.get(Message.TYPE));
}
read = Integer.parseInt(messageInfo.get(Message.READ));
threadID = Long.parseLong(messageInfo.get(Message.THREAD_ID));
threadID = new ThreadID(Long.parseLong(messageInfo.get(Message.THREAD_ID)));
uID = Integer.parseInt(messageInfo.get(Message.U_ID));
subscriptionID = Integer.parseInt(messageInfo.get(Message.SUBSCRIPTION_ID));
event = Integer.parseInt(messageInfo.get(Message.EVENT));
}
public JSONObject toJSONObject() throws JSONException {
@ -314,6 +660,8 @@ public class SMSHelper {
json.put(Message.READ, read);
json.put(Message.THREAD_ID, threadID);
json.put(Message.U_ID, uID);
json.put(Message.SUBSCRIPTION_ID, subscriptionID);
json.put(Message.EVENT, event);
return json;
}

View File

@ -55,12 +55,12 @@ import org.kde.kdeconnect_tp.R;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import androidx.annotation.RequiresApi;
import androidx.core.content.ContextCompat;
import static org.kde.kdeconnect.Plugins.TelephonyPlugin.TelephonyPlugin.PACKET_TYPE_TELEPHONY;
@ -197,9 +197,9 @@ public class SMSPlugin extends Plugin {
long mostRecentTimestamp = mPlugin.mostRecentTimestamp;
mostRecentTimestampLock.unlock();
List<SMSHelper.Message> messages = SMSHelper.getMessagesSinceTimestamp(mPlugin.context, mostRecentTimestamp);
SMSHelper.Message message = SMSHelper.getNewestMessage(mPlugin.context);
if (messages.size() == 0) {
if (message.date <= mostRecentTimestamp) {
// Our onChange often gets called many times for a single message. Don't make unnecessary
// noise
return;
@ -207,15 +207,11 @@ public class SMSPlugin extends Plugin {
// Update the most recent counter
mostRecentTimestampLock.lock();
for (SMSHelper.Message message : messages) {
if (message.date > mostRecentTimestamp) {
mPlugin.mostRecentTimestamp = message.date;
}
}
mPlugin.mostRecentTimestamp = message.date;
mostRecentTimestampLock.unlock();
// Send the alert about the update
device.sendPacket(constructBulkMessagePacket(messages));
device.sendPacket(constructBulkMessagePacket(Collections.singleton(message)));
}
}
@ -352,8 +348,6 @@ public class SMSPlugin extends Plugin {
try {
JSONObject json = message.toJSONObject();
json.put("event", SMSHelper.Message.TEXT_MESSAGE);
body.put(json);
} catch (JSONException e) {
Log.e("Conversations", "Error serializing message");