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;
|
|
|
|
|
2014-05-14 18:16:56 +00:00
|
|
|
import android.accounts.AccountManager;
|
2012-06-28 17:40:46 +00:00
|
|
|
import android.appwidget.AppWidgetManager;
|
2013-10-11 21:20:03 +00:00
|
|
|
import android.content.ComponentCallbacks;
|
2012-06-28 17:40:46 +00:00
|
|
|
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;
|
2013-04-19 23:32:57 +00:00
|
|
|
import android.content.PeriodicSync;
|
2014-06-16 20:30:25 +00:00
|
|
|
import android.content.SharedPreferences;
|
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.UriMatcher;
|
2013-10-11 21:20:03 +00:00
|
|
|
import android.content.pm.ActivityInfo;
|
|
|
|
import android.content.res.Configuration;
|
2013-03-09 00:54:50 +00:00
|
|
|
import android.content.res.Resources;
|
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.ContentObserver;
|
|
|
|
import android.database.Cursor;
|
2012-06-28 17:40:46 +00:00
|
|
|
import android.database.CursorWrapper;
|
2013-08-17 01:30:44 +00:00
|
|
|
import android.database.DatabaseUtils;
|
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;
|
2014-05-08 20:07:54 +00:00
|
|
|
import android.database.sqlite.SQLiteStatement;
|
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.net.Uri;
|
2013-05-30 02:30:52 +00:00
|
|
|
import android.os.AsyncTask;
|
2013-03-21 23:54:49 +00:00
|
|
|
import android.os.Binder;
|
2013-10-30 02:34:17 +00:00
|
|
|
import android.os.Build;
|
2012-06-28 17:40:46 +00:00
|
|
|
import android.os.Bundle;
|
2013-09-25 23:33:55 +00:00
|
|
|
import android.os.Handler;
|
|
|
|
import android.os.Handler.Callback;
|
2013-10-08 20:38:47 +00:00
|
|
|
import android.os.Looper;
|
2012-06-28 17:40:46 +00:00
|
|
|
import android.os.Parcel;
|
2013-03-21 23:54:49 +00:00
|
|
|
import android.os.ParcelFileDescriptor;
|
2012-06-28 17:40:46 +00:00
|
|
|
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;
|
2013-04-12 00:11:34 +00:00
|
|
|
import android.text.format.DateUtils;
|
2013-04-12 02:51:19 +00:00
|
|
|
import android.util.Base64;
|
2013-09-25 23:33:55 +00:00
|
|
|
import android.util.Log;
|
2013-08-14 18:05:19 +00:00
|
|
|
import android.util.SparseArray;
|
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
|
|
|
|
2012-06-28 17:40:46 +00:00
|
|
|
import com.android.common.content.ProjectionMap;
|
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;
|
2014-06-20 21:32:15 +00:00
|
|
|
import com.android.email.activity.setup.AccountSettingsFragment;
|
2014-05-14 18:16:56 +00:00
|
|
|
import com.android.email.activity.setup.AccountSettingsUtils;
|
2014-07-11 17:08:20 +00:00
|
|
|
import com.android.email.activity.setup.HeadlessAccountSettingsLoader;
|
2014-06-25 16:56:29 +00:00
|
|
|
import com.android.email.service.AttachmentService;
|
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;
|
2013-12-04 00:55:48 +00:00
|
|
|
import com.android.emailcommon.provider.Credential;
|
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;
|
2013-09-25 23:33:55 +00:00
|
|
|
import com.android.emailcommon.provider.EmailContent.HostAuthColumns;
|
2011-02-10 18:26:56 +00:00
|
|
|
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;
|
2014-04-11 21:42:28 +00:00
|
|
|
import com.android.emailcommon.provider.EmailContent.QuickResponseColumns;
|
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;
|
2013-10-11 18:28:32 +00:00
|
|
|
import com.android.emailcommon.provider.MailboxUtilities;
|
2013-09-05 20:52:03 +00:00
|
|
|
import com.android.emailcommon.provider.MessageChangeLogTable;
|
|
|
|
import com.android.emailcommon.provider.MessageMove;
|
|
|
|
import com.android.emailcommon.provider.MessageStateChange;
|
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;
|
2013-05-03 01:32:36 +00:00
|
|
|
import com.android.emailcommon.service.EmailServiceStatus;
|
2012-06-28 17:40:46 +00:00
|
|
|
import com.android.emailcommon.service.IEmailService;
|
|
|
|
import com.android.emailcommon.service.SearchParams;
|
|
|
|
import com.android.emailcommon.utility.AttachmentUtilities;
|
2014-03-20 20:33:34 +00:00
|
|
|
import com.android.emailcommon.utility.EmailAsyncTask;
|
2014-01-30 20:00:03 +00:00
|
|
|
import com.android.emailcommon.utility.IntentUtilities;
|
2012-06-28 17:40:46 +00:00
|
|
|
import com.android.emailcommon.utility.Utility;
|
2012-07-24 19:57:21 +00:00
|
|
|
import com.android.ex.photo.provider.PhotoContract;
|
2013-02-14 22:34:33 +00:00
|
|
|
import com.android.mail.preferences.MailPrefs;
|
2014-06-16 20:30:25 +00:00
|
|
|
import com.android.mail.preferences.MailPrefs.PreferenceKeys;
|
2012-07-27 16:22:50 +00:00
|
|
|
import com.android.mail.providers.Folder;
|
2012-12-08 03:33:24 +00:00
|
|
|
import com.android.mail.providers.FolderList;
|
2013-10-30 17:07:59 +00:00
|
|
|
import com.android.mail.providers.Settings;
|
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;
|
2013-02-23 03:42:40 +00:00
|
|
|
import com.android.mail.utils.AttachmentUtils;
|
2013-05-26 04:32:32 +00:00
|
|
|
import com.android.mail.utils.LogTag;
|
2012-06-28 17:40:46 +00:00
|
|
|
import com.android.mail.utils.LogUtils;
|
2013-02-06 23:21:15 +00:00
|
|
|
import com.android.mail.utils.MatrixCursorWithCachedColumns;
|
2012-06-28 17:40:46 +00:00
|
|
|
import com.android.mail.utils.MatrixCursorWithExtra;
|
2013-04-02 00:17:32 +00:00
|
|
|
import com.android.mail.utils.MimeType;
|
2012-06-28 17:40:46 +00:00
|
|
|
import com.android.mail.utils.Utils;
|
|
|
|
import com.android.mail.widget.BaseWidgetProvider;
|
2012-08-03 21:10:32 +00:00
|
|
|
import com.google.common.collect.ImmutableMap;
|
2012-07-20 21:57:40 +00:00
|
|
|
import com.google.common.collect.ImmutableSet;
|
2013-10-30 22:56:33 +00:00
|
|
|
import com.google.common.collect.Sets;
|
2009-05-26 23:40:34 +00:00
|
|
|
|
2009-11-19 01:11:33 +00:00
|
|
|
import java.io.File;
|
2012-09-08 01:31:04 +00:00
|
|
|
import java.io.FileDescriptor;
|
2013-03-21 23:54:49 +00:00
|
|
|
import java.io.FileNotFoundException;
|
2014-05-12 18:33:43 +00:00
|
|
|
import java.io.FileWriter;
|
2014-05-08 20:07:54 +00:00
|
|
|
import java.io.IOException;
|
2012-09-08 01:31:04 +00:00
|
|
|
import java.io.PrintWriter;
|
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;
|
2014-05-14 18:16:56 +00:00
|
|
|
import java.util.Collection;
|
2013-09-25 23:33:55 +00:00
|
|
|
import java.util.HashSet;
|
2011-06-28 03:11:24 +00:00
|
|
|
import java.util.List;
|
2013-09-05 20:52:03 +00:00
|
|
|
import java.util.Locale;
|
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-07-20 21:57:40 +00:00
|
|
|
import java.util.Set;
|
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
|
|
|
|
*
|
|
|
|
*/
|
2014-06-16 20:30:25 +00:00
|
|
|
public class EmailProvider extends ContentProvider
|
|
|
|
implements SharedPreferences.OnSharedPreferenceChangeListener {
|
2009-05-26 23:40:34 +00:00
|
|
|
|
2013-05-26 04:32:32 +00:00
|
|
|
private static final String TAG = LogTag.getLogTag();
|
2009-05-26 23:40:34 +00:00
|
|
|
|
2013-09-25 23:33:55 +00:00
|
|
|
// Time to delay upsync requests.
|
|
|
|
public static final long SYNC_DELAY_MILLIS = 30 * DateUtils.SECOND_IN_MILLIS;
|
|
|
|
|
2012-09-08 17:50:40 +00:00
|
|
|
public static String EMAIL_APP_MIME_TYPE;
|
2012-06-28 17:40:46 +00:00
|
|
|
|
2013-11-27 04:41:53 +00:00
|
|
|
// exposed for testing
|
|
|
|
public static final String DATABASE_NAME = "EmailProvider.db";
|
|
|
|
public static final String BODY_DATABASE_NAME = "EmailProviderBody.db";
|
|
|
|
|
2014-05-14 18:16:56 +00:00
|
|
|
// We don't back up to the backup database anymore, just keep this constant here so we can
|
|
|
|
// delete the old backups and trigger a new backup to the account manager
|
|
|
|
@Deprecated
|
2012-12-11 18:37:35 +00:00
|
|
|
private static final String BACKUP_DATABASE_NAME = "EmailProviderBackup.db";
|
2014-05-14 18:16:56 +00:00
|
|
|
private static final String ACCOUNT_MANAGER_JSON_TAG = "accountJson";
|
2010-08-10 00:48:53 +00:00
|
|
|
|
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.
|
|
|
|
*/
|
2012-12-11 18:37:35 +00:00
|
|
|
private 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
|
|
|
|
2012-12-11 18:37:35 +00:00
|
|
|
private static final String EMAIL_MESSAGE_MIME_TYPE =
|
2010-10-14 02:04:46 +00:00
|
|
|
"vnd.android.cursor.item/email-message";
|
2012-12-11 18:37:35 +00:00
|
|
|
private static final String EMAIL_ATTACHMENT_MIME_TYPE =
|
2010-08-10 00:48:53 +00:00
|
|
|
"vnd.android.cursor.item/email-attachment";
|
|
|
|
|
2011-05-06 18:31:10 +00:00
|
|
|
/** Appended to the notification URI for delete operations */
|
2012-12-11 18:37:35 +00:00
|
|
|
private static final String NOTIFICATION_OP_DELETE = "delete";
|
2011-05-06 18:31:10 +00:00
|
|
|
/** Appended to the notification URI for insert operations */
|
2012-12-11 18:37:35 +00:00
|
|
|
private static final String NOTIFICATION_OP_INSERT = "insert";
|
2011-05-06 18:31:10 +00:00
|
|
|
/** Appended to the notification URI for update operations */
|
2012-12-11 18:37:35 +00:00
|
|
|
private static final String NOTIFICATION_OP_UPDATE = "update";
|
2011-05-06 18:31:10 +00:00
|
|
|
|
2013-04-12 00:11:34 +00:00
|
|
|
/** The query string to trigger a folder refresh. */
|
2014-04-09 21:15:58 +00:00
|
|
|
protected static String QUERY_UIREFRESH = "uirefresh";
|
2013-04-12 00:11:34 +00:00
|
|
|
|
2009-10-13 23:25:00 +00:00
|
|
|
// Definitions for our queries looking for orphaned messages
|
|
|
|
private static final String[] ORPHANS_PROJECTION
|
2014-04-11 21:42:28 +00:00
|
|
|
= new String[] {MessageColumns._ID, MessageColumns.MAILBOX_KEY};
|
2009-10-13 23:25:00 +00:00
|
|
|
private static final int ORPHANS_ID = 0;
|
|
|
|
private static final int ORPHANS_MAILBOX_KEY = 1;
|
|
|
|
|
2014-04-11 21:42:28 +00:00
|
|
|
private static final String WHERE_ID = BaseColumns._ID + "=?";
|
2009-06-27 19:14:14 +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;
|
2014-05-14 18:16:56 +00:00
|
|
|
private static final int ACCOUNT_CHECK = ACCOUNT_BASE + 2;
|
|
|
|
private static final int ACCOUNT_PICK_TRASH_FOLDER = ACCOUNT_BASE + 3;
|
|
|
|
private static final int ACCOUNT_PICK_SENT_FOLDER = ACCOUNT_BASE + 4;
|
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;
|
2013-04-18 23:56:03 +00:00
|
|
|
private static final int MAILBOX_NOTIFICATION = MAILBOX_BASE + 2;
|
|
|
|
private static final int MAILBOX_MOST_RECENT_MESSAGE = MAILBOX_BASE + 3;
|
|
|
|
private static final int MAILBOX_MESSAGE_COUNT = MAILBOX_BASE + 4;
|
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-07-20 17:17:02 +00:00
|
|
|
private static final int MESSAGE_SELECTION = MESSAGE_BASE + 3;
|
2013-09-05 20:52:03 +00:00
|
|
|
private static final int MESSAGE_MOVE = MESSAGE_BASE + 4;
|
|
|
|
private static final int MESSAGE_STATE_CHANGE = MESSAGE_BASE + 5;
|
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;
|
2013-03-21 23:54:49 +00:00
|
|
|
private static final int ATTACHMENTS_CACHED_FILE_ACCESS = ATTACHMENT_BASE + 3;
|
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;
|
2013-03-14 20:35:37 +00:00
|
|
|
private static final int UI_UNDO = UI_BASE + 4;
|
|
|
|
private static final int UI_FOLDER_REFRESH = UI_BASE + 5;
|
|
|
|
private static final int UI_FOLDER = UI_BASE + 6;
|
|
|
|
private static final int UI_ACCOUNT = UI_BASE + 7;
|
|
|
|
private static final int UI_ACCTS = UI_BASE + 8;
|
|
|
|
private static final int UI_ATTACHMENTS = UI_BASE + 9;
|
|
|
|
private static final int UI_ATTACHMENT = UI_BASE + 10;
|
2014-04-09 19:59:28 +00:00
|
|
|
private static final int UI_ATTACHMENT_BY_CID = UI_BASE + 11;
|
|
|
|
private static final int UI_SEARCH = UI_BASE + 12;
|
|
|
|
private static final int UI_ACCOUNT_DATA = UI_BASE + 13;
|
|
|
|
private static final int UI_FOLDER_LOAD_MORE = UI_BASE + 14;
|
|
|
|
private static final int UI_CONVERSATION = UI_BASE + 15;
|
|
|
|
private static final int UI_RECENT_FOLDERS = UI_BASE + 16;
|
|
|
|
private static final int UI_DEFAULT_RECENT_FOLDERS = UI_BASE + 17;
|
|
|
|
private static final int UI_FULL_FOLDERS = UI_BASE + 18;
|
|
|
|
private static final int UI_ALL_FOLDERS = UI_BASE + 19;
|
|
|
|
private static final int UI_PURGE_FOLDER = UI_BASE + 20;
|
2014-05-01 22:27:21 +00:00
|
|
|
private static final int UI_INBOX = UI_BASE + 21;
|
2012-06-28 17:40:46 +00:00
|
|
|
|
2013-08-14 18:05:19 +00:00
|
|
|
private static final int BODY_BASE = 0xA000;
|
2009-05-26 23:40:34 +00:00
|
|
|
private static final int BODY = BODY_BASE;
|
|
|
|
private static final int BODY_ID = BODY_BASE + 1;
|
2014-05-08 20:07:54 +00:00
|
|
|
private static final int BODY_HTML = BODY_BASE + 2;
|
|
|
|
private static final int BODY_TEXT = BODY_BASE + 3;
|
2009-06-27 19:14:14 +00:00
|
|
|
|
2013-12-04 00:55:48 +00:00
|
|
|
private static final int CREDENTIAL_BASE = 0xB000;
|
|
|
|
private static final int CREDENTIAL = CREDENTIAL_BASE;
|
|
|
|
private static final int CREDENTIAL_ID = CREDENTIAL_BASE + 1;
|
|
|
|
|
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
|
|
|
|
2013-08-14 18:05:19 +00:00
|
|
|
private static final SparseArray<String> TABLE_NAMES;
|
|
|
|
static {
|
|
|
|
SparseArray<String> array = new SparseArray<String>(11);
|
|
|
|
array.put(ACCOUNT_BASE >> BASE_SHIFT, Account.TABLE_NAME);
|
|
|
|
array.put(MAILBOX_BASE >> BASE_SHIFT, Mailbox.TABLE_NAME);
|
|
|
|
array.put(MESSAGE_BASE >> BASE_SHIFT, Message.TABLE_NAME);
|
|
|
|
array.put(ATTACHMENT_BASE >> BASE_SHIFT, Attachment.TABLE_NAME);
|
|
|
|
array.put(HOSTAUTH_BASE >> BASE_SHIFT, HostAuth.TABLE_NAME);
|
|
|
|
array.put(UPDATED_MESSAGE_BASE >> BASE_SHIFT, Message.UPDATED_TABLE_NAME);
|
|
|
|
array.put(DELETED_MESSAGE_BASE >> BASE_SHIFT, Message.DELETED_TABLE_NAME);
|
|
|
|
array.put(POLICY_BASE >> BASE_SHIFT, Policy.TABLE_NAME);
|
|
|
|
array.put(QUICK_RESPONSE_BASE >> BASE_SHIFT, QuickResponse.TABLE_NAME);
|
|
|
|
array.put(UI_BASE >> BASE_SHIFT, null);
|
|
|
|
array.put(BODY_BASE >> BASE_SHIFT, Body.TABLE_NAME);
|
2013-12-04 00:55:48 +00:00
|
|
|
array.put(CREDENTIAL_BASE >> BASE_SHIFT, Credential.TABLE_NAME);
|
2013-08-14 18:05:19 +00:00
|
|
|
TABLE_NAMES = array;
|
|
|
|
}
|
2009-06-27 19:14:14 +00:00
|
|
|
|
2013-08-20 21:54:42 +00:00
|
|
|
private static final UriMatcher sURIMatcher = new UriMatcher(UriMatcher.NO_MATCH);
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Functions which manipulate the database connection or files synchronize on this.
|
|
|
|
* It's static because there can be multiple provider objects.
|
|
|
|
* TODO: Do we actually need to synchronize across all DB access, not just connection creation?
|
|
|
|
*/
|
|
|
|
private static final Object sDatabaseLock = new Object();
|
2009-05-26 23:40:34 +00:00
|
|
|
|
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 " +
|
2014-04-11 21:42:28 +00:00
|
|
|
BaseColumns._ID + '=';
|
2009-06-27 19:14:14 +00:00
|
|
|
|
|
|
|
private static final String UPDATED_MESSAGE_DELETE = "delete from " +
|
2014-04-11 21:42:28 +00:00
|
|
|
Message.UPDATED_TABLE_NAME + " where " + BaseColumns._ID + '=';
|
2009-06-27 19:14:14 +00:00
|
|
|
|
|
|
|
private static final String DELETED_MESSAGE_INSERT = "insert or replace into " +
|
|
|
|
Message.DELETED_TABLE_NAME + " select * from " + Message.TABLE_NAME + " where " +
|
2014-04-11 21:42:28 +00:00
|
|
|
BaseColumns._ID + '=';
|
2009-06-27 19:14:14 +00:00
|
|
|
|
2014-05-12 18:33:43 +00:00
|
|
|
private static final String ORPHAN_BODY_MESSAGE_ID_SELECT =
|
|
|
|
"select " + BodyColumns.MESSAGE_KEY + " from " + Body.TABLE_NAME +
|
|
|
|
" except select " + BaseColumns._ID + " from " + Message.TABLE_NAME;
|
|
|
|
|
2009-06-27 19:14:14 +00:00
|
|
|
private static final String DELETE_ORPHAN_BODIES = "delete from " + Body.TABLE_NAME +
|
2014-05-12 18:33:43 +00:00
|
|
|
" where " + BodyColumns.MESSAGE_KEY + " in " + '(' + ORPHAN_BODY_MESSAGE_ID_SELECT + ')';
|
2009-06-27 19:14:14 +00:00
|
|
|
|
|
|
|
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
|
|
|
|
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
|
|
|
|
2012-12-11 18:37:35 +00:00
|
|
|
private static final String MESSAGE_URI_PARAMETER_MAILBOX_ID = "mailboxId";
|
2010-10-14 02:04:46 +00:00
|
|
|
|
2012-06-28 17:40:46 +00:00
|
|
|
// For undo handling
|
|
|
|
private int mLastSequence = -1;
|
2013-02-22 02:10:10 +00:00
|
|
|
private final ArrayList<ContentProviderOperation> mLastSequenceOps =
|
2012-06-28 17:40:46 +00:00
|
|
|
new ArrayList<ContentProviderOperation>();
|
|
|
|
|
|
|
|
// Query parameter indicating the command came from UIProvider
|
|
|
|
private static final String IS_UIPROVIDER = "is_uiprovider";
|
|
|
|
|
2013-05-03 01:32:36 +00:00
|
|
|
private static final String SYNC_STATUS_CALLBACK_METHOD = "sync_status";
|
|
|
|
|
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) {
|
2013-05-26 04:32:32 +00:00
|
|
|
LogUtils.v(TAG, methodName + ": uri=" + uri + ", match is " + match);
|
2010-12-16 20:28:16 +00:00
|
|
|
}
|
|
|
|
return match;
|
|
|
|
}
|
|
|
|
|
2013-11-27 04:41:53 +00:00
|
|
|
// exposed for testing
|
|
|
|
public static Uri INTEGRITY_CHECK_URI;
|
|
|
|
|
2012-08-23 05:25:42 +00:00
|
|
|
public static Uri ACCOUNT_BACKUP_URI;
|
2012-12-11 18:37:35 +00:00
|
|
|
private static Uri FOLDER_STATUS_URI;
|
2012-08-23 05:25:42 +00:00
|
|
|
|
2012-06-28 17:40:46 +00:00
|
|
|
private SQLiteDatabase mDatabase;
|
|
|
|
private SQLiteDatabase mBodyDatabase;
|
2012-03-09 19:52:45 +00:00
|
|
|
|
2013-09-25 23:33:55 +00:00
|
|
|
private Handler mDelayedSyncHandler;
|
|
|
|
private final Set<SyncRequestMessage> mDelayedSyncRequests = new HashSet<SyncRequestMessage>();
|
|
|
|
|
2014-03-20 20:33:34 +00:00
|
|
|
private static void reconcileAccountsAsync(final Context context) {
|
|
|
|
EmailAsyncTask.runAsyncParallel(new Runnable() {
|
|
|
|
@Override
|
|
|
|
public void run() {
|
|
|
|
AccountReconciler.reconcileAccounts(context);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
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.
|
2013-09-06 23:05:39 +00:00
|
|
|
* @return uri string
|
2012-06-28 17:40:46 +00:00
|
|
|
*/
|
|
|
|
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>)
|
2013-11-27 04:41:53 +00:00
|
|
|
* Exposed for testing.
|
2011-06-30 23:03:36 +00:00
|
|
|
* @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
|
|
|
|
*/
|
2013-11-27 04:41:53 +00:00
|
|
|
public static void deleteUnlinked(SQLiteDatabase db, String table, String column,
|
2012-12-11 18:37:35 +00:00
|
|
|
String foreignColumn, String foreignTable) {
|
2011-06-30 23:03:36 +00:00
|
|
|
int count = db.delete(table, column + " not in (select " + foreignColumn + " from " +
|
|
|
|
foreignTable + ")", null);
|
|
|
|
if (count > 0) {
|
2013-05-26 04:32:32 +00:00
|
|
|
LogUtils.w(TAG, "Found " + count + " orphaned row(s) in " + table);
|
2011-06-30 23:03:36 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-10-11 18:28:32 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Make sure that parentKeys match with parentServerId.
|
|
|
|
* When we sync folders, we do two passes: First to create the mailbox rows, and second
|
|
|
|
* to set the parentKeys. Two passes are needed because we won't know the parent's Id
|
|
|
|
* until that row is inserted, and the order in which the rows are given is arbitrary.
|
|
|
|
* If we crash while this operation is in progress, the parent keys can be left uninitialized.
|
2014-04-09 21:15:58 +00:00
|
|
|
* @param db SQLiteDatabase to modify
|
2013-10-11 18:28:32 +00:00
|
|
|
*/
|
|
|
|
private void fixParentKeys(SQLiteDatabase db) {
|
|
|
|
LogUtils.d(TAG, "Fixing parent keys");
|
|
|
|
|
|
|
|
// Update the parentKey for each mailbox row to match the _id of the row whose
|
|
|
|
// serverId matches our parentServerId. This will leave parentKey blank for any
|
|
|
|
// row that does not have a parentServerId
|
|
|
|
|
|
|
|
// This is kind of a confusing sql statement, so here's the actual text of it,
|
|
|
|
// for reference:
|
|
|
|
//
|
|
|
|
// update mailbox set parentKey = (select _id from mailbox as b where
|
|
|
|
// mailbox.parentServerId=b.serverId and mailbox.parentServerId not null and
|
|
|
|
// mailbox.accountKey=b.accountKey)
|
|
|
|
db.execSQL("update " + Mailbox.TABLE_NAME + " set " + MailboxColumns.PARENT_KEY + "="
|
2014-04-11 21:42:28 +00:00
|
|
|
+ "(select " + Mailbox._ID + " from " + Mailbox.TABLE_NAME + " as b where "
|
2013-10-11 18:28:32 +00:00
|
|
|
+ Mailbox.TABLE_NAME + "." + MailboxColumns.PARENT_SERVER_ID + "="
|
|
|
|
+ "b." + MailboxColumns.SERVER_ID + " and "
|
|
|
|
+ Mailbox.TABLE_NAME + "." + MailboxColumns.PARENT_SERVER_ID + " not null and "
|
|
|
|
+ Mailbox.TABLE_NAME + "." + MailboxColumns.ACCOUNT_KEY
|
|
|
|
+ "=b." + Mailbox.ACCOUNT_KEY + ")");
|
|
|
|
|
|
|
|
// Top level folders can still have uninitialized parent keys. Update these
|
|
|
|
// to indicate that the parent is -1.
|
|
|
|
//
|
|
|
|
// update mailbox set parentKey = -1 where parentKey=0 or parentKey is null;
|
|
|
|
db.execSQL("update " + Mailbox.TABLE_NAME + " set " + MailboxColumns.PARENT_KEY
|
|
|
|
+ "=" + Mailbox.NO_MAILBOX + " where " + MailboxColumns.PARENT_KEY
|
|
|
|
+ "=" + Mailbox.PARENT_KEY_UNINITIALIZED + " or " + MailboxColumns.PARENT_KEY
|
|
|
|
+ " is null");
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2013-11-27 04:41:53 +00:00
|
|
|
// exposed for testing
|
|
|
|
public SQLiteDatabase getDatabase(Context context) {
|
2013-08-20 21:54:42 +00:00
|
|
|
synchronized (sDatabaseLock) {
|
|
|
|
// Always return the cached database, if we've got one
|
|
|
|
if (mDatabase != null) {
|
|
|
|
return mDatabase;
|
|
|
|
}
|
2009-11-19 01:11:33 +00:00
|
|
|
|
2013-08-20 21:54:42 +00:00
|
|
|
// Whenever we create or re-cache the databases, make sure that we haven't lost one
|
|
|
|
// to corruption
|
|
|
|
checkDatabases();
|
2009-11-19 01:11:33 +00:00
|
|
|
|
2013-08-20 21:54:42 +00:00
|
|
|
DBHelper.DatabaseHelper helper = new DBHelper.DatabaseHelper(context, DATABASE_NAME);
|
|
|
|
mDatabase = helper.getWritableDatabase();
|
|
|
|
DBHelper.BodyDatabaseHelper bodyHelper =
|
|
|
|
new DBHelper.BodyDatabaseHelper(context, BODY_DATABASE_NAME);
|
|
|
|
mBodyDatabase = bodyHelper.getWritableDatabase();
|
|
|
|
if (mBodyDatabase != null) {
|
|
|
|
String bodyFileName = mBodyDatabase.getPath();
|
|
|
|
mDatabase.execSQL("attach \"" + bodyFileName + "\" as BodyDatabase");
|
|
|
|
}
|
2009-10-13 23:25:00 +00:00
|
|
|
|
2013-08-20 21:54:42 +00:00
|
|
|
// Restore accounts if the database is corrupted...
|
|
|
|
restoreIfNeeded(context, mDatabase);
|
|
|
|
// Check for any orphaned Messages in the updated/deleted tables
|
|
|
|
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,
|
2014-04-11 21:42:28 +00:00
|
|
|
AccountColumns._ID, Account.TABLE_NAME);
|
2013-08-20 21:54:42 +00:00
|
|
|
deleteUnlinked(mDatabase, Message.TABLE_NAME, MessageColumns.ACCOUNT_KEY,
|
2014-04-11 21:42:28 +00:00
|
|
|
AccountColumns._ID, Account.TABLE_NAME);
|
|
|
|
deleteUnlinked(mDatabase, Policy.TABLE_NAME, PolicyColumns._ID,
|
2013-08-20 21:54:42 +00:00
|
|
|
AccountColumns.POLICY_KEY, Account.TABLE_NAME);
|
2013-10-11 18:28:32 +00:00
|
|
|
fixParentKeys(mDatabase);
|
2013-08-20 21:54:42 +00:00
|
|
|
initUiProvider();
|
|
|
|
return mDatabase;
|
|
|
|
}
|
2009-05-26 23:40:34 +00:00
|
|
|
}
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2011-06-21 01:10:10 +00:00
|
|
|
/**
|
|
|
|
* Restore user Account and HostAuth data from our backup database
|
|
|
|
*/
|
2012-12-11 18:37:35 +00:00
|
|
|
private static void restoreIfNeeded(Context context, SQLiteDatabase mainDatabase) {
|
2012-06-28 17:40:46 +00:00
|
|
|
if (MailActivityEmail.DEBUG) {
|
2013-05-26 04:32:32 +00:00
|
|
|
LogUtils.w(TAG, "restoreIfNeeded...");
|
2011-06-21 01:10:10 +00:00
|
|
|
}
|
|
|
|
// 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);
|
2013-05-26 04:32:32 +00:00
|
|
|
LogUtils.w(TAG, "Created new EmailProvider backup database");
|
2011-06-21 01:10:10 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2014-05-14 18:16:56 +00:00
|
|
|
// If there's a backup database (old style) delete it and trigger an account manager backup.
|
|
|
|
// Roughly the same comment as above applies
|
|
|
|
final File backupDb = context.getDatabasePath(BACKUP_DATABASE_NAME);
|
|
|
|
if (backupDb.exists()) {
|
|
|
|
backupAccounts(context, mainDatabase);
|
|
|
|
context.deleteDatabase(BACKUP_DATABASE_NAME);
|
|
|
|
LogUtils.w(TAG, "Migrated from backup database to account manager");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2011-06-21 01:10:10 +00:00
|
|
|
// If we have accounts, we're done
|
2013-05-16 00:20:12 +00:00
|
|
|
if (DatabaseUtils.longForQuery(mainDatabase,
|
2013-09-04 20:49:54 +00:00
|
|
|
"SELECT EXISTS (SELECT ? FROM " + Account.TABLE_NAME + " )",
|
2013-05-16 00:20:12 +00:00
|
|
|
EmailContent.ID_PROJECTION) > 0) {
|
2013-12-04 22:25:59 +00:00
|
|
|
if (MailActivityEmail.DEBUG) {
|
|
|
|
LogUtils.w(TAG, "restoreIfNeeded: Account exists.");
|
|
|
|
}
|
|
|
|
return;
|
2011-06-21 01:10:10 +00:00
|
|
|
}
|
2012-12-08 03:33:24 +00:00
|
|
|
|
2014-05-14 18:16:56 +00:00
|
|
|
restoreAccounts(context);
|
2011-06-21 01:10:10 +00:00
|
|
|
}
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-11-27 04:41:53 +00:00
|
|
|
// exposed for testing
|
|
|
|
public 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) {
|
2013-09-25 23:33:55 +00:00
|
|
|
Log.d(TAG, "Delete: " + uri);
|
2010-12-16 20:28:16 +00:00
|
|
|
final int match = findMatch(uri, "delete");
|
2013-12-17 19:31:28 +00:00
|
|
|
final 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)
|
2013-12-17 19:31:28 +00:00
|
|
|
final SQLiteDatabase db = getDatabase(context);
|
|
|
|
final 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;
|
2009-06-27 19:14:14 +00:00
|
|
|
|
2013-12-17 19:31:28 +00:00
|
|
|
final String tableName = TABLE_NAMES.valueAt(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);
|
2014-04-10 18:52:45 +00:00
|
|
|
case UI_PURGE_FOLDER:
|
|
|
|
return uiPurgeFolder(uri);
|
2012-07-20 17:17:02 +00:00
|
|
|
case MESSAGE_SELECTION:
|
2012-06-29 16:42:05 +00:00
|
|
|
Cursor findCursor = db.query(tableName, Message.ID_COLUMN_PROJECTION, selection,
|
|
|
|
selectionArgs, null, null, null);
|
|
|
|
try {
|
|
|
|
if (findCursor.moveToFirst()) {
|
|
|
|
return delete(ContentUris.withAppendedId(
|
2012-07-20 17:17:02 +00:00
|
|
|
Message.CONTENT_URI,
|
2012-06-29 16:42:05 +00:00
|
|
|
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:
|
2013-12-04 00:55:48 +00:00
|
|
|
case CREDENTIAL_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);
|
|
|
|
}
|
2013-04-18 23:56:03 +00:00
|
|
|
|
2013-08-06 23:09:00 +00:00
|
|
|
final long accountId;
|
|
|
|
if (match == MAILBOX_ID) {
|
|
|
|
accountId = Mailbox.getAccountIdForMailbox(context, id);
|
|
|
|
} else {
|
|
|
|
accountId = Account.NO_ACCOUNT;
|
|
|
|
}
|
|
|
|
|
2013-04-18 23:56:03 +00:00
|
|
|
result = db.delete(tableName, whereWithId(id, selection), selectionArgs);
|
|
|
|
|
2012-06-28 17:40:46 +00:00
|
|
|
if (match == ACCOUNT_ID) {
|
|
|
|
notifyUI(UIPROVIDER_ACCOUNT_NOTIFIER, id);
|
2013-10-30 22:56:33 +00:00
|
|
|
notifyUI(UIPROVIDER_ALL_ACCOUNTS_NOTIFIER, null);
|
2012-06-28 17:40:46 +00:00
|
|
|
} else if (match == MAILBOX_ID) {
|
2013-08-06 23:09:00 +00:00
|
|
|
notifyUIFolder(id, accountId);
|
2012-08-02 17:53:40 +00:00
|
|
|
} else if (match == ATTACHMENT_ID) {
|
|
|
|
notifyUI(UIPROVIDER_ATTACHMENT_NOTIFIER, id);
|
2012-06-28 17:40:46 +00:00
|
|
|
}
|
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,
|
2014-04-11 21:42:28 +00:00
|
|
|
whereWith(AttachmentColumns.MESSAGE_KEY + "=" + id, selection),
|
|
|
|
selectionArgs);
|
2009-09-10 18:52:36 +00:00
|
|
|
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
|
|
|
result = db.delete(tableName, selection, selectionArgs);
|
2009-06-16 19:03:45 +00:00
|
|
|
break;
|
2013-09-05 20:52:03 +00:00
|
|
|
case MESSAGE_MOVE:
|
|
|
|
db.delete(MessageMove.TABLE_NAME, selection, selectionArgs);
|
|
|
|
break;
|
|
|
|
case MESSAGE_STATE_CHANGE:
|
|
|
|
db.delete(MessageStateChange.TABLE_NAME, selection, selectionArgs);
|
|
|
|
break;
|
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
|
2014-05-12 18:33:43 +00:00
|
|
|
final ContentValues emptyValues = new ContentValues(2);
|
|
|
|
emptyValues.putNull(BodyColumns.HTML_CONTENT);
|
|
|
|
emptyValues.putNull(BodyColumns.TEXT_CONTENT);
|
|
|
|
final long messageId = Long.valueOf(id);
|
|
|
|
try {
|
|
|
|
writeBodyFiles(context, messageId, emptyValues);
|
|
|
|
} catch (final IllegalStateException e) {
|
|
|
|
LogUtils.v(LogUtils.TAG, e, "Exception while deleting bodies");
|
|
|
|
}
|
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
|
2014-05-12 18:33:43 +00:00
|
|
|
final Cursor orphans = db.rawQuery(ORPHAN_BODY_MESSAGE_ID_SELECT, null);
|
|
|
|
try {
|
|
|
|
final ContentValues emptyValues = new ContentValues(2);
|
|
|
|
emptyValues.putNull(BodyColumns.HTML_CONTENT);
|
|
|
|
emptyValues.putNull(BodyColumns.TEXT_CONTENT);
|
|
|
|
while (orphans.moveToNext()) {
|
|
|
|
final long messageId = orphans.getLong(0);
|
|
|
|
try {
|
|
|
|
writeBodyFiles(context, messageId, emptyValues);
|
|
|
|
} catch (final IllegalStateException e) {
|
|
|
|
LogUtils.v(LogUtils.TAG, e, "Exception while deleting bodies");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} finally {
|
|
|
|
orphans.close();
|
|
|
|
}
|
2009-08-03 13:05:50 +00:00
|
|
|
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
|
2013-10-30 22:56:33 +00:00
|
|
|
notifyUI(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:
|
2013-04-12 23:19:58 +00:00
|
|
|
return null;
|
2009-05-26 23:40:34 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-08-06 23:09:00 +00:00
|
|
|
// These URIs are used for specific UI notifications. We don't use EmailContent.CONTENT_URI
|
|
|
|
// as the base because that gets spammed.
|
2013-09-11 18:48:38 +00:00
|
|
|
// These can't be statically initialized because they depend on EmailContent.AUTHORITY
|
|
|
|
private static Uri UIPROVIDER_CONVERSATION_NOTIFIER;
|
|
|
|
private static Uri UIPROVIDER_FOLDER_NOTIFIER;
|
|
|
|
private static Uri UIPROVIDER_FOLDERLIST_NOTIFIER;
|
|
|
|
private static Uri UIPROVIDER_ACCOUNT_NOTIFIER;
|
2013-09-10 23:18:22 +00:00
|
|
|
// Not currently used
|
2013-09-11 18:48:38 +00:00
|
|
|
//public static Uri UIPROVIDER_SETTINGS_NOTIFIER;
|
|
|
|
private static Uri UIPROVIDER_ATTACHMENT_NOTIFIER;
|
|
|
|
private static Uri UIPROVIDER_ATTACHMENTS_NOTIFIER;
|
|
|
|
public static Uri UIPROVIDER_ALL_ACCOUNTS_NOTIFIER;
|
|
|
|
private static Uri UIPROVIDER_MESSAGE_NOTIFIER;
|
|
|
|
private static Uri UIPROVIDER_RECENT_FOLDERS_NOTIFIER;
|
2012-06-28 17:40:46 +00:00
|
|
|
|
2009-05-26 23:40:34 +00:00
|
|
|
@Override
|
|
|
|
public Uri insert(Uri uri, ContentValues values) {
|
2013-09-25 23:33:55 +00:00
|
|
|
Log.d(TAG, "Insert: " + uri);
|
2013-12-17 19:31:28 +00:00
|
|
|
final int match = findMatch(uri, "insert");
|
|
|
|
final Context context = getContext();
|
2010-09-23 16:19:44 +00:00
|
|
|
|
2009-08-06 00:31:50 +00:00
|
|
|
// See the comment at delete(), above
|
2013-12-17 19:31:28 +00:00
|
|
|
final SQLiteDatabase db = getDatabase(context);
|
|
|
|
final 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
|
|
|
|
2013-09-10 23:18:22 +00:00
|
|
|
final Uri resultUri;
|
2009-06-27 19:14:14 +00:00
|
|
|
|
2009-11-19 01:11:33 +00:00
|
|
|
try {
|
|
|
|
switch (match) {
|
2014-05-12 18:33:43 +00:00
|
|
|
case BODY:
|
|
|
|
final ContentValues dbValues = new ContentValues(values);
|
|
|
|
// Prune out the content we don't want in the DB
|
|
|
|
dbValues.remove(BodyColumns.HTML_CONTENT);
|
|
|
|
dbValues.remove(BodyColumns.TEXT_CONTENT);
|
|
|
|
// TODO: move this to the message table
|
|
|
|
longId = db.insert(Body.TABLE_NAME, "foo", dbValues);
|
|
|
|
resultUri = ContentUris.withAppendedId(uri, longId);
|
|
|
|
// Write content to the filesystem where appropriate
|
|
|
|
// This will look less ugly once the body table is folded into the message table
|
|
|
|
// and we can just use longId instead
|
|
|
|
if (!values.containsKey(BodyColumns.MESSAGE_KEY)) {
|
|
|
|
throw new IllegalArgumentException(
|
|
|
|
"Cannot insert body without MESSAGE_KEY");
|
|
|
|
}
|
|
|
|
final long messageId = values.getAsLong(BodyColumns.MESSAGE_KEY);
|
|
|
|
writeBodyFiles(getContext(), messageId, values);
|
|
|
|
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
|
|
|
// 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:
|
2014-01-25 00:43:36 +00:00
|
|
|
decodeEmailAddresses(values);
|
2009-11-19 01:11:33 +00:00
|
|
|
case ATTACHMENT:
|
|
|
|
case MAILBOX:
|
|
|
|
case ACCOUNT:
|
|
|
|
case HOSTAUTH:
|
2013-12-04 00:55:48 +00:00
|
|
|
case CREDENTIAL:
|
2011-04-28 00:12:06 +00:00
|
|
|
case POLICY:
|
2011-06-01 17:09:26 +00:00
|
|
|
case QUICK_RESPONSE:
|
2013-08-14 18:05:19 +00:00
|
|
|
longId = db.insert(TABLE_NAMES.valueAt(table), "foo", values);
|
2011-05-06 18:31:10 +00:00
|
|
|
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:
|
2014-04-11 21:42:28 +00:00
|
|
|
final long mailboxId = values.getAsLong(MessageColumns.MAILBOX_KEY);
|
2012-06-28 17:40:46 +00:00
|
|
|
if (!uri.getBooleanQueryParameter(IS_UIPROVIDER, false)) {
|
2013-08-06 23:09:00 +00:00
|
|
|
notifyUIConversationMailbox(mailboxId);
|
2012-06-28 17:40:46 +00:00
|
|
|
}
|
2014-04-11 21:42:28 +00:00
|
|
|
notifyUIFolder(mailboxId, values.getAsLong(MessageColumns.ACCOUNT_KEY));
|
2012-06-28 17:40:46 +00:00
|
|
|
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)) {
|
2013-08-06 23:09:00 +00:00
|
|
|
if (values.getAsInteger(MailboxColumns.TYPE) <
|
|
|
|
Mailbox.TYPE_NOT_EMAIL) {
|
|
|
|
// Notify the account when a new mailbox is added
|
|
|
|
final Long accountId =
|
|
|
|
values.getAsLong(MailboxColumns.ACCOUNT_KEY);
|
2013-09-10 23:18:22 +00:00
|
|
|
if (accountId != null && accountId > 0) {
|
2013-08-06 23:09:00 +00:00
|
|
|
notifyUI(UIPROVIDER_ACCOUNT_NOTIFIER, accountId);
|
|
|
|
notifyUI(UIPROVIDER_FOLDERLIST_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
|
|
|
}
|
|
|
|
}
|
2013-04-18 23:56:03 +00:00
|
|
|
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 ACCOUNT:
|
2013-04-19 23:32:57 +00:00
|
|
|
updateAccountSyncInterval(longId, values);
|
2013-04-18 23:56:03 +00:00
|
|
|
if (!uri.getBooleanQueryParameter(IS_UIPROVIDER, false)) {
|
|
|
|
notifyUIAccount(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
|
|
|
}
|
2013-10-30 22:56:33 +00:00
|
|
|
notifyUI(UIPROVIDER_ALL_ACCOUNTS_NOTIFIER, null);
|
2013-04-18 23:56:03 +00:00
|
|
|
break;
|
|
|
|
case UPDATED_MESSAGE:
|
|
|
|
case DELETED_MESSAGE:
|
|
|
|
throw new IllegalArgumentException("Unknown URL " + uri);
|
|
|
|
case ATTACHMENT:
|
|
|
|
int flags = 0;
|
2014-04-11 21:42:28 +00:00
|
|
|
if (values.containsKey(AttachmentColumns.FLAGS)) {
|
|
|
|
flags = values.getAsInteger(AttachmentColumns.FLAGS);
|
2013-04-18 23:56:03 +00:00
|
|
|
}
|
|
|
|
// Report all new attachments to the download service
|
2014-04-11 21:42:28 +00:00
|
|
|
if (TextUtils.isEmpty(values.getAsString(AttachmentColumns.LOCATION))) {
|
2013-10-31 20:08:40 +00:00
|
|
|
LogUtils.w(TAG, new Throwable(), "attachment with blank location");
|
|
|
|
}
|
2013-04-18 23:56:03 +00:00
|
|
|
mAttachmentService.attachmentChanged(getContext(), longId, flags);
|
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
|
|
|
break;
|
2010-08-10 00:48:53 +00:00
|
|
|
}
|
2009-11-19 01:11:33 +00:00
|
|
|
break;
|
2013-08-14 18:05:19 +00:00
|
|
|
case QUICK_RESPONSE_ACCOUNT_ID:
|
|
|
|
longId = Long.parseLong(uri.getPathSegments().get(2));
|
2014-04-11 21:42:28 +00:00
|
|
|
values.put(QuickResponseColumns.ACCOUNT_KEY, longId);
|
2013-08-14 18:05:19 +00:00
|
|
|
return insert(QuickResponse.CONTENT_URI, values);
|
2009-11-19 01:11:33 +00:00
|
|
|
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:
|
2013-08-14 18:05:19 +00:00
|
|
|
longId = db.insert(TABLE_NAMES.valueAt(table), "foo", values);
|
2011-05-06 18:31:10 +00:00
|
|
|
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.
|
2013-10-30 22:56:33 +00:00
|
|
|
notifyUI(EmailContent.CONTENT_URI, null);
|
2009-06-01 21:34:16 +00:00
|
|
|
return resultUri;
|
2009-05-26 23:40:34 +00:00
|
|
|
}
|
|
|
|
|
2013-08-20 16:58:17 +00:00
|
|
|
@Override
|
|
|
|
public boolean onCreate() {
|
|
|
|
Context context = getContext();
|
|
|
|
EmailContent.init(context);
|
2013-08-20 21:54:42 +00:00
|
|
|
init(context);
|
|
|
|
// Do this last, so that EmailContent/EmailProvider are initialized
|
|
|
|
MailActivityEmail.setServicesEnabledAsync(context);
|
2014-03-20 20:33:34 +00:00
|
|
|
reconcileAccountsAsync(context);
|
2013-10-08 18:47:08 +00:00
|
|
|
|
|
|
|
// Update widgets
|
|
|
|
final Intent updateAllWidgetsIntent =
|
|
|
|
new Intent(com.android.mail.utils.Utils.ACTION_NOTIFY_DATASET_CHANGED);
|
|
|
|
updateAllWidgetsIntent.putExtra(BaseWidgetProvider.EXTRA_UPDATE_ALL_WIDGETS, true);
|
|
|
|
updateAllWidgetsIntent.setType(context.getString(R.string.application_mime_type));
|
|
|
|
context.sendBroadcast(updateAllWidgetsIntent);
|
|
|
|
|
2013-10-11 21:20:03 +00:00
|
|
|
// The combined account name changes on locale changes
|
|
|
|
final Configuration oldConfiguration =
|
|
|
|
new Configuration(context.getResources().getConfiguration());
|
|
|
|
context.registerComponentCallbacks(new ComponentCallbacks() {
|
|
|
|
@Override
|
|
|
|
public void onConfigurationChanged(Configuration configuration) {
|
|
|
|
int delta = oldConfiguration.updateFrom(configuration);
|
|
|
|
if (Configuration.needNewResources(delta, ActivityInfo.CONFIG_LOCALE)) {
|
|
|
|
notifyUIAccount(COMBINED_ACCOUNT_ID);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onLowMemory() {}
|
|
|
|
});
|
|
|
|
|
2014-06-16 20:30:25 +00:00
|
|
|
MailPrefs.get(context).registerOnSharedPreferenceChangeListener(this);
|
|
|
|
|
2013-08-20 21:54:42 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
private static void init(final Context context) {
|
|
|
|
// Synchronize on the matcher rather than the class object to minimize risk of contention
|
|
|
|
// & deadlock.
|
|
|
|
synchronized (sURIMatcher) {
|
|
|
|
// We use the existence of this variable as indicative of whether this function has
|
|
|
|
// already run.
|
|
|
|
if (INTEGRITY_CHECK_URI != null) {
|
|
|
|
return;
|
|
|
|
}
|
2013-08-20 16:58:17 +00:00
|
|
|
INTEGRITY_CHECK_URI = Uri.parse("content://" + EmailContent.AUTHORITY +
|
|
|
|
"/integrityCheck");
|
|
|
|
ACCOUNT_BACKUP_URI =
|
|
|
|
Uri.parse("content://" + EmailContent.AUTHORITY + "/accountBackup");
|
|
|
|
FOLDER_STATUS_URI =
|
|
|
|
Uri.parse("content://" + EmailContent.AUTHORITY + "/status");
|
|
|
|
EMAIL_APP_MIME_TYPE = context.getString(R.string.application_mime_type);
|
|
|
|
|
2013-09-11 18:48:38 +00:00
|
|
|
final String uiNotificationAuthority =
|
|
|
|
EmailContent.EMAIL_PACKAGE_NAME + ".uinotifications";
|
|
|
|
UIPROVIDER_CONVERSATION_NOTIFIER =
|
|
|
|
Uri.parse("content://" + uiNotificationAuthority + "/uimessages");
|
|
|
|
UIPROVIDER_FOLDER_NOTIFIER =
|
|
|
|
Uri.parse("content://" + uiNotificationAuthority + "/uifolder");
|
|
|
|
UIPROVIDER_FOLDERLIST_NOTIFIER =
|
|
|
|
Uri.parse("content://" + uiNotificationAuthority + "/uifolders");
|
|
|
|
UIPROVIDER_ACCOUNT_NOTIFIER =
|
|
|
|
Uri.parse("content://" + uiNotificationAuthority + "/uiaccount");
|
|
|
|
// Not currently used
|
|
|
|
/* UIPROVIDER_SETTINGS_NOTIFIER =
|
|
|
|
Uri.parse("content://" + uiNotificationAuthority + "/uisettings");*/
|
|
|
|
UIPROVIDER_ATTACHMENT_NOTIFIER =
|
|
|
|
Uri.parse("content://" + uiNotificationAuthority + "/uiattachment");
|
|
|
|
UIPROVIDER_ATTACHMENTS_NOTIFIER =
|
|
|
|
Uri.parse("content://" + uiNotificationAuthority + "/uiattachments");
|
|
|
|
UIPROVIDER_ALL_ACCOUNTS_NOTIFIER =
|
|
|
|
Uri.parse("content://" + uiNotificationAuthority + "/uiaccts");
|
|
|
|
UIPROVIDER_MESSAGE_NOTIFIER =
|
|
|
|
Uri.parse("content://" + uiNotificationAuthority + "/uimessage");
|
|
|
|
UIPROVIDER_RECENT_FOLDERS_NOTIFIER =
|
|
|
|
Uri.parse("content://" + uiNotificationAuthority + "/uirecentfolders");
|
|
|
|
|
2013-08-20 16:58:17 +00:00
|
|
|
// All accounts
|
2013-08-20 21:54:42 +00:00
|
|
|
sURIMatcher.addURI(EmailContent.AUTHORITY, "account", ACCOUNT);
|
2013-08-20 16:58:17 +00:00
|
|
|
// A specific account
|
|
|
|
// insert into this URI causes a mailbox to be added to the account
|
2013-08-20 21:54:42 +00:00
|
|
|
sURIMatcher.addURI(EmailContent.AUTHORITY, "account/#", ACCOUNT_ID);
|
|
|
|
sURIMatcher.addURI(EmailContent.AUTHORITY, "accountCheck/#", ACCOUNT_CHECK);
|
2013-08-20 16:58:17 +00:00
|
|
|
|
|
|
|
// All mailboxes
|
2013-08-20 21:54:42 +00:00
|
|
|
sURIMatcher.addURI(EmailContent.AUTHORITY, "mailbox", MAILBOX);
|
2013-08-20 16:58:17 +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!
|
2013-08-20 21:54:42 +00:00
|
|
|
sURIMatcher.addURI(EmailContent.AUTHORITY, "mailbox/*", MAILBOX_ID);
|
|
|
|
sURIMatcher.addURI(EmailContent.AUTHORITY, "mailboxNotification/#",
|
|
|
|
MAILBOX_NOTIFICATION);
|
|
|
|
sURIMatcher.addURI(EmailContent.AUTHORITY, "mailboxMostRecentMessage/#",
|
2013-08-20 16:58:17 +00:00
|
|
|
MAILBOX_MOST_RECENT_MESSAGE);
|
2013-08-20 21:54:42 +00:00
|
|
|
sURIMatcher.addURI(EmailContent.AUTHORITY, "mailboxCount/#", MAILBOX_MESSAGE_COUNT);
|
2013-08-20 16:58:17 +00:00
|
|
|
|
|
|
|
// All messages
|
2013-08-20 21:54:42 +00:00
|
|
|
sURIMatcher.addURI(EmailContent.AUTHORITY, "message", MESSAGE);
|
2013-08-20 16:58:17 +00:00
|
|
|
// A specific message
|
|
|
|
// insert into this URI causes an attachment to be added to the message
|
2013-08-20 21:54:42 +00:00
|
|
|
sURIMatcher.addURI(EmailContent.AUTHORITY, "message/#", MESSAGE_ID);
|
2013-08-20 16:58:17 +00:00
|
|
|
|
|
|
|
// A specific attachment
|
2013-08-20 21:54:42 +00:00
|
|
|
sURIMatcher.addURI(EmailContent.AUTHORITY, "attachment", ATTACHMENT);
|
2013-08-20 16:58:17 +00:00
|
|
|
// A specific attachment (the header information)
|
2013-08-20 21:54:42 +00:00
|
|
|
sURIMatcher.addURI(EmailContent.AUTHORITY, "attachment/#", ATTACHMENT_ID);
|
2013-08-20 16:58:17 +00:00
|
|
|
// The attachments of a specific message (query only) (insert & delete TBD)
|
2013-08-20 21:54:42 +00:00
|
|
|
sURIMatcher.addURI(EmailContent.AUTHORITY, "attachment/message/#",
|
|
|
|
ATTACHMENTS_MESSAGE_ID);
|
|
|
|
sURIMatcher.addURI(EmailContent.AUTHORITY, "attachment/cachedFile",
|
2013-08-20 16:58:17 +00:00
|
|
|
ATTACHMENTS_CACHED_FILE_ACCESS);
|
|
|
|
|
|
|
|
// All mail bodies
|
2013-08-20 21:54:42 +00:00
|
|
|
sURIMatcher.addURI(EmailContent.AUTHORITY, "body", BODY);
|
2013-08-20 16:58:17 +00:00
|
|
|
// A specific mail body
|
2013-08-20 21:54:42 +00:00
|
|
|
sURIMatcher.addURI(EmailContent.AUTHORITY, "body/#", BODY_ID);
|
2014-05-08 20:07:54 +00:00
|
|
|
// A specific HTML body part, for openFile
|
|
|
|
sURIMatcher.addURI(EmailContent.AUTHORITY, "bodyHtml/#", BODY_HTML);
|
|
|
|
// A specific text body part, for openFile
|
|
|
|
sURIMatcher.addURI(EmailContent.AUTHORITY, "bodyText/#", BODY_TEXT);
|
2013-08-20 16:58:17 +00:00
|
|
|
|
|
|
|
// All hostauth records
|
2013-08-20 21:54:42 +00:00
|
|
|
sURIMatcher.addURI(EmailContent.AUTHORITY, "hostauth", HOSTAUTH);
|
2013-08-20 16:58:17 +00:00
|
|
|
// A specific hostauth
|
2013-08-20 21:54:42 +00:00
|
|
|
sURIMatcher.addURI(EmailContent.AUTHORITY, "hostauth/*", HOSTAUTH_ID);
|
2013-08-20 16:58:17 +00:00
|
|
|
|
2013-12-04 00:55:48 +00:00
|
|
|
// All credential records
|
|
|
|
sURIMatcher.addURI(EmailContent.AUTHORITY, "credential", CREDENTIAL);
|
|
|
|
// A specific credential
|
|
|
|
sURIMatcher.addURI(EmailContent.AUTHORITY, "credential/*", CREDENTIAL_ID);
|
|
|
|
|
2013-08-20 16:58:17 +00:00
|
|
|
/**
|
|
|
|
* THIS URI HAS SPECIAL SEMANTICS
|
|
|
|
* ITS USE IS INTENDED FOR THE UI TO MARK CHANGES THAT NEED TO BE SYNCED BACK
|
|
|
|
* TO A SERVER VIA A SYNC ADAPTER
|
|
|
|
*/
|
2013-08-20 21:54:42 +00:00
|
|
|
sURIMatcher.addURI(EmailContent.AUTHORITY, "syncedMessage/#", SYNCED_MESSAGE_ID);
|
|
|
|
sURIMatcher.addURI(EmailContent.AUTHORITY, "messageBySelection", MESSAGE_SELECTION);
|
2013-08-20 16:58:17 +00:00
|
|
|
|
2013-09-05 20:52:03 +00:00
|
|
|
sURIMatcher.addURI(EmailContent.AUTHORITY, MessageMove.PATH, MESSAGE_MOVE);
|
|
|
|
sURIMatcher.addURI(EmailContent.AUTHORITY, MessageStateChange.PATH,
|
|
|
|
MESSAGE_STATE_CHANGE);
|
2013-08-23 01:22:44 +00:00
|
|
|
|
2013-08-20 16:58:17 +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
|
2013-08-20 21:54:42 +00:00
|
|
|
sURIMatcher.addURI(EmailContent.AUTHORITY, "deletedMessage", DELETED_MESSAGE);
|
2013-08-20 16:58:17 +00:00
|
|
|
// A specific deleted message
|
2013-08-20 21:54:42 +00:00
|
|
|
sURIMatcher.addURI(EmailContent.AUTHORITY, "deletedMessage/#", DELETED_MESSAGE_ID);
|
2013-08-20 16:58:17 +00:00
|
|
|
|
|
|
|
// All updated messages
|
2013-08-20 21:54:42 +00:00
|
|
|
sURIMatcher.addURI(EmailContent.AUTHORITY, "updatedMessage", UPDATED_MESSAGE);
|
2013-08-20 16:58:17 +00:00
|
|
|
// A specific updated message
|
2013-08-20 21:54:42 +00:00
|
|
|
sURIMatcher.addURI(EmailContent.AUTHORITY, "updatedMessage/#", UPDATED_MESSAGE_ID);
|
2013-08-20 16:58:17 +00:00
|
|
|
|
2013-08-20 21:54:42 +00:00
|
|
|
sURIMatcher.addURI(EmailContent.AUTHORITY, "policy", POLICY);
|
|
|
|
sURIMatcher.addURI(EmailContent.AUTHORITY, "policy/#", POLICY_ID);
|
2013-08-20 16:58:17 +00:00
|
|
|
|
|
|
|
// All quick responses
|
2013-08-20 21:54:42 +00:00
|
|
|
sURIMatcher.addURI(EmailContent.AUTHORITY, "quickresponse", QUICK_RESPONSE);
|
2013-08-20 16:58:17 +00:00
|
|
|
// A specific quick response
|
2013-08-20 21:54:42 +00:00
|
|
|
sURIMatcher.addURI(EmailContent.AUTHORITY, "quickresponse/#", QUICK_RESPONSE_ID);
|
2013-08-20 16:58:17 +00:00
|
|
|
// All quick responses associated with a particular account id
|
2013-08-20 21:54:42 +00:00
|
|
|
sURIMatcher.addURI(EmailContent.AUTHORITY, "quickresponse/account/#",
|
2013-08-20 16:58:17 +00:00
|
|
|
QUICK_RESPONSE_ACCOUNT_ID);
|
|
|
|
|
2013-08-20 21:54:42 +00:00
|
|
|
sURIMatcher.addURI(EmailContent.AUTHORITY, "uifolders/#", UI_FOLDERS);
|
2013-09-20 21:30:57 +00:00
|
|
|
sURIMatcher.addURI(EmailContent.AUTHORITY, "uifullfolders/#", UI_FULL_FOLDERS);
|
2013-08-20 21:54:42 +00:00
|
|
|
sURIMatcher.addURI(EmailContent.AUTHORITY, "uiallfolders/#", UI_ALL_FOLDERS);
|
|
|
|
sURIMatcher.addURI(EmailContent.AUTHORITY, "uisubfolders/#", UI_SUBFOLDERS);
|
|
|
|
sURIMatcher.addURI(EmailContent.AUTHORITY, "uimessages/#", UI_MESSAGES);
|
|
|
|
sURIMatcher.addURI(EmailContent.AUTHORITY, "uimessage/#", UI_MESSAGE);
|
|
|
|
sURIMatcher.addURI(EmailContent.AUTHORITY, "uiundo", UI_UNDO);
|
|
|
|
sURIMatcher.addURI(EmailContent.AUTHORITY, QUERY_UIREFRESH + "/#", UI_FOLDER_REFRESH);
|
2013-08-20 16:58:17 +00:00
|
|
|
// We listen to everything trailing uifolder/ since there might be an appVersion
|
|
|
|
// as in Utils.appendVersionQueryParameter().
|
2013-08-20 21:54:42 +00:00
|
|
|
sURIMatcher.addURI(EmailContent.AUTHORITY, "uifolder/*", UI_FOLDER);
|
2014-05-01 19:45:09 +00:00
|
|
|
sURIMatcher.addURI(EmailContent.AUTHORITY, "uiinbox/#", UI_INBOX);
|
2013-08-20 21:54:42 +00:00
|
|
|
sURIMatcher.addURI(EmailContent.AUTHORITY, "uiaccount/#", UI_ACCOUNT);
|
|
|
|
sURIMatcher.addURI(EmailContent.AUTHORITY, "uiaccts", UI_ACCTS);
|
|
|
|
sURIMatcher.addURI(EmailContent.AUTHORITY, "uiattachments/#", UI_ATTACHMENTS);
|
|
|
|
sURIMatcher.addURI(EmailContent.AUTHORITY, "uiattachment/#", UI_ATTACHMENT);
|
2014-04-09 19:59:28 +00:00
|
|
|
sURIMatcher.addURI(EmailContent.AUTHORITY, "uiattachmentbycid/#/*", UI_ATTACHMENT_BY_CID);
|
2013-08-20 21:54:42 +00:00
|
|
|
sURIMatcher.addURI(EmailContent.AUTHORITY, "uisearch/#", UI_SEARCH);
|
|
|
|
sURIMatcher.addURI(EmailContent.AUTHORITY, "uiaccountdata/#", UI_ACCOUNT_DATA);
|
|
|
|
sURIMatcher.addURI(EmailContent.AUTHORITY, "uiloadmore/#", UI_FOLDER_LOAD_MORE);
|
|
|
|
sURIMatcher.addURI(EmailContent.AUTHORITY, "uiconversation/#", UI_CONVERSATION);
|
|
|
|
sURIMatcher.addURI(EmailContent.AUTHORITY, "uirecentfolders/#", UI_RECENT_FOLDERS);
|
|
|
|
sURIMatcher.addURI(EmailContent.AUTHORITY, "uidefaultrecentfolders/#",
|
2013-08-20 16:58:17 +00:00
|
|
|
UI_DEFAULT_RECENT_FOLDERS);
|
2013-08-20 21:54:42 +00:00
|
|
|
sURIMatcher.addURI(EmailContent.AUTHORITY, "pickTrashFolder/#",
|
|
|
|
ACCOUNT_PICK_TRASH_FOLDER);
|
|
|
|
sURIMatcher.addURI(EmailContent.AUTHORITY, "pickSentFolder/#",
|
|
|
|
ACCOUNT_PICK_SENT_FOLDER);
|
2014-04-10 18:52:45 +00:00
|
|
|
sURIMatcher.addURI(EmailContent.AUTHORITY, "uipurgefolder/#", UI_PURGE_FOLDER);
|
2013-08-20 16:58:17 +00:00
|
|
|
}
|
|
|
|
}
|
2009-05-26 23:40:34 +00:00
|
|
|
|
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.
|
|
|
|
*/
|
2013-08-20 21:54:42 +00:00
|
|
|
public void checkDatabases() {
|
|
|
|
synchronized (sDatabaseLock) {
|
|
|
|
// 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
|
|
|
|
final File databaseFile = getContext().getDatabasePath(DATABASE_NAME);
|
|
|
|
final File bodyFile = getContext().getDatabasePath(BODY_DATABASE_NAME);
|
|
|
|
|
|
|
|
// TODO Make sure attachments are deleted
|
|
|
|
if (databaseFile.exists() && !bodyFile.exists()) {
|
|
|
|
LogUtils.w(TAG, "Deleting orphaned EmailProvider database...");
|
|
|
|
getContext().deleteDatabase(DATABASE_NAME);
|
|
|
|
} else if (bodyFile.exists() && !databaseFile.exists()) {
|
|
|
|
LogUtils.w(TAG, "Deleting orphaned EmailProviderBody database...");
|
|
|
|
getContext().deleteDatabase(BODY_DATABASE_NAME);
|
|
|
|
}
|
2009-11-19 01:11:33 +00:00
|
|
|
}
|
|
|
|
}
|
2013-08-20 21:54:42 +00:00
|
|
|
|
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) {
|
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:
|
2013-12-11 19:58:58 +00:00
|
|
|
case CREDENTIAL_ID:
|
2011-04-28 00:12:06 +00:00
|
|
|
case POLICY_ID:
|
2013-02-06 23:21:15 +00:00
|
|
|
return new MatrixCursorWithCachedColumns(projection, 0);
|
2010-12-30 22:55:27 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
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
|
|
|
|
2013-08-14 18:05:19 +00:00
|
|
|
String tableName = TABLE_NAMES.valueAt(table);
|
2010-10-27 23:50:54 +00:00
|
|
|
|
2009-11-19 01:11:33 +00:00
|
|
|
try {
|
|
|
|
switch (match) {
|
2014-04-09 19:59:28 +00:00
|
|
|
// First, dispatch queries from UnifiedEmail
|
2012-06-28 17:40:46 +00:00
|
|
|
case UI_SEARCH:
|
2012-09-05 19:32:49 +00:00
|
|
|
c = uiSearch(uri, projection);
|
|
|
|
return c;
|
2012-06-28 17:40:46 +00:00
|
|
|
case UI_ACCTS:
|
2014-06-20 07:09:07 +00:00
|
|
|
final String suppressParam =
|
|
|
|
uri.getQueryParameter(EmailContent.SUPPRESS_COMBINED_ACCOUNT_PARAM);
|
|
|
|
final boolean suppressCombined =
|
|
|
|
suppressParam != null && Boolean.parseBoolean(suppressParam);
|
|
|
|
c = uiAccounts(projection, suppressCombined);
|
2012-06-28 17:40:46 +00:00
|
|
|
return c;
|
|
|
|
case UI_UNDO:
|
|
|
|
return uiUndo(projection);
|
|
|
|
case UI_SUBFOLDERS:
|
|
|
|
case UI_MESSAGES:
|
|
|
|
case UI_MESSAGE:
|
|
|
|
case UI_FOLDER:
|
2014-05-01 19:45:09 +00:00
|
|
|
case UI_INBOX:
|
2012-06-28 17:40:46 +00:00
|
|
|
case UI_ACCOUNT:
|
|
|
|
case UI_ATTACHMENT:
|
|
|
|
case UI_ATTACHMENTS:
|
2014-04-09 19:59:28 +00:00
|
|
|
case UI_ATTACHMENT_BY_CID:
|
2012-06-28 17:40:46 +00:00
|
|
|
case UI_CONVERSATION:
|
|
|
|
case UI_RECENT_FOLDERS:
|
2013-09-20 21:30:57 +00:00
|
|
|
case UI_FULL_FOLDERS:
|
2012-06-28 17:40:46 +00:00
|
|
|
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");
|
|
|
|
}
|
2012-12-11 18:37:35 +00:00
|
|
|
|
|
|
|
final String seenParam = uri.getQueryParameter(UIProvider.SEEN_QUERY_PARAMETER);
|
2013-09-10 23:18:22 +00:00
|
|
|
final boolean unseenOnly =
|
|
|
|
seenParam != null && Boolean.FALSE.toString().equals(seenParam);
|
2012-12-11 18:37:35 +00:00
|
|
|
|
|
|
|
c = uiQuery(match, uri, projection, unseenOnly);
|
2012-06-28 17:40:46 +00:00
|
|
|
return c;
|
|
|
|
case UI_FOLDERS:
|
|
|
|
c = uiFolders(uri, projection);
|
|
|
|
return c;
|
|
|
|
case UI_FOLDER_LOAD_MORE:
|
2013-04-12 00:11:34 +00:00
|
|
|
c = uiFolderLoadMore(getMailbox(uri));
|
2012-06-28 17:40:46 +00:00
|
|
|
return c;
|
|
|
|
case UI_FOLDER_REFRESH:
|
2013-04-12 00:11:34 +00:00
|
|
|
c = uiFolderRefresh(getMailbox(uri), 0);
|
2012-06-28 17:40:46 +00:00
|
|
|
return c;
|
|
|
|
case MAILBOX_NOTIFICATION:
|
|
|
|
c = notificationQuery(uri);
|
|
|
|
return c;
|
|
|
|
case MAILBOX_MOST_RECENT_MESSAGE:
|
|
|
|
c = mostRecentMessageQuery(uri);
|
|
|
|
return c;
|
2013-04-05 01:30:39 +00:00
|
|
|
case MAILBOX_MESSAGE_COUNT:
|
|
|
|
c = getMailboxMessageCount(uri);
|
|
|
|
return c;
|
2013-09-05 20:52:03 +00:00
|
|
|
case MESSAGE_MOVE:
|
|
|
|
return db.query(MessageMove.TABLE_NAME, projection, selection, selectionArgs,
|
|
|
|
null, null, sortOrder, limit);
|
|
|
|
case MESSAGE_STATE_CHANGE:
|
|
|
|
return db.query(MessageStateChange.TABLE_NAME, projection, selection,
|
|
|
|
selectionArgs, null, null, sortOrder, limit);
|
2009-11-19 01:11:33 +00:00
|
|
|
case MESSAGE:
|
|
|
|
case UPDATED_MESSAGE:
|
|
|
|
case DELETED_MESSAGE:
|
|
|
|
case ATTACHMENT:
|
|
|
|
case MAILBOX:
|
|
|
|
case ACCOUNT:
|
|
|
|
case HOSTAUTH:
|
2013-12-11 19:58:58 +00:00
|
|
|
case CREDENTIAL:
|
2011-04-28 00:12:06 +00:00
|
|
|
case POLICY:
|
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;
|
2013-08-14 18:05:19 +00:00
|
|
|
case QUICK_RESPONSE:
|
|
|
|
c = uiQuickResponse(projection);
|
|
|
|
break;
|
2014-04-15 18:27:23 +00:00
|
|
|
case BODY:
|
|
|
|
case BODY_ID: {
|
|
|
|
final ProjectionMap map = new ProjectionMap.Builder()
|
|
|
|
.addAll(projection)
|
|
|
|
.build();
|
2014-05-08 20:07:54 +00:00
|
|
|
if (map.containsKey(BodyColumns.HTML_CONTENT) ||
|
|
|
|
map.containsKey(BodyColumns.TEXT_CONTENT)) {
|
|
|
|
throw new IllegalArgumentException(
|
|
|
|
"Body content cannot be returned in the cursor");
|
|
|
|
}
|
|
|
|
|
2014-04-15 18:27:23 +00:00
|
|
|
final ContentValues cv = new ContentValues(2);
|
2014-05-08 20:07:54 +00:00
|
|
|
cv.put(BodyColumns.HTML_CONTENT_URI, "@" + uriWithColumn("bodyHtml",
|
|
|
|
BodyColumns.MESSAGE_KEY));
|
|
|
|
cv.put(BodyColumns.TEXT_CONTENT_URI, "@" + uriWithColumn("bodyText",
|
|
|
|
BodyColumns.MESSAGE_KEY));
|
|
|
|
|
2014-04-15 18:27:23 +00:00
|
|
|
final StringBuilder sb = genSelect(map, projection, cv);
|
|
|
|
sb.append(" FROM ").append(Body.TABLE_NAME);
|
|
|
|
if (match == BODY_ID) {
|
|
|
|
id = uri.getPathSegments().get(1);
|
|
|
|
sb.append(" WHERE ").append(whereWithId(id, selection));
|
|
|
|
} else if (!TextUtils.isEmpty(selection)) {
|
|
|
|
sb.append(" WHERE ").append(selection);
|
|
|
|
}
|
|
|
|
if (!TextUtils.isEmpty(sortOrder)) {
|
|
|
|
sb.append(" ORDER BY ").append(sortOrder);
|
|
|
|
}
|
|
|
|
if (!TextUtils.isEmpty(limit)) {
|
|
|
|
sb.append(" LIMIT ").append(limit);
|
|
|
|
}
|
|
|
|
c = db.rawQuery(sb.toString(), selectionArgs);
|
|
|
|
break;
|
|
|
|
}
|
2009-11-19 01:11:33 +00:00
|
|
|
case MESSAGE_ID:
|
|
|
|
case DELETED_MESSAGE_ID:
|
|
|
|
case UPDATED_MESSAGE_ID:
|
|
|
|
case ATTACHMENT_ID:
|
|
|
|
case MAILBOX_ID:
|
|
|
|
case ACCOUNT_ID:
|
|
|
|
case HOSTAUTH_ID:
|
2013-12-11 19:58:58 +00:00
|
|
|
case CREDENTIAL_ID:
|
2011-04-28 00:12:06 +00:00
|
|
|
case POLICY_ID:
|
2009-11-19 01:11:33 +00:00
|
|
|
id = uri.getPathSegments().get(1);
|
2013-04-18 23:56:03 +00:00
|
|
|
c = db.query(tableName, projection, whereWithId(id, selection),
|
|
|
|
selectionArgs, null, null, sortOrder, limit);
|
2009-11-19 01:11:33 +00:00
|
|
|
break;
|
2013-08-14 18:05:19 +00:00
|
|
|
case QUICK_RESPONSE_ID:
|
|
|
|
id = uri.getPathSegments().get(1);
|
|
|
|
c = uiQuickResponseId(projection, id);
|
|
|
|
break;
|
2009-11-19 01:11:33 +00:00
|
|
|
case ATTACHMENTS_MESSAGE_ID:
|
|
|
|
// All attachments for the given message
|
|
|
|
id = uri.getPathSegments().get(2);
|
|
|
|
c = db.query(Attachment.TABLE_NAME, projection,
|
2014-04-11 21:42:28 +00:00
|
|
|
whereWith(AttachmentColumns.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);
|
2013-08-14 18:05:19 +00:00
|
|
|
c = uiQuickResponseAccount(projection, id);
|
2011-06-01 17:09:26 +00:00
|
|
|
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 {
|
2011-07-27 23:48:35 +00:00
|
|
|
if (c == null) {
|
|
|
|
// This should never happen, but let's be sure to log it...
|
2013-02-15 19:42:22 +00:00
|
|
|
// TODO: There are actually cases where c == null is expected, for example
|
|
|
|
// UI_FOLDER_LOAD_MORE.
|
|
|
|
// Demoting this to a warning for now until we figure out what to do with it.
|
2014-01-09 19:22:45 +00:00
|
|
|
LogUtils.w(TAG, "Query returning null for uri: %s selection: %s", uri, selection);
|
2011-07-27 23:48:35 +00:00
|
|
|
}
|
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;
|
|
|
|
}
|
|
|
|
|
2012-12-11 18:37:35 +00:00
|
|
|
private static String whereWithId(String id, String selection) {
|
2009-05-26 23:40:34 +00:00
|
|
|
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
|
|
|
|
*/
|
2012-12-11 18:37:35 +00:00
|
|
|
private static 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
|
|
|
}
|
2014-04-09 21:15:58 +00:00
|
|
|
return where + " AND (" + selection + ")";
|
2009-05-26 23:40:34 +00:00
|
|
|
}
|
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,
|
2014-04-11 21:42:28 +00:00
|
|
|
HostAuthColumns._ID + "=?", new String[] {Long.toString(id)}, null, null, null);
|
2011-05-06 19:07:39 +00:00
|
|
|
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;
|
2013-05-26 04:32:32 +00:00
|
|
|
LogUtils.d(TAG, "fromDatabase accounts: " + c.getCount());
|
2012-06-28 17:40:46 +00:00
|
|
|
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 {
|
|
|
|
toDatabase.endTransaction();
|
|
|
|
}
|
2012-06-28 17:40:46 +00:00
|
|
|
} catch (SQLiteException ex) {
|
2013-05-26 04:32:32 +00:00
|
|
|
LogUtils.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 {
|
|
|
|
fromDatabase.endTransaction();
|
2011-05-06 19:07:39 +00:00
|
|
|
}
|
|
|
|
return copyCount;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Backup account data, returning the number of accounts backed up
|
|
|
|
*/
|
2014-05-14 18:16:56 +00:00
|
|
|
private static int backupAccounts(final Context context, final SQLiteDatabase db) {
|
|
|
|
final AccountManager am = AccountManager.get(context);
|
|
|
|
final Cursor accountCursor = db.query(Account.TABLE_NAME, Account.CONTENT_PROJECTION,
|
|
|
|
null, null, null, null, null);
|
|
|
|
int updatedCount = 0;
|
2011-05-06 19:07:39 +00:00
|
|
|
try {
|
2014-05-14 18:16:56 +00:00
|
|
|
while (accountCursor.moveToNext()) {
|
|
|
|
final Account account = new Account();
|
|
|
|
account.restore(accountCursor);
|
|
|
|
EmailServiceInfo serviceInfo =
|
|
|
|
EmailServiceUtils.getServiceInfo(context, account.getProtocol(context));
|
|
|
|
if (serviceInfo == null) {
|
|
|
|
LogUtils.d(LogUtils.TAG, "Could not find service info for account");
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
final String jsonString = account.toJsonString(context);
|
|
|
|
final android.accounts.Account amAccount =
|
|
|
|
account.getAccountManagerAccount(serviceInfo.accountType);
|
|
|
|
am.setUserData(amAccount, ACCOUNT_MANAGER_JSON_TAG, jsonString);
|
|
|
|
updatedCount++;
|
2011-06-21 01:10:10 +00:00
|
|
|
}
|
2011-05-06 19:07:39 +00:00
|
|
|
} finally {
|
2014-05-14 18:16:56 +00:00
|
|
|
accountCursor.close();
|
2011-05-06 19:07:39 +00:00
|
|
|
}
|
2014-05-14 18:16:56 +00:00
|
|
|
return updatedCount;
|
2011-05-06 19:07:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Restore account data, returning the number of accounts restored
|
|
|
|
*/
|
2014-05-14 18:16:56 +00:00
|
|
|
private static int restoreAccounts(final Context context) {
|
|
|
|
final Collection<EmailServiceInfo> infos = EmailServiceUtils.getServiceInfoList(context);
|
|
|
|
// Find all possible account types
|
|
|
|
final Set<String> accountTypes = new HashSet<String>(3);
|
|
|
|
for (final EmailServiceInfo info : infos) {
|
|
|
|
accountTypes.add(info.accountType);
|
2011-06-21 01:10:10 +00:00
|
|
|
}
|
2014-05-14 18:16:56 +00:00
|
|
|
// Find all accounts we own
|
|
|
|
final List<android.accounts.Account> amAccounts = new ArrayList<android.accounts.Account>();
|
|
|
|
final AccountManager am = AccountManager.get(context);
|
|
|
|
for (final String accountType : accountTypes) {
|
|
|
|
amAccounts.addAll(Arrays.asList(am.getAccountsByType(accountType)));
|
|
|
|
}
|
|
|
|
// Try to restore them from saved JSON
|
|
|
|
int restoredCount = 0;
|
|
|
|
for (final android.accounts.Account amAccount : amAccounts) {
|
|
|
|
final String jsonString = am.getUserData(amAccount, ACCOUNT_MANAGER_JSON_TAG);
|
|
|
|
if (TextUtils.isEmpty(jsonString)) {
|
|
|
|
continue;
|
2011-06-21 01:10:10 +00:00
|
|
|
}
|
2014-05-14 18:16:56 +00:00
|
|
|
final Account account = Account.fromJsonString(jsonString);
|
|
|
|
if (account != null) {
|
|
|
|
AccountSettingsUtils.commitSettings(context, account);
|
|
|
|
restoredCount++;
|
2011-05-06 19:07:39 +00:00
|
|
|
}
|
|
|
|
}
|
2014-05-14 18:16:56 +00:00
|
|
|
return restoredCount;
|
2011-05-06 19:07:39 +00:00
|
|
|
}
|
|
|
|
|
2013-09-05 20:52:03 +00:00
|
|
|
private static final String MESSAGE_CHANGE_LOG_TABLE_INSERT_PREFIX = "insert into %s ("
|
|
|
|
+ MessageChangeLogTable.MESSAGE_KEY + "," + MessageChangeLogTable.SERVER_ID + ","
|
|
|
|
+ MessageChangeLogTable.ACCOUNT_KEY + "," + MessageChangeLogTable.STATUS + ",";
|
|
|
|
|
|
|
|
private static final String MESSAGE_CHANGE_LOG_TABLE_VALUES_PREFIX = ") values (%s, "
|
2014-04-11 21:42:28 +00:00
|
|
|
+ "(select " + MessageColumns.SERVER_ID + " from " +
|
|
|
|
Message.TABLE_NAME + " where _id=%s),"
|
|
|
|
+ "(select " + MessageColumns.ACCOUNT_KEY + " from " +
|
|
|
|
Message.TABLE_NAME + " where _id=%s),"
|
2013-09-05 20:52:03 +00:00
|
|
|
+ MessageMove.STATUS_NONE_STRING + ",";
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Formatting string to generate the SQL statement for inserting into MessageMove.
|
|
|
|
* The formatting parameters are:
|
|
|
|
* table name, message id x 4, destination folder id, message id, destination folder id.
|
|
|
|
* Duplications are needed for sub-selects.
|
|
|
|
*/
|
|
|
|
private static final String MESSAGE_MOVE_INSERT = MESSAGE_CHANGE_LOG_TABLE_INSERT_PREFIX
|
|
|
|
+ MessageMove.SRC_FOLDER_KEY + "," + MessageMove.DST_FOLDER_KEY + ","
|
|
|
|
+ MessageMove.SRC_FOLDER_SERVER_ID + "," + MessageMove.DST_FOLDER_SERVER_ID
|
|
|
|
+ MESSAGE_CHANGE_LOG_TABLE_VALUES_PREFIX
|
2014-04-11 21:42:28 +00:00
|
|
|
+ "(select " + MessageColumns.MAILBOX_KEY +
|
|
|
|
" from " + Message.TABLE_NAME + " where _id=%s)," + "%d,"
|
2013-09-05 20:52:03 +00:00
|
|
|
+ "(select " + Mailbox.SERVER_ID + " from " + Mailbox.TABLE_NAME + " where _id=(select "
|
2014-04-11 21:42:28 +00:00
|
|
|
+ MessageColumns.MAILBOX_KEY + " from " + Message.TABLE_NAME + " where _id=%s)),"
|
2013-09-05 20:52:03 +00:00
|
|
|
+ "(select " + Mailbox.SERVER_ID + " from " + Mailbox.TABLE_NAME + " where _id=%d))";
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Insert a row into the MessageMove table when that message is moved.
|
|
|
|
* @param db The {@link SQLiteDatabase}.
|
|
|
|
* @param messageId The id of the message being moved.
|
|
|
|
* @param dstFolderKey The folder to which the message is being moved.
|
|
|
|
*/
|
|
|
|
private void addToMessageMove(final SQLiteDatabase db, final String messageId,
|
|
|
|
final long dstFolderKey) {
|
|
|
|
db.execSQL(String.format(Locale.US, MESSAGE_MOVE_INSERT, MessageMove.TABLE_NAME,
|
|
|
|
messageId, messageId, messageId, messageId, dstFolderKey, messageId, dstFolderKey));
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Formatting string to generate the SQL statement for inserting into MessageStateChange.
|
|
|
|
* The formatting parameters are:
|
|
|
|
* table name, message id x 4, new flag read, message id, new flag favorite.
|
|
|
|
* Duplications are needed for sub-selects.
|
|
|
|
*/
|
|
|
|
private static final String MESSAGE_STATE_CHANGE_INSERT = MESSAGE_CHANGE_LOG_TABLE_INSERT_PREFIX
|
|
|
|
+ MessageStateChange.OLD_FLAG_READ + "," + MessageStateChange.NEW_FLAG_READ + ","
|
|
|
|
+ MessageStateChange.OLD_FLAG_FAVORITE + "," + MessageStateChange.NEW_FLAG_FAVORITE
|
|
|
|
+ MESSAGE_CHANGE_LOG_TABLE_VALUES_PREFIX
|
2014-04-11 21:42:28 +00:00
|
|
|
+ "(select " + MessageColumns.FLAG_READ +
|
|
|
|
" from " + Message.TABLE_NAME + " where _id=%s)," + "%d,"
|
|
|
|
+ "(select " + MessageColumns.FLAG_FAVORITE +
|
|
|
|
" from " + Message.TABLE_NAME + " where _id=%s)," + "%d)";
|
2013-09-05 20:52:03 +00:00
|
|
|
|
|
|
|
private void addToMessageStateChange(final SQLiteDatabase db, final String messageId,
|
|
|
|
final int newFlagRead, final int newFlagFavorite) {
|
|
|
|
db.execSQL(String.format(Locale.US, MESSAGE_STATE_CHANGE_INSERT,
|
|
|
|
MessageStateChange.TABLE_NAME, messageId, messageId, messageId, messageId,
|
|
|
|
newFlagRead, messageId, newFlagFavorite));
|
|
|
|
}
|
|
|
|
|
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";
|
|
|
|
|
2013-09-12 22:23:54 +00:00
|
|
|
|
|
|
|
// Query to get the protocol for a message. Temporary to switch between new and old upsync
|
|
|
|
// behavior; should go away when IMAP gets converted.
|
2013-09-25 23:33:55 +00:00
|
|
|
private static final String GET_MESSAGE_DETAILS = "SELECT"
|
|
|
|
+ " h." + HostAuthColumns.PROTOCOL + ","
|
2014-04-11 21:42:28 +00:00
|
|
|
+ " m." + MessageColumns.MAILBOX_KEY + ","
|
|
|
|
+ " a." + AccountColumns._ID
|
2013-09-25 23:33:55 +00:00
|
|
|
+ " FROM " + Message.TABLE_NAME + " AS m"
|
|
|
|
+ " INNER JOIN " + Account.TABLE_NAME + " AS a"
|
2014-04-11 21:42:28 +00:00
|
|
|
+ " ON m." + MessageColumns.ACCOUNT_KEY + "=a." + AccountColumns._ID
|
2013-09-25 23:33:55 +00:00
|
|
|
+ " INNER JOIN " + HostAuth.TABLE_NAME + " AS h"
|
2014-04-11 21:42:28 +00:00
|
|
|
+ " ON a." + AccountColumns.HOST_AUTH_KEY_RECV + "=h." + HostAuthColumns._ID
|
|
|
|
+ " WHERE m." + MessageColumns._ID + "=?";
|
2013-09-27 00:20:11 +00:00
|
|
|
private static final int INDEX_PROTOCOL = 0;
|
|
|
|
private static final int INDEX_MAILBOX_KEY = 1;
|
|
|
|
private static final int INDEX_ACCOUNT_KEY = 2;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Query to get the protocol and email address for an account. Note that this uses
|
|
|
|
* {@link #INDEX_PROTOCOL} and {@link #INDEX_EMAIL_ADDRESS} for its columns.
|
|
|
|
*/
|
|
|
|
private static final String GET_ACCOUNT_DETAILS = "SELECT"
|
|
|
|
+ " h." + HostAuthColumns.PROTOCOL + ","
|
2013-10-04 18:37:11 +00:00
|
|
|
+ " a." + AccountColumns.EMAIL_ADDRESS + ","
|
|
|
|
+ " a." + AccountColumns.SYNC_KEY
|
2013-09-27 00:20:11 +00:00
|
|
|
+ " FROM " + Account.TABLE_NAME + " AS a"
|
|
|
|
+ " INNER JOIN " + HostAuth.TABLE_NAME + " AS h"
|
2014-04-11 21:42:28 +00:00
|
|
|
+ " ON a." + AccountColumns.HOST_AUTH_KEY_RECV + "=h." + HostAuthColumns._ID
|
|
|
|
+ " WHERE a." + AccountColumns._ID + "=?";
|
2013-09-27 00:20:11 +00:00
|
|
|
private static final int INDEX_EMAIL_ADDRESS = 1;
|
2013-10-04 18:37:11 +00:00
|
|
|
private static final int INDEX_SYNC_KEY = 2;
|
2013-09-27 00:20:11 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Restart push if we need it (currently only for Exchange accounts).
|
|
|
|
* @param context A {@link Context}.
|
|
|
|
* @param db The {@link SQLiteDatabase}.
|
|
|
|
* @param id The id of the thing we're looking for.
|
|
|
|
* @return Whether or not we sent a request to restart the push.
|
|
|
|
*/
|
|
|
|
private static boolean restartPush(final Context context, final SQLiteDatabase db,
|
|
|
|
final String id) {
|
|
|
|
final Cursor c = db.rawQuery(GET_ACCOUNT_DETAILS, new String[] {id});
|
|
|
|
if (c != null) {
|
|
|
|
try {
|
|
|
|
if (c.moveToFirst()) {
|
|
|
|
final String protocol = c.getString(INDEX_PROTOCOL);
|
2013-10-04 18:37:11 +00:00
|
|
|
// Only restart push for EAS accounts that have completed initial sync.
|
|
|
|
if (context.getString(R.string.protocol_eas).equals(protocol) &&
|
|
|
|
!EmailContent.isInitialSyncKey(c.getString(INDEX_SYNC_KEY))) {
|
|
|
|
final String emailAddress = c.getString(INDEX_EMAIL_ADDRESS);
|
2013-09-27 00:20:11 +00:00
|
|
|
final android.accounts.Account account =
|
|
|
|
getAccountManagerAccount(context, emailAddress, protocol);
|
2014-05-05 20:59:39 +00:00
|
|
|
if (account != null) {
|
|
|
|
restartPush(account);
|
|
|
|
return true;
|
|
|
|
}
|
2013-09-27 00:20:11 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} finally {
|
|
|
|
c.close();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Restart push if a mailbox's settings change in a way that requires it.
|
|
|
|
* @param context A {@link Context}.
|
|
|
|
* @param db The {@link SQLiteDatabase}.
|
|
|
|
* @param values The {@link ContentValues} that were updated for the mailbox.
|
|
|
|
* @param accountId The id of the account for this mailbox.
|
|
|
|
* @return Whether or not the push was restarted.
|
|
|
|
*/
|
|
|
|
private static boolean restartPushForMailbox(final Context context, final SQLiteDatabase db,
|
|
|
|
final ContentValues values, final String accountId) {
|
|
|
|
if (values.containsKey(MailboxColumns.SYNC_LOOKBACK) ||
|
|
|
|
values.containsKey(MailboxColumns.SYNC_INTERVAL)) {
|
|
|
|
return restartPush(context, db, accountId);
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Restart push if an account's settings change in a way that requires it.
|
|
|
|
* @param context A {@link Context}.
|
|
|
|
* @param db The {@link SQLiteDatabase}.
|
|
|
|
* @param values The {@link ContentValues} that were updated for the account.
|
|
|
|
* @param accountId The id of the account.
|
|
|
|
* @return Whether or not the push was restarted.
|
|
|
|
*/
|
|
|
|
private static boolean restartPushForAccount(final Context context, final SQLiteDatabase db,
|
|
|
|
final ContentValues values, final String accountId) {
|
|
|
|
if (values.containsKey(AccountColumns.SYNC_LOOKBACK) ||
|
|
|
|
values.containsKey(AccountColumns.SYNC_INTERVAL)) {
|
|
|
|
return restartPush(context, db, accountId);
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
2013-09-12 22:23:54 +00:00
|
|
|
|
2009-05-26 23:40:34 +00:00
|
|
|
@Override
|
|
|
|
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
|
2013-10-11 15:42:48 +00:00
|
|
|
LogUtils.d(TAG, "Update: " + uri);
|
2010-09-10 20:06:07 +00:00
|
|
|
// Handle this special case the fastest possible way
|
2014-05-14 18:32:20 +00:00
|
|
|
if (INTEGRITY_CHECK_URI.equals(uri)) {
|
2010-09-10 20:06:07 +00:00
|
|
|
checkDatabases();
|
|
|
|
return 0;
|
2014-05-14 18:32:20 +00:00
|
|
|
} else if (ACCOUNT_BACKUP_URI.equals(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;
|
|
|
|
|
2013-12-17 19:31:28 +00:00
|
|
|
final int match = findMatch(uri, "update");
|
|
|
|
final Context context = getContext();
|
2009-08-06 00:31:50 +00:00
|
|
|
// See the comment at delete(), above
|
2013-12-17 19:31:28 +00:00
|
|
|
final SQLiteDatabase db = getDatabase(context);
|
|
|
|
final 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
|
|
|
|
2013-12-17 19:31:28 +00:00
|
|
|
final String tableName = TABLE_NAMES.valueAt(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 {
|
|
|
|
switch (match) {
|
2012-06-29 16:42:05 +00:00
|
|
|
case ACCOUNT_PICK_TRASH_FOLDER:
|
|
|
|
return pickTrashFolder(uri);
|
2012-07-18 19:52:40 +00:00
|
|
|
case ACCOUNT_PICK_SENT_FOLDER:
|
|
|
|
return pickSentFolder(uri);
|
2012-06-28 17:40:46 +00:00
|
|
|
case UI_FOLDER:
|
2012-12-11 18:37:35 +00:00
|
|
|
return uiUpdateFolder(context, uri, values);
|
2012-06-28 17:40:46 +00:00
|
|
|
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_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;
|
2012-07-20 17:17:02 +00:00
|
|
|
case MESSAGE_SELECTION:
|
2012-06-29 16:42:05 +00:00
|
|
|
Cursor findCursor = db.query(tableName, Message.ID_COLUMN_PROJECTION, selection,
|
|
|
|
selectionArgs, null, null, null);
|
|
|
|
try {
|
|
|
|
if (findCursor.moveToFirst()) {
|
|
|
|
return update(ContentUris.withAppendedId(
|
2012-07-20 17:17:02 +00:00
|
|
|
Message.CONTENT_URI,
|
2012-06-29 16:42:05 +00:00
|
|
|
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:
|
2009-11-19 01:11:33 +00:00
|
|
|
case ATTACHMENT_ID:
|
|
|
|
case MAILBOX_ID:
|
|
|
|
case ACCOUNT_ID:
|
|
|
|
case HOSTAUTH_ID:
|
2013-12-11 19:58:58 +00:00
|
|
|
case CREDENTIAL_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);
|
2013-08-06 23:09:00 +00:00
|
|
|
if (match == SYNCED_MESSAGE_ID) {
|
2013-09-12 22:23:54 +00:00
|
|
|
// TODO: Migrate IMAP to use MessageMove/MessageStateChange as well.
|
|
|
|
boolean isEas = false;
|
2013-09-25 23:33:55 +00:00
|
|
|
long mailboxId = -1;
|
|
|
|
long accountId = -1;
|
|
|
|
final Cursor c = db.rawQuery(GET_MESSAGE_DETAILS, new String[] {id});
|
2013-09-12 22:23:54 +00:00
|
|
|
if (c != null) {
|
|
|
|
try {
|
|
|
|
if (c.moveToFirst()) {
|
2013-09-25 23:33:55 +00:00
|
|
|
final String protocol = c.getString(INDEX_PROTOCOL);
|
2013-09-12 22:23:54 +00:00
|
|
|
isEas = context.getString(R.string.protocol_eas)
|
|
|
|
.equals(protocol);
|
2013-09-25 23:33:55 +00:00
|
|
|
mailboxId = c.getLong(INDEX_MAILBOX_KEY);
|
|
|
|
accountId = c.getLong(INDEX_ACCOUNT_KEY);
|
2013-09-12 22:23:54 +00:00
|
|
|
}
|
|
|
|
} finally {
|
|
|
|
c.close();
|
|
|
|
}
|
2013-09-05 20:52:03 +00:00
|
|
|
}
|
2013-09-12 22:23:54 +00:00
|
|
|
|
|
|
|
if (isEas) {
|
|
|
|
// EAS uses the new upsync classes.
|
|
|
|
Long dstFolderId = values.getAsLong(MessageColumns.MAILBOX_KEY);
|
|
|
|
if (dstFolderId != null) {
|
|
|
|
addToMessageMove(db, id, dstFolderId);
|
|
|
|
}
|
|
|
|
Integer flagRead = values.getAsInteger(MessageColumns.FLAG_READ);
|
|
|
|
Integer flagFavorite = values.getAsInteger(MessageColumns.FLAG_FAVORITE);
|
|
|
|
int flagReadValue = (flagRead != null) ?
|
|
|
|
flagRead : MessageStateChange.VALUE_UNCHANGED;
|
|
|
|
int flagFavoriteValue = (flagFavorite != null) ?
|
|
|
|
flagFavorite : MessageStateChange.VALUE_UNCHANGED;
|
|
|
|
if (flagRead != null || flagFavorite != null) {
|
|
|
|
addToMessageStateChange(db, id, flagReadValue, flagFavoriteValue);
|
|
|
|
}
|
2013-09-25 23:33:55 +00:00
|
|
|
|
|
|
|
// Request a sync for the messages mailbox so the update will upsync.
|
|
|
|
// This is normally done with ContentResolver.notifyUpdate() but doesn't
|
|
|
|
// work for Exchange because the Sync Adapter is declared as
|
|
|
|
// android:supportsUploading="false". Changing it to true is not trivial
|
|
|
|
// because that would require us to protect all calls to notifyUpdate()
|
|
|
|
// with syncToServer=false except in cases where we actually want to
|
|
|
|
// upsync.
|
|
|
|
// TODO: Look into making Exchange Sync Adapter supportsUploading=true
|
|
|
|
// Since we can't use the Sync Manager "delayed-sync" feature which
|
|
|
|
// applies only to UPLOAD syncs, we need to do this ourselves. The
|
|
|
|
// purpose of this is not to spam syncs when making frequent
|
|
|
|
// modifications.
|
|
|
|
final Handler handler = getDelayedSyncHandler();
|
2013-09-30 18:51:14 +00:00
|
|
|
final android.accounts.Account amAccount =
|
|
|
|
getAccountManagerAccount(accountId);
|
|
|
|
if (amAccount != null) {
|
|
|
|
final SyncRequestMessage request = new SyncRequestMessage(
|
|
|
|
uri.getAuthority(), amAccount, mailboxId);
|
|
|
|
synchronized (mDelayedSyncRequests) {
|
|
|
|
if (!mDelayedSyncRequests.contains(request)) {
|
|
|
|
mDelayedSyncRequests.add(request);
|
|
|
|
final android.os.Message message =
|
|
|
|
handler.obtainMessage(0, request);
|
|
|
|
handler.sendMessageDelayed(message, SYNC_DELAY_MILLIS);
|
|
|
|
}
|
2013-09-25 23:33:55 +00:00
|
|
|
}
|
2013-09-30 18:51:14 +00:00
|
|
|
} else {
|
|
|
|
LogUtils.d(TAG,
|
|
|
|
"Attempted to start delayed sync for invalid account %d",
|
|
|
|
accountId);
|
2013-09-25 23:33:55 +00:00
|
|
|
}
|
2013-09-12 22:23:54 +00:00
|
|
|
} else {
|
|
|
|
// Old way of doing upsync.
|
|
|
|
// 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);
|
2013-09-05 20:52:03 +00:00
|
|
|
}
|
2013-08-06 23:09:00 +00:00
|
|
|
} else if (match == MESSAGE_ID) {
|
|
|
|
db.execSQL(UPDATED_MESSAGE_DELETE + id);
|
2009-11-19 01:11:33 +00:00
|
|
|
}
|
2013-08-06 23:09:00 +00:00
|
|
|
result = db.update(tableName, values, whereWithId(id, selection),
|
|
|
|
selectionArgs);
|
2012-06-29 16:42:05 +00:00
|
|
|
if (match == MESSAGE_ID || match == SYNCED_MESSAGE_ID) {
|
2013-08-06 23:09:00 +00:00
|
|
|
handleMessageUpdateNotifications(uri, id, values);
|
2012-06-29 16:42:05 +00:00
|
|
|
} else if (match == ATTACHMENT_ID) {
|
2012-06-28 17:40:46 +00:00
|
|
|
long attId = Integer.parseInt(id);
|
2014-04-11 21:42:28 +00:00
|
|
|
if (values.containsKey(AttachmentColumns.FLAGS)) {
|
|
|
|
int flags = values.getAsInteger(AttachmentColumns.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
|
|
|
}
|
2013-08-06 23:09:00 +00:00
|
|
|
} else if (match == MAILBOX_ID) {
|
2013-09-27 00:20:11 +00:00
|
|
|
final long accountId = Mailbox.getAccountIdForMailbox(context, id);
|
|
|
|
notifyUIFolder(id, accountId);
|
|
|
|
restartPushForMailbox(context, db, values, Long.toString(accountId));
|
2012-06-28 17:40:46 +00:00
|
|
|
} else if (match == ACCOUNT_ID) {
|
2013-04-19 23:32:57 +00:00
|
|
|
updateAccountSyncInterval(Long.parseLong(id), values);
|
2012-07-26 20:45:47 +00:00
|
|
|
// Notify individual account and "all accounts"
|
2012-06-28 17:40:46 +00:00
|
|
|
notifyUI(UIPROVIDER_ACCOUNT_NOTIFIER, id);
|
2013-10-30 22:56:33 +00:00
|
|
|
notifyUI(UIPROVIDER_ALL_ACCOUNTS_NOTIFIER, null);
|
2013-09-27 00:20:11 +00:00
|
|
|
restartPushForAccount(context, db, values, id);
|
2010-08-10 00:48:53 +00:00
|
|
|
}
|
2009-11-19 01:11:33 +00:00
|
|
|
break;
|
2014-05-12 18:33:43 +00:00
|
|
|
case BODY_ID: {
|
|
|
|
final ContentValues updateValues = new ContentValues(values);
|
|
|
|
updateValues.remove(BodyColumns.HTML_CONTENT);
|
|
|
|
updateValues.remove(BodyColumns.TEXT_CONTENT);
|
|
|
|
|
|
|
|
result = db.update(tableName, updateValues, whereWithId(id, selection),
|
|
|
|
selectionArgs);
|
|
|
|
|
|
|
|
if (values.containsKey(BodyColumns.HTML_CONTENT) ||
|
|
|
|
values.containsKey(BodyColumns.TEXT_CONTENT)) {
|
|
|
|
final long messageId;
|
|
|
|
if (values.containsKey(BodyColumns.MESSAGE_KEY)) {
|
|
|
|
messageId = values.getAsLong(BodyColumns.MESSAGE_KEY);
|
|
|
|
} else {
|
|
|
|
final long bodyId = Long.parseLong(id);
|
|
|
|
final SQLiteStatement sql = db.compileStatement(
|
|
|
|
"select " + BodyColumns.MESSAGE_KEY +
|
|
|
|
" from " + Body.TABLE_NAME +
|
|
|
|
" where " + BodyColumns._ID + "=" + Long
|
|
|
|
.toString(bodyId)
|
|
|
|
);
|
|
|
|
messageId = sql.simpleQueryForLong();
|
|
|
|
}
|
|
|
|
writeBodyFiles(context, messageId, values);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case BODY: {
|
|
|
|
final ContentValues updateValues = new ContentValues(values);
|
|
|
|
updateValues.remove(BodyColumns.HTML_CONTENT);
|
|
|
|
updateValues.remove(BodyColumns.TEXT_CONTENT);
|
|
|
|
|
|
|
|
result = db.update(tableName, updateValues, selection, selectionArgs);
|
|
|
|
|
2013-10-21 21:59:53 +00:00
|
|
|
if (result == 0 && selection.equals(Body.SELECTION_BY_MESSAGE_KEY)) {
|
|
|
|
// TODO: This is a hack. Notably, the selection equality test above
|
|
|
|
// is hokey at best.
|
|
|
|
LogUtils.i(TAG, "Body Update to non-existent row, morphing to insert");
|
|
|
|
final ContentValues insertValues = new ContentValues(values);
|
2014-04-11 21:42:28 +00:00
|
|
|
insertValues.put(BodyColumns.MESSAGE_KEY, selectionArgs[0]);
|
|
|
|
insert(Body.CONTENT_URI, insertValues);
|
2014-05-12 18:33:43 +00:00
|
|
|
} else {
|
|
|
|
// possibly need to write new body values
|
|
|
|
if (values.containsKey(BodyColumns.HTML_CONTENT) ||
|
|
|
|
values.containsKey(BodyColumns.TEXT_CONTENT)) {
|
|
|
|
final long messageIds[];
|
|
|
|
if (values.containsKey(BodyColumns.MESSAGE_KEY)) {
|
|
|
|
messageIds = new long[] {values.getAsLong(BodyColumns.MESSAGE_KEY)};
|
|
|
|
} else if (values.containsKey(BodyColumns._ID)) {
|
|
|
|
final long bodyId = values.getAsLong(BodyColumns._ID);
|
|
|
|
final SQLiteStatement sql = db.compileStatement(
|
|
|
|
"select " + BodyColumns.MESSAGE_KEY +
|
|
|
|
" from " + Body.TABLE_NAME +
|
|
|
|
" where " + BodyColumns._ID + "=" + Long
|
|
|
|
.toString(bodyId)
|
|
|
|
);
|
|
|
|
messageIds = new long[] {sql.simpleQueryForLong()};
|
|
|
|
} else {
|
|
|
|
final String proj[] = {BodyColumns.MESSAGE_KEY};
|
|
|
|
final Cursor c = db.query(Body.TABLE_NAME, proj,
|
|
|
|
selection, selectionArgs,
|
|
|
|
null, null, null);
|
|
|
|
try {
|
|
|
|
final int count = c.getCount();
|
|
|
|
if (count == 0) {
|
|
|
|
throw new IllegalStateException("Can't find body record");
|
|
|
|
}
|
|
|
|
messageIds = new long[count];
|
|
|
|
int i = 0;
|
|
|
|
while (c.moveToNext()) {
|
|
|
|
messageIds[i++] = c.getLong(0);
|
|
|
|
}
|
|
|
|
} finally {
|
|
|
|
c.close();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// This is probably overkill
|
|
|
|
for (int i = 0; i < messageIds.length; i++) {
|
|
|
|
final long messageId = messageIds[i];
|
|
|
|
writeBodyFiles(context, messageId, values);
|
|
|
|
}
|
|
|
|
}
|
2013-10-21 21:59:53 +00:00
|
|
|
}
|
|
|
|
break;
|
2014-05-12 18:33:43 +00:00
|
|
|
}
|
2009-11-19 01:11:33 +00:00
|
|
|
case MESSAGE:
|
2014-01-25 00:43:36 +00:00
|
|
|
decodeEmailAddresses(values);
|
2009-11-19 01:11:33 +00:00
|
|
|
case UPDATED_MESSAGE:
|
|
|
|
case ATTACHMENT:
|
|
|
|
case MAILBOX:
|
|
|
|
case ACCOUNT:
|
|
|
|
case HOSTAUTH:
|
2013-12-04 00:55:48 +00:00
|
|
|
case CREDENTIAL:
|
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:
|
2013-10-31 20:08:40 +00:00
|
|
|
if (match == ATTACHMENT) {
|
|
|
|
if (values.containsKey(AttachmentColumns.LOCATION) &&
|
|
|
|
TextUtils.isEmpty(values.getAsString(AttachmentColumns.LOCATION))) {
|
|
|
|
LogUtils.w(TAG, new Throwable(), "attachment with blank location");
|
|
|
|
}
|
|
|
|
}
|
2013-04-18 23:56:03 +00:00
|
|
|
result = db.update(tableName, values, selection, selectionArgs);
|
|
|
|
break;
|
2013-09-05 20:52:03 +00:00
|
|
|
case MESSAGE_MOVE:
|
|
|
|
result = db.update(MessageMove.TABLE_NAME, values, selection, selectionArgs);
|
|
|
|
break;
|
|
|
|
case MESSAGE_STATE_CHANGE:
|
|
|
|
result = db.update(MessageStateChange.TABLE_NAME, values, selection,
|
|
|
|
selectionArgs);
|
|
|
|
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
|
|
|
|
2014-05-14 22:34:47 +00:00
|
|
|
// Notify all notifier cursors if some records where changed in the database
|
|
|
|
if (result > 0) {
|
|
|
|
sendNotifierChange(getBaseNotificationUri(match), NOTIFICATION_OP_UPDATE, id);
|
|
|
|
notifyUI(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
|
|
|
|
2013-07-30 02:11:41 +00:00
|
|
|
private void updateSyncStatus(final Bundle extras) {
|
|
|
|
final long id = extras.getLong(EmailServiceStatus.SYNC_STATUS_ID);
|
2013-10-18 00:46:26 +00:00
|
|
|
final int statusCode = extras.getInt(EmailServiceStatus.SYNC_STATUS_CODE);
|
2013-07-30 02:11:41 +00:00
|
|
|
final Uri uri = ContentUris.withAppendedId(FOLDER_STATUS_URI, id);
|
2013-10-30 22:56:33 +00:00
|
|
|
notifyUI(uri, null);
|
2013-10-18 00:46:26 +00:00
|
|
|
final boolean inProgress = statusCode == EmailServiceStatus.IN_PROGRESS;
|
|
|
|
if (inProgress) {
|
|
|
|
RefreshStatusMonitor.getInstance(getContext()).setSyncStarted(id);
|
|
|
|
} else {
|
|
|
|
final int result = extras.getInt(EmailServiceStatus.SYNC_RESULT);
|
|
|
|
final ContentValues values = new ContentValues();
|
|
|
|
values.put(Mailbox.UI_LAST_SYNC_RESULT, result);
|
|
|
|
mDatabase.update(
|
|
|
|
Mailbox.TABLE_NAME,
|
|
|
|
values,
|
|
|
|
WHERE_ID,
|
|
|
|
new String[] { String.valueOf(id) });
|
|
|
|
}
|
2013-07-30 02:11:41 +00:00
|
|
|
}
|
|
|
|
|
2013-02-22 01:32:02 +00:00
|
|
|
@Override
|
|
|
|
public Bundle call(String method, String arg, Bundle extras) {
|
|
|
|
LogUtils.d(TAG, "EmailProvider#call(%s, %s)", method, arg);
|
2013-05-03 01:32:36 +00:00
|
|
|
|
2013-10-30 02:34:17 +00:00
|
|
|
// Handle queries for the device friendly name.
|
|
|
|
// TODO: This should eventually be a device property, not defined by the app.
|
|
|
|
if (TextUtils.equals(method, EmailContent.DEVICE_FRIENDLY_NAME)) {
|
|
|
|
final Bundle bundle = new Bundle(1);
|
|
|
|
// TODO: For now, just use the model name since we don't yet have a user-supplied name.
|
|
|
|
bundle.putString(EmailContent.DEVICE_FRIENDLY_NAME, Build.MODEL);
|
|
|
|
return bundle;
|
|
|
|
}
|
|
|
|
|
2013-07-30 02:11:41 +00:00
|
|
|
// Handle sync status callbacks.
|
2013-05-03 01:32:36 +00:00
|
|
|
if (TextUtils.equals(method, SYNC_STATUS_CALLBACK_METHOD)) {
|
2013-07-30 02:11:41 +00:00
|
|
|
updateSyncStatus(extras);
|
2013-05-03 01:32:36 +00:00
|
|
|
return null;
|
|
|
|
}
|
2013-10-11 18:28:32 +00:00
|
|
|
if (TextUtils.equals(method, MailboxUtilities.FIX_PARENT_KEYS_METHOD)) {
|
|
|
|
fixParentKeys(getDatabase(getContext()));
|
|
|
|
return null;
|
|
|
|
}
|
2013-05-03 01:32:36 +00:00
|
|
|
|
|
|
|
// Handle send & save.
|
2013-02-22 01:32:02 +00:00
|
|
|
final Uri accountUri = Uri.parse(arg);
|
2013-04-19 20:36:21 +00:00
|
|
|
final long accountId = Long.parseLong(accountUri.getPathSegments().get(1));
|
2013-02-22 01:32:02 +00:00
|
|
|
|
|
|
|
Uri messageUri = null;
|
2013-10-11 18:28:32 +00:00
|
|
|
|
2013-02-22 01:32:02 +00:00
|
|
|
if (TextUtils.equals(method, UIProvider.AccountCallMethods.SEND_MESSAGE)) {
|
2013-05-03 01:32:36 +00:00
|
|
|
messageUri = uiSendDraftMessage(accountId, extras);
|
2013-06-26 22:49:12 +00:00
|
|
|
Preferences.getPreferences(getContext()).setLastUsedAccountId(accountId);
|
2013-02-22 01:32:02 +00:00
|
|
|
} else if (TextUtils.equals(method, UIProvider.AccountCallMethods.SAVE_MESSAGE)) {
|
2013-05-03 01:32:36 +00:00
|
|
|
messageUri = uiSaveDraftMessage(accountId, extras);
|
2013-06-26 22:49:12 +00:00
|
|
|
} else if (TextUtils.equals(method, UIProvider.AccountCallMethods.SET_CURRENT_ACCOUNT)) {
|
|
|
|
LogUtils.d(TAG, "Unhandled (but expected) Content provider method: %s", method);
|
|
|
|
} else {
|
|
|
|
LogUtils.wtf(TAG, "Unexpected Content provider method: %s", method);
|
2013-02-22 01:32:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
final Bundle result;
|
|
|
|
if (messageUri != null) {
|
|
|
|
result = new Bundle(1);
|
|
|
|
result.putParcelable(UIProvider.MessageColumns.URI, messageUri);
|
|
|
|
} else {
|
|
|
|
result = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2014-05-12 18:33:43 +00:00
|
|
|
/**
|
|
|
|
* Writes message bodies to disk, read from a set of ContentValues
|
|
|
|
*
|
|
|
|
* @param c Context for finding files
|
|
|
|
* @param messageId id of message to write body for
|
|
|
|
* @param cv {@link ContentValues} containing {@link BodyColumns#HTML_CONTENT} and/or
|
|
|
|
* {@link BodyColumns#TEXT_CONTENT}. Inserting a null or empty value will delete the
|
|
|
|
* associated text or html body file
|
|
|
|
* @throws IllegalStateException
|
|
|
|
*/
|
|
|
|
private static void writeBodyFiles(final Context c, final long messageId,
|
|
|
|
final ContentValues cv) throws IllegalStateException {
|
|
|
|
if (cv.containsKey(BodyColumns.HTML_CONTENT)) {
|
|
|
|
final String htmlContent = cv.getAsString(BodyColumns.HTML_CONTENT);
|
|
|
|
try {
|
|
|
|
writeBodyFile(c, messageId, "html", htmlContent);
|
|
|
|
} catch (final IOException e) {
|
|
|
|
throw new IllegalStateException("IOException while writing html body " +
|
|
|
|
"for message id " + Long.toString(messageId), e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (cv.containsKey(BodyColumns.TEXT_CONTENT)) {
|
|
|
|
final String textContent = cv.getAsString(BodyColumns.TEXT_CONTENT);
|
|
|
|
try {
|
|
|
|
writeBodyFile(c, messageId, "txt", textContent);
|
|
|
|
} catch (final IOException e) {
|
|
|
|
throw new IllegalStateException("IOException while writing text body " +
|
|
|
|
"for message id " + Long.toString(messageId), e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2014-05-08 20:07:54 +00:00
|
|
|
|
2014-05-12 18:33:43 +00:00
|
|
|
/**
|
|
|
|
* Writes a message body file to disk
|
|
|
|
*
|
|
|
|
* @param c Context for finding files dir
|
|
|
|
* @param messageId id of message to write body for
|
|
|
|
* @param ext "html" or "txt"
|
|
|
|
* @param content Body content to write to file, or null/empty to delete file
|
|
|
|
* @throws IOException
|
|
|
|
*/
|
|
|
|
private static void writeBodyFile(final Context c, final long messageId, final String ext,
|
|
|
|
final String content) throws IOException {
|
|
|
|
final File textFile = getBodyFile(c, messageId, ext);
|
|
|
|
if (TextUtils.isEmpty(content)) {
|
|
|
|
if (!textFile.delete()) {
|
|
|
|
LogUtils.v(LogUtils.TAG, "did not delete text body for %d", messageId);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
final FileWriter w = new FileWriter(textFile);
|
|
|
|
try {
|
|
|
|
w.write(content);
|
|
|
|
} finally {
|
|
|
|
w.close();
|
|
|
|
}
|
2014-05-08 20:07:54 +00:00
|
|
|
}
|
2014-05-12 18:33:43 +00:00
|
|
|
}
|
2014-05-08 20:07:54 +00:00
|
|
|
|
|
|
|
/**
|
2014-05-12 18:33:43 +00:00
|
|
|
* Returns a {@link java.io.File} object pointing to the body content file for the message
|
2014-05-08 20:07:54 +00:00
|
|
|
*
|
2014-05-12 18:33:43 +00:00
|
|
|
* @param c Context for finding files dir
|
|
|
|
* @param messageId id of message to locate
|
|
|
|
* @param ext "html" or "txt"
|
|
|
|
* @return File ready for operating upon
|
2014-05-08 20:07:54 +00:00
|
|
|
*/
|
2014-05-12 18:33:43 +00:00
|
|
|
protected static File getBodyFile(final Context c, final long messageId, final String ext)
|
|
|
|
throws FileNotFoundException {
|
|
|
|
if (!TextUtils.equals(ext, "html") && !TextUtils.equals(ext, "txt")) {
|
|
|
|
throw new IllegalArgumentException("ext must be one of 'html' or 'txt'");
|
|
|
|
}
|
|
|
|
long l1 = messageId / 100 % 100;
|
|
|
|
long l2 = messageId % 100;
|
|
|
|
final File dir = new File(c.getFilesDir(),
|
|
|
|
"body/" + Long.toString(l1) + "/" + Long.toString(l2) + "/");
|
|
|
|
if (!dir.isDirectory() && !dir.mkdirs()) {
|
|
|
|
throw new FileNotFoundException("Could not create directory for body file");
|
|
|
|
}
|
|
|
|
return new File(dir, Long.toString(messageId) + "." + ext);
|
|
|
|
}
|
2014-05-08 20:07:54 +00:00
|
|
|
|
2013-03-21 23:54:49 +00:00
|
|
|
@Override
|
2014-05-08 20:07:54 +00:00
|
|
|
public ParcelFileDescriptor openFile(final Uri uri, final String mode)
|
|
|
|
throws FileNotFoundException {
|
2013-03-21 23:54:49 +00:00
|
|
|
if (LogUtils.isLoggable(TAG, LogUtils.DEBUG)) {
|
|
|
|
LogUtils.d(TAG, "EmailProvider.openFile: %s", LogUtils.contentUriToString(TAG, uri));
|
|
|
|
}
|
|
|
|
|
|
|
|
final int match = findMatch(uri, "openFile");
|
|
|
|
switch (match) {
|
|
|
|
case ATTACHMENTS_CACHED_FILE_ACCESS:
|
|
|
|
// Parse the cache file path out from the uri
|
|
|
|
final String cachedFilePath =
|
2014-04-11 21:42:28 +00:00
|
|
|
uri.getQueryParameter(Attachment.CACHED_FILE_QUERY_PARAM);
|
2013-03-21 23:54:49 +00:00
|
|
|
|
|
|
|
if (cachedFilePath != null) {
|
|
|
|
// clearCallingIdentity means that the download manager will
|
|
|
|
// check our permissions rather than the permissions of whatever
|
|
|
|
// code is calling us.
|
|
|
|
long binderToken = Binder.clearCallingIdentity();
|
|
|
|
try {
|
|
|
|
LogUtils.d(TAG, "Opening attachment %s", cachedFilePath);
|
|
|
|
return ParcelFileDescriptor.open(
|
|
|
|
new File(cachedFilePath), ParcelFileDescriptor.MODE_READ_ONLY);
|
|
|
|
} finally {
|
|
|
|
Binder.restoreCallingIdentity(binderToken);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
2014-05-12 18:33:43 +00:00
|
|
|
case BODY_HTML: {
|
2014-05-08 20:07:54 +00:00
|
|
|
final long messageKey = Long.valueOf(uri.getLastPathSegment());
|
2014-05-12 18:33:43 +00:00
|
|
|
return ParcelFileDescriptor.open(getBodyFile(getContext(), messageKey, "html"),
|
|
|
|
Utilities.parseMode(mode));
|
|
|
|
}
|
|
|
|
case BODY_TEXT:{
|
|
|
|
final long messageKey = Long.valueOf(uri.getLastPathSegment());
|
|
|
|
return ParcelFileDescriptor.open(getBodyFile(getContext(), messageKey, "txt"),
|
|
|
|
Utilities.parseMode(mode));
|
|
|
|
}
|
2013-03-21 23:54:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
throw new FileNotFoundException("unable to open file");
|
|
|
|
}
|
|
|
|
|
|
|
|
|
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.
|
|
|
|
*/
|
2012-12-11 18:37:35 +00:00
|
|
|
private static Uri getBaseNotificationUri(int match) {
|
2011-05-11 22:29:24 +00:00
|
|
|
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
|
|
|
// 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) {
|
2013-10-30 22:56:33 +00:00
|
|
|
notifyUI(baseUri, id);
|
2011-05-06 18:31:10 +00:00
|
|
|
} else {
|
2013-10-30 22:56:33 +00:00
|
|
|
notifyUI(baseUri, null);
|
2011-05-06 18:31:10 +00:00
|
|
|
}
|
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
|
|
|
}
|
|
|
|
|
2014-01-03 00:16:08 +00:00
|
|
|
// We might have more than one thread trying to make its way through applyBatch() so the
|
|
|
|
// notification coalescing needs to be thread-local to work correctly.
|
|
|
|
private final ThreadLocal<Set<Uri>> mTLBatchNotifications =
|
|
|
|
new ThreadLocal<Set<Uri>>();
|
|
|
|
|
|
|
|
private Set<Uri> getBatchNotificationsSet() {
|
|
|
|
return mTLBatchNotifications.get();
|
|
|
|
}
|
|
|
|
|
|
|
|
private void setBatchNotificationsSet(Set<Uri> batchNotifications) {
|
|
|
|
mTLBatchNotifications.set(batchNotifications);
|
|
|
|
}
|
2013-10-30 22:56:33 +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 {
|
2013-10-30 22:56:33 +00:00
|
|
|
/**
|
|
|
|
* Collect notification URIs to notify at the end of batch processing.
|
|
|
|
* These are populated by calls to notifyUI() by way of update(), insert() and delete()
|
|
|
|
* calls made in super.applyBatch()
|
|
|
|
*/
|
2014-01-03 00:16:08 +00:00
|
|
|
setBatchNotificationsSet(Sets.<Uri>newHashSet());
|
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();
|
2014-01-03 00:16:08 +00:00
|
|
|
final Set<Uri> notifications = getBatchNotificationsSet();
|
|
|
|
setBatchNotificationsSet(null);
|
2013-10-30 22:56:33 +00:00
|
|
|
for (final Uri uri : notifications) {
|
|
|
|
context.getContentResolver().notifyChange(uri, null);
|
|
|
|
}
|
2009-05-26 23:40:34 +00:00
|
|
|
}
|
|
|
|
}
|
2010-07-30 20:53:59 +00:00
|
|
|
|
2014-06-25 16:56:29 +00:00
|
|
|
public static interface EmailAttachmentService {
|
2012-04-25 17:26:46 +00:00
|
|
|
/**
|
|
|
|
* Notify the service that an attachment has changed.
|
|
|
|
*/
|
2014-06-25 16:56:29 +00:00
|
|
|
void attachmentChanged(final Context context, final long id, final int flags);
|
2012-03-09 22:04:49 +00:00
|
|
|
}
|
|
|
|
|
2014-06-25 16:56:29 +00:00
|
|
|
private final EmailAttachmentService DEFAULT_ATTACHMENT_SERVICE = new EmailAttachmentService() {
|
2012-02-18 00:31:23 +00:00
|
|
|
@Override
|
2014-06-25 16:56:29 +00:00
|
|
|
public void attachmentChanged(final Context context, final long id, final int flags) {
|
2012-04-25 17:26:46 +00:00
|
|
|
// The default implementation delegates to the real service.
|
2014-06-25 16:56:29 +00:00
|
|
|
AttachmentService.attachmentChanged(context, id, flags);
|
2012-02-18 00:31:23 +00:00
|
|
|
}
|
|
|
|
};
|
2014-06-25 16:56:29 +00:00
|
|
|
private EmailAttachmentService mAttachmentService = DEFAULT_ATTACHMENT_SERVICE;
|
2013-11-27 04:41:53 +00:00
|
|
|
|
|
|
|
// exposed for testing
|
2014-06-25 16:56:29 +00:00
|
|
|
public void injectAttachmentService(final EmailAttachmentService attachmentService) {
|
2013-11-27 04:41:53 +00:00
|
|
|
mAttachmentService =
|
|
|
|
attachmentService == null ? DEFAULT_ATTACHMENT_SERVICE : attachmentService;
|
|
|
|
}
|
2012-03-09 19:52:45 +00:00
|
|
|
|
2012-12-11 18:37:35 +00:00
|
|
|
private Cursor notificationQuery(final Uri uri) {
|
|
|
|
final SQLiteDatabase db = getDatabase(getContext());
|
|
|
|
final String accountId = uri.getLastPathSegment();
|
|
|
|
|
2014-04-09 21:15:58 +00:00
|
|
|
final String sql = "SELECT " + MessageColumns.MAILBOX_KEY + ", " +
|
|
|
|
"SUM(CASE " + MessageColumns.FLAG_READ + " WHEN 0 THEN 1 ELSE 0 END), " +
|
|
|
|
"SUM(CASE " + MessageColumns.FLAG_SEEN + " WHEN 0 THEN 1 ELSE 0 END)\n" +
|
|
|
|
"FROM " + Message.TABLE_NAME + "\n" +
|
|
|
|
"WHERE " + MessageColumns.ACCOUNT_KEY + " = ?\n" +
|
|
|
|
"GROUP BY " + MessageColumns.MAILBOX_KEY;
|
2012-12-11 18:37:35 +00:00
|
|
|
|
|
|
|
final String[] selectionArgs = {accountId};
|
|
|
|
|
|
|
|
return db.rawQuery(sql, selectionArgs);
|
2012-03-09 19:52:45 +00:00
|
|
|
}
|
2012-06-28 17:40:46 +00:00
|
|
|
|
|
|
|
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});
|
2013-04-05 01:30:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
private Cursor getMailboxMessageCount(Uri uri) {
|
|
|
|
SQLiteDatabase db = getDatabase(getContext());
|
|
|
|
String mailboxId = uri.getLastPathSegment();
|
|
|
|
return db.rawQuery("select count(*) from Message where mailboxKey=?",
|
|
|
|
new String[] {mailboxId});
|
|
|
|
}
|
2012-06-28 17:40:46 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* 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 =
|
2014-04-11 21:42:28 +00:00
|
|
|
"@CASE (" + AccountColumns._ID + " - 1) % " + ACCOUNT_COLORS.length +
|
2012-06-28 17:40:46 +00:00
|
|
|
" 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";
|
2014-04-09 21:15:58 +00:00
|
|
|
|
2012-06-28 17:40:46 +00:00
|
|
|
/**
|
|
|
|
* Mapping of UIProvider columns to EmailProvider columns for the message list (called the
|
|
|
|
* conversation list in UnifiedEmail)
|
|
|
|
*/
|
2012-12-11 18:37:35 +00:00
|
|
|
private static ProjectionMap getMessageListMap() {
|
2012-08-23 05:25:42 +00:00
|
|
|
if (sMessageListMap == null) {
|
|
|
|
sMessageListMap = ProjectionMap.builder()
|
2014-04-11 21:42:28 +00:00
|
|
|
.add(BaseColumns._ID, MessageColumns._ID)
|
2012-08-23 05:25:42 +00:00
|
|
|
.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.CONVERSATION_INFO, null)
|
|
|
|
.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)
|
2012-12-11 18:37:35 +00:00
|
|
|
.add(UIProvider.ConversationColumns.SEEN, MessageColumns.FLAG_SEEN)
|
2012-08-23 05:25:42 +00:00
|
|
|
.add(UIProvider.ConversationColumns.STARRED, MessageColumns.FLAG_FAVORITE)
|
|
|
|
.add(UIProvider.ConversationColumns.FLAGS, CONVERSATION_FLAGS)
|
|
|
|
.add(UIProvider.ConversationColumns.ACCOUNT_URI,
|
|
|
|
uriWithColumn("uiaccount", MessageColumns.ACCOUNT_KEY))
|
2013-05-07 18:00:02 +00:00
|
|
|
.add(UIProvider.ConversationColumns.SENDER_INFO, MessageColumns.FROM_LIST)
|
2014-06-11 23:16:41 +00:00
|
|
|
.add(UIProvider.ConversationColumns.ORDER_KEY, MessageColumns.TIMESTAMP)
|
2012-08-23 05:25:42 +00:00
|
|
|
.build();
|
|
|
|
}
|
|
|
|
return sMessageListMap;
|
|
|
|
}
|
|
|
|
private static ProjectionMap sMessageListMap;
|
2012-06-28 17:40:46 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* 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 +
|
2013-10-15 00:40:44 +00:00
|
|
|
" WHEN (" + MessageColumns.FLAGS + "&" + Message.FLAG_TYPE_REPLY_ALL +
|
2012-06-28 17:40:46 +00:00
|
|
|
") !=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
|
|
|
|
*/
|
2012-12-11 18:37:35 +00:00
|
|
|
private static ProjectionMap getMessageViewMap() {
|
2012-08-23 05:25:42 +00:00
|
|
|
if (sMessageViewMap == null) {
|
|
|
|
sMessageViewMap = ProjectionMap.builder()
|
2014-04-11 21:42:28 +00:00
|
|
|
.add(BaseColumns._ID, Message.TABLE_NAME + "." + MessageColumns._ID)
|
2012-08-23 05:25:42 +00:00
|
|
|
.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))
|
2014-04-11 21:42:28 +00:00
|
|
|
.add(UIProvider.MessageColumns.SUBJECT, MessageColumns.SUBJECT)
|
|
|
|
.add(UIProvider.MessageColumns.SNIPPET, MessageColumns.SNIPPET)
|
|
|
|
.add(UIProvider.MessageColumns.FROM, MessageColumns.FROM_LIST)
|
|
|
|
.add(UIProvider.MessageColumns.TO, MessageColumns.TO_LIST)
|
|
|
|
.add(UIProvider.MessageColumns.CC, MessageColumns.CC_LIST)
|
|
|
|
.add(UIProvider.MessageColumns.BCC, MessageColumns.BCC_LIST)
|
|
|
|
.add(UIProvider.MessageColumns.REPLY_TO, MessageColumns.REPLY_TO_LIST)
|
|
|
|
.add(UIProvider.MessageColumns.DATE_RECEIVED_MS, MessageColumns.TIMESTAMP)
|
2014-04-16 21:21:35 +00:00
|
|
|
.add(UIProvider.MessageColumns.BODY_HTML, null) // Loaded in EmailMessageCursor
|
|
|
|
.add(UIProvider.MessageColumns.BODY_TEXT, null) // Loaded in EmailMessageCursor
|
2012-08-23 05:25:42 +00:00
|
|
|
.add(UIProvider.MessageColumns.REF_MESSAGE_ID, "0")
|
|
|
|
.add(UIProvider.MessageColumns.DRAFT_TYPE, NOT_A_DRAFT_STRING)
|
|
|
|
.add(UIProvider.MessageColumns.APPEND_REF_MESSAGE_CONTENT, "0")
|
2014-04-11 21:42:28 +00:00
|
|
|
.add(UIProvider.MessageColumns.HAS_ATTACHMENTS, MessageColumns.FLAG_ATTACHMENT)
|
2012-08-23 05:25:42 +00:00
|
|
|
.add(UIProvider.MessageColumns.ATTACHMENT_LIST_URI,
|
|
|
|
uriWithFQId("uiattachments", Message.TABLE_NAME))
|
2014-04-09 19:59:28 +00:00
|
|
|
.add(UIProvider.MessageColumns.ATTACHMENT_BY_CID_URI,
|
|
|
|
uriWithFQId("uiattachmentbycid", Message.TABLE_NAME))
|
2012-08-23 05:25:42 +00:00
|
|
|
.add(UIProvider.MessageColumns.MESSAGE_FLAGS, MESSAGE_FLAGS)
|
|
|
|
.add(UIProvider.MessageColumns.DRAFT_TYPE, MESSAGE_DRAFT_TYPE)
|
|
|
|
.add(UIProvider.MessageColumns.MESSAGE_ACCOUNT_URI,
|
2013-07-13 01:15:46 +00:00
|
|
|
uriWithColumn("uiaccount", MessageColumns.ACCOUNT_KEY))
|
2014-04-11 21:42:28 +00:00
|
|
|
.add(UIProvider.MessageColumns.STARRED, MessageColumns.FLAG_FAVORITE)
|
|
|
|
.add(UIProvider.MessageColumns.READ, MessageColumns.FLAG_READ)
|
|
|
|
.add(UIProvider.MessageColumns.SEEN, MessageColumns.FLAG_SEEN)
|
2012-08-23 05:25:42 +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))
|
|
|
|
.add(UIProvider.MessageColumns.VIA_DOMAIN, null)
|
2014-02-20 18:29:37 +00:00
|
|
|
.add(UIProvider.MessageColumns.CLIPPED, "0")
|
2014-04-16 18:48:59 +00:00
|
|
|
.add(UIProvider.MessageColumns.PERMALINK, null)
|
2012-08-23 05:25:42 +00:00
|
|
|
.build();
|
|
|
|
}
|
|
|
|
return sMessageViewMap;
|
|
|
|
}
|
|
|
|
private static ProjectionMap sMessageViewMap;
|
2012-06-28 17:40:46 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* 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
|
2013-06-27 00:19:17 +00:00
|
|
|
+ " WHEN " + Mailbox.TYPE_UNREAD + " THEN " + UIProvider.FolderType.UNREAD
|
2013-09-04 20:49:54 +00:00
|
|
|
+ " WHEN " + Mailbox.TYPE_SEARCH + " THEN "
|
|
|
|
+ getFolderTypeFromMailboxType(Mailbox.TYPE_SEARCH)
|
2012-06-28 17:40:46 +00:00
|
|
|
+ " ELSE " + UIProvider.FolderType.DEFAULT + " END";
|
|
|
|
|
|
|
|
private static final String FOLDER_ICON = "CASE " + MailboxColumns.TYPE
|
2014-07-08 22:55:40 +00:00
|
|
|
+ " WHEN " + Mailbox.TYPE_INBOX + " THEN " + R.drawable.ic_drawer_inbox
|
|
|
|
+ " WHEN " + Mailbox.TYPE_DRAFTS + " THEN " + R.drawable.ic_drawer_drafts
|
|
|
|
+ " WHEN " + Mailbox.TYPE_OUTBOX + " THEN " + R.drawable.ic_drawer_outbox
|
|
|
|
+ " WHEN " + Mailbox.TYPE_SENT + " THEN " + R.drawable.ic_drawer_sent
|
|
|
|
+ " WHEN " + Mailbox.TYPE_TRASH + " THEN " + R.drawable.ic_drawer_trash
|
|
|
|
+ " WHEN " + Mailbox.TYPE_STARRED + " THEN " + R.drawable.ic_drawer_starred
|
|
|
|
+ " ELSE " + R.drawable.ic_drawer_folder + " END";
|
2012-06-28 17:40:46 +00:00
|
|
|
|
2013-07-31 03:10:36 +00:00
|
|
|
/**
|
2013-08-02 19:23:19 +00:00
|
|
|
* Local-only folders set totalCount < 0; such folders should substitute message count for
|
|
|
|
* total count.
|
|
|
|
* TODO: IMAP and POP don't adhere to this convention yet so for now we force a few types.
|
2013-07-31 03:10:36 +00:00
|
|
|
*/
|
|
|
|
private static final String TOTAL_COUNT = "CASE WHEN "
|
2013-08-02 19:23:19 +00:00
|
|
|
+ MailboxColumns.TOTAL_COUNT + "<0 OR "
|
2013-07-31 03:10:36 +00:00
|
|
|
+ MailboxColumns.TYPE + "=" + Mailbox.TYPE_DRAFTS + " OR "
|
|
|
|
+ MailboxColumns.TYPE + "=" + Mailbox.TYPE_OUTBOX + " OR "
|
|
|
|
+ MailboxColumns.TYPE + "=" + Mailbox.TYPE_TRASH
|
|
|
|
+ " THEN " + MailboxColumns.MESSAGE_COUNT
|
|
|
|
+ " ELSE " + MailboxColumns.TOTAL_COUNT + " END";
|
|
|
|
|
2012-12-11 18:37:35 +00:00
|
|
|
private static ProjectionMap getFolderListMap() {
|
2012-08-23 05:25:42 +00:00
|
|
|
if (sFolderListMap == null) {
|
|
|
|
sFolderListMap = ProjectionMap.builder()
|
2014-04-11 21:42:28 +00:00
|
|
|
.add(BaseColumns._ID, MailboxColumns._ID)
|
2012-12-11 18:37:35 +00:00
|
|
|
.add(UIProvider.FolderColumns.PERSISTENT_ID, MailboxColumns.SERVER_ID)
|
2012-08-23 05:25:42 +00:00
|
|
|
.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)
|
2013-07-31 03:10:36 +00:00
|
|
|
.add(UIProvider.FolderColumns.TOTAL_COUNT, TOTAL_COUNT)
|
2013-04-12 00:11:34 +00:00
|
|
|
.add(UIProvider.FolderColumns.REFRESH_URI, uriWithId(QUERY_UIREFRESH))
|
2012-08-23 05:25:42 +00:00
|
|
|
.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)
|
2013-10-14 03:52:09 +00:00
|
|
|
.add(UIProvider.FolderColumns.LOAD_MORE_URI, uriWithId("uiloadmore"))
|
2012-08-23 05:25:42 +00:00
|
|
|
.add(UIProvider.FolderColumns.HIERARCHICAL_DESC, MailboxColumns.HIERARCHICAL_NAME)
|
2013-07-03 22:53:35 +00:00
|
|
|
.add(UIProvider.FolderColumns.PARENT_URI, "case when " + MailboxColumns.PARENT_KEY
|
|
|
|
+ "=" + Mailbox.NO_MAILBOX + " then NULL else " +
|
|
|
|
uriWithColumn("uifolder", MailboxColumns.PARENT_KEY) + " end")
|
2013-10-23 18:41:05 +00:00
|
|
|
/**
|
|
|
|
* SELECT group_concat(fromList) FROM
|
|
|
|
* (SELECT fromList FROM message WHERE mailboxKey=? AND flagRead=0
|
|
|
|
* GROUP BY fromList ORDER BY timestamp DESC)
|
|
|
|
*/
|
|
|
|
.add(UIProvider.FolderColumns.UNREAD_SENDERS,
|
|
|
|
"(SELECT group_concat(" + MessageColumns.FROM_LIST + ") FROM " +
|
|
|
|
"(SELECT " + MessageColumns.FROM_LIST + " FROM " + Message.TABLE_NAME +
|
|
|
|
" WHERE " + MessageColumns.MAILBOX_KEY + "=" + Mailbox.TABLE_NAME + "." +
|
2014-04-11 21:42:28 +00:00
|
|
|
MailboxColumns._ID + " AND " + MessageColumns.FLAG_READ + "=0" +
|
2013-10-23 18:41:05 +00:00
|
|
|
" GROUP BY " + MessageColumns.FROM_LIST + " ORDER BY " +
|
|
|
|
MessageColumns.TIMESTAMP + " DESC))")
|
2012-08-23 05:25:42 +00:00
|
|
|
.build();
|
|
|
|
}
|
|
|
|
return sFolderListMap;
|
|
|
|
}
|
|
|
|
private static ProjectionMap sFolderListMap;
|
|
|
|
|
2013-01-28 19:53:09 +00:00
|
|
|
/**
|
|
|
|
* Constructs the map of default entries for accounts. These values can be overridden in
|
|
|
|
* {@link #genQueryAccount(String[], String)}.
|
|
|
|
*/
|
2013-03-09 00:54:50 +00:00
|
|
|
private static ProjectionMap getAccountListMap(Context context) {
|
2012-08-23 05:25:42 +00:00
|
|
|
if (sAccountListMap == null) {
|
2013-03-09 00:54:50 +00:00
|
|
|
final ProjectionMap.Builder builder = ProjectionMap.builder()
|
2014-04-11 21:42:28 +00:00
|
|
|
.add(BaseColumns._ID, AccountColumns._ID)
|
2013-03-09 00:54:50 +00:00
|
|
|
.add(UIProvider.AccountColumns.FOLDER_LIST_URI, uriWithId("uifolders"))
|
2013-09-20 21:30:57 +00:00
|
|
|
.add(UIProvider.AccountColumns.FULL_FOLDER_LIST_URI, uriWithId("uifullfolders"))
|
|
|
|
.add(UIProvider.AccountColumns.ALL_FOLDER_LIST_URI, uriWithId("uiallfolders"))
|
2013-03-09 00:54:50 +00:00
|
|
|
.add(UIProvider.AccountColumns.NAME, AccountColumns.DISPLAY_NAME)
|
2013-09-24 23:39:49 +00:00
|
|
|
.add(UIProvider.AccountColumns.ACCOUNT_MANAGER_NAME,
|
|
|
|
AccountColumns.EMAIL_ADDRESS)
|
2013-10-18 21:04:49 +00:00
|
|
|
.add(UIProvider.AccountColumns.SENDER_NAME,
|
|
|
|
AccountColumns.SENDER_NAME)
|
2013-03-09 00:54:50 +00:00
|
|
|
.add(UIProvider.AccountColumns.UNDO_URI,
|
2013-03-16 02:32:32 +00:00
|
|
|
("'content://" + EmailContent.AUTHORITY + "/uiundo'"))
|
2013-03-09 00:54:50 +00:00
|
|
|
.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.CONFIRM_ARCHIVE, "0")
|
|
|
|
.add(UIProvider.AccountColumns.SettingsColumns.CONVERSATION_VIEW_MODE,
|
|
|
|
Integer.toString(UIProvider.ConversationViewMode.UNDEFINED))
|
|
|
|
.add(UIProvider.AccountColumns.SettingsColumns.VEILED_ADDRESS_PATTERN, null);
|
|
|
|
|
|
|
|
final String feedbackUri = context.getString(R.string.email_feedback_uri);
|
|
|
|
if (!TextUtils.isEmpty(feedbackUri)) {
|
2013-07-11 17:49:25 +00:00
|
|
|
// This string needs to be in single quotes, as it will be used as a constant
|
|
|
|
// in a sql expression
|
|
|
|
builder.add(UIProvider.AccountColumns.SEND_FEEDBACK_INTENT_URI,
|
|
|
|
"'" + feedbackUri + "'");
|
2013-03-09 00:54:50 +00:00
|
|
|
}
|
|
|
|
|
2014-03-03 21:01:04 +00:00
|
|
|
final String helpUri = context.getString(R.string.help_uri);
|
|
|
|
if (!TextUtils.isEmpty(helpUri)) {
|
|
|
|
// This string needs to be in single quotes, as it will be used as a constant
|
|
|
|
// in a sql expression
|
|
|
|
builder.add(UIProvider.AccountColumns.HELP_INTENT_URI,
|
|
|
|
"'" + helpUri + "'");
|
|
|
|
}
|
|
|
|
|
2013-03-09 00:54:50 +00:00
|
|
|
sAccountListMap = builder.build();
|
2012-08-23 05:25:42 +00:00
|
|
|
}
|
|
|
|
return sAccountListMap;
|
|
|
|
}
|
|
|
|
private static ProjectionMap sAccountListMap;
|
2012-06-28 17:40:46 +00:00
|
|
|
|
2013-08-14 18:05:19 +00:00
|
|
|
private static ProjectionMap getQuickResponseMap() {
|
|
|
|
if (sQuickResponseMap == null) {
|
|
|
|
sQuickResponseMap = ProjectionMap.builder()
|
2014-04-11 21:42:28 +00:00
|
|
|
.add(UIProvider.QuickResponseColumns.TEXT, QuickResponseColumns.TEXT)
|
2013-08-14 18:05:19 +00:00
|
|
|
.add(UIProvider.QuickResponseColumns.URI,
|
|
|
|
"'" + combinedUriString("quickresponse", "") + "'||"
|
2014-04-11 21:42:28 +00:00
|
|
|
+ QuickResponseColumns._ID)
|
2013-08-14 18:05:19 +00:00
|
|
|
.build();
|
|
|
|
}
|
|
|
|
return sQuickResponseMap;
|
|
|
|
}
|
|
|
|
private static ProjectionMap sQuickResponseMap;
|
|
|
|
|
2012-06-28 17:40:46 +00:00
|
|
|
/**
|
|
|
|
* 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
|
|
|
|
*/
|
2012-12-11 18:37:35 +00:00
|
|
|
private static ProjectionMap getAttachmentMap() {
|
2012-08-23 05:25:42 +00:00
|
|
|
if (sAttachmentMap == null) {
|
|
|
|
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)
|
2013-09-21 00:34:09 +00:00
|
|
|
.add(UIProvider.AttachmentColumns.FLAGS, AttachmentColumns.FLAGS)
|
2012-08-23 05:25:42 +00:00
|
|
|
.build();
|
|
|
|
}
|
|
|
|
return sAttachmentMap;
|
|
|
|
}
|
|
|
|
private static ProjectionMap sAttachmentMap;
|
2012-06-28 17:40:46 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* 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
|
|
|
|
* @return a StringBuilder containing the SELECT expression for a SQLite query
|
|
|
|
*/
|
2012-12-11 18:37:35 +00:00
|
|
|
private static StringBuilder genSelect(ProjectionMap map, String[] projection) {
|
2012-06-28 17:40:46 +00:00
|
|
|
return genSelect(map, projection, EMPTY_CONTENT_VALUES);
|
|
|
|
}
|
|
|
|
|
2012-12-11 18:37:35 +00:00
|
|
|
private static StringBuilder genSelect(ProjectionMap map, String[] projection,
|
|
|
|
ContentValues values) {
|
2013-09-06 23:05:39 +00:00
|
|
|
final StringBuilder sb = new StringBuilder("SELECT ");
|
2012-06-28 17:40:46 +00:00
|
|
|
boolean first = true;
|
2013-09-06 23:05:39 +00:00
|
|
|
for (final String column: projection) {
|
2012-06-28 17:40:46 +00:00
|
|
|
if (first) {
|
|
|
|
first = false;
|
|
|
|
} else {
|
|
|
|
sb.append(',');
|
|
|
|
}
|
2013-09-06 23:05:39 +00:00
|
|
|
final String val;
|
2012-06-28 17:40:46 +00:00
|
|
|
// First look at values; this is an override of default behavior
|
|
|
|
if (values.containsKey(column)) {
|
2013-09-06 23:05:39 +00:00
|
|
|
final String value = values.getAsString(column);
|
2012-08-17 20:03:30 +00:00
|
|
|
if (value == null) {
|
2012-09-03 21:14:53 +00:00
|
|
|
val = "NULL AS " + column;
|
2012-08-17 20:03:30 +00:00
|
|
|
} else if (value.startsWith("@")) {
|
2012-06-28 17:40:46 +00:00
|
|
|
val = value.substring(1) + " AS " + column;
|
|
|
|
} else {
|
2014-05-05 17:51:19 +00:00
|
|
|
val = DatabaseUtils.sqlEscapeString(value) + " AS " + column;
|
2012-06-28 17:40:46 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// Now, get the standard value for the column from our projection map
|
2013-09-06 23:05:39 +00:00
|
|
|
final String mapVal = map.get(column);
|
2012-06-28 17:40:46 +00:00
|
|
|
// If we don't have the column, return "NULL AS <column>", and warn
|
2013-09-06 23:05:39 +00:00
|
|
|
if (mapVal == null) {
|
2012-06-28 17:40:46 +00:00
|
|
|
val = "NULL AS " + column;
|
2013-09-10 18:38:34 +00:00
|
|
|
// Apparently there's a lot of these, so don't spam the log with warnings
|
|
|
|
// LogUtils.w(TAG, "column " + column + " missing from projection map");
|
2013-09-06 23:05:39 +00:00
|
|
|
} else {
|
|
|
|
val = mapVal;
|
2012-06-28 17:40:46 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
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) {
|
2014-04-11 21:42:28 +00:00
|
|
|
return uriWithColumn(type, BaseColumns._ID);
|
2012-06-28 17:40:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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) {
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2014-01-22 23:34:41 +00:00
|
|
|
Address[] fromList = Address.fromHeader(msg.mFrom);
|
2012-06-28 17:40:46 +00:00
|
|
|
int autoShowImages = 0;
|
2013-06-08 00:31:15 +00:00
|
|
|
final MailPrefs mailPrefs = MailPrefs.get(context);
|
2012-06-28 17:40:46 +00:00
|
|
|
for (Address sender : fromList) {
|
2013-06-08 00:31:15 +00:00
|
|
|
final String email = sender.getAddress();
|
|
|
|
if (mailPrefs.getDisplayImagesFromSender(email)) {
|
2012-06-28 17:40:46 +00:00
|
|
|
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) {
|
2013-10-04 19:10:27 +00:00
|
|
|
// TODO: This code is intended to strip out any inlined attachments (which
|
|
|
|
// would have a non-null contentId) so that they will not display at the bottom
|
|
|
|
// along with the non-inlined attachments.
|
|
|
|
// The problem is that the UI_ATTACHMENTS query does not behave the same way,
|
|
|
|
// which causes crazy formatting.
|
|
|
|
// There is an open question here, should attachments that are inlined
|
|
|
|
// ALSO appear in the list of attachments at the bottom with the non-inlined
|
|
|
|
// attachments?
|
|
|
|
// Either way, the two queries need to behave the same way.
|
|
|
|
// As of now, they will. If we decide to stop this, then we need to enable
|
|
|
|
// the code below, and then also make the UI_ATTACHMENTS query behave
|
|
|
|
// the same way.
|
|
|
|
//
|
|
|
|
// if (att.mContentId != null && att.getContentUri() != null) {
|
|
|
|
// continue;
|
|
|
|
// }
|
2012-06-28 17:40:46 +00:00
|
|
|
com.android.mail.providers.Attachment uiAtt =
|
|
|
|
new com.android.mail.providers.Attachment();
|
2013-02-22 02:10:10 +00:00
|
|
|
uiAtt.setName(att.mFileName);
|
|
|
|
uiAtt.setContentType(att.mMimeType);
|
2012-06-28 17:40:46 +00:00
|
|
|
uiAtt.size = (int) att.mSize;
|
|
|
|
uiAtt.uri = uiUri("uiattachment", att.mId);
|
2013-09-21 00:34:09 +00:00
|
|
|
uiAtt.flags = att.mFlags;
|
2012-06-28 17:40:46 +00:00
|
|
|
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);
|
|
|
|
}
|
2012-07-31 16:53:21 +00:00
|
|
|
if ((msg.mFlags & Message.FLAG_INCOMING_MEETING_INVITE) != 0) {
|
|
|
|
values.put(UIProvider.MessageColumns.EVENT_INTENT_URI,
|
|
|
|
"content://ui.email2.android.com/event/" + msg.mId);
|
|
|
|
}
|
2013-11-05 19:22:12 +00:00
|
|
|
/**
|
|
|
|
* HACK: override the attachment uri to contain a query parameter
|
|
|
|
* This forces the message footer to reload the attachment display when the message is
|
|
|
|
* fully loaded.
|
|
|
|
*/
|
|
|
|
final Uri attachmentListUri = uiUri("uiattachments", messageId).buildUpon()
|
|
|
|
.appendQueryParameter("MessageLoaded",
|
|
|
|
msg.mFlagLoaded == Message.FLAG_LOADED_COMPLETE ? "true" : "false")
|
|
|
|
.build();
|
|
|
|
values.put(UIProvider.MessageColumns.ATTACHMENT_LIST_URI, attachmentListUri.toString());
|
2012-06-28 17:40:46 +00:00
|
|
|
}
|
2012-08-23 05:25:42 +00:00
|
|
|
StringBuilder sb = genSelect(getMessageViewMap(), uiProjection, values);
|
2014-04-11 21:42:28 +00:00
|
|
|
sb.append(" FROM " + Message.TABLE_NAME + " LEFT JOIN " + Body.TABLE_NAME +
|
|
|
|
" ON " + BodyColumns.MESSAGE_KEY + "=" + Message.TABLE_NAME + "." +
|
|
|
|
MessageColumns._ID +
|
|
|
|
" WHERE " + Message.TABLE_NAME + "." + MessageColumns._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
|
|
|
}
|
|
|
|
|
2013-09-23 21:05:20 +00:00
|
|
|
private static void appendConversationInfoColumns(final StringBuilder stringBuilder) {
|
|
|
|
// TODO(skennedy) These columns are needed for the respond call for ConversationInfo :(
|
|
|
|
// There may be a better way to do this, but since the projection is specified by the
|
|
|
|
// unified UI code, it can't ask for these columns.
|
|
|
|
stringBuilder.append(',').append(MessageColumns.DISPLAY_NAME)
|
2014-02-06 00:00:58 +00:00
|
|
|
.append(',').append(MessageColumns.FROM_LIST)
|
|
|
|
.append(',').append(MessageColumns.TO_LIST);
|
2013-09-23 21:05:20 +00:00
|
|
|
}
|
|
|
|
|
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
|
2012-12-11 18:37:35 +00:00
|
|
|
* @param unseenOnly <code>true</code> to only return unseen messages
|
2012-06-28 17:40:46 +00:00
|
|
|
* @return the SQLite query to be executed on the EmailProvider database
|
|
|
|
*/
|
2012-12-11 18:37:35 +00:00
|
|
|
private static String genQueryMailboxMessages(String[] uiProjection, final boolean unseenOnly) {
|
2012-08-23 05:25:42 +00:00
|
|
|
StringBuilder sb = genSelect(getMessageListMap(), uiProjection);
|
2013-09-23 21:05:20 +00:00
|
|
|
appendConversationInfoColumns(sb);
|
2013-04-02 02:57:13 +00:00
|
|
|
sb.append(" FROM " + Message.TABLE_NAME + " WHERE " +
|
2013-07-30 00:02:27 +00:00
|
|
|
Message.FLAG_LOADED_SELECTION + " AND " +
|
2014-04-11 21:42:28 +00:00
|
|
|
MessageColumns.MAILBOX_KEY + "=? ");
|
2012-12-11 18:37:35 +00:00
|
|
|
if (unseenOnly) {
|
|
|
|
sb.append("AND ").append(MessageColumns.FLAG_SEEN).append(" = 0 ");
|
2013-09-30 22:32:42 +00:00
|
|
|
sb.append("AND ").append(MessageColumns.FLAG_READ).append(" = 0 ");
|
2012-12-11 18:37:35 +00:00
|
|
|
}
|
2013-06-06 17:08:22 +00:00
|
|
|
sb.append("ORDER BY " + MessageColumns.TIMESTAMP + " DESC ");
|
2014-01-03 23:43:24 +00:00
|
|
|
sb.append("LIMIT " + UIProvider.CONVERSATION_PROJECTION_QUERY_CURSOR_WINDOW_LIMIT);
|
2012-06-28 17:40:46 +00:00
|
|
|
return sb.toString();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Generate various virtual mailbox SQLite queries, given a projection from UnifiedEmail
|
|
|
|
*
|
|
|
|
* @param uiProjection as passed from UnifiedEmail
|
2012-12-11 18:37:35 +00:00
|
|
|
* @param mailboxId the id of the virtual mailbox
|
|
|
|
* @param unseenOnly <code>true</code> to only return unseen messages
|
2012-06-28 17:40:46 +00:00
|
|
|
* @return the SQLite query to be executed on the EmailProvider database
|
|
|
|
*/
|
2012-12-11 18:37:35 +00:00
|
|
|
private static Cursor getVirtualMailboxMessagesCursor(SQLiteDatabase db, String[] uiProjection,
|
|
|
|
long mailboxId, final boolean unseenOnly) {
|
2012-06-28 17:40:46 +00:00
|
|
|
ContentValues values = new ContentValues();
|
|
|
|
values.put(UIProvider.ConversationColumns.COLOR, CONVERSATION_COLOR);
|
2013-04-05 21:21:25 +00:00
|
|
|
final int virtualMailboxId = getVirtualMailboxType(mailboxId);
|
|
|
|
final String[] selectionArgs;
|
2012-08-23 05:25:42 +00:00
|
|
|
StringBuilder sb = genSelect(getMessageListMap(), uiProjection, values);
|
2013-09-23 21:05:20 +00:00
|
|
|
appendConversationInfoColumns(sb);
|
2013-04-02 02:57:13 +00:00
|
|
|
sb.append(" FROM " + Message.TABLE_NAME + " WHERE " +
|
2013-07-30 00:02:27 +00:00
|
|
|
Message.FLAG_LOADED_SELECTION + " AND ");
|
2012-06-28 17:40:46 +00:00
|
|
|
if (isCombinedMailbox(mailboxId)) {
|
2013-04-05 21:21:25 +00:00
|
|
|
if (unseenOnly) {
|
|
|
|
sb.append(MessageColumns.FLAG_SEEN).append("=0 AND ");
|
2013-09-30 22:32:42 +00:00
|
|
|
sb.append(MessageColumns.FLAG_READ).append("=0 AND ");
|
2012-06-28 17:40:46 +00:00
|
|
|
}
|
2013-04-05 21:21:25 +00:00
|
|
|
selectionArgs = null;
|
2012-06-28 17:40:46 +00:00
|
|
|
} else {
|
2013-04-05 21:21:25 +00:00
|
|
|
if (virtualMailboxId == Mailbox.TYPE_INBOX) {
|
|
|
|
throw new IllegalArgumentException("No virtual mailbox for: " + mailboxId);
|
2012-06-28 17:40:46 +00:00
|
|
|
}
|
2013-04-05 21:21:25 +00:00
|
|
|
sb.append(MessageColumns.ACCOUNT_KEY).append("=? AND ");
|
|
|
|
selectionArgs = new String[]{getVirtualMailboxAccountIdString(mailboxId)};
|
|
|
|
}
|
|
|
|
switch (getVirtualMailboxType(mailboxId)) {
|
|
|
|
case Mailbox.TYPE_INBOX:
|
2014-04-11 21:42:28 +00:00
|
|
|
sb.append(MessageColumns.MAILBOX_KEY + " IN (SELECT " + MailboxColumns._ID +
|
2013-04-05 21:21:25 +00:00
|
|
|
" FROM " + Mailbox.TABLE_NAME + " WHERE " + MailboxColumns.TYPE +
|
|
|
|
"=" + Mailbox.TYPE_INBOX + ")");
|
|
|
|
break;
|
|
|
|
case Mailbox.TYPE_STARRED:
|
|
|
|
sb.append(MessageColumns.FLAG_FAVORITE + "=1");
|
|
|
|
break;
|
2013-06-27 00:19:17 +00:00
|
|
|
case Mailbox.TYPE_UNREAD:
|
2013-04-05 21:21:25 +00:00
|
|
|
sb.append(MessageColumns.FLAG_READ + "=0 AND " + MessageColumns.MAILBOX_KEY +
|
2014-04-11 21:42:28 +00:00
|
|
|
" NOT IN (SELECT " + MailboxColumns._ID + " FROM " + Mailbox.TABLE_NAME +
|
2013-04-05 21:21:25 +00:00
|
|
|
" WHERE " + MailboxColumns.TYPE + "=" + Mailbox.TYPE_TRASH + ")");
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
throw new IllegalArgumentException("No virtual mailbox for: " + mailboxId);
|
2012-06-28 17:40:46 +00:00
|
|
|
}
|
2013-04-05 21:21:25 +00:00
|
|
|
sb.append(" ORDER BY " + MessageColumns.TIMESTAMP + " DESC");
|
|
|
|
return db.rawQuery(sb.toString(), selectionArgs);
|
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
|
|
|
|
*/
|
2012-12-11 18:37:35 +00:00
|
|
|
private static String genQueryConversation(String[] uiProjection) {
|
2012-08-23 05:25:42 +00:00
|
|
|
StringBuilder sb = genSelect(getMessageListMap(), uiProjection);
|
2014-04-11 21:42:28 +00:00
|
|
|
sb.append(" FROM " + Message.TABLE_NAME + " WHERE " + MessageColumns._ID + "=?");
|
2012-06-28 17:40:46 +00:00
|
|
|
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
|
|
|
|
*/
|
2012-12-11 18:37:35 +00:00
|
|
|
private static String genQueryAccountMailboxes(String[] uiProjection) {
|
2012-08-23 05:25:42 +00:00
|
|
|
StringBuilder sb = genSelect(getFolderListMap(), uiProjection);
|
2012-06-28 17:40:46 +00:00
|
|
|
sb.append(" FROM " + Mailbox.TABLE_NAME + " WHERE " + MailboxColumns.ACCOUNT_KEY +
|
|
|
|
"=? AND " + MailboxColumns.TYPE + " < " + Mailbox.TYPE_NOT_EMAIL +
|
2013-06-06 18:06:12 +00:00
|
|
|
" AND " + MailboxColumns.TYPE + " != " + Mailbox.TYPE_SEARCH +
|
2012-06-28 17:40:46 +00:00
|
|
|
" 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
|
|
|
|
*/
|
2012-12-11 18:37:35 +00:00
|
|
|
private static String genQueryAccountAllMailboxes(String[] uiProjection) {
|
2012-08-23 05:25:42 +00:00
|
|
|
StringBuilder sb = genSelect(getFolderListMap(), uiProjection);
|
2012-06-28 17:40:46 +00:00
|
|
|
// 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 +
|
2013-06-06 18:06:12 +00:00
|
|
|
" AND " + MailboxColumns.TYPE + " != " + Mailbox.TYPE_SEARCH +
|
2012-06-28 17:40:46 +00:00
|
|
|
" 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
|
|
|
|
*/
|
2012-12-11 18:37:35 +00:00
|
|
|
private static String genQueryRecentMailboxes(String[] uiProjection) {
|
2012-08-23 05:25:42 +00:00
|
|
|
StringBuilder sb = genSelect(getFolderListMap(), uiProjection);
|
2012-06-28 17:40:46 +00:00
|
|
|
sb.append(" FROM " + Mailbox.TABLE_NAME + " WHERE " + MailboxColumns.ACCOUNT_KEY +
|
|
|
|
"=? AND " + MailboxColumns.TYPE + " < " + Mailbox.TYPE_NOT_EMAIL +
|
2013-06-06 18:06:12 +00:00
|
|
|
" AND " + MailboxColumns.TYPE + " != " + Mailbox.TYPE_SEARCH +
|
2012-06-28 17:40:46 +00:00
|
|
|
" AND " + MailboxColumns.PARENT_KEY + " < 0 AND " +
|
|
|
|
MailboxColumns.LAST_TOUCHED_TIME + " > 0 ORDER BY " +
|
|
|
|
MailboxColumns.LAST_TOUCHED_TIME + " DESC");
|
|
|
|
return sb.toString();
|
|
|
|
}
|
|
|
|
|
2014-03-27 21:33:58 +00:00
|
|
|
private int getFolderCapabilities(EmailServiceInfo info, int mailboxType, long mailboxId) {
|
2013-10-14 03:52:09 +00:00
|
|
|
// Special case for Search folders: only permit delete, do not try to give any other caps.
|
2014-03-27 21:33:58 +00:00
|
|
|
if (mailboxType == Mailbox.TYPE_SEARCH) {
|
2013-10-14 03:52:09 +00:00
|
|
|
return UIProvider.FolderCapabilities.DELETE;
|
|
|
|
}
|
|
|
|
|
2013-08-07 03:20:15 +00:00
|
|
|
// All folders support delete, except drafts.
|
|
|
|
int caps = 0;
|
2014-03-27 21:33:58 +00:00
|
|
|
if (mailboxType != Mailbox.TYPE_DRAFTS) {
|
2013-08-07 03:20:15 +00:00
|
|
|
caps = UIProvider.FolderCapabilities.DELETE;
|
|
|
|
}
|
2012-08-28 17:40:37 +00:00
|
|
|
if (info != null && info.offerLookback) {
|
|
|
|
// Protocols supporting lookback support settings
|
|
|
|
caps |= UIProvider.FolderCapabilities.SUPPORTS_SETTINGS;
|
|
|
|
}
|
2013-10-14 03:52:09 +00:00
|
|
|
|
2014-03-27 21:33:58 +00:00
|
|
|
if (mailboxType == Mailbox.TYPE_MAIL || mailboxType == Mailbox.TYPE_TRASH ||
|
|
|
|
mailboxType == Mailbox.TYPE_JUNK || mailboxType == Mailbox.TYPE_INBOX) {
|
2013-03-14 20:45:56 +00:00
|
|
|
// If the mailbox can accept moved mail, report that as well
|
|
|
|
caps |= UIProvider.FolderCapabilities.CAN_ACCEPT_MOVED_MESSAGES;
|
|
|
|
caps |= UIProvider.FolderCapabilities.ALLOWS_REMOVE_CONVERSATION;
|
|
|
|
}
|
|
|
|
|
2012-08-28 17:40:37 +00:00
|
|
|
// For trash, we don't allow undo
|
2014-03-27 21:33:58 +00:00
|
|
|
if (mailboxType == Mailbox.TYPE_TRASH) {
|
2012-08-28 17:40:37 +00:00
|
|
|
caps = UIProvider.FolderCapabilities.CAN_ACCEPT_MOVED_MESSAGES |
|
2013-05-17 00:55:42 +00:00
|
|
|
UIProvider.FolderCapabilities.ALLOWS_REMOVE_CONVERSATION |
|
2012-08-28 17:40:37 +00:00
|
|
|
UIProvider.FolderCapabilities.DELETE |
|
|
|
|
UIProvider.FolderCapabilities.DELETE_ACTION_FINAL;
|
|
|
|
}
|
|
|
|
if (isVirtualMailbox(mailboxId)) {
|
|
|
|
caps |= UIProvider.FolderCapabilities.IS_VIRTUAL;
|
|
|
|
}
|
2013-10-29 17:35:03 +00:00
|
|
|
|
2013-11-19 23:45:07 +00:00
|
|
|
// If we don't know the protocol or the protocol doesn't support it, don't allow moving
|
|
|
|
// messages
|
|
|
|
if (info == null || !info.offerMoveTo) {
|
2013-10-29 17:35:03 +00:00
|
|
|
caps &= ~UIProvider.FolderCapabilities.CAN_ACCEPT_MOVED_MESSAGES &
|
|
|
|
~UIProvider.FolderCapabilities.ALLOWS_REMOVE_CONVERSATION &
|
|
|
|
~UIProvider.FolderCapabilities.ALLOWS_MOVE_TO_INBOX;
|
|
|
|
}
|
2014-03-27 21:33:58 +00:00
|
|
|
|
|
|
|
// If the mailbox stores outgoing mail, show recipients instead of senders
|
|
|
|
// (however the Drafts folder shows neither senders nor recipients... just the word "Draft")
|
|
|
|
if (mailboxType == Mailbox.TYPE_OUTBOX || mailboxType == Mailbox.TYPE_SENT) {
|
|
|
|
caps |= UIProvider.FolderCapabilities.SHOW_RECIPIENTS;
|
|
|
|
}
|
|
|
|
|
2012-08-28 17:40:37 +00:00
|
|
|
return caps;
|
|
|
|
}
|
|
|
|
|
2012-06-28 17:40:46 +00:00
|
|
|
/**
|
|
|
|
* 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);
|
2013-10-07 21:40:14 +00:00
|
|
|
ContentValues values = new ContentValues(3);
|
2012-06-28 17:40:46 +00:00
|
|
|
if (mSearchParams != null && mailboxId == mSearchParams.mSearchMailboxId) {
|
|
|
|
// "load more" is valid for search results
|
|
|
|
values.put(UIProvider.FolderColumns.LOAD_MORE_URI,
|
|
|
|
uiUriString("uiloadmore", mailboxId));
|
2013-03-31 19:00:14 +00:00
|
|
|
values.put(UIProvider.FolderColumns.CAPABILITIES, UIProvider.FolderCapabilities.DELETE);
|
2012-06-28 17:40:46 +00:00
|
|
|
} 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);
|
2012-08-22 17:45:13 +00:00
|
|
|
// All folders support delete
|
2012-08-24 02:40:47 +00:00
|
|
|
if (info != null && info.offerLoadMore) {
|
2012-08-22 17:45:13 +00:00
|
|
|
// "load more" is valid for protocols not supporting "lookback"
|
2012-06-28 17:40:46 +00:00
|
|
|
values.put(UIProvider.FolderColumns.LOAD_MORE_URI,
|
|
|
|
uiUriString("uiloadmore", mailboxId));
|
2012-09-03 21:42:59 +00:00
|
|
|
}
|
2012-08-28 17:40:37 +00:00
|
|
|
values.put(UIProvider.FolderColumns.CAPABILITIES,
|
2013-10-14 03:52:09 +00:00
|
|
|
getFolderCapabilities(info, mailbox.mType, mailboxId));
|
2013-04-12 02:51:19 +00:00
|
|
|
// The persistent id is used to form a filename, so we must ensure that it doesn't
|
|
|
|
// include illegal characters (such as '/'). Only perform the encoding if this
|
|
|
|
// query wants the persistent id.
|
|
|
|
boolean shouldEncodePersistentId = false;
|
|
|
|
if (uiProjection == null) {
|
|
|
|
shouldEncodePersistentId = true;
|
|
|
|
} else {
|
|
|
|
for (final String column : uiProjection) {
|
|
|
|
if (TextUtils.equals(column, UIProvider.FolderColumns.PERSISTENT_ID)) {
|
|
|
|
shouldEncodePersistentId = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (shouldEncodePersistentId) {
|
|
|
|
values.put(UIProvider.FolderColumns.PERSISTENT_ID,
|
|
|
|
Base64.encodeToString(mailbox.mServerId.getBytes(),
|
|
|
|
Base64.URL_SAFE | Base64.NO_WRAP | Base64.NO_PADDING));
|
|
|
|
}
|
2012-08-28 17:40:37 +00:00
|
|
|
}
|
2012-06-28 17:40:46 +00:00
|
|
|
}
|
2012-08-23 05:25:42 +00:00
|
|
|
StringBuilder sb = genSelect(getFolderListMap(), uiProjection, values);
|
2014-04-11 21:42:28 +00:00
|
|
|
sb.append(" FROM " + Mailbox.TABLE_NAME + " WHERE " + MailboxColumns._ID + "=?");
|
2012-06-28 17:40:46 +00:00
|
|
|
return sb.toString();
|
|
|
|
}
|
|
|
|
|
2013-04-04 09:42:48 +00:00
|
|
|
public static final String LEGACY_AUTHORITY = "ui.email.android.com";
|
|
|
|
private static final Uri BASE_EXTERNAL_URI = Uri.parse("content://" + LEGACY_AUTHORITY);
|
2012-06-28 17:40:46 +00:00
|
|
|
|
|
|
|
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();
|
|
|
|
}
|
|
|
|
|
2012-12-11 18:37:35 +00:00
|
|
|
private static String getBits(int bitField) {
|
2012-09-06 21:29:31 +00:00
|
|
|
StringBuilder sb = new StringBuilder(" ");
|
|
|
|
for (int i = 0; i < 32; i++, bitField >>= 1) {
|
|
|
|
if ((bitField & 1) != 0) {
|
2013-09-06 23:05:39 +00:00
|
|
|
sb.append(i)
|
|
|
|
.append(" ");
|
2012-09-06 21:29:31 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return sb.toString();
|
|
|
|
}
|
|
|
|
|
2013-08-01 15:47:51 +00:00
|
|
|
private static int getCapabilities(Context context, long accountId) {
|
2013-10-18 21:04:29 +00:00
|
|
|
final Account account = Account.restoreAccountWithId(context, accountId);
|
|
|
|
if (account == null) {
|
|
|
|
LogUtils.d(TAG, "Account %d not found during getCapabilities", accountId);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
// Account capabilities are based on protocol -- different protocols (and, for EAS,
|
|
|
|
// different protocol versions) support different feature sets.
|
|
|
|
final String protocol = account.getProtocol(context);
|
|
|
|
int capabilities;
|
|
|
|
if (TextUtils.equals(context.getString(R.string.protocol_imap), protocol) ||
|
|
|
|
TextUtils.equals(context.getString(R.string.protocol_legacy_imap), protocol)) {
|
|
|
|
capabilities = AccountCapabilities.SYNCABLE_FOLDERS |
|
2014-01-28 19:10:05 +00:00
|
|
|
AccountCapabilities.SERVER_SEARCH |
|
2013-10-18 21:04:29 +00:00
|
|
|
AccountCapabilities.FOLDER_SERVER_SEARCH |
|
|
|
|
AccountCapabilities.UNDO |
|
|
|
|
AccountCapabilities.DISCARD_CONVERSATION_DRAFTS;
|
|
|
|
} else if (TextUtils.equals(context.getString(R.string.protocol_pop3), protocol)) {
|
|
|
|
capabilities = AccountCapabilities.UNDO |
|
|
|
|
AccountCapabilities.DISCARD_CONVERSATION_DRAFTS;
|
|
|
|
} else if (TextUtils.equals(context.getString(R.string.protocol_eas), protocol)) {
|
|
|
|
final String easVersion = account.mProtocolVersion;
|
|
|
|
double easVersionDouble = 2.5D;
|
|
|
|
if (easVersion != null) {
|
|
|
|
try {
|
|
|
|
easVersionDouble = Double.parseDouble(easVersion);
|
|
|
|
} catch (final NumberFormatException e) {
|
|
|
|
// Use the default (lowest) set of capabilities.
|
|
|
|
}
|
2012-09-06 21:29:31 +00:00
|
|
|
}
|
2013-10-18 21:04:29 +00:00
|
|
|
if (easVersionDouble >= 12.0D) {
|
|
|
|
capabilities = AccountCapabilities.SYNCABLE_FOLDERS |
|
|
|
|
AccountCapabilities.SERVER_SEARCH |
|
|
|
|
AccountCapabilities.FOLDER_SERVER_SEARCH |
|
|
|
|
AccountCapabilities.SMART_REPLY |
|
|
|
|
AccountCapabilities.UNDO |
|
|
|
|
AccountCapabilities.DISCARD_CONVERSATION_DRAFTS;
|
|
|
|
} else {
|
|
|
|
capabilities = AccountCapabilities.SYNCABLE_FOLDERS |
|
|
|
|
AccountCapabilities.SMART_REPLY |
|
|
|
|
AccountCapabilities.UNDO |
|
|
|
|
AccountCapabilities.DISCARD_CONVERSATION_DRAFTS;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
LogUtils.w(TAG, "Unknown protocol for account %d", accountId);
|
|
|
|
return 0;
|
2012-07-25 19:23:01 +00:00
|
|
|
}
|
2013-10-18 21:04:29 +00:00
|
|
|
LogUtils.d(TAG, "getCapabilities() for %d (protocol %s): 0x%x %s", accountId, protocol,
|
|
|
|
capabilities, getBits(capabilities));
|
2013-03-09 00:54:50 +00:00
|
|
|
|
|
|
|
// If the configuration states that feedback is supported, add that capability
|
|
|
|
final Resources res = context.getResources();
|
|
|
|
if (res.getBoolean(R.bool.feedback_supported)) {
|
2014-04-10 18:52:45 +00:00
|
|
|
capabilities |= AccountCapabilities.SEND_FEEDBACK;
|
2013-03-09 00:54:50 +00:00
|
|
|
}
|
2014-03-03 21:01:04 +00:00
|
|
|
|
|
|
|
// If we can find a help URL then add the Help capability
|
|
|
|
if (!TextUtils.isEmpty(context.getResources().getString(R.string.help_uri))) {
|
2014-04-10 18:52:45 +00:00
|
|
|
capabilities |= AccountCapabilities.HELP_CONTENT;
|
2014-03-03 21:01:04 +00:00
|
|
|
}
|
|
|
|
|
2014-04-10 18:52:45 +00:00
|
|
|
capabilities |= AccountCapabilities.EMPTY_TRASH;
|
|
|
|
|
2013-10-18 21:04:29 +00:00
|
|
|
// TODO: Should this be stored per-account, or some other mechanism?
|
2014-04-10 18:52:45 +00:00
|
|
|
capabilities |= AccountCapabilities.NESTED_FOLDERS;
|
2013-09-10 18:14:59 +00:00
|
|
|
|
2014-05-20 21:15:12 +00:00
|
|
|
// sanitization happens lazily in the EmailMessageCursor as HTML email bodies are requested
|
|
|
|
capabilities |= UIProvider.AccountCapabilities.SANITIZED_HTML;
|
|
|
|
|
2012-07-25 19:23:01 +00:00
|
|
|
return capabilities;
|
|
|
|
}
|
|
|
|
|
2012-06-28 17:40:46 +00:00
|
|
|
/**
|
|
|
|
* Generate a "single account" SQLite query, given a projection from UnifiedEmail
|
|
|
|
*
|
|
|
|
* @param uiProjection as passed from UnifiedEmail
|
2013-08-14 18:05:19 +00:00
|
|
|
* @param id account row ID
|
2012-06-28 17:40:46 +00:00
|
|
|
* @return the SQLite query to be executed on the EmailProvider database
|
|
|
|
*/
|
|
|
|
private String genQueryAccount(String[] uiProjection, String id) {
|
2012-07-20 21:57:40 +00:00
|
|
|
final ContentValues values = new ContentValues();
|
|
|
|
final long accountId = Long.parseLong(id);
|
2012-07-25 19:23:01 +00:00
|
|
|
final Context context = getContext();
|
2012-06-28 17:40:46 +00:00
|
|
|
|
2013-04-25 20:52:49 +00:00
|
|
|
EmailServiceInfo info = null;
|
|
|
|
|
2013-04-15 19:57:35 +00:00
|
|
|
// TODO: If uiProjection is null, this will NPE. We should do everything here if it's null.
|
2012-07-20 21:57:40 +00:00
|
|
|
final Set<String> projectionColumns = ImmutableSet.copyOf(uiProjection);
|
|
|
|
|
|
|
|
if (projectionColumns.contains(UIProvider.AccountColumns.CAPABILITIES)) {
|
|
|
|
// Get account capabilities from the service
|
2012-07-25 19:23:01 +00:00
|
|
|
values.put(UIProvider.AccountColumns.CAPABILITIES, getCapabilities(context, accountId));
|
2012-07-20 21:57:40 +00:00
|
|
|
}
|
|
|
|
if (projectionColumns.contains(UIProvider.AccountColumns.SETTINGS_INTENT_URI)) {
|
|
|
|
values.put(UIProvider.AccountColumns.SETTINGS_INTENT_URI,
|
|
|
|
getExternalUriString("settings", id));
|
|
|
|
}
|
|
|
|
if (projectionColumns.contains(UIProvider.AccountColumns.COMPOSE_URI)) {
|
|
|
|
values.put(UIProvider.AccountColumns.COMPOSE_URI,
|
|
|
|
getExternalUriStringEmail2("compose", id));
|
|
|
|
}
|
2014-01-30 20:00:03 +00:00
|
|
|
if (projectionColumns.contains(UIProvider.AccountColumns.REAUTHENTICATION_INTENT_URI)) {
|
|
|
|
values.put(UIProvider.AccountColumns.REAUTHENTICATION_INTENT_URI,
|
2014-07-11 17:08:20 +00:00
|
|
|
HeadlessAccountSettingsLoader.getIncomingSettingsUri(accountId)
|
|
|
|
.toString());
|
2014-01-30 20:00:03 +00:00
|
|
|
}
|
2012-07-20 21:57:40 +00:00
|
|
|
if (projectionColumns.contains(UIProvider.AccountColumns.MIME_TYPE)) {
|
|
|
|
values.put(UIProvider.AccountColumns.MIME_TYPE, EMAIL_APP_MIME_TYPE);
|
|
|
|
}
|
|
|
|
if (projectionColumns.contains(UIProvider.AccountColumns.COLOR)) {
|
|
|
|
values.put(UIProvider.AccountColumns.COLOR, ACCOUNT_COLOR);
|
|
|
|
}
|
|
|
|
|
2014-06-12 23:20:14 +00:00
|
|
|
// TODO: if we're getting the values out of MailPrefs then we don't need to be passing the
|
|
|
|
// values this way
|
2013-02-14 22:34:33 +00:00
|
|
|
final MailPrefs mailPrefs = MailPrefs.get(getContext());
|
2012-07-20 21:57:40 +00:00
|
|
|
if (projectionColumns.contains(UIProvider.AccountColumns.SettingsColumns.CONFIRM_DELETE)) {
|
|
|
|
values.put(UIProvider.AccountColumns.SettingsColumns.CONFIRM_DELETE,
|
2014-06-12 23:20:14 +00:00
|
|
|
mailPrefs.getConfirmDelete() ? "1" : "0");
|
2012-07-20 21:57:40 +00:00
|
|
|
}
|
|
|
|
if (projectionColumns.contains(UIProvider.AccountColumns.SettingsColumns.CONFIRM_SEND)) {
|
|
|
|
values.put(UIProvider.AccountColumns.SettingsColumns.CONFIRM_SEND,
|
2014-06-12 23:20:14 +00:00
|
|
|
mailPrefs.getConfirmSend() ? "1" : "0");
|
2012-07-20 21:57:40 +00:00
|
|
|
}
|
2012-07-26 16:22:22 +00:00
|
|
|
if (projectionColumns.contains(UIProvider.AccountColumns.SettingsColumns.SWIPE)) {
|
|
|
|
values.put(UIProvider.AccountColumns.SettingsColumns.SWIPE,
|
2013-04-23 01:31:26 +00:00
|
|
|
mailPrefs.getConversationListSwipeActionInteger(false));
|
2012-07-26 16:22:22 +00:00
|
|
|
}
|
2012-07-20 21:57:40 +00:00
|
|
|
if (projectionColumns.contains(
|
2013-04-11 21:16:01 +00:00
|
|
|
UIProvider.AccountColumns.SettingsColumns.CONV_LIST_ICON)) {
|
|
|
|
values.put(UIProvider.AccountColumns.SettingsColumns.CONV_LIST_ICON,
|
2013-07-16 00:59:30 +00:00
|
|
|
getConversationListIcon(mailPrefs));
|
2012-07-20 21:57:40 +00:00
|
|
|
}
|
2013-08-07 01:20:45 +00:00
|
|
|
if (projectionColumns.contains(
|
|
|
|
UIProvider.AccountColumns.SettingsColumns.CONV_LIST_ATTACHMENT_PREVIEWS)) {
|
|
|
|
values.put(UIProvider.AccountColumns.SettingsColumns.CONV_LIST_ATTACHMENT_PREVIEWS,
|
2013-09-13 07:43:55 +00:00
|
|
|
"0");
|
2013-08-07 01:20:45 +00:00
|
|
|
}
|
2012-07-20 21:57:40 +00:00
|
|
|
if (projectionColumns.contains(UIProvider.AccountColumns.SettingsColumns.AUTO_ADVANCE)) {
|
|
|
|
values.put(UIProvider.AccountColumns.SettingsColumns.AUTO_ADVANCE,
|
2014-06-12 23:20:14 +00:00
|
|
|
Integer.toString(mailPrefs.getAutoAdvanceMode()));
|
2012-07-20 21:57:40 +00:00
|
|
|
}
|
2012-12-11 18:37:35 +00:00
|
|
|
// Set default inbox, if we've got an inbox; otherwise, say initial sync needed
|
2013-06-12 02:56:44 +00:00
|
|
|
final long inboxMailboxId =
|
|
|
|
Mailbox.findMailboxOfType(context, accountId, Mailbox.TYPE_INBOX);
|
2012-07-20 21:57:40 +00:00
|
|
|
if (projectionColumns.contains(UIProvider.AccountColumns.SettingsColumns.DEFAULT_INBOX) &&
|
2013-06-12 02:56:44 +00:00
|
|
|
inboxMailboxId != Mailbox.NO_MAILBOX) {
|
2012-06-28 17:40:46 +00:00
|
|
|
values.put(UIProvider.AccountColumns.SettingsColumns.DEFAULT_INBOX,
|
2013-06-12 02:56:44 +00:00
|
|
|
uiUriString("uifolder", inboxMailboxId));
|
2014-05-01 19:45:09 +00:00
|
|
|
} else {
|
|
|
|
values.put(UIProvider.AccountColumns.SettingsColumns.DEFAULT_INBOX,
|
|
|
|
uiUriString("uiinbox", accountId));
|
2012-07-20 21:57:40 +00:00
|
|
|
}
|
|
|
|
if (projectionColumns.contains(
|
|
|
|
UIProvider.AccountColumns.SettingsColumns.DEFAULT_INBOX_NAME) &&
|
2013-06-12 02:56:44 +00:00
|
|
|
inboxMailboxId != Mailbox.NO_MAILBOX) {
|
2012-07-19 17:58:50 +00:00
|
|
|
values.put(UIProvider.AccountColumns.SettingsColumns.DEFAULT_INBOX_NAME,
|
2013-06-12 02:56:44 +00:00
|
|
|
Mailbox.getDisplayName(context, inboxMailboxId));
|
2012-07-20 21:57:40 +00:00
|
|
|
}
|
|
|
|
if (projectionColumns.contains(UIProvider.AccountColumns.SYNC_STATUS)) {
|
2013-06-12 02:56:44 +00:00
|
|
|
if (inboxMailboxId != Mailbox.NO_MAILBOX) {
|
2012-07-20 21:57:40 +00:00
|
|
|
values.put(UIProvider.AccountColumns.SYNC_STATUS, UIProvider.SyncStatus.NO_SYNC);
|
|
|
|
} else {
|
|
|
|
values.put(UIProvider.AccountColumns.SYNC_STATUS,
|
|
|
|
UIProvider.SyncStatus.INITIAL_SYNC_NEEDED);
|
|
|
|
}
|
2012-06-28 17:40:46 +00:00
|
|
|
}
|
2014-05-20 21:15:12 +00:00
|
|
|
if (projectionColumns.contains(UIProvider.AccountColumns.ENABLE_MESSAGE_TRANSFORMS)) {
|
|
|
|
// Email is now sanitized, which grants the ability to inject beautifying javascript.
|
|
|
|
values.put(UIProvider.AccountColumns.ENABLE_MESSAGE_TRANSFORMS, 1);
|
|
|
|
}
|
2012-07-31 23:10:05 +00:00
|
|
|
if (projectionColumns.contains(
|
2014-03-07 05:46:24 +00:00
|
|
|
UIProvider.AccountColumns.SettingsColumns.IMPORTANCE_MARKERS_ENABLED)) {
|
|
|
|
// Email doesn't support priority inbox, so always state importance markers disabled.
|
|
|
|
values.put(UIProvider.AccountColumns.SettingsColumns.IMPORTANCE_MARKERS_ENABLED, "0");
|
|
|
|
}
|
|
|
|
if (projectionColumns.contains(
|
|
|
|
UIProvider.AccountColumns.SettingsColumns.SHOW_CHEVRONS_ENABLED)) {
|
|
|
|
// Email doesn't support priority inbox, so always state show chevrons disabled.
|
|
|
|
values.put(UIProvider.AccountColumns.SettingsColumns.SHOW_CHEVRONS_ENABLED, "0");
|
2012-07-31 23:10:05 +00:00
|
|
|
}
|
2012-09-06 17:19:34 +00:00
|
|
|
if (projectionColumns.contains(
|
|
|
|
UIProvider.AccountColumns.SettingsColumns.SETUP_INTENT_URI)) {
|
2012-09-08 20:08:50 +00:00
|
|
|
// Set the setup intent if needed
|
|
|
|
// TODO We should clarify/document the trash/setup relationship
|
2012-09-06 17:19:34 +00:00
|
|
|
long trashId = Mailbox.findMailboxOfType(context, accountId, Mailbox.TYPE_TRASH);
|
|
|
|
if (trashId == Mailbox.NO_MAILBOX) {
|
2013-04-25 20:52:49 +00:00
|
|
|
info = EmailServiceUtils.getServiceInfoForAccount(context, accountId);
|
2012-09-08 20:08:50 +00:00
|
|
|
if (info != null && info.requiresSetup) {
|
2012-09-07 21:14:19 +00:00
|
|
|
values.put(UIProvider.AccountColumns.SettingsColumns.SETUP_INTENT_URI,
|
|
|
|
getExternalUriString("setup", id));
|
|
|
|
}
|
2012-09-06 17:19:34 +00:00
|
|
|
}
|
|
|
|
}
|
2013-04-15 19:57:35 +00:00
|
|
|
if (projectionColumns.contains(UIProvider.AccountColumns.TYPE)) {
|
|
|
|
final String type;
|
2013-04-25 20:52:49 +00:00
|
|
|
if (info == null) {
|
|
|
|
info = EmailServiceUtils.getServiceInfoForAccount(context, accountId);
|
|
|
|
}
|
|
|
|
if (info != null) {
|
|
|
|
type = info.accountType;
|
2013-04-15 19:57:35 +00:00
|
|
|
} else {
|
|
|
|
type = "unknown";
|
|
|
|
}
|
|
|
|
|
|
|
|
values.put(UIProvider.AccountColumns.TYPE, type);
|
|
|
|
}
|
2013-06-12 02:56:44 +00:00
|
|
|
if (projectionColumns.contains(UIProvider.AccountColumns.SettingsColumns.MOVE_TO_INBOX) &&
|
|
|
|
inboxMailboxId != Mailbox.NO_MAILBOX) {
|
|
|
|
values.put(UIProvider.AccountColumns.SettingsColumns.MOVE_TO_INBOX,
|
|
|
|
uiUriString("uifolder", inboxMailboxId));
|
|
|
|
}
|
2013-08-07 15:31:29 +00:00
|
|
|
if (projectionColumns.contains(UIProvider.AccountColumns.SYNC_AUTHORITY)) {
|
|
|
|
values.put(UIProvider.AccountColumns.SYNC_AUTHORITY, EmailContent.AUTHORITY);
|
|
|
|
}
|
2013-08-14 18:05:19 +00:00
|
|
|
if (projectionColumns.contains(UIProvider.AccountColumns.QUICK_RESPONSE_URI)) {
|
|
|
|
values.put(UIProvider.AccountColumns.QUICK_RESPONSE_URI,
|
|
|
|
combinedUriString("quickresponse/account", id));
|
|
|
|
}
|
2014-06-20 21:32:15 +00:00
|
|
|
if (projectionColumns.contains(UIProvider.AccountColumns.SETTINGS_FRAGMENT_CLASS)) {
|
|
|
|
values.put(UIProvider.AccountColumns.SETTINGS_FRAGMENT_CLASS,
|
|
|
|
AccountSettingsFragment.class.getName());
|
|
|
|
}
|
2013-10-04 20:25:26 +00:00
|
|
|
if (projectionColumns.contains(UIProvider.AccountColumns.SettingsColumns.REPLY_BEHAVIOR)) {
|
|
|
|
values.put(UIProvider.AccountColumns.SettingsColumns.REPLY_BEHAVIOR,
|
|
|
|
mailPrefs.getDefaultReplyAll()
|
|
|
|
? UIProvider.DefaultReplyBehavior.REPLY_ALL
|
|
|
|
: UIProvider.DefaultReplyBehavior.REPLY);
|
|
|
|
}
|
2013-12-19 17:50:01 +00:00
|
|
|
if (projectionColumns.contains(UIProvider.AccountColumns.SettingsColumns.SHOW_IMAGES)) {
|
|
|
|
values.put(UIProvider.AccountColumns.SettingsColumns.SHOW_IMAGES,
|
|
|
|
Settings.ShowImages.ASK_FIRST);
|
|
|
|
}
|
2013-04-15 19:57:35 +00:00
|
|
|
|
2013-03-09 00:54:50 +00:00
|
|
|
final StringBuilder sb = genSelect(getAccountListMap(getContext()), uiProjection, values);
|
2014-04-11 21:42:28 +00:00
|
|
|
sb.append(" FROM " + Account.TABLE_NAME + " WHERE " + AccountColumns._ID + "=?");
|
2012-06-28 17:40:46 +00:00
|
|
|
return sb.toString();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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;
|
|
|
|
}
|
|
|
|
|
2013-04-19 22:06:11 +00:00
|
|
|
public static final long COMBINED_ACCOUNT_ID = 0x10000000;
|
2012-06-28 17:40:46 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* 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);
|
|
|
|
}
|
|
|
|
|
2013-04-19 22:06:11 +00:00
|
|
|
public static long getVirtualMailboxId(long accountId, int type) {
|
2012-06-28 17:40:46 +00:00
|
|
|
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) {
|
2013-06-26 22:49:12 +00:00
|
|
|
final long lastUsedAccountId =
|
|
|
|
Preferences.getPreferences(getContext()).getLastUsedAccountId();
|
|
|
|
final long id = Account.getDefaultAccountId(getContext(), lastUsedAccountId);
|
2012-06-28 17:40:46 +00:00
|
|
|
if (id == Account.NO_ACCOUNT) return;
|
2012-08-03 21:10:32 +00:00
|
|
|
|
|
|
|
// Build a map of the requested columns to the appropriate positions
|
|
|
|
final ImmutableMap.Builder<String, Integer> builder =
|
|
|
|
new ImmutableMap.Builder<String, Integer>();
|
|
|
|
final String[] columnNames = mc.getColumnNames();
|
|
|
|
for (int i = 0; i < columnNames.length; i++) {
|
|
|
|
builder.put(columnNames[i], i);
|
|
|
|
}
|
|
|
|
final Map<String, Integer> colPosMap = builder.build();
|
|
|
|
|
2013-05-07 02:55:37 +00:00
|
|
|
final MailPrefs mailPrefs = MailPrefs.get(getContext());
|
|
|
|
|
2012-08-03 21:10:32 +00:00
|
|
|
final Object[] values = new Object[columnNames.length];
|
|
|
|
if (colPosMap.containsKey(BaseColumns._ID)) {
|
|
|
|
values[colPosMap.get(BaseColumns._ID)] = 0;
|
|
|
|
}
|
|
|
|
if (colPosMap.containsKey(UIProvider.AccountColumns.CAPABILITIES)) {
|
|
|
|
values[colPosMap.get(UIProvider.AccountColumns.CAPABILITIES)] =
|
2014-06-26 21:18:01 +00:00
|
|
|
AccountCapabilities.UNDO | AccountCapabilities.VIRTUAL_ACCOUNT;
|
2012-08-03 21:10:32 +00:00
|
|
|
}
|
|
|
|
if (colPosMap.containsKey(UIProvider.AccountColumns.FOLDER_LIST_URI)) {
|
|
|
|
values[colPosMap.get(UIProvider.AccountColumns.FOLDER_LIST_URI)] =
|
|
|
|
combinedUriString("uifolders", COMBINED_ACCOUNT_ID_STRING);
|
|
|
|
}
|
|
|
|
if (colPosMap.containsKey(UIProvider.AccountColumns.NAME)) {
|
|
|
|
values[colPosMap.get(UIProvider.AccountColumns.NAME)] = getContext().getString(
|
2013-09-24 23:39:49 +00:00
|
|
|
R.string.mailbox_list_account_selector_combined_view);
|
|
|
|
}
|
|
|
|
if (colPosMap.containsKey(UIProvider.AccountColumns.ACCOUNT_MANAGER_NAME)) {
|
|
|
|
values[colPosMap.get(UIProvider.AccountColumns.ACCOUNT_MANAGER_NAME)] =
|
|
|
|
getContext().getString(R.string.mailbox_list_account_selector_combined_view);
|
2012-08-03 21:10:32 +00:00
|
|
|
}
|
2013-04-15 19:57:35 +00:00
|
|
|
if (colPosMap.containsKey(UIProvider.AccountColumns.TYPE)) {
|
|
|
|
values[colPosMap.get(UIProvider.AccountColumns.TYPE)] = "unknown";
|
|
|
|
}
|
2012-08-03 21:10:32 +00:00
|
|
|
if (colPosMap.containsKey(UIProvider.AccountColumns.UNDO_URI)) {
|
|
|
|
values[colPosMap.get(UIProvider.AccountColumns.UNDO_URI)] =
|
2013-03-16 02:32:32 +00:00
|
|
|
"'content://" + EmailContent.AUTHORITY + "/uiundo'";
|
2012-08-03 21:10:32 +00:00
|
|
|
}
|
|
|
|
if (colPosMap.containsKey(UIProvider.AccountColumns.URI)) {
|
|
|
|
values[colPosMap.get(UIProvider.AccountColumns.URI)] =
|
|
|
|
combinedUriString("uiaccount", COMBINED_ACCOUNT_ID_STRING);
|
|
|
|
}
|
|
|
|
if (colPosMap.containsKey(UIProvider.AccountColumns.MIME_TYPE)) {
|
|
|
|
values[colPosMap.get(UIProvider.AccountColumns.MIME_TYPE)] =
|
|
|
|
EMAIL_APP_MIME_TYPE;
|
|
|
|
}
|
|
|
|
if (colPosMap.containsKey(UIProvider.AccountColumns.SETTINGS_INTENT_URI)) {
|
|
|
|
values[colPosMap.get(UIProvider.AccountColumns.SETTINGS_INTENT_URI)] =
|
|
|
|
getExternalUriString("settings", COMBINED_ACCOUNT_ID_STRING);
|
|
|
|
}
|
|
|
|
if (colPosMap.containsKey(UIProvider.AccountColumns.COMPOSE_URI)) {
|
|
|
|
values[colPosMap.get(UIProvider.AccountColumns.COMPOSE_URI)] =
|
|
|
|
getExternalUriStringEmail2("compose", Long.toString(id));
|
|
|
|
}
|
2012-06-28 17:40:46 +00:00
|
|
|
|
2012-08-03 21:10:32 +00:00
|
|
|
if (colPosMap.containsKey(UIProvider.AccountColumns.SettingsColumns.AUTO_ADVANCE)) {
|
|
|
|
values[colPosMap.get(UIProvider.AccountColumns.SettingsColumns.AUTO_ADVANCE)] =
|
2014-06-12 23:20:14 +00:00
|
|
|
Integer.toString(mailPrefs.getAutoAdvanceMode());
|
2012-08-03 21:10:32 +00:00
|
|
|
}
|
|
|
|
if (colPosMap.containsKey(UIProvider.AccountColumns.SettingsColumns.SNAP_HEADERS)) {
|
|
|
|
values[colPosMap.get(UIProvider.AccountColumns.SettingsColumns.SNAP_HEADERS)] =
|
|
|
|
Integer.toString(UIProvider.SnapHeaderValue.ALWAYS);
|
|
|
|
}
|
2012-06-28 17:40:46 +00:00
|
|
|
//.add(UIProvider.SettingsColumns.SIGNATURE, AccountColumns.SIGNATURE)
|
2012-08-03 21:10:32 +00:00
|
|
|
if (colPosMap.containsKey(UIProvider.AccountColumns.SettingsColumns.REPLY_BEHAVIOR)) {
|
|
|
|
values[colPosMap.get(UIProvider.AccountColumns.SettingsColumns.REPLY_BEHAVIOR)] =
|
2013-05-07 02:55:37 +00:00
|
|
|
Integer.toString(mailPrefs.getDefaultReplyAll()
|
|
|
|
? UIProvider.DefaultReplyBehavior.REPLY_ALL
|
|
|
|
: UIProvider.DefaultReplyBehavior.REPLY);
|
2012-08-03 21:10:32 +00:00
|
|
|
}
|
2013-04-11 21:16:01 +00:00
|
|
|
if (colPosMap.containsKey(UIProvider.AccountColumns.SettingsColumns.CONV_LIST_ICON)) {
|
|
|
|
values[colPosMap.get(UIProvider.AccountColumns.SettingsColumns.CONV_LIST_ICON)] =
|
2013-07-16 00:59:30 +00:00
|
|
|
getConversationListIcon(mailPrefs);
|
2012-08-03 21:10:32 +00:00
|
|
|
}
|
2013-08-07 01:20:45 +00:00
|
|
|
if (colPosMap.containsKey(
|
|
|
|
UIProvider.AccountColumns.SettingsColumns.CONV_LIST_ATTACHMENT_PREVIEWS)) {
|
2013-09-13 07:43:55 +00:00
|
|
|
values[colPosMap.get(
|
|
|
|
UIProvider.AccountColumns.SettingsColumns.CONV_LIST_ATTACHMENT_PREVIEWS)] = 0;
|
2013-08-07 01:20:45 +00:00
|
|
|
}
|
2012-08-03 21:10:32 +00:00
|
|
|
if (colPosMap.containsKey(UIProvider.AccountColumns.SettingsColumns.CONFIRM_DELETE)) {
|
|
|
|
values[colPosMap.get(UIProvider.AccountColumns.SettingsColumns.CONFIRM_DELETE)] =
|
2014-06-12 23:20:14 +00:00
|
|
|
mailPrefs.getConfirmDelete() ? 1 : 0;
|
2012-08-03 21:10:32 +00:00
|
|
|
}
|
|
|
|
if (colPosMap.containsKey(UIProvider.AccountColumns.SettingsColumns.CONFIRM_ARCHIVE)) {
|
|
|
|
values[colPosMap.get(
|
|
|
|
UIProvider.AccountColumns.SettingsColumns.CONFIRM_ARCHIVE)] = 0;
|
|
|
|
}
|
|
|
|
if (colPosMap.containsKey(UIProvider.AccountColumns.SettingsColumns.CONFIRM_SEND)) {
|
|
|
|
values[colPosMap.get(UIProvider.AccountColumns.SettingsColumns.CONFIRM_SEND)] =
|
2014-06-12 23:20:14 +00:00
|
|
|
mailPrefs.getConfirmSend() ? 1 : 0;
|
2012-08-03 21:10:32 +00:00
|
|
|
}
|
|
|
|
if (colPosMap.containsKey(UIProvider.AccountColumns.SettingsColumns.DEFAULT_INBOX)) {
|
|
|
|
values[colPosMap.get(UIProvider.AccountColumns.SettingsColumns.DEFAULT_INBOX)] =
|
|
|
|
combinedUriString("uifolder", combinedMailboxId(Mailbox.TYPE_INBOX));
|
|
|
|
}
|
2013-06-12 02:56:44 +00:00
|
|
|
if (colPosMap.containsKey(UIProvider.AccountColumns.SettingsColumns.MOVE_TO_INBOX)) {
|
|
|
|
values[colPosMap.get(UIProvider.AccountColumns.SettingsColumns.MOVE_TO_INBOX)] =
|
|
|
|
combinedUriString("uifolder", combinedMailboxId(Mailbox.TYPE_INBOX));
|
|
|
|
}
|
2013-10-30 17:07:59 +00:00
|
|
|
if (colPosMap.containsKey(UIProvider.AccountColumns.SettingsColumns.SHOW_IMAGES)) {
|
|
|
|
values[colPosMap.get(UIProvider.AccountColumns.SettingsColumns.SHOW_IMAGES)] =
|
|
|
|
Settings.ShowImages.ASK_FIRST;
|
|
|
|
}
|
2012-06-28 17:40:46 +00:00
|
|
|
|
|
|
|
mc.addRow(values);
|
|
|
|
}
|
|
|
|
|
2013-08-01 15:47:51 +00:00
|
|
|
private static int getConversationListIcon(MailPrefs mailPrefs) {
|
2013-07-16 00:59:30 +00:00
|
|
|
return mailPrefs.getShowSenderImages() ?
|
|
|
|
UIProvider.ConversationListIcon.SENDER_IMAGE :
|
|
|
|
UIProvider.ConversationListIcon.NONE;
|
|
|
|
}
|
|
|
|
|
2013-10-14 23:32:58 +00:00
|
|
|
private Cursor getVirtualMailboxCursor(long mailboxId, String[] projection) {
|
|
|
|
MatrixCursor mc = new MatrixCursorWithCachedColumns(projection, 1);
|
2012-06-28 17:40:46 +00:00
|
|
|
mc.addRow(getVirtualMailboxRow(getVirtualMailboxAccountId(mailboxId),
|
2013-10-14 23:32:58 +00:00
|
|
|
getVirtualMailboxType(mailboxId), projection));
|
2012-06-28 17:40:46 +00:00
|
|
|
return mc;
|
|
|
|
}
|
|
|
|
|
2013-10-14 23:32:58 +00:00
|
|
|
private Object[] getVirtualMailboxRow(long accountId, int mailboxType, String[] projection) {
|
2013-05-28 20:07:24 +00:00
|
|
|
final long id = getVirtualMailboxId(accountId, mailboxType);
|
|
|
|
final String idString = Long.toString(id);
|
2013-10-14 23:32:58 +00:00
|
|
|
Object[] values = new Object[projection.length];
|
|
|
|
// Not all column values are filled in here, as some are not applicable to virtual mailboxes
|
|
|
|
// The remainder are left null
|
|
|
|
for (int i = 0; i < projection.length; i++) {
|
|
|
|
final String column = projection[i];
|
|
|
|
if (column.equals(UIProvider.FolderColumns._ID)) {
|
|
|
|
values[i] = id;
|
|
|
|
} else if (column.equals(UIProvider.FolderColumns.URI)) {
|
|
|
|
values[i] = combinedUriString("uifolder", idString);
|
|
|
|
} else if (column.equals(UIProvider.FolderColumns.NAME)) {
|
2013-06-27 00:19:17 +00:00
|
|
|
// default empty string since all of these should use resource strings
|
2013-10-14 23:32:58 +00:00
|
|
|
values[i] = getFolderDisplayName(getFolderTypeFromMailboxType(mailboxType), "");
|
|
|
|
} else if (column.equals(UIProvider.FolderColumns.HAS_CHILDREN)) {
|
|
|
|
values[i] = 0;
|
|
|
|
} else if (column.equals(UIProvider.FolderColumns.CAPABILITIES)) {
|
|
|
|
values[i] = UIProvider.FolderCapabilities.DELETE
|
|
|
|
| UIProvider.FolderCapabilities.IS_VIRTUAL;
|
|
|
|
} else if (column.equals(UIProvider.FolderColumns.CONVERSATION_LIST_URI)) {
|
|
|
|
values[i] = combinedUriString("uimessages", idString);
|
|
|
|
} else if (column.equals(UIProvider.FolderColumns.UNREAD_COUNT)) {
|
|
|
|
if (mailboxType == Mailbox.TYPE_INBOX && accountId == COMBINED_ACCOUNT_ID) {
|
2013-05-28 20:07:24 +00:00
|
|
|
final int unreadCount = EmailContent.count(getContext(), Message.CONTENT_URI,
|
2014-04-11 21:42:28 +00:00
|
|
|
MessageColumns.MAILBOX_KEY + " IN (SELECT " + MailboxColumns._ID
|
2013-05-28 20:07:24 +00:00
|
|
|
+ " FROM " + Mailbox.TABLE_NAME + " WHERE " + MailboxColumns.TYPE
|
|
|
|
+ "=" + Mailbox.TYPE_INBOX + ") AND " + MessageColumns.FLAG_READ + "=0",
|
|
|
|
null);
|
2013-10-14 23:32:58 +00:00
|
|
|
values[i] = unreadCount;
|
|
|
|
} else if (mailboxType == Mailbox.TYPE_UNREAD) {
|
|
|
|
final String accountKeyClause;
|
|
|
|
final String[] whereArgs;
|
|
|
|
if (accountId == COMBINED_ACCOUNT_ID) {
|
|
|
|
accountKeyClause = "";
|
|
|
|
whereArgs = null;
|
|
|
|
} else {
|
|
|
|
accountKeyClause = MessageColumns.ACCOUNT_KEY + "= ? AND ";
|
|
|
|
whereArgs = new String[] { Long.toString(accountId) };
|
|
|
|
}
|
|
|
|
final int unreadCount = EmailContent.count(getContext(), Message.CONTENT_URI,
|
|
|
|
accountKeyClause + MessageColumns.FLAG_READ + "=0 AND "
|
2014-04-11 21:42:28 +00:00
|
|
|
+ MessageColumns.MAILBOX_KEY + " NOT IN (SELECT " + MailboxColumns._ID
|
2013-10-14 23:32:58 +00:00
|
|
|
+ " FROM " + Mailbox.TABLE_NAME + " WHERE " + MailboxColumns.TYPE + "="
|
|
|
|
+ Mailbox.TYPE_TRASH + ")", whereArgs);
|
|
|
|
values[i] = unreadCount;
|
|
|
|
} else if (mailboxType == Mailbox.TYPE_STARRED) {
|
|
|
|
final String accountKeyClause;
|
|
|
|
final String[] whereArgs;
|
|
|
|
if (accountId == COMBINED_ACCOUNT_ID) {
|
|
|
|
accountKeyClause = "";
|
|
|
|
whereArgs = null;
|
|
|
|
} else {
|
|
|
|
accountKeyClause = MessageColumns.ACCOUNT_KEY + "= ? AND ";
|
|
|
|
whereArgs = new String[] { Long.toString(accountId) };
|
|
|
|
}
|
|
|
|
final int starredCount = EmailContent.count(getContext(), Message.CONTENT_URI,
|
|
|
|
accountKeyClause + MessageColumns.FLAG_FAVORITE + "=1", whereArgs);
|
|
|
|
values[i] = starredCount;
|
2013-05-28 20:07:24 +00:00
|
|
|
}
|
2013-10-14 23:32:58 +00:00
|
|
|
} else if (column.equals(UIProvider.FolderColumns.ICON_RES_ID)) {
|
|
|
|
if (mailboxType == Mailbox.TYPE_INBOX) {
|
2014-07-08 22:55:40 +00:00
|
|
|
values[i] = R.drawable.ic_drawer_inbox;
|
2013-10-14 23:32:58 +00:00
|
|
|
} else if (mailboxType == Mailbox.TYPE_UNREAD) {
|
2014-07-08 22:55:40 +00:00
|
|
|
values[i] = R.drawable.ic_drawer_unread;
|
2013-10-14 23:32:58 +00:00
|
|
|
} else if (mailboxType == Mailbox.TYPE_STARRED) {
|
2014-07-08 22:55:40 +00:00
|
|
|
values[i] = R.drawable.ic_drawer_starred;
|
2013-05-28 20:07:24 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2012-06-28 17:40:46 +00:00
|
|
|
return values;
|
|
|
|
}
|
|
|
|
|
2014-06-20 07:09:07 +00:00
|
|
|
private Cursor uiAccounts(String[] uiProjection, boolean suppressCombined) {
|
2013-02-26 20:04:29 +00:00
|
|
|
final Context context = getContext();
|
|
|
|
final SQLiteDatabase db = getDatabase(context);
|
|
|
|
final Cursor accountIdCursor =
|
2012-06-28 17:40:46 +00:00
|
|
|
db.rawQuery("select _id from " + Account.TABLE_NAME, new String[0]);
|
2013-02-26 20:04:29 +00:00
|
|
|
final MatrixCursor mc;
|
2012-06-28 17:40:46 +00:00
|
|
|
try {
|
2013-02-26 20:04:29 +00:00
|
|
|
boolean combinedAccount = false;
|
2014-06-20 07:09:07 +00:00
|
|
|
if (!suppressCombined && accountIdCursor.getCount() > 1) {
|
2013-02-26 20:04:29 +00:00
|
|
|
combinedAccount = true;
|
|
|
|
}
|
|
|
|
final Bundle extras = new Bundle();
|
|
|
|
// Email always returns the accurate number of accounts
|
|
|
|
extras.putInt(AccountCursorExtraKeys.ACCOUNTS_LOADED, 1);
|
|
|
|
mc = new MatrixCursorWithExtra(uiProjection, accountIdCursor.getCount(), extras);
|
|
|
|
final Object[] values = new Object[uiProjection.length];
|
2012-06-28 17:40:46 +00:00
|
|
|
while (accountIdCursor.moveToNext()) {
|
2013-02-26 20:04:29 +00:00
|
|
|
final String id = accountIdCursor.getString(0);
|
|
|
|
final Cursor accountCursor =
|
2012-06-28 17:40:46 +00:00
|
|
|
db.rawQuery(genQueryAccount(uiProjection, id), new String[] {id});
|
2013-02-26 20:04:29 +00:00
|
|
|
try {
|
|
|
|
if (accountCursor.moveToNext()) {
|
|
|
|
for (int i = 0; i < uiProjection.length; i++) {
|
|
|
|
values[i] = accountCursor.getString(i);
|
|
|
|
}
|
|
|
|
mc.addRow(values);
|
2012-06-28 17:40:46 +00:00
|
|
|
}
|
2013-02-26 20:04:29 +00:00
|
|
|
} finally {
|
|
|
|
accountCursor.close();
|
2012-06-28 17:40:46 +00:00
|
|
|
}
|
|
|
|
}
|
2012-09-05 16:49:55 +00:00
|
|
|
if (combinedAccount) {
|
|
|
|
addCombinedAccountRow(mc);
|
|
|
|
}
|
2012-06-28 17:40:46 +00:00
|
|
|
} finally {
|
|
|
|
accountIdCursor.close();
|
|
|
|
}
|
2012-07-26 20:45:47 +00:00
|
|
|
mc.setNotificationUri(context.getContentResolver(), UIPROVIDER_ALL_ACCOUNTS_NOTIFIER);
|
2013-02-26 20:04:29 +00:00
|
|
|
|
2012-06-28 17:40:46 +00:00
|
|
|
return mc;
|
|
|
|
}
|
|
|
|
|
2013-08-14 18:05:19 +00:00
|
|
|
private Cursor uiQuickResponseAccount(String[] uiProjection, String account) {
|
|
|
|
final Context context = getContext();
|
|
|
|
final SQLiteDatabase db = getDatabase(context);
|
|
|
|
final StringBuilder sb = genSelect(getQuickResponseMap(), uiProjection);
|
|
|
|
sb.append(" FROM " + QuickResponse.TABLE_NAME);
|
|
|
|
sb.append(" WHERE " + QuickResponse.ACCOUNT_KEY + "=?");
|
|
|
|
final String query = sb.toString();
|
|
|
|
return db.rawQuery(query, new String[] {account});
|
|
|
|
}
|
|
|
|
|
|
|
|
private Cursor uiQuickResponseId(String[] uiProjection, String id) {
|
|
|
|
final Context context = getContext();
|
|
|
|
final SQLiteDatabase db = getDatabase(context);
|
|
|
|
final StringBuilder sb = genSelect(getQuickResponseMap(), uiProjection);
|
|
|
|
sb.append(" FROM " + QuickResponse.TABLE_NAME);
|
2014-04-11 21:42:28 +00:00
|
|
|
sb.append(" WHERE " + QuickResponse._ID + "=?");
|
2013-08-14 18:05:19 +00:00
|
|
|
final String query = sb.toString();
|
|
|
|
return db.rawQuery(query, new String[] {id});
|
|
|
|
}
|
|
|
|
|
|
|
|
private Cursor uiQuickResponse(String[] uiProjection) {
|
|
|
|
final Context context = getContext();
|
|
|
|
final SQLiteDatabase db = getDatabase(context);
|
|
|
|
final StringBuilder sb = genSelect(getQuickResponseMap(), uiProjection);
|
|
|
|
sb.append(" FROM " + QuickResponse.TABLE_NAME);
|
|
|
|
final String query = sb.toString();
|
|
|
|
return db.rawQuery(query, new String[0]);
|
|
|
|
}
|
|
|
|
|
2012-06-28 17:40:46 +00:00
|
|
|
/**
|
|
|
|
* Generate the "attachment list" SQLite query, given a projection from UnifiedEmail
|
|
|
|
*
|
|
|
|
* @param uiProjection as passed from UnifiedEmail
|
2012-07-24 19:57:21 +00:00
|
|
|
* @param contentTypeQueryParameters list of mimeTypes, used as a filter for the attachments
|
|
|
|
* or null if there are no query parameters
|
2012-06-28 17:40:46 +00:00
|
|
|
* @return the SQLite query to be executed on the EmailProvider database
|
|
|
|
*/
|
2012-12-11 18:37:35 +00:00
|
|
|
private static String genQueryAttachments(String[] uiProjection,
|
2012-07-24 19:57:21 +00:00
|
|
|
List<String> contentTypeQueryParameters) {
|
2013-06-21 18:30:49 +00:00
|
|
|
// MAKE SURE THESE VALUES STAY IN SYNC WITH GEN QUERY ATTACHMENT
|
|
|
|
ContentValues values = new ContentValues(1);
|
|
|
|
values.put(UIProvider.AttachmentColumns.SUPPORTS_DOWNLOAD_AGAIN, 1);
|
|
|
|
StringBuilder sb = genSelect(getAttachmentMap(), uiProjection, values);
|
2013-09-06 23:05:39 +00:00
|
|
|
sb.append(" FROM ")
|
|
|
|
.append(Attachment.TABLE_NAME)
|
|
|
|
.append(" WHERE ")
|
|
|
|
.append(AttachmentColumns.MESSAGE_KEY)
|
|
|
|
.append(" =? ");
|
2012-07-24 19:57:21 +00:00
|
|
|
|
|
|
|
// Filter for certain content types.
|
|
|
|
// The filter works by adding LIKE operators for each
|
|
|
|
// content type you wish to request. Content types
|
|
|
|
// are filtered by performing a case-insensitive "starts with"
|
|
|
|
// filter. IE, "image/" would return "image/png" as well as "image/jpeg".
|
|
|
|
if (contentTypeQueryParameters != null && !contentTypeQueryParameters.isEmpty()) {
|
|
|
|
final int size = contentTypeQueryParameters.size();
|
|
|
|
sb.append("AND (");
|
|
|
|
for (int i = 0; i < size; i++) {
|
|
|
|
final String contentType = contentTypeQueryParameters.get(i);
|
2013-09-06 23:05:39 +00:00
|
|
|
sb.append(AttachmentColumns.MIME_TYPE)
|
|
|
|
.append(" LIKE '")
|
|
|
|
.append(contentType)
|
|
|
|
.append("%'");
|
2012-07-24 19:57:21 +00:00
|
|
|
|
|
|
|
if (i != size - 1) {
|
|
|
|
sb.append(" OR ");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
sb.append(")");
|
|
|
|
}
|
2012-06-28 17:40:46 +00:00
|
|
|
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
|
|
|
|
*/
|
2014-04-09 19:59:28 +00:00
|
|
|
private String genQueryAttachment(String[] uiProjection) {
|
2013-06-21 18:30:49 +00:00
|
|
|
// MAKE SURE THESE VALUES STAY IN SYNC WITH GEN QUERY ATTACHMENTS
|
2014-04-09 19:59:28 +00:00
|
|
|
final ContentValues values = new ContentValues(2);
|
|
|
|
values.put(AttachmentColumns.CONTENT_URI, createAttachmentUriColumnSQL());
|
2013-06-21 18:30:49 +00:00
|
|
|
values.put(UIProvider.AttachmentColumns.SUPPORTS_DOWNLOAD_AGAIN, 1);
|
2014-04-09 19:59:28 +00:00
|
|
|
|
|
|
|
return genSelect(getAttachmentMap(), uiProjection, values)
|
|
|
|
.append(" FROM ").append(Attachment.TABLE_NAME)
|
2013-09-06 23:05:39 +00:00
|
|
|
.append(" WHERE ")
|
2014-04-09 19:59:28 +00:00
|
|
|
.append(AttachmentColumns._ID).append(" =? ")
|
|
|
|
.toString();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Generate the "single attachment by Content ID" 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 genQueryAttachmentByMessageIDAndCid(String[] uiProjection) {
|
|
|
|
final ContentValues values = new ContentValues(2);
|
|
|
|
values.put(AttachmentColumns.CONTENT_URI, createAttachmentUriColumnSQL());
|
|
|
|
values.put(UIProvider.AttachmentColumns.SUPPORTS_DOWNLOAD_AGAIN, 1);
|
|
|
|
|
|
|
|
return genSelect(getAttachmentMap(), uiProjection, values)
|
|
|
|
.append(" FROM ").append(Attachment.TABLE_NAME)
|
|
|
|
.append(" WHERE ")
|
|
|
|
.append(AttachmentColumns.MESSAGE_KEY).append(" =? ")
|
|
|
|
.append(" AND ")
|
|
|
|
.append(AttachmentColumns.CONTENT_ID).append(" =? ")
|
|
|
|
.toString();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return a fragment of SQL that is the expression which, when evaluated for a particular
|
|
|
|
* Attachment row, produces the Content URI for the attachment
|
|
|
|
*/
|
|
|
|
private static String createAttachmentUriColumnSQL() {
|
|
|
|
final String uriPrefix = Attachment.ATTACHMENT_PROVIDER_URI_PREFIX;
|
|
|
|
final String accountKey = AttachmentColumns.ACCOUNT_KEY;
|
|
|
|
final String id = AttachmentColumns._ID;
|
|
|
|
final String raw = AttachmentUtilities.FORMAT_RAW;
|
|
|
|
final String contentUri = String.format("%s/' || %s || '/' || %s || '/%s", uriPrefix,
|
|
|
|
accountKey, id, raw);
|
|
|
|
|
|
|
|
return "@CASE " +
|
|
|
|
"WHEN contentUri IS NULL THEN '" + contentUri + "' " +
|
|
|
|
"WHEN contentUri IS NOT NULL THEN contentUri " +
|
|
|
|
"END";
|
2012-06-28 17:40:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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
|
|
|
|
*/
|
2012-12-11 18:37:35 +00:00
|
|
|
private static String genQuerySubfolders(String[] uiProjection) {
|
2012-08-23 05:25:42 +00:00
|
|
|
StringBuilder sb = genSelect(getFolderListMap(), uiProjection);
|
2012-06-28 17:40:46 +00:00
|
|
|
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.
|
2013-09-06 23:05:39 +00:00
|
|
|
* @param uri uri to query
|
|
|
|
* @param uiProjection projection
|
|
|
|
* @return query result cursor
|
2012-06-28 17:40:46 +00:00
|
|
|
*/
|
2013-09-20 21:30:57 +00:00
|
|
|
private Cursor uiFolders(final Uri uri, final String[] uiProjection) {
|
|
|
|
final Context context = getContext();
|
|
|
|
final SQLiteDatabase db = getDatabase(context);
|
|
|
|
final String id = uri.getPathSegments().get(1);
|
|
|
|
|
|
|
|
final Uri notifyUri =
|
|
|
|
UIPROVIDER_FOLDERLIST_NOTIFIER.buildUpon().appendEncodedPath(id).build();
|
|
|
|
|
|
|
|
final Cursor vc = uiVirtualMailboxes(id, uiProjection);
|
|
|
|
vc.setNotificationUri(context.getContentResolver(), notifyUri);
|
2012-06-28 17:40:46 +00:00
|
|
|
if (id.equals(COMBINED_ACCOUNT_ID_STRING)) {
|
2013-09-20 21:30:57 +00:00
|
|
|
return vc;
|
2012-06-28 17:40:46 +00:00
|
|
|
} else {
|
2013-10-14 03:52:09 +00:00
|
|
|
Cursor c = db.rawQuery(genQueryAccountMailboxes(UIProvider.FOLDERS_PROJECTION),
|
|
|
|
new String[] {id});
|
|
|
|
c = getFolderListCursor(c, Long.valueOf(id), uiProjection);
|
2013-08-06 23:09:00 +00:00
|
|
|
c.setNotificationUri(context.getContentResolver(), notifyUri);
|
2014-05-01 19:45:09 +00:00
|
|
|
if (c.getCount() > 0) {
|
|
|
|
Cursor[] cursors = new Cursor[]{vc, c};
|
|
|
|
return new MergeCursor(cursors);
|
|
|
|
} else {
|
|
|
|
return c;
|
|
|
|
}
|
2012-06-28 17:40:46 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-09-20 21:30:57 +00:00
|
|
|
private Cursor uiVirtualMailboxes(final String id, final String[] uiProjection) {
|
|
|
|
final MatrixCursor mc = new MatrixCursorWithCachedColumns(uiProjection);
|
|
|
|
|
|
|
|
if (id.equals(COMBINED_ACCOUNT_ID_STRING)) {
|
2013-10-14 23:32:58 +00:00
|
|
|
mc.addRow(getVirtualMailboxRow(COMBINED_ACCOUNT_ID, Mailbox.TYPE_INBOX, uiProjection));
|
|
|
|
mc.addRow(
|
|
|
|
getVirtualMailboxRow(COMBINED_ACCOUNT_ID, Mailbox.TYPE_STARRED, uiProjection));
|
|
|
|
mc.addRow(getVirtualMailboxRow(COMBINED_ACCOUNT_ID, Mailbox.TYPE_UNREAD, uiProjection));
|
2013-09-20 21:30:57 +00:00
|
|
|
} else {
|
|
|
|
final long acctId = Long.parseLong(id);
|
2013-10-14 23:32:58 +00:00
|
|
|
mc.addRow(getVirtualMailboxRow(acctId, Mailbox.TYPE_STARRED, uiProjection));
|
|
|
|
mc.addRow(getVirtualMailboxRow(acctId, Mailbox.TYPE_UNREAD, uiProjection));
|
2013-09-20 21:30:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return mc;
|
|
|
|
}
|
|
|
|
|
2012-06-28 17:40:46 +00:00
|
|
|
/**
|
|
|
|
* 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.
|
2013-09-06 23:05:39 +00:00
|
|
|
* @param id account id
|
|
|
|
* @return array of URIs
|
2012-06-28 17:40:46 +00:00
|
|
|
*/
|
|
|
|
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.
|
2012-08-23 05:25:42 +00:00
|
|
|
final StringBuilder sb = genSelect(getFolderListMap(), idAndType);
|
2013-09-06 23:05:39 +00:00
|
|
|
sb.append(" FROM ")
|
|
|
|
.append(Mailbox.TABLE_NAME)
|
|
|
|
.append(" WHERE ")
|
|
|
|
.append(MailboxColumns.ACCOUNT_KEY)
|
|
|
|
.append(" = ")
|
|
|
|
.append(id)
|
|
|
|
.append(" AND ")
|
|
|
|
.append(MailboxColumns.TYPE)
|
|
|
|
.append(" IN (")
|
|
|
|
.append(Mailbox.TYPE_SENT)
|
|
|
|
.append(", ")
|
|
|
|
.append(Mailbox.TYPE_DRAFTS)
|
|
|
|
.append(", ")
|
|
|
|
.append(Mailbox.TYPE_STARRED)
|
|
|
|
.append(")");
|
2012-06-28 17:40:46 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2013-05-23 18:31:29 +00:00
|
|
|
/**
|
|
|
|
* Convenience method to create a {@link Folder}
|
2013-09-06 23:05:39 +00:00
|
|
|
* @param context to get a {@link ContentResolver}
|
2013-05-23 18:31:29 +00:00
|
|
|
* @param mailboxId id of the {@link Mailbox} that we want
|
|
|
|
* @return the {@link Folder} or null
|
|
|
|
*/
|
|
|
|
public static Folder getFolder(Context context, long mailboxId) {
|
|
|
|
final ContentResolver resolver = context.getContentResolver();
|
|
|
|
final Cursor fc = resolver.query(EmailProvider.uiUri("uifolder", mailboxId),
|
|
|
|
UIProvider.FOLDERS_PROJECTION, null, null, null);
|
|
|
|
|
|
|
|
if (fc == null) {
|
|
|
|
LogUtils.e(TAG, "Null folder cursor for mailboxId %d", mailboxId);
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
Folder uiFolder = null;
|
|
|
|
try {
|
|
|
|
if (fc.moveToFirst()) {
|
|
|
|
uiFolder = new Folder(fc);
|
|
|
|
}
|
|
|
|
} finally {
|
|
|
|
fc.close();
|
|
|
|
}
|
|
|
|
return uiFolder;
|
|
|
|
}
|
|
|
|
|
2012-09-22 04:18:10 +00:00
|
|
|
static class AttachmentsCursor extends CursorWrapper {
|
|
|
|
private final int mContentUriIndex;
|
|
|
|
private final int mUriIndex;
|
|
|
|
private final Context mContext;
|
2014-06-30 17:21:25 +00:00
|
|
|
private final String[] mContentUriStrings;
|
2012-09-22 04:18:10 +00:00
|
|
|
|
|
|
|
public AttachmentsCursor(Context context, Cursor cursor) {
|
|
|
|
super(cursor);
|
|
|
|
mContentUriIndex = cursor.getColumnIndex(UIProvider.AttachmentColumns.CONTENT_URI);
|
|
|
|
mUriIndex = cursor.getColumnIndex(UIProvider.AttachmentColumns.URI);
|
|
|
|
mContext = context;
|
2014-06-30 17:21:25 +00:00
|
|
|
mContentUriStrings = new String[cursor.getCount()];
|
|
|
|
if (mContentUriIndex == -1) {
|
|
|
|
// Nothing to do here, move along
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
while (cursor.moveToNext()) {
|
|
|
|
final int index = cursor.getPosition();
|
2013-04-02 00:17:32 +00:00
|
|
|
final Uri uri = Uri.parse(getString(mUriIndex));
|
|
|
|
final long id = Long.parseLong(uri.getLastPathSegment());
|
|
|
|
final Attachment att = Attachment.restoreAttachmentWithId(mContext, id);
|
2014-06-30 17:21:25 +00:00
|
|
|
|
|
|
|
if (att == null) {
|
|
|
|
mContentUriStrings[index] = "";
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
Make draft attachments work correctly
b/10968838
The main problem here is that Ui Attachment was always using
a content Uri that was generated using the attachment's account
Id and rowId. This works correctly for attachments in messages
we have received, but it does not work for drafts: Draft attachments
are not stored in the normal place, rather, they are stored in
the cache directory. There is an additional column in the
Attachment table, called cachedFile, which is not replicated in the
Ui attachment. That's okay though, if we have a cachedFile, then
when we are populating a Ui Attachment, we should just use that
for content Uri.
Also, I discoverd that for draft attachments, we were not correctly
setting the account key. That didn't turn out to be the problem,
but I'm fixing it anyway because it will cause problems later on.
Change-Id: I0143ba824f3a5bfcd77f32828931b94d6977626f
2013-09-30 22:06:33 +00:00
|
|
|
if (!TextUtils.isEmpty(att.getCachedFileUri())) {
|
2014-06-30 17:21:25 +00:00
|
|
|
mContentUriStrings[index] = att.getCachedFileUri();
|
|
|
|
continue;
|
Make draft attachments work correctly
b/10968838
The main problem here is that Ui Attachment was always using
a content Uri that was generated using the attachment's account
Id and rowId. This works correctly for attachments in messages
we have received, but it does not work for drafts: Draft attachments
are not stored in the normal place, rather, they are stored in
the cache directory. There is an additional column in the
Attachment table, called cachedFile, which is not replicated in the
Ui attachment. That's okay though, if we have a cachedFile, then
when we are populating a Ui Attachment, we should just use that
for content Uri.
Also, I discoverd that for draft attachments, we were not correctly
setting the account key. That didn't turn out to be the problem,
but I'm fixing it anyway because it will cause problems later on.
Change-Id: I0143ba824f3a5bfcd77f32828931b94d6977626f
2013-09-30 22:06:33 +00:00
|
|
|
}
|
2013-04-02 00:17:32 +00:00
|
|
|
|
|
|
|
final String contentUri;
|
|
|
|
// Until the package installer can handle opening apks from a content:// uri, for
|
|
|
|
// any apk that was successfully saved in external storage, return the
|
|
|
|
// content uri from the attachment
|
|
|
|
if (att.mUiDestination == UIProvider.AttachmentDestination.EXTERNAL &&
|
|
|
|
att.mUiState == UIProvider.AttachmentState.SAVED &&
|
|
|
|
TextUtils.equals(att.mMimeType, MimeType.ANDROID_ARCHIVE)) {
|
|
|
|
contentUri = att.getContentUri();
|
|
|
|
} else {
|
2014-04-08 21:18:56 +00:00
|
|
|
final String attUriString = att.getContentUri();
|
|
|
|
final String authority;
|
|
|
|
if (!TextUtils.isEmpty(attUriString)) {
|
|
|
|
authority = Uri.parse(attUriString).getAuthority();
|
|
|
|
} else {
|
|
|
|
authority = null;
|
|
|
|
}
|
|
|
|
if (TextUtils.equals(authority, Attachment.ATTACHMENT_PROVIDER_AUTHORITY)) {
|
|
|
|
contentUri = attUriString;
|
|
|
|
} else {
|
|
|
|
contentUri = AttachmentUtilities.getAttachmentUri(att.mAccountKey, id)
|
|
|
|
.toString();
|
|
|
|
}
|
2013-04-02 00:17:32 +00:00
|
|
|
}
|
2014-06-30 17:21:25 +00:00
|
|
|
mContentUriStrings[index] = contentUri;
|
Make draft attachments work correctly
b/10968838
The main problem here is that Ui Attachment was always using
a content Uri that was generated using the attachment's account
Id and rowId. This works correctly for attachments in messages
we have received, but it does not work for drafts: Draft attachments
are not stored in the normal place, rather, they are stored in
the cache directory. There is an additional column in the
Attachment table, called cachedFile, which is not replicated in the
Ui attachment. That's okay though, if we have a cachedFile, then
when we are populating a Ui Attachment, we should just use that
for content Uri.
Also, I discoverd that for draft attachments, we were not correctly
setting the account key. That didn't turn out to be the problem,
but I'm fixing it anyway because it will cause problems later on.
Change-Id: I0143ba824f3a5bfcd77f32828931b94d6977626f
2013-09-30 22:06:33 +00:00
|
|
|
|
2014-06-30 17:21:25 +00:00
|
|
|
}
|
|
|
|
cursor.moveToPosition(-1);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public String getString(int column) {
|
|
|
|
if (column == mContentUriIndex) {
|
|
|
|
return mContentUriStrings[getPosition()];
|
2012-09-22 04:18:10 +00:00
|
|
|
} else {
|
|
|
|
return super.getString(column);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-08-21 17:33:17 +00:00
|
|
|
/**
|
|
|
|
* For debugging purposes; shouldn't be used in production code
|
|
|
|
*/
|
2013-09-06 23:05:39 +00:00
|
|
|
@SuppressWarnings("unused")
|
2012-08-21 17:33:17 +00:00
|
|
|
static class CloseDetectingCursor extends CursorWrapper {
|
|
|
|
|
|
|
|
public CloseDetectingCursor(Cursor cursor) {
|
|
|
|
super(cursor);
|
|
|
|
}
|
|
|
|
|
2012-09-18 16:47:57 +00:00
|
|
|
@Override
|
2012-08-21 17:33:17 +00:00
|
|
|
public void close() {
|
|
|
|
super.close();
|
2013-05-26 04:32:32 +00:00
|
|
|
LogUtils.d(TAG, "Closing cursor", new Error());
|
2012-08-21 17:33:17 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-06-27 00:19:17 +00:00
|
|
|
/**
|
|
|
|
* Converts a mailbox in a row of the mailboxCursor into a row
|
|
|
|
* in the supplied {@link MatrixCursor} in the format required for {@link Folder}.
|
|
|
|
* As a convenience, the modified {@link MatrixCursor} is also returned.
|
|
|
|
* @param mc the {@link MatrixCursor} into which the mailbox data will be converted
|
|
|
|
* @param projectionLength the length of the projection for this Cursor
|
|
|
|
* @param mailboxCursor the cursor supplying the mailbox data
|
|
|
|
* @param nameColumn column in the cursor containing the folder name value
|
|
|
|
* @param typeColumn column in the cursor containing the folder type value
|
|
|
|
* @return the {@link MatrixCursor} containing the transformed data.
|
|
|
|
*/
|
|
|
|
private Cursor getUiFolderCursorRowFromMailboxCursorRow(
|
|
|
|
MatrixCursor mc, int projectionLength, Cursor mailboxCursor,
|
|
|
|
int nameColumn, int typeColumn) {
|
|
|
|
final MatrixCursor.RowBuilder builder = mc.newRow();
|
|
|
|
for (int i = 0; i < projectionLength; i++) {
|
|
|
|
// If we are at the name column, get the type
|
|
|
|
// and use it to use a properly translated string
|
|
|
|
// from resources instead of the display name.
|
|
|
|
// This ignores display names for system mailboxes.
|
|
|
|
if (nameColumn == i) {
|
|
|
|
// We implicitly assume that if name is requested,
|
|
|
|
// type has also been requested. If not, this will
|
|
|
|
// error in unknown ways.
|
|
|
|
final int type = mailboxCursor.getInt(typeColumn);
|
|
|
|
builder.add(getFolderDisplayName(type, mailboxCursor.getString(i)));
|
|
|
|
} else {
|
|
|
|
builder.add(mailboxCursor.getString(i));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return mc;
|
|
|
|
}
|
|
|
|
|
2013-10-14 03:52:09 +00:00
|
|
|
/**
|
|
|
|
* Takes a uifolder cursor (that was generated with a full projection) and remaps values for
|
|
|
|
* columns that are difficult to generate in the SQL query. This currently includes:
|
|
|
|
* - Folder name (due to system folder localization).
|
|
|
|
* - Capabilities (due to this varying by account protocol).
|
|
|
|
* - Persistent id (due to needing to base64 encode it).
|
|
|
|
* - Load more uri (due to this varying by account protocol).
|
|
|
|
* TODO: This would be better as a CursorWrapper, rather than doing a copy.
|
|
|
|
* @param inputCursor A cursor containing all columns of {@link UIProvider.FolderColumns}.
|
|
|
|
* Strictly speaking doesn't need all, but simpler if we assume that.
|
|
|
|
* @param outputCursor A MatrixCursor which this function will populate.
|
|
|
|
* @param accountId The account id for the mailboxes in this query.
|
|
|
|
* @param uiProjection The projection specified by the query.
|
|
|
|
*/
|
|
|
|
private void remapFolderCursor(final Cursor inputCursor, final MatrixCursor outputCursor,
|
|
|
|
final long accountId, final String[] uiProjection) {
|
|
|
|
// Return early if our input cursor is empty.
|
|
|
|
if (inputCursor == null || inputCursor.getCount() == 0) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
// Get the column indices for the columns we need during remapping.
|
2014-03-27 21:33:58 +00:00
|
|
|
// While we currently could assume the column indices for UIProvider.FOLDERS_PROJECTION
|
2013-10-14 03:52:09 +00:00
|
|
|
// and therefore avoid the calls to getColumnIndex, this at least tries to future-proof a
|
|
|
|
// bit.
|
|
|
|
// Note that id and type MUST be present for this function to work correctly.
|
|
|
|
final int idColumn = inputCursor.getColumnIndex(BaseColumns._ID);
|
|
|
|
final int typeColumn = inputCursor.getColumnIndex(UIProvider.FolderColumns.TYPE);
|
|
|
|
final int nameColumn = inputCursor.getColumnIndex(UIProvider.FolderColumns.NAME);
|
|
|
|
final int capabilitiesColumn =
|
|
|
|
inputCursor.getColumnIndex(UIProvider.FolderColumns.CAPABILITIES);
|
|
|
|
final int persistentIdColumn =
|
|
|
|
inputCursor.getColumnIndex(UIProvider.FolderColumns.PERSISTENT_ID);
|
|
|
|
final int loadMoreUriColumn =
|
|
|
|
inputCursor.getColumnIndex(UIProvider.FolderColumns.LOAD_MORE_URI);
|
|
|
|
|
|
|
|
// Get the EmailServiceInfo for the current account.
|
|
|
|
final Context context = getContext();
|
|
|
|
final String protocol = Account.getProtocol(context, accountId);
|
|
|
|
final EmailServiceInfo info = EmailServiceUtils.getServiceInfo(context, protocol);
|
|
|
|
|
|
|
|
// Build the return cursor. We iterate over all rows of the input cursor and construct
|
|
|
|
// a row in the output using the columns in uiProjection.
|
|
|
|
while (inputCursor.moveToNext()) {
|
|
|
|
final MatrixCursor.RowBuilder builder = outputCursor.newRow();
|
2014-03-27 21:33:58 +00:00
|
|
|
final int folderType = inputCursor.getInt(typeColumn);
|
2013-10-14 03:52:09 +00:00
|
|
|
for (int i = 0; i < uiProjection.length; i++) {
|
|
|
|
// Find the index in the input cursor corresponding the column requested in the
|
|
|
|
// output projection.
|
|
|
|
final int index = inputCursor.getColumnIndex(uiProjection[i]);
|
|
|
|
if (index == -1) {
|
|
|
|
// We don't have this value, so put a blank in the output and move on.
|
|
|
|
builder.add(null);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
final String value = inputCursor.getString(index);
|
|
|
|
// remapped indicates whether we've written a value to the output for this column.
|
|
|
|
final boolean remapped;
|
|
|
|
if (nameColumn == index) {
|
|
|
|
// Remap folder name for system folders.
|
2014-03-27 21:33:58 +00:00
|
|
|
builder.add(getFolderDisplayName(folderType, value));
|
2013-10-14 03:52:09 +00:00
|
|
|
remapped = true;
|
|
|
|
} else if (capabilitiesColumn == index) {
|
|
|
|
// Get the correct capabilities for this folder.
|
2014-03-27 21:33:58 +00:00
|
|
|
final long mailboxID = inputCursor.getLong(idColumn);
|
|
|
|
final int mailboxType = getMailboxTypeFromFolderType(folderType);
|
|
|
|
builder.add(getFolderCapabilities(info, mailboxType, mailboxID));
|
2013-10-14 03:52:09 +00:00
|
|
|
remapped = true;
|
|
|
|
} else if (persistentIdColumn == index) {
|
|
|
|
// Hash the persistent id.
|
|
|
|
builder.add(Base64.encodeToString(value.getBytes(),
|
|
|
|
Base64.URL_SAFE | Base64.NO_WRAP | Base64.NO_PADDING));
|
|
|
|
remapped = true;
|
2014-03-27 21:33:58 +00:00
|
|
|
} else if (loadMoreUriColumn == index && folderType != Mailbox.TYPE_SEARCH &&
|
2013-10-14 03:52:09 +00:00
|
|
|
(info == null || !info.offerLoadMore)) {
|
|
|
|
// Blank the load more uri for account types that don't offer it.
|
|
|
|
// Note that all account types permit load more for search results.
|
|
|
|
builder.add(null);
|
|
|
|
remapped = true;
|
|
|
|
} else {
|
|
|
|
remapped = false;
|
|
|
|
}
|
|
|
|
// If the above logic didn't write some other value to the output, use the value
|
|
|
|
// from the input cursor.
|
|
|
|
if (!remapped) {
|
|
|
|
builder.add(value);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private Cursor getFolderListCursor(final Cursor inputCursor, final long accountId,
|
|
|
|
final String[] uiProjection) {
|
|
|
|
final MatrixCursor mc = new MatrixCursorWithCachedColumns(uiProjection);
|
|
|
|
if (inputCursor != null) {
|
|
|
|
try {
|
|
|
|
remapFolderCursor(inputCursor, mc, accountId, uiProjection);
|
|
|
|
} finally {
|
|
|
|
inputCursor.close();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return mc;
|
|
|
|
}
|
|
|
|
|
2013-06-27 00:19:17 +00:00
|
|
|
/**
|
|
|
|
* Returns a {@link String} from Resources corresponding
|
|
|
|
* to the {@link UIProvider.FolderType} requested.
|
|
|
|
* @param folderType {@link UIProvider.FolderType} value for the folder
|
|
|
|
* @param defaultName a {@link String} to use in case the {@link UIProvider.FolderType}
|
|
|
|
* provided is not a system folder.
|
|
|
|
* @return a {@link String} to use as the display name for the folder
|
|
|
|
*/
|
|
|
|
private String getFolderDisplayName(int folderType, String defaultName) {
|
2013-09-06 23:05:39 +00:00
|
|
|
final int resId;
|
2013-06-27 00:19:17 +00:00
|
|
|
switch (folderType) {
|
|
|
|
case UIProvider.FolderType.INBOX:
|
|
|
|
resId = R.string.mailbox_name_display_inbox;
|
|
|
|
break;
|
|
|
|
case UIProvider.FolderType.OUTBOX:
|
|
|
|
resId = R.string.mailbox_name_display_outbox;
|
|
|
|
break;
|
|
|
|
case UIProvider.FolderType.DRAFT:
|
|
|
|
resId = R.string.mailbox_name_display_drafts;
|
|
|
|
break;
|
|
|
|
case UIProvider.FolderType.TRASH:
|
|
|
|
resId = R.string.mailbox_name_display_trash;
|
|
|
|
break;
|
|
|
|
case UIProvider.FolderType.SENT:
|
|
|
|
resId = R.string.mailbox_name_display_sent;
|
|
|
|
break;
|
|
|
|
case UIProvider.FolderType.SPAM:
|
|
|
|
resId = R.string.mailbox_name_display_junk;
|
|
|
|
break;
|
|
|
|
case UIProvider.FolderType.STARRED:
|
|
|
|
resId = R.string.mailbox_name_display_starred;
|
|
|
|
break;
|
|
|
|
case UIProvider.FolderType.UNREAD:
|
|
|
|
resId = R.string.mailbox_name_display_unread;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
return defaultName;
|
|
|
|
}
|
|
|
|
return getContext().getString(resId);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Converts a {@link Mailbox} type value to its {@link UIProvider.FolderType}
|
|
|
|
* equivalent.
|
|
|
|
* @param mailboxType a {@link Mailbox} type
|
|
|
|
* @return a {@link UIProvider.FolderType} value
|
|
|
|
*/
|
|
|
|
private static int getFolderTypeFromMailboxType(int mailboxType) {
|
|
|
|
switch (mailboxType) {
|
|
|
|
case Mailbox.TYPE_INBOX:
|
|
|
|
return UIProvider.FolderType.INBOX;
|
|
|
|
case Mailbox.TYPE_OUTBOX:
|
|
|
|
return UIProvider.FolderType.OUTBOX;
|
|
|
|
case Mailbox.TYPE_DRAFTS:
|
|
|
|
return UIProvider.FolderType.DRAFT;
|
|
|
|
case Mailbox.TYPE_TRASH:
|
|
|
|
return UIProvider.FolderType.TRASH;
|
|
|
|
case Mailbox.TYPE_SENT:
|
|
|
|
return UIProvider.FolderType.SENT;
|
|
|
|
case Mailbox.TYPE_JUNK:
|
|
|
|
return UIProvider.FolderType.SPAM;
|
|
|
|
case Mailbox.TYPE_STARRED:
|
|
|
|
return UIProvider.FolderType.STARRED;
|
|
|
|
case Mailbox.TYPE_UNREAD:
|
|
|
|
return UIProvider.FolderType.UNREAD;
|
2013-09-04 20:49:54 +00:00
|
|
|
case Mailbox.TYPE_SEARCH:
|
|
|
|
// TODO Can the DEFAULT type be removed from SEARCH folders?
|
|
|
|
return UIProvider.FolderType.DEFAULT | UIProvider.FolderType.SEARCH;
|
2013-06-27 00:19:17 +00:00
|
|
|
default:
|
|
|
|
return UIProvider.FolderType.DEFAULT;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-03-27 21:33:58 +00:00
|
|
|
/**
|
|
|
|
* Converts a {@link UIProvider.FolderType} type value to its {@link Mailbox} equivalent.
|
|
|
|
* @param folderType a {@link UIProvider.FolderType} type
|
|
|
|
* @return a {@link Mailbox} value
|
|
|
|
*/
|
|
|
|
private static int getMailboxTypeFromFolderType(int folderType) {
|
|
|
|
switch (folderType) {
|
|
|
|
case UIProvider.FolderType.DEFAULT:
|
|
|
|
return Mailbox.TYPE_MAIL;
|
|
|
|
case UIProvider.FolderType.INBOX:
|
|
|
|
return Mailbox.TYPE_INBOX;
|
|
|
|
case UIProvider.FolderType.OUTBOX:
|
|
|
|
return Mailbox.TYPE_OUTBOX;
|
|
|
|
case UIProvider.FolderType.DRAFT:
|
|
|
|
return Mailbox.TYPE_DRAFTS;
|
|
|
|
case UIProvider.FolderType.TRASH:
|
|
|
|
return Mailbox.TYPE_TRASH;
|
|
|
|
case UIProvider.FolderType.SENT:
|
|
|
|
return Mailbox.TYPE_SENT;
|
|
|
|
case UIProvider.FolderType.SPAM:
|
|
|
|
return Mailbox.TYPE_JUNK;
|
|
|
|
case UIProvider.FolderType.STARRED:
|
|
|
|
return Mailbox.TYPE_STARRED;
|
|
|
|
case UIProvider.FolderType.UNREAD:
|
|
|
|
return Mailbox.TYPE_UNREAD;
|
|
|
|
case UIProvider.FolderType.DEFAULT | UIProvider.FolderType.SEARCH:
|
|
|
|
// TODO Can the DEFAULT type be removed from SEARCH folders?
|
|
|
|
return Mailbox.TYPE_SEARCH;
|
|
|
|
default:
|
|
|
|
throw new IllegalArgumentException("Unable to map folder type: " + folderType);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-10-23 18:41:05 +00:00
|
|
|
/**
|
|
|
|
* We need a reasonably full projection for getFolderListCursor to work, but don't always want
|
|
|
|
* to do the subquery needed for FolderColumns.UNREAD_SENDERS
|
|
|
|
* @param uiProjection The projection we actually want
|
|
|
|
* @return Full projection, possibly with or without FolderColumns.UNREAD_SENDERS
|
|
|
|
*/
|
|
|
|
private String[] folderProjectionFromUiProjection(final String[] uiProjection) {
|
|
|
|
final Set<String> columns = ImmutableSet.copyOf(uiProjection);
|
|
|
|
if (columns.contains(UIProvider.FolderColumns.UNREAD_SENDERS)) {
|
|
|
|
return UIProvider.FOLDERS_PROJECTION_WITH_UNREAD_SENDERS;
|
|
|
|
} else {
|
|
|
|
return UIProvider.FOLDERS_PROJECTION;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-06-28 17:40:46 +00:00
|
|
|
/**
|
|
|
|
* 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
|
2012-12-11 18:37:35 +00:00
|
|
|
* @param unseenOnly <code>true</code> to only return unseen messages (where supported)
|
2012-06-28 17:40:46 +00:00
|
|
|
* @return the result Cursor
|
|
|
|
*/
|
2012-12-11 18:37:35 +00:00
|
|
|
private Cursor uiQuery(int match, Uri uri, String[] uiProjection, final boolean unseenOnly) {
|
2012-06-28 17:40:46 +00:00
|
|
|
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:
|
2013-09-20 21:30:57 +00:00
|
|
|
notifyUri =
|
|
|
|
UIPROVIDER_FOLDERLIST_NOTIFIER.buildUpon().appendEncodedPath(id).build();
|
|
|
|
final Cursor vc = uiVirtualMailboxes(id, uiProjection);
|
|
|
|
if (id.equals(COMBINED_ACCOUNT_ID_STRING)) {
|
|
|
|
// There's no real mailboxes, so just return the virtual ones
|
|
|
|
c = vc;
|
|
|
|
} else {
|
|
|
|
// Return real and virtual mailboxes alike
|
|
|
|
final Cursor rawc = db.rawQuery(genQueryAccountAllMailboxes(uiProjection),
|
|
|
|
new String[] {id});
|
|
|
|
rawc.setNotificationUri(context.getContentResolver(), notifyUri);
|
|
|
|
vc.setNotificationUri(context.getContentResolver(), notifyUri);
|
2014-05-01 19:45:09 +00:00
|
|
|
if (rawc.getCount() > 0) {
|
|
|
|
c = new MergeCursor(new Cursor[]{rawc, vc});
|
|
|
|
} else {
|
|
|
|
c = rawc;
|
|
|
|
}
|
2013-09-20 21:30:57 +00:00
|
|
|
}
|
|
|
|
break;
|
2013-10-23 18:41:05 +00:00
|
|
|
case UI_FULL_FOLDERS: {
|
|
|
|
// We need a full projection for getFolderListCursor
|
|
|
|
final String[] folderProjection = folderProjectionFromUiProjection(uiProjection);
|
|
|
|
c = db.rawQuery(genQueryAccountAllMailboxes(folderProjection), new String[] {id});
|
2013-10-14 03:52:09 +00:00
|
|
|
c = getFolderListCursor(c, Long.valueOf(id), uiProjection);
|
2013-08-06 23:09:00 +00:00
|
|
|
notifyUri =
|
|
|
|
UIPROVIDER_FOLDERLIST_NOTIFIER.buildUpon().appendEncodedPath(id).build();
|
2012-06-28 17:40:46 +00:00
|
|
|
break;
|
2013-10-23 18:41:05 +00:00
|
|
|
}
|
2012-06-28 17:40:46 +00:00
|
|
|
case UI_RECENT_FOLDERS:
|
|
|
|
c = db.rawQuery(genQueryRecentMailboxes(uiProjection), new String[] {id});
|
|
|
|
notifyUri = UIPROVIDER_RECENT_FOLDERS_NOTIFIER.buildUpon().appendPath(id).build();
|
|
|
|
break;
|
2013-10-23 18:41:05 +00:00
|
|
|
case UI_SUBFOLDERS: {
|
|
|
|
// We need a full projection for getFolderListCursor
|
|
|
|
final String[] folderProjection = folderProjectionFromUiProjection(uiProjection);
|
|
|
|
c = db.rawQuery(genQuerySubfolders(folderProjection), new String[] {id});
|
2013-10-14 03:52:09 +00:00
|
|
|
c = getFolderListCursor(c, Mailbox.getAccountIdForMailbox(context, id),
|
|
|
|
uiProjection);
|
2013-08-06 23:09:00 +00:00
|
|
|
// Get notifications for any folder changes on this account. This is broader than
|
|
|
|
// we need but otherwise we'd need for every folder change to notify on all relevant
|
|
|
|
// subtrees. For now we opt for simplicity.
|
|
|
|
final long accountId = Mailbox.getAccountIdForMailbox(context, id);
|
|
|
|
notifyUri = ContentUris.withAppendedId(UIPROVIDER_FOLDERLIST_NOTIFIER, accountId);
|
2012-06-28 17:40:46 +00:00
|
|
|
break;
|
2013-10-23 18:41:05 +00:00
|
|
|
}
|
2012-06-28 17:40:46 +00:00
|
|
|
case UI_MESSAGES:
|
|
|
|
long mailboxId = Long.parseLong(id);
|
2013-06-19 01:13:45 +00:00
|
|
|
final Folder folder = getFolder(context, mailboxId);
|
|
|
|
if (folder == null) {
|
2013-09-18 17:53:58 +00:00
|
|
|
// This mailboxId is bogus. Return an empty cursor
|
|
|
|
// TODO: Make callers of this query handle null cursors instead b/10819309
|
|
|
|
return new MatrixCursor(uiProjection);
|
2013-06-19 01:13:45 +00:00
|
|
|
}
|
2012-06-28 17:40:46 +00:00
|
|
|
if (isVirtualMailbox(mailboxId)) {
|
2012-12-11 18:37:35 +00:00
|
|
|
c = getVirtualMailboxMessagesCursor(db, uiProjection, mailboxId, unseenOnly);
|
2012-06-28 17:40:46 +00:00
|
|
|
} else {
|
2012-12-11 18:37:35 +00:00
|
|
|
c = db.rawQuery(
|
|
|
|
genQueryMailboxMessages(uiProjection, unseenOnly), new String[] {id});
|
2012-06-28 17:40:46 +00:00
|
|
|
}
|
|
|
|
notifyUri = UIPROVIDER_CONVERSATION_NOTIFIER.buildUpon().appendPath(id).build();
|
2013-06-19 01:13:45 +00:00
|
|
|
c = new EmailConversationCursor(context, c, folder, mailboxId);
|
2012-06-28 17:40:46 +00:00
|
|
|
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});
|
|
|
|
}
|
2014-04-15 18:27:23 +00:00
|
|
|
if (c != null) {
|
2014-05-12 18:33:43 +00:00
|
|
|
c = new EmailMessageCursor(getContext(), c, UIProvider.MessageColumns.BODY_HTML,
|
2014-05-08 20:07:54 +00:00
|
|
|
UIProvider.MessageColumns.BODY_TEXT);
|
2014-04-15 18:27:23 +00:00
|
|
|
}
|
2013-09-16 23:15:32 +00:00
|
|
|
notifyUri = UIPROVIDER_MESSAGE_NOTIFIER.buildUpon().appendPath(id).build();
|
2012-06-28 17:40:46 +00:00
|
|
|
break;
|
|
|
|
case UI_ATTACHMENTS:
|
2012-07-24 19:57:21 +00:00
|
|
|
final List<String> contentTypeQueryParameters =
|
|
|
|
uri.getQueryParameters(PhotoContract.ContentTypeParameters.CONTENT_TYPE);
|
|
|
|
c = db.rawQuery(genQueryAttachments(uiProjection, contentTypeQueryParameters),
|
|
|
|
new String[] {id});
|
2012-09-22 04:18:10 +00:00
|
|
|
c = new AttachmentsCursor(context, c);
|
2012-06-28 17:40:46 +00:00
|
|
|
notifyUri = UIPROVIDER_ATTACHMENTS_NOTIFIER.buildUpon().appendPath(id).build();
|
|
|
|
break;
|
|
|
|
case UI_ATTACHMENT:
|
2014-04-09 19:59:28 +00:00
|
|
|
c = db.rawQuery(genQueryAttachment(uiProjection), new String[] {id});
|
2012-06-28 17:40:46 +00:00
|
|
|
notifyUri = UIPROVIDER_ATTACHMENT_NOTIFIER.buildUpon().appendPath(id).build();
|
|
|
|
break;
|
2014-04-09 19:59:28 +00:00
|
|
|
case UI_ATTACHMENT_BY_CID:
|
|
|
|
final String cid = uri.getPathSegments().get(2);
|
|
|
|
final String[] selectionArgs = {id, cid};
|
|
|
|
c = db.rawQuery(genQueryAttachmentByMessageIDAndCid(uiProjection), selectionArgs);
|
|
|
|
|
|
|
|
// we don't have easy access to the attachment ID (which is buried in the cursor
|
|
|
|
// being returned), so we notify on the parent message object
|
|
|
|
notifyUri = UIPROVIDER_ATTACHMENTS_NOTIFIER.buildUpon().appendPath(id).build();
|
|
|
|
break;
|
2012-06-28 17:40:46 +00:00
|
|
|
case UI_FOLDER:
|
2014-05-01 19:45:09 +00:00
|
|
|
case UI_INBOX:
|
|
|
|
if (match == UI_INBOX) {
|
|
|
|
mailboxId = Mailbox.findMailboxOfType(context, Long.parseLong(id),
|
|
|
|
Mailbox.TYPE_INBOX);
|
|
|
|
if (mailboxId == Mailbox.NO_MAILBOX) {
|
|
|
|
LogUtils.d(LogUtils.TAG, "No inbox found for account %s", id);
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
LogUtils.d(LogUtils.TAG, "Found inbox id %d", mailboxId);
|
|
|
|
} else {
|
|
|
|
mailboxId = Long.parseLong(id);
|
|
|
|
}
|
|
|
|
final String mailboxIdString = Long.toString(mailboxId);
|
2012-06-28 17:40:46 +00:00
|
|
|
if (isVirtualMailbox(mailboxId)) {
|
2013-10-14 23:32:58 +00:00
|
|
|
c = getVirtualMailboxCursor(mailboxId, uiProjection);
|
2014-05-01 19:45:09 +00:00
|
|
|
notifyUri = UIPROVIDER_FOLDER_NOTIFIER.buildUpon().appendPath(mailboxIdString)
|
|
|
|
.build();
|
2012-06-28 17:40:46 +00:00
|
|
|
} else {
|
2014-05-01 19:45:09 +00:00
|
|
|
c = db.rawQuery(genQueryMailbox(uiProjection, mailboxIdString),
|
|
|
|
new String[]{mailboxIdString});
|
2013-06-27 00:19:17 +00:00
|
|
|
final List<String> projectionList = Arrays.asList(uiProjection);
|
|
|
|
final int nameColumn = projectionList.indexOf(UIProvider.FolderColumns.NAME);
|
|
|
|
final int typeColumn = projectionList.indexOf(UIProvider.FolderColumns.TYPE);
|
|
|
|
if (c.moveToFirst()) {
|
2014-06-23 20:38:22 +00:00
|
|
|
final Cursor closeThis = c;
|
|
|
|
try {
|
|
|
|
c = getUiFolderCursorRowFromMailboxCursorRow(
|
|
|
|
new MatrixCursorWithCachedColumns(uiProjection),
|
|
|
|
uiProjection.length, c, nameColumn, typeColumn);
|
|
|
|
} finally {
|
|
|
|
closeThis.close();
|
|
|
|
}
|
2013-06-27 00:19:17 +00:00
|
|
|
}
|
2014-05-01 19:45:09 +00:00
|
|
|
notifyUri = UIPROVIDER_FOLDER_NOTIFIER.buildUpon().appendPath(mailboxIdString)
|
|
|
|
.build();
|
2012-06-28 17:40:46 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
case UI_ACCOUNT:
|
|
|
|
if (id.equals(COMBINED_ACCOUNT_ID_STRING)) {
|
2013-02-06 23:21:15 +00:00
|
|
|
MatrixCursor mc = new MatrixCursorWithCachedColumns(uiProjection, 1);
|
2012-06-28 17:40:46 +00:00
|
|
|
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
|
2013-02-23 03:42:40 +00:00
|
|
|
* @param cachedFile the path to the cached file to
|
2012-06-28 17:40:46 +00:00
|
|
|
* @return the EmailProvider attachment
|
|
|
|
*/
|
2013-02-23 03:42:40 +00:00
|
|
|
// TODO(pwestbro): once the Attachment contains the cached uri, the second parameter can be
|
|
|
|
// removed
|
2013-10-04 19:10:27 +00:00
|
|
|
// TODO(mhibdon): if the UI Attachment contained the account key, the third parameter could
|
Make draft attachments work correctly
b/10968838
The main problem here is that Ui Attachment was always using
a content Uri that was generated using the attachment's account
Id and rowId. This works correctly for attachments in messages
we have received, but it does not work for drafts: Draft attachments
are not stored in the normal place, rather, they are stored in
the cache directory. There is an additional column in the
Attachment table, called cachedFile, which is not replicated in the
Ui attachment. That's okay though, if we have a cachedFile, then
when we are populating a Ui Attachment, we should just use that
for content Uri.
Also, I discoverd that for draft attachments, we were not correctly
setting the account key. That didn't turn out to be the problem,
but I'm fixing it anyway because it will cause problems later on.
Change-Id: I0143ba824f3a5bfcd77f32828931b94d6977626f
2013-09-30 22:06:33 +00:00
|
|
|
// be removed.
|
2012-12-11 18:37:35 +00:00
|
|
|
private static Attachment convertUiAttachmentToAttachment(
|
Make draft attachments work correctly
b/10968838
The main problem here is that Ui Attachment was always using
a content Uri that was generated using the attachment's account
Id and rowId. This works correctly for attachments in messages
we have received, but it does not work for drafts: Draft attachments
are not stored in the normal place, rather, they are stored in
the cache directory. There is an additional column in the
Attachment table, called cachedFile, which is not replicated in the
Ui attachment. That's okay though, if we have a cachedFile, then
when we are populating a Ui Attachment, we should just use that
for content Uri.
Also, I discoverd that for draft attachments, we were not correctly
setting the account key. That didn't turn out to be the problem,
but I'm fixing it anyway because it will cause problems later on.
Change-Id: I0143ba824f3a5bfcd77f32828931b94d6977626f
2013-09-30 22:06:33 +00:00
|
|
|
com.android.mail.providers.Attachment uiAtt, String cachedFile, long accountKey) {
|
2013-02-23 03:42:40 +00:00
|
|
|
final Attachment att = new Attachment();
|
Make draft attachments work correctly
b/10968838
The main problem here is that Ui Attachment was always using
a content Uri that was generated using the attachment's account
Id and rowId. This works correctly for attachments in messages
we have received, but it does not work for drafts: Draft attachments
are not stored in the normal place, rather, they are stored in
the cache directory. There is an additional column in the
Attachment table, called cachedFile, which is not replicated in the
Ui attachment. That's okay though, if we have a cachedFile, then
when we are populating a Ui Attachment, we should just use that
for content Uri.
Also, I discoverd that for draft attachments, we were not correctly
setting the account key. That didn't turn out to be the problem,
but I'm fixing it anyway because it will cause problems later on.
Change-Id: I0143ba824f3a5bfcd77f32828931b94d6977626f
2013-09-30 22:06:33 +00:00
|
|
|
|
2012-09-08 17:36:32 +00:00
|
|
|
att.setContentUri(uiAtt.contentUri.toString());
|
2013-03-21 23:54:49 +00:00
|
|
|
|
|
|
|
if (!TextUtils.isEmpty(cachedFile)) {
|
|
|
|
// Generate the content provider uri for this cached file
|
|
|
|
final Uri.Builder cachedFileBuilder = Uri.parse(
|
|
|
|
"content://" + EmailContent.AUTHORITY + "/attachment/cachedFile").buildUpon();
|
|
|
|
cachedFileBuilder.appendQueryParameter(Attachment.CACHED_FILE_QUERY_PARAM, cachedFile);
|
|
|
|
att.setCachedFileUri(cachedFileBuilder.build().toString());
|
|
|
|
}
|
Make draft attachments work correctly
b/10968838
The main problem here is that Ui Attachment was always using
a content Uri that was generated using the attachment's account
Id and rowId. This works correctly for attachments in messages
we have received, but it does not work for drafts: Draft attachments
are not stored in the normal place, rather, they are stored in
the cache directory. There is an additional column in the
Attachment table, called cachedFile, which is not replicated in the
Ui attachment. That's okay though, if we have a cachedFile, then
when we are populating a Ui Attachment, we should just use that
for content Uri.
Also, I discoverd that for draft attachments, we were not correctly
setting the account key. That didn't turn out to be the problem,
but I'm fixing it anyway because it will cause problems later on.
Change-Id: I0143ba824f3a5bfcd77f32828931b94d6977626f
2013-09-30 22:06:33 +00:00
|
|
|
att.mAccountKey = accountKey;
|
2013-02-22 02:10:10 +00:00
|
|
|
att.mFileName = uiAtt.getName();
|
|
|
|
att.mMimeType = uiAtt.getContentType();
|
2012-06-28 17:40:46 +00:00
|
|
|
att.mSize = uiAtt.size;
|
|
|
|
return att;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Create a mailbox given the account and mailboxType.
|
|
|
|
*/
|
|
|
|
private Mailbox createMailbox(long accountId, int mailboxType) {
|
|
|
|
Context context = getContext();
|
2013-03-15 02:58:21 +00:00
|
|
|
Mailbox box = Mailbox.newSystemMailbox(context, accountId, mailboxType);
|
2012-06-28 17:40:46 +00:00
|
|
|
// 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
|
2012-12-11 18:37:35 +00:00
|
|
|
* @param accountId the account id to use
|
2012-06-28 17:40:46 +00:00
|
|
|
* @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
|
|
|
|
*/
|
2013-04-19 20:36:21 +00:00
|
|
|
private Mailbox getMailboxByAccountIdAndType(final long accountId, final int mailboxType) {
|
|
|
|
Mailbox mailbox = Mailbox.restoreMailboxOfType(getContext(), accountId, mailboxType);
|
2012-06-28 17:40:46 +00:00
|
|
|
if (mailbox == null) {
|
2013-04-19 20:36:21 +00:00
|
|
|
mailbox = createMailbox(accountId, mailboxType);
|
2012-06-28 17:40:46 +00:00
|
|
|
}
|
|
|
|
return mailbox;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Given a mailbox and the content values for a message, create/save the message in the mailbox
|
|
|
|
* @param mailbox the mailbox to use
|
2013-03-14 20:35:37 +00:00
|
|
|
* @param extras the bundle containing the message fields
|
2012-06-28 17:40:46 +00:00
|
|
|
* @return the uri of the newly created message
|
2013-03-14 20:35:37 +00:00
|
|
|
* TODO(yph): The following fields are available in extras but unused, verify whether they
|
|
|
|
* should be respected:
|
|
|
|
* - UIProvider.MessageColumns.SNIPPET
|
|
|
|
* - UIProvider.MessageColumns.REPLY_TO
|
|
|
|
* - UIProvider.MessageColumns.FROM
|
2012-06-28 17:40:46 +00:00
|
|
|
*/
|
2013-03-14 20:35:37 +00:00
|
|
|
private Uri uiSaveMessage(Message msg, Mailbox mailbox, Bundle extras) {
|
2013-02-23 03:42:40 +00:00
|
|
|
final Context context = getContext();
|
2012-06-28 17:40:46 +00:00
|
|
|
// Fill in the message
|
2013-02-23 03:42:40 +00:00
|
|
|
final Account account = Account.restoreAccountWithId(context, mailbox.mAccountKey);
|
2012-06-28 17:40:46 +00:00
|
|
|
if (account == null) return null;
|
2013-10-18 21:04:49 +00:00
|
|
|
final String customFromAddress =
|
|
|
|
extras.getString(UIProvider.MessageColumns.CUSTOM_FROM_ADDRESS);
|
|
|
|
if (!TextUtils.isEmpty(customFromAddress)) {
|
|
|
|
msg.mFrom = customFromAddress;
|
|
|
|
} else {
|
|
|
|
msg.mFrom = account.getEmailAddress();
|
|
|
|
}
|
2012-06-28 17:40:46 +00:00
|
|
|
msg.mTimeStamp = System.currentTimeMillis();
|
2013-03-14 20:35:37 +00:00
|
|
|
msg.mTo = extras.getString(UIProvider.MessageColumns.TO);
|
|
|
|
msg.mCc = extras.getString(UIProvider.MessageColumns.CC);
|
|
|
|
msg.mBcc = extras.getString(UIProvider.MessageColumns.BCC);
|
|
|
|
msg.mSubject = extras.getString(UIProvider.MessageColumns.SUBJECT);
|
|
|
|
msg.mText = extras.getString(UIProvider.MessageColumns.BODY_TEXT);
|
|
|
|
msg.mHtml = extras.getString(UIProvider.MessageColumns.BODY_HTML);
|
2012-06-28 17:40:46 +00:00
|
|
|
msg.mMailboxKey = mailbox.mId;
|
|
|
|
msg.mAccountKey = mailbox.mAccountKey;
|
|
|
|
msg.mDisplayName = msg.mTo;
|
|
|
|
msg.mFlagLoaded = Message.FLAG_LOADED_COMPLETE;
|
|
|
|
msg.mFlagRead = true;
|
2012-12-11 18:37:35 +00:00
|
|
|
msg.mFlagSeen = true;
|
2013-12-17 19:31:28 +00:00
|
|
|
msg.mQuotedTextStartPos = extras.getInt(UIProvider.MessageColumns.QUOTE_START_POS, 0);
|
2012-06-28 17:40:46 +00:00
|
|
|
int flags = 0;
|
2013-03-14 20:35:37 +00:00
|
|
|
final int draftType = extras.getInt(UIProvider.MessageColumns.DRAFT_TYPE);
|
2012-06-28 17:40:46 +00:00
|
|
|
switch(draftType) {
|
|
|
|
case DraftType.FORWARD:
|
|
|
|
flags |= Message.FLAG_TYPE_FORWARD;
|
|
|
|
break;
|
|
|
|
case DraftType.REPLY_ALL:
|
|
|
|
flags |= Message.FLAG_TYPE_REPLY_ALL;
|
2012-12-11 18:37:35 +00:00
|
|
|
//$FALL-THROUGH$
|
2012-06-28 17:40:46 +00:00
|
|
|
case DraftType.REPLY:
|
|
|
|
flags |= Message.FLAG_TYPE_REPLY;
|
|
|
|
break;
|
|
|
|
case DraftType.COMPOSE:
|
|
|
|
flags |= Message.FLAG_TYPE_ORIGINAL;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
int draftInfo = 0;
|
2013-03-14 20:35:37 +00:00
|
|
|
if (extras.containsKey(UIProvider.MessageColumns.QUOTE_START_POS)) {
|
|
|
|
draftInfo = extras.getInt(UIProvider.MessageColumns.QUOTE_START_POS);
|
|
|
|
if (extras.getInt(UIProvider.MessageColumns.APPEND_REF_MESSAGE_CONTENT) != 0) {
|
2012-06-28 17:40:46 +00:00
|
|
|
draftInfo |= Message.DRAFT_INFO_APPEND_REF_MESSAGE;
|
|
|
|
}
|
|
|
|
}
|
2013-03-14 20:35:37 +00:00
|
|
|
if (!extras.containsKey(UIProvider.MessageColumns.APPEND_REF_MESSAGE_CONTENT)) {
|
2012-09-18 16:47:57 +00:00
|
|
|
flags |= Message.FLAG_NOT_INCLUDE_QUOTED_TEXT;
|
|
|
|
}
|
2012-06-28 17:40:46 +00:00
|
|
|
msg.mDraftInfo = draftInfo;
|
2012-09-18 16:47:57 +00:00
|
|
|
msg.mFlags = flags;
|
|
|
|
|
2013-03-14 20:35:37 +00:00
|
|
|
final String ref = extras.getString(UIProvider.MessageColumns.REF_MESSAGE_ID);
|
2012-07-18 23:55:27 +00:00
|
|
|
if (ref != null && msg.mQuotedTextStartPos >= 0) {
|
2012-06-28 17:40:46 +00:00
|
|
|
String refId = Uri.parse(ref).getLastPathSegment();
|
|
|
|
try {
|
2013-09-06 23:05:39 +00:00
|
|
|
msg.mSourceKey = Long.parseLong(refId);
|
2012-06-28 17:40:46 +00:00
|
|
|
} catch (NumberFormatException e) {
|
|
|
|
// This will be zero; the default
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get attachments from the ContentValues
|
2013-02-23 03:42:40 +00:00
|
|
|
final List<com.android.mail.providers.Attachment> uiAtts =
|
2012-06-28 17:40:46 +00:00
|
|
|
com.android.mail.providers.Attachment.fromJSONArray(
|
2013-03-14 20:35:37 +00:00
|
|
|
extras.getString(UIProvider.MessageColumns.ATTACHMENTS));
|
2013-02-23 03:42:40 +00:00
|
|
|
final ArrayList<Attachment> atts = new ArrayList<Attachment>();
|
2012-06-28 17:40:46 +00:00
|
|
|
boolean hasUnloadedAttachments = false;
|
2013-03-14 20:35:37 +00:00
|
|
|
Bundle attachmentFds =
|
|
|
|
extras.getParcelable(UIProvider.SendOrSaveMethodParamKeys.OPENED_FD_MAP);
|
2012-06-28 17:40:46 +00:00
|
|
|
for (com.android.mail.providers.Attachment uiAtt: uiAtts) {
|
2013-02-23 03:42:40 +00:00
|
|
|
final Uri attUri = uiAtt.uri;
|
2012-06-28 17:40:46 +00:00
|
|
|
if (attUri != null && attUri.getAuthority().equals(EmailContent.AUTHORITY)) {
|
|
|
|
// If it's one of ours, retrieve the attachment and add it to the list
|
2013-02-23 03:42:40 +00:00
|
|
|
final long attId = Long.parseLong(attUri.getLastPathSegment());
|
|
|
|
final Attachment att = Attachment.restoreAttachmentWithId(context, attId);
|
2012-06-28 17:40:46 +00:00
|
|
|
if (att != null) {
|
|
|
|
// We must clone the attachment into a new one for this message; easiest to
|
|
|
|
// use a parcel here
|
2013-02-23 03:42:40 +00:00
|
|
|
final Parcel p = Parcel.obtain();
|
2012-06-28 17:40:46 +00:00
|
|
|
att.writeToParcel(p, 0);
|
|
|
|
p.setDataPosition(0);
|
2013-02-23 03:42:40 +00:00
|
|
|
final Attachment attClone = new Attachment(p);
|
2012-06-28 17:40:46 +00:00
|
|
|
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
|
2012-09-08 17:36:32 +00:00
|
|
|
if (mailbox.mType == Mailbox.TYPE_OUTBOX && att.getContentUri() == null &&
|
2012-06-28 17:40:46 +00:00
|
|
|
((account.mFlags & Account.FLAGS_SUPPORTS_SMART_FORWARD) == 0)) {
|
|
|
|
attClone.mFlags |= Attachment.FLAG_DOWNLOAD_FORWARD;
|
|
|
|
hasUnloadedAttachments = true;
|
|
|
|
}
|
|
|
|
atts.add(attClone);
|
|
|
|
}
|
|
|
|
} else {
|
2013-02-23 03:42:40 +00:00
|
|
|
// Cache the attachment. This will allow us to send it, if the permissions are
|
Make draft attachments work correctly
b/10968838
The main problem here is that Ui Attachment was always using
a content Uri that was generated using the attachment's account
Id and rowId. This works correctly for attachments in messages
we have received, but it does not work for drafts: Draft attachments
are not stored in the normal place, rather, they are stored in
the cache directory. There is an additional column in the
Attachment table, called cachedFile, which is not replicated in the
Ui attachment. That's okay though, if we have a cachedFile, then
when we are populating a Ui Attachment, we should just use that
for content Uri.
Also, I discoverd that for draft attachments, we were not correctly
setting the account key. That didn't turn out to be the problem,
but I'm fixing it anyway because it will cause problems later on.
Change-Id: I0143ba824f3a5bfcd77f32828931b94d6977626f
2013-09-30 22:06:33 +00:00
|
|
|
// revoked.
|
2013-02-23 03:42:40 +00:00
|
|
|
final String cachedFileUri =
|
|
|
|
AttachmentUtils.cacheAttachmentUri(context, uiAtt, attachmentFds);
|
|
|
|
|
2012-06-28 17:40:46 +00:00
|
|
|
// Convert external attachment to one of ours and add to the list
|
Make draft attachments work correctly
b/10968838
The main problem here is that Ui Attachment was always using
a content Uri that was generated using the attachment's account
Id and rowId. This works correctly for attachments in messages
we have received, but it does not work for drafts: Draft attachments
are not stored in the normal place, rather, they are stored in
the cache directory. There is an additional column in the
Attachment table, called cachedFile, which is not replicated in the
Ui attachment. That's okay though, if we have a cachedFile, then
when we are populating a Ui Attachment, we should just use that
for content Uri.
Also, I discoverd that for draft attachments, we were not correctly
setting the account key. That didn't turn out to be the problem,
but I'm fixing it anyway because it will cause problems later on.
Change-Id: I0143ba824f3a5bfcd77f32828931b94d6977626f
2013-09-30 22:06:33 +00:00
|
|
|
atts.add(convertUiAttachmentToAttachment(uiAtt, cachedFileUri, msg.mAccountKey));
|
2012-06-28 17:40:46 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
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
|
2013-02-23 03:42:40 +00:00
|
|
|
final ArrayList<ContentProviderOperation> ops =
|
|
|
|
new ArrayList<ContentProviderOperation>();
|
2012-06-28 17:40:46 +00:00
|
|
|
// 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)
|
2014-04-11 21:42:28 +00:00
|
|
|
.withSelection(BodyColumns.MESSAGE_KEY + "=?",
|
|
|
|
new String[] {Long.toString(msg.mId)})
|
2012-06-28 17:40:46 +00:00
|
|
|
.build());
|
|
|
|
// Add the ops for the message, atts, and body
|
|
|
|
msg.addSaveOps(ops);
|
|
|
|
// Do it!
|
|
|
|
try {
|
|
|
|
applyBatch(ops);
|
|
|
|
} catch (OperationApplicationException e) {
|
2013-09-06 23:05:39 +00:00
|
|
|
LogUtils.d(TAG, "applyBatch exception");
|
2012-06-28 17:40:46 +00:00
|
|
|
}
|
|
|
|
}
|
2013-09-16 23:15:32 +00:00
|
|
|
notifyUIMessage(msg.mId);
|
|
|
|
|
2012-06-28 17:40:46 +00:00
|
|
|
if (mailbox.mType == Mailbox.TYPE_OUTBOX) {
|
2013-04-25 20:52:49 +00:00
|
|
|
startSync(mailbox, 0);
|
2013-02-23 03:42:40 +00:00
|
|
|
final long originalMsgId = msg.mSourceKey;
|
2012-06-28 17:40:46 +00:00
|
|
|
if (originalMsgId != 0) {
|
2013-02-23 03:42:40 +00:00
|
|
|
final Message originalMsg = Message.restoreMessageWithId(context, originalMsgId);
|
2012-06-28 17:40:46 +00:00
|
|
|
// If the original message exists, set its forwarded/replied to flags
|
|
|
|
if (originalMsg != null) {
|
2013-02-23 03:42:40 +00:00
|
|
|
final ContentValues cv = new ContentValues();
|
2012-06-28 17:40:46 +00:00
|
|
|
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;
|
|
|
|
}
|
2014-04-11 21:42:28 +00:00
|
|
|
cv.put(MessageColumns.FLAGS, flags);
|
2012-06-28 17:40:46 +00:00
|
|
|
context.getContentResolver().update(ContentUris.withAppendedId(
|
|
|
|
Message.CONTENT_URI, originalMsgId), cv, null, null);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return uiUri("uimessage", msg.mId);
|
|
|
|
}
|
|
|
|
|
2013-04-19 20:36:21 +00:00
|
|
|
private Uri uiSaveDraftMessage(final long accountId, final Bundle extras) {
|
2013-02-23 03:42:40 +00:00
|
|
|
final Mailbox mailbox =
|
2013-04-19 20:36:21 +00:00
|
|
|
getMailboxByAccountIdAndType(accountId, Mailbox.TYPE_DRAFTS);
|
2013-02-22 01:32:02 +00:00
|
|
|
if (mailbox == null) return null;
|
2013-03-14 20:35:37 +00:00
|
|
|
final Message msg;
|
|
|
|
if (extras.containsKey(BaseColumns._ID)) {
|
|
|
|
final long messageId = extras.getLong(BaseColumns._ID);
|
|
|
|
msg = Message.restoreMessageWithId(getContext(), messageId);
|
|
|
|
} else {
|
|
|
|
msg = new Message();
|
|
|
|
}
|
|
|
|
return uiSaveMessage(msg, mailbox, extras);
|
2013-02-22 01:32:02 +00:00
|
|
|
}
|
|
|
|
|
2013-04-19 20:36:21 +00:00
|
|
|
private Uri uiSendDraftMessage(final long accountId, final Bundle extras) {
|
2013-02-22 01:32:02 +00:00
|
|
|
final Message msg;
|
|
|
|
if (extras.containsKey(BaseColumns._ID)) {
|
|
|
|
final long messageId = extras.getLong(BaseColumns._ID);
|
|
|
|
msg = Message.restoreMessageWithId(getContext(), messageId);
|
|
|
|
} else {
|
|
|
|
msg = new Message();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (msg == null) return null;
|
2013-04-19 20:36:21 +00:00
|
|
|
final Mailbox mailbox = getMailboxByAccountIdAndType(accountId, Mailbox.TYPE_OUTBOX);
|
2013-02-22 01:32:02 +00:00
|
|
|
if (mailbox == null) return null;
|
2013-03-14 17:02:38 +00:00
|
|
|
// Make sure the sent mailbox exists, since it will be necessary soon.
|
|
|
|
// TODO(yph): move system mailbox creation to somewhere sane.
|
2013-04-19 20:36:21 +00:00
|
|
|
final Mailbox sentMailbox = getMailboxByAccountIdAndType(accountId, Mailbox.TYPE_SENT);
|
2013-03-14 17:02:38 +00:00
|
|
|
if (sentMailbox == null) return null;
|
2013-03-14 20:35:37 +00:00
|
|
|
final Uri messageUri = uiSaveMessage(msg, mailbox, extras);
|
2013-02-22 01:32:02 +00:00
|
|
|
// Kick observers
|
2013-10-30 22:56:33 +00:00
|
|
|
notifyUI(Mailbox.CONTENT_URI, null);
|
2013-02-22 01:32:02 +00:00
|
|
|
return messageUri;
|
|
|
|
}
|
|
|
|
|
2012-12-11 18:37:35 +00:00
|
|
|
private static void putIntegerLongOrBoolean(ContentValues values, String columnName,
|
|
|
|
Object value) {
|
2012-06-28 17:40:46 +00:00
|
|
|
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.
|
2013-09-06 23:05:39 +00:00
|
|
|
* @param folders array of folder Uris to update
|
2012-06-28 17:40:46 +00:00
|
|
|
* @return number of folders updated
|
|
|
|
*/
|
2013-10-30 22:56:33 +00:00
|
|
|
private int updateTimestamp(final Context context, String id, Uri[] folders){
|
2012-06-28 17:40:46 +00:00
|
|
|
int updated = 0;
|
|
|
|
final long now = System.currentTimeMillis();
|
|
|
|
final ContentResolver resolver = context.getContentResolver();
|
2013-10-30 22:56:33 +00:00
|
|
|
final ContentValues touchValues = new ContentValues(1);
|
2013-09-10 23:18:22 +00:00
|
|
|
for (final Uri folder : folders) {
|
2012-06-28 17:40:46 +00:00
|
|
|
touchValues.put(MailboxColumns.LAST_TOUCHED_TIME, now);
|
2013-09-10 23:18:22 +00:00
|
|
|
LogUtils.d(TAG, "updateStamp: %s updated", folder);
|
|
|
|
updated += resolver.update(folder, touchValues, null, null);
|
2012-06-28 17:40:46 +00:00
|
|
|
}
|
|
|
|
final Uri toNotify =
|
|
|
|
UIPROVIDER_RECENT_FOLDERS_NOTIFIER.buildUpon().appendPath(id).build();
|
|
|
|
LogUtils.d(TAG, "updateTimestamp: Notifying on %s", toNotify);
|
2013-10-30 22:56:33 +00:00
|
|
|
notifyUI(toNotify, null);
|
2012-06-28 17:40:46 +00:00
|
|
|
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.
|
2013-09-10 23:18:22 +00:00
|
|
|
* @param uri provider query uri
|
2013-09-06 23:05:39 +00:00
|
|
|
* @param values uri, timestamp pairs
|
2012-06-28 17:40:46 +00:00
|
|
|
* @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();
|
|
|
|
int i = 0;
|
2013-05-23 01:36:07 +00:00
|
|
|
for (final String uriString : values.keySet()) {
|
2012-06-28 17:40:46 +00:00
|
|
|
folders[i] = Uri.parse(uriString);
|
|
|
|
}
|
|
|
|
return updateTimestamp(context, id, folders);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Populates the recent folders according to the design.
|
2013-09-10 23:18:22 +00:00
|
|
|
* @param uri provider query uri
|
2012-06-28 17:40:46 +00:00
|
|
|
* @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) {
|
2013-06-19 23:11:32 +00:00
|
|
|
int result = 0;
|
2012-06-28 17:40:46 +00:00
|
|
|
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...
|
2013-06-19 23:11:32 +00:00
|
|
|
return result;
|
2012-06-28 17:40:46 +00:00
|
|
|
}
|
2013-09-06 23:05:39 +00:00
|
|
|
int state = stateValue;
|
2012-06-28 17:40:46 +00:00
|
|
|
ContentValues values = new ContentValues();
|
2013-06-19 23:11:32 +00:00
|
|
|
if (state == UIProvider.AttachmentState.NOT_SAVED
|
|
|
|
|| state == UIProvider.AttachmentState.REDOWNLOADING) {
|
|
|
|
// Set state, try to cancel request
|
|
|
|
values.put(AttachmentColumns.UI_STATE, UIProvider.AttachmentState.NOT_SAVED);
|
|
|
|
values.put(AttachmentColumns.FLAGS,
|
|
|
|
attachment.mFlags &= ~Attachment.FLAG_DOWNLOAD_USER_REQUEST);
|
|
|
|
attachment.update(context, values);
|
|
|
|
result = 1;
|
|
|
|
}
|
|
|
|
if (state == UIProvider.AttachmentState.DOWNLOADING
|
|
|
|
|| state == UIProvider.AttachmentState.REDOWNLOADING) {
|
|
|
|
// Set state and destination; request download
|
|
|
|
values.put(AttachmentColumns.UI_STATE, UIProvider.AttachmentState.DOWNLOADING);
|
|
|
|
Integer destinationValue =
|
2012-06-28 17:40:46 +00:00
|
|
|
uiValues.getAsInteger(UIProvider.AttachmentColumns.DESTINATION);
|
2013-06-19 23:11:32 +00:00
|
|
|
values.put(AttachmentColumns.UI_DESTINATION,
|
|
|
|
destinationValue == null ? 0 : destinationValue);
|
|
|
|
values.put(AttachmentColumns.FLAGS,
|
|
|
|
attachment.mFlags | Attachment.FLAG_DOWNLOAD_USER_REQUEST);
|
2013-10-31 20:08:40 +00:00
|
|
|
|
|
|
|
if (values.containsKey(AttachmentColumns.LOCATION) &&
|
|
|
|
TextUtils.isEmpty(values.getAsString(AttachmentColumns.LOCATION))) {
|
|
|
|
LogUtils.w(TAG, new Throwable(), "attachment with blank location");
|
|
|
|
}
|
|
|
|
|
2013-06-19 23:11:32 +00:00
|
|
|
attachment.update(context, values);
|
|
|
|
result = 1;
|
|
|
|
}
|
|
|
|
if (state == UIProvider.AttachmentState.SAVED) {
|
|
|
|
// If this is an inline attachment, notify message has changed
|
|
|
|
if (!TextUtils.isEmpty(attachment.mContentId)) {
|
|
|
|
notifyUI(UIPROVIDER_MESSAGE_NOTIFIER, attachment.mMessageKey);
|
|
|
|
}
|
|
|
|
result = 1;
|
2012-06-28 17:40:46 +00:00
|
|
|
}
|
|
|
|
}
|
2013-06-19 23:11:32 +00:00
|
|
|
return result;
|
2012-06-28 17:40:46 +00:00
|
|
|
}
|
|
|
|
|
2012-12-11 18:37:35 +00:00
|
|
|
private int uiUpdateFolder(final Context context, Uri uri, ContentValues uiValues) {
|
|
|
|
// We need to mark seen separately
|
|
|
|
if (uiValues.containsKey(UIProvider.ConversationColumns.SEEN)) {
|
|
|
|
final int seenValue = uiValues.getAsInteger(UIProvider.ConversationColumns.SEEN);
|
|
|
|
|
|
|
|
if (seenValue == 1) {
|
|
|
|
final String mailboxId = uri.getLastPathSegment();
|
|
|
|
final int rows = markAllSeen(context, mailboxId);
|
|
|
|
|
|
|
|
if (uiValues.size() == 1) {
|
|
|
|
// Nothing else to do, so return this value
|
|
|
|
return rows;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
final Uri ourUri = convertToEmailProviderUri(uri, Mailbox.CONTENT_URI, true);
|
2012-06-28 17:40:46 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2012-12-11 18:37:35 +00:00
|
|
|
private int markAllSeen(final Context context, final String mailboxId) {
|
|
|
|
final SQLiteDatabase db = getDatabase(context);
|
|
|
|
final String table = Message.TABLE_NAME;
|
|
|
|
final ContentValues values = new ContentValues(1);
|
|
|
|
values.put(MessageColumns.FLAG_SEEN, 1);
|
|
|
|
final String whereClause = MessageColumns.MAILBOX_KEY + " = ?";
|
|
|
|
final String[] whereArgs = new String[] {mailboxId};
|
|
|
|
|
|
|
|
return db.update(table, values, whereClause, whereArgs);
|
|
|
|
}
|
|
|
|
|
2012-06-28 17:40:46 +00:00
|
|
|
private ContentValues convertUiMessageValues(Message message, ContentValues values) {
|
2013-03-14 20:45:56 +00:00
|
|
|
final ContentValues ourValues = new ContentValues();
|
2012-07-27 18:20:19 +00:00
|
|
|
for (String columnName : values.keySet()) {
|
2013-03-14 20:45:56 +00:00
|
|
|
final Object val = values.get(columnName);
|
2012-06-28 17:40:46 +00:00
|
|
|
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);
|
2012-12-11 18:37:35 +00:00
|
|
|
} else if (columnName.equals(UIProvider.ConversationColumns.SEEN)) {
|
|
|
|
putIntegerLongOrBoolean(ourValues, MessageColumns.FLAG_SEEN, val);
|
2012-06-28 17:40:46 +00:00
|
|
|
} else if (columnName.equals(MessageColumns.MAILBOX_KEY)) {
|
|
|
|
putIntegerLongOrBoolean(ourValues, MessageColumns.MAILBOX_KEY, val);
|
2013-03-14 20:45:56 +00:00
|
|
|
} else if (columnName.equals(UIProvider.ConversationOperations.FOLDERS_UPDATED)) {
|
|
|
|
// Skip this column, as the folders will also be specified the RAW_FOLDERS column
|
2012-06-28 17:40:46 +00:00
|
|
|
} else if (columnName.equals(UIProvider.ConversationColumns.RAW_FOLDERS)) {
|
2012-07-27 16:22:50 +00:00
|
|
|
// Convert from folder list uri to mailbox key
|
2012-12-08 03:33:24 +00:00
|
|
|
final FolderList flist = FolderList.fromBlob(values.getAsByteArray(columnName));
|
|
|
|
if (flist.folders.size() != 1) {
|
|
|
|
LogUtils.e(TAG,
|
2012-07-27 18:20:19 +00:00
|
|
|
"Incorrect number of folders for this message: Message is %s",
|
|
|
|
message.mId);
|
|
|
|
} else {
|
2012-12-08 03:33:24 +00:00
|
|
|
final Folder f = flist.folders.get(0);
|
2013-07-11 20:24:59 +00:00
|
|
|
final Uri uri = f.folderUri.fullUri;
|
2012-12-08 03:33:24 +00:00
|
|
|
final Long mailboxId = Long.parseLong(uri.getLastPathSegment());
|
2012-07-27 16:22:50 +00:00
|
|
|
putIntegerLongOrBoolean(ourValues, MessageColumns.MAILBOX_KEY, mailboxId);
|
|
|
|
}
|
2012-06-28 17:40:46 +00:00
|
|
|
} else if (columnName.equals(UIProvider.MessageColumns.ALWAYS_SHOW_IMAGES)) {
|
2014-01-22 23:34:41 +00:00
|
|
|
Address[] fromList = Address.fromHeader(message.mFrom);
|
2013-06-08 00:31:15 +00:00
|
|
|
final MailPrefs mailPrefs = MailPrefs.get(getContext());
|
2012-06-28 17:40:46 +00:00
|
|
|
for (Address sender : fromList) {
|
2013-06-08 00:31:15 +00:00
|
|
|
final String email = sender.getAddress();
|
|
|
|
mailPrefs.setDisplayImagesFromSender(email, null);
|
2012-06-28 17:40:46 +00:00
|
|
|
}
|
2012-11-05 21:49:23 +00:00
|
|
|
} else if (columnName.equals(UIProvider.ConversationColumns.VIEWED) ||
|
|
|
|
columnName.equals(UIProvider.ConversationOperations.Parameters.SUPPRESS_UNDO)) {
|
2012-08-03 01:09:56 +00:00
|
|
|
// Ignore for now
|
2013-09-10 18:14:59 +00:00
|
|
|
} else if (UIProvider.ConversationColumns.CONVERSATION_INFO.equals(columnName)) {
|
|
|
|
// Email's conversation info is generated, not stored, so just ignore this update
|
2012-06-28 17:40:46 +00:00
|
|
|
} else {
|
|
|
|
throw new IllegalArgumentException("Can't update " + columnName + " in message");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return ourValues;
|
|
|
|
}
|
|
|
|
|
2012-12-11 18:37:35 +00:00
|
|
|
private static Uri convertToEmailProviderUri(Uri uri, Uri newBaseUri, boolean asProvider) {
|
2013-03-14 20:45:56 +00:00
|
|
|
final String idString = uri.getLastPathSegment();
|
2012-06-28 17:40:46 +00:00
|
|
|
try {
|
2013-03-14 20:45:56 +00:00
|
|
|
final long id = Long.parseLong(idString);
|
2012-06-28 17:40:46 +00:00
|
|
|
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...
|
2012-12-11 18:37:35 +00:00
|
|
|
private static boolean uploadsToServer(Context context, Mailbox m) {
|
2012-06-28 17:40:46 +00:00
|
|
|
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) {
|
2012-08-03 01:09:56 +00:00
|
|
|
return uiUpdateMessage(uri, values, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
private int uiUpdateMessage(Uri uri, ContentValues values, boolean forceSync) {
|
2012-06-28 17:40:46 +00:00
|
|
|
Context context = getContext();
|
|
|
|
Message msg = getMessageFromLastSegment(uri);
|
|
|
|
if (msg == null) return 0;
|
|
|
|
Mailbox mailbox = Mailbox.restoreMailboxWithId(context, msg.mMailboxKey);
|
|
|
|
if (mailbox == null) return 0;
|
2012-08-03 01:09:56 +00:00
|
|
|
Uri ourBaseUri =
|
|
|
|
(forceSync || uploadsToServer(context, mailbox)) ? Message.SYNCED_CONTENT_URI :
|
|
|
|
Message.CONTENT_URI;
|
2012-06-28 17:40:46 +00:00
|
|
|
Uri ourUri = convertToEmailProviderUri(uri, ourBaseUri, true);
|
|
|
|
if (ourUri == null) return 0;
|
|
|
|
|
|
|
|
// Special case - meeting response
|
|
|
|
if (values.containsKey(UIProvider.MessageOperations.RESPOND_COLUMN)) {
|
2013-07-30 02:11:41 +00:00
|
|
|
final EmailServiceProxy service =
|
|
|
|
EmailServiceUtils.getServiceForAccount(context, mailbox.mAccountKey);
|
2012-06-28 17:40:46 +00:00
|
|
|
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) {
|
2013-09-10 23:18:22 +00:00
|
|
|
LogUtils.d(TAG, "Remote exception while sending meeting response");
|
2012-06-28 17:40:46 +00:00
|
|
|
}
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2013-08-07 03:20:15 +00:00
|
|
|
// Another special case - deleting a draft.
|
|
|
|
final String operation = values.getAsString(
|
|
|
|
UIProvider.ConversationOperations.OPERATION_KEY);
|
|
|
|
if (UIProvider.ConversationOperations.DISCARD_DRAFTS.equals(operation)) {
|
|
|
|
uiDeleteMessage(uri);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2012-06-28 17:40:46 +00:00
|
|
|
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);
|
2012-12-11 18:37:35 +00:00
|
|
|
} else if (columnName.equals(MessageColumns.FLAG_SEEN)) {
|
|
|
|
undoValues.put(MessageColumns.FLAG_SEEN, msg.mFlagSeen);
|
2012-06-28 17:40:46 +00:00
|
|
|
} else if (columnName.equals(MessageColumns.FLAG_FAVORITE)) {
|
|
|
|
undoValues.put(MessageColumns.FLAG_FAVORITE, msg.mFlagFavorite);
|
|
|
|
}
|
|
|
|
}
|
2013-06-25 22:15:02 +00:00
|
|
|
if (undoValues.size() == 0) {
|
2012-06-28 17:40:46 +00:00
|
|
|
return -1;
|
|
|
|
}
|
2012-11-05 21:33:12 +00:00
|
|
|
final Boolean suppressUndo =
|
|
|
|
values.getAsBoolean(UIProvider.ConversationOperations.Parameters.SUPPRESS_UNDO);
|
2013-09-06 23:05:39 +00:00
|
|
|
if (suppressUndo == null || !suppressUndo) {
|
2012-11-05 21:33:12 +00:00
|
|
|
final ContentProviderOperation op =
|
|
|
|
ContentProviderOperation.newUpdate(convertToEmailProviderUri(
|
|
|
|
uri, ourBaseUri, false))
|
|
|
|
.withValues(undoValues)
|
|
|
|
.build();
|
|
|
|
addToSequence(uri, op);
|
|
|
|
}
|
2013-08-06 23:09:00 +00:00
|
|
|
|
2012-06-28 17:40:46 +00:00
|
|
|
return update(ourUri, ourValues, null, null);
|
|
|
|
}
|
|
|
|
|
2013-08-06 23:09:00 +00:00
|
|
|
/**
|
|
|
|
* Projection for use with getting mailbox & account keys for a message.
|
|
|
|
*/
|
|
|
|
private static final String[] MESSAGE_KEYS_PROJECTION =
|
|
|
|
{ MessageColumns.MAILBOX_KEY, MessageColumns.ACCOUNT_KEY };
|
|
|
|
private static final int MESSAGE_KEYS_MAILBOX_KEY_COLUMN = 0;
|
|
|
|
private static final int MESSAGE_KEYS_ACCOUNT_KEY_COLUMN = 1;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Notify necessary UI components in response to a message update.
|
|
|
|
* @param uri The {@link Uri} for this message update.
|
|
|
|
* @param messageId The id of the message that's been updated.
|
|
|
|
* @param values The {@link ContentValues} that were updated in the message.
|
|
|
|
*/
|
|
|
|
private void handleMessageUpdateNotifications(final Uri uri, final String messageId,
|
|
|
|
final ContentValues values) {
|
|
|
|
if (!uri.getBooleanQueryParameter(IS_UIPROVIDER, false)) {
|
|
|
|
notifyUIConversation(uri);
|
|
|
|
}
|
2013-11-05 19:22:12 +00:00
|
|
|
notifyUIMessage(messageId);
|
2013-08-06 23:09:00 +00:00
|
|
|
// TODO: Ideally, also test that the values actually changed.
|
|
|
|
if (values.containsKey(MessageColumns.FLAG_READ) ||
|
|
|
|
values.containsKey(MessageColumns.MAILBOX_KEY)) {
|
|
|
|
final Cursor c = query(
|
|
|
|
Message.CONTENT_URI.buildUpon().appendEncodedPath(messageId).build(),
|
|
|
|
MESSAGE_KEYS_PROJECTION, null, null, null);
|
|
|
|
if (c != null) {
|
|
|
|
try {
|
|
|
|
if (c.moveToFirst()) {
|
|
|
|
notifyUIFolder(c.getLong(MESSAGE_KEYS_MAILBOX_KEY_COLUMN),
|
|
|
|
c.getLong(MESSAGE_KEYS_ACCOUNT_KEY_COLUMN));
|
|
|
|
}
|
|
|
|
} finally {
|
|
|
|
c.close();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-09-10 23:18:22 +00:00
|
|
|
/**
|
|
|
|
* Perform a "Delete" operation
|
|
|
|
* @param uri message to delete
|
|
|
|
* @return number of rows affected
|
|
|
|
*/
|
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);
|
2013-09-10 23:18:22 +00:00
|
|
|
final int r = context.getContentResolver().delete(
|
2012-06-29 16:42:05 +00:00
|
|
|
ContentUris.withAppendedId(Message.SYNCED_CONTENT_URI, msg.mId), null, null);
|
2013-09-10 23:18:22 +00:00
|
|
|
notifyUIFolder(mailbox.mId, mailbox.mAccountKey);
|
2013-09-25 17:09:01 +00:00
|
|
|
notifyUIMessage(msg.mId);
|
2013-09-10 23:18:22 +00:00
|
|
|
return r;
|
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);
|
2013-09-10 23:18:22 +00:00
|
|
|
final int r = uiUpdateMessage(uri, values, true);
|
2013-03-06 22:34:18 +00:00
|
|
|
notifyUIFolder(mailbox.mId, mailbox.mAccountKey);
|
2013-09-25 17:09:01 +00:00
|
|
|
notifyUIMessage(msg.mId);
|
2013-09-10 23:18:22 +00:00
|
|
|
return r;
|
2012-06-28 17:40:46 +00:00
|
|
|
}
|
|
|
|
|
2014-04-10 18:52:45 +00:00
|
|
|
/**
|
|
|
|
* Hard delete all synced messages in a particular mailbox
|
|
|
|
* @param uri Mailbox to empty (Trash, or maybe Spam/Junk later)
|
|
|
|
* @return number of rows affected
|
|
|
|
*/
|
|
|
|
private int uiPurgeFolder(Uri uri) {
|
|
|
|
final Context context = getContext();
|
|
|
|
final long mailboxId = Long.parseLong(uri.getLastPathSegment());
|
|
|
|
final SQLiteDatabase db = getDatabase(context);
|
|
|
|
|
|
|
|
// Find the account ID (needed in a few calls)
|
|
|
|
final Cursor mailboxCursor = db.query(
|
|
|
|
Mailbox.TABLE_NAME, new String[] { MailboxColumns.ACCOUNT_KEY },
|
2014-04-11 21:42:28 +00:00
|
|
|
Mailbox._ID + "=" + mailboxId, null, null, null, null);
|
2014-04-10 18:52:45 +00:00
|
|
|
if (mailboxCursor == null || !mailboxCursor.moveToFirst()) {
|
|
|
|
LogUtils.wtf(LogUtils.TAG, "Null or empty cursor when trying to purge mailbox %d",
|
|
|
|
mailboxId);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
final long accountId = mailboxCursor.getLong(mailboxCursor.getColumnIndex(
|
|
|
|
MailboxColumns.ACCOUNT_KEY));
|
|
|
|
|
|
|
|
// Find all the messages in the mailbox
|
|
|
|
final String[] messageProjection =
|
2014-04-11 21:42:28 +00:00
|
|
|
new String[] { MessageColumns._ID };
|
2014-04-10 18:52:45 +00:00
|
|
|
final String messageWhere = MessageColumns.MAILBOX_KEY + "=" + mailboxId;
|
|
|
|
final Cursor messageCursor = db.query(Message.TABLE_NAME, messageProjection, messageWhere,
|
|
|
|
null, null, null, null);
|
|
|
|
int deletedCount = 0;
|
|
|
|
|
|
|
|
// Kill them with fire
|
|
|
|
while (messageCursor != null && messageCursor.moveToNext()) {
|
|
|
|
final long messageId = messageCursor.getLong(messageCursor.getColumnIndex(
|
2014-04-11 21:42:28 +00:00
|
|
|
MessageColumns._ID));
|
2014-04-10 18:52:45 +00:00
|
|
|
AttachmentUtilities.deleteAllAttachmentFiles(context, accountId, messageId);
|
|
|
|
deletedCount += context.getContentResolver().delete(
|
|
|
|
ContentUris.withAppendedId(Message.SYNCED_CONTENT_URI, messageId), null, null);
|
|
|
|
notifyUIMessage(messageId);
|
|
|
|
}
|
|
|
|
|
|
|
|
notifyUIFolder(mailboxId, accountId);
|
|
|
|
return deletedCount;
|
|
|
|
}
|
|
|
|
|
2013-09-10 23:18:22 +00:00
|
|
|
public static final String PICKER_UI_ACCOUNT = "picker_ui_account";
|
|
|
|
public static final String PICKER_MAILBOX_TYPE = "picker_mailbox_type";
|
|
|
|
// Currently unused
|
|
|
|
//public static final String PICKER_MESSAGE_ID = "picker_message_id";
|
|
|
|
public static final String PICKER_HEADER_ID = "picker_header_id";
|
|
|
|
|
2012-07-18 19:52:40 +00:00
|
|
|
private int pickFolder(Uri uri, int type, int headerId) {
|
2012-06-29 16:42:05 +00:00
|
|
|
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);
|
2012-07-18 19:52:40 +00:00
|
|
|
intent.putExtra(PICKER_MAILBOX_TYPE, type);
|
|
|
|
intent.putExtra(PICKER_HEADER_ID, headerId);
|
2012-06-29 16:42:05 +00:00
|
|
|
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
|
|
|
context.startActivity(intent);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
} finally {
|
|
|
|
ac.close();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-07-18 19:52:40 +00:00
|
|
|
private int pickTrashFolder(Uri uri) {
|
|
|
|
return pickFolder(uri, Mailbox.TYPE_TRASH, R.string.trash_folder_selection_title);
|
|
|
|
}
|
|
|
|
|
|
|
|
private int pickSentFolder(Uri uri) {
|
|
|
|
return pickFolder(uri, Mailbox.TYPE_SENT, R.string.sent_folder_selection_title);
|
|
|
|
}
|
|
|
|
|
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
|
2013-02-06 23:21:15 +00:00
|
|
|
MatrixCursor c = new MatrixCursorWithCachedColumns(
|
2012-06-28 17:40:46 +00:00
|
|
|
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();
|
|
|
|
return c;
|
|
|
|
} catch (OperationApplicationException e) {
|
2013-09-06 23:05:39 +00:00
|
|
|
LogUtils.d(TAG, "applyBatch exception");
|
2012-06-28 17:40:46 +00:00
|
|
|
}
|
|
|
|
}
|
2013-02-06 23:21:15 +00:00
|
|
|
return new MatrixCursorWithCachedColumns(projection, 0);
|
2012-06-28 17:40:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
2012-07-25 16:00:45 +00:00
|
|
|
if (mailbox == null) {
|
2013-05-26 04:32:32 +00:00
|
|
|
LogUtils.w(TAG, "No mailbox for notification: " + id);
|
2012-07-25 16:00:45 +00:00
|
|
|
return;
|
|
|
|
}
|
2012-06-28 17:40:46 +00:00
|
|
|
// Notify combined inbox...
|
|
|
|
if (mailbox.mType == Mailbox.TYPE_INBOX) {
|
|
|
|
notifyUI(UIPROVIDER_CONVERSATION_NOTIFIER,
|
|
|
|
EmailProvider.combinedMailboxId(Mailbox.TYPE_INBOX));
|
|
|
|
}
|
|
|
|
notifyWidgets(id);
|
|
|
|
}
|
|
|
|
|
2013-09-16 23:15:32 +00:00
|
|
|
/**
|
|
|
|
* Notify about the message id passed in
|
|
|
|
* @param id the message id to be notified
|
|
|
|
*/
|
|
|
|
private void notifyUIMessage(long id) {
|
|
|
|
notifyUI(UIPROVIDER_MESSAGE_NOTIFIER, id);
|
|
|
|
}
|
|
|
|
|
2013-11-05 19:22:12 +00:00
|
|
|
/**
|
|
|
|
* Notify about the message id passed in
|
|
|
|
* @param id the message id to be notified
|
|
|
|
*/
|
|
|
|
private void notifyUIMessage(String id) {
|
|
|
|
notifyUI(UIPROVIDER_MESSAGE_NOTIFIER, id);
|
|
|
|
}
|
|
|
|
|
2013-02-26 20:04:29 +00:00
|
|
|
/**
|
|
|
|
* Notify about the Account id passed in
|
|
|
|
* @param id the Account id to be notified
|
|
|
|
*/
|
|
|
|
private void notifyUIAccount(long id) {
|
|
|
|
// Notify on the specific account
|
|
|
|
notifyUI(UIPROVIDER_ACCOUNT_NOTIFIER, Long.toString(id));
|
|
|
|
|
|
|
|
// Notify on the all accounts list
|
|
|
|
notifyUI(UIPROVIDER_ALL_ACCOUNTS_NOTIFIER, null);
|
|
|
|
}
|
|
|
|
|
2013-10-08 20:38:47 +00:00
|
|
|
// TODO: temporary workaround for ConversationCursor
|
|
|
|
@Deprecated
|
|
|
|
private static final int NOTIFY_FOLDER_LOOP_MESSAGE_ID = 0;
|
|
|
|
@Deprecated
|
|
|
|
private Handler mFolderNotifierHandler;
|
|
|
|
|
2013-03-06 22:34:18 +00:00
|
|
|
/**
|
|
|
|
* Notify about a folder update. Because folder changes can affect the conversation cursor's
|
|
|
|
* extras, the conversation must also be notified here.
|
|
|
|
* @param folderId the folder id to be notified
|
2013-08-06 23:09:00 +00:00
|
|
|
* @param accountId the account id to be notified (for folder list notification).
|
2013-03-06 22:34:18 +00:00
|
|
|
*/
|
2013-08-06 23:09:00 +00:00
|
|
|
private void notifyUIFolder(final String folderId, final long accountId) {
|
2013-03-06 22:34:18 +00:00
|
|
|
notifyUI(UIPROVIDER_CONVERSATION_NOTIFIER, folderId);
|
|
|
|
notifyUI(UIPROVIDER_FOLDER_NOTIFIER, folderId);
|
2013-08-06 23:09:00 +00:00
|
|
|
if (accountId != Account.NO_ACCOUNT) {
|
2013-03-06 22:34:18 +00:00
|
|
|
notifyUI(UIPROVIDER_FOLDERLIST_NOTIFIER, accountId);
|
|
|
|
}
|
2013-09-10 21:25:28 +00:00
|
|
|
|
|
|
|
// Notify for combined account too
|
|
|
|
// TODO: might be nice to only notify when an inbox changes
|
|
|
|
notifyUI(UIPROVIDER_FOLDER_NOTIFIER,
|
|
|
|
getVirtualMailboxId(COMBINED_ACCOUNT_ID, Mailbox.TYPE_INBOX));
|
|
|
|
notifyUI(UIPROVIDER_FOLDERLIST_NOTIFIER, COMBINED_ACCOUNT_ID);
|
2013-10-08 20:38:47 +00:00
|
|
|
|
|
|
|
// TODO: temporary workaround for ConversationCursor
|
|
|
|
synchronized (this) {
|
|
|
|
if (mFolderNotifierHandler == null) {
|
|
|
|
mFolderNotifierHandler = new Handler(Looper.getMainLooper(),
|
|
|
|
new Callback() {
|
|
|
|
@Override
|
|
|
|
public boolean handleMessage(final android.os.Message message) {
|
|
|
|
final String folderId = (String) message.obj;
|
|
|
|
LogUtils.d(TAG, "Notifying conversation Uri %s twice", folderId);
|
|
|
|
notifyUI(UIPROVIDER_CONVERSATION_NOTIFIER, folderId);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
mFolderNotifierHandler.removeMessages(NOTIFY_FOLDER_LOOP_MESSAGE_ID);
|
|
|
|
android.os.Message message = android.os.Message.obtain(mFolderNotifierHandler,
|
|
|
|
NOTIFY_FOLDER_LOOP_MESSAGE_ID);
|
|
|
|
message.obj = folderId;
|
|
|
|
mFolderNotifierHandler.sendMessageDelayed(message, 2000);
|
2013-03-06 22:34:18 +00:00
|
|
|
}
|
|
|
|
|
2013-08-06 23:09:00 +00:00
|
|
|
private void notifyUIFolder(final long folderId, final long accountId) {
|
|
|
|
notifyUIFolder(Long.toString(folderId), accountId);
|
2013-03-06 22:34:18 +00:00
|
|
|
}
|
|
|
|
|
2013-10-30 22:56:33 +00:00
|
|
|
private void notifyUI(final Uri uri, final String id) {
|
2013-02-26 20:04:29 +00:00
|
|
|
final Uri notifyUri = (id != null) ? uri.buildUpon().appendPath(id).build() : uri;
|
2014-01-03 00:16:08 +00:00
|
|
|
final Set<Uri> batchNotifications = getBatchNotificationsSet();
|
|
|
|
if (batchNotifications != null) {
|
|
|
|
batchNotifications.add(notifyUri);
|
2013-10-30 22:56:33 +00:00
|
|
|
} else {
|
|
|
|
getContext().getContentResolver().notifyChange(notifyUri, null);
|
|
|
|
}
|
2012-06-28 17:40:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
private void notifyUI(Uri uri, long id) {
|
|
|
|
notifyUI(uri, Long.toString(id));
|
|
|
|
}
|
|
|
|
|
2013-04-12 00:11:34 +00:00
|
|
|
private Mailbox getMailbox(final Uri uri) {
|
|
|
|
final long id = Long.parseLong(uri.getLastPathSegment());
|
|
|
|
return Mailbox.restoreMailboxWithId(getContext(), id);
|
|
|
|
}
|
|
|
|
|
2013-04-19 23:32:57 +00:00
|
|
|
/**
|
|
|
|
* Create an android.accounts.Account object for this account.
|
|
|
|
* @param accountId id of account to load.
|
|
|
|
* @return an android.accounts.Account for this account, or null if we can't load it.
|
|
|
|
*/
|
|
|
|
private android.accounts.Account getAccountManagerAccount(final long accountId) {
|
|
|
|
final Context context = getContext();
|
|
|
|
final Account account = Account.restoreAccountWithId(context, accountId);
|
|
|
|
if (account == null) return null;
|
2013-09-27 00:20:11 +00:00
|
|
|
return getAccountManagerAccount(context, account.mEmailAddress,
|
|
|
|
account.getProtocol(context));
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Create an android.accounts.Account object for an emailAddress/protocol pair.
|
|
|
|
* @param context A {@link Context}.
|
|
|
|
* @param emailAddress The email address we're interested in.
|
|
|
|
* @param protocol The protocol we're intereted in.
|
|
|
|
* @return an {@link android.accounts.Account} for this info.
|
|
|
|
*/
|
|
|
|
private static android.accounts.Account getAccountManagerAccount(final Context context,
|
|
|
|
final String emailAddress, final String protocol) {
|
|
|
|
final EmailServiceInfo info = EmailServiceUtils.getServiceInfo(context, protocol);
|
2014-05-05 20:59:39 +00:00
|
|
|
if (info == null) {
|
|
|
|
return null;
|
|
|
|
}
|
2013-09-27 00:20:11 +00:00
|
|
|
return new android.accounts.Account(emailAddress, info.accountType);
|
2013-04-19 23:32:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Update an account's periodic sync if the sync interval has changed.
|
|
|
|
* @param accountId id for the account to update.
|
|
|
|
* @param values the ContentValues for this update to the account.
|
|
|
|
*/
|
|
|
|
private void updateAccountSyncInterval(final long accountId, final ContentValues values) {
|
|
|
|
final Integer syncInterval = values.getAsInteger(AccountColumns.SYNC_INTERVAL);
|
|
|
|
if (syncInterval == null) {
|
|
|
|
// No change to the sync interval.
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
final android.accounts.Account account = getAccountManagerAccount(accountId);
|
|
|
|
if (account == null) {
|
|
|
|
// Unable to load the account, or unknown protocol.
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2013-05-26 04:32:32 +00:00
|
|
|
LogUtils.d(TAG, "Setting sync interval for account " + accountId + " to " + syncInterval +
|
2013-04-19 23:32:57 +00:00
|
|
|
" minutes");
|
|
|
|
|
2013-06-20 00:42:08 +00:00
|
|
|
// First remove all existing periodic syncs.
|
|
|
|
final List<PeriodicSync> syncs =
|
|
|
|
ContentResolver.getPeriodicSyncs(account, EmailContent.AUTHORITY);
|
|
|
|
for (final PeriodicSync sync : syncs) {
|
|
|
|
ContentResolver.removePeriodicSync(account, EmailContent.AUTHORITY, sync.extras);
|
|
|
|
}
|
2013-04-19 23:32:57 +00:00
|
|
|
|
2013-07-12 01:04:13 +00:00
|
|
|
// Only positive values of sync interval indicate periodic syncs. The value is in minutes,
|
|
|
|
// while addPeriodicSync expects its time in seconds.
|
|
|
|
if (syncInterval > 0) {
|
|
|
|
ContentResolver.addPeriodicSync(account, EmailContent.AUTHORITY, Bundle.EMPTY,
|
|
|
|
syncInterval * DateUtils.MINUTE_IN_MILLIS / DateUtils.SECOND_IN_MILLIS);
|
2013-04-19 23:32:57 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-09-27 00:20:11 +00:00
|
|
|
/**
|
|
|
|
* Request a sync.
|
|
|
|
* @param account The {@link android.accounts.Account} we want to sync.
|
|
|
|
* @param mailboxId The mailbox id we want to sync (or one of the special constants in
|
|
|
|
* {@link Mailbox}).
|
|
|
|
* @param deltaMessageCount If we're requesting a load more, the number of additional messages
|
|
|
|
* to sync.
|
|
|
|
*/
|
|
|
|
private static void startSync(final android.accounts.Account account, final long mailboxId,
|
|
|
|
final int deltaMessageCount) {
|
2013-10-09 18:03:40 +00:00
|
|
|
final Bundle extras = Mailbox.createSyncBundle(mailboxId);
|
2013-04-25 20:52:49 +00:00
|
|
|
extras.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
|
|
|
|
extras.putBoolean(ContentResolver.SYNC_EXTRAS_DO_NOT_RETRY, true);
|
|
|
|
extras.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true);
|
|
|
|
if (deltaMessageCount != 0) {
|
|
|
|
extras.putInt(Mailbox.SYNC_EXTRA_DELTA_MESSAGE_COUNT, deltaMessageCount);
|
|
|
|
}
|
2013-05-03 01:32:36 +00:00
|
|
|
extras.putString(EmailServiceStatus.SYNC_EXTRAS_CALLBACK_URI,
|
|
|
|
EmailContent.CONTENT_URI.toString());
|
|
|
|
extras.putString(EmailServiceStatus.SYNC_EXTRAS_CALLBACK_METHOD,
|
|
|
|
SYNC_STATUS_CALLBACK_METHOD);
|
2013-04-25 20:52:49 +00:00
|
|
|
ContentResolver.requestSync(account, EmailContent.AUTHORITY, extras);
|
2013-10-01 23:11:54 +00:00
|
|
|
LogUtils.i(TAG, "requestSync EmailProvider startSync %s, %s", account.toString(),
|
|
|
|
extras.toString());
|
2013-04-25 20:52:49 +00:00
|
|
|
}
|
|
|
|
|
2013-09-27 00:20:11 +00:00
|
|
|
/**
|
|
|
|
* Request a sync.
|
|
|
|
* @param mailbox The {@link Mailbox} we want to sync.
|
|
|
|
* @param deltaMessageCount If we're requesting a load more, the number of additional messages
|
|
|
|
* to sync.
|
|
|
|
*/
|
|
|
|
private void startSync(final Mailbox mailbox, final int deltaMessageCount) {
|
|
|
|
final android.accounts.Account account = getAccountManagerAccount(mailbox.mAccountKey);
|
2014-05-05 20:59:39 +00:00
|
|
|
if (account != null) {
|
|
|
|
startSync(account, mailbox.mId, deltaMessageCount);
|
|
|
|
}
|
2013-09-27 00:20:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Restart any push operations for an account.
|
|
|
|
* @param account The {@link android.accounts.Account} we're interested in.
|
|
|
|
*/
|
|
|
|
private static void restartPush(final android.accounts.Account account) {
|
2013-10-09 18:03:40 +00:00
|
|
|
final Bundle extras = new Bundle();
|
|
|
|
extras.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
|
|
|
|
extras.putBoolean(ContentResolver.SYNC_EXTRAS_DO_NOT_RETRY, true);
|
|
|
|
extras.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true);
|
|
|
|
extras.putBoolean(Mailbox.SYNC_EXTRA_PUSH_ONLY, true);
|
|
|
|
extras.putString(EmailServiceStatus.SYNC_EXTRAS_CALLBACK_URI,
|
|
|
|
EmailContent.CONTENT_URI.toString());
|
|
|
|
extras.putString(EmailServiceStatus.SYNC_EXTRAS_CALLBACK_METHOD,
|
|
|
|
SYNC_STATUS_CALLBACK_METHOD);
|
|
|
|
ContentResolver.requestSync(account, EmailContent.AUTHORITY, extras);
|
|
|
|
LogUtils.i(TAG, "requestSync EmailProvider startSync %s, %s", account.toString(),
|
|
|
|
extras.toString());
|
2013-09-27 00:20:11 +00:00
|
|
|
}
|
|
|
|
|
2013-04-12 00:11:34 +00:00
|
|
|
private Cursor uiFolderRefresh(final Mailbox mailbox, final int deltaMessageCount) {
|
2013-04-25 20:52:49 +00:00
|
|
|
if (mailbox != null) {
|
2013-10-18 00:46:26 +00:00
|
|
|
RefreshStatusMonitor.getInstance(getContext())
|
|
|
|
.monitorRefreshStatus(mailbox.mId, new RefreshStatusMonitor.Callback() {
|
|
|
|
@Override
|
|
|
|
public void onRefreshCompleted(long mailboxId, int result) {
|
|
|
|
final ContentValues values = new ContentValues();
|
|
|
|
values.put(Mailbox.UI_SYNC_STATUS, UIProvider.SyncStatus.NO_SYNC);
|
|
|
|
values.put(Mailbox.UI_LAST_SYNC_RESULT, result);
|
|
|
|
mDatabase.update(
|
|
|
|
Mailbox.TABLE_NAME,
|
|
|
|
values,
|
|
|
|
WHERE_ID,
|
|
|
|
new String[] { String.valueOf(mailboxId) });
|
|
|
|
notifyUIFolder(mailbox.mId, mailbox.mAccountKey);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onTimeout(long mailboxId) {
|
|
|
|
// todo
|
|
|
|
}
|
|
|
|
});
|
2013-04-25 20:52:49 +00:00
|
|
|
startSync(mailbox, deltaMessageCount);
|
2012-06-28 17:40:46 +00:00
|
|
|
}
|
|
|
|
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;
|
|
|
|
|
2013-04-12 00:11:34 +00:00
|
|
|
private Cursor uiFolderLoadMore(final Mailbox mailbox) {
|
2012-06-28 17:40:46 +00:00
|
|
|
if (mailbox == null) return null;
|
|
|
|
if (mailbox.mType == Mailbox.TYPE_SEARCH) {
|
|
|
|
// Ask for 10 more messages
|
|
|
|
mSearchParams.mOffset += SEARCH_MORE_INCREMENT;
|
2013-04-12 00:11:34 +00:00
|
|
|
runSearchQuery(getContext(), mailbox.mAccountKey, mailbox.mId);
|
2012-06-28 17:40:46 +00:00
|
|
|
} else {
|
2013-04-12 00:11:34 +00:00
|
|
|
uiFolderRefresh(mailbox, VISIBLE_LIMIT_INCREMENT);
|
2012-06-28 17:40:46 +00:00
|
|
|
}
|
|
|
|
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;
|
2013-06-21 00:11:01 +00:00
|
|
|
m.mSyncInterval = 0;
|
2012-06-28 17:40:46 +00:00
|
|
|
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) {
|
2013-05-30 02:30:52 +00:00
|
|
|
LogUtils.d(TAG, "runSearchQuery. account: %d mailbox id: %d",
|
|
|
|
accountId, searchMailboxId);
|
|
|
|
|
2012-06-28 17:40:46 +00:00
|
|
|
// Start the search running in the background
|
2013-05-30 02:30:52 +00:00
|
|
|
new AsyncTask<Void, Void, Void>() {
|
2012-06-28 17:40:46 +00:00
|
|
|
@Override
|
2013-05-30 02:30:52 +00:00
|
|
|
public Void doInBackground(Void... params) {
|
2013-07-30 02:11:41 +00:00
|
|
|
final EmailServiceProxy service =
|
|
|
|
EmailServiceUtils.getServiceForAccount(context, accountId);
|
2013-05-30 02:30:52 +00:00
|
|
|
if (service != null) {
|
|
|
|
try {
|
2013-10-07 21:40:14 +00:00
|
|
|
final int totalCount =
|
2013-05-30 02:30:52 +00:00
|
|
|
service.searchMessages(accountId, mSearchParams, searchMailboxId);
|
2013-10-07 21:40:14 +00:00
|
|
|
|
|
|
|
// Save away the total count
|
|
|
|
final ContentValues cv = new ContentValues(1);
|
|
|
|
cv.put(MailboxColumns.TOTAL_COUNT, totalCount);
|
|
|
|
update(ContentUris.withAppendedId(Mailbox.CONTENT_URI, searchMailboxId), cv,
|
|
|
|
null, null);
|
2013-05-30 02:30:52 +00:00
|
|
|
LogUtils.d(TAG, "EmailProvider#runSearchQuery. TotalCount to UI: %d",
|
2013-10-07 21:40:14 +00:00
|
|
|
totalCount);
|
2013-05-30 02:30:52 +00:00
|
|
|
} catch (RemoteException e) {
|
2013-05-26 04:32:32 +00:00
|
|
|
LogUtils.e("searchMessages", "RemoteException", e);
|
2012-06-28 17:40:46 +00:00
|
|
|
}
|
|
|
|
}
|
2013-05-30 02:30:52 +00:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
2012-06-28 17:40:46 +00:00
|
|
|
}
|
|
|
|
|
2013-10-23 21:00:11 +00:00
|
|
|
// This handles an initial search query. More results are loaded using uiFolderLoadMore.
|
2012-06-28 17:40:46 +00:00
|
|
|
private Cursor uiSearch(Uri uri, String[] projection) {
|
2013-05-30 02:30:52 +00:00
|
|
|
LogUtils.d(TAG, "runSearchQuery in search %s", uri);
|
2012-06-28 17:40:46 +00:00
|
|
|
final long accountId = Long.parseLong(uri.getLastPathSegment());
|
|
|
|
|
|
|
|
// TODO: Check the actual mailbox
|
|
|
|
Mailbox inbox = Mailbox.restoreMailboxOfType(getContext(), accountId, Mailbox.TYPE_INBOX);
|
2012-09-05 19:32:49 +00:00
|
|
|
if (inbox == null) {
|
2013-05-26 04:32:32 +00:00
|
|
|
LogUtils.w(Logging.LOG_TAG, "In uiSearch, inbox doesn't exist for account "
|
|
|
|
+ accountId);
|
2013-05-30 02:30:52 +00:00
|
|
|
|
2012-09-05 19:32:49 +00:00
|
|
|
return null;
|
|
|
|
}
|
2012-06-28 17:40:46 +00:00
|
|
|
|
|
|
|
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) {
|
2013-10-23 21:00:11 +00:00
|
|
|
// TODO: This conditional is unnecessary, just two lines earlier we created
|
|
|
|
// mSearchParams using a constructor that never sets mOffset.
|
2013-05-30 02:30:52 +00:00
|
|
|
LogUtils.d(TAG, "deleting existing search results.");
|
|
|
|
|
2012-06-28 17:40:46 +00:00
|
|
|
// Delete existing contents of search mailbox
|
|
|
|
ContentResolver resolver = context.getContentResolver();
|
2014-04-11 21:42:28 +00:00
|
|
|
resolver.delete(Message.CONTENT_URI, MessageColumns.MAILBOX_KEY + "=" + searchMailboxId,
|
2012-06-28 17:40:46 +00:00
|
|
|
null);
|
2013-09-16 18:56:11 +00:00
|
|
|
final ContentValues cv = new ContentValues(1);
|
2012-06-28 17:40:46 +00:00
|
|
|
// 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,
|
2012-12-11 18:37:35 +00:00
|
|
|
searchMailbox.mId), projection, false);
|
2012-06-28 17:40:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
private static final String MAILBOXES_FOR_ACCOUNT_SELECTION = MailboxColumns.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);
|
2013-11-25 18:02:34 +00:00
|
|
|
// TODO: We ought to reconcile accounts here, but some callers do this in a loop,
|
|
|
|
// which would be a problem when the first account reconciliation shuts us down.
|
2012-06-28 17:40:46 +00:00
|
|
|
return 1;
|
|
|
|
} catch (Exception e) {
|
2013-05-26 04:32:32 +00:00
|
|
|
LogUtils.w(Logging.LOG_TAG, "Exception while deleting account", e);
|
2012-06-28 17:40:46 +00:00
|
|
|
}
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2014-01-25 00:43:36 +00:00
|
|
|
/**
|
2014-03-28 22:52:02 +00:00
|
|
|
* The method will no longer be needed after platform L releases. As emails are received from
|
2014-01-25 00:43:36 +00:00
|
|
|
* various protocols the email addresses are decoded and intended to be stored in the database
|
|
|
|
* in decoded form. The problem is that Exchange is a separate .apk and the old Exchange .apk
|
|
|
|
* still attempts to store <strong>encoded</strong> email addresses. So, we decode here at the
|
|
|
|
* Provider before writing to the database to ensure the addresses are written in decoded form.
|
|
|
|
*
|
|
|
|
* @param values the values to be written into the Message table
|
|
|
|
*/
|
|
|
|
private static void decodeEmailAddresses(ContentValues values) {
|
|
|
|
if (values.containsKey(Message.MessageColumns.TO_LIST)) {
|
|
|
|
final String to = values.getAsString(Message.MessageColumns.TO_LIST);
|
|
|
|
values.put(Message.MessageColumns.TO_LIST, Address.fromHeaderToString(to));
|
|
|
|
}
|
|
|
|
|
|
|
|
if (values.containsKey(Message.MessageColumns.FROM_LIST)) {
|
|
|
|
final String from = values.getAsString(Message.MessageColumns.FROM_LIST);
|
|
|
|
values.put(Message.MessageColumns.FROM_LIST, Address.fromHeaderToString(from));
|
|
|
|
}
|
|
|
|
|
|
|
|
if (values.containsKey(Message.MessageColumns.CC_LIST)) {
|
|
|
|
final String cc = values.getAsString(Message.MessageColumns.CC_LIST);
|
|
|
|
values.put(Message.MessageColumns.CC_LIST, Address.fromHeaderToString(cc));
|
|
|
|
}
|
|
|
|
|
|
|
|
if (values.containsKey(Message.MessageColumns.BCC_LIST)) {
|
|
|
|
final String bcc = values.getAsString(Message.MessageColumns.BCC_LIST);
|
|
|
|
values.put(Message.MessageColumns.BCC_LIST, Address.fromHeaderToString(bcc));
|
|
|
|
}
|
|
|
|
|
|
|
|
if (values.containsKey(Message.MessageColumns.REPLY_TO_LIST)) {
|
|
|
|
final String replyTo = values.getAsString(Message.MessageColumns.REPLY_TO_LIST);
|
|
|
|
values.put(Message.MessageColumns.REPLY_TO_LIST,
|
|
|
|
Address.fromHeaderToString(replyTo));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-07-20 01:42:43 +00:00
|
|
|
/** Projection used for getting email address for an account. */
|
|
|
|
private static final String[] ACCOUNT_EMAIL_PROJECTION = { AccountColumns.EMAIL_ADDRESS };
|
|
|
|
|
2012-12-11 18:37:35 +00:00
|
|
|
private static void deleteAccountData(Context context, long accountId) {
|
2013-07-20 01:42:43 +00:00
|
|
|
// We will delete PIM data, but by the time the asynchronous call to do that happens,
|
|
|
|
// the account may have been deleted from the DB. Therefore we have to get the email
|
|
|
|
// address now and send that, rather than the account id.
|
|
|
|
final String emailAddress = Utility.getFirstRowString(context, Account.CONTENT_URI,
|
|
|
|
ACCOUNT_EMAIL_PROJECTION, Account.ID_SELECTION,
|
|
|
|
new String[] {Long.toString(accountId)}, null, 0);
|
|
|
|
if (emailAddress == null) {
|
|
|
|
LogUtils.e(TAG, "Could not find email address for account %d", accountId);
|
|
|
|
}
|
|
|
|
|
2012-06-28 17:40:46 +00:00
|
|
|
// Delete synced attachments
|
|
|
|
AttachmentUtilities.deleteAllAccountAttachmentFiles(context, accountId);
|
|
|
|
|
2013-06-18 02:45:17 +00:00
|
|
|
// Delete all mailboxes.
|
2012-06-28 17:40:46 +00:00
|
|
|
ContentResolver resolver = context.getContentResolver();
|
|
|
|
String[] accountIdArgs = new String[] { Long.toString(accountId) };
|
2013-06-18 02:45:17 +00:00
|
|
|
resolver.delete(Mailbox.CONTENT_URI, MAILBOXES_FOR_ACCOUNT_SELECTION, accountIdArgs);
|
2012-06-28 17:40:46 +00:00
|
|
|
|
2013-06-18 02:45:17 +00:00
|
|
|
// Delete account sync key.
|
|
|
|
final ContentValues cv = new ContentValues();
|
2014-04-11 21:42:28 +00:00
|
|
|
cv.putNull(AccountColumns.SYNC_KEY);
|
2012-06-28 17:40:46 +00:00
|
|
|
resolver.update(Account.CONTENT_URI, cv, Account.ID_SELECTION, accountIdArgs);
|
|
|
|
|
2014-05-21 20:41:37 +00:00
|
|
|
// Delete PIM data (contacts, calendar), stop syncs, etc. if applicable
|
2013-07-20 01:42:43 +00:00
|
|
|
if (emailAddress != null) {
|
2014-05-21 20:41:37 +00:00
|
|
|
final IEmailService service =
|
|
|
|
EmailServiceUtils.getServiceForAccount(context, accountId);
|
|
|
|
if (service != null) {
|
|
|
|
try {
|
|
|
|
service.deleteAccountPIMData(emailAddress);
|
|
|
|
} catch (final RemoteException e) {
|
|
|
|
// Can't do anything about this
|
|
|
|
}
|
|
|
|
}
|
2012-06-28 17:40:46 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private int[] mSavedWidgetIds = new int[0];
|
2013-02-22 02:10:10 +00:00
|
|
|
private final ArrayList<Long> mWidgetNotifyMailboxes = new ArrayList<Long>();
|
2012-06-28 17:40:46 +00:00
|
|
|
private AppWidgetManager mAppWidgetManager;
|
|
|
|
private ComponentName mEmailComponent;
|
|
|
|
|
|
|
|
private void notifyWidgets(long mailboxId) {
|
|
|
|
Context context = getContext();
|
|
|
|
// Lazily initialize these
|
|
|
|
if (mAppWidgetManager == null) {
|
|
|
|
mAppWidgetManager = AppWidgetManager.getInstance(context);
|
2013-10-09 19:39:43 +00:00
|
|
|
mEmailComponent = new ComponentName(context, WidgetProvider.getProviderName(context));
|
2012-06-28 17:40:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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 {
|
2013-10-09 19:39:43 +00:00
|
|
|
if (widgetInfo == null || TextUtils.isEmpty(widgetInfo[1])) continue;
|
2012-06-28 17:40:46 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
2012-09-08 01:31:04 +00:00
|
|
|
|
2013-08-01 15:47:51 +00:00
|
|
|
@Override
|
2012-09-08 01:31:04 +00:00
|
|
|
public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
|
|
|
|
Context context = getContext();
|
|
|
|
writer.println("Installed services:");
|
|
|
|
for (EmailServiceInfo info: EmailServiceUtils.getServiceInfoList(context)) {
|
|
|
|
writer.println(" " + info);
|
|
|
|
}
|
|
|
|
writer.println();
|
|
|
|
writer.println("Accounts: ");
|
|
|
|
Cursor cursor = query(Account.CONTENT_URI, Account.CONTENT_PROJECTION, null, null, null);
|
|
|
|
if (cursor.getCount() == 0) {
|
|
|
|
writer.println(" None");
|
|
|
|
}
|
|
|
|
try {
|
|
|
|
while (cursor.moveToNext()) {
|
|
|
|
Account account = new Account();
|
|
|
|
account.restore(cursor);
|
|
|
|
writer.println(" Account " + account.mDisplayName);
|
|
|
|
HostAuth hostAuth =
|
|
|
|
HostAuth.restoreHostAuthWithId(context, account.mHostAuthKeyRecv);
|
|
|
|
if (hostAuth != null) {
|
|
|
|
writer.println(" Protocol = " + hostAuth.mProtocol +
|
|
|
|
(TextUtils.isEmpty(account.mProtocolVersion) ? "" : " version " +
|
|
|
|
account.mProtocolVersion));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} finally {
|
|
|
|
cursor.close();
|
|
|
|
}
|
|
|
|
}
|
2013-09-25 23:33:55 +00:00
|
|
|
|
|
|
|
synchronized public Handler getDelayedSyncHandler() {
|
|
|
|
if (mDelayedSyncHandler == null) {
|
|
|
|
mDelayedSyncHandler = new Handler(getContext().getMainLooper(), new Callback() {
|
|
|
|
@Override
|
|
|
|
public boolean handleMessage(android.os.Message msg) {
|
|
|
|
synchronized (mDelayedSyncRequests) {
|
|
|
|
final SyncRequestMessage request = (SyncRequestMessage) msg.obj;
|
2013-09-30 18:51:14 +00:00
|
|
|
// TODO: It's possible that the account is deleted by the time we get here
|
|
|
|
// It would be nice if we could validate it before trying to sync
|
|
|
|
final android.accounts.Account account = request.mAccount;
|
2013-10-09 18:03:40 +00:00
|
|
|
final Bundle extras = Mailbox.createSyncBundle(request.mMailboxId);
|
2013-09-30 18:51:14 +00:00
|
|
|
ContentResolver.requestSync(account, request.mAuthority, extras);
|
2013-10-01 23:11:54 +00:00
|
|
|
LogUtils.i(TAG, "requestSync getDelayedSyncHandler %s, %s",
|
|
|
|
account.toString(), extras.toString());
|
2013-09-25 23:33:55 +00:00
|
|
|
mDelayedSyncRequests.remove(request);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
return mDelayedSyncHandler;
|
|
|
|
}
|
|
|
|
|
|
|
|
private class SyncRequestMessage {
|
|
|
|
private final String mAuthority;
|
2013-09-30 18:51:14 +00:00
|
|
|
private final android.accounts.Account mAccount;
|
2013-09-25 23:33:55 +00:00
|
|
|
private final long mMailboxId;
|
|
|
|
|
2013-09-30 18:51:14 +00:00
|
|
|
private SyncRequestMessage(final String authority, final android.accounts.Account account,
|
|
|
|
final long mailboxId) {
|
2013-09-25 23:33:55 +00:00
|
|
|
mAuthority = authority;
|
2013-09-30 18:51:14 +00:00
|
|
|
mAccount = account;
|
2013-09-25 23:33:55 +00:00
|
|
|
mMailboxId = mailboxId;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public boolean equals(Object o) {
|
|
|
|
if (this == o) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
if (o == null || getClass() != o.getClass()) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
SyncRequestMessage that = (SyncRequestMessage) o;
|
|
|
|
|
2013-09-30 18:51:14 +00:00
|
|
|
return mAccount.equals(that.mAccount)
|
|
|
|
&& mMailboxId == that.mMailboxId
|
|
|
|
&& mAuthority.equals(that.mAuthority);
|
2013-09-25 23:33:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public int hashCode() {
|
|
|
|
int result = mAuthority.hashCode();
|
2013-09-30 18:51:14 +00:00
|
|
|
result = 31 * result + mAccount.hashCode();
|
2013-09-25 23:33:55 +00:00
|
|
|
result = 31 * result + (int) (mMailboxId ^ (mMailboxId >>> 32));
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
}
|
2014-06-16 20:30:25 +00:00
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
|
|
|
|
if (PreferenceKeys.REMOVAL_ACTION.equals(key) ||
|
|
|
|
PreferenceKeys.CONVERSATION_LIST_SWIPE.equals(key) ||
|
|
|
|
PreferenceKeys.SHOW_SENDER_IMAGES.equals(key) ||
|
|
|
|
PreferenceKeys.DEFAULT_REPLY_ALL.equals(key) ||
|
|
|
|
PreferenceKeys.CONVERSATION_OVERVIEW_MODE.equals(key) ||
|
|
|
|
PreferenceKeys.AUTO_ADVANCE_MODE.equals(key) ||
|
|
|
|
PreferenceKeys.SNAP_HEADER_MODE.equals(key) ||
|
|
|
|
PreferenceKeys.CONFIRM_DELETE.equals(key) ||
|
|
|
|
PreferenceKeys.CONFIRM_ARCHIVE.equals(key) ||
|
|
|
|
PreferenceKeys.CONFIRM_SEND.equals(key)) {
|
|
|
|
notifyUI(UIPROVIDER_ALL_ACCOUNTS_NOTIFIER, null);
|
|
|
|
}
|
|
|
|
}
|
2009-05-26 23:40:34 +00:00
|
|
|
}
|