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