| /* |
| * Copyright (C) 2010 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 android.app.ActionBar; |
| import android.app.Activity; |
| import android.app.AlertDialog; |
| import android.app.Dialog; |
| import android.app.DialogFragment; |
| import android.app.Fragment; |
| import android.content.Context; |
| import android.content.DialogInterface; |
| import android.content.Intent; |
| import android.content.res.Resources; |
| import android.database.ContentObserver; |
| import android.database.Cursor; |
| import android.net.Uri; |
| import android.os.AsyncTask; |
| import android.os.Bundle; |
| import android.preference.PreferenceActivity; |
| import android.text.SpannableString; |
| import android.text.method.LinkMovementMethod; |
| import android.text.util.Linkify; |
| import android.view.KeyEvent; |
| import android.view.Menu; |
| import android.view.MenuItem; |
| import android.widget.TextView; |
| |
| import com.android.email.R; |
| import com.android.email.activity.ActivityHelper; |
| import com.android.email.provider.EmailProvider; |
| import com.android.emailcommon.Logging; |
| import com.android.emailcommon.provider.Account; |
| import com.android.emailcommon.provider.EmailContent.AccountColumns; |
| import com.android.emailcommon.utility.IntentUtilities; |
| import com.android.emailcommon.utility.Utility; |
| import com.android.mail.providers.Folder; |
| import com.android.mail.providers.UIProvider.EditSettingsExtras; |
| import com.android.mail.ui.FeedbackEnabledActivity; |
| import com.android.mail.utils.LogUtils; |
| import com.android.mail.utils.Utils; |
| |
| import java.util.List; |
| |
| /** |
| * Handles account preferences, using multi-pane arrangement when possible. |
| * |
| * This activity uses the following fragments: |
| * AccountSettingsFragment |
| * Account{Incoming/Outgoing}Fragment |
| * AccountCheckSettingsFragment |
| * GeneralPreferences |
| * DebugFragment |
| * |
| * TODO: Delete account - on single-pane view (phone UX) the account list doesn't update properly |
| * TODO: Handle dynamic changes to the account list (exit if necessary). It probably makes |
| * sense to use a loader for the accounts list, because it would provide better support for |
| * dealing with accounts being added/deleted and triggering the header reload. |
| */ |
| public class AccountSettings extends PreferenceActivity implements FeedbackEnabledActivity, |
| SetupDataFragment.SetupDataContainer { |
| /* |
| * Intent to open account settings for account=1 |
| adb shell am start -a android.intent.action.EDIT \ |
| -d '"content://ui.email.android.com/settings?ACCOUNT_ID=1"' |
| */ |
| |
| // Intent extras for our internal activity launch |
| private static final String EXTRA_ENABLE_DEBUG = "AccountSettings.enable_debug"; |
| private static final String EXTRA_LOGIN_WARNING_FOR_ACCOUNT = "AccountSettings.for_account"; |
| private static final String EXTRA_LOGIN_WARNING_REASON_FOR_ACCOUNT = |
| "AccountSettings.for_account_reason"; |
| private static final String EXTRA_TITLE = "AccountSettings.title"; |
| public static final String EXTRA_NO_ACCOUNTS = "AccountSettings.no_account"; |
| |
| // Intent extras for launch directly from system account manager |
| // NOTE: This string must match the one in res/xml/account_preferences.xml |
| private static String INTENT_ACCOUNT_MANAGER_ENTRY; |
| // NOTE: This constant should eventually be defined in android.accounts.Constants |
| private static final String EXTRA_ACCOUNT_MANAGER_ACCOUNT = "account"; |
| |
| // Key for arguments bundle for QuickResponse editing |
| private static final String QUICK_RESPONSE_ACCOUNT_KEY = "account"; |
| |
| // Key codes used to open a debug settings fragment. |
| private static final int[] SECRET_KEY_CODES = { |
| KeyEvent.KEYCODE_D, KeyEvent.KEYCODE_E, KeyEvent.KEYCODE_B, KeyEvent.KEYCODE_U, |
| KeyEvent.KEYCODE_G |
| }; |
| private int mSecretKeyCodeIndex = 0; |
| |
| // Support for account-by-name lookup |
| private static final String SELECTION_ACCOUNT_EMAIL_ADDRESS = |
| AccountColumns.EMAIL_ADDRESS + "=?"; |
| |
| // When the user taps "Email Preferences" 10 times in a row, we'll enable the debug settings. |
| private int mNumGeneralHeaderClicked = 0; |
| |
| private long mRequestedAccountId; |
| private Header[] mAccountListHeaders; |
| private Header mAppPreferencesHeader; |
| /* package */ Fragment mCurrentFragment; |
| private long mDeletingAccountId = -1; |
| private boolean mShowDebugMenu; |
| private List<Header> mGeneratedHeaders; |
| private Uri mFeedbackUri; |
| private MenuItem mFeedbackMenuItem; |
| |
| private SetupDataFragment mSetupData; |
| |
| // Async Tasks |
| private LoadAccountListTask mLoadAccountListTask; |
| private GetAccountIdFromAccountTask mGetAccountIdFromAccountTask; |
| private ContentObserver mAccountObserver; |
| |
| // Specific callbacks used by settings fragments |
| private final AccountSettingsFragmentCallback mAccountSettingsFragmentCallback |
| = new AccountSettingsFragmentCallback(); |
| private final AccountServerSettingsFragmentCallback mAccountServerSettingsFragmentCallback |
| = new AccountServerSettingsFragmentCallback(); |
| |
| /** |
| * Display (and edit) settings for a specific account, or -1 for any/all accounts |
| */ |
| public static void actionSettings(Activity fromActivity, long accountId) { |
| fromActivity.startActivity(createAccountSettingsIntent(accountId, null, null)); |
| } |
| |
| /** |
| * Create and return an intent to display (and edit) settings for a specific account, or -1 |
| * for any/all accounts. If an account name string is provided, a warning dialog will be |
| * displayed as well. |
| */ |
| public static Intent createAccountSettingsIntent(long accountId, |
| String loginWarningAccountName, String loginWarningReason) { |
| final Uri.Builder b = IntentUtilities.createActivityIntentUrlBuilder( |
| IntentUtilities.PATH_SETTINGS); |
| IntentUtilities.setAccountId(b, accountId); |
| final Intent i = new Intent(Intent.ACTION_EDIT, b.build()); |
| if (loginWarningAccountName != null) { |
| i.putExtra(EXTRA_LOGIN_WARNING_FOR_ACCOUNT, loginWarningAccountName); |
| } |
| if (loginWarningReason != null) { |
| i.putExtra(EXTRA_LOGIN_WARNING_REASON_FOR_ACCOUNT, loginWarningReason); |
| } |
| return i; |
| } |
| |
| @Override |
| public Intent getIntent() { |
| final Intent intent = super.getIntent(); |
| final long accountId = IntentUtilities.getAccountIdFromIntent(intent); |
| if (accountId < 0) { |
| return intent; |
| } |
| Intent modIntent = new Intent(intent); |
| modIntent.putExtra(EXTRA_SHOW_FRAGMENT, AccountSettingsFragment.class.getCanonicalName()); |
| modIntent.putExtra( |
| EXTRA_SHOW_FRAGMENT_ARGUMENTS, |
| AccountSettingsFragment.buildArguments( |
| accountId, IntentUtilities.getAccountNameFromIntent(intent))); |
| modIntent.putExtra(EXTRA_NO_HEADERS, true); |
| return modIntent; |
| } |
| |
| |
| /** |
| * Launch generic settings and pre-enable the debug preferences |
| */ |
| public static void actionSettingsWithDebug(Context fromContext) { |
| final Intent i = new Intent(fromContext, AccountSettings.class); |
| i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); |
| i.putExtra(EXTRA_ENABLE_DEBUG, true); |
| fromContext.startActivity(i); |
| } |
| |
| @Override |
| public void onCreate(Bundle savedInstanceState) { |
| super.onCreate(savedInstanceState); |
| ActivityHelper.debugSetWindowFlags(this); |
| |
| final Intent i = getIntent(); |
| if (savedInstanceState == null) { |
| // If we are not restarting from a previous instance, we need to |
| // figure out the initial prefs to show. (Otherwise, we want to |
| // continue showing whatever the user last selected.) |
| if (INTENT_ACCOUNT_MANAGER_ENTRY == null) { |
| INTENT_ACCOUNT_MANAGER_ENTRY = getString(R.string.intent_account_manager_entry); |
| } |
| if (INTENT_ACCOUNT_MANAGER_ENTRY.equals(i.getAction())) { |
| // This case occurs if we're changing account settings from Settings -> Accounts |
| mGetAccountIdFromAccountTask = |
| (GetAccountIdFromAccountTask) new GetAccountIdFromAccountTask() |
| .executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, i); |
| } else if (i.hasExtra(EditSettingsExtras.EXTRA_FOLDER)) { |
| launchMailboxSettings(i); |
| return; |
| } else if (i.hasExtra(EXTRA_NO_ACCOUNTS)) { |
| AccountSetupBasics.actionNewAccountWithResult(this); |
| finish(); |
| return; |
| } else { |
| // Otherwise, we're called from within the Email app and look for our extras |
| mRequestedAccountId = IntentUtilities.getAccountIdFromIntent(i); |
| String loginWarningAccount = i.getStringExtra(EXTRA_LOGIN_WARNING_FOR_ACCOUNT); |
| String loginWarningReason = |
| i.getStringExtra(EXTRA_LOGIN_WARNING_REASON_FOR_ACCOUNT); |
| if (loginWarningAccount != null) { |
| // Show dialog (first time only - don't re-show on a rotation) |
| LoginWarningDialog dialog = |
| LoginWarningDialog.newInstance(loginWarningAccount, loginWarningReason); |
| dialog.show(getFragmentManager(), "loginwarning"); |
| } |
| } |
| } else { |
| mSetupData = savedInstanceState.getParcelable(SetupDataFragment.EXTRA_SETUP_DATA); |
| } |
| mShowDebugMenu = i.getBooleanExtra(EXTRA_ENABLE_DEBUG, false); |
| |
| final String title = i.getStringExtra(EXTRA_TITLE); |
| if (title != null) { |
| setTitle(title); |
| } |
| |
| getActionBar().setDisplayOptions( |
| ActionBar.DISPLAY_HOME_AS_UP, ActionBar.DISPLAY_HOME_AS_UP); |
| |
| mAccountObserver = new ContentObserver(Utility.getMainThreadHandler()) { |
| @Override |
| public void onChange(boolean selfChange) { |
| updateAccounts(); |
| } |
| }; |
| |
| mFeedbackUri = Utils.getValidUri(getString(R.string.email_feedback_uri)); |
| } |
| |
| @Override |
| protected void onSaveInstanceState(Bundle outState) { |
| super.onSaveInstanceState( |
| outState); |
| // TODO: use the fragment manager to persist this |
| outState.putParcelable(SetupDataFragment.EXTRA_SETUP_DATA, mSetupData); |
| } |
| |
| @Override |
| public void onResume() { |
| super.onResume(); |
| getContentResolver().registerContentObserver(Account.NOTIFIER_URI, true, mAccountObserver); |
| updateAccounts(); |
| } |
| |
| @Override |
| public void onPause() { |
| super.onPause(); |
| getContentResolver().unregisterContentObserver(mAccountObserver); |
| } |
| |
| @Override |
| protected void onDestroy() { |
| super.onDestroy(); |
| Utility.cancelTaskInterrupt(mLoadAccountListTask); |
| mLoadAccountListTask = null; |
| Utility.cancelTaskInterrupt(mGetAccountIdFromAccountTask); |
| mGetAccountIdFromAccountTask = null; |
| } |
| |
| /** |
| * Listen for secret sequence and, if heard, enable debug menu |
| */ |
| @Override |
| public boolean onKeyDown(int keyCode, KeyEvent event) { |
| if (event.getKeyCode() == SECRET_KEY_CODES[mSecretKeyCodeIndex]) { |
| mSecretKeyCodeIndex++; |
| if (mSecretKeyCodeIndex == SECRET_KEY_CODES.length) { |
| mSecretKeyCodeIndex = 0; |
| enableDebugMenu(); |
| } |
| } else { |
| mSecretKeyCodeIndex = 0; |
| } |
| return super.onKeyDown(keyCode, event); |
| } |
| |
| @Override |
| public boolean onCreateOptionsMenu(Menu menu) { |
| super.onCreateOptionsMenu(menu); |
| getMenuInflater().inflate(R.menu.settings_menu, menu); |
| |
| mFeedbackMenuItem = menu.findItem(R.id.feedback_menu_item); |
| return true; |
| } |
| |
| @Override |
| public boolean onPrepareOptionsMenu(Menu menu) { |
| super.onPrepareOptionsMenu(menu); |
| |
| if (mFeedbackMenuItem != null) { |
| // We only want to enable the feedback menu item, if there is a valid feedback uri |
| mFeedbackMenuItem.setVisible(!Uri.EMPTY.equals(mFeedbackUri)); |
| } |
| return true; |
| } |
| |
| |
| @Override |
| public boolean onOptionsItemSelected(MenuItem item) { |
| switch (item.getItemId()) { |
| case android.R.id.home: |
| // The app icon on the action bar is pressed. Just emulate a back press. |
| // TODO: this should navigate to the main screen, even if a sub-setting is open. |
| // But we shouldn't just finish(), as we want to show "discard changes?" dialog |
| // when necessary. |
| onBackPressed(); |
| break; |
| case R.id.add_new_account: |
| onAddNewAccount(); |
| break; |
| case R.id.feedback_menu_item: |
| Utils.sendFeedback(this, mFeedbackUri, false /* reportingProblem */); |
| break; |
| default: |
| return super.onOptionsItemSelected(item); |
| } |
| return true; |
| } |
| |
| @Override |
| public Intent onBuildStartFragmentIntent(String fragmentName, Bundle args, |
| int titleRes, int shortTitleRes) { |
| final Intent intent = super.onBuildStartFragmentIntent( |
| fragmentName, args, titleRes, shortTitleRes); |
| |
| // When opening a sub-settings page (e.g. account specific page), see if we want to modify |
| // the activity title. |
| String title = AccountSettingsFragment.getTitleFromArgs(args); |
| if ((titleRes == 0) && (title != null)) { |
| intent.putExtra(EXTRA_TITLE, title); |
| } |
| return intent; |
| } |
| |
| /** |
| * 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 |
| * It will *not* apply in the following cases: |
| * Click the parent breadcrumb - need to find a hook for this |
| * Click in the header list (e.g. another account) - handled elsewhere |
| */ |
| @Override |
| public void onBackPressed() { |
| if (mCurrentFragment instanceof AccountServerBaseFragment) { |
| if (((AccountServerBaseFragment) mCurrentFragment).haveSettingsChanged()) { |
| UnsavedChangesDialogFragment dialogFragment = |
| UnsavedChangesDialogFragment.newInstanceForBack(); |
| dialogFragment.show(getFragmentManager(), UnsavedChangesDialogFragment.TAG); |
| return; // Prevent "back" from being handled |
| } |
| } |
| super.onBackPressed(); |
| } |
| |
| private void launchMailboxSettings(Intent intent) { |
| final Folder folder = intent.getParcelableExtra(EditSettingsExtras.EXTRA_FOLDER); |
| |
| // TODO: determine from the account if we should navigate to the mailbox settings. |
| // See bug 6242668 |
| |
| // Get the mailbox id from the folder |
| final long mailboxId = |
| Long.parseLong(folder.folderUri.fullUri.getPathSegments().get(1)); |
| |
| MailboxSettings.start(this, mailboxId); |
| finish(); |
| } |
| |
| |
| private void enableDebugMenu() { |
| mShowDebugMenu = true; |
| invalidateHeaders(); |
| } |
| |
| private void onAddNewAccount() { |
| AccountSetupBasics.actionNewAccount(this); |
| } |
| |
| /** |
| * Start the async reload of the accounts list (if the headers are being displayed) |
| */ |
| private void updateAccounts() { |
| if (hasHeaders()) { |
| Utility.cancelTaskInterrupt(mLoadAccountListTask); |
| mLoadAccountListTask = (LoadAccountListTask) |
| new LoadAccountListTask().executeOnExecutor( |
| AsyncTask.THREAD_POOL_EXECUTOR, mDeletingAccountId); |
| } |
| } |
| |
| /** |
| * Write the current header (accounts) array into the one provided by the PreferenceActivity. |
| * Skip any headers that match mDeletingAccountId (this is a quick-hide algorithm while a |
| * background thread works on deleting the account). Also sets mRequestedAccountHeader if |
| * we find the requested account (by id). |
| */ |
| @Override |
| public void onBuildHeaders(List<Header> target) { |
| // Always add app preferences as first header |
| target.clear(); |
| target.add(getAppPreferencesHeader()); |
| |
| // Then add zero or more account headers as necessary |
| if (mAccountListHeaders != null) { |
| final int headerCount = mAccountListHeaders.length; |
| for (int index = 0; index < headerCount; index++) { |
| Header header = mAccountListHeaders[index]; |
| if (header != null && header.id != HEADER_ID_UNDEFINED) { |
| if (header.id != mDeletingAccountId) { |
| target.add(header); |
| if (header.id == mRequestedAccountId) { |
| mRequestedAccountId = -1; |
| } |
| } |
| } |
| } |
| } |
| |
| // finally, if debug header is enabled, show it |
| if (mShowDebugMenu) { |
| // setup lightweight header for debugging |
| final Header debugHeader = new Header(); |
| debugHeader.title = getText(R.string.debug_title); |
| debugHeader.summary = null; |
| debugHeader.iconRes = 0; |
| debugHeader.fragment = DebugFragment.class.getCanonicalName(); |
| debugHeader.fragmentArguments = null; |
| target.add(debugHeader); |
| } |
| |
| // Save for later use (see forceSwitch) |
| mGeneratedHeaders = target; |
| } |
| |
| /** |
| * Generate and return the first header, for app preferences |
| */ |
| private Header getAppPreferencesHeader() { |
| // Set up fixed header for general settings |
| if (mAppPreferencesHeader == null) { |
| mAppPreferencesHeader = new Header(); |
| mAppPreferencesHeader.title = getText(R.string.header_label_general_preferences); |
| mAppPreferencesHeader.summary = null; |
| mAppPreferencesHeader.iconRes = 0; |
| mAppPreferencesHeader.fragment = GeneralPreferences.class.getCanonicalName(); |
| mAppPreferencesHeader.fragmentArguments = null; |
| } |
| return mAppPreferencesHeader; |
| } |
| |
| /** |
| * This AsyncTask reads the accounts list and generates the headers. When the headers are |
| * ready, we'll trigger PreferenceActivity to refresh the account list with them. |
| * |
| * The array generated and stored in mAccountListHeaders may be sparse so any readers should |
| * check for and skip over null entries, and should not assume array length is # of accounts. |
| * |
| * TODO: Smaller projection |
| * TODO: Convert to Loader |
| * TODO: Write a test, including operation of deletingAccountId param |
| */ |
| private class LoadAccountListTask extends AsyncTask<Long, Void, Object[]> { |
| |
| @Override |
| protected Object[] doInBackground(Long... params) { |
| Header[] result = null; |
| Boolean deletingAccountFound = false; |
| final long deletingAccountId = params[0]; |
| |
| Cursor c = getContentResolver().query( |
| Account.CONTENT_URI, |
| Account.CONTENT_PROJECTION, null, null, null); |
| try { |
| int index = 0; |
| result = new Header[c.getCount()]; |
| |
| while (c.moveToNext()) { |
| final long accountId = c.getLong(Account.CONTENT_ID_COLUMN); |
| if (accountId == deletingAccountId) { |
| deletingAccountFound = true; |
| continue; |
| } |
| final String name = c.getString(Account.CONTENT_DISPLAY_NAME_COLUMN); |
| final String email = c.getString(Account.CONTENT_EMAIL_ADDRESS_COLUMN); |
| final Header newHeader = new Header(); |
| newHeader.id = accountId; |
| newHeader.title = name; |
| newHeader.summary = email; |
| newHeader.fragment = AccountSettingsFragment.class.getCanonicalName(); |
| newHeader.fragmentArguments = |
| AccountSettingsFragment.buildArguments(accountId, email); |
| |
| result[index++] = newHeader; |
| } |
| } finally { |
| if (c != null) { |
| c.close(); |
| } |
| } |
| return new Object[] { result, deletingAccountFound }; |
| } |
| |
| @Override |
| protected void onPostExecute(Object[] result) { |
| if (isCancelled() || result == null) return; |
| // Extract the results |
| final Header[] headers = (Header[]) result[0]; |
| final boolean deletingAccountFound = (Boolean) result[1]; |
| // report the settings |
| mAccountListHeaders = headers; |
| invalidateHeaders(); |
| if (!deletingAccountFound) { |
| mDeletingAccountId = -1; |
| } |
| } |
| } |
| |
| /** |
| * Called when the user selects an item in the header list. Handles save-data cases as needed |
| * |
| * @param header The header that was selected. |
| * @param position The header's position in the list. |
| */ |
| @Override |
| public void onHeaderClick(Header header, int position) { |
| // special case when exiting the server settings fragments |
| if ((mCurrentFragment instanceof AccountServerBaseFragment) |
| && (((AccountServerBaseFragment)mCurrentFragment).haveSettingsChanged())) { |
| UnsavedChangesDialogFragment dialogFragment = |
| UnsavedChangesDialogFragment.newInstanceForHeader(position); |
| dialogFragment.show(getFragmentManager(), UnsavedChangesDialogFragment.TAG); |
| return; |
| } |
| |
| // Secret keys: Click 10x to enable debug settings |
| if (position == 0) { |
| mNumGeneralHeaderClicked++; |
| if (mNumGeneralHeaderClicked == 10) { |
| enableDebugMenu(); |
| } |
| } else { |
| mNumGeneralHeaderClicked = 0; |
| } |
| |
| // Process header click normally |
| super.onHeaderClick(header, position); |
| } |
| |
| /** |
| * Switch to a specific header without checking for server settings fragments as done |
| * 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 position) { |
| // Clear the current fragment; we're navigating away |
| mCurrentFragment = null; |
| // Ensure the UI visually shows the correct header selected |
| setSelection(position); |
| switchToHeader(mGeneratedHeaders.get(position)); |
| } |
| |
| /** |
| * 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(); |
| } |
| |
| @Override |
| public void onAttachFragment(Fragment f) { |
| super.onAttachFragment(f); |
| |
| if (f instanceof AccountSettingsFragment) { |
| final AccountSettingsFragment asf = (AccountSettingsFragment) f; |
| asf.setCallback(mAccountSettingsFragmentCallback); |
| } else if (f instanceof AccountServerBaseFragment) { |
| final AccountServerBaseFragment asbf = (AccountServerBaseFragment) f; |
| asbf.setCallback(mAccountServerSettingsFragmentCallback); |
| } else { |
| // Possibly uninteresting fragment, such as a dialog. |
| return; |
| } |
| mCurrentFragment = f; |
| |
| // When we're changing fragments, enable/disable the add account button |
| invalidateOptionsMenu(); |
| } |
| |
| /** |
| * Callbacks for AccountSettingsFragment |
| */ |
| private class AccountSettingsFragmentCallback implements AccountSettingsFragment.Callback { |
| @Override |
| public void onSettingsChanged(Account account, String preference, Object value) { |
| AccountSettings.this.onSettingsChanged(account, preference, value); |
| } |
| @Override |
| public void onEditQuickResponses(com.android.mail.providers.Account account) { |
| AccountSettings.this.onEditQuickResponses(account); |
| } |
| @Override |
| public void onIncomingSettings(Account account) { |
| AccountSettings.this.onIncomingSettings(account); |
| } |
| @Override |
| public void onOutgoingSettings(Account account) { |
| AccountSettings.this.onOutgoingSettings(account); |
| } |
| @Override |
| public void abandonEdit() { |
| finish(); |
| } |
| } |
| |
| /** |
| * Callbacks for AccountServerSettingsFragmentCallback |
| */ |
| private class AccountServerSettingsFragmentCallback |
| implements AccountServerBaseFragment.Callback { |
| @Override |
| public void onEnableProceedButtons(boolean enable) { |
| // This is not used - it's a callback for the legacy activities |
| } |
| |
| @Override |
| public void onProceedNext(int checkMode, AccountServerBaseFragment target) { |
| AccountCheckSettingsFragment checkerFragment = |
| AccountCheckSettingsFragment.newInstance(checkMode, target); |
| startPreferenceFragment(checkerFragment, true); |
| } |
| |
| /** |
| * After verifying a new server configuration as OK, we return here and continue. This |
| * simply does a "back" to exit the settings screen. |
| */ |
| @Override |
| public void onCheckSettingsComplete(int result, SetupDataFragment setupData) { |
| if (result == AccountCheckSettingsFragment.CHECK_SETTINGS_OK) { |
| // Settings checked & saved; clear current fragment |
| mCurrentFragment = null; |
| onBackPressed(); |
| } |
| } |
| } |
| |
| /** |
| * Some of the settings have changed. Update internal state as necessary. |
| */ |
| public void onSettingsChanged(Account account, String preference, Object value) { |
| if (AccountSettingsFragment.PREFERENCE_DESCRIPTION.equals(preference)) { |
| for (Header header : mAccountListHeaders) { |
| if (header.id == account.mId) { |
| // Manually tweak the header title. We cannot rebuild the header list from |
| // an account cursor as the account database has not been saved yet. |
| header.title = value.toString(); |
| invalidateHeaders(); |
| break; |
| } |
| } |
| } |
| } |
| |
| /** |
| * Dispatch to edit quick responses. |
| */ |
| public void onEditQuickResponses(com.android.mail.providers.Account account) { |
| try { |
| final Bundle args = new Bundle(1); |
| args.putParcelable(QUICK_RESPONSE_ACCOUNT_KEY, account); |
| startPreferencePanel(AccountSettingsEditQuickResponsesFragment.class.getName(), args, |
| R.string.account_settings_edit_quick_responses_label, null, null, 0); |
| } catch (Exception e) { |
| LogUtils.d(Logging.LOG_TAG, "Error while trying to invoke edit quick responses.", e); |
| } |
| } |
| |
| /** |
| * Dispatch to edit incoming settings. |
| */ |
| public void onIncomingSettings(Account account) { |
| try { |
| mSetupData = new SetupDataFragment(SetupDataFragment.FLOW_MODE_EDIT, account); |
| final Fragment f = new AccountSetupIncomingFragment(); |
| f.setArguments(AccountSetupIncomingFragment.getArgs(true)); |
| // Use startPreferenceFragment here because we need to keep this activity instance |
| startPreferenceFragment(f, true); |
| } catch (Exception e) { |
| LogUtils.d(Logging.LOG_TAG, "Error while trying to invoke store settings.", e); |
| } |
| } |
| |
| /** |
| * Dispatch to edit outgoing settings. |
| * |
| * TODO: Make things less hardwired |
| */ |
| public void onOutgoingSettings(Account account) { |
| try { |
| mSetupData = new SetupDataFragment(SetupDataFragment.FLOW_MODE_EDIT, account); |
| final Fragment f = new AccountSetupOutgoingFragment(); |
| f.setArguments(AccountSetupOutgoingFragment.getArgs(true)); |
| // Use startPreferenceFragment here because we need to keep this activity instance |
| startPreferenceFragment(f, true); |
| } catch (Exception e) { |
| LogUtils.d(Logging.LOG_TAG, "Error while trying to invoke sender settings.", e); |
| } |
| } |
| |
| /** |
| * Delete the selected account |
| */ |
| public void deleteAccount(final Account account) { |
| // Kick off the work to actually delete the account |
| new Thread(new Runnable() { |
| @Override |
| public void run() { |
| final Uri uri = EmailProvider.uiUri("uiaccount", account.mId); |
| getContentResolver().delete(uri, null, null); |
| }}).start(); |
| |
| // TODO: Remove ui glue for unified |
| // Then update the UI as appropriate: |
| // If single pane, return to the header list. If multi, rebuild header list |
| if (onIsMultiPane()) { |
| final Header prefsHeader = getAppPreferencesHeader(); |
| this.switchToHeader(prefsHeader.fragment, prefsHeader.fragmentArguments); |
| mDeletingAccountId = account.mId; |
| updateAccounts(); |
| } else { |
| // We should only be calling this while showing AccountSettingsFragment, |
| // so a finish() should bring us back to headers. No point hiding the deleted account. |
| finish(); |
| } |
| } |
| |
| /** |
| * This AsyncTask looks up an account based on its email address (which is what we get from |
| * the Account Manager). When the account id is determined, we refresh the header list, |
| * which will select the preferences for that account. |
| */ |
| private class GetAccountIdFromAccountTask extends AsyncTask<Intent, Void, Long> { |
| |
| @Override |
| protected Long doInBackground(Intent... params) { |
| final Intent intent = params[0]; |
| android.accounts.Account acct = |
| intent.getParcelableExtra(EXTRA_ACCOUNT_MANAGER_ACCOUNT); |
| return Utility.getFirstRowLong(AccountSettings.this, Account.CONTENT_URI, |
| Account.ID_PROJECTION, SELECTION_ACCOUNT_EMAIL_ADDRESS, |
| new String[] {acct.name}, null, Account.ID_PROJECTION_COLUMN, -1L); |
| } |
| |
| @Override |
| protected void onPostExecute(Long accountId) { |
| if (accountId != -1 && !isCancelled()) { |
| mRequestedAccountId = accountId; |
| invalidateHeaders(); |
| } |
| } |
| } |
| |
| /** |
| * Dialog fragment to show "exit with unsaved changes?" dialog |
| */ |
| public static class UnsavedChangesDialogFragment extends DialogFragment { |
| final static String TAG = "UnsavedChangesDialogFragment"; |
| |
| // Argument bundle keys |
| private final static String BUNDLE_KEY_HEADER = "UnsavedChangesDialogFragment.Header"; |
| private final static String BUNDLE_KEY_BACK = "UnsavedChangesDialogFragment.Back"; |
| |
| /** |
| * 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 newInstanceForHeader(int position) { |
| final UnsavedChangesDialogFragment f = new UnsavedChangesDialogFragment(); |
| final Bundle b = new Bundle(1); |
| b.putInt(BUNDLE_KEY_HEADER, position); |
| f.setArguments(b); |
| return f; |
| } |
| |
| /** |
| * Creates a save changes dialog when the user navigates "back". |
| * {@link #onBackPressed()} defines in which case this may be triggered. |
| */ |
| public static UnsavedChangesDialogFragment newInstanceForBack() { |
| final UnsavedChangesDialogFragment f = new UnsavedChangesDialogFragment(); |
| final Bundle b = new Bundle(1); |
| b.putBoolean(BUNDLE_KEY_BACK, true); |
| f.setArguments(b); |
| return f; |
| } |
| |
| // Force usage of newInstance() |
| public UnsavedChangesDialogFragment() {} |
| |
| @Override |
| public Dialog onCreateDialog(Bundle savedInstanceState) { |
| final AccountSettings activity = (AccountSettings) getActivity(); |
| final int position = getArguments().getInt(BUNDLE_KEY_HEADER); |
| final boolean isBack = getArguments().getBoolean(BUNDLE_KEY_BACK); |
| |
| return new AlertDialog.Builder(activity) |
| .setIconAttribute(android.R.attr.alertDialogIcon) |
| .setTitle(android.R.string.dialog_alert_title) |
| .setMessage(R.string.account_settings_exit_server_settings) |
| .setPositiveButton( |
| R.string.okay_action, |
| new DialogInterface.OnClickListener() { |
| @Override |
| public void onClick(DialogInterface dialog, int which) { |
| if (isBack) { |
| activity.forceBack(); |
| } else { |
| activity.forceSwitchHeader(position); |
| } |
| dismiss(); |
| } |
| }) |
| .setNegativeButton( |
| activity.getString(R.string.cancel_action), null) |
| .create(); |
| } |
| } |
| |
| /** |
| * Dialog briefly shown in some cases, to indicate the user that login failed. If the user |
| * clicks OK, we simply dismiss the dialog, leaving the user in the account settings for |
| * that account; If the user clicks "cancel", we exit account settings. |
| */ |
| public static class LoginWarningDialog extends DialogFragment |
| implements DialogInterface.OnClickListener { |
| private static final String BUNDLE_KEY_ACCOUNT_NAME = "account_name"; |
| private String mReason; |
| |
| // Public no-args constructor needed for fragment re-instantiation |
| public LoginWarningDialog() {} |
| |
| /** |
| * Create a new dialog. |
| */ |
| public static LoginWarningDialog newInstance(String accountName, String reason) { |
| final LoginWarningDialog dialog = new LoginWarningDialog(); |
| final Bundle b = new Bundle(1); |
| b.putString(BUNDLE_KEY_ACCOUNT_NAME, accountName); |
| dialog.setArguments(b); |
| dialog.mReason = reason; |
| return dialog; |
| } |
| |
| @Override |
| public Dialog onCreateDialog(Bundle savedInstanceState) { |
| final String accountName = getArguments().getString(BUNDLE_KEY_ACCOUNT_NAME); |
| |
| final Context context = getActivity(); |
| final Resources res = context.getResources(); |
| final AlertDialog.Builder b = new AlertDialog.Builder(context); |
| b.setTitle(R.string.account_settings_login_dialog_title); |
| b.setIconAttribute(android.R.attr.alertDialogIcon); |
| if (mReason != null) { |
| final TextView message = new TextView(context); |
| final String alert = res.getString( |
| R.string.account_settings_login_dialog_reason_fmt, accountName, mReason); |
| SpannableString spannableAlertString = new SpannableString(alert); |
| Linkify.addLinks(spannableAlertString, Linkify.WEB_URLS); |
| message.setText(spannableAlertString); |
| // There must be a better way than specifying size/padding this way |
| // It does work and look right, though |
| final int textSize = res.getDimensionPixelSize(R.dimen.dialog_text_size); |
| message.setTextSize(textSize); |
| final int paddingLeft = res.getDimensionPixelSize(R.dimen.dialog_padding_left); |
| final int paddingOther = res.getDimensionPixelSize(R.dimen.dialog_padding_other); |
| message.setPadding(paddingLeft, paddingOther, paddingOther, paddingOther); |
| message.setMovementMethod(LinkMovementMethod.getInstance()); |
| b.setView(message); |
| } else { |
| b.setMessage(res.getString(R.string.account_settings_login_dialog_content_fmt, |
| accountName)); |
| } |
| b.setPositiveButton(R.string.okay_action, this); |
| b.setNegativeButton(R.string.cancel_action, this); |
| return b.create(); |
| } |
| |
| @Override |
| public void onClick(DialogInterface dialog, int which) { |
| dismiss(); |
| if (which == DialogInterface.BUTTON_NEGATIVE) { |
| getActivity().finish(); |
| } |
| } |
| } |
| |
| @Override |
| public Context getActivityContext() { |
| return this; |
| } |
| |
| @Override |
| public SetupDataFragment getSetupData() { |
| return mSetupData; |
| } |
| |
| @Override |
| public void setSetupData(SetupDataFragment setupData) { |
| mSetupData = setupData; |
| } |
| } |