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