From ea6fea9bb22368c10083d5dce52adae86e51a243 Mon Sep 17 00:00:00 2001 From: Andy Stadler <> Date: Tue, 7 Apr 2009 17:35:13 -0700 Subject: [PATCH] AI 144953: Provide UI for push mode accounts. 1. Generalize the code for the various spinners that control account check frequency. 2. Provide an API for looking up store attributes (and refactor existing instatiateStore logic to use it). 3. Cleanup the old code that was used to setup frequency spinners. 4. Hardwire Exchange accounts to default into push mode. Notes to tester: 1. For each account type (POP, IMAP, EAS) we need to check that auto & manual creation "do the right thing" for frequencies. POP & IMAP should offer "none" or time intervals, while EAS should offer "push", "none", or time intervals. 2. EAS accounts should default to "push", all others to "15 min" 3. Make sure that you can edit existing account settings and see the right choices (only EAS should be offered push). 4. I couldn't write an automated test for the mail checker service, please confirm that POP & IMAP accounts are checked at the right intervals (or never, if set for "none".) BUG=1776149 Automated import of CL 144953 --- res/values/arrays.xml | 22 ++++ res/values/strings.xml | 2 + res/xml/stores.xml | 3 +- src/com/android/email/Account.java | 3 + .../email/activity/setup/AccountSettings.java | 10 +- .../setup/AccountSetupAccountType.java | 4 +- .../activity/setup/AccountSetupOptions.java | 45 ++++--- src/com/android/email/mail/Store.java | 64 +++++---- .../android/email/service/MailService.java | 2 +- .../activity/setup/AccountSettingsTests.java | 120 +++++++++++++++++ .../setup/AccountSetupOptionsTests.java | 123 ++++++++++++++++++ .../com/android/email/mail/StoreTests.java | 99 ++++++++++++++ 12 files changed, 447 insertions(+), 50 deletions(-) create mode 100644 tests/src/com/android/email/activity/setup/AccountSettingsTests.java create mode 100644 tests/src/com/android/email/activity/setup/AccountSetupOptionsTests.java create mode 100644 tests/src/com/android/email/mail/StoreTests.java diff --git a/res/values/arrays.xml b/res/values/arrays.xml index cdf14b159..3fcc558bf 100644 --- a/res/values/arrays.xml +++ b/res/values/arrays.xml @@ -39,4 +39,26 @@ 60 + + + + @string/account_setup_options_mail_check_frequency_push + @string/account_setup_options_mail_check_frequency_never + @string/account_setup_options_mail_check_frequency_5min + @string/account_setup_options_mail_check_frequency_10min + @string/account_setup_options_mail_check_frequency_15min + @string/account_setup_options_mail_check_frequency_30min + @string/account_setup_options_mail_check_frequency_1hour + + + + -2 + -1 + 5 + 10 + 15 + 30 + 60 + + diff --git a/res/values/strings.xml b/res/values/strings.xml index 47dffdc6e..a7d8da2cf 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -310,6 +310,8 @@ Email checking frequency Never + + Automatic (Push) Every 5 minutes diff --git a/res/xml/stores.xml b/res/xml/stores.xml index efe4ac227..f63b03495 100644 --- a/res/xml/stores.xml +++ b/res/xml/stores.xml @@ -35,5 +35,6 @@ - + diff --git a/src/com/android/email/Account.java b/src/com/android/email/Account.java index 2d98ce3ab..c1e8cb2e4 100644 --- a/src/com/android/email/Account.java +++ b/src/com/android/email/Account.java @@ -35,6 +35,9 @@ public class Account implements Serializable { public static final int DELETE_POLICY_7DAYS = 1; public static final int DELETE_POLICY_ON_DELETE = 2; + public static final int CHECK_INTERVAL_NEVER = -1; + public static final int CHECK_INTERVAL_PUSH = -2; + private static final long serialVersionUID = 2975156672298625121L; String mUuid; diff --git a/src/com/android/email/activity/setup/AccountSettings.java b/src/com/android/email/activity/setup/AccountSettings.java index c9bd1a032..52536c706 100644 --- a/src/com/android/email/activity/setup/AccountSettings.java +++ b/src/com/android/email/activity/setup/AccountSettings.java @@ -101,8 +101,16 @@ public class AccountSettings extends PreferenceActivity { return false; } }); - + mCheckFrequency = (ListPreference) findPreference(PREFERENCE_FREQUENCY); + + // Before setting value, we may need to adjust the lists + Store.StoreInfo info = Store.StoreInfo.getStoreInfo(mAccount.getStoreUri(), this); + if (info.mPushSupported) { + mCheckFrequency.setEntries(R.array.account_settings_check_frequency_entries_push); + mCheckFrequency.setEntryValues(R.array.account_settings_check_frequency_values_push); + } + mCheckFrequency.setValue(String.valueOf(mAccount.getAutomaticCheckIntervalMinutes())); mCheckFrequency.setSummary(mCheckFrequency.getEntry()); mCheckFrequency.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { diff --git a/src/com/android/email/activity/setup/AccountSetupAccountType.java b/src/com/android/email/activity/setup/AccountSetupAccountType.java index 021175463..58e910490 100644 --- a/src/com/android/email/activity/setup/AccountSetupAccountType.java +++ b/src/com/android/email/activity/setup/AccountSetupAccountType.java @@ -109,7 +109,8 @@ public class AccountSetupAccountType extends Activity implements OnClickListener /** * The user has selected an exchange account type. Try to put together a URI using the entered - * email address. Also set the mail delete policy here, because there is no UI (for exchange). + * email address. Also set the mail delete policy here, because there is no UI (for exchange), + * and switch the default sync interval to "push". */ private void onExchange() { try { @@ -125,6 +126,7 @@ public class AccountSetupAccountType extends Activity implements OnClickListener } // TODO: Confirm correct delete policy for exchange mAccount.setDeletePolicy(Account.DELETE_POLICY_ON_DELETE); + mAccount.setAutomaticCheckIntervalMinutes(Account.CHECK_INTERVAL_PUSH); AccountSetupExchange.actionIncomingSettings(this, mAccount, mMakeDefault); finish(); } diff --git a/src/com/android/email/activity/setup/AccountSetupOptions.java b/src/com/android/email/activity/setup/AccountSetupOptions.java index 9facf5353..a82877da9 100644 --- a/src/com/android/email/activity/setup/AccountSetupOptions.java +++ b/src/com/android/email/activity/setup/AccountSetupOptions.java @@ -20,6 +20,7 @@ import com.android.email.Account; import com.android.email.Email; import com.android.email.Preferences; import com.android.email.R; +import com.android.email.mail.Store; import android.app.Activity; import android.content.Intent; @@ -61,24 +62,29 @@ public class AccountSetupOptions extends Activity implements OnClickListener { findViewById(R.id.next).setOnClickListener(this); - // NOTE: If you change these values, confirm that the new intervals exist in arrays.xml - // NOTE: It would be cleaner if the numeric values were obtained from the - // account_settings_check_frequency_values, array, so the options could be controlled - // entirely via XML. - SpinnerOption checkFrequencies[] = { - new SpinnerOption(-1, - getString(R.string.account_setup_options_mail_check_frequency_never)), - new SpinnerOption(5, - getString(R.string.account_setup_options_mail_check_frequency_5min)), - new SpinnerOption(10, - getString(R.string.account_setup_options_mail_check_frequency_10min)), - new SpinnerOption(15, - getString(R.string.account_setup_options_mail_check_frequency_15min)), - new SpinnerOption(30, - getString(R.string.account_setup_options_mail_check_frequency_30min)), - new SpinnerOption(60, - getString(R.string.account_setup_options_mail_check_frequency_1hour)), - }; + mAccount = (Account)getIntent().getSerializableExtra(EXTRA_ACCOUNT); + boolean makeDefault = getIntent().getBooleanExtra(EXTRA_MAKE_DEFAULT, false); + + // Generate spinner entries using XML arrays used by the preferences + int frequencyValuesId; + int frequencyEntriesId; + Store.StoreInfo info = Store.StoreInfo.getStoreInfo(mAccount.getStoreUri(), this); + if (info.mPushSupported) { + frequencyValuesId = R.array.account_settings_check_frequency_values_push; + frequencyEntriesId = R.array.account_settings_check_frequency_entries_push; + } else { + frequencyValuesId = R.array.account_settings_check_frequency_values; + frequencyEntriesId = R.array.account_settings_check_frequency_entries; + } + CharSequence[] frequencyValues = getResources().getTextArray(frequencyValuesId); + CharSequence[] frequencyEntries = getResources().getTextArray(frequencyEntriesId); + + // Now create the array used by the Spinner + SpinnerOption[] checkFrequencies = new SpinnerOption[frequencyEntries.length]; + for (int i = 0; i < frequencyEntries.length; i++) { + checkFrequencies[i] = new SpinnerOption( + Integer.valueOf(frequencyValues[i].toString()), frequencyEntries[i].toString()); + } ArrayAdapter checkFrequenciesAdapter = new ArrayAdapter(this, android.R.layout.simple_spinner_item, checkFrequencies); @@ -86,9 +92,6 @@ public class AccountSetupOptions extends Activity implements OnClickListener { .setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); mCheckFrequencyView.setAdapter(checkFrequenciesAdapter); - mAccount = (Account)getIntent().getSerializableExtra(EXTRA_ACCOUNT); - boolean makeDefault = getIntent().getBooleanExtra(EXTRA_MAKE_DEFAULT, false); - if (mAccount.equals(Preferences.getPreferences(this).getDefaultAccount()) || makeDefault) { mDefaultView.setChecked(true); } diff --git a/src/com/android/email/mail/Store.java b/src/com/android/email/mail/Store.java index f76639934..f32bf871a 100644 --- a/src/com/android/email/mail/Store.java +++ b/src/com/android/email/mail/Store.java @@ -85,33 +85,47 @@ public abstract class Store { } /** - * Find Store implementation object consulting with stores.xml file. + * Look up descriptive information about a particular type of store. */ - private static Store findStore(int resourceId, String uri, Context context) - throws MessagingException { - Store store = null; - try { - XmlResourceParser xml = context.getResources().getXml(resourceId); - int xmlEventType; - // walk through stores.xml file. - while ((xmlEventType = xml.next()) != XmlResourceParser.END_DOCUMENT) { - if (xmlEventType == XmlResourceParser.START_TAG && - "store".equals(xml.getName())) { - String scheme = xml.getAttributeValue(null, "scheme"); - if (uri.startsWith(scheme)) { - // found store entry whose scheme is matched with uri. - // then load store class. - String className = xml.getAttributeValue(null, "class"); - store = instantiateStore(className, uri, context); + public static class StoreInfo { + public String mScheme; + public String mClassName; + public boolean mPushSupported = false; + + public static StoreInfo getStoreInfo(String scheme, Context context) { + StoreInfo result = getStoreInfo(R.xml.stores_product, scheme, context); + if (result == null) { + result = getStoreInfo(R.xml.stores, scheme, context); + } + return result; + } + + public static StoreInfo getStoreInfo(int resourceId, String scheme, Context context) { + try { + XmlResourceParser xml = context.getResources().getXml(resourceId); + int xmlEventType; + // walk through stores.xml file. + while ((xmlEventType = xml.next()) != XmlResourceParser.END_DOCUMENT) { + if (xmlEventType == XmlResourceParser.START_TAG && + "store".equals(xml.getName())) { + String xmlScheme = xml.getAttributeValue(null, "scheme"); + if (scheme.startsWith(xmlScheme)) { + StoreInfo result = new StoreInfo(); + result.mScheme = scheme; + result.mClassName = xml.getAttributeValue(null, "class"); + result.mPushSupported = xml.getAttributeBooleanValue( + null, "push", false); + return result; + } } } + } catch (XmlPullParserException e) { + // ignore + } catch (IOException e) { + // ignore } - } catch (XmlPullParserException e) { - // ignore - } catch (IOException e) { - // ignore + return null; } - return store; } /** @@ -139,9 +153,9 @@ public abstract class Store { throws MessagingException { Store store = mStores.get(uri); if (store == null) { - store = findStore(R.xml.stores_product, uri, context); - if (store == null) { - store = findStore(R.xml.stores, uri, context); + StoreInfo info = StoreInfo.getStoreInfo(uri, context); + if (info != null) { + store = instantiateStore(info.mClassName, uri, context); } if (store != null) { diff --git a/src/com/android/email/service/MailService.java b/src/com/android/email/service/MailService.java index b4f6a1046..9cd8c0169 100644 --- a/src/com/android/email/service/MailService.java +++ b/src/com/android/email/service/MailService.java @@ -86,7 +86,7 @@ public class MailService extends Service { // and make a single call to controller.checkMail(). ArrayList accountsToCheck = new ArrayList(); for (Account account : Preferences.getPreferences(this).getAccounts()) { - if (account.getAutomaticCheckIntervalMinutes() != -1) { + if (account.getAutomaticCheckIntervalMinutes() > 0) { accountsToCheck.add(account); } } diff --git a/tests/src/com/android/email/activity/setup/AccountSettingsTests.java b/tests/src/com/android/email/activity/setup/AccountSettingsTests.java new file mode 100644 index 000000000..48caba0a6 --- /dev/null +++ b/tests/src/com/android/email/activity/setup/AccountSettingsTests.java @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2009 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.setup; + +import com.android.email.Account; +import com.android.email.mail.Store; + +import android.content.Intent; +import android.preference.ListPreference; +import android.test.ActivityInstrumentationTestCase2; +import android.test.suitebuilder.annotation.MediumTest; + +/** + * Tests of basic UI logic in the AccountSettings screen. + */ +@MediumTest +public class AccountSettingsTests extends ActivityInstrumentationTestCase2 { + + private AccountSettings mActivity; + private ListPreference mCheckFrequency; + + private static final String PREFERENCE_FREQUENCY = "account_check_frequency"; + + public AccountSettingsTests() { + super("com.android.email", AccountSettings.class); + } + + /** + * Test that POP accounts aren't displayed with a push option + */ + public void testPushOptionPOP() { + Intent i = getTestIntent("Name", "pop3://user:password@server.com"); + this.setActivityIntent(i); + + getActivityAndFields(); + + boolean hasPush = frequencySpinnerHasValue(Account.CHECK_INTERVAL_PUSH); + assertFalse(hasPush); + } + + /** + * Test that IMAP accounts aren't displayed with a push option + */ + public void testPushOptionIMAP() { + Intent i = getTestIntent("Name", "imap://user:password@server.com"); + this.setActivityIntent(i); + + getActivityAndFields(); + + boolean hasPush = frequencySpinnerHasValue(Account.CHECK_INTERVAL_PUSH); + assertFalse(hasPush); + } + + /** + * Test that EAS accounts are displayed with a push option + */ + public void testPushOptionEAS() { + // This test should only be run if EAS is supported + if (Store.StoreInfo.getStoreInfo("eas", this.getInstrumentation().getTargetContext()) + == null) { + return; + } + + Intent i = getTestIntent("Name", "eas://user:password@server.com"); + this.setActivityIntent(i); + + getActivityAndFields(); + + boolean hasPush = frequencySpinnerHasValue(Account.CHECK_INTERVAL_PUSH); + assertTrue(hasPush); + } + + /** + * Get the activity (which causes it to be started, using our intent) and get the UI fields + */ + private void getActivityAndFields() { + mActivity = getActivity(); + mCheckFrequency = (ListPreference) mActivity.findPreference(PREFERENCE_FREQUENCY); + } + + /** + * Test the frequency values list for a particular value + */ + private boolean frequencySpinnerHasValue(int value) { + CharSequence[] values = mCheckFrequency.getEntryValues(); + for (CharSequence listValue : values) { + if (listValue != null && Integer.parseInt(listValue.toString()) == value) { + return true; + } + } + return false; + } + + /** + * Create an intent with the Account in it + */ + private Intent getTestIntent(String name, String storeUri) { + Account account = new Account(this.getInstrumentation().getTargetContext()); + account.setName(name); + account.setStoreUri(storeUri); + Intent i = new Intent(Intent.ACTION_MAIN); + i.putExtra("account", account); // AccountSetupNames.EXTRA_ACCOUNT == "account" + return i; + } + +} diff --git a/tests/src/com/android/email/activity/setup/AccountSetupOptionsTests.java b/tests/src/com/android/email/activity/setup/AccountSetupOptionsTests.java new file mode 100644 index 000000000..44c62b157 --- /dev/null +++ b/tests/src/com/android/email/activity/setup/AccountSetupOptionsTests.java @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2009 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.setup; + +import com.android.email.Account; +import com.android.email.R; +import com.android.email.mail.Store; + +import android.content.Intent; +import android.test.ActivityInstrumentationTestCase2; +import android.test.suitebuilder.annotation.MediumTest; +import android.widget.Spinner; +import android.widget.SpinnerAdapter; + +/** + * Tests of basic UI logic in the AccountSetupOptions screen. + */ +@MediumTest +public class AccountSetupOptionsTests + extends ActivityInstrumentationTestCase2 { + + private AccountSetupOptions mActivity; + private Spinner mCheckFrequencyView; + + public AccountSetupOptionsTests() { + super("com.android.email", AccountSetupOptions.class); + } + + /** + * Test that POP accounts aren't displayed with a push option + */ + public void testPushOptionPOP() { + Intent i = getTestIntent("Name", "pop3://user:password@server.com"); + this.setActivityIntent(i); + + getActivityAndFields(); + + boolean hasPush = frequencySpinnerHasValue(Account.CHECK_INTERVAL_PUSH); + assertFalse(hasPush); + } + + /** + * Test that IMAP accounts aren't displayed with a push option + */ + public void testPushOptionIMAP() { + Intent i = getTestIntent("Name", "imap://user:password@server.com"); + this.setActivityIntent(i); + + getActivityAndFields(); + + boolean hasPush = frequencySpinnerHasValue(Account.CHECK_INTERVAL_PUSH); + assertFalse(hasPush); + } + + /** + * Test that EAS accounts are displayed with a push option + */ + public void testPushOptionEAS() { + // This test should only be run if EAS is supported + if (Store.StoreInfo.getStoreInfo("eas", this.getInstrumentation().getTargetContext()) + == null) { + return; + } + + Intent i = getTestIntent("Name", "eas://user:password@server.com"); + this.setActivityIntent(i); + + getActivityAndFields(); + + boolean hasPush = frequencySpinnerHasValue(Account.CHECK_INTERVAL_PUSH); + assertTrue(hasPush); + } + + /** + * Get the activity (which causes it to be started, using our intent) and get the UI fields + */ + private void getActivityAndFields() { + mActivity = getActivity(); + mCheckFrequencyView = (Spinner) mActivity.findViewById(R.id.account_check_frequency); + } + + /** + * Test the frequency values list for a particular value + */ + private boolean frequencySpinnerHasValue(int value) { + SpinnerAdapter sa = mCheckFrequencyView.getAdapter(); + + for (int i = 0; i < sa.getCount(); ++i) { + SpinnerOption so = (SpinnerOption) sa.getItem(i); + if (so != null && ((Integer)so.value).intValue() == value) { + return true; + } + } + return false; + } + + /** + * Create an intent with the Account in it + */ + private Intent getTestIntent(String name, String storeUri) { + Account account = new Account(this.getInstrumentation().getTargetContext()); + account.setName(name); + account.setStoreUri(storeUri); + Intent i = new Intent(Intent.ACTION_MAIN); + i.putExtra("account", account); // AccountSetupNames.EXTRA_ACCOUNT == "account" + return i; + } + +} diff --git a/tests/src/com/android/email/mail/StoreTests.java b/tests/src/com/android/email/mail/StoreTests.java new file mode 100644 index 000000000..9df89f4e0 --- /dev/null +++ b/tests/src/com/android/email/mail/StoreTests.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2009 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.mail; + +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.MediumTest; + +/** + * Tests of StoreInfo & Store lookup in the Store abstract class + */ +@MediumTest +public class StoreTests extends AndroidTestCase { + + /** + * Test StoreInfo & Store lookup for POP accounts + */ + public void testStoreLookupPOP() throws MessagingException { + final String storeUri = "pop3://user:password@server.com"; + Store.StoreInfo info = Store.StoreInfo.getStoreInfo(storeUri, getContext()); + + assertNotNull("storeInfo null", info); + assertNotNull("scheme null", info.mScheme); + assertNotNull("classname null", info.mClassName); + assertFalse(info.mPushSupported); + + // This will throw MessagingException if the result would have been null + Store store = Store.getInstance(storeUri, getContext()); + } + + /** + * Test StoreInfo & Store lookup for IMAP accounts + */ + public void testStoreLookupIMAP() throws MessagingException { + final String storeUri = "imap://user:password@server.com"; + Store.StoreInfo info = Store.StoreInfo.getStoreInfo(storeUri, getContext()); + + assertNotNull("storeInfo null", info); + assertNotNull("scheme null", info.mScheme); + assertNotNull("classname null", info.mClassName); + assertFalse(info.mPushSupported); + + // This will throw MessagingException if the result would have been null + Store store = Store.getInstance(storeUri, getContext()); + } + + /** + * Test StoreInfo & Store lookup for EAS accounts + */ + public void testStoreLookupEAS() throws MessagingException { + final String storeUri = "eas://user:password@server.com"; + Store.StoreInfo info = Store.StoreInfo.getStoreInfo(storeUri, getContext()); + if (info != null) { + assertNotNull("scheme null", info.mScheme); + assertNotNull("classname null", info.mClassName); + assertTrue(info.mPushSupported); + + // This will throw MessagingException if the result would have been null + Store store = Store.getInstance(storeUri, getContext()); + } else { + try { + Store store = Store.getInstance(storeUri, getContext()); + fail("MessagingException expected when EAS not supported"); + } catch (MessagingException me) { + // expected - fall through + } + } + } + + /** + * Test StoreInfo & Store lookup for unknown accounts + */ + public void testStoreLookupUnknown() { + final String storeUri = "bogus-scheme://user:password@server.com"; + Store.StoreInfo info = Store.StoreInfo.getStoreInfo(storeUri, getContext()); + assertNull(info); + + try { + Store store = Store.getInstance(storeUri, getContext()); + fail("MessagingException expected from bogus URI scheme"); + } catch (MessagingException me) { + // expected - fall through + } + } + +}