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