More work on account migration
* Split account copy loop to do POP3 accounts first, then IMAP * After upgrading accounts, upgrade folders * Upgrade messages in those folders * Preserve attachments on outgoing messages (e.g. drafts) * Enable composer and start syncing after upgrade * Fix latent bug in LocalStore (which was not used in Eclair) * Add tests for upgrade workers in LegacyConversions Bug: 2065528
This commit is contained in:
parent
5b69eb3e4b
commit
fd249f61dd
|
@ -17,7 +17,9 @@
|
|||
package com.android.email;
|
||||
|
||||
import com.android.email.mail.Address;
|
||||
import com.android.email.mail.Body;
|
||||
import com.android.email.mail.Flag;
|
||||
import com.android.email.mail.Folder;
|
||||
import com.android.email.mail.Message;
|
||||
import com.android.email.mail.MessagingException;
|
||||
import com.android.email.mail.Part;
|
||||
|
@ -28,10 +30,12 @@ import com.android.email.mail.internet.MimeMessage;
|
|||
import com.android.email.mail.internet.MimeMultipart;
|
||||
import com.android.email.mail.internet.MimeUtility;
|
||||
import com.android.email.mail.internet.TextBody;
|
||||
import com.android.email.mail.store.LocalStore;
|
||||
import com.android.email.provider.AttachmentProvider;
|
||||
import com.android.email.provider.EmailContent;
|
||||
import com.android.email.provider.EmailContent.Attachment;
|
||||
import com.android.email.provider.EmailContent.AttachmentColumns;
|
||||
import com.android.email.provider.EmailContent.Mailbox;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
|
||||
|
@ -40,6 +44,7 @@ import android.content.ContentValues;
|
|||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.provider.OpenableColumns;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.File;
|
||||
|
@ -48,12 +53,17 @@ import java.io.IOException;
|
|||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
|
||||
public class LegacyConversions {
|
||||
|
||||
/** DO NOT CHECK IN "TRUE" */
|
||||
private static final boolean DEBUG_ATTACHMENTS = false;
|
||||
|
||||
/** Used for mapping folder names to type codes (e.g. inbox, drafts, trash) */
|
||||
private static final HashMap<String, Integer>
|
||||
sServerMailboxNames = new HashMap<String, Integer>();
|
||||
|
||||
/**
|
||||
* Values for HEADER_ANDROID_BODY_QUOTED_PART to tag body parts
|
||||
*/
|
||||
|
@ -61,6 +71,15 @@ public class LegacyConversions {
|
|||
/* package */ static final String BODY_QUOTED_PART_FORWARD = "quoted-forward";
|
||||
/* package */ static final String BODY_QUOTED_PART_INTRO = "quoted-intro";
|
||||
|
||||
/**
|
||||
* Standard columns for querying content providers
|
||||
*/
|
||||
private static final String[] ATTACHMENT_META_COLUMNS_PROJECTION = {
|
||||
OpenableColumns.DISPLAY_NAME,
|
||||
OpenableColumns.SIZE
|
||||
};
|
||||
private static final int ATTACHMENT_META_COLUMNS_SIZE = 1;
|
||||
|
||||
/**
|
||||
* Copy field-by-field from a "store" message to a "provider" message
|
||||
* @param message The message we've just downloaded (must be a MimeMessage)
|
||||
|
@ -246,13 +265,14 @@ public class LegacyConversions {
|
|||
* @param context a context for file operations
|
||||
* @param localMessage the attachments will be built against this message
|
||||
* @param attachments the attachments to add
|
||||
* @param upgrading if true, we are upgrading a local account - handle attachments differently
|
||||
* @throws IOException
|
||||
*/
|
||||
public static void updateAttachments(Context context, EmailContent.Message localMessage,
|
||||
ArrayList<Part> attachments) throws MessagingException, IOException {
|
||||
ArrayList<Part> attachments, boolean upgrading) throws MessagingException, IOException {
|
||||
localMessage.mAttachments = null;
|
||||
for (Part attachmentPart : attachments) {
|
||||
addOneAttachment(context, localMessage, attachmentPart);
|
||||
addOneAttachment(context, localMessage, attachmentPart, upgrading);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -272,10 +292,11 @@ public class LegacyConversions {
|
|||
* @param context a context for file operations
|
||||
* @param localMessage the attachments will be built against this message
|
||||
* @param part a single attachment part from POP or IMAP
|
||||
* @param upgrading true if upgrading a local account - handle attachments differently
|
||||
* @throws IOException
|
||||
*/
|
||||
private static void addOneAttachment(Context context, EmailContent.Message localMessage,
|
||||
Part part) throws MessagingException, IOException {
|
||||
Part part, boolean upgrading) throws MessagingException, IOException {
|
||||
|
||||
Attachment localAttachment = new Attachment();
|
||||
|
||||
|
@ -287,13 +308,53 @@ public class LegacyConversions {
|
|||
name = MimeUtility.getHeaderParameter(contentDisposition, "filename");
|
||||
}
|
||||
|
||||
// Try to pull size from disposition (if not downloaded)
|
||||
// Select the URI for the new attachments. For attachments downloaded by the legacy
|
||||
// IMAP/POP code, this is not determined yet, so is null (it will be rewritten below,
|
||||
// or later, when the actual attachment file is created.)
|
||||
//
|
||||
// When upgrading older local accounts, the URI represents a local asset (e.g. a photo)
|
||||
// so we need to preserve the URI.
|
||||
// TODO This works for outgoing messages, where the URI does not change. May need
|
||||
// additional logic to handle the case of rewriting URI for received attachments.
|
||||
Uri contentUri = null;
|
||||
String contentUriString = null;
|
||||
if (upgrading) {
|
||||
Body body = part.getBody();
|
||||
if (body instanceof LocalStore.LocalAttachmentBody) {
|
||||
LocalStore.LocalAttachmentBody localBody = (LocalStore.LocalAttachmentBody) body;
|
||||
contentUri = localBody.getContentUri();
|
||||
if (contentUri != null) {
|
||||
contentUriString = contentUri.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Find size, if available, via a number of techniques:
|
||||
long size = 0;
|
||||
String disposition = part.getDisposition();
|
||||
if (disposition != null) {
|
||||
String s = MimeUtility.getHeaderParameter(disposition, "size");
|
||||
if (s != null) {
|
||||
size = Long.parseLong(s);
|
||||
if (upgrading) {
|
||||
// If upgrading a legacy account, the size must be recaptured from the data source
|
||||
if (contentUri != null) {
|
||||
Cursor metadataCursor = context.getContentResolver().query(contentUri,
|
||||
ATTACHMENT_META_COLUMNS_PROJECTION, null, null, null);
|
||||
if (metadataCursor != null) {
|
||||
try {
|
||||
if (metadataCursor.moveToFirst()) {
|
||||
size = metadataCursor.getInt(ATTACHMENT_META_COLUMNS_SIZE);
|
||||
}
|
||||
} finally {
|
||||
metadataCursor.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
// TODO: a downloaded legacy attachment - see if the above code works
|
||||
} else {
|
||||
// Incoming attachment: Try to pull size from disposition (if not downloaded yet)
|
||||
String disposition = part.getDisposition();
|
||||
if (disposition != null) {
|
||||
String s = MimeUtility.getHeaderParameter(disposition, "size");
|
||||
if (s != null) {
|
||||
size = Long.parseLong(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -306,7 +367,7 @@ public class LegacyConversions {
|
|||
localAttachment.mMimeType = part.getMimeType();
|
||||
localAttachment.mSize = size; // May be reset below if file handled
|
||||
localAttachment.mContentId = part.getContentId();
|
||||
localAttachment.mContentUri = null; // Will be set when file is saved
|
||||
localAttachment.mContentUri = contentUriString;
|
||||
localAttachment.mMessageKey = localMessage.mId;
|
||||
localAttachment.mLocation = partId;
|
||||
localAttachment.mEncoding = "B"; // TODO - convert other known encodings
|
||||
|
@ -351,7 +412,9 @@ public class LegacyConversions {
|
|||
}
|
||||
|
||||
// If an attachment body was actually provided, we need to write the file now
|
||||
saveAttachmentBody(context, part, localAttachment, localMessage.mAccountKey);
|
||||
if (!upgrading) {
|
||||
saveAttachmentBody(context, part, localAttachment, localMessage.mAccountKey);
|
||||
}
|
||||
|
||||
if (localMessage.mAttachments == null) {
|
||||
localMessage.mAttachments = new ArrayList<Attachment>();
|
||||
|
@ -610,4 +673,73 @@ public class LegacyConversions {
|
|||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Conversion from legacy folder to provider mailbox. Used for account migration.
|
||||
* Note: Many mailbox fields are unused in IMAP & POP accounts.
|
||||
*
|
||||
* @param context application context
|
||||
* @param toAccount the provider account that this folder will be associated with
|
||||
* @param fromFolder the legacy folder to convert to modern format
|
||||
* @return an Account ready to be committed to provider
|
||||
*/
|
||||
public static EmailContent.Mailbox makeMailbox(Context context, EmailContent.Account toAccount,
|
||||
Folder fromFolder) throws MessagingException {
|
||||
EmailContent.Mailbox result = new EmailContent.Mailbox();
|
||||
|
||||
result.mDisplayName = fromFolder.getName();
|
||||
// result.mServerId
|
||||
// result.mParentServerId
|
||||
result.mAccountKey = toAccount.mId;
|
||||
result.mType = inferMailboxTypeFromName(context, fromFolder.getName());
|
||||
// result.mDelimiter
|
||||
// result.mSyncKey
|
||||
// result.mSyncLookback
|
||||
// result.mSyncInterval
|
||||
result.mSyncTime = 0;
|
||||
result.mUnreadCount = fromFolder.getUnreadMessageCount();
|
||||
result.mFlagVisible = true;
|
||||
result.mFlags = 0;
|
||||
result.mVisibleLimit = Email.VISIBLE_LIMIT_DEFAULT;
|
||||
// result.mSyncStatus
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Infer mailbox type from mailbox name. Used by MessagingController (for live folder sync)
|
||||
* and for legacy account upgrades.
|
||||
*/
|
||||
public static synchronized int inferMailboxTypeFromName(Context context, String mailboxName) {
|
||||
if (sServerMailboxNames.size() == 0) {
|
||||
// preload the hashmap, one time only
|
||||
sServerMailboxNames.put(
|
||||
context.getString(R.string.mailbox_name_server_inbox).toLowerCase(),
|
||||
Mailbox.TYPE_INBOX);
|
||||
sServerMailboxNames.put(
|
||||
context.getString(R.string.mailbox_name_server_outbox).toLowerCase(),
|
||||
Mailbox.TYPE_OUTBOX);
|
||||
sServerMailboxNames.put(
|
||||
context.getString(R.string.mailbox_name_server_drafts).toLowerCase(),
|
||||
Mailbox.TYPE_DRAFTS);
|
||||
sServerMailboxNames.put(
|
||||
context.getString(R.string.mailbox_name_server_trash).toLowerCase(),
|
||||
Mailbox.TYPE_TRASH);
|
||||
sServerMailboxNames.put(
|
||||
context.getString(R.string.mailbox_name_server_sent).toLowerCase(),
|
||||
Mailbox.TYPE_SENT);
|
||||
sServerMailboxNames.put(
|
||||
context.getString(R.string.mailbox_name_server_junk).toLowerCase(),
|
||||
Mailbox.TYPE_JUNK);
|
||||
}
|
||||
if (mailboxName == null || mailboxName.length() == 0) {
|
||||
return EmailContent.Mailbox.TYPE_MAIL;
|
||||
}
|
||||
String lowerCaseName = mailboxName.toLowerCase();
|
||||
Integer type = sServerMailboxNames.get(lowerCaseName);
|
||||
if (type != null) {
|
||||
return type;
|
||||
}
|
||||
return EmailContent.Mailbox.TYPE_MAIL;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -116,7 +116,6 @@ public class MessagingController implements Runnable {
|
|||
private static MessagingController inst = null;
|
||||
private BlockingQueue<Command> mCommands = new LinkedBlockingQueue<Command>();
|
||||
private Thread mThread;
|
||||
private final HashMap<String, Integer> mServerMailboxNames = new HashMap<String, Integer>();
|
||||
|
||||
/**
|
||||
* All access to mListeners *must* be synchronized
|
||||
|
@ -128,26 +127,6 @@ public class MessagingController implements Runnable {
|
|||
protected MessagingController(Context _context) {
|
||||
mContext = _context;
|
||||
|
||||
// Create lookup table for server-side mailbox names
|
||||
mServerMailboxNames.put(
|
||||
mContext.getString(R.string.mailbox_name_server_inbox).toLowerCase(),
|
||||
Mailbox.TYPE_INBOX);
|
||||
mServerMailboxNames.put(
|
||||
mContext.getString(R.string.mailbox_name_server_outbox).toLowerCase(),
|
||||
Mailbox.TYPE_OUTBOX);
|
||||
mServerMailboxNames.put(
|
||||
mContext.getString(R.string.mailbox_name_server_drafts).toLowerCase(),
|
||||
Mailbox.TYPE_DRAFTS);
|
||||
mServerMailboxNames.put(
|
||||
mContext.getString(R.string.mailbox_name_server_trash).toLowerCase(),
|
||||
Mailbox.TYPE_TRASH);
|
||||
mServerMailboxNames.put(
|
||||
mContext.getString(R.string.mailbox_name_server_sent).toLowerCase(),
|
||||
Mailbox.TYPE_SENT);
|
||||
mServerMailboxNames.put(
|
||||
mContext.getString(R.string.mailbox_name_server_junk).toLowerCase(),
|
||||
Mailbox.TYPE_JUNK);
|
||||
|
||||
mThread = new Thread(this);
|
||||
mThread.start();
|
||||
}
|
||||
|
@ -341,7 +320,8 @@ public class MessagingController implements Runnable {
|
|||
// box.mServerId;
|
||||
// box.mParentServerId;
|
||||
box.mAccountKey = account.mId;
|
||||
box.mType = inferMailboxTypeFromName(account, remoteNameToAdd);
|
||||
box.mType = LegacyConversions.inferMailboxTypeFromName(
|
||||
mContext, remoteNameToAdd);
|
||||
// box.mDelimiter;
|
||||
// box.mSyncKey;
|
||||
// box.mSyncLookback;
|
||||
|
@ -366,23 +346,6 @@ public class MessagingController implements Runnable {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Temporarily: Infer mailbox type from mailbox name. This should probably be
|
||||
* mutated into something that the stores can provide directly, instead of the two-step
|
||||
* where we scan and report.
|
||||
*/
|
||||
public int inferMailboxTypeFromName(EmailContent.Account account, String mailboxName) {
|
||||
if (mailboxName == null || mailboxName.length() == 0) {
|
||||
return EmailContent.Mailbox.TYPE_MAIL;
|
||||
}
|
||||
String lowerCaseName = mailboxName.toLowerCase();
|
||||
Integer type = mServerMailboxNames.get(lowerCaseName);
|
||||
if (type != null) {
|
||||
return type;
|
||||
}
|
||||
return EmailContent.Mailbox.TYPE_MAIL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start background synchronization of the specified folder.
|
||||
* @param account
|
||||
|
@ -1025,7 +988,7 @@ public class MessagingController implements Runnable {
|
|||
|
||||
// process (and save) attachments
|
||||
LegacyConversions.updateAttachments(mContext, localMessage,
|
||||
attachments);
|
||||
attachments, false);
|
||||
|
||||
// One last update of message with two updated flags
|
||||
localMessage.mFlagLoaded = loadStatus;
|
||||
|
|
|
@ -23,13 +23,18 @@ import com.android.email.Preferences;
|
|||
import com.android.email.R;
|
||||
import com.android.email.activity.setup.AccountSettingsUtils;
|
||||
import com.android.email.activity.setup.AccountSettingsUtils.Provider;
|
||||
import com.android.email.mail.FetchProfile;
|
||||
import com.android.email.mail.Folder;
|
||||
import com.android.email.mail.Message;
|
||||
import com.android.email.mail.MessagingException;
|
||||
import com.android.email.mail.Part;
|
||||
import com.android.email.mail.Store;
|
||||
import com.android.email.mail.internet.MimeUtility;
|
||||
import com.android.email.mail.store.LocalStore;
|
||||
import com.android.email.provider.EmailContent;
|
||||
import com.android.email.provider.EmailContent.AccountColumns;
|
||||
import com.android.email.provider.EmailContent.HostAuth;
|
||||
import com.android.email.provider.EmailContent.Mailbox;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.ListActivity;
|
||||
|
@ -49,8 +54,11 @@ import android.widget.ListView;
|
|||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
|
||||
/**
|
||||
* This activity will be used whenever we have a large/slow bulk upgrade operation.
|
||||
|
@ -62,10 +70,11 @@ import java.net.URISyntaxException;
|
|||
* This allows it to continue through without restarting.
|
||||
* Do not attempt to define orientation-specific resources, they won't be loaded.
|
||||
*
|
||||
* TODO: More work on actual conversions
|
||||
* TODO: Confirm from donut sources the right way to ID the drafts, outbox, sent folders in IMAP
|
||||
* TODO: Finish actual conversions
|
||||
* TODO: POP3 inbox needs to handle local-delete sentinels
|
||||
* TODO: Read pending events and convert them to things like updates or deletes in the DB
|
||||
* TODO: Smarter cleanup of SSL/TLS situation, since certificates may be bad (see design spec)
|
||||
* TODO: Trigger refresh after upgrade
|
||||
* TODO: Close db (add to LocalStore) since we're getting so many DB warnings here
|
||||
*/
|
||||
public class UpgradeAccounts extends ListActivity implements OnClickListener {
|
||||
|
||||
|
@ -335,22 +344,46 @@ public class UpgradeAccounts extends ListActivity implements OnClickListener {
|
|||
}
|
||||
}
|
||||
|
||||
// Step 3: Copy accounts (and delete old accounts)
|
||||
// Step 3: Copy accounts (and delete old accounts). POP accounts first.
|
||||
for (int i = 0; i < mAccountInfo.length; i++) {
|
||||
if (mAccountInfo[i].error == null) {
|
||||
copyAccount(mContext, mAccountInfo[i].account, i, handler);
|
||||
}
|
||||
deleteAccountStore(mContext, mAccountInfo[i].account, handler);
|
||||
mAccountInfo[i].account.delete(mPreferences);
|
||||
|
||||
// reset the progress indicator to mark account "complete" (in case est was wrong)
|
||||
UpgradeAccounts.this.mHandler.setMaxProgress(i, 100);
|
||||
UpgradeAccounts.this.mHandler.setProgress(i, 100);
|
||||
AccountInfo info = mAccountInfo[i];
|
||||
copyAndDeleteAccount(info, i, handler, Store.STORE_SCHEME_POP3);
|
||||
}
|
||||
// IMAP accounts next.
|
||||
for (int i = 0; i < mAccountInfo.length; i++) {
|
||||
AccountInfo info = mAccountInfo[i];
|
||||
copyAndDeleteAccount(info, i, handler, Store.STORE_SCHEME_IMAP);
|
||||
}
|
||||
|
||||
// Step 4: Enable app-wide features such as composer, and start mail service(s)
|
||||
Email.setServicesEnabled(mContext);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy and delete one account (helper for doInBackground). Can select accounts by type
|
||||
* to force conversion of one or another type only.
|
||||
*/
|
||||
private void copyAndDeleteAccount(AccountInfo info, int i, UIHandler handler, String type) {
|
||||
if (type != null) {
|
||||
String storeUri = info.account.getStoreUri();
|
||||
boolean isType = storeUri.startsWith(type);
|
||||
if (!isType) {
|
||||
return; // skip this account
|
||||
}
|
||||
}
|
||||
if (info.error == null) {
|
||||
copyAccount(mContext, info.account, i, handler);
|
||||
}
|
||||
deleteAccountStore(mContext, info.account, handler);
|
||||
info.account.delete(mPreferences);
|
||||
|
||||
// reset the progress indicator to mark account "complete" (in case est was wrong)
|
||||
handler.setMaxProgress(i, 100);
|
||||
handler.setProgress(i, 100);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Void result) {
|
||||
if (!isCancelled()) {
|
||||
|
@ -377,6 +410,7 @@ public class UpgradeAccounts extends ListActivity implements OnClickListener {
|
|||
Folder folder = folders[i];
|
||||
folder.open(Folder.OpenMode.READ_ONLY, null);
|
||||
estimate += folder.getMessageCount();
|
||||
folder.close(false);
|
||||
}
|
||||
estimate += ((LocalStore)store).getStoredAttachmentCount();
|
||||
|
||||
|
@ -419,6 +453,7 @@ public class UpgradeAccounts extends ListActivity implements OnClickListener {
|
|||
handler.incProgress(accountNum, 1 + messageCount);
|
||||
}
|
||||
}
|
||||
folder.close(false);
|
||||
}
|
||||
int pruned = ((LocalStore)store).pruneCachedAttachments();
|
||||
if (handler != null) {
|
||||
|
@ -428,7 +463,17 @@ public class UpgradeAccounts extends ListActivity implements OnClickListener {
|
|||
Log.d(Email.LOG_TAG, "Exception while cleaning IMAP account " + e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class FolderConversion {
|
||||
final Folder folder;
|
||||
final EmailContent.Mailbox mailbox;
|
||||
|
||||
public FolderConversion(Folder _folder, EmailContent.Mailbox _mailbox) {
|
||||
folder = _folder;
|
||||
mailbox = _mailbox;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy an account.
|
||||
*/
|
||||
|
@ -452,9 +497,147 @@ public class UpgradeAccounts extends ListActivity implements OnClickListener {
|
|||
handler.incProgress(accountNum);
|
||||
}
|
||||
|
||||
// TODO folders
|
||||
// TODO messages
|
||||
// TODO attachments
|
||||
// copy the folders, making a set of them as we go, and recording a few that we
|
||||
// need to process first (highest priority for saving the messages)
|
||||
HashSet<FolderConversion> conversions = new HashSet<FolderConversion>();
|
||||
FolderConversion drafts = null;
|
||||
FolderConversion outbox = null;
|
||||
FolderConversion sent = null;
|
||||
try {
|
||||
Store store = LocalStore.newInstance(account.getLocalStoreUri(), context, null);
|
||||
Folder[] folders = store.getPersonalNamespaces();
|
||||
for (Folder folder : folders) {
|
||||
String folderName = null;
|
||||
try {
|
||||
folder.open(Folder.OpenMode.READ_ONLY, null);
|
||||
folderName = folder.getName();
|
||||
Log.d(Email.LOG_TAG, "Copy " + account.getDescription() + "." + folderName);
|
||||
EmailContent.Mailbox mailbox =
|
||||
LegacyConversions.makeMailbox(context, newAccount, folder);
|
||||
mailbox.save(context);
|
||||
if (handler != null) {
|
||||
handler.incProgress(accountNum);
|
||||
}
|
||||
folder.close(false);
|
||||
// Now record the conversion, to come back and do the messages
|
||||
FolderConversion conversion = new FolderConversion(folder, mailbox);
|
||||
conversions.add(conversion);
|
||||
switch (mailbox.mType) {
|
||||
case Mailbox.TYPE_DRAFTS:
|
||||
drafts = conversion;
|
||||
break;
|
||||
case Mailbox.TYPE_OUTBOX:
|
||||
outbox = conversion;
|
||||
break;
|
||||
case Mailbox.TYPE_SENT:
|
||||
sent = conversion;
|
||||
break;
|
||||
}
|
||||
} catch (MessagingException e) {
|
||||
// We make a best-effort attempt at each folder, so even if this one fails,
|
||||
// we'll try to keep going.
|
||||
Log.d(Email.LOG_TAG, "Exception copying folder " + folderName + ": " + e);
|
||||
if (handler != null) {
|
||||
handler.error(context.getString(R.string.upgrade_accounts_error));
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (MessagingException e) {
|
||||
Log.d(Email.LOG_TAG, "Exception while copying folders " + e);
|
||||
// Couldn't copy folders at all
|
||||
if (handler != null) {
|
||||
handler.error(context.getString(R.string.upgrade_accounts_error));
|
||||
}
|
||||
}
|
||||
|
||||
// copy the messages, starting with the most critical folders, and then doing the rest
|
||||
// outbox & drafts are the most important, as they don't exist anywhere else
|
||||
if (outbox != null) {
|
||||
copyMessages(context, outbox, true, newAccount.mId, accountNum, handler);
|
||||
conversions.remove(outbox);
|
||||
}
|
||||
if (drafts != null) {
|
||||
copyMessages(context, drafts, true, newAccount.mId, accountNum, handler);
|
||||
conversions.remove(drafts);
|
||||
}
|
||||
if (sent != null) {
|
||||
copyMessages(context, sent, true, newAccount.mId, accountNum, handler);
|
||||
conversions.remove(outbox);
|
||||
}
|
||||
// Now handle any remaining folders
|
||||
for (FolderConversion conversion : conversions) {
|
||||
copyMessages(context, conversion, false, newAccount.mId, accountNum, handler);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy all messages in a given folder
|
||||
*
|
||||
* @param context a system context
|
||||
* @param conversion a folder->mailbox conversion record
|
||||
* @param localAttachments true if the attachments refer to local data (to be sent)
|
||||
* @param newAccountId the id of the newly-created account
|
||||
* @param accountNum the UI list # of the account
|
||||
* @param handler the handler for updating the UI
|
||||
*/
|
||||
/* package */ static void copyMessages(Context context, FolderConversion conversion,
|
||||
boolean localAttachments, long newAccountId, int accountNum, UIHandler handler) {
|
||||
try {
|
||||
conversion.folder.open(Folder.OpenMode.READ_ONLY, null);
|
||||
Message[] oldMessages = conversion.folder.getMessages(null);
|
||||
for (Message oldMessage : oldMessages) {
|
||||
Exception e = null;
|
||||
try {
|
||||
// load message data from legacy Store
|
||||
FetchProfile fp = new FetchProfile();
|
||||
fp.add(FetchProfile.Item.ENVELOPE);
|
||||
fp.add(FetchProfile.Item.BODY);
|
||||
conversion.folder.fetch(new Message[] { oldMessage }, fp, null);
|
||||
// convert message (headers)
|
||||
EmailContent.Message newMessage = new EmailContent.Message();
|
||||
LegacyConversions.updateMessageFields(newMessage, oldMessage, newAccountId,
|
||||
conversion.mailbox.mId);
|
||||
// convert body (text)
|
||||
EmailContent.Body newBody = new EmailContent.Body();
|
||||
ArrayList<Part> viewables = new ArrayList<Part>();
|
||||
ArrayList<Part> attachments = new ArrayList<Part>();
|
||||
MimeUtility.collectParts(oldMessage, viewables, attachments);
|
||||
LegacyConversions.updateBodyFields(newBody, newMessage, viewables);
|
||||
// commit changes so far so we have real id's
|
||||
newMessage.save(context);
|
||||
newBody.save(context);
|
||||
// convert attachments
|
||||
if (localAttachments) {
|
||||
// These are references to local data, and should create records only
|
||||
// (e.g. the content URI). No files should be created.
|
||||
LegacyConversions.updateAttachments(context, newMessage, attachments, true);
|
||||
} else {
|
||||
// TODO handle downloaded attachments
|
||||
}
|
||||
// done
|
||||
if (handler != null) {
|
||||
handler.incProgress(accountNum);
|
||||
}
|
||||
} catch (MessagingException me) {
|
||||
e = me;
|
||||
} catch (IOException ioe) {
|
||||
e = ioe;
|
||||
}
|
||||
if (e != null) {
|
||||
Log.d(Email.LOG_TAG, "Exception copying message " + oldMessage.getSubject()
|
||||
+ ": "+ e);
|
||||
if (handler != null) {
|
||||
handler.error(context.getString(R.string.upgrade_accounts_error));
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (MessagingException e) {
|
||||
// Couldn't copy messages at all
|
||||
Log.d(Email.LOG_TAG, "Exception while copying messages " + e);
|
||||
if (handler != null) {
|
||||
handler.error(context.getString(R.string.upgrade_accounts_error));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -25,6 +25,8 @@ import android.text.TextUtils;
|
|||
import android.text.util.Rfc822Token;
|
||||
import android.text.util.Rfc822Tokenizer;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLEncoder;
|
||||
import java.util.ArrayList;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
|
@ -439,7 +441,7 @@ public class Address {
|
|||
* as found in LocalStore (Donut; db version up to 24).
|
||||
* @See unpack()
|
||||
*/
|
||||
/* package */ static Address[] legacyUnpack(String addressList) {
|
||||
public static Address[] legacyUnpack(String addressList) {
|
||||
if (addressList == null || addressList.length() == 0) {
|
||||
return new Address[] { };
|
||||
}
|
||||
|
@ -471,4 +473,35 @@ public class Address {
|
|||
}
|
||||
return addresses.toArray(new Address[] { });
|
||||
}
|
||||
|
||||
/**
|
||||
* Legacy pack() used for writing to old data (migration),
|
||||
* as found in LocalStore (Donut; db version up to 24).
|
||||
* @See unpack()
|
||||
*/
|
||||
public static String legacyPack(Address[] addresses) {
|
||||
if (addresses == null) {
|
||||
return null;
|
||||
} else if (addresses.length == 0) {
|
||||
return "";
|
||||
}
|
||||
StringBuffer sb = new StringBuffer();
|
||||
for (int i = 0, count = addresses.length; i < count; i++) {
|
||||
Address address = addresses[i];
|
||||
try {
|
||||
sb.append(URLEncoder.encode(address.getAddress(), "UTF-8"));
|
||||
if (address.getPersonal() != null) {
|
||||
sb.append(';');
|
||||
sb.append(URLEncoder.encode(address.getPersonal(), "UTF-8"));
|
||||
}
|
||||
if (i < count - 1) {
|
||||
sb.append(',');
|
||||
}
|
||||
}
|
||||
catch (UnsupportedEncodingException uee) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -921,7 +921,7 @@ public class LocalStore extends Store implements PersistentDataCallbacks {
|
|||
private void populateMessageFromGetMessageCursor(LocalMessage message, Cursor cursor)
|
||||
throws MessagingException{
|
||||
message.setSubject(cursor.getString(0) == null ? "" : cursor.getString(0));
|
||||
Address[] from = Address.unpack(cursor.getString(1));
|
||||
Address[] from = Address.legacyUnpack(cursor.getString(1));
|
||||
if (from.length > 0) {
|
||||
message.setFrom(from[0]);
|
||||
}
|
||||
|
@ -938,10 +938,10 @@ public class LocalStore extends Store implements PersistentDataCallbacks {
|
|||
}
|
||||
}
|
||||
message.mId = cursor.getLong(5);
|
||||
message.setRecipients(RecipientType.TO, Address.unpack(cursor.getString(6)));
|
||||
message.setRecipients(RecipientType.CC, Address.unpack(cursor.getString(7)));
|
||||
message.setRecipients(RecipientType.BCC, Address.unpack(cursor.getString(8)));
|
||||
message.setReplyTo(Address.unpack(cursor.getString(9)));
|
||||
message.setRecipients(RecipientType.TO, Address.legacyUnpack(cursor.getString(6)));
|
||||
message.setRecipients(RecipientType.CC, Address.legacyUnpack(cursor.getString(7)));
|
||||
message.setRecipients(RecipientType.BCC, Address.legacyUnpack(cursor.getString(8)));
|
||||
message.setReplyTo(Address.legacyUnpack(cursor.getString(9)));
|
||||
message.mAttachmentCount = cursor.getInt(10);
|
||||
message.setInternalDate(new Date(cursor.getLong(11)));
|
||||
message.setMessageId(cursor.getString(12));
|
||||
|
@ -1190,17 +1190,18 @@ public class LocalStore extends Store implements PersistentDataCallbacks {
|
|||
ContentValues cv = new ContentValues();
|
||||
cv.put("uid", message.getUid());
|
||||
cv.put("subject", message.getSubject());
|
||||
cv.put("sender_list", Address.pack(message.getFrom()));
|
||||
cv.put("sender_list", Address.legacyPack(message.getFrom()));
|
||||
cv.put("date", message.getSentDate() == null
|
||||
? System.currentTimeMillis() : message.getSentDate().getTime());
|
||||
cv.put("flags", makeFlagsString(message));
|
||||
cv.put("folder_id", mFolderId);
|
||||
cv.put("to_list", Address.pack(message.getRecipients(RecipientType.TO)));
|
||||
cv.put("cc_list", Address.pack(message.getRecipients(RecipientType.CC)));
|
||||
cv.put("bcc_list", Address.pack(message.getRecipients(RecipientType.BCC)));
|
||||
cv.put("to_list", Address.legacyPack(message.getRecipients(RecipientType.TO)));
|
||||
cv.put("cc_list", Address.legacyPack(message.getRecipients(RecipientType.CC)));
|
||||
cv.put("bcc_list", Address.legacyPack(
|
||||
message.getRecipients(RecipientType.BCC)));
|
||||
cv.put("html_content", sbHtml.length() > 0 ? sbHtml.toString() : null);
|
||||
cv.put("text_content", sbText.length() > 0 ? sbText.toString() : null);
|
||||
cv.put("reply_to_list", Address.pack(message.getReplyTo()));
|
||||
cv.put("reply_to_list", Address.legacyPack(message.getReplyTo()));
|
||||
cv.put("attachment_count", attachments.size());
|
||||
cv.put("internal_date", message.getInternalDate() == null
|
||||
? System.currentTimeMillis() : message.getInternalDate().getTime());
|
||||
|
@ -1272,21 +1273,21 @@ public class LocalStore extends Store implements PersistentDataCallbacks {
|
|||
new Object[] {
|
||||
message.getUid(),
|
||||
message.getSubject(),
|
||||
Address.pack(message.getFrom()),
|
||||
Address.legacyPack(message.getFrom()),
|
||||
message.getSentDate() == null ? System
|
||||
.currentTimeMillis() : message.getSentDate()
|
||||
.getTime(),
|
||||
makeFlagsString(message),
|
||||
mFolderId,
|
||||
Address.pack(message
|
||||
Address.legacyPack(message
|
||||
.getRecipients(RecipientType.TO)),
|
||||
Address.pack(message
|
||||
Address.legacyPack(message
|
||||
.getRecipients(RecipientType.CC)),
|
||||
Address.pack(message
|
||||
Address.legacyPack(message
|
||||
.getRecipients(RecipientType.BCC)),
|
||||
sbHtml.length() > 0 ? sbHtml.toString() : null,
|
||||
sbText.length() > 0 ? sbText.toString() : null,
|
||||
Address.pack(message.getReplyTo()),
|
||||
Address.legacyPack(message.getReplyTo()),
|
||||
attachments.size(),
|
||||
message.getMessageId(),
|
||||
makeFlagNumeric(message, Flag.X_STORE_1),
|
||||
|
|
|
@ -16,14 +16,16 @@
|
|||
|
||||
package com.android.email;
|
||||
|
||||
import com.android.email.Account;
|
||||
import com.android.email.mail.Address;
|
||||
import com.android.email.mail.Body;
|
||||
import com.android.email.mail.BodyPart;
|
||||
import com.android.email.mail.Flag;
|
||||
import com.android.email.mail.Folder;
|
||||
import com.android.email.mail.Message;
|
||||
import com.android.email.mail.MessageTestUtils;
|
||||
import com.android.email.mail.MessagingException;
|
||||
import com.android.email.mail.Part;
|
||||
import com.android.email.mail.Folder.OpenMode;
|
||||
import com.android.email.mail.Message.RecipientType;
|
||||
import com.android.email.mail.MessageTestUtils.MessageBuilder;
|
||||
import com.android.email.mail.MessageTestUtils.MultipartBuilder;
|
||||
|
@ -32,10 +34,13 @@ import com.android.email.mail.internet.MimeHeader;
|
|||
import com.android.email.mail.internet.MimeMessage;
|
||||
import com.android.email.mail.internet.MimeUtility;
|
||||
import com.android.email.mail.internet.TextBody;
|
||||
import com.android.email.mail.store.LocalStore;
|
||||
import com.android.email.mail.store.LocalStoreUnitTests;
|
||||
import com.android.email.provider.EmailContent;
|
||||
import com.android.email.provider.EmailProvider;
|
||||
import com.android.email.provider.ProviderTestUtils;
|
||||
import com.android.email.provider.EmailContent.Attachment;
|
||||
import com.android.email.provider.EmailContent.Mailbox;
|
||||
|
||||
import android.content.ContentUris;
|
||||
import android.content.Context;
|
||||
|
@ -208,13 +213,13 @@ public class LegacyConversionsTests extends ProviderTestCase2<EmailProvider> {
|
|||
"local-message", accountId, mailboxId, false, true, mProviderContext);
|
||||
|
||||
// Prepare a legacy message with attachments
|
||||
final Message legacyMessage = prepareLegacyMessageWithAttachments(2);
|
||||
final Message legacyMessage = prepareLegacyMessageWithAttachments(2, false);
|
||||
|
||||
// Now, convert from legacy to provider and see what happens
|
||||
ArrayList<Part> viewables = new ArrayList<Part>();
|
||||
ArrayList<Part> attachments = new ArrayList<Part>();
|
||||
MimeUtility.collectParts(legacyMessage, viewables, attachments);
|
||||
LegacyConversions.updateAttachments(mProviderContext, localMessage, attachments);
|
||||
LegacyConversions.updateAttachments(mProviderContext, localMessage, attachments, false);
|
||||
|
||||
// Read back all attachments for message and check field values
|
||||
Uri uri = ContentUris.withAppendedId(Attachment.MESSAGE_ID_URI, localMessage.mId);
|
||||
|
@ -249,50 +254,115 @@ public class LegacyConversionsTests extends ProviderTestCase2<EmailProvider> {
|
|||
"local-message", accountId, mailboxId, false, true, mProviderContext);
|
||||
|
||||
// Prepare a legacy message with attachments
|
||||
Message legacyMessage = prepareLegacyMessageWithAttachments(2);
|
||||
Message legacyMessage = prepareLegacyMessageWithAttachments(2, false);
|
||||
|
||||
// Now, convert from legacy to provider and see what happens
|
||||
ArrayList<Part> viewables = new ArrayList<Part>();
|
||||
ArrayList<Part> attachments = new ArrayList<Part>();
|
||||
MimeUtility.collectParts(legacyMessage, viewables, attachments);
|
||||
LegacyConversions.updateAttachments(mProviderContext, localMessage, attachments);
|
||||
LegacyConversions.updateAttachments(mProviderContext, localMessage, attachments, false);
|
||||
|
||||
// Confirm two attachment objects created
|
||||
Uri uri = ContentUris.withAppendedId(Attachment.MESSAGE_ID_URI, localMessage.mId);
|
||||
assertEquals(2, EmailContent.count(mProviderContext, uri, null, null));
|
||||
|
||||
// Now add the attachments again and confirm there are still only two
|
||||
LegacyConversions.updateAttachments(mProviderContext, localMessage, attachments);
|
||||
LegacyConversions.updateAttachments(mProviderContext, localMessage, attachments, false);
|
||||
assertEquals(2, EmailContent.count(mProviderContext, uri, null, null));
|
||||
|
||||
// Now add a 3rd & 4th attachment and make sure the total is 4, not 2 or 6
|
||||
legacyMessage = prepareLegacyMessageWithAttachments(4);
|
||||
legacyMessage = prepareLegacyMessageWithAttachments(4, false);
|
||||
viewables = new ArrayList<Part>();
|
||||
attachments = new ArrayList<Part>();
|
||||
MimeUtility.collectParts(legacyMessage, viewables, attachments);
|
||||
LegacyConversions.updateAttachments(mProviderContext, localMessage, attachments);
|
||||
LegacyConversions.updateAttachments(mProviderContext, localMessage, attachments, false);
|
||||
assertEquals(4, EmailContent.count(mProviderContext, uri, null, null));
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare a legacy message with 1+ attachments
|
||||
* Sunny day test of adding attachments in "local account upgrade" mode
|
||||
*/
|
||||
private static Message prepareLegacyMessageWithAttachments(int numAttachments)
|
||||
public void testLocalUpgradeAttachments() throws MessagingException, IOException {
|
||||
// Prepare a local message to add the attachments to
|
||||
final long accountId = 1;
|
||||
final long mailboxId = 1;
|
||||
final EmailContent.Message localMessage = ProviderTestUtils.setupMessage(
|
||||
"local-upgrade", accountId, mailboxId, false, true, mProviderContext);
|
||||
|
||||
// Prepare a legacy message with attachments
|
||||
final Message legacyMessage = prepareLegacyMessageWithAttachments(2, true);
|
||||
|
||||
// Now, convert from legacy to provider and see what happens
|
||||
ArrayList<Part> viewables = new ArrayList<Part>();
|
||||
ArrayList<Part> attachments = new ArrayList<Part>();
|
||||
MimeUtility.collectParts(legacyMessage, viewables, attachments);
|
||||
LegacyConversions.updateAttachments(mProviderContext, localMessage, attachments, true);
|
||||
|
||||
// Read back all attachments for message and check field values
|
||||
Uri uri = ContentUris.withAppendedId(Attachment.MESSAGE_ID_URI, localMessage.mId);
|
||||
Cursor c = mProviderContext.getContentResolver().query(uri, Attachment.CONTENT_PROJECTION,
|
||||
null, null, null);
|
||||
try {
|
||||
assertEquals(2, c.getCount());
|
||||
while (c.moveToNext()) {
|
||||
Attachment attachment = Attachment.getContent(c, Attachment.class);
|
||||
// This attachment should look as if created by modern (provider) MessageCompose.
|
||||
// 1. find the original that it was created from
|
||||
Part fromPart = null;
|
||||
for (Part from : attachments) {
|
||||
String contentType = MimeUtility.unfoldAndDecode(from.getContentType());
|
||||
String name = MimeUtility.getHeaderParameter(contentType, "name");
|
||||
if (name.equals(attachment.mFileName)) {
|
||||
fromPart = from;
|
||||
break;
|
||||
}
|
||||
}
|
||||
assertTrue(fromPart != null);
|
||||
// 2. Check values
|
||||
checkAttachment(attachment.mFileName, fromPart, attachment);
|
||||
}
|
||||
} finally {
|
||||
c.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare a legacy message with 1+ attachments
|
||||
* @param numAttachments how many attachments to add
|
||||
* @param localData if true, attachments are "local" data. false = "remote" (from server)
|
||||
*/
|
||||
private Message prepareLegacyMessageWithAttachments(int numAttachments, boolean localData)
|
||||
throws MessagingException {
|
||||
// First, build one or more attachment parts
|
||||
MultipartBuilder mpBuilder = new MultipartBuilder("multipart/mixed");
|
||||
for (int i = 1; i <= numAttachments; ++i) {
|
||||
BodyPart attachmentPart = MessageTestUtils.bodyPart("image/jpg", null);
|
||||
if (localData) {
|
||||
// generate an attachment that was generated by legacy code (e.g. donut)
|
||||
// for test of upgrading accounts in place
|
||||
// This creator models the code in legacy MessageCompose
|
||||
Uri uri = Uri.parse("content://test/attachment/" + i);
|
||||
String quotedName = "\"test-attachment-" + i + "\"";
|
||||
MimeBodyPart bp = new MimeBodyPart(
|
||||
new LocalStore.LocalAttachmentBody(uri, mProviderContext));
|
||||
bp.setHeader(MimeHeader.HEADER_CONTENT_TYPE, "image/jpg;\n name=" + quotedName);
|
||||
bp.setHeader(MimeHeader.HEADER_CONTENT_TRANSFER_ENCODING, "base64");
|
||||
bp.setHeader(MimeHeader.HEADER_CONTENT_DISPOSITION,
|
||||
"attachment;\n filename=" + quotedName);
|
||||
mpBuilder.addBodyPart(bp);
|
||||
} else {
|
||||
// generate an attachment that came from a server
|
||||
BodyPart attachmentPart = MessageTestUtils.bodyPart("image/jpg", null);
|
||||
|
||||
// name=attachmentN size=N00 location=10N
|
||||
attachmentPart.setHeader(MimeHeader.HEADER_CONTENT_TYPE,
|
||||
"image/jpg;\n name=\"attachment" + i + "\"");
|
||||
attachmentPart.setHeader(MimeHeader.HEADER_CONTENT_TRANSFER_ENCODING, "base64");
|
||||
attachmentPart.setHeader(MimeHeader.HEADER_CONTENT_DISPOSITION,
|
||||
"attachment;\n filename=\"attachment2\";\n size=" + i + "00");
|
||||
attachmentPart.setHeader(MimeHeader.HEADER_ANDROID_ATTACHMENT_STORE_DATA, "10" + i);
|
||||
// name=attachmentN size=N00 location=10N
|
||||
attachmentPart.setHeader(MimeHeader.HEADER_CONTENT_TYPE,
|
||||
"image/jpg;\n name=\"attachment" + i + "\"");
|
||||
attachmentPart.setHeader(MimeHeader.HEADER_CONTENT_TRANSFER_ENCODING, "base64");
|
||||
attachmentPart.setHeader(MimeHeader.HEADER_CONTENT_DISPOSITION,
|
||||
"attachment;\n filename=\"attachment2\";\n size=" + i + "00");
|
||||
attachmentPart.setHeader(MimeHeader.HEADER_ANDROID_ATTACHMENT_STORE_DATA, "10" + i);
|
||||
|
||||
mpBuilder.addBodyPart(attachmentPart);
|
||||
mpBuilder.addBodyPart(attachmentPart);
|
||||
}
|
||||
}
|
||||
|
||||
// Now build a message with them
|
||||
|
@ -338,13 +408,30 @@ public class LegacyConversionsTests extends ProviderTestCase2<EmailProvider> {
|
|||
assertEquals(tag, expected.getMimeType(), actual.mMimeType);
|
||||
String disposition = expected.getDisposition();
|
||||
String sizeString = MimeUtility.getHeaderParameter(disposition, "size");
|
||||
long expectedSize = Long.parseLong(sizeString);
|
||||
long expectedSize = (sizeString != null) ? Long.parseLong(sizeString) : 0;
|
||||
assertEquals(tag, expectedSize, actual.mSize);
|
||||
assertEquals(tag, expected.getContentId(), actual.mContentId);
|
||||
assertNull(tag, actual.mContentUri);
|
||||
|
||||
// content URI either both null or both matching
|
||||
String expectedUriString = null;
|
||||
Body body = expected.getBody();
|
||||
if (body instanceof LocalStore.LocalAttachmentBody) {
|
||||
LocalStore.LocalAttachmentBody localBody = (LocalStore.LocalAttachmentBody) body;
|
||||
Uri contentUri = localBody.getContentUri();
|
||||
if (contentUri != null) {
|
||||
expectedUriString = contentUri.toString();
|
||||
}
|
||||
}
|
||||
assertEquals(tag, expectedUriString, actual.mContentUri);
|
||||
|
||||
assertTrue(tag, 0 != actual.mMessageKey);
|
||||
String expectedPartId =
|
||||
expected.getHeader(MimeHeader.HEADER_ANDROID_ATTACHMENT_STORE_DATA)[0];
|
||||
|
||||
// location is either both null or both matching
|
||||
String expectedPartId = null;
|
||||
String[] storeData = expected.getHeader(MimeHeader.HEADER_ANDROID_ATTACHMENT_STORE_DATA);
|
||||
if (storeData != null && storeData.length > 0) {
|
||||
expectedPartId = storeData[0];
|
||||
}
|
||||
assertEquals(tag, expectedPartId, actual.mLocation);
|
||||
assertEquals(tag, "B", actual.mEncoding);
|
||||
}
|
||||
|
@ -618,4 +705,49 @@ public class LegacyConversionsTests extends ProviderTestCase2<EmailProvider> {
|
|||
assertEquals(tag + " security", expect.mSecurityFlags, actual.mSecurityFlags);
|
||||
assertEquals(tag + " signature", expect.mSignature, actual.mSignature);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test conversion of a legacy mailbox to a provider mailbox
|
||||
*/
|
||||
public void testMakeProviderMailbox() throws MessagingException {
|
||||
EmailContent.Account toAccount = ProviderTestUtils.setupAccount("convert-mailbox",
|
||||
true, mProviderContext);
|
||||
Folder fromFolder = buildTestFolder("INBOX");
|
||||
Mailbox toMailbox = LegacyConversions.makeMailbox(mProviderContext, toAccount, fromFolder);
|
||||
|
||||
// Now test fields in created mailbox
|
||||
assertEquals("INBOX", toMailbox.mDisplayName);
|
||||
assertNull(toMailbox.mServerId);
|
||||
assertNull(toMailbox.mParentServerId);
|
||||
assertEquals(toAccount.mId, toMailbox.mAccountKey);
|
||||
assertEquals(Mailbox.TYPE_INBOX, toMailbox.mType);
|
||||
assertEquals(0, toMailbox.mDelimiter);
|
||||
assertNull(toMailbox.mSyncKey);
|
||||
assertEquals(0, toMailbox.mSyncLookback);
|
||||
assertEquals(0, toMailbox.mSyncInterval);
|
||||
assertEquals(0, toMailbox.mSyncTime);
|
||||
assertEquals(100, toMailbox.mUnreadCount);
|
||||
assertTrue(toMailbox.mFlagVisible);
|
||||
assertEquals(0, toMailbox.mFlags);
|
||||
assertEquals(Email.VISIBLE_LIMIT_DEFAULT, toMailbox.mVisibleLimit);
|
||||
assertNull(toMailbox.mSyncStatus);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a lightweight Store Folder with simple field population. The folder is "open"
|
||||
* and should be closed by the caller.
|
||||
*/
|
||||
private Folder buildTestFolder(String folderName) throws MessagingException {
|
||||
String localStoreUri =
|
||||
"local://localhost/" + mProviderContext.getDatabasePath(LocalStoreUnitTests.DB_NAME);
|
||||
LocalStore store = (LocalStore) LocalStore.newInstance(localStoreUri, getContext(), null);
|
||||
LocalStore.LocalFolder folder = (LocalStore.LocalFolder) store.getFolder(folderName);
|
||||
folder.open(OpenMode.READ_WRITE, null); // this will create it
|
||||
|
||||
// set a few fields to test values
|
||||
// folder.getName - set by getFolder()
|
||||
folder.setUnreadMessageCount(100);
|
||||
|
||||
return folder;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -56,7 +56,7 @@ import java.util.HashSet;
|
|||
@MediumTest
|
||||
public class LocalStoreUnitTests extends AndroidTestCase {
|
||||
|
||||
private static final String dbName = "com.android.email.mail.store.LocalStoreUnitTests.db";
|
||||
public static final String DB_NAME = "com.android.email.mail.store.LocalStoreUnitTests.db";
|
||||
|
||||
private static final String SENDER = "sender@android.com";
|
||||
private static final String RECIPIENT_TO = "recipient-to@android.com";
|
||||
|
@ -85,7 +85,7 @@ public class LocalStoreUnitTests extends AndroidTestCase {
|
|||
|
||||
// These are needed so we can get at the inner classes
|
||||
// Create a dummy database (be sure to delete it in tearDown())
|
||||
mLocalStoreUri = "local://localhost/" + getContext().getDatabasePath(dbName);
|
||||
mLocalStoreUri = "local://localhost/" + getContext().getDatabasePath(DB_NAME);
|
||||
|
||||
mStore = (LocalStore) LocalStore.newInstance(mLocalStoreUri, getContext(), null);
|
||||
mFolder = (LocalStore.LocalFolder) mStore.getFolder(FOLDER_NAME);
|
||||
|
|
Loading…
Reference in New Issue