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