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