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