Finally get rid of EmailProvider

This commit is contained in:
cketti 2022-08-31 16:52:02 +02:00
parent 6be1eb11dc
commit 7ea928bba5
11 changed files with 0 additions and 1666 deletions

View file

@ -1,144 +0,0 @@
package com.fsck.k9.cache;
import java.util.ArrayList;
import java.util.List;
import com.fsck.k9.provider.EmailProvider.MessageColumns;
import com.fsck.k9.provider.EmailProvider.ThreadColumns;
import android.content.Context;
import android.database.Cursor;
import android.database.CursorWrapper;
/**
* A {@link CursorWrapper} that utilizes {@link EmailProviderCache}.
*/
public class EmailProviderCacheCursor extends CursorWrapper {
private EmailProviderCache mCache;
private List<Integer> mHiddenRows = new ArrayList<>();
private int mMessageIdColumn;
private int mFolderIdColumn;
private int mThreadRootColumn;
/**
* The cursor's current position.
*
* Note: This is only used when {@link #mHiddenRows} isn't empty.
*/
private int mPosition;
public EmailProviderCacheCursor(String accountUuid, Cursor cursor) {
super(cursor);
mCache = EmailProviderCache.getCache(accountUuid);
mMessageIdColumn = cursor.getColumnIndex(MessageColumns.ID);
mFolderIdColumn = cursor.getColumnIndex(MessageColumns.FOLDER_ID);
mThreadRootColumn = cursor.getColumnIndex(ThreadColumns.ROOT);
if (mMessageIdColumn == -1 || mFolderIdColumn == -1 || mThreadRootColumn == -1) {
throw new IllegalArgumentException("The supplied cursor needs to contain the " +
"following columns: " + MessageColumns.ID + ", " + MessageColumns.FOLDER_ID +
", " + ThreadColumns.ROOT);
}
while (cursor.moveToNext()) {
long messageId = cursor.getLong(mMessageIdColumn);
long folderId = cursor.getLong(mFolderIdColumn);
if (mCache.isMessageHidden(messageId, folderId)) {
mHiddenRows.add(cursor.getPosition());
}
}
// Reset the cursor position
cursor.moveToFirst();
cursor.moveToPrevious();
}
@Override
public int getInt(int columnIndex) {
long messageId = getLong(mMessageIdColumn);
long threadRootId = getLong(mThreadRootColumn);
String columnName = getColumnName(columnIndex);
String value = mCache.getValueForMessage(messageId, columnName);
if (value != null) {
return Integer.parseInt(value);
}
value = mCache.getValueForThread(threadRootId, columnName);
if (value != null) {
return Integer.parseInt(value);
}
return super.getInt(columnIndex);
}
@Override
public int getCount() {
return super.getCount() - mHiddenRows.size();
}
@Override
public boolean moveToFirst() {
return moveToPosition(0);
}
@Override
public boolean moveToLast() {
return moveToPosition(getCount());
}
@Override
public boolean moveToNext() {
return moveToPosition(getPosition() + 1);
}
@Override
public boolean moveToPrevious() {
return moveToPosition(getPosition() - 1);
}
@Override
public boolean move(int offset) {
return moveToPosition(getPosition() + offset);
}
@Override
public boolean moveToPosition(int position) {
if (mHiddenRows.isEmpty()) {
return super.moveToPosition(position);
}
mPosition = position;
int newPosition = position;
for (int hiddenRow : mHiddenRows) {
if (hiddenRow > newPosition) {
break;
}
newPosition++;
}
return super.moveToPosition(newPosition);
}
@Override
public int getPosition() {
if (mHiddenRows.isEmpty()) {
return super.getPosition();
}
return mPosition;
}
@Override
public boolean isLast() {
if (mHiddenRows.isEmpty()) {
return super.isLast();
}
return (mPosition == getCount() - 1);
}
}

View file

