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