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