FileDocCategorySizeDatePackage
ContactsListActivity.javaAPI DocAndroid 1.5 API72131Wed May 06 22:42:44 BST 2009com.android.contacts

ContactsListActivity

public final class ContactsListActivity extends android.app.ListActivity implements View.OnCreateContextMenuListener, DialogInterface.OnClickListener
Displays a list of contacts. Usually is embedded into the ContactsActivity.

Fields Summary
private static final String
TAG
private static final String
LIST_STATE_KEY
private static final String
FOCUS_KEY
static final int
MENU_ITEM_VIEW_CONTACT
static final int
MENU_ITEM_CALL
static final int
MENU_ITEM_EDIT_BEFORE_CALL
static final int
MENU_ITEM_SEND_SMS
static final int
MENU_ITEM_SEND_IM
static final int
MENU_ITEM_EDIT
static final int
MENU_ITEM_DELETE
static final int
MENU_ITEM_TOGGLE_STAR
public static final int
MENU_SEARCH
public static final int
MENU_DIALER
public static final int
MENU_NEW_CONTACT
public static final int
MENU_DISPLAY_GROUP
private static final int
SUBACTIVITY_NEW_CONTACT
static final int
MODE_MASK_PICKER
Mask for picker mode
static final int
MODE_MASK_NO_PRESENCE
Mask for no presence mode
static final int
MODE_MASK_NO_FILTER
Mask for enabling list filtering
static final int
MODE_MASK_CREATE_NEW
Mask for having a "create new contact" header in the list
static final int
MODE_MASK_SHOW_PHOTOS
Mask for showing photos in the list
static final int
MODE_UNKNOWN
Unknown mode
static final int
MODE_GROUP
Show members of the "Contacts" group
static final int
MODE_ALL_CONTACTS
Show all contacts sorted alphabetically
static final int
MODE_WITH_PHONES
Show all contacts with phone numbers, sorted alphabetically
static final int
MODE_STARRED
Show all starred contacts
static final int
MODE_FREQUENT
Show frequently contacted contacts
static final int
MODE_STREQUENT
Show starred and the frequent
static final int
MODE_PICK_CONTACT
Show all contacts and pick them when clicking
static final int
MODE_PICK_OR_CREATE_CONTACT
Show all contacts as well as the option to create a new one
static final int
MODE_INSERT_OR_EDIT_CONTACT
Show all contacts and pick them when clicking, and allow creating a new contact
static final int
MODE_PICK_PHONE
Show all phone numbers and pick them when clicking
static final int
MODE_PICK_POSTAL
Show all postal addresses and pick them when clicking
static final int
MODE_QUERY
Run a search query
static final int
MODE_QUERY_PICK_TO_VIEW
Run a search query in PICK mode, but that still launches to VIEW
static final int
DEFAULT_MODE
static final String
PREF_DISPLAY_TYPE
The type of data to display in the main contacts list.
static final int
DISPLAY_TYPE_UNKNOWN
Unknown display type.
static final int
DISPLAY_TYPE_ALL
Display all contacts
static final int
DISPLAY_TYPE_ALL_WITH_PHONES
Display all contacts that have phone numbers
static final int
DISPLAY_TYPE_SYSTEM_GROUP
Display a system group
static final int
DISPLAY_TYPE_USER_GROUP
Display a user group
static final String
PREF_DISPLAY_INFO
Info about what to display. If {@link #PREF_DISPLAY_TYPE} is {@link #DISPLAY_TYPE_SYSTEM_GROUP} then this will be the system id. If {@link #PREF_DISPLAY_TYPE} is {@link #DISPLAY_TYPE_USER_GROUP} then this will be the group name.
static final String
NAME_COLUMN
static final String
SORT_STRING
static final String[]
CONTACTS_PROJECTION
static final String[]
SIMPLE_CONTACTS_PROJECTION
static final String[]
STREQUENT_PROJECTION
static final String[]
PHONES_PROJECTION
static final String[]
CONTACT_METHODS_PROJECTION
static final int
ID_COLUMN_INDEX
static final int
NAME_COLUMN_INDEX
static final int
NUMBER_COLUMN_INDEX
static final int
DATA_COLUMN_INDEX
static final int
TYPE_COLUMN_INDEX
static final int
LABEL_COLUMN_INDEX
static final int
STARRED_COLUMN_INDEX
static final int
PRIMARY_PHONE_ID_COLUMN_INDEX
static final int
PRIMARY_EMAIL_ID_COLUMN_INDEX
static final int
SERVER_STATUS_COLUMN_INDEX
static final int
PHOTO_COLUMN_INDEX
static final int
SORT_STRING_INDEX
static final int
PHONES_PERSON_ID_INDEX
static final int
SIMPLE_CONTACTS_PERSON_ID_INDEX
static final int
DISPLAY_GROUP_INDEX_ALL_CONTACTS
static final int
DISPLAY_GROUP_INDEX_ALL_CONTACTS_WITH_PHONES
static final int
DISPLAY_GROUP_INDEX_MY_CONTACTS
private static final int
QUERY_TOKEN
private static final String[]
GROUPS_PROJECTION
private static final int
GROUPS_COLUMN_INDEX_SYSTEM_ID
private static final int
GROUPS_COLUMN_INDEX_NAME
static final String
GROUP_WITH_PHONES
ContactItemListAdapter
mAdapter
int
mMode
private String
mDisplayInfo
private int
mDisplayType
private CharSequence[]
mDisplayGroups
private boolean
mDisplayGroupsIncludesMyContacts
private int
mDisplayGroupOriginalSelection
private int
mDisplayGroupCurrentSelection
private QueryHandler
mQueryHandler
private String
mQuery
private android.net.Uri
mGroupFilterUri
private android.net.Uri
mGroupUri
private boolean
mJustCreated
private boolean
mSyncEnabled
private int
mQueryPersonIdIndex
Cursor row index that holds reference back to {@link People#_ID}, such as {@link ContactMethods#PERSON_ID}. Used when responding to a {@link Intent#ACTION_SEARCH} in mode {@link #MODE_QUERY_PICK_TO_VIEW}.
private android.os.Parcelable
mListState
Used to keep track of the scroll state of the list.
private boolean
mListHasFocus
private boolean
mCreateShortcut
private boolean
mDefaultMode
private int
mQueryMode
Internal query type when in mode {@link #MODE_QUERY_PICK_TO_VIEW}.
private static final int
QUERY_MODE_NONE
private static final int
QUERY_MODE_MAILTO
private static final int
QUERY_MODE_TEL
private String
mQueryData
Data to use when in mode {@link #MODE_QUERY_PICK_TO_VIEW}. Usually provided by scheme-specific part of incoming {@link Intent#getData()}.
Constructors Summary
Methods Summary
private voidbuildSystemGroupUris(java.lang.String systemId)
Builds the URIs to query when displaying a system group

param
systemId the system group's ID

        mGroupFilterUri = Uri.parse("content://contacts/groups/system_id/" + systemId
                + "/members/filter/");
        mGroupUri = Uri.parse("content://contacts/groups/system_id/" + systemId + "/members");
    
private voidbuildUserGroupUris(java.lang.String groupName)
Builds the URIs to query when displaying a user group

param
groupName the group being displayed

        mGroupFilterUri = Uri.parse("content://contacts/groups/name/" + groupName
                + "/members/filter/");
        mGroupUri = Uri.parse("content://contacts/groups/name/" + groupName + "/members");
    
booleancallSelection()
Calls the currently selected list item.

return
true if the call was initiated, false otherwise

        ListView list = getListView();
        if (list.hasFocus()) {
            Cursor cursor = (Cursor) list.getSelectedItem();
            if (cursor != null) {
                long phoneId = cursor.getLong(PRIMARY_PHONE_ID_COLUMN_INDEX);
                if (phoneId == 0) {
                    // There is no phone number.
                    signalError();
                    return false;
                }
                Uri uri = ContentUris.withAppendedId(Phones.CONTENT_URI, phoneId);
                Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED, uri);
                startActivity(intent);
                return true;
            }
        }

        return false;
    
android.database.CursordoFilter(java.lang.String filter)
Called from a background thread to do the filter and return the resulting cursor.

param
filter the text that was entered to filter on
return
a cursor with the results of the filter

        final ContentResolver resolver = getContentResolver();

        switch (mMode) {
            case MODE_GROUP: {
                Uri uri;
                if (TextUtils.isEmpty(filter)) {
                    uri = mGroupUri;
                } else {
                    uri = Uri.withAppendedPath(mGroupFilterUri, Uri.encode(filter));
                }
                return resolver.query(uri, CONTACTS_PROJECTION, null, null,
                        getSortOrder(CONTACTS_PROJECTION));
            }

            case MODE_ALL_CONTACTS:
            case MODE_PICK_CONTACT:
            case MODE_PICK_OR_CREATE_CONTACT:
            case MODE_INSERT_OR_EDIT_CONTACT: {
                return resolver.query(getPeopleFilterUri(filter), CONTACTS_PROJECTION, null, null,
                        getSortOrder(CONTACTS_PROJECTION));
            }

            case MODE_WITH_PHONES: {
                return resolver.query(getPeopleFilterUri(filter), CONTACTS_PROJECTION,
                        People.PRIMARY_PHONE_ID + " IS NOT NULL", null,
                        getSortOrder(CONTACTS_PROJECTION));
            }

            case MODE_STARRED: {
                return resolver.query(getPeopleFilterUri(filter), CONTACTS_PROJECTION,
                        People.STARRED + "=1", null, getSortOrder(CONTACTS_PROJECTION));
            }

            case MODE_FREQUENT: {
                return resolver.query(getPeopleFilterUri(filter), CONTACTS_PROJECTION,
                        People.TIMES_CONTACTED + " > 0", null,
                        People.TIMES_CONTACTED + " DESC, " + getSortOrder(CONTACTS_PROJECTION));
                
            }

            case MODE_STREQUENT: {
                Uri uri;
                if (!TextUtils.isEmpty(filter)) {
                    uri = Uri.withAppendedPath(People.CONTENT_URI, "strequent/filter/"
                            + Uri.encode(filter));
                } else {
                    uri = Uri.withAppendedPath(People.CONTENT_URI, "strequent");
                }
                return resolver.query(uri, STREQUENT_PROJECTION, null, null, null);
            }

            case MODE_PICK_PHONE: {
                Uri uri;
                if (!TextUtils.isEmpty(filter)) {
                    uri = Uri.withAppendedPath(Phones.CONTENT_URI, "filter_name/"
                            + Uri.encode(filter));
                } else {
                    uri = Phones.CONTENT_URI;
                }
                return resolver.query(uri, PHONES_PROJECTION, null, null,
                        getSortOrder(PHONES_PROJECTION));
            }
        }
        throw new UnsupportedOperationException("filtering not allowed in mode " + mMode);
    
android.database.CursorgetItemForView(android.view.View view)

        ListView listView = getListView();
        int index = listView.getPositionForView(view);
        if (index < 0) {
            return null;
        }
        return (Cursor) listView.getAdapter().getItem(index);
    
private android.net.UrigetPeopleFilterUri(java.lang.String filter)

        if (!TextUtils.isEmpty(filter)) {
            return Uri.withAppendedPath(People.CONTENT_FILTER_URI, Uri.encode(filter));
        } else {
            return People.CONTENT_URI;
        }
    
java.lang.String[]getProjection()

        switch (mMode) {
            case MODE_GROUP:
            case MODE_ALL_CONTACTS:
            case MODE_WITH_PHONES:
            case MODE_PICK_CONTACT:
            case MODE_PICK_OR_CREATE_CONTACT:
            case MODE_QUERY:
            case MODE_STARRED:
            case MODE_FREQUENT:
            case MODE_INSERT_OR_EDIT_CONTACT:
                return CONTACTS_PROJECTION;

            case MODE_STREQUENT:
                return STREQUENT_PROJECTION;

            case MODE_PICK_PHONE:
                return PHONES_PROJECTION;

            case MODE_PICK_POSTAL:
                return CONTACT_METHODS_PROJECTION;
        }
        return null;
    
private static java.lang.StringgetSortOrder(java.lang.String[] projectionType)

        if (Locale.getDefault().equals(Locale.JAPAN) &&
                projectionType == CONTACTS_PROJECTION) {
            return SORT_STRING + " ASC";
        } else {
            return NAME_COLUMN + " COLLATE LOCALIZED ASC";
        }
    
protected voidonActivityResult(int requestCode, int resultCode, android.content.Intent data)

        switch (requestCode) {
            case SUBACTIVITY_NEW_CONTACT:
                if (resultCode == RESULT_OK) {
                    // Contact was created, pass it back
                    returnPickerResult(data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME),
                            data.getData());
                }
        }
    
public voidonClick(android.content.DialogInterface dialogInterface, int which)

        if (which == DialogInterface.BUTTON_POSITIVE) {
            // The OK button was pressed
            if (mDisplayGroupOriginalSelection != mDisplayGroupCurrentSelection) {
                // Set the group to display
                if (mDisplayGroupCurrentSelection == DISPLAY_GROUP_INDEX_ALL_CONTACTS) {
                    // Display all
                    mDisplayType = DISPLAY_TYPE_ALL;
                    mDisplayInfo = null;
                } else if (mDisplayGroupCurrentSelection
                        == DISPLAY_GROUP_INDEX_ALL_CONTACTS_WITH_PHONES) {
                    // Display all with phone numbers
                    mDisplayType = DISPLAY_TYPE_ALL_WITH_PHONES;
                    mDisplayInfo = null;
                } else if (mDisplayGroupsIncludesMyContacts &&
                        mDisplayGroupCurrentSelection == DISPLAY_GROUP_INDEX_MY_CONTACTS) {
                    mDisplayType = DISPLAY_TYPE_SYSTEM_GROUP;
                    mDisplayInfo = Groups.GROUP_MY_CONTACTS;
                } else {
                    mDisplayType = DISPLAY_TYPE_USER_GROUP;
                    mDisplayInfo = mDisplayGroups[mDisplayGroupCurrentSelection].toString();
                }

                // Save the changes to the preferences
                SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
                prefs.edit()
                        .putInt(PREF_DISPLAY_TYPE, mDisplayType)
                        .putString(PREF_DISPLAY_INFO, mDisplayInfo)
                        .commit();

                // Update the display state
                updateGroup();
            }
        } else {
            // A list item was selected, cache the position
            mDisplayGroupCurrentSelection = which;
        }
    
public booleanonContextItemSelected(android.view.MenuItem item)

        AdapterView.AdapterContextMenuInfo info;
        try {
             info = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo();
        } catch (ClassCastException e) {
            Log.e(TAG, "bad menuInfo", e);
            return false;
        }

        Cursor cursor = (Cursor) getListAdapter().getItem(info.position);

        switch (item.getItemId()) {
            case MENU_ITEM_TOGGLE_STAR: {
                // Toggle the star
                ContentValues values = new ContentValues(1);
                values.put(People.STARRED, cursor.getInt(STARRED_COLUMN_INDEX) == 0 ? 1 : 0);
                Uri personUri = ContentUris.withAppendedId(People.CONTENT_URI,
                        cursor.getInt(ID_COLUMN_INDEX));
                getContentResolver().update(personUri, values, null, null);
                return true;
            }

            case MENU_ITEM_DELETE: {
                // Get confirmation
                Uri uri = ContentUris.withAppendedId(People.CONTENT_URI,
                        cursor.getLong(ID_COLUMN_INDEX));
                //TODO make this dialog persist across screen rotations
                new AlertDialog.Builder(ContactsListActivity.this)
                    .setTitle(R.string.deleteConfirmation_title)
                    .setIcon(android.R.drawable.ic_dialog_alert)
                    .setMessage(R.string.deleteConfirmation)
                    .setNegativeButton(android.R.string.cancel, null)
                    .setPositiveButton(android.R.string.ok, new DeleteClickListener(uri))
                    .show();
                return true;
            }
        }

        return super.onContextItemSelected(item);
    
protected voidonCreate(android.os.Bundle icicle)

        super.onCreate(icicle);

        // Resolve the intent
        final Intent intent = getIntent();

        // Allow the title to be set to a custom String using an extra on the intent
        String title = intent.getStringExtra(Contacts.Intents.UI.TITLE_EXTRA_KEY);
        if (title != null) {
            setTitle(title);
        }
        
        final String action = intent.getAction();
        mMode = MODE_UNKNOWN;
        
        setContentView(R.layout.contacts_list_content);

        if (UI.LIST_DEFAULT.equals(action)) {
            mDefaultMode = true;
            // When mDefaultMode is true the mode is set in onResume(), since the preferneces
            // activity may change it whenever this activity isn't running
        } else if (UI.LIST_GROUP_ACTION.equals(action)) {
            mMode = MODE_GROUP;
            String groupName = intent.getStringExtra(UI.GROUP_NAME_EXTRA_KEY);
            if (TextUtils.isEmpty(groupName)) {
                finish();
                return;
            }
            buildUserGroupUris(groupName);
        } else if (UI.LIST_ALL_CONTACTS_ACTION.equals(action)) {
            mMode = MODE_ALL_CONTACTS;
        } else if (UI.LIST_STARRED_ACTION.equals(action)) {
            mMode = MODE_STARRED;
        } else if (UI.LIST_FREQUENT_ACTION.equals(action)) {
            mMode = MODE_FREQUENT;
        } else if (UI.LIST_STREQUENT_ACTION.equals(action)) {
            mMode = MODE_STREQUENT;
        } else if (UI.LIST_CONTACTS_WITH_PHONES_ACTION.equals(action)) {
            mMode = MODE_WITH_PHONES;
        } else if (Intent.ACTION_PICK.equals(action)) {
            // XXX These should be showing the data from the URI given in
            // the Intent.
            final String type = intent.resolveType(this);
            if (People.CONTENT_TYPE.equals(type)) {
                mMode = MODE_PICK_CONTACT;
            } else if (Phones.CONTENT_TYPE.equals(type)) {
                mMode = MODE_PICK_PHONE;
            } else if (ContactMethods.CONTENT_POSTAL_TYPE.equals(type)) {
                mMode = MODE_PICK_POSTAL;
            }
        } else if (Intent.ACTION_CREATE_SHORTCUT.equals(action)) {
            mMode = MODE_PICK_OR_CREATE_CONTACT;
            mCreateShortcut = true;
        } else if (Intent.ACTION_GET_CONTENT.equals(action)) {
            final String type = intent.resolveType(this);
            if (People.CONTENT_ITEM_TYPE.equals(type)) {
                mMode = MODE_PICK_OR_CREATE_CONTACT;
            } else if (Phones.CONTENT_ITEM_TYPE.equals(type)) {
                mMode = MODE_PICK_PHONE;
            } else if (ContactMethods.CONTENT_POSTAL_ITEM_TYPE.equals(type)) {
                mMode = MODE_PICK_POSTAL;
            }
        } else if (Intent.ACTION_INSERT_OR_EDIT.equals(action)) {
            mMode = MODE_INSERT_OR_EDIT_CONTACT;
        } else if (Intent.ACTION_SEARCH.equals(action)) {
            // See if the suggestion was clicked with a search action key (call button)
            if ("call".equals(intent.getStringExtra(SearchManager.ACTION_MSG))) {
                String query = intent.getStringExtra(SearchManager.QUERY);
                if (!TextUtils.isEmpty(query)) {
                    Intent newIntent = new Intent(Intent.ACTION_CALL_PRIVILEGED,
                            Uri.fromParts("tel", query, null));
                    startActivity(newIntent);
                }
                finish();
                return;
            }
            
            // See if search request has extras to specify query
            if (intent.hasExtra(Insert.EMAIL)) {
                mMode = MODE_QUERY_PICK_TO_VIEW;
                mQueryMode = QUERY_MODE_MAILTO;
                mQueryData = intent.getStringExtra(Insert.EMAIL);
            } else if (intent.hasExtra(Insert.PHONE)) {
                mMode = MODE_QUERY_PICK_TO_VIEW;
                mQueryMode = QUERY_MODE_TEL;
                mQueryData = intent.getStringExtra(Insert.PHONE);
            } else {
                // Otherwise handle the more normal search case
                mMode = MODE_QUERY;
            }

        // Since this is the filter activity it receives all intents
        // dispatched from the SearchManager for security reasons
        // so we need to re-dispatch from here to the intended target.
        } else if (Intents.SEARCH_SUGGESTION_CLICKED.equals(action)) {
            // See if the suggestion was clicked with a search action key (call button)
            Intent newIntent;
            if ("call".equals(intent.getStringExtra(SearchManager.ACTION_MSG))) {
                newIntent = new Intent(Intent.ACTION_CALL_PRIVILEGED, intent.getData());
            } else {
                newIntent = new Intent(Intent.ACTION_VIEW, intent.getData());
            }
            startActivity(newIntent);
            finish();
            return;
        } else if (Intents.SEARCH_SUGGESTION_DIAL_NUMBER_CLICKED.equals(action)) {
            Intent newIntent = new Intent(Intent.ACTION_CALL_PRIVILEGED, intent.getData());
            startActivity(newIntent);
            finish();
            return;
        } else if (Intents.SEARCH_SUGGESTION_CREATE_CONTACT_CLICKED.equals(action)) {
            String number = intent.getData().getSchemeSpecificPart();
            Intent newIntent = new Intent(Intent.ACTION_INSERT, People.CONTENT_URI);
            newIntent.putExtra(Intents.Insert.PHONE, number);
            startActivity(newIntent);
            finish();
            return;
        }

        if (mMode == MODE_UNKNOWN) {
            mMode = DEFAULT_MODE;
        }

        // Setup the UI
        final ListView list = getListView();
        list.setFocusable(true);
        list.setOnCreateContextMenuListener(this);
        if ((mMode & MODE_MASK_NO_FILTER) != MODE_MASK_NO_FILTER) {
            list.setTextFilterEnabled(true);
        }

        if ((mMode & MODE_MASK_CREATE_NEW) != 0) {
            // Add the header for creating a new contact
            final LayoutInflater inflater = getLayoutInflater();
            View header = inflater.inflate(android.R.layout.simple_list_item_1, list, false);
            TextView text = (TextView) header.findViewById(android.R.id.text1);
            text.setText(R.string.pickerNewContactHeader);
            list.addHeaderView(header);
        }

        // Set the proper empty string
        setEmptyText();
        
        mAdapter = new ContactItemListAdapter(this);
        setListAdapter(mAdapter);

        // We manually save/restore the listview state
        list.setSaveEnabled(false);

        mQueryHandler = new QueryHandler(this);
        mJustCreated = true;

        // Check to see if sync is enabled
        final ContentResolver resolver = getContentResolver();
        IContentProvider provider = resolver.acquireProvider(Contacts.CONTENT_URI);
        if (provider == null) {
            // No contacts provider, bail.
            finish();
            return;
        }

        try {
            ISyncAdapter sa = provider.getSyncAdapter();
            mSyncEnabled = sa != null;
        } catch (RemoteException e) {
            mSyncEnabled = false;
        } finally {
            resolver.releaseProvider(provider);
        }
    
public voidonCreateContextMenu(android.view.ContextMenu menu, android.view.View view, android.view.ContextMenu.ContextMenuInfo menuInfo)

        // If Contacts was invoked by another Activity simply as a way of
        // picking a contact, don't show the context menu
        if ((mMode & MODE_MASK_PICKER) == MODE_MASK_PICKER) {
            return;
        }

        AdapterView.AdapterContextMenuInfo info;
        try {
             info = (AdapterView.AdapterContextMenuInfo) menuInfo;
        } catch (ClassCastException e) {
            Log.e(TAG, "bad menuInfo", e);
            return;
        }

        Cursor cursor = (Cursor) getListAdapter().getItem(info.position);
        if (cursor == null) {
            // For some reason the requested item isn't available, do nothing
            return;
        }
        long id = info.id;
        Uri personUri = ContentUris.withAppendedId(People.CONTENT_URI, id);

        // Setup the menu header
        menu.setHeaderTitle(cursor.getString(NAME_COLUMN_INDEX));

        // View contact details
        menu.add(0, MENU_ITEM_VIEW_CONTACT, 0, R.string.menu_viewContact)
                .setIntent(new Intent(Intent.ACTION_VIEW, personUri));

        // Calling contact
        long phoneId = cursor.getLong(PRIMARY_PHONE_ID_COLUMN_INDEX);
        if (phoneId > 0) {
            // Get the display label for the number
            CharSequence label = cursor.getString(LABEL_COLUMN_INDEX);
            int type = cursor.getInt(TYPE_COLUMN_INDEX);
            label = Phones.getDisplayLabel(this, type, label);
            Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED,
                    ContentUris.withAppendedId(Phones.CONTENT_URI, phoneId));
            menu.add(0, MENU_ITEM_CALL, 0, String.format(getString(R.string.menu_callNumber), label))
                    .setIntent(intent);

            // Send SMS item
            menu.add(0, MENU_ITEM_SEND_SMS, 0, R.string.menu_sendSMS)
                    .setIntent(new Intent(Intent.ACTION_SENDTO,
                            Uri.fromParts("sms", cursor.getString(NUMBER_COLUMN_INDEX), null)));
        }

        // Star toggling
        int starState = cursor.getInt(STARRED_COLUMN_INDEX);
        if (starState == 0) {
            menu.add(0, MENU_ITEM_TOGGLE_STAR, 0, R.string.menu_addStar);
        } else {
            menu.add(0, MENU_ITEM_TOGGLE_STAR, 0, R.string.menu_removeStar);
        }

        // Contact editing
        menu.add(0, MENU_ITEM_EDIT, 0, R.string.menu_editContact)
                .setIntent(new Intent(Intent.ACTION_EDIT, personUri));
        menu.add(0, MENU_ITEM_DELETE, 0, R.string.menu_deleteContact);
    
public booleanonCreateOptionsMenu(android.view.Menu menu)

        // If Contacts was invoked by another Activity simply as a way of
        // picking a contact, don't show the options menu
        if ((mMode & MODE_MASK_PICKER) == MODE_MASK_PICKER) {
            return false;
        }

        // Search
        menu.add(0, MENU_SEARCH, 0, R.string.menu_search)
                .setIcon(android.R.drawable.ic_menu_search);

        // New contact
        menu.add(0, MENU_NEW_CONTACT, 0, R.string.menu_newContact)
                .setIcon(android.R.drawable.ic_menu_add)
                .setIntent(new Intent(Intents.Insert.ACTION, People.CONTENT_URI))
                .setAlphabeticShortcut('n");

        // Display group
        if (mDefaultMode) {
            menu.add(0, MENU_DISPLAY_GROUP, 0, R.string.menu_displayGroup)
                    .setIcon(com.android.internal.R.drawable.ic_menu_allfriends);
        }

        // Sync settings
        if (mSyncEnabled) {
            Intent syncIntent = new Intent(Intent.ACTION_VIEW);
            syncIntent.setClass(this, ContactsGroupSyncSelector.class);
            menu.add(0, 0, 0, R.string.syncGroupPreference)
                    .setIcon(com.android.internal.R.drawable.ic_menu_refresh)
                    .setIntent(syncIntent);
        }
        
        // SIM import
        Intent importIntent = new Intent(Intent.ACTION_VIEW);
        importIntent.setType("vnd.android.cursor.item/sim-contact");
        importIntent.setClassName("com.android.phone", "com.android.phone.SimContacts");
        menu.add(0, 0, 0, R.string.importFromSim)
                .setIcon(R.drawable.ic_menu_import_contact)
                .setIntent(importIntent);

        return super.onCreateOptionsMenu(menu);
    
public booleanonKeyDown(int keyCode, android.view.KeyEvent event)

        switch (keyCode) {
            case KeyEvent.KEYCODE_CALL: {
                if (callSelection()) {
                    return true;
                }
                break;
            }

            case KeyEvent.KEYCODE_DEL: {
                Object o = getListView().getSelectedItem();
                if (o != null) {
                    Cursor cursor = (Cursor) o;
                    Uri uri = ContentUris.withAppendedId(People.CONTENT_URI,
                            cursor.getLong(ID_COLUMN_INDEX));
                    //TODO make this dialog persist across screen rotations
                    new AlertDialog.Builder(ContactsListActivity.this)
                        .setTitle(R.string.deleteConfirmation_title)
                        .setIcon(android.R.drawable.ic_dialog_alert)
                        .setMessage(R.string.deleteConfirmation)
                        .setNegativeButton(android.R.string.cancel, null)
                        .setPositiveButton(android.R.string.ok, new DeleteClickListener(uri))
                        .setCancelable(false)
                        .show();
                    return true;
                }
                break;
            }
        }

        return super.onKeyDown(keyCode, event);
    
protected voidonListItemClick(android.widget.ListView l, android.view.View v, int position, long id)

        // Hide soft keyboard, if visible
        InputMethodManager inputMethodManager = (InputMethodManager)
                getSystemService(Context.INPUT_METHOD_SERVICE);
        inputMethodManager.hideSoftInputFromWindow(mList.getWindowToken(), 0);

        if (mMode == MODE_INSERT_OR_EDIT_CONTACT) {
            Intent intent;
            if (position == 0) {
                // Insert
                intent = new Intent(Intent.ACTION_INSERT, People.CONTENT_URI);
            } else {
                // Edit
                intent = new Intent(Intent.ACTION_EDIT,
                        ContentUris.withAppendedId(People.CONTENT_URI, id));
            }
            intent.setFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
            final Bundle extras = getIntent().getExtras();
            if (extras != null) {
                intent.putExtras(extras);
            }
            startActivity(intent);
            finish();
        } else if (id != -1) {
            if ((mMode & MODE_MASK_PICKER) == 0) {
                Intent intent = new Intent(Intent.ACTION_VIEW,
                        ContentUris.withAppendedId(People.CONTENT_URI, id));
                startActivity(intent);
            } else if (mMode == MODE_QUERY_PICK_TO_VIEW) {
                // Started with query that should launch to view contact
                Cursor c = (Cursor) mAdapter.getItem(position);
                long personId = c.getLong(mQueryPersonIdIndex);
                Intent intent = new Intent(Intent.ACTION_VIEW,
                        ContentUris.withAppendedId(People.CONTENT_URI, personId));
                startActivity(intent);
                finish();
            } else if (mMode == MODE_PICK_CONTACT 
                    || mMode == MODE_PICK_OR_CREATE_CONTACT) {
                Uri uri = ContentUris.withAppendedId(People.CONTENT_URI, id);
                if (mCreateShortcut) {
                    // Subtract one if we have Create Contact at the top
                    Cursor c = (Cursor) mAdapter.getItem(position
                            - (mMode == MODE_PICK_OR_CREATE_CONTACT? 1:0));
                    returnPickerResult(c.getString(NAME_COLUMN_INDEX), uri);
                } else {
                    returnPickerResult(null, uri);
                }
            } else if (mMode == MODE_PICK_PHONE) {
                setResult(RESULT_OK, new Intent().setData(
                        ContentUris.withAppendedId(Phones.CONTENT_URI, id)));
                finish();
            } else if (mMode == MODE_PICK_POSTAL) {
                setResult(RESULT_OK, new Intent().setData(
                        ContentUris.withAppendedId(ContactMethods.CONTENT_URI, id)));
                finish();
            }
        } else if ((mMode & MODE_MASK_CREATE_NEW) == MODE_MASK_CREATE_NEW
                && position == 0) {
            Intent newContact = new Intent(Intents.Insert.ACTION, People.CONTENT_URI);
            startActivityForResult(newContact, SUBACTIVITY_NEW_CONTACT);
        } else {
            signalError();
        }
    
public booleanonOptionsItemSelected(android.view.MenuItem item)

        switch (item.getItemId()) {
            case MENU_DISPLAY_GROUP:
                AlertDialog.Builder builder = new AlertDialog.Builder(this)
                    .setTitle(R.string.select_group_title)
                    .setPositiveButton(android.R.string.ok, this)
                    .setNegativeButton(android.R.string.cancel, null);
                
                setGroupEntries(builder);
                
                builder.show();
                return true;

            case MENU_SEARCH:
                startSearch(null, false, null, false);
                return true;
        }
        return false;
    
protected voidonRestart()

        super.onRestart();

        // The cursor was killed off in onStop(), so we need to get a new one here
        // We do not perform the query if a filter is set on the list because the
        // filter will cause the query to happen anyway
        if (TextUtils.isEmpty(getListView().getTextFilter())) {
            startQuery();
        } else {
            // Run the filtered query on the adapter
            ((ContactItemListAdapter) getListAdapter()).onContentChanged();
        }
    
protected voidonRestoreInstanceState(android.os.Bundle icicle)

        super.onRestoreInstanceState(icicle);
        // Retrieve list state. This will be applied after the QueryHandler has run
        mListState = icicle.getParcelable(LIST_STATE_KEY);
        mListHasFocus = icicle.getBoolean(FOCUS_KEY);
    
protected voidonResume()

        super.onResume();

        boolean runQuery = true;
        Activity parent = getParent();
        
        // Do this before setting the filter. The filter thread relies
        // on some state that is initialized in setDefaultMode
        if (mDefaultMode) {
            // If we're in default mode we need to possibly reset the mode due to a change
            // in the preferences activity while we weren't running
            setDefaultMode();
        }
        
        // See if we were invoked with a filter
        if (parent != null && parent instanceof DialtactsActivity) {
            String filterText = ((DialtactsActivity) parent).getAndClearFilterText();
            if (filterText != null && filterText.length() > 0) {
                getListView().setFilterText(filterText);
                // Don't start a new query since it will conflict with the filter
                runQuery = false;
            } else if (mJustCreated) {
                getListView().clearTextFilter();
            }
        }

        if (mJustCreated && runQuery) {
            // We need to start a query here the first time the activity is launched, as long
            // as we aren't doing a filter.
            startQuery();
        }
        mJustCreated = false;
    
protected voidonSaveInstanceState(android.os.Bundle icicle)

        super.onSaveInstanceState(icicle);
        // Save list state in the bundle so we can restore it after the QueryHandler has run
        icicle.putParcelable(LIST_STATE_KEY, mList.onSaveInstanceState());
        icicle.putBoolean(FOCUS_KEY, mList.hasFocus());
    
protected voidonStop()

        super.onStop();

        // We don't want the list to display the empty state, since when we resume it will still
        // be there and show up while the new query is happening. After the async query finished
        // in response to onRestart() setLoading(false) will be called.
        mAdapter.setLoading(true);
        mAdapter.changeCursor(null);

        if (mMode == MODE_QUERY) {
            // Make sure the search box is closed
            SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE);
            searchManager.stopSearch();
        }
    
private voidreturnPickerResult(java.lang.String name, android.net.Uri uri)

        final Intent intent = new Intent();
    
        if (mCreateShortcut) {
            Intent shortcutIntent = new Intent(Intent.ACTION_VIEW, uri);
            shortcutIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
            intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
            intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, name);
            final Bitmap icon = People.loadContactPhoto(this, uri, 0, null);
            if (icon != null) {
                intent.putExtra(Intent.EXTRA_SHORTCUT_ICON, icon);
            } else {
                intent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE,
                        Intent.ShortcutIconResource.fromContext(this,
                                R.drawable.ic_launcher_shortcut_contact));
            }
            setResult(RESULT_OK, intent);
        } else {
            setResult(RESULT_OK, intent.setData(uri));
        }
        finish();
    
