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