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