2009-05-26 23:40:34 +00:00
|
|
|
/*
|
|
|
|
* Copyright (C) 2009 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.android.email.provider;
|
|
|
|
|
2012-06-28 17:40:46 +00:00
|
|
|
import android.appwidget.AppWidgetManager;
|
|
|
|
import android.content.ComponentName;
|
Improve EmailContent caching...
* Guarantee that up to 16 Account (with HostAuths), and Policy rows
are always cached. Also, 6 commonly used Mailboxes per Account
(Inbox, Outbox, Drafts, Sent, Trash, and Search)
* Precache these rows when EmailProvider starts up
* Ensure that newly added, precachable rows are cached when created
* Clean up some inefficient/wrong caching code
* Fix a commonly called method in NotificationManager in which we
load a single Mailbox row using selection vs withAppendedId
* Confirm that we don't read from the database in typical use and
heavy message loading
* Add a special URI for finding mailbox by type (using the cache)
* Add special-case code for EmailContent.count(Account.CONTENT_URI)
which is used in a number of places (including on the UI thread)
and whose value is easily determined
* Add a special URI to get the default account id
* Confirm that all unit tests work
The goal here is to be able to load all Account, HostAuth, Policy,
and Mailbox objects (by id) without worrying about disk access.
There will still be a single disk read for uncommon Mailbox reads,
but this should be considered acceptable.
Change-Id: Ibc9aa7acc73185e360b0b6f3053b90a985e97210
TODO: Unit tests
2011-06-19 01:03:11 +00:00
|
|
|
import android.content.ContentProvider;
|
|
|
|
import android.content.ContentProviderOperation;
|
|
|
|
import android.content.ContentProviderResult;
|
|
|
|
import android.content.ContentResolver;
|
|
|
|
import android.content.ContentUris;
|
|
|
|
import android.content.ContentValues;
|
2012-04-25 17:26:46 +00:00
|
|
|
import android.content.Context;
|
2012-06-28 17:40:46 +00:00
|
|
|
import android.content.Intent;
|
Improve EmailContent caching...
* Guarantee that up to 16 Account (with HostAuths), and Policy rows
are always cached. Also, 6 commonly used Mailboxes per Account
(Inbox, Outbox, Drafts, Sent, Trash, and Search)
* Precache these rows when EmailProvider starts up
* Ensure that newly added, precachable rows are cached when created
* Clean up some inefficient/wrong caching code
* Fix a commonly called method in NotificationManager in which we
load a single Mailbox row using selection vs withAppendedId
* Confirm that we don't read from the database in typical use and
heavy message loading
* Add a special URI for finding mailbox by type (using the cache)
* Add special-case code for EmailContent.count(Account.CONTENT_URI)
which is used in a number of places (including on the UI thread)
and whose value is easily determined
* Add a special URI to get the default account id
* Confirm that all unit tests work
The goal here is to be able to load all Account, HostAuth, Policy,
and Mailbox objects (by id) without worrying about disk access.
There will still be a single disk read for uncommon Mailbox reads,
but this should be considered acceptable.
Change-Id: Ibc9aa7acc73185e360b0b6f3053b90a985e97210
TODO: Unit tests
2011-06-19 01:03:11 +00:00
|
|
|
import android.content.OperationApplicationException;
|
|
|
|
import android.content.UriMatcher;
|
|
|
|
import android.database.ContentObserver;
|
|
|
|
import android.database.Cursor;
|
2012-06-28 17:40:46 +00:00
|
|
|
import android.database.CursorWrapper;
|
Improve EmailContent caching...
* Guarantee that up to 16 Account (with HostAuths), and Policy rows
are always cached. Also, 6 commonly used Mailboxes per Account
(Inbox, Outbox, Drafts, Sent, Trash, and Search)
* Precache these rows when EmailProvider starts up
* Ensure that newly added, precachable rows are cached when created
* Clean up some inefficient/wrong caching code
* Fix a commonly called method in NotificationManager in which we
load a single Mailbox row using selection vs withAppendedId
* Confirm that we don't read from the database in typical use and
heavy message loading
* Add a special URI for finding mailbox by type (using the cache)
* Add special-case code for EmailContent.count(Account.CONTENT_URI)
which is used in a number of places (including on the UI thread)
and whose value is easily determined
* Add a special URI to get the default account id
* Confirm that all unit tests work
The goal here is to be able to load all Account, HostAuth, Policy,
and Mailbox objects (by id) without worrying about disk access.
There will still be a single disk read for uncommon Mailbox reads,
but this should be considered acceptable.
Change-Id: Ibc9aa7acc73185e360b0b6f3053b90a985e97210
TODO: Unit tests
2011-06-19 01:03:11 +00:00
|
|
|
import android.database.MatrixCursor;
|
2012-06-28 17:40:46 +00:00
|
|
|
import android.database.MergeCursor;
|
Improve EmailContent caching...
* Guarantee that up to 16 Account (with HostAuths), and Policy rows
are always cached. Also, 6 commonly used Mailboxes per Account
(Inbox, Outbox, Drafts, Sent, Trash, and Search)
* Precache these rows when EmailProvider starts up
* Ensure that newly added, precachable rows are cached when created
* Clean up some inefficient/wrong caching code
* Fix a commonly called method in NotificationManager in which we
load a single Mailbox row using selection vs withAppendedId
* Confirm that we don't read from the database in typical use and
heavy message loading
* Add a special URI for finding mailbox by type (using the cache)
* Add special-case code for EmailContent.count(Account.CONTENT_URI)
which is used in a number of places (including on the UI thread)
and whose value is easily determined
* Add a special URI to get the default account id
* Confirm that all unit tests work
The goal here is to be able to load all Account, HostAuth, Policy,
and Mailbox objects (by id) without worrying about disk access.
There will still be a single disk read for uncommon Mailbox reads,
but this should be considered acceptable.
Change-Id: Ibc9aa7acc73185e360b0b6f3053b90a985e97210
TODO: Unit tests
2011-06-19 01:03:11 +00:00
|
|
|
import android.database.sqlite.SQLiteDatabase;
|
|
|
|
import android.database.sqlite.SQLiteException;
|
|
|
|
import android.net.Uri;
|
2012-06-28 17:40:46 +00:00
|
|
|
import android.os.Bundle;
|
|
|
|
import android.os.Parcel;
|
|
|
|
import android.os.RemoteException;
|
|
|
|
import android.provider.BaseColumns;
|
Improve EmailContent caching...
* Guarantee that up to 16 Account (with HostAuths), and Policy rows
are always cached. Also, 6 commonly used Mailboxes per Account
(Inbox, Outbox, Drafts, Sent, Trash, and Search)
* Precache these rows when EmailProvider starts up
* Ensure that newly added, precachable rows are cached when created
* Clean up some inefficient/wrong caching code
* Fix a commonly called method in NotificationManager in which we
load a single Mailbox row using selection vs withAppendedId
* Confirm that we don't read from the database in typical use and
heavy message loading
* Add a special URI for finding mailbox by type (using the cache)
* Add special-case code for EmailContent.count(Account.CONTENT_URI)
which is used in a number of places (including on the UI thread)
and whose value is easily determined
* Add a special URI to get the default account id
* Confirm that all unit tests work
The goal here is to be able to load all Account, HostAuth, Policy,
and Mailbox objects (by id) without worrying about disk access.
There will still be a single disk read for uncommon Mailbox reads,
but this should be considered acceptable.
Change-Id: Ibc9aa7acc73185e360b0b6f3053b90a985e97210
TODO: Unit tests
2011-06-19 01:03:11 +00:00
|
|
|
import android.text.TextUtils;
|
|
|
|
import android.util.Log;
|
|
|
|
|
2012-06-28 17:40:46 +00:00
|
|
|
import com.android.common.content.ProjectionMap;
|
|
|
|
import com.android.email.NotificationController;
|
2011-06-21 01:10:10 +00:00
|
|
|
import com.android.email.Preferences;
|
2012-06-28 17:40:46 +00:00
|
|
|
import com.android.email.R;
|
|
|
|
import com.android.email.SecurityPolicy;
|
2010-10-27 23:50:54 +00:00
|
|
|
import com.android.email.provider.ContentCache.CacheToken;
|
2010-08-10 00:48:53 +00:00
|
|
|
import com.android.email.service.AttachmentDownloadService;
|
2012-06-28 17:40:46 +00:00
|
|
|
import com.android.email.service.EmailServiceUtils;
|
|
|
|
import com.android.email.service.EmailServiceUtils.EmailServiceInfo;
|
|
|
|
import com.android.email2.ui.MailActivityEmail;
|
2011-05-13 18:20:04 +00:00
|
|
|
import com.android.emailcommon.Logging;
|
2012-06-28 17:40:46 +00:00
|
|
|
import com.android.emailcommon.mail.Address;
|
2011-06-13 22:32:27 +00:00
|
|
|
import com.android.emailcommon.provider.Account;
|
2011-02-10 18:26:56 +00:00
|
|
|
import com.android.emailcommon.provider.EmailContent;
|
|
|
|
import com.android.emailcommon.provider.EmailContent.AccountColumns;
|
|
|
|
import com.android.emailcommon.provider.EmailContent.Attachment;
|
|
|
|
import com.android.emailcommon.provider.EmailContent.AttachmentColumns;
|
|
|
|
import com.android.emailcommon.provider.EmailContent.Body;
|
|
|
|
import com.android.emailcommon.provider.EmailContent.BodyColumns;
|
|
|
|
import com.android.emailcommon.provider.EmailContent.MailboxColumns;
|
|
|
|
import com.android.emailcommon.provider.EmailContent.Message;
|
|
|
|
import com.android.emailcommon.provider.EmailContent.MessageColumns;
|
2011-04-28 00:12:06 +00:00
|
|
|
import com.android.emailcommon.provider.EmailContent.PolicyColumns;
|
2011-02-10 18:26:56 +00:00
|
|
|
import com.android.emailcommon.provider.EmailContent.SyncColumns;
|
2011-05-19 22:18:12 +00:00
|
|
|
import com.android.emailcommon.provider.HostAuth;
|
2011-05-14 00:26:27 +00:00
|
|
|
import com.android.emailcommon.provider.Mailbox;
|
2011-04-28 00:12:06 +00:00
|
|
|
import com.android.emailcommon.provider.Policy;
|
Improve EmailContent caching...
* Guarantee that up to 16 Account (with HostAuths), and Policy rows
are always cached. Also, 6 commonly used Mailboxes per Account
(Inbox, Outbox, Drafts, Sent, Trash, and Search)
* Precache these rows when EmailProvider starts up
* Ensure that newly added, precachable rows are cached when created
* Clean up some inefficient/wrong caching code
* Fix a commonly called method in NotificationManager in which we
load a single Mailbox row using selection vs withAppendedId
* Confirm that we don't read from the database in typical use and
heavy message loading
* Add a special URI for finding mailbox by type (using the cache)
* Add special-case code for EmailContent.count(Account.CONTENT_URI)
which is used in a number of places (including on the UI thread)
and whose value is easily determined
* Add a special URI to get the default account id
* Confirm that all unit tests work
The goal here is to be able to load all Account, HostAuth, Policy,
and Mailbox objects (by id) without worrying about disk access.
There will still be a single disk read for uncommon Mailbox reads,
but this should be considered acceptable.
Change-Id: Ibc9aa7acc73185e360b0b6f3053b90a985e97210
TODO: Unit tests
2011-06-19 01:03:11 +00:00
|
|
|
import com.android.emailcommon.provider.QuickResponse;
|
2012-06-28 17:40:46 +00:00
|
|
|
import com.android.emailcommon.service.EmailServiceProxy;
|
|
|
|
import com.android.emailcommon.service.IEmailService;
|
|
|
|
import com.android.emailcommon.service.IEmailServiceCallback;
|
|
|
|
import com.android.emailcommon.service.SearchParams;
|
|
|
|
import com.android.emailcommon.utility.AttachmentUtilities;
|
|
|
|
import com.android.emailcommon.utility.Utility;
|
2012-06-29 16:42:05 +00:00
|
|
|
import com.android.mail.providers.Conversation;
|
|
|
|
import com.android.mail.providers.Folder;
|
2012-06-28 17:40:46 +00:00
|
|
|
import com.android.mail.providers.UIProvider;
|
|
|
|
import com.android.mail.providers.UIProvider.AccountCapabilities;
|
|
|
|
import com.android.mail.providers.UIProvider.AccountCursorExtraKeys;
|
|
|
|
import com.android.mail.providers.UIProvider.ConversationPriority;
|
|
|
|
import com.android.mail.providers.UIProvider.ConversationSendingState;
|
|
|
|
import com.android.mail.providers.UIProvider.DraftType;
|
2012-06-29 16:42:05 +00:00
|
|
|
import com.android.mail.ui.ConversationUpdater;
|
|
|
|
import com.android.mail.ui.DestructiveAction;
|
|
|
|
import com.android.mail.ui.FoldersSelectionDialog;
|
2012-06-28 17:40:46 +00:00
|
|
|
import com.android.mail.utils.LogUtils;
|
|
|
|
import com.android.mail.utils.MatrixCursorWithExtra;
|
|
|
|
import com.android.mail.utils.Utils;
|
|
|
|
import com.android.mail.widget.BaseWidgetProvider;
|
|
|
|
import com.android.mail.widget.WidgetProvider;
|
2011-04-22 22:45:11 +00:00
|
|
|
import com.google.common.annotations.VisibleForTesting;
|
2009-05-26 23:40:34 +00:00
|
|
|
|
2009-11-19 01:11:33 +00:00
|
|
|
import java.io.File;
|
2009-06-01 19:55:50 +00:00
|
|
|
import java.util.ArrayList;
|
Improve EmailContent caching...
* Guarantee that up to 16 Account (with HostAuths), and Policy rows
are always cached. Also, 6 commonly used Mailboxes per Account
(Inbox, Outbox, Drafts, Sent, Trash, and Search)
* Precache these rows when EmailProvider starts up
* Ensure that newly added, precachable rows are cached when created
* Clean up some inefficient/wrong caching code
* Fix a commonly called method in NotificationManager in which we
load a single Mailbox row using selection vs withAppendedId
* Confirm that we don't read from the database in typical use and
heavy message loading
* Add a special URI for finding mailbox by type (using the cache)
* Add special-case code for EmailContent.count(Account.CONTENT_URI)
which is used in a number of places (including on the UI thread)
and whose value is easily determined
* Add a special URI to get the default account id
* Confirm that all unit tests work
The goal here is to be able to load all Account, HostAuth, Policy,
and Mailbox objects (by id) without worrying about disk access.
There will still be a single disk read for uncommon Mailbox reads,
but this should be considered acceptable.
Change-Id: Ibc9aa7acc73185e360b0b6f3053b90a985e97210
TODO: Unit tests
2011-06-19 01:03:11 +00:00
|
|
|
import java.util.Arrays;
|
|
|
|
import java.util.Collection;
|
|
|
|
import java.util.HashMap;
|
2011-06-28 03:11:24 +00:00
|
|
|
import java.util.List;
|
Improve EmailContent caching...
* Guarantee that up to 16 Account (with HostAuths), and Policy rows
are always cached. Also, 6 commonly used Mailboxes per Account
(Inbox, Outbox, Drafts, Sent, Trash, and Search)
* Precache these rows when EmailProvider starts up
* Ensure that newly added, precachable rows are cached when created
* Clean up some inefficient/wrong caching code
* Fix a commonly called method in NotificationManager in which we
load a single Mailbox row using selection vs withAppendedId
* Confirm that we don't read from the database in typical use and
heavy message loading
* Add a special URI for finding mailbox by type (using the cache)
* Add special-case code for EmailContent.count(Account.CONTENT_URI)
which is used in a number of places (including on the UI thread)
and whose value is easily determined
* Add a special URI to get the default account id
* Confirm that all unit tests work
The goal here is to be able to load all Account, HostAuth, Policy,
and Mailbox objects (by id) without worrying about disk access.
There will still be a single disk read for uncommon Mailbox reads,
but this should be considered acceptable.
Change-Id: Ibc9aa7acc73185e360b0b6f3053b90a985e97210
TODO: Unit tests
2011-06-19 01:03:11 +00:00
|
|
|
import java.util.Map;
|
2012-06-28 17:40:46 +00:00
|
|
|
import java.util.regex.Pattern;
|
2009-06-01 19:55:50 +00:00
|
|
|
|
2012-06-28 17:40:46 +00:00
|
|
|
/**
|
|
|
|
* @author mblank
|
|
|
|
*
|
|
|
|
*/
|
2009-05-26 23:40:34 +00:00
|
|
|
public class EmailProvider extends ContentProvider {
|
|
|
|
|
|
|
|
private static final String TAG = "EmailProvider";
|
|
|
|
|
2012-06-28 17:40:46 +00:00
|
|
|
public static final String EMAIL_APP_MIME_TYPE = "application/email-ls";
|
|
|
|
|
2009-11-19 01:11:33 +00:00
|
|
|
protected static final String DATABASE_NAME = "EmailProvider.db";
|
|
|
|
protected static final String BODY_DATABASE_NAME = "EmailProviderBody.db";
|
2011-05-06 19:07:39 +00:00
|
|
|
protected static final String BACKUP_DATABASE_NAME = "EmailProviderBackup.db";
|
2009-11-19 01:11:33 +00:00
|
|
|
|
2010-08-10 00:48:53 +00:00
|
|
|
public static final String ACTION_ATTACHMENT_UPDATED = "com.android.email.ATTACHMENT_UPDATED";
|
|
|
|
public static final String ATTACHMENT_UPDATED_EXTRA_FLAGS =
|
|
|
|
"com.android.email.ATTACHMENT_UPDATED_FLAGS";
|
|
|
|
|
2012-01-09 19:36:16 +00:00
|
|
|
/**
|
|
|
|
* Notifies that changes happened. Certain UI components, e.g., widgets, can register for this
|
|
|
|
* {@link android.content.Intent} and update accordingly. However, this can be very broad and
|
|
|
|
* is NOT the preferred way of getting notification.
|
|
|
|
*/
|
|
|
|
public static final String ACTION_NOTIFY_MESSAGE_LIST_DATASET_CHANGED =
|
2012-06-28 17:40:46 +00:00
|
|
|
"com.android.email.MESSAGE_LIST_DATASET_CHANGED";
|
2012-01-09 19:36:16 +00:00
|
|
|
|
2010-10-14 02:04:46 +00:00
|
|
|
public static final String EMAIL_MESSAGE_MIME_TYPE =
|
|
|
|
"vnd.android.cursor.item/email-message";
|
2010-08-10 00:48:53 +00:00
|
|
|
public static final String EMAIL_ATTACHMENT_MIME_TYPE =
|
|
|
|
"vnd.android.cursor.item/email-attachment";
|
|
|
|
|
2009-11-19 01:11:33 +00:00
|
|
|
public static final Uri INTEGRITY_CHECK_URI =
|
|
|
|
Uri.parse("content://" + EmailContent.AUTHORITY + "/integrityCheck");
|
2011-05-06 19:07:39 +00:00
|
|
|
public static final Uri ACCOUNT_BACKUP_URI =
|
|
|
|
Uri.parse("content://" + EmailContent.AUTHORITY + "/accountBackup");
|
2012-06-28 17:40:46 +00:00
|
|
|
public static final Uri FOLDER_STATUS_URI =
|
|
|
|
Uri.parse("content://" + EmailContent.AUTHORITY + "/status");
|
|
|
|
public static final Uri FOLDER_REFRESH_URI =
|
|
|
|
Uri.parse("content://" + EmailContent.AUTHORITY + "/refresh");
|
2009-10-13 23:25:00 +00:00
|
|
|
|
2011-05-06 18:31:10 +00:00
|
|
|
/** Appended to the notification URI for delete operations */
|
|
|
|
public static final String NOTIFICATION_OP_DELETE = "delete";
|
|
|
|
/** Appended to the notification URI for insert operations */
|
|
|
|
public static final String NOTIFICATION_OP_INSERT = "insert";
|
|
|
|
/** Appended to the notification URI for update operations */
|
|
|
|
public static final String NOTIFICATION_OP_UPDATE = "update";
|
|
|
|
|
2009-10-13 23:25:00 +00:00
|
|
|
// Definitions for our queries looking for orphaned messages
|
|
|
|
private static final String[] ORPHANS_PROJECTION
|
|
|
|
= new String[] {MessageColumns.ID, MessageColumns.MAILBOX_KEY};
|
|
|
|
private static final int ORPHANS_ID = 0;
|
|
|
|
private static final int ORPHANS_MAILBOX_KEY = 1;
|
|
|
|
|
|
|
|
private static final String WHERE_ID = EmailContent.RECORD_ID + "=?";
|
2009-06-27 19:14:14 +00:00
|
|
|
|
Improve EmailContent caching...
* Guarantee that up to 16 Account (with HostAuths), and Policy rows
are always cached. Also, 6 commonly used Mailboxes per Account
(Inbox, Outbox, Drafts, Sent, Trash, and Search)
* Precache these rows when EmailProvider starts up
* Ensure that newly added, precachable rows are cached when created
* Clean up some inefficient/wrong caching code
* Fix a commonly called method in NotificationManager in which we
load a single Mailbox row using selection vs withAppendedId
* Confirm that we don't read from the database in typical use and
heavy message loading
* Add a special URI for finding mailbox by type (using the cache)
* Add special-case code for EmailContent.count(Account.CONTENT_URI)
which is used in a number of places (including on the UI thread)
and whose value is easily determined
* Add a special URI to get the default account id
* Confirm that all unit tests work
The goal here is to be able to load all Account, HostAuth, Policy,
and Mailbox objects (by id) without worrying about disk access.
There will still be a single disk read for uncommon Mailbox reads,
but this should be considered acceptable.
Change-Id: Ibc9aa7acc73185e360b0b6f3053b90a985e97210
TODO: Unit tests
2011-06-19 01:03:11 +00:00
|
|
|
// This is not a hard limit on accounts, per se, but beyond this, we can't guarantee that all
|
|
|
|
// critical mailboxes, host auth's, accounts, and policies are cached
|
|
|
|
private static final int MAX_CACHED_ACCOUNTS = 16;
|
|
|
|
// Inbox, Drafts, Sent, Outbox, Trash, and Search (these boxes are cached when possible)
|
|
|
|
private static final int NUM_ALWAYS_CACHED_MAILBOXES = 6;
|
|
|
|
|
2010-10-27 23:50:54 +00:00
|
|
|
// We'll cache the following four tables; sizes are best estimates of effective values
|
Improve EmailContent caching...
* Guarantee that up to 16 Account (with HostAuths), and Policy rows
are always cached. Also, 6 commonly used Mailboxes per Account
(Inbox, Outbox, Drafts, Sent, Trash, and Search)
* Precache these rows when EmailProvider starts up
* Ensure that newly added, precachable rows are cached when created
* Clean up some inefficient/wrong caching code
* Fix a commonly called method in NotificationManager in which we
load a single Mailbox row using selection vs withAppendedId
* Confirm that we don't read from the database in typical use and
heavy message loading
* Add a special URI for finding mailbox by type (using the cache)
* Add special-case code for EmailContent.count(Account.CONTENT_URI)
which is used in a number of places (including on the UI thread)
and whose value is easily determined
* Add a special URI to get the default account id
* Confirm that all unit tests work
The goal here is to be able to load all Account, HostAuth, Policy,
and Mailbox objects (by id) without worrying about disk access.
There will still be a single disk read for uncommon Mailbox reads,
but this should be considered acceptable.
Change-Id: Ibc9aa7acc73185e360b0b6f3053b90a985e97210
TODO: Unit tests
2011-06-19 01:03:11 +00:00
|
|
|
private final ContentCache mCacheAccount =
|
|
|
|
new ContentCache("Account", Account.CONTENT_PROJECTION, MAX_CACHED_ACCOUNTS);
|
|
|
|
private final ContentCache mCacheHostAuth =
|
|
|
|
new ContentCache("HostAuth", HostAuth.CONTENT_PROJECTION, MAX_CACHED_ACCOUNTS * 2);
|
|
|
|
/*package*/ final ContentCache mCacheMailbox =
|
|
|
|
new ContentCache("Mailbox", Mailbox.CONTENT_PROJECTION,
|
|
|
|
MAX_CACHED_ACCOUNTS * (NUM_ALWAYS_CACHED_MAILBOXES + 2));
|
|
|
|
private final ContentCache mCacheMessage =
|
2011-04-28 00:12:06 +00:00
|
|
|
new ContentCache("Message", Message.CONTENT_PROJECTION, 8);
|
Improve EmailContent caching...
* Guarantee that up to 16 Account (with HostAuths), and Policy rows
are always cached. Also, 6 commonly used Mailboxes per Account
(Inbox, Outbox, Drafts, Sent, Trash, and Search)
* Precache these rows when EmailProvider starts up
* Ensure that newly added, precachable rows are cached when created
* Clean up some inefficient/wrong caching code
* Fix a commonly called method in NotificationManager in which we
load a single Mailbox row using selection vs withAppendedId
* Confirm that we don't read from the database in typical use and
heavy message loading
* Add a special URI for finding mailbox by type (using the cache)
* Add special-case code for EmailContent.count(Account.CONTENT_URI)
which is used in a number of places (including on the UI thread)
and whose value is easily determined
* Add a special URI to get the default account id
* Confirm that all unit tests work
The goal here is to be able to load all Account, HostAuth, Policy,
and Mailbox objects (by id) without worrying about disk access.
There will still be a single disk read for uncommon Mailbox reads,
but this should be considered acceptable.
Change-Id: Ibc9aa7acc73185e360b0b6f3053b90a985e97210
TODO: Unit tests
2011-06-19 01:03:11 +00:00
|
|
|
private final ContentCache mCachePolicy =
|
|
|
|
new ContentCache("Policy", Policy.CONTENT_PROJECTION, MAX_CACHED_ACCOUNTS);
|
2010-10-27 23:50:54 +00:00
|
|
|
|
2009-05-26 23:40:34 +00:00
|
|
|
private static final int ACCOUNT_BASE = 0;
|
|
|
|
private static final int ACCOUNT = ACCOUNT_BASE;
|
2010-09-10 22:19:57 +00:00
|
|
|
private static final int ACCOUNT_ID = ACCOUNT_BASE + 1;
|
|
|
|
private static final int ACCOUNT_ID_ADD_TO_FIELD = ACCOUNT_BASE + 2;
|
2010-09-14 23:28:50 +00:00
|
|
|
private static final int ACCOUNT_RESET_NEW_COUNT = ACCOUNT_BASE + 3;
|
|
|
|
private static final int ACCOUNT_RESET_NEW_COUNT_ID = ACCOUNT_BASE + 4;
|
Improve EmailContent caching...
* Guarantee that up to 16 Account (with HostAuths), and Policy rows
are always cached. Also, 6 commonly used Mailboxes per Account
(Inbox, Outbox, Drafts, Sent, Trash, and Search)
* Precache these rows when EmailProvider starts up
* Ensure that newly added, precachable rows are cached when created
* Clean up some inefficient/wrong caching code
* Fix a commonly called method in NotificationManager in which we
load a single Mailbox row using selection vs withAppendedId
* Confirm that we don't read from the database in typical use and
heavy message loading
* Add a special URI for finding mailbox by type (using the cache)
* Add special-case code for EmailContent.count(Account.CONTENT_URI)
which is used in a number of places (including on the UI thread)
and whose value is easily determined
* Add a special URI to get the default account id
* Confirm that all unit tests work
The goal here is to be able to load all Account, HostAuth, Policy,
and Mailbox objects (by id) without worrying about disk access.
There will still be a single disk read for uncommon Mailbox reads,
but this should be considered acceptable.
Change-Id: Ibc9aa7acc73185e360b0b6f3053b90a985e97210
TODO: Unit tests
2011-06-19 01:03:11 +00:00
|
|
|
private static final int ACCOUNT_DEFAULT_ID = ACCOUNT_BASE + 5;
|
2012-06-28 17:40:46 +00:00
|
|
|
private static final int ACCOUNT_CHECK = ACCOUNT_BASE + 6;
|
2012-06-29 16:42:05 +00:00
|
|
|
private static final int ACCOUNT_PICK_TRASH_FOLDER = ACCOUNT_BASE + 7;
|
2009-06-27 19:14:14 +00:00
|
|
|
|
2009-05-26 23:40:34 +00:00
|
|
|
private static final int MAILBOX_BASE = 0x1000;
|
|
|
|
private static final int MAILBOX = MAILBOX_BASE;
|
2010-09-10 22:19:57 +00:00
|
|
|
private static final int MAILBOX_ID = MAILBOX_BASE + 1;
|
Improve EmailContent caching...
* Guarantee that up to 16 Account (with HostAuths), and Policy rows
are always cached. Also, 6 commonly used Mailboxes per Account
(Inbox, Outbox, Drafts, Sent, Trash, and Search)
* Precache these rows when EmailProvider starts up
* Ensure that newly added, precachable rows are cached when created
* Clean up some inefficient/wrong caching code
* Fix a commonly called method in NotificationManager in which we
load a single Mailbox row using selection vs withAppendedId
* Confirm that we don't read from the database in typical use and
heavy message loading
* Add a special URI for finding mailbox by type (using the cache)
* Add special-case code for EmailContent.count(Account.CONTENT_URI)
which is used in a number of places (including on the UI thread)
and whose value is easily determined
* Add a special URI to get the default account id
* Confirm that all unit tests work
The goal here is to be able to load all Account, HostAuth, Policy,
and Mailbox objects (by id) without worrying about disk access.
There will still be a single disk read for uncommon Mailbox reads,
but this should be considered acceptable.
Change-Id: Ibc9aa7acc73185e360b0b6f3053b90a985e97210
TODO: Unit tests
2011-06-19 01:03:11 +00:00
|
|
|
private static final int MAILBOX_ID_FROM_ACCOUNT_AND_TYPE = MAILBOX_BASE + 2;
|
2012-06-28 17:40:46 +00:00
|
|
|
private static final int MAILBOX_ID_ADD_TO_FIELD = MAILBOX_BASE + 3;
|
|
|
|
private static final int MAILBOX_NOTIFICATION = MAILBOX_BASE + 4;
|
|
|
|
private static final int MAILBOX_MOST_RECENT_MESSAGE = MAILBOX_BASE + 5;
|
2009-06-27 19:14:14 +00:00
|
|
|
|
2009-05-26 23:40:34 +00:00
|
|
|
private static final int MESSAGE_BASE = 0x2000;
|
|
|
|
private static final int MESSAGE = MESSAGE_BASE;
|
2009-07-16 23:03:40 +00:00
|
|
|
private static final int MESSAGE_ID = MESSAGE_BASE + 1;
|
|
|
|
private static final int SYNCED_MESSAGE_ID = MESSAGE_BASE + 2;
|
2012-06-29 16:42:05 +00:00
|
|
|
private static final int SYNCED_MESSAGE_SELECTION = MESSAGE_BASE + 3;
|
2009-06-27 19:14:14 +00:00
|
|
|
|
2009-05-26 23:40:34 +00:00
|
|
|
private static final int ATTACHMENT_BASE = 0x3000;
|
|
|
|
private static final int ATTACHMENT = ATTACHMENT_BASE;
|
2010-09-10 22:19:57 +00:00
|
|
|
private static final int ATTACHMENT_ID = ATTACHMENT_BASE + 1;
|
|
|
|
private static final int ATTACHMENTS_MESSAGE_ID = ATTACHMENT_BASE + 2;
|
2009-06-27 19:14:14 +00:00
|
|
|
|
2009-05-26 23:40:34 +00:00
|
|
|
private static final int HOSTAUTH_BASE = 0x4000;
|
|
|
|
private static final int HOSTAUTH = HOSTAUTH_BASE;
|
|
|
|
private static final int HOSTAUTH_ID = HOSTAUTH_BASE + 1;
|
2009-06-27 19:14:14 +00:00
|
|
|
|
2009-06-17 17:22:37 +00:00
|
|
|
private static final int UPDATED_MESSAGE_BASE = 0x5000;
|
|
|
|
private static final int UPDATED_MESSAGE = UPDATED_MESSAGE_BASE;
|
2009-06-27 19:14:14 +00:00
|
|
|
private static final int UPDATED_MESSAGE_ID = UPDATED_MESSAGE_BASE + 1;
|
|
|
|
|
|
|
|
private static final int DELETED_MESSAGE_BASE = 0x6000;
|
|
|
|
private static final int DELETED_MESSAGE = DELETED_MESSAGE_BASE;
|
|
|
|
private static final int DELETED_MESSAGE_ID = DELETED_MESSAGE_BASE + 1;
|
|
|
|
|
2011-04-28 00:12:06 +00:00
|
|
|
private static final int POLICY_BASE = 0x7000;
|
|
|
|
private static final int POLICY = POLICY_BASE;
|
|
|
|
private static final int POLICY_ID = POLICY_BASE + 1;
|
|
|
|
|
2011-06-01 17:09:26 +00:00
|
|
|
private static final int QUICK_RESPONSE_BASE = 0x8000;
|
|
|
|
private static final int QUICK_RESPONSE = QUICK_RESPONSE_BASE;
|
|
|
|
private static final int QUICK_RESPONSE_ID = QUICK_RESPONSE_BASE + 1;
|
|
|
|
private static final int QUICK_RESPONSE_ACCOUNT_ID = QUICK_RESPONSE_BASE + 2;
|
|
|
|
|
2012-06-28 17:40:46 +00:00
|
|
|
private static final int UI_BASE = 0x9000;
|
|
|
|
private static final int UI_FOLDERS = UI_BASE;
|
|
|
|
private static final int UI_SUBFOLDERS = UI_BASE + 1;
|
|
|
|
private static final int UI_MESSAGES = UI_BASE + 2;
|
|
|
|
private static final int UI_MESSAGE = UI_BASE + 3;
|
|
|
|
private static final int UI_SENDMAIL = UI_BASE + 4;
|
|
|
|
private static final int UI_UNDO = UI_BASE + 5;
|
|
|
|
private static final int UI_SAVEDRAFT = UI_BASE + 6;
|
|
|
|
private static final int UI_UPDATEDRAFT = UI_BASE + 7;
|
|
|
|
private static final int UI_SENDDRAFT = UI_BASE + 8;
|
|
|
|
private static final int UI_FOLDER_REFRESH = UI_BASE + 9;
|
|
|
|
private static final int UI_FOLDER = UI_BASE + 10;
|
|
|
|
private static final int UI_ACCOUNT = UI_BASE + 11;
|
|
|
|
private static final int UI_ACCTS = UI_BASE + 12;
|
|
|
|
private static final int UI_ATTACHMENTS = UI_BASE + 13;
|
|
|
|
private static final int UI_ATTACHMENT = UI_BASE + 14;
|
|
|
|
private static final int UI_SEARCH = UI_BASE + 15;
|
|
|
|
private static final int UI_ACCOUNT_DATA = UI_BASE + 16;
|
|
|
|
private static final int UI_FOLDER_LOAD_MORE = UI_BASE + 17;
|
|
|
|
private static final int UI_CONVERSATION = UI_BASE + 18;
|
|
|
|
private static final int UI_RECENT_FOLDERS = UI_BASE + 19;
|
|
|
|
private static final int UI_DEFAULT_RECENT_FOLDERS = UI_BASE + 20;
|
|
|
|
private static final int UI_ALL_FOLDERS = UI_BASE + 21;
|
|
|
|
|
2009-06-27 19:14:14 +00:00
|
|
|
// MUST ALWAYS EQUAL THE LAST OF THE PREVIOUS BASE CONSTANTS
|
2012-06-28 17:40:46 +00:00
|
|
|
private static final int LAST_EMAIL_PROVIDER_DB_BASE = UI_BASE;
|
2009-06-27 19:14:14 +00:00
|
|
|
|
|
|
|
// DO NOT CHANGE BODY_BASE!!
|
|
|
|
private static final int BODY_BASE = LAST_EMAIL_PROVIDER_DB_BASE + 0x1000;
|
2009-05-26 23:40:34 +00:00
|
|
|
private static final int BODY = BODY_BASE;
|
|
|
|
private static final int BODY_ID = BODY_BASE + 1;
|
2009-06-27 19:14:14 +00:00
|
|
|
|
2009-05-26 23:40:34 +00:00
|
|
|
private static final int BASE_SHIFT = 12; // 12 bits to the base type: 0, 0x1000, 0x2000, etc.
|
2009-06-27 19:14:14 +00:00
|
|
|
|
2010-10-27 23:50:54 +00:00
|
|
|
// TABLE_NAMES MUST remain in the order of the BASE constants above (e.g. ACCOUNT_BASE = 0x0000,
|
|
|
|
// MESSAGE_BASE = 0x1000, etc.)
|
2009-05-26 23:40:34 +00:00
|
|
|
private static final String[] TABLE_NAMES = {
|
2011-06-13 22:32:27 +00:00
|
|
|
Account.TABLE_NAME,
|
2011-05-14 00:26:27 +00:00
|
|
|
Mailbox.TABLE_NAME,
|
Improve EmailContent caching...
* Guarantee that up to 16 Account (with HostAuths), and Policy rows
are always cached. Also, 6 commonly used Mailboxes per Account
(Inbox, Outbox, Drafts, Sent, Trash, and Search)
* Precache these rows when EmailProvider starts up
* Ensure that newly added, precachable rows are cached when created
* Clean up some inefficient/wrong caching code
* Fix a commonly called method in NotificationManager in which we
load a single Mailbox row using selection vs withAppendedId
* Confirm that we don't read from the database in typical use and
heavy message loading
* Add a special URI for finding mailbox by type (using the cache)
* Add special-case code for EmailContent.count(Account.CONTENT_URI)
which is used in a number of places (including on the UI thread)
and whose value is easily determined
* Add a special URI to get the default account id
* Confirm that all unit tests work
The goal here is to be able to load all Account, HostAuth, Policy,
and Mailbox objects (by id) without worrying about disk access.
There will still be a single disk read for uncommon Mailbox reads,
but this should be considered acceptable.
Change-Id: Ibc9aa7acc73185e360b0b6f3053b90a985e97210
TODO: Unit tests
2011-06-19 01:03:11 +00:00
|
|
|
Message.TABLE_NAME,
|
|
|
|
Attachment.TABLE_NAME,
|
2011-05-19 22:18:12 +00:00
|
|
|
HostAuth.TABLE_NAME,
|
Improve EmailContent caching...
* Guarantee that up to 16 Account (with HostAuths), and Policy rows
are always cached. Also, 6 commonly used Mailboxes per Account
(Inbox, Outbox, Drafts, Sent, Trash, and Search)
* Precache these rows when EmailProvider starts up
* Ensure that newly added, precachable rows are cached when created
* Clean up some inefficient/wrong caching code
* Fix a commonly called method in NotificationManager in which we
load a single Mailbox row using selection vs withAppendedId
* Confirm that we don't read from the database in typical use and
heavy message loading
* Add a special URI for finding mailbox by type (using the cache)
* Add special-case code for EmailContent.count(Account.CONTENT_URI)
which is used in a number of places (including on the UI thread)
and whose value is easily determined
* Add a special URI to get the default account id
* Confirm that all unit tests work
The goal here is to be able to load all Account, HostAuth, Policy,
and Mailbox objects (by id) without worrying about disk access.
There will still be a single disk read for uncommon Mailbox reads,
but this should be considered acceptable.
Change-Id: Ibc9aa7acc73185e360b0b6f3053b90a985e97210
TODO: Unit tests
2011-06-19 01:03:11 +00:00
|
|
|
Message.UPDATED_TABLE_NAME,
|
|
|
|
Message.DELETED_TABLE_NAME,
|
2011-04-28 00:12:06 +00:00
|
|
|
Policy.TABLE_NAME,
|
2011-06-01 17:09:26 +00:00
|
|
|
QuickResponse.TABLE_NAME,
|
2012-06-28 17:40:46 +00:00
|
|
|
null, // UI
|
|
|
|
Body.TABLE_NAME,
|
2009-05-26 23:40:34 +00:00
|
|
|
};
|
2009-06-27 19:14:14 +00:00
|
|
|
|
2010-10-27 23:50:54 +00:00
|
|
|
// CONTENT_CACHES MUST remain in the order of the BASE constants above
|
Improve EmailContent caching...
* Guarantee that up to 16 Account (with HostAuths), and Policy rows
are always cached. Also, 6 commonly used Mailboxes per Account
(Inbox, Outbox, Drafts, Sent, Trash, and Search)
* Precache these rows when EmailProvider starts up
* Ensure that newly added, precachable rows are cached when created
* Clean up some inefficient/wrong caching code
* Fix a commonly called method in NotificationManager in which we
load a single Mailbox row using selection vs withAppendedId
* Confirm that we don't read from the database in typical use and
heavy message loading
* Add a special URI for finding mailbox by type (using the cache)
* Add special-case code for EmailContent.count(Account.CONTENT_URI)
which is used in a number of places (including on the UI thread)
and whose value is easily determined
* Add a special URI to get the default account id
* Confirm that all unit tests work
The goal here is to be able to load all Account, HostAuth, Policy,
and Mailbox objects (by id) without worrying about disk access.
There will still be a single disk read for uncommon Mailbox reads,
but this should be considered acceptable.
Change-Id: Ibc9aa7acc73185e360b0b6f3053b90a985e97210
TODO: Unit tests
2011-06-19 01:03:11 +00:00
|
|
|
private final ContentCache[] mContentCaches = {
|
|
|
|
mCacheAccount,
|
|
|
|
mCacheMailbox,
|
|
|
|
mCacheMessage,
|
|
|
|
null, // Attachment
|
|
|
|
mCacheHostAuth,
|
|
|
|
null, // Updated message
|
|
|
|
null, // Deleted message
|
|
|
|
mCachePolicy,
|
|
|
|
null, // Quick response
|
2012-06-28 17:40:46 +00:00
|
|
|
null, // Body
|
|
|
|
null // UI
|
Improve EmailContent caching...
* Guarantee that up to 16 Account (with HostAuths), and Policy rows
are always cached. Also, 6 commonly used Mailboxes per Account
(Inbox, Outbox, Drafts, Sent, Trash, and Search)
* Precache these rows when EmailProvider starts up
* Ensure that newly added, precachable rows are cached when created
* Clean up some inefficient/wrong caching code
* Fix a commonly called method in NotificationManager in which we
load a single Mailbox row using selection vs withAppendedId
* Confirm that we don't read from the database in typical use and
heavy message loading
* Add a special URI for finding mailbox by type (using the cache)
* Add special-case code for EmailContent.count(Account.CONTENT_URI)
which is used in a number of places (including on the UI thread)
and whose value is easily determined
* Add a special URI to get the default account id
* Confirm that all unit tests work
The goal here is to be able to load all Account, HostAuth, Policy,
and Mailbox objects (by id) without worrying about disk access.
There will still be a single disk read for uncommon Mailbox reads,
but this should be considered acceptable.
Change-Id: Ibc9aa7acc73185e360b0b6f3053b90a985e97210
TODO: Unit tests
2011-06-19 01:03:11 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
// CACHE_PROJECTIONS MUST remain in the order of the BASE constants above
|
|
|
|
private static final String[][] CACHE_PROJECTIONS = {
|
|
|
|
Account.CONTENT_PROJECTION,
|
|
|
|
Mailbox.CONTENT_PROJECTION,
|
|
|
|
Message.CONTENT_PROJECTION,
|
2011-04-28 00:12:06 +00:00
|
|
|
null, // Attachment
|
Improve EmailContent caching...
* Guarantee that up to 16 Account (with HostAuths), and Policy rows
are always cached. Also, 6 commonly used Mailboxes per Account
(Inbox, Outbox, Drafts, Sent, Trash, and Search)
* Precache these rows when EmailProvider starts up
* Ensure that newly added, precachable rows are cached when created
* Clean up some inefficient/wrong caching code
* Fix a commonly called method in NotificationManager in which we
load a single Mailbox row using selection vs withAppendedId
* Confirm that we don't read from the database in typical use and
heavy message loading
* Add a special URI for finding mailbox by type (using the cache)
* Add special-case code for EmailContent.count(Account.CONTENT_URI)
which is used in a number of places (including on the UI thread)
and whose value is easily determined
* Add a special URI to get the default account id
* Confirm that all unit tests work
The goal here is to be able to load all Account, HostAuth, Policy,
and Mailbox objects (by id) without worrying about disk access.
There will still be a single disk read for uncommon Mailbox reads,
but this should be considered acceptable.
Change-Id: Ibc9aa7acc73185e360b0b6f3053b90a985e97210
TODO: Unit tests
2011-06-19 01:03:11 +00:00
|
|
|
HostAuth.CONTENT_PROJECTION,
|
2011-04-28 00:12:06 +00:00
|
|
|
null, // Updated message
|
|
|
|
null, // Deleted message
|
Improve EmailContent caching...
* Guarantee that up to 16 Account (with HostAuths), and Policy rows
are always cached. Also, 6 commonly used Mailboxes per Account
(Inbox, Outbox, Drafts, Sent, Trash, and Search)
* Precache these rows when EmailProvider starts up
* Ensure that newly added, precachable rows are cached when created
* Clean up some inefficient/wrong caching code
* Fix a commonly called method in NotificationManager in which we
load a single Mailbox row using selection vs withAppendedId
* Confirm that we don't read from the database in typical use and
heavy message loading
* Add a special URI for finding mailbox by type (using the cache)
* Add special-case code for EmailContent.count(Account.CONTENT_URI)
which is used in a number of places (including on the UI thread)
and whose value is easily determined
* Add a special URI to get the default account id
* Confirm that all unit tests work
The goal here is to be able to load all Account, HostAuth, Policy,
and Mailbox objects (by id) without worrying about disk access.
There will still be a single disk read for uncommon Mailbox reads,
but this should be considered acceptable.
Change-Id: Ibc9aa7acc73185e360b0b6f3053b90a985e97210
TODO: Unit tests
2011-06-19 01:03:11 +00:00
|
|
|
Policy.CONTENT_PROJECTION,
|
2011-06-01 17:09:26 +00:00
|
|
|
null, // Quick response
|
2012-06-28 17:40:46 +00:00
|
|
|
null, // Body
|
|
|
|
null // UI
|
2011-04-28 00:12:06 +00:00
|
|
|
};
|
2010-10-27 23:50:54 +00:00
|
|
|
|
2009-05-26 23:40:34 +00:00
|
|
|
private static final UriMatcher sURIMatcher = new UriMatcher(UriMatcher.NO_MATCH);
|
|
|
|
|
Improve EmailContent caching...
* Guarantee that up to 16 Account (with HostAuths), and Policy rows
are always cached. Also, 6 commonly used Mailboxes per Account
(Inbox, Outbox, Drafts, Sent, Trash, and Search)
* Precache these rows when EmailProvider starts up
* Ensure that newly added, precachable rows are cached when created
* Clean up some inefficient/wrong caching code
* Fix a commonly called method in NotificationManager in which we
load a single Mailbox row using selection vs withAppendedId
* Confirm that we don't read from the database in typical use and
heavy message loading
* Add a special URI for finding mailbox by type (using the cache)
* Add special-case code for EmailContent.count(Account.CONTENT_URI)
which is used in a number of places (including on the UI thread)
and whose value is easily determined
* Add a special URI to get the default account id
* Confirm that all unit tests work
The goal here is to be able to load all Account, HostAuth, Policy,
and Mailbox objects (by id) without worrying about disk access.
There will still be a single disk read for uncommon Mailbox reads,
but this should be considered acceptable.
Change-Id: Ibc9aa7acc73185e360b0b6f3053b90a985e97210
TODO: Unit tests
2011-06-19 01:03:11 +00:00
|
|
|
private static final String MAILBOX_PRE_CACHE_SELECTION = MailboxColumns.TYPE + " IN (" +
|
|
|
|
Mailbox.TYPE_INBOX + "," + Mailbox.TYPE_DRAFTS + "," + Mailbox.TYPE_TRASH + "," +
|
|
|
|
Mailbox.TYPE_SENT + "," + Mailbox.TYPE_SEARCH + "," + Mailbox.TYPE_OUTBOX + ")";
|
|
|
|
|
2009-06-27 19:14:14 +00:00
|
|
|
/**
|
|
|
|
* Let's only generate these SQL strings once, as they are used frequently
|
|
|
|
* Note that this isn't relevant for table creation strings, since they are used only once
|
|
|
|
*/
|
|
|
|
private static final String UPDATED_MESSAGE_INSERT = "insert or ignore into " +
|
|
|
|
Message.UPDATED_TABLE_NAME + " select * from " + Message.TABLE_NAME + " where " +
|
|
|
|
EmailContent.RECORD_ID + '=';
|
|
|
|
|
|
|
|
private static final String UPDATED_MESSAGE_DELETE = "delete from " +
|
|
|
|
Message.UPDATED_TABLE_NAME + " where " + EmailContent.RECORD_ID + '=';
|
|
|
|
|
|
|
|
private static final String DELETED_MESSAGE_INSERT = "insert or replace into " +
|
|
|
|
Message.DELETED_TABLE_NAME + " select * from " + Message.TABLE_NAME + " where " +
|
|
|
|
EmailContent.RECORD_ID + '=';
|
|
|
|
|
|
|
|
private static final String DELETE_ORPHAN_BODIES = "delete from " + Body.TABLE_NAME +
|
2009-08-03 13:05:50 +00:00
|
|
|
" where " + BodyColumns.MESSAGE_KEY + " in " + "(select " + BodyColumns.MESSAGE_KEY +
|
2009-06-27 19:14:14 +00:00
|
|
|
" from " + Body.TABLE_NAME + " except select " + EmailContent.RECORD_ID + " from " +
|
|
|
|
Message.TABLE_NAME + ')';
|
|
|
|
|
|
|
|
private static final String DELETE_BODY = "delete from " + Body.TABLE_NAME +
|
2009-08-03 13:05:50 +00:00
|
|
|
" where " + BodyColumns.MESSAGE_KEY + '=';
|
2009-06-27 19:14:14 +00:00
|
|
|
|
2009-08-20 02:07:29 +00:00
|
|
|
private static final String ID_EQUALS = EmailContent.RECORD_ID + "=?";
|
|
|
|
|
2010-09-14 23:28:50 +00:00
|
|
|
private static final ContentValues CONTENT_VALUES_RESET_NEW_MESSAGE_COUNT;
|
2012-06-28 17:40:46 +00:00
|
|
|
private static final ContentValues EMPTY_CONTENT_VALUES = new ContentValues();
|
2010-09-14 23:28:50 +00:00
|
|
|
|
2010-10-14 02:04:46 +00:00
|
|
|
public static final String MESSAGE_URI_PARAMETER_MAILBOX_ID = "mailboxId";
|
|
|
|
|
2012-06-28 17:40:46 +00:00
|
|
|
// For undo handling
|
|
|
|
private int mLastSequence = -1;
|
|
|
|
private ArrayList<ContentProviderOperation> mLastSequenceOps =
|
|
|
|
new ArrayList<ContentProviderOperation>();
|
|
|
|
|
|
|
|
// Query parameter indicating the command came from UIProvider
|
|
|
|
private static final String IS_UIPROVIDER = "is_uiprovider";
|
|
|
|
|
2009-05-26 23:40:34 +00:00
|
|
|
static {
|
|
|
|
// Email URI matching table
|
|
|
|
UriMatcher matcher = sURIMatcher;
|
2009-06-27 19:14:14 +00:00
|
|
|
|
2009-05-26 23:40:34 +00:00
|
|
|
// All accounts
|
2011-02-11 23:05:17 +00:00
|
|
|
matcher.addURI(EmailContent.AUTHORITY, "account", ACCOUNT);
|
2009-05-26 23:40:34 +00:00
|
|
|
// A specific account
|
2009-07-30 18:41:31 +00:00
|
|
|
// insert into this URI causes a mailbox to be added to the account
|
2011-02-11 23:05:17 +00:00
|
|
|
matcher.addURI(EmailContent.AUTHORITY, "account/#", ACCOUNT_ID);
|
Improve EmailContent caching...
* Guarantee that up to 16 Account (with HostAuths), and Policy rows
are always cached. Also, 6 commonly used Mailboxes per Account
(Inbox, Outbox, Drafts, Sent, Trash, and Search)
* Precache these rows when EmailProvider starts up
* Ensure that newly added, precachable rows are cached when created
* Clean up some inefficient/wrong caching code
* Fix a commonly called method in NotificationManager in which we
load a single Mailbox row using selection vs withAppendedId
* Confirm that we don't read from the database in typical use and
heavy message loading
* Add a special URI for finding mailbox by type (using the cache)
* Add special-case code for EmailContent.count(Account.CONTENT_URI)
which is used in a number of places (including on the UI thread)
and whose value is easily determined
* Add a special URI to get the default account id
* Confirm that all unit tests work
The goal here is to be able to load all Account, HostAuth, Policy,
and Mailbox objects (by id) without worrying about disk access.
There will still be a single disk read for uncommon Mailbox reads,
but this should be considered acceptable.
Change-Id: Ibc9aa7acc73185e360b0b6f3053b90a985e97210
TODO: Unit tests
2011-06-19 01:03:11 +00:00
|
|
|
matcher.addURI(EmailContent.AUTHORITY, "account/default", ACCOUNT_DEFAULT_ID);
|
2012-06-28 17:40:46 +00:00
|
|
|
matcher.addURI(EmailContent.AUTHORITY, "accountCheck/#", ACCOUNT_CHECK);
|
2009-06-27 19:14:14 +00:00
|
|
|
|
2010-09-14 23:28:50 +00:00
|
|
|
// Special URI to reset the new message count. Only update works, and content values
|
|
|
|
// will be ignored.
|
2011-02-11 23:05:17 +00:00
|
|
|
matcher.addURI(EmailContent.AUTHORITY, "resetNewMessageCount",
|
|
|
|
ACCOUNT_RESET_NEW_COUNT);
|
|
|
|
matcher.addURI(EmailContent.AUTHORITY, "resetNewMessageCount/#",
|
|
|
|
ACCOUNT_RESET_NEW_COUNT_ID);
|
2010-09-14 23:28:50 +00:00
|
|
|
|
2009-05-26 23:40:34 +00:00
|
|
|
// All mailboxes
|
2011-02-11 23:05:17 +00:00
|
|
|
matcher.addURI(EmailContent.AUTHORITY, "mailbox", MAILBOX);
|
2009-05-26 23:40:34 +00:00
|
|
|
// A specific mailbox
|
|
|
|
// insert into this URI causes a message to be added to the mailbox
|
|
|
|
// ** NOTE For now, the accountKey must be set manually in the values!
|
2011-02-11 23:05:17 +00:00
|
|
|
matcher.addURI(EmailContent.AUTHORITY, "mailbox/#", MAILBOX_ID);
|
Improve EmailContent caching...
* Guarantee that up to 16 Account (with HostAuths), and Policy rows
are always cached. Also, 6 commonly used Mailboxes per Account
(Inbox, Outbox, Drafts, Sent, Trash, and Search)
* Precache these rows when EmailProvider starts up
* Ensure that newly added, precachable rows are cached when created
* Clean up some inefficient/wrong caching code
* Fix a commonly called method in NotificationManager in which we
load a single Mailbox row using selection vs withAppendedId
* Confirm that we don't read from the database in typical use and
heavy message loading
* Add a special URI for finding mailbox by type (using the cache)
* Add special-case code for EmailContent.count(Account.CONTENT_URI)
which is used in a number of places (including on the UI thread)
and whose value is easily determined
* Add a special URI to get the default account id
* Confirm that all unit tests work
The goal here is to be able to load all Account, HostAuth, Policy,
and Mailbox objects (by id) without worrying about disk access.
There will still be a single disk read for uncommon Mailbox reads,
but this should be considered acceptable.
Change-Id: Ibc9aa7acc73185e360b0b6f3053b90a985e97210
TODO: Unit tests
2011-06-19 01:03:11 +00:00
|
|
|
matcher.addURI(EmailContent.AUTHORITY, "mailboxIdFromAccountAndType/#/#",
|
|
|
|
MAILBOX_ID_FROM_ACCOUNT_AND_TYPE);
|
2012-06-28 17:40:46 +00:00
|
|
|
matcher.addURI(EmailContent.AUTHORITY, "mailboxNotification/#", MAILBOX_NOTIFICATION);
|
|
|
|
matcher.addURI(EmailContent.AUTHORITY, "mailboxMostRecentMessage/#",
|
|
|
|
MAILBOX_MOST_RECENT_MESSAGE);
|
|
|
|
|
2009-05-26 23:40:34 +00:00
|
|
|
// All messages
|
2011-02-11 23:05:17 +00:00
|
|
|
matcher.addURI(EmailContent.AUTHORITY, "message", MESSAGE);
|
2009-05-26 23:40:34 +00:00
|
|
|
// A specific message
|
2009-07-30 18:41:31 +00:00
|
|
|
// insert into this URI causes an attachment to be added to the message
|
2011-02-11 23:05:17 +00:00
|
|
|
matcher.addURI(EmailContent.AUTHORITY, "message/#", MESSAGE_ID);
|
2009-06-27 19:14:14 +00:00
|
|
|
|
2009-05-26 23:40:34 +00:00
|
|
|
// A specific attachment
|
2011-02-11 23:05:17 +00:00
|
|
|
matcher.addURI(EmailContent.AUTHORITY, "attachment", ATTACHMENT);
|
2009-05-26 23:40:34 +00:00
|
|
|
// A specific attachment (the header information)
|
2011-02-11 23:05:17 +00:00
|
|
|
matcher.addURI(EmailContent.AUTHORITY, "attachment/#", ATTACHMENT_ID);
|
2009-07-16 23:03:40 +00:00
|
|
|
// The attachments of a specific message (query only) (insert & delete TBD)
|
2011-02-11 23:05:17 +00:00
|
|
|
matcher.addURI(EmailContent.AUTHORITY, "attachment/message/#",
|
|
|
|
ATTACHMENTS_MESSAGE_ID);
|
2009-05-26 23:40:34 +00:00
|
|
|
|
|
|
|
// All mail bodies
|
2011-02-11 23:05:17 +00:00
|
|
|
matcher.addURI(EmailContent.AUTHORITY, "body", BODY);
|
2009-05-26 23:40:34 +00:00
|
|
|
// A specific mail body
|
2011-02-11 23:05:17 +00:00
|
|
|
matcher.addURI(EmailContent.AUTHORITY, "body/#", BODY_ID);
|
2009-06-27 19:14:14 +00:00
|
|
|
|
2009-07-16 23:03:40 +00:00
|
|
|
// All hostauth records
|
2011-02-11 23:05:17 +00:00
|
|
|
matcher.addURI(EmailContent.AUTHORITY, "hostauth", HOSTAUTH);
|
2009-07-16 23:03:40 +00:00
|
|
|
// A specific hostauth
|
2011-02-11 23:05:17 +00:00
|
|
|
matcher.addURI(EmailContent.AUTHORITY, "hostauth/#", HOSTAUTH_ID);
|
2009-06-27 19:14:14 +00:00
|
|
|
|
2009-08-20 02:07:29 +00:00
|
|
|
// Atomically a constant value to a particular field of a mailbox/account
|
2011-02-11 23:05:17 +00:00
|
|
|
matcher.addURI(EmailContent.AUTHORITY, "mailboxIdAddToField/#",
|
|
|
|
MAILBOX_ID_ADD_TO_FIELD);
|
|
|
|
matcher.addURI(EmailContent.AUTHORITY, "accountIdAddToField/#",
|
|
|
|
ACCOUNT_ID_ADD_TO_FIELD);
|
2009-08-20 02:07:29 +00:00
|
|
|
|
2009-06-27 19:14:14 +00:00
|
|
|
/**
|
|
|
|
* THIS URI HAS SPECIAL SEMANTICS
|
2009-08-20 02:07:29 +00:00
|
|
|
* ITS USE IS INTENDED FOR THE UI APPLICATION TO MARK CHANGES THAT NEED TO BE SYNCED BACK
|
2009-06-27 19:14:14 +00:00
|
|
|
* TO A SERVER VIA A SYNC ADAPTER
|
|
|
|
*/
|
2011-02-11 23:05:17 +00:00
|
|
|
matcher.addURI(EmailContent.AUTHORITY, "syncedMessage/#", SYNCED_MESSAGE_ID);
|
2012-06-29 16:42:05 +00:00
|
|
|
matcher.addURI(EmailContent.AUTHORITY, "syncedMessageSelection",
|
|
|
|
SYNCED_MESSAGE_SELECTION);
|
2009-06-27 19:14:14 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* THE URIs BELOW THIS POINT ARE INTENDED TO BE USED BY SYNC ADAPTERS ONLY
|
|
|
|
* THEY REFER TO DATA CREATED AND MAINTAINED BY CALLS TO THE SYNCED_MESSAGE_ID URI
|
|
|
|
* BY THE UI APPLICATION
|
|
|
|
*/
|
|
|
|
// All deleted messages
|
2011-02-11 23:05:17 +00:00
|
|
|
matcher.addURI(EmailContent.AUTHORITY, "deletedMessage", DELETED_MESSAGE);
|
2009-06-27 19:14:14 +00:00
|
|
|
// A specific deleted message
|
2011-02-11 23:05:17 +00:00
|
|
|
matcher.addURI(EmailContent.AUTHORITY, "deletedMessage/#", DELETED_MESSAGE_ID);
|
2009-06-27 19:14:14 +00:00
|
|
|
|
|
|
|
// All updated messages
|
2011-02-11 23:05:17 +00:00
|
|
|
matcher.addURI(EmailContent.AUTHORITY, "updatedMessage", UPDATED_MESSAGE);
|
2009-06-27 19:14:14 +00:00
|
|
|
// A specific updated message
|
2011-02-11 23:05:17 +00:00
|
|
|
matcher.addURI(EmailContent.AUTHORITY, "updatedMessage/#", UPDATED_MESSAGE_ID);
|
2010-09-14 23:28:50 +00:00
|
|
|
|
|
|
|
CONTENT_VALUES_RESET_NEW_MESSAGE_COUNT = new ContentValues();
|
|
|
|
CONTENT_VALUES_RESET_NEW_MESSAGE_COUNT.put(Account.NEW_MESSAGE_COUNT, 0);
|
2011-04-28 00:12:06 +00:00
|
|
|
|
|
|
|
matcher.addURI(EmailContent.AUTHORITY, "policy", POLICY);
|
|
|
|
matcher.addURI(EmailContent.AUTHORITY, "policy/#", POLICY_ID);
|
2011-06-01 17:09:26 +00:00
|
|
|
|
|
|
|
// All quick responses
|
|
|
|
matcher.addURI(EmailContent.AUTHORITY, "quickresponse", QUICK_RESPONSE);
|
|
|
|
// A specific quick response
|
|
|
|
matcher.addURI(EmailContent.AUTHORITY, "quickresponse/#", QUICK_RESPONSE_ID);
|
|
|
|
// All quick responses associated with a particular account id
|
|
|
|
matcher.addURI(EmailContent.AUTHORITY, "quickresponse/account/#",
|
|
|
|
QUICK_RESPONSE_ACCOUNT_ID);
|
2012-06-28 17:40:46 +00:00
|
|
|
|
|
|
|
matcher.addURI(EmailContent.AUTHORITY, "uifolders/#", UI_FOLDERS);
|
|
|
|
matcher.addURI(EmailContent.AUTHORITY, "uiallfolders/#", UI_ALL_FOLDERS);
|
|
|
|
matcher.addURI(EmailContent.AUTHORITY, "uisubfolders/#", UI_SUBFOLDERS);
|
|
|
|
matcher.addURI(EmailContent.AUTHORITY, "uimessages/#", UI_MESSAGES);
|
|
|
|
matcher.addURI(EmailContent.AUTHORITY, "uimessage/#", UI_MESSAGE);
|
|
|
|
matcher.addURI(EmailContent.AUTHORITY, "uisendmail/#", UI_SENDMAIL);
|
|
|
|
matcher.addURI(EmailContent.AUTHORITY, "uiundo", UI_UNDO);
|
|
|
|
matcher.addURI(EmailContent.AUTHORITY, "uisavedraft/#", UI_SAVEDRAFT);
|
|
|
|
matcher.addURI(EmailContent.AUTHORITY, "uiupdatedraft/#", UI_UPDATEDRAFT);
|
|
|
|
matcher.addURI(EmailContent.AUTHORITY, "uisenddraft/#", UI_SENDDRAFT);
|
|
|
|
matcher.addURI(EmailContent.AUTHORITY, "uirefresh/#", UI_FOLDER_REFRESH);
|
|
|
|
matcher.addURI(EmailContent.AUTHORITY, "uifolder/#", UI_FOLDER);
|
|
|
|
matcher.addURI(EmailContent.AUTHORITY, "uiaccount/#", UI_ACCOUNT);
|
|
|
|
matcher.addURI(EmailContent.AUTHORITY, "uiaccts", UI_ACCTS);
|
|
|
|
matcher.addURI(EmailContent.AUTHORITY, "uiattachments/#", UI_ATTACHMENTS);
|
|
|
|
matcher.addURI(EmailContent.AUTHORITY, "uiattachment/#", UI_ATTACHMENT);
|
|
|
|
matcher.addURI(EmailContent.AUTHORITY, "uisearch/#", UI_SEARCH);
|
|
|
|
matcher.addURI(EmailContent.AUTHORITY, "uiaccountdata/#", UI_ACCOUNT_DATA);
|
|
|
|
matcher.addURI(EmailContent.AUTHORITY, "uiloadmore/#", UI_FOLDER_LOAD_MORE);
|
|
|
|
matcher.addURI(EmailContent.AUTHORITY, "uiconversation/#", UI_CONVERSATION);
|
|
|
|
matcher.addURI(EmailContent.AUTHORITY, "uirecentfolders/#", UI_RECENT_FOLDERS);
|
|
|
|
matcher.addURI(EmailContent.AUTHORITY, "uidefaultrecentfolders/#",
|
|
|
|
UI_DEFAULT_RECENT_FOLDERS);
|
2012-06-29 16:42:05 +00:00
|
|
|
matcher.addURI(EmailContent.AUTHORITY, "pickTrashFolder/#", ACCOUNT_PICK_TRASH_FOLDER);
|
2009-05-26 23:40:34 +00:00
|
|
|
}
|
|
|
|
|
2010-12-16 20:28:16 +00:00
|
|
|
/**
|
|
|
|
* Wrap the UriMatcher call so we can throw a runtime exception if an unknown Uri is passed in
|
|
|
|
* @param uri the Uri to match
|
|
|
|
* @return the match value
|
|
|
|
*/
|
|
|
|
private static int findMatch(Uri uri, String methodName) {
|
|
|
|
int match = sURIMatcher.match(uri);
|
|
|
|
if (match < 0) {
|
2010-12-30 04:51:16 +00:00
|
|
|
throw new IllegalArgumentException("Unknown uri: " + uri);
|
2011-05-13 18:20:04 +00:00
|
|
|
} else if (Logging.LOGD) {
|
2010-12-16 20:28:16 +00:00
|
|
|
Log.v(TAG, methodName + ": uri=" + uri + ", match is " + match);
|
|
|
|
}
|
|
|
|
return match;
|
|
|
|
}
|
|
|
|
|
2012-06-28 17:40:46 +00:00
|
|
|
private SQLiteDatabase mDatabase;
|
|
|
|
private SQLiteDatabase mBodyDatabase;
|
2012-03-09 19:52:45 +00:00
|
|
|
|
2012-06-28 17:40:46 +00:00
|
|
|
public static Uri uiUri(String type, long id) {
|
|
|
|
return Uri.parse(uiUriString(type, id));
|
2012-03-09 19:52:45 +00:00
|
|
|
}
|
|
|
|
|
2012-06-28 17:40:46 +00:00
|
|
|
/**
|
|
|
|
* Creates a URI string from a database ID (guaranteed to be unique).
|
|
|
|
* @param type of the resource: uifolder, message, etc.
|
|
|
|
* @param id the id of the resource.
|
|
|
|
* @return
|
|
|
|
*/
|
|
|
|
public static String uiUriString(String type, long id) {
|
|
|
|
return "content://" + EmailContent.AUTHORITY + "/" + type + ((id == -1) ? "" : ("/" + id));
|
2012-04-25 17:26:46 +00:00
|
|
|
}
|
|
|
|
|
2011-06-30 23:03:36 +00:00
|
|
|
/**
|
|
|
|
* Orphan record deletion utility. Generates a sqlite statement like:
|
|
|
|
* delete from <table> where <column> not in (select <foreignColumn> from <foreignTable>)
|
|
|
|
* @param db the EmailProvider database
|
|
|
|
* @param table the table whose orphans are to be removed
|
|
|
|
* @param column the column deletion will be based on
|
|
|
|
* @param foreignColumn the column in the foreign table whose absence will trigger the deletion
|
|
|
|
* @param foreignTable the foreign table
|
|
|
|
*/
|
|
|
|
@VisibleForTesting
|
|
|
|
void deleteUnlinked(SQLiteDatabase db, String table, String column, String foreignColumn,
|
|
|
|
String foreignTable) {
|
|
|
|
int count = db.delete(table, column + " not in (select " + foreignColumn + " from " +
|
|
|
|
foreignTable + ")", null);
|
|
|
|
if (count > 0) {
|
|
|
|
Log.w(TAG, "Found " + count + " orphaned row(s) in " + table);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-06-21 01:10:10 +00:00
|
|
|
@VisibleForTesting
|
|
|
|
synchronized SQLiteDatabase getDatabase(Context context) {
|
2009-11-19 01:11:33 +00:00
|
|
|
// Always return the cached database, if we've got one
|
|
|
|
if (mDatabase != null) {
|
2009-05-26 23:40:34 +00:00
|
|
|
return mDatabase;
|
2009-06-15 21:40:06 +00:00
|
|
|
}
|
2009-11-19 01:11:33 +00:00
|
|
|
|
|
|
|
// Whenever we create or re-cache the databases, make sure that we haven't lost one
|
|
|
|
// to corruption
|
|
|
|
checkDatabases();
|
|
|
|
|
2012-06-28 17:40:46 +00:00
|
|
|
DBHelper.DatabaseHelper helper = new DBHelper.DatabaseHelper(context, DATABASE_NAME);
|
2009-05-26 23:40:34 +00:00
|
|
|
mDatabase = helper.getWritableDatabase();
|
2012-06-28 17:40:46 +00:00
|
|
|
DBHelper.BodyDatabaseHelper bodyHelper =
|
|
|
|
new DBHelper.BodyDatabaseHelper(context, BODY_DATABASE_NAME);
|
2011-06-21 02:57:56 +00:00
|
|
|
mBodyDatabase = bodyHelper.getWritableDatabase();
|
|
|
|
if (mBodyDatabase != null) {
|
|
|
|
String bodyFileName = mBodyDatabase.getPath();
|
|
|
|
mDatabase.execSQL("attach \"" + bodyFileName + "\" as BodyDatabase");
|
2009-06-15 21:40:06 +00:00
|
|
|
}
|
2009-10-13 23:25:00 +00:00
|
|
|
|
2011-06-21 02:57:56 +00:00
|
|
|
// Restore accounts if the database is corrupted...
|
|
|
|
restoreIfNeeded(context, mDatabase);
|
2009-10-13 23:25:00 +00:00
|
|
|
// Check for any orphaned Messages in the updated/deleted tables
|
2011-06-30 23:03:36 +00:00
|
|
|
deleteMessageOrphans(mDatabase, Message.UPDATED_TABLE_NAME);
|
|
|
|
deleteMessageOrphans(mDatabase, Message.DELETED_TABLE_NAME);
|
|
|
|
// Delete orphaned mailboxes/messages/policies (account no longer exists)
|
|
|
|
deleteUnlinked(mDatabase, Mailbox.TABLE_NAME, MailboxColumns.ACCOUNT_KEY, AccountColumns.ID,
|
|
|
|
Account.TABLE_NAME);
|
|
|
|
deleteUnlinked(mDatabase, Message.TABLE_NAME, MessageColumns.ACCOUNT_KEY, AccountColumns.ID,
|
|
|
|
Account.TABLE_NAME);
|
|
|
|
deleteUnlinked(mDatabase, Policy.TABLE_NAME, PolicyColumns.ID, AccountColumns.POLICY_KEY,
|
|
|
|
Account.TABLE_NAME);
|
2012-06-28 17:40:46 +00:00
|
|
|
initUiProvider();
|
Improve EmailContent caching...
* Guarantee that up to 16 Account (with HostAuths), and Policy rows
are always cached. Also, 6 commonly used Mailboxes per Account
(Inbox, Outbox, Drafts, Sent, Trash, and Search)
* Precache these rows when EmailProvider starts up
* Ensure that newly added, precachable rows are cached when created
* Clean up some inefficient/wrong caching code
* Fix a commonly called method in NotificationManager in which we
load a single Mailbox row using selection vs withAppendedId
* Confirm that we don't read from the database in typical use and
heavy message loading
* Add a special URI for finding mailbox by type (using the cache)
* Add special-case code for EmailContent.count(Account.CONTENT_URI)
which is used in a number of places (including on the UI thread)
and whose value is easily determined
* Add a special URI to get the default account id
* Confirm that all unit tests work
The goal here is to be able to load all Account, HostAuth, Policy,
and Mailbox objects (by id) without worrying about disk access.
There will still be a single disk read for uncommon Mailbox reads,
but this should be considered acceptable.
Change-Id: Ibc9aa7acc73185e360b0b6f3053b90a985e97210
TODO: Unit tests
2011-06-19 01:03:11 +00:00
|
|
|
preCacheData();
|
2009-05-26 23:40:34 +00:00
|
|
|
return mDatabase;
|
|
|
|
}
|
|
|
|
|
2012-06-28 17:40:46 +00:00
|
|
|
/**
|
|
|
|
* Perform startup actions related to UI
|
|
|
|
*/
|
|
|
|
private void initUiProvider() {
|
|
|
|
// Clear mailbox sync status
|
|
|
|
mDatabase.execSQL("update " + Mailbox.TABLE_NAME + " set " + MailboxColumns.UI_SYNC_STATUS +
|
|
|
|
"=" + UIProvider.SyncStatus.NO_SYNC);
|
|
|
|
}
|
|
|
|
|
Improve EmailContent caching...
* Guarantee that up to 16 Account (with HostAuths), and Policy rows
are always cached. Also, 6 commonly used Mailboxes per Account
(Inbox, Outbox, Drafts, Sent, Trash, and Search)
* Precache these rows when EmailProvider starts up
* Ensure that newly added, precachable rows are cached when created
* Clean up some inefficient/wrong caching code
* Fix a commonly called method in NotificationManager in which we
load a single Mailbox row using selection vs withAppendedId
* Confirm that we don't read from the database in typical use and
heavy message loading
* Add a special URI for finding mailbox by type (using the cache)
* Add special-case code for EmailContent.count(Account.CONTENT_URI)
which is used in a number of places (including on the UI thread)
and whose value is easily determined
* Add a special URI to get the default account id
* Confirm that all unit tests work
The goal here is to be able to load all Account, HostAuth, Policy,
and Mailbox objects (by id) without worrying about disk access.
There will still be a single disk read for uncommon Mailbox reads,
but this should be considered acceptable.
Change-Id: Ibc9aa7acc73185e360b0b6f3053b90a985e97210
TODO: Unit tests
2011-06-19 01:03:11 +00:00
|
|
|
/**
|
|
|
|
* Pre-cache all of the items in a given table meeting the selection criteria
|
|
|
|
* @param tableUri the table uri
|
|
|
|
* @param baseProjection the base projection of that table
|
|
|
|
* @param selection the selection criteria
|
|
|
|
*/
|
|
|
|
private void preCacheTable(Uri tableUri, String[] baseProjection, String selection) {
|
|
|
|
Cursor c = query(tableUri, EmailContent.ID_PROJECTION, selection, null, null);
|
|
|
|
try {
|
|
|
|
while (c.moveToNext()) {
|
|
|
|
long id = c.getLong(EmailContent.ID_PROJECTION_COLUMN);
|
|
|
|
Cursor cachedCursor = query(ContentUris.withAppendedId(
|
|
|
|
tableUri, id), baseProjection, null, null, null);
|
|
|
|
if (cachedCursor != null) {
|
|
|
|
// For accounts, create a mailbox type map entry (if necessary)
|
|
|
|
if (tableUri == Account.CONTENT_URI) {
|
|
|
|
getOrCreateAccountMailboxTypeMap(id);
|
|
|
|
}
|
|
|
|
cachedCursor.close();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} finally {
|
|
|
|
c.close();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-06-28 03:11:24 +00:00
|
|
|
private final HashMap<Long, HashMap<Integer, Long>> mMailboxTypeMap =
|
Improve EmailContent caching...
* Guarantee that up to 16 Account (with HostAuths), and Policy rows
are always cached. Also, 6 commonly used Mailboxes per Account
(Inbox, Outbox, Drafts, Sent, Trash, and Search)
* Precache these rows when EmailProvider starts up
* Ensure that newly added, precachable rows are cached when created
* Clean up some inefficient/wrong caching code
* Fix a commonly called method in NotificationManager in which we
load a single Mailbox row using selection vs withAppendedId
* Confirm that we don't read from the database in typical use and
heavy message loading
* Add a special URI for finding mailbox by type (using the cache)
* Add special-case code for EmailContent.count(Account.CONTENT_URI)
which is used in a number of places (including on the UI thread)
and whose value is easily determined
* Add a special URI to get the default account id
* Confirm that all unit tests work
The goal here is to be able to load all Account, HostAuth, Policy,
and Mailbox objects (by id) without worrying about disk access.
There will still be a single disk read for uncommon Mailbox reads,
but this should be considered acceptable.
Change-Id: Ibc9aa7acc73185e360b0b6f3053b90a985e97210
TODO: Unit tests
2011-06-19 01:03:11 +00:00
|
|
|
new HashMap<Long, HashMap<Integer, Long>>();
|
|
|
|
|
2011-06-28 03:11:24 +00:00
|
|
|
private HashMap<Integer, Long> getOrCreateAccountMailboxTypeMap(long accountId) {
|
|
|
|
synchronized(mMailboxTypeMap) {
|
|
|
|
HashMap<Integer, Long> accountMailboxTypeMap = mMailboxTypeMap.get(accountId);
|
|
|
|
if (accountMailboxTypeMap == null) {
|
|
|
|
accountMailboxTypeMap = new HashMap<Integer, Long>();
|
|
|
|
mMailboxTypeMap.put(accountId, accountMailboxTypeMap);
|
|
|
|
}
|
|
|
|
return accountMailboxTypeMap;
|
Improve EmailContent caching...
* Guarantee that up to 16 Account (with HostAuths), and Policy rows
are always cached. Also, 6 commonly used Mailboxes per Account
(Inbox, Outbox, Drafts, Sent, Trash, and Search)
* Precache these rows when EmailProvider starts up
* Ensure that newly added, precachable rows are cached when created
* Clean up some inefficient/wrong caching code
* Fix a commonly called method in NotificationManager in which we
load a single Mailbox row using selection vs withAppendedId
* Confirm that we don't read from the database in typical use and
heavy message loading
* Add a special URI for finding mailbox by type (using the cache)
* Add special-case code for EmailContent.count(Account.CONTENT_URI)
which is used in a number of places (including on the UI thread)
and whose value is easily determined
* Add a special URI to get the default account id
* Confirm that all unit tests work
The goal here is to be able to load all Account, HostAuth, Policy,
and Mailbox objects (by id) without worrying about disk access.
There will still be a single disk read for uncommon Mailbox reads,
but this should be considered acceptable.
Change-Id: Ibc9aa7acc73185e360b0b6f3053b90a985e97210
TODO: Unit tests
2011-06-19 01:03:11 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-06-28 03:11:24 +00:00
|
|
|
private void addToMailboxTypeMap(Cursor c) {
|
Improve EmailContent caching...
* Guarantee that up to 16 Account (with HostAuths), and Policy rows
are always cached. Also, 6 commonly used Mailboxes per Account
(Inbox, Outbox, Drafts, Sent, Trash, and Search)
* Precache these rows when EmailProvider starts up
* Ensure that newly added, precachable rows are cached when created
* Clean up some inefficient/wrong caching code
* Fix a commonly called method in NotificationManager in which we
load a single Mailbox row using selection vs withAppendedId
* Confirm that we don't read from the database in typical use and
heavy message loading
* Add a special URI for finding mailbox by type (using the cache)
* Add special-case code for EmailContent.count(Account.CONTENT_URI)
which is used in a number of places (including on the UI thread)
and whose value is easily determined
* Add a special URI to get the default account id
* Confirm that all unit tests work
The goal here is to be able to load all Account, HostAuth, Policy,
and Mailbox objects (by id) without worrying about disk access.
There will still be a single disk read for uncommon Mailbox reads,
but this should be considered acceptable.
Change-Id: Ibc9aa7acc73185e360b0b6f3053b90a985e97210
TODO: Unit tests
2011-06-19 01:03:11 +00:00
|
|
|
long accountId = c.getLong(Mailbox.CONTENT_ACCOUNT_KEY_COLUMN);
|
|
|
|
int type = c.getInt(Mailbox.CONTENT_TYPE_COLUMN);
|
2011-06-28 03:11:24 +00:00
|
|
|
synchronized(mMailboxTypeMap) {
|
|
|
|
HashMap<Integer, Long> accountMailboxTypeMap =
|
|
|
|
getOrCreateAccountMailboxTypeMap(accountId);
|
|
|
|
accountMailboxTypeMap.put(type, c.getLong(Mailbox.CONTENT_ID_COLUMN));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private long getMailboxIdFromMailboxTypeMap(long accountId, int type) {
|
|
|
|
synchronized(mMailboxTypeMap) {
|
|
|
|
HashMap<Integer, Long> accountMap = mMailboxTypeMap.get(accountId);
|
2011-06-28 17:58:22 +00:00
|
|
|
Long mailboxId = null;
|
2011-06-28 03:11:24 +00:00
|
|
|
if (accountMap != null) {
|
|
|
|
mailboxId = accountMap.get(type);
|
|
|
|
}
|
2011-06-28 17:58:22 +00:00
|
|
|
if (mailboxId == null) return Mailbox.NO_MAILBOX;
|
2011-06-28 03:11:24 +00:00
|
|
|
return mailboxId;
|
|
|
|
}
|
Improve EmailContent caching...
* Guarantee that up to 16 Account (with HostAuths), and Policy rows
are always cached. Also, 6 commonly used Mailboxes per Account
(Inbox, Outbox, Drafts, Sent, Trash, and Search)
* Precache these rows when EmailProvider starts up
* Ensure that newly added, precachable rows are cached when created
* Clean up some inefficient/wrong caching code
* Fix a commonly called method in NotificationManager in which we
load a single Mailbox row using selection vs withAppendedId
* Confirm that we don't read from the database in typical use and
heavy message loading
* Add a special URI for finding mailbox by type (using the cache)
* Add special-case code for EmailContent.count(Account.CONTENT_URI)
which is used in a number of places (including on the UI thread)
and whose value is easily determined
* Add a special URI to get the default account id
* Confirm that all unit tests work
The goal here is to be able to load all Account, HostAuth, Policy,
and Mailbox objects (by id) without worrying about disk access.
There will still be a single disk read for uncommon Mailbox reads,
but this should be considered acceptable.
Change-Id: Ibc9aa7acc73185e360b0b6f3053b90a985e97210
TODO: Unit tests
2011-06-19 01:03:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
private void preCacheData() {
|
2011-06-28 03:11:24 +00:00
|
|
|
synchronized(mMailboxTypeMap) {
|
|
|
|
mMailboxTypeMap.clear();
|
|
|
|
|
|
|
|
// Pre-cache accounts, host auth's, policies, and special mailboxes
|
|
|
|
preCacheTable(Account.CONTENT_URI, Account.CONTENT_PROJECTION, null);
|
|
|
|
preCacheTable(HostAuth.CONTENT_URI, HostAuth.CONTENT_PROJECTION, null);
|
|
|
|
preCacheTable(Policy.CONTENT_URI, Policy.CONTENT_PROJECTION, null);
|
|
|
|
preCacheTable(Mailbox.CONTENT_URI, Mailbox.CONTENT_PROJECTION,
|
|
|
|
MAILBOX_PRE_CACHE_SELECTION);
|
|
|
|
|
|
|
|
// Create a map from account,type to a mailbox
|
|
|
|
Map<String, Cursor> snapshot = mCacheMailbox.getSnapshot();
|
|
|
|
Collection<Cursor> values = snapshot.values();
|
|
|
|
if (values != null) {
|
|
|
|
for (Cursor c: values) {
|
|
|
|
if (c.moveToFirst()) {
|
|
|
|
addToMailboxTypeMap(c);
|
|
|
|
}
|
|
|
|
}
|
Improve EmailContent caching...
* Guarantee that up to 16 Account (with HostAuths), and Policy rows
are always cached. Also, 6 commonly used Mailboxes per Account
(Inbox, Outbox, Drafts, Sent, Trash, and Search)
* Precache these rows when EmailProvider starts up
* Ensure that newly added, precachable rows are cached when created
* Clean up some inefficient/wrong caching code
* Fix a commonly called method in NotificationManager in which we
load a single Mailbox row using selection vs withAppendedId
* Confirm that we don't read from the database in typical use and
heavy message loading
* Add a special URI for finding mailbox by type (using the cache)
* Add special-case code for EmailContent.count(Account.CONTENT_URI)
which is used in a number of places (including on the UI thread)
and whose value is easily determined
* Add a special URI to get the default account id
* Confirm that all unit tests work
The goal here is to be able to load all Account, HostAuth, Policy,
and Mailbox objects (by id) without worrying about disk access.
There will still be a single disk read for uncommon Mailbox reads,
but this should be considered acceptable.
Change-Id: Ibc9aa7acc73185e360b0b6f3053b90a985e97210
TODO: Unit tests
2011-06-19 01:03:11 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-10-13 23:25:00 +00:00
|
|
|
/*package*/ static SQLiteDatabase getReadableDatabase(Context context) {
|
2012-06-28 17:40:46 +00:00
|
|
|
DBHelper.DatabaseHelper helper = new DBHelper.DatabaseHelper(context, DATABASE_NAME);
|
2009-10-13 23:25:00 +00:00
|
|
|
return helper.getReadableDatabase();
|
|
|
|
}
|
|
|
|
|
2011-06-21 01:10:10 +00:00
|
|
|
/**
|
|
|
|
* Restore user Account and HostAuth data from our backup database
|
|
|
|
*/
|
|
|
|
public static void restoreIfNeeded(Context context, SQLiteDatabase mainDatabase) {
|
2012-06-28 17:40:46 +00:00
|
|
|
if (MailActivityEmail.DEBUG) {
|
2011-06-21 01:10:10 +00:00
|
|
|
Log.w(TAG, "restoreIfNeeded...");
|
|
|
|
}
|
|
|
|
// Check for legacy backup
|
|
|
|
String legacyBackup = Preferences.getLegacyBackupPreference(context);
|
|
|
|
// If there's a legacy backup, create a new-style backup and delete the legacy backup
|
|
|
|
// In the 1:1000000000 chance that the user gets an app update just as his database becomes
|
|
|
|
// corrupt, oh well...
|
|
|
|
if (!TextUtils.isEmpty(legacyBackup)) {
|
|
|
|
backupAccounts(context, mainDatabase);
|
|
|
|
Preferences.clearLegacyBackupPreference(context);
|
|
|
|
Log.w(TAG, "Created new EmailProvider backup database");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// If we have accounts, we're done
|
|
|
|
Cursor c = mainDatabase.query(Account.TABLE_NAME, EmailContent.ID_PROJECTION, null, null,
|
|
|
|
null, null, null);
|
2012-06-28 17:40:46 +00:00
|
|
|
try {
|
|
|
|
if (c.moveToFirst()) {
|
|
|
|
if (MailActivityEmail.DEBUG) {
|
|
|
|
Log.w(TAG, "restoreIfNeeded: Account exists.");
|
|
|
|
}
|
|
|
|
return; // At least one account exists.
|
2011-06-21 01:10:10 +00:00
|
|
|
}
|
2012-06-28 17:40:46 +00:00
|
|
|
} finally {
|
|
|
|
c.close();
|
2011-06-21 01:10:10 +00:00
|
|
|
}
|
2012-06-28 17:40:46 +00:00
|
|
|
|
2011-06-21 01:10:10 +00:00
|
|
|
restoreAccounts(context, mainDatabase);
|
|
|
|
}
|
|
|
|
|
2010-08-20 21:56:44 +00:00
|
|
|
/** {@inheritDoc} */
|
|
|
|
@Override
|
|
|
|
public void shutdown() {
|
|
|
|
if (mDatabase != null) {
|
|
|
|
mDatabase.close();
|
|
|
|
mDatabase = null;
|
|
|
|
}
|
|
|
|
if (mBodyDatabase != null) {
|
|
|
|
mBodyDatabase.close();
|
|
|
|
mBodyDatabase = null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-06-30 23:03:36 +00:00
|
|
|
/*package*/ static void deleteMessageOrphans(SQLiteDatabase database, String tableName) {
|
2009-10-13 23:25:00 +00:00
|
|
|
if (database != null) {
|
|
|
|
// We'll look at all of the items in the table; there won't be many typically
|
|
|
|
Cursor c = database.query(tableName, ORPHANS_PROJECTION, null, null, null, null, null);
|
|
|
|
// Usually, there will be nothing in these tables, so make a quick check
|
|
|
|
try {
|
|
|
|
if (c.getCount() == 0) return;
|
|
|
|
ArrayList<Long> foundMailboxes = new ArrayList<Long>();
|
|
|
|
ArrayList<Long> notFoundMailboxes = new ArrayList<Long>();
|
|
|
|
ArrayList<Long> deleteList = new ArrayList<Long>();
|
|
|
|
String[] bindArray = new String[1];
|
|
|
|
while (c.moveToNext()) {
|
|
|
|
// Get the mailbox key and see if we've already found this mailbox
|
|
|
|
// If so, we're fine
|
|
|
|
long mailboxId = c.getLong(ORPHANS_MAILBOX_KEY);
|
|
|
|
// If we already know this mailbox doesn't exist, mark the message for deletion
|
|
|
|
if (notFoundMailboxes.contains(mailboxId)) {
|
|
|
|
deleteList.add(c.getLong(ORPHANS_ID));
|
|
|
|
// If we don't know about this mailbox, we'll try to find it
|
|
|
|
} else if (!foundMailboxes.contains(mailboxId)) {
|
|
|
|
bindArray[0] = Long.toString(mailboxId);
|
|
|
|
Cursor boxCursor = database.query(Mailbox.TABLE_NAME,
|
|
|
|
Mailbox.ID_PROJECTION, WHERE_ID, bindArray, null, null, null);
|
|
|
|
try {
|
|
|
|
// If it exists, we'll add it to the "found" mailboxes
|
|
|
|
if (boxCursor.moveToFirst()) {
|
|
|
|
foundMailboxes.add(mailboxId);
|
|
|
|
// Otherwise, we'll add to "not found" and mark the message for deletion
|
|
|
|
} else {
|
|
|
|
notFoundMailboxes.add(mailboxId);
|
|
|
|
deleteList.add(c.getLong(ORPHANS_ID));
|
|
|
|
}
|
|
|
|
} finally {
|
|
|
|
boxCursor.close();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Now, delete the orphan messages
|
|
|
|
for (long messageId: deleteList) {
|
|
|
|
bindArray[0] = Long.toString(messageId);
|
|
|
|
database.delete(tableName, WHERE_ID, bindArray);
|
|
|
|
}
|
|
|
|
} finally {
|
|
|
|
c.close();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-05-26 23:40:34 +00:00
|
|
|
@Override
|
|
|
|
public int delete(Uri uri, String selection, String[] selectionArgs) {
|
2010-12-16 20:28:16 +00:00
|
|
|
final int match = findMatch(uri, "delete");
|
2009-06-15 21:40:06 +00:00
|
|
|
Context context = getContext();
|
2009-08-06 00:31:50 +00:00
|
|
|
// Pick the correct database for this operation
|
|
|
|
// If we're in a transaction already (which would happen during applyBatch), then the
|
|
|
|
// body database is already attached to the email database and any attempt to use the
|
|
|
|
// body database directly will result in a SQLiteException (the database is locked)
|
2009-08-18 18:44:27 +00:00
|
|
|
SQLiteDatabase db = getDatabase(context);
|
2009-05-26 23:40:34 +00:00
|
|
|
int table = match >> BASE_SHIFT;
|
2009-06-16 19:03:45 +00:00
|
|
|
String id = "0";
|
2009-08-06 00:31:50 +00:00
|
|
|
boolean messageDeletion = false;
|
2010-09-23 16:19:44 +00:00
|
|
|
ContentResolver resolver = context.getContentResolver();
|
2009-06-27 19:14:14 +00:00
|
|
|
|
Improve EmailContent caching...
* Guarantee that up to 16 Account (with HostAuths), and Policy rows
are always cached. Also, 6 commonly used Mailboxes per Account
(Inbox, Outbox, Drafts, Sent, Trash, and Search)
* Precache these rows when EmailProvider starts up
* Ensure that newly added, precachable rows are cached when created
* Clean up some inefficient/wrong caching code
* Fix a commonly called method in NotificationManager in which we
load a single Mailbox row using selection vs withAppendedId
* Confirm that we don't read from the database in typical use and
heavy message loading
* Add a special URI for finding mailbox by type (using the cache)
* Add special-case code for EmailContent.count(Account.CONTENT_URI)
which is used in a number of places (including on the UI thread)
and whose value is easily determined
* Add a special URI to get the default account id
* Confirm that all unit tests work
The goal here is to be able to load all Account, HostAuth, Policy,
and Mailbox objects (by id) without worrying about disk access.
There will still be a single disk read for uncommon Mailbox reads,
but this should be considered acceptable.
Change-Id: Ibc9aa7acc73185e360b0b6f3053b90a985e97210
TODO: Unit tests
2011-06-19 01:03:11 +00:00
|
|
|
ContentCache cache = mContentCaches[table];
|
2010-10-27 23:50:54 +00:00
|
|
|
String tableName = TABLE_NAMES[table];
|
2009-07-05 19:54:49 +00:00
|
|
|
int result = -1;
|
2009-06-27 19:14:14 +00:00
|
|
|
|
2009-06-16 19:03:45 +00:00
|
|
|
try {
|
2012-06-28 17:40:46 +00:00
|
|
|
if (match == MESSAGE_ID || match == SYNCED_MESSAGE_ID) {
|
|
|
|
if (!uri.getBooleanQueryParameter(IS_UIPROVIDER, false)) {
|
|
|
|
notifyUIConversation(uri);
|
|
|
|
}
|
|
|
|
}
|
2009-06-16 19:03:45 +00:00
|
|
|
switch (match) {
|
2012-06-28 17:40:46 +00:00
|
|
|
case UI_MESSAGE:
|
|
|
|
return uiDeleteMessage(uri);
|
|
|
|
case UI_ACCOUNT_DATA:
|
|
|
|
return uiDeleteAccountData(uri);
|
|
|
|
case UI_ACCOUNT:
|
|
|
|
return uiDeleteAccount(uri);
|
2012-06-29 16:42:05 +00:00
|
|
|
case SYNCED_MESSAGE_SELECTION:
|
|
|
|
Cursor findCursor = db.query(tableName, Message.ID_COLUMN_PROJECTION, selection,
|
|
|
|
selectionArgs, null, null, null);
|
|
|
|
try {
|
|
|
|
if (findCursor.moveToFirst()) {
|
|
|
|
return delete(ContentUris.withAppendedId(
|
|
|
|
Message.SYNCED_CONTENT_URI,
|
|
|
|
findCursor.getLong(Message.ID_COLUMNS_ID_COLUMN)),
|
|
|
|
null, null);
|
|
|
|
} else {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
} finally {
|
|
|
|
findCursor.close();
|
|
|
|
}
|
2009-06-16 19:03:45 +00:00
|
|
|
// These are cases in which one or more Messages might get deleted, either by
|
|
|
|
// cascade or explicitly
|
|
|
|
case MAILBOX_ID:
|
|
|
|
case MAILBOX:
|
|
|
|
case ACCOUNT_ID:
|
|
|
|
case ACCOUNT:
|
|
|
|
case MESSAGE:
|
2009-06-27 19:14:14 +00:00
|
|
|
case SYNCED_MESSAGE_ID:
|
2009-06-16 19:03:45 +00:00
|
|
|
case MESSAGE_ID:
|
|
|
|
// Handle lost Body records here, since this cannot be done in a trigger
|
|
|
|
// The process is:
|
2009-08-18 18:44:27 +00:00
|
|
|
// 1) Begin a transaction, ensuring that both databases are affected atomically
|
|
|
|
// 2) Do the requested deletion, with cascading deletions handled in triggers
|
|
|
|
// 3) End the transaction, committing all changes atomically
|
2009-09-10 18:52:36 +00:00
|
|
|
//
|
|
|
|
// Bodies are auto-deleted here; Attachments are auto-deleted via trigger
|
2009-08-06 00:31:50 +00:00
|
|
|
messageDeletion = true;
|
2009-09-19 03:36:15 +00:00
|
|
|
db.beginTransaction();
|
2009-06-16 19:03:45 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
switch (match) {
|
|
|
|
case BODY_ID:
|
2009-06-27 19:14:14 +00:00
|
|
|
case DELETED_MESSAGE_ID:
|
|
|
|
case SYNCED_MESSAGE_ID:
|
2009-06-16 19:03:45 +00:00
|
|
|
case MESSAGE_ID:
|
|
|
|
case UPDATED_MESSAGE_ID:
|
|
|
|
case ATTACHMENT_ID:
|
|
|
|
case MAILBOX_ID:
|
|
|
|
case ACCOUNT_ID:
|
|
|
|
case HOSTAUTH_ID:
|
2011-04-28 00:12:06 +00:00
|
|
|
case POLICY_ID:
|
2011-06-01 17:09:26 +00:00
|
|
|
case QUICK_RESPONSE_ID:
|
2009-06-16 19:03:45 +00:00
|
|
|
id = uri.getPathSegments().get(1);
|
2009-06-27 19:14:14 +00:00
|
|
|
if (match == SYNCED_MESSAGE_ID) {
|
|
|
|
// For synced messages, first copy the old message to the deleted table and
|
|
|
|
// delete it from the updated table (in case it was updated first)
|
|
|
|
// Note that this is all within a transaction, for atomicity
|
|
|
|
db.execSQL(DELETED_MESSAGE_INSERT + id);
|
|
|
|
db.execSQL(UPDATED_MESSAGE_DELETE + id);
|
|
|
|
}
|
2010-10-27 23:50:54 +00:00
|
|
|
if (cache != null) {
|
|
|
|
cache.lock(id);
|
|
|
|
}
|
|
|
|
try {
|
|
|
|
result = db.delete(tableName, whereWithId(id, selection), selectionArgs);
|
|
|
|
if (cache != null) {
|
|
|
|
switch(match) {
|
|
|
|
case ACCOUNT_ID:
|
|
|
|
// Account deletion will clear all of the caches, as HostAuth's,
|
|
|
|
// Mailboxes, and Messages will be deleted in the process
|
Improve EmailContent caching...
* Guarantee that up to 16 Account (with HostAuths), and Policy rows
are always cached. Also, 6 commonly used Mailboxes per Account
(Inbox, Outbox, Drafts, Sent, Trash, and Search)
* Precache these rows when EmailProvider starts up
* Ensure that newly added, precachable rows are cached when created
* Clean up some inefficient/wrong caching code
* Fix a commonly called method in NotificationManager in which we
load a single Mailbox row using selection vs withAppendedId
* Confirm that we don't read from the database in typical use and
heavy message loading
* Add a special URI for finding mailbox by type (using the cache)
* Add special-case code for EmailContent.count(Account.CONTENT_URI)
which is used in a number of places (including on the UI thread)
and whose value is easily determined
* Add a special URI to get the default account id
* Confirm that all unit tests work
The goal here is to be able to load all Account, HostAuth, Policy,
and Mailbox objects (by id) without worrying about disk access.
There will still be a single disk read for uncommon Mailbox reads,
but this should be considered acceptable.
Change-Id: Ibc9aa7acc73185e360b0b6f3053b90a985e97210
TODO: Unit tests
2011-06-19 01:03:11 +00:00
|
|
|
mCacheMailbox.invalidate("Delete", uri, selection);
|
|
|
|
mCacheHostAuth.invalidate("Delete", uri, selection);
|
|
|
|
mCachePolicy.invalidate("Delete", uri, selection);
|
2010-10-27 23:50:54 +00:00
|
|
|
//$FALL-THROUGH$
|
|
|
|
case MAILBOX_ID:
|
|
|
|
// Mailbox deletion will clear the Message cache
|
Improve EmailContent caching...
* Guarantee that up to 16 Account (with HostAuths), and Policy rows
are always cached. Also, 6 commonly used Mailboxes per Account
(Inbox, Outbox, Drafts, Sent, Trash, and Search)
* Precache these rows when EmailProvider starts up
* Ensure that newly added, precachable rows are cached when created
* Clean up some inefficient/wrong caching code
* Fix a commonly called method in NotificationManager in which we
load a single Mailbox row using selection vs withAppendedId
* Confirm that we don't read from the database in typical use and
heavy message loading
* Add a special URI for finding mailbox by type (using the cache)
* Add special-case code for EmailContent.count(Account.CONTENT_URI)
which is used in a number of places (including on the UI thread)
and whose value is easily determined
* Add a special URI to get the default account id
* Confirm that all unit tests work
The goal here is to be able to load all Account, HostAuth, Policy,
and Mailbox objects (by id) without worrying about disk access.
There will still be a single disk read for uncommon Mailbox reads,
but this should be considered acceptable.
Change-Id: Ibc9aa7acc73185e360b0b6f3053b90a985e97210
TODO: Unit tests
2011-06-19 01:03:11 +00:00
|
|
|
mCacheMessage.invalidate("Delete", uri, selection);
|
2010-10-27 23:50:54 +00:00
|
|
|
//$FALL-THROUGH$
|
|
|
|
case SYNCED_MESSAGE_ID:
|
|
|
|
case MESSAGE_ID:
|
|
|
|
case HOSTAUTH_ID:
|
Improve EmailContent caching...
* Guarantee that up to 16 Account (with HostAuths), and Policy rows
are always cached. Also, 6 commonly used Mailboxes per Account
(Inbox, Outbox, Drafts, Sent, Trash, and Search)
* Precache these rows when EmailProvider starts up
* Ensure that newly added, precachable rows are cached when created
* Clean up some inefficient/wrong caching code
* Fix a commonly called method in NotificationManager in which we
load a single Mailbox row using selection vs withAppendedId
* Confirm that we don't read from the database in typical use and
heavy message loading
* Add a special URI for finding mailbox by type (using the cache)
* Add special-case code for EmailContent.count(Account.CONTENT_URI)
which is used in a number of places (including on the UI thread)
and whose value is easily determined
* Add a special URI to get the default account id
* Confirm that all unit tests work
The goal here is to be able to load all Account, HostAuth, Policy,
and Mailbox objects (by id) without worrying about disk access.
There will still be a single disk read for uncommon Mailbox reads,
but this should be considered acceptable.
Change-Id: Ibc9aa7acc73185e360b0b6f3053b90a985e97210
TODO: Unit tests
2011-06-19 01:03:11 +00:00
|
|
|
case POLICY_ID:
|
2010-10-27 23:50:54 +00:00
|
|
|
cache.invalidate("Delete", uri, selection);
|
Improve EmailContent caching...
* Guarantee that up to 16 Account (with HostAuths), and Policy rows
are always cached. Also, 6 commonly used Mailboxes per Account
(Inbox, Outbox, Drafts, Sent, Trash, and Search)
* Precache these rows when EmailProvider starts up
* Ensure that newly added, precachable rows are cached when created
* Clean up some inefficient/wrong caching code
* Fix a commonly called method in NotificationManager in which we
load a single Mailbox row using selection vs withAppendedId
* Confirm that we don't read from the database in typical use and
heavy message loading
* Add a special URI for finding mailbox by type (using the cache)
* Add special-case code for EmailContent.count(Account.CONTENT_URI)
which is used in a number of places (including on the UI thread)
and whose value is easily determined
* Add a special URI to get the default account id
* Confirm that all unit tests work
The goal here is to be able to load all Account, HostAuth, Policy,
and Mailbox objects (by id) without worrying about disk access.
There will still be a single disk read for uncommon Mailbox reads,
but this should be considered acceptable.
Change-Id: Ibc9aa7acc73185e360b0b6f3053b90a985e97210
TODO: Unit tests
2011-06-19 01:03:11 +00:00
|
|
|
// Make sure all data is properly cached
|
|
|
|
if (match != MESSAGE_ID) {
|
|
|
|
preCacheData();
|
|
|
|
}
|
2010-10-27 23:50:54 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} finally {
|
|
|
|
if (cache != null) {
|
|
|
|
cache.unlock(id);
|
|
|
|
}
|
2011-05-06 18:31:10 +00:00
|
|
|
}
|
2012-06-28 17:40:46 +00:00
|
|
|
if (match == ACCOUNT_ID) {
|
|
|
|
notifyUI(UIPROVIDER_ACCOUNT_NOTIFIER, id);
|
|
|
|
resolver.notifyChange(UIPROVIDER_ACCOUNTS_NOTIFIER, null);
|
|
|
|
} else if (match == MAILBOX_ID) {
|
|
|
|
notifyUI(UIPROVIDER_FOLDER_NOTIFIER, id);
|
|
|
|
}
|
2009-06-16 19:03:45 +00:00
|
|
|
break;
|
2009-09-10 18:52:36 +00:00
|
|
|
case ATTACHMENTS_MESSAGE_ID:
|
|
|
|
// All attachments for the given message
|
|
|
|
id = uri.getPathSegments().get(2);
|
2010-10-27 23:50:54 +00:00
|
|
|
result = db.delete(tableName,
|
2009-09-10 18:52:36 +00:00
|
|
|
whereWith(Attachment.MESSAGE_KEY + "=" + id, selection), selectionArgs);
|
|
|
|
break;
|
|
|
|
|
2009-06-16 19:03:45 +00:00
|
|
|
case BODY:
|
|
|
|
case MESSAGE:
|
2009-06-27 19:14:14 +00:00
|
|
|
case DELETED_MESSAGE:
|
2009-06-16 19:03:45 +00:00
|
|
|
case UPDATED_MESSAGE:
|
|
|
|
case ATTACHMENT:
|
|
|
|
case MAILBOX:
|
|
|
|
case ACCOUNT:
|
|
|
|
case HOSTAUTH:
|
2011-04-28 00:12:06 +00:00
|
|
|
case POLICY:
|
2010-10-27 23:50:54 +00:00
|
|
|
switch(match) {
|
|
|
|
// See the comments above for deletion of ACCOUNT_ID, etc
|
|
|
|
case ACCOUNT:
|
Improve EmailContent caching...
* Guarantee that up to 16 Account (with HostAuths), and Policy rows
are always cached. Also, 6 commonly used Mailboxes per Account
(Inbox, Outbox, Drafts, Sent, Trash, and Search)
* Precache these rows when EmailProvider starts up
* Ensure that newly added, precachable rows are cached when created
* Clean up some inefficient/wrong caching code
* Fix a commonly called method in NotificationManager in which we
load a single Mailbox row using selection vs withAppendedId
* Confirm that we don't read from the database in typical use and
heavy message loading
* Add a special URI for finding mailbox by type (using the cache)
* Add special-case code for EmailContent.count(Account.CONTENT_URI)
which is used in a number of places (including on the UI thread)
and whose value is easily determined
* Add a special URI to get the default account id
* Confirm that all unit tests work
The goal here is to be able to load all Account, HostAuth, Policy,
and Mailbox objects (by id) without worrying about disk access.
There will still be a single disk read for uncommon Mailbox reads,
but this should be considered acceptable.
Change-Id: Ibc9aa7acc73185e360b0b6f3053b90a985e97210
TODO: Unit tests
2011-06-19 01:03:11 +00:00
|
|
|
mCacheMailbox.invalidate("Delete", uri, selection);
|
|
|
|
mCacheHostAuth.invalidate("Delete", uri, selection);
|
|
|
|
mCachePolicy.invalidate("Delete", uri, selection);
|
2010-10-27 23:50:54 +00:00
|
|
|
//$FALL-THROUGH$
|
|
|
|
case MAILBOX:
|
Improve EmailContent caching...
* Guarantee that up to 16 Account (with HostAuths), and Policy rows
are always cached. Also, 6 commonly used Mailboxes per Account
(Inbox, Outbox, Drafts, Sent, Trash, and Search)
* Precache these rows when EmailProvider starts up
* Ensure that newly added, precachable rows are cached when created
* Clean up some inefficient/wrong caching code
* Fix a commonly called method in NotificationManager in which we
load a single Mailbox row using selection vs withAppendedId
* Confirm that we don't read from the database in typical use and
heavy message loading
* Add a special URI for finding mailbox by type (using the cache)
* Add special-case code for EmailContent.count(Account.CONTENT_URI)
which is used in a number of places (including on the UI thread)
and whose value is easily determined
* Add a special URI to get the default account id
* Confirm that all unit tests work
The goal here is to be able to load all Account, HostAuth, Policy,
and Mailbox objects (by id) without worrying about disk access.
There will still be a single disk read for uncommon Mailbox reads,
but this should be considered acceptable.
Change-Id: Ibc9aa7acc73185e360b0b6f3053b90a985e97210
TODO: Unit tests
2011-06-19 01:03:11 +00:00
|
|
|
mCacheMessage.invalidate("Delete", uri, selection);
|
2010-10-27 23:50:54 +00:00
|
|
|
//$FALL-THROUGH$
|
|
|
|
case MESSAGE:
|
|
|
|
case HOSTAUTH:
|
Improve EmailContent caching...
* Guarantee that up to 16 Account (with HostAuths), and Policy rows
are always cached. Also, 6 commonly used Mailboxes per Account
(Inbox, Outbox, Drafts, Sent, Trash, and Search)
* Precache these rows when EmailProvider starts up
* Ensure that newly added, precachable rows are cached when created
* Clean up some inefficient/wrong caching code
* Fix a commonly called method in NotificationManager in which we
load a single Mailbox row using selection vs withAppendedId
* Confirm that we don't read from the database in typical use and
heavy message loading
* Add a special URI for finding mailbox by type (using the cache)
* Add special-case code for EmailContent.count(Account.CONTENT_URI)
which is used in a number of places (including on the UI thread)
and whose value is easily determined
* Add a special URI to get the default account id
* Confirm that all unit tests work
The goal here is to be able to load all Account, HostAuth, Policy,
and Mailbox objects (by id) without worrying about disk access.
There will still be a single disk read for uncommon Mailbox reads,
but this should be considered acceptable.
Change-Id: Ibc9aa7acc73185e360b0b6f3053b90a985e97210
TODO: Unit tests
2011-06-19 01:03:11 +00:00
|
|
|
case POLICY:
|
2010-10-27 23:50:54 +00:00
|
|
|
cache.invalidate("Delete", uri, selection);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
result = db.delete(tableName, selection, selectionArgs);
|
Improve EmailContent caching...
* Guarantee that up to 16 Account (with HostAuths), and Policy rows
are always cached. Also, 6 commonly used Mailboxes per Account
(Inbox, Outbox, Drafts, Sent, Trash, and Search)
* Precache these rows when EmailProvider starts up
* Ensure that newly added, precachable rows are cached when created
* Clean up some inefficient/wrong caching code
* Fix a commonly called method in NotificationManager in which we
load a single Mailbox row using selection vs withAppendedId
* Confirm that we don't read from the database in typical use and
heavy message loading
* Add a special URI for finding mailbox by type (using the cache)
* Add special-case code for EmailContent.count(Account.CONTENT_URI)
which is used in a number of places (including on the UI thread)
and whose value is easily determined
* Add a special URI to get the default account id
* Confirm that all unit tests work
The goal here is to be able to load all Account, HostAuth, Policy,
and Mailbox objects (by id) without worrying about disk access.
There will still be a single disk read for uncommon Mailbox reads,
but this should be considered acceptable.
Change-Id: Ibc9aa7acc73185e360b0b6f3053b90a985e97210
TODO: Unit tests
2011-06-19 01:03:11 +00:00
|
|
|
switch(match) {
|
|
|
|
case ACCOUNT:
|
|
|
|
case MAILBOX:
|
|
|
|
case HOSTAUTH:
|
|
|
|
case POLICY:
|
|
|
|
// Make sure all data is properly cached
|
|
|
|
preCacheData();
|
|
|
|
break;
|
|
|
|
}
|
2009-06-16 19:03:45 +00:00
|
|
|
break;
|
2009-09-10 18:52:36 +00:00
|
|
|
|
2009-06-16 19:03:45 +00:00
|
|
|
default:
|
|
|
|
throw new IllegalArgumentException("Unknown URI " + uri);
|
|
|
|
}
|
2009-08-06 00:31:50 +00:00
|
|
|
if (messageDeletion) {
|
2009-08-03 13:05:50 +00:00
|
|
|
if (match == MESSAGE_ID) {
|
2009-06-24 19:48:57 +00:00
|
|
|
// Delete the Body record associated with the deleted message
|
2009-06-27 19:14:14 +00:00
|
|
|
db.execSQL(DELETE_BODY + id);
|
2009-08-03 13:05:50 +00:00
|
|
|
} else {
|
|
|
|
// Delete any orphaned Body records
|
|
|
|
db.execSQL(DELETE_ORPHAN_BODIES);
|
2009-08-06 00:31:50 +00:00
|
|
|
}
|
2009-09-19 03:36:15 +00:00
|
|
|
db.setTransactionSuccessful();
|
2009-06-24 19:48:57 +00:00
|
|
|
}
|
2009-11-19 01:11:33 +00:00
|
|
|
} catch (SQLiteException e) {
|
|
|
|
checkDatabases();
|
|
|
|
throw e;
|
2009-06-16 19:03:45 +00:00
|
|
|
} finally {
|
2009-08-06 00:31:50 +00:00
|
|
|
if (messageDeletion) {
|
2009-09-19 03:36:15 +00:00
|
|
|
db.endTransaction();
|
2009-06-24 19:48:57 +00:00
|
|
|
}
|
2009-05-26 23:40:34 +00:00
|
|
|
}
|
2010-09-14 23:28:50 +00:00
|
|
|
|
2011-05-06 18:31:10 +00:00
|
|
|
// Notify all notifier cursors
|
2011-05-11 22:29:24 +00:00
|
|
|
sendNotifierChange(getBaseNotificationUri(match), NOTIFICATION_OP_DELETE, id);
|
2011-05-06 18:31:10 +00:00
|
|
|
|
|
|
|
// Notify all email content cursors
|
2010-09-23 16:19:44 +00:00
|
|
|
resolver.notifyChange(EmailContent.CONTENT_URI, null);
|
2009-06-01 21:34:16 +00:00
|
|
|
return result;
|
2009-05-26 23:40:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
// Use the email- prefix because message, mailbox, and account are so generic (e.g. SMS, IM)
|
|
|
|
public String getType(Uri uri) {
|
2010-12-16 20:28:16 +00:00
|
|
|
int match = findMatch(uri, "getType");
|
2009-05-26 23:40:34 +00:00
|
|
|
switch (match) {
|
2009-06-15 21:40:06 +00:00
|
|
|
case BODY_ID:
|
|
|
|
return "vnd.android.cursor.item/email-body";
|
|
|
|
case BODY:
|
2010-10-14 02:04:46 +00:00
|
|
|
return "vnd.android.cursor.dir/email-body";
|
2009-06-17 17:22:37 +00:00
|
|
|
case UPDATED_MESSAGE_ID:
|
2009-06-15 21:40:06 +00:00
|
|
|
case MESSAGE_ID:
|
2010-10-14 02:04:46 +00:00
|
|
|
// NOTE: According to the framework folks, we're supposed to invent mime types as
|
|
|
|
// a way of passing information to drag & drop recipients.
|
|
|
|
// If there's a mailboxId parameter in the url, we respond with a mime type that
|
|
|
|
// has -n appended, where n is the mailboxId of the message. The drag & drop code
|
|
|
|
// uses this information to know not to allow dragging the item to its own mailbox
|
|
|
|
String mimeType = EMAIL_MESSAGE_MIME_TYPE;
|
|
|
|
String mailboxId = uri.getQueryParameter(MESSAGE_URI_PARAMETER_MAILBOX_ID);
|
|
|
|
if (mailboxId != null) {
|
|
|
|
mimeType += "-" + mailboxId;
|
|
|
|
}
|
|
|
|
return mimeType;
|
2009-06-17 17:22:37 +00:00
|
|
|
case UPDATED_MESSAGE:
|
2009-05-29 21:24:34 +00:00
|
|
|
case MESSAGE:
|
2009-06-15 21:40:06 +00:00
|
|
|
return "vnd.android.cursor.dir/email-message";
|
2009-05-29 21:24:34 +00:00
|
|
|
case MAILBOX:
|
|
|
|
return "vnd.android.cursor.dir/email-mailbox";
|
|
|
|
case MAILBOX_ID:
|
|
|
|
return "vnd.android.cursor.item/email-mailbox";
|
|
|
|
case ACCOUNT:
|
|
|
|
return "vnd.android.cursor.dir/email-account";
|
|
|
|
case ACCOUNT_ID:
|
|
|
|
return "vnd.android.cursor.item/email-account";
|
2009-07-16 23:03:40 +00:00
|
|
|
case ATTACHMENTS_MESSAGE_ID:
|
2009-05-29 21:24:34 +00:00
|
|
|
case ATTACHMENT:
|
|
|
|
return "vnd.android.cursor.dir/email-attachment";
|
|
|
|
case ATTACHMENT_ID:
|
2010-08-10 00:48:53 +00:00
|
|
|
return EMAIL_ATTACHMENT_MIME_TYPE;
|
2009-05-29 21:24:34 +00:00
|
|
|
case HOSTAUTH:
|
|
|
|
return "vnd.android.cursor.dir/email-hostauth";
|
|
|
|
case HOSTAUTH_ID:
|
|
|
|
return "vnd.android.cursor.item/email-hostauth";
|
|
|
|
default:
|
|
|
|
throw new IllegalArgumentException("Unknown URI " + uri);
|
2009-05-26 23:40:34 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-06-28 17:40:46 +00:00
|
|
|
private static final Uri UIPROVIDER_CONVERSATION_NOTIFIER =
|
|
|
|
Uri.parse("content://" + UIProvider.AUTHORITY + "/uimessages");
|
|
|
|
private static final Uri UIPROVIDER_FOLDER_NOTIFIER =
|
|
|
|
Uri.parse("content://" + UIProvider.AUTHORITY + "/uifolder");
|
|
|
|
private static final Uri UIPROVIDER_ACCOUNT_NOTIFIER =
|
|
|
|
Uri.parse("content://" + UIProvider.AUTHORITY + "/uiaccount");
|
|
|
|
public static final Uri UIPROVIDER_SETTINGS_NOTIFIER =
|
|
|
|
Uri.parse("content://" + UIProvider.AUTHORITY + "/uisettings");
|
|
|
|
private static final Uri UIPROVIDER_ATTACHMENT_NOTIFIER =
|
|
|
|
Uri.parse("content://" + UIProvider.AUTHORITY + "/uiattachment");
|
|
|
|
private static final Uri UIPROVIDER_ATTACHMENTS_NOTIFIER =
|
|
|
|
Uri.parse("content://" + UIProvider.AUTHORITY + "/uiattachments");
|
|
|
|
private static final Uri UIPROVIDER_ACCOUNTS_NOTIFIER =
|
|
|
|
Uri.parse("content://" + UIProvider.AUTHORITY + "/uiaccts");
|
|
|
|
private static final Uri UIPROVIDER_MESSAGE_NOTIFIER =
|
|
|
|
Uri.parse("content://" + UIProvider.AUTHORITY + "/uimessage");
|
|
|
|
private static final Uri UIPROVIDER_RECENT_FOLDERS_NOTIFIER =
|
|
|
|
Uri.parse("content://" + UIProvider.AUTHORITY + "/uirecentfolders");
|
|
|
|
|
2009-05-26 23:40:34 +00:00
|
|
|
@Override
|
|
|
|
public Uri insert(Uri uri, ContentValues values) {
|
2010-12-16 20:28:16 +00:00
|
|
|
int match = findMatch(uri, "insert");
|
2009-06-15 21:40:06 +00:00
|
|
|
Context context = getContext();
|
2010-09-23 16:19:44 +00:00
|
|
|
ContentResolver resolver = context.getContentResolver();
|
|
|
|
|
2009-08-06 00:31:50 +00:00
|
|
|
// See the comment at delete(), above
|
2009-08-18 18:44:27 +00:00
|
|
|
SQLiteDatabase db = getDatabase(context);
|
2009-05-26 23:40:34 +00:00
|
|
|
int table = match >> BASE_SHIFT;
|
2011-05-06 18:31:10 +00:00
|
|
|
String id = "0";
|
|
|
|
long longId;
|
2009-06-27 19:14:14 +00:00
|
|
|
|
2010-09-10 21:37:01 +00:00
|
|
|
// We do NOT allow setting of unreadCount/messageCount via the provider
|
|
|
|
// These columns are maintained via triggers
|
|
|
|
if (match == MAILBOX_ID || match == MAILBOX) {
|
|
|
|
values.put(MailboxColumns.UNREAD_COUNT, 0);
|
|
|
|
values.put(MailboxColumns.MESSAGE_COUNT, 0);
|
|
|
|
}
|
2010-09-10 20:06:07 +00:00
|
|
|
|
2009-06-01 21:34:16 +00:00
|
|
|
Uri resultUri = null;
|
2009-06-27 19:14:14 +00:00
|
|
|
|
2009-11-19 01:11:33 +00:00
|
|
|
try {
|
|
|
|
switch (match) {
|
2012-06-28 17:40:46 +00:00
|
|
|
case UI_SAVEDRAFT:
|
|
|
|
return uiSaveDraft(uri, values);
|
|
|
|
case UI_SENDMAIL:
|
|
|
|
return uiSendMail(uri, values);
|
Improve EmailContent caching...
* Guarantee that up to 16 Account (with HostAuths), and Policy rows
are always cached. Also, 6 commonly used Mailboxes per Account
(Inbox, Outbox, Drafts, Sent, Trash, and Search)
* Precache these rows when EmailProvider starts up
* Ensure that newly added, precachable rows are cached when created
* Clean up some inefficient/wrong caching code
* Fix a commonly called method in NotificationManager in which we
load a single Mailbox row using selection vs withAppendedId
* Confirm that we don't read from the database in typical use and
heavy message loading
* Add a special URI for finding mailbox by type (using the cache)
* Add special-case code for EmailContent.count(Account.CONTENT_URI)
which is used in a number of places (including on the UI thread)
and whose value is easily determined
* Add a special URI to get the default account id
* Confirm that all unit tests work
The goal here is to be able to load all Account, HostAuth, Policy,
and Mailbox objects (by id) without worrying about disk access.
There will still be a single disk read for uncommon Mailbox reads,
but this should be considered acceptable.
Change-Id: Ibc9aa7acc73185e360b0b6f3053b90a985e97210
TODO: Unit tests
2011-06-19 01:03:11 +00:00
|
|
|
// NOTE: It is NOT legal for production code to insert directly into UPDATED_MESSAGE
|
|
|
|
// or DELETED_MESSAGE; see the comment below for details
|
2009-11-19 01:11:33 +00:00
|
|
|
case UPDATED_MESSAGE:
|
|
|
|
case DELETED_MESSAGE:
|
Improve EmailContent caching...
* Guarantee that up to 16 Account (with HostAuths), and Policy rows
are always cached. Also, 6 commonly used Mailboxes per Account
(Inbox, Outbox, Drafts, Sent, Trash, and Search)
* Precache these rows when EmailProvider starts up
* Ensure that newly added, precachable rows are cached when created
* Clean up some inefficient/wrong caching code
* Fix a commonly called method in NotificationManager in which we
load a single Mailbox row using selection vs withAppendedId
* Confirm that we don't read from the database in typical use and
heavy message loading
* Add a special URI for finding mailbox by type (using the cache)
* Add special-case code for EmailContent.count(Account.CONTENT_URI)
which is used in a number of places (including on the UI thread)
and whose value is easily determined
* Add a special URI to get the default account id
* Confirm that all unit tests work
The goal here is to be able to load all Account, HostAuth, Policy,
and Mailbox objects (by id) without worrying about disk access.
There will still be a single disk read for uncommon Mailbox reads,
but this should be considered acceptable.
Change-Id: Ibc9aa7acc73185e360b0b6f3053b90a985e97210
TODO: Unit tests
2011-06-19 01:03:11 +00:00
|
|
|
case MESSAGE:
|
2009-11-19 01:11:33 +00:00
|
|
|
case BODY:
|
|
|
|
case ATTACHMENT:
|
|
|
|
case MAILBOX:
|
|
|
|
case ACCOUNT:
|
|
|
|
case HOSTAUTH:
|
2011-04-28 00:12:06 +00:00
|
|
|
case POLICY:
|
2011-06-01 17:09:26 +00:00
|
|
|
case QUICK_RESPONSE:
|
2011-05-06 18:31:10 +00:00
|
|
|
longId = db.insert(TABLE_NAMES[table], "foo", values);
|
|
|
|
resultUri = ContentUris.withAppendedId(uri, longId);
|
Improve EmailContent caching...
* Guarantee that up to 16 Account (with HostAuths), and Policy rows
are always cached. Also, 6 commonly used Mailboxes per Account
(Inbox, Outbox, Drafts, Sent, Trash, and Search)
* Precache these rows when EmailProvider starts up
* Ensure that newly added, precachable rows are cached when created
* Clean up some inefficient/wrong caching code
* Fix a commonly called method in NotificationManager in which we
load a single Mailbox row using selection vs withAppendedId
* Confirm that we don't read from the database in typical use and
heavy message loading
* Add a special URI for finding mailbox by type (using the cache)
* Add special-case code for EmailContent.count(Account.CONTENT_URI)
which is used in a number of places (including on the UI thread)
and whose value is easily determined
* Add a special URI to get the default account id
* Confirm that all unit tests work
The goal here is to be able to load all Account, HostAuth, Policy,
and Mailbox objects (by id) without worrying about disk access.
There will still be a single disk read for uncommon Mailbox reads,
but this should be considered acceptable.
Change-Id: Ibc9aa7acc73185e360b0b6f3053b90a985e97210
TODO: Unit tests
2011-06-19 01:03:11 +00:00
|
|
|
switch(match) {
|
2012-06-28 17:40:46 +00:00
|
|
|
case MESSAGE:
|
|
|
|
if (!uri.getBooleanQueryParameter(IS_UIPROVIDER, false)) {
|
|
|
|
notifyUIConversationMailbox(values.getAsLong(Message.MAILBOX_KEY));
|
|
|
|
}
|
|
|
|
break;
|
Improve EmailContent caching...
* Guarantee that up to 16 Account (with HostAuths), and Policy rows
are always cached. Also, 6 commonly used Mailboxes per Account
(Inbox, Outbox, Drafts, Sent, Trash, and Search)
* Precache these rows when EmailProvider starts up
* Ensure that newly added, precachable rows are cached when created
* Clean up some inefficient/wrong caching code
* Fix a commonly called method in NotificationManager in which we
load a single Mailbox row using selection vs withAppendedId
* Confirm that we don't read from the database in typical use and
heavy message loading
* Add a special URI for finding mailbox by type (using the cache)
* Add special-case code for EmailContent.count(Account.CONTENT_URI)
which is used in a number of places (including on the UI thread)
and whose value is easily determined
* Add a special URI to get the default account id
* Confirm that all unit tests work
The goal here is to be able to load all Account, HostAuth, Policy,
and Mailbox objects (by id) without worrying about disk access.
There will still be a single disk read for uncommon Mailbox reads,
but this should be considered acceptable.
Change-Id: Ibc9aa7acc73185e360b0b6f3053b90a985e97210
TODO: Unit tests
2011-06-19 01:03:11 +00:00
|
|
|
case MAILBOX:
|
|
|
|
if (values.containsKey(MailboxColumns.TYPE)) {
|
|
|
|
// Only cache special mailbox types
|
|
|
|
int type = values.getAsInteger(MailboxColumns.TYPE);
|
|
|
|
if (type != Mailbox.TYPE_INBOX && type != Mailbox.TYPE_OUTBOX &&
|
|
|
|
type != Mailbox.TYPE_DRAFTS && type != Mailbox.TYPE_SENT &&
|
|
|
|
type != Mailbox.TYPE_TRASH && type != Mailbox.TYPE_SEARCH) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2012-06-28 17:40:46 +00:00
|
|
|
// Notify the account when a new mailbox is added
|
|
|
|
Long accountId = values.getAsLong(MailboxColumns.ACCOUNT_KEY);
|
|
|
|
if (accountId != null && accountId.longValue() > 0) {
|
|
|
|
notifyUI(UIPROVIDER_ACCOUNT_NOTIFIER, accountId);
|
|
|
|
}
|
Improve EmailContent caching...
* Guarantee that up to 16 Account (with HostAuths), and Policy rows
are always cached. Also, 6 commonly used Mailboxes per Account
(Inbox, Outbox, Drafts, Sent, Trash, and Search)
* Precache these rows when EmailProvider starts up
* Ensure that newly added, precachable rows are cached when created
* Clean up some inefficient/wrong caching code
* Fix a commonly called method in NotificationManager in which we
load a single Mailbox row using selection vs withAppendedId
* Confirm that we don't read from the database in typical use and
heavy message loading
* Add a special URI for finding mailbox by type (using the cache)
* Add special-case code for EmailContent.count(Account.CONTENT_URI)
which is used in a number of places (including on the UI thread)
and whose value is easily determined
* Add a special URI to get the default account id
* Confirm that all unit tests work
The goal here is to be able to load all Account, HostAuth, Policy,
and Mailbox objects (by id) without worrying about disk access.
There will still be a single disk read for uncommon Mailbox reads,
but this should be considered acceptable.
Change-Id: Ibc9aa7acc73185e360b0b6f3053b90a985e97210
TODO: Unit tests
2011-06-19 01:03:11 +00:00
|
|
|
//$FALL-THROUGH$
|
|
|
|
case ACCOUNT:
|
|
|
|
case HOSTAUTH:
|
|
|
|
case POLICY:
|
|
|
|
// Cache new account, host auth, policy, and some mailbox rows
|
|
|
|
Cursor c = query(resultUri, CACHE_PROJECTIONS[table], null, null, null);
|
|
|
|
if (c != null) {
|
|
|
|
if (match == MAILBOX) {
|
|
|
|
addToMailboxTypeMap(c);
|
|
|
|
} else if (match == ACCOUNT) {
|
|
|
|
getOrCreateAccountMailboxTypeMap(longId);
|
|
|
|
}
|
|
|
|
c.close();
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
2009-11-19 01:11:33 +00:00
|
|
|
// Clients shouldn't normally be adding rows to these tables, as they are
|
|
|
|
// maintained by triggers. However, we need to be able to do this for unit
|
|
|
|
// testing, so we allow the insert and then throw the same exception that we
|
|
|
|
// would if this weren't allowed.
|
|
|
|
if (match == UPDATED_MESSAGE || match == DELETED_MESSAGE) {
|
|
|
|
throw new IllegalArgumentException("Unknown URL " + uri);
|
2012-06-28 17:40:46 +00:00
|
|
|
} else if (match == ATTACHMENT) {
|
2010-12-09 01:11:04 +00:00
|
|
|
int flags = 0;
|
2010-08-10 00:48:53 +00:00
|
|
|
if (values.containsKey(Attachment.FLAGS)) {
|
2010-12-09 01:11:04 +00:00
|
|
|
flags = values.getAsInteger(Attachment.FLAGS);
|
2010-08-10 00:48:53 +00:00
|
|
|
}
|
2010-12-09 01:11:04 +00:00
|
|
|
// Report all new attachments to the download service
|
2011-08-24 01:02:11 +00:00
|
|
|
mAttachmentService.attachmentChanged(getContext(), longId, flags);
|
2012-06-28 17:40:46 +00:00
|
|
|
} else if (match == ACCOUNT) {
|
|
|
|
resolver.notifyChange(UIPROVIDER_ACCOUNTS_NOTIFIER, null);
|
2010-08-10 00:48:53 +00:00
|
|
|
}
|
2009-11-19 01:11:33 +00:00
|
|
|
break;
|
|
|
|
case MAILBOX_ID:
|
|
|
|
// This implies adding a message to a mailbox
|
|
|
|
// Hmm, a problem here is that we can't link the account as well, so it must be
|
|
|
|
// already in the values...
|
2011-05-06 18:31:10 +00:00
|
|
|
longId = Long.parseLong(uri.getPathSegments().get(1));
|
|
|
|
values.put(MessageColumns.MAILBOX_KEY, longId);
|
2010-09-14 23:28:50 +00:00
|
|
|
return insert(Message.CONTENT_URI, values); // Recurse
|
2009-11-19 01:11:33 +00:00
|
|
|
case MESSAGE_ID:
|
|
|
|
// This implies adding an attachment to a message.
|
2011-05-06 18:31:10 +00:00
|
|
|
id = uri.getPathSegments().get(1);
|
|
|
|
longId = Long.parseLong(id);
|
|
|
|
values.put(AttachmentColumns.MESSAGE_KEY, longId);
|
2010-09-14 23:28:50 +00:00
|
|
|
return insert(Attachment.CONTENT_URI, values); // Recurse
|
2009-11-19 01:11:33 +00:00
|
|
|
case ACCOUNT_ID:
|
|
|
|
// This implies adding a mailbox to an account.
|
2011-05-06 18:31:10 +00:00
|
|
|
longId = Long.parseLong(uri.getPathSegments().get(1));
|
|
|
|
values.put(MailboxColumns.ACCOUNT_KEY, longId);
|
2010-09-14 23:28:50 +00:00
|
|
|
return insert(Mailbox.CONTENT_URI, values); // Recurse
|
2009-11-19 01:11:33 +00:00
|
|
|
case ATTACHMENTS_MESSAGE_ID:
|
2011-05-06 18:31:10 +00:00
|
|
|
longId = db.insert(TABLE_NAMES[table], "foo", values);
|
|
|
|
resultUri = ContentUris.withAppendedId(Attachment.CONTENT_URI, longId);
|
2009-11-19 01:11:33 +00:00
|
|
|
break;
|
|
|
|
default:
|
2009-10-13 23:25:00 +00:00
|
|
|
throw new IllegalArgumentException("Unknown URL " + uri);
|
2009-11-19 01:11:33 +00:00
|
|
|
}
|
|
|
|
} catch (SQLiteException e) {
|
|
|
|
checkDatabases();
|
|
|
|
throw e;
|
2009-05-26 23:40:34 +00:00
|
|
|
}
|
2009-06-27 19:14:14 +00:00
|
|
|
|
2011-05-06 18:31:10 +00:00
|
|
|
// Notify all notifier cursors
|
2011-05-11 22:29:24 +00:00
|
|
|
sendNotifierChange(getBaseNotificationUri(match), NOTIFICATION_OP_INSERT, id);
|
2011-05-06 18:31:10 +00:00
|
|
|
|
2010-09-14 23:28:50 +00:00
|
|
|
// Notify all existing cursors.
|
2010-09-23 16:19:44 +00:00
|
|
|
resolver.notifyChange(EmailContent.CONTENT_URI, null);
|
2009-06-01 21:34:16 +00:00
|
|
|
return resultUri;
|
2009-05-26 23:40:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public boolean onCreate() {
|
2012-06-28 17:40:46 +00:00
|
|
|
MailActivityEmail.setServicesEnabledAsync(getContext());
|
2009-11-19 01:11:33 +00:00
|
|
|
checkDatabases();
|
2009-05-26 23:40:34 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2009-11-19 01:11:33 +00:00
|
|
|
/**
|
|
|
|
* The idea here is that the two databases (EmailProvider.db and EmailProviderBody.db must
|
|
|
|
* always be in sync (i.e. there are two database or NO databases). This code will delete
|
|
|
|
* any "orphan" database, so that both will be created together. Note that an "orphan" database
|
|
|
|
* will exist after either of the individual databases is deleted due to data corruption.
|
|
|
|
*/
|
|
|
|
public void checkDatabases() {
|
|
|
|
// Uncache the databases
|
|
|
|
if (mDatabase != null) {
|
|
|
|
mDatabase = null;
|
|
|
|
}
|
|
|
|
if (mBodyDatabase != null) {
|
|
|
|
mBodyDatabase = null;
|
|
|
|
}
|
|
|
|
// Look for orphans, and delete as necessary; these must always be in sync
|
|
|
|
File databaseFile = getContext().getDatabasePath(DATABASE_NAME);
|
|
|
|
File bodyFile = getContext().getDatabasePath(BODY_DATABASE_NAME);
|
|
|
|
|
|
|
|
// TODO Make sure attachments are deleted
|
|
|
|
if (databaseFile.exists() && !bodyFile.exists()) {
|
|
|
|
Log.w(TAG, "Deleting orphaned EmailProvider database...");
|
|
|
|
databaseFile.delete();
|
|
|
|
} else if (bodyFile.exists() && !databaseFile.exists()) {
|
|
|
|
Log.w(TAG, "Deleting orphaned EmailProviderBody database...");
|
|
|
|
bodyFile.delete();
|
|
|
|
}
|
|
|
|
}
|
2009-05-26 23:40:34 +00:00
|
|
|
@Override
|
2009-07-30 18:41:31 +00:00
|
|
|
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
|
2009-05-29 21:24:34 +00:00
|
|
|
String sortOrder) {
|
2010-10-27 23:50:54 +00:00
|
|
|
long time = 0L;
|
2012-06-28 17:40:46 +00:00
|
|
|
if (MailActivityEmail.DEBUG) {
|
2010-10-27 23:50:54 +00:00
|
|
|
time = System.nanoTime();
|
|
|
|
}
|
2009-05-26 23:40:34 +00:00
|
|
|
Cursor c = null;
|
2010-12-30 22:55:27 +00:00
|
|
|
int match;
|
|
|
|
try {
|
|
|
|
match = findMatch(uri, "query");
|
|
|
|
} catch (IllegalArgumentException e) {
|
|
|
|
String uriString = uri.toString();
|
|
|
|
// If we were passed an illegal uri, see if it ends in /-1
|
|
|
|
// if so, and if substituting 0 for -1 results in a valid uri, return an empty cursor
|
|
|
|
if (uriString != null && uriString.endsWith("/-1")) {
|
|
|
|
uri = Uri.parse(uriString.substring(0, uriString.length() - 2) + "0");
|
|
|
|
match = findMatch(uri, "query");
|
|
|
|
switch (match) {
|
|
|
|
case BODY_ID:
|
|
|
|
case MESSAGE_ID:
|
|
|
|
case DELETED_MESSAGE_ID:
|
|
|
|
case UPDATED_MESSAGE_ID:
|
|
|
|
case ATTACHMENT_ID:
|
|
|
|
case MAILBOX_ID:
|
|
|
|
case ACCOUNT_ID:
|
|
|
|
case HOSTAUTH_ID:
|
2011-04-28 00:12:06 +00:00
|
|
|
case POLICY_ID:
|
2010-12-30 22:55:27 +00:00
|
|
|
return new MatrixCursor(projection, 0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
throw e;
|
|
|
|
}
|
2009-06-15 21:40:06 +00:00
|
|
|
Context context = getContext();
|
2009-08-06 00:31:50 +00:00
|
|
|
// See the comment at delete(), above
|
2009-08-18 18:44:27 +00:00
|
|
|
SQLiteDatabase db = getDatabase(context);
|
2009-05-26 23:40:34 +00:00
|
|
|
int table = match >> BASE_SHIFT;
|
2010-09-29 15:43:31 +00:00
|
|
|
String limit = uri.getQueryParameter(EmailContent.PARAMETER_LIMIT);
|
2009-05-26 23:40:34 +00:00
|
|
|
String id;
|
2009-06-27 19:14:14 +00:00
|
|
|
|
2010-10-27 23:50:54 +00:00
|
|
|
// Find the cache for this query's table (if any)
|
|
|
|
ContentCache cache = null;
|
|
|
|
String tableName = TABLE_NAMES[table];
|
|
|
|
// We can only use the cache if there's no selection
|
|
|
|
if (selection == null) {
|
Improve EmailContent caching...
* Guarantee that up to 16 Account (with HostAuths), and Policy rows
are always cached. Also, 6 commonly used Mailboxes per Account
(Inbox, Outbox, Drafts, Sent, Trash, and Search)
* Precache these rows when EmailProvider starts up
* Ensure that newly added, precachable rows are cached when created
* Clean up some inefficient/wrong caching code
* Fix a commonly called method in NotificationManager in which we
load a single Mailbox row using selection vs withAppendedId
* Confirm that we don't read from the database in typical use and
heavy message loading
* Add a special URI for finding mailbox by type (using the cache)
* Add special-case code for EmailContent.count(Account.CONTENT_URI)
which is used in a number of places (including on the UI thread)
and whose value is easily determined
* Add a special URI to get the default account id
* Confirm that all unit tests work
The goal here is to be able to load all Account, HostAuth, Policy,
and Mailbox objects (by id) without worrying about disk access.
There will still be a single disk read for uncommon Mailbox reads,
but this should be considered acceptable.
Change-Id: Ibc9aa7acc73185e360b0b6f3053b90a985e97210
TODO: Unit tests
2011-06-19 01:03:11 +00:00
|
|
|
cache = mContentCaches[table];
|
2010-10-27 23:50:54 +00:00
|
|
|
}
|
|
|
|
if (cache == null) {
|
|
|
|
ContentCache.notCacheable(uri, selection);
|
|
|
|
}
|
|
|
|
|
2009-11-19 01:11:33 +00:00
|
|
|
try {
|
|
|
|
switch (match) {
|
2012-06-28 17:40:46 +00:00
|
|
|
// First, dispatch queries from UnfiedEmail
|
|
|
|
case UI_SEARCH:
|
|
|
|
return uiSearch(uri, projection);
|
|
|
|
case UI_ACCTS:
|
|
|
|
c = uiAccounts(projection);
|
|
|
|
return c;
|
|
|
|
case UI_UNDO:
|
|
|
|
return uiUndo(projection);
|
|
|
|
case UI_SUBFOLDERS:
|
|
|
|
case UI_MESSAGES:
|
|
|
|
case UI_MESSAGE:
|
|
|
|
case UI_FOLDER:
|
|
|
|
case UI_ACCOUNT:
|
|
|
|
case UI_ATTACHMENT:
|
|
|
|
case UI_ATTACHMENTS:
|
|
|
|
case UI_CONVERSATION:
|
|
|
|
case UI_RECENT_FOLDERS:
|
|
|
|
case UI_ALL_FOLDERS:
|
|
|
|
// For now, we don't allow selection criteria within these queries
|
|
|
|
if (selection != null || selectionArgs != null) {
|
|
|
|
throw new IllegalArgumentException("UI queries can't have selection/args");
|
|
|
|
}
|
|
|
|
c = uiQuery(match, uri, projection);
|
|
|
|
return c;
|
|
|
|
case UI_FOLDERS:
|
|
|
|
c = uiFolders(uri, projection);
|
|
|
|
return c;
|
|
|
|
case UI_FOLDER_LOAD_MORE:
|
|
|
|
c = uiFolderLoadMore(uri);
|
|
|
|
return c;
|
|
|
|
case UI_FOLDER_REFRESH:
|
|
|
|
c = uiFolderRefresh(uri);
|
|
|
|
return c;
|
|
|
|
case MAILBOX_NOTIFICATION:
|
|
|
|
c = notificationQuery(uri);
|
|
|
|
return c;
|
|
|
|
case MAILBOX_MOST_RECENT_MESSAGE:
|
|
|
|
c = mostRecentMessageQuery(uri);
|
|
|
|
return c;
|
Improve EmailContent caching...
* Guarantee that up to 16 Account (with HostAuths), and Policy rows
are always cached. Also, 6 commonly used Mailboxes per Account
(Inbox, Outbox, Drafts, Sent, Trash, and Search)
* Precache these rows when EmailProvider starts up
* Ensure that newly added, precachable rows are cached when created
* Clean up some inefficient/wrong caching code
* Fix a commonly called method in NotificationManager in which we
load a single Mailbox row using selection vs withAppendedId
* Confirm that we don't read from the database in typical use and
heavy message loading
* Add a special URI for finding mailbox by type (using the cache)
* Add special-case code for EmailContent.count(Account.CONTENT_URI)
which is used in a number of places (including on the UI thread)
and whose value is easily determined
* Add a special URI to get the default account id
* Confirm that all unit tests work
The goal here is to be able to load all Account, HostAuth, Policy,
and Mailbox objects (by id) without worrying about disk access.
There will still be a single disk read for uncommon Mailbox reads,
but this should be considered acceptable.
Change-Id: Ibc9aa7acc73185e360b0b6f3053b90a985e97210
TODO: Unit tests
2011-06-19 01:03:11 +00:00
|
|
|
case ACCOUNT_DEFAULT_ID:
|
|
|
|
// Start with a snapshot of the cache
|
|
|
|
Map<String, Cursor> accountCache = mCacheAccount.getSnapshot();
|
|
|
|
long accountId = Account.NO_ACCOUNT;
|
2011-07-13 21:43:21 +00:00
|
|
|
// Find the account with "isDefault" set, or the lowest account ID otherwise.
|
|
|
|
// Note that the snapshot from the cached isn't guaranteed to be sorted in any
|
|
|
|
// way.
|
Improve EmailContent caching...
* Guarantee that up to 16 Account (with HostAuths), and Policy rows
are always cached. Also, 6 commonly used Mailboxes per Account
(Inbox, Outbox, Drafts, Sent, Trash, and Search)
* Precache these rows when EmailProvider starts up
* Ensure that newly added, precachable rows are cached when created
* Clean up some inefficient/wrong caching code
* Fix a commonly called method in NotificationManager in which we
load a single Mailbox row using selection vs withAppendedId
* Confirm that we don't read from the database in typical use and
heavy message loading
* Add a special URI for finding mailbox by type (using the cache)
* Add special-case code for EmailContent.count(Account.CONTENT_URI)
which is used in a number of places (including on the UI thread)
and whose value is easily determined
* Add a special URI to get the default account id
* Confirm that all unit tests work
The goal here is to be able to load all Account, HostAuth, Policy,
and Mailbox objects (by id) without worrying about disk access.
There will still be a single disk read for uncommon Mailbox reads,
but this should be considered acceptable.
Change-Id: Ibc9aa7acc73185e360b0b6f3053b90a985e97210
TODO: Unit tests
2011-06-19 01:03:11 +00:00
|
|
|
Collection<Cursor> accounts = accountCache.values();
|
|
|
|
for (Cursor accountCursor: accounts) {
|
2011-06-23 01:36:28 +00:00
|
|
|
// For now, at least, we can have zero count cursors (e.g. if someone looks
|
|
|
|
// up a non-existent id); we need to skip these
|
|
|
|
if (accountCursor.moveToFirst()) {
|
|
|
|
boolean isDefault =
|
|
|
|
accountCursor.getInt(Account.CONTENT_IS_DEFAULT_COLUMN) == 1;
|
2011-07-13 21:43:21 +00:00
|
|
|
long iterId = accountCursor.getLong(Account.CONTENT_ID_COLUMN);
|
2011-06-23 01:36:28 +00:00
|
|
|
// We'll remember this one if it's the default or the first one we see
|
2011-07-13 21:43:21 +00:00
|
|
|
if (isDefault) {
|
|
|
|
accountId = iterId;
|
|
|
|
break;
|
|
|
|
} else if ((accountId == Account.NO_ACCOUNT) || (iterId < accountId)) {
|
|
|
|
accountId = iterId;
|
2011-06-23 01:36:28 +00:00
|
|
|
}
|
Improve EmailContent caching...
* Guarantee that up to 16 Account (with HostAuths), and Policy rows
are always cached. Also, 6 commonly used Mailboxes per Account
(Inbox, Outbox, Drafts, Sent, Trash, and Search)
* Precache these rows when EmailProvider starts up
* Ensure that newly added, precachable rows are cached when created
* Clean up some inefficient/wrong caching code
* Fix a commonly called method in NotificationManager in which we
load a single Mailbox row using selection vs withAppendedId
* Confirm that we don't read from the database in typical use and
heavy message loading
* Add a special URI for finding mailbox by type (using the cache)
* Add special-case code for EmailContent.count(Account.CONTENT_URI)
which is used in a number of places (including on the UI thread)
and whose value is easily determined
* Add a special URI to get the default account id
* Confirm that all unit tests work
The goal here is to be able to load all Account, HostAuth, Policy,
and Mailbox objects (by id) without worrying about disk access.
There will still be a single disk read for uncommon Mailbox reads,
but this should be considered acceptable.
Change-Id: Ibc9aa7acc73185e360b0b6f3053b90a985e97210
TODO: Unit tests
2011-06-19 01:03:11 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
// Return a cursor with an id projection
|
|
|
|
MatrixCursor mc = new MatrixCursor(EmailContent.ID_PROJECTION);
|
|
|
|
mc.addRow(new Object[] {accountId});
|
2011-07-28 04:57:06 +00:00
|
|
|
c = mc;
|
|
|
|
break;
|
Improve EmailContent caching...
* Guarantee that up to 16 Account (with HostAuths), and Policy rows
are always cached. Also, 6 commonly used Mailboxes per Account
(Inbox, Outbox, Drafts, Sent, Trash, and Search)
* Precache these rows when EmailProvider starts up
* Ensure that newly added, precachable rows are cached when created
* Clean up some inefficient/wrong caching code
* Fix a commonly called method in NotificationManager in which we
load a single Mailbox row using selection vs withAppendedId
* Confirm that we don't read from the database in typical use and
heavy message loading
* Add a special URI for finding mailbox by type (using the cache)
* Add special-case code for EmailContent.count(Account.CONTENT_URI)
which is used in a number of places (including on the UI thread)
and whose value is easily determined
* Add a special URI to get the default account id
* Confirm that all unit tests work
The goal here is to be able to load all Account, HostAuth, Policy,
and Mailbox objects (by id) without worrying about disk access.
There will still be a single disk read for uncommon Mailbox reads,
but this should be considered acceptable.
Change-Id: Ibc9aa7acc73185e360b0b6f3053b90a985e97210
TODO: Unit tests
2011-06-19 01:03:11 +00:00
|
|
|
case MAILBOX_ID_FROM_ACCOUNT_AND_TYPE:
|
|
|
|
// Get accountId and type and find the mailbox in our map
|
2011-06-28 03:11:24 +00:00
|
|
|
List<String> pathSegments = uri.getPathSegments();
|
|
|
|
accountId = Long.parseLong(pathSegments.get(1));
|
|
|
|
int type = Integer.parseInt(pathSegments.get(2));
|
|
|
|
long mailboxId = getMailboxIdFromMailboxTypeMap(accountId, type);
|
Improve EmailContent caching...
* Guarantee that up to 16 Account (with HostAuths), and Policy rows
are always cached. Also, 6 commonly used Mailboxes per Account
(Inbox, Outbox, Drafts, Sent, Trash, and Search)
* Precache these rows when EmailProvider starts up
* Ensure that newly added, precachable rows are cached when created
* Clean up some inefficient/wrong caching code
* Fix a commonly called method in NotificationManager in which we
load a single Mailbox row using selection vs withAppendedId
* Confirm that we don't read from the database in typical use and
heavy message loading
* Add a special URI for finding mailbox by type (using the cache)
* Add special-case code for EmailContent.count(Account.CONTENT_URI)
which is used in a number of places (including on the UI thread)
and whose value is easily determined
* Add a special URI to get the default account id
* Confirm that all unit tests work
The goal here is to be able to load all Account, HostAuth, Policy,
and Mailbox objects (by id) without worrying about disk access.
There will still be a single disk read for uncommon Mailbox reads,
but this should be considered acceptable.
Change-Id: Ibc9aa7acc73185e360b0b6f3053b90a985e97210
TODO: Unit tests
2011-06-19 01:03:11 +00:00
|
|
|
// Return a cursor with an id projection
|
2011-06-28 03:11:24 +00:00
|
|
|
mc = new MatrixCursor(EmailContent.ID_PROJECTION);
|
Improve EmailContent caching...
* Guarantee that up to 16 Account (with HostAuths), and Policy rows
are always cached. Also, 6 commonly used Mailboxes per Account
(Inbox, Outbox, Drafts, Sent, Trash, and Search)
* Precache these rows when EmailProvider starts up
* Ensure that newly added, precachable rows are cached when created
* Clean up some inefficient/wrong caching code
* Fix a commonly called method in NotificationManager in which we
load a single Mailbox row using selection vs withAppendedId
* Confirm that we don't read from the database in typical use and
heavy message loading
* Add a special URI for finding mailbox by type (using the cache)
* Add special-case code for EmailContent.count(Account.CONTENT_URI)
which is used in a number of places (including on the UI thread)
and whose value is easily determined
* Add a special URI to get the default account id
* Confirm that all unit tests work
The goal here is to be able to load all Account, HostAuth, Policy,
and Mailbox objects (by id) without worrying about disk access.
There will still be a single disk read for uncommon Mailbox reads,
but this should be considered acceptable.
Change-Id: Ibc9aa7acc73185e360b0b6f3053b90a985e97210
TODO: Unit tests
2011-06-19 01:03:11 +00:00
|
|
|
mc.addRow(new Object[] {mailboxId});
|
2011-07-28 04:57:06 +00:00
|
|
|
c = mc;
|
|
|
|
break;
|
2009-11-19 01:11:33 +00:00
|
|
|
case BODY:
|
|
|
|
case MESSAGE:
|
|
|
|
case UPDATED_MESSAGE:
|
|
|
|
case DELETED_MESSAGE:
|
|
|
|
case ATTACHMENT:
|
|
|
|
case MAILBOX:
|
|
|
|
case ACCOUNT:
|
|
|
|
case HOSTAUTH:
|
2011-04-28 00:12:06 +00:00
|
|
|
case POLICY:
|
2011-06-01 17:09:26 +00:00
|
|
|
case QUICK_RESPONSE:
|
Improve EmailContent caching...
* Guarantee that up to 16 Account (with HostAuths), and Policy rows
are always cached. Also, 6 commonly used Mailboxes per Account
(Inbox, Outbox, Drafts, Sent, Trash, and Search)
* Precache these rows when EmailProvider starts up
* Ensure that newly added, precachable rows are cached when created
* Clean up some inefficient/wrong caching code
* Fix a commonly called method in NotificationManager in which we
load a single Mailbox row using selection vs withAppendedId
* Confirm that we don't read from the database in typical use and
heavy message loading
* Add a special URI for finding mailbox by type (using the cache)
* Add special-case code for EmailContent.count(Account.CONTENT_URI)
which is used in a number of places (including on the UI thread)
and whose value is easily determined
* Add a special URI to get the default account id
* Confirm that all unit tests work
The goal here is to be able to load all Account, HostAuth, Policy,
and Mailbox objects (by id) without worrying about disk access.
There will still be a single disk read for uncommon Mailbox reads,
but this should be considered acceptable.
Change-Id: Ibc9aa7acc73185e360b0b6f3053b90a985e97210
TODO: Unit tests
2011-06-19 01:03:11 +00:00
|
|
|
// Special-case "count of accounts"; it's common and we always know it
|
|
|
|
if (match == ACCOUNT && Arrays.equals(projection, EmailContent.COUNT_COLUMNS) &&
|
|
|
|
selection == null && limit.equals("1")) {
|
|
|
|
int accountCount = mMailboxTypeMap.size();
|
|
|
|
// In the rare case there are MAX_CACHED_ACCOUNTS or more, we can't do this
|
|
|
|
if (accountCount < MAX_CACHED_ACCOUNTS) {
|
|
|
|
mc = new MatrixCursor(projection, 1);
|
|
|
|
mc.addRow(new Object[] {accountCount});
|
2011-08-05 16:50:56 +00:00
|
|
|
c = mc;
|
|
|
|
break;
|
Improve EmailContent caching...
* Guarantee that up to 16 Account (with HostAuths), and Policy rows
are always cached. Also, 6 commonly used Mailboxes per Account
(Inbox, Outbox, Drafts, Sent, Trash, and Search)
* Precache these rows when EmailProvider starts up
* Ensure that newly added, precachable rows are cached when created
* Clean up some inefficient/wrong caching code
* Fix a commonly called method in NotificationManager in which we
load a single Mailbox row using selection vs withAppendedId
* Confirm that we don't read from the database in typical use and
heavy message loading
* Add a special URI for finding mailbox by type (using the cache)
* Add special-case code for EmailContent.count(Account.CONTENT_URI)
which is used in a number of places (including on the UI thread)
and whose value is easily determined
* Add a special URI to get the default account id
* Confirm that all unit tests work
The goal here is to be able to load all Account, HostAuth, Policy,
and Mailbox objects (by id) without worrying about disk access.
There will still be a single disk read for uncommon Mailbox reads,
but this should be considered acceptable.
Change-Id: Ibc9aa7acc73185e360b0b6f3053b90a985e97210
TODO: Unit tests
2011-06-19 01:03:11 +00:00
|
|
|
}
|
|
|
|
}
|
2010-10-27 23:50:54 +00:00
|
|
|
c = db.query(tableName, projection,
|
2010-09-28 01:29:50 +00:00
|
|
|
selection, selectionArgs, null, null, sortOrder, limit);
|
2009-11-19 01:11:33 +00:00
|
|
|
break;
|
|
|
|
case BODY_ID:
|
|
|
|
case MESSAGE_ID:
|
|
|
|
case DELETED_MESSAGE_ID:
|
|
|
|
case UPDATED_MESSAGE_ID:
|
|
|
|
case ATTACHMENT_ID:
|
|
|
|
case MAILBOX_ID:
|
|
|
|
case ACCOUNT_ID:
|
|
|
|
case HOSTAUTH_ID:
|
2011-04-28 00:12:06 +00:00
|
|
|
case POLICY_ID:
|
2011-06-01 17:09:26 +00:00
|
|
|
case QUICK_RESPONSE_ID:
|
2009-11-19 01:11:33 +00:00
|
|
|
id = uri.getPathSegments().get(1);
|
2010-10-27 23:50:54 +00:00
|
|
|
if (cache != null) {
|
|
|
|
c = cache.getCachedCursor(id, projection);
|
|
|
|
}
|
|
|
|
if (c == null) {
|
|
|
|
CacheToken token = null;
|
|
|
|
if (cache != null) {
|
|
|
|
token = cache.getCacheToken(id);
|
|
|
|
}
|
|
|
|
c = db.query(tableName, projection, whereWithId(id, selection),
|
|
|
|
selectionArgs, null, null, sortOrder, limit);
|
|
|
|
if (cache != null) {
|
|
|
|
c = cache.putCursor(c, id, projection, token);
|
|
|
|
}
|
|
|
|
}
|
2009-11-19 01:11:33 +00:00
|
|
|
break;
|
|
|
|
case ATTACHMENTS_MESSAGE_ID:
|
|
|
|
// All attachments for the given message
|
|
|
|
id = uri.getPathSegments().get(2);
|
|
|
|
c = db.query(Attachment.TABLE_NAME, projection,
|
|
|
|
whereWith(Attachment.MESSAGE_KEY + "=" + id, selection),
|
2010-09-28 01:29:50 +00:00
|
|
|
selectionArgs, null, null, sortOrder, limit);
|
2009-11-19 01:11:33 +00:00
|
|
|
break;
|
2011-06-01 17:09:26 +00:00
|
|
|
case QUICK_RESPONSE_ACCOUNT_ID:
|
|
|
|
// All quick responses for the given account
|
|
|
|
id = uri.getPathSegments().get(2);
|
|
|
|
c = db.query(QuickResponse.TABLE_NAME, projection,
|
|
|
|
whereWith(QuickResponse.ACCOUNT_KEY + "=" + id, selection),
|
|
|
|
selectionArgs, null, null, sortOrder);
|
|
|
|
break;
|
2009-11-19 01:11:33 +00:00
|
|
|
default:
|
|
|
|
throw new IllegalArgumentException("Unknown URI " + uri);
|
|
|
|
}
|
|
|
|
} catch (SQLiteException e) {
|
|
|
|
checkDatabases();
|
|
|
|
throw e;
|
2010-10-27 23:50:54 +00:00
|
|
|
} catch (RuntimeException e) {
|
|
|
|
checkDatabases();
|
|
|
|
e.printStackTrace();
|
|
|
|
throw e;
|
|
|
|
} finally {
|
2012-06-28 17:40:46 +00:00
|
|
|
if (cache != null && c != null && MailActivityEmail.DEBUG) {
|
2010-10-27 23:50:54 +00:00
|
|
|
cache.recordQueryTime(c, System.nanoTime() - time);
|
|
|
|
}
|
2011-07-27 23:48:35 +00:00
|
|
|
if (c == null) {
|
|
|
|
// This should never happen, but let's be sure to log it...
|
|
|
|
Log.e(TAG, "Query returning null for uri: " + uri + ", selection: " + selection);
|
|
|
|
}
|
2009-05-26 23:40:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if ((c != null) && !isTemporary()) {
|
2010-09-14 23:28:50 +00:00
|
|
|
c.setNotificationUri(getContext().getContentResolver(), uri);
|
2009-05-26 23:40:34 +00:00
|
|
|
}
|
|
|
|
return c;
|
|
|
|
}
|
|
|
|
|
|
|
|
private String whereWithId(String id, String selection) {
|
|
|
|
StringBuilder sb = new StringBuilder(256);
|
|
|
|
sb.append("_id=");
|
|
|
|
sb.append(id);
|
|
|
|
if (selection != null) {
|
2009-09-10 18:52:36 +00:00
|
|
|
sb.append(" AND (");
|
2009-05-26 23:40:34 +00:00
|
|
|
sb.append(selection);
|
2009-09-10 18:52:36 +00:00
|
|
|
sb.append(')');
|
2009-05-26 23:40:34 +00:00
|
|
|
}
|
|
|
|
return sb.toString();
|
|
|
|
}
|
2009-06-27 19:14:14 +00:00
|
|
|
|
2009-09-10 18:52:36 +00:00
|
|
|
/**
|
|
|
|
* Combine a locally-generated selection with a user-provided selection
|
|
|
|
*
|
|
|
|
* This introduces risk that the local selection might insert incorrect chars
|
|
|
|
* into the SQL, so use caution.
|
|
|
|
*
|
|
|
|
* @param where locally-generated selection, must not be null
|
|
|
|
* @param selection user-provided selection, may be null
|
|
|
|
* @return a single selection string
|
|
|
|
*/
|
2009-05-26 23:40:34 +00:00
|
|
|
private String whereWith(String where, String selection) {
|
2009-09-10 18:52:36 +00:00
|
|
|
if (selection == null) {
|
|
|
|
return where;
|
2009-05-26 23:40:34 +00:00
|
|
|
}
|
2009-09-10 18:52:36 +00:00
|
|
|
StringBuilder sb = new StringBuilder(where);
|
|
|
|
sb.append(" AND (");
|
|
|
|
sb.append(selection);
|
|
|
|
sb.append(')');
|
|
|
|
|
2009-05-26 23:40:34 +00:00
|
|
|
return sb.toString();
|
|
|
|
}
|
2009-06-27 19:14:14 +00:00
|
|
|
|
2011-05-06 19:07:39 +00:00
|
|
|
/**
|
|
|
|
* Restore a HostAuth from a database, given its unique id
|
|
|
|
* @param db the database
|
|
|
|
* @param id the unique id (_id) of the row
|
|
|
|
* @return a fully populated HostAuth or null if the row does not exist
|
|
|
|
*/
|
2011-06-21 01:10:10 +00:00
|
|
|
private static HostAuth restoreHostAuth(SQLiteDatabase db, long id) {
|
2011-05-06 19:07:39 +00:00
|
|
|
Cursor c = db.query(HostAuth.TABLE_NAME, HostAuth.CONTENT_PROJECTION,
|
|
|
|
HostAuth.RECORD_ID + "=?", new String[] {Long.toString(id)}, null, null, null);
|
|
|
|
try {
|
|
|
|
if (c.moveToFirst()) {
|
|
|
|
HostAuth hostAuth = new HostAuth();
|
|
|
|
hostAuth.restore(c);
|
|
|
|
return hostAuth;
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
} finally {
|
|
|
|
c.close();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Copy the Account and HostAuth tables from one database to another
|
|
|
|
* @param fromDatabase the source database
|
|
|
|
* @param toDatabase the destination database
|
|
|
|
* @return the number of accounts copied, or -1 if an error occurred
|
|
|
|
*/
|
2011-06-21 01:10:10 +00:00
|
|
|
private static int copyAccountTables(SQLiteDatabase fromDatabase, SQLiteDatabase toDatabase) {
|
2011-05-06 19:07:39 +00:00
|
|
|
if (fromDatabase == null || toDatabase == null) return -1;
|
2012-06-28 17:40:46 +00:00
|
|
|
|
|
|
|
// Lock both databases; for the "from" database, we don't want anyone changing it from
|
|
|
|
// under us; for the "to" database, we want to make the operation atomic
|
2011-05-06 19:07:39 +00:00
|
|
|
int copyCount = 0;
|
2012-06-28 17:40:46 +00:00
|
|
|
fromDatabase.beginTransaction();
|
2011-05-06 19:07:39 +00:00
|
|
|
try {
|
|
|
|
toDatabase.beginTransaction();
|
|
|
|
try {
|
2012-06-28 17:40:46 +00:00
|
|
|
// Delete anything hanging around here
|
|
|
|
toDatabase.delete(Account.TABLE_NAME, null, null);
|
|
|
|
toDatabase.delete(HostAuth.TABLE_NAME, null, null);
|
|
|
|
|
|
|
|
// Get our account cursor
|
|
|
|
Cursor c = fromDatabase.query(Account.TABLE_NAME, Account.CONTENT_PROJECTION,
|
|
|
|
null, null, null, null, null);
|
|
|
|
if (c == null) return 0;
|
|
|
|
Log.d(TAG, "fromDatabase accounts: " + c.getCount());
|
|
|
|
try {
|
|
|
|
// Loop through accounts, copying them and associated host auth's
|
|
|
|
while (c.moveToNext()) {
|
|
|
|
Account account = new Account();
|
|
|
|
account.restore(c);
|
|
|
|
|
|
|
|
// Clear security sync key and sync key, as these were specific to the
|
|
|
|
// state of the account, and we've reset that...
|
|
|
|
// Clear policy key so that we can re-establish policies from the server
|
|
|
|
// TODO This is pretty EAS specific, but there's a lot of that around
|
|
|
|
account.mSecuritySyncKey = null;
|
|
|
|
account.mSyncKey = null;
|
|
|
|
account.mPolicyKey = 0;
|
|
|
|
|
|
|
|
// Copy host auth's and update foreign keys
|
|
|
|
HostAuth hostAuth = restoreHostAuth(fromDatabase,
|
|
|
|
account.mHostAuthKeyRecv);
|
|
|
|
|
|
|
|
// The account might have gone away, though very unlikely
|
2011-05-06 19:07:39 +00:00
|
|
|
if (hostAuth == null) continue;
|
2012-06-28 17:40:46 +00:00
|
|
|
account.mHostAuthKeyRecv = toDatabase.insert(HostAuth.TABLE_NAME, null,
|
2011-05-06 19:07:39 +00:00
|
|
|
hostAuth.toContentValues());
|
2012-06-28 17:40:46 +00:00
|
|
|
|
|
|
|
// EAS accounts have no send HostAuth
|
|
|
|
if (account.mHostAuthKeySend > 0) {
|
|
|
|
hostAuth = restoreHostAuth(fromDatabase, account.mHostAuthKeySend);
|
|
|
|
// Belt and suspenders; I can't imagine that this is possible,
|
|
|
|
// since we checked the validity of the account above, and the
|
|
|
|
// database is now locked
|
|
|
|
if (hostAuth == null) continue;
|
|
|
|
account.mHostAuthKeySend = toDatabase.insert(
|
|
|
|
HostAuth.TABLE_NAME, null, hostAuth.toContentValues());
|
|
|
|
}
|
|
|
|
|
|
|
|
// Now, create the account in the "to" database
|
|
|
|
toDatabase.insert(Account.TABLE_NAME, null, account.toContentValues());
|
|
|
|
copyCount++;
|
2011-05-06 19:07:39 +00:00
|
|
|
}
|
2012-06-28 17:40:46 +00:00
|
|
|
} finally {
|
|
|
|
c.close();
|
2011-05-06 19:07:39 +00:00
|
|
|
}
|
2012-06-28 17:40:46 +00:00
|
|
|
|
|
|
|
// Say it's ok to commit
|
|
|
|
toDatabase.setTransactionSuccessful();
|
2011-05-06 19:07:39 +00:00
|
|
|
} finally {
|
2012-06-28 17:40:46 +00:00
|
|
|
// STOPSHIP: Remove logging here and in at endTransaction() below
|
|
|
|
Log.d(TAG, "ending toDatabase transaction; copyCount = " + copyCount);
|
2011-05-06 19:07:39 +00:00
|
|
|
toDatabase.endTransaction();
|
|
|
|
}
|
2012-06-28 17:40:46 +00:00
|
|
|
} catch (SQLiteException ex) {
|
|
|
|
Log.w(TAG, "Exception while copying account tables", ex);
|
2011-05-06 19:07:39 +00:00
|
|
|
copyCount = -1;
|
2012-06-28 17:40:46 +00:00
|
|
|
} finally {
|
|
|
|
Log.d(TAG, "ending fromDatabase transaction; copyCount = " + copyCount);
|
|
|
|
fromDatabase.endTransaction();
|
2011-05-06 19:07:39 +00:00
|
|
|
}
|
|
|
|
return copyCount;
|
|
|
|
}
|
|
|
|
|
2011-06-21 01:10:10 +00:00
|
|
|
private static SQLiteDatabase getBackupDatabase(Context context) {
|
2012-06-28 17:40:46 +00:00
|
|
|
DBHelper.DatabaseHelper helper = new DBHelper.DatabaseHelper(context, BACKUP_DATABASE_NAME);
|
2011-05-06 19:07:39 +00:00
|
|
|
return helper.getWritableDatabase();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Backup account data, returning the number of accounts backed up
|
|
|
|
*/
|
2011-06-21 01:10:10 +00:00
|
|
|
private static int backupAccounts(Context context, SQLiteDatabase mainDatabase) {
|
2012-06-28 17:40:46 +00:00
|
|
|
if (MailActivityEmail.DEBUG) {
|
2011-06-21 01:10:10 +00:00
|
|
|
Log.d(TAG, "backupAccounts...");
|
|
|
|
}
|
2011-05-06 19:07:39 +00:00
|
|
|
SQLiteDatabase backupDatabase = getBackupDatabase(context);
|
|
|
|
try {
|
2011-06-21 01:10:10 +00:00
|
|
|
int numBackedUp = copyAccountTables(mainDatabase, backupDatabase);
|
|
|
|
if (numBackedUp < 0) {
|
|
|
|
Log.e(TAG, "Account backup failed!");
|
2012-06-28 17:40:46 +00:00
|
|
|
} else if (MailActivityEmail.DEBUG) {
|
2011-06-21 01:10:10 +00:00
|
|
|
Log.d(TAG, "Backed up " + numBackedUp + " accounts...");
|
|
|
|
}
|
|
|
|
return numBackedUp;
|
2011-05-06 19:07:39 +00:00
|
|
|
} finally {
|
|
|
|
if (backupDatabase != null) {
|
|
|
|
backupDatabase.close();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Restore account data, returning the number of accounts restored
|
|
|
|
*/
|
2011-06-21 01:10:10 +00:00
|
|
|
private static int restoreAccounts(Context context, SQLiteDatabase mainDatabase) {
|
2012-06-28 17:40:46 +00:00
|
|
|
if (MailActivityEmail.DEBUG) {
|
2011-06-21 01:10:10 +00:00
|
|
|
Log.d(TAG, "restoreAccounts...");
|
|
|
|
}
|
2011-05-06 19:07:39 +00:00
|
|
|
SQLiteDatabase backupDatabase = getBackupDatabase(context);
|
|
|
|
try {
|
2011-06-21 01:10:10 +00:00
|
|
|
int numRecovered = copyAccountTables(backupDatabase, mainDatabase);
|
|
|
|
if (numRecovered > 0) {
|
|
|
|
Log.e(TAG, "Recovered " + numRecovered + " accounts!");
|
|
|
|
} else if (numRecovered < 0) {
|
|
|
|
Log.e(TAG, "Account recovery failed?");
|
2012-06-28 17:40:46 +00:00
|
|
|
} else if (MailActivityEmail.DEBUG) {
|
2011-06-21 01:10:10 +00:00
|
|
|
Log.d(TAG, "No accounts to restore...");
|
|
|
|
}
|
|
|
|
return numRecovered;
|
2011-05-06 19:07:39 +00:00
|
|
|
} finally {
|
|
|
|
if (backupDatabase != null) {
|
|
|
|
backupDatabase.close();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-06-28 17:40:46 +00:00
|
|
|
// select count(*) from (select count(*) as dupes from Mailbox where accountKey=?
|
|
|
|
// group by serverId) where dupes > 1;
|
|
|
|
private static final String ACCOUNT_INTEGRITY_SQL =
|
|
|
|
"select count(*) from (select count(*) as dupes from " + Mailbox.TABLE_NAME +
|
|
|
|
" where accountKey=? group by " + MailboxColumns.SERVER_ID + ") where dupes > 1";
|
|
|
|
|
2009-05-26 23:40:34 +00:00
|
|
|
@Override
|
|
|
|
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
|
2010-09-10 20:06:07 +00:00
|
|
|
// Handle this special case the fastest possible way
|
|
|
|
if (uri == INTEGRITY_CHECK_URI) {
|
|
|
|
checkDatabases();
|
|
|
|
return 0;
|
2011-05-06 19:07:39 +00:00
|
|
|
} else if (uri == ACCOUNT_BACKUP_URI) {
|
2011-06-21 01:10:10 +00:00
|
|
|
return backupAccounts(getContext(), getDatabase(getContext()));
|
2010-09-10 20:06:07 +00:00
|
|
|
}
|
|
|
|
|
2010-09-14 23:28:50 +00:00
|
|
|
// Notify all existing cursors, except for ACCOUNT_RESET_NEW_COUNT(_ID)
|
|
|
|
Uri notificationUri = EmailContent.CONTENT_URI;
|
|
|
|
|
2010-12-16 20:28:16 +00:00
|
|
|
int match = findMatch(uri, "update");
|
2009-06-15 21:40:06 +00:00
|
|
|
Context context = getContext();
|
2010-09-23 16:19:44 +00:00
|
|
|
ContentResolver resolver = context.getContentResolver();
|
2009-08-06 00:31:50 +00:00
|
|
|
// See the comment at delete(), above
|
2009-08-18 18:44:27 +00:00
|
|
|
SQLiteDatabase db = getDatabase(context);
|
2009-05-26 23:40:34 +00:00
|
|
|
int table = match >> BASE_SHIFT;
|
2009-06-01 21:34:16 +00:00
|
|
|
int result;
|
2009-06-15 21:40:06 +00:00
|
|
|
|
2010-09-10 21:37:01 +00:00
|
|
|
// We do NOT allow setting of unreadCount/messageCount via the provider
|
|
|
|
// These columns are maintained via triggers
|
|
|
|
if (match == MAILBOX_ID || match == MAILBOX) {
|
|
|
|
values.remove(MailboxColumns.UNREAD_COUNT);
|
|
|
|
values.remove(MailboxColumns.MESSAGE_COUNT);
|
|
|
|
}
|
2009-11-19 01:11:33 +00:00
|
|
|
|
Improve EmailContent caching...
* Guarantee that up to 16 Account (with HostAuths), and Policy rows
are always cached. Also, 6 commonly used Mailboxes per Account
(Inbox, Outbox, Drafts, Sent, Trash, and Search)
* Precache these rows when EmailProvider starts up
* Ensure that newly added, precachable rows are cached when created
* Clean up some inefficient/wrong caching code
* Fix a commonly called method in NotificationManager in which we
load a single Mailbox row using selection vs withAppendedId
* Confirm that we don't read from the database in typical use and
heavy message loading
* Add a special URI for finding mailbox by type (using the cache)
* Add special-case code for EmailContent.count(Account.CONTENT_URI)
which is used in a number of places (including on the UI thread)
and whose value is easily determined
* Add a special URI to get the default account id
* Confirm that all unit tests work
The goal here is to be able to load all Account, HostAuth, Policy,
and Mailbox objects (by id) without worrying about disk access.
There will still be a single disk read for uncommon Mailbox reads,
but this should be considered acceptable.
Change-Id: Ibc9aa7acc73185e360b0b6f3053b90a985e97210
TODO: Unit tests
2011-06-19 01:03:11 +00:00
|
|
|
ContentCache cache = mContentCaches[table];
|
2010-10-27 23:50:54 +00:00
|
|
|
String tableName = TABLE_NAMES[table];
|
2011-05-06 18:31:10 +00:00
|
|
|
String id = "0";
|
2010-10-27 23:50:54 +00:00
|
|
|
|
2009-11-19 01:11:33 +00:00
|
|
|
try {
|
Improve EmailContent caching...
* Guarantee that up to 16 Account (with HostAuths), and Policy rows
are always cached. Also, 6 commonly used Mailboxes per Account
(Inbox, Outbox, Drafts, Sent, Trash, and Search)
* Precache these rows when EmailProvider starts up
* Ensure that newly added, precachable rows are cached when created
* Clean up some inefficient/wrong caching code
* Fix a commonly called method in NotificationManager in which we
load a single Mailbox row using selection vs withAppendedId
* Confirm that we don't read from the database in typical use and
heavy message loading
* Add a special URI for finding mailbox by type (using the cache)
* Add special-case code for EmailContent.count(Account.CONTENT_URI)
which is used in a number of places (including on the UI thread)
and whose value is easily determined
* Add a special URI to get the default account id
* Confirm that all unit tests work
The goal here is to be able to load all Account, HostAuth, Policy,
and Mailbox objects (by id) without worrying about disk access.
There will still be a single disk read for uncommon Mailbox reads,
but this should be considered acceptable.
Change-Id: Ibc9aa7acc73185e360b0b6f3053b90a985e97210
TODO: Unit tests
2011-06-19 01:03:11 +00:00
|
|
|
outer:
|
2009-11-19 01:11:33 +00:00
|
|
|
switch (match) {
|
2012-06-29 16:42:05 +00:00
|
|
|
case ACCOUNT_PICK_TRASH_FOLDER:
|
|
|
|
return pickTrashFolder(uri);
|
2012-06-28 17:40:46 +00:00
|
|
|
case UI_FOLDER:
|
|
|
|
return uiUpdateFolder(uri, values);
|
|
|
|
case UI_RECENT_FOLDERS:
|
|
|
|
return uiUpdateRecentFolders(uri, values);
|
|
|
|
case UI_DEFAULT_RECENT_FOLDERS:
|
|
|
|
return uiPopulateRecentFolders(uri);
|
|
|
|
case UI_ATTACHMENT:
|
|
|
|
return uiUpdateAttachment(uri, values);
|
|
|
|
case UI_UPDATEDRAFT:
|
|
|
|
return uiUpdateDraft(uri, values);
|
|
|
|
case UI_SENDDRAFT:
|
|
|
|
return uiSendDraft(uri, values);
|
|
|
|
case UI_MESSAGE:
|
|
|
|
return uiUpdateMessage(uri, values);
|
|
|
|
case ACCOUNT_CHECK:
|
|
|
|
id = uri.getLastPathSegment();
|
|
|
|
// With any error, return 1 (a failure)
|
|
|
|
int res = 1;
|
|
|
|
Cursor ic = null;
|
|
|
|
try {
|
|
|
|
ic = db.rawQuery(ACCOUNT_INTEGRITY_SQL, new String[] {id});
|
|
|
|
if (ic.moveToFirst()) {
|
|
|
|
res = ic.getInt(0);
|
|
|
|
}
|
|
|
|
} finally {
|
|
|
|
if (ic != null) {
|
|
|
|
ic.close();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Count of duplicated mailboxes
|
|
|
|
return res;
|
2009-11-19 01:11:33 +00:00
|
|
|
case MAILBOX_ID_ADD_TO_FIELD:
|
|
|
|
case ACCOUNT_ID_ADD_TO_FIELD:
|
|
|
|
id = uri.getPathSegments().get(1);
|
|
|
|
String field = values.getAsString(EmailContent.FIELD_COLUMN_NAME);
|
|
|
|
Long add = values.getAsLong(EmailContent.ADD_COLUMN_NAME);
|
|
|
|
if (field == null || add == null) {
|
|
|
|
throw new IllegalArgumentException("No field/add specified " + uri);
|
2009-08-20 02:07:29 +00:00
|
|
|
}
|
2010-12-03 00:33:49 +00:00
|
|
|
ContentValues actualValues = new ContentValues();
|
2010-12-02 23:19:14 +00:00
|
|
|
if (cache != null) {
|
|
|
|
cache.lock(id);
|
|
|
|
}
|
2009-11-19 01:11:33 +00:00
|
|
|
try {
|
2010-12-03 00:33:49 +00:00
|
|
|
db.beginTransaction();
|
2010-12-02 23:19:14 +00:00
|
|
|
try {
|
2010-12-03 00:33:49 +00:00
|
|
|
Cursor c = db.query(tableName,
|
|
|
|
new String[] {EmailContent.RECORD_ID, field},
|
|
|
|
whereWithId(id, selection),
|
|
|
|
selectionArgs, null, null, null);
|
|
|
|
try {
|
|
|
|
result = 0;
|
|
|
|
String[] bind = new String[1];
|
|
|
|
if (c.moveToNext()) {
|
|
|
|
bind[0] = c.getString(0); // _id
|
|
|
|
long value = c.getLong(1) + add;
|
|
|
|
actualValues.put(field, value);
|
|
|
|
result = db.update(tableName, actualValues, ID_EQUALS, bind);
|
|
|
|
}
|
|
|
|
db.setTransactionSuccessful();
|
|
|
|
} finally {
|
|
|
|
c.close();
|
2010-12-02 23:19:14 +00:00
|
|
|
}
|
|
|
|
} finally {
|
2010-12-03 00:33:49 +00:00
|
|
|
db.endTransaction();
|
2009-11-19 01:11:33 +00:00
|
|
|
}
|
|
|
|
} finally {
|
2010-12-02 23:19:14 +00:00
|
|
|
if (cache != null) {
|
|
|
|
cache.unlock(id, actualValues);
|
|
|
|
}
|
2009-11-19 01:11:33 +00:00
|
|
|
}
|
|
|
|
break;
|
2012-06-29 16:42:05 +00:00
|
|
|
case SYNCED_MESSAGE_SELECTION:
|
|
|
|
Cursor findCursor = db.query(tableName, Message.ID_COLUMN_PROJECTION, selection,
|
|
|
|
selectionArgs, null, null, null);
|
|
|
|
try {
|
|
|
|
if (findCursor.moveToFirst()) {
|
|
|
|
return update(ContentUris.withAppendedId(
|
|
|
|
Message.SYNCED_CONTENT_URI,
|
|
|
|
findCursor.getLong(Message.ID_COLUMNS_ID_COLUMN)),
|
|
|
|
values, null, null);
|
|
|
|
} else {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
} finally {
|
|
|
|
findCursor.close();
|
|
|
|
}
|
2009-11-19 01:11:33 +00:00
|
|
|
case SYNCED_MESSAGE_ID:
|
|
|
|
case UPDATED_MESSAGE_ID:
|
2010-09-23 16:19:44 +00:00
|
|
|
case MESSAGE_ID:
|
|
|
|
case BODY_ID:
|
2009-11-19 01:11:33 +00:00
|
|
|
case ATTACHMENT_ID:
|
|
|
|
case MAILBOX_ID:
|
|
|
|
case ACCOUNT_ID:
|
|
|
|
case HOSTAUTH_ID:
|
2011-06-01 17:09:26 +00:00
|
|
|
case QUICK_RESPONSE_ID:
|
Improve EmailContent caching...
* Guarantee that up to 16 Account (with HostAuths), and Policy rows
are always cached. Also, 6 commonly used Mailboxes per Account
(Inbox, Outbox, Drafts, Sent, Trash, and Search)
* Precache these rows when EmailProvider starts up
* Ensure that newly added, precachable rows are cached when created
* Clean up some inefficient/wrong caching code
* Fix a commonly called method in NotificationManager in which we
load a single Mailbox row using selection vs withAppendedId
* Confirm that we don't read from the database in typical use and
heavy message loading
* Add a special URI for finding mailbox by type (using the cache)
* Add special-case code for EmailContent.count(Account.CONTENT_URI)
which is used in a number of places (including on the UI thread)
and whose value is easily determined
* Add a special URI to get the default account id
* Confirm that all unit tests work
The goal here is to be able to load all Account, HostAuth, Policy,
and Mailbox objects (by id) without worrying about disk access.
There will still be a single disk read for uncommon Mailbox reads,
but this should be considered acceptable.
Change-Id: Ibc9aa7acc73185e360b0b6f3053b90a985e97210
TODO: Unit tests
2011-06-19 01:03:11 +00:00
|
|
|
case POLICY_ID:
|
2009-11-19 01:11:33 +00:00
|
|
|
id = uri.getPathSegments().get(1);
|
2010-10-27 23:50:54 +00:00
|
|
|
if (cache != null) {
|
|
|
|
cache.lock(id);
|
|
|
|
}
|
|
|
|
try {
|
|
|
|
if (match == SYNCED_MESSAGE_ID) {
|
|
|
|
// For synced messages, first copy the old message to the updated table
|
|
|
|
// Note the insert or ignore semantics, guaranteeing that only the first
|
|
|
|
// update will be reflected in the updated message table; therefore this
|
|
|
|
// row will always have the "original" data
|
|
|
|
db.execSQL(UPDATED_MESSAGE_INSERT + id);
|
|
|
|
} else if (match == MESSAGE_ID) {
|
|
|
|
db.execSQL(UPDATED_MESSAGE_DELETE + id);
|
|
|
|
}
|
|
|
|
result = db.update(tableName, values, whereWithId(id, selection),
|
|
|
|
selectionArgs);
|
|
|
|
} catch (SQLiteException e) {
|
|
|
|
// Null out values (so they aren't cached) and re-throw
|
|
|
|
values = null;
|
|
|
|
throw e;
|
|
|
|
} finally {
|
|
|
|
if (cache != null) {
|
|
|
|
cache.unlock(id, values);
|
|
|
|
}
|
2009-11-19 01:11:33 +00:00
|
|
|
}
|
2012-06-29 16:42:05 +00:00
|
|
|
if (match == MESSAGE_ID || match == SYNCED_MESSAGE_ID) {
|
|
|
|
if (!uri.getBooleanQueryParameter(IS_UIPROVIDER, false)) {
|
|
|
|
notifyUIConversation(uri);
|
|
|
|
}
|
|
|
|
} else if (match == ATTACHMENT_ID) {
|
2012-06-28 17:40:46 +00:00
|
|
|
long attId = Integer.parseInt(id);
|
2010-08-10 00:48:53 +00:00
|
|
|
if (values.containsKey(Attachment.FLAGS)) {
|
|
|
|
int flags = values.getAsInteger(Attachment.FLAGS);
|
2012-06-28 17:40:46 +00:00
|
|
|
mAttachmentService.attachmentChanged(context, attId, flags);
|
|
|
|
}
|
|
|
|
// Notify UI if necessary; there are only two columns we can change that
|
|
|
|
// would be worth a notification
|
|
|
|
if (values.containsKey(AttachmentColumns.UI_STATE) ||
|
|
|
|
values.containsKey(AttachmentColumns.UI_DOWNLOADED_SIZE)) {
|
|
|
|
// Notify on individual attachment
|
|
|
|
notifyUI(UIPROVIDER_ATTACHMENT_NOTIFIER, id);
|
|
|
|
Attachment att = Attachment.restoreAttachmentWithId(context, attId);
|
|
|
|
if (att != null) {
|
|
|
|
// And on owning Message
|
|
|
|
notifyUI(UIPROVIDER_ATTACHMENTS_NOTIFIER, att.mMessageKey);
|
|
|
|
}
|
2012-03-09 23:19:08 +00:00
|
|
|
}
|
2012-06-28 17:40:46 +00:00
|
|
|
} else if (match == MAILBOX_ID && values.containsKey(Mailbox.UI_SYNC_STATUS)) {
|
|
|
|
notifyUI(UIPROVIDER_FOLDER_NOTIFIER, id);
|
|
|
|
} else if (match == ACCOUNT_ID) {
|
|
|
|
notifyUI(UIPROVIDER_ACCOUNT_NOTIFIER, id);
|
2010-08-10 00:48:53 +00:00
|
|
|
}
|
2009-11-19 01:11:33 +00:00
|
|
|
break;
|
|
|
|
case BODY:
|
|
|
|
case MESSAGE:
|
|
|
|
case UPDATED_MESSAGE:
|
|
|
|
case ATTACHMENT:
|
|
|
|
case MAILBOX:
|
|
|
|
case ACCOUNT:
|
|
|
|
case HOSTAUTH:
|
Improve EmailContent caching...
* Guarantee that up to 16 Account (with HostAuths), and Policy rows
are always cached. Also, 6 commonly used Mailboxes per Account
(Inbox, Outbox, Drafts, Sent, Trash, and Search)
* Precache these rows when EmailProvider starts up
* Ensure that newly added, precachable rows are cached when created
* Clean up some inefficient/wrong caching code
* Fix a commonly called method in NotificationManager in which we
load a single Mailbox row using selection vs withAppendedId
* Confirm that we don't read from the database in typical use and
heavy message loading
* Add a special URI for finding mailbox by type (using the cache)
* Add special-case code for EmailContent.count(Account.CONTENT_URI)
which is used in a number of places (including on the UI thread)
and whose value is easily determined
* Add a special URI to get the default account id
* Confirm that all unit tests work
The goal here is to be able to load all Account, HostAuth, Policy,
and Mailbox objects (by id) without worrying about disk access.
There will still be a single disk read for uncommon Mailbox reads,
but this should be considered acceptable.
Change-Id: Ibc9aa7acc73185e360b0b6f3053b90a985e97210
TODO: Unit tests
2011-06-19 01:03:11 +00:00
|
|
|
case POLICY:
|
2010-10-27 23:50:54 +00:00
|
|
|
switch(match) {
|
Improve EmailContent caching...
* Guarantee that up to 16 Account (with HostAuths), and Policy rows
are always cached. Also, 6 commonly used Mailboxes per Account
(Inbox, Outbox, Drafts, Sent, Trash, and Search)
* Precache these rows when EmailProvider starts up
* Ensure that newly added, precachable rows are cached when created
* Clean up some inefficient/wrong caching code
* Fix a commonly called method in NotificationManager in which we
load a single Mailbox row using selection vs withAppendedId
* Confirm that we don't read from the database in typical use and
heavy message loading
* Add a special URI for finding mailbox by type (using the cache)
* Add special-case code for EmailContent.count(Account.CONTENT_URI)
which is used in a number of places (including on the UI thread)
and whose value is easily determined
* Add a special URI to get the default account id
* Confirm that all unit tests work
The goal here is to be able to load all Account, HostAuth, Policy,
and Mailbox objects (by id) without worrying about disk access.
There will still be a single disk read for uncommon Mailbox reads,
but this should be considered acceptable.
Change-Id: Ibc9aa7acc73185e360b0b6f3053b90a985e97210
TODO: Unit tests
2011-06-19 01:03:11 +00:00
|
|
|
// To avoid invalidating the cache on updates, we execute them one at a
|
|
|
|
// time using the XXX_ID uri; these are all executed atomically
|
2010-10-27 23:50:54 +00:00
|
|
|
case ACCOUNT:
|
|
|
|
case MAILBOX:
|
|
|
|
case HOSTAUTH:
|
Improve EmailContent caching...
* Guarantee that up to 16 Account (with HostAuths), and Policy rows
are always cached. Also, 6 commonly used Mailboxes per Account
(Inbox, Outbox, Drafts, Sent, Trash, and Search)
* Precache these rows when EmailProvider starts up
* Ensure that newly added, precachable rows are cached when created
* Clean up some inefficient/wrong caching code
* Fix a commonly called method in NotificationManager in which we
load a single Mailbox row using selection vs withAppendedId
* Confirm that we don't read from the database in typical use and
heavy message loading
* Add a special URI for finding mailbox by type (using the cache)
* Add special-case code for EmailContent.count(Account.CONTENT_URI)
which is used in a number of places (including on the UI thread)
and whose value is easily determined
* Add a special URI to get the default account id
* Confirm that all unit tests work
The goal here is to be able to load all Account, HostAuth, Policy,
and Mailbox objects (by id) without worrying about disk access.
There will still be a single disk read for uncommon Mailbox reads,
but this should be considered acceptable.
Change-Id: Ibc9aa7acc73185e360b0b6f3053b90a985e97210
TODO: Unit tests
2011-06-19 01:03:11 +00:00
|
|
|
case POLICY:
|
|
|
|
Cursor c = db.query(tableName, EmailContent.ID_PROJECTION,
|
|
|
|
selection, selectionArgs, null, null, null);
|
|
|
|
db.beginTransaction();
|
|
|
|
result = 0;
|
|
|
|
try {
|
|
|
|
while (c.moveToNext()) {
|
|
|
|
update(ContentUris.withAppendedId(
|
|
|
|
uri, c.getLong(EmailContent.ID_PROJECTION_COLUMN)),
|
|
|
|
values, null, null);
|
|
|
|
result++;
|
|
|
|
}
|
|
|
|
db.setTransactionSuccessful();
|
|
|
|
} finally {
|
|
|
|
db.endTransaction();
|
|
|
|
c.close();
|
|
|
|
}
|
|
|
|
break outer;
|
|
|
|
// Any cached table other than those above should be invalidated here
|
|
|
|
case MESSAGE:
|
2010-10-27 23:50:54 +00:00
|
|
|
// If we're doing some generic update, the whole cache needs to be
|
|
|
|
// invalidated. This case should be quite rare
|
|
|
|
cache.invalidate("Update", uri, selection);
|
Improve EmailContent caching...
* Guarantee that up to 16 Account (with HostAuths), and Policy rows
are always cached. Also, 6 commonly used Mailboxes per Account
(Inbox, Outbox, Drafts, Sent, Trash, and Search)
* Precache these rows when EmailProvider starts up
* Ensure that newly added, precachable rows are cached when created
* Clean up some inefficient/wrong caching code
* Fix a commonly called method in NotificationManager in which we
load a single Mailbox row using selection vs withAppendedId
* Confirm that we don't read from the database in typical use and
heavy message loading
* Add a special URI for finding mailbox by type (using the cache)
* Add special-case code for EmailContent.count(Account.CONTENT_URI)
which is used in a number of places (including on the UI thread)
and whose value is easily determined
* Add a special URI to get the default account id
* Confirm that all unit tests work
The goal here is to be able to load all Account, HostAuth, Policy,
and Mailbox objects (by id) without worrying about disk access.
There will still be a single disk read for uncommon Mailbox reads,
but this should be considered acceptable.
Change-Id: Ibc9aa7acc73185e360b0b6f3053b90a985e97210
TODO: Unit tests
2011-06-19 01:03:11 +00:00
|
|
|
//$FALL-THROUGH$
|
|
|
|
default:
|
|
|
|
result = db.update(tableName, values, selection, selectionArgs);
|
|
|
|
break outer;
|
2010-10-27 23:50:54 +00:00
|
|
|
}
|
2010-09-14 23:28:50 +00:00
|
|
|
case ACCOUNT_RESET_NEW_COUNT_ID:
|
|
|
|
id = uri.getPathSegments().get(1);
|
2010-12-02 23:19:14 +00:00
|
|
|
if (cache != null) {
|
|
|
|
cache.lock(id);
|
|
|
|
}
|
2011-05-03 21:42:26 +00:00
|
|
|
ContentValues newMessageCount = CONTENT_VALUES_RESET_NEW_MESSAGE_COUNT;
|
|
|
|
if (values != null) {
|
|
|
|
Long set = values.getAsLong(EmailContent.SET_COLUMN_NAME);
|
|
|
|
if (set != null) {
|
|
|
|
newMessageCount = new ContentValues();
|
|
|
|
newMessageCount.put(Account.NEW_MESSAGE_COUNT, set);
|
|
|
|
}
|
|
|
|
}
|
2010-12-02 23:19:14 +00:00
|
|
|
try {
|
2011-05-03 21:42:26 +00:00
|
|
|
result = db.update(tableName, newMessageCount,
|
2010-12-02 23:19:14 +00:00
|
|
|
whereWithId(id, selection), selectionArgs);
|
|
|
|
} finally {
|
|
|
|
if (cache != null) {
|
|
|
|
cache.unlock(id, values);
|
|
|
|
}
|
|
|
|
}
|
2010-09-14 23:28:50 +00:00
|
|
|
notificationUri = Account.CONTENT_URI; // Only notify account cursors.
|
|
|
|
break;
|
|
|
|
case ACCOUNT_RESET_NEW_COUNT:
|
2010-10-27 23:50:54 +00:00
|
|
|
result = db.update(tableName, CONTENT_VALUES_RESET_NEW_MESSAGE_COUNT,
|
2010-09-14 23:28:50 +00:00
|
|
|
selection, selectionArgs);
|
2010-12-02 23:19:14 +00:00
|
|
|
// Affects all accounts. Just invalidate all account cache.
|
|
|
|
cache.invalidate("Reset all new counts", null, null);
|
2010-09-14 23:28:50 +00:00
|
|
|
notificationUri = Account.CONTENT_URI; // Only notify account cursors.
|
|
|
|
break;
|
2009-11-19 01:11:33 +00:00
|
|
|
default:
|
|
|
|
throw new IllegalArgumentException("Unknown URI " + uri);
|
|
|
|
}
|
|
|
|
} catch (SQLiteException e) {
|
|
|
|
checkDatabases();
|
|
|
|
throw e;
|
2009-05-29 21:24:34 +00:00
|
|
|
}
|
2009-06-15 21:40:06 +00:00
|
|
|
|
2011-05-06 18:31:10 +00:00
|
|
|
// Notify all notifier cursors
|
2011-05-11 22:29:24 +00:00
|
|
|
sendNotifierChange(getBaseNotificationUri(match), NOTIFICATION_OP_UPDATE, id);
|
2011-05-06 18:31:10 +00:00
|
|
|
|
2010-09-23 16:19:44 +00:00
|
|
|
resolver.notifyChange(notificationUri, null);
|
2009-06-01 21:34:16 +00:00
|
|
|
return result;
|
2009-05-29 21:24:34 +00:00
|
|
|
}
|
2009-06-27 19:14:14 +00:00
|
|
|
|
2011-05-11 22:29:24 +00:00
|
|
|
/**
|
|
|
|
* Returns the base notification URI for the given content type.
|
|
|
|
*
|
|
|
|
* @param match The type of content that was modified.
|
|
|
|
*/
|
|
|
|
private Uri getBaseNotificationUri(int match) {
|
|
|
|
Uri baseUri = null;
|
|
|
|
switch (match) {
|
|
|
|
case MESSAGE:
|
|
|
|
case MESSAGE_ID:
|
|
|
|
case SYNCED_MESSAGE_ID:
|
|
|
|
baseUri = Message.NOTIFIER_URI;
|
|
|
|
break;
|
|
|
|
case ACCOUNT:
|
|
|
|
case ACCOUNT_ID:
|
|
|
|
baseUri = Account.NOTIFIER_URI;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return baseUri;
|
|
|
|
}
|
|
|
|
|
2011-05-06 18:31:10 +00:00
|
|
|
/**
|
|
|
|
* Sends a change notification to any cursors observers of the given base URI. The final
|
|
|
|
* notification URI is dynamically built to contain the specified information. It will be
|
|
|
|
* of the format <<baseURI>>/<<op>>/<<id>>; where <<op>> and <<id>> are optional depending
|
|
|
|
* upon the given values.
|
|
|
|
* NOTE: If <<op>> is specified, notifications for <<baseURI>>/<<id>> will NOT be invoked.
|
|
|
|
* If this is necessary, it can be added. However, due to the implementation of
|
|
|
|
* {@link ContentObserver}, observers of <<baseURI>> will receive multiple notifications.
|
|
|
|
*
|
|
|
|
* @param baseUri The base URI to send notifications to. Must be able to take appended IDs.
|
|
|
|
* @param op Optional operation to be appended to the URI.
|
|
|
|
* @param id If a positive value, the ID to append to the base URI. Otherwise, no ID will be
|
|
|
|
* appended to the base URI.
|
2009-05-29 21:24:34 +00:00
|
|
|
*/
|
2011-05-06 18:31:10 +00:00
|
|
|
private void sendNotifierChange(Uri baseUri, String op, String id) {
|
2011-05-11 22:29:24 +00:00
|
|
|
if (baseUri == null) return;
|
|
|
|
|
2011-05-06 18:31:10 +00:00
|
|
|
final ContentResolver resolver = getContext().getContentResolver();
|
|
|
|
|
|
|
|
// Append the operation, if specified
|
|
|
|
if (op != null) {
|
|
|
|
baseUri = baseUri.buildUpon().appendEncodedPath(op).build();
|
|
|
|
}
|
|
|
|
|
|
|
|
long longId = 0L;
|
|
|
|
try {
|
|
|
|
longId = Long.valueOf(id);
|
|
|
|
} catch (NumberFormatException ignore) {}
|
|
|
|
if (longId > 0) {
|
|
|
|
resolver.notifyChange(ContentUris.withAppendedId(baseUri, longId), null);
|
|
|
|
} else {
|
|
|
|
resolver.notifyChange(baseUri, null);
|
|
|
|
}
|
2012-01-09 19:36:16 +00:00
|
|
|
|
|
|
|
// We want to send the message list changed notification if baseUri is Message.NOTIFIER_URI.
|
|
|
|
if (baseUri.equals(Message.NOTIFIER_URI)) {
|
|
|
|
sendMessageListDataChangedNotification();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private void sendMessageListDataChangedNotification() {
|
|
|
|
final Context context = getContext();
|
|
|
|
final Intent intent = new Intent(ACTION_NOTIFY_MESSAGE_LIST_DATASET_CHANGED);
|
|
|
|
// Ideally this intent would contain information about which account changed, to limit the
|
|
|
|
// updates to that particular account. Unfortunately, that information is not available in
|
|
|
|
// sendNotifierChange().
|
|
|
|
context.sendBroadcast(intent);
|
2011-05-06 18:31:10 +00:00
|
|
|
}
|
|
|
|
|
2009-07-30 18:41:31 +00:00
|
|
|
@Override
|
2009-06-01 19:55:50 +00:00
|
|
|
public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations)
|
2009-07-05 19:54:49 +00:00
|
|
|
throws OperationApplicationException {
|
2009-08-06 00:31:50 +00:00
|
|
|
Context context = getContext();
|
|
|
|
SQLiteDatabase db = getDatabase(context);
|
2009-05-29 21:24:34 +00:00
|
|
|
db.beginTransaction();
|
|
|
|
try {
|
|
|
|
ContentProviderResult[] results = super.applyBatch(operations);
|
|
|
|
db.setTransactionSuccessful();
|
|
|
|
return results;
|
|
|
|
} finally {
|
|
|
|
db.endTransaction();
|
2009-05-26 23:40:34 +00:00
|
|
|
}
|
|
|
|
}
|
2010-07-30 20:53:59 +00:00
|
|
|
|
2012-01-12 01:46:00 +00:00
|
|
|
/**
|
2012-04-25 17:26:46 +00:00
|
|
|
* For testing purposes, check whether a given row is cached
|
|
|
|
* @param baseUri the base uri of the EmailContent
|
|
|
|
* @param id the row id of the EmailContent
|
|
|
|
* @return whether or not the row is currently cached
|
|
|
|
*/
|
|
|
|
@VisibleForTesting
|
|
|
|
protected boolean isCached(Uri baseUri, long id) {
|
|
|
|
int match = findMatch(baseUri, "isCached");
|
|
|
|
int table = match >> BASE_SHIFT;
|
|
|
|
ContentCache cache = mContentCaches[table];
|
|
|
|
if (cache == null) return false;
|
|
|
|
Cursor cc = cache.get(Long.toString(id));
|
|
|
|
return (cc != null);
|
2012-02-03 23:19:09 +00:00
|
|
|
}
|
2012-02-18 00:31:23 +00:00
|
|
|
|
2012-04-25 17:26:46 +00:00
|
|
|
public static interface AttachmentService {
|
|
|
|
/**
|
|
|
|
* Notify the service that an attachment has changed.
|
|
|
|
*/
|
|
|
|
void attachmentChanged(Context context, long id, int flags);
|
2012-03-09 22:04:49 +00:00
|
|
|
}
|
|
|
|
|
2012-04-25 17:26:46 +00:00
|
|
|
private final AttachmentService DEFAULT_ATTACHMENT_SERVICE = new AttachmentService() {
|
2012-02-18 00:31:23 +00:00
|
|
|
@Override
|
2012-04-25 17:26:46 +00:00
|
|
|
public void attachmentChanged(Context context, long id, int flags) {
|
|
|
|
// The default implementation delegates to the real service.
|
|
|
|
AttachmentDownloadService.attachmentChanged(context, id, flags);
|
2012-02-18 00:31:23 +00:00
|
|
|
}
|
|
|
|
};
|
2012-04-25 17:26:46 +00:00
|
|
|
private AttachmentService mAttachmentService = DEFAULT_ATTACHMENT_SERVICE;
|
2012-03-09 19:52:45 +00:00
|
|
|
|
|
|
|
/**
|
2012-04-25 17:26:46 +00:00
|
|
|
* Injects a custom attachment service handler. If null is specified, will reset to the
|
|
|
|
* default service.
|
2012-03-09 19:52:45 +00:00
|
|
|
*/
|
2012-04-25 17:26:46 +00:00
|
|
|
public void injectAttachmentService(AttachmentService as) {
|
|
|
|
mAttachmentService = (as == null) ? DEFAULT_ATTACHMENT_SERVICE : as;
|
2012-03-09 19:52:45 +00:00
|
|
|
}
|
2012-06-28 17:40:46 +00:00
|
|
|
|
|
|
|
// SELECT DISTINCT Boxes._id, Boxes.unreadCount count(Message._id) from Message,
|
|
|
|
// (SELECT _id, unreadCount, messageCount, lastNotifiedMessageCount, lastNotifiedMessageKey
|
|
|
|
// FROM Mailbox WHERE accountKey=6 AND ((type = 0) OR (syncInterval!=0 AND syncInterval!=-1)))
|
|
|
|
// AS Boxes
|
|
|
|
// WHERE Boxes.messageCount!=Boxes.lastNotifiedMessageCount
|
|
|
|
// OR (Boxes._id=Message.mailboxKey AND Message._id>Boxes.lastNotifiedMessageKey)
|
|
|
|
// TODO: This query can be simplified a bit
|
|
|
|
private static final String NOTIFICATION_QUERY =
|
|
|
|
"SELECT DISTINCT Boxes." + MailboxColumns.ID + ", Boxes." + MailboxColumns.UNREAD_COUNT +
|
|
|
|
", count(" + Message.TABLE_NAME + "." + MessageColumns.ID + ")" +
|
|
|
|
" FROM " +
|
|
|
|
Message.TABLE_NAME + "," +
|
|
|
|
"(SELECT " + MailboxColumns.ID + "," + MailboxColumns.UNREAD_COUNT + "," +
|
|
|
|
MailboxColumns.MESSAGE_COUNT + "," + MailboxColumns.LAST_NOTIFIED_MESSAGE_COUNT +
|
|
|
|
"," + MailboxColumns.LAST_NOTIFIED_MESSAGE_KEY + " FROM " + Mailbox.TABLE_NAME +
|
|
|
|
" WHERE " + MailboxColumns.ACCOUNT_KEY + "=?" +
|
|
|
|
" AND (" + MailboxColumns.TYPE + "=" + Mailbox.TYPE_INBOX + " OR ("
|
|
|
|
+ MailboxColumns.SYNC_INTERVAL + "!=0 AND " +
|
|
|
|
MailboxColumns.SYNC_INTERVAL + "!=-1))) AS Boxes " +
|
|
|
|
"WHERE Boxes." + MailboxColumns.ID + '=' + Message.TABLE_NAME + "." +
|
|
|
|
MessageColumns.MAILBOX_KEY + " AND " + Message.TABLE_NAME + "." +
|
|
|
|
MessageColumns.ID + ">Boxes." + MailboxColumns.LAST_NOTIFIED_MESSAGE_KEY +
|
|
|
|
" AND " + MessageColumns.FLAG_READ + "=0 AND " + MessageColumns.TIMESTAMP + "!=0";
|
|
|
|
|
|
|
|
public Cursor notificationQuery(Uri uri) {
|
|
|
|
SQLiteDatabase db = getDatabase(getContext());
|
|
|
|
String accountId = uri.getLastPathSegment();
|
|
|
|
return db.rawQuery(NOTIFICATION_QUERY, new String[] {accountId});
|
|
|
|
}
|
|
|
|
|
|
|
|
public Cursor mostRecentMessageQuery(Uri uri) {
|
|
|
|
SQLiteDatabase db = getDatabase(getContext());
|
|
|
|
String mailboxId = uri.getLastPathSegment();
|
|
|
|
return db.rawQuery("select max(_id) from Message where mailboxKey=?",
|
|
|
|
new String[] {mailboxId});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Support for UnifiedEmail below
|
|
|
|
*/
|
|
|
|
|
|
|
|
private static final String NOT_A_DRAFT_STRING =
|
|
|
|
Integer.toString(UIProvider.DraftType.NOT_A_DRAFT);
|
|
|
|
|
|
|
|
private static final String CONVERSATION_FLAGS =
|
|
|
|
"CASE WHEN (" + MessageColumns.FLAGS + "&" + Message.FLAG_INCOMING_MEETING_INVITE +
|
|
|
|
") !=0 THEN " + UIProvider.ConversationFlags.CALENDAR_INVITE +
|
|
|
|
" ELSE 0 END + " +
|
|
|
|
"CASE WHEN (" + MessageColumns.FLAGS + "&" + Message.FLAG_FORWARDED +
|
|
|
|
") !=0 THEN " + UIProvider.ConversationFlags.FORWARDED +
|
|
|
|
" ELSE 0 END + " +
|
|
|
|
"CASE WHEN (" + MessageColumns.FLAGS + "&" + Message.FLAG_REPLIED_TO +
|
|
|
|
") !=0 THEN " + UIProvider.ConversationFlags.REPLIED +
|
|
|
|
" ELSE 0 END";
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Array of pre-defined account colors (legacy colors from old email app)
|
|
|
|
*/
|
|
|
|
private static final int[] ACCOUNT_COLORS = new int[] {
|
|
|
|
0xff71aea7, 0xff621919, 0xff18462f, 0xffbf8e52, 0xff001f79,
|
|
|
|
0xffa8afc2, 0xff6b64c4, 0xff738359, 0xff9d50a4
|
|
|
|
};
|
|
|
|
|
|
|
|
private static final String CONVERSATION_COLOR =
|
|
|
|
"@CASE (" + MessageColumns.ACCOUNT_KEY + " - 1) % " + ACCOUNT_COLORS.length +
|
|
|
|
" WHEN 0 THEN " + ACCOUNT_COLORS[0] +
|
|
|
|
" WHEN 1 THEN " + ACCOUNT_COLORS[1] +
|
|
|
|
" WHEN 2 THEN " + ACCOUNT_COLORS[2] +
|
|
|
|
" WHEN 3 THEN " + ACCOUNT_COLORS[3] +
|
|
|
|
" WHEN 4 THEN " + ACCOUNT_COLORS[4] +
|
|
|
|
" WHEN 5 THEN " + ACCOUNT_COLORS[5] +
|
|
|
|
" WHEN 6 THEN " + ACCOUNT_COLORS[6] +
|
|
|
|
" WHEN 7 THEN " + ACCOUNT_COLORS[7] +
|
|
|
|
" WHEN 8 THEN " + ACCOUNT_COLORS[8] +
|
|
|
|
" END";
|
|
|
|
|
|
|
|
private static final String ACCOUNT_COLOR =
|
|
|
|
"@CASE (" + AccountColumns.ID + " - 1) % " + ACCOUNT_COLORS.length +
|
|
|
|
" WHEN 0 THEN " + ACCOUNT_COLORS[0] +
|
|
|
|
" WHEN 1 THEN " + ACCOUNT_COLORS[1] +
|
|
|
|
" WHEN 2 THEN " + ACCOUNT_COLORS[2] +
|
|
|
|
" WHEN 3 THEN " + ACCOUNT_COLORS[3] +
|
|
|
|
" WHEN 4 THEN " + ACCOUNT_COLORS[4] +
|
|
|
|
" WHEN 5 THEN " + ACCOUNT_COLORS[5] +
|
|
|
|
" WHEN 6 THEN " + ACCOUNT_COLORS[6] +
|
|
|
|
" WHEN 7 THEN " + ACCOUNT_COLORS[7] +
|
|
|
|
" WHEN 8 THEN " + ACCOUNT_COLORS[8] +
|
|
|
|
" END";
|
|
|
|
/**
|
|
|
|
* Mapping of UIProvider columns to EmailProvider columns for the message list (called the
|
|
|
|
* conversation list in UnifiedEmail)
|
|
|
|
*/
|
|
|
|
private static final ProjectionMap sMessageListMap = ProjectionMap.builder()
|
|
|
|
.add(BaseColumns._ID, MessageColumns.ID)
|
|
|
|
.add(UIProvider.ConversationColumns.URI, uriWithId("uimessage"))
|
|
|
|
.add(UIProvider.ConversationColumns.MESSAGE_LIST_URI, uriWithId("uimessage"))
|
|
|
|
.add(UIProvider.ConversationColumns.SUBJECT, MessageColumns.SUBJECT)
|
|
|
|
.add(UIProvider.ConversationColumns.SNIPPET, MessageColumns.SNIPPET)
|
|
|
|
.add(UIProvider.ConversationColumns.SENDER_INFO, MessageColumns.FROM_LIST)
|
|
|
|
.add(UIProvider.ConversationColumns.DATE_RECEIVED_MS, MessageColumns.TIMESTAMP)
|
|
|
|
.add(UIProvider.ConversationColumns.HAS_ATTACHMENTS, MessageColumns.FLAG_ATTACHMENT)
|
|
|
|
.add(UIProvider.ConversationColumns.NUM_MESSAGES, "1")
|
|
|
|
.add(UIProvider.ConversationColumns.NUM_DRAFTS, "0")
|
|
|
|
.add(UIProvider.ConversationColumns.SENDING_STATE,
|
|
|
|
Integer.toString(ConversationSendingState.OTHER))
|
|
|
|
.add(UIProvider.ConversationColumns.PRIORITY, Integer.toString(ConversationPriority.LOW))
|
|
|
|
.add(UIProvider.ConversationColumns.READ, MessageColumns.FLAG_READ)
|
|
|
|
.add(UIProvider.ConversationColumns.STARRED, MessageColumns.FLAG_FAVORITE)
|
|
|
|
.add(UIProvider.ConversationColumns.FOLDER_LIST,
|
|
|
|
"'content://" + EmailContent.AUTHORITY + "/uifolder/' || "
|
|
|
|
+ MessageColumns.MAILBOX_KEY)
|
|
|
|
.add(UIProvider.ConversationColumns.FLAGS, CONVERSATION_FLAGS)
|
|
|
|
.add(UIProvider.ConversationColumns.ACCOUNT_URI,
|
|
|
|
"'content://" + EmailContent.AUTHORITY + "/uiaccount/' || "
|
|
|
|
+ MessageColumns.ACCOUNT_KEY)
|
|
|
|
.build();
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Generate UIProvider draft type; note the test for "reply all" must come before "reply"
|
|
|
|
*/
|
|
|
|
private static final String MESSAGE_DRAFT_TYPE =
|
|
|
|
"CASE WHEN (" + MessageColumns.FLAGS + "&" + Message.FLAG_TYPE_ORIGINAL +
|
|
|
|
") !=0 THEN " + UIProvider.DraftType.COMPOSE +
|
|
|
|
" WHEN (" + MessageColumns.FLAGS + "&" + (1<<20) +
|
|
|
|
") !=0 THEN " + UIProvider.DraftType.REPLY_ALL +
|
|
|
|
" WHEN (" + MessageColumns.FLAGS + "&" + Message.FLAG_TYPE_REPLY +
|
|
|
|
") !=0 THEN " + UIProvider.DraftType.REPLY +
|
|
|
|
" WHEN (" + MessageColumns.FLAGS + "&" + Message.FLAG_TYPE_FORWARD +
|
|
|
|
") !=0 THEN " + UIProvider.DraftType.FORWARD +
|
|
|
|
" ELSE " + UIProvider.DraftType.NOT_A_DRAFT + " END";
|
|
|
|
|
|
|
|
private static final String MESSAGE_FLAGS =
|
|
|
|
"CASE WHEN (" + MessageColumns.FLAGS + "&" + Message.FLAG_INCOMING_MEETING_INVITE +
|
|
|
|
") !=0 THEN " + UIProvider.MessageFlags.CALENDAR_INVITE +
|
|
|
|
" ELSE 0 END";
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Mapping of UIProvider columns to EmailProvider columns for a detailed message view in
|
|
|
|
* UnifiedEmail
|
|
|
|
*/
|
|
|
|
private static final ProjectionMap sMessageViewMap = ProjectionMap.builder()
|
|
|
|
.add(BaseColumns._ID, Message.TABLE_NAME + "." + EmailContent.MessageColumns.ID)
|
|
|
|
.add(UIProvider.MessageColumns.SERVER_ID, SyncColumns.SERVER_ID)
|
|
|
|
.add(UIProvider.MessageColumns.URI, uriWithFQId("uimessage", Message.TABLE_NAME))
|
|
|
|
.add(UIProvider.MessageColumns.CONVERSATION_ID,
|
|
|
|
uriWithFQId("uimessage", Message.TABLE_NAME))
|
|
|
|
.add(UIProvider.MessageColumns.SUBJECT, EmailContent.MessageColumns.SUBJECT)
|
|
|
|
.add(UIProvider.MessageColumns.SNIPPET, EmailContent.MessageColumns.SNIPPET)
|
|
|
|
.add(UIProvider.MessageColumns.FROM, EmailContent.MessageColumns.FROM_LIST)
|
|
|
|
.add(UIProvider.MessageColumns.TO, EmailContent.MessageColumns.TO_LIST)
|
|
|
|
.add(UIProvider.MessageColumns.CC, EmailContent.MessageColumns.CC_LIST)
|
|
|
|
.add(UIProvider.MessageColumns.BCC, EmailContent.MessageColumns.BCC_LIST)
|
|
|
|
.add(UIProvider.MessageColumns.REPLY_TO, EmailContent.MessageColumns.REPLY_TO_LIST)
|
|
|
|
.add(UIProvider.MessageColumns.DATE_RECEIVED_MS, EmailContent.MessageColumns.TIMESTAMP)
|
|
|
|
.add(UIProvider.MessageColumns.BODY_HTML, Body.HTML_CONTENT)
|
|
|
|
.add(UIProvider.MessageColumns.BODY_TEXT, Body.TEXT_CONTENT)
|
|
|
|
.add(UIProvider.MessageColumns.REF_MESSAGE_ID, "0")
|
|
|
|
.add(UIProvider.MessageColumns.DRAFT_TYPE, NOT_A_DRAFT_STRING)
|
|
|
|
.add(UIProvider.MessageColumns.APPEND_REF_MESSAGE_CONTENT, "0")
|
|
|
|
.add(UIProvider.MessageColumns.HAS_ATTACHMENTS, EmailContent.MessageColumns.FLAG_ATTACHMENT)
|
|
|
|
.add(UIProvider.MessageColumns.ATTACHMENT_LIST_URI,
|
|
|
|
uriWithFQId("uiattachments", Message.TABLE_NAME))
|
|
|
|
.add(UIProvider.MessageColumns.MESSAGE_FLAGS, MESSAGE_FLAGS)
|
|
|
|
.add(UIProvider.MessageColumns.SAVE_MESSAGE_URI,
|
|
|
|
uriWithFQId("uiupdatedraft", Message.TABLE_NAME))
|
|
|
|
.add(UIProvider.MessageColumns.SEND_MESSAGE_URI,
|
|
|
|
uriWithFQId("uisenddraft", Message.TABLE_NAME))
|
|
|
|
.add(UIProvider.MessageColumns.DRAFT_TYPE, MESSAGE_DRAFT_TYPE)
|
|
|
|
.add(UIProvider.MessageColumns.MESSAGE_ACCOUNT_URI,
|
|
|
|
uriWithColumn("account", MessageColumns.ACCOUNT_KEY))
|
|
|
|
.add(UIProvider.MessageColumns.STARRED, EmailContent.MessageColumns.FLAG_FAVORITE)
|
|
|
|
.add(UIProvider.MessageColumns.READ, EmailContent.MessageColumns.FLAG_READ)
|
2012-07-10 22:56:45 +00:00
|
|
|
.add(UIProvider.MessageColumns.SPAM_WARNING_STRING, null)
|
|
|
|
.add(UIProvider.MessageColumns.SPAM_WARNING_LEVEL,
|
|
|
|
Integer.toString(UIProvider.SpamWarningLevel.NO_WARNING))
|
|
|
|
.add(UIProvider.MessageColumns.SPAM_WARNING_LINK_TYPE,
|
|
|
|
Integer.toString(UIProvider.SpamWarningLinkType.NO_LINK))
|
2012-07-13 19:29:10 +00:00
|
|
|
.add(UIProvider.MessageColumns.VIA_DOMAIN, null)
|
2012-06-28 17:40:46 +00:00
|
|
|
.build();
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Generate UIProvider folder capabilities from mailbox flags
|
|
|
|
*/
|
|
|
|
private static final String FOLDER_CAPABILITIES =
|
|
|
|
"CASE WHEN (" + MailboxColumns.FLAGS + "&" + Mailbox.FLAG_ACCEPTS_MOVED_MAIL +
|
|
|
|
") !=0 THEN " + UIProvider.FolderCapabilities.CAN_ACCEPT_MOVED_MESSAGES +
|
|
|
|
" ELSE 0 END";
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Convert EmailProvider type to UIProvider type
|
|
|
|
*/
|
|
|
|
private static final String FOLDER_TYPE = "CASE " + MailboxColumns.TYPE
|
|
|
|
+ " WHEN " + Mailbox.TYPE_INBOX + " THEN " + UIProvider.FolderType.INBOX
|
|
|
|
+ " WHEN " + Mailbox.TYPE_DRAFTS + " THEN " + UIProvider.FolderType.DRAFT
|
|
|
|
+ " WHEN " + Mailbox.TYPE_OUTBOX + " THEN " + UIProvider.FolderType.OUTBOX
|
|
|
|
+ " WHEN " + Mailbox.TYPE_SENT + " THEN " + UIProvider.FolderType.SENT
|
|
|
|
+ " WHEN " + Mailbox.TYPE_TRASH + " THEN " + UIProvider.FolderType.TRASH
|
|
|
|
+ " WHEN " + Mailbox.TYPE_JUNK + " THEN " + UIProvider.FolderType.SPAM
|
|
|
|
+ " WHEN " + Mailbox.TYPE_STARRED + " THEN " + UIProvider.FolderType.STARRED
|
|
|
|
+ " ELSE " + UIProvider.FolderType.DEFAULT + " END";
|
|
|
|
|
|
|
|
private static final String FOLDER_ICON = "CASE " + MailboxColumns.TYPE
|
|
|
|
+ " WHEN " + Mailbox.TYPE_INBOX + " THEN " + R.drawable.ic_folder_inbox_holo_light
|
|
|
|
+ " WHEN " + Mailbox.TYPE_DRAFTS + " THEN " + R.drawable.ic_folder_drafts_holo_light
|
|
|
|
+ " WHEN " + Mailbox.TYPE_OUTBOX + " THEN " + R.drawable.ic_folder_outbox_holo_light
|
|
|
|
+ " WHEN " + Mailbox.TYPE_SENT + " THEN " + R.drawable.ic_folder_sent_holo_light
|
|
|
|
+ " WHEN " + Mailbox.TYPE_STARRED + " THEN " + R.drawable.ic_menu_star_holo_light
|
|
|
|
+ " ELSE -1 END";
|
|
|
|
|
|
|
|
private static final ProjectionMap sFolderListMap = ProjectionMap.builder()
|
|
|
|
.add(BaseColumns._ID, MailboxColumns.ID)
|
|
|
|
.add(UIProvider.FolderColumns.URI, uriWithId("uifolder"))
|
|
|
|
.add(UIProvider.FolderColumns.NAME, "displayName")
|
|
|
|
.add(UIProvider.FolderColumns.HAS_CHILDREN,
|
|
|
|
MailboxColumns.FLAGS + "&" + Mailbox.FLAG_HAS_CHILDREN)
|
|
|
|
.add(UIProvider.FolderColumns.CAPABILITIES, FOLDER_CAPABILITIES)
|
|
|
|
.add(UIProvider.FolderColumns.SYNC_WINDOW, "3")
|
|
|
|
.add(UIProvider.FolderColumns.CONVERSATION_LIST_URI, uriWithId("uimessages"))
|
|
|
|
.add(UIProvider.FolderColumns.CHILD_FOLDERS_LIST_URI, uriWithId("uisubfolders"))
|
|
|
|
.add(UIProvider.FolderColumns.UNREAD_COUNT, MailboxColumns.UNREAD_COUNT)
|
|
|
|
.add(UIProvider.FolderColumns.TOTAL_COUNT, MailboxColumns.MESSAGE_COUNT)
|
|
|
|
.add(UIProvider.FolderColumns.REFRESH_URI, uriWithId("uirefresh"))
|
|
|
|
.add(UIProvider.FolderColumns.SYNC_STATUS, MailboxColumns.UI_SYNC_STATUS)
|
|
|
|
.add(UIProvider.FolderColumns.LAST_SYNC_RESULT, MailboxColumns.UI_LAST_SYNC_RESULT)
|
|
|
|
.add(UIProvider.FolderColumns.TYPE, FOLDER_TYPE)
|
|
|
|
.add(UIProvider.FolderColumns.ICON_RES_ID, FOLDER_ICON)
|
|
|
|
.add(UIProvider.FolderColumns.HIERARCHICAL_DESC, MailboxColumns.HIERARCHICAL_NAME)
|
|
|
|
.build();
|
|
|
|
|
|
|
|
private static final ProjectionMap sAccountListMap = ProjectionMap.builder()
|
|
|
|
.add(BaseColumns._ID, AccountColumns.ID)
|
|
|
|
.add(UIProvider.AccountColumns.FOLDER_LIST_URI, uriWithId("uifolders"))
|
|
|
|
.add(UIProvider.AccountColumns.FULL_FOLDER_LIST_URI, uriWithId("uiallfolders"))
|
|
|
|
.add(UIProvider.AccountColumns.NAME, AccountColumns.DISPLAY_NAME)
|
|
|
|
.add(UIProvider.AccountColumns.SAVE_DRAFT_URI, uriWithId("uisavedraft"))
|
|
|
|
.add(UIProvider.AccountColumns.SEND_MAIL_URI, uriWithId("uisendmail"))
|
|
|
|
.add(UIProvider.AccountColumns.UNDO_URI,
|
|
|
|
("'content://" + UIProvider.AUTHORITY + "/uiundo'"))
|
|
|
|
.add(UIProvider.AccountColumns.URI, uriWithId("uiaccount"))
|
|
|
|
.add(UIProvider.AccountColumns.SEARCH_URI, uriWithId("uisearch"))
|
|
|
|
// TODO: Is provider version used?
|
|
|
|
.add(UIProvider.AccountColumns.PROVIDER_VERSION, "1")
|
|
|
|
.add(UIProvider.AccountColumns.SYNC_STATUS, "0")
|
|
|
|
.add(UIProvider.AccountColumns.RECENT_FOLDER_LIST_URI, uriWithId("uirecentfolders"))
|
|
|
|
.add(UIProvider.AccountColumns.DEFAULT_RECENT_FOLDER_LIST_URI,
|
|
|
|
uriWithId("uidefaultrecentfolders"))
|
|
|
|
.add(UIProvider.AccountColumns.SettingsColumns.SIGNATURE, AccountColumns.SIGNATURE)
|
|
|
|
.add(UIProvider.AccountColumns.SettingsColumns.SNAP_HEADERS,
|
|
|
|
Integer.toString(UIProvider.SnapHeaderValue.ALWAYS))
|
|
|
|
.add(UIProvider.AccountColumns.SettingsColumns.REPLY_BEHAVIOR,
|
|
|
|
Integer.toString(UIProvider.DefaultReplyBehavior.REPLY))
|
|
|
|
.add(UIProvider.AccountColumns.SettingsColumns.CONFIRM_ARCHIVE, "0")
|
|
|
|
.build();
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The "ORDER BY" clause for top level folders
|
|
|
|
*/
|
|
|
|
private static final String MAILBOX_ORDER_BY = "CASE " + MailboxColumns.TYPE
|
|
|
|
+ " WHEN " + Mailbox.TYPE_INBOX + " THEN 0"
|
|
|
|
+ " WHEN " + Mailbox.TYPE_DRAFTS + " THEN 1"
|
|
|
|
+ " WHEN " + Mailbox.TYPE_OUTBOX + " THEN 2"
|
|
|
|
+ " WHEN " + Mailbox.TYPE_SENT + " THEN 3"
|
|
|
|
+ " WHEN " + Mailbox.TYPE_TRASH + " THEN 4"
|
|
|
|
+ " WHEN " + Mailbox.TYPE_JUNK + " THEN 5"
|
|
|
|
// Other mailboxes (i.e. of Mailbox.TYPE_MAIL) are shown in alphabetical order.
|
|
|
|
+ " ELSE 10 END"
|
|
|
|
+ " ," + MailboxColumns.DISPLAY_NAME + " COLLATE LOCALIZED ASC";
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Mapping of UIProvider columns to EmailProvider columns for a message's attachments
|
|
|
|
*/
|
|
|
|
private static final ProjectionMap sAttachmentMap = ProjectionMap.builder()
|
|
|
|
.add(UIProvider.AttachmentColumns.NAME, AttachmentColumns.FILENAME)
|
|
|
|
.add(UIProvider.AttachmentColumns.SIZE, AttachmentColumns.SIZE)
|
|
|
|
.add(UIProvider.AttachmentColumns.URI, uriWithId("uiattachment"))
|
|
|
|
.add(UIProvider.AttachmentColumns.CONTENT_TYPE, AttachmentColumns.MIME_TYPE)
|
|
|
|
.add(UIProvider.AttachmentColumns.STATE, AttachmentColumns.UI_STATE)
|
|
|
|
.add(UIProvider.AttachmentColumns.DESTINATION, AttachmentColumns.UI_DESTINATION)
|
|
|
|
.add(UIProvider.AttachmentColumns.DOWNLOADED_SIZE, AttachmentColumns.UI_DOWNLOADED_SIZE)
|
|
|
|
.add(UIProvider.AttachmentColumns.CONTENT_URI, AttachmentColumns.CONTENT_URI)
|
|
|
|
.build();
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Generate the SELECT clause using a specified mapping and the original UI projection
|
|
|
|
* @param map the ProjectionMap to use for this projection
|
|
|
|
* @param projection the projection as sent by UnifiedEmail
|
|
|
|
* @param values ContentValues to be used if the ProjectionMap entry is null
|
|
|
|
* @return a StringBuilder containing the SELECT expression for a SQLite query
|
|
|
|
*/
|
|
|
|
private StringBuilder genSelect(ProjectionMap map, String[] projection) {
|
|
|
|
return genSelect(map, projection, EMPTY_CONTENT_VALUES);
|
|
|
|
}
|
|
|
|
|
|
|
|
private StringBuilder genSelect(ProjectionMap map, String[] projection, ContentValues values) {
|
|
|
|
StringBuilder sb = new StringBuilder("SELECT ");
|
|
|
|
boolean first = true;
|
|
|
|
for (String column: projection) {
|
|
|
|
if (first) {
|
|
|
|
first = false;
|
|
|
|
} else {
|
|
|
|
sb.append(',');
|
|
|
|
}
|
|
|
|
String val = null;
|
|
|
|
// First look at values; this is an override of default behavior
|
|
|
|
if (values.containsKey(column)) {
|
|
|
|
String value = values.getAsString(column);
|
|
|
|
if (value.startsWith("@")) {
|
|
|
|
val = value.substring(1) + " AS " + column;
|
|
|
|
} else {
|
|
|
|
val = "'" + values.getAsString(column) + "' AS " + column;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// Now, get the standard value for the column from our projection map
|
|
|
|
val = map.get(column);
|
|
|
|
// If we don't have the column, return "NULL AS <column>", and warn
|
|
|
|
if (val == null) {
|
|
|
|
val = "NULL AS " + column;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
sb.append(val);
|
|
|
|
}
|
|
|
|
return sb;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Convenience method to create a Uri string given the "type" of query; we append the type
|
|
|
|
* of the query and the id column name (_id)
|
|
|
|
*
|
|
|
|
* @param type the "type" of the query, as defined by our UriMatcher definitions
|
|
|
|
* @return a Uri string
|
|
|
|
*/
|
|
|
|
private static String uriWithId(String type) {
|
|
|
|
return uriWithColumn(type, EmailContent.RECORD_ID);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Convenience method to create a Uri string given the "type" of query; we append the type
|
|
|
|
* of the query and the passed in column name
|
|
|
|
*
|
|
|
|
* @param type the "type" of the query, as defined by our UriMatcher definitions
|
|
|
|
* @param columnName the column in the table being queried
|
|
|
|
* @return a Uri string
|
|
|
|
*/
|
|
|
|
private static String uriWithColumn(String type, String columnName) {
|
|
|
|
return "'content://" + EmailContent.AUTHORITY + "/" + type + "/' || " + columnName;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Convenience method to create a Uri string given the "type" of query and the table name to
|
|
|
|
* which it applies; we append the type of the query and the fully qualified (FQ) id column
|
|
|
|
* (i.e. including the table name); we need this for join queries where _id would otherwise
|
|
|
|
* be ambiguous
|
|
|
|
*
|
|
|
|
* @param type the "type" of the query, as defined by our UriMatcher definitions
|
|
|
|
* @param tableName the name of the table whose _id is referred to
|
|
|
|
* @return a Uri string
|
|
|
|
*/
|
|
|
|
private static String uriWithFQId(String type, String tableName) {
|
|
|
|
return "'content://" + EmailContent.AUTHORITY + "/" + type + "/' || " + tableName + "._id";
|
|
|
|
}
|
|
|
|
|
|
|
|
// Regex that matches start of img tag. '<(?i)img\s+'.
|
|
|
|
private static final Pattern IMG_TAG_START_REGEX = Pattern.compile("<(?i)img\\s+");
|
|
|
|
|
2012-06-28 20:09:13 +00:00
|
|
|
/**
|
|
|
|
* Class that holds the sqlite query and the attachment (JSON) value (which might be null)
|
|
|
|
*/
|
|
|
|
private static class MessageQuery {
|
|
|
|
final String query;
|
|
|
|
final String attachmentJson;
|
|
|
|
|
|
|
|
MessageQuery(String _query, String _attachmentJson) {
|
|
|
|
query = _query;
|
|
|
|
attachmentJson = _attachmentJson;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-06-28 17:40:46 +00:00
|
|
|
/**
|
|
|
|
* Generate the "view message" SQLite query, given a projection from UnifiedEmail
|
|
|
|
*
|
|
|
|
* @param uiProjection as passed from UnifiedEmail
|
|
|
|
* @return the SQLite query to be executed on the EmailProvider database
|
|
|
|
*/
|
2012-06-28 20:09:13 +00:00
|
|
|
private MessageQuery genQueryViewMessage(String[] uiProjection, String id) {
|
2012-06-28 17:40:46 +00:00
|
|
|
Context context = getContext();
|
|
|
|
long messageId = Long.parseLong(id);
|
|
|
|
Message msg = Message.restoreMessageWithId(context, messageId);
|
|
|
|
ContentValues values = new ContentValues();
|
2012-06-28 20:09:13 +00:00
|
|
|
String attachmentJson = null;
|
2012-06-28 17:40:46 +00:00
|
|
|
if (msg != null) {
|
|
|
|
if (msg.mFlagLoaded == Message.FLAG_LOADED_PARTIAL) {
|
|
|
|
EmailServiceProxy service =
|
|
|
|
EmailServiceUtils.getServiceForAccount(context, null, msg.mAccountKey);
|
|
|
|
try {
|
|
|
|
service.loadMore(messageId);
|
|
|
|
} catch (RemoteException e) {
|
|
|
|
// Nothing to do
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Body body = Body.restoreBodyWithMessageId(context, messageId);
|
|
|
|
if (body != null) {
|
|
|
|
if (body.mHtmlContent != null) {
|
|
|
|
if (IMG_TAG_START_REGEX.matcher(body.mHtmlContent).find()) {
|
|
|
|
values.put(UIProvider.MessageColumns.EMBEDS_EXTERNAL_RESOURCES, 1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Address[] fromList = Address.unpack(msg.mFrom);
|
|
|
|
int autoShowImages = 0;
|
|
|
|
Preferences prefs = Preferences.getPreferences(context);
|
|
|
|
for (Address sender : fromList) {
|
|
|
|
String email = sender.getAddress();
|
|
|
|
if (prefs.shouldShowImagesFor(email)) {
|
|
|
|
autoShowImages = 1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
values.put(UIProvider.MessageColumns.ALWAYS_SHOW_IMAGES, autoShowImages);
|
|
|
|
// Add attachments...
|
|
|
|
Attachment[] atts = Attachment.restoreAttachmentsWithMessageId(context, messageId);
|
|
|
|
if (atts.length > 0) {
|
|
|
|
ArrayList<com.android.mail.providers.Attachment> uiAtts =
|
|
|
|
new ArrayList<com.android.mail.providers.Attachment>();
|
|
|
|
for (Attachment att : atts) {
|
|
|
|
if (att.mContentId != null && att.mContentUri != null) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
com.android.mail.providers.Attachment uiAtt =
|
|
|
|
new com.android.mail.providers.Attachment();
|
|
|
|
uiAtt.name = att.mFileName;
|
|
|
|
uiAtt.contentType = att.mMimeType;
|
|
|
|
uiAtt.size = (int) att.mSize;
|
|
|
|
uiAtt.uri = uiUri("uiattachment", att.mId);
|
|
|
|
uiAtts.add(uiAtt);
|
|
|
|
}
|
2012-06-28 20:09:13 +00:00
|
|
|
values.put(UIProvider.MessageColumns.ATTACHMENTS, "@?"); // @ for literal
|
|
|
|
attachmentJson = com.android.mail.providers.Attachment.toJSONArray(uiAtts);
|
2012-06-28 17:40:46 +00:00
|
|
|
}
|
|
|
|
if (msg.mDraftInfo != 0) {
|
|
|
|
values.put(UIProvider.MessageColumns.APPEND_REF_MESSAGE_CONTENT,
|
|
|
|
(msg.mDraftInfo & Message.DRAFT_INFO_APPEND_REF_MESSAGE) != 0 ? 1 : 0);
|
|
|
|
values.put(UIProvider.MessageColumns.QUOTE_START_POS,
|
|
|
|
msg.mDraftInfo & Message.DRAFT_INFO_QUOTE_POS_MASK);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if ((msg.mFlags & Message.FLAG_INCOMING_MEETING_INVITE) != 0) {
|
|
|
|
values.put(UIProvider.MessageColumns.EVENT_INTENT_URI,
|
|
|
|
"content://ui.email2.android.com/event/" + msg.mId);
|
|
|
|
}
|
|
|
|
StringBuilder sb = genSelect(sMessageViewMap, uiProjection, values);
|
|
|
|
sb.append(" FROM " + Message.TABLE_NAME + "," + Body.TABLE_NAME + " WHERE " +
|
|
|
|
Body.MESSAGE_KEY + "=" + Message.TABLE_NAME + "." + Message.RECORD_ID + " AND " +
|
|
|
|
Message.TABLE_NAME + "." + Message.RECORD_ID + "=?");
|
2012-06-28 20:09:13 +00:00
|
|
|
String sql = sb.toString();
|
|
|
|
return new MessageQuery(sql, attachmentJson);
|
2012-06-28 17:40:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Generate the "message list" SQLite query, given a projection from UnifiedEmail
|
|
|
|
*
|
|
|
|
* @param uiProjection as passed from UnifiedEmail
|
|
|
|
* @return the SQLite query to be executed on the EmailProvider database
|
|
|
|
*/
|
|
|
|
private String genQueryMailboxMessages(String[] uiProjection) {
|
|
|
|
StringBuilder sb = genSelect(sMessageListMap, uiProjection);
|
|
|
|
sb.append(" FROM " + Message.TABLE_NAME + " WHERE " + Message.MAILBOX_KEY + "=? ORDER BY " +
|
|
|
|
MessageColumns.TIMESTAMP + " DESC");
|
|
|
|
return sb.toString();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Generate various virtual mailbox SQLite queries, given a projection from UnifiedEmail
|
|
|
|
*
|
|
|
|
* @param uiProjection as passed from UnifiedEmail
|
|
|
|
* @param id the id of the virtual mailbox
|
|
|
|
* @return the SQLite query to be executed on the EmailProvider database
|
|
|
|
*/
|
|
|
|
private Cursor getVirtualMailboxMessagesCursor(SQLiteDatabase db, String[] uiProjection,
|
|
|
|
long mailboxId) {
|
|
|
|
ContentValues values = new ContentValues();
|
|
|
|
values.put(UIProvider.ConversationColumns.COLOR, CONVERSATION_COLOR);
|
|
|
|
StringBuilder sb = genSelect(sMessageListMap, uiProjection, values);
|
|
|
|
if (isCombinedMailbox(mailboxId)) {
|
|
|
|
switch (getVirtualMailboxType(mailboxId)) {
|
|
|
|
case Mailbox.TYPE_INBOX:
|
|
|
|
sb.append(" FROM " + Message.TABLE_NAME + " WHERE " +
|
|
|
|
MessageColumns.MAILBOX_KEY + " IN (SELECT " + MailboxColumns.ID +
|
|
|
|
" FROM " + Mailbox.TABLE_NAME + " WHERE " + MailboxColumns.TYPE +
|
|
|
|
"=" + Mailbox.TYPE_INBOX + ") ORDER BY " + MessageColumns.TIMESTAMP +
|
|
|
|
" DESC");
|
|
|
|
break;
|
|
|
|
case Mailbox.TYPE_STARRED:
|
|
|
|
sb.append(" FROM " + Message.TABLE_NAME + " WHERE " +
|
|
|
|
MessageColumns.FLAG_FAVORITE + "=1 ORDER BY " +
|
|
|
|
MessageColumns.TIMESTAMP + " DESC");
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
throw new IllegalArgumentException("No virtual mailbox for: " + mailboxId);
|
|
|
|
}
|
|
|
|
return db.rawQuery(sb.toString(), null);
|
|
|
|
} else {
|
|
|
|
switch (getVirtualMailboxType(mailboxId)) {
|
|
|
|
case Mailbox.TYPE_STARRED:
|
|
|
|
sb.append(" FROM " + Message.TABLE_NAME + " WHERE " +
|
|
|
|
MessageColumns.ACCOUNT_KEY + "=? AND " +
|
|
|
|
MessageColumns.FLAG_FAVORITE + "=1 ORDER BY " +
|
|
|
|
MessageColumns.TIMESTAMP + " DESC");
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
throw new IllegalArgumentException("No virtual mailbox for: " + mailboxId);
|
|
|
|
}
|
|
|
|
return db.rawQuery(sb.toString(),
|
|
|
|
new String[] {getVirtualMailboxAccountIdString(mailboxId)});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Generate the "message list" SQLite query, given a projection from UnifiedEmail
|
|
|
|
*
|
|
|
|
* @param uiProjection as passed from UnifiedEmail
|
|
|
|
* @return the SQLite query to be executed on the EmailProvider database
|
|
|
|
*/
|
|
|
|
private String genQueryConversation(String[] uiProjection) {
|
|
|
|
StringBuilder sb = genSelect(sMessageListMap, uiProjection);
|
|
|
|
sb.append(" FROM " + Message.TABLE_NAME + " WHERE " + Message.RECORD_ID + "=?");
|
|
|
|
return sb.toString();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Generate the "top level folder list" SQLite query, given a projection from UnifiedEmail
|
|
|
|
*
|
|
|
|
* @param uiProjection as passed from UnifiedEmail
|
|
|
|
* @return the SQLite query to be executed on the EmailProvider database
|
|
|
|
*/
|
|
|
|
private String genQueryAccountMailboxes(String[] uiProjection) {
|
|
|
|
StringBuilder sb = genSelect(sFolderListMap, uiProjection);
|
|
|
|
sb.append(" FROM " + Mailbox.TABLE_NAME + " WHERE " + MailboxColumns.ACCOUNT_KEY +
|
|
|
|
"=? AND " + MailboxColumns.TYPE + " < " + Mailbox.TYPE_NOT_EMAIL +
|
|
|
|
" AND " + MailboxColumns.PARENT_KEY + " < 0 ORDER BY ");
|
|
|
|
sb.append(MAILBOX_ORDER_BY);
|
|
|
|
return sb.toString();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Generate the "all folders" SQLite query, given a projection from UnifiedEmail. The list is
|
|
|
|
* sorted by the name as it appears in a hierarchical listing
|
|
|
|
*
|
|
|
|
* @param uiProjection as passed from UnifiedEmail
|
|
|
|
* @return the SQLite query to be executed on the EmailProvider database
|
|
|
|
*/
|
|
|
|
private String genQueryAccountAllMailboxes(String[] uiProjection) {
|
|
|
|
StringBuilder sb = genSelect(sFolderListMap, uiProjection);
|
|
|
|
// Use a derived column to choose either hierarchicalName or displayName
|
|
|
|
sb.append(", case when " + MailboxColumns.HIERARCHICAL_NAME + " is null then " +
|
|
|
|
MailboxColumns.DISPLAY_NAME + " else " + MailboxColumns.HIERARCHICAL_NAME +
|
|
|
|
" end as h_name");
|
|
|
|
// Order by the derived column
|
|
|
|
sb.append(" FROM " + Mailbox.TABLE_NAME + " WHERE " + MailboxColumns.ACCOUNT_KEY +
|
|
|
|
"=? AND " + MailboxColumns.TYPE + " < " + Mailbox.TYPE_NOT_EMAIL +
|
|
|
|
" ORDER BY h_name");
|
|
|
|
return sb.toString();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Generate the "recent folder list" SQLite query, given a projection from UnifiedEmail
|
|
|
|
*
|
|
|
|
* @param uiProjection as passed from UnifiedEmail
|
|
|
|
* @return the SQLite query to be executed on the EmailProvider database
|
|
|
|
*/
|
|
|
|
private String genQueryRecentMailboxes(String[] uiProjection) {
|
|
|
|
StringBuilder sb = genSelect(sFolderListMap, uiProjection);
|
|
|
|
sb.append(" FROM " + Mailbox.TABLE_NAME + " WHERE " + MailboxColumns.ACCOUNT_KEY +
|
|
|
|
"=? AND " + MailboxColumns.TYPE + " < " + Mailbox.TYPE_NOT_EMAIL +
|
|
|
|
" AND " + MailboxColumns.PARENT_KEY + " < 0 AND " +
|
|
|
|
MailboxColumns.LAST_TOUCHED_TIME + " > 0 ORDER BY " +
|
|
|
|
MailboxColumns.LAST_TOUCHED_TIME + " DESC");
|
|
|
|
return sb.toString();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Generate a "single mailbox" SQLite query, given a projection from UnifiedEmail
|
|
|
|
*
|
|
|
|
* @param uiProjection as passed from UnifiedEmail
|
|
|
|
* @return the SQLite query to be executed on the EmailProvider database
|
|
|
|
*/
|
|
|
|
private String genQueryMailbox(String[] uiProjection, String id) {
|
|
|
|
long mailboxId = Long.parseLong(id);
|
|
|
|
ContentValues values = new ContentValues();
|
|
|
|
if (mSearchParams != null && mailboxId == mSearchParams.mSearchMailboxId) {
|
|
|
|
// This is the current search mailbox; use the total count
|
|
|
|
values = new ContentValues();
|
|
|
|
values.put(UIProvider.FolderColumns.TOTAL_COUNT, mSearchParams.mTotalCount);
|
|
|
|
// "load more" is valid for search results
|
|
|
|
values.put(UIProvider.FolderColumns.LOAD_MORE_URI,
|
|
|
|
uiUriString("uiloadmore", mailboxId));
|
|
|
|
} else {
|
|
|
|
Context context = getContext();
|
|
|
|
Mailbox mailbox = Mailbox.restoreMailboxWithId(context, mailboxId);
|
|
|
|
// Make sure we can't get NPE if mailbox has disappeared (the result will end up moot)
|
|
|
|
if (mailbox != null) {
|
|
|
|
String protocol = Account.getProtocol(context, mailbox.mAccountKey);
|
|
|
|
EmailServiceInfo info = EmailServiceUtils.getServiceInfo(context, protocol);
|
|
|
|
// "load more" is valid for protocols not supporting "lookback"
|
|
|
|
if (info != null && !info.offerLookback) {
|
|
|
|
values.put(UIProvider.FolderColumns.LOAD_MORE_URI,
|
|
|
|
uiUriString("uiloadmore", mailboxId));
|
|
|
|
} else {
|
|
|
|
int caps = UIProvider.FolderCapabilities.SUPPORTS_SETTINGS;
|
|
|
|
if ((mailbox.mFlags & Mailbox.FLAG_ACCEPTS_MOVED_MAIL) != 0) {
|
|
|
|
caps |= UIProvider.FolderCapabilities.CAN_ACCEPT_MOVED_MESSAGES;
|
|
|
|
}
|
|
|
|
values.put(UIProvider.FolderColumns.CAPABILITIES, caps);
|
|
|
|
}
|
|
|
|
// For trash, we don't allow undo
|
|
|
|
if (mailbox.mType == Mailbox.TYPE_TRASH) {
|
|
|
|
values.put(UIProvider.FolderColumns.CAPABILITIES,
|
|
|
|
UIProvider.FolderCapabilities.CAN_ACCEPT_MOVED_MESSAGES |
|
|
|
|
UIProvider.FolderCapabilities.CAN_HOLD_MAIL |
|
|
|
|
UIProvider.FolderCapabilities.DELETE_ACTION_FINAL);
|
|
|
|
}
|
|
|
|
if (isVirtualMailbox(mailboxId)) {
|
|
|
|
int capa = values.getAsInteger(UIProvider.FolderColumns.CAPABILITIES);
|
|
|
|
values.put(UIProvider.FolderColumns.CAPABILITIES,
|
|
|
|
capa | UIProvider.FolderCapabilities.IS_VIRTUAL);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
StringBuilder sb = genSelect(sFolderListMap, uiProjection, values);
|
|
|
|
sb.append(" FROM " + Mailbox.TABLE_NAME + " WHERE " + MailboxColumns.ID + "=?");
|
|
|
|
return sb.toString();
|
|
|
|
}
|
|
|
|
|
|
|
|
private static final Uri BASE_EXTERNAL_URI = Uri.parse("content://ui.email.android.com");
|
|
|
|
|
|
|
|
private static final Uri BASE_EXTERAL_URI2 = Uri.parse("content://ui.email2.android.com");
|
|
|
|
|
|
|
|
private static String getExternalUriString(String segment, String account) {
|
|
|
|
return BASE_EXTERNAL_URI.buildUpon().appendPath(segment)
|
|
|
|
.appendQueryParameter("account", account).build().toString();
|
|
|
|
}
|
|
|
|
|
|
|
|
private static String getExternalUriStringEmail2(String segment, String account) {
|
|
|
|
return BASE_EXTERAL_URI2.buildUpon().appendPath(segment)
|
|
|
|
.appendQueryParameter("account", account).build().toString();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Generate a "single account" SQLite query, given a projection from UnifiedEmail
|
|
|
|
*
|
|
|
|
* @param uiProjection as passed from UnifiedEmail
|
|
|
|
* @return the SQLite query to be executed on the EmailProvider database
|
|
|
|
*/
|
|
|
|
private String genQueryAccount(String[] uiProjection, String id) {
|
|
|
|
ContentValues values = new ContentValues();
|
|
|
|
long accountId = Long.parseLong(id);
|
|
|
|
|
|
|
|
// Get account capabilities from the service
|
|
|
|
EmailServiceProxy service = EmailServiceUtils.getServiceForAccount(getContext(),
|
|
|
|
mServiceCallback, accountId);
|
|
|
|
int capabilities = 0;
|
|
|
|
try {
|
|
|
|
capabilities = service.getCapabilities(accountId);
|
|
|
|
} catch (RemoteException e) {
|
|
|
|
}
|
|
|
|
values.put(UIProvider.AccountColumns.CAPABILITIES, capabilities);
|
|
|
|
|
|
|
|
values.put(UIProvider.AccountColumns.SETTINGS_INTENT_URI,
|
|
|
|
getExternalUriString("settings", id));
|
|
|
|
values.put(UIProvider.AccountColumns.COMPOSE_URI,
|
|
|
|
getExternalUriStringEmail2("compose", id));
|
|
|
|
values.put(UIProvider.AccountColumns.MIME_TYPE, EMAIL_APP_MIME_TYPE);
|
|
|
|
values.put(UIProvider.AccountColumns.COLOR, ACCOUNT_COLOR);
|
|
|
|
|
|
|
|
Preferences prefs = Preferences.getPreferences(getContext());
|
|
|
|
values.put(UIProvider.AccountColumns.SettingsColumns.CONFIRM_DELETE,
|
|
|
|
prefs.getConfirmDelete() ? "1" : "0");
|
|
|
|
values.put(UIProvider.AccountColumns.SettingsColumns.CONFIRM_SEND,
|
|
|
|
prefs.getConfirmSend() ? "1" : "0");
|
|
|
|
values.put(UIProvider.AccountColumns.SettingsColumns.HIDE_CHECKBOXES,
|
|
|
|
prefs.getHideCheckboxes() ? "1" : "0");
|
|
|
|
int autoAdvance = prefs.getAutoAdvanceDirection();
|
|
|
|
values.put(UIProvider.AccountColumns.SettingsColumns.AUTO_ADVANCE,
|
|
|
|
autoAdvanceToUiValue(autoAdvance));
|
|
|
|
int textZoom = prefs.getTextZoom();
|
|
|
|
values.put(UIProvider.AccountColumns.SettingsColumns.MESSAGE_TEXT_SIZE,
|
|
|
|
textZoomToUiValue(textZoom));
|
|
|
|
// Set default inbox, if we've got an inbox; otherwise, say initial sync needed
|
|
|
|
long mailboxId = Mailbox.findMailboxOfType(getContext(), accountId, Mailbox.TYPE_INBOX);
|
|
|
|
if (mailboxId != Mailbox.NO_MAILBOX) {
|
|
|
|
values.put(UIProvider.AccountColumns.SettingsColumns.DEFAULT_INBOX,
|
|
|
|
uiUriString("uifolder", mailboxId));
|
|
|
|
values.put(UIProvider.AccountColumns.SYNC_STATUS, UIProvider.SyncStatus.NO_SYNC);
|
|
|
|
} else {
|
|
|
|
values.put(UIProvider.AccountColumns.SYNC_STATUS,
|
|
|
|
UIProvider.SyncStatus.INITIAL_SYNC_NEEDED);
|
|
|
|
}
|
|
|
|
|
|
|
|
StringBuilder sb = genSelect(sAccountListMap, uiProjection, values);
|
|
|
|
sb.append(" FROM " + Account.TABLE_NAME + " WHERE " + AccountColumns.ID + "=?");
|
|
|
|
return sb.toString();
|
|
|
|
}
|
|
|
|
|
|
|
|
private int autoAdvanceToUiValue(int autoAdvance) {
|
|
|
|
switch(autoAdvance) {
|
|
|
|
case Preferences.AUTO_ADVANCE_OLDER:
|
|
|
|
return UIProvider.AutoAdvance.OLDER;
|
|
|
|
case Preferences.AUTO_ADVANCE_NEWER:
|
|
|
|
return UIProvider.AutoAdvance.NEWER;
|
|
|
|
case Preferences.AUTO_ADVANCE_MESSAGE_LIST:
|
|
|
|
default:
|
|
|
|
return UIProvider.AutoAdvance.LIST;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private int textZoomToUiValue(int textZoom) {
|
|
|
|
switch(textZoom) {
|
|
|
|
case Preferences.TEXT_ZOOM_HUGE:
|
|
|
|
return UIProvider.MessageTextSize.HUGE;
|
|
|
|
case Preferences.TEXT_ZOOM_LARGE:
|
|
|
|
return UIProvider.MessageTextSize.LARGE;
|
|
|
|
case Preferences.TEXT_ZOOM_NORMAL:
|
|
|
|
return UIProvider.MessageTextSize.NORMAL;
|
|
|
|
case Preferences.TEXT_ZOOM_SMALL:
|
|
|
|
return UIProvider.MessageTextSize.SMALL;
|
|
|
|
case Preferences.TEXT_ZOOM_TINY:
|
|
|
|
return UIProvider.MessageTextSize.TINY;
|
|
|
|
default:
|
|
|
|
return UIProvider.MessageTextSize.NORMAL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Generate a Uri string for a combined mailbox uri
|
|
|
|
* @param type the uri command type (e.g. "uimessages")
|
|
|
|
* @param id the id of the item (e.g. an account, mailbox, or message id)
|
|
|
|
* @return a Uri string
|
|
|
|
*/
|
|
|
|
private static String combinedUriString(String type, String id) {
|
|
|
|
return "content://" + EmailContent.AUTHORITY + "/" + type + "/" + id;
|
|
|
|
}
|
|
|
|
|
|
|
|
private static final long COMBINED_ACCOUNT_ID = 0x10000000;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Generate an id for a combined mailbox of a given type
|
|
|
|
* @param type the mailbox type for the combined mailbox
|
|
|
|
* @return the id, as a String
|
|
|
|
*/
|
|
|
|
private static String combinedMailboxId(int type) {
|
|
|
|
return Long.toString(Account.ACCOUNT_ID_COMBINED_VIEW + type);
|
|
|
|
}
|
|
|
|
|
|
|
|
private static String getVirtualMailboxIdString(long accountId, int type) {
|
|
|
|
return Long.toString(getVirtualMailboxId(accountId, type));
|
|
|
|
}
|
|
|
|
|
|
|
|
private static long getVirtualMailboxId(long accountId, int type) {
|
|
|
|
return (accountId << 32) + type;
|
|
|
|
}
|
|
|
|
|
|
|
|
private static boolean isVirtualMailbox(long mailboxId) {
|
|
|
|
return mailboxId >= 0x100000000L;
|
|
|
|
}
|
|
|
|
|
|
|
|
private static boolean isCombinedMailbox(long mailboxId) {
|
|
|
|
return (mailboxId >> 32) == COMBINED_ACCOUNT_ID;
|
|
|
|
}
|
|
|
|
|
|
|
|
private static long getVirtualMailboxAccountId(long mailboxId) {
|
|
|
|
return mailboxId >> 32;
|
|
|
|
}
|
|
|
|
|
|
|
|
private static String getVirtualMailboxAccountIdString(long mailboxId) {
|
|
|
|
return Long.toString(mailboxId >> 32);
|
|
|
|
}
|
|
|
|
|
|
|
|
private static int getVirtualMailboxType(long mailboxId) {
|
|
|
|
return (int)(mailboxId & 0xF);
|
|
|
|
}
|
|
|
|
|
|
|
|
private void addCombinedAccountRow(MatrixCursor mc) {
|
|
|
|
long id = Account.getDefaultAccountId(getContext());
|
|
|
|
if (id == Account.NO_ACCOUNT) return;
|
|
|
|
String idString = Long.toString(id);
|
|
|
|
Object[] values = new Object[UIProvider.ACCOUNTS_PROJECTION.length];
|
|
|
|
values[UIProvider.ACCOUNT_ID_COLUMN] = 0;
|
|
|
|
values[UIProvider.ACCOUNT_CAPABILITIES_COLUMN] =
|
|
|
|
AccountCapabilities.UNDO | AccountCapabilities.SENDING_UNAVAILABLE;
|
|
|
|
values[UIProvider.ACCOUNT_FOLDER_LIST_URI_COLUMN] =
|
|
|
|
combinedUriString("uifolders", COMBINED_ACCOUNT_ID_STRING);
|
|
|
|
values[UIProvider.ACCOUNT_NAME_COLUMN] = getContext().getString(
|
|
|
|
R.string.mailbox_list_account_selector_combined_view);
|
|
|
|
values[UIProvider.ACCOUNT_SAVE_DRAFT_URI_COLUMN] =
|
|
|
|
combinedUriString("uisavedraft", idString);
|
|
|
|
values[UIProvider.ACCOUNT_SEND_MESSAGE_URI_COLUMN] =
|
|
|
|
combinedUriString("uisendmail", idString);
|
|
|
|
values[UIProvider.ACCOUNT_UNDO_URI_COLUMN] =
|
|
|
|
"'content://" + UIProvider.AUTHORITY + "/uiundo'";
|
|
|
|
values[UIProvider.ACCOUNT_URI_COLUMN] =
|
|
|
|
combinedUriString("uiaccount", COMBINED_ACCOUNT_ID_STRING);
|
|
|
|
values[UIProvider.ACCOUNT_MIME_TYPE_COLUMN] = EMAIL_APP_MIME_TYPE;
|
|
|
|
values[UIProvider.ACCOUNT_SETTINGS_INTENT_URI_COLUMN] =
|
|
|
|
getExternalUriString("settings", COMBINED_ACCOUNT_ID_STRING);
|
|
|
|
values[UIProvider.ACCOUNT_COMPOSE_INTENT_URI_COLUMN] =
|
|
|
|
getExternalUriStringEmail2("compose", Long.toString(id));
|
|
|
|
|
|
|
|
// TODO: Get these from default account?
|
|
|
|
Preferences prefs = Preferences.getPreferences(getContext());
|
|
|
|
values[UIProvider.ACCOUNT_SETTINGS_AUTO_ADVANCE_COLUMN] =
|
|
|
|
Integer.toString(UIProvider.AutoAdvance.NEWER);
|
|
|
|
values[UIProvider.ACCOUNT_SETTINGS_MESSAGE_TEXT_SIZE_COLUMN] =
|
|
|
|
Integer.toString(UIProvider.MessageTextSize.NORMAL);
|
|
|
|
values[UIProvider.ACCOUNT_SETTINGS_SNAP_HEADERS_COLUMN] =
|
|
|
|
Integer.toString(UIProvider.SnapHeaderValue.ALWAYS);
|
|
|
|
//.add(UIProvider.SettingsColumns.SIGNATURE, AccountColumns.SIGNATURE)
|
|
|
|
values[UIProvider.ACCOUNT_SETTINGS_REPLY_BEHAVIOR_COLUMN] =
|
|
|
|
Integer.toString(UIProvider.DefaultReplyBehavior.REPLY);
|
|
|
|
values[UIProvider.ACCOUNT_SETTINGS_HIDE_CHECKBOXES_COLUMN] = 0;
|
|
|
|
values[UIProvider.ACCOUNT_SETTINGS_CONFIRM_DELETE_COLUMN] =
|
|
|
|
prefs.getConfirmDelete() ? 1 : 0;
|
|
|
|
values[UIProvider.ACCOUNT_SETTINGS_CONFIRM_ARCHIVE_COLUMN] = 0;
|
|
|
|
values[UIProvider.ACCOUNT_SETTINGS_CONFIRM_SEND_COLUMN] = prefs.getConfirmSend() ? 1 : 0;
|
|
|
|
values[UIProvider.ACCOUNT_SETTINGS_HIDE_CHECKBOXES_COLUMN] =
|
|
|
|
prefs.getHideCheckboxes() ? 1 : 0;
|
|
|
|
values[UIProvider.ACCOUNT_SETTINGS_DEFAULT_INBOX_COLUMN] = combinedUriString("uifolder",
|
|
|
|
combinedMailboxId(Mailbox.TYPE_INBOX));
|
|
|
|
|
|
|
|
mc.addRow(values);
|
|
|
|
}
|
|
|
|
|
|
|
|
private Cursor getVirtualMailboxCursor(long mailboxId) {
|
|
|
|
MatrixCursor mc = new MatrixCursor(UIProvider.FOLDERS_PROJECTION, 1);
|
|
|
|
mc.addRow(getVirtualMailboxRow(getVirtualMailboxAccountId(mailboxId),
|
|
|
|
getVirtualMailboxType(mailboxId)));
|
|
|
|
return mc;
|
|
|
|
}
|
|
|
|
|
|
|
|
private Object[] getVirtualMailboxRow(long accountId, int mailboxType) {
|
|
|
|
String idString = getVirtualMailboxIdString(accountId, mailboxType);
|
|
|
|
Object[] values = new Object[UIProvider.FOLDERS_PROJECTION.length];
|
|
|
|
values[UIProvider.FOLDER_ID_COLUMN] = 0;
|
|
|
|
values[UIProvider.FOLDER_URI_COLUMN] = combinedUriString("uifolder", idString);
|
|
|
|
values[UIProvider.FOLDER_NAME_COLUMN] = getMailboxNameForType(mailboxType);
|
|
|
|
values[UIProvider.FOLDER_HAS_CHILDREN_COLUMN] = 0;
|
|
|
|
values[UIProvider.FOLDER_CAPABILITIES_COLUMN] = UIProvider.FolderCapabilities.IS_VIRTUAL;
|
|
|
|
values[UIProvider.FOLDER_CONVERSATION_LIST_URI_COLUMN] = combinedUriString("uimessages",
|
|
|
|
idString);
|
|
|
|
values[UIProvider.FOLDER_ID_COLUMN] = 0;
|
|
|
|
return values;
|
|
|
|
}
|
|
|
|
|
|
|
|
private Cursor uiAccounts(String[] uiProjection) {
|
|
|
|
Context context = getContext();
|
|
|
|
SQLiteDatabase db = getDatabase(context);
|
|
|
|
Cursor accountIdCursor =
|
|
|
|
db.rawQuery("select _id from " + Account.TABLE_NAME, new String[0]);
|
|
|
|
int numAccounts = accountIdCursor.getCount();
|
|
|
|
boolean combinedAccount = false;
|
|
|
|
if (numAccounts > 1) {
|
|
|
|
combinedAccount = true;
|
|
|
|
numAccounts++;
|
|
|
|
}
|
|
|
|
final Bundle extras = new Bundle();
|
|
|
|
// Email always returns the accurate number of accounts
|
|
|
|
extras.putInt(AccountCursorExtraKeys.ACCOUNTS_LOADED, 1);
|
|
|
|
final MatrixCursor mc =
|
|
|
|
new MatrixCursorWithExtra(uiProjection, accountIdCursor.getCount(), extras);
|
|
|
|
Object[] values = new Object[uiProjection.length];
|
|
|
|
try {
|
|
|
|
if (combinedAccount) {
|
|
|
|
addCombinedAccountRow(mc);
|
|
|
|
}
|
|
|
|
while (accountIdCursor.moveToNext()) {
|
|
|
|
String id = accountIdCursor.getString(0);
|
|
|
|
Cursor accountCursor =
|
|
|
|
db.rawQuery(genQueryAccount(uiProjection, id), new String[] {id});
|
|
|
|
if (accountCursor.moveToNext()) {
|
|
|
|
for (int i = 0; i < uiProjection.length; i++) {
|
|
|
|
values[i] = accountCursor.getString(i);
|
|
|
|
}
|
|
|
|
mc.addRow(values);
|
|
|
|
}
|
|
|
|
accountCursor.close();
|
|
|
|
}
|
|
|
|
} finally {
|
|
|
|
accountIdCursor.close();
|
|
|
|
}
|
|
|
|
mc.setNotificationUri(context.getContentResolver(), UIPROVIDER_ACCOUNTS_NOTIFIER);
|
|
|
|
return mc;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Generate the "attachment list" SQLite query, given a projection from UnifiedEmail
|
|
|
|
*
|
|
|
|
* @param uiProjection as passed from UnifiedEmail
|
|
|
|
* @return the SQLite query to be executed on the EmailProvider database
|
|
|
|
*/
|
|
|
|
private String genQueryAttachments(String[] uiProjection) {
|
|
|
|
StringBuilder sb = genSelect(sAttachmentMap, uiProjection);
|
|
|
|
sb.append(" FROM " + Attachment.TABLE_NAME + " WHERE " + AttachmentColumns.MESSAGE_KEY +
|
|
|
|
" =? ");
|
|
|
|
return sb.toString();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Generate the "single attachment" SQLite query, given a projection from UnifiedEmail
|
|
|
|
*
|
|
|
|
* @param uiProjection as passed from UnifiedEmail
|
|
|
|
* @return the SQLite query to be executed on the EmailProvider database
|
|
|
|
*/
|
|
|
|
private String genQueryAttachment(String[] uiProjection) {
|
|
|
|
StringBuilder sb = genSelect(sAttachmentMap, uiProjection);
|
|
|
|
sb.append(" FROM " + Attachment.TABLE_NAME + " WHERE " + AttachmentColumns.ID + " =? ");
|
|
|
|
return sb.toString();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Generate the "subfolder list" SQLite query, given a projection from UnifiedEmail
|
|
|
|
*
|
|
|
|
* @param uiProjection as passed from UnifiedEmail
|
|
|
|
* @return the SQLite query to be executed on the EmailProvider database
|
|
|
|
*/
|
|
|
|
private String genQuerySubfolders(String[] uiProjection) {
|
|
|
|
StringBuilder sb = genSelect(sFolderListMap, uiProjection);
|
|
|
|
sb.append(" FROM " + Mailbox.TABLE_NAME + " WHERE " + MailboxColumns.PARENT_KEY +
|
|
|
|
" =? ORDER BY ");
|
|
|
|
sb.append(MAILBOX_ORDER_BY);
|
|
|
|
return sb.toString();
|
|
|
|
}
|
|
|
|
|
|
|
|
private static final String COMBINED_ACCOUNT_ID_STRING = Long.toString(COMBINED_ACCOUNT_ID);
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns a cursor over all the folders for a specific URI which corresponds to a single
|
|
|
|
* account.
|
|
|
|
* @param uri
|
|
|
|
* @param uiProjection
|
|
|
|
* @return
|
|
|
|
*/
|
|
|
|
private Cursor uiFolders(Uri uri, String[] uiProjection) {
|
|
|
|
Context context = getContext();
|
|
|
|
SQLiteDatabase db = getDatabase(context);
|
|
|
|
String id = uri.getPathSegments().get(1);
|
|
|
|
if (id.equals(COMBINED_ACCOUNT_ID_STRING)) {
|
|
|
|
MatrixCursor mc = new MatrixCursor(UIProvider.FOLDERS_PROJECTION, 2);
|
|
|
|
Object[] row = getVirtualMailboxRow(COMBINED_ACCOUNT_ID, Mailbox.TYPE_INBOX);
|
|
|
|
int numUnread = EmailContent.count(context, Message.CONTENT_URI,
|
|
|
|
MessageColumns.MAILBOX_KEY + " IN (SELECT " + MailboxColumns.ID +
|
|
|
|
" FROM " + Mailbox.TABLE_NAME + " WHERE " + MailboxColumns.TYPE +
|
|
|
|
"=" + Mailbox.TYPE_INBOX + ") AND " + MessageColumns.FLAG_READ + "=0", null);
|
|
|
|
row[UIProvider.FOLDER_UNREAD_COUNT_COLUMN] = numUnread;
|
|
|
|
mc.addRow(row);
|
|
|
|
int numStarred = EmailContent.count(context, Message.CONTENT_URI,
|
|
|
|
MessageColumns.FLAG_FAVORITE + "=1", null);
|
|
|
|
if (numStarred > 0) {
|
|
|
|
row = getVirtualMailboxRow(COMBINED_ACCOUNT_ID, Mailbox.TYPE_STARRED);
|
|
|
|
row[UIProvider.FOLDER_UNREAD_COUNT_COLUMN] = numStarred;
|
|
|
|
mc.addRow(row);
|
|
|
|
}
|
|
|
|
return mc;
|
|
|
|
} else {
|
|
|
|
Cursor c = db.rawQuery(genQueryAccountMailboxes(uiProjection), new String[] {id});
|
|
|
|
int numStarred = EmailContent.count(context, Message.CONTENT_URI,
|
|
|
|
MessageColumns.ACCOUNT_KEY + "=? AND " + MessageColumns.FLAG_FAVORITE + "=1",
|
|
|
|
new String[] {id});
|
|
|
|
if (numStarred == 0) {
|
|
|
|
return c;
|
|
|
|
} else {
|
|
|
|
// Add starred virtual folder to the cursor
|
|
|
|
// Show number of messages as unread count (for backward compatibility)
|
|
|
|
MatrixCursor starCursor = new MatrixCursor(uiProjection, 1);
|
|
|
|
Object[] row = getVirtualMailboxRow(Long.parseLong(id), Mailbox.TYPE_STARRED);
|
|
|
|
row[UIProvider.FOLDER_UNREAD_COUNT_COLUMN] = numStarred;
|
|
|
|
row[UIProvider.FOLDER_ICON_RES_ID_COLUMN] = R.drawable.ic_menu_star_holo_light;
|
|
|
|
starCursor.addRow(row);
|
|
|
|
Cursor[] cursors = new Cursor[] {starCursor, c};
|
|
|
|
return new MergeCursor(cursors);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns an array of the default recent folders for a given URI which is unique for an
|
|
|
|
* account. Some accounts might not have default recent folders, in which case an empty array
|
|
|
|
* is returned.
|
|
|
|
* @param id
|
|
|
|
* @return
|
|
|
|
*/
|
|
|
|
private Uri[] defaultRecentFolders(final String id) {
|
|
|
|
final SQLiteDatabase db = getDatabase(getContext());
|
|
|
|
if (id.equals(COMBINED_ACCOUNT_ID_STRING)) {
|
|
|
|
// We don't have default recents for the combined view.
|
|
|
|
return new Uri[0];
|
|
|
|
}
|
|
|
|
// We search for the types we want, and find corresponding IDs.
|
|
|
|
final String[] idAndType = { BaseColumns._ID, UIProvider.FolderColumns.TYPE };
|
|
|
|
|
|
|
|
// Sent, Drafts, and Starred are the default recents.
|
|
|
|
final StringBuilder sb = genSelect(sFolderListMap, idAndType);
|
|
|
|
sb.append(" FROM " + Mailbox.TABLE_NAME
|
|
|
|
+ " WHERE " + MailboxColumns.ACCOUNT_KEY + " = " + id
|
|
|
|
+ " AND "
|
|
|
|
+ MailboxColumns.TYPE + " IN (" + Mailbox.TYPE_SENT +
|
|
|
|
", " + Mailbox.TYPE_DRAFTS +
|
|
|
|
", " + Mailbox.TYPE_STARRED
|
|
|
|
+ ")");
|
|
|
|
LogUtils.d(TAG, "defaultRecentFolders: Query is %s", sb);
|
|
|
|
final Cursor c = db.rawQuery(sb.toString(), null);
|
|
|
|
if (c == null || c.getCount() <= 0 || !c.moveToFirst()) {
|
|
|
|
return new Uri[0];
|
|
|
|
}
|
|
|
|
// Read all the IDs of the mailboxes, and turn them into URIs.
|
|
|
|
final Uri[] recentFolders = new Uri[c.getCount()];
|
|
|
|
int i = 0;
|
|
|
|
do {
|
|
|
|
final long folderId = c.getLong(0);
|
|
|
|
recentFolders[i] = uiUri("uifolder", folderId);
|
|
|
|
LogUtils.d(TAG, "Default recent folder: %d, with uri %s", folderId, recentFolders[i]);
|
|
|
|
++i;
|
|
|
|
} while (c.moveToNext());
|
|
|
|
return recentFolders;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Wrapper that handles the visibility feature (i.e. the conversation list is visible, so
|
|
|
|
* any pending notifications for the corresponding mailbox should be canceled)
|
|
|
|
*/
|
|
|
|
static class VisibilityCursor extends CursorWrapper {
|
|
|
|
private final long mMailboxId;
|
|
|
|
private final Context mContext;
|
|
|
|
|
|
|
|
public VisibilityCursor(Context context, Cursor cursor, long mailboxId) {
|
|
|
|
super(cursor);
|
|
|
|
mMailboxId = mailboxId;
|
|
|
|
mContext = context;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public Bundle respond(Bundle params) {
|
|
|
|
final String setVisibilityKey =
|
|
|
|
UIProvider.ConversationCursorCommand.COMMAND_KEY_SET_VISIBILITY;
|
|
|
|
if (params.containsKey(setVisibilityKey)) {
|
|
|
|
final boolean visible = params.getBoolean(setVisibilityKey);
|
|
|
|
if (visible) {
|
|
|
|
NotificationController.getInstance(mContext).cancelNewMessageNotification(
|
|
|
|
mMailboxId);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Return success
|
|
|
|
Bundle response = new Bundle();
|
|
|
|
response.putString(setVisibilityKey,
|
|
|
|
UIProvider.ConversationCursorCommand.COMMAND_RESPONSE_OK);
|
|
|
|
return response;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Handle UnifiedEmail queries here (dispatched from query())
|
|
|
|
*
|
|
|
|
* @param match the UriMatcher match for the original uri passed in from UnifiedEmail
|
|
|
|
* @param uri the original uri passed in from UnifiedEmail
|
|
|
|
* @param uiProjection the projection passed in from UnifiedEmail
|
|
|
|
* @return the result Cursor
|
|
|
|
*/
|
|
|
|
private Cursor uiQuery(int match, Uri uri, String[] uiProjection) {
|
|
|
|
Context context = getContext();
|
|
|
|
ContentResolver resolver = context.getContentResolver();
|
|
|
|
SQLiteDatabase db = getDatabase(context);
|
|
|
|
// Should we ever return null, or throw an exception??
|
|
|
|
Cursor c = null;
|
|
|
|
String id = uri.getPathSegments().get(1);
|
|
|
|
Uri notifyUri = null;
|
|
|
|
switch(match) {
|
|
|
|
case UI_ALL_FOLDERS:
|
|
|
|
c = db.rawQuery(genQueryAccountAllMailboxes(uiProjection), new String[] {id});
|
|
|
|
break;
|
|
|
|
case UI_RECENT_FOLDERS:
|
|
|
|
c = db.rawQuery(genQueryRecentMailboxes(uiProjection), new String[] {id});
|
|
|
|
notifyUri = UIPROVIDER_RECENT_FOLDERS_NOTIFIER.buildUpon().appendPath(id).build();
|
|
|
|
break;
|
|
|
|
case UI_SUBFOLDERS:
|
|
|
|
c = db.rawQuery(genQuerySubfolders(uiProjection), new String[] {id});
|
|
|
|
break;
|
|
|
|
case UI_MESSAGES:
|
|
|
|
long mailboxId = Long.parseLong(id);
|
|
|
|
if (isVirtualMailbox(mailboxId)) {
|
|
|
|
c = getVirtualMailboxMessagesCursor(db, uiProjection, mailboxId);
|
|
|
|
} else {
|
|
|
|
c = db.rawQuery(genQueryMailboxMessages(uiProjection), new String[] {id});
|
|
|
|
}
|
|
|
|
notifyUri = UIPROVIDER_CONVERSATION_NOTIFIER.buildUpon().appendPath(id).build();
|
|
|
|
c = new VisibilityCursor(context, c, mailboxId);
|
|
|
|
break;
|
|
|
|
case UI_MESSAGE:
|
2012-06-28 20:09:13 +00:00
|
|
|
MessageQuery qq = genQueryViewMessage(uiProjection, id);
|
|
|
|
String sql = qq.query;
|
|
|
|
String attJson = qq.attachmentJson;
|
|
|
|
// With attachments, we have another argument to bind
|
|
|
|
if (attJson != null) {
|
|
|
|
c = db.rawQuery(sql, new String[] {attJson, id});
|
|
|
|
} else {
|
|
|
|
c = db.rawQuery(sql, new String[] {id});
|
|
|
|
}
|
2012-06-28 17:40:46 +00:00
|
|
|
break;
|
|
|
|
case UI_ATTACHMENTS:
|
|
|
|
c = db.rawQuery(genQueryAttachments(uiProjection), new String[] {id});
|
|
|
|
notifyUri = UIPROVIDER_ATTACHMENTS_NOTIFIER.buildUpon().appendPath(id).build();
|
|
|
|
break;
|
|
|
|
case UI_ATTACHMENT:
|
|
|
|
c = db.rawQuery(genQueryAttachment(uiProjection), new String[] {id});
|
|
|
|
notifyUri = UIPROVIDER_ATTACHMENT_NOTIFIER.buildUpon().appendPath(id).build();
|
|
|
|
break;
|
|
|
|
case UI_FOLDER:
|
|
|
|
mailboxId = Long.parseLong(id);
|
|
|
|
if (isVirtualMailbox(mailboxId)) {
|
|
|
|
c = getVirtualMailboxCursor(mailboxId);
|
|
|
|
} else {
|
|
|
|
c = db.rawQuery(genQueryMailbox(uiProjection, id), new String[] {id});
|
|
|
|
notifyUri = UIPROVIDER_FOLDER_NOTIFIER.buildUpon().appendPath(id).build();
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case UI_ACCOUNT:
|
|
|
|
if (id.equals(COMBINED_ACCOUNT_ID_STRING)) {
|
|
|
|
MatrixCursor mc = new MatrixCursor(UIProvider.ACCOUNTS_PROJECTION, 1);
|
|
|
|
addCombinedAccountRow(mc);
|
|
|
|
c = mc;
|
|
|
|
} else {
|
|
|
|
c = db.rawQuery(genQueryAccount(uiProjection, id), new String[] {id});
|
|
|
|
}
|
|
|
|
notifyUri = UIPROVIDER_ACCOUNT_NOTIFIER.buildUpon().appendPath(id).build();
|
|
|
|
break;
|
|
|
|
case UI_CONVERSATION:
|
|
|
|
c = db.rawQuery(genQueryConversation(uiProjection), new String[] {id});
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (notifyUri != null) {
|
|
|
|
c.setNotificationUri(resolver, notifyUri);
|
|
|
|
}
|
|
|
|
return c;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Convert a UIProvider attachment to an EmailProvider attachment (for sending); we only need
|
|
|
|
* a few of the fields
|
|
|
|
* @param uiAtt the UIProvider attachment to convert
|
|
|
|
* @return the EmailProvider attachment
|
|
|
|
*/
|
|
|
|
private Attachment convertUiAttachmentToAttachment(
|
|
|
|
com.android.mail.providers.Attachment uiAtt) {
|
|
|
|
Attachment att = new Attachment();
|
|
|
|
att.mContentUri = uiAtt.contentUri.toString();
|
|
|
|
att.mFileName = uiAtt.name;
|
|
|
|
att.mMimeType = uiAtt.contentType;
|
|
|
|
att.mSize = uiAtt.size;
|
|
|
|
return att;
|
|
|
|
}
|
|
|
|
|
|
|
|
private String getMailboxNameForType(int mailboxType) {
|
|
|
|
Context context = getContext();
|
|
|
|
int resId;
|
|
|
|
switch (mailboxType) {
|
|
|
|
case Mailbox.TYPE_INBOX:
|
|
|
|
resId = R.string.mailbox_name_server_inbox;
|
|
|
|
break;
|
|
|
|
case Mailbox.TYPE_OUTBOX:
|
|
|
|
resId = R.string.mailbox_name_server_outbox;
|
|
|
|
break;
|
|
|
|
case Mailbox.TYPE_DRAFTS:
|
|
|
|
resId = R.string.mailbox_name_server_drafts;
|
|
|
|
break;
|
|
|
|
case Mailbox.TYPE_TRASH:
|
|
|
|
resId = R.string.mailbox_name_server_trash;
|
|
|
|
break;
|
|
|
|
case Mailbox.TYPE_SENT:
|
|
|
|
resId = R.string.mailbox_name_server_sent;
|
|
|
|
break;
|
|
|
|
case Mailbox.TYPE_JUNK:
|
|
|
|
resId = R.string.mailbox_name_server_junk;
|
|
|
|
break;
|
|
|
|
case Mailbox.TYPE_STARRED:
|
|
|
|
resId = R.string.widget_starred;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
throw new IllegalArgumentException("Illegal mailbox type");
|
|
|
|
}
|
|
|
|
return context.getString(resId);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Create a mailbox given the account and mailboxType.
|
|
|
|
*/
|
|
|
|
private Mailbox createMailbox(long accountId, int mailboxType) {
|
|
|
|
Context context = getContext();
|
|
|
|
Mailbox box = Mailbox.newSystemMailbox(accountId, mailboxType,
|
|
|
|
getMailboxNameForType(mailboxType));
|
|
|
|
// Make sure drafts and save will show up in recents...
|
|
|
|
// If these already exist (from old Email app), they will have touch times
|
|
|
|
switch (mailboxType) {
|
|
|
|
case Mailbox.TYPE_DRAFTS:
|
|
|
|
box.mLastTouchedTime = Mailbox.DRAFTS_DEFAULT_TOUCH_TIME;
|
|
|
|
break;
|
|
|
|
case Mailbox.TYPE_SENT:
|
|
|
|
box.mLastTouchedTime = Mailbox.SENT_DEFAULT_TOUCH_TIME;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
box.save(context);
|
|
|
|
return box;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Given an account name and a mailbox type, return that mailbox, creating it if necessary
|
|
|
|
* @param accountName the account name to use
|
|
|
|
* @param mailboxType the type of mailbox we're trying to find
|
|
|
|
* @return the mailbox of the given type for the account in the uri, or null if not found
|
|
|
|
*/
|
|
|
|
private Mailbox getMailboxByAccountIdAndType(String accountId, int mailboxType) {
|
|
|
|
long id = Long.parseLong(accountId);
|
|
|
|
Mailbox mailbox = Mailbox.restoreMailboxOfType(getContext(), id, mailboxType);
|
|
|
|
if (mailbox == null) {
|
|
|
|
mailbox = createMailbox(id, mailboxType);
|
|
|
|
}
|
|
|
|
return mailbox;
|
|
|
|
}
|
|
|
|
|
|
|
|
private Message getMessageFromPathSegments(List<String> pathSegments) {
|
|
|
|
Message msg = null;
|
|
|
|
if (pathSegments.size() > 2) {
|
|
|
|
msg = Message.restoreMessageWithId(getContext(), Long.parseLong(pathSegments.get(2)));
|
|
|
|
}
|
|
|
|
if (msg == null) {
|
|
|
|
msg = new Message();
|
|
|
|
}
|
|
|
|
return msg;
|
|
|
|
}
|
|
|
|
/**
|
|
|
|
* Given a mailbox and the content values for a message, create/save the message in the mailbox
|
|
|
|
* @param mailbox the mailbox to use
|
|
|
|
* @param values the content values that represent message fields
|
|
|
|
* @return the uri of the newly created message
|
|
|
|
*/
|
|
|
|
private Uri uiSaveMessage(Message msg, Mailbox mailbox, ContentValues values) {
|
|
|
|
Context context = getContext();
|
|
|
|
// Fill in the message
|
|
|
|
Account account = Account.restoreAccountWithId(context, mailbox.mAccountKey);
|
|
|
|
if (account == null) return null;
|
|
|
|
msg.mFrom = account.mEmailAddress;
|
|
|
|
msg.mTimeStamp = System.currentTimeMillis();
|
|
|
|
msg.mTo = values.getAsString(UIProvider.MessageColumns.TO);
|
|
|
|
msg.mCc = values.getAsString(UIProvider.MessageColumns.CC);
|
|
|
|
msg.mBcc = values.getAsString(UIProvider.MessageColumns.BCC);
|
|
|
|
msg.mSubject = values.getAsString(UIProvider.MessageColumns.SUBJECT);
|
|
|
|
msg.mText = values.getAsString(UIProvider.MessageColumns.BODY_TEXT);
|
|
|
|
msg.mHtml = values.getAsString(UIProvider.MessageColumns.BODY_HTML);
|
|
|
|
msg.mMailboxKey = mailbox.mId;
|
|
|
|
msg.mAccountKey = mailbox.mAccountKey;
|
|
|
|
msg.mDisplayName = msg.mTo;
|
|
|
|
msg.mFlagLoaded = Message.FLAG_LOADED_COMPLETE;
|
|
|
|
msg.mFlagRead = true;
|
|
|
|
Integer quoteStartPos = values.getAsInteger(UIProvider.MessageColumns.QUOTE_START_POS);
|
|
|
|
msg.mQuotedTextStartPos = quoteStartPos == null ? 0 : quoteStartPos;
|
|
|
|
int flags = 0;
|
|
|
|
int draftType = values.getAsInteger(UIProvider.MessageColumns.DRAFT_TYPE);
|
|
|
|
switch(draftType) {
|
|
|
|
case DraftType.FORWARD:
|
|
|
|
flags |= Message.FLAG_TYPE_FORWARD;
|
|
|
|
break;
|
|
|
|
case DraftType.REPLY_ALL:
|
|
|
|
flags |= Message.FLAG_TYPE_REPLY_ALL;
|
|
|
|
// Fall through
|
|
|
|
case DraftType.REPLY:
|
|
|
|
flags |= Message.FLAG_TYPE_REPLY;
|
|
|
|
break;
|
|
|
|
case DraftType.COMPOSE:
|
|
|
|
flags |= Message.FLAG_TYPE_ORIGINAL;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
msg.mFlags = flags;
|
|
|
|
int draftInfo = 0;
|
|
|
|
if (values.containsKey(UIProvider.MessageColumns.QUOTE_START_POS)) {
|
|
|
|
draftInfo = values.getAsInteger(UIProvider.MessageColumns.QUOTE_START_POS);
|
|
|
|
if (values.getAsInteger(UIProvider.MessageColumns.APPEND_REF_MESSAGE_CONTENT) != 0) {
|
|
|
|
draftInfo |= Message.DRAFT_INFO_APPEND_REF_MESSAGE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
msg.mDraftInfo = draftInfo;
|
|
|
|
String ref = values.getAsString(UIProvider.MessageColumns.REF_MESSAGE_ID);
|
|
|
|
if (ref != null && msg.mQuotedTextStartPos > 0) {
|
|
|
|
String refId = Uri.parse(ref).getLastPathSegment();
|
|
|
|
try {
|
|
|
|
long sourceKey = Long.parseLong(refId);
|
|
|
|
msg.mSourceKey = sourceKey;
|
|
|
|
} catch (NumberFormatException e) {
|
|
|
|
// This will be zero; the default
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get attachments from the ContentValues
|
|
|
|
List<com.android.mail.providers.Attachment> uiAtts =
|
|
|
|
com.android.mail.providers.Attachment.fromJSONArray(
|
|
|
|
values.getAsString(UIProvider.MessageColumns.JOINED_ATTACHMENT_INFOS));
|
|
|
|
ArrayList<Attachment> atts = new ArrayList<Attachment>();
|
|
|
|
boolean hasUnloadedAttachments = false;
|
|
|
|
for (com.android.mail.providers.Attachment uiAtt: uiAtts) {
|
|
|
|
Uri attUri = uiAtt.uri;
|
|
|
|
if (attUri != null && attUri.getAuthority().equals(EmailContent.AUTHORITY)) {
|
|
|
|
// If it's one of ours, retrieve the attachment and add it to the list
|
|
|
|
long attId = Long.parseLong(attUri.getLastPathSegment());
|
|
|
|
Attachment att = Attachment.restoreAttachmentWithId(context, attId);
|
|
|
|
if (att != null) {
|
|
|
|
// We must clone the attachment into a new one for this message; easiest to
|
|
|
|
// use a parcel here
|
|
|
|
Parcel p = Parcel.obtain();
|
|
|
|
att.writeToParcel(p, 0);
|
|
|
|
p.setDataPosition(0);
|
|
|
|
Attachment attClone = new Attachment(p);
|
|
|
|
p.recycle();
|
|
|
|
// Clear the messageKey (this is going to be a new attachment)
|
|
|
|
attClone.mMessageKey = 0;
|
|
|
|
// If we're sending this, it's not loaded, and we're not smart forwarding
|
|
|
|
// add the download flag, so that ADS will start up
|
|
|
|
if (mailbox.mType == Mailbox.TYPE_OUTBOX && att.mContentUri == null &&
|
|
|
|
((account.mFlags & Account.FLAGS_SUPPORTS_SMART_FORWARD) == 0)) {
|
|
|
|
attClone.mFlags |= Attachment.FLAG_DOWNLOAD_FORWARD;
|
|
|
|
hasUnloadedAttachments = true;
|
|
|
|
}
|
|
|
|
atts.add(attClone);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// Convert external attachment to one of ours and add to the list
|
|
|
|
atts.add(convertUiAttachmentToAttachment(uiAtt));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!atts.isEmpty()) {
|
|
|
|
msg.mAttachments = atts;
|
|
|
|
msg.mFlagAttachment = true;
|
|
|
|
if (hasUnloadedAttachments) {
|
|
|
|
Utility.showToast(context, R.string.message_view_attachment_background_load);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Save it or update it...
|
|
|
|
if (!msg.isSaved()) {
|
|
|
|
msg.save(context);
|
|
|
|
} else {
|
|
|
|
// This is tricky due to how messages/attachments are saved; rather than putz with
|
|
|
|
// what's changed, we'll delete/re-add them
|
|
|
|
ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
|
|
|
|
// Delete all existing attachments
|
|
|
|
ops.add(ContentProviderOperation.newDelete(
|
|
|
|
ContentUris.withAppendedId(Attachment.MESSAGE_ID_URI, msg.mId))
|
|
|
|
.build());
|
|
|
|
// Delete the body
|
|
|
|
ops.add(ContentProviderOperation.newDelete(Body.CONTENT_URI)
|
|
|
|
.withSelection(Body.MESSAGE_KEY + "=?", new String[] {Long.toString(msg.mId)})
|
|
|
|
.build());
|
|
|
|
// Add the ops for the message, atts, and body
|
|
|
|
msg.addSaveOps(ops);
|
|
|
|
// Do it!
|
|
|
|
try {
|
|
|
|
applyBatch(ops);
|
|
|
|
} catch (OperationApplicationException e) {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (mailbox.mType == Mailbox.TYPE_OUTBOX) {
|
|
|
|
EmailServiceProxy service = EmailServiceUtils.getServiceForAccount(context,
|
|
|
|
mServiceCallback, mailbox.mAccountKey);
|
|
|
|
try {
|
|
|
|
service.startSync(mailbox.mId, true);
|
|
|
|
} catch (RemoteException e) {
|
|
|
|
}
|
|
|
|
long originalMsgId = msg.mSourceKey;
|
|
|
|
if (originalMsgId != 0) {
|
|
|
|
Message originalMsg = Message.restoreMessageWithId(context, originalMsgId);
|
|
|
|
// If the original message exists, set its forwarded/replied to flags
|
|
|
|
if (originalMsg != null) {
|
|
|
|
ContentValues cv = new ContentValues();
|
|
|
|
flags = originalMsg.mFlags;
|
|
|
|
switch(draftType) {
|
|
|
|
case DraftType.FORWARD:
|
|
|
|
flags |= Message.FLAG_FORWARDED;
|
|
|
|
break;
|
|
|
|
case DraftType.REPLY_ALL:
|
|
|
|
case DraftType.REPLY:
|
|
|
|
flags |= Message.FLAG_REPLIED_TO;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
cv.put(Message.FLAGS, flags);
|
|
|
|
context.getContentResolver().update(ContentUris.withAppendedId(
|
|
|
|
Message.CONTENT_URI, originalMsgId), cv, null, null);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return uiUri("uimessage", msg.mId);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Create and send the message via the account indicated in the uri
|
|
|
|
* @param uri the incoming uri
|
|
|
|
* @param values the content values that represent message fields
|
|
|
|
* @return the uri of the created message
|
|
|
|
*/
|
|
|
|
private Uri uiSendMail(Uri uri, ContentValues values) {
|
|
|
|
List<String> pathSegments = uri.getPathSegments();
|
|
|
|
Mailbox mailbox = getMailboxByAccountIdAndType(pathSegments.get(1), Mailbox.TYPE_OUTBOX);
|
|
|
|
if (mailbox == null) return null;
|
|
|
|
Message msg = getMessageFromPathSegments(pathSegments);
|
|
|
|
try {
|
|
|
|
return uiSaveMessage(msg, mailbox, values);
|
|
|
|
} finally {
|
|
|
|
// Kick observers
|
|
|
|
getContext().getContentResolver().notifyChange(Mailbox.CONTENT_URI, null);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Create a message and save it to the drafts folder of the account indicated in the uri
|
|
|
|
* @param uri the incoming uri
|
|
|
|
* @param values the content values that represent message fields
|
|
|
|
* @return the uri of the created message
|
|
|
|
*/
|
|
|
|
private Uri uiSaveDraft(Uri uri, ContentValues values) {
|
|
|
|
List<String> pathSegments = uri.getPathSegments();
|
|
|
|
Mailbox mailbox = getMailboxByAccountIdAndType(pathSegments.get(1), Mailbox.TYPE_DRAFTS);
|
|
|
|
if (mailbox == null) return null;
|
|
|
|
Message msg = getMessageFromPathSegments(pathSegments);
|
|
|
|
return uiSaveMessage(msg, mailbox, values);
|
|
|
|
}
|
|
|
|
|
|
|
|
private int uiUpdateDraft(Uri uri, ContentValues values) {
|
|
|
|
Context context = getContext();
|
|
|
|
Message msg = Message.restoreMessageWithId(context,
|
|
|
|
Long.parseLong(uri.getPathSegments().get(1)));
|
|
|
|
if (msg == null) return 0;
|
|
|
|
Mailbox mailbox = Mailbox.restoreMailboxWithId(context, msg.mMailboxKey);
|
|
|
|
if (mailbox == null) return 0;
|
|
|
|
uiSaveMessage(msg, mailbox, values);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
private int uiSendDraft(Uri uri, ContentValues values) {
|
|
|
|
Context context = getContext();
|
|
|
|
Message msg = Message.restoreMessageWithId(context,
|
|
|
|
Long.parseLong(uri.getPathSegments().get(1)));
|
|
|
|
if (msg == null) return 0;
|
|
|
|
long mailboxId = Mailbox.findMailboxOfType(context, msg.mAccountKey, Mailbox.TYPE_OUTBOX);
|
|
|
|
if (mailboxId == Mailbox.NO_MAILBOX) return 0;
|
|
|
|
Mailbox mailbox = Mailbox.restoreMailboxWithId(context, mailboxId);
|
|
|
|
if (mailbox == null) return 0;
|
|
|
|
uiSaveMessage(msg, mailbox, values);
|
|
|
|
// Kick observers
|
|
|
|
context.getContentResolver().notifyChange(Mailbox.CONTENT_URI, null);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
private void putIntegerLongOrBoolean(ContentValues values, String columnName, Object value) {
|
|
|
|
if (value instanceof Integer) {
|
|
|
|
Integer intValue = (Integer)value;
|
|
|
|
values.put(columnName, intValue);
|
|
|
|
} else if (value instanceof Boolean) {
|
|
|
|
Boolean boolValue = (Boolean)value;
|
|
|
|
values.put(columnName, boolValue ? 1 : 0);
|
|
|
|
} else if (value instanceof Long) {
|
|
|
|
Long longValue = (Long)value;
|
|
|
|
values.put(columnName, longValue);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Update the timestamps for the folders specified and notifies on the recent folder URI.
|
|
|
|
* @param folders
|
|
|
|
* @return number of folders updated
|
|
|
|
*/
|
|
|
|
private int updateTimestamp(final Context context, String id, Uri[] folders){
|
|
|
|
int updated = 0;
|
|
|
|
final long now = System.currentTimeMillis();
|
|
|
|
final ContentResolver resolver = context.getContentResolver();
|
|
|
|
final ContentValues touchValues = new ContentValues();
|
|
|
|
for (int i=0, size=folders.length; i < size; ++i) {
|
|
|
|
touchValues.put(MailboxColumns.LAST_TOUCHED_TIME, now);
|
|
|
|
LogUtils.d(TAG, "updateStamp: %s updated", folders[i]);
|
|
|
|
updated += resolver.update(folders[i], touchValues, null, null);
|
|
|
|
}
|
|
|
|
final Uri toNotify =
|
|
|
|
UIPROVIDER_RECENT_FOLDERS_NOTIFIER.buildUpon().appendPath(id).build();
|
|
|
|
LogUtils.d(TAG, "updateTimestamp: Notifying on %s", toNotify);
|
|
|
|
resolver.notifyChange(toNotify, null);
|
|
|
|
return updated;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Updates the recent folders. The values to be updated are specified as ContentValues pairs
|
|
|
|
* of (Folder URI, access timestamp). Returns nonzero if successful, always.
|
|
|
|
* @param uri
|
|
|
|
* @param values
|
|
|
|
* @return nonzero value always.
|
|
|
|
*/
|
|
|
|
private int uiUpdateRecentFolders(Uri uri, ContentValues values) {
|
|
|
|
final int numFolders = values.size();
|
|
|
|
final String id = uri.getPathSegments().get(1);
|
|
|
|
final Uri[] folders = new Uri[numFolders];
|
|
|
|
final Context context = getContext();
|
|
|
|
final NotificationController controller = NotificationController.getInstance(context);
|
|
|
|
int i = 0;
|
|
|
|
for (final String uriString: values.keySet()) {
|
|
|
|
folders[i] = Uri.parse(uriString);
|
|
|
|
try {
|
|
|
|
final String mailboxIdString = folders[i].getLastPathSegment();
|
|
|
|
final long mailboxId = Long.parseLong(mailboxIdString);
|
|
|
|
controller.cancelNewMessageNotification(mailboxId);
|
|
|
|
} catch (NumberFormatException e) {
|
|
|
|
// Keep on going...
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return updateTimestamp(context, id, folders);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Populates the recent folders according to the design.
|
|
|
|
* @param uri
|
|
|
|
* @return the number of recent folders were populated.
|
|
|
|
*/
|
|
|
|
private int uiPopulateRecentFolders(Uri uri) {
|
|
|
|
final Context context = getContext();
|
|
|
|
final String id = uri.getLastPathSegment();
|
|
|
|
final Uri[] recentFolders = defaultRecentFolders(id);
|
|
|
|
final int numFolders = recentFolders.length;
|
|
|
|
if (numFolders <= 0) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
final int rowsUpdated = updateTimestamp(context, id, recentFolders);
|
|
|
|
LogUtils.d(TAG, "uiPopulateRecentFolders: %d folders changed", rowsUpdated);
|
|
|
|
return rowsUpdated;
|
|
|
|
}
|
|
|
|
|
|
|
|
private int uiUpdateAttachment(Uri uri, ContentValues uiValues) {
|
|
|
|
Integer stateValue = uiValues.getAsInteger(UIProvider.AttachmentColumns.STATE);
|
|
|
|
if (stateValue != null) {
|
|
|
|
// This is a command from UIProvider
|
|
|
|
long attachmentId = Long.parseLong(uri.getLastPathSegment());
|
|
|
|
Context context = getContext();
|
|
|
|
Attachment attachment =
|
|
|
|
Attachment.restoreAttachmentWithId(context, attachmentId);
|
|
|
|
if (attachment == null) {
|
|
|
|
// Went away; ah, well...
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
ContentValues values = new ContentValues();
|
|
|
|
switch (stateValue.intValue()) {
|
|
|
|
case UIProvider.AttachmentState.NOT_SAVED:
|
|
|
|
// Set state, try to cancel request
|
|
|
|
values.put(AttachmentColumns.UI_STATE, stateValue);
|
|
|
|
values.put(AttachmentColumns.FLAGS,
|
|
|
|
attachment.mFlags &= ~Attachment.FLAG_DOWNLOAD_USER_REQUEST);
|
|
|
|
attachment.update(context, values);
|
|
|
|
return 1;
|
|
|
|
case UIProvider.AttachmentState.DOWNLOADING:
|
|
|
|
// Set state and destination; request download
|
|
|
|
values.put(AttachmentColumns.UI_STATE, stateValue);
|
|
|
|
Integer destinationValue =
|
|
|
|
uiValues.getAsInteger(UIProvider.AttachmentColumns.DESTINATION);
|
|
|
|
values.put(AttachmentColumns.UI_DESTINATION,
|
|
|
|
destinationValue == null ? 0 : destinationValue);
|
|
|
|
values.put(AttachmentColumns.FLAGS,
|
|
|
|
attachment.mFlags | Attachment.FLAG_DOWNLOAD_USER_REQUEST);
|
|
|
|
attachment.update(context, values);
|
|
|
|
return 1;
|
|
|
|
case UIProvider.AttachmentState.SAVED:
|
|
|
|
// If this is an inline attachment, notify message has changed
|
|
|
|
if (!TextUtils.isEmpty(attachment.mContentId)) {
|
|
|
|
notifyUI(UIPROVIDER_MESSAGE_NOTIFIER, attachment.mMessageKey);
|
|
|
|
}
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
private int uiUpdateFolder(Uri uri, ContentValues uiValues) {
|
|
|
|
Uri ourUri = convertToEmailProviderUri(uri, Mailbox.CONTENT_URI, true);
|
|
|
|
if (ourUri == null) return 0;
|
|
|
|
ContentValues ourValues = new ContentValues();
|
|
|
|
// This should only be called via update to "recent folders"
|
|
|
|
for (String columnName: uiValues.keySet()) {
|
|
|
|
if (columnName.equals(MailboxColumns.LAST_TOUCHED_TIME)) {
|
|
|
|
ourValues.put(MailboxColumns.LAST_TOUCHED_TIME, uiValues.getAsLong(columnName));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return update(ourUri, ourValues, null, null);
|
|
|
|
}
|
|
|
|
|
|
|
|
private ContentValues convertUiMessageValues(Message message, ContentValues values) {
|
|
|
|
ContentValues ourValues = new ContentValues();
|
|
|
|
for (String columnName: values.keySet()) {
|
|
|
|
Object val = values.get(columnName);
|
|
|
|
if (columnName.equals(UIProvider.ConversationColumns.STARRED)) {
|
|
|
|
putIntegerLongOrBoolean(ourValues, MessageColumns.FLAG_FAVORITE, val);
|
|
|
|
} else if (columnName.equals(UIProvider.ConversationColumns.READ)) {
|
|
|
|
putIntegerLongOrBoolean(ourValues, MessageColumns.FLAG_READ, val);
|
|
|
|
} else if (columnName.equals(MessageColumns.MAILBOX_KEY)) {
|
|
|
|
putIntegerLongOrBoolean(ourValues, MessageColumns.MAILBOX_KEY, val);
|
|
|
|
} else if (columnName.equals(UIProvider.ConversationColumns.FOLDER_LIST)) {
|
|
|
|
// Convert from folder list uri to mailbox key
|
|
|
|
Uri uri = Uri.parse((String)val);
|
|
|
|
Long mailboxId = Long.parseLong(uri.getLastPathSegment());
|
|
|
|
putIntegerLongOrBoolean(ourValues, MessageColumns.MAILBOX_KEY, mailboxId);
|
|
|
|
} else if (columnName.equals(UIProvider.ConversationColumns.RAW_FOLDERS)) {
|
|
|
|
// Ignore; this is updated by the FOLDER_LIST update above.
|
|
|
|
} else if (columnName.equals(UIProvider.MessageColumns.ALWAYS_SHOW_IMAGES)) {
|
|
|
|
Address[] fromList = Address.unpack(message.mFrom);
|
|
|
|
Preferences prefs = Preferences.getPreferences(getContext());
|
|
|
|
for (Address sender : fromList) {
|
|
|
|
String email = sender.getAddress();
|
|
|
|
prefs.setSenderAsTrusted(email);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
throw new IllegalArgumentException("Can't update " + columnName + " in message");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return ourValues;
|
|
|
|
}
|
|
|
|
|
|
|
|
private Uri convertToEmailProviderUri(Uri uri, Uri newBaseUri, boolean asProvider) {
|
|
|
|
String idString = uri.getLastPathSegment();
|
|
|
|
try {
|
|
|
|
long id = Long.parseLong(idString);
|
|
|
|
Uri ourUri = ContentUris.withAppendedId(newBaseUri, id);
|
|
|
|
if (asProvider) {
|
|
|
|
ourUri = ourUri.buildUpon().appendQueryParameter(IS_UIPROVIDER, "true").build();
|
|
|
|
}
|
|
|
|
return ourUri;
|
|
|
|
} catch (NumberFormatException e) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private Message getMessageFromLastSegment(Uri uri) {
|
|
|
|
long messageId = Long.parseLong(uri.getLastPathSegment());
|
|
|
|
return Message.restoreMessageWithId(getContext(), messageId);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Add an undo operation for the current sequence; if the sequence is newer than what we've had,
|
|
|
|
* clear out the undo list and start over
|
|
|
|
* @param uri the uri we're working on
|
|
|
|
* @param op the ContentProviderOperation to perform upon undo
|
|
|
|
*/
|
|
|
|
private void addToSequence(Uri uri, ContentProviderOperation op) {
|
|
|
|
String sequenceString = uri.getQueryParameter(UIProvider.SEQUENCE_QUERY_PARAMETER);
|
|
|
|
if (sequenceString != null) {
|
|
|
|
int sequence = Integer.parseInt(sequenceString);
|
|
|
|
if (sequence > mLastSequence) {
|
|
|
|
// Reset sequence
|
|
|
|
mLastSequenceOps.clear();
|
|
|
|
mLastSequence = sequence;
|
|
|
|
}
|
|
|
|
// TODO: Need something to indicate a change isn't ready (undoable)
|
|
|
|
mLastSequenceOps.add(op);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: This should depend on flags on the mailbox...
|
|
|
|
private boolean uploadsToServer(Context context, Mailbox m) {
|
|
|
|
if (m.mType == Mailbox.TYPE_DRAFTS || m.mType == Mailbox.TYPE_OUTBOX ||
|
|
|
|
m.mType == Mailbox.TYPE_SEARCH) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
String protocol = Account.getProtocol(context, m.mAccountKey);
|
|
|
|
EmailServiceInfo info = EmailServiceUtils.getServiceInfo(context, protocol);
|
|
|
|
return (info != null && info.syncChanges);
|
|
|
|
}
|
|
|
|
|
|
|
|
private int uiUpdateMessage(Uri uri, ContentValues values) {
|
|
|
|
Context context = getContext();
|
|
|
|
Message msg = getMessageFromLastSegment(uri);
|
|
|
|
if (msg == null) return 0;
|
|
|
|
Mailbox mailbox = Mailbox.restoreMailboxWithId(context, msg.mMailboxKey);
|
|
|
|
if (mailbox == null) return 0;
|
|
|
|
Uri ourBaseUri = uploadsToServer(context, mailbox) ? Message.SYNCED_CONTENT_URI :
|
|
|
|
Message.CONTENT_URI;
|
|
|
|
Uri ourUri = convertToEmailProviderUri(uri, ourBaseUri, true);
|
|
|
|
if (ourUri == null) return 0;
|
|
|
|
|
|
|
|
// Special case - meeting response
|
|
|
|
if (values.containsKey(UIProvider.MessageOperations.RESPOND_COLUMN)) {
|
|
|
|
EmailServiceProxy service = EmailServiceUtils.getServiceForAccount(context,
|
|
|
|
mServiceCallback, mailbox.mAccountKey);
|
|
|
|
try {
|
|
|
|
service.sendMeetingResponse(msg.mId,
|
|
|
|
values.getAsInteger(UIProvider.MessageOperations.RESPOND_COLUMN));
|
|
|
|
// Delete the message immediately
|
|
|
|
uiDeleteMessage(uri);
|
|
|
|
Utility.showToast(context, R.string.confirm_response);
|
|
|
|
// Notify box has changed so the deletion is reflected in the UI
|
|
|
|
notifyUIConversationMailbox(mailbox.mId);
|
|
|
|
} catch (RemoteException e) {
|
|
|
|
}
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
ContentValues undoValues = new ContentValues();
|
|
|
|
ContentValues ourValues = convertUiMessageValues(msg, values);
|
|
|
|
for (String columnName: ourValues.keySet()) {
|
|
|
|
if (columnName.equals(MessageColumns.MAILBOX_KEY)) {
|
|
|
|
undoValues.put(MessageColumns.MAILBOX_KEY, msg.mMailboxKey);
|
|
|
|
} else if (columnName.equals(MessageColumns.FLAG_READ)) {
|
|
|
|
undoValues.put(MessageColumns.FLAG_READ, msg.mFlagRead);
|
|
|
|
} else if (columnName.equals(MessageColumns.FLAG_FAVORITE)) {
|
|
|
|
undoValues.put(MessageColumns.FLAG_FAVORITE, msg.mFlagFavorite);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (undoValues == null || undoValues.size() == 0) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
ContentProviderOperation op =
|
|
|
|
ContentProviderOperation.newUpdate(convertToEmailProviderUri(
|
|
|
|
uri, ourBaseUri, false))
|
|
|
|
.withValues(undoValues)
|
|
|
|
.build();
|
|
|
|
addToSequence(uri, op);
|
|
|
|
return update(ourUri, ourValues, null, null);
|
|
|
|
}
|
|
|
|
|
2012-06-29 16:42:05 +00:00
|
|
|
public static final String PICKER_UI_ACCOUNT = "picker_ui_account";
|
|
|
|
public static final String PICKER_MAILBOX_TYPE = "picker_mailbox_type";
|
|
|
|
public static final String PICKER_MESSAGE_ID = "picker_message_id";
|
|
|
|
|
2012-06-28 17:40:46 +00:00
|
|
|
private int uiDeleteMessage(Uri uri) {
|
2012-06-29 16:42:05 +00:00
|
|
|
final Context context = getContext();
|
2012-06-28 17:40:46 +00:00
|
|
|
Message msg = getMessageFromLastSegment(uri);
|
|
|
|
if (msg == null) return 0;
|
|
|
|
Mailbox mailbox = Mailbox.restoreMailboxWithId(context, msg.mMailboxKey);
|
|
|
|
if (mailbox == null) return 0;
|
|
|
|
if (mailbox.mType == Mailbox.TYPE_TRASH || mailbox.mType == Mailbox.TYPE_DRAFTS) {
|
|
|
|
// We actually delete these, including attachments
|
|
|
|
AttachmentUtilities.deleteAllAttachmentFiles(context, msg.mAccountKey, msg.mId);
|
|
|
|
notifyUI(UIPROVIDER_FOLDER_NOTIFIER, mailbox.mId);
|
|
|
|
return context.getContentResolver().delete(
|
2012-06-29 16:42:05 +00:00
|
|
|
ContentUris.withAppendedId(Message.SYNCED_CONTENT_URI, msg.mId), null, null);
|
2012-06-28 17:40:46 +00:00
|
|
|
}
|
|
|
|
Mailbox trashMailbox =
|
|
|
|
Mailbox.restoreMailboxOfType(context, msg.mAccountKey, Mailbox.TYPE_TRASH);
|
2012-06-29 16:42:05 +00:00
|
|
|
if (trashMailbox == null) {
|
|
|
|
return 0;
|
|
|
|
}
|
2012-06-28 17:40:46 +00:00
|
|
|
ContentValues values = new ContentValues();
|
|
|
|
values.put(MessageColumns.MAILBOX_KEY, trashMailbox.mId);
|
|
|
|
notifyUI(UIPROVIDER_FOLDER_NOTIFIER, mailbox.mId);
|
|
|
|
return uiUpdateMessage(uri, values);
|
|
|
|
}
|
|
|
|
|
2012-06-29 16:42:05 +00:00
|
|
|
private int pickTrashFolder(Uri uri) {
|
|
|
|
Context context = getContext();
|
|
|
|
Long acctId = Long.parseLong(uri.getLastPathSegment());
|
|
|
|
// For push imap, for example, we want the user to select the trash mailbox
|
|
|
|
Cursor ac = query(uiUri("uiaccount", acctId), UIProvider.ACCOUNTS_PROJECTION,
|
|
|
|
null, null, null);
|
|
|
|
try {
|
|
|
|
if (ac.moveToFirst()) {
|
|
|
|
final com.android.mail.providers.Account uiAccount =
|
|
|
|
new com.android.mail.providers.Account(ac);
|
|
|
|
Intent intent = new Intent(context, FolderPickerActivity.class);
|
|
|
|
intent.putExtra(PICKER_UI_ACCOUNT, uiAccount);
|
|
|
|
intent.putExtra(PICKER_MAILBOX_TYPE, Mailbox.TYPE_TRASH);
|
|
|
|
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
|
|
|
context.startActivity(intent);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
} finally {
|
|
|
|
ac.close();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-06-28 17:40:46 +00:00
|
|
|
private Cursor uiUndo(String[] projection) {
|
|
|
|
// First see if we have any operations saved
|
|
|
|
// TODO: Make sure seq matches
|
|
|
|
if (!mLastSequenceOps.isEmpty()) {
|
|
|
|
try {
|
|
|
|
// TODO Always use this projection? Or what's passed in?
|
|
|
|
// Not sure if UI wants it, but I'm making a cursor of convo uri's
|
|
|
|
MatrixCursor c = new MatrixCursor(
|
|
|
|
new String[] {UIProvider.ConversationColumns.URI},
|
|
|
|
mLastSequenceOps.size());
|
|
|
|
for (ContentProviderOperation op: mLastSequenceOps) {
|
|
|
|
c.addRow(new String[] {op.getUri().toString()});
|
|
|
|
}
|
|
|
|
// Just apply the batch and we're done!
|
|
|
|
applyBatch(mLastSequenceOps);
|
|
|
|
// But clear the operations
|
|
|
|
mLastSequenceOps.clear();
|
|
|
|
// Tell the UI there are changes
|
|
|
|
ContentResolver resolver = getContext().getContentResolver();
|
|
|
|
resolver.notifyChange(UIPROVIDER_CONVERSATION_NOTIFIER, null);
|
|
|
|
resolver.notifyChange(UIPROVIDER_FOLDER_NOTIFIER, null);
|
|
|
|
return c;
|
|
|
|
} catch (OperationApplicationException e) {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return new MatrixCursor(projection, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
private void notifyUIConversation(Uri uri) {
|
|
|
|
String id = uri.getLastPathSegment();
|
|
|
|
Message msg = Message.restoreMessageWithId(getContext(), Long.parseLong(id));
|
|
|
|
if (msg != null) {
|
|
|
|
notifyUIConversationMailbox(msg.mMailboxKey);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Notify about the Mailbox id passed in
|
|
|
|
* @param id the Mailbox id to be notified
|
|
|
|
*/
|
|
|
|
private void notifyUIConversationMailbox(long id) {
|
|
|
|
notifyUI(UIPROVIDER_CONVERSATION_NOTIFIER, Long.toString(id));
|
|
|
|
Mailbox mailbox = Mailbox.restoreMailboxWithId(getContext(), id);
|
|
|
|
// Notify combined inbox...
|
|
|
|
if (mailbox.mType == Mailbox.TYPE_INBOX) {
|
|
|
|
notifyUI(UIPROVIDER_CONVERSATION_NOTIFIER,
|
|
|
|
EmailProvider.combinedMailboxId(Mailbox.TYPE_INBOX));
|
|
|
|
}
|
|
|
|
notifyWidgets(id);
|
|
|
|
}
|
|
|
|
|
|
|
|
private void notifyUI(Uri uri, String id) {
|
|
|
|
Uri notifyUri = uri.buildUpon().appendPath(id).build();
|
|
|
|
getContext().getContentResolver().notifyChange(notifyUri, null);
|
|
|
|
}
|
|
|
|
|
|
|
|
private void notifyUI(Uri uri, long id) {
|
|
|
|
notifyUI(uri, Long.toString(id));
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Support for services and service notifications
|
|
|
|
*/
|
|
|
|
|
|
|
|
private final IEmailServiceCallback.Stub mServiceCallback =
|
|
|
|
new IEmailServiceCallback.Stub() {
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void syncMailboxListStatus(long accountId, int statusCode, int progress)
|
|
|
|
throws RemoteException {
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void syncMailboxStatus(long mailboxId, int statusCode, int progress)
|
|
|
|
throws RemoteException {
|
|
|
|
// We'll get callbacks here from the services, which we'll pass back to the UI
|
|
|
|
Uri uri = ContentUris.withAppendedId(FOLDER_STATUS_URI, mailboxId);
|
|
|
|
EmailProvider.this.getContext().getContentResolver().notifyChange(uri, null);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void loadAttachmentStatus(long messageId, long attachmentId, int statusCode,
|
|
|
|
int progress) throws RemoteException {
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void sendMessageStatus(long messageId, String subject, int statusCode, int progress)
|
|
|
|
throws RemoteException {
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void loadMessageStatus(long messageId, int statusCode, int progress)
|
|
|
|
throws RemoteException {
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
private Cursor uiFolderRefresh(Uri uri) {
|
|
|
|
Context context = getContext();
|
|
|
|
String idString = uri.getLastPathSegment();
|
|
|
|
long id = Long.parseLong(idString);
|
|
|
|
Mailbox mailbox = Mailbox.restoreMailboxWithId(context, id);
|
|
|
|
if (mailbox == null) return null;
|
|
|
|
EmailServiceProxy service = EmailServiceUtils.getServiceForAccount(context,
|
|
|
|
mServiceCallback, mailbox.mAccountKey);
|
|
|
|
try {
|
|
|
|
service.startSync(id, true);
|
|
|
|
} catch (RemoteException e) {
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
//Number of additional messages to load when a user selects "Load more..." in POP/IMAP boxes
|
|
|
|
public static final int VISIBLE_LIMIT_INCREMENT = 10;
|
|
|
|
//Number of additional messages to load when a user selects "Load more..." in a search
|
|
|
|
public static final int SEARCH_MORE_INCREMENT = 10;
|
|
|
|
|
|
|
|
private Cursor uiFolderLoadMore(Uri uri) {
|
|
|
|
Context context = getContext();
|
|
|
|
String idString = uri.getLastPathSegment();
|
|
|
|
long id = Long.parseLong(idString);
|
|
|
|
Mailbox mailbox = Mailbox.restoreMailboxWithId(context, id);
|
|
|
|
if (mailbox == null) return null;
|
|
|
|
if (mailbox.mType == Mailbox.TYPE_SEARCH) {
|
|
|
|
// Ask for 10 more messages
|
|
|
|
mSearchParams.mOffset += SEARCH_MORE_INCREMENT;
|
|
|
|
runSearchQuery(context, mailbox.mAccountKey, id);
|
|
|
|
} else {
|
|
|
|
ContentValues values = new ContentValues();
|
|
|
|
values.put(EmailContent.FIELD_COLUMN_NAME, MailboxColumns.VISIBLE_LIMIT);
|
|
|
|
values.put(EmailContent.ADD_COLUMN_NAME, VISIBLE_LIMIT_INCREMENT);
|
|
|
|
Uri mailboxUri = ContentUris.withAppendedId(Mailbox.ADD_TO_FIELD_URI, id);
|
|
|
|
// Increase the limit
|
|
|
|
context.getContentResolver().update(mailboxUri, values, null, null);
|
|
|
|
// And order a refresh
|
|
|
|
uiFolderRefresh(uri);
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
private static final String SEARCH_MAILBOX_SERVER_ID = "__search_mailbox__";
|
|
|
|
private SearchParams mSearchParams;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the search mailbox for the specified account, creating one if necessary
|
|
|
|
* @return the search mailbox for the passed in account
|
|
|
|
*/
|
|
|
|
private Mailbox getSearchMailbox(long accountId) {
|
|
|
|
Context context = getContext();
|
|
|
|
Mailbox m = Mailbox.restoreMailboxOfType(context, accountId, Mailbox.TYPE_SEARCH);
|
|
|
|
if (m == null) {
|
|
|
|
m = new Mailbox();
|
|
|
|
m.mAccountKey = accountId;
|
|
|
|
m.mServerId = SEARCH_MAILBOX_SERVER_ID;
|
|
|
|
m.mFlagVisible = false;
|
|
|
|
m.mDisplayName = SEARCH_MAILBOX_SERVER_ID;
|
|
|
|
m.mSyncInterval = Mailbox.CHECK_INTERVAL_NEVER;
|
|
|
|
m.mType = Mailbox.TYPE_SEARCH;
|
|
|
|
m.mFlags = Mailbox.FLAG_HOLDS_MAIL;
|
|
|
|
m.mParentKey = Mailbox.NO_MAILBOX;
|
|
|
|
m.save(context);
|
|
|
|
}
|
|
|
|
return m;
|
|
|
|
}
|
|
|
|
|
|
|
|
private void runSearchQuery(final Context context, final long accountId,
|
|
|
|
final long searchMailboxId) {
|
|
|
|
// Start the search running in the background
|
|
|
|
new Thread(new Runnable() {
|
|
|
|
@Override
|
|
|
|
public void run() {
|
|
|
|
try {
|
|
|
|
EmailServiceProxy service = EmailServiceUtils.getServiceForAccount(context,
|
|
|
|
mServiceCallback, accountId);
|
|
|
|
if (service != null) {
|
|
|
|
try {
|
|
|
|
// Save away the total count
|
|
|
|
mSearchParams.mTotalCount = service.searchMessages(accountId,
|
|
|
|
mSearchParams, searchMailboxId);
|
|
|
|
//Log.d(TAG, "TotalCount to UI: " + mSearchParams.mTotalCount);
|
|
|
|
notifyUI(UIPROVIDER_FOLDER_NOTIFIER, searchMailboxId);
|
|
|
|
} catch (RemoteException e) {
|
|
|
|
Log.e("searchMessages", "RemoteException", e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} finally {
|
|
|
|
}
|
|
|
|
}}).start();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: Handle searching for more...
|
|
|
|
private Cursor uiSearch(Uri uri, String[] projection) {
|
|
|
|
final long accountId = Long.parseLong(uri.getLastPathSegment());
|
|
|
|
|
|
|
|
// TODO: Check the actual mailbox
|
|
|
|
Mailbox inbox = Mailbox.restoreMailboxOfType(getContext(), accountId, Mailbox.TYPE_INBOX);
|
|
|
|
if (inbox == null) return null;
|
|
|
|
|
|
|
|
String filter = uri.getQueryParameter(UIProvider.SearchQueryParameters.QUERY);
|
|
|
|
if (filter == null) {
|
|
|
|
throw new IllegalArgumentException("No query parameter in search query");
|
|
|
|
}
|
|
|
|
|
|
|
|
// Find/create our search mailbox
|
|
|
|
Mailbox searchMailbox = getSearchMailbox(accountId);
|
|
|
|
final long searchMailboxId = searchMailbox.mId;
|
|
|
|
|
|
|
|
mSearchParams = new SearchParams(inbox.mId, filter, searchMailboxId);
|
|
|
|
|
|
|
|
final Context context = getContext();
|
|
|
|
if (mSearchParams.mOffset == 0) {
|
|
|
|
// Delete existing contents of search mailbox
|
|
|
|
ContentResolver resolver = context.getContentResolver();
|
|
|
|
resolver.delete(Message.CONTENT_URI, Message.MAILBOX_KEY + "=" + searchMailboxId,
|
|
|
|
null);
|
|
|
|
ContentValues cv = new ContentValues();
|
|
|
|
// For now, use the actual query as the name of the mailbox
|
|
|
|
cv.put(Mailbox.DISPLAY_NAME, mSearchParams.mFilter);
|
|
|
|
resolver.update(ContentUris.withAppendedId(Mailbox.CONTENT_URI, searchMailboxId),
|
|
|
|
cv, null, null);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Start the search running in the background
|
|
|
|
runSearchQuery(context, accountId, searchMailboxId);
|
|
|
|
|
|
|
|
// This will look just like a "normal" folder
|
|
|
|
return uiQuery(UI_FOLDER, ContentUris.withAppendedId(Mailbox.CONTENT_URI,
|
|
|
|
searchMailbox.mId), projection);
|
|
|
|
}
|
|
|
|
|
|
|
|
private static final String MAILBOXES_FOR_ACCOUNT_SELECTION = MailboxColumns.ACCOUNT_KEY + "=?";
|
|
|
|
private static final String MAILBOXES_FOR_ACCOUNT_EXCEPT_ACCOUNT_MAILBOX_SELECTION =
|
|
|
|
MAILBOXES_FOR_ACCOUNT_SELECTION + " AND " + MailboxColumns.TYPE + "!=" +
|
|
|
|
Mailbox.TYPE_EAS_ACCOUNT_MAILBOX;
|
|
|
|
private static final String MESSAGES_FOR_ACCOUNT_SELECTION = MessageColumns.ACCOUNT_KEY + "=?";
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Delete an account and clean it up
|
|
|
|
*/
|
|
|
|
private int uiDeleteAccount(Uri uri) {
|
|
|
|
Context context = getContext();
|
|
|
|
long accountId = Long.parseLong(uri.getLastPathSegment());
|
|
|
|
try {
|
|
|
|
// Get the account URI.
|
|
|
|
final Account account = Account.restoreAccountWithId(context, accountId);
|
|
|
|
if (account == null) {
|
|
|
|
return 0; // Already deleted?
|
|
|
|
}
|
|
|
|
|
|
|
|
deleteAccountData(context, accountId);
|
|
|
|
|
|
|
|
// Now delete the account itself
|
|
|
|
uri = ContentUris.withAppendedId(Account.CONTENT_URI, accountId);
|
|
|
|
context.getContentResolver().delete(uri, null, null);
|
|
|
|
|
|
|
|
// Clean up
|
|
|
|
AccountBackupRestore.backup(context);
|
|
|
|
SecurityPolicy.getInstance(context).reducePolicies();
|
|
|
|
MailActivityEmail.setServicesEnabledSync(context);
|
|
|
|
return 1;
|
|
|
|
} catch (Exception e) {
|
|
|
|
Log.w(Logging.LOG_TAG, "Exception while deleting account", e);
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
private int uiDeleteAccountData(Uri uri) {
|
|
|
|
Context context = getContext();
|
|
|
|
long accountId = Long.parseLong(uri.getLastPathSegment());
|
|
|
|
// Get the account URI.
|
|
|
|
final Account account = Account.restoreAccountWithId(context, accountId);
|
|
|
|
if (account == null) {
|
|
|
|
return 0; // Already deleted?
|
|
|
|
}
|
|
|
|
deleteAccountData(context, accountId);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
private void deleteAccountData(Context context, long accountId) {
|
|
|
|
// Delete synced attachments
|
|
|
|
AttachmentUtilities.deleteAllAccountAttachmentFiles(context, accountId);
|
|
|
|
|
|
|
|
// Delete synced email, leaving only an empty inbox. We do this in two phases:
|
|
|
|
// 1. Delete all non-inbox mailboxes (which will delete all of their messages)
|
|
|
|
// 2. Delete all remaining messages (which will be the inbox messages)
|
|
|
|
ContentResolver resolver = context.getContentResolver();
|
|
|
|
String[] accountIdArgs = new String[] { Long.toString(accountId) };
|
|
|
|
resolver.delete(Mailbox.CONTENT_URI,
|
|
|
|
MAILBOXES_FOR_ACCOUNT_EXCEPT_ACCOUNT_MAILBOX_SELECTION,
|
|
|
|
accountIdArgs);
|
|
|
|
resolver.delete(Message.CONTENT_URI, MESSAGES_FOR_ACCOUNT_SELECTION, accountIdArgs);
|
|
|
|
|
|
|
|
// Delete sync keys on remaining items
|
|
|
|
ContentValues cv = new ContentValues();
|
|
|
|
cv.putNull(Account.SYNC_KEY);
|
|
|
|
resolver.update(Account.CONTENT_URI, cv, Account.ID_SELECTION, accountIdArgs);
|
|
|
|
cv.clear();
|
|
|
|
cv.putNull(Mailbox.SYNC_KEY);
|
|
|
|
resolver.update(Mailbox.CONTENT_URI, cv,
|
|
|
|
MAILBOXES_FOR_ACCOUNT_SELECTION, accountIdArgs);
|
|
|
|
|
|
|
|
// Delete PIM data (contacts, calendar), stop syncs, etc. if applicable
|
|
|
|
IEmailService service = EmailServiceUtils.getServiceForAccount(context, null, accountId);
|
|
|
|
if (service != null) {
|
|
|
|
try {
|
|
|
|
service.deleteAccountPIMData(accountId);
|
|
|
|
} catch (RemoteException e) {
|
|
|
|
// Can't do anything about this
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private int[] mSavedWidgetIds = new int[0];
|
|
|
|
private ArrayList<Long> mWidgetNotifyMailboxes = new ArrayList<Long>();
|
|
|
|
private AppWidgetManager mAppWidgetManager;
|
|
|
|
private ComponentName mEmailComponent;
|
|
|
|
|
|
|
|
private void notifyWidgets(long mailboxId) {
|
|
|
|
Context context = getContext();
|
|
|
|
// Lazily initialize these
|
|
|
|
if (mAppWidgetManager == null) {
|
|
|
|
mAppWidgetManager = AppWidgetManager.getInstance(context);
|
|
|
|
mEmailComponent = new ComponentName(context, WidgetProvider.PROVIDER_NAME);
|
|
|
|
}
|
|
|
|
|
|
|
|
// See if we have to populate our array of mailboxes used in widgets
|
|
|
|
int[] widgetIds = mAppWidgetManager.getAppWidgetIds(mEmailComponent);
|
|
|
|
if (!Arrays.equals(widgetIds, mSavedWidgetIds)) {
|
|
|
|
mSavedWidgetIds = widgetIds;
|
|
|
|
String[][] widgetInfos = BaseWidgetProvider.getWidgetInfo(context, widgetIds);
|
|
|
|
// widgetInfo now has pairs of account uri/folder uri
|
|
|
|
mWidgetNotifyMailboxes.clear();
|
|
|
|
for (String[] widgetInfo: widgetInfos) {
|
|
|
|
try {
|
|
|
|
if (widgetInfo == null) continue;
|
|
|
|
long id = Long.parseLong(Uri.parse(widgetInfo[1]).getLastPathSegment());
|
|
|
|
if (!isCombinedMailbox(id)) {
|
|
|
|
// For a regular mailbox, just add it to the list
|
|
|
|
if (!mWidgetNotifyMailboxes.contains(id)) {
|
|
|
|
mWidgetNotifyMailboxes.add(id);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
switch (getVirtualMailboxType(id)) {
|
|
|
|
// We only handle the combined inbox in widgets
|
|
|
|
case Mailbox.TYPE_INBOX:
|
|
|
|
Cursor c = query(Mailbox.CONTENT_URI, Mailbox.ID_PROJECTION,
|
|
|
|
MailboxColumns.TYPE + "=?",
|
|
|
|
new String[] {Integer.toString(Mailbox.TYPE_INBOX)}, null);
|
|
|
|
try {
|
|
|
|
while (c.moveToNext()) {
|
|
|
|
mWidgetNotifyMailboxes.add(
|
|
|
|
c.getLong(Mailbox.ID_PROJECTION_COLUMN));
|
|
|
|
}
|
|
|
|
} finally {
|
|
|
|
c.close();
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} catch (NumberFormatException e) {
|
|
|
|
// Move along
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// If our mailbox needs to be notified, do so...
|
|
|
|
if (mWidgetNotifyMailboxes.contains(mailboxId)) {
|
|
|
|
Intent intent = new Intent(Utils.ACTION_NOTIFY_DATASET_CHANGED);
|
|
|
|
intent.putExtra(Utils.EXTRA_FOLDER_URI, uiUri("uifolder", mailboxId));
|
|
|
|
intent.setType(EMAIL_APP_MIME_TYPE);
|
|
|
|
context.sendBroadcast(intent);
|
|
|
|
}
|
|
|
|
}
|
2009-05-26 23:40:34 +00:00
|
|
|
}
|