1 /* 2 * Copyright (C) 2014 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.AccountManagerFuture; 20 import android.accounts.AuthenticatorException; 21 import android.accounts.OperationCanceledException; 22 import android.app.Fragment; 23 import android.app.LoaderManager; 24 import android.content.Context; 25 import android.content.Intent; 26 import android.content.Loader; 27 import android.os.Bundle; 28 import android.os.Handler; 29 import android.os.RemoteException; 30 31 import com.android.email.service.EmailServiceUtils; 32 import com.android.email2.ui.MailActivityEmail; 33 import com.android.emailcommon.provider.Account; 34 import com.android.emailcommon.service.EmailServiceProxy; 35 import com.android.mail.preferences.AccountPreferences; 36 import com.android.mail.ui.MailAsyncTaskLoader; 37 import com.android.mail.utils.LogUtils; 38 39 import java.io.IOException; 40 41 /** 42 * This retained headless fragment acts as a container for the multi-step task of creating the 43 * AccountManager account and saving our account object to the database, as well as some misc 44 * related background tasks. 45 */ 46 public class AccountCreationFragment extends Fragment { 47 public static final String TAG = "AccountCreationFragment"; 48 49 public static final int REQUEST_CODE_ACCEPT_POLICIES = 1; 50 51 private static final String ACCOUNT_TAG = "account"; 52 private static final String SYNC_EMAIL_TAG = "email"; 53 private static final String SYNC_CALENDAR_TAG = "calendar"; 54 private static final String SYNC_CONTACTS_TAG = "contacts"; 55 private static final String NOTIFICATIONS_TAG = "notifications"; 56 57 private static final String SAVESTATE_STAGE = "AccountCreationFragment.stage"; 58 private static final int STAGE_BEFORE_ACCOUNT_SECURITY = 0; 59 private static final int STAGE_REFRESHING_ACCOUNT = 1; 60 private static final int STAGE_WAITING_FOR_ACCOUNT_SECURITY = 2; 61 private static final int STAGE_AFTER_ACCOUNT_SECURITY = 3; 62 private int mStage = 0; 63 64 private Context mAppContext; 65 private final Handler mHandler; 66 67 public interface Callback { 68 void onAccountCreationFragmentComplete(); 69 void destroyAccountCreationFragment(); 70 void showCreateAccountErrorDialog(); 71 void setAccount(Account account); 72 } 73 74 public AccountCreationFragment() { 75 mHandler = new Handler(); 76 } 77 78 public static AccountCreationFragment newInstance(Account account, boolean syncEmail, 79 boolean syncCalendar, boolean syncContacts, boolean enableNotifications) { 80 final Bundle args = new Bundle(5); 81 args.putParcelable(AccountCreationFragment.ACCOUNT_TAG, account); 82 args.putBoolean(AccountCreationFragment.SYNC_EMAIL_TAG, syncEmail); 83 args.putBoolean(AccountCreationFragment.SYNC_CALENDAR_TAG, syncCalendar); 84 args.putBoolean(AccountCreationFragment.SYNC_CONTACTS_TAG, syncContacts); 85 args.putBoolean(AccountCreationFragment.NOTIFICATIONS_TAG, enableNotifications); 86 87 final AccountCreationFragment f = new AccountCreationFragment(); 88 f.setArguments(args); 89 return f; 90 } 91 92 @Override 93 public void onCreate(Bundle savedInstanceState) { 94 super.onCreate(savedInstanceState); 95 setRetainInstance(true); 96 if (savedInstanceState != null) { 97 mStage = savedInstanceState.getInt(SAVESTATE_STAGE); 98 } 99 } 100 101 @Override 102 public void onActivityCreated(Bundle savedInstanceState) { 103 super.onActivityCreated(savedInstanceState); 104 mAppContext = getActivity().getApplicationContext(); 105 } 106 107 @Override 108 public void onSaveInstanceState(Bundle outState) { 109 super.onSaveInstanceState(outState); 110 outState.putInt(SAVESTATE_STAGE, mStage); 111 } 112 113 @Override 114 public void onResume() { 115 super.onResume(); 116 117 switch (mStage) { 118 case STAGE_BEFORE_ACCOUNT_SECURITY: 119 kickBeforeAccountSecurityLoader(); 120 break; 121 case STAGE_REFRESHING_ACCOUNT: 122 kickRefreshingAccountLoader(); 123 break; 124 case STAGE_WAITING_FOR_ACCOUNT_SECURITY: 125 // TODO: figure out when we might get here and what to do if we do 126 break; 127 case STAGE_AFTER_ACCOUNT_SECURITY: 128 kickAfterAccountSecurityLoader(); 129 break; 130 } 131 } 132 133 private void kickBeforeAccountSecurityLoader() { 134 final LoaderManager loaderManager = getLoaderManager(); 135 136 loaderManager.destroyLoader(STAGE_REFRESHING_ACCOUNT); 137 loaderManager.destroyLoader(STAGE_AFTER_ACCOUNT_SECURITY); 138 loaderManager.initLoader(STAGE_BEFORE_ACCOUNT_SECURITY, getArguments(), 139 new BeforeAccountSecurityCallbacks()); 140 } 141 142 private void kickRefreshingAccountLoader() { 143 final LoaderManager loaderManager = getLoaderManager(); 144 145 loaderManager.destroyLoader(STAGE_BEFORE_ACCOUNT_SECURITY); 146 loaderManager.destroyLoader(STAGE_AFTER_ACCOUNT_SECURITY); 147 loaderManager.initLoader(STAGE_REFRESHING_ACCOUNT, getArguments(), 148 new RefreshAccountCallbacks()); 149 } 150 151 private void kickAfterAccountSecurityLoader() { 152 final LoaderManager loaderManager = getLoaderManager(); 153 154 loaderManager.destroyLoader(STAGE_BEFORE_ACCOUNT_SECURITY); 155 loaderManager.destroyLoader(STAGE_REFRESHING_ACCOUNT); 156 loaderManager.initLoader(STAGE_AFTER_ACCOUNT_SECURITY, getArguments(), 157 new AfterAccountSecurityCallbacks()); 158 } 159 160 private class BeforeAccountSecurityCallbacks 161 implements LoaderManager.LoaderCallbacks<Boolean> { 162 public BeforeAccountSecurityCallbacks() {} 163 164 @Override 165 public Loader<Boolean> onCreateLoader(int id, Bundle args) { 166 final Account account = args.getParcelable(ACCOUNT_TAG); 167 final boolean email = args.getBoolean(SYNC_EMAIL_TAG); 168 final boolean calendar = args.getBoolean(SYNC_CALENDAR_TAG); 169 final boolean contacts = args.getBoolean(SYNC_CONTACTS_TAG); 170 final boolean notificationsEnabled = args.getBoolean(NOTIFICATIONS_TAG); 171 172 /** 173 * Task loader returns true if we created the account, false if we bailed out. 174 */ 175 return new MailAsyncTaskLoader<Boolean>(mAppContext) { 176 @Override 177 protected void onDiscardResult(Boolean result) {} 178 179 @Override 180 public Boolean loadInBackground() { 181 // Set the incomplete flag here to avoid reconciliation issues 182 account.mFlags |= Account.FLAGS_INCOMPLETE; 183 184 AccountSettingsUtils.commitSettings(mAppContext, account); 185 final AccountManagerFuture<Bundle> future = 186 EmailServiceUtils.setupAccountManagerAccount(mAppContext, account, 187 email, calendar, contacts, null); 188 189 boolean createSuccess = false; 190 try { 191 future.getResult(); 192 createSuccess = true; 193 } catch (OperationCanceledException e) { 194 LogUtils.d(LogUtils.TAG, "addAccount was canceled"); 195 } catch (IOException e) { 196 LogUtils.d(LogUtils.TAG, "addAccount failed: " + e); 197 } catch (AuthenticatorException e) { 198 LogUtils.d(LogUtils.TAG, "addAccount failed: " + e); 199 } 200 if (!createSuccess) { 201 return false; 202 } 203 // We can move the notification setting to the inbox FolderPreferences 204 // later, once we know what the inbox is 205 new AccountPreferences(mAppContext, account.getEmailAddress()) 206 .setDefaultInboxNotificationsEnabled(notificationsEnabled); 207 208 // Now that AccountManager account creation is complete, clear the 209 // INCOMPLETE flag 210 account.mFlags &= ~Account.FLAGS_INCOMPLETE; 211 AccountSettingsUtils.commitSettings(mAppContext, account); 212 213 return true; 214 } 215 }; 216 } 217 218 @Override 219 public void onLoadFinished(Loader<Boolean> loader, Boolean success) { 220 if (success == null || !isResumed()) { 221 return; 222 } 223 if (success) { 224 mStage = STAGE_REFRESHING_ACCOUNT; 225 kickRefreshingAccountLoader(); 226 } else { 227 final Callback callback = (Callback) getActivity(); 228 mHandler.post(new Runnable() { 229 @Override 230 public void run() { 231 if (!isResumed()) { 232 return; 233 } 234 // Can't do this from within onLoadFinished 235 callback.destroyAccountCreationFragment(); 236 callback.showCreateAccountErrorDialog(); 237 } 238 }); 239 } 240 } 241 242 @Override 243 public void onLoaderReset(Loader<Boolean> loader) {} 244 } 245 246 private class RefreshAccountCallbacks implements LoaderManager.LoaderCallbacks<Account> { 247 248 @Override 249 public Loader<Account> onCreateLoader(int id, Bundle args) { 250 final Account account = args.getParcelable(ACCOUNT_TAG); 251 return new MailAsyncTaskLoader<Account>(mAppContext) { 252 @Override 253 protected void onDiscardResult(Account result) {} 254 255 @Override 256 public Account loadInBackground() { 257 account.refresh(mAppContext); 258 return account; 259 } 260 }; 261 } 262 263 @Override 264 public void onLoadFinished(Loader<Account> loader, Account account) { 265 if (account == null || !isResumed()) { 266 return; 267 } 268 269 getArguments().putParcelable(ACCOUNT_TAG, account); 270 271 if ((account.mFlags & Account.FLAGS_SECURITY_HOLD) != 0) { 272 final Intent intent = AccountSecurity 273 .actionUpdateSecurityIntent(getActivity(), account.mId, false); 274 startActivityForResult(intent, REQUEST_CODE_ACCEPT_POLICIES); 275 mStage = STAGE_WAITING_FOR_ACCOUNT_SECURITY; 276 } else { 277 mStage = STAGE_AFTER_ACCOUNT_SECURITY; 278 kickAfterAccountSecurityLoader(); 279 } 280 } 281 282 @Override 283 public void onLoaderReset(Loader<Account> loader) {} 284 } 285 286 private class AfterAccountSecurityCallbacks 287 implements LoaderManager.LoaderCallbacks<Account> { 288 @Override 289 public Loader<Account> onCreateLoader(int id, Bundle args) { 290 final Account account = args.getParcelable(ACCOUNT_TAG); 291 return new MailAsyncTaskLoader<Account>(mAppContext) { 292 @Override 293 protected void onDiscardResult(Account result) {} 294 295 @Override 296 public Account loadInBackground() { 297 // Clear the security hold flag now 298 account.mFlags &= ~Account.FLAGS_SECURITY_HOLD; 299 AccountSettingsUtils.commitSettings(mAppContext, account); 300 // Start up services based on new account(s) 301 MailActivityEmail.setServicesEnabledSync(mAppContext); 302 EmailServiceUtils 303 .startService(mAppContext, account.mHostAuthRecv.mProtocol); 304 return account; 305 } 306 }; 307 } 308 309 @Override 310 public void onLoadFinished(final Loader<Account> loader, final Account account) { 311 // Need to do this from a runnable because this triggers fragment transactions 312 mHandler.post(new Runnable() { 313 @Override 314 public void run() { 315 if (account == null || !isResumed()) { 316 return; 317 } 318 319 // Move to final setup screen 320 Callback callback = (Callback) getActivity(); 321 callback.setAccount(account); 322 callback.onAccountCreationFragmentComplete(); 323 324 // Update the folder list (to get our starting folders, e.g. Inbox) 325 final EmailServiceProxy proxy = EmailServiceUtils 326 .getServiceForAccount(mAppContext, account.mId); 327 try { 328 proxy.updateFolderList(account.mId); 329 } catch (RemoteException e) { 330 // It's all good 331 } 332 333 } 334 }); 335 } 336 337 @Override 338 public void onLoaderReset(Loader<Account> loader) {} 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 mStage = STAGE_AFTER_ACCOUNT_SECURITY; 347 // onResume() will be called immediately after this to kick the next loader 348 } 349 } 350