Improvements to default account handling

* Remove all references to Account.mIsDefault, which was not the right
    way to find the default account (it is lazy initialized)
* Change Account.getDefaultAccount to getDefaultAccountId, which is more
    efficient and suitable in most uses.
* Wrote unit tests for provider default account handling

This should resolve bug 1983390 as well as a few other issues with default
account management.
This commit is contained in:
Andrew Stadler 2009-07-21 16:44:16 -07:00
parent 986a3b5f03
commit 54c1f2bf9a
10 changed files with 124 additions and 52 deletions

View File

@ -363,9 +363,9 @@ public class AccountFolderList extends ExpandableListActivity {
}
private void onCompose() {
EmailContent.Account defaultAccount = EmailContent.Account.getDefaultAccount(this);
if (defaultAccount != null) {
MessageCompose.actionCompose(this, defaultAccount.mId);
long defaultAccountId = Account.getDefaultAccountId(this);
if (defaultAccountId != -1) {
MessageCompose.actionCompose(this, defaultAccountId);
} else {
onAddNewAccount();
}

View File

@ -16,6 +16,7 @@
package com.android.email.activity;
import com.android.email.Controller;
import com.android.email.Email;
import com.android.email.EmailAddressAdapter;
import com.android.email.EmailAddressValidator;
@ -23,7 +24,6 @@ import com.android.email.MessagingController;
import com.android.email.MessagingListener;
import com.android.email.R;
import com.android.email.Utility;
import com.android.email.Controller;
import com.android.email.mail.Address;
import com.android.email.mail.Body;
import com.android.email.mail.Message;
@ -32,15 +32,11 @@ import com.android.email.mail.Multipart;
import com.android.email.mail.Part;
import com.android.email.mail.Message.RecipientType;
import com.android.email.mail.internet.EmailHtmlUtil;
import com.android.email.mail.internet.MimeBodyPart;
import com.android.email.mail.internet.MimeHeader;
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.mail.store.LocalStore.LocalAttachmentBody;
import com.android.email.provider.EmailContent;
import com.android.email.provider.EmailContent.Account;
import android.app.Activity;
import android.content.ActivityNotFoundException;
@ -81,7 +77,6 @@ import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
public class MessageCompose extends Activity implements OnClickListener, OnFocusChangeListener {
@ -117,7 +112,7 @@ public class MessageCompose extends Activity implements OnClickListener, OnFocus
private static final int ACTIVITY_REQUEST_PICK_ATTACHMENT = 1;
private long mAccountId;
private EmailContent.Account mAccount;
private Account mAccount;
private String mFolder;
private String mSourceMessageUid;
private Message mSourceMessage;
@ -432,14 +427,16 @@ public class MessageCompose extends Activity implements OnClickListener, OnFocus
(Intent.ACTION_SEND.equals(action))) {
// Check first for a valid account
mAccount = EmailContent.Account.getDefaultAccount(this);
if (mAccount == null) {
mAccountId = Account.getDefaultAccountId(this);
if (mAccountId == -1) {
// There are no accounts set up. This should not have happened. Prompt the
// user to set up an account as an acceptable bailout.
AccountFolderList.actionShowAccounts(this);
mDraftNeedsSaving = false;
finish();
return;
} else {
mAccount = Account.restoreAccountWithId(this, mAccountId);
}
// Use the fields found in the Intent to prefill as much of the message as possible
@ -448,7 +445,7 @@ public class MessageCompose extends Activity implements OnClickListener, OnFocus
else {
// Otherwise, handle the internal cases (Message Composer invoked from within app)
mAccountId = intent.getLongExtra(EXTRA_ACCOUNT_ID, -1);
mAccount = EmailContent.Account.restoreAccountWithId(this, mAccountId);
mAccount = Account.restoreAccountWithId(this, mAccountId);
mFolder = intent.getStringExtra(EXTRA_FOLDER);
// TODO: change sourceMessageUid to be long _id instead of String
mSourceMessageUid = intent.getStringExtra(EXTRA_MESSAGE);
@ -1305,19 +1302,19 @@ public class MessageCompose extends Activity implements OnClickListener, OnFocus
class Listener extends MessagingListener {
@Override
public void loadMessageForViewStarted(EmailContent.Account account, String folder,
public void loadMessageForViewStarted(Account account, String folder,
String uid) {
mHandler.sendEmptyMessage(MSG_PROGRESS_ON);
}
@Override
public void loadMessageForViewFinished(EmailContent.Account account, String folder,
public void loadMessageForViewFinished(Account account, String folder,
String uid, Message message) {
mHandler.sendEmptyMessage(MSG_PROGRESS_OFF);
}
@Override
public void loadMessageForViewBodyAvailable(EmailContent.Account account, String folder,
public void loadMessageForViewBodyAvailable(Account account, String folder,
String uid, final Message message) {
mSourceMessage = message;
runOnUiThread(new Runnable() {
@ -1328,14 +1325,14 @@ public class MessageCompose extends Activity implements OnClickListener, OnFocus
}
@Override
public void loadMessageForViewFailed(EmailContent.Account account, String folder, String uid,
public void loadMessageForViewFailed(Account account, String folder, String uid,
final String message) {
mHandler.sendEmptyMessage(MSG_PROGRESS_OFF);
// TODO show network error
}
@Override
public void messageUidChanged(EmailContent.Account account, String folder,String oldUid,
public void messageUidChanged(Account account, String folder,String oldUid,
String newUid) {
if (account.equals(mAccount) && (folder.equals(mFolder)
|| (mFolder == null

View File

@ -22,6 +22,7 @@ import com.android.email.mail.MessagingException;
import com.android.email.mail.Sender;
import com.android.email.mail.Store;
import com.android.email.provider.EmailContent;
import com.android.email.provider.EmailContent.Account;
import com.android.email.provider.EmailContent.HostAuth;
import android.app.Activity;
@ -55,7 +56,7 @@ public class AccountSettings extends PreferenceActivity {
private static final String PREFERENCE_ADD_ACCOUNT = "add_account";
private long mAccountId;
private EmailContent.Account mAccount;
private Account mAccount;
private boolean mAccountDirty;
private EditTextPreference mAccountDescription;
@ -82,7 +83,7 @@ public class AccountSettings extends PreferenceActivity {
Intent i = getIntent();
mAccountId = getIntent().getLongExtra(EXTRA_ACCOUNT_ID, -1);
mAccount = EmailContent.Account.restoreAccountWithId(this, mAccountId);
mAccount = Account.restoreAccountWithId(this, mAccountId);
mAccountDirty = false;
addPreferencesFromResource(R.xml.account_settings_preferences);
@ -158,11 +159,10 @@ public class AccountSettings extends PreferenceActivity {
}
mAccountDefault = (CheckBoxPreference) findPreference(PREFERENCE_DEFAULT);
mAccountDefault.setChecked(mAccount.mIsDefault);
mAccountDefault.setChecked(mAccount.mId == Account.getDefaultAccountId(this));
mAccountNotify = (CheckBoxPreference) findPreference(PREFERENCE_NOTIFY);
mAccountNotify.setChecked(0 !=
(mAccount.getFlags() & EmailContent.Account.FLAGS_NOTIFY_NEW_MAIL));
mAccountNotify.setChecked(0 != (mAccount.getFlags() & Account.FLAGS_NOTIFY_NEW_MAIL));
mAccountRingtone = (RingtonePreference) findPreference(PREFERENCE_RINGTONE);
@ -173,7 +173,7 @@ public class AccountSettings extends PreferenceActivity {
mAccountVibrate = (CheckBoxPreference) findPreference(PREFERENCE_VIBRATE);
mAccountVibrate.setChecked(0 !=
(mAccount.getFlags() & EmailContent.Account.FLAGS_VIBRATE));
(mAccount.getFlags() & Account.FLAGS_VIBRATE));
findPreference(PREFERENCE_INCOMING).setOnPreferenceClickListener(
new Preference.OnPreferenceClickListener() {
@ -237,18 +237,18 @@ public class AccountSettings extends PreferenceActivity {
private void saveSettings() {
int newFlags = mAccount.getFlags() &
~(EmailContent.Account.FLAGS_NOTIFY_NEW_MAIL | EmailContent.Account.FLAGS_VIBRATE);
~(Account.FLAGS_NOTIFY_NEW_MAIL | Account.FLAGS_VIBRATE);
mAccount.setDefaultAccount(mAccountDefault.isChecked());
mAccount.setDescription(mAccountDescription.getText());
mAccount.setName(mAccountName.getText());
newFlags |= mAccountNotify.isChecked() ? EmailContent.Account.FLAGS_NOTIFY_NEW_MAIL : 0;
newFlags |= mAccountNotify.isChecked() ? Account.FLAGS_NOTIFY_NEW_MAIL : 0;
mAccount.setAutomaticCheckIntervalMinutes(Integer.parseInt(mCheckFrequency.getValue()));
if (mSyncWindow != null)
{
mAccount.setSyncWindow(Integer.parseInt(mSyncWindow.getValue()));
}
newFlags |= mAccountVibrate.isChecked() ? EmailContent.Account.FLAGS_VIBRATE : 0;
newFlags |= mAccountVibrate.isChecked() ? Account.FLAGS_VIBRATE : 0;
SharedPreferences prefs = mAccountRingtone.getPreferenceManager().getSharedPreferences();
mAccount.setRingtone(prefs.getString(PREFERENCE_RINGTONE, null));
mAccount.setFlags(newFlags);
@ -272,7 +272,7 @@ public class AccountSettings extends PreferenceActivity {
Class<? extends android.app.Activity> setting = store.getSettingActivityClass();
if (setting != null) {
java.lang.reflect.Method m = setting.getMethod("actionEditIncomingSettings",
android.app.Activity.class, EmailContent.Account.class);
android.app.Activity.class, Account.class);
m.invoke(null, this, mAccount);
mAccountDirty = true;
}
@ -289,7 +289,7 @@ public class AccountSettings extends PreferenceActivity {
Class<? extends android.app.Activity> setting = sender.getSettingActivityClass();
if (setting != null) {
java.lang.reflect.Method m = setting.getMethod("actionEditOutgoingSettings",
android.app.Activity.class, EmailContent.Account.class);
android.app.Activity.class, Account.class);
m.invoke(null, this, mAccount);
mAccountDirty = true;
}

View File

@ -24,6 +24,7 @@ import com.android.email.Utility;
import com.android.email.activity.Debug;
import com.android.email.activity.FolderMessageList;
import com.android.email.provider.EmailContent;
import com.android.email.provider.EmailContent.Account;
import android.app.Activity;
import android.app.AlertDialog;
@ -182,9 +183,12 @@ public class AccountSetupBasics extends Activity
}
if (name == null || name.length() == 0) {
EmailContent.Account account = EmailContent.Account.getDefaultAccount(this);
if (account != null) {
name = account.getName();
long defaultId = Account.getDefaultAccountId(this);
if (defaultId != -1) {
Account account = Account.restoreAccountWithId(this, defaultId);
if (account != null) {
name = account.getName();
}
}
}
return name;

View File

@ -95,6 +95,8 @@ public class AccountSetupOptions extends Activity implements OnClickListener {
enableEASSyncWindowSpinner();
}
// Note: It is OK to use mAccount.mIsDefault here *only* because the account
// has not been written to the DB yet. Ordinarily, call Account.getDefaultAccountId().
if (mAccount.mIsDefault || makeDefault) {
mDefaultView.setChecked(true);
}

View File

@ -797,7 +797,7 @@ public abstract class EmailContent {
public long mHostAuthKeyRecv;
public long mHostAuthKeySend;
public int mFlags;
public boolean mIsDefault;
public boolean mIsDefault; // note: callers should use getDefaultAccountId()
public String mCompatibilityUuid;
public String mSenderName;
public String mRingtoneUri;
@ -838,6 +838,13 @@ public abstract class EmailContent {
RECORD_ID
};
/**
* This projection is for searching for the default account
*/
private static final String[] DEFAULT_ID_PROJECTION = new String[] {
RECORD_ID, IS_DEFAULT
};
/**
* no public constructor since this is a utility class
*/
@ -1151,31 +1158,37 @@ public abstract class EmailContent {
mIsDefault = newDefaultState;
}
static private Account getAccountWhere(Context context, String where) {
Cursor cursor = context.getContentResolver().query(CONTENT_URI, CONTENT_PROJECTION,
/**
* Helper method for finding the default account.
*/
static private long getDefaultAccountWhere(Context context, String where) {
Cursor cursor = context.getContentResolver().query(CONTENT_URI,
DEFAULT_ID_PROJECTION,
where, null, null);
try {
if (cursor.moveToFirst()) {
return getContent(cursor, Account.class);
return cursor.getLong(0); // column 0 is id
}
} finally {
cursor.close();
}
return null;
return -1;
}
/**
* Return the default Account; if one hasn't been explicitly specified, return the first
* account found in the database.
* @param context
* @return the default Account (or none, if there are no accounts)
* Return the id of the default account. If one hasn't been explicitly specified, return
* the first one in the database. For any account saved in the DB, this must be used
* to check for the default account - the mIsDefault field is set lazily and may be
* incorrect.
* @param context the caller's context
* @return the id of the default account, or -1 if there are no accounts
*/
static public Account getDefaultAccount(Context context) {
Account acct = getAccountWhere(context, AccountColumns.IS_DEFAULT + "=1");
if (acct == null) {
acct = getAccountWhere(context, null);
static public long getDefaultAccountId(Context context) {
long id = getDefaultAccountWhere(context, AccountColumns.IS_DEFAULT + "=1");
if (id == -1) {
id = getDefaultAccountWhere(context, null);
}
return acct;
return id;
}
/**

View File

@ -26,6 +26,7 @@ import com.android.email.mail.Message.RecipientType;
import com.android.email.mail.internet.MimeMessage;
import com.android.email.mail.internet.TextBody;
import com.android.email.provider.EmailContent;
import com.android.email.provider.EmailContent.Account;
import android.content.Context;
import android.content.Intent;
@ -104,7 +105,9 @@ public class MessageComposeInstrumentationTests
super.setUp();
Context context = getInstrumentation().getTargetContext();
EmailContent.Account.getDefaultAccount(context);
// Force assignment of a default account
long accountId = Account.getDefaultAccountId(context);
Account.restoreAccountWithId(context, accountId);
Email.setServicesEnabled(context);
Intent intent = new Intent(Intent.ACTION_VIEW);

View File

@ -21,6 +21,7 @@ import com.android.email.MessagingController;
import com.android.email.R;
import com.android.email.mail.internet.BinaryTempFileBody;
import com.android.email.provider.EmailContent;
import com.android.email.provider.EmailContent.Account;
import android.app.Application;
import android.content.Context;
@ -75,9 +76,9 @@ public class MessageViewTests
super.setUp();
mContext = getInstrumentation().getTargetContext();
// force assignment of a default account
mAccount = EmailContent.Account.getDefaultAccount(mContext);
mAccountId = mAccount.mId;
// Force assignment of a default account, and retrieve it
mAccountId = Account.getDefaultAccountId(mContext);
mAccount = Account.restoreAccountWithId(mContext, mAccountId);
Email.setServicesEnabled(mContext);
// setup an intent to spin up this activity with something useful

View File

@ -24,6 +24,7 @@ import com.android.email.mail.MessageTestUtils.MultipartBuilder;
import com.android.email.mail.MessageTestUtils.TextBuilder;
import com.android.email.mail.store.LocalStore;
import com.android.email.provider.EmailContent;
import com.android.email.provider.EmailContent.Account;
import android.net.Uri;
import android.test.AndroidTestCase;
@ -43,7 +44,8 @@ public class EmailHtmlUtilTest extends AndroidTestCase {
protected void setUp() throws Exception {
super.setUp();
// Force assignment of a default account, and retrieve it
mAccount = EmailContent.Account.getDefaultAccount(getContext());
long accountId = Account.getDefaultAccountId(getContext());
mAccount = Account.restoreAccountWithId(getContext(), accountId);
// This is needed for mime image bodypart.
BinaryTempFileBody.setTempDirectory(getContext().getCacheDir());

View File

@ -649,4 +649,54 @@ public class ProviderTests extends ProviderTestCase2<EmailProvider> {
c.close();
}
}
/**
* Tests of default account behavior
*
* 1. Simple set/get
* 2. Moving default between 3 accounts
* 3. Delete default, make sure another becomes default
*/
public void testSetGetDefaultAccount() {
// There should be no default account if there are no accounts
long defaultAccountId = Account.getDefaultAccountId(mMockContext);
assertEquals(-1, defaultAccountId);
Account account1 = ProviderTestUtils.setupAccount("account-default-1", true, mMockContext);
long account1Id = account1.mId;
Account account2 = ProviderTestUtils.setupAccount("account-default-2", true, mMockContext);
long account2Id = account2.mId;
Account account3 = ProviderTestUtils.setupAccount("account-default-3", true, mMockContext);
long account3Id = account3.mId;
account1.setDefaultAccount(true);
account1.saveOrUpdate(mMockContext);
defaultAccountId = Account.getDefaultAccountId(mMockContext);
assertEquals(account1Id, defaultAccountId);
account2.setDefaultAccount(true);
account2.saveOrUpdate(mMockContext);
defaultAccountId = Account.getDefaultAccountId(mMockContext);
assertEquals(account2Id, defaultAccountId);
account3.setDefaultAccount(true);
account3.saveOrUpdate(mMockContext);
defaultAccountId = Account.getDefaultAccountId(mMockContext);
assertEquals(account3Id, defaultAccountId);
// Now delete a non-default account and confirm no change
Uri uri = ContentUris.withAppendedId(Account.CONTENT_URI, account1Id);
mMockContext.getContentResolver().delete(uri, null, null);
defaultAccountId = Account.getDefaultAccountId(mMockContext);
assertEquals(account3Id, defaultAccountId);
// Now confirm deleting the default account and it switches to another one
uri = ContentUris.withAppendedId(Account.CONTENT_URI, account3Id);
mMockContext.getContentResolver().delete(uri, null, null);
defaultAccountId = Account.getDefaultAccountId(mMockContext);
assertEquals(account2Id, defaultAccountId);
}
}