EmailProvider.java revision 626f3e48a4f14c38a973dd2bea2e2debea7637a5
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.EmailStore.Attachment;
20import com.android.email.provider.EmailStore.AttachmentColumns;
21import com.android.email.provider.EmailStore.Mailbox;
22import com.android.email.provider.EmailStore.MailboxColumns;
23import com.android.email.provider.EmailStore.Message;
24import com.android.email.provider.EmailStore.MessageColumns;
25
26import android.content.ContentProvider;
27import android.content.ContentProviderOperation;
28import android.content.ContentProviderResult;
29import android.content.ContentUris;
30import android.content.ContentValues;
31import android.content.Context;
32import android.content.OperationApplicationException;
33import android.content.UriMatcher;
34import android.database.Cursor;
35import android.database.sqlite.SQLiteDatabase;
36import android.database.sqlite.SQLiteOpenHelper;
37import android.net.Uri;
38import android.util.Config;
39import android.util.Log;
40
41/*
42 * TODO
43 *
44 * Add Email.Body class and support, now that this is stored separately
45 * Handle deletion cascades (either w/ triggers or code)
46 *
47 */
48public class EmailProvider extends ContentProvider {
49
50    private static final String TAG = "EmailProvider";
51
52    static final String DATABASE_NAME = "EmailProvider.db";
53
54    // In these early versions, updating the database version will cause all tables to be deleted
55    // Obviously, we'll handle upgrades differently once things are a bit stable
56    public static final int DATABASE_VERSION = 11;
57
58    protected static final String EMAIL_AUTHORITY = "com.android.email.provider";
59
60    private static final int ACCOUNT_BASE = 0;
61    private static final int ACCOUNT = ACCOUNT_BASE;
62    private static final int ACCOUNT_MAILBOXES = ACCOUNT_BASE + 1;
63    private static final int ACCOUNT_ID = ACCOUNT_BASE + 2;
64
65    private static final int MAILBOX_BASE = 0x1000;
66    private static final int MAILBOX = MAILBOX_BASE;
67    private static final int MAILBOX_MESSAGES = MAILBOX_BASE + 1;
68    private static final int MAILBOX_ID = MAILBOX_BASE + 2;
69
70    private static final int MESSAGE_BASE = 0x2000;
71    private static final int MESSAGE = MESSAGE_BASE;
72    private static final int MESSAGE_ATTACHMENTS = MESSAGE_BASE + 1;
73    private static final int MESSAGE_ID = MESSAGE_BASE + 2;
74
75    private static final int ATTACHMENT_BASE = 0x3000;
76    private static final int ATTACHMENT = ATTACHMENT_BASE;
77    private static final int ATTACHMENT_CONTENT = ATTACHMENT_BASE + 1;
78    private static final int ATTACHMENT_ID = ATTACHMENT_BASE + 2;
79
80     // TEMPORARY UNTIL ACCOUNT MANAGER CAN BE USED
81    private static final int HOSTAUTH_BASE = 0x4000;
82    private static final int HOSTAUTH = HOSTAUTH_BASE;
83    private static final int HOSTAUTH_ID = HOSTAUTH_BASE + 1;
84
85    private static final int BODY_BASE = 0x5000;
86    private static final int BODY = BODY_BASE;
87    private static final int BODY_ID = BODY_BASE + 1;
88    private static final int BODY_HTML = BODY_BASE + 2;
89    private static final int BODY_TEXT = BODY_BASE + 4;
90
91
92    private static final int BASE_SHIFT = 12;  // 12 bits to the base type: 0, 0x1000, 0x2000, etc.
93
94    private static final String[] TABLE_NAMES = {
95        EmailStore.Account.TABLE_NAME,
96        EmailStore.Mailbox.TABLE_NAME,
97        EmailStore.Message.TABLE_NAME,
98        EmailStore.Attachment.TABLE_NAME,
99        EmailStore.HostAuth.TABLE_NAME
100    };
101
102    private static final UriMatcher sURIMatcher = new UriMatcher(UriMatcher.NO_MATCH);
103
104    static {
105        // Email URI matching table
106        UriMatcher matcher = sURIMatcher;
107        // All accounts
108        matcher.addURI(EMAIL_AUTHORITY, "account", ACCOUNT); // IMPLEMENTED
109        // A specific account
110        // insert into this URI causes a mailbox to be added to the account
111        matcher.addURI(EMAIL_AUTHORITY, "account/#", ACCOUNT_ID);  // IMPLEMENTED
112        // The mailboxes in a specific account
113        matcher.addURI(EMAIL_AUTHORITY, "account/#/mailbox", ACCOUNT_MAILBOXES);
114        // All mailboxes
115        matcher.addURI(EMAIL_AUTHORITY, "mailbox", MAILBOX);  // IMPLEMENTED
116        // A specific mailbox
117        // insert into this URI causes a message to be added to the mailbox
118        // ** NOTE For now, the accountKey must be set manually in the values!
119        matcher.addURI(EMAIL_AUTHORITY, "mailbox/#", MAILBOX_ID);  // IMPLEMENTED
120        // The messages in a specific mailbox
121        matcher.addURI(EMAIL_AUTHORITY, "mailbox/#/message", MAILBOX_MESSAGES);
122        // All messages
123        matcher.addURI(EMAIL_AUTHORITY, "message", MESSAGE); // IMPLEMENTED
124        // A specific message
125        // insert into this URI causes an attachment to be added to the message
126        matcher.addURI(EMAIL_AUTHORITY, "message/#", MESSAGE_ID); // IMPLEMENTED
127        // The attachments of a specific message
128        matcher.addURI(EMAIL_AUTHORITY, "message/#/attachment", MESSAGE_ATTACHMENTS); // IMPLEMENTED
129        // A specific attachment
130        matcher.addURI(EMAIL_AUTHORITY, "attachment", ATTACHMENT); // IMPLEMENTED
131        // A specific attachment (the header information)
132        matcher.addURI(EMAIL_AUTHORITY, "attachment/#", ATTACHMENT_ID);  // IMPLEMENTED
133        // The content for a specific attachment
134        matcher.addURI(EMAIL_AUTHORITY, "attachment/content/*", ATTACHMENT_CONTENT);
135
136        // All mail bodies
137        matcher.addURI(EMAIL_AUTHORITY, "body", BODY);
138        // A specific mail body
139        matcher.addURI(EMAIL_AUTHORITY, "body/#", BODY_ID);
140        // The HTML part of a specific mail body
141        matcher.addURI(EMAIL_AUTHORITY, "body/#/html", BODY_HTML);
142        // The plain text part of a specific mail body
143        matcher.addURI(EMAIL_AUTHORITY, "body/#/text", BODY_TEXT);
144
145        // A specific attachment
146        matcher.addURI(EMAIL_AUTHORITY, "hostauth", HOSTAUTH); // IMPLEMENTED
147        // A specific attachment (the header information)
148        matcher.addURI(EMAIL_AUTHORITY, "hostauth/#", HOSTAUTH_ID);  // IMPLEMENTED
149
150    }
151
152    private final int mDatabaseVersion = DATABASE_VERSION;
153
154    private SQLiteDatabase mDatabase;
155
156    public SQLiteDatabase getDatabase(Context context) {
157        if (mDatabase !=  null)
158            return mDatabase;
159        DatabaseHelper helper = new DatabaseHelper(context, DATABASE_NAME);
160        mDatabase = helper.getWritableDatabase();
161        if (mDatabase != null)
162            mDatabase.setLockingEnabled(true);
163        return mDatabase;
164    }
165
166
167
168    private class DatabaseHelper extends SQLiteOpenHelper {
169        DatabaseHelper(Context context, String name) {
170             super(context, name, null, mDatabaseVersion);
171        }
172
173        @Override
174        public void onCreate(SQLiteDatabase db) {
175            // Create all tables here; each class has its own method
176            EmailStore.Message.createTable(db);
177            EmailStore.Attachment.createTable(db);
178            EmailStore.Mailbox.createTable(db);
179            EmailStore.HostAuth.createTable(db);
180            EmailStore.Account.createTable(db);
181       }
182
183        @Override
184        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
185            EmailStore.Message.upgradeTable(db, oldVersion, newVersion);
186            EmailStore.Attachment.upgradeTable(db, oldVersion, newVersion);
187            EmailStore.Mailbox.upgradeTable(db, oldVersion, newVersion);
188            EmailStore.HostAuth.upgradeTable(db, oldVersion, newVersion);
189            EmailStore.Account.upgradeTable(db, oldVersion, newVersion);
190        }
191
192        @Override
193        public void onOpen(SQLiteDatabase db) {
194        }
195    }
196
197    @SuppressWarnings("deprecation")
198    @Override
199    public int delete(Uri uri, String selection, String[] selectionArgs) {
200        SQLiteDatabase db = getDatabase(getContext());
201        int match = sURIMatcher.match(uri);
202        int table = match >> BASE_SHIFT;
203
204        if (Config.LOGV) {
205            Log.v(TAG, "EmailProvider.delete: uri=" + uri + ", match is " + match);
206        }
207
208        int result;
209        switch (match) {
210            case MESSAGE_ID:
211            case ATTACHMENT_ID:
212            case MAILBOX_ID:
213            case ACCOUNT_ID:
214            case HOSTAUTH_ID:
215                String id = uri.getPathSegments().get(1);
216                result = db.delete(TABLE_NAMES[table], whereWithId(id, selection), selectionArgs);
217                break;
218            case MESSAGE:
219            case ATTACHMENT:
220            case MAILBOX:
221            case ACCOUNT:
222            case HOSTAUTH:
223                result = db.delete(TABLE_NAMES[table], selection, selectionArgs);
224                break;
225            default:
226                throw new IllegalArgumentException("Unknown URI " + uri);
227        }
228
229        getContext().getContentResolver().notifyChange(uri, null);
230        return result;
231    }
232
233    @Override
234    // Use the email- prefix because message, mailbox, and account are so generic (e.g. SMS, IM)
235    public String getType(Uri uri) {
236        int match = sURIMatcher.match(uri);
237        switch (match) {
238            case MESSAGE_ID:
239                return "vnd.android.cursor.dir/email-message";
240            case MAILBOX_MESSAGES:
241            case MESSAGE:
242                return "vnd.android.cursor.item/email-message";
243            case ACCOUNT_MAILBOXES:
244            case MAILBOX:
245                return "vnd.android.cursor.dir/email-mailbox";
246            case MAILBOX_ID:
247                return "vnd.android.cursor.item/email-mailbox";
248            case ACCOUNT:
249                return "vnd.android.cursor.dir/email-account";
250            case ACCOUNT_ID:
251                return "vnd.android.cursor.item/email-account";
252            case MESSAGE_ATTACHMENTS:
253            case ATTACHMENT:
254                return "vnd.android.cursor.dir/email-attachment";
255            case ATTACHMENT_ID:
256                return "vnd.android.cursor.item/email-attachment";
257            case HOSTAUTH:
258                return "vnd.android.cursor.dir/email-hostauth";
259            case HOSTAUTH_ID:
260                return "vnd.android.cursor.item/email-hostauth";
261            default:
262                throw new IllegalArgumentException("Unknown URI " + uri);
263        }
264    }
265
266    @SuppressWarnings("deprecation")
267    @Override
268    public Uri insert(Uri uri, ContentValues values) {
269        SQLiteDatabase db = getDatabase(getContext());
270        long id;
271        int match = sURIMatcher.match(uri);
272        int table = match >> BASE_SHIFT;
273
274        if (Config.LOGV) {
275            Log.v(TAG, "EmailProvider.insert: uri=" + uri + ", match is " + match);
276        }
277
278        Uri resultUri = null;
279        switch (match) {
280            case MESSAGE:
281            case ATTACHMENT:
282            case MAILBOX:
283            case ACCOUNT:
284            case HOSTAUTH:
285                id = db.insert(TABLE_NAMES[table], "foo", values);
286                resultUri = ContentUris.withAppendedId(uri, id);
287                break;
288            case MAILBOX_ID:
289                // This implies adding a message to a mailbox
290                // Hmm, one problem here is that we can't link the account as well, so it must be
291                // already in the values...
292                id = Long.parseLong(uri.getPathSegments().get(1));
293                values.put(MessageColumns.MAILBOX_KEY, id);
294                resultUri = insert(Message.CONTENT_URI, values);
295                break;
296            case MESSAGE_ID:
297                // This implies adding an attachment to a message.
298                id = Long.parseLong(uri.getPathSegments().get(1));
299                values.put(AttachmentColumns.MESSAGE_KEY, id);
300                resultUri = insert(Attachment.CONTENT_URI, values);
301                break;
302            case ACCOUNT_ID:
303                // This implies adding a mailbox to an account.
304                id = Long.parseLong(uri.getPathSegments().get(1));
305                values.put(MailboxColumns.ACCOUNT_KEY, id);
306                resultUri = insert(Mailbox.CONTENT_URI, values);
307                break;
308            case MESSAGE_ATTACHMENTS:
309                id = db.insert(TABLE_NAMES[table], "foo", values);
310                resultUri = ContentUris.withAppendedId(EmailStore.Attachment.CONTENT_URI, id);
311                break;
312            default:
313                throw new IllegalArgumentException("Unknown URL " + uri);
314        }
315
316        getContext().getContentResolver().notifyChange(resultUri, null);
317        return resultUri;
318    }
319
320    @Override
321    public boolean onCreate() {
322        // TODO Auto-generated method stub
323        return false;
324    }
325
326    @SuppressWarnings("deprecation")
327    @Override
328    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
329            String sortOrder) {
330        SQLiteDatabase db = getDatabase(getContext());
331        Cursor c = null;
332        Uri notificationUri = EmailStore.CONTENT_URI;
333        int match = sURIMatcher.match(uri);
334        int table = match >> BASE_SHIFT;
335        String id;
336
337        if (Config.LOGV) {
338            Log.v(TAG, "EmailProvider.query: uri=" + uri + ", match is " + match);
339        }
340
341        switch (match) {
342            case MESSAGE:
343            case ATTACHMENT:
344            case MAILBOX:
345            case ACCOUNT:
346            case HOSTAUTH:
347                c = db.query(TABLE_NAMES[table], projection,
348                        selection, selectionArgs, null, null, sortOrder);
349                break;
350            case MESSAGE_ID:
351            case ATTACHMENT_ID:
352            case MAILBOX_ID:
353            case ACCOUNT_ID:
354            case HOSTAUTH_ID:
355                id = uri.getPathSegments().get(1);
356                c = db.query(TABLE_NAMES[table], projection,
357                        whereWithId(id, selection), selectionArgs, null, null, sortOrder);
358                break;
359            case MESSAGE_ATTACHMENTS:
360                // All attachments for the given message
361                id = uri.getPathSegments().get(1);
362                c = db.query(Attachment.TABLE_NAME, projection,
363                        whereWith(Attachment.MESSAGE_KEY + "=" + id, selection),
364                        selectionArgs, null, null, sortOrder);
365                break;
366            default:
367                throw new IllegalArgumentException("Unknown URI " + uri);
368        }
369
370        if ((c != null) && !isTemporary()) {
371            c.setNotificationUri(getContext().getContentResolver(), notificationUri);
372        }
373        return c;
374    }
375
376    private String whereWithId(String id, String selection) {
377        StringBuilder sb = new StringBuilder(256);
378        sb.append("_id=");
379        sb.append(id);
380        if (selection != null) {
381            sb.append(" AND ");
382            sb.append(selection);
383        }
384        return sb.toString();
385    }
386
387    private String whereWith(String where, String selection) {
388        StringBuilder sb = new StringBuilder(where);
389        if (selection != null) {
390            sb.append(" AND ");
391            sb.append(selection);
392        }
393        return sb.toString();
394    }
395
396    @SuppressWarnings("deprecation")
397    @Override
398    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
399        SQLiteDatabase db = getDatabase(getContext());
400        int match = sURIMatcher.match(uri);
401        int table = match >> BASE_SHIFT;
402        if (Config.LOGV) {
403            Log.v(TAG, "EmailProvider.update: uri=" + uri + ", match is " + match);
404        }
405
406        int result;
407        switch (match) {
408            case MESSAGE_ID:
409            case ATTACHMENT_ID:
410            case MAILBOX_ID:
411            case ACCOUNT_ID:
412                String id = uri.getPathSegments().get(1);
413                result = db.update(TABLE_NAMES[table], values, whereWithId(id, selection),
414                        selectionArgs);
415                break;
416            case MESSAGE:
417            case ATTACHMENT:
418            case MAILBOX:
419            case ACCOUNT:
420                result = db.update(TABLE_NAMES[table], values, selection, selectionArgs);
421                break;
422            default:
423                throw new IllegalArgumentException("Unknown URI " + uri);
424        }
425
426        getContext().getContentResolver().notifyChange(uri, null);
427        return result;
428    }
429
430    /* (non-Javadoc)
431     * @see android.content.ContentProvider#applyBatch(android.content.ContentProviderOperation[])
432     *
433     * TODO: How do we call notifyChange() or do we need to - does this call the various
434     * update/insert/delete calls?
435     */
436    public ContentProviderResult[] applyBatch(ContentProviderOperation[] operations)
437            throws OperationApplicationException {
438        SQLiteDatabase db = getDatabase(getContext());
439        db.beginTransaction();
440        try {
441            ContentProviderResult[] results = super.applyBatch(operations);
442            db.setTransactionSuccessful();
443            return results;
444        } finally {
445            db.endTransaction();
446        }
447    }
448}
449