EmailProvider.java revision 9019315b2642d58691cf09d32c07c0cf902f0a41
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 = 12; 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 case HOSTAUTH_ID: 415 String id = uri.getPathSegments().get(1); 416 result = db.update(TABLE_NAMES[table], values, whereWithId(id, selection), 417 selectionArgs); 418 break; 419 case MESSAGE: 420 case ATTACHMENT: 421 case MAILBOX: 422 case ACCOUNT: 423 case HOSTAUTH: 424 result = db.update(TABLE_NAMES[table], values, selection, selectionArgs); 425 break; 426 default: 427 throw new IllegalArgumentException("Unknown URI " + uri); 428 } 429 430 getContext().getContentResolver().notifyChange(uri, null); 431 return result; 432 } 433 434 /* (non-Javadoc) 435 * @see android.content.ContentProvider#applyBatch(android.content.ContentProviderOperation) 436 * 437 * TODO: How do we call notifyChange() or do we need to - does this call the various 438 * update/insert/delete calls? 439 */ 440 public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations) 441 throws OperationApplicationException { 442 SQLiteDatabase db = getDatabase(getContext()); 443 db.beginTransaction(); 444 try { 445 ContentProviderResult[] results = super.applyBatch(operations); 446 db.setTransactionSuccessful(); 447 return results; 448 } finally { 449 db.endTransaction(); 450 } 451 } 452} 453