private voidsetDefaultMode()
Sets the mode when the request is for "default"

        // Load the preferences
        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
        
        // Lookup the group to display
        mDisplayType = prefs.getInt(PREF_DISPLAY_TYPE, DISPLAY_TYPE_UNKNOWN);
        switch (mDisplayType) {
            case DISPLAY_TYPE_ALL_WITH_PHONES: {
                mMode = MODE_WITH_PHONES;
                mDisplayInfo = null;
                break;
            }

            case DISPLAY_TYPE_SYSTEM_GROUP: {
                String systemId = prefs.getString(
                        PREF_DISPLAY_INFO, null);
                if (!TextUtils.isEmpty(systemId)) {
                    // Display the selected system group
                    mMode = MODE_GROUP;
                    buildSystemGroupUris(systemId);
                    mDisplayInfo = systemId;
                } else {
                    // No valid group is present, display everything
                    mMode = MODE_WITH_PHONES;
                    mDisplayInfo = null;
                    mDisplayType = DISPLAY_TYPE_ALL;
                }
                break;
            }

            case DISPLAY_TYPE_USER_GROUP: {
                String displayGroup = prefs.getString(
                        PREF_DISPLAY_INFO, null);
                if (!TextUtils.isEmpty(displayGroup)) {
                    // Display the selected user group
                    mMode = MODE_GROUP;
                    buildUserGroupUris(displayGroup);
                    mDisplayInfo = displayGroup;
                } else {
                    // No valid group is present, display everything
                    mMode = MODE_WITH_PHONES;
                    mDisplayInfo = null;
                    mDisplayType = DISPLAY_TYPE_ALL;
                }
                break;
            }

            case DISPLAY_TYPE_ALL: {
                mMode = MODE_ALL_CONTACTS;
                mDisplayInfo = null;
                break;
            }

            default: {
                // We don't know what to display, default to My Contacts
                mMode = MODE_GROUP;
                mDisplayType = DISPLAY_TYPE_SYSTEM_GROUP;
                buildSystemGroupUris(Groups.GROUP_MY_CONTACTS);
                mDisplayInfo = Groups.GROUP_MY_CONTACTS;
                break;
            }
        }

        // Update the empty text view with the proper string, as the group may have changed
        setEmptyText();
    
