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