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