package org.bitseal.database;
import java.util.ArrayList;
import org.bitseal.data.Message;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.util.Log;
/**
* A singleton class which controls the creation, reading, updating, and
* deletion of stored Message objects.
*
* @author Jonathan Coe
*/
public class MessageProvider
{
/**
* This is the maximum age of an object (in seconds) that PyBitmessage will accept.
* In this instance we use it as the period for which identical messages received will
* be treated as duplicates and ignored.
* */
private static final int PYBITMESSAGE_NEW_OBJECT_ACCEPTANCE_PERIOD = 216000;
private static MessageProvider sMessageProvider;
private Context mAppContext;
private static ContentResolver mContentResolver;
private static final String TAG = "MESSAGE_PROVIDER";
private MessageProvider(Context appContext)
{
mAppContext = appContext;
mContentResolver = mAppContext.getContentResolver();
}
/**
* Returns an instance of this singleton class.
*
* @param c - A Context object for the currently running application
*/
public static MessageProvider get(Context c)
{
if (sMessageProvider == null)
{
Context appContext = c.getApplicationContext();
sMessageProvider = new MessageProvider(appContext);
}
return sMessageProvider;
}
/**
* Takes an Message object and adds it to the app's
* SQLite database as a new record, returning the ID of the
* newly created record.
*
* @param m - The Message object to be added
*
* @return id - A long value representing the ID of the newly
* created record
*/
public long addMessage(Message m)
{
int belongsToMe = 0;
if (m.belongsToMe())
{
belongsToMe = 1;
}
ContentValues values = new ContentValues();
values.put(MessagesTable.COLUMN_MSG_PAYLOAD_ID, m.getMsgPayloadId());
values.put(MessagesTable.COLUMN_ACK_PAYLOAD_ID, m.getAckPayloadId());
values.put(MessagesTable.COLUMN_BELONGS_TO_ME, belongsToMe);
values.put(MessagesTable.COLUMN_READ, m.hasBeenRead());
values.put(MessagesTable.COLUMN_STATUS, m.getStatus());
values.put(MessagesTable.COLUMN_TIME, m.getTime());
values.put(MessagesTable.COLUMN_TO_ADDRESS, m.getToAddress());
values.put(MessagesTable.COLUMN_FROM_ADDRESS, m.getFromAddress());
values.put(MessagesTable.COLUMN_SUBJECT, m.getSubject());
values.put(MessagesTable.COLUMN_BODY, m.getBody());
Uri insertionUri = mContentResolver.insert(DatabaseContentProvider.CONTENT_URI_MESSAGES, values);
Log.i(TAG, "Message with subject " + m.getSubject() + " saved to database");
// Parse the ID of the newly created record from the insertion Uri
String uriString = insertionUri.toString();
String idString = uriString.substring(uriString.indexOf("/") + 1);
long id = Long.parseLong(idString);
return id;
}
/**
* Finds all Messages in the application's database that match the given field
*
* @param columnName - A String specifying the name of the column in the database that
* should be used to find matching records. See the MessagesTable class to find
* the relevant column name.
* @param searchString - A String specifying the value to search for. There are 4 use cases
* for this:<br>
* 1) The value to search for is a String (e.g. A label from the UI). In this case the value
* can be passed in directly.<br>
* 2) The value to search for is an int or long. In this case you should use String.valueOf(x)
* and pass in the resulting String.<br>
* 3) The value to search for is a boolean. In this case you should pass in the String "0" for
* false or the String "1" for true. <br>
* 4) The value to search for is a byte[]. In this case you should encode the byte[] into a
* Base64 encoded String using the class android.util.Base64 and pass in the resulting String.<br><br>
*
* <b>NOTE:</b> The above String conversion is very clumsy, but seems to be necessary. See
* https://stackoverflow.com/questions/20911760/android-how-to-query-sqlitedatabase-with-non-string-selection-args
*
* @return An ArrayList containing Message objects populated with the data from
* the database search
*/
public ArrayList<Message> searchMessages(String columnName, String searchString)
{
ArrayList<Message> matchingRecords = new ArrayList<Message>();
// Specify which colums from the table we are interested in
String[] projection = {
MessagesTable.COLUMN_ID,
MessagesTable.COLUMN_MSG_PAYLOAD_ID,
MessagesTable.COLUMN_ACK_PAYLOAD_ID,
MessagesTable.COLUMN_BELONGS_TO_ME,
MessagesTable.COLUMN_READ,
MessagesTable.COLUMN_STATUS,
MessagesTable.COLUMN_TIME,
MessagesTable.COLUMN_TO_ADDRESS,
MessagesTable.COLUMN_FROM_ADDRESS,
MessagesTable.COLUMN_SUBJECT,
MessagesTable.COLUMN_BODY};
// Query the database via the ContentProvider
Cursor cursor = mContentResolver.query(
DatabaseContentProvider.CONTENT_URI_MESSAGES,
projection,
MessagesTable.TABLE_MESSAGES + "." + columnName + " = ? ",
new String[]{searchString},
null);
if (cursor.moveToFirst())
{
do
{
long id = cursor.getLong(0);
long msgPayloadId = cursor.getLong(1);
long ackPayloadId = cursor.getLong(2);
int belongsToMeValue = cursor.getInt(3);
boolean belongsToMe = false;
if (belongsToMeValue == 1)
{
belongsToMe = true;
}
int readValue = cursor.getInt(4);
boolean read = false;
if (readValue == 1)
{
read = true;
}
String status = cursor.getString(5);
long time = cursor.getLong(6);
String toAddress = cursor.getString(7);
String fromAddress = cursor.getString(8);
String subject = cursor.getString(9);
String body = cursor.getString(10);
Message m = new Message();
m.setId(id);
m.setMsgPayloadId(msgPayloadId);
m.setAckPayloadId(ackPayloadId);
m.setBelongsToMe(belongsToMe);
m.setRead(read);
m.setStatus(status);
m.setTime(time);
m.setToAddress(toAddress);
m.setFromAddress(fromAddress);
m.setSubject(subject);
m.setBody(body);
matchingRecords.add(m);
}
while (cursor.moveToNext());
}
else
{
Log.i(TAG, "Unable to find any Messages with the value " + searchString + " in the " + columnName + " column");
cursor.close();
return matchingRecords;
}
cursor.close();
return matchingRecords;
}
/**
* Searches the database for the Message with the given ID.
* This method will return exactly one Message object or throw
* a RuntimeException.
*
* @param id - A long value representing the Message's ID.
*
* @return The Message object with the given ID.
*/
public Message searchForSingleRecord(long id)
{
ArrayList<Message> retrievedRecords = searchMessages(MessagesTable.COLUMN_ID, String.valueOf(id));
if (retrievedRecords.size() != 1)
{
throw new RuntimeException("There should be exactly 1 record found in this search. Instead " + retrievedRecords.size() + " records were found");
}
else
{
return retrievedRecords.get(0);
}
}
/**
* Returns an ArrayList containing all the Messages stored in the
* application's database
*
* @return An ArrayList containing one Message object for
* each record in the Messages table.
*/
public ArrayList<Message> getAllMessages()
{
ArrayList<Message> messages = new ArrayList<Message>();
// Specify which colums from the table we are interested in
String[] projection = {
MessagesTable.COLUMN_ID,
MessagesTable.COLUMN_MSG_PAYLOAD_ID,
MessagesTable.COLUMN_ACK_PAYLOAD_ID,
MessagesTable.COLUMN_BELONGS_TO_ME,
MessagesTable.COLUMN_READ,
MessagesTable.COLUMN_STATUS,
MessagesTable.COLUMN_TIME,
MessagesTable.COLUMN_TO_ADDRESS,
MessagesTable.COLUMN_FROM_ADDRESS,
MessagesTable.COLUMN_SUBJECT,
MessagesTable.COLUMN_BODY};
// Query the database via the ContentProvider
Cursor cursor = mContentResolver.query(
DatabaseContentProvider.CONTENT_URI_MESSAGES,
projection,
null,
null,
null);
if (cursor.moveToFirst())
{
do
{
long id = cursor.getLong(0);
long msgPayloadId = cursor.getLong(1);
long ackPayloadId = cursor.getLong(2);
int belongsToMeValue = cursor.getInt(3);
boolean belongsToMe = false;
if (belongsToMeValue == 1)
{
belongsToMe = true;
}
int readValue = cursor.getInt(4);
boolean read = false;
if (readValue == 1)
{
read = true;
}
String status = cursor.getString(5);
long time = cursor.getLong(6);
String toAddress = cursor.getString(7);
String fromAddress = cursor.getString(8);
String subject = cursor.getString(9);
String body = cursor.getString(10);
Message m = new Message();
m.setId(id);
m.setMsgPayloadId(msgPayloadId);
m.setAckPayloadId(ackPayloadId);
m.setBelongsToMe(belongsToMe);
m.setRead(read);
m.setStatus(status);
m.setTime(time);
m.setToAddress(toAddress);
m.setFromAddress(fromAddress);
m.setSubject(subject);
m.setBody(body);
messages.add(m);
}
while (cursor.moveToNext());
}
cursor.close();
return messages;
}
/**
* Updates the database record for a given Message object<br><br>
*
* <b>NOTE:</b> This method uses the given Message's ID field to determine
* which record in the database to update
*
* @param m - The Message object to be updated
*/
public void updateMessage(Message m)
{
int belongsToMe = 0;
if (m.belongsToMe())
{
belongsToMe = 1;
}
ContentValues values = new ContentValues();
values.put(MessagesTable.COLUMN_MSG_PAYLOAD_ID, m.getMsgPayloadId());
values.put(MessagesTable.COLUMN_ACK_PAYLOAD_ID, m.getAckPayloadId());
values.put(MessagesTable.COLUMN_BELONGS_TO_ME, belongsToMe);
values.put(MessagesTable.COLUMN_READ, m.hasBeenRead());
values.put(MessagesTable.COLUMN_STATUS, m.getStatus());
values.put(MessagesTable.COLUMN_TIME, m.getTime());
values.put(MessagesTable.COLUMN_TO_ADDRESS, m.getToAddress());
values.put(MessagesTable.COLUMN_FROM_ADDRESS, m.getFromAddress());
values.put(MessagesTable.COLUMN_SUBJECT, m.getSubject());
values.put(MessagesTable.COLUMN_BODY, m.getBody());
long id = m.getId();
// Query the database via the ContentProvider and update the record with the matching ID
mContentResolver.update(DatabaseContentProvider.CONTENT_URI_MESSAGES,
values,
MessagesTable.COLUMN_ID + " = ? ",
new String[]{String.valueOf(id)});
Log.i(TAG, "Message ID " + id + " updated");
}
/**
* Deletes a Message object from the application's SQLite database<br><br>
*
* <b>NOTE:</b> This method uses the given Message's ID field to determine
* which record in the database to delete
*
* @param m - The Message object to be deleted
*/
public void deleteMessage(Message m)
{
long id = m.getId();
// Query the database via the ContentProvider and delete the record with the matching ID
int recordsDeleted = mContentResolver.delete(
DatabaseContentProvider.CONTENT_URI_MESSAGES,
MessagesTable.COLUMN_ID + " = ? ",
new String[]{String.valueOf(id)});
Log.i(TAG, recordsDeleted + " Message(s) deleted from database");
}
/**
* Deletes all Messages from the database
*/
public void deleteAllMessages()
{
// Query the database via the ContentProvider and delete all the records
int recordsDeleted = mContentResolver.delete(
DatabaseContentProvider.CONTENT_URI_MESSAGES,
null,
null);
Log.i(TAG, recordsDeleted + " Message(s) deleted from database");
}
/**
* Takes a Message and determines whether it is a duplicate of
* any messages that are already in the inbox. This takes account
* of the to address, from address, subject, body, and received time
* of the message.
*
* @param message - The Message object to be checked
*
* @return A boolean indicating whether or not the message is a duplicate
*/
public boolean detectDuplicateMessage(Message message)
{
// Extract the data that we will use to make the duplicate comparison from the Message provided
String toAddress = message.getToAddress();
String fromAddress = message.getFromAddress();
String subject = message.getSubject();
String body = message.getBody();
long receivedTime = message.getTime();
// Work out the time value to use in searching for duplicates. We will only consider a
// Message to be a duplicate if we received it within a certain period of time, a period
// defined precisely by PYBITMESSAGE_NEW_OBJECT_ACCEPTANCE_PERIOD.
long receivedSinceTime = receivedTime - PYBITMESSAGE_NEW_OBJECT_ACCEPTANCE_PERIOD;
// Get the String representation of the time value
String receivedSinceString = String.valueOf(receivedSinceTime);
// Build the selection statement we will use
String selection =
MessagesTable.TABLE_MESSAGES + "." + MessagesTable.COLUMN_TO_ADDRESS + " = ? AND " +
MessagesTable.TABLE_MESSAGES + "." + MessagesTable.COLUMN_FROM_ADDRESS + " = ? AND " +
MessagesTable.TABLE_MESSAGES + "." + MessagesTable.COLUMN_SUBJECT + " = ? AND " +
MessagesTable.TABLE_MESSAGES + "." + MessagesTable.COLUMN_BODY + " = ? AND " +
MessagesTable.TABLE_MESSAGES + "." + MessagesTable.COLUMN_TIME + " > ?";
// Build the String[] of selection arguments we will use
String[] selectionArgs = new String[]{toAddress, fromAddress, subject, body, receivedSinceString};
// Specify which columns from the table we are interested in
String[] projection = {
MessagesTable.COLUMN_ID,
MessagesTable.COLUMN_MSG_PAYLOAD_ID,
MessagesTable.COLUMN_ACK_PAYLOAD_ID,
MessagesTable.COLUMN_BELONGS_TO_ME,
MessagesTable.COLUMN_READ,
MessagesTable.COLUMN_STATUS,
MessagesTable.COLUMN_TIME,
MessagesTable.COLUMN_TO_ADDRESS,
MessagesTable.COLUMN_FROM_ADDRESS,
MessagesTable.COLUMN_SUBJECT,
MessagesTable.COLUMN_BODY};
// Query the database via the ContentProvider
Cursor cursor = mContentResolver.query(
DatabaseContentProvider.CONTENT_URI_MESSAGES,
projection,
selection,
selectionArgs,
null);
int counter = 0;
if (cursor.moveToFirst())
{
while (cursor.moveToNext());
{
counter ++;
}
Log.d(TAG, "Found " + counter + " duplicates of message with subject " + subject + " and to address " + toAddress);
cursor.close();
return true;
}
else
{
Log.i(TAG, "Found no duplicates for the message provided");
cursor.close();
return false;
}
}
}