EmailProvider.java revision a867ebba6233becf2061c80e1c53d7d395a6cffc
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.EmailContent.Account; 21import com.android.email.provider.EmailContent.AccountColumns; 22import com.android.email.provider.EmailContent.Attachment; 23import com.android.email.provider.EmailContent.AttachmentColumns; 24import com.android.email.provider.EmailContent.Body; 25import com.android.email.provider.EmailContent.BodyColumns; 26import com.android.email.provider.EmailContent.HostAuth; 27import com.android.email.provider.EmailContent.HostAuthColumns; 28import com.android.email.provider.EmailContent.Mailbox; 29import com.android.email.provider.EmailContent.MailboxColumns; 30import com.android.email.provider.EmailContent.Message; 31import com.android.email.provider.EmailContent.MessageColumns; 32import com.android.email.provider.EmailContent.SyncColumns; 33 34import android.content.ContentProvider; 35import android.content.ContentProviderOperation; 36import android.content.ContentProviderResult; 37import android.content.ContentUris; 38import android.content.ContentValues; 39import android.content.Context; 40import android.content.OperationApplicationException; 41import android.content.UriMatcher; 42import android.database.Cursor; 43import android.database.SQLException; 44import android.database.sqlite.SQLiteDatabase; 45import android.database.sqlite.SQLiteOpenHelper; 46import android.net.Uri; 47import android.util.Log; 48 49import java.util.ArrayList; 50 51public class EmailProvider extends ContentProvider { 52 53 private static final String TAG = "EmailProvider"; 54 55 static final String DATABASE_NAME = "EmailProvider.db"; 56 static final String BODY_DATABASE_NAME = "EmailProviderBody.db"; 57 58 // Any changes to the database format *must* include update-in-place code. 59 60 public static final int DATABASE_VERSION = 1; 61 public static final int BODY_DATABASE_VERSION = 1; 62 63 public static final String EMAIL_AUTHORITY = "com.android.email.provider"; 64 65 private static final int ACCOUNT_BASE = 0; 66 private static final int ACCOUNT = ACCOUNT_BASE; 67 private static final int ACCOUNT_MAILBOXES = ACCOUNT_BASE + 1; 68 private static final int ACCOUNT_ID = ACCOUNT_BASE + 2; 69 70 private static final int MAILBOX_BASE = 0x1000; 71 private static final int MAILBOX = MAILBOX_BASE; 72 private static final int MAILBOX_MESSAGES = MAILBOX_BASE + 1; 73 private static final int MAILBOX_ID = MAILBOX_BASE + 2; 74 75 private static final int MESSAGE_BASE = 0x2000; 76 private static final int MESSAGE = MESSAGE_BASE; 77 private static final int MESSAGE_ID = MESSAGE_BASE + 1; 78 private static final int SYNCED_MESSAGE_ID = MESSAGE_BASE + 2; 79 80 private static final int ATTACHMENT_BASE = 0x3000; 81 private static final int ATTACHMENT = ATTACHMENT_BASE; 82 private static final int ATTACHMENT_CONTENT = ATTACHMENT_BASE + 1; 83 private static final int ATTACHMENT_ID = ATTACHMENT_BASE + 2; 84 private static final int ATTACHMENTS_MESSAGE_ID = ATTACHMENT_BASE + 3; 85 86 private static final int HOSTAUTH_BASE = 0x4000; 87 private static final int HOSTAUTH = HOSTAUTH_BASE; 88 private static final int HOSTAUTH_ID = HOSTAUTH_BASE + 1; 89 90 private static final int UPDATED_MESSAGE_BASE = 0x5000; 91 private static final int UPDATED_MESSAGE = UPDATED_MESSAGE_BASE; 92 private static final int UPDATED_MESSAGE_ID = UPDATED_MESSAGE_BASE + 1; 93 94 private static final int DELETED_MESSAGE_BASE = 0x6000; 95 private static final int DELETED_MESSAGE = DELETED_MESSAGE_BASE; 96 private static final int DELETED_MESSAGE_ID = DELETED_MESSAGE_BASE + 1; 97 private static final int DELETED_MESSAGE_MAILBOX = DELETED_MESSAGE_BASE + 2; 98 99 // MUST ALWAYS EQUAL THE LAST OF THE PREVIOUS BASE CONSTANTS 100 private static final int LAST_EMAIL_PROVIDER_DB_BASE = DELETED_MESSAGE_BASE; 101 102 // DO NOT CHANGE BODY_BASE!! 103 private static final int BODY_BASE = LAST_EMAIL_PROVIDER_DB_BASE + 0x1000; 104 private static final int BODY = BODY_BASE; 105 private static final int BODY_ID = BODY_BASE + 1; 106 private static final int BODY_MESSAGE_ID = BODY_BASE + 2; 107 private static final int BODY_HTML = BODY_BASE + 3; 108 private static final int BODY_TEXT = BODY_BASE + 4; 109 110 111 private static final int BASE_SHIFT = 12; // 12 bits to the base type: 0, 0x1000, 0x2000, etc. 112 113 private static final String[] TABLE_NAMES = { 114 EmailContent.Account.TABLE_NAME, 115 EmailContent.Mailbox.TABLE_NAME, 116 EmailContent.Message.TABLE_NAME, 117 EmailContent.Attachment.TABLE_NAME, 118 EmailContent.HostAuth.TABLE_NAME, 119 EmailContent.Message.UPDATED_TABLE_NAME, 120 EmailContent.Message.DELETED_TABLE_NAME, 121 EmailContent.Body.TABLE_NAME 122 }; 123 124 private static final UriMatcher sURIMatcher = new UriMatcher(UriMatcher.NO_MATCH); 125 126 /** 127 * Let's only generate these SQL strings once, as they are used frequently 128 * Note that this isn't relevant for table creation strings, since they are used only once 129 */ 130 private static final String UPDATED_MESSAGE_INSERT = "insert or ignore into " + 131 Message.UPDATED_TABLE_NAME + " select * from " + Message.TABLE_NAME + " where " + 132 EmailContent.RECORD_ID + '='; 133 134 private static final String UPDATED_MESSAGE_DELETE = "delete from " + 135 Message.UPDATED_TABLE_NAME + " where " + EmailContent.RECORD_ID + '='; 136 137 private static final String DELETED_MESSAGE_INSERT = "insert or replace into " + 138 Message.DELETED_TABLE_NAME + " select * from " + Message.TABLE_NAME + " where " + 139 EmailContent.RECORD_ID + '='; 140 141 private static final String DELETE_ORPHAN_BODIES = "delete from " + Body.TABLE_NAME + 142 " where " + BodyColumns.MESSAGE_KEY + " in " + "(select " + BodyColumns.MESSAGE_KEY + 143 " from " + Body.TABLE_NAME + " except select " + EmailContent.RECORD_ID + " from " + 144 Message.TABLE_NAME + ')'; 145 146 private static final String DELETE_BODY = "delete from " + Body.TABLE_NAME + 147 " where " + BodyColumns.MESSAGE_KEY + '='; 148 149 static { 150 // Email URI matching table 151 UriMatcher matcher = sURIMatcher; 152 153 // All accounts 154 matcher.addURI(EMAIL_AUTHORITY, "account", ACCOUNT); 155 // A specific account 156 // insert into this URI causes a mailbox to be added to the account 157 matcher.addURI(EMAIL_AUTHORITY, "account/#", ACCOUNT_ID); 158 // The mailboxes in a specific account 159 matcher.addURI(EMAIL_AUTHORITY, "account/#/mailbox", ACCOUNT_MAILBOXES); 160 161 // All mailboxes 162 matcher.addURI(EMAIL_AUTHORITY, "mailbox", MAILBOX); 163 // A specific mailbox 164 // insert into this URI causes a message to be added to the mailbox 165 // ** NOTE For now, the accountKey must be set manually in the values! 166 matcher.addURI(EMAIL_AUTHORITY, "mailbox/#", MAILBOX_ID); 167 // The messages in a specific mailbox 168 matcher.addURI(EMAIL_AUTHORITY, "mailbox/#/message", MAILBOX_MESSAGES); 169 170 // All messages 171 matcher.addURI(EMAIL_AUTHORITY, "message", MESSAGE); 172 // A specific message 173 // insert into this URI causes an attachment to be added to the message 174 matcher.addURI(EMAIL_AUTHORITY, "message/#", MESSAGE_ID); 175 176 // A specific attachment 177 matcher.addURI(EMAIL_AUTHORITY, "attachment", ATTACHMENT); 178 // A specific attachment (the header information) 179 matcher.addURI(EMAIL_AUTHORITY, "attachment/#", ATTACHMENT_ID); 180 // The content for a specific attachment 181 // NOT IMPLEMENTED 182 matcher.addURI(EMAIL_AUTHORITY, "attachment/content/*", ATTACHMENT_CONTENT); 183 // The attachments of a specific message (query only) (insert & delete TBD) 184 matcher.addURI(EMAIL_AUTHORITY, "attachment/message/#", ATTACHMENTS_MESSAGE_ID); 185 186 // All mail bodies 187 matcher.addURI(EMAIL_AUTHORITY, "body", BODY); 188 // A specific mail body 189 matcher.addURI(EMAIL_AUTHORITY, "body/#", BODY_ID); 190 // The body for a specific message 191 matcher.addURI(EMAIL_AUTHORITY, "body/message/#", BODY_MESSAGE_ID); 192 // The HTML part of a specific mail body 193 matcher.addURI(EMAIL_AUTHORITY, "body/#/html", BODY_HTML); 194 // The plain text part of a specific mail body 195 matcher.addURI(EMAIL_AUTHORITY, "body/#/text", BODY_TEXT); 196 197 // All hostauth records 198 matcher.addURI(EMAIL_AUTHORITY, "hostauth", HOSTAUTH); 199 // A specific hostauth 200 matcher.addURI(EMAIL_AUTHORITY, "hostauth/#", HOSTAUTH_ID); 201 202 /** 203 * THIS URI HAS SPECIAL SEMANTICS 204 * ITS USE IS INDENTED FOR THE UI APPLICATION TO MARK CHANGES THAT NEED TO BE SYNCED BACK 205 * TO A SERVER VIA A SYNC ADAPTER 206 */ 207 matcher.addURI(EMAIL_AUTHORITY, "syncedMessage/#", SYNCED_MESSAGE_ID); 208 209 /** 210 * THE URIs BELOW THIS POINT ARE INTENDED TO BE USED BY SYNC ADAPTERS ONLY 211 * THEY REFER TO DATA CREATED AND MAINTAINED BY CALLS TO THE SYNCED_MESSAGE_ID URI 212 * BY THE UI APPLICATION 213 */ 214 // All deleted messages 215 matcher.addURI(EMAIL_AUTHORITY, "deletedMessage", DELETED_MESSAGE); 216 // A specific deleted message 217 matcher.addURI(EMAIL_AUTHORITY, "deletedMessage/#", DELETED_MESSAGE_ID); 218 // All deleted messages from a specific mailbox 219 // NOT IMPLEMENTED; do we need this as a convenience? 220 matcher.addURI(EMAIL_AUTHORITY, "deletedMessage/mailbox/#", DELETED_MESSAGE_MAILBOX); 221 222 // All updated messages 223 matcher.addURI(EMAIL_AUTHORITY, "updatedMessage", UPDATED_MESSAGE); 224 // A specific updated message 225 matcher.addURI(EMAIL_AUTHORITY, "updatedMessage/#", UPDATED_MESSAGE_ID); 226 } 227 228 /* 229 * Internal helper method for index creation. 230 * Example: 231 * "create index message_" + MessageColumns.FLAG_READ 232 * + " on " + Message.TABLE_NAME + " (" + MessageColumns.FLAG_READ + ");" 233 */ 234 /* package */ 235 static String createIndex(String tableName, String columnName) { 236 return "create index " + tableName.toLowerCase() + '_' + columnName 237 + " on " + tableName + " (" + columnName + ");"; 238 } 239 240 static void createMessageTable(SQLiteDatabase db) { 241 String messageColumns = MessageColumns.DISPLAY_NAME + " text, " 242 + MessageColumns.TIMESTAMP + " integer, " 243 + MessageColumns.SUBJECT + " text, " 244 + MessageColumns.PREVIEW + " text, " 245 + MessageColumns.FLAG_READ + " integer, " 246 + MessageColumns.FLAG_LOADED + " integer, " 247 + MessageColumns.FLAG_FAVORITE + " integer, " 248 + MessageColumns.FLAG_ATTACHMENT + " integer, " 249 + MessageColumns.FLAGS + " integer, " 250 + MessageColumns.TEXT_INFO + " text, " 251 + MessageColumns.HTML_INFO + " text, " 252 + MessageColumns.CLIENT_ID + " integer, " 253 + MessageColumns.MESSAGE_ID + " text, " 254 + MessageColumns.THREAD_ID + " text, " 255 + MessageColumns.MAILBOX_KEY + " integer, " 256 + MessageColumns.ACCOUNT_KEY + " integer, " 257 + MessageColumns.REFERENCE_KEY + " integer, " 258 + MessageColumns.SENDER_LIST + " text, " 259 + MessageColumns.FROM_LIST + " text, " 260 + MessageColumns.TO_LIST + " text, " 261 + MessageColumns.CC_LIST + " text, " 262 + MessageColumns.BCC_LIST + " text, " 263 + MessageColumns.REPLY_TO_LIST + " text" 264 + ");"; 265 266 // This String and the following String MUST have the same columns, except for the type 267 // of those columns! 268 String createString = " (" + EmailContent.RECORD_ID + " integer primary key autoincrement, " 269 + SyncColumns.ACCOUNT_KEY + " integer, " 270 + SyncColumns.SERVER_ID + " integer, " 271 + SyncColumns.SERVER_VERSION + " integer, " 272 + SyncColumns.DATA + " text, " 273 + SyncColumns.DIRTY_COUNT + " integer, " 274 + messageColumns; 275 276 // For the updated and deleted tables, the id is assigned, but we do want to keep track 277 // of the ORDER of updates using an autoincrement primary key. We use the DATA column 278 // at this point; it has no other function 279 String altCreateString = " (" + EmailContent.RECORD_ID + " integer unique, " 280 + SyncColumns.ACCOUNT_KEY + " integer, " 281 + SyncColumns.SERVER_ID + " integer, " 282 + SyncColumns.SERVER_VERSION + " integer, " 283 + SyncColumns.DATA + " integer primary key autoincrement, " 284 + SyncColumns.DIRTY_COUNT + " integer, " 285 + messageColumns; 286 287 // The three tables have the same schema 288 db.execSQL("create table " + Message.TABLE_NAME + createString); 289 db.execSQL("create table " + Message.UPDATED_TABLE_NAME + altCreateString); 290 db.execSQL("create table " + Message.DELETED_TABLE_NAME + altCreateString); 291 292 String indexColumns[] = { 293 MessageColumns.TIMESTAMP, 294 MessageColumns.FLAG_READ, 295 MessageColumns.FLAG_LOADED, 296 MessageColumns.MAILBOX_KEY, 297 SyncColumns.SERVER_ID 298 }; 299 300 for (String columnName : indexColumns) { 301 db.execSQL(createIndex(Message.TABLE_NAME, columnName)); 302 } 303 304 // Deleting a Message deletes all associated Attachments 305 // Deleting the associated Body cannot be done in a trigger, because the Body is stored 306 // in a separate database, and trigger cannot operate on attached databases. 307 db.execSQL("create trigger message_delete before delete on " + Message.TABLE_NAME + 308 " begin delete from " + Attachment.TABLE_NAME + 309 " where " + AttachmentColumns.MESSAGE_KEY + "=old." + EmailContent.RECORD_ID + 310 "; end"); 311 312 // Add triggers to keep unread count accurate per mailbox 313 314 // Insert a message; if flagRead is zero, add to the unread count of the message's mailbox 315 db.execSQL("create trigger unread_message_insert before insert on " + Message.TABLE_NAME + 316 " when NEW." + MessageColumns.FLAG_READ + "=0" + 317 " begin update " + Mailbox.TABLE_NAME + " set " + MailboxColumns.UNREAD_COUNT + 318 '=' + MailboxColumns.UNREAD_COUNT + "+1" + 319 " where " + EmailContent.RECORD_ID + "=NEW." + MessageColumns.MAILBOX_KEY + 320 "; end"); 321 322 // Delete a message; if flagRead is zero, decrement the unread count of the msg's mailbox 323 db.execSQL("create trigger unread_message_delete before delete on " + Message.TABLE_NAME + 324 " when OLD." + MessageColumns.FLAG_READ + "=0" + 325 " begin update " + Mailbox.TABLE_NAME + " set " + MailboxColumns.UNREAD_COUNT + 326 '=' + MailboxColumns.UNREAD_COUNT + "-1" + 327 " where " + EmailContent.RECORD_ID + "=OLD." + MessageColumns.MAILBOX_KEY + 328 "; end"); 329 330 // Change a message's mailbox 331 db.execSQL("create trigger unread_message_move before update of " + 332 MessageColumns.MAILBOX_KEY + " on " + Message.TABLE_NAME + 333 " when OLD." + MessageColumns.FLAG_READ + "=0" + 334 " begin update " + Mailbox.TABLE_NAME + " set " + MailboxColumns.UNREAD_COUNT + 335 '=' + MailboxColumns.UNREAD_COUNT + "-1" + 336 " where " + EmailContent.RECORD_ID + "=OLD." + MessageColumns.MAILBOX_KEY + 337 "; update " + Mailbox.TABLE_NAME + " set " + MailboxColumns.UNREAD_COUNT + 338 '=' + MailboxColumns.UNREAD_COUNT + "+1" + 339 " where " + EmailContent.RECORD_ID + "=NEW." + MessageColumns.MAILBOX_KEY + 340 "; end"); 341 342 // Change a message's read state 343 db.execSQL("create trigger unread_message_read before update of " + 344 MessageColumns.FLAG_READ + " on " + Message.TABLE_NAME + 345 " when OLD." + MessageColumns.FLAG_READ + "!=NEW." + MessageColumns.FLAG_READ + 346 " begin update " + Mailbox.TABLE_NAME + " set " + MailboxColumns.UNREAD_COUNT + 347 '=' + MailboxColumns.UNREAD_COUNT + "+ case OLD." + MessageColumns.FLAG_READ + 348 " when 0 then -1 else 1 end" + 349 " where " + EmailContent.RECORD_ID + "=OLD." + MessageColumns.MAILBOX_KEY + 350 "; end"); 351 } 352 353 static void upgradeMessageTable(SQLiteDatabase db, int oldVersion, int newVersion) { 354 try { 355 db.execSQL("drop table " + Message.TABLE_NAME); 356 db.execSQL("drop table " + Message.UPDATED_TABLE_NAME); 357 db.execSQL("drop table " + Message.DELETED_TABLE_NAME); 358 } catch (SQLException e) { 359 } 360 createMessageTable(db); 361 } 362 363 static void createAccountTable(SQLiteDatabase db) { 364 String s = " (" + EmailContent.RECORD_ID + " integer primary key autoincrement, " 365 + AccountColumns.DISPLAY_NAME + " text, " 366 + AccountColumns.EMAIL_ADDRESS + " text, " 367 + AccountColumns.SYNC_KEY + " text, " 368 + AccountColumns.SYNC_LOOKBACK + " integer, " 369 + AccountColumns.SYNC_INTERVAL + " text, " 370 + AccountColumns.HOST_AUTH_KEY_RECV + " integer, " 371 + AccountColumns.HOST_AUTH_KEY_SEND + " integer, " 372 + AccountColumns.FLAGS + " integer, " 373 + AccountColumns.IS_DEFAULT + " integer, " 374 + AccountColumns.COMPATIBILITY_UUID + " text, " 375 + AccountColumns.SENDER_NAME + " text, " 376 + AccountColumns.RINGTONE_URI + " text, " 377 + AccountColumns.PROTOCOL_VERSION + " text, " 378 + AccountColumns.NEW_MESSAGE_COUNT + " integer" 379 + ");"; 380 db.execSQL("create table " + Account.TABLE_NAME + s); 381 // Deleting an account deletes associated Mailboxes and HostAuth's 382 db.execSQL("create trigger account_delete before delete on " + Account.TABLE_NAME + 383 " begin delete from " + Mailbox.TABLE_NAME + 384 " where " + MailboxColumns.ACCOUNT_KEY + "=old." + EmailContent.RECORD_ID + 385 "; delete from " + HostAuth.TABLE_NAME + 386 " where " + EmailContent.RECORD_ID + "=old." + AccountColumns.HOST_AUTH_KEY_RECV + 387 "; delete from " + HostAuth.TABLE_NAME + 388 " where " + EmailContent.RECORD_ID + "=old." + AccountColumns.HOST_AUTH_KEY_SEND + 389 "; end"); 390 } 391 392 static void upgradeAccountTable(SQLiteDatabase db, int oldVersion, int newVersion) { 393 try { 394 db.execSQL("drop table " + Account.TABLE_NAME); 395 } catch (SQLException e) { 396 } 397 createAccountTable(db); 398 } 399 400 static void createHostAuthTable(SQLiteDatabase db) { 401 String s = " (" + EmailContent.RECORD_ID + " integer primary key autoincrement, " 402 + HostAuthColumns.PROTOCOL + " text, " 403 + HostAuthColumns.ADDRESS + " text, " 404 + HostAuthColumns.PORT + " integer, " 405 + HostAuthColumns.FLAGS + " integer, " 406 + HostAuthColumns.LOGIN + " text, " 407 + HostAuthColumns.PASSWORD + " text, " 408 + HostAuthColumns.DOMAIN + " text, " 409 + HostAuthColumns.ACCOUNT_KEY + " integer" 410 + ");"; 411 db.execSQL("create table " + HostAuth.TABLE_NAME + s); 412 } 413 414 static void upgradeHostAuthTable(SQLiteDatabase db, int oldVersion, int newVersion) { 415 try { 416 db.execSQL("drop table " + HostAuth.TABLE_NAME); 417 } catch (SQLException e) { 418 } 419 createHostAuthTable(db); 420 } 421 422 static void createMailboxTable(SQLiteDatabase db) { 423 String s = " (" + EmailContent.RECORD_ID + " integer primary key autoincrement, " 424 + MailboxColumns.DISPLAY_NAME + " text, " 425 + MailboxColumns.SERVER_ID + " text unique on conflict replace, " 426 + MailboxColumns.PARENT_SERVER_ID + " text, " 427 + MailboxColumns.ACCOUNT_KEY + " integer, " 428 + MailboxColumns.TYPE + " integer, " 429 + MailboxColumns.DELIMITER + " integer, " 430 + MailboxColumns.SYNC_KEY + " text, " 431 + MailboxColumns.SYNC_LOOKBACK + " integer, " 432 + MailboxColumns.SYNC_INTERVAL + " integer, " 433 + MailboxColumns.SYNC_TIME + " integer, " 434 + MailboxColumns.UNREAD_COUNT + " integer, " 435 + MailboxColumns.FLAG_VISIBLE + " integer, " 436 + MailboxColumns.FLAGS + " integer, " 437 + MailboxColumns.VISIBLE_LIMIT + " integer, " 438 + MailboxColumns.SYNC_STATUS + " text" 439 + ");"; 440 db.execSQL("create table " + Mailbox.TABLE_NAME + s); 441 db.execSQL("create index mailbox_" + MailboxColumns.SERVER_ID 442 + " on " + Mailbox.TABLE_NAME + " (" + MailboxColumns.SERVER_ID + ")"); 443 db.execSQL("create index mailbox_" + MailboxColumns.ACCOUNT_KEY 444 + " on " + Mailbox.TABLE_NAME + " (" + MailboxColumns.ACCOUNT_KEY + ")"); 445 // Deleting a Mailbox deletes associated Messages 446 db.execSQL("create trigger mailbox_delete before delete on " + Mailbox.TABLE_NAME + 447 " begin delete from " + Message.TABLE_NAME + 448 " where " + MessageColumns.MAILBOX_KEY + "=old." + EmailContent.RECORD_ID + 449 "; end"); 450 } 451 452 static void upgradeMailboxTable(SQLiteDatabase db, int oldVersion, int newVersion) { 453 try { 454 db.execSQL("drop table " + Mailbox.TABLE_NAME); 455 } catch (SQLException e) { 456 } 457 createMailboxTable(db); 458 } 459 460 static void createAttachmentTable(SQLiteDatabase db) { 461 String s = " (" + EmailContent.RECORD_ID + " integer primary key autoincrement, " 462 + AttachmentColumns.FILENAME + " text, " 463 + AttachmentColumns.MIME_TYPE + " text, " 464 + AttachmentColumns.SIZE + " integer, " 465 + AttachmentColumns.CONTENT_ID + " text, " 466 + AttachmentColumns.CONTENT_URI + " text, " 467 + AttachmentColumns.MESSAGE_KEY + " integer, " 468 + AttachmentColumns.LOCATION + " text, " 469 + AttachmentColumns.ENCODING + " text" 470 + ");"; 471 db.execSQL("create table " + Attachment.TABLE_NAME + s); 472 db.execSQL(createIndex(Attachment.TABLE_NAME, AttachmentColumns.MESSAGE_KEY)); 473 } 474 475 static void upgradeAttachmentTable(SQLiteDatabase db, int oldVersion, int newVersion) { 476 try { 477 db.execSQL("drop table " + Attachment.TABLE_NAME); 478 } catch (SQLException e) { 479 } 480 createAttachmentTable(db); 481 } 482 483 static void createBodyTable(SQLiteDatabase db) { 484 String s = " (" + EmailContent.RECORD_ID + " integer primary key autoincrement, " 485 + BodyColumns.MESSAGE_KEY + " integer, " 486 + BodyColumns.HTML_CONTENT + " text, " 487 + BodyColumns.TEXT_CONTENT + " text" 488 + ");"; 489 db.execSQL("create table " + Body.TABLE_NAME + s); 490 db.execSQL(createIndex(Body.TABLE_NAME, BodyColumns.MESSAGE_KEY)); 491 } 492 493 static void upgradeBodyTable(SQLiteDatabase db, int oldVersion, int newVersion) { 494 try { 495 db.execSQL("drop table " + Body.TABLE_NAME); 496 } catch (SQLException e) { 497 } 498 createBodyTable(db); 499 } 500 501 private final int mDatabaseVersion = DATABASE_VERSION; 502 private final int mBodyDatabaseVersion = BODY_DATABASE_VERSION; 503 504 private SQLiteDatabase mDatabase; 505 private SQLiteDatabase mBodyDatabase; 506 private boolean mInTransaction = false; 507 508 public SQLiteDatabase getDatabase(Context context) { 509 if (mDatabase != null) { 510 return mDatabase; 511 } 512 DatabaseHelper helper = new DatabaseHelper(context, DATABASE_NAME); 513 mDatabase = helper.getWritableDatabase(); 514 if (mDatabase != null) { 515 mDatabase.setLockingEnabled(true); 516 BodyDatabaseHelper bodyHelper = new BodyDatabaseHelper(context, BODY_DATABASE_NAME); 517 mBodyDatabase = bodyHelper.getWritableDatabase(); 518 if (mBodyDatabase != null) { 519 mBodyDatabase.setLockingEnabled(true); 520 String bodyFileName = mBodyDatabase.getPath(); 521 mDatabase.execSQL("attach \"" + bodyFileName + "\" as BodyDatabase"); 522 } 523 } 524 return mDatabase; 525 } 526 527 private class BodyDatabaseHelper extends SQLiteOpenHelper { 528 BodyDatabaseHelper(Context context, String name) { 529 super(context, name, null, mBodyDatabaseVersion); 530 } 531 532 @Override 533 public void onCreate(SQLiteDatabase db) { 534 // Create all tables here; each class has its own method 535 createBodyTable(db); 536 } 537 538 @Override 539 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { 540 upgradeBodyTable(db, oldVersion, newVersion); 541 } 542 543 @Override 544 public void onOpen(SQLiteDatabase db) { 545 } 546 } 547 548 private class DatabaseHelper extends SQLiteOpenHelper { 549 DatabaseHelper(Context context, String name) { 550 super(context, name, null, mDatabaseVersion); 551 } 552 553 @Override 554 public void onCreate(SQLiteDatabase db) { 555 // Create all tables here; each class has its own method 556 createMessageTable(db); 557 createAttachmentTable(db); 558 createMailboxTable(db); 559 createHostAuthTable(db); 560 createAccountTable(db); 561 } 562 563 @Override 564 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { 565 upgradeMessageTable(db, oldVersion, newVersion); 566 upgradeAttachmentTable(db, oldVersion, newVersion); 567 upgradeMailboxTable(db, oldVersion, newVersion); 568 upgradeHostAuthTable(db, oldVersion, newVersion); 569 upgradeAccountTable(db, oldVersion, newVersion); 570 } 571 572 @Override 573 public void onOpen(SQLiteDatabase db) { 574 } 575 } 576 577 @Override 578 public int delete(Uri uri, String selection, String[] selectionArgs) { 579 final int match = sURIMatcher.match(uri); 580 Context context = getContext(); 581 // Pick the correct database for this operation 582 // If we're in a transaction already (which would happen during applyBatch), then the 583 // body database is already attached to the email database and any attempt to use the 584 // body database directly will result in a SQLiteException (the database is locked) 585 SQLiteDatabase db = getDatabase(context); 586 int table = match >> BASE_SHIFT; 587 String id = "0"; 588 boolean messageDeletion = false; 589 590 if (Email.LOGD) { 591 Log.v(TAG, "EmailProvider.delete: uri=" + uri + ", match is " + match); 592 } 593 594 int result = -1; 595 596 try { 597 switch (match) { 598 // These are cases in which one or more Messages might get deleted, either by 599 // cascade or explicitly 600 case MAILBOX_ID: 601 case MAILBOX: 602 case ACCOUNT_ID: 603 case ACCOUNT: 604 case MESSAGE: 605 case SYNCED_MESSAGE_ID: 606 case MESSAGE_ID: 607 // Handle lost Body records here, since this cannot be done in a trigger 608 // The process is: 609 // 1) Begin a transaction, ensuring that both databases are affected atomically 610 // 2) Do the requested deletion, with cascading deletions handled in triggers 611 // 3) End the transaction, committing all changes atomically 612 613 messageDeletion = true; 614 if (!mInTransaction) { 615 db.beginTransaction(); 616 } 617 break; 618 } 619 switch (match) { 620 case BODY_ID: 621 case DELETED_MESSAGE_ID: 622 case SYNCED_MESSAGE_ID: 623 case MESSAGE_ID: 624 case UPDATED_MESSAGE_ID: 625 case ATTACHMENT_ID: 626 case MAILBOX_ID: 627 case ACCOUNT_ID: 628 case HOSTAUTH_ID: 629 id = uri.getPathSegments().get(1); 630 if (match == SYNCED_MESSAGE_ID) { 631 // For synced messages, first copy the old message to the deleted table and 632 // delete it from the updated table (in case it was updated first) 633 // Note that this is all within a transaction, for atomicity 634 db.execSQL(DELETED_MESSAGE_INSERT + id); 635 db.execSQL(UPDATED_MESSAGE_DELETE + id); 636 } 637 result = db.delete(TABLE_NAMES[table], whereWithId(id, selection), 638 selectionArgs); 639 break; 640 case BODY: 641 case MESSAGE: 642 case DELETED_MESSAGE: 643 case UPDATED_MESSAGE: 644 case ATTACHMENT: 645 case MAILBOX: 646 case ACCOUNT: 647 case HOSTAUTH: 648 result = db.delete(TABLE_NAMES[table], selection, selectionArgs); 649 break; 650 default: 651 throw new IllegalArgumentException("Unknown URI " + uri); 652 } 653 if (messageDeletion) { 654 if (match == MESSAGE_ID) { 655 // Delete the Body record associated with the deleted message 656 db.execSQL(DELETE_BODY + id); 657 } else { 658 // Delete any orphaned Body records 659 db.execSQL(DELETE_ORPHAN_BODIES); 660 } 661 if (!mInTransaction) { 662 db.setTransactionSuccessful(); 663 } 664 } 665 } finally { 666 if (messageDeletion) { 667 if (!mInTransaction) { 668 db.endTransaction(); 669 } 670 } 671 } 672 getContext().getContentResolver().notifyChange(uri, null); 673 return result; 674 } 675 676 @Override 677 // Use the email- prefix because message, mailbox, and account are so generic (e.g. SMS, IM) 678 public String getType(Uri uri) { 679 int match = sURIMatcher.match(uri); 680 switch (match) { 681 case BODY_ID: 682 return "vnd.android.cursor.item/email-body"; 683 case BODY: 684 return "vnd.android.cursor.dir/email-message"; 685 case UPDATED_MESSAGE_ID: 686 case MESSAGE_ID: 687 return "vnd.android.cursor.item/email-message"; 688 case MAILBOX_MESSAGES: 689 case UPDATED_MESSAGE: 690 case MESSAGE: 691 return "vnd.android.cursor.dir/email-message"; 692 case ACCOUNT_MAILBOXES: 693 case MAILBOX: 694 return "vnd.android.cursor.dir/email-mailbox"; 695 case MAILBOX_ID: 696 return "vnd.android.cursor.item/email-mailbox"; 697 case ACCOUNT: 698 return "vnd.android.cursor.dir/email-account"; 699 case ACCOUNT_ID: 700 return "vnd.android.cursor.item/email-account"; 701 case ATTACHMENTS_MESSAGE_ID: 702 case ATTACHMENT: 703 return "vnd.android.cursor.dir/email-attachment"; 704 case ATTACHMENT_ID: 705 return "vnd.android.cursor.item/email-attachment"; 706 case HOSTAUTH: 707 return "vnd.android.cursor.dir/email-hostauth"; 708 case HOSTAUTH_ID: 709 return "vnd.android.cursor.item/email-hostauth"; 710 default: 711 throw new IllegalArgumentException("Unknown URI " + uri); 712 } 713 } 714 715 @Override 716 public Uri insert(Uri uri, ContentValues values) { 717 int match = sURIMatcher.match(uri); 718 Context context = getContext(); 719 // See the comment at delete(), above 720 SQLiteDatabase db = getDatabase(context); 721 int table = match >> BASE_SHIFT; 722 long id; 723 724 if (Email.LOGD) { 725 Log.v(TAG, "EmailProvider.insert: uri=" + uri + ", match is " + match); 726 } 727 728 Uri resultUri = null; 729 730 switch (match) { 731 case BODY: 732 case MESSAGE: 733 case ATTACHMENT: 734 case MAILBOX: 735 case ACCOUNT: 736 case HOSTAUTH: 737 // Make sure all new message records have dirty count of 0 738 if (match == MESSAGE) { 739 values.put(SyncColumns.DIRTY_COUNT, 0); 740 } 741 id = db.insert(TABLE_NAMES[table], "foo", values); 742 resultUri = ContentUris.withAppendedId(uri, id); 743 break; 744 case MAILBOX_ID: 745 // This implies adding a message to a mailbox 746 // Hmm, one problem here is that we can't link the account as well, so it must be 747 // already in the values... 748 id = Long.parseLong(uri.getPathSegments().get(1)); 749 values.put(MessageColumns.MAILBOX_KEY, id); 750 resultUri = insert(Message.CONTENT_URI, values); 751 break; 752 case MESSAGE_ID: 753 // This implies adding an attachment to a message. 754 id = Long.parseLong(uri.getPathSegments().get(1)); 755 values.put(AttachmentColumns.MESSAGE_KEY, id); 756 resultUri = insert(Attachment.CONTENT_URI, values); 757 break; 758 case ACCOUNT_ID: 759 // This implies adding a mailbox to an account. 760 id = Long.parseLong(uri.getPathSegments().get(1)); 761 values.put(MailboxColumns.ACCOUNT_KEY, id); 762 resultUri = insert(Mailbox.CONTENT_URI, values); 763 break; 764 case ATTACHMENTS_MESSAGE_ID: 765 id = db.insert(TABLE_NAMES[table], "foo", values); 766 resultUri = ContentUris.withAppendedId(Attachment.CONTENT_URI, id); 767 break; 768 default: 769 throw new IllegalArgumentException("Unknown URL " + uri); 770 } 771 772 // Notify with the base uri, not the new uri (nobody is watching a new record) 773 getContext().getContentResolver().notifyChange(uri, null); 774 return resultUri; 775 } 776 777 @Override 778 public boolean onCreate() { 779 // TODO Auto-generated method stub 780 return false; 781 } 782 783 @Override 784 public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, 785 String sortOrder) { 786 Cursor c = null; 787 Uri notificationUri = EmailContent.CONTENT_URI; 788 int match = sURIMatcher.match(uri); 789 Context context = getContext(); 790 // See the comment at delete(), above 791 SQLiteDatabase db = getDatabase(context); 792 int table = match >> BASE_SHIFT; 793 String id; 794 795 if (Email.LOGD) { 796 Log.v(TAG, "EmailProvider.query: uri=" + uri + ", match is " + match); 797 } 798 799 switch (match) { 800 case BODY: 801 case MESSAGE: 802 case UPDATED_MESSAGE: 803 case DELETED_MESSAGE: 804 case ATTACHMENT: 805 case MAILBOX: 806 case ACCOUNT: 807 case HOSTAUTH: 808 c = db.query(TABLE_NAMES[table], projection, 809 selection, selectionArgs, null, null, sortOrder); 810 break; 811 case BODY_ID: 812 case MESSAGE_ID: 813 case DELETED_MESSAGE_ID: 814 case UPDATED_MESSAGE_ID: 815 case ATTACHMENT_ID: 816 case MAILBOX_ID: 817 case ACCOUNT_ID: 818 case HOSTAUTH_ID: 819 id = uri.getPathSegments().get(1); 820 c = db.query(TABLE_NAMES[table], projection, 821 whereWithId(id, selection), selectionArgs, null, null, sortOrder); 822 break; 823 case ATTACHMENTS_MESSAGE_ID: 824 // All attachments for the given message 825 id = uri.getPathSegments().get(2); 826 c = db.query(Attachment.TABLE_NAME, projection, 827 whereWith(Attachment.MESSAGE_KEY + "=" + id, selection), 828 selectionArgs, null, null, sortOrder); 829 break; 830 default: 831 throw new IllegalArgumentException("Unknown URI " + uri); 832 } 833 834 if ((c != null) && !isTemporary()) { 835 c.setNotificationUri(getContext().getContentResolver(), notificationUri); 836 } 837 return c; 838 } 839 840 private String whereWithId(String id, String selection) { 841 StringBuilder sb = new StringBuilder(256); 842 sb.append("_id="); 843 sb.append(id); 844 if (selection != null) { 845 sb.append(" AND "); 846 sb.append(selection); 847 } 848 return sb.toString(); 849 } 850 851 private String whereWith(String where, String selection) { 852 StringBuilder sb = new StringBuilder(where); 853 if (selection != null) { 854 sb.append(" AND "); 855 sb.append(selection); 856 } 857 return sb.toString(); 858 } 859 860 @Override 861 public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { 862 int match = sURIMatcher.match(uri); 863 Context context = getContext(); 864 // See the comment at delete(), above 865 SQLiteDatabase db = getDatabase(context); 866 int table = match >> BASE_SHIFT; 867 int result; 868 869 if (Email.LOGD) { 870 Log.v(TAG, "EmailProvider.update: uri=" + uri + ", match is " + match); 871 } 872 873 // We do NOT allow setting of unreadCount via the provider 874 // This column is maintained via triggers 875 if (match == MAILBOX_ID || match == MAILBOX) { 876 values.remove(MailboxColumns.UNREAD_COUNT); 877 } 878 879 switch (match) { 880 case BODY_ID: 881 case MESSAGE_ID: 882 case SYNCED_MESSAGE_ID: 883 case UPDATED_MESSAGE_ID: 884 case ATTACHMENT_ID: 885 case MAILBOX_ID: 886 case ACCOUNT_ID: 887 case HOSTAUTH_ID: 888 String id = uri.getPathSegments().get(1); 889 if (match == SYNCED_MESSAGE_ID) { 890 // For synced messages, first copy the old message to the updated table 891 // Note the insert or ignore semantics, guaranteeing that only the first 892 // update will be reflected in the updated message table; therefore this row 893 // will always have the "original" data 894 db.execSQL(UPDATED_MESSAGE_INSERT + id); 895 } else if (match == MESSAGE_ID) { 896 db.execSQL(UPDATED_MESSAGE_DELETE + id); 897 } 898 result = db.update(TABLE_NAMES[table], values, whereWithId(id, selection), 899 selectionArgs); 900 break; 901 case BODY: 902 case MESSAGE: 903 case UPDATED_MESSAGE: 904 case ATTACHMENT: 905 case MAILBOX: 906 case ACCOUNT: 907 case HOSTAUTH: 908 result = db.update(TABLE_NAMES[table], values, selection, selectionArgs); 909 break; 910 default: 911 throw new IllegalArgumentException("Unknown URI " + uri); 912 } 913 914 getContext().getContentResolver().notifyChange(uri, null); 915 return result; 916 } 917 918 /* (non-Javadoc) 919 * @see android.content.ContentProvider#applyBatch(android.content.ContentProviderOperation) 920 */ 921 @Override 922 public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations) 923 throws OperationApplicationException { 924 Context context = getContext(); 925 SQLiteDatabase db = getDatabase(context); 926 db.beginTransaction(); 927 mInTransaction = true; 928 try { 929 ContentProviderResult[] results = super.applyBatch(operations); 930 db.setTransactionSuccessful(); 931 return results; 932 } finally { 933 db.endTransaction(); 934 mInTransaction = false; 935 } 936 } 937} 938