• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 androidx.core.text.BidiFormatter;
27 import androidx.collection.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 
FolderHolder(final View itemView, final TextView sendersTextView, final TextView countTextView, final ImageView folderIconImageView)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 
setFolder(final Folder folder)130         public void setFolder(final Folder folder) {
131             mFolder = folder;
132         }
133 
getItemView()134         public View getItemView() {
135             return mItemView;
136         }
137 
getSendersTextView()138         public TextView getSendersTextView() {
139             return mSendersTextView;
140         }
141 
getCountTextView()142         public TextView getCountTextView() {
143             return mCountTextView;
144         }
145 
getFolderIconImageView()146         public ImageView getFolderIconImageView() { return mFolderIconImageView; }
147 
getFolder()148         public Folder getFolder() {
149             return mFolder;
150         }
151 
152         /**
153          * @return a {@link List} of senders of unread messages
154          */
getUnreadSenders()155         public List<String> getUnreadSenders() {
156             return mUnreadSenders;
157         }
158 
setUnreadSenders(final List<String> unreadSenders)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 
NestedFolderTeaserView(final Context context)172     public NestedFolderTeaserView(final Context context) {
173         this(context, null);
174     }
175 
NestedFolderTeaserView(final Context context, final AttributeSet attrs)176     public NestedFolderTeaserView(final Context context, final AttributeSet attrs) {
177         this(context, attrs, -1);
178     }
179 
NestedFolderTeaserView( final Context context, final AttributeSet attrs, final int defStyle)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
onFinishInflate()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 
bind(final Account account, final FolderSelector listener)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      */
createFolderHolder(final CharSequence folderName)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 
attachOnClickListener(final View view, final FolderHolder holder)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
onUpdate(final Folder folder, final ConversationCursor cursor)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
onGetView()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 
updateShowMoreView()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 
updateViews(final FolderHolder folderHolder)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
getShouldDisplayInList()355     public boolean getShouldDisplayInList() {
356         return mShouldDisplayInList;
357     }
358 
359     @Override
getPosition()360     public int getPosition() {
361         return 0;
362     }
363 
364     @Override
setAdapter(final AnimatedAdapter adapter)365     public void setAdapter(final AnimatedAdapter adapter) {
366         mAdapter = adapter;
367     }
368 
369     @Override
bindFragment(final LoaderManager loaderManager, final Bundle savedInstanceState)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
cleanup()379     public void cleanup() {
380         // Do nothing
381     }
382 
383     @Override
onConversationSelected()384     public void onConversationSelected() {
385         // Do nothing
386     }
387 
388     @Override
onCabModeEntered()389     public void onCabModeEntered() {
390         // Do nothing
391     }
392 
393     @Override
onCabModeExited()394     public void onCabModeExited() {
395         // Do nothing
396     }
397 
398     @Override
onConversationListVisibilityChanged(final boolean visible)399     public void onConversationListVisibilityChanged(final boolean visible) {
400         // Do nothing
401     }
402 
403     @Override
saveInstanceState(final Bundle outState)404     public void saveInstanceState(final Bundle outState) {
405         // Do nothing
406     }
407 
408     @Override
acceptsUserTaps()409     public boolean acceptsUserTaps() {
410         // The teaser does not allow user tap in the list.
411         return false;
412     }
413 
getLoaderId(final int folderId)414     private static int getLoaderId(final int folderId) {
415         return folderId + LOADER_FOLDER_LIST;
416     }
417 
getFolderId(final int loaderId)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      */
populateUnreadSenders(final FolderHolder folderHolder, final String unreadSenders)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
commitLeaveBehindItem()657     public boolean commitLeaveBehindItem() {
658         // This view has no leave-behind
659         return false;
660     }
661 }
662