• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2008 Esmertec AG.
3  * Copyright (C) 2008 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.im.app;
18 
19 import android.app.Activity;
20 import android.content.AsyncQueryHandler;
21 import android.content.ContentQueryMap;
22 import android.content.ContentResolver;
23 import android.content.ContentUris;
24 import android.content.ContentValues;
25 import android.content.Context;
26 import android.content.res.Resources;
27 import android.database.ContentObserver;
28 import android.database.Cursor;
29 import android.database.DataSetObserver;
30 import android.net.Uri;
31 import android.os.RemoteException;
32 import android.util.Log;
33 import android.view.LayoutInflater;
34 import android.view.View;
35 import android.view.ViewGroup;
36 import android.widget.BaseExpandableListAdapter;
37 import android.widget.CursorTreeAdapter;
38 import android.widget.TextView;
39 import android.widget.AbsListView;
40 import android.widget.AbsListView.OnScrollListener;
41 
42 import com.android.im.IImConnection;
43 import com.android.im.R;
44 import com.android.im.plugin.BrandingResourceIDs;
45 import com.android.im.provider.Imps;
46 
47 import java.util.ArrayList;
48 import java.util.Observable;
49 import java.util.Observer;
50 
51 public class ContactListTreeAdapter extends BaseExpandableListAdapter
52         implements AbsListView.OnScrollListener{
53 
54     private static final String[] CONTACT_LIST_PROJECTION = {
55             Imps.ContactList._ID,
56             Imps.ContactList.NAME,
57     };
58 
59     private static final int COLUMN_CONTACT_LIST_ID = 0;
60     private static final int COLUMN_CONTACT_LIST_NAME = 1;
61 
62     Activity mActivity;
63     SimpleAlertHandler mHandler;
64     private LayoutInflater mInflate;
65     private long mProviderId;
66     long mAccountId;
67     Cursor mOngoingConversations;
68     Cursor mSubscriptions;
69     boolean mDataValid;
70     ListTreeAdapter mAdapter;
71     private boolean mHideOfflineContacts;
72 
73     final MyContentObserver mContentObserver;
74     final MyDataSetObserver mDataSetObserver;
75 
76     private ArrayList<Integer> mExpandedGroups;
77 
78     private static final int TOKEN_CONTACT_LISTS = -1;
79     private static final int TOKEN_ONGOING_CONVERSATION = -2;
80     private static final int TOKEN_SUBSCRITPTION = -3;
81 
82     private static final String NON_CHAT_AND_BLOCKED_CONTACTS = "("
83         + Imps.Contacts.LAST_MESSAGE_DATE + " IS NULL) AND ("
84         + Imps.Contacts.TYPE + "!=" + Imps.Contacts.TYPE_BLOCKED + ")";
85 
86     private static final String CONTACTS_SELECTION = Imps.Contacts.CONTACTLIST
87             + "=? AND " + NON_CHAT_AND_BLOCKED_CONTACTS;
88 
89     private static final String ONLINE_CONTACT_SELECTION = CONTACTS_SELECTION
90             + " AND "+ Imps.Contacts.PRESENCE_STATUS + " != " + Imps.Presence.OFFLINE;
91 
log(String msg)92     static final void log(String msg) {
93         Log.d(ImApp.LOG_TAG, "<ContactListAdapter>" + msg);
94     }
95 
96     static final String[] CONTACT_COUNT_PROJECTION = {
97         Imps.Contacts.CONTACTLIST,
98         Imps.Contacts._COUNT,
99     };
100 
101     ContentQueryMap mOnlineContactsCountMap;
102 
103     // Async QueryHandler
104     private final class QueryHandler extends AsyncQueryHandler {
QueryHandler(Context context)105         public QueryHandler(Context context) {
106             super(context.getContentResolver());
107         }
108 
109         @Override
onQueryComplete(int token, Object cookie, Cursor c)110         protected void onQueryComplete(int token, Object cookie, Cursor c) {
111             if(Log.isLoggable(ImApp.LOG_TAG, Log.DEBUG)){
112                 log("onQueryComplete:token=" + token);
113             }
114 
115             if (token == TOKEN_CONTACT_LISTS) {
116                 mDataValid = true;
117                 mAdapter.setGroupCursor(c);
118             } else if (token == TOKEN_ONGOING_CONVERSATION) {
119                 setOngoingConversations(c);
120                 notifyDataSetChanged();
121             } else if (token == TOKEN_SUBSCRITPTION) {
122                 setSubscriptions(c);
123                 notifyDataSetChanged();
124             } else {
125                 int count = mAdapter.getGroupCount();
126                 for (int pos = 0; pos < count; pos++) {
127                     long listId = mAdapter.getGroupId(pos);
128                     if (listId == token) {
129                         mAdapter.setChildrenCursor(pos, c);
130                         break;
131                     }
132                 }
133             }
134         }
135     }
136     private QueryHandler mQueryHandler;
137 
138     private int mScrollState;
139 
140     private boolean mAutoRequery;
141     private boolean mRequeryPending;
142 
ContactListTreeAdapter(IImConnection conn, Activity activity)143     public ContactListTreeAdapter(IImConnection conn, Activity activity) {
144         mActivity = activity;
145         mInflate = activity.getLayoutInflater();
146         mHandler = new SimpleAlertHandler(activity);
147 
148         mAdapter = new ListTreeAdapter(null);
149 
150         mContentObserver = new MyContentObserver();
151         mDataSetObserver = new MyDataSetObserver();
152         mExpandedGroups = new ArrayList<Integer>();
153         mQueryHandler = new QueryHandler(activity);
154 
155         changeConnection(conn);
156     }
157 
changeConnection(IImConnection conn)158     public void changeConnection(IImConnection conn) {
159         mQueryHandler.cancelOperation(TOKEN_ONGOING_CONVERSATION);
160         mQueryHandler.cancelOperation(TOKEN_SUBSCRITPTION);
161         mQueryHandler.cancelOperation(TOKEN_CONTACT_LISTS);
162 
163         synchronized (this) {
164             if (mOngoingConversations != null) {
165                 mOngoingConversations.close();
166                 mOngoingConversations = null;
167             }
168             if (mSubscriptions != null) {
169                 mSubscriptions.close();
170                 mSubscriptions = null;
171             }
172             if (mOnlineContactsCountMap != null) {
173                 mOnlineContactsCountMap.close();
174             }
175         }
176 
177         mAdapter.notifyDataSetChanged();
178         if (conn != null) {
179             try {
180                 mProviderId = conn.getProviderId();
181                 mAccountId = conn.getAccountId();
182                 startQueryOngoingConversations();
183                 startQueryContactLists();
184                 startQuerySubscriptions();
185             } catch (RemoteException e) {
186                 // Service died!
187             }
188         }
189     }
190 
setHideOfflineContacts(boolean hide)191     public void setHideOfflineContacts(boolean hide) {
192         if (mHideOfflineContacts != hide) {
193             mHideOfflineContacts = hide;
194             mAdapter.notifyDataSetChanged();
195         }
196     }
197 
startAutoRequery()198     public void startAutoRequery() {
199         if (Log.isLoggable(ImApp.LOG_TAG, Log.DEBUG)){
200             log("startAutoRequery()");
201         }
202         mAutoRequery = true;
203         if (mRequeryPending) {
204             mRequeryPending = false;
205             startQueryOngoingConversations();
206         }
207     }
208 
startQueryContactLists()209     private void startQueryContactLists() {
210         if (Log.isLoggable(ImApp.LOG_TAG, Log.DEBUG)){
211             log("startQueryContactLists()");
212         }
213 
214         Uri uri = Imps.ContactList.CONTENT_URI;
215         uri = ContentUris.withAppendedId(uri, mProviderId);
216         uri = ContentUris.withAppendedId(uri, mAccountId);
217 
218         mQueryHandler.startQuery(TOKEN_CONTACT_LISTS, null, uri, CONTACT_LIST_PROJECTION,
219                 null, null, Imps.ContactList.DEFAULT_SORT_ORDER);
220     }
221 
startQueryOngoingConversations()222     void startQueryOngoingConversations() {
223         if (Log.isLoggable(ImApp.LOG_TAG, Log.DEBUG)){
224             log("startQueryOngoingConversations()");
225         }
226 
227         Uri uri = Imps.Contacts.CONTENT_URI_CHAT_CONTACTS_BY;
228         uri = ContentUris.withAppendedId(uri, mProviderId);
229         uri = ContentUris.withAppendedId(uri, mAccountId);
230 
231         mQueryHandler.startQuery(TOKEN_ONGOING_CONVERSATION, null, uri,
232                 ContactView.CONTACT_PROJECTION, null, null, Imps.Contacts.DEFAULT_SORT_ORDER);
233     }
234 
startQuerySubscriptions()235     void startQuerySubscriptions() {
236         if (Log.isLoggable(ImApp.LOG_TAG, Log.DEBUG)){
237             log("startQuerySubscriptions()");
238         }
239 
240         Uri uri = Imps.Contacts.CONTENT_URI_CONTACTS_BY;
241         uri = ContentUris.withAppendedId(uri, mProviderId);
242         uri = ContentUris.withAppendedId(uri, mAccountId);
243 
244         mQueryHandler.startQuery(TOKEN_SUBSCRITPTION, null, uri,
245                 ContactView.CONTACT_PROJECTION,
246                 String.format("%s=%d AND %s=%d",
247                     Imps.Contacts.SUBSCRIPTION_STATUS, Imps.Contacts.SUBSCRIPTION_STATUS_SUBSCRIBE_PENDING,
248                     Imps.Contacts.SUBSCRIPTION_TYPE, Imps.Contacts.SUBSCRIPTION_TYPE_FROM),
249                 null,Imps.Contacts.DEFAULT_SORT_ORDER);
250     }
251 
startQueryContacts(long listId)252     void startQueryContacts(long listId) {
253         if (Log.isLoggable(ImApp.LOG_TAG, Log.DEBUG)){
254             log("startQueryContacts - listId=" + listId);
255         }
256 
257         String selection = mHideOfflineContacts ? ONLINE_CONTACT_SELECTION : CONTACTS_SELECTION;
258         String[] args = { Long.toString(listId) };
259         int token = (int)listId;
260         mQueryHandler.startQuery(token, null, Imps.Contacts.CONTENT_URI,
261                 ContactView.CONTACT_PROJECTION, selection, args, Imps.Contacts.DEFAULT_SORT_ORDER);
262     }
263 
getChild(int groupPosition, int childPosition)264     public Object getChild(int groupPosition, int childPosition) {
265         if (isPosForOngoingConversation(groupPosition)) {
266             // No cursor exists for the "Empty" TextView item
267             if (getOngoingConversationCount() == 0) return null;
268             return moveTo(getOngoingConversations(), childPosition);
269         } else if (isPosForSubscription(groupPosition)) {
270             return moveTo(getSubscriptions(), childPosition);
271         } else {
272             return mAdapter.getChild(getChildAdapterPosition(groupPosition), childPosition);
273         }
274     }
275 
getChildId(int groupPosition, int childPosition)276     public long getChildId(int groupPosition, int childPosition) {
277         if (isPosForOngoingConversation(groupPosition)) {
278             // No cursor id exists for the "Empty" TextView item
279             if (getOngoingConversationCount() == 0) return 0;
280             return getId(getOngoingConversations(), childPosition);
281         } else if (isPosForSubscription(groupPosition)) {
282             return getId(getSubscriptions(), childPosition);
283         } else {
284             return mAdapter.getChildId(getChildAdapterPosition(groupPosition), childPosition);
285         }
286     }
287 
getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent)288     public View getChildView(int groupPosition, int childPosition, boolean isLastChild,
289             View convertView, ViewGroup parent) {
290         boolean isOngoingConversation = isPosForOngoingConversation(groupPosition);
291         boolean displayEmpty = isOngoingConversation && (getOngoingConversationCount() == 0);
292         if (isOngoingConversation || isPosForSubscription(groupPosition)) {
293             View view = null;
294             if (convertView != null) {
295                 // use the convert view if it matches the type required by displayEmpty
296                 if (displayEmpty && (convertView instanceof TextView)) {
297                     view = convertView;
298                     ((TextView) view).setText(mActivity.getText(R.string.empty_conversation_group));
299                 } else if (!displayEmpty && (convertView instanceof ContactView)) {
300                      view = convertView;
301                 }
302             }
303             if (view == null) {
304                 if (displayEmpty) {
305                     view = newEmptyView(parent);
306                 } else {
307                     view = newChildView(parent);
308                 }
309             }
310             if (!displayEmpty) {
311                 Cursor cursor = isPosForOngoingConversation(groupPosition)
312                         ? getOngoingConversations() : getSubscriptions();
313                 cursor.moveToPosition(childPosition);
314                 ((ContactView) view).bind(cursor, null, isScrolling());
315             }
316             return view;
317         } else {
318             return mAdapter.getChildView(getChildAdapterPosition(groupPosition), childPosition,
319                     isLastChild, convertView, parent);
320         }
321     }
322 
getChildrenCount(int groupPosition)323     public int getChildrenCount(int groupPosition) {
324         if (!mDataValid) {
325             return 0;
326         }
327         if (isPosForOngoingConversation(groupPosition)) {
328             // if there are no ongoing conversations, we want to display "empty" textview
329             int count = getOngoingConversationCount();
330             if (count == 0) {
331                 count = 1;
332             }
333             return count;
334         } else if (isPosForSubscription(groupPosition)) {
335             return getSubscriptionCount();
336         } else {
337             // XXX getChildrenCount() may be called with an invalid groupPosition that is larger
338             // than the total number of all groups.
339             int position = getChildAdapterPosition(groupPosition);
340             if (position >= mAdapter.getGroupCount()) {
341                 Log.w(ImApp.LOG_TAG, "getChildrenCount out of range");
342                 return 0;
343             }
344             return mAdapter.getChildrenCount(position);
345         }
346     }
347 
getGroup(int groupPosition)348     public Object getGroup(int groupPosition) {
349         if (isPosForOngoingConversation(groupPosition)
350                 || isPosForSubscription(groupPosition)) {
351             return null;
352         } else {
353             return mAdapter.getGroup(getChildAdapterPosition(groupPosition));
354         }
355     }
356 
getGroupCount()357     public int getGroupCount() {
358         if (!mDataValid) {
359             return 0;
360         }
361         int count = mAdapter.getGroupCount();
362 
363         // ongoing conversations
364         count++;
365 
366         if (getSubscriptionCount() > 0) {
367             count++;
368         }
369 
370         return count;
371     }
372 
getGroupId(int groupPosition)373     public long getGroupId(int groupPosition) {
374         if (isPosForOngoingConversation(groupPosition) || isPosForSubscription(groupPosition)) {
375             return 0;
376         } else {
377             return mAdapter.getGroupId(getChildAdapterPosition(groupPosition));
378         }
379     }
380 
getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent)381     public View getGroupView(int groupPosition, boolean isExpanded, View convertView,
382             ViewGroup parent) {
383         if (isPosForOngoingConversation(groupPosition) || isPosForSubscription(groupPosition)) {
384             View v;
385             if (convertView != null) {
386                 v = convertView;
387             } else {
388                 v = newGroupView(parent);
389             }
390 
391             TextView text1 = (TextView)v.findViewById(R.id.text1);
392             TextView text2 = (TextView)v.findViewById(R.id.text2);
393 
394             Resources r = v.getResources();
395             ImApp app = ImApp.getApplication(mActivity);
396             BrandingResources brandingRes = app.getBrandingResource(mProviderId);
397             String text = isPosForOngoingConversation(groupPosition) ?
398                     brandingRes.getString(
399                             BrandingResourceIDs.STRING_ONGOING_CONVERSATION,
400                             getOngoingConversationCount()) :
401                     r.getString(R.string.subscriptions);
402             text1.setText(text);
403             text2.setVisibility(View.GONE);
404             return v;
405         } else {
406             return mAdapter.getGroupView(getChildAdapterPosition(groupPosition), isExpanded,
407                     convertView, parent);
408         }
409     }
410 
isChildSelectable(int groupPosition, int childPosition)411     public boolean isChildSelectable(int groupPosition, int childPosition) {
412         if (isPosForOngoingConversation(groupPosition)) {
413             // "Empty" TextView is not selectable
414             if (getOngoingConversationCount()==0) return false;
415             return true;
416         }
417         if (isPosForSubscription(groupPosition)) return true;
418         return mAdapter.isChildSelectable(getChildAdapterPosition(groupPosition), childPosition);
419     }
420 
stableIds()421     public boolean stableIds() {
422         return true;
423     }
424 
425     @Override
registerDataSetObserver(DataSetObserver observer)426     public void registerDataSetObserver(DataSetObserver observer) {
427         mAdapter.registerDataSetObserver(observer);
428         super.registerDataSetObserver(observer);
429     }
430 
431     @Override
unregisterDataSetObserver(DataSetObserver observer)432     public void unregisterDataSetObserver(DataSetObserver observer) {
433         mAdapter.unregisterDataSetObserver(observer);
434         super.unregisterDataSetObserver(observer);
435     }
436 
hasStableIds()437     public boolean hasStableIds() {
438         return true;
439     }
440 
441     @Override
onGroupCollapsed(int groupPosition)442     public void onGroupCollapsed(int groupPosition) {
443         super.onGroupCollapsed(groupPosition);
444         mExpandedGroups.remove(Integer.valueOf(groupPosition));
445         int pos = getChildAdapterPosition(groupPosition);
446         if (pos >= 0) {
447             mAdapter.onGroupCollapsed(pos);
448         }
449     }
450 
451     @Override
onGroupExpanded(int groupPosition)452     public void onGroupExpanded(int groupPosition) {
453         super.onGroupExpanded(groupPosition);
454         mExpandedGroups.add(groupPosition);
455         int pos = getChildAdapterPosition(groupPosition);
456         if (pos >= 0) {
457             mAdapter.onGroupExpanded(pos);
458         }
459     }
460 
getExpandedGroups()461     public int[] getExpandedGroups() {
462         ArrayList<Integer> expandedGroups = mExpandedGroups;
463         int size = expandedGroups.size();
464         int[] res = new int[size];
465         for (int i = 0; i < size; i++) {
466             res[i] = expandedGroups.get(i);
467         }
468         return res;
469     }
470 
newChildView(ViewGroup parent)471     View newChildView(ViewGroup parent) {
472         return mInflate.inflate(R.layout.contact_view, parent, false);
473     }
474 
newEmptyView(ViewGroup parent)475     View newEmptyView(ViewGroup parent) {
476         return mInflate.inflate(R.layout.empty_conversation_group_view, parent, false);
477     }
478 
newGroupView(ViewGroup parent)479     View newGroupView(ViewGroup parent) {
480         return mInflate.inflate(R.layout.group_view, parent, false);
481     }
482 
getOngoingConversations()483     private synchronized Cursor getOngoingConversations() {
484         if (mOngoingConversations == null) {
485             startQueryOngoingConversations();
486         }
487         return mOngoingConversations;
488     }
489 
setOngoingConversations(Cursor c)490     synchronized void setOngoingConversations(Cursor c) {
491         if (mOngoingConversations != null) {
492             mOngoingConversations.unregisterContentObserver(mContentObserver);
493             mOngoingConversations.unregisterDataSetObserver(mDataSetObserver);
494             mOngoingConversations.close();
495         }
496         c.registerContentObserver(mContentObserver);
497         c.registerDataSetObserver(mDataSetObserver);
498         mOngoingConversations = c;
499     }
500 
getOngoingConversationCount()501     private int getOngoingConversationCount() {
502         Cursor c = getOngoingConversations();
503         return c == null ? 0 : c.getCount();
504     }
505 
getSubscriptions()506     private synchronized Cursor getSubscriptions() {
507         if (mSubscriptions == null) {
508             startQuerySubscriptions();
509         }
510         return mSubscriptions;
511     }
512 
setSubscriptions(Cursor c)513     synchronized void setSubscriptions(Cursor c) {
514         if (mSubscriptions != null) {
515             mSubscriptions.close();
516         }
517         // we don't need to register observers on mSubscriptions because
518         // we already have observers on mOngoingConversations and they
519         // will be notified if there is any changes of subscription
520         // since the two cursors come from the same table.
521         mSubscriptions = c;
522     }
523 
getSubscriptionCount()524     private int getSubscriptionCount() {
525         Cursor c = getSubscriptions();
526         return c == null ? 0 : c.getCount();
527     }
528 
isPosForOngoingConversation(int groupPosition)529     public boolean isPosForOngoingConversation(int groupPosition) {
530         return groupPosition == 0;
531     }
532 
isPosForSubscription(int groupPosition)533     public boolean isPosForSubscription(int groupPosition) {
534         return groupPosition == 1 && getSubscriptionCount() > 0;
535     }
536 
getChildAdapterPosition(int groupPosition)537     private int getChildAdapterPosition(int groupPosition) {
538         if (getSubscriptionCount() > 0) {
539             return groupPosition - 2;
540         } else {
541             return groupPosition - 1;
542         }
543     }
544 
moveTo(Cursor cursor, int position)545     private Cursor moveTo(Cursor cursor, int position) {
546         if (cursor.moveToPosition(position)) {
547             return cursor;
548         }
549         return null;
550     }
551 
getId(Cursor cursor, int position)552     private long getId(Cursor cursor, int position) {
553         if (cursor.moveToPosition(position)) {
554             return cursor.getLong(ContactView.COLUMN_CONTACT_ID);
555         }
556         return 0;
557     }
558 
559     class ListTreeAdapter extends CursorTreeAdapter {
560 
ListTreeAdapter(Cursor cursor)561         public ListTreeAdapter(Cursor cursor) {
562             super(cursor, mActivity);
563         }
564 
565         @Override
bindChildView(View view, Context context, Cursor cursor, boolean isLastChild)566         protected void bindChildView(View view, Context context, Cursor cursor,
567                 boolean isLastChild) {
568             // binding when child is text view for an empty group
569             if (view instanceof TextView) {
570                 ((TextView) view).setText(mActivity.getText(R.string.empty_contact_group));
571             } else {
572                 ((ContactView) view).bind(cursor, null, isScrolling());
573             }
574         }
575 
576         @Override
bindGroupView(View view, Context context, Cursor cursor, boolean isExpanded)577         protected void bindGroupView(View view, Context context, Cursor cursor,
578                 boolean isExpanded) {
579             TextView text1 = (TextView)view.findViewById(R.id.text1);
580             TextView text2 = (TextView)view.findViewById(R.id.text2);
581             Resources r = view.getResources();
582 
583             text1.setText(cursor.getString(COLUMN_CONTACT_LIST_NAME));
584             text2.setVisibility(View.VISIBLE);
585             text2.setText(r.getString(R.string.online_count, getOnlineChildCount(cursor)));
586         }
587 
newEmptyView(ViewGroup parent)588         View newEmptyView(ViewGroup parent) {
589             return mInflate.inflate(R.layout.empty_contact_group_view, parent, false);
590         }
591 
592         // if the group is empty, provide a text view. The infrastructure provides a "convertView"
593         // as a possible suggestion to reuse an existing view's data. It may be null, it may be a
594         // TextView, or it may be a ContactView, so we need to test the possible cases.
595         @Override
getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent)596         public View getChildView(int groupPosition, int childPosition, boolean isLastChild,
597                 View convertView, ViewGroup parent) {
598             // Provide a TextView if the group is empty
599             if (super.getChildrenCount(groupPosition)==0) {
600                 if (convertView != null) {
601                     if (convertView instanceof TextView) {
602                         ((TextView) convertView).setText(
603                                 mActivity.getText(R.string.empty_contact_group));
604                         return convertView;
605                     }
606                 }
607                 return newEmptyView(parent);
608             }
609             if ( !(convertView instanceof ContactView) ) {
610                 convertView = null;
611             }
612             return super.getChildView(groupPosition, childPosition, isLastChild, convertView,
613                     parent);
614         }
615 
616         @Override
getChildrenCursor(Cursor groupCursor)617         protected Cursor getChildrenCursor(Cursor groupCursor) {
618             long listId = groupCursor.getLong(COLUMN_CONTACT_LIST_ID);
619             startQueryContacts(listId);
620             return null;
621         }
622 
623         // return a TextView for empty groups
624         @Override
newChildView(Context context, Cursor cursor, boolean isLastChild, ViewGroup parent)625         protected View newChildView(Context context, Cursor cursor, boolean isLastChild,
626                 ViewGroup parent) {
627             if (cursor.getCount() == 0) {
628                 return newEmptyView(parent);
629             } else {
630                 return ContactListTreeAdapter.this.newChildView(parent);
631             }
632         }
633 
634         @Override
newGroupView(Context context, Cursor cursor, boolean isExpanded, ViewGroup parent)635         protected View newGroupView(Context context, Cursor cursor, boolean isExpanded,
636                 ViewGroup parent) {
637             return ContactListTreeAdapter.this.newGroupView(parent);
638         }
639 
getOnlineChildCount(Cursor groupCursor)640         private int getOnlineChildCount(Cursor groupCursor) {
641             long listId = groupCursor.getLong(COLUMN_CONTACT_LIST_ID);
642             if (mOnlineContactsCountMap == null) {
643                 String where = Imps.Contacts.ACCOUNT + "=" + mAccountId;
644                 ContentResolver cr = mActivity.getContentResolver();
645 
646                 Cursor c = cr.query(Imps.Contacts.CONTENT_URI_ONLINE_COUNT,
647                         CONTACT_COUNT_PROJECTION, where, null, null);
648                 mOnlineContactsCountMap = new ContentQueryMap(c,
649                         Imps.Contacts.CONTACTLIST, true, mHandler);
650                 mOnlineContactsCountMap.addObserver(new Observer(){
651                     public void update(Observable observable, Object data) {
652                         notifyDataSetChanged();
653                     }});
654             }
655             ContentValues value = mOnlineContactsCountMap.getValues(String.valueOf(listId));
656             return  value == null ? 0 : value.getAsInteger(Imps.Contacts._COUNT);
657         }
658 
659         @Override
getChildrenCount(int groupPosition)660         public int getChildrenCount(int groupPosition) {
661             int children = super.getChildrenCount(groupPosition);
662             if (children == 0) {
663                 // Count the empty group text item as a child
664                 return 1;
665             }
666             return children;
667         }
668 
669         // Don't allow the empty group text item to be selected
670         @Override
isChildSelectable(int groupPosition, int childPosition)671         public boolean isChildSelectable(int groupPosition, int childPosition) {
672             return (super.getChildrenCount(groupPosition) > 0);
673         }
674     }
675 
676     private class MyContentObserver extends ContentObserver {
677 
MyContentObserver()678         public MyContentObserver() {
679             super(mHandler);
680         }
681 
682         @Override
onChange(boolean selfChange)683         public void onChange(boolean selfChange) {
684             if (Log.isLoggable(ImApp.LOG_TAG, Log.DEBUG)){
685                 log("MyContentObserver.onChange() autoRequery=" + mAutoRequery);
686             }
687             // Don't requery when fling. We will schedule a requery when the fling is complete.
688             if (isScrolling()) {
689                 return;
690             }
691             if (mAutoRequery) {
692                 startQueryOngoingConversations();
693             } else {
694                 mRequeryPending = true;
695             }
696         }
697     }
698 
699     private class MyDataSetObserver extends DataSetObserver {
MyDataSetObserver()700         public MyDataSetObserver() {
701         }
702 
703         @Override
onChanged()704         public void onChanged() {
705             if (Log.isLoggable(ImApp.LOG_TAG, Log.DEBUG)){
706                 log("MyDataSetObserver.onChanged()");
707             }
708 
709             mDataValid = true;
710             notifyDataSetChanged();
711         }
712 
713         @Override
onInvalidated()714         public void onInvalidated() {
715             if (Log.isLoggable(ImApp.LOG_TAG, Log.DEBUG)){
716                 log("MyDataSetObserver.onInvalidated()");
717             }
718 
719             mDataValid = false;
720             notifyDataSetInvalidated();
721         }
722     }
723 
onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount)724     public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
725             int totalItemCount) {
726         // no op
727     }
728 
onScrollStateChanged(AbsListView view, int scrollState)729     public void onScrollStateChanged(AbsListView view, int scrollState) {
730         int oldState = mScrollState;
731 
732         mScrollState = scrollState;
733         //  If we just finished a fling then some items may not have an icon
734         //  So force a full redraw now that the fling is complete
735         if (oldState == OnScrollListener.SCROLL_STATE_FLING) {
736             notifyDataSetChanged();
737         }
738     }
739 
isScrolling()740     public boolean isScrolling() {
741         return mScrollState == OnScrollListener.SCROLL_STATE_FLING;
742     }
743 }
744