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