Home | History | Annotate | Download | only in app
      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 
     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 {
    105         public QueryHandler(Context context) {
    106             super(context.getContentResolver());
    107         }
    108 
    109         @Override
    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 
    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 
    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 
    191     public void setHideOfflineContacts(boolean hide) {
    192         if (mHideOfflineContacts != hide) {
    193             mHideOfflineContacts = hide;
    194             mAdapter.notifyDataSetChanged();
    195         }
    196     }
    197 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    421     public boolean stableIds() {
    422         return true;
    423     }
    424 
    425     @Override
    426     public void registerDataSetObserver(DataSetObserver observer) {
    427         mAdapter.registerDataSetObserver(observer);
    428         super.registerDataSetObserver(observer);
    429     }
    430 
    431     @Override
    432     public void unregisterDataSetObserver(DataSetObserver observer) {
    433         mAdapter.unregisterDataSetObserver(observer);
    434         super.unregisterDataSetObserver(observer);
    435     }
    436 
    437     public boolean hasStableIds() {
    438         return true;
    439     }
    440 
    441     @Override
    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
    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 
    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 
    471     View newChildView(ViewGroup parent) {
    472         return mInflate.inflate(R.layout.contact_view, parent, false);
    473     }
    474 
    475     View newEmptyView(ViewGroup parent) {
    476         return mInflate.inflate(R.layout.empty_conversation_group_view, parent, false);
    477     }
    478 
    479     View newGroupView(ViewGroup parent) {
    480         return mInflate.inflate(R.layout.group_view, parent, false);
    481     }
    482 
    483     private synchronized Cursor getOngoingConversations() {
    484         if (mOngoingConversations == null) {
    485             startQueryOngoingConversations();
    486         }
    487         return mOngoingConversations;
    488     }
    489 
    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 
    501     private int getOngoingConversationCount() {
    502         Cursor c = getOngoingConversations();
    503         return c == null ? 0 : c.getCount();
    504     }
    505 
    506     private synchronized Cursor getSubscriptions() {
    507         if (mSubscriptions == null) {
    508             startQuerySubscriptions();
    509         }
    510         return mSubscriptions;
    511     }
    512 
    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 
    524     private int getSubscriptionCount() {
    525         Cursor c = getSubscriptions();
    526         return c == null ? 0 : c.getCount();
    527     }
    528 
    529     public boolean isPosForOngoingConversation(int groupPosition) {
    530         return groupPosition == 0;
    531     }
    532 
    533     public boolean isPosForSubscription(int groupPosition) {
    534         return groupPosition == 1 && getSubscriptionCount() > 0;
    535     }
    536 
    537     private int getChildAdapterPosition(int groupPosition) {
    538         if (getSubscriptionCount() > 0) {
    539             return groupPosition - 2;
    540         } else {
    541             return groupPosition - 1;
    542         }
    543     }
    544 
    545     private Cursor moveTo(Cursor cursor, int position) {
    546         if (cursor.moveToPosition(position)) {
    547             return cursor;
    548         }
    549         return null;
    550     }
    551 
    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 
    561         public ListTreeAdapter(Cursor cursor) {
    562             super(cursor, mActivity);
    563         }
    564 
    565         @Override
    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
    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 
    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
    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
    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
    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
    635         protected View newGroupView(Context context, Cursor cursor, boolean isExpanded,
    636                 ViewGroup parent) {
    637             return ContactListTreeAdapter.this.newGroupView(parent);
    638         }
    639 
    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
    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
    671         public boolean isChildSelectable(int groupPosition, int childPosition) {
    672             return (super.getChildrenCount(groupPosition) > 0);
    673         }
    674     }
    675 
    676     private class MyContentObserver extends ContentObserver {
    677 
    678         public MyContentObserver() {
    679             super(mHandler);
    680         }
    681 
    682         @Override
    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 {
    700         public MyDataSetObserver() {
    701         }
    702 
    703         @Override
    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
    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 
    724     public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
    725             int totalItemCount) {
    726         // no op
    727     }
    728 
    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 
    740     public boolean isScrolling() {
    741         return mScrollState == OnScrollListener.SCROLL_STATE_FLING;
    742     }
    743 }
    744