EmailProvider.java revision 22208771b7b39c5d131372ba6bc45ab23cc22232
1/* 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 19import com.android.email.Email; 20import com.android.email.provider.ContentCache.CacheToken; 21import com.android.email.service.AttachmentDownloadService; 22import com.android.emailcommon.AccountManagerTypes; 23import com.android.emailcommon.provider.EmailContent; 24import com.android.emailcommon.provider.EmailContent.Account; 25import com.android.emailcommon.provider.EmailContent.AccountColumns; 26import com.android.emailcommon.provider.EmailContent.Attachment; 27import com.android.emailcommon.provider.EmailContent.AttachmentColumns; 28import com.android.emailcommon.provider.EmailContent.Body; 29import com.android.emailcommon.provider.EmailContent.BodyColumns; 30import com.android.emailcommon.provider.EmailContent.HostAuth; 31import com.android.emailcommon.provider.EmailContent.HostAuthColumns; 32import com.android.emailcommon.provider.EmailContent.Mailbox; 33import com.android.emailcommon.provider.EmailContent.MailboxColumns; 34import com.android.emailcommon.provider.EmailContent.Message; 35import com.android.emailcommon.provider.EmailContent.MessageColumns; 36import com.android.emailcommon.provider.EmailContent.SyncColumns; 37import com.google.common.annotations.VisibleForTesting; 38 39import android.accounts.AccountManager; 40import android.content.ContentProvider; 41import android.content.ContentProviderOperation; 42import android.content.ContentProviderResult; 43import android.content.ContentResolver; 44import android.content.ContentUris; 45import android.content.ContentValues; 46import android.content.Context; 47import android.content.OperationApplicationException; 48import android.content.UriMatcher; 49import android.database.Cursor; 50import android.database.MatrixCursor; 51import android.database.SQLException; 52import android.database.sqlite.SQLiteDatabase; 53import android.database.sqlite.SQLiteException; 54import android.database.sqlite.SQLiteOpenHelper; 55import android.net.Uri; 56import android.util.Log; 57 58import java.io.File; 59import java.util.ArrayList; 60 61public class EmailProvider extends ContentProvider { 62 63 private static final String TAG = "EmailProvider"; 64 65 protected static final String DATABASE_NAME = "EmailProvider.db"; 66 protected static final String BODY_DATABASE_NAME = "EmailProviderBody.db"; 67 68 public static final String ACTION_ATTACHMENT_UPDATED = "com.android.email.ATTACHMENT_UPDATED"; 69 public static final String ATTACHMENT_UPDATED_EXTRA_FLAGS = 70 "com.android.email.ATTACHMENT_UPDATED_FLAGS"; 71 72 public static final String EMAIL_MESSAGE_MIME_TYPE = 73 "vnd.android.cursor.item/email-message"; 74 public static final String EMAIL_ATTACHMENT_MIME_TYPE = 75 "vnd.android.cursor.item/email-attachment"; 76 77 public static final Uri INTEGRITY_CHECK_URI = 78 Uri.parse("content://" + EmailContent.AUTHORITY + "/integrityCheck"); 79 80 // Definitions for our queries looking for orphaned messages 81 private static final String[] ORPHANS_PROJECTION 82 = new String[] {MessageColumns.ID, MessageColumns.MAILBOX_KEY}; 83 private static final int ORPHANS_ID = 0; 84 private static final int ORPHANS_MAILBOX_KEY = 1; 85 86 private static final String WHERE_ID = EmailContent.RECORD_ID + "=?"; 87 88 // We'll cache the following four tables; sizes are best estimates of effective values 89 private static final ContentCache sCacheAccount = 90 new ContentCache("Account", Account.CONTENT_PROJECTION, 4); 91 private static final ContentCache sCacheHostAuth = 92 new ContentCache("HostAuth", HostAuth.CONTENT_PROJECTION, 8); 93 /*package*/ static final ContentCache sCacheMailbox = 94 new ContentCache("Mailbox", Mailbox.CONTENT_PROJECTION, 8); 95 private static final ContentCache sCacheMessage = 96 new ContentCache("Message", Message.CONTENT_PROJECTION, 3); 97 98 // Any changes to the database format *must* include update-in-place code. 99 // Original version: 3 100 // Version 4: Database wipe required; changing AccountManager interface w/Exchange 101 // Version 5: Database wipe required; changing AccountManager interface w/Exchange 102 // Version 6: Adding Message.mServerTimeStamp column 103 // Version 7: Replace the mailbox_delete trigger with a version that removes orphaned messages 104 // from the Message_Deletes and Message_Updates tables 105 // Version 8: Add security flags column to accounts table 106 // Version 9: Add security sync key and signature to accounts table 107 // Version 10: Add meeting info to message table 108 // Version 11: Add content and flags to attachment table 109 // Version 12: Add content_bytes to attachment table. content is deprecated. 110 // Version 13: Add messageCount to Mailbox table. 111 // Version 14: Add snippet to Message table 112 // Version 15: Fix upgrade problem in version 14. 113 // Version 16: Add accountKey to Attachment table 114 // Version 17: Add parentKey to Mailbox table 115 // Version 18: Copy Mailbox.displayName to Mailbox.serverId for all IMAP & POP3 mailboxes. 116 // Column Mailbox.serverId is used for the server-side pathname of a mailbox. 117 public static final int DATABASE_VERSION = 18; 118 119 // Any changes to the database format *must* include update-in-place code. 120 // Original version: 2 121 // Version 3: Add "sourceKey" column 122 // Version 4: Database wipe required; changing AccountManager interface w/Exchange 123 // Version 5: Database wipe required; changing AccountManager interface w/Exchange 124 // Version 6: Adding Body.mIntroText column 125 public static final int BODY_DATABASE_VERSION = 6; 126 127 private static final int ACCOUNT_BASE = 0; 128 private static final int ACCOUNT = ACCOUNT_BASE; 129 private static final int ACCOUNT_ID = ACCOUNT_BASE + 1; 130 private static final int ACCOUNT_ID_ADD_TO_FIELD = ACCOUNT_BASE + 2; 131 private static final int ACCOUNT_RESET_NEW_COUNT = ACCOUNT_BASE + 3; 132 private static final int ACCOUNT_RESET_NEW_COUNT_ID = ACCOUNT_BASE + 4; 133 134 private static final int MAILBOX_BASE = 0x1000; 135 private static final int MAILBOX = MAILBOX_BASE; 136 private static final int MAILBOX_ID = MAILBOX_BASE + 1; 137 private static final int MAILBOX_ID_ADD_TO_FIELD = MAILBOX_BASE + 2; 138 139 private static final int MESSAGE_BASE = 0x2000; 140 private static final int MESSAGE = MESSAGE_BASE; 141 private static final int MESSAGE_ID = MESSAGE_BASE + 1; 142 private static final int SYNCED_MESSAGE_ID = MESSAGE_BASE + 2; 143 144 private static final int ATTACHMENT_BASE = 0x3000; 145 private static final int ATTACHMENT = ATTACHMENT_BASE; 146 private static final int ATTACHMENT_ID = ATTACHMENT_BASE + 1; 147 private static final int ATTACHMENTS_MESSAGE_ID = ATTACHMENT_BASE + 2; 148 149 private static final int HOSTAUTH_BASE = 0x4000; 150 private static final int HOSTAUTH = HOSTAUTH_BASE; 151 private static final int HOSTAUTH_ID = HOSTAUTH_BASE + 1; 152 153 private static final int UPDATED_MESSAGE_BASE = 0x5000; 154 private static final int UPDATED_MESSAGE = UPDATED_MESSAGE_BASE; 155 private static final int UPDATED_MESSAGE_ID = UPDATED_MESSAGE_BASE + 1; 156 157 private static final int DELETED_MESSAGE_BASE = 0x6000; 158 private static final int DELETED_MESSAGE = DELETED_MESSAGE_BASE; 159 private static final int DELETED_MESSAGE_ID = DELETED_MESSAGE_BASE + 1; 160 161 // MUST ALWAYS EQUAL THE LAST OF THE PREVIOUS BASE CONSTANTS 162 private static final int LAST_EMAIL_PROVIDER_DB_BASE = DELETED_MESSAGE_BASE; 163 164 // DO NOT CHANGE BODY_BASE!! 165 private static final int BODY_BASE = LAST_EMAIL_PROVIDER_DB_BASE + 0x1000; 166 private static final int BODY = BODY_BASE; 167 private static final int BODY_ID = BODY_BASE + 1; 168 169 private static final int BASE_SHIFT = 12; // 12 bits to the base type: 0, 0x1000, 0x2000, etc. 170 171 // TABLE_NAMES MUST remain in the order of the BASE constants above (e.g. ACCOUNT_BASE = 0x0000, 172 // MESSAGE_BASE = 0x1000, etc.) 173 private static final String[] TABLE_NAMES = { 174 EmailContent.Account.TABLE_NAME, 175 EmailContent.Mailbox.TABLE_NAME, 176 EmailContent.Message.TABLE_NAME, 177 EmailContent.Attachment.TABLE_NAME, 178 EmailContent.HostAuth.TABLE_NAME, 179 EmailContent.Message.UPDATED_TABLE_NAME, 180 EmailContent.Message.DELETED_TABLE_NAME, 181 EmailContent.Body.TABLE_NAME 182 }; 183 184 // CONTENT_CACHES MUST remain in the order of the BASE constants above 185 private static final ContentCache[] CONTENT_CACHES = { 186 sCacheAccount, 187 sCacheMailbox, 188 sCacheMessage, 189 null, 190 sCacheHostAuth, 191 null, 192 null, 193 null}; 194 195 private static final UriMatcher sURIMatcher = new UriMatcher(UriMatcher.NO_MATCH); 196 197 /** 198 * Let's only generate these SQL strings once, as they are used frequently 199 * Note that this isn't relevant for table creation strings, since they are used only once 200 */ 201 private static final String UPDATED_MESSAGE_INSERT = "insert or ignore into " + 202 Message.UPDATED_TABLE_NAME + " select * from " + Message.TABLE_NAME + " where " + 203 EmailContent.RECORD_ID + '='; 204 205 private static final String UPDATED_MESSAGE_DELETE = "delete from " + 206 Message.UPDATED_TABLE_NAME + " where " + EmailContent.RECORD_ID + '='; 207 208 private static final String DELETED_MESSAGE_INSERT = "insert or replace into " + 209 Message.DELETED_TABLE_NAME + " select * from " + Message.TABLE_NAME + " where " + 210 EmailContent.RECORD_ID + '='; 211 212 private static final String DELETE_ORPHAN_BODIES = "delete from " + Body.TABLE_NAME + 213 " where " + BodyColumns.MESSAGE_KEY + " in " + "(select " + BodyColumns.MESSAGE_KEY + 214 " from " + Body.TABLE_NAME + " except select " + EmailContent.RECORD_ID + " from " + 215 Message.TABLE_NAME + ')'; 216 217 private static final String DELETE_BODY = "delete from " + Body.TABLE_NAME + 218 " where " + BodyColumns.MESSAGE_KEY + '='; 219 220 private static final String ID_EQUALS = EmailContent.RECORD_ID + "=?"; 221 222 private static final String TRIGGER_MAILBOX_DELETE = 223 "create trigger mailbox_delete before delete on " + Mailbox.TABLE_NAME + 224 " begin" + 225 " delete from " + Message.TABLE_NAME + 226 " where " + MessageColumns.MAILBOX_KEY + "=old." + EmailContent.RECORD_ID + 227 "; delete from " + Message.UPDATED_TABLE_NAME + 228 " where " + MessageColumns.MAILBOX_KEY + "=old." + EmailContent.RECORD_ID + 229 "; delete from " + Message.DELETED_TABLE_NAME + 230 " where " + MessageColumns.MAILBOX_KEY + "=old." + EmailContent.RECORD_ID + 231 "; end"; 232 233 private static final ContentValues CONTENT_VALUES_RESET_NEW_MESSAGE_COUNT; 234 235 public static final String MESSAGE_URI_PARAMETER_MAILBOX_ID = "mailboxId"; 236 237 static { 238 // Email URI matching table 239 UriMatcher matcher = sURIMatcher; 240 241 // All accounts 242 matcher.addURI(EmailContent.AUTHORITY, "account", ACCOUNT); 243 // A specific account 244 // insert into this URI causes a mailbox to be added to the account 245 matcher.addURI(EmailContent.AUTHORITY, "account/#", ACCOUNT_ID); 246 247 // Special URI to reset the new message count. Only update works, and content values 248 // will be ignored. 249 matcher.addURI(EmailContent.AUTHORITY, "resetNewMessageCount", 250 ACCOUNT_RESET_NEW_COUNT); 251 matcher.addURI(EmailContent.AUTHORITY, "resetNewMessageCount/#", 252 ACCOUNT_RESET_NEW_COUNT_ID); 253 254 // All mailboxes 255 matcher.addURI(EmailContent.AUTHORITY, "mailbox", MAILBOX); 256 // A specific mailbox 257 // insert into this URI causes a message to be added to the mailbox 258 // ** NOTE For now, the accountKey must be set manually in the values! 259 matcher.addURI(EmailContent.AUTHORITY, "mailbox/#", MAILBOX_ID); 260 261 // All messages 262 matcher.addURI(EmailContent.AUTHORITY, "message", MESSAGE); 263 // A specific message 264 // insert into this URI causes an attachment to be added to the message 265 matcher.addURI(EmailContent.AUTHORITY, "message/#", MESSAGE_ID); 266 267 // A specific attachment 268 matcher.addURI(EmailContent.AUTHORITY, "attachment", ATTACHMENT); 269 // A specific attachment (the header information) 270 matcher.addURI(EmailContent.AUTHORITY, "attachment/#", ATTACHMENT_ID); 271 // The attachments of a specific message (query only) (insert & delete TBD) 272 matcher.addURI(EmailContent.AUTHORITY, "attachment/message/#", 273 ATTACHMENTS_MESSAGE_ID); 274 275 // All mail bodies 276 matcher.addURI(EmailContent.AUTHORITY, "body", BODY); 277 // A specific mail body 278 matcher.addURI(EmailContent.AUTHORITY, "body/#", BODY_ID); 279 280 // All hostauth records 281 matcher.addURI(EmailContent.AUTHORITY, "hostauth", HOSTAUTH); 282 // A specific hostauth 283 matcher.addURI(EmailContent.AUTHORITY, "hostauth/#", HOSTAUTH_ID); 284 285 // Atomically a constant value to a particular field of a mailbox/account 286 matcher.addURI(EmailContent.AUTHORITY, "mailboxIdAddToField/#", 287 MAILBOX_ID_ADD_TO_FIELD); 288 matcher.addURI(EmailContent.AUTHORITY, "accountIdAddToField/#", 289 ACCOUNT_ID_ADD_TO_FIELD); 290 291 /** 292 * THIS URI HAS SPECIAL SEMANTICS 293 * ITS USE IS INTENDED FOR THE UI APPLICATION TO MARK CHANGES THAT NEED TO BE SYNCED BACK 294 * TO A SERVER VIA A SYNC ADAPTER 295 */ 296 matcher.addURI(EmailContent.AUTHORITY, "syncedMessage/#", SYNCED_MESSAGE_ID); 297 298 /** 299 * THE URIs BELOW THIS POINT ARE INTENDED TO BE USED BY SYNC ADAPTERS ONLY 300 * THEY REFER TO DATA CREATED AND MAINTAINED BY CALLS TO THE SYNCED_MESSAGE_ID URI 301 * BY THE UI APPLICATION 302 */ 303 // All deleted messages 304 matcher.addURI(EmailContent.AUTHORITY, "deletedMessage", DELETED_MESSAGE); 305 // A specific deleted message 306 matcher.addURI(EmailContent.AUTHORITY, "deletedMessage/#", DELETED_MESSAGE_ID); 307 308 // All updated messages 309 matcher.addURI(EmailContent.AUTHORITY, "updatedMessage", UPDATED_MESSAGE); 310 // A specific updated message 311 matcher.addURI(EmailContent.AUTHORITY, "updatedMessage/#", UPDATED_MESSAGE_ID); 312 313 CONTENT_VALUES_RESET_NEW_MESSAGE_COUNT = new ContentValues(); 314 CONTENT_VALUES_RESET_NEW_MESSAGE_COUNT.put(Account.NEW_MESSAGE_COUNT, 0); 315 } 316 317 318 /** 319 * Wrap the UriMatcher call so we can throw a runtime exception if an unknown Uri is passed in 320 * @param uri the Uri to match 321 * @return the match value 322 */ 323 private static int findMatch(Uri uri, String methodName) { 324 int match = sURIMatcher.match(uri); 325 if (match < 0) { 326 throw new IllegalArgumentException("Unknown uri: " + uri); 327 } else if (Email.LOGD) { 328 Log.v(TAG, methodName + ": uri=" + uri + ", match is " + match); 329 } 330 return match; 331 } 332 333 /* 334 * Internal helper method for index creation. 335 * Example: 336 * "create index message_" + MessageColumns.FLAG_READ 337 * + " on " + Message.TABLE_NAME + " (" + MessageColumns.FLAG_READ + ");" 338 */ 339 /* package */ 340 static String createIndex(String tableName, String columnName) { 341 return "create index " + tableName.toLowerCase() + '_' + columnName 342 + " on " + tableName + " (" + columnName + ");"; 343 } 344 345 static void createMessageTable(SQLiteDatabase db) { 346 String messageColumns = MessageColumns.DISPLAY_NAME + " text, " 347 + MessageColumns.TIMESTAMP + " integer, " 348 + MessageColumns.SUBJECT + " text, " 349 + MessageColumns.FLAG_READ + " integer, " 350 + MessageColumns.FLAG_LOADED + " integer, " 351 + MessageColumns.FLAG_FAVORITE + " integer, " 352 + MessageColumns.FLAG_ATTACHMENT + " integer, " 353 + MessageColumns.FLAGS + " integer, " 354 + MessageColumns.CLIENT_ID + " integer, " 355 + MessageColumns.MESSAGE_ID + " text, " 356 + MessageColumns.MAILBOX_KEY + " integer, " 357 + MessageColumns.ACCOUNT_KEY + " integer, " 358 + MessageColumns.FROM_LIST + " text, " 359 + MessageColumns.TO_LIST + " text, " 360 + MessageColumns.CC_LIST + " text, " 361 + MessageColumns.BCC_LIST + " text, " 362 + MessageColumns.REPLY_TO_LIST + " text, " 363 + MessageColumns.MEETING_INFO + " text, " 364 + MessageColumns.SNIPPET + " text" 365 + ");"; 366 367 // This String and the following String MUST have the same columns, except for the type 368 // of those columns! 369 String createString = " (" + EmailContent.RECORD_ID + " integer primary key autoincrement, " 370 + SyncColumns.SERVER_ID + " text, " 371 + SyncColumns.SERVER_TIMESTAMP + " integer, " 372 + messageColumns; 373 374 // For the updated and deleted tables, the id is assigned, but we do want to keep track 375 // of the ORDER of updates using an autoincrement primary key. We use the DATA column 376 // at this point; it has no other function 377 String altCreateString = " (" + EmailContent.RECORD_ID + " integer unique, " 378 + SyncColumns.SERVER_ID + " text, " 379 + SyncColumns.SERVER_TIMESTAMP + " integer, " 380 + messageColumns; 381 382 // The three tables have the same schema 383 db.execSQL("create table " + Message.TABLE_NAME + createString); 384 db.execSQL("create table " + Message.UPDATED_TABLE_NAME + altCreateString); 385 db.execSQL("create table " + Message.DELETED_TABLE_NAME + altCreateString); 386 387 String indexColumns[] = { 388 MessageColumns.TIMESTAMP, 389 MessageColumns.FLAG_READ, 390 MessageColumns.FLAG_LOADED, 391 MessageColumns.MAILBOX_KEY, 392 SyncColumns.SERVER_ID 393 }; 394 395 for (String columnName : indexColumns) { 396 db.execSQL(createIndex(Message.TABLE_NAME, columnName)); 397 } 398 399 // Deleting a Message deletes all associated Attachments 400 // Deleting the associated Body cannot be done in a trigger, because the Body is stored 401 // in a separate database, and trigger cannot operate on attached databases. 402 db.execSQL("create trigger message_delete before delete on " + Message.TABLE_NAME + 403 " begin delete from " + Attachment.TABLE_NAME + 404 " where " + AttachmentColumns.MESSAGE_KEY + "=old." + EmailContent.RECORD_ID + 405 "; end"); 406 407 // Add triggers to keep unread count accurate per mailbox 408 409 // NOTE: SQLite's before triggers are not safe when recursive triggers are involved. 410 // Use caution when changing them. 411 412 // Insert a message; if flagRead is zero, add to the unread count of the message's mailbox 413 db.execSQL("create trigger unread_message_insert before insert on " + Message.TABLE_NAME + 414 " when NEW." + MessageColumns.FLAG_READ + "=0" + 415 " begin update " + Mailbox.TABLE_NAME + " set " + MailboxColumns.UNREAD_COUNT + 416 '=' + MailboxColumns.UNREAD_COUNT + "+1" + 417 " where " + EmailContent.RECORD_ID + "=NEW." + MessageColumns.MAILBOX_KEY + 418 "; end"); 419 420 // Delete a message; if flagRead is zero, decrement the unread count of the msg's mailbox 421 db.execSQL("create trigger unread_message_delete before delete on " + Message.TABLE_NAME + 422 " when OLD." + MessageColumns.FLAG_READ + "=0" + 423 " begin update " + Mailbox.TABLE_NAME + " set " + MailboxColumns.UNREAD_COUNT + 424 '=' + MailboxColumns.UNREAD_COUNT + "-1" + 425 " where " + EmailContent.RECORD_ID + "=OLD." + MessageColumns.MAILBOX_KEY + 426 "; end"); 427 428 // Change a message's mailbox 429 db.execSQL("create trigger unread_message_move before update of " + 430 MessageColumns.MAILBOX_KEY + " on " + Message.TABLE_NAME + 431 " when OLD." + MessageColumns.FLAG_READ + "=0" + 432 " begin update " + Mailbox.TABLE_NAME + " set " + MailboxColumns.UNREAD_COUNT + 433 '=' + MailboxColumns.UNREAD_COUNT + "-1" + 434 " where " + EmailContent.RECORD_ID + "=OLD." + MessageColumns.MAILBOX_KEY + 435 "; update " + Mailbox.TABLE_NAME + " set " + MailboxColumns.UNREAD_COUNT + 436 '=' + MailboxColumns.UNREAD_COUNT + "+1" + 437 " where " + EmailContent.RECORD_ID + "=NEW." + MessageColumns.MAILBOX_KEY + 438 "; end"); 439 440 // Change a message's read state 441 db.execSQL("create trigger unread_message_read before update of " + 442 MessageColumns.FLAG_READ + " on " + Message.TABLE_NAME + 443 " when OLD." + MessageColumns.FLAG_READ + "!=NEW." + MessageColumns.FLAG_READ + 444 " begin update " + Mailbox.TABLE_NAME + " set " + MailboxColumns.UNREAD_COUNT + 445 '=' + MailboxColumns.UNREAD_COUNT + "+ case OLD." + MessageColumns.FLAG_READ + 446 " when 0 then -1 else 1 end" + 447 " where " + EmailContent.RECORD_ID + "=OLD." + MessageColumns.MAILBOX_KEY + 448 "; end"); 449 450 // Add triggers to update message count per mailbox 451 452 // Insert a message. 453 db.execSQL("create trigger message_count_message_insert after insert on " + 454 Message.TABLE_NAME + 455 " begin update " + Mailbox.TABLE_NAME + " set " + MailboxColumns.MESSAGE_COUNT + 456 '=' + MailboxColumns.MESSAGE_COUNT + "+1" + 457 " where " + EmailContent.RECORD_ID + "=NEW." + MessageColumns.MAILBOX_KEY + 458 "; end"); 459 460 // Delete a message; if flagRead is zero, decrement the unread count of the msg's mailbox 461 db.execSQL("create trigger message_count_message_delete after delete on " + 462 Message.TABLE_NAME + 463 " begin update " + Mailbox.TABLE_NAME + " set " + MailboxColumns.MESSAGE_COUNT + 464 '=' + MailboxColumns.MESSAGE_COUNT + "-1" + 465 " where " + EmailContent.RECORD_ID + "=OLD." + MessageColumns.MAILBOX_KEY + 466 "; end"); 467 468 // Change a message's mailbox 469 db.execSQL("create trigger message_count_message_move after update of " + 470 MessageColumns.MAILBOX_KEY + " on " + Message.TABLE_NAME + 471 " begin update " + Mailbox.TABLE_NAME + " set " + MailboxColumns.MESSAGE_COUNT + 472 '=' + MailboxColumns.MESSAGE_COUNT + "-1" + 473 " where " + EmailContent.RECORD_ID + "=OLD." + MessageColumns.MAILBOX_KEY + 474 "; update " + Mailbox.TABLE_NAME + " set " + MailboxColumns.MESSAGE_COUNT + 475 '=' + MailboxColumns.MESSAGE_COUNT + "+1" + 476 " where " + EmailContent.RECORD_ID + "=NEW." + MessageColumns.MAILBOX_KEY + 477 "; end"); 478 } 479 480 static void resetMessageTable(SQLiteDatabase db, int oldVersion, int newVersion) { 481 try { 482 db.execSQL("drop table " + Message.TABLE_NAME); 483 db.execSQL("drop table " + Message.UPDATED_TABLE_NAME); 484 db.execSQL("drop table " + Message.DELETED_TABLE_NAME); 485 } catch (SQLException e) { 486 } 487 createMessageTable(db); 488 } 489 490 static void createAccountTable(SQLiteDatabase db) { 491 String s = " (" + EmailContent.RECORD_ID + " integer primary key autoincrement, " 492 + AccountColumns.DISPLAY_NAME + " text, " 493 + AccountColumns.EMAIL_ADDRESS + " text, " 494 + AccountColumns.SYNC_KEY + " text, " 495 + AccountColumns.SYNC_LOOKBACK + " integer, " 496 + AccountColumns.SYNC_INTERVAL + " text, " 497 + AccountColumns.HOST_AUTH_KEY_RECV + " integer, " 498 + AccountColumns.HOST_AUTH_KEY_SEND + " integer, " 499 + AccountColumns.FLAGS + " integer, " 500 + AccountColumns.IS_DEFAULT + " integer, " 501 + AccountColumns.COMPATIBILITY_UUID + " text, " 502 + AccountColumns.SENDER_NAME + " text, " 503 + AccountColumns.RINGTONE_URI + " text, " 504 + AccountColumns.PROTOCOL_VERSION + " text, " 505 + AccountColumns.NEW_MESSAGE_COUNT + " integer, " 506 + AccountColumns.SECURITY_FLAGS + " integer, " 507 + AccountColumns.SECURITY_SYNC_KEY + " text, " 508 + AccountColumns.SIGNATURE + " text " 509 + ");"; 510 db.execSQL("create table " + Account.TABLE_NAME + s); 511 // Deleting an account deletes associated Mailboxes and HostAuth's 512 db.execSQL("create trigger account_delete before delete on " + Account.TABLE_NAME + 513 " begin delete from " + Mailbox.TABLE_NAME + 514 " where " + MailboxColumns.ACCOUNT_KEY + "=old." + EmailContent.RECORD_ID + 515 "; delete from " + HostAuth.TABLE_NAME + 516 " where " + EmailContent.RECORD_ID + "=old." + AccountColumns.HOST_AUTH_KEY_RECV + 517 "; delete from " + HostAuth.TABLE_NAME + 518 " where " + EmailContent.RECORD_ID + "=old." + AccountColumns.HOST_AUTH_KEY_SEND + 519 "; end"); 520 } 521 522 static void resetAccountTable(SQLiteDatabase db, int oldVersion, int newVersion) { 523 try { 524 db.execSQL("drop table " + Account.TABLE_NAME); 525 } catch (SQLException e) { 526 } 527 createAccountTable(db); 528 } 529 530 static void createHostAuthTable(SQLiteDatabase db) { 531 String s = " (" + EmailContent.RECORD_ID + " integer primary key autoincrement, " 532 + HostAuthColumns.PROTOCOL + " text, " 533 + HostAuthColumns.ADDRESS + " text, " 534 + HostAuthColumns.PORT + " integer, " 535 + HostAuthColumns.FLAGS + " integer, " 536 + HostAuthColumns.LOGIN + " text, " 537 + HostAuthColumns.PASSWORD + " text, " 538 + HostAuthColumns.DOMAIN + " text, " 539 + HostAuthColumns.ACCOUNT_KEY + " integer" 540 + ");"; 541 db.execSQL("create table " + HostAuth.TABLE_NAME + s); 542 } 543 544 static void resetHostAuthTable(SQLiteDatabase db, int oldVersion, int newVersion) { 545 try { 546 db.execSQL("drop table " + HostAuth.TABLE_NAME); 547 } catch (SQLException e) { 548 } 549 createHostAuthTable(db); 550 } 551 552 static void createMailboxTable(SQLiteDatabase db) { 553 String s = " (" + EmailContent.RECORD_ID + " integer primary key autoincrement, " 554 + MailboxColumns.DISPLAY_NAME + " text, " 555 + MailboxColumns.SERVER_ID + " text, " 556 + MailboxColumns.PARENT_SERVER_ID + " text, " 557 + MailboxColumns.PARENT_KEY + " integer, " 558 + MailboxColumns.ACCOUNT_KEY + " integer, " 559 + MailboxColumns.TYPE + " integer, " 560 + MailboxColumns.DELIMITER + " integer, " 561 + MailboxColumns.SYNC_KEY + " text, " 562 + MailboxColumns.SYNC_LOOKBACK + " integer, " 563 + MailboxColumns.SYNC_INTERVAL + " integer, " 564 + MailboxColumns.SYNC_TIME + " integer, " 565 + MailboxColumns.UNREAD_COUNT + " integer, " 566 + MailboxColumns.FLAG_VISIBLE + " integer, " 567 + MailboxColumns.FLAGS + " integer, " 568 + MailboxColumns.VISIBLE_LIMIT + " integer, " 569 + MailboxColumns.SYNC_STATUS + " text, " 570 + MailboxColumns.MESSAGE_COUNT + " integer not null default 0" 571 + ");"; 572 db.execSQL("create table " + Mailbox.TABLE_NAME + s); 573 db.execSQL("create index mailbox_" + MailboxColumns.SERVER_ID 574 + " on " + Mailbox.TABLE_NAME + " (" + MailboxColumns.SERVER_ID + ")"); 575 db.execSQL("create index mailbox_" + MailboxColumns.ACCOUNT_KEY 576 + " on " + Mailbox.TABLE_NAME + " (" + MailboxColumns.ACCOUNT_KEY + ")"); 577 // Deleting a Mailbox deletes associated Messages in all three tables 578 db.execSQL(TRIGGER_MAILBOX_DELETE); 579 } 580 581 static void resetMailboxTable(SQLiteDatabase db, int oldVersion, int newVersion) { 582 try { 583 db.execSQL("drop table " + Mailbox.TABLE_NAME); 584 } catch (SQLException e) { 585 } 586 createMailboxTable(db); 587 } 588 589 static void createAttachmentTable(SQLiteDatabase db) { 590 String s = " (" + EmailContent.RECORD_ID + " integer primary key autoincrement, " 591 + AttachmentColumns.FILENAME + " text, " 592 + AttachmentColumns.MIME_TYPE + " text, " 593 + AttachmentColumns.SIZE + " integer, " 594 + AttachmentColumns.CONTENT_ID + " text, " 595 + AttachmentColumns.CONTENT_URI + " text, " 596 + AttachmentColumns.MESSAGE_KEY + " integer, " 597 + AttachmentColumns.LOCATION + " text, " 598 + AttachmentColumns.ENCODING + " text, " 599 + AttachmentColumns.CONTENT + " text, " 600 + AttachmentColumns.FLAGS + " integer, " 601 + AttachmentColumns.CONTENT_BYTES + " blob, " 602 + AttachmentColumns.ACCOUNT_KEY + " integer" 603 + ");"; 604 db.execSQL("create table " + Attachment.TABLE_NAME + s); 605 db.execSQL(createIndex(Attachment.TABLE_NAME, AttachmentColumns.MESSAGE_KEY)); 606 } 607 608 static void resetAttachmentTable(SQLiteDatabase db, int oldVersion, int newVersion) { 609 try { 610 db.execSQL("drop table " + Attachment.TABLE_NAME); 611 } catch (SQLException e) { 612 } 613 createAttachmentTable(db); 614 } 615 616 static void createBodyTable(SQLiteDatabase db) { 617 String s = " (" + EmailContent.RECORD_ID + " integer primary key autoincrement, " 618 + BodyColumns.MESSAGE_KEY + " integer, " 619 + BodyColumns.HTML_CONTENT + " text, " 620 + BodyColumns.TEXT_CONTENT + " text, " 621 + BodyColumns.HTML_REPLY + " text, " 622 + BodyColumns.TEXT_REPLY + " text, " 623 + BodyColumns.SOURCE_MESSAGE_KEY + " text, " 624 + BodyColumns.INTRO_TEXT + " text" 625 + ");"; 626 db.execSQL("create table " + Body.TABLE_NAME + s); 627 db.execSQL(createIndex(Body.TABLE_NAME, BodyColumns.MESSAGE_KEY)); 628 } 629 630 static void upgradeBodyTable(SQLiteDatabase db, int oldVersion, int newVersion) { 631 if (oldVersion < 5) { 632 try { 633 db.execSQL("drop table " + Body.TABLE_NAME); 634 createBodyTable(db); 635 } catch (SQLException e) { 636 } 637 } else if (oldVersion == 5) { 638 try { 639 db.execSQL("alter table " + Body.TABLE_NAME 640 + " add " + BodyColumns.INTRO_TEXT + " text"); 641 } catch (SQLException e) { 642 // Shouldn't be needed unless we're debugging and interrupt the process 643 Log.w(TAG, "Exception upgrading EmailProviderBody.db from v5 to v6", e); 644 } 645 oldVersion = 6; 646 } 647 } 648 649 private SQLiteDatabase mDatabase; 650 private SQLiteDatabase mBodyDatabase; 651 652 public synchronized SQLiteDatabase getDatabase(Context context) { 653 // Always return the cached database, if we've got one 654 if (mDatabase != null) { 655 return mDatabase; 656 } 657 658 // Whenever we create or re-cache the databases, make sure that we haven't lost one 659 // to corruption 660 checkDatabases(); 661 662 DatabaseHelper helper = new DatabaseHelper(context, DATABASE_NAME); 663 mDatabase = helper.getWritableDatabase(); 664 if (mDatabase != null) { 665 mDatabase.setLockingEnabled(true); 666 BodyDatabaseHelper bodyHelper = new BodyDatabaseHelper(context, BODY_DATABASE_NAME); 667 mBodyDatabase = bodyHelper.getWritableDatabase(); 668 if (mBodyDatabase != null) { 669 mBodyDatabase.setLockingEnabled(true); 670 String bodyFileName = mBodyDatabase.getPath(); 671 mDatabase.execSQL("attach \"" + bodyFileName + "\" as BodyDatabase"); 672 } 673 } 674 675 // Check for any orphaned Messages in the updated/deleted tables 676 deleteOrphans(mDatabase, Message.UPDATED_TABLE_NAME); 677 deleteOrphans(mDatabase, Message.DELETED_TABLE_NAME); 678 679 return mDatabase; 680 } 681 682 /*package*/ static SQLiteDatabase getReadableDatabase(Context context) { 683 DatabaseHelper helper = new EmailProvider().new DatabaseHelper(context, DATABASE_NAME); 684 return helper.getReadableDatabase(); 685 } 686 687 /** {@inheritDoc} */ 688 @Override 689 public void shutdown() { 690 if (mDatabase != null) { 691 mDatabase.close(); 692 mDatabase = null; 693 } 694 if (mBodyDatabase != null) { 695 mBodyDatabase.close(); 696 mBodyDatabase = null; 697 } 698 } 699 700 /*package*/ static void deleteOrphans(SQLiteDatabase database, String tableName) { 701 if (database != null) { 702 // We'll look at all of the items in the table; there won't be many typically 703 Cursor c = database.query(tableName, ORPHANS_PROJECTION, null, null, null, null, null); 704 // Usually, there will be nothing in these tables, so make a quick check 705 try { 706 if (c.getCount() == 0) return; 707 ArrayList<Long> foundMailboxes = new ArrayList<Long>(); 708 ArrayList<Long> notFoundMailboxes = new ArrayList<Long>(); 709 ArrayList<Long> deleteList = new ArrayList<Long>(); 710 String[] bindArray = new String[1]; 711 while (c.moveToNext()) { 712 // Get the mailbox key and see if we've already found this mailbox 713 // If so, we're fine 714 long mailboxId = c.getLong(ORPHANS_MAILBOX_KEY); 715 // If we already know this mailbox doesn't exist, mark the message for deletion 716 if (notFoundMailboxes.contains(mailboxId)) { 717 deleteList.add(c.getLong(ORPHANS_ID)); 718 // If we don't know about this mailbox, we'll try to find it 719 } else if (!foundMailboxes.contains(mailboxId)) { 720 bindArray[0] = Long.toString(mailboxId); 721 Cursor boxCursor = database.query(Mailbox.TABLE_NAME, 722 Mailbox.ID_PROJECTION, WHERE_ID, bindArray, null, null, null); 723 try { 724 // If it exists, we'll add it to the "found" mailboxes 725 if (boxCursor.moveToFirst()) { 726 foundMailboxes.add(mailboxId); 727 // Otherwise, we'll add to "not found" and mark the message for deletion 728 } else { 729 notFoundMailboxes.add(mailboxId); 730 deleteList.add(c.getLong(ORPHANS_ID)); 731 } 732 } finally { 733 boxCursor.close(); 734 } 735 } 736 } 737 // Now, delete the orphan messages 738 for (long messageId: deleteList) { 739 bindArray[0] = Long.toString(messageId); 740 database.delete(tableName, WHERE_ID, bindArray); 741 } 742 } finally { 743 c.close(); 744 } 745 } 746 } 747 748 private class BodyDatabaseHelper extends SQLiteOpenHelper { 749 BodyDatabaseHelper(Context context, String name) { 750 super(context, name, null, BODY_DATABASE_VERSION); 751 } 752 753 @Override 754 public void onCreate(SQLiteDatabase db) { 755 Log.d(TAG, "Creating EmailProviderBody database"); 756 createBodyTable(db); 757 } 758 759 @Override 760 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { 761 upgradeBodyTable(db, oldVersion, newVersion); 762 } 763 764 @Override 765 public void onOpen(SQLiteDatabase db) { 766 } 767 } 768 769 private class DatabaseHelper extends SQLiteOpenHelper { 770 Context mContext; 771 772 DatabaseHelper(Context context, String name) { 773 super(context, name, null, DATABASE_VERSION); 774 mContext = context; 775 } 776 777 @Override 778 public void onCreate(SQLiteDatabase db) { 779 Log.d(TAG, "Creating EmailProvider database"); 780 // Create all tables here; each class has its own method 781 createMessageTable(db); 782 createAttachmentTable(db); 783 createMailboxTable(db); 784 createHostAuthTable(db); 785 createAccountTable(db); 786 } 787 788 @Override 789 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { 790 // For versions prior to 5, delete all data 791 // Versions >= 5 require that data be preserved! 792 if (oldVersion < 5) { 793 android.accounts.Account[] accounts = AccountManager.get(mContext) 794 .getAccountsByType(AccountManagerTypes.TYPE_EXCHANGE); 795 for (android.accounts.Account account: accounts) { 796 AccountManager.get(mContext).removeAccount(account, null, null); 797 } 798 resetMessageTable(db, oldVersion, newVersion); 799 resetAttachmentTable(db, oldVersion, newVersion); 800 resetMailboxTable(db, oldVersion, newVersion); 801 resetHostAuthTable(db, oldVersion, newVersion); 802 resetAccountTable(db, oldVersion, newVersion); 803 return; 804 } 805 if (oldVersion == 5) { 806 // Message Tables: Add SyncColumns.SERVER_TIMESTAMP 807 try { 808 db.execSQL("alter table " + Message.TABLE_NAME 809 + " add column " + SyncColumns.SERVER_TIMESTAMP + " integer" + ";"); 810 db.execSQL("alter table " + Message.UPDATED_TABLE_NAME 811 + " add column " + SyncColumns.SERVER_TIMESTAMP + " integer" + ";"); 812 db.execSQL("alter table " + Message.DELETED_TABLE_NAME 813 + " add column " + SyncColumns.SERVER_TIMESTAMP + " integer" + ";"); 814 } catch (SQLException e) { 815 // Shouldn't be needed unless we're debugging and interrupt the process 816 Log.w(TAG, "Exception upgrading EmailProvider.db from v5 to v6", e); 817 } 818 oldVersion = 6; 819 } 820 if (oldVersion == 6) { 821 // Use the newer mailbox_delete trigger 822 db.execSQL("drop trigger mailbox_delete;"); 823 db.execSQL(TRIGGER_MAILBOX_DELETE); 824 oldVersion = 7; 825 } 826 if (oldVersion == 7) { 827 // add the security (provisioning) column 828 try { 829 db.execSQL("alter table " + Account.TABLE_NAME 830 + " add column " + AccountColumns.SECURITY_FLAGS + " integer" + ";"); 831 } catch (SQLException e) { 832 // Shouldn't be needed unless we're debugging and interrupt the process 833 Log.w(TAG, "Exception upgrading EmailProvider.db from 7 to 8 " + e); 834 } 835 oldVersion = 8; 836 } 837 if (oldVersion == 8) { 838 // accounts: add security sync key & user signature columns 839 try { 840 db.execSQL("alter table " + Account.TABLE_NAME 841 + " add column " + AccountColumns.SECURITY_SYNC_KEY + " text" + ";"); 842 db.execSQL("alter table " + Account.TABLE_NAME 843 + " add column " + AccountColumns.SIGNATURE + " text" + ";"); 844 } catch (SQLException e) { 845 // Shouldn't be needed unless we're debugging and interrupt the process 846 Log.w(TAG, "Exception upgrading EmailProvider.db from 8 to 9 " + e); 847 } 848 oldVersion = 9; 849 } 850 if (oldVersion == 9) { 851 // Message: add meeting info column into Message tables 852 try { 853 db.execSQL("alter table " + Message.TABLE_NAME 854 + " add column " + MessageColumns.MEETING_INFO + " text" + ";"); 855 db.execSQL("alter table " + Message.UPDATED_TABLE_NAME 856 + " add column " + MessageColumns.MEETING_INFO + " text" + ";"); 857 db.execSQL("alter table " + Message.DELETED_TABLE_NAME 858 + " add column " + MessageColumns.MEETING_INFO + " text" + ";"); 859 } catch (SQLException e) { 860 // Shouldn't be needed unless we're debugging and interrupt the process 861 Log.w(TAG, "Exception upgrading EmailProvider.db from 9 to 10 " + e); 862 } 863 oldVersion = 10; 864 } 865 if (oldVersion == 10) { 866 // Attachment: add content and flags columns 867 try { 868 db.execSQL("alter table " + Attachment.TABLE_NAME 869 + " add column " + AttachmentColumns.CONTENT + " text" + ";"); 870 db.execSQL("alter table " + Attachment.TABLE_NAME 871 + " add column " + AttachmentColumns.FLAGS + " integer" + ";"); 872 } catch (SQLException e) { 873 // Shouldn't be needed unless we're debugging and interrupt the process 874 Log.w(TAG, "Exception upgrading EmailProvider.db from 10 to 11 " + e); 875 } 876 oldVersion = 11; 877 } 878 if (oldVersion == 11) { 879 // Attachment: add content_bytes 880 try { 881 db.execSQL("alter table " + Attachment.TABLE_NAME 882 + " add column " + AttachmentColumns.CONTENT_BYTES + " blob" + ";"); 883 } catch (SQLException e) { 884 // Shouldn't be needed unless we're debugging and interrupt the process 885 Log.w(TAG, "Exception upgrading EmailProvider.db from 11 to 12 " + e); 886 } 887 oldVersion = 12; 888 } 889 if (oldVersion == 12) { 890 try { 891 db.execSQL("alter table " + Mailbox.TABLE_NAME 892 + " add column " + Mailbox.MESSAGE_COUNT 893 +" integer not null default 0" + ";"); 894 recalculateMessageCount(db); 895 } catch (SQLException e) { 896 // Shouldn't be needed unless we're debugging and interrupt the process 897 Log.w(TAG, "Exception upgrading EmailProvider.db from 12 to 13 " + e); 898 } 899 oldVersion = 13; 900 } 901 if (oldVersion == 13) { 902 try { 903 db.execSQL("alter table " + Message.TABLE_NAME 904 + " add column " + Message.SNIPPET 905 +" text" + ";"); 906 } catch (SQLException e) { 907 // Shouldn't be needed unless we're debugging and interrupt the process 908 Log.w(TAG, "Exception upgrading EmailProvider.db from 13 to 14 " + e); 909 } 910 oldVersion = 14; 911 } 912 if (oldVersion == 14) { 913 try { 914 db.execSQL("alter table " + Message.DELETED_TABLE_NAME 915 + " add column " + Message.SNIPPET +" text" + ";"); 916 db.execSQL("alter table " + Message.UPDATED_TABLE_NAME 917 + " add column " + Message.SNIPPET +" text" + ";"); 918 } catch (SQLException e) { 919 // Shouldn't be needed unless we're debugging and interrupt the process 920 Log.w(TAG, "Exception upgrading EmailProvider.db from 14 to 15 " + e); 921 } 922 oldVersion = 15; 923 } 924 if (oldVersion == 15) { 925 try { 926 db.execSQL("alter table " + Attachment.TABLE_NAME 927 + " add column " + Attachment.ACCOUNT_KEY +" integer" + ";"); 928 // Update all existing attachments to add the accountKey data 929 db.execSQL("update " + Attachment.TABLE_NAME + " set " + 930 Attachment.ACCOUNT_KEY + "= (SELECT " + Message.TABLE_NAME + "." + 931 Message.ACCOUNT_KEY + " from " + Message.TABLE_NAME + " where " + 932 Message.TABLE_NAME + "." + Message.RECORD_ID + " = " + 933 Attachment.TABLE_NAME + "." + Attachment.MESSAGE_KEY + ")"); 934 } catch (SQLException e) { 935 // Shouldn't be needed unless we're debugging and interrupt the process 936 Log.w(TAG, "Exception upgrading EmailProvider.db from 15 to 16 " + e); 937 } 938 oldVersion = 16; 939 } 940 if (oldVersion == 16) { 941 try { 942 db.execSQL("alter table " + Mailbox.TABLE_NAME 943 + " add column " + Mailbox.PARENT_KEY + " integer;"); 944 } catch (SQLException e) { 945 // Shouldn't be needed unless we're debugging and interrupt the process 946 Log.w(TAG, "Exception upgrading EmailProvider.db from 16 to 17 " + e); 947 } 948 oldVersion = 17; 949 } 950 if (oldVersion == 17) { 951 upgradeFromVersion17ToVersion18(db); 952 oldVersion = 18; 953 } 954 } 955 956 @Override 957 public void onOpen(SQLiteDatabase db) { 958 } 959 } 960 961 @Override 962 public int delete(Uri uri, String selection, String[] selectionArgs) { 963 final int match = findMatch(uri, "delete"); 964 Context context = getContext(); 965 // Pick the correct database for this operation 966 // If we're in a transaction already (which would happen during applyBatch), then the 967 // body database is already attached to the email database and any attempt to use the 968 // body database directly will result in a SQLiteException (the database is locked) 969 SQLiteDatabase db = getDatabase(context); 970 int table = match >> BASE_SHIFT; 971 String id = "0"; 972 boolean messageDeletion = false; 973 ContentResolver resolver = context.getContentResolver(); 974 975 ContentCache cache = CONTENT_CACHES[table]; 976 String tableName = TABLE_NAMES[table]; 977 int result = -1; 978 979 try { 980 switch (match) { 981 // These are cases in which one or more Messages might get deleted, either by 982 // cascade or explicitly 983 case MAILBOX_ID: 984 case MAILBOX: 985 case ACCOUNT_ID: 986 case ACCOUNT: 987 case MESSAGE: 988 case SYNCED_MESSAGE_ID: 989 case MESSAGE_ID: 990 // Handle lost Body records here, since this cannot be done in a trigger 991 // The process is: 992 // 1) Begin a transaction, ensuring that both databases are affected atomically 993 // 2) Do the requested deletion, with cascading deletions handled in triggers 994 // 3) End the transaction, committing all changes atomically 995 // 996 // Bodies are auto-deleted here; Attachments are auto-deleted via trigger 997 messageDeletion = true; 998 db.beginTransaction(); 999 resolver.notifyChange(Message.NOTIFIER_URI, null); 1000 break; 1001 } 1002 switch (match) { 1003 case BODY_ID: 1004 case DELETED_MESSAGE_ID: 1005 case SYNCED_MESSAGE_ID: 1006 case MESSAGE_ID: 1007 case UPDATED_MESSAGE_ID: 1008 case ATTACHMENT_ID: 1009 case MAILBOX_ID: 1010 case ACCOUNT_ID: 1011 case HOSTAUTH_ID: 1012 id = uri.getPathSegments().get(1); 1013 if (match == SYNCED_MESSAGE_ID) { 1014 // For synced messages, first copy the old message to the deleted table and 1015 // delete it from the updated table (in case it was updated first) 1016 // Note that this is all within a transaction, for atomicity 1017 db.execSQL(DELETED_MESSAGE_INSERT + id); 1018 db.execSQL(UPDATED_MESSAGE_DELETE + id); 1019 } 1020 if (cache != null) { 1021 cache.lock(id); 1022 } 1023 try { 1024 result = db.delete(tableName, whereWithId(id, selection), selectionArgs); 1025 if (cache != null) { 1026 switch(match) { 1027 case ACCOUNT_ID: 1028 // Account deletion will clear all of the caches, as HostAuth's, 1029 // Mailboxes, and Messages will be deleted in the process 1030 sCacheMailbox.invalidate("Delete", uri, selection); 1031 sCacheHostAuth.invalidate("Delete", uri, selection); 1032 //$FALL-THROUGH$ 1033 case MAILBOX_ID: 1034 // Mailbox deletion will clear the Message cache 1035 sCacheMessage.invalidate("Delete", uri, selection); 1036 //$FALL-THROUGH$ 1037 case SYNCED_MESSAGE_ID: 1038 case MESSAGE_ID: 1039 case HOSTAUTH_ID: 1040 cache.invalidate("Delete", uri, selection); 1041 break; 1042 } 1043 } 1044 } finally { 1045 if (cache != null) { 1046 cache.unlock(id); 1047 } 1048 } 1049 break; 1050 case ATTACHMENTS_MESSAGE_ID: 1051 // All attachments for the given message 1052 id = uri.getPathSegments().get(2); 1053 result = db.delete(tableName, 1054 whereWith(Attachment.MESSAGE_KEY + "=" + id, selection), selectionArgs); 1055 break; 1056 1057 case BODY: 1058 case MESSAGE: 1059 case DELETED_MESSAGE: 1060 case UPDATED_MESSAGE: 1061 case ATTACHMENT: 1062 case MAILBOX: 1063 case ACCOUNT: 1064 case HOSTAUTH: 1065 switch(match) { 1066 // See the comments above for deletion of ACCOUNT_ID, etc 1067 case ACCOUNT: 1068 sCacheMailbox.invalidate("Delete", uri, selection); 1069 sCacheHostAuth.invalidate("Delete", uri, selection); 1070 //$FALL-THROUGH$ 1071 case MAILBOX: 1072 sCacheMessage.invalidate("Delete", uri, selection); 1073 //$FALL-THROUGH$ 1074 case MESSAGE: 1075 case HOSTAUTH: 1076 cache.invalidate("Delete", uri, selection); 1077 break; 1078 } 1079 result = db.delete(tableName, selection, selectionArgs); 1080 break; 1081 1082 default: 1083 throw new IllegalArgumentException("Unknown URI " + uri); 1084 } 1085 if (messageDeletion) { 1086 if (match == MESSAGE_ID) { 1087 // Delete the Body record associated with the deleted message 1088 db.execSQL(DELETE_BODY + id); 1089 } else { 1090 // Delete any orphaned Body records 1091 db.execSQL(DELETE_ORPHAN_BODIES); 1092 } 1093 db.setTransactionSuccessful(); 1094 } 1095 } catch (SQLiteException e) { 1096 checkDatabases(); 1097 throw e; 1098 } finally { 1099 if (messageDeletion) { 1100 db.endTransaction(); 1101 } 1102 } 1103 1104 // Notify all existing cursors. 1105 resolver.notifyChange(EmailContent.CONTENT_URI, null); 1106 return result; 1107 } 1108 1109 @Override 1110 // Use the email- prefix because message, mailbox, and account are so generic (e.g. SMS, IM) 1111 public String getType(Uri uri) { 1112 int match = findMatch(uri, "getType"); 1113 switch (match) { 1114 case BODY_ID: 1115 return "vnd.android.cursor.item/email-body"; 1116 case BODY: 1117 return "vnd.android.cursor.dir/email-body"; 1118 case UPDATED_MESSAGE_ID: 1119 case MESSAGE_ID: 1120 // NOTE: According to the framework folks, we're supposed to invent mime types as 1121 // a way of passing information to drag & drop recipients. 1122 // If there's a mailboxId parameter in the url, we respond with a mime type that 1123 // has -n appended, where n is the mailboxId of the message. The drag & drop code 1124 // uses this information to know not to allow dragging the item to its own mailbox 1125 String mimeType = EMAIL_MESSAGE_MIME_TYPE; 1126 String mailboxId = uri.getQueryParameter(MESSAGE_URI_PARAMETER_MAILBOX_ID); 1127 if (mailboxId != null) { 1128 mimeType += "-" + mailboxId; 1129 } 1130 return mimeType; 1131 case UPDATED_MESSAGE: 1132 case MESSAGE: 1133 return "vnd.android.cursor.dir/email-message"; 1134 case MAILBOX: 1135 return "vnd.android.cursor.dir/email-mailbox"; 1136 case MAILBOX_ID: 1137 return "vnd.android.cursor.item/email-mailbox"; 1138 case ACCOUNT: 1139 return "vnd.android.cursor.dir/email-account"; 1140 case ACCOUNT_ID: 1141 return "vnd.android.cursor.item/email-account"; 1142 case ATTACHMENTS_MESSAGE_ID: 1143 case ATTACHMENT: 1144 return "vnd.android.cursor.dir/email-attachment"; 1145 case ATTACHMENT_ID: 1146 return EMAIL_ATTACHMENT_MIME_TYPE; 1147 case HOSTAUTH: 1148 return "vnd.android.cursor.dir/email-hostauth"; 1149 case HOSTAUTH_ID: 1150 return "vnd.android.cursor.item/email-hostauth"; 1151 default: 1152 throw new IllegalArgumentException("Unknown URI " + uri); 1153 } 1154 } 1155 1156 @Override 1157 public Uri insert(Uri uri, ContentValues values) { 1158 int match = findMatch(uri, "insert"); 1159 Context context = getContext(); 1160 ContentResolver resolver = context.getContentResolver(); 1161 1162 // See the comment at delete(), above 1163 SQLiteDatabase db = getDatabase(context); 1164 int table = match >> BASE_SHIFT; 1165 long id; 1166 1167 // We do NOT allow setting of unreadCount/messageCount via the provider 1168 // These columns are maintained via triggers 1169 if (match == MAILBOX_ID || match == MAILBOX) { 1170 values.put(MailboxColumns.UNREAD_COUNT, 0); 1171 values.put(MailboxColumns.MESSAGE_COUNT, 0); 1172 } 1173 1174 Uri resultUri = null; 1175 1176 try { 1177 switch (match) { 1178 case MESSAGE: 1179 resolver.notifyChange(Message.NOTIFIER_URI, null); 1180 //$FALL-THROUGH$ 1181 case UPDATED_MESSAGE: 1182 case DELETED_MESSAGE: 1183 case BODY: 1184 case ATTACHMENT: 1185 case MAILBOX: 1186 case ACCOUNT: 1187 case HOSTAUTH: 1188 id = db.insert(TABLE_NAMES[table], "foo", values); 1189 resultUri = ContentUris.withAppendedId(uri, id); 1190 // Clients shouldn't normally be adding rows to these tables, as they are 1191 // maintained by triggers. However, we need to be able to do this for unit 1192 // testing, so we allow the insert and then throw the same exception that we 1193 // would if this weren't allowed. 1194 if (match == UPDATED_MESSAGE || match == DELETED_MESSAGE) { 1195 throw new IllegalArgumentException("Unknown URL " + uri); 1196 } 1197 if (match == ATTACHMENT) { 1198 int flags = 0; 1199 if (values.containsKey(Attachment.FLAGS)) { 1200 flags = values.getAsInteger(Attachment.FLAGS); 1201 } 1202 // Report all new attachments to the download service 1203 AttachmentDownloadService.attachmentChanged(id, flags); 1204 } 1205 break; 1206 case MAILBOX_ID: 1207 // This implies adding a message to a mailbox 1208 // Hmm, a problem here is that we can't link the account as well, so it must be 1209 // already in the values... 1210 id = Long.parseLong(uri.getPathSegments().get(1)); 1211 values.put(MessageColumns.MAILBOX_KEY, id); 1212 return insert(Message.CONTENT_URI, values); // Recurse 1213 case MESSAGE_ID: 1214 // This implies adding an attachment to a message. 1215 id = Long.parseLong(uri.getPathSegments().get(1)); 1216 values.put(AttachmentColumns.MESSAGE_KEY, id); 1217 return insert(Attachment.CONTENT_URI, values); // Recurse 1218 case ACCOUNT_ID: 1219 // This implies adding a mailbox to an account. 1220 id = Long.parseLong(uri.getPathSegments().get(1)); 1221 values.put(MailboxColumns.ACCOUNT_KEY, id); 1222 return insert(Mailbox.CONTENT_URI, values); // Recurse 1223 case ATTACHMENTS_MESSAGE_ID: 1224 id = db.insert(TABLE_NAMES[table], "foo", values); 1225 resultUri = ContentUris.withAppendedId(Attachment.CONTENT_URI, id); 1226 break; 1227 default: 1228 throw new IllegalArgumentException("Unknown URL " + uri); 1229 } 1230 } catch (SQLiteException e) { 1231 checkDatabases(); 1232 throw e; 1233 } 1234 1235 // Notify all existing cursors. 1236 resolver.notifyChange(EmailContent.CONTENT_URI, null); 1237 return resultUri; 1238 } 1239 1240 @Override 1241 public boolean onCreate() { 1242 checkDatabases(); 1243 return false; 1244 } 1245 1246 /** 1247 * The idea here is that the two databases (EmailProvider.db and EmailProviderBody.db must 1248 * always be in sync (i.e. there are two database or NO databases). This code will delete 1249 * any "orphan" database, so that both will be created together. Note that an "orphan" database 1250 * will exist after either of the individual databases is deleted due to data corruption. 1251 */ 1252 public void checkDatabases() { 1253 // Uncache the databases 1254 if (mDatabase != null) { 1255 mDatabase = null; 1256 } 1257 if (mBodyDatabase != null) { 1258 mBodyDatabase = null; 1259 } 1260 // Look for orphans, and delete as necessary; these must always be in sync 1261 File databaseFile = getContext().getDatabasePath(DATABASE_NAME); 1262 File bodyFile = getContext().getDatabasePath(BODY_DATABASE_NAME); 1263 1264 // TODO Make sure attachments are deleted 1265 if (databaseFile.exists() && !bodyFile.exists()) { 1266 Log.w(TAG, "Deleting orphaned EmailProvider database..."); 1267 databaseFile.delete(); 1268 } else if (bodyFile.exists() && !databaseFile.exists()) { 1269 Log.w(TAG, "Deleting orphaned EmailProviderBody database..."); 1270 bodyFile.delete(); 1271 } 1272 } 1273 1274 @Override 1275 public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, 1276 String sortOrder) { 1277 long time = 0L; 1278 if (Email.DEBUG) { 1279 time = System.nanoTime(); 1280 } 1281 Cursor c = null; 1282 int match; 1283 try { 1284 match = findMatch(uri, "query"); 1285 } catch (IllegalArgumentException e) { 1286 String uriString = uri.toString(); 1287 // If we were passed an illegal uri, see if it ends in /-1 1288 // if so, and if substituting 0 for -1 results in a valid uri, return an empty cursor 1289 if (uriString != null && uriString.endsWith("/-1")) { 1290 uri = Uri.parse(uriString.substring(0, uriString.length() - 2) + "0"); 1291 match = findMatch(uri, "query"); 1292 switch (match) { 1293 case BODY_ID: 1294 case MESSAGE_ID: 1295 case DELETED_MESSAGE_ID: 1296 case UPDATED_MESSAGE_ID: 1297 case ATTACHMENT_ID: 1298 case MAILBOX_ID: 1299 case ACCOUNT_ID: 1300 case HOSTAUTH_ID: 1301 return new MatrixCursor(projection, 0); 1302 } 1303 } 1304 throw e; 1305 } 1306 Context context = getContext(); 1307 // See the comment at delete(), above 1308 SQLiteDatabase db = getDatabase(context); 1309 int table = match >> BASE_SHIFT; 1310 String limit = uri.getQueryParameter(EmailContent.PARAMETER_LIMIT); 1311 String id; 1312 1313 // Find the cache for this query's table (if any) 1314 ContentCache cache = null; 1315 String tableName = TABLE_NAMES[table]; 1316 // We can only use the cache if there's no selection 1317 if (selection == null) { 1318 cache = CONTENT_CACHES[table]; 1319 } 1320 if (cache == null) { 1321 ContentCache.notCacheable(uri, selection); 1322 } 1323 1324 try { 1325 switch (match) { 1326 case BODY: 1327 case MESSAGE: 1328 case UPDATED_MESSAGE: 1329 case DELETED_MESSAGE: 1330 case ATTACHMENT: 1331 case MAILBOX: 1332 case ACCOUNT: 1333 case HOSTAUTH: 1334 c = db.query(tableName, projection, 1335 selection, selectionArgs, null, null, sortOrder, limit); 1336 break; 1337 case BODY_ID: 1338 case MESSAGE_ID: 1339 case DELETED_MESSAGE_ID: 1340 case UPDATED_MESSAGE_ID: 1341 case ATTACHMENT_ID: 1342 case MAILBOX_ID: 1343 case ACCOUNT_ID: 1344 case HOSTAUTH_ID: 1345 id = uri.getPathSegments().get(1); 1346 if (cache != null) { 1347 c = cache.getCachedCursor(id, projection); 1348 } 1349 if (c == null) { 1350 CacheToken token = null; 1351 if (cache != null) { 1352 token = cache.getCacheToken(id); 1353 } 1354 c = db.query(tableName, projection, whereWithId(id, selection), 1355 selectionArgs, null, null, sortOrder, limit); 1356 if (cache != null) { 1357 c = cache.putCursor(c, id, projection, token); 1358 } 1359 } 1360 break; 1361 case ATTACHMENTS_MESSAGE_ID: 1362 // All attachments for the given message 1363 id = uri.getPathSegments().get(2); 1364 c = db.query(Attachment.TABLE_NAME, projection, 1365 whereWith(Attachment.MESSAGE_KEY + "=" + id, selection), 1366 selectionArgs, null, null, sortOrder, limit); 1367 break; 1368 default: 1369 throw new IllegalArgumentException("Unknown URI " + uri); 1370 } 1371 } catch (SQLiteException e) { 1372 checkDatabases(); 1373 throw e; 1374 } catch (RuntimeException e) { 1375 checkDatabases(); 1376 e.printStackTrace(); 1377 throw e; 1378 } finally { 1379 if (cache != null && Email.DEBUG) { 1380 cache.recordQueryTime(c, System.nanoTime() - time); 1381 } 1382 } 1383 1384 if ((c != null) && !isTemporary()) { 1385 c.setNotificationUri(getContext().getContentResolver(), uri); 1386 } 1387 return c; 1388 } 1389 1390 private String whereWithId(String id, String selection) { 1391 StringBuilder sb = new StringBuilder(256); 1392 sb.append("_id="); 1393 sb.append(id); 1394 if (selection != null) { 1395 sb.append(" AND ("); 1396 sb.append(selection); 1397 sb.append(')'); 1398 } 1399 return sb.toString(); 1400 } 1401 1402 /** 1403 * Combine a locally-generated selection with a user-provided selection 1404 * 1405 * This introduces risk that the local selection might insert incorrect chars 1406 * into the SQL, so use caution. 1407 * 1408 * @param where locally-generated selection, must not be null 1409 * @param selection user-provided selection, may be null 1410 * @return a single selection string 1411 */ 1412 private String whereWith(String where, String selection) { 1413 if (selection == null) { 1414 return where; 1415 } 1416 StringBuilder sb = new StringBuilder(where); 1417 sb.append(" AND ("); 1418 sb.append(selection); 1419 sb.append(')'); 1420 1421 return sb.toString(); 1422 } 1423 1424 @Override 1425 public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { 1426 // Handle this special case the fastest possible way 1427 if (uri == INTEGRITY_CHECK_URI) { 1428 checkDatabases(); 1429 return 0; 1430 } 1431 1432 // Notify all existing cursors, except for ACCOUNT_RESET_NEW_COUNT(_ID) 1433 Uri notificationUri = EmailContent.CONTENT_URI; 1434 1435 int match = findMatch(uri, "update"); 1436 Context context = getContext(); 1437 ContentResolver resolver = context.getContentResolver(); 1438 // See the comment at delete(), above 1439 SQLiteDatabase db = getDatabase(context); 1440 int table = match >> BASE_SHIFT; 1441 int result; 1442 1443 // We do NOT allow setting of unreadCount/messageCount via the provider 1444 // These columns are maintained via triggers 1445 if (match == MAILBOX_ID || match == MAILBOX) { 1446 values.remove(MailboxColumns.UNREAD_COUNT); 1447 values.remove(MailboxColumns.MESSAGE_COUNT); 1448 } 1449 1450 ContentCache cache = CONTENT_CACHES[table]; 1451 String tableName = TABLE_NAMES[table]; 1452 String id; 1453 1454 try { 1455 switch (match) { 1456 case MAILBOX_ID_ADD_TO_FIELD: 1457 case ACCOUNT_ID_ADD_TO_FIELD: 1458 id = uri.getPathSegments().get(1); 1459 String field = values.getAsString(EmailContent.FIELD_COLUMN_NAME); 1460 Long add = values.getAsLong(EmailContent.ADD_COLUMN_NAME); 1461 if (field == null || add == null) { 1462 throw new IllegalArgumentException("No field/add specified " + uri); 1463 } 1464 ContentValues actualValues = new ContentValues(); 1465 if (cache != null) { 1466 cache.lock(id); 1467 } 1468 try { 1469 db.beginTransaction(); 1470 try { 1471 Cursor c = db.query(tableName, 1472 new String[] {EmailContent.RECORD_ID, field}, 1473 whereWithId(id, selection), 1474 selectionArgs, null, null, null); 1475 try { 1476 result = 0; 1477 String[] bind = new String[1]; 1478 if (c.moveToNext()) { 1479 bind[0] = c.getString(0); // _id 1480 long value = c.getLong(1) + add; 1481 actualValues.put(field, value); 1482 result = db.update(tableName, actualValues, ID_EQUALS, bind); 1483 } 1484 db.setTransactionSuccessful(); 1485 } finally { 1486 c.close(); 1487 } 1488 } finally { 1489 db.endTransaction(); 1490 } 1491 } finally { 1492 if (cache != null) { 1493 cache.unlock(id, actualValues); 1494 } 1495 } 1496 break; 1497 case SYNCED_MESSAGE_ID: 1498 resolver.notifyChange(Message.NOTIFIER_URI, null); 1499 //$FALL-THROUGH$ 1500 case UPDATED_MESSAGE_ID: 1501 case MESSAGE_ID: 1502 case BODY_ID: 1503 case ATTACHMENT_ID: 1504 case MAILBOX_ID: 1505 case ACCOUNT_ID: 1506 case HOSTAUTH_ID: 1507 id = uri.getPathSegments().get(1); 1508 if (cache != null) { 1509 cache.lock(id); 1510 } 1511 try { 1512 if (match == SYNCED_MESSAGE_ID) { 1513 // For synced messages, first copy the old message to the updated table 1514 // Note the insert or ignore semantics, guaranteeing that only the first 1515 // update will be reflected in the updated message table; therefore this 1516 // row will always have the "original" data 1517 db.execSQL(UPDATED_MESSAGE_INSERT + id); 1518 } else if (match == MESSAGE_ID) { 1519 db.execSQL(UPDATED_MESSAGE_DELETE + id); 1520 } 1521 result = db.update(tableName, values, whereWithId(id, selection), 1522 selectionArgs); 1523 } catch (SQLiteException e) { 1524 // Null out values (so they aren't cached) and re-throw 1525 values = null; 1526 throw e; 1527 } finally { 1528 if (cache != null) { 1529 cache.unlock(id, values); 1530 } 1531 } 1532 if (match == ATTACHMENT_ID) { 1533 if (values.containsKey(Attachment.FLAGS)) { 1534 int flags = values.getAsInteger(Attachment.FLAGS); 1535 AttachmentDownloadService.attachmentChanged( 1536 Integer.parseInt(id), flags); 1537 } 1538 } 1539 break; 1540 case BODY: 1541 case MESSAGE: 1542 case UPDATED_MESSAGE: 1543 case ATTACHMENT: 1544 case MAILBOX: 1545 case ACCOUNT: 1546 case HOSTAUTH: 1547 switch(match) { 1548 case MESSAGE: 1549 case ACCOUNT: 1550 case MAILBOX: 1551 case HOSTAUTH: 1552 // If we're doing some generic update, the whole cache needs to be 1553 // invalidated. This case should be quite rare 1554 cache.invalidate("Update", uri, selection); 1555 break; 1556 } 1557 result = db.update(tableName, values, selection, selectionArgs); 1558 break; 1559 case ACCOUNT_RESET_NEW_COUNT_ID: 1560 id = uri.getPathSegments().get(1); 1561 if (cache != null) { 1562 cache.lock(id); 1563 } 1564 try { 1565 result = db.update(tableName, CONTENT_VALUES_RESET_NEW_MESSAGE_COUNT, 1566 whereWithId(id, selection), selectionArgs); 1567 } finally { 1568 if (cache != null) { 1569 cache.unlock(id, values); 1570 } 1571 } 1572 notificationUri = Account.CONTENT_URI; // Only notify account cursors. 1573 break; 1574 case ACCOUNT_RESET_NEW_COUNT: 1575 result = db.update(tableName, CONTENT_VALUES_RESET_NEW_MESSAGE_COUNT, 1576 selection, selectionArgs); 1577 // Affects all accounts. Just invalidate all account cache. 1578 cache.invalidate("Reset all new counts", null, null); 1579 notificationUri = Account.CONTENT_URI; // Only notify account cursors. 1580 break; 1581 default: 1582 throw new IllegalArgumentException("Unknown URI " + uri); 1583 } 1584 } catch (SQLiteException e) { 1585 checkDatabases(); 1586 throw e; 1587 } 1588 1589 resolver.notifyChange(notificationUri, null); 1590 return result; 1591 } 1592 1593 /* (non-Javadoc) 1594 * @see android.content.ContentProvider#applyBatch(android.content.ContentProviderOperation) 1595 */ 1596 @Override 1597 public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations) 1598 throws OperationApplicationException { 1599 Context context = getContext(); 1600 SQLiteDatabase db = getDatabase(context); 1601 db.beginTransaction(); 1602 try { 1603 ContentProviderResult[] results = super.applyBatch(operations); 1604 db.setTransactionSuccessful(); 1605 return results; 1606 } finally { 1607 db.endTransaction(); 1608 } 1609 } 1610 1611 /** Counts the number of messages in each mailbox, and updates the message count column. */ 1612 @VisibleForTesting 1613 static void recalculateMessageCount(SQLiteDatabase db) { 1614 db.execSQL("update " + Mailbox.TABLE_NAME + " set " + MailboxColumns.MESSAGE_COUNT + 1615 "= (select count(*) from " + Message.TABLE_NAME + 1616 " where " + Message.MAILBOX_KEY + " = " + 1617 Mailbox.TABLE_NAME + "." + EmailContent.RECORD_ID + ")"); 1618 } 1619 1620 /** Upgrades the database from v17 to v18 */ 1621 @VisibleForTesting 1622 static void upgradeFromVersion17ToVersion18(SQLiteDatabase db) { 1623 // Copy the displayName column to the serverId column. In v18 of the database, 1624 // we use the serverId for IMAP/POP3 mailboxes instead of overloading the 1625 // display name. 1626 // 1627 // For posterity; this is the command we're executing: 1628 //sqlite> UPDATE mailbox SET serverid=displayname WHERE mailbox._id in ( 1629 // ...> SELECT mailbox._id FROM mailbox,account,hostauth WHERE 1630 // ...> mailbox.parentkey=0 AND mailbox.accountkey=account._id AND 1631 // ...> account.hostauthkeyrecv=hostauth._id AND 1632 // ...> (hostauth.protocol='imap' OR hostauth.protocol='pop3')); 1633 try { 1634 db.execSQL( 1635 "UPDATE " + Mailbox.TABLE_NAME + " SET " 1636 + MailboxColumns.SERVER_ID + "=" + MailboxColumns.DISPLAY_NAME 1637 + " WHERE " 1638 + Mailbox.TABLE_NAME + "." + MailboxColumns.ID + " IN ( SELECT " 1639 + Mailbox.TABLE_NAME + "." + MailboxColumns.ID + " FROM " 1640 + Mailbox.TABLE_NAME + "," + Account.TABLE_NAME + "," 1641 + HostAuth.TABLE_NAME + " WHERE " 1642 + Mailbox.TABLE_NAME + "." + MailboxColumns.PARENT_KEY + "=0 AND " 1643 + Mailbox.TABLE_NAME + "." + MailboxColumns.ACCOUNT_KEY + "=" 1644 + Account.TABLE_NAME + "." + AccountColumns.ID + " AND " 1645 + Account.TABLE_NAME + "." + AccountColumns.HOST_AUTH_KEY_RECV + "=" 1646 + HostAuth.TABLE_NAME + "." + HostAuthColumns.ID + " AND ( " 1647 + HostAuth.TABLE_NAME + "." + HostAuthColumns.PROTOCOL + "='imap' OR " 1648 + HostAuth.TABLE_NAME + "." + HostAuthColumns.PROTOCOL + "='pop3' ) )"); 1649 } catch (SQLException e) { 1650 // Shouldn't be needed unless we're debugging and interrupt the process 1651 Log.w(TAG, "Exception upgrading EmailProvider.db from 17 to 18 " + e); 1652 } 1653 } 1654} 1655