• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2010 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.email.activity.setup;
18 
19 import android.app.ActionBar;
20 import android.app.Activity;
21 import android.app.AlertDialog;
22 import android.app.Dialog;
23 import android.app.DialogFragment;
24 import android.app.Fragment;
25 import android.content.Context;
26 import android.content.DialogInterface;
27 import android.content.Intent;
28 import android.content.res.Resources;
29 import android.database.ContentObserver;
30 import android.database.Cursor;
31 import android.net.Uri;
32 import android.os.AsyncTask;
33 import android.os.Bundle;
34 import android.preference.PreferenceActivity;
35 import android.util.Log;
36 import android.view.KeyEvent;
37 import android.view.Menu;
38 import android.view.MenuItem;
39 
40 import com.android.email.Controller;
41 import com.android.email.R;
42 import com.android.email.activity.ActivityHelper;
43 import com.android.email.mail.Sender;
44 import com.android.email.mail.Store;
45 import com.android.emailcommon.Logging;
46 import com.android.emailcommon.provider.Account;
47 import com.android.emailcommon.utility.IntentUtilities;
48 import com.android.emailcommon.utility.Utility;
49 
50 import java.util.List;
51 
52 /**
53  * Handles account preferences, using multi-pane arrangement when possible.
54  *
55  * This activity uses the following fragments:
56  *   AccountSettingsFragment
57  *   Account{Incoming/Outgoing/Exchange}Fragment
58  *   AccountCheckSettingsFragment
59  *   GeneralPreferences
60  *   DebugFragment
61  *
62  * TODO: Delete account - on single-pane view (phone UX) the account list doesn't update properly
63  * TODO: Handle dynamic changes to the account list (exit if necessary).  It probably makes
64  *       sense to use a loader for the accounts list, because it would provide better support for
65  *       dealing with accounts being added/deleted and triggering the header reload.
66  */
67 public class AccountSettings extends PreferenceActivity {
68     /*
69      * Intent to open account settings for account=1
70         adb shell am start -a android.intent.action.EDIT \
71             -d '"content://ui.email.android.com/settings?ACCOUNT_ID=1"'
72      */
73 
74     // Intent extras for our internal activity launch
75     private static final String EXTRA_ENABLE_DEBUG = "AccountSettings.enable_debug";
76     private static final String EXTRA_LOGIN_WARNING_FOR_ACCOUNT = "AccountSettings.for_account";
77     private static final String EXTRA_TITLE = "AccountSettings.title";
78 
79     // Key for arguments bundle for QuickResponse editing
80     private static final String QUICK_RESPONSE_ACCOUNT_KEY = "account";
81 
82     // Key codes used to open a debug settings fragment.
83     private static final int[] SECRET_KEY_CODES = {
84             KeyEvent.KEYCODE_D, KeyEvent.KEYCODE_E, KeyEvent.KEYCODE_B, KeyEvent.KEYCODE_U,
85             KeyEvent.KEYCODE_G
86             };
87     private int mSecretKeyCodeIndex = 0;
88 
89     // When the user taps "Email Preferences" 10 times in a row, we'll enable the debug settings.
90     private int mNumGeneralHeaderClicked = 0;
91 
92     private long mRequestedAccountId;
93     private Header mRequestedAccountHeader;
94     private Header[] mAccountListHeaders;
95     private Header mAppPreferencesHeader;
96     /* package */ Fragment mCurrentFragment;
97     private long mDeletingAccountId = -1;
98     private boolean mShowDebugMenu;
99     private List<Header> mGeneratedHeaders;
100 
101     // Async Tasks
102     private LoadAccountListTask mLoadAccountListTask;
103     private ContentObserver mAccountObserver;
104 
105     // Specific callbacks used by settings fragments
106     private final AccountSettingsFragmentCallback mAccountSettingsFragmentCallback
107             = new AccountSettingsFragmentCallback();
108     private final AccountServerSettingsFragmentCallback mAccountServerSettingsFragmentCallback
109             = new AccountServerSettingsFragmentCallback();
110 
111     /**
112      * Display (and edit) settings for a specific account, or -1 for any/all accounts
113      */
actionSettings(Activity fromActivity, long accountId)114     public static void actionSettings(Activity fromActivity, long accountId) {
115         fromActivity.startActivity(createAccountSettingsIntent(fromActivity, accountId, null));
116     }
117 
118     /**
119      * Create and return an intent to display (and edit) settings for a specific account, or -1
120      * for any/all accounts.  If an account name string is provided, a warning dialog will be
121      * displayed as well.
122      */
createAccountSettingsIntent(Context context, long accountId, String loginWarningAccountName)123     public static Intent createAccountSettingsIntent(Context context, long accountId,
124             String loginWarningAccountName) {
125         final Uri.Builder b = IntentUtilities.createActivityIntentUrlBuilder("settings");
126         IntentUtilities.setAccountId(b, accountId);
127         Intent i = new Intent(Intent.ACTION_EDIT, b.build());
128         if (loginWarningAccountName != null) {
129             i.putExtra(EXTRA_LOGIN_WARNING_FOR_ACCOUNT, loginWarningAccountName);
130         }
131         return i;
132     }
133 
134     /**
135      * Launch generic settings and pre-enable the debug preferences
136      */
actionSettingsWithDebug(Context fromContext)137     public static void actionSettingsWithDebug(Context fromContext) {
138         Intent i = new Intent(fromContext, AccountSettings.class);
139         i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
140         i.putExtra(EXTRA_ENABLE_DEBUG, true);
141         fromContext.startActivity(i);
142     }
143 
144     @Override
onCreate(Bundle savedInstanceState)145     public void onCreate(Bundle savedInstanceState) {
146         super.onCreate(savedInstanceState);
147         ActivityHelper.debugSetWindowFlags(this);
148 
149         Intent i = getIntent();
150         if (savedInstanceState == null) {
151             // This will be -1 if not included in the intent, which is safe as onBuildHeaders
152             // will never find an account with that id
153             mRequestedAccountId = IntentUtilities.getAccountIdFromIntent(i);
154             String loginWarningAccount = i.getStringExtra(EXTRA_LOGIN_WARNING_FOR_ACCOUNT);
155             if (loginWarningAccount != null) {
156                 // Show dialog (first time only - don't re-show on a rotation)
157                 LoginWarningDialog dialog = LoginWarningDialog.newInstance(loginWarningAccount);
158                 dialog.show(getFragmentManager(), "loginwarning");
159             }
160         }
161         mShowDebugMenu = i.getBooleanExtra(EXTRA_ENABLE_DEBUG, false);
162 
163         String title = i.getStringExtra(EXTRA_TITLE);
164         if (title != null) {
165             setTitle(title);
166         }
167 
168         getActionBar().setDisplayOptions(
169                 ActionBar.DISPLAY_HOME_AS_UP, ActionBar.DISPLAY_HOME_AS_UP);
170 
171         mAccountObserver = new ContentObserver(Utility.getMainThreadHandler()) {
172             @Override
173             public void onChange(boolean selfChange) {
174                 updateAccounts();
175             }
176         };
177     }
178 
179     @Override
onResume()180     public void onResume() {
181         super.onResume();
182         getContentResolver().registerContentObserver(Account.NOTIFIER_URI, true, mAccountObserver);
183         updateAccounts();
184     }
185 
186     @Override
onPause()187     public void onPause() {
188         super.onPause();
189         getContentResolver().unregisterContentObserver(mAccountObserver);
190     }
191 
192     @Override
onDestroy()193     protected void onDestroy() {
194         super.onDestroy();
195         Utility.cancelTaskInterrupt(mLoadAccountListTask);
196         mLoadAccountListTask = null;
197     }
198 
199     /**
200      * Listen for secret sequence and, if heard, enable debug menu
201      */
202     @Override
onKeyDown(int keyCode, KeyEvent event)203     public boolean onKeyDown(int keyCode, KeyEvent event) {
204         if (event.getKeyCode() == SECRET_KEY_CODES[mSecretKeyCodeIndex]) {
205             mSecretKeyCodeIndex++;
206             if (mSecretKeyCodeIndex == SECRET_KEY_CODES.length) {
207                 mSecretKeyCodeIndex = 0;
208                 enableDebugMenu();
209             }
210         } else {
211             mSecretKeyCodeIndex = 0;
212         }
213         return super.onKeyDown(keyCode, event);
214     }
215 
216     @Override
onCreateOptionsMenu(Menu menu)217     public boolean onCreateOptionsMenu(Menu menu) {
218         super.onCreateOptionsMenu(menu);
219         getMenuInflater().inflate(R.menu.account_settings_add_account_option, menu);
220         return true;
221     }
222 
223     @Override
onPrepareOptionsMenu(Menu menu)224     public boolean onPrepareOptionsMenu(Menu menu) {
225         return shouldShowNewAccount();
226     }
227 
228     @Override
onOptionsItemSelected(MenuItem item)229     public boolean onOptionsItemSelected(MenuItem item) {
230         switch (item.getItemId()) {
231             case android.R.id.home:
232                 // The app icon on the action bar is pressed.  Just emulate a back press.
233                 // TODO: this should navigate to the main screen, even if a sub-setting is open.
234                 // But we shouldn't just finish(), as we want to show "discard changes?" dialog
235                 // when necessary.
236                 onBackPressed();
237                 break;
238             case R.id.add_new_account:
239                 onAddNewAccount();
240                 break;
241             default:
242                 return super.onOptionsItemSelected(item);
243         }
244         return true;
245     }
246 
247     @Override
onBuildStartFragmentIntent(String fragmentName, Bundle args, int titleRes, int shortTitleRes)248     public Intent onBuildStartFragmentIntent(String fragmentName, Bundle args,
249             int titleRes, int shortTitleRes) {
250         Intent result = super.onBuildStartFragmentIntent(
251                 fragmentName, args, titleRes, shortTitleRes);
252 
253         // When opening a sub-settings page (e.g. account specific page), see if we want to modify
254         // the activity title.
255         String title = AccountSettingsFragment.getTitleFromArgs(args);
256         if ((titleRes == 0) && (title != null)) {
257             result.putExtra(EXTRA_TITLE, title);
258         }
259         return result;
260     }
261 
262     /**
263      * Any time we exit via this pathway, and we are showing a server settings fragment,
264      * we put up the exit-save-changes dialog.  This will work for the following cases:
265      *   Cancel button
266      *   Back button
267      *   Up arrow in application icon
268      * It will *not* apply in the following cases:
269      *   Click the parent breadcrumb - need to find a hook for this
270      *   Click in the header list (e.g. another account) - handled elsewhere
271      */
272     @Override
onBackPressed()273     public void onBackPressed() {
274         if (mCurrentFragment instanceof AccountServerBaseFragment) {
275             boolean changed = ((AccountServerBaseFragment) mCurrentFragment).haveSettingsChanged();
276             if (changed) {
277                 UnsavedChangesDialogFragment dialogFragment =
278                         UnsavedChangesDialogFragment.newInstanceForBack();
279                 dialogFragment.show(getFragmentManager(), UnsavedChangesDialogFragment.TAG);
280                 return; // Prevent "back" from being handled
281             }
282         }
283         super.onBackPressed();
284     }
285 
286     /**
287      * If the caller requested a specific account to be edited, switch to it.  This is a one-shot,
288      * so the user is free to edit another account as well.
289      */
290     @Override
onGetNewHeader()291     public Header onGetNewHeader() {
292         Header result = mRequestedAccountHeader;
293         mRequestedAccountHeader = null;
294         return result;
295     }
296 
enableDebugMenu()297     private void enableDebugMenu() {
298         mShowDebugMenu = true;
299         invalidateHeaders();
300     }
301 
302     /**
303      * Decide if "add account" should be shown
304      */
shouldShowNewAccount()305     private boolean shouldShowNewAccount() {
306         // If in single pane mode, only add accounts at top level
307         if (!onIsMultiPane()) {
308             return hasHeaders();
309         } else {
310             // If in multi pane mode, only add accounts when showing a top level fragment
311             // Note: null is OK; This is the case when we first launch the activity
312             if ((mCurrentFragment != null)
313                 && !(mCurrentFragment instanceof GeneralPreferences)
314                 && !(mCurrentFragment instanceof DebugFragment)
315                 && !(mCurrentFragment instanceof AccountSettingsFragment)) return false;
316         }
317         return true;
318     }
319 
onAddNewAccount()320     private void onAddNewAccount() {
321         AccountSetupBasics.actionNewAccount(this);
322     }
323 
324     /**
325      * Start the async reload of the accounts list (if the headers are being displayed)
326      */
updateAccounts()327     private void updateAccounts() {
328         if (hasHeaders()) {
329             Utility.cancelTaskInterrupt(mLoadAccountListTask);
330             mLoadAccountListTask = (LoadAccountListTask)
331                     new LoadAccountListTask().executeOnExecutor(
332                             AsyncTask.THREAD_POOL_EXECUTOR, mDeletingAccountId);
333         }
334     }
335 
336     /**
337      * Write the current header (accounts) array into the one provided by the PreferenceActivity.
338      * Skip any headers that match mDeletingAccountId (this is a quick-hide algorithm while a
339      * background thread works on deleting the account).  Also sets mRequestedAccountHeader if
340      * we find the requested account (by id).
341      */
342     @Override
onBuildHeaders(List<Header> target)343     public void onBuildHeaders(List<Header> target) {
344         // Assume the account is unspecified
345         mRequestedAccountHeader = null;
346 
347         // Always add app preferences as first header
348         target.clear();
349         target.add(getAppPreferencesHeader());
350 
351         // Then add zero or more account headers as necessary
352         if (mAccountListHeaders != null) {
353             int headerCount = mAccountListHeaders.length;
354             for (int index = 0; index < headerCount; index++) {
355                 Header header = mAccountListHeaders[index];
356                 if (header != null && header.id != HEADER_ID_UNDEFINED) {
357                     if (header.id != mDeletingAccountId) {
358                         target.add(header);
359                         if (header.id == mRequestedAccountId) {
360                             mRequestedAccountHeader = header;
361                             mRequestedAccountId = -1;
362                         }
363                     }
364                 }
365             }
366         }
367 
368         // finally, if debug header is enabled, show it
369         if (mShowDebugMenu) {
370             // setup lightweight header for debugging
371             Header debugHeader = new Header();
372             debugHeader.title = getText(R.string.debug_title);
373             debugHeader.summary = null;
374             debugHeader.iconRes = 0;
375             debugHeader.fragment = DebugFragment.class.getCanonicalName();
376             debugHeader.fragmentArguments = null;
377             target.add(debugHeader);
378         }
379 
380         // Save for later use (see forceSwitch)
381         mGeneratedHeaders = target;
382     }
383 
384     /**
385      * Generate and return the first header, for app preferences
386      */
getAppPreferencesHeader()387     private Header getAppPreferencesHeader() {
388         // Set up fixed header for general settings
389         if (mAppPreferencesHeader == null) {
390             mAppPreferencesHeader = new Header();
391             mAppPreferencesHeader.title = getText(R.string.header_label_general_preferences);
392             mAppPreferencesHeader.summary = null;
393             mAppPreferencesHeader.iconRes = 0;
394             mAppPreferencesHeader.fragment = GeneralPreferences.class.getCanonicalName();
395             mAppPreferencesHeader.fragmentArguments = null;
396         }
397         return mAppPreferencesHeader;
398     }
399 
400     /**
401      * This AsyncTask reads the accounts list and generates the headers.  When the headers are
402      * ready, we'll trigger PreferenceActivity to refresh the account list with them.
403      *
404      * The array generated and stored in mAccountListHeaders may be sparse so any readers should
405      * check for and skip over null entries, and should not assume array length is # of accounts.
406      *
407      * TODO: Smaller projection
408      * TODO: Convert to Loader
409      * TODO: Write a test, including operation of deletingAccountId param
410      */
411     private class LoadAccountListTask extends AsyncTask<Long, Void, Object[]> {
412 
413         @Override
doInBackground(Long... params)414         protected Object[] doInBackground(Long... params) {
415             Header[] result = null;
416             Boolean deletingAccountFound = false;
417             long deletingAccountId = params[0];
418 
419             Cursor c = getContentResolver().query(
420                     Account.CONTENT_URI,
421                     Account.CONTENT_PROJECTION, null, null, null);
422             try {
423                 int index = 0;
424                 int headerCount = c.getCount();
425                 result = new Header[headerCount];
426 
427                 while (c.moveToNext()) {
428                     long accountId = c.getLong(Account.CONTENT_ID_COLUMN);
429                     if (accountId == deletingAccountId) {
430                         deletingAccountFound = true;
431                         continue;
432                     }
433                     String name = c.getString(Account.CONTENT_DISPLAY_NAME_COLUMN);
434                     String email = c.getString(Account.CONTENT_EMAIL_ADDRESS_COLUMN);
435                     Header newHeader = new Header();
436                     newHeader.id = accountId;
437                     newHeader.title = name;
438                     newHeader.summary = email;
439                     newHeader.fragment = AccountSettingsFragment.class.getCanonicalName();
440                     newHeader.fragmentArguments =
441                             AccountSettingsFragment.buildArguments(accountId, email);
442 
443                     result[index++] = newHeader;
444                 }
445             } finally {
446                 if (c != null) {
447                     c.close();
448                 }
449             }
450             return new Object[] { result, deletingAccountFound };
451         }
452 
453         @Override
onPostExecute(Object[] result)454         protected void onPostExecute(Object[] result) {
455             if (isCancelled() || result == null) return;
456             // Extract the results
457             Header[] headers = (Header[]) result[0];
458             boolean deletingAccountFound = (Boolean) result[1];
459             // report the settings
460             mAccountListHeaders = headers;
461             invalidateHeaders();
462             if (!deletingAccountFound) {
463                 mDeletingAccountId = -1;
464             }
465         }
466     }
467 
468     /**
469      * Called when the user selects an item in the header list.  Handles save-data cases as needed
470      *
471      * @param header The header that was selected.
472      * @param position The header's position in the list.
473      */
474     @Override
onHeaderClick(Header header, int position)475     public void onHeaderClick(Header header, int position) {
476         // special case when exiting the server settings fragments
477         if (mCurrentFragment instanceof AccountServerBaseFragment) {
478             boolean changed = ((AccountServerBaseFragment)mCurrentFragment).haveSettingsChanged();
479             if (changed) {
480                 UnsavedChangesDialogFragment dialogFragment =
481                     UnsavedChangesDialogFragment.newInstanceForHeader(position);
482                 dialogFragment.show(getFragmentManager(), UnsavedChangesDialogFragment.TAG);
483                 return;
484             }
485         }
486 
487         // Secret keys:  Click 10x to enable debug settings
488         if (position == 0) {
489             mNumGeneralHeaderClicked++;
490             if (mNumGeneralHeaderClicked == 10) {
491                 enableDebugMenu();
492             }
493         } else {
494             mNumGeneralHeaderClicked = 0;
495         }
496 
497         // Process header click normally
498         super.onHeaderClick(header, position);
499     }
500 
501     /**
502      * Switch to a specific header without checking for server settings fragments as done
503      * in {@link #onHeaderClick(Header, int)}.  Called after we interrupted a header switch
504      * with a dialog, and the user OK'd it.
505      */
forceSwitchHeader(int position)506     private void forceSwitchHeader(int position) {
507         // Clear the current fragment; we're navigating away
508         mCurrentFragment = null;
509         // Ensure the UI visually shows the correct header selected
510         setSelection(position);
511         Header header = mGeneratedHeaders.get(position);
512         switchToHeader(header);
513     }
514 
515     /**
516      * Forcefully go backward in the stack. This may potentially discard unsaved settings.
517      */
forceBack()518     private void forceBack() {
519         // Clear the current fragment; we're navigating away
520         mCurrentFragment = null;
521         onBackPressed();
522     }
523 
524     @Override
onAttachFragment(Fragment f)525     public void onAttachFragment(Fragment f) {
526         super.onAttachFragment(f);
527 
528         if (f instanceof AccountSettingsFragment) {
529             AccountSettingsFragment asf = (AccountSettingsFragment) f;
530             asf.setCallback(mAccountSettingsFragmentCallback);
531         } else if (f instanceof AccountServerBaseFragment) {
532             AccountServerBaseFragment asbf = (AccountServerBaseFragment) f;
533             asbf.setCallback(mAccountServerSettingsFragmentCallback);
534         } else {
535             // Possibly uninteresting fragment, such as a dialog.
536             return;
537         }
538         mCurrentFragment = f;
539 
540         // When we're changing fragments, enable/disable the add account button
541         invalidateOptionsMenu();
542     }
543 
544     /**
545      * Callbacks for AccountSettingsFragment
546      */
547     private class AccountSettingsFragmentCallback implements AccountSettingsFragment.Callback {
548         @Override
onSettingsChanged(Account account, String preference, Object value)549         public void onSettingsChanged(Account account, String preference, Object value) {
550             AccountSettings.this.onSettingsChanged(account, preference, value);
551         }
552         @Override
onEditQuickResponses(Account account)553         public void onEditQuickResponses(Account account) {
554             AccountSettings.this.onEditQuickResponses(account);
555         }
556         @Override
onIncomingSettings(Account account)557         public void onIncomingSettings(Account account) {
558             AccountSettings.this.onIncomingSettings(account);
559         }
560         @Override
onOutgoingSettings(Account account)561         public void onOutgoingSettings(Account account) {
562             AccountSettings.this.onOutgoingSettings(account);
563         }
564         @Override
abandonEdit()565         public void abandonEdit() {
566             finish();
567         }
568         @Override
deleteAccount(Account account)569         public void deleteAccount(Account account) {
570             AccountSettings.this.deleteAccount(account);
571         }
572     }
573 
574     /**
575      * Callbacks for AccountServerSettingsFragmentCallback
576      */
577     private class AccountServerSettingsFragmentCallback
578             implements AccountServerBaseFragment.Callback {
579         @Override
onEnableProceedButtons(boolean enable)580         public void onEnableProceedButtons(boolean enable) {
581             // This is not used - it's a callback for the legacy activities
582         }
583 
584         @Override
onProceedNext(int checkMode, AccountServerBaseFragment target)585         public void onProceedNext(int checkMode, AccountServerBaseFragment target) {
586             AccountCheckSettingsFragment checkerFragment =
587                 AccountCheckSettingsFragment.newInstance(checkMode, target);
588             startPreferenceFragment(checkerFragment, true);
589         }
590 
591         /**
592          * After verifying a new server configuration as OK, we return here and continue.  This
593          * simply does a "back" to exit the settings screen.
594          */
595         @Override
onCheckSettingsComplete(int result, int setupMode)596         public void onCheckSettingsComplete(int result, int setupMode) {
597             if (result == AccountCheckSettingsFragment.CHECK_SETTINGS_OK) {
598                 // Settings checked & saved; clear current fragment
599                 mCurrentFragment = null;
600                 onBackPressed();
601             }
602         }
603     }
604 
605     /**
606      * Some of the settings have changed. Update internal state as necessary.
607      */
onSettingsChanged(Account account, String preference, Object value)608     public void onSettingsChanged(Account account, String preference, Object value) {
609         if (AccountSettingsFragment.PREFERENCE_DESCRIPTION.equals(preference)) {
610             for (Header header : mAccountListHeaders) {
611                 if (header.id == account.mId) {
612                     // Manually tweak the header title. We cannot rebuild the header list from
613                     // an account cursor as the account database has not been saved yet.
614                     header.title = value.toString();
615                     invalidateHeaders();
616                     break;
617                 }
618             }
619         }
620     }
621 
622     /**
623      * Dispatch to edit quick responses.
624      */
onEditQuickResponses(Account account)625     public void onEditQuickResponses(Account account) {
626         try {
627             Bundle args = new Bundle();
628             args.putParcelable(QUICK_RESPONSE_ACCOUNT_KEY, account);
629             startPreferencePanel(AccountSettingsEditQuickResponsesFragment.class.getName(), args,
630                     R.string.account_settings_edit_quick_responses_label, null, null, 0);
631         } catch (Exception e) {
632             Log.d(Logging.LOG_TAG, "Error while trying to invoke edit quick responses.", e);
633         }
634     }
635 
636     /**
637      * Dispatch to edit incoming settings.
638      *
639      * TODO: Make things less hardwired
640      */
onIncomingSettings(Account account)641     public void onIncomingSettings(Account account) {
642         try {
643             Store store = Store.getInstance(account, getApplication());
644             if (store != null) {
645                 Class<? extends android.app.Activity> setting = store.getSettingActivityClass();
646                 if (setting != null) {
647                     SetupData.init(SetupData.FLOW_MODE_EDIT, account);
648                     if (setting.equals(AccountSetupIncoming.class)) {
649                         startPreferencePanel(AccountSetupIncomingFragment.class.getName(),
650                                 AccountSetupIncomingFragment.getSettingsModeArgs(),
651                                 R.string.account_settings_incoming_label, null, null, 0);
652                     } else if (setting.equals(AccountSetupExchange.class)) {
653                         startPreferencePanel(AccountSetupExchangeFragment.class.getName(),
654                                 AccountSetupExchangeFragment.getSettingsModeArgs(),
655                                 R.string.account_settings_incoming_label, null, null, 0);
656                     }
657                 }
658             }
659         } catch (Exception e) {
660             Log.d(Logging.LOG_TAG, "Error while trying to invoke store settings.", e);
661         }
662     }
663 
664     /**
665      * Dispatch to edit outgoing settings.
666      *
667      * TODO: Make things less hardwired
668      */
onOutgoingSettings(Account account)669     public void onOutgoingSettings(Account account) {
670         try {
671             Sender sender = Sender.getInstance(getApplication(), account);
672             if (sender != null) {
673                 Class<? extends android.app.Activity> setting = sender.getSettingActivityClass();
674                 if (setting != null) {
675                     SetupData.init(SetupData.FLOW_MODE_EDIT, account);
676                     if (setting.equals(AccountSetupOutgoing.class)) {
677                         startPreferencePanel(AccountSetupOutgoingFragment.class.getName(),
678                                 AccountSetupOutgoingFragment.getSettingsModeArgs(),
679                                 R.string.account_settings_outgoing_label, null, null, 0);
680                     }
681                 }
682             }
683         } catch (Exception e) {
684             Log.d(Logging.LOG_TAG, "Error while trying to invoke sender settings.", e);
685         }
686     }
687 
688     /**
689      * Delete the selected account
690      */
deleteAccount(Account account)691     public void deleteAccount(Account account) {
692         // Kick off the work to actually delete the account
693         // Delete the account (note, this is async.  Would be nice to get a callback.
694         Controller.getInstance(this).deleteAccount(account.mId);
695 
696         // Then update the UI as appropriate:
697         // If single pane, return to the header list.  If multi, rebuild header list
698         if (onIsMultiPane()) {
699             Header prefsHeader = getAppPreferencesHeader();
700             this.switchToHeader(prefsHeader.fragment, prefsHeader.fragmentArguments);
701             mDeletingAccountId = account.mId;
702             updateAccounts();
703         } else {
704             // We should only be calling this while showing AccountSettingsFragment,
705             // so a finish() should bring us back to headers.  No point hiding the deleted account.
706             finish();
707         }
708     }
709 
710     /**
711      * Dialog fragment to show "exit with unsaved changes?" dialog
712      */
713     /* package */ static class UnsavedChangesDialogFragment extends DialogFragment {
714         private final static String TAG = "UnsavedChangesDialogFragment";
715 
716         // Argument bundle keys
717         private final static String BUNDLE_KEY_HEADER = "UnsavedChangesDialogFragment.Header";
718         private final static String BUNDLE_KEY_BACK = "UnsavedChangesDialogFragment.Back";
719 
720         /**
721          * Creates a save changes dialog when the user selects a new header
722          * @param position The new header index to make active if the user accepts the dialog. This
723          * must be a valid header index although there is no error checking.
724          */
newInstanceForHeader(int position)725         public static UnsavedChangesDialogFragment newInstanceForHeader(int position) {
726             UnsavedChangesDialogFragment f = new UnsavedChangesDialogFragment();
727             Bundle b = new Bundle();
728             b.putInt(BUNDLE_KEY_HEADER, position);
729             f.setArguments(b);
730             return f;
731         }
732 
733         /**
734          * Creates a save changes dialog when the user navigates "back".
735          * {@link #onBackPressed()} defines in which case this may be triggered.
736          */
newInstanceForBack()737         public static UnsavedChangesDialogFragment newInstanceForBack() {
738             UnsavedChangesDialogFragment f = new UnsavedChangesDialogFragment();
739             Bundle b = new Bundle();
740             b.putBoolean(BUNDLE_KEY_BACK, true);
741             f.setArguments(b);
742             return f;
743         }
744 
745         // Force usage of newInstance()
UnsavedChangesDialogFragment()746         private UnsavedChangesDialogFragment() {
747         }
748 
749         @Override
onCreateDialog(Bundle savedInstanceState)750         public Dialog onCreateDialog(Bundle savedInstanceState) {
751             final AccountSettings activity = (AccountSettings) getActivity();
752             final int position = getArguments().getInt(BUNDLE_KEY_HEADER);
753             final boolean isBack = getArguments().getBoolean(BUNDLE_KEY_BACK);
754 
755             return new AlertDialog.Builder(activity)
756                 .setIconAttribute(android.R.attr.alertDialogIcon)
757                 .setTitle(android.R.string.dialog_alert_title)
758                 .setMessage(R.string.account_settings_exit_server_settings)
759                 .setPositiveButton(
760                         R.string.okay_action,
761                         new DialogInterface.OnClickListener() {
762                             @Override
763                             public void onClick(DialogInterface dialog, int which) {
764                                 if (isBack) {
765                                     activity.forceBack();
766                                 } else {
767                                     activity.forceSwitchHeader(position);
768                                 }
769                                 dismiss();
770                             }
771                         })
772                 .setNegativeButton(
773                         activity.getString(R.string.cancel_action), null)
774                 .create();
775         }
776     }
777 
778     /**
779      * Dialog briefly shown in some cases, to indicate the user that login failed.  If the user
780      * clicks OK, we simply dismiss the dialog, leaving the user in the account settings for
781      * that account;  If the user clicks "cancel", we exit account settings.
782      */
783     public static class LoginWarningDialog extends DialogFragment
784             implements DialogInterface.OnClickListener {
785         private static final String BUNDLE_KEY_ACCOUNT_NAME = "account_name";
786 
787         /**
788          * Create a new dialog.
789          */
790         public static LoginWarningDialog newInstance(String accountName) {
791             final LoginWarningDialog dialog = new LoginWarningDialog();
792             Bundle b = new Bundle();
793             b.putString(BUNDLE_KEY_ACCOUNT_NAME, accountName);
794             dialog.setArguments(b);
795             return dialog;
796         }
797 
798         @Override
799         public Dialog onCreateDialog(Bundle savedInstanceState) {
800             final String accountName = getArguments().getString(BUNDLE_KEY_ACCOUNT_NAME);
801 
802             final Context context = getActivity();
803             final Resources res = context.getResources();
804             final AlertDialog.Builder b = new AlertDialog.Builder(context);
805             b.setTitle(R.string.account_settings_login_dialog_title);
806             b.setIconAttribute(android.R.attr.alertDialogIcon);
807             b.setMessage(res.getString(R.string.account_settings_login_dialog_content_fmt,
808                     accountName));
809             b.setPositiveButton(R.string.okay_action, this);
810             b.setNegativeButton(R.string.cancel_action, this);
811             return b.create();
812         }
813 
814         @Override
815         public void onClick(DialogInterface dialog, int which) {
816             dismiss();
817             if (which == DialogInterface.BUTTON_NEGATIVE) {
818                 getActivity().finish();
819             }
820         }
821     }
822 }
823