diff --git a/src/com/android/email/activity/setup/AccountServerBaseFragment.java b/src/com/android/email/activity/setup/AccountServerBaseFragment.java index 38b67bfc7..8daedb1cd 100644 --- a/src/com/android/email/activity/setup/AccountServerBaseFragment.java +++ b/src/com/android/email/activity/setup/AccountServerBaseFragment.java @@ -31,6 +31,9 @@ import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; +import java.net.URI; +import java.net.URISyntaxException; + /** * Common base class for server settings fragments, so they can be more easily manipulated by * AccountSettingsXL. Provides the following common functionality: @@ -42,18 +45,22 @@ import android.widget.Button; public abstract class AccountServerBaseFragment extends Fragment implements AccountCheckSettingsFragment.Callbacks, OnClickListener { + public static Bundle sSetupModeArgs = null; + protected static URI sDefaultUri; + private final static String BUNDLE_KEY_SETTINGS = "AccountServerBaseFragment.settings"; protected Context mContext; protected Callback mCallback = EmptyCallback.INSTANCE; protected boolean mSettingsMode; + // The URI that represents this account's currently saved settings + protected URI mLoadedUri; + // This is null in the setup wizard screens, and non-null in AccountSettings mode public Button mProceedButton; // This is used to debounce multiple clicks on the proceed button (which does async work) public boolean mProceedButtonPressed; - public static Bundle sSetupModeArgs = null; - /** * Callback interface that owning activities must provide */ @@ -99,6 +106,16 @@ public abstract class AccountServerBaseFragment extends Fragment return sSetupModeArgs; } + public AccountServerBaseFragment() { + if (sDefaultUri == null) { + try { + sDefaultUri = new URI(""); + } catch (URISyntaxException ignore) { + // ignore; will never happen + } + } + } + /** * At onCreate time, read the fragment arguments */ @@ -272,6 +289,21 @@ public abstract class AccountServerBaseFragment extends Fragment throw new IllegalStateException(); } + /** + * Returns whether or not any settings have changed. + */ + public boolean haveSettingsChanged() { + URI newUri = null; + + try { + newUri = getUri(); + } catch (URISyntaxException ignore) { + // ignore + } + + return (mLoadedUri == null) || !mLoadedUri.equals(newUri); + } + /** * Save settings after "OK" result from checker. Concrete classes must implement. * This is called from a worker thread and is allowed to perform DB operations. @@ -288,4 +320,7 @@ public abstract class AccountServerBaseFragment extends Fragment * Respond to a click of the "Next" button. Concrete classes must implement. */ public abstract void onNext(); + + protected abstract URI getUri() throws URISyntaxException; + } diff --git a/src/com/android/email/activity/setup/AccountSettingsXL.java b/src/com/android/email/activity/setup/AccountSettingsXL.java index ef1a01845..f2477069d 100644 --- a/src/com/android/email/activity/setup/AccountSettingsXL.java +++ b/src/com/android/email/activity/setup/AccountSettingsXL.java @@ -66,7 +66,6 @@ import java.util.List; * dealing with accounts being added/deleted and triggering the header reload. */ public class AccountSettingsXL extends PreferenceActivity { - // Intent extras for our internal activity launch /* package */ static final String EXTRA_ACCOUNT_ID = "AccountSettingsXL.account_id"; private static final String EXTRA_ENABLE_DEBUG = "AccountSettingsXL.enable_debug"; @@ -243,8 +242,8 @@ public class AccountSettingsXL extends PreferenceActivity { } /** - * TODO: Any time we exit via this pathway, and we are showing a server settings fragment, - * we should put up the exit-save-changes dialog. This will work for the following cases: + * Any time we exit via this pathway, and we are showing a server settings fragment, + * we put up the exit-save-changes dialog. This will work for the following cases: * Cancel button * Back button * Up arrow in application icon @@ -254,6 +253,15 @@ public class AccountSettingsXL extends PreferenceActivity { */ @Override public void onBackPressed() { + if (mCurrentFragment instanceof AccountServerBaseFragment) { + boolean changed = ((AccountServerBaseFragment)mCurrentFragment).haveSettingsChanged(); + if (changed) { + UnsavedChangesDialogFragment dialogFragment = + UnsavedChangesDialogFragment.newInstanceForBack(); + dialogFragment.show(getFragmentManager(), UnsavedChangesDialogFragment.TAG); + return; // Prevent "back" from being handled + } + } super.onBackPressed(); } @@ -444,12 +452,13 @@ public class AccountSettingsXL extends PreferenceActivity { public void onHeaderClick(Header header, int position) { // special case when exiting the server settings fragments if (mCurrentFragment instanceof AccountServerBaseFragment) { - if (position != mCurrentHeaderPosition) { + boolean changed = ((AccountServerBaseFragment)mCurrentFragment).haveSettingsChanged(); + if (changed) { UnsavedChangesDialogFragment dialogFragment = - UnsavedChangesDialogFragment.newInstance(position); + UnsavedChangesDialogFragment.newInstanceForHeader(position); dialogFragment.show(getFragmentManager(), UnsavedChangesDialogFragment.TAG); + return; } - return; } // Secret keys: Click 10x to enable debug settings @@ -472,10 +481,23 @@ public class AccountSettingsXL extends PreferenceActivity { * in {@link #onHeaderClick(Header, int)}. Called after we interrupted a header switch * with a dialog, and the user OK'd it. */ - private void forceSwitchHeader(int newPosition) { - mCurrentHeaderPosition = newPosition; - Header header = mGeneratedHeaders.get(newPosition); - switchToHeader(header.fragment, header.fragmentArguments); + private void forceSwitchHeader(int position) { + mCurrentHeaderPosition = position; + // Clear the current fragment; we're navigating away + mCurrentFragment = null; + // Ensure the UI visually shows the correct header selected + setSelection(position); + Header header = mGeneratedHeaders.get(position); + switchToHeader(header); + } + + /** + * Forcefully go backward in the stack. This may potentially discard unsaved settings. + */ + private void forceBack() { + // Clear the current fragment; we're navigating away + mCurrentFragment = null; + onBackPressed(); } /** @@ -539,6 +561,8 @@ public class AccountSettingsXL extends PreferenceActivity { */ public void onCheckSettingsComplete(int result, int setupMode) { if (result == AccountCheckSettingsFragment.CHECK_SETTINGS_OK) { + // Settings checked & saved; clear current fragment + mCurrentFragment = null; onBackPressed(); } } @@ -653,27 +677,47 @@ public class AccountSettingsXL extends PreferenceActivity { /** * Dialog fragment to show "exit with unsaved changes?" dialog */ - public static class UnsavedChangesDialogFragment extends DialogFragment { + /* package */ static class UnsavedChangesDialogFragment extends DialogFragment { private final static String TAG = "UnsavedChangesDialogFragment"; // Argument bundle keys - private final static String BUNDLE_KEY_NEW_HEADER = "UnsavedChangesDialogFragment.Header"; + private final static String BUNDLE_KEY_HEADER = "UnsavedChangesDialogFragment.Header"; + private final static String BUNDLE_KEY_BACK = "UnsavedChangesDialogFragment.Back"; /** - * Create the dialog with parameters + * Creates a save changes dialog when the user selects a new header + * @param position The new header index to make active if the user accepts the dialog. This + * must be a valid header index although there is no error checking. */ - public static UnsavedChangesDialogFragment newInstance(int newPosition) { + public static UnsavedChangesDialogFragment newInstanceForHeader(int position) { UnsavedChangesDialogFragment f = new UnsavedChangesDialogFragment(); Bundle b = new Bundle(); - b.putInt(BUNDLE_KEY_NEW_HEADER, newPosition); + b.putInt(BUNDLE_KEY_HEADER, position); f.setArguments(b); return f; } + /** + * Creates a save changes dialog when the user navigates "back". + * {@link AccountSettingsXL#onBackPressed()} defines in which case this may be triggered. + */ + public static UnsavedChangesDialogFragment newInstanceForBack() { + UnsavedChangesDialogFragment f = new UnsavedChangesDialogFragment(); + Bundle b = new Bundle(); + b.putBoolean(BUNDLE_KEY_BACK, true); + f.setArguments(b); + return f; + } + + // Force usage of newInstance() + private UnsavedChangesDialogFragment() { + } + @Override public Dialog onCreateDialog(Bundle savedInstanceState) { final AccountSettingsXL activity = (AccountSettingsXL) getActivity(); - final int newPosition = getArguments().getInt(BUNDLE_KEY_NEW_HEADER); + final int position = getArguments().getInt(BUNDLE_KEY_HEADER); + final boolean isBack = getArguments().getBoolean(BUNDLE_KEY_BACK); return new AlertDialog.Builder(activity) .setIcon(android.R.drawable.ic_dialog_alert) @@ -683,13 +727,16 @@ public class AccountSettingsXL extends PreferenceActivity { R.string.okay_action, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { - activity.forceSwitchHeader(newPosition); + if (isBack) { + activity.forceBack(); + } else { + activity.forceSwitchHeader(position); + } dismiss(); } }) .setNegativeButton( - activity.getString(R.string.cancel_action), - null) + activity.getString(R.string.cancel_action), null) .create(); } } diff --git a/src/com/android/email/activity/setup/AccountSetupExchangeFragment.java b/src/com/android/email/activity/setup/AccountSetupExchangeFragment.java index 5c4654b1f..67b4c2da7 100644 --- a/src/com/android/email/activity/setup/AccountSetupExchangeFragment.java +++ b/src/com/android/email/activity/setup/AccountSetupExchangeFragment.java @@ -262,6 +262,12 @@ public class AccountSetupExchangeFragment extends AccountServerBaseFragment mTrustCertificatesView.setChecked(trustCertificates); showTrustCertificates(ssl); + try { + mLoadedUri = getUri(); + } catch (URISyntaxException ignore) { + // ignore; should not happen + } + mLoaded = true; return validateFields(); } @@ -347,7 +353,8 @@ public class AccountSetupExchangeFragment extends AccountServerBaseFragment * a problem with the user input. * @return a URI built from the account setup fields */ - /* package */ URI getUri() throws URISyntaxException { + @Override + protected URI getUri() throws URISyntaxException { boolean sslRequired = mSslSecurityView.isChecked(); boolean trustCertificates = mTrustCertificatesView.isChecked(); String scheme = (sslRequired) diff --git a/src/com/android/email/activity/setup/AccountSetupIncomingFragment.java b/src/com/android/email/activity/setup/AccountSetupIncomingFragment.java index 3859d19c4..f489ae4fa 100644 --- a/src/com/android/email/activity/setup/AccountSetupIncomingFragment.java +++ b/src/com/android/email/activity/setup/AccountSetupIncomingFragment.java @@ -78,6 +78,8 @@ public class AccountSetupIncomingFragment extends AccountServerBaseFragment { private Spinner mDeletePolicyView; private View mImapPathPrefixSectionView; private EditText mImapPathPrefixView; + // Delete policy as loaded from the device + private int mLoadedDeletePolicy; // Support for lifecycle private boolean mStarted; @@ -333,7 +335,8 @@ public class AccountSetupIncomingFragment extends AccountServerBaseFragment { } if (uri.getScheme().startsWith("pop3")) { - SpinnerOption.setSpinnerOptionValue(mDeletePolicyView, account.getDeletePolicy()); + mLoadedDeletePolicy = account.getDeletePolicy(); + SpinnerOption.setSpinnerOptionValue(mDeletePolicyView, mLoadedDeletePolicy); } else if (uri.getScheme().startsWith("imap")) { if (uri.getPath() != null && uri.getPath().length() > 0) { mImapPathPrefixView.setText(uri.getPath().substring(1)); @@ -363,6 +366,13 @@ public class AccountSetupIncomingFragment extends AccountServerBaseFragment { */ throw new Error(use); } + + try { + mLoadedUri = getUri(); + } catch (URISyntaxException ignore) { + // ignore; should not happen + } + mLoaded = true; validateFields(); } @@ -440,7 +450,8 @@ public class AccountSetupIncomingFragment extends AccountServerBaseFragment { * a problem with the user input. * @return a URI built from the account setup fields */ - /* package */ URI getUri() throws URISyntaxException { + @Override + protected URI getUri() throws URISyntaxException { int securityType = (Integer)((SpinnerOption)mSecurityTypeView.getSelectedItem()).value; String path = null; if (mAccountSchemes[securityType].startsWith("imap")) { @@ -485,4 +496,18 @@ public class AccountSetupIncomingFragment extends AccountServerBaseFragment { throw new Error(use); } } + + @Override + public boolean haveSettingsChanged() { + boolean deletePolicyChanged = false; + + // Only verify the delete policy if the control is visible (i.e. is a pop3 account) + if (mDeletePolicyView.getVisibility() == View.VISIBLE) { + int newDeletePolicy = + (Integer)((SpinnerOption)mDeletePolicyView.getSelectedItem()).value; + deletePolicyChanged = mLoadedDeletePolicy != newDeletePolicy; + } + + return deletePolicyChanged || super.haveSettingsChanged(); + } } diff --git a/src/com/android/email/activity/setup/AccountSetupOutgoingFragment.java b/src/com/android/email/activity/setup/AccountSetupOutgoingFragment.java index 044026181..f265c7d65 100644 --- a/src/com/android/email/activity/setup/AccountSetupOutgoingFragment.java +++ b/src/com/android/email/activity/setup/AccountSetupOutgoingFragment.java @@ -300,6 +300,13 @@ public class AccountSetupOutgoingFragment extends AccountServerBaseFragment */ throw new Error(use); } + + try { + mLoadedUri = getUri(); + } catch (URISyntaxException ignore) { + // ignore; should not happen + } + mLoaded = true; validateFields(); } @@ -368,7 +375,8 @@ public class AccountSetupOutgoingFragment extends AccountServerBaseFragment * a problem with the user input. * @return a URI built from the account setup fields */ - /* package */ URI getUri() throws URISyntaxException { + @Override + protected URI getUri() throws URISyntaxException { int securityType = (Integer)((SpinnerOption)mSecurityTypeView.getSelectedItem()).value; String userInfo = null; if (mRequireLoginView.isChecked()) {