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
17package com.android.email.activity.setup;
18
19import android.accounts.AccountManagerFuture;
20import android.accounts.AuthenticatorException;
21import android.accounts.OperationCanceledException;
22import android.app.Fragment;
23import android.app.LoaderManager;
24import android.content.Context;
25import android.content.Intent;
26import android.content.Loader;
27import android.os.Bundle;
28import android.os.Handler;
29import android.os.RemoteException;
30
31import com.android.email.provider.EmailProvider;
32import com.android.email.service.EmailServiceUtils;
33import com.android.email2.ui.MailActivityEmail;
34import com.android.emailcommon.provider.Account;
35import com.android.emailcommon.service.EmailServiceProxy;
36import com.android.mail.preferences.AccountPreferences;
37import com.android.mail.ui.MailAsyncTaskLoader;
38import com.android.mail.utils.LogUtils;
39
40import 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 */
47public 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 {
69        void onAccountCreationFragmentComplete();
70        void destroyAccountCreationFragment();
71        void showCreateAccountErrorDialog();
72        void setAccount(Account account);
73    }
74
75    public AccountCreationFragment() {
76        mHandler = new Handler();
77    }
78
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
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
103    public void onActivityCreated(Bundle savedInstanceState) {
104        super.onActivityCreated(savedInstanceState);
105        mAppContext = getActivity().getApplicationContext();
106    }
107
108    @Override
109    public void onSaveInstanceState(Bundle outState) {
110        super.onSaveInstanceState(outState);
111        outState.putInt(SAVESTATE_STAGE, mStage);
112    }
113
114    @Override
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
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
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
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> {
163        public BeforeAccountSecurityCallbacks() {}
164
165        @Override
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
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
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