1/*
2 * Copyright (C) 2012 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 */
16package com.android.mail.ui;
17
18import com.android.mail.R;
19import com.android.mail.analytics.Analytics;
20import com.android.mail.providers.Account;
21import com.android.mail.providers.MailAppProvider;
22import com.android.mail.providers.UIProvider;
23import com.android.mail.utils.LogTag;
24
25import java.util.ArrayList;
26
27import android.app.Fragment;
28import android.app.FragmentTransaction;
29import android.app.LoaderManager;
30import android.appwidget.AppWidgetManager;
31import android.content.ContentResolver;
32import android.content.CursorLoader;
33import android.content.Intent;
34import android.content.Loader;
35import android.database.Cursor;
36import android.os.AsyncTask;
37import android.os.Bundle;
38import android.os.Handler;
39import android.support.v7.app.ActionBarActivity;
40import android.view.View;
41import android.view.View.OnClickListener;
42import android.view.ViewGroup;
43import android.widget.AdapterView;
44import android.widget.ListView;
45import android.widget.SimpleCursorAdapter;
46import android.widget.TextView;
47
48/**
49 * An activity that shows the list of all the available accounts and return the
50 * one selected in onResult().
51 */
52public class MailboxSelectionActivity extends ActionBarActivity implements OnClickListener,
53        LoaderManager.LoaderCallbacks<Cursor>, AdapterView.OnItemClickListener {
54
55    // Used to save our instance state
56    private static final String CREATE_SHORTCUT_KEY = "createShortcut";
57    private static final String CREATE_WIDGET_KEY = "createWidget";
58    private static final String WIDGET_ID_KEY = "widgetId";
59    private static final String WAITING_FOR_ADD_ACCOUNT_RESULT_KEY = "waitingForAddAccountResult";
60
61    private static final String ACCOUNT = "name";
62    private static final String[] COLUMN_NAMES = { ACCOUNT };
63    protected static final String LOG_TAG = LogTag.getLogTag();
64    private static final int RESULT_CREATE_ACCOUNT = 2;
65    private static final int LOADER_ACCOUNT_CURSOR = 0;
66    private static final String TAG_WAIT = "wait-fragment";
67    private final int[] VIEW_IDS = { R.id.mailbox_name };
68    private boolean mCreateShortcut = false;
69    private boolean mConfigureWidget = false;
70    private SimpleCursorAdapter mAdapter;
71    private int mAppWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID;
72
73    // Boolean to indicate that we are waiting for the result from an add account
74    // operation.  This boolean is necessary, as there is no guarantee on whether the
75    // AccountManager callback or onResume will be called first.
76    boolean mWaitingForAddAccountResult = false;
77
78    // Can only do certain actions if the Activity is resumed (e.g. setVisible)
79    private boolean mResumed = false;
80    private Handler mHandler = new Handler();
81    private ListView mList;
82    private View mContent;
83    private View mWait;
84
85    @Override
86    public void onCreate(Bundle icicle) {
87        super.onCreate(icicle);
88        setContentView(R.layout.mailbox_selection_activity);
89        mList = (ListView) findViewById(android.R.id.list);
90        mList.setOnItemClickListener(this);
91        mContent = findViewById(R.id.content);
92        mWait = findViewById(R.id.wait);
93        if (icicle != null) {
94            restoreState(icicle);
95        } else {
96            if (Intent.ACTION_CREATE_SHORTCUT.equals(getIntent().getAction())) {
97                mCreateShortcut = true;
98            }
99            mAppWidgetId = getIntent().getIntExtra(
100                    AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID);
101            if (mAppWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID) {
102                mConfigureWidget = true;
103            }
104        }
105        // We set the default title to "Gmail" or "Google Mail" for consistency
106        // in Task Switcher. If this is for create shortcut or configure widget,
107        // we should set the title to "Select account".
108        if (mCreateShortcut || mConfigureWidget) {
109            setTitle(getResources().getString(R.string.activity_mailbox_selection));
110        }
111        findViewById(R.id.first_button).setOnClickListener(this);
112
113        // Initially, assume that the main view is invisible.  It will be made visible,
114        // if we display the account list
115        setVisible(false);
116        setResult(RESULT_CANCELED);
117    }
118
119    @Override
120    protected void onSaveInstanceState(Bundle icicle) {
121        super.onSaveInstanceState(icicle);
122
123        icicle.putBoolean(CREATE_SHORTCUT_KEY, mCreateShortcut);
124        icicle.putBoolean(CREATE_WIDGET_KEY, mConfigureWidget);
125        if (mAppWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID) {
126            icicle.putInt(WIDGET_ID_KEY, mAppWidgetId);
127        }
128        icicle.putBoolean(WAITING_FOR_ADD_ACCOUNT_RESULT_KEY, mWaitingForAddAccountResult);
129    }
130
131    @Override
132    public void onStart() {
133        super.onStart();
134
135        Analytics.getInstance().activityStart(this);
136    }
137
138    @Override
139    protected void onStop() {
140        super.onStop();
141
142        Analytics.getInstance().activityStop(this);
143    }
144
145    @Override
146    public void onResume() {
147        super.onResume();
148        mResumed = true;
149        // Only fetch the accounts, if we are not handling a response from the
150        // launched child activity.
151        if (!mWaitingForAddAccountResult) {
152            setupWithAccounts();
153        }
154    }
155
156    @Override
157    public void onPause() {
158        super.onPause();
159        mResumed = false;
160    }
161
162    @Override
163    public void onNewIntent(Intent intent) {
164        super.onNewIntent(intent);
165        setIntent(intent);
166    }
167
168    /**
169     * Restores the activity state from a bundle
170     */
171    private void restoreState(Bundle icicle) {
172        if (icicle.containsKey(CREATE_SHORTCUT_KEY)) {
173            mCreateShortcut = icicle.getBoolean(CREATE_SHORTCUT_KEY);
174        }
175        if (icicle.containsKey(CREATE_WIDGET_KEY)) {
176            mConfigureWidget = icicle.getBoolean(CREATE_WIDGET_KEY);
177        }
178        if (icicle.containsKey(WIDGET_ID_KEY)) {
179            mAppWidgetId = icicle.getInt(WIDGET_ID_KEY);
180        }
181        if (icicle.containsKey(WAITING_FOR_ADD_ACCOUNT_RESULT_KEY)) {
182            mWaitingForAddAccountResult = icicle.getBoolean(WAITING_FOR_ADD_ACCOUNT_RESULT_KEY);
183        }
184    }
185
186    private void setupWithAccounts() {
187        final ContentResolver resolver = getContentResolver();
188        new AsyncTask<Void, Void, Void>() {
189            @Override
190            protected Void doInBackground(Void... params) {
191                Cursor cursor = null;
192                try {
193                    cursor = resolver.query(MailAppProvider.getAccountsUri(),
194                            UIProvider.ACCOUNTS_PROJECTION, null, null, null);
195                    completeSetupWithAccounts(cursor);
196                } finally {
197                    if (cursor != null) {
198                        cursor.close();
199                    }
200                }
201                return null;
202            }
203
204        }.execute();
205    }
206
207    private void completeSetupWithAccounts(final Cursor accounts) {
208        mHandler.post(new Runnable() {
209            @Override
210            public void run() {
211                updateAccountList(accounts);
212            }
213        });
214    }
215
216    private void updateAccountList(final Cursor accounts) {
217        boolean displayAccountList = true;
218        // Configuring a widget or shortcut.
219        if (mConfigureWidget || mCreateShortcut) {
220            if (accounts == null || accounts.getCount() == 0) {
221                // No account found, show Add Account screen, for both the widget or
222                // shortcut creation process
223                // No account found, show Add Account screen, for both the widget or
224                // shortcut creation process
225                final Intent noAccountIntent = MailAppProvider.getNoAccountIntent(this);
226                if (noAccountIntent != null) {
227                    startActivityForResult(noAccountIntent, RESULT_CREATE_ACCOUNT);
228                }
229                // No reason to display the account list
230                displayAccountList = false;
231
232                // Indicate that we need to handle the response from the add account action
233                // This allows us to process the results that we get in the AddAccountCallback
234                mWaitingForAddAccountResult = true;
235            } else if (mConfigureWidget && accounts.getCount() == 1) {
236                mWait.setVisibility(View.GONE);
237                // When configuring a widget, if there is only one account, automatically
238                // choose that account.
239                accounts.moveToFirst();
240                selectAccount(Account.builder().buildFrom(accounts));
241                // No reason to display the account list
242                displayAccountList = false;
243            }
244        }
245
246        if (displayAccountList) {
247            mContent.setVisibility(View.VISIBLE);
248            // We are about to display the list, make this activity visible
249            // But only if the Activity is not paused!
250            if (mResumed) {
251                setVisible(true);
252            }
253
254            mAdapter = new SimpleCursorAdapter(this, R.layout.mailbox_item, accounts,
255                    COLUMN_NAMES, VIEW_IDS, 0) {
256                @Override
257                public View getView(int position, View convertView, ViewGroup parent) {
258                    View v = super.getView(position, convertView, parent);
259                    TextView accountView = (TextView) v.findViewById(R.id.mailbox_name);
260                    final Account account = Account.builder().buildFrom((Cursor) getItem(position));
261                    accountView.setText(account.getDisplayName());
262                    return v;
263                }
264            };
265            mList.setAdapter(mAdapter);
266        }
267    }
268
269    @Override
270    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
271        selectAccount(Account.builder().buildFrom((Cursor) mAdapter.getItem(position)));
272    }
273
274    private void selectAccount(Account account) {
275        if (mCreateShortcut || mConfigureWidget) {
276            // Invoked for a shortcut creation
277            final Intent intent = new Intent(this, getFolderSelectionActivity());
278            intent.setFlags(
279                    Intent.FLAG_ACTIVITY_NO_HISTORY | Intent.FLAG_ACTIVITY_FORWARD_RESULT);
280            intent.setAction(mCreateShortcut ?
281                    Intent.ACTION_CREATE_SHORTCUT : AppWidgetManager.ACTION_APPWIDGET_CONFIGURE);
282            if (mConfigureWidget) {
283                intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetId);
284            }
285            intent.putExtra(FolderSelectionActivity.EXTRA_ACCOUNT_SHORTCUT, account);
286            startActivity(intent);
287            finish();
288        } else {
289            // TODO: (mindyp) handle changing the account for this shortcut.
290            finish();
291        }
292    }
293
294    /**
295     * Return the class responsible for launching the folder selection activity.
296     */
297    protected Class<?> getFolderSelectionActivity() {
298        return FolderSelectionActivity.class;
299    }
300
301    @Override
302    public void onClick(View v) {
303        final int id = v.getId();
304        if (id == R.id.first_button) {
305            setResult(RESULT_CANCELED);
306            finish();
307        }
308    }
309
310    @Override
311    protected final void onActivityResult(int request, int result, Intent data) {
312        if (request == RESULT_CREATE_ACCOUNT) {
313            // We were waiting for the user to create an account
314            if (result != RESULT_OK) {
315                finish();
316            } else {
317                // Watch for accounts to show up!
318                // restart the loader to get the updated list of accounts
319                getLoaderManager().initLoader(LOADER_ACCOUNT_CURSOR, null, this);
320                showWaitFragment(null);
321            }
322        }
323    }
324
325    private void showWaitFragment(Account account) {
326        WaitFragment fragment = getWaitFragment();
327        if (fragment != null) {
328            fragment.updateAccount(account);
329        } else {
330            mWait.setVisibility(View.VISIBLE);
331            replaceFragment(WaitFragment.newInstance(account, false /* expectingMessages */),
332                    FragmentTransaction.TRANSIT_FRAGMENT_OPEN, TAG_WAIT);
333        }
334        mContent.setVisibility(View.GONE);
335    }
336
337    @Override
338    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
339        switch (id) {
340            case LOADER_ACCOUNT_CURSOR:
341                return new CursorLoader(this, MailAppProvider.getAccountsUri(),
342                        UIProvider.ACCOUNTS_PROJECTION, null, null, null);
343        }
344        return null;
345    }
346
347    private WaitFragment getWaitFragment() {
348        return (WaitFragment) getFragmentManager().findFragmentByTag(TAG_WAIT);
349    }
350
351    private int replaceFragment(Fragment fragment, int transition, String tag) {
352        FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
353        fragmentTransaction.setTransition(transition);
354        fragmentTransaction.replace(R.id.wait, fragment, tag);
355        final int transactionId = fragmentTransaction.commitAllowingStateLoss();
356        return transactionId;
357    }
358
359    @Override
360    public void onLoaderReset(Loader<Cursor> arg0) {
361        // Do nothing.
362    }
363
364    @Override
365    public void onLoadFinished(Loader<Cursor> cursor, Cursor data) {
366        if (data != null && data.moveToFirst()) {
367            // there are accounts now!
368            Account account;
369            ArrayList<Account> accounts = new ArrayList<Account>();
370            ArrayList<Account> initializedAccounts = new ArrayList<Account>();
371            do {
372                account = Account.builder().buildFrom(data);
373                if (account.isAccountReady()) {
374                    initializedAccounts.add(account);
375                }
376                accounts.add(account);
377            } while (data.moveToNext());
378            if (initializedAccounts.size() > 0) {
379                mWait.setVisibility(View.GONE);
380                getLoaderManager().destroyLoader(LOADER_ACCOUNT_CURSOR);
381                mContent.setVisibility(View.VISIBLE);
382                updateAccountList(data);
383            } else {
384                // Show "waiting"
385                account = accounts.size() > 0 ? accounts.get(0) : null;
386                showWaitFragment(account);
387            }
388        }
389    }
390
391    @Override
392    public void onBackPressed() {
393        mWaitingForAddAccountResult = false;
394        // If we are showing the wait fragment, just exit.
395        if (getWaitFragment() != null) {
396            finish();
397        } else {
398            super.onBackPressed();
399        }
400    }
401}
402