@ -1,462 +0,0 @@
/*
* Copyright (C) 2012 The K-9 Dog Walkers
* Copyright (C) 2006 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 com.fsck.k9.helper;
import java.util.Comparator;
import android.annotation.TargetApi;
import android.content.ContentResolver;
import android.database.CharArrayBuffer;
import android.database.ContentObserver;
import android.database.Cursor;
import android.database.DataSetObserver;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
/**
* This class can be used to combine multiple {@link Cursor}s into one.
*/
public class MergeCursor implements Cursor {
/**
* List of the cursors combined in this object.
*/
protected final Cursor[] mCursors;
/**
* The currently active cursor.
*/
protected Cursor mActiveCursor;
/**
* The index of the currently active cursor in {@link #mCursors}.
*
* @see #mActiveCursor
*/
protected int mActiveCursorIndex;
/**
* The cursor's current position.
*/
protected int mPosition;
/**
* Used to cache the value of {@link #getCount()}.
*/
private int mCount = -1;
/**
* The comparator that is used to decide how the individual cursors are merged.
*/
private final Comparator<Cursor> mComparator;
/**
* Constructor
*
* @param cursors
* The list of cursors this {@code MultiCursor} should combine.
* @param comparator
* A comparator that is used to decide in what order the individual cursors are merged.
*/
public MergeCursor(Cursor[] cursors, Comparator<Cursor> comparator) {
mCursors = cursors.clone();
mComparator = comparator;
resetCursors();
}
private void resetCursors() {
mActiveCursorIndex = -1;
mActiveCursor = null;
mPosition = -1;
for (int i = 0, len = mCursors.length; i < len; i++) {
Cursor cursor = mCursors[i];
if (cursor != null) {
cursor.moveToPosition(-1);
if (mActiveCursor == null) {
mActiveCursorIndex = i;
mActiveCursor = mCursors[mActiveCursorIndex];
}
}
}
}
@Override
public void close() {
for (Cursor cursor : mCursors) {
if (cursor != null) {
cursor.close();
}
}
}
@Override
public void copyStringToBuffer(int columnIndex, CharArrayBuffer buffer) {
mActiveCursor.copyStringToBuffer(columnIndex, buffer);
}
@Override
public void deactivate() {
for (Cursor cursor : mCursors) {
if (cursor != null) {
cursor.deactivate();
}
}
}
@Override
public byte[] getBlob(int columnIndex) {
return mActiveCursor.getBlob(columnIndex);
}
@Override
public int getColumnCount() {
return mActiveCursor.getColumnCount();
}
@Override
public int getColumnIndex(String columnName) {
return mActiveCursor.getColumnIndex(columnName);
}
@Override
public int getColumnIndexOrThrow(String columnName) throws IllegalArgumentException {
return mActiveCursor.getColumnIndexOrThrow(columnName);
}
@Override
public String getColumnName(int columnIndex) {
return mActiveCursor.getColumnName(columnIndex);
}
@Override
public String[] getColumnNames() {
return mActiveCursor.getColumnNames();
}
@Override
public int getCount() {
// CursorLoaders seem to call getCount() a lot. So we're caching the aggregated count.
if (mCount == -1) {
int count = 0;
for (Cursor cursor : mCursors) {
if (cursor != null) {
count += cursor.getCount();
}
}
mCount = count;
}
return mCount;
}
@Override
public double getDouble(int columnIndex) {
return mActiveCursor.getDouble(columnIndex);
}
@Override
public float getFloat(int columnIndex) {
return mActiveCursor.getFloat(columnIndex);
}
@Override
public int getInt(int columnIndex) {
return mActiveCursor.getInt(columnIndex);
}
@Override
public long getLong(int columnIndex) {
return mActiveCursor.getLong(columnIndex);
}
@Override
public int getPosition() {
return mPosition;
}
@Override
public short getShort(int columnIndex) {
return mActiveCursor.getShort(columnIndex);
}
@Override
public String getString(int columnIndex) {
return mActiveCursor.getString(columnIndex);
}
@Override
public int getType(int columnIndex) {
return mActiveCursor.getType(columnIndex);
}
@Override
public boolean getWantsAllOnMoveCalls() {
return mActiveCursor.getWantsAllOnMoveCalls();
}
@TargetApi(Build.VERSION_CODES.M)
@Override
public void setExtras(Bundle extras) {
mActiveCursor.setExtras(extras);
}
@Override
public boolean isAfterLast() {
int count = getCount();
return count == 0 || mPosition == count;
}
@Override
public boolean isBeforeFirst() {
return getCount() == 0 || mPosition == -1;
}
@Override
public boolean isClosed() {
return mActiveCursor.isClosed();
}
@Override
public boolean isFirst() {
return getCount() != 0 && mPosition == 0;
}
@Override
public boolean isLast() {
int count = getCount();
return count != 0 && mPosition == count - 1;
}
@Override
public boolean isNull(int columnIndex) {
return mActiveCursor.isNull(columnIndex);
}
@Override
public boolean move(int offset) {
return moveToPosition(mPosition + offset);
}
@Override
public boolean moveToFirst() {
return moveToPosition(0);
}
@Override
public boolean moveToLast() {
return moveToPosition(getCount() - 1);
}
@Override
public boolean moveToNext() {
int count = getCount();
if (mPosition == count) {
return false;
}
if (mPosition == count - 1) {
mActiveCursor.moveToNext();
mPosition++;
return false;
}
int smallest = -1;
for (int i = 0, len = mCursors.length; i < len; i++) {
if (mCursors[i] == null || mCursors[i].getCount() == 0 || mCursors[i].isLast()) {
continue;
}
if (smallest == -1) {
smallest = i;
mCursors[smallest].moveToNext();
continue;
}
Cursor left = mCursors[smallest];
Cursor right = mCursors[i];
right.moveToNext();
int result = mComparator.compare(left, right);
if (result > 0) {
smallest = i;
left.moveToPrevious();
} else {
right.moveToPrevious();
}
}
mPosition++;
if (smallest != -1) {
mActiveCursorIndex = smallest;
mActiveCursor = mCursors[mActiveCursorIndex];
}
return true;
}
@Override
public boolean moveToPosition(int position) {
// Make sure position isn't past the end of the cursor
final int count = getCount();
if (position >= count) {
mPosition = count;
return false;
}
// Make sure position isn't before the beginning of the cursor
if (position < 0) {
mPosition = -1;
return false;
}
// Check for no-op moves, and skip the rest of the work for them
if (position == mPosition) {
return true;
}
if (position > mPosition) {
for (int i = 0, end = position - mPosition; i < end; i++) {
if (!moveToNext()) {
return false;
}
}
} else {
for (int i = 0, end = mPosition - position; i < end; i++) {
if (!moveToPrevious()) {
return false;
}
}
}
return true;
}
@Override
public boolean moveToPrevious() {
if (mPosition < 0) {
return false;
}
mActiveCursor.moveToPrevious();
if (mPosition == 0) {
mPosition = -1;
return false;
}
int greatest = -1;
for (int i = 0, len = mCursors.length; i < len; i++) {
if (mCursors[i] == null || mCursors[i].isBeforeFirst()) {
continue;
}
if (greatest == -1) {
greatest = i;
continue;
}
Cursor left = mCursors[greatest];
Cursor right = mCursors[i];
int result = mComparator.compare(left, right);
if (result <= 0) {
greatest = i;
}
}
mPosition--;
if (greatest != -1) {
mActiveCursorIndex = greatest;
mActiveCursor = mCursors[mActiveCursorIndex];
}
return true;
}
@Override
public void registerContentObserver(ContentObserver observer) {
for (Cursor cursor : mCursors) {
cursor.registerContentObserver(observer);
}
}
@Override
public void registerDataSetObserver(DataSetObserver observer) {
for (Cursor cursor : mCursors) {
cursor.registerDataSetObserver(observer);
}
}
@Deprecated
@Override
public boolean requery() {
boolean success = true;
for (Cursor cursor : mCursors) {
success &= cursor.requery();
}
return success;
}
@Override
public void setNotificationUri(ContentResolver cr, Uri uri) {
for (Cursor cursor : mCursors) {
cursor.setNotificationUri(cr, uri);
}
}
@Override
public void unregisterContentObserver(ContentObserver observer) {
for (Cursor cursor : mCursors) {
cursor.unregisterContentObserver(observer);
}
}
@Override
public void unregisterDataSetObserver(DataSetObserver observer) {
for (Cursor cursor : mCursors) {
cursor.unregisterDataSetObserver(observer);
}
}
@Override
public Bundle getExtras() {
throw new RuntimeException("Not implemented");
}
@Override
public Bundle respond(Bundle extras) {
throw new RuntimeException("Not implemented");
}
@Override
public Uri getNotificationUri() {
return null;
}
}