private voidsetEmptyText()

        TextView empty = (TextView) findViewById(R.id.emptyText);
        // Center the text by default
        int gravity = Gravity.CENTER;
        switch (mMode) {
            case MODE_GROUP:
                if (Groups.GROUP_MY_CONTACTS.equals(mDisplayInfo)) {
                    if (mSyncEnabled) {
                        empty.setText(getText(R.string.noContactsHelpTextWithSync));
                    } else {
                        empty.setText(getText(R.string.noContactsHelpText));
                    }
                    gravity = Gravity.NO_GRAVITY;
                } else {
                    empty.setText(getString(R.string.groupEmpty, mDisplayInfo));
                }
                break;

            case MODE_STARRED:
            case MODE_STREQUENT:
            case MODE_FREQUENT:
                empty.setText(getText(R.string.noFavorites));
                break;

            case MODE_WITH_PHONES:
                empty.setText(getText(R.string.noContactsWithPhoneNumbers));
                break;

            default:
                empty.setText(getText(R.string.noContacts));
                break;
        }
        empty.setGravity(gravity);
    
private voidsetGroupEntries(AlertDialog.Builder builder)

        boolean syncEverything;
        // For now we only support a single account and the UI doesn't know what
        // the account name is, so we're using a global setting for SYNC_EVERYTHING.
        // Some day when we add multiple accounts to the UI this should use the per
        // account setting.
        String value = Contacts.Settings.getSetting(getContentResolver(), null,
                Contacts.Settings.SYNC_EVERYTHING);
        if (value == null) {
            // If nothing is set yet we default to syncing everything
            syncEverything = true;
        } else {
            syncEverything = !TextUtils.isEmpty(value) && !"0".equals(value);
        }

        Cursor cursor;
        if (!syncEverything) {
            cursor = getContentResolver().query(Groups.CONTENT_URI, GROUPS_PROJECTION,
                    Groups.SHOULD_SYNC + " != 0", null, Groups.DEFAULT_SORT_ORDER);
        } else {
            cursor = getContentResolver().query(Groups.CONTENT_URI, GROUPS_PROJECTION,
                    null, null, Groups.DEFAULT_SORT_ORDER);
        }
        try {
            ArrayList<CharSequence> groups = new ArrayList<CharSequence>();
            ArrayList<CharSequence> prefStrings = new ArrayList<CharSequence>();

            // Add All Contacts
            groups.add(DISPLAY_GROUP_INDEX_ALL_CONTACTS, getString(R.string.showAllGroups));
            prefStrings.add("");
            
            // Add Contacts with phones
            groups.add(DISPLAY_GROUP_INDEX_ALL_CONTACTS_WITH_PHONES,
                    getString(R.string.groupNameWithPhones));
            prefStrings.add(GROUP_WITH_PHONES);
            
            int currentIndex = DISPLAY_GROUP_INDEX_ALL_CONTACTS;
            while (cursor.moveToNext()) {
                String systemId = cursor.getString(GROUPS_COLUMN_INDEX_SYSTEM_ID);
                String name = cursor.getString(GROUPS_COLUMN_INDEX_NAME);
                if (cursor.isNull(GROUPS_COLUMN_INDEX_SYSTEM_ID)
                        && !Groups.GROUP_MY_CONTACTS.equals(systemId)) {
                    // All groups that aren't My Contacts, since that one is localized on the phone

                    // Localize the "Starred in Android" string which we get from the server side.
                    if (Groups.GROUP_ANDROID_STARRED.equals(name)) {
                        name = getString(R.string.starredInAndroid);
                    }
                    groups.add(name);
                    if (name.equals(mDisplayInfo)) {
                        currentIndex = groups.size() - 1;
                    }
                } else {
                    // The My Contacts group
                    groups.add(DISPLAY_GROUP_INDEX_MY_CONTACTS,
                            getString(R.string.groupNameMyContacts));
                    if (mDisplayType == DISPLAY_TYPE_SYSTEM_GROUP
                            && Groups.GROUP_MY_CONTACTS.equals(mDisplayInfo)) {
                        currentIndex = DISPLAY_GROUP_INDEX_MY_CONTACTS;
                    }
                    mDisplayGroupsIncludesMyContacts = true;
                }
            }
            if (mMode == MODE_ALL_CONTACTS) {
                currentIndex = DISPLAY_GROUP_INDEX_ALL_CONTACTS;
            } else if (mMode == MODE_WITH_PHONES) {
                currentIndex = DISPLAY_GROUP_INDEX_ALL_CONTACTS_WITH_PHONES;
            }
            mDisplayGroups = groups.toArray(new CharSequence[groups.size()]);
            builder.setSingleChoiceItems(mDisplayGroups, currentIndex, this);
            mDisplayGroupOriginalSelection = currentIndex;
        } finally {
            cursor.close();
        }
    
