2 * Copyright (C) 2007 The Android Open Source Project
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package com.android.contacts;
19 import android.app.Activity;
20 import android.app.AlertDialog;
21 import android.app.ListActivity;
22 import android.app.SearchManager;
23 import android.content.AsyncQueryHandler;
24 import android.content.ContentResolver;
25 import android.content.ContentUris;
26 import android.content.ContentValues;
27 import android.content.Context;
28 import android.content.DialogInterface;
29 import android.content.IContentProvider;
30 import android.content.ISyncAdapter;
31 import android.content.Intent;
32 import android.content.SharedPreferences;
33 import android.database.CharArrayBuffer;
34 import android.database.Cursor;
35 import android.graphics.Bitmap;
36 import android.net.Uri;
37 import android.os.Bundle;
38 import android.os.Parcelable;
39 import android.os.RemoteException;
40 import android.preference.PreferenceManager;
41 import android.provider.Contacts;
42 import android.provider.Contacts.ContactMethods;
43 import android.provider.Contacts.Groups;
44 import android.provider.Contacts.Intents;
45 import android.provider.Contacts.People;
46 import android.provider.Contacts.Phones;
47 import android.provider.Contacts.Presence;
48 import android.provider.Contacts.Intents.UI;
49 import android.text.TextUtils;
50 import android.util.Log;
51 import android.view.ContextMenu;
52 import android.view.Gravity;
53 import android.view.KeyEvent;
54 import android.view.LayoutInflater;
55 import android.view.Menu;
56 import android.view.MenuItem;
57 import android.view.View;
58 import android.view.ViewGroup;
59 import android.view.ContextMenu.ContextMenuInfo;
60 import android.widget.AdapterView;
61 import android.widget.AlphabetIndexer;
62 import android.widget.ImageView;
63 import android.widget.ListView;
64 import android.widget.ResourceCursorAdapter;
65 import android.widget.SectionIndexer;
66 import android.widget.TextView;
68 import java.lang.ref.WeakReference;
69 import java.util.ArrayList;
72 * Displays a list of contacts. Usually is embedded into the ContactsActivity.
74 public final class ContactsListActivity extends ListActivity
75 implements View.OnCreateContextMenuListener, DialogInterface.OnClickListener {
76 private static final String TAG = "ContactsListActivity";
78 private static final String LIST_STATE_KEY = "liststate";
79 private static final String FOCUS_KEY = "focused";
81 static final int MENU_ITEM_VIEW_CONTACT = 1;
82 static final int MENU_ITEM_CALL = 2;
83 static final int MENU_ITEM_EDIT_BEFORE_CALL = 3;
84 static final int MENU_ITEM_SEND_SMS = 4;
85 static final int MENU_ITEM_SEND_IM = 5;
86 static final int MENU_ITEM_EDIT = 6;
87 static final int MENU_ITEM_DELETE = 7;
88 static final int MENU_ITEM_TOGGLE_STAR = 8;
90 public static final int MENU_DIALER = 9;
91 public static final int MENU_NEW_CONTACT = 10;
92 public static final int MENU_DISPLAY_GROUP = 11;
94 private static final int SUBACTIVITY_NEW_CONTACT = 1;
96 /** Mask for picker mode */
97 static final int MODE_MASK_PICKER = 0x80000000;
98 /** Mask for no presence mode */
99 static final int MODE_MASK_NO_PRESENCE = 0x40000000;
100 /** Mask for enabling list filtering */
101 static final int MODE_MASK_NO_FILTER = 0x20000000;
102 /** Mask for having a "create new contact" header in the list */
103 static final int MODE_MASK_CREATE_NEW = 0x10000000;
106 static final int MODE_UNKNOWN = 0;
107 /** Show members of the "Contacts" group */
108 static final int MODE_GROUP = 5;
109 /** Show all contacts sorted alphabetically */
110 static final int MODE_ALL_CONTACTS = 10;
111 /** Show all contacts with phone numbers, sorted alphabetically */
112 static final int MODE_WITH_PHONES = 15;
113 /** Show all starred contacts */
114 static final int MODE_STARRED = 20;
115 /** Show frequently contacted contacts */
116 static final int MODE_FREQUENT = 30;
117 /** Show starred and the frequent */
118 static final int MODE_STREQUENT = 35;
119 /** Show all contacts and pick them when clicking */
120 static final int MODE_PICK_CONTACT = 40 | MODE_MASK_PICKER;
121 /** Show all contacts as well as the option to create a new one */
122 static final int MODE_PICK_OR_CREATE_CONTACT = 42 | MODE_MASK_PICKER | MODE_MASK_CREATE_NEW;
123 /** Show all contacts and pick them when clicking, and allow creating a new contact */
124 static final int MODE_INSERT_OR_EDIT_CONTACT = 45 | MODE_MASK_PICKER | MODE_MASK_CREATE_NEW;
125 /** Show all phone numbers and pick them when clicking */
126 static final int MODE_PICK_PHONE = 50 | MODE_MASK_PICKER | MODE_MASK_NO_PRESENCE;
127 /** Show all postal addresses and pick them when clicking */
128 static final int MODE_PICK_POSTAL =
129 55 | MODE_MASK_PICKER | MODE_MASK_NO_PRESENCE | MODE_MASK_NO_FILTER;
130 /** Run a search query */
131 static final int MODE_QUERY = 60 | MODE_MASK_NO_FILTER;
133 static final int DEFAULT_MODE = MODE_ALL_CONTACTS;
136 * The type of data to display in the main contacts list.
138 static final String PREF_DISPLAY_TYPE = "display_system_group";
140 /** Unknown display type. */
141 static final int DISPLAY_TYPE_UNKNOWN = -1;
142 /** Display all contacts */
143 static final int DISPLAY_TYPE_ALL = 0;
144 /** Display all contacts that have phone numbers */
145 static final int DISPLAY_TYPE_ALL_WITH_PHONES = 1;
146 /** Display a system group */
147 static final int DISPLAY_TYPE_SYSTEM_GROUP = 2;
148 /** Display a user group */
149 static final int DISPLAY_TYPE_USER_GROUP = 3;
152 * Info about what to display. If {@link #PREF_DISPLAY_TYPE}
153 * is {@link #DISPLAY_TYPE_SYSTEM_GROUP} then this will be the system id.
154 * If {@link #PREF_DISPLAY_TYPE} is {@link #DISPLAY_TYPE_USER_GROUP} then this will
157 static final String PREF_DISPLAY_INFO = "display_group";
160 static final String NAME_COLUMN = People.DISPLAY_NAME;
162 static final String[] CONTACTS_PROJECTION = new String[] {
169 People.PRIMARY_PHONE_ID, // 6
170 People.PRIMARY_EMAIL_ID, // 7
171 People.PRESENCE_STATUS, // 8
174 static final String[] STREQUENT_PROJECTION = new String[] {
181 People.PRIMARY_PHONE_ID, // 6
182 People.PRIMARY_EMAIL_ID, // 7
183 People.PRESENCE_STATUS, // 8
184 People.TIMES_CONTACTED, // 9 (not displayed, but required for the order by to work)
187 static final String[] PHONES_PROJECTION = new String[] {
196 static final String[] CONTACT_METHODS_PROJECTION = new String[] {
197 ContactMethods._ID, // 0
199 ContactMethods.DATA, // 2
200 ContactMethods.TYPE, // 3
201 ContactMethods.LABEL, // 4
202 ContactMethods.STARRED, // 5
205 static final int ID_COLUMN_INDEX = 0;
206 static final int NAME_COLUMN_INDEX = 1;
207 static final int NUMBER_COLUMN_INDEX = 2;
208 static final int DATA_COLUMN_INDEX = 2;
209 static final int TYPE_COLUMN_INDEX = 3;
210 static final int LABEL_COLUMN_INDEX = 4;
211 static final int STARRED_COLUMN_INDEX = 5;
212 static final int PRIMARY_PHONE_ID_COLUMN_INDEX = 6;
213 static final int PRIMARY_EMAIL_ID_COLUMN_INDEX = 7;
214 static final int SERVER_STATUS_COLUMN_INDEX = 8;
217 static final int DISPLAY_GROUP_INDEX_ALL_CONTACTS = 0;
218 static final int DISPLAY_GROUP_INDEX_ALL_CONTACTS_WITH_PHONES = 1;
219 static final int DISPLAY_GROUP_INDEX_MY_CONTACTS = 2;
221 static final String SORT_ORDER = NAME_COLUMN + " COLLATE LOCALIZED ASC";
223 private static final int QUERY_TOKEN = 42;
225 private static final String[] GROUPS_PROJECTION = new String[] {
226 Groups.SYSTEM_ID, // 0
229 private static final int GROUPS_COLUMN_INDEX_SYSTEM_ID = 0;
230 private static final int GROUPS_COLUMN_INDEX_NAME = 1;
232 static final String GROUP_WITH_PHONES = "android_smartgroup_phone";
234 ContactItemListAdapter mAdapter;
236 int mMode = DEFAULT_MODE;
237 // The current display group
238 private String mDisplayInfo;
239 private int mDisplayType;
240 // The current list of display groups, during selection from menu
241 private CharSequence[] mDisplayGroups;
242 // If true position 2 in mDisplayGroups is the MyContacts group
243 private boolean mDisplayGroupsIncludesMyContacts = false;
245 private int mDisplayGroupOriginalSelection;
246 private int mDisplayGroupCurrentSelection;
248 private QueryHandler mQueryHandler;
249 private String mQuery;
250 private Uri mGroupFilterUri;
251 private Uri mGroupUri;
252 private boolean mJustCreated;
253 private boolean mSyncEnabled;
256 * Used to keep track of the scroll state of the list.
258 private Parcelable mListState = null;
259 private boolean mListHasFocus;
261 private boolean mCreateShortcut;
262 private boolean mDefaultMode = false;
264 private class DeleteClickListener implements DialogInterface.OnClickListener {
267 public DeleteClickListener(Uri uri) {
271 public void onClick(DialogInterface dialog, int which) {
272 getContentResolver().delete(mUri, null, null);
277 protected void onCreate(Bundle icicle) {
278 super.onCreate(icicle);
280 // Resolve the intent
281 final Intent intent = getIntent();
283 // Allow the title to be set to a custom String using an extra on the intent
284 String title = intent.getStringExtra(Contacts.Intents.UI.TITLE_EXTRA_KEY);
289 final String action = intent.getAction();
290 mMode = MODE_UNKNOWN;
292 setContentView(R.layout.contacts_list_content);
294 if (UI.LIST_DEFAULT.equals(action)) {
296 // When mDefaultMode is true the mode is set in onResume(), since the preferneces
297 // activity may change it whenever this activity isn't running
298 } else if (UI.LIST_GROUP_ACTION.equals(action)) {
300 String groupName = intent.getStringExtra(UI.GROUP_NAME_EXTRA_KEY);
301 if (TextUtils.isEmpty(groupName)) {
305 buildUserGroupUris(groupName);
306 } else if (UI.LIST_ALL_CONTACTS_ACTION.equals(action)) {
307 mMode = MODE_ALL_CONTACTS;
308 } else if (UI.LIST_STARRED_ACTION.equals(action)) {
309 mMode = MODE_STARRED;
310 } else if (UI.LIST_FREQUENT_ACTION.equals(action)) {
311 mMode = MODE_FREQUENT;
312 } else if (UI.LIST_STREQUENT_ACTION.equals(action)) {
313 mMode = MODE_STREQUENT;
314 } else if (UI.LIST_CONTACTS_WITH_PHONES_ACTION.equals(action)) {
315 mMode = MODE_WITH_PHONES;
316 } else if (Intent.ACTION_PICK.equals(action)) {
317 // XXX These should be showing the data from the URI given in
319 final String type = intent.resolveType(this);
320 if (People.CONTENT_TYPE.equals(type)) {
321 mMode = MODE_PICK_CONTACT;
322 } else if (Phones.CONTENT_TYPE.equals(type)) {
323 mMode = MODE_PICK_PHONE;
324 } else if (ContactMethods.CONTENT_POSTAL_TYPE.equals(type)) {
325 mMode = MODE_PICK_POSTAL;
327 } else if (Intent.ACTION_CREATE_SHORTCUT.equals(action)) {
328 mMode = MODE_PICK_OR_CREATE_CONTACT;
329 mCreateShortcut = true;
330 } else if (Intent.ACTION_GET_CONTENT.equals(action)) {
331 final String type = intent.resolveType(this);
332 if (People.CONTENT_ITEM_TYPE.equals(type)) {
333 mMode = MODE_PICK_OR_CREATE_CONTACT;
334 } else if (Phones.CONTENT_ITEM_TYPE.equals(type)) {
335 mMode = MODE_PICK_PHONE;
336 } else if (ContactMethods.CONTENT_POSTAL_ITEM_TYPE.equals(type)) {
337 mMode = MODE_PICK_POSTAL;
339 } else if (Intent.ACTION_INSERT_OR_EDIT.equals(action)) {
340 mMode = MODE_INSERT_OR_EDIT_CONTACT;
341 } else if (Intent.ACTION_SEARCH.equals(action)) {
342 // See if the suggestion was clicked with a search action key (call button)
343 if ("call".equals(intent.getStringExtra(SearchManager.ACTION_MSG))) {
344 String query = intent.getStringExtra(SearchManager.QUERY);
345 if (!TextUtils.isEmpty(query)) {
346 Intent newIntent = new Intent(Intent.ACTION_CALL_PRIVILEGED,
347 Uri.fromParts("tel", query, null));
348 startActivity(newIntent);
353 // Otherwise handle the more normal search case
356 // Since this is the filter activity it receives all intents
357 // dispatched from the SearchManager for security reasons
358 // so we need to re-dispatch from here to the intended target.
359 } else if (Intents.SEARCH_SUGGESTION_CLICKED.equals(action)) {
360 // See if the suggestion was clicked with a search action key (call button)
362 if ("call".equals(intent.getStringExtra(SearchManager.ACTION_MSG))) {
363 newIntent = new Intent(Intent.ACTION_CALL_PRIVILEGED, intent.getData());
365 newIntent = new Intent(Intent.ACTION_VIEW, intent.getData());
367 startActivity(newIntent);
370 } else if (Intents.SEARCH_SUGGESTION_DIAL_NUMBER_CLICKED.equals(action)) {
371 Intent newIntent = new Intent(Intent.ACTION_CALL_PRIVILEGED, intent.getData());
372 startActivity(newIntent);
375 } else if (Intents.SEARCH_SUGGESTION_CREATE_CONTACT_CLICKED.equals(action)) {
376 String number = intent.getData().getSchemeSpecificPart();
377 Intent newIntent = new Intent(Intent.ACTION_INSERT, People.CONTENT_URI);
378 newIntent.putExtra(Intents.Insert.PHONE, number);
379 startActivity(newIntent);
384 if (mMode == MODE_UNKNOWN) {
385 mMode = DEFAULT_MODE;
389 final ListView list = getListView();
390 list.setFocusable(true);
391 list.setOnCreateContextMenuListener(this);
392 if ((mMode & MODE_MASK_NO_FILTER) != MODE_MASK_NO_FILTER) {
393 list.setTextFilterEnabled(true);
396 if ((mMode & MODE_MASK_CREATE_NEW) != 0) {
397 // Add the header for creating a new contact
398 final LayoutInflater inflater = getLayoutInflater();
399 View header = inflater.inflate(android.R.layout.simple_list_item_1, list, false);
400 TextView text = (TextView) header.findViewById(android.R.id.text1);
401 text.setText(R.string.pickerNewContactHeader);
402 list.addHeaderView(header);
405 // Set the proper empty string
408 mAdapter = new ContactItemListAdapter(this);
409 setListAdapter(mAdapter);
411 // We manually save/restore the listview state
412 list.setSaveEnabled(false);
414 mQueryHandler = new QueryHandler(this);
417 // Check to see if sync is enabled
418 final ContentResolver resolver = getContentResolver();
419 IContentProvider provider = resolver.acquireProvider(Contacts.CONTENT_URI);
420 if (provider == null) {
421 // No contacts provider, bail.
427 ISyncAdapter sa = provider.getSyncAdapter();
428 mSyncEnabled = sa != null;
429 } catch (RemoteException e) {
430 mSyncEnabled = false;
432 resolver.releaseProvider(provider);
436 private void setEmptyText() {
437 TextView empty = (TextView) findViewById(R.id.emptyText);
438 // Center the text by default
439 int gravity = Gravity.CENTER;
442 if (Groups.GROUP_MY_CONTACTS.equals(mDisplayInfo)) {
444 empty.setText(getText(R.string.noContactsHelpTextWithSync));
446 empty.setText(getText(R.string.noContactsHelpText));
448 gravity = Gravity.NO_GRAVITY;
450 empty.setText(getString(R.string.groupEmpty, mDisplayInfo));
457 empty.setText(getText(R.string.noFavorites));
460 case MODE_WITH_PHONES:
461 empty.setText(getText(R.string.noContactsWithPhoneNumbers));
465 empty.setText(getText(R.string.noContacts));
468 empty.setGravity(gravity);
472 * Builds the URIs to query when displaying a user group
474 * @param groupName the group being displayed
476 private void buildUserGroupUris(String groupName) {
477 mGroupFilterUri = Uri.parse("content://contacts/groups/name/" + groupName
478 + "/members/filter/");
479 mGroupUri = Uri.parse("content://contacts/groups/name/" + groupName + "/members");
483 * Builds the URIs to query when displaying a system group
485 * @param systemId the system group's ID
487 private void buildSystemGroupUris(String systemId) {
488 mGroupFilterUri = Uri.parse("content://contacts/groups/system_id/" + systemId
489 + "/members/filter/");
490 mGroupUri = Uri.parse("content://contacts/groups/system_id/" + systemId + "/members");
494 * Sets the mode when the request is for "default"
496 private void setDefaultMode() {
497 // Load the preferences
498 SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
500 // Lookup the group to display
501 mDisplayType = prefs.getInt(PREF_DISPLAY_TYPE, DISPLAY_TYPE_UNKNOWN);
502 switch (mDisplayType) {
503 case DISPLAY_TYPE_ALL_WITH_PHONES: {
504 mMode = MODE_WITH_PHONES;
509 case DISPLAY_TYPE_SYSTEM_GROUP: {
510 String systemId = prefs.getString(
511 PREF_DISPLAY_INFO, null);
512 if (!TextUtils.isEmpty(systemId)) {
513 // Display the selected system group
515 buildSystemGroupUris(systemId);
516 mDisplayInfo = systemId;
518 // No valid group is present, display everything
519 mMode = MODE_WITH_PHONES;
521 mDisplayType = DISPLAY_TYPE_ALL;
526 case DISPLAY_TYPE_USER_GROUP: {
527 String displayGroup = prefs.getString(
528 PREF_DISPLAY_INFO, null);
529 if (!TextUtils.isEmpty(displayGroup)) {
530 // Display the selected user group
532 buildUserGroupUris(displayGroup);
533 mDisplayInfo = displayGroup;
535 // No valid group is present, display everything
536 mMode = MODE_WITH_PHONES;
538 mDisplayType = DISPLAY_TYPE_ALL;
543 case DISPLAY_TYPE_ALL: {
544 mMode = MODE_ALL_CONTACTS;
550 // We don't know what to display, default to My Contacts
552 mDisplayType = DISPLAY_TYPE_SYSTEM_GROUP;
553 buildSystemGroupUris(Groups.GROUP_MY_CONTACTS);
554 mDisplayInfo = Groups.GROUP_MY_CONTACTS;
559 // Update the empty text view with the proper string, as the group may have changed
564 protected void onResume() {
567 boolean runQuery = true;
568 Activity parent = getParent();
570 // Do this before setting the filter. The filter thread relies
571 // on some state that is initialized in setDefaultMode
573 // If we're in default mode we need to possibly reset the mode due to a change
574 // in the preferences activity while we weren't running
578 // See if we were invoked with a filter
579 if (parent != null && parent instanceof DialtactsActivity) {
580 String filterText = ((DialtactsActivity) parent).getAndClearFilterText();
581 if (filterText != null && filterText.length() > 0) {
582 getListView().setFilterText(filterText);
583 // Don't start a new query since it will conflict with the filter
585 } else if (mJustCreated) {
586 getListView().clearTextFilter();
591 // Calling requery here may cause an ANR, so always do the async query
594 mJustCreated = false;
597 private void updateGroup() {
602 // Calling requery here may cause an ANR, so always do the async query
607 protected void onSaveInstanceState(Bundle icicle) {
608 super.onSaveInstanceState(icicle);
609 // Save list state in the bundle so we can restore it after the QueryHandler has run
610 icicle.putParcelable(LIST_STATE_KEY, mList.onSaveInstanceState());
611 icicle.putBoolean(FOCUS_KEY, mList.hasFocus());
615 protected void onRestoreInstanceState(Bundle icicle) {
616 super.onRestoreInstanceState(icicle);
617 // Retrieve list state. This will be applied after the QueryHandler has run
618 mListState = icicle.getParcelable(LIST_STATE_KEY);
619 mListHasFocus = icicle.getBoolean(FOCUS_KEY);
623 protected void onStop() {
626 // We don't want the list to display the empty state, since when we resume it will still
627 // be there and show up while the new query is happening. After the async query finished
628 // in response to onResume() setLoading(false) will be called.
629 mAdapter.setLoading(true);
630 mAdapter.changeCursor(null);
632 if (mMode == MODE_QUERY) {
633 // Make sure the search box is closed
634 SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE);
635 searchManager.stopSearch();
640 public boolean onCreateOptionsMenu(Menu menu) {
641 // If Contacts was invoked by another Activity simply as a way of
642 // picking a contact, don't show the options menu
643 if ((mMode & MODE_MASK_PICKER) == MODE_MASK_PICKER) {
648 menu.add(0, MENU_NEW_CONTACT, 0, R.string.menu_newContact)
649 .setIcon(android.R.drawable.ic_menu_add)
650 .setIntent(new Intent(Intents.Insert.ACTION, People.CONTENT_URI))
651 .setAlphabeticShortcut('n');
655 menu.add(0, MENU_DISPLAY_GROUP, 0, R.string.menu_displayGroup)
656 .setIcon(com.android.internal.R.drawable.ic_menu_allfriends);
661 Intent syncIntent = new Intent(Intent.ACTION_VIEW);
662 syncIntent.setClass(this, ContactsGroupSyncSelector.class);
663 menu.add(0, 0, 0, R.string.syncGroupPreference)
664 .setIcon(com.android.internal.R.drawable.ic_menu_refresh)
665 .setIntent(syncIntent);
669 Intent importIntent = new Intent(Intent.ACTION_VIEW);
670 importIntent.setType("vnd.android.cursor.item/sim-contact");
671 importIntent.setClassName("com.android.phone", "com.android.phone.SimContacts");
672 menu.add(0, 0, 0, R.string.importFromSim)
673 .setIcon(R.drawable.ic_menu_import_contact)
674 .setIntent(importIntent);
676 return super.onCreateOptionsMenu(menu);
680 * Implements the handler for display group selection.
682 public void onClick(DialogInterface dialogInterface, int which) {
683 if (which == DialogInterface.BUTTON1) {
684 // The OK button was pressed
685 if (mDisplayGroupOriginalSelection != mDisplayGroupCurrentSelection) {
686 // Set the group to display
687 if (mDisplayGroupCurrentSelection == DISPLAY_GROUP_INDEX_ALL_CONTACTS) {
689 mDisplayType = DISPLAY_TYPE_ALL;
691 } else if (mDisplayGroupCurrentSelection
692 == DISPLAY_GROUP_INDEX_ALL_CONTACTS_WITH_PHONES) {
693 // Display all with phone numbers
694 mDisplayType = DISPLAY_TYPE_ALL_WITH_PHONES;
696 } else if (mDisplayGroupsIncludesMyContacts &&
697 mDisplayGroupCurrentSelection == DISPLAY_GROUP_INDEX_MY_CONTACTS) {
698 mDisplayType = DISPLAY_TYPE_SYSTEM_GROUP;
699 mDisplayInfo = Groups.GROUP_MY_CONTACTS;
701 mDisplayType = DISPLAY_TYPE_USER_GROUP;
702 mDisplayInfo = mDisplayGroups[mDisplayGroupCurrentSelection].toString();
705 // Save the changes to the preferences
706 SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
708 .putInt(PREF_DISPLAY_TYPE, mDisplayType)
709 .putString(PREF_DISPLAY_INFO, mDisplayInfo)
712 // Update the display state
716 // A list item was selected, cache the position
717 mDisplayGroupCurrentSelection = which;
722 public boolean onOptionsItemSelected(MenuItem item) {
723 if (item.getItemId() == MENU_DISPLAY_GROUP) {
724 AlertDialog.Builder builder = new AlertDialog.Builder(this)
725 .setTitle(R.string.select_group_title)
726 .setPositiveButton(android.R.string.ok, this)
727 .setNegativeButton(android.R.string.cancel, null);
729 setGroupEntries(builder);
738 protected void onActivityResult(int requestCode, int resultCode,
740 switch (requestCode) {
741 case SUBACTIVITY_NEW_CONTACT:
742 if (resultCode == RESULT_OK) {
743 // Contact was created, pass it back
744 returnPickerResult(data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME),
751 public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo) {
752 // If Contacts was invoked by another Activity simply as a way of
753 // picking a contact, don't show the context menu
754 if ((mMode & MODE_MASK_PICKER) == MODE_MASK_PICKER) {
758 AdapterView.AdapterContextMenuInfo info;
760 info = (AdapterView.AdapterContextMenuInfo) menuInfo;
761 } catch (ClassCastException e) {
762 Log.e(TAG, "bad menuInfo", e);
766 Cursor cursor = (Cursor) getListAdapter().getItem(info.position);
767 if (cursor == null) {
768 // For some reason the requested item isn't available, do nothing
772 Uri personUri = ContentUris.withAppendedId(People.CONTENT_URI, id);
774 // Setup the menu header
775 menu.setHeaderTitle(cursor.getString(NAME_COLUMN_INDEX));
777 // View contact details
778 menu.add(0, MENU_ITEM_VIEW_CONTACT, 0, R.string.menu_viewContact)
779 .setIntent(new Intent(Intent.ACTION_VIEW, personUri));
782 long phoneId = cursor.getLong(PRIMARY_PHONE_ID_COLUMN_INDEX);
784 // Get the display label for the number
785 CharSequence label = cursor.getString(LABEL_COLUMN_INDEX);
786 int type = cursor.getInt(TYPE_COLUMN_INDEX);
787 label = Phones.getDisplayLabel(this, type, label);
788 Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED,
789 ContentUris.withAppendedId(Phones.CONTENT_URI, phoneId));
790 menu.add(0, MENU_ITEM_CALL, 0, String.format(getString(R.string.menu_callNumber), label))
794 menu.add(0, MENU_ITEM_SEND_SMS, 0, R.string.menu_sendSMS)
795 .setIntent(new Intent(Intent.ACTION_SENDTO,
796 Uri.fromParts("sms", cursor.getString(NUMBER_COLUMN_INDEX), null)));
800 int starState = cursor.getInt(STARRED_COLUMN_INDEX);
801 if (starState == 0) {
802 menu.add(0, MENU_ITEM_TOGGLE_STAR, 0, R.string.menu_addStar);
804 menu.add(0, MENU_ITEM_TOGGLE_STAR, 0, R.string.menu_removeStar);
808 menu.add(0, MENU_ITEM_EDIT, 0, R.string.menu_editContact)
809 .setIntent(new Intent(Intent.ACTION_EDIT, personUri));
810 menu.add(0, MENU_ITEM_DELETE, 0, R.string.menu_deleteContact);
814 public boolean onContextItemSelected(MenuItem item) {
815 AdapterView.AdapterContextMenuInfo info;
817 info = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo();
818 } catch (ClassCastException e) {
819 Log.e(TAG, "bad menuInfo", e);
823 Cursor cursor = (Cursor) getListAdapter().getItem(info.position);
825 switch (item.getItemId()) {
826 case MENU_ITEM_TOGGLE_STAR: {
828 ContentValues values = new ContentValues(1);
829 values.put(People.STARRED, cursor.getInt(STARRED_COLUMN_INDEX) == 0 ? 1 : 0);
830 Uri personUri = ContentUris.withAppendedId(People.CONTENT_URI,
831 cursor.getInt(ID_COLUMN_INDEX));
832 getContentResolver().update(personUri, values, null, null);
836 case MENU_ITEM_DELETE: {
838 Uri uri = ContentUris.withAppendedId(People.CONTENT_URI,
839 cursor.getLong(ID_COLUMN_INDEX));
840 //TODO make this dialog persist across screen rotations
841 new AlertDialog.Builder(ContactsListActivity.this)
842 .setTitle(R.string.deleteConfirmation_title)
843 .setIcon(android.R.drawable.ic_dialog_alert)
844 .setMessage(R.string.deleteConfirmation)
845 .setNegativeButton(android.R.string.cancel, null)
846 .setPositiveButton(android.R.string.ok, new DeleteClickListener(uri))
852 return super.onContextItemSelected(item);
856 public boolean onKeyDown(int keyCode, KeyEvent event) {
858 case KeyEvent.KEYCODE_CALL: {
859 if (callSelection()) {
865 case KeyEvent.KEYCODE_DEL: {
866 Object o = getListView().getSelectedItem();
868 Cursor cursor = (Cursor) o;
869 Uri uri = ContentUris.withAppendedId(People.CONTENT_URI,
870 cursor.getLong(ID_COLUMN_INDEX));
871 //TODO make this dialog persist across screen rotations
872 new AlertDialog.Builder(ContactsListActivity.this)
873 .setTitle(R.string.deleteConfirmation_title)
874 .setIcon(android.R.drawable.ic_dialog_alert)
875 .setMessage(R.string.deleteConfirmation)
876 .setNegativeButton(android.R.string.cancel, null)
877 .setPositiveButton(android.R.string.ok, new DeleteClickListener(uri))
878 .setCancelable(false)
886 return super.onKeyDown(keyCode, event);
890 protected void onListItemClick(ListView l, View v, int position, long id) {
891 if (mMode == MODE_INSERT_OR_EDIT_CONTACT) {
895 intent = new Intent(Intent.ACTION_INSERT, People.CONTENT_URI);
898 intent = new Intent(Intent.ACTION_EDIT,
899 ContentUris.withAppendedId(People.CONTENT_URI, id));
901 intent.setFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
902 final Bundle extras = getIntent().getExtras();
903 if (extras != null) {
904 intent.putExtras(extras);
906 startActivity(intent);
908 } else if (id != -1) {
909 if ((mMode & MODE_MASK_PICKER) == 0) {
910 Intent intent = new Intent(Intent.ACTION_VIEW,
911 ContentUris.withAppendedId(People.CONTENT_URI, id));
912 startActivity(intent);
913 } else if (mMode == MODE_PICK_CONTACT
914 || mMode == MODE_PICK_OR_CREATE_CONTACT) {
915 Uri uri = ContentUris.withAppendedId(People.CONTENT_URI, id);
916 if (mCreateShortcut) {
917 // Subtract one if we have Create Contact at the top
918 Cursor c = (Cursor) mAdapter.getItem(position
919 - (mMode == MODE_PICK_OR_CREATE_CONTACT? 1:0));
920 returnPickerResult(c.getString(NAME_COLUMN_INDEX), uri);
922 returnPickerResult(null, uri);
924 } else if (mMode == MODE_PICK_PHONE) {
925 setResult(RESULT_OK, new Intent().setData(
926 ContentUris.withAppendedId(Phones.CONTENT_URI, id)));
928 } else if (mMode == MODE_PICK_POSTAL) {
929 setResult(RESULT_OK, new Intent().setData(
930 ContentUris.withAppendedId(ContactMethods.CONTENT_URI, id)));
933 } else if ((mMode & MODE_MASK_CREATE_NEW) == MODE_MASK_CREATE_NEW
935 Intent newContact = new Intent(Intents.Insert.ACTION, People.CONTENT_URI);
936 startActivityForResult(newContact, SUBACTIVITY_NEW_CONTACT);
942 private void returnPickerResult(String name, Uri uri) {
943 final Intent intent = new Intent();
945 if (mCreateShortcut) {
946 Intent shortcutIntent = new Intent(Intent.ACTION_VIEW, uri);
947 shortcutIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
948 intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
949 intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, name);
950 final Bitmap icon = People.loadContactPhoto(this, uri, 0, null);
952 intent.putExtra(Intent.EXTRA_SHORTCUT_ICON, icon);
954 intent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE,
955 Intent.ShortcutIconResource.fromContext(this,
956 R.drawable.ic_launcher_contacts));
958 setResult(RESULT_OK, intent);
960 setResult(RESULT_OK, intent.setData(uri));
965 String[] getProjection() {
968 case MODE_ALL_CONTACTS:
969 case MODE_WITH_PHONES:
970 case MODE_PICK_CONTACT:
971 case MODE_PICK_OR_CREATE_CONTACT:
975 case MODE_INSERT_OR_EDIT_CONTACT:
976 return CONTACTS_PROJECTION;
979 return STREQUENT_PROJECTION;
981 case MODE_PICK_PHONE:
982 return PHONES_PROJECTION;
984 case MODE_PICK_POSTAL:
985 return CONTACT_METHODS_PROJECTION;
990 private Uri getPeopleFilterUri(String filter) {
991 if (!TextUtils.isEmpty(filter)) {
992 return Uri.withAppendedPath(People.CONTENT_FILTER_URI, Uri.encode(filter));
994 return People.CONTENT_URI;
999 mAdapter.setLoading(true);
1001 // Cancel any pending queries
1002 mQueryHandler.cancelOperation(QUERY_TOKEN);
1004 // Kick off the new query
1007 mQueryHandler.startQuery(QUERY_TOKEN, null,
1008 mGroupUri, CONTACTS_PROJECTION, null, null, SORT_ORDER);
1011 case MODE_ALL_CONTACTS:
1012 case MODE_PICK_CONTACT:
1013 case MODE_PICK_OR_CREATE_CONTACT:
1014 case MODE_INSERT_OR_EDIT_CONTACT:
1015 mQueryHandler.startQuery(QUERY_TOKEN, null, People.CONTENT_URI, CONTACTS_PROJECTION,
1016 null, null, SORT_ORDER);
1019 case MODE_WITH_PHONES:
1020 mQueryHandler.startQuery(QUERY_TOKEN, null, People.CONTENT_URI, CONTACTS_PROJECTION,
1021 People.PRIMARY_PHONE_ID + " IS NOT NULL", null, People.DEFAULT_SORT_ORDER);
1025 mQuery = getIntent().getStringExtra(SearchManager.QUERY);
1026 mQueryHandler.startQuery(QUERY_TOKEN, null, getPeopleFilterUri(mQuery),
1027 CONTACTS_PROJECTION, null, null, SORT_ORDER);
1032 mQueryHandler.startQuery(QUERY_TOKEN, null, People.CONTENT_URI, CONTACTS_PROJECTION,
1033 People.STARRED + "=1", null, SORT_ORDER);
1037 mQueryHandler.startQuery(QUERY_TOKEN, null, People.CONTENT_URI, CONTACTS_PROJECTION,
1038 People.TIMES_CONTACTED + " > 0", null,
1039 People.TIMES_CONTACTED + " DESC, " + NAME_COLUMN + " ASC");
1042 case MODE_STREQUENT:
1043 mQueryHandler.startQuery(QUERY_TOKEN, null,
1044 Uri.withAppendedPath(People.CONTENT_URI, "strequent"), STREQUENT_PROJECTION,
1048 case MODE_PICK_PHONE:
1049 mQueryHandler.startQuery(QUERY_TOKEN, null, Phones.CONTENT_URI, PHONES_PROJECTION,
1050 null, null, SORT_ORDER);
1053 case MODE_PICK_POSTAL:
1054 mQueryHandler.startQuery(QUERY_TOKEN, null, ContactMethods.CONTENT_URI,
1055 CONTACT_METHODS_PROJECTION,
1056 ContactMethods.KIND + "=" + Contacts.KIND_POSTAL, null, SORT_ORDER);
1062 * Called from a background thread to do the filter and return the resulting cursor.
1064 * @param filter the text that was entered to filter on
1065 * @return a cursor with the results of the filter
1067 Cursor doFilter(String filter) {
1068 final ContentResolver resolver = getContentResolver();
1073 if (TextUtils.isEmpty(filter)) {
1076 uri = Uri.withAppendedPath(mGroupFilterUri, Uri.encode(filter));
1078 return resolver.query(uri, CONTACTS_PROJECTION, null, null,
1079 People.DEFAULT_SORT_ORDER);
1082 case MODE_ALL_CONTACTS:
1083 case MODE_PICK_CONTACT:
1084 case MODE_PICK_OR_CREATE_CONTACT:
1085 case MODE_INSERT_OR_EDIT_CONTACT: {
1086 return resolver.query(getPeopleFilterUri(filter), CONTACTS_PROJECTION, null, null,
1090 case MODE_WITH_PHONES: {
1091 return resolver.query(getPeopleFilterUri(filter), CONTACTS_PROJECTION,
1092 People.PRIMARY_PHONE_ID + " IS NOT NULL", null, SORT_ORDER);
1095 case MODE_STARRED: {
1096 return resolver.query(getPeopleFilterUri(filter), CONTACTS_PROJECTION,
1097 People.STARRED + "=1", null, SORT_ORDER);
1100 case MODE_FREQUENT: {
1101 return resolver.query(getPeopleFilterUri(filter), CONTACTS_PROJECTION,
1102 People.TIMES_CONTACTED + " > 0", null,
1103 People.TIMES_CONTACTED + " DESC, " + NAME_COLUMN + " ASC");
1106 case MODE_STREQUENT: {
1108 if (!TextUtils.isEmpty(filter)) {
1109 uri = Uri.withAppendedPath(People.CONTENT_URI, "strequent/filter/"
1110 + Uri.encode(filter));
1112 uri = Uri.withAppendedPath(People.CONTENT_URI, "strequent");
1114 return resolver.query(uri, STREQUENT_PROJECTION, null, null, null);
1117 case MODE_PICK_PHONE: {
1119 if (!TextUtils.isEmpty(filter)) {
1120 uri = Uri.withAppendedPath(Phones.CONTENT_URI, "filter_name/"
1121 + Uri.encode(filter));
1123 uri = Phones.CONTENT_URI;
1125 return resolver.query(uri, PHONES_PROJECTION, null, null, SORT_ORDER);
1128 throw new UnsupportedOperationException("filtering not allowed in mode " + mMode);
1132 * Calls the currently selected list item.
1133 * @return true if the call was initiated, false otherwise
1135 boolean callSelection() {
1136 ListView list = getListView();
1137 if (list.hasFocus()) {
1138 Cursor cursor = (Cursor) list.getSelectedItem();
1139 if (cursor != null) {
1140 long phoneId = cursor.getLong(PRIMARY_PHONE_ID_COLUMN_INDEX);
1142 // There is no phone number.
1146 Uri uri = ContentUris.withAppendedId(Phones.CONTENT_URI, phoneId);
1147 Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED, uri);
1148 startActivity(intent);
1157 * Signal an error to the user.
1159 void signalError() {
1160 //TODO play an error beep or something...
1163 Cursor getItemForView(View view) {
1164 ListView listView = getListView();
1165 int index = listView.getPositionForView(view);
1169 return (Cursor) listView.getAdapter().getItem(index);
1172 private void setGroupEntries(AlertDialog.Builder builder) {
1173 boolean syncEverything;
1174 // For now we only support a single account and the UI doesn't know what
1175 // the account name is, so we're using a global setting for SYNC_EVERYTHING.
1176 // Some day when we add multiple accounts to the UI this should use the per
1178 String value = Contacts.Settings.getSetting(getContentResolver(), null,
1179 Contacts.Settings.SYNC_EVERYTHING);
1180 if (value == null) {
1181 // If nothing is set yet we default to syncing everything
1182 syncEverything = true;
1184 syncEverything = !TextUtils.isEmpty(value) && !"0".equals(value);
1188 if (!syncEverything) {
1189 cursor = getContentResolver().query(Groups.CONTENT_URI, GROUPS_PROJECTION,
1190 Groups.SHOULD_SYNC + " != 0", null, Groups.DEFAULT_SORT_ORDER);
1192 cursor = getContentResolver().query(Groups.CONTENT_URI, GROUPS_PROJECTION,
1193 null, null, Groups.DEFAULT_SORT_ORDER);
1196 ArrayList<CharSequence> groups = new ArrayList<CharSequence>();
1197 ArrayList<CharSequence> prefStrings = new ArrayList<CharSequence>();
1200 groups.add(DISPLAY_GROUP_INDEX_ALL_CONTACTS, getString(R.string.showAllGroups));
1201 prefStrings.add("");
1203 // Add Contacts with phones
1204 groups.add(DISPLAY_GROUP_INDEX_ALL_CONTACTS_WITH_PHONES,
1205 getString(R.string.groupNameWithPhones));
1206 prefStrings.add(GROUP_WITH_PHONES);
1209 int currentIndex = DISPLAY_GROUP_INDEX_ALL_CONTACTS;
1210 while (cursor.moveToNext()) {
1211 String systemId = cursor.getString(GROUPS_COLUMN_INDEX_SYSTEM_ID);
1212 String name = cursor.getString(GROUPS_COLUMN_INDEX_NAME);
1213 if (cursor.isNull(GROUPS_COLUMN_INDEX_SYSTEM_ID)
1214 && !Groups.GROUP_MY_CONTACTS.equals(systemId)) {
1215 // All groups that aren't My Contacts, since that one is localized on the phone
1217 if (name.equals(mDisplayInfo)) {
1222 // The My Contacts group
1223 groups.add(DISPLAY_GROUP_INDEX_MY_CONTACTS,
1224 getString(R.string.groupNameMyContacts));
1225 if (mDisplayType == DISPLAY_TYPE_SYSTEM_GROUP
1226 && Groups.GROUP_MY_CONTACTS.equals(mDisplayInfo)) {
1227 currentIndex = DISPLAY_GROUP_INDEX_MY_CONTACTS;
1229 mDisplayGroupsIncludesMyContacts = true;
1232 if (mMode == MODE_ALL_CONTACTS) {
1233 currentIndex = DISPLAY_GROUP_INDEX_ALL_CONTACTS;
1234 } else if (mMode == MODE_WITH_PHONES) {
1235 currentIndex = DISPLAY_GROUP_INDEX_ALL_CONTACTS_WITH_PHONES;
1237 mDisplayGroups = groups.toArray(new CharSequence[groups.size()]);
1238 builder.setSingleChoiceItems(mDisplayGroups,
1239 currentIndex, this);
1240 mDisplayGroupOriginalSelection = currentIndex;
1246 private static final class QueryHandler extends AsyncQueryHandler {
1247 private final WeakReference<ContactsListActivity> mActivity;
1249 public QueryHandler(Context context) {
1250 super(context.getContentResolver());
1251 mActivity = new WeakReference<ContactsListActivity>((ContactsListActivity) context);
1255 protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
1256 final ContactsListActivity activity = mActivity.get();
1257 if (activity != null && !activity.isFinishing()) {
1258 activity.mAdapter.setLoading(false);
1259 activity.getListView().clearTextFilter();
1260 activity.mAdapter.changeCursor(cursor);
1262 // Now that the cursor is populated again, it's possible to restore the list state
1263 if (activity.mListState != null) {
1264 activity.mList.onRestoreInstanceState(activity.mListState);
1265 if (activity.mListHasFocus) {
1266 activity.mList.requestFocus();
1268 activity.mListHasFocus = false;
1269 activity.mListState = null;
1277 final static class ContactListItemCache {
1278 public TextView nameView;
1279 public CharArrayBuffer nameBuffer = new CharArrayBuffer(128);
1280 public TextView labelView;
1281 public CharArrayBuffer labelBuffer = new CharArrayBuffer(128);
1282 public TextView numberView;
1283 public CharArrayBuffer numberBuffer = new CharArrayBuffer(128);
1284 public ImageView presenceView;
1287 private final class ContactItemListAdapter extends ResourceCursorAdapter
1288 implements SectionIndexer {
1290 private AlphabetIndexer mIndexer;
1291 private String mAlphabet;
1292 private boolean mLoading = true;
1293 private CharSequence mUnknownNameText;
1294 private CharSequence[] mLocalizedLabels;
1296 public ContactItemListAdapter(Context context) {
1297 super(context, R.layout.contacts_list_item, null);
1299 mAlphabet = context.getString(com.android.internal.R.string.fast_scroll_alphabet);
1301 mUnknownNameText = context.getText(android.R.string.unknownName);
1303 case MODE_PICK_POSTAL:
1304 mLocalizedLabels = EditContactActivity.getLabelsForKind(mContext,
1305 Contacts.KIND_POSTAL);
1308 mLocalizedLabels = EditContactActivity.getLabelsForKind(mContext,
1309 Contacts.KIND_PHONE);
1314 public void setLoading(boolean loading) {
1319 public boolean isEmpty() {
1320 if ((mMode & MODE_MASK_CREATE_NEW) == MODE_MASK_CREATE_NEW) {
1321 // This mode mask adds a header and we always want it to show up, even
1322 // if the list is empty, so always claim the list is not empty.
1326 // We don't want the empty state to show when loading.
1329 return super.isEmpty();
1335 public View newView(Context context, Cursor cursor, ViewGroup parent) {
1336 final View view = super.newView(context, cursor, parent);
1338 final ContactListItemCache cache = new ContactListItemCache();
1339 cache.nameView = (TextView) view.findViewById(R.id.name);
1340 cache.labelView = (TextView) view.findViewById(R.id.label);
1341 cache.numberView = (TextView) view.findViewById(R.id.number);
1342 cache.presenceView = (ImageView) view.findViewById(R.id.presence);
1349 public void bindView(View view, Context context, Cursor cursor) {
1350 final ContactListItemCache cache = (ContactListItemCache) view.getTag();
1353 cursor.copyStringToBuffer(NAME_COLUMN_INDEX, cache.nameBuffer);
1354 int size = cache.nameBuffer.sizeCopied;
1356 cache.nameView.setText(cache.nameBuffer.data, 0, size);
1358 cache.nameView.setText(mUnknownNameText);
1361 // Set the phone number
1362 TextView numberView = cache.numberView;
1363 TextView labelView = cache.labelView;
1364 cursor.copyStringToBuffer(NUMBER_COLUMN_INDEX, cache.numberBuffer);
1365 size = cache.numberBuffer.sizeCopied;
1367 numberView.setText(cache.numberBuffer.data, 0, size);
1368 numberView.setVisibility(View.VISIBLE);
1369 labelView.setVisibility(View.VISIBLE);
1371 numberView.setVisibility(View.GONE);
1372 labelView.setVisibility(View.GONE);
1376 if (!cursor.isNull(TYPE_COLUMN_INDEX)) {
1377 int type = cursor.getInt(TYPE_COLUMN_INDEX);
1379 if (type != People.Phones.TYPE_CUSTOM) {
1381 labelView.setText(mLocalizedLabels[type - 1]);
1382 } catch (ArrayIndexOutOfBoundsException e) {
1383 labelView.setText(mLocalizedLabels[People.Phones.TYPE_HOME - 1]);
1386 cursor.copyStringToBuffer(LABEL_COLUMN_INDEX, cache.labelBuffer);
1387 // Don't check size, if it's zero just don't show anything
1388 labelView.setText(cache.labelBuffer.data, 0, cache.labelBuffer.sizeCopied);
1391 // There is no label, hide the the view
1392 labelView.setVisibility(View.GONE);
1395 // Set the proper icon (star or presence or nothing)
1396 ImageView presenceView = cache.presenceView;
1397 if (mMode != MODE_STREQUENT) {
1398 if ((mMode & MODE_MASK_NO_PRESENCE) == 0) {
1400 if (!cursor.isNull(SERVER_STATUS_COLUMN_INDEX)) {
1401 serverStatus = cursor.getInt(SERVER_STATUS_COLUMN_INDEX);
1402 presenceView.setImageResource(
1403 Presence.getPresenceIconResourceId(serverStatus));
1404 presenceView.setVisibility(View.VISIBLE);
1406 presenceView.setVisibility(View.GONE);
1409 presenceView.setVisibility(View.GONE);
1412 if (cursor.getInt(STARRED_COLUMN_INDEX) != 0) {
1413 presenceView.setImageResource(R.drawable.star_on);
1414 presenceView.setVisibility(View.VISIBLE);
1416 presenceView.setVisibility(View.GONE);
1422 public void changeCursor(Cursor cursor) {
1423 super.changeCursor(cursor);
1424 updateIndexer(cursor);
1427 private void updateIndexer(Cursor cursor) {
1428 if (mIndexer == null) {
1429 mIndexer = new AlphabetIndexer(cursor, NAME_COLUMN_INDEX, mAlphabet);
1431 mIndexer.setCursor(cursor);
1436 * Run the query on a helper thread. Beware that this code does not run
1437 * on the main UI thread!
1440 public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
1441 return doFilter(constraint.toString());
1444 public Object [] getSections() {
1445 if (mMode == MODE_STREQUENT) {
1446 return new String[] { " " };
1448 return mIndexer.getSections();
1452 public int getPositionForSection(int sectionIndex) {
1453 if (mMode == MODE_STREQUENT) {
1457 if (mIndexer == null) {
1458 Cursor cursor = mAdapter.getCursor();
1459 if (cursor == null) {
1460 // No cursor, the section doesn't exist so just return 0
1463 mIndexer = new AlphabetIndexer(cursor, NAME_COLUMN_INDEX, mAlphabet);
1466 return mIndexer.getPositionForSection(sectionIndex);
1469 public int getSectionForPosition(int position) {