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 17 package com.android.email.provider; 18 19 import android.content.ContentProviderOperation; 20 import android.content.ContentProviderResult; 21 import android.content.ContentResolver; 22 import android.content.ContentUris; 23 import android.content.ContentValues; 24 import android.content.Context; 25 import android.content.OperationApplicationException; 26 import android.database.Cursor; 27 import android.net.Uri; 28 import android.os.Environment; 29 import android.os.Parcel; 30 import android.os.Parcelable; 31 import android.os.RemoteException; 32 33 import java.io.File; 34 import java.net.URI; 35 import java.net.URISyntaxException; 36 import java.util.ArrayList; 37 import java.util.UUID; 38 39 40 /** 41 * EmailContent is the superclass of the various classes of content stored by EmailProvider. 42 * 43 * It is intended to include 1) column definitions for use with the Provider, and 2) convenience 44 * methods for saving and retrieving content from the Provider. 45 * 46 * This class will be used by 1) the Email process (which includes the application and 47 * EmaiLProvider) as well as 2) the Exchange process (which runs independently). It will 48 * necessarily be cloned for use in these two cases. 49 * 50 * Conventions used in naming columns: 51 * RECORD_ID is the primary key for all Email records 52 * The SyncColumns interface is used by all classes that are synced to the server directly 53 * (Mailbox and Email) 54 * 55 * <name>_KEY always refers to a foreign key 56 * <name>_ID always refers to a unique identifier (whether on client, server, etc.) 57 * 58 */ 59 public abstract class EmailContent { 60 public static final String AUTHORITY = EmailProvider.EMAIL_AUTHORITY; 61 public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY); 62 // All classes share this 63 public static final String RECORD_ID = "_id"; 64 65 private static final String[] COUNT_COLUMNS = new String[]{"count(*)"}; 66 67 /** 68 * This projection can be used with any of the EmailContent classes, when all you need 69 * is a list of id's. Use ID_PROJECTION_COLUMN to access the row data. 70 */ 71 public static final String[] ID_PROJECTION = new String[] { 72 RECORD_ID 73 }; 74 public static final int ID_PROJECTION_COLUMN = 0; 75 76 public static final String FIELD_COLUMN_NAME = "field"; 77 public static final String ADD_COLUMN_NAME = "add"; 78 79 // Newly created objects get this id 80 private static final int NOT_SAVED = -1; 81 // The base Uri that this piece of content came from 82 public Uri mBaseUri; 83 // Lazily initialized uri for this Content 84 private Uri mUri = null; 85 // The id of the Content 86 public long mId = NOT_SAVED; 87 88 // Write the Content into a ContentValues container toContentValues()89 public abstract ContentValues toContentValues(); 90 // Read the Content from a ContentCursor restore(Cursor cursor)91 public abstract <T extends EmailContent> T restore (Cursor cursor); 92 93 // The Uri is lazily initialized getUri()94 public Uri getUri() { 95 if (mUri == null) { 96 mUri = ContentUris.withAppendedId(mBaseUri, mId); 97 } 98 return mUri; 99 } 100 isSaved()101 public boolean isSaved() { 102 return mId != NOT_SAVED; 103 } 104 105 @SuppressWarnings("unchecked") 106 // The Content sub class must have a no-arg constructor getContent(Cursor cursor, Class<T> klass)107 static public <T extends EmailContent> T getContent(Cursor cursor, Class<T> klass) { 108 try { 109 T content = klass.newInstance(); 110 content.mId = cursor.getLong(0); 111 return (T)content.restore(cursor); 112 } catch (IllegalAccessException e) { 113 e.printStackTrace(); 114 } catch (InstantiationException e) { 115 e.printStackTrace(); 116 } 117 return null; 118 } 119 save(Context context)120 public Uri save(Context context) { 121 if (isSaved()) { 122 throw new UnsupportedOperationException(); 123 } 124 Uri res = context.getContentResolver().insert(mBaseUri, toContentValues()); 125 mId = Long.parseLong(res.getPathSegments().get(1)); 126 return res; 127 } 128 update(Context context, ContentValues contentValues)129 public int update(Context context, ContentValues contentValues) { 130 if (!isSaved()) { 131 throw new UnsupportedOperationException(); 132 } 133 return context.getContentResolver().update(getUri(), contentValues, null, null); 134 } 135 update(Context context, Uri baseUri, long id, ContentValues contentValues)136 static public int update(Context context, Uri baseUri, long id, ContentValues contentValues) { 137 return context.getContentResolver() 138 .update(ContentUris.withAppendedId(baseUri, id), contentValues, null, null); 139 } 140 141 /** 142 * Generic count method that can be used for any ContentProvider 143 * @param context the calling Context 144 * @param uri the Uri for the provider query 145 * @param selection as with a query call 146 * @param selectionArgs as with a query call 147 * @return the number of items matching the query (or zero) 148 */ count(Context context, Uri uri, String selection, String[] selectionArgs)149 static public int count(Context context, Uri uri, String selection, String[] selectionArgs) { 150 Cursor cursor = context.getContentResolver() 151 .query(uri, COUNT_COLUMNS, selection, selectionArgs, null); 152 try { 153 if (!cursor.moveToFirst()) { 154 return 0; 155 } 156 return cursor.getInt(0); 157 } finally { 158 cursor.close(); 159 } 160 } 161 162 /** 163 * no public constructor since this is a utility class 164 */ EmailContent()165 private EmailContent() { 166 } 167 168 public interface SyncColumns { 169 public static final String ID = "_id"; 170 // source id (string) : the source's name of this item 171 public static final String SERVER_ID = "syncServerId"; 172 // source's timestamp (long) for this item 173 public static final String SERVER_TIMESTAMP = "syncServerTimeStamp"; 174 } 175 176 public interface BodyColumns { 177 public static final String ID = "_id"; 178 // Foreign key to the message corresponding to this body 179 public static final String MESSAGE_KEY = "messageKey"; 180 // The html content itself 181 public static final String HTML_CONTENT = "htmlContent"; 182 // The plain text content itself 183 public static final String TEXT_CONTENT = "textContent"; 184 // Replied-to or forwarded body (in html form) 185 public static final String HTML_REPLY = "htmlReply"; 186 // Replied-to or forwarded body (in text form) 187 public static final String TEXT_REPLY = "textReply"; 188 // Message id of the source (if this is a reply/forward) 189 public static final String SOURCE_MESSAGE_KEY = "sourceMessageKey"; 190 // The text to be placed between a reply/forward response and the original message 191 public static final String INTRO_TEXT = "introText"; 192 } 193 194 public static final class Body extends EmailContent implements BodyColumns { 195 public static final String TABLE_NAME = "Body"; 196 public static final Uri CONTENT_URI = Uri.parse(EmailContent.CONTENT_URI + "/body"); 197 198 public static final int CONTENT_ID_COLUMN = 0; 199 public static final int CONTENT_MESSAGE_KEY_COLUMN = 1; 200 public static final int CONTENT_HTML_CONTENT_COLUMN = 2; 201 public static final int CONTENT_TEXT_CONTENT_COLUMN = 3; 202 public static final int CONTENT_HTML_REPLY_COLUMN = 4; 203 public static final int CONTENT_TEXT_REPLY_COLUMN = 5; 204 public static final int CONTENT_SOURCE_KEY_COLUMN = 6; 205 public static final int CONTENT_INTRO_TEXT_COLUMN = 7; 206 public static final String[] CONTENT_PROJECTION = new String[] { 207 RECORD_ID, BodyColumns.MESSAGE_KEY, BodyColumns.HTML_CONTENT, BodyColumns.TEXT_CONTENT, 208 BodyColumns.HTML_REPLY, BodyColumns.TEXT_REPLY, BodyColumns.SOURCE_MESSAGE_KEY, 209 BodyColumns.INTRO_TEXT 210 }; 211 212 public static final String[] COMMON_PROJECTION_TEXT = new String[] { 213 RECORD_ID, BodyColumns.TEXT_CONTENT 214 }; 215 public static final String[] COMMON_PROJECTION_HTML = new String[] { 216 RECORD_ID, BodyColumns.HTML_CONTENT 217 }; 218 public static final String[] COMMON_PROJECTION_REPLY_TEXT = new String[] { 219 RECORD_ID, BodyColumns.TEXT_REPLY 220 }; 221 public static final String[] COMMON_PROJECTION_REPLY_HTML = new String[] { 222 RECORD_ID, BodyColumns.HTML_REPLY 223 }; 224 public static final String[] COMMON_PROJECTION_INTRO = new String[] { 225 RECORD_ID, BodyColumns.INTRO_TEXT 226 }; 227 public static final int COMMON_PROJECTION_COLUMN_TEXT = 1; 228 229 public long mMessageKey; 230 public String mHtmlContent; 231 public String mTextContent; 232 public String mHtmlReply; 233 public String mTextReply; 234 public long mSourceKey; 235 public String mIntroText; 236 Body()237 public Body() { 238 mBaseUri = CONTENT_URI; 239 } 240 241 @Override toContentValues()242 public ContentValues toContentValues() { 243 ContentValues values = new ContentValues(); 244 245 // Assign values for each row. 246 values.put(BodyColumns.MESSAGE_KEY, mMessageKey); 247 values.put(BodyColumns.HTML_CONTENT, mHtmlContent); 248 values.put(BodyColumns.TEXT_CONTENT, mTextContent); 249 values.put(BodyColumns.HTML_REPLY, mHtmlReply); 250 values.put(BodyColumns.TEXT_REPLY, mTextReply); 251 values.put(BodyColumns.SOURCE_MESSAGE_KEY, mSourceKey); 252 values.put(BodyColumns.INTRO_TEXT, mIntroText); 253 return values; 254 } 255 restoreBodyWithCursor(Cursor cursor)256 private static Body restoreBodyWithCursor(Cursor cursor) { 257 try { 258 if (cursor.moveToFirst()) { 259 return getContent(cursor, Body.class); 260 } else { 261 return null; 262 } 263 } finally { 264 cursor.close(); 265 } 266 } 267 restoreBodyWithId(Context context, long id)268 public static Body restoreBodyWithId(Context context, long id) { 269 Uri u = ContentUris.withAppendedId(Body.CONTENT_URI, id); 270 Cursor c = context.getContentResolver().query(u, Body.CONTENT_PROJECTION, 271 null, null, null); 272 return restoreBodyWithCursor(c); 273 } 274 restoreBodyWithMessageId(Context context, long messageId)275 public static Body restoreBodyWithMessageId(Context context, long messageId) { 276 Cursor c = context.getContentResolver().query(Body.CONTENT_URI, 277 Body.CONTENT_PROJECTION, Body.MESSAGE_KEY + "=?", 278 new String[] {Long.toString(messageId)}, null); 279 return restoreBodyWithCursor(c); 280 } 281 282 /** 283 * Returns the bodyId for the given messageId, or -1 if no body is found. 284 */ lookupBodyIdWithMessageId(ContentResolver resolver, long messageId)285 public static long lookupBodyIdWithMessageId(ContentResolver resolver, long messageId) { 286 Cursor c = resolver.query(Body.CONTENT_URI, ID_PROJECTION, 287 Body.MESSAGE_KEY + "=?", 288 new String[] {Long.toString(messageId)}, null); 289 try { 290 return c.moveToFirst() ? c.getLong(ID_PROJECTION_COLUMN) : -1; 291 } finally { 292 c.close(); 293 } 294 } 295 296 /** 297 * Updates the Body for a messageId with the given ContentValues. 298 * If the message has no body, a new body is inserted for the message. 299 * Warning: the argument "values" is modified by this method, setting MESSAGE_KEY. 300 */ updateBodyWithMessageId(Context context, long messageId, ContentValues values)301 public static void updateBodyWithMessageId(Context context, long messageId, 302 ContentValues values) { 303 ContentResolver resolver = context.getContentResolver(); 304 long bodyId = lookupBodyIdWithMessageId(resolver, messageId); 305 values.put(BodyColumns.MESSAGE_KEY, messageId); 306 if (bodyId == -1) { 307 resolver.insert(CONTENT_URI, values); 308 } else { 309 final Uri uri = ContentUris.withAppendedId(CONTENT_URI, bodyId); 310 resolver.update(uri, values, null, null); 311 } 312 } 313 restoreTextWithMessageId(Context context, long messageId, String[] projection)314 private static String restoreTextWithMessageId(Context context, long messageId, 315 String[] projection) { 316 Cursor c = context.getContentResolver().query(Body.CONTENT_URI, projection, 317 Body.MESSAGE_KEY + "=?", new String[] {Long.toString(messageId)}, null); 318 try { 319 if (c.moveToFirst()) { 320 return c.getString(COMMON_PROJECTION_COLUMN_TEXT); 321 } else { 322 return null; 323 } 324 } finally { 325 c.close(); 326 } 327 } 328 restoreBodyTextWithMessageId(Context context, long messageId)329 public static String restoreBodyTextWithMessageId(Context context, long messageId) { 330 return restoreTextWithMessageId(context, messageId, Body.COMMON_PROJECTION_TEXT); 331 } 332 restoreBodyHtmlWithMessageId(Context context, long messageId)333 public static String restoreBodyHtmlWithMessageId(Context context, long messageId) { 334 return restoreTextWithMessageId(context, messageId, Body.COMMON_PROJECTION_HTML); 335 } 336 restoreReplyTextWithMessageId(Context context, long messageId)337 public static String restoreReplyTextWithMessageId(Context context, long messageId) { 338 return restoreTextWithMessageId(context, messageId, Body.COMMON_PROJECTION_REPLY_TEXT); 339 } 340 restoreReplyHtmlWithMessageId(Context context, long messageId)341 public static String restoreReplyHtmlWithMessageId(Context context, long messageId) { 342 return restoreTextWithMessageId(context, messageId, Body.COMMON_PROJECTION_REPLY_HTML); 343 } 344 restoreIntroTextWithMessageId(Context context, long messageId)345 public static String restoreIntroTextWithMessageId(Context context, long messageId) { 346 return restoreTextWithMessageId(context, messageId, Body.COMMON_PROJECTION_INTRO); 347 } 348 349 @Override 350 @SuppressWarnings("unchecked") restore(Cursor c)351 public EmailContent.Body restore(Cursor c) { 352 mBaseUri = EmailContent.Body.CONTENT_URI; 353 mMessageKey = c.getLong(CONTENT_MESSAGE_KEY_COLUMN); 354 mHtmlContent = c.getString(CONTENT_HTML_CONTENT_COLUMN); 355 mTextContent = c.getString(CONTENT_TEXT_CONTENT_COLUMN); 356 mHtmlReply = c.getString(CONTENT_HTML_REPLY_COLUMN); 357 mTextReply = c.getString(CONTENT_TEXT_REPLY_COLUMN); 358 mSourceKey = c.getLong(CONTENT_SOURCE_KEY_COLUMN); 359 mIntroText = c.getString(CONTENT_INTRO_TEXT_COLUMN); 360 return this; 361 } 362 update()363 public boolean update() { 364 // TODO Auto-generated method stub 365 return false; 366 } 367 } 368 369 public interface MessageColumns { 370 public static final String ID = "_id"; 371 // Basic columns used in message list presentation 372 // The name as shown to the user in a message list 373 public static final String DISPLAY_NAME = "displayName"; 374 // The time (millis) as shown to the user in a message list [INDEX] 375 public static final String TIMESTAMP = "timeStamp"; 376 // Message subject 377 public static final String SUBJECT = "subject"; 378 // Boolean, unread = 0, read = 1 [INDEX] 379 public static final String FLAG_READ = "flagRead"; 380 // Load state, see constants below (unloaded, partial, complete, deleted) 381 public static final String FLAG_LOADED = "flagLoaded"; 382 // Boolean, unflagged = 0, flagged (favorite) = 1 383 public static final String FLAG_FAVORITE = "flagFavorite"; 384 // Boolean, no attachment = 0, attachment = 1 385 public static final String FLAG_ATTACHMENT = "flagAttachment"; 386 // Bit field for flags which we'll not be selecting on 387 public static final String FLAGS = "flags"; 388 389 // Sync related identifiers 390 // Any client-required identifier 391 public static final String CLIENT_ID = "clientId"; 392 // The message-id in the message's header 393 public static final String MESSAGE_ID = "messageId"; 394 395 // References to other Email objects in the database 396 // Foreign key to the Mailbox holding this message [INDEX] 397 public static final String MAILBOX_KEY = "mailboxKey"; 398 // Foreign key to the Account holding this message 399 public static final String ACCOUNT_KEY = "accountKey"; 400 401 // Address lists, packed with Address.pack() 402 public static final String FROM_LIST = "fromList"; 403 public static final String TO_LIST = "toList"; 404 public static final String CC_LIST = "ccList"; 405 public static final String BCC_LIST = "bccList"; 406 public static final String REPLY_TO_LIST = "replyToList"; 407 } 408 409 public static final class Message extends EmailContent implements SyncColumns, MessageColumns { 410 public static final String TABLE_NAME = "Message"; 411 public static final String UPDATED_TABLE_NAME = "Message_Updates"; 412 public static final String DELETED_TABLE_NAME = "Message_Deletes"; 413 414 // To refer to a specific message, use ContentUris.withAppendedId(CONTENT_URI, id) 415 public static final Uri CONTENT_URI = Uri.parse(EmailContent.CONTENT_URI + "/message"); 416 public static final Uri SYNCED_CONTENT_URI = 417 Uri.parse(EmailContent.CONTENT_URI + "/syncedMessage"); 418 public static final Uri DELETED_CONTENT_URI = 419 Uri.parse(EmailContent.CONTENT_URI + "/deletedMessage"); 420 public static final Uri UPDATED_CONTENT_URI = 421 Uri.parse(EmailContent.CONTENT_URI + "/updatedMessage"); 422 423 public static final String KEY_TIMESTAMP_DESC = MessageColumns.TIMESTAMP + " desc"; 424 425 public static final int CONTENT_ID_COLUMN = 0; 426 public static final int CONTENT_DISPLAY_NAME_COLUMN = 1; 427 public static final int CONTENT_TIMESTAMP_COLUMN = 2; 428 public static final int CONTENT_SUBJECT_COLUMN = 3; 429 public static final int CONTENT_FLAG_READ_COLUMN = 4; 430 public static final int CONTENT_FLAG_LOADED_COLUMN = 5; 431 public static final int CONTENT_FLAG_FAVORITE_COLUMN = 6; 432 public static final int CONTENT_FLAG_ATTACHMENT_COLUMN = 7; 433 public static final int CONTENT_FLAGS_COLUMN = 8; 434 public static final int CONTENT_SERVER_ID_COLUMN = 9; 435 public static final int CONTENT_CLIENT_ID_COLUMN = 10; 436 public static final int CONTENT_MESSAGE_ID_COLUMN = 11; 437 public static final int CONTENT_MAILBOX_KEY_COLUMN = 12; 438 public static final int CONTENT_ACCOUNT_KEY_COLUMN = 13; 439 public static final int CONTENT_FROM_LIST_COLUMN = 14; 440 public static final int CONTENT_TO_LIST_COLUMN = 15; 441 public static final int CONTENT_CC_LIST_COLUMN = 16; 442 public static final int CONTENT_BCC_LIST_COLUMN = 17; 443 public static final int CONTENT_REPLY_TO_COLUMN = 18; 444 public static final int CONTENT_SERVER_TIMESTAMP_COLUMN = 19; 445 446 public static final String[] CONTENT_PROJECTION = new String[] { 447 RECORD_ID, 448 MessageColumns.DISPLAY_NAME, MessageColumns.TIMESTAMP, 449 MessageColumns.SUBJECT, MessageColumns.FLAG_READ, 450 MessageColumns.FLAG_LOADED, MessageColumns.FLAG_FAVORITE, 451 MessageColumns.FLAG_ATTACHMENT, MessageColumns.FLAGS, 452 SyncColumns.SERVER_ID, MessageColumns.CLIENT_ID, 453 MessageColumns.MESSAGE_ID, MessageColumns.MAILBOX_KEY, 454 MessageColumns.ACCOUNT_KEY, MessageColumns.FROM_LIST, 455 MessageColumns.TO_LIST, MessageColumns.CC_LIST, 456 MessageColumns.BCC_LIST, MessageColumns.REPLY_TO_LIST, 457 SyncColumns.SERVER_TIMESTAMP, 458 }; 459 460 public static final int LIST_ID_COLUMN = 0; 461 public static final int LIST_DISPLAY_NAME_COLUMN = 1; 462 public static final int LIST_TIMESTAMP_COLUMN = 2; 463 public static final int LIST_SUBJECT_COLUMN = 3; 464 public static final int LIST_READ_COLUMN = 4; 465 public static final int LIST_LOADED_COLUMN = 5; 466 public static final int LIST_FAVORITE_COLUMN = 6; 467 public static final int LIST_ATTACHMENT_COLUMN = 7; 468 public static final int LIST_FLAGS_COLUMN = 8; 469 public static final int LIST_MAILBOX_KEY_COLUMN = 9; 470 public static final int LIST_ACCOUNT_KEY_COLUMN = 10; 471 public static final int LIST_SERVER_ID_COLUMN = 11; 472 473 // Public projection for common list columns 474 public static final String[] LIST_PROJECTION = new String[] { 475 RECORD_ID, 476 MessageColumns.DISPLAY_NAME, MessageColumns.TIMESTAMP, 477 MessageColumns.SUBJECT, MessageColumns.FLAG_READ, 478 MessageColumns.FLAG_LOADED, MessageColumns.FLAG_FAVORITE, 479 MessageColumns.FLAG_ATTACHMENT, MessageColumns.FLAGS, 480 MessageColumns.MAILBOX_KEY, MessageColumns.ACCOUNT_KEY, 481 SyncColumns.SERVER_ID 482 }; 483 484 public static final int ID_COLUMNS_ID_COLUMN = 0; 485 public static final int ID_COLUMNS_SYNC_SERVER_ID = 1; 486 public static final String[] ID_COLUMNS_PROJECTION = new String[] { 487 RECORD_ID, SyncColumns.SERVER_ID 488 }; 489 490 public static final int ID_MAILBOX_COLUMN_ID = 0; 491 public static final int ID_MAILBOX_COLUMN_MAILBOX_KEY = 1; 492 public static final String[] ID_MAILBOX_PROJECTION = new String[] { 493 RECORD_ID, MessageColumns.MAILBOX_KEY 494 }; 495 496 public static final String[] ID_COLUMN_PROJECTION = new String[] { RECORD_ID }; 497 498 // _id field is in AbstractContent 499 public String mDisplayName; 500 public long mTimeStamp; 501 public String mSubject; 502 public boolean mFlagRead = false; 503 public int mFlagLoaded = FLAG_LOADED_UNLOADED; 504 public boolean mFlagFavorite = false; 505 public boolean mFlagAttachment = false; 506 public int mFlags = 0; 507 508 public String mServerId; 509 public long mServerTimeStamp; 510 public String mClientId; 511 public String mMessageId; 512 513 public long mMailboxKey; 514 public long mAccountKey; 515 516 public String mFrom; 517 public String mTo; 518 public String mCc; 519 public String mBcc; 520 public String mReplyTo; 521 522 // The following transient members may be used while building and manipulating messages, 523 // but they are NOT persisted directly by EmailProvider 524 transient public String mText; 525 transient public String mHtml; 526 transient public String mTextReply; 527 transient public String mHtmlReply; 528 transient public long mSourceKey; 529 transient public ArrayList<Attachment> mAttachments = null; 530 transient public String mIntroText; 531 532 // Values used in mFlagRead 533 public static final int UNREAD = 0; 534 public static final int READ = 1; 535 536 // Values used in mFlagLoaded 537 public static final int FLAG_LOADED_UNLOADED = 0; 538 public static final int FLAG_LOADED_COMPLETE = 1; 539 public static final int FLAG_LOADED_PARTIAL = 2; 540 public static final int FLAG_LOADED_DELETED = 3; 541 542 // Bits used in mFlags 543 // These three states are mutually exclusive, and indicate whether the message is an 544 // original, a reply, or a forward 545 public static final int FLAG_TYPE_ORIGINAL = 0; 546 public static final int FLAG_TYPE_REPLY = 1<<0; 547 public static final int FLAG_TYPE_FORWARD = 1<<1; 548 public static final int FLAG_TYPE_MASK = FLAG_TYPE_REPLY | FLAG_TYPE_FORWARD; 549 Message()550 public Message() { 551 mBaseUri = CONTENT_URI; 552 } 553 554 @Override toContentValues()555 public ContentValues toContentValues() { 556 ContentValues values = new ContentValues(); 557 558 // Assign values for each row. 559 values.put(MessageColumns.DISPLAY_NAME, mDisplayName); 560 values.put(MessageColumns.TIMESTAMP, mTimeStamp); 561 values.put(MessageColumns.SUBJECT, mSubject); 562 values.put(MessageColumns.FLAG_READ, mFlagRead); 563 values.put(MessageColumns.FLAG_LOADED, mFlagLoaded); 564 values.put(MessageColumns.FLAG_FAVORITE, mFlagFavorite); 565 values.put(MessageColumns.FLAG_ATTACHMENT, mFlagAttachment); 566 values.put(MessageColumns.FLAGS, mFlags); 567 568 values.put(SyncColumns.SERVER_ID, mServerId); 569 values.put(SyncColumns.SERVER_TIMESTAMP, mServerTimeStamp); 570 values.put(MessageColumns.CLIENT_ID, mClientId); 571 values.put(MessageColumns.MESSAGE_ID, mMessageId); 572 573 values.put(MessageColumns.MAILBOX_KEY, mMailboxKey); 574 values.put(MessageColumns.ACCOUNT_KEY, mAccountKey); 575 576 values.put(MessageColumns.FROM_LIST, mFrom); 577 values.put(MessageColumns.TO_LIST, mTo); 578 values.put(MessageColumns.CC_LIST, mCc); 579 values.put(MessageColumns.BCC_LIST, mBcc); 580 values.put(MessageColumns.REPLY_TO_LIST, mReplyTo); 581 582 return values; 583 } 584 restoreMessageWithId(Context context, long id)585 public static Message restoreMessageWithId(Context context, long id) { 586 Uri u = ContentUris.withAppendedId(Message.CONTENT_URI, id); 587 Cursor c = context.getContentResolver().query(u, Message.CONTENT_PROJECTION, 588 null, null, null); 589 590 try { 591 if (c.moveToFirst()) { 592 return getContent(c, Message.class); 593 } else { 594 return null; 595 } 596 } finally { 597 c.close(); 598 } 599 } 600 601 @Override 602 @SuppressWarnings("unchecked") restore(Cursor c)603 public EmailContent.Message restore(Cursor c) { 604 mBaseUri = CONTENT_URI; 605 mId = c.getLong(CONTENT_ID_COLUMN); 606 mDisplayName = c.getString(CONTENT_DISPLAY_NAME_COLUMN); 607 mTimeStamp = c.getLong(CONTENT_TIMESTAMP_COLUMN); 608 mSubject = c.getString(CONTENT_SUBJECT_COLUMN); 609 mFlagRead = c.getInt(CONTENT_FLAG_READ_COLUMN) == 1; 610 mFlagLoaded = c.getInt(CONTENT_FLAG_LOADED_COLUMN); 611 mFlagFavorite = c.getInt(CONTENT_FLAG_FAVORITE_COLUMN) == 1; 612 mFlagAttachment = c.getInt(CONTENT_FLAG_ATTACHMENT_COLUMN) == 1; 613 mFlags = c.getInt(CONTENT_FLAGS_COLUMN); 614 mServerId = c.getString(CONTENT_SERVER_ID_COLUMN); 615 mServerTimeStamp = c.getLong(CONTENT_SERVER_TIMESTAMP_COLUMN); 616 mClientId = c.getString(CONTENT_CLIENT_ID_COLUMN); 617 mMessageId = c.getString(CONTENT_MESSAGE_ID_COLUMN); 618 mMailboxKey = c.getLong(CONTENT_MAILBOX_KEY_COLUMN); 619 mAccountKey = c.getLong(CONTENT_ACCOUNT_KEY_COLUMN); 620 mFrom = c.getString(CONTENT_FROM_LIST_COLUMN); 621 mTo = c.getString(CONTENT_TO_LIST_COLUMN); 622 mCc = c.getString(CONTENT_CC_LIST_COLUMN); 623 mBcc = c.getString(CONTENT_BCC_LIST_COLUMN); 624 mReplyTo = c.getString(CONTENT_REPLY_TO_COLUMN); 625 return this; 626 } 627 update()628 public boolean update() { 629 // TODO Auto-generated method stub 630 return false; 631 } 632 633 /* 634 * Override this so that we can store the Body first and link it to the Message 635 * Also, attachments when we get there... 636 * (non-Javadoc) 637 * @see com.android.email.provider.EmailContent#save(android.content.Context) 638 */ 639 @Override save(Context context)640 public Uri save(Context context) { 641 642 boolean doSave = !isSaved(); 643 644 // This logic is in place so I can (a) short circuit the expensive stuff when 645 // possible, and (b) override (and throw) if anyone tries to call save() or update() 646 // directly for Message, which are unsupported. 647 if (mText == null && mHtml == null && mTextReply == null && mHtmlReply == null && 648 (mAttachments == null || mAttachments.isEmpty())) { 649 if (doSave) { 650 return super.save(context); 651 } else { 652 // Call update, rather than super.update in case we ever override it 653 if (update(context, toContentValues()) == 1) { 654 return getUri(); 655 } 656 return null; 657 } 658 } 659 660 ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>(); 661 addSaveOps(ops); 662 try { 663 ContentProviderResult[] results = 664 context.getContentResolver().applyBatch(EmailProvider.EMAIL_AUTHORITY, ops); 665 // If saving, set the mId's of the various saved objects 666 if (doSave) { 667 Uri u = results[0].uri; 668 mId = Long.parseLong(u.getPathSegments().get(1)); 669 if (mAttachments != null) { 670 int resultOffset = 2; 671 for (Attachment a : mAttachments) { 672 // Save the id of the attachment record 673 u = results[resultOffset++].uri; 674 if (u != null) { 675 a.mId = Long.parseLong(u.getPathSegments().get(1)); 676 } 677 a.mMessageKey = mId; 678 } 679 } 680 return u; 681 } else { 682 return null; 683 } 684 } catch (RemoteException e) { 685 // There is nothing to be done here; fail by returning null 686 } catch (OperationApplicationException e) { 687 // There is nothing to be done here; fail by returning null 688 } 689 return null; 690 } 691 addSaveOps(ArrayList<ContentProviderOperation> ops)692 public void addSaveOps(ArrayList<ContentProviderOperation> ops) { 693 // First, save the message 694 ContentProviderOperation.Builder b = ContentProviderOperation.newInsert(mBaseUri); 695 ops.add(b.withValues(toContentValues()).build()); 696 697 // Create and save the body 698 ContentValues cv = new ContentValues(); 699 if (mText != null) { 700 cv.put(Body.TEXT_CONTENT, mText); 701 } 702 if (mHtml != null) { 703 cv.put(Body.HTML_CONTENT, mHtml); 704 } 705 if (mTextReply != null) { 706 cv.put(Body.TEXT_REPLY, mTextReply); 707 } 708 if (mHtmlReply != null) { 709 cv.put(Body.HTML_REPLY, mHtmlReply); 710 } 711 if (mSourceKey != 0) { 712 cv.put(Body.SOURCE_MESSAGE_KEY, mSourceKey); 713 } 714 if (mIntroText != null) { 715 cv.put(Body.INTRO_TEXT, mIntroText); 716 } 717 b = ContentProviderOperation.newInsert(Body.CONTENT_URI); 718 b.withValues(cv); 719 ContentValues backValues = new ContentValues(); 720 int messageBackValue = ops.size() - 1; 721 backValues.put(Body.MESSAGE_KEY, messageBackValue); 722 ops.add(b.withValueBackReferences(backValues).build()); 723 724 // Create the attaachments, if any 725 if (mAttachments != null) { 726 for (Attachment att: mAttachments) { 727 ops.add(ContentProviderOperation.newInsert(Attachment.CONTENT_URI) 728 .withValues(att.toContentValues()) 729 .withValueBackReference(Attachment.MESSAGE_KEY, messageBackValue) 730 .build()); 731 } 732 } 733 } 734 } 735 736 public interface AccountColumns { 737 public static final String ID = "_id"; 738 // The display name of the account (user-settable) 739 public static final String DISPLAY_NAME = "displayName"; 740 // The email address corresponding to this account 741 public static final String EMAIL_ADDRESS = "emailAddress"; 742 // A server-based sync key on an account-wide basis (EAS needs this) 743 public static final String SYNC_KEY = "syncKey"; 744 // The default sync lookback period for this account 745 public static final String SYNC_LOOKBACK = "syncLookback"; 746 // The default sync frequency for this account, in minutes 747 public static final String SYNC_INTERVAL = "syncInterval"; 748 // A foreign key into the account manager, having host, login, password, port, and ssl flags 749 public static final String HOST_AUTH_KEY_RECV = "hostAuthKeyRecv"; 750 // (optional) A foreign key into the account manager, having host, login, password, port, 751 // and ssl flags 752 public static final String HOST_AUTH_KEY_SEND = "hostAuthKeySend"; 753 // Flags 754 public static final String FLAGS = "flags"; 755 // Default account 756 public static final String IS_DEFAULT = "isDefault"; 757 // Old-Style UUID for compatibility with previous versions 758 public static final String COMPATIBILITY_UUID = "compatibilityUuid"; 759 // User name (for outgoing messages) 760 public static final String SENDER_NAME = "senderName"; 761 // Ringtone 762 public static final String RINGTONE_URI = "ringtoneUri"; 763 // Protocol version (arbitrary string, used by EAS currently) 764 public static final String PROTOCOL_VERSION = "protocolVersion"; 765 // The number of new messages (reported by the sync/download engines 766 public static final String NEW_MESSAGE_COUNT = "newMessageCount"; 767 } 768 769 public static final class Account extends EmailContent implements AccountColumns, Parcelable { 770 public static final String TABLE_NAME = "Account"; 771 public static final Uri CONTENT_URI = Uri.parse(EmailContent.CONTENT_URI + "/account"); 772 public static final Uri ADD_TO_FIELD_URI = 773 Uri.parse(EmailContent.CONTENT_URI + "/accountIdAddToField"); 774 775 public final static int FLAGS_NOTIFY_NEW_MAIL = 1; 776 public final static int FLAGS_VIBRATE = 2; 777 public static final int FLAGS_DELETE_POLICY_MASK = 4+8; 778 public static final int FLAGS_DELETE_POLICY_SHIFT = 2; 779 780 public static final int DELETE_POLICY_NEVER = 0; 781 public static final int DELETE_POLICY_7DAYS = 1; // not supported 782 public static final int DELETE_POLICY_ON_DELETE = 2; 783 784 // Sentinel values for the mSyncInterval field of both Account records 785 public static final int CHECK_INTERVAL_NEVER = -1; 786 public static final int CHECK_INTERVAL_PUSH = -2; 787 788 public static final int SYNC_WINDOW_USER = -1; 789 790 public String mDisplayName; 791 public String mEmailAddress; 792 public String mSyncKey; 793 public int mSyncLookback; 794 public int mSyncInterval; 795 public long mHostAuthKeyRecv; 796 public long mHostAuthKeySend; 797 public int mFlags; 798 public boolean mIsDefault; // note: callers should use getDefaultAccountId() 799 public String mCompatibilityUuid; 800 public String mSenderName; 801 public String mRingtoneUri; 802 public String mProtocolVersion; 803 public int mNewMessageCount; 804 805 // Convenience for creating an account 806 public transient HostAuth mHostAuthRecv; 807 public transient HostAuth mHostAuthSend; 808 809 public static final int CONTENT_ID_COLUMN = 0; 810 public static final int CONTENT_DISPLAY_NAME_COLUMN = 1; 811 public static final int CONTENT_EMAIL_ADDRESS_COLUMN = 2; 812 public static final int CONTENT_SYNC_KEY_COLUMN = 3; 813 public static final int CONTENT_SYNC_LOOKBACK_COLUMN = 4; 814 public static final int CONTENT_SYNC_INTERVAL_COLUMN = 5; 815 public static final int CONTENT_HOST_AUTH_KEY_RECV_COLUMN = 6; 816 public static final int CONTENT_HOST_AUTH_KEY_SEND_COLUMN = 7; 817 public static final int CONTENT_FLAGS_COLUMN = 8; 818 public static final int CONTENT_IS_DEFAULT_COLUMN = 9; 819 public static final int CONTENT_COMPATIBILITY_UUID_COLUMN = 10; 820 public static final int CONTENT_SENDER_NAME_COLUMN = 11; 821 public static final int CONTENT_RINGTONE_URI_COLUMN = 12; 822 public static final int CONTENT_PROTOCOL_VERSION_COLUMN = 13; 823 public static final int CONTENT_NEW_MESSAGE_COUNT_COLUMN = 14; 824 825 public static final String[] CONTENT_PROJECTION = new String[] { 826 RECORD_ID, AccountColumns.DISPLAY_NAME, 827 AccountColumns.EMAIL_ADDRESS, AccountColumns.SYNC_KEY, AccountColumns.SYNC_LOOKBACK, 828 AccountColumns.SYNC_INTERVAL, AccountColumns.HOST_AUTH_KEY_RECV, 829 AccountColumns.HOST_AUTH_KEY_SEND, AccountColumns.FLAGS, AccountColumns.IS_DEFAULT, 830 AccountColumns.COMPATIBILITY_UUID, AccountColumns.SENDER_NAME, 831 AccountColumns.RINGTONE_URI, AccountColumns.PROTOCOL_VERSION, 832 AccountColumns.NEW_MESSAGE_COUNT 833 }; 834 835 public static final int CONTENT_MAILBOX_TYPE_COLUMN = 1; 836 837 /** 838 * This projection is for listing account id's only 839 */ 840 public static final String[] ID_TYPE_PROJECTION = new String[] { 841 RECORD_ID, MailboxColumns.TYPE 842 }; 843 844 public static final String MAILBOX_SELECTION = 845 MessageColumns.MAILBOX_KEY + " =?"; 846 847 public static final String UNREAD_COUNT_SELECTION = 848 MessageColumns.MAILBOX_KEY + " =? and " + MessageColumns.FLAG_READ + "= 0"; 849 850 /** 851 * This projection is for searching for the default account 852 */ 853 private static final String[] DEFAULT_ID_PROJECTION = new String[] { 854 RECORD_ID, IS_DEFAULT 855 }; 856 857 /** 858 * no public constructor since this is a utility class 859 */ Account()860 public Account() { 861 mBaseUri = CONTENT_URI; 862 863 // other defaults (policy) 864 mRingtoneUri = "content://settings/system/notification_sound"; 865 mSyncInterval = -1; 866 mSyncLookback = -1; 867 mFlags = FLAGS_NOTIFY_NEW_MAIL; 868 mCompatibilityUuid = UUID.randomUUID().toString(); 869 } 870 restoreAccountWithId(Context context, long id)871 public static Account restoreAccountWithId(Context context, long id) { 872 Uri u = ContentUris.withAppendedId(Account.CONTENT_URI, id); 873 Cursor c = context.getContentResolver().query(u, Account.CONTENT_PROJECTION, 874 null, null, null); 875 876 try { 877 if (c.moveToFirst()) { 878 return getContent(c, Account.class); 879 } else { 880 return null; 881 } 882 } finally { 883 c.close(); 884 } 885 } 886 887 /** 888 * Refresh an account that has already been loaded. This is slightly less expensive 889 * that generating a brand-new account object. 890 */ refresh(Context context)891 public void refresh(Context context) { 892 Cursor c = context.getContentResolver().query(this.getUri(), Account.CONTENT_PROJECTION, 893 null, null, null); 894 try { 895 c.moveToFirst(); 896 restore(c); 897 } finally { 898 if (c != null) { 899 c.close(); 900 } 901 } 902 } 903 904 @Override 905 @SuppressWarnings("unchecked") restore(Cursor cursor)906 public EmailContent.Account restore(Cursor cursor) { 907 mId = cursor.getLong(CONTENT_ID_COLUMN); 908 mBaseUri = CONTENT_URI; 909 mDisplayName = cursor.getString(CONTENT_DISPLAY_NAME_COLUMN); 910 mEmailAddress = cursor.getString(CONTENT_EMAIL_ADDRESS_COLUMN); 911 mSyncKey = cursor.getString(CONTENT_SYNC_KEY_COLUMN); 912 mSyncLookback = cursor.getInt(CONTENT_SYNC_LOOKBACK_COLUMN); 913 mSyncInterval = cursor.getInt(CONTENT_SYNC_INTERVAL_COLUMN); 914 mHostAuthKeyRecv = cursor.getLong(CONTENT_HOST_AUTH_KEY_RECV_COLUMN); 915 mHostAuthKeySend = cursor.getLong(CONTENT_HOST_AUTH_KEY_SEND_COLUMN); 916 mFlags = cursor.getInt(CONTENT_FLAGS_COLUMN); 917 mIsDefault = cursor.getInt(CONTENT_IS_DEFAULT_COLUMN) == 1; 918 mCompatibilityUuid = cursor.getString(CONTENT_COMPATIBILITY_UUID_COLUMN); 919 mSenderName = cursor.getString(CONTENT_SENDER_NAME_COLUMN); 920 mRingtoneUri = cursor.getString(CONTENT_RINGTONE_URI_COLUMN); 921 mProtocolVersion = cursor.getString(CONTENT_PROTOCOL_VERSION_COLUMN); 922 mNewMessageCount = cursor.getInt(CONTENT_NEW_MESSAGE_COUNT_COLUMN); 923 return this; 924 } 925 getId(Uri u)926 private long getId(Uri u) { 927 return Long.parseLong(u.getPathSegments().get(1)); 928 } 929 930 /** 931 * @return the user-visible name for the account 932 */ getDisplayName()933 public String getDisplayName() { 934 return mDisplayName; 935 } 936 937 /** 938 * Set the description. Be sure to call save() to commit to database. 939 * @param description the new description 940 */ setDisplayName(String description)941 public void setDisplayName(String description) { 942 mDisplayName = description; 943 } 944 945 /** 946 * @return the email address for this account 947 */ getEmailAddress()948 public String getEmailAddress() { 949 return mEmailAddress; 950 } 951 952 /** 953 * Set the Email address for this account. Be sure to call save() to commit to database. 954 * @param emailAddress the new email address for this account 955 */ setEmailAddress(String emailAddress)956 public void setEmailAddress(String emailAddress) { 957 mEmailAddress = emailAddress; 958 } 959 960 /** 961 * @return the sender's name for this account 962 */ getSenderName()963 public String getSenderName() { 964 return mSenderName; 965 } 966 967 /** 968 * Set the sender's name. Be sure to call save() to commit to database. 969 * @param name the new sender name 970 */ setSenderName(String name)971 public void setSenderName(String name) { 972 mSenderName = name; 973 } 974 975 /** 976 * @return the minutes per check (for polling) 977 * TODO define sentinel values for "never", "push", etc. See Account.java 978 */ getSyncInterval()979 public int getSyncInterval() 980 { 981 return mSyncInterval; 982 } 983 984 /** 985 * Set the minutes per check (for polling). Be sure to call save() to commit to database. 986 * TODO define sentinel values for "never", "push", etc. See Account.java 987 * @param minutes the number of minutes between polling checks 988 */ setSyncInterval(int minutes)989 public void setSyncInterval(int minutes) 990 { 991 mSyncInterval = minutes; 992 } 993 994 /** 995 * @return the sync lookback window in # of days 996 * TODO define sentinel values for "all", "1 month", etc. See Account.java 997 */ getSyncLookback()998 public int getSyncLookback() { 999 return mSyncLookback; 1000 } 1001 1002 /** 1003 * Set the sync lookback window in # of days. Be sure to call save() to commit to database. 1004 * TODO define sentinel values for "all", "1 month", etc. See Account.java 1005 * @param days number of days to look back for syncing messages 1006 */ setSyncLookback(int days)1007 public void setSyncLookback(int days) { 1008 mSyncLookback = days; 1009 } 1010 1011 /** 1012 * @return the flags for this account 1013 * @see #FLAGS_NOTIFY_NEW_MAIL 1014 * @see #FLAGS_VIBRATE 1015 */ getFlags()1016 public int getFlags() { 1017 return mFlags; 1018 } 1019 1020 /** 1021 * Set the flags for this account 1022 * @see #FLAGS_NOTIFY_NEW_MAIL 1023 * @see #FLAGS_VIBRATE 1024 * @param newFlags the new value for the flags 1025 */ setFlags(int newFlags)1026 public void setFlags(int newFlags) { 1027 mFlags = newFlags; 1028 } 1029 1030 /** 1031 * @return the ringtone Uri for this account 1032 */ getRingtone()1033 public String getRingtone() { 1034 return mRingtoneUri; 1035 } 1036 1037 /** 1038 * Set the ringtone Uri for this account 1039 * @param newUri the new URI string for the ringtone for this account 1040 */ setRingtone(String newUri)1041 public void setRingtone(String newUri) { 1042 mRingtoneUri = newUri; 1043 } 1044 1045 /** 1046 * Set the "delete policy" as a simple 0,1,2 value set. 1047 * @param newPolicy the new delete policy 1048 */ setDeletePolicy(int newPolicy)1049 public void setDeletePolicy(int newPolicy) { 1050 mFlags &= ~FLAGS_DELETE_POLICY_MASK; 1051 mFlags |= (newPolicy << FLAGS_DELETE_POLICY_SHIFT) & FLAGS_DELETE_POLICY_MASK; 1052 } 1053 1054 /** 1055 * Return the "delete policy" as a simple 0,1,2 value set. 1056 * @return the current delete policy 1057 */ getDeletePolicy()1058 public int getDeletePolicy() { 1059 return (mFlags & FLAGS_DELETE_POLICY_MASK) >> FLAGS_DELETE_POLICY_SHIFT; 1060 } 1061 1062 /** 1063 * Return the Uuid associated with this account. This is primarily for compatibility 1064 * with accounts set up by previous versions, because there are externals references 1065 * to the Uuid (e.g. desktop shortcuts). 1066 */ getUuid()1067 String getUuid() { 1068 return mCompatibilityUuid; 1069 } 1070 1071 /** 1072 * For compatibility while converting to provider model, generate a "store URI" 1073 * 1074 * @return a string in the form of a Uri, as used by the other parts of the email app 1075 */ getStoreUri(Context context)1076 public String getStoreUri(Context context) { 1077 // reconstitute if necessary 1078 if (mHostAuthRecv == null) { 1079 mHostAuthRecv = HostAuth.restoreHostAuthWithId(context, mHostAuthKeyRecv); 1080 } 1081 // convert if available 1082 if (mHostAuthRecv != null) { 1083 String storeUri = mHostAuthRecv.getStoreUri(); 1084 if (storeUri != null) { 1085 return storeUri; 1086 } 1087 } 1088 return ""; 1089 } 1090 1091 /** 1092 * For compatibility while converting to provider model, generate a "sender URI" 1093 * 1094 * @return a string in the form of a Uri, as used by the other parts of the email app 1095 */ getSenderUri(Context context)1096 public String getSenderUri(Context context) { 1097 // reconstitute if necessary 1098 if (mHostAuthSend == null) { 1099 mHostAuthSend = HostAuth.restoreHostAuthWithId(context, mHostAuthKeySend); 1100 } 1101 // convert if available 1102 if (mHostAuthSend != null) { 1103 String senderUri = mHostAuthSend.getStoreUri(); 1104 if (senderUri != null) { 1105 return senderUri; 1106 } 1107 } 1108 return ""; 1109 } 1110 1111 /** 1112 * For compatibility while converting to provider model, set the store URI 1113 * 1114 * @param context 1115 * @param storeUri the new value 1116 */ 1117 @Deprecated setStoreUri(Context context, String storeUri)1118 public void setStoreUri(Context context, String storeUri) { 1119 // reconstitute or create if necessary 1120 if (mHostAuthRecv == null) { 1121 if (mHostAuthKeyRecv != 0) { 1122 mHostAuthRecv = HostAuth.restoreHostAuthWithId(context, mHostAuthKeyRecv); 1123 } else { 1124 mHostAuthRecv = new EmailContent.HostAuth(); 1125 } 1126 } 1127 1128 if (mHostAuthRecv != null) { 1129 mHostAuthRecv.setStoreUri(storeUri); 1130 } 1131 } 1132 1133 /** 1134 * For compatibility while converting to provider model, set the sender URI 1135 * 1136 * @param context 1137 * @param senderUri the new value 1138 */ 1139 @Deprecated setSenderUri(Context context, String senderUri)1140 public void setSenderUri(Context context, String senderUri) { 1141 // reconstitute or create if necessary 1142 if (mHostAuthSend == null) { 1143 if (mHostAuthKeySend != 0) { 1144 mHostAuthSend = HostAuth.restoreHostAuthWithId(context, mHostAuthKeySend); 1145 } else { 1146 mHostAuthSend = new EmailContent.HostAuth(); 1147 } 1148 } 1149 1150 if (mHostAuthSend != null) { 1151 mHostAuthSend.setStoreUri(senderUri); 1152 } 1153 } 1154 1155 /** 1156 * For compatibility while converting to provider model, generate a "local store URI" 1157 * 1158 * @return a string in the form of a Uri, as used by the other parts of the email app 1159 */ getLocalStoreUri(Context context)1160 public String getLocalStoreUri(Context context) { 1161 return "local://localhost/" + context.getDatabasePath(getUuid() + ".db"); 1162 } 1163 1164 /** 1165 * Set the account to be the default account. If this is set to "true", when the account 1166 * is saved, all other accounts will have the same value set to "false". 1167 * @param newDefaultState the new default state - if true, others will be cleared. 1168 */ setDefaultAccount(boolean newDefaultState)1169 public void setDefaultAccount(boolean newDefaultState) { 1170 mIsDefault = newDefaultState; 1171 } 1172 1173 /** 1174 * Helper method for finding the default account. 1175 */ getDefaultAccountWhere(Context context, String where)1176 static private long getDefaultAccountWhere(Context context, String where) { 1177 Cursor cursor = context.getContentResolver().query(CONTENT_URI, 1178 DEFAULT_ID_PROJECTION, 1179 where, null, null); 1180 try { 1181 if (cursor.moveToFirst()) { 1182 return cursor.getLong(0); // column 0 is id 1183 } 1184 } finally { 1185 cursor.close(); 1186 } 1187 return -1; 1188 } 1189 1190 /** 1191 * Return the id of the default account. If one hasn't been explicitly specified, return 1192 * the first one in the database. For any account saved in the DB, this must be used 1193 * to check for the default account - the mIsDefault field is set lazily and may be 1194 * incorrect. 1195 * @param context the caller's context 1196 * @return the id of the default account, or -1 if there are no accounts 1197 */ getDefaultAccountId(Context context)1198 static public long getDefaultAccountId(Context context) { 1199 long id = getDefaultAccountWhere(context, AccountColumns.IS_DEFAULT + "=1"); 1200 if (id == -1) { 1201 id = getDefaultAccountWhere(context, null); 1202 } 1203 return id; 1204 } 1205 1206 /** 1207 * Override update to enforce a single default account, and do it atomically 1208 */ 1209 @Override update(Context context, ContentValues cv)1210 public int update(Context context, ContentValues cv) { 1211 if (cv.containsKey(AccountColumns.IS_DEFAULT) && 1212 cv.getAsBoolean(AccountColumns.IS_DEFAULT)) { 1213 ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>(); 1214 ContentValues cv1 = new ContentValues(); 1215 cv1.put(AccountColumns.IS_DEFAULT, false); 1216 // Clear the default flag in all accounts 1217 ops.add(ContentProviderOperation.newUpdate(CONTENT_URI).withValues(cv1).build()); 1218 // Update this account 1219 ops.add(ContentProviderOperation 1220 .newUpdate(ContentUris.withAppendedId(CONTENT_URI, mId)) 1221 .withValues(cv).build()); 1222 try { 1223 context.getContentResolver().applyBatch(EmailProvider.EMAIL_AUTHORITY, ops); 1224 return 1; 1225 } catch (RemoteException e) { 1226 // There is nothing to be done here; fail by returning 0 1227 } catch (OperationApplicationException e) { 1228 // There is nothing to be done here; fail by returning 0 1229 } 1230 return 0; 1231 } 1232 return super.update(context, cv); 1233 } 1234 1235 /* 1236 * Override this so that we can store the HostAuth's first and link them to the Account 1237 * (non-Javadoc) 1238 * @see com.android.email.provider.EmailContent#save(android.content.Context) 1239 */ 1240 @Override save(Context context)1241 public Uri save(Context context) { 1242 if (isSaved()) { 1243 throw new UnsupportedOperationException(); 1244 } 1245 // This logic is in place so I can (a) short circuit the expensive stuff when 1246 // possible, and (b) override (and throw) if anyone tries to call save() or update() 1247 // directly for Account, which are unsupported. 1248 if (mHostAuthRecv == null && mHostAuthSend == null && mIsDefault == false) { 1249 return super.save(context); 1250 } 1251 1252 int index = 0; 1253 int recvIndex = -1; 1254 int sendIndex = -1; 1255 1256 // Create operations for saving the send and recv hostAuths 1257 // Also, remember which operation in the array they represent 1258 ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>(); 1259 if (mHostAuthRecv != null) { 1260 recvIndex = index++; 1261 ops.add(ContentProviderOperation.newInsert(mHostAuthRecv.mBaseUri) 1262 .withValues(mHostAuthRecv.toContentValues()) 1263 .build()); 1264 } 1265 if (mHostAuthSend != null) { 1266 sendIndex = index++; 1267 ops.add(ContentProviderOperation.newInsert(mHostAuthRecv.mBaseUri) 1268 .withValues(mHostAuthSend.toContentValues()) 1269 .build()); 1270 } 1271 1272 // Create operations for making this the only default account 1273 // Note, these are always updates because they change existing accounts 1274 if (mIsDefault) { 1275 index++; 1276 ContentValues cv1 = new ContentValues(); 1277 cv1.put(AccountColumns.IS_DEFAULT, 0); 1278 ops.add(ContentProviderOperation.newUpdate(CONTENT_URI).withValues(cv1).build()); 1279 } 1280 1281 // Now do the Account 1282 ContentValues cv = null; 1283 if (recvIndex >= 0 || sendIndex >= 0) { 1284 cv = new ContentValues(); 1285 if (recvIndex >= 0) { 1286 cv.put(Account.HOST_AUTH_KEY_RECV, recvIndex); 1287 } 1288 if (sendIndex >= 0) { 1289 cv.put(Account.HOST_AUTH_KEY_SEND, sendIndex); 1290 } 1291 } 1292 1293 ContentProviderOperation.Builder b = ContentProviderOperation.newInsert(mBaseUri); 1294 b.withValues(toContentValues()); 1295 if (cv != null) { 1296 b.withValueBackReferences(cv); 1297 } 1298 ops.add(b.build()); 1299 1300 try { 1301 ContentProviderResult[] results = 1302 context.getContentResolver().applyBatch(EmailProvider.EMAIL_AUTHORITY, ops); 1303 // If saving, set the mId's of the various saved objects 1304 if (recvIndex >= 0) { 1305 long newId = getId(results[recvIndex].uri); 1306 mHostAuthKeyRecv = newId; 1307 mHostAuthRecv.mId = newId; 1308 } 1309 if (sendIndex >= 0) { 1310 long newId = getId(results[sendIndex].uri); 1311 mHostAuthKeySend = newId; 1312 mHostAuthSend.mId = newId; 1313 } 1314 Uri u = results[index].uri; 1315 mId = getId(u); 1316 return u; 1317 } catch (RemoteException e) { 1318 // There is nothing to be done here; fail by returning null 1319 } catch (OperationApplicationException e) { 1320 // There is nothing to be done here; fail by returning null 1321 } 1322 return null; 1323 } 1324 1325 @Override toContentValues()1326 public ContentValues toContentValues() { 1327 ContentValues values = new ContentValues(); 1328 values.put(AccountColumns.DISPLAY_NAME, mDisplayName); 1329 values.put(AccountColumns.EMAIL_ADDRESS, mEmailAddress); 1330 values.put(AccountColumns.SYNC_KEY, mSyncKey); 1331 values.put(AccountColumns.SYNC_LOOKBACK, mSyncLookback); 1332 values.put(AccountColumns.SYNC_INTERVAL, mSyncInterval); 1333 values.put(AccountColumns.HOST_AUTH_KEY_RECV, mHostAuthKeyRecv); 1334 values.put(AccountColumns.HOST_AUTH_KEY_SEND, mHostAuthKeySend); 1335 values.put(AccountColumns.FLAGS, mFlags); 1336 values.put(AccountColumns.IS_DEFAULT, mIsDefault); 1337 values.put(AccountColumns.COMPATIBILITY_UUID, mCompatibilityUuid); 1338 values.put(AccountColumns.SENDER_NAME, mSenderName); 1339 values.put(AccountColumns.RINGTONE_URI, mRingtoneUri); 1340 values.put(AccountColumns.PROTOCOL_VERSION, mProtocolVersion); 1341 values.put(AccountColumns.NEW_MESSAGE_COUNT, mNewMessageCount); 1342 return values; 1343 } 1344 1345 /** 1346 * Supports Parcelable 1347 */ describeContents()1348 public int describeContents() { 1349 return 0; 1350 } 1351 1352 /** 1353 * Supports Parcelable 1354 */ 1355 public static final Parcelable.Creator<EmailContent.Account> CREATOR 1356 = new Parcelable.Creator<EmailContent.Account>() { 1357 public EmailContent.Account createFromParcel(Parcel in) { 1358 return new EmailContent.Account(in); 1359 } 1360 1361 public EmailContent.Account[] newArray(int size) { 1362 return new EmailContent.Account[size]; 1363 } 1364 }; 1365 1366 /** 1367 * Supports Parcelable 1368 */ writeToParcel(Parcel dest, int flags)1369 public void writeToParcel(Parcel dest, int flags) { 1370 // mBaseUri is not parceled 1371 dest.writeLong(mId); 1372 dest.writeString(mDisplayName); 1373 dest.writeString(mEmailAddress); 1374 dest.writeString(mSyncKey); 1375 dest.writeInt(mSyncLookback); 1376 dest.writeInt(mSyncInterval); 1377 dest.writeLong(mHostAuthKeyRecv); 1378 dest.writeLong(mHostAuthKeySend); 1379 dest.writeInt(mFlags); 1380 dest.writeByte(mIsDefault ? (byte)1 : (byte)0); 1381 dest.writeString(mCompatibilityUuid); 1382 dest.writeString(mSenderName); 1383 dest.writeString(mRingtoneUri); 1384 dest.writeString(mProtocolVersion); 1385 dest.writeInt(mNewMessageCount); 1386 1387 if (mHostAuthRecv != null) { 1388 dest.writeByte((byte)1); 1389 mHostAuthRecv.writeToParcel(dest, flags); 1390 } else { 1391 dest.writeByte((byte)0); 1392 } 1393 1394 if (mHostAuthSend != null) { 1395 dest.writeByte((byte)1); 1396 mHostAuthSend.writeToParcel(dest, flags); 1397 } else { 1398 dest.writeByte((byte)0); 1399 } 1400 } 1401 1402 /** 1403 * Supports Parcelable 1404 */ Account(Parcel in)1405 public Account(Parcel in) { 1406 mBaseUri = EmailContent.Account.CONTENT_URI; 1407 mId = in.readLong(); 1408 mDisplayName = in.readString(); 1409 mEmailAddress = in.readString(); 1410 mSyncKey = in.readString(); 1411 mSyncLookback = in.readInt(); 1412 mSyncInterval = in.readInt(); 1413 mHostAuthKeyRecv = in.readLong(); 1414 mHostAuthKeySend = in.readLong(); 1415 mFlags = in.readInt(); 1416 mIsDefault = in.readByte() == 1; 1417 mCompatibilityUuid = in.readString(); 1418 mSenderName = in.readString(); 1419 mRingtoneUri = in.readString(); 1420 mProtocolVersion = in.readString(); 1421 mNewMessageCount = in.readInt(); 1422 1423 mHostAuthRecv = null; 1424 if (in.readByte() == 1) { 1425 mHostAuthRecv = new EmailContent.HostAuth(in); 1426 } 1427 1428 mHostAuthSend = null; 1429 if (in.readByte() == 1) { 1430 mHostAuthSend = new EmailContent.HostAuth(in); 1431 } 1432 } 1433 1434 /** 1435 * For debugger support only - DO NOT use for code. 1436 */ 1437 @Override toString()1438 public String toString() { 1439 StringBuilder sb = new StringBuilder('['); 1440 if (mHostAuthRecv != null && mHostAuthRecv.mProtocol != null) { 1441 sb.append(mHostAuthRecv.mProtocol); 1442 sb.append(':'); 1443 } 1444 if (mDisplayName != null) sb.append(mDisplayName); 1445 sb.append(':'); 1446 if (mEmailAddress != null) sb.append(mEmailAddress); 1447 sb.append(':'); 1448 if (mSenderName != null) sb.append(mSenderName); 1449 sb.append(']'); 1450 return sb.toString(); 1451 } 1452 1453 } 1454 1455 public interface AttachmentColumns { 1456 public static final String ID = "_id"; 1457 // The display name of the attachment 1458 public static final String FILENAME = "fileName"; 1459 // The mime type of the attachment 1460 public static final String MIME_TYPE = "mimeType"; 1461 // The size of the attachment in bytes 1462 public static final String SIZE = "size"; 1463 // The (internal) contentId of the attachment (inline attachments will have these) 1464 public static final String CONTENT_ID = "contentId"; 1465 // The location of the loaded attachment (probably a file) 1466 public static final String CONTENT_URI = "contentUri"; 1467 // A foreign key into the Message table (the message owning this attachment) 1468 public static final String MESSAGE_KEY = "messageKey"; 1469 // The location of the attachment on the server side 1470 // For IMAP, this is a part number (e.g. 2.1); for EAS, it's the internal file name 1471 public static final String LOCATION = "location"; 1472 // The transfer encoding of the attachment 1473 public static final String ENCODING = "encoding"; 1474 } 1475 1476 public static final class Attachment extends EmailContent implements AttachmentColumns { 1477 public static final String TABLE_NAME = "Attachment"; 1478 public static final Uri CONTENT_URI = Uri.parse(EmailContent.CONTENT_URI + "/attachment"); 1479 // This must be used with an appended id: ContentUris.withAppendedId(MESSAGE_ID_URI, id) 1480 public static final Uri MESSAGE_ID_URI = Uri.parse( 1481 EmailContent.CONTENT_URI + "/attachment/message"); 1482 1483 public String mFileName; 1484 public String mMimeType; 1485 public long mSize; 1486 public String mContentId; 1487 public String mContentUri; 1488 public long mMessageKey; 1489 public String mLocation; 1490 public String mEncoding; 1491 1492 public static final int CONTENT_ID_COLUMN = 0; 1493 public static final int CONTENT_FILENAME_COLUMN = 1; 1494 public static final int CONTENT_MIME_TYPE_COLUMN = 2; 1495 public static final int CONTENT_SIZE_COLUMN = 3; 1496 public static final int CONTENT_CONTENT_ID_COLUMN = 4; 1497 public static final int CONTENT_CONTENT_URI_COLUMN = 5; 1498 public static final int CONTENT_MESSAGE_ID_COLUMN = 6; 1499 public static final int CONTENT_LOCATION_COLUMN = 7; 1500 public static final int CONTENT_ENCODING_COLUMN = 8; 1501 public static final String[] CONTENT_PROJECTION = new String[] { 1502 RECORD_ID, AttachmentColumns.FILENAME, AttachmentColumns.MIME_TYPE, 1503 AttachmentColumns.SIZE, AttachmentColumns.CONTENT_ID, AttachmentColumns.CONTENT_URI, 1504 AttachmentColumns.MESSAGE_KEY, AttachmentColumns.LOCATION, AttachmentColumns.ENCODING 1505 }; 1506 1507 /** 1508 * no public constructor since this is a utility class 1509 */ Attachment()1510 public Attachment() { 1511 mBaseUri = CONTENT_URI; 1512 } 1513 1514 /** 1515 * Restore an Attachment from the database, given its unique id 1516 * @param context 1517 * @param id 1518 * @return the instantiated Attachment 1519 */ restoreAttachmentWithId(Context context, long id)1520 public static Attachment restoreAttachmentWithId (Context context, long id) { 1521 Uri u = ContentUris.withAppendedId(Attachment.CONTENT_URI, id); 1522 Cursor c = context.getContentResolver().query(u, Attachment.CONTENT_PROJECTION, 1523 null, null, null); 1524 1525 try { 1526 if (c.moveToFirst()) { 1527 return getContent(c, Attachment.class); 1528 } else { 1529 return null; 1530 } 1531 } finally { 1532 c.close(); 1533 } 1534 } 1535 1536 /** 1537 * Restore all the Attachments of a message given its messageId 1538 */ restoreAttachmentsWithMessageId(Context context, long messageId)1539 public static Attachment[] restoreAttachmentsWithMessageId(Context context, 1540 long messageId) { 1541 Uri uri = ContentUris.withAppendedId(MESSAGE_ID_URI, messageId); 1542 Cursor c = context.getContentResolver().query(uri, CONTENT_PROJECTION, 1543 null, null, null); 1544 try { 1545 int count = c.getCount(); 1546 Attachment[] attachments = new Attachment[count]; 1547 for (int i = 0; i < count; ++i) { 1548 c.moveToNext(); 1549 attachments[i] = new Attachment().restore(c); 1550 } 1551 return attachments; 1552 } finally { 1553 c.close(); 1554 } 1555 } 1556 1557 /** 1558 * Creates a unique file in the external store by appending a hyphen 1559 * and a number to the given filename. 1560 * @param filename 1561 * @return a new File object, or null if one could not be created 1562 */ createUniqueFile(String filename)1563 public static File createUniqueFile(String filename) { 1564 // TODO Handle internal storage, as required 1565 if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { 1566 File directory = Environment.getExternalStorageDirectory(); 1567 File file = new File(directory, filename); 1568 if (!file.exists()) { 1569 return file; 1570 } 1571 // Get the extension of the file, if any. 1572 int index = filename.lastIndexOf('.'); 1573 String name = filename; 1574 String extension = ""; 1575 if (index != -1) { 1576 name = filename.substring(0, index); 1577 extension = filename.substring(index); 1578 } 1579 for (int i = 2; i < Integer.MAX_VALUE; i++) { 1580 file = new File(directory, name + '-' + i + extension); 1581 if (!file.exists()) { 1582 return file; 1583 } 1584 } 1585 return null; 1586 } 1587 return null; 1588 } 1589 1590 @Override 1591 @SuppressWarnings("unchecked") restore(Cursor cursor)1592 public EmailContent.Attachment restore(Cursor cursor) { 1593 mBaseUri = CONTENT_URI; 1594 mId = cursor.getLong(CONTENT_ID_COLUMN); 1595 mFileName= cursor.getString(CONTENT_FILENAME_COLUMN); 1596 mMimeType = cursor.getString(CONTENT_MIME_TYPE_COLUMN); 1597 mSize = cursor.getLong(CONTENT_SIZE_COLUMN); 1598 mContentId = cursor.getString(CONTENT_CONTENT_ID_COLUMN); 1599 mContentUri = cursor.getString(CONTENT_CONTENT_URI_COLUMN); 1600 mMessageKey = cursor.getLong(CONTENT_MESSAGE_ID_COLUMN); 1601 mLocation = cursor.getString(CONTENT_LOCATION_COLUMN); 1602 mEncoding = cursor.getString(CONTENT_ENCODING_COLUMN); 1603 return this; 1604 } 1605 1606 @Override toContentValues()1607 public ContentValues toContentValues() { 1608 ContentValues values = new ContentValues(); 1609 values.put(AttachmentColumns.FILENAME, mFileName); 1610 values.put(AttachmentColumns.MIME_TYPE, mMimeType); 1611 values.put(AttachmentColumns.SIZE, mSize); 1612 values.put(AttachmentColumns.CONTENT_ID, mContentId); 1613 values.put(AttachmentColumns.CONTENT_URI, mContentUri); 1614 values.put(AttachmentColumns.MESSAGE_KEY, mMessageKey); 1615 values.put(AttachmentColumns.LOCATION, mLocation); 1616 values.put(AttachmentColumns.ENCODING, mEncoding); 1617 return values; 1618 } 1619 describeContents()1620 public int describeContents() { 1621 return 0; 1622 } 1623 writeToParcel(Parcel dest, int flags)1624 public void writeToParcel(Parcel dest, int flags) { 1625 // mBaseUri is not parceled 1626 dest.writeLong(mId); 1627 dest.writeString(mFileName); 1628 dest.writeString(mMimeType); 1629 dest.writeLong(mSize); 1630 dest.writeString(mContentId); 1631 dest.writeString(mContentUri); 1632 dest.writeLong(mMessageKey); 1633 dest.writeString(mLocation); 1634 dest.writeString(mEncoding); 1635 } 1636 Attachment(Parcel in)1637 public Attachment(Parcel in) { 1638 mBaseUri = EmailContent.Attachment.CONTENT_URI; 1639 mId = in.readLong(); 1640 mFileName = in.readString(); 1641 mMimeType = in.readString(); 1642 mSize = in.readLong(); 1643 mContentId = in.readString(); 1644 mContentUri = in.readString(); 1645 mMessageKey = in.readLong(); 1646 mLocation = in.readString(); 1647 mEncoding = in.readString(); 1648 } 1649 1650 public static final Parcelable.Creator<EmailContent.Attachment> CREATOR 1651 = new Parcelable.Creator<EmailContent.Attachment>() { 1652 public EmailContent.Attachment createFromParcel(Parcel in) { 1653 return new EmailContent.Attachment(in); 1654 } 1655 1656 public EmailContent.Attachment[] newArray(int size) { 1657 return new EmailContent.Attachment[size]; 1658 } 1659 }; 1660 1661 @Override toString()1662 public String toString() { 1663 return "[" + mFileName + ", " + mMimeType + ", " + mSize + ", " + mContentId + ", " 1664 + mContentUri + ", " + mMessageKey + ", " + mLocation + ", " + mEncoding + "]"; 1665 } 1666 } 1667 1668 public interface MailboxColumns { 1669 public static final String ID = "_id"; 1670 // The display name of this mailbox [INDEX] 1671 static final String DISPLAY_NAME = "displayName"; 1672 // The server's identifier for this mailbox 1673 public static final String SERVER_ID = "serverId"; 1674 // The server's identifier for the parent of this mailbox (null = top-level) 1675 public static final String PARENT_SERVER_ID = "parentServerId"; 1676 // A foreign key to the Account that owns this mailbox 1677 public static final String ACCOUNT_KEY = "accountKey"; 1678 // The type (role) of this mailbox 1679 public static final String TYPE = "type"; 1680 // The hierarchy separator character 1681 public static final String DELIMITER = "delimiter"; 1682 // Server-based sync key or validity marker (e.g. "SyncKey" for EAS, "uidvalidity" for IMAP) 1683 public static final String SYNC_KEY = "syncKey"; 1684 // The sync lookback period for this mailbox (or null if using the account default) 1685 public static final String SYNC_LOOKBACK = "syncLookback"; 1686 // The sync frequency for this mailbox (or null if using the account default) 1687 public static final String SYNC_INTERVAL = "syncInterval"; 1688 // The time of last successful sync completion (millis) 1689 public static final String SYNC_TIME = "syncTime"; 1690 // Cached unread count 1691 public static final String UNREAD_COUNT = "unreadCount"; 1692 // Visibility of this folder in a list of folders [INDEX] 1693 public static final String FLAG_VISIBLE = "flagVisible"; 1694 // Other states, as a bit field, e.g. CHILDREN_VISIBLE, HAS_CHILDREN 1695 public static final String FLAGS = "flags"; 1696 // Backward compatible 1697 public static final String VISIBLE_LIMIT = "visibleLimit"; 1698 // Sync status (can be used as desired by sync services) 1699 public static final String SYNC_STATUS = "syncStatus"; 1700 } 1701 1702 public static final class Mailbox extends EmailContent implements SyncColumns, MailboxColumns { 1703 public static final String TABLE_NAME = "Mailbox"; 1704 public static final Uri CONTENT_URI = Uri.parse(EmailContent.CONTENT_URI + "/mailbox"); 1705 public static final Uri ADD_TO_FIELD_URI = 1706 Uri.parse(EmailContent.CONTENT_URI + "/mailboxIdAddToField"); 1707 1708 public String mDisplayName; 1709 public String mServerId; 1710 public String mParentServerId; 1711 public long mAccountKey; 1712 public int mType; 1713 public int mDelimiter; 1714 public String mSyncKey; 1715 public int mSyncLookback; 1716 public int mSyncInterval; 1717 public long mSyncTime; 1718 public int mUnreadCount; 1719 public boolean mFlagVisible = true; 1720 public int mFlags; 1721 public int mVisibleLimit; 1722 public String mSyncStatus; 1723 1724 public static final int CONTENT_ID_COLUMN = 0; 1725 public static final int CONTENT_DISPLAY_NAME_COLUMN = 1; 1726 public static final int CONTENT_SERVER_ID_COLUMN = 2; 1727 public static final int CONTENT_PARENT_SERVER_ID_COLUMN = 3; 1728 public static final int CONTENT_ACCOUNT_KEY_COLUMN = 4; 1729 public static final int CONTENT_TYPE_COLUMN = 5; 1730 public static final int CONTENT_DELIMITER_COLUMN = 6; 1731 public static final int CONTENT_SYNC_KEY_COLUMN = 7; 1732 public static final int CONTENT_SYNC_LOOKBACK_COLUMN = 8; 1733 public static final int CONTENT_SYNC_INTERVAL_COLUMN = 9; 1734 public static final int CONTENT_SYNC_TIME_COLUMN = 10; 1735 public static final int CONTENT_UNREAD_COUNT_COLUMN = 11; 1736 public static final int CONTENT_FLAG_VISIBLE_COLUMN = 12; 1737 public static final int CONTENT_FLAGS_COLUMN = 13; 1738 public static final int CONTENT_VISIBLE_LIMIT_COLUMN = 14; 1739 public static final int CONTENT_SYNC_STATUS_COLUMN = 15; 1740 public static final String[] CONTENT_PROJECTION = new String[] { 1741 RECORD_ID, MailboxColumns.DISPLAY_NAME, MailboxColumns.SERVER_ID, 1742 MailboxColumns.PARENT_SERVER_ID, MailboxColumns.ACCOUNT_KEY, MailboxColumns.TYPE, 1743 MailboxColumns.DELIMITER, MailboxColumns.SYNC_KEY, MailboxColumns.SYNC_LOOKBACK, 1744 MailboxColumns.SYNC_INTERVAL, MailboxColumns.SYNC_TIME,MailboxColumns.UNREAD_COUNT, 1745 MailboxColumns.FLAG_VISIBLE, MailboxColumns.FLAGS, MailboxColumns.VISIBLE_LIMIT, 1746 MailboxColumns.SYNC_STATUS 1747 }; 1748 public static final long NO_MAILBOX = -1; 1749 1750 // Sentinel values for the mSyncInterval field of both Mailbox records 1751 public static final int CHECK_INTERVAL_NEVER = -1; 1752 public static final int CHECK_INTERVAL_PUSH = -2; 1753 // The following two sentinel values are used by EAS 1754 // Ping indicates that the EAS mailbox is synced based on a "ping" from the server 1755 public static final int CHECK_INTERVAL_PING = -3; 1756 // Push-Hold indicates an EAS push or ping Mailbox shouldn't sync just yet 1757 public static final int CHECK_INTERVAL_PUSH_HOLD = -4; 1758 1759 private static final String WHERE_TYPE_AND_ACCOUNT_KEY = 1760 MailboxColumns.TYPE + "=? and " + MailboxColumns.ACCOUNT_KEY + "=?"; 1761 1762 // Types of mailboxes. The list is ordered to match a typical UI presentation, e.g. 1763 // placing the inbox at the top. 1764 // The "main" mailbox for the account, almost always referred to as "Inbox" 1765 // Arrays of "special_mailbox_display_names" and "special_mailbox_icons" are depends on 1766 // types Id of mailboxes. 1767 public static final int TYPE_INBOX = 0; 1768 // Types of mailboxes 1769 // Holds mail (generic) 1770 public static final int TYPE_MAIL = 1; 1771 // Parent-only mailbox; holds no mail 1772 public static final int TYPE_PARENT = 2; 1773 // Holds drafts 1774 public static final int TYPE_DRAFTS = 3; 1775 // The local outbox associated with the Account 1776 public static final int TYPE_OUTBOX = 4; 1777 // Holds sent mail 1778 public static final int TYPE_SENT = 5; 1779 // Holds deleted mail 1780 public static final int TYPE_TRASH = 6; 1781 // Holds junk mail 1782 public static final int TYPE_JUNK = 7; 1783 1784 // Types after this are used for non-mail mailboxes (as in EAS) 1785 public static final int TYPE_NOT_EMAIL = 0x40; 1786 public static final int TYPE_CALENDAR = 0x41; 1787 public static final int TYPE_CONTACTS = 0x42; 1788 public static final int TYPE_TASKS = 0x43; 1789 public static final int TYPE_EAS_ACCOUNT_MAILBOX = 0x44; 1790 1791 // Bit field flags 1792 public static final int FLAG_HAS_CHILDREN = 1<<0; 1793 public static final int FLAG_CHILDREN_VISIBLE = 1<<1; 1794 public static final int FLAG_CANT_PUSH = 1<<2; 1795 1796 // Magic mailbox ID's 1797 // NOTE: This is a quick solution for merged mailboxes. I would rather implement this 1798 // with a more generic way of packaging and sharing queries between activities 1799 public static final long QUERY_ALL_INBOXES = -2; 1800 public static final long QUERY_ALL_UNREAD = -3; 1801 public static final long QUERY_ALL_FAVORITES = -4; 1802 public static final long QUERY_ALL_DRAFTS = -5; 1803 public static final long QUERY_ALL_OUTBOX = -6; 1804 Mailbox()1805 public Mailbox() { 1806 mBaseUri = CONTENT_URI; 1807 } 1808 1809 /** 1810 * Restore a Mailbox from the database, given its unique id 1811 * @param context 1812 * @param id 1813 * @return the instantiated Mailbox 1814 */ restoreMailboxWithId(Context context, long id)1815 public static Mailbox restoreMailboxWithId(Context context, long id) { 1816 Uri u = ContentUris.withAppendedId(Mailbox.CONTENT_URI, id); 1817 Cursor c = context.getContentResolver().query(u, Mailbox.CONTENT_PROJECTION, 1818 null, null, null); 1819 1820 try { 1821 if (c.moveToFirst()) { 1822 return EmailContent.getContent(c, Mailbox.class); 1823 } else { 1824 return null; 1825 } 1826 } finally { 1827 c.close(); 1828 } 1829 } 1830 1831 @Override 1832 @SuppressWarnings("unchecked") restore(Cursor cursor)1833 public EmailContent.Mailbox restore(Cursor cursor) { 1834 mBaseUri = CONTENT_URI; 1835 mId = cursor.getLong(CONTENT_ID_COLUMN); 1836 mDisplayName = cursor.getString(CONTENT_DISPLAY_NAME_COLUMN); 1837 mServerId = cursor.getString(CONTENT_SERVER_ID_COLUMN); 1838 mParentServerId = cursor.getString(CONTENT_PARENT_SERVER_ID_COLUMN); 1839 mAccountKey = cursor.getLong(CONTENT_ACCOUNT_KEY_COLUMN); 1840 mType = cursor.getInt(CONTENT_TYPE_COLUMN); 1841 mDelimiter = cursor.getInt(CONTENT_DELIMITER_COLUMN); 1842 mSyncKey = cursor.getString(CONTENT_SYNC_KEY_COLUMN); 1843 mSyncLookback = cursor.getInt(CONTENT_SYNC_LOOKBACK_COLUMN); 1844 mSyncInterval = cursor.getInt(CONTENT_SYNC_INTERVAL_COLUMN); 1845 mSyncTime = cursor.getLong(CONTENT_SYNC_TIME_COLUMN); 1846 mUnreadCount = cursor.getInt(CONTENT_UNREAD_COUNT_COLUMN); 1847 mFlagVisible = cursor.getInt(CONTENT_FLAG_VISIBLE_COLUMN) == 1; 1848 mFlags = cursor.getInt(CONTENT_FLAGS_COLUMN); 1849 mVisibleLimit = cursor.getInt(CONTENT_VISIBLE_LIMIT_COLUMN); 1850 mSyncStatus = cursor.getString(CONTENT_SYNC_STATUS_COLUMN); 1851 return this; 1852 } 1853 1854 @Override toContentValues()1855 public ContentValues toContentValues() { 1856 ContentValues values = new ContentValues(); 1857 values.put(MailboxColumns.DISPLAY_NAME, mDisplayName); 1858 values.put(MailboxColumns.SERVER_ID, mServerId); 1859 values.put(MailboxColumns.PARENT_SERVER_ID, mParentServerId); 1860 values.put(MailboxColumns.ACCOUNT_KEY, mAccountKey); 1861 values.put(MailboxColumns.TYPE, mType); 1862 values.put(MailboxColumns.DELIMITER, mDelimiter); 1863 values.put(MailboxColumns.SYNC_KEY, mSyncKey); 1864 values.put(MailboxColumns.SYNC_LOOKBACK, mSyncLookback); 1865 values.put(MailboxColumns.SYNC_INTERVAL, mSyncInterval); 1866 values.put(MailboxColumns.SYNC_TIME, mSyncTime); 1867 values.put(MailboxColumns.UNREAD_COUNT, mUnreadCount); 1868 values.put(MailboxColumns.FLAG_VISIBLE, mFlagVisible); 1869 values.put(MailboxColumns.FLAGS, mFlags); 1870 values.put(MailboxColumns.VISIBLE_LIMIT, mVisibleLimit); 1871 values.put(MailboxColumns.SYNC_STATUS, mSyncStatus); 1872 return values; 1873 } 1874 1875 /** 1876 * Convenience method to return the id of a given type of Mailbox for a given Account 1877 * @param context the caller's context, used to get a ContentResolver 1878 * @param accountId the id of the account to be queried 1879 * @param type the mailbox type, as defined above 1880 * @return the id of the mailbox, or -1 if not found 1881 */ findMailboxOfType(Context context, long accountId, int type)1882 public static long findMailboxOfType(Context context, long accountId, int type) { 1883 long mailboxId = NO_MAILBOX; 1884 String[] bindArguments = new String[] {Long.toString(type), Long.toString(accountId)}; 1885 Cursor c = context.getContentResolver().query(Mailbox.CONTENT_URI, 1886 ID_PROJECTION, WHERE_TYPE_AND_ACCOUNT_KEY, bindArguments, null); 1887 try { 1888 if (c.moveToFirst()) { 1889 mailboxId = c.getLong(ID_PROJECTION_COLUMN); 1890 } 1891 } finally { 1892 c.close(); 1893 } 1894 return mailboxId; 1895 } 1896 } 1897 1898 public interface HostAuthColumns { 1899 public static final String ID = "_id"; 1900 // The protocol (e.g. "imap", "pop3", "eas", "smtp" 1901 static final String PROTOCOL = "protocol"; 1902 // The host address 1903 static final String ADDRESS = "address"; 1904 // The port to use for the connection 1905 static final String PORT = "port"; 1906 // General purpose flags 1907 static final String FLAGS = "flags"; 1908 // The login (user name) 1909 static final String LOGIN = "login"; 1910 // Password 1911 static final String PASSWORD = "password"; 1912 // A domain or path, if required (used in IMAP and EAS) 1913 static final String DOMAIN = "domain"; 1914 // DEPRECATED - Will not be set or stored 1915 static final String ACCOUNT_KEY = "accountKey"; 1916 } 1917 1918 public static final class HostAuth extends EmailContent implements HostAuthColumns, Parcelable { 1919 public static final String TABLE_NAME = "HostAuth"; 1920 public static final Uri CONTENT_URI = Uri.parse(EmailContent.CONTENT_URI + "/hostauth"); 1921 1922 public static final int FLAG_SSL = 1; 1923 public static final int FLAG_TLS = 2; 1924 public static final int FLAG_AUTHENTICATE = 4; 1925 public static final int FLAG_TRUST_ALL_CERTIFICATES = 8; 1926 1927 public String mProtocol; 1928 public String mAddress; 1929 public int mPort; 1930 public int mFlags; 1931 public String mLogin; 1932 public String mPassword; 1933 public String mDomain; 1934 public long mAccountKey; // DEPRECATED - Will not be set or stored 1935 1936 public static final int CONTENT_ID_COLUMN = 0; 1937 public static final int CONTENT_PROTOCOL_COLUMN = 1; 1938 public static final int CONTENT_ADDRESS_COLUMN = 2; 1939 public static final int CONTENT_PORT_COLUMN = 3; 1940 public static final int CONTENT_FLAGS_COLUMN = 4; 1941 public static final int CONTENT_LOGIN_COLUMN = 5; 1942 public static final int CONTENT_PASSWORD_COLUMN = 6; 1943 public static final int CONTENT_DOMAIN_COLUMN = 7; 1944 public static final int CONTENT_ACCOUNT_KEY_COLUMN = 8; 1945 1946 public static final String[] CONTENT_PROJECTION = new String[] { 1947 RECORD_ID, HostAuthColumns.PROTOCOL, HostAuthColumns.ADDRESS, HostAuthColumns.PORT, 1948 HostAuthColumns.FLAGS, HostAuthColumns.LOGIN, 1949 HostAuthColumns.PASSWORD, HostAuthColumns.DOMAIN, 1950 HostAuthColumns.ACCOUNT_KEY 1951 }; 1952 1953 /** 1954 * no public constructor since this is a utility class 1955 */ HostAuth()1956 public HostAuth() { 1957 mBaseUri = CONTENT_URI; 1958 1959 // other defaults policy) 1960 mPort = -1; 1961 } 1962 1963 /** 1964 * Restore a HostAuth from the database, given its unique id 1965 * @param context 1966 * @param id 1967 * @return the instantiated HostAuth 1968 */ restoreHostAuthWithId(Context context, long id)1969 public static HostAuth restoreHostAuthWithId(Context context, long id) { 1970 Uri u = ContentUris.withAppendedId(EmailContent.HostAuth.CONTENT_URI, id); 1971 Cursor c = context.getContentResolver().query(u, HostAuth.CONTENT_PROJECTION, 1972 null, null, null); 1973 1974 try { 1975 if (c.moveToFirst()) { 1976 return getContent(c, HostAuth.class); 1977 } else { 1978 return null; 1979 } 1980 } finally { 1981 c.close(); 1982 } 1983 } 1984 1985 @Override 1986 @SuppressWarnings("unchecked") restore(Cursor cursor)1987 public EmailContent.HostAuth restore(Cursor cursor) { 1988 mBaseUri = CONTENT_URI; 1989 mId = cursor.getLong(CONTENT_ID_COLUMN); 1990 mProtocol = cursor.getString(CONTENT_PROTOCOL_COLUMN); 1991 mAddress = cursor.getString(CONTENT_ADDRESS_COLUMN); 1992 mPort = cursor.getInt(CONTENT_PORT_COLUMN); 1993 mFlags = cursor.getInt(CONTENT_FLAGS_COLUMN); 1994 mLogin = cursor.getString(CONTENT_LOGIN_COLUMN); 1995 mPassword = cursor.getString(CONTENT_PASSWORD_COLUMN); 1996 mDomain = cursor.getString(CONTENT_DOMAIN_COLUMN); 1997 mAccountKey = cursor.getLong(CONTENT_ACCOUNT_KEY_COLUMN); 1998 return this; 1999 } 2000 2001 @Override toContentValues()2002 public ContentValues toContentValues() { 2003 ContentValues values = new ContentValues(); 2004 values.put(HostAuthColumns.PROTOCOL, mProtocol); 2005 values.put(HostAuthColumns.ADDRESS, mAddress); 2006 values.put(HostAuthColumns.PORT, mPort); 2007 values.put(HostAuthColumns.FLAGS, mFlags); 2008 values.put(HostAuthColumns.LOGIN, mLogin); 2009 values.put(HostAuthColumns.PASSWORD, mPassword); 2010 values.put(HostAuthColumns.DOMAIN, mDomain); 2011 values.put(HostAuthColumns.ACCOUNT_KEY, mAccountKey); 2012 return values; 2013 } 2014 2015 /** 2016 * For compatibility while converting to provider model, generate a "store URI" 2017 * TODO cache this so we don't rebuild every time 2018 * 2019 * @return a string in the form of a Uri, as used by the other parts of the email app 2020 */ getStoreUri()2021 public String getStoreUri() { 2022 String security; 2023 switch (mFlags & (FLAG_SSL | FLAG_TLS | FLAG_TRUST_ALL_CERTIFICATES)) { 2024 case FLAG_SSL: 2025 security = "+ssl+"; 2026 break; 2027 case FLAG_SSL | FLAG_TRUST_ALL_CERTIFICATES: 2028 security = "+ssl+trustallcerts"; 2029 break; 2030 case FLAG_TLS: 2031 security = "+tls+"; 2032 break; 2033 case FLAG_TLS | FLAG_TRUST_ALL_CERTIFICATES: 2034 security = "+tls+trustallcerts"; 2035 break; 2036 default: 2037 security = ""; 2038 break; 2039 } 2040 String userInfo = null; 2041 if ((mFlags & FLAG_AUTHENTICATE) != 0) { 2042 String trimUser = (mLogin != null) ? mLogin.trim() : ""; 2043 String trimPassword = (mPassword != null) ? mPassword.trim() : ""; 2044 userInfo = trimUser + ":" + trimPassword; 2045 } 2046 String address = (mAddress != null) ? mAddress.trim() : null; 2047 String path = (mDomain != null) ? "/" + mDomain : null; 2048 2049 URI uri; 2050 try { 2051 uri = new URI( 2052 mProtocol + security, 2053 userInfo, 2054 address, 2055 mPort, 2056 path, 2057 null, 2058 null); 2059 return uri.toString(); 2060 } catch (URISyntaxException e) { 2061 return null; 2062 } 2063 } 2064 2065 /** 2066 * For compatibility while converting to provider model, set fields from a "store URI" 2067 * 2068 * @param uriString a String containing a Uri 2069 */ 2070 @Deprecated setStoreUri(String uriString)2071 public void setStoreUri(String uriString) { 2072 try { 2073 URI uri = new URI(uriString); 2074 mLogin = null; 2075 mPassword = null; 2076 mFlags &= ~FLAG_AUTHENTICATE; 2077 if (uri.getUserInfo() != null) { 2078 String[] userInfoParts = uri.getUserInfo().split(":", 2); 2079 mLogin = userInfoParts[0]; 2080 mFlags |= FLAG_AUTHENTICATE; 2081 if (userInfoParts.length > 1) { 2082 mPassword = userInfoParts[1]; 2083 } 2084 } 2085 2086 // Set protocol, security, and additional flags based on uri scheme 2087 String[] schemeParts = uri.getScheme().split("\\+"); 2088 mProtocol = (schemeParts.length >= 1) ? schemeParts[0] : null; 2089 mFlags &= ~(FLAG_SSL | FLAG_TLS | FLAG_TRUST_ALL_CERTIFICATES); 2090 boolean ssl = false; 2091 if (schemeParts.length >= 2) { 2092 String part1 = schemeParts[1]; 2093 if ("ssl".equals(part1)) { 2094 ssl = true; 2095 mFlags |= FLAG_SSL; 2096 } else if ("tls".equals(part1)) { 2097 mFlags |= FLAG_TLS; 2098 } 2099 if (schemeParts.length >= 3) { 2100 String part2 = schemeParts[2]; 2101 if ("trustallcerts".equals(part2)) { 2102 mFlags |= FLAG_TRUST_ALL_CERTIFICATES; 2103 } 2104 } 2105 } 2106 2107 mAddress = uri.getHost(); 2108 mPort = uri.getPort(); 2109 if (mPort == -1) { 2110 // infer port# from protocol + security 2111 // SSL implies a different port - TLS runs in the "regular" port 2112 // TODO: This really shouldn't be here - it should have been set up 2113 // in the account setup screens. 2114 if ("pop3".equals(mProtocol)) { 2115 mPort = ssl ? 995 : 110; 2116 } else if ("imap".equals(mProtocol)) { 2117 mPort = ssl ? 993 : 143; 2118 } else if ("eas".equals(mProtocol)) { 2119 mPort = ssl ? 443 : 80; 2120 } else if ("smtp".equals(mProtocol)) { 2121 mPort = ssl ? 465 : 587; 2122 } 2123 } 2124 2125 if (uri.getPath() != null && uri.getPath().length() > 0) { 2126 mDomain = uri.getPath().substring(1); 2127 } 2128 2129 2130 } catch (URISyntaxException use) { 2131 /* 2132 * We should always be able to parse our own settings. 2133 */ 2134 throw new Error(use); 2135 } 2136 2137 } 2138 2139 /** 2140 * Supports Parcelable 2141 */ describeContents()2142 public int describeContents() { 2143 return 0; 2144 } 2145 2146 /** 2147 * Supports Parcelable 2148 */ 2149 public static final Parcelable.Creator<EmailContent.HostAuth> CREATOR 2150 = new Parcelable.Creator<EmailContent.HostAuth>() { 2151 public EmailContent.HostAuth createFromParcel(Parcel in) { 2152 return new EmailContent.HostAuth(in); 2153 } 2154 2155 public EmailContent.HostAuth[] newArray(int size) { 2156 return new EmailContent.HostAuth[size]; 2157 } 2158 }; 2159 2160 /** 2161 * Supports Parcelable 2162 */ writeToParcel(Parcel dest, int flags)2163 public void writeToParcel(Parcel dest, int flags) { 2164 // mBaseUri is not parceled 2165 dest.writeLong(mId); 2166 dest.writeString(mProtocol); 2167 dest.writeString(mAddress); 2168 dest.writeInt(mPort); 2169 dest.writeInt(mFlags); 2170 dest.writeString(mLogin); 2171 dest.writeString(mPassword); 2172 dest.writeString(mDomain); 2173 dest.writeLong(mAccountKey); 2174 } 2175 2176 /** 2177 * Supports Parcelable 2178 */ HostAuth(Parcel in)2179 public HostAuth(Parcel in) { 2180 mBaseUri = CONTENT_URI; 2181 mId = in.readLong(); 2182 mProtocol = in.readString(); 2183 mAddress = in.readString(); 2184 mPort = in.readInt(); 2185 mFlags = in.readInt(); 2186 mLogin = in.readString(); 2187 mPassword = in.readString(); 2188 mDomain = in.readString(); 2189 mAccountKey = in.readLong(); 2190 } 2191 2192 /** 2193 * For debugger support only - DO NOT use for code. 2194 */ 2195 @Override toString()2196 public String toString() { 2197 return getStoreUri(); 2198 } 2199 } 2200 } 2201