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