FolderListFragment.java revision 380929d02b329cafe8330903a01b862aafdd044d
1/* 2 * Copyright (C) 2012 Google Inc. 3 * Licensed to The Android Open Source Project. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18package com.android.mail.ui; 19 20import android.app.Activity; 21import android.app.ListFragment; 22import android.app.LoaderManager; 23import android.content.CursorLoader; 24import android.content.Loader; 25import android.database.Cursor; 26import android.database.DataSetObserver; 27import android.net.Uri; 28import android.os.Bundle; 29import android.view.LayoutInflater; 30import android.view.View; 31import android.view.ViewGroup; 32import android.widget.ArrayAdapter; 33import android.widget.BaseAdapter; 34import android.widget.ImageView; 35import android.widget.ListAdapter; 36import android.widget.ListView; 37 38import com.android.mail.R; 39import com.android.mail.adapter.DrawerItem; 40import com.android.mail.providers.*; 41import com.android.mail.providers.UIProvider.FolderType; 42import com.android.mail.utils.LogTag; 43import com.android.mail.utils.LogUtils; 44import java.util.ArrayList; 45import java.util.Iterator; 46import java.util.List; 47 48/** 49 * The folder list UI component. 50 */ 51public final class FolderListFragment extends ListFragment implements 52 LoaderManager.LoaderCallbacks<Cursor> { 53 private static final String LOG_TAG = LogTag.getLogTag(); 54 /** The parent activity */ 55 private ControllableActivity mActivity; 56 /** The underlying list view */ 57 private ListView mListView; 58 /** URI that points to the list of folders for the current account. */ 59 private Uri mFolderListUri; 60 /** True if you want a sectioned FolderList, false otherwise. */ 61 private boolean mIsSectioned; 62 /** Is the current device using tablet UI (true if 2-pane, false if 1-pane) */ 63 private boolean mIsTabletUI; 64 /** An {@link ArrayList} of {@link FolderType}s to exclude from displaying. */ 65 private ArrayList<Integer> mExcludedFolderTypes; 66 /** Object that changes folders on our behalf. */ 67 private FolderListSelectionListener mFolderChanger; 68 /** Object that changes accounts on our behalf */ 69 private AccountController mAccountChanger; 70 71 /** The currently selected folder (the folder being viewed). This is never null. */ 72 private Uri mSelectedFolderUri = Uri.EMPTY; 73 /** 74 * The current folder from the controller. This is meant only to check when the unread count 75 * goes out of sync and fixing it. This should NOT be serialized and stored. 76 */ 77 private Folder mCurrentFolderForUnreadCheck; 78 /** Parent of the current folder, or null if the current folder is not a child. */ 79 private Folder mParentFolder; 80 81 private static final int FOLDER_LOADER_ID = 0; 82 /** Key to store {@link #mParentFolder}. */ 83 private static final String ARG_PARENT_FOLDER = "arg-parent-folder"; 84 /** Key to store {@link #mFolderListUri}. */ 85 private static final String ARG_FOLDER_URI = "arg-folder-list-uri"; 86 /** Key to store {@link #mIsSectioned} */ 87 private static final String ARG_IS_SECTIONED = "arg-is-sectioned"; 88 /** Key to store {@link #mIsTabletUI} */ 89 private static final String ARG_IS_TABLET_UI = "arg-is-tablet-ui"; 90 /** Key to store {@link #mExcludedFolderTypes} */ 91 private static final String ARG_EXCLUDED_FOLDER_TYPES = "arg-excluded-folder-types"; 92 /** Should the {@link FolderListFragment} show less labels to begin with? */ 93 private static final boolean ARE_ITEMS_COLLAPSED = true; 94 95 private static final String BUNDLE_LIST_STATE = "flf-list-state"; 96 private static final String BUNDLE_SELECTED_FOLDER = "flf-selected-folder"; 97 private static final String BUNDLE_SELECTED_TYPE = "flf-selected-type"; 98 99 private FolderListFragmentCursorAdapter mCursorAdapter; 100 /** View that we show while we are waiting for the folder list to load */ 101 private View mEmptyView; 102 /** Observer to wait for changes to the current folder so we can change the selected folder */ 103 private FolderObserver mFolderObserver = null; 104 /** Listen for account changes. */ 105 private AccountObserver mAccountObserver = null; 106 107 /** Listen to changes to list of all accounts */ 108 private AllAccountObserver mAllAccountObserver = null; 109 /** 110 * Type of currently selected folder: {@link DrawerItem#FOLDER_SYSTEM}, 111 * {@link DrawerItem#FOLDER_RECENT} or {@link DrawerItem#FOLDER_USER}. 112 */ 113 // Setting to INERT_HEADER = leaving uninitialized. 114 private int mSelectedFolderType = DrawerItem.INERT_HEADER; 115 private Cursor mFutureData; 116 private ConversationListCallbacks mConversationListCallback; 117 /** The current account according to the controller */ 118 private Account mCurrentAccount; 119 120 /** List of all accounts currently known */ 121 private Account[] mAllAccounts; 122 123 /** 124 * Constructor needs to be public to handle orientation changes and activity lifecycle events. 125 */ 126 public FolderListFragment() { 127 super(); 128 } 129 130 /** 131 * Creates a new instance of {@link ConversationListFragment}, initialized 132 * to display conversation list context. 133 * @param isSectioned True if sections should be shown for folder list 134 * @param isTabletUI True if two-pane layout, false if not 135 */ 136 public static FolderListFragment newInstance(Folder parentFolder, Uri folderUri, 137 boolean isSectioned, boolean isTabletUI) { 138 return newInstance(parentFolder, folderUri, isSectioned, null, isTabletUI); 139 } 140 141 /** 142 * Creates a new instance of {@link ConversationListFragment}, initialized 143 * to display conversation list context. 144 * @param isSectioned True if sections should be shown for folder list 145 * @param excludedFolderTypes A list of {@link FolderType}s to exclude from displaying 146 * @param isTabletUI True if two-pane layout, false if not 147 */ 148 public static FolderListFragment newInstance(Folder parentFolder, Uri folderUri, 149 boolean isSectioned, final ArrayList<Integer> excludedFolderTypes, 150 boolean isTabletUI) { 151 final FolderListFragment fragment = new FolderListFragment(); 152 final Bundle args = new Bundle(); 153 if (parentFolder != null) { 154 args.putParcelable(ARG_PARENT_FOLDER, parentFolder); 155 } 156 args.putString(ARG_FOLDER_URI, folderUri.toString()); 157 args.putBoolean(ARG_IS_SECTIONED, isSectioned); 158 args.putBoolean(ARG_IS_TABLET_UI, isTabletUI); 159 if (excludedFolderTypes != null) { 160 args.putIntegerArrayList(ARG_EXCLUDED_FOLDER_TYPES, excludedFolderTypes); 161 } 162 fragment.setArguments(args); 163 return fragment; 164 } 165 166 @Override 167 public void onActivityCreated(Bundle savedState) { 168 super.onActivityCreated(savedState); 169 // Strictly speaking, we get back an android.app.Activity from getActivity. However, the 170 // only activity creating a ConversationListContext is a MailActivity which is of type 171 // ControllableActivity, so this cast should be safe. If this cast fails, some other 172 // activity is creating ConversationListFragments. This activity must be of type 173 // ControllableActivity. 174 final Activity activity = getActivity(); 175 if (! (activity instanceof ControllableActivity)){ 176 LogUtils.wtf(LOG_TAG, "FolderListFragment expects only a ControllableActivity to" + 177 "create it. Cannot proceed."); 178 } 179 mActivity = (ControllableActivity) activity; 180 mConversationListCallback = mActivity.getListHandler(); 181 final FolderController controller = mActivity.getFolderController(); 182 // Listen to folder changes in the future 183 mFolderObserver = new FolderObserver() { 184 @Override 185 public void onChanged(Folder newFolder) { 186 setSelectedFolder(newFolder); 187 } 188 }; 189 if (controller != null) { 190 // Only register for selected folder updates if we have a controller. 191 mCurrentFolderForUnreadCheck = mFolderObserver.initialize(controller); 192 } 193 final AccountController accountController = mActivity.getAccountController(); 194 mAccountObserver = new AccountObserver() { 195 @Override 196 public void onChanged(Account newAccount) { 197 setSelectedAccount(newAccount); 198 } 199 }; 200 if (accountController != null) { 201 // Current account and its observer. 202 mCurrentAccount = mAccountObserver.initialize(accountController); 203 // List of all accounts and its observer. 204 mAllAccountObserver = new AllAccountObserver(){ 205 @Override 206 public void onChanged(Account[] allAccounts) { 207 mAllAccounts = allAccounts; 208 } 209 }; 210 mAllAccounts = mAllAccountObserver.initialize(accountController); 211 mAccountChanger = accountController; 212 } 213 214 mFolderChanger = mActivity.getFolderListSelectionListener(); 215 if (mActivity.isFinishing()) { 216 // Activity is finishing, just bail. 217 return; 218 } 219 220 final Folder selectedFolder; 221 if (mParentFolder != null) { 222 mCursorAdapter = new HierarchicalFolderListAdapter(null, mParentFolder); 223 selectedFolder = mActivity.getHierarchyFolder(); 224 } else { 225 // Initiate FLA with accounts and folders collapsed in the list 226 // The second param is for whether folders should be collapsed 227 // The third param is for whether accounts should be collapsed 228 mCursorAdapter = new FolderListAdapter(mIsSectioned, 229 !mIsTabletUI && ARE_ITEMS_COLLAPSED, 230 !mIsTabletUI && ARE_ITEMS_COLLAPSED); 231 selectedFolder = controller == null ? null : controller.getFolder(); 232 } 233 // Is the selected folder fresher than the one we have restored from a bundle? 234 if (selectedFolder != null && !selectedFolder.uri.equals(mSelectedFolderUri)) { 235 setSelectedFolder(selectedFolder); 236 } 237 setListAdapter(mCursorAdapter); 238 // Set the region which gets highlighted since it might not have been set till now. 239 getLoaderManager().initLoader(FOLDER_LOADER_ID, Bundle.EMPTY, this); 240 } 241 242 @Override 243 public View onCreateView(LayoutInflater inflater, ViewGroup container, 244 Bundle savedState) { 245 final Bundle args = getArguments(); 246 mFolderListUri = Uri.parse(args.getString(ARG_FOLDER_URI)); 247 mParentFolder = (Folder) args.getParcelable(ARG_PARENT_FOLDER); 248 mIsSectioned = args.getBoolean(ARG_IS_SECTIONED); 249 mIsTabletUI = args.getBoolean(ARG_IS_TABLET_UI); 250 mExcludedFolderTypes = args.getIntegerArrayList(ARG_EXCLUDED_FOLDER_TYPES); 251 final View rootView = inflater.inflate(R.layout.folder_list, null); 252 mListView = (ListView) rootView.findViewById(android.R.id.list); 253 mListView.setHeaderDividersEnabled(false); 254 mListView.setChoiceMode(ListView.CHOICE_MODE_SINGLE); 255 mListView.setEmptyView(null); 256 if (savedState != null && savedState.containsKey(BUNDLE_LIST_STATE)) { 257 mListView.onRestoreInstanceState(savedState.getParcelable(BUNDLE_LIST_STATE)); 258 } 259 mEmptyView = rootView.findViewById(R.id.empty_view); 260 if (savedState != null && savedState.containsKey(BUNDLE_SELECTED_FOLDER)) { 261 mSelectedFolderUri = Uri.parse(savedState.getString(BUNDLE_SELECTED_FOLDER)); 262 mSelectedFolderType = savedState.getInt(BUNDLE_SELECTED_TYPE); 263 } else if (mParentFolder != null) { 264 mSelectedFolderUri = mParentFolder.uri; 265 // No selected folder type required for hierarchical lists. 266 } 267 268 return rootView; 269 } 270 271 @Override 272 public void onStart() { 273 super.onStart(); 274 } 275 276 @Override 277 public void onStop() { 278 super.onStop(); 279 } 280 281 @Override 282 public void onPause() { 283 super.onPause(); 284 } 285 286 @Override 287 public void onSaveInstanceState(Bundle outState) { 288 super.onSaveInstanceState(outState); 289 if (mListView != null) { 290 outState.putParcelable(BUNDLE_LIST_STATE, mListView.onSaveInstanceState()); 291 } 292 if (mSelectedFolderUri != null) { 293 outState.putString(BUNDLE_SELECTED_FOLDER, mSelectedFolderUri.toString()); 294 } 295 outState.putInt(BUNDLE_SELECTED_TYPE, mSelectedFolderType); 296 } 297 298 @Override 299 public void onDestroyView() { 300 if (mCursorAdapter != null) { 301 mCursorAdapter.destroy(); 302 } 303 // Clear the adapter. 304 setListAdapter(null); 305 if (mFolderObserver != null) { 306 mFolderObserver.unregisterAndDestroy(); 307 mFolderObserver = null; 308 } 309 if (mAccountObserver != null) { 310 mAccountObserver.unregisterAndDestroy(); 311 mAccountObserver = null; 312 } 313 if (mAllAccountObserver != null) { 314 mAllAccountObserver.unregisterAndDestroy(); 315 mAllAccountObserver = null; 316 } 317 super.onDestroyView(); 318 } 319 320 @Override 321 public void onListItemClick(ListView l, View v, int position, long id) { 322 viewFolderOrChangeAccount(position); 323 } 324 325 /** 326 * Display the conversation list from the folder at the position given. 327 * @param position a zero indexed position into the list. 328 */ 329 private void viewFolderOrChangeAccount(int position) { 330 final Object item = getListAdapter().getItem(position); 331 final Folder folder; 332 if (item instanceof DrawerItem) { 333 final DrawerItem folderItem = (DrawerItem) item; 334 // Could be a folder, account, or expand block. 335 final int itemType = mCursorAdapter.getItemType(folderItem); 336 if (itemType == DrawerItem.VIEW_ACCOUNT) { 337 // Account, so switch. 338 folder = null; 339 final Account account = mCursorAdapter.getFullAccount(folderItem); 340 mAccountChanger.changeAccount(account); 341 } else if (itemType == DrawerItem.VIEW_FOLDER) { 342 // Folder type, so change folders only. 343 folder = mCursorAdapter.getFullFolder(folderItem); 344 mSelectedFolderType = folderItem.mFolderType; 345 } else { 346 // Block for expanding/contracting labels/accounts 347 folder = null; 348 if(!folderItem.mIsExpandForAccount) { 349 mCursorAdapter.toggleShowLessFolders(); 350 } else { 351 mCursorAdapter.toggleShowLessAccounts(); 352 } 353 } 354 } else if (item instanceof Folder) { 355 folder = (Folder) item; 356 } else { 357 folder = new Folder((Cursor) item); 358 } 359 if (folder != null) { 360 // Since we may be looking at hierarchical views, if we can 361 // determine the parent of the folder we have tapped, set it here. 362 // If we are looking at the folder we are already viewing, don't 363 // update its parent! 364 folder.parent = folder.equals(mParentFolder) ? null : mParentFolder; 365 // Go to the conversation list for this folder. 366 mFolderChanger.onFolderSelected(folder); 367 } 368 } 369 370 @Override 371 public Loader<Cursor> onCreateLoader(int id, Bundle args) { 372 mListView.setEmptyView(null); 373 mEmptyView.setVisibility(View.GONE); 374 return new CursorLoader(mActivity.getActivityContext(), mFolderListUri, 375 UIProvider.FOLDERS_PROJECTION, null, null, null); 376 } 377 378 public void onAnimationEnd() { 379 if (mFutureData != null) { 380 updateCursorAdapter(mFutureData); 381 mFutureData = null; 382 } 383 } 384 385 private void updateCursorAdapter(Cursor data) { 386 mCursorAdapter.setCursor(data); 387 if (data == null || data.getCount() == 0) { 388 mEmptyView.setVisibility(View.VISIBLE); 389 mListView.setEmptyView(mEmptyView); 390 } 391 } 392 393 @Override 394 public void onLoadFinished(Loader<Cursor> loader, Cursor data) { 395 if (mConversationListCallback == null || !mConversationListCallback.isAnimating()) { 396 updateCursorAdapter(data); 397 } else { 398 mFutureData = data; 399 mCursorAdapter.setCursor(null); 400 } 401 } 402 403 @Override 404 public void onLoaderReset(Loader<Cursor> loader) { 405 mCursorAdapter.setCursor(null); 406 } 407 408 /** 409 * Interface for all cursor adapters that allow setting a cursor and being destroyed. 410 */ 411 private interface FolderListFragmentCursorAdapter extends ListAdapter { 412 /** Update the folder list cursor with the cursor given here. */ 413 void setCursor(Cursor cursor); 414 /** Toggles showing more accounts or less accounts. */ 415 boolean toggleShowLessAccounts(); 416 /** Toggles showing more folders or less. */ 417 boolean toggleShowLessFolders(); 418 /** 419 * Given an item, find the type of the item, which is {@link 420 * DrawerItem#VIEW_FOLDER}, {@link DrawerItem#VIEW_ACCOUNT} or 421 * {@link DrawerItem#VIEW_MORE} 422 * @return the type of the item. 423 */ 424 int getItemType(DrawerItem item); 425 /** Get the folder associated with this item. **/ 426 Folder getFullFolder(DrawerItem item); 427 /** Get the account associated with this item. **/ 428 Account getFullAccount(DrawerItem item); 429 /** Remove all observers and destroy the object. */ 430 void destroy(); 431 /** Notifies the adapter that the data has changed. */ 432 void notifyDataSetChanged(); 433 } 434 435 /** 436 * An adapter for flat folder lists. 437 */ 438 private class FolderListAdapter extends BaseAdapter implements FolderListFragmentCursorAdapter { 439 440 private final RecentFolderObserver mRecentFolderObserver = new RecentFolderObserver() { 441 @Override 442 public void onChanged() { 443 recalculateList(); 444 } 445 }; 446 447 /** After given number of accounts, show "more" until expanded. */ 448 private static final int MAX_ACCOUNTS = 2; 449 /** After the given number of labels, show "more" until expanded. */ 450 private static final int MAX_FOLDERS = 7; 451 452 private final RecentFolderList mRecentFolders; 453 /** True if the list is sectioned, false otherwise */ 454 private final boolean mIsSectioned; 455 /** All the items */ 456 private final List<DrawerItem> mItemList = new ArrayList<DrawerItem>(); 457 /** Cursor into the folder list. This might be null. */ 458 private Cursor mCursor = null; 459 /** Watcher for tracking and receiving unread counts for mail */ 460 private FolderWatcher mFolderWatcher = null; 461 private boolean mShowLessFolders; 462 private boolean mShowLessAccounts; 463 464 /** 465 * Creates a {@link FolderListAdapter}.This is a flat folder list of all the folders for the 466 * given account. 467 * @param isSectioned TODO(viki): 468 */ 469 public FolderListAdapter(boolean isSectioned, boolean showLess, boolean showLessAccounts) { 470 super(); 471 mIsSectioned = isSectioned; 472 final RecentFolderController controller = mActivity.getRecentFolderController(); 473 if (controller != null && mIsSectioned) { 474 mRecentFolders = mRecentFolderObserver.initialize(controller); 475 } else { 476 mRecentFolders = null; 477 } 478 mFolderWatcher = new FolderWatcher(mActivity, this); 479 mShowLessFolders = showLess; 480 mShowLessAccounts = showLessAccounts; 481 for (int i=0; i < mAllAccounts.length; i++) { 482 final Uri uri = mAllAccounts[i].settings.defaultInbox; 483 mFolderWatcher.startWatching(uri); 484 } 485 } 486 487 @Override 488 public View getView(int position, View convertView, ViewGroup parent) { 489 final DrawerItem item = (DrawerItem) getItem(position); 490 final View view = item.getView(position, convertView, parent); 491 final int type = item.mType; 492 if (mListView!= null) { 493 final boolean isSelected = 494 item.isHighlighted(mCurrentFolderForUnreadCheck, mSelectedFolderType); 495 if (type == DrawerItem.VIEW_FOLDER) { 496 mListView.setItemChecked(position, isSelected); 497 } 498 // If this is the current folder, also check to verify that the unread count 499 // matches what the action bar shows. 500 if (type == DrawerItem.VIEW_FOLDER 501 && isSelected 502 && (mCurrentFolderForUnreadCheck != null) 503 && item.mFolder.unreadCount != mCurrentFolderForUnreadCheck.unreadCount) { 504 ((FolderItemView) view).overrideUnreadCount( 505 mCurrentFolderForUnreadCheck.unreadCount); 506 } 507 } 508 return view; 509 } 510 511 @Override 512 public int getViewTypeCount() { 513 // Accounts, headers and folders 514 return DrawerItem.getViewTypes(); 515 } 516 517 @Override 518 public int getItemViewType(int position) { 519 return ((DrawerItem) getItem(position)).mType; 520 } 521 522 @Override 523 public int getCount() { 524 return mItemList.size(); 525 } 526 527 @Override 528 public boolean isEnabled(int position) { 529 final DrawerItem item = (DrawerItem) getItem(position); 530 return item.isItemEnabled(getCurrentAccountUri()); 531 532 } 533 534 private Uri getCurrentAccountUri() { 535 return mCurrentAccount == null ? Uri.EMPTY : mCurrentAccount.uri; 536 } 537 538 @Override 539 public boolean areAllItemsEnabled() { 540 // The headers and current accounts are not enabled. 541 return false; 542 } 543 544 /** 545 * Returns all the recent folders from the list given here. Safe to call with a null list. 546 * @param recentList a list of all recently accessed folders. 547 * @return a valid list of folders, which are all recent folders. 548 */ 549 private List<Folder> getRecentFolders(RecentFolderList recentList) { 550 final List<Folder> folderList = new ArrayList<Folder>(); 551 if (recentList == null) { 552 return folderList; 553 } 554 // Get all recent folders, after removing system folders. 555 for (final Folder f : recentList.getRecentFolderList(null)) { 556 if (!f.isProviderFolder()) { 557 folderList.add(f); 558 } 559 } 560 return folderList; 561 } 562 563 /** 564 * Toggle boolean for what folders are shown and which ones are 565 * hidden. Redraws list after toggling to show changes. 566 * @return true if folders are hidden, false if all are shown 567 */ 568 @Override 569 public boolean toggleShowLessFolders() { 570 mShowLessFolders = !mShowLessFolders; 571 recalculateList(); 572 return mShowLessFolders; 573 } 574 575 /** 576 * Toggle boolean for what accounts are shown and which ones are 577 * hidden. Redraws list after toggling to show changes. 578 * @return true if accounts are hidden, false if all are shown 579 */ 580 @Override 581 public boolean toggleShowLessAccounts() { 582 mShowLessAccounts = !mShowLessAccounts; 583 recalculateList(); 584 return mShowLessAccounts; 585 } 586 587 /** 588 * Responsible for verifying mCursor, adding collapsed view items 589 * when necessary, and notifying the data set has changed. 590 */ 591 private void recalculateList() { 592 if (mCursor == null || mCursor.isClosed() || mCursor.getCount() <= 0 593 || !mCursor.moveToFirst()) { 594 return; 595 } 596 recalculateListFolders(); 597 if(mShowLessFolders) { 598 mItemList.add(new DrawerItem(mActivity, R.string.folder_list_more, false)); 599 } 600 // Ask the list to invalidate its views. 601 notifyDataSetChanged(); 602 } 603 604 /** 605 * Recalculates the system, recent and user label lists. 606 * This method modifies all the three lists on every single invocation. 607 */ 608 private void recalculateListFolders() { 609 mItemList.clear(); 610 if (mAllAccounts != null) { 611 // Add the accounts at the top. 612 // TODO(shahrk): The logic here is messy and will be changed 613 // to properly add/reflect on LRU/MRU account 614 // changes similar to RecentFoldersList 615 if (mShowLessAccounts && mAllAccounts.length > MAX_ACCOUNTS) { 616 mItemList.add(new DrawerItem( 617 mActivity, R.string.folder_list_show_all_accounts, true)); 618 final int unreadCount = 619 getFolderUnreadCount(mCurrentAccount.settings.defaultInbox); 620 mItemList.add(new DrawerItem(mActivity, mCurrentAccount, unreadCount)); 621 } else { 622 Uri currentAccountUri = getCurrentAccountUri(); 623 for (final Account c : mAllAccounts) { 624 if (!currentAccountUri.equals(c.uri)) { 625 final int otherAccountUnreadCount = 626 getFolderUnreadCount(c.settings.defaultInbox); 627 mItemList.add(new DrawerItem(mActivity, c, otherAccountUnreadCount)); 628 } 629 } 630 final int accountUnreadCount = 631 getFolderUnreadCount(mCurrentAccount.settings.defaultInbox); 632 mItemList.add(new DrawerItem(mActivity, mCurrentAccount, accountUnreadCount)); 633 } 634 } 635 if (!mIsSectioned) { 636 // Adapter for a flat list. Everything is a FOLDER_USER, and there are no headers. 637 do { 638 final Folder f = Folder.getDeficientDisplayOnlyFolder(mCursor); 639 if (mExcludedFolderTypes == null || !mExcludedFolderTypes.contains(f.type)) { 640 mItemList.add(new DrawerItem(mActivity, f, DrawerItem.FOLDER_USER, 641 mCursor.getPosition())); 642 } 643 } while (mCursor.moveToNext()); 644 // Ask the list to invalidate its views. 645 notifyDataSetChanged(); 646 return; 647 } 648 649 // Tracks how many folders have been added through the rest of the function 650 int folderCount = 0; 651 // Otherwise, this is an adapter for a sectioned list. 652 // First add all the system folders. 653 final List<DrawerItem> userFolderList = new ArrayList<DrawerItem>(); 654 do { 655 final Folder f = Folder.getDeficientDisplayOnlyFolder(mCursor); 656 if (mExcludedFolderTypes == null || !mExcludedFolderTypes.contains(f.type)) { 657 if (f.isProviderFolder()) { 658 mItemList.add(new DrawerItem(mActivity, f, DrawerItem.FOLDER_SYSTEM, 659 mCursor.getPosition())); 660 // Check if show less is enabled and we've passed max folders 661 folderCount++; 662 if(mShowLessFolders && folderCount >= MAX_FOLDERS) { 663 return; 664 } 665 } else { 666 userFolderList.add(new DrawerItem( 667 mActivity, f, DrawerItem.FOLDER_USER, mCursor.getPosition())); 668 } 669 } 670 } while (mCursor.moveToNext()); 671 // If there are recent folders, add them and a header. 672 final List<Folder> recentFolderList = getRecentFolders(mRecentFolders); 673 674 // Remove any excluded folder types 675 if (mExcludedFolderTypes != null) { 676 final Iterator<Folder> iterator = recentFolderList.iterator(); 677 while (iterator.hasNext()) { 678 if (mExcludedFolderTypes.contains(iterator.next().type)) { 679 iterator.remove(); 680 } 681 } 682 } 683 684 if (recentFolderList.size() > 0) { 685 mItemList.add(new DrawerItem(mActivity, R.string.recent_folders_heading)); 686 for (Folder f : recentFolderList) { 687 mItemList.add(new DrawerItem(mActivity, f, DrawerItem.FOLDER_RECENT, -1)); 688 // Check if show less is enabled and we've passed max folders 689 folderCount++; 690 if(mShowLessFolders && folderCount >= MAX_FOLDERS) { 691 return; 692 } 693 } 694 } 695 // If there are user folders, add them and a header. 696 if (userFolderList.size() > 0) { 697 mItemList.add(new DrawerItem(mActivity, R.string.all_folders_heading)); 698 for (final DrawerItem i : userFolderList) { 699 mItemList.add(i); 700 // Check if show less is enabled and we've passed max folders 701 folderCount++; 702 if(mShowLessFolders && folderCount >= MAX_FOLDERS) { 703 return; 704 } 705 } 706 } 707 } 708 709 private int getFolderUnreadCount(Uri folderUri) { 710 final Folder folder = mFolderWatcher.get(folderUri); 711 return folder != null ? folder.unreadCount : 0; 712 } 713 714 @Override 715 public void setCursor(Cursor cursor) { 716 mCursor = cursor; 717 recalculateList(); 718 } 719 720 @Override 721 public Object getItem(int position) { 722 return mItemList.get(position); 723 } 724 725 @Override 726 public long getItemId(int position) { 727 return getItem(position).hashCode(); 728 } 729 730 @Override 731 public final void destroy() { 732 mRecentFolderObserver.unregisterAndDestroy(); 733 } 734 735 @Override 736 public int getItemType(DrawerItem item) { 737 return item.mType; 738 } 739 740 @Override 741 public Folder getFullFolder(DrawerItem folderItem) { 742 if (folderItem.mFolderType == DrawerItem.FOLDER_RECENT) { 743 return folderItem.mFolder; 744 } else { 745 int pos = folderItem.mPosition; 746 if (mFutureData != null) { 747 mCursor = mFutureData; 748 mFutureData = null; 749 } 750 if (pos > -1 && mCursor != null && !mCursor.isClosed() 751 && mCursor.moveToPosition(folderItem.mPosition)) { 752 return new Folder(mCursor); 753 } else { 754 return null; 755 } 756 } 757 } 758 759 @Override 760 public Account getFullAccount(DrawerItem item) { 761 return item.mAccount; 762 } 763 } 764 765 private class HierarchicalFolderListAdapter extends ArrayAdapter<Folder> 766 implements FolderListFragmentCursorAdapter{ 767 768 private static final int PARENT = 0; 769 private static final int CHILD = 1; 770 private final Uri mParentUri; 771 private final Folder mParent; 772 private final FolderItemView.DropHandler mDropHandler; 773 private Cursor mCursor; 774 775 public HierarchicalFolderListAdapter(Cursor c, Folder parentFolder) { 776 super(mActivity.getActivityContext(), R.layout.folder_item); 777 mDropHandler = mActivity; 778 mParent = parentFolder; 779 mParentUri = parentFolder.uri; 780 setCursor(c); 781 } 782 783 @Override 784 public int getViewTypeCount() { 785 // Child and Parent 786 return 2; 787 } 788 789 @Override 790 public int getItemViewType(int position) { 791 final Folder f = getItem(position); 792 return f.uri.equals(mParentUri) ? PARENT : CHILD; 793 } 794 795 @Override 796 public View getView(int position, View convertView, ViewGroup parent) { 797 final FolderItemView folderItemView; 798 final Folder folder = getItem(position); 799 boolean isParent = folder.uri.equals(mParentUri); 800 if (convertView != null) { 801 folderItemView = (FolderItemView) convertView; 802 } else { 803 int resId = isParent ? R.layout.folder_item : R.layout.child_folder_item; 804 folderItemView = (FolderItemView) LayoutInflater.from( 805 mActivity.getActivityContext()).inflate(resId, null); 806 } 807 folderItemView.bind(folder, mDropHandler); 808 if (folder.uri.equals(mSelectedFolderUri)) { 809 getListView().setItemChecked(position, true); 810 // If this is the current folder, also check to verify that the unread count 811 // matches what the action bar shows. 812 final boolean unreadCountDiffers = (mCurrentFolderForUnreadCheck != null) 813 && folder.unreadCount != mCurrentFolderForUnreadCheck.unreadCount; 814 if (unreadCountDiffers) { 815 folderItemView.overrideUnreadCount(mCurrentFolderForUnreadCheck.unreadCount); 816 } 817 } 818 Folder.setFolderBlockColor(folder, folderItemView.findViewById(R.id.color_block)); 819 Folder.setIcon(folder, (ImageView) folderItemView.findViewById(R.id.folder_icon)); 820 return folderItemView; 821 } 822 823 @Override 824 public void setCursor(Cursor cursor) { 825 mCursor = cursor; 826 clear(); 827 if (mParent != null) { 828 add(mParent); 829 } 830 if (cursor != null && cursor.getCount() > 0) { 831 cursor.moveToFirst(); 832 do { 833 Folder f = new Folder(cursor); 834 f.parent = mParent; 835 add(f); 836 } while (cursor.moveToNext()); 837 } 838 } 839 840 @Override 841 public void destroy() { 842 // Do nothing. 843 } 844 845 @Override 846 public int getItemType(DrawerItem item) { 847 // Always returns folders for now. 848 return DrawerItem.VIEW_FOLDER; 849 } 850 851 @Override 852 public Folder getFullFolder(DrawerItem folderItem) { 853 int pos = folderItem.mPosition; 854 if (mCursor == null || mCursor.isClosed()) { 855 // See if we have a cursor hanging out we can use 856 mCursor = mFutureData; 857 mFutureData = null; 858 } 859 if (pos > -1 && mCursor != null && !mCursor.isClosed() 860 && mCursor.moveToPosition(folderItem.mPosition)) { 861 return new Folder(mCursor); 862 } else { 863 return null; 864 } 865 } 866 867 @Override 868 public Account getFullAccount(DrawerItem item) { 869 return null; 870 } 871 872 @Override 873 public boolean toggleShowLessFolders() { 874 return false; 875 } 876 877 @Override 878 public boolean toggleShowLessAccounts() { 879 return false; 880 } 881 } 882 883 /** 884 * Sets the currently selected folder safely. 885 * @param folder 886 */ 887 private void setSelectedFolder(Folder folder) { 888 if (folder == null) { 889 mSelectedFolderUri = Uri.EMPTY; 890 LogUtils.e(LOG_TAG, "FolderListFragment.setSelectedFolder(null) called!"); 891 return; 892 } 893 // If the current folder changed, we don't have a selected folder type anymore. 894 if (!folder.uri.equals(mSelectedFolderUri)) { 895 mSelectedFolderType = DrawerItem.INERT_HEADER; 896 } 897 mCurrentFolderForUnreadCheck = folder; 898 mSelectedFolderUri = folder.uri; 899 setSelectedFolderType(folder); 900 if (mCursorAdapter != null) { 901 mCursorAdapter.notifyDataSetChanged(); 902 } 903 } 904 905 /** 906 * Sets the selected folder type safely. 907 * @param folder folder to set to. 908 */ 909 private void setSelectedFolderType(Folder folder) { 910 // If it is set already, assume it is correct. 911 if (mSelectedFolderType != DrawerItem.INERT_HEADER) { 912 return; 913 } 914 mSelectedFolderType = folder.isProviderFolder() ? DrawerItem.FOLDER_SYSTEM 915 : DrawerItem.FOLDER_USER; 916 } 917 918 /** 919 * Sets the current account to the one provided here. 920 * @param account the current account to set to. 921 */ 922 private void setSelectedAccount(Account account){ 923 mCurrentAccount = account; 924 } 925 926 public interface FolderListSelectionListener { 927 public void onFolderSelected(Folder folder); 928 } 929 930 /** 931 * Get whether the FolderListFragment is currently showing the hierarchy 932 * under a single parent. 933 */ 934 public boolean showingHierarchy() { 935 return mParentFolder != null; 936 } 937} 938