View file

@ -1,83 +0,0 @@
package com.fsck.k9.helper;
import java.util.Comparator;
import android.database.Cursor;
public class MergeCursorWithUniqueId extends MergeCursor {
private static final int SHIFT = 48;
private static final long MAX_ID = (1L << SHIFT) - 1;
private static final long MAX_CURSORS = 1L << (63 - SHIFT);
private int mColumnCount = -1;
private int mIdColumnIndex = -1;
public MergeCursorWithUniqueId(Cursor[] cursors, Comparator<Cursor> comparator) {
super(cursors, comparator);
if (cursors.length > MAX_CURSORS) {
throw new IllegalArgumentException("This class only supports up to " +
MAX_CURSORS + " cursors");
}
}
@Override
public int getColumnCount() {
if (mColumnCount == -1) {
mColumnCount = super.getColumnCount();
}
return mColumnCount + 1;
}
@Override
public int getColumnIndex(String columnName) {
if ("_id".equals(columnName)) {
return getUniqueIdColumnIndex();
}
return super.getColumnIndexOrThrow(columnName);
}
@Override
public int getColumnIndexOrThrow(String columnName) throws IllegalArgumentException {
if ("_id".equals(columnName)) {
return getUniqueIdColumnIndex();
}
return super.getColumnIndexOrThrow(columnName);
}
@Override
public long getLong(int columnIndex) {
if (columnIndex == getUniqueIdColumnIndex()) {
long id = getPerCursorId();
if (id > MAX_ID) {
throw new RuntimeException("Sorry, " + this.getClass().getName() +
" can only handle '_id' values up to " + SHIFT + " bits.");
}
return (((long) mActiveCursorIndex) << SHIFT) + id;
}
return super.getLong(columnIndex);
}
protected int getUniqueIdColumnIndex() {
if (mColumnCount == -1) {
mColumnCount = super.getColumnCount();
}
return mColumnCount;
}
protected long getPerCursorId() {
if (mIdColumnIndex == -1) {
mIdColumnIndex = super.getColumnIndexOrThrow("_id");
}
return super.getLong(mIdColumnIndex);
}
}

View file

@ -18,7 +18,6 @@ import java.util.Locale;
import java.util.Map;
import java.util.Stack;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
@ -50,7 +49,6 @@ import com.fsck.k9.mailstore.LockableDatabase.DbCallback;
import com.fsck.k9.mailstore.LockableDatabase.SchemaDefinition;
import com.fsck.k9.mailstore.StorageManager.InternalStorageProvider;
import com.fsck.k9.message.extractors.AttachmentInfoExtractor;
import com.fsck.k9.provider.EmailProvider.MessageColumns;
import com.fsck.k9.search.LocalSearch;
import com.fsck.k9.search.SearchSpecification.Attribute;
import com.fsck.k9.search.SearchSpecification.SearchField;

