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