blob: 59acee1122e5bc0e481ced014d09fb44343ccb21 [file] [log] [blame]
Andrew Stadlerf3d5b202009-05-26 16:40:34 -07001/*
2 * Copyright (C) 2009 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.email.provider;
18
Marc Blanke7e1ca42009-09-15 19:27:05 -070019import android.accounts.AccountManager;
Andrew Stadlerf3d5b202009-05-26 16:40:34 -070020import android.content.ContentProvider;
Marc Blankfae47272009-05-29 14:24:34 -070021import android.content.ContentProviderOperation;
22import android.content.ContentProviderResult;
Marc Blank1b9337e2010-09-23 09:19:44 -070023import android.content.ContentResolver;
Andrew Stadlerf3d5b202009-05-26 16:40:34 -070024import android.content.ContentUris;
25import android.content.ContentValues;
26import android.content.Context;
Marc Blankfae47272009-05-29 14:24:34 -070027import android.content.OperationApplicationException;
Andrew Stadlerf3d5b202009-05-26 16:40:34 -070028import android.content.UriMatcher;
Todd Kennedybf30f942011-05-06 11:31:10 -070029import android.database.ContentObserver;
Andrew Stadlerf3d5b202009-05-26 16:40:34 -070030import android.database.Cursor;
Marc Blankd306ba32010-12-30 14:55:27 -080031import android.database.MatrixCursor;
Marc Blanka290f502009-06-15 14:40:06 -070032import android.database.SQLException;
Andrew Stadlerf3d5b202009-05-26 16:40:34 -070033import android.database.sqlite.SQLiteDatabase;
Marc Blank0e1595c2009-11-18 17:11:33 -080034import android.database.sqlite.SQLiteException;
Andrew Stadlerf3d5b202009-05-26 16:40:34 -070035import android.database.sqlite.SQLiteOpenHelper;
36import android.net.Uri;
Marc Blankf3ff0ba2011-05-19 14:14:14 -070037import android.provider.ContactsContract;
Makoto Onuki9dad9ad2011-06-20 18:10:10 -070038import android.text.TextUtils;
Andrew Stadlerf3d5b202009-05-26 16:40:34 -070039import android.util.Log;
40
Marc Blank6e418aa2011-06-18 18:03:11 -070041import com.android.email.Email;
42import com.android.email.Preferences;
43import com.android.email.provider.ContentCache.CacheToken;
44import com.android.email.service.AttachmentDownloadService;
45import com.android.emailcommon.AccountManagerTypes;
46import com.android.emailcommon.CalendarProviderStub;
47import com.android.emailcommon.Logging;
48import com.android.emailcommon.provider.Account;
49import com.android.emailcommon.provider.EmailContent;
50import com.android.emailcommon.provider.EmailContent.AccountColumns;
51import com.android.emailcommon.provider.EmailContent.Attachment;
52import com.android.emailcommon.provider.EmailContent.AttachmentColumns;
53import com.android.emailcommon.provider.EmailContent.Body;
54import com.android.emailcommon.provider.EmailContent.BodyColumns;
55import com.android.emailcommon.provider.EmailContent.HostAuthColumns;
56import com.android.emailcommon.provider.EmailContent.MailboxColumns;
57import com.android.emailcommon.provider.EmailContent.Message;
58import com.android.emailcommon.provider.EmailContent.MessageColumns;
59import com.android.emailcommon.provider.EmailContent.PolicyColumns;
60import com.android.emailcommon.provider.EmailContent.QuickResponseColumns;
61import com.android.emailcommon.provider.EmailContent.SyncColumns;
62import com.android.emailcommon.provider.HostAuth;
63import com.android.emailcommon.provider.Mailbox;
64import com.android.emailcommon.provider.Policy;
65import com.android.emailcommon.provider.QuickResponse;
66import com.android.emailcommon.service.LegacyPolicySet;
67import com.google.common.annotations.VisibleForTesting;
68
Marc Blank0e1595c2009-11-18 17:11:33 -080069import java.io.File;
Fred Quintana84969fb2009-06-01 12:55:50 -070070import java.util.ArrayList;
Marc Blank6e418aa2011-06-18 18:03:11 -070071import java.util.Arrays;
72import java.util.Collection;
73import java.util.HashMap;
Marc Blank5d7ff852011-06-27 20:11:24 -070074import java.util.List;
Marc Blank6e418aa2011-06-18 18:03:11 -070075import java.util.Map;
Fred Quintana84969fb2009-06-01 12:55:50 -070076
Andrew Stadlerf3d5b202009-05-26 16:40:34 -070077public class EmailProvider extends ContentProvider {
78
79 private static final String TAG = "EmailProvider";
80
Marc Blank0e1595c2009-11-18 17:11:33 -080081 protected static final String DATABASE_NAME = "EmailProvider.db";
82 protected static final String BODY_DATABASE_NAME = "EmailProviderBody.db";
Marc Blank09931902011-05-06 12:07:39 -070083 protected static final String BACKUP_DATABASE_NAME = "EmailProviderBackup.db";
Marc Blank0e1595c2009-11-18 17:11:33 -080084
Marc Blank09fd4d02010-08-09 17:48:53 -070085 public static final String ACTION_ATTACHMENT_UPDATED = "com.android.email.ATTACHMENT_UPDATED";
86 public static final String ATTACHMENT_UPDATED_EXTRA_FLAGS =
87 "com.android.email.ATTACHMENT_UPDATED_FLAGS";
88
Marc Blankc81bef62010-10-13 19:04:46 -070089 public static final String EMAIL_MESSAGE_MIME_TYPE =
90 "vnd.android.cursor.item/email-message";
Marc Blank09fd4d02010-08-09 17:48:53 -070091 public static final String EMAIL_ATTACHMENT_MIME_TYPE =
92 "vnd.android.cursor.item/email-attachment";
93
Marc Blank0e1595c2009-11-18 17:11:33 -080094 public static final Uri INTEGRITY_CHECK_URI =
95 Uri.parse("content://" + EmailContent.AUTHORITY + "/integrityCheck");
Marc Blank09931902011-05-06 12:07:39 -070096 public static final Uri ACCOUNT_BACKUP_URI =
97 Uri.parse("content://" + EmailContent.AUTHORITY + "/accountBackup");
Marc Blankef832992009-10-13 16:25:00 -070098
Todd Kennedybf30f942011-05-06 11:31:10 -070099 /** Appended to the notification URI for delete operations */
100 public static final String NOTIFICATION_OP_DELETE = "delete";
101 /** Appended to the notification URI for insert operations */
102 public static final String NOTIFICATION_OP_INSERT = "insert";
103 /** Appended to the notification URI for update operations */
104 public static final String NOTIFICATION_OP_UPDATE = "update";
105
Marc Blankef832992009-10-13 16:25:00 -0700106 // Definitions for our queries looking for orphaned messages
107 private static final String[] ORPHANS_PROJECTION
108 = new String[] {MessageColumns.ID, MessageColumns.MAILBOX_KEY};
109 private static final int ORPHANS_ID = 0;
110 private static final int ORPHANS_MAILBOX_KEY = 1;
111
112 private static final String WHERE_ID = EmailContent.RECORD_ID + "=?";
Marc Blankf3743042009-06-27 12:14:14 -0700113
Marc Blank6e418aa2011-06-18 18:03:11 -0700114 // This is not a hard limit on accounts, per se, but beyond this, we can't guarantee that all
115 // critical mailboxes, host auth's, accounts, and policies are cached
116 private static final int MAX_CACHED_ACCOUNTS = 16;
117 // Inbox, Drafts, Sent, Outbox, Trash, and Search (these boxes are cached when possible)
118 private static final int NUM_ALWAYS_CACHED_MAILBOXES = 6;
119
Marc Blankfab77f12010-10-27 16:50:54 -0700120 // We'll cache the following four tables; sizes are best estimates of effective values
Marc Blank6e418aa2011-06-18 18:03:11 -0700121 private final ContentCache mCacheAccount =
122 new ContentCache("Account", Account.CONTENT_PROJECTION, MAX_CACHED_ACCOUNTS);
123 private final ContentCache mCacheHostAuth =
124 new ContentCache("HostAuth", HostAuth.CONTENT_PROJECTION, MAX_CACHED_ACCOUNTS * 2);
125 /*package*/ final ContentCache mCacheMailbox =
126 new ContentCache("Mailbox", Mailbox.CONTENT_PROJECTION,
127 MAX_CACHED_ACCOUNTS * (NUM_ALWAYS_CACHED_MAILBOXES + 2));
128 private final ContentCache mCacheMessage =
Marc Blankaeee10e2011-04-27 17:12:06 -0700129 new ContentCache("Message", Message.CONTENT_PROJECTION, 8);
Marc Blank6e418aa2011-06-18 18:03:11 -0700130 private final ContentCache mCachePolicy =
131 new ContentCache("Policy", Policy.CONTENT_PROJECTION, MAX_CACHED_ACCOUNTS);
Marc Blankfab77f12010-10-27 16:50:54 -0700132
Andrew Stadler4a8c70c2009-08-18 12:14:15 -0700133 // Any changes to the database format *must* include update-in-place code.
Marc Blanke2569832009-09-07 16:03:02 -0700134 // Original version: 3
Marc Blanke7e1ca42009-09-15 19:27:05 -0700135 // Version 4: Database wipe required; changing AccountManager interface w/Exchange
Andrew Stadler0d008892009-09-22 18:31:10 -0700136 // Version 5: Database wipe required; changing AccountManager interface w/Exchange
137 // Version 6: Adding Message.mServerTimeStamp column
Marc Blankef832992009-10-13 16:25:00 -0700138 // Version 7: Replace the mailbox_delete trigger with a version that removes orphaned messages
139 // from the Message_Deletes and Message_Updates tables
Andrew Stadlerfc8d9432010-01-21 11:48:02 -0800140 // Version 8: Add security flags column to accounts table
Andrew Stadler345fb8b2010-01-26 17:24:15 -0800141 // Version 9: Add security sync key and signature to accounts table
Marc Blank4006e5f2010-02-15 16:01:38 -0800142 // Version 10: Add meeting info to message table
Andrew Stadler3aaba9e2010-02-22 12:57:33 -0800143 // Version 11: Add content and flags to attachment table
Makoto Onuki20225d52010-03-12 13:30:26 -0800144 // Version 12: Add content_bytes to attachment table. content is deprecated.
Makoto Onuki574854b2010-07-30 13:53:59 -0700145 // Version 13: Add messageCount to Mailbox table.
Marc Blanke7b9e4a2010-09-01 19:06:15 -0700146 // Version 14: Add snippet to Message table
Makoto Onukiddc8dea2010-09-07 12:36:48 -0700147 // Version 15: Fix upgrade problem in version 14.
Marc Blank75a873b2010-12-08 17:11:04 -0800148 // Version 16: Add accountKey to Attachment table
Andy Stadler3a585092011-03-01 10:45:50 -0800149 // Version 17: Add parentKey to Mailbox table
Todd Kennedy22208772011-04-22 15:45:11 -0700150 // Version 18: Copy Mailbox.displayName to Mailbox.serverId for all IMAP & POP3 mailboxes.
151 // Column Mailbox.serverId is used for the server-side pathname of a mailbox.
Marc Blankaeee10e2011-04-27 17:12:06 -0700152 // Version 19: Add Policy table; add policyKey to Account table and trigger to delete an
153 // Account's policy when the Account is deleted
Marc Blankf91a03f2011-05-05 10:34:29 -0700154 // Version 20: Add new policies to Policy table
Todd Kennedya9ac20b2011-05-06 13:37:02 -0700155 // Version 21: Add lastSeenMessageKey column to Mailbox table
Marc Blankf3ff0ba2011-05-19 14:14:14 -0700156 // Version 22: Upgrade path for IMAP/POP accounts to integrate with AccountManager
Todd Kennedy9dcb72e2011-06-03 08:51:25 -0700157 // Version 23: Add column to mailbox table for time of last access
Ben Komalo313586c2011-06-07 11:39:16 -0700158 // Version 24: Add column to hostauth table for client cert alias
Jorge Lugo5a3888f2011-06-01 10:09:26 -0700159 // Version 25: Added QuickResponse table
Marc Blankdeb345a2011-06-23 10:22:25 -0700160 // Version 26: Update IMAP accounts to add FLAG_SUPPORTS_SEARCH flag
Marc Blank9a013532011-06-23 10:52:21 -0700161 // Version 27: Add protocolSearchInfo to Message table
Marc Blankaca94262011-07-19 18:19:59 -0700162 // Version 28: Add notifiedMessageId and notifiedMessageCount to Account
Marc Blankf3ff0ba2011-05-19 14:14:14 -0700163
Marc Blankaca94262011-07-19 18:19:59 -0700164 public static final int DATABASE_VERSION = 28;
Marc Blanke2569832009-09-07 16:03:02 -0700165
166 // Any changes to the database format *must* include update-in-place code.
167 // Original version: 2
168 // Version 3: Add "sourceKey" column
Marc Blanke7e1ca42009-09-15 19:27:05 -0700169 // Version 4: Database wipe required; changing AccountManager interface w/Exchange
Andrew Stadler0d008892009-09-22 18:31:10 -0700170 // Version 5: Database wipe required; changing AccountManager interface w/Exchange
Marc Blank5fc57ec2009-09-22 18:38:28 -0700171 // Version 6: Adding Body.mIntroText column
172 public static final int BODY_DATABASE_VERSION = 6;
Andrew Stadlerf3d5b202009-05-26 16:40:34 -0700173
Andrew Stadlerf3d5b202009-05-26 16:40:34 -0700174 private static final int ACCOUNT_BASE = 0;
175 private static final int ACCOUNT = ACCOUNT_BASE;
Makoto Onuki7bcf1882010-09-10 15:19:57 -0700176 private static final int ACCOUNT_ID = ACCOUNT_BASE + 1;
177 private static final int ACCOUNT_ID_ADD_TO_FIELD = ACCOUNT_BASE + 2;
Makoto Onuki261d6c32010-09-14 16:28:50 -0700178 private static final int ACCOUNT_RESET_NEW_COUNT = ACCOUNT_BASE + 3;
179 private static final int ACCOUNT_RESET_NEW_COUNT_ID = ACCOUNT_BASE + 4;
Marc Blank6e418aa2011-06-18 18:03:11 -0700180 private static final int ACCOUNT_DEFAULT_ID = ACCOUNT_BASE + 5;
Marc Blankf3743042009-06-27 12:14:14 -0700181
Andrew Stadlerf3d5b202009-05-26 16:40:34 -0700182 private static final int MAILBOX_BASE = 0x1000;
183 private static final int MAILBOX = MAILBOX_BASE;
Makoto Onuki7bcf1882010-09-10 15:19:57 -0700184 private static final int MAILBOX_ID = MAILBOX_BASE + 1;
Marc Blank6e418aa2011-06-18 18:03:11 -0700185 private static final int MAILBOX_ID_FROM_ACCOUNT_AND_TYPE = MAILBOX_BASE + 2;
Makoto Onuki7bcf1882010-09-10 15:19:57 -0700186 private static final int MAILBOX_ID_ADD_TO_FIELD = MAILBOX_BASE + 2;
Marc Blankf3743042009-06-27 12:14:14 -0700187
Andrew Stadlerf3d5b202009-05-26 16:40:34 -0700188 private static final int MESSAGE_BASE = 0x2000;
189 private static final int MESSAGE = MESSAGE_BASE;
Andrew Stadler41192182009-07-16 16:03:40 -0700190 private static final int MESSAGE_ID = MESSAGE_BASE + 1;
191 private static final int SYNCED_MESSAGE_ID = MESSAGE_BASE + 2;
Marc Blankf3743042009-06-27 12:14:14 -0700192
Andrew Stadlerf3d5b202009-05-26 16:40:34 -0700193 private static final int ATTACHMENT_BASE = 0x3000;
194 private static final int ATTACHMENT = ATTACHMENT_BASE;
Makoto Onuki7bcf1882010-09-10 15:19:57 -0700195 private static final int ATTACHMENT_ID = ATTACHMENT_BASE + 1;
196 private static final int ATTACHMENTS_MESSAGE_ID = ATTACHMENT_BASE + 2;
Marc Blankf3743042009-06-27 12:14:14 -0700197
Andrew Stadlerf3d5b202009-05-26 16:40:34 -0700198 private static final int HOSTAUTH_BASE = 0x4000;
199 private static final int HOSTAUTH = HOSTAUTH_BASE;
200 private static final int HOSTAUTH_ID = HOSTAUTH_BASE + 1;
Marc Blankf3743042009-06-27 12:14:14 -0700201
Marc Blanke34525d2009-06-17 10:22:37 -0700202 private static final int UPDATED_MESSAGE_BASE = 0x5000;
203 private static final int UPDATED_MESSAGE = UPDATED_MESSAGE_BASE;
Marc Blankf3743042009-06-27 12:14:14 -0700204 private static final int UPDATED_MESSAGE_ID = UPDATED_MESSAGE_BASE + 1;
205
206 private static final int DELETED_MESSAGE_BASE = 0x6000;
207 private static final int DELETED_MESSAGE = DELETED_MESSAGE_BASE;
208 private static final int DELETED_MESSAGE_ID = DELETED_MESSAGE_BASE + 1;
Marc Blankf3743042009-06-27 12:14:14 -0700209
Marc Blankaeee10e2011-04-27 17:12:06 -0700210 private static final int POLICY_BASE = 0x7000;
211 private static final int POLICY = POLICY_BASE;
212 private static final int POLICY_ID = POLICY_BASE + 1;
213
Jorge Lugo5a3888f2011-06-01 10:09:26 -0700214 private static final int QUICK_RESPONSE_BASE = 0x8000;
215 private static final int QUICK_RESPONSE = QUICK_RESPONSE_BASE;
216 private static final int QUICK_RESPONSE_ID = QUICK_RESPONSE_BASE + 1;
217 private static final int QUICK_RESPONSE_ACCOUNT_ID = QUICK_RESPONSE_BASE + 2;
218
Marc Blankf3743042009-06-27 12:14:14 -0700219 // MUST ALWAYS EQUAL THE LAST OF THE PREVIOUS BASE CONSTANTS
Jorge Lugo5a3888f2011-06-01 10:09:26 -0700220 private static final int LAST_EMAIL_PROVIDER_DB_BASE = QUICK_RESPONSE_BASE;
Marc Blankf3743042009-06-27 12:14:14 -0700221
222 // DO NOT CHANGE BODY_BASE!!
223 private static final int BODY_BASE = LAST_EMAIL_PROVIDER_DB_BASE + 0x1000;
Andrew Stadlerf3d5b202009-05-26 16:40:34 -0700224 private static final int BODY = BODY_BASE;
225 private static final int BODY_ID = BODY_BASE + 1;
Marc Blankf3743042009-06-27 12:14:14 -0700226
Andrew Stadlerf3d5b202009-05-26 16:40:34 -0700227 private static final int BASE_SHIFT = 12; // 12 bits to the base type: 0, 0x1000, 0x2000, etc.
Marc Blankf3743042009-06-27 12:14:14 -0700228
Marc Blankfab77f12010-10-27 16:50:54 -0700229 // TABLE_NAMES MUST remain in the order of the BASE constants above (e.g. ACCOUNT_BASE = 0x0000,
230 // MESSAGE_BASE = 0x1000, etc.)
Andrew Stadlerf3d5b202009-05-26 16:40:34 -0700231 private static final String[] TABLE_NAMES = {
Marc Blankf5418f12011-06-13 15:32:27 -0700232 Account.TABLE_NAME,
Ben Komalo53ea83e2011-05-13 17:26:27 -0700233 Mailbox.TABLE_NAME,
Marc Blank6e418aa2011-06-18 18:03:11 -0700234 Message.TABLE_NAME,
235 Attachment.TABLE_NAME,
Ben Komalo12b82d92011-05-19 15:18:12 -0700236 HostAuth.TABLE_NAME,
Marc Blank6e418aa2011-06-18 18:03:11 -0700237 Message.UPDATED_TABLE_NAME,
238 Message.DELETED_TABLE_NAME,
Marc Blankaeee10e2011-04-27 17:12:06 -0700239 Policy.TABLE_NAME,
Jorge Lugo5a3888f2011-06-01 10:09:26 -0700240 QuickResponse.TABLE_NAME,
Marc Blank6e418aa2011-06-18 18:03:11 -0700241 Body.TABLE_NAME
Andrew Stadlerf3d5b202009-05-26 16:40:34 -0700242 };
Marc Blankf3743042009-06-27 12:14:14 -0700243
Marc Blankfab77f12010-10-27 16:50:54 -0700244 // CONTENT_CACHES MUST remain in the order of the BASE constants above
Marc Blank6e418aa2011-06-18 18:03:11 -0700245 private final ContentCache[] mContentCaches = {
246 mCacheAccount,
247 mCacheMailbox,
248 mCacheMessage,
Marc Blankaeee10e2011-04-27 17:12:06 -0700249 null, // Attachment
Marc Blank6e418aa2011-06-18 18:03:11 -0700250 mCacheHostAuth,
Marc Blankaeee10e2011-04-27 17:12:06 -0700251 null, // Updated message
252 null, // Deleted message
Marc Blank6e418aa2011-06-18 18:03:11 -0700253 mCachePolicy,
254 null, // Quick response
255 null // Body
256 };
257
258 // CACHE_PROJECTIONS MUST remain in the order of the BASE constants above
259 private static final String[][] CACHE_PROJECTIONS = {
260 Account.CONTENT_PROJECTION,
261 Mailbox.CONTENT_PROJECTION,
262 Message.CONTENT_PROJECTION,
263 null, // Attachment
264 HostAuth.CONTENT_PROJECTION,
265 null, // Updated message
266 null, // Deleted message
267 Policy.CONTENT_PROJECTION,
Jorge Lugo5a3888f2011-06-01 10:09:26 -0700268 null, // Quick response
Marc Blankaeee10e2011-04-27 17:12:06 -0700269 null // Body
270 };
Marc Blankfab77f12010-10-27 16:50:54 -0700271
Andrew Stadlerf3d5b202009-05-26 16:40:34 -0700272 private static final UriMatcher sURIMatcher = new UriMatcher(UriMatcher.NO_MATCH);
273
Marc Blank6e418aa2011-06-18 18:03:11 -0700274 private static final String MAILBOX_PRE_CACHE_SELECTION = MailboxColumns.TYPE + " IN (" +
275 Mailbox.TYPE_INBOX + "," + Mailbox.TYPE_DRAFTS + "," + Mailbox.TYPE_TRASH + "," +
276 Mailbox.TYPE_SENT + "," + Mailbox.TYPE_SEARCH + "," + Mailbox.TYPE_OUTBOX + ")";
277
Marc Blankf3743042009-06-27 12:14:14 -0700278 /**
279 * Let's only generate these SQL strings once, as they are used frequently
280 * Note that this isn't relevant for table creation strings, since they are used only once
281 */
282 private static final String UPDATED_MESSAGE_INSERT = "insert or ignore into " +
283 Message.UPDATED_TABLE_NAME + " select * from " + Message.TABLE_NAME + " where " +
284 EmailContent.RECORD_ID + '=';
285
286 private static final String UPDATED_MESSAGE_DELETE = "delete from " +
287 Message.UPDATED_TABLE_NAME + " where " + EmailContent.RECORD_ID + '=';
288
289 private static final String DELETED_MESSAGE_INSERT = "insert or replace into " +
290 Message.DELETED_TABLE_NAME + " select * from " + Message.TABLE_NAME + " where " +
291 EmailContent.RECORD_ID + '=';
292
293 private static final String DELETE_ORPHAN_BODIES = "delete from " + Body.TABLE_NAME +
Mihai Predafb7974f2009-08-03 15:05:50 +0200294 " where " + BodyColumns.MESSAGE_KEY + " in " + "(select " + BodyColumns.MESSAGE_KEY +
Marc Blankf3743042009-06-27 12:14:14 -0700295 " from " + Body.TABLE_NAME + " except select " + EmailContent.RECORD_ID + " from " +
296 Message.TABLE_NAME + ')';
297
298 private static final String DELETE_BODY = "delete from " + Body.TABLE_NAME +
Mihai Predafb7974f2009-08-03 15:05:50 +0200299 " where " + BodyColumns.MESSAGE_KEY + '=';
Marc Blankf3743042009-06-27 12:14:14 -0700300
Marc Blankc0c9c332009-08-19 19:07:29 -0700301 private static final String ID_EQUALS = EmailContent.RECORD_ID + "=?";
302
Marc Blankef832992009-10-13 16:25:00 -0700303 private static final String TRIGGER_MAILBOX_DELETE =
304 "create trigger mailbox_delete before delete on " + Mailbox.TABLE_NAME +
305 " begin" +
306 " delete from " + Message.TABLE_NAME +
307 " where " + MessageColumns.MAILBOX_KEY + "=old." + EmailContent.RECORD_ID +
308 "; delete from " + Message.UPDATED_TABLE_NAME +
309 " where " + MessageColumns.MAILBOX_KEY + "=old." + EmailContent.RECORD_ID +
310 "; delete from " + Message.DELETED_TABLE_NAME +
311 " where " + MessageColumns.MAILBOX_KEY + "=old." + EmailContent.RECORD_ID +
312 "; end";
Marc Blank36796362009-10-19 22:15:45 -0700313
Marc Blankaeee10e2011-04-27 17:12:06 -0700314 private static final String TRIGGER_ACCOUNT_DELETE =
315 "create trigger account_delete before delete on " + Account.TABLE_NAME +
316 " begin delete from " + Mailbox.TABLE_NAME +
317 " where " + MailboxColumns.ACCOUNT_KEY + "=old." + EmailContent.RECORD_ID +
318 "; delete from " + HostAuth.TABLE_NAME +
319 " where " + EmailContent.RECORD_ID + "=old." + AccountColumns.HOST_AUTH_KEY_RECV +
320 "; delete from " + HostAuth.TABLE_NAME +
321 " where " + EmailContent.RECORD_ID + "=old." + AccountColumns.HOST_AUTH_KEY_SEND +
322 "; delete from " + Policy.TABLE_NAME +
323 " where " + EmailContent.RECORD_ID + "=old." + AccountColumns.POLICY_KEY +
324 "; end";
325
Makoto Onuki261d6c32010-09-14 16:28:50 -0700326 private static final ContentValues CONTENT_VALUES_RESET_NEW_MESSAGE_COUNT;
327
Marc Blankc81bef62010-10-13 19:04:46 -0700328 public static final String MESSAGE_URI_PARAMETER_MAILBOX_ID = "mailboxId";
329
Andrew Stadlerf3d5b202009-05-26 16:40:34 -0700330 static {
331 // Email URI matching table
332 UriMatcher matcher = sURIMatcher;
Marc Blankf3743042009-06-27 12:14:14 -0700333
Andrew Stadlerf3d5b202009-05-26 16:40:34 -0700334 // All accounts
Marc Blank31d9acb2011-02-11 15:05:17 -0800335 matcher.addURI(EmailContent.AUTHORITY, "account", ACCOUNT);
Andrew Stadlerf3d5b202009-05-26 16:40:34 -0700336 // A specific account
Marc Blank758a5322009-07-30 11:41:31 -0700337 // insert into this URI causes a mailbox to be added to the account
Marc Blank31d9acb2011-02-11 15:05:17 -0800338 matcher.addURI(EmailContent.AUTHORITY, "account/#", ACCOUNT_ID);
Marc Blank6e418aa2011-06-18 18:03:11 -0700339 matcher.addURI(EmailContent.AUTHORITY, "account/default", ACCOUNT_DEFAULT_ID);
Marc Blankf3743042009-06-27 12:14:14 -0700340
Makoto Onuki261d6c32010-09-14 16:28:50 -0700341 // Special URI to reset the new message count. Only update works, and content values
342 // will be ignored.
Marc Blank31d9acb2011-02-11 15:05:17 -0800343 matcher.addURI(EmailContent.AUTHORITY, "resetNewMessageCount",
344 ACCOUNT_RESET_NEW_COUNT);
345 matcher.addURI(EmailContent.AUTHORITY, "resetNewMessageCount/#",
346 ACCOUNT_RESET_NEW_COUNT_ID);
Makoto Onuki261d6c32010-09-14 16:28:50 -0700347
Andrew Stadlerf3d5b202009-05-26 16:40:34 -0700348 // All mailboxes
Marc Blank31d9acb2011-02-11 15:05:17 -0800349 matcher.addURI(EmailContent.AUTHORITY, "mailbox", MAILBOX);
Andrew Stadlerf3d5b202009-05-26 16:40:34 -0700350 // A specific mailbox
351 // insert into this URI causes a message to be added to the mailbox
352 // ** NOTE For now, the accountKey must be set manually in the values!
Marc Blank31d9acb2011-02-11 15:05:17 -0800353 matcher.addURI(EmailContent.AUTHORITY, "mailbox/#", MAILBOX_ID);
Marc Blank6e418aa2011-06-18 18:03:11 -0700354 matcher.addURI(EmailContent.AUTHORITY, "mailboxIdFromAccountAndType/#/#",
355 MAILBOX_ID_FROM_ACCOUNT_AND_TYPE);
Andrew Stadlerf3d5b202009-05-26 16:40:34 -0700356 // All messages
Marc Blank31d9acb2011-02-11 15:05:17 -0800357 matcher.addURI(EmailContent.AUTHORITY, "message", MESSAGE);
Andrew Stadlerf3d5b202009-05-26 16:40:34 -0700358 // A specific message
Marc Blank758a5322009-07-30 11:41:31 -0700359 // insert into this URI causes an attachment to be added to the message
Marc Blank31d9acb2011-02-11 15:05:17 -0800360 matcher.addURI(EmailContent.AUTHORITY, "message/#", MESSAGE_ID);
Marc Blankf3743042009-06-27 12:14:14 -0700361
Andrew Stadlerf3d5b202009-05-26 16:40:34 -0700362 // A specific attachment
Marc Blank31d9acb2011-02-11 15:05:17 -0800363 matcher.addURI(EmailContent.AUTHORITY, "attachment", ATTACHMENT);
Andrew Stadlerf3d5b202009-05-26 16:40:34 -0700364 // A specific attachment (the header information)
Marc Blank31d9acb2011-02-11 15:05:17 -0800365 matcher.addURI(EmailContent.AUTHORITY, "attachment/#", ATTACHMENT_ID);
Andrew Stadler41192182009-07-16 16:03:40 -0700366 // The attachments of a specific message (query only) (insert & delete TBD)
Marc Blank31d9acb2011-02-11 15:05:17 -0800367 matcher.addURI(EmailContent.AUTHORITY, "attachment/message/#",
368 ATTACHMENTS_MESSAGE_ID);
Andrew Stadlerf3d5b202009-05-26 16:40:34 -0700369
370 // All mail bodies
Marc Blank31d9acb2011-02-11 15:05:17 -0800371 matcher.addURI(EmailContent.AUTHORITY, "body", BODY);
Andrew Stadlerf3d5b202009-05-26 16:40:34 -0700372 // A specific mail body
Marc Blank31d9acb2011-02-11 15:05:17 -0800373 matcher.addURI(EmailContent.AUTHORITY, "body/#", BODY_ID);
Marc Blankf3743042009-06-27 12:14:14 -0700374
Andrew Stadler41192182009-07-16 16:03:40 -0700375 // All hostauth records
Marc Blank31d9acb2011-02-11 15:05:17 -0800376 matcher.addURI(EmailContent.AUTHORITY, "hostauth", HOSTAUTH);
Andrew Stadler41192182009-07-16 16:03:40 -0700377 // A specific hostauth
Marc Blank31d9acb2011-02-11 15:05:17 -0800378 matcher.addURI(EmailContent.AUTHORITY, "hostauth/#", HOSTAUTH_ID);
Marc Blankf3743042009-06-27 12:14:14 -0700379
Marc Blankc0c9c332009-08-19 19:07:29 -0700380 // Atomically a constant value to a particular field of a mailbox/account
Marc Blank31d9acb2011-02-11 15:05:17 -0800381 matcher.addURI(EmailContent.AUTHORITY, "mailboxIdAddToField/#",
382 MAILBOX_ID_ADD_TO_FIELD);
383 matcher.addURI(EmailContent.AUTHORITY, "accountIdAddToField/#",
384 ACCOUNT_ID_ADD_TO_FIELD);
Marc Blankc0c9c332009-08-19 19:07:29 -0700385
Marc Blankf3743042009-06-27 12:14:14 -0700386 /**
387 * THIS URI HAS SPECIAL SEMANTICS
Marc Blankc0c9c332009-08-19 19:07:29 -0700388 * ITS USE IS INTENDED FOR THE UI APPLICATION TO MARK CHANGES THAT NEED TO BE SYNCED BACK
Marc Blankf3743042009-06-27 12:14:14 -0700389 * TO A SERVER VIA A SYNC ADAPTER
390 */
Marc Blank31d9acb2011-02-11 15:05:17 -0800391 matcher.addURI(EmailContent.AUTHORITY, "syncedMessage/#", SYNCED_MESSAGE_ID);
Marc Blankf3743042009-06-27 12:14:14 -0700392
393 /**
394 * THE URIs BELOW THIS POINT ARE INTENDED TO BE USED BY SYNC ADAPTERS ONLY
395 * THEY REFER TO DATA CREATED AND MAINTAINED BY CALLS TO THE SYNCED_MESSAGE_ID URI
396 * BY THE UI APPLICATION
397 */
398 // All deleted messages
Marc Blank31d9acb2011-02-11 15:05:17 -0800399 matcher.addURI(EmailContent.AUTHORITY, "deletedMessage", DELETED_MESSAGE);
Marc Blankf3743042009-06-27 12:14:14 -0700400 // A specific deleted message
Marc Blank31d9acb2011-02-11 15:05:17 -0800401 matcher.addURI(EmailContent.AUTHORITY, "deletedMessage/#", DELETED_MESSAGE_ID);
Marc Blankf3743042009-06-27 12:14:14 -0700402
403 // All updated messages
Marc Blank31d9acb2011-02-11 15:05:17 -0800404 matcher.addURI(EmailContent.AUTHORITY, "updatedMessage", UPDATED_MESSAGE);
Marc Blankf3743042009-06-27 12:14:14 -0700405 // A specific updated message
Marc Blank31d9acb2011-02-11 15:05:17 -0800406 matcher.addURI(EmailContent.AUTHORITY, "updatedMessage/#", UPDATED_MESSAGE_ID);
Makoto Onuki261d6c32010-09-14 16:28:50 -0700407
408 CONTENT_VALUES_RESET_NEW_MESSAGE_COUNT = new ContentValues();
409 CONTENT_VALUES_RESET_NEW_MESSAGE_COUNT.put(Account.NEW_MESSAGE_COUNT, 0);
Marc Blankaeee10e2011-04-27 17:12:06 -0700410
411 matcher.addURI(EmailContent.AUTHORITY, "policy", POLICY);
412 matcher.addURI(EmailContent.AUTHORITY, "policy/#", POLICY_ID);
Jorge Lugo5a3888f2011-06-01 10:09:26 -0700413
414 // All quick responses
415 matcher.addURI(EmailContent.AUTHORITY, "quickresponse", QUICK_RESPONSE);
416 // A specific quick response
417 matcher.addURI(EmailContent.AUTHORITY, "quickresponse/#", QUICK_RESPONSE_ID);
418 // All quick responses associated with a particular account id
419 matcher.addURI(EmailContent.AUTHORITY, "quickresponse/account/#",
420 QUICK_RESPONSE_ACCOUNT_ID);
Andrew Stadlerf3d5b202009-05-26 16:40:34 -0700421 }
422
Marc Blanke6a22df2010-12-16 12:28:16 -0800423 /**
424 * Wrap the UriMatcher call so we can throw a runtime exception if an unknown Uri is passed in
425 * @param uri the Uri to match
426 * @return the match value
427 */
428 private static int findMatch(Uri uri, String methodName) {
429 int match = sURIMatcher.match(uri);
430 if (match < 0) {
Marc Blank1c1bd6a2010-12-29 20:51:16 -0800431 throw new IllegalArgumentException("Unknown uri: " + uri);
Makoto Onukibfac9f22011-05-13 11:20:04 -0700432 } else if (Logging.LOGD) {
Marc Blanke6a22df2010-12-16 12:28:16 -0800433 Log.v(TAG, methodName + ": uri=" + uri + ", match is " + match);
434 }
435 return match;
436 }
437
Mihai Preda9627d012009-08-12 12:51:26 +0200438 /*
439 * Internal helper method for index creation.
440 * Example:
441 * "create index message_" + MessageColumns.FLAG_READ
442 * + " on " + Message.TABLE_NAME + " (" + MessageColumns.FLAG_READ + ");"
443 */
444 /* package */
445 static String createIndex(String tableName, String columnName) {
446 return "create index " + tableName.toLowerCase() + '_' + columnName
447 + " on " + tableName + " (" + columnName + ");";
448 }
449
Marc Blanka290f502009-06-15 14:40:06 -0700450 static void createMessageTable(SQLiteDatabase db) {
Marc Blankb6493a02009-07-05 12:54:49 -0700451 String messageColumns = MessageColumns.DISPLAY_NAME + " text, "
452 + MessageColumns.TIMESTAMP + " integer, "
453 + MessageColumns.SUBJECT + " text, "
Marc Blankb6493a02009-07-05 12:54:49 -0700454 + MessageColumns.FLAG_READ + " integer, "
455 + MessageColumns.FLAG_LOADED + " integer, "
456 + MessageColumns.FLAG_FAVORITE + " integer, "
457 + MessageColumns.FLAG_ATTACHMENT + " integer, "
458 + MessageColumns.FLAGS + " integer, "
Marc Blankb6493a02009-07-05 12:54:49 -0700459 + MessageColumns.CLIENT_ID + " integer, "
460 + MessageColumns.MESSAGE_ID + " text, "
Marc Blankb6493a02009-07-05 12:54:49 -0700461 + MessageColumns.MAILBOX_KEY + " integer, "
462 + MessageColumns.ACCOUNT_KEY + " integer, "
Marc Blankb6493a02009-07-05 12:54:49 -0700463 + MessageColumns.FROM_LIST + " text, "
464 + MessageColumns.TO_LIST + " text, "
465 + MessageColumns.CC_LIST + " text, "
466 + MessageColumns.BCC_LIST + " text, "
Marc Blank4006e5f2010-02-15 16:01:38 -0800467 + MessageColumns.REPLY_TO_LIST + " text, "
Marc Blanke7b9e4a2010-09-01 19:06:15 -0700468 + MessageColumns.MEETING_INFO + " text, "
Marc Blank9a013532011-06-23 10:52:21 -0700469 + MessageColumns.SNIPPET + " text, "
470 + MessageColumns.PROTOCOL_SEARCH_INFO + " text"
Marc Blankb6493a02009-07-05 12:54:49 -0700471 + ");";
472
473 // This String and the following String MUST have the same columns, except for the type
474 // of those columns!
475 String createString = " (" + EmailContent.RECORD_ID + " integer primary key autoincrement, "
Andrew Stadler0d008892009-09-22 18:31:10 -0700476 + SyncColumns.SERVER_ID + " text, "
477 + SyncColumns.SERVER_TIMESTAMP + " integer, "
Marc Blankb6493a02009-07-05 12:54:49 -0700478 + messageColumns;
479
480 // For the updated and deleted tables, the id is assigned, but we do want to keep track
481 // of the ORDER of updates using an autoincrement primary key. We use the DATA column
482 // at this point; it has no other function
Marc Blank6b158f72009-07-14 13:08:19 -0700483 String altCreateString = " (" + EmailContent.RECORD_ID + " integer unique, "
Andrew Stadler0d008892009-09-22 18:31:10 -0700484 + SyncColumns.SERVER_ID + " text, "
485 + SyncColumns.SERVER_TIMESTAMP + " integer, "
Marc Blankb6493a02009-07-05 12:54:49 -0700486 + messageColumns;
Marc Blankf3743042009-06-27 12:14:14 -0700487
488 // The three tables have the same schema
Marc Blankb6493a02009-07-05 12:54:49 -0700489 db.execSQL("create table " + Message.TABLE_NAME + createString);
490 db.execSQL("create table " + Message.UPDATED_TABLE_NAME + altCreateString);
491 db.execSQL("create table " + Message.DELETED_TABLE_NAME + altCreateString);
Marc Blankf3743042009-06-27 12:14:14 -0700492
Mihai Preda9627d012009-08-12 12:51:26 +0200493 String indexColumns[] = {
494 MessageColumns.TIMESTAMP,
495 MessageColumns.FLAG_READ,
496 MessageColumns.FLAG_LOADED,
497 MessageColumns.MAILBOX_KEY,
498 SyncColumns.SERVER_ID
499 };
500
501 for (String columnName : indexColumns) {
502 db.execSQL(createIndex(Message.TABLE_NAME, columnName));
503 }
Marc Blank2c67f1f2009-06-16 12:03:45 -0700504
Marc Blankf3743042009-06-27 12:14:14 -0700505 // Deleting a Message deletes all associated Attachments
Marc Blank2c67f1f2009-06-16 12:03:45 -0700506 // Deleting the associated Body cannot be done in a trigger, because the Body is stored
507 // in a separate database, and trigger cannot operate on attached databases.
Marc Blank758a5322009-07-30 11:41:31 -0700508 db.execSQL("create trigger message_delete before delete on " + Message.TABLE_NAME +
Marc Blank2c67f1f2009-06-16 12:03:45 -0700509 " begin delete from " + Attachment.TABLE_NAME +
Marc Blank758a5322009-07-30 11:41:31 -0700510 " where " + AttachmentColumns.MESSAGE_KEY + "=old." + EmailContent.RECORD_ID +
511 "; end");
512
513 // Add triggers to keep unread count accurate per mailbox
514
Makoto Onuki574854b2010-07-30 13:53:59 -0700515 // NOTE: SQLite's before triggers are not safe when recursive triggers are involved.
516 // Use caution when changing them.
517
Marc Blank758a5322009-07-30 11:41:31 -0700518 // Insert a message; if flagRead is zero, add to the unread count of the message's mailbox
519 db.execSQL("create trigger unread_message_insert before insert on " + Message.TABLE_NAME +
520 " when NEW." + MessageColumns.FLAG_READ + "=0" +
521 " begin update " + Mailbox.TABLE_NAME + " set " + MailboxColumns.UNREAD_COUNT +
522 '=' + MailboxColumns.UNREAD_COUNT + "+1" +
523 " where " + EmailContent.RECORD_ID + "=NEW." + MessageColumns.MAILBOX_KEY +
524 "; end");
525
526 // Delete a message; if flagRead is zero, decrement the unread count of the msg's mailbox
527 db.execSQL("create trigger unread_message_delete before delete on " + Message.TABLE_NAME +
528 " when OLD." + MessageColumns.FLAG_READ + "=0" +
529 " begin update " + Mailbox.TABLE_NAME + " set " + MailboxColumns.UNREAD_COUNT +
530 '=' + MailboxColumns.UNREAD_COUNT + "-1" +
531 " where " + EmailContent.RECORD_ID + "=OLD." + MessageColumns.MAILBOX_KEY +
532 "; end");
533
534 // Change a message's mailbox
535 db.execSQL("create trigger unread_message_move before update of " +
536 MessageColumns.MAILBOX_KEY + " on " + Message.TABLE_NAME +
537 " when OLD." + MessageColumns.FLAG_READ + "=0" +
538 " begin update " + Mailbox.TABLE_NAME + " set " + MailboxColumns.UNREAD_COUNT +
539 '=' + MailboxColumns.UNREAD_COUNT + "-1" +
540 " where " + EmailContent.RECORD_ID + "=OLD." + MessageColumns.MAILBOX_KEY +
541 "; update " + Mailbox.TABLE_NAME + " set " + MailboxColumns.UNREAD_COUNT +
542 '=' + MailboxColumns.UNREAD_COUNT + "+1" +
543 " where " + EmailContent.RECORD_ID + "=NEW." + MessageColumns.MAILBOX_KEY +
544 "; end");
545
546 // Change a message's read state
Vasu Norib720ed32009-12-03 18:32:58 -0800547 db.execSQL("create trigger unread_message_read before update of " +
Marc Blank758a5322009-07-30 11:41:31 -0700548 MessageColumns.FLAG_READ + " on " + Message.TABLE_NAME +
549 " when OLD." + MessageColumns.FLAG_READ + "!=NEW." + MessageColumns.FLAG_READ +
550 " begin update " + Mailbox.TABLE_NAME + " set " + MailboxColumns.UNREAD_COUNT +
551 '=' + MailboxColumns.UNREAD_COUNT + "+ case OLD." + MessageColumns.FLAG_READ +
552 " when 0 then -1 else 1 end" +
553 " where " + EmailContent.RECORD_ID + "=OLD." + MessageColumns.MAILBOX_KEY +
554 "; end");
Makoto Onuki574854b2010-07-30 13:53:59 -0700555
556 // Add triggers to update message count per mailbox
557
558 // Insert a message.
559 db.execSQL("create trigger message_count_message_insert after insert on " +
560 Message.TABLE_NAME +
561 " begin update " + Mailbox.TABLE_NAME + " set " + MailboxColumns.MESSAGE_COUNT +
562 '=' + MailboxColumns.MESSAGE_COUNT + "+1" +
563 " where " + EmailContent.RECORD_ID + "=NEW." + MessageColumns.MAILBOX_KEY +
564 "; end");
565
566 // Delete a message; if flagRead is zero, decrement the unread count of the msg's mailbox
567 db.execSQL("create trigger message_count_message_delete after delete on " +
568 Message.TABLE_NAME +
569 " begin update " + Mailbox.TABLE_NAME + " set " + MailboxColumns.MESSAGE_COUNT +
570 '=' + MailboxColumns.MESSAGE_COUNT + "-1" +
571 " where " + EmailContent.RECORD_ID + "=OLD." + MessageColumns.MAILBOX_KEY +
572 "; end");
573
574 // Change a message's mailbox
575 db.execSQL("create trigger message_count_message_move after update of " +
576 MessageColumns.MAILBOX_KEY + " on " + Message.TABLE_NAME +
577 " begin update " + Mailbox.TABLE_NAME + " set " + MailboxColumns.MESSAGE_COUNT +
578 '=' + MailboxColumns.MESSAGE_COUNT + "-1" +
579 " where " + EmailContent.RECORD_ID + "=OLD." + MessageColumns.MAILBOX_KEY +
580 "; update " + Mailbox.TABLE_NAME + " set " + MailboxColumns.MESSAGE_COUNT +
581 '=' + MailboxColumns.MESSAGE_COUNT + "+1" +
582 " where " + EmailContent.RECORD_ID + "=NEW." + MessageColumns.MAILBOX_KEY +
583 "; end");
584 }
Marc Blanka290f502009-06-15 14:40:06 -0700585
Marc Blanke7e1ca42009-09-15 19:27:05 -0700586 static void resetMessageTable(SQLiteDatabase db, int oldVersion, int newVersion) {
Andrew Stadlerddc871d2009-06-29 23:08:40 -0700587 try {
588 db.execSQL("drop table " + Message.TABLE_NAME);
589 db.execSQL("drop table " + Message.UPDATED_TABLE_NAME);
590 db.execSQL("drop table " + Message.DELETED_TABLE_NAME);
591 } catch (SQLException e) {
592 }
Marc Blanka290f502009-06-15 14:40:06 -0700593 createMessageTable(db);
594 }
595
Todd Kennedybf30f942011-05-06 11:31:10 -0700596 @SuppressWarnings("deprecation")
Marc Blanka290f502009-06-15 14:40:06 -0700597 static void createAccountTable(SQLiteDatabase db) {
Marc Blank758a5322009-07-30 11:41:31 -0700598 String s = " (" + EmailContent.RECORD_ID + " integer primary key autoincrement, "
Marc Blanka290f502009-06-15 14:40:06 -0700599 + AccountColumns.DISPLAY_NAME + " text, "
600 + AccountColumns.EMAIL_ADDRESS + " text, "
601 + AccountColumns.SYNC_KEY + " text, "
602 + AccountColumns.SYNC_LOOKBACK + " integer, "
Andrew Stadler9e2c6bd2009-07-22 15:13:30 -0700603 + AccountColumns.SYNC_INTERVAL + " text, "
Marc Blanka290f502009-06-15 14:40:06 -0700604 + AccountColumns.HOST_AUTH_KEY_RECV + " integer, "
605 + AccountColumns.HOST_AUTH_KEY_SEND + " integer, "
606 + AccountColumns.FLAGS + " integer, "
607 + AccountColumns.IS_DEFAULT + " integer, "
608 + AccountColumns.COMPATIBILITY_UUID + " text, "
609 + AccountColumns.SENDER_NAME + " text, "
Marc Blank53093872009-07-17 16:29:35 -0700610 + AccountColumns.RINGTONE_URI + " text, "
Andrew Stadler4a8c70c2009-08-18 12:14:15 -0700611 + AccountColumns.PROTOCOL_VERSION + " text, "
Andrew Stadlerfc8d9432010-01-21 11:48:02 -0800612 + AccountColumns.NEW_MESSAGE_COUNT + " integer, "
Andrew Stadler345fb8b2010-01-26 17:24:15 -0800613 + AccountColumns.SECURITY_FLAGS + " integer, "
614 + AccountColumns.SECURITY_SYNC_KEY + " text, "
Marc Blankaeee10e2011-04-27 17:12:06 -0700615 + AccountColumns.SIGNATURE + " text, "
Marc Blankaca94262011-07-19 18:19:59 -0700616 + AccountColumns.POLICY_KEY + " integer, "
617 + AccountColumns.NOTIFIED_MESSAGE_ID + " integer, "
618 + AccountColumns.NOTIFIED_MESSAGE_COUNT + " integer"
Marc Blanka290f502009-06-15 14:40:06 -0700619 + ");";
620 db.execSQL("create table " + Account.TABLE_NAME + s);
Marc Blank2c67f1f2009-06-16 12:03:45 -0700621 // Deleting an account deletes associated Mailboxes and HostAuth's
Marc Blankaeee10e2011-04-27 17:12:06 -0700622 db.execSQL(TRIGGER_ACCOUNT_DELETE);
Marc Blankf3743042009-06-27 12:14:14 -0700623 }
Marc Blanka290f502009-06-15 14:40:06 -0700624
Marc Blanke7e1ca42009-09-15 19:27:05 -0700625 static void resetAccountTable(SQLiteDatabase db, int oldVersion, int newVersion) {
Marc Blanka290f502009-06-15 14:40:06 -0700626 try {
627 db.execSQL("drop table " + Account.TABLE_NAME);
628 } catch (SQLException e) {
629 }
630 createAccountTable(db);
631 }
Marc Blankf3743042009-06-27 12:14:14 -0700632
Marc Blankaeee10e2011-04-27 17:12:06 -0700633 static void createPolicyTable(SQLiteDatabase db) {
634 String s = " (" + EmailContent.RECORD_ID + " integer primary key autoincrement, "
635 + PolicyColumns.PASSWORD_MODE + " integer, "
636 + PolicyColumns.PASSWORD_MIN_LENGTH + " integer, "
637 + PolicyColumns.PASSWORD_EXPIRATION_DAYS + " integer, "
638 + PolicyColumns.PASSWORD_HISTORY + " integer, "
639 + PolicyColumns.PASSWORD_COMPLEX_CHARS + " integer, "
640 + PolicyColumns.PASSWORD_MAX_FAILS + " integer, "
641 + PolicyColumns.MAX_SCREEN_LOCK_TIME + " integer, "
642 + PolicyColumns.REQUIRE_REMOTE_WIPE + " integer, "
643 + PolicyColumns.REQUIRE_ENCRYPTION + " integer, "
Marc Blankf91a03f2011-05-05 10:34:29 -0700644 + PolicyColumns.REQUIRE_ENCRYPTION_EXTERNAL + " integer, "
645 + PolicyColumns.REQUIRE_MANUAL_SYNC_WHEN_ROAMING + " integer, "
646 + PolicyColumns.DONT_ALLOW_CAMERA + " integer, "
647 + PolicyColumns.DONT_ALLOW_ATTACHMENTS + " integer, "
648 + PolicyColumns.DONT_ALLOW_HTML + " integer, "
649 + PolicyColumns.MAX_ATTACHMENT_SIZE + " integer, "
650 + PolicyColumns.MAX_TEXT_TRUNCATION_SIZE + " integer, "
651 + PolicyColumns.MAX_HTML_TRUNCATION_SIZE + " integer, "
652 + PolicyColumns.MAX_EMAIL_LOOKBACK + " integer, "
653 + PolicyColumns.MAX_CALENDAR_LOOKBACK + " integer, "
654 + PolicyColumns.PASSWORD_RECOVERY_ENABLED + " integer"
Marc Blankaeee10e2011-04-27 17:12:06 -0700655 + ");";
656 db.execSQL("create table " + Policy.TABLE_NAME + s);
657 }
658
Marc Blanka290f502009-06-15 14:40:06 -0700659 static void createHostAuthTable(SQLiteDatabase db) {
Marc Blank758a5322009-07-30 11:41:31 -0700660 String s = " (" + EmailContent.RECORD_ID + " integer primary key autoincrement, "
Marc Blanka290f502009-06-15 14:40:06 -0700661 + HostAuthColumns.PROTOCOL + " text, "
662 + HostAuthColumns.ADDRESS + " text, "
663 + HostAuthColumns.PORT + " integer, "
664 + HostAuthColumns.FLAGS + " integer, "
665 + HostAuthColumns.LOGIN + " text, "
666 + HostAuthColumns.PASSWORD + " text, "
667 + HostAuthColumns.DOMAIN + " text, "
Ben Komalo313586c2011-06-07 11:39:16 -0700668 + HostAuthColumns.ACCOUNT_KEY + " integer,"
669 + HostAuthColumns.CLIENT_CERT_ALIAS + " text"
Marc Blanka290f502009-06-15 14:40:06 -0700670 + ");";
671 db.execSQL("create table " + HostAuth.TABLE_NAME + s);
672 }
673
Marc Blanke7e1ca42009-09-15 19:27:05 -0700674 static void resetHostAuthTable(SQLiteDatabase db, int oldVersion, int newVersion) {
Marc Blanka290f502009-06-15 14:40:06 -0700675 try {
676 db.execSQL("drop table " + HostAuth.TABLE_NAME);
677 } catch (SQLException e) {
678 }
679 createHostAuthTable(db);
680 }
681
Marc Blankf3743042009-06-27 12:14:14 -0700682 static void createMailboxTable(SQLiteDatabase db) {
Marc Blank758a5322009-07-30 11:41:31 -0700683 String s = " (" + EmailContent.RECORD_ID + " integer primary key autoincrement, "
Marc Blanka290f502009-06-15 14:40:06 -0700684 + MailboxColumns.DISPLAY_NAME + " text, "
Marc Blank9be10e62009-09-03 16:42:20 -0700685 + MailboxColumns.SERVER_ID + " text, "
Marc Blanka290f502009-06-15 14:40:06 -0700686 + MailboxColumns.PARENT_SERVER_ID + " text, "
Andy Stadler3a585092011-03-01 10:45:50 -0800687 + MailboxColumns.PARENT_KEY + " integer, "
Marc Blanka290f502009-06-15 14:40:06 -0700688 + MailboxColumns.ACCOUNT_KEY + " integer, "
689 + MailboxColumns.TYPE + " integer, "
690 + MailboxColumns.DELIMITER + " integer, "
691 + MailboxColumns.SYNC_KEY + " text, "
692 + MailboxColumns.SYNC_LOOKBACK + " integer, "
Andrew Stadler9e2c6bd2009-07-22 15:13:30 -0700693 + MailboxColumns.SYNC_INTERVAL + " integer, "
Marc Blanka290f502009-06-15 14:40:06 -0700694 + MailboxColumns.SYNC_TIME + " integer, "
695 + MailboxColumns.UNREAD_COUNT + " integer, "
696 + MailboxColumns.FLAG_VISIBLE + " integer, "
697 + MailboxColumns.FLAGS + " integer, "
Marc Blank9b598922009-08-05 08:41:16 -0700698 + MailboxColumns.VISIBLE_LIMIT + " integer, "
Makoto Onuki574854b2010-07-30 13:53:59 -0700699 + MailboxColumns.SYNC_STATUS + " text, "
Todd Kennedya9ac20b2011-05-06 13:37:02 -0700700 + MailboxColumns.MESSAGE_COUNT + " integer not null default 0, "
Todd Kennedy2e112b22011-06-03 16:47:39 -0700701 + MailboxColumns.LAST_SEEN_MESSAGE_KEY + " integer, "
702 + MailboxColumns.LAST_TOUCHED_TIME + " integer default 0"
Marc Blankb6493a02009-07-05 12:54:49 -0700703 + ");";
Marc Blankf3743042009-06-27 12:14:14 -0700704 db.execSQL("create table " + Mailbox.TABLE_NAME + s);
Marc Blank758a5322009-07-30 11:41:31 -0700705 db.execSQL("create index mailbox_" + MailboxColumns.SERVER_ID
Marc Blanka290f502009-06-15 14:40:06 -0700706 + " on " + Mailbox.TABLE_NAME + " (" + MailboxColumns.SERVER_ID + ")");
Marc Blank758a5322009-07-30 11:41:31 -0700707 db.execSQL("create index mailbox_" + MailboxColumns.ACCOUNT_KEY
Marc Blanka290f502009-06-15 14:40:06 -0700708 + " on " + Mailbox.TABLE_NAME + " (" + MailboxColumns.ACCOUNT_KEY + ")");
Marc Blankef832992009-10-13 16:25:00 -0700709 // Deleting a Mailbox deletes associated Messages in all three tables
710 db.execSQL(TRIGGER_MAILBOX_DELETE);
Marc Blanka290f502009-06-15 14:40:06 -0700711 }
712
Marc Blanke7e1ca42009-09-15 19:27:05 -0700713 static void resetMailboxTable(SQLiteDatabase db, int oldVersion, int newVersion) {
Marc Blanka290f502009-06-15 14:40:06 -0700714 try {
715 db.execSQL("drop table " + Mailbox.TABLE_NAME);
716 } catch (SQLException e) {
717 }
718 createMailboxTable(db);
719 }
Marc Blankf3743042009-06-27 12:14:14 -0700720
Marc Blanka290f502009-06-15 14:40:06 -0700721 static void createAttachmentTable(SQLiteDatabase db) {
Marc Blank758a5322009-07-30 11:41:31 -0700722 String s = " (" + EmailContent.RECORD_ID + " integer primary key autoincrement, "
Marc Blanka290f502009-06-15 14:40:06 -0700723 + AttachmentColumns.FILENAME + " text, "
724 + AttachmentColumns.MIME_TYPE + " text, "
725 + AttachmentColumns.SIZE + " integer, "
726 + AttachmentColumns.CONTENT_ID + " text, "
727 + AttachmentColumns.CONTENT_URI + " text, "
728 + AttachmentColumns.MESSAGE_KEY + " integer, "
729 + AttachmentColumns.LOCATION + " text, "
Andrew Stadler3aaba9e2010-02-22 12:57:33 -0800730 + AttachmentColumns.ENCODING + " text, "
731 + AttachmentColumns.CONTENT + " text, "
Makoto Onuki20225d52010-03-12 13:30:26 -0800732 + AttachmentColumns.FLAGS + " integer, "
Marc Blank75a873b2010-12-08 17:11:04 -0800733 + AttachmentColumns.CONTENT_BYTES + " blob, "
734 + AttachmentColumns.ACCOUNT_KEY + " integer"
Marc Blanka290f502009-06-15 14:40:06 -0700735 + ");";
736 db.execSQL("create table " + Attachment.TABLE_NAME + s);
Mihai Preda9627d012009-08-12 12:51:26 +0200737 db.execSQL(createIndex(Attachment.TABLE_NAME, AttachmentColumns.MESSAGE_KEY));
Marc Blanka290f502009-06-15 14:40:06 -0700738 }
739
Marc Blanke7e1ca42009-09-15 19:27:05 -0700740 static void resetAttachmentTable(SQLiteDatabase db, int oldVersion, int newVersion) {
Marc Blanka290f502009-06-15 14:40:06 -0700741 try {
742 db.execSQL("drop table " + Attachment.TABLE_NAME);
743 } catch (SQLException e) {
744 }
745 createAttachmentTable(db);
746 }
747
Jorge Lugo5a3888f2011-06-01 10:09:26 -0700748 static void createQuickResponseTable(SQLiteDatabase db) {
749 String s = " (" + EmailContent.RECORD_ID + " integer primary key autoincrement, "
750 + QuickResponseColumns.TEXT + " text, "
751 + QuickResponseColumns.ACCOUNT_KEY + " integer"
752 + ");";
753 db.execSQL("create table " + QuickResponse.TABLE_NAME + s);
754 }
755
Marc Blanka290f502009-06-15 14:40:06 -0700756 static void createBodyTable(SQLiteDatabase db) {
Marc Blank758a5322009-07-30 11:41:31 -0700757 String s = " (" + EmailContent.RECORD_ID + " integer primary key autoincrement, "
Marc Blanka290f502009-06-15 14:40:06 -0700758 + BodyColumns.MESSAGE_KEY + " integer, "
759 + BodyColumns.HTML_CONTENT + " text, "
Andrew Stadler936babc2009-09-01 23:19:12 -0700760 + BodyColumns.TEXT_CONTENT + " text, "
761 + BodyColumns.HTML_REPLY + " text, "
Marc Blanke2569832009-09-07 16:03:02 -0700762 + BodyColumns.TEXT_REPLY + " text, "
Marc Blank5fc57ec2009-09-22 18:38:28 -0700763 + BodyColumns.SOURCE_MESSAGE_KEY + " text, "
764 + BodyColumns.INTRO_TEXT + " text"
Marc Blanka290f502009-06-15 14:40:06 -0700765 + ");";
766 db.execSQL("create table " + Body.TABLE_NAME + s);
Mihai Preda9627d012009-08-12 12:51:26 +0200767 db.execSQL(createIndex(Body.TABLE_NAME, BodyColumns.MESSAGE_KEY));
Marc Blanka290f502009-06-15 14:40:06 -0700768 }
769
770 static void upgradeBodyTable(SQLiteDatabase db, int oldVersion, int newVersion) {
Marc Blank657de3b2009-09-18 20:44:26 -0700771 if (oldVersion < 5) {
Marc Blanke2569832009-09-07 16:03:02 -0700772 try {
773 db.execSQL("drop table " + Body.TABLE_NAME);
774 createBodyTable(db);
775 } catch (SQLException e) {
776 }
Marc Blank5fc57ec2009-09-22 18:38:28 -0700777 } else if (oldVersion == 5) {
778 try {
779 db.execSQL("alter table " + Body.TABLE_NAME
780 + " add " + BodyColumns.INTRO_TEXT + " text");
781 } catch (SQLException e) {
782 // Shouldn't be needed unless we're debugging and interrupt the process
783 Log.w(TAG, "Exception upgrading EmailProviderBody.db from v5 to v6", e);
784 }
785 oldVersion = 6;
Marc Blanke7e1ca42009-09-15 19:27:05 -0700786 }
Andrew Stadlerf3d5b202009-05-26 16:40:34 -0700787 }
788
Marc Blanka290f502009-06-15 14:40:06 -0700789 private SQLiteDatabase mDatabase;
790 private SQLiteDatabase mBodyDatabase;
Marc Blankf3743042009-06-27 12:14:14 -0700791
Marc Blank2bdf7ee2011-06-30 16:03:36 -0700792 /**
793 * Orphan record deletion utility. Generates a sqlite statement like:
794 * delete from <table> where <column> not in (select <foreignColumn> from <foreignTable>)
795 * @param db the EmailProvider database
796 * @param table the table whose orphans are to be removed
797 * @param column the column deletion will be based on
798 * @param foreignColumn the column in the foreign table whose absence will trigger the deletion
799 * @param foreignTable the foreign table
800 */
801 @VisibleForTesting
802 void deleteUnlinked(SQLiteDatabase db, String table, String column, String foreignColumn,
803 String foreignTable) {
804 int count = db.delete(table, column + " not in (select " + foreignColumn + " from " +
805 foreignTable + ")", null);
806 if (count > 0) {
807 Log.w(TAG, "Found " + count + " orphaned row(s) in " + table);
808 }
809 }
810
Makoto Onuki9dad9ad2011-06-20 18:10:10 -0700811 @VisibleForTesting
812 synchronized SQLiteDatabase getDatabase(Context context) {
Marc Blank0e1595c2009-11-18 17:11:33 -0800813 // Always return the cached database, if we've got one
814 if (mDatabase != null) {
Marc Blanka290f502009-06-15 14:40:06 -0700815 return mDatabase;
816 }
Marc Blank0e1595c2009-11-18 17:11:33 -0800817
818 // Whenever we create or re-cache the databases, make sure that we haven't lost one
819 // to corruption
820 checkDatabases();
821
Marc Blanka290f502009-06-15 14:40:06 -0700822 DatabaseHelper helper = new DatabaseHelper(context, DATABASE_NAME);
823 mDatabase = helper.getWritableDatabase();
Makoto Onuki369905c2011-06-20 19:57:56 -0700824 mDatabase.setLockingEnabled(true);
825 BodyDatabaseHelper bodyHelper = new BodyDatabaseHelper(context, BODY_DATABASE_NAME);
826 mBodyDatabase = bodyHelper.getWritableDatabase();
827 if (mBodyDatabase != null) {
828 mBodyDatabase.setLockingEnabled(true);
829 String bodyFileName = mBodyDatabase.getPath();
830 mDatabase.execSQL("attach \"" + bodyFileName + "\" as BodyDatabase");
Marc Blanka290f502009-06-15 14:40:06 -0700831 }
Marc Blankef832992009-10-13 16:25:00 -0700832
Makoto Onuki369905c2011-06-20 19:57:56 -0700833 // Restore accounts if the database is corrupted...
834 restoreIfNeeded(context, mDatabase);
835
Marc Blank2bdf7ee2011-06-30 16:03:36 -0700836 if (Email.DEBUG) {
837 Log.d(TAG, "Deleting orphans...");
838 }
Marc Blankef832992009-10-13 16:25:00 -0700839 // Check for any orphaned Messages in the updated/deleted tables
Marc Blank2bdf7ee2011-06-30 16:03:36 -0700840 deleteMessageOrphans(mDatabase, Message.UPDATED_TABLE_NAME);
841 deleteMessageOrphans(mDatabase, Message.DELETED_TABLE_NAME);
842 // Delete orphaned mailboxes/messages/policies (account no longer exists)
843 deleteUnlinked(mDatabase, Mailbox.TABLE_NAME, MailboxColumns.ACCOUNT_KEY, AccountColumns.ID,
844 Account.TABLE_NAME);
845 deleteUnlinked(mDatabase, Message.TABLE_NAME, MessageColumns.ACCOUNT_KEY, AccountColumns.ID,
846 Account.TABLE_NAME);
847 deleteUnlinked(mDatabase, Policy.TABLE_NAME, PolicyColumns.ID, AccountColumns.POLICY_KEY,
848 Account.TABLE_NAME);
849
Marc Blank6e418aa2011-06-18 18:03:11 -0700850 if (Email.DEBUG) {
851 Log.d(TAG, "EmailProvider pre-caching...");
852 }
853 preCacheData();
854 if (Email.DEBUG) {
Marc Blank2bdf7ee2011-06-30 16:03:36 -0700855 Log.d(TAG, "EmailProvider ready.");
Marc Blank6e418aa2011-06-18 18:03:11 -0700856 }
Marc Blanka290f502009-06-15 14:40:06 -0700857 return mDatabase;
858 }
859
Marc Blank6e418aa2011-06-18 18:03:11 -0700860 /**
861 * Pre-cache all of the items in a given table meeting the selection criteria
862 * @param tableUri the table uri
863 * @param baseProjection the base projection of that table
864 * @param selection the selection criteria
865 */
866 private void preCacheTable(Uri tableUri, String[] baseProjection, String selection) {
867 Cursor c = query(tableUri, EmailContent.ID_PROJECTION, selection, null, null);
868 try {
869 while (c.moveToNext()) {
870 long id = c.getLong(EmailContent.ID_PROJECTION_COLUMN);
871 Cursor cachedCursor = query(ContentUris.withAppendedId(
872 tableUri, id), baseProjection, null, null, null);
873 if (cachedCursor != null) {
874 // For accounts, create a mailbox type map entry (if necessary)
875 if (tableUri == Account.CONTENT_URI) {
876 getOrCreateAccountMailboxTypeMap(id);
877 }
878 cachedCursor.close();
879 }
880 }
881 } finally {
882 c.close();
883 }
884 }
885
Marc Blank5d7ff852011-06-27 20:11:24 -0700886 private final HashMap<Long, HashMap<Integer, Long>> mMailboxTypeMap =
Marc Blank6e418aa2011-06-18 18:03:11 -0700887 new HashMap<Long, HashMap<Integer, Long>>();
888
Marc Blank5d7ff852011-06-27 20:11:24 -0700889 private HashMap<Integer, Long> getOrCreateAccountMailboxTypeMap(long accountId) {
890 synchronized(mMailboxTypeMap) {
891 HashMap<Integer, Long> accountMailboxTypeMap = mMailboxTypeMap.get(accountId);
892 if (accountMailboxTypeMap == null) {
893 accountMailboxTypeMap = new HashMap<Integer, Long>();
894 mMailboxTypeMap.put(accountId, accountMailboxTypeMap);
895 }
896 return accountMailboxTypeMap;
Marc Blank6e418aa2011-06-18 18:03:11 -0700897 }
Marc Blank6e418aa2011-06-18 18:03:11 -0700898 }
899
Marc Blank5d7ff852011-06-27 20:11:24 -0700900 private void addToMailboxTypeMap(Cursor c) {
Marc Blank6e418aa2011-06-18 18:03:11 -0700901 long accountId = c.getLong(Mailbox.CONTENT_ACCOUNT_KEY_COLUMN);
902 int type = c.getInt(Mailbox.CONTENT_TYPE_COLUMN);
Marc Blank5d7ff852011-06-27 20:11:24 -0700903 synchronized(mMailboxTypeMap) {
904 HashMap<Integer, Long> accountMailboxTypeMap =
905 getOrCreateAccountMailboxTypeMap(accountId);
906 accountMailboxTypeMap.put(type, c.getLong(Mailbox.CONTENT_ID_COLUMN));
907 }
908 }
909
910 private long getMailboxIdFromMailboxTypeMap(long accountId, int type) {
911 synchronized(mMailboxTypeMap) {
912 HashMap<Integer, Long> accountMap = mMailboxTypeMap.get(accountId);
Marc Blankc0a9fa92011-06-28 10:58:22 -0700913 Long mailboxId = null;
Marc Blank5d7ff852011-06-27 20:11:24 -0700914 if (accountMap != null) {
915 mailboxId = accountMap.get(type);
916 }
Marc Blankc0a9fa92011-06-28 10:58:22 -0700917 if (mailboxId == null) return Mailbox.NO_MAILBOX;
Marc Blank5d7ff852011-06-27 20:11:24 -0700918 return mailboxId;
919 }
Marc Blank6e418aa2011-06-18 18:03:11 -0700920 }
921
922 private void preCacheData() {
Marc Blank5d7ff852011-06-27 20:11:24 -0700923 synchronized(mMailboxTypeMap) {
924 mMailboxTypeMap.clear();
Marc Blank6e418aa2011-06-18 18:03:11 -0700925
Marc Blank5d7ff852011-06-27 20:11:24 -0700926 // Pre-cache accounts, host auth's, policies, and special mailboxes
927 preCacheTable(Account.CONTENT_URI, Account.CONTENT_PROJECTION, null);
928 preCacheTable(HostAuth.CONTENT_URI, HostAuth.CONTENT_PROJECTION, null);
929 preCacheTable(Policy.CONTENT_URI, Policy.CONTENT_PROJECTION, null);
930 preCacheTable(Mailbox.CONTENT_URI, Mailbox.CONTENT_PROJECTION,
931 MAILBOX_PRE_CACHE_SELECTION);
Marc Blank6e418aa2011-06-18 18:03:11 -0700932
Marc Blank5d7ff852011-06-27 20:11:24 -0700933 // Create a map from account,type to a mailbox
934 Map<String, Cursor> snapshot = mCacheMailbox.getSnapshot();
935 Collection<Cursor> values = snapshot.values();
936 if (values != null) {
937 for (Cursor c: values) {
938 if (c.moveToFirst()) {
939 addToMailboxTypeMap(c);
940 }
941 }
Marc Blank6e418aa2011-06-18 18:03:11 -0700942 }
943 }
944 }
945
Marc Blankef832992009-10-13 16:25:00 -0700946 /*package*/ static SQLiteDatabase getReadableDatabase(Context context) {
Makoto Onuki9dad9ad2011-06-20 18:10:10 -0700947 DatabaseHelper helper = new DatabaseHelper(context, DATABASE_NAME);
Marc Blankef832992009-10-13 16:25:00 -0700948 return helper.getReadableDatabase();
949 }
950
Makoto Onuki9dad9ad2011-06-20 18:10:10 -0700951 /**
952 * Restore user Account and HostAuth data from our backup database
953 */
954 public static void restoreIfNeeded(Context context, SQLiteDatabase mainDatabase) {
955 if (Email.DEBUG) {
956 Log.w(TAG, "restoreIfNeeded...");
957 }
958 // Check for legacy backup
959 String legacyBackup = Preferences.getLegacyBackupPreference(context);
960 // If there's a legacy backup, create a new-style backup and delete the legacy backup
961 // In the 1:1000000000 chance that the user gets an app update just as his database becomes
962 // corrupt, oh well...
963 if (!TextUtils.isEmpty(legacyBackup)) {
964 backupAccounts(context, mainDatabase);
965 Preferences.clearLegacyBackupPreference(context);
966 Log.w(TAG, "Created new EmailProvider backup database");
967 return;
968 }
969
970 // If we have accounts, we're done
971 Cursor c = mainDatabase.query(Account.TABLE_NAME, EmailContent.ID_PROJECTION, null, null,
972 null, null, null);
973 if (c.moveToFirst()) {
974 if (Email.DEBUG) {
975 Log.w(TAG, "restoreIfNeeded: Account exists.");
976 }
977 return; // At least one account exists.
978 }
979 restoreAccounts(context, mainDatabase);
980 }
981
Makoto Onuki6c36b4c2010-08-20 14:56:44 -0700982 /** {@inheritDoc} */
983 @Override
984 public void shutdown() {
985 if (mDatabase != null) {
986 mDatabase.close();
987 mDatabase = null;
988 }
989 if (mBodyDatabase != null) {
990 mBodyDatabase.close();
991 mBodyDatabase = null;
992 }
993 }
994
Marc Blank2bdf7ee2011-06-30 16:03:36 -0700995 /*package*/ static void deleteMessageOrphans(SQLiteDatabase database, String tableName) {
Marc Blankef832992009-10-13 16:25:00 -0700996 if (database != null) {
997 // We'll look at all of the items in the table; there won't be many typically
998 Cursor c = database.query(tableName, ORPHANS_PROJECTION, null, null, null, null, null);
999 // Usually, there will be nothing in these tables, so make a quick check
1000 try {
1001 if (c.getCount() == 0) return;
1002 ArrayList<Long> foundMailboxes = new ArrayList<Long>();
1003 ArrayList<Long> notFoundMailboxes = new ArrayList<Long>();
1004 ArrayList<Long> deleteList = new ArrayList<Long>();
1005 String[] bindArray = new String[1];
1006 while (c.moveToNext()) {
1007 // Get the mailbox key and see if we've already found this mailbox
1008 // If so, we're fine
1009 long mailboxId = c.getLong(ORPHANS_MAILBOX_KEY);
1010 // If we already know this mailbox doesn't exist, mark the message for deletion
1011 if (notFoundMailboxes.contains(mailboxId)) {
1012 deleteList.add(c.getLong(ORPHANS_ID));
1013 // If we don't know about this mailbox, we'll try to find it
1014 } else if (!foundMailboxes.contains(mailboxId)) {
1015 bindArray[0] = Long.toString(mailboxId);
1016 Cursor boxCursor = database.query(Mailbox.TABLE_NAME,
1017 Mailbox.ID_PROJECTION, WHERE_ID, bindArray, null, null, null);
1018 try {
1019 // If it exists, we'll add it to the "found" mailboxes
1020 if (boxCursor.moveToFirst()) {
1021 foundMailboxes.add(mailboxId);
1022 // Otherwise, we'll add to "not found" and mark the message for deletion
1023 } else {
1024 notFoundMailboxes.add(mailboxId);
1025 deleteList.add(c.getLong(ORPHANS_ID));
1026 }
1027 } finally {
1028 boxCursor.close();
1029 }
1030 }
1031 }
1032 // Now, delete the orphan messages
1033 for (long messageId: deleteList) {
1034 bindArray[0] = Long.toString(messageId);
1035 database.delete(tableName, WHERE_ID, bindArray);
1036 }
1037 } finally {
1038 c.close();
1039 }
1040 }
1041 }
1042
Marc Blanka290f502009-06-15 14:40:06 -07001043 private class BodyDatabaseHelper extends SQLiteOpenHelper {
1044 BodyDatabaseHelper(Context context, String name) {
Andrew Stadler0d008892009-09-22 18:31:10 -07001045 super(context, name, null, BODY_DATABASE_VERSION);
Marc Blanka290f502009-06-15 14:40:06 -07001046 }
1047
1048 @Override
1049 public void onCreate(SQLiteDatabase db) {
Marc Blank0e1595c2009-11-18 17:11:33 -08001050 Log.d(TAG, "Creating EmailProviderBody database");
Marc Blanka290f502009-06-15 14:40:06 -07001051 createBodyTable(db);
1052 }
1053
1054 @Override
1055 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
1056 upgradeBodyTable(db, oldVersion, newVersion);
1057 }
1058
1059 @Override
1060 public void onOpen(SQLiteDatabase db) {
1061 }
1062 }
Marc Blankf3743042009-06-27 12:14:14 -07001063
Makoto Onuki9dad9ad2011-06-20 18:10:10 -07001064 private static class DatabaseHelper extends SQLiteOpenHelper {
Marc Blanke7e1ca42009-09-15 19:27:05 -07001065 Context mContext;
1066
Andrew Stadlerf3d5b202009-05-26 16:40:34 -07001067 DatabaseHelper(Context context, String name) {
Andrew Stadler0d008892009-09-22 18:31:10 -07001068 super(context, name, null, DATABASE_VERSION);
Marc Blanke7e1ca42009-09-15 19:27:05 -07001069 mContext = context;
Andrew Stadlerf3d5b202009-05-26 16:40:34 -07001070 }
1071
1072 @Override
1073 public void onCreate(SQLiteDatabase db) {
Marc Blank0e1595c2009-11-18 17:11:33 -08001074 Log.d(TAG, "Creating EmailProvider database");
Andrew Stadlerf3d5b202009-05-26 16:40:34 -07001075 // Create all tables here; each class has its own method
Marc Blanka290f502009-06-15 14:40:06 -07001076 createMessageTable(db);
1077 createAttachmentTable(db);
1078 createMailboxTable(db);
1079 createHostAuthTable(db);
1080 createAccountTable(db);
Marc Blankaeee10e2011-04-27 17:12:06 -07001081 createPolicyTable(db);
Jorge Lugo5a3888f2011-06-01 10:09:26 -07001082 createQuickResponseTable(db);
Marc Blankf3743042009-06-27 12:14:14 -07001083 }
Andrew Stadlerf3d5b202009-05-26 16:40:34 -07001084
1085 @Override
Todd Kennedybf30f942011-05-06 11:31:10 -07001086 @SuppressWarnings("deprecation")
Andrew Stadlerf3d5b202009-05-26 16:40:34 -07001087 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
Marc Blank657de3b2009-09-18 20:44:26 -07001088 // For versions prior to 5, delete all data
1089 // Versions >= 5 require that data be preserved!
1090 if (oldVersion < 5) {
Makoto Onukifaed6172010-02-01 15:34:03 -08001091 android.accounts.Account[] accounts = AccountManager.get(mContext)
Marc Blank3a5c1fb2011-02-12 18:56:09 -08001092 .getAccountsByType(AccountManagerTypes.TYPE_EXCHANGE);
Marc Blanke7e1ca42009-09-15 19:27:05 -07001093 for (android.accounts.Account account: accounts) {
1094 AccountManager.get(mContext).removeAccount(account, null, null);
1095 }
1096 resetMessageTable(db, oldVersion, newVersion);
1097 resetAttachmentTable(db, oldVersion, newVersion);
1098 resetMailboxTable(db, oldVersion, newVersion);
1099 resetHostAuthTable(db, oldVersion, newVersion);
1100 resetAccountTable(db, oldVersion, newVersion);
Andrew Stadler0d008892009-09-22 18:31:10 -07001101 return;
1102 }
1103 if (oldVersion == 5) {
1104 // Message Tables: Add SyncColumns.SERVER_TIMESTAMP
Marc Blank5fc57ec2009-09-22 18:38:28 -07001105 try {
1106 db.execSQL("alter table " + Message.TABLE_NAME
1107 + " add column " + SyncColumns.SERVER_TIMESTAMP + " integer" + ";");
1108 db.execSQL("alter table " + Message.UPDATED_TABLE_NAME
1109 + " add column " + SyncColumns.SERVER_TIMESTAMP + " integer" + ";");
1110 db.execSQL("alter table " + Message.DELETED_TABLE_NAME
1111 + " add column " + SyncColumns.SERVER_TIMESTAMP + " integer" + ";");
1112 } catch (SQLException e) {
1113 // Shouldn't be needed unless we're debugging and interrupt the process
1114 Log.w(TAG, "Exception upgrading EmailProvider.db from v5 to v6", e);
1115 }
Andrew Stadler0d008892009-09-22 18:31:10 -07001116 oldVersion = 6;
Marc Blanke7e1ca42009-09-15 19:27:05 -07001117 }
Marc Blankef832992009-10-13 16:25:00 -07001118 if (oldVersion == 6) {
1119 // Use the newer mailbox_delete trigger
Marc Blank36796362009-10-19 22:15:45 -07001120 db.execSQL("drop trigger mailbox_delete;");
Marc Blankef832992009-10-13 16:25:00 -07001121 db.execSQL(TRIGGER_MAILBOX_DELETE);
1122 oldVersion = 7;
1123 }
Andrew Stadlerfc8d9432010-01-21 11:48:02 -08001124 if (oldVersion == 7) {
1125 // add the security (provisioning) column
1126 try {
1127 db.execSQL("alter table " + Account.TABLE_NAME
1128 + " add column " + AccountColumns.SECURITY_FLAGS + " integer" + ";");
1129 } catch (SQLException e) {
1130 // Shouldn't be needed unless we're debugging and interrupt the process
1131 Log.w(TAG, "Exception upgrading EmailProvider.db from 7 to 8 " + e);
1132 }
1133 oldVersion = 8;
1134 }
Andrew Stadler345fb8b2010-01-26 17:24:15 -08001135 if (oldVersion == 8) {
1136 // accounts: add security sync key & user signature columns
1137 try {
1138 db.execSQL("alter table " + Account.TABLE_NAME
1139 + " add column " + AccountColumns.SECURITY_SYNC_KEY + " text" + ";");
1140 db.execSQL("alter table " + Account.TABLE_NAME
1141 + " add column " + AccountColumns.SIGNATURE + " text" + ";");
1142 } catch (SQLException e) {
1143 // Shouldn't be needed unless we're debugging and interrupt the process
1144 Log.w(TAG, "Exception upgrading EmailProvider.db from 8 to 9 " + e);
1145 }
1146 oldVersion = 9;
1147 }
Marc Blank4006e5f2010-02-15 16:01:38 -08001148 if (oldVersion == 9) {
1149 // Message: add meeting info column into Message tables
1150 try {
1151 db.execSQL("alter table " + Message.TABLE_NAME
1152 + " add column " + MessageColumns.MEETING_INFO + " text" + ";");
1153 db.execSQL("alter table " + Message.UPDATED_TABLE_NAME
1154 + " add column " + MessageColumns.MEETING_INFO + " text" + ";");
1155 db.execSQL("alter table " + Message.DELETED_TABLE_NAME
1156 + " add column " + MessageColumns.MEETING_INFO + " text" + ";");
1157 } catch (SQLException e) {
1158 // Shouldn't be needed unless we're debugging and interrupt the process
1159 Log.w(TAG, "Exception upgrading EmailProvider.db from 9 to 10 " + e);
1160 }
1161 oldVersion = 10;
1162 }
Andrew Stadler3aaba9e2010-02-22 12:57:33 -08001163 if (oldVersion == 10) {
1164 // Attachment: add content and flags columns
1165 try {
1166 db.execSQL("alter table " + Attachment.TABLE_NAME
1167 + " add column " + AttachmentColumns.CONTENT + " text" + ";");
1168 db.execSQL("alter table " + Attachment.TABLE_NAME
1169 + " add column " + AttachmentColumns.FLAGS + " integer" + ";");
1170 } catch (SQLException e) {
1171 // Shouldn't be needed unless we're debugging and interrupt the process
1172 Log.w(TAG, "Exception upgrading EmailProvider.db from 10 to 11 " + e);
1173 }
1174 oldVersion = 11;
1175 }
Makoto Onuki20225d52010-03-12 13:30:26 -08001176 if (oldVersion == 11) {
1177 // Attachment: add content_bytes
1178 try {
1179 db.execSQL("alter table " + Attachment.TABLE_NAME
1180 + " add column " + AttachmentColumns.CONTENT_BYTES + " blob" + ";");
1181 } catch (SQLException e) {
1182 // Shouldn't be needed unless we're debugging and interrupt the process
1183 Log.w(TAG, "Exception upgrading EmailProvider.db from 11 to 12 " + e);
1184 }
1185 oldVersion = 12;
1186 }
Makoto Onuki574854b2010-07-30 13:53:59 -07001187 if (oldVersion == 12) {
1188 try {
1189 db.execSQL("alter table " + Mailbox.TABLE_NAME
1190 + " add column " + Mailbox.MESSAGE_COUNT
1191 +" integer not null default 0" + ";");
1192 recalculateMessageCount(db);
1193 } catch (SQLException e) {
1194 // Shouldn't be needed unless we're debugging and interrupt the process
1195 Log.w(TAG, "Exception upgrading EmailProvider.db from 12 to 13 " + e);
1196 }
1197 oldVersion = 13;
1198 }
Marc Blanke7b9e4a2010-09-01 19:06:15 -07001199 if (oldVersion == 13) {
1200 try {
1201 db.execSQL("alter table " + Message.TABLE_NAME
1202 + " add column " + Message.SNIPPET
1203 +" text" + ";");
1204 } catch (SQLException e) {
1205 // Shouldn't be needed unless we're debugging and interrupt the process
1206 Log.w(TAG, "Exception upgrading EmailProvider.db from 13 to 14 " + e);
1207 }
1208 oldVersion = 14;
1209 }
Makoto Onukiddc8dea2010-09-07 12:36:48 -07001210 if (oldVersion == 14) {
1211 try {
1212 db.execSQL("alter table " + Message.DELETED_TABLE_NAME
1213 + " add column " + Message.SNIPPET +" text" + ";");
1214 db.execSQL("alter table " + Message.UPDATED_TABLE_NAME
1215 + " add column " + Message.SNIPPET +" text" + ";");
1216 } catch (SQLException e) {
1217 // Shouldn't be needed unless we're debugging and interrupt the process
1218 Log.w(TAG, "Exception upgrading EmailProvider.db from 14 to 15 " + e);
1219 }
1220 oldVersion = 15;
1221 }
Marc Blank75a873b2010-12-08 17:11:04 -08001222 if (oldVersion == 15) {
1223 try {
1224 db.execSQL("alter table " + Attachment.TABLE_NAME
1225 + " add column " + Attachment.ACCOUNT_KEY +" integer" + ";");
1226 // Update all existing attachments to add the accountKey data
1227 db.execSQL("update " + Attachment.TABLE_NAME + " set " +
1228 Attachment.ACCOUNT_KEY + "= (SELECT " + Message.TABLE_NAME + "." +
1229 Message.ACCOUNT_KEY + " from " + Message.TABLE_NAME + " where " +
1230 Message.TABLE_NAME + "." + Message.RECORD_ID + " = " +
1231 Attachment.TABLE_NAME + "." + Attachment.MESSAGE_KEY + ")");
1232 } catch (SQLException e) {
1233 // Shouldn't be needed unless we're debugging and interrupt the process
1234 Log.w(TAG, "Exception upgrading EmailProvider.db from 15 to 16 " + e);
1235 }
1236 oldVersion = 16;
1237 }
Andy Stadler3a585092011-03-01 10:45:50 -08001238 if (oldVersion == 16) {
1239 try {
1240 db.execSQL("alter table " + Mailbox.TABLE_NAME
1241 + " add column " + Mailbox.PARENT_KEY + " integer;");
1242 } catch (SQLException e) {
1243 // Shouldn't be needed unless we're debugging and interrupt the process
1244 Log.w(TAG, "Exception upgrading EmailProvider.db from 16 to 17 " + e);
1245 }
1246 oldVersion = 17;
1247 }
Todd Kennedy22208772011-04-22 15:45:11 -07001248 if (oldVersion == 17) {
1249 upgradeFromVersion17ToVersion18(db);
1250 oldVersion = 18;
1251 }
Marc Blankaeee10e2011-04-27 17:12:06 -07001252 if (oldVersion == 18) {
Marc Blankaeee10e2011-04-27 17:12:06 -07001253 try {
1254 db.execSQL("alter table " + Account.TABLE_NAME
1255 + " add column " + Account.POLICY_KEY + " integer;");
1256 db.execSQL("drop trigger account_delete;");
1257 db.execSQL(TRIGGER_ACCOUNT_DELETE);
1258 createPolicyTable(db);
1259 convertPolicyFlagsToPolicyTable(db);
1260 } catch (SQLException e) {
1261 // Shouldn't be needed unless we're debugging and interrupt the process
1262 Log.w(TAG, "Exception upgrading EmailProvider.db from 18 to 19 " + e);
1263 }
1264 oldVersion = 19;
1265 }
Marc Blankf91a03f2011-05-05 10:34:29 -07001266 if (oldVersion == 19) {
1267 try {
1268 db.execSQL("alter table " + Policy.TABLE_NAME
1269 + " add column " + PolicyColumns.REQUIRE_MANUAL_SYNC_WHEN_ROAMING +
1270 " integer;");
1271 db.execSQL("alter table " + Policy.TABLE_NAME
1272 + " add column " + PolicyColumns.DONT_ALLOW_CAMERA + " integer;");
1273 db.execSQL("alter table " + Policy.TABLE_NAME
1274 + " add column " + PolicyColumns.DONT_ALLOW_ATTACHMENTS + " integer;");
1275 db.execSQL("alter table " + Policy.TABLE_NAME
1276 + " add column " + PolicyColumns.DONT_ALLOW_HTML + " integer;");
1277 db.execSQL("alter table " + Policy.TABLE_NAME
1278 + " add column " + PolicyColumns.MAX_ATTACHMENT_SIZE + " integer;");
1279 db.execSQL("alter table " + Policy.TABLE_NAME
1280 + " add column " + PolicyColumns.MAX_TEXT_TRUNCATION_SIZE +
1281 " integer;");
1282 db.execSQL("alter table " + Policy.TABLE_NAME
1283 + " add column " + PolicyColumns.MAX_HTML_TRUNCATION_SIZE +
1284 " integer;");
1285 db.execSQL("alter table " + Policy.TABLE_NAME
1286 + " add column " + PolicyColumns.MAX_EMAIL_LOOKBACK + " integer;");
1287 db.execSQL("alter table " + Policy.TABLE_NAME
1288 + " add column " + PolicyColumns.MAX_CALENDAR_LOOKBACK + " integer;");
1289 db.execSQL("alter table " + Policy.TABLE_NAME
1290 + " add column " + PolicyColumns.PASSWORD_RECOVERY_ENABLED +
1291 " integer;");
1292 } catch (SQLException e) {
1293 // Shouldn't be needed unless we're debugging and interrupt the process
Todd Kennedya9ac20b2011-05-06 13:37:02 -07001294 Log.w(TAG, "Exception upgrading EmailProvider.db from 19 to 20 " + e);
Marc Blankf91a03f2011-05-05 10:34:29 -07001295 }
1296 oldVersion = 20;
1297 }
Todd Kennedya9ac20b2011-05-06 13:37:02 -07001298 if (oldVersion == 20) {
1299 upgradeFromVersion20ToVersion21(db);
1300 oldVersion = 21;
1301 }
Marc Blankf3ff0ba2011-05-19 14:14:14 -07001302 if (oldVersion == 21) {
1303 upgradeFromVersion21ToVersion22(db, mContext);
1304 oldVersion = 22;
1305 }
Todd Kennedy9dcb72e2011-06-03 08:51:25 -07001306 if (oldVersion == 22) {
1307 upgradeFromVersion22ToVersion23(db);
1308 oldVersion = 23;
1309 }
Ben Komalo313586c2011-06-07 11:39:16 -07001310 if (oldVersion == 23) {
1311 upgradeFromVersion23ToVersion24(db);
1312 oldVersion = 24;
1313 }
Jorge Lugo5a3888f2011-06-01 10:09:26 -07001314 if (oldVersion == 24) {
1315 upgradeFromVersion24ToVersion25(db);
1316 oldVersion = 25;
1317 }
Marc Blankdeb345a2011-06-23 10:22:25 -07001318 if (oldVersion == 25) {
1319 upgradeFromVersion25ToVersion26(db);
1320 oldVersion = 26;
1321 }
Marc Blank9a013532011-06-23 10:52:21 -07001322 if (oldVersion == 26) {
1323 try {
1324 db.execSQL("alter table " + Message.TABLE_NAME
1325 + " add column " + Message.PROTOCOL_SEARCH_INFO + " text;");
1326 db.execSQL("alter table " + Message.DELETED_TABLE_NAME
1327 + " add column " + Message.PROTOCOL_SEARCH_INFO +" text" + ";");
1328 db.execSQL("alter table " + Message.UPDATED_TABLE_NAME
1329 + " add column " + Message.PROTOCOL_SEARCH_INFO +" text" + ";");
1330 } catch (SQLException e) {
1331 // Shouldn't be needed unless we're debugging and interrupt the process
1332 Log.w(TAG, "Exception upgrading EmailProvider.db from 26 to 27 " + e);
1333 }
1334 oldVersion = 27;
1335 }
Marc Blankaca94262011-07-19 18:19:59 -07001336 if (oldVersion == 27) {
1337 try {
1338 db.execSQL("alter table " + Account.TABLE_NAME
1339 + " add column " + Account.NOTIFIED_MESSAGE_ID + " integer;");
1340 db.execSQL("alter table " + Account.TABLE_NAME
1341 + " add column " + Account.NOTIFIED_MESSAGE_COUNT + " integer;");
1342 } catch (SQLException e) {
1343 // Shouldn't be needed unless we're debugging and interrupt the process
1344 Log.w(TAG, "Exception upgrading EmailProvider.db from 27 to 27 " + e);
1345 }
1346 oldVersion = 28;
1347 }
Andrew Stadlerf3d5b202009-05-26 16:40:34 -07001348 }
1349
1350 @Override
1351 public void onOpen(SQLiteDatabase db) {
1352 }
1353 }
1354
1355 @Override
1356 public int delete(Uri uri, String selection, String[] selectionArgs) {
Marc Blanke6a22df2010-12-16 12:28:16 -08001357 final int match = findMatch(uri, "delete");
Marc Blanka290f502009-06-15 14:40:06 -07001358 Context context = getContext();
Marc Blankcba3a482009-08-05 17:31:50 -07001359 // Pick the correct database for this operation
1360 // If we're in a transaction already (which would happen during applyBatch), then the
1361 // body database is already attached to the email database and any attempt to use the
1362 // body database directly will result in a SQLiteException (the database is locked)
Marc Blanka867ebb2009-08-18 11:44:27 -07001363 SQLiteDatabase db = getDatabase(context);
Andrew Stadlerf3d5b202009-05-26 16:40:34 -07001364 int table = match >> BASE_SHIFT;
Marc Blank2c67f1f2009-06-16 12:03:45 -07001365 String id = "0";
Marc Blankcba3a482009-08-05 17:31:50 -07001366 boolean messageDeletion = false;
Marc Blank1b9337e2010-09-23 09:19:44 -07001367 ContentResolver resolver = context.getContentResolver();
Marc Blankf3743042009-06-27 12:14:14 -07001368
Marc Blank6e418aa2011-06-18 18:03:11 -07001369 ContentCache cache = mContentCaches[table];
Marc Blankfab77f12010-10-27 16:50:54 -07001370 String tableName = TABLE_NAMES[table];
Marc Blankb6493a02009-07-05 12:54:49 -07001371 int result = -1;
Marc Blankf3743042009-06-27 12:14:14 -07001372
Marc Blank2c67f1f2009-06-16 12:03:45 -07001373 try {
1374 switch (match) {
1375 // These are cases in which one or more Messages might get deleted, either by
1376 // cascade or explicitly
1377 case MAILBOX_ID:
1378 case MAILBOX:
1379 case ACCOUNT_ID:
1380 case ACCOUNT:
1381 case MESSAGE:
Marc Blankf3743042009-06-27 12:14:14 -07001382 case SYNCED_MESSAGE_ID:
Marc Blank2c67f1f2009-06-16 12:03:45 -07001383 case MESSAGE_ID:
1384 // Handle lost Body records here, since this cannot be done in a trigger
1385 // The process is:
Marc Blanka867ebb2009-08-18 11:44:27 -07001386 // 1) Begin a transaction, ensuring that both databases are affected atomically
1387 // 2) Do the requested deletion, with cascading deletions handled in triggers
1388 // 3) End the transaction, committing all changes atomically
Andrew Stadler6c219422009-09-10 11:52:36 -07001389 //
1390 // Bodies are auto-deleted here; Attachments are auto-deleted via trigger
Marc Blankcba3a482009-08-05 17:31:50 -07001391 messageDeletion = true;
Marc Blank8587aa62009-09-18 20:36:15 -07001392 db.beginTransaction();
Marc Blank2c67f1f2009-06-16 12:03:45 -07001393 break;
1394 }
1395 switch (match) {
1396 case BODY_ID:
Marc Blankf3743042009-06-27 12:14:14 -07001397 case DELETED_MESSAGE_ID:
1398 case SYNCED_MESSAGE_ID:
Marc Blank2c67f1f2009-06-16 12:03:45 -07001399 case MESSAGE_ID:
1400 case UPDATED_MESSAGE_ID:
1401 case ATTACHMENT_ID:
1402 case MAILBOX_ID:
1403 case ACCOUNT_ID:
1404 case HOSTAUTH_ID:
Marc Blankaeee10e2011-04-27 17:12:06 -07001405 case POLICY_ID:
Jorge Lugo5a3888f2011-06-01 10:09:26 -07001406 case QUICK_RESPONSE_ID:
Marc Blank2c67f1f2009-06-16 12:03:45 -07001407 id = uri.getPathSegments().get(1);
Marc Blankf3743042009-06-27 12:14:14 -07001408 if (match == SYNCED_MESSAGE_ID) {
1409 // For synced messages, first copy the old message to the deleted table and
1410 // delete it from the updated table (in case it was updated first)
1411 // Note that this is all within a transaction, for atomicity
1412 db.execSQL(DELETED_MESSAGE_INSERT + id);
1413 db.execSQL(UPDATED_MESSAGE_DELETE + id);
1414 }
Marc Blankfab77f12010-10-27 16:50:54 -07001415 if (cache != null) {
1416 cache.lock(id);
1417 }
1418 try {
1419 result = db.delete(tableName, whereWithId(id, selection), selectionArgs);
1420 if (cache != null) {
1421 switch(match) {
1422 case ACCOUNT_ID:
1423 // Account deletion will clear all of the caches, as HostAuth's,
1424 // Mailboxes, and Messages will be deleted in the process
Marc Blank6e418aa2011-06-18 18:03:11 -07001425 mCacheMailbox.invalidate("Delete", uri, selection);
1426 mCacheHostAuth.invalidate("Delete", uri, selection);
1427 mCachePolicy.invalidate("Delete", uri, selection);
Marc Blankfab77f12010-10-27 16:50:54 -07001428 //$FALL-THROUGH$
1429 case MAILBOX_ID:
1430 // Mailbox deletion will clear the Message cache
Marc Blank6e418aa2011-06-18 18:03:11 -07001431 mCacheMessage.invalidate("Delete", uri, selection);
Marc Blankfab77f12010-10-27 16:50:54 -07001432 //$FALL-THROUGH$
1433 case SYNCED_MESSAGE_ID:
1434 case MESSAGE_ID:
1435 case HOSTAUTH_ID:
Marc Blank6e418aa2011-06-18 18:03:11 -07001436 case POLICY_ID:
Marc Blankfab77f12010-10-27 16:50:54 -07001437 cache.invalidate("Delete", uri, selection);
Marc Blank6e418aa2011-06-18 18:03:11 -07001438 // Make sure all data is properly cached
1439 if (match != MESSAGE_ID) {
1440 preCacheData();
1441 }
Marc Blankfab77f12010-10-27 16:50:54 -07001442 break;
1443 }
1444 }
1445 } finally {
1446 if (cache != null) {
1447 cache.unlock(id);
1448 }
Todd Kennedybf30f942011-05-06 11:31:10 -07001449 }
Marc Blank2c67f1f2009-06-16 12:03:45 -07001450 break;
Andrew Stadler6c219422009-09-10 11:52:36 -07001451 case ATTACHMENTS_MESSAGE_ID:
1452 // All attachments for the given message
1453 id = uri.getPathSegments().get(2);
Marc Blankfab77f12010-10-27 16:50:54 -07001454 result = db.delete(tableName,
Andrew Stadler6c219422009-09-10 11:52:36 -07001455 whereWith(Attachment.MESSAGE_KEY + "=" + id, selection), selectionArgs);
1456 break;
1457
Marc Blank2c67f1f2009-06-16 12:03:45 -07001458 case BODY:
1459 case MESSAGE:
Marc Blankf3743042009-06-27 12:14:14 -07001460 case DELETED_MESSAGE:
Marc Blank2c67f1f2009-06-16 12:03:45 -07001461 case UPDATED_MESSAGE:
1462 case ATTACHMENT:
1463 case MAILBOX:
1464 case ACCOUNT:
1465 case HOSTAUTH:
Marc Blankaeee10e2011-04-27 17:12:06 -07001466 case POLICY:
Marc Blankfab77f12010-10-27 16:50:54 -07001467 switch(match) {
1468 // See the comments above for deletion of ACCOUNT_ID, etc
1469 case ACCOUNT:
Marc Blank6e418aa2011-06-18 18:03:11 -07001470 mCacheMailbox.invalidate("Delete", uri, selection);
1471 mCacheHostAuth.invalidate("Delete", uri, selection);
1472 mCachePolicy.invalidate("Delete", uri, selection);
Marc Blankfab77f12010-10-27 16:50:54 -07001473 //$FALL-THROUGH$
1474 case MAILBOX:
Marc Blank6e418aa2011-06-18 18:03:11 -07001475 mCacheMessage.invalidate("Delete", uri, selection);
Marc Blankfab77f12010-10-27 16:50:54 -07001476 //$FALL-THROUGH$
1477 case MESSAGE:
1478 case HOSTAUTH:
Marc Blank6e418aa2011-06-18 18:03:11 -07001479 case POLICY:
Marc Blankfab77f12010-10-27 16:50:54 -07001480 cache.invalidate("Delete", uri, selection);
1481 break;
1482 }
1483 result = db.delete(tableName, selection, selectionArgs);
Marc Blank6e418aa2011-06-18 18:03:11 -07001484 switch(match) {
1485 case ACCOUNT:
1486 case MAILBOX:
1487 case HOSTAUTH:
1488 case POLICY:
1489 // Make sure all data is properly cached
1490 preCacheData();
1491 break;
1492 }
Marc Blank2c67f1f2009-06-16 12:03:45 -07001493 break;
Andrew Stadler6c219422009-09-10 11:52:36 -07001494
Marc Blank2c67f1f2009-06-16 12:03:45 -07001495 default:
1496 throw new IllegalArgumentException("Unknown URI " + uri);
1497 }
Marc Blankcba3a482009-08-05 17:31:50 -07001498 if (messageDeletion) {
Mihai Predafb7974f2009-08-03 15:05:50 +02001499 if (match == MESSAGE_ID) {
Andrew Stadler5f4dbd62009-06-24 12:48:57 -07001500 // Delete the Body record associated with the deleted message
Marc Blankf3743042009-06-27 12:14:14 -07001501 db.execSQL(DELETE_BODY + id);
Mihai Predafb7974f2009-08-03 15:05:50 +02001502 } else {
1503 // Delete any orphaned Body records
1504 db.execSQL(DELETE_ORPHAN_BODIES);
Marc Blankcba3a482009-08-05 17:31:50 -07001505 }
Marc Blank8587aa62009-09-18 20:36:15 -07001506 db.setTransactionSuccessful();
Andrew Stadler5f4dbd62009-06-24 12:48:57 -07001507 }
Marc Blank0e1595c2009-11-18 17:11:33 -08001508 } catch (SQLiteException e) {
1509 checkDatabases();
1510 throw e;
Marc Blank2c67f1f2009-06-16 12:03:45 -07001511 } finally {
Marc Blankcba3a482009-08-05 17:31:50 -07001512 if (messageDeletion) {
Marc Blank8587aa62009-09-18 20:36:15 -07001513 db.endTransaction();
Andrew Stadler5f4dbd62009-06-24 12:48:57 -07001514 }
Andrew Stadlerf3d5b202009-05-26 16:40:34 -07001515 }
Makoto Onuki261d6c32010-09-14 16:28:50 -07001516
Todd Kennedybf30f942011-05-06 11:31:10 -07001517 // Notify all notifier cursors
Todd Kennedye7fb4ac2011-05-11 15:29:24 -07001518 sendNotifierChange(getBaseNotificationUri(match), NOTIFICATION_OP_DELETE, id);
Todd Kennedybf30f942011-05-06 11:31:10 -07001519
1520 // Notify all email content cursors
Marc Blank1b9337e2010-09-23 09:19:44 -07001521 resolver.notifyChange(EmailContent.CONTENT_URI, null);
Andrew Stadler626f3e42009-06-01 14:34:16 -07001522 return result;
Andrew Stadlerf3d5b202009-05-26 16:40:34 -07001523 }
1524
1525 @Override
1526 // Use the email- prefix because message, mailbox, and account are so generic (e.g. SMS, IM)
1527 public String getType(Uri uri) {
Marc Blanke6a22df2010-12-16 12:28:16 -08001528 int match = findMatch(uri, "getType");
Andrew Stadlerf3d5b202009-05-26 16:40:34 -07001529 switch (match) {
Marc Blanka290f502009-06-15 14:40:06 -07001530 case BODY_ID:
1531 return "vnd.android.cursor.item/email-body";
1532 case BODY:
Marc Blankc81bef62010-10-13 19:04:46 -07001533 return "vnd.android.cursor.dir/email-body";
Marc Blanke34525d2009-06-17 10:22:37 -07001534 case UPDATED_MESSAGE_ID:
Marc Blanka290f502009-06-15 14:40:06 -07001535 case MESSAGE_ID:
Marc Blankc81bef62010-10-13 19:04:46 -07001536 // NOTE: According to the framework folks, we're supposed to invent mime types as
1537 // a way of passing information to drag & drop recipients.
1538 // If there's a mailboxId parameter in the url, we respond with a mime type that
1539 // has -n appended, where n is the mailboxId of the message. The drag & drop code
1540 // uses this information to know not to allow dragging the item to its own mailbox
1541 String mimeType = EMAIL_MESSAGE_MIME_TYPE;
1542 String mailboxId = uri.getQueryParameter(MESSAGE_URI_PARAMETER_MAILBOX_ID);
1543 if (mailboxId != null) {
1544 mimeType += "-" + mailboxId;
1545 }
1546 return mimeType;
Marc Blanke34525d2009-06-17 10:22:37 -07001547 case UPDATED_MESSAGE:
Marc Blankfae47272009-05-29 14:24:34 -07001548 case MESSAGE:
Marc Blanka290f502009-06-15 14:40:06 -07001549 return "vnd.android.cursor.dir/email-message";
Marc Blankfae47272009-05-29 14:24:34 -07001550 case MAILBOX:
1551 return "vnd.android.cursor.dir/email-mailbox";
1552 case MAILBOX_ID:
1553 return "vnd.android.cursor.item/email-mailbox";
1554 case ACCOUNT:
1555 return "vnd.android.cursor.dir/email-account";
1556 case ACCOUNT_ID:
1557 return "vnd.android.cursor.item/email-account";
Andrew Stadler41192182009-07-16 16:03:40 -07001558 case ATTACHMENTS_MESSAGE_ID:
Marc Blankfae47272009-05-29 14:24:34 -07001559 case ATTACHMENT:
1560 return "vnd.android.cursor.dir/email-attachment";
1561 case ATTACHMENT_ID:
Marc Blank09fd4d02010-08-09 17:48:53 -07001562 return EMAIL_ATTACHMENT_MIME_TYPE;
Marc Blankfae47272009-05-29 14:24:34 -07001563 case HOSTAUTH:
1564 return "vnd.android.cursor.dir/email-hostauth";
1565 case HOSTAUTH_ID:
1566 return "vnd.android.cursor.item/email-hostauth";
1567 default:
1568 throw new IllegalArgumentException("Unknown URI " + uri);
Andrew Stadlerf3d5b202009-05-26 16:40:34 -07001569 }
1570 }
1571
1572 @Override
1573 public Uri insert(Uri uri, ContentValues values) {
Marc Blanke6a22df2010-12-16 12:28:16 -08001574 int match = findMatch(uri, "insert");
Marc Blanka290f502009-06-15 14:40:06 -07001575 Context context = getContext();
Marc Blank1b9337e2010-09-23 09:19:44 -07001576 ContentResolver resolver = context.getContentResolver();
1577
Marc Blankcba3a482009-08-05 17:31:50 -07001578 // See the comment at delete(), above
Marc Blanka867ebb2009-08-18 11:44:27 -07001579 SQLiteDatabase db = getDatabase(context);
Andrew Stadlerf3d5b202009-05-26 16:40:34 -07001580 int table = match >> BASE_SHIFT;
Todd Kennedybf30f942011-05-06 11:31:10 -07001581 String id = "0";
1582 long longId;
Marc Blankf3743042009-06-27 12:14:14 -07001583
Makoto Onuki5b0c2c72010-09-10 14:37:01 -07001584 // We do NOT allow setting of unreadCount/messageCount via the provider
1585 // These columns are maintained via triggers
1586 if (match == MAILBOX_ID || match == MAILBOX) {
1587 values.put(MailboxColumns.UNREAD_COUNT, 0);
1588 values.put(MailboxColumns.MESSAGE_COUNT, 0);
1589 }
Makoto Onukif678a8e2010-09-10 13:06:07 -07001590
Andrew Stadler626f3e42009-06-01 14:34:16 -07001591 Uri resultUri = null;
Marc Blankf3743042009-06-27 12:14:14 -07001592
Marc Blank0e1595c2009-11-18 17:11:33 -08001593 try {
1594 switch (match) {
Marc Blank6e418aa2011-06-18 18:03:11 -07001595 // NOTE: It is NOT legal for production code to insert directly into UPDATED_MESSAGE
1596 // or DELETED_MESSAGE; see the comment below for details
Marc Blank0e1595c2009-11-18 17:11:33 -08001597 case UPDATED_MESSAGE:
1598 case DELETED_MESSAGE:
Marc Blank6e418aa2011-06-18 18:03:11 -07001599 case MESSAGE:
Marc Blank0e1595c2009-11-18 17:11:33 -08001600 case BODY:
Marc Blank0e1595c2009-11-18 17:11:33 -08001601 case ATTACHMENT:
1602 case MAILBOX:
1603 case ACCOUNT:
1604 case HOSTAUTH:
Marc Blankaeee10e2011-04-27 17:12:06 -07001605 case POLICY:
Jorge Lugo5a3888f2011-06-01 10:09:26 -07001606 case QUICK_RESPONSE:
Todd Kennedybf30f942011-05-06 11:31:10 -07001607 longId = db.insert(TABLE_NAMES[table], "foo", values);
1608 resultUri = ContentUris.withAppendedId(uri, longId);
Marc Blank6e418aa2011-06-18 18:03:11 -07001609 switch(match) {
1610 case MAILBOX:
1611 if (values.containsKey(MailboxColumns.TYPE)) {
1612 // Only cache special mailbox types
1613 int type = values.getAsInteger(MailboxColumns.TYPE);
1614 if (type != Mailbox.TYPE_INBOX && type != Mailbox.TYPE_OUTBOX &&
1615 type != Mailbox.TYPE_DRAFTS && type != Mailbox.TYPE_SENT &&
1616 type != Mailbox.TYPE_TRASH && type != Mailbox.TYPE_SEARCH) {
1617 break;
1618 }
1619 }
1620 //$FALL-THROUGH$
1621 case ACCOUNT:
1622 case HOSTAUTH:
1623 case POLICY:
1624 // Cache new account, host auth, policy, and some mailbox rows
1625 Cursor c = query(resultUri, CACHE_PROJECTIONS[table], null, null, null);
1626 if (c != null) {
1627 if (match == MAILBOX) {
1628 addToMailboxTypeMap(c);
1629 } else if (match == ACCOUNT) {
1630 getOrCreateAccountMailboxTypeMap(longId);
1631 }
1632 c.close();
1633 }
1634 break;
1635 }
Marc Blank0e1595c2009-11-18 17:11:33 -08001636 // Clients shouldn't normally be adding rows to these tables, as they are
1637 // maintained by triggers. However, we need to be able to do this for unit
1638 // testing, so we allow the insert and then throw the same exception that we
1639 // would if this weren't allowed.
1640 if (match == UPDATED_MESSAGE || match == DELETED_MESSAGE) {
1641 throw new IllegalArgumentException("Unknown URL " + uri);
1642 }
Marc Blank09fd4d02010-08-09 17:48:53 -07001643 if (match == ATTACHMENT) {
Marc Blank75a873b2010-12-08 17:11:04 -08001644 int flags = 0;
Marc Blank09fd4d02010-08-09 17:48:53 -07001645 if (values.containsKey(Attachment.FLAGS)) {
Marc Blank75a873b2010-12-08 17:11:04 -08001646 flags = values.getAsInteger(Attachment.FLAGS);
Marc Blank09fd4d02010-08-09 17:48:53 -07001647 }
Marc Blank75a873b2010-12-08 17:11:04 -08001648 // Report all new attachments to the download service
Ben Komalo32bed4b2011-08-23 18:02:11 -07001649 mAttachmentService.attachmentChanged(getContext(), longId, flags);
Marc Blank09fd4d02010-08-09 17:48:53 -07001650 }
Marc Blank0e1595c2009-11-18 17:11:33 -08001651 break;
1652 case MAILBOX_ID:
1653 // This implies adding a message to a mailbox
1654 // Hmm, a problem here is that we can't link the account as well, so it must be
1655 // already in the values...
Todd Kennedybf30f942011-05-06 11:31:10 -07001656 longId = Long.parseLong(uri.getPathSegments().get(1));
1657 values.put(MessageColumns.MAILBOX_KEY, longId);
Makoto Onuki261d6c32010-09-14 16:28:50 -07001658 return insert(Message.CONTENT_URI, values); // Recurse
Marc Blank0e1595c2009-11-18 17:11:33 -08001659 case MESSAGE_ID:
1660 // This implies adding an attachment to a message.
Todd Kennedybf30f942011-05-06 11:31:10 -07001661 id = uri.getPathSegments().get(1);
1662 longId = Long.parseLong(id);
1663 values.put(AttachmentColumns.MESSAGE_KEY, longId);
Makoto Onuki261d6c32010-09-14 16:28:50 -07001664 return insert(Attachment.CONTENT_URI, values); // Recurse
Marc Blank0e1595c2009-11-18 17:11:33 -08001665 case ACCOUNT_ID:
1666 // This implies adding a mailbox to an account.
Todd Kennedybf30f942011-05-06 11:31:10 -07001667 longId = Long.parseLong(uri.getPathSegments().get(1));
1668 values.put(MailboxColumns.ACCOUNT_KEY, longId);
Makoto Onuki261d6c32010-09-14 16:28:50 -07001669 return insert(Mailbox.CONTENT_URI, values); // Recurse
Marc Blank0e1595c2009-11-18 17:11:33 -08001670 case ATTACHMENTS_MESSAGE_ID:
Todd Kennedybf30f942011-05-06 11:31:10 -07001671 longId = db.insert(TABLE_NAMES[table], "foo", values);
1672 resultUri = ContentUris.withAppendedId(Attachment.CONTENT_URI, longId);
Marc Blank0e1595c2009-11-18 17:11:33 -08001673 break;
1674 default:
Marc Blankef832992009-10-13 16:25:00 -07001675 throw new IllegalArgumentException("Unknown URL " + uri);
Marc Blank0e1595c2009-11-18 17:11:33 -08001676 }
1677 } catch (SQLiteException e) {
1678 checkDatabases();
1679 throw e;
Andrew Stadlerf3d5b202009-05-26 16:40:34 -07001680 }
Marc Blankf3743042009-06-27 12:14:14 -07001681
Todd Kennedybf30f942011-05-06 11:31:10 -07001682 // Notify all notifier cursors
Todd Kennedye7fb4ac2011-05-11 15:29:24 -07001683 sendNotifierChange(getBaseNotificationUri(match), NOTIFICATION_OP_INSERT, id);
Todd Kennedybf30f942011-05-06 11:31:10 -07001684
Makoto Onuki261d6c32010-09-14 16:28:50 -07001685 // Notify all existing cursors.
Marc Blank1b9337e2010-09-23 09:19:44 -07001686 resolver.notifyChange(EmailContent.CONTENT_URI, null);
Andrew Stadler626f3e42009-06-01 14:34:16 -07001687 return resultUri;
Andrew Stadlerf3d5b202009-05-26 16:40:34 -07001688 }
1689
1690 @Override
1691 public boolean onCreate() {
Marc Blank0e1595c2009-11-18 17:11:33 -08001692 checkDatabases();
Andrew Stadlerf3d5b202009-05-26 16:40:34 -07001693 return false;
1694 }
1695
Marc Blank0e1595c2009-11-18 17:11:33 -08001696 /**
1697 * The idea here is that the two databases (EmailProvider.db and EmailProviderBody.db must
1698 * always be in sync (i.e. there are two database or NO databases). This code will delete
1699 * any "orphan" database, so that both will be created together. Note that an "orphan" database
1700 * will exist after either of the individual databases is deleted due to data corruption.
1701 */
1702 public void checkDatabases() {
1703 // Uncache the databases
1704 if (mDatabase != null) {
1705 mDatabase = null;
1706 }
1707 if (mBodyDatabase != null) {
1708 mBodyDatabase = null;
1709 }
1710 // Look for orphans, and delete as necessary; these must always be in sync
1711 File databaseFile = getContext().getDatabasePath(DATABASE_NAME);
1712 File bodyFile = getContext().getDatabasePath(BODY_DATABASE_NAME);
1713
1714 // TODO Make sure attachments are deleted
1715 if (databaseFile.exists() && !bodyFile.exists()) {
1716 Log.w(TAG, "Deleting orphaned EmailProvider database...");
1717 databaseFile.delete();
1718 } else if (bodyFile.exists() && !databaseFile.exists()) {
1719 Log.w(TAG, "Deleting orphaned EmailProviderBody database...");
1720 bodyFile.delete();
1721 }
1722 }
1723
Andrew Stadlerf3d5b202009-05-26 16:40:34 -07001724 @Override
Marc Blank758a5322009-07-30 11:41:31 -07001725 public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
Marc Blankfae47272009-05-29 14:24:34 -07001726 String sortOrder) {
Marc Blankfab77f12010-10-27 16:50:54 -07001727 long time = 0L;
1728 if (Email.DEBUG) {
1729 time = System.nanoTime();
1730 }
Andrew Stadlerf3d5b202009-05-26 16:40:34 -07001731 Cursor c = null;
Marc Blankd306ba32010-12-30 14:55:27 -08001732 int match;
1733 try {
1734 match = findMatch(uri, "query");
1735 } catch (IllegalArgumentException e) {
1736 String uriString = uri.toString();
1737 // If we were passed an illegal uri, see if it ends in /-1
1738 // if so, and if substituting 0 for -1 results in a valid uri, return an empty cursor
1739 if (uriString != null && uriString.endsWith("/-1")) {
1740 uri = Uri.parse(uriString.substring(0, uriString.length() - 2) + "0");
1741 match = findMatch(uri, "query");
1742 switch (match) {
1743 case BODY_ID:
1744 case MESSAGE_ID:
1745 case DELETED_MESSAGE_ID:
1746 case UPDATED_MESSAGE_ID:
1747 case ATTACHMENT_ID:
1748 case MAILBOX_ID:
1749 case ACCOUNT_ID:
1750 case HOSTAUTH_ID:
Marc Blankaeee10e2011-04-27 17:12:06 -07001751 case POLICY_ID:
Marc Blankd306ba32010-12-30 14:55:27 -08001752 return new MatrixCursor(projection, 0);
1753 }
1754 }
1755 throw e;
1756 }
Marc Blanka290f502009-06-15 14:40:06 -07001757 Context context = getContext();
Marc Blankcba3a482009-08-05 17:31:50 -07001758 // See the comment at delete(), above
Marc Blanka867ebb2009-08-18 11:44:27 -07001759 SQLiteDatabase db = getDatabase(context);
Andrew Stadlerf3d5b202009-05-26 16:40:34 -07001760 int table = match >> BASE_SHIFT;
Marc Blank07597e52010-09-29 08:43:31 -07001761 String limit = uri.getQueryParameter(EmailContent.PARAMETER_LIMIT);
Andrew Stadlerf3d5b202009-05-26 16:40:34 -07001762 String id;
Marc Blankf3743042009-06-27 12:14:14 -07001763
Marc Blankfab77f12010-10-27 16:50:54 -07001764 // Find the cache for this query's table (if any)
1765 ContentCache cache = null;
1766 String tableName = TABLE_NAMES[table];
1767 // We can only use the cache if there's no selection
1768 if (selection == null) {
Marc Blank6e418aa2011-06-18 18:03:11 -07001769 cache = mContentCaches[table];
Marc Blankfab77f12010-10-27 16:50:54 -07001770 }
1771 if (cache == null) {
1772 ContentCache.notCacheable(uri, selection);
1773 }
1774
Marc Blank0e1595c2009-11-18 17:11:33 -08001775 try {
1776 switch (match) {
Marc Blank6e418aa2011-06-18 18:03:11 -07001777 case ACCOUNT_DEFAULT_ID:
1778 // Start with a snapshot of the cache
1779 Map<String, Cursor> accountCache = mCacheAccount.getSnapshot();
1780 long accountId = Account.NO_ACCOUNT;
Ben Komalo4de538b2011-07-13 14:43:21 -07001781 // Find the account with "isDefault" set, or the lowest account ID otherwise.
1782 // Note that the snapshot from the cached isn't guaranteed to be sorted in any
1783 // way.
Marc Blank6e418aa2011-06-18 18:03:11 -07001784 Collection<Cursor> accounts = accountCache.values();
Marc Blank6e418aa2011-06-18 18:03:11 -07001785 for (Cursor accountCursor: accounts) {
Marc Blankc09ca392011-06-22 18:36:28 -07001786 // For now, at least, we can have zero count cursors (e.g. if someone looks
1787 // up a non-existent id); we need to skip these
1788 if (accountCursor.moveToFirst()) {
1789 boolean isDefault =
1790 accountCursor.getInt(Account.CONTENT_IS_DEFAULT_COLUMN) == 1;
Ben Komalo4de538b2011-07-13 14:43:21 -07001791 long iterId = accountCursor.getLong(Account.CONTENT_ID_COLUMN);
Marc Blankc09ca392011-06-22 18:36:28 -07001792 // We'll remember this one if it's the default or the first one we see
Ben Komalo4de538b2011-07-13 14:43:21 -07001793 if (isDefault) {
1794 accountId = iterId;
1795 break;
1796 } else if ((accountId == Account.NO_ACCOUNT) || (iterId < accountId)) {
1797 accountId = iterId;
Marc Blankc09ca392011-06-22 18:36:28 -07001798 }
Marc Blank6e418aa2011-06-18 18:03:11 -07001799 }
1800 }
1801 // Return a cursor with an id projection
1802 MatrixCursor mc = new MatrixCursor(EmailContent.ID_PROJECTION);
1803 mc.addRow(new Object[] {accountId});
Marc Blank0306da12011-07-27 21:57:06 -07001804 c = mc;
1805 break;
Marc Blank6e418aa2011-06-18 18:03:11 -07001806 case MAILBOX_ID_FROM_ACCOUNT_AND_TYPE:
1807 // Get accountId and type and find the mailbox in our map
Marc Blank5d7ff852011-06-27 20:11:24 -07001808 List<String> pathSegments = uri.getPathSegments();
1809 accountId = Long.parseLong(pathSegments.get(1));
1810 int type = Integer.parseInt(pathSegments.get(2));
1811 long mailboxId = getMailboxIdFromMailboxTypeMap(accountId, type);
Marc Blank6e418aa2011-06-18 18:03:11 -07001812 // Return a cursor with an id projection
Marc Blank5d7ff852011-06-27 20:11:24 -07001813 mc = new MatrixCursor(EmailContent.ID_PROJECTION);
Marc Blank6e418aa2011-06-18 18:03:11 -07001814 mc.addRow(new Object[] {mailboxId});
Marc Blank0306da12011-07-27 21:57:06 -07001815 c = mc;
1816 break;
Marc Blank0e1595c2009-11-18 17:11:33 -08001817 case BODY:
1818 case MESSAGE:
1819 case UPDATED_MESSAGE:
1820 case DELETED_MESSAGE:
1821 case ATTACHMENT:
1822 case MAILBOX:
1823 case ACCOUNT:
1824 case HOSTAUTH:
Marc Blankaeee10e2011-04-27 17:12:06 -07001825 case POLICY:
Jorge Lugo5a3888f2011-06-01 10:09:26 -07001826 case QUICK_RESPONSE:
Marc Blank6e418aa2011-06-18 18:03:11 -07001827 // Special-case "count of accounts"; it's common and we always know it
1828 if (match == ACCOUNT && Arrays.equals(projection, EmailContent.COUNT_COLUMNS) &&
1829 selection == null && limit.equals("1")) {
1830 int accountCount = mMailboxTypeMap.size();
1831 // In the rare case there are MAX_CACHED_ACCOUNTS or more, we can't do this
1832 if (accountCount < MAX_CACHED_ACCOUNTS) {
1833 mc = new MatrixCursor(projection, 1);
1834 mc.addRow(new Object[] {accountCount});
Marc Blank577e6042011-08-05 09:50:56 -07001835 c = mc;
1836 break;
Marc Blank6e418aa2011-06-18 18:03:11 -07001837 }
1838 }
Marc Blankfab77f12010-10-27 16:50:54 -07001839 c = db.query(tableName, projection,
Marc Blank0efe7382010-09-27 18:29:50 -07001840 selection, selectionArgs, null, null, sortOrder, limit);
Marc Blank0e1595c2009-11-18 17:11:33 -08001841 break;
1842 case BODY_ID:
1843 case MESSAGE_ID:
1844 case DELETED_MESSAGE_ID:
1845 case UPDATED_MESSAGE_ID:
1846 case ATTACHMENT_ID:
1847 case MAILBOX_ID:
1848 case ACCOUNT_ID:
1849 case HOSTAUTH_ID:
Marc Blankaeee10e2011-04-27 17:12:06 -07001850 case POLICY_ID:
Jorge Lugo5a3888f2011-06-01 10:09:26 -07001851 case QUICK_RESPONSE_ID:
Marc Blank0e1595c2009-11-18 17:11:33 -08001852 id = uri.getPathSegments().get(1);
Marc Blankfab77f12010-10-27 16:50:54 -07001853 if (cache != null) {
1854 c = cache.getCachedCursor(id, projection);
1855 }
1856 if (c == null) {
1857 CacheToken token = null;
1858 if (cache != null) {
1859 token = cache.getCacheToken(id);
1860 }
1861 c = db.query(tableName, projection, whereWithId(id, selection),
1862 selectionArgs, null, null, sortOrder, limit);
1863 if (cache != null) {
1864 c = cache.putCursor(c, id, projection, token);
1865 }
1866 }
Marc Blank0e1595c2009-11-18 17:11:33 -08001867 break;
1868 case ATTACHMENTS_MESSAGE_ID:
1869 // All attachments for the given message
1870 id = uri.getPathSegments().get(2);
1871 c = db.query(Attachment.TABLE_NAME, projection,
1872 whereWith(Attachment.MESSAGE_KEY + "=" + id, selection),
Marc Blank0efe7382010-09-27 18:29:50 -07001873 selectionArgs, null, null, sortOrder, limit);
Marc Blank0e1595c2009-11-18 17:11:33 -08001874 break;
Jorge Lugo5a3888f2011-06-01 10:09:26 -07001875 case QUICK_RESPONSE_ACCOUNT_ID:
1876 // All quick responses for the given account
1877 id = uri.getPathSegments().get(2);
1878 c = db.query(QuickResponse.TABLE_NAME, projection,
1879 whereWith(QuickResponse.ACCOUNT_KEY + "=" + id, selection),
1880 selectionArgs, null, null, sortOrder);
1881 break;
Marc Blank0e1595c2009-11-18 17:11:33 -08001882 default:
1883 throw new IllegalArgumentException("Unknown URI " + uri);
1884 }
1885 } catch (SQLiteException e) {
1886 checkDatabases();
1887 throw e;
Marc Blankfab77f12010-10-27 16:50:54 -07001888 } catch (RuntimeException e) {
1889 checkDatabases();
1890 e.printStackTrace();
1891 throw e;
1892 } finally {
Marc Blank6e418aa2011-06-18 18:03:11 -07001893 if (cache != null && c != null && Email.DEBUG) {
Marc Blankfab77f12010-10-27 16:50:54 -07001894 cache.recordQueryTime(c, System.nanoTime() - time);
1895 }
Marc Blank7390b182011-07-27 16:48:35 -07001896 if (c == null) {
1897 // This should never happen, but let's be sure to log it...
1898 Log.e(TAG, "Query returning null for uri: " + uri + ", selection: " + selection);
1899 }
Andrew Stadlerf3d5b202009-05-26 16:40:34 -07001900 }
1901
1902 if ((c != null) && !isTemporary()) {
Makoto Onuki261d6c32010-09-14 16:28:50 -07001903 c.setNotificationUri(getContext().getContentResolver(), uri);
Andrew Stadlerf3d5b202009-05-26 16:40:34 -07001904 }
1905 return c;
1906 }
1907
1908 private String whereWithId(String id, String selection) {
1909 StringBuilder sb = new StringBuilder(256);
1910 sb.append("_id=");
1911 sb.append(id);
1912 if (selection != null) {
Andrew Stadler6c219422009-09-10 11:52:36 -07001913 sb.append(" AND (");
Andrew Stadlerf3d5b202009-05-26 16:40:34 -07001914 sb.append(selection);
Andrew Stadler6c219422009-09-10 11:52:36 -07001915 sb.append(')');
Andrew Stadlerf3d5b202009-05-26 16:40:34 -07001916 }
1917 return sb.toString();
1918 }
Marc Blankf3743042009-06-27 12:14:14 -07001919
Andrew Stadler6c219422009-09-10 11:52:36 -07001920 /**
1921 * Combine a locally-generated selection with a user-provided selection
1922 *
1923 * This introduces risk that the local selection might insert incorrect chars
1924 * into the SQL, so use caution.
1925 *
1926 * @param where locally-generated selection, must not be null
1927 * @param selection user-provided selection, may be null
1928 * @return a single selection string
1929 */
Andrew Stadlerf3d5b202009-05-26 16:40:34 -07001930 private String whereWith(String where, String selection) {
Andrew Stadler6c219422009-09-10 11:52:36 -07001931 if (selection == null) {
1932 return where;
Andrew Stadlerf3d5b202009-05-26 16:40:34 -07001933 }
Andrew Stadler6c219422009-09-10 11:52:36 -07001934 StringBuilder sb = new StringBuilder(where);
1935 sb.append(" AND (");
1936 sb.append(selection);
1937 sb.append(')');
1938
Andrew Stadlerf3d5b202009-05-26 16:40:34 -07001939 return sb.toString();
1940 }
Marc Blankf3743042009-06-27 12:14:14 -07001941
Marc Blank09931902011-05-06 12:07:39 -07001942 /**
1943 * Restore a HostAuth from a database, given its unique id
1944 * @param db the database
1945 * @param id the unique id (_id) of the row
1946 * @return a fully populated HostAuth or null if the row does not exist
1947 */
Makoto Onuki9dad9ad2011-06-20 18:10:10 -07001948 private static HostAuth restoreHostAuth(SQLiteDatabase db, long id) {
Marc Blank09931902011-05-06 12:07:39 -07001949 Cursor c = db.query(HostAuth.TABLE_NAME, HostAuth.CONTENT_PROJECTION,
1950 HostAuth.RECORD_ID + "=?", new String[] {Long.toString(id)}, null, null, null);
1951 try {
1952 if (c.moveToFirst()) {
1953 HostAuth hostAuth = new HostAuth();
1954 hostAuth.restore(c);
1955 return hostAuth;
1956 }
1957 return null;
1958 } finally {
1959 c.close();
1960 }
1961 }
1962
1963 /**
1964 * Copy the Account and HostAuth tables from one database to another
1965 * @param fromDatabase the source database
1966 * @param toDatabase the destination database
1967 * @return the number of accounts copied, or -1 if an error occurred
1968 */
Makoto Onuki9dad9ad2011-06-20 18:10:10 -07001969 private static int copyAccountTables(SQLiteDatabase fromDatabase, SQLiteDatabase toDatabase) {
Marc Blank09931902011-05-06 12:07:39 -07001970 if (fromDatabase == null || toDatabase == null) return -1;
1971 int copyCount = 0;
1972 try {
1973 // Lock both databases; for the "from" database, we don't want anyone changing it from
1974 // under us; for the "to" database, we want to make the operation atomic
1975 fromDatabase.beginTransaction();
1976 toDatabase.beginTransaction();
1977 // Delete anything hanging around here
1978 toDatabase.delete(Account.TABLE_NAME, null, null);
1979 toDatabase.delete(HostAuth.TABLE_NAME, null, null);
1980 // Get our account cursor
1981 Cursor c = fromDatabase.query(Account.TABLE_NAME, Account.CONTENT_PROJECTION,
1982 null, null, null, null, null);
1983 boolean noErrors = true;
1984 try {
1985 // Loop through accounts, copying them and associated host auth's
1986 while (c.moveToNext()) {
1987 Account account = new Account();
1988 account.restore(c);
1989
1990 // Clear security sync key and sync key, as these were specific to the state of
1991 // the account, and we've reset that...
1992 // Clear policy key so that we can re-establish policies from the server
1993 // TODO This is pretty EAS specific, but there's a lot of that around
1994 account.mSecuritySyncKey = null;
1995 account.mSyncKey = null;
1996 account.mPolicyKey = 0;
1997
1998 // Copy host auth's and update foreign keys
1999 HostAuth hostAuth = restoreHostAuth(fromDatabase, account.mHostAuthKeyRecv);
2000 // The account might have gone away, though very unlikely
2001 if (hostAuth == null) continue;
2002 account.mHostAuthKeyRecv = toDatabase.insert(HostAuth.TABLE_NAME, null,
2003 hostAuth.toContentValues());
2004 // EAS accounts have no send HostAuth
2005 if (account.mHostAuthKeySend > 0) {
2006 hostAuth = restoreHostAuth(fromDatabase, account.mHostAuthKeySend);
2007 // Belt and suspenders; I can't imagine that this is possible, since we
2008 // checked the validity of the account above, and the database is now locked
2009 if (hostAuth == null) continue;
2010 account.mHostAuthKeySend = toDatabase.insert(HostAuth.TABLE_NAME, null,
2011 hostAuth.toContentValues());
2012 }
2013 // Now, create the account in the "to" database
2014 toDatabase.insert(Account.TABLE_NAME, null, account.toContentValues());
2015 copyCount++;
2016 }
2017 } catch (SQLiteException e) {
2018 noErrors = false;
2019 copyCount = -1;
2020 } finally {
2021 fromDatabase.endTransaction();
2022 if (noErrors) {
2023 // Say it's ok to commit
2024 toDatabase.setTransactionSuccessful();
2025 }
2026 toDatabase.endTransaction();
2027 c.close();
2028 }
2029 } catch (SQLiteException e) {
2030 copyCount = -1;
2031 }
2032 return copyCount;
2033 }
2034
Makoto Onuki9dad9ad2011-06-20 18:10:10 -07002035 private static SQLiteDatabase getBackupDatabase(Context context) {
Marc Blank09931902011-05-06 12:07:39 -07002036 DatabaseHelper helper = new DatabaseHelper(context, BACKUP_DATABASE_NAME);
2037 return helper.getWritableDatabase();
2038 }
2039
2040 /**
2041 * Backup account data, returning the number of accounts backed up
2042 */
Makoto Onuki9dad9ad2011-06-20 18:10:10 -07002043 private static int backupAccounts(Context context, SQLiteDatabase mainDatabase) {
2044 if (Email.DEBUG) {
2045 Log.d(TAG, "backupAccounts...");
2046 }
Marc Blank09931902011-05-06 12:07:39 -07002047 SQLiteDatabase backupDatabase = getBackupDatabase(context);
2048 try {
Makoto Onuki9dad9ad2011-06-20 18:10:10 -07002049 int numBackedUp = copyAccountTables(mainDatabase, backupDatabase);
2050 if (numBackedUp < 0) {
2051 Log.e(TAG, "Account backup failed!");
2052 } else if (Email.DEBUG) {
2053 Log.d(TAG, "Backed up " + numBackedUp + " accounts...");
2054 }
2055 return numBackedUp;
Marc Blank09931902011-05-06 12:07:39 -07002056 } finally {
2057 if (backupDatabase != null) {
2058 backupDatabase.close();
2059 }
2060 }
2061 }
2062
2063 /**
2064 * Restore account data, returning the number of accounts restored
2065 */
Makoto Onuki9dad9ad2011-06-20 18:10:10 -07002066 private static int restoreAccounts(Context context, SQLiteDatabase mainDatabase) {
2067 if (Email.DEBUG) {
2068 Log.d(TAG, "restoreAccounts...");
2069 }
Marc Blank09931902011-05-06 12:07:39 -07002070 SQLiteDatabase backupDatabase = getBackupDatabase(context);
2071 try {
Makoto Onuki9dad9ad2011-06-20 18:10:10 -07002072 int numRecovered = copyAccountTables(backupDatabase, mainDatabase);
2073 if (numRecovered > 0) {
2074 Log.e(TAG, "Recovered " + numRecovered + " accounts!");
2075 } else if (numRecovered < 0) {
2076 Log.e(TAG, "Account recovery failed?");
2077 } else if (Email.DEBUG) {
2078 Log.d(TAG, "No accounts to restore...");
2079 }
2080 return numRecovered;
Marc Blank09931902011-05-06 12:07:39 -07002081 } finally {
2082 if (backupDatabase != null) {
2083 backupDatabase.close();
2084 }
2085 }
2086 }
2087
Andrew Stadlerf3d5b202009-05-26 16:40:34 -07002088 @Override
2089 public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
Makoto Onukif678a8e2010-09-10 13:06:07 -07002090 // Handle this special case the fastest possible way
2091 if (uri == INTEGRITY_CHECK_URI) {
2092 checkDatabases();
2093 return 0;
Marc Blank09931902011-05-06 12:07:39 -07002094 } else if (uri == ACCOUNT_BACKUP_URI) {
Makoto Onuki9dad9ad2011-06-20 18:10:10 -07002095 return backupAccounts(getContext(), getDatabase(getContext()));
Makoto Onukif678a8e2010-09-10 13:06:07 -07002096 }
2097
Makoto Onuki261d6c32010-09-14 16:28:50 -07002098 // Notify all existing cursors, except for ACCOUNT_RESET_NEW_COUNT(_ID)
2099 Uri notificationUri = EmailContent.CONTENT_URI;
2100
Marc Blanke6a22df2010-12-16 12:28:16 -08002101 int match = findMatch(uri, "update");
Marc Blanka290f502009-06-15 14:40:06 -07002102 Context context = getContext();
Marc Blank1b9337e2010-09-23 09:19:44 -07002103 ContentResolver resolver = context.getContentResolver();
Marc Blankcba3a482009-08-05 17:31:50 -07002104 // See the comment at delete(), above
Marc Blanka867ebb2009-08-18 11:44:27 -07002105 SQLiteDatabase db = getDatabase(context);
Andrew Stadlerf3d5b202009-05-26 16:40:34 -07002106 int table = match >> BASE_SHIFT;
Andrew Stadler626f3e42009-06-01 14:34:16 -07002107 int result;
Marc Blanka290f502009-06-15 14:40:06 -07002108
Makoto Onuki5b0c2c72010-09-10 14:37:01 -07002109 // We do NOT allow setting of unreadCount/messageCount via the provider
2110 // These columns are maintained via triggers
2111 if (match == MAILBOX_ID || match == MAILBOX) {
2112 values.remove(MailboxColumns.UNREAD_COUNT);
2113 values.remove(MailboxColumns.MESSAGE_COUNT);
2114 }
Marc Blank0e1595c2009-11-18 17:11:33 -08002115
Marc Blank6e418aa2011-06-18 18:03:11 -07002116 ContentCache cache = mContentCaches[table];
Marc Blankfab77f12010-10-27 16:50:54 -07002117 String tableName = TABLE_NAMES[table];
Todd Kennedybf30f942011-05-06 11:31:10 -07002118 String id = "0";
Marc Blankfab77f12010-10-27 16:50:54 -07002119
Marc Blank0e1595c2009-11-18 17:11:33 -08002120 try {
Marc Blank6e418aa2011-06-18 18:03:11 -07002121outer:
Marc Blank0e1595c2009-11-18 17:11:33 -08002122 switch (match) {
2123 case MAILBOX_ID_ADD_TO_FIELD:
2124 case ACCOUNT_ID_ADD_TO_FIELD:
Marc Blank0e1595c2009-11-18 17:11:33 -08002125 id = uri.getPathSegments().get(1);
2126 String field = values.getAsString(EmailContent.FIELD_COLUMN_NAME);
2127 Long add = values.getAsLong(EmailContent.ADD_COLUMN_NAME);
2128 if (field == null || add == null) {
2129 throw new IllegalArgumentException("No field/add specified " + uri);
Marc Blankc0c9c332009-08-19 19:07:29 -07002130 }
Makoto Onuki9d5aaea2010-12-02 16:33:49 -08002131 ContentValues actualValues = new ContentValues();
Makoto Onukiedb8af82010-12-02 15:19:14 -08002132 if (cache != null) {
2133 cache.lock(id);
Marc Blank0e1595c2009-11-18 17:11:33 -08002134 }
Makoto Onukiedb8af82010-12-02 15:19:14 -08002135 try {
Makoto Onuki9d5aaea2010-12-02 16:33:49 -08002136 db.beginTransaction();
Makoto Onukiedb8af82010-12-02 15:19:14 -08002137 try {
Makoto Onuki9d5aaea2010-12-02 16:33:49 -08002138 Cursor c = db.query(tableName,
2139 new String[] {EmailContent.RECORD_ID, field},
2140 whereWithId(id, selection),
2141 selectionArgs, null, null, null);
2142 try {
2143 result = 0;
2144 String[] bind = new String[1];
2145 if (c.moveToNext()) {
2146 bind[0] = c.getString(0); // _id
2147 long value = c.getLong(1) + add;
2148 actualValues.put(field, value);
2149 result = db.update(tableName, actualValues, ID_EQUALS, bind);
2150 }
2151 db.setTransactionSuccessful();
2152 } finally {
2153 c.close();
Makoto Onukiedb8af82010-12-02 15:19:14 -08002154 }
2155 } finally {
Makoto Onuki9d5aaea2010-12-02 16:33:49 -08002156 db.endTransaction();
Makoto Onukiedb8af82010-12-02 15:19:14 -08002157 }
Makoto Onukiedb8af82010-12-02 15:19:14 -08002158 } finally {
2159 if (cache != null) {
2160 cache.unlock(id, actualValues);
2161 }
2162 }
Marc Blank0e1595c2009-11-18 17:11:33 -08002163 break;
Marc Blank0e1595c2009-11-18 17:11:33 -08002164 case SYNCED_MESSAGE_ID:
2165 case UPDATED_MESSAGE_ID:
Marc Blank1b9337e2010-09-23 09:19:44 -07002166 case MESSAGE_ID:
2167 case BODY_ID:
Marc Blank0e1595c2009-11-18 17:11:33 -08002168 case ATTACHMENT_ID:
2169 case MAILBOX_ID:
2170 case ACCOUNT_ID:
2171 case HOSTAUTH_ID:
Jorge Lugo5a3888f2011-06-01 10:09:26 -07002172 case QUICK_RESPONSE_ID:
Marc Blank6e418aa2011-06-18 18:03:11 -07002173 case POLICY_ID:
Marc Blank0e1595c2009-11-18 17:11:33 -08002174 id = uri.getPathSegments().get(1);
Marc Blankfab77f12010-10-27 16:50:54 -07002175 if (cache != null) {
2176 cache.lock(id);
Marc Blank0e1595c2009-11-18 17:11:33 -08002177 }
Marc Blankfab77f12010-10-27 16:50:54 -07002178 try {
2179 if (match == SYNCED_MESSAGE_ID) {
2180 // For synced messages, first copy the old message to the updated table
2181 // Note the insert or ignore semantics, guaranteeing that only the first
2182 // update will be reflected in the updated message table; therefore this
2183 // row will always have the "original" data
2184 db.execSQL(UPDATED_MESSAGE_INSERT + id);
2185 } else if (match == MESSAGE_ID) {
2186 db.execSQL(UPDATED_MESSAGE_DELETE + id);
2187 }
2188 result = db.update(tableName, values, whereWithId(id, selection),
2189 selectionArgs);
2190 } catch (SQLiteException e) {
2191 // Null out values (so they aren't cached) and re-throw
2192 values = null;
2193 throw e;
2194 } finally {
2195 if (cache != null) {
2196 cache.unlock(id, values);
2197 }
2198 }
Marc Blank09fd4d02010-08-09 17:48:53 -07002199 if (match == ATTACHMENT_ID) {
2200 if (values.containsKey(Attachment.FLAGS)) {
2201 int flags = values.getAsInteger(Attachment.FLAGS);
Ben Komalo32bed4b2011-08-23 18:02:11 -07002202 mAttachmentService.attachmentChanged(getContext(),
Marc Blank09fd4d02010-08-09 17:48:53 -07002203 Integer.parseInt(id), flags);
2204 }
2205 }
Marc Blank0e1595c2009-11-18 17:11:33 -08002206 break;
2207 case BODY:
2208 case MESSAGE:
2209 case UPDATED_MESSAGE:
2210 case ATTACHMENT:
2211 case MAILBOX:
2212 case ACCOUNT:
2213 case HOSTAUTH:
Marc Blank6e418aa2011-06-18 18:03:11 -07002214 case POLICY:
Marc Blankfab77f12010-10-27 16:50:54 -07002215 switch(match) {
Marc Blank6e418aa2011-06-18 18:03:11 -07002216 // To avoid invalidating the cache on updates, we execute them one at a
2217 // time using the XXX_ID uri; these are all executed atomically
Marc Blankfab77f12010-10-27 16:50:54 -07002218 case ACCOUNT:
2219 case MAILBOX:
2220 case HOSTAUTH:
Marc Blank6e418aa2011-06-18 18:03:11 -07002221 case POLICY:
2222 Cursor c = db.query(tableName, EmailContent.ID_PROJECTION,
2223 selection, selectionArgs, null, null, null);
2224 db.beginTransaction();
2225 result = 0;
2226 try {
2227 while (c.moveToNext()) {
2228 update(ContentUris.withAppendedId(
2229 uri, c.getLong(EmailContent.ID_PROJECTION_COLUMN)),
2230 values, null, null);
2231 result++;
2232 }
2233 db.setTransactionSuccessful();
2234 } finally {
2235 db.endTransaction();
2236 c.close();
2237 }
2238 break outer;
2239 // Any cached table other than those above should be invalidated here
2240 case MESSAGE:
Marc Blankfab77f12010-10-27 16:50:54 -07002241 // If we're doing some generic update, the whole cache needs to be
2242 // invalidated. This case should be quite rare
2243 cache.invalidate("Update", uri, selection);
Marc Blank6e418aa2011-06-18 18:03:11 -07002244 //$FALL-THROUGH$
2245 default:
2246 result = db.update(tableName, values, selection, selectionArgs);
2247 break outer;
Marc Blankfab77f12010-10-27 16:50:54 -07002248 }
Makoto Onuki261d6c32010-09-14 16:28:50 -07002249 case ACCOUNT_RESET_NEW_COUNT_ID:
2250 id = uri.getPathSegments().get(1);
Makoto Onukiedb8af82010-12-02 15:19:14 -08002251 if (cache != null) {
2252 cache.lock(id);
2253 }
Todd Kennedyc4cdb112011-05-03 14:42:26 -07002254 ContentValues newMessageCount = CONTENT_VALUES_RESET_NEW_MESSAGE_COUNT;
2255 if (values != null) {
2256 Long set = values.getAsLong(EmailContent.SET_COLUMN_NAME);
2257 if (set != null) {
2258 newMessageCount = new ContentValues();
2259 newMessageCount.put(Account.NEW_MESSAGE_COUNT, set);
2260 }
2261 }
Makoto Onukiedb8af82010-12-02 15:19:14 -08002262 try {
Todd Kennedyc4cdb112011-05-03 14:42:26 -07002263 result = db.update(tableName, newMessageCount,
Makoto Onukiedb8af82010-12-02 15:19:14 -08002264 whereWithId(id, selection), selectionArgs);
2265 } finally {
2266 if (cache != null) {
2267 cache.unlock(id, values);
2268 }
2269 }
Makoto Onuki261d6c32010-09-14 16:28:50 -07002270 notificationUri = Account.CONTENT_URI; // Only notify account cursors.
2271 break;
2272 case ACCOUNT_RESET_NEW_COUNT:
Marc Blankfab77f12010-10-27 16:50:54 -07002273 result = db.update(tableName, CONTENT_VALUES_RESET_NEW_MESSAGE_COUNT,
Makoto Onuki261d6c32010-09-14 16:28:50 -07002274 selection, selectionArgs);
Makoto Onukiedb8af82010-12-02 15:19:14 -08002275 // Affects all accounts. Just invalidate all account cache.
Makoto Onukiedb8af82010-12-02 15:19:14 -08002276 cache.invalidate("Reset all new counts", null, null);
Makoto Onuki261d6c32010-09-14 16:28:50 -07002277 notificationUri = Account.CONTENT_URI; // Only notify account cursors.
2278 break;
Marc Blank0e1595c2009-11-18 17:11:33 -08002279 default:
2280 throw new IllegalArgumentException("Unknown URI " + uri);
2281 }
2282 } catch (SQLiteException e) {
2283 checkDatabases();
2284 throw e;
Marc Blankfae47272009-05-29 14:24:34 -07002285 }
Marc Blanka290f502009-06-15 14:40:06 -07002286
Todd Kennedybf30f942011-05-06 11:31:10 -07002287 // Notify all notifier cursors
Todd Kennedye7fb4ac2011-05-11 15:29:24 -07002288 sendNotifierChange(getBaseNotificationUri(match), NOTIFICATION_OP_UPDATE, id);
Todd Kennedybf30f942011-05-06 11:31:10 -07002289
Marc Blank1b9337e2010-09-23 09:19:44 -07002290 resolver.notifyChange(notificationUri, null);
Andrew Stadler626f3e42009-06-01 14:34:16 -07002291 return result;
Marc Blankfae47272009-05-29 14:24:34 -07002292 }
Marc Blankf3743042009-06-27 12:14:14 -07002293
Todd Kennedybf30f942011-05-06 11:31:10 -07002294 /**
Todd Kennedye7fb4ac2011-05-11 15:29:24 -07002295 * Returns the base notification URI for the given content type.
2296 *
2297 * @param match The type of content that was modified.
2298 */
2299 private Uri getBaseNotificationUri(int match) {
2300 Uri baseUri = null;
2301 switch (match) {
2302 case MESSAGE:
2303 case MESSAGE_ID:
2304 case SYNCED_MESSAGE_ID:
2305 baseUri = Message.NOTIFIER_URI;
2306 break;
2307 case ACCOUNT:
2308 case ACCOUNT_ID:
2309 baseUri = Account.NOTIFIER_URI;
2310 break;
2311 }
2312 return baseUri;
2313 }
2314
2315 /**
Todd Kennedybf30f942011-05-06 11:31:10 -07002316 * Sends a change notification to any cursors observers of the given base URI. The final
2317 * notification URI is dynamically built to contain the specified information. It will be
2318 * of the format <<baseURI>>/<<op>>/<<id>>; where <<op>> and <<id>> are optional depending
2319 * upon the given values.
2320 * NOTE: If <<op>> is specified, notifications for <<baseURI>>/<<id>> will NOT be invoked.
2321 * If this is necessary, it can be added. However, due to the implementation of
2322 * {@link ContentObserver}, observers of <<baseURI>> will receive multiple notifications.
2323 *
2324 * @param baseUri The base URI to send notifications to. Must be able to take appended IDs.
2325 * @param op Optional operation to be appended to the URI.
2326 * @param id If a positive value, the ID to append to the base URI. Otherwise, no ID will be
2327 * appended to the base URI.
Marc Blankfae47272009-05-29 14:24:34 -07002328 */
Todd Kennedybf30f942011-05-06 11:31:10 -07002329 private void sendNotifierChange(Uri baseUri, String op, String id) {
Todd Kennedye7fb4ac2011-05-11 15:29:24 -07002330 if (baseUri == null) return;
2331
Todd Kennedybf30f942011-05-06 11:31:10 -07002332 final ContentResolver resolver = getContext().getContentResolver();
2333
2334 // Append the operation, if specified
2335 if (op != null) {
2336 baseUri = baseUri.buildUpon().appendEncodedPath(op).build();
2337 }
2338
2339 long longId = 0L;
2340 try {
2341 longId = Long.valueOf(id);
2342 } catch (NumberFormatException ignore) {}
2343 if (longId > 0) {
2344 resolver.notifyChange(ContentUris.withAppendedId(baseUri, longId), null);
2345 } else {
2346 resolver.notifyChange(baseUri, null);
2347 }
2348 }
2349
Marc Blank758a5322009-07-30 11:41:31 -07002350 @Override
Fred Quintana84969fb2009-06-01 12:55:50 -07002351 public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations)
Marc Blankb6493a02009-07-05 12:54:49 -07002352 throws OperationApplicationException {
Marc Blankcba3a482009-08-05 17:31:50 -07002353 Context context = getContext();
2354 SQLiteDatabase db = getDatabase(context);
Marc Blankfae47272009-05-29 14:24:34 -07002355 db.beginTransaction();
2356 try {
2357 ContentProviderResult[] results = super.applyBatch(operations);
2358 db.setTransactionSuccessful();
2359 return results;
2360 } finally {
2361 db.endTransaction();
Andrew Stadlerf3d5b202009-05-26 16:40:34 -07002362 }
2363 }
Makoto Onuki574854b2010-07-30 13:53:59 -07002364
Todd Kennedy22208772011-04-22 15:45:11 -07002365 /** Counts the number of messages in each mailbox, and updates the message count column. */
2366 @VisibleForTesting
2367 static void recalculateMessageCount(SQLiteDatabase db) {
Makoto Onuki574854b2010-07-30 13:53:59 -07002368 db.execSQL("update " + Mailbox.TABLE_NAME + " set " + MailboxColumns.MESSAGE_COUNT +
2369 "= (select count(*) from " + Message.TABLE_NAME +
2370 " where " + Message.MAILBOX_KEY + " = " +
2371 Mailbox.TABLE_NAME + "." + EmailContent.RECORD_ID + ")");
2372 }
Todd Kennedy22208772011-04-22 15:45:11 -07002373
Marc Blankaeee10e2011-04-27 17:12:06 -07002374 @VisibleForTesting
Todd Kennedybf30f942011-05-06 11:31:10 -07002375 @SuppressWarnings("deprecation")
Makoto Onuki9dad9ad2011-06-20 18:10:10 -07002376 static void convertPolicyFlagsToPolicyTable(SQLiteDatabase db) {
Marc Blankaeee10e2011-04-27 17:12:06 -07002377 Cursor c = db.query(Account.TABLE_NAME,
2378 new String[] {EmailContent.RECORD_ID /*0*/, AccountColumns.SECURITY_FLAGS /*1*/},
2379 AccountColumns.SECURITY_FLAGS + ">0", null, null, null, null);
2380 ContentValues cv = new ContentValues();
2381 String[] args = new String[1];
2382 while (c.moveToNext()) {
2383 long securityFlags = c.getLong(1 /*SECURITY_FLAGS*/);
2384 Policy policy = LegacyPolicySet.flagsToPolicy(securityFlags);
2385 long policyId = db.insert(Policy.TABLE_NAME, null, policy.toContentValues());
2386 cv.put(AccountColumns.POLICY_KEY, policyId);
2387 cv.putNull(AccountColumns.SECURITY_FLAGS);
2388 args[0] = Long.toString(c.getLong(0 /*RECORD_ID*/));
2389 db.update(Account.TABLE_NAME, cv, EmailContent.RECORD_ID + "=?", args);
2390 }
2391 }
2392
Todd Kennedy22208772011-04-22 15:45:11 -07002393 /** Upgrades the database from v17 to v18 */
2394 @VisibleForTesting
2395 static void upgradeFromVersion17ToVersion18(SQLiteDatabase db) {
2396 // Copy the displayName column to the serverId column. In v18 of the database,
2397 // we use the serverId for IMAP/POP3 mailboxes instead of overloading the
2398 // display name.
2399 //
2400 // For posterity; this is the command we're executing:
2401 //sqlite> UPDATE mailbox SET serverid=displayname WHERE mailbox._id in (
2402 // ...> SELECT mailbox._id FROM mailbox,account,hostauth WHERE
Ben Komalo2c57e702011-09-13 17:11:39 -07002403 // ...> (mailbox.parentkey isnull OR mailbox.parentkey=0) AND
2404 // ...> mailbox.accountkey=account._id AND
Todd Kennedy22208772011-04-22 15:45:11 -07002405 // ...> account.hostauthkeyrecv=hostauth._id AND
2406 // ...> (hostauth.protocol='imap' OR hostauth.protocol='pop3'));
2407 try {
2408 db.execSQL(
2409 "UPDATE " + Mailbox.TABLE_NAME + " SET "
2410 + MailboxColumns.SERVER_ID + "=" + MailboxColumns.DISPLAY_NAME
2411 + " WHERE "
2412 + Mailbox.TABLE_NAME + "." + MailboxColumns.ID + " IN ( SELECT "
2413 + Mailbox.TABLE_NAME + "." + MailboxColumns.ID + " FROM "
2414 + Mailbox.TABLE_NAME + "," + Account.TABLE_NAME + ","
2415 + HostAuth.TABLE_NAME + " WHERE "
Ben Komalo2c57e702011-09-13 17:11:39 -07002416 + "("
2417 + Mailbox.TABLE_NAME + "." + MailboxColumns.PARENT_KEY + " isnull OR "
2418 + Mailbox.TABLE_NAME + "." + MailboxColumns.PARENT_KEY + "=0 "
2419 + ") AND "
Todd Kennedy22208772011-04-22 15:45:11 -07002420 + Mailbox.TABLE_NAME + "." + MailboxColumns.ACCOUNT_KEY + "="
2421 + Account.TABLE_NAME + "." + AccountColumns.ID + " AND "
2422 + Account.TABLE_NAME + "." + AccountColumns.HOST_AUTH_KEY_RECV + "="
2423 + HostAuth.TABLE_NAME + "." + HostAuthColumns.ID + " AND ( "
2424 + HostAuth.TABLE_NAME + "." + HostAuthColumns.PROTOCOL + "='imap' OR "
2425 + HostAuth.TABLE_NAME + "." + HostAuthColumns.PROTOCOL + "='pop3' ) )");
2426 } catch (SQLException e) {
2427 // Shouldn't be needed unless we're debugging and interrupt the process
2428 Log.w(TAG, "Exception upgrading EmailProvider.db from 17 to 18 " + e);
2429 }
Marc Blank6e418aa2011-06-18 18:03:11 -07002430 ContentCache.invalidateAllCaches();
Todd Kennedy22208772011-04-22 15:45:11 -07002431 }
Todd Kennedya9ac20b2011-05-06 13:37:02 -07002432
2433 /** Upgrades the database from v20 to v21 */
2434 private static void upgradeFromVersion20ToVersion21(SQLiteDatabase db) {
2435 try {
2436 db.execSQL("alter table " + Mailbox.TABLE_NAME
2437 + " add column " + Mailbox.LAST_SEEN_MESSAGE_KEY + " integer;");
2438 } catch (SQLException e) {
2439 // Shouldn't be needed unless we're debugging and interrupt the process
2440 Log.w(TAG, "Exception upgrading EmailProvider.db from 20 to 21 " + e);
2441 }
2442 }
Marc Blankf3ff0ba2011-05-19 14:14:14 -07002443
2444 /**
2445 * Upgrade the database from v21 to v22
2446 * This entails creating AccountManager accounts for all pop3 and imap accounts
2447 */
2448
Marc Blank27a04fe2011-07-29 21:53:44 -07002449 private static final String[] V21_ACCOUNT_PROJECTION =
Marc Blank20b511f2011-08-02 14:01:58 -07002450 new String[] {AccountColumns.HOST_AUTH_KEY_RECV, AccountColumns.EMAIL_ADDRESS};
Marc Blank27a04fe2011-07-29 21:53:44 -07002451 private static final int V21_ACCOUNT_RECV = 0;
Marc Blank20b511f2011-08-02 14:01:58 -07002452 private static final int V21_ACCOUNT_EMAIL = 1;
Marc Blankf3ff0ba2011-05-19 14:14:14 -07002453
Marc Blank27a04fe2011-07-29 21:53:44 -07002454 private static final String[] V21_HOSTAUTH_PROJECTION =
Marc Blank20b511f2011-08-02 14:01:58 -07002455 new String[] {HostAuthColumns.PROTOCOL, HostAuthColumns.PASSWORD};
Marc Blank27a04fe2011-07-29 21:53:44 -07002456 private static final int V21_HOSTAUTH_PROTOCOL = 0;
Marc Blank20b511f2011-08-02 14:01:58 -07002457 private static final int V21_HOSTAUTH_PASSWORD = 1;
Marc Blank27a04fe2011-07-29 21:53:44 -07002458
2459 static private void createAccountManagerAccount(Context context, String login,
2460 String password) {
Marc Blankf3ff0ba2011-05-19 14:14:14 -07002461 AccountManager accountManager = AccountManager.get(context);
2462 android.accounts.Account amAccount =
Marc Blank27a04fe2011-07-29 21:53:44 -07002463 new android.accounts.Account(login, AccountManagerTypes.TYPE_POP_IMAP);
2464 accountManager.addAccountExplicitly(amAccount, password, null);
Marc Blankf3ff0ba2011-05-19 14:14:14 -07002465 ContentResolver.setIsSyncable(amAccount, EmailContent.AUTHORITY, 1);
2466 ContentResolver.setSyncAutomatically(amAccount, EmailContent.AUTHORITY, true);
2467 ContentResolver.setIsSyncable(amAccount, ContactsContract.AUTHORITY, 0);
2468 ContentResolver.setIsSyncable(amAccount, CalendarProviderStub.AUTHORITY, 0);
2469 }
2470
2471 @VisibleForTesting
2472 static void upgradeFromVersion21ToVersion22(SQLiteDatabase db, Context accountManagerContext) {
2473 try {
2474 // Loop through accounts, looking for pop/imap accounts
Marc Blank27a04fe2011-07-29 21:53:44 -07002475 Cursor accountCursor = db.query(Account.TABLE_NAME, V21_ACCOUNT_PROJECTION, null,
Marc Blankf3ff0ba2011-05-19 14:14:14 -07002476 null, null, null, null);
2477 try {
2478 String[] hostAuthArgs = new String[1];
2479 while (accountCursor.moveToNext()) {
Marc Blank27a04fe2011-07-29 21:53:44 -07002480 hostAuthArgs[0] = accountCursor.getString(V21_ACCOUNT_RECV);
Marc Blankf3ff0ba2011-05-19 14:14:14 -07002481 // Get the "receive" HostAuth for this account
2482 Cursor hostAuthCursor = db.query(HostAuth.TABLE_NAME,
Marc Blank27a04fe2011-07-29 21:53:44 -07002483 V21_HOSTAUTH_PROJECTION, HostAuth.RECORD_ID + "=?", hostAuthArgs,
Marc Blankf3ff0ba2011-05-19 14:14:14 -07002484 null, null, null);
2485 try {
2486 if (hostAuthCursor.moveToFirst()) {
Marc Blank27a04fe2011-07-29 21:53:44 -07002487 String protocol = hostAuthCursor.getString(V21_HOSTAUTH_PROTOCOL);
Marc Blankf3ff0ba2011-05-19 14:14:14 -07002488 // If this is a pop3 or imap account, create the account manager account
Marc Blank36aef9f2011-08-02 11:44:41 -07002489 if (HostAuth.SCHEME_IMAP.equals(protocol) ||
2490 HostAuth.SCHEME_POP3.equals(protocol)) {
Ben Komalo32bed4b2011-08-23 18:02:11 -07002491 if (Email.DEBUG) {
Marc Blank36aef9f2011-08-02 11:44:41 -07002492 Log.d(TAG, "Create AccountManager account for " + protocol +
Marc Blank20b511f2011-08-02 14:01:58 -07002493 "account: " +
2494 accountCursor.getString(V21_ACCOUNT_EMAIL));
Marc Blank36aef9f2011-08-02 11:44:41 -07002495 }
Marc Blank27a04fe2011-07-29 21:53:44 -07002496 createAccountManagerAccount(accountManagerContext,
Marc Blank20b511f2011-08-02 14:01:58 -07002497 accountCursor.getString(V21_ACCOUNT_EMAIL),
Marc Blank27a04fe2011-07-29 21:53:44 -07002498 hostAuthCursor.getString(V21_HOSTAUTH_PASSWORD));
Marc Blankf3ff0ba2011-05-19 14:14:14 -07002499 }
2500 }
2501 } finally {
2502 hostAuthCursor.close();
2503 }
2504 }
2505 } finally {
2506 accountCursor.close();
2507 }
2508 } catch (SQLException e) {
2509 // Shouldn't be needed unless we're debugging and interrupt the process
2510 Log.w(TAG, "Exception upgrading EmailProvider.db from 20 to 21 " + e);
2511 }
2512 }
Todd Kennedy9dcb72e2011-06-03 08:51:25 -07002513
2514 /** Upgrades the database from v22 to v23 */
2515 private static void upgradeFromVersion22ToVersion23(SQLiteDatabase db) {
2516 try {
2517 db.execSQL("alter table " + Mailbox.TABLE_NAME
2518 + " add column " + Mailbox.LAST_TOUCHED_TIME + " integer default 0;");
2519 } catch (SQLException e) {
2520 // Shouldn't be needed unless we're debugging and interrupt the process
2521 Log.w(TAG, "Exception upgrading EmailProvider.db from 22 to 23 " + e);
2522 }
2523 }
Ben Komalo313586c2011-06-07 11:39:16 -07002524
2525 /** Adds in a column for information about a client certificate to use. */
2526 private static void upgradeFromVersion23ToVersion24(SQLiteDatabase db) {
2527 try {
2528 db.execSQL("alter table " + HostAuth.TABLE_NAME
2529 + " add column " + HostAuth.CLIENT_CERT_ALIAS + " text;");
2530 } catch (SQLException e) {
2531 // Shouldn't be needed unless we're debugging and interrupt the process
2532 Log.w(TAG, "Exception upgrading EmailProvider.db from 23 to 24 " + e);
2533 }
2534 }
Jorge Lugo5a3888f2011-06-01 10:09:26 -07002535
2536 /** Upgrades the database from v24 to v25 by creating table for quick responses */
2537 private static void upgradeFromVersion24ToVersion25(SQLiteDatabase db) {
2538 try {
2539 createQuickResponseTable(db);
2540 } catch (SQLException e) {
2541 // Shouldn't be needed unless we're debugging and interrupt the process
2542 Log.w(TAG, "Exception upgrading EmailProvider.db from 24 to 25 " + e);
2543 }
2544 }
Marc Blank6e418aa2011-06-18 18:03:11 -07002545
Marc Blank27a04fe2011-07-29 21:53:44 -07002546 private static final String[] V25_ACCOUNT_PROJECTION =
Marc Blankdeb345a2011-06-23 10:22:25 -07002547 new String[] {AccountColumns.ID, AccountColumns.FLAGS, AccountColumns.HOST_AUTH_KEY_RECV};
Marc Blank27a04fe2011-07-29 21:53:44 -07002548 private static final int V25_ACCOUNT_ID = 0;
2549 private static final int V25_ACCOUNT_FLAGS = 1;
2550 private static final int V25_ACCOUNT_RECV = 2;
2551
2552 private static final String[] V25_HOSTAUTH_PROJECTION = new String[] {HostAuthColumns.PROTOCOL};
2553 private static final int V25_HOSTAUTH_PROTOCOL = 0;
Marc Blankdeb345a2011-06-23 10:22:25 -07002554
2555 /** Upgrades the database from v25 to v26 by adding FLAG_SUPPORTS_SEARCH to IMAP accounts */
2556 private static void upgradeFromVersion25ToVersion26(SQLiteDatabase db) {
2557 try {
2558 // Loop through accounts, looking for imap accounts
Marc Blank27a04fe2011-07-29 21:53:44 -07002559 Cursor accountCursor = db.query(Account.TABLE_NAME, V25_ACCOUNT_PROJECTION, null,
Marc Blankdeb345a2011-06-23 10:22:25 -07002560 null, null, null, null);
2561 ContentValues cv = new ContentValues();
2562 try {
2563 String[] hostAuthArgs = new String[1];
2564 while (accountCursor.moveToNext()) {
Marc Blank27a04fe2011-07-29 21:53:44 -07002565 hostAuthArgs[0] = accountCursor.getString(V25_ACCOUNT_RECV);
Marc Blankdeb345a2011-06-23 10:22:25 -07002566 // Get the "receive" HostAuth for this account
2567 Cursor hostAuthCursor = db.query(HostAuth.TABLE_NAME,
Marc Blank27a04fe2011-07-29 21:53:44 -07002568 V25_HOSTAUTH_PROJECTION, HostAuth.RECORD_ID + "=?", hostAuthArgs,
Marc Blankdeb345a2011-06-23 10:22:25 -07002569 null, null, null);
2570 try {
2571 if (hostAuthCursor.moveToFirst()) {
Marc Blank27a04fe2011-07-29 21:53:44 -07002572 String protocol = hostAuthCursor.getString(V25_HOSTAUTH_PROTOCOL);
Marc Blankdeb345a2011-06-23 10:22:25 -07002573 // If this is an imap account, add the search flag
Marc Blank27a04fe2011-07-29 21:53:44 -07002574 if (HostAuth.SCHEME_IMAP.equals(protocol)) {
2575 String id = accountCursor.getString(V25_ACCOUNT_ID);
2576 int flags = accountCursor.getInt(V25_ACCOUNT_FLAGS);
Marc Blankdeb345a2011-06-23 10:22:25 -07002577 cv.put(AccountColumns.FLAGS, flags | Account.FLAGS_SUPPORTS_SEARCH);
2578 db.update(Account.TABLE_NAME, cv, Account.RECORD_ID + "=?",
2579 new String[] {id});
2580 }
2581 }
2582 } finally {
2583 hostAuthCursor.close();
2584 }
2585 }
2586 } finally {
2587 accountCursor.close();
2588 }
2589 } catch (SQLException e) {
2590 // Shouldn't be needed unless we're debugging and interrupt the process
2591 Log.w(TAG, "Exception upgrading EmailProvider.db from 25 to 26 " + e);
2592 }
2593 }
2594
Marc Blank6e418aa2011-06-18 18:03:11 -07002595 /**
2596 * For testing purposes, check whether a given row is cached
2597 * @param baseUri the base uri of the EmailContent
2598 * @param id the row id of the EmailContent
2599 * @return whether or not the row is currently cached
2600 */
2601 @VisibleForTesting
2602 protected boolean isCached(Uri baseUri, long id) {
2603 int match = findMatch(baseUri, "isCached");
2604 int table = match >> BASE_SHIFT;
2605 ContentCache cache = mContentCaches[table];
2606 if (cache == null) return false;
2607 Cursor cc = cache.get(Long.toString(id));
2608 return (cc != null);
2609 }
Ben Komalo32bed4b2011-08-23 18:02:11 -07002610
2611 public static interface AttachmentService {
2612 /**
2613 * Notify the service that an attachment has changed.
2614 */
2615 void attachmentChanged(Context context, long id, int flags);
2616 }
2617
2618 private final AttachmentService DEFAULT_ATTACHMENT_SERVICE = new AttachmentService() {
2619 @Override
2620 public void attachmentChanged(Context context, long id, int flags) {
2621 // The default implementation delegates to the real service.
2622 AttachmentDownloadService.attachmentChanged(context, id, flags);
2623 }
2624 };
2625 private AttachmentService mAttachmentService = DEFAULT_ATTACHMENT_SERVICE;
2626
2627 /**
2628 * Injects a custom attachment service handler. If null is specified, will reset to the
2629 * default service.
2630 */
2631 public void injectAttachmentService(AttachmentService as) {
2632 mAttachmentService = (as == null) ? DEFAULT_ATTACHMENT_SERVICE : as;
2633 }
Andrew Stadlerf3d5b202009-05-26 16:40:34 -07002634}