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.AppCompatActivity; 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 AppCompatActivity 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