Home | History | Annotate | Download | only in ui
      1 /*
      2  * Copyright (C) 2013 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 package com.android.mail.ui;
     18 
     19 import android.app.LoaderManager;
     20 import android.app.LoaderManager.LoaderCallbacks;
     21 import android.content.Context;
     22 import android.content.Loader;
     23 import android.content.res.Resources;
     24 import android.net.Uri;
     25 import android.os.Bundle;
     26 import android.support.v4.text.BidiFormatter;
     27 import android.support.v4.util.SparseArrayCompat;
     28 import android.text.TextUtils;
     29 import android.util.AttributeSet;
     30 import android.view.LayoutInflater;
     31 import android.view.View;
     32 import android.view.ViewGroup;
     33 import android.widget.ImageView;
     34 import android.widget.LinearLayout;
     35 import android.widget.TextView;
     36 
     37 import com.android.emailcommon.mail.Address;
     38 import com.android.mail.R;
     39 import com.android.mail.browse.ConversationCursor;
     40 import com.android.mail.content.ObjectCursor;
     41 import com.android.mail.content.ObjectCursorLoader;
     42 import com.android.mail.providers.Account;
     43 import com.android.mail.providers.Conversation;
     44 import com.android.mail.providers.Folder;
     45 import com.android.mail.providers.ParticipantInfo;
     46 import com.android.mail.providers.UIProvider;
     47 import com.android.mail.providers.UIProvider.AccountCapabilities;
     48 import com.android.mail.providers.UIProvider.ConversationListQueryParameters;
     49 import com.android.mail.utils.LogUtils;
     50 import com.android.mail.utils.Utils;
     51 import com.google.common.collect.ImmutableList;
     52 import com.google.common.collect.ImmutableSortedSet;
     53 import com.google.common.collect.Lists;
     54 import com.google.common.collect.Maps;
     55 
     56 import java.util.ArrayList;
     57 import java.util.Collections;
     58 import java.util.Comparator;
     59 import java.util.List;
     60 import java.util.Map;
     61 
     62 /**
     63  * The teaser list item in the conversation list that shows nested folders.
     64  */
     65 public class NestedFolderTeaserView extends LinearLayout implements ConversationSpecialItemView {
     66     private static final String LOG_TAG = "NestedFolderTeaserView";
     67 
     68     private boolean mShouldDisplayInList = false;
     69 
     70     private Account mAccount;
     71     private Uri mFolderListUri;
     72     private FolderSelector mListener;
     73 
     74     private LoaderManager mLoaderManager = null;
     75     private AnimatedAdapter mAdapter = null;
     76 
     77     private final SparseArrayCompat<FolderHolder> mFolderHolders =
     78             new SparseArrayCompat<FolderHolder>();
     79     private ImmutableSortedSet<FolderHolder> mSortedFolderHolders;
     80 
     81     private final int mFolderItemUpdateDelayMs;
     82 
     83     private final LayoutInflater mInflater;
     84     private ViewGroup mNestedFolderContainer;
     85 
     86     private View mShowMoreFoldersRow;
     87     private ImageView mShowMoreFoldersIcon;
     88     private TextView mShowMoreFoldersTextView;
     89     private TextView mShowMoreFoldersCountTextView;
     90 
     91     /**
     92      * If <code>true</code> we show a limited set of folders, and a means to show all folders. If
     93      * <code>false</code>, we show all folders.
     94      */
     95     private boolean mCollapsed = true;
     96 
     97     /** If <code>true</code>, the list of folders has updated since the view was last shown. */
     98     private boolean mListUpdated;
     99 
    100     // Each folder's loader will be this value plus the folder id
    101     private static final int LOADER_FOLDER_LIST =
    102             AbstractActivityController.LAST_FRAGMENT_LOADER_ID + 100000;
    103 
    104     /**
    105      * The maximum number of senders to show in the sender snippet.
    106      */
    107     private static final String MAX_SENDERS = "20";
    108 
    109     /**
    110      * The number of folders to show when the teaser is collapsed.
    111      */
    112     private static int sCollapsedFolderThreshold = -1;
    113 
    114     private static class FolderHolder {
    115         private final View mItemView;
    116         private final TextView mSendersTextView;
    117         private final TextView mCountTextView;
    118         private final ImageView mFolderIconImageView;
    119         private Folder mFolder;
    120         private List<String> mUnreadSenders = ImmutableList.of();
    121 
    122         public FolderHolder(final View itemView, final TextView sendersTextView,
    123                 final TextView countTextView, final ImageView folderIconImageView) {
    124             mItemView = itemView;
    125             mSendersTextView = sendersTextView;
    126             mCountTextView = countTextView;
    127             mFolderIconImageView = folderIconImageView;
    128         }
    129 
    130         public void setFolder(final Folder folder) {
    131             mFolder = folder;
    132         }
    133 
    134         public View getItemView() {
    135             return mItemView;
    136         }
    137 
    138         public TextView getSendersTextView() {
    139             return mSendersTextView;
    140         }
    141 
    142         public TextView getCountTextView() {
    143             return mCountTextView;
    144         }
    145 
    146         public ImageView getFolderIconImageView() { return mFolderIconImageView; }
    147 
    148         public Folder getFolder() {
    149             return mFolder;
    150         }
    151 
    152         /**
    153          * @return a {@link List} of senders of unread messages
    154          */
    155         public List<String> getUnreadSenders() {
    156             return mUnreadSenders;
    157         }
    158 
    159         public void setUnreadSenders(final List<String> unreadSenders) {
    160             mUnreadSenders = unreadSenders;
    161         }
    162 
    163         public static final Comparator<FolderHolder> NAME_COMPARATOR =
    164                 new Comparator<FolderHolder>() {
    165             @Override
    166             public int compare(final FolderHolder lhs, final FolderHolder rhs) {
    167                 return lhs.getFolder().name.compareTo(rhs.getFolder().name);
    168             }
    169         };
    170     }
    171 
    172     public NestedFolderTeaserView(final Context context) {
    173         this(context, null);
    174     }
    175 
    176     public NestedFolderTeaserView(final Context context, final AttributeSet attrs) {
    177         this(context, attrs, -1);
    178     }
    179 
    180     public NestedFolderTeaserView(
    181             final Context context, final AttributeSet attrs, final int defStyle) {
    182         super(context, attrs, defStyle);
    183 
    184         final Resources resources = context.getResources();
    185 
    186         if (sCollapsedFolderThreshold < 0) {
    187             sCollapsedFolderThreshold =
    188                     resources.getInteger(R.integer.nested_folders_collapse_threshold);
    189         }
    190 
    191         mFolderItemUpdateDelayMs = resources.getInteger(R.integer.folder_item_refresh_delay_ms);
    192         mInflater = LayoutInflater.from(context);
    193     }
    194 
    195     @Override
    196     protected void onFinishInflate() {
    197         mNestedFolderContainer = (ViewGroup) findViewById(R.id.nested_folder_container);
    198 
    199         mShowMoreFoldersRow = findViewById(R.id.show_more_folders_row);
    200         mShowMoreFoldersRow.setOnClickListener(mShowMoreOnClickListener);
    201 
    202         mShowMoreFoldersIcon =
    203                 (ImageView) mShowMoreFoldersRow.findViewById(R.id.show_more_folders_icon);
    204         mShowMoreFoldersTextView =
    205                 (TextView) mShowMoreFoldersRow.findViewById(R.id.show_more_folders_textView);
    206         mShowMoreFoldersCountTextView =
    207                 (TextView) mShowMoreFoldersRow.findViewById(R.id.show_more_folders_count_textView);
    208     }
    209 
    210     public void bind(final Account account, final FolderSelector listener) {
    211         mAccount = account;
    212         mListener = listener;
    213     }
    214 
    215     /**
    216      * Creates a {@link FolderHolder}.
    217      */
    218     private FolderHolder createFolderHolder(final CharSequence folderName) {
    219         final View itemView = mInflater.inflate(R.layout.folder_teaser_item, mNestedFolderContainer,
    220                 false /* attachToRoot */);
    221 
    222         ((TextView) itemView.findViewById(R.id.folder_textView)).setText(folderName);
    223         final TextView sendersTextView = (TextView) itemView.findViewById(R.id.senders_textView);
    224         final TextView countTextView = (TextView) itemView.findViewById(R.id.unread_count_textView);
    225         final ImageView folderIconImageView =
    226                 (ImageView) itemView.findViewById(R.id.nested_folder_icon);
    227         final FolderHolder holder = new FolderHolder(itemView, sendersTextView, countTextView,
    228                 folderIconImageView);
    229         countTextView.setVisibility(View.VISIBLE);
    230         attachOnClickListener(itemView, holder);
    231 
    232         return holder;
    233     }
    234 
    235     private void attachOnClickListener(final View view, final FolderHolder holder) {
    236         view.setOnClickListener(new OnClickListener() {
    237             @Override
    238             public void onClick(final View v) {
    239                 mListener.onFolderSelected(holder.getFolder());
    240             }
    241         });
    242     }
    243 
    244     @Override
    245     public void onUpdate(final Folder folder, final ConversationCursor cursor) {
    246         mShouldDisplayInList = false; // Assume disabled
    247 
    248         if (folder == null) {
    249             return;
    250         }
    251 
    252         final Uri folderListUri = folder.childFoldersListUri;
    253         if (folderListUri == null) {
    254             return;
    255         }
    256 
    257         // If we don't support nested folders, don't show this view
    258         if (!mAccount.supportsCapability(AccountCapabilities.NESTED_FOLDERS)) {
    259             return;
    260         }
    261 
    262         if (mFolderListUri == null || !mFolderListUri.equals(folder.childFoldersListUri)) {
    263             // We have a new uri
    264             mFolderListUri = folderListUri;
    265 
    266             // Restart the loader
    267             mLoaderManager.destroyLoader(LOADER_FOLDER_LIST);
    268             mLoaderManager.initLoader(LOADER_FOLDER_LIST, null, mFolderListLoaderCallbacks);
    269         }
    270 
    271         mShouldDisplayInList = true; // Now we know we have something to display
    272     }
    273 
    274     @Override
    275     public void onGetView() {
    276         if (mListUpdated) {
    277             // Clear out the folder views
    278             mNestedFolderContainer.removeAllViews();
    279 
    280             // We either show all folders if it's not over the threshold, or we show none.
    281             if (mSortedFolderHolders.size() <= sCollapsedFolderThreshold || !mCollapsed) {
    282                 for (final FolderHolder folderHolder : mSortedFolderHolders) {
    283                     mNestedFolderContainer.addView(folderHolder.getItemView());
    284                 }
    285             }
    286 
    287             updateShowMoreView();
    288             mListUpdated = false;
    289         }
    290     }
    291 
    292     private final OnClickListener mShowMoreOnClickListener = new OnClickListener() {
    293         @Override
    294         public void onClick(final View v) {
    295             mCollapsed = !mCollapsed;
    296             mListUpdated = true;
    297             mAdapter.notifyDataSetChanged();
    298         }
    299     };
    300 
    301     private void updateShowMoreView() {
    302         final int total = mFolderHolders.size();
    303         final int displayed = mNestedFolderContainer.getChildCount();
    304 
    305         if (displayed == 0) {
    306             // We are not displaying all the folders
    307             mShowMoreFoldersRow.setVisibility(VISIBLE);
    308             mShowMoreFoldersIcon.setImageResource(R.drawable.ic_drawer_folder_24dp);
    309             mShowMoreFoldersTextView.setText(String.format(
    310                     getContext().getString(R.string.show_n_more_folders), total));
    311             mShowMoreFoldersCountTextView.setVisibility(VISIBLE);
    312 
    313             // Get a count of unread messages in other folders
    314             int unreadCount = 0;
    315             for (int i = 0; i < mFolderHolders.size(); i++) {
    316                 final FolderHolder holder = mFolderHolders.valueAt(i);
    317                 // TODO(skennedy) We want a "nested" unread count, that includes the unread
    318                 // count of nested folders
    319                 unreadCount += holder.getFolder().unreadCount;
    320             }
    321             mShowMoreFoldersCountTextView.setText(Integer.toString(unreadCount));
    322         } else if (displayed > sCollapsedFolderThreshold) {
    323             // We are expanded
    324             mShowMoreFoldersRow.setVisibility(VISIBLE);
    325             mShowMoreFoldersIcon.setImageResource(R.drawable.ic_collapse_24dp);
    326             mShowMoreFoldersTextView.setText(R.string.hide_folders);
    327             mShowMoreFoldersCountTextView.setVisibility(GONE);
    328         } else {
    329             // We don't need to collapse the folders
    330             mShowMoreFoldersRow.setVisibility(GONE);
    331         }
    332     }
    333 
    334     private void updateViews(final FolderHolder folderHolder) {
    335         final Folder folder = folderHolder.getFolder();
    336 
    337         // Update unread count
    338         final String unreadText = Utils.getUnreadCountString(getContext(), folder.unreadCount);
    339         folderHolder.getCountTextView().setText(unreadText.isEmpty() ? "0" : unreadText);
    340 
    341         // Update unread senders
    342         final String sendersText = TextUtils.join(
    343                 getResources().getString(R.string.enumeration_comma),
    344                 folderHolder.getUnreadSenders());
    345         final TextView sendersTextView = folderHolder.getSendersTextView();
    346         if (!TextUtils.isEmpty(sendersText)) {
    347             sendersTextView.setVisibility(VISIBLE);
    348             sendersTextView.setText(sendersText);
    349         } else {
    350             sendersTextView.setVisibility(GONE);
    351         }
    352     }
    353 
    354     @Override
    355     public boolean getShouldDisplayInList() {
    356         return mShouldDisplayInList;
    357     }
    358 
    359     @Override
    360     public int getPosition() {
    361         return 0;
    362     }
    363 
    364     @Override
    365     public void setAdapter(final AnimatedAdapter adapter) {
    366         mAdapter = adapter;
    367     }
    368 
    369     @Override
    370     public void bindFragment(final LoaderManager loaderManager, final Bundle savedInstanceState) {
    371         if (mLoaderManager != null) {
    372             throw new IllegalStateException("This view has already been bound to a LoaderManager.");
    373         }
    374 
    375         mLoaderManager = loaderManager;
    376     }
    377 
    378     @Override
    379     public void cleanup() {
    380         // Do nothing
    381     }
    382 
    383     @Override
    384     public void onConversationSelected() {
    385         // Do nothing
    386     }
    387 
    388     @Override
    389     public void onCabModeEntered() {
    390         // Do nothing
    391     }
    392 
    393     @Override
    394     public void onCabModeExited() {
    395         // Do nothing
    396     }
    397 
    398     @Override
    399     public void onConversationListVisibilityChanged(final boolean visible) {
    400         // Do nothing
    401     }
    402 
    403     @Override
    404     public void saveInstanceState(final Bundle outState) {
    405         // Do nothing
    406     }
    407 
    408     @Override
    409     public boolean acceptsUserTaps() {
    410         // The teaser does not allow user tap in the list.
    411         return false;
    412     }
    413 
    414     private static int getLoaderId(final int folderId) {
    415         return folderId + LOADER_FOLDER_LIST;
    416     }
    417 
    418     private static int getFolderId(final int loaderId) {
    419         return loaderId - LOADER_FOLDER_LIST;
    420     }
    421 
    422     private final LoaderCallbacks<ObjectCursor<Folder>> mFolderListLoaderCallbacks =
    423             new LoaderCallbacks<ObjectCursor<Folder>>() {
    424         @Override
    425         public void onLoaderReset(final Loader<ObjectCursor<Folder>> loader) {
    426             // Do nothing
    427         }
    428 
    429         @Override
    430         public void onLoadFinished(final Loader<ObjectCursor<Folder>> loader,
    431                 final ObjectCursor<Folder> data) {
    432             if (data != null) {
    433                 // We need to keep track of all current folders in case one has been removed
    434                 final List<Integer> oldFolderIds = new ArrayList<Integer>(mFolderHolders.size());
    435                 for (int i = 0; i < mFolderHolders.size(); i++) {
    436                     oldFolderIds.add(mFolderHolders.keyAt(i));
    437                 }
    438 
    439                 if (data.moveToFirst()) {
    440                     do {
    441                         final Folder folder = data.getModel();
    442                         FolderHolder holder = mFolderHolders.get(folder.id);
    443 
    444                         if (holder != null) {
    445                             final Folder oldFolder = holder.getFolder();
    446                             holder.setFolder(folder);
    447 
    448                             /*
    449                              * We only need to change anything if the old Folder was null, or the
    450                              * unread count has changed.
    451                              */
    452                             if (oldFolder == null || oldFolder.unreadCount != folder.unreadCount) {
    453                                 populateUnreadSenders(holder, folder.unreadSenders);
    454                                 updateViews(holder);
    455                             }
    456                         } else {
    457                             // Create the holder, and init a loader
    458                             holder = createFolderHolder(folder.name);
    459                             holder.setFolder(folder);
    460                             mFolderHolders.put(folder.id, holder);
    461 
    462                             // We can not support displaying sender info with nested folders
    463                             // because it doesn't scale. Disabling it for now, until we can
    464                             // optimize it.
    465                             // initFolderLoader(getLoaderId(folder.id));
    466                             populateUnreadSenders(holder, folder.unreadSenders);
    467 
    468                             updateViews(holder);
    469 
    470                             mListUpdated = true;
    471                         }
    472 
    473                         if (folder.hasChildren) {
    474                             holder.getFolderIconImageView().setImageDrawable(
    475                                     getResources().getDrawable(R.drawable.ic_folder_parent_24dp));
    476                         }
    477 
    478                         // Note: #remove(int) removes from that POSITION
    479                         //       #remove(Integer) removes that OBJECT
    480                         oldFolderIds.remove(Integer.valueOf(folder.id));
    481                     } while (data.moveToNext());
    482                 }
    483 
    484                 // Sort the folders by name
    485                 // TODO(skennedy) recents? starred?
    486                 final ImmutableSortedSet.Builder<FolderHolder> folderHoldersBuilder =
    487                         new ImmutableSortedSet.Builder<FolderHolder>(FolderHolder.NAME_COMPARATOR);
    488                 for (int i = 0; i < mFolderHolders.size(); i++) {
    489                     folderHoldersBuilder.add(mFolderHolders.valueAt(i));
    490                 }
    491                 mSortedFolderHolders = folderHoldersBuilder.build();
    492 
    493                 for (final int folderId : oldFolderIds) {
    494                     // We have a folder that no longer exists
    495                     mFolderHolders.remove(folderId);
    496                     mLoaderManager.destroyLoader(getLoaderId(folderId));
    497                     mListUpdated = true;
    498                 }
    499 
    500                 // If the list has not changed, we've already updated the counts, etc.
    501                 // If the list has changed, we need to rebuild it
    502                 if (mListUpdated) {
    503                     mAdapter.notifyDataSetChanged();
    504                 }
    505             } else {
    506                 LogUtils.w(LOG_TAG, "Problem with folder list cursor returned from loader");
    507             }
    508         }
    509 
    510         private void initFolderLoader(final int loaderId) {
    511             LogUtils.d(LOG_TAG, "Initializing folder loader %d", loaderId);
    512             mLoaderManager.initLoader(loaderId, null, mFolderLoaderCallbacks);
    513         }
    514 
    515         @Override
    516         public Loader<ObjectCursor<Folder>> onCreateLoader(final int id, final Bundle args) {
    517             final ObjectCursorLoader<Folder> loader = new ObjectCursorLoader<Folder>(getContext(),
    518                     mFolderListUri, UIProvider.FOLDERS_PROJECTION_WITH_UNREAD_SENDERS,
    519                     Folder.FACTORY);
    520             loader.setUpdateThrottle(mFolderItemUpdateDelayMs);
    521             return loader;
    522         }
    523     };
    524 
    525     /**
    526      * This code is intended to roughly duplicate the FolderLoaderCallback's onLoadFinished
    527      */
    528     private void populateUnreadSenders(final FolderHolder folderHolder,
    529             final String unreadSenders) {
    530         if (TextUtils.isEmpty(unreadSenders)) {
    531             folderHolder.setUnreadSenders(Collections.<String>emptyList());
    532             return;
    533         }
    534         // Use a LinkedHashMap here to maintain ordering
    535         final Map<String, String> emailtoNameMap = Maps.newLinkedHashMap();
    536 
    537         final Address[] senderAddresses = Address.parse(unreadSenders);
    538 
    539         final BidiFormatter bidiFormatter = mAdapter.getBidiFormatter();
    540         for (final Address senderAddress : senderAddresses) {
    541             String sender = senderAddress.getPersonal();
    542             sender = (sender != null) ? bidiFormatter.unicodeWrap(sender) : null;
    543             final String senderEmail = senderAddress.getAddress();
    544 
    545             if (!TextUtils.isEmpty(sender)) {
    546                 final String existingSender = emailtoNameMap.get(senderEmail);
    547                 if (!TextUtils.isEmpty(existingSender)) {
    548                     // Prefer longer names
    549                     if (existingSender.length() >= sender.length()) {
    550                         // old name is longer
    551                         sender = existingSender;
    552                     }
    553                 }
    554                 emailtoNameMap.put(senderEmail, sender);
    555             }
    556             if (emailtoNameMap.size() >= 20) {
    557                 break;
    558             }
    559         }
    560 
    561         final List<String> senders = Lists.newArrayList(emailtoNameMap.values());
    562         folderHolder.setUnreadSenders(senders);
    563     }
    564 
    565     private final LoaderCallbacks<ObjectCursor<Conversation>> mFolderLoaderCallbacks =
    566             new LoaderCallbacks<ObjectCursor<Conversation>>() {
    567         @Override
    568         public void onLoaderReset(final Loader<ObjectCursor<Conversation>> loader) {
    569             // Do nothing
    570         }
    571 
    572         @Override
    573         public void onLoadFinished(final Loader<ObjectCursor<Conversation>> loader,
    574                 final ObjectCursor<Conversation> data) {
    575             // Sometimes names are condensed to just the first name.
    576             // This data structure keeps a map of emails to names
    577             final Map<String, String> emailToNameMap = Maps.newHashMap();
    578             final List<String> senders = Lists.newArrayList();
    579 
    580             final int folderId = getFolderId(loader.getId());
    581 
    582             final FolderHolder folderHolder = mFolderHolders.get(folderId);
    583             final int maxSenders = folderHolder.mFolder.unreadCount;
    584 
    585             if (maxSenders > 0 && data != null && data.moveToFirst()) {
    586                 LogUtils.d(LOG_TAG, "Folder id %d loader finished", folderId);
    587 
    588                 // Look through all conversations until we find 'maxSenders' unread
    589                 int sendersFound = 0;
    590 
    591                 do {
    592                     final Conversation conversation = data.getModel();
    593 
    594                     if (!conversation.read) {
    595                         String sender = null;
    596                         String senderEmail = null;
    597                         int priority = Integer.MIN_VALUE;
    598 
    599                         // Find the highest priority participant
    600                         for (final ParticipantInfo p :
    601                                 conversation.conversationInfo.participantInfos) {
    602                             if (sender == null || priority < p.priority) {
    603                                 sender = p.name;
    604                                 senderEmail = p.email;
    605                                 priority = p.priority;
    606                             }
    607                         }
    608 
    609                         if (sender != null) {
    610                             sendersFound++;
    611                             final String existingSender = emailToNameMap.get(senderEmail);
    612                             if (existingSender != null) {
    613                                 // Prefer longer names
    614                                 if (existingSender.length() >= sender.length()) {
    615                                     // old name is longer
    616                                     sender = existingSender;
    617                                 } else {
    618                                     // new name is longer
    619                                     int index = senders.indexOf(existingSender);
    620                                     senders.set(index, sender);
    621                                 }
    622                             } else {
    623                                 senders.add(sender);
    624                             }
    625                             emailToNameMap.put(senderEmail, sender);
    626                         }
    627                     }
    628                 } while (data.moveToNext() && sendersFound < maxSenders);
    629             } else {
    630                 LogUtils.w(LOG_TAG, "Problem with folder cursor returned from loader");
    631             }
    632 
    633             folderHolder.setUnreadSenders(senders);
    634 
    635             /*
    636              * Just update the views in place. We don't need to call notifyDataSetChanged()
    637              * because we aren't changing the teaser's visibility or position.
    638              */
    639             updateViews(folderHolder);
    640         }
    641 
    642         @Override
    643         public Loader<ObjectCursor<Conversation>> onCreateLoader(final int id, final Bundle args) {
    644             final int folderId = getFolderId(id);
    645             final Uri uri = mFolderHolders.get(folderId).mFolder.conversationListUri
    646                     .buildUpon()
    647                     .appendQueryParameter(ConversationListQueryParameters.USE_NETWORK,
    648                             Boolean.FALSE.toString())
    649                     .appendQueryParameter(ConversationListQueryParameters.LIMIT, MAX_SENDERS)
    650                     .build();
    651             return new ObjectCursorLoader<Conversation>(getContext(), uri,
    652                     UIProvider.CONVERSATION_PROJECTION, Conversation.FACTORY);
    653         }
    654     };
    655 
    656     @Override
    657     public boolean commitLeaveBehindItem() {
    658         // This view has no leave-behind
    659         return false;
    660     }
    661 }
    662