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