FolderListFragment.java revision 4a4de37596a8172c767749c361c1ebbba082d56f
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 int unreadCount; 616 if (mShowLessAccounts && mAllAccounts.length > MAX_ACCOUNTS) { 617 mItemList.add(new DrawerItem( 618 mActivity, R.string.folder_list_show_all_accounts, true)); 619 unreadCount = mFolderWatcher.get( 620 mCurrentAccount.settings.defaultInbox).unreadCount; 621 mItemList.add(new DrawerItem(mActivity, mCurrentAccount, unreadCount)); 622 } else { 623 Uri currentAccountUri = getCurrentAccountUri(); 624 for (final Account c : mAllAccounts) { 625 if (!currentAccountUri.equals(c.uri)) { 626 unreadCount = mFolderWatcher.get(c.settings.defaultInbox).unreadCount; 627 mItemList.add(new DrawerItem(mActivity, c, unreadCount)); 628 } 629 } 630 unreadCount = mFolderWatcher.get( 631 mCurrentAccount.settings.defaultInbox).unreadCount; 632 mItemList.add(new DrawerItem(mActivity, mCurrentAccount, unreadCount)); 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 @Override 710 public void setCursor(Cursor cursor) { 711 mCursor = cursor; 712 recalculateList(); 713 } 714 715 @Override 716 public Object getItem(int position) { 717 return mItemList.get(position); 718 } 719 720 @Override 721 public long getItemId(int position) { 722 return getItem(position).hashCode(); 723 } 724 725 @Override 726 public final void destroy() { 727 mRecentFolderObserver.unregisterAndDestroy(); 728 } 729 730 @Override 731 public int getItemType(DrawerItem item) { 732 return item.mType; 733 } 734 735 @Override 736 public Folder getFullFolder(DrawerItem folderItem) { 737 if (folderItem.mFolderType == DrawerItem.FOLDER_RECENT) { 738 return folderItem.mFolder; 739 } else { 740 int pos = folderItem.mPosition; 741 if (mFutureData != null) { 742 mCursor = mFutureData; 743 mFutureData = null; 744 } 745 if (pos > -1 && mCursor != null && !mCursor.isClosed() 746 && mCursor.moveToPosition(folderItem.mPosition)) { 747 return new Folder(mCursor); 748 } else { 749 return null; 750 } 751 } 752 } 753 754 @Override 755 public Account getFullAccount(DrawerItem item) { 756 return item.mAccount; 757 } 758 } 759 760 private class HierarchicalFolderListAdapter extends ArrayAdapter<Folder> 761 implements FolderListFragmentCursorAdapter{ 762 763 private static final int PARENT = 0; 764 private static final int CHILD = 1; 765 private final Uri mParentUri; 766 private final Folder mParent; 767 private final FolderItemView.DropHandler mDropHandler; 768 private Cursor mCursor; 769 770 public HierarchicalFolderListAdapter(Cursor c, Folder parentFolder) { 771 super(mActivity.getActivityContext(), R.layout.folder_item); 772 mDropHandler = mActivity; 773 mParent = parentFolder; 774 mParentUri = parentFolder.uri; 775 setCursor(c); 776 } 777 778 @Override 779 public int getViewTypeCount() { 780 // Child and Parent 781 return 2; 782 } 783 784 @Override 785 public int getItemViewType(int position) { 786 final Folder f = getItem(position); 787 return f.uri.equals(mParentUri) ? PARENT : CHILD; 788 } 789 790 @Override 791 public View getView(int position, View convertView, ViewGroup parent) { 792 final FolderItemView folderItemView; 793 final Folder folder = getItem(position); 794 boolean isParent = folder.uri.equals(mParentUri); 795 if (convertView != null) { 796 folderItemView = (FolderItemView) convertView; 797 } else { 798 int resId = isParent ? R.layout.folder_item : R.layout.child_folder_item; 799 folderItemView = (FolderItemView) LayoutInflater.from( 800 mActivity.getActivityContext()).inflate(resId, null); 801 } 802 folderItemView.bind(folder, mDropHandler); 803 if (folder.uri.equals(mSelectedFolderUri)) { 804 getListView().setItemChecked(position, true); 805 // If this is the current folder, also check to verify that the unread count 806 // matches what the action bar shows. 807 final boolean unreadCountDiffers = (mCurrentFolderForUnreadCheck != null) 808 && folder.unreadCount != mCurrentFolderForUnreadCheck.unreadCount; 809 if (unreadCountDiffers) { 810 folderItemView.overrideUnreadCount(mCurrentFolderForUnreadCheck.unreadCount); 811 } 812 } 813 Folder.setFolderBlockColor(folder, folderItemView.findViewById(R.id.color_block)); 814 Folder.setIcon(folder, (ImageView) folderItemView.findViewById(R.id.folder_icon)); 815 return folderItemView; 816 } 817 818 @Override 819 public void setCursor(Cursor cursor) { 820 mCursor = cursor; 821 clear(); 822 if (mParent != null) { 823 add(mParent); 824 } 825 if (cursor != null && cursor.getCount() > 0) { 826 cursor.moveToFirst(); 827 do { 828 Folder f = new Folder(cursor); 829 f.parent = mParent; 830 add(f); 831 } while (cursor.moveToNext()); 832 } 833 } 834 835 @Override 836 public void destroy() { 837 // Do nothing. 838 } 839 840 @Override 841 public int getItemType(DrawerItem item) { 842 // Always returns folders for now. 843 return DrawerItem.VIEW_FOLDER; 844 } 845 846 @Override 847 public Folder getFullFolder(DrawerItem folderItem) { 848 int pos = folderItem.mPosition; 849 if (mCursor == null || mCursor.isClosed()) { 850 // See if we have a cursor hanging out we can use 851 mCursor = mFutureData; 852 mFutureData = null; 853 } 854 if (pos > -1 && mCursor != null && !mCursor.isClosed() 855 && mCursor.moveToPosition(folderItem.mPosition)) { 856 return new Folder(mCursor); 857 } else { 858 return null; 859 } 860 } 861 862 @Override 863 public Account getFullAccount(DrawerItem item) { 864 return null; 865 } 866 867 @Override 868 public boolean toggleShowLessFolders() { 869 return false; 870 } 871 872 @Override 873 public boolean toggleShowLessAccounts() { 874 return false; 875 } 876 } 877 878 /** 879 * Sets the currently selected folder safely. 880 * @param folder 881 */ 882 private void setSelectedFolder(Folder folder) { 883 if (folder == null) { 884 mSelectedFolderUri = Uri.EMPTY; 885 LogUtils.e(LOG_TAG, "FolderListFragment.setSelectedFolder(null) called!"); 886 return; 887 } 888 // If the current folder changed, we don't have a selected folder type anymore. 889 if (!folder.uri.equals(mSelectedFolderUri)) { 890 mSelectedFolderType = DrawerItem.INERT_HEADER; 891 } 892 mCurrentFolderForUnreadCheck = folder; 893 mSelectedFolderUri = folder.uri; 894 setSelectedFolderType(folder); 895 if (mCursorAdapter != null) { 896 mCursorAdapter.notifyDataSetChanged(); 897 } 898 } 899 900 /** 901 * Sets the selected folder type safely. 902 * @param folder folder to set to. 903 */ 904 private void setSelectedFolderType(Folder folder) { 905 // If it is set already, assume it is correct. 906 if (mSelectedFolderType != DrawerItem.INERT_HEADER) { 907 return; 908 } 909 mSelectedFolderType = folder.isProviderFolder() ? DrawerItem.FOLDER_SYSTEM 910 : DrawerItem.FOLDER_USER; 911 } 912 913 /** 914 * Sets the current account to the one provided here. 915 * @param account the current account to set to. 916 */ 917 private void setSelectedAccount(Account account){ 918 mCurrentAccount = account; 919 } 920 921 public interface FolderListSelectionListener { 922 public void onFolderSelected(Folder folder); 923 } 924 925 /** 926 * Get whether the FolderListFragment is currently showing the hierarchy 927 * under a single parent. 928 */ 929 public boolean showingHierarchy() { 930 return mParentFolder != null; 931 } 932} 933