View file

@ -1,719 +0,0 @@
package com.fsck.k9.provider;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.CursorWrapper;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import android.text.TextUtils;
import com.fsck.k9.Account;
import com.fsck.k9.DI;
import com.fsck.k9.Preferences;
import com.fsck.k9.cache.EmailProviderCacheCursor;
import com.fsck.k9.helper.Utility;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mailstore.LocalStore;
import com.fsck.k9.mailstore.LocalStoreProvider;
import com.fsck.k9.mailstore.LockableDatabase;
import com.fsck.k9.mailstore.LockableDatabase.DbCallback;
import com.fsck.k9.search.SqlQueryBuilder;
/**
* 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 {
public static String AUTHORITY;
public static Uri CONTENT_URI;
private static Uri getNotificationUri(String accountUuid) {
return Uri.withAppendedPath(CONTENT_URI, "account/" + accountUuid + "/messages");
}
private UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
/*
* Constants that are used for the URI matching.
*/
private static final int MESSAGE_BASE = 0;
private static final int MESSAGES = MESSAGE_BASE;
private static final int MESSAGES_THREADED = MESSAGE_BASE + 1;
private static final int MESSAGES_THREAD = MESSAGE_BASE + 2;
private static final String MESSAGES_TABLE = "messages";
private static final Map<String, String> THREAD_AGGREGATION_FUNCS = new HashMap<>();
static {
THREAD_AGGREGATION_FUNCS.put(MessageColumns.DATE, "MAX");
THREAD_AGGREGATION_FUNCS.put(MessageColumns.INTERNAL_DATE, "MAX");
THREAD_AGGREGATION_FUNCS.put(MessageColumns.ATTACHMENT_COUNT, "SUM");
THREAD_AGGREGATION_FUNCS.put(MessageColumns.READ, "MIN");
THREAD_AGGREGATION_FUNCS.put(MessageColumns.FLAGGED, "MAX");
THREAD_AGGREGATION_FUNCS.put(MessageColumns.ANSWERED, "MIN");
THREAD_AGGREGATION_FUNCS.put(MessageColumns.FORWARDED, "MIN");
}
private static final String[] FIXUP_MESSAGES_COLUMNS = {
MessageColumns.ID
};
private static final String[] FIXUP_AGGREGATED_MESSAGES_COLUMNS = {
MessageColumns.DATE,
MessageColumns.INTERNAL_DATE,
MessageColumns.ATTACHMENT_COUNT,
MessageColumns.READ,
MessageColumns.FLAGGED,
MessageColumns.ANSWERED,
MessageColumns.FORWARDED
};
private static final String FOLDERS_TABLE = "folders";
private static final String[] FOLDERS_COLUMNS = {
FolderColumns.ID,
FolderColumns.NAME,
FolderColumns.LAST_UPDATED,
FolderColumns.UNREAD_COUNT,
FolderColumns.VISIBLE_LIMIT,
FolderColumns.STATUS,
FolderColumns.PUSH_STATE,
FolderColumns.LAST_PUSHED,
FolderColumns.FLAGGED_COUNT,
FolderColumns.INTEGRATE,
FolderColumns.TOP_GROUP,
FolderColumns.POLL_CLASS,
FolderColumns.PUSH_CLASS,
FolderColumns.DISPLAY_CLASS,
FolderColumns.SERVER_ID
};
private static final String THREADS_TABLE = "threads";
public interface SpecialColumns {
String ACCOUNT_UUID = "account_uuid";
String THREAD_COUNT = "thread_count";
String FOLDER_SERVER_ID = "server_id";
String INTEGRATE = "integrate";
}
public interface MessageColumns {
String ID = "id";
String UID = "uid";
String INTERNAL_DATE = "internal_date";
String SUBJECT = "subject";
String DATE = "date";
String MESSAGE_ID = "message_id";
String SENDER_LIST = "sender_list";
String TO_LIST = "to_list";
String CC_LIST = "cc_list";
String BCC_LIST = "bcc_list";
String REPLY_TO_LIST = "reply_to_list";
String FLAGS = "flags";
String ATTACHMENT_COUNT = "attachment_count";
String FOLDER_ID = "folder_id";
String PREVIEW_TYPE = "preview_type";
String PREVIEW = "preview";
String READ = "read";
String FLAGGED = "flagged";
String ANSWERED = "answered";
String FORWARDED = "forwarded";
}
private interface InternalMessageColumns extends MessageColumns {
String DELETED = "deleted";
String EMPTY = "empty";
String MIME_TYPE = "mime_type";
}
public interface FolderColumns {
String ID = "id";
String NAME = "name";
String LAST_UPDATED = "last_updated";
String UNREAD_COUNT = "unread_count";
String VISIBLE_LIMIT = "visible_limit";
String STATUS = "status";
String PUSH_STATE = "push_state";
String LAST_PUSHED = "last_pushed";
String FLAGGED_COUNT = "flagged_count";
String INTEGRATE = "integrate";
String TOP_GROUP = "top_group";
String POLL_CLASS = "poll_class";
String PUSH_CLASS = "push_class";
String DISPLAY_CLASS = "display_class";
String SERVER_ID = "server_id";
}
public interface ThreadColumns {
String ID = "id";
String MESSAGE_ID = "message_id";
String ROOT = "root";
String PARENT = "parent";
}
public interface StatsColumns {
String UNREAD_COUNT = "unread_count";
String FLAGGED_COUNT = "flagged_count";
}
private Preferences mPreferences;
@Override
public boolean onCreate() {
String packageName = getContext().getPackageName();
AUTHORITY = packageName + ".provider.email";
CONTENT_URI = Uri.parse("content://" + AUTHORITY);
uriMatcher.addURI(AUTHORITY, "account/*/messages", MESSAGES);
uriMatcher.addURI(AUTHORITY, "account/*/messages/threaded", MESSAGES_THREADED);
uriMatcher.addURI(AUTHORITY, "account/*/thread/#", MESSAGES_THREAD);
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 = uriMatcher.match(uri);
if (match < 0) {
throw new IllegalArgumentException("Unknown URI: " + uri);
}
ContentResolver contentResolver = getContext().getContentResolver();
Cursor cursor = null;
switch (match) {
case MESSAGES:
case MESSAGES_THREADED:
case MESSAGES_THREAD: {
List<String> segments = uri.getPathSegments();
String accountUuid = segments.get(1);
List<String> dbColumnNames = new ArrayList<>(projection.length);
Map<String, String> specialColumns = new HashMap<>();
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]);
if (match == MESSAGES) {
cursor = getMessages(accountUuid, dbProjection, selection, selectionArgs, sortOrder);
} else if (match == MESSAGES_THREADED) {
cursor = getThreadedMessages(accountUuid, dbProjection, selection, selectionArgs, sortOrder);
} else if (match == MESSAGES_THREAD) {
String threadId = segments.get(3);
cursor = getThread(accountUuid, dbProjection, threadId, sortOrder);
} else {
throw new RuntimeException("Not implemented");
}
cursor.setNotificationUri(contentResolver, getNotificationUri(accountUuid));
cursor = new SpecialColumnsCursor(new IdTrickeryCursor(cursor), projection, specialColumns);
cursor = new EmailProviderCacheCursor(accountUuid, cursor);
break;
}
}
return cursor;
}
@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) {
String where;
if (TextUtils.isEmpty(selection)) {
where = InternalMessageColumns.DELETED + " = 0 AND " + InternalMessageColumns.EMPTY + " = 0";
} else {
where = "(" + selection + ") AND " +
InternalMessageColumns.DELETED + " = 0 AND " + InternalMessageColumns.EMPTY + " = 0";
}
final Cursor cursor;
if (Utility.arrayContainsAny(projection, (Object[]) FOLDERS_COLUMNS)) {
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 " +
"JOIN threads t ON (t.message_id = m.id) " +
"LEFT JOIN folders f ON (m.folder_id = f.id) " +
"WHERE ");
query.append(SqlQueryBuilder.addPrefixToSelection(FIXUP_MESSAGES_COLUMNS, "m.", where));
query.append(" ORDER BY ");
query.append(SqlQueryBuilder.addPrefixToSelection(FIXUP_MESSAGES_COLUMNS, "m.", sortOrder));
cursor = db.rawQuery(query.toString(), selectionArgs);
} else {
cursor = db.query(MESSAGES_TABLE, projection, where, selectionArgs, null, null, sortOrder);
}
return cursor;
}
});
} catch (MessagingException e) {
throw new RuntimeException("messaging exception", e);
}
}
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) {
StringBuilder query = new StringBuilder();
query.append("SELECT ");
boolean first = true;
for (String columnName : projection) {
if (!first) {
query.append(",");
} else {
first = false;
}
final String aggregationFunc = THREAD_AGGREGATION_FUNCS.get(columnName);
if (MessageColumns.ID.equals(columnName)) {
query.append("m." + MessageColumns.ID + " AS " + MessageColumns.ID);
} else if (aggregationFunc != null) {
query.append("a.");
query.append(columnName);
query.append(" AS ");
query.append(columnName);
} else {
query.append(columnName);
}
}
query.append(" FROM (");
createThreadedSubQuery(projection, selection, query);
query.append(") a ");
query.append("JOIN " + THREADS_TABLE + " t " +
"ON (t." + ThreadColumns.ROOT + " = a.thread_root) " +
"JOIN " + MESSAGES_TABLE + " m " +
"ON (m." + MessageColumns.ID + " = t." + ThreadColumns.MESSAGE_ID + " AND " +
"m." + InternalMessageColumns.EMPTY + "=0 AND " +
"m." + InternalMessageColumns.DELETED + "=0 AND " +
"m." + MessageColumns.DATE + " = a." + MessageColumns.DATE +
") ");
if (Utility.arrayContainsAny(projection, (Object[]) FOLDERS_COLUMNS)) {
query.append("JOIN " + FOLDERS_TABLE + " f " +
"ON (m." + MessageColumns.FOLDER_ID + " = f." + FolderColumns.ID + ") ");
}
query.append(" GROUP BY " + ThreadColumns.ROOT);
if (!TextUtils.isEmpty(sortOrder)) {
query.append(" ORDER BY ");
query.append(SqlQueryBuilder.addPrefixToSelection(
FIXUP_AGGREGATED_MESSAGES_COLUMNS, "a.", sortOrder));
}
return db.rawQuery(query.toString(), selectionArgs);
}
});
} catch (MessagingException e) {
throw new RuntimeException("messaging exception", e);
}
}
private void createThreadedSubQuery(String[] projection, String selection, StringBuilder query) {
query.append("SELECT t." + ThreadColumns.ROOT + " AS thread_root");
for (String columnName : projection) {
String aggregationFunc = THREAD_AGGREGATION_FUNCS.get(columnName);
if (SpecialColumns.THREAD_COUNT.equals(columnName)) {
query.append(",COUNT(t." + ThreadColumns.ROOT + ") AS " + SpecialColumns.THREAD_COUNT);
} else if (aggregationFunc != null) {
query.append(",");
query.append(aggregationFunc);
query.append("(");
query.append(columnName);
query.append(") AS ");
query.append(columnName);
} else {
// Skip
}
}
query.append(
" FROM " + MESSAGES_TABLE + " m " +
"JOIN " + THREADS_TABLE + " t " +
"ON (t." + ThreadColumns.MESSAGE_ID + " = m." + MessageColumns.ID + ")");
if (Utility.arrayContainsAny(projection, (Object[]) FOLDERS_COLUMNS)) {
query.append(" JOIN " + FOLDERS_TABLE + " f " +
"ON (m." + MessageColumns.FOLDER_ID + " = f." + FolderColumns.ID + ")");
}
query.append(" WHERE (t." + ThreadColumns.ROOT + " IN (" +
"SELECT " + ThreadColumns.ROOT + " " +
"FROM " + MESSAGES_TABLE + " m " +
"JOIN " + THREADS_TABLE + " t " +
"ON (t." + ThreadColumns.MESSAGE_ID + " = m." + MessageColumns.ID + ") " +
"WHERE " +
"m." + InternalMessageColumns.EMPTY + " = 0 AND " +
"m." + InternalMessageColumns.DELETED + " = 0)");
if (!TextUtils.isEmpty(selection)) {
query.append(" AND (");
query.append(selection);
query.append(")");
}
query.append(
") AND " +
InternalMessageColumns.DELETED + " = 0 AND " + InternalMessageColumns.EMPTY + " = 0");
query.append(" GROUP BY t." + ThreadColumns.ROOT);
}
protected Cursor getThread(String accountUuid, final String[] projection, final String threadId,
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) {
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." + MessageColumns.ID + " AS " + MessageColumns.ID);
} else {
query.append(columnName);
}
}
query.append(" FROM " + THREADS_TABLE + " t JOIN " + MESSAGES_TABLE + " m " +
"ON (m." + MessageColumns.ID + " = t." + ThreadColumns.MESSAGE_ID + ") ");
if (Utility.arrayContainsAny(projection, (Object[]) FOLDERS_COLUMNS)) {
query.append("LEFT JOIN " + FOLDERS_TABLE + " f " +
"ON (m." + MessageColumns.FOLDER_ID + " = f." + FolderColumns.ID + ") ");
}
query.append("WHERE " +
ThreadColumns.ROOT + " = ? AND " +
InternalMessageColumns.DELETED + " = 0 AND " + InternalMessageColumns.EMPTY + " = 0");
query.append(" ORDER BY ");
query.append(SqlQueryBuilder.addPrefixToSelection(FIXUP_MESSAGES_COLUMNS, "m.", sortOrder));
return db.rawQuery(query.toString(), new String[] { threadId });
}
});
} catch (MessagingException e) {
throw new RuntimeException("messaging exception", e);
}
}
private Account getAccount(String accountUuid) {
if (mPreferences == null) {
mPreferences = Preferences.getPreferences();
}
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 = DI.get(LocalStoreProvider.class).getInstance(account);
} catch (MessagingException e) {
throw new RuntimeException("Couldn't get LocalStore", e);
}
return localStore.getDatabase();
}
/**
* This class is needed to make {@link androidx.cursoradapter.widget.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);
}
}
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);
}
@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);
}
}
}

View file

@ -1,7 +1,6 @@
package com.fsck.k9.mailstore
import android.database.sqlite.SQLiteDatabase
import android.net.Uri
import androidx.core.content.contentValuesOf
import com.fsck.k9.Account
import com.fsck.k9.K9RobolectricTest
@ -17,12 +16,10 @@ import com.fsck.k9.mail.MessageDownloadState
import com.fsck.k9.mail.internet.MimeMessage
import com.fsck.k9.mail.internet.MimeMessageHelper
import com.fsck.k9.mail.internet.TextBody
import com.fsck.k9.provider.EmailProvider
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Assert.fail
import org.junit.Before
import org.junit.Test
import org.koin.core.component.inject
@ -36,12 +33,6 @@ class K9BackendFolderTest : K9RobolectricTest() {
val backendFolder = createBackendFolder()
val database: LockableDatabase = localStoreProvider.getInstance(account).database
@Before
fun setUp() {
// Set EmailProvider.CONTENT_URI so LocalStore.notifyChange() won't crash
EmailProvider.CONTENT_URI = Uri.parse("content://dummy")
}
@After
fun tearDown() {
preferences.deleteAccount(account)

View file

@ -1,15 +1,12 @@
package com.fsck.k9.mailstore
import android.net.Uri
import com.fsck.k9.Account
import com.fsck.k9.K9RobolectricTest
import com.fsck.k9.Preferences
import com.fsck.k9.backend.api.BackendStorage
import com.fsck.k9.mail.FolderClass
import com.fsck.k9.provider.EmailProvider
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
import org.koin.core.component.inject
import org.mockito.kotlin.any
@ -26,12 +23,6 @@ class K9BackendStorageTest : K9RobolectricTest() {
val database: LockableDatabase = localStoreProvider.getInstance(account).database
val backendStorage = createBackendStorage()
@Before
fun setUp() {
// Set EmailProvider.CONTENT_URI so LocalStore.notifyChange() won't crash
EmailProvider.CONTENT_URI = Uri.parse("content://dummy")
}
@After
fun tearDown() {
preferences.deleteAccount(account)

View file

@ -401,11 +401,6 @@
android:authorities="${applicationId}.messageprovider"
android:exported="false" />
<provider
android:name=".provider.EmailProvider"
android:authorities="${applicationId}.provider.email"
android:exported="false"/>
<provider
android:name=".provider.DecryptedFileProvider"
android:authorities="${applicationId}.decryptedfileprovider"

View file

@ -1,60 +0,0 @@
package com.fsck.k9.fragment;
import java.util.Arrays;
import com.fsck.k9.provider.EmailProvider.MessageColumns;
import com.fsck.k9.provider.EmailProvider.SpecialColumns;
import com.fsck.k9.provider.EmailProvider.ThreadColumns;
public final class MLFProjectionInfo {
public static final String[] THREADED_PROJECTION = {
MessageColumns.ID,
MessageColumns.UID,
MessageColumns.INTERNAL_DATE,
MessageColumns.SUBJECT,
MessageColumns.DATE,
MessageColumns.SENDER_LIST,
MessageColumns.TO_LIST,
MessageColumns.CC_LIST,
MessageColumns.READ,
MessageColumns.FLAGGED,
MessageColumns.ANSWERED,
MessageColumns.FORWARDED,
MessageColumns.ATTACHMENT_COUNT,
MessageColumns.FOLDER_ID,
MessageColumns.PREVIEW_TYPE,
MessageColumns.PREVIEW,
ThreadColumns.ROOT,
SpecialColumns.ACCOUNT_UUID,
SpecialColumns.FOLDER_SERVER_ID,
SpecialColumns.THREAD_COUNT,
};
public static final int ID_COLUMN = 0;
public static final int UID_COLUMN = 1;
public static final int INTERNAL_DATE_COLUMN = 2;
public static final int SUBJECT_COLUMN = 3;
public static final int DATE_COLUMN = 4;
public static final int SENDER_LIST_COLUMN = 5;
public static final int TO_LIST_COLUMN = 6;
public static final int CC_LIST_COLUMN = 7;
public static final int READ_COLUMN = 8;
public static final int FLAGGED_COLUMN = 9;
public static final int ANSWERED_COLUMN = 10;
public static final int FORWARDED_COLUMN = 11;
public static final int ATTACHMENT_COUNT_COLUMN = 12;
public static final int FOLDER_ID_COLUMN = 13;
public static final int PREVIEW_TYPE_COLUMN = 14;
public static final int PREVIEW_COLUMN = 15;
public static final int THREAD_ROOT_COLUMN = 16;
public static final int ACCOUNT_UUID_COLUMN = 17;
public static final int FOLDER_SERVER_ID_COLUMN = 18;
public static final int THREAD_COUNT_COLUMN = 19;
public static final String[] PROJECTION = Arrays.copyOf(THREADED_PROJECTION,
THREAD_COUNT_COLUMN);
}

View file

@ -1,163 +0,0 @@
package com.fsck.k9.fragment;
import android.database.Cursor;
import java.util.Comparator;
import java.util.List;
/**
* A set of {@link Comparator} classes used for {@link Cursor} data comparison.
*/
public class MessageListFragmentComparators {
/**
* Reverses the result of a {@link Comparator}.
*
* @param <T>
*/
public static class ReverseComparator<T> implements Comparator<T> {
private Comparator<T> mDelegate;
/**
* @param delegate
* Never {@code null}.
*/
public ReverseComparator(final Comparator<T> delegate) {
mDelegate = delegate;
}
@Override
public int compare(final T object1, final T object2) {
// arg1 & 2 are mixed up, this is done on purpose
return mDelegate.compare(object2, object1);
}
}
/**
* Chains comparator to find a non-0 result.
*
* @param <T>
*/
public static class ComparatorChain<T> implements Comparator<T> {
private List<Comparator<T>> mChain;
/**
* @param chain
* Comparator chain. Never {@code null}.
*/
public ComparatorChain(final List<Comparator<T>> chain) {
mChain = chain;
}
@Override
public int compare(T object1, T object2) {
int result = 0;
for (final Comparator<T> comparator : mChain) {
result = comparator.compare(object1, object2);
if (result != 0) {
break;
}
}
return result;
}
}
public static class ReverseIdComparator implements Comparator<Cursor> {
private int mIdColumn = -1;
@Override
public int compare(Cursor cursor1, Cursor cursor2) {
if (mIdColumn == -1) {
mIdColumn = cursor1.getColumnIndex("_id");
}
long o1Id = cursor1.getLong(mIdColumn);
long o2Id = cursor2.getLong(mIdColumn);
return (o1Id > o2Id) ? -1 : 1;
}
}
public static class AttachmentComparator implements Comparator<Cursor> {
@Override
public int compare(Cursor cursor1, Cursor cursor2) {
int o1HasAttachment = (cursor1.getInt(MLFProjectionInfo.ATTACHMENT_COUNT_COLUMN) > 0) ? 0 : 1;
int o2HasAttachment = (cursor2.getInt(MLFProjectionInfo.ATTACHMENT_COUNT_COLUMN) > 0) ? 0 : 1;
return o1HasAttachment - o2HasAttachment;
}
}
public static class FlaggedComparator implements Comparator<Cursor> {
@Override
public int compare(Cursor cursor1, Cursor cursor2) {
int o1IsFlagged = (cursor1.getInt(MLFProjectionInfo.FLAGGED_COLUMN) == 1) ? 0 : 1;
int o2IsFlagged = (cursor2.getInt(MLFProjectionInfo.FLAGGED_COLUMN) == 1) ? 0 : 1;
return o1IsFlagged - o2IsFlagged;
}
}
public static class UnreadComparator implements Comparator<Cursor> {
@Override
public int compare(Cursor cursor1, Cursor cursor2) {
int o1IsUnread = cursor1.getInt(MLFProjectionInfo.READ_COLUMN);
int o2IsUnread = cursor2.getInt(MLFProjectionInfo.READ_COLUMN);
return o1IsUnread - o2IsUnread;
}
}
public static class DateComparator implements Comparator<Cursor> {
@Override
public int compare(Cursor cursor1, Cursor cursor2) {
long o1Date = cursor1.getLong(MLFProjectionInfo.DATE_COLUMN);
long o2Date = cursor2.getLong(MLFProjectionInfo.DATE_COLUMN);
return Long.compare(o1Date, o2Date);
}
}
public static class ArrivalComparator implements Comparator<Cursor> {
@Override
public int compare(Cursor cursor1, Cursor cursor2) {
long o1Date = cursor1.getLong(MLFProjectionInfo.INTERNAL_DATE_COLUMN);
long o2Date = cursor2.getLong(MLFProjectionInfo.INTERNAL_DATE_COLUMN);
return Long.compare(o1Date, o2Date);
}
}
public static class SubjectComparator implements Comparator<Cursor> {
@Override
public int compare(Cursor cursor1, Cursor cursor2) {
String subject1 = cursor1.getString(MLFProjectionInfo.SUBJECT_COLUMN);
String subject2 = cursor2.getString(MLFProjectionInfo.SUBJECT_COLUMN);
if (subject1 == null) {
return (subject2 == null) ? 0 : -1;
} else if (subject2 == null) {
return 1;
}
return subject1.compareToIgnoreCase(subject2);
}
}
public static class SenderComparator implements Comparator<Cursor> {
@Override
public int compare(Cursor cursor1, Cursor cursor2) {
String sender1 = MlfUtils.getSenderAddressFromCursor(cursor1);
String sender2 = MlfUtils.getSenderAddressFromCursor(cursor2);
if (sender1 == null && sender2 == null) {
return 0;
} else if (sender1 == null) {
return 1;
} else if (sender2 == null) {
return -1;
} else {
return sender1.compareToIgnoreCase(sender2);
}
}
}
}

