| /* |
| * Copyright (C) 2008 Esmertec AG. |
| * Copyright (C) 2008 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.android.im.app; |
| |
| import android.app.Activity; |
| import android.content.AsyncQueryHandler; |
| import android.content.ContentQueryMap; |
| import android.content.ContentResolver; |
| import android.content.ContentUris; |
| import android.content.ContentValues; |
| import android.content.Context; |
| import android.content.res.Resources; |
| import android.database.ContentObserver; |
| import android.database.Cursor; |
| import android.database.DataSetObserver; |
| import android.net.Uri; |
| import android.os.RemoteException; |
| import android.util.Log; |
| import android.view.LayoutInflater; |
| import android.view.View; |
| import android.view.ViewGroup; |
| import android.widget.BaseExpandableListAdapter; |
| import android.widget.CursorTreeAdapter; |
| import android.widget.TextView; |
| import android.widget.AbsListView; |
| import android.widget.AbsListView.OnScrollListener; |
| |
| import com.android.im.IImConnection; |
| import com.android.im.R; |
| import com.android.im.plugin.BrandingResourceIDs; |
| import com.android.im.provider.Imps; |
| |
| import java.util.ArrayList; |
| import java.util.Observable; |
| import java.util.Observer; |
| |
| public class ContactListTreeAdapter extends BaseExpandableListAdapter |
| implements AbsListView.OnScrollListener{ |
| |
| private static final String[] CONTACT_LIST_PROJECTION = { |
| Imps.ContactList._ID, |
| Imps.ContactList.NAME, |
| }; |
| |
| private static final int COLUMN_CONTACT_LIST_ID = 0; |
| private static final int COLUMN_CONTACT_LIST_NAME = 1; |
| |
| Activity mActivity; |
| SimpleAlertHandler mHandler; |
| private LayoutInflater mInflate; |
| private long mProviderId; |
| long mAccountId; |
| Cursor mOngoingConversations; |
| Cursor mSubscriptions; |
| boolean mDataValid; |
| ListTreeAdapter mAdapter; |
| private boolean mHideOfflineContacts; |
| |
| final MyContentObserver mContentObserver; |
| final MyDataSetObserver mDataSetObserver; |
| |
| private ArrayList<Integer> mExpandedGroups; |
| |
| private static final int TOKEN_CONTACT_LISTS = -1; |
| private static final int TOKEN_ONGOING_CONVERSATION = -2; |
| private static final int TOKEN_SUBSCRITPTION = -3; |
| |
| private static final String NON_CHAT_AND_BLOCKED_CONTACTS = "(" |
| + Imps.Contacts.LAST_MESSAGE_DATE + " IS NULL) AND (" |
| + Imps.Contacts.TYPE + "!=" + Imps.Contacts.TYPE_BLOCKED + ")"; |
| |
| private static final String CONTACTS_SELECTION = Imps.Contacts.CONTACTLIST |
| + "=? AND " + NON_CHAT_AND_BLOCKED_CONTACTS; |
| |
| private static final String ONLINE_CONTACT_SELECTION = CONTACTS_SELECTION |
| + " AND "+ Imps.Contacts.PRESENCE_STATUS + " != " + Imps.Presence.OFFLINE; |
| |
| static final void log(String msg) { |
| Log.d(ImApp.LOG_TAG, "<ContactListAdapter>" + msg); |
| } |
| |
| static final String[] CONTACT_COUNT_PROJECTION = { |
| Imps.Contacts.CONTACTLIST, |
| Imps.Contacts._COUNT, |
| }; |
| |
| ContentQueryMap mOnlineContactsCountMap; |
| |
| // Async QueryHandler |
| private final class QueryHandler extends AsyncQueryHandler { |
| public QueryHandler(Context context) { |
| super(context.getContentResolver()); |
| } |
| |
| @Override |
| protected void onQueryComplete(int token, Object cookie, Cursor c) { |
| if(Log.isLoggable(ImApp.LOG_TAG, Log.DEBUG)){ |
| log("onQueryComplete:token=" + token); |
| } |
| |
| if (token == TOKEN_CONTACT_LISTS) { |
| mDataValid = true; |
| mAdapter.setGroupCursor(c); |
| } else if (token == TOKEN_ONGOING_CONVERSATION) { |
| setOngoingConversations(c); |
| notifyDataSetChanged(); |
| } else if (token == TOKEN_SUBSCRITPTION) { |
| setSubscriptions(c); |
| notifyDataSetChanged(); |
| } else { |
| int count = mAdapter.getGroupCount(); |
| for (int pos = 0; pos < count; pos++) { |
| long listId = mAdapter.getGroupId(pos); |
| if (listId == token) { |
| mAdapter.setChildrenCursor(pos, c); |
| break; |
| } |
| } |
| } |
| } |
| } |
| private QueryHandler mQueryHandler; |
| |
| private int mScrollState; |
| |
| private boolean mAutoRequery; |
| private boolean mRequeryPending; |
| |
| public ContactListTreeAdapter(IImConnection conn, Activity activity) { |
| mActivity = activity; |
| mInflate = activity.getLayoutInflater(); |
| mHandler = new SimpleAlertHandler(activity); |
| |
| mAdapter = new ListTreeAdapter(null); |
| |
| mContentObserver = new MyContentObserver(); |
| mDataSetObserver = new MyDataSetObserver(); |
| mExpandedGroups = new ArrayList<Integer>(); |
| mQueryHandler = new QueryHandler(activity); |
| |
| changeConnection(conn); |
| } |
| |
| public void changeConnection(IImConnection conn) { |
| mQueryHandler.cancelOperation(TOKEN_ONGOING_CONVERSATION); |
| mQueryHandler.cancelOperation(TOKEN_SUBSCRITPTION); |
| mQueryHandler.cancelOperation(TOKEN_CONTACT_LISTS); |
| |
| synchronized (this) { |
| if (mOngoingConversations != null) { |
| mOngoingConversations.close(); |
| mOngoingConversations = null; |
| } |
| if (mSubscriptions != null) { |
| mSubscriptions.close(); |
| mSubscriptions = null; |
| } |
| if (mOnlineContactsCountMap != null) { |
| mOnlineContactsCountMap.close(); |
| } |
| } |
| |
| mAdapter.notifyDataSetChanged(); |
| if (conn != null) { |
| try { |
| mProviderId = conn.getProviderId(); |
| mAccountId = conn.getAccountId(); |
| startQueryOngoingConversations(); |
| startQueryContactLists(); |
| startQuerySubscriptions(); |
| } catch (RemoteException e) { |
| // Service died! |
| } |
| } |
| } |
| |
| public void setHideOfflineContacts(boolean hide) { |
| if (mHideOfflineContacts != hide) { |
| mHideOfflineContacts = hide; |
| mAdapter.notifyDataSetChanged(); |
| } |
| } |
| |
| public void startAutoRequery() { |
| if (Log.isLoggable(ImApp.LOG_TAG, Log.DEBUG)){ |
| log("startAutoRequery()"); |
| } |
| mAutoRequery = true; |
| if (mRequeryPending) { |
| mRequeryPending = false; |
| startQueryOngoingConversations(); |
| } |
| } |
| |
| private void startQueryContactLists() { |
| if (Log.isLoggable(ImApp.LOG_TAG, Log.DEBUG)){ |
| log("startQueryContactLists()"); |
| } |
| |
| Uri uri = Imps.ContactList.CONTENT_URI; |
| uri = ContentUris.withAppendedId(uri, mProviderId); |
| uri = ContentUris.withAppendedId(uri, mAccountId); |
| |
| mQueryHandler.startQuery(TOKEN_CONTACT_LISTS, null, uri, CONTACT_LIST_PROJECTION, |
| null, null, Imps.ContactList.DEFAULT_SORT_ORDER); |
| } |
| |
| void startQueryOngoingConversations() { |
| if (Log.isLoggable(ImApp.LOG_TAG, Log.DEBUG)){ |
| log("startQueryOngoingConversations()"); |
| } |
| |
| Uri uri = Imps.Contacts.CONTENT_URI_CHAT_CONTACTS_BY; |
| uri = ContentUris.withAppendedId(uri, mProviderId); |
| uri = ContentUris.withAppendedId(uri, mAccountId); |
| |
| mQueryHandler.startQuery(TOKEN_ONGOING_CONVERSATION, null, uri, |
| ContactView.CONTACT_PROJECTION, null, null, Imps.Contacts.DEFAULT_SORT_ORDER); |
| } |
| |
| void startQuerySubscriptions() { |
| if (Log.isLoggable(ImApp.LOG_TAG, Log.DEBUG)){ |
| log("startQuerySubscriptions()"); |
| } |
| |
| Uri uri = Imps.Contacts.CONTENT_URI_CONTACTS_BY; |
| uri = ContentUris.withAppendedId(uri, mProviderId); |
| uri = ContentUris.withAppendedId(uri, mAccountId); |
| |
| mQueryHandler.startQuery(TOKEN_SUBSCRITPTION, null, uri, |
| ContactView.CONTACT_PROJECTION, |
| String.format("%s=%d AND %s=%d", |
| Imps.Contacts.SUBSCRIPTION_STATUS, Imps.Contacts.SUBSCRIPTION_STATUS_SUBSCRIBE_PENDING, |
| Imps.Contacts.SUBSCRIPTION_TYPE, Imps.Contacts.SUBSCRIPTION_TYPE_FROM), |
| null,Imps.Contacts.DEFAULT_SORT_ORDER); |
| } |
| |
| void startQueryContacts(long listId) { |
| if (Log.isLoggable(ImApp.LOG_TAG, Log.DEBUG)){ |
| log("startQueryContacts - listId=" + listId); |
| } |
| |
| String selection = mHideOfflineContacts ? ONLINE_CONTACT_SELECTION : CONTACTS_SELECTION; |
| String[] args = { Long.toString(listId) }; |
| int token = (int)listId; |
| mQueryHandler.startQuery(token, null, Imps.Contacts.CONTENT_URI, |
| ContactView.CONTACT_PROJECTION, selection, args, Imps.Contacts.DEFAULT_SORT_ORDER); |
| } |
| |
| public Object getChild(int groupPosition, int childPosition) { |
| if (isPosForOngoingConversation(groupPosition)) { |
| // No cursor exists for the "Empty" TextView item |
| if (getOngoingConversationCount() == 0) return null; |
| return moveTo(getOngoingConversations(), childPosition); |
| } else if (isPosForSubscription(groupPosition)) { |
| return moveTo(getSubscriptions(), childPosition); |
| } else { |
| return mAdapter.getChild(getChildAdapterPosition(groupPosition), childPosition); |
| } |
| } |
| |
| public long getChildId(int groupPosition, int childPosition) { |
| if (isPosForOngoingConversation(groupPosition)) { |
| // No cursor id exists for the "Empty" TextView item |
| if (getOngoingConversationCount() == 0) return 0; |
| return getId(getOngoingConversations(), childPosition); |
| } else if (isPosForSubscription(groupPosition)) { |
| return getId(getSubscriptions(), childPosition); |
| } else { |
| return mAdapter.getChildId(getChildAdapterPosition(groupPosition), childPosition); |
| } |
| } |
| |
| public View getChildView(int groupPosition, int childPosition, boolean isLastChild, |
| View convertView, ViewGroup parent) { |
| boolean isOngoingConversation = isPosForOngoingConversation(groupPosition); |
| boolean displayEmpty = isOngoingConversation && (getOngoingConversationCount() == 0); |
| if (isOngoingConversation || isPosForSubscription(groupPosition)) { |
| View view = null; |
| if (convertView != null) { |
| // use the convert view if it matches the type required by displayEmpty |
| if (displayEmpty && (convertView instanceof TextView)) { |
| view = convertView; |
| ((TextView) view).setText(mActivity.getText(R.string.empty_conversation_group)); |
| } else if (!displayEmpty && (convertView instanceof ContactView)) { |
| view = convertView; |
| } |
| } |
| if (view == null) { |
| if (displayEmpty) { |
| view = newEmptyView(parent); |
| } else { |
| view = newChildView(parent); |
| } |
| } |
| if (!displayEmpty) { |
| Cursor cursor = isPosForOngoingConversation(groupPosition) |
| ? getOngoingConversations() : getSubscriptions(); |
| cursor.moveToPosition(childPosition); |
| ((ContactView) view).bind(cursor, null, isScrolling()); |
| } |
| return view; |
| } else { |
| return mAdapter.getChildView(getChildAdapterPosition(groupPosition), childPosition, |
| isLastChild, convertView, parent); |
| } |
| } |
| |
| public int getChildrenCount(int groupPosition) { |
| if (!mDataValid) { |
| return 0; |
| } |
| if (isPosForOngoingConversation(groupPosition)) { |
| // if there are no ongoing conversations, we want to display "empty" textview |
| int count = getOngoingConversationCount(); |
| if (count == 0) { |
| count = 1; |
| } |
| return count; |
| } else if (isPosForSubscription(groupPosition)) { |
| return getSubscriptionCount(); |
| } else { |
| // XXX getChildrenCount() may be called with an invalid groupPosition that is larger |
| // than the total number of all groups. |
| int position = getChildAdapterPosition(groupPosition); |
| if (position >= mAdapter.getGroupCount()) { |
| Log.w(ImApp.LOG_TAG, "getChildrenCount out of range"); |
| return 0; |
| } |
| return mAdapter.getChildrenCount(position); |
| } |
| } |
| |
| public Object getGroup(int groupPosition) { |
| if (isPosForOngoingConversation(groupPosition) |
| || isPosForSubscription(groupPosition)) { |
| return null; |
| } else { |
| return mAdapter.getGroup(getChildAdapterPosition(groupPosition)); |
| } |
| } |
| |
| public int getGroupCount() { |
| if (!mDataValid) { |
| return 0; |
| } |
| int count = mAdapter.getGroupCount(); |
| |
| // ongoing conversations |
| count++; |
| |
| if (getSubscriptionCount() > 0) { |
| count++; |
| } |
| |
| return count; |
| } |
| |
| public long getGroupId(int groupPosition) { |
| if (isPosForOngoingConversation(groupPosition) || isPosForSubscription(groupPosition)) { |
| return 0; |
| } else { |
| return mAdapter.getGroupId(getChildAdapterPosition(groupPosition)); |
| } |
| } |
| |
| public View getGroupView(int groupPosition, boolean isExpanded, View convertView, |
| ViewGroup parent) { |
| if (isPosForOngoingConversation(groupPosition) || isPosForSubscription(groupPosition)) { |
| View v; |
| if (convertView != null) { |
| v = convertView; |
| } else { |
| v = newGroupView(parent); |
| } |
| |
| TextView text1 = (TextView)v.findViewById(R.id.text1); |
| TextView text2 = (TextView)v.findViewById(R.id.text2); |
| |
| Resources r = v.getResources(); |
| ImApp app = ImApp.getApplication(mActivity); |
| BrandingResources brandingRes = app.getBrandingResource(mProviderId); |
| String text = isPosForOngoingConversation(groupPosition) ? |
| brandingRes.getString( |
| BrandingResourceIDs.STRING_ONGOING_CONVERSATION, |
| getOngoingConversationCount()) : |
| r.getString(R.string.subscriptions); |
| text1.setText(text); |
| text2.setVisibility(View.GONE); |
| return v; |
| } else { |
| return mAdapter.getGroupView(getChildAdapterPosition(groupPosition), isExpanded, |
| convertView, parent); |
| } |
| } |
| |
| public boolean isChildSelectable(int groupPosition, int childPosition) { |
| if (isPosForOngoingConversation(groupPosition)) { |
| // "Empty" TextView is not selectable |
| if (getOngoingConversationCount()==0) return false; |
| return true; |
| } |
| if (isPosForSubscription(groupPosition)) return true; |
| return mAdapter.isChildSelectable(getChildAdapterPosition(groupPosition), childPosition); |
| } |
| |
| public boolean stableIds() { |
| return true; |
| } |
| |
| @Override |
| public void registerDataSetObserver(DataSetObserver observer) { |
| mAdapter.registerDataSetObserver(observer); |
| super.registerDataSetObserver(observer); |
| } |
| |
| @Override |
| public void unregisterDataSetObserver(DataSetObserver observer) { |
| mAdapter.unregisterDataSetObserver(observer); |
| super.unregisterDataSetObserver(observer); |
| } |
| |
| public boolean hasStableIds() { |
| return true; |
| } |
| |
| @Override |
| public void onGroupCollapsed(int groupPosition) { |
| super.onGroupCollapsed(groupPosition); |
| mExpandedGroups.remove(Integer.valueOf(groupPosition)); |
| int pos = getChildAdapterPosition(groupPosition); |
| if (pos >= 0) { |
| mAdapter.onGroupCollapsed(pos); |
| } |
| } |
| |
| @Override |
| public void onGroupExpanded(int groupPosition) { |
| super.onGroupExpanded(groupPosition); |
| mExpandedGroups.add(groupPosition); |
| int pos = getChildAdapterPosition(groupPosition); |
| if (pos >= 0) { |
| mAdapter.onGroupExpanded(pos); |
| } |
| } |
| |
| public int[] getExpandedGroups() { |
| ArrayList<Integer> expandedGroups = mExpandedGroups; |
| int size = expandedGroups.size(); |
| int[] res = new int[size]; |
| for (int i = 0; i < size; i++) { |
| res[i] = expandedGroups.get(i); |
| } |
| return res; |
| } |
| |
| View newChildView(ViewGroup parent) { |
| return mInflate.inflate(R.layout.contact_view, parent, false); |
| } |
| |
| View newEmptyView(ViewGroup parent) { |
| return mInflate.inflate(R.layout.empty_conversation_group_view, parent, false); |
| } |
| |
| View newGroupView(ViewGroup parent) { |
| return mInflate.inflate(R.layout.group_view, parent, false); |
| } |
| |
| private synchronized Cursor getOngoingConversations() { |
| if (mOngoingConversations == null) { |
| startQueryOngoingConversations(); |
| } |
| return mOngoingConversations; |
| } |
| |
| synchronized void setOngoingConversations(Cursor c) { |
| if (mOngoingConversations != null) { |
| mOngoingConversations.unregisterContentObserver(mContentObserver); |
| mOngoingConversations.unregisterDataSetObserver(mDataSetObserver); |
| mOngoingConversations.close(); |
| } |
| c.registerContentObserver(mContentObserver); |
| c.registerDataSetObserver(mDataSetObserver); |
| mOngoingConversations = c; |
| } |
| |
| private int getOngoingConversationCount() { |
| Cursor c = getOngoingConversations(); |
| return c == null ? 0 : c.getCount(); |
| } |
| |
| private synchronized Cursor getSubscriptions() { |
| if (mSubscriptions == null) { |
| startQuerySubscriptions(); |
| } |
| return mSubscriptions; |
| } |
| |
| synchronized void setSubscriptions(Cursor c) { |
| if (mSubscriptions != null) { |
| mSubscriptions.close(); |
| } |
| // we don't need to register observers on mSubscriptions because |
| // we already have observers on mOngoingConversations and they |
| // will be notified if there is any changes of subscription |
| // since the two cursors come from the same table. |
| mSubscriptions = c; |
| } |
| |
| private int getSubscriptionCount() { |
| Cursor c = getSubscriptions(); |
| return c == null ? 0 : c.getCount(); |
| } |
| |
| public boolean isPosForOngoingConversation(int groupPosition) { |
| return groupPosition == 0; |
| } |
| |
| public boolean isPosForSubscription(int groupPosition) { |
| return groupPosition == 1 && getSubscriptionCount() > 0; |
| } |
| |
| private int getChildAdapterPosition(int groupPosition) { |
| if (getSubscriptionCount() > 0) { |
| return groupPosition - 2; |
| } else { |
| return groupPosition - 1; |
| } |
| } |
| |
| private Cursor moveTo(Cursor cursor, int position) { |
| if (cursor.moveToPosition(position)) { |
| return cursor; |
| } |
| return null; |
| } |
| |
| private long getId(Cursor cursor, int position) { |
| if (cursor.moveToPosition(position)) { |
| return cursor.getLong(ContactView.COLUMN_CONTACT_ID); |
| } |
| return 0; |
| } |
| |
| class ListTreeAdapter extends CursorTreeAdapter { |
| |
| public ListTreeAdapter(Cursor cursor) { |
| super(cursor, mActivity); |
| } |
| |
| @Override |
| protected void bindChildView(View view, Context context, Cursor cursor, |
| boolean isLastChild) { |
| // binding when child is text view for an empty group |
| if (view instanceof TextView) { |
| ((TextView) view).setText(mActivity.getText(R.string.empty_contact_group)); |
| } else { |
| ((ContactView) view).bind(cursor, null, isScrolling()); |
| } |
| } |
| |
| @Override |
| protected void bindGroupView(View view, Context context, Cursor cursor, |
| boolean isExpanded) { |
| TextView text1 = (TextView)view.findViewById(R.id.text1); |
| TextView text2 = (TextView)view.findViewById(R.id.text2); |
| Resources r = view.getResources(); |
| |
| text1.setText(cursor.getString(COLUMN_CONTACT_LIST_NAME)); |
| text2.setVisibility(View.VISIBLE); |
| text2.setText(r.getString(R.string.online_count, getOnlineChildCount(cursor))); |
| } |
| |
| View newEmptyView(ViewGroup parent) { |
| return mInflate.inflate(R.layout.empty_contact_group_view, parent, false); |
| } |
| |
| // if the group is empty, provide a text view. The infrastructure provides a "convertView" |
| // as a possible suggestion to reuse an existing view's data. It may be null, it may be a |
| // TextView, or it may be a ContactView, so we need to test the possible cases. |
| @Override |
| public View getChildView(int groupPosition, int childPosition, boolean isLastChild, |
| View convertView, ViewGroup parent) { |
| // Provide a TextView if the group is empty |
| if (super.getChildrenCount(groupPosition)==0) { |
| if (convertView != null) { |
| if (convertView instanceof TextView) { |
| ((TextView) convertView).setText( |
| mActivity.getText(R.string.empty_contact_group)); |
| return convertView; |
| } |
| } |
| return newEmptyView(parent); |
| } |
| if ( !(convertView instanceof ContactView) ) { |
| convertView = null; |
| } |
| return super.getChildView(groupPosition, childPosition, isLastChild, convertView, |
| parent); |
| } |
| |
| @Override |
| protected Cursor getChildrenCursor(Cursor groupCursor) { |
| long listId = groupCursor.getLong(COLUMN_CONTACT_LIST_ID); |
| startQueryContacts(listId); |
| return null; |
| } |
| |
| // return a TextView for empty groups |
| @Override |
| protected View newChildView(Context context, Cursor cursor, boolean isLastChild, |
| ViewGroup parent) { |
| if (cursor.getCount() == 0) { |
| return newEmptyView(parent); |
| } else { |
| return ContactListTreeAdapter.this.newChildView(parent); |
| } |
| } |
| |
| @Override |
| protected View newGroupView(Context context, Cursor cursor, boolean isExpanded, |
| ViewGroup parent) { |
| return ContactListTreeAdapter.this.newGroupView(parent); |
| } |
| |
| private int getOnlineChildCount(Cursor groupCursor) { |
| long listId = groupCursor.getLong(COLUMN_CONTACT_LIST_ID); |
| if (mOnlineContactsCountMap == null) { |
| String where = Imps.Contacts.ACCOUNT + "=" + mAccountId; |
| ContentResolver cr = mActivity.getContentResolver(); |
| |
| Cursor c = cr.query(Imps.Contacts.CONTENT_URI_ONLINE_COUNT, |
| CONTACT_COUNT_PROJECTION, where, null, null); |
| mOnlineContactsCountMap = new ContentQueryMap(c, |
| Imps.Contacts.CONTACTLIST, true, mHandler); |
| mOnlineContactsCountMap.addObserver(new Observer(){ |
| public void update(Observable observable, Object data) { |
| notifyDataSetChanged(); |
| }}); |
| } |
| ContentValues value = mOnlineContactsCountMap.getValues(String.valueOf(listId)); |
| return value == null ? 0 : value.getAsInteger(Imps.Contacts._COUNT); |
| } |
| |
| @Override |
| public int getChildrenCount(int groupPosition) { |
| int children = super.getChildrenCount(groupPosition); |
| if (children == 0) { |
| // Count the empty group text item as a child |
| return 1; |
| } |
| return children; |
| } |
| |
| // Don't allow the empty group text item to be selected |
| @Override |
| public boolean isChildSelectable(int groupPosition, int childPosition) { |
| return (super.getChildrenCount(groupPosition) > 0); |
| } |
| } |
| |
| private class MyContentObserver extends ContentObserver { |
| |
| public MyContentObserver() { |
| super(mHandler); |
| } |
| |
| @Override |
| public void onChange(boolean selfChange) { |
| if (Log.isLoggable(ImApp.LOG_TAG, Log.DEBUG)){ |
| log("MyContentObserver.onChange() autoRequery=" + mAutoRequery); |
| } |
| // Don't requery when fling. We will schedule a requery when the fling is complete. |
| if (isScrolling()) { |
| return; |
| } |
| if (mAutoRequery) { |
| startQueryOngoingConversations(); |
| } else { |
| mRequeryPending = true; |
| } |
| } |
| } |
| |
| private class MyDataSetObserver extends DataSetObserver { |
| public MyDataSetObserver() { |
| } |
| |
| @Override |
| public void onChanged() { |
| if (Log.isLoggable(ImApp.LOG_TAG, Log.DEBUG)){ |
| log("MyDataSetObserver.onChanged()"); |
| } |
| |
| mDataValid = true; |
| notifyDataSetChanged(); |
| } |
| |
| @Override |
| public void onInvalidated() { |
| if (Log.isLoggable(ImApp.LOG_TAG, Log.DEBUG)){ |
| log("MyDataSetObserver.onInvalidated()"); |
| } |
| |
| mDataValid = false; |
| notifyDataSetInvalidated(); |
| } |
| } |
| |
| public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, |
| int totalItemCount) { |
| // no op |
| } |
| |
| public void onScrollStateChanged(AbsListView view, int scrollState) { |
| int oldState = mScrollState; |
| |
| mScrollState = scrollState; |
| // If we just finished a fling then some items may not have an icon |
| // So force a full redraw now that the fling is complete |
| if (oldState == OnScrollListener.SCROLL_STATE_FLING) { |
| notifyDataSetChanged(); |
| } |
| } |
| |
| public boolean isScrolling() { |
| return mScrollState == OnScrollListener.SCROLL_STATE_FLING; |
| } |
| } |