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