Finally get rid of EmailProvider
This commit is contained in:
parent
6be1eb11dc
commit
7ea928bba5
11 changed files with 0 additions and 1666 deletions
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue