• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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