EmailProvider.java revision e34525d0f026a7467cee1cb5fddcf25ba6f35207
1/*
2 * Copyright (C) 2009 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.email.provider;
18
19import com.android.email.provider.EmailContent.Account;
20import com.android.email.provider.EmailContent.AccountColumns;
21import com.android.email.provider.EmailContent.Attachment;
22import com.android.email.provider.EmailContent.AttachmentColumns;
23import com.android.email.provider.EmailContent.Body;
24import com.android.email.provider.EmailContent.BodyColumns;
25import com.android.email.provider.EmailContent.HostAuth;
26import com.android.email.provider.EmailContent.HostAuthColumns;
27import com.android.email.provider.EmailContent.Mailbox;
28import com.android.email.provider.EmailContent.MailboxColumns;
29import com.android.email.provider.EmailContent.Message;
30import com.android.email.provider.EmailContent.MessageColumns;
31import com.android.email.provider.EmailContent.SyncColumns;
32
33import android.content.ContentProvider;
34import android.content.ContentProviderOperation;
35import android.content.ContentProviderResult;
36import android.content.ContentUris;
37import android.content.ContentValues;
38import android.content.Context;
39import android.content.OperationApplicationException;
40import android.content.UriMatcher;
41import android.database.Cursor;
42import android.database.SQLException;
43import android.database.sqlite.SQLiteDatabase;
44import android.database.sqlite.SQLiteOpenHelper;
45import android.net.Uri;
46import android.util.Config;
47import android.util.Log;
48
49import java.util.ArrayList;
50
51/*
52 * TODO
53 *
54 * Add Email.Body class and support, now that this is stored separately
55 * Handle deletion cascades (either w/ triggers or code)
56 *
57 */
58public class EmailProvider extends ContentProvider {
59
60    private static final String TAG = "EmailProvider";
61
62    static final String DATABASE_NAME = "EmailProvider.db";
63    static final String BODY_DATABASE_NAME = "EmailProviderBody.db";
64
65    // In these early versions, updating the database version will cause all tables to be deleted
66    // Obviously, we'll handle upgrades differently once things are a bit stable
67    public static final int DATABASE_VERSION = 12;
68    public static final int BODY_DATABASE_VERSION = 1;
69
70    public static final String EMAIL_AUTHORITY = "com.android.email.provider";
71
72    private static final int ACCOUNT_BASE = 0;
73    private static final int ACCOUNT = ACCOUNT_BASE;
74    private static final int ACCOUNT_MAILBOXES = ACCOUNT_BASE + 1;
75    private static final int ACCOUNT_ID = ACCOUNT_BASE + 2;
76
77    private static final int MAILBOX_BASE = 0x1000;
78    private static final int MAILBOX = MAILBOX_BASE;
79    private static final int MAILBOX_MESSAGES = MAILBOX_BASE + 1;
80    private static final int MAILBOX_ID = MAILBOX_BASE + 2;
81
82    private static final int MESSAGE_BASE = 0x2000;
83    private static final int MESSAGE = MESSAGE_BASE;
84    private static final int MESSAGE_ATTACHMENTS = MESSAGE_BASE + 1;
85    private static final int MESSAGE_ID = MESSAGE_BASE + 2;
86
87    private static final int ATTACHMENT_BASE = 0x3000;
88    private static final int ATTACHMENT = ATTACHMENT_BASE;
89    private static final int ATTACHMENT_CONTENT = ATTACHMENT_BASE + 1;
90    private static final int ATTACHMENT_ID = ATTACHMENT_BASE + 2;
91
92     // TEMPORARY UNTIL ACCOUNT MANAGER CAN BE USED
93    private static final int HOSTAUTH_BASE = 0x4000;
94    private static final int HOSTAUTH = HOSTAUTH_BASE;
95    private static final int HOSTAUTH_ID = HOSTAUTH_BASE + 1;
96
97    private static final int UPDATED_MESSAGE_BASE = 0x5000;
98    private static final int UPDATED_MESSAGE = UPDATED_MESSAGE_BASE;
99    private static final int UPDATED_MESSAGE_ATTACHMENTS = UPDATED_MESSAGE_BASE + 1;
100    private static final int UPDATED_MESSAGE_ID = UPDATED_MESSAGE_BASE + 2;
101
102    // BODY_BASE MAY BE CHANGED BUT IT MUST BE HIGHEST BASE VALUE (it's in a different database!)
103    private static final int BODY_BASE = 0x6000;
104    private static final int BODY = BODY_BASE;
105    private static final int BODY_ID = BODY_BASE + 1;
106    private static final int BODY_HTML = BODY_BASE + 2;
107    private static final int BODY_TEXT = BODY_BASE + 4;
108
109
110    private static final int BASE_SHIFT = 12;  // 12 bits to the base type: 0, 0x1000, 0x2000, etc.
111
112    private static final String[] TABLE_NAMES = {
113        EmailContent.Account.TABLE_NAME,
114        EmailContent.Mailbox.TABLE_NAME,
115        EmailContent.Message.TABLE_NAME,
116        EmailContent.Attachment.TABLE_NAME,
117        EmailContent.HostAuth.TABLE_NAME,
118        EmailContent.Message.UPDATES_TABLE_NAME,
119        EmailContent.Body.TABLE_NAME
120    };
121
122    private static final UriMatcher sURIMatcher = new UriMatcher(UriMatcher.NO_MATCH);
123
124    static {
125        // Email URI matching table
126        UriMatcher matcher = sURIMatcher;
127        // All accounts
128        matcher.addURI(EMAIL_AUTHORITY, "account", ACCOUNT); // IMPLEMENTED
129        // A specific account
130        // insert into this URI causes a mailbox to be added to the account
131        matcher.addURI(EMAIL_AUTHORITY, "account/#", ACCOUNT_ID);  // IMPLEMENTED
132        // The mailboxes in a specific account
133        matcher.addURI(EMAIL_AUTHORITY, "account/#/mailbox", ACCOUNT_MAILBOXES);
134        // All mailboxes
135        matcher.addURI(EMAIL_AUTHORITY, "mailbox", MAILBOX);  // IMPLEMENTED
136        // A specific mailbox
137        // insert into this URI causes a message to be added to the mailbox
138        // ** NOTE For now, the accountKey must be set manually in the values!
139        matcher.addURI(EMAIL_AUTHORITY, "mailbox/#", MAILBOX_ID);  // IMPLEMENTED
140        // The messages in a specific mailbox
141        matcher.addURI(EMAIL_AUTHORITY, "mailbox/#/message", MAILBOX_MESSAGES);
142        // All messages
143        matcher.addURI(EMAIL_AUTHORITY, "message", MESSAGE); // IMPLEMENTED
144        // A specific message
145        // insert into this URI causes an attachment to be added to the message
146        matcher.addURI(EMAIL_AUTHORITY, "message/#", MESSAGE_ID); // IMPLEMENTED
147        // The attachments of a specific message
148        matcher.addURI(EMAIL_AUTHORITY, "message/#/attachment", MESSAGE_ATTACHMENTS); // IMPLEMENTED
149        // All updated messages
150        matcher.addURI(EMAIL_AUTHORITY, "updatedMessage", UPDATED_MESSAGE); // IMPLEMENTED
151        // A specific updated message
152        matcher.addURI(EMAIL_AUTHORITY, "updatedMessage/#", UPDATED_MESSAGE_ID); // IMPLEMENTED
153        // The attachments of a specific updated message
154        matcher.addURI(EMAIL_AUTHORITY, "updatedMessage/#/attachment", UPDATED_MESSAGE_ATTACHMENTS);
155        // A specific attachment
156        matcher.addURI(EMAIL_AUTHORITY, "attachment", ATTACHMENT); // IMPLEMENTED
157        // A specific attachment (the header information)
158        matcher.addURI(EMAIL_AUTHORITY, "attachment/#", ATTACHMENT_ID);  // IMPLEMENTED
159        // The content for a specific attachment
160        matcher.addURI(EMAIL_AUTHORITY, "attachment/content/*", ATTACHMENT_CONTENT);
161
162        // All mail bodies
163        matcher.addURI(EMAIL_AUTHORITY, "body", BODY);
164        // A specific mail body
165        matcher.addURI(EMAIL_AUTHORITY, "body/#", BODY_ID);
166        // The HTML part of a specific mail body
167        matcher.addURI(EMAIL_AUTHORITY, "body/#/html", BODY_HTML);
168        // The plain text part of a specific mail body
169        matcher.addURI(EMAIL_AUTHORITY, "body/#/text", BODY_TEXT);
170
171        // A specific attachment
172        matcher.addURI(EMAIL_AUTHORITY, "hostauth", HOSTAUTH); // IMPLEMENTED
173        // A specific attachment (the header information)
174        matcher.addURI(EMAIL_AUTHORITY, "hostauth/#", HOSTAUTH_ID);  // IMPLEMENTED
175
176    }
177
178    static void createMessageTable(SQLiteDatabase db) {
179        String s = " (" + EmailContent.RECORD_ID + " integer primary key autoincrement, "
180            + SyncColumns.ACCOUNT_KEY + " integer, "
181            + SyncColumns.SERVER_ID + " integer, "
182            + SyncColumns.SERVER_VERSION + " integer, "
183            + SyncColumns.DATA + " text, "
184            + SyncColumns.DIRTY_COUNT + " integer, "
185            + MessageColumns.DISPLAY_NAME + " text, "
186            + MessageColumns.TIMESTAMP + " integer, "
187            + MessageColumns.SUBJECT + " text, "
188            + MessageColumns.PREVIEW + " text, "
189            + MessageColumns.FLAG_READ + " integer, "
190            + MessageColumns.FLAG_LOADED + " integer, "
191            + MessageColumns.FLAG_FAVORITE + " integer, "
192            + MessageColumns.FLAG_ATTACHMENT + " integer, "
193            + MessageColumns.FLAGS + " integer, "
194            + MessageColumns.TEXT_INFO + " text, "
195            + MessageColumns.HTML_INFO + " text, "
196            + MessageColumns.CLIENT_ID + " integer, "
197            + MessageColumns.MESSAGE_ID + " text, "
198            + MessageColumns.THREAD_ID + " text, "
199            + MessageColumns.BODY_ID + " integer, "
200            + MessageColumns.MAILBOX_KEY + " integer, "
201            + MessageColumns.ACCOUNT_KEY + " integer, "
202            + MessageColumns.REFERENCE_KEY + " integer, "
203            + MessageColumns.SENDER_LIST + " text, "
204            + MessageColumns.FROM_LIST + " text, "
205            + MessageColumns.TO_LIST + " text, "
206            + MessageColumns.CC_LIST + " text, "
207            + MessageColumns.BCC_LIST + " text, "
208            + MessageColumns.REPLY_TO_LIST + " text"
209            + ");";
210        db.execSQL("create table " + Message.TABLE_NAME + s);
211        db.execSQL("create table " + Message.UPDATES_TABLE_NAME + s);
212        db.execSQL("create index message_" + MessageColumns.TIMESTAMP
213                + " on " + Message.TABLE_NAME + " (" + MessageColumns.TIMESTAMP + ");");
214        db.execSQL("create index message_" + MessageColumns.FLAG_READ
215                + " on " + Message.TABLE_NAME + " (" + MessageColumns.FLAG_READ + ");");
216        db.execSQL("create index message_" + MessageColumns.FLAG_LOADED
217                + " on " + Message.TABLE_NAME + " (" + MessageColumns.FLAG_LOADED + ");");
218        db.execSQL("create index message_" + MessageColumns.MAILBOX_KEY
219                + " on " + Message.TABLE_NAME + " (" + MessageColumns.MAILBOX_KEY + ");");
220        db.execSQL("create index message_" + SyncColumns.SERVER_ID
221                + " on " + Message.TABLE_NAME + " (" + SyncColumns.SERVER_ID + ");");
222
223        // When a record is FIRST updated, copy the original data into the updates table
224        // Server version not null tells us that this is synced back to the server
225        // The sync engine can determine what needs to go up to the server
226        db.execSQL("CREATE TRIGGER message_update UPDATE ON " + Message.TABLE_NAME +
227                " WHEN old." + SyncColumns.DIRTY_COUNT + "=0 AND new." + SyncColumns.DIRTY_COUNT +
228                "!=0 AND old." + SyncColumns.SERVER_VERSION + " IS NOT NULL " +
229                "BEGIN INSERT INTO " + Message.UPDATES_TABLE_NAME +
230                " SELECT * FROM message WHERE " +
231                EmailContent.RECORD_ID + "=old." + EmailContent.RECORD_ID + ";END");
232
233        // Deleted records are automatically copied into updates table
234        // TODO How will the sync adapter know that these records are deletions?
235        // Answer:  WeDo we may have to use an EXCEPT clause, as in
236        // SELECT id from Message_Update where mailboxKey=n EXCEPT SELECT id from Message?
237        db.execSQL("CREATE TRIGGER message_delete BEFORE DELETE ON " + Message.TABLE_NAME +
238                " BEGIN INSERT INTO " + Message.UPDATES_TABLE_NAME +
239                " SELECT * FROM message WHERE " + EmailContent.RECORD_ID +
240                "=old." + EmailContent.RECORD_ID + ";END");
241    }
242
243    static void upgradeMessageTable(SQLiteDatabase db, int oldVersion, int newVersion) {
244        db.execSQL("drop table " + Message.TABLE_NAME);
245        db.execSQL("drop table " + Message.UPDATES_TABLE_NAME);
246        createMessageTable(db);
247    }
248
249    static void createAccountTable(SQLiteDatabase db) {
250        String s = " (" + EmailContent.RECORD_ID + " integer primary key autoincrement, "
251            + AccountColumns.DISPLAY_NAME + " text, "
252            + AccountColumns.EMAIL_ADDRESS + " text, "
253            + AccountColumns.SYNC_KEY + " text, "
254            + AccountColumns.SYNC_LOOKBACK + " integer, "
255            + AccountColumns.SYNC_FREQUENCY + " text, "
256            + AccountColumns.HOST_AUTH_KEY_RECV + " integer, "
257            + AccountColumns.HOST_AUTH_KEY_SEND + " integer, "
258            + AccountColumns.FLAGS + " integer, "
259            + AccountColumns.IS_DEFAULT + " integer, "
260            + AccountColumns.COMPATIBILITY_UUID + " text, "
261            + AccountColumns.SENDER_NAME + " text, "
262            + AccountColumns.RINGTONE_URI + " text "
263            + ");";
264        db.execSQL("create table " + Account.TABLE_NAME + s);
265    }
266
267    static void upgradeAccountTable(SQLiteDatabase db, int oldVersion, int newVersion) {
268        try {
269            db.execSQL("drop table " +  Account.TABLE_NAME);
270        } catch (SQLException e) {
271        }
272        createAccountTable(db);
273    }
274
275    static void createHostAuthTable(SQLiteDatabase db) {
276        String s = " (" + EmailContent.RECORD_ID + " integer primary key autoincrement, "
277            + HostAuthColumns.PROTOCOL + " text, "
278            + HostAuthColumns.ADDRESS + " text, "
279            + HostAuthColumns.PORT + " integer, "
280            + HostAuthColumns.FLAGS + " integer, "
281            + HostAuthColumns.LOGIN + " text, "
282            + HostAuthColumns.PASSWORD + " text, "
283            + HostAuthColumns.DOMAIN + " text, "
284            + HostAuthColumns.ACCOUNT_KEY + " integer"
285            + ");";
286        db.execSQL("create table " + HostAuth.TABLE_NAME + s);
287    }
288
289    static void upgradeHostAuthTable(SQLiteDatabase db, int oldVersion, int newVersion) {
290        try {
291            db.execSQL("drop table " + HostAuth.TABLE_NAME);
292        } catch (SQLException e) {
293        }
294        createHostAuthTable(db);
295    }
296
297   static void createMailboxTable(SQLiteDatabase db) {
298        String s = " (" + EmailContent.RECORD_ID + " integer primary key autoincrement, "
299            + MailboxColumns.DISPLAY_NAME + " text, "
300            + MailboxColumns.SERVER_ID + " text, "
301            + MailboxColumns.PARENT_SERVER_ID + " text, "
302            + MailboxColumns.ACCOUNT_KEY + " integer, "
303            + MailboxColumns.TYPE + " integer, "
304            + MailboxColumns.DELIMITER + " integer, "
305            + MailboxColumns.SYNC_KEY + " text, "
306            + MailboxColumns.SYNC_LOOKBACK + " integer, "
307            + MailboxColumns.SYNC_FREQUENCY+ " integer, "
308            + MailboxColumns.SYNC_TIME + " integer, "
309            + MailboxColumns.UNREAD_COUNT + " integer, "
310            + MailboxColumns.FLAG_VISIBLE + " integer, "
311            + MailboxColumns.FLAGS + " integer, "
312            + MailboxColumns.VISIBLE_LIMIT + " integer"
313            + ");";
314            db.execSQL("create table " + Mailbox.TABLE_NAME + s);
315        db.execSQL("create index mailbox_" + MailboxColumns.SERVER_ID
316                + " on " + Mailbox.TABLE_NAME + " (" + MailboxColumns.SERVER_ID + ")");
317        db.execSQL("create index mailbox_" + MailboxColumns.ACCOUNT_KEY
318                + " on " + Mailbox.TABLE_NAME + " (" + MailboxColumns.ACCOUNT_KEY + ")");
319
320    }
321
322    static void upgradeMailboxTable(SQLiteDatabase db, int oldVersion, int newVersion) {
323        try {
324            db.execSQL("drop table " + Mailbox.TABLE_NAME);
325        } catch (SQLException e) {
326        }
327        createMailboxTable(db);
328    }
329
330    static void createAttachmentTable(SQLiteDatabase db) {
331        String s = " (" + EmailContent.RECORD_ID + " integer primary key autoincrement, "
332            + AttachmentColumns.FILENAME + " text, "
333            + AttachmentColumns.MIME_TYPE + " text, "
334            + AttachmentColumns.SIZE + " integer, "
335            + AttachmentColumns.CONTENT_ID + " text, "
336            + AttachmentColumns.CONTENT_URI + " text, "
337            + AttachmentColumns.MESSAGE_KEY + " integer, "
338            + AttachmentColumns.LOCATION + " text, "
339            + AttachmentColumns.ENCODING + " text"
340            + ");";
341        db.execSQL("create table " + Attachment.TABLE_NAME + s);
342    }
343
344    static void upgradeAttachmentTable(SQLiteDatabase db, int oldVersion, int newVersion) {
345        try {
346            db.execSQL("drop table " + Attachment.TABLE_NAME);
347        } catch (SQLException e) {
348        }
349        createAttachmentTable(db);
350    }
351
352    static void createBodyTable(SQLiteDatabase db) {
353        String s = " (" + EmailContent.RECORD_ID + " integer primary key autoincrement, "
354            + BodyColumns.MESSAGE_KEY + " integer, "
355            + BodyColumns.HTML_CONTENT + " text, "
356            + BodyColumns.TEXT_CONTENT + " text"
357            + ");";
358        db.execSQL("create table " + Body.TABLE_NAME + s);
359    }
360
361    static void upgradeBodyTable(SQLiteDatabase db, int oldVersion, int newVersion) {
362        db.execSQL("drop table " + Body.TABLE_NAME);
363        createBodyTable(db);
364    }
365
366
367
368    private final int mDatabaseVersion = DATABASE_VERSION;
369    private final int mBodyDatabaseVersion = BODY_DATABASE_VERSION;
370
371    private SQLiteDatabase mDatabase;
372    private SQLiteDatabase mBodyDatabase;
373
374    public SQLiteDatabase getDatabase(Context context) {
375        if (mDatabase !=  null) {
376            return mDatabase;
377        }
378        DatabaseHelper helper = new DatabaseHelper(context, DATABASE_NAME);
379        mDatabase = helper.getWritableDatabase();
380        if (mDatabase != null) {
381            mDatabase.setLockingEnabled(true);
382        }
383        return mDatabase;
384    }
385
386    public SQLiteDatabase getBodyDatabase(Context context) {
387        if (mBodyDatabase !=  null) {
388            return mBodyDatabase;
389        }
390        BodyDatabaseHelper helper = new BodyDatabaseHelper(context, BODY_DATABASE_NAME);
391        mBodyDatabase = helper.getWritableDatabase();
392        if (mBodyDatabase != null) {
393            mBodyDatabase.setLockingEnabled(true);
394        }
395        return mBodyDatabase;
396    }
397
398    private class BodyDatabaseHelper extends SQLiteOpenHelper {
399        BodyDatabaseHelper(Context context, String name) {
400             super(context, name, null, mBodyDatabaseVersion);
401        }
402
403        @Override
404        public void onCreate(SQLiteDatabase db) {
405            // Create all tables here; each class has its own method
406            createBodyTable(db);
407        }
408
409        @Override
410        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
411            upgradeBodyTable(db, oldVersion, newVersion);
412        }
413
414        @Override
415        public void onOpen(SQLiteDatabase db) {
416        }
417    }
418
419    private class DatabaseHelper extends SQLiteOpenHelper {
420        DatabaseHelper(Context context, String name) {
421             super(context, name, null, mDatabaseVersion);
422        }
423
424        @Override
425        public void onCreate(SQLiteDatabase db) {
426            // Create all tables here; each class has its own method
427            createMessageTable(db);
428            createAttachmentTable(db);
429            createMailboxTable(db);
430            createHostAuthTable(db);
431            createAccountTable(db);
432       }
433
434        @Override
435        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
436            upgradeMessageTable(db, oldVersion, newVersion);
437            upgradeAttachmentTable(db, oldVersion, newVersion);
438            upgradeMailboxTable(db, oldVersion, newVersion);
439            upgradeHostAuthTable(db, oldVersion, newVersion);
440            upgradeAccountTable(db, oldVersion, newVersion);
441        }
442
443        @Override
444        public void onOpen(SQLiteDatabase db) {
445        }
446    }
447
448    @Override
449    public int delete(Uri uri, String selection, String[] selectionArgs) {
450        int match = sURIMatcher.match(uri);
451        Context context = getContext();
452        SQLiteDatabase db = (match >= BODY_BASE) ? getBodyDatabase(context) : getDatabase(context);
453        int table = match >> BASE_SHIFT;
454        String id;
455
456        if (Config.LOGV) {
457            Log.v(TAG, "EmailProvider.delete: uri=" + uri + ", match is " + match);
458        }
459
460        int result;
461        switch (match) {
462            case BODY_ID:
463            case MESSAGE_ID:
464            case UPDATED_MESSAGE_ID:
465            case ATTACHMENT_ID:
466            case MAILBOX_ID:
467            case ACCOUNT_ID:
468            case HOSTAUTH_ID:
469                id = uri.getPathSegments().get(1);
470                result = db.delete(TABLE_NAMES[table], whereWithId(id, selection), selectionArgs);
471                break;
472            case BODY:
473            case MESSAGE:
474            case UPDATED_MESSAGE:
475            case ATTACHMENT:
476            case MAILBOX:
477            case ACCOUNT:
478            case HOSTAUTH:
479                result = db.delete(TABLE_NAMES[table], selection, selectionArgs);
480                break;
481            default:
482                throw new IllegalArgumentException("Unknown URI " + uri);
483        }
484
485        getContext().getContentResolver().notifyChange(uri, null);
486        return result;
487    }
488
489    @Override
490    // Use the email- prefix because message, mailbox, and account are so generic (e.g. SMS, IM)
491    public String getType(Uri uri) {
492        int match = sURIMatcher.match(uri);
493        switch (match) {
494            case BODY_ID:
495                return "vnd.android.cursor.item/email-body";
496            case BODY:
497                return "vnd.android.cursor.dir/email-message";
498            case UPDATED_MESSAGE_ID:
499            case MESSAGE_ID:
500                return "vnd.android.cursor.item/email-message";
501            case MAILBOX_MESSAGES:
502            case UPDATED_MESSAGE:
503            case MESSAGE:
504                return "vnd.android.cursor.dir/email-message";
505            case ACCOUNT_MAILBOXES:
506            case MAILBOX:
507                return "vnd.android.cursor.dir/email-mailbox";
508            case MAILBOX_ID:
509                return "vnd.android.cursor.item/email-mailbox";
510            case ACCOUNT:
511                return "vnd.android.cursor.dir/email-account";
512            case ACCOUNT_ID:
513                return "vnd.android.cursor.item/email-account";
514            case MESSAGE_ATTACHMENTS:
515            case ATTACHMENT:
516                return "vnd.android.cursor.dir/email-attachment";
517            case ATTACHMENT_ID:
518                return "vnd.android.cursor.item/email-attachment";
519            case HOSTAUTH:
520                return "vnd.android.cursor.dir/email-hostauth";
521            case HOSTAUTH_ID:
522                return "vnd.android.cursor.item/email-hostauth";
523            default:
524                throw new IllegalArgumentException("Unknown URI " + uri);
525        }
526    }
527
528    @Override
529    public Uri insert(Uri uri, ContentValues values) {
530        int match = sURIMatcher.match(uri);
531        Context context = getContext();
532        SQLiteDatabase db = (match >= BODY_BASE) ? getBodyDatabase(context) : getDatabase(context);
533        int table = match >> BASE_SHIFT;
534        long id;
535
536        if (Config.LOGV) {
537            Log.v(TAG, "EmailProvider.insert: uri=" + uri + ", match is " + match);
538        }
539
540        Uri resultUri = null;
541
542        switch (match) {
543            case BODY:
544            case MESSAGE:
545            case ATTACHMENT:
546            case MAILBOX:
547            case ACCOUNT:
548            case HOSTAUTH:
549                // Make sure all new message records have dirty count of 0
550                if (match == MESSAGE) {
551                    values.put(SyncColumns.DIRTY_COUNT, 0);
552                }
553                id = db.insert(TABLE_NAMES[table], "foo", values);
554                resultUri = ContentUris.withAppendedId(uri, id);
555                break;
556            case MAILBOX_ID:
557                // This implies adding a message to a mailbox
558                // Hmm, one problem here is that we can't link the account as well, so it must be
559                // already in the values...
560                id = Long.parseLong(uri.getPathSegments().get(1));
561                values.put(MessageColumns.MAILBOX_KEY, id);
562                resultUri = insert(Message.CONTENT_URI, values);
563                break;
564            case MESSAGE_ID:
565                // This implies adding an attachment to a message.
566                id = Long.parseLong(uri.getPathSegments().get(1));
567                values.put(AttachmentColumns.MESSAGE_KEY, id);
568                resultUri = insert(Attachment.CONTENT_URI, values);
569                break;
570            case ACCOUNT_ID:
571                // This implies adding a mailbox to an account.
572                id = Long.parseLong(uri.getPathSegments().get(1));
573                values.put(MailboxColumns.ACCOUNT_KEY, id);
574                resultUri = insert(Mailbox.CONTENT_URI, values);
575                break;
576            case MESSAGE_ATTACHMENTS:
577                id = db.insert(TABLE_NAMES[table], "foo", values);
578                resultUri = ContentUris.withAppendedId(Attachment.CONTENT_URI, id);
579                break;
580            default:
581                throw new IllegalArgumentException("Unknown URL " + uri);
582        }
583
584        // Notify with the base uri, not the new uri (nobody is watching a new record)
585        getContext().getContentResolver().notifyChange(uri, null);
586        return resultUri;
587    }
588
589    @Override
590    public boolean onCreate() {
591        // TODO Auto-generated method stub
592        return false;
593    }
594
595    @Override
596    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
597            String sortOrder) {
598        Cursor c = null;
599        Uri notificationUri = EmailContent.CONTENT_URI;
600        int match = sURIMatcher.match(uri);
601        Context context = getContext();
602        SQLiteDatabase db = (match >= BODY_BASE) ? getBodyDatabase(context) : getDatabase(context);
603        int table = match >> BASE_SHIFT;
604        String id;
605
606        if (Config.LOGV) {
607            Log.v(TAG, "EmailProvider.query: uri=" + uri + ", match is " + match);
608        }
609
610        switch (match) {
611            case BODY:
612            case MESSAGE:
613            case UPDATED_MESSAGE:
614            case ATTACHMENT:
615            case MAILBOX:
616            case ACCOUNT:
617            case HOSTAUTH:
618                c = db.query(TABLE_NAMES[table], projection,
619                        selection, selectionArgs, null, null, sortOrder);
620                break;
621            case BODY_ID:
622            case MESSAGE_ID:
623            case UPDATED_MESSAGE_ID:
624            case ATTACHMENT_ID:
625            case MAILBOX_ID:
626            case ACCOUNT_ID:
627            case HOSTAUTH_ID:
628                id = uri.getPathSegments().get(1);
629                c = db.query(TABLE_NAMES[table], projection,
630                        whereWithId(id, selection), selectionArgs, null, null, sortOrder);
631                break;
632            case MESSAGE_ATTACHMENTS:
633                // All attachments for the given message
634                id = uri.getPathSegments().get(1);
635                c = db.query(Attachment.TABLE_NAME, projection,
636                        whereWith(Attachment.MESSAGE_KEY + "=" + id, selection),
637                        selectionArgs, null, null, sortOrder);
638                break;
639            default:
640                throw new IllegalArgumentException("Unknown URI " + uri);
641        }
642
643        if ((c != null) && !isTemporary()) {
644            c.setNotificationUri(getContext().getContentResolver(), notificationUri);
645        }
646        return c;
647    }
648
649    private String whereWithId(String id, String selection) {
650        StringBuilder sb = new StringBuilder(256);
651        sb.append("_id=");
652        sb.append(id);
653        if (selection != null) {
654            sb.append(" AND ");
655            sb.append(selection);
656        }
657        return sb.toString();
658    }
659
660    private String whereWith(String where, String selection) {
661        StringBuilder sb = new StringBuilder(where);
662        if (selection != null) {
663            sb.append(" AND ");
664            sb.append(selection);
665        }
666        return sb.toString();
667    }
668
669    @Override
670    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
671        int match = sURIMatcher.match(uri);
672        Context context = getContext();
673        SQLiteDatabase db = (match >= BODY_BASE) ? getBodyDatabase(context) : getDatabase(context);
674        int table = match >> BASE_SHIFT;
675        if (Config.LOGV) {
676            Log.v(TAG, "EmailProvider.update: uri=" + uri + ", match is " + match);
677        }
678
679        int result;
680
681        // Set the dirty bit for messages
682        if ((match == MESSAGE_ID || match == MESSAGE)
683                && values.get(SyncColumns.DIRTY_COUNT) == null) {
684            values.put(SyncColumns.DIRTY_COUNT, 1);
685        }
686
687        switch (match) {
688            case BODY_ID:
689            case MESSAGE_ID:
690            case UPDATED_MESSAGE_ID:
691            case ATTACHMENT_ID:
692            case MAILBOX_ID:
693            case ACCOUNT_ID:
694            case HOSTAUTH_ID:
695                String id = uri.getPathSegments().get(1);
696                // Set dirty if nobody is setting this manually
697                result = db.update(TABLE_NAMES[table], values, whereWithId(id, selection),
698                        selectionArgs);
699                break;
700            case BODY:
701            case MESSAGE:
702            case UPDATED_MESSAGE:
703            case ATTACHMENT:
704            case MAILBOX:
705            case ACCOUNT:
706            case HOSTAUTH:
707                result = db.update(TABLE_NAMES[table], values, selection, selectionArgs);
708                break;
709            default:
710                throw new IllegalArgumentException("Unknown URI " + uri);
711        }
712
713        getContext().getContentResolver().notifyChange(uri, null);
714        return result;
715    }
716
717    /* (non-Javadoc)
718     * @see android.content.ContentProvider#applyBatch(android.content.ContentProviderOperation)
719     *
720     * TODO: How do we call notifyChange() or do we need to - does this call the various
721     * update/insert/delete calls?
722     */
723    public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations)
724            throws OperationApplicationException {
725        SQLiteDatabase db = getDatabase(getContext());
726        db.beginTransaction();
727        try {
728            ContentProviderResult[] results = super.applyBatch(operations);
729            db.setTransactionSuccessful();
730            return results;
731        } finally {
732            db.endTransaction();
733        }
734    }
735}
736