FolderListFragment.java revision 9be59911221b236b10c7575e407ac587c9231bc7
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; 37import android.widget.TextView; 38 39import com.android.mail.R; 40import com.android.mail.providers.Folder; 41import com.android.mail.providers.RecentFolderObserver; 42import com.android.mail.providers.UIProvider; 43import com.android.mail.providers.UIProvider.FolderType; 44import com.android.mail.utils.LogTag; 45import com.android.mail.utils.LogUtils; 46import com.android.mail.utils.Utils; 47 48import java.util.ArrayList; 49import java.util.Iterator; 50import java.util.List; 51 52/** 53 * The folder list UI component. 54 */ 55public final class FolderListFragment extends ListFragment implements 56 LoaderManager.LoaderCallbacks<Cursor> { 57 private static final String LOG_TAG = LogTag.getLogTag(); 58 /** The parent activity */ 59 private ControllableActivity mActivity; 60 /** The underlying list view */ 61 private ListView mListView; 62 /** URI that points to the list of folders for the current account. */ 63 private Uri mFolderListUri; 64 /** True if you want a sectioned FolderList, false otherwise. */ 65 private boolean mIsSectioned; 66 /** An {@link ArrayList} of {@link FolderType}s to exclude from displaying. */ 67 private ArrayList<Integer> mExcludedFolderTypes; 68 /** Callback into the parent */ 69 private FolderListSelectionListener mListener; 70 71 /** The currently selected folder (the folder being viewed). This is never null. */ 72 private Uri mSelectedFolderUri = Uri.EMPTY; 73 /** Parent of the current folder, or null if the current folder is not a child. */ 74 private Folder mParentFolder; 75 76 private static final int FOLDER_LOADER_ID = 0; 77 public static final int MODE_DEFAULT = 0; 78 public static final int MODE_PICK = 1; 79 /** Key to store {@link #mParentFolder}. */ 80 private static final String ARG_PARENT_FOLDER = "arg-parent-folder"; 81 /** Key to store {@link #mFolderListUri}. */ 82 private static final String ARG_FOLDER_URI = "arg-folder-list-uri"; 83 /** Key to store {@link #mIsSectioned} */ 84 private static final String ARG_IS_SECTIONED = "arg-is-sectioned"; 85 /** Key to store {@link #mExcludedFolderTypes} */ 86 private static final String ARG_EXCLUDED_FOLDER_TYPES = "arg-excluded-folder-types"; 87 88 private static final String BUNDLE_LIST_STATE = "flf-list-state"; 89 private static final String BUNDLE_SELECTED_FOLDER = "flf-selected-folder"; 90 private static final String BUNDLE_SELECTED_TYPE = "flf-selected-type"; 91 92 private FolderListFragmentCursorAdapter mCursorAdapter; 93 /** View that we show while we are waiting for the folder list to load */ 94 private View mEmptyView; 95 /** Observer to wait for changes to the current folder so we can change the selected folder */ 96 private FolderObserver mFolderObserver = null; 97 /** 98 * Type of currently selected folder: {@link FolderListAdapter.Item#FOLDER_SYSTEM}, 99 * {@link FolderListAdapter.Item#FOLDER_RECENT} or {@link FolderListAdapter.Item#FOLDER_USER}. 100 */ 101 // Setting to NOT_A_FOLDER = leaving uninitialized. 102 private int mSelectedFolderType = FolderListAdapter.Item.NOT_A_FOLDER; 103 private Cursor mFutureData; 104 private ConversationListCallbacks mConversationListCallback; 105 106 /** 107 * Listens to folder changes from the controller and updates state accordingly. 108 */ 109 private final class FolderObserver extends DataSetObserver { 110 @Override 111 public void onChanged() { 112 if (mActivity == null) { 113 return; 114 } 115 final FolderController controller = mActivity.getFolderController(); 116 if (controller == null) { 117 return; 118 } 119 setSelectedFolder(controller.getFolder()); 120 } 121 } 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 TODO(viki): 134 */ 135 public static FolderListFragment newInstance(Folder parentFolder, Uri folderUri, 136 boolean isSectioned) { 137 return newInstance(parentFolder, folderUri, isSectioned, null); 138 } 139 140 /** 141 * Creates a new instance of {@link ConversationListFragment}, initialized 142 * to display conversation list context. 143 * @param isSectioned TODO(viki): 144 * @param excludedFolderTypes A list of {@link FolderType}s to exclude from displaying 145 */ 146 public static FolderListFragment newInstance(Folder parentFolder, Uri folderUri, 147 boolean isSectioned, final ArrayList<Integer> excludedFolderTypes) { 148 final FolderListFragment fragment = new FolderListFragment(); 149 final Bundle args = new Bundle(); 150 if (parentFolder != null) { 151 args.putParcelable(ARG_PARENT_FOLDER, parentFolder); 152 } 153 args.putString(ARG_FOLDER_URI, folderUri.toString()); 154 args.putBoolean(ARG_IS_SECTIONED, isSectioned); 155 if (excludedFolderTypes != null) { 156 args.putIntegerArrayList(ARG_EXCLUDED_FOLDER_TYPES, excludedFolderTypes); 157 } 158 fragment.setArguments(args); 159 return fragment; 160 } 161 162 @Override 163 public void onActivityCreated(Bundle savedState) { 164 super.onActivityCreated(savedState); 165 // Strictly speaking, we get back an android.app.Activity from getActivity. However, the 166 // only activity creating a ConversationListContext is a MailActivity which is of type 167 // ControllableActivity, so this cast should be safe. If this cast fails, some other 168 // activity is creating ConversationListFragments. This activity must be of type 169 // ControllableActivity. 170 final Activity activity = getActivity(); 171 if (! (activity instanceof ControllableActivity)){ 172 LogUtils.wtf(LOG_TAG, "FolderListFragment expects only a ControllableActivity to" + 173 "create it. Cannot proceed."); 174 } 175 mActivity = (ControllableActivity) activity; 176 mConversationListCallback = mActivity.getListHandler(); 177 final FolderController controller = mActivity.getFolderController(); 178 // Listen to folder changes in the future 179 mFolderObserver = new FolderObserver(); 180 if (controller != null) { 181 // Only register for selected folder updates if we have a controller. 182 controller.registerFolderObserver(mFolderObserver); 183 } 184 185 mListener = mActivity.getFolderListSelectionListener(); 186 if (mActivity.isFinishing()) { 187 // Activity is finishing, just bail. 188 return; 189 } 190 191 final Folder selectedFolder; 192 if (mParentFolder != null) { 193 mCursorAdapter = new HierarchicalFolderListAdapter(null, mParentFolder); 194 selectedFolder = mActivity.getHierarchyFolder(); 195 } else { 196 mCursorAdapter = new FolderListAdapter(R.layout.folder_item, mIsSectioned); 197 selectedFolder = controller == null ? null : controller.getFolder(); 198 } 199 // Is the selected folder fresher than the one we have restored from a bundle? 200 if (selectedFolder != null && !selectedFolder.uri.equals(mSelectedFolderUri)) { 201 setSelectedFolder(selectedFolder); 202 } 203 setListAdapter(mCursorAdapter); 204 // Set the region which gets highlighted since it might not have been set till now. 205 getLoaderManager().initLoader(FOLDER_LOADER_ID, Bundle.EMPTY, this); 206 } 207 208 @Override 209 public View onCreateView(LayoutInflater inflater, ViewGroup container, 210 Bundle savedState) { 211 final Bundle args = getArguments(); 212 mFolderListUri = Uri.parse(args.getString(ARG_FOLDER_URI)); 213 mParentFolder = (Folder) args.getParcelable(ARG_PARENT_FOLDER); 214 mIsSectioned = args.getBoolean(ARG_IS_SECTIONED); 215 mExcludedFolderTypes = args.getIntegerArrayList(ARG_EXCLUDED_FOLDER_TYPES); 216 final View rootView = inflater.inflate(R.layout.folder_list, null); 217 mListView = (ListView) rootView.findViewById(android.R.id.list); 218 mListView.setHeaderDividersEnabled(false); 219 mListView.setChoiceMode(ListView.CHOICE_MODE_SINGLE); 220 mListView.setEmptyView(null); 221 if (savedState != null && savedState.containsKey(BUNDLE_LIST_STATE)) { 222 mListView.onRestoreInstanceState(savedState.getParcelable(BUNDLE_LIST_STATE)); 223 } 224 mEmptyView = rootView.findViewById(R.id.empty_view); 225 if (savedState != null && savedState.containsKey(BUNDLE_SELECTED_FOLDER)) { 226 mSelectedFolderUri = Uri.parse(savedState.getString(BUNDLE_SELECTED_FOLDER)); 227 mSelectedFolderType = savedState.getInt(BUNDLE_SELECTED_TYPE); 228 } else if (mParentFolder != null) { 229 mSelectedFolderUri = mParentFolder.uri; 230 // No selected folder type required for hierarchical lists. 231 } 232 233 return rootView; 234 } 235 236 @Override 237 public void onStart() { 238 super.onStart(); 239 } 240 241 @Override 242 public void onStop() { 243 super.onStop(); 244 } 245 246 @Override 247 public void onPause() { 248 super.onPause(); 249 } 250 251 @Override 252 public void onSaveInstanceState(Bundle outState) { 253 super.onSaveInstanceState(outState); 254 if (mListView != null) { 255 outState.putParcelable(BUNDLE_LIST_STATE, mListView.onSaveInstanceState()); 256 } 257 if (mSelectedFolderUri != null) { 258 outState.putString(BUNDLE_SELECTED_FOLDER, mSelectedFolderUri.toString()); 259 } 260 outState.putInt(BUNDLE_SELECTED_TYPE, mSelectedFolderType); 261 } 262 263 @Override 264 public void onDestroyView() { 265 if (mCursorAdapter != null) { 266 mCursorAdapter.destroy(); 267 } 268 // Clear the adapter. 269 setListAdapter(null); 270 if (mFolderObserver != null) { 271 FolderController controller = mActivity.getFolderController(); 272 if (controller != null) { 273 controller.unregisterFolderObserver(mFolderObserver); 274 mFolderObserver = null; 275 } 276 } 277 super.onDestroyView(); 278 } 279 280 @Override 281 public void onListItemClick(ListView l, View v, int position, long id) { 282 viewFolder(position); 283 } 284 285 /** 286 * Display the conversation list from the folder at the position given. 287 * @param position 288 */ 289 private void viewFolder(int position) { 290 final Object item = getListAdapter().getItem(position); 291 final Folder folder; 292 if (item instanceof FolderListAdapter.Item) { 293 final FolderListAdapter.Item folderItem = (FolderListAdapter.Item) item; 294 folder = mCursorAdapter.getFullFolder(folderItem); 295 mSelectedFolderType = folderItem.mFolderType; 296 } else if (item instanceof Folder) { 297 folder = (Folder) item; 298 } else { 299 folder = new Folder((Cursor) item); 300 } 301 if (folder != null) { 302 // Since we may be looking at hierarchical views, if we can 303 // determine the parent of the folder we have tapped, set it here. 304 // If we are looking at the folder we are already viewing, don't 305 // update its parent! 306 folder.parent = folder.equals(mParentFolder) ? null : mParentFolder; 307 // Go to the conversation list for this folder. 308 mListener.onFolderSelected(folder); 309 } else { 310 LogUtils.d(LOG_TAG, "FolderListFragment unable to get a full fledged folder" + 311 " to hand to the listener for position %d", position); 312 } 313 } 314 315 @Override 316 public Loader<Cursor> onCreateLoader(int id, Bundle args) { 317 mListView.setEmptyView(null); 318 mEmptyView.setVisibility(View.GONE); 319 return new CursorLoader(mActivity.getActivityContext(), mFolderListUri, 320 UIProvider.FOLDERS_PROJECTION, null, null, null); 321 } 322 323 public void onAnimationEnd() { 324 if (mFutureData != null) { 325 updateCursorAdapter(mFutureData); 326 mFutureData = null; 327 } 328 } 329 330 private void updateCursorAdapter(Cursor data) { 331 mCursorAdapter.setCursor(data); 332 if (data == null || data.getCount() == 0) { 333 mEmptyView.setVisibility(View.VISIBLE); 334 mListView.setEmptyView(mEmptyView); 335 } 336 } 337 338 @Override 339 public void onLoadFinished(Loader<Cursor> loader, Cursor data) { 340 if (mConversationListCallback == null || !mConversationListCallback.isAnimating()) { 341 updateCursorAdapter(data); 342 } else { 343 mFutureData = data; 344 mCursorAdapter.setCursor(null); 345 } 346 } 347 348 @Override 349 public void onLoaderReset(Loader<Cursor> loader) { 350 mCursorAdapter.setCursor(null); 351 } 352 353 /** 354 * Interface for all cursor adapters that allow setting a cursor and being destroyed. 355 */ 356 private interface FolderListFragmentCursorAdapter extends ListAdapter { 357 /** Update the folder list cursor with the cursor given here. */ 358 void setCursor(Cursor cursor); 359 /** Get the cursor associated with this adapter **/ 360 Folder getFullFolder(FolderListAdapter.Item item); 361 /** Remove all observers and destroy the object. */ 362 void destroy(); 363 /** Notifies the adapter that the data has changed. */ 364 void notifyDataSetChanged(); 365 } 366 367 /** 368 * An adapter for flat folder lists. 369 */ 370 private class FolderListAdapter extends BaseAdapter implements FolderListFragmentCursorAdapter { 371 372 private final RecentFolderObserver mRecentFolderObserver = new RecentFolderObserver() { 373 @Override 374 public void onChanged() { 375 recalculateList(); 376 } 377 }; 378 379 private final RecentFolderList mRecentFolders; 380 /** True if the list is sectioned, false otherwise */ 381 private final boolean mIsSectioned; 382 private final LayoutInflater mInflater; 383 /** All the items */ 384 private final List<Item> mItemList = new ArrayList<Item>(); 385 /** Cursor into the folder list. This might be null. */ 386 private Cursor mCursor = null; 387 388 /** A union of either a folder or a resource string */ 389 private class Item { 390 public int mPosition; 391 public final Folder mFolder; 392 public final int mResource; 393 /** Either {@link #VIEW_FOLDER} or {@link #VIEW_HEADER} */ 394 public final int mType; 395 /** A normal folder, also a child, if a parent is specified. */ 396 private static final int VIEW_FOLDER = 0; 397 /** A text-label which serves as a header in sectioned lists. */ 398 private static final int VIEW_HEADER = 1; 399 400 /** 401 * Either {@link #FOLDER_SYSTEM}, {@link #FOLDER_RECENT} or {@link #FOLDER_USER} when 402 * {@link #mType} is {@link #VIEW_FOLDER}, and {@link #NOT_A_FOLDER} otherwise. 403 */ 404 public final int mFolderType; 405 private static final int NOT_A_FOLDER = 0; 406 private static final int FOLDER_SYSTEM = 1; 407 private static final int FOLDER_RECENT = 2; 408 private static final int FOLDER_USER = 3; 409 410 /** 411 * Create a folder item with the given type. 412 * @param folder 413 * @param folderType one of {@link #FOLDER_SYSTEM}, {@link #FOLDER_RECENT} or 414 * {@link #FOLDER_USER} 415 */ 416 private Item(Folder folder, int folderType, int cursorPosition) { 417 mFolder = folder; 418 mResource = -1; 419 mType = VIEW_FOLDER; 420 mFolderType = folderType; 421 mPosition = cursorPosition; 422 } 423 /** 424 * Create a header item with a string resource. 425 * @param resource the string resource: R.string.all_folders_heading 426 */ 427 private Item(int resource) { 428 mFolder = null; 429 mResource = resource; 430 mType = VIEW_HEADER; 431 mFolderType = NOT_A_FOLDER; 432 } 433 434 private final View getView(int position, View convertView, ViewGroup parent) { 435 if (mType == VIEW_FOLDER) { 436 return getFolderView(position, convertView, parent); 437 } else { 438 return getHeaderView(position, convertView, parent); 439 } 440 } 441 442 /** 443 * Returns a text divider between sections. 444 * @param convertView 445 * @param parent 446 * @return a text header at the given position. 447 */ 448 private final View getHeaderView(int position, View convertView, ViewGroup parent) { 449 final TextView headerView; 450 if (convertView != null) { 451 headerView = (TextView) convertView; 452 } else { 453 headerView = (TextView) mInflater.inflate( 454 R.layout.folder_list_header, parent, false); 455 } 456 headerView.setText(mResource); 457 return headerView; 458 } 459 460 /** 461 * Return a folder: either a parent folder or a normal (child or flat) 462 * folder. 463 * @param position 464 * @param convertView 465 * @param parent 466 * @return a view showing a folder at the given position. 467 */ 468 private final View getFolderView(int position, View convertView, ViewGroup parent) { 469 final FolderItemView folderItemView; 470 if (convertView != null) { 471 folderItemView = (FolderItemView) convertView; 472 } else { 473 folderItemView = 474 (FolderItemView) mInflater.inflate(R.layout.folder_item, null, false); 475 } 476 folderItemView.bind(mFolder, mActivity); 477 if (mListView != null) { 478 final boolean isSelected = (mFolderType == mSelectedFolderType) 479 && mFolder.uri.equals(mSelectedFolderUri); 480 mListView.setItemChecked(position, isSelected); 481 } 482 Folder.setFolderBlockColor(mFolder, folderItemView.findViewById(R.id.color_block)); 483 Folder.setIcon(mFolder, (ImageView) folderItemView.findViewById(R.id.folder_box)); 484 return folderItemView; 485 } 486 } 487 488 /** 489 * Creates a {@link FolderListAdapter}.This is a flat folder list of all the folders for the 490 * given account. 491 * @param layout 492 * @param isSectioned TODO(viki): 493 */ 494 public FolderListAdapter(int layout, boolean isSectioned) { 495 super(); 496 mInflater = LayoutInflater.from(mActivity.getActivityContext()); 497 mIsSectioned = isSectioned; 498 final RecentFolderController controller = mActivity.getRecentFolderController(); 499 if (controller != null && mIsSectioned) { 500 mRecentFolders = mRecentFolderObserver.initialize(controller); 501 } else { 502 mRecentFolders = null; 503 } 504 } 505 506 @Override 507 public View getView(int position, View convertView, ViewGroup parent) { 508 return ((Item) getItem(position)).getView(position, convertView, parent); 509 } 510 511 @Override 512 public int getViewTypeCount() { 513 // Headers and folders 514 return 2; 515 } 516 517 @Override 518 public int getItemViewType(int position) { 519 return ((Item) 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 // We disallow taps on headers 530 return ((Item) getItem(position)).mType != Item.VIEW_HEADER; 531 } 532 533 @Override 534 public boolean areAllItemsEnabled() { 535 // The headers are not enabled. 536 return false; 537 } 538 539 /** 540 * Returns all the recent folders from the list given here. Safe to call with a null list. 541 * @param recentList 542 * @return a valid list of folders, which are all recent folders. 543 */ 544 private final List<Folder> getRecentFolders(RecentFolderList recentList) { 545 final List<Folder> folderList = new ArrayList<Folder>(); 546 if (recentList == null) { 547 return folderList; 548 } 549 // Get all recent folders, after removing system folders. 550 for (final Folder f : recentList.getRecentFolderList(null)) { 551 if (!f.isProviderFolder()) { 552 folderList.add(f); 553 } 554 } 555 return folderList; 556 } 557 558 /** 559 * Recalculates the system, recent and user label lists. Notifies that the data has changed. 560 * This method modifies all the three lists on every single invocation. 561 */ 562 private void recalculateList() { 563 if (mCursor == null || mCursor.isClosed() || mCursor.getCount() <= 0 564 || !mCursor.moveToFirst()) { 565 return; 566 } 567 mItemList.clear(); 568 if (!mIsSectioned) { 569 // Adapter for a flat list. Everything is a FOLDER_USER, and there are no headers. 570 do { 571 final Folder f = Folder.getDeficientDisplayOnlyFolder(mCursor); 572 if (mExcludedFolderTypes == null || !mExcludedFolderTypes.contains(f.type)) { 573 mItemList.add(new Item(f, Item.FOLDER_USER, mCursor.getPosition())); 574 } 575 } while (mCursor.moveToNext()); 576 // Ask the list to invalidate its views. 577 notifyDataSetChanged(); 578 return; 579 } 580 581 // Otherwise, this is an adapter for a sectioned list. 582 // First add all the system folders. 583 final List<Item> userFolderList = new ArrayList<Item>(); 584 do { 585 final Folder f = Folder.getDeficientDisplayOnlyFolder(mCursor); 586 if (mExcludedFolderTypes == null || !mExcludedFolderTypes.contains(f.type)) { 587 if (f.isProviderFolder()) { 588 mItemList.add(new Item(f, Item.FOLDER_SYSTEM, mCursor.getPosition())); 589 } else { 590 userFolderList.add(new Item(f, Item.FOLDER_USER, mCursor.getPosition())); 591 } 592 } 593 } while (mCursor.moveToNext()); 594 // If there are recent folders, add them and a header. 595 final List<Folder> recentFolderList = getRecentFolders(mRecentFolders); 596 597 // Remove any excluded folder types 598 if (mExcludedFolderTypes != null) { 599 final Iterator<Folder> iterator = recentFolderList.iterator(); 600 while (iterator.hasNext()) { 601 if (mExcludedFolderTypes.contains(iterator.next().type)) { 602 iterator.remove(); 603 } 604 } 605 } 606 607 if (recentFolderList.size() > 0) { 608 mItemList.add(new Item(R.string.recent_folders_heading)); 609 for (Folder f : recentFolderList) { 610 mItemList.add(new Item(f, Item.FOLDER_RECENT, -1)); 611 } 612 } 613 // If there are user folders, add them and a header. 614 if (userFolderList.size() > 0) { 615 mItemList.add(new Item(R.string.all_folders_heading)); 616 for (final Item i : userFolderList) { 617 mItemList.add(i); 618 } 619 } 620 // Ask the list to invalidate its views. 621 notifyDataSetChanged(); 622 } 623 624 @Override 625 public void setCursor(Cursor cursor) { 626 mCursor = cursor; 627 recalculateList(); 628 } 629 630 @Override 631 public Object getItem(int position) { 632 return mItemList.get(position); 633 } 634 635 @Override 636 public long getItemId(int position) { 637 return getItem(position).hashCode(); 638 } 639 640 @Override 641 public final void destroy() { 642 mRecentFolderObserver.unregisterAndDestroy(); 643 } 644 645 @Override 646 public Folder getFullFolder(Item folderItem) { 647 if (folderItem.mFolderType == Item.FOLDER_RECENT) { 648 return folderItem.mFolder; 649 } else { 650 int pos = folderItem.mPosition; 651 if (mFutureData != null) { 652 mCursor = mFutureData; 653 mFutureData = null; 654 } 655 if (pos > -1 && mCursor != null && !mCursor.isClosed() 656 && mCursor.moveToPosition(folderItem.mPosition)) { 657 return new Folder(mCursor); 658 } else { 659 return null; 660 } 661 } 662 } 663 } 664 665 private class HierarchicalFolderListAdapter extends ArrayAdapter<Folder> 666 implements FolderListFragmentCursorAdapter{ 667 668 private static final int PARENT = 0; 669 private static final int CHILD = 1; 670 private final Uri mParentUri; 671 private final Folder mParent; 672 private final FolderItemView.DropHandler mDropHandler; 673 private Cursor mCursor; 674 675 public HierarchicalFolderListAdapter(Cursor c, Folder parentFolder) { 676 super(mActivity.getActivityContext(), R.layout.folder_item); 677 mDropHandler = mActivity; 678 mParent = parentFolder; 679 mParentUri = parentFolder.uri; 680 setCursor(c); 681 } 682 683 @Override 684 public int getViewTypeCount() { 685 // Child and Parent 686 return 2; 687 } 688 689 @Override 690 public int getItemViewType(int position) { 691 Folder f = getItem(position); 692 return f.uri.equals(mParentUri) ? PARENT : CHILD; 693 } 694 695 @Override 696 public View getView(int position, View convertView, ViewGroup parent) { 697 FolderItemView folderItemView; 698 Folder folder = getItem(position); 699 boolean isParent = folder.uri.equals(mParentUri); 700 if (convertView != null) { 701 folderItemView = (FolderItemView) convertView; 702 } else { 703 int resId = isParent ? R.layout.folder_item : R.layout.child_folder_item; 704 folderItemView = (FolderItemView) LayoutInflater.from( 705 mActivity.getActivityContext()).inflate(resId, null); 706 } 707 folderItemView.bind(folder, mDropHandler); 708 if (folder.uri.equals(mSelectedFolderUri)) { 709 getListView().setItemChecked(position, true); 710 } 711 Folder.setFolderBlockColor(folder, folderItemView.findViewById(R.id.folder_box)); 712 return folderItemView; 713 } 714 715 @Override 716 public void setCursor(Cursor cursor) { 717 mCursor = cursor; 718 clear(); 719 if (mParent != null) { 720 add(mParent); 721 } 722 if (cursor != null && cursor.getCount() > 0) { 723 cursor.moveToFirst(); 724 do { 725 Folder f = new Folder(cursor); 726 f.parent = mParent; 727 add(f); 728 } while (cursor.moveToNext()); 729 } 730 } 731 732 @Override 733 public void destroy() { 734 // Do nothing. 735 } 736 737 @Override 738 public Folder getFullFolder(FolderListAdapter.Item folderItem) { 739 int pos = folderItem.mPosition; 740 if (mCursor == null || mCursor.isClosed()) { 741 // See if we have a cursor hanging out we can use 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 /** 755 * Sets the currently selected folder safely. 756 * @param folder 757 */ 758 private void setSelectedFolder(Folder folder) { 759 if (folder == null) { 760 mSelectedFolderUri = Uri.EMPTY; 761 return; 762 } 763 mSelectedFolderUri = folder.uri; 764 setSelectedFolderType(folder); 765 if (mCursorAdapter != null) { 766 mCursorAdapter.notifyDataSetChanged(); 767 } 768 } 769 770 /** 771 * Sets the selected folder type safely. 772 * @param folder 773 */ 774 private void setSelectedFolderType(Folder folder) { 775 // If it is set already, assume it is correct. 776 if (mSelectedFolderType != FolderListAdapter.Item.NOT_A_FOLDER) { 777 return; 778 } 779 mSelectedFolderType = folder.isProviderFolder() ? FolderListAdapter.Item.FOLDER_SYSTEM 780 : FolderListAdapter.Item.FOLDER_USER; 781 } 782 783 public interface FolderListSelectionListener { 784 public void onFolderSelected(Folder folder); 785 } 786 787 /** 788 * Get whether the FolderListFragment is currently showing the hierarchy 789 * under a single parent. 790 */ 791 public boolean showingHierarchy() { 792 return mParentFolder != null; 793 } 794} 795