2012-10-16 09:46:40 -04:00
|
|
|
package com.fsck.k9.provider;
|
|
|
|
|
2012-10-17 23:15:40 -04:00
|
|
|
import java.util.ArrayList;
|
|
|
|
import java.util.HashMap;
|
2012-10-16 09:46:40 -04:00
|
|
|
import java.util.List;
|
2012-10-17 23:15:40 -04:00
|
|
|
import java.util.Map;
|
2012-10-16 09:46:40 -04:00
|
|
|
|
|
|
|
import com.fsck.k9.Account;
|
|
|
|
import com.fsck.k9.Preferences;
|
|
|
|
import com.fsck.k9.helper.StringUtils;
|
2012-10-30 11:27:09 -04:00
|
|
|
import com.fsck.k9.helper.Utility;
|
2012-10-16 09:46:40 -04:00
|
|
|
import com.fsck.k9.mail.MessagingException;
|
|
|
|
import com.fsck.k9.mail.store.LocalStore;
|
|
|
|
import com.fsck.k9.mail.store.LockableDatabase;
|
|
|
|
import com.fsck.k9.mail.store.LockableDatabase.DbCallback;
|
|
|
|
import com.fsck.k9.mail.store.LockableDatabase.WrappedException;
|
|
|
|
import com.fsck.k9.mail.store.UnavailableStorageException;
|
2012-11-02 20:52:45 -04:00
|
|
|
import com.fsck.k9.search.SqlQueryBuilder;
|
2012-10-16 09:46:40 -04:00
|
|
|
|
2012-10-17 23:15:40 -04:00
|
|
|
import android.annotation.TargetApi;
|
2012-10-16 09:46:40 -04:00
|
|
|
import android.content.ContentProvider;
|
|
|
|
import android.content.ContentResolver;
|
|
|
|
import android.content.ContentValues;
|
|
|
|
import android.content.Context;
|
|
|
|
import android.content.UriMatcher;
|
|
|
|
import android.database.Cursor;
|
|
|
|
import android.database.CursorWrapper;
|
|
|
|
import android.database.sqlite.SQLiteDatabase;
|
|
|
|
import android.net.Uri;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Content Provider used to display the message list etc.
|
|
|
|
*
|
|
|
|
* <p>
|
|
|
|
* For now this content provider is for internal use only. In the future we may allow third-party
|
|
|
|
* apps to access K-9 Mail content using this content provider.
|
|
|
|
* </p>
|
|
|
|
*/
|
|
|
|
/*
|
|
|
|
* TODO:
|
|
|
|
* - add support for account list and folder list
|
|
|
|
*/
|
|
|
|
public class EmailProvider extends ContentProvider {
|
|
|
|
private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
|
|
|
|
|
2012-11-22 14:51:41 -05:00
|
|
|
public static final String AUTHORITY = "com.fsck.k9.provider.email";
|
2012-10-16 09:46:40 -04:00
|
|
|
|
|
|
|
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY);
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Constants that are used for the URI matching.
|
|
|
|
*/
|
|
|
|
private static final int MESSAGE_BASE = 0;
|
|
|
|
private static final int MESSAGES = MESSAGE_BASE;
|
2012-10-22 21:01:50 -04:00
|
|
|
private static final int MESSAGES_THREADED = MESSAGE_BASE + 1;
|
2012-10-16 09:46:40 -04:00
|
|
|
//private static final int MESSAGES_THREAD = MESSAGE_BASE + 2;
|
|
|
|
|
2012-12-06 23:29:05 -05:00
|
|
|
private static final int STATS_BASE = 100;
|
|
|
|
private static final int STATS = STATS_BASE;
|
|
|
|
|
2012-10-16 09:46:40 -04:00
|
|
|
|
|
|
|
private static final String MESSAGES_TABLE = "messages";
|
|
|
|
|
2012-10-22 21:01:50 -04:00
|
|
|
private static final String[] MESSAGES_COLUMNS = {
|
|
|
|
MessageColumns.ID,
|
|
|
|
MessageColumns.UID,
|
|
|
|
MessageColumns.INTERNAL_DATE,
|
|
|
|
MessageColumns.SUBJECT,
|
|
|
|
MessageColumns.DATE,
|
|
|
|
MessageColumns.MESSAGE_ID,
|
|
|
|
MessageColumns.SENDER_LIST,
|
|
|
|
MessageColumns.TO_LIST,
|
|
|
|
MessageColumns.CC_LIST,
|
|
|
|
MessageColumns.BCC_LIST,
|
|
|
|
MessageColumns.REPLY_TO_LIST,
|
|
|
|
MessageColumns.FLAGS,
|
|
|
|
MessageColumns.ATTACHMENT_COUNT,
|
|
|
|
MessageColumns.FOLDER_ID,
|
|
|
|
MessageColumns.PREVIEW,
|
|
|
|
MessageColumns.THREAD_ROOT,
|
|
|
|
MessageColumns.THREAD_PARENT,
|
2012-12-03 23:13:58 -05:00
|
|
|
MessageColumns.READ,
|
|
|
|
MessageColumns.FLAGGED,
|
|
|
|
MessageColumns.ANSWERED,
|
|
|
|
MessageColumns.FORWARDED,
|
2012-10-22 21:01:50 -04:00
|
|
|
InternalMessageColumns.DELETED,
|
|
|
|
InternalMessageColumns.EMPTY,
|
|
|
|
InternalMessageColumns.TEXT_CONTENT,
|
|
|
|
InternalMessageColumns.HTML_CONTENT,
|
|
|
|
InternalMessageColumns.MIME_TYPE
|
|
|
|
};
|
2012-10-16 09:46:40 -04:00
|
|
|
|
|
|
|
static {
|
|
|
|
UriMatcher matcher = sUriMatcher;
|
|
|
|
|
|
|
|
matcher.addURI(AUTHORITY, "account/*/messages", MESSAGES);
|
2012-10-22 21:01:50 -04:00
|
|
|
matcher.addURI(AUTHORITY, "account/*/messages/threaded", MESSAGES_THREADED);
|
2012-10-16 09:46:40 -04:00
|
|
|
//matcher.addURI(AUTHORITY, "account/*/thread/#", MESSAGES_THREAD);
|
2012-12-06 23:29:05 -05:00
|
|
|
|
|
|
|
matcher.addURI(AUTHORITY, "account/*/stats", STATS);
|
2012-10-16 09:46:40 -04:00
|
|
|
}
|
|
|
|
|
2012-10-17 23:15:40 -04:00
|
|
|
public interface SpecialColumns {
|
|
|
|
public static final String ACCOUNT_UUID = "account_uuid";
|
2012-10-30 11:27:09 -04:00
|
|
|
|
|
|
|
public static final String FOLDER_NAME = "name";
|
2012-10-30 20:45:44 -04:00
|
|
|
public static final String INTEGRATE = "integrate";
|
2012-10-17 23:15:40 -04:00
|
|
|
}
|
2012-10-16 09:46:40 -04:00
|
|
|
|
|
|
|
public interface MessageColumns {
|
|
|
|
public static final String ID = "id";
|
|
|
|
public static final String UID = "uid";
|
|
|
|
public static final String INTERNAL_DATE = "internal_date";
|
|
|
|
public static final String SUBJECT = "subject";
|
|
|
|
public static final String DATE = "date";
|
|
|
|
public static final String MESSAGE_ID = "message_id";
|
|
|
|
public static final String SENDER_LIST = "sender_list";
|
|
|
|
public static final String TO_LIST = "to_list";
|
|
|
|
public static final String CC_LIST = "cc_list";
|
|
|
|
public static final String BCC_LIST = "bcc_list";
|
|
|
|
public static final String REPLY_TO_LIST = "reply_to_list";
|
|
|
|
public static final String FLAGS = "flags";
|
|
|
|
public static final String ATTACHMENT_COUNT = "attachment_count";
|
|
|
|
public static final String FOLDER_ID = "folder_id";
|
|
|
|
public static final String PREVIEW = "preview";
|
|
|
|
public static final String THREAD_ROOT = "thread_root";
|
|
|
|
public static final String THREAD_PARENT = "thread_parent";
|
2012-10-22 21:01:50 -04:00
|
|
|
public static final String THREAD_COUNT = "thread_count";
|
2012-12-03 23:13:58 -05:00
|
|
|
public static final String READ = "read";
|
|
|
|
public static final String FLAGGED = "flagged";
|
|
|
|
public static final String ANSWERED = "answered";
|
|
|
|
public static final String FORWARDED = "forwarded";
|
2012-10-16 09:46:40 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
private interface InternalMessageColumns extends MessageColumns {
|
|
|
|
public static final String DELETED = "deleted";
|
|
|
|
public static final String EMPTY = "empty";
|
|
|
|
public static final String TEXT_CONTENT = "text_content";
|
|
|
|
public static final String HTML_CONTENT = "html_content";
|
|
|
|
public static final String MIME_TYPE = "mime_type";
|
|
|
|
}
|
|
|
|
|
2012-12-06 23:29:05 -05:00
|
|
|
public interface StatsColumns {
|
|
|
|
public static final String UNREAD_COUNT = "unread_count";
|
|
|
|
public static final String FLAGGED_COUNT = "flagged_count";
|
|
|
|
}
|
|
|
|
|
|
|
|
private static final String[] STATS_DEFAULT_PROJECTION = {
|
|
|
|
StatsColumns.UNREAD_COUNT,
|
|
|
|
StatsColumns.FLAGGED_COUNT
|
|
|
|
};
|
2012-10-16 09:46:40 -04:00
|
|
|
|
|
|
|
private Preferences mPreferences;
|
|
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public boolean onCreate() {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public String getType(Uri uri) {
|
|
|
|
throw new RuntimeException("not implemented yet");
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
|
|
|
|
String sortOrder) {
|
|
|
|
|
|
|
|
int match = sUriMatcher.match(uri);
|
|
|
|
if (match < 0) {
|
|
|
|
throw new IllegalArgumentException("Unknown URI: " + uri);
|
|
|
|
}
|
|
|
|
|
|
|
|
ContentResolver contentResolver = getContext().getContentResolver();
|
|
|
|
Cursor cursor = null;
|
|
|
|
switch (match) {
|
2012-10-22 21:01:50 -04:00
|
|
|
case MESSAGES:
|
|
|
|
case MESSAGES_THREADED: {
|
2012-10-16 09:46:40 -04:00
|
|
|
List<String> segments = uri.getPathSegments();
|
|
|
|
String accountUuid = segments.get(1);
|
|
|
|
|
2012-10-17 23:15:40 -04:00
|
|
|
List<String> dbColumnNames = new ArrayList<String>(projection.length);
|
|
|
|
Map<String, String> specialColumns = new HashMap<String, String>();
|
|
|
|
for (String columnName : projection) {
|
|
|
|
if (SpecialColumns.ACCOUNT_UUID.equals(columnName)) {
|
|
|
|
specialColumns.put(SpecialColumns.ACCOUNT_UUID, accountUuid);
|
|
|
|
} else {
|
|
|
|
dbColumnNames.add(columnName);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
String[] dbProjection = dbColumnNames.toArray(new String[0]);
|
|
|
|
|
2012-10-22 21:01:50 -04:00
|
|
|
if (match == MESSAGES) {
|
|
|
|
cursor = getMessages(accountUuid, dbProjection, selection, selectionArgs,
|
|
|
|
sortOrder);
|
|
|
|
} else if (match == MESSAGES_THREADED) {
|
|
|
|
cursor = getThreadedMessages(accountUuid, dbProjection, selection,
|
|
|
|
selectionArgs, sortOrder);
|
|
|
|
} else {
|
|
|
|
throw new RuntimeException("Not implemented");
|
|
|
|
}
|
2012-10-16 09:46:40 -04:00
|
|
|
|
|
|
|
cursor.setNotificationUri(contentResolver, uri);
|
2012-10-17 23:15:40 -04:00
|
|
|
|
|
|
|
cursor = new SpecialColumnsCursor(new IdTrickeryCursor(cursor), projection,
|
|
|
|
specialColumns);
|
|
|
|
|
2012-10-16 09:46:40 -04:00
|
|
|
break;
|
|
|
|
}
|
2012-12-06 23:29:05 -05:00
|
|
|
case STATS: {
|
|
|
|
List<String> segments = uri.getPathSegments();
|
|
|
|
String accountUuid = segments.get(1);
|
|
|
|
|
|
|
|
cursor = getAccountStats(accountUuid, projection, selection, selectionArgs);
|
|
|
|
|
|
|
|
Uri notificationUri = Uri.withAppendedPath(CONTENT_URI, "account/" + accountUuid +
|
|
|
|
"/messages");
|
|
|
|
|
|
|
|
cursor.setNotificationUri(contentResolver, notificationUri);
|
|
|
|
break;
|
|
|
|
}
|
2012-10-16 09:46:40 -04:00
|
|
|
}
|
|
|
|
|
2012-10-17 23:15:40 -04:00
|
|
|
return cursor;
|
2012-10-16 09:46:40 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public int delete(Uri uri, String selection, String[] selectionArgs) {
|
|
|
|
throw new RuntimeException("not implemented yet");
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public Uri insert(Uri uri, ContentValues values) {
|
|
|
|
throw new RuntimeException("not implemented yet");
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
|
|
|
|
throw new RuntimeException("not implemented yet");
|
|
|
|
}
|
|
|
|
|
|
|
|
protected Cursor getMessages(String accountUuid, final String[] projection,
|
|
|
|
final String selection, final String[] selectionArgs, final String sortOrder) {
|
|
|
|
|
|
|
|
Account account = getAccount(accountUuid);
|
|
|
|
LockableDatabase database = getDatabase(account);
|
|
|
|
|
|
|
|
try {
|
|
|
|
return database.execute(false, new DbCallback<Cursor>() {
|
|
|
|
@Override
|
|
|
|
public Cursor doDbWork(SQLiteDatabase db) throws WrappedException,
|
|
|
|
UnavailableStorageException {
|
|
|
|
|
|
|
|
String where;
|
|
|
|
if (StringUtils.isNullOrEmpty(selection)) {
|
2012-10-25 15:51:14 -04:00
|
|
|
where = InternalMessageColumns.DELETED + "=0 AND (" +
|
|
|
|
InternalMessageColumns.EMPTY + " IS NULL OR " +
|
|
|
|
InternalMessageColumns.EMPTY + "!=1)";
|
2012-10-16 09:46:40 -04:00
|
|
|
} else {
|
|
|
|
where = "(" + selection + ") AND " +
|
2012-10-25 15:51:14 -04:00
|
|
|
InternalMessageColumns.DELETED + "=0 AND (" +
|
|
|
|
InternalMessageColumns.EMPTY + " IS NULL OR " +
|
|
|
|
InternalMessageColumns.EMPTY + "!=1)";
|
2012-10-16 09:46:40 -04:00
|
|
|
}
|
|
|
|
|
2012-10-30 11:27:09 -04:00
|
|
|
final Cursor cursor;
|
2012-10-30 20:45:44 -04:00
|
|
|
//TODO: check projection and selection for folder columns
|
2012-10-30 11:27:09 -04:00
|
|
|
if (Utility.arrayContains(projection, SpecialColumns.FOLDER_NAME)) {
|
|
|
|
StringBuilder query = new StringBuilder();
|
|
|
|
query.append("SELECT ");
|
|
|
|
boolean first = true;
|
|
|
|
for (String columnName : projection) {
|
|
|
|
if (!first) {
|
|
|
|
query.append(",");
|
|
|
|
} else {
|
|
|
|
first = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (MessageColumns.ID.equals(columnName)) {
|
|
|
|
query.append("m.");
|
|
|
|
query.append(MessageColumns.ID);
|
|
|
|
query.append(" AS ");
|
|
|
|
query.append(MessageColumns.ID);
|
|
|
|
} else {
|
|
|
|
query.append(columnName);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
query.append(" FROM messages m " +
|
|
|
|
"LEFT JOIN folders f ON (m.folder_id = f.id) " +
|
|
|
|
"WHERE ");
|
2012-11-02 20:52:45 -04:00
|
|
|
query.append(SqlQueryBuilder.addPrefixToSelection(MESSAGES_COLUMNS,
|
|
|
|
"m.", where));
|
2012-10-30 11:27:09 -04:00
|
|
|
query.append(" ORDER BY ");
|
2012-11-02 20:52:45 -04:00
|
|
|
query.append(SqlQueryBuilder.addPrefixToSelection(MESSAGES_COLUMNS,
|
|
|
|
"m.", sortOrder));
|
2012-10-30 11:27:09 -04:00
|
|
|
|
|
|
|
cursor = db.rawQuery(query.toString(), selectionArgs);
|
|
|
|
} else {
|
|
|
|
cursor = db.query(MESSAGES_TABLE, projection, where, selectionArgs, null,
|
|
|
|
null, sortOrder);
|
|
|
|
}
|
|
|
|
|
|
|
|
return cursor;
|
2012-10-16 09:46:40 -04:00
|
|
|
}
|
|
|
|
});
|
|
|
|
} catch (UnavailableStorageException e) {
|
|
|
|
throw new RuntimeException("Storage not available", e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-10-22 21:01:50 -04:00
|
|
|
protected Cursor getThreadedMessages(String accountUuid, final String[] projection,
|
|
|
|
final String selection, final String[] selectionArgs, final String sortOrder) {
|
|
|
|
|
|
|
|
Account account = getAccount(accountUuid);
|
|
|
|
LockableDatabase database = getDatabase(account);
|
|
|
|
|
|
|
|
try {
|
|
|
|
return database.execute(false, new DbCallback<Cursor>() {
|
|
|
|
@Override
|
|
|
|
public Cursor doDbWork(SQLiteDatabase db) throws WrappedException,
|
|
|
|
UnavailableStorageException {
|
|
|
|
|
|
|
|
StringBuilder query = new StringBuilder();
|
|
|
|
query.append("SELECT ");
|
|
|
|
boolean first = true;
|
|
|
|
for (String columnName : projection) {
|
|
|
|
if (!first) {
|
|
|
|
query.append(",");
|
|
|
|
} else {
|
|
|
|
first = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (MessageColumns.DATE.equals(columnName)) {
|
|
|
|
query.append("MAX(m.date) AS " + MessageColumns.DATE);
|
|
|
|
} else if (MessageColumns.THREAD_COUNT.equals(columnName)) {
|
|
|
|
query.append("COUNT(h.id) AS " + MessageColumns.THREAD_COUNT);
|
2012-10-30 11:27:09 -04:00
|
|
|
} else if (SpecialColumns.FOLDER_NAME.equals(columnName)) {
|
|
|
|
query.append("f." + SpecialColumns.FOLDER_NAME + " AS " +
|
|
|
|
SpecialColumns.FOLDER_NAME);
|
2012-10-30 20:45:44 -04:00
|
|
|
} else if (SpecialColumns.INTEGRATE.equals(columnName)) {
|
|
|
|
query.append("f." + SpecialColumns.INTEGRATE + " AS " +
|
|
|
|
SpecialColumns.INTEGRATE);
|
2012-10-22 21:01:50 -04:00
|
|
|
} else {
|
|
|
|
query.append("m.");
|
|
|
|
query.append(columnName);
|
|
|
|
query.append(" AS ");
|
|
|
|
query.append(columnName);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
query.append(
|
|
|
|
" FROM messages h JOIN messages m " +
|
2012-10-30 11:27:09 -04:00
|
|
|
"ON (h.id = m.thread_root OR h.id = m.id) ");
|
|
|
|
|
2012-10-30 20:45:44 -04:00
|
|
|
//TODO: check projection and selection for folder columns
|
2012-10-30 11:27:09 -04:00
|
|
|
if (Utility.arrayContains(projection, SpecialColumns.FOLDER_NAME)) {
|
|
|
|
query.append("LEFT JOIN folders f ON (m.folder_id = f.id) ");
|
|
|
|
}
|
|
|
|
|
|
|
|
query.append(
|
2012-10-22 21:01:50 -04:00
|
|
|
"WHERE " +
|
|
|
|
"(h.deleted = 0 AND m.deleted = 0 AND " +
|
|
|
|
"(m.empty IS NULL OR m.empty != 1) AND " +
|
|
|
|
"h.thread_root IS NULL) ");
|
|
|
|
|
|
|
|
if (!StringUtils.isNullOrEmpty(selection)) {
|
|
|
|
query.append("AND (");
|
2012-11-02 20:52:45 -04:00
|
|
|
query.append(SqlQueryBuilder.addPrefixToSelection(MESSAGES_COLUMNS,
|
|
|
|
"h.", selection));
|
2012-10-22 21:01:50 -04:00
|
|
|
query.append(") ");
|
|
|
|
}
|
|
|
|
|
|
|
|
query.append("GROUP BY h.id");
|
|
|
|
|
|
|
|
if (!StringUtils.isNullOrEmpty(sortOrder)) {
|
|
|
|
query.append(" ORDER BY ");
|
2012-11-02 20:52:45 -04:00
|
|
|
query.append(SqlQueryBuilder.addPrefixToSelection(MESSAGES_COLUMNS,
|
|
|
|
"m.", sortOrder));
|
2012-10-22 21:01:50 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
return db.rawQuery(query.toString(), selectionArgs);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
} catch (UnavailableStorageException e) {
|
|
|
|
throw new RuntimeException("Storage not available", e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-12-06 23:29:05 -05:00
|
|
|
private Cursor getAccountStats(String accountUuid, String[] columns,
|
|
|
|
final String selection, final String[] selectionArgs) {
|
|
|
|
|
|
|
|
Account account = getAccount(accountUuid);
|
|
|
|
LockableDatabase database = getDatabase(account);
|
|
|
|
|
|
|
|
// Use default projection if none was given
|
|
|
|
String[] sourceProjection = (columns == null) ? STATS_DEFAULT_PROJECTION : columns;
|
|
|
|
|
|
|
|
// Create SQL query string
|
|
|
|
final StringBuilder sql = new StringBuilder();
|
|
|
|
sql.append("SELECT ");
|
|
|
|
|
|
|
|
// Append projection for the database query
|
|
|
|
// e.g. "SUM(read=0) AS unread_count, SUM(flagged) AS flagged_count"
|
|
|
|
boolean first = true;
|
|
|
|
for (String columnName : sourceProjection) {
|
|
|
|
if (!first) {
|
|
|
|
sql.append(',');
|
|
|
|
} else {
|
|
|
|
first = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (StatsColumns.UNREAD_COUNT.equals(columnName)) {
|
|
|
|
sql.append("SUM(" + MessageColumns.READ + "=0) AS " + StatsColumns.UNREAD_COUNT);
|
|
|
|
} else if (StatsColumns.FLAGGED_COUNT.equals(columnName)) {
|
|
|
|
sql.append("SUM(" + MessageColumns.FLAGGED + ") AS " + StatsColumns.FLAGGED_COUNT);
|
|
|
|
} else {
|
|
|
|
throw new IllegalArgumentException("Column name not allowed: " + columnName);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Table selection
|
|
|
|
sql.append(" FROM messages");
|
|
|
|
if (selection != null && selection.contains(SpecialColumns.INTEGRATE)) {
|
|
|
|
sql.append(" JOIN folders ON (folders.id = messages.folder_id)");
|
|
|
|
}
|
|
|
|
|
|
|
|
// WHERE clause
|
|
|
|
sql.append(" WHERE (deleted=0 AND (empty IS NULL OR empty!=1))");
|
|
|
|
if (selection != null) {
|
|
|
|
sql.append(" AND (");
|
|
|
|
sql.append(selection);
|
|
|
|
sql.append(")");
|
|
|
|
}
|
|
|
|
|
|
|
|
// Query the database and return the result cursor
|
|
|
|
try {
|
|
|
|
return database.execute(false, new DbCallback<Cursor>() {
|
|
|
|
@Override
|
|
|
|
public Cursor doDbWork(SQLiteDatabase db) throws WrappedException,
|
|
|
|
UnavailableStorageException {
|
|
|
|
|
|
|
|
return db.rawQuery(sql.toString(), selectionArgs);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
} catch (UnavailableStorageException e) {
|
|
|
|
throw new RuntimeException("Storage not available", e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-10-16 09:46:40 -04:00
|
|
|
private Account getAccount(String accountUuid) {
|
|
|
|
if (mPreferences == null) {
|
|
|
|
Context appContext = getContext().getApplicationContext();
|
|
|
|
mPreferences = Preferences.getPreferences(appContext);
|
|
|
|
}
|
|
|
|
|
|
|
|
Account account = mPreferences.getAccount(accountUuid);
|
|
|
|
|
|
|
|
if (account == null) {
|
|
|
|
throw new IllegalArgumentException("Unknown account: " + accountUuid);
|
|
|
|
}
|
|
|
|
|
|
|
|
return account;
|
|
|
|
}
|
|
|
|
|
|
|
|
private LockableDatabase getDatabase(Account account) {
|
|
|
|
LocalStore localStore;
|
|
|
|
try {
|
|
|
|
localStore = account.getLocalStore();
|
|
|
|
} catch (MessagingException e) {
|
|
|
|
throw new RuntimeException("Couldn't get LocalStore", e);
|
|
|
|
}
|
|
|
|
|
|
|
|
return localStore.getDatabase();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* This class is needed to make {@link CursorAdapter} work with our database schema.
|
|
|
|
*
|
|
|
|
* <p>
|
|
|
|
* {@code CursorAdapter} requires a column named {@code "_id"} containing a stable id. We use
|
|
|
|
* the column name {@code "id"} as primary key in all our tables. So this {@link CursorWrapper}
|
|
|
|
* maps all queries for {@code "_id"} to {@code "id"}.
|
|
|
|
* </p><p>
|
|
|
|
* Please note that this only works for the returned {@code Cursor}. When querying the content
|
|
|
|
* provider you still need to use {@link MessageColumns#ID}.
|
|
|
|
* </p>
|
|
|
|
*/
|
|
|
|
static class IdTrickeryCursor extends CursorWrapper {
|
|
|
|
public IdTrickeryCursor(Cursor cursor) {
|
|
|
|
super(cursor);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public int getColumnIndex(String columnName) {
|
|
|
|
if ("_id".equals(columnName)) {
|
|
|
|
return super.getColumnIndex("id");
|
|
|
|
}
|
|
|
|
|
|
|
|
return super.getColumnIndex(columnName);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public int getColumnIndexOrThrow(String columnName) {
|
|
|
|
if ("_id".equals(columnName)) {
|
|
|
|
return super.getColumnIndexOrThrow("id");
|
|
|
|
}
|
|
|
|
|
|
|
|
return super.getColumnIndexOrThrow(columnName);
|
|
|
|
}
|
|
|
|
}
|
2012-10-17 23:15:40 -04:00
|
|
|
|
|
|
|
static class SpecialColumnsCursor extends CursorWrapper {
|
|
|
|
private int[] mColumnMapping;
|
|
|
|
private String[] mSpecialColumnValues;
|
|
|
|
private String[] mColumnNames;
|
|
|
|
|
|
|
|
public SpecialColumnsCursor(Cursor cursor, String[] allColumnNames,
|
|
|
|
Map<String, String> specialColumns) {
|
|
|
|
super(cursor);
|
|
|
|
|
|
|
|
mColumnNames = allColumnNames;
|
|
|
|
mColumnMapping = new int[allColumnNames.length];
|
|
|
|
mSpecialColumnValues = new String[specialColumns.size()];
|
|
|
|
for (int i = 0, columnIndex = 0, specialColumnCount = 0, len = allColumnNames.length;
|
|
|
|
i < len; i++) {
|
|
|
|
|
|
|
|
String columnName = allColumnNames[i];
|
|
|
|
|
|
|
|
if (specialColumns.containsKey(columnName)) {
|
|
|
|
// This is a special column name, so save the value in mSpecialColumnValues
|
|
|
|
mSpecialColumnValues[specialColumnCount] = specialColumns.get(columnName);
|
|
|
|
|
|
|
|
// Write the index into mSpecialColumnValues negated into mColumnMapping
|
|
|
|
mColumnMapping[i] = -(specialColumnCount + 1);
|
|
|
|
specialColumnCount++;
|
|
|
|
} else {
|
|
|
|
mColumnMapping[i] = columnIndex++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public byte[] getBlob(int columnIndex) {
|
|
|
|
int realColumnIndex = mColumnMapping[columnIndex];
|
|
|
|
if (realColumnIndex < 0) {
|
|
|
|
throw new RuntimeException("Special column can only be retrieved as string.");
|
|
|
|
}
|
|
|
|
|
|
|
|
return super.getBlob(realColumnIndex);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public int getColumnCount() {
|
|
|
|
return mColumnMapping.length;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public int getColumnIndex(String columnName) {
|
|
|
|
for (int i = 0, len = mColumnNames.length; i < len; i++) {
|
|
|
|
if (mColumnNames[i].equals(columnName)) {
|
|
|
|
return i;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return super.getColumnIndex(columnName);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public int getColumnIndexOrThrow(String columnName) throws IllegalArgumentException {
|
|
|
|
int index = getColumnIndex(columnName);
|
|
|
|
|
|
|
|
if (index == -1) {
|
|
|
|
throw new IllegalArgumentException("Unknown column name");
|
|
|
|
}
|
|
|
|
|
|
|
|
return index;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public String getColumnName(int columnIndex) {
|
|
|
|
return mColumnNames[columnIndex];
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public String[] getColumnNames() {
|
|
|
|
return mColumnNames.clone();
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public double getDouble(int columnIndex) {
|
|
|
|
int realColumnIndex = mColumnMapping[columnIndex];
|
|
|
|
if (realColumnIndex < 0) {
|
|
|
|
throw new RuntimeException("Special column can only be retrieved as string.");
|
|
|
|
}
|
|
|
|
|
|
|
|
return super.getDouble(realColumnIndex);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public float getFloat(int columnIndex) {
|
|
|
|
int realColumnIndex = mColumnMapping[columnIndex];
|
|
|
|
if (realColumnIndex < 0) {
|
|
|
|
throw new RuntimeException("Special column can only be retrieved as string.");
|
|
|
|
}
|
|
|
|
|
|
|
|
return super.getFloat(realColumnIndex);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public int getInt(int columnIndex) {
|
|
|
|
int realColumnIndex = mColumnMapping[columnIndex];
|
|
|
|
if (realColumnIndex < 0) {
|
|
|
|
throw new RuntimeException("Special column can only be retrieved as string.");
|
|
|
|
}
|
|
|
|
|
|
|
|
return super.getInt(realColumnIndex);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public long getLong(int columnIndex) {
|
|
|
|
int realColumnIndex = mColumnMapping[columnIndex];
|
|
|
|
if (realColumnIndex < 0) {
|
|
|
|
throw new RuntimeException("Special column can only be retrieved as string.");
|
|
|
|
}
|
|
|
|
|
|
|
|
return super.getLong(realColumnIndex);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public short getShort(int columnIndex) {
|
|
|
|
int realColumnIndex = mColumnMapping[columnIndex];
|
|
|
|
if (realColumnIndex < 0) {
|
|
|
|
throw new RuntimeException("Special column can only be retrieved as string.");
|
|
|
|
}
|
|
|
|
|
|
|
|
return super.getShort(realColumnIndex);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public String getString(int columnIndex) {
|
|
|
|
int realColumnIndex = mColumnMapping[columnIndex];
|
|
|
|
if (realColumnIndex < 0) {
|
|
|
|
return mSpecialColumnValues[-realColumnIndex - 1];
|
|
|
|
}
|
|
|
|
|
|
|
|
return super.getString(realColumnIndex);
|
|
|
|
}
|
|
|
|
|
|
|
|
@TargetApi(11)
|
|
|
|
@Override
|
|
|
|
public int getType(int columnIndex) {
|
|
|
|
int realColumnIndex = mColumnMapping[columnIndex];
|
|
|
|
if (realColumnIndex < 0) {
|
|
|
|
return FIELD_TYPE_STRING;
|
|
|
|
}
|
|
|
|
|
|
|
|
return super.getType(realColumnIndex);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public boolean isNull(int columnIndex) {
|
|
|
|
int realColumnIndex = mColumnMapping[columnIndex];
|
|
|
|
if (realColumnIndex < 0) {
|
|
|
|
return (mSpecialColumnValues[-realColumnIndex - 1] == null);
|
|
|
|
}
|
|
|
|
|
|
|
|
return super.isNull(realColumnIndex);
|
|
|
|
}
|
|
|
|
}
|
2012-10-16 09:46:40 -04:00
|
|
|
}
|