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