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