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