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