blob: ac6a3a873c8822d11dce3fc7bf4e28f2b561002e [file] [log] [blame]
/*
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.contacts;
import com.android.contacts.TextHighlightingAnimation.TextWithHighlighting;
import com.android.contacts.model.ContactsSource;
import com.android.contacts.model.Sources;
import com.android.contacts.ui.ContactsPreferences;
import com.android.contacts.ui.ContactsPreferencesActivity;
import com.android.contacts.ui.ContactsPreferencesActivity.Prefs;
import com.android.contacts.util.AccountSelectionUtil;
import com.android.contacts.util.Constants;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.ListActivity;
import android.app.SearchManager;
import android.content.AsyncQueryHandler;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.DialogInterface;
import android.content.IContentService;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.UriMatcher;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.database.CharArrayBuffer;
import android.database.ContentObserver;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.net.Uri.Builder;
import android.os.Bundle;
import android.os.Handler;
import android.os.Parcelable;
import android.os.RemoteException;
import android.preference.PreferenceManager;
import android.provider.ContactsContract;
import android.provider.Settings;
import android.provider.Contacts.ContactMethods;
import android.provider.Contacts.People;
import android.provider.Contacts.PeopleColumns;
import android.provider.Contacts.Phones;
import android.provider.ContactsContract.ContactCounts;
import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.Data;
import android.provider.ContactsContract.Intents;
import android.provider.ContactsContract.ProviderStatus;
import android.provider.ContactsContract.RawContacts;
import android.provider.ContactsContract.SearchSnippetColumns;
import android.provider.ContactsContract.CommonDataKinds.Email;
import android.provider.ContactsContract.CommonDataKinds.Nickname;
import android.provider.ContactsContract.CommonDataKinds.Organization;
import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.ContactsContract.CommonDataKinds.Photo;
import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
import android.provider.ContactsContract.Contacts.AggregationSuggestions;
import android.provider.ContactsContract.Intents.Insert;
import android.provider.ContactsContract.Intents.UI;
import android.telephony.TelephonyManager;
import android.text.Editable;
import android.text.Html;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.util.Log;
import android.view.ContextMenu;
import android.view.ContextThemeWrapper;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.View.OnClickListener;
import android.view.View.OnFocusChangeListener;
import android.view.View.OnTouchListener;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.AbsListView;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.CursorAdapter;
import android.widget.Filter;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.QuickContactBadge;
import android.widget.SectionIndexer;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.AbsListView.OnScrollListener;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
/**
* Displays a list of contacts. Usually is embedded into the ContactsActivity.
*/
@SuppressWarnings("deprecation")
public class ContactsListActivity extends ListActivity implements View.OnCreateContextMenuListener,
View.OnClickListener, View.OnKeyListener, TextWatcher, TextView.OnEditorActionListener,
OnFocusChangeListener, OnTouchListener {
public static class JoinContactActivity extends ContactsListActivity {
}
public static class ContactsSearchActivity extends ContactsListActivity {
}
private static final String TAG = "ContactsListActivity";
private static final boolean ENABLE_ACTION_ICON_OVERLAYS = true;
private static final String LIST_STATE_KEY = "liststate";
private static final String SHORTCUT_ACTION_KEY = "shortcutAction";
static final int MENU_ITEM_VIEW_CONTACT = 1;
static final int MENU_ITEM_CALL = 2;
static final int MENU_ITEM_EDIT_BEFORE_CALL = 3;
static final int MENU_ITEM_SEND_SMS = 4;
static final int MENU_ITEM_SEND_IM = 5;
static final int MENU_ITEM_EDIT = 6;
static final int MENU_ITEM_DELETE = 7;
static final int MENU_ITEM_TOGGLE_STAR = 8;
private static final int SUBACTIVITY_NEW_CONTACT = 1;
private static final int SUBACTIVITY_VIEW_CONTACT = 2;
private static final int SUBACTIVITY_DISPLAY_GROUP = 3;
private static final int SUBACTIVITY_SEARCH = 4;
private static final int SUBACTIVITY_FILTER = 5;
private static final int TEXT_HIGHLIGHTING_ANIMATION_DURATION = 350;
/**
* The action for the join contact activity.
* <p>
* Input: extra field {@link #EXTRA_AGGREGATE_ID} is the aggregate ID.
*
* TODO: move to {@link ContactsContract}.
*/
public static final String JOIN_AGGREGATE =
"com.android.contacts.action.JOIN_AGGREGATE";
/**
* Used with {@link #JOIN_AGGREGATE} to give it the target for aggregation.
* <p>
* Type: LONG
*/
public static final String EXTRA_AGGREGATE_ID =
"com.android.contacts.action.AGGREGATE_ID";
/**
* Used with {@link #JOIN_AGGREGATE} to give it the name of the aggregation target.
* <p>
* Type: STRING
*/
@Deprecated
public static final String EXTRA_AGGREGATE_NAME =
"com.android.contacts.action.AGGREGATE_NAME";
public static final String AUTHORITIES_FILTER_KEY = "authorities";
private static final Uri CONTACTS_CONTENT_URI_WITH_LETTER_COUNTS =
buildSectionIndexerUri(Contacts.CONTENT_URI);
/** Mask for picker mode */
static final int MODE_MASK_PICKER = 0x80000000;
/** Mask for no presence mode */
static final int MODE_MASK_NO_PRESENCE = 0x40000000;
/** Mask for enabling list filtering */
static final int MODE_MASK_NO_FILTER = 0x20000000;
/** Mask for having a "create new contact" header in the list */
static final int MODE_MASK_CREATE_NEW = 0x10000000;
/** Mask for showing photos in the list */
static final int MODE_MASK_SHOW_PHOTOS = 0x08000000;
/** Mask for hiding additional information e.g. primary phone number in the list */
static final int MODE_MASK_NO_DATA = 0x04000000;
/** Mask for showing a call button in the list */
static final int MODE_MASK_SHOW_CALL_BUTTON = 0x02000000;
/** Mask to disable quickcontact (images will show as normal images) */
static final int MODE_MASK_DISABLE_QUIKCCONTACT = 0x01000000;
/** Mask to show the total number of contacts at the top */
static final int MODE_MASK_SHOW_NUMBER_OF_CONTACTS = 0x00800000;
/** Unknown mode */
static final int MODE_UNKNOWN = 0;
/** Default mode */
static final int MODE_DEFAULT = 4 | MODE_MASK_SHOW_PHOTOS | MODE_MASK_SHOW_NUMBER_OF_CONTACTS;
/** Custom mode */
static final int MODE_CUSTOM = 8;
/** Show all starred contacts */
static final int MODE_STARRED = 20 | MODE_MASK_SHOW_PHOTOS;
/** Show frequently contacted contacts */
static final int MODE_FREQUENT = 30 | MODE_MASK_SHOW_PHOTOS;
/** Show starred and the frequent */
static final int MODE_STREQUENT = 35 | MODE_MASK_SHOW_PHOTOS | MODE_MASK_SHOW_CALL_BUTTON;
/** Show all contacts and pick them when clicking */
static final int MODE_PICK_CONTACT = 40 | MODE_MASK_PICKER | MODE_MASK_SHOW_PHOTOS
| MODE_MASK_DISABLE_QUIKCCONTACT;
/** Show all contacts as well as the option to create a new one */
static final int MODE_PICK_OR_CREATE_CONTACT = 42 | MODE_MASK_PICKER | MODE_MASK_CREATE_NEW
| MODE_MASK_SHOW_PHOTOS | MODE_MASK_DISABLE_QUIKCCONTACT;
/** Show all people through the legacy provider and pick them when clicking */
static final int MODE_LEGACY_PICK_PERSON = 43 | MODE_MASK_PICKER
| MODE_MASK_DISABLE_QUIKCCONTACT;
/** Show all people through the legacy provider as well as the option to create a new one */
static final int MODE_LEGACY_PICK_OR_CREATE_PERSON = 44 | MODE_MASK_PICKER
| MODE_MASK_CREATE_NEW | MODE_MASK_DISABLE_QUIKCCONTACT;
/** Show all contacts and pick them when clicking, and allow creating a new contact */
static final int MODE_INSERT_OR_EDIT_CONTACT = 45 | MODE_MASK_PICKER | MODE_MASK_CREATE_NEW
| MODE_MASK_SHOW_PHOTOS | MODE_MASK_DISABLE_QUIKCCONTACT;
/** Show all phone numbers and pick them when clicking */
static final int MODE_PICK_PHONE = 50 | MODE_MASK_PICKER | MODE_MASK_NO_PRESENCE;
/** Show all phone numbers through the legacy provider and pick them when clicking */
static final int MODE_LEGACY_PICK_PHONE =
51 | MODE_MASK_PICKER | MODE_MASK_NO_PRESENCE | MODE_MASK_NO_FILTER;
/** Show all postal addresses and pick them when clicking */
static final int MODE_PICK_POSTAL =
55 | MODE_MASK_PICKER | MODE_MASK_NO_PRESENCE | MODE_MASK_NO_FILTER;
/** Show all postal addresses and pick them when clicking */
static final int MODE_LEGACY_PICK_POSTAL =
56 | MODE_MASK_PICKER | MODE_MASK_NO_PRESENCE | MODE_MASK_NO_FILTER;
static final int MODE_GROUP = 57 | MODE_MASK_SHOW_PHOTOS;
/** Run a search query */
static final int MODE_QUERY = 60 | MODE_MASK_SHOW_PHOTOS | MODE_MASK_NO_FILTER
| MODE_MASK_SHOW_NUMBER_OF_CONTACTS;
/** Run a search query in PICK mode, but that still launches to VIEW */
static final int MODE_QUERY_PICK_TO_VIEW = 65 | MODE_MASK_SHOW_PHOTOS | MODE_MASK_PICKER
| MODE_MASK_SHOW_NUMBER_OF_CONTACTS;
/** Show join suggestions followed by an A-Z list */
static final int MODE_JOIN_CONTACT = 70 | MODE_MASK_PICKER | MODE_MASK_NO_PRESENCE
| MODE_MASK_NO_DATA | MODE_MASK_SHOW_PHOTOS | MODE_MASK_DISABLE_QUIKCCONTACT;
/** Run a search query in a PICK mode */
static final int MODE_QUERY_PICK = 75 | MODE_MASK_SHOW_PHOTOS | MODE_MASK_NO_FILTER
| MODE_MASK_PICKER | MODE_MASK_DISABLE_QUIKCCONTACT | MODE_MASK_SHOW_NUMBER_OF_CONTACTS;
/** Run a search query in a PICK_PHONE mode */
static final int MODE_QUERY_PICK_PHONE = 80 | MODE_MASK_NO_FILTER | MODE_MASK_PICKER
| MODE_MASK_SHOW_NUMBER_OF_CONTACTS;
/** Run a search query in PICK mode, but that still launches to EDIT */
static final int MODE_QUERY_PICK_TO_EDIT = 85 | MODE_MASK_NO_FILTER | MODE_MASK_SHOW_PHOTOS
| MODE_MASK_PICKER | MODE_MASK_SHOW_NUMBER_OF_CONTACTS;
/**
* An action used to do perform search while in a contact picker. It is initiated
* by the ContactListActivity itself.
*/
private static final String ACTION_SEARCH_INTERNAL = "com.android.contacts.INTERNAL_SEARCH";
/** Maximum number of suggestions shown for joining aggregates */
static final int MAX_SUGGESTIONS = 4;
static final String[] CONTACTS_SUMMARY_PROJECTION = new String[] {
Contacts._ID, // 0
Contacts.DISPLAY_NAME_PRIMARY, // 1
Contacts.DISPLAY_NAME_ALTERNATIVE, // 2
Contacts.SORT_KEY_PRIMARY, // 3
Contacts.STARRED, // 4
Contacts.TIMES_CONTACTED, // 5
Contacts.CONTACT_PRESENCE, // 6
Contacts.PHOTO_ID, // 7
Contacts.LOOKUP_KEY, // 8
Contacts.PHONETIC_NAME, // 9
Contacts.HAS_PHONE_NUMBER, // 10
};
static final String[] CONTACTS_SUMMARY_PROJECTION_FROM_EMAIL = new String[] {
Contacts._ID, // 0
Contacts.DISPLAY_NAME_PRIMARY, // 1
Contacts.DISPLAY_NAME_ALTERNATIVE, // 2
Contacts.SORT_KEY_PRIMARY, // 3
Contacts.STARRED, // 4
Contacts.TIMES_CONTACTED, // 5
Contacts.CONTACT_PRESENCE, // 6
Contacts.PHOTO_ID, // 7
Contacts.LOOKUP_KEY, // 8
Contacts.PHONETIC_NAME, // 9
// email lookup doesn't included HAS_PHONE_NUMBER in projection
};
static final String[] CONTACTS_SUMMARY_FILTER_PROJECTION = new String[] {
Contacts._ID, // 0
Contacts.DISPLAY_NAME_PRIMARY, // 1
Contacts.DISPLAY_NAME_ALTERNATIVE, // 2
Contacts.SORT_KEY_PRIMARY, // 3
Contacts.STARRED, // 4
Contacts.TIMES_CONTACTED, // 5
Contacts.CONTACT_PRESENCE, // 6
Contacts.PHOTO_ID, // 7
Contacts.LOOKUP_KEY, // 8
Contacts.PHONETIC_NAME, // 9
Contacts.HAS_PHONE_NUMBER, // 10
SearchSnippetColumns.SNIPPET_MIMETYPE, // 11
SearchSnippetColumns.SNIPPET_DATA1, // 12
SearchSnippetColumns.SNIPPET_DATA4, // 13
};
static final String[] LEGACY_PEOPLE_PROJECTION = new String[] {
People._ID, // 0
People.DISPLAY_NAME, // 1
People.DISPLAY_NAME, // 2
People.DISPLAY_NAME, // 3
People.STARRED, // 4
PeopleColumns.TIMES_CONTACTED, // 5
People.PRESENCE_STATUS, // 6
};
static final int SUMMARY_ID_COLUMN_INDEX = 0;
static final int SUMMARY_DISPLAY_NAME_PRIMARY_COLUMN_INDEX = 1;
static final int SUMMARY_DISPLAY_NAME_ALTERNATIVE_COLUMN_INDEX = 2;
static final int SUMMARY_SORT_KEY_PRIMARY_COLUMN_INDEX = 3;
static final int SUMMARY_STARRED_COLUMN_INDEX = 4;
static final int SUMMARY_TIMES_CONTACTED_COLUMN_INDEX = 5;
static final int SUMMARY_PRESENCE_STATUS_COLUMN_INDEX = 6;
static final int SUMMARY_PHOTO_ID_COLUMN_INDEX = 7;
static final int SUMMARY_LOOKUP_KEY_COLUMN_INDEX = 8;
static final int SUMMARY_PHONETIC_NAME_COLUMN_INDEX = 9;
static final int SUMMARY_HAS_PHONE_COLUMN_INDEX = 10;
static final int SUMMARY_SNIPPET_MIMETYPE_COLUMN_INDEX = 11;
static final int SUMMARY_SNIPPET_DATA1_COLUMN_INDEX = 12;
static final int SUMMARY_SNIPPET_DATA4_COLUMN_INDEX = 13;
static final String[] PHONES_PROJECTION = new String[] {
Phone._ID, //0
Phone.TYPE, //1
Phone.LABEL, //2
Phone.NUMBER, //3
Phone.DISPLAY_NAME, // 4
Phone.CONTACT_ID, // 5
};
static final String[] LEGACY_PHONES_PROJECTION = new String[] {
Phones._ID, //0
Phones.TYPE, //1
Phones.LABEL, //2
Phones.NUMBER, //3
People.DISPLAY_NAME, // 4
};
static final int PHONE_ID_COLUMN_INDEX = 0;
static final int PHONE_TYPE_COLUMN_INDEX = 1;
static final int PHONE_LABEL_COLUMN_INDEX = 2;
static final int PHONE_NUMBER_COLUMN_INDEX = 3;
static final int PHONE_DISPLAY_NAME_COLUMN_INDEX = 4;
static final int PHONE_CONTACT_ID_COLUMN_INDEX = 5;
static final String[] POSTALS_PROJECTION = new String[] {
StructuredPostal._ID, //0
StructuredPostal.TYPE, //1
StructuredPostal.LABEL, //2
StructuredPostal.DATA, //3
StructuredPostal.DISPLAY_NAME, // 4
};
static final String[] LEGACY_POSTALS_PROJECTION = new String[] {
ContactMethods._ID, //0
ContactMethods.TYPE, //1
ContactMethods.LABEL, //2
ContactMethods.DATA, //3
People.DISPLAY_NAME, // 4
};
static final String[] RAW_CONTACTS_PROJECTION = new String[] {
RawContacts._ID, //0
RawContacts.CONTACT_ID, //1
RawContacts.ACCOUNT_TYPE, //2
};
static final int POSTAL_ID_COLUMN_INDEX = 0;
static final int POSTAL_TYPE_COLUMN_INDEX = 1;
static final int POSTAL_LABEL_COLUMN_INDEX = 2;
static final int POSTAL_ADDRESS_COLUMN_INDEX = 3;
static final int POSTAL_DISPLAY_NAME_COLUMN_INDEX = 4;
private static final int QUERY_TOKEN = 42;
static final String KEY_PICKER_MODE = "picker_mode";
private ContactItemListAdapter mAdapter;
int mMode = MODE_DEFAULT;
private QueryHandler mQueryHandler;
private boolean mJustCreated;
private boolean mSyncEnabled;
Uri mSelectedContactUri;
// private boolean mDisplayAll;
private boolean mDisplayOnlyPhones;
private Uri mGroupUri;
private long mQueryAggregateId;
private ArrayList<Long> mWritableRawContactIds = new ArrayList<Long>();
private int mWritableSourcesCnt;
private int mReadOnlySourcesCnt;
/**
* Used to keep track of the scroll state of the list.
*/
private Parcelable mListState = null;
private String mShortcutAction;
/**
* Internal query type when in mode {@link #MODE_QUERY_PICK_TO_VIEW}.
*/
private int mQueryMode = QUERY_MODE_NONE;
private static final int QUERY_MODE_NONE = -1;
private static final int QUERY_MODE_MAILTO = 1;
private static final int QUERY_MODE_TEL = 2;
private int mProviderStatus = ProviderStatus.STATUS_NORMAL;
private boolean mSearchMode;
private boolean mSearchResultsMode;
private boolean mShowNumberOfContacts;
private boolean mShowSearchSnippets;
private boolean mSearchInitiated;
private String mInitialFilter;
private static final String CLAUSE_ONLY_VISIBLE = Contacts.IN_VISIBLE_GROUP + "=1";
private static final String CLAUSE_ONLY_PHONES = Contacts.HAS_PHONE_NUMBER + "=1";
/**
* In the {@link #MODE_JOIN_CONTACT} determines whether we display a list item with the label
* "Show all contacts" or actually show all contacts
*/
private boolean mJoinModeShowAllContacts;
/**
* The ID of the special item described above.
*/
private static final long JOIN_MODE_SHOW_ALL_CONTACTS_ID = -2;
// Uri matcher for contact id
private static final int CONTACTS_ID = 1001;
private static final UriMatcher sContactsIdMatcher;
private ContactPhotoLoader mPhotoLoader;
final String[] sLookupProjection = new String[] {
Contacts.LOOKUP_KEY
};
static {
sContactsIdMatcher = new UriMatcher(UriMatcher.NO_MATCH);
sContactsIdMatcher.addURI(ContactsContract.AUTHORITY, "contacts/#", CONTACTS_ID);
}
private class DeleteClickListener implements DialogInterface.OnClickListener {
public void onClick(DialogInterface dialog, int which) {
if (mSelectedContactUri != null) {
getContentResolver().delete(mSelectedContactUri, null, null);
}
}
}
/**
* A {@link TextHighlightingAnimation} that redraws just the contact display name in a
* list item.
*/
private static class NameHighlightingAnimation extends TextHighlightingAnimation {
private final ListView mListView;
private NameHighlightingAnimation(ListView listView, int duration) {
super(duration);
this.mListView = listView;
}
/**
* Redraws all visible items of the list corresponding to contacts
*/
@Override
protected void invalidate() {
int childCount = mListView.getChildCount();
for (int i = 0; i < childCount; i++) {
View itemView = mListView.getChildAt(i);
if (itemView instanceof ContactListItemView) {
final ContactListItemView view = (ContactListItemView)itemView;
view.getNameTextView().invalidate();
}
}
}
@Override
protected void onAnimationStarted() {
mListView.setScrollingCacheEnabled(false);
}
@Override
protected void onAnimationEnded() {
mListView.setScrollingCacheEnabled(true);
}
}
// The size of a home screen shortcut icon.
private int mIconSize;
private ContactsPreferences mContactsPrefs;
private int mDisplayOrder;
private int mSortOrder;
private boolean mHighlightWhenScrolling;
private TextHighlightingAnimation mHighlightingAnimation;
private SearchEditText mSearchEditText;
/**
* An approximation of the background color of the pinned header. This color
* is used when the pinned header is being pushed up. At that point the header
* "fades away". Rather than computing a faded bitmap based on the 9-patch
* normally used for the background, we will use a solid color, which will
* provide better performance and reduced complexity.
*/
private int mPinnedHeaderBackgroundColor;
private ContentObserver mProviderStatusObserver = new ContentObserver(new Handler()) {
@Override
public void onChange(boolean selfChange) {
checkProviderState(true);
}
};
@Override
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
mIconSize = getResources().getDimensionPixelSize(android.R.dimen.app_icon_size);
mContactsPrefs = new ContactsPreferences(this);
mPhotoLoader = new ContactPhotoLoader(this, R.drawable.ic_contact_list_picture);
// 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(UI.TITLE_EXTRA_KEY);
if (title != null) {
setTitle(title);
}
String action = intent.getAction();
String component = intent.getComponent().getClassName();
// When we get a FILTER_CONTACTS_ACTION, it represents search in the context
// of some other action. Let's retrieve the original action to provide proper
// context for the search queries.
if (UI.FILTER_CONTACTS_ACTION.equals(action)) {
mSearchMode = true;
mShowSearchSnippets = true;
Bundle extras = intent.getExtras();
if (extras != null) {
mInitialFilter = extras.getString(UI.FILTER_TEXT_EXTRA_KEY);
String originalAction =
extras.getString(ContactsSearchManager.ORIGINAL_ACTION_EXTRA_KEY);
if (originalAction != null) {
action = originalAction;
}
String originalComponent =
extras.getString(ContactsSearchManager.ORIGINAL_COMPONENT_EXTRA_KEY);
if (originalComponent != null) {
component = originalComponent;
}
} else {
mInitialFilter = null;
}
}
Log.i(TAG, "Called with action: " + action);
mMode = MODE_UNKNOWN;
if (UI.LIST_DEFAULT.equals(action) || UI.FILTER_CONTACTS_ACTION.equals(action)) {
mMode = MODE_DEFAULT;
// 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;
}
buildUserGroupUri(groupName);
} else if (UI.LIST_ALL_CONTACTS_ACTION.equals(action)) {
mMode = MODE_CUSTOM;
mDisplayOnlyPhones = false;
} else if (UI.LIST_STARRED_ACTION.equals(action)) {
mMode = mSearchMode ? MODE_DEFAULT : MODE_STARRED;
} else if (UI.LIST_FREQUENT_ACTION.equals(action)) {
mMode = mSearchMode ? MODE_DEFAULT : MODE_FREQUENT;
} else if (UI.LIST_STREQUENT_ACTION.equals(action)) {
mMode = mSearchMode ? MODE_DEFAULT : MODE_STREQUENT;
} else if (UI.LIST_CONTACTS_WITH_PHONES_ACTION.equals(action)) {
mMode = MODE_CUSTOM;
mDisplayOnlyPhones = true;
} 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 (Contacts.CONTENT_TYPE.equals(type)) {
mMode = MODE_PICK_CONTACT;
} else if (People.CONTENT_TYPE.equals(type)) {
mMode = MODE_LEGACY_PICK_PERSON;
} else if (Phone.CONTENT_TYPE.equals(type)) {
mMode = MODE_PICK_PHONE;
} else if (Phones.CONTENT_TYPE.equals(type)) {
mMode = MODE_LEGACY_PICK_PHONE;
} else if (StructuredPostal.CONTENT_TYPE.equals(type)) {
mMode = MODE_PICK_POSTAL;
} else if (ContactMethods.CONTENT_POSTAL_TYPE.equals(type)) {
mMode = MODE_LEGACY_PICK_POSTAL;
}
} else if (Intent.ACTION_CREATE_SHORTCUT.equals(action)) {
if (component.equals("alias.DialShortcut")) {
mMode = MODE_PICK_PHONE;
mShortcutAction = Intent.ACTION_CALL;
mShowSearchSnippets = false;
setTitle(R.string.callShortcutActivityTitle);
} else if (component.equals("alias.MessageShortcut")) {
mMode = MODE_PICK_PHONE;
mShortcutAction = Intent.ACTION_SENDTO;
mShowSearchSnippets = false;
setTitle(R.string.messageShortcutActivityTitle);
} else if (mSearchMode) {
mMode = MODE_PICK_CONTACT;
mShortcutAction = Intent.ACTION_VIEW;
setTitle(R.string.shortcutActivityTitle);
} else {
mMode = MODE_PICK_OR_CREATE_CONTACT;
mShortcutAction = Intent.ACTION_VIEW;
setTitle(R.string.shortcutActivityTitle);
}
} else if (Intent.ACTION_GET_CONTENT.equals(action)) {
final String type = intent.resolveType(this);
if (Contacts.CONTENT_ITEM_TYPE.equals(type)) {
if (mSearchMode) {
mMode = MODE_PICK_CONTACT;
} else {
mMode = MODE_PICK_OR_CREATE_CONTACT;
}
} else if (Phone.CONTENT_ITEM_TYPE.equals(type)) {
mMode = MODE_PICK_PHONE;
} else if (Phones.CONTENT_ITEM_TYPE.equals(type)) {
mMode = MODE_LEGACY_PICK_PHONE;
} else if (StructuredPostal.CONTENT_ITEM_TYPE.equals(type)) {
mMode = MODE_PICK_POSTAL;
} else if (ContactMethods.CONTENT_POSTAL_ITEM_TYPE.equals(type)) {
mMode = MODE_LEGACY_PICK_POSTAL;
} else if (People.CONTENT_ITEM_TYPE.equals(type)) {
if (mSearchMode) {
mMode = MODE_LEGACY_PICK_PERSON;
} else {
mMode = MODE_LEGACY_PICK_OR_CREATE_PERSON;
}
}
} 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;
mInitialFilter = intent.getStringExtra(Insert.EMAIL);
} else if (intent.hasExtra(Insert.PHONE)) {
mMode = MODE_QUERY_PICK_TO_VIEW;
mQueryMode = QUERY_MODE_TEL;
mInitialFilter = intent.getStringExtra(Insert.PHONE);
} else {
// Otherwise handle the more normal search case
mMode = MODE_QUERY;
mShowSearchSnippets = true;
mInitialFilter = getIntent().getStringExtra(SearchManager.QUERY);
}
mSearchResultsMode = true;
} else if (ACTION_SEARCH_INTERNAL.equals(action)) {
String originalAction = null;
Bundle extras = intent.getExtras();
if (extras != null) {
originalAction = extras.getString(ContactsSearchManager.ORIGINAL_ACTION_EXTRA_KEY);
}
mShortcutAction = intent.getStringExtra(SHORTCUT_ACTION_KEY);
if (Intent.ACTION_INSERT_OR_EDIT.equals(originalAction)) {
mMode = MODE_QUERY_PICK_TO_EDIT;
mShowSearchSnippets = true;
mInitialFilter = getIntent().getStringExtra(SearchManager.QUERY);
} else if (mShortcutAction != null && intent.hasExtra(Insert.PHONE)) {
mMode = MODE_QUERY_PICK_PHONE;
mQueryMode = QUERY_MODE_TEL;
mInitialFilter = intent.getStringExtra(Insert.PHONE);
} else {
mMode = MODE_QUERY_PICK;
mQueryMode = QUERY_MODE_NONE;
mShowSearchSnippets = true;
mInitialFilter = getIntent().getStringExtra(SearchManager.QUERY);
}
mSearchResultsMode = true;
// 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)) {
Uri data = intent.getData();
Uri telUri = null;
if (sContactsIdMatcher.match(data) == CONTACTS_ID) {
long contactId = Long.valueOf(data.getLastPathSegment());
final Cursor cursor = queryPhoneNumbers(contactId);
if (cursor != null) {
if (cursor.getCount() == 1 && cursor.moveToFirst()) {
int phoneNumberIndex = cursor.getColumnIndex(Phone.NUMBER);
String phoneNumber = cursor.getString(phoneNumberIndex);
telUri = Uri.parse("tel:" + phoneNumber);
}
cursor.close();
}
}
// See if the suggestion was clicked with a search action key (call button)
Intent newIntent;
if ("call".equals(intent.getStringExtra(SearchManager.ACTION_MSG)) && telUri != null) {
newIntent = new Intent(Intent.ACTION_CALL_PRIVILEGED, telUri);
} else {
newIntent = new Intent(Intent.ACTION_VIEW, data);
}
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)) {
// TODO actually support this in EditContactActivity.
String number = intent.getData().getSchemeSpecificPart();
Intent newIntent = new Intent(Intent.ACTION_INSERT, Contacts.CONTENT_URI);
newIntent.putExtra(Intents.Insert.PHONE, number);
startActivity(newIntent);
finish();
return;
}
if (JOIN_AGGREGATE.equals(action)) {
if (mSearchMode) {
mMode = MODE_PICK_CONTACT;
} else {
mMode = MODE_JOIN_CONTACT;
mQueryAggregateId = intent.getLongExtra(EXTRA_AGGREGATE_ID, -1);
if (mQueryAggregateId == -1) {
Log.e(TAG, "Intent " + action + " is missing required extra: "
+ EXTRA_AGGREGATE_ID);
setResult(RESULT_CANCELED);
finish();
}
}
}
if (mMode == MODE_UNKNOWN) {
mMode = MODE_DEFAULT;
}
if (((mMode & MODE_MASK_SHOW_NUMBER_OF_CONTACTS) != 0 || mSearchMode)
&& !mSearchResultsMode) {
mShowNumberOfContacts = true;
}
if (mMode == MODE_JOIN_CONTACT) {
setContentView(R.layout.contacts_list_content_join);
TextView blurbView = (TextView)findViewById(R.id.join_contact_blurb);
String blurb = getString(R.string.blurbJoinContactDataWith,
getContactDisplayName(mQueryAggregateId));
blurbView.setText(blurb);
mJoinModeShowAllContacts = true;
} else if (mSearchMode) {
setContentView(R.layout.contacts_search_content);
} else if (mSearchResultsMode) {
setContentView(R.layout.contacts_list_search_results);
TextView titleText = (TextView)findViewById(R.id.search_results_for);
titleText.setText(Html.fromHtml(getString(R.string.search_results_for,
"<b>" + mInitialFilter + "</b>")));
} else {
setContentView(R.layout.contacts_list_content);
}
setupListView();
if (mSearchMode) {
setupSearchView();
}
mQueryHandler = new QueryHandler(this);
mJustCreated = true;
mSyncEnabled = true;
}
/**
* Register an observer for provider status changes - we will need to
* reflect them in the UI.
*/
private void registerProviderStatusObserver() {
getContentResolver().registerContentObserver(ProviderStatus.CONTENT_URI,
false, mProviderStatusObserver);
}
/**
* Register an observer for provider status changes - we will need to
* reflect them in the UI.
*/
private void unregisterProviderStatusObserver() {
getContentResolver().unregisterContentObserver(mProviderStatusObserver);
}
private void setupListView() {
final ListView list = getListView();
final LayoutInflater inflater = getLayoutInflater();
mHighlightingAnimation =
new NameHighlightingAnimation(list, TEXT_HIGHLIGHTING_ANIMATION_DURATION);
// Tell list view to not show dividers. We'll do it ourself so that we can *not* show
// them when an A-Z headers is visible.
list.setDividerHeight(0);
list.setOnCreateContextMenuListener(this);
mAdapter = new ContactItemListAdapter(this);
setListAdapter(mAdapter);
if (list instanceof PinnedHeaderListView && mAdapter.getDisplaySectionHeadersEnabled()) {
mPinnedHeaderBackgroundColor =
getResources().getColor(R.color.pinned_header_background);
PinnedHeaderListView pinnedHeaderList = (PinnedHeaderListView)list;
View pinnedHeader = inflater.inflate(R.layout.list_section, list, false);
pinnedHeaderList.setPinnedHeaderView(pinnedHeader);
}
list.setOnScrollListener(mAdapter);
list.setOnKeyListener(this);
list.setOnFocusChangeListener(this);
list.setOnTouchListener(this);
// We manually save/restore the listview state
list.setSaveEnabled(false);
}
/**
* Configures search UI.
*/
private void setupSearchView() {
mSearchEditText = (SearchEditText)findViewById(R.id.search_src_text);
mSearchEditText.addTextChangedListener(this);
mSearchEditText.setOnEditorActionListener(this);
mSearchEditText.setText(mInitialFilter);
}
private String getContactDisplayName(long contactId) {
String contactName = null;
Cursor c = getContentResolver().query(
ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId),
new String[] {Contacts.DISPLAY_NAME}, null, null, null);
try {
if (c != null && c.moveToFirst()) {
contactName = c.getString(0);
}
} finally {
if (c != null) {
c.close();
}
}
if (contactName == null) {
contactName = "";
}
return contactName;
}
private int getSummaryDisplayNameColumnIndex() {
if (mDisplayOrder == ContactsContract.Preferences.DISPLAY_ORDER_PRIMARY) {
return SUMMARY_DISPLAY_NAME_PRIMARY_COLUMN_INDEX;
} else {
return SUMMARY_DISPLAY_NAME_ALTERNATIVE_COLUMN_INDEX;
}
}
/** {@inheritDoc} */
public void onClick(View v) {
int id = v.getId();
switch (id) {
// TODO a better way of identifying the button
case android.R.id.button1: {
final int position = (Integer)v.getTag();
Cursor c = mAdapter.getCursor();
if (c != null) {
c.moveToPosition(position);
callContact(c);
}
break;
}
}
}
private void setEmptyText() {
if (mMode == MODE_JOIN_CONTACT || mSearchMode) {
return;
}
TextView empty = (TextView) findViewById(R.id.emptyText);
if (mDisplayOnlyPhones) {
empty.setText(getText(R.string.noContactsWithPhoneNumbers));
} else if (mMode == MODE_STREQUENT || mMode == MODE_STARRED) {
empty.setText(getText(R.string.noFavoritesHelpText));
} else if (mMode == MODE_QUERY || mMode == MODE_QUERY_PICK
|| mMode == MODE_QUERY_PICK_PHONE || mMode == MODE_QUERY_PICK_TO_VIEW
|| mMode == MODE_QUERY_PICK_TO_EDIT) {
empty.setText(getText(R.string.noMatchingContacts));
} else {
boolean hasSim = ((TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE))
.hasIccCard();
boolean createShortcut = Intent.ACTION_CREATE_SHORTCUT.equals(getIntent().getAction());
if (isSyncActive()) {
if (createShortcut) {
// Help text is the same no matter whether there is SIM or not.
empty.setText(getText(R.string.noContactsHelpTextWithSyncForCreateShortcut));
} else if (hasSim) {
empty.setText(getText(R.string.noContactsHelpTextWithSync));
} else {
empty.setText(getText(R.string.noContactsNoSimHelpTextWithSync));
}
} else {
if (createShortcut) {
// Help text is the same no matter whether there is SIM or not.
empty.setText(getText(R.string.noContactsHelpTextForCreateShortcut));
} else if (hasSim) {
empty.setText(getText(R.string.noContactsHelpText));
} else {
empty.setText(getText(R.string.noContactsNoSimHelpText));
}
}
}
}
private boolean isSyncActive() {
Account[] accounts = AccountManager.get(this).getAccounts();
if (accounts != null && accounts.length > 0) {
IContentService contentService = ContentResolver.getContentService();
for (Account account : accounts) {
try {
if (contentService.isSyncActive(account, ContactsContract.AUTHORITY)) {
return true;
}
} catch (RemoteException e) {
Log.e(TAG, "Could not get the sync status");
}
}
}
return false;
}
private void buildUserGroupUri(String group) {
mGroupUri = Uri.withAppendedPath(Contacts.CONTENT_GROUP_URI, group);
}
/**
* Sets the mode when the request is for "default"
*/
private void setDefaultMode() {
// Load the preferences
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
mDisplayOnlyPhones = prefs.getBoolean(Prefs.DISPLAY_ONLY_PHONES,
Prefs.DISPLAY_ONLY_PHONES_DEFAULT);
}
@Override
protected void onDestroy() {
super.onDestroy();
mPhotoLoader.stop();
}
@Override
protected void onStart() {
super.onStart();
mContactsPrefs.registerChangeListener(mPreferencesChangeListener);
}
@Override
protected void onPause() {
super.onPause();
unregisterProviderStatusObserver();
}
@Override
protected void onResume() {
super.onResume();
registerProviderStatusObserver();
mPhotoLoader.resume();
Activity parent = getParent();
// Do this before setting the filter. The filter thread relies
// on some state that is initialized in setDefaultMode
if (mMode == MODE_DEFAULT) {
// 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 (mSearchMode) {
mSearchEditText.requestFocus();
}
if (!mSearchMode && !checkProviderState(mJustCreated)) {
return;
}
if (mJustCreated) {
// 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;
mSearchInitiated = false;
}
/**
* Obtains the contacts provider status and configures the UI accordingly.
*
* @param loadData true if the method needs to start a query when the
* provider is in the normal state
* @return true if the provider status is normal
*/
private boolean checkProviderState(boolean loadData) {
View importFailureView = findViewById(R.id.import_failure);
if (importFailureView == null) {
return true;
}
TextView messageView = (TextView) findViewById(R.id.emptyText);
// This query can be performed on the UI thread because
// the API explicitly allows such use.
Cursor cursor = getContentResolver().query(ProviderStatus.CONTENT_URI, new String[] {
ProviderStatus.STATUS, ProviderStatus.DATA1
}, null, null, null);
try {
if (cursor.moveToFirst()) {
int status = cursor.getInt(0);
if (status != mProviderStatus) {
mProviderStatus = status;
switch (status) {
case ProviderStatus.STATUS_NORMAL:
mAdapter.notifyDataSetInvalidated();
if (loadData) {
startQuery();
}
break;
case ProviderStatus.STATUS_CHANGING_LOCALE:
messageView.setText(R.string.locale_change_in_progress);
mAdapter.changeCursor(null);
mAdapter.notifyDataSetInvalidated();
break;
case ProviderStatus.STATUS_UPGRADING:
messageView.setText(R.string.upgrade_in_progress);
mAdapter.changeCursor(null);
mAdapter.notifyDataSetInvalidated();
break;
case ProviderStatus.STATUS_UPGRADE_OUT_OF_MEMORY:
long size = cursor.getLong(1);
String message = getResources().getString(
R.string.upgrade_out_of_memory, new Object[] {size});
messageView.setText(message);
configureImportFailureView(importFailureView);
mAdapter.changeCursor(null);
mAdapter.notifyDataSetInvalidated();
break;
}
}
}
} finally {
cursor.close();
}
importFailureView.setVisibility(
mProviderStatus == ProviderStatus.STATUS_UPGRADE_OUT_OF_MEMORY
? View.VISIBLE
: View.GONE);
return mProviderStatus == ProviderStatus.STATUS_NORMAL;
}
private void configureImportFailureView(View importFailureView) {
OnClickListener listener = new OnClickListener(){
public void onClick(View v) {
switch(v.getId()) {
case R.id.import_failure_uninstall_apps: {
startActivity(new Intent(Settings.ACTION_MANAGE_APPLICATIONS_SETTINGS));
break;
}
case R.id.import_failure_retry_upgrade: {
// Send a provider status update, which will trigger a retry
ContentValues values = new ContentValues();
values.put(ProviderStatus.STATUS, ProviderStatus.STATUS_UPGRADING);
getContentResolver().update(ProviderStatus.CONTENT_URI, values, null, null);
break;
}
}
}};
Button uninstallApps = (Button) findViewById(R.id.import_failure_uninstall_apps);
uninstallApps.setOnClickListener(listener);
Button retryUpgrade = (Button) findViewById(R.id.import_failure_retry_upgrade);
retryUpgrade.setOnClickListener(listener);
}
private String getTextFilter() {
if (mSearchEditText != null) {
return mSearchEditText.getText().toString();
}
return null;
}
@Override
protected void onRestart() {
super.onRestart();
if (!checkProviderState(false)) {
return;
}
// 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(getTextFilter())) {
startQuery();
} else {
// Run the filtered query on the adapter
mAdapter.onContentChanged();
}
}
@Override
protected void onSaveInstanceState(Bundle icicle) {
super.onSaveInstanceState(icicle);
// Save list state in the bundle so we can restore it after the QueryHandler has run
if (mList != null) {
icicle.putParcelable(LIST_STATE_KEY, mList.onSaveInstanceState());
}
}
@Override
protected void onRestoreInstanceState(Bundle icicle) {
super.onRestoreInstanceState(icicle);
// Retrieve list state. This will be applied after the QueryHandler has run
mListState = icicle.getParcelable(LIST_STATE_KEY);
}
@Override
protected void onStop() {
super.onStop();
mContactsPrefs.unregisterChangeListener();
mAdapter.setSuggestionsCursor(null);
mAdapter.changeCursor(null);
if (mMode == MODE_QUERY) {
// Make sure the search box is closed
SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE);
searchManager.stopSearch();
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(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;
}
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.list, menu);
return true;
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
final boolean defaultMode = (mMode == MODE_DEFAULT);
menu.findItem(R.id.menu_display_groups).setVisible(defaultMode);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_display_groups: {
final Intent intent = new Intent(this, ContactsPreferencesActivity.class);
startActivityForResult(intent, SUBACTIVITY_DISPLAY_GROUP);
return true;
}
case R.id.menu_search: {
onSearchRequested();
return true;
}
case R.id.menu_add: {
final Intent intent = new Intent(Intent.ACTION_INSERT, Contacts.CONTENT_URI);
startActivity(intent);
return true;
}
case R.id.menu_import_export: {
displayImportExportDialog();
return true;
}
case R.id.menu_accounts: {
final Intent intent = new Intent(Settings.ACTION_SYNC_SETTINGS);
intent.putExtra(AUTHORITIES_FILTER_KEY, new String[] {
ContactsContract.AUTHORITY
});
startActivity(intent);
return true;
}
}
return false;
}
@Override
public void startSearch(String initialQuery, boolean selectInitialQuery, Bundle appSearchData,
boolean globalSearch) {
if (mProviderStatus != ProviderStatus.STATUS_NORMAL) {
return;
}
if (globalSearch) {
super.startSearch(initialQuery, selectInitialQuery, appSearchData, globalSearch);
} else {
if (!mSearchMode && (mMode & MODE_MASK_NO_FILTER) == 0) {
if ((mMode & MODE_MASK_PICKER) != 0) {
ContactsSearchManager.startSearchForResult(this, initialQuery,
SUBACTIVITY_FILTER);
} else {
ContactsSearchManager.startSearch(this, initialQuery);
}
}
}
}
/**
* Performs filtering of the list based on the search query entered in the
* search text edit.
*/
protected void onSearchTextChanged() {
// Set the proper empty string
setEmptyText();
Filter filter = mAdapter.getFilter();
filter.filter(getTextFilter());
}
/**
* Starts a new activity that will run a search query and display search results.
*/
private void doSearch() {
String query = getTextFilter();
if (TextUtils.isEmpty(query)) {
return;
}
Intent intent = new Intent(this, SearchResultsActivity.class);
Intent originalIntent = getIntent();
Bundle originalExtras = originalIntent.getExtras();
if (originalExtras != null) {
intent.putExtras(originalExtras);
}
intent.putExtra(SearchManager.QUERY, query);
if ((mMode & MODE_MASK_PICKER) != 0) {
intent.setAction(ACTION_SEARCH_INTERNAL);
intent.putExtra(SHORTCUT_ACTION_KEY, mShortcutAction);
if (mShortcutAction != null) {
if (Intent.ACTION_CALL.equals(mShortcutAction)
|| Intent.ACTION_SENDTO.equals(mShortcutAction)) {
intent.putExtra(Insert.PHONE, query);
}
} else {
switch (mQueryMode) {
case QUERY_MODE_MAILTO:
intent.putExtra(Insert.EMAIL, query);
break;
case QUERY_MODE_TEL:
intent.putExtra(Insert.PHONE, query);
break;
}
}
startActivityForResult(intent, SUBACTIVITY_SEARCH);
} else {
intent.setAction(Intent.ACTION_SEARCH);
startActivity(intent);
}
}
@Override
protected Dialog onCreateDialog(int id, Bundle bundle) {
switch (id) {
case R.string.import_from_sim:
case R.string.import_from_sdcard: {
return AccountSelectionUtil.getSelectAccountDialog(this, id);
}
case R.id.dialog_sdcard_not_found: {
return new AlertDialog.Builder(this)
.setTitle(R.string.no_sdcard_title)
.setIcon(android.R.drawable.ic_dialog_alert)
.setMessage(R.string.no_sdcard_message)
.setPositiveButton(android.R.string.ok, null).create();
}
case R.id.dialog_delete_contact_confirmation: {
return new AlertDialog.Builder(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()).create();
}
case R.id.dialog_readonly_contact_hide_confirmation: {
return new AlertDialog.Builder(this)
.setTitle(R.string.deleteConfirmation_title)
.setIcon(android.R.drawable.ic_dialog_alert)
.setMessage(R.string.readOnlyContactWarning)
.setNegativeButton(android.R.string.cancel, null)
.setPositiveButton(android.R.string.ok,
new DeleteClickListener()).create();
}
case R.id.dialog_readonly_contact_delete_confirmation: {
return new AlertDialog.Builder(this)
.setTitle(R.string.deleteConfirmation_title)
.setIcon(android.R.drawable.ic_dialog_alert)
.setMessage(R.string.readOnlyContactDeleteConfirmation)
.setNegativeButton(android.R.string.cancel, null)
.setPositiveButton(android.R.string.ok,
new DeleteClickListener()).create();
}
case R.id.dialog_multiple_contact_delete_confirmation: {
return new AlertDialog.Builder(this)
.setTitle(R.string.deleteConfirmation_title)
.setIcon(android.R.drawable.ic_dialog_alert)
.setMessage(R.string.multipleContactDeleteConfirmation)
.setNegativeButton(android.R.string.cancel, null)
.setPositiveButton(android.R.string.ok,
new DeleteClickListener()).create();
}
}
return super.onCreateDialog(id, bundle);
}
/**
* Create a {@link Dialog} that allows the user to pick from a bulk import
* or bulk export task across all contacts.
*/
private void displayImportExportDialog() {
// Wrap our context to inflate list items using correct theme
final Context dialogContext = new ContextThemeWrapper(this, android.R.style.Theme_Light);
final Resources res = dialogContext.getResources();
final LayoutInflater dialogInflater = (LayoutInflater)dialogContext
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
// Adapter that shows a list of string resources
final ArrayAdapter<Integer> adapter = new ArrayAdapter<Integer>(this,
android.R.layout.simple_list_item_1) {
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = dialogInflater.inflate(android.R.layout.simple_list_item_1,
parent, false);
}
final int resId = this.getItem(position);
((TextView)convertView).setText(resId);
return convertView;
}
};
if (TelephonyManager.getDefault().hasIccCard()) {
adapter.add(R.string.import_from_sim);
}
if (res.getBoolean(R.bool.config_allow_import_from_sdcard)) {
adapter.add(R.string.import_from_sdcard);
}
if (res.getBoolean(R.bool.config_allow_export_to_sdcard)) {
adapter.add(R.string.export_to_sdcard);
}
if (res.getBoolean(R.bool.config_allow_share_visible_contacts)) {
adapter.add(R.string.share_visible_contacts);
}
final DialogInterface.OnClickListener clickListener =
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
final int resId = adapter.getItem(which);
switch (resId) {
case R.string.import_from_sim:
case R.string.import_from_sdcard: {
handleImportRequest(resId);
break;
}
case R.string.export_to_sdcard: {
Context context = ContactsListActivity.this;
Intent exportIntent = new Intent(context, ExportVCardActivity.class);
context.startActivity(exportIntent);
break;
}
case R.string.share_visible_contacts: {
doShareVisibleContacts();
break;
}
default: {
Log.e(TAG, "Unexpected resource: " +
getResources().getResourceEntryName(resId));
}
}
}
};
new AlertDialog.Builder(this)
.setTitle(R.string.dialog_import_export)
.setNegativeButton(android.R.string.cancel, null)
.setSingleChoiceItems(adapter, -1, clickListener)
.show();
}
private void doShareVisibleContacts() {
final Cursor cursor = getContentResolver().query(Contacts.CONTENT_URI,
sLookupProjection, getContactSelection(), null, null);
try {
if (!cursor.moveToFirst()) {
Toast.makeText(this, R.string.share_error, Toast.LENGTH_SHORT).show();
return;
}
StringBuilder uriListBuilder = new StringBuilder();
int index = 0;
for (;!cursor.isAfterLast(); cursor.moveToNext()) {
if (index != 0)
uriListBuilder.append(':');
uriListBuilder.append(cursor.getString(0));
index++;
}
Uri uri = Uri.withAppendedPath(
Contacts.CONTENT_MULTI_VCARD_URI,
Uri.encode(uriListBuilder.toString()));
final Intent intent = new Intent(Intent.ACTION_SEND);
intent.setType(Contacts.CONTENT_VCARD_TYPE);
intent.putExtra(Intent.EXTRA_STREAM, uri);
startActivity(intent);
} finally {
cursor.close();
}
}
private void handleImportRequest(int resId) {
// There's three possibilities:
// - more than one accounts -> ask the user
// - just one account -> use the account without asking the user
// - no account -> use phone-local storage without asking the user
final Sources sources = Sources.getInstance(this);
final List<Account> accountList = sources.getAccounts(true);
final int size = accountList.size();
if (size > 1) {
showDialog(resId);
return;
}
AccountSelectionUtil.doImport(this, resId, (size == 1 ? accountList.get(0) : null));
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
case SUBACTIVITY_NEW_CONTACT:
if (resultCode == RESULT_OK) {
returnPickerResult(null, data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME),
data.getData(), (mMode & MODE_MASK_PICKER) != 0
? Intent.FLAG_GRANT_READ_URI_PERMISSION : 0);
}
break;
case SUBACTIVITY_VIEW_CONTACT:
if (resultCode == RESULT_OK) {
mAdapter.notifyDataSetChanged();
}
break;
case SUBACTIVITY_DISPLAY_GROUP:
// Mark as just created so we re-run the view query
mJustCreated = true;
break;
case SUBACTIVITY_FILTER:
case SUBACTIVITY_SEARCH:
// Pass through results of filter or search UI
if (resultCode == RESULT_OK) {
setResult(RESULT_OK, data);
finish();
}
break;
}
}
@Override
public void onCreateContextMenu(ContextMenu menu, View view, 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 contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, id);
long rawContactId = ContactsUtils.queryForRawContactId(getContentResolver(), id);
Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId);
// Setup the menu header
menu.setHeaderTitle(cursor.getString(getSummaryDisplayNameColumnIndex()));
// View contact details
final Intent viewContactIntent = new Intent(Intent.ACTION_VIEW, contactUri);
StickyTabs.setTab(viewContactIntent, getIntent());
menu.add(0, MENU_ITEM_VIEW_CONTACT, 0, R.string.menu_viewContact)
.setIntent(viewContactIntent);
if (cursor.getInt(SUMMARY_HAS_PHONE_COLUMN_INDEX) != 0) {
// Calling contact
menu.add(0, MENU_ITEM_CALL, 0, getString(R.string.menu_call));
// Send SMS item
menu.add(0, MENU_ITEM_SEND_SMS, 0, getString(R.string.menu_sendSMS));
}
// Star toggling
int starState = cursor.getInt(SUMMARY_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, rawContactUri));
menu.add(0, MENU_ITEM_DELETE, 0, R.string.menu_deleteContact);
}
@Override
public boolean onContextItemSelected(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(Contacts.STARRED, cursor.getInt(SUMMARY_STARRED_COLUMN_INDEX) == 0 ? 1 : 0);
final Uri selectedUri = this.getContactUri(info.position);
getContentResolver().update(selectedUri, values, null, null);
return true;
}
case MENU_ITEM_CALL: {
callContact(cursor);
return true;
}
case MENU_ITEM_SEND_SMS: {
smsContact(cursor);
return true;
}
case MENU_ITEM_DELETE: {
doContactDelete(getContactUri(info.position));
return true;
}
}
return super.onContextItemSelected(item);
}
/**
* Event handler for the use case where the user starts typing without
* bringing up the search UI first.
*/
public boolean onKey(View v, int keyCode, KeyEvent event) {
if (!mSearchMode && (mMode & MODE_MASK_NO_FILTER) == 0 && !mSearchInitiated) {
int unicodeChar = event.getUnicodeChar();
if (unicodeChar != 0) {
mSearchInitiated = true;
startSearch(new String(new int[]{unicodeChar}, 0, 1), false, null, false);
return true;
}
}
return false;
}
/**
* Event handler for search UI.
*/
public void afterTextChanged(Editable s) {
onSearchTextChanged();
}
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
/**
* Event handler for search UI.
*/
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if (actionId == EditorInfo.IME_ACTION_DONE) {
hideSoftKeyboard();
if (TextUtils.isEmpty(getTextFilter())) {
finish();
}
return true;
}
return false;
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
switch (keyCode) {
case KeyEvent.KEYCODE_CALL: {
if (callSelection()) {
return true;
}
break;
}
case KeyEvent.KEYCODE_DEL: {
if (deleteSelection()) {
return true;
}
break;
}
}
return super.onKeyDown(keyCode, event);
}
private boolean deleteSelection() {
if ((mMode & MODE_MASK_PICKER) != 0) {
return false;
}
final int position = getListView().getSelectedItemPosition();
if (position != ListView.INVALID_POSITION) {
Uri contactUri = getContactUri(position);
if (contactUri != null) {
doContactDelete(contactUri);
return true;
}
}
return false;
}
/**
* Prompt the user before deleting the given {@link Contacts} entry.
*/
protected void doContactDelete(Uri contactUri) {
mReadOnlySourcesCnt = 0;
mWritableSourcesCnt = 0;
mWritableRawContactIds.clear();
Sources sources = Sources.getInstance(ContactsListActivity.this);
Cursor c = getContentResolver().query(RawContacts.CONTENT_URI, RAW_CONTACTS_PROJECTION,
RawContacts.CONTACT_ID + "=" + ContentUris.parseId(contactUri), null,
null);
if (c != null) {
try {
while (c.moveToNext()) {
final String accountType = c.getString(2);
final long rawContactId = c.getLong(0);
ContactsSource contactsSource = sources.getInflatedSource(accountType,
ContactsSource.LEVEL_SUMMARY);
if (contactsSource != null && contactsSource.readOnly) {
mReadOnlySourcesCnt += 1;
} else {
mWritableSourcesCnt += 1;
mWritableRawContactIds.add(rawContactId);
}
}
} finally {
c.close();
}
}
mSelectedContactUri = contactUri;
if (mReadOnlySourcesCnt > 0 && mWritableSourcesCnt > 0) {
showDialog(R.id.dialog_readonly_contact_delete_confirmation);
} else if (mReadOnlySourcesCnt > 0 && mWritableSourcesCnt == 0) {
showDialog(R.id.dialog_readonly_contact_hide_confirmation);
} else if (mReadOnlySourcesCnt == 0 && mWritableSourcesCnt > 1) {
showDialog(R.id.dialog_multiple_contact_delete_confirmation);
} else {
showDialog(R.id.dialog_delete_contact_confirmation);
}
}
/**
* Dismisses the soft keyboard when the list takes focus.
*/
public void onFocusChange(View view, boolean hasFocus) {
if (view == getListView() && hasFocus) {
hideSoftKeyboard();
}
}
/**
* Dismisses the soft keyboard when the list takes focus.
*/
public boolean onTouch(View view, MotionEvent event) {
if (view == getListView()) {
hideSoftKeyboard();
}
return false;
}
/**
* Dismisses the search UI along with the keyboard if the filter text is empty.
*/
public boolean onKeyPreIme(int keyCode, KeyEvent event) {
if (mSearchMode && keyCode == KeyEvent.KEYCODE_BACK && TextUtils.isEmpty(getTextFilter())) {
hideSoftKeyboard();
onBackPressed();
return true;
}
return false;
}
@Override
protected void onListItemClick(ListView l, View v, int position, long id) {
hideSoftKeyboard();
if (mSearchMode && mAdapter.isSearchAllContactsItemPosition(position)) {
doSearch();
} else if (mMode == MODE_INSERT_OR_EDIT_CONTACT || mMode == MODE_QUERY_PICK_TO_EDIT) {
Intent intent;
if (position == 0 && !mSearchMode && mMode != MODE_QUERY_PICK_TO_EDIT) {
intent = new Intent(Intent.ACTION_INSERT, Contacts.CONTENT_URI);
} else {
intent = new Intent(Intent.ACTION_EDIT, getSelectedUri(position));
}
intent.setFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
Bundle extras = getIntent().getExtras();
if (extras != null) {
intent.putExtras(extras);
}
intent.putExtra(KEY_PICKER_MODE, (mMode & MODE_MASK_PICKER) == MODE_MASK_PICKER);
startActivity(intent);
finish();
} else if ((mMode & MODE_MASK_CREATE_NEW) == MODE_MASK_CREATE_NEW
&& position == 0) {
Intent newContact = new Intent(Intents.Insert.ACTION, Contacts.CONTENT_URI);
startActivityForResult(newContact, SUBACTIVITY_NEW_CONTACT);
} else if (mMode == MODE_JOIN_CONTACT && id == JOIN_MODE_SHOW_ALL_CONTACTS_ID) {
mJoinModeShowAllContacts = false;
startQuery();
} else if (id > 0) {
final Uri uri = getSelectedUri(position);
if ((mMode & MODE_MASK_PICKER) == 0) {
final Intent intent = new Intent(Intent.ACTION_VIEW, uri);
StickyTabs.setTab(intent, getIntent());
startActivityForResult(intent, SUBACTIVITY_VIEW_CONTACT);
} else if (mMode == MODE_JOIN_CONTACT) {
returnPickerResult(null, null, uri, 0);
} else if (mMode == MODE_QUERY_PICK_TO_VIEW) {
// Started with query that should launch to view contact
final Intent intent = new Intent(Intent.ACTION_VIEW, uri);
startActivity(intent);
finish();
} else if (mMode == MODE_PICK_PHONE || mMode == MODE_QUERY_PICK_PHONE) {
Cursor c = (Cursor) mAdapter.getItem(position);
returnPickerResult(c, c.getString(PHONE_DISPLAY_NAME_COLUMN_INDEX), uri,
Intent.FLAG_GRANT_READ_URI_PERMISSION);
} else if ((mMode & MODE_MASK_PICKER) != 0) {
Cursor c = (Cursor) mAdapter.getItem(position);
returnPickerResult(c, c.getString(getSummaryDisplayNameColumnIndex()), uri,
Intent.FLAG_GRANT_READ_URI_PERMISSION);
} else if (mMode == MODE_PICK_POSTAL
|| mMode == MODE_LEGACY_PICK_POSTAL
|| mMode == MODE_LEGACY_PICK_PHONE) {
returnPickerResult(null, null, uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
}
} else {
signalError();
}
}
private void hideSoftKeyboard() {
// Hide soft keyboard, if visible
InputMethodManager inputMethodManager = (InputMethodManager)
getSystemService(Context.INPUT_METHOD_SERVICE);
inputMethodManager.hideSoftInputFromWindow(mList.getWindowToken(), 0);
}
/**
* @param selectedUri In most cases, this should be a lookup {@link Uri}, possibly
* generated through {@link Contacts#getLookupUri(long, String)}.
*/
private void returnPickerResult(Cursor c, String name, Uri selectedUri, int uriPerms) {
final Intent intent = new Intent();
if (mShortcutAction != null) {
Intent shortcutIntent;
if (Intent.ACTION_VIEW.equals(mShortcutAction)) {
// This is a simple shortcut to view a contact.
shortcutIntent = new Intent(ContactsContract.QuickContact.ACTION_QUICK_CONTACT);
shortcutIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
shortcutIntent.setData(selectedUri);
shortcutIntent.putExtra(ContactsContract.QuickContact.EXTRA_MODE,
ContactsContract.QuickContact.MODE_LARGE);
shortcutIntent.putExtra(ContactsContract.QuickContact.EXTRA_EXCLUDE_MIMES,
(String[]) null);
final Bitmap icon = framePhoto(loadContactPhoto(selectedUri, null));
if (icon != null) {
intent.putExtra(Intent.EXTRA_SHORTCUT_ICON, scaleToAppIconSize(icon));
} else {
intent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE,
Intent.ShortcutIconResource.fromContext(this,
R.drawable.ic_launcher_shortcut_contact));
}
} else {
// This is a direct dial or sms shortcut.
String number = c.getString(PHONE_NUMBER_COLUMN_INDEX);
int type = c.getInt(PHONE_TYPE_COLUMN_INDEX);
String scheme;
int resid;
if (Intent.ACTION_CALL.equals(mShortcutAction)) {
scheme = Constants.SCHEME_TEL;
resid = R.drawable.badge_action_call;
} else {
scheme = Constants.SCHEME_SMSTO;
resid = R.drawable.badge_action_sms;
}
// Make the URI a direct tel: URI so that it will always continue to work
Uri phoneUri = Uri.fromParts(scheme, number, null);
shortcutIntent = new Intent(mShortcutAction, phoneUri);
intent.putExtra(Intent.EXTRA_SHORTCUT_ICON,
generatePhoneNumberIcon(selectedUri, type, resid));
}
shortcutIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, name);
setResult(RESULT_OK, intent);
} else {
intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, name);
intent.addFlags(uriPerms);
setResult(RESULT_OK, intent.setData(selectedUri));
}
finish();
}
private Bitmap framePhoto(Bitmap photo) {
final Resources r = getResources();
final Drawable frame = r.getDrawable(com.android.internal.R.drawable.quickcontact_badge);
final int width = r.getDimensionPixelSize(R.dimen.contact_shortcut_frame_width);
final int height = r.getDimensionPixelSize(R.dimen.contact_shortcut_frame_height);
frame.setBounds(0, 0, width, height);
final Rect padding = new Rect();
frame.getPadding(padding);
final Rect source = new Rect(0, 0, photo.getWidth(), photo.getHeight());
final Rect destination = new Rect(padding.left, padding.top,
width - padding.right, height - padding.bottom);
final int d = Math.max(width, height);
final Bitmap b = Bitmap.createBitmap(d, d, Bitmap.Config.ARGB_8888);
final Canvas c = new Canvas(b);
c.translate((d - width) / 2.0f, (d - height) / 2.0f);
frame.draw(c);
c.drawBitmap(photo, source, destination, new Paint(Paint.FILTER_BITMAP_FLAG));
return b;
}
/**
* Generates a phone number shortcut icon. Adds an overlay describing the type of the phone
* number, and if there is a photo also adds the call action icon.
*
* @param lookupUri The person the phone number belongs to
* @param type The type of the phone number
* @param actionResId The ID for the action resource
* @return The bitmap for the icon
*/
private Bitmap generatePhoneNumberIcon(Uri lookupUri, int type, int actionResId) {
final Resources r = getResources();
boolean drawPhoneOverlay = true;
final float scaleDensity = getResources().getDisplayMetrics().scaledDensity;
Bitmap photo = loadContactPhoto(lookupUri, null);
if (photo == null) {
// If there isn't a photo use the generic phone action icon instead
Bitmap phoneIcon = getPhoneActionIcon(r, actionResId);
if (phoneIcon != null) {
photo = phoneIcon;
drawPhoneOverlay = false;
} else {
return null;
}
}
// Setup the drawing classes
Bitmap icon = createShortcutBitmap();
Canvas canvas = new Canvas(icon);
// Copy in the photo
Paint photoPaint = new Paint();
photoPaint.setDither(true);
photoPaint.setFilterBitmap(true);
Rect src = new Rect(0,0, photo.getWidth(),photo.getHeight());
Rect dst = new Rect(0,0, mIconSize, mIconSize);
canvas.drawBitmap(photo, src, dst, photoPaint);
// Create an overlay for the phone number type
String overlay = null;
switch (type) {
case Phone.TYPE_HOME:
overlay = getString(R.string.type_short_home);
break;
case Phone.TYPE_MOBILE:
overlay = getString(R.string.type_short_mobile);
break;
case Phone.TYPE_WORK:
overlay = getString(R.string.type_short_work);
break;
case Phone.TYPE_PAGER:
overlay = getString(R.string.type_short_pager);
break;
case Phone.TYPE_OTHER:
overlay = getString(R.string.type_short_other);
break;
}
if (overlay != null) {
Paint textPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DEV_KERN_TEXT_FLAG);
textPaint.setTextSize(20.0f * scaleDensity);
textPaint.setTypeface(Typeface.DEFAULT_BOLD);
textPaint.setColor(r.getColor(R.color.textColorIconOverlay));
textPaint.setShadowLayer(3f, 1, 1, r.getColor(R.color.textColorIconOverlayShadow));
canvas.drawText(overlay, 2 * scaleDensity, 16 * scaleDensity, textPaint);
}
// Draw the phone action icon as an overlay
if (ENABLE_ACTION_ICON_OVERLAYS && drawPhoneOverlay) {
Bitmap phoneIcon = getPhoneActionIcon(r, actionResId);
if (phoneIcon != null) {
src.set(0, 0, phoneIcon.getWidth(), phoneIcon.getHeight());
int iconWidth = icon.getWidth();
dst.set(iconWidth - ((int) (20 * scaleDensity)), -1,
iconWidth, ((int) (19 * scaleDensity)));
canvas.drawBitmap(phoneIcon, src, dst, photoPaint);
}
}
return icon;
}
private Bitmap scaleToAppIconSize(Bitmap photo) {
// Setup the drawing classes
Bitmap icon = createShortcutBitmap();
Canvas canvas = new Canvas(icon);
// Copy in the photo
Paint photoPaint = new Paint();
photoPaint.setDither(true);
photoPaint.setFilterBitmap(true);
Rect src = new Rect(0,0, photo.getWidth(),photo.getHeight());
Rect dst = new Rect(0,0, mIconSize, mIconSize);
canvas.drawBitmap(photo, src, dst, photoPaint);
return icon;
}
private Bitmap createShortcutBitmap() {
return Bitmap.createBitmap(mIconSize, mIconSize, Bitmap.Config.ARGB_8888);
}
/**
* Returns the icon for the phone call action.
*
* @param r The resources to load the icon from
* @param resId The resource ID to load
* @return the icon for the phone call action
*/
private Bitmap getPhoneActionIcon(Resources r, int resId) {
Drawable phoneIcon = r.getDrawable(resId);
if (phoneIcon instanceof BitmapDrawable) {
BitmapDrawable bd = (BitmapDrawable) phoneIcon;
return bd.getBitmap();
} else {
return null;
}
}
private Uri getUriToQuery() {
switch(mMode) {
case MODE_JOIN_CONTACT:
return getJoinSuggestionsUri(null);
case MODE_FREQUENT:
case MODE_STARRED:
return Contacts.CONTENT_URI;
case MODE_DEFAULT:
case MODE_CUSTOM:
case MODE_INSERT_OR_EDIT_CONTACT:
case MODE_PICK_CONTACT:
case MODE_PICK_OR_CREATE_CONTACT:{
return CONTACTS_CONTENT_URI_WITH_LETTER_COUNTS;
}
case MODE_STREQUENT: {
return Contacts.CONTENT_STREQUENT_URI;
}
case MODE_LEGACY_PICK_PERSON:
case MODE_LEGACY_PICK_OR_CREATE_PERSON: {
return People.CONTENT_URI;
}
case MODE_PICK_PHONE: {
return buildSectionIndexerUri(Phone.CONTENT_URI);
}
case MODE_LEGACY_PICK_PHONE: {
return Phones.CONTENT_URI;
}
case MODE_PICK_POSTAL: {
return buildSectionIndexerUri(StructuredPostal.CONTENT_URI);
}
case MODE_LEGACY_PICK_POSTAL: {
return ContactMethods.CONTENT_URI;
}
case MODE_QUERY_PICK_TO_VIEW: {
if (mQueryMode == QUERY_MODE_MAILTO) {
return Uri.withAppendedPath(Email.CONTENT_FILTER_URI,
Uri.encode(mInitialFilter));
} else if (mQueryMode == QUERY_MODE_TEL) {
return Uri.withAppendedPath(Phone.CONTENT_FILTER_URI,
Uri.encode(mInitialFilter));
}
return CONTACTS_CONTENT_URI_WITH_LETTER_COUNTS;
}
case MODE_QUERY:
case MODE_QUERY_PICK:
case MODE_QUERY_PICK_TO_EDIT: {
return getContactFilterUri(mInitialFilter);
}
case MODE_QUERY_PICK_PHONE: {
return Uri.withAppendedPath(Phone.CONTENT_FILTER_URI,
Uri.encode(mInitialFilter));
}
case MODE_GROUP: {
return mGroupUri;
}
default: {
throw new IllegalStateException("Can't generate URI: Unsupported Mode.");
}
}
}
/**
* Build the {@link Contacts#CONTENT_LOOKUP_URI} for the given
* {@link ListView} position, using {@link #mAdapter}.
*/
private Uri getContactUri(int position) {
if (position == ListView.INVALID_POSITION) {
throw new IllegalArgumentException("Position not in list bounds");
}
final Cursor cursor = (Cursor)mAdapter.getItem(position);
if (cursor == null) {
return null;
}
switch(mMode) {
case MODE_LEGACY_PICK_PERSON:
case MODE_LEGACY_PICK_OR_CREATE_PERSON: {
final long personId = cursor.getLong(SUMMARY_ID_COLUMN_INDEX);
return ContentUris.withAppendedId(People.CONTENT_URI, personId);
}
default: {
// Build and return soft, lookup reference
final long contactId = cursor.getLong(SUMMARY_ID_COLUMN_INDEX);
final String lookupKey = cursor.getString(SUMMARY_LOOKUP_KEY_COLUMN_INDEX);
return Contacts.getLookupUri(contactId, lookupKey);
}
}
}
/**
* Build the {@link Uri} for the given {@link ListView} position, which can
* be used as result when in {@link #MODE_MASK_PICKER} mode.
*/
private Uri getSelectedUri(int position) {
if (position == ListView.INVALID_POSITION) {
throw new IllegalArgumentException("Position not in list bounds");
}
final long id = mAdapter.getItemId(position);
switch(mMode) {
case MODE_LEGACY_PICK_PERSON:
case MODE_LEGACY_PICK_OR_CREATE_PERSON: {
return ContentUris.withAppendedId(People.CONTENT_URI, id);
}
case MODE_PICK_PHONE:
case MODE_QUERY_PICK_PHONE: {
return ContentUris.withAppendedId(Data.CONTENT_URI, id);
}
case MODE_LEGACY_PICK_PHONE: {
return ContentUris.withAppendedId(Phones.CONTENT_URI, id);
}
case MODE_PICK_POSTAL: {
return ContentUris.withAppendedId(Data.CONTENT_URI, id);
}
case MODE_LEGACY_PICK_POSTAL: {
return ContentUris.withAppendedId(ContactMethods.CONTENT_URI, id);
}
default: {
return getContactUri(position);
}
}
}
String[] getProjectionForQuery() {
switch(mMode) {
case MODE_JOIN_CONTACT:
case MODE_STREQUENT:
case MODE_FREQUENT:
case MODE_STARRED:
case MODE_DEFAULT:
case MODE_CUSTOM:
case MODE_INSERT_OR_EDIT_CONTACT:
case MODE_GROUP:
case MODE_PICK_CONTACT:
case MODE_PICK_OR_CREATE_CONTACT: {
return mSearchMode
? CONTACTS_SUMMARY_FILTER_PROJECTION
: CONTACTS_SUMMARY_PROJECTION;
}
case MODE_QUERY:
case MODE_QUERY_PICK:
case MODE_QUERY_PICK_TO_EDIT: {
return CONTACTS_SUMMARY_FILTER_PROJECTION;
}
case MODE_LEGACY_PICK_PERSON:
case MODE_LEGACY_PICK_OR_CREATE_PERSON: {
return LEGACY_PEOPLE_PROJECTION ;
}
case MODE_QUERY_PICK_PHONE:
case MODE_PICK_PHONE: {
return PHONES_PROJECTION;
}
case MODE_LEGACY_PICK_PHONE: {
return LEGACY_PHONES_PROJECTION;
}
case MODE_PICK_POSTAL: {
return POSTALS_PROJECTION;
}
case MODE_LEGACY_PICK_POSTAL: {
return LEGACY_POSTALS_PROJECTION;
}
case MODE_QUERY_PICK_TO_VIEW: {
if (mQueryMode == QUERY_MODE_MAILTO) {
return CONTACTS_SUMMARY_PROJECTION_FROM_EMAIL;
} else if (mQueryMode == QUERY_MODE_TEL) {
return PHONES_PROJECTION;
}
break;
}
}
// Default to normal aggregate projection
return CONTACTS_SUMMARY_PROJECTION;
}
private Bitmap loadContactPhoto(Uri selectedUri, BitmapFactory.Options options) {
Uri contactUri = null;
if (Contacts.CONTENT_ITEM_TYPE.equals(getContentResolver().getType(selectedUri))) {
// TODO we should have a "photo" directory under the lookup URI itself
contactUri = Contacts.lookupContact(getContentResolver(), selectedUri);
} else {
Cursor cursor = getContentResolver().query(selectedUri,
new String[] { Data.CONTACT_ID }, null, null, null);
try {
if (cursor != null && cursor.moveToFirst()) {
final long contactId = cursor.getLong(0);
contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId);
}
} finally {
if (cursor != null) cursor.close();
}
}
Cursor cursor = null;
Bitmap bm = null;
try {
Uri photoUri = Uri.withAppendedPath(contactUri, Contacts.Photo.CONTENT_DIRECTORY);
cursor = getContentResolver().query(photoUri, new String[] {Photo.PHOTO},
null, null, null);
if (cursor != null && cursor.moveToFirst()) {
bm = ContactsUtils.loadContactPhoto(cursor, 0, options);
}
} finally {
if (cursor != null) {
cursor.close();
}
}
if (bm == null) {
final int[] fallbacks = {
R.drawable.ic_contact_picture,
R.drawable.ic_contact_picture_2,
R.drawable.ic_contact_picture_3
};
bm = BitmapFactory.decodeResource(getResources(),
fallbacks[new Random().nextInt(fallbacks.length)]);
}
return bm;
}
/**
* Return the selection arguments for a default query based on the
* {@link #mDisplayOnlyPhones} flag.
*/
private String getContactSelection() {
if (mDisplayOnlyPhones) {
return CLAUSE_ONLY_VISIBLE + " AND " + CLAUSE_ONLY_PHONES;
} else {
return CLAUSE_ONLY_VISIBLE;
}
}
private Uri getContactFilterUri(String filter) {
Uri baseUri;
if (!TextUtils.isEmpty(filter)) {
baseUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI, Uri.encode(filter));
} else {
baseUri = Contacts.CONTENT_URI;
}
if (mAdapter.getDisplaySectionHeadersEnabled()) {
return buildSectionIndexerUri(baseUri);
} else {
return baseUri;
}
}
private Uri getPeopleFilterUri(String filter) {
if (!TextUtils.isEmpty(filter)) {
return Uri.withAppendedPath(People.CONTENT_FILTER_URI, Uri.encode(filter));
} else {
return People.CONTENT_URI;
}
}
private static Uri buildSectionIndexerUri(Uri uri) {
return uri.buildUpon()
.appendQueryParameter(ContactCounts.ADDRESS_BOOK_INDEX_EXTRAS, "true").build();
}
private Uri getJoinSuggestionsUri(String filter) {
Builder builder = Contacts.CONTENT_URI.buildUpon();
builder.appendEncodedPath(String.valueOf(mQueryAggregateId));
builder.appendEncodedPath(AggregationSuggestions.CONTENT_DIRECTORY);
if (!TextUtils.isEmpty(filter)) {
builder.appendEncodedPath(Uri.encode(filter));
}
builder.appendQueryParameter("limit", String.valueOf(MAX_SUGGESTIONS));
return builder.build();
}
private String getSortOrder(String[] projectionType) {
if (mSortOrder == ContactsContract.Preferences.SORT_ORDER_PRIMARY) {
return Contacts.SORT_KEY_PRIMARY;
} else {
return Contacts.SORT_KEY_ALTERNATIVE;
}
}
void startQuery() {
// Set the proper empty string
setEmptyText();
if (mSearchResultsMode) {
TextView foundContactsText = (TextView)findViewById(R.id.search_results_found);
foundContactsText.setText(R.string.search_results_searching);
}
mAdapter.setLoading(true);
// Cancel any pending queries
mQueryHandler.cancelOperation(QUERY_TOKEN);
mQueryHandler.setLoadingJoinSuggestions(false);
mSortOrder = mContactsPrefs.getSortOrder();
mDisplayOrder = mContactsPrefs.getDisplayOrder();
// When sort order and display order contradict each other, we want to
// highlight the part of the name used for sorting.
mHighlightWhenScrolling = false;
if (mSortOrder == ContactsContract.Preferences.SORT_ORDER_PRIMARY &&
mDisplayOrder == ContactsContract.Preferences.DISPLAY_ORDER_ALTERNATIVE) {
mHighlightWhenScrolling = true;
} else if (mSortOrder == ContactsContract.Preferences.SORT_ORDER_ALTERNATIVE &&
mDisplayOrder == ContactsContract.Preferences.DISPLAY_ORDER_PRIMARY) {
mHighlightWhenScrolling = true;
}
String[] projection = getProjectionForQuery();
if (mSearchMode && TextUtils.isEmpty(getTextFilter())) {
mAdapter.changeCursor(new MatrixCursor(projection));
return;
}
String callingPackage = getCallingPackage();
Uri uri = getUriToQuery();
if (!TextUtils.isEmpty(callingPackage)) {
uri = uri.buildUpon()
.appendQueryParameter(ContactsContract.REQUESTING_PACKAGE_PARAM_KEY,
callingPackage)
.build();
}
// Kick off the new query
switch (mMode) {
case MODE_GROUP:
case MODE_DEFAULT:
case MODE_CUSTOM:
case MODE_PICK_CONTACT:
case MODE_PICK_OR_CREATE_CONTACT:
case MODE_INSERT_OR_EDIT_CONTACT:
mQueryHandler.startQuery(QUERY_TOKEN, null, uri, projection, getContactSelection(),
null, getSortOrder(projection));
break;
case MODE_LEGACY_PICK_PERSON:
case MODE_LEGACY_PICK_OR_CREATE_PERSON: {
mQueryHandler.startQuery(QUERY_TOKEN, null, uri, projection, null, null,
People.DISPLAY_NAME);
break;
}
case MODE_PICK_POSTAL:
case MODE_QUERY:
case MODE_QUERY_PICK:
case MODE_QUERY_PICK_PHONE:
case MODE_QUERY_PICK_TO_VIEW:
case MODE_QUERY_PICK_TO_EDIT: {
mQueryHandler.startQuery(QUERY_TOKEN, null, uri, projection, null, null,
getSortOrder(projection));
break;
}
case MODE_STARRED:
mQueryHandler.startQuery(QUERY_TOKEN, null, uri,
projection, Contacts.STARRED + "=1", null,
getSortOrder(projection));
break;
case MODE_FREQUENT:
mQueryHandler.startQuery(QUERY_TOKEN, null, uri,
projection,
Contacts.TIMES_CONTACTED + " > 0", null,
Contacts.TIMES_CONTACTED + " DESC, "
+ getSortOrder(projection));
break;
case MODE_STREQUENT:
mQueryHandler.startQuery(QUERY_TOKEN, null, uri, projection, null, null, null);
break;
case MODE_PICK_PHONE:
mQueryHandler.startQuery(QUERY_TOKEN, null, uri,
projection, CLAUSE_ONLY_VISIBLE, null, getSortOrder(projection));
break;
case MODE_LEGACY_PICK_PHONE:
mQueryHandler.startQuery(QUERY_TOKEN, null, uri,
projection, null, null, Phones.DISPLAY_NAME);
break;
case MODE_LEGACY_PICK_POSTAL:
mQueryHandler.startQuery(QUERY_TOKEN, null, uri,
projection,
ContactMethods.KIND + "=" + android.provider.Contacts.KIND_POSTAL, null,
ContactMethods.DISPLAY_NAME);
break;
case MODE_JOIN_CONTACT:
mQueryHandler.setLoadingJoinSuggestions(true);
mQueryHandler.startQuery(QUERY_TOKEN, null, uri, projection,
null, null, null);
break;
}
}
/**
* 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
*/
Cursor doFilter(String filter) {
String[] projection = getProjectionForQuery();
if (mSearchMode && TextUtils.isEmpty(getTextFilter())) {
return new MatrixCursor(projection);
}
final ContentResolver resolver = getContentResolver();
switch (mMode) {
case MODE_DEFAULT:
case MODE_CUSTOM:
case MODE_PICK_CONTACT:
case MODE_PICK_OR_CREATE_CONTACT:
case MODE_INSERT_OR_EDIT_CONTACT: {
return resolver.query(getContactFilterUri(filter), projection,
getContactSelection(), null, getSortOrder(projection));
}
case MODE_LEGACY_PICK_PERSON:
case MODE_LEGACY_PICK_OR_CREATE_PERSON: {
return resolver.query(getPeopleFilterUri(filter), projection, null, null,
People.DISPLAY_NAME);
}
case MODE_STARRED: {
return resolver.query(getContactFilterUri(filter), projection,
Contacts.STARRED + "=1", null,
getSortOrder(projection));
}
case MODE_FREQUENT: {
return resolver.query(getContactFilterUri(filter), projection,
Contacts.TIMES_CONTACTED + " > 0", null,
Contacts.TIMES_CONTACTED + " DESC, "
+ getSortOrder(projection));
}
case MODE_STREQUENT: {
Uri uri;
if (!TextUtils.isEmpty(filter)) {
uri = Uri.withAppendedPath(Contacts.CONTENT_STREQUENT_FILTER_URI,
Uri.encode(filter));
} else {
uri = Contacts.CONTENT_STREQUENT_URI;
}
return resolver.query(uri, projection, null, null, null);
}
case MODE_PICK_PHONE: {
Uri uri = getUriToQuery();
if (!TextUtils.isEmpty(filter)) {
uri = Uri.withAppendedPath(Phone.CONTENT_FILTER_URI, Uri.encode(filter));
}
return resolver.query(uri, projection, CLAUSE_ONLY_VISIBLE, null,
getSortOrder(projection));
}
case MODE_LEGACY_PICK_PHONE: {
//TODO: Support filtering here (bug 2092503)
break;
}
case MODE_JOIN_CONTACT: {
// We are on a background thread. Run queries one after the other synchronously
Cursor cursor = resolver.query(getJoinSuggestionsUri(filter), projection, null,
null, null);
mAdapter.setSuggestionsCursor(cursor);
mJoinModeShowAllContacts = false;
return resolver.query(getContactFilterUri(filter), projection,
Contacts._ID + " != " + mQueryAggregateId + " AND " + CLAUSE_ONLY_VISIBLE,
null, getSortOrder(projection));
}
}
throw new UnsupportedOperationException("filtering not allowed in mode " + mMode);
}
private Cursor getShowAllContactsLabelCursor(String[] projection) {
MatrixCursor matrixCursor = new MatrixCursor(projection);
Object[] row = new Object[projection.length];
// The only columns we care about is the id
row[SUMMARY_ID_COLUMN_INDEX] = JOIN_MODE_SHOW_ALL_CONTACTS_ID;
matrixCursor.addRow(row);
return matrixCursor;
}
/**
* Calls the currently selected list item.
* @return true if the call was initiated, false otherwise
*/
boolean callSelection() {
ListView list = getListView();
if (list.hasFocus()) {
Cursor cursor = (Cursor) list.getSelectedItem();
return callContact(cursor);
}
return false;
}
boolean callContact(Cursor cursor) {
return callOrSmsContact(cursor, false /*call*/);
}
boolean smsContact(Cursor cursor) {
return callOrSmsContact(cursor, true /*sms*/);
}
/**
* Calls the contact which the cursor is point to.
* @return true if the call was initiated, false otherwise
*/
boolean callOrSmsContact(Cursor cursor, boolean sendSms) {
if (cursor == null) {
return false;
}
switch (mMode) {
case MODE_PICK_PHONE:
case MODE_LEGACY_PICK_PHONE:
case MODE_QUERY_PICK_PHONE: {
String phone = cursor.getString(PHONE_NUMBER_COLUMN_INDEX);
if (sendSms) {
ContactsUtils.initiateSms(this, phone);
} else {
ContactsUtils.initiateCall(this, phone);
}
return true;
}
case MODE_PICK_POSTAL:
case MODE_LEGACY_PICK_POSTAL: {
return false;
}
default: {
boolean hasPhone = cursor.getInt(SUMMARY_HAS_PHONE_COLUMN_INDEX) != 0;
if (!hasPhone) {
// There is no phone number.
signalError();
return false;
}
String phone = null;
Cursor phonesCursor = null;
phonesCursor = queryPhoneNumbers(cursor.getLong(SUMMARY_ID_COLUMN_INDEX));
if (phonesCursor == null || phonesCursor.getCount() == 0) {
// No valid number
signalError();
return false;
} else if (phonesCursor.getCount() == 1) {
// only one number, call it.
phone = phonesCursor.getString(phonesCursor.getColumnIndex(Phone.NUMBER));
} else {
phonesCursor.moveToPosition(-1);
while (phonesCursor.moveToNext()) {
if (phonesCursor.getInt(phonesCursor.
getColumnIndex(Phone.IS_SUPER_PRIMARY)) != 0) {
// Found super primary, call it.
phone = phonesCursor.
getString(phonesCursor.getColumnIndex(Phone.NUMBER));
break;
}
}
}
if (phone == null) {
// Display dialog to choose a number to call.
PhoneDisambigDialog phoneDialog = new PhoneDisambigDialog(
this, phonesCursor, sendSms, StickyTabs.getTab(getIntent()));
phoneDialog.show();
} else {
if (sendSms) {
ContactsUtils.initiateSms(this, phone);
} else {
StickyTabs.saveTab(this, getIntent());
ContactsUtils.initiateCall(this, phone);
}
}
}
}
return true;
}
private Cursor queryPhoneNumbers(long contactId) {
Uri baseUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId);
Uri dataUri = Uri.withAppendedPath(baseUri, Contacts.Data.CONTENT_DIRECTORY);
Cursor c = getContentResolver().query(dataUri,
new String[] {Phone._ID, Phone.NUMBER, Phone.IS_SUPER_PRIMARY,
RawContacts.ACCOUNT_TYPE, Phone.TYPE, Phone.LABEL},
Data.MIMETYPE + "=?", new String[] {Phone.CONTENT_ITEM_TYPE}, null);
if (c != null) {
if (c.moveToFirst()) {
return c;
}
c.close();
}
return null;
}
// TODO: fix PluralRules to handle zero correctly and use Resources.getQuantityText directly
protected String getQuantityText(int count, int zeroResourceId, int pluralResourceId) {
if (count == 0) {
return getString(zeroResourceId);
} else {
String format = getResources().getQuantityText(pluralResourceId, count).toString();
return String.format(format, count);
}
}
/**
* Signal an error to the user.
*/
void signalError() {
//TODO play an error beep or something...
}
Cursor getItemForView(View view) {
ListView listView = getListView();
int index = listView.getPositionForView(view);
if (index < 0) {
return null;
}
return (Cursor) listView.getAdapter().getItem(index);
}
private static class QueryHandler extends AsyncQueryHandler {
protected final WeakReference<ContactsListActivity> mActivity;
protected boolean mLoadingJoinSuggestions = false;
public QueryHandler(Context context) {
super(context.getContentResolver());
mActivity = new WeakReference<ContactsListActivity>((ContactsListActivity) context);
}
public void setLoadingJoinSuggestions(boolean flag) {
mLoadingJoinSuggestions = flag;
}
@Override
protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
final ContactsListActivity activity = mActivity.get();
if (activity != null && !activity.isFinishing()) {
// Whenever we get a suggestions cursor, we need to immediately kick off
// another query for the complete list of contacts
if (cursor != null && mLoadingJoinSuggestions) {
mLoadingJoinSuggestions = false;
if (cursor.getCount() > 0) {
activity.mAdapter.setSuggestionsCursor(cursor);
} else {
cursor.close();
activity.mAdapter.setSuggestionsCursor(null);
}
if (activity.mAdapter.mSuggestionsCursorCount == 0
|| !activity.mJoinModeShowAllContacts) {
startQuery(QUERY_TOKEN, null, activity.getContactFilterUri(
activity.getTextFilter()),
CONTACTS_SUMMARY_PROJECTION,
Contacts._ID + " != " + activity.mQueryAggregateId
+ " AND " + CLAUSE_ONLY_VISIBLE, null,
activity.getSortOrder(CONTACTS_SUMMARY_PROJECTION));
return;
}
cursor = activity.getShowAllContactsLabelCursor(CONTACTS_SUMMARY_PROJECTION);
}
activity.mAdapter.changeCursor(cursor);
// Now that the cursor is populated again, it's possible to restore the list state
if (activity.mListState != null) {
activity.mList.onRestoreInstanceState(activity.mListState);
activity.mListState = null;
}
} else {
if (cursor != null) {
cursor.close();
}
}
}
}
final static class ContactListItemCache {
public CharArrayBuffer nameBuffer = new CharArrayBuffer(128);
public CharArrayBuffer dataBuffer = new CharArrayBuffer(128);
public CharArrayBuffer highlightedTextBuffer = new CharArrayBuffer(128);
public TextWithHighlighting textWithHighlighting;
public CharArrayBuffer phoneticNameBuffer = new CharArrayBuffer(128);
}
final static class PinnedHeaderCache {
public TextView titleView;
public ColorStateList textColor;
public Drawable background;
}
private final class ContactItemListAdapter extends CursorAdapter
implements SectionIndexer, OnScrollListener, PinnedHeaderListView.PinnedHeaderAdapter {
private SectionIndexer mIndexer;
private boolean mLoading = true;
private CharSequence mUnknownNameText;
private boolean mDisplayPhotos = false;
private boolean mDisplayCallButton = false;
private boolean mDisplayAdditionalData = true;
private int mFrequentSeparatorPos = ListView.INVALID_POSITION;
private boolean mDisplaySectionHeaders = true;
private Cursor mSuggestionsCursor;
private int mSuggestionsCursorCount;
public ContactItemListAdapter(Context context) {
super(context, null, false);
mUnknownNameText = context.getText(android.R.string.unknownName);
switch (mMode) {
case MODE_LEGACY_PICK_POSTAL:
case MODE_PICK_POSTAL:
case MODE_LEGACY_PICK_PHONE:
case MODE_PICK_PHONE:
case MODE_STREQUENT:
case MODE_FREQUENT:
mDisplaySectionHeaders = false;
break;
}
if (mSearchMode) {
mDisplaySectionHeaders = false;
}
// Do not display the second line of text if in a specific SEARCH query mode, usually for
// matching a specific E-mail or phone number. Any contact details
// shown would be identical, and columns might not even be present
// in the returned cursor.
if (mMode != MODE_QUERY_PICK_PHONE && mQueryMode != QUERY_MODE_NONE) {
mDisplayAdditionalData = false;
}
if ((mMode & MODE_MASK_NO_DATA) == MODE_MASK_NO_DATA) {
mDisplayAdditionalData = false;
}
if ((mMode & MODE_MASK_SHOW_CALL_BUTTON) == MODE_MASK_SHOW_CALL_BUTTON) {
mDisplayCallButton = true;
}
if ((mMode & MODE_MASK_SHOW_PHOTOS) == MODE_MASK_SHOW_PHOTOS) {
mDisplayPhotos = true;
}
}
public boolean getDisplaySectionHeadersEnabled() {
return mDisplaySectionHeaders;
}
public void setSuggestionsCursor(Cursor cursor) {
if (mSuggestionsCursor != null) {
mSuggestionsCursor.close();
}
mSuggestionsCursor = cursor;
mSuggestionsCursorCount = cursor == null ? 0 : cursor.getCount();
}
/**
* Callback on the UI thread when the content observer on the backing cursor fires.
* Instead of calling requery we need to do an async query so that the requery doesn't
* block the UI thread for a long time.
*/
@Override
protected void onContentChanged() {
CharSequence constraint = getTextFilter();
if (!TextUtils.isEmpty(constraint)) {
// Reset the filter state then start an async filter operation
Filter filter = getFilter();
filter.filter(constraint);
} else {
// Start an async query
startQuery();
}
}
public void setLoading(boolean loading) {
mLoading = loading;
}
@Override
public boolean isEmpty() {
if (mProviderStatus != ProviderStatus.STATUS_NORMAL) {
return true;
}
if (mSearchMode) {
return TextUtils.isEmpty(getTextFilter());
} else if ((mMode & MODE_MASK_CREATE_NEW) == MODE_MASK_CREATE_NEW) {
// This mode mask adds a header and we always want it to show up, even
// if the list is empty, so always claim the list is not empty.
return false;
} else {
if (mCursor == null || mLoading) {
// We don't want the empty state to show when loading.
return false;
} else {
return super.isEmpty();
}
}
}
@Override
public int getItemViewType(int position) {
if (position == 0 && (mShowNumberOfContacts || (mMode & MODE_MASK_CREATE_NEW) != 0)) {
return IGNORE_ITEM_VIEW_TYPE;
}
if (isShowAllContactsItemPosition(position)) {
return IGNORE_ITEM_VIEW_TYPE;
}
if (isSearchAllContactsItemPosition(position)) {
return IGNORE_ITEM_VIEW_TYPE;
}
if (getSeparatorId(position) != 0) {
// We don't want the separator view to be recycled.
return IGNORE_ITEM_VIEW_TYPE;
}
return super.getItemViewType(position);
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (!mDataValid) {
throw new IllegalStateException(
"this should only be called when the cursor is valid");
}
// handle the total contacts item
if (position == 0 && mShowNumberOfContacts) {
return getTotalContactCountView(parent);
}
if (position == 0 && (mMode & MODE_MASK_CREATE_NEW) != 0) {
// Add the header for creating a new contact
return getLayoutInflater().inflate(R.layout.create_new_contact, parent, false);
}
if (isShowAllContactsItemPosition(position)) {
return getLayoutInflater().
inflate(R.layout.contacts_list_show_all_item, parent, false);
}
if (isSearchAllContactsItemPosition(position)) {
return getLayoutInflater().
inflate(R.layout.contacts_list_search_all_item, parent, false);
}
// Handle the separator specially
int separatorId = getSeparatorId(position);
if (separatorId != 0) {
TextView view = (TextView) getLayoutInflater().
inflate(R.layout.list_separator, parent, false);
view.setText(separatorId);
return view;
}
boolean showingSuggestion;
Cursor cursor;
if (mSuggestionsCursorCount != 0 && position < mSuggestionsCursorCount + 2) {
showingSuggestion = true;
cursor = mSuggestionsCursor;
} else {
showingSuggestion = false;
cursor = mCursor;
}
int realPosition = getRealPosition(position);
if (!cursor.moveToPosition(realPosition)) {
throw new IllegalStateException("couldn't move cursor to position " + position);
}
boolean newView;
View v;
if (convertView == null || convertView.getTag() == null) {
newView = true;
v = newView(mContext, cursor, parent);
} else {
newView = false;
v = convertView;
}
bindView(v, mContext, cursor);
bindSectionHeader(v, realPosition, mDisplaySectionHeaders && !showingSuggestion);
return v;
}
private View getTotalContactCountView(ViewGroup parent) {
final LayoutInflater inflater = getLayoutInflater();
View view = inflater.inflate(R.layout.total_contacts, parent, false);
TextView totalContacts = (TextView) view.findViewById(R.id.totalContactsText);
String text;
int count = getRealCount();
if (mSearchMode && !TextUtils.isEmpty(getTextFilter())) {
text = getQuantityText(count, R.string.listFoundAllContactsZero,
R.plurals.searchFoundContacts);
} else {
if (mDisplayOnlyPhones) {
text = getQuantityText(count, R.string.listTotalPhoneContactsZero,
R.plurals.listTotalPhoneContacts);
} else {
text = getQuantityText(count, R.string.listTotalAllContactsZero,
R.plurals.listTotalAllContacts);
}
}
totalContacts.setText(text);
return view;
}
private boolean isShowAllContactsItemPosition(int position) {
return mMode == MODE_JOIN_CONTACT && mJoinModeShowAllContacts
&& mSuggestionsCursorCount != 0 && position == mSuggestionsCursorCount + 2;
}
private boolean isSearchAllContactsItemPosition(int position) {
return mSearchMode && position == getCount() - 1;
}
private int getSeparatorId(int position) {
int separatorId = 0;
if (position == mFrequentSeparatorPos) {
separatorId = R.string.favoritesFrquentSeparator;
}
if (mSuggestionsCursorCount != 0) {
if (position == 0) {
separatorId = R.string.separatorJoinAggregateSuggestions;
} else if (position == mSuggestionsCursorCount + 1) {
separatorId = R.string.separatorJoinAggregateAll;
}
}
return separatorId;
}
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
final ContactListItemView view = new ContactListItemView(context, null);
view.setOnCallButtonClickListener(ContactsListActivity.this);
view.setTag(new ContactListItemCache());
return view;
}
@Override
public void bindView(View itemView, Context context, Cursor cursor) {
final ContactListItemView view = (ContactListItemView)itemView;
final ContactListItemCache cache = (ContactListItemCache) view.getTag();
int typeColumnIndex;
int dataColumnIndex;
int labelColumnIndex;
int defaultType;
int nameColumnIndex;
int phoneticNameColumnIndex;
boolean displayAdditionalData = mDisplayAdditionalData;
boolean highlightingEnabled = false;
switch(mMode) {
case MODE_PICK_PHONE:
case MODE_LEGACY_PICK_PHONE:
case MODE_QUERY_PICK_PHONE: {
nameColumnIndex = PHONE_DISPLAY_NAME_COLUMN_INDEX;
phoneticNameColumnIndex = -1;
dataColumnIndex = PHONE_NUMBER_COLUMN_INDEX;
typeColumnIndex = PHONE_TYPE_COLUMN_INDEX;
labelColumnIndex = PHONE_LABEL_COLUMN_INDEX;
defaultType = Phone.TYPE_HOME;
break;
}
case MODE_PICK_POSTAL:
case MODE_LEGACY_PICK_POSTAL: {
nameColumnIndex = POSTAL_DISPLAY_NAME_COLUMN_INDEX;
phoneticNameColumnIndex = -1;
dataColumnIndex = POSTAL_ADDRESS_COLUMN_INDEX;
typeColumnIndex = POSTAL_TYPE_COLUMN_INDEX;
labelColumnIndex = POSTAL_LABEL_COLUMN_INDEX;
defaultType = StructuredPostal.TYPE_HOME;
break;
}
default: {
nameColumnIndex = getSummaryDisplayNameColumnIndex();
if (mMode == MODE_LEGACY_PICK_PERSON
|| mMode == MODE_LEGACY_PICK_OR_CREATE_PERSON) {
phoneticNameColumnIndex = -1;
} else {
phoneticNameColumnIndex = SUMMARY_PHONETIC_NAME_COLUMN_INDEX;
}
dataColumnIndex = -1;
typeColumnIndex = -1;
labelColumnIndex = -1;
defaultType = Phone.TYPE_HOME;
displayAdditionalData = false;
highlightingEnabled = mHighlightWhenScrolling && mMode != MODE_STREQUENT;
}
}
// Set the name
cursor.copyStringToBuffer(nameColumnIndex, cache.nameBuffer);
TextView nameView = view.getNameTextView();
int size = cache.nameBuffer.sizeCopied;
if (size != 0) {
if (highlightingEnabled) {
if (cache.textWithHighlighting == null) {
cache.textWithHighlighting =
mHighlightingAnimation.createTextWithHighlighting();
}
buildDisplayNameWithHighlighting(nameView, cursor, cache.nameBuffer,
cache.highlightedTextBuffer, cache.textWithHighlighting);
} else {
nameView.setText(cache.nameBuffer.data, 0, size);
}
} else {
nameView.setText(mUnknownNameText);
}
boolean hasPhone = cursor.getColumnCount() >= SUMMARY_HAS_PHONE_COLUMN_INDEX
&& cursor.getInt(SUMMARY_HAS_PHONE_COLUMN_INDEX) != 0;
// Make the call button visible if requested.
if (mDisplayCallButton && hasPhone) {
int pos = cursor.getPosition();
view.showCallButton(android.R.id.button1, pos);
} else {
view.hideCallButton();
}
// Set the photo, if requested
if (mDisplayPhotos) {
boolean useQuickContact = (mMode & MODE_MASK_DISABLE_QUIKCCONTACT) == 0;
long photoId = 0;
if (!cursor.isNull(SUMMARY_PHOTO_ID_COLUMN_INDEX)) {
photoId = cursor.getLong(SUMMARY_PHOTO_ID_COLUMN_INDEX);
}
ImageView viewToUse;
if (useQuickContact) {
// Build soft lookup reference
final long contactId = cursor.getLong(SUMMARY_ID_COLUMN_INDEX);
final String lookupKey = cursor.getString(SUMMARY_LOOKUP_KEY_COLUMN_INDEX);
QuickContactBadge quickContact = view.getQuickContact();
quickContact.assignContactUri(Contacts.getLookupUri(contactId, lookupKey));
quickContact.setSelectedContactsAppTabIndex(StickyTabs.getTab(getIntent()));
viewToUse = quickContact;
} else {
viewToUse = view.getPhotoView();
}
final int position = cursor.getPosition();
mPhotoLoader.loadPhoto(viewToUse, photoId);
}
if ((mMode & MODE_MASK_NO_PRESENCE) == 0) {
// Set the proper icon (star or presence or nothing)
int serverStatus;
if (!cursor.isNull(SUMMARY_PRESENCE_STATUS_COLUMN_INDEX)) {
serverStatus = cursor.getInt(SUMMARY_PRESENCE_STATUS_COLUMN_INDEX);
Drawable icon = ContactPresenceIconUtil.getPresenceIcon(mContext, serverStatus);
if (icon != null) {
view.setPresence(icon);
} else {
view.setPresence(null);
}
} else {
view.setPresence(null);
}
} else {
view.setPresence(null);
}
if (mShowSearchSnippets) {
boolean showSnippet = false;
String snippetMimeType = cursor.getString(SUMMARY_SNIPPET_MIMETYPE_COLUMN_INDEX);
if (Email.CONTENT_ITEM_TYPE.equals(snippetMimeType)) {
String email = cursor.getString(SUMMARY_SNIPPET_DATA1_COLUMN_INDEX);
if (!TextUtils.isEmpty(email)) {
view.setSnippet(email);
showSnippet = true;
}
} else if (Organization.CONTENT_ITEM_TYPE.equals(snippetMimeType)) {
String company = cursor.getString(SUMMARY_SNIPPET_DATA1_COLUMN_INDEX);
String title = cursor.getString(SUMMARY_SNIPPET_DATA4_COLUMN_INDEX);
if (!TextUtils.isEmpty(company)) {
if (!TextUtils.isEmpty(title)) {
view.setSnippet(company + " / " + title);
} else {
view.setSnippet(company);
}
showSnippet = true;
} else if (!TextUtils.isEmpty(title)) {
view.setSnippet(title);
showSnippet = true;
}
} else if (Nickname.CONTENT_ITEM_TYPE.equals(snippetMimeType)) {
String nickname = cursor.getString(SUMMARY_SNIPPET_DATA1_COLUMN_INDEX);
if (!TextUtils.isEmpty(nickname)) {
view.setSnippet(nickname);
showSnippet = true;
}
}
if (!showSnippet) {
view.setSnippet(null);
}
}
if (!displayAdditionalData) {
if (phoneticNameColumnIndex != -1) {
// Set the name
cursor.copyStringToBuffer(phoneticNameColumnIndex, cache.phoneticNameBuffer);
int phoneticNameSize = cache.phoneticNameBuffer.sizeCopied;
if (phoneticNameSize != 0) {
view.setLabel(cache.phoneticNameBuffer.data, phoneticNameSize);
} else {
view.setLabel(null);
}
} else {
view.setLabel(null);
}
return;
}
// Set the data.
cursor.copyStringToBuffer(dataColumnIndex, cache.dataBuffer);
size = cache.dataBuffer.sizeCopied;
view.setData(cache.dataBuffer.data, size);
// Set the label.
if (!cursor.isNull(typeColumnIndex)) {
final int type = cursor.getInt(typeColumnIndex);
final String label = cursor.getString(labelColumnIndex);
if (mMode == MODE_LEGACY_PICK_POSTAL || mMode == MODE_PICK_POSTAL) {
// TODO cache
view.setLabel(StructuredPostal.getTypeLabel(context.getResources(), type,
label));
} else {
// TODO cache
view.setLabel(Phone.getTypeLabel(context.getResources(), type, label));
}
} else {
view.setLabel(null);
}
}
/**
* Computes the span of the display name that has highlighted parts and configures
* the display name text view accordingly.
*/
private void buildDisplayNameWithHighlighting(TextView textView, Cursor cursor,
CharArrayBuffer buffer1, CharArrayBuffer buffer2,
TextWithHighlighting textWithHighlighting) {
int oppositeDisplayOrderColumnIndex;
if (mDisplayOrder == ContactsContract.Preferences.DISPLAY_ORDER_PRIMARY) {
oppositeDisplayOrderColumnIndex = SUMMARY_DISPLAY_NAME_ALTERNATIVE_COLUMN_INDEX;
} else {
oppositeDisplayOrderColumnIndex = SUMMARY_DISPLAY_NAME_PRIMARY_COLUMN_INDEX;
}
cursor.copyStringToBuffer(oppositeDisplayOrderColumnIndex, buffer2);
textWithHighlighting.setText(buffer1, buffer2);
textView.setText(textWithHighlighting);
}
private void bindSectionHeader(View itemView, int position, boolean displaySectionHeaders) {
final ContactListItemView view = (ContactListItemView)itemView;
final ContactListItemCache cache = (ContactListItemCache) view.getTag();
if (!displaySectionHeaders) {
view.setSectionHeader(null);
view.setDividerVisible(true);
} else {
final int section = getSectionForPosition(position);
if (getPositionForSection(section) == position) {
String title = (String)mIndexer.getSections()[section];
view.setSectionHeader(title);
} else {
view.setDividerVisible(false);
view.setSectionHeader(null);
}
// move the divider for the last item in a section
if (getPositionForSection(section + 1) - 1 == position) {
view.setDividerVisible(false);
} else {
view.setDividerVisible(true);
}
}
}
@Override
public void changeCursor(Cursor cursor) {
if (cursor != null) {
setLoading(false);
}
// Get the split between starred and frequent items, if the mode is strequent
mFrequentSeparatorPos = ListView.INVALID_POSITION;
int cursorCount = 0;
if (cursor != null && (cursorCount = cursor.getCount()) > 0
&& mMode == MODE_STREQUENT) {
cursor.move(-1);
for (int i = 0; cursor.moveToNext(); i++) {
int starred = cursor.getInt(SUMMARY_STARRED_COLUMN_INDEX);
if (starred == 0) {
if (i > 0) {
// Only add the separator when there are starred items present
mFrequentSeparatorPos = i;
}
break;
}
}
}
if (cursor != null && mSearchResultsMode) {
TextView foundContactsText = (TextView)findViewById(R.id.search_results_found);
String text = getQuantityText(cursor.getCount(), R.string.listFoundAllContactsZero,
R.plurals.listFoundAllContacts);
foundContactsText.setText(text);
}
super.changeCursor(cursor);
// Update the indexer for the fast scroll widget
updateIndexer(cursor);
}
private void updateIndexer(Cursor cursor) {
if (cursor == null) {
mIndexer = null;
return;
}
Bundle bundle = cursor.getExtras();
if (bundle.containsKey(ContactCounts.EXTRA_ADDRESS_BOOK_INDEX_TITLES)) {
String sections[] =
bundle.getStringArray(ContactCounts.EXTRA_ADDRESS_BOOK_INDEX_TITLES);
int counts[] = bundle.getIntArray(ContactCounts.EXTRA_ADDRESS_BOOK_INDEX_COUNTS);
mIndexer = new ContactsSectionIndexer(sections, counts);
} else {
mIndexer = null;
}
}
/**
* Run the query on a helper thread. Beware that this code does not run
* on the main UI thread!
*/
@Override
public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
return doFilter(constraint.toString());
}
public Object [] getSections() {
if (mIndexer == null) {
return new String[] { " " };
} else {
return mIndexer.getSections();
}
}
public int getPositionForSection(int sectionIndex) {
if (mIndexer == null) {
return -1;
}
return mIndexer.getPositionForSection(sectionIndex);
}
public int getSectionForPosition(int position) {
if (mIndexer == null) {
return -1;
}
return mIndexer.getSectionForPosition(position);
}
@Override
public boolean areAllItemsEnabled() {
return mMode != MODE_STARRED
&& !mShowNumberOfContacts
&& mSuggestionsCursorCount == 0;
}
@Override
public boolean isEnabled(int position) {
if (mShowNumberOfContacts) {
if (position == 0) {
return false;
}
position--;
}
if (mSuggestionsCursorCount > 0) {
return position != 0 && position != mSuggestionsCursorCount + 1;
}
return position != mFrequentSeparatorPos;
}
@Override
public int getCount() {
if (!mDataValid) {
return 0;
}
int superCount = super.getCount();
if (mShowNumberOfContacts && (mSearchMode || superCount > 0)) {
// We don't want to count this header if it's the only thing visible, so that
// the empty text will display.
superCount++;
}
if (mSearchMode) {
// Last element in the list is the "Find
superCount++;
}
// We do not show the "Create New" button in Search mode
if ((mMode & MODE_MASK_CREATE_NEW) != 0 && !mSearchMode) {
// Count the "Create new contact" line
superCount++;
}
if (mSuggestionsCursorCount != 0) {
// When showing suggestions, we have 2 additional list items: the "Suggestions"
// and "All contacts" headers.
return mSuggestionsCursorCount + superCount + 2;
}
else if (mFrequentSeparatorPos != ListView.INVALID_POSITION) {
// When showing strequent list, we have an additional list item - the separator.
return superCount + 1;
} else {
return superCount;
}
}
/**
* Gets the actual count of contacts and excludes all the headers.
*/
public int getRealCount() {
return super.getCount();
}
private int getRealPosition(int pos) {
if (mShowNumberOfContacts) {
pos--;
}
if ((mMode & MODE_MASK_CREATE_NEW) != 0 && !mSearchMode) {
return pos - 1;
} else if (mSuggestionsCursorCount != 0) {
// When showing suggestions, we have 2 additional list items: the "Suggestions"
// and "All contacts" separators.
if (pos < mSuggestionsCursorCount + 2) {
// We are in the upper partition (Suggestions). Adjusting for the "Suggestions"
// separator.
return pos - 1;
} else {
// We are in the lower partition (All contacts). Adjusting for the size
// of the upper partition plus the two separators.
return pos - mSuggestionsCursorCount - 2;
}
} else if (mFrequentSeparatorPos == ListView.INVALID_POSITION) {
// No separator, identity map
return pos;
} else if (pos <= mFrequentSeparatorPos) {
// Before or at the separator, identity map
return pos;
} else {
// After the separator, remove 1 from the pos to get the real underlying pos
return pos - 1;
}
}
@Override
public Object getItem(int pos) {
if (mSuggestionsCursorCount != 0 && pos <= mSuggestionsCursorCount) {
mSuggestionsCursor.moveToPosition(getRealPosition(pos));
return mSuggestionsCursor;
} else if (isSearchAllContactsItemPosition(pos)){
return null;
} else {
int realPosition = getRealPosition(pos);
if (realPosition < 0) {
return null;
}
return super.getItem(realPosition);
}
}
@Override
public long getItemId(int pos) {
if (mSuggestionsCursorCount != 0 && pos < mSuggestionsCursorCount + 2) {
if (mSuggestionsCursor.moveToPosition(pos - 1)) {
return mSuggestionsCursor.getLong(mRowIDColumn);
} else {
return 0;
}
} else if (isSearchAllContactsItemPosition(pos)) {
return 0;
}
int realPosition = getRealPosition(pos);
if (realPosition < 0) {
return 0;
}
return super.getItemId(realPosition);
}
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
int totalItemCount) {
if (view instanceof PinnedHeaderListView) {
((PinnedHeaderListView)view).configureHeaderView(firstVisibleItem);
}
}
public void onScrollStateChanged(AbsListView view, int scrollState) {
if (mHighlightWhenScrolling) {
if (scrollState != OnScrollListener.SCROLL_STATE_IDLE) {
mHighlightingAnimation.startHighlighting();
} else {
mHighlightingAnimation.stopHighlighting();
}
}
if (scrollState == OnScrollListener.SCROLL_STATE_FLING) {
mPhotoLoader.pause();
} else if (mDisplayPhotos) {
mPhotoLoader.resume();
}
}
/**
* Computes the state of the pinned header. It can be invisible, fully
* visible or partially pushed up out of the view.
*/
public int getPinnedHeaderState(int position) {
if (mIndexer == null || mCursor == null || mCursor.getCount() == 0) {
return PINNED_HEADER_GONE;
}
int realPosition = getRealPosition(position);
if (realPosition < 0) {
return PINNED_HEADER_GONE;
}
// The header should get pushed up if the top item shown
// is the last item in a section for a particular letter.
int section = getSectionForPosition(realPosition);
int nextSectionPosition = getPositionForSection(section + 1);
if (nextSectionPosition != -1 && realPosition == nextSectionPosition - 1) {
return PINNED_HEADER_PUSHED_UP;
}
return PINNED_HEADER_VISIBLE;
}
/**
* Configures the pinned header by setting the appropriate text label
* and also adjusting color if necessary. The color needs to be
* adjusted when the pinned header is being pushed up from the view.
*/
public void configurePinnedHeader(View header, int position, int alpha) {
PinnedHeaderCache cache = (PinnedHeaderCache)header.getTag();
if (cache == null) {
cache = new PinnedHeaderCache();
cache.titleView = (TextView)header.findViewById(R.id.header_text);
cache.textColor = cache.titleView.getTextColors();
cache.background = header.getBackground();
header.setTag(cache);
}
int realPosition = getRealPosition(position);
int section = getSectionForPosition(realPosition);
String title = (String)mIndexer.getSections()[section];
cache.titleView.setText(title);
if (alpha == 255) {
// Opaque: use the default background, and the original text color
header.setBackgroundDrawable(cache.background);
cache.titleView.setTextColor(cache.textColor);
} else {
// Faded: use a solid color approximation of the background, and
// a translucent text color
header.setBackgroundColor(Color.rgb(
Color.red(mPinnedHeaderBackgroundColor) * alpha / 255,
Color.green(mPinnedHeaderBackgroundColor) * alpha / 255,
Color.blue(mPinnedHeaderBackgroundColor) * alpha / 255));
int textColor = cache.textColor.getDefaultColor();
cache.titleView.setTextColor(Color.argb(alpha,
Color.red(textColor), Color.green(textColor), Color.blue(textColor)));
}
}
}
private ContactsPreferences.ChangeListener mPreferencesChangeListener =
new ContactsPreferences.ChangeListener() {
@Override
public void onChange() {
// When returning from DisplayOptions, onActivityResult ensures that we reload the list,
// so we do not have to do anything here. However, ContactsPreferences requires a change
// listener, otherwise it would not reload its settings.
}
};
}