EmailProvider.java revision b6493a07ef625c0e290890c2e60256b47a066e5e
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.provider.EmailContent.Account; 20import com.android.email.provider.EmailContent.AccountColumns; 21import com.android.email.provider.EmailContent.Attachment; 22import com.android.email.provider.EmailContent.AttachmentColumns; 23import com.android.email.provider.EmailContent.Body; 24import com.android.email.provider.EmailContent.BodyColumns; 25import com.android.email.provider.EmailContent.HostAuth; 26import com.android.email.provider.EmailContent.HostAuthColumns; 27import com.android.email.provider.EmailContent.Mailbox; 28import com.android.email.provider.EmailContent.MailboxColumns; 29import com.android.email.provider.EmailContent.Message; 30import com.android.email.provider.EmailContent.MessageColumns; 31import com.android.email.provider.EmailContent.SyncColumns; 32 33import android.content.ContentProvider; 34import android.content.ContentProviderOperation; 35import android.content.ContentProviderResult; 36import android.content.ContentUris; 37import android.content.ContentValues; 38import android.content.Context; 39import android.content.OperationApplicationException; 40import android.content.UriMatcher; 41import android.database.Cursor; 42import android.database.SQLException; 43import android.database.sqlite.SQLiteDatabase; 44import android.database.sqlite.SQLiteOpenHelper; 45import android.net.Uri; 46import android.util.Config; 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 public static final int DATABASE_VERSION = 14; 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_ATTACHMENTS = MESSAGE_BASE + 1; 78 private static final int MESSAGE_ID = MESSAGE_BASE + 2; 79 private static final int SYNCED_MESSAGE_ID = MESSAGE_BASE + 3; 80 81 private static final int ATTACHMENT_BASE = 0x3000; 82 private static final int ATTACHMENT = ATTACHMENT_BASE; 83 private static final int ATTACHMENT_CONTENT = ATTACHMENT_BASE + 1; 84 private static final int ATTACHMENT_ID = ATTACHMENT_BASE + 2; 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 " + EmailContent.RECORD_ID + " in " + "(select " + EmailContent.RECORD_ID + 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 " + EmailContent.RECORD_ID + '='; 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 // The attachments of a specific message 176 matcher.addURI(EMAIL_AUTHORITY, "message/#/attachment", MESSAGE_ATTACHMENTS); 177 178 // A specific attachment 179 matcher.addURI(EMAIL_AUTHORITY, "attachment", ATTACHMENT); 180 // A specific attachment (the header information) 181 matcher.addURI(EMAIL_AUTHORITY, "attachment/#", ATTACHMENT_ID); 182 // The content for a specific attachment 183 // NOT IMPLEMENTED 184 matcher.addURI(EMAIL_AUTHORITY, "attachment/content/*", ATTACHMENT_CONTENT); 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 // A specific attachment 198 matcher.addURI(EMAIL_AUTHORITY, "hostauth", HOSTAUTH); // IMPLEMENTED 199 // A specific attachment (the header information) 200 matcher.addURI(EMAIL_AUTHORITY, "hostauth/#", HOSTAUTH_ID); // IMPLEMENTED 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 static void createMessageTable(SQLiteDatabase db) { 229 String messageColumns = MessageColumns.DISPLAY_NAME + " text, " 230 + MessageColumns.TIMESTAMP + " integer, " 231 + MessageColumns.SUBJECT + " text, " 232 + MessageColumns.PREVIEW + " text, " 233 + MessageColumns.FLAG_READ + " integer, " 234 + MessageColumns.FLAG_LOADED + " integer, " 235 + MessageColumns.FLAG_FAVORITE + " integer, " 236 + MessageColumns.FLAG_ATTACHMENT + " integer, " 237 + MessageColumns.FLAGS + " integer, " 238 + MessageColumns.TEXT_INFO + " text, " 239 + MessageColumns.HTML_INFO + " text, " 240 + MessageColumns.CLIENT_ID + " integer, " 241 + MessageColumns.MESSAGE_ID + " text, " 242 + MessageColumns.THREAD_ID + " text, " 243 + MessageColumns.MAILBOX_KEY + " integer, " 244 + MessageColumns.ACCOUNT_KEY + " integer, " 245 + MessageColumns.REFERENCE_KEY + " integer, " 246 + MessageColumns.SENDER_LIST + " text, " 247 + MessageColumns.FROM_LIST + " text, " 248 + MessageColumns.TO_LIST + " text, " 249 + MessageColumns.CC_LIST + " text, " 250 + MessageColumns.BCC_LIST + " text, " 251 + MessageColumns.REPLY_TO_LIST + " text" 252 + ");"; 253 254 // This String and the following String MUST have the same columns, except for the type 255 // of those columns! 256 String createString = " (" + EmailContent.RECORD_ID + " integer primary key autoincrement, " 257 + SyncColumns.ACCOUNT_KEY + " integer, " 258 + SyncColumns.SERVER_ID + " integer, " 259 + SyncColumns.SERVER_VERSION + " integer, " 260 + SyncColumns.DATA + " text, " 261 + SyncColumns.DIRTY_COUNT + " integer, " 262 + messageColumns; 263 264 // For the updated and deleted tables, the id is assigned, but we do want to keep track 265 // of the ORDER of updates using an autoincrement primary key. We use the DATA column 266 // at this point; it has no other function 267 String altCreateString = " (" + EmailContent.RECORD_ID + " integer, " 268 + SyncColumns.ACCOUNT_KEY + " integer, " 269 + SyncColumns.SERVER_ID + " integer, " 270 + SyncColumns.SERVER_VERSION + " integer, " 271 + SyncColumns.DATA + " integer primary key autoincrement, " 272 + SyncColumns.DIRTY_COUNT + " integer, " 273 + messageColumns; 274 275 // The three tables have the same schema 276 db.execSQL("create table " + Message.TABLE_NAME + createString); 277 db.execSQL("create table " + Message.UPDATED_TABLE_NAME + altCreateString); 278 db.execSQL("create table " + Message.DELETED_TABLE_NAME + altCreateString); 279 280 // For now, indices only on the Message table 281 db.execSQL("create index message_" + MessageColumns.TIMESTAMP 282 + " on " + Message.TABLE_NAME + " (" + MessageColumns.TIMESTAMP + ");"); 283 db.execSQL("create index message_" + MessageColumns.FLAG_READ 284 + " on " + Message.TABLE_NAME + " (" + MessageColumns.FLAG_READ + ");"); 285 db.execSQL("create index message_" + MessageColumns.FLAG_LOADED 286 + " on " + Message.TABLE_NAME + " (" + MessageColumns.FLAG_LOADED + ");"); 287 db.execSQL("create index message_" + MessageColumns.MAILBOX_KEY 288 + " on " + Message.TABLE_NAME + " (" + MessageColumns.MAILBOX_KEY + ");"); 289 db.execSQL("create index message_" + SyncColumns.SERVER_ID 290 + " on " + Message.TABLE_NAME + " (" + SyncColumns.SERVER_ID + ");"); 291 292 // Deleting a Message deletes all associated Attachments 293 // Deleting the associated Body cannot be done in a trigger, because the Body is stored 294 // in a separate database, and trigger cannot operate on attached databases. 295 db.execSQL("create trigger message_delete before delete on " + Message.TABLE_NAME + 296 " begin delete from " + Attachment.TABLE_NAME + 297 " where " + AttachmentColumns.MESSAGE_KEY + "=old." + EmailContent.RECORD_ID + 298 "; end"); 299 } 300 301 static void upgradeMessageTable(SQLiteDatabase db, int oldVersion, int newVersion) { 302 try { 303 db.execSQL("drop table " + Message.TABLE_NAME); 304 db.execSQL("drop table " + Message.UPDATED_TABLE_NAME); 305 db.execSQL("drop table " + Message.DELETED_TABLE_NAME); 306 } catch (SQLException e) { 307 } 308 createMessageTable(db); 309 } 310 311 static void createAccountTable(SQLiteDatabase db) { 312 String s = " (" + EmailContent.RECORD_ID + " integer primary key autoincrement, " 313 + AccountColumns.DISPLAY_NAME + " text, " 314 + AccountColumns.EMAIL_ADDRESS + " text, " 315 + AccountColumns.SYNC_KEY + " text, " 316 + AccountColumns.SYNC_LOOKBACK + " integer, " 317 + AccountColumns.SYNC_FREQUENCY + " text, " 318 + AccountColumns.HOST_AUTH_KEY_RECV + " integer, " 319 + AccountColumns.HOST_AUTH_KEY_SEND + " integer, " 320 + AccountColumns.FLAGS + " integer, " 321 + AccountColumns.IS_DEFAULT + " integer, " 322 + AccountColumns.COMPATIBILITY_UUID + " text, " 323 + AccountColumns.SENDER_NAME + " text, " 324 + AccountColumns.RINGTONE_URI + " text " 325 + ");"; 326 db.execSQL("create table " + Account.TABLE_NAME + s); 327 // Deleting an account deletes associated Mailboxes and HostAuth's 328 db.execSQL("create trigger account_delete before delete on " + Account.TABLE_NAME + 329 " begin delete from " + Mailbox.TABLE_NAME + 330 " where " + MailboxColumns.ACCOUNT_KEY + "=old." + EmailContent.RECORD_ID + 331 "; delete from " + HostAuth.TABLE_NAME + 332 " where " + HostAuthColumns.ACCOUNT_KEY + "=old." + EmailContent.RECORD_ID + 333 "; end"); 334 } 335 336 static void upgradeAccountTable(SQLiteDatabase db, int oldVersion, int newVersion) { 337 try { 338 db.execSQL("drop table " + Account.TABLE_NAME); 339 } catch (SQLException e) { 340 } 341 createAccountTable(db); 342 } 343 344 static void createHostAuthTable(SQLiteDatabase db) { 345 String s = " (" + EmailContent.RECORD_ID + " integer primary key autoincrement, " 346 + HostAuthColumns.PROTOCOL + " text, " 347 + HostAuthColumns.ADDRESS + " text, " 348 + HostAuthColumns.PORT + " integer, " 349 + HostAuthColumns.FLAGS + " integer, " 350 + HostAuthColumns.LOGIN + " text, " 351 + HostAuthColumns.PASSWORD + " text, " 352 + HostAuthColumns.DOMAIN + " text, " 353 + HostAuthColumns.ACCOUNT_KEY + " integer" 354 + ");"; 355 db.execSQL("create table " + HostAuth.TABLE_NAME + s); 356 } 357 358 static void upgradeHostAuthTable(SQLiteDatabase db, int oldVersion, int newVersion) { 359 try { 360 db.execSQL("drop table " + HostAuth.TABLE_NAME); 361 } catch (SQLException e) { 362 } 363 createHostAuthTable(db); 364 } 365 366 static void createMailboxTable(SQLiteDatabase db) { 367 String s = " (" + EmailContent.RECORD_ID + " integer primary key autoincrement, " 368 + MailboxColumns.DISPLAY_NAME + " text, " 369 + MailboxColumns.SERVER_ID + " text, " 370 + MailboxColumns.PARENT_SERVER_ID + " text, " 371 + MailboxColumns.ACCOUNT_KEY + " integer, " 372 + MailboxColumns.TYPE + " integer, " 373 + MailboxColumns.DELIMITER + " integer, " 374 + MailboxColumns.SYNC_KEY + " text, " 375 + MailboxColumns.SYNC_LOOKBACK + " integer, " 376 + MailboxColumns.SYNC_FREQUENCY+ " integer, " 377 + MailboxColumns.SYNC_TIME + " integer, " 378 + MailboxColumns.UNREAD_COUNT + " integer, " 379 + MailboxColumns.FLAG_VISIBLE + " integer, " 380 + MailboxColumns.FLAGS + " integer, " 381 + MailboxColumns.VISIBLE_LIMIT + " integer" 382 + ");"; 383 db.execSQL("create table " + Mailbox.TABLE_NAME + s); 384 db.execSQL("create index mailbox_" + MailboxColumns.SERVER_ID 385 + " on " + Mailbox.TABLE_NAME + " (" + MailboxColumns.SERVER_ID + ")"); 386 db.execSQL("create index mailbox_" + MailboxColumns.ACCOUNT_KEY 387 + " on " + Mailbox.TABLE_NAME + " (" + MailboxColumns.ACCOUNT_KEY + ")"); 388 // Deleting a Mailbox deletes associated Messages 389 db.execSQL("create trigger mailbox_delete before delete on " + Mailbox.TABLE_NAME + 390 " begin delete from " + Message.TABLE_NAME + 391 " where " + MessageColumns.MAILBOX_KEY + "=old." + EmailContent.RECORD_ID + 392 "; end"); 393 } 394 395 static void upgradeMailboxTable(SQLiteDatabase db, int oldVersion, int newVersion) { 396 try { 397 db.execSQL("drop table " + Mailbox.TABLE_NAME); 398 } catch (SQLException e) { 399 } 400 createMailboxTable(db); 401 } 402 403 static void createAttachmentTable(SQLiteDatabase db) { 404 String s = " (" + EmailContent.RECORD_ID + " integer primary key autoincrement, " 405 + AttachmentColumns.FILENAME + " text, " 406 + AttachmentColumns.MIME_TYPE + " text, " 407 + AttachmentColumns.SIZE + " integer, " 408 + AttachmentColumns.CONTENT_ID + " text, " 409 + AttachmentColumns.CONTENT_URI + " text, " 410 + AttachmentColumns.MESSAGE_KEY + " integer, " 411 + AttachmentColumns.LOCATION + " text, " 412 + AttachmentColumns.ENCODING + " text" 413 + ");"; 414 db.execSQL("create table " + Attachment.TABLE_NAME + s); 415 } 416 417 static void upgradeAttachmentTable(SQLiteDatabase db, int oldVersion, int newVersion) { 418 try { 419 db.execSQL("drop table " + Attachment.TABLE_NAME); 420 } catch (SQLException e) { 421 } 422 createAttachmentTable(db); 423 } 424 425 static void createBodyTable(SQLiteDatabase db) { 426 String s = " (" + EmailContent.RECORD_ID + " integer primary key autoincrement, " 427 + BodyColumns.MESSAGE_KEY + " integer, " 428 + BodyColumns.HTML_CONTENT + " text, " 429 + BodyColumns.TEXT_CONTENT + " text" 430 + ");"; 431 db.execSQL("create table " + Body.TABLE_NAME + s); 432 } 433 434 static void upgradeBodyTable(SQLiteDatabase db, int oldVersion, int newVersion) { 435 try { 436 db.execSQL("drop table " + Body.TABLE_NAME); 437 } catch (SQLException e) { 438 } 439 createBodyTable(db); 440 } 441 442 private final int mDatabaseVersion = DATABASE_VERSION; 443 private final int mBodyDatabaseVersion = BODY_DATABASE_VERSION; 444 445 private SQLiteDatabase mDatabase; 446 private SQLiteDatabase mBodyDatabase; 447 448 public SQLiteDatabase getDatabase(Context context) { 449 if (mDatabase != null) { 450 return mDatabase; 451 } 452 DatabaseHelper helper = new DatabaseHelper(context, DATABASE_NAME); 453 mDatabase = helper.getWritableDatabase(); 454 if (mDatabase != null) { 455 mDatabase.setLockingEnabled(true); 456 } 457 return mDatabase; 458 } 459 460 public SQLiteDatabase getBodyDatabase(Context context) { 461 if (mBodyDatabase != null) { 462 return mBodyDatabase; 463 } 464 BodyDatabaseHelper helper = new BodyDatabaseHelper(context, BODY_DATABASE_NAME); 465 mBodyDatabase = helper.getWritableDatabase(); 466 if (mBodyDatabase != null) { 467 mBodyDatabase.setLockingEnabled(true); 468 } 469 return mBodyDatabase; 470 } 471 472 private class BodyDatabaseHelper extends SQLiteOpenHelper { 473 BodyDatabaseHelper(Context context, String name) { 474 super(context, name, null, mBodyDatabaseVersion); 475 } 476 477 @Override 478 public void onCreate(SQLiteDatabase db) { 479 // Create all tables here; each class has its own method 480 createBodyTable(db); 481 } 482 483 @Override 484 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { 485 upgradeBodyTable(db, oldVersion, newVersion); 486 } 487 488 @Override 489 public void onOpen(SQLiteDatabase db) { 490 } 491 } 492 493 private class DatabaseHelper extends SQLiteOpenHelper { 494 DatabaseHelper(Context context, String name) { 495 super(context, name, null, mDatabaseVersion); 496 } 497 498 @Override 499 public void onCreate(SQLiteDatabase db) { 500 // Create all tables here; each class has its own method 501 createMessageTable(db); 502 createAttachmentTable(db); 503 createMailboxTable(db); 504 createHostAuthTable(db); 505 createAccountTable(db); 506 } 507 508 @Override 509 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { 510 upgradeMessageTable(db, oldVersion, newVersion); 511 upgradeAttachmentTable(db, oldVersion, newVersion); 512 upgradeMailboxTable(db, oldVersion, newVersion); 513 upgradeHostAuthTable(db, oldVersion, newVersion); 514 upgradeAccountTable(db, oldVersion, newVersion); 515 } 516 517 @Override 518 public void onOpen(SQLiteDatabase db) { 519 } 520 } 521 522 @Override 523 public int delete(Uri uri, String selection, String[] selectionArgs) { 524 int match = sURIMatcher.match(uri); 525 Context context = getContext(); 526 SQLiteDatabase db = (match >= BODY_BASE) ? getBodyDatabase(context) : getDatabase(context); 527 int table = match >> BASE_SHIFT; 528 String id = "0"; 529 boolean attachBodyDb = false; 530 boolean deleteOrphanedBodies = false; 531 532 if (Config.LOGV) { 533 Log.v(TAG, "EmailProvider.delete: uri=" + uri + ", match is " + match); 534 } 535 536 int result = -1; 537 538 try { 539 switch (match) { 540 // These are cases in which one or more Messages might get deleted, either by 541 // cascade or explicitly 542 case MAILBOX_ID: 543 case MAILBOX: 544 case ACCOUNT_ID: 545 case ACCOUNT: 546 case MESSAGE: 547 case SYNCED_MESSAGE_ID: 548 case MESSAGE_ID: 549 // Handle lost Body records here, since this cannot be done in a trigger 550 // The process is: 551 // 0) Activate the body database (bootstrap step, if doesn't exist yet) 552 // 1) Attach the Body database 553 // 2) Begin a transaction, ensuring that both databases are affected atomically 554 // 3) Do the requested deletion, with cascading deletions handled in triggers 555 // 4) End the transaction, committing all changes atomically 556 // 5) Detach the Body database 557 attachBodyDb = true; 558 getBodyDatabase(context); 559 String bodyFileName = mBodyDatabase.getPath(); 560 db.execSQL("attach \"" + bodyFileName + "\" as BodyDatabase"); 561 db.beginTransaction(); 562 if (match != MESSAGE_ID) { 563 deleteOrphanedBodies = true; 564 } 565 break; 566 } 567 switch (match) { 568 case BODY_ID: 569 case DELETED_MESSAGE_ID: 570 case SYNCED_MESSAGE_ID: 571 case MESSAGE_ID: 572 case UPDATED_MESSAGE_ID: 573 case ATTACHMENT_ID: 574 case MAILBOX_ID: 575 case ACCOUNT_ID: 576 case HOSTAUTH_ID: 577 id = uri.getPathSegments().get(1); 578 if (match == SYNCED_MESSAGE_ID) { 579 // For synced messages, first copy the old message to the deleted table and 580 // delete it from the updated table (in case it was updated first) 581 // Note that this is all within a transaction, for atomicity 582 db.execSQL(DELETED_MESSAGE_INSERT + id); 583 db.execSQL(UPDATED_MESSAGE_DELETE + id); 584 } 585 result = db.delete(TABLE_NAMES[table], whereWithId(id, selection), 586 selectionArgs); 587 break; 588 case BODY: 589 case MESSAGE: 590 case DELETED_MESSAGE: 591 case UPDATED_MESSAGE: 592 case ATTACHMENT: 593 case MAILBOX: 594 case ACCOUNT: 595 case HOSTAUTH: 596 result = db.delete(TABLE_NAMES[table], selection, selectionArgs); 597 break; 598 default: 599 throw new IllegalArgumentException("Unknown URI " + uri); 600 } 601 if (attachBodyDb) { 602 if (deleteOrphanedBodies) { 603 // Delete any orphaned Body records 604 db.execSQL(DELETE_ORPHAN_BODIES); 605 db.setTransactionSuccessful(); 606 } else { 607 // Delete the Body record associated with the deleted message 608 db.execSQL(DELETE_BODY + id); 609 db.setTransactionSuccessful(); 610 } 611 } 612 } finally { 613 if (attachBodyDb) { 614 db.endTransaction(); 615 db.execSQL("detach BodyDatabase"); 616 } 617 } 618 getContext().getContentResolver().notifyChange(uri, null); 619 return result; 620 } 621 622 @Override 623 // Use the email- prefix because message, mailbox, and account are so generic (e.g. SMS, IM) 624 public String getType(Uri uri) { 625 int match = sURIMatcher.match(uri); 626 switch (match) { 627 case BODY_ID: 628 return "vnd.android.cursor.item/email-body"; 629 case BODY: 630 return "vnd.android.cursor.dir/email-message"; 631 case UPDATED_MESSAGE_ID: 632 case MESSAGE_ID: 633 return "vnd.android.cursor.item/email-message"; 634 case MAILBOX_MESSAGES: 635 case UPDATED_MESSAGE: 636 case MESSAGE: 637 return "vnd.android.cursor.dir/email-message"; 638 case ACCOUNT_MAILBOXES: 639 case MAILBOX: 640 return "vnd.android.cursor.dir/email-mailbox"; 641 case MAILBOX_ID: 642 return "vnd.android.cursor.item/email-mailbox"; 643 case ACCOUNT: 644 return "vnd.android.cursor.dir/email-account"; 645 case ACCOUNT_ID: 646 return "vnd.android.cursor.item/email-account"; 647 case MESSAGE_ATTACHMENTS: 648 case ATTACHMENT: 649 return "vnd.android.cursor.dir/email-attachment"; 650 case ATTACHMENT_ID: 651 return "vnd.android.cursor.item/email-attachment"; 652 case HOSTAUTH: 653 return "vnd.android.cursor.dir/email-hostauth"; 654 case HOSTAUTH_ID: 655 return "vnd.android.cursor.item/email-hostauth"; 656 default: 657 throw new IllegalArgumentException("Unknown URI " + uri); 658 } 659 } 660 661 @Override 662 public Uri insert(Uri uri, ContentValues values) { 663 int match = sURIMatcher.match(uri); 664 Context context = getContext(); 665 SQLiteDatabase db = (match >= BODY_BASE) ? getBodyDatabase(context) : getDatabase(context); 666 int table = match >> BASE_SHIFT; 667 long id; 668 669 if (Config.LOGV) { 670 Log.v(TAG, "EmailProvider.insert: uri=" + uri + ", match is " + match); 671 } 672 673 Uri resultUri = null; 674 675 switch (match) { 676 case BODY: 677 case MESSAGE: 678 case ATTACHMENT: 679 case MAILBOX: 680 case ACCOUNT: 681 case HOSTAUTH: 682 // Make sure all new message records have dirty count of 0 683 if (match == MESSAGE) { 684 values.put(SyncColumns.DIRTY_COUNT, 0); 685 } 686 id = db.insert(TABLE_NAMES[table], "foo", values); 687 resultUri = ContentUris.withAppendedId(uri, id); 688 break; 689 case MAILBOX_ID: 690 // This implies adding a message to a mailbox 691 // Hmm, one problem here is that we can't link the account as well, so it must be 692 // already in the values... 693 id = Long.parseLong(uri.getPathSegments().get(1)); 694 values.put(MessageColumns.MAILBOX_KEY, id); 695 resultUri = insert(Message.CONTENT_URI, values); 696 break; 697 case MESSAGE_ID: 698 // This implies adding an attachment to a message. 699 id = Long.parseLong(uri.getPathSegments().get(1)); 700 values.put(AttachmentColumns.MESSAGE_KEY, id); 701 resultUri = insert(Attachment.CONTENT_URI, values); 702 break; 703 case ACCOUNT_ID: 704 // This implies adding a mailbox to an account. 705 id = Long.parseLong(uri.getPathSegments().get(1)); 706 values.put(MailboxColumns.ACCOUNT_KEY, id); 707 resultUri = insert(Mailbox.CONTENT_URI, values); 708 break; 709 case MESSAGE_ATTACHMENTS: 710 id = db.insert(TABLE_NAMES[table], "foo", values); 711 resultUri = ContentUris.withAppendedId(Attachment.CONTENT_URI, id); 712 break; 713 default: 714 throw new IllegalArgumentException("Unknown URL " + uri); 715 } 716 717 // Notify with the base uri, not the new uri (nobody is watching a new record) 718 getContext().getContentResolver().notifyChange(uri, null); 719 return resultUri; 720 } 721 722 @Override 723 public boolean onCreate() { 724 // TODO Auto-generated method stub 725 return false; 726 } 727 728 @Override 729 public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, 730 String sortOrder) { 731 Cursor c = null; 732 Uri notificationUri = EmailContent.CONTENT_URI; 733 int match = sURIMatcher.match(uri); 734 Context context = getContext(); 735 SQLiteDatabase db = (match >= BODY_BASE) ? getBodyDatabase(context) : getDatabase(context); 736 int table = match >> BASE_SHIFT; 737 String id; 738 739 if (Config.LOGV) { 740 Log.v(TAG, "EmailProvider.query: uri=" + uri + ", match is " + match); 741 } 742 743 switch (match) { 744 case BODY: 745 case MESSAGE: 746 case UPDATED_MESSAGE: 747 case DELETED_MESSAGE: 748 case ATTACHMENT: 749 case MAILBOX: 750 case ACCOUNT: 751 case HOSTAUTH: 752 c = db.query(TABLE_NAMES[table], projection, 753 selection, selectionArgs, null, null, sortOrder); 754 break; 755 case BODY_ID: 756 case MESSAGE_ID: 757 case DELETED_MESSAGE_ID: 758 case UPDATED_MESSAGE_ID: 759 case ATTACHMENT_ID: 760 case MAILBOX_ID: 761 case ACCOUNT_ID: 762 case HOSTAUTH_ID: 763 id = uri.getPathSegments().get(1); 764 c = db.query(TABLE_NAMES[table], projection, 765 whereWithId(id, selection), selectionArgs, null, null, sortOrder); 766 break; 767 case MESSAGE_ATTACHMENTS: 768 // All attachments for the given message 769 id = uri.getPathSegments().get(1); 770 c = db.query(Attachment.TABLE_NAME, projection, 771 whereWith(Attachment.MESSAGE_KEY + "=" + id, selection), 772 selectionArgs, null, null, sortOrder); 773 break; 774 default: 775 throw new IllegalArgumentException("Unknown URI " + uri); 776 } 777 778 if ((c != null) && !isTemporary()) { 779 c.setNotificationUri(getContext().getContentResolver(), notificationUri); 780 } 781 return c; 782 } 783 784 private String whereWithId(String id, String selection) { 785 StringBuilder sb = new StringBuilder(256); 786 sb.append("_id="); 787 sb.append(id); 788 if (selection != null) { 789 sb.append(" AND "); 790 sb.append(selection); 791 } 792 return sb.toString(); 793 } 794 795 private String whereWith(String where, String selection) { 796 StringBuilder sb = new StringBuilder(where); 797 if (selection != null) { 798 sb.append(" AND "); 799 sb.append(selection); 800 } 801 return sb.toString(); 802 } 803 804 @Override 805 public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { 806 int match = sURIMatcher.match(uri); 807 Context context = getContext(); 808 SQLiteDatabase db = (match >= BODY_BASE) ? getBodyDatabase(context) : getDatabase(context); 809 int table = match >> BASE_SHIFT; 810 int result; 811 812 if (Config.LOGV) { 813 Log.v(TAG, "EmailProvider.update: uri=" + uri + ", match is " + match); 814 } 815 816 switch (match) { 817 case BODY_ID: 818 case MESSAGE_ID: 819 case SYNCED_MESSAGE_ID: 820 case UPDATED_MESSAGE_ID: 821 case ATTACHMENT_ID: 822 case MAILBOX_ID: 823 case ACCOUNT_ID: 824 case HOSTAUTH_ID: 825 String id = uri.getPathSegments().get(1); 826 if (match == SYNCED_MESSAGE_ID) { 827 // For synced messages, first copy the old message to the updated table 828 // Note the insert or ignore semantics, guaranteeing that only the first 829 // update will be reflected in the updated message table; therefore this row 830 // will always have the "original" data 831 db.execSQL(UPDATED_MESSAGE_INSERT + id); 832 } else if (match == MESSAGE_ID) { 833 db.execSQL(UPDATED_MESSAGE_DELETE + id); 834 } 835 result = db.update(TABLE_NAMES[table], values, whereWithId(id, selection), 836 selectionArgs); 837 break; 838 case BODY: 839 case MESSAGE: 840 case UPDATED_MESSAGE: 841 case ATTACHMENT: 842 case MAILBOX: 843 case ACCOUNT: 844 case HOSTAUTH: 845 result = db.update(TABLE_NAMES[table], values, selection, selectionArgs); 846 break; 847 default: 848 throw new IllegalArgumentException("Unknown URI " + uri); 849 } 850 851 getContext().getContentResolver().notifyChange(uri, null); 852 return result; 853 } 854 855 /* (non-Javadoc) 856 * @see android.content.ContentProvider#applyBatch(android.content.ContentProviderOperation) 857 * 858 * TODO: How do we call notifyChange() or do we need to - does this call the various 859 * update/insert/delete calls? 860 */ 861 public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations) 862 throws OperationApplicationException { 863 SQLiteDatabase db = getDatabase(getContext()); 864 db.beginTransaction(); 865 try { 866 ContentProviderResult[] results = super.applyBatch(operations); 867 db.setTransactionSuccessful(); 868 return results; 869 } finally { 870 db.endTransaction(); 871 } 872 } 873} 874