voidsignalError()
Signal an error to the user.

        //TODO play an error beep or something...
    
voidstartQuery()

        mAdapter.setLoading(true);
        
        // Cancel any pending queries
        mQueryHandler.cancelOperation(QUERY_TOKEN);

        // Kick off the new query
        switch (mMode) {
            case MODE_GROUP:
                mQueryHandler.startQuery(QUERY_TOKEN, null,
                        mGroupUri, CONTACTS_PROJECTION, null, null,
                        getSortOrder(CONTACTS_PROJECTION));
                break;

            case MODE_ALL_CONTACTS:
            case MODE_PICK_CONTACT:
            case MODE_PICK_OR_CREATE_CONTACT:
            case MODE_INSERT_OR_EDIT_CONTACT:
                mQueryHandler.startQuery(QUERY_TOKEN, null, People.CONTENT_URI, CONTACTS_PROJECTION,
                        null, null, getSortOrder(CONTACTS_PROJECTION));
                break;

            case MODE_WITH_PHONES:
                mQueryHandler.startQuery(QUERY_TOKEN, null, People.CONTENT_URI, CONTACTS_PROJECTION,
                        People.PRIMARY_PHONE_ID + " IS NOT NULL", null,
                        getSortOrder(CONTACTS_PROJECTION));
                break;

            case MODE_QUERY: {
                mQuery = getIntent().getStringExtra(SearchManager.QUERY);
                mQueryHandler.startQuery(QUERY_TOKEN, null, getPeopleFilterUri(mQuery),
                        CONTACTS_PROJECTION, null, null,
                        getSortOrder(CONTACTS_PROJECTION));
                break;
            }
            
            case MODE_QUERY_PICK_TO_VIEW: {
                if (mQueryMode == QUERY_MODE_MAILTO) {
                    // Find all contacts with the given search string as either
                    // an E-mail or IM address.
                    mQueryPersonIdIndex = SIMPLE_CONTACTS_PERSON_ID_INDEX;
                    Uri uri = Uri.withAppendedPath(People.WITH_EMAIL_OR_IM_FILTER_URI,
                            Uri.encode(mQueryData));
                    mQueryHandler.startQuery(QUERY_TOKEN, null,
                            uri, SIMPLE_CONTACTS_PROJECTION, null, null,
                            getSortOrder(CONTACTS_PROJECTION));
                    
                } else if (mQueryMode == QUERY_MODE_TEL) {
                    mQueryPersonIdIndex = PHONES_PERSON_ID_INDEX;
                    mQueryHandler.startQuery(QUERY_TOKEN, null,
                            Uri.withAppendedPath(Phones.CONTENT_FILTER_URL, mQueryData),
                            PHONES_PROJECTION, null, null,
                            getSortOrder(PHONES_PROJECTION));
                }
                break;
            }
            
            case MODE_STARRED:
                mQueryHandler.startQuery(QUERY_TOKEN, null, People.CONTENT_URI,
                        CONTACTS_PROJECTION,
                        People.STARRED + "=1", null, getSortOrder(CONTACTS_PROJECTION));
                break;

            case MODE_FREQUENT:
                mQueryHandler.startQuery(QUERY_TOKEN, null,
                        People.CONTENT_URI, CONTACTS_PROJECTION,
                        People.TIMES_CONTACTED + " > 0", null,
                        People.TIMES_CONTACTED + " DESC, " + getSortOrder(CONTACTS_PROJECTION));
                break;

            case MODE_STREQUENT:
                mQueryHandler.startQuery(QUERY_TOKEN, null,
                        Uri.withAppendedPath(People.CONTENT_URI, "strequent"), STREQUENT_PROJECTION,
                        null, null, null);
                break;

            case MODE_PICK_PHONE:
                mQueryHandler.startQuery(QUERY_TOKEN, null, Phones.CONTENT_URI, PHONES_PROJECTION,
                        null, null, getSortOrder(PHONES_PROJECTION));
                break;

            case MODE_PICK_POSTAL:
                mQueryHandler.startQuery(QUERY_TOKEN, null, ContactMethods.CONTENT_URI,
                        CONTACT_METHODS_PROJECTION,
                        ContactMethods.KIND + "=" + Contacts.KIND_POSTAL, null,
                        getSortOrder(CONTACT_METHODS_PROJECTION));
                break;
        }
    
private voidupdateGroup()

        if (mDefaultMode) {
            setDefaultMode();
        }

        // Calling requery here may cause an ANR, so always do the async query
        startQuery();