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.app.ProgressDialog; 28 import android.content.Context; 29 import android.content.DialogInterface; 30 import android.content.Intent; 31 import android.os.AsyncTask; 32 import android.os.Bundle; 33 import android.os.RemoteException; 34 import android.view.View; 35 import android.view.View.OnClickListener; 36 import android.widget.ArrayAdapter; 37 import android.widget.CheckBox; 38 import android.widget.Spinner; 39 40 import com.android.email.R; 41 import com.android.email.activity.ActivityHelper; 42 import com.android.email.activity.UiUtilities; 43 import com.android.email.service.EmailServiceUtils; 44 import com.android.email.service.EmailServiceUtils.EmailServiceInfo; 45 import com.android.email2.ui.MailActivityEmail; 46 import com.android.emailcommon.Logging; 47 import com.android.emailcommon.provider.Account; 48 import com.android.emailcommon.provider.Policy; 49 import com.android.emailcommon.service.EmailServiceProxy; 50 import com.android.emailcommon.service.SyncWindow; 51 import com.android.emailcommon.utility.Utility; 52 import com.android.mail.preferences.AccountPreferences; 53 import com.android.mail.utils.LogUtils; 54 55 import java.io.IOException; 56 57 public class AccountSetupOptions extends AccountSetupActivity implements OnClickListener { 58 private static final String EXTRA_IS_PROCESSING_KEY = "com.android.email.is_processing"; 59 60 private Spinner mCheckFrequencyView; 61 private Spinner mSyncWindowView; 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 private EmailServiceInfo mServiceInfo; 70 private boolean mIsProcessing = false; 71 72 private ProgressDialog mCreateAccountDialog; 73 74 public static final int REQUEST_CODE_ACCEPT_POLICIES = 1; 75 76 /** Default sync window for new EAS accounts */ 77 private static final int SYNC_WINDOW_EAS_DEFAULT = SyncWindow.SYNC_WINDOW_1_WEEK; 78 79 public static void actionOptions(Activity fromActivity, SetupData setupData) { 80 final Intent intent = new ForwardingIntent(fromActivity, AccountSetupOptions.class); 81 intent.putExtra(SetupData.EXTRA_SETUP_DATA, setupData); 82 fromActivity.startActivity(intent); 83 } 84 85 @Override 86 public void onCreate(Bundle savedInstanceState) { 87 super.onCreate(savedInstanceState); 88 ActivityHelper.debugSetWindowFlags(this); 89 setContentView(R.layout.account_setup_options); 90 91 mCheckFrequencyView = UiUtilities.getView(this, R.id.account_check_frequency); 92 mSyncWindowView = UiUtilities.getView(this, R.id.account_sync_window); 93 mNotifyView = UiUtilities.getView(this, R.id.account_notify); 94 mSyncContactsView = UiUtilities.getView(this, R.id.account_sync_contacts); 95 mSyncCalendarView = UiUtilities.getView(this, R.id.account_sync_calendar); 96 mSyncEmailView = UiUtilities.getView(this, R.id.account_sync_email); 97 mSyncEmailView.setChecked(true); 98 mBackgroundAttachmentsView = UiUtilities.getView(this, R.id.account_background_attachments); 99 mBackgroundAttachmentsView.setChecked(true); 100 UiUtilities.getView(this, R.id.previous).setOnClickListener(this); 101 UiUtilities.getView(this, R.id.next).setOnClickListener(this); 102 mAccountSyncWindowRow = UiUtilities.getView(this, R.id.account_sync_window_row); 103 104 final Account account = mSetupData.getAccount(); 105 mServiceInfo = EmailServiceUtils.getServiceInfo(getApplicationContext(), 106 account.mHostAuthRecv.mProtocol); 107 final CharSequence[] frequencyValues = mServiceInfo.syncIntervals; 108 final CharSequence[] frequencyEntries = mServiceInfo.syncIntervalStrings; 109 110 // Now create the array used by the sync interval Spinner 111 final SpinnerOption[] checkFrequencies = new SpinnerOption[frequencyEntries.length]; 112 for (int i = 0; i < frequencyEntries.length; i++) { 113 checkFrequencies[i] = new SpinnerOption( 114 Integer.valueOf(frequencyValues[i].toString()), frequencyEntries[i].toString()); 115 } 116 final ArrayAdapter<SpinnerOption> checkFrequenciesAdapter = 117 new ArrayAdapter<SpinnerOption>(this, android.R.layout.simple_spinner_item, 118 checkFrequencies); 119 checkFrequenciesAdapter 120 .setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); 121 mCheckFrequencyView.setAdapter(checkFrequenciesAdapter); 122 123 if (mServiceInfo.offerLookback) { 124 enableLookbackSpinner(); 125 } 126 127 mNotifyView.setChecked(true); // By default, we want notifications on 128 SpinnerOption.setSpinnerOptionValue(mCheckFrequencyView, account.getSyncInterval()); 129 130 if (mServiceInfo.syncContacts) { 131 mSyncContactsView.setVisibility(View.VISIBLE); 132 mSyncContactsView.setChecked(true); 133 UiUtilities.setVisibilitySafe(this, R.id.account_sync_contacts_divider, View.VISIBLE); 134 } 135 if (mServiceInfo.syncCalendar) { 136 mSyncCalendarView.setVisibility(View.VISIBLE); 137 mSyncCalendarView.setChecked(true); 138 UiUtilities.setVisibilitySafe(this, R.id.account_sync_calendar_divider, View.VISIBLE); 139 } 140 141 if (!mServiceInfo.offerAttachmentPreload) { 142 mBackgroundAttachmentsView.setVisibility(View.GONE); 143 UiUtilities.setVisibilitySafe(this, R.id.account_background_attachments_divider, 144 View.GONE); 145 } 146 147 mIsProcessing = savedInstanceState != null && 148 savedInstanceState.getBoolean(EXTRA_IS_PROCESSING_KEY, false); 149 if (mIsProcessing) { 150 // We are already processing, so just show the dialog until we finish 151 showCreateAccountDialog(); 152 } else if (mSetupData.getFlowMode() == SetupData.FLOW_MODE_FORCE_CREATE) { 153 // If we are just visiting here to fill in details, exit immediately 154 onDone(); 155 } 156 } 157 158 @Override 159 public void onSaveInstanceState(Bundle outState) { 160 super.onSaveInstanceState(outState); 161 outState.putBoolean(EXTRA_IS_PROCESSING_KEY, mIsProcessing); 162 } 163 164 @Override 165 public void finish() { 166 // If the account manager initiated the creation, and success was not reported, 167 // then we assume that we're giving up (for any reason) - report failure. 168 final AccountAuthenticatorResponse authenticatorResponse = 169 mSetupData.getAccountAuthenticatorResponse(); 170 if (authenticatorResponse != null) { 171 authenticatorResponse.onError(AccountManager.ERROR_CODE_CANCELED, "canceled"); 172 mSetupData.setAccountAuthenticatorResponse(null); 173 } 174 super.finish(); 175 } 176 177 /** 178 * Respond to clicks in the "Next" or "Previous" buttons 179 */ 180 @Override 181 public void onClick(View view) { 182 switch (view.getId()) { 183 case R.id.next: 184 // Don't allow this more than once (Exchange accounts call an async method 185 // before finish()'ing the Activity, which allows this code to potentially be 186 // executed multiple times 187 if (!mDonePressed) { 188 onDone(); 189 mDonePressed = true; 190 } 191 break; 192 case R.id.previous: 193 onBackPressed(); 194 break; 195 } 196 } 197 198 /** 199 * Ths is called when the user clicks the "done" button. 200 * It collects the data from the UI, updates the setup account record, and commits 201 * the account to the database (making it real for the first time.) 202 * Finally, we call setupAccountManagerAccount(), which will eventually complete via callback. 203 */ 204 @SuppressWarnings("deprecation") 205 private void onDone() { 206 final Account account = mSetupData.getAccount(); 207 if (account.isSaved()) { 208 // Disrupting the normal flow could get us here, but if the account is already 209 // saved, we've done this work 210 return; 211 } else if (account.mHostAuthRecv == null) { 212 throw new IllegalStateException("in AccountSetupOptions with null mHostAuthRecv"); 213 } 214 215 mIsProcessing = true; 216 account.setDisplayName(account.getEmailAddress()); 217 int newFlags = account.getFlags() & ~(Account.FLAGS_BACKGROUND_ATTACHMENTS); 218 if (mServiceInfo.offerAttachmentPreload && mBackgroundAttachmentsView.isChecked()) { 219 newFlags |= Account.FLAGS_BACKGROUND_ATTACHMENTS; 220 } 221 account.setFlags(newFlags); 222 account.setSyncInterval((Integer)((SpinnerOption)mCheckFrequencyView 223 .getSelectedItem()).value); 224 if (mAccountSyncWindowRow.getVisibility() == View.VISIBLE) { 225 account.setSyncLookback( 226 (Integer)((SpinnerOption)mSyncWindowView.getSelectedItem()).value); 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 if (mSetupData.getPolicy() != null) { 233 account.mFlags |= Account.FLAGS_SECURITY_HOLD; 234 account.mPolicy = mSetupData.getPolicy(); 235 } 236 237 // Finally, write the completed account (for the first time) and then 238 // install it into the Account manager as well. These are done off-thread. 239 // The account manager will report back via the callback, which will take us to 240 // the next operations. 241 final boolean email = mSyncEmailView.isChecked(); 242 final boolean calendar = mServiceInfo.syncCalendar && mSyncCalendarView.isChecked(); 243 final boolean contacts = mServiceInfo.syncContacts && mSyncContactsView.isChecked(); 244 245 showCreateAccountDialog(); 246 Utility.runAsync(new Runnable() { 247 @Override 248 public void run() { 249 final Context context = AccountSetupOptions.this; 250 AccountSettingsUtils.commitSettings(context, account); 251 EmailServiceUtils.setupAccountManagerAccount(context, account, 252 email, calendar, contacts, mAccountManagerCallback); 253 254 // We can move the notification setting to the inbox FolderPreferences later, once 255 // we know what the inbox is 256 final AccountPreferences accountPreferences = 257 new AccountPreferences(context, account.getEmailAddress()); 258 accountPreferences.setDefaultInboxNotificationsEnabled(mNotifyView.isChecked()); 259 } 260 }); 261 } 262 263 private void showCreateAccountDialog() { 264 /// Show "Creating account..." dialog 265 mCreateAccountDialog = new ProgressDialog(this); 266 mCreateAccountDialog.setIndeterminate(true); 267 mCreateAccountDialog.setMessage(getString(R.string.account_setup_creating_account_msg)); 268 mCreateAccountDialog.show(); 269 } 270 271 /** 272 * This is called at the completion of MailService.setupAccountManagerAccount() 273 */ 274 AccountManagerCallback<Bundle> mAccountManagerCallback = new AccountManagerCallback<Bundle>() { 275 @Override 276 public void run(AccountManagerFuture<Bundle> future) { 277 try { 278 // Block until the operation completes 279 future.getResult(); 280 AccountSetupOptions.this.runOnUiThread(new Runnable() { 281 @Override 282 public void run() { 283 optionsComplete(); 284 } 285 }); 286 return; 287 } catch (OperationCanceledException e) { 288 LogUtils.d(Logging.LOG_TAG, "addAccount was canceled"); 289 } catch (IOException e) { 290 LogUtils.d(Logging.LOG_TAG, "addAccount failed: " + e); 291 } catch (AuthenticatorException e) { 292 LogUtils.d(Logging.LOG_TAG, "addAccount failed: " + e); 293 } 294 showErrorDialog(R.string.account_setup_failed_dlg_auth_message, 295 R.string.system_account_create_failed); 296 } 297 }; 298 299 /** 300 * This is called if MailService.setupAccountManagerAccount() fails for some reason 301 */ 302 private void showErrorDialog(final int msgResId, final Object... args) { 303 runOnUiThread(new Runnable() { 304 @Override 305 public void run() { 306 new AlertDialog.Builder(AccountSetupOptions.this) 307 .setIconAttribute(android.R.attr.alertDialogIcon) 308 .setTitle(getString(R.string.account_setup_failed_dlg_title)) 309 .setMessage(getString(msgResId, args)) 310 .setCancelable(true) 311 .setPositiveButton( 312 getString(R.string.account_setup_failed_dlg_edit_details_action), 313 new DialogInterface.OnClickListener() { 314 @Override 315 public void onClick(DialogInterface dialog, int which) { 316 finish(); 317 } 318 }) 319 .show(); 320 } 321 }); 322 } 323 324 /** 325 * This is called after the account manager creates the new account. 326 */ 327 private void optionsComplete() { 328 // If the account manager initiated the creation, report success at this point 329 final AccountAuthenticatorResponse authenticatorResponse = 330 mSetupData.getAccountAuthenticatorResponse(); 331 if (authenticatorResponse != null) { 332 authenticatorResponse.onResult(null); 333 mSetupData.setAccountAuthenticatorResponse(null); 334 } 335 336 // Now that AccountManager account creation is complete, clear the INCOMPLETE flag 337 final Account account = mSetupData.getAccount(); 338 account.mFlags &= ~Account.FLAGS_INCOMPLETE; 339 AccountSettingsUtils.commitSettings(AccountSetupOptions.this, account); 340 341 // If we've got policies for this account, ask the user to accept. 342 if ((account.mFlags & Account.FLAGS_SECURITY_HOLD) != 0) { 343 final Intent intent = 344 AccountSecurity.actionUpdateSecurityIntent(this, account.mId, false); 345 startActivityForResult(intent, AccountSetupOptions.REQUEST_CODE_ACCEPT_POLICIES); 346 return; 347 } 348 saveAccountAndFinish(); 349 350 // Update the folder list (to get our starting folders, e.g. Inbox) 351 final EmailServiceProxy proxy = EmailServiceUtils.getServiceForAccount(this, account.mId); 352 try { 353 proxy.updateFolderList(account.mId); 354 } catch (RemoteException e) { 355 // It's all good 356 } 357 } 358 359 /** 360 * This is called after the AccountSecurity activity completes. 361 */ 362 @Override 363 public void onActivityResult(int requestCode, int resultCode, Intent data) { 364 saveAccountAndFinish(); 365 } 366 367 /** 368 * These are the final cleanup steps when creating an account: 369 * Clear incomplete & security hold flags 370 * Update account in DB 371 * Enable email services 372 * Enable exchange services 373 * Move to final setup screen 374 */ 375 private void saveAccountAndFinish() { 376 AsyncTask<Void, Void, Void> asyncTask = new AsyncTask<Void, Void, Void>() { 377 @Override 378 protected Void doInBackground(Void... params) { 379 final AccountSetupOptions context = AccountSetupOptions.this; 380 // Clear the security hold flag now 381 final Account account = mSetupData.getAccount(); 382 account.mFlags &= ~Account.FLAGS_SECURITY_HOLD; 383 AccountSettingsUtils.commitSettings(context, account); 384 // Start up services based on new account(s) 385 MailActivityEmail.setServicesEnabledSync(context); 386 EmailServiceUtils.startService(context, account.mHostAuthRecv.mProtocol); 387 // Move to final setup screen 388 AccountSetupNames.actionSetNames(context, mSetupData); 389 finish(); 390 return null; 391 } 392 }; 393 asyncTask.execute(); 394 } 395 396 /** 397 * Enable an additional spinner using the arrays normally handled by preferences 398 */ 399 private void enableLookbackSpinner() { 400 // Show everything 401 mAccountSyncWindowRow.setVisibility(View.VISIBLE); 402 403 // Generate spinner entries using XML arrays used by the preferences 404 final CharSequence[] windowValues = getResources().getTextArray( 405 R.array.account_settings_mail_window_values); 406 final CharSequence[] windowEntries = getResources().getTextArray( 407 R.array.account_settings_mail_window_entries); 408 409 // Find a proper maximum for email lookback, based on policy (if we have one) 410 int maxEntry = windowEntries.length; 411 final Policy policy = mSetupData.getAccount().mPolicy; 412 if (policy != null) { 413 final int maxLookback = policy.mMaxEmailLookback; 414 if (maxLookback != 0) { 415 // Offset/Code 0 1 2 3 4 5 416 // Entries auto, 1 day, 3 day, 1 week, 2 week, 1 month 417 // Lookback N/A 1 day, 3 day, 1 week, 2 week, 1 month 418 // Since our test below is i < maxEntry, we must set maxEntry to maxLookback + 1 419 maxEntry = maxLookback + 1; 420 } 421 } 422 423 // Now create the array used by the Spinner 424 final SpinnerOption[] windowOptions = new SpinnerOption[maxEntry]; 425 int defaultIndex = -1; 426 for (int i = 0; i < maxEntry; i++) { 427 final int value = Integer.valueOf(windowValues[i].toString()); 428 windowOptions[i] = new SpinnerOption(value, windowEntries[i].toString()); 429 if (value == SYNC_WINDOW_EAS_DEFAULT) { 430 defaultIndex = i; 431 } 432 } 433 434 final ArrayAdapter<SpinnerOption> windowOptionsAdapter = 435 new ArrayAdapter<SpinnerOption>(this, android.R.layout.simple_spinner_item, 436 windowOptions); 437 windowOptionsAdapter 438 .setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); 439 mSyncWindowView.setAdapter(windowOptionsAdapter); 440 441 SpinnerOption.setSpinnerOptionValue(mSyncWindowView, 442 mSetupData.getAccount().getSyncLookback()); 443 if (defaultIndex >= 0) { 444 mSyncWindowView.setSelection(defaultIndex); 445 } 446 } 447 } 448