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