replicant-packages_apps_Email/src/com/android/email/activity/UpgradeAccounts.java

881 lines
37 KiB
Java

/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.email.activity;
import com.android.email.Account;
import com.android.email.Email;
import com.android.email.LegacyConversions;
import com.android.email.Preferences;
import com.android.email.R;
import com.android.email.Utility;
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.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;
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.ListActivity;
import android.content.Context;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.View.OnClickListener;
import android.widget.BaseAdapter;
import android.widget.Button;
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;
/**
* *** Account upgrade has been disabled for the tablet release ***
*
* TODO Remove it and related code, when we no longer have to support upgrade from donut.
*
* This activity will be used whenever we have a large/slow bulk upgrade operation.
*
* The general strategy is to iterate through the legacy accounts, convert them one-by-one, and
* then delete them. The loop is very conservative; If there is any problem, the bias will be
* to abandon the conversion and let the account be deleted. We never want to get stuck here, and
* we never want to run more than once (on a device being upgraded from 1.6). After this code
* runs, there should be zero legacy accounts.
*
* Note: It's preferable to check for "accounts needing upgrade" before launching this
* activity, so as to not waste time before every launch.
*
* Note: This activity is set (in the manifest) to disregard configuration changes (e.g. rotation).
* This allows it to continue through without restarting.
* Do not attempt to define orientation-specific resources, they won't be loaded.
*
* 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)
*/
public class UpgradeAccounts extends ListActivity implements OnClickListener {
/** DO NOT CHECK IN AS 'TRUE' - DEVELOPMENT ONLY */
private static final boolean DEBUG_FORCE_UPGRADES = false;
private AccountInfo[] mLegacyAccounts;
private UIHandler mHandler = new UIHandler();
private AccountsAdapter mAdapter;
private ListView mListView;
private Button mProceedButton;
private ConversionTask mConversionTask;
// These are used to hold off restart of this activity while worker is still busy
private static final Object sConversionInProgress = new Object();
private static boolean sConversionHasRun = false;
/** This projection is for looking up accounts by their legacy UUID */
private static final String WHERE_ACCOUNT_UUID_IS = AccountColumns.COMPATIBILITY_UUID + "=?";
private static void actionStart(Context context) {
Intent i = new Intent(context, UpgradeAccounts.class);
context.startActivity(i);
}
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
ActivityHelper.debugSetWindowFlags(this);
Preferences p = Preferences.getPreferences(this);
Account[] legacyAccounts = p.getAccounts();
if (legacyAccounts.length == 0) {
finish();
return;
}
loadAccountInfoArray(legacyAccounts);
Log.d(Email.LOG_TAG, "*** Preparing to upgrade " +
Integer.toString(mLegacyAccounts.length) + " accounts");
setContentView(R.layout.upgrade_accounts);
mListView = getListView();
mProceedButton = (Button) findViewById(R.id.action_button);
mProceedButton.setEnabled(false);
mProceedButton.setOnClickListener(this);
}
@Override
protected void onResume() {
super.onResume();
updateList();
// Start the big conversion engine
mConversionTask = new ConversionTask(mLegacyAccounts);
mConversionTask.execute();
}
@Override
protected void onDestroy() {
super.onDestroy();
Utility.cancelTask(mConversionTask, false); // false = Don't interrupt running task
mConversionTask = null;
}
/**
* Stopgap measure to prevent monkey or zealous user from exiting while we're still at work.
*/
@Override
public void onBackPressed() {
if (!mProceedButton.isEnabled()) {
finish();
}
}
public void onClick(View v) {
switch (v.getId()) {
case R.id.action_button:
onClickOk();
break;
}
}
private void onClickOk() {
Welcome.actionStart(UpgradeAccounts.this);
finish();
}
private void updateList() {
mAdapter = new AccountsAdapter();
getListView().setAdapter(mAdapter);
}
private static class AccountInfo {
Account account;
int maxProgress;
int progress;
String errorMessage; // set/read by handler - UI thread only
boolean isError; // set/read by worker thread
public AccountInfo(Account legacyAccount) {
account = legacyAccount;
maxProgress = 0;
progress = 0;
errorMessage = null;
isError = false;
}
}
private void loadAccountInfoArray(Account[] legacyAccounts) {
mLegacyAccounts = new AccountInfo[legacyAccounts.length];
for (int i = 0; i < legacyAccounts.length; i++) {
AccountInfo ai = new AccountInfo(legacyAccounts[i]);
mLegacyAccounts[i] = ai;
}
}
private static class ViewHolder {
TextView displayName;
ProgressBar progress;
TextView errorReport;
}
class AccountsAdapter extends BaseAdapter {
final LayoutInflater mInflater;
AccountsAdapter() {
mInflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
@Override
public boolean hasStableIds() {
return true;
}
public int getCount() {
return mLegacyAccounts.length;
}
public Object getItem(int position) {
return mLegacyAccounts[position];
}
public long getItemId(int position) {
return position;
}
public View getView(int position, View convertView, ViewGroup parent) {
View v;
if (convertView == null) {
v = newView(parent);
} else {
v = convertView;
}
bindView(v, position);
return v;
}
public View newView(ViewGroup parent) {
View v = mInflater.inflate(R.layout.upgrade_accounts_item, parent, false);
ViewHolder h = new ViewHolder();
h.displayName = (TextView) v.findViewById(R.id.name);
h.progress = (ProgressBar) v.findViewById(R.id.progress);
h.errorReport = (TextView) v.findViewById(R.id.error);
v.setTag(h);
return v;
}
public void bindView(View view, int position) {
ViewHolder vh = (ViewHolder) view.getTag();
AccountInfo ai = mLegacyAccounts[position];
vh.displayName.setText(ai.account.getDescription());
if (ai.errorMessage == null) {
vh.errorReport.setVisibility(View.GONE);
vh.progress.setVisibility(View.VISIBLE);
vh.progress.setMax(ai.maxProgress);
vh.progress.setProgress(ai.progress);
} else {
vh.progress.setVisibility(View.GONE);
vh.errorReport.setVisibility(View.VISIBLE);
vh.errorReport.setText(ai.errorMessage);
}
}
}
/**
* Handler for updating UI from async workers
*
* TODO: I don't know the right paradigm for updating a progress bar in a ListView. I'd
* like to be able to say, "update it if it's visible, skip it if it's not visible."
*/
class UIHandler extends Handler {
private static final int MSG_SET_MAX = 1;
private static final int MSG_SET_PROGRESS = 2;
private static final int MSG_INC_PROGRESS = 3;
private static final int MSG_ERROR = 4;
@Override
public void handleMessage(android.os.Message msg) {
switch (msg.what) {
case MSG_SET_MAX:
mLegacyAccounts[msg.arg1].maxProgress = msg.arg2;
mListView.invalidateViews(); // find a less annoying way to do that
break;
case MSG_SET_PROGRESS:
mLegacyAccounts[msg.arg1].progress = msg.arg2;
mListView.invalidateViews(); // find a less annoying way to do that
break;
case MSG_INC_PROGRESS:
mLegacyAccounts[msg.arg1].progress += msg.arg2;
mListView.invalidateViews(); // find a less annoying way to do that
break;
case MSG_ERROR:
mLegacyAccounts[msg.arg1].errorMessage = (String) msg.obj;
mListView.invalidateViews(); // find a less annoying way to do that
mProceedButton.setEnabled(true);
break;
default:
super.handleMessage(msg);
}
}
public void setMaxProgress(int accountNum, int max) {
android.os.Message msg = android.os.Message.obtain();
msg.what = MSG_SET_MAX;
msg.arg1 = accountNum;
msg.arg2 = max;
sendMessage(msg);
}
public void setProgress(int accountNum, int progress) {
android.os.Message msg = android.os.Message.obtain();
msg.what = MSG_SET_PROGRESS;
msg.arg1 = accountNum;
msg.arg2 = progress;
sendMessage(msg);
}
public void incProgress(int accountNum) {
incProgress(accountNum, 1);
}
public void incProgress(int accountNum, int incrementBy) {
if (incrementBy == 0) return;
android.os.Message msg = android.os.Message.obtain();
msg.what = MSG_INC_PROGRESS;
msg.arg1 = accountNum;
msg.arg2 = incrementBy;
sendMessage(msg);
}
// Note: also enables the "OK" button, so we pause when complete
public void error(int accountNum, String error) {
android.os.Message msg = android.os.Message.obtain();
msg.what = MSG_ERROR;
msg.arg1 = accountNum;
msg.obj = error;
sendMessage(msg);
}
}
/**
* Everything above was UI plumbing. This is the meat of this class - a conversion
* engine to rebuild accounts from the "LocalStore" (pre Android 2.0) format to the
* "Provider" (2.0 and beyond) format.
*/
private class ConversionTask extends AsyncTask<Void, Void, Void> {
UpgradeAccounts.AccountInfo[] mAccountInfo;
final Context mContext;
final Preferences mPreferences;
public ConversionTask(UpgradeAccounts.AccountInfo[] accountInfo) {
// TODO: should I copy this?
mAccountInfo = accountInfo;
mContext = UpgradeAccounts.this;
mPreferences = Preferences.getPreferences(mContext);
}
@Override
protected Void doInBackground(Void... params) {
// Globally synchronize this entire block to prevent it from running in multiple
// threads. this is used in case we wind up relaunching during a conversion.
// If this is anything but the first thread, sConversionHasRun will be set and we'll
// exit immediately when it's all over.
synchronized (sConversionInProgress) {
if (sConversionHasRun) {
return null;
}
sConversionHasRun = true;
UIHandler handler = UpgradeAccounts.this.mHandler;
// Step 1: Analyze accounts and generate progress max values
for (int i = 0; i < mAccountInfo.length; i++) {
int estimate = UpgradeAccounts.estimateWork(mContext, mAccountInfo[i].account);
if (estimate == -1) {
mAccountInfo[i].isError = true;
mHandler.error(i, mContext.getString(R.string.upgrade_accounts_error));
}
UpgradeAccounts.this.mHandler.setMaxProgress(i, estimate);
}
// Step 2: Scrub accounts, deleting anything we're not keeping to reclaim storage
for (int i = 0; i < mAccountInfo.length; i++) {
if (!mAccountInfo[i].isError) {
boolean ok = scrubAccount(mContext, mAccountInfo[i].account, i, handler);
if (!ok) {
mAccountInfo[i].isError = true;
mHandler.error(i, mContext.getString(R.string.upgrade_accounts_error));
}
}
}
// Step 3: Copy accounts (and delete old accounts). POP accounts first.
// Note: We don't check error condition here because we still want to
// delete the remaining parts of all accounts (even if in error condition).
for (int i = 0; i < mAccountInfo.length; i++) {
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.setServicesEnabledSync(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) {
try {
if (type != null) {
String storeUri = info.account.getStoreUri();
boolean isType = storeUri.startsWith(type);
if (!isType) {
return; // skip this account
}
}
// Don't try copying if this account is already in error state
if (!info.isError) {
copyAccount(mContext, info.account, i, handler);
}
} catch (RuntimeException e) {
Log.d(Email.LOG_TAG, "Exception while copying account " + e);
mHandler.error(i, mContext.getString(R.string.upgrade_accounts_error));
info.isError = true;
}
// best effort to delete it (whether copied or not)
try {
deleteAccountStore(mContext, info.account, i, handler);
info.account.delete(mPreferences);
} catch (RuntimeException e) {
Log.d(Email.LOG_TAG, "Exception while deleting account " + e);
// No user notification is required here - we're done
}
// jam the progress indicator to mark account "complete" (in case est was wrong)
handler.setProgress(i, Integer.MAX_VALUE);
}
@Override
protected void onPostExecute(Void result) {
if (!isCancelled()) {
// if there were no errors, we never enabled the OK button, but
// we'll just proceed through anyway and return to the Welcome activity
if (!mProceedButton.isEnabled()) {
onClickOk();
}
}
}
}
/**
* Estimate the work required to convert an account.
* 1 (account) + # folders + # messages + # attachments
* @return conversion operations estimate, or -1 if there's any problem
*/
/* package */ static int estimateWork(Context context, Account account) {
int estimate = 1; // account
try {
LocalStore store = LocalStore.newInstance(account.getLocalStoreUri(), context, null);
Folder[] folders = store.getPersonalNamespaces();
estimate += folders.length;
for (int i = 0; i < folders.length; i++) {
Folder folder = folders[i];
folder.open(Folder.OpenMode.READ_ONLY, null);
estimate += folder.getMessageCount();
folder.close(false);
}
estimate += store.getStoredAttachmentCount();
store.close();
} catch (MessagingException e) {
Log.d(Email.LOG_TAG, "Exception while estimating account size " + e);
return -1;
} catch (RuntimeException e) {
Log.d(Email.LOG_TAG, "Exception while estimating account size " + e);
return -1;
}
return estimate;
}
/**
* Clean out an account.
*
* For IMAP: Anything we can reload from server, we delete. This reduces the risk of running
* out of disk space by copying everything.
* For POP: Delete the trash folder (which we won't bring forward).
* @return true if successful, false if any kind of error
*/
/* package */ static boolean scrubAccount(Context context, Account account, int accountNum,
UIHandler handler) {
try {
String storeUri = account.getStoreUri();
boolean isImap = storeUri.startsWith(Store.STORE_SCHEME_IMAP);
LocalStore store = LocalStore.newInstance(account.getLocalStoreUri(), context, null);
Folder[] folders = store.getPersonalNamespaces();
for (Folder folder : folders) {
folder.open(Folder.OpenMode.READ_ONLY, null);
String folderName = folder.getName();
if ("drafts".equalsIgnoreCase(folderName)) {
// do not delete drafts
} else if ("outbox".equalsIgnoreCase(folderName)) {
// do not delete outbox
} else if ("sent".equalsIgnoreCase(folderName)) {
// do not delete sent
} else if (isImap || "trash".equalsIgnoreCase(folderName)) {
Log.d(Email.LOG_TAG, "Scrub " + account.getDescription() + "." + folderName);
// for all other folders, delete the folder (and its messages & attachments)
int messageCount = folder.getMessageCount();
folder.delete(true);
if (handler != null) {
handler.incProgress(accountNum, 1 + messageCount);
}
}
folder.close(false);
}
int pruned = store.pruneCachedAttachments();
if (handler != null) {
handler.incProgress(accountNum, pruned);
}
store.close();
} catch (MessagingException e) {
Log.d(Email.LOG_TAG, "Exception while scrubbing account " + e);
return false;
} catch (RuntimeException e) {
Log.d(Email.LOG_TAG, "Exception while scrubbing account " + e);
return false;
}
return true;
}
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.
*/
/* package */ static void copyAccount(Context context, Account account, int accountNum,
UIHandler handler) {
// If already exists- just skip it
int existCount = EmailContent.count(context, EmailContent.Account.CONTENT_URI,
WHERE_ACCOUNT_UUID_IS, new String[] { account.getUuid() });
if (existCount > 0) {
Log.d(Email.LOG_TAG, "No conversion, account exists: " + account.getDescription());
if (handler != null) {
handler.error(accountNum, context.getString(R.string.upgrade_accounts_error));
}
return;
}
// Create the new account and write it
EmailContent.Account newAccount = LegacyConversions.makeAccount(context, account);
cleanupConnections(context, newAccount, account);
newAccount.save(context);
if (handler != null) {
handler.incProgress(accountNum);
}
// 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;
LocalStore store = null;
try {
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(accountNum,
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. we also
// process local (outgoing) attachments here
if (outbox != null) {
copyMessages(context, outbox, true, newAccount, accountNum, handler);
conversions.remove(outbox);
}
if (drafts != null) {
copyMessages(context, drafts, true, newAccount, accountNum, handler);
conversions.remove(drafts);
}
if (sent != null) {
copyMessages(context, sent, true, newAccount, accountNum, handler);
conversions.remove(outbox);
}
// Now handle any remaining folders. For incoming folders we skip attachments, as they
// can be reloaded from the server.
for (FolderConversion conversion : conversions) {
copyMessages(context, conversion, false, newAccount, accountNum, handler);
}
} catch (MessagingException e) {
Log.d(Email.LOG_TAG, "Exception while copying folders " + e);
// Couldn't copy folders at all
if (handler != null) {
handler.error(accountNum, context.getString(R.string.upgrade_accounts_error));
}
} finally {
if (store != null) {
store.close();
}
}
}
/**
* 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 newAccount 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, EmailContent.Account newAccount, int accountNum,
UIHandler handler) {
try {
boolean makeDeleteSentinels = (conversion.mailbox.mType == Mailbox.TYPE_INBOX) &&
(newAccount.getDeletePolicy() == EmailContent.Account.DELETE_POLICY_NEVER);
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);
EmailContent.Message newMessage = new EmailContent.Message();
if (makeDeleteSentinels && oldMessage.isSet(Flag.DELETED)) {
// Special case for POP3 locally-deleted messages.
// Creates a local "deleted message sentinel" which hides the message
// Echos provider code in MessagingController.processPendingMoveToTrash()
newMessage.mAccountKey = newAccount.mId;
newMessage.mMailboxKey = conversion.mailbox.mId;
newMessage.mFlagLoaded = EmailContent.Message.FLAG_LOADED_DELETED;
newMessage.mFlagRead = true;
newMessage.mServerId = oldMessage.getUid();
newMessage.save(context);
} else {
// Main case for converting real messages with bodies & attachments
// convert message (headers)
LegacyConversions.updateMessageFields(newMessage, oldMessage,
newAccount.mId, 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);
}
}
// 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(accountNum,
context.getString(R.string.upgrade_accounts_error));
}
}
}
} catch (MessagingException e) {
// Couldn't copy folder at all
Log.d(Email.LOG_TAG, "Exception while copying messages in " +
conversion.folder.toString() + ": " + e);
if (handler != null) {
handler.error(accountNum, context.getString(R.string.upgrade_accounts_error));
}
}
}
/**
* Delete an account
*/
/* package */ static void deleteAccountStore(Context context, Account account, int accountNum,
UIHandler handler) {
try {
Store store = LocalStore.newInstance(account.getLocalStoreUri(), context, null);
store.delete();
// delete() closes the store
} catch (MessagingException e) {
Log.d(Email.LOG_TAG, "Exception while deleting account " + e);
if (handler != null) {
handler.error(accountNum, context.getString(R.string.upgrade_accounts_error));
}
}
}
/**
* Cleanup SSL, TLS, etc for each converted account.
*/
/* package */ static void cleanupConnections(Context context, EmailContent.Account newAccount,
Account account) {
// 1. Look up provider for this email address
String email = newAccount.mEmailAddress;
int atSignPos = email.lastIndexOf('@');
String domain = email.substring(atSignPos + 1);
Provider p = AccountSettingsUtils.findProviderForDomain(context, domain);
// 2. If provider found, just use its settings (overriding what user had)
// This is drastic but most reliable. Note: This also benefits from newer provider
// data that might be found in a vendor policy module.
if (p != null) {
// Incoming
try {
URI incomingUriTemplate = p.incomingUriTemplate;
String incomingUsername = newAccount.mHostAuthRecv.mLogin;
String incomingPassword = newAccount.mHostAuthRecv.mPassword;
URI incomingUri = new URI(incomingUriTemplate.getScheme(), incomingUsername + ":"
+ incomingPassword, incomingUriTemplate.getHost(),
incomingUriTemplate.getPort(), incomingUriTemplate.getPath(), null, null);
newAccount.mHostAuthRecv.setStoreUri(incomingUri.toString());
} catch (URISyntaxException e) {
// Ignore - just use the data we copied across (for better or worse)
}
// Outgoing
try {
URI outgoingUriTemplate = p.outgoingUriTemplate;
String outgoingUsername = newAccount.mHostAuthSend.mLogin;
String outgoingPassword = newAccount.mHostAuthSend.mPassword;
URI outgoingUri = new URI(outgoingUriTemplate.getScheme(), outgoingUsername + ":"
+ outgoingPassword, outgoingUriTemplate.getHost(),
outgoingUriTemplate.getPort(), outgoingUriTemplate.getPath(), null, null);
newAccount.mHostAuthSend.setStoreUri(outgoingUri.toString());
} catch (URISyntaxException e) {
// Ignore - just use the data we copied across (for better or worse)
}
Log.d(Email.LOG_TAG, "Rewriting connection details for " + account.getDescription());
return;
}
// 3. Otherwise, use simple heuristics to adjust connection and attempt to keep it
// reliable. NOTE: These are the "legacy" ssl/tls encodings, not the ones in
// the current provider.
newAccount.mHostAuthRecv.mFlags |= HostAuth.FLAG_TRUST_ALL_CERTIFICATES;
String receiveUri = account.getStoreUri();
if (receiveUri.contains("+ssl+")) {
// non-optional SSL - keep as is, with trust all
} else if (receiveUri.contains("+ssl")) {
// optional SSL - TBD
} else if (receiveUri.contains("+tls+")) {
// non-optional TLS - keep as is, with trust all
} else if (receiveUri.contains("+tls")) {
// optional TLS - TBD
}
newAccount.mHostAuthSend.mFlags |= HostAuth.FLAG_TRUST_ALL_CERTIFICATES;
String sendUri = account.getSenderUri();
if (sendUri.contains("+ssl+")) {
// non-optional SSL - keep as is, with trust all
} else if (sendUri.contains("+ssl")) {
// optional SSL - TBD
} else if (sendUri.contains("+tls+")) {
// non-optional TLS - keep as is, with trust all
} else if (sendUri.contains("+tls")) {
// optional TLS - TBD
}
}
/**
* Bulk upgrade old accounts if exist.
*
* @return true if bulk upgrade has started. false otherwise.
*/
/* package */ static boolean doBulkUpgradeIfNecessary(Context context) {
// if (bulkUpgradesRequired(context, Preferences.getPreferences(context))) {
// UpgradeAccounts.actionStart(context);
// return true;
// }
return false;
}
/**
* Test for bulk upgrades and return true if necessary
*
* @return true if upgrades required (old accounts exist). false otherwise.
*/
private static boolean bulkUpgradesRequired(Context context, Preferences preferences) {
if (DEBUG_FORCE_UPGRADES) {
// build at least one fake account
Account fake = new Account(context);
fake.setDescription("Fake Account");
fake.setEmail("user@gmail.com");
fake.setName("First Last");
fake.setSenderUri("smtp://user:password@smtp.gmail.com");
fake.setStoreUri("imap://user:password@imap.gmail.com");
fake.save(preferences);
return true;
}
// 1. Get list of legacy accounts and look for any non-backup entries
Account[] legacyAccounts = preferences.getAccounts();
if (legacyAccounts.length == 0) {
return false;
}
// 2. Look at the first legacy account and decide what to do
// We only need to look at the first: If it's not a backup account, then it's a true
// legacy account, and there are one or more accounts needing upgrade. If it is a backup
// account, then we know for sure that there are no legacy accounts (backup deletes all
// old accounts, and indicates that "modern" code has already run on this device.)
if (0 != (legacyAccounts[0].getBackupFlags() & Account.BACKUP_FLAGS_IS_BACKUP)) {
return false;
} else {
return true;
}
}
}