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.service.EmailServiceUtils;
32import com.android.email2.ui.MailActivityEmail;
33import com.android.emailcommon.provider.Account;
34import com.android.emailcommon.service.EmailServiceProxy;
35import com.android.mail.preferences.AccountPreferences;
36import com.android.mail.ui.MailAsyncTaskLoader;
37import com.android.mail.utils.LogUtils;
38
39import 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 */
46public 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