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