Home | History | Annotate | Download | only in setup
      1 /*
      2  * Copyright (C) 2008 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.accounts.AccountAuthenticatorResponse;
     20 import android.accounts.AccountManager;
     21 import android.accounts.AccountManagerCallback;
     22 import android.accounts.AccountManagerFuture;
     23 import android.accounts.AuthenticatorException;
     24 import android.accounts.OperationCanceledException;
     25 import android.app.Activity;
     26 import android.app.AlertDialog;
     27 import android.app.ProgressDialog;
     28 import android.content.Context;
     29 import android.content.DialogInterface;
     30 import android.content.Intent;
     31 import android.os.AsyncTask;
     32 import android.os.Bundle;
     33 import android.os.RemoteException;
     34 import android.view.View;
     35 import android.view.View.OnClickListener;
     36 import android.widget.ArrayAdapter;
     37 import android.widget.CheckBox;
     38 import android.widget.Spinner;
     39 
     40 import com.android.email.R;
     41 import com.android.email.activity.ActivityHelper;
     42 import com.android.email.activity.UiUtilities;
     43 import com.android.email.service.EmailServiceUtils;
     44 import com.android.email.service.EmailServiceUtils.EmailServiceInfo;
     45 import com.android.email2.ui.MailActivityEmail;
     46 import com.android.emailcommon.Logging;
     47 import com.android.emailcommon.provider.Account;
     48 import com.android.emailcommon.provider.Policy;
     49 import com.android.emailcommon.service.EmailServiceProxy;
     50 import com.android.emailcommon.service.SyncWindow;
     51 import com.android.emailcommon.utility.Utility;
     52 import com.android.mail.preferences.AccountPreferences;
     53 import com.android.mail.utils.LogUtils;
     54 
     55 import java.io.IOException;
     56 
     57 public class AccountSetupOptions extends AccountSetupActivity implements OnClickListener {
     58     private static final String EXTRA_IS_PROCESSING_KEY = "com.android.email.is_processing";
     59 
     60     private Spinner mCheckFrequencyView;
     61     private Spinner mSyncWindowView;
     62     private CheckBox mNotifyView;
     63     private CheckBox mSyncContactsView;
     64     private CheckBox mSyncCalendarView;
     65     private CheckBox mSyncEmailView;
     66     private CheckBox mBackgroundAttachmentsView;
     67     private View mAccountSyncWindowRow;
     68     private boolean mDonePressed = false;
     69     private EmailServiceInfo mServiceInfo;
     70     private boolean mIsProcessing = false;
     71 
     72     private ProgressDialog mCreateAccountDialog;
     73 
     74     public static final int REQUEST_CODE_ACCEPT_POLICIES = 1;
     75 
     76     /** Default sync window for new EAS accounts */
     77     private static final int SYNC_WINDOW_EAS_DEFAULT = SyncWindow.SYNC_WINDOW_1_WEEK;
     78 
     79     public static void actionOptions(Activity fromActivity, SetupData setupData) {
     80         final Intent intent = new ForwardingIntent(fromActivity, AccountSetupOptions.class);
     81         intent.putExtra(SetupData.EXTRA_SETUP_DATA, setupData);
     82         fromActivity.startActivity(intent);
     83     }
     84 
     85     @Override
     86     public void onCreate(Bundle savedInstanceState) {
     87         super.onCreate(savedInstanceState);
     88         ActivityHelper.debugSetWindowFlags(this);
     89         setContentView(R.layout.account_setup_options);
     90 
     91         mCheckFrequencyView = UiUtilities.getView(this, R.id.account_check_frequency);
     92         mSyncWindowView = UiUtilities.getView(this, R.id.account_sync_window);
     93         mNotifyView = UiUtilities.getView(this, R.id.account_notify);
     94         mSyncContactsView = UiUtilities.getView(this, R.id.account_sync_contacts);
     95         mSyncCalendarView = UiUtilities.getView(this, R.id.account_sync_calendar);
     96         mSyncEmailView = UiUtilities.getView(this, R.id.account_sync_email);
     97         mSyncEmailView.setChecked(true);
     98         mBackgroundAttachmentsView = UiUtilities.getView(this, R.id.account_background_attachments);
     99         mBackgroundAttachmentsView.setChecked(true);
    100         UiUtilities.getView(this, R.id.previous).setOnClickListener(this);
    101         UiUtilities.getView(this, R.id.next).setOnClickListener(this);
    102         mAccountSyncWindowRow = UiUtilities.getView(this, R.id.account_sync_window_row);
    103 
    104         final Account account = mSetupData.getAccount();
    105         mServiceInfo = EmailServiceUtils.getServiceInfo(getApplicationContext(),
    106                 account.mHostAuthRecv.mProtocol);
    107         final CharSequence[] frequencyValues = mServiceInfo.syncIntervals;
    108         final CharSequence[] frequencyEntries = mServiceInfo.syncIntervalStrings;
    109 
    110         // Now create the array used by the sync interval Spinner
    111         final SpinnerOption[] checkFrequencies = new SpinnerOption[frequencyEntries.length];
    112         for (int i = 0; i < frequencyEntries.length; i++) {
    113             checkFrequencies[i] = new SpinnerOption(
    114                     Integer.valueOf(frequencyValues[i].toString()), frequencyEntries[i].toString());
    115         }
    116         final ArrayAdapter<SpinnerOption> checkFrequenciesAdapter =
    117                 new ArrayAdapter<SpinnerOption>(this, android.R.layout.simple_spinner_item,
    118                         checkFrequencies);
    119         checkFrequenciesAdapter
    120                 .setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
    121         mCheckFrequencyView.setAdapter(checkFrequenciesAdapter);
    122 
    123         if (mServiceInfo.offerLookback) {
    124             enableLookbackSpinner();
    125         }
    126 
    127         mNotifyView.setChecked(true); // By default, we want notifications on
    128         SpinnerOption.setSpinnerOptionValue(mCheckFrequencyView, account.getSyncInterval());
    129 
    130         if (mServiceInfo.syncContacts) {
    131             mSyncContactsView.setVisibility(View.VISIBLE);
    132             mSyncContactsView.setChecked(true);
    133             UiUtilities.setVisibilitySafe(this, R.id.account_sync_contacts_divider, View.VISIBLE);
    134         }
    135         if (mServiceInfo.syncCalendar) {
    136             mSyncCalendarView.setVisibility(View.VISIBLE);
    137             mSyncCalendarView.setChecked(true);
    138             UiUtilities.setVisibilitySafe(this, R.id.account_sync_calendar_divider, View.VISIBLE);
    139         }
    140 
    141         if (!mServiceInfo.offerAttachmentPreload) {
    142             mBackgroundAttachmentsView.setVisibility(View.GONE);
    143             UiUtilities.setVisibilitySafe(this, R.id.account_background_attachments_divider,
    144                     View.GONE);
    145         }
    146 
    147         mIsProcessing = savedInstanceState != null &&
    148                 savedInstanceState.getBoolean(EXTRA_IS_PROCESSING_KEY, false);
    149         if (mIsProcessing) {
    150             // We are already processing, so just show the dialog until we finish
    151             showCreateAccountDialog();
    152         } else if (mSetupData.getFlowMode() == SetupData.FLOW_MODE_FORCE_CREATE) {
    153             // If we are just visiting here to fill in details, exit immediately
    154             onDone();
    155         }
    156     }
    157 
    158     @Override
    159     public void onSaveInstanceState(Bundle outState) {
    160         super.onSaveInstanceState(outState);
    161         outState.putBoolean(EXTRA_IS_PROCESSING_KEY, mIsProcessing);
    162     }
    163 
    164     @Override
    165     public void finish() {
    166         // If the account manager initiated the creation, and success was not reported,
    167         // then we assume that we're giving up (for any reason) - report failure.
    168         final AccountAuthenticatorResponse authenticatorResponse =
    169                 mSetupData.getAccountAuthenticatorResponse();
    170         if (authenticatorResponse != null) {
    171             authenticatorResponse.onError(AccountManager.ERROR_CODE_CANCELED, "canceled");
    172             mSetupData.setAccountAuthenticatorResponse(null);
    173         }
    174         super.finish();
    175     }
    176 
    177     /**
    178      * Respond to clicks in the "Next" or "Previous" buttons
    179      */
    180     @Override
    181     public void onClick(View view) {
    182         switch (view.getId()) {
    183             case R.id.next:
    184                 // Don't allow this more than once (Exchange accounts call an async method
    185                 // before finish()'ing the Activity, which allows this code to potentially be
    186                 // executed multiple times
    187                 if (!mDonePressed) {
    188                     onDone();
    189                     mDonePressed = true;
    190                 }
    191                 break;
    192             case R.id.previous:
    193                 onBackPressed();
    194                 break;
    195         }
    196     }
    197 
    198     /**
    199      * Ths is called when the user clicks the "done" button.
    200      * It collects the data from the UI, updates the setup account record, and commits
    201      * the account to the database (making it real for the first time.)
    202      * Finally, we call setupAccountManagerAccount(), which will eventually complete via callback.
    203      */
    204     @SuppressWarnings("deprecation")
    205     private void onDone() {
    206         final Account account = mSetupData.getAccount();
    207         if (account.isSaved()) {
    208             // Disrupting the normal flow could get us here, but if the account is already
    209             // saved, we've done this work
    210             return;
    211         } else if (account.mHostAuthRecv == null) {
    212             throw new IllegalStateException("in AccountSetupOptions with null mHostAuthRecv");
    213         }
    214 
    215         mIsProcessing = true;
    216         account.setDisplayName(account.getEmailAddress());
    217         int newFlags = account.getFlags() & ~(Account.FLAGS_BACKGROUND_ATTACHMENTS);
    218         if (mServiceInfo.offerAttachmentPreload && mBackgroundAttachmentsView.isChecked()) {
    219             newFlags |= Account.FLAGS_BACKGROUND_ATTACHMENTS;
    220         }
    221         account.setFlags(newFlags);
    222         account.setSyncInterval((Integer)((SpinnerOption)mCheckFrequencyView
    223                 .getSelectedItem()).value);
    224         if (mAccountSyncWindowRow.getVisibility() == View.VISIBLE) {
    225             account.setSyncLookback(
    226                     (Integer)((SpinnerOption)mSyncWindowView.getSelectedItem()).value);
    227         }
    228 
    229         // Finish setting up the account, and commit it to the database
    230         // Set the incomplete flag here to avoid reconciliation issues in ExchangeService
    231         account.mFlags |= Account.FLAGS_INCOMPLETE;
    232         if (mSetupData.getPolicy() != null) {
    233             account.mFlags |= Account.FLAGS_SECURITY_HOLD;
    234             account.mPolicy = mSetupData.getPolicy();
    235         }
    236 
    237         // Finally, write the completed account (for the first time) and then
    238         // install it into the Account manager as well.  These are done off-thread.
    239         // The account manager will report back via the callback, which will take us to
    240         // the next operations.
    241         final boolean email = mSyncEmailView.isChecked();
    242         final boolean calendar = mServiceInfo.syncCalendar && mSyncCalendarView.isChecked();
    243         final boolean contacts = mServiceInfo.syncContacts && mSyncContactsView.isChecked();
    244 
    245         showCreateAccountDialog();
    246         Utility.runAsync(new Runnable() {
    247             @Override
    248             public void run() {
    249                 final Context context = AccountSetupOptions.this;
    250                 AccountSettingsUtils.commitSettings(context, account);
    251                 EmailServiceUtils.setupAccountManagerAccount(context, account,
    252                         email, calendar, contacts, mAccountManagerCallback);
    253 
    254                 // We can move the notification setting to the inbox FolderPreferences later, once
    255                 // we know what the inbox is
    256                 final AccountPreferences accountPreferences =
    257                         new AccountPreferences(context, account.getEmailAddress());
    258                 accountPreferences.setDefaultInboxNotificationsEnabled(mNotifyView.isChecked());
    259             }
    260         });
    261     }
    262 
    263     private void showCreateAccountDialog() {
    264         /// Show "Creating account..." dialog
    265         mCreateAccountDialog = new ProgressDialog(this);
    266         mCreateAccountDialog.setIndeterminate(true);
    267         mCreateAccountDialog.setMessage(getString(R.string.account_setup_creating_account_msg));
    268         mCreateAccountDialog.show();
    269     }
    270 
    271     /**
    272      * This is called at the completion of MailService.setupAccountManagerAccount()
    273      */
    274     AccountManagerCallback<Bundle> mAccountManagerCallback = new AccountManagerCallback<Bundle>() {
    275         @Override
    276         public void run(AccountManagerFuture<Bundle> future) {
    277             try {
    278                 // Block until the operation completes
    279                 future.getResult();
    280                 AccountSetupOptions.this.runOnUiThread(new Runnable() {
    281                     @Override
    282                     public void run() {
    283                         optionsComplete();
    284                     }
    285                 });
    286                 return;
    287             } catch (OperationCanceledException e) {
    288                 LogUtils.d(Logging.LOG_TAG, "addAccount was canceled");
    289             } catch (IOException e) {
    290                 LogUtils.d(Logging.LOG_TAG, "addAccount failed: " + e);
    291             } catch (AuthenticatorException e) {
    292                 LogUtils.d(Logging.LOG_TAG, "addAccount failed: " + e);
    293             }
    294             showErrorDialog(R.string.account_setup_failed_dlg_auth_message,
    295                     R.string.system_account_create_failed);
    296         }
    297     };
    298 
    299     /**
    300      * This is called if MailService.setupAccountManagerAccount() fails for some reason
    301      */
    302     private void showErrorDialog(final int msgResId, final Object... args) {
    303         runOnUiThread(new Runnable() {
    304             @Override
    305             public void run() {
    306                 new AlertDialog.Builder(AccountSetupOptions.this)
    307                         .setIconAttribute(android.R.attr.alertDialogIcon)
    308                         .setTitle(getString(R.string.account_setup_failed_dlg_title))
    309                         .setMessage(getString(msgResId, args))
    310                         .setCancelable(true)
    311                         .setPositiveButton(
    312                                 getString(R.string.account_setup_failed_dlg_edit_details_action),
    313                                 new DialogInterface.OnClickListener() {
    314                                     @Override
    315                                     public void onClick(DialogInterface dialog, int which) {
    316                                         finish();
    317                                     }
    318                                 })
    319                         .show();
    320             }
    321         });
    322     }
    323 
    324     /**
    325      * This is called after the account manager creates the new account.
    326      */
    327     private void optionsComplete() {
    328         // If the account manager initiated the creation, report success at this point
    329         final AccountAuthenticatorResponse authenticatorResponse =
    330             mSetupData.getAccountAuthenticatorResponse();
    331         if (authenticatorResponse != null) {
    332             authenticatorResponse.onResult(null);
    333             mSetupData.setAccountAuthenticatorResponse(null);
    334         }
    335 
    336         // Now that AccountManager account creation is complete, clear the INCOMPLETE flag
    337         final Account account = mSetupData.getAccount();
    338         account.mFlags &= ~Account.FLAGS_INCOMPLETE;
    339         AccountSettingsUtils.commitSettings(AccountSetupOptions.this, account);
    340 
    341         // If we've got policies for this account, ask the user to accept.
    342         if ((account.mFlags & Account.FLAGS_SECURITY_HOLD) != 0) {
    343             final Intent intent =
    344                     AccountSecurity.actionUpdateSecurityIntent(this, account.mId, false);
    345             startActivityForResult(intent, AccountSetupOptions.REQUEST_CODE_ACCEPT_POLICIES);
    346             return;
    347         }
    348         saveAccountAndFinish();
    349 
    350         // Update the folder list (to get our starting folders, e.g. Inbox)
    351         final EmailServiceProxy proxy = EmailServiceUtils.getServiceForAccount(this, account.mId);
    352         try {
    353             proxy.updateFolderList(account.mId);
    354         } catch (RemoteException e) {
    355             // It's all good
    356         }
    357     }
    358 
    359     /**
    360      * This is called after the AccountSecurity activity completes.
    361      */
    362     @Override
    363     public void onActivityResult(int requestCode, int resultCode, Intent data) {
    364         saveAccountAndFinish();
    365     }
    366 
    367     /**
    368      * These are the final cleanup steps when creating an account:
    369      *  Clear incomplete & security hold flags
    370      *  Update account in DB
    371      *  Enable email services
    372      *  Enable exchange services
    373      *  Move to final setup screen
    374      */
    375     private void saveAccountAndFinish() {
    376         AsyncTask<Void, Void, Void> asyncTask = new AsyncTask<Void, Void, Void>() {
    377             @Override
    378             protected Void doInBackground(Void... params) {
    379                 final AccountSetupOptions context = AccountSetupOptions.this;
    380                 // Clear the security hold flag now
    381                 final Account account = mSetupData.getAccount();
    382                 account.mFlags &= ~Account.FLAGS_SECURITY_HOLD;
    383                 AccountSettingsUtils.commitSettings(context, account);
    384                 // Start up services based on new account(s)
    385                 MailActivityEmail.setServicesEnabledSync(context);
    386                 EmailServiceUtils.startService(context, account.mHostAuthRecv.mProtocol);
    387                 // Move to final setup screen
    388                 AccountSetupNames.actionSetNames(context, mSetupData);
    389                 finish();
    390                 return null;
    391             }
    392         };
    393         asyncTask.execute();
    394     }
    395 
    396     /**
    397      * Enable an additional spinner using the arrays normally handled by preferences
    398      */
    399     private void enableLookbackSpinner() {
    400         // Show everything
    401         mAccountSyncWindowRow.setVisibility(View.VISIBLE);
    402 
    403         // Generate spinner entries using XML arrays used by the preferences
    404         final CharSequence[] windowValues = getResources().getTextArray(
    405                 R.array.account_settings_mail_window_values);
    406         final CharSequence[] windowEntries = getResources().getTextArray(
    407                 R.array.account_settings_mail_window_entries);
    408 
    409         // Find a proper maximum for email lookback, based on policy (if we have one)
    410         int maxEntry = windowEntries.length;
    411         final Policy policy = mSetupData.getAccount().mPolicy;
    412         if (policy != null) {
    413             final int maxLookback = policy.mMaxEmailLookback;
    414             if (maxLookback != 0) {
    415                 // Offset/Code   0      1      2      3      4        5
    416                 // Entries      auto, 1 day, 3 day, 1 week, 2 week, 1 month
    417                 // Lookback     N/A   1 day, 3 day, 1 week, 2 week, 1 month
    418                 // Since our test below is i < maxEntry, we must set maxEntry to maxLookback + 1
    419                 maxEntry = maxLookback + 1;
    420             }
    421         }
    422 
    423         // Now create the array used by the Spinner
    424         final SpinnerOption[] windowOptions = new SpinnerOption[maxEntry];
    425         int defaultIndex = -1;
    426         for (int i = 0; i < maxEntry; i++) {
    427             final int value = Integer.valueOf(windowValues[i].toString());
    428             windowOptions[i] = new SpinnerOption(value, windowEntries[i].toString());
    429             if (value == SYNC_WINDOW_EAS_DEFAULT) {
    430                 defaultIndex = i;
    431             }
    432         }
    433 
    434         final ArrayAdapter<SpinnerOption> windowOptionsAdapter =
    435                 new ArrayAdapter<SpinnerOption>(this, android.R.layout.simple_spinner_item,
    436                         windowOptions);
    437         windowOptionsAdapter
    438                 .setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
    439         mSyncWindowView.setAdapter(windowOptionsAdapter);
    440 
    441         SpinnerOption.setSpinnerOptionValue(mSyncWindowView,
    442                 mSetupData.getAccount().getSyncLookback());
    443         if (defaultIndex >= 0) {
    444             mSyncWindowView.setSelection(defaultIndex);
    445         }
    446     }
    447 }
    448