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.view.inputmethod.InputMethodManager;
61 import android.widget.AdapterView;
62 import android.widget.AlphabetIndexer;
63 import android.widget.Filter;
64 import android.widget.ImageView;
65 import android.widget.ListView;
66 import android.widget.ResourceCursorAdapter;
67 import android.widget.SectionIndexer;
68 import android.widget.TextView;
70 import java.lang.ref.WeakReference;
71 import java.util.ArrayList;
74 * Displays a list of contacts. Usually is embedded into the ContactsActivity.
76 public final class ContactsListActivity extends ListActivity
77 implements View.OnCreateContextMenuListener, DialogInterface.OnClickListener {
78 private static final String TAG = "ContactsListActivity";
80 private static final String LIST_STATE_KEY = "liststate";
81 private static final String FOCUS_KEY = "focused";
83 static final int MENU_ITEM_VIEW_CONTACT = 1;
84 static final int MENU_ITEM_CALL = 2;
85 static final int MENU_ITEM_EDIT_BEFORE_CALL = 3;
86 static final int MENU_ITEM_SEND_SMS = 4;
87 static final int MENU_ITEM_SEND_IM = 5;
88 static final int MENU_ITEM_EDIT = 6;
89 static final int MENU_ITEM_DELETE = 7;
90 static final int MENU_ITEM_TOGGLE_STAR = 8;
92 public static final int MENU_SEARCH = 1;
93 public static final int MENU_DIALER = 9;
94 public static final int MENU_NEW_CONTACT = 10;
95 public static final int MENU_DISPLAY_GROUP = 11;
97 private static final int SUBACTIVITY_NEW_CONTACT = 1;
99 /** Mask for picker mode */
100 static final int MODE_MASK_PICKER = 0x80000000;
101 /** Mask for no presence mode */
102 static final int MODE_MASK_NO_PRESENCE = 0x40000000;
103 /** Mask for enabling list filtering */
104 static final int MODE_MASK_NO_FILTER = 0x20000000;
105 /** Mask for having a "create new contact" header in the list */
106 static final int MODE_MASK_CREATE_NEW = 0x10000000;
109 static final int MODE_UNKNOWN = 0;
110 /** Show members of the "Contacts" group */
111 static final int MODE_GROUP = 5;
112 /** Show all contacts sorted alphabetically */
113 static final int MODE_ALL_CONTACTS = 10;
114 /** Show all contacts with phone numbers, sorted alphabetically */
115 static final int MODE_WITH_PHONES = 15;
116 /** Show all starred contacts */
117 static final int MODE_STARRED = 20;
118 /** Show frequently contacted contacts */
119 static final int MODE_FREQUENT = 30;
120 /** Show starred and the frequent */
121 static final int MODE_STREQUENT = 35;
122 /** Show all contacts and pick them when clicking */
123 static final int MODE_PICK_CONTACT = 40 | MODE_MASK_PICKER;
124 /** Show all contacts as well as the option to create a new one */
125 static final int MODE_PICK_OR_CREATE_CONTACT = 42 | MODE_MASK_PICKER | MODE_MASK_CREATE_NEW;
126 /** Show all contacts and pick them when clicking, and allow creating a new contact */
127 static final int MODE_INSERT_OR_EDIT_CONTACT = 45 | MODE_MASK_PICKER | MODE_MASK_CREATE_NEW;
128 /** Show all phone numbers and pick them when clicking */
129 static final int MODE_PICK_PHONE = 50 | MODE_MASK_PICKER | MODE_MASK_NO_PRESENCE;
130 /** Show all postal addresses and pick them when clicking */
131 static final int MODE_PICK_POSTAL =
132 55 | MODE_MASK_PICKER | MODE_MASK_NO_PRESENCE | MODE_MASK_NO_FILTER;
133 /** Run a search query */
134 static final int MODE_QUERY = 60 | MODE_MASK_NO_FILTER;
136 static final int DEFAULT_MODE = MODE_ALL_CONTACTS;
139 * The type of data to display in the main contacts list.
141 static final String PREF_DISPLAY_TYPE = "display_system_group";
143 /** Unknown display type. */
144 static final int DISPLAY_TYPE_UNKNOWN = -1;
145 /** Display all contacts */
146 static final int DISPLAY_TYPE_ALL = 0;
147 /** Display all contacts that have phone numbers */
148 static final int DISPLAY_TYPE_ALL_WITH_PHONES = 1;
149 /** Display a system group */
150 static final int DISPLAY_TYPE_SYSTEM_GROUP = 2;
151 /** Display a user group */
152 static final int DISPLAY_TYPE_USER_GROUP = 3;
155 * Info about what to display. If {@link #PREF_DISPLAY_TYPE}
156 * is {@link #DISPLAY_TYPE_SYSTEM_GROUP} then this will be the system id.
157 * If {@link #PREF_DISPLAY_TYPE} is {@link #DISPLAY_TYPE_USER_GROUP} then this will
160 static final String PREF_DISPLAY_INFO = "display_group";
163 static final String NAME_COLUMN = People.DISPLAY_NAME;
165 static final String[] CONTACTS_PROJECTION = new String[] {
172 People.PRIMARY_PHONE_ID, // 6
173 People.PRIMARY_EMAIL_ID, // 7
174 People.PRESENCE_STATUS, // 8
177 static final String[] STREQUENT_PROJECTION = new String[] {
184 People.PRIMARY_PHONE_ID, // 6
185 People.PRIMARY_EMAIL_ID, // 7
186 People.PRESENCE_STATUS, // 8
187 People.TIMES_CONTACTED, // 9 (not displayed, but required for the order by to work)
190 static final String[] PHONES_PROJECTION = new String[] {
199 static final String[] CONTACT_METHODS_PROJECTION = new String[] {
200 ContactMethods._ID, // 0
202 ContactMethods.DATA, // 2
203 ContactMethods.TYPE, // 3
204 ContactMethods.LABEL, // 4
205 ContactMethods.STARRED, // 5
208 static final int ID_COLUMN_INDEX = 0;
209 static final int NAME_COLUMN_INDEX = 1;
210 static final int NUMBER_COLUMN_INDEX = 2;
211 static final int DATA_COLUMN_INDEX = 2;
212 static final int TYPE_COLUMN_INDEX = 3;
213 static final int LABEL_COLUMN_INDEX = 4;
214 static final int STARRED_COLUMN_INDEX = 5;
215 static final int PRIMARY_PHONE_ID_COLUMN_INDEX = 6;
216 static final int PRIMARY_EMAIL_ID_COLUMN_INDEX = 7;
217 static final int SERVER_STATUS_COLUMN_INDEX = 8;
220 static final int DISPLAY_GROUP_INDEX_ALL_CONTACTS = 0;
221 static final int DISPLAY_GROUP_INDEX_ALL_CONTACTS_WITH_PHONES = 1;
222 static final int DISPLAY_GROUP_INDEX_MY_CONTACTS = 2;
224 static final String SORT_ORDER = NAME_COLUMN + " COLLATE LOCALIZED ASC";
226 private static final int QUERY_TOKEN = 42;
228 private static final String[] GROUPS_PROJECTION = new String[] {
229 Groups.SYSTEM_ID, // 0
232 private static final int GROUPS_COLUMN_INDEX_SYSTEM_ID = 0;
233 private static final int GROUPS_COLUMN_INDEX_NAME = 1;
235 static final String GROUP_WITH_PHONES = "android_smartgroup_phone";
237 ContactItemListAdapter mAdapter;
239 int mMode = DEFAULT_MODE;
240 // The current display group
241 private String mDisplayInfo;
242 private int mDisplayType;
243 // The current list of display groups, during selection from menu
244 private CharSequence[] mDisplayGroups;
245 // If true position 2 in mDisplayGroups is the MyContacts group
246 private boolean mDisplayGroupsIncludesMyContacts = false;
248 private int mDisplayGroupOriginalSelection;
249 private int mDisplayGroupCurrentSelection;
251 private QueryHandler mQueryHandler;
252 private String mQuery;
253 private Uri mGroupFilterUri;
254 private Uri mGroupUri;
255 private boolean mJustCreated;
256 private boolean mSyncEnabled;
259 * Used to keep track of the scroll state of the list.
261 private Parcelable mListState = null;
262 private boolean mListHasFocus;
264 private boolean mCreateShortcut;
265 private boolean mDefaultMode = false;
267 private class DeleteClickListener implements DialogInterface.OnClickListener {
270 public DeleteClickListener(Uri uri) {
274 public void onClick(DialogInterface dialog, int which) {
275 getContentResolver().delete(mUri, null, null);
280 protected void onCreate(Bundle icicle) {
281 super.onCreate(icicle);
283 // Resolve the intent
284 final Intent intent = getIntent();
286 // Allow the title to be set to a custom String using an extra on the intent
287 String title = intent.getStringExtra(Contacts.Intents.UI.TITLE_EXTRA_KEY);
292 final String action = intent.getAction();
293 mMode = MODE_UNKNOWN;
295 setContentView(R.layout.contacts_list_content);
297 if (UI.LIST_DEFAULT.equals(action)) {
299 // When mDefaultMode is true the mode is set in onResume(), since the preferneces
300 // activity may change it whenever this activity isn't running
301 } else if (UI.LIST_GROUP_ACTION.equals(action)) {
303 String groupName = intent.getStringExtra(UI.GROUP_NAME_EXTRA_KEY);
304 if (TextUtils.isEmpty(groupName)) {
308 buildUserGroupUris(groupName);
309 } else if (UI.LIST_ALL_CONTACTS_ACTION.equals(action)) {
310 mMode = MODE_ALL_CONTACTS;
311 } else if (UI.LIST_STARRED_ACTION.equals(action)) {
312 mMode = MODE_STARRED;
313 } else if (UI.LIST_FREQUENT_ACTION.equals(action)) {
314 mMode = MODE_FREQUENT;
315 } else if (UI.LIST_STREQUENT_ACTION.equals(action)) {
316 mMode = MODE_STREQUENT;
317 } else if (UI.LIST_CONTACTS_WITH_PHONES_ACTION.equals(action)) {
318 mMode = MODE_WITH_PHONES;
319 } else if (Intent.ACTION_PICK.equals(action)) {
320 // XXX These should be showing the data from the URI given in
322 final String type = intent.resolveType(this);
323 if (People.CONTENT_TYPE.equals(type)) {
324 mMode = MODE_PICK_CONTACT;
325 } else if (Phones.CONTENT_TYPE.equals(type)) {
326 mMode = MODE_PICK_PHONE;
327 } else if (ContactMethods.CONTENT_POSTAL_TYPE.equals(type)) {
328 mMode = MODE_PICK_POSTAL;
330 } else if (Intent.ACTION_CREATE_SHORTCUT.equals(action)) {
331 mMode = MODE_PICK_OR_CREATE_CONTACT;
332 mCreateShortcut = true;
333 } else if (Intent.ACTION_GET_CONTENT.equals(action)) {
334 final String type = intent.resolveType(this);
335 if (People.CONTENT_ITEM_TYPE.equals(type)) {
336 mMode = MODE_PICK_OR_CREATE_CONTACT;
337 } else if (Phones.CONTENT_ITEM_TYPE.equals(type)) {
338 mMode = MODE_PICK_PHONE;
339 } else if (ContactMethods.CONTENT_POSTAL_ITEM_TYPE.equals(type)) {
340 mMode = MODE_PICK_POSTAL;
342 } else if (Intent.ACTION_INSERT_OR_EDIT.equals(action)) {
343 mMode = MODE_INSERT_OR_EDIT_CONTACT;
344 } else if (Intent.ACTION_SEARCH.equals(action)) {
345 // See if the suggestion was clicked with a search action key (call button)
346 if ("call".equals(intent.getStringExtra(SearchManager.ACTION_MSG))) {
347 String query = intent.getStringExtra(SearchManager.QUERY);
348 if (!TextUtils.isEmpty(query)) {
349 Intent newIntent = new Intent(Intent.ACTION_CALL_PRIVILEGED,
350 Uri.fromParts("tel", query, null));
351 startActivity(newIntent);
356 // Otherwise handle the more normal search case
359 // Since this is the filter activity it receives all intents
360 // dispatched from the SearchManager for security reasons
361 // so we need to re-dispatch from here to the intended target.
362 } else if (Intents.SEARCH_SUGGESTION_CLICKED.equals(action)) {
363 // See if the suggestion was clicked with a search action key (call button)
365 if ("call".equals(intent.getStringExtra(SearchManager.ACTION_MSG))) {
366 newIntent = new Intent(Intent.ACTION_CALL_PRIVILEGED, intent.getData());
368 newIntent = new Intent(Intent.ACTION_VIEW, intent.getData());
370 startActivity(newIntent);
373 } else if (Intents.SEARCH_SUGGESTION_DIAL_NUMBER_CLICKED.equals(action)) {
374 Intent newIntent = new Intent(Intent.ACTION_CALL_PRIVILEGED, intent.getData());
375 startActivity(newIntent);
378 } else if (Intents.SEARCH_SUGGESTION_CREATE_CONTACT_CLICKED.equals(action)) {
379 String number = intent.getData().getSchemeSpecificPart();
380 Intent newIntent = new Intent(Intent.ACTION_INSERT, People.CONTENT_URI);
381 newIntent.putExtra(Intents.Insert.PHONE, number);
382 startActivity(newIntent);
387 if (mMode == MODE_UNKNOWN) {
388 mMode = DEFAULT_MODE;
392 final ListView list = getListView();
393 list.setFocusable(true);
394 list.setOnCreateContextMenuListener(this);
395 if ((mMode & MODE_MASK_NO_FILTER) != MODE_MASK_NO_FILTER) {
396 list.setTextFilterEnabled(true);
399 if ((mMode & MODE_MASK_CREATE_NEW) != 0) {
400 // Add the header for creating a new contact
401 final LayoutInflater inflater = getLayoutInflater();
402 View header = inflater.inflate(android.R.layout.simple_list_item_1, list, false);
403 TextView text = (TextView) header.findViewById(android.R.id.text1);
404 text.setText(R.string.pickerNewContactHeader);
405 list.addHeaderView(header);
408 // Set the proper empty string
411 mAdapter = new ContactItemListAdapter(this);
412 setListAdapter(mAdapter);
414 // We manually save/restore the listview state
415 list.setSaveEnabled(false);
417 mQueryHandler = new QueryHandler(this);
420 // Check to see if sync is enabled
421 final ContentResolver resolver = getContentResolver();
422 IContentProvider provider = resolver.acquireProvider(Contacts.CONTENT_URI);
423 if (provider == null) {
424 // No contacts provider, bail.
430 ISyncAdapter sa = provider.getSyncAdapter();
431 mSyncEnabled = sa != null;
432 } catch (RemoteException e) {
433 mSyncEnabled = false;
435 resolver.releaseProvider(provider);
439 private void setEmptyText() {
440 TextView empty = (TextView) findViewById(R.id.emptyText);
441 // Center the text by default
442 int gravity = Gravity.CENTER;
445 if (Groups.GROUP_MY_CONTACTS.equals(mDisplayInfo)) {
447 empty.setText(getText(R.string.noContactsHelpTextWithSync));
449 empty.setText(getText(R.string.noContactsHelpText));
451 gravity = Gravity.NO_GRAVITY;
453 empty.setText(getString(R.string.groupEmpty, mDisplayInfo));
460 empty.setText(getText(R.string.noFavorites));
463 case MODE_WITH_PHONES:
464 empty.setText(getText(R.string.noContactsWithPhoneNumbers));
468 empty.setText(getText(R.string.noContacts));
471 empty.setGravity(gravity);
475 * Builds the URIs to query when displaying a user group
477 * @param groupName the group being displayed
479 private void buildUserGroupUris(String groupName) {
480 mGroupFilterUri = Uri.parse("content://contacts/groups/name/" + groupName
481 + "/members/filter/");
482 mGroupUri = Uri.parse("content://contacts/groups/name/" + groupName + "/members");
486 * Builds the URIs to query when displaying a system group
488 * @param systemId the system group's ID
490 private void buildSystemGroupUris(String systemId) {
491 mGroupFilterUri = Uri.parse("content://contacts/groups/system_id/" + systemId
492 + "/members/filter/");
493 mGroupUri = Uri.parse("content://contacts/groups/system_id/" + systemId + "/members");
497 * Sets the mode when the request is for "default"
499 private void setDefaultMode() {
500 // Load the preferences
501 SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
503 // Lookup the group to display
504 mDisplayType = prefs.getInt(PREF_DISPLAY_TYPE, DISPLAY_TYPE_UNKNOWN);
505 switch (mDisplayType) {
506 case DISPLAY_TYPE_ALL_WITH_PHONES: {
507 mMode = MODE_WITH_PHONES;
512 case DISPLAY_TYPE_SYSTEM_GROUP: {
513 String systemId = prefs.getString(
514 PREF_DISPLAY_INFO, null);
515 if (!TextUtils.isEmpty(systemId)) {
516 // Display the selected system group
518 buildSystemGroupUris(systemId);
519 mDisplayInfo = systemId;
521 // No valid group is present, display everything
522 mMode = MODE_WITH_PHONES;
524 mDisplayType = DISPLAY_TYPE_ALL;
529 case DISPLAY_TYPE_USER_GROUP: {
530 String displayGroup = prefs.getString(
531 PREF_DISPLAY_INFO, null);
532 if (!TextUtils.isEmpty(displayGroup)) {
533 // Display the selected user group
535 buildUserGroupUris(displayGroup);
536 mDisplayInfo = displayGroup;
538 // No valid group is present, display everything
539 mMode = MODE_WITH_PHONES;
541 mDisplayType = DISPLAY_TYPE_ALL;
546 case DISPLAY_TYPE_ALL: {
547 mMode = MODE_ALL_CONTACTS;
553 // We don't know what to display, default to My Contacts
555 mDisplayType = DISPLAY_TYPE_SYSTEM_GROUP;
556 buildSystemGroupUris(Groups.GROUP_MY_CONTACTS);
557 mDisplayInfo = Groups.GROUP_MY_CONTACTS;
562 // Update the empty text view with the proper string, as the group may have changed
567 protected void onResume() {
570 boolean runQuery = true;
571 Activity parent = getParent();
573 // Do this before setting the filter. The filter thread relies
574 // on some state that is initialized in setDefaultMode
576 // If we're in default mode we need to possibly reset the mode due to a change
577 // in the preferences activity while we weren't running
581 // See if we were invoked with a filter
582 if (parent != null && parent instanceof DialtactsActivity) {
583 String filterText = ((DialtactsActivity) parent).getAndClearFilterText();
584 if (filterText != null && filterText.length() > 0) {
585 getListView().setFilterText(filterText);
586 // Don't start a new query since it will conflict with the filter
588 } else if (mJustCreated) {
589 getListView().clearTextFilter();
593 if (mJustCreated && runQuery) {
594 // We need to start a query here the first time the activity is launched, as long
595 // as we aren't doing a filter.
598 mJustCreated = false;
602 protected void onRestart() {
605 // The cursor was killed off in onStop(), so we need to get a new one here
609 private void updateGroup() {
614 // Calling requery here may cause an ANR, so always do the async query
619 protected void onSaveInstanceState(Bundle icicle) {
620 super.onSaveInstanceState(icicle);
621 // Save list state in the bundle so we can restore it after the QueryHandler has run
622 icicle.putParcelable(LIST_STATE_KEY, mList.onSaveInstanceState());
623 icicle.putBoolean(FOCUS_KEY, mList.hasFocus());
627 protected void onRestoreInstanceState(Bundle icicle) {
628 super.onRestoreInstanceState(icicle);
629 // Retrieve list state. This will be applied after the QueryHandler has run
630 mListState = icicle.getParcelable(LIST_STATE_KEY);
631 mListHasFocus = icicle.getBoolean(FOCUS_KEY);
635 protected void onStop() {
638 // We don't want the list to display the empty state, since when we resume it will still
639 // be there and show up while the new query is happening. After the async query finished
640 // in response to onRestart() setLoading(false) will be called.
641 mAdapter.setLoading(true);
642 mAdapter.changeCursor(null);
644 if (mMode == MODE_QUERY) {
645 // Make sure the search box is closed
646 SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE);
647 searchManager.stopSearch();
652 public boolean onCreateOptionsMenu(Menu menu) {
653 // If Contacts was invoked by another Activity simply as a way of
654 // picking a contact, don't show the options menu
655 if ((mMode & MODE_MASK_PICKER) == MODE_MASK_PICKER) {
660 menu.add(0, MENU_SEARCH, 0, R.string.menu_search)
661 .setIcon(android.R.drawable.ic_menu_search);
664 menu.add(0, MENU_NEW_CONTACT, 0, R.string.menu_newContact)
665 .setIcon(android.R.drawable.ic_menu_add)
666 .setIntent(new Intent(Intents.Insert.ACTION, People.CONTENT_URI))
667 .setAlphabeticShortcut('n');
671 menu.add(0, MENU_DISPLAY_GROUP, 0, R.string.menu_displayGroup)
672 .setIcon(com.android.internal.R.drawable.ic_menu_allfriends);
677 Intent syncIntent = new Intent(Intent.ACTION_VIEW);
678 syncIntent.setClass(this, ContactsGroupSyncSelector.class);
679 menu.add(0, 0, 0, R.string.syncGroupPreference)
680 .setIcon(com.android.internal.R.drawable.ic_menu_refresh)
681 .setIntent(syncIntent);
685 Intent importIntent = new Intent(Intent.ACTION_VIEW);
686 importIntent.setType("vnd.android.cursor.item/sim-contact");
687 importIntent.setClassName("com.android.phone", "com.android.phone.SimContacts");
688 menu.add(0, 0, 0, R.string.importFromSim)
689 .setIcon(R.drawable.ic_menu_import_contact)
690 .setIntent(importIntent);
692 return super.onCreateOptionsMenu(menu);
696 * Implements the handler for display group selection.
698 public void onClick(DialogInterface dialogInterface, int which) {
699 if (which == DialogInterface.BUTTON1) {
700 // The OK button was pressed
701 if (mDisplayGroupOriginalSelection != mDisplayGroupCurrentSelection) {
702 // Set the group to display
703 if (mDisplayGroupCurrentSelection == DISPLAY_GROUP_INDEX_ALL_CONTACTS) {
705 mDisplayType = DISPLAY_TYPE_ALL;
707 } else if (mDisplayGroupCurrentSelection
708 == DISPLAY_GROUP_INDEX_ALL_CONTACTS_WITH_PHONES) {
709 // Display all with phone numbers
710 mDisplayType = DISPLAY_TYPE_ALL_WITH_PHONES;
712 } else if (mDisplayGroupsIncludesMyContacts &&
713 mDisplayGroupCurrentSelection == DISPLAY_GROUP_INDEX_MY_CONTACTS) {
714 mDisplayType = DISPLAY_TYPE_SYSTEM_GROUP;
715 mDisplayInfo = Groups.GROUP_MY_CONTACTS;
717 mDisplayType = DISPLAY_TYPE_USER_GROUP;
718 mDisplayInfo = mDisplayGroups[mDisplayGroupCurrentSelection].toString();
721 // Save the changes to the preferences
722 SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
724 .putInt(PREF_DISPLAY_TYPE, mDisplayType)
725 .putString(PREF_DISPLAY_INFO, mDisplayInfo)
728 // Update the display state
732 // A list item was selected, cache the position
733 mDisplayGroupCurrentSelection = which;
738 public boolean onOptionsItemSelected(MenuItem item) {
739 switch (item.getItemId()) {
740 case MENU_DISPLAY_GROUP:
741 AlertDialog.Builder builder = new AlertDialog.Builder(this)
742 .setTitle(R.string.select_group_title)
743 .setPositiveButton(android.R.string.ok, this)
744 .setNegativeButton(android.R.string.cancel, null);
746 setGroupEntries(builder);
752 startSearch(null, false, null, false);
759 protected void onActivityResult(int requestCode, int resultCode,
761 switch (requestCode) {
762 case SUBACTIVITY_NEW_CONTACT:
763 if (resultCode == RESULT_OK) {
764 // Contact was created, pass it back
765 returnPickerResult(data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME),
772 public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo) {
773 // If Contacts was invoked by another Activity simply as a way of
774 // picking a contact, don't show the context menu
775 if ((mMode & MODE_MASK_PICKER) == MODE_MASK_PICKER) {
779 AdapterView.AdapterContextMenuInfo info;
781 info = (AdapterView.AdapterContextMenuInfo) menuInfo;
782 } catch (ClassCastException e) {
783 Log.e(TAG, "bad menuInfo", e);
787 Cursor cursor = (Cursor) getListAdapter().getItem(info.position);
788 if (cursor == null) {
789 // For some reason the requested item isn't available, do nothing
793 Uri personUri = ContentUris.withAppendedId(People.CONTENT_URI, id);
795 // Setup the menu header
796 menu.setHeaderTitle(cursor.getString(NAME_COLUMN_INDEX));
798 // View contact details
799 menu.add(0, MENU_ITEM_VIEW_CONTACT, 0, R.string.menu_viewContact)
800 .setIntent(new Intent(Intent.ACTION_VIEW, personUri));
803 long phoneId = cursor.getLong(PRIMARY_PHONE_ID_COLUMN_INDEX);
805 // Get the display label for the number
806 CharSequence label = cursor.getString(LABEL_COLUMN_INDEX);
807 int type = cursor.getInt(TYPE_COLUMN_INDEX);
808 label = Phones.getDisplayLabel(this, type, label);
809 Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED,
810 ContentUris.withAppendedId(Phones.CONTENT_URI, phoneId));
811 menu.add(0, MENU_ITEM_CALL, 0, String.format(getString(R.string.menu_callNumber), label))
815 menu.add(0, MENU_ITEM_SEND_SMS, 0, R.string.menu_sendSMS)
816 .setIntent(new Intent(Intent.ACTION_SENDTO,
817 Uri.fromParts("sms", cursor.getString(NUMBER_COLUMN_INDEX), null)));
821 int starState = cursor.getInt(STARRED_COLUMN_INDEX);
822 if (starState == 0) {
823 menu.add(0, MENU_ITEM_TOGGLE_STAR, 0, R.string.menu_addStar);
825 menu.add(0, MENU_ITEM_TOGGLE_STAR, 0, R.string.menu_removeStar);
829 menu.add(0, MENU_ITEM_EDIT, 0, R.string.menu_editContact)
830 .setIntent(new Intent(Intent.ACTION_EDIT, personUri));
831 menu.add(0, MENU_ITEM_DELETE, 0, R.string.menu_deleteContact);
835 public boolean onContextItemSelected(MenuItem item) {
836 AdapterView.AdapterContextMenuInfo info;
838 info = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo();
839 } catch (ClassCastException e) {
840 Log.e(TAG, "bad menuInfo", e);
844 Cursor cursor = (Cursor) getListAdapter().getItem(info.position);
846 switch (item.getItemId()) {
847 case MENU_ITEM_TOGGLE_STAR: {
849 ContentValues values = new ContentValues(1);
850 values.put(People.STARRED, cursor.getInt(STARRED_COLUMN_INDEX) == 0 ? 1 : 0);
851 Uri personUri = ContentUris.withAppendedId(People.CONTENT_URI,
852 cursor.getInt(ID_COLUMN_INDEX));
853 getContentResolver().update(personUri, values, null, null);
857 case MENU_ITEM_DELETE: {
859 Uri uri = ContentUris.withAppendedId(People.CONTENT_URI,
860 cursor.getLong(ID_COLUMN_INDEX));
861 //TODO make this dialog persist across screen rotations
862 new AlertDialog.Builder(ContactsListActivity.this)
863 .setTitle(R.string.deleteConfirmation_title)
864 .setIcon(android.R.drawable.ic_dialog_alert)
865 .setMessage(R.string.deleteConfirmation)
866 .setNegativeButton(android.R.string.cancel, null)
867 .setPositiveButton(android.R.string.ok, new DeleteClickListener(uri))
873 return super.onContextItemSelected(item);
877 public boolean onKeyDown(int keyCode, KeyEvent event) {
879 case KeyEvent.KEYCODE_CALL: {
880 if (callSelection()) {
886 case KeyEvent.KEYCODE_DEL: {
887 Object o = getListView().getSelectedItem();
889 Cursor cursor = (Cursor) o;
890 Uri uri = ContentUris.withAppendedId(People.CONTENT_URI,
891 cursor.getLong(ID_COLUMN_INDEX));
892 //TODO make this dialog persist across screen rotations
893 new AlertDialog.Builder(ContactsListActivity.this)
894 .setTitle(R.string.deleteConfirmation_title)
895 .setIcon(android.R.drawable.ic_dialog_alert)
896 .setMessage(R.string.deleteConfirmation)
897 .setNegativeButton(android.R.string.cancel, null)
898 .setPositiveButton(android.R.string.ok, new DeleteClickListener(uri))
899 .setCancelable(false)
907 return super.onKeyDown(keyCode, event);
911 protected void onListItemClick(ListView l, View v, int position, long id) {
912 // Hide soft keyboard, if visible
913 InputMethodManager inputMethodManager = (InputMethodManager)
914 getSystemService(Context.INPUT_METHOD_SERVICE);
915 inputMethodManager.hideSoftInputFromWindow(mList.getWindowToken(), 0);
917 if (mMode == MODE_INSERT_OR_EDIT_CONTACT) {
921 intent = new Intent(Intent.ACTION_INSERT, People.CONTENT_URI);
924 intent = new Intent(Intent.ACTION_EDIT,
925 ContentUris.withAppendedId(People.CONTENT_URI, id));
927 intent.setFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
928 final Bundle extras = getIntent().getExtras();
929 if (extras != null) {
930 intent.putExtras(extras);
932 startActivity(intent);
934 } else if (id != -1) {
935 if ((mMode & MODE_MASK_PICKER) == 0) {
936 Intent intent = new Intent(Intent.ACTION_VIEW,
937 ContentUris.withAppendedId(People.CONTENT_URI, id));
938 startActivity(intent);
939 } else if (mMode == MODE_PICK_CONTACT
940 || mMode == MODE_PICK_OR_CREATE_CONTACT) {
941 Uri uri = ContentUris.withAppendedId(People.CONTENT_URI, id);
942 if (mCreateShortcut) {
943 // Subtract one if we have Create Contact at the top
944 Cursor c = (Cursor) mAdapter.getItem(position
945 - (mMode == MODE_PICK_OR_CREATE_CONTACT? 1:0));
946 returnPickerResult(c.getString(NAME_COLUMN_INDEX), uri);
948 returnPickerResult(null, uri);
950 } else if (mMode == MODE_PICK_PHONE) {
951 setResult(RESULT_OK, new Intent().setData(
952 ContentUris.withAppendedId(Phones.CONTENT_URI, id)));
954 } else if (mMode == MODE_PICK_POSTAL) {
955 setResult(RESULT_OK, new Intent().setData(
956 ContentUris.withAppendedId(ContactMethods.CONTENT_URI, id)));
959 } else if ((mMode & MODE_MASK_CREATE_NEW) == MODE_MASK_CREATE_NEW
961 Intent newContact = new Intent(Intents.Insert.ACTION, People.CONTENT_URI);
962 startActivityForResult(newContact, SUBACTIVITY_NEW_CONTACT);
968 private void returnPickerResult(String name, Uri uri) {
969 final Intent intent = new Intent();
971 if (mCreateShortcut) {
972 Intent shortcutIntent = new Intent(Intent.ACTION_VIEW, uri);
973 shortcutIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
974 intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
975 intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, name);
976 final Bitmap icon = People.loadContactPhoto(this, uri, 0, null);
978 intent.putExtra(Intent.EXTRA_SHORTCUT_ICON, icon);
980 intent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE,
981 Intent.ShortcutIconResource.fromContext(this,
982 R.drawable.ic_launcher_contacts));
984 setResult(RESULT_OK, intent);
986 setResult(RESULT_OK, intent.setData(uri));
991 String[] getProjection() {
994 case MODE_ALL_CONTACTS:
995 case MODE_WITH_PHONES:
996 case MODE_PICK_CONTACT:
997 case MODE_PICK_OR_CREATE_CONTACT:
1001 case MODE_INSERT_OR_EDIT_CONTACT:
1002 return CONTACTS_PROJECTION;
1004 case MODE_STREQUENT:
1005 return STREQUENT_PROJECTION;
1007 case MODE_PICK_PHONE:
1008 return PHONES_PROJECTION;
1010 case MODE_PICK_POSTAL:
1011 return CONTACT_METHODS_PROJECTION;
1016 private Uri getPeopleFilterUri(String filter) {
1017 if (!TextUtils.isEmpty(filter)) {
1018 return Uri.withAppendedPath(People.CONTENT_FILTER_URI, Uri.encode(filter));
1020 return People.CONTENT_URI;
1025 mAdapter.setLoading(true);
1027 // Cancel any pending queries
1028 mQueryHandler.cancelOperation(QUERY_TOKEN);
1030 // Kick off the new query
1033 mQueryHandler.startQuery(QUERY_TOKEN, null,
1034 mGroupUri, CONTACTS_PROJECTION, null, null, SORT_ORDER);
1037 case MODE_ALL_CONTACTS:
1038 case MODE_PICK_CONTACT:
1039 case MODE_PICK_OR_CREATE_CONTACT:
1040 case MODE_INSERT_OR_EDIT_CONTACT:
1041 mQueryHandler.startQuery(QUERY_TOKEN, null, People.CONTENT_URI, CONTACTS_PROJECTION,
1042 null, null, SORT_ORDER);
1045 case MODE_WITH_PHONES:
1046 mQueryHandler.startQuery(QUERY_TOKEN, null, People.CONTENT_URI, CONTACTS_PROJECTION,
1047 People.PRIMARY_PHONE_ID + " IS NOT NULL", null, SORT_ORDER);
1051 mQuery = getIntent().getStringExtra(SearchManager.QUERY);
1052 mQueryHandler.startQuery(QUERY_TOKEN, null, getPeopleFilterUri(mQuery),
1053 CONTACTS_PROJECTION, null, null, SORT_ORDER);
1058 mQueryHandler.startQuery(QUERY_TOKEN, null, People.CONTENT_URI, CONTACTS_PROJECTION,
1059 People.STARRED + "=1", null, SORT_ORDER);
1063 mQueryHandler.startQuery(QUERY_TOKEN, null, People.CONTENT_URI, CONTACTS_PROJECTION,
1064 People.TIMES_CONTACTED + " > 0", null,
1065 People.TIMES_CONTACTED + " DESC, " + NAME_COLUMN + " ASC");
1068 case MODE_STREQUENT:
1069 mQueryHandler.startQuery(QUERY_TOKEN, null,
1070 Uri.withAppendedPath(People.CONTENT_URI, "strequent"), STREQUENT_PROJECTION,
1074 case MODE_PICK_PHONE:
1075 mQueryHandler.startQuery(QUERY_TOKEN, null, Phones.CONTENT_URI, PHONES_PROJECTION,
1076 null, null, SORT_ORDER);
1079 case MODE_PICK_POSTAL:
1080 mQueryHandler.startQuery(QUERY_TOKEN, null, ContactMethods.CONTENT_URI,
1081 CONTACT_METHODS_PROJECTION,
1082 ContactMethods.KIND + "=" + Contacts.KIND_POSTAL, null, SORT_ORDER);
1088 * Called from a background thread to do the filter and return the resulting cursor.
1090 * @param filter the text that was entered to filter on
1091 * @return a cursor with the results of the filter
1093 Cursor doFilter(String filter) {
1094 final ContentResolver resolver = getContentResolver();
1099 if (TextUtils.isEmpty(filter)) {
1102 uri = Uri.withAppendedPath(mGroupFilterUri, Uri.encode(filter));
1104 return resolver.query(uri, CONTACTS_PROJECTION, null, null, SORT_ORDER);
1107 case MODE_ALL_CONTACTS:
1108 case MODE_PICK_CONTACT:
1109 case MODE_PICK_OR_CREATE_CONTACT:
1110 case MODE_INSERT_OR_EDIT_CONTACT: {
1111 return resolver.query(getPeopleFilterUri(filter), CONTACTS_PROJECTION, null, null,
1115 case MODE_WITH_PHONES: {
1116 return resolver.query(getPeopleFilterUri(filter), CONTACTS_PROJECTION,
1117 People.PRIMARY_PHONE_ID + " IS NOT NULL", null, SORT_ORDER);
1120 case MODE_STARRED: {
1121 return resolver.query(getPeopleFilterUri(filter), CONTACTS_PROJECTION,
1122 People.STARRED + "=1", null, SORT_ORDER);
1125 case MODE_FREQUENT: {
1126 return resolver.query(getPeopleFilterUri(filter), CONTACTS_PROJECTION,
1127 People.TIMES_CONTACTED + " > 0", null,
1128 People.TIMES_CONTACTED + " DESC, " + NAME_COLUMN + " ASC");
1131 case MODE_STREQUENT: {
1133 if (!TextUtils.isEmpty(filter)) {
1134 uri = Uri.withAppendedPath(People.CONTENT_URI, "strequent/filter/"
1135 + Uri.encode(filter));
1137 uri = Uri.withAppendedPath(People.CONTENT_URI, "strequent");
1139 return resolver.query(uri, STREQUENT_PROJECTION, null, null, null);
1142 case MODE_PICK_PHONE: {
1144 if (!TextUtils.isEmpty(filter)) {
1145 uri = Uri.withAppendedPath(Phones.CONTENT_URI, "filter_name/"
1146 + Uri.encode(filter));
1148 uri = Phones.CONTENT_URI;
1150 return resolver.query(uri, PHONES_PROJECTION, null, null, SORT_ORDER);
1153 throw new UnsupportedOperationException("filtering not allowed in mode " + mMode);
1157 * Calls the currently selected list item.
1158 * @return true if the call was initiated, false otherwise
1160 boolean callSelection() {
1161 ListView list = getListView();
1162 if (list.hasFocus()) {
1163 Cursor cursor = (Cursor) list.getSelectedItem();
1164 if (cursor != null) {
1165 long phoneId = cursor.getLong(PRIMARY_PHONE_ID_COLUMN_INDEX);
1167 // There is no phone number.
1171 Uri uri = ContentUris.withAppendedId(Phones.CONTENT_URI, phoneId);
1172 Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED, uri);
1173 startActivity(intent);
1182 * Signal an error to the user.
1184 void signalError() {
1185 //TODO play an error beep or something...
1188 Cursor getItemForView(View view) {
1189 ListView listView = getListView();
1190 int index = listView.getPositionForView(view);
1194 return (Cursor) listView.getAdapter().getItem(index);
1197 private void setGroupEntries(AlertDialog.Builder builder) {
1198 boolean syncEverything;
1199 // For now we only support a single account and the UI doesn't know what
1200 // the account name is, so we're using a global setting for SYNC_EVERYTHING.
1201 // Some day when we add multiple accounts to the UI this should use the per
1203 String value = Contacts.Settings.getSetting(getContentResolver(), null,
1204 Contacts.Settings.SYNC_EVERYTHING);
1205 if (value == null) {
1206 // If nothing is set yet we default to syncing everything
1207 syncEverything = true;
1209 syncEverything = !TextUtils.isEmpty(value) && !"0".equals(value);
1213 if (!syncEverything) {
1214 cursor = getContentResolver().query(Groups.CONTENT_URI, GROUPS_PROJECTION,
1215 Groups.SHOULD_SYNC + " != 0", null, Groups.DEFAULT_SORT_ORDER);
1217 cursor = getContentResolver().query(Groups.CONTENT_URI, GROUPS_PROJECTION,
1218 null, null, Groups.DEFAULT_SORT_ORDER);
1221 ArrayList<CharSequence> groups = new ArrayList<CharSequence>();
1222 ArrayList<CharSequence> prefStrings = new ArrayList<CharSequence>();
1225 groups.add(DISPLAY_GROUP_INDEX_ALL_CONTACTS, getString(R.string.showAllGroups));
1226 prefStrings.add("");
1228 // Add Contacts with phones
1229 groups.add(DISPLAY_GROUP_INDEX_ALL_CONTACTS_WITH_PHONES,
1230 getString(R.string.groupNameWithPhones));
1231 prefStrings.add(GROUP_WITH_PHONES);
1234 int currentIndex = DISPLAY_GROUP_INDEX_ALL_CONTACTS;
1235 while (cursor.moveToNext()) {
1236 String systemId = cursor.getString(GROUPS_COLUMN_INDEX_SYSTEM_ID);
1237 String name = cursor.getString(GROUPS_COLUMN_INDEX_NAME);
1238 if (cursor.isNull(GROUPS_COLUMN_INDEX_SYSTEM_ID)
1239 && !Groups.GROUP_MY_CONTACTS.equals(systemId)) {
1240 // All groups that aren't My Contacts, since that one is localized on the phone
1242 if (name.equals(mDisplayInfo)) {
1247 // The My Contacts group
1248 groups.add(DISPLAY_GROUP_INDEX_MY_CONTACTS,
1249 getString(R.string.groupNameMyContacts));
1250 if (mDisplayType == DISPLAY_TYPE_SYSTEM_GROUP
1251 && Groups.GROUP_MY_CONTACTS.equals(mDisplayInfo)) {
1252 currentIndex = DISPLAY_GROUP_INDEX_MY_CONTACTS;
1254 mDisplayGroupsIncludesMyContacts = true;
1257 if (mMode == MODE_ALL_CONTACTS) {
1258 currentIndex = DISPLAY_GROUP_INDEX_ALL_CONTACTS;
1259 } else if (mMode == MODE_WITH_PHONES) {
1260 currentIndex = DISPLAY_GROUP_INDEX_ALL_CONTACTS_WITH_PHONES;
1262 mDisplayGroups = groups.toArray(new CharSequence[groups.size()]);
1263 builder.setSingleChoiceItems(mDisplayGroups,
1264 currentIndex, this);
1265 mDisplayGroupOriginalSelection = currentIndex;
1271 private static final class QueryHandler extends AsyncQueryHandler {
1272 private final WeakReference<ContactsListActivity> mActivity;
1274 public QueryHandler(Context context) {
1275 super(context.getContentResolver());
1276 mActivity = new WeakReference<ContactsListActivity>((ContactsListActivity) context);
1280 protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
1281 final ContactsListActivity activity = mActivity.get();
1282 if (activity != null && !activity.isFinishing()) {
1283 activity.mAdapter.setLoading(false);
1284 activity.getListView().clearTextFilter();
1285 activity.mAdapter.changeCursor(cursor);
1287 // Now that the cursor is populated again, it's possible to restore the list state
1288 if (activity.mListState != null) {
1289 activity.mList.onRestoreInstanceState(activity.mListState);
1290 if (activity.mListHasFocus) {
1291 activity.mList.requestFocus();
1293 activity.mListHasFocus = false;
1294 activity.mListState = null;
1302 final static class ContactListItemCache {
1303 public TextView nameView;
1304 public CharArrayBuffer nameBuffer = new CharArrayBuffer(128);
1305 public TextView labelView;
1306 public CharArrayBuffer labelBuffer = new CharArrayBuffer(128);
1307 public TextView numberView;
1308 public CharArrayBuffer numberBuffer = new CharArrayBuffer(128);
1309 public ImageView presenceView;
1312 private final class ContactItemListAdapter extends ResourceCursorAdapter
1313 implements SectionIndexer {
1315 private AlphabetIndexer mIndexer;
1316 private String mAlphabet;
1317 private boolean mLoading = true;
1318 private CharSequence mUnknownNameText;
1319 private CharSequence[] mLocalizedLabels;
1321 public ContactItemListAdapter(Context context) {
1322 super(context, R.layout.contacts_list_item, null, false);
1324 mAlphabet = context.getString(com.android.internal.R.string.fast_scroll_alphabet);
1326 mUnknownNameText = context.getText(android.R.string.unknownName);
1328 case MODE_PICK_POSTAL:
1329 mLocalizedLabels = EditContactActivity.getLabelsForKind(mContext,
1330 Contacts.KIND_POSTAL);
1333 mLocalizedLabels = EditContactActivity.getLabelsForKind(mContext,
1334 Contacts.KIND_PHONE);
1340 * Callback on the UI thread when the content observer on the backing cursor fires.
1341 * Instead of calling requery we need to do an async query so that the requery doesn't
1342 * block the UI thread for a long time.
1345 protected void onContentChanged() {
1346 CharSequence constraint = getListView().getTextFilter();
1347 if (!TextUtils.isEmpty(constraint)) {
1348 // Reset the filter state then start an async filter operation
1349 Filter filter = getFilter();
1350 filter.filter(constraint);
1352 // Start an async query
1357 public void setLoading(boolean loading) {
1362 public boolean isEmpty() {
1363 if ((mMode & MODE_MASK_CREATE_NEW) == MODE_MASK_CREATE_NEW) {
1364 // This mode mask adds a header and we always want it to show up, even
1365 // if the list is empty, so always claim the list is not empty.
1369 // We don't want the empty state to show when loading.
1372 return super.isEmpty();
1378 public View newView(Context context, Cursor cursor, ViewGroup parent) {
1379 final View view = super.newView(context, cursor, parent);
1381 final ContactListItemCache cache = new ContactListItemCache();
1382 cache.nameView = (TextView) view.findViewById(R.id.name);
1383 cache.labelView = (TextView) view.findViewById(R.id.label);
1384 cache.numberView = (TextView) view.findViewById(R.id.number);
1385 cache.presenceView = (ImageView) view.findViewById(R.id.presence);
1392 public void bindView(View view, Context context, Cursor cursor) {
1393 final ContactListItemCache cache = (ContactListItemCache) view.getTag();
1396 cursor.copyStringToBuffer(NAME_COLUMN_INDEX, cache.nameBuffer);
1397 int size = cache.nameBuffer.sizeCopied;
1399 cache.nameView.setText(cache.nameBuffer.data, 0, size);
1401 cache.nameView.setText(mUnknownNameText);
1404 // Set the phone number
1405 TextView numberView = cache.numberView;
1406 TextView labelView = cache.labelView;
1407 cursor.copyStringToBuffer(NUMBER_COLUMN_INDEX, cache.numberBuffer);
1408 size = cache.numberBuffer.sizeCopied;
1410 numberView.setText(cache.numberBuffer.data, 0, size);
1411 numberView.setVisibility(View.VISIBLE);
1412 labelView.setVisibility(View.VISIBLE);
1414 numberView.setVisibility(View.GONE);
1415 labelView.setVisibility(View.GONE);
1419 if (!cursor.isNull(TYPE_COLUMN_INDEX)) {
1420 int type = cursor.getInt(TYPE_COLUMN_INDEX);
1422 if (type != People.Phones.TYPE_CUSTOM) {
1424 labelView.setText(mLocalizedLabels[type - 1]);
1425 } catch (ArrayIndexOutOfBoundsException e) {
1426 labelView.setText(mLocalizedLabels[People.Phones.TYPE_HOME - 1]);
1429 cursor.copyStringToBuffer(LABEL_COLUMN_INDEX, cache.labelBuffer);
1430 // Don't check size, if it's zero just don't show anything
1431 labelView.setText(cache.labelBuffer.data, 0, cache.labelBuffer.sizeCopied);
1434 // There is no label, hide the the view
1435 labelView.setVisibility(View.GONE);
1438 // Set the proper icon (star or presence or nothing)
1439 ImageView presenceView = cache.presenceView;
1440 if (mMode != MODE_STREQUENT) {
1441 if ((mMode & MODE_MASK_NO_PRESENCE) == 0) {
1443 if (!cursor.isNull(SERVER_STATUS_COLUMN_INDEX)) {
1444 serverStatus = cursor.getInt(SERVER_STATUS_COLUMN_INDEX);
1445 presenceView.setImageResource(
1446 Presence.getPresenceIconResourceId(serverStatus));
1447 presenceView.setVisibility(View.VISIBLE);
1449 presenceView.setVisibility(View.GONE);
1452 presenceView.setVisibility(View.GONE);
1455 if (cursor.getInt(STARRED_COLUMN_INDEX) != 0) {
1456 presenceView.setImageResource(R.drawable.star_on);
1457 presenceView.setVisibility(View.VISIBLE);
1459 presenceView.setVisibility(View.GONE);
1465 public void changeCursor(Cursor cursor) {
1466 super.changeCursor(cursor);
1467 updateIndexer(cursor);
1470 private void updateIndexer(Cursor cursor) {
1471 if (mIndexer == null) {
1472 mIndexer = new AlphabetIndexer(cursor, NAME_COLUMN_INDEX, mAlphabet);
1474 mIndexer.setCursor(cursor);
1479 * Run the query on a helper thread. Beware that this code does not run
1480 * on the main UI thread!
1483 public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
1484 return doFilter(constraint.toString());
1487 public Object [] getSections() {
1488 if (mMode == MODE_STREQUENT) {
1489 return new String[] { " " };
1491 return mIndexer.getSections();
1495 public int getPositionForSection(int sectionIndex) {
1496 if (mMode == MODE_STREQUENT) {
1500 if (mIndexer == null) {
1501 Cursor cursor = mAdapter.getCursor();
1502 if (cursor == null) {
1503 // No cursor, the section doesn't exist so just return 0
1506 mIndexer = new AlphabetIndexer(cursor, NAME_COLUMN_INDEX, mAlphabet);
1509 return mIndexer.getPositionForSection(sectionIndex);
1512 public int getSectionForPosition(int position) {