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  */
16 package com.android.mail.ui;
17 
18 import com.android.mail.R;
19 import com.android.mail.analytics.Analytics;
20 import com.android.mail.providers.Account;
21 import com.android.mail.providers.MailAppProvider;
22 import com.android.mail.providers.UIProvider;
23 import com.android.mail.utils.LogTag;
24 
25 import java.util.ArrayList;
26 
27 import android.app.Fragment;
28 import android.app.FragmentTransaction;
29 import android.app.LoaderManager;
30 import android.appwidget.AppWidgetManager;
31 import android.content.ContentResolver;
32 import android.content.CursorLoader;
33 import android.content.Intent;
34 import android.content.Loader;
35 import android.database.Cursor;
36 import android.os.AsyncTask;
37 import android.os.Bundle;
38 import android.os.Handler;
39 import android.support.v7.app.ActionBarActivity;
40 import android.view.View;
41 import android.view.View.OnClickListener;
42 import android.view.ViewGroup;
43 import android.widget.AdapterView;
44 import android.widget.ListView;
45 import android.widget.SimpleCursorAdapter;
46 import 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  */
52 public 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
onCreate(Bundle icicle)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
onSaveInstanceState(Bundle icicle)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
onStart()132     public void onStart() {
133         super.onStart();
134 
135         Analytics.getInstance().activityStart(this);
136     }
137 
138     @Override
onStop()139     protected void onStop() {
140         super.onStop();
141 
142         Analytics.getInstance().activityStop(this);
143     }
144 
145     @Override
onResume()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
onPause()157     public void onPause() {
158         super.onPause();
159         mResumed = false;
160     }
161 
162     @Override
onNewIntent(Intent intent)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      */
restoreState(Bundle icicle)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 
setupWithAccounts()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 
completeSetupWithAccounts(final Cursor accounts)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 
updateAccountList(final Cursor accounts)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
onItemClick(AdapterView<?> parent, View view, int position, long id)270     public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
271         selectAccount(Account.builder().buildFrom((Cursor) mAdapter.getItem(position)));
272     }
273 
selectAccount(Account account)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      */
getFolderSelectionActivity()297     protected Class<?> getFolderSelectionActivity() {
298         return FolderSelectionActivity.class;
299     }
300 
301     @Override
onClick(View v)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
onActivityResult(int request, int result, Intent data)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 
showWaitFragment(Account account)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
onCreateLoader(int id, Bundle args)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 
getWaitFragment()347     private WaitFragment getWaitFragment() {
348         return (WaitFragment) getFragmentManager().findFragmentByTag(TAG_WAIT);
349     }
350 
replaceFragment(Fragment fragment, int transition, String tag)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
onLoaderReset(Loader<Cursor> arg0)360     public void onLoaderReset(Loader<Cursor> arg0) {
361         // Do nothing.
362     }
363 
364     @Override
onLoadFinished(Loader<Cursor> cursor, Cursor data)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
onBackPressed()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