AccountSetupOptions.java revision 2fbb3db5d86210d03175ce77ff08c989a96c5864
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.activity.UiUtilities;
24import com.android.email.mail.Store;
25import com.android.email.service.MailService;
26import com.android.emailcommon.Logging;
27import com.android.emailcommon.provider.EmailContent;
28import com.android.emailcommon.provider.EmailContent.Account;
29import com.android.emailcommon.service.PolicySet;
30import com.android.emailcommon.service.SyncWindow;
31import com.android.emailcommon.utility.Utility;
32
33import android.accounts.AccountAuthenticatorResponse;
34import android.accounts.AccountManager;
35import android.accounts.AccountManagerCallback;
36import android.accounts.AccountManagerFuture;
37import android.accounts.AuthenticatorException;
38import android.accounts.OperationCanceledException;
39import android.app.Activity;
40import android.app.AlertDialog;
41import android.content.Context;
42import android.content.DialogInterface;
43import android.content.Intent;
44import android.os.Bundle;
45import android.util.Log;
46import android.view.View;
47import android.view.View.OnClickListener;
48import android.widget.ArrayAdapter;
49import android.widget.CheckBox;
50import android.widget.Spinner;
51
52import java.io.IOException;
53
54/**
55 * TODO: Cleanup the manipulation of Account.FLAGS_INCOMPLETE and make sure it's never left set.
56 */
57public class AccountSetupOptions extends AccountSetupActivity implements OnClickListener {
58
59    private Spinner mCheckFrequencyView;
60    private Spinner mSyncWindowView;
61    private CheckBox mDefaultView;
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
70    public static final int REQUEST_CODE_ACCEPT_POLICIES = 1;
71
72    /** Default sync window for new EAS accounts */
73    private static final int SYNC_WINDOW_EAS_DEFAULT = SyncWindow.SYNC_WINDOW_3_DAYS;
74
75    public static void actionOptions(Activity fromActivity) {
76        fromActivity.startActivity(new Intent(fromActivity, AccountSetupOptions.class));
77    }
78
79    @Override
80    public void onCreate(Bundle savedInstanceState) {
81        super.onCreate(savedInstanceState);
82        ActivityHelper.debugSetWindowFlags(this);
83        setContentView(R.layout.account_setup_options);
84
85        mCheckFrequencyView = (Spinner) UiUtilities.getView(this, R.id.account_check_frequency);
86        mSyncWindowView = (Spinner) UiUtilities.getView(this, R.id.account_sync_window);
87        mDefaultView = (CheckBox) UiUtilities.getView(this, R.id.account_default);
88        mNotifyView = (CheckBox) UiUtilities.getView(this, R.id.account_notify);
89        mSyncContactsView = (CheckBox) UiUtilities.getView(this, R.id.account_sync_contacts);
90        mSyncCalendarView = (CheckBox) UiUtilities.getView(this, R.id.account_sync_calendar);
91        mSyncEmailView = (CheckBox) UiUtilities.getView(this, R.id.account_sync_email);
92        mSyncEmailView.setChecked(true);
93        mBackgroundAttachmentsView = (CheckBox) UiUtilities.getView(this,
94                R.id.account_background_attachments);
95        mBackgroundAttachmentsView.setChecked(true);
96        UiUtilities.getView(this, R.id.previous).setOnClickListener(this);
97        UiUtilities.getView(this, R.id.next).setOnClickListener(this);
98        mAccountSyncWindowRow = UiUtilities.getView(this, R.id.account_sync_window_row);
99
100        // Generate spinner entries using XML arrays used by the preferences
101        int frequencyValuesId;
102        int frequencyEntriesId;
103        Account account = SetupData.getAccount();
104        Store.StoreInfo info = Store.StoreInfo.getStoreInfo(account.getStoreUri(this), this);
105        if (info.mPushSupported) {
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 (info.mVisibleLimitDefault == -1) {
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() & EmailContent.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".equals(info.mScheme)) {
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 ("pop3".equals(info.mScheme)) {
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        account.setDisplayName(account.getEmailAddress());
210        int newFlags = account.getFlags() &
211                ~(Account.FLAGS_NOTIFY_NEW_MAIL | Account.FLAGS_BACKGROUND_ATTACHMENTS);
212        if (mNotifyView.isChecked()) {
213            newFlags |= Account.FLAGS_NOTIFY_NEW_MAIL;
214        }
215        if (mBackgroundAttachmentsView.isChecked()) {
216            newFlags |= Account.FLAGS_BACKGROUND_ATTACHMENTS;
217        }
218        account.setFlags(newFlags);
219        account.setSyncInterval((Integer)((SpinnerOption)mCheckFrequencyView
220                .getSelectedItem()).value);
221        if (mAccountSyncWindowRow.getVisibility() == View.VISIBLE) {
222            int window = (Integer)((SpinnerOption)mSyncWindowView.getSelectedItem()).value;
223            account.setSyncLookback(window);
224        }
225        account.setDefaultAccount(mDefaultView.isChecked());
226
227        if (account.isSaved()) {
228            throw new IllegalStateException("in AccountSetupOptions with already-saved account");
229        }
230        if (account.mHostAuthRecv == null) {
231            throw new IllegalStateException("in AccountSetupOptions with null mHostAuthRecv");
232        }
233
234        // Finish setting up the account, and commit it to the database
235        // Set the incomplete flag here to avoid reconciliation issues in ExchangeService
236        account.mFlags |= Account.FLAGS_INCOMPLETE;
237        boolean calendar = false;
238        boolean contacts = false;
239        boolean email = mSyncEmailView.isChecked();
240        if (account.mHostAuthRecv.mProtocol.equals("eas")) {
241            // Set security hold if necessary to prevent sync until policies are accepted
242            PolicySet policySet = SetupData.getPolicySet();
243            if (policySet != null && policySet.getSecurityCode() != 0) {
244                account.mSecurityFlags = policySet.getSecurityCode();
245                account.mFlags |= Account.FLAGS_SECURITY_HOLD;
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        // If we've got policies for this account, ask the user to accept.
332        Account account = SetupData.getAccount();
333        if ((account.mFlags & Account.FLAGS_SECURITY_HOLD) != 0) {
334            Intent intent = AccountSecurity.actionUpdateSecurityIntent(this, account.mId, false);
335            startActivityForResult(intent, AccountSetupOptions.REQUEST_CODE_ACCEPT_POLICIES);
336            return;
337        }
338        saveAccountAndFinish();
339    }
340
341    /**
342     * This is called after the AccountSecurity activity completes.
343     */
344    @Override
345    public void onActivityResult(int requestCode, int resultCode, Intent data) {
346        saveAccountAndFinish();
347    }
348
349    /**
350     * These are the final cleanup steps when creating an account:
351     *  Clear incomplete & security hold flags
352     *  Update account in DB
353     *  Enable email services
354     *  Enable exchange services
355     *  Move to final setup screen
356     */
357    private void saveAccountAndFinish() {
358        Utility.runAsync(new Runnable() {
359            @Override
360            public void run() {
361                AccountSetupOptions context = AccountSetupOptions.this;
362                // Clear the incomplete/security hold flag now
363                Account account = SetupData.getAccount();
364                account.mFlags &= ~(Account.FLAGS_INCOMPLETE | Account.FLAGS_SECURITY_HOLD);
365                AccountSettingsUtils.commitSettings(context, account);
366                // Start up services based on new account(s)
367                Email.setServicesEnabledSync(context);
368                ExchangeUtils.startExchangeService(context);
369                // Move to final setup screen
370                AccountSetupNames.actionSetNames(context);
371                finish();
372            }
373        });
374    }
375
376    /**
377     * Enable an additional spinner using the arrays normally handled by preferences
378     */
379    private void enableEASSyncWindowSpinner() {
380        // Show everything
381        mAccountSyncWindowRow.setVisibility(View.VISIBLE);
382
383        // Generate spinner entries using XML arrays used by the preferences
384        CharSequence[] windowValues = getResources().getTextArray(
385                R.array.account_settings_mail_window_values);
386        CharSequence[] windowEntries = getResources().getTextArray(
387                R.array.account_settings_mail_window_entries);
388
389        // Now create the array used by the Spinner
390        SpinnerOption[] windowOptions = new SpinnerOption[windowEntries.length];
391        int defaultIndex = -1;
392        for (int i = 0; i < windowEntries.length; i++) {
393            final int value = Integer.valueOf(windowValues[i].toString());
394            windowOptions[i] = new SpinnerOption(value, windowEntries[i].toString());
395            if (value == SYNC_WINDOW_EAS_DEFAULT) {
396                defaultIndex = i;
397            }
398        }
399
400        ArrayAdapter<SpinnerOption> windowOptionsAdapter = new ArrayAdapter<SpinnerOption>(this,
401                android.R.layout.simple_spinner_item, windowOptions);
402        windowOptionsAdapter
403                .setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
404        mSyncWindowView.setAdapter(windowOptionsAdapter);
405
406        SpinnerOption.setSpinnerOptionValue(mSyncWindowView,
407                SetupData.getAccount().getSyncLookback());
408        if (defaultIndex >= 0) {
409            mSyncWindowView.setSelection(defaultIndex);
410        }
411    }
412}
413