ContactListTreeAdapter.java
上传用户:szyujian
上传日期:2016-09-20
资源大小:320k
文件大小:26k
源码类别:

android开发

开发平台:

C/C++

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