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 74 public static void actionOptions(Activity fromActivity) { 75 fromActivity.startActivity(new Intent(fromActivity, AccountSetupOptions.class)); 76 } 77 78 @Override 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 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 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 */ 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 */ 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 */ 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 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 */ 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 */ 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