View file

@ -3,7 +3,6 @@ package com.fsck.k9.fragment;
import java.util.List;
import android.database.Cursor;
import android.text.TextUtils;
import com.fsck.k9.Account;
@ -11,14 +10,11 @@ import com.fsck.k9.DI;
import com.fsck.k9.Preferences;
import com.fsck.k9.controller.MessageReference;
import com.fsck.k9.helper.Utility;
import com.fsck.k9.mail.Address;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mailstore.LocalFolder;
import com.fsck.k9.mailstore.LocalStore;
import com.fsck.k9.mailstore.LocalStoreProvider;
import static com.fsck.k9.fragment.MLFProjectionInfo.SENDER_LIST_COLUMN;
public class MlfUtils {
@ -35,12 +31,6 @@ public class MlfUtils {
account.setLastSelectedFolderId(folderId);
}
static String getSenderAddressFromCursor(Cursor cursor) {
String fromList = cursor.getString(SENDER_LIST_COLUMN);
Address[] fromAddrs = Address.unpack(fromList);
return (fromAddrs.length > 0) ? fromAddrs[0].getAddress() : null;
}
static String buildSubject(String subjectFromCursor, String emptySubject, int threadCount) {
if (TextUtils.isEmpty(subjectFromCursor)) {
return